@@ -68,6 +68,7 @@ def __init__(
6868 [input_parameters .min_sec_root_length , input_parameters .max_sec_root_length ]
6969 )
7070 self .length_reduction = input_parameters .length_reduction
71+ self .mass = 0
7172
7273 self .reset_transform ()
7374 self .simulation_tag = simulation_tag
@@ -225,6 +226,20 @@ def init_segment_coordinates(
225226 coordinates [:, 2 ] *= - 1
226227 return coordinates
227228
229+ def calculate_mass (self ) -> float :
230+ """Calculate the mass of the root organ.
231+
232+ Returns:
233+ float:
234+ The mass.
235+ """
236+ diameters = self .get_diameters ()
237+ radius = diameters / 2
238+ heights = self .get_lengths ()
239+ volume = np .pi * radius ** 2 * heights
240+ mass = volume * self .input_parameters .root_tissue_density
241+ return sum (mass )
242+
228243 def construct_root (
229244 self , segments_per_root : int , apex_diameter : int , root_tissue_density : float
230245 ) -> List [RootNode ]:
@@ -270,6 +285,7 @@ def construct_root(
270285 new_organ = False ,
271286 )
272287
288+ self .mass = self .calculate_mass ()
273289 return self .segments
274290
275291 def add_child_organ (
@@ -360,6 +376,7 @@ def construct_root_from_parent(
360376 new_organ = False ,
361377 )
362378
379+ self .mass = self .calculate_mass ()
363380 return self .segments
364381
365382 def reset_transform (self ) -> np .ndarray :
@@ -456,9 +473,13 @@ def cascading_update_transform(
456473 scale = scale ,
457474 )
458475
459- def get_coordinates (self ) -> np .ndarray :
476+ def get_coordinates (self , as_array : bool = True ) -> np .ndarray :
460477 """Get the coordinates of the root segments.
461478
479+ Args:
480+ as_array (bool, optional):
481+ Return the coordinates as a Numpy array. Defaults to True.
482+
462483 Returns:
463484 np.ndarray:
464485 The coordinates of the root segments
@@ -469,9 +490,50 @@ def get_coordinates(self) -> np.ndarray:
469490 coordinate = [node_data .x , node_data .y , node_data .z ]
470491 coordinates .append (coordinate )
471492
472- coordinates = np .array (coordinates )
493+ if as_array :
494+ coordinates = np .array (coordinates )
473495 return coordinates
474496
497+ def get_diameters (self , as_array : bool = True ) -> np .ndarray :
498+ """Get the diameters of the root segments.
499+
500+ Args:
501+ as_array (bool, optional):
502+ Return the coordinates as a Numpy array. Defaults to True.
503+
504+ Returns:
505+ np.ndarray:
506+ The coordinates of the root segments
507+ """
508+ diameters = []
509+ for segment in self .segments :
510+ node_data = segment .node_data
511+ diameters .append (node_data .diameter )
512+
513+ if as_array :
514+ diameters = np .array (diameters )
515+ return diameters
516+
517+ def get_lengths (self , as_array : bool = True ) -> np .ndarray :
518+ """Get the lengths of the root segments.
519+
520+ Args:
521+ as_array (bool, optional):
522+ Return the coordinates as a Numpy array. Defaults to True.
523+
524+ Returns:
525+ np.ndarray:
526+ The coordinates of the root segments
527+ """
528+ lengths = []
529+ for segment in self .segments :
530+ node_data = segment .node_data
531+ lengths .append (node_data .length )
532+
533+ if as_array :
534+ lengths = np .array (lengths )
535+ return lengths
536+
475537 def transform (self ) -> np .ndarray :
476538 """Apply the transformation matrix to the root system coordinates.
477539
@@ -610,6 +672,72 @@ def __transform(**kwargs):
610672 if np .any (np .not_equal (local_origin , parent_coordinates )):
611673 return self .cascading_set_invalid_root ()
612674
675+ def grow (
676+ self , simulation : "RootSystemSimulation" , input_parameters : RootSimulationModel
677+ ) -> None :
678+ """Grow the root organ.
679+
680+ Args:
681+ simulation (RootSystemSimulation):
682+ The root simulation model.
683+ input_parameters (RootSimulationModel):
684+ The root simulation parameters.
685+ """
686+ diameter_growth = self .rng .uniform (1.01 , 1.05 )
687+ diameters = self .get_diameters ()
688+ last_diameter = diameters [- 1 ]
689+ diameters *= diameter_growth
690+
691+ for i , segment in enumerate (self .segments ):
692+ segment .node_data .diameter = diameters [i ]
693+
694+ diameters = diameters .tolist ()
695+ diameters .append (last_diameter )
696+ diameters = np .array (diameters )
697+
698+ lengths = self .get_lengths (False )
699+ new_length = self .init_lengths (1 ) / (len (lengths ) + 1 )
700+ lengths .append (new_length .item ())
701+ lengths = np .array (lengths )
702+
703+ coordinates = self .get_coordinates (False )
704+ new_coordinate = self .init_segment_coordinates (1 , new_length )
705+ new_coordinate += coordinates [- 1 ]
706+ new_coordinate = new_coordinate [1 , :]
707+ coordinates .append (new_coordinate )
708+ coordinates = np .array (coordinates )
709+
710+ apex_segment = self .segments [- 1 ]
711+ root_tissue_density = input_parameters .root_tissue_density
712+ self .add_child_node (
713+ apex_segment ,
714+ diameters = diameters ,
715+ lengths = lengths ,
716+ coordinates = coordinates ,
717+ root_type = self .root_type ,
718+ root_tissue_density = root_tissue_density ,
719+ i = i + 1 ,
720+ new_organ = False ,
721+ )
722+
723+ mass = self .calculate_mass ()
724+ if mass <= self .mass * 1.5 :
725+ return
726+ self .mass = mass
727+ next_order = self .base_node .node_data .order + 1
728+ if simulation .organs .get (next_order ) is None :
729+ simulation .organs [next_order ] = []
730+
731+ child_organ = self .add_child_organ (
732+ floor_threshold = input_parameters .floor_threshold ,
733+ ceiling_threshold = input_parameters .ceiling_threshold ,
734+ )
735+ simulation .organs [next_order ].append (child_organ )
736+ child_organ .construct_root_from_parent (
737+ input_parameters .segments_per_root ,
738+ input_parameters .apex_diameter ,
739+ )
740+
613741
614742class RootSystemSimulation :
615743 """The root system architecture simulation model."""
@@ -1051,6 +1179,22 @@ def validate(
10511179 input_parameters .max_val_attempts ,
10521180 )
10531181
1182+ def grow (
1183+ self , simulation : RootSimulationModel , input_parameters : RootSimulationModel
1184+ ) -> None :
1185+ """Model the growth of root organs.
1186+
1187+ Args:
1188+ simulation (RootSimulationModel):
1189+ The root simulation model.
1190+ input_parameters (RootSimulationModel):
1191+ The simulation input parameters.
1192+ """
1193+ for _ in range (input_parameters .t ):
1194+ for order in range (2 , input_parameters .max_order + 1 ):
1195+ for organ in self .organs [order ]:
1196+ organ .grow (simulation , input_parameters )
1197+
10541198 def run (self , input_parameters : RootSimulationModel ) -> None :
10551199 """Run a root system architecture simulation.
10561200
@@ -1063,6 +1207,7 @@ def run(self, input_parameters: RootSimulationModel) -> None:
10631207 The simulation results.
10641208 """
10651209 self .init_organs (input_parameters )
1210+ self .grow (self , input_parameters )
10661211 self .position_secondary_roots (input_parameters )
10671212 self .position_primary_roots (input_parameters )
10681213 self .validate (input_parameters , pitch = 60 )
0 commit comments