-
Notifications
You must be signed in to change notification settings - Fork 23
Adding tests for solid-liquid equilibrium in equilibrate() #295
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
package, installed with the "phreeqc" extra.
…hon incompatibility)
|
Hi @vineetbansal , I ran the pyEQL |
|
The main issue is related to the saturation index should default to 0 ( Example: Calcite 0 100 instead of Calcite 1 100 |
tests/test_phreeqc.py
Outdated
| total_alk = HCO3 + 2 * CO3 + OH - H | ||
| assert alk.to("mg/L").magnitude == pytest.approx(total_alk, abs=0.01) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here it appears you're comparing alkalinity calculated in mol/L with the value returned from the wrapper converted to mg/L. Why do you expect these to be equal?
Also the tolerance here is 0.01, but the total amount of CO2 in the starting solution is only 0.001 mol/L, so I think the test might be passing just because the tolerance is so high.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When running the following test:
def test_alkalinity():
solution = Solution({"CO2(aq)": "0.001 mol/L"}, pH=7, volume="1 L", engine="phreeqc")
solution.equilibrate()
alk = solution.alkalinity
# Total alkalinity
HCO3 = solution.get_amount("HCO3-", "mg/L").magnitude
CO3 = solution.get_amount("CO3-2", "mg/L").magnitude
OH = solution.get_amount("OH-", "mg/L").magnitude
H = solution.get_amount("H+", "mg/L").magnitude
# Alkalinity calculated from the excess of negative charges from weak acids
total_alk = HCO3 + 2 * CO3 + OH - H
assert alk.to("mg/L").magnitude == pytest.approx(total_alk, abs=0.001)
I encountered the following error:
============= short test summary info =========================
FAILED test_phreeqc.py::test_alkalinity - assert 0.0 == 50.05633916820964 ± 1.0e-03
The alkalinity units appears to be handling properly in mg/L but yields an incorrect value.
Upon inspecting def alkalinity(self), it looks like the carbonate species (HCO3-, CO3-2) are not included in the calculation, which explains why the method returns 0 mg/L. Would it make sense to create a separate PR to add carbonate alkalinity before we proceed with the test?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See my comment on this below - please mark this test an expected failure for now, and open an Issue with the content of this post. This results from a discrepancy in how pyEQL defines alkalinity, so it doesn't really indicate a problem with equilibrate, but it is something we should look into.
tests/test_phreeqc.py
Outdated
| def test_equilibrate_liquid(): | ||
| solution = Solution([["Cu+2", "4 mol/L"], ["O-2", "4 mol/L"]], volume="2 L", engine="phreeqc") | ||
| # Specifying an unrecognized solid raises an Exception | ||
| with pytest.raises(Exception): # noqa: B017, PT011 | ||
| solution.equilibrate(solids=["Ferroxite"]) | ||
| solution.equilibrate() | ||
| assert solution.get_total_amount("Cu", "mol").magnitude == pytest.approx(1.654340558452752) | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please change this test to Ca2+ instead of copper, and use a lower concentration like 0.05 M so that the Ca doesn't precipitate. That way the expected result (0.05 M * 2 L) will be much clearer
…nto pytest_vb186
Phreeqc wrapper
rkingsbury
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@SuixiongTay and @YitongPan1 , thank you for your work on these tests. With these recent changes they are getting close to ready - please both of you read the comments I've made, ask any technical questions especially if the rationale doesn't make sense to you, and do your best to address.
tests/test_phreeqc.py
Outdated
| def test_equilibrate_water_pH7(): | ||
| solution = Solution({}, pH=7.00, temperature="25 degC", volume="1 L", engine="phreeqc") | ||
| solution.equilibrate() | ||
| # pH = -log10[H+] | ||
| assert solution.get_amount("H+", "mol").magnitude == pytest.approx(1.0e-07 * 0.997, rel=0.01) | ||
| # 14 - pH = log10[OH-] | ||
| assert solution.get_amount("OH-", "mol").magnitude == pytest.approx(1.01e-07 * 0.997, rel=0.01) | ||
| # small amount of H2 gas | ||
| assert solution.get_amount("H2", "mol").magnitude == pytest.approx(0 * 0.997, rel=0.01) | ||
| # 55.5 mol/kg * 0.997 kg = total moles of water | ||
| assert solution.get_amount("H2O", "mol").magnitude == pytest.approx(55.5 * 0.997, rel=0.01) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good. Two comments here:
- Since
pH = -log10[H+], pH 7 equals 1e-7 mol/L, and you have set the volume of the solution as 1 L. Therefore, I think themolof H+ should be exactly 1e-7; you don't need to adjust for the density (0.997). - I think your test tolerance is too large (too easy to pass).
rel=0.01means that the actual and expected results can differ by 1%. That's ok in some cases (like comparing a model to experiment), but here we know exactly what the answer should be. Because of that, I'd prefer to see an absolute rather than a relative tolerance, and I would think at least 10x smaller than your expected result (so,atol=1e-8) or lower would be appropriate.
If the test won't pass with that tolerance, we can look at why and decide whether it's numerical noise, something unexpected that equlibrate is doing, etc.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @rkingsbury ,
For this case, the output of PHREEQC is in mol/kg rather than mol, so a unit conversion may still be needed to make this part work correctly.
We have also noticed a difference in the density values reported by pyEQL and PHREEQC: for this solution, pyEQL gives a density of 0.99704 kg/L, while PHREEQC reports 0.99757 kg/L. If the tolerance is set too low, this difference could cause the tests to fail.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@YitongPan1, @SuixiongTay - for the simple phreeqc script:
SOLUTION 1
temp 25
I'm seeing the output (among other lines):
----------------------------Description of solution----------------------------
pH = 7.000
pe = 4.000
Specific Conductance (uS/cm, 25oC) = 0
Density (g/cm3) = 0.99704
So phreeqc's output matches pyEQL's (as it should). Let me know where you're seeing 0.99757 kg/L.
Given that this is the case, I'm wondering if its okay to change this test case to its simplest form (which does pass):
def test_equilibrate_water_pH7():
solution = Solution({}, pH=7.00, temperature="25 degC", volume="1 L", engine="phreeqc")
solution.equilibrate()
# pH = -log10[H+]
assert np.isclose(solution.get_amount("H+", "mol/kg").magnitude, 1e-07)
# # 14 - pH = log10[OH-]
assert np.isclose(solution.get_amount("OH-", "mol/kg").magnitude, 1e-07)
# # small amount of H2 gas
assert solution.get_amount("H2", "mol/kg").magnitude > 0
# Density of H2O in phreeqc = 0.99704 kg/L
# For 0.99704 kg H2O
# mol = 0.99704 kg * 1000 g/kg / (18.01528 g/mol) = 55.34413009400909
# mol/kg = 55.34413009400909 / 0.99704 = 55.50843506179199
assert np.isclose(solution.get_amount("H2O", "mol/kg").magnitude, 55.50843506179199)
The default tolerance for np.isclose is 1e-8 anyway so I think its better to leave it out.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes I'm good with your changes here @vineetbansal
tests/test_phreeqc.py
Outdated
| def test_equilibrate_FeO3H3_ppt(): | ||
| solution = Solution({"Fe+3": "0.01 mol/L", "OH-": "10**-7 mol/L"}, volume="1 L", engine="phreeqc") | ||
| solution.equilibrate() | ||
| # Under supersaturation, Fe(OH)3 precipitates out with Ksp = 10**-38.8 | ||
| Fe3_act_coef = solution.get_activity_coefficient("Fe+3") | ||
| OH_act_coef = solution.get_activity_coefficient("OH-") | ||
| Fe3_conc = solution.get_amount("Fe+3", "mol/L").magnitude | ||
| OH_conc = solution.get_amount("OH-", "mol/L").magnitude | ||
| SI_FeO3H3 = np.log10((Fe3_act_coef.magnitude * Fe3_conc) * (OH_act_coef.magnitude * OH_conc) ** 3 / 10**-38.8) | ||
| assert solution.engine.ppsol.si("Fe(OH)3(a)") > 0 | ||
| assert solution.engine.ppsol.si("Fe(OH)3(a)") == pytest.approx(SI_FeO3H3, rel=0.5) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the thought process here. To streamline this test:
- You can use
get_activity('Fe_3')to directly get the activity instead of having to separately get the coefficient and the concentration - In the
assertstatements, since you are already testing thesinumerically (2nd test), there's no reason to also test that it's > 0. Please delete the firstassert. - The existing
assertchecks the underlying engine (.engine.ppsol.si) but not the state of theSolutioninstance itself. Add anassertthat check the total Fe concentration in the solution is less than 0.01 mol/L (indicating that some has precipitated).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The saturation index calculation is somewhat tricky as it deviates from the typical SI = log(IAP/pKsp) calculation. We have included the assertion assert solution.engine.ppsol.si("Fe(OH)3(a)") > 0 to check the expected behavior for now.
tests/test_phreeqc.py
Outdated
| assert solution.get_total_amount("Cu", "mol").magnitude == pytest.approx(0.001 * 2, rel=0.01) | ||
| assert solution.get_total_amount("N(0)", "mol").magnitude == pytest.approx(0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add asserts that also verify both Oxygen and CO2(g) are in solution. Preferably, compute the expected concentrations from the respective Henry's law constants and test each quantitatively.
PHREEQC pytest revision|
Closing to continue discussion in #292 |
Summary
This PR revises the new test functions to
tests/test_phreeqc.py: