Skip to content
Merged
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
44 changes: 22 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,10 +118,10 @@ Note: Don't forget to use a virtual environment (e.g., `venv`) to avoid conflict
g.add_node("A")
g.add_node("B")
g.add_node("C")
g.add_edge("A", "B", metric=1, capacity=1)
g.add_edge("A", "B", metric=1, capacity=1)
g.add_edge("B", "C", metric=1, capacity=2)
g.add_edge("A", "C", metric=2, capacity=3)
g.add_edge("A", "B", cost=1, capacity=1)
g.add_edge("A", "B", cost=1, capacity=1)
g.add_edge("B", "C", cost=1, capacity=2)
g.add_edge("A", "C", cost=2, capacity=3)

# Calculate MaxFlow between the source and destination nodes
max_flow = calc_max_flow(g, "A", "C")
Expand All @@ -136,7 +136,7 @@ Note: Don't forget to use a virtual environment (e.g., `venv`) to avoid conflict
"""
Tests max flow calculations on a graph with parallel edges.

Graph topology (metrics/capacities):
Graph topology (costs/capacities):

[1,1] & [1,2] [1,1] & [1,2]
A ──────────────────► B ─────────────► C
Expand All @@ -145,10 +145,10 @@ Note: Don't forget to use a virtual environment (e.g., `venv`) to avoid conflict
└───────────────────► D ───────────────┘

Edges:
- A→B: two parallel edges with (metric=1, capacity=1) and (metric=1, capacity=2)
- B→C: two parallel edges with (metric=1, capacity=1) and (metric=1, capacity=2)
- A→D: (metric=2, capacity=3)
- D→C: (metric=2, capacity=3)
- A→B: two parallel edges with (cost=1, capacity=1) and (cost=1, capacity=2)
- B→C: two parallel edges with (cost=1, capacity=1) and (cost=1, capacity=2)
- A→D: (cost=2, capacity=3)
- D→C: (cost=2, capacity=3)

The test computes:
- The true maximum flow (expected flow: 6.0)
Expand All @@ -164,13 +164,13 @@ Note: Don't forget to use a virtual environment (e.g., `venv`) to avoid conflict
g.add_node(node)

# Create parallel edges between A→B and B→C
g.add_edge("A", "B", key=0, metric=1, capacity=1)
g.add_edge("A", "B", key=1, metric=1, capacity=2)
g.add_edge("B", "C", key=2, metric=1, capacity=1)
g.add_edge("B", "C", key=3, metric=1, capacity=2)
g.add_edge("A", "B", key=0, cost=1, capacity=1)
g.add_edge("A", "B", key=1, cost=1, capacity=2)
g.add_edge("B", "C", key=2, cost=1, capacity=1)
g.add_edge("B", "C", key=3, cost=1, capacity=2)
# Create an alternative path A→D→C
g.add_edge("A", "D", key=4, metric=2, capacity=3)
g.add_edge("D", "C", key=5, metric=2, capacity=3)
g.add_edge("A", "D", key=4, cost=2, capacity=3)
g.add_edge("D", "C", key=5, cost=2, capacity=3)

# 1. The true maximum flow
max_flow_prop = calc_max_flow(g, "A", "C")
Expand All @@ -193,7 +193,7 @@ Note: Don't forget to use a virtual environment (e.g., `venv`) to avoid conflict
"""
Demonstrates traffic engineering by placing two demands on a network.

Graph topology (metrics/capacities):
Graph topology (costs/capacities):

[15]
A ─────── B
Expand All @@ -219,12 +219,12 @@ Note: Don't forget to use a virtual environment (e.g., `venv`) to avoid conflict
g.add_node(node)

# Create bidirectional edges with distinct labels (for clarity).
g.add_edge("A", "B", key=0, metric=1, capacity=15, label="1")
g.add_edge("B", "A", key=1, metric=1, capacity=15, label="1")
g.add_edge("B", "C", key=2, metric=1, capacity=15, label="2")
g.add_edge("C", "B", key=3, metric=1, capacity=15, label="2")
g.add_edge("A", "C", key=4, metric=1, capacity=5, label="3")
g.add_edge("C", "A", key=5, metric=1, capacity=5, label="3")
g.add_edge("A", "B", key=0, cost=1, capacity=15, label="1")
g.add_edge("B", "A", key=1, cost=1, capacity=15, label="1")
g.add_edge("B", "C", key=2, cost=1, capacity=15, label="2")
g.add_edge("C", "B", key=3, cost=1, capacity=15, label="2")
g.add_edge("A", "C", key=4, cost=1, capacity=5, label="3")
g.add_edge("C", "A", key=5, cost=1, capacity=5, label="3")

