8000 Converge ionic strength by secant and warm-start equilibrium searches (#61) by rafatahmed · Pull Request #62 · rafatahmed/Flahax · GitHub
[go: up one dir, main page]

Skip to content

Converge ionic strength by secant and warm-start equilibrium searches (#61) - #62

Merged
rafatahmed merged 2 commits into
mainfrom
fix/equilibrium-convergence-speed
Oct 20, 2026
Merged

rafatahmed merged 2 commits into
mainfrom
fix/equilibrium-convergence-speed

Conversation

@rafatahmed
Copy link
Copy Markdown
Owner

Closes #61.

Root cause

The #40 comparison runs the same inputs through PHREEQC (PhreeqPython) and FlahaX. It showed equilibrium solves 2–4× slower than PHREEQC, and searches about 200× slower. Two causes, both measured:

  1. Averaged ionic-strength fixed point. _solve set strength=(strength+updated)/2 (and the same for water) and stopped at |ΔI| < 1e-12, about 35 outer passes per solve, each a Newton loop.
  2. Cold starts in searches. The nitric-target, calculated-pH, acid-selection and measured-water searches restarted every solve from free activities at total/1000. The calculated-pH EC path did 28–39 cold solves per recipe.

Fix (pure Python, no dependencies)

  • Ionic strength: a safeguarded secant step on g(I) = I(speciation at I) − I, falling back to the old average. Water activity uses plain substitution, since its self-coupling is very weak. The 1e-12 stopping rule is unchanged.
  • solve(..., start=result) seeds free-ion activities, ionic strength, water and the precipitated set from a nearby state. A failed warm start falls back to the unchanged cold path. A warm start takes at least one Newton step, so it keeps the cold path's quadratic finish. Without this, calculated pH could not close |charge| ≤ 1e-12.
  • The four searches warm-start from their previous solve.

Evidence (same laptop, best runs)

Workload Before After
21 PHREEQC fixtures, single solves 2.7–84 ms 0.6–64 ms (3–5× on most)
Mixed-product nitric target 1078 ms 73 ms (PHREEQC: 4.5 ms)
Calculated-pH irrigation EC, 26 recipes 0.9–2.6 s 74–244 ms
Test suite 133 s 32 s

Results are unchanged within solver tolerance:

  • Species are within 7e-10 relative on every PHREEQC fixture.
  • The nitric dose is within 5e-10.
  • Calculated pH is within its 1e-7 bisection width, and EC within 2e-8 relative.

Owner decision: phosphate fit guard

The shipped H2PO4⁻/HPO4²⁻ coefficients are fitted through calculated pH, so a new iteration path moves the refit in the ninth digit (λ0 H2PO4⁻ 31.7300387184 → 31.7300386939; largest coefficient change 7.4e-9 relative; the Table III data is good to ~1e-4).

Remaining

A single cold solve of a chelate-rich mix still takes about 68 Newton iterations (mixed_products: 64 ms vs PHREEQC 3.5 ms). The cold-start strategy is a separate decision, so it is not changed here.

Tests

  • 361 tests pass, with 1 skipped as on main.
  • New tests/test_solver_convergence.py: warm start equals cold state, a warm start from the solution takes ≤ 2 Newton steps, the nitric dose is unchanged, and gypsum converges in < 60 iterations (was 102).

…rches (#61)

The ionic-strength/water fixed point averaged each update, so every solve
spent about 35 outer passes reaching |dI| < 1e-12. Ionic strength now takes
a safeguarded secant step on g(I) = I(speciation at I) - I (the average
remains the fallback) and water activity plain substitution.

solve() accepts start=, a nearby AqueousResult, which seeds free-ion
activities, ionic strength, water activity and the precipitated set; a
failed warm start falls back to the unchanged cold path. A warm start takes
at least one Newton step, so it keeps the cold path's quadratic finish.
nitric_target, calculated pH, acid selection and measured-water carbon and
balance searches warm-start from their previous solve.

Converged states are unchanged within solver tolerance (species <= 7e-10
relative on all PHREEQC fixtures). The nitric acid target drops from about
1080 to 73 ms, calculated-pH EC by 10-25x, the test suite from 133 to 40 s.
…phate refit at 1e-7

The phosphate conductivity refit speciates through calculated pH, whose
result is the midpoint of a bisection bracket up to 1e-7 pH wide, so the
faster iteration path moves the refit in the ninth digit (largest
coefficient change 7.4e-9 relative). Shipped coefficients stay unchanged;
the reproduction test now bounds numbers at 1e-7 relative and percentages
at 1e-5 points (owner decision).
@read-the-docs-community
Copy link
Copy Markdown

Documentation build overview

📚 Flahax | 🛠️ Build #35283002 | 📁 Comparing bed43ef against latest (50751e4)

  🔍 Preview build  

2 files changed
± changelog.html
± models/fertilizer-equilibrium-model.html

@rafatahmed
rafatahmed merged commit 06b0fce into main Oct 20, 2026
6 checks passed
8000 @rafatahmed
rafatahmed deleted the fix/equilibrium-convergence-speed branch October 20, 2026 22:38
@rafatahmed rafatahmed self-assigned this Oct 20, 2026
rafatahmed added a commit that referenced this pull request Nov 7, 2026
Converge ionic strength by secant and warm-start equilibrium searches (#61)
rafatahmed added a commit that referenced this pull request Nov 7, 2026
Snapshot 2026-10-21-competitors-ed568c5: formulation, chemistry and EC
track outputs from one run at ed568c5 (after #62), with the environment
record and a checksummed evidence.zip. tools/publish_competitor_snapshot.py
refuses a commit whose src/ or benchmarks/ differ from HEAD. The new
evidence page reports every engine, including results unfavourable to
FlahaX; the Benchmark v1 page links to it.
rafatahmed added a commit that referenced this pull request Nov 7, 2026
…failed gates (#66)

Hosted run 39511463809 on main at 4dffdf8 (after #62 and #65) is the first
capture with every correctness gate passing: 7/7 controlled NNLS cases,
21/21 stored chemistry comparisons and 17/17 timing workloads.

The publisher wrote 'The numerical scorecard is not fully passing'
unconditionally. scorecard_notice() now gives that warning only when an NNLS
case or chemistry comparison fails, a timing workload is missing or the
workflow did not succeed, and a note otherwise; both branches are tested.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Equilibrium solver spends ~35 damped ionic-strength passes per solve; acid targets ~200x slower than PHREEQC

1 participant

0