Skip to content

Flexible allometry: leaf and sapwood as ODE states - #647

Open
dfalster wants to merge 31 commits into
developfrom
feature/flexible-allometry-516
Open

Flexible allometry: leaf and sapwood as ODE states#647
dfalster wants to merge 31 commits into
developfrom
feature/flexible-allometry-516

Conversation

@dfalster

@dfalster dfalster commented Sep 7, 2026

Copy link
Copy Markdown
Member

Frees leaf and sapwood area as ODE states, so replacement of turned-over
leaf becomes conditional on the marginal leaf's carbon balance and the stem
can track the Huber value that maximises growth.

Both are OFF at the defaults (a_pl0 = 0, a_sw = 0) and the model is then
bit-exact: the scenario gateway passes unmodified against develop.

TF24 v10 -> v11; TF24f v10.1 -> v11.1. Merges #617.

Draft: the demo's "thinning does not buy survival" section still quotes
pre-#617 numbers. Detail, measurements and retractions in the first comment.

dfalster and others added 30 commits September 7, 2026 17:48
Records the decisions for #516 before the code, so they are reviewable
and visible to anyone else on this branch: leaf area and sapwood area as
ODE states, replacement gated on NSC reserves, gradual relaxation toward
the preferred trajectory, and the birth-date density coordinate.

Also records why sapwood is required rather than optional (freeing leaf
area alone lets a plant shrink storage capacity and so its mortality at
zero carbon cost), the invariants a later edit could break, and what is
deliberately out of scope.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Control's node_density_in_birth_date bool becomes a three-valued string
node_density_coordinate ("auto"/"birth_date"/"height"), so a model can
carry a default that a passed Control does not silently override. TF24
and TF24f resolve "auto" to birth date; FF16 and K93 to height.

