@@ -125,16 +125,19 @@ def load_sensitivity_results(results_dir: Path) -> List[Dict]:
125125 return json .load (f )
126126
127127
128- def load_bifurcation_results (results_dir : Path ) -> Tuple [np .ndarray , np .ndarray ]:
128+ def load_bifurcation_results (results_dir : Path ) -> Tuple [np .ndarray , np .ndarray , np . ndarray ]:
129129 """
130130 Load bifurcation analysis results.
131131
132132 Returns
133133 -------
134134 sweep_params : np.ndarray
135135 1D array of control parameter values (prey death rates).
136- results : np.ndarray
137- 2D array of shape (n_sweep, n_replicates) with population counts
136+ prey_results : np.ndarray
137+ 2D array of shape (n_sweep, n_replicates) with prey population counts
138+ at equilibrium.
139+ predator_results : np.ndarray
140+ 2D array of shape (n_sweep, n_replicates) with predator population counts
138141 at equilibrium.
139142 """
140143 npz_file = results_dir / "bifurcation_results.npz"
@@ -143,12 +146,27 @@ def load_bifurcation_results(results_dir: Path) -> Tuple[np.ndarray, np.ndarray]
143146 if npz_file .exists ():
144147 logging .info (f"Loading bifurcation results from { npz_file } " )
145148 data = np .load (npz_file )
146- return data ['sweep_params' ], data ['results' ]
149+ # Handle both old format (single 'results') and new format (prey/predator)
150+ if 'prey_results' in data :
151+ return data ['sweep_params' ], data ['prey_results' ], data ['predator_results' ]
152+ else :
153+ # Old format - only prey results, create empty predator array
154+ prey_results = data ['results' ]
155+ predator_results = np .full_like (prey_results , np .nan )
156+ return data ['sweep_params' ], prey_results , predator_results
147157 elif json_file .exists ():
148158 logging .info (f"Loading bifurcation results from { json_file } " )
149159 with open (json_file , 'r' ) as f :
150160 data = json .load (f )
151- return np .array (data ['sweep_params' ]), np .array (data ['results' ])
161+ # Handle both old and new format
162+ if 'prey_results' in data :
163+ return (np .array (data ['sweep_params' ]),
164+ np .array (data ['prey_results' ]),
165+ np .array (data ['predator_results' ]))
166+ else :
167+ prey_results = np .array (data ['results' ])
168+ predator_results = np .full_like (prey_results , np .nan )
169+ return np .array (data ['sweep_params' ]), prey_results , predator_results
152170 else :
153171 raise FileNotFoundError (f"Bifurcation results not found in { results_dir } " )
154172
@@ -575,12 +593,14 @@ def plot_fss_analysis(fss_results: List[Dict], output_dir: Path, dpi: int = 150)
575593 logging .info (f"Saved { output_file } " )
576594
577595
578- def plot_bifurcation_diagram (sweep_params : np .ndarray , results : np .ndarray ,
596+ def plot_bifurcation_diagram (sweep_params : np .ndarray ,
597+ prey_results : np .ndarray ,
598+ predator_results : np .ndarray ,
579599 output_dir : Path , dpi : int = 150 ,
580600 control_label : str = "Prey Death Rate" ,
581601 population_label : str = "Population at Equilibrium" ):
582602 """
583- Generate a stochastic bifurcation diagram.
603+ Generate a stochastic bifurcation diagram for both prey and predator .
584604
585605 Shows the distribution of equilibrium population counts as a function of
586606 a control parameter (e.g., prey death rate), with scatter points for each
@@ -591,10 +611,13 @@ def plot_bifurcation_diagram(sweep_params: np.ndarray, results: np.ndarray,
591611 sweep_params : np.ndarray
592612 1D array of control parameter values (e.g., prey death rates).
593613 Shape: (n_sweep,)
594- results : np.ndarray
595- 2D array of population counts at equilibrium.
614+ prey_results : np.ndarray
615+ 2D array of prey population counts at equilibrium.
596616 Shape: (n_sweep, n_replicates) where rows correspond to sweep_params
597617 and columns are replicate simulation runs.
618+ predator_results : np.ndarray
619+ 2D array of predator population counts at equilibrium.
620+ Shape: (n_sweep, n_replicates).
598621 output_dir : Path
599622 Directory to save the output figure.
600623 dpi : int
@@ -604,36 +627,64 @@ def plot_bifurcation_diagram(sweep_params: np.ndarray, results: np.ndarray,
604627 population_label : str
605628 Label for y-axis (population count).
606629 """
607- n_sweep , n_replicates = results .shape
630+ n_sweep , n_replicates = prey_results .shape
631+ has_predator_data = not np .all (np .isnan (predator_results ))
608632
609- fig , ax = plt .subplots (figsize = (12 , 7 ))
633+ fig , ax = plt .subplots (figsize = (14 , 8 ))
610634
611635 # Scatter all individual replicates with transparency
636+ # Prey - green tones
612637 for i , param in enumerate (sweep_params ):
613638 ax .scatter (
614639 np .full (n_replicates , param ),
615- results [i , :],
616- alpha = 0.3 , s = 15 , c = 'steelblue ' , edgecolors = 'none'
640+ prey_results [i , :],
641+ alpha = 0.3 , s = 15 , c = 'forestgreen ' , edgecolors = 'none'
617642 )
618643
619- # Compute summary statistics
620- means = np .mean (results , axis = 1 )
621- medians = np .median (results , axis = 1 )
622- q25 = np .percentile (results , 25 , axis = 1 )
623- q75 = np .percentile (results , 75 , axis = 1 )
624-
625- # Plot median line and IQR envelope
626- ax .fill_between (sweep_params , q25 , q75 , alpha = 0.25 , color = 'coral' ,
627- label = 'IQR (25th-75th percentile)' )
628- ax .plot (sweep_params , medians , 'o-' , color = 'darkred' , linewidth = 2 ,
629- markersize = 5 , label = 'Median' )
630- ax .plot (sweep_params , means , 's--' , color = 'black' , linewidth = 1.5 ,
631- markersize = 4 , alpha = 0.7 , label = 'Mean' )
644+ # Predator - red tones (if data available)
645+ if has_predator_data :
646+ for i , param in enumerate (sweep_params ):
647+ ax .scatter (
648+ np .full (n_replicates , param ),
649+ predator_results [i , :],
650+ alpha = 0.3 , s = 15 , c = 'crimson' , edgecolors = 'none'
651+ )
652+
653+ # Compute summary statistics for prey
654+ prey_means = np .mean (prey_results , axis = 1 )
655+ prey_medians = np .median (prey_results , axis = 1 )
656+ prey_q25 = np .percentile (prey_results , 25 , axis = 1 )
657+ prey_q75 = np .percentile (prey_results , 75 , axis = 1 )
658+
659+ # Plot prey median line and IQR envelope
660+ ax .fill_between (sweep_params , prey_q25 , prey_q75 , alpha = 0.2 , color = 'green' ,
661+ label = 'Prey IQR' )
662+ ax .plot (sweep_params , prey_medians , 'o-' , color = 'darkgreen' , linewidth = 2 ,
663+ markersize = 5 , label = 'Prey Median' )
664+ ax .plot (sweep_params , prey_means , 's--' , color = 'forestgreen' , linewidth = 1.5 ,
665+ markersize = 4 , alpha = 0.7 , label = 'Prey Mean' )
666+
667+ # Compute and plot predator statistics if available
668+ if has_predator_data :
669+ pred_means = np .mean (predator_results , axis = 1 )
670+ pred_medians = np .median (predator_results , axis = 1 )
671+ pred_q25 = np .percentile (predator_results , 25 , axis = 1 )
672+ pred_q75 = np .percentile (predator_results , 75 , axis = 1 )
673+
674+ ax .fill_between (sweep_params , pred_q25 , pred_q75 , alpha = 0.2 , color = 'red' ,
675+ label = 'Predator IQR' )
676+ ax .plot (sweep_params , pred_medians , 'o-' , color = 'darkred' , linewidth = 2 ,
677+ markersize = 5 , label = 'Predator Median' )
678+ ax .plot (sweep_params , pred_means , 's--' , color = 'crimson' , linewidth = 1.5 ,
679+ markersize = 4 , alpha = 0.7 , label = 'Predator Mean' )
632680
633681 ax .set_xlabel (control_label )
634682 ax .set_ylabel (population_label )
635- ax .set_title (f"Stochastic Bifurcation Diagram\n ({ n_replicates } replicates per parameter value)" )
636- ax .legend (loc = 'best' )
683+ title = f"Stochastic Bifurcation Diagram\n ({ n_replicates } replicates per parameter value)"
684+ if has_predator_data :
685+ title = f"Prey-Predator { title } "
686+ ax .set_title (title )
687+ ax .legend (loc = 'best' , ncol = 2 )
637688 ax .grid (True , alpha = 0.3 )
638689
639690 # Add rug plot at bottom showing parameter sampling density
@@ -887,10 +938,11 @@ def main():
887938 # Bifurcation diagram
888939 if plot_all or args .bifurcation_only :
889940 try :
890- sweep_params , bifurc_results = load_bifurcation_results (results_dir )
941+ sweep_params , prey_results , predator_results = load_bifurcation_results (results_dir )
891942 logging .info (f"Loaded bifurcation results: { len (sweep_params )} sweep values, "
892- f"{ bifurc_results .shape [1 ]} replicates each" )
893- plot_bifurcation_diagram (sweep_params , bifurc_results , output_dir , args .dpi )
943+ f"{ prey_results .shape [1 ]} replicates each" )
944+ plot_bifurcation_diagram (sweep_params , prey_results , predator_results ,
945+ output_dir , args .dpi )
894946 except FileNotFoundError as e :
895947 logging .warning (f"Bifurcation results not found: { e } " )
896948
0 commit comments