diff --git a/main.py b/main.py index 9d56ec0..5883e28 100644 --- a/main.py +++ b/main.py @@ -9,17 +9,35 @@ import numpy as np import pandas as pd from src.sim.simulation import sim +from src.arora_enums import CircModEnum +from src.arora_enums import PinLocalizationRulesetEnum import pyglet """ File to run simulation """ -DEFAULT_PARAM_NAMES = ["k_s","k_d","k1","k2","k3","k4","k5","k6","tau"] -INDEP_PARAM_NAMES = ["ks_aux","kd_aux","ks_pinu","kd_pinu","kd_pinloc","ks_auxlax","kd_auxlax","k1","k2","k3","k4","k5","k6","tau"] +DEFAULT_PARAM_NAMES = ["k_s", "k_d", "k1", "k2", "k3", "k4", "k5", "k6", "tau"] +INDEP_PARAM_NAMES = [ + "ks_aux", + "kd_aux", + "ks_pinu", + "kd_pinu", + "kd_pinloc", + "ks_auxlax", + "kd_auxlax", + "k1", + "k2", + "k3", + "k4", + "k5", + "k6", + "tau", +] + def make_default_param_series(): - ks_range = 0.0553636 - kd_range = 0.0278859 + ks_range = 0.01 + kd_range = 0.0001 k1_range = 60 k2_range = 64 k3_range = 35 @@ -27,14 +45,58 @@ def make_default_param_series(): k5_range = 0.464545 k6_range = 0.959596 tau_range = 18 - param_vals = [ks_range, kd_range, k1_range, k2_range, k3_range, k4_range, k5_range, k6_range, tau_range] + param_vals = [ + ks_range, + kd_range, + k1_range, + k2_range, + k3_range, + k4_range, + k5_range, + k6_range, + tau_range, + ] return pd.Series(param_vals, index=DEFAULT_PARAM_NAMES) +def make_aux_syn_deg_exp_param_series(): + ks_aux = 1 + kd_aux = .2 + ks_pinu = 0 + kd_pinu = 0 + kd_pinloc = 0 + ks_auxlax = 0 + kd_auxlax = 0 + k1_range = 0 + k2_range = 0 + k3_range = 0 + k4_range = 0 + kal_range = .02 + kpin_range = .02 + tau_range = 1 + param_vals = [ + ks_aux, + kd_aux, + ks_pinu, + kd_pinu, + kd_pinloc, + ks_auxlax, + kd_auxlax, + k1_range, + k2_range, + k3_range, + k4_range, + kal_range, + kpin_range, + tau_range, + ] + return pd.Series(param_vals, index=INDEP_PARAM_NAMES) + + def make_indep_param_series(): ks_aux = 0.266778 kd_aux = 0.0224495 ks_pinu = 0.523434 - kd_pinu = .0176172 + kd_pinu = 0.0176172 kd_pinloc = 0.00191212 ks_auxlax = 0.0040202 kd_auxlax = 0.0257717 @@ -44,56 +106,114 @@ def make_indep_param_series(): k4_range = 58 k5_range = 1 k6_range = 0.975758 - tau_range =3 - param_vals = [ks_aux, kd_aux, ks_pinu, kd_pinu, kd_pinloc, ks_auxlax, kd_auxlax, k1_range, k2_range, k3_range, k4_range, k5_range, k6_range, tau_range] + tau_range = 3 + param_vals = [ + ks_aux, + kd_aux, + ks_pinu, + kd_pinu, + kd_pinloc, + ks_auxlax, + kd_auxlax, + k1_range, + k2_range, + k3_range, + k4_range, + k5_range, + k6_range, + tau_range, + ] return pd.Series(param_vals, index=INDEP_PARAM_NAMES) -def get_simulation_config(circ_mod: str): - if circ_mod == "universal_syndeg": + +def get_simulation_config(circ_mod: CircModEnum): + if circ_mod == CircModEnum.UNIVERSAL_SYN_DEG: return { "cell_val_file": "src/sim/input/default_init_vals_higher_auxinw_in_shootward_vasc.json", "v_file": "src/sim/input/default_vs.json", "gparam_series": make_default_param_series(), } - elif circ_mod == "indep_syndeg": + elif CircModEnum.INDEP_SYN_DEG: return { "cell_val_file": "src/sim/input/indep_syndeg_init_vals.json", "v_file": "src/sim/input/default_vs.json", "gparam_series": make_indep_param_series(), } - elif circ_mod == "aux_syndegonly": + elif circ_mod == CircModEnum.AUX_SYN_DEG_ONLY: return { "cell_val_file": "src/sim/input/aux_syndegonly_init_vals.json", "v_file": "src/sim/input/default_vs.json", "gparam_series": make_default_param_series(), } + elif circ_mod == CircModEnum.AUX_SYN_DEG_EXP: + return { + "cell_val_file": "src/sim/input/aux_syndegonly_init_vals.json", + "v_file": "src/sim/input/default_vs.json", + "gparam_series": make_aux_syn_deg_exp_param_series(), + } else: raise ValueError(f"Unsupported circ_mod: {circ_mod}") -if __name__ == '__main__': + +def get_circ_mod_enum(circ_mod_str: str): + if circ_mod_str == "universal_syndeg" or circ_mod_str == "1": + return CircModEnum.UNIVERSAL_SYN_DEG + elif circ_mod_str == "indep_syndeg" or circ_mod_str == "2": + return CircModEnum.INDEP_SYN_DEG + elif circ_mod_str == "aux_syndegonly" or circ_mod_str == "3": + return CircModEnum.AUX_SYN_DEG_ONLY + elif circ_mod_str == "aux_syndegtrans" or circ_mod_str == "4": + return CircModEnum.AUX_SYN_DEG_EXP + else: + raise ValueError(f"Unsupported circ_mod: {circ_mod}") + + +def get_pin_loc_rules_enum(pin_loc_rules_str: str): + if pin_loc_rules_str == "simple_inheritance" or pin_loc_rules_str == "1": + return PinLocalizationRulesetEnum.SIMPLE_INHERITANCE + elif pin_loc_rules_str == "imposed" or pin_loc_rules_str == "2": + return PinLocalizationRulesetEnum.IMPOSED + + +if __name__ == "__main__": parser = argparse.ArgumentParser(description="Run ARORA Simulation") - parser.add_argument("--circ_mod", type=str, default="universal_syndeg", - choices=["universal_syndeg", "indep_syndeg", "aux_syndegonly"], - help="Which circulation module to use") parser.add_argument( - "--output_file", type=str, default="output", - help="Base name for the output files (without extension)" -) + "--circ_mod", + type=str, + default="universal_syndeg", + choices=["universal_syndeg", "1", "indep_syndeg", "2", "aux_syndegonly", "3", "aux_syndegtrans", "4"], + help="Which circulation module to use", + ) + parser.add_argument( + "--output_file", + type=str, + default="output", + help="Base name for the output files (without extension)", + ) + parser.add_argument( + "--pin_loc_rules", + type=str, + default="simple_inheritance", + choices=["simple_inheritance", "1", "imposed", "2"], + help="Which PIN localization ruleset to use", + ) args = parser.parse_args() - circ_mod = args.circ_mod - timestep = 1 + circ_mod = get_circ_mod_enum(args.circ_mod) + timestep = .2 vis = True start_time = time.time() config = get_simulation_config(circ_mod) sim.main( timestep, vis, + pin_loc_rules=get_pin_loc_rules_enum(args.pin_loc_rules), + circ_mod = circ_mod, cell_val_file=config["cell_val_file"], v_file=config["v_file"], gparam_series=config["gparam_series"], - output_file=args.output_file + output_file=args.output_file, ) end_time = time.time() elapsed_time = end_time - start_time - print(f"Elapsed Time: {elapsed_time} seconds") \ No newline at end of file + print(f"Elapsed Time: {elapsed_time} seconds") diff --git a/param_est/ARORA_genetic_alg.py b/param_est/ARORA_genetic_alg.py index a97a957..1af8167 100644 --- a/param_est/ARORA_genetic_alg.py +++ b/param_est/ARORA_genetic_alg.py @@ -1,306 +1,347 @@ -import json -import os -import platform - - -if platform.system() == "Linux": - os.environ["ARCADE_HEADLESS"] = "True" -import numpy as np -import pandas as pd -import pygad -from pygad import GA -from param_est.fitness_functions import ( - auxin_greater_in_larger_cells_at_trans_elon_interface, - avg_auxin_root_tip_greater_than_elsewhere, - parity_of_mz_auxin_concentrations_with_VDB_data, - parity_of_auxin_c_for_xpp_boundary_cell_at_each_time_point, -) -from src.sim.simulation.sim import GrowingSim - -SCREEN_WIDTH = 1000 -SCREEN_HEIGHT = 1000 -SCREEN_TITLE = "ARORA" - -DEFAULT_PARAM_NAMES = ["k_s", "k_d", "k1", "k2", "k3", "k4", "k5", "k6", "tau"] -INDEP_SYN_DEG_PARAM_NAMES = [ - "ks_aux", - "kd_aux", - "ks_pinu", - "kd_pinu", - "kd_pinloc", - "ks_auxlax", - "kd_auxlax", - "k1", - "k2", - "k3", - "k4", - "k5", - "k6", - "tau", -] -AUX_SYN_DEG_PARAM_NAMES = [ - "ks_aux", - "kd_aux", - "k1", - "k2", - "k3", - "k4", - "k5", - "k6", - "tau", -] - - -class ARORAGeneticAlg: - def __init__(self, filename: str, circ_mod: str): - self.ga_instance = None - self.filename = filename - self.population = [] - if circ_mod == "auxsyndeg_only": - self.param_names = AUX_SYN_DEG_PARAM_NAMES - elif circ_mod == "indep_syn_deg": - self.param_names = INDEP_SYN_DEG_PARAM_NAMES - else: - raise ValueError("Invalid circ_mod") - - def fitness_function(self, ga_instance, solution, solution_idx): - print(f"-----------------------{solution_idx}---------------------------") - print(f"Chromosome {solution_idx} : {solution}") - chromosome = {} - chromosome["sol_idx"] = solution_idx - params = pd.Series(solution, index=self.param_names) - for param in self.param_names: - chromosome[param] = params[param] - if not self._check_constraints(params, chromosome): - print("Invalid solution") - cost = np.inf - else: - print(f"Running ARORA with params: {params}") - fitness = self._run_ARORA(params, chromosome) - chromosome["fitness"] = fitness - self.population.append(chromosome) - print(f"Chromosome entry: {chromosome}") - with open(self.filename, "w") as f: - json.dump(self.population, f, indent=4) - return fitness - - def _check_constraints(self, params, chromosome): - # Check constraints here - # ks = params['k_s'] - # kd = params['k_d'] - # Add more constraints as needed - # if ks <= kd: - # print("k_s must be greater than k_d") - # return False - return True - - def _run_ARORA(self, params, chromosome): - timestep = 1 - vis = False - if self.param_names == AUX_SYN_DEG_PARAM_NAMES: - cell_val_file = "src/sim/input/aux_syndegonly_init_vals.json" - elif self.param_names == INDEP_SYN_DEG_PARAM_NAMES: - cell_val_file = "src/sim/input/indep_syn_deg_init_vals.json" - v_file = "src/sim/input/default_vs.json" - gparam_series = params - geometry = "default" - simulation = GrowingSim( - SCREEN_WIDTH, - SCREEN_HEIGHT, - SCREEN_TITLE, - timestep, - root_midpoint_x, - vis, - cell_val_file, - v_file, - gparam_series, - geometry, - f"param_est/ARORA_output_{chromosome['sol_idx']}", - ) - simulation.setup() - try: - simulation.run_sim() - chromosome["finished"] = True - fitness = self._calculate_fitness(simulation, chromosome) - try: - os.remove(f"param_est/ARORA_output_{chromosome['sol_idx']}.csv") - os.remove(f"param_est/ARORA_output_{chromosome['sol_idx']}.json") - except Exception as e: - print(e) - except Exception as e: - print(e) - chromosome["exception"] = str(e) - chromosome["finished"] = False - tick = simulation.get_tick() - chromosome["tick"] = tick - print("Fitness set to -infinity") - fitness = -np.inf - try: - os.remove(f"param_est/ARORA_output_{chromosome['sol_idx']}.csv") - os.remove(f"param_est/ARORA_output_{chromosome['sol_idx']}.json") - except Exception as e: - print(e) - return fitness - - # def _calculate_fitness_original(self, simulation, chromosome): - # # calculate fitness - # fitness = ( - # 100 - # * auxin_greater_in_larger_cells_at_trans_elon_interface( - # simulation, chromosome - # ) - # ) + avg_auxin_root_tip_greater_than_elsewhere(simulation, chromosome) - # chromosome["auxin_corr_with_cell_size"] = ( - # 100 - # * auxin_greater_in_larger_cells_at_trans_elon_interface( - # simulation, chromosome - # ) - # ) - # chromosome["auxin_peak_at_root_tip"] = ( - # avg_auxin_root_tip_greater_than_elsewhere(simulation, chromosome) - # ) - # print(f"auxin_corr_with_cell_size: {chromosome['auxin_corr_with_cell_size']}") - # print(f"Auxin peak at root tip: {chromosome['auxin_peak_at_root_tip']}") - # print(f"Fitness: {fitness}") - # return fitness - - def _calculate_fitness(self, simulation, chromosome): - # calculate fitness - fitness = parity_of_mz_auxin_concentrations_with_VDB_data( - simulation, chromosome - ) + parity_of_auxin_c_for_xpp_boundary_cell_at_each_time_point( - simulation, chromosome - ) - return fitness - - def make_paramspace_ks_kd(self): - ks_range = np.linspace(0.0001, 3, 1000).astype(float) - kd_range = np.linspace(0.00001, 0.3, 1000).astype(float) - k1_range = np.linspace(1, 1600, 1510).astype(int) - k2_range = np.linspace(5, 1000, 510).astype(int) - k3_range = np.linspace(1, 750, 660).astype(int) - k4_range = np.linspace(5, 1000, 510).astype(int) - k5_range = np.linspace(0.007, 10, 1000).astype(float) - k6_range = np.linspace(0.02, 10, 1000).astype(float) - tau_range = np.linspace(1, 240, 240).astype(int) - return [ - ks_range, - kd_range, - k1_range, - k2_range, - k3_range, - k4_range, - k5_range, - k6_range, - tau_range, - ] - - def make_paramspace_indep_syn_deg(self): - ks_aux_range = np.linspace(0.001, 0.3, 100).astype(float) - kd_aux_range = np.linspace(0.0001, 0.03, 100).astype(float) - ks_pinu_range = np.linspace(0.001, 0.3, 100).astype(float) - kd_pinu_range = np.linspace(0.0001, 0.03, 100).astype(float) - kd_pinloc_range = np.linspace(0.0001, 0.03, 100).astype(float) - ks_auxlax_range = np.linspace(0.001, 0.3, 100).astype(float) - kd_auxlax_range = np.linspace(0.0001, 0.03, 100).astype(float) - k1_range = np.linspace(10, 160, 151).astype(int) - k2_range = np.linspace(50, 100, 51).astype(int) - k3_range = np.linspace(10, 75, 66).astype(int) - k4_range = np.linspace(50, 100, 51).astype(int) - k5_range = np.linspace(0.07, 1, 100).astype(float) - k6_range = np.linspace(0.2, 1, 100).astype(float) - tau_range = np.linspace(1, 24, 24).astype(int) - return [ - ks_aux_range, - kd_aux_range, - ks_pinu_range, - kd_pinu_range, - kd_pinloc_range, - ks_auxlax_range, - kd_auxlax_range, - k1_range, - k2_range, - k3_range, - k4_range, - k5_range, - k6_range, - tau_range, - ] - - def make_paramspace_aux_syn_deg(self): - ks_aux_range = np.linspace(0.001, 0.3, 100).astype(float) - kd_aux_range = np.linspace(0.0001, 0.03, 100).astype(float) - k1_range = np.linspace(10, 160, 151).astype(int) - k2_range = np.linspace(50, 100, 51).astype(int) - k3_range = np.linspace(10, 75, 66).astype(int) - k4_range = np.linspace(50, 100, 51).astype(int) - k5_range = np.linspace(0.07, 1, 100).astype(float) - k6_range = np.linspace(0.2, 1, 100).astype(float) - tau_range = np.linspace(1, 24, 24).astype(int) - return [ - ks_aux_range, - kd_aux_range, - k1_range, - k2_range, - k3_range, - k4_range, - k5_range, - k6_range, - tau_range, - ] - - def run_genetic_alg(self): - if self.param_names == AUX_SYN_DEG_PARAM_NAMES: - genespace = self.make_paramspace_aux_syn_deg() - elif self.param_names == INDEP_SYN_DEG_PARAM_NAMES: - genespace = self.make_paramspace_indep_syn_deg() - ga_parameters = { - "num_generations":20, - "num_parents_mating": 25, - "fitness_func": self.fitness_function, - "sol_per_pop": 50, - "num_genes": len(genespace), - "gene_space": genespace, - "mutation_percent_genes": 5, - "save_best_solutions": False, - "parent_selection_type": "sss", - } - ga_parameters_for_saving = { - "num_generations": 20, - "num_parents_mating": 5, - "fitness_func": "parity_of_mz_auxin_concentrations_with_VDB_data + parity_of_auxin_c_for_xpp_boundary_cell_at_each_time_point", - "negative_corr_sets_fitness_to_neg_inf": False, - "sol_per_pop": 20, - "num_genes": len(genespace), - "gene_space": "ks .001 to .3, kd .0001 to .03, k1 10 to 160, k2 50 to 100, k3 10 to 75, k4 50 to 100, k5 .07 to 1, k6 .2 to 1, tau 1 to 24", - "mutation_percent_genes": 5, - "save_best_solutions": False, - "parent_selection_type": "sss", - "initialization_file": "aux_syndegonly_init_vals.json", - "hours_per_simulation": 27, - } - self.population.append(ga_parameters_for_saving) - # with open(self.filename, 'w') as f: - # json.dump({'GA_parameters': ga_parameters_for_saving}, f, indent=4) - # Initialize the GA with the parameters - self.ga_instance = pygad.GA(**ga_parameters) - - self.ga_instance.run() - - def on_gen(self, ga_instance): - print("Generation : ", ga_instance.generations_completed) - print("Fitness of the best solution :", ga_instance.best_solution()[1]) - - def analyze_results(self): - 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( - solution_fitness=solution_fitness - ) - ) - print( - "Index of the best solution : {solution_idx}".format( - solution_idx=solution_idx - ) - ) +import json +import os +import platform +import csv +from skimage.draw import polygon + +from src.arora_enums import CircModEnum, PinLocalizationRulesetEnum + + +if platform.system() == "Linux": + os.environ["ARCADE_HEADLESS"] = "True" +import numpy as np +import pandas as pd +import pygad +from pygad import GA +from param_est.fitness_functions import ( + auxin_greater_in_larger_cells_at_trans_elon_interface, + avg_auxin_root_tip_greater_than_elsewhere, + parity_of_mz_auxin_concentrations_with_VDB_data, + parity_of_auxin_c_for_xpp_boundary_cell_at_each_time_point, + arora_vdb_ssd +) +from src.sim.simulation.sim import GrowingSim + +SCREEN_WIDTH = 1000 +SCREEN_HEIGHT = 1000 +SCREEN_TITLE = "ARORA" + +DEFAULT_PARAM_NAMES = ["k_s", "k_d", "k1", "k2", "k3", "k4", "k5", "k6", "tau"] +INDEP_SYN_DEG_PARAM_NAMES = [ + "ks_aux", + "kd_aux", + "ks_pinu", + "kd_pinu", + "kd_pinloc", + "ks_auxlax", + "kd_auxlax", + "k1", + "k2", + "k3", + "k4", + "k5", + "k6", + "tau", +] +AUX_SYN_DEG_PARAM_NAMES = [ + "ks_aux", + "kd_aux", + "k1", + "k2", + "k3", + "k4", + "k5", + "k6", + "tau", +] + +def fix_json(path: str): + with open(path, 'r') as file: + file_content = file.read() + find_text = "][" + replace_text = "],[" + modified_content = file_content.replace(find_text, replace_text) + with open(path, 'w') as file: + file.write("[" + modified_content + "]") + +class ARORAGeneticAlg: + def __init__(self, filename: str, circ_mod: str): + self.ga_instance = None + self.filename = filename + self.population = [] + if circ_mod == "auxsyndeg_only": + self.param_names = AUX_SYN_DEG_PARAM_NAMES + elif circ_mod == "indep_syn_deg": + self.param_names = INDEP_SYN_DEG_PARAM_NAMES + else: + raise ValueError("Invalid circ_mod") + + def fitness_function(self, ga_instance, solution, solution_idx): + print(f"-----------------------{solution_idx}---------------------------") + print(f"Chromosome {solution_idx} : {solution}") + chromosome = {} + chromosome["sol_idx"] = solution_idx + params = pd.Series(solution, index=self.param_names) + for param in self.param_names: + chromosome[param] = params[param] + if not self._check_constraints(params, chromosome): + print("Invalid solution") + cost = np.inf + else: + print(f"Running ARORA with params: {params}") + fitness = self._run_ARORA(params, chromosome) + chromosome["fitness"] = fitness + self.population.append(chromosome) + print(f"Chromosome entry: {chromosome}") + with open(self.filename, "w") as f: + json.dump(self.population, f, indent=4) + return fitness + + def _check_constraints(self, params, chromosome): + # Check constraints here + # ks = params['k_s'] + # kd = params['k_d'] + # Add more constraints as needed + # if ks <= kd: + # print("k_s must be greater than k_d") + # return False + return True + + def _run_ARORA(self, params, chromosome): + timestep = 1 + vis = False + if self.param_names == AUX_SYN_DEG_PARAM_NAMES: + cell_val_file = "src/sim/input/aux_syndegonly_init_vals.json" + elif self.param_names == INDEP_SYN_DEG_PARAM_NAMES: + cell_val_file = "src/sim/input/indep_syn_deg_init_vals.json" + v_file = "src/sim/input/default_vs.json" + gparam_series = params + geometry = "default" + simulation = GrowingSim( + width=SCREEN_WIDTH, + height=SCREEN_HEIGHT, + title=SCREEN_TITLE, + timestep=timestep, + # root_midpoint_x, + vis=vis, + cell_val_file=cell_val_file, + v_file=v_file, + gparam_series=gparam_series, + geometry=geometry, + output_file=f"param_est/ARORA_output_{chromosome['sol_idx']}", + + circ_mod=CircModEnum.AUX_SYN_DEG_ONLY, + pin_loc_rules=PinLocalizationRulesetEnum.IMPOSED, + ) + simulation.setup() + try: + simulation.run_sim() + chromosome["finished"] = True + self.create_arora_csv(f"param_est/ARORAoutput{chromosome['sol_idx']}.json") + fitness = self._calculate_fitness(simulation, chromosome) + try: + os.remove(f"param_est/ARORAoutput{chromosome['sol_idx']}.csv") + os.remove(f"param_est/ARORAoutput{chromosome['sol_idx']}.json") + except Exception as e: + print(e) + except Exception as e: + print(e) + chromosome["exception"] = str(e) + chromosome["finished"] = False + tick = simulation.get_tick() + chromosome["tick"] = tick + print("Fitness set to -infinity") + fitness = -np.inf + try: + os.remove(f"param_est/ARORAoutput{chromosome['sol_idx']}.csv") + os.remove(f"param_est/ARORAoutput{chromosome['sol_idx']}.json") + except Exception as e: + print(e) + return fitness + + def create_arora_csv(path: str): + fix_json(path) + with open(path) as file: + data = json.load(file) + + for i in range(len(data)): # ticks + arr = np.zeros((1207, 142)) + + ymin = 0 + for j in range(len(data[i])): # cells + for k in range(4): + ymin = min(ymin, data[i][j]["location"][k][1]) + + for j in range(len(data[i])): # cells + auxin = data[i][j]["auxin"] + location = np.array(data[i][j]["location"]) + if (ymin < 0): + for k in range(4): + location[k][1] += abs(ymin) + rr, cc = polygon(location[:, 1], location[:, 0], arr.shape) + arr[rr, cc] = auxin + np.savetxt(f'output/ARORAoutput{i}.csv', arr, delimiter=",", fmt="%f") + + # def _calculate_fitness_original(self, simulation, chromosome): + # # calculate fitness + # fitness = ( + # 100 + # * auxin_greater_in_larger_cells_at_trans_elon_interface( + # simulation, chromosome + # ) + # ) + avg_auxin_root_tip_greater_than_elsewhere(simulation, chromosome) + # chromosome["auxin_corr_with_cell_size"] = ( + # 100 + # * auxin_greater_in_larger_cells_at_trans_elon_interface( + # simulation, chromosome + # ) + # ) + # chromosome["auxin_peak_at_root_tip"] = ( + # avg_auxin_root_tip_greater_than_elsewhere(simulation, chromosome) + # ) + # print(f"auxin_corr_with_cell_size: {chromosome['auxin_corr_with_cell_size']}") + # print(f"Auxin peak at root tip: {chromosome['auxin_peak_at_root_tip']}") + # print(f"Fitness: {fitness}") + # return fitness + + def _calculate_fitness(self, simulation, chromosome): + # calculate fitness + # fitness = parity_of_mz_auxin_concentrations_with_VDB_data( + # simulation, chromosome + # ) + parity_of_auxin_c_for_xpp_boundary_cell_at_each_time_point( + # simulation, chromosome + # ) + fitness = arora_vdb_ssd() + return fitness + + def make_paramspace_ks_kd(self): + ks_range = np.linspace(0.0001, 3, 1000).astype(float) + kd_range = np.linspace(0.00001, 0.3, 1000).astype(float) + k1_range = np.linspace(1, 1600, 1510).astype(int) + k2_range = np.linspace(5, 1000, 510).astype(int) + k3_range = np.linspace(1, 750, 660).astype(int) + k4_range = np.linspace(5, 1000, 510).astype(int) + k5_range = np.linspace(0.007, 10, 1000).astype(float) + k6_range = np.linspace(0.02, 10, 1000).astype(float) + tau_range = np.linspace(1, 240, 240).astype(int) + return [ + ks_range, + kd_range, + k1_range, + k2_range, + k3_range, + k4_range, + k5_range, + k6_range, + tau_range, + ] + + def make_paramspace_indep_syn_deg(self): + ks_aux_range = np.linspace(0.001, 0.3, 100).astype(float) + kd_aux_range = np.linspace(0.0001, 0.03, 100).astype(float) + ks_pinu_range = np.linspace(0.001, 0.3, 100).astype(float) + kd_pinu_range = np.linspace(0.0001, 0.03, 100).astype(float) + kd_pinloc_range = np.linspace(0.0001, 0.03, 100).astype(float) + ks_auxlax_range = np.linspace(0.001, 0.3, 100).astype(float) + kd_auxlax_range = np.linspace(0.0001, 0.03, 100).astype(float) + k1_range = np.linspace(10, 160, 151).astype(int) + k2_range = np.linspace(50, 100, 51).astype(int) + k3_range = np.linspace(10, 75, 66).astype(int) + k4_range = np.linspace(50, 100, 51).astype(int) + k5_range = np.linspace(0.07, 1, 100).astype(float) + k6_range = np.linspace(0.2, 1, 100).astype(float) + tau_range = np.linspace(1, 24, 24).astype(int) + return [ + ks_aux_range, + kd_aux_range, + ks_pinu_range, + kd_pinu_range, + kd_pinloc_range, + ks_auxlax_range, + kd_auxlax_range, + k1_range, + k2_range, + k3_range, + k4_range, + k5_range, + k6_range, + tau_range, + ] + + def make_paramspace_aux_syn_deg(self): + ks_aux_range = np.linspace(0.001, 0.3, 100).astype(float) + kd_aux_range = np.linspace(0.0001, 0.03, 100).astype(float) + k1_range = np.linspace(10, 160, 151).astype(int) + k2_range = np.linspace(50, 100, 51).astype(int) + k3_range = np.linspace(10, 75, 66).astype(int) + k4_range = np.linspace(50, 100, 51).astype(int) + k5_range = np.linspace(0.07, 1, 100).astype(float) + k6_range = np.linspace(0.2, 1, 100).astype(float) + tau_range = np.linspace(1, 24, 24).astype(int) + return [ + ks_aux_range, + kd_aux_range, + k1_range, + k2_range, + k3_range, + k4_range, + k5_range, + k6_range, + tau_range, + ] + + def run_genetic_alg(self): + if self.param_names == AUX_SYN_DEG_PARAM_NAMES: + genespace = self.make_paramspace_aux_syn_deg() + elif self.param_names == INDEP_SYN_DEG_PARAM_NAMES: + genespace = self.make_paramspace_indep_syn_deg() + ga_parameters = { + "num_generations":20, + "num_parents_mating": 25, + "fitness_func": self.fitness_function, + "sol_per_pop": 50, + "num_genes": len(genespace), + "gene_space": genespace, + "mutation_percent_genes": 5, + "save_best_solutions": False, + "parent_selection_type": "sss", + } + ga_parameters_for_saving = { + "num_generations": 20, + "num_parents_mating": 5, + "fitness_func": "parity_of_mz_auxin_concentrations_with_VDB_data + parity_of_auxin_c_for_xpp_boundary_cell_at_each_time_point", + "negative_corr_sets_fitness_to_neg_inf": False, + "sol_per_pop": 20, + "num_genes": len(genespace), + "gene_space": "ks .001 to .3, kd .0001 to .03, k1 10 to 160, k2 50 to 100, k3 10 to 75, k4 50 to 100, k5 .07 to 1, k6 .2 to 1, tau 1 to 24", + "mutation_percent_genes": 5, + "save_best_solutions": False, + "parent_selection_type": "sss", + "initialization_file": "aux_syndegonly_init_vals.json", + "hours_per_simulation": 27, + } + self.population.append(ga_parameters_for_saving) + # with open(self.filename, 'w') as f: + # json.dump({'GA_parameters': ga_parameters_for_saving}, f, indent=4) + # Initialize the GA with the parameters + self.ga_instance = pygad.GA(**ga_parameters) + + self.ga_instance.run() + + def on_gen(self, ga_instance): + print("Generation : ", ga_instance.generations_completed) + print("Fitness of the best solution :", ga_instance.best_solution()[1]) + + def analyze_results(self): + 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( + solution_fitness=solution_fitness + ) + ) + print( + "Index of the best solution : {solution_idx}".format( + solution_idx=solution_idx + ) + ) diff --git a/param_est/ARORA_genetic_alg_imposed_auxsyndegexport.py b/param_est/ARORA_genetic_alg_imposed_auxsyndegexport.py new file mode 100644 index 0000000..18a5fbb --- /dev/null +++ b/param_est/ARORA_genetic_alg_imposed_auxsyndegexport.py @@ -0,0 +1,314 @@ +import json +import os +import platform +import csv +import glob +from skimage.draw import polygon + +from src.arora_enums import CircModEnum, PinLocalizationRulesetEnum + + +if platform.system() == "Linux": + os.environ["ARCADE_HEADLESS"] = "True" +import numpy as np +import pandas as pd +import pygad +from pygad import GA +from param_est.fitness_functions import ( + auxin_greater_in_larger_cells_at_trans_elon_interface, + avg_auxin_root_tip_greater_than_elsewhere, + parity_of_mz_auxin_concentrations_with_VDB_data, + parity_of_auxin_c_for_xpp_boundary_cell_at_each_time_point, + arora_vdb_ssd +) +from src.sim.simulation.sim import GrowingSim + +SCREEN_WIDTH = 1000 +SCREEN_HEIGHT = 1000 +SCREEN_TITLE = "ARORA" + +DEFAULT_PARAM_NAMES = ["k_s", "k_d", "k1", "k2", "k3", "k4", "k5", "k6", "tau"] + +AUX_SYN_DEG_EXPORT_PARAM_NAMES = [ + "ks_aux", + "kd_aux", + "k1", + "k2", + "k3", + "k4", + "k5", + "k6", + "tau", +] + +def make_dumpable(obj): # lol we have to change the name of this but right now this dumbness is keeping me going + import numpy as np + + # numpy scalars -> Python scalars + if isinstance(obj, (np.integer, np.int_, np.int64)): + return int(obj) + if isinstance(obj, (np.floating, np.float_, np.float64)): + return float(obj) + if isinstance(obj, (np.bool_,)): + return bool(obj) + + # numpy arrays -> lists + if isinstance(obj, np.ndarray): + return obj.tolist() + + # Raise TypeError if anything isn't a scalar or list (should never be the case but who knows) + raise TypeError(f"Object of type {type(obj).__name__} is not JSON serializable") + +class ARORAGeneticAlgImposedAuxinSynDegExport: + def __init__(self, filename: str): + self.ga_instance = None + self.filename = filename + self.population = [] + self.param_names = AUX_SYN_DEG_EXPORT_PARAM_NAMES + + def fitness_function(self, ga_instance, solution, solution_idx): + print(f"-----------------------{solution_idx}---------------------------") + print(f"Chromosome {solution_idx} : {solution}") + chromosome = {} + chromosome["sol_idx"] = solution_idx + params = pd.Series(solution, index=self.param_names) + for param in self.param_names: + chromosome[param] = params[param] + if not self._check_constraints(params, chromosome): + print("Invalid solution") + cost = np.inf + else: + print(f"Running ARORA with params: {params}") + fitness = self._run_ARORA(params, chromosome) + chromosome["fitness"] = fitness + self.population.append(chromosome) + print(f"Chromosome entry: {chromosome}") + with open(self.filename, "w") as f: + json.dump(self.population, f, indent=4, default=make_dumpable) + return fitness + + def _check_constraints(self, params, chromosome): + # Check constraints here + # ks = params['k_s'] + # kd = params['k_d'] + # Add more constraints as needed + # if ks <= kd: + # print("k_s must be greater than k_d") + # return False + return True + + def _cleanup_sim_files(self, chrom_idx: int) -> None: + """Delete all intermediate files produced for a given chromosome index.""" + patterns = [ + f"param_est/ARORA_output_{chrom_idx}.csv", + 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", + ] + + for pattern in patterns: + for path in glob.glob(pattern): + try: + os.remove(path) + print(f"Deleted {path}") + except FileNotFoundError: + pass + except OSError as e: + 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. + vis = False + cell_val_file = "src/sim/input/aux_syndegonly_init_vals.json" + v_file = "src/sim/input/default_vs.json" + gparam_series = params + geometry = "default" + + 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=gparam_series, + geometry=geometry, + 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 + ) + + try: + simulation.run_sim() + chromosome["finished"] = True + ticks = range(simulation.get_tick()) + for tick in ticks: + self.create_arora_csv(f"param_est/ARORA_output_{chromosome['sol_idx']}_tick_{tick}.json", chromosome['sol_idx'], tick) + fitness = self._calculate_fitness(simulation, chromosome) + except Exception as e: + print(e) + chromosome["exception"] = str(e) + chromosome["finished"] = False + tick = simulation.get_tick() + chromosome["tick"] = tick + print("Fitness set to -infinity") + fitness = -np.inf + finally: + self._cleanup_sim_files(chromosome["sol_idx"]) + return fitness + + def create_arora_csv(self, path: str, chromosome_idx: int,tick: int) -> None: + """ + Convert a per-tick ARORA JSON file into a 2D auxin CSV image. + + New JSON structure (per file): + [ + { + "tick": , + "auxin": , + "location": [[x0, y0], [x1, y1], [x2, y2], [x3, y3]], + ... + }, + ... + ] + + This function: + - Loads all cell records for a single tick. + - Computes the minimum y across all polygon corners (ymin). + - If ymin < 0, shifts all y-coordinates up by |ymin|. + - Rasterizes each cell polygon into a (1207, 142) array (aligning with dimensions of VDB sim space), + filling it with the cell's auxin value. + - Saves the resulting 2D auxin field as a CSV file named with the tick. + """ + with open(path) as file: + cells = json.load(file) # list of cell dicts for a single tick + + # Initialize auxin image + arr = np.zeros((1207, 142), dtype=float) + + # --- Compute ymin across all cells --- + ymin = 0 + for cell in cells: + loc = cell["location"] + for corner in loc: + y = corner[1] + ymin = min(ymin, y) + + # --- Rasterize each cell into the auxin array --- + for cell in cells: + auxin = cell["auxin"] + location = np.array(cell["location"], dtype=float) # shape (4, 2), columns [x, y] + + if ymin < 0: + # Shift y-coordinates up so that they are non-negative + location[:, 1] += abs(ymin) + + # polygon() expects (r, c) = (y, x) + rr, cc = polygon(location[:, 1], location[:, 0], arr.shape) + arr[rr, cc] = auxin + + # --- Save to CSV --- + out_path = f"param_est/ARORA_auxin_output_chrom_{chromosome_idx}_tick_{tick}.csv" + np.savetxt(out_path, arr, delimiter=",", fmt="%f") + + + def _calculate_fitness(self, simulation, chromosome): + fitness = 100 # :) dummy fitness function lol + return fitness + + def make_paramspace_aux_syn_deg_trans(self): + # ks_aux: auxin synthesis rate [a.u./h]. + # Chosen so that A_ss = ks_aux * auxin_w / kd_aux spans ~1–200 a.u. over kd_aux range. + ks_aux_range = np.geomspace(0.1, 10.0, 100).astype(float) + + # kd_aux: auxin degradation rate [1/h]. + # Half-life = ln(2)/kd_aux ≈ 1.4–13.9 h → “few hours to half-day” auxin turnover. + kd_aux_range = np.geomspace(0.05, 0.5, 100).astype(float) + + # --- Parameters not used by this circ mod --- + + # k1–k4: ARR / AUX-LAX / PIN regulatory couplings. + k1_range = 0 + k2_range = 0 + k3_range = 0 + k4_range = 0 + + # k5: k_al, AUX/LAX-mediated exchange factor [1/h]. + k5_range = 1 + + # k6: k_pin, PIN-mediated export factor [1/h]. + k_pin_range = np.geomspace(0.05, 0.5, 40) + + # tau: time course of ARR's self- repression + tau_range = 1 + + return [ + ks_aux_range, + kd_aux_range, + k1_range, + k2_range, + k3_range, + k4_range, + k5_range, + k_pin_range, + tau_range, + ] + + def run_genetic_alg(self): + genespace = self.make_paramspace_aux_syn_deg_trans() + # make GA hyperparameters + num_generations = 20 + num_parents_mating = 25 + sol_per_pop = 50 + fitness_function = "descriptive string" # also dummy :) + mutation_percent_genes = 5 # this is really low + save_best_solutions = False + parent_selection_type = "sss" + ga_parameters = { + "num_generations":num_generations, + "num_parents_mating": num_parents_mating, + "fitness_func": self.fitness_function, + "sol_per_pop": sol_per_pop, + "num_genes": len(genespace), + "gene_space": genespace, + "mutation_percent_genes": mutation_percent_genes, + "save_best_solutions": save_best_solutions, + "parent_selection_type": parent_selection_type, + } + ga_parameters_for_saving = { + "num_generations": num_generations, + "num_parents_mating": num_parents_mating, + "fitness_func": fitness_function, + "sol_per_pop":sol_per_pop, + "num_genes": len(genespace), + "gene_space": genespace, + "mutation_percent_genes": mutation_percent_genes, + "save_best_solutions": save_best_solutions, + "parent_selection_type": parent_selection_type, + "initialization_file": "aux_syndegonly_init_vals.json", + "hours_per_simulation": 27, + } + self.population.append(ga_parameters_for_saving) + self.ga_instance = pygad.GA(**ga_parameters) + print("Running GA!") + self.ga_instance.run() + + def on_gen(self, ga_instance): + print("Generation : ", ga_instance.generations_completed) + print("Fitness of the best solution :", ga_instance.best_solution()[1]) + + def analyze_results(self): + 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( + solution_fitness=solution_fitness + ) + ) + print( + "Index of the best solution : {solution_idx}".format( + solution_idx=solution_idx + ) + ) diff --git a/param_est/fitness_functions.py b/param_est/fitness_functions.py index 3fa08b6..1278229 100644 --- a/param_est/fitness_functions.py +++ b/param_est/fitness_functions.py @@ -1,280 +1,307 @@ -import numpy as np -import pandas as pd -import ast -from scipy.stats import spearmanr - -from src.sim.simulation.sim import GrowingSim -from src.agent.cell import Cell - - -def avg_auxin_root_tip_greater_than_elsewhere(sim: GrowingSim, chromosome: dict) -> float: - """ - Returns the ratio of the average auxin concentration in non-root tip cells - to the average auxin concentration in root tip cells. - - Parameters - ---------- - sim : GrowingSima - The simulation object containing the cells. - chromosome: Dict - The dictionary being populated with information about this simulation's run - - Returns - ------- - float - The ratio of the average auxin concentration in root tip cells to non-root tip cells - """ - non_root_tip_auxins = [cell.get_circ_mod().get_auxin() for cell in sim.cell_list if cell.get_dev_zone() != 'roottip'] - root_tip_auxins = [cell.get_circ_mod().get_auxin() for cell in sim.cell_list if cell.get_dev_zone() == 'roottip'] - avg_non_root_tip_auxins = sum(non_root_tip_auxins)/len(non_root_tip_auxins) - avg_root_tip_auxins = sum(root_tip_auxins)/len(root_tip_auxins) - return avg_root_tip_auxins/avg_non_root_tip_auxins # maximizing this - -def auxin_greater_in_larger_cells_at_trans_elon_interface(sim: GrowingSim, chromosome: dict) -> float: - """ - """ - transition_and_elongation_cells = [cell for cell in sim.cell_list if (cell.get_dev_zone() == 'transition' or cell.get_dev_zone() == 'elongation')] - xpp_trans_and_elon_cells = [cell for cell in transition_and_elongation_cells if (cell.get_cell_type() == 'peri')] - # get correlation coefficient between cell size and auxin concentration in xpp_trans_and_elon_cells - # Trying with all cells - areas = [cell.get_quad_perimeter().get_area() for cell in xpp_trans_and_elon_cells] - auxins = [cell.get_circ_mod().get_auxin() for cell in xpp_trans_and_elon_cells] - corr_coeff = spearmanr(areas, auxins).statistic - if corr_coeff < 0: - print(f"Inverse correlation between xpp cell size and auxin concentration in transition and elongation zone. Fitness set to {abs(corr_coeff)}.") - print(f"corr_coef = {corr_coeff}") - chromosome["notes"] = f"Inverse correlation between xpp cell size and auxin concentration in transition and elongation zone. Fitness set to {abs(corr_coeff)}." - return abs(corr_coeff) #we want there to be a strong positive correlation - return abs(corr_coeff) - -def auxin_oscillation_across_XPP_cells_in_OZ(sim: GrowingSim, chromosome: dict) -> float: - """ - Performs a fourier transform on the auxin concentrations of the XPP cells in the oscillation zone. - Looks for oscillation in auxin concentration across these cells - Filters out high frequency noise and returns highest frequency peak. - - Parameters - ---------- - sim : GrowingSim - The simulation object containing the cells. - chromosome: Dict - The dictionary being populated with information about this simulation's run - - Returns - ------- - float - The highest frequency peak in the auxin concentration of the XPP cells in the oscillation zone. - """ - # Get auxin concentrations of XPP cells in the oscillation zone - xpp_cells_in_oz = [cell for cell in sim.cell_list if cell.get_dev_zone() in ["transition", "elongation"] and cell.get_cell_type() == 'peri'] - auxins = [cell.get_circ_mod().get_auxin() for cell in xpp_cells_in_oz] - # Perform fourier transform - fourier = np.fft.fft(auxins) - freqs = np.fft.fftfreq(len(auxins)) - # Filter out high frequency noise - fourier[freqs > 0.1] = 0 - # Return highest frequency peak - return max(fourier) # maximizing this - -def parity_of_mz_auxin_concentrations_with_VDB_data(sim: GrowingSim, chromosome: dict) -> float: - """ - Returns the Pearson correlation coefficient of parity of the auxin concentrations in the meristematic zone cells - between ARORA and the VDB data. - - Parameters - ---------- - sim : GrowingSim - The simulation object containing the cells. - chromosome : dict - Dictionary being populated with information about this simulation's run. - - Returns - ------- - float - The parity of the auxin concentrations in the marginal zone cells with the VDB data. - """ - - # Step 1: Load VDB and ARORA data - vdb_summary_df = pd.read_csv('param_est/vdb_summary_seven_peri_cells_across_27_ticks.csv') - sim_output_df = pd.read_csv(sim.output.filename_csv) - # Preprocess ARORA simulation output for analysis - sim_output_df = preprocess_ARORA_sim_output(sim_output_df) - - # Step 2: Collect auxin concentrations at specific locations for each tick - centroid_y_locations = np.linspace(75, 178, 7) - closest_arora_cells_dfs = collect_auxin_data_by_tick(sim_output_df, centroid_y_locations) - - # Step 3: Generate summary statistics of auxin concentration per location - ARORA_summary_df = calculate_auxin_summary(closest_arora_cells_dfs) - # Step 4: Calculate Pearson correlation between ARORA and VDB data - correlation_coefficient = np.corrcoef(vdb_summary_df['auxin_mean'], ARORA_summary_df['auxin_mean'])[0, 1] - chromosome["auxin_corr_with_mz"] = correlation_coefficient - return correlation_coefficient - -def parity_of_auxin_c_for_xpp_boundary_cell_at_each_time_point(sim: GrowingSim, chromosome: dict) -> float: - # Load VDB data - vdb_auxins = pd.read_csv('param_est/vdb_auxins_at_56pt5_336pt5.csv') - sim_output_df = pd.read_csv(sim.output.filename_csv) - sim_output_df = preprocess_ARORA_sim_output(sim_output_df) - # Get auxin concentrations of XPP boundary cell at each time point - xpp_boundary_cell_loc = [56.5, 336.5] - # For every tick in sim_output_df, find the ARORA cell closest to the XPP boundary cell - closest_cells = [] - for tick in sim_output_df['tick'].unique(): - sim_output_df_tick = sim_output_df[sim_output_df['tick'] == tick] - closest_cell = find_ARORA_cell_closest_to_centroid(xpp_boundary_cell_loc, sim_output_df_tick) - closest_cells.append(closest_cell) - # Calculate Pearson correlation between ARORA and VDB data - correlation_coefficient = np.corrcoef(vdb_auxins['auxin'], [cell['Auxin'] for cell in closest_cells])[0, 1] - chromosome["auxin_corr_with_xpp_boundary"] = correlation_coefficient - return correlation_coefficient - - -def collect_auxin_data_by_tick(sim_output_df: pd.DataFrame, centroid_y_locations: np.ndarray) -> pd.DataFrame: - """ - Collects the auxin concentration data for cells closest to the specified centroid locations across ticks. - - Parameters - ---------- - sim_output_df : pd.DataFrame - DataFrame of ARORA simulation output. - centroid_y_locations : np.ndarray - Array of centroid y locations to sample from. - - Returns - ------- - pd.DataFrame - DataFrame with auxin data for closest cells per tick. - """ - closest_arora_cells_dfs = [] - unique_ticks = sim_output_df['tick'].unique() - - for tick in unique_ticks: - closest_cells_df = pd.DataFrame() - sim_output_df_meri_peri = sim_output_df[ - (sim_output_df['tick'] == tick) & - (sim_output_df['dev_zone'] == 'meristematic') & - (sim_output_df['cell_type'] == 'peri') - ].copy() - closest_cells = find_closest_ARORA_pericycle_cell(centroid_y_locations, sim_output_df_meri_peri) - closest_cells_df = pd.concat([closest_cells_df, pd.DataFrame(closest_cells)]) - closest_cells_df = closest_cells_df.sort_values(by='adj_centroid_y') - closest_arora_cells_dfs.append(closest_cells_df) - - return pd.concat(closest_arora_cells_dfs) - -def calculate_auxin_summary(closest_arora_cells_dfs: pd.DataFrame) -> pd.DataFrame: - """ - Calculates summary statistics (range, mean, median, std deviation) for auxin concentrations. - - Parameters - ---------- - closest_arora_cells_dfs : pd.DataFrame - DataFrame of closest ARORA cells auxin concentrations. - - Returns - ------- - pd.DataFrame - Summary statistics of auxin concentrations for each rank. - """ - grouped = closest_arora_cells_dfs.groupby('tick') - max_rows_per_tick = grouped.size().max() - - summary_stats = [] - - for rank in range(max_rows_per_tick): - rows_at_rank = [] - - for _, group in grouped: - sorted_group = group.sort_values(by='centroid_y') - if rank < len(sorted_group): - rows_at_rank.append(sorted_group.iloc[rank]) - - rank_df = pd.DataFrame(rows_at_rank) - - auxin_values = rank_df['auxin'] - summary_stats.append({ - 'rank': rank + 1, - 'auxin_range': auxin_values.max() - auxin_values.min(), - 'auxin_mean': auxin_values.mean(), - 'auxin_median': auxin_values.median(), - 'auxin_standard_deviation': auxin_values.std() - }) - - return pd.DataFrame(summary_stats) - -def preprocess_ARORA_sim_output(sim_output_df): - # Ensure 'location' is parsed from strings only if necessary - sim_output_df['location'] = sim_output_df['location'].apply( - lambda loc: ast.literal_eval(loc) if isinstance(loc, str) else loc - ) - - # Calculate centroids from location - sim_output_df['centroid'] = sim_output_df['location'].apply(parse_and_compute_centroid) - sim_output_df['centroid_x'] = sim_output_df['centroid'].apply(lambda x: x[0]) - sim_output_df['centroid_y'] = sim_output_df['centroid'].apply(lambda x: x[1]) - - # Adjust centroid y-values based on minimum y in each tick - sim_output_df['min_y'] = sim_output_df.groupby('tick')['location'].transform(get_min_y) - VDB_Y_min = 11 - sim_output_df['adj_centroid_y'] = sim_output_df['centroid_y'] - (sim_output_df['min_y'] + VDB_Y_min) - # Apply row-wise operation for 'adj_centroid' - sim_output_df['adj_centroid'] = sim_output_df.apply( - lambda row: [row['centroid_x'], row['centroid_y'] - (row['min_y'] + VDB_Y_min)], axis=1 - ) - - return sim_output_df - - -def find_closest_ARORA_pericycle_cell(centroid_y_locations, arora_df_ONLY_PERI): - closest_cells = [] - # Make a copy of the DataFrame to avoid the SettingWithCopyWarning - arora_df_ONLY_PERICYCLE = arora_df_ONLY_PERI.copy() - for ylocation in centroid_y_locations: - # Calculate the distance between each cell's centroid and the y-location - arora_df_ONLY_PERICYCLE.loc[:, 'distance'] = (arora_df_ONLY_PERICYCLE['adj_centroid_y'] - ylocation).abs() - closest_cell = arora_df_ONLY_PERICYCLE.loc[arora_df_ONLY_PERICYCLE['distance'].idxmin()] - # Drop the 'distance' column after finding the closest cell - arora_df_ONLY_PERICYCLE = arora_df_ONLY_PERICYCLE.drop(columns=['distance']) - closest_cells.append(closest_cell) - return closest_cells - -def find_ARORA_cell_closest_to_centroid(arora_centroid_location, arora_df): - # Ensure centroids are in the correct format (e.g., list of tuples) - if isinstance(arora_df['adj_centroid'].iloc[0], str): - arora_df['adj_centroid'] = arora_df['adj_centroid'].apply(eval) - - # Extract centroids and calculate distances vectorized - centroids = np.array(arora_df['adj_centroid'].tolist()) - distances = np.linalg.norm(centroids - np.array(arora_centroid_location), axis=1) - - # Find the index of the closest centroid - closest_index = np.argmin(distances) - - # Get the closest cell and centroid - closest_cell = arora_df.iloc[closest_index]['cell'] - closest_centroid = arora_df.iloc[closest_index]['adj_centroid'] - closest_distance = distances[closest_index] - closest_cell_auxin = arora_df.iloc[closest_index]['auxin'] - this_tick = arora_df.iloc[closest_index]['tick'] - - # Return the closest cell and its centroid - return { - 'Closest_Cell': closest_cell, - 'Closest_Adj_Centroid': closest_centroid, - 'Distance': closest_distance, - 'Auxin': closest_cell_auxin, - 'Tick': this_tick - } - -def get_min_y(locations): - return min(y for loc in locations for _, y in loc) - -def parse_and_compute_centroid(location): - # Convert points into a NumPy array for easier manipulation - points_array = np.array(location) - # Calculate the centroid - x_coords = points_array[:, 0] - y_coords = points_array[:, 1] - centroid = [sum(x_coords) / len(x_coords), sum(y_coords) / len(y_coords)] - return centroid \ No newline at end of file +import numpy as np +import pandas as pd +import ast +import csv +from scipy.stats import spearmanr + +from src.sim.simulation.sim import GrowingSim +from src.agent.cell import Cell + + +def avg_auxin_root_tip_greater_than_elsewhere(sim: GrowingSim, chromosome: dict) -> float: + """ + Returns the ratio of the average auxin concentration in non-root tip cells + to the average auxin concentration in root tip cells. + + Parameters + ---------- + sim : GrowingSima + The simulation object containing the cells. + chromosome: Dict + The dictionary being populated with information about this simulation's run + + Returns + ------- + float + The ratio of the average auxin concentration in root tip cells to non-root tip cells + """ + non_root_tip_auxins = [cell.get_circ_mod().get_auxin() for cell in sim.cell_list if cell.get_dev_zone() != 'roottip'] + root_tip_auxins = [cell.get_circ_mod().get_auxin() for cell in sim.cell_list if cell.get_dev_zone() == 'roottip'] + avg_non_root_tip_auxins = sum(non_root_tip_auxins)/len(non_root_tip_auxins) + avg_root_tip_auxins = sum(root_tip_auxins)/len(root_tip_auxins) + return avg_root_tip_auxins/avg_non_root_tip_auxins # maximizing this + +def auxin_greater_in_larger_cells_at_trans_elon_interface(sim: GrowingSim, chromosome: dict) -> float: + """ + """ + transition_and_elongation_cells = [cell for cell in sim.cell_list if (cell.get_dev_zone() == 'transition' or cell.get_dev_zone() == 'elongation')] + xpp_trans_and_elon_cells = [cell for cell in transition_and_elongation_cells if (cell.get_cell_type() == 'peri')] + # get correlation coefficient between cell size and auxin concentration in xpp_trans_and_elon_cells + # Trying with all cells + areas = [cell.get_quad_perimeter().get_area() for cell in xpp_trans_and_elon_cells] + auxins = [cell.get_circ_mod().get_auxin() for cell in xpp_trans_and_elon_cells] + corr_coeff = spearmanr(areas, auxins).statistic + if corr_coeff < 0: + print(f"Inverse correlation between xpp cell size and auxin concentration in transition and elongation zone. Fitness set to {abs(corr_coeff)}.") + print(f"corr_coef = {corr_coeff}") + chromosome["notes"] = f"Inverse correlation between xpp cell size and auxin concentration in transition and elongation zone. Fitness set to {abs(corr_coeff)}." + return abs(corr_coeff) #we want there to be a strong positive correlation + return abs(corr_coeff) + +def auxin_oscillation_across_XPP_cells_in_OZ(sim: GrowingSim, chromosome: dict) -> float: + """ + Performs a fourier transform on the auxin concentrations of the XPP cells in the oscillation zone. + Looks for oscillation in auxin concentration across these cells + Filters out high frequency noise and returns highest frequency peak. + + Parameters + ---------- + sim : GrowingSim + The simulation object containing the cells. + chromosome: Dict + The dictionary being populated with information about this simulation's run + + Returns + ------- + float + The highest frequency peak in the auxin concentration of the XPP cells in the oscillation zone. + """ + # Get auxin concentrations of XPP cells in the oscillation zone + xpp_cells_in_oz = [cell for cell in sim.cell_list if cell.get_dev_zone() in ["transition", "elongation"] and cell.get_cell_type() == 'peri'] + auxins = [cell.get_circ_mod().get_auxin() for cell in xpp_cells_in_oz] + # Perform fourier transform + fourier = np.fft.fft(auxins) + freqs = np.fft.fftfreq(len(auxins)) + # Filter out high frequency noise + fourier[freqs > 0.1] = 0 + # Return highest frequency peak + return max(fourier) # maximizing this + +def parity_of_mz_auxin_concentrations_with_VDB_data(sim: GrowingSim, chromosome: dict) -> float: + """ + Returns the Pearson correlation coefficient of parity of the auxin concentrations in the meristematic zone cells + between ARORA and the VDB data. + + Parameters + ---------- + sim : GrowingSim + The simulation object containing the cells. + chromosome : dict + Dictionary being populated with information about this simulation's run. + + Returns + ------- + float + The parity of the auxin concentrations in the marginal zone cells with the VDB data. + """ + + # Step 1: Load VDB and ARORA data + vdb_summary_df = pd.read_csv('param_est/vdb_summary_seven_peri_cells_across_27_ticks.csv') + sim_output_df = pd.read_csv(sim.output.filename_csv) + # Preprocess ARORA simulation output for analysis + sim_output_df = preprocess_ARORA_sim_output(sim_output_df) + + # Step 2: Collect auxin concentrations at specific locations for each tick + centroid_y_locations = np.linspace(75, 178, 7) + closest_arora_cells_dfs = collect_auxin_data_by_tick(sim_output_df, centroid_y_locations) + + # Step 3: Generate summary statistics of auxin concentration per location + ARORA_summary_df = calculate_auxin_summary(closest_arora_cells_dfs) + # Step 4: Calculate Pearson correlation between ARORA and VDB data + correlation_coefficient = np.corrcoef(vdb_summary_df['auxin_mean'], ARORA_summary_df['auxin_mean'])[0, 1] + chromosome["auxin_corr_with_mz"] = correlation_coefficient + return correlation_coefficient + +def parity_of_auxin_c_for_xpp_boundary_cell_at_each_time_point(sim: GrowingSim, chromosome: dict) -> float: + # Load VDB data + vdb_auxins = pd.read_csv('param_est/vdb_auxins_at_56pt5_336pt5.csv') + sim_output_df = pd.read_csv(sim.output.filename_csv) + sim_output_df = preprocess_ARORA_sim_output(sim_output_df) + # Get auxin concentrations of XPP boundary cell at each time point + xpp_boundary_cell_loc = [56.5, 336.5] + # For every tick in sim_output_df, find the ARORA cell closest to the XPP boundary cell + closest_cells = [] + for tick in sim_output_df['tick'].unique(): + sim_output_df_tick = sim_output_df[sim_output_df['tick'] == tick] + closest_cell = find_ARORA_cell_closest_to_centroid(xpp_boundary_cell_loc, sim_output_df_tick) + closest_cells.append(closest_cell) + # Calculate Pearson correlation between ARORA and VDB data + correlation_coefficient = np.corrcoef(vdb_auxins['auxin'], [cell['Auxin'] for cell in closest_cells])[0, 1] + chromosome["auxin_corr_with_xpp_boundary"] = correlation_coefficient + return correlation_coefficient + + +def collect_auxin_data_by_tick(sim_output_df: pd.DataFrame, centroid_y_locations: np.ndarray) -> pd.DataFrame: + """ + Collects the auxin concentration data for cells closest to the specified centroid locations across ticks. + + Parameters + ---------- + sim_output_df : pd.DataFrame + DataFrame of ARORA simulation output. + centroid_y_locations : np.ndarray + Array of centroid y locations to sample from. + + Returns + ------- + pd.DataFrame + DataFrame with auxin data for closest cells per tick. + """ + closest_arora_cells_dfs = [] + unique_ticks = sim_output_df['tick'].unique() + + for tick in unique_ticks: + closest_cells_df = pd.DataFrame() + sim_output_df_meri_peri = sim_output_df[ + (sim_output_df['tick'] == tick) & + (sim_output_df['dev_zone'] == 'meristematic') & + (sim_output_df['cell_type'] == 'peri') + ].copy() + closest_cells = find_closest_ARORA_pericycle_cell(centroid_y_locations, sim_output_df_meri_peri) + closest_cells_df = pd.concat([closest_cells_df, pd.DataFrame(closest_cells)]) + closest_cells_df = closest_cells_df.sort_values(by='adj_centroid_y') + closest_arora_cells_dfs.append(closest_cells_df) + + return pd.concat(closest_arora_cells_dfs) + +def calculate_auxin_summary(closest_arora_cells_dfs: pd.DataFrame) -> pd.DataFrame: + """ + Calculates summary statistics (range, mean, median, std deviation) for auxin concentrations. + + Parameters + ---------- + closest_arora_cells_dfs : pd.DataFrame + DataFrame of closest ARORA cells auxin concentrations. + + Returns + ------- + pd.DataFrame + Summary statistics of auxin concentrations for each rank. + """ + grouped = closest_arora_cells_dfs.groupby('tick') + max_rows_per_tick = grouped.size().max() + + summary_stats = [] + + for rank in range(max_rows_per_tick): + rows_at_rank = [] + + for _, group in grouped: + sorted_group = group.sort_values(by='centroid_y') + if rank < len(sorted_group): + rows_at_rank.append(sorted_group.iloc[rank]) + + rank_df = pd.DataFrame(rows_at_rank) + + auxin_values = rank_df['auxin'] + summary_stats.append({ + 'rank': rank + 1, + 'auxin_range': auxin_values.max() - auxin_values.min(), + 'auxin_mean': auxin_values.mean(), + 'auxin_median': auxin_values.median(), + 'auxin_standard_deviation': auxin_values.std() + }) + + return pd.DataFrame(summary_stats) + +def preprocess_ARORA_sim_output(sim_output_df): + # Ensure 'location' is parsed from strings only if necessary + sim_output_df['location'] = sim_output_df['location'].apply( + lambda loc: ast.literal_eval(loc) if isinstance(loc, str) else loc + ) + + # Calculate centroids from location + sim_output_df['centroid'] = sim_output_df['location'].apply(parse_and_compute_centroid) + sim_output_df['centroid_x'] = sim_output_df['centroid'].apply(lambda x: x[0]) + sim_output_df['centroid_y'] = sim_output_df['centroid'].apply(lambda x: x[1]) + + # Adjust centroid y-values based on minimum y in each tick + sim_output_df['min_y'] = sim_output_df.groupby('tick')['location'].transform(get_min_y) + VDB_Y_min = 11 + sim_output_df['adj_centroid_y'] = sim_output_df['centroid_y'] - (sim_output_df['min_y'] + VDB_Y_min) + # Apply row-wise operation for 'adj_centroid' + sim_output_df['adj_centroid'] = sim_output_df.apply( + lambda row: [row['centroid_x'], row['centroid_y'] - (row['min_y'] + VDB_Y_min)], axis=1 + ) + + return sim_output_df + + +def find_closest_ARORA_pericycle_cell(centroid_y_locations, arora_df_ONLY_PERI): + closest_cells = [] + # Make a copy of the DataFrame to avoid the SettingWithCopyWarning + arora_df_ONLY_PERICYCLE = arora_df_ONLY_PERI.copy() + for ylocation in centroid_y_locations: + # Calculate the distance between each cell's centroid and the y-location + arora_df_ONLY_PERICYCLE.loc[:, 'distance'] = (arora_df_ONLY_PERICYCLE['adj_centroid_y'] - ylocation).abs() + closest_cell = arora_df_ONLY_PERICYCLE.loc[arora_df_ONLY_PERICYCLE['distance'].idxmin()] + # Drop the 'distance' column after finding the closest cell + arora_df_ONLY_PERICYCLE = arora_df_ONLY_PERICYCLE.drop(columns=['distance']) + closest_cells.append(closest_cell) + return closest_cells + +def find_ARORA_cell_closest_to_centroid(arora_centroid_location, arora_df): + # Ensure centroids are in the correct format (e.g., list of tuples) + if isinstance(arora_df['adj_centroid'].iloc[0], str): + arora_df['adj_centroid'] = arora_df['adj_centroid'].apply(eval) + + # Extract centroids and calculate distances vectorized + centroids = np.array(arora_df['adj_centroid'].tolist()) + distances = np.linalg.norm(centroids - np.array(arora_centroid_location), axis=1) + + # Find the index of the closest centroid + closest_index = np.argmin(distances) + + # Get the closest cell and centroid + closest_cell = arora_df.iloc[closest_index]['cell'] + closest_centroid = arora_df.iloc[closest_index]['adj_centroid'] + closest_distance = distances[closest_index] + closest_cell_auxin = arora_df.iloc[closest_index]['auxin'] + this_tick = arora_df.iloc[closest_index]['tick'] + + # Return the closest cell and its centroid + return { + 'Closest_Cell': closest_cell, + 'Closest_Adj_Centroid': closest_centroid, + 'Distance': closest_distance, + 'Auxin': closest_cell_auxin, + 'Tick': this_tick + } + +def get_min_y(locations): + return min(y for loc in locations for _, y in loc) + +def parse_and_compute_centroid(location): + # Convert points into a NumPy array for easier manipulation + points_array = np.array(location) + # Calculate the centroid + x_coords = points_array[:, 0] + y_coords = points_array[:, 1] + centroid = [sum(x_coords) / len(x_coords), sum(y_coords) / len(y_coords)] + return centroid + +def arora_vdb_ssd(): + total_ssd = 0.0 + reference_filenames = get_filenames("param_est/vdbdata/references/Caux1Array_", 0, 1000, 26000) # not quite sure how the matching of temporal scales is going + output_filenames = [f"output/ARORAoutput{i}.csv" for i in range(27)] + 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 + +def file_extract_values(filename): + with open(filename, 'r') as file: + reader = csv.reader(file) + data = [] + for row in reader: + if row and row[-1].strip() == '': + row.pop() + float_row = [float(cell) for cell in row] + data.append(float_row) + return np.array(data) + +def get_filenames(stringstart, t_start, tstep, t_total): # changed + filenames = [] + for t in range(t_start, (t_total + tstep), tstep): + filename = stringstart + str(t).zfill(8) + ".csv" # if we decide to change number of digits in filename later, change this line + filenames.append(filename) + return filenames diff --git a/param_est/pe_2024110701.json b/param_est/pe_2024110701.json new file mode 100644 index 0000000..a8e4052 --- /dev/null +++ b/param_est/pe_2024110701.json @@ -0,0 +1,80 @@ +[ + { + "num_generations": 20, + "num_parents_mating": 5, + "fitness_func": "parity_of_mz_auxin_concentrations_with_VDB_data + parity_of_auxin_c_for_xpp_boundary_cell_at_each_time_point", + "negative_corr_sets_fitness_to_neg_inf": false, + "sol_per_pop": 20, + "num_genes": 9, + "gene_space": "ks .001 to .3, kd .0001 to .03, k1 10 to 160, k2 50 to 100, k3 10 to 75, k4 50 to 100, k5 .07 to 1, k6 .2 to 1, tau 1 to 24", + "mutation_percent_genes": 5, + "save_best_solutions": false, + "parent_selection_type": "sss", + "initialization_file": "aux_syndegonly_init_vals.json", + "hours_per_simulation": 27 + }, + { + "sol_idx": 0, + "ks_aux": 0.23657575757575758, + "kd_aux": 0.013388888888888888, + "k1": 65.0, + "k2": 79.0, + "k3": 53.0, + "k4": 75.0, + "k5": 0.13575757575757574, + "k6": 0.3292929292929293, + "tau": 10.0, + "exception": "'<' not supported between instances of 'Cell' and 'Cell'", + "finished": false, + "tick": 1, + "fitness": -Infinity + }, + { + "sol_idx": 1, + "ks_aux": 0.09462626262626263, + "kd_aux": 0.028187878787878786, + "k1": 77.0, + "k2": 64.0, + "k3": 38.0, + "k4": 90.0, + "k5": 1.0, + "k6": 0.4505050505050505, + "tau": 21.0, + "exception": "Multiple delta vals added to VertexMover for cell 61. VertexMover must be updated between cell updates.", + "finished": false, + "tick": 0, + "fitness": -Infinity + }, + { + "sol_idx": 2, + "ks_aux": 0.022141414141414142, + "kd_aux": 0.012482828282828281, + "k1": 18.0, + "k2": 99.0, + "k3": 66.0, + "k4": 76.0, + "k5": 0.47393939393939394, + "k6": 0.9515151515151514, + "tau": 14.0, + "exception": "'<' not supported between instances of 'Cell' and 'Cell'", + "finished": false, + "tick": 1, + "fitness": -Infinity + }, + { + "sol_idx": 3, + "ks_aux": 0.049323232323232324, + "kd_aux": 0.003422222222222222, + "k1": 97.0, + "k2": 80.0, + "k3": 41.0, + "k4": 75.0, + "k5": 0.9248484848484848, + "k6": 0.2646464646464647, + "tau": 11.0, + "exception": "Multiple delta vals added to VertexMover for cell 61. VertexMover must be updated between cell updates.", + "finished": false, + "tick": 0, + "fitness": -Infinity + } +] \ No newline at end of file diff --git a/param_est/vdb_data/all_inspected_vdb_peri_cells.csv b/param_est/vdb_data/preprocessed/all_inspected_vdb_peri_cells.csv similarity index 100% rename from param_est/vdb_data/all_inspected_vdb_peri_cells.csv rename to param_est/vdb_data/preprocessed/all_inspected_vdb_peri_cells.csv diff --git a/param_est/vdb_data/processed_vdb_outputs/vdb_t_0.csv b/param_est/vdb_data/preprocessed/processed_vdb_outputs/vdb_t_0.csv similarity index 100% rename from param_est/vdb_data/processed_vdb_outputs/vdb_t_0.csv rename to param_est/vdb_data/preprocessed/processed_vdb_outputs/vdb_t_0.csv diff --git a/param_est/vdb_data/processed_vdb_outputs/vdb_t_1.csv b/param_est/vdb_data/preprocessed/processed_vdb_outputs/vdb_t_1.csv similarity index 100% rename from param_est/vdb_data/processed_vdb_outputs/vdb_t_1.csv rename to param_est/vdb_data/preprocessed/processed_vdb_outputs/vdb_t_1.csv diff --git a/param_est/vdb_data/processed_vdb_outputs/vdb_t_10.csv b/param_est/vdb_data/preprocessed/processed_vdb_outputs/vdb_t_10.csv similarity index 100% rename from param_est/vdb_data/processed_vdb_outputs/vdb_t_10.csv rename to param_est/vdb_data/preprocessed/processed_vdb_outputs/vdb_t_10.csv diff --git a/param_est/vdb_data/processed_vdb_outputs/vdb_t_11.csv b/param_est/vdb_data/preprocessed/processed_vdb_outputs/vdb_t_11.csv similarity index 100% rename from param_est/vdb_data/processed_vdb_outputs/vdb_t_11.csv rename to param_est/vdb_data/preprocessed/processed_vdb_outputs/vdb_t_11.csv diff --git a/param_est/vdb_data/processed_vdb_outputs/vdb_t_12.csv b/param_est/vdb_data/preprocessed/processed_vdb_outputs/vdb_t_12.csv similarity index 100% rename from param_est/vdb_data/processed_vdb_outputs/vdb_t_12.csv rename to param_est/vdb_data/preprocessed/processed_vdb_outputs/vdb_t_12.csv diff --git a/param_est/vdb_data/processed_vdb_outputs/vdb_t_13.csv b/param_est/vdb_data/preprocessed/processed_vdb_outputs/vdb_t_13.csv similarity index 100% rename from param_est/vdb_data/processed_vdb_outputs/vdb_t_13.csv rename to param_est/vdb_data/preprocessed/processed_vdb_outputs/vdb_t_13.csv diff --git a/param_est/vdb_data/processed_vdb_outputs/vdb_t_14.csv b/param_est/vdb_data/preprocessed/processed_vdb_outputs/vdb_t_14.csv similarity index 100% rename from param_est/vdb_data/processed_vdb_outputs/vdb_t_14.csv rename to param_est/vdb_data/preprocessed/processed_vdb_outputs/vdb_t_14.csv diff --git a/param_est/vdb_data/processed_vdb_outputs/vdb_t_15.csv b/param_est/vdb_data/preprocessed/processed_vdb_outputs/vdb_t_15.csv similarity index 100% rename from param_est/vdb_data/processed_vdb_outputs/vdb_t_15.csv rename to param_est/vdb_data/preprocessed/processed_vdb_outputs/vdb_t_15.csv diff --git a/param_est/vdb_data/processed_vdb_outputs/vdb_t_16.csv b/param_est/vdb_data/preprocessed/processed_vdb_outputs/vdb_t_16.csv similarity index 100% rename from param_est/vdb_data/processed_vdb_outputs/vdb_t_16.csv rename to param_est/vdb_data/preprocessed/processed_vdb_outputs/vdb_t_16.csv diff --git a/param_est/vdb_data/processed_vdb_outputs/vdb_t_17.csv b/param_est/vdb_data/preprocessed/processed_vdb_outputs/vdb_t_17.csv similarity index 100% rename from param_est/vdb_data/processed_vdb_outputs/vdb_t_17.csv rename to param_est/vdb_data/preprocessed/processed_vdb_outputs/vdb_t_17.csv diff --git a/param_est/vdb_data/processed_vdb_outputs/vdb_t_18.csv b/param_est/vdb_data/preprocessed/processed_vdb_outputs/vdb_t_18.csv similarity index 100% rename from param_est/vdb_data/processed_vdb_outputs/vdb_t_18.csv rename to param_est/vdb_data/preprocessed/processed_vdb_outputs/vdb_t_18.csv diff --git a/param_est/vdb_data/processed_vdb_outputs/vdb_t_19.csv b/param_est/vdb_data/preprocessed/processed_vdb_outputs/vdb_t_19.csv similarity index 100% rename from param_est/vdb_data/processed_vdb_outputs/vdb_t_19.csv rename to param_est/vdb_data/preprocessed/processed_vdb_outputs/vdb_t_19.csv diff --git a/param_est/vdb_data/processed_vdb_outputs/vdb_t_2.csv b/param_est/vdb_data/preprocessed/processed_vdb_outputs/vdb_t_2.csv similarity index 100% rename from param_est/vdb_data/processed_vdb_outputs/vdb_t_2.csv rename to param_est/vdb_data/preprocessed/processed_vdb_outputs/vdb_t_2.csv diff --git a/param_est/vdb_data/processed_vdb_outputs/vdb_t_20.csv b/param_est/vdb_data/preprocessed/processed_vdb_outputs/vdb_t_20.csv similarity index 100% rename from param_est/vdb_data/processed_vdb_outputs/vdb_t_20.csv rename to param_est/vdb_data/preprocessed/processed_vdb_outputs/vdb_t_20.csv diff --git a/param_est/vdb_data/processed_vdb_outputs/vdb_t_21.csv b/param_est/vdb_data/preprocessed/processed_vdb_outputs/vdb_t_21.csv similarity index 100% rename from param_est/vdb_data/processed_vdb_outputs/vdb_t_21.csv rename to param_est/vdb_data/preprocessed/processed_vdb_outputs/vdb_t_21.csv diff --git a/param_est/vdb_data/processed_vdb_outputs/vdb_t_22.csv b/param_est/vdb_data/preprocessed/processed_vdb_outputs/vdb_t_22.csv similarity index 100% rename from param_est/vdb_data/processed_vdb_outputs/vdb_t_22.csv rename to param_est/vdb_data/preprocessed/processed_vdb_outputs/vdb_t_22.csv diff --git a/param_est/vdb_data/processed_vdb_outputs/vdb_t_23.csv b/param_est/vdb_data/preprocessed/processed_vdb_outputs/vdb_t_23.csv similarity index 100% rename from param_est/vdb_data/processed_vdb_outputs/vdb_t_23.csv rename to param_est/vdb_data/preprocessed/processed_vdb_outputs/vdb_t_23.csv diff --git a/param_est/vdb_data/processed_vdb_outputs/vdb_t_24.csv b/param_est/vdb_data/preprocessed/processed_vdb_outputs/vdb_t_24.csv similarity index 100% rename from param_est/vdb_data/processed_vdb_outputs/vdb_t_24.csv rename to param_est/vdb_data/preprocessed/processed_vdb_outputs/vdb_t_24.csv diff --git a/param_est/vdb_data/processed_vdb_outputs/vdb_t_25.csv b/param_est/vdb_data/preprocessed/processed_vdb_outputs/vdb_t_25.csv similarity index 100% rename from param_est/vdb_data/processed_vdb_outputs/vdb_t_25.csv rename to param_est/vdb_data/preprocessed/processed_vdb_outputs/vdb_t_25.csv diff --git a/param_est/vdb_data/processed_vdb_outputs/vdb_t_26.csv b/param_est/vdb_data/preprocessed/processed_vdb_outputs/vdb_t_26.csv similarity index 100% rename from param_est/vdb_data/processed_vdb_outputs/vdb_t_26.csv rename to param_est/vdb_data/preprocessed/processed_vdb_outputs/vdb_t_26.csv diff --git a/param_est/vdb_data/processed_vdb_outputs/vdb_t_27.csv b/param_est/vdb_data/preprocessed/processed_vdb_outputs/vdb_t_27.csv similarity index 100% rename from param_est/vdb_data/processed_vdb_outputs/vdb_t_27.csv rename to param_est/vdb_data/preprocessed/processed_vdb_outputs/vdb_t_27.csv diff --git a/param_est/vdb_data/processed_vdb_outputs/vdb_t_3.csv b/param_est/vdb_data/preprocessed/processed_vdb_outputs/vdb_t_3.csv similarity index 100% rename from param_est/vdb_data/processed_vdb_outputs/vdb_t_3.csv rename to param_est/vdb_data/preprocessed/processed_vdb_outputs/vdb_t_3.csv diff --git a/param_est/vdb_data/processed_vdb_outputs/vdb_t_4.csv b/param_est/vdb_data/preprocessed/processed_vdb_outputs/vdb_t_4.csv similarity index 100% rename from param_est/vdb_data/processed_vdb_outputs/vdb_t_4.csv rename to param_est/vdb_data/preprocessed/processed_vdb_outputs/vdb_t_4.csv diff --git a/param_est/vdb_data/processed_vdb_outputs/vdb_t_5.csv b/param_est/vdb_data/preprocessed/processed_vdb_outputs/vdb_t_5.csv similarity index 100% rename from param_est/vdb_data/processed_vdb_outputs/vdb_t_5.csv rename to param_est/vdb_data/preprocessed/processed_vdb_outputs/vdb_t_5.csv diff --git a/param_est/vdb_data/processed_vdb_outputs/vdb_t_6.csv b/param_est/vdb_data/preprocessed/processed_vdb_outputs/vdb_t_6.csv similarity index 100% rename from param_est/vdb_data/processed_vdb_outputs/vdb_t_6.csv rename to param_est/vdb_data/preprocessed/processed_vdb_outputs/vdb_t_6.csv diff --git a/param_est/vdb_data/processed_vdb_outputs/vdb_t_7.csv b/param_est/vdb_data/preprocessed/processed_vdb_outputs/vdb_t_7.csv similarity index 100% rename from param_est/vdb_data/processed_vdb_outputs/vdb_t_7.csv rename to param_est/vdb_data/preprocessed/processed_vdb_outputs/vdb_t_7.csv diff --git a/param_est/vdb_data/processed_vdb_outputs/vdb_t_8.csv b/param_est/vdb_data/preprocessed/processed_vdb_outputs/vdb_t_8.csv similarity index 100% rename from param_est/vdb_data/processed_vdb_outputs/vdb_t_8.csv rename to param_est/vdb_data/preprocessed/processed_vdb_outputs/vdb_t_8.csv diff --git a/param_est/vdb_data/processed_vdb_outputs/vdb_t_9.csv b/param_est/vdb_data/preprocessed/processed_vdb_outputs/vdb_t_9.csv similarity index 100% rename from param_est/vdb_data/processed_vdb_outputs/vdb_t_9.csv rename to param_est/vdb_data/preprocessed/processed_vdb_outputs/vdb_t_9.csv diff --git a/param_est/vdb_data/vdb_auxins_at_56pt5_336pt5.csv b/param_est/vdb_data/preprocessed/vdb_auxins_at_56pt5_336pt5.csv similarity index 100% rename from param_est/vdb_data/vdb_auxins_at_56pt5_336pt5.csv rename to param_est/vdb_data/preprocessed/vdb_auxins_at_56pt5_336pt5.csv diff --git a/param_est/vdb_data/vdb_summary_seven_peri_cells_across_27_ticks.csv b/param_est/vdb_data/preprocessed/vdb_summary_seven_peri_cells_across_27_ticks.csv similarity index 100% rename from param_est/vdb_data/vdb_summary_seven_peri_cells_across_27_ticks.csv rename to param_est/vdb_data/preprocessed/vdb_summary_seven_peri_cells_across_27_ticks.csv diff --git a/run_ga_imposed_auxsyndegexp.py b/run_ga_imposed_auxsyndegexp.py new file mode 100644 index 0000000..acf4175 --- /dev/null +++ b/run_ga_imposed_auxsyndegexp.py @@ -0,0 +1,7 @@ +from param_est.ARORA_genetic_alg_imposed_auxsyndegexport import ARORAGeneticAlgImposedAuxinSynDegExport + +if __name__ == "__main__": + ga = ARORAGeneticAlgImposedAuxinSynDegExport("PE_test") + ga.run_genetic_alg() + ga.analyze_results() + exit(0) \ No newline at end of file diff --git a/src/agent/cell.py b/src/agent/cell.py index 2320484..7f96e14 100644 --- a/src/agent/cell.py +++ b/src/agent/cell.py @@ -1,9 +1,11 @@ from arcade import Sprite from arcade import draw_polygon_filled, draw_polygon_outline from typing import TYPE_CHECKING, Any, cast, Union, Dict +from src.arora_enums import PinLocalizationRulesetEnum, CircModEnum from src.agent.circ_module_universal_syndeg import CirculateModuleUniversalSynDeg from src.agent.circ_module_indep_syn_deg import CirculateModuleIndSynDeg from src.agent.circ_module_aux_syn_deg_only import CirculateModuleAuxinSynDegOnly +from src.agent.circ_module_aux_syn_deg_export import CirculateModuleAuxinSynDegExport from src.loc.quad_perimeter.quad_perimeter import QuadPerimeter from src.agent.default_geo_neighbor_helpers import NeighborHelpers from src.agent.circ_module import CirculateModule @@ -13,19 +15,17 @@ from src.loc.vertex.vertex import Vertex # Growth rate of cells in meristematic zone in um per um per hour from Van den Berg et al. 2018 -# MERISTEMATIC_GROWTH_RATE: float = -0.0179 -MERISTEMATIC_GROWTH_RATE: float = -1.2 # trying to divide every 5 hours +MERISTEMATIC_GROWTH_RATE: float = -0.0179 # Growth rate cells in transition zone in um per um per hour from Van den Berg et al. 2018 -# TRANSITION_GROWTH_RATE: float = -0.0179 -TRANSITION_GROWTH_RATE: float = -1.2 # same as Meristematic +TRANSITION_GROWTH_RATE: float = -0.0179 # Growth rate cells in elongation zone in um per um per hour from Van den Berg et al. 2018 -# ELONGATION_GROWTH_RATE: float = -0.00112 -ELONGATION_GROWTH_RATE: float = -0.075 # same ratio as van den berg +ELONGATION_GROWTH_RATE: float = -0.00112 + # Growth rate cells in differentiation zone in um per um per hour from Van den Berg et al. 2018 -# DIFFERENTIATION_GROWTH_RATE: float = -0.00112 -DIFFERENTIATION_GROWTH_RATE: float = -0.075 # same ratio as van den berg +DIFFERENTIATION_GROWTH_RATE: float = -0.00112 + # um Y distance from tip at which cells pass from root tip to meristematic zone # Inferred from Van dn Berg et al. 2018 @@ -33,19 +33,19 @@ # um Y distance from tip at which cells pass from meristemtic to transition zone # from Van den Berg et al. 2018 -MERISTEMATIC_MAX_DIST_FROM_TIP: int = 160 +MERISTEMATIC_MAX_DIST_FROM_TIP: int = ROOT_TIP_DIST_FROM_TIP + 160 # um Y distance from tip at which cells pass from transition to elongation zone # from Van den Berg et al. 2018 -TRANSITION_MAX_DIST_FROM_TIP: int = 340 +TRANSITION_MAX_DIST_FROM_TIP: int = MERISTEMATIC_MAX_DIST_FROM_TIP + 180 # um Y distance from tip at which cells pass from elongation to differentiation zone # from Van den Berg et al. 2018 -ELONGATION_MAX_DIST_FROM_TIP: int = 460 +ELONGATION_MAX_DIST_FROM_TIP: int = TRANSITION_MAX_DIST_FROM_TIP + 600 # um Y distance from tip at which cells leave differentiation zone # from Van den Berg et al. 2018 -DIFFERENTIATION_MAX_DIST_FROM_TIP: int = 960 +DIFFERENTIATION_MAX_DIST_FROM_TIP: int = ELONGATION_MAX_DIST_FROM_TIP + 500 # max um X distance vasc cells can be from self.sim.get_root_midpointx() # from Salvi et al. 2020 @@ -67,6 +67,14 @@ # from Salvi et al. 2020 EPIDERMIS_CELL_DIST_FROM_ROOT_MIDPOINTX: int = 45 +MERISTEM_FIRST_ROW_CENTER_Y = 75.5 # μm distance from tip for first meristematic band +MERISTEM_ROW_STEP = 5.0 # μm between successive meristematic bands +MERISTEM_MAX_ROW_INDEX = 16 # last observed meristematic band index + +TRANSITION_FIRST_ROW_CENTER_Y = 160.5 # μm distance from tip for first transition band +TRANSITION_ROW_STEP = 5.0 +TRANSITION_MAX_ROW_INDEX = 33 # highest observed transition band index + class Cell(Sprite): """ @@ -90,7 +98,7 @@ class Cell(Sprite): a_neighbors : list List of apical neighbors. b_neighbors : list - List of basal neighbors. + List of basal neighbors.get_pin l_neighbors : list List of lateral neighbors. m_neighbors : list @@ -107,6 +115,8 @@ class Cell(Sprite): The development zone of the cell. cell_type : str The type of the cell. + pin_loc_ruleset : PinLocalizationRules + The rules the cell is following to determine its PIN localization color : str The color of the cell. """ @@ -114,6 +124,7 @@ class Cell(Sprite): dev_zone: str cell_type: str growing: bool + pin_loc_ruleset: PinLocalizationRulesetEnum def __init__( self, @@ -145,28 +156,34 @@ def __init__( self.sim: "GrowingSim" = simulation simulation.increment_next_cell_id() self.quad_perimeter = QuadPerimeter(corners) + # Type hint circ_mod to accept any class that implements the CirculateModule protocol self.circ_mod: CirculateModule - circ_mod_name = init_vals.get("circ_mod") - if circ_mod_name == "universal_syndeg": - self.circ_mod = CirculateModuleUniversalSynDeg(self, init_vals) - elif circ_mod_name == "indep_syndeg": - self.circ_mod = CirculateModuleIndSynDeg(self, init_vals) - elif circ_mod_name == "aux_syndegonly": - self.circ_mod = CirculateModuleAuxinSynDegOnly(self, init_vals) - else: - print(f"Warning: Unknown circ_mod '{circ_mod_name}', defaulting to universal_syndeg.") - self.circ_mod = CirculateModuleUniversalSynDeg(self, init_vals) + circ_mod_name = simulation.get_circ_mod() + match circ_mod_name: + case CircModEnum.UNIVERSAL_SYN_DEG: + self.circ_mod = CirculateModuleUniversalSynDeg(self, init_vals) + case CircModEnum.INDEP_SYN_DEG: + self.circ_mod = CirculateModuleIndSynDeg(self, init_vals) + case CircModEnum.AUX_SYN_DEG_ONLY: + self.circ_mod = CirculateModuleAuxinSynDegOnly(self, init_vals) + case CircModEnum.AUX_SYN_DEG_EXP: + self.circ_mod = CirculateModuleAuxinSynDegExport(self, init_vals) + case _: + raise SyntaxError(f"Unknown circ mod '{circ_mod_name}'") - self.pin_weights: Dict[str, float] = self.calculate_pin_weights() if self.sim.geometry != "default": self.dev_zone = "" self.cell_type = "" self.growing = False else: - self.dev_zone = self.calculate_dev_zone(self.get_distance_from_tip()) + dist = self.get_distance_from_tip() + self.dev_zone = self.calculate_dev_zone(dist) self.cell_type = self.calculate_cell_type() self.growing = cast(bool, init_vals.get("growing")) + + self.pin_loc_ruleset = self.get_sim().get_pin_loc_rules() + self.pin_weights: Dict[str, float] = self.calculate_pin_weights() self.color: tuple[int, int, int, int] = self.calculate_color() self.sim.add_to_cell_list(self) @@ -362,7 +379,13 @@ def find_new_neighbor_relative_location(self, neighbor: "Cell") -> str: neighbor_dir = self.get_neighbor_di_neighbor_shares_two_vs_std(neighbor) if len(set(self_vs).intersection(set(neighbor_vs))) == 1 and neighbor_dir == "": neighbor_dir = self.get_neighbor_dir_neighbor_shares_one_v_std(neighbor) - if neighbor_dir not in ["a", "b", "l", "m", "cell no longer root cap cell neighbor"]: + if neighbor_dir not in [ + "a", + "b", + "l", + "m", + "cell no longer root cap cell neighbor", + ]: raise ValueError("Neighbor not recognized") return neighbor_dir @@ -758,33 +781,269 @@ def get_growth_rate(self) -> float: def calculate_delta(self) -> float: """ - Calculate the growth delta of the cell. + Calculate the growth delta of the cell for a single simulation tick. Returns ------- float - The growth delta of the cell. + The growth delta (in μm) for this tick. Note ---- - This method only works for the default geometry. + Growth is exponential: dL/dt = r * L, so + ΔL ≈ r * L * Δt. """ dist_to_root_tip = self.get_distance_from_tip() self.dev_zone = self.calculate_dev_zone(dist_to_root_tip) - return self.get_growth_rate() + + # Relative growth rate (1/hour) + rate = self.get_growth_rate() + if rate == 0.0: + return 0.0 + + # Current cell height in μm + height = self.get_quad_perimeter().get_height() + + # Biological time for one tick (hours) + dt_hours = self.get_sim().get_timestep_hours() + + # Exponential growth step: ΔL = r * L * Δt + return rate * height * dt_hours def calculate_pin_weights(self) -> dict: """ Calculates the pin weights of each membrane of the cell. + For SIMPLE_INHERITANCE, uses circ_mod's dynamic PIN. + For IMPOSED, uses the VdB-style imposed PIN pattern and + converts it to fractions that sum to 1. + """ + if self.pin_loc_ruleset == PinLocalizationRulesetEnum.SIMPLE_INHERITANCE: + # circ_mod holds the current PIN levels per membrane + return self.circ_mod.get_pin_weights() + + elif self.pin_loc_ruleset == PinLocalizationRulesetEnum.IMPOSED: + # use imposed spatial pattern (VdB) as "absolute PIN" + pins = self.get_imposed_pin_distribution() # {"a": ..., "b": ..., "l": ..., "m": ...} + total = pins["a"] + pins["b"] + pins["l"] + pins["m"] + if total == 0: + return {"a": 0.0, "b": 0.0, "l": 0.0, "m": 0.0} + return {key: val / total for key, val in pins.items()} + + else: + raise NotImplementedError("Unknown PIN localization ruleset") + + def get_imposed_pin_distribution(self) -> Dict[str, float]: + """ + Assign PIN weights according to a cell's global location, maintaining the + PIN distributions established in the initial conditions. Returns ------- dict - The pin weights for each membrane of the cell. + The PIN weights for each membrane of the cell. Keys are "a", "b", "l", and "m". + Returns current pin_weights for roottip cells. + """ + dev_zone = self.get_dev_zone() + cell_type = self.get_cell_type() + + # Root tip cells: use whatever the ODE module's initial PIN distribution is. + if dev_zone == "roottip" or cell_type == "roottip": + return self.circ_mod.get_pin_weights() + + dist_to_root_tip = self.get_distance_from_tip() + + if dev_zone == "meristematic": + pins = self._pin_meristematic(cell_type, dist_to_root_tip) + elif dev_zone == "transition": + pins = self._pin_transition(cell_type, dist_to_root_tip) + elif dev_zone == "elongation": + pins = self._pin_elongation(cell_type) + elif dev_zone == "differentiation": + pins = self._pin_differentiation(cell_type) + else: + raise SyntaxError("Dev zone error cannot get imposed PIN pattern") + + return pins + + def _meristem_row_index(self, dist_to_root_tip: float) -> int: + """ + Map distance from tip to a discrete 'row' index in the meristematic zone, + using 5 μm bands centered at MERISTEM_FIRST_ROW_CENTER_Y. + """ + row = int(round((dist_to_root_tip - MERISTEM_FIRST_ROW_CENTER_Y) / MERISTEM_ROW_STEP)) + if row < 0: + row = 0 + if row > MERISTEM_MAX_ROW_INDEX: + row = MERISTEM_MAX_ROW_INDEX + return row + def _transition_row_index(self, dist_to_root_tip: float) -> int: """ - return self.circ_mod.get_pin_weights() + Map distance from tip to a discrete 'row' index in the transition zone, + using 5 μm bands centered at TRANSITION_FIRST_ROW_CENTER_Y. + """ + row = int(round((dist_to_root_tip - TRANSITION_FIRST_ROW_CENTER_Y) / TRANSITION_ROW_STEP)) + if row < 0: + row = 0 + if row > TRANSITION_MAX_ROW_INDEX: + row = TRANSITION_MAX_ROW_INDEX + return row + + def _pin_meristematic(self, cell_type: str, dist_to_root_tip: float) -> Dict[str, float]: + """ + Meristematic zone PIN patterns, banded along y. + """ + row = self._meristem_row_index(dist_to_root_tip) + + if cell_type == "vasc": + if row == 0: + return {"a": 0.0, "b": 1.0, "l": 0.4, "m": 0.4} + # rows 1–16: alternate stripes + if row % 2 == 1: + return {"a": 0.0, "b": 1.0, "l": 0.0, "m": 0.0} + else: + return {"a": 0.0, "b": 1.0, "l": 0.025, "m": 0.025} + + elif cell_type == "peri": + if row == 0: + return {"a": 0.0, "b": 1.0, "l": 0.35, "m": 0.5} + if row % 2 == 1: + return {"a": 0.0, "b": 1.0, "l": 0.0, "m": 0.0} + else: + return {"a": 0.0, "b": 1.0, "l": 0.0, "m": 0.1} + + elif cell_type == "endo": + if row == 0: + return {"a": 0.0, "b": 1.0, "l": 0.35, "m": 0.5} + if row % 2 == 1: + return {"a": 0.0, "b": 1.0, "l": 0.0, "m": 0.0} + else: + return {"a": 0.0, "b": 1.0, "l": 0.1, "m": 0.35} + + elif cell_type == "cortex": + if row == 0: + return {"a": 0.0, "b": 1.0, "l": 0.1, "m": 0.1} + if row % 2 == 1: + return {"a": 0.0, "b": 1.0, "l": 0.0, "m": 0.0} + else: + return {"a": 0.0, "b": 1.0, "l": 0.0, "m": 0.1} + + elif cell_type == "epidermis": + # row 0: low lateral PIN + if row == 0: + return {"a": 1.0, "b": 0.0, "l": 0.1, "m": 0.1} + # rows 1–9: alternate (high lateral) vs (higher shootward) + if 1 <= row <= 9: + if row % 2 == 1: + return {"a": 1.0, "b": 0.0, "l": 1.0, "m": 1.0} + else: + return {"a": 1.0, "b": 0.0, "l": 0.3, "m": 0.1} + # rows 10–16: alternate between low lateral and high lateral + if row % 2 == 0: + return {"a": 1.0, "b": 0.0, "l": 0.1, "m": 0.1} + else: + return {"a": 1.0, "b": 0.0, "l": 1.0, "m": 1.0} + + # unknown types: raise exception + raise SyntaxError( + "Meristematic cell not within meristamatic PIN bands. Check for growth errros." + ) + + def _pin_transition(self, cell_type: str, dist_to_root_tip: float) -> Dict[str, float]: + """ + Transition zone PIN patterns, banded along y. + """ + row = self._transition_row_index(dist_to_root_tip) + + if cell_type == "vasc": + # rows 0–22: alternate weak vs stronger shootward + if row <= 22: + if row % 2 == 0: + return {"a": 0.0, "b": 1.0, "l": 0.0, "m": 0.0} + else: + return {"a": 0.0, "b": 1.0, "l": 0.025, "m": 0.025} + # upper transition: match elongation/diff vasc profile + return {"a": 0.0, "b": 1.0, "l": 0.0525, "m": 0.0525} + + elif cell_type == "peri": + if row <= 22: + if row % 2 == 0: + return {"a": 0.0, "b": 1.0, "l": 0.0, "m": 0.0} + else: + return {"a": 0.0, "b": 1.0, "l": 0.0, "m": 0.1} + return {"a": 0.0, "b": 1.0, "l": 0.1, "m": 0.1} + + elif cell_type == "endo": + # rows 0–14: alternate (0,0) vs (0.1,0.35) + if row <= 14: + if row % 2 == 0: + return {"a": 0.0, "b": 1.0, "l": 0.0, "m": 0.0} + else: + return {"a": 0.0, "b": 1.0, "l": 0.1, "m": 0.35} + # rows 15–22: alternate (0,0) vs (0,0.35) + if row <= 22: + if row % 2 == 0: + return {"a": 0.0, "b": 1.0, "l": 0.0, "m": 0.0} + else: + return {"a": 0.0, "b": 1.0, "l": 0.0, "m": 0.35} + # upper transition: fixed 0.35 on m + return {"a": 0.0, "b": 1.0, "l": 0.0, "m": 0.35} + + elif cell_type == "cortex": + if row <= 22: + if row % 2 == 0: + return {"a": 0.0, "b": 1.0, "l": 0.0, "m": 0.0} + else: + return {"a": 0.0, "b": 1.0, "l": 0.0, "m": 0.1} + # upper transition cortex matches elongation cortex + return {"a": 1.0, "b": 0.0, "l": 0.0, "m": 0.1} + + elif cell_type == "epidermis": + if row <= 22: + if row % 2 == 0: + return {"a": 1.0, "b": 0.0, "l": 1.0, "m": 1.0} + else: + return {"a": 1.0, "b": 0.0, "l": 0.1, "m": 0.1} + # upper transition epidermis matches elongation epidermis + return {"a": 1.0, "b": 0.0, "l": 0.0, "m": 0.1} + + # unknown types: raise exception + raise SyntaxError( + "Transition cell not within transition PIN bands. Check for growth errros." + ) + + def _pin_elongation(self, cell_type: str) -> Dict[str, float]: + """ + Elongation zone PIN patterns, constant within each cell type. + """ + if cell_type == "vasc": + return {"a": 0.0, "b": 1.0, "l": 0.0525, "m": 0.0525} + if cell_type == "peri": + return {"a": 0.0, "b": 1.0, "l": 0.1, "m": 0.1} + if cell_type == "endo": + return {"a": 0.0, "b": 1.0, "l": 0.0, "m": 0.35} + if cell_type == "cortex": + return {"a": 1.0, "b": 0.0, "l": 0.0, "m": 0.1} + if cell_type == "epidermis": + return {"a": 1.0, "b": 0.0, "l": 0.0, "m": 0.1} + raise SyntaxError("Vascular cell type unrecognized.") + + def _pin_differentiation(self, cell_type: str) -> Dict[str, float]: + """ + Differentiation zone PIN patterns, constant within each cell type. + """ + if cell_type == "vasc": + return {"a": 0.0, "b": 1.0, "l": 0.0525, "m": 0.0525} + if cell_type == "peri": + return {"a": 0.0, "b": 1.0, "l": 0.1, "m": 0.1} + if cell_type == "endo": + return {"a": 0.0, "b": 1.0, "l": 0.0, "m": 0.35} + if cell_type == "cortex": + return {"a": 0.35, "b": 0.0, "l": 0.0, "m": 0.1} + if cell_type == "epidermis": + return {"a": 0.35, "b": 0.0, "l": 0.0, "m": 0.1} + raise SyntaxError("Differentiation cell type unrecognized.") def get_pin_weights(self) -> dict: """ @@ -799,12 +1058,20 @@ def get_pin_weights(self) -> dict: return self.pin_weights def update(self) -> None: - """ - Updates the cell by growing, calculating pin weights, and updating the circ module. - """ if self.growing: self.grow() - self.pin_weights = self.calculate_pin_weights() + + self.dev_zone = self.calculate_dev_zone(self.get_distance_from_tip()) + + rules = self.get_sim().get_pin_loc_rules() + + # Recompute pin_weights every tick so imposed patterns can change with zone / position + if rules in ( + PinLocalizationRulesetEnum.SIMPLE_INHERITANCE, + PinLocalizationRulesetEnum.IMPOSED, + ): + self.pin_weights = self.calculate_pin_weights() + self.circ_mod.update() def get_neighbor_di_neighbor_shares_two_vs_std(self, neighbor: "Cell") -> str: diff --git a/src/agent/circ_module.py b/src/agent/circ_module.py index 527dcd9..6db438a 100644 --- a/src/agent/circ_module.py +++ b/src/agent/circ_module.py @@ -4,6 +4,7 @@ from typing import TYPE_CHECKING from scipy.integrate import odeint from src.sim.util.math_helpers import round_to_sf +from src.arora_enums import PinLocalizationRulesetEnum from src.loc.quad_perimeter.quad_perimeter import get_len_perimeter_in_common if TYPE_CHECKING: @@ -164,22 +165,20 @@ def f(self, y: list[float], t: float) -> list[float]: return [f0, f1, f2, f3, f4, f5, f6, f7] - def solve_equations(self, time_step: float = 0.001, duration: float = 1.0) -> np.ndarray: + def solve_equations(self, dt_hours: float) -> np.ndarray: """ - Solve the model's differential equations over a given time span with a specified time step. + Advance the ODEs by dt_hours of biological time. Parameters ---------- - time_step : float - The time step for the ODE solver. Default is 0.001 hours. - duration : float - The total duration for the simulation. Default is 1.0 hours. + dt_hours : float + Biological time elapsed this simulation tick (in hours). Returns ------- ndarray - An array containing the solution of the differential equations at each time step. - Each row corresponds to a time step, and each column corresponds to one of the model variables. + Solution of the ODEs evaluated at t = 0 and t = dt_hours. + The last row (soln[-1]) is the state at t = dt_hours. """ y0 = [ self.get_auxin(), @@ -191,31 +190,26 @@ def solve_equations(self, time_step: float = 0.001, duration: float = 1.0) -> np self.get_lateral_pin(), self.get_medial_pin(), ] - t = np.linspace(0, duration, int(duration / time_step) + 1) + + t = np.array([0.0, dt_hours], dtype=float) soln = odeint(self.f, y0, t) return soln def update(self) -> None: """ - Update the model's circulation contents based on current PIN weights and differential equations. - - This method performs a series of updates to the model's state, including: - - Retrieving current PIN weights from the associated cell. - - Solving the model's differential equations over a time step. - - Updating auxin levels and other circulation-related contents based on the solutions obtained. - - Ensures that the sum of PIN weights is 1.0 before proceeding with the updates. + Update circulation contents based on current PIN weights and differential equations. """ - # Retrieve current PIN weights - self.pin_weights = ( - self.cell.get_pin_weights() - ) # Calculations to update PIN weights are done in Cell class - # assert round_to_sf(sum(self.pin_weights.values()), 2) == 1.0, "PIN weights sum to 1.0" + # Retrieve current PIN weights from the cell + self.pin_weights = self.cell.get_pin_weights() + assert round_to_sf(sum(self.pin_weights.values()), 2) == 1.0, "PIN weights sum to 1.0" + + # How much biological time does one simulation tick represent? + dt_hours = self.cell.get_sim().get_timestep_hours() - # Solve the differential equations for the current state - soln = self.solve_equations() + # Solve the ODEs over this biological time window + soln = self.solve_equations(dt_hours=dt_hours) - # Update model states based on the solutions + # Update model states based on the solution at t = dt_hours self.update_auxin(soln) self.update_circ_contents(soln) @@ -272,7 +266,7 @@ def calculate_neighbor_memfrac(self, neighbor: "Cell") -> float: return round_to_sf(memfrac, 6) def get_aux_exchange_across_membrane( - self, al: float, pindi: float, neighbors: list + self, al: float, pindi: float, neighbors: list, dt_hours: float ) -> dict["Cell", float]: """ Calculate the amount of auxin that will be transported across each @@ -301,27 +295,25 @@ def get_aux_exchange_across_membrane( memfrac = self.calculate_neighbor_memfrac(neighbor) neighbor_memfrac = neighbor.get_circ_mod().calculate_neighbor_memfrac(self.cell) neighbor_aux = neighbor.get_circ_mod().get_auxin() - auxin_influx = (neighbor_aux * (neighbor_memfrac)) * (al * memfrac) * self.k_al + + # Per-hour rates: + influx_rate = neighbor_aux * al * memfrac * self.k_al pin_activity = pindi * self.k_pin - # pin_activity = ((pindi * memfrac) / (pindi * memfrac + self.k_pin)) * self.k_pin accessible_auxin = self.auxin * memfrac - # print(f"accessible auxin {accessible_auxin}") - auxin_efflux = accessible_auxin * pin_activity - # print(f"auxin efflux {auxin_efflux}") - if ( - auxin_influx == float("inf") - or auxin_influx == float("-inf") - or auxin_efflux == float("inf") - or auxin_efflux == float("-inf") - ): + efflux_rate = accessible_auxin * pin_activity + + # Integrate over dt_hours + auxin_influx = influx_rate * dt_hours + auxin_efflux = efflux_rate * dt_hours + + if any(x in (float("inf"), float("-inf")) for x in (auxin_influx, auxin_efflux)): print(f"cell {self.cell.get_c_id()} neighbor {neighbor.get_c_id()}") print(f"neighbor's auxin {auxin_influx}, self aux out {auxin_efflux}") + raise ValueError("Negative auxin") + neighbor_aux_exchange = auxin_influx - auxin_efflux - if neighbor_aux_exchange != 0: - print( - f"HERE cell {self.cell.get_c_id()} neighbor {neighbor.get_c_id()} auxin exchange {neighbor_aux_exchange}" - ) neighbor_dict[neighbor] = round_to_sf(neighbor_aux_exchange, 5) + return neighbor_dict def calculate_delta_auxin(self, syn_deg_auxin: float, neighbors_auxin: list) -> float: @@ -370,6 +362,7 @@ def update_arr_hist(self) -> None: if i == (len(self.arr_hist) - 1): self.arr_hist[i] = self.arr + # TODO: (maybe) Make this abstract and implement in each circ mod def update_circ_contents(self, soln: np.ndarray) -> None: """ Update the circulation contents of the cell, except for auxin, based on the @@ -383,15 +376,27 @@ def update_circ_contents(self, soln: np.ndarray) -> None: different time point and columns represent the concentrations of different substances at that time point. """ - self.arr = round_to_sf(soln[1, 1], 5) - self.auxlax = round_to_sf(soln[1, 2], 5) - self.pin = round_to_sf(soln[1, 3], 5) - self.pin - self.pina = round_to_sf(soln[1, 4], 5) - self.pinb = round_to_sf(soln[1, 5], 5) - self.pinl = round_to_sf(soln[1, 6], 5) - self.pinm = round_to_sf(soln[1, 7], 5) + last = soln[-1] + self.arr = round_to_sf(last[1], 5) + self.auxlax = round_to_sf(last[2], 5) + self.pin = round_to_sf(last[3], 5) + self.pina = round_to_sf(last[4], 5) + self.pinb = round_to_sf(last[5], 5) + self.pinl = round_to_sf(last[6], 5) + self.pinm = round_to_sf(last[7], 5) self.update_arr_hist() + rules = self.cell.get_sim().get_pin_loc_rules() + if rules == PinLocalizationRulesetEnum.IMPOSED: + # For imposed, overwrite pina/pinb/pinl/pinm with the spatial pattern, + # but DO NOT touch self.pin_weights (those already came from cell) + pins_frac = self.cell.get_pin_weights() # already normalized + self.pina = pins_frac["a"] + self.pinb = pins_frac["b"] + self.pinl = pins_frac["l"] + self.pinm = pins_frac["m"] + self.auxlax = 1 + def update_neighbor_auxin(self, neighbors_auxin: list[dict]) -> None: """ Update the change in auxin concentrations for neighbor cells in the circulator. @@ -445,15 +450,38 @@ def update_auxin(self, soln: np.ndarray) -> None: parameters or the numerical solution. """ curr_cell = self.cell + dt_hours = self.cell.get_sim().get_timestep_hours() # Retrieve neighbors for auxin exchange neighborsa, neighborsb, neighborsl, neighborsm = self.get_neighbors() + reg = self.get_pin_reg_factor() + # Calculate auxin exchange across membranes - auxina_exchange = self.get_aux_exchange_across_membrane(self.auxlax, self.pina, neighborsa) - auxinb_exchange = self.get_aux_exchange_across_membrane(self.auxlax, self.pinb, neighborsb) - auxinl_exchange = self.get_aux_exchange_across_membrane(self.auxlax, self.pinl, neighborsl) - auxinm_exchange = self.get_aux_exchange_across_membrane(self.auxlax, self.pinm, neighborsm) + auxina_exchange = self.get_aux_exchange_across_membrane( + self.auxlax, + reg * self.cell.get_pin_weights()["a"], + neighborsa, + dt_hours + ) + auxinb_exchange = self.get_aux_exchange_across_membrane( + self.auxlax, + reg * self.cell.get_pin_weights()["b"], + neighborsb, + dt_hours + ) + auxinl_exchange = self.get_aux_exchange_across_membrane( + self.auxlax, + reg * self.cell.get_pin_weights()["l"], + neighborsl, + dt_hours + ) + auxinm_exchange = self.get_aux_exchange_across_membrane( + self.auxlax, + reg * self.cell.get_pin_weights()["m"], + neighborsm, + dt_hours + ) neighbors_auxin_exchange = [ auxina_exchange, auxinb_exchange, @@ -462,7 +490,9 @@ def update_auxin(self, soln: np.ndarray) -> None: ] # Compute net auxin synthesized and degraded at this time step - auxin_synthesized_and_degraded_this_timestep = soln[1, 0] - self.auxin + # Net auxin change over dt_hours from synthesis/degradation + aux_new = soln[-1, 0] + auxin_synthesized_and_degraded_this_timestep = aux_new - self.auxin delta_auxin = self.calculate_delta_auxin( auxin_synthesized_and_degraded_this_timestep, neighbors_auxin_exchange @@ -474,6 +504,50 @@ def update_auxin(self, soln: np.ndarray) -> None: # Update auxin levels in neighbor cells self.update_neighbor_auxin(neighbors_auxin_exchange) + # delta_from_syn_deg = aux_new - self.auxin + # delta_from_neighbors = delta_from_neighbors = sum(sum(d.values()) for d in neighbors_auxin_exchange) + # print(self.cell.c_id, + # "aux:", self.auxin, + # "Δ_syn_deg:", delta_from_syn_deg, + # "Δ_neighbors:", delta_from_neighbors) + + + def get_directional_pattern(self, direction: str) -> float: + return self.cell.get_pin_weights()[direction] # "a", "b", "l", "m" + + def get_pin_reg_factor(self) -> float: + """ + Compute a regulatory factor for PIN-mediated auxin efflux based on + the current auxin and ARR levels. + + - Auxin ACTIVATES export (increasing factor with auxin, saturating). + - ARR INHIBITS export (decreasing factor with ARR, saturating). + + The returned value is in [0, 1] and is dimensionless. + """ + # Guard against negatives / NaNs + aux = max(float(self.auxin), 0.0) + arr = max(float(self.arr), 0.0) + + # TODO: Restructure the circulation modules so that this is how PIN is regulated!!! + # Simple “Hill-like” saturating activation by auxin: + # aux_term ~ 0 when aux ~ 0, aux_term -> 1 as aux grows large + aux_term = aux / (aux + 1.0) + + # Simple saturating inhibition by ARR: + # arr_term ~ 1 when arr ~ 0, arr_term -> 0 as arr grows large + arr_term = 1.0 / (1.0 + arr) + + reg = aux_term * arr_term + + # Floating point protection + if reg < 0.0: + reg = 0.0 + elif reg > 1.0: + reg = 1.0 + + return reg + # getter functions def get_auxin(self) -> float: """ @@ -628,7 +702,7 @@ def set_auxin(self, new_aux: float) -> None: def get_pin_weights(self) -> dict[str, float]: """ - Get the weights of PIN localized in each membrane direction. + Get the weights of PIN localized in each membrane direction. Pin weights sum to 1. Returns ------- diff --git a/src/agent/circ_module_aux_syn_deg_export.py b/src/agent/circ_module_aux_syn_deg_export.py new file mode 100644 index 0000000..51b63ea --- /dev/null +++ b/src/agent/circ_module_aux_syn_deg_export.py @@ -0,0 +1,77 @@ +from typing import Any, TYPE_CHECKING +from src.agent.circ_module import CirculateModule +from src.arora_enums import PinLocalizationRulesetEnum + +if TYPE_CHECKING: + from src.agent.cell import Cell + + +class CirculateModuleAuxinSynDegExport(CirculateModule): + ks_aux: float + kd_aux: float + + def __init__(self, cell: "Cell", init_vals: dict[str, Any]): + super().__init__(cell, init_vals) + if cell.get_sim().get_pin_loc_rules() != PinLocalizationRulesetEnum.IMPOSED: + raise NotImplementedError( + "CirculateModuleAuxinSynDegTransport only supports imposed transporter distribution" + ) + + def get_float(key: str) -> float: + value = init_vals.get(key) + if value is None: + raise ValueError(f"Missing value for key: {key}") + return float(value) + + # Get auxin synthesis and degradation rates + self.ks_aux = get_float("ks_aux") + self.output_list.append("ks_aux") + self.kd_aux = get_float("kd_aux") + self.output_list.append("kd_aux") + # Set auxlax to 1 ("on") + self.auxlax = 1.0 + # Zero out arr + self.arr = 0 + + # Transporter expression and localization is imposed using functions in the Cell class, so we do not calculate anything here. + def calculate_auxin(self, auxini: float) -> float: + auxin = (self.ks_aux * self.auxin_w) - (self.kd_aux * auxini) + return auxin + + def calculate_arr(self, arri: float) -> float: + return 0 + + def calculate_auxlax(self, auxin: float, auxlax: float) -> float: + return 0 + + def calculate_pin(self, auxin: float, arr: float) -> float: + return 0 + + def calculate_membrane_pin( + self, pin: float, membrane_pin: float, direction: str, weight: float + ) -> float: + return 0 + + def get_state(self) -> dict[str, Any]: + state = { + "auxin": self.auxin, + "arr": self.arr, + "al": self.auxlax, + "pin": self.pin, + "pina": self.pina, + "pinb": self.pinb, + "pinl": self.pinl, + "pinm": self.pinm, + "k1": self.k_arr_arr, + "k2": self.k_auxin_auxlax, + "k3": self.k_auxin_pin, + "k4": self.k_arr_pin, + "k5": self.k_al, + "k6": self.k_pin, + "ks_aux": self.ks_aux, + "kd_aux": self.kd_aux, + "auxin_w": self.auxin_w, + "arr_hist": self.arr_hist, + "circ_mod": "aux_syndegonly", + } + return state diff --git a/src/arora_enums.py b/src/arora_enums.py new file mode 100644 index 0000000..16d70d2 --- /dev/null +++ b/src/arora_enums.py @@ -0,0 +1,13 @@ +from enum import Enum + + +class CircModEnum(Enum): + UNIVERSAL_SYN_DEG = 1 + INDEP_SYN_DEG = 2 + AUX_SYN_DEG_ONLY = 3 + AUX_SYN_DEG_EXP = 4 + + +class PinLocalizationRulesetEnum(Enum): + SIMPLE_INHERITANCE = 1 + IMPOSED = 2 diff --git a/src/loc/quad_perimeter/default_perimeter_geo_neighor_helper.py b/src/loc/quad_perimeter/default_perimeter_geo_neighor_helper.py index 292bf68..307045f 100644 --- a/src/loc/quad_perimeter/default_perimeter_geo_neighor_helper.py +++ b/src/loc/quad_perimeter/default_perimeter_geo_neighor_helper.py @@ -57,7 +57,10 @@ def get_default_len_perimeter_in_common(cell: "Cell", neighbor: "Cell") -> float neighborqp.get_bottom_left(), neighborqp.get_top_left(), ] - cell_vs_on_shared_edge = [cellqp.get_bottom_right(), cellqp.get_top_right()] + cell_vs_on_shared_edge = [ + cellqp.get_bottom_right(), + cellqp.get_top_right(), + ] length = PerimeterNeighborHelpers.get_overlap( neighbor_vs_on_shared_edge, cell_vs_on_shared_edge ) @@ -69,7 +72,10 @@ def get_default_len_perimeter_in_common(cell: "Cell", neighbor: "Cell") -> float neighborqp.get_bottom_right(), neighborqp.get_top_right(), ] - cell_vs_on_shared_edge = [cellqp.get_bottom_left(), cellqp.get_top_left()] + cell_vs_on_shared_edge = [ + cellqp.get_bottom_left(), + cellqp.get_top_left(), + ] length = PerimeterNeighborHelpers.get_overlap( neighbor_vs_on_shared_edge, cell_vs_on_shared_edge ) @@ -79,7 +85,10 @@ def get_default_len_perimeter_in_common(cell: "Cell", neighbor: "Cell") -> float cellqp.get_left_lateral_or_medial(neighbor.get_sim().get_root_midpointx()) == "lateral" ): - cell_vs_on_shared_edge = [cellqp.get_bottom_left(), cellqp.get_top_left()] + cell_vs_on_shared_edge = [ + cellqp.get_bottom_left(), + cellqp.get_top_left(), + ] neighbor_vs_on_shared_edge = [ neighborqp.get_bottom_right(), neighborqp.get_top_right(), @@ -91,7 +100,10 @@ def get_default_len_perimeter_in_common(cell: "Cell", neighbor: "Cell") -> float cellqp.get_right_lateral_or_medial(neighbor.get_sim().get_root_midpointx()) == "lateral" ): - cell_vs_on_shared_edge = [cellqp.get_bottom_right(), cellqp.get_top_right()] + cell_vs_on_shared_edge = [ + cellqp.get_bottom_right(), + cellqp.get_top_right(), + ] neighbor_vs_on_shared_edge = [ neighborqp.get_bottom_left(), neighborqp.get_top_left(), @@ -247,7 +259,8 @@ def get_default_len_perimeter_in_common(cell: "Cell", neighbor: "Cell") -> float @staticmethod def get_overlap( - cell_vs_on_shared_edge: list["Vertex"], neighbor_vs_on_shared_edge: list["Vertex"] + cell_vs_on_shared_edge: list["Vertex"], + neighbor_vs_on_shared_edge: list["Vertex"], ) -> float: """ Calculate the length of the overlap between two segments. diff --git a/src/loc/quad_perimeter/quad_perimeter.py b/src/loc/quad_perimeter/quad_perimeter.py index e08704c..bf86d39 100644 --- a/src/loc/quad_perimeter/quad_perimeter.py +++ b/src/loc/quad_perimeter/quad_perimeter.py @@ -2,7 +2,9 @@ from typing import TYPE_CHECKING from src.sim.util.math_helpers import round_to_sf from src.loc.vertex.vertex import Vertex -from src.loc.quad_perimeter.default_perimeter_geo_neighor_helper import PerimeterNeighborHelpers +from src.loc.quad_perimeter.default_perimeter_geo_neighor_helper import ( + PerimeterNeighborHelpers, +) if TYPE_CHECKING: from src.agent.cell import Cell diff --git a/src/sim/mover/vertex_mover.py b/src/sim/mover/vertex_mover.py index 67f7025..150c096 100644 --- a/src/sim/mover/vertex_mover.py +++ b/src/sim/mover/vertex_mover.py @@ -173,6 +173,11 @@ def sort_top_row(self, top_row: list["Cell"]) -> list["Cell"]: left_xs = [] for cell in top_row: left_xs.append(cell.get_quad_perimeter().get_top_left().get_x()) + zipped_list = zip(left_xs, top_row) + sorted_zipped_list = sorted(zipped_list) + sorted_top_row = [] + for _, cell in sorted_zipped_list: + sorted_top_row.append(cell) return [cell for _, cell in sorted(zip(left_xs, top_row))] def propogate_deltas(self, top_row: list["Cell"]) -> None: @@ -249,7 +254,7 @@ def execute_vertex_movement(self, max_delta: float) -> None: # iterate through all nongrowing cells in root tip, move all basal vertices not yet moved moved_vs = list(self.vertex_deltas.keys()) for cell in self.sim.get_cell_list(): - if not cell.get_growing() and cell.get_dev_zone() is "roottip": + if not cell.get_growing() and cell.get_dev_zone() == "roottip": vertices = cell.get_quad_perimeter().get_vs() for vertex in vertices: if vertex not in moved_vs: @@ -266,7 +271,8 @@ def check_if_divide(self, cells: list["Cell"]) -> None: The list of cells this VertexMover affected this time point. """ for cell in cells: - if cell.get_quad_perimeter().get_area() >= ( - 2 * cell.get_quad_perimeter().get_init_area() - ): - self.sim.get_divider().add_cell(cell) + if cell.get_dev_zone() == "meristematic": + if cell.get_quad_perimeter().get_area() >= ( + 2 * cell.get_quad_perimeter().get_init_area() + ): + self.sim.get_divider().add_cell(cell) diff --git a/src/sim/output/output.py b/src/sim/output/output.py index 87197e3..8d8c25b 100644 --- a/src/sim/output/output.py +++ b/src/sim/output/output.py @@ -1,5 +1,6 @@ import csv import json +import os from typing import TYPE_CHECKING, Any if TYPE_CHECKING: @@ -11,8 +12,8 @@ class Output: """ Handles the generation and management of simulation output data. - This class is responsible for creating and writing simulation results to a CSV file, - including detailed information about each cell. + This class is responsible for creating and writing simulation results to a CSV file + and to per-tick JSON files, including detailed information about each cell. Attributes ---------- @@ -21,7 +22,8 @@ class Output: filename_csv : str The name of the CSV file to which output data will be written. filename_json : str - The name of the JSON file to which output data will be written. + The *base* name of the JSON file. A per-tick suffix will be added, e.g. + 'ARORA_output_tick_10.json'. Parameters ---------- @@ -30,39 +32,46 @@ class Output: filename_csv : str The filename for the output CSV file. filename_json : str - The filename for the output JSON file. + The base filename for the output JSON files. """ def __init__(self, sim: "GrowingSim", filename_csv: str, filename_json: str): - """ - Initializes the Output object with a simulation instance and output filenames. - - Parameters - ---------- - sim : GrowingSim - The simulation instance associated with this output. - filename_csv : str - The filename for the output CSV file. - filename_json : str - The filename for the output JSON file. - """ self.sim = sim self.filename_csv = filename_csv self.filename_json = filename_json self.title_labels_written_to_output_file = False + self.sim_and_cell_contents: list[str] | None = None + self.circ_contents: list[str] | None = None + + def _json_filename_for_tick(self, tick: int) -> str: + """ + Build a per-tick JSON filename from the base filename_json. + + Example: + base: 'ARORA_output.json', tick: 10 -> + 'ARORA_output_tick_10.json' + """ + base, ext = os.path.splitext(self.filename_json) + if not ext: + ext = ".json" + return f"{base}_tick_{tick}{ext}" def output_cells(self) -> None: """ - Writes the current state of all cells to the output files. + Writes the current state of all cells to the CSV file and a per-tick JSON file. - This method gathers data from each cell within the simulation, including - concentrations, locations, and PIN distributions, and writes this information - to the specified output files. + For each call: + - Appends rows for all cells at the current tick to the CSV. + - Writes all cell summaries for this tick to a separate JSON file whose name + includes the current tick. """ - if self.title_labels_written_to_output_file == False: - if len(self.sim.get_cell_list()) <= 0: - print("No Cells added to simulation. Cannot output simulation contents.") - return + cell_list = list(self.sim.get_cell_list()) + if not cell_list: + print("No Cells added to simulation. Cannot output simulation contents.") + return + + # Initialize CSV header once + if not self.title_labels_written_to_output_file: sim_and_cell_contents = [ "tick", "cell", @@ -75,17 +84,22 @@ def output_cells(self) -> None: "cell_type", ] self.sim_and_cell_contents = sim_and_cell_contents - circ_contents = list(self.sim.get_cell_list()[0].get_circ_mod().get_state().keys()) + circ_contents = list(cell_list[0].get_circ_mod().get_state().keys()) self.circ_contents = circ_contents + with open(self.filename_csv, "w", newline="") as file: writer = csv.writer(file) writer.writerow(sim_and_cell_contents + circ_contents) + self.title_labels_written_to_output_file = True - output = [] - cell_list = list(self.sim.get_cell_list()) + + # Build summaries for all cells at this tick + tick = self.sim.get_tick() + output: list[dict[str, Any]] = [] + for cell in cell_list: summary: dict[str, Any] = {} - summary["tick"] = self.sim.get_tick() + summary["tick"] = tick summary["cell"] = cell.get_c_id() summary["location"] = cell.quad_perimeter.get_corners_for_disp() summary["apical_memlen"] = cell.quad_perimeter.get_apical_memlen() @@ -97,54 +111,25 @@ def output_cells(self) -> None: summary.update(self.get_circ_contents(summary, cell)) output.append(summary) - # Generate CSV - header = output[0].keys() + # --- CSV: append rows --- + header = list(output[0].keys()) with open(self.filename_csv, "a", newline="") as file: csv_writer = csv.DictWriter(file, fieldnames=header) csv_writer.writerows(output) - # Generate JSON - with open(self.filename_json, "a") as file: + # --- JSON: write one file per tick --- + json_filename = self._json_filename_for_tick(tick) + with open(json_filename, "w") as file: json.dump(output, file, indent=4) def get_circ_contents(self, summary: dict[str, Any], cell: "Cell") -> dict[str, Any]: """ Populates the summary dictionary with circulation content information for a given cell. - - Parameters - ---------- - summary : dict[str, Any] - The summary dictionary to be populated with cell circulation data. - cell : Cell - The cell from which to retrieve circulation content information. - - Returns - ------- - dict[str, Any] - The updated summary dictionary containing circulation content information for the cell. """ return cell.get_circ_mod().get_state() def get_division_number(self, cell: "Cell") -> int: """ - Retrieves the number of divisions a cell has undergone. - - This method is a placeholder and is not implemented. - - Parameters - ---------- - cell : Cell - The cell from which to retrieve the division count. - - Returns - ------- - int - The number of divisions the cell has undergone. - - Raises - ------ - NotImplementedError - Indicates that the method is not yet implemented. + Placeholder for division number retrieval. """ - raise NotImplementedError - return 0 + raise NotImplementedError \ No newline at end of file diff --git a/src/sim/simulation/sim.py b/src/sim/simulation/sim.py index 978f4b8..58f46cc 100644 --- a/src/sim/simulation/sim.py +++ b/src/sim/simulation/sim.py @@ -9,6 +9,8 @@ from arcade import set_background_color from arcade import close_window, set_window import time +from src.arora_enums import PinLocalizationRulesetEnum +from src.arora_enums import CircModEnum from src.sim.circulator.circulator import Circulator from src.sim.divider.divider import Divider from src.sim.mover.vertex_mover import VertexMover @@ -34,7 +36,7 @@ class GrowingSim(Window): Attributes ---------- - timestep : int + timestep : float The size of the timestep of the simulation, in seconds. circulator : Circulator Manages the circulation of auxin within the simulation. @@ -50,6 +52,10 @@ class GrowingSim(Window): A list of vertices currently present in the simulation. vis : bool Indicates whether the simulation should be visualized. + pin_loc_rules : PinLocalizationRuleset + The rules the cells follow to determine how they localize PIN auxin exporters. + circ_mod : CircMod + The circulation module the cells should be using. next_cell_id : int The ID to be assigned to the next new cell. root_tip_y : float @@ -73,6 +79,10 @@ class GrowingSim(Window): The timestep size for the simulation, in seconds. vis : bool Flag to indicate whether the simulation should be visualized. + pin_loc_rules : PinLocalizationRuleset + The rules the cells follow to determine how they localize PIN auxin exporters. + circ_mod : CircMod + The circulation module the cells should be using. cell_val_file : str, optional The filename containing cell values to initialize the simulation. v_file : str, optional @@ -81,10 +91,12 @@ class GrowingSim(Window): Series containing global parameters for the simulation. geometry : str, optional Indicates the geometric configuration of the simulation. + output_frequency : int + Indicates how often (in units of ticks) the simulation should generate an output file """ - timestep: int + timestep: float circulator: "Circulator" vertex_mover: "VertexMover" divider: "Divider" @@ -92,6 +104,8 @@ class GrowingSim(Window): cell_list: SpriteList vertex_list: list vis: bool + pin_loc_rules: PinLocalizationRulesetEnum + circ_mod: CircModEnum next_cell_id: int root_tip_y: float = 0 cell_val_file: str @@ -103,27 +117,25 @@ def __init__( width: int, height: int, title: str, - timestep: int, + timestep: float, vis: bool, + pin_loc_rules: PinLocalizationRulesetEnum, + circ_mod: CircModEnum, cell_val_file: str = "", v_file: str = "", gparam_series: pandas.core.series.Series | str = "", geometry: str = "", output_file: str = "output", + output_frequency: int = 1 ): """ Initializes a new instance of the GrowingSim class, setting up the simulation environment and parameters. """ self.cell_list = SpriteList(use_spatial_hash=False) self.vertex_list = [] - if vis is False: - print("Running headless") - # for mac - pyglet.options["headless"] = True - # for PC - os.environ["ARCADE_HEADLESS"] = "true" - super().__init__(width, height, title, visible=False) - if vis is True: + if not vis: + print("Running headless (no window)") + else: super().__init__(width, height, title) set_background_color(color=(250, 250, 250, 250)) if cell_val_file != "" and v_file != "": @@ -136,10 +148,13 @@ def __init__( self.geometry = geometry self.timestep = timestep self.vis = vis + self.pin_loc_rules = pin_loc_rules + self.circ_mod = circ_mod self.cmap = plt.get_cmap("coolwarm") self.setup() self.output = Output(self, f"{output_file}.csv", f"{output_file}.json") self.exit_flag = False + self.output_frequency = output_frequency def get_root_midpointx(self) -> float: """ @@ -177,9 +192,21 @@ def get_cell_by_ID(self, ID: int) -> "Cell": raise ValueError(f"Cell with ID {ID} not found in cell_list") def get_timestep(self) -> float: - """Returns the timestep of the simulation (in seconds).""" + """Returns the simulation timestep (in hours per tick).""" return self.timestep + def get_timestep_hours(self) -> float: + """Returns the timestep of the simulation in hours. Redundant with get_timestep.""" + return float(self.timestep) + + def get_pin_loc_rules(self) -> PinLocalizationRulesetEnum: + """Returns the PIN localization ruleset""" + return self.pin_loc_rules + + def get_circ_mod(self) -> CircModEnum: + """Returns the circulation module the cells should use""" + return self.circ_mod + def get_tick(self) -> int: """Returns the current tick (or step) of the simulation.""" return self.tick @@ -233,6 +260,8 @@ def setup(self) -> None: This method is called to (re)start the simulation, setting up initial cell configurations, and preparing the simulation environment. """ + if len(self.cell_list) != 0: + raise Exception("Setup is being called twice. This can lead to doubling instances of cells if using default setup files. Reminder setup is called during simulation initialization. Please remove second setup call.") # find midpoint x of root basd on vertices that exist self.tick = 0 self.next_cell_id = 0 @@ -318,12 +347,12 @@ def on_update(self, delta_time: float) -> None: delta_time: The time step. """ print("----") - self.output.output_cells() + if self.tick % self.output_frequency == 0: + self.output.output_cells() self.tick += 1 - # max_tick = 24 * 8 + max_tick = 26 / self.get_timestep_hours() # number of timesteps to reach 26 hours, adjust as you see fit try: - if self.tick < 27: - self.output.output_cells() + if self.tick < max_tick: print(f"tick: {self.tick}") if self.vis: self.update_viewport_position() @@ -348,23 +377,33 @@ def on_update(self, delta_time: float) -> None: raise e def run_sim(self) -> None: - while not self.exit_flag: - pyglet.clock.tick() - self.dispatch_events() - self.on_update(1 / 60.0) - print("CLOSING WINDOW") - self.close() # Close the window - print("WINDOW CLOSED") - pyglet.app.exit() # Exit the pyglet event loop + if self.vis: + # Normal arcade/pyglet loop with a real window + while not self.exit_flag: + pyglet.clock.tick() + self.dispatch_events() + self.on_update(1 / 60.0) + print("CLOSING WINDOW") + self.close() + print("WINDOW CLOSED") + pyglet.app.exit() + else: + # Pure headless loop: just step the simulation, no window, no events + while not self.exit_flag: + self.on_update(self.timestep) + print("Headless simulation complete") def main( - timestep: int, + timestep: float, vis: bool, + pin_loc_rules: PinLocalizationRulesetEnum, + circ_mod: CircModEnum, cell_val_file: str = "", v_file: str = "", gparam_series: Series | str = "", output_file: str = "output", + output_frequency: int = 1 ) -> int: """Creates and runs the ABM.""" print("Making GrowingSim") @@ -377,11 +416,14 @@ def main( SCREEN_TITLE, timestep, vis, + pin_loc_rules, + circ_mod, cell_val_file, v_file, gparam_series, geometry, output_file, + output_frequency ) set_window(simulation) print("Running Simulation") diff --git a/tests/functional/test_initialization_symmetry_unittest.py b/tests/functional/test_initialization_symmetry_unittest.py index 3719754..df4ac3c 100644 --- a/tests/functional/test_initialization_symmetry_unittest.py +++ b/tests/functional/test_initialization_symmetry_unittest.py @@ -5,12 +5,16 @@ os.environ["ARCADE_HEADLESS"] = "True" import unittest from src.sim.simulation.sim import GrowingSim +from src.arora_enums import PinLocalizationRulesetEnum, CircModEnum SCREEN_WIDTH = 1000 SCREEN_HEIGHT = 1000 SCREEN_TITLE = "Starting Template" +PIN_LOC_RULES = PinLocalizationRulesetEnum.SIMPLE_INHERITANCE +CIRC_MOD = CircModEnum.UNIVERSAL_SYN_DEG +# TODO: Add integration tests with different PIN_LOC_RULES and CIRC_MOD values class TestInitializationSymmetry(unittest.TestCase): """ Tests the symmetry of the default simulation initialization. @@ -28,6 +32,8 @@ def test_initial_symmetry(self): SCREEN_TITLE, timestep, vis, + PIN_LOC_RULES, + CIRC_MOD, cell_val_file, v_file, gparam_series, @@ -938,6 +944,8 @@ def test_symmetry_after_updates(self): SCREEN_TITLE, timestep, vis, + PIN_LOC_RULES, + CIRC_MOD, cell_val_file, v_file, gparam_series, diff --git a/tests/unit/test_cell_unittest.py b/tests/unit/test_cell_unittest.py index 8293c07..25cfeab 100644 --- a/tests/unit/test_cell_unittest.py +++ b/tests/unit/test_cell_unittest.py @@ -4,6 +4,7 @@ if platform.system() == "Linux": os.environ["ARCADE_HEADLESS"] = "True" import unittest +from src.arora_enums import PinLocalizationRulesetEnum, CircModEnum from src.agent.cell import Cell from src.loc.vertex.vertex import Vertex from src.loc.quad_perimeter.quad_perimeter import QuadPerimeter @@ -12,6 +13,8 @@ SCREEN_WIDTH = 800 SCREEN_HEIGHT = 600 SCREEN_TITLE = "Starting Template" +PIN_LOC_RULES = PinLocalizationRulesetEnum.SIMPLE_INHERITANCE +CIRC_MOD = CircModEnum.UNIVERSAL_SYN_DEG class TestCell(unittest.TestCase): @@ -41,7 +44,9 @@ class TestCell(unittest.TestCase): def test_get_area(self): timestep = 1 - simulation = GrowingSim(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, timestep, False) + simulation = GrowingSim( + SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, timestep, False, PIN_LOC_RULES, CIRC_MOD + ) v1 = Vertex(10, 10) v2 = Vertex(10, 30) v3 = Vertex(30, 30) @@ -51,7 +56,9 @@ def test_get_area(self): def test_add_neighbor(self): timestep = 1 - simulation = GrowingSim(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, timestep, False) + simulation = GrowingSim( + SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, timestep, False, PIN_LOC_RULES, CIRC_MOD + ) init_id = simulation.get_next_cell_id() v1 = Vertex(10, 10) v2 = Vertex(10, 30) @@ -96,7 +103,9 @@ def test_add_neighbor(self): def test_remove_neighbor(self): timestep = 1 - simulation = GrowingSim(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, timestep, False) + simulation = GrowingSim( + SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, timestep, False, PIN_LOC_RULES, CIRC_MOD + ) v1 = Vertex(10, 10) v2 = Vertex(10, 30) v3 = Vertex(30, 30) diff --git a/tests/unit/test_circ_module_cont_unittest.py b/tests/unit/test_circ_module_cont_unittest.py index 3f143b0..34a1843 100644 --- a/tests/unit/test_circ_module_cont_unittest.py +++ b/tests/unit/test_circ_module_cont_unittest.py @@ -8,12 +8,15 @@ import numpy as np from src.loc.vertex.vertex import Vertex from src.agent.cell import Cell +from src.arora_enums import PinLocalizationRulesetEnum, CircModEnum from src.sim.simulation.sim import GrowingSim from src.sim.util.math_helpers import round_to_sf SCREEN_WIDTH = 800 SCREEN_HEIGHT = 600 SCREEN_TITLE = "Starting Template" +PIN_LOC_RULES = PinLocalizationRulesetEnum.SIMPLE_INHERITANCE +CIRC_MOD = CircModEnum.UNIVERSAL_SYN_DEG KS = 0.005 KD = 0.0015 @@ -33,7 +36,9 @@ class BaseCirculateModuleContTests(unittest.TestCase): time_step = 0.001 def test_calculate_cont_auxin(self): - sim = GrowingSim(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, 1, False) + sim = GrowingSim( + SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, 1, False, PIN_LOC_RULES, CIRC_MOD + ) cell = Cell( sim, [ @@ -64,7 +69,9 @@ def test_calculate_cont_auxin(self): self.assertAlmostEqual(expected_auxin, found_auxin, places=3) def test_calculate_arr(self): - sim = GrowingSim(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, 1, False) + sim = GrowingSim( + SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, 1, False, PIN_LOC_RULES, CIRC_MOD + ) cell = Cell( sim, [ @@ -89,7 +96,9 @@ def test_calculate_arr(self): self.assertAlmostEqual(expected_arr, found_arr, places=5) def test_calculate_auxlax(self): - sim = GrowingSim(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, 1, 40, False) + sim = GrowingSim( + SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, 1, False, PIN_LOC_RULES, CIRC_MOD + ) cell = Cell( sim, [ @@ -112,7 +121,9 @@ def test_calculate_auxlax(self): self.assertAlmostEqual(expected_al, found_al, places=5) def test_calculate_pin(self): - sim = GrowingSim(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, 1, False) + sim = GrowingSim( + SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, 1, False, PIN_LOC_RULES, CIRC_MOD + ) cell = Cell( sim, [ @@ -137,7 +148,9 @@ def test_calculate_pin(self): self.assertAlmostEqual(expected_pin, found_pin, places=5) def test_calculate_membrane_pin(self): - sim = GrowingSim(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, 1, False) + sim = GrowingSim( + SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, 1, False, PIN_LOC_RULES, CIRC_MOD + ) cell = Cell( sim, [ @@ -171,7 +184,9 @@ def test_calculate_membrane_pin(self): self.assertAlmostEqual(expected_pin, found_pin, places=3) def test_calculate_neighbor_memfrac(self): - sim = GrowingSim(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, 1, False) + sim = GrowingSim( + SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, 1, False, PIN_LOC_RULES, CIRC_MOD + ) v1 = Vertex(10, 10) v2 = Vertex(10, 30) v3 = Vertex(30, 30) @@ -200,7 +215,9 @@ def test_calculate_neighbor_memfrac(self): self.assertAlmostEqual(expected_f, found_f, places=5) def test_get_neighbor_auxin_exchange(self): - sim = GrowingSim(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, 1, False) + sim = GrowingSim( + SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, 1, False, PIN_LOC_RULES, CIRC_MOD + ) v1 = Vertex(10, 10) v2 = Vertex(10, 30) v3 = Vertex(30, 30) @@ -241,7 +258,9 @@ def test_get_neighbor_auxin_exchange(self): self.assertAlmostEqual(expected, found, places=5) def test_calculate_delta_auxin(self): - sim = GrowingSim(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, 1, False) + sim = GrowingSim( + SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, 1, False, PIN_LOC_RULES, CIRC_MOD + ) cell = Cell( sim, [ @@ -272,7 +291,9 @@ def test_calculate_delta_auxin(self): self.assertAlmostEqual(expected_delta_auxin, found_delta_auxin, places=5) def test_solve_equations(self): - sim = GrowingSim(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, 1, False) + sim = GrowingSim( + SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, 1, False, PIN_LOC_RULES, CIRC_MOD + ) cell = Cell( sim, [ @@ -314,7 +335,9 @@ def test_solve_equations(self): self.assertAlmostEqual(expected_soln[-1, i], found_soln[-1, i], places=3) def test_update_arr_hist(self): - sim = GrowingSim(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, 1, False) + sim = GrowingSim( + SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, 1, False, PIN_LOC_RULES, CIRC_MOD + ) cell = Cell( sim, [ @@ -334,7 +357,9 @@ def test_update_arr_hist(self): self.assertEqual(expected_arr_hist, found_arr_hist) def test_update_circ_contents(self): - sim = GrowingSim(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, 1, False) + sim = GrowingSim( + SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, 1, False, PIN_LOC_RULES, CIRC_MOD + ) cell = Cell( sim, [ @@ -375,7 +400,9 @@ def test_update_circ_contents(self): self.assertAlmostEqual(expected_pina, found_pina, places=4) def test_update_neighbor_auxin(self): - sim = GrowingSim(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, 1, False) + sim = GrowingSim( + SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, 1, False, PIN_LOC_RULES, CIRC_MOD + ) curr_cell = Cell( sim, [ @@ -418,7 +445,9 @@ def test_update_neighbor_auxin(self): self.assertEqual(expected, found) def test_get_neighbors(self): - sim = GrowingSim(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, 1, False) + sim = GrowingSim( + SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, 1, False, PIN_LOC_RULES, CIRC_MOD + ) v1 = Vertex(10, 10) v2 = Vertex(10, 30) v3 = Vertex(30, 30) @@ -455,7 +484,9 @@ def test_get_neighbors(self): # TODO: Rewrite this test this thing is brutal def test_update_auxin(self): - sim = GrowingSim(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, 1, vis=False) + sim = GrowingSim( + SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, 1, False, PIN_LOC_RULES, CIRC_MOD + ) v1 = Vertex(10, 10) v2 = Vertex(10, 30) v3 = Vertex(30, 30) @@ -502,7 +533,9 @@ def test_update_auxin(self): found = curr_cell.get_sim().get_circulator().delta_auxins # Make new cells with same properties to check calculations individually - sim2 = GrowingSim(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, 1, vis=False) + sim2 = GrowingSim( + SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, 1, False, PIN_LOC_RULES, CIRC_MOD + ) v1 = Vertex(10, 10) v2 = Vertex(10, 30) v3 = Vertex(30, 30) @@ -555,7 +588,9 @@ def test_update_auxin(self): self.assertAlmostEqual(expected[key], found[key], places=5) def test_get_auxin(self): - sim = GrowingSim(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, 1, False) + sim = GrowingSim( + SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, 1, False, PIN_LOC_RULES, CIRC_MOD + ) cell = Cell( sim, [ @@ -574,7 +609,9 @@ def test_get_auxin(self): self.assertEqual(expected, found) def test_get_arr(self): - sim = GrowingSim(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, 1, False) + sim = GrowingSim( + SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, 1, False, PIN_LOC_RULES, CIRC_MOD + ) cell = Cell( sim, [ @@ -593,7 +630,9 @@ def test_get_arr(self): self.assertEqual(expected, found) def test_get_auxlax(self): - sim = GrowingSim(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, 1, 40, False) + sim = GrowingSim( + SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, 1, False, PIN_LOC_RULES, CIRC_MOD + ) cell = Cell( sim, [ @@ -612,7 +651,9 @@ def test_get_auxlax(self): self.assertEqual(expected, found) def test_get_pin(self): - sim = GrowingSim(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, 1, False) + sim = GrowingSim( + SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, 1, False, PIN_LOC_RULES, CIRC_MOD + ) cell = Cell( sim, [ @@ -631,7 +672,9 @@ def test_get_pin(self): self.assertEqual(expected, found) def test_get_apical_pin(self): - sim = GrowingSim(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, 1, False) + sim = GrowingSim( + SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, 1, False, PIN_LOC_RULES, CIRC_MOD + ) cell = Cell( sim, [ @@ -661,7 +704,9 @@ def test_get_apical_pin(self): self.assertEqual(expected, found) def test_get_left_pin(self): - sim = GrowingSim(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, 1, False) + sim = GrowingSim( + SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, 1, False, PIN_LOC_RULES, CIRC_MOD + ) cell = Cell( sim, [ @@ -691,7 +736,9 @@ def test_get_left_pin(self): self.assertEqual(expected, found) def test_get_right_pin(self): - sim = GrowingSim(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, 1, False) + sim = GrowingSim( + SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, 1, False, PIN_LOC_RULES, CIRC_MOD + ) cell = Cell( sim, [ diff --git a/tests/unit/test_circulator_unittest.py b/tests/unit/test_circulator_unittest.py index 2d4028a..257d310 100644 --- a/tests/unit/test_circulator_unittest.py +++ b/tests/unit/test_circulator_unittest.py @@ -8,10 +8,13 @@ from src.agent.circ_module_universal_syndeg import CirculateModuleUniversalSynDeg from src.agent.cell import Cell from src.sim.simulation.sim import GrowingSim +from src.arora_enums import PinLocalizationRulesetEnum, CircModEnum SCREEN_WIDTH = 800 SCREEN_HEIGHT = 600 SCREEN_TITLE = "Starting Template" +PIN_LOC_RULES = PinLocalizationRulesetEnum.SIMPLE_INHERITANCE +CIRC_MOD = CircModEnum.UNIVERSAL_SYN_DEG def make_init_vals(): @@ -49,7 +52,9 @@ class TestCirculator(unittest.TestCase): """ def test_add_delta(self): - sim = GrowingSim(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, 1, False) + sim = GrowingSim( + SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, 1, False, PIN_LOC_RULES, CIRC_MOD + ) cell = Cell( sim, [ @@ -65,7 +70,9 @@ def test_add_delta(self): self.assertEqual(sim.get_circulator().get_delta_auxins()[cell], delta) def test_update(self): - sim = GrowingSim(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, 1, False) + sim = GrowingSim( + SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, 1, False, PIN_LOC_RULES, CIRC_MOD + ) cell = Cell( sim, [ diff --git a/tests/unit/test_divider_unittest.py b/tests/unit/test_divider_unittest.py index 261665b..399e2d8 100644 --- a/tests/unit/test_divider_unittest.py +++ b/tests/unit/test_divider_unittest.py @@ -8,10 +8,13 @@ from src.agent.cell import Cell from src.sim.simulation.sim import GrowingSim from src.loc.vertex.vertex import Vertex +from src.arora_enums import PinLocalizationRulesetEnum, CircModEnum SCREEN_WIDTH = 800 SCREEN_HEIGHT = 600 SCREEN_TITLE = "Starting Template" +PIN_LOC_RULES = PinLocalizationRulesetEnum.SIMPLE_INHERITANCE +CIRC_MOD = CircModEnum.UNIVERSAL_SYN_DEG class TestDivider(unittest.TestCase): @@ -41,7 +44,9 @@ class TestDivider(unittest.TestCase): def test_get_new_vs(self): timestep = 1 - simulation = GrowingSim(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, timestep, False) + simulation = GrowingSim( + SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, timestep, False, PIN_LOC_RULES, CIRC_MOD + ) v1 = Vertex(10, 10) v2 = Vertex(10, 30) v3 = Vertex(30, 30) @@ -53,7 +58,9 @@ def test_get_new_vs(self): def test_check_neighbors_for_v_existence(self): timestep = 1 - simulation = GrowingSim(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, timestep, False) + simulation = GrowingSim( + SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, timestep, False, PIN_LOC_RULES, CIRC_MOD + ) v1 = Vertex(10, 10) v2 = Vertex(10, 30) v3 = Vertex(30, 30) @@ -74,7 +81,9 @@ def test_check_neighbors_for_v_existence(self): def test_swap_neighbors(self): timestep = 1 - simulation = GrowingSim(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, timestep, False) + simulation = GrowingSim( + SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, timestep, False, PIN_LOC_RULES, CIRC_MOD + ) v1 = Vertex(10, 10) v2 = Vertex(10, 30) v3 = Vertex(30, 30) @@ -100,7 +109,9 @@ def test_swap_neighbors(self): def test_set_one_side_neighbors(self): timestep = 1 - simulation = GrowingSim(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, timestep, False) + simulation = GrowingSim( + SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, timestep, False, PIN_LOC_RULES, CIRC_MOD + ) v12 = Vertex(110, 10) v13 = Vertex(110, 30) simulation.vertex_list.extend([v12, v13]) @@ -172,7 +183,9 @@ def test_set_one_side_neighbors(self): def test_update_neighbor_lists(self): timestep = 1 - simulation = GrowingSim(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, timestep, False) + simulation = GrowingSim( + SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, timestep, False, PIN_LOC_RULES, CIRC_MOD + ) v1 = Vertex(10, 10) v2 = Vertex(10, 30) v3 = Vertex(30, 30) @@ -223,7 +236,9 @@ def test_update_neighbor_lists(self): def test_update(self): timestep = 1 - simulation = GrowingSim(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, timestep, False) + simulation = GrowingSim( + SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, timestep, False, PIN_LOC_RULES, CIRC_MOD + ) v1 = Vertex(10, 10) v2 = Vertex(10, 30) v3 = Vertex(30, 30) diff --git a/tests/unit/test_input_unittest.py b/tests/unit/test_input_unittest.py index 5504c26..096ab6f 100644 --- a/tests/unit/test_input_unittest.py +++ b/tests/unit/test_input_unittest.py @@ -12,10 +12,13 @@ from src.agent.cell import Cell from src.loc.vertex.vertex import Vertex from src.agent.circ_module_universal_syndeg import CirculateModuleUniversalSynDeg +from src.arora_enums import PinLocalizationRulesetEnum, CircModEnum SCREEN_WIDTH = 800 SCREEN_HEIGHT = 600 SCREEN_TITLE = "Starting Template" +PIN_LOC_RULES = PinLocalizationRulesetEnum.SIMPLE_INHERITANCE +CIRC_MOD = CircModEnum.UNIVERSAL_SYN_DEG class TestInput(unittest.TestCase): @@ -24,7 +27,9 @@ class TestInput(unittest.TestCase): """ def test_get_vertex(self): - sim = GrowingSim(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, 1, False) + sim = GrowingSim( + SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, 1, False, PIN_LOC_RULES, CIRC_MOD + ) input = Input( "tests/unit/test_input_files/init_vals.json", "tests/unit/test_input_files/vertex.json", @@ -44,7 +49,9 @@ def test_get_vertex(self): self.assertEqual(expected_vertex_list[each], found_vertex_list) def test_get_init_vals(self): - sim = GrowingSim(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, 1, False) + sim = GrowingSim( + SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, 1, False, PIN_LOC_RULES, CIRC_MOD + ) input = Input( "tests/unit/test_input_files/init_vals.json", "tests/unit/test_input_files/vertex.json", @@ -106,7 +113,9 @@ def test_get_init_vals(self): self.assertEqual(expected[cell][val], found[cell][val]) def test_set_arr_hist(self): - sim = GrowingSim(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, 1, False) + sim = GrowingSim( + SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, 1, False, PIN_LOC_RULES, CIRC_MOD + ) input = Input( "tests/unit/test_input_files/init_vals.json", "tests/unit/test_input_files/vertex.json", @@ -219,7 +228,9 @@ def test_set_arr_hist(self): self.assertEqual(expected[cell][val], found[cell][val]) def test_get_vertex_assignment(self): - sim = GrowingSim(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, 1, False) + sim = GrowingSim( + SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, 1, False, PIN_LOC_RULES, CIRC_MOD + ) input = Input( "tests/unit/test_input_files/init_vals.json", "tests/unit/test_input_files/vertex.json", @@ -231,7 +242,9 @@ def test_get_vertex_assignment(self): self.assertEqual(expected[cell], found[cell]) def test_get_neighbors_assignment(self): - sim = GrowingSim(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, 1, False) + sim = GrowingSim( + SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, 1, False, PIN_LOC_RULES, CIRC_MOD + ) input = Input( "tests/unit/test_input_files/init_vals.json", "tests/unit/test_input_files/vertex.json", @@ -244,7 +257,9 @@ def test_get_neighbors_assignment(self): self.assertEqual(expected[cell][i], found[cell][i]) def test_group_vertices(self): - sim = GrowingSim(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, 1, False) + sim = GrowingSim( + SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, 1, False, PIN_LOC_RULES, CIRC_MOD + ) input = Input( "tests/unit/test_input_files/init_vals.json", "tests/unit/test_input_files/vertex.json", @@ -263,7 +278,9 @@ def test_group_vertices(self): self.assertEqual(expected_vertex_cell1[i], found["c1"][i].get_xy()) def test_create_cells(self): - sim = GrowingSim(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, 1, False) + sim = GrowingSim( + SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, 1, False, PIN_LOC_RULES, CIRC_MOD + ) input = Input( "tests/unit/test_input_files/init_vals.json", "tests/unit/test_input_files/vertex.json", @@ -308,7 +325,9 @@ def test_create_cells(self): self.assertTrue(isinstance(found_cell1.get_circ_mod(), CirculateModuleUniversalSynDeg)) def test_get_neighbors(self): - sim = GrowingSim(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, 1, False) + sim = GrowingSim( + SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, 1, False, PIN_LOC_RULES, CIRC_MOD + ) input = Input( "tests/unit/test_input_files/init_vals.json", "tests/unit/test_input_files/vertex.json", @@ -340,7 +359,9 @@ def test_get_neighbors(self): ) def test_update_neighbors(self): - sim = GrowingSim(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, 1, False) + sim = GrowingSim( + SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, 1, False, PIN_LOC_RULES, CIRC_MOD + ) input = Input( "tests/unit/test_input_files/init_vals.json", "tests/unit/test_input_files/vertex.json", @@ -371,8 +392,12 @@ def test_update_neighbors(self): self.assertEqual(expected.get_c_id(), found.get_c_id()) def test_input(self): - sim = GrowingSim(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, 1, False) - sim2 = GrowingSim(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, 1, False) + sim = GrowingSim( + SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, 1, False, PIN_LOC_RULES, CIRC_MOD + ) + sim2 = GrowingSim( + SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, 1, False, PIN_LOC_RULES, CIRC_MOD + ) input = Input( "tests/unit/test_input_files/init_vals.json", "tests/unit/test_input_files/vertex.json", @@ -413,6 +438,8 @@ def test_replace_default_to_gparam(self): SCREEN_TITLE, 1, False, + PIN_LOC_RULES, + CIRC_MOD, cell_val_file="tests/unit/test_input_files/init_vals.json", v_file="tests/unit/test_input_files/vertex.json", ) @@ -426,7 +453,9 @@ def test_replace_default_to_gparam(self): self.assertEqual(len(row_df["arr_hist"]), value) def test_make_arr_hist_to_list(self): - sim = GrowingSim(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, 1, False) + sim = GrowingSim( + SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, 1, False, PIN_LOC_RULES, CIRC_MOD + ) input = Input( "tests/unit/test_input_files/init_vals.csv", "tests/unit/test_input_files/vertex.csv", @@ -439,7 +468,9 @@ def test_make_arr_hist_to_list(self): self.assertEqual(type(val), type(0.1)) def test_make_vertices_to_list(self): - sim = GrowingSim(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, 1, False) + sim = GrowingSim( + SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, 1, False, PIN_LOC_RULES, CIRC_MOD + ) input = Input( "tests/unit/test_input_files/init_vals.csv", "tests/unit/test_input_files/vertex.csv", @@ -453,7 +484,9 @@ def test_make_vertices_to_list(self): self.assertEqual(type(val), type(1)) def test_make_neighbors_to_list(self): - sim = GrowingSim(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, 1, False) + sim = GrowingSim( + SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, 1, False, PIN_LOC_RULES, CIRC_MOD + ) input = Input( "tests/unit/test_input_files/init_vals.csv", "tests/unit/test_input_files/vertex.csv", @@ -466,7 +499,9 @@ def test_make_neighbors_to_list(self): self.assertEqual(type(val), type("")) def test_make_param_to_int(self): - sim = GrowingSim(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, 1, False) + sim = GrowingSim( + SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, 1, False, PIN_LOC_RULES, CIRC_MOD + ) input = Input( "tests/unit/test_input_files/init_vals.csv", "tests/unit/test_input_files/vertex.csv", diff --git a/tests/unit/test_output_unittest.py b/tests/unit/test_output_unittest.py index 171e5b4..2f36a0e 100644 --- a/tests/unit/test_output_unittest.py +++ b/tests/unit/test_output_unittest.py @@ -6,10 +6,13 @@ from src.loc.vertex.vertex import Vertex from src.agent.cell import Cell from src.sim.simulation.sim import GrowingSim +from src.arora_enums import PinLocalizationRulesetEnum, CircModEnum SCREEN_WIDTH = 800 SCREEN_HEIGHT = 600 SCREEN_TITLE = "TEST" +PIN_LOC_RULES = PinLocalizationRulesetEnum.SIMPLE_INHERITANCE +CIRC_MOD = CircModEnum.UNIVERSAL_SYN_DEG init_vals = { "auxin": 2, @@ -106,7 +109,9 @@ class TestOutput(unittest.TestCase): output_json = "output.json" def setUp(self): - self.sim = GrowingSim(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, 1, 40, False) + self.sim = GrowingSim( + SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, 1, False, PIN_LOC_RULES, CIRC_MOD + ) self.cell0 = Cell( self.sim, [Vertex(10.0, 10.0), Vertex(10.0, 30.0), Vertex(30.0, 30.0), Vertex(30.0, 10.0)], diff --git a/tests/unit/test_quad_perimeter_unittest.py b/tests/unit/test_quad_perimeter_unittest.py index 61ce903..e52db83 100644 --- a/tests/unit/test_quad_perimeter_unittest.py +++ b/tests/unit/test_quad_perimeter_unittest.py @@ -15,10 +15,13 @@ from src.loc.vertex.vertex import Vertex from src.agent.cell import Cell from src.sim.simulation.sim import GrowingSim +from src.arora_enums import PinLocalizationRulesetEnum, CircModEnum SCREEN_WIDTH = 800 SCREEN_HEIGHT = 600 SCREEN_TITLE = "Starting Template" +PIN_LOC_RULES = PinLocalizationRulesetEnum.SIMPLE_INHERITANCE +CIRC_MOD = CircModEnum.UNIVERSAL_SYN_DEG init_vals = { "auxin": 2, @@ -120,7 +123,9 @@ def test_QuadPerimeter_get_perimeter_len(self): def test_get_len_perimeter_in_common_right_neighbor(self): timestep = 1 - simulation = GrowingSim(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, timestep, False) + simulation = GrowingSim( + SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, timestep, False, PIN_LOC_RULES, CIRC_MOD + ) v1 = Vertex(10, 10) v2 = Vertex(10, 30) v3 = Vertex(30, 30) @@ -136,7 +141,9 @@ def test_get_len_perimeter_in_common_right_neighbor(self): def test_get_len_perimeter_in_common_left_neighbor(self): timestep = 1 - simulation = GrowingSim(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, timestep, False) + simulation = GrowingSim( + SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, timestep, False, PIN_LOC_RULES, CIRC_MOD + ) v1 = Vertex(10, 10) v2 = Vertex(10, 30) v3 = Vertex(30, 30) @@ -152,7 +159,9 @@ def test_get_len_perimeter_in_common_left_neighbor(self): def test_get_len_perimeter_in_common_top_neighbor(self): timestep = 1 - simulation = GrowingSim(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, timestep, False) + simulation = GrowingSim( + SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, timestep, False, PIN_LOC_RULES, CIRC_MOD + ) v1 = Vertex(10, 10) v2 = Vertex(10, 30) v3 = Vertex(30, 30) @@ -168,7 +177,9 @@ def test_get_len_perimeter_in_common_top_neighbor(self): def test_get_len_perimeter_in_common_top_neighbor(self): timestep = 1 - simulation = GrowingSim(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, timestep, False) + simulation = GrowingSim( + SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, timestep, False, PIN_LOC_RULES, CIRC_MOD + ) v1 = Vertex(10, 10) v2 = Vertex(10, 30) v3 = Vertex(30, 30) diff --git a/tests/unit/test_vertex_mover_unittest.py b/tests/unit/test_vertex_mover_unittest.py index f546e26..d509220 100644 --- a/tests/unit/test_vertex_mover_unittest.py +++ b/tests/unit/test_vertex_mover_unittest.py @@ -9,10 +9,13 @@ from src.loc.quad_perimeter.quad_perimeter import QuadPerimeter from src.agent.cell import Cell from src.sim.simulation.sim import GrowingSim +from src.arora_enums import PinLocalizationRulesetEnum, CircModEnum SCREEN_WIDTH = 800 SCREEN_HEIGHT = 600 SCREEN_TITLE = "Starting Template" +PIN_LOC_RULES = PinLocalizationRulesetEnum.SIMPLE_INHERITANCE +CIRC_MOD = CircModEnum.UNIVERSAL_SYN_DEG class TestVertexMover(unittest.TestCase): @@ -42,7 +45,9 @@ class TestVertexMover(unittest.TestCase): def test_add_cell_delta_val(self): timestep = 1 - simulation = GrowingSim(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, timestep, False) + simulation = GrowingSim( + SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, timestep, False, PIN_LOC_RULES, CIRC_MOD + ) v1 = Vertex(10, 10) v2 = Vertex(10, 30) v3 = Vertex(30, 30) @@ -54,7 +59,9 @@ def test_add_cell_delta_val(self): def test_add_cell_b_vertices_to_vertex_deltas(self): timestep = 1 - simulation = GrowingSim(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, timestep, False) + simulation = GrowingSim( + SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, timestep, False, PIN_LOC_RULES, CIRC_MOD + ) v1 = Vertex(10, 10) v2 = Vertex(10, 30) v3 = Vertex(30, 30) @@ -67,7 +74,9 @@ def test_add_cell_b_vertices_to_vertex_deltas(self): def test_propogate_deltas(self): timestep = 1 - simulation = GrowingSim(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, timestep, False) + simulation = GrowingSim( + SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, timestep, False, PIN_LOC_RULES, CIRC_MOD + ) v1 = Vertex(10, 10) v2 = Vertex(10, 30) v3 = Vertex(30, 30) @@ -104,7 +113,9 @@ def test_propogate_deltas(self): def test_execute_vertex_movement(self): timestep = 1 - simulation = GrowingSim(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, timestep, False) + simulation = GrowingSim( + SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, timestep, False, PIN_LOC_RULES, CIRC_MOD + ) v1 = Vertex(10, 10) v2 = Vertex(10, 30) v3 = Vertex(30, 30) @@ -140,7 +151,9 @@ def test_execute_vertex_movement(self): def test_get_top_row(self): timestep = 1 - simulation = GrowingSim(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, timestep, vis=False) + simulation = GrowingSim( + SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, timestep, False, PIN_LOC_RULES, CIRC_MOD + ) v1 = Vertex(10, 10) v2 = Vertex(10, 30) v3 = Vertex(30, 30) @@ -184,7 +197,9 @@ def test_get_top_row(self): def test_update(self): timestep = 1 - simulation = GrowingSim(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, timestep, vis=False) + simulation = GrowingSim( + SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, timestep, False, PIN_LOC_RULES, CIRC_MOD + ) v1 = Vertex(10, 10) v2 = Vertex(10, 30) v3 = Vertex(30, 30) @@ -228,7 +243,9 @@ def test_update(self): def test_sort_top_row(self): timestep = 1 - simulation = GrowingSim(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, timestep, False) + simulation = GrowingSim( + SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, timestep, False, PIN_LOC_RULES, CIRC_MOD + ) v1 = Vertex(10, 10) v2 = Vertex(10, 30) v3 = Vertex(30, 30) @@ -267,7 +284,9 @@ def test_sort_top_row(self): def test_update_onecol(self): timestep = 1 - simulation = GrowingSim(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, timestep, False) + simulation = GrowingSim( + SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, timestep, False, PIN_LOC_RULES, CIRC_MOD + ) v50 = Vertex(50, 10) simulation.vertex_list.append(v50) v1 = Vertex(10, 10) @@ -303,7 +322,9 @@ def test_update_onecol(self): def test_update_threecol(self): timestep = 1 - simulation = GrowingSim(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, timestep, vis=False) + simulation = GrowingSim( + SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, timestep, False, PIN_LOC_RULES, CIRC_MOD + ) v1 = Vertex(10, 10) v2 = Vertex(10, 30) v3 = Vertex(30, 30) @@ -370,7 +391,9 @@ def test_update_threecol(self): def test_check_if_divide(self): timestep = 1 - simulation = GrowingSim(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, timestep, False) + simulation = GrowingSim( + SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE, timestep, False, PIN_LOC_RULES, CIRC_MOD + ) v1 = Vertex(10, 10) v2 = Vertex(10, 30) v3 = Vertex(30, 30) @@ -379,5 +402,6 @@ def test_check_if_divide(self): simulation.setup() v2.set_y(50) v3.set_y(50) + cell1.set_dev_zone("meristematic") simulation.get_vertex_mover().check_if_divide([cell1]) self.assertEqual([cell1], simulation.get_divider().get_cells_to_divide())