The height coordinate's compression term equals d(growth)/d(height) only
where growth is a function of size alone, which TF24's reserve pool
breaks, so this is a correctness default rather than a preference (#590).
An unrecognised value is refused, since a typo read as either coordinate
would return a plausible number.

FF16 and K93 are bit-identical, measured over the full ODE state. TF24
results move, so TF24@v9 -> v10 (TF24f@v9.1 -> v10.1).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Height's monotonicity has to stay structural: a state predicate cannot
see the previous step, so non_negative_states() can bound height at zero
but cannot require it to be non-decreasing. The fallback if the
product-of-non-negatives form ever goes is stop_domain on the rate.

Also notes that a new phylloptim call path must translate
infeasible_error to stop_domain, since the two exception types are
siblings and odelia's handler cannot see phylloptim's.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The three candidate growth rates are proxies for fitness, and plant
computes fitness, so which one to use is empirical inside the model.
Records the two-stage test: find the ESS sapwood:leaf ratio with theta
as a trait first (no proximate rule needed, and no new code), then ask
whether a plastic rule invades that resident.

Notes that the comparison has no power in a constant environment, where
all three proxies agree, and that the answer is a property of the model
rather than evidence about real plants -- #512 is that check.

Also settles the gate parameters: TF24_Pars fields are already settable
as traits, and no hyperpar entry is wanted until there is a trade-off.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Adds log_area_leaf_departure = ln(A / A*(h)) as a seventh state, with
leaf area derived as A*(h) * exp(phi) into the aux the model already
reads. The rate is held at zero, so this changes no result: it
establishes the coordinate the plasticity terms will be written on.

Integrating A directly would have made A = A*(h) a redundant invariant
that Runge-Kutta preserves only to truncation error, putting exactness
out of reach by construction. On the departure coordinate the allometric
motion stays analytic, exp(0) is exactly 1, and odelia's max-norm error
control ignores a zero-error state, so step sizes are untouched.

Verified bit-identical: 288 named quantities over 12 individual states,
plus offspring production, R0 and all 88 cohort heights of an SCM run.
No scientific_version bump for the same reason.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The fitness landscape in theta spans ~1e20 on a wet stand and is
steeply asymmetric, so the optimality epic is worth building. The peak
is one iteration step, not an ESS, and the arid stand has R0 << 1
throughout so its ranking is among strategies that all die out.

Also records that mutants must be evaluated one per run_mutant call.
A batch of nine moved the resident's own recomputed R0 by 2.3%, where
a single twin moves it by 5.5e-7; the twin matches the resident slot
exactly either way, so mutants are perturbing the shared step-size
control rather than the competitive environment.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Adds log_area_sapwood_departure = ln(A_s / (theta * A)) as an eighth
state, and routes every sapwood quantity through it: the masses net
production respires and turns over, storage capacity, the sapwood to
heartwood conversion, and the hydraulic supply per leaf area, which now
follows the Huber value a plant has rather than pars.theta.

Form the ratio as theta * exp(psi), never as A_s / A. The two are
algebraically equal but not bitwise -- a multiply followed by a divide
does not recover its operand -- and that is enough to lose exactness.

The rate is held at zero, so results are unchanged: verified bit-
identical over the same 288 named quantities and SCM trajectory as the
leaf-area commit. No scientific_version bump.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A plant with low reserves declines to replace part of the leaf and
sapwood its turnover destroys, so its canopy thins instead of its
mortality spiking. The withheld fraction is a smooth logistic on the
reserve fraction, centred below the growth gate, so the allocation
ladder emerges without branching: reproduction and growth are cut
first, replacement next, then reserves empty.

Carbon and tissue balance exactly by construction -- the turnover not
charged is the mass the departure states lose -- and only a pool whose
size is a state may be gated, so bark and root stay fully charged.

The Huber value rises as the difference of two turnover rates, k_l -
k_s, so drought acclimation is arithmetic rather than an imposed
asymmetry. Growth is evaluated on the allometry the height implies, so
extension is departure-preserving and plasticity owns all the motion.

Off by default (a_pl0 = 0) and exactly so: verified bit-identical again.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Without it the gap alone drove rebuilding, so a starving plant withheld
replacement and spent its growth flux rebuilding at once -- carbon
enough to rebuild being carbon enough to maintain. Measured, it rebuilt
at ~1.5/yr against shedding's 0.42/yr: the canopy thinned 5 per cent and
stalled, and the Huber value fell instead of rising.

Records what the first behavioural validation found, and why the default
stays off: the response is only 1.6-3% on a strongly seasonal stand
because the reserve gate rarely opens, the sapwood departure has no
restoring force and drifts under-built, and the rebuild rate grows as
exp(-phi) near an empty canopy.

a_pl0 = 0 remains exact.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
It said sapwood is not sheddable, so its plasticity could only enter
through new growth. It is in fact lost to heartwood at k_s and currently
replaced implicitly, exactly as leaf turnover is, so one uniform gate
covers both pools. The rising Huber value is then a consequence of
k_l > k_s rather than an assumption about sapwood being special.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Rebuilding a canopy now buys the whole package -- leaf, fine root, and
the sapwood and bark that supply it -- at the ratio the plant prefers
rather than the one it has, and sapwood renews a share scaled by
exp(-psi / a_pl2) so an over-built stem declines to renew the excess.

Neither needed the three-way flux split that first looked unavoidable.
psi >= 0 is now invariant by the form: at psi = 0 the two replacement
fractions coincide, so the rate is (1 - replacement)(k_l - k_s) >= 0 and
the boundary flow points inward.

Measured on the seasonal stands, psi moves to [0, +0.043] where before
it ran to [-0.10, 0] and left plants under-built. Neither departure is
bounded under a PERMANENT deficit, which is recorded rather than fixed.

a_pl0 = 0 remains exact.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Shows the mechanism working -- a canopy thinning to 19% of what its
height prefers through a four-year drought, a Huber value reaching 2.4
times preferred purely because k_l > k_s, height stopping but never
falling, and a rebuild to 98% on recovery.

And leads with the issue it exposes: thinning buys only ~1% survival
and costs ~2 m of height, because mortality reads the reserve fraction,
which stays pinned near zero however thin the canopy gets. Thinning
should help through the carbon balance instead, but sapwood respiration
scales with A_s*h and thinning cannot touch it. Three candidate fixes
are set out, none chosen.

Numbers are asserted in test-allometry-demo.R, not just computed: a
first draft quoted a Huber rise from a shorter drought than the one
plotted and rendered clean anyway.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Adds a full derivation to the demo: symbols, the size relations, the
replacement gate, the growth split, and the two departure equations,
followed by the four properties that follow from the form -- exact
carbon conservation under withholding, phi <= 0 and psi >= 0 as
one-sided invariants, and height monotonicity.

Also states the one approximation: extension is charged at the marginal
cost evaluated at A*, so a plant off its trajectory is over-charged by
exp(-phi). Conservative in direction, and the price of keeping extension
exactly departure-preserving.

The equations are reimplemented in R from the prose and checked against
the C++ over 81 states, agreeing to 1e-10, so the description cannot
drift from the model without failing a test.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Restructures for a reader: the behaviour comes first and the maths moves
to an appendix, so 150 lines of equations no longer sit between the
opening and the first figure. Adds a lead that states the punchline, a
plain-language account of the mechanism, and figure captions.

Adds leaf and sapwood area against height, with the fixed allometry as a
dashed reference. This is the clearest statement of the change: the
fixed plant cannot leave the curve, the flexible one drops below it
during drought and climbs back.

Answers why the two departures move in opposite directions -- each is
measured against its own parent in the chain h -> A -> A_s, so opposite
signs are the physical result of k_l > k_s, not an inconsistency. Notes
what referencing both to height would cost.

Two prose claims were wrong and are fixed: a caption promised arrows
that were never drawn, and "horizontal excursion" invited a comparison
by eye across axes differing by four orders of magnitude. The true
statement is relative -- leaf falls to 19% of its curve, sapwood to 46%
-- and is now asserted in the test.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The demo claimed sapwood respiration dominates a drought deficit, so
thinning could not reach it. Measured, that is wrong: leaf, bark and
root all follow leaf area, so 76.5% of a 10 m plant's maintenance is
reachable by thinning.

What actually defeats it is that assimilation scales with leaf area
too, so thinning shrinks both sides of the budget together. Net
production per unit leaf area is flat as the canopy falls from 100% to
1.8%, and slightly worsens as the stem is spread over fewer leaves.

And there is no drought where it changes the outcome: between soil 0.15
and 0.13 the plant goes from untroubled to dead with nothing between,
so thinning early buys 1.0 to 1.13x. The pool has no stable
intermediate -- it fills when production is positive and empties when
negative -- so the reserve fraction is near-binary in the sign of
production, and exp(-20r) turns that into near-binary mortality.

The lever is the pool or what mortality reads, not the gate.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Five measurements move the problem out of the allometry. 76.5% of a 10 m
plant's maintenance does follow its canopy, so the earlier claim that
sapwood dominates the deficit is retracted. What defeats thinning is
that assimilation scales with leaf area too, so production per leaf area
is flat as the canopy falls to 1.8%.

There is no drought where it changes the outcome: a cliff between soil
0.15 and 0.13, and raising the gate to r = 0.30 buys 1.0-1.13x. The
cause is that dS/dt has no resting point, so r is near-binary in the
sign of production and exp(-20r) makes mortality near-binary too.

The departures are unbounded and it bites in a plain wet stand: a
suppressed cohort reaches canopy 0.000 and stem-per-leaf 1.3e9.

Literature contradicts the design directly: minimum NSC is ~46% of
maximum across 177 species, and plants sacrifice growth to defend
storage. TF24 gates growth and leaves storage as the residual.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…y term

The ordering across LMA is correct biology -- a fast-leaved species
should shed faster and die sooner, and u * k_l is the right rate law.
What fails is the state running to leaf area of 1e-8 m2, with divisions
by it reaching -9.2e7, inside a solver step shared with live cohorts.

Underneath it is a biological gap rather than a need for a clamp:
nothing kills a plant for losing its canopy, because mortality reads
reserves alone. A canopy-dependent mortality term would close it and
would also give mortality something continuous to read.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Robinson's mulga chapter and Manzoni 2015 both say the trigger should
be the instantaneous carbon balance, not the reserve pool. Manzoni's
optimum is A_net = 0, derived by maximising season carbon gain over leaf
area, and it fires while the plant is still carbon-rich -- waiting for
reserves to run down IS the evergreen strategy, which loses at long
drought. Robinson: 85% of trees shed including the healthiest, and died
and survived are indistinguishable until a year in.

The size result runs backwards for a reserve rule: large trees shed less
and survive best, canopy area being the strongest mortality predictor.

Also kills the canopy-dependent mortality idea: leaf area must reach
near zero and come back in a living plant. That creates a tension with
the log-departure coordinate, which cannot represent the endpoint.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Manzoni et al. (2015) derive the trigger by maximising a season's carbon
gain over leaf area: shed once a leaf stops covering its own upkeep.
That fires while the plant is still carbon-rich, whereas waiting for
reserves to drain IS the evergreen strategy that loses a long drought.
Robinson's mulga agree -- 85% of trees shed, the healthiest included.

The gate reads a running mean of that balance, on a new state with a
four-month memory, so a plant does not shed and re-flush with the
weather. And a canopy floor at a_pl3 taperss thinning to nothing, so
leaf area stays where its arithmetic means something: without it a
suppressed cohort in a plain WET stand reached canopy 0.000 with
stem-per-leaf 1.3e9. Both bounds hold by the shape of the flow.

a_pl0 = 0 remains exact. Measured: the new gate does not buy survival
either (1.001), which rules the trigger out as the cause.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
P = c*abar*A - g(h)*A - s(h,A_s), verified bit-exactly. Differentiating
at FIXED sapwood gives dP/dA = c*abar*(1-eta) - g, so shedding pays iff
eta > 1 - kappa, where eta is the elasticity of per-leaf assimilation to
hydraulic supply and kappa the leaf-side cost over leaf-side gain.

eta = 0 recovers Manzoni exactly, so the implemented criterion is the
special case with no hydraulic feedback. eta rises with height (0.20 at
5 m to 0.84 at 20 m), which closes the gap where tall plants died
without shedding, and it rules out gating on whole-plant solvency: a
solvent plant can still improve its budget by thinning.

Two cautions recorded: the profit auxes misreport assimilation by 2% off
the trajectory, so every eta is an estimate; and the size of the benefit
is an artefact of the plant sitting far from its optimal Huber value.
Hence the order -- sapwood optimality first, and #617 changes the
height-resistance relation all of this is measured against.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Makes it the centre of the demo. The budget splits by whether a cost
scales with leaf area -- leaf, root and bark do, sapwood does not -- and
that split reconstructs net production to machine precision, so it is
the model's own budget rearranged rather than an approximation.

Differentiating at FIXED sapwood, which is what thinning does since the
stem is lost only at k_s, gives dP/dA = c*abar*(1-eta) - g, so shedding
pays iff eta > 1 - kappa. eta = 0 recovers Manzoni exactly and is what
is implemented today; eta exceeds 1 above about 10 m, where removing
leaves raises TOTAL assimilation.

This settles two things: whole-plant solvency is the wrong gate, since a
solvent plant can still improve its budget by thinning, and the gap
where tall plants died without shedding was an artefact of eta = 0.

Not implemented, for two reasons recorded in the demo: the profit auxes
cannot difference abar reliably, and #617 replaces the height-resistance
relation every eta is measured against.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
No state has a plant insolvent while shedding hurts, so gating on
whole-plant solvency would never make one thin pointlessly -- it would
fire late. The band where a solvent plant should already be shedding is
an artefact of the stem, not a disagreement between criteria: sweeping
sapwood at fixed leaf area, net production peaks at stem-per-leaf ~3.3x
the pipe-model value, and at that optimum shedding no longer pays.

Which also says a single fixed theta cannot be right, since kmax ~ 1/h:
the ratio that suits a seedling starves a 15 m tree.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Four decisions. The objective is growth rate not P -- which leaves the
shedding criterion unchanged, since its conversion factor is evaluated
at A*(h) and so is constant at fixed height, but is decisive for sapwood
because extra stem must be paid for in forgone leaf growth.

First fix dmass_sapwood_darea_leaf, which prices new growth at pars.theta
rather than the plant's actual Huber value, so an over-built plant gets
the hydraulic benefit free. That is much of why P peaked at 3.3x.

psi integrating the marginal return IS the slow time-averaged response,
so no extra tracked state is needed -- integration is averaging.

And the derivative is nearly free by the envelope theorem: profit_ is
already maximised over the collar potential, so d(profit*)/d(kmax) is
the partial at the optimum already found, with the KKT corner harmless
because psi_crit does not depend on kmax.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
dmass_sapwood_darea_leaf used pars.theta, so a plant holding extra
conducting area paid the pipe-model price for new growth and got the
hydraulic benefit free. That is most of why net production appeared to
peak at 3.3x the pipe-model ratio.

Priced properly, and measured on GROWTH rather than production as Daniel
corrected, the optimum sits at 1.35x at 10 m and 1.82x at 15 m -- the
stem now being paid for in forgone leaf-area growth.

Bark keeps pars.theta, since it is pinned to leaf area and has no
business carrying the Huber value's excursion. It is expressed as a_b1
times the sapwood form at zero departure rather than written flat:
floating-point multiplication is not associative, and writing it out
moved a whole SCM trajectory. Exact at rest, verified again.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
An integral controller on the marginal growth return of sapwood area:
dpsi/dt gains a_sw * R_s, converging onto R_s = 0. Integration is
averaging, so the slow response Daniel asked for needs no extra tracked
state -- a small a_sw makes the stem follow the mean of a signal that
swings with the weather.

R_s is the GROWTH marginal, not production: it subtracts the price of the
extra stem, paid in forgone leaf area. Optimising production instead puts
the optimum near 3.3x the pipe-model ratio; growth puts it at 1.35x, and
the zero-crossing lands on the measured argmax of dh/dt.

d(profit)/d(kmax) comes from the envelope theorem -- one re-evaluation of
the leaf at the psi* already found, so no phylloptim change is needed.

a_sw = 0 by default and the model is then bit-exact: built at HEAD and
re-ran the scenario gateway, same eight numbers to every digit.
Adds the section the demo's own callout said had to come first: the
growth-versus-production objective, the envelope-theorem derivative, and a
figure showing the controller's zero landing on the growth peak at three
soil moistures.

Guards the numbers in test-allometry-demo.R, including the wart the
section admits to -- at the pipe-model ratio in dry soil the plant cannot
grow, so the controller switches off rather than pointing uphill.
Resolves against #617 and #645. Three substantive resolutions rather than
textual ones:

- kmax takes develop's path-integral denominator AND this branch's actual
  Huber value as numerator, and is now STORED so the sapwood controller
  differentiates the value the model used. The controller had been
  recomputing it on the old height-linear relation -- wrong by a factor
  0.35 at 16 m, and silent.
- scientific_version -> 11. Both branches independently claimed v10, and a
  merged TF24 carries both changes so it is comparable to neither.
- #617's exactness test is pinned to the height coordinate; this branch
  changes TF24's default, so a bare Control() compared two coordinates
  rather than two hydraulic models (404.5 against 30.2).

Full suite green at 3738.
The sapwood sensitivity held the collar potential fixed, on the argument
that the indirect term vanishes at an optimum. It does not: opt_root_psi_
is a ROOT FIND matching supply to demand, not a maximisation, so
d(profit)/d(collar) is non-zero. Holding it fixed under-reported
d(profit)/d(kmax) by 15-19%, putting the controller's zero at 1.22x where
growth peaks at 1.28x. Now a full re-solve: matches an FD of the model's
own assimilation to 0.09%, and the zero lands on the peak in all twelve
height/soil cells.

It hid because the wet-soil growth peak is flat enough that a 0.025 grid
manufactured an exact-looking agreement. Only dry soil on a 0.01 grid
showed it.

Re-measured after #617: the viability ceiling roughly doubled (17.5 m to
30-36 m) and the shedding band moved with it, as predicted. The growth
optimum is now height-dependent, 0.87x the pipe model at 5 m to 1.30x at
20 m.
@dfalster

