Repository navigation
Explain every recipe miss; ammonium limit, formula balance, faster ceilings - #57
Merged
Merged
Conversation
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.
This was referenced Oct 20, 2026
rafatahmed
added a commit
that referenced
this pull request
Nov 7, 2026
Explain every recipe miss; ammonium limit, formula balance, faster ceilings
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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:
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
max_ammonium_fraction), or passNonefor 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, orconflicting_targets(with the conflicting targets and whether chloride products would reach it). The diagnosis is dependency-free least squares.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.Evidence (52 benchmark cases)
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
max_ammonium_fraction=None.Verification
sphinx -Wbuilds cleanly.Closes #39, closes #54, closes #55, closes #56.