Skip to content

Commit 2d0801d

Browse files
authored
Demand placement (#53)
1 parent a21bc56 commit 2d0801d

File tree

14 files changed

+1750
-199
lines changed

14 files changed

+1750
-199
lines changed

ngraph/lib/demand.py

Lines changed: 52 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,95 +1,100 @@
11
from __future__ import annotations
22

3+
from dataclasses import dataclass, field
34
from typing import Optional, Tuple
45

5-
from ngraph.lib.graph import NodeID, StrictMultiDiGraph
66
from ngraph.lib.flow_policy import FlowPolicy
7+
from ngraph.lib.graph import NodeID, StrictMultiDiGraph
78

89

10+
@dataclass
911
class Demand:
1012
"""
11-
Represents a network demand between two nodes.
12-
13-
A Demand can be realized through one or more flows.
13+
Represents a network demand between two nodes. It is realized via one or more
14+
flows through a single FlowPolicy.
1415
"""
1516

16-
def __init__(
17-
self,
18-
src_node: NodeID,
19-
dst_node: NodeID,
20-
volume: float,
21-
demand_class: int = 0,
22-
) -> None:
17+
src_node: NodeID
18+
dst_node: NodeID
19+
volume: float
20+
demand_class: int = 0
21+
flow_policy: Optional[FlowPolicy] = None
22+
placed_demand: float = field(default=0.0, init=False)
23+
24+
def __lt__(self, other: Demand) -> bool:
2325
"""
24-
Initializes a Demand instance.
26+
Compare Demands by their demand_class (priority). A lower demand_class
27+
indicates higher priority, so it should come first in sorting.
2528
2629
Args:
27-
src_node: The source node identifier.
28-
dst_node: The destination node identifier.
29-
volume: The total volume of the demand.
30-
demand_class: An integer representing the demand's class or priority.
31-
"""
32-
self.src_node: NodeID = src_node
33-
self.dst_node: NodeID = dst_node
34-
self.volume: float = volume
35-
self.demand_class: int = demand_class
36-
self.placed_demand: float = 0.0
30+
other (Demand): Demand to compare against.
3731
38-
def __lt__(self, other: Demand) -> bool:
39-
"""Compares Demands based on their demand class."""
32+
Returns:
33+
bool: True if self has higher priority (lower class value).
34+
"""
4035
return self.demand_class < other.demand_class
4136

4237
def __str__(self) -> str:
43-
"""Returns a string representation of the Demand."""
38+
"""
39+
String representation showing src, dst, volume, priority, and placed_demand.
40+
"""
4441
return (
4542
f"Demand(src_node={self.src_node}, dst_node={self.dst_node}, "
46-
f"volume={self.volume}, demand_class={self.demand_class}, placed_demand={self.placed_demand})"
43+
f"volume={self.volume}, demand_class={self.demand_class}, "
44+
f"placed_demand={self.placed_demand})"
4745
)
4846

4947
def place(
5048
self,
5149
flow_graph: StrictMultiDiGraph,
52-
flow_policy: FlowPolicy,
5350
max_fraction: float = 1.0,
5451
max_placement: Optional[float] = None,
5552
) -> Tuple[float, float]:
5653
"""
57-
Places demand volume onto the network graph using the specified flow policy.
58-
59-
The function computes the remaining volume to place, applies any maximum
60-
placement or fraction constraints, and delegates the flow placement to the
61-
provided flow policy. It then updates the placed demand.
54+
Places demand volume onto the network via self.flow_policy.
6255
6356
Args:
64-
flow_graph: The network graph on which flows are placed.
65-
flow_policy: The flow policy used to place the demand.
66-
max_fraction: Maximum fraction of the total demand volume to place in this call.
67-
max_placement: Optional absolute limit on the volume to place.
57+
flow_graph (StrictMultiDiGraph): The graph to place flows onto.
58+
max_fraction (float): The fraction of the remaining demand to place now.
59+
max_placement (Optional[float]): An absolute upper bound on volume.
6860
6961
Returns:
70-
A tuple (placed, remaining) where 'placed' is the volume successfully placed,
71-
and 'remaining' is the volume that could not be placed.
62+
Tuple[float, float]:
63+
placed_now: Volume placed in this call.
64+
remaining: Volume that could not be placed in this call.
65+
66+
Raises:
67+
RuntimeError: If no FlowPolicy is set on this Demand.
68+
ValueError: If max_fraction is outside [0, 1].
7269
"""
73-
to_place = self.volume - self.placed_demand
70+
if self.flow_policy is None:
71+
raise RuntimeError("No FlowPolicy set on this Demand.")
7472

73+
if not (0 <= max_fraction <= 1):
74+
raise ValueError("max_fraction must be in the range [0, 1].")
75+
76+
to_place = self.volume - self.placed_demand
7577
if max_placement is not None:
7678
to_place = min(to_place, max_placement)
7779

7880
if max_fraction > 0:
7981
to_place = min(to_place, self.volume * max_fraction)
8082
else:
81-
# When max_fraction is non-positive, place the entire volume only if infinite;
82-
# otherwise, no placement is performed.
83-
to_place = self.volume if self.volume == float("inf") else 0
83+
# If max_fraction <= 0, do not place any new volume (unless volume is infinite).
84+
to_place = self.volume if self.volume == float("inf") else 0.0
8485

85-
flow_policy.place_demand(
86+
# Delegate flow placement
87+
self.flow_policy.place_demand(
8688
flow_graph,
8789
self.src_node,
8890
self.dst_node,
8991
self.demand_class,
9092
to_place,
9193
)
92-
placed = flow_policy.placed_demand - self.placed_demand
93-
self.placed_demand = flow_policy.placed_demand
94-
remaining = to_place - placed
95-
return placed, remaining
94+
95+
# placed_now is the difference from the old placed_demand
96+
placed_now = self.flow_policy.placed_demand - self.placed_demand
97+
self.placed_demand = self.flow_policy.placed_demand
98+
remaining = to_place - placed_now
99+
100+
return placed_now, remaining

ngraph/lib/flow.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,22 +20,22 @@ class FlowIndex(NamedTuple):
2020
src_node (NodeID): The source node of the flow.
2121
dst_node (NodeID): The destination node of the flow.
2222
flow_class (int): Integer representing the 'class' of this flow (e.g., traffic class).
23-
flow_id (int): A unique integer ID for this flow.
23+
flow_id (str): A unique ID for this flow.
2424
"""
2525

2626
src_node: NodeID
2727
dst_node: NodeID
2828
flow_class: int
29-
flow_id: int
29+
flow_id: str
3030

3131

3232
class Flow:
3333
"""
3434
Represents a fraction of demand routed along a given PathBundle.
3535
3636
In traffic-engineering scenarios, a `Flow` object can model:
37-
- An MPLS LSP/tunnel,
38-
- IP forwarding behavior (with ECMP),
37+
- MPLS LSPs/tunnels with explicit paths,
38+
- IP forwarding behavior (with ECMP or UCMP),
3939
- Or anything that follows a specific set of paths.
4040
"""
4141

0 commit comments

Comments
 (0)