dfalster commented Sep 7, 2026

Copy link
Copy Markdown
Member Author

What this does

Two mechanisms, both defaulting to off.

1. Conditional leaf replacement (a_pl0). Leaf turnover is currently charged as a cost and the tissue restored unconditionally by the allometry, so a struggling plant cannot economise by carrying less leaf. Replacement now depends on whether the marginal leaf is paying for itself. Carbon not spent and leaf mass not replaced are two halves of one decision and conserve exactly by construction.

2. Sapwood tracks its growth optimum (a_sw). dψ/dt gains a_sw · R_s, an integral controller on the marginal growth return of sapwood area. It converges onto R_s = 0, and because integrating is averaging, a small a_sw makes the stem follow the long-run mean of a signal that swings with the weather — no extra tracked state.

States are carried as departures, log(actual/preferred), which is what makes the fixed-allometry model recoverable bit-for-bit rather than approximately.

The objective is growth, not production

This is the design decision everything else follows from. Against production, extra sapwood is nearly always worth building: it raises k_max, and nothing in P charges the plant for the leaf area it did not build instead. R_s subtracts that price. The optimum is not one number — after #617 it rises with height and again as soil dries:

stem area per leaf area, ×pipe model 5 m 10 m 16 m 20 m
soil 0.30 0.87 1.03 1.17 1.26
soil 0.20 0.89 1.05 1.21 1.30
soil 0.15 1.04 1.28 1.54 1.68

