@@ -245,6 +245,12 @@ const generateBaseStructure = (nodes: TestNode[], spec: GraphSpec, _config: Grap
245245 return edges ;
246246 }
247247
248+ // Handle flow networks
249+ if ( spec . flowNetwork ?. kind === "flow_network" ) {
250+ generateFlowNetworkEdges ( nodes , edges , spec , spec . flowNetwork . source , spec . flowNetwork . sink , rng ) ;
251+ return edges ;
252+ }
253+
248254 // Handle Eulerian and semi-Eulerian graphs
249255 if ( spec . eulerian ?. kind === "eulerian" || spec . eulerian ?. kind === "semi_eulerian" ) {
250256 generateEulerianEdges ( nodes , edges , spec , rng ) ;
@@ -619,45 +625,200 @@ const generateRegularEdges = (nodes: TestNode[], edges: TestEdge[], spec: GraphS
619625 throw new Error ( `k-regular graph requires n*k to be even (got n=${ n } , k=${ k } , n*k=${ n * k } )` ) ;
620626 }
621627
622- // Create stubs (half-edges): each vertex has k stubs
623- const stubs : string [ ] = [ ] ;
624- for ( const node of nodes ) {
625- for ( let i = 0 ; i < k ; i ++ ) {
626- stubs . push ( node . id ) ;
628+ // Use configuration model with retry logic
629+ // Keep trying until we successfully create all n*k/2 edges
630+ const maxAttempts = 1000 ;
631+ let attempt = 0 ;
632+
633+ while ( attempt < maxAttempts && edges . length < ( n * k ) / 2 ) {
634+ attempt ++ ;
635+
636+ // Clear previous failed attempt
637+ if ( attempt > 1 ) {
638+ edges . length = 0 ;
639+ }
640+
641+ // Create stubs (half-edges): each vertex has k stubs
642+ const stubs : string [ ] = [ ] ;
643+ for ( const node of nodes ) {
644+ for ( let i = 0 ; i < k ; i ++ ) {
645+ stubs . push ( node . id ) ;
646+ }
647+ }
648+
649+ // Shuffle stubs randomly
650+ for ( let i = stubs . length - 1 ; i > 0 ; i -- ) {
651+ const j = rng . integer ( 0 , i ) ;
652+ [ stubs [ i ] , stubs [ j ] ] = [ stubs [ j ] , stubs [ i ] ] ;
653+ }
654+
655+ // Track existing edges to avoid duplicates in simple graphs
656+ const existingEdges = new Set < string > ( ) ;
657+
658+ // Pair up stubs to create edges
659+ let success = true ;
660+ for ( let i = 0 ; i < stubs . length ; i += 2 ) {
661+ const source = stubs [ i ] ;
662+ const target = stubs [ i + 1 ] ;
663+
664+ // Skip self-loops (unless allowed) - fail this attempt
665+ if ( source === target && spec . selfLoops . kind === "disallowed" ) {
666+ success = false ;
667+ break ;
668+ }
669+
670+ // For simple graphs, check for duplicate edges - fail this attempt
671+ const edgeKey = spec . directionality . kind === 'directed'
672+ ? `${ source } →${ target } `
673+ : [ source , target ] . sort ( ) . join ( '-' ) ;
674+
675+ if ( spec . edgeMultiplicity . kind === 'simple' && existingEdges . has ( edgeKey ) ) {
676+ success = false ;
677+ break ;
678+ }
679+
680+ addEdge ( edges , source , target , spec , rng ) ;
681+
682+ if ( spec . edgeMultiplicity . kind === 'simple' ) {
683+ existingEdges . add ( edgeKey ) ;
684+ }
685+ }
686+
687+ // If we successfully created all edges, we're done
688+ if ( success && edges . length === ( n * k ) / 2 ) {
689+ break ;
627690 }
628691 }
629692
630- // Shuffle stubs randomly
631- for ( let i = stubs . length - 1 ; i > 0 ; i -- ) {
632- const j = rng . integer ( 0 , i ) ;
633- [ stubs [ i ] , stubs [ j ] ] = [ stubs [ j ] , stubs [ i ] ] ;
693+ // If we failed after max attempts, throw an error
694+ if ( edges . length < ( n * k ) / 2 ) {
695+ throw new Error ( `Failed to generate ${ k } -regular graph after ${ maxAttempts } attempts (got ${ edges . length } edges, expected ${ ( n * k ) / 2 } )` ) ;
634696 }
697+ } ;
635698
636- // Pair up stubs to create edges
637- const existingEdges = new Set < string > ( ) ;
638- for ( let i = 0 ; i < stubs . length ; i += 2 ) {
639- const source = stubs [ i ] ;
640- const target = stubs [ i + 1 ] ;
699+ /**
700+ * Generate flow network edges.
701+ * Flow networks have:
702+ * - Directed edges from source toward sink
703+ * - Weighted edges (capacities)
704+ * - Every node on some path from source to sink
705+ * - No edges entering source
706+ * - No edges leaving sink
707+ * @param nodes
708+ * @param edges
709+ * @param spec
710+ * @param source
711+ * @param sink
712+ * @param rng
713+ */
714+ const generateFlowNetworkEdges = ( nodes : TestNode [ ] , edges : TestEdge [ ] , spec : GraphSpec , source : string , sink : string , rng : SeededRandom ) : void => {
715+ const n = nodes . length ;
641716
642- // Skip self-loops (unless allowed)
643- if ( source === target && spec . selfLoops . kind === "disallowed" ) {
644- continue ;
645- }
717+ // Validate source and sink exist
718+ const sourceNode = nodes . find ( node => node . id === source ) ;
719+ const sinkNode = nodes . find ( node => node . id === sink ) ;
646720
647- // For simple graphs, skip duplicate edges
648- const edgeKey = spec . directionality . kind === 'directed'
649- ? `${ source } →${ target } `
650- : [ source , target ] . sort ( ) . join ( '-' ) ;
721+ if ( ! sourceNode ) {
722+ throw new Error ( `Source node '${ source } ' not found in nodes` ) ;
723+ }
724+ if ( ! sinkNode ) {
725+ throw new Error ( `Sink node '${ sink } ' not found in nodes` ) ;
726+ }
727+ if ( source === sink ) {
728+ throw new Error ( 'Source and sink must be different nodes' ) ;
729+ }
651730
652- if ( spec . edgeMultiplicity . kind === 'simple' && existingEdges . has ( edgeKey ) ) {
653- // Skip this edge if it's a duplicate and we want a simple graph
654- continue ;
731+ // Build layers to ensure all nodes lie on source→sink paths
732+ // Layer 0: source
733+ // Layer 1: intermediate nodes
734+ // Layer 2: sink
735+ const sourceLayer = new Set < string > ( [ source ] ) ;
736+ const sinkLayer = new Set < string > ( [ sink ] ) ;
737+ const intermediateLayer = new Set < string > (
738+ nodes . filter ( node => node . id !== source && node . id !== sink ) . map ( node => node . id )
739+ ) ;
740+
741+ // Connect source to intermediate nodes (50-75% connectivity)
742+ const sourceConnectivity = 0.5 + rng . next ( ) * 0.25 ;
743+ for ( const targetId of intermediateLayer ) {
744+ if ( rng . next ( ) < sourceConnectivity ) {
745+ edges . push ( {
746+ id : `edge-${ source } -${ targetId } ` ,
747+ source,
748+ target : targetId ,
749+ directed : true ,
750+ weight : Math . floor ( rng . next ( ) * 10 ) + 1 , // Capacity 1-10
751+ } ) ;
655752 }
753+ }
656754
657- addEdge ( edges , source , target , spec , rng ) ;
755+ // Connect intermediate nodes among themselves (creating paths)
756+ const intermediateArray = Array . from ( intermediateLayer ) ;
757+ if ( intermediateArray . length > 1 ) {
758+ // Create a roughly connected structure among intermediate nodes
759+ for ( let i = 0 ; i < intermediateArray . length ; i ++ ) {
760+ const fromId = intermediateArray [ i ] ;
761+
762+ // Connect to next 1-2 nodes to create paths
763+ const connections = Math . floor ( rng . next ( ) * 2 ) + 1 ;
764+ for ( let j = 1 ; j <= connections ; j ++ ) {
765+ const toIndex = ( i + j ) % intermediateArray . length ;
766+ const toId = intermediateArray [ toIndex ] ;
767+
768+ // Avoid backward edges (maintain general flow direction)
769+ if ( rng . next ( ) < 0.7 ) {
770+ edges . push ( {
771+ id : `edge-${ fromId } -${ toId } ` ,
772+ source : fromId ,
773+ target : toId ,
774+ directed : true ,
775+ weight : Math . floor ( rng . next ( ) * 10 ) + 1 ,
776+ } ) ;
777+ }
778+ }
779+ }
780+ }
658781
659- if ( spec . edgeMultiplicity . kind === 'simple' ) {
660- existingEdges . add ( edgeKey ) ;
782+ // Connect intermediate nodes to sink (50-75% connectivity)
783+ const sinkConnectivity = 0.5 + rng . next ( ) * 0.25 ;
784+ for ( const sourceId of intermediateLayer ) {
785+ if ( rng . next ( ) < sinkConnectivity ) {
786+ edges . push ( {
787+ id : `edge-${ sourceId } -${ sink } ` ,
788+ source : sourceId ,
789+ target : sink ,
790+ directed : true ,
791+ weight : Math . floor ( rng . next ( ) * 10 ) + 1 ,
792+ } ) ;
793+ }
794+ }
795+
796+ // Also add a direct source→sink edge sometimes (higher capacity)
797+ if ( rng . next ( ) < 0.3 ) {
798+ edges . push ( {
799+ id : `edge-${ source } -${ sink } ` ,
800+ source,
801+ target : sink ,
802+ directed : true ,
803+ weight : Math . floor ( rng . next ( ) * 20 ) + 10 , // Higher capacity 10-30
804+ } ) ;
805+ }
806+
807+ // Ensure minimum edge count for connectivity
808+ if ( edges . length < n - 1 ) {
809+ // Add more connections from source
810+ for ( const targetId of intermediateLayer ) {
811+ const hasEdge = edges . some ( e => e . source === source && e . target === targetId ) ;
812+ if ( ! hasEdge ) {
813+ edges . push ( {
814+ id : `edge-${ source } -${ targetId } ` ,
815+ source,
816+ target : targetId ,
817+ directed : true ,
818+ weight : Math . floor ( rng . next ( ) * 10 ) + 1 ,
819+ } ) ;
820+ if ( edges . length >= n - 1 ) break ;
821+ }
661822 }
662823 }
663824} ;
@@ -1341,6 +1502,9 @@ const addDensityEdges = (nodes: TestNode[], edges: TestEdge[], spec: GraphSpec,
13411502 if ( spec . specificRegular ?. kind === "k_regular" ) {
13421503 return ; // k-regular graphs have exact structure
13431504 }
1505+ if ( spec . flowNetwork ?. kind === "flow_network" ) {
1506+ return ; // Flow networks have exact structure
1507+ }
13441508 if ( spec . eulerian ?. kind === "eulerian" || spec . eulerian ?. kind === "semi_eulerian" ) {
13451509 return ; // Eulerian graphs have exact structure
13461510 }
0 commit comments