# Initialize flow-related structures (e.g., to track placed flows in the graph).
flow_graph = init_flow_graph(g)
Expand Down
34 changes: 2 additions & 32 deletions ngraph/blueprints.py
Original file line number Diff line number Diff line change
Expand Up @@ -231,8 +231,8 @@ def _expand_adjacency_pattern(
wrap-around if one side is an integer multiple of the other.
Also skips self-loops.
"""
source_nodes = _find_nodes_by_path(ctx.network, source_path)
target_nodes = _find_nodes_by_path(ctx.network, target_path)
source_nodes = ctx.network.select_nodes_by_path(source_path)
target_nodes = ctx.network.select_nodes_by_path(target_path)

if not source_nodes or not target_nodes:
return
Expand Down Expand Up @@ -338,36 +338,6 @@ def _join_paths(parent_path: str, rel_path: str) -> str:
return rel_path


def _find_nodes_by_path(net: Network, path: str) -> List[Node]:
"""
Returns all nodes whose name is exactly 'path' or begins with 'path/'.
If none are found, tries 'path-' as a fallback prefix.
If still none are found, tries partial prefix "path" => "pathX".

Examples:
path="SEA/clos_instance/spine" might match "SEA/clos_instance/spine/myspine-1"
path="S" might match "S1", "S2" if we resort to partial prefix logic.
"""
# 1) Exact or slash-based
result = [
n for n in net.nodes.values() if n.name == path or n.name.startswith(f"{path}/")
]
if result:
return result

# 2) Fallback: path-
result = [n for n in net.nodes.values() if n.name.startswith(f"{path}-")]
if result:
return result

# 3) Partial
partial = []
for n in net.nodes.values():
if n.name.startswith(path) and n.name != path:
partial.append(n)
return partial


def _process_direct_nodes(net: Network, network_data: Dict[str, Any]) -> None:
"""Processes direct node definitions (network_data["nodes"])."""
for node_name, node_attrs in network_data.get("nodes", {}).items():
Expand Down
14 changes: 7 additions & 7 deletions ngraph/lib/algorithms/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,18 +40,18 @@ class EdgeSelect(IntEnum):
for path-finding between a node and its neighbor(s).
"""

#: Return all edges matching the minimum metric among the candidate edges.
#: Return all edges matching the minimum cost among the candidate edges.
ALL_MIN_COST = 1
#: Return all edges matching the minimum metric among edges with remaining capacity.
#: Return all edges matching the minimum cost among edges with remaining capacity.
ALL_MIN_COST_WITH_CAP_REMAINING = 2
#: Return all edges that have remaining capacity, ignoring metric except for returning min_cost.
#: Return all edges that have remaining capacity, ignoring cost except for returning min_cost.
ALL_ANY_COST_WITH_CAP_REMAINING = 3
#: Return exactly one edge (the single lowest metric).
#: Return exactly one edge (the single lowest cost).
SINGLE_MIN_COST = 4
#: Return exactly one edge, the lowest-metric edge with remaining capacity.
#: Return exactly one edge, the lowest-cost edge with remaining capacity.
SINGLE_MIN_COST_WITH_CAP_REMAINING = 5
#: Return exactly one edge factoring both metric and load:
#: cost = (metric * 100) + round(flow / capacity * 10).
#: Return exactly one edge factoring both cost and load:
#: cost = (cost * 100) + round(flow / capacity * 10).
SINGLE_MIN_COST_WITH_CAP_REMAINING_LOAD_FACTORED = 6
#: Use a user-defined function for edge selection logic.
USER_DEFINED = 99
Expand Down
12 changes: 6 additions & 6 deletions ngraph/lib/algorithms/edge_select.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def edge_select_fabric(
] = None,
excluded_edges: Optional[Set[EdgeID]] = None,
excluded_nodes: Optional[Set[NodeID]] = None,
cost_attr: str = "metric",
cost_attr: str = "cost",
capacity_attr: str = "capacity",
flow_attr: str = "flow",
) -> Callable[
Expand Down Expand Up @@ -73,7 +73,7 @@ def get_all_min_cost_edges(
ignored_edges: Optional[Set[EdgeID]] = None,
ignored_nodes: Optional[Set[NodeID]] = None,
) -> Tuple[Cost, List[EdgeID]]:
"""Return all edges with the minimal metric among those available."""
"""Return all edges with the minimal cost among those available."""
if ignored_nodes and dst_node in ignored_nodes:
return float("inf"), []

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

Expand All @@ -128,7 +128,7 @@ def get_all_edges_with_cap_remaining(
) -> Tuple[Cost, List[EdgeID]]:
"""
Return all edges that have remaining capacity >= min_cap, ignoring
their metric except for reporting the minimal one found.
their cost except for reporting the minimal one found.
"""
if ignored_nodes and dst_node in ignored_nodes:
return float("inf"), []
Expand Down Expand Up @@ -191,7 +191,7 @@ def get_single_min_cost_edge_with_cap_remaining(
ignored_nodes: Optional[Set[NodeID]] = None,
) -> Tuple[Cost, List[EdgeID]]:
"""
Return exactly one edge with the minimal metric among those with
Return exactly one edge with the minimal cost among those with
remaining capacity >= min_cap.
"""
if ignored_nodes and dst_node in ignored_nodes:
Expand Down Expand Up @@ -224,7 +224,7 @@ def get_single_min_cost_edge_with_cap_remaining_load_factored(
"""
Return exactly one edge, factoring both 'cost_attr' and load level
into a combined cost:
combined_cost = (metric * 100) + round((flow / capacity) * 10)
combined_cost = (cost * 100) + round((flow / capacity) * 10)
Only edges with remaining capacity >= min_cap are considered.
"""
if ignored_nodes and dst_node in ignored_nodes:
Expand Down
4 changes: 2 additions & 2 deletions ngraph/lib/path.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ def get_sub_path(
self,
dst_node: NodeID,
graph: StrictMultiDiGraph,
cost_attr: str = "metric",
cost_attr: str = "cost",
) -> Path:
"""
Create a sub-path ending at the specified destination node, recalculating the cost.
Expand All @@ -172,7 +172,7 @@ def get_sub_path(
Args:
dst_node: The node at which to truncate the path.
graph: The graph containing edge attributes.
cost_attr: The edge attribute name to use for cost (default is "metric").
cost_attr: The edge attribute name to use for cost (default is "cost").

Returns:
A new Path instance representing the sub-path from the original source to `dst_node`.
Expand Down
8 changes: 4 additions & 4 deletions ngraph/lib/path_bundle.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ def __init__(
...
}
Typically generated by a shortest-path or multi-path algorithm.
cost: The total path cost (e.g. distance, metric) of all paths in the bundle.
cost: The total path cost (e.g. distance, cost) of all paths in the bundle.
"""
self.src_node: NodeID = src_node
self.dst_node: NodeID = dst_node
Expand Down Expand Up @@ -150,7 +150,7 @@ def from_path(
resolve_edges: bool = False,
graph: Optional[StrictMultiDiGraph] = None,
edge_select: Optional[EdgeSelect] = None,
cost_attr: str = "metric",
cost_attr: str = "cost",
capacity_attr: str = "capacity",
) -> PathBundle:
"""
Expand All @@ -162,7 +162,7 @@ def from_path(
between each node pair via the provided `edge_select`.
graph: The graph used for edge resolution (required if `resolve_edges=True`).
edge_select: The selection criterion for picking edges if `resolve_edges=True`.
cost_attr: The attribute name on edges representing cost (e.g., 'metric').
cost_attr: The attribute name on edges representing cost (e.g., 'cost').
capacity_attr: The attribute name on edges representing capacity.

Returns:
Expand Down Expand Up @@ -268,7 +268,7 @@ def get_sub_path_bundle(
self,
new_dst_node: NodeID,
graph: StrictMultiDiGraph,
cost_attr: str = "metric",
cost_attr: str = "cost",
) -> PathBundle:
"""
Create a sub-bundle ending at `new_dst_node` (which must appear in this bundle).
Expand Down
Loading