Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,6 @@
## 2026-05-14 - O(N^2) LINQ Evaluation inside Nested Routing Loop
**Learning:** `BuildCandidateRoutes` runs repeatedly during capacity bidding (inside a `while (true)` loop). In its original form, it performed `context.Demand.Where(...).Select(...)` within the body of an outer `foreach` loop over `context.Supply`. This led to the `Demand` dictionary being repeatedly iterated and filtered on *every* producer iteration, causing severe O(P * C) allocation and evaluation bottlenecks.
**Action:** When a method processes combinations from two collections using nested loops, always pre-compute the filtered/projected sequences (e.g., `ToList()`) *outside* of the outer loop.
## 2026-05-17 - O(N*M) LINQ Evaluation in `BuildCandidateRoutes` Inner Loops
**Learning:** In routing services (`TemporalNetworkSimulationEngine.cs` and `MixedRouting.cs`), `BuildCandidateRoutes` used LINQ filtering (`Where`, `Select`) on dictionaries (`context.Supply`, `context.Demand`) directly within the inner scopes of nested `foreach` loops. Because the outer loop iterated over producing nodes and the inner over consuming nodes, this led to extreme O(P*C) enumerator allocations and redundant LINQ evaluations.
**Action:** Always pre-compute and materialize `.ToList()` filtered source collections outside of outer iteration loops when processing combinatorics (like routes between producers and consumers).
48 changes: 48 additions & 0 deletions patch_bolt.diff
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
--- src/MedWNetworkSim.App/Services/TemporalNetworkSimulationEngine.cs
+++ src/MedWNetworkSim.App/Services/TemporalNetworkSimulationEngine.cs
@@ -1941,10 +1941,16 @@
{
var routes = new List<RouteCandidate>();

- foreach (var producerNodeId in context.Supply.Where(pair => pair.Value > Epsilon).Select(pair => pair.Key))
+ // ⚑ Bolt: Pre-compute active producers and consumers outside the nested loops
+ // to avoid O(P * C) redundant LINQ evaluations and allocations during routing.
+ var activeProducers = context.Supply.Where(pair => pair.Value > Epsilon).Select(pair => pair.Key).ToList();
+ var activeConsumers = context.Demand
+ .Where(pair => pair.Value > Epsilon)
+ .Select(pair => pair.Key)
+ .Where(nodeId => context.MeetingDemandEligibleNodeIds.Contains(nodeId))
+ .ToList();
+
+ foreach (var producerNodeId in activeProducers)
{
- foreach (var consumerNodeId in context.Demand
- .Where(pair => pair.Value > Epsilon)
- .Select(pair => pair.Key)
- .Where(nodeId => context.MeetingDemandEligibleNodeIds.Contains(nodeId)))
+ foreach (var consumerNodeId in activeConsumers)
{
--- src/MedWNetworkSim.App/Services/MixedRouting.cs
+++ src/MedWNetworkSim.App/Services/MixedRouting.cs
@@ -825,10 +825,16 @@
{
var routes = new List<RouteCandidate>();
- foreach (var producerNodeId in context.Supply.Where(pair => pair.Value > Epsilon).Select(pair => pair.Key))
+
+ // ⚑ Bolt: Pre-compute active producers and consumers outside the nested loops
+ // to avoid O(P * C) redundant LINQ evaluations and allocations during routing.
+ var activeProducers = context.Supply.Where(pair => pair.Value > Epsilon).Select(pair => pair.Key).ToList();
+ var activeConsumers = context.Demand
+ .Where(pair => pair.Value > Epsilon)
+ .Select(pair => pair.Key)
+ .Where(nodeId => context.MeetingDemandEligibleNodeIds.Contains(nodeId))
+ .ToList();
+
+ foreach (var producerNodeId in activeProducers)
{
- foreach (var consumerNodeId in context.Demand
- .Where(pair => pair.Value > Epsilon)
- .Select(pair => pair.Key)
- .Where(nodeId => context.MeetingDemandEligibleNodeIds.Contains(nodeId)))
+ foreach (var consumerNodeId in activeConsumers)
{
25 changes: 25 additions & 0 deletions patch_bolt2.diff
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
--- src/MedWNetworkSim.App/Services/MixedRouting.cs
+++ src/MedWNetworkSim.App/Services/MixedRouting.cs
@@ -825,12 +825,18 @@
{
var routes = new List<RouteCandidate>();
- foreach (var producerNodeId in context.Supply.Where(pair => pair.Value > Epsilon).Select(pair => pair.Key))
+
+ // ⚑ Bolt: Pre-compute active producers and consumers outside the nested loops
+ // to avoid O(P * C) redundant LINQ evaluations and allocations during routing.
+ var activeProducers = context.Supply.Where(pair => pair.Value > Epsilon).Select(pair => pair.Key).ToList();
+ var activeConsumers = context.Demand
+ .Where(pair => pair.Value > Epsilon)
+ .Select(pair => pair.Key)
+ .Where(nodeId => context.MeetingDemandEligibleNodeIds.Contains(nodeId))
+ .ToList();
+
+ foreach (var producerNodeId in activeProducers)
{
- foreach (var consumerNodeId in context.Demand
- .Where(pair => pair.Value > Epsilon)
- .Select(pair => pair.Key)
- .Where(nodeId => context.MeetingDemandEligibleNodeIds.Contains(nodeId)))
+ foreach (var consumerNodeId in activeConsumers)
{
if (Comparer.Equals(producerNodeId, consumerNodeId))
17 changes: 12 additions & 5 deletions src/MedWNetworkSim.App/Services/MixedRouting.cs
Original file line number Diff line number Diff line change
Expand Up @@ -825,12 +825,19 @@ public static void ApplyLocalAllocations(RoutingTrafficContext context, NetworkM
private static List<RouteCandidate> BuildCandidateRoutes(RoutingTrafficContext context, NetworkState state, AllocationContext allocationContext)
{
var routes = new List<RouteCandidate>();
foreach (var producerNodeId in context.Supply.Where(pair => pair.Value > Epsilon).Select(pair => pair.Key))

// ⚑ Bolt: Pre-compute active producers and consumers outside the nested loops
// to avoid O(P * C) redundant LINQ evaluations and allocations during routing.
var activeProducers = context.Supply.Where(pair => pair.Value > Epsilon).Select(pair => pair.Key).ToList();
var activeConsumers = context.Demand
.Where(pair => pair.Value > Epsilon)
.Select(pair => pair.Key)
.Where(nodeId => context.MeetingDemandEligibleNodeIds.Contains(nodeId))
.ToList();

foreach (var producerNodeId in activeProducers)
{
foreach (var consumerNodeId in context.Demand
.Where(pair => pair.Value > Epsilon)
.Select(pair => pair.Key)
.Where(nodeId => context.MeetingDemandEligibleNodeIds.Contains(nodeId)))
foreach (var consumerNodeId in activeConsumers)
{
if (Comparer.Equals(producerNodeId, consumerNodeId))
{
Expand Down
16 changes: 11 additions & 5 deletions src/MedWNetworkSim.App/Services/TemporalNetworkSimulationEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1941,12 +1941,18 @@ private static List<RouteCandidate> BuildCandidateRoutes(
{
var routes = new List<RouteCandidate>();

foreach (var producerNodeId in context.Supply.Where(pair => pair.Value > Epsilon).Select(pair => pair.Key))
// ⚑ Bolt: Pre-compute active producers and consumers outside the nested loops
// to avoid O(P * C) redundant LINQ evaluations and allocations during routing.
var activeProducers = context.Supply.Where(pair => pair.Value > Epsilon).Select(pair => pair.Key).ToList();
var activeConsumers = context.Demand
.Where(pair => pair.Value > Epsilon)
.Select(pair => pair.Key)
.Where(nodeId => context.MeetingDemandEligibleNodeIds.Contains(nodeId))
.ToList();

foreach (var producerNodeId in activeProducers)
{
foreach (var consumerNodeId in context.Demand
.Where(pair => pair.Value > Epsilon)
.Select(pair => pair.Key)
.Where(nodeId => context.MeetingDemandEligibleNodeIds.Contains(nodeId)))
foreach (var consumerNodeId in activeConsumers)
{
if (Comparer.Equals(producerNodeId, consumerNodeId))
{
Expand Down
Loading