Skip to content

Commit ccfe154

Browse files
committed
adding dynamic growth
1 parent a2a13f8 commit ccfe154

File tree

6 files changed

+186
-3
lines changed

6 files changed

+186
-3
lines changed

app/conf/calibration_form/common.yaml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,21 @@ components:
333333
persistence: true
334334
pushable: false
335335
allowCross: false
336+
- id: sim-t-slider
337+
param: t
338+
label: Time steps
339+
help: The number of time steps to run the model (hourly)
340+
data_type: discrete
341+
class_name: dash.dcc.RangeSlider
342+
handler: range_slider
343+
min_value: 0
344+
max_value: 20
345+
kwargs:
346+
min: 0
347+
max: 100
348+
persistence: true
349+
pushable: false
350+
allowCross: false
336351
- id: random-seed-input
337352
param: random_seed
338353
label: Random seed

app/conf/deployments_form/common.yaml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,17 @@ components:
263263
step: 1
264264
value: 7.5
265265
persistence: true
266+
- id: sim-t-input
267+
param: t
268+
label: Time steps
269+
help: The number of time steps to run the model (hourly)
270+
class_name: dash_bootstrap_components.Input
271+
kwargs:
272+
type: number
273+
min: 0
274+
step: 1
275+
value: 1
276+
persistence: true
266277
- id: enable-soil-input
267278
param: enable_soil
268279
label: Enable soil

app/conf/simulation_form/common.yaml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,17 @@ components:
263263
step: 1
264264
value: 7.5
265265
persistence: true
266+
- id: sim-t-input
267+
param: t
268+
label: Time steps
269+
help: The number of time steps to run the model (hourly)
270+
class_name: dash_bootstrap_components.Input
271+
kwargs:
272+
type: number
273+
min: 0
274+
step: 1
275+
value: 1
276+
persistence: true
266277
- id: enable-soil-input
267278
param: enable_soil
268279
label: Enable soil

app/pages/deployments_root_system.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -315,7 +315,6 @@ def endpoint_predict(task: str, endpoint: str, json: dict) -> str:
315315
"""
316316
res = requests.post(url=f"{endpoint}/predict", json=json)
317317
json_data = res.json()
318-
print(json_data)
319318
df = pd.DataFrame(json_data)
320319
outdir = get_outdir()
321320
date_now = get_datetime_now()

deeprootgen/data_model/simulation_data_models.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ class RootSimulationModel(BaseModel):
108108
mechanical_constraints: float
109109
root_tissue_density: float
110110
gravitropism: float
111+
t: int
111112
origin_min: Optional[float] = 1e-3
112113
origin_max: Optional[float] = 1e-2
113114
enable_soil: Optional[bool] = False
@@ -199,6 +200,7 @@ class RootCalibrationIntervals(BaseModel):
199200
mechanical_constraints: ParameterIntervalModel
200201
root_tissue_density: ParameterIntervalModel
201202
gravitropism: ParameterIntervalModel
203+
t: ParameterIntervalModel
202204

203205

204206
class RootCalibrationModel(BaseModel):

deeprootgen/model/root.py

Lines changed: 147 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -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

614742
class 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

Comments
 (0)