Skip to content

Commit 86a005e

Browse files
committed
Polish documentation tests and fix method call
- Fixed incorrect network.to_graph() method call in basic.md - Replaced basic Clos fabric test with proper hierarchical blueprint tests - Renamed test_api_docs.py to test_doc_generation.py for clarity - All documentation tests now correspond exactly to actual examples - All 421 tests passing with 93.53% coverage
1 parent 4093588 commit 86a005e

8 files changed

Lines changed: 1158 additions & 11 deletions

File tree

docs/examples/basic.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,34 @@ print(f"Equal-balanced flow: {max_flow_shortest_balanced}")
119119

120120
Note that `EQUAL_BALANCED` flow placement is only applicable when calculating MaxFlow on shortest paths.
121121

122+
### Advanced Analysis: Sensitivity Analysis
123+
124+
For deeper network analysis, you can use the low-level graph algorithms to perform sensitivity analysis and identify bottleneck edges:
125+
126+
```python
127+
from ngraph.lib.algorithms.max_flow import calc_max_flow, saturated_edges, run_sensitivity
128+
129+
# Get the underlying graph for low-level analysis
130+
graph = network.to_strict_multidigraph()
131+
132+
# Identify bottleneck (saturated) edges
133+
bottlenecks = saturated_edges(graph, "A", "C")
134+
print(f"Bottleneck edges: {bottlenecks}")
135+
136+
# Perform sensitivity analysis - test increasing capacity by 1 unit
137+
sensitivity_increase = run_sensitivity(graph, "A", "C", change_amount=1.0)
138+
print(f"Sensitivity to capacity increases: {sensitivity_increase}")
139+
140+
# Test sensitivity to capacity decreases
141+
sensitivity_decrease = run_sensitivity(graph, "A", "C", change_amount=-1.0)
142+
print(f"Sensitivity to capacity decreases: {sensitivity_decrease}")
143+
```
144+
145+
This analysis helps identify:
146+
- **Bottleneck edges**: Links that are fully utilized and limit overall flow
147+
- **High-impact upgrades**: Which capacity increases provide the most benefit
148+
- **Vulnerability assessment**: How flow decreases when links are degraded
149+
122150
## Next Steps
123151

124152
- **[Tutorial](../getting-started/tutorial.md)** - Build complete network scenarios

docs/reference/api-full.md

Lines changed: 63 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ For a curated, example-driven API guide, see **[api.md](api.md)**.
1010
> - **[CLI Reference](cli.md)** - Command-line interface
1111
> - **[DSL Reference](dsl.md)** - YAML syntax guide
1212
13-
**Generated from source code on:** June 08, 2025 at 03:05 UTC
13+
**Generated from source code on:** June 09, 2025 at 00:40 UTC
1414

1515
---
1616

@@ -950,7 +950,7 @@ Raises:
950950

951951
## ngraph.lib.algorithms.max_flow
952952

953-
### calc_max_flow(graph: ngraph.lib.graph.StrictMultiDiGraph, src_node: Hashable, dst_node: Hashable, flow_placement: ngraph.lib.algorithms.base.FlowPlacement = <FlowPlacement.PROPORTIONAL: 1>, shortest_path: bool = False, reset_flow_graph: bool = False, capacity_attr: str = 'capacity', flow_attr: str = 'flow', flows_attr: str = 'flows', copy_graph: bool = True) -> float
953+
### calc_max_flow(graph: ngraph.lib.graph.StrictMultiDiGraph, src_node: Hashable, dst_node: Hashable, *, return_summary: bool = False, return_graph: bool = False, flow_placement: ngraph.lib.algorithms.base.FlowPlacement = <FlowPlacement.PROPORTIONAL: 1>, shortest_path: bool = False, reset_flow_graph: bool = False, capacity_attr: str = 'capacity', flow_attr: str = 'flow', flows_attr: str = 'flows', copy_graph: bool = True) -> Union[float, tuple]
954954

955955
Compute the maximum flow between two nodes in a directed multi-graph,
956956
using an iterative shortest-path augmentation approach.
@@ -972,6 +972,12 @@ Args:
972972
The source node for flow.
973973
dst_node (NodeID):
974974
The destination node for flow.
975+
return_summary (bool):
976+
If True, return a FlowSummary with detailed flow analytics.
977+
Defaults to False.
978+
return_graph (bool):
979+
If True, return the mutated flow graph along with other results.
980+
Defaults to False.
975981
flow_placement (FlowPlacement):
976982
Determines how flow is split among parallel edges of equal cost.
977983
Defaults to ``FlowPlacement.PROPORTIONAL``.
@@ -992,24 +998,74 @@ Args:
992998
Defaults to True.
993999

9941000
Returns:
995-
float:
996-
The total flow placed between ``src_node`` and ``dst_node``. If ``shortest_path=True``,
997-
this is just the flow from a single augmentation.
1001+
Union[float, tuple]:
1002+
- If neither return_summary nor return_graph: float (total flow)
1003+
- If return_summary only: tuple[float, FlowSummary]
1004+
- If both flags: tuple[float, FlowSummary, StrictMultiDiGraph]
9981005

9991006
Notes:
10001007
- For large graphs or performance-critical scenarios, consider specialized max-flow
10011008
algorithms (e.g., Dinic, Edmond-Karp) for better scaling.
1009+
- When using return_summary or return_graph, callers must unpack the returned tuple.
10021010