The defining correctness property is that R_s crosses zero exactly where dh/dt peaks — verified in all twelve cells above on a 0.01 grid in ψ. A controller built on production instead is still monotone, still converges, and is still wrong, so the test asserts the crossing on the grid rather than by interpolation.

⚠️ Two retractions

The envelope theorem does not apply here. I claimed the sensitivity was nearly free: profit is already maximised, so re-evaluate at the collar potential already found and the indirect term vanishes. That holds for opt_psi_stem_, which is genuinely maximised. It does not hold for opt_root_psi_, which is a root find — the collar potential where the soil-root network's supply matches the leaf's demand. A constraint is not an optimum, so d(profit)/d(collar) ≠ 0.

Holding it fixed under-reported d(profit)/d(k_max) by 15–19%, putting the controller's zero at 1.22× where growth peaks at 1.28×. A full re-solve matches a finite difference of the model's own assimilation to 0.09%. Consequence: a_sw > 0 costs a second full leaf solve per rate evaluation; a_sw = 0 skips the block. plant #614 (analytic d(profit)/d(k_max)) would remove that cost and is worth more than it looked.

Why it survived so long is worth its own warning. In wet soil the growth peak is nearly flat — at 16 m, dh/dt varies by 6e-4 relative between 1.13× and 1.22× — so a 0.025 scan grid could not resolve a 0.03 offset in ψ and the zero appeared to land exactly on the peak. A coarse grid here does not merely lose precision; it manufactures an exact-looking agreement.

