From bc355b1b1f12637f01651a9e65acf1bbd70127ff Mon Sep 17 00:00:00 2001 From: jannetty Date: Thu, 18 Dec 2025 14:11:38 -0800 Subject: [PATCH 1/4] adding functionality to generate matplotlib plots for fitness over time of GA and auxin over time in oscillation zone for best performing parameters --- ...ORA_genetic_alg_imposed_auxsyndegexport.py | 134 +++++++++++++++++- 1 file changed, 131 insertions(+), 3 deletions(-) diff --git a/param_est/ARORA_genetic_alg_imposed_auxsyndegexport.py b/param_est/ARORA_genetic_alg_imposed_auxsyndegexport.py index 4613ca0..715df8f 100644 --- a/param_est/ARORA_genetic_alg_imposed_auxsyndegexport.py +++ b/param_est/ARORA_genetic_alg_imposed_auxsyndegexport.py @@ -4,6 +4,7 @@ import csv import glob from skimage.draw import polygon +import matplotlib.pyplot as plt from src.arora_enums import CircModEnum, PinLocalizationRulesetEnum @@ -65,6 +66,7 @@ def __init__(self, filename: str): self.filename = filename self.population = [] self.param_names = AUX_SYN_DEG_EXPORT_PARAM_NAMES + self.cleanup = False def fitness_function(self, ga_instance, solution, solution_idx): print(f"-----------------------{solution_idx}---------------------------") @@ -117,7 +119,7 @@ def _cleanup_sim_files(self, chrom_idx: int) -> None: print(f"Warning: could not delete {path}: {e}") def _run_ARORA(self, params, chromosome): - timestep = .1 # 6 minutes, this is less frequent than VDB outputs. + timestep = 1/9 # 6.6 minutes, this is less frequent than VDB outputs. vis = False cell_val_file = "src/sim/input/aux_syndegonly_init_vals.json" v_file = "src/sim/input/default_vs.json" @@ -137,7 +139,7 @@ def _run_ARORA(self, params, chromosome): output_file=f"param_est/ARORA_output_{chromosome['sol_idx']}", circ_mod=CircModEnum.AUX_SYN_DEG_EXP, pin_loc_rules=PinLocalizationRulesetEnum.IMPOSED, - output_frequency=1 # outputting every 6 minutes + output_frequency=1 # outputting every 6.6 minutes ) try: @@ -156,7 +158,8 @@ def _run_ARORA(self, params, chromosome): print("Fitness set to -infinity") fitness = -np.inf finally: - self._cleanup_sim_files(chromosome["sol_idx"]) + if self.cleanup: + self._cleanup_sim_files(chromosome["sol_idx"]) return fitness def create_arora_csv(self, path: str, chromosome_idx: int,tick: int) -> None: @@ -217,6 +220,12 @@ def create_arora_csv(self, path: str, chromosome_idx: int,tick: int) -> None: def _calculate_fitness(self, simulation, chromosome): + ref0 = "param_est/vdb_data/references/Caux1Array_00000000.csv" + if not os.path.exists(ref0): + raise FileNotFoundError( + f"Missing VDB reference file: {ref0}\n" + "Either the VDB dataset isn't present, path is wrong, or you're running from a different working directory." + ) fitness = arora_vdb_ssd(chromosome['sol_idx']) return fitness @@ -301,8 +310,47 @@ def on_gen(self, ga_instance): print("Generation : ", ga_instance.generations_completed) print("Fitness of the best solution :", ga_instance.best_solution()[1]) + def _auxin_at_rc_from_tick_json(self, path: str, r: int, c: int) -> float: + """ + Returns the auxin value at array location (r, c) for a single tick JSON. + + - Uses the same rasterization conventions as create_arora_csv(): + arr shape = (1208, 141), polygon uses (row=y, col=x), and x is shifted by -1. + - If the point falls outside all polygons, returns 0.0 (background). + """ + with open(path) as file: + cells = json.load(file) + + arr = np.zeros((1208, 141), dtype=float) + + ymin = 0 + for cell in cells: + for corner in cell["location"]: + ymin = min(ymin, corner[1]) + + for cell in cells: + auxin = float(cell["auxin"]) + location = np.array(cell["location"], dtype=float) # (4,2) [x,y] + + # match VDB alignment (same as create_arora_csv) + location[:, 0] -= 1 + + if ymin < 0: + location[:, 1] += abs(ymin) + + rr, cc = polygon(location[:, 1], location[:, 0], arr.shape) + arr[rr, cc] = auxin + + # guard against out-of-bounds + if r < 0 or r >= arr.shape[0] or c < 0 or c >= arr.shape[1]: + raise ValueError(f"(r,c)=({r},{c}) out of bounds for auxin array shape {arr.shape}") + + return float(arr[r, c]) + def analyze_results(self): + print("Generating plot of best fitness per generation.") solution, solution_fitness, solution_idx = self.ga_instance.best_solution() + print("Parameters of the best solution : {solution}".format(solution=solution)) print( "Fitness value of the best solution = {solution_fitness}".format( @@ -314,3 +362,83 @@ def analyze_results(self): solution_idx=solution_idx ) ) + + # --------------------------- + # Plot 1: Best fitness per generation + # --------------------------- + # pygad keeps this as a list of best fitness values (one per generation) + best_fitness = getattr(self.ga_instance, "best_solutions_fitness", None) + + if best_fitness is None or len(best_fitness) == 0: + # fallback: pygad also exposes plot_fitness(), but we keep this robust + print("Warning: ga_instance.best_solutions_fitness not found or empty; trying ga_instance.plot_fitness().") + try: + self.ga_instance.plot_fitness() + plt.title("GA fitness over generations") + plt.show() + except Exception as e: + print(f"Could not plot fitness automatically: {e}") + else: + gens = np.arange(1, len(best_fitness) + 1) + plt.figure() + plt.plot(gens, best_fitness) + plt.xlabel("Generation") + plt.ylabel("Best fitness") + plt.title("Best fitness per generation") + plt.tight_layout() + plt.show() + + # --------------------------- + # Plot 2: Auxin at location (336, 56) over time for BEST params + # --------------------------- + print("Generating plot of auxin concentration at location (336, 56)") + + row, col = 336, 56 + + # Re-run ARORA using the best set of parameters (and keep outputs so we can read per-tick JSONs) + timestep = 1 / 9 # hours + vis = False + cell_val_file = "src/sim/input/aux_syndegonly_init_vals.json" + v_file = "src/sim/input/default_vs.json" + geometry = "default" + + best_params = pd.Series(solution, index=self.param_names) + + out_base = "param_est/ARORA_best_solution" + + simulation = GrowingSim( + width=SCREEN_WIDTH, + height=SCREEN_HEIGHT, + title=SCREEN_TITLE, + timestep=timestep, + vis=vis, + cell_val_file=cell_val_file, + v_file=v_file, + gparam_series=best_params, + geometry=geometry, + output_file=out_base, + circ_mod=CircModEnum.AUX_SYN_DEG_EXP, + pin_loc_rules=PinLocalizationRulesetEnum.IMPOSED, + output_frequency=1, + ) + + simulation.run_sim() + + # Extract auxin at (r_query, c_query) for every tick + n_ticks = simulation.get_tick() + aux_vals = [] + times_hrs = [] + + for tick in range(n_ticks): + tick_json = f"{out_base}_tick_{tick}.json" + a = self._auxin_at_rc_from_tick_json(tick_json, r=row, c=col) + aux_vals.append(a) + times_hrs.append(tick * timestep) + + plt.figure() + plt.plot(times_hrs, aux_vals) + plt.xlabel("Time (hours)") + plt.ylabel(f"Auxin at (row={row}, col={col})") + plt.title("Auxin time course at a fixed location (best solution)") + plt.tight_layout() + plt.show() From 0a459043b22186eceff081673422a685d5d0c6a1 Mon Sep 17 00:00:00 2001 From: jannetty Date: Thu, 18 Dec 2025 14:17:13 -0800 Subject: [PATCH 2/4] replaced cleanup --- param_est/ARORA_genetic_alg_imposed_auxsyndegexport.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/param_est/ARORA_genetic_alg_imposed_auxsyndegexport.py b/param_est/ARORA_genetic_alg_imposed_auxsyndegexport.py index 715df8f..47869ac 100644 --- a/param_est/ARORA_genetic_alg_imposed_auxsyndegexport.py +++ b/param_est/ARORA_genetic_alg_imposed_auxsyndegexport.py @@ -66,7 +66,7 @@ def __init__(self, filename: str): self.filename = filename self.population = [] self.param_names = AUX_SYN_DEG_EXPORT_PARAM_NAMES - self.cleanup = False + self.cleanup = True def fitness_function(self, ga_instance, solution, solution_idx): print(f"-----------------------{solution_idx}---------------------------") From e01ceb37edc1c90e7043887217a8c8f46ac41fbf Mon Sep 17 00:00:00 2001 From: jannetty Date: Thu, 18 Dec 2025 14:18:35 -0800 Subject: [PATCH 3/4] removed some troubleshooting print statements --- param_est/ARORA_genetic_alg_imposed_auxsyndegexport.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/param_est/ARORA_genetic_alg_imposed_auxsyndegexport.py b/param_est/ARORA_genetic_alg_imposed_auxsyndegexport.py index 47869ac..83936ec 100644 --- a/param_est/ARORA_genetic_alg_imposed_auxsyndegexport.py +++ b/param_est/ARORA_genetic_alg_imposed_auxsyndegexport.py @@ -221,11 +221,6 @@ def create_arora_csv(self, path: str, chromosome_idx: int,tick: int) -> None: def _calculate_fitness(self, simulation, chromosome): ref0 = "param_est/vdb_data/references/Caux1Array_00000000.csv" - if not os.path.exists(ref0): - raise FileNotFoundError( - f"Missing VDB reference file: {ref0}\n" - "Either the VDB dataset isn't present, path is wrong, or you're running from a different working directory." - ) fitness = arora_vdb_ssd(chromosome['sol_idx']) return fitness @@ -437,7 +432,7 @@ def analyze_results(self): plt.figure() plt.plot(times_hrs, aux_vals) - plt.xlabel("Time (hours)") + plt.xlabel("Time (hours?)") plt.ylabel(f"Auxin at (row={row}, col={col})") plt.title("Auxin time course at a fixed location (best solution)") plt.tight_layout() From f2c3878081126d0e1f2a862c0d6ed257ce9cf25d Mon Sep 17 00:00:00 2001 From: navyacodes Date: Fri, 19 Dec 2025 00:48:34 -0800 Subject: [PATCH 4/4] Fixed fitness function to work with updated ARORA timestep --- param_est/ARORA_genetic_alg_imposed_auxsyndegexport.py | 5 +++-- param_est/fitness_functions.py | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/param_est/ARORA_genetic_alg_imposed_auxsyndegexport.py b/param_est/ARORA_genetic_alg_imposed_auxsyndegexport.py index 83936ec..570801f 100644 --- a/param_est/ARORA_genetic_alg_imposed_auxsyndegexport.py +++ b/param_est/ARORA_genetic_alg_imposed_auxsyndegexport.py @@ -73,6 +73,7 @@ def fitness_function(self, ga_instance, solution, solution_idx): print(f"Chromosome {solution_idx} : {solution}") chromosome = {} chromosome["sol_idx"] = solution_idx + chromosome["generation"] = ga_instance.generations_completed # debugging params = pd.Series(solution, index=self.param_names) for param in self.param_names: chromosome[param] = params[param] @@ -106,6 +107,7 @@ def _cleanup_sim_files(self, chrom_idx: int) -> None: f"param_est/ARORA_output_{chrom_idx}.json", f"param_est/ARORA_output_{chrom_idx}_tick_*.json", f"param_est/ARORA_auxin_output_chrom_{chrom_idx}_tick_*.csv", + f"param_est/ARORA_best_solution_tick_*.json" # added this to clean up the best_solution files too ] for pattern in patterns: @@ -220,7 +222,6 @@ def create_arora_csv(self, path: str, chromosome_idx: int,tick: int) -> None: def _calculate_fitness(self, simulation, chromosome): - ref0 = "param_est/vdb_data/references/Caux1Array_00000000.csv" fitness = arora_vdb_ssd(chromosome['sol_idx']) return fitness @@ -362,7 +363,7 @@ def analyze_results(self): # Plot 1: Best fitness per generation # --------------------------- # pygad keeps this as a list of best fitness values (one per generation) - best_fitness = getattr(self.ga_instance, "best_solutions_fitness", None) + best_fitness = self.ga_instance.best_solutions_fitness if best_fitness is None or len(best_fitness) == 0: # fallback: pygad also exposes plot_fitness(), but we keep this robust diff --git a/param_est/fitness_functions.py b/param_est/fitness_functions.py index fdde6cb..6d4295c 100644 --- a/param_est/fitness_functions.py +++ b/param_est/fitness_functions.py @@ -282,8 +282,8 @@ def parse_and_compute_centroid(location): def arora_vdb_ssd(chromosome_idx: int): total_ssd = 0.0 - reference_filenames = get_filenames("param_est/vdb_data/references/Caux1Array_", 0, 1800, 93600) # not quite sure how the matching of temporal scales is going - output_filenames = [f"param_est/ARORA_auxin_output_chrom_{chromosome_idx}_tick_{tick*5}.csv" for tick in range(52)] + reference_filenames = get_filenames("param_est/vdb_data/references/Caux1Array_", 0, 400, 93600) # not quite sure how the matching of temporal scales is going + output_filenames = [f"param_est/ARORA_auxin_output_chrom_{chromosome_idx}_tick_{tick}.csv" for tick in range(234)] for output_file, reference_file in zip(output_filenames, reference_filenames): total_ssd += np.sum((file_extract_values(output_file) - file_extract_values(reference_file))**2) return -total_ssd