Skip to content

Commit 9447eb3

Browse files
committed
Fix capacity envelope calculations in CapacityEnvelope and CapacityEnvelopeAnalysis.
1 parent 39bcccd commit 9447eb3

2 files changed

Lines changed: 47 additions & 15 deletions

File tree

ngraph/results_artifacts.py

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -271,18 +271,29 @@ def from_values(
271271
if not values:
272272
raise ValueError("Cannot create envelope from empty values list")
273273

274-
# Build frequency map
274+
# Single pass to calculate everything efficiently
275275
frequencies = {}
276+
total_sum = 0.0
277+
sum_squares = 0.0
278+
min_capacity = float("inf")
279+
max_capacity = float("-inf")
280+
276281
for value in values:
282+
# Update frequency map
277283
frequencies[value] = frequencies.get(value, 0) + 1
278284

279-
# Calculate statistics
280-
min_capacity = min(values)
281-
max_capacity = max(values)
282-
mean_capacity = sum(values) / len(values)
285+
# Update statistics
286+
total_sum += value
287+
sum_squares += value * value
288+
min_capacity = min(min_capacity, value)
289+
max_capacity = max(max_capacity, value)
290+
291+
# Calculate derived statistics
292+
n = len(values)
293+
mean_capacity = total_sum / n
283294

284-
# Calculate standard deviation
285-
variance = sum((x - mean_capacity) ** 2 for x in values) / len(values)
295+
# Use computational formula for variance: Var(X) = E[X²] - (E[X])²
296+
variance = (sum_squares / n) - (mean_capacity * mean_capacity)
286297
stdev_capacity = variance**0.5
287298

288299
return cls(
@@ -294,7 +305,7 @@ def from_values(
294305
max_capacity=max_capacity,
295306
mean_capacity=mean_capacity,
296307
stdev_capacity=stdev_capacity,
297-
total_samples=len(values),
308+
total_samples=n,
298309
)
299310

300311
def to_dict(self) -> Dict[str, Any]:

ngraph/workflow/capacity_envelope_analysis.py

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -441,14 +441,17 @@ def run(self, scenario: "Scenario") -> None:
441441
if key not in pattern_map:
442442
# Get capacity matrix for this pattern
443443
capacity_matrix = {}
444-
for flow_key, envelope_data in envelopes.items():
444+
for flow_key, _envelope_data in envelopes.items():
445445
# Find capacity value for this pattern's iteration
446446
pattern_iter = pattern["iteration_index"]
447-
if pattern_iter < len(envelope_data["frequencies"]):
447+
flow_tuple = self._parse_flow_key(flow_key)
448+
if flow_tuple in samples and pattern_iter < len(
449+
samples[flow_tuple]
450+
):
448451
# Get capacity value from original samples
449-
capacity_matrix[flow_key] = samples[
450-
self._parse_flow_key(flow_key)
451-
][pattern_iter]
452+
capacity_matrix[flow_key] = samples[flow_tuple][
453+
pattern_iter
454+
]
452455

453456
pattern_map[key] = FailurePatternResult(
454457
excluded_nodes=pattern["excluded_nodes"],
@@ -841,8 +844,14 @@ def _build_capacity_envelopes(
841844
Returns:
842845
Dictionary mapping flow keys to serialized CapacityEnvelope data.
843846
"""
844-
logger.debug(f"Building capacity envelopes from {len(samples)} flow pairs")
847+
start_time = time.time()
848+
total_samples = sum(len(values) for values in samples.values())
849+
logger.info(
850+
f"Building capacity envelopes from {len(samples)} flow pairs with {total_samples:,} total samples"
851+
)
852+
845853
envelopes = {}
854+
processed_flows = 0
846855

847856
for (src_label, dst_label), capacity_values in samples.items():
848857
if not capacity_values:
@@ -863,14 +872,26 @@ def _build_capacity_envelopes(
863872
)
864873
envelopes[flow_key] = envelope.to_dict()
865874

875+
processed_flows += 1
876+
866877
# Detailed logging with statistics
867878
logger.debug(
868879
f"Created frequency-based envelope for {flow_key}: {envelope.total_samples} samples, "
869880
f"min={envelope.min_capacity:.2f}, max={envelope.max_capacity:.2f}, "
870881
f"mean={envelope.mean_capacity:.2f}, unique_values={len(envelope.frequencies)}"
871882
)
872883

873-
logger.debug(f"Successfully created {len(envelopes)} capacity envelopes")
884+
# Progress logging for large numbers of flows
885+
if len(samples) > 100 and processed_flows % max(1, len(samples) // 10) == 0:
886+
elapsed = time.time() - start_time
887+
logger.info(
888+
f"Envelope building progress: {processed_flows}/{len(samples)} flows processed in {elapsed:.1f}s"
889+
)
890+
891+
elapsed_time = time.time() - start_time
892+
logger.info(
893+
f"Generated {len(envelopes)} capacity envelopes in {elapsed_time:.2f} seconds"
894+
)
874895
return envelopes
875896

876897

0 commit comments

Comments
 (0)