pyEQL 1.1.2__tar.gz → 1.1.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pyeql-1.1.2 → pyeql-1.1.4}/CHANGELOG.md +21 -0
- {pyeql-1.1.2/src/pyEQL.egg-info → pyeql-1.1.4}/PKG-INFO +1 -1
- {pyeql-1.1.2 → pyeql-1.1.4}/docs/changelog.md +21 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/src/pyEQL/engines.py +2 -14
- {pyeql-1.1.2 → pyeql-1.1.4}/src/pyEQL/solution.py +51 -12
- {pyeql-1.1.2 → pyeql-1.1.4/src/pyEQL.egg-info}/PKG-INFO +1 -1
- {pyeql-1.1.2 → pyeql-1.1.4}/tests/test_solution.py +32 -6
- {pyeql-1.1.2 → pyeql-1.1.4}/.coveragerc +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/.gitattributes +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/.github/dependabot.yml +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/.github/pull_request_template.md +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/.github/release.yml +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/.github/workflows/post-process.yml +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/.github/workflows/release.yml +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/.github/workflows/testing.yaml +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/.github/workflows/upgrade_dependencies.yml +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/.gitignore +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/.pre-commit-config.yaml +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/.readthedocs.yml +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/.zenodo.json +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/AUTHORS.md +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/CITATION.cff +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/COPYING +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/LICENSE.txt +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/MANIFEST.in +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/README.md +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/docs/Makefile +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/docs/_static/.gitignore +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/docs/amounts.md +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/docs/arithmetic.md +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/docs/authors.md +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/docs/chemistry.md +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/docs/class_solution.md +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/docs/conf.py +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/docs/contributing.md +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/docs/creating.md +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/docs/database.md +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/docs/engines.md +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/docs/examples/.ipynb_checkpoints/pyEQL_demo_1-checkpoint.ipynb +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/docs/examples/.ipynb_checkpoints/pyeql_demo-checkpoint.ipynb +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/docs/examples/.ipynb_checkpoints/pyeql_tutorial_database-checkpoint.ipynb +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/docs/examples/.ipynb_checkpoints/pyeql_tutorial_osmotic_pressure-checkpoint.ipynb +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/docs/examples/.ipynb_checkpoints/speedup-checkpoint.ipynb +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/docs/examples/pyEQL_demo_1.ipynb +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/docs/examples/pyeql_demo.ipynb +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/docs/examples/pyeql_tutorial_database.ipynb +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/docs/examples/pyeql_tutorial_osmotic_pressure.ipynb +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/docs/examples/speedup.ipynb +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/docs/index.md +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/docs/installation.md +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/docs/internal.md +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/docs/license.md +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/docs/mixing.md +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/docs/quickstart.md +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/docs/readme.md +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/docs/requirements.txt +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/docs/serialization.md +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/docs/tutorials.md +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/docs/units.md +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/pyeql-logo.png +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/pyeql-logo.svg +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/pyproject.toml +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/requirements/macos-latest_py3.10.txt +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/requirements/macos-latest_py3.10_extras.txt +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/requirements/macos-latest_py3.11.txt +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/requirements/macos-latest_py3.11_extras.txt +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/requirements/macos-latest_py3.12.txt +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/requirements/macos-latest_py3.12_extras.txt +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/requirements/macos-latest_py3.9.txt +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/requirements/macos-latest_py3.9_extras.txt +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/requirements/ubuntu-latest_py3.10.txt +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/requirements/ubuntu-latest_py3.10_extras.txt +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/requirements/ubuntu-latest_py3.11.txt +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/requirements/ubuntu-latest_py3.11_extras.txt +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/requirements/ubuntu-latest_py3.12.txt +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/requirements/ubuntu-latest_py3.12_extras.txt +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/requirements/ubuntu-latest_py3.9.txt +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/requirements/ubuntu-latest_py3.9_extras.txt +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/requirements/windows-latest_py3.10.txt +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/requirements/windows-latest_py3.10_extras.txt +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/requirements/windows-latest_py3.11.txt +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/requirements/windows-latest_py3.11_extras.txt +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/requirements/windows-latest_py3.12.txt +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/requirements/windows-latest_py3.12_extras.txt +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/requirements/windows-latest_py3.9.txt +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/requirements/windows-latest_py3.9_extras.txt +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/setup.cfg +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/setup.py +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/src/pyEQL/__init__.py +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/src/pyEQL/activity_correction.py +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/src/pyEQL/database/geothermal.dat +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/src/pyEQL/database/llnl.dat +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/src/pyEQL/database/phreeqc_license.txt +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/src/pyEQL/database/pyeql_db.json +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/src/pyEQL/equilibrium.py +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/src/pyEQL/functions.py +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/src/pyEQL/presets/Ringers lactate.yaml +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/src/pyEQL/presets/normal saline.yaml +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/src/pyEQL/presets/rainwater.yaml +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/src/pyEQL/presets/seawater.yaml +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/src/pyEQL/presets/urine.yaml +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/src/pyEQL/presets/wastewater.yaml +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/src/pyEQL/salt_ion_match.py +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/src/pyEQL/solute.py +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/src/pyEQL/utils.py +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/src/pyEQL.egg-info/SOURCES.txt +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/src/pyEQL.egg-info/dependency_links.txt +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/src/pyEQL.egg-info/requires.txt +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/src/pyEQL.egg-info/top_level.txt +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/tests/conftest.py +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/tests/test_activity.py +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/tests/test_bulk_properties.py +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/tests/test_debye_length.py +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/tests/test_density.py +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/tests/test_dielectric.py +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/tests/test_effective_pitzer.py +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/tests/test_equilibrium.py +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/tests/test_functions.py +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/tests/test_logging.py +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/tests/test_mixed_electrolyte_activity.py +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/tests/test_osmotic_coeff.py +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/tests/test_phreeqc.py +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/tests/test_salt_matching.py +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/tests/test_solute.py +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/tests/test_solute_properties.py +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/tests/test_utils.py +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/tests/test_volume_concentration.py +0 -0
- {pyeql-1.1.2 → pyeql-1.1.4}/tox.ini +0 -0
|
@@ -5,6 +5,27 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.1.4] - 2024-07-28
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- `Solution`: Fix a bug in`pH` charge balancing that erroneously prevented charge
|
|
13
|
+
balancing from occurring in certain cases, and raised an error.
|
|
14
|
+
|
|
15
|
+
## [1.1.3] - 2024-07-28
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- `Solution`: Fix a bug in which setting `balance_charge` to `pH` could result in
|
|
20
|
+
negative concentration errors when charge balancing or after `equilibrate` was
|
|
21
|
+
called. `Solution` now correctly enforces the ion product of water (Kw=1e-14)
|
|
22
|
+
whenever adjusting the amounts of H+ or OH- for charge balancing.
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
|
|
26
|
+
- `Solution._adjust_charge_balance`: Added a privat helper method to consolidate charge
|
|
27
|
+
balancing code used in `__init__` and `equilibrate`.
|
|
28
|
+
|
|
8
29
|
## [1.1.2] - 2024-07-28
|
|
9
30
|
|
|
10
31
|
### Fixed
|
|
@@ -5,6 +5,27 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.1.4] - 2024-07-28
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- `Solution`: Fix a bug in`pH` charge balancing that erroneously prevented charge
|
|
13
|
+
balancing from occurring in certain cases, and raised an error.
|
|
14
|
+
|
|
15
|
+
## [1.1.3] - 2024-07-28
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- `Solution`: Fix a bug in which setting `balance_charge` to `pH` could result in
|
|
20
|
+
negative concentration errors when charge balancing or after `equilibrate` was
|
|
21
|
+
called. `Solution` now correctly enforces the ion product of water (Kw=1e-14)
|
|
22
|
+
whenever adjusting the amounts of H+ or OH- for charge balancing.
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
|
|
26
|
+
- `Solution._adjust_charge_balance`: Added a privat helper method to consolidate charge
|
|
27
|
+
balancing code used in `__init__` and `equilibrate`.
|
|
28
|
+
|
|
8
29
|
## [1.1.2] - 2024-07-28
|
|
9
30
|
|
|
10
31
|
### Fixed
|
|
@@ -687,20 +687,8 @@ class NativeEOS(EOS):
|
|
|
687
687
|
)
|
|
688
688
|
|
|
689
689
|
# re-adjust charge balance for any missing species
|
|
690
|
-
# note that if balance_charge is set, it will have been passed to PHREEQC, so
|
|
691
|
-
|
|
692
|
-
charge_adjust = 0
|
|
693
|
-
for s in missing_species:
|
|
694
|
-
charge_adjust += -1 * solution.get_amount(s, "eq").magnitude
|
|
695
|
-
if charge_adjust != 0:
|
|
696
|
-
logger.warning(
|
|
697
|
-
"After equilibration, the charge balance of the solution was not electroneutral."
|
|
698
|
-
f" {charge_adjust} eq of charge were added via {solution._cb_species}"
|
|
699
|
-
)
|
|
700
|
-
|
|
701
|
-
if solution.balance_charge is not None:
|
|
702
|
-
z = solution.get_property(solution._cb_species, "charge")
|
|
703
|
-
solution.add_amount(solution._cb_species, f"{charge_adjust/z} mol")
|
|
690
|
+
# note that if balance_charge is set, it will have been passed to PHREEQC, so the only reason to re-adjust charge balance here is to account for any missing species.
|
|
691
|
+
solution._adjust_charge_balance()
|
|
704
692
|
|
|
705
693
|
# rescale the solvent mass to ensure the total mass of solution does not change
|
|
706
694
|
# this is important because PHREEQC and the pyEQL database may use slightly different molecular
|
|
@@ -35,6 +35,7 @@ from pyEQL.utils import FormulaDict, create_water_substance, interpret_units, st
|
|
|
35
35
|
EQUIV_WT_CACO3 = ureg.Quantity(100.09 / 2, "g/mol")
|
|
36
36
|
# string to denote unknown oxidation states
|
|
37
37
|
UNKNOWN_OXI_STATE = "unk"
|
|
38
|
+
K_W = 1e-14 # ion product of water at 25 degC
|
|
38
39
|
|
|
39
40
|
|
|
40
41
|
class Solution(MSONable):
|
|
@@ -242,7 +243,7 @@ class Solution(MSONable):
|
|
|
242
243
|
|
|
243
244
|
# set the pH with H+ and OH-
|
|
244
245
|
self.add_solute("H+", str(10 ** (-1 * pH)) + "mol/L")
|
|
245
|
-
self.add_solute("OH-", str(10 ** (-1 *
|
|
246
|
+
self.add_solute("OH-", str(K_W / (10 ** (-1 * pH))) + "mol/L")
|
|
246
247
|
|
|
247
248
|
# populate the other solutes
|
|
248
249
|
self._solutes = solutes
|
|
@@ -288,17 +289,7 @@ class Solution(MSONable):
|
|
|
288
289
|
)
|
|
289
290
|
|
|
290
291
|
# adjust charge balance, if necessary
|
|
291
|
-
|
|
292
|
-
balanced = False
|
|
293
|
-
self.logger.info(
|
|
294
|
-
f"Solution is not electroneutral (C.B. = {cb} eq/L). Adding {self._cb_species} to compensate."
|
|
295
|
-
)
|
|
296
|
-
z = self.get_property(self._cb_species, "charge")
|
|
297
|
-
self.components[self._cb_species] += -1 * cb / z * self.volume.to("L").magnitude
|
|
298
|
-
if np.isclose(self.charge_balance, 0, atol=1e-8):
|
|
299
|
-
balanced = True
|
|
300
|
-
if not balanced:
|
|
301
|
-
warnings.warn(f"Unable to balance charge using species {self._cb_species}")
|
|
292
|
+
self._adjust_charge_balance()
|
|
302
293
|
|
|
303
294
|
@property
|
|
304
295
|
def mass(self) -> Quantity:
|
|
@@ -2293,6 +2284,54 @@ class Solution(MSONable):
|
|
|
2293
2284
|
|
|
2294
2285
|
return distance.to("nm")
|
|
2295
2286
|
|
|
2287
|
+
def _adjust_charge_balance(self, atol=1e-8) -> None:
|
|
2288
|
+
"""Helper method to adjust the charge balance of the Solution."""
|
|
2289
|
+
cb = self.charge_balance
|
|
2290
|
+
if not np.isclose(cb, 0, atol=atol):
|
|
2291
|
+
self.logger.info(f"Solution is not electroneutral (C.B. = {cb} eq/L).")
|
|
2292
|
+
if self.balance_charge is None:
|
|
2293
|
+
# Nothing to do.
|
|
2294
|
+
self.logger.info("balance_charge is None, so no charge balancing will be performed.")
|
|
2295
|
+
return
|
|
2296
|
+
|
|
2297
|
+
self.logger.info(
|
|
2298
|
+
f"Solution is not electroneutral (C.B. = {cb} eq/L). Adjusting {self._cb_species} to compensate."
|
|
2299
|
+
)
|
|
2300
|
+
|
|
2301
|
+
if self.balance_charge == "pH":
|
|
2302
|
+
# the charge imbalance associated with the H+ / OH- system can be expressed
|
|
2303
|
+
# as ([H+] - [OH-]) or ([H+] - K_W/[H+]). If we adjust H+, we also have to
|
|
2304
|
+
# adjust OH- to maintain water equilibrium.
|
|
2305
|
+
C_hplus = self.get_amount("H+", "mol/L").magnitude
|
|
2306
|
+
start_imbalance = C_hplus - K_W / C_hplus
|
|
2307
|
+
new_imbalance = start_imbalance - cb
|
|
2308
|
+
# calculate the new concentration of H+ that will balance the charge
|
|
2309
|
+
# solve H^2 - new_imbalance H - K_W = 0, so a=1, b=-new_imbalance, c=-K_W. Note that this is guaranteed to have real roots as
|
|
2310
|
+
# b^2-4ac > 0
|
|
2311
|
+
new_hplus = max(
|
|
2312
|
+
[
|
|
2313
|
+
(new_imbalance + np.sqrt(new_imbalance**2 + 4 * 1 * K_W)) / 2,
|
|
2314
|
+
(new_imbalance - np.sqrt(new_imbalance**2 + 4 * 1 * K_W)) / 2,
|
|
2315
|
+
]
|
|
2316
|
+
)
|
|
2317
|
+
self.set_amount("H+", f"{new_hplus} mol/L")
|
|
2318
|
+
self.set_amount("OH-", f"{K_W/new_hplus} mol/L")
|
|
2319
|
+
assert np.isclose(self.charge_balance, 0, atol=atol), f"{self.charge_balance}"
|
|
2320
|
+
return
|
|
2321
|
+
|
|
2322
|
+
z = self.get_property(self._cb_species, "charge")
|
|
2323
|
+
try:
|
|
2324
|
+
self.add_amount(self._cb_species, f"{-1*cb/z} mol")
|
|
2325
|
+
return
|
|
2326
|
+
except ValueError:
|
|
2327
|
+
# if the concentration is negative, it must mean there is not enough present.
|
|
2328
|
+
# remove everything that's present and log an error.
|
|
2329
|
+
self.components[self._cb_species] = 0
|
|
2330
|
+
self.logger.error(
|
|
2331
|
+
f"There is not enough {self._cb_species} present to balance the charge. Try a different species."
|
|
2332
|
+
)
|
|
2333
|
+
return
|
|
2334
|
+
|
|
2296
2335
|
def _update_volume(self):
|
|
2297
2336
|
"""Recalculate the solution volume based on composition."""
|
|
2298
2337
|
self._volume = self._get_solvent_volume() + self._get_solute_volume()
|
|
@@ -202,8 +202,8 @@ def test_init_engines():
|
|
|
202
202
|
|
|
203
203
|
|
|
204
204
|
def test_component_subsets(s2):
|
|
205
|
-
assert s2.cations ==
|
|
206
|
-
assert s2.anions ==
|
|
205
|
+
assert list(s2.cations.keys()) == ["Na[+1]", "H[+1]"]
|
|
206
|
+
assert list(s2.anions.keys()) == ["Cl[-1]", "OH[-1]"]
|
|
207
207
|
assert list(s2.neutrals.keys()) == ["H2O(aq)"]
|
|
208
208
|
|
|
209
209
|
|
|
@@ -285,6 +285,32 @@ def test_charge_balance(s3, s5, s5_pH, s6, s6_Ca):
|
|
|
285
285
|
assert s._cb_species == "Cl[-1]"
|
|
286
286
|
assert np.isclose(s.charge_balance, 0, atol=1e-8)
|
|
287
287
|
|
|
288
|
+
# check 'pH' when the solution needs to be made more POSITIVE
|
|
289
|
+
s = Solution({"Na+": "2 mM", "Cl-": "1 mM"}, balance_charge="pH", pH=4)
|
|
290
|
+
assert s.balance_charge == "pH"
|
|
291
|
+
assert s._cb_species == "H[+1]"
|
|
292
|
+
assert np.isclose(s.charge_balance, 0, atol=1e-8)
|
|
293
|
+
assert s.pH > 4
|
|
294
|
+
s.equilibrate()
|
|
295
|
+
assert s.balance_charge == "pH"
|
|
296
|
+
assert s._cb_species == "H[+1]"
|
|
297
|
+
assert np.isclose(s.charge_balance, 0, atol=1e-8)
|
|
298
|
+
|
|
299
|
+
# check 'pH' when the imbalance is extreme
|
|
300
|
+
s = Solution({"Na+": "2 mM", "Cl-": "1 M"}, balance_charge="pH", pH=4)
|
|
301
|
+
assert s.balance_charge == "pH"
|
|
302
|
+
assert s._cb_species == "H[+1]"
|
|
303
|
+
assert np.isclose(s.charge_balance, 0, atol=1e-8)
|
|
304
|
+
assert np.isclose(s.pH, 0, atol=0.1)
|
|
305
|
+
s.equilibrate()
|
|
306
|
+
assert s.balance_charge == "pH"
|
|
307
|
+
assert s._cb_species == "H[+1]"
|
|
308
|
+
assert np.isclose(s.charge_balance, 0, atol=1e-8)
|
|
309
|
+
|
|
310
|
+
# check warning when there isn't enough to balance
|
|
311
|
+
s = Solution({"Na+": "1 M", "K+": "2 mM", "Cl-": "2 mM"}, balance_charge="K+")
|
|
312
|
+
assert s.get_amount("K+", "mol/L") == 0
|
|
313
|
+
|
|
288
314
|
# check "auto" with an electroneutral solution
|
|
289
315
|
s = Solution({"Na+": "2 mM", "Cl-": "2 mM"}, balance_charge="auto")
|
|
290
316
|
assert s.balance_charge == "auto"
|
|
@@ -405,16 +431,16 @@ def test_components_by_element(s1, s2):
|
|
|
405
431
|
assert s1.get_components_by_element() == {
|
|
406
432
|
"H(1.0)": [
|
|
407
433
|
"H2O(aq)",
|
|
408
|
-
"H[+1]",
|
|
409
434
|
"OH[-1]",
|
|
435
|
+
"H[+1]",
|
|
410
436
|
],
|
|
411
437
|
"O(-2.0)": ["H2O(aq)", "OH[-1]"],
|
|
412
438
|
}
|
|
413
439
|
assert s2.get_components_by_element() == {
|
|
414
440
|
"H(1.0)": [
|
|
415
441
|
"H2O(aq)",
|
|
416
|
-
"H[+1]",
|
|
417
442
|
"OH[-1]",
|
|
443
|
+
"H[+1]",
|
|
418
444
|
],
|
|
419
445
|
"O(-2.0)": ["H2O(aq)", "OH[-1]"],
|
|
420
446
|
"Na(1.0)": ["Na[+1]"],
|
|
@@ -498,8 +524,8 @@ def test_equilibrate(s1, s2, s5_pH):
|
|
|
498
524
|
orig_solv_mass = s5_pH.solvent_mass.magnitude
|
|
499
525
|
set(s5_pH.components.keys())
|
|
500
526
|
s5_pH.equilibrate()
|
|
501
|
-
assert np.isclose(s5_pH.get_total_amount("Ca", "mol").magnitude, 0.001)
|
|
502
|
-
assert np.isclose(s5_pH.get_total_amount("C(4)", "mol").magnitude, 0.001)
|
|
527
|
+
assert np.isclose(s5_pH.get_total_amount("Ca", "mol").magnitude, 0.001, atol=1e-7)
|
|
528
|
+
assert np.isclose(s5_pH.get_total_amount("C(4)", "mol").magnitude, 0.001, atol=1e-7)
|
|
503
529
|
# due to the large pH shift, water mass and density need not be perfectly conserved
|
|
504
530
|
assert np.isclose(s5_pH.solvent_mass.magnitude, orig_solv_mass, atol=1e-3)
|
|
505
531
|
assert np.isclose(s5_pH.density.magnitude, orig_density, atol=1e-3)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|