22
33from __future__ import annotations
44
5+ import hashlib
56from collections import deque
67from enum import Enum
78from typing import TYPE_CHECKING
@@ -401,11 +402,16 @@ def _build_in_degrees(
401402 In-degree for each compartment node.
402403 """
403404 in_degree : dict [str , int ] = dict .fromkeys (compartment_labels , 0 )
405+ seen_edges : set [tuple [str , str ]] = set ()
404406 for edge in graph .edges :
407+ edge_pair = (edge .source , edge .target )
408+ if edge_pair in seen_edges :
409+ continue
410+ seen_edges .add (edge_pair )
405411 if (
406412 edge .source in compartment_labels
407413 and edge .target in compartment_labels
408- and ( edge . source , edge . target ) not in back_edges
414+ and edge_pair not in back_edges
409415 ):
410416 in_degree [edge .target ] += 1
411417 return in_degree
@@ -464,20 +470,27 @@ def _propagate_layers(
464470 Edges to skip.
465471 """
466472 processed : set [str ] = set ()
473+ seen_edges : set [tuple [str , str ]] = set ()
467474 while queue :
468475 node = queue .popleft ()
469476 if node in processed :
470477 continue
471478 processed .add (node )
472479
473480 for neighbor in sorted (graph .successors (node )):
474- if neighbor not in compartment_labels or (node , neighbor ) in back_edges :
481+ edge_pair = (node , neighbor )
482+ if (
483+ neighbor not in compartment_labels
484+ or edge_pair in back_edges
485+ or edge_pair in seen_edges
486+ ):
475487 continue
488+ seen_edges .add (edge_pair )
476489 new_layer = layers [node ] + 1
477490 if neighbor not in layers or new_layer > layers [neighbor ]:
478491 layers [neighbor ] = new_layer
479- in_degree [neighbor ] -= 1
480- if in_degree [neighbor ] < = 0 :
492+ in_degree [neighbor ] = max ( 0 , in_degree [ neighbor ] - 1 )
493+ if in_degree [neighbor ] = = 0 :
481494 queue .append (neighbor )
482495
483496
@@ -552,7 +565,22 @@ def sort_key(label: str) -> tuple[int, int, str]:
552565 else :
553566 barycenters [label ] = 0.0
554567
555- return sorted (nodes_in_layer , key = lambda n : (barycenters [n ], n ))
568+ def barycenter_sort_key (label : str ) -> tuple [float , str ]:
569+ """Return sort key based on barycenter and label.
570+
571+ Parameters
572+ ----------
573+ label : str
574+ Node label.
575+
576+ Returns
577+ -------
578+ tuple[float, str]
579+ Tuple of barycenter value and label for sorting.
580+ """
581+ return barycenters [label ], label
582+
583+ return sorted (nodes_in_layer , key = barycenter_sort_key )
556584
557585
558586def _node_sort_index (label : str ) -> float :
@@ -566,9 +594,10 @@ def _node_sort_index(label: str) -> float:
566594 Returns
567595 -------
568596 float
569- The sort index based on label hash.
597+ The sort index based on deterministic label hash.
570598 """
571- return float (hash (label ) % 1000 ) / 1000.0
599+ digest = hashlib .md5 (label .encode (), usedforsecurity = False ).digest ()
600+ return int .from_bytes (digest [:4 ], "big" ) / 4294967296.0
572601
573602
574603def _find_back_edges (graph : KineticGraph , compartment_labels : set [str ]) -> set [tuple [str , str ]]:
0 commit comments