1"""Typed model for Helios plant architecture parameters.
3This module provides discoverable, validated dataclasses mirroring the nested
4C++ ``ShootParameters`` / ``PhytomerParameters`` / ``LeafPrototype`` structures
5(plus the flat ``CarbohydrateParameters`` / ``NitrogenParameters``). The objects
6serialize to/from the plain ``dict`` JSON transport used by the PlantArchitecture
7wrapper, so they can be used interchangeably with the raw-dict API.
9Canonical usage builds a typed object from the values currently defined in the
10native library, mutates it, and applies it back -- this avoids any drift between
11Python-side defaults and the C++ defaults::
13 from pyhelios import Context, PlantArchitecture
14 from pyhelios.plant_architecture_params import ShootParameters, RandomParameterFloat
16 with Context() as ctx:
17 pa = PlantArchitecture(ctx)
18 pa.loadPlantModelFromLibrary("almond")
19 sp = ShootParameters.from_dict(pa.getCurrentShootParameters("trunk"))
20 sp.phytomer_parameters.leaf.pitch = RandomParameterFloat.uniform(40, 50)
21 pa.defineShootType("trunk2", sp)
25* Every numeric leaf/shoot/phytomer field is a :class:`RandomParameterFloat` or
26 :class:`RandomParameterInt` carrying a distribution and its parameters.
27* Prototype functions (leaf/flower/fruit) are referenced by *name* -- a string
28 naming a built-in Helios prototype (e.g. ``"AlmondFlowerPrototype"``). An empty
29 string / ``None`` means "unset". Phytomer creation and callback functions are not
30 part of these parameter objects; see
31 :meth:`~pyhelios.PlantArchitecture.PlantArchitecture.setPhytomerCreationFunction` and
32 :meth:`~pyhelios.PlantArchitecture.PlantArchitecture.setPhytomerCallbackFunction`.
33* Child shoot types round-trip in both directions as of helios-core 1.3.84; see
34 :attr:`ShootParameters.child_shoot_types`.
37from __future__
import annotations
40from dataclasses
import dataclass, field
41from enum
import IntEnum
42from typing
import Any, Dict, List, Optional, Tuple
46 "RandomParameterFloat",
50 "InternodeParameters",
54 "InflorescenceParameters",
57 "CarbohydrateParameters",
59 "LEAF_EXPANSION_RATE_UNSET",
67LEAF_EXPANSION_RATE_UNSET = -1.0
74 """State of a vegetative or floral bud.
76 ``DEAD`` means the bud will produce nothing further. It is set both when a bud is
77 killed -- by :meth:`~pyhelios.PlantArchitecture.PlantArchitecture.removeShootVegetativeBuds`
78 or by a failed bud-break draw -- and when a bud has *already broken into a child
79 shoot*. A count of dead buds is therefore not a count of killed buds; to test
80 whether a shoot can still grow, count the live states instead.
96 """A float-valued parameter with a sampling distribution.
98 Use the classmethod constructors (:meth:`constant`, :meth:`uniform`,
99 :meth:`normal`, :meth:`weibull`) rather than constructing directly.
102 distribution: str =
"constant"
103 parameters: List[float] = field(default_factory=
lambda: [0.0])
106 def constant(cls, value: float) ->
"RandomParameterFloat":
107 return cls(
"constant", [float(value)])
110 def uniform(cls, min_val: float, max_val: float) ->
"RandomParameterFloat":
111 if min_val > max_val:
112 raise ValueError(f
"min_val ({min_val}) must be <= max_val ({max_val})")
113 return cls(
"uniform", [float(min_val), float(max_val)])
116 def normal(cls, mean: float, std_dev: float) ->
"RandomParameterFloat":
118 raise ValueError(f
"std_dev ({std_dev}) must be >= 0")
119 return cls(
"normal", [float(mean), float(std_dev)])
122 def weibull(cls, shape: float, scale: float) ->
"RandomParameterFloat":
124 raise ValueError(f
"shape ({shape}) must be > 0")
126 raise ValueError(f
"scale ({scale}) must be > 0")
127 return cls(
"weibull", [float(shape), float(scale)])
133 def from_dict(cls, d: Dict[str, Any]) ->
"RandomParameterFloat":
134 return cls(str(d[
"distribution"]), [float(p)
for p
in d[
"parameters"]])
139 """An int-valued parameter with a sampling distribution."""
141 distribution: str =
"constant"
142 parameters: List[int] = field(default_factory=
lambda: [0])
145 def constant(cls, value: int) ->
"RandomParameterInt":
146 return cls(
"constant", [int(value)])
149 def uniform(cls, min_val: int, max_val: int) ->
"RandomParameterInt":
150 if min_val > max_val:
151 raise ValueError(f
"min_val ({min_val}) must be <= max_val ({max_val})")
152 return cls(
"uniform", [int(min_val), int(max_val)])
155 def discrete(cls, values: List[int]) ->
"RandomParameterInt":
157 raise ValueError(
"values list cannot be empty")
158 return cls(
"discretevalues", [int(v)
for v
in values])
165 def from_dict(cls, d: Dict[str, Any]) ->
"RandomParameterInt":
166 return cls(str(d[
"distribution"]), [int(round(float(p)))
for p
in d[
"parameters"]])
171RandomParameter = RandomParameterFloat
177Color = Tuple[float, float, float]
178Vec3 = Tuple[float, float, float]
181def _rpf(d: Dict[str, Any], key: str, default: RandomParameterFloat) -> RandomParameterFloat:
182 return RandomParameterFloat.from_dict(d[key])
if key
in d
else default
185def _rpi(d: Dict[str, Any], key: str, default: RandomParameterInt) -> RandomParameterInt:
186 return RandomParameterInt.from_dict(d[key])
if key
in d
else default
190 return {
"r": float(c[0]),
"g": float(c[1]),
"b": float(c[2])}
194 return (float(d.get(
"r", default[0])), float(d.get(
"g", default[1])), float(d.get(
"b", default[2])))
198 return {
"x": float(v[0]),
"y": float(v[1]),
"z": float(v[2])}
202 return (float(d.get(
"x", default[0])), float(d.get(
"y", default[1])), float(d.get(
"z", default[2])))
210 leaf_aspect_ratio: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(1.0))
211 midrib_fold_fraction: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.0))
212 longitudinal_curvature: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.0))
213 lateral_curvature: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.0))
214 petiole_roll: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.0))
215 wave_period: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.0))
216 wave_amplitude: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.0))
217 longitudinal_curvature_exponent: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(4.0))
218 flexibility: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.0))
219 flexibility_taper: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(1.0))
220 flexibility_aging: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.0))
221 flexibility_aging_max: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(4.0))
225 leaf_buckle_length: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.0))
228 leaf_buckle_angle: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.0))
229 leaf_offset: Vec3 = (0.0, 0.0, 0.0)
230 subdivisions: int = 1
231 unique_prototypes: int = 1
236 build_petiolule: bool =
False
243 petiolule_length: Dict[int, float] = field(default_factory=
lambda: {0: 0.05})
244 OBJ_model_file: str =
""
245 leaf_texture_file: Dict[int, str] = field(default_factory=dict)
246 prototype_function: Optional[str] =
None
249 """Warn when a deprecated buckle parameter carries a non-zero value.
251 helios-core 1.3.84 replaced the fixed-station kink these produced with the
252 continuous self-weight droop of :attr:`flexibility`. The buckle values are still
253 converted to an equivalent flexibility natively, but only while :attr:`flexibility`
254 is zero, so a plant setting both silently ignores the buckle pair.
256 for name
in (
"leaf_buckle_length",
"leaf_buckle_angle"):
257 param = getattr(self, name)
258 if any(v != 0.0
for v
in param.parameters):
260 f
"LeafPrototype.{name} is deprecated since helios-core 1.3.84 and is "
261 "superseded by LeafPrototype.flexibility, which bends the leaf continuously "
262 "under its own weight as it grows rather than kinking it at a fixed station. "
263 "Set flexibility (and optionally flexibility_taper / flexibility_aging) instead; "
264 "the buckle value is converted to an equivalent flexibility only while "
265 "flexibility is left at zero.",
270 def to_dict(self) -> Dict[str, Any]:
291 "petiolule_length": {str(int(k)): float(v)
for k, v
in self.
petiolule_length.items()},
298 def from_dict(cls, d: Dict[str, Any]) ->
"LeafPrototype":
300 tex = d.get(
"leaf_texture_file", {})
302 leaf_aspect_ratio=
_rpf(d,
"leaf_aspect_ratio", base.leaf_aspect_ratio),
303 midrib_fold_fraction=
_rpf(d,
"midrib_fold_fraction", base.midrib_fold_fraction),
304 longitudinal_curvature=
_rpf(d,
"longitudinal_curvature", base.longitudinal_curvature),
305 lateral_curvature=
_rpf(d,
"lateral_curvature", base.lateral_curvature),
306 petiole_roll=
_rpf(d,
"petiole_roll", base.petiole_roll),
307 wave_period=
_rpf(d,
"wave_period", base.wave_period),
308 wave_amplitude=
_rpf(d,
"wave_amplitude", base.wave_amplitude),
309 longitudinal_curvature_exponent=
_rpf(d,
"longitudinal_curvature_exponent", base.longitudinal_curvature_exponent),
310 flexibility=
_rpf(d,
"flexibility", base.flexibility),
311 flexibility_taper=
_rpf(d,
"flexibility_taper", base.flexibility_taper),
312 flexibility_aging=
_rpf(d,
"flexibility_aging", base.flexibility_aging),
313 flexibility_aging_max=
_rpf(d,
"flexibility_aging_max", base.flexibility_aging_max),
314 leaf_buckle_length=
_rpf(d,
"leaf_buckle_length", base.leaf_buckle_length),
315 leaf_buckle_angle=
_rpf(d,
"leaf_buckle_angle", base.leaf_buckle_angle),
316 leaf_offset=
_vec3_from_dict(d[
"leaf_offset"], base.leaf_offset)
if "leaf_offset" in d
else base.leaf_offset,
317 subdivisions=int(d.get(
"subdivisions", base.subdivisions)),
318 unique_prototypes=int(d.get(
"unique_prototypes", base.unique_prototypes)),
319 build_petiolule=bool(d.get(
"build_petiolule", base.build_petiolule)),
320 petiolule_length=({int(k): float(v)
for k, v
in d[
"petiolule_length"].items()}
321 if "petiolule_length" in d
else base.petiolule_length),
322 OBJ_model_file=str(d.get(
"OBJ_model_file", base.OBJ_model_file)),
323 leaf_texture_file={int(k): str(v)
for k, v
in tex.items()},
324 prototype_function=(d.get(
"prototype_function")
or None),
333 pitch: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(20.0))
334 phyllotactic_angle: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(137.5))
335 radius_initial: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.001))
336 max_vegetative_buds_per_petiole: RandomParameterInt = field(default_factory=
lambda: RandomParameterInt.constant(0))
337 max_floral_buds_per_petiole: RandomParameterInt = field(default_factory=
lambda: RandomParameterInt.constant(0))
338 color: Color = (0.0, 0.0, 0.0)
339 image_texture: str =
""
340 length_segments: int = 1
341 radial_subdivisions: int = 7
357 def from_dict(cls, d: Dict[str, Any]) ->
"InternodeParameters":
360 pitch=
_rpf(d,
"pitch", base.pitch),
361 phyllotactic_angle=
_rpf(d,
"phyllotactic_angle", base.phyllotactic_angle),
362 radius_initial=
_rpf(d,
"radius_initial", base.radius_initial),
363 max_vegetative_buds_per_petiole=
_rpi(d,
"max_vegetative_buds_per_petiole", base.max_vegetative_buds_per_petiole),
364 max_floral_buds_per_petiole=
_rpi(d,
"max_floral_buds_per_petiole", base.max_floral_buds_per_petiole),
365 color=
_color_from_dict(d[
"color"], base.color)
if "color" in d
else base.color,
366 image_texture=str(d.get(
"image_texture", base.image_texture)),
367 length_segments=int(d.get(
"length_segments", base.length_segments)),
368 radial_subdivisions=int(d.get(
"radial_subdivisions", base.radial_subdivisions)),
374 petioles_per_internode: int = 1
377 pitch: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(90.0))
380 radius: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.001))
386 length: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.05))
387 curvature: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.0))
388 taper: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.0))
389 color: Color = (0.0, 0.0, 0.0)
390 length_segments: int = 1
391 radial_subdivisions: int = 7
396 flexibility: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.0))
400 flexibility_aging: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.0))
418 def from_dict(cls, d: Dict[str, Any]) ->
"PetioleParameters":
421 petioles_per_internode=int(d.get(
"petioles_per_internode", base.petioles_per_internode)),
422 pitch=
_rpf(d,
"pitch", base.pitch),
423 radius=
_rpf(d,
"radius", base.radius),
424 length=
_rpf(d,
"length", base.length),
425 curvature=
_rpf(d,
"curvature", base.curvature),
426 taper=
_rpf(d,
"taper", base.taper),
427 color=
_color_from_dict(d[
"color"], base.color)
if "color" in d
else base.color,
428 length_segments=int(d.get(
"length_segments", base.length_segments)),
429 radial_subdivisions=int(d.get(
"radial_subdivisions", base.radial_subdivisions)),
430 flexibility=
_rpf(d,
"flexibility", base.flexibility),
431 flexibility_aging=
_rpf(d,
"flexibility_aging", base.flexibility_aging),
437 leaves_per_petiole: RandomParameterInt = field(default_factory=
lambda: RandomParameterInt.constant(1))
442 pitch: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.0))
443 yaw: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.0))
444 roll: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.0))
445 leaflet_offset: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.0))
446 leaflet_scale: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(1.0))
450 intercalary_leaflet_scale: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.0))
451 prototype_scale: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.05))
452 prototype: LeafPrototype = field(default_factory=LeafPrototype)
468 def from_dict(cls, d: Dict[str, Any]) ->
"LeafParameters":
471 leaves_per_petiole=
_rpi(d,
"leaves_per_petiole", base.leaves_per_petiole),
472 pitch=
_rpf(d,
"pitch", base.pitch),
473 yaw=
_rpf(d,
"yaw", base.yaw),
474 roll=
_rpf(d,
"roll", base.roll),
475 leaflet_offset=
_rpf(d,
"leaflet_offset", base.leaflet_offset),
476 leaflet_scale=
_rpf(d,
"leaflet_scale", base.leaflet_scale),
477 intercalary_leaflet_scale=
_rpf(d,
"intercalary_leaflet_scale", base.intercalary_leaflet_scale),
478 prototype_scale=
_rpf(d,
"prototype_scale", base.prototype_scale),
479 prototype=LeafPrototype.from_dict(d[
"prototype"])
if "prototype" in d
else base.prototype,
485 length: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.05))
486 radius: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.001))
487 pitch: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.0))
488 roll: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.0))
491 yaw: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.0))
492 curvature: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.0))
497 color: Color = (0.0, 0.0, 0.0)
498 length_segments: int = 3
499 radial_subdivisions: int = 7
515 def from_dict(cls, d: Dict[str, Any]) ->
"PeduncleParameters":
518 length=
_rpf(d,
"length", base.length),
519 radius=
_rpf(d,
"radius", base.radius),
520 pitch=
_rpf(d,
"pitch", base.pitch),
521 roll=
_rpf(d,
"roll", base.roll),
522 yaw=
_rpf(d,
"yaw", base.yaw),
523 curvature=
_rpf(d,
"curvature", base.curvature),
524 color=
_color_from_dict(d[
"color"], base.color)
if "color" in d
else base.color,
525 length_segments=int(d.get(
"length_segments", base.length_segments)),
526 radial_subdivisions=int(d.get(
"radial_subdivisions", base.radial_subdivisions)),
532 flowers_per_peduncle: RandomParameterInt = field(default_factory=
lambda: RandomParameterInt.constant(1))
533 flower_offset: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.0))
534 pitch: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.0))
535 roll: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.0))
536 flower_prototype_scale: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.0075))
537 fruit_prototype_scale: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.0075))
538 fruit_gravity_factor_fraction: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.0))
539 unique_prototypes: int = 1
544 inflorescence_maturity_period: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(-1.0))
545 flower_prototype_function: Optional[str] =
None
546 fruit_prototype_function: Optional[str] =
None
564 def from_dict(cls, d: Dict[str, Any]) ->
"InflorescenceParameters":
567 flowers_per_peduncle=
_rpi(d,
"flowers_per_peduncle", base.flowers_per_peduncle),
568 flower_offset=
_rpf(d,
"flower_offset", base.flower_offset),
569 pitch=
_rpf(d,
"pitch", base.pitch),
570 roll=
_rpf(d,
"roll", base.roll),
571 flower_prototype_scale=
_rpf(d,
"flower_prototype_scale", base.flower_prototype_scale),
572 fruit_prototype_scale=
_rpf(d,
"fruit_prototype_scale", base.fruit_prototype_scale),
573 fruit_gravity_factor_fraction=
_rpf(d,
"fruit_gravity_factor_fraction", base.fruit_gravity_factor_fraction),
574 unique_prototypes=int(d.get(
"unique_prototypes", base.unique_prototypes)),
575 inflorescence_maturity_period=
_rpf(d,
"inflorescence_maturity_period", base.inflorescence_maturity_period),
576 flower_prototype_function=(d.get(
"flower_prototype_function")
or None),
577 fruit_prototype_function=(d.get(
"fruit_prototype_function")
or None),
583 internode: InternodeParameters = field(default_factory=InternodeParameters)
584 petiole: PetioleParameters = field(default_factory=PetioleParameters)
585 leaf: LeafParameters = field(default_factory=LeafParameters)
586 peduncle: PeduncleParameters = field(default_factory=PeduncleParameters)
587 inflorescence: InflorescenceParameters = field(default_factory=InflorescenceParameters)
599 def from_dict(cls, d: Dict[str, Any]) ->
"PhytomerParameters":
602 internode=InternodeParameters.from_dict(d[
"internode"])
if "internode" in d
else base.internode,
603 petiole=PetioleParameters.from_dict(d[
"petiole"])
if "petiole" in d
else base.petiole,
604 leaf=LeafParameters.from_dict(d[
"leaf"])
if "leaf" in d
else base.leaf,
605 peduncle=PeduncleParameters.from_dict(d[
"peduncle"])
if "peduncle" in d
else base.peduncle,
606 inflorescence=InflorescenceParameters.from_dict(d[
"inflorescence"])
if "inflorescence" in d
else base.inflorescence,
616 (
"girth_area_factor", 0.0),
617 (
"insertion_angle_tip", 20.0),
618 (
"insertion_angle_decay_rate", 0.0),
619 (
"internode_length_max", 0.02),
620 (
"internode_length_min", 0.002),
621 (
"internode_length_decay_rate", 0.0),
624 (
"gravitropic_curvature", 0.0),
626 (
"tortuosity_persistence_length", 0.5),
627 (
"phyllochron_min", 2.0),
628 (
"elongation_rate_max", 0.2),
629 (
"leaf_expansion_rate_max", LEAF_EXPANSION_RATE_UNSET),
630 (
"vegetative_bud_break_probability_min", 0.0),
631 (
"vegetative_bud_break_probability_max", 1.0),
632 (
"vegetative_bud_break_probability_decay_rate", -0.5),
633 (
"flower_bud_break_probability", 0.0),
634 (
"fruit_set_probability", 0.0),
635 (
"vegetative_bud_break_time", 5.0),
639 (
"max_nodes_per_season", 9999),
640 (
"max_terminal_floral_buds", 0),
642_SHOOT_BOOL_FIELDS = (
643 (
"flowers_require_dormancy",
False),
644 (
"growth_requires_dormancy",
False),
645 (
"determinate_shoot_growth",
True),
651 phytomer_parameters: PhytomerParameters = field(default_factory=PhytomerParameters)
659 girth_area_factor: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.0))
660 insertion_angle_tip: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(20.0))
661 insertion_angle_decay_rate: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.0))
662 internode_length_max: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.02))
663 internode_length_min: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.002))
664 internode_length_decay_rate: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.0))
665 base_roll: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.0))
666 base_yaw: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.0))
667 gravitropic_curvature: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.0))
673 tortuosity: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.0))
677 tortuosity_persistence_length: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.5))
678 phyllochron_min: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(2.0))
679 elongation_rate_max: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.2))
688 leaf_expansion_rate_max: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(LEAF_EXPANSION_RATE_UNSET))
689 vegetative_bud_break_probability_min: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.0))
690 vegetative_bud_break_probability_max: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(1.0))
691 vegetative_bud_break_probability_decay_rate: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(-0.5))
692 flower_bud_break_probability: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.0))
693 fruit_set_probability: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.0))
694 vegetative_bud_break_time: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(5.0))
697 max_nodes: RandomParameterInt = field(default_factory=
lambda: RandomParameterInt.constant(10))
698 max_nodes_per_season: RandomParameterInt = field(default_factory=
lambda: RandomParameterInt.constant(9999))
699 max_terminal_floral_buds: RandomParameterInt = field(default_factory=
lambda: RandomParameterInt.constant(0))
702 flowers_require_dormancy: bool =
False
703 growth_requires_dormancy: bool =
False
704 determinate_shoot_growth: bool =
True
712 child_shoot_types: Optional[Dict[str, Any]] =
None
716 for name, _
in _SHOOT_RPF_FIELDS:
717 d[name] = getattr(self, name).
to_dict()
718 for name, _
in _SHOOT_RPI_FIELDS:
719 d[name] = getattr(self, name).
to_dict()
720 for name, _
in _SHOOT_BOOL_FIELDS:
721 d[name] = bool(getattr(self, name))
727 def from_dict(cls, d: Dict[str, Any]) ->
"ShootParameters":
728 kwargs: Dict[str, Any] = {}
729 if "phytomer_parameters" in d:
730 kwargs[
"phytomer_parameters"] = PhytomerParameters.from_dict(d[
"phytomer_parameters"])
731 for name, default
in _SHOOT_RPF_FIELDS:
733 kwargs[name] = RandomParameterFloat.from_dict(d[name])
734 for name, default
in _SHOOT_RPI_FIELDS:
736 kwargs[name] = RandomParameterInt.from_dict(d[name])
737 for name, default
in _SHOOT_BOOL_FIELDS:
739 kwargs[name] = bool(d[name])
740 if "child_shoot_types" in d:
741 kwargs[
"child_shoot_types"] = d[
"child_shoot_types"]
745 """Set child shoot types; probabilities must sum to 1 (validated natively)."""
746 if len(labels) != len(probabilities):
747 raise ValueError(
"labels and probabilities must be the same length")
749 raise ValueError(
"labels and probabilities cannot be empty")
750 self.
child_shoot_types = {
"labels": list(labels),
"probabilities": [float(p)
for p
in probabilities]}
757 return {n: float(getattr(obj, n))
for n
in names}
760def _flat_from_dict(cls: Any, d: Dict[str, Any], names: Tuple[str, ...]) -> Any:
762 kwargs = {n: float(d[n])
for n
in names
if n
in d}
764 kwargs.setdefault(n, getattr(base, n))
769 "stem_density",
"stem_carbon_percentage",
"stem_carbohydrate_percentage",
770 "stem_structural_carbon_percentage",
"maturity_age",
"initial_density_ratio",
771 "shoot_root_ratio",
"leaf_total_carbon_percentage",
"SLA",
772 "leaf_carbohydrate_percentage",
"leaf_carbon_percentage",
"total_flower_cost",
773 "fruit_density",
"fruit_carbon_percentage",
"r_m_w_20",
"r_m_r_20",
774 "living_wood_fraction",
"growth_respiration_fraction",
"carbohydrate_abortion_threshold",
775 "carbohydrate_pruning_threshold",
"bud_death_threshold_days",
"branch_death_threshold_days",
776 "carbohydrate_phyllochron_threshold",
"carbohydrate_vegetative_break_threshold",
777 "carbohydrate_growth_threshold",
"starch_sequestration_ratio",
778 "carbohydrate_transfer_threshold_down",
"carbohydrate_transfer_threshold_up",
779 "carbon_conductance_down",
"carbon_conductance_up",
785 """Flat carbohydrate-model parameters. Defaults mirror the Helios C++ defaults;
786 prefer ``CarbohydrateParameters.from_dict(pa.getDefaultCarbohydrateParameters())``."""
788 stem_density: float = 675000.0
789 stem_carbon_percentage: float = 0.457
790 stem_carbohydrate_percentage: float = 1 - (1 / 1.14)
791 stem_structural_carbon_percentage: float = 0.457 - (1 - (1 / 1.14))
792 maturity_age: float = 120.0
793 initial_density_ratio: float = 0.25
794 shoot_root_ratio: float = 3.0
795 leaf_total_carbon_percentage: float = 0.453
796 SLA: float = 9.2 / 10000 / 0.453 * 12.01
797 leaf_carbohydrate_percentage: float = 1 - (1 / 1.13)
798 leaf_carbon_percentage: float = 0.453
799 total_flower_cost: float = 8.33e-4
800 fruit_density: float = 525000.0
801 fruit_carbon_percentage: float = 0.475
802 r_m_w_20: float = 5.25164e-05
803 r_m_r_20: float = 5.25164e-03
804 living_wood_fraction: float = 0.5
805 growth_respiration_fraction: float = 0.211
806 carbohydrate_abortion_threshold: float = 0.1
807 carbohydrate_pruning_threshold: float = 0.025
808 bud_death_threshold_days: float = 2.0
809 branch_death_threshold_days: float = 5.0
810 carbohydrate_phyllochron_threshold: float = 0.05
811 carbohydrate_vegetative_break_threshold: float = 0.05
812 carbohydrate_growth_threshold: float = 0.2
813 starch_sequestration_ratio: float = 0.025
814 carbohydrate_transfer_threshold_down: float = 0.025
815 carbohydrate_transfer_threshold_up: float = 0.04
816 carbon_conductance_down: float = 0.95
817 carbon_conductance_up: float = 0.95 * 0.5
823 def from_dict(cls, d: Dict[str, Any]) ->
"CarbohydrateParameters":
828 "target_leaf_N_area",
"minimum_leaf_N_area",
"root_allocation_fraction",
829 "max_N_accumulation_rate",
"leaf_remobilization_rate",
"leaf_remobilization_efficiency",
830 "leaf_senescence_duration_fraction",
"stress_senescence_advance_fraction",
"fruit_N_area",
836 """Flat nitrogen-model parameters. Prefer
837 ``NitrogenParameters.from_dict(pa.getDefaultNitrogenParameters())``."""
839 target_leaf_N_area: float = 1.5
840 minimum_leaf_N_area: float = 0.5
841 root_allocation_fraction: float = 0.15
842 max_N_accumulation_rate: float = 0.1
843 leaf_remobilization_rate: float = 0.03
844 leaf_remobilization_efficiency: float = 0.70
845 leaf_senescence_duration_fraction: float = 0.25
846 stress_senescence_advance_fraction: float = 0.30
847 fruit_N_area: float = 1.0
849 def to_dict(self) -> Dict[str, float]:
853 def from_dict(cls, d: Dict[str, Any]) ->
"NitrogenParameters":
State of a vegetative or floral bud.
Flat carbohydrate-model parameters.
"CarbohydrateParameters" from_dict(cls, Dict[str, Any] d)
Dict[str, float] to_dict(self)
Optional fruit_prototype_function
RandomParameterFloat roll
Dict[str, Any] to_dict(self)
RandomParameterFloat fruit_prototype_scale
RandomParameterFloat pitch
Optional flower_prototype_function
RandomParameterInt flowers_per_peduncle
RandomParameterFloat fruit_gravity_factor_fraction
"InflorescenceParameters" from_dict(cls, Dict[str, Any] d)
RandomParameterFloat flower_offset
RandomParameterFloat flower_prototype_scale
RandomParameterFloat inflorescence_maturity_period
"InternodeParameters" from_dict(cls, Dict[str, Any] d)
RandomParameterFloat pitch
RandomParameterInt max_floral_buds_per_petiole
RandomParameterFloat radius_initial
Dict[str, Any] to_dict(self)
RandomParameterInt max_vegetative_buds_per_petiole
RandomParameterFloat phyllotactic_angle
RandomParameterFloat intercalary_leaflet_scale
RandomParameterFloat leaflet_offset
RandomParameterFloat prototype_scale
RandomParameterFloat roll
RandomParameterFloat pitch
"LeafParameters" from_dict(cls, Dict[str, Any] d)
RandomParameterInt leaves_per_petiole
RandomParameterFloat leaflet_scale
Dict[str, Any] to_dict(self)
RandomParameterFloat longitudinal_curvature
Optional prototype_function
None _warn_if_deprecated_buckle_set(self)
Warn when a deprecated buckle parameter carries a non-zero value.
RandomParameterFloat flexibility
Dict[str, Any] to_dict(self)
RandomParameterFloat leaf_buckle_length
"LeafPrototype" from_dict(cls, Dict[str, Any] d)
RandomParameterFloat flexibility_aging_max
RandomParameterFloat wave_period
RandomParameterFloat longitudinal_curvature_exponent
RandomParameterFloat leaf_aspect_ratio
RandomParameterFloat midrib_fold_fraction
RandomParameterFloat wave_amplitude
RandomParameterFloat lateral_curvature
RandomParameterFloat leaf_buckle_angle
RandomParameterFloat petiole_roll
RandomParameterFloat flexibility_aging
RandomParameterFloat flexibility_taper
Flat nitrogen-model parameters.
RandomParameterFloat pitch
RandomParameterFloat curvature
RandomParameterFloat length
RandomParameterFloat roll
Dict[str, Any] to_dict(self)
RandomParameterFloat radius
"PeduncleParameters" from_dict(cls, Dict[str, Any] d)
Dict[str, Any] to_dict(self)
RandomParameterFloat flexibility
"PetioleParameters" from_dict(cls, Dict[str, Any] d)
RandomParameterFloat flexibility_aging
RandomParameterFloat length
RandomParameterFloat curvature
RandomParameterFloat pitch
int petioles_per_internode
RandomParameterFloat radius
RandomParameterFloat taper
Dict[str, Any] to_dict(self)
PetioleParameters petiole
InternodeParameters internode
PeduncleParameters peduncle
"PhytomerParameters" from_dict(cls, Dict[str, Any] d)
InflorescenceParameters inflorescence
A float-valued parameter with a sampling distribution.
"RandomParameterFloat" from_dict(cls, Dict[str, Any] d)
"RandomParameterFloat" normal(cls, float mean, float std_dev)
"RandomParameterFloat" weibull(cls, float shape, float scale)
Dict[str, Any] to_dict(self)
"RandomParameterFloat" uniform(cls, float min_val, float max_val)
"RandomParameterFloat" constant(cls, float value)
An int-valued parameter with a sampling distribution.
"RandomParameterInt" from_dict(cls, Dict[str, Any] d)
Dict[str, Any] to_dict(self)
"RandomParameterInt" discrete(cls, List[int] values)
"RandomParameterInt" uniform(cls, int min_val, int max_val)
"RandomParameterInt" constant(cls, int value)
None define_child_shoot_types(self, List[str] labels, List[float] probabilities)
Set child shoot types; probabilities must sum to 1 (validated natively).
Dict[str, Any] to_dict(self)
Optional child_shoot_types
PhytomerParameters phytomer_parameters
"ShootParameters" from_dict(cls, Dict[str, Any] d)
Vec3 _vec3_from_dict(Dict[str, Any] d, Vec3 default)
Any _flat_from_dict(Any cls, Dict[str, Any] d, Tuple[str,...] names)
Dict[str, float] _vec3_to_dict(Vec3 v)
Dict[str, float] _flat_to_dict(Any obj, Tuple[str,...] names)
RandomParameterInt _rpi(Dict[str, Any] d, str key, RandomParameterInt default)
Dict[str, float] _color_to_dict(Color c)
Color _color_from_dict(Dict[str, Any] d, Color default)
RandomParameterFloat _rpf(Dict[str, Any] d, str key, RandomParameterFloat default)