Enable retrieving alkalinity from phreeqc2026 engine - #459
Conversation
|
Disclosure: I used Claude to help, but the changes are few and make sense to me. Sorry about lumping removal of |
rkingsbury
left a comment
There was a problem hiding this comment.
Thanks very much @jjstickel ; this is a helpful contribution! I'd like to proceed but with a few structural changes:
- If not too much trouble, can you split the
.gitignorechanges into another PR? That is a very helpful change; I'd just prefer to keep it separate - The changes to expose
ALKto the underlying PHREEQC solution (pyEQL/phreeqc/core.pyandsolution.py) are very helpful; I'm happy with those as-is - I'm not (yet) ready to make
get_alkalinityan engine method, so can you please revert the changes inpyEQL/engines.pyandpyEQL/solution.py? In general, I we try to use engine-specific methods only for those quantities that do not have an unambiguous definition, and rely onpyEQL.Solutionnative methods for everything else.
Regarding 3 - you have highlighted an important discrepancy between the pyEQL.Solution alkalinity and the underlying PHREEQC one which I want to instead treat as a bug in get_alkalinity. We recently implemented the weak acid-base definition of alkalinity (see #299 and #398 ) and perhaps that introduced a bug we weren't aware of. OR perhaps it isn't appropriate to rely on the Stumm and Morgan definition as the default (which is our current approach). Maybe it's more appropriate to use the proton condition as default.
I welcome additional thoughts!
|
Thanks for the feedback. I'll work on these as I have time (likely later this week or next week). |
|
FYI, I (actually Claude Code) took care of the |
aca095e to
9d5673d
Compare
I added a keyword argument to select between returning the alkalinity calculated by the engine vs. by pyEQL, and set it to be pyEQL by default. I work with geochemists who have been using PHREEQC for years and trust it a great deal. It is, and will continue to be, necessary for me in my work to report the alkalinity that PHREEQC calculates. Also, from the current bug in alkalinity, it seems that alkalinity calculations are not always straightforward, and so having a feature to calculate it a couple different ways could be useful. (I am not a geochemist, and so I am not able to suggest how to improve pyEQL's alkalinity calculation without performing some literature research). Does the keyword approach work for you? If this all gets sorted and everyone is convinced that pyEQL is doing the correct calculation [1], then the keyword argument could be removed in the future. [1] If the "correct" alkalinity calculation ends up being different from PHREEQC, even by a small fraction, it would be important to document why the calculations are different. |
Yes, totally understand. I'm concerned that there is this big a difference between what PHREEQC vs. pyEQL are returning. Clearly something going on.
I'm not opposed to a keyword argument if there turns out to be some structural difference in the calculations that's correct, but I still feel we shouldn't implement that (especially at the class level) until the bug is better understood. I've done a bit of digging (see the Issue thread) and this seems related to complexes and complex solutions (hence why it appears after an How about this - let's contain your fix to the body of And you would revert the changes in I feel confident that we can find a way to fix the behavior of the built-in |
1aefa52 to
bb43461
Compare
|
That's great you were able to fix the internal alkalinity calculation in #462. However, the alkalinity calculated by phreeqc is still different by more than 1% for seawater (114 vs 117 mg/L).[1] I've made changes to this PR to use the engine's alkalinity for an internal check (but removing the code from engines.py, like you requested). You can let me know what you think about keeping this check or something like it. Separately, is it helpful to keep the line, in [1] Interestingly, both values also increase by about 1 mg/L after equilibration with air. |
rkingsbury
left a comment
There was a problem hiding this comment.
That's great you were able to fix the internal alkalinity calculation in #462. However, the alkalinity calculated by phreeqc is still different by more than 1% for seawater (114 vs 117 mg/L).[1]
Interesting. At that magnitude, the difference could be something subtle like a difference in the precision of the molecular weights used in the two respective databases. In any case, once you've finished putting this infrastructure in place to compare the two directly, it'll be much easier to pin down.
I've made changes to this PR to use the engine's alkalinity for an internal check (but removing the code from engines.py, like you requested). You can let me know what you think about keeping this check or something like it.
Thank you for the edits. Yes, I'm OK with this approach at least for now; there's no harm in a warning. I requested several specific changes on the implementation to align with the norms in our repo, but nothing changes at a high level.
Separately, is it helpful to keep the line,
self._stored_comp = solution.components.copy()in
pyEQL/engines.py? Claude suggested it as an efficiency improvement, but I don't know the base code well enough to know if it should be kept.
Yes, I think so! See comment, but ideally I'd prefer it in a dedicated PR. Claude Code should be able to do that very easily.
Finally, you will need to make edits to two places in test/phreeqc/test_phreeqc.py to account for the fact that there is now an extra column (ALK) coming out of the phreeqc output, e.g.
> assert set(props.keys()) == set(expected[solution_index].keys())
E assert {'ALK', 'CELL...s', 'species'} == {'CELL_NO', '...s', 'species'}
E
E Extra items in the left set:
E 'ALK'
E
E Full diff:
E {
E + 'ALK',
E 'CELL_NO',
E 'OSMOTIC',
E "TOT['water']",
E 'eq_species',
E 'species',
E }
and
> assert phreeqc.get_selected_output_column_count() == 27
E assert 28 == 27
E + where 28 = get_selected_output_column_count()
E + where get_selected_output_column_count = <pyEQL.phreeqc.core.Phreeqc object at 0x7f0b355dcb40>.get_selected_output_column_count
…ranch) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P3tRiwXYW569tAp8vMye9N
bc392ea to
b11335d
Compare
b11335d to
b879c83
Compare
Adds ALK to the expected USER_PUNCH string, bumps the selected-output column count 27->28, and adds ALK values to test_species_all_props. Uses an absolute tolerance for the scalar prop comparison so solution 0's near-zero alkalinity stays robust across platforms. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Pure water alkalinity is zero to within numerical noise; assert it as approx(0.0) rather than pinning the exact near-zero value. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Thank you @jjstickel ! I jumped in and updated I left in place the one remaining test failure, which I realized is a consequence of the way we detect the engine: The The fix is to change to The If you agree with that change, I'm ready to merge once it lands. |
|
Thanks for correcting the tests.
This didn't work, maybe because Instead, I changed the try-except block to also catch the AttributeError. Does that work for you? |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #459 +/- ##
==========================================
+ Coverage 87.67% 87.78% +0.10%
==========================================
Files 14 14
Lines 1972 1989 +17
Branches 344 347 +3
==========================================
+ Hits 1729 1746 +17
Misses 192 192
Partials 51 51 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
Thank you @jjstickel ! Now I can let Claude Code dig into the PHREEQC vs. pyEQL discrepancy. |
Summary
Major changes:
Solution.engine.get_alkalinity()phreeqc2026engineTodos
Checklist
ruff. (For guidance in fixing rule violates, see rule list)mypy.Tip: Install
pre-commithooks to auto-check types and linting before every commit: