Repository navigation
Converge ionic strength by secant and warm-start equilibrium searches (#61) - #62
Merged
Merged
Conversation
…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).
This was referenced 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.
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.
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:
_solvesetstrength=(strength+updated)/2(and the same for water) and stopped at |ΔI| < 1e-12, about 35 outer passes per solve, each a Newton loop.Fix (pure Python, no dependencies)
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.Evidence (same laptop, best runs)
Results are unchanged within solver tolerance:
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
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).