Shedding's benefit is a function of size, and my first measurement was taken at one size. An early sweep at 10 m found 1.001–1.06× and I concluded thinning does not buy survival. That is true at 10 m and false at 20 m+.

What #617 changed, measured before and after

The benefit band sits at the height-resistance relation's viability ceiling, so replacing that relation moved both:

before #617 after
viability ceiling (P > 0 in wet soil) 17–18 m 30–36 m
benefit at soil 0.16, 4 yr, any height ≤24 m up to 4.4e9× exactly 1.00
largest benefit found 6.1e9× (both arms dead) 84× at 30 m

TF24 is now much more drought-tolerant: a 4-year drought at soil 0.16 kills nothing up to 24 m, where before it took a 20 m plant to 3e-19. The size dependence survives, shifted — nothing below 13 m, 2.4× at 20 m, 84× at 30 m at soil 0.14.

Shedding still never converts death into survival. Zero cells of the post-#617 grid have the fixed plant dead and the shedding plant alive; the large ratios sit between two very small numbers. The benefit is also largest at moderate drought, not the harshest — at soil 0.12 everything dies whatever it does. This is the open scientific question, and it is why the demo carries a section on it.

Merge resolutions worth reviewing

  • k_max takes Derive TF24 height-resistance from stem anatomy #617's path-integral denominator and this branch's actual Huber value as numerator, and is now stored rather than recomputed. The controller had been recomputing it on the old height-linear relation — wrong by a factor 0.35 at 16 m, and silent. This is the resolution I would most like a second pair of eyes on.
  • scientific_version → 11. Both branches independently claimed v10; a merged TF24 carries both changes and is comparable to neither.
  • Derive TF24 height-resistance from stem anatomy #617's exactness test is pinned to the height coordinate. This branch changes TF24's default, so a bare Control() compared two coordinates rather than two hydraulic models (404.5 against 30.2) — a failure that reads exactly like a broken exactness test.
  • The TF24f k_acclim bracket moved rather than its threshold relaxed. The peak shifted from k≈0.1 to k≈1 under the birth-date coordinate, so 0.1-vs-10 straddled the maximum and read 3% — a badly-placed bracket, not weak acclimation. Spanning 0.001–1 gives 13.2%.

