@@ -30,13 +30,15 @@ class Node:
3030 the key in the Network's node dictionary.
3131
3232 Attributes:
33- name: Unique identifier for the node.
34- disabled: Whether the node is disabled (excluded from calculations).
35- attrs: Additional metadata (e.g., coordinates, region, shared_risk_groups).
33+ name (str): Unique identifier for the node.
34+ disabled (bool): Whether the node is disabled (excluded from calculations).
35+ risk_groups (Set[str]): Set of risk group names this node belongs to.
36+ attrs (Dict[str, Any]): Additional metadata (e.g., coordinates, region).
3637 """
3738
3839 name : str
3940 disabled : bool = False
41+ risk_groups : Set [str ] = field (default_factory = set )
4042 attrs : Dict [str , Any ] = field (default_factory = dict )
4143
4244
@@ -46,20 +48,22 @@ class Link:
4648 Represents a directed link between two nodes in the network.
4749
4850 Attributes:
49- source: Name of the source node.
50- target: Name of the target node.
51- capacity: Link capacity (default 1.0).
52- cost: Link cost (default 1.0).
53- disabled: Whether the link is disabled.
54- attrs: Additional metadata (e.g., distance, shared_risk_groups).
55- id: Auto-generated unique identifier: "{source}|{target}|<base64_uuid>".
51+ source (str): Name of the source node.
52+ target (str): Name of the target node.
53+ capacity (float): Link capacity (default 1.0).
54+ cost (float): Link cost (default 1.0).
55+ disabled (bool): Whether the link is disabled.
56+ risk_groups (Set[str]): Set of risk group names this link belongs to.
57+ attrs (Dict[str, Any]): Additional metadata (e.g., distance).
58+ id (str): Auto-generated unique identifier: "{source}|{target}|<base64_uuid>".
5659 """
5760
5861 source : str
5962 target : str
6063 capacity : float = 1.0
6164 cost : float = 1.0
6265 disabled : bool = False
66+ risk_groups : Set [str ] = field (default_factory = set )
6367 attrs : Dict [str , Any ] = field (default_factory = dict )
6468 id : str = field (init = False )
6569
@@ -76,10 +80,9 @@ class RiskGroup:
7680 Represents a shared-risk or failure domain, which may have nested children.
7781
7882 Attributes:
79- name: Unique name of this risk group.
80- children: Subdomains in a nested structure.
81- disabled: Whether this group was declared disabled on load.
82- Disabling is enforced by `disable_risk_group()` in the Network.
83+ name (str): Unique name of this risk group.
84+ children (List[RiskGroup]): Subdomains in a nested structure.
85+ disabled (bool): Whether this group was declared disabled on load.
8386 """
8487
8588 name : str
@@ -95,29 +98,25 @@ class Network:
9598 Attributes:
9699 nodes (Dict[str, Node]): Mapping from node name -> Node object.
97100 links (Dict[str, Link]): Mapping from link ID -> Link object.
98- risk_groups: Top-level risk groups by name.
101+ risk_groups (Dict[str, RiskGroup]) : Top-level risk groups by name.
99102 attrs (Dict[str, Any]): Optional metadata about the network.
100103 """
101104
102105 nodes : Dict [str , Node ] = field (default_factory = dict )
103106 links : Dict [str , Link ] = field (default_factory = dict )
107+ risk_groups : Dict [str , RiskGroup ] = field (default_factory = dict )
104108 attrs : Dict [str , Any ] = field (default_factory = dict )
105109
106110 def add_node (self , node : Node ) -> None :
107111 """
108112 Add a node to the network (keyed by node.name).
109113
110- Auto-tags node.attrs["type"] = "node" if not already set,
111- and node.attrs["disabled"] = False if not specified.
112-
113114 Args:
114- node: Node to add.
115+ node (Node) : Node to add.
115116
116117 Raises:
117118 ValueError: If a node with the same name already exists.
118119 """
119- node .attrs .setdefault ("type" , "node" )
120- node .attrs .setdefault ("disabled" , False )
121120 if node .name in self .nodes :
122121 raise ValueError (f"Node '{ node .name } ' already exists in the network." )
123122 self .nodes [node .name ] = node
@@ -126,11 +125,8 @@ def add_link(self, link: Link) -> None:
126125 """
127126 Add a link to the network (keyed by the link's auto-generated ID).
128127
129- Auto-tags link.attrs["type"] = "link" if not already set,
130- and link.attrs["disabled"] = False if not specified.
131-
132128 Args:
133- link: Link to add.
129+ link (Link) : Link to add.
134130
135131 Raises:
136132 ValueError: If the link's source or target node does not exist.
@@ -140,8 +136,6 @@ def add_link(self, link: Link) -> None:
140136 if link .target not in self .nodes :
141137 raise ValueError (f"Target node '{ link .target } ' not found in network." )
142138
143- link .attrs .setdefault ("type" , "link" )
144- link .attrs .setdefault ("disabled" , False )
145139 self .links [link .id ] = link
146140
147141 def to_strict_multidigraph (self , add_reverse : bool = True ) -> StrictMultiDiGraph :
@@ -151,10 +145,10 @@ def to_strict_multidigraph(self, add_reverse: bool = True) -> StrictMultiDiGraph
151145 Skips disabled nodes/links. Optionally adds reverse edges.
152146
153147 Args:
154- add_reverse: If True, also add a reverse edge for each link.
148+ add_reverse (bool) : If True, also add a reverse edge for each link.
155149
156150 Returns:
157- A directed multigraph representation of the network.
151+ StrictMultiDiGraph: A directed multigraph representation of the network.
158152 """
159153 graph = StrictMultiDiGraph ()
160154 disabled_nodes = {name for name , nd in self .nodes .items () if nd .disabled }
@@ -199,14 +193,13 @@ def select_node_groups_by_path(self, path: str) -> Dict[str, List[Node]]:
199193 """
200194 Select and group nodes whose names match a given regular expression.
201195
202- This method uses re.match(), so the pattern is anchored at the start
203- of the node name. If the pattern includes capturing groups,
204- the group label is formed by joining all non-None captures with '|'.
205- If no capturing groups exist, the group label is the original
206- pattern string.
196+ Uses re.match(), so the pattern is anchored at the start of the node name.
197+ If the pattern includes capturing groups, the group label is formed by
198+ joining all non-None captures with '|'. If no capturing groups exist,
199+ the group label is the original pattern string.
207200
208201 Args:
209- path: A Python regular expression pattern (e.g., "^foo", "bar(\\ d+)", etc.).
202+ path (str) : A Python regular expression pattern (e.g., "^foo", "bar(\\ d+)", etc.).
210203
211204 Returns:
212205 Dict[str, List[Node]]: A mapping from group label -> list of matching nodes.
@@ -240,17 +233,18 @@ def max_flow(
240233 Returns a dictionary of flow values keyed by (source_label, sink_label).
241234
242235 Args:
243- source_path: Regex pattern for selecting source nodes.
244- sink_path: Regex pattern for selecting sink nodes.
245- mode: Either "combine" or "pairwise".
246- - "combine": Treat all matched sources as one group,
247- and all matched sinks as one group. Returns a single entry.
248- - "pairwise": Compute flow for each (source_group, sink_group) pair.
249- shortest_path: If True, flows are constrained to shortest paths.
250- flow_placement: Determines how parallel equal-cost paths are handled.
236+ source_path (str): Regex pattern for selecting source nodes.
237+ sink_path (str): Regex pattern for selecting sink nodes.
238+ mode (str): Either "combine" or "pairwise".
239+ - "combine": Treat all matched sources as one group,
240+ and all matched sinks as one group. Returns a single entry.
241+ - "pairwise": Compute flow for each (source_group, sink_group) pair.
242+ shortest_path (bool): If True, flows are constrained to shortest paths.
243+ flow_placement (FlowPlacement): Determines how parallel equal-cost paths
244+ are handled.
251245
252246 Returns:
253- A dict of { (src_label, snk_label): flow_value} .
247+ Dict[Tuple[str, str], float]: Flow values keyed by (src_label, snk_label).
254248
255249 Raises:
256250 ValueError: If no matching source or sink groups are found, or invalid mode.
@@ -303,7 +297,7 @@ def _compute_flow_single_group(
303297 sources : List [Node ],
304298 sinks : List [Node ],
305299 shortest_path : bool ,
306- flow_placement : FlowPlacement ,
300+ flow_placement : Optional [ FlowPlacement ] ,
307301 ) -> float :
308302 """
309303 Attach a pseudo-source and pseudo-sink to the provided node lists,
@@ -313,14 +307,18 @@ def _compute_flow_single_group(
313307 Disabled nodes are excluded from flow computation.
314308
315309 Args:
316- sources: List of source nodes.
317- sinks: List of sink nodes.
318- shortest_path: If True, restrict flows to shortest paths only.
319- flow_placement: Strategy for placing flow among parallel equal-cost paths.
310+ sources (List[Node]): List of source nodes.
311+ sinks (List[Node]): List of sink nodes.
312+ shortest_path (bool): If True, restrict flows to shortest paths only.
313+ flow_placement (FlowPlacement or None): Strategy for placing flow among
314+ parallel equal-cost paths. If None, defaults to FlowPlacement.PROPORTIONAL.
320315
321316 Returns:
322- The computed max flow value, or 0.0 if no active sources or sinks.
317+ float: The computed max flow value, or 0.0 if no active sources or sinks.
323318 """
319+ if flow_placement is None :
320+ flow_placement = FlowPlacement .PROPORTIONAL
321+
324322 active_sources = [s for s in sources if not s .disabled ]
325323 active_sinks = [s for s in sinks if not s .disabled ]
326324
@@ -353,7 +351,7 @@ def disable_node(self, node_name: str) -> None:
353351 Mark a node as disabled.
354352
355353 Args:
356- node_name: Name of the node to disable.
354+ node_name (str) : Name of the node to disable.
357355
358356 Raises:
359357 ValueError: If the specified node does not exist.
@@ -367,7 +365,7 @@ def enable_node(self, node_name: str) -> None:
367365 Mark a node as enabled.
368366
369367 Args:
370- node_name: Name of the node to enable.
368+ node_name (str) : Name of the node to enable.
371369
372370 Raises:
373371 ValueError: If the specified node does not exist.
@@ -381,7 +379,7 @@ def disable_link(self, link_id: str) -> None:
381379 Mark a link as disabled.
382380
383381 Args:
384- link_id: ID of the link to disable.
382+ link_id (str) : ID of the link to disable.
385383
386384 Raises:
387385 ValueError: If the specified link does not exist.
@@ -395,7 +393,7 @@ def enable_link(self, link_id: str) -> None:
395393 Mark a link as enabled.
396394
397395 Args:
398- link_id: ID of the link to enable.
396+ link_id (str) : ID of the link to enable.
399397
400398 Raises:
401399 ValueError: If the specified link does not exist.
@@ -428,11 +426,11 @@ def get_links_between(self, source: str, target: str) -> List[str]:
428426 to the target node.
429427
430428 Args:
431- source: Source node name.
432- target: Target node name.
429+ source (str) : Source node name.
430+ target (str) : Target node name.
433431
434432 Returns:
435- A list of link IDs for all direct links from source to target.
433+ List[str]: A list of link IDs for all direct links from source to target.
436434 """
437435 matches = []
438436 for link_id , link in self .links .items ():
@@ -450,12 +448,12 @@ def find_links(
450448 Search for links using optional regex patterns for source or target node names.
451449
452450 Args:
453- source_regex: Regex pattern to match link.source. If None, matches all sources.
454- target_regex: Regex pattern to match link.target. If None, matches all targets.
455- any_direction: If True, also match where source and target are reversed .
451+ source_regex (str or None) : Regex to match link.source. If None, matches all sources.
452+ target_regex (str or None) : Regex to match link.target. If None, matches all targets.
453+ any_direction (bool) : If True, also match reversed source/ target.
456454
457455 Returns:
458- A list of unique Link objects that match the criteria.
456+ List[Link]: A list of unique Link objects that match the criteria.
459457 """
460458 src_pat = re .compile (source_regex ) if source_regex else None
461459 tgt_pat = re .compile (target_regex ) if target_regex else None
@@ -482,12 +480,12 @@ def find_links(
482480
483481 def disable_risk_group (self , name : str , recursive : bool = True ) -> None :
484482 """
485- Disable all nodes/links that have 'name' in their shared_risk_groups .
483+ Disable all nodes/links that have 'name' in their risk_groups .
486484 If recursive=True, also disable items belonging to child risk groups.
487485
488486 Args:
489- name: The name of the risk group to disable.
490- recursive: If True, also disable subgroups recursively.
487+ name (str) : The name of the risk group to disable.
488+ recursive (bool) : If True, also disable subgroups recursively.
491489 """
492490 if name not in self .risk_groups :
493491 return
@@ -500,19 +498,19 @@ def disable_risk_group(self, name: str, recursive: bool = True) -> None:
500498 if recursive :
501499 queue .extend (grp .children )
502500
501+ # Disable nodes
503502 for node_name , node_obj in self .nodes .items ():
504- srgs = node_obj .attrs .get ("shared_risk_groups" , [])
505- if any (g in to_disable for g in srgs ):
503+ if node_obj .risk_groups & to_disable :
506504 self .disable_node (node_name )
507505
506+ # Disable links
508507 for link_id , link_obj in self .links .items ():
509- srgs = link_obj .attrs .get ("shared_risk_groups" , [])
510- if any (g in to_disable for g in srgs ):
508+ if link_obj .risk_groups & to_disable :
511509 self .disable_link (link_id )
512510
513511 def enable_risk_group (self , name : str , recursive : bool = True ) -> None :
514512 """
515- Enable all nodes/links that have 'name' in their shared_risk_groups .
513+ Enable all nodes/links that have 'name' in their risk_groups .
516514 If recursive=True, also enable items belonging to child risk groups.
517515
518516 Note:
@@ -521,8 +519,8 @@ def enable_risk_group(self, name: str, recursive: bool = True) -> None:
521519 remain disabled.
522520
523521 Args:
524- name: The name of the risk group to enable.
525- recursive: If True, also enable subgroups recursively.
522+ name (str) : The name of the risk group to enable.
523+ recursive (bool) : If True, also enable subgroups recursively.
526524 """
527525 if name not in self .risk_groups :
528526 return
@@ -535,12 +533,12 @@ def enable_risk_group(self, name: str, recursive: bool = True) -> None:
535533 if recursive :
536534 queue .extend (grp .children )
537535
536+ # Enable nodes
538537 for node_name , node_obj in self .nodes .items ():
539- srgs = node_obj .attrs .get ("shared_risk_groups" , [])
540- if any (g in to_enable for g in srgs ):
538+ if node_obj .risk_groups & to_enable :
541539 self .enable_node (node_name )
542540
541+ # Enable links
543542 for link_id , link_obj in self .links .items ():
544- srgs = link_obj .attrs .get ("shared_risk_groups" , [])
545- if any (g in to_enable for g in srgs ):
543+ if link_obj .risk_groups & to_enable :
546544 self .enable_link (link_id )
0 commit comments