10031011
Examples:
10041012
>>> g = StrictMultiDiGraph()
10051013
>>> g.add_node('A')
10061014
>>> g.add_node('B')
10071015
>>> g.add_node('C')
1008-
>>> _ = g.add_edge('A', 'B', capacity=10.0, flow=0.0, flows={})
1009-
>>> _ = g.add_edge('B', 'C', capacity=5.0, flow=0.0, flows={})
1016+
>>> _ = g.add_edge('A', 'B', capacity=10.0, flow=0.0, flows={}, cost=1)
1017+
>>> _ = g.add_edge('B', 'C', capacity=5.0, flow=0.0, flows={}, cost=1)
1018+
>>>
1019+
>>> # Basic usage (scalar return)
10101020
>>> max_flow_value = calc_max_flow(g, 'A', 'C')
10111021
>>> print(max_flow_value)
10121022
5.0
1023+
>>>
1024+
>>> # With flow summary analytics
1025+
>>> flow, summary = calc_max_flow(g, 'A', 'C', return_summary=True)
1026+
>>> print(f"Min-cut edges: {summary.min_cut}")
1027+
>>>
1028+
>>> # With both summary and mutated graph
1029+
>>> flow, summary, flow_graph = calc_max_flow(
1030+
... g, 'A', 'C', return_summary=True, return_graph=True
1031+
... )
1032+
>>> # flow_graph contains the flow assignments
1033+
1034+
### run_sensitivity(graph: ngraph.lib.graph.StrictMultiDiGraph, src_node: Hashable, dst_node: Hashable, *, capacity_attr: str = 'capacity', flow_attr: str = 'flow', change_amount: float = 1.0, **kwargs) -> dict[tuple, float]
1035+
1036+
Perform sensitivity analysis to identify high-impact capacity changes.
1037+
1038+
Tests changing each saturated edge capacity by change_amount and measures
1039+
the resulting change in total flow. Positive values increase capacity,
1040+
negative values decrease capacity (with validation to prevent negative capacities).
1041+
1042+
Args:
1043+
graph: The graph to analyze
1044+
src_node: Source node
1045+
dst_node: Destination node
1046+
capacity_attr: Name of capacity attribute
1047+
flow_attr: Name of flow attribute
1048+
change_amount: Amount to change capacity for testing (positive=increase, negative=decrease)
1049+
**kwargs: Additional arguments passed to calc_max_flow
1050+
1051+
Returns:
1052+
Dictionary mapping edge tuples to flow change when capacity is modified
1053+
1054+
### saturated_edges(graph: ngraph.lib.graph.StrictMultiDiGraph, src_node: Hashable, dst_node: Hashable, *, capacity_attr: str = 'capacity', flow_attr: str = 'flow', tolerance: float = 1e-10, **kwargs) -> list[tuple]
1055+
1056+
Identify saturated (bottleneck) edges in the max flow solution.
1057+
1058+
Args:
1059+
graph: The graph to analyze
1060+
src_node: Source node
1061+
dst_node: Destination node
1062+
capacity_attr: Name of capacity attribute
1063+
flow_attr: Name of flow attribute
1064+
tolerance: Tolerance for considering an edge saturated
1065+
**kwargs: Additional arguments passed to calc_max_flow
1066+
1067+
Returns:
1068+
List of saturated edge tuples (u, v, k) where residual capacity <= tolerance
10131069

10141070
---
10151071

docs/reference/api.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -213,18 +213,23 @@ Low-level graph analysis functions.
213213
```python
214214
from ngraph.lib.graph import StrictMultiDiGraph
215215
from ngraph.lib.algorithms.spf import spf, ksp
216-
from ngraph.lib.algorithms.max_flow import calc_max_flow
216+
from ngraph.lib.algorithms.max_flow import calc_max_flow, run_sensitivity, saturated_edges
217217

218218
# Direct graph manipulation
219219
graph = StrictMultiDiGraph()
220220
graph.add_node("A")
221-
graph.add_edge("A", "B", capacity=10)
221+
graph.add_node("B")
222+
graph.add_edge("A", "B", capacity=10, cost=1)
222223

223224
# Run shortest path algorithm
224225
costs, pred = spf(graph, "A")
225226

226227
# Calculate maximum flow
227228
max_flow = calc_max_flow(graph, "A", "B")
229+
230+
# Sensitivity analysis - identify bottleneck edges and test capacity changes
231+
saturated = saturated_edges(graph, "A", "B")
232+
sensitivity = run_sensitivity(graph, "A", "B", change_amount=1.0)
228233
```
229234

230235
## Error Handling

ngraph/lib/algorithms/max_flow.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -156,8 +156,8 @@ def calc_max_flow(
156156
>>> g.add_node('A')
157157
>>> g.add_node('B')
158158
>>> g.add_node('C')
159-
>>> _ = g.add_edge('A', 'B', capacity=10.0, flow=0.0, flows={})
160-
>>> _ = g.add_edge('B', 'C', capacity=5.0, flow=0.0, flows={})
159+
>>> g.add_edge('A', 'B', capacity=10.0, flow=0.0, flows={}, cost=1)
160+
>>> g.add_edge('B', 'C', capacity=5.0, flow=0.0, flows={}, cost=1)
161161
>>>
162162
>>> # Basic usage (scalar return)
163163
>>> max_flow_value = calc_max_flow(g, 'A', 'C')

0 commit comments

Comments
 (0)