Also fixed along the way

  • Sapwood turnover is gated, so its ψ-derivative is not k_s·m_s. The charge falls as exp((1 − 1/a_pl2)ψ). Differentiating the ungated cost dropped a term worth 6.8 of 13.1 kg/yr — invisible to any check on sign or monotonicity.
  • The controller's denominator must stay positive, and profit is not: it goes negative in exactly the drought where the sign is most needed. It now divides by the maintenance bill.
  • Re-proportioning has to be paid for. The reserve gate G is not the right switch — at soil 0.13 reserves are still full and it is production that has gone to zero.
  • Deep-crown is refused, not skipped (prepare_strategy() throws). Skipping leaves the controller with only its negative cost term, and the stem shrinks without bound while the run looks plausible the whole way down.

Status

Full suite green at 3738, and the scenario gateway passes unmodified — the strongest available statement that a_sw = 0 is bit-exact against develop.

Draft because the demo's "thinning does not buy survival" section still quotes pre-#617 numbers. Its chunks run, so nothing fails; the prose beside them is what is stale.

@dfalster

dfalster commented Sep 7, 2026

Copy link
Copy Markdown
Member Author

@itowers1 @elijahmagistrado — feedback welcome on this one, whenever suits.

Please run the demo rather than only reading the diff. It is the honest account of what this does and, more usefully, of where it does not yet work:

git fetch && git checkout feature/flexible-allometry-516
R CMD INSTALL --preclean .          # header + yml changed, so a clean build
quarto render overstorey_staging/TF24_flexible_allometry_demo.qmd

It runs in a few minutes and needs nothing beyond the branch. If you would rather not build it, say so and I will render and attach the HTML.

What I would most like challenged, in priority order:

  1. The claim that shedding does not buy survival. Across a 90-cell grid of size, drought intensity and duration, there is no cell where the fixed plant dies and the shedding plant lives. That is the central negative result and I would like it attacked — either a regime I have not tried, or a reason the experiment is the wrong one. The demo's section on this is the part still carrying pre-Derive TF24 height-resistance from stem anatomy #617 numbers, so read the prose sceptically.

  2. Whether the growth rate is the right objective. R_s is zero where dh/dt peaks. Height growth is one choice; dA_leaf/dt, or the relative rate (dA/dt)/A, are defensible and give different optima. Daniel's view is that this is ultimately an empirical question for evolutionary analysis of competing strategies, but if either of you has a prior I would rather hear it before we run that.

  3. The merged k_max. It takes Derive TF24 height-resistance from stem anatomy #617's path-integral denominator with the plant's actual Huber value as numerator. I think that is right — supply per unit leaf area should follow the stem the plant actually has — but it is the one place where this branch and Derive TF24 height-resistance from stem anatomy #617 genuinely interact, and it was silently wrong once already.

  4. Whether the parameters are the right ones to expose. a_pl0a_pl4 and a_sw are all TF24_Pars fields and so settable as traits. Defaults reproduce the current model exactly. Whether any of them should instead be derived from an existing trait is open, and [TF24 allometry] Review empirical data on plasticity in allometry (height-to leaf area for individuals) in response to environment (light or water) #512 is the input.

