Correctness, performance, and documentation overhaul - #108
Conversation
Multi-round review program over the analysis engine, model layer, DSL, and documentation. Findings were verified by runtime probes and, for the higher-risk ones, adversarially re-checked before being applied. Correctness (probe-confirmed, each with regression tests): - Weighted failure sampling computed Efraimidis-Spirakis keys as u**(1/w), which underflows for small weights: with per-hour failure rates (~1e-5) one entity was selected 97% of the time, biased by entity id rather than weight. Keys are now computed in the log domain. - Demand placement could report more flow than the network carries. An SPF-cached demand and a FlowPolicy-based demand sharing (source, destination, priority) produced colliding FlowIndex values whose flows silently merged; a plain-YAML scenario reported 15 units placed across a 10-unit min cut. - Demand expansion could fuse distinct demands' pseudo endpoints, because composed ids concatenate demand ids and group labels and both may contain "|". Duplicate ids and colliding pseudo endpoints are now rejected. - A risk group sharing a name with a node was misclassified under failure: the node was excluded and the group's members were not. - expand_groups expanded differently depending on whether the failure came from an entity rule or a risk_group rule, and did not reach members of nested groups. - Seeded Monte Carlo was not reproducible across identical scenario rebuilds: uuid-suffixed link ids sorted differently per rebuild, remapping seeded draws onto different parallel links. Link ids are now deterministic. - Boundary guards replace silent corruption: link capacities at or above the internal pseudo-edge capacity, accumulated path costs that overflow the core's int64 arithmetic, bound-context/argument mismatches, scalar in/not_in values, and unhashable group_by values now raise instead of producing wrong numbers. Performance: - Weighted-choice selection precomputes per-rule weight splits and selects via a heap (~4x faster on 100k-candidate pools). - MaximumSupportedDemand probes share one SPF DAG cache, cutting SPF runs from probes x sources to sources. - Membership rules flatten entity attributes once instead of once per rule. Structure: - Selector evaluation moved to ngraph.model.selectors; the DSL package keeps YAML-facing parsing and re-exports the moved names. - FailureManager pre-builds per-run inputs through a prepare_inputs hook on analysis functions, replacing kwarg-name sniffing in the engine. - Repeated logic consolidated behind shared helpers; dead and production-orphaned code removed; the package layering policy is documented. Documentation: - Every curated document was verified by execution: all Python examples, 46 DSL YAML blocks, and the bundled scenarios run against this tree, and documented outputs match. The design reference's algorithm sections were checked line-by-line against the NetGraph-Core C++ sources, which corrected unsupported complexity bounds and a mischaracterization of reverse residual arcs. - A prose pass tightened docstrings and docs, followed by independent verification that restored facts the tightening had dropped. Suite grows to 1186 tests; coverage 91.65%. make check-ci, make validate, and make docs are green. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9ead3875f6
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| cached = flat_maps.get(scope) | ||
| if cached is not None: | ||
| return cached |
There was a problem hiding this comment.
Refresh flattened attributes after membership mutations
When multiple membership rules use the same scope, this cache preserves the attributes from before the first rule mutated the entities. For example, if group A adds itself to nodes and a later group B matches risk_groups contains A, B sees the cached pre-A risk_groups values and silently matches nothing; risk-group-scoped rules similarly cannot observe children appended by earlier rules. Invalidate or update the relevant flattened map after each mutation rather than reusing it for the entire resolution pass.
Useful? React with 👍 / 👎.
| for child in group.children: | ||
| child_nodes, child_links = expand_group(child) |
There was a problem hiding this comment.
Resolve registered descendants when expanding risk groups
When YAML defines mid and leaf as top-level groups and references mid by name as a child of top, the parser creates a child stub object rather than linking to network.risk_groups["mid"]. Recursing directly into that stub therefore never sees mid's registered leaf child, so failing top excludes direct mid members but silently leaves grandchild members and links active. Resolve a child through the registered group with the same name when available, while retaining inline-only descendants.
Useful? React with 👍 / 👎.
| if context is not None: | ||
| ctx = context | ||
| if ctx.is_bound and ( |
There was a problem hiding this comment.
Dispatch selectors through explicitly supplied unbound contexts
When a caller supplies the newly documented unbound context form, such as context=analyze(network), this branch accepts it because is_bound is false, but the later ctx.max_flow()/ctx.max_flow_detailed() call omits source and sink and raises Unbound context: source and sink are required. sensitivity_analysis has the same path. Pass the analysis selectors into calls on unbound contexts, or document and reject them as unsupported.
Useful? React with 👍 / 👎.
What this is
A multi-round review of the analysis engine, model layer, DSL, and documentation. Every finding was verified with a runtime probe before being applied, and the higher-risk fixes were re-checked by independent reviewers instructed to refute them — which caught several fixes that were initially incomplete (see How this was verified).
The
[Unreleased]section ofCHANGELOG.mdis the authoritative summary. This description highlights what matters for review.Correctness fixes
Each is probe-confirmed and covered by a regression test.
u**(1/w)underflows for small weights. With per-hour failure rates (~1e-5), one entity was chosen 97% of the time, ordered by entity id rather than weight — silently inverting the intended bias.(source, destination, priority)produced colliding flow ids whose flows merged. A plain-YAML scenario reported 15 units placed across a 10-unit min cut.|, so distinct demands could share a pseudo endpoint and route through a zero-cost bypass.apply_failures_typedexpand_groupsrisk_grouprule, and never reached members of nested groups.in/not_in, and unhashablegroup_byall produced wrong numbers silently; they now raise.Performance
MaximumSupportedDemandprobes share one SPF DAG cache: SPF runs drop from probes x sources to sources.Structure
ngraph.model.selectors;ngraph.dsl.selectorskeeps YAML-facing parsing and re-exports the moved names, so the model layer no longer depends on the DSL package at import or runtime.FailureManagerpre-builds per-run inputs via aprepare_inputshook on analysis functions, replacing kwarg-name sniffing inside the engine. Custom analysis functions opt in by setting the attribute.Breaking changes
MaxFlowResult.min_cutreturns a true minimum cut (capacity equals max flow) rather than all saturated edges. Saturated-edge analysis remains available viasensitivity().from_networkx(bidirectional=...)defaults toNoneand is inferred from graph type; undirected inputs now produce antiparallel arc pairs. Passbidirectional=Falsefor the old behavior.expand_childrenremoved — cascading to children is inherent and always applied. Scenarios using the key must drop it.flow_policyremoved from the scenario schema; use a preset name string.Documentation
Every curated document was verified by execution, not by reading: all Python examples, 46 DSL YAML blocks, the bundled scenarios, and 22 CLI invocations run against this branch, and documented outputs match actual output.
The design reference's algorithm sections were checked line-by-line against the NetGraph-Core C++ sources. That corrected unsupported complexity bounds and a mischaracterization of reverse residual arcs (they serve min-cut reachability, not cross-tier flow cancellation). A final prose pass tightened docstrings and docs, followed by independent verification that restored facts the tightening had dropped.
How this was verified
Findings came from parallel reviewers, then went through adversarial confirmation before being applied — reviewers were told to refute each fix. That step earned its keep: it caught that the
expand_groupsfix missed depth-3 hierarchies, that deterministic link ids collide when node names contain\|, that composed demand ids could still collide across demands, and that a cost bound belonged at 2^62 rather than 2^63. All four were repaired before this branch was cut.Gates on this branch:
make check-cigreen (1186 tests, coverage 91.65%),make validategreen,make docsregenerated.Reviewing this
It is 149 files, so a suggested order:
CHANGELOG.md— the full picture in one place.ngraph/model/failure/policy.py,ngraph/analysis/placement.py,ngraph/analysis/demand.py— the three highest-impact correctness fixes.ngraph/model/selectors/(new package) andngraph/analysis/failure_manager.py— the structural changes.docs/reference/design.md— the C++-verified algorithm descriptions.docs/reference/api-full.mdis generated bymake docs; review the docstrings rather than that file.