|
1 | 1 | # Changelog |
2 | 2 |
|
| 3 | +# [1.3.72] 2026-05-06 |
| 4 | + |
| 5 | +## Core |
| 6 | +- Added `Context::deleteTimeseriesVariable()` to delete a single timeseries variable and all of its data points, identified by label. Issues a non-fatal warning if the variable does not exist; complements the existing `clearTimeseriesData()` (which removes all variables) and `updateTimeseriesData()` (which modifies a single point). |
| 7 | + |
| 8 | +## Plant Architecture |
| 9 | +- Added leaf-to-fruit nitrogen translocation in `removeFruitNitrogen()`. Per-plant fruit demand is now aggregated across all fruiting buds and first satisfied from the available pool; any remaining shortfall is drawn from leaves, with old leaves (age ≥ `remobilization_age_threshold`) donating first and young leaves serving as a fallback. Per-leaf availability uses the same `(current_N_area − minimum_leaf_N_area) × leaf_remobilization_efficiency` reserve as leaf-to-leaf remobilization, distributed proportionally across source leaves; no stress-acceleration multiplier is applied because withdrawal is governed by fruit demand rather than stress signaling. Residual demand that even leaves cannot cover is silently absorbed, preserving the preexisting fail-soft accounting. |
| 10 | +- Petiole geometry is now suppressed when either `petiole.radius` or `petiole.length` is zero (previously only radius=0 suppressed it); in that case no petiole tube object is created and leaves attach directly at the internode tip. Vertices are still computed so leaf orientation is well-defined. |
| 11 | +- Added `Phytomer::rotatePetiole(petiole_index, AxisRotation)` for solid-body rotation of a petiole and all leaves anchored to it (the terminal floral bud and its peduncle/inflorescence, anchored to the internode, are unaffected). Pitch rotates about the stored `petiole_rotation_axis` using the construction-time `abs(pitch)` convention, yaw rotates about the internode axis, and roll rotates about the petiole's current length axis. Used internally by the sorghum flag-leaf rule, which now sets the terminal phytomer petiole pitch to a small non-zero value (5°) to keep the rotation axis well-defined and applies an additional +25° solid-body petiole rotation so the flag leaf clears the panicle. |
| 12 | +- Reworked leaf orientation on curved stems: the legacy roll term `(acos(internode_axis.z) − roll)` is removed and replaced by a curvature-aware blade-up correction applied after the pitch/yaw chain. The leaf is rolled about its own length axis (the petiole tip axis) by an angle scaled by `min(petiole_length / leaf_size, 1)` and clamped below 90°, so leaves on long petioles hang at their natural angle while leaves that hug the stem follow stem curvature. Also fixed leaf-wave vertex displacement to be applied along the post-rotation surface normal rather than along world Z, so wave bumps no longer flatten visually past the buckle. |
| 13 | +- Tightened maize and sorghum library parameters: widened phyllotactic angle (170–190° → 155–205°), increased leaf buckle angles, increased sorghum wave amplitude, lengthened maize wave period (0.1 → 0.15), and relaxed sorghum gravitropic curvature (−1000…−400 → −800…−200) for less-extreme stem droop. |
| 14 | + |
| 15 | +## Leaf Optics |
| 16 | +- Added Fluspect-B SIF parameters `V2Z` (violaxanthin↔zeaxanthin de-epoxidation state) and `fqe` (intrinsic fluorescence quantum efficiency scalar) to `LeafOpticsProperties` and `LeafOpticsProperties_Nauto`. They are ignored by the PROSPECT reflectance/transmittance calculation; on every `LeafOptics::run()` call (and per-bin in the nitrogen-auto path), Helios now writes an 11-element `fluspect_biochem_<label>` global-data vector containing `[Cab, Cca, Cw, Cdm, Cs, Cant, Cp, Cbc, N, V2Z, fqe]` and stamps the primitive data label `fluspect_spectrum` on processed UUIDs so the radiation plug-in's SIF pipeline can identify fluorescing leaves and look up their biochemistry by label. |
| 17 | + |
| 18 | +## Photosynthesis |
| 19 | +- Added an implementation of the von Caemmerer (2021) steady-state C4 photosynthesis model, exposed via `setModelType_C4()` and a new `C4ModelCoefficients` struct that mirrors the C3 Farquhar API (four temperature-response setter overloads per rate parameter `Vpmax`/`Vcmax`/`Jmax`/`Rd`/`gm`, plus directly-editable kinetic constants and structural scalars). Includes a fail-fast species library (`SetariaViridis_vC2021`, `GenericC4_vC2000`, `Maize_Massad2007`) accessed via `setC4CoefficientsFromLibrary()`, per-UUID and per-material coefficient assignment with full material-data serialization, a `setCm()` override for direct mesophyll-CO2 prescription (testing/validation against the paper's reference spreadsheet), and new optional output primitive data `Cm` (mesophyll cytosolic CO2) and `Vp` (PEP carboxylation rate). The `limitation_state` output uses 1 = enzyme-limited and 2 = electron-transport-limited for the C4 model. |
| 20 | +- Added mesophyll conductance `gm` to the Farquhar C3 model via four `setMesophyllConductance_gm()` overloads on `FarquharModelCoefficients` (constant / Arrhenius / peaked / peaked+dHd) with the same temperature-response framework as the other rate parameters. When `gm` is finite, net assimilation is obtained by solving the Ethier & Livingston (2004) / Sharkey et al. (2007) quadratics derived from `Cc = Ci - A/gm` for the Rubisco- and electron-transport-limited branches; the default value is +infinity, which short-circuits to the legacy `Cc ≡ Ci` path and reproduces the original Farquhar `A` bit-for-bit so existing code and parameter libraries that do not set `gm` are unaffected. |
| 21 | + |
| 22 | +## Radiation |
| 23 | +- Added a solar-induced chlorophyll fluorescence (SIF) modelling pipeline to `RadiationModel`, exposed through a new SIF camera type. Users add a camera via `addSIFCamera()` (taking a list of user-defined emission bands, a `SIFCameraProperties` struct with `excitation_bin_width_nm` and `excitation_scattering_depth`, and standard camera geometry); `isSIFCamera()` queries whether a label was registered as SIF. Helios auto-creates internal excitation bands spanning 400–750 nm at the requested bin width, ray-traces them to populate per-leaf APAR (with optional inter-leaf scattering when the user wants accurate NIR APAR), evaluates a per-leaf Fluspect-B excitation→emission kernel (Vilfan 2016, ported in `FluspectB.cpp`) cached by biochemistry label + bin width, scales by the van der Tol (2014) rate-coefficient quantum yield Φ_F (read from `electron_transport_ratio` and `temperature` primitive data), integrates per emission-band wavelength range, and injects the resulting per-leaf top/bottom source flux (Mf/Mb) into the standard `runBand()` emission loop so escape and reabsorption are 3D-faithful. Excitation bands are piggy-backed onto regular non-SIF dispatches when present, and cached APAR is invalidated when radiative properties change. SIF-flagged emission bands bypass the Stefan-Boltzmann ε+ρ+τ=1 check (still enforcing ρ+τ ≤ 1) and skip the broadband σ·ε·T⁴ fallback for primitives without an SIF source flux, so non-emitting primitives (ground, sensors) do not inject thermal energy into visible/red SIF bands. Each emission band is locked to one excitation bin width — re-flagging it from a second camera with a different width is an error. |
| 24 | +- CMake now disables the Vulkan radiation backend (rather than emitting an unresolvable `-lvulkan` linker flag that broke downstream linking) when no Vulkan loader is installed on Linux/macOS, with a status message pointing at the appropriate install command. |
| 25 | +- `VulkanComputeBackend::copyScatterToRadiation()` now sizes the scatter→radiation memcpy by `launch_band_count` rather than the global `band_count`, fixing an OOB read crash whenever a `runBand` dispatch is a strict subset of all bands (e.g., a SIF emission-only dispatch following an excitation-band dispatch that raised the global band count). |
| 26 | +- `addRadiationCamera()` now warns once per camera (aggregated across all bound bands) when a camera is bound to a band with `scatteringDepth == 0`, since camera pixels for that band would otherwise read as zero with no diagnostic. |
| 27 | +- Spectral-camera auto-exposure now anchors to the 95th percentile mapped to ~0.7 instead of the median mapped to 0.18, so narrow scientific bands (fluorescence, thermal IR, Fraunhofer-line retrievals) where most of the frame reads near zero no longer drive the gain to astronomical values and saturate the subject. |
| 28 | +- `runBand()` now skips cameras whose bound bands don't intersect the current dispatch, avoiding wasted full-camera launches when (e.g.) running PAR with a SIF camera registered. |
| 29 | +- BVH traversal kernels (`traverse_bvh`, `traverse_cwbvh`) drop the unused `hit_prim_type` and `hit_uv` outputs and the corresponding leaf-data load in cwBVH, removing dead reads from every camera/diffuse/direct/pixel-label raygen path. |
| 30 | + |
| 31 | +## Visualizer |
| 32 | +- Fixed `colorContextPrimitivesByObjectData()` (no-arg overload) so that orphan primitives (those with no parent compound object) are colored by the colormap at value 0 — matching the existing behaviour for compound-object primitives whose parent object lacks the data label, and matching the behaviour of `colorContextPrimitivesByData()` for primitives without primitive data. Previously orphans were rendered with their base RGBA, which left ground tiles and other standalone primitives showing through as their default color (e.g., the green from `Context::addTile`) instead of participating in the colormap. |
| 33 | +- Colorbar ticks are now filtered to the colorbar's `[cmin, cmax]` range (with a small epsilon) before placement, so a fixed-range colorbar no longer shows orphan labels at "nice" tick values that `generateNiceTicks()` extends past the data range. |
| 34 | + |
3 | 35 | # [1.3.71] 2026-04-14 |
4 | 36 |
|
5 | 37 | ## Core |
|
0 commit comments