The first comment above has the measurements and two retractions. No rush on any of this — it is draft and the demo needs a pass before it is merge-ready.

Every table in it was a pre-#617 number and the section rendered clean
throughout, because its tables are prose rather than chunks.

One argument is retracted outright. It said thinning "cuts income just as
fast", with per-leaf production worsening monotonically as the canopy
shrank. That is no longer true: with resistance derived from stem anatomy
the remaining leaves inherit the whole stem's supply, so thinning improves
the per-leaf balance by 43% and there is an interior optimum near a tenth
of full canopy. The conclusion survives the argument being replaced --
thinning narrows the gap but never closes it.

The stand-level cost also shrank, from 0.55-0.64x offspring production to
0.92-1.00x, so the mechanism is now close to free rather than expensive.

Guards added for all of it, including the ordering argument the section
turns on: at soil 0.140 a 10 m plant is at one in a million with its canopy
still at 1.000.
@dfalster
dfalster marked this pull request as ready for review September 9, 2026 04:24
@dfalster

dfalster commented Sep 9, 2026

Copy link
Copy Markdown
Member Author

Ready for review — the stale section is re-measured.

@itowers1 @elijahmagistrado the demo is now safe to read end to end. It renders clean and the full suite is green at 3753.

One argument in it is retracted outright, and it is worth knowing about before you read the section. It used to say that thinning "cuts income just as fast" — that shrinking the canopy shrinks both sides of the budget together, with per-leaf production worsening monotonically from −3.81 to −4.68 as the canopy went to a fiftieth. That is no longer true after #617. With resistance derived from stem anatomy, the remaining leaves inherit the whole stem's hydraulic supply, so thinning improves the per-leaf balance by 43% and there is an interior optimum near a tenth of full canopy:

canopy fullness 1.00 0.50 0.20 0.10 0.05 0.018
net production per m² leaf −1.47 −1.17 −0.94 −0.84 −0.86 −0.99

The conclusion survives the argument being replaced: a struggling plant can now narrow its deficit by shedding, but it still cannot close it. Thinning slows the burn; it never reaches balance.

The stand-level cost also shrank a lot. Switching the mechanism on used to cost 0.55–0.64× lifetime offspring production on seasonal stands. It is now 0.92–1.00×, so the mechanism is close to free rather than expensive — but still buys no survival, which is what the section is about.

The decisive measurement is unchanged and now guarded. The two thresholds are in the wrong order in stress space: at soil 0.145 nothing thins because the leaves are still profitable, and by 0.140 a 10 m plant is at a survivorship of one in a million with its canopy still at 1.000. Mortality saturates at a milder drought than shedding begins at, so there is no intensity where a plant is both stressed enough to thin and alive enough to profit. Across the grid the largest gain is 20×, and it moves survivorship from 2.8e-08 to 5.6e-07 — both dead.

Why this rotted, and what stops it next time. The section's tables were prose, not chunks, so every number in it survived #617 while the page rendered clean. test-allometry-demo.R now asserts the ordering, the absence of any rescue cell, the interior optimum, and the trajectory's height cost — against demo_soil_history's own arguments, since quoting a number from a nearby-but-different run is how an earlier Huber-value claim went wrong.

The four things I would most like challenged are unchanged, in the comment above.

@itowers1

Copy link
Copy Markdown
Collaborator

Just adding my current understanding of how this all works via a flowchart, this can be edited in powerpoint as I learn more.
Slide1

@itowers1

Copy link
Copy Markdown
Collaborator

In terms of optimisation of allometry, if the purpose of thinning is to stave off mortality in drought periods, would there be a functional difference to maximising survivorship as opposed to growth. Is survival maximised where height growth is greatest anyway?

@elijahmagistrado

Copy link
Copy Markdown
Collaborator

I think once the trees reach their height at maturation and af_1 = 1, then the purpose of thinning would be exclusively for reducing mortality. So in wet periods, survival would be the highest where height growth is also the highest when there is still room to grow, otherwise the trade-off is between survival and reproduction.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants