Skip to content

The two zero-E branches inside the solve read the previous candidate's temperature #112

Description

@dfalster

Found while fixing #105, which covers the same defect at the two exits outside the solve. This is the half that is inside the objective function, so it moves the argmax rather than just the reported state — hence a separate issue.

The gap

set_leaf_states_rates_from_psi_stem has three branches. The transpiring one derives the operating point's leaf temperature and re-derives the Farquhar block at it:

Tleaf_ = use_energy_balance_ ? leaf_temp_from_E(transpiration_) : leaf_temp_;
if (use_energy_balance_) {
  update_temperature_dependent_params(Tleaf_);
}

The two zero-transpiration branches — psi_upstream >= psi_stem, and the inner assim_max_ < 0 — write Tleaf_ and stop:

ci_ = gamma_*umol_per_mol_to_Pa_;
transpiration_ = 0;
stom_cond_CO2_ = 0;
Tleaf_ = use_energy_balance_ ? leaf_temp_from_E(0.0) : leaf_temp_;

So ci_ is read off gamma_, and assim_colimited_ = assim_colimited(ci_) at the bottom of the function is evaluated, at whatever temperature the block currently holds.

Why this is sharper than #105, not milder

At the exits #105 fixed, the block held the Tair baseline — wrong, but at least a fixed wrong temperature. Here it holds whatever the previous candidate left, because the transpiring branch mutates the block per candidate and by design (update_temperature_dependent_params bypasses the cache). So these branches read neither Tair nor their own temperature: they read the last psi the optimiser happened to probe.

That makes the objective history-dependent — the value at a given psi depends on the order candidates were visited. Hazard 3 is about the argmax varying smoothly with inputs, and an objective whose value depends on evaluation order is a mechanism for exactly the kind of non-smoothness that hazard describes.

Why it was not folded into #105

profit_psi_stem_TF calls this function, so this is inside the function the collar solve maximises. Changing it moves the argmax on the PM path, where #105 only moved the reported state at exits the optimiser never reaches. Different blast radius, different evidence needed.

What is needed

  • Whether psi_upstream >= psi_stem is reachable during a collar solve at all, or only at bracket endpoints and through the standalone optimise_psi_stem_* entry points. The inner assim_max_ < 0 looks unreachable from find_root_collar_psiprepare_collar_solve returns early on that condition before the optimiser runs — but it is reachable from the single-layer optimisers.
  • The measured effect on the argmax and on profit over the PM-path grid, since operating_points.tsv runs gate-off and cannot see it.
  • ⚠️ It adds transcendentals to a branch of the hot path (~10³ candidates per solve), so hazard 5's interleaved A/B applies. The branch is presumably cold, but that should be measured rather than assumed.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions