Skip to content

Commit ae885a2

Browse files
authored
Capacity probe (#50)
1 parent 9b0a124 commit ae885a2

29 files changed

+942
-546
lines changed

README.md

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -118,10 +118,10 @@ Note: Don't forget to use a virtual environment (e.g., `venv`) to avoid conflict
118118
g.add_node("A")
119119
g.add_node("B")
120120
g.add_node("C")
121-
g.add_edge("A", "B", metric=1, capacity=1)
122-
g.add_edge("A", "B", metric=1, capacity=1)
123-
g.add_edge("B", "C", metric=1, capacity=2)
124-
g.add_edge("A", "C", metric=2, capacity=3)
121+
g.add_edge("A", "B", cost=1, capacity=1)
122+
g.add_edge("A", "B", cost=1, capacity=1)
123+
g.add_edge("B", "C", cost=1, capacity=2)
124+
g.add_edge("A", "C", cost=2, capacity=3)
125125
126126
# Calculate MaxFlow between the source and destination nodes
127127
max_flow = calc_max_flow(g, "A", "C")
@@ -136,7 +136,7 @@ Note: Don't forget to use a virtual environment (e.g., `venv`) to avoid conflict
136136
"""
137137
Tests max flow calculations on a graph with parallel edges.
138138
139-
Graph topology (metrics/capacities):
139+
Graph topology (costs/capacities):
140140
141141
[1,1] & [1,2] [1,1] & [1,2]
142142
A ──────────────────► B ─────────────► C
@@ -145,10 +145,10 @@ Note: Don't forget to use a virtual environment (e.g., `venv`) to avoid conflict
145145
└───────────────────► D ───────────────┘
146146
147147
Edges:
148-
- A→B: two parallel edges with (metric=1, capacity=1) and (metric=1, capacity=2)
149-
- B→C: two parallel edges with (metric=1, capacity=1) and (metric=1, capacity=2)
150-
- A→D: (metric=2, capacity=3)
151-
- D→C: (metric=2, capacity=3)
148+
- A→B: two parallel edges with (cost=1, capacity=1) and (cost=1, capacity=2)
149+
- B→C: two parallel edges with (cost=1, capacity=1) and (cost=1, capacity=2)
150+
- A→D: (cost=2, capacity=3)
151+
- D→C: (cost=2, capacity=3)
152152
153153
The test computes:
154154
- The true maximum flow (expected flow: 6.0)
@@ -164,13 +164,13 @@ Note: Don't forget to use a virtual environment (e.g., `venv`) to avoid conflict
164164
g.add_node(node)
165165
166166
# Create parallel edges between A→B and B→C
167-
g.add_edge("A", "B", key=0, metric=1, capacity=1)
168-
g.add_edge("A", "B", key=1, metric=1, capacity=2)
169-
g.add_edge("B", "C", key=2, metric=1, capacity=1)
170-
g.add_edge("B", "C", key=3, metric=1, capacity=2)
167+
g.add_edge("A", "B", key=0, cost=1, capacity=1)
168+
g.add_edge("A", "B", key=1, cost=1, capacity=2)
169+
g.add_edge("B", "C", key=2, cost=1, capacity=1)
170+
g.add_edge("B", "C", key=3, cost=1, capacity=2)
171171
# Create an alternative path A→D→C
172-
g.add_edge("A", "D", key=4, metric=2, capacity=3)
173-
g.add_edge("D", "C", key=5, metric=2, capacity=3)
172+
g.add_edge("A", "D", key=4, cost=2, capacity=3)
173+
g.add_edge("D", "C", key=5, cost=2, capacity=3)
174174
175175
# 1. The true maximum flow
176176
max_flow_prop = calc_max_flow(g, "A", "C")
@@ -193,7 +193,7 @@ Note: Don't forget to use a virtual environment (e.g., `venv`) to avoid conflict
193193
"""
194194
Demonstrates traffic engineering by placing two demands on a network.
195195
196-
Graph topology (metrics/capacities):
196+
Graph topology (costs/capacities):
197197
198198
[15]
199199
A ─────── B
@@ -219,12 +219,12 @@ Note: Don't forget to use a virtual environment (e.g., `venv`) to avoid conflict
219219
g.add_node(node)
220220
221221
# Create bidirectional edges with distinct labels (for clarity).
222-
g.add_edge("A", "B", key=0, metric=1, capacity=15, label="1")
223-
g.add_edge("B", "A", key=1, metric=1, capacity=15, label="1")
224-
g.add_edge("B", "C", key=2, metric=1, capacity=15, label="2")
225-
g.add_edge("C", "B", key=3, metric=1, capacity=15, label="2")
226-
g.add_edge("A", "C", key=4, metric=1, capacity=5, label="3")
227-
g.add_edge("C", "A", key=5, metric=1, capacity=5, label="3")
222+
g.add_edge("A", "B", key=0, cost=1, capacity=15, label="1")
223+
g.add_edge("B", "A", key=1, cost=1, capacity=15, label="1")
224+
g.add_edge("B", "C", key=2, cost=1, capacity=15, label="2")
225+
g.add_edge("C", "B", key=3, cost=1, capacity=15, label="2")
226+
g.add_edge("A", "C", key=4, cost=1, capacity=5, label="3")
227+
g.add_edge("C", "A", key=5, cost=1, capacity=5, label="3")
228228
229229
# Initialize flow-related structures (e.g., to track placed flows in the graph).
230230
flow_graph = init_flow_graph(g)

ngraph/blueprints.py

Lines changed: 2 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -231,8 +231,8 @@ def _expand_adjacency_pattern(
231231
wrap-around if one side is an integer multiple of the other.
232232
Also skips self-loops.
233233
"""
234-
source_nodes = _find_nodes_by_path(ctx.network, source_path)
235-
target_nodes = _find_nodes_by_path(ctx.network, target_path)
234+
source_nodes = ctx.network.select_nodes_by_path(source_path)
235+
target_nodes = ctx.network.select_nodes_by_path(target_path)
236236

237237
if not source_nodes or not target_nodes:
238238
return
@@ -338,36 +338,6 @@ def _join_paths(parent_path: str, rel_path: str) -> str:
338338
return rel_path
339339

340340

341-
def _find_nodes_by_path(net: Network, path: str) -> List[Node]:
342-
"""
343-
Returns all nodes whose name is exactly 'path' or begins with 'path/'.
344-
If none are found, tries 'path-' as a fallback prefix.
345-
If still none are found, tries partial prefix "path" => "pathX".
346-
347-
Examples:
348-
path="SEA/clos_instance/spine" might match "SEA/clos_instance/spine/myspine-1"
349-
path="S" might match "S1", "S2" if we resort to partial prefix logic.
350-
"""
351-
# 1) Exact or slash-based
352-
result = [
353-
n for n in net.nodes.values() if n.name == path or n.name.startswith(f"{path}/")
354-
]
355-
if result:
356-
return result
357-
358-
# 2) Fallback: path-
359-
result = [n for n in net.nodes.values() if n.name.startswith(f"{path}-")]
360-
if result:
361-
return result
362-
363-
# 3) Partial
364-
partial = []
365-
for n in net.nodes.values():
366-
if n.name.startswith(path) and n.name != path:
367-
partial.append(n)
368-
return partial
369-
370-
371341
def _process_direct_nodes(net: Network, network_data: Dict[str, Any]) -> None:
372342
"""Processes direct node definitions (network_data["nodes"])."""
373343
for node_name, node_attrs in network_data.get("nodes", {}).items():

ngraph/lib/algorithms/base.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,18 +40,18 @@ class EdgeSelect(IntEnum):
4040
for path-finding between a node and its neighbor(s).
4141
"""
4242

43-
#: Return all edges matching the minimum metric among the candidate edges.
43+
#: Return all edges matching the minimum cost among the candidate edges.
4444
ALL_MIN_COST = 1
45-
#: Return all edges matching the minimum metric among edges with remaining capacity.
45+
#: Return all edges matching the minimum cost among edges with remaining capacity.
4646
ALL_MIN_COST_WITH_CAP_REMAINING = 2
47-
#: Return all edges that have remaining capacity, ignoring metric except for returning min_cost.
47+
#: Return all edges that have remaining capacity, ignoring cost except for returning min_cost.
4848
ALL_ANY_COST_WITH_CAP_REMAINING = 3
49-
#: Return exactly one edge (the single lowest metric).
49+
#: Return exactly one edge (the single lowest cost).
5050
SINGLE_MIN_COST = 4
51-
#: Return exactly one edge, the lowest-metric edge with remaining capacity.
51+
#: Return exactly one edge, the lowest-cost edge with remaining capacity.
5252
SINGLE_MIN_COST_WITH_CAP_REMAINING = 5
53-
#: Return exactly one edge factoring both metric and load:
54-
#: cost = (metric * 100) + round(flow / capacity * 10).
53+
#: Return exactly one edge factoring both cost and load:
54+
#: cost = (cost * 100) + round(flow / capacity * 10).
5555
SINGLE_MIN_COST_WITH_CAP_REMAINING_LOAD_FACTORED = 6
5656
#: Use a user-defined function for edge selection logic.
5757
USER_DEFINED = 99

ngraph/lib/algorithms/edge_select.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ def edge_select_fabric(
2424
] = None,
2525
excluded_edges: Optional[Set[EdgeID]] = None,
2626
excluded_nodes: Optional[Set[NodeID]] = None,
27-
cost_attr: str = "metric",
27+
cost_attr: str = "cost",
2828
capacity_attr: str = "capacity",
2929
flow_attr: str = "flow",
3030
) -> Callable[
@@ -73,7 +73,7 @@ def get_all_min_cost_edges(
7373
ignored_edges: Optional[Set[EdgeID]] = None,
7474
ignored_nodes: Optional[Set[NodeID]] = None,
7575
) -> Tuple[Cost, List[EdgeID]]:
76-
"""Return all edges with the minimal metric among those available."""
76+
"""Return all edges with the minimal cost among those available."""
7777
if ignored_nodes and dst_node in ignored_nodes:
7878
return float("inf"), []
7979

@@ -101,7 +101,7 @@ def get_single_min_cost_edge(
101101
ignored_edges: Optional[Set[EdgeID]] = None,
102102
ignored_nodes: Optional[Set[NodeID]] = None,
103103
) -> Tuple[Cost, List[EdgeID]]:
104-
"""Return exactly one edge: the single lowest-metric edge."""
104+
"""Return exactly one edge: the single lowest-cost edge."""
105105
if ignored_nodes and dst_node in ignored_nodes:
106106
return float("inf"), []
107107

@@ -128,7 +128,7 @@ def get_all_edges_with_cap_remaining(
128128
) -> Tuple[Cost, List[EdgeID]]:
129129
"""
130130
Return all edges that have remaining capacity >= min_cap, ignoring
131-
their metric except for reporting the minimal one found.
131+
their cost except for reporting the minimal one found.
132132
"""
133133
if ignored_nodes and dst_node in ignored_nodes:
134134
return float("inf"), []
@@ -191,7 +191,7 @@ def get_single_min_cost_edge_with_cap_remaining(
191191
ignored_nodes: Optional[Set[NodeID]] = None,
192192
) -> Tuple[Cost, List[EdgeID]]:
193193
"""
194-
Return exactly one edge with the minimal metric among those with
194+
Return exactly one edge with the minimal cost among those with
195195
remaining capacity >= min_cap.
196196
"""
197197
if ignored_nodes and dst_node in ignored_nodes:
@@ -224,7 +224,7 @@ def get_single_min_cost_edge_with_cap_remaining_load_factored(
224224
"""
225225
Return exactly one edge, factoring both 'cost_attr' and load level
226226
into a combined cost:
227-
combined_cost = (metric * 100) + round((flow / capacity) * 10)
227+
combined_cost = (cost * 100) + round((flow / capacity) * 10)
228228
Only edges with remaining capacity >= min_cap are considered.
229229
"""
230230
if ignored_nodes and dst_node in ignored_nodes:

ngraph/lib/path.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ def get_sub_path(
159159
self,
160160
dst_node: NodeID,
161161
graph: StrictMultiDiGraph,
162-
cost_attr: str = "metric",
162+
cost_attr: str = "cost",
163163
) -> Path:
164164
"""
165165
Create a sub-path ending at the specified destination node, recalculating the cost.
@@ -172,7 +172,7 @@ def get_sub_path(
172172
Args:
173173
dst_node: The node at which to truncate the path.
174174
graph: The graph containing edge attributes.
175-
cost_attr: The edge attribute name to use for cost (default is "metric").
175+
cost_attr: The edge attribute name to use for cost (default is "cost").
176176
177177
Returns:
178178
A new Path instance representing the sub-path from the original source to `dst_node`.

ngraph/lib/path_bundle.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ def __init__(
4848
...
4949
}
5050
Typically generated by a shortest-path or multi-path algorithm.
51-
cost: The total path cost (e.g. distance, metric) of all paths in the bundle.
51+
cost: The total path cost (e.g. distance, cost) of all paths in the bundle.
5252
"""
5353
self.src_node: NodeID = src_node
5454
self.dst_node: NodeID = dst_node
@@ -150,7 +150,7 @@ def from_path(
150150
resolve_edges: bool = False,
151151
graph: Optional[StrictMultiDiGraph] = None,
152152
edge_select: Optional[EdgeSelect] = None,
153-
cost_attr: str = "metric",
153+
cost_attr: str = "cost",
154154
capacity_attr: str = "capacity",
155155
) -> PathBundle:
156156
"""
@@ -162,7 +162,7 @@ def from_path(
162162
between each node pair via the provided `edge_select`.
163163
graph: The graph used for edge resolution (required if `resolve_edges=True`).
164164
edge_select: The selection criterion for picking edges if `resolve_edges=True`.
165-
cost_attr: The attribute name on edges representing cost (e.g., 'metric').
165+
cost_attr: The attribute name on edges representing cost (e.g., 'cost').
166166
capacity_attr: The attribute name on edges representing capacity.
167167
168168
Returns:
@@ -268,7 +268,7 @@ def get_sub_path_bundle(
268268
self,
269269
new_dst_node: NodeID,
270270
graph: StrictMultiDiGraph,
271-
cost_attr: str = "metric",
271+
cost_attr: str = "cost",
272272
) -> PathBundle:
273273
"""
274274
Create a sub-bundle ending at `new_dst_node` (which must appear in this bundle).

0 commit comments

Comments
 (0)