Design pass: the soil cascade needs no change - #608
Conversation
theta_sat is already a barrier in the exact vector field, and error control already rejects the overshooting steps. The excursions to theta = 1.7 are explicit overshoot triggered by the step size a leg inherits, not a water-balance defect. Two faults instead: odelia accepts a NaN error estimate (odelia#52), and a throw inside a stage evaluation preempts rejection entirely. Capacity limiting is measured to cost stiffness for no benefit. Design doc plus standalone scripts; no package code changes. Refs #522, #599, #565. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
What this was convened to do, and why it concluded the oppositeScoping #522 (rainfall pulses) produced a plan whose first stage was a redistribution fix in Everything below is from 1. θ_sat is already a barrierInflow
So θ cannot exceed saturation in exact arithmetic. One Euler step from θ₁ = 0.05: h = 1e-3 → 0.593; h = 0.08 → 43.5, reproducing the recorded 2. Error control already rejects those stepsh = 1e-3, 1e-2 and 0.08 all reject. From a cold start the worst case integrates in 15 steps with max θ = 0.428 exactly; 3. The trigger is the inherited step size
Two independent faults:
4. Capacity limiting: measured, and rejected
Donor-throttling — the literal reading of "a layer can't overfill the one below" — breaks lower-triangularity, which vindicates the original donor-only choice. Receiver-side rejection keeps it but costs 1.20× stiffness at 0.9 θ_sat and 1.45× at 0.99, and at 0.99 turns a +81 yr⁻¹ rate into −420 yr⁻¹ by discarding inflow the baseline retains — a real hydrological change needing a 5. Relation to #565Same criterion, independently: #565 uses donor control to triangularise the cross-patch Jacobian under the "design out stiffness rather than reach for implicit solvers" philosophy. This pass supplies measured backing for two things it asserts — the stiff diagonal (≈1.5e4 yr⁻¹, structure exactly triangular to machine precision) and the need for a spill term on large events. It also flags that its N = 15 bucket makes the §3 trigger worse: measured max |λ| at 0.99 θ_sat goes 1.77e4 (N=5, dz=0.3) → 5.30e4 (N=15, dz=0.1). 6. Consequence for #522Pulses are no longer gated on a physics change. They need the event queue, the pulse action, and the R generator. The one physics-adjacent piece that is required is capping the jump at layer 0's free capacity, because a jump is applied outside the integrator where nothing can reject it — and a ~13 mm event already exceeds capacity from θ₀ = 0.40. CaveatThese experiments isolate the soil column with a stub for physiology, so they establish the mechanism, not the 43%-of-seeds figure. The 40-seed harness should be re-run against real fixes before #599 closes. Retractions from my own earlier notes on this
|
R's max() propagates NaN and C++'s std::max does not, so the replica overstated the controller fault as "any NaN is accepted". It is narrower and positional: the reduction wipes a NaN on the next finite element, so the step is accepted either with rmax still NaN or with rmax finite and the state carrying a NaN. The second is the mode coupled runs hit. Conclusions are unchanged -- the barrier result, the rejected overshoot, the inherited-step trigger and the capacity-limiting cost all stand. Also deduplicates the controller that exp2.R had copied. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adopts odelia 0.3.1's opt-in domain checks. A non-finite environment state and an infeasible leaf probe both used to kill a run outright; both are now handed to the stepper to shrink and retry. If the minimum step still cannot escape, odelia stops and reports the original message, so nothing is lost. phylloptim's infeasible_error is a sibling of odelia's DomainError, not a subclass, so the stepper cannot see it -- solve_leaf() translates it. Only that one type, so a bug stays a bug rather than becoming step-shrinking. A runaway cohort density stays fatal: that divergence is in the equations, not the stepper. And ode_state_valid() checks only the environment block, because a node's log_density is legitimately -Inf. Inert where nothing is wrong: FF16, K93 and TF24 stay identical(). Refs #628, #608, #599. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Adopts odelia 0.3.1's opt-in domain checks. A non-finite environment state and an infeasible leaf probe both used to kill a run outright; both are now handed to the stepper to shrink and retry. If the minimum step still cannot escape, odelia stops and reports the original message, so nothing is lost. phylloptim's infeasible_error is a sibling of odelia's DomainError, not a subclass, so the stepper cannot see it -- solve_leaf() translates it. Only that one type, so a bug stays a bug rather than becoming step-shrinking. A runaway cohort density stays fatal: that divergence is in the equations, not the stepper. And ode_state_valid() checks only the environment block, because a node's log_density is legitimately -Inf. Inert where nothing is wrong: FF16, K93 and TF24 stay identical(). Refs #628, #608, #599. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
theta_sat is already a barrier in the exact vector field, and error
control already rejects the overshooting steps. The excursions to
theta = 1.7 are explicit overshoot triggered by the step size a leg
inherits, not a water-balance defect.
Two faults instead: odelia accepts a NaN error estimate (odelia#52),
and a throw inside a stage evaluation preempts rejection entirely.
Capacity limiting is measured to cost stiffness for no benefit.
Design doc plus standalone scripts; no package code changes.
Refs #522, #599, #565.
🤖 Generated with Claude Code