8000 Explain every recipe miss; ammonium limit, formula balance, faster ceilings by rafatahmed · Pull Request #57 · rafatahmed/Flahax · GitHub
[go: up one dir, main page]

Skip to content

Explain every recipe miss; ammonium limit, formula balance, faster ceilings - #57

Merged
rafatahmed merged 5 commits into
mainfrom
feat/recipe-feasibility-report
Oct 20, 2026
Merged

rafatahmed merged 5 commits into
mainfrom
feat/recipe-feasibility-report

Conversation

@rafatahmed
Copy link
Copy Markdown
Owner

Completes #39 (recipe misses) and the bugs found while auditing it. The design follows the owner's decisions after a research review of Dutch soilless practice (De Kreij, Voogt & Baas 2003; Sonneveld 2000; Steiner 1961; Dickson & Fisher 2019).

Root cause of the 23 benchmark misses (#39)

A linear-programming check (scipy HiGHS) classified each miss:

Cause Recipes
Engine bug: a recipe meeting every target existed, but the incidental-ion penalty (mainly NH4-N from calcium nitrate) outweighed the target miss (#54) 7
Ca and NO3 locked together without chloride (fixed in #51 via the Cl ceiling) 12
Unreachable with any product: Pepper (target conflict), Rice (no Si source) 4

The formula's own charge balance explains most misses. Eight shipped formulas imply cation/anion sums that differ by more than 10% (Pepper -39.5%, U of Florida clusters +19 to +30%), so no salt combination can meet them exactly.

Changes

  • Ammonium default (recommend() misses recipes that meet every target because incidental ions are penalised without a limit #54): when targets omit N_NH4, incidental NH4-N may reach min(10% of total target N, 1.5 mmol/L). Owner decision based on Dutch practice (5-15% of total N, at most 1-1.5 mmol/L; Voogt "max 90:10"). The citation is corrected: the "5-10% hydroponics / 10-15% substrate" wording is Dickson & Fisher's attribution without a page. Users can raise the share (max_ammonium_fraction), or pass None for the previous fit; an explicit N_NH4 target, including 0, is unchanged.
  • targetIonBalance: the formula's cation and anion meq/L, with a warning above 10% imbalance.
  • unmetTargets: every missed target gets a reason: no_source, limited_by_ceiling, incidental_tradeoff, or conflicting_targets (with the conflicting targets and whether chloride products would reach it). The diagnosis is dependency-free least squares.
  • Ceiling performance (Incidental-ion ceilings triple recommend() time through repeated refits #56): binding ceilings are reached in one refit instead of up to 13. Mean recommend() time on the benchmark falls from 482 to 211 ms with the ammonium default and from 858 to 246 ms with a Cl ceiling.
  • NNLS cap (NNLS returns a non-optimal result silently when its iteration cap is reached #55): the solver raises when it is still not optimal at its iteration cap, instead of returning silently.
  • pH: the workflow notes a target pH outside the 5.3-5.8 Dutch feed range (or 5.0-7.0). The equilibrium acid plan reports 8000 the residual bicarbonate at the target pH and warns below the 0.5 mmol/L buffer.
  • Docs: the 1% tolerance is stated as a FlahaX assumption; the usage guide's outdated claim that ceilings do not affect the fit is corrected.

Evidence (52 benchmark cases)

Before After
Whole recipes passing (default) 29 34
Targets passing 563/636 571/636
Limit violations 0 0
Misses without an explanation 23 0

Of the 7 target-exact cases, 5 now pass. Generic Grow (both waters) needs 20-34 mg/L NH4-N, more than the limit allows; it is reported as limited_by_ceiling.

Behaviour changes to note

  • Recipes change for formulas without an NH4 target; the penalty-only fit is still available with max_ammonium_fraction=None.
  • New warnings appear for unbalanced formulas and for target pH outside 5.3-5.8.

Verification

  • 354 tests pass (1 skipped), with new tests for ammonium limits, ion balance, unmet targets, pH guidance, residual bicarbonate and the NNLS cap.
  • sphinx -W builds cleanly.

Closes #39, closes #54, closes #55, closes #56.

recommend() penalised every mg/L of an untargeted ion against relative
target error with an arbitrary 1/100 weight. Calcium nitrate (ag grade)
always carries 1.1% N_NH4, so for formulas without an NH4 target the
fit under-dosed calcium even where a linear-programming check found a
recipe meeting every target (7 benchmark cases).

When targets omit N_NH4 and no N_NH4 maximum is given, incidental NH4-N
may now reach max_ammonium_fraction (default 0.10) of the total target
N through the existing ceiling mechanism. The 10% is an owner decision
within the ammonium share attributed to Sonneveld & Voogt (2009),
doi:10.1007/978-90-481-2532-6. An explicit N_NH4 target, including 0,
is unchanged; None restores the penalty-only fit; ammoniumLimit reports
what was applied.

Benchmark recipes passing in full rise from 29 to 34 of 52 (targets
563 -> 571 of 636), with no limit violations. Also corrects the usage
guide, which still said ceilings do not affect the fit.
…#56)

recommend() now returns unmetTargets: each positive target missed by
more than the 1% tolerance gets a reason from a least-squares diagnosis
on the target rows alone (dependency-free):
- no_source: no catalogue product supplies the element (Rice Si);
- limited_by_ceiling: the targets are reachable but an incidental
  ceiling binds (Generic Grow, ammonium), named in bindingLimits;
- incidental_tradeoff: reachable, but the incidental penalty chose
  otherwise;
- conflicting_targets: dropping a named partner makes the rest
  reachable (N_NO3 vs Ca), with reachableWithChloride when chloride
  products, which need consent, would reach it.
Every benchmark miss is explained. The diagnosis runs only when a
target is missed, adding about 9% to mean recommend() time.

_fit_within pulled an ion above its maximum back with a weight starting
at 1 and doubling up to 13 times, and _simplify repeats that for every
candidate removal (#56). The fit is convex, so a binding ceiling's
optimum lies on the limit; a strong first weight (1024) reaches it in
one refit. Mean recommend() time over the 52 benchmark cases falls
from 482 to 211 ms with the ammonium default and from 858 to 246 ms
with a Cl ceiling. Pass counts and zero limit violations are unchanged;
the few changed recipes have equal or better target fit.
The incidental NH4-N limit is now the smaller of 10% of total target N
and 1.5 mmol/L NH4 (21.01 mg/L N), following Dutch soilless practice
(5-15% of total N, at most 1-1.5 mmol/L; De Kreij, Voogt et al.; Voogt
'max 90:10'). The research showed the '5-10% hydroponics / 10-15%
substrate' wording is Dickson & Fisher's (2019) attribution to
Sonneveld & Voogt (2009) without a page, describing the pH-neutral
share rather than a limit; docs and code now say so. Benchmark results
are unchanged (34/52 recipes, no limit violations).
…idance

Owner decisions after the 2026-10-20 research report:
- recommend() reports targetIonBalance (cation and anion meq/L implied
  by the formula; P as H2PO4-, S as SO4 2-) and warns above 10%
  imbalance (Steiner 1961; window of Steiner-type software). Eight
  shipped formulas exceed it, which explains most #39 misses.
- The 1% preparation tolerance is documented as a FlahaX assumption;
  no published standard gives one.
- The equilibrium acid plan reports residual bicarbonate at the target
  pH and warns below the 0.5 mmol/L buffer Dutch practice leaves.
  FlahaX doses to pH, so the residual is reported rather than imposed.
- plan_fertilizer_workflow notes a target pH outside 5.3-5.8 (Dutch
  feed range) or 5.0-7.0.
When the solver still needs another column addition but has used its
4n budget, the current x is not optimal; it now raises InputError
instead of returning that fit. The check runs after the optimality
test, so a solve that becomes optimal exactly at the cap is unaffected.
No benchmark fit reaches the cap.
@read-the-docs-community
Copy link
Copy Markdown

Documentation build overview

📚 Flahax | 🛠️ Build #35277291 | 📁 Comparing 30f3bf5 against latest (e560755)

  🔍 Preview build  

4 files changed
± changelog.html
± api/reference.html
± guide/manual.html
± guide/usage.html

@rafatahmed
rafatahmed merged commit 0aacd27 into main Oct 20, 2026
6 checks passed
@rafatahmed
rafatahmed deleted the feat/recipe-feasibility-report branch October 20, 2026 18:10
@rafatahmed rafatahmed self-assigned this Oct 23, 2026
rafatahmed added a commit that referenced this pull request Nov 7, 2026
Explain every recipe miss; ammonium limit, formula balance, faster ceilings
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
0