From 9f0c0af90b593832f1df0daf438776bfee14d54e Mon Sep 17 00:00:00 2001 From: jannetty Date: Mon, 17 Nov 2025 15:21:03 -0800 Subject: [PATCH 1/8] adding framewirk for imposing PIN distributions. Not actually implementing imposed PIN distributions yet --- main.py | 118 +++++++++++++--- src/agent/cell.py | 129 ++++++++++++++---- src/agent/circ_module.py | 62 ++++++--- src/agent/circ_module_indep_syn_deg.py | 5 +- src/agent/circ_module_universal_syndeg.py | 68 ++++++--- src/agent/default_geo_neighbor_helpers.py | 35 +++-- src/arora_enums.py | 12 ++ .../default_perimeter_geo_neighor_helper.py | 49 +++++-- src/loc/quad_perimeter/quad_perimeter.py | 12 +- src/sim/divider/divider.py | 28 +++- src/sim/input/input.py | 38 ++++-- src/sim/mover/vertex_mover.py | 4 +- src/sim/output/output.py | 12 +- src/sim/simulation/sim.py | 22 ++- 14 files changed, 464 insertions(+), 130 deletions(-) create mode 100644 src/arora_enums.py diff --git a/main.py b/main.py index 9d56ec0..a016a57 100644 --- a/main.py +++ b/main.py @@ -9,13 +9,31 @@ import numpy as np import pandas as pd from src.sim.simulation import sim +from src.arora_enums import CircMod +from src.arora_enums import PinLocalizationRuleset 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 @@ -27,14 +45,25 @@ 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_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,24 +73,40 @@ 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: CircMod): + if circ_mod == CircMod.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 CircMod.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 == CircMod.AUX_SYN_DEG_ONLY: return { "cell_val_file": "src/sim/input/aux_syndegonly_init_vals.json", "v_file": "src/sim/input/default_vs.json", @@ -70,18 +115,50 @@ def get_simulation_config(circ_mod: str): 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 CircMod.UNIVERSAL_SYN_DEG + elif circ_mod_str == "indep_syndeg" or circ_mod_str == "2": + return CircMod.INDEP_SYN_DEG + elif circ_mod_str == "aux_syndegonly" or circ_mod_str == "3": + return CircMod.AUX_SYN_DEG_ONLY + 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 PinLocalizationRuleset.SIMPLE_INHERITANCE + elif pin_loc_rules_str == "imposed" or pin_loc_rules_str == "2": + return PinLocalizationRuleset.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"], + 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 + circ_mod = get_circ_mod_enum(args.circ_mod) timestep = 1 vis = True start_time = time.time() @@ -89,11 +166,12 @@ def get_simulation_config(circ_mod: str): sim.main( timestep, vis, + pin_loc_rules=get_pin_loc_rules_enum(args.pin_loc_rules), 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/src/agent/cell.py b/src/agent/cell.py index 2320484..d1c4a11 100644 --- a/src/agent/cell.py +++ b/src/agent/cell.py @@ -1,6 +1,7 @@ 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 PinLocalizationRuleset 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 @@ -107,6 +108,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 +117,7 @@ class Cell(Sprite): dev_zone: str cell_type: str growing: bool + pin_loc_ruleset: PinLocalizationRuleset def __init__( self, @@ -147,6 +151,7 @@ def __init__( self.quad_perimeter = QuadPerimeter(corners) # Type hint circ_mod to accept any class that implements the CirculateModule protocol self.circ_mod: CirculateModule + # TODO: change this to rely on enums instead of string matching circ_mod_name = init_vals.get("circ_mod") if circ_mod_name == "universal_syndeg": self.circ_mod = CirculateModuleUniversalSynDeg(self, init_vals) @@ -155,9 +160,12 @@ def __init__( 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.") + print( + f"Warning: Unknown circ_mod '{circ_mod_name}', defaulting to universal_syndeg." + ) self.circ_mod = CirculateModuleUniversalSynDeg(self, init_vals) + self.pin_loc_ruleset = self.get_sim().get_pin_loc_rules() self.pin_weights: Dict[str, float] = self.calculate_pin_weights() if self.sim.geometry != "default": self.dev_zone = "" @@ -321,7 +329,9 @@ def add_neighbor(self, neighbor: "Cell") -> None: elif neighbor_location == "cell no longer root cap cell neighbor": pass elif neighbor_location is None: - print(f"cell {self.c_id} is not neighbors with cell {neighbor.get_c_id()}") + print( + f"cell {self.c_id} is not neighbors with cell {neighbor.get_c_id()}" + ) raise ValueError("Non-neighbor added as neighbor") else: raise ValueError("Non-neighbor added as neighbor") @@ -350,19 +360,35 @@ def find_new_neighbor_relative_location(self, neighbor: "Cell") -> str: self_vs = self.get_quad_perimeter().get_vs() neighbor_vs = neighbor.get_quad_perimeter().get_vs() neighbor_dir = "" - if len(set(self_vs).intersection(set(neighbor_vs))) == 1 and self.sim.geometry == "default": - neighbor_dir = NeighborHelpers.get_neighbor_dir_neighbor_shares_one_v_default_geo( - self, neighbor + if ( + len(set(self_vs).intersection(set(neighbor_vs))) == 1 + and self.sim.geometry == "default" + ): + neighbor_dir = ( + NeighborHelpers.get_neighbor_dir_neighbor_shares_one_v_default_geo( + self, neighbor + ) ) - if len(set(self_vs).intersection(set(neighbor_vs))) == 0 and self.sim.geometry == "default": - neighbor_dir = NeighborHelpers.get_neighbor_dir_neighbor_shares_no_vs_default_geo( - self, neighbor + if ( + len(set(self_vs).intersection(set(neighbor_vs))) == 0 + and self.sim.geometry == "default" + ): + neighbor_dir = ( + NeighborHelpers.get_neighbor_dir_neighbor_shares_no_vs_default_geo( + self, neighbor + ) ) if len(set(self_vs).intersection(set(neighbor_vs))) == 2 and neighbor_dir == "": 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 @@ -775,7 +801,7 @@ def calculate_delta(self) -> float: def calculate_pin_weights(self) -> dict: """ - Calculates the pin weights of each membrane of the cell. + Calculates the pin weights of each membrane of the cell. Depends on what PinLocalizationRuleset is being used for the simulation. Returns ------- @@ -784,7 +810,17 @@ def calculate_pin_weights(self) -> dict: Keys are "a", "b", "l", and "m". """ - return self.circ_mod.get_pin_weights() + if self.pin_loc_ruleset == PinLocalizationRuleset.SIMPLE_INHERITANCE: + return self.circ_mod.get_pin_weights() + elif self.pin_loc_ruleset == PinLocalizationRuleset.IMPOSED: + return self.get_imposed_pin_distribution() + else: + raise NotImplementedError("Please choose either simple_inheritance or imposed as your pin_loc_rules") + + def get_imposed_pin_distribution(self) -> dict: + dev_zone = self.get_dev_zone() + # TODO: IMPLEMENT!!! + return {} def get_pin_weights(self) -> dict: """ @@ -804,6 +840,7 @@ def update(self) -> None: """ if self.growing: self.grow() + self.dev_zone = self.calculate_dev_zone() self.pin_weights = self.calculate_pin_weights() self.circ_mod.update() @@ -824,33 +861,51 @@ def get_neighbor_di_neighbor_shares_two_vs_std(self, neighbor: "Cell") -> str: """ # standard case, check which vertices neighbor shares with self # if neighbor shares top left and bottom left, neighbor is to the left - if (self.quad_perimeter.get_top_left() in neighbor.get_quad_perimeter().get_vs()) and ( - self.quad_perimeter.get_bottom_left() in neighbor.get_quad_perimeter().get_vs() + if ( + self.quad_perimeter.get_top_left() in neighbor.get_quad_perimeter().get_vs() + ) and ( + self.quad_perimeter.get_bottom_left() + in neighbor.get_quad_perimeter().get_vs() ): if ( - self.quad_perimeter.get_left_lateral_or_medial(self.sim.get_root_midpointx()) + self.quad_perimeter.get_left_lateral_or_medial( + self.sim.get_root_midpointx() + ) == "lateral" ): neighbor_direction = "l" else: neighbor_direction = "m" # if neighbor shares top right and bottom right, neighbor is to the right - elif (self.quad_perimeter.get_top_right() in neighbor.get_quad_perimeter().get_vs()) and ( - self.quad_perimeter.get_bottom_right() in neighbor.get_quad_perimeter().get_vs() + elif ( + self.quad_perimeter.get_top_right() + in neighbor.get_quad_perimeter().get_vs() + ) and ( + self.quad_perimeter.get_bottom_right() + in neighbor.get_quad_perimeter().get_vs() ): if ( - self.quad_perimeter.get_right_lateral_or_medial(self.sim.get_root_midpointx()) + self.quad_perimeter.get_right_lateral_or_medial( + self.sim.get_root_midpointx() + ) == "lateral" ): neighbor_direction = "l" else: neighbor_direction = "m" - elif (self.quad_perimeter.get_top_left() in neighbor.get_quad_perimeter().get_vs()) and ( - self.quad_perimeter.get_top_right() in neighbor.get_quad_perimeter().get_vs() + elif ( + self.quad_perimeter.get_top_left() in neighbor.get_quad_perimeter().get_vs() + ) and ( + self.quad_perimeter.get_top_right() + in neighbor.get_quad_perimeter().get_vs() ): neighbor_direction = "a" - elif (self.quad_perimeter.get_bottom_left() in neighbor.get_quad_perimeter().get_vs()) and ( - self.quad_perimeter.get_bottom_right() in neighbor.get_quad_perimeter().get_vs() + elif ( + self.quad_perimeter.get_bottom_left() + in neighbor.get_quad_perimeter().get_vs() + ) and ( + self.quad_perimeter.get_bottom_right() + in neighbor.get_quad_perimeter().get_vs() ): neighbor_direction = "b" return neighbor_direction @@ -875,12 +930,16 @@ def get_neighbor_dir_neighbor_shares_one_v_std(self, neighbor: "Cell") -> str: == neighbor.get_quad_perimeter().get_top_right() ): if ( - self.get_quad_perimeter().get_left_lateral_or_medial(self.sim.get_root_midpointx()) + self.get_quad_perimeter().get_left_lateral_or_medial( + self.sim.get_root_midpointx() + ) == "lateral" ): neighbor_direction = "l" elif ( - self.get_quad_perimeter().get_left_lateral_or_medial(self.sim.get_root_midpointx()) + self.get_quad_perimeter().get_left_lateral_or_medial( + self.sim.get_root_midpointx() + ) == "medial" ): neighbor_direction = "m" @@ -890,12 +949,16 @@ def get_neighbor_dir_neighbor_shares_one_v_std(self, neighbor: "Cell") -> str: == neighbor.get_quad_perimeter().get_top_left() ): if ( - self.get_quad_perimeter().get_right_lateral_or_medial(self.sim.get_root_midpointx()) + self.get_quad_perimeter().get_right_lateral_or_medial( + self.sim.get_root_midpointx() + ) == "lateral" ): neighbor_direction = "l" elif ( - self.get_quad_perimeter().get_right_lateral_or_medial(self.sim.get_root_midpointx()) + self.get_quad_perimeter().get_right_lateral_or_medial( + self.sim.get_root_midpointx() + ) == "medial" ): neighbor_direction = "m" @@ -905,12 +968,16 @@ def get_neighbor_dir_neighbor_shares_one_v_std(self, neighbor: "Cell") -> str: == neighbor.get_quad_perimeter().get_bottom_right() ): if ( - self.get_quad_perimeter().get_left_lateral_or_medial(self.sim.get_root_midpointx()) + self.get_quad_perimeter().get_left_lateral_or_medial( + self.sim.get_root_midpointx() + ) == "lateral" ): neighbor_direction = "l" elif ( - self.get_quad_perimeter().get_left_lateral_or_medial(self.sim.get_root_midpointx()) + self.get_quad_perimeter().get_left_lateral_or_medial( + self.sim.get_root_midpointx() + ) == "medial" ): neighbor_direction = "m" @@ -920,12 +987,16 @@ def get_neighbor_dir_neighbor_shares_one_v_std(self, neighbor: "Cell") -> str: == neighbor.get_quad_perimeter().get_bottom_left() ): if ( - self.get_quad_perimeter().get_right_lateral_or_medial(self.sim.get_root_midpointx()) + self.get_quad_perimeter().get_right_lateral_or_medial( + self.sim.get_root_midpointx() + ) == "lateral" ): neighbor_direction = "l" elif ( - self.get_quad_perimeter().get_right_lateral_or_medial(self.sim.get_root_midpointx()) + self.get_quad_perimeter().get_right_lateral_or_medial( + self.sim.get_root_midpointx() + ) == "medial" ): neighbor_direction = "m" diff --git a/src/agent/circ_module.py b/src/agent/circ_module.py index 527dcd9..849335c 100644 --- a/src/agent/circ_module.py +++ b/src/agent/circ_module.py @@ -157,14 +157,24 @@ def f(self, y: list[float], t: float) -> list[float]: # pin f3 = self.calculate_pin(auxini, arri) # neighbor pin - f4 = self.calculate_membrane_pin(pini, pinai, "a", cast(float, self.pin_weights.get("a"))) - f5 = self.calculate_membrane_pin(pini, pinbi, "b", cast(float, self.pin_weights.get("b"))) - f6 = self.calculate_membrane_pin(pini, pinli, "l", cast(float, self.pin_weights.get("l"))) - f7 = self.calculate_membrane_pin(pini, pinmi, "m", cast(float, self.pin_weights.get("m"))) + f4 = self.calculate_membrane_pin( + pini, pinai, "a", cast(float, self.pin_weights.get("a")) + ) + f5 = self.calculate_membrane_pin( + pini, pinbi, "b", cast(float, self.pin_weights.get("b")) + ) + f6 = self.calculate_membrane_pin( + pini, pinli, "l", cast(float, self.pin_weights.get("l")) + ) + f7 = self.calculate_membrane_pin( + pini, pinmi, "m", cast(float, self.pin_weights.get("m")) + ) 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, time_step: float = 0.001, duration: float = 1.0 + ) -> np.ndarray: """ Solve the model's differential equations over a given time span with a specified time step. @@ -299,9 +309,13 @@ def get_aux_exchange_across_membrane( neighbor_dict = {} for neighbor in neighbors: memfrac = self.calculate_neighbor_memfrac(neighbor) - neighbor_memfrac = neighbor.get_circ_mod().calculate_neighbor_memfrac(self.cell) + 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 + auxin_influx = ( + (neighbor_aux * (neighbor_memfrac)) * (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 @@ -324,7 +338,9 @@ def get_aux_exchange_across_membrane( 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: + def calculate_delta_auxin( + self, syn_deg_auxin: float, neighbors_auxin: list + ) -> float: """ Calculate the total amount of change in auxin concentration for the current cell, considering both synthesized/degraded auxin and auxin exchanged with @@ -352,7 +368,9 @@ def calculate_delta_auxin(self, syn_deg_auxin: float, neighbors_auxin: list) -> or auxin == float("-inf") or total_auxin == float("-inf") ): - print(f"cell {self.cell.get_c_id()} auxin {auxin}, total auxin {total_auxin}") + print( + f"cell {self.cell.get_c_id()} auxin {auxin}, total auxin {total_auxin}" + ) else: total_auxin += auxin return total_auxin @@ -407,7 +425,9 @@ def update_neighbor_auxin(self, neighbors_auxin: list[dict]) -> None: """ for each_dirct in neighbors_auxin: for neighbor in each_dirct: - self.cell.get_sim().get_circulator().add_delta(neighbor, -each_dirct[neighbor]) + self.cell.get_sim().get_circulator().add_delta( + neighbor, -each_dirct[neighbor] + ) def get_neighbors(self) -> tuple: """ @@ -450,10 +470,18 @@ def update_auxin(self, soln: np.ndarray) -> None: neighborsa, neighborsb, neighborsl, neighborsm = self.get_neighbors() # 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, 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 + ) neighbors_auxin_exchange = [ auxina_exchange, auxinb_exchange, @@ -469,7 +497,9 @@ def update_auxin(self, soln: np.ndarray) -> None: ) # Update current cell auxin - curr_cell.get_sim().get_circulator().add_delta(curr_cell, round_to_sf(delta_auxin, 5)) + curr_cell.get_sim().get_circulator().add_delta( + curr_cell, round_to_sf(delta_auxin, 5) + ) # Update auxin levels in neighbor cells self.update_neighbor_auxin(neighbors_auxin_exchange) @@ -628,7 +658,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_indep_syn_deg.py b/src/agent/circ_module_indep_syn_deg.py index 325aefa..c5de4f2 100644 --- a/src/agent/circ_module_indep_syn_deg.py +++ b/src/agent/circ_module_indep_syn_deg.py @@ -153,7 +153,10 @@ def calculate_auxlax(self, auxini: float, ali: float) -> float: float The calculated AUX/LAX expression after synthesis and degradation. """ - al = self.ks_auxlax * (auxini / (auxini + self.k_auxin_auxlax)) - self.kd_auxlax * ali + al = ( + self.ks_auxlax * (auxini / (auxini + self.k_auxin_auxlax)) + - self.kd_auxlax * ali + ) return al def calculate_pin(self, auxini: float, arri: float) -> float: diff --git a/src/agent/circ_module_universal_syndeg.py b/src/agent/circ_module_universal_syndeg.py index 24de926..2db8979 100644 --- a/src/agent/circ_module_universal_syndeg.py +++ b/src/agent/circ_module_universal_syndeg.py @@ -230,14 +230,24 @@ def f(self, y: list[float], t: float) -> list[float]: # pin f3 = self.calculate_pin(auxini, arri) # neighbor pin - f4 = self.calculate_membrane_pin(pini, pinai, "a", cast(float, self.pin_weights.get("a"))) - f5 = self.calculate_membrane_pin(pini, pinbi, "b", cast(float, self.pin_weights.get("b"))) - f6 = self.calculate_membrane_pin(pini, pinli, "l", cast(float, self.pin_weights.get("l"))) - f7 = self.calculate_membrane_pin(pini, pinmi, "m", cast(float, self.pin_weights.get("m"))) + f4 = self.calculate_membrane_pin( + pini, pinai, "a", cast(float, self.pin_weights.get("a")) + ) + f5 = self.calculate_membrane_pin( + pini, pinbi, "b", cast(float, self.pin_weights.get("b")) + ) + f6 = self.calculate_membrane_pin( + pini, pinli, "l", cast(float, self.pin_weights.get("l")) + ) + f7 = self.calculate_membrane_pin( + pini, pinmi, "m", cast(float, self.pin_weights.get("m")) + ) 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, time_step: float = 0.001, duration: float = 1.0 + ) -> np.ndarray: """ Solve the model's differential equations over a given time span. @@ -276,7 +286,9 @@ def update(self) -> None: # Retrieve current PIN weights 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" + assert ( + round_to_sf(sum(self.pin_weights.values()), 2) == 1.0 + ), "PIN weights sum to 1.0" # Solve the differential equations for the current state soln = self.solve_equations() @@ -319,7 +331,9 @@ def calculate_arr(self, arri: float) -> float: The calculated ARR concentration after accounting for synthesis and degradation dynamics. """ - arr = (self.ks * (self.k_arr_arr / (self.arr_hist[0] + self.k_arr_arr))) - (self.kd * arri) + arr = (self.ks * (self.k_arr_arr / (self.arr_hist[0] + self.k_arr_arr))) - ( + self.kd * arri + ) return arr def calculate_auxlax(self, auxini: float, ali: float) -> float: @@ -459,9 +473,13 @@ def get_aux_exchange_across_membrane( neighbor_dict = {} for neighbor in neighbors: memfrac = self.calculate_neighbor_memfrac(neighbor) - neighbor_memfrac = neighbor.get_circ_mod().calculate_neighbor_memfrac(self.cell) + 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 + auxin_influx = ( + (neighbor_aux * (neighbor_memfrac)) * (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 @@ -480,7 +498,9 @@ def get_aux_exchange_across_membrane( 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: + def calculate_delta_auxin( + self, syn_deg_auxin: float, neighbors_auxin: list + ) -> float: """ Calculate the total amount of change in auxin concentration for the current cell, considering both synthesized/degraded auxin and auxin exchanged with @@ -508,7 +528,9 @@ def calculate_delta_auxin(self, syn_deg_auxin: float, neighbors_auxin: list) -> or auxin == float("-inf") or total_auxin == float("-inf") ): - print(f"cell {self.cell.get_c_id()} auxin {auxin}, total auxin {total_auxin}") + print( + f"cell {self.cell.get_c_id()} auxin {auxin}, total auxin {total_auxin}" + ) else: total_auxin += auxin return total_auxin @@ -563,7 +585,9 @@ def update_neighbor_auxin(self, neighbors_auxin: list[dict]) -> None: """ for each_dirct in neighbors_auxin: for neighbor in each_dirct: - self.cell.get_sim().get_circulator().add_delta(neighbor, -each_dirct[neighbor]) + self.cell.get_sim().get_circulator().add_delta( + neighbor, -each_dirct[neighbor] + ) def get_neighbors(self) -> tuple: """ @@ -606,10 +630,18 @@ def update_auxin(self, soln: np.ndarray) -> None: neighborsa, neighborsb, neighborsl, neighborsm = self.get_neighbors() # 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, 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 + ) neighbors_auxin_exchange = [ auxina_exchange, auxinb_exchange, @@ -625,7 +657,9 @@ def update_auxin(self, soln: np.ndarray) -> None: ) # Update current cell auxin - curr_cell.get_sim().get_circulator().add_delta(curr_cell, round_to_sf(delta_auxin, 5)) + curr_cell.get_sim().get_circulator().add_delta( + curr_cell, round_to_sf(delta_auxin, 5) + ) # Update auxin levels in neighbor cells self.update_neighbor_auxin(neighbors_auxin_exchange) diff --git a/src/agent/default_geo_neighbor_helpers.py b/src/agent/default_geo_neighbor_helpers.py index 892d997..ce89c1d 100644 --- a/src/agent/default_geo_neighbor_helpers.py +++ b/src/agent/default_geo_neighbor_helpers.py @@ -29,7 +29,9 @@ class NeighborHelpers: ] @staticmethod - def get_neighbor_dir_neighbor_shares_one_v_default_geo(cell: "Cell", neighbor: "Cell") -> str: + def get_neighbor_dir_neighbor_shares_one_v_default_geo( + cell: "Cell", neighbor: "Cell" + ) -> str: """ Determine the direction of a neighbor cell sharing one vertex in default geometry. @@ -162,7 +164,9 @@ def get_neighbor_dir_neighbor_shares_one_v_default_geo(cell: "Cell", neighbor: " return neighbor_direct @staticmethod - def check_if_neighbors_with_new_root_cap_cell(cell: "Cell", sim: "GrowingSim") -> None: + def check_if_neighbors_with_new_root_cap_cell( + cell: "Cell", sim: "GrowingSim" + ) -> None: """ Checks if the neighbor is the next root cap cell. @@ -180,7 +184,9 @@ def check_if_neighbors_with_new_root_cap_cell(cell: "Cell", sim: "GrowingSim") - if cell.get_c_id() in NeighborHelpers.ROOTCAP_CELL_IDs ] non_current_neighbor_lrc_cells = [ - lrc_cell for lrc_cell in all_lrc_cells if lrc_cell not in cell.get_l_neighbors() + lrc_cell + for lrc_cell in all_lrc_cells + if lrc_cell not in cell.get_l_neighbors() ] for lrc_cell in non_current_neighbor_lrc_cells: if NeighborHelpers.cell_and_lrc_cell_are_neighbors(cell, lrc_cell): @@ -188,7 +194,9 @@ def check_if_neighbors_with_new_root_cap_cell(cell: "Cell", sim: "GrowingSim") - lrc_cell.add_m_neighbor(cell) @staticmethod - def get_neighbor_dir_neighbor_shares_no_vs_default_geo(cell: "Cell", neighbor: "Cell") -> str: + def get_neighbor_dir_neighbor_shares_no_vs_default_geo( + cell: "Cell", neighbor: "Cell" + ) -> str: """ Determine the direction of a neighbor cell without shared vertices in default geometry. @@ -244,7 +252,9 @@ def get_neighbor_dir_neighbor_shares_no_vs_default_geo(cell: "Cell", neighbor: " neighbor_direct = "l" else: neighbor_direct = "cell no longer root cap cell neighbor" - NeighborHelpers.check_if_neighbors_with_new_root_cap_cell(cell, cell.get_sim()) + NeighborHelpers.check_if_neighbors_with_new_root_cap_cell( + cell, cell.get_sim() + ) return neighbor_direct @staticmethod # This relies on the assumption that only cells that were previously neighbors with root cap cells will ever be neighbors with root cap cells @@ -263,7 +273,9 @@ def fix_lrc_neighbors_after_growth(sim: "GrowingSim") -> None: ) @staticmethod - def check_if_no_longer_neighbors_with_root_cap_cell(cell: "Cell", lrc_neighbor: "Cell") -> None: + def check_if_no_longer_neighbors_with_root_cap_cell( + cell: "Cell", lrc_neighbor: "Cell" + ) -> None: if not NeighborHelpers.cell_and_lrc_cell_are_neighbors(cell, lrc_neighbor): cell.remove_l_neighbor(lrc_neighbor) lrc_neighbor.remove_m_neighbor(cell) @@ -283,11 +295,13 @@ def cell_and_lrc_cell_are_neighbors(cell: "Cell", lrc_cell: "Cell") -> bool: # Cells must share a membrane (x-coordinates of adjacent membrane don't match) if cell_left_l_or_m == "lateral": assert ( - cell.get_quad_perimeter().get_min_x() == lrc_cell.get_quad_perimeter().get_max_x() + cell.get_quad_perimeter().get_min_x() + == lrc_cell.get_quad_perimeter().get_max_x() ) else: assert ( - cell.get_quad_perimeter().get_max_x() == lrc_cell.get_quad_perimeter().get_min_x() + cell.get_quad_perimeter().get_max_x() + == lrc_cell.get_quad_perimeter().get_min_x() ) cell_miny = cell.get_quad_perimeter().get_min_y() @@ -297,7 +311,10 @@ def cell_and_lrc_cell_are_neighbors(cell: "Cell", lrc_cell: "Cell") -> bool: if ( lrc_cell_miny <= cell_miny <= lrc_cell_maxy or lrc_cell_miny <= cell_maxy <= lrc_cell_maxy - or (cell_miny <= lrc_cell_miny <= cell_maxy and cell_miny <= lrc_cell_maxy <= cell_maxy) + or ( + cell_miny <= lrc_cell_miny <= cell_maxy + and cell_miny <= lrc_cell_maxy <= cell_maxy + ) ): return True else: diff --git a/src/arora_enums.py b/src/arora_enums.py new file mode 100644 index 0000000..9d8a16b --- /dev/null +++ b/src/arora_enums.py @@ -0,0 +1,12 @@ +from enum import Enum + + +class CircMod(Enum): + AUX_SYN_DEG_ONLY = 3 + INDEP_SYN_DEG = 2 + UNIVERSAL_SYN_DEG = 1 + + +class PinLocalizationRuleset(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..a28b8f4 100644 --- a/src/loc/quad_perimeter/default_perimeter_geo_neighor_helper.py +++ b/src/loc/quad_perimeter/default_perimeter_geo_neighor_helper.py @@ -50,36 +50,51 @@ def get_default_len_perimeter_in_common(cell: "Cell", neighbor: "Cell") -> float if cell.get_c_id() in rootcap_cell_ids: if ( - neighborqp.get_left_lateral_or_medial(neighbor.get_sim().get_root_midpointx()) + neighborqp.get_left_lateral_or_medial( + neighbor.get_sim().get_root_midpointx() + ) == "lateral" ): neighbor_vs_on_shared_edge = [ 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 ) elif ( - neighborqp.get_right_lateral_or_medial(neighbor.get_sim().get_root_midpointx()) + neighborqp.get_right_lateral_or_medial( + neighbor.get_sim().get_root_midpointx() + ) == "lateral" ): neighbor_vs_on_shared_edge = [ 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 ) elif neighbor.get_c_id() in rootcap_cell_ids: if ( - cellqp.get_left_lateral_or_medial(neighbor.get_sim().get_root_midpointx()) + 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(), @@ -88,10 +103,15 @@ def get_default_len_perimeter_in_common(cell: "Cell", neighbor: "Cell") -> float cell_vs_on_shared_edge, neighbor_vs_on_shared_edge ) elif ( - cellqp.get_right_lateral_or_medial(neighbor.get_sim().get_root_midpointx()) + 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 +267,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. @@ -271,12 +292,14 @@ def get_overlap( len(set(vertex_xs)) == 1 ), "In PerimeterNeighborHelpers.get_overlap, the vertices of the shared membrane do not share an x value." cell_v_ys_on_shared_edge = sorted([v.get_y() for v in cell_vs_on_shared_edge]) - neighbor_v_ys_on_shared_edge = sorted([v.get_y() for v in neighbor_vs_on_shared_edge]) + neighbor_v_ys_on_shared_edge = sorted( + [v.get_y() for v in neighbor_vs_on_shared_edge] + ) if cell_v_ys_on_shared_edge[1] < neighbor_v_ys_on_shared_edge[0]: raise ValueError( "In PerimeterNeighborHelpers.get_overlap, the vertices of the shared membrane do not overlap." ) else: - return min(cell_v_ys_on_shared_edge[1], neighbor_v_ys_on_shared_edge[1]) - max( - cell_v_ys_on_shared_edge[0], neighbor_v_ys_on_shared_edge[0] - ) + return min( + cell_v_ys_on_shared_edge[1], neighbor_v_ys_on_shared_edge[1] + ) - max(cell_v_ys_on_shared_edge[0], neighbor_v_ys_on_shared_edge[0]) diff --git a/src/loc/quad_perimeter/quad_perimeter.py b/src/loc/quad_perimeter/quad_perimeter.py index e08704c..6ef78ad 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 @@ -62,10 +64,14 @@ def get_len_perimeter_in_common(cell: "Cell", neighbor: "Cell") -> float: vs = list(set(cellqp.get_vs()).intersection(set(neighborqp.get_vs()))) length = math.dist(vs[0].get_xy(), vs[1].get_xy()) elif cell.sim.geometry == "default": - length = PerimeterNeighborHelpers.get_default_len_perimeter_in_common(cell, neighbor) + length = PerimeterNeighborHelpers.get_default_len_perimeter_in_common( + cell, neighbor + ) if length == 0: - raise ValueError("Neighbor list is incorrect, neighbor does not share membrane with cell") + raise ValueError( + "Neighbor list is incorrect, neighbor does not share membrane with cell" + ) return length diff --git a/src/sim/divider/divider.py b/src/sim/divider/divider.py index 31702e1..3fa0c11 100644 --- a/src/sim/divider/divider.py +++ b/src/sim/divider/divider.py @@ -74,7 +74,9 @@ def update(self) -> None: if len(self.cells_to_divide) != 0: print("---------- Dividing cells ----------") meristematic_cells_to_divide = [ - cell for cell in self.cells_to_divide if cell.get_dev_zone() == "meristematic" + cell + for cell in self.cells_to_divide + if cell.get_dev_zone() == "meristematic" ] for cell in meristematic_cells_to_divide: new_vs = self.get_new_vs(cell) @@ -139,10 +141,14 @@ def get_new_vs(self, cell: "Cell") -> list["Vertex"]: bottomleft = cell.get_quad_perimeter().get_bottom_left() bottomright = cell.get_quad_perimeter().get_bottom_right() new_left = Vertex(topleft.get_x(), (topleft.get_y() + bottomleft.get_y()) / 2) - new_right = Vertex(topright.get_x(), (topright.get_y() + bottomright.get_y()) / 2) + new_right = Vertex( + topright.get_x(), (topright.get_y() + bottomright.get_y()) / 2 + ) return [new_left, new_right] - def check_neighbors_for_v_existence(self, cell: "Cell", new_vertex: Vertex) -> Vertex: + def check_neighbors_for_v_existence( + self, cell: "Cell", new_vertex: Vertex + ) -> Vertex: """ Checks the neighboring cells for the existence of a given vertex. @@ -187,8 +193,12 @@ def update_neighbor_lists( self.swap_neighbors(new_top_cell, apical_neighbor, cell) for basal_neighbor in cell.get_b_neighbors(): self.swap_neighbors(new_bottom_cell, basal_neighbor, cell) - self.set_one_side_neighbors(new_top_cell, new_bottom_cell, cell.get_l_neighbors(), cell) - self.set_one_side_neighbors(new_top_cell, new_bottom_cell, cell.get_m_neighbors(), cell) + self.set_one_side_neighbors( + new_top_cell, new_bottom_cell, cell.get_l_neighbors(), cell + ) + self.set_one_side_neighbors( + new_top_cell, new_bottom_cell, cell.get_m_neighbors(), cell + ) def set_one_side_neighbors( self, @@ -228,8 +238,12 @@ def set_one_side_neighbors( self.sim.geometry == "default" and neighbor.get_c_id() in NeighborHelpers.ROOTCAP_CELL_IDs ): - NeighborHelpers.check_if_neighbors_with_new_root_cap_cell(new_top_cell, self.sim) - NeighborHelpers.check_if_neighbors_with_new_root_cap_cell(new_bottom_cell, self.sim) + NeighborHelpers.check_if_neighbors_with_new_root_cap_cell( + new_top_cell, self.sim + ) + NeighborHelpers.check_if_neighbors_with_new_root_cap_cell( + new_bottom_cell, self.sim + ) neighbor.remove_neighbor(cell) elif ( diff --git a/src/sim/input/input.py b/src/sim/input/input.py index 065e76e..59a3119 100644 --- a/src/sim/input/input.py +++ b/src/sim/input/input.py @@ -137,7 +137,9 @@ def load_cell_json(self, file_path: str) -> pd.DataFrame: try: entry[key] = eval(entry[key]) except Exception as e: - raise ValueError(f"Error parsing {key} in {file_path}: {entry[key]} — {e}") + raise ValueError( + f"Error parsing {key} in {file_path}: {entry[key]} — {e}" + ) return pd.DataFrame(data) def get_initial_v_miny(self) -> float: @@ -224,7 +226,9 @@ def replace_default_to_gparam(self, gparam_series: pd.Series) -> None: # print(f"current self.init_vals_input.at[index_df, index_s] = {self.init_vals_input.at[index_df, index_s]}, type: {type(self.init_vals_input.at[index_df, index_s])}") self.init_vals_input.at[index_df, index_s] = float(value) if index_s == "tau": - self.init_vals_input.at[index_df, "arr_hist"] = [row["arr"]] * int(value) + self.init_vals_input.at[index_df, "arr_hist"] = [row["arr"]] * int( + value + ) def get_init_vals(self) -> dict: """ @@ -254,12 +258,16 @@ def get_init_vals(self) -> dict: ): try: # Safely evaluate strings that are supposed to be Python literals (lists, dicts) - init_vals_dict[cell_num][val] = eval(init_vals_dict[cell_num][val]) + init_vals_dict[cell_num][val] = eval( + init_vals_dict[cell_num][val] + ) except SyntaxError as e: raise ValueError(f"Error evaluating {val}: {e}") # Specifically handling neighbors to strip spaces from entries if it's a list of strings - if val == "neighbors" and isinstance(init_vals_dict[cell_num][val], list): + if val == "neighbors" and isinstance( + init_vals_dict[cell_num][val], list + ): init_vals_dict[cell_num][val] = [ item.strip() for item in init_vals_dict[cell_num][val] @@ -269,7 +277,9 @@ def get_init_vals(self) -> dict: # Replicate arr_hist based on a specific length if it's a list if "arr_hist" in init_vals_dict[cell_num]: arr_len = len(init_vals_dict[cell_num]["arr_hist"]) - init_vals_dict[cell_num]["arr_hist"] = [init_vals_dict[cell_num]["arr"]] * arr_len + init_vals_dict[cell_num]["arr_hist"] = [ + init_vals_dict[cell_num]["arr"] + ] * arr_len return init_vals_dict def set_arr_hist(self, init_vals_dict: dict) -> None: @@ -416,7 +426,10 @@ def make_arr_hist_to_list(self) -> None: all_cells_arr_hist_lists = [] for arr_hist_list in self.init_vals_input["arr_hist"]: one_cell_arr_hist_list = ( - arr_hist_list.replace(" ", "").replace("[", "").replace("]", "").split(",") + arr_hist_list.replace(" ", "") + .replace("[", "") + .replace("]", "") + .split(",") ) for index, arr_val in enumerate(one_cell_arr_hist_list): one_cell_arr_hist_list[index] = float(arr_val) @@ -429,7 +442,9 @@ def make_cell_vertices_to_list(self) -> None: """ all_cells_v_lists = [] for v_list in self.init_vals_input["vertices"]: - one_cell_v_list = v_list.replace(" ", "").replace("[", "").replace("]", "").split(",") + one_cell_v_list = ( + v_list.replace(" ", "").replace("[", "").replace("]", "").split(",") + ) for index, v in enumerate(one_cell_v_list): one_cell_v_list[index] = int(v) all_cells_v_lists.append(one_cell_v_list) @@ -442,7 +457,10 @@ def make_neighbors_to_list(self) -> None: all_cells_neighbors_lists = [] for neighbor_list in self.init_vals_input["neighbors"]: one_cell_neighbors_list = ( - neighbor_list.replace(" ", "").replace("[", "").replace("]", "").split(",") + neighbor_list.replace(" ", "") + .replace("[", "") + .replace("]", "") + .split(",") ) all_cells_neighbors_lists.append(one_cell_neighbors_list) self.init_vals_input["neighbors"] = pd.Series(all_cells_neighbors_lists) @@ -454,4 +472,6 @@ def make_param_to_int(self) -> None: int_params = ["k1", "k2", "k3", "k4"] for param in int_params: for index in range(len(param)): - self.init_vals_input.loc[index, param] = int(self.init_vals_input[param][index]) + self.init_vals_input.loc[index, param] = int( + self.init_vals_input[param][index] + ) diff --git a/src/sim/mover/vertex_mover.py b/src/sim/mover/vertex_mover.py index 67f7025..6508b7c 100644 --- a/src/sim/mover/vertex_mover.py +++ b/src/sim/mover/vertex_mover.py @@ -232,7 +232,9 @@ def propogate_deltas_to_b_neighbors(self, cell: Cell, delta: float) -> None: neighbor_delta = self.cell_deltas[b_neighbor] else: neighbor_delta = 0 - self.add_cell_b_vertices_to_vertex_deltas(b_neighbor, delta + neighbor_delta) + self.add_cell_b_vertices_to_vertex_deltas( + b_neighbor, delta + neighbor_delta + ) stack.append((b_neighbor, delta + neighbor_delta)) def execute_vertex_movement(self, max_delta: float) -> None: diff --git a/src/sim/output/output.py b/src/sim/output/output.py index 87197e3..6c3972f 100644 --- a/src/sim/output/output.py +++ b/src/sim/output/output.py @@ -61,7 +61,9 @@ def output_cells(self) -> None: """ 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.") + print( + "No Cells added to simulation. Cannot output simulation contents." + ) return sim_and_cell_contents = [ "tick", @@ -75,7 +77,9 @@ 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( + self.sim.get_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) @@ -107,7 +111,9 @@ def output_cells(self) -> None: with open(self.filename_json, "a") as file: json.dump(output, file, indent=4) - def get_circ_contents(self, summary: dict[str, Any], cell: "Cell") -> dict[str, Any]: + 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. diff --git a/src/sim/simulation/sim.py b/src/sim/simulation/sim.py index 978f4b8..b0b3e14 100644 --- a/src/sim/simulation/sim.py +++ b/src/sim/simulation/sim.py @@ -9,6 +9,7 @@ from arcade import set_background_color from arcade import close_window, set_window import time +from src.arora_enums import PinLocalizationRuleset from src.sim.circulator.circulator import Circulator from src.sim.divider.divider import Divider from src.sim.mover.vertex_mover import VertexMover @@ -50,6 +51,8 @@ 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 next_cell_id : int The ID to be assigned to the next new cell. root_tip_y : float @@ -73,6 +76,8 @@ 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 cell_val_file : str, optional The filename containing cell values to initialize the simulation. v_file : str, optional @@ -92,6 +97,7 @@ class GrowingSim(Window): cell_list: SpriteList vertex_list: list vis: bool + pin_loc_rules: PinLocalizationRuleset next_cell_id: int root_tip_y: float = 0 cell_val_file: str @@ -105,6 +111,7 @@ def __init__( title: str, timestep: int, vis: bool, + pin_loc_rules: PinLocalizationRuleset, cell_val_file: str = "", v_file: str = "", gparam_series: pandas.core.series.Series | str = "", @@ -136,6 +143,7 @@ def __init__( self.geometry = geometry self.timestep = timestep self.vis = vis + self.pin_loc_rules = pin_loc_rules self.cmap = plt.get_cmap("coolwarm") self.setup() self.output = Output(self, f"{output_file}.csv", f"{output_file}.json") @@ -180,6 +188,10 @@ def get_timestep(self) -> float: """Returns the timestep of the simulation (in seconds).""" return self.timestep + def get_pin_loc_rules(self) -> PinLocalizationRuleset: + """Returns the PIN localization ruleset""" + return self.pin_loc_rules + def get_tick(self) -> int: """Returns the current tick (or step) of the simulation.""" return self.tick @@ -333,8 +345,12 @@ def on_update(self, delta_time: float) -> None: self.circulator.update() self.divider.update() self.root_tip_y = self.calculate_root_tip_y() - total_aux = sum([cell.get_circ_mod().get_auxin() for cell in self.cell_list]) - total_area = sum([cell.get_quad_perimeter().get_area() for cell in self.cell_list]) + total_aux = sum( + [cell.get_circ_mod().get_auxin() for cell in self.cell_list] + ) + total_area = sum( + [cell.get_quad_perimeter().get_area() for cell in self.cell_list] + ) print(f"Total auxin: {total_aux}") print(f"Total area: {total_area}") print(f"Total auxin/area = {total_aux/total_area}") @@ -361,6 +377,7 @@ def run_sim(self) -> None: def main( timestep: int, vis: bool, + pin_loc_rules: PinLocalizationRuleset, cell_val_file: str = "", v_file: str = "", gparam_series: Series | str = "", @@ -377,6 +394,7 @@ def main( SCREEN_TITLE, timestep, vis, + pin_loc_rules, cell_val_file, v_file, gparam_series, From d85ad969137682b0391570a9085331eded1f7305 Mon Sep 17 00:00:00 2001 From: jannetty Date: Tue, 18 Nov 2025 10:10:58 -0800 Subject: [PATCH 2/8] adding functionality to impose auxlax and pin expression, adding circ mod that includes auxin transport --- main.py | 37 ++- src/agent/cell.py | 262 ++++++++++++++++-- src/agent/circ_module.py | 25 +- .../circ_module_aux_syn_deg_transport.py | 73 +++++ src/arora_enums.py | 9 +- src/sim/mover/vertex_mover.py | 9 +- src/sim/simulation/sim.py | 28 +- 7 files changed, 383 insertions(+), 60 deletions(-) create mode 100644 src/agent/circ_module_aux_syn_deg_transport.py diff --git a/main.py b/main.py index a016a57..8f7ee13 100644 --- a/main.py +++ b/main.py @@ -9,8 +9,8 @@ import numpy as np import pandas as pd from src.sim.simulation import sim -from src.arora_enums import CircMod -from src.arora_enums import PinLocalizationRuleset +from src.arora_enums import CircModEnum +from src.arora_enums import PinLocalizationRulesetEnum import pyglet """ @@ -36,8 +36,8 @@ 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 @@ -93,20 +93,26 @@ def make_indep_param_series(): return pd.Series(param_vals, index=INDEP_PARAM_NAMES) -def get_simulation_config(circ_mod: CircMod): - if circ_mod == CircMod.UNIVERSAL_SYN_DEG: +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 CircMod.INDEP_SYN_DEG: + 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 == CircMod.AUX_SYN_DEG_ONLY: + 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_TRANS: return { "cell_val_file": "src/sim/input/aux_syndegonly_init_vals.json", "v_file": "src/sim/input/default_vs.json", @@ -118,20 +124,22 @@ def get_simulation_config(circ_mod: CircMod): def get_circ_mod_enum(circ_mod_str: str): if circ_mod_str == "universal_syndeg" or circ_mod_str == "1": - return CircMod.UNIVERSAL_SYN_DEG + return CircModEnum.UNIVERSAL_SYN_DEG elif circ_mod_str == "indep_syndeg" or circ_mod_str == "2": - return CircMod.INDEP_SYN_DEG + return CircModEnum.INDEP_SYN_DEG elif circ_mod_str == "aux_syndegonly" or circ_mod_str == "3": - return CircMod.AUX_SYN_DEG_ONLY + return CircModEnum.AUX_SYN_DEG_ONLY + elif circ_mod_str == "aux_syndegtrans" or circ_mod_str == "4": + return CircModEnum.AUX_SYN_DEG_TRANS 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 PinLocalizationRuleset.SIMPLE_INHERITANCE + return PinLocalizationRulesetEnum.SIMPLE_INHERITANCE elif pin_loc_rules_str == "imposed" or pin_loc_rules_str == "2": - return PinLocalizationRuleset.IMPOSED + return PinLocalizationRulesetEnum.IMPOSED if __name__ == "__main__": @@ -140,7 +148,7 @@ def get_pin_loc_rules_enum(pin_loc_rules_str: str): "--circ_mod", type=str, default="universal_syndeg", - choices=["universal_syndeg", "1", "indep_syndeg", "2", "aux_syndegonly", "3"], + choices=["universal_syndeg", "1", "indep_syndeg", "2", "aux_syndegonly", "3", "aux_syndegtrans", "4"], help="Which circulation module to use", ) parser.add_argument( @@ -167,6 +175,7 @@ def get_pin_loc_rules_enum(pin_loc_rules_str: str): 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"], diff --git a/src/agent/cell.py b/src/agent/cell.py index d1c4a11..252b8d8 100644 --- a/src/agent/cell.py +++ b/src/agent/cell.py @@ -1,10 +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 PinLocalizationRuleset +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_transport import CirculateModuleAuxinSynDegTransport 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 @@ -68,6 +69,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): """ @@ -117,7 +126,7 @@ class Cell(Sprite): dev_zone: str cell_type: str growing: bool - pin_loc_ruleset: PinLocalizationRuleset + pin_loc_ruleset: PinLocalizationRulesetEnum def __init__( self, @@ -149,21 +158,21 @@ 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 - # TODO: change this to rely on enums instead of string matching - 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_TRANS: + self.circ_mod = CirculateModuleAuxinSynDegTransport(self, init_vals) + case _: + raise SyntaxError(f"Unknown circ mod '{circ_mod_name}'") self.pin_loc_ruleset = self.get_sim().get_pin_loc_rules() self.pin_weights: Dict[str, float] = self.calculate_pin_weights() @@ -810,17 +819,223 @@ def calculate_pin_weights(self) -> dict: Keys are "a", "b", "l", and "m". """ - if self.pin_loc_ruleset == PinLocalizationRuleset.SIMPLE_INHERITANCE: + if self.pin_loc_ruleset == PinLocalizationRulesetEnum.SIMPLE_INHERITANCE: + return self.circ_mod.get_pin_weights() + elif self.pin_loc_ruleset == PinLocalizationRulesetEnum.IMPOSED: + # when pin is imposed, it is not synthesized or degraded, only maintained at level calculated by self.get_imposed_pin_distribution() + Warning ("Only SIMPLE_INHERITANCE currently supports pin weights, setting all weights to 0 for imposed PIN localization") return self.circ_mod.get_pin_weights() - elif self.pin_loc_ruleset == PinLocalizationRuleset.IMPOSED: - return self.get_imposed_pin_distribution() else: - raise NotImplementedError("Please choose either simple_inheritance or imposed as your pin_loc_rules") + raise NotImplementedError("Only SIMPLE_INHERITANCE currently supports pin weights") def get_imposed_pin_distribution(self) -> dict: + """ + 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. + Keys are "a", "b", "l", and "m". + Returns current pin_weights for roottip cells. + """ dev_zone = self.get_dev_zone() - # TODO: IMPLEMENT!!! - return {} + cell_type = self.get_cell_type() + if dev_zone == "roottip" or cell_type == "roottip": + return self.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: + """ + 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 | None: + """ + 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 | None: + """ + 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 | None: + """ + 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 | None: + """ + 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: """ @@ -840,8 +1055,9 @@ def update(self) -> None: """ if self.growing: self.grow() - self.dev_zone = self.calculate_dev_zone() - self.pin_weights = self.calculate_pin_weights() + self.dev_zone = self.calculate_dev_zone(self.get_distance_from_tip()) + if self.get_sim().get_pin_loc_rules == PinLocalizationRulesetEnum.SIMPLE_INHERITANCE: + 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 849335c..e996a92 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: @@ -220,7 +221,7 @@ def update(self) -> None: 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" + assert round_to_sf(sum(self.pin_weights.values()), 2) == 1.0, "PIN weights sum to 1.0" # Solve the differential equations for the current state soln = self.solve_equations() @@ -317,11 +318,8 @@ def get_aux_exchange_across_membrane( (neighbor_aux * (neighbor_memfrac)) * (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") @@ -331,10 +329,10 @@ def get_aux_exchange_across_membrane( 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}") 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}" - ) + # 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 @@ -388,6 +386,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 @@ -410,6 +409,16 @@ def update_circ_contents(self, soln: np.ndarray) -> None: self.pinm = round_to_sf(soln[1, 7], 5) self.update_arr_hist() + if self.cell.get_sim().get_pin_loc_rules == PinLocalizationRulesetEnum.IMPOSED: + pins = self.cell.get_imposed_pin_distribution() + self.pina = pins.get("a") + self.pinb = pins.get("b") + self.pinl = pins.get("l") + self.pinm = pins.get("m") + self.pin_weights = self.initialize_pin_weights() + 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. diff --git a/src/agent/circ_module_aux_syn_deg_transport.py b/src/agent/circ_module_aux_syn_deg_transport.py new file mode 100644 index 0000000..4ca923c --- /dev/null +++ b/src/agent/circ_module_aux_syn_deg_transport.py @@ -0,0 +1,73 @@ +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 CirculateModuleAuxinSynDegTransport(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") + # 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 index 9d8a16b..0ad1544 100644 --- a/src/arora_enums.py +++ b/src/arora_enums.py @@ -1,12 +1,13 @@ from enum import Enum -class CircMod(Enum): - AUX_SYN_DEG_ONLY = 3 - INDEP_SYN_DEG = 2 +class CircModEnum(Enum): UNIVERSAL_SYN_DEG = 1 + INDEP_SYN_DEG = 2 + AUX_SYN_DEG_ONLY = 3 + AUX_SYN_DEG_TRANS = 4 -class PinLocalizationRuleset(Enum): +class PinLocalizationRulesetEnum(Enum): SIMPLE_INHERITANCE = 1 IMPOSED = 2 diff --git a/src/sim/mover/vertex_mover.py b/src/sim/mover/vertex_mover.py index 6508b7c..138d717 100644 --- a/src/sim/mover/vertex_mover.py +++ b/src/sim/mover/vertex_mover.py @@ -268,7 +268,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/simulation/sim.py b/src/sim/simulation/sim.py index b0b3e14..a547606 100644 --- a/src/sim/simulation/sim.py +++ b/src/sim/simulation/sim.py @@ -9,7 +9,8 @@ from arcade import set_background_color from arcade import close_window, set_window import time -from src.arora_enums import PinLocalizationRuleset +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 @@ -52,7 +53,9 @@ class GrowingSim(Window): 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 + 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 @@ -77,7 +80,9 @@ class GrowingSim(Window): 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 + 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 @@ -97,7 +102,8 @@ class GrowingSim(Window): cell_list: SpriteList vertex_list: list vis: bool - pin_loc_rules: PinLocalizationRuleset + pin_loc_rules: PinLocalizationRulesetEnum + circ_mod: CircModEnum next_cell_id: int root_tip_y: float = 0 cell_val_file: str @@ -111,7 +117,8 @@ def __init__( title: str, timestep: int, vis: bool, - pin_loc_rules: PinLocalizationRuleset, + pin_loc_rules: PinLocalizationRulesetEnum, + circ_mod: CircModEnum, cell_val_file: str = "", v_file: str = "", gparam_series: pandas.core.series.Series | str = "", @@ -144,6 +151,7 @@ def __init__( 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") @@ -188,9 +196,13 @@ def get_timestep(self) -> float: """Returns the timestep of the simulation (in seconds).""" return self.timestep - def get_pin_loc_rules(self) -> PinLocalizationRuleset: + 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.""" @@ -377,7 +389,8 @@ def run_sim(self) -> None: def main( timestep: int, vis: bool, - pin_loc_rules: PinLocalizationRuleset, + pin_loc_rules: PinLocalizationRulesetEnum, + circ_mod: CircModEnum, cell_val_file: str = "", v_file: str = "", gparam_series: Series | str = "", @@ -395,6 +408,7 @@ def main( timestep, vis, pin_loc_rules, + circ_mod, cell_val_file, v_file, gparam_series, From 235e522b199d5c506f7a3adbb5397f1b365b0aca Mon Sep 17 00:00:00 2001 From: jannetty Date: Tue, 18 Nov 2025 10:45:08 -0800 Subject: [PATCH 3/8] changed signature of simulation instantiations in tests --- src/agent/cell.py | 198 +++++++----------- src/agent/circ_module.py | 61 ++---- .../circ_module_aux_syn_deg_transport.py | 4 +- src/agent/circ_module_indep_syn_deg.py | 5 +- src/agent/circ_module_universal_syndeg.py | 68 ++---- src/agent/default_geo_neighbor_helpers.py | 35 +--- .../default_perimeter_geo_neighor_helper.py | 26 +-- src/loc/quad_perimeter/quad_perimeter.py | 8 +- src/sim/divider/divider.py | 28 +-- src/sim/input/input.py | 38 +--- src/sim/mover/vertex_mover.py | 4 +- src/sim/output/output.py | 12 +- src/sim/simulation/sim.py | 10 +- .../test_initialization_symmetry_unittest.py | 8 + tests/unit/test_cell_unittest.py | 15 +- tests/unit/test_circ_module_cont_unittest.py | 91 ++++++-- tests/unit/test_circulator_unittest.py | 11 +- tests/unit/test_divider_unittest.py | 27 ++- tests/unit/test_input_unittest.py | 65 ++++-- tests/unit/test_output_unittest.py | 7 +- tests/unit/test_quad_perimeter_unittest.py | 19 +- tests/unit/test_vertex_mover_unittest.py | 44 +++- 22 files changed, 380 insertions(+), 404 deletions(-) diff --git a/src/agent/cell.py b/src/agent/cell.py index 252b8d8..65608a7 100644 --- a/src/agent/cell.py +++ b/src/agent/cell.py @@ -69,13 +69,13 @@ # 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 +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 +TRANSITION_MAX_ROW_INDEX = 33 # highest observed transition band index class Cell(Sprite): @@ -338,9 +338,7 @@ def add_neighbor(self, neighbor: "Cell") -> None: elif neighbor_location == "cell no longer root cap cell neighbor": pass elif neighbor_location is None: - print( - f"cell {self.c_id} is not neighbors with cell {neighbor.get_c_id()}" - ) + print(f"cell {self.c_id} is not neighbors with cell {neighbor.get_c_id()}") raise ValueError("Non-neighbor added as neighbor") else: raise ValueError("Non-neighbor added as neighbor") @@ -369,23 +367,13 @@ def find_new_neighbor_relative_location(self, neighbor: "Cell") -> str: self_vs = self.get_quad_perimeter().get_vs() neighbor_vs = neighbor.get_quad_perimeter().get_vs() neighbor_dir = "" - if ( - len(set(self_vs).intersection(set(neighbor_vs))) == 1 - and self.sim.geometry == "default" - ): - neighbor_dir = ( - NeighborHelpers.get_neighbor_dir_neighbor_shares_one_v_default_geo( - self, neighbor - ) + if len(set(self_vs).intersection(set(neighbor_vs))) == 1 and self.sim.geometry == "default": + neighbor_dir = NeighborHelpers.get_neighbor_dir_neighbor_shares_one_v_default_geo( + self, neighbor ) - if ( - len(set(self_vs).intersection(set(neighbor_vs))) == 0 - and self.sim.geometry == "default" - ): - neighbor_dir = ( - NeighborHelpers.get_neighbor_dir_neighbor_shares_no_vs_default_geo( - self, neighbor - ) + if len(set(self_vs).intersection(set(neighbor_vs))) == 0 and self.sim.geometry == "default": + neighbor_dir = NeighborHelpers.get_neighbor_dir_neighbor_shares_no_vs_default_geo( + self, neighbor ) if len(set(self_vs).intersection(set(neighbor_vs))) == 2 and neighbor_dir == "": neighbor_dir = self.get_neighbor_di_neighbor_shares_two_vs_std(neighbor) @@ -823,7 +811,9 @@ def calculate_pin_weights(self) -> dict: return self.circ_mod.get_pin_weights() elif self.pin_loc_ruleset == PinLocalizationRulesetEnum.IMPOSED: # when pin is imposed, it is not synthesized or degraded, only maintained at level calculated by self.get_imposed_pin_distribution() - Warning ("Only SIMPLE_INHERITANCE currently supports pin weights, setting all weights to 0 for imposed PIN localization") + Warning( + "Only SIMPLE_INHERITANCE currently supports pin weights, setting all weights to 0 for imposed PIN localization" + ) return self.circ_mod.get_pin_weights() else: raise NotImplementedError("Only SIMPLE_INHERITANCE currently supports pin weights") @@ -872,7 +862,6 @@ def _meristem_row_index(self, dist_to_root_tip: float) -> int: row = MERISTEM_MAX_ROW_INDEX return row - def _transition_row_index(self, dist_to_root_tip: float) -> int: """ Map distance from tip to a discrete 'row' index in the transition zone, @@ -893,55 +882,57 @@ def _pin_meristematic(self, cell_type: str, dist_to_root_tip: float) -> dict | N if cell_type == "vasc": if row == 0: - return {"a": 0.0, "b": 1.0, "l": 0.4, "m": 0.4} + 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} + 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} + 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} + 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.0, "m": 0.1} elif cell_type == "endo": if row == 0: - return {"a": 0.0, "b": 1.0, "l": 0.35, "m": 0.5} + 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} + 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} + 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} + 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} + 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.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} + 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} + 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} + 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} + 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} + 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.") + 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 | None: """ @@ -953,56 +944,58 @@ def _pin_transition(self, cell_type: str, dist_to_root_tip: float) -> dict | Non # 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} + 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} + 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} + 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} + 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} + 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} + 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} + 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} + 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} + 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} + 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.0, "m": 0.1} # upper transition cortex matches elongation cortex - return {"a": 1.0, "b": 0.0, "l": 0.0, "m": 0.1} + 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} + 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} + 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} + 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.") + raise SyntaxError( + "Transition cell not within transition PIN bands. Check for growth errros." + ) def _pin_elongation(self, cell_type: str) -> dict | None: """ @@ -1011,16 +1004,15 @@ def _pin_elongation(self, cell_type: str) -> dict | None: 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} + 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} + 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} + 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} + 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 | None: """ Differentiation zone PIN patterns, constant within each cell type. @@ -1028,13 +1020,13 @@ def _pin_differentiation(self, cell_type: str) -> dict | None: 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} + 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} + 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} + 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} + 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: @@ -1077,51 +1069,33 @@ def get_neighbor_di_neighbor_shares_two_vs_std(self, neighbor: "Cell") -> str: """ # standard case, check which vertices neighbor shares with self # if neighbor shares top left and bottom left, neighbor is to the left - if ( - self.quad_perimeter.get_top_left() in neighbor.get_quad_perimeter().get_vs() - ) and ( - self.quad_perimeter.get_bottom_left() - in neighbor.get_quad_perimeter().get_vs() + if (self.quad_perimeter.get_top_left() in neighbor.get_quad_perimeter().get_vs()) and ( + self.quad_perimeter.get_bottom_left() in neighbor.get_quad_perimeter().get_vs() ): if ( - self.quad_perimeter.get_left_lateral_or_medial( - self.sim.get_root_midpointx() - ) + self.quad_perimeter.get_left_lateral_or_medial(self.sim.get_root_midpointx()) == "lateral" ): neighbor_direction = "l" else: neighbor_direction = "m" # if neighbor shares top right and bottom right, neighbor is to the right - elif ( - self.quad_perimeter.get_top_right() - in neighbor.get_quad_perimeter().get_vs() - ) and ( - self.quad_perimeter.get_bottom_right() - in neighbor.get_quad_perimeter().get_vs() + elif (self.quad_perimeter.get_top_right() in neighbor.get_quad_perimeter().get_vs()) and ( + self.quad_perimeter.get_bottom_right() in neighbor.get_quad_perimeter().get_vs() ): if ( - self.quad_perimeter.get_right_lateral_or_medial( - self.sim.get_root_midpointx() - ) + self.quad_perimeter.get_right_lateral_or_medial(self.sim.get_root_midpointx()) == "lateral" ): neighbor_direction = "l" else: neighbor_direction = "m" - elif ( - self.quad_perimeter.get_top_left() in neighbor.get_quad_perimeter().get_vs() - ) and ( - self.quad_perimeter.get_top_right() - in neighbor.get_quad_perimeter().get_vs() + elif (self.quad_perimeter.get_top_left() in neighbor.get_quad_perimeter().get_vs()) and ( + self.quad_perimeter.get_top_right() in neighbor.get_quad_perimeter().get_vs() ): neighbor_direction = "a" - elif ( - self.quad_perimeter.get_bottom_left() - in neighbor.get_quad_perimeter().get_vs() - ) and ( - self.quad_perimeter.get_bottom_right() - in neighbor.get_quad_perimeter().get_vs() + elif (self.quad_perimeter.get_bottom_left() in neighbor.get_quad_perimeter().get_vs()) and ( + self.quad_perimeter.get_bottom_right() in neighbor.get_quad_perimeter().get_vs() ): neighbor_direction = "b" return neighbor_direction @@ -1146,16 +1120,12 @@ def get_neighbor_dir_neighbor_shares_one_v_std(self, neighbor: "Cell") -> str: == neighbor.get_quad_perimeter().get_top_right() ): if ( - self.get_quad_perimeter().get_left_lateral_or_medial( - self.sim.get_root_midpointx() - ) + self.get_quad_perimeter().get_left_lateral_or_medial(self.sim.get_root_midpointx()) == "lateral" ): neighbor_direction = "l" elif ( - self.get_quad_perimeter().get_left_lateral_or_medial( - self.sim.get_root_midpointx() - ) + self.get_quad_perimeter().get_left_lateral_or_medial(self.sim.get_root_midpointx()) == "medial" ): neighbor_direction = "m" @@ -1165,16 +1135,12 @@ def get_neighbor_dir_neighbor_shares_one_v_std(self, neighbor: "Cell") -> str: == neighbor.get_quad_perimeter().get_top_left() ): if ( - self.get_quad_perimeter().get_right_lateral_or_medial( - self.sim.get_root_midpointx() - ) + self.get_quad_perimeter().get_right_lateral_or_medial(self.sim.get_root_midpointx()) == "lateral" ): neighbor_direction = "l" elif ( - self.get_quad_perimeter().get_right_lateral_or_medial( - self.sim.get_root_midpointx() - ) + self.get_quad_perimeter().get_right_lateral_or_medial(self.sim.get_root_midpointx()) == "medial" ): neighbor_direction = "m" @@ -1184,16 +1150,12 @@ def get_neighbor_dir_neighbor_shares_one_v_std(self, neighbor: "Cell") -> str: == neighbor.get_quad_perimeter().get_bottom_right() ): if ( - self.get_quad_perimeter().get_left_lateral_or_medial( - self.sim.get_root_midpointx() - ) + self.get_quad_perimeter().get_left_lateral_or_medial(self.sim.get_root_midpointx()) == "lateral" ): neighbor_direction = "l" elif ( - self.get_quad_perimeter().get_left_lateral_or_medial( - self.sim.get_root_midpointx() - ) + self.get_quad_perimeter().get_left_lateral_or_medial(self.sim.get_root_midpointx()) == "medial" ): neighbor_direction = "m" @@ -1203,16 +1165,12 @@ def get_neighbor_dir_neighbor_shares_one_v_std(self, neighbor: "Cell") -> str: == neighbor.get_quad_perimeter().get_bottom_left() ): if ( - self.get_quad_perimeter().get_right_lateral_or_medial( - self.sim.get_root_midpointx() - ) + self.get_quad_perimeter().get_right_lateral_or_medial(self.sim.get_root_midpointx()) == "lateral" ): neighbor_direction = "l" elif ( - self.get_quad_perimeter().get_right_lateral_or_medial( - self.sim.get_root_midpointx() - ) + self.get_quad_perimeter().get_right_lateral_or_medial(self.sim.get_root_midpointx()) == "medial" ): neighbor_direction = "m" diff --git a/src/agent/circ_module.py b/src/agent/circ_module.py index e996a92..b450d7c 100644 --- a/src/agent/circ_module.py +++ b/src/agent/circ_module.py @@ -158,24 +158,14 @@ def f(self, y: list[float], t: float) -> list[float]: # pin f3 = self.calculate_pin(auxini, arri) # neighbor pin - f4 = self.calculate_membrane_pin( - pini, pinai, "a", cast(float, self.pin_weights.get("a")) - ) - f5 = self.calculate_membrane_pin( - pini, pinbi, "b", cast(float, self.pin_weights.get("b")) - ) - f6 = self.calculate_membrane_pin( - pini, pinli, "l", cast(float, self.pin_weights.get("l")) - ) - f7 = self.calculate_membrane_pin( - pini, pinmi, "m", cast(float, self.pin_weights.get("m")) - ) + f4 = self.calculate_membrane_pin(pini, pinai, "a", cast(float, self.pin_weights.get("a"))) + f5 = self.calculate_membrane_pin(pini, pinbi, "b", cast(float, self.pin_weights.get("b"))) + f6 = self.calculate_membrane_pin(pini, pinli, "l", cast(float, self.pin_weights.get("l"))) + f7 = self.calculate_membrane_pin(pini, pinmi, "m", cast(float, self.pin_weights.get("m"))) 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, time_step: float = 0.001, duration: float = 1.0) -> np.ndarray: """ Solve the model's differential equations over a given time span with a specified time step. @@ -310,13 +300,9 @@ def get_aux_exchange_across_membrane( neighbor_dict = {} for neighbor in neighbors: memfrac = self.calculate_neighbor_memfrac(neighbor) - neighbor_memfrac = neighbor.get_circ_mod().calculate_neighbor_memfrac( - self.cell - ) + 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 - ) + auxin_influx = (neighbor_aux * (neighbor_memfrac)) * (al * memfrac) * self.k_al pin_activity = pindi * self.k_pin accessible_auxin = self.auxin * memfrac auxin_efflux = accessible_auxin * pin_activity @@ -336,9 +322,7 @@ def get_aux_exchange_across_membrane( 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: + def calculate_delta_auxin(self, syn_deg_auxin: float, neighbors_auxin: list) -> float: """ Calculate the total amount of change in auxin concentration for the current cell, considering both synthesized/degraded auxin and auxin exchanged with @@ -366,9 +350,7 @@ def calculate_delta_auxin( or auxin == float("-inf") or total_auxin == float("-inf") ): - print( - f"cell {self.cell.get_c_id()} auxin {auxin}, total auxin {total_auxin}" - ) + print(f"cell {self.cell.get_c_id()} auxin {auxin}, total auxin {total_auxin}") else: total_auxin += auxin return total_auxin @@ -418,7 +400,6 @@ def update_circ_contents(self, soln: np.ndarray) -> None: self.pin_weights = self.initialize_pin_weights() 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. @@ -434,9 +415,7 @@ def update_neighbor_auxin(self, neighbors_auxin: list[dict]) -> None: """ for each_dirct in neighbors_auxin: for neighbor in each_dirct: - self.cell.get_sim().get_circulator().add_delta( - neighbor, -each_dirct[neighbor] - ) + self.cell.get_sim().get_circulator().add_delta(neighbor, -each_dirct[neighbor]) def get_neighbors(self) -> tuple: """ @@ -479,18 +458,10 @@ def update_auxin(self, soln: np.ndarray) -> None: neighborsa, neighborsb, neighborsl, neighborsm = self.get_neighbors() # 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, 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) neighbors_auxin_exchange = [ auxina_exchange, auxinb_exchange, @@ -506,9 +477,7 @@ def update_auxin(self, soln: np.ndarray) -> None: ) # Update current cell auxin - curr_cell.get_sim().get_circulator().add_delta( - curr_cell, round_to_sf(delta_auxin, 5) - ) + curr_cell.get_sim().get_circulator().add_delta(curr_cell, round_to_sf(delta_auxin, 5)) # Update auxin levels in neighbor cells self.update_neighbor_auxin(neighbors_auxin_exchange) diff --git a/src/agent/circ_module_aux_syn_deg_transport.py b/src/agent/circ_module_aux_syn_deg_transport.py index 4ca923c..15eb55a 100644 --- a/src/agent/circ_module_aux_syn_deg_transport.py +++ b/src/agent/circ_module_aux_syn_deg_transport.py @@ -13,7 +13,9 @@ class CirculateModuleAuxinSynDegTransport(CirculateModule): 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") + raise NotImplementedError( + "CirculateModuleAuxinSynDegTransport only supports imposed transporter distribution" + ) def get_float(key: str) -> float: value = init_vals.get(key) diff --git a/src/agent/circ_module_indep_syn_deg.py b/src/agent/circ_module_indep_syn_deg.py index c5de4f2..325aefa 100644 --- a/src/agent/circ_module_indep_syn_deg.py +++ b/src/agent/circ_module_indep_syn_deg.py @@ -153,10 +153,7 @@ def calculate_auxlax(self, auxini: float, ali: float) -> float: float The calculated AUX/LAX expression after synthesis and degradation. """ - al = ( - self.ks_auxlax * (auxini / (auxini + self.k_auxin_auxlax)) - - self.kd_auxlax * ali - ) + al = self.ks_auxlax * (auxini / (auxini + self.k_auxin_auxlax)) - self.kd_auxlax * ali return al def calculate_pin(self, auxini: float, arri: float) -> float: diff --git a/src/agent/circ_module_universal_syndeg.py b/src/agent/circ_module_universal_syndeg.py index 2db8979..24de926 100644 --- a/src/agent/circ_module_universal_syndeg.py +++ b/src/agent/circ_module_universal_syndeg.py @@ -230,24 +230,14 @@ def f(self, y: list[float], t: float) -> list[float]: # pin f3 = self.calculate_pin(auxini, arri) # neighbor pin - f4 = self.calculate_membrane_pin( - pini, pinai, "a", cast(float, self.pin_weights.get("a")) - ) - f5 = self.calculate_membrane_pin( - pini, pinbi, "b", cast(float, self.pin_weights.get("b")) - ) - f6 = self.calculate_membrane_pin( - pini, pinli, "l", cast(float, self.pin_weights.get("l")) - ) - f7 = self.calculate_membrane_pin( - pini, pinmi, "m", cast(float, self.pin_weights.get("m")) - ) + f4 = self.calculate_membrane_pin(pini, pinai, "a", cast(float, self.pin_weights.get("a"))) + f5 = self.calculate_membrane_pin(pini, pinbi, "b", cast(float, self.pin_weights.get("b"))) + f6 = self.calculate_membrane_pin(pini, pinli, "l", cast(float, self.pin_weights.get("l"))) + f7 = self.calculate_membrane_pin(pini, pinmi, "m", cast(float, self.pin_weights.get("m"))) 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, time_step: float = 0.001, duration: float = 1.0) -> np.ndarray: """ Solve the model's differential equations over a given time span. @@ -286,9 +276,7 @@ def update(self) -> None: # Retrieve current PIN weights 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" + assert round_to_sf(sum(self.pin_weights.values()), 2) == 1.0, "PIN weights sum to 1.0" # Solve the differential equations for the current state soln = self.solve_equations() @@ -331,9 +319,7 @@ def calculate_arr(self, arri: float) -> float: The calculated ARR concentration after accounting for synthesis and degradation dynamics. """ - arr = (self.ks * (self.k_arr_arr / (self.arr_hist[0] + self.k_arr_arr))) - ( - self.kd * arri - ) + arr = (self.ks * (self.k_arr_arr / (self.arr_hist[0] + self.k_arr_arr))) - (self.kd * arri) return arr def calculate_auxlax(self, auxini: float, ali: float) -> float: @@ -473,13 +459,9 @@ def get_aux_exchange_across_membrane( neighbor_dict = {} for neighbor in neighbors: memfrac = self.calculate_neighbor_memfrac(neighbor) - neighbor_memfrac = neighbor.get_circ_mod().calculate_neighbor_memfrac( - self.cell - ) + 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 - ) + auxin_influx = (neighbor_aux * (neighbor_memfrac)) * (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 @@ -498,9 +480,7 @@ def get_aux_exchange_across_membrane( 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: + def calculate_delta_auxin(self, syn_deg_auxin: float, neighbors_auxin: list) -> float: """ Calculate the total amount of change in auxin concentration for the current cell, considering both synthesized/degraded auxin and auxin exchanged with @@ -528,9 +508,7 @@ def calculate_delta_auxin( or auxin == float("-inf") or total_auxin == float("-inf") ): - print( - f"cell {self.cell.get_c_id()} auxin {auxin}, total auxin {total_auxin}" - ) + print(f"cell {self.cell.get_c_id()} auxin {auxin}, total auxin {total_auxin}") else: total_auxin += auxin return total_auxin @@ -585,9 +563,7 @@ def update_neighbor_auxin(self, neighbors_auxin: list[dict]) -> None: """ for each_dirct in neighbors_auxin: for neighbor in each_dirct: - self.cell.get_sim().get_circulator().add_delta( - neighbor, -each_dirct[neighbor] - ) + self.cell.get_sim().get_circulator().add_delta(neighbor, -each_dirct[neighbor]) def get_neighbors(self) -> tuple: """ @@ -630,18 +606,10 @@ def update_auxin(self, soln: np.ndarray) -> None: neighborsa, neighborsb, neighborsl, neighborsm = self.get_neighbors() # 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, 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) neighbors_auxin_exchange = [ auxina_exchange, auxinb_exchange, @@ -657,9 +625,7 @@ def update_auxin(self, soln: np.ndarray) -> None: ) # Update current cell auxin - curr_cell.get_sim().get_circulator().add_delta( - curr_cell, round_to_sf(delta_auxin, 5) - ) + curr_cell.get_sim().get_circulator().add_delta(curr_cell, round_to_sf(delta_auxin, 5)) # Update auxin levels in neighbor cells self.update_neighbor_auxin(neighbors_auxin_exchange) diff --git a/src/agent/default_geo_neighbor_helpers.py b/src/agent/default_geo_neighbor_helpers.py index ce89c1d..892d997 100644 --- a/src/agent/default_geo_neighbor_helpers.py +++ b/src/agent/default_geo_neighbor_helpers.py @@ -29,9 +29,7 @@ class NeighborHelpers: ] @staticmethod - def get_neighbor_dir_neighbor_shares_one_v_default_geo( - cell: "Cell", neighbor: "Cell" - ) -> str: + def get_neighbor_dir_neighbor_shares_one_v_default_geo(cell: "Cell", neighbor: "Cell") -> str: """ Determine the direction of a neighbor cell sharing one vertex in default geometry. @@ -164,9 +162,7 @@ def get_neighbor_dir_neighbor_shares_one_v_default_geo( return neighbor_direct @staticmethod - def check_if_neighbors_with_new_root_cap_cell( - cell: "Cell", sim: "GrowingSim" - ) -> None: + def check_if_neighbors_with_new_root_cap_cell(cell: "Cell", sim: "GrowingSim") -> None: """ Checks if the neighbor is the next root cap cell. @@ -184,9 +180,7 @@ def check_if_neighbors_with_new_root_cap_cell( if cell.get_c_id() in NeighborHelpers.ROOTCAP_CELL_IDs ] non_current_neighbor_lrc_cells = [ - lrc_cell - for lrc_cell in all_lrc_cells - if lrc_cell not in cell.get_l_neighbors() + lrc_cell for lrc_cell in all_lrc_cells if lrc_cell not in cell.get_l_neighbors() ] for lrc_cell in non_current_neighbor_lrc_cells: if NeighborHelpers.cell_and_lrc_cell_are_neighbors(cell, lrc_cell): @@ -194,9 +188,7 @@ def check_if_neighbors_with_new_root_cap_cell( lrc_cell.add_m_neighbor(cell) @staticmethod - def get_neighbor_dir_neighbor_shares_no_vs_default_geo( - cell: "Cell", neighbor: "Cell" - ) -> str: + def get_neighbor_dir_neighbor_shares_no_vs_default_geo(cell: "Cell", neighbor: "Cell") -> str: """ Determine the direction of a neighbor cell without shared vertices in default geometry. @@ -252,9 +244,7 @@ def get_neighbor_dir_neighbor_shares_no_vs_default_geo( neighbor_direct = "l" else: neighbor_direct = "cell no longer root cap cell neighbor" - NeighborHelpers.check_if_neighbors_with_new_root_cap_cell( - cell, cell.get_sim() - ) + NeighborHelpers.check_if_neighbors_with_new_root_cap_cell(cell, cell.get_sim()) return neighbor_direct @staticmethod # This relies on the assumption that only cells that were previously neighbors with root cap cells will ever be neighbors with root cap cells @@ -273,9 +263,7 @@ def fix_lrc_neighbors_after_growth(sim: "GrowingSim") -> None: ) @staticmethod - def check_if_no_longer_neighbors_with_root_cap_cell( - cell: "Cell", lrc_neighbor: "Cell" - ) -> None: + def check_if_no_longer_neighbors_with_root_cap_cell(cell: "Cell", lrc_neighbor: "Cell") -> None: if not NeighborHelpers.cell_and_lrc_cell_are_neighbors(cell, lrc_neighbor): cell.remove_l_neighbor(lrc_neighbor) lrc_neighbor.remove_m_neighbor(cell) @@ -295,13 +283,11 @@ def cell_and_lrc_cell_are_neighbors(cell: "Cell", lrc_cell: "Cell") -> bool: # Cells must share a membrane (x-coordinates of adjacent membrane don't match) if cell_left_l_or_m == "lateral": assert ( - cell.get_quad_perimeter().get_min_x() - == lrc_cell.get_quad_perimeter().get_max_x() + cell.get_quad_perimeter().get_min_x() == lrc_cell.get_quad_perimeter().get_max_x() ) else: assert ( - cell.get_quad_perimeter().get_max_x() - == lrc_cell.get_quad_perimeter().get_min_x() + cell.get_quad_perimeter().get_max_x() == lrc_cell.get_quad_perimeter().get_min_x() ) cell_miny = cell.get_quad_perimeter().get_min_y() @@ -311,10 +297,7 @@ def cell_and_lrc_cell_are_neighbors(cell: "Cell", lrc_cell: "Cell") -> bool: if ( lrc_cell_miny <= cell_miny <= lrc_cell_maxy or lrc_cell_miny <= cell_maxy <= lrc_cell_maxy - or ( - cell_miny <= lrc_cell_miny <= cell_maxy - and cell_miny <= lrc_cell_maxy <= cell_maxy - ) + or (cell_miny <= lrc_cell_miny <= cell_maxy and cell_miny <= lrc_cell_maxy <= cell_maxy) ): return True else: 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 a28b8f4..307045f 100644 --- a/src/loc/quad_perimeter/default_perimeter_geo_neighor_helper.py +++ b/src/loc/quad_perimeter/default_perimeter_geo_neighor_helper.py @@ -50,9 +50,7 @@ def get_default_len_perimeter_in_common(cell: "Cell", neighbor: "Cell") -> float if cell.get_c_id() in rootcap_cell_ids: if ( - neighborqp.get_left_lateral_or_medial( - neighbor.get_sim().get_root_midpointx() - ) + neighborqp.get_left_lateral_or_medial(neighbor.get_sim().get_root_midpointx()) == "lateral" ): neighbor_vs_on_shared_edge = [ @@ -67,9 +65,7 @@ def get_default_len_perimeter_in_common(cell: "Cell", neighbor: "Cell") -> float neighbor_vs_on_shared_edge, cell_vs_on_shared_edge ) elif ( - neighborqp.get_right_lateral_or_medial( - neighbor.get_sim().get_root_midpointx() - ) + neighborqp.get_right_lateral_or_medial(neighbor.get_sim().get_root_midpointx()) == "lateral" ): neighbor_vs_on_shared_edge = [ @@ -86,9 +82,7 @@ def get_default_len_perimeter_in_common(cell: "Cell", neighbor: "Cell") -> float elif neighbor.get_c_id() in rootcap_cell_ids: if ( - cellqp.get_left_lateral_or_medial( - neighbor.get_sim().get_root_midpointx() - ) + cellqp.get_left_lateral_or_medial(neighbor.get_sim().get_root_midpointx()) == "lateral" ): cell_vs_on_shared_edge = [ @@ -103,9 +97,7 @@ def get_default_len_perimeter_in_common(cell: "Cell", neighbor: "Cell") -> float cell_vs_on_shared_edge, neighbor_vs_on_shared_edge ) elif ( - cellqp.get_right_lateral_or_medial( - neighbor.get_sim().get_root_midpointx() - ) + cellqp.get_right_lateral_or_medial(neighbor.get_sim().get_root_midpointx()) == "lateral" ): cell_vs_on_shared_edge = [ @@ -292,14 +284,12 @@ def get_overlap( len(set(vertex_xs)) == 1 ), "In PerimeterNeighborHelpers.get_overlap, the vertices of the shared membrane do not share an x value." cell_v_ys_on_shared_edge = sorted([v.get_y() for v in cell_vs_on_shared_edge]) - neighbor_v_ys_on_shared_edge = sorted( - [v.get_y() for v in neighbor_vs_on_shared_edge] - ) + neighbor_v_ys_on_shared_edge = sorted([v.get_y() for v in neighbor_vs_on_shared_edge]) if cell_v_ys_on_shared_edge[1] < neighbor_v_ys_on_shared_edge[0]: raise ValueError( "In PerimeterNeighborHelpers.get_overlap, the vertices of the shared membrane do not overlap." ) else: - return min( - cell_v_ys_on_shared_edge[1], neighbor_v_ys_on_shared_edge[1] - ) - max(cell_v_ys_on_shared_edge[0], neighbor_v_ys_on_shared_edge[0]) + return min(cell_v_ys_on_shared_edge[1], neighbor_v_ys_on_shared_edge[1]) - max( + cell_v_ys_on_shared_edge[0], neighbor_v_ys_on_shared_edge[0] + ) diff --git a/src/loc/quad_perimeter/quad_perimeter.py b/src/loc/quad_perimeter/quad_perimeter.py index 6ef78ad..bf86d39 100644 --- a/src/loc/quad_perimeter/quad_perimeter.py +++ b/src/loc/quad_perimeter/quad_perimeter.py @@ -64,14 +64,10 @@ def get_len_perimeter_in_common(cell: "Cell", neighbor: "Cell") -> float: vs = list(set(cellqp.get_vs()).intersection(set(neighborqp.get_vs()))) length = math.dist(vs[0].get_xy(), vs[1].get_xy()) elif cell.sim.geometry == "default": - length = PerimeterNeighborHelpers.get_default_len_perimeter_in_common( - cell, neighbor - ) + length = PerimeterNeighborHelpers.get_default_len_perimeter_in_common(cell, neighbor) if length == 0: - raise ValueError( - "Neighbor list is incorrect, neighbor does not share membrane with cell" - ) + raise ValueError("Neighbor list is incorrect, neighbor does not share membrane with cell") return length diff --git a/src/sim/divider/divider.py b/src/sim/divider/divider.py index 3fa0c11..31702e1 100644 --- a/src/sim/divider/divider.py +++ b/src/sim/divider/divider.py @@ -74,9 +74,7 @@ def update(self) -> None: if len(self.cells_to_divide) != 0: print("---------- Dividing cells ----------") meristematic_cells_to_divide = [ - cell - for cell in self.cells_to_divide - if cell.get_dev_zone() == "meristematic" + cell for cell in self.cells_to_divide if cell.get_dev_zone() == "meristematic" ] for cell in meristematic_cells_to_divide: new_vs = self.get_new_vs(cell) @@ -141,14 +139,10 @@ def get_new_vs(self, cell: "Cell") -> list["Vertex"]: bottomleft = cell.get_quad_perimeter().get_bottom_left() bottomright = cell.get_quad_perimeter().get_bottom_right() new_left = Vertex(topleft.get_x(), (topleft.get_y() + bottomleft.get_y()) / 2) - new_right = Vertex( - topright.get_x(), (topright.get_y() + bottomright.get_y()) / 2 - ) + new_right = Vertex(topright.get_x(), (topright.get_y() + bottomright.get_y()) / 2) return [new_left, new_right] - def check_neighbors_for_v_existence( - self, cell: "Cell", new_vertex: Vertex - ) -> Vertex: + def check_neighbors_for_v_existence(self, cell: "Cell", new_vertex: Vertex) -> Vertex: """ Checks the neighboring cells for the existence of a given vertex. @@ -193,12 +187,8 @@ def update_neighbor_lists( self.swap_neighbors(new_top_cell, apical_neighbor, cell) for basal_neighbor in cell.get_b_neighbors(): self.swap_neighbors(new_bottom_cell, basal_neighbor, cell) - self.set_one_side_neighbors( - new_top_cell, new_bottom_cell, cell.get_l_neighbors(), cell - ) - self.set_one_side_neighbors( - new_top_cell, new_bottom_cell, cell.get_m_neighbors(), cell - ) + self.set_one_side_neighbors(new_top_cell, new_bottom_cell, cell.get_l_neighbors(), cell) + self.set_one_side_neighbors(new_top_cell, new_bottom_cell, cell.get_m_neighbors(), cell) def set_one_side_neighbors( self, @@ -238,12 +228,8 @@ def set_one_side_neighbors( self.sim.geometry == "default" and neighbor.get_c_id() in NeighborHelpers.ROOTCAP_CELL_IDs ): - NeighborHelpers.check_if_neighbors_with_new_root_cap_cell( - new_top_cell, self.sim - ) - NeighborHelpers.check_if_neighbors_with_new_root_cap_cell( - new_bottom_cell, self.sim - ) + NeighborHelpers.check_if_neighbors_with_new_root_cap_cell(new_top_cell, self.sim) + NeighborHelpers.check_if_neighbors_with_new_root_cap_cell(new_bottom_cell, self.sim) neighbor.remove_neighbor(cell) elif ( diff --git a/src/sim/input/input.py b/src/sim/input/input.py index 59a3119..065e76e 100644 --- a/src/sim/input/input.py +++ b/src/sim/input/input.py @@ -137,9 +137,7 @@ def load_cell_json(self, file_path: str) -> pd.DataFrame: try: entry[key] = eval(entry[key]) except Exception as e: - raise ValueError( - f"Error parsing {key} in {file_path}: {entry[key]} — {e}" - ) + raise ValueError(f"Error parsing {key} in {file_path}: {entry[key]} — {e}") return pd.DataFrame(data) def get_initial_v_miny(self) -> float: @@ -226,9 +224,7 @@ def replace_default_to_gparam(self, gparam_series: pd.Series) -> None: # print(f"current self.init_vals_input.at[index_df, index_s] = {self.init_vals_input.at[index_df, index_s]}, type: {type(self.init_vals_input.at[index_df, index_s])}") self.init_vals_input.at[index_df, index_s] = float(value) if index_s == "tau": - self.init_vals_input.at[index_df, "arr_hist"] = [row["arr"]] * int( - value - ) + self.init_vals_input.at[index_df, "arr_hist"] = [row["arr"]] * int(value) def get_init_vals(self) -> dict: """ @@ -258,16 +254,12 @@ def get_init_vals(self) -> dict: ): try: # Safely evaluate strings that are supposed to be Python literals (lists, dicts) - init_vals_dict[cell_num][val] = eval( - init_vals_dict[cell_num][val] - ) + init_vals_dict[cell_num][val] = eval(init_vals_dict[cell_num][val]) except SyntaxError as e: raise ValueError(f"Error evaluating {val}: {e}") # Specifically handling neighbors to strip spaces from entries if it's a list of strings - if val == "neighbors" and isinstance( - init_vals_dict[cell_num][val], list - ): + if val == "neighbors" and isinstance(init_vals_dict[cell_num][val], list): init_vals_dict[cell_num][val] = [ item.strip() for item in init_vals_dict[cell_num][val] @@ -277,9 +269,7 @@ def get_init_vals(self) -> dict: # Replicate arr_hist based on a specific length if it's a list if "arr_hist" in init_vals_dict[cell_num]: arr_len = len(init_vals_dict[cell_num]["arr_hist"]) - init_vals_dict[cell_num]["arr_hist"] = [ - init_vals_dict[cell_num]["arr"] - ] * arr_len + init_vals_dict[cell_num]["arr_hist"] = [init_vals_dict[cell_num]["arr"]] * arr_len return init_vals_dict def set_arr_hist(self, init_vals_dict: dict) -> None: @@ -426,10 +416,7 @@ def make_arr_hist_to_list(self) -> None: all_cells_arr_hist_lists = [] for arr_hist_list in self.init_vals_input["arr_hist"]: one_cell_arr_hist_list = ( - arr_hist_list.replace(" ", "") - .replace("[", "") - .replace("]", "") - .split(",") + arr_hist_list.replace(" ", "").replace("[", "").replace("]", "").split(",") ) for index, arr_val in enumerate(one_cell_arr_hist_list): one_cell_arr_hist_list[index] = float(arr_val) @@ -442,9 +429,7 @@ def make_cell_vertices_to_list(self) -> None: """ all_cells_v_lists = [] for v_list in self.init_vals_input["vertices"]: - one_cell_v_list = ( - v_list.replace(" ", "").replace("[", "").replace("]", "").split(",") - ) + one_cell_v_list = v_list.replace(" ", "").replace("[", "").replace("]", "").split(",") for index, v in enumerate(one_cell_v_list): one_cell_v_list[index] = int(v) all_cells_v_lists.append(one_cell_v_list) @@ -457,10 +442,7 @@ def make_neighbors_to_list(self) -> None: all_cells_neighbors_lists = [] for neighbor_list in self.init_vals_input["neighbors"]: one_cell_neighbors_list = ( - neighbor_list.replace(" ", "") - .replace("[", "") - .replace("]", "") - .split(",") + neighbor_list.replace(" ", "").replace("[", "").replace("]", "").split(",") ) all_cells_neighbors_lists.append(one_cell_neighbors_list) self.init_vals_input["neighbors"] = pd.Series(all_cells_neighbors_lists) @@ -472,6 +454,4 @@ def make_param_to_int(self) -> None: int_params = ["k1", "k2", "k3", "k4"] for param in int_params: for index in range(len(param)): - self.init_vals_input.loc[index, param] = int( - self.init_vals_input[param][index] - ) + self.init_vals_input.loc[index, param] = int(self.init_vals_input[param][index]) diff --git a/src/sim/mover/vertex_mover.py b/src/sim/mover/vertex_mover.py index 138d717..7de4ac2 100644 --- a/src/sim/mover/vertex_mover.py +++ b/src/sim/mover/vertex_mover.py @@ -232,9 +232,7 @@ def propogate_deltas_to_b_neighbors(self, cell: Cell, delta: float) -> None: neighbor_delta = self.cell_deltas[b_neighbor] else: neighbor_delta = 0 - self.add_cell_b_vertices_to_vertex_deltas( - b_neighbor, delta + neighbor_delta - ) + self.add_cell_b_vertices_to_vertex_deltas(b_neighbor, delta + neighbor_delta) stack.append((b_neighbor, delta + neighbor_delta)) def execute_vertex_movement(self, max_delta: float) -> None: diff --git a/src/sim/output/output.py b/src/sim/output/output.py index 6c3972f..87197e3 100644 --- a/src/sim/output/output.py +++ b/src/sim/output/output.py @@ -61,9 +61,7 @@ def output_cells(self) -> None: """ 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." - ) + print("No Cells added to simulation. Cannot output simulation contents.") return sim_and_cell_contents = [ "tick", @@ -77,9 +75,7 @@ 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(self.sim.get_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) @@ -111,9 +107,7 @@ def output_cells(self) -> None: with open(self.filename_json, "a") as file: json.dump(output, file, indent=4) - def get_circ_contents( - self, summary: dict[str, Any], cell: "Cell" - ) -> dict[str, Any]: + 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. diff --git a/src/sim/simulation/sim.py b/src/sim/simulation/sim.py index a547606..c8286e3 100644 --- a/src/sim/simulation/sim.py +++ b/src/sim/simulation/sim.py @@ -199,7 +199,7 @@ def get_timestep(self) -> float: 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 @@ -357,12 +357,8 @@ def on_update(self, delta_time: float) -> None: self.circulator.update() self.divider.update() self.root_tip_y = self.calculate_root_tip_y() - total_aux = sum( - [cell.get_circ_mod().get_auxin() for cell in self.cell_list] - ) - total_area = sum( - [cell.get_quad_perimeter().get_area() for cell in self.cell_list] - ) + total_aux = sum([cell.get_circ_mod().get_auxin() for cell in self.cell_list]) + total_area = sum([cell.get_quad_perimeter().get_area() for cell in self.cell_list]) print(f"Total auxin: {total_aux}") print(f"Total area: {total_area}") print(f"Total auxin/area = {total_aux/total_area}") 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()) From 39454a595cd7c79af1090c1557b91001cefa8e06 Mon Sep 17 00:00:00 2001 From: jannetty Date: Tue, 18 Nov 2025 11:02:54 -0800 Subject: [PATCH 4/8] mypy issues --- src/agent/cell.py | 10 +++++----- src/agent/circ_module.py | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/agent/cell.py b/src/agent/cell.py index 65608a7..306e42b 100644 --- a/src/agent/cell.py +++ b/src/agent/cell.py @@ -818,7 +818,7 @@ def calculate_pin_weights(self) -> dict: else: raise NotImplementedError("Only SIMPLE_INHERITANCE currently supports pin weights") - def get_imposed_pin_distribution(self) -> dict: + 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. @@ -874,7 +874,7 @@ def _transition_row_index(self, dist_to_root_tip: float) -> int: row = TRANSITION_MAX_ROW_INDEX return row - def _pin_meristematic(self, cell_type: str, dist_to_root_tip: float) -> dict | None: + def _pin_meristematic(self, cell_type: str, dist_to_root_tip: float) -> Dict[str, float]: """ Meristematic zone PIN patterns, banded along y. """ @@ -934,7 +934,7 @@ def _pin_meristematic(self, cell_type: str, dist_to_root_tip: float) -> dict | N "Meristematic cell not within meristamatic PIN bands. Check for growth errros." ) - def _pin_transition(self, cell_type: str, dist_to_root_tip: float) -> dict | None: + def _pin_transition(self, cell_type: str, dist_to_root_tip: float) -> Dict[str, float]: """ Transition zone PIN patterns, banded along y. """ @@ -997,7 +997,7 @@ def _pin_transition(self, cell_type: str, dist_to_root_tip: float) -> dict | Non "Transition cell not within transition PIN bands. Check for growth errros." ) - def _pin_elongation(self, cell_type: str) -> dict | None: + def _pin_elongation(self, cell_type: str) -> Dict[str, float]: """ Elongation zone PIN patterns, constant within each cell type. """ @@ -1013,7 +1013,7 @@ def _pin_elongation(self, cell_type: str) -> dict | None: 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 | None: + def _pin_differentiation(self, cell_type: str) -> Dict[str, float]: """ Differentiation zone PIN patterns, constant within each cell type. """ diff --git a/src/agent/circ_module.py b/src/agent/circ_module.py index b450d7c..ae388c5 100644 --- a/src/agent/circ_module.py +++ b/src/agent/circ_module.py @@ -393,12 +393,12 @@ def update_circ_contents(self, soln: np.ndarray) -> None: if self.cell.get_sim().get_pin_loc_rules == PinLocalizationRulesetEnum.IMPOSED: pins = self.cell.get_imposed_pin_distribution() - self.pina = pins.get("a") - self.pinb = pins.get("b") - self.pinl = pins.get("l") - self.pinm = pins.get("m") + self.pina = pins["a"] + self.pinb = pins["b"] + self.pinl = pins["l"] + self.pinm = pins["m"] self.pin_weights = self.initialize_pin_weights() - self.auxlax = 1 + self.auxlax = 1.0 def update_neighbor_auxin(self, neighbors_auxin: list[dict]) -> None: """ From cdad21b583c1929f89c6a3babe9bf047e1796667 Mon Sep 17 00:00:00 2001 From: jannetty Date: Tue, 25 Nov 2025 15:12:15 -0800 Subject: [PATCH 5/8] reorganized vdb_data folder: --- .../vdb_data/{ => preprocessed}/all_inspected_vdb_peri_cells.csv | 0 .../vdb_data/{ => preprocessed}/processed_vdb_outputs/vdb_t_0.csv | 0 .../vdb_data/{ => preprocessed}/processed_vdb_outputs/vdb_t_1.csv | 0 .../{ => preprocessed}/processed_vdb_outputs/vdb_t_10.csv | 0 .../{ => preprocessed}/processed_vdb_outputs/vdb_t_11.csv | 0 .../{ => preprocessed}/processed_vdb_outputs/vdb_t_12.csv | 0 .../{ => preprocessed}/processed_vdb_outputs/vdb_t_13.csv | 0 .../{ => preprocessed}/processed_vdb_outputs/vdb_t_14.csv | 0 .../{ => preprocessed}/processed_vdb_outputs/vdb_t_15.csv | 0 .../{ => preprocessed}/processed_vdb_outputs/vdb_t_16.csv | 0 .../{ => preprocessed}/processed_vdb_outputs/vdb_t_17.csv | 0 .../{ => preprocessed}/processed_vdb_outputs/vdb_t_18.csv | 0 .../{ => preprocessed}/processed_vdb_outputs/vdb_t_19.csv | 0 .../vdb_data/{ => preprocessed}/processed_vdb_outputs/vdb_t_2.csv | 0 .../{ => preprocessed}/processed_vdb_outputs/vdb_t_20.csv | 0 .../{ => preprocessed}/processed_vdb_outputs/vdb_t_21.csv | 0 .../{ => preprocessed}/processed_vdb_outputs/vdb_t_22.csv | 0 .../{ => preprocessed}/processed_vdb_outputs/vdb_t_23.csv | 0 .../{ => preprocessed}/processed_vdb_outputs/vdb_t_24.csv | 0 .../{ => preprocessed}/processed_vdb_outputs/vdb_t_25.csv | 0 .../{ => preprocessed}/processed_vdb_outputs/vdb_t_26.csv | 0 .../{ => preprocessed}/processed_vdb_outputs/vdb_t_27.csv | 0 .../vdb_data/{ => preprocessed}/processed_vdb_outputs/vdb_t_3.csv | 0 .../vdb_data/{ => preprocessed}/processed_vdb_outputs/vdb_t_4.csv | 0 .../vdb_data/{ => preprocessed}/processed_vdb_outputs/vdb_t_5.csv | 0 .../vdb_data/{ => preprocessed}/processed_vdb_outputs/vdb_t_6.csv | 0 .../vdb_data/{ => preprocessed}/processed_vdb_outputs/vdb_t_7.csv | 0 .../vdb_data/{ => preprocessed}/processed_vdb_outputs/vdb_t_8.csv | 0 .../vdb_data/{ => preprocessed}/processed_vdb_outputs/vdb_t_9.csv | 0 .../vdb_data/{ => preprocessed}/vdb_auxins_at_56pt5_336pt5.csv | 0 .../vdb_summary_seven_peri_cells_across_27_ticks.csv | 0 31 files changed, 0 insertions(+), 0 deletions(-) rename param_est/vdb_data/{ => preprocessed}/all_inspected_vdb_peri_cells.csv (100%) rename param_est/vdb_data/{ => preprocessed}/processed_vdb_outputs/vdb_t_0.csv (100%) rename param_est/vdb_data/{ => preprocessed}/processed_vdb_outputs/vdb_t_1.csv (100%) rename param_est/vdb_data/{ => preprocessed}/processed_vdb_outputs/vdb_t_10.csv (100%) rename param_est/vdb_data/{ => preprocessed}/processed_vdb_outputs/vdb_t_11.csv (100%) rename param_est/vdb_data/{ => preprocessed}/processed_vdb_outputs/vdb_t_12.csv (100%) rename param_est/vdb_data/{ => preprocessed}/processed_vdb_outputs/vdb_t_13.csv (100%) rename param_est/vdb_data/{ => preprocessed}/processed_vdb_outputs/vdb_t_14.csv (100%) rename param_est/vdb_data/{ => preprocessed}/processed_vdb_outputs/vdb_t_15.csv (100%) rename param_est/vdb_data/{ => preprocessed}/processed_vdb_outputs/vdb_t_16.csv (100%) rename param_est/vdb_data/{ => preprocessed}/processed_vdb_outputs/vdb_t_17.csv (100%) rename param_est/vdb_data/{ => preprocessed}/processed_vdb_outputs/vdb_t_18.csv (100%) rename param_est/vdb_data/{ => preprocessed}/processed_vdb_outputs/vdb_t_19.csv (100%) rename param_est/vdb_data/{ => preprocessed}/processed_vdb_outputs/vdb_t_2.csv (100%) rename param_est/vdb_data/{ => preprocessed}/processed_vdb_outputs/vdb_t_20.csv (100%) rename param_est/vdb_data/{ => preprocessed}/processed_vdb_outputs/vdb_t_21.csv (100%) rename param_est/vdb_data/{ => preprocessed}/processed_vdb_outputs/vdb_t_22.csv (100%) rename param_est/vdb_data/{ => preprocessed}/processed_vdb_outputs/vdb_t_23.csv (100%) rename param_est/vdb_data/{ => preprocessed}/processed_vdb_outputs/vdb_t_24.csv (100%) rename param_est/vdb_data/{ => preprocessed}/processed_vdb_outputs/vdb_t_25.csv (100%) rename param_est/vdb_data/{ => preprocessed}/processed_vdb_outputs/vdb_t_26.csv (100%) rename param_est/vdb_data/{ => preprocessed}/processed_vdb_outputs/vdb_t_27.csv (100%) rename param_est/vdb_data/{ => preprocessed}/processed_vdb_outputs/vdb_t_3.csv (100%) rename param_est/vdb_data/{ => preprocessed}/processed_vdb_outputs/vdb_t_4.csv (100%) rename param_est/vdb_data/{ => preprocessed}/processed_vdb_outputs/vdb_t_5.csv (100%) rename param_est/vdb_data/{ => preprocessed}/processed_vdb_outputs/vdb_t_6.csv (100%) rename param_est/vdb_data/{ => preprocessed}/processed_vdb_outputs/vdb_t_7.csv (100%) rename param_est/vdb_data/{ => preprocessed}/processed_vdb_outputs/vdb_t_8.csv (100%) rename param_est/vdb_data/{ => preprocessed}/processed_vdb_outputs/vdb_t_9.csv (100%) rename param_est/vdb_data/{ => preprocessed}/vdb_auxins_at_56pt5_336pt5.csv (100%) rename param_est/vdb_data/{ => preprocessed}/vdb_summary_seven_peri_cells_across_27_ticks.csv (100%) 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 From 48d42dc05dee1e092ff67a1d4673bf9ae0de5ca9 Mon Sep 17 00:00:00 2001 From: jannetty Date: Wed, 3 Dec 2025 20:18:29 -0800 Subject: [PATCH 6/8] fixing ga for circ_mod_aux_syn_deg_export --- param_est/ARORA_genetic_alg.py | 653 ++++++++++-------- ...ORA_genetic_alg_imposed_auxsyndegexport.py | 296 ++++++++ param_est/fitness_functions.py | 587 ++++++++-------- param_est/pe_2024110701.json | 80 +++ run_ga_imposed_auxsyndegexp.py | 7 + src/agent/cell.py | 6 +- ...t.py => circ_module_aux_syn_deg_export.py} | 2 +- src/sim/mover/vertex_mover.py | 7 +- src/sim/output/output.py | 113 ++- src/sim/simulation/sim.py | 38 +- 10 files changed, 1117 insertions(+), 672 deletions(-) create mode 100644 param_est/ARORA_genetic_alg_imposed_auxsyndegexport.py create mode 100644 param_est/pe_2024110701.json create mode 100644 run_ga_imposed_auxsyndegexp.py rename src/agent/{circ_module_aux_syn_deg_transport.py => circ_module_aux_syn_deg_export.py} (97%) 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..c0b9d1d --- /dev/null +++ b/param_est/ARORA_genetic_alg_imposed_auxsyndegexport.py @@ -0,0 +1,296 @@ +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 + 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_TRANS, + pin_loc_rules=PinLocalizationRulesetEnum.IMPOSED, + ) + + 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_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): + 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) + 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/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 306e42b..a2fc7e7 100644 --- a/src/agent/cell.py +++ b/src/agent/cell.py @@ -5,7 +5,7 @@ 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_transport import CirculateModuleAuxinSynDegTransport +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 @@ -100,7 +100,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 @@ -170,7 +170,7 @@ def __init__( case CircModEnum.AUX_SYN_DEG_ONLY: self.circ_mod = CirculateModuleAuxinSynDegOnly(self, init_vals) case CircModEnum.AUX_SYN_DEG_TRANS: - self.circ_mod = CirculateModuleAuxinSynDegTransport(self, init_vals) + self.circ_mod = CirculateModuleAuxinSynDegExport(self, init_vals) case _: raise SyntaxError(f"Unknown circ mod '{circ_mod_name}'") diff --git a/src/agent/circ_module_aux_syn_deg_transport.py b/src/agent/circ_module_aux_syn_deg_export.py similarity index 97% rename from src/agent/circ_module_aux_syn_deg_transport.py rename to src/agent/circ_module_aux_syn_deg_export.py index 15eb55a..fe9f3af 100644 --- a/src/agent/circ_module_aux_syn_deg_transport.py +++ b/src/agent/circ_module_aux_syn_deg_export.py @@ -6,7 +6,7 @@ from src.agent.cell import Cell -class CirculateModuleAuxinSynDegTransport(CirculateModule): +class CirculateModuleAuxinSynDegExport(CirculateModule): ks_aux: float kd_aux: float diff --git a/src/sim/mover/vertex_mover.py b/src/sim/mover/vertex_mover.py index 7de4ac2..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: 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 c8286e3..430cd45 100644 --- a/src/sim/simulation/sim.py +++ b/src/sim/simulation/sim.py @@ -130,14 +130,9 @@ def __init__( """ 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 != "": @@ -257,6 +252,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 @@ -346,7 +343,7 @@ def on_update(self, delta_time: float) -> None: self.tick += 1 # max_tick = 24 * 8 try: - if self.tick < 27: + if self.tick < 5: self.output.output_cells() print(f"tick: {self.tick}") if self.vis: @@ -372,14 +369,21 @@ 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( From 8bb643803a82d14625b3bb7129b44e7b1a7e4866 Mon Sep 17 00:00:00 2001 From: jannetty Date: Thu, 4 Dec 2025 00:05:46 -0800 Subject: [PATCH 7/8] aligned temporal resolutions of models --- main.py | 41 +++- ...ORA_genetic_alg_imposed_auxsyndegexport.py | 39 ++-- src/agent/cell.py | 100 ++++++---- src/agent/circ_module.py | 180 ++++++++++++------ src/agent/circ_module_aux_syn_deg_export.py | 2 + src/arora_enums.py | 2 +- src/sim/simulation/sim.py | 25 ++- 7 files changed, 270 insertions(+), 119 deletions(-) diff --git a/main.py b/main.py index 8f7ee13..5883e28 100644 --- a/main.py +++ b/main.py @@ -58,6 +58,39 @@ def make_default_param_series(): ] 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 @@ -112,11 +145,11 @@ def get_simulation_config(circ_mod: CircModEnum): "v_file": "src/sim/input/default_vs.json", "gparam_series": make_default_param_series(), } - elif circ_mod == CircModEnum.AUX_SYN_DEG_TRANS: + 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_default_param_series(), + "gparam_series": make_aux_syn_deg_exp_param_series(), } else: raise ValueError(f"Unsupported circ_mod: {circ_mod}") @@ -130,7 +163,7 @@ def get_circ_mod_enum(circ_mod_str: str): 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_TRANS + return CircModEnum.AUX_SYN_DEG_EXP else: raise ValueError(f"Unsupported circ_mod: {circ_mod}") @@ -167,7 +200,7 @@ def get_pin_loc_rules_enum(pin_loc_rules_str: str): args = parser.parse_args() circ_mod = get_circ_mod_enum(args.circ_mod) - timestep = 1 + timestep = .2 vis = True start_time = time.time() config = get_simulation_config(circ_mod) diff --git a/param_est/ARORA_genetic_alg_imposed_auxsyndegexport.py b/param_est/ARORA_genetic_alg_imposed_auxsyndegexport.py index c0b9d1d..5b77641 100644 --- a/param_est/ARORA_genetic_alg_imposed_auxsyndegexport.py +++ b/param_est/ARORA_genetic_alg_imposed_auxsyndegexport.py @@ -135,7 +135,7 @@ def _run_ARORA(self, params, chromosome): gparam_series=gparam_series, geometry=geometry, output_file=f"param_est/ARORA_output_{chromosome['sol_idx']}", - circ_mod=CircModEnum.AUX_SYN_DEG_TRANS, + circ_mod=CircModEnum.AUX_SYN_DEG_EXP, pin_loc_rules=PinLocalizationRulesetEnum.IMPOSED, ) @@ -218,15 +218,31 @@ def _calculate_fitness(self, simulation, chromosome): return fitness def make_paramspace_aux_syn_deg_trans(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) + # 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, 1.0, 60) + + # tau: time course of ARR's self- repression + tau_range = 1 + return [ ks_aux_range, kd_aux_range, @@ -235,7 +251,7 @@ def make_paramspace_aux_syn_deg_trans(self): k3_range, k4_range, k5_range, - k6_range, + k_pin_range, tau_range, ] @@ -275,6 +291,7 @@ def run_genetic_alg(self): } 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): diff --git a/src/agent/cell.py b/src/agent/cell.py index a2fc7e7..7f96e14 100644 --- a/src/agent/cell.py +++ b/src/agent/cell.py @@ -15,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 @@ -35,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 @@ -169,21 +167,23 @@ def __init__( 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_TRANS: + 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_loc_ruleset = self.get_sim().get_pin_loc_rules() - 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) @@ -781,42 +781,56 @@ 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() - def calculate_pin_weights(self) -> dict: - """ - Calculates the pin weights of each membrane of the cell. Depends on what PinLocalizationRuleset is being used for the simulation. + # Relative growth rate (1/hour) + rate = self.get_growth_rate() + if rate == 0.0: + return 0.0 - Returns - ------- - dict - The pin weights for each membrane of the cell. - Keys are "a", "b", "l", and "m". + # 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: - # when pin is imposed, it is not synthesized or degraded, only maintained at level calculated by self.get_imposed_pin_distribution() - Warning( - "Only SIMPLE_INHERITANCE currently supports pin weights, setting all weights to 0 for imposed PIN localization" - ) - return self.circ_mod.get_pin_weights() + # 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("Only SIMPLE_INHERITANCE currently supports pin weights") + raise NotImplementedError("Unknown PIN localization ruleset") def get_imposed_pin_distribution(self) -> Dict[str, float]: """ @@ -832,8 +846,10 @@ def get_imposed_pin_distribution(self) -> Dict[str, float]: """ 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.pin_weights + return self.circ_mod.get_pin_weights() dist_to_root_tip = self.get_distance_from_tip() @@ -1042,14 +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.dev_zone = self.calculate_dev_zone(self.get_distance_from_tip()) - if self.get_sim().get_pin_loc_rules == PinLocalizationRulesetEnum.SIMPLE_INHERITANCE: + + 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 ae388c5..f5d1978 100644 --- a/src/agent/circ_module.py +++ b/src/agent/circ_module.py @@ -165,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(), @@ -192,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 + # 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" - # Solve the differential equations for the current state - soln = self.solve_equations() + # How much biological time does one simulation tick represent? + dt_hours = self.cell.get_sim().get_timestep_hours() + + # 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) @@ -273,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 @@ -302,24 +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 accessible_auxin = self.auxin * memfrac - auxin_efflux = accessible_auxin * pin_activity - 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: @@ -382,23 +376,26 @@ 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() - if self.cell.get_sim().get_pin_loc_rules == PinLocalizationRulesetEnum.IMPOSED: - pins = self.cell.get_imposed_pin_distribution() - self.pina = pins["a"] - self.pinb = pins["b"] - self.pinl = pins["l"] - self.pinm = pins["m"] - self.pin_weights = self.initialize_pin_weights() - self.auxlax = 1.0 + 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: """ @@ -453,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, @@ -470,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 @@ -482,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: """ diff --git a/src/agent/circ_module_aux_syn_deg_export.py b/src/agent/circ_module_aux_syn_deg_export.py index fe9f3af..51b63ea 100644 --- a/src/agent/circ_module_aux_syn_deg_export.py +++ b/src/agent/circ_module_aux_syn_deg_export.py @@ -28,6 +28,8 @@ def get_float(key: str) -> float: 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 diff --git a/src/arora_enums.py b/src/arora_enums.py index 0ad1544..16d70d2 100644 --- a/src/arora_enums.py +++ b/src/arora_enums.py @@ -5,7 +5,7 @@ class CircModEnum(Enum): UNIVERSAL_SYN_DEG = 1 INDEP_SYN_DEG = 2 AUX_SYN_DEG_ONLY = 3 - AUX_SYN_DEG_TRANS = 4 + AUX_SYN_DEG_EXP = 4 class PinLocalizationRulesetEnum(Enum): diff --git a/src/sim/simulation/sim.py b/src/sim/simulation/sim.py index 430cd45..a035905 100644 --- a/src/sim/simulation/sim.py +++ b/src/sim/simulation/sim.py @@ -36,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. @@ -91,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" @@ -115,7 +117,7 @@ def __init__( width: int, height: int, title: str, - timestep: int, + timestep: float, vis: bool, pin_loc_rules: PinLocalizationRulesetEnum, circ_mod: CircModEnum, @@ -124,6 +126,7 @@ def __init__( 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. @@ -151,6 +154,7 @@ def __init__( 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: """ @@ -188,9 +192,13 @@ 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 @@ -339,11 +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 try: - if self.tick < 5: + if self.tick < 28: self.output.output_cells() print(f"tick: {self.tick}") if self.vis: @@ -387,7 +396,7 @@ def run_sim(self) -> None: def main( - timestep: int, + timestep: float, vis: bool, pin_loc_rules: PinLocalizationRulesetEnum, circ_mod: CircModEnum, @@ -395,6 +404,7 @@ def main( v_file: str = "", gparam_series: Series | str = "", output_file: str = "output", + output_frequency: int = 1 ) -> int: """Creates and runs the ABM.""" print("Making GrowingSim") @@ -414,6 +424,7 @@ def main( gparam_series, geometry, output_file, + output_frequency ) set_window(simulation) print("Running Simulation") From d9a6e48aa7d693432d09ee48ccfe1707517760f6 Mon Sep 17 00:00:00 2001 From: jannetty Date: Thu, 4 Dec 2025 00:19:03 -0800 Subject: [PATCH 8/8] changing timestep in ga pipeline --- .../ARORA_genetic_alg_imposed_auxsyndegexport.py | 5 +++-- src/agent/circ_module.py | 12 ++++++------ src/sim/simulation/sim.py | 5 ++--- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/param_est/ARORA_genetic_alg_imposed_auxsyndegexport.py b/param_est/ARORA_genetic_alg_imposed_auxsyndegexport.py index 5b77641..18a5fbb 100644 --- a/param_est/ARORA_genetic_alg_imposed_auxsyndegexport.py +++ b/param_est/ARORA_genetic_alg_imposed_auxsyndegexport.py @@ -117,7 +117,7 @@ def _cleanup_sim_files(self, chrom_idx: int) -> None: print(f"Warning: could not delete {path}: {e}") def _run_ARORA(self, params, chromosome): - timestep = 1 + 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" @@ -137,6 +137,7 @@ def _run_ARORA(self, params, chromosome): output_file=f"param_est/ARORA_output_{chromosome['sol_idx']}", circ_mod=CircModEnum.AUX_SYN_DEG_EXP, pin_loc_rules=PinLocalizationRulesetEnum.IMPOSED, + output_frequency=1 # outputting every 6 minutes ) try: @@ -238,7 +239,7 @@ def make_paramspace_aux_syn_deg_trans(self): k5_range = 1 # k6: k_pin, PIN-mediated export factor [1/h]. - k_pin_range = np.geomspace(0.05, 1.0, 60) + k_pin_range = np.geomspace(0.05, 0.5, 40) # tau: time course of ARR's self- repression tau_range = 1 diff --git a/src/agent/circ_module.py b/src/agent/circ_module.py index f5d1978..6db438a 100644 --- a/src/agent/circ_module.py +++ b/src/agent/circ_module.py @@ -504,12 +504,12 @@ 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) + # 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: diff --git a/src/sim/simulation/sim.py b/src/sim/simulation/sim.py index a035905..58f46cc 100644 --- a/src/sim/simulation/sim.py +++ b/src/sim/simulation/sim.py @@ -350,10 +350,9 @@ def on_update(self, delta_time: float) -> None: 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 < 28: - self.output.output_cells() + if self.tick < max_tick: print(f"tick: {self.tick}") if self.vis: self.update_viewport_position()