Conversation
Layers or nodes? The framing question on the issueIt was put that plant "doesn't really have layers, but nodes at depth". Half true — and the half that is false is the structural half.
So the values were not "none of top/midpoint/bottom": What was nodal was the vocabulary: the setter said The inter-layer cascade needs no change, and this is the note asking you not to "fix" it later
Two floating-point constraints, both load-bearingMeasured over 180
Hence two rules, both stated at their call sites:
Bit-exactness, and the plant-side proof the upstream fix landedBuilt both ways — per-layer, and with the scalar temporarily restored — in the same tree:
Bit-identical, which is why no default output moves and At the individual level (θ = 0.25, height 5 m, 1.5 m column):
Read the middle row: a scalar thickness charges the 2 cm layer for 0.3 m of root segment — 225× over-resistance in that layer, 3.7× in total — so the plant appears to need more suction when it needs far less. Reversing the profile reverses the error. So The stiffness and runoff limits, which this PR does not fixShipped permissive by choice: widths are validated for finiteness and positivity, nothing more. The numbers, so they are on the record:
The analytic scaling reproduces the 1.77e4 / 5.30e4 measured in Separately, runoff is recalibrated by a thin layer 0. Infiltration keys off that layer alone through Both are in
|
| S01 | S02 | S03 | S04 | S05 | S06 | S07 | S08 | |
|---|---|---|---|---|---|---|---|---|
| rel. change | −0.68 | −0.88 | +29.3 | +26.8 | +40.9 | +19.1 | −0.43 | −0.37 |
19–41× on S03–S06 is the range #590's density-coordinate change is documented to produce, so the recorded baseline predates it. The bit-identical run_scm above is what says #626 did not move it — every scenario runs a uniform column.
Deliberately not re-blessed here. Filed separately so it stops costing reviewers an A/B; folding an unrelated re-baselining into this PR would hide what actually moved.
Verification
make clean && make RcppR6 && make full_compile; fulltest_dirgreen, no failures.test-model-version.R(the scientific-surface hash) green underNOT_CRAN=true.- Water budget closes to
< 1e-12relative on four graded profiles, including a single 2 cm layer. test-patch.R's pinnedn+5ODE layout and first-step rates unmoved, as expected — the default environment is untouched.
Release order
Remotes:. The pin is == and plant compiles against the installed headers, so install 0.9.0 locally before bumping the pin — doing it the other way round fails in a way that reads like a broken build (it did here).
git rebase --onto develop feature/events-522 feature/soil-layer-widths-626, and check the changed-file count is ~19 and not ~55 before merging.
|
Upstream merged; pin swapped to the commitphylloptim#134 merged as This PR passes #640's stricter gateway#640 re-blessed the baseline and added a numeric Worth noting why it passes: the numbers #640 blessed on
So neither #626 nor #632 moves a scenario, which is the expected result for #626 (every scenario runs a uniform column, and the default column is bit-identical) and a useful free datapoint for #632. Those two files were reverted rather than committed — they belong to Correction to #639I attributed the drift there to the density-coordinate change (#590), flagged as unconfirmed and with bisection as its first action item. #640 did the bisection and the answer is #633 and #619, not #590. The magnitude range fitted #590's documented 2.4–47x, but fitting a range is not identifying a cause. Still blocked on #632
|
8273ce7 to
b6b5734
Compare
Rebased onto
|
|
Wondering whether there would be an issue with a more narrow layer being beneath a wider layer. Small loss of water from above could overwhelm the water capacity of the layer below. Also, can't necessarily see a case where that would be necessary anyway. Maybe we should apply a block, enforcing small -> large? |
Soil evaporation needs a very thin layer at the top of the profile, which a layer count cannot express. set_soil_layer_widths() takes the WIDTH of each layer, so nothing is ambiguous between a top, a midpoint and a bottom. `depth` becomes read-only: writing it rebuilt no geometry. Needs phylloptim 0.9.0, whose per-layer root resistance is a fix rather than just an enabler. No default output moves. Decisions and limits: notes/plan-626-soil-layer-widths.md Closes #626 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
@itowers1 — measured it. The answer splits in two: the water-capacity worry does not materialise, but you are right that something breaks, and it is not what either of us expected. Notation: a profile like The water side is fine, and that part is cleanA thin layer's storage is not a bottleneck, because the water arriving from above does not depend on how thick the layer receiving it is. Drainage between layers is Measured, starting the whole column at 99% of saturation under 2 m yr⁻¹ rain — a saturated thick layer draining hard into a thin one, which is the case you describe:
No layer gets past 73% of saturation in any of them, and water is conserved to rounding. The middle column is the rate at which the thinnest layer's moisture responds to a change — it is what sets how short a step the solver has to take, and it comes out identical whether the thin layer sits at the top, the middle or the bottom. It depends only on that layer's own thickness, so which end of the column it sits at cannot enter it. But something does break, and it is not the soil
That is the guard #552 added for #550, whose own comment says "the divergence is in the equations, not the stepper" — the number of plants of a given size runs away to infinity, while the plants themselves stay perfectly ordinary (that node is a healthy 0.86 m tall). And the soil is genuinely fine underneath it. Running the same configuration to t = 4, which is 0.37 yr before it dies:
So no layer is ever overwhelmed, nothing oversaturates, and water is conserved. The water balance is not what fails. Where that leaves your proposal — including the part that favours itTwo findings pull opposite ways, so here are both. For the block. Every failure I can find has a thin layer sitting under a thicker one, and the mirror image is clean. Scanning the bottom layer's thickness with two equal layers above it, total held at 1.5 m — then the same scan with the thin layer moved to the top instead:
None of 18 profiles that get thicker with depth failed, including every Against it. Three things:
Your second point is the one I cannot settle from the code: whether a thin layer under a thick one is ever actually wanted. A thin bottom layer at a water table or a bedrock contact is a real land-surface-model pattern, and land models often thin at both ends — but I have no plant use case in hand, and if there is none, a block costs nothing and removes a foot-gun. My recommendation: file the reproducer against #550 rather than restricting the geometry, because the fragility lives there, it fails loudly, and the ragged boundary means a geometry rule would give false reassurance rather than real safety. But this is a modelling call between the two of you, and the evidence for your instinct is stronger than I expected — so if you would rather close the door, say so and I will add it. It is a one-line check in Everything above is measured on the rebased branch (now based on |
b6b5734 to
19c0669
Compare
Refining towards the surface is what a thin layer is for here. Refining at depth has no counterpart in TF24 -- a contrast in soil properties is per-layer parameters, not a thin layer, and there is no water table -- while it does reach a failure the model cannot absorb: a thin layer under a thicker one drives the size-density equations to overflow (#550). Equal widths are allowed, so a uniform profile stays legal. Review: @itowers1. Measurements, the literature, and what the rule does NOT guarantee are in the PR comment. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Done — @itowers1 you asked whether there is a case for thin-under-thick. I went looking rather than guessing, and the answer supports you — with one caveat that is worth having in writing. The literature is one-directionalRefining towards the surface is the standard, and I found nothing advocating the reverse:
And the two cases one might invoke for refining at depth are served by other mechanisms, not by geometry. A contrast in soil properties at a horizon boundary is per-layer parameters — which we already have from #558 — and is what the layered-soil literature (Berninger et al. 2015, Aryeni et al. 2022) is actually about. A water table is a bottom boundary condition (He et al. 2022, He et al. 2026), and TF24 does not have one. So the geometry was never the right place to express either.
|
| count | |
|---|---|
| run fine — refused for nothing | 46 (96%) |
| actually fail | 2 (4%) |
Examples now refused that ran clean: 0.707/0.462/0.331, 0.677/0.670/0.153, 0.633/0.444/0.423, 0.981/0.324/0.195.
I have gone with the block anyway, on your reasoning rather than against it: the 96% is only an argument for permitting these if someone wants them, and on the evidence above nobody should. But it means the rule is a foot-gun removed, not a stability proof — the failures it blocks are ragged in the geometry (5 cm fails, 4 cm passes, 3–1.5 cm fail, 1 cm passes again), so thinning-with-depth is a property the observed failures share rather than a characterisation of them. Both that and the #550 pointer are in the comment at the guard, so the next person does not mistake it for a guarantee.
Verification
- Full suite: 3196 pass, 0 fail.
run_scmon the default environment:11.143684537309916— unchanged by the guard, as it must be.- Scenario gateway (Re-bless the scenario gateway and diff its numbers #640's numeric 1e-3 diff): passes.
- New coverage in
test-environment-TF24.R: the refusal, that it names both offending layers and their widths, that non-monotone counts too, that equal widths and every thickening profile stay legal, and that a refusal leaves the previous geometry intact. - Two of my own tests used thick-over-thin profiles and had to change. The one that mattered was the sign test in
test-tf24-root-pars.R: it asserted the per-layer fix in both directions, and the thick-over-thin half is now unreachable. The thin-top half still discriminates on its own — a scalar thickness putsopt_root_psiat 1.747 MPa where per-layer gives 0.598, against 1.662 for uniform, so the sign of the difference still flips — and I have added a steeply-graded profile as a second increasing case. The retired numbers are kept in the comment.
One thing for someone else
make roxygen regenerated man/rainfall_pulse.Rd and man/run_scm.Rd, which are stale on develop: rainfall_pulse() gained an env argument and run_scm() reordered its arguments in the R source without a doc regen. Not included here — not my change, and it would muddy this diff — but they want regenerating on develop.
Not part of #626. `rainfall_pulse()` gained an `env` argument and `run_scm()` moved `events` to the end of its signature, both without a doc regen, so `make roxygen` emits them on any branch that runs it. Regeneration only -- no roxygen source touched, and a second run is idempotent. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Soil evaporation needs a very thin layer at the top of the profile, which a layer
count cannot express.
set_soil_layer_widths()takes the WIDTH of each layer, sonothing is left ambiguous between a top, a midpoint and a bottom; boundaries and
midpoints are derived from the widths.
depthbecomes read-only, because writingit rebuilt no geometry and silently left the old column in place.
Needs phylloptim 0.9.0 (traitecoevo/phylloptim#134), whose per-layer root
resistance is a fix and not only an enabler: one column average inflated total
vertical root resistance 3.68x on a 2 cm surface layer.
No default output moves —
run_scmon the default environment is bit-identical.Soil evaporation itself is out of scope.
Closes #626.