R_d rises with temperature, and is settable (#41) - #86
Conversation
Binding the temperature-response parameters made them writable but not effective. The cache was keyed on (leaf_temp_, atm_o2_kpa_) alone, justified by "same inputs -> bit-identical outputs" -- true while those parameters were unreachable C++ members, false once they were bound. Silent: setting rd_to_vcmax_ratio_ on a solved leaf and re-supplying the same drivers left R_d at 1.44 where a fresh leaf gave 2.88, with assimilation unchanged to every digit. The key now covers every scalar the block reads, which also closes the silent half of hazard 10 for vcmax_25/jmax_25. Golden bit-identical; 2.99 vs 3.00 us/solve interleaved x3. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
R_d_25 is a new parameter in umol m-2 s-1 -- what a caller with a measured respiration sets. Its default is a NaN SENTINEL meaning "derive as rd_to_vcmax_ratio_ * vcmax_25", not a value computed at construction: a computed default would silently decouple respiration from Vcmax for every caller that sets vcmax_25 afterwards, which is what plant does. The response is now Q10 (settable, default 2.0) on that reference, in place of inheriting Vcmax's peaked Arrhenius, which made R_d FALL above the thermal optimum. Moves results at every T except 25 C, where agreement is exact. Measured: -0.2% at 15 C, -12.5% at 35 C, -73% at 40 C, and NO OPERATING POINT at 45 C -- psi_stem_to_ci cannot bracket a root once net assimilation is negative throughout. The compensation-point fallback for exactly that case fires only on the energy-balance path. Golden bit-identical BY CONSTRUCTION: its grid is all at 25 C. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A constant Q10 = 2 was implemented first and measured too aggressive at the top of the range: R_d 4.07 at 40 C, -73% on A, and no operating point by 45 C. That is a constant Q10 used outside where it holds, which is what Tjoelker et al. (2001) measured. Q10(T) = 3.09 - 0.043*(T+25)/2, evaluated at the midpoint as the land-surface literature implements it. Blast radius now +0.1% at 15 C, exact at 25 C, -9.7% at 35 C, -53% at 40 C. Setting the slope to zero recovers a constant Q10 exactly, asserted. 45 C still has no operating point -- the fallback comes next. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The compensation-point fallback was gated on use_energy_balance_ because the prescribed-temperature path "never reaches here". #41 made that false: a Q10 respiration drives assimilation negative across the whole [gamma*, ca] bracket by ~45 C, and the prescribed path then threw where the PM path shut down -- same physics, two outcomes, decided by how leaf_temp was obtained. The gate is not simply removed, which would mask real solver failures: the target is monotone over (gamma*, ca], so "no root exists" is distinguishable from "the solver broke" by the sign at both ends. Only the former shuts down. At 45 C the leaf now reports ci at the compensation point and net A = -R_d = -3.618 instead of aborting. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Wires R_d_25 through leaf_traits()/set_traits() and into the gradient enumeration, where it goes LAST rather than in set_traits' argument order: inserting it at 13 would displace par_kmax and par_resistance, and C++ indexes theta by position. The NA sentinel -- "derive as rd_to_vcmax_ratio_ * vcmax_25" -- is resolved where theta is built rather than carried into it. That fixes the batch path, which read it as a missing trait, and makes R_d_25 differentiable at its default rather than only once a finite value is set, which is what a calibration needs. dA/dR_d_25 = -0.24874 against a central difference of the solve at -0.24868.⚠️ dY/dvcmax_25 is now a PARTIAL at fixed respiration, where it used to carry rd_to_vcmax_ratio_ * dY/dR_d_25 as well. Moves 14 of 60 recorded gradient cells, all of them vcmax_25, by 16-250%; nothing else moves, and the chain rule reconstructs every old value. gradient_golden.tsv regenerated deliberately, with R_d_25 recorded at all five points. Also adds the NEWS entries for the three landed commits that had none. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
rd_tracks_vcmax_ restores R_d = rd_to_vcmax_ratio_ * vcmax_(T), Vcmax's peaked Arrhenius, bit-identically to the pre-#41 model.⚠️ This closes a reproducibility hole we made rather than adding an option. No combination of rd_q10_intercept_ and rd_q10_slope_ produces a PEAKED function, so without it no published plant result computed under the old shape could be regenerated. It also makes the plant revalidation a controlled A/B instead of an argument. In the temperature cache key, because flipping it changes R_d at every temperature but 25 C and nothing else the key mentions -- a key that missed it would take a cache hit and run the whole comparison under whichever shape was seated first. Setting it with a measured R_d_25 is refused rather than resolved. The blast-radius table's "old" column is now the flag rather than an emulation of it; its printed numbers are unchanged. plant binds Leaf's fields by name, so it needs its own line for this one. 3.03-3.04 us/solve on both arms, interleaved x3 from one tree, identical checksums. Golden bit-identical; 496 C++ checks, 940 R. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The inline consumer/main.cpp in cpp-tests.yml initialises theta with a brace list, which compiles and zero-fills whatever it omits. Appending R_d_25 to the enumeration therefore made it pass 0.0 -- a leaf with no respiration -- with no diagnostic, and its arbitrated dA/dvcmax_25 check still carried the pre-#41 total derivative. Both fixed and verified by extracting the two heredocs and building the consumer locally, which is the only way to see this: dA/dvcmax_25 = 0.020934892290554417, matching the recorded gradient, profit unchanged, exit 0. ctest 2/2. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
⚠️ The golden file was blind to every temperature response in the model. Its 288 points were all at 25 C, where the reference values of Vcmax, Jmax and R_d are DEFINED -- so a change to any response curve is inert there by construction. #41 changed R_d's shape at every temperature except 25 C and this file did not move a bit. Now the same state grid at 15 / 25 / 35 / 40 C, with a leaf_temp column. 40 C is included deliberately: it is where the response bites hardest, and the shut-down rows at the hot end pin the limit. Deliberate regeneration, and checkable as an ADDITION: temperature is the OUTERMOST loop, so the 25 C block is BYTE-IDENTICAL to the pre-#41 file in all nine output columns. 864 rows added, none moved. The classification is counted per temperature now, because it moves -- dry-pinned 18/18/3/0 against wet-pinned 24/24/43/80. A hot leaf has less to gain from water, so its optimum presses against the WET bound. The 48 shut-down rows are temperature-independent, as hydraulics rather than heat is what forbids transpiration there. bench_solve deliberately stays at 288 points at 25 C: a timing baseline is only useful against its own history. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
⚠️ Pinning leaf temperature at 25 C would have proved nothing: #41 is inert there by construction, so bit-identicality only says the reference point survived. Both new harnesses vary temperature. Three arms over the 1152-point grid, against origin/master: default at 25 C only 0 of 2592 cells differ rd_tracks_vcmax_ = TRUE, all four temps 0 of 10368 cells differ default, all four temps 6018 of 10368 cells differ The middle arm is the result: with the old shape restored, everything else in #41 -- cache key, settable R_d_25, shut-down exit, R API -- is jointly bit-identical to master. A moves +0.05..+3.5% at 15 C, exactly 0 at 25 C, -5.7..-100% at 35 C, -35..-213% at 40 C; no row changed feasibility. plant, TF24 SCM offspring production: BIT-IDENTICAL at plant's defaults (TF24_Environment pins leaf_temp = 25 with PM off, so #41 is inert there), -7.9% at a 30 C driver, /331 at 35 C, and -47.2% with Penman-Monteith on -- where leaf temperature reaches 62 C, +22 K above air, and Tleaf itself moves through the transpiration coupling. plant's leaf/TF24 tests: 486 checks, 0 failures, and its one error is present identically on the control. compare_with_plant.R still does not run; its header blames the wrong thing and PLAN 41 records the real obstacle. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The measurementsBlast radius of the shape change, at the defaults
The 25 °C row is exact rather than approximate: the reference value defaults to Three arms, over the 1152-point golden grid, against
|
| arm | cells differing | what it isolates |
|---|---|---|
| default, 25 °C only | 0 of 2592 | the control |
rd_tracks_vcmax_ = TRUE, all four temperatures |
0 of 10368 | the shape change alone |
| default, all four temperatures | 6018 of 10368 | the blast radius |
The middle arm is the result of this PR. With the old shape restored, everything
else in #41 — the cache key, the settable R_d_25, the shut-down exit, the whole R
API — is jointly bit-identical to master. That is proved rather than argued, and it
is what the escape hatch was worth adding for.
Assimilation, relative change by temperature:
| T | min | median | max |
|---|---|---|---|
| 15 °C | +0.05% | +0.13% | +3.51% |
| 25 °C | 0 | 0 | 0 |
| 35 °C | −100% | −10.86% | −5.67% |
| 40 °C | −213% | −62.35% | −34.95% |
Per field, worst / median relative: assim 1 / 0.109, profit 1.91 / 0.082,
ci 0.615 / 0.078, psi_stem 0.484 / 0.022, the four flux fields 1 / 0.054.
No row changed feasibility — the 48 shut-down rows per temperature are the same
rows. Of the 864 rows whose A moves, 269 keep their exact operating point: 144
shut down (E = 0), and 125 sit pinned to a bound that temperature does not shift, so
only their carbon balance moves.
plant revalidation, deliberately not pinned at 25 °C
Harness: tests/validate/rd_shape_arms.R. TF24 SCM offspring production, one
species, max_patch_lifetime = 5 — the setup plant's own regression test uses.
| plant configuration | phylloptim master | this branch | change |
|---|---|---|---|
default (leaf_temp = 25, PM off) |
81.857087216483691 | 81.857087216483691 | bit-identical |
leaf_temp driver = 30 °C |
24.283073719078399 | 22.369992929645893 | −7.9% |
leaf_temp driver = 35 °C |
2.1302e-04 | 6.4472e-07 | ÷331 |
| Penman-Monteith on | 2.2034981948510728 | 1.1638497249192270 | −47.2% |
lucky: TF24_Environment sets leaf_temp as a constant extrinsic driver at 25 and
use_energy_balance defaults to 0. So a default plant run is unaffected, by
construction, and the exposure is entirely in the two configurations that leave 25 °C.
19.1 to 62.0 °C — up to +22 K above air (over PAR 400–2000 × Tair 20–40 × VPD
1–3), so a PM leaf at 25 °C air already sits at 32.9 °C and loses 23% of its mean
assimilation. Tleaf itself moved on 22 of 90 cells: the energy balance couples
back through transpiration, so on that path leaf temperature is an output, not an
input.
plant needs no source change — it never calls set_traits, so the fourteenth
argument is invisible to it, and it binds only R_d_ of the respiration fields. Its
leaf/TF24 test files give 486 checks, 0 failures and one error, and that error is
present identically on the control, so it is pre-existing on plant develop.
The gradient reallocation, which is the one thing a caller must know
Making R_d_25 a parameter turns dY/dvcmax_25 from a total derivative into a
partial at fixed respiration. It used to carry ratio * dY/dR_d_25 as well.
Of the 60 cells in tests/testthat/gradient_golden.tsv, 14 moved and all 14 are
vcmax_25, by 16% to 250%; every stem_b, psi_crit,
leaf_specific_conductance_max and resistance cell is bit-identical, as is every
solved value. The 250% is the five-layer row, where the two terms nearly cancelled
(8.6e-06 total against 3.0e-05 partial), so dropping one is not a small edit.
The chain rule reconstructs all 14 old values — exactly where both arms are exact,
and to 2.8e-04 at worst where they are not. That residual is the direct term's own
finite-difference round-off, not a modelling discrepancy: it falls to 2.5e-06 at
step = 1e-4 and rises to 0.030 at step = 1e-8, i.e. coarser is better there.
Asserted as the identity rather than left as two unrelated recorded numbers.
Cleanest case, a shut-down point where A = -R_d exactly:
| before | now | |
|---|---|---|
dA/dvcmax_25 |
−0.015 | exactly 0 |
dA/dR_d_25 |
(no such column) | −1, to 6e-11 |
Verified against a finite difference of the solve: dA/dR_d_25 = -0.24874 against
−0.24868 at one observation.
operating point, so the total derivative is well below the direct −R_d term.
Cost
Cache key 3.03–3.04 µs/solve on both arms, interleaved ×3 from one tree, with
identical checksums — inside the ±0.01 within-process noise. The key is compared once
per set_physiology(), not per solve iteration.
What was rejected, and why
A constant Q10 = 2 was implemented first and rejected on measurement: R_d 4.07
at 40 °C (2.8× its 25 °C value over 15 K), −73% on assimilation, and no operating
point at all by 45 °C. That is not the physics; it is a constant Q10 used far
outside where it holds, which is what Tjoelker measured. Slope zero recovers a
constant Q10 exactly, and that is asserted — it is the escape hatch for anyone who
wants the conventional form.
rd_tracks_vcmax_ is a separate flag and not a parameter value.
Extending the generated Leaf constructor to take R_d_25, instead of assigning
the field after construction. Not taken: its argument list is positional, seventeen
long, and pinned by the raw-versus-friendly comparison in test-surface.R. The cost
is recorded — R_d_25 is now listed in the constructor-partition test as a trait the
constructor does not take, with a test that the assignment is really there.
Traps, each of which cost real time
NaN == NaN is false. The temperature cache key is compared with ==, so the
raw sentinel must never go in it — it keys on rd_reference(), the resolved value.
Putting R_d_25 in directly silently disables the cache for every default caller, and
nothing notices.
set_traits' argument
order. R_d_25 is set_traits' fourteenth argument and the enumeration's
sixteenth, appended after the two non-traits, because slotting it in at 13 would
displace par_kmax and par_resistance — the reorder gradient.hpp warns is unsafe.
Appending is safe; reordering differentiates the wrong parameter and returns plausible
numbers. test-gradient-batch.R asserts the divergence in both directions.
.gradient_setter's positional trait call is where this broke. Its own comment
predicted that "if a trait is ever added, this call is the one place that will not fail
to compile" — and that is exactly how it went, at run time, with argument R_d_25 is missing raised inside the generated binding, naming neither the call nor the count.
R_d_25 as a missing trait, and the message was worse than
useless: `traits` is missing: NA, where the NA was the name of a trait that
does not exist. .gradient_theta_matrix() assumed the traits were the leading block
of par_names and sliced off "the last two", which now takes
leaf_specific_conductance_max. Both ends are placed by match() now.
A parameter whose value is "derive it" is not differentiable: perturbing NA gives NA.
Resolving it where theta is built is what makes R_d_25 differentiable at the
default, which is what a calibration starting from the defaults needs.
cpp-tests.yml, that no
local build covers. Its theta is brace-initialised, so appending to the enumeration
silently zero-filled the new element — a leaf with no respiration — and its arbitrated
dA/dvcmax_25 still carried the pre-#41 total derivative. Both fixed and verified by
extracting the two heredocs and building it locally: dA/dvcmax = 0.020934892290554417,
profit unchanged, exit 0, ctest 2/2.
R_LIBS falls back to the site library in silence. It happened once here,
mid-install, and produced a plausible file describing the wrong package. Both new
harnesses hard-fail on the wrong library rather than warning, because every number
they print is plausible for either arm.
use_energy_balance lives in plant's strategy$pars. Assigning it at the
strategy's top level is accepted and silently ignored, and the run comes back exactly
equal to the default arm — which reads as "PM changes nothing" rather than as a broken
switch. Asserted after setting.
comparison table lives in the doc block above
update_temperature_dependent_params, not inline.
Corrections to the record
- The C++ suite's blast-radius table was labelled "old form vs Q10 = 2" — the
rejected first implementation. Its numbers were always the shipped declining-Q10
form. A printed label is asserted by nothing, so it outlived what it named. The
"old" column is now therd_tracks_vcmax_branch itself, so the table is evidence
the escape hatch reproduces what plant used to compute; the printed numbers are
unchanged. vignettes/phylloptim.Rmdsaid "all 16" parameters when the answer was 14. That
label is now computed fromleaf_gradient()rather than written down.compare_with_plant.R's header blames the constructor signature for the harness
being unrunnable. That is no longer the obstacle: against plant at76df7169both
Leaf()andset_physiology()work. What actually stops it isroot_collar_psi_
versusopt_root_psi_— the One representation for water potential: positive magnitudes throughout (supersedes the two-type approach in #23) #25 rename, which flipped the sign — on top of results
deliberately moved by API cleanup: renames, a purely-intensive input set, the hidden hard-coded pressure, and the shutdown fix #15, 11a, 11b, One representation for water potential: positive magnitudes throughout (supersedes the two-type approach in #23) #25, Bound the root vulnerability curves past the grid #77 and Solve for leaf temperature inside the collar FOC #84. That is tests/validate/ has not compiled or run since #15 #64, and it needs a
decision about the reference build, not a rename. Recorded so the next attempt
starts from the real obstacle.bench_solve.cppclaimed to cover "the same state space the regression baseline
covers". It deliberately stays at 288 points at 25 °C: a timing baseline is only
useful against its own history (tools/cost-baseline.tsv,
tools/bench_history.sh), so quadrupling it would end that history to measure
nothing new about the solve.
The golden grid, and why it needed the axis
been all along. 288 points, all at 25 °C — where the reference values of Vcmax,
Jmax and R_d are defined, so a change to any response curve is inert there by
construction. #41 changed R_d's shape at every temperature except 25 °C and the file
did not move a bit. The same blindness covered the thirteen temperature-response
parameters bound in the first commit.
Now 15 / 25 / 35 / 40 °C, 1152 rows. 40 °C is deliberate: it is where the response
bites hardest, and the shut-down rows at the hot end pin the temperature at which the
model stops having an operating point, which is a limit that moved here.
The regeneration is checkable as an addition rather than taken on trust.
Temperature is the outermost loop, so the file is four contiguous copies of the
original grid and the 25 °C block is byte-identical to the pre-#41 file in all nine
output columns. 864 rows added, none moved.
The classification is counted per temperature now, because it moves:
| T | interior | pinned wet | pinned dry | shutdown |
|---|---|---|---|---|
| 15 °C | 198 | 24 | 18 | 48 |
| 25 °C | 198 | 24 | 18 | 48 |
| 35 °C | 194 | 43 | 3 | 48 |
| 40 °C | 160 | 80 | 0 | 48 |
A single total would have let points move between branches and still add up. The
direction is physical: a hot leaf assimilates less and respires more, so it has less
to gain from water and its optimum presses against the wet bound instead of the
dry one. The 48 shut-down rows are temperature-independent, which is also right —
those are the ψ_soil = 6 MPa rows, where hydraulics forbid transpiration whatever the
temperature.
Two things the hot end does not reach, so the zeros are not read as coverage:
shade-death (reached by light, not by heat) and the compensation-point exit, which
needs about 45 °C at the defaults. test_rd_temperature_response covers the latter.
Still not addressed, and arguably the bigger question
R_d now rises with temperature, which was the ask. The calibration finding that
motivated it is not explained: leaf_calibration_test's ManyPeaksRange misfit is
reducible (a within-site random forest gets R² 0.45 on A where phylloptim gets
−0.27), and the carbon-shape defaults were refuted as the cause by a control.
R_d was the last untested carbon-side candidate. That study can free it now —
sabot-2022/R/ LEAF_OPTIONAL_PARS is where it would go — and that is the test of
whether any of this actually helps.
Verification
- C++ suite 496 checks, 0 failures; golden 1152 points, all bit-identical
- R suite 940 checks, 0 failures, 1 skip
R CMD check: 1 NOTE, pre-existing (tibblenot declared for a vignette)make -C tests/cpp benchbuilds (not inall, but CI builds it)- CMake no-R build +
ctest2/2, and the installed-package consumer exit 0
Linux CI: `dA/dR_d_25` at interior-1layer disagrees by 1.3e-03 relative, against 1.3e-04 for every gradient column that was there before, so the tolerance borrowed from the solved-output baseline (1e-03) failed. Not a regression, and the mechanism is exact. A gradient here is a finite difference, so it carries the solve's ~1e-9 floor DIVIDED BY THE STEP, and the step is relative -- so the smallest-magnitude parameter sets the tolerance for the whole file. R_d_25 is 1.44 where vcmax_25 is 96: a 67x smaller absolute step. The arithmetic closes at 1.3e-03 x 0.2487 x 2h = 9.3e-10, the floor itself, and a step sweep agrees (at step 1e-7 the R_d_25 column has moved 1.3% and vcmax_25 0.2%). gradient_golden_tolerance() is 5e-03, 3.8x the one observation there is. Verified by forcing the tolerant branch that macOS never takes: the CI disagreement passes, the old 1e-03 fails it, and a real 5% regression still fails. Also corrects three stale cross-platform figures found while reading the CI summary line: test_golden.cpp's header carried the pre-11a pair with a compiler split 11a removed, and the guide's profit row said 1.82e-07 where CI now reports 2.14e-09. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Follow-up: the recorded gradients needed their own tolerance (
|
| step | dA/dvcmax_25 |
dA/dstem_b |
dA/dR_d_25 |
|---|---|---|---|
| 1e-04 | 0.020939751 | 2.8465363 | −0.24873735 |
| 1e-05 | 0.020939195 | 2.8465389 | −0.24873711 |
| 1e-06 (default) | 0.020934892 | 2.8465358 | −0.24873740 |
| 1e-07 | 0.020894746 | 2.8474104 | −0.25197702 |
gradient_golden_tolerance() is 5e-03, 3.8× the one observation there is, and it
cannot hide a real change: this PR's own reallocation moved these cells by 16–250%,
two orders above it. The values stay recorded at the default step, so the file
still pins what a default caller gets — recording them at a coarser step would have
been more portable and would have stopped pinning the default.
the CI disagreement passes at 5e-03, fails under the old 1e-03, and a real 5%
regression still fails.
Three stale figures found while reading the CI summary line
The guide says "if you need a magnitude, read the summary line, not the FAIL lines".
Doing that turned up three comments that nothing asserts, so all three had rotted:
test_golden.cpp's own header carriedprofit1.85e-06 / 5.87e-07 and the
argmax class 5.53e-04 / 2.73e-04 — the pre-11a figures, two generations old,
still showing a gcc-versus-clang split that 11a removed.- The developer guide's
profitrow said 1.82e-07; CI now reports 2.14e-09,
both compilers identical, over 1152 points with 7702 of 10368 values differing.
⚠️ Deliberately not attributed: a bigger grid can only move a maximum upward
and the 25 °C block is byte-identical, so the improvement is a change onmaster
since the figure was recorded (Bound the root vulnerability curves past the grid #77 and Solve for leaf temperature inside the collar FOC #84 are the candidates) — and confirming
that needs a cross-platform run of master, which no macOS box can give you. - The guide's gradient figure said 1.3e-4, which is the solved-output class, not
this file's.
The sqrt(worst profit) ≈ worst argmax sanity check no longer holds numerically
(sqrt(2.14e-09) ≈ 4.6e-05 against 1.4e-04 observed) and the guide now says why that
is expected rather than alarming: the two column maxima fall at different operating
points, and the argmax column now has 864 hot rows the profit column's worst case is
not among. It was never a global identity.
dfalster
left a comment
There was a problem hiding this comment.
Good implementation, but we have put in too much detail ensuring and documenting back compatibility. back compatibility is ensured by using the same values as was previously used. I don't think we need extra infrastructure. We should treat R_d_25 as a first class trait with no special fallbacks.
Also too much text in the comments, particularly related to past history. We want to simplify for the future.
| // compared for the same reason profit is below. | ||
| if (std::fabs(g[0].grad[0] - 0.0172086) > 1e-5) { | ||
| // | ||
| // ⚠️ This read 0.0172086 until #41. That was the TOTAL derivative, with |
| inline constexpr int par_stem_b = 2; | ||
| inline constexpr int par_kmax = 13; | ||
| inline constexpr int par_resistance = 14; | ||
| // ⚠️ `R_d_25` IS A TRAIT AND IS APPENDED AT THE END RATHER THAN PUT IN |
There was a problem hiding this comment.
Let's fix it properly with reorder
| # own finite-difference round-off: it falls to 2.5e-06 at `step = 1e-4`). | ||
| # `dA/dvcmax_25` at a shut-down point is the cleanest case -- exactly -0.015 | ||
| # before, exactly 0 now, with the -1 moved to `dA/dR_d_25`. | ||
| .resolve_rd_25 <- function(R_d_25, vcmax_25, rd_to_vcmax_ratio) { |
There was a problem hiding this comment.
remove. Always require respiration to be set
| curv_fact_colim = 0.99, | ||
| cost_scale_TF24 = 7.5) { | ||
| cost_scale_TF24 = 7.5, | ||
| R_d_25 = NA_real_) { |
| @@ -0,0 +1,138 @@ | |||
| # The #41 respiration-shape revalidation, ON THE PLANT SIDE. | |||
| @@ -0,0 +1,112 @@ | |||
| // The #41 respiration-shape A/B, on this package's own grid. | |||
Review: back compatibility comes from using the same VALUES, not from infrastructure, so the special-casing goes. R_d_25 now defaults to 1.44 -- 0.015 * 96, bit-for-bit the double the old vcmax-derived form gave -- with no NaN sentinel, no rd_to_vcmax_ratio_ (deleted, C++ and R bindings), and no rd_tracks_vcmax_ escape hatch. An unset or negative value fails instead of deriving something. The enumeration is reordered properly: R_d_25 is the fourteenth trait, in set_traits' own argument order, with kmax and resistance after it. Every index in gradient.hpp has a name, so nothing indexes theta with a bare integer, and the R side needs no set difference to reconcile.⚠️ Respiration no longer follows vcmax_25 -- the point of a trait, and the one exception to "same defaults, same results". plant's TF24 SCM offspring production is BIT-IDENTICAL at its defaults (81.857087216483691), and moves when vcmax_25 varies: 0.40692 -> 0.34336 at 60, 73.703 -> 92.026 at 150. That coupling belongs in the caller's parameterisation, where plant already derives its other parameters. Golden grid cut to two temperatures, 25 and 40 C, so 1152 rows become 576; the 25 C block stays byte-identical. Comments cut throughout, the two single-use validate harnesses removed, and their measurements kept in PLAN 41. 486 C++ checks, golden bit-identical, 933 R checks, ctest 2/2, consumer exit 0, R CMD check 1 pre-existing NOTE. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Done — all of it.
Enumeration reordered properly. Golden grid: two temperatures, 25 and 40 °C. 1152 rows → 576. The 25 °C block is Both validate harnesses removed, and their numbers kept in PLAN 41. Comments cut throughout: −1965 lines against +372. The history is out of One consequence to flag, measured rather than argued
The default arm is bit-identical to all 17 digits, so your premise holds exactly where And a correction to my own earlier justificationMy argument for the escape hatch was that "no published plant result under the old Verification486 C++ checks, golden 576 points bit-identical, 933 R checks, |
PLAN 41 quotes offspring-production numbers from a two-build comparison with no harness behind it, so the recipe goes next to them: two phylloptim libraries, two plant installs from clean sources, and a hard find.package() check because R_LIBS falls back to the site build in silence. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Three edits from the previous round never landed -- they ran from a
drifted working directory -- so the developer guide still described a
1152-point golden grid at four temperatures, and both vignettes still
described R_d_25 as an appended parameter with a NaN sentinel. Redone,
and this time swept for every count rather than the ones I remembered:
1152 / 10368 -> 576 / 5184, from CI's own summary line
"4 leaf temperatures" -> 25 and 40 C
thirteen traits -> fourteen, in nine files
"A character vector of fifteen names" -> sixteen (src/gradient.cpp)
"all 13 traits" in bench_gradient -> "13 of the 14", which is what it
measures; R_d_25 needs no rebuild and adds
nothing to the cheap/rebuild split
the temperature-response block binds fifteen fields now, not thirteen
Also cuts the history the review flagged in the golden-file section: the
"it was 288 points until #41" paragraph, the 1.82e-07 retraction, and the
sqrt-story archaeology are gone, leaving the mechanism and the current
figures.
486 C++ checks, golden bit-identical, 933 R checks, R CMD check 1
pre-existing NOTE, bench builds.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
No — you caught a real miss. Three edits from the last round never landed. They ran from a drifted working So this time I swept for every count instead of the ones I remembered:
The cross-platform figures in the table were not stale — CI reports the same Two judgement calls in there:
And the history you flagged is out of the golden-file section: the "it was 288 486 C++ checks, golden bit-identical, 933 R checks, |
- Rebased onto master. `gradient_golden.tsv` regenerated in full on macOS/arm64; the four pre-existing columns are bit-identical to MASTER, not merely to the branch point, and the envelope measurement was re-taken after #84 and #86. - Dropped `golden_tolerance(kind =)` for master's `gradient_golden_tolerance()`, which is the same fix with the better-reasoned number: it is the smallest differentiated parameter's step that sets it, not the argmax amplification. - `gradient_output_names()` is exported and R reads it, so the output list has one definition rather than two that can disagree. - profit's column is ASSIGNED from the direct term rather than reached by multiplying a zeroed dY/dpsi -- the treatment `collar` already gets, and immune to a non-finite dpsi/dtheta where `0 * x` would be NaN in one column alone. - The shut-down profit column is asserted against its closed form: `A = -R_d` gives `dprofit/dR_d_25 = -1`, and the pinned collar gives `dcollar/dpsi_crit = 1`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
R_dwasrd_to_vcmax_ratio_ * vcmax_(T), so it inherited Vcmax's PEAKEDArrhenius and FELL above the thermal optimum, where real dark respiration
rises. No value of the ratio fixes a function of the wrong shape. Now
Tjoelker's declining Q10, with
R_d_25a plain trait -- default 1.44, thesame double
0.015 * 96gave, so the defaults are unchanged.Respiration no longer follows
vcmax_25, and results move at everytemperature but 25 C, where agreement is exact by construction. plant's SCM
is bit-identical at its defaults. The golden grid gains a 40 C block because
it was blind to all of this. Measurements and traps in the comments.