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". The C++ ``shared_ptr<Phytomer>`` creation and
30 callback functions are not exposable from Python and are not represented here.
31* Child shoot types round-trip in both directions as of helios-core 1.3.84; see
32 :attr:`ShootParameters.child_shoot_types`.
35from __future__
import annotations
38from dataclasses
import dataclass, field
39from enum
import IntEnum
40from typing
import Any, Dict, List, Optional, Tuple
44 "RandomParameterFloat",
48 "InternodeParameters",
52 "InflorescenceParameters",
55 "CarbohydrateParameters",
57 "LEAF_EXPANSION_RATE_UNSET",
65LEAF_EXPANSION_RATE_UNSET = -1.0
72 """State of a vegetative or floral bud, mirroring the C++ ``BudState`` enum.
74 The values are the wire format passed to the native library, so they match the
75 C++ enumerators (``BUD_DORMANT`` through ``BUD_DEAD``) one for one.
77 ``DEAD`` means the bud will produce nothing further. It is set both when a bud is
78 killed -- by :meth:`~pyhelios.PlantArchitecture.PlantArchitecture.removeShootVegetativeBuds`
79 or by a failed bud-break draw -- and when a bud has *already broken into a child
80 shoot*. A count of dead buds is therefore not a count of killed buds; to test
81 whether a shoot can still grow, count the live states instead.
97 """A float-valued parameter with a sampling distribution.
99 Use the classmethod constructors (:meth:`constant`, :meth:`uniform`,
100 :meth:`normal`, :meth:`weibull`) rather than constructing directly.
103 distribution: str =
"constant"
104 parameters: List[float] = field(default_factory=
lambda: [0.0])
107 def constant(cls, value: float) ->
"RandomParameterFloat":
108 return cls(
"constant", [float(value)])
111 def uniform(cls, min_val: float, max_val: float) ->
"RandomParameterFloat":
112 if min_val > max_val:
113 raise ValueError(f
"min_val ({min_val}) must be <= max_val ({max_val})")
114 return cls(
"uniform", [float(min_val), float(max_val)])
117 def normal(cls, mean: float, std_dev: float) ->
"RandomParameterFloat":
119 raise ValueError(f
"std_dev ({std_dev}) must be >= 0")
120 return cls(
"normal", [float(mean), float(std_dev)])
123 def weibull(cls, shape: float, scale: float) ->
"RandomParameterFloat":
125 raise ValueError(f
"shape ({shape}) must be > 0")
127 raise ValueError(f
"scale ({scale}) must be > 0")
128 return cls(
"weibull", [float(shape), float(scale)])
134 def from_dict(cls, d: Dict[str, Any]) ->
"RandomParameterFloat":
135 return cls(str(d[
"distribution"]), [float(p)
for p
in d[
"parameters"]])
140 """An int-valued parameter with a sampling distribution."""
142 distribution: str =
"constant"
143 parameters: List[int] = field(default_factory=
lambda: [0])
146 def constant(cls, value: int) ->
"RandomParameterInt":
147 return cls(
"constant", [int(value)])
150 def uniform(cls, min_val: int, max_val: int) ->
"RandomParameterInt":
151 if min_val > max_val:
152 raise ValueError(f
"min_val ({min_val}) must be <= max_val ({max_val})")
153 return cls(
"uniform", [int(min_val), int(max_val)])
156 def discrete(cls, values: List[int]) ->
"RandomParameterInt":
158 raise ValueError(
"values list cannot be empty")
159 return cls(
"discretevalues", [int(v)
for v
in values])
166 def from_dict(cls, d: Dict[str, Any]) ->
"RandomParameterInt":
167 return cls(str(d[
"distribution"]), [int(round(float(p)))
for p
in d[
"parameters"]])
172RandomParameter = RandomParameterFloat
178Color = Tuple[float, float, float]
179Vec3 = Tuple[float, float, float]
182def _rpf(d: Dict[str, Any], key: str, default: RandomParameterFloat) -> RandomParameterFloat:
183 return RandomParameterFloat.from_dict(d[key])
if key
in d
else default
186def _rpi(d: Dict[str, Any], key: str, default: RandomParameterInt) -> RandomParameterInt:
187 return RandomParameterInt.from_dict(d[key])
if key
in d
else default
191 return {
"r": float(c[0]),
"g": float(c[1]),
"b": float(c[2])}
195 return (float(d.get(
"r", default[0])), float(d.get(
"g", default[1])), float(d.get(
"b", default[2])))
199 return {
"x": float(v[0]),
"y": float(v[1]),
"z": float(v[2])}
203 return (float(d.get(
"x", default[0])), float(d.get(
"y", default[1])), float(d.get(
"z", default[2])))
211 leaf_aspect_ratio: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(1.0))
212 midrib_fold_fraction: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.0))
213 longitudinal_curvature: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.0))
214 lateral_curvature: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.0))
215 petiole_roll: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.0))
216 wave_period: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.0))
217 wave_amplitude: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.0))
218 longitudinal_curvature_exponent: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(4.0))
219 flexibility: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.0))
220 flexibility_taper: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(1.0))
221 flexibility_aging: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.0))
222 flexibility_aging_max: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(4.0))
226 leaf_buckle_length: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.0))
229 leaf_buckle_angle: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.0))
230 leaf_offset: Vec3 = (0.0, 0.0, 0.0)
231 subdivisions: int = 1
232 unique_prototypes: int = 1
233 build_petiolule: bool =
False
234 OBJ_model_file: str =
""
235 leaf_texture_file: Dict[int, str] = field(default_factory=dict)
236 prototype_function: Optional[str] =
None
239 """Warn when a deprecated buckle parameter carries a non-zero value.
241 helios-core 1.3.84 replaced the fixed-station kink these produced with the
242 continuous self-weight droop of :attr:`flexibility`. The buckle values are still
243 converted to an equivalent flexibility natively, but only while :attr:`flexibility`
244 is zero, so a plant setting both silently ignores the buckle pair.
246 for name
in (
"leaf_buckle_length",
"leaf_buckle_angle"):
247 param = getattr(self, name)
248 if any(v != 0.0
for v
in param.parameters):
250 f
"LeafPrototype.{name} is deprecated since helios-core 1.3.84 and is "
251 "superseded by LeafPrototype.flexibility, which bends the leaf continuously "
252 "under its own weight as it grows rather than kinking it at a fixed station. "
253 "Set flexibility (and optionally flexibility_taper / flexibility_aging) instead; "
254 "the buckle value is converted to an equivalent flexibility only while "
255 "flexibility is left at zero.",
260 def to_dict(self) -> Dict[str, Any]:
287 def from_dict(cls, d: Dict[str, Any]) ->
"LeafPrototype":
289 tex = d.get(
"leaf_texture_file", {})
291 leaf_aspect_ratio=
_rpf(d,
"leaf_aspect_ratio", base.leaf_aspect_ratio),
292 midrib_fold_fraction=
_rpf(d,
"midrib_fold_fraction", base.midrib_fold_fraction),
293 longitudinal_curvature=
_rpf(d,
"longitudinal_curvature", base.longitudinal_curvature),
294 lateral_curvature=
_rpf(d,
"lateral_curvature", base.lateral_curvature),
295 petiole_roll=
_rpf(d,
"petiole_roll", base.petiole_roll),
296 wave_period=
_rpf(d,
"wave_period", base.wave_period),
297 wave_amplitude=
_rpf(d,
"wave_amplitude", base.wave_amplitude),
298 longitudinal_curvature_exponent=
_rpf(d,
"longitudinal_curvature_exponent", base.longitudinal_curvature_exponent),
299 flexibility=
_rpf(d,
"flexibility", base.flexibility),
300 flexibility_taper=
_rpf(d,
"flexibility_taper", base.flexibility_taper),
301 flexibility_aging=
_rpf(d,
"flexibility_aging", base.flexibility_aging),
302 flexibility_aging_max=
_rpf(d,
"flexibility_aging_max", base.flexibility_aging_max),
303 leaf_buckle_length=
_rpf(d,
"leaf_buckle_length", base.leaf_buckle_length),
304 leaf_buckle_angle=
_rpf(d,
"leaf_buckle_angle", base.leaf_buckle_angle),
305 leaf_offset=
_vec3_from_dict(d[
"leaf_offset"], base.leaf_offset)
if "leaf_offset" in d
else base.leaf_offset,
306 subdivisions=int(d.get(
"subdivisions", base.subdivisions)),
307 unique_prototypes=int(d.get(
"unique_prototypes", base.unique_prototypes)),
308 build_petiolule=bool(d.get(
"build_petiolule", base.build_petiolule)),
309 OBJ_model_file=str(d.get(
"OBJ_model_file", base.OBJ_model_file)),
310 leaf_texture_file={int(k): str(v)
for k, v
in tex.items()},
311 prototype_function=(d.get(
"prototype_function")
or None),
320 pitch: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(20.0))
321 phyllotactic_angle: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(137.5))
322 radius_initial: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.001))
323 max_vegetative_buds_per_petiole: RandomParameterInt = field(default_factory=
lambda: RandomParameterInt.constant(0))
324 max_floral_buds_per_petiole: RandomParameterInt = field(default_factory=
lambda: RandomParameterInt.constant(0))
325 color: Color = (0.0, 0.0, 0.0)
326 image_texture: str =
""
327 length_segments: int = 1
328 radial_subdivisions: int = 7
344 def from_dict(cls, d: Dict[str, Any]) ->
"InternodeParameters":
347 pitch=
_rpf(d,
"pitch", base.pitch),
348 phyllotactic_angle=
_rpf(d,
"phyllotactic_angle", base.phyllotactic_angle),
349 radius_initial=
_rpf(d,
"radius_initial", base.radius_initial),
350 max_vegetative_buds_per_petiole=
_rpi(d,
"max_vegetative_buds_per_petiole", base.max_vegetative_buds_per_petiole),
351 max_floral_buds_per_petiole=
_rpi(d,
"max_floral_buds_per_petiole", base.max_floral_buds_per_petiole),
352 color=
_color_from_dict(d[
"color"], base.color)
if "color" in d
else base.color,
353 image_texture=str(d.get(
"image_texture", base.image_texture)),
354 length_segments=int(d.get(
"length_segments", base.length_segments)),
355 radial_subdivisions=int(d.get(
"radial_subdivisions", base.radial_subdivisions)),
361 petioles_per_internode: int = 1
362 pitch: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(90.0))
363 radius: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.001))
364 length: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.05))
365 curvature: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.0))
366 taper: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.0))
367 color: Color = (0.0, 0.0, 0.0)
368 length_segments: int = 1
369 radial_subdivisions: int = 7
374 flexibility: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.0))
378 flexibility_aging: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.0))
396 def from_dict(cls, d: Dict[str, Any]) ->
"PetioleParameters":
399 petioles_per_internode=int(d.get(
"petioles_per_internode", base.petioles_per_internode)),
400 pitch=
_rpf(d,
"pitch", base.pitch),
401 radius=
_rpf(d,
"radius", base.radius),
402 length=
_rpf(d,
"length", base.length),
403 curvature=
_rpf(d,
"curvature", base.curvature),
404 taper=
_rpf(d,
"taper", base.taper),
405 color=
_color_from_dict(d[
"color"], base.color)
if "color" in d
else base.color,
406 length_segments=int(d.get(
"length_segments", base.length_segments)),
407 radial_subdivisions=int(d.get(
"radial_subdivisions", base.radial_subdivisions)),
408 flexibility=
_rpf(d,
"flexibility", base.flexibility),
409 flexibility_aging=
_rpf(d,
"flexibility_aging", base.flexibility_aging),
415 leaves_per_petiole: RandomParameterInt = field(default_factory=
lambda: RandomParameterInt.constant(1))
416 pitch: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.0))
417 yaw: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.0))
418 roll: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.0))
419 leaflet_offset: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.0))
420 leaflet_scale: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(1.0))
424 intercalary_leaflet_scale: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.0))
425 prototype_scale: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.05))
426 prototype: LeafPrototype = field(default_factory=LeafPrototype)
442 def from_dict(cls, d: Dict[str, Any]) ->
"LeafParameters":
445 leaves_per_petiole=
_rpi(d,
"leaves_per_petiole", base.leaves_per_petiole),
446 pitch=
_rpf(d,
"pitch", base.pitch),
447 yaw=
_rpf(d,
"yaw", base.yaw),
448 roll=
_rpf(d,
"roll", base.roll),
449 leaflet_offset=
_rpf(d,
"leaflet_offset", base.leaflet_offset),
450 leaflet_scale=
_rpf(d,
"leaflet_scale", base.leaflet_scale),
451 intercalary_leaflet_scale=
_rpf(d,
"intercalary_leaflet_scale", base.intercalary_leaflet_scale),
452 prototype_scale=
_rpf(d,
"prototype_scale", base.prototype_scale),
453 prototype=LeafPrototype.from_dict(d[
"prototype"])
if "prototype" in d
else base.prototype,
459 length: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.05))
460 radius: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.001))
461 pitch: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.0))
462 roll: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.0))
463 curvature: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.0))
464 color: Color = (0.0, 0.0, 0.0)
465 length_segments: int = 3
466 radial_subdivisions: int = 7
481 def from_dict(cls, d: Dict[str, Any]) ->
"PeduncleParameters":
484 length=
_rpf(d,
"length", base.length),
485 radius=
_rpf(d,
"radius", base.radius),
486 pitch=
_rpf(d,
"pitch", base.pitch),
487 roll=
_rpf(d,
"roll", base.roll),
488 curvature=
_rpf(d,
"curvature", base.curvature),
489 color=
_color_from_dict(d[
"color"], base.color)
if "color" in d
else base.color,
490 length_segments=int(d.get(
"length_segments", base.length_segments)),
491 radial_subdivisions=int(d.get(
"radial_subdivisions", base.radial_subdivisions)),
497 flowers_per_peduncle: RandomParameterInt = field(default_factory=
lambda: RandomParameterInt.constant(1))
498 flower_offset: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.0))
499 pitch: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.0))
500 roll: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.0))
501 flower_prototype_scale: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.0075))
502 fruit_prototype_scale: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.0075))
503 fruit_gravity_factor_fraction: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.0))
504 unique_prototypes: int = 1
509 inflorescence_maturity_period: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(-1.0))
510 flower_prototype_function: Optional[str] =
None
511 fruit_prototype_function: Optional[str] =
None
529 def from_dict(cls, d: Dict[str, Any]) ->
"InflorescenceParameters":
532 flowers_per_peduncle=
_rpi(d,
"flowers_per_peduncle", base.flowers_per_peduncle),
533 flower_offset=
_rpf(d,
"flower_offset", base.flower_offset),
534 pitch=
_rpf(d,
"pitch", base.pitch),
535 roll=
_rpf(d,
"roll", base.roll),
536 flower_prototype_scale=
_rpf(d,
"flower_prototype_scale", base.flower_prototype_scale),
537 fruit_prototype_scale=
_rpf(d,
"fruit_prototype_scale", base.fruit_prototype_scale),
538 fruit_gravity_factor_fraction=
_rpf(d,
"fruit_gravity_factor_fraction", base.fruit_gravity_factor_fraction),
539 unique_prototypes=int(d.get(
"unique_prototypes", base.unique_prototypes)),
540 inflorescence_maturity_period=
_rpf(d,
"inflorescence_maturity_period", base.inflorescence_maturity_period),
541 flower_prototype_function=(d.get(
"flower_prototype_function")
or None),
542 fruit_prototype_function=(d.get(
"fruit_prototype_function")
or None),
548 internode: InternodeParameters = field(default_factory=InternodeParameters)
549 petiole: PetioleParameters = field(default_factory=PetioleParameters)
550 leaf: LeafParameters = field(default_factory=LeafParameters)
551 peduncle: PeduncleParameters = field(default_factory=PeduncleParameters)
552 inflorescence: InflorescenceParameters = field(default_factory=InflorescenceParameters)
564 def from_dict(cls, d: Dict[str, Any]) ->
"PhytomerParameters":
567 internode=InternodeParameters.from_dict(d[
"internode"])
if "internode" in d
else base.internode,
568 petiole=PetioleParameters.from_dict(d[
"petiole"])
if "petiole" in d
else base.petiole,
569 leaf=LeafParameters.from_dict(d[
"leaf"])
if "leaf" in d
else base.leaf,
570 peduncle=PeduncleParameters.from_dict(d[
"peduncle"])
if "peduncle" in d
else base.peduncle,
571 inflorescence=InflorescenceParameters.from_dict(d[
"inflorescence"])
if "inflorescence" in d
else base.inflorescence,
581 (
"girth_area_factor", 0.0),
582 (
"insertion_angle_tip", 20.0),
583 (
"insertion_angle_decay_rate", 0.0),
584 (
"internode_length_max", 0.02),
585 (
"internode_length_min", 0.002),
586 (
"internode_length_decay_rate", 0.0),
589 (
"gravitropic_curvature", 0.0),
591 (
"phyllochron_min", 2.0),
592 (
"elongation_rate_max", 0.2),
593 (
"leaf_expansion_rate_max", LEAF_EXPANSION_RATE_UNSET),
594 (
"vegetative_bud_break_probability_min", 0.0),
595 (
"vegetative_bud_break_probability_max", 1.0),
596 (
"vegetative_bud_break_probability_decay_rate", -0.5),
597 (
"flower_bud_break_probability", 0.0),
598 (
"fruit_set_probability", 0.0),
599 (
"vegetative_bud_break_time", 5.0),
603 (
"max_nodes_per_season", 9999),
604 (
"max_terminal_floral_buds", 0),
606_SHOOT_BOOL_FIELDS = (
607 (
"flowers_require_dormancy",
False),
608 (
"growth_requires_dormancy",
False),
609 (
"determinate_shoot_growth",
True),
615 phytomer_parameters: PhytomerParameters = field(default_factory=PhytomerParameters)
618 girth_area_factor: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.0))
619 insertion_angle_tip: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(20.0))
620 insertion_angle_decay_rate: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.0))
621 internode_length_max: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.02))
622 internode_length_min: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.002))
623 internode_length_decay_rate: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.0))
624 base_roll: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.0))
625 base_yaw: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.0))
626 gravitropic_curvature: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.0))
627 tortuosity: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.0))
628 phyllochron_min: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(2.0))
629 elongation_rate_max: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.2))
638 leaf_expansion_rate_max: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(LEAF_EXPANSION_RATE_UNSET))
639 vegetative_bud_break_probability_min: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.0))
640 vegetative_bud_break_probability_max: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(1.0))
641 vegetative_bud_break_probability_decay_rate: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(-0.5))
642 flower_bud_break_probability: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.0))
643 fruit_set_probability: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(0.0))
644 vegetative_bud_break_time: RandomParameterFloat = field(default_factory=
lambda: RandomParameterFloat.constant(5.0))
647 max_nodes: RandomParameterInt = field(default_factory=
lambda: RandomParameterInt.constant(10))
648 max_nodes_per_season: RandomParameterInt = field(default_factory=
lambda: RandomParameterInt.constant(9999))
649 max_terminal_floral_buds: RandomParameterInt = field(default_factory=
lambda: RandomParameterInt.constant(0))
652 flowers_require_dormancy: bool =
False
653 growth_requires_dormancy: bool =
False
654 determinate_shoot_growth: bool =
True
662 child_shoot_types: Optional[Dict[str, Any]] =
None
666 for name, _
in _SHOOT_RPF_FIELDS:
667 d[name] = getattr(self, name).
to_dict()
668 for name, _
in _SHOOT_RPI_FIELDS:
669 d[name] = getattr(self, name).
to_dict()
670 for name, _
in _SHOOT_BOOL_FIELDS:
671 d[name] = bool(getattr(self, name))
677 def from_dict(cls, d: Dict[str, Any]) ->
"ShootParameters":
678 kwargs: Dict[str, Any] = {}
679 if "phytomer_parameters" in d:
680 kwargs[
"phytomer_parameters"] = PhytomerParameters.from_dict(d[
"phytomer_parameters"])
681 for name, default
in _SHOOT_RPF_FIELDS:
683 kwargs[name] = RandomParameterFloat.from_dict(d[name])
684 for name, default
in _SHOOT_RPI_FIELDS:
686 kwargs[name] = RandomParameterInt.from_dict(d[name])
687 for name, default
in _SHOOT_BOOL_FIELDS:
689 kwargs[name] = bool(d[name])
690 if "child_shoot_types" in d:
691 kwargs[
"child_shoot_types"] = d[
"child_shoot_types"]
695 """Set child shoot types; probabilities must sum to 1 (validated natively)."""
696 if len(labels) != len(probabilities):
697 raise ValueError(
"labels and probabilities must be the same length")
699 raise ValueError(
"labels and probabilities cannot be empty")
700 self.
child_shoot_types = {
"labels": list(labels),
"probabilities": [float(p)
for p
in probabilities]}
707 return {n: float(getattr(obj, n))
for n
in names}
710def _flat_from_dict(cls: Any, d: Dict[str, Any], names: Tuple[str, ...]) -> Any:
712 kwargs = {n: float(d[n])
for n
in names
if n
in d}
714 kwargs.setdefault(n, getattr(base, n))
719 "stem_density",
"stem_carbon_percentage",
"stem_carbohydrate_percentage",
720 "stem_structural_carbon_percentage",
"maturity_age",
"initial_density_ratio",
721 "shoot_root_ratio",
"leaf_total_carbon_percentage",
"SLA",
722 "leaf_carbohydrate_percentage",
"leaf_carbon_percentage",
"total_flower_cost",
723 "fruit_density",
"fruit_carbon_percentage",
"r_m_w_20",
"r_m_r_20",
724 "living_wood_fraction",
"growth_respiration_fraction",
"carbohydrate_abortion_threshold",
725 "carbohydrate_pruning_threshold",
"bud_death_threshold_days",
"branch_death_threshold_days",
726 "carbohydrate_phyllochron_threshold",
"carbohydrate_vegetative_break_threshold",
727 "carbohydrate_growth_threshold",
"starch_sequestration_ratio",
728 "carbohydrate_transfer_threshold_down",
"carbohydrate_transfer_threshold_up",
729 "carbon_conductance_down",
"carbon_conductance_up",
735 """Flat carbohydrate-model parameters. Defaults mirror the Helios C++ defaults;
736 prefer ``CarbohydrateParameters.from_dict(pa.getDefaultCarbohydrateParameters())``."""
738 stem_density: float = 675000.0
739 stem_carbon_percentage: float = 0.457
740 stem_carbohydrate_percentage: float = 1 - (1 / 1.14)
741 stem_structural_carbon_percentage: float = 0.457 - (1 - (1 / 1.14))
742 maturity_age: float = 120.0
743 initial_density_ratio: float = 0.25
744 shoot_root_ratio: float = 3.0
745 leaf_total_carbon_percentage: float = 0.453
746 SLA: float = 9.2 / 10000 / 0.453 * 12.01
747 leaf_carbohydrate_percentage: float = 1 - (1 / 1.13)
748 leaf_carbon_percentage: float = 0.453
749 total_flower_cost: float = 8.33e-4
750 fruit_density: float = 525000.0
751 fruit_carbon_percentage: float = 0.475
752 r_m_w_20: float = 5.25164e-05
753 r_m_r_20: float = 5.25164e-03
754 living_wood_fraction: float = 0.5
755 growth_respiration_fraction: float = 0.211
756 carbohydrate_abortion_threshold: float = 0.1
757 carbohydrate_pruning_threshold: float = 0.025
758 bud_death_threshold_days: float = 2.0
759 branch_death_threshold_days: float = 5.0
760 carbohydrate_phyllochron_threshold: float = 0.05
761 carbohydrate_vegetative_break_threshold: float = 0.05
762 carbohydrate_growth_threshold: float = 0.2
763 starch_sequestration_ratio: float = 0.025
764 carbohydrate_transfer_threshold_down: float = 0.025
765 carbohydrate_transfer_threshold_up: float = 0.04
766 carbon_conductance_down: float = 0.95
767 carbon_conductance_up: float = 0.95 * 0.5
773 def from_dict(cls, d: Dict[str, Any]) ->
"CarbohydrateParameters":
778 "target_leaf_N_area",
"minimum_leaf_N_area",
"root_allocation_fraction",
779 "max_N_accumulation_rate",
"leaf_remobilization_efficiency",
780 "remobilization_age_threshold",
"fruit_N_area",
786 """Flat nitrogen-model parameters. Prefer
787 ``NitrogenParameters.from_dict(pa.getDefaultNitrogenParameters())``."""
789 target_leaf_N_area: float = 1.5
790 minimum_leaf_N_area: float = 0.5
791 root_allocation_fraction: float = 0.15
792 max_N_accumulation_rate: float = 0.1
793 leaf_remobilization_efficiency: float = 0.70
794 remobilization_age_threshold: float = 0.70
795 fruit_N_area: float = 1.0
797 def to_dict(self) -> Dict[str, float]:
801 def from_dict(cls, d: Dict[str, Any]) ->
"NitrogenParameters":
State of a vegetative or floral bud, mirroring the C++ BudState enum.
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)