Skip to content

Commit

Permalink
add auto charge balancing
Browse files Browse the repository at this point in the history
  • Loading branch information
rkingsbury committed Jul 26, 2024
1 parent 5f33691 commit 800eb88
Show file tree
Hide file tree
Showing 3 changed files with 42 additions and 9 deletions.
8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,20 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [1.0.4] - 2024-07-26
## [1.1.0] - 2024-07-26

### Fixed

- `equilibrate`: Fixed a bug that could cause an `AttributeError` when pH was used for charge
balancing.
- Database: `size.radius_ionic` was missing units for `Ni[+2]` and `Cr[+3]`. Correct units have been added.

### Added

- `Solution`: New automatic charge balancing method will automatically identify the majority (highest concentration)
cation or anion as appropriate (depending on the charge balance) for charge balancing. To use this mode, set
`balance_charge='auto'` when instantiating a `Solution`.

### Changed

- `Solution.add_amount`: This method will now add solutes that are absent from the Solution. Previously, calling, e.g.,
Expand Down
26 changes: 18 additions & 8 deletions src/pyEQL/solution.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,11 +92,15 @@ def __init__(
-7 to +14. The default value corresponds to a pE value typical of natural
waters in equilibrium with the atmosphere.
balance_charge: The strategy for balancing charge during init and equilibrium calculations. Valid options
are 'pH', which will adjust the solution pH to balance charge, 'pE' which will adjust the
redox equilibrium to balance charge, or the name of a dissolved species e.g. 'Ca+2' or 'Cl-'
that will be added/subtracted to balance charge. If set to None, no charge balancing will be
performed either on init or when equilibrate() is called. Note that in this case, equilibrate()
can distort the charge balance!
are
- 'pH', which will adjust the solution pH to balance charge,
- 'auto' which will use the majority cation or anion (i.e., that with the largest concentration)
as needed,
- 'pE' (not currently implemented) which will adjust the redox equilibrium to balance charge, or
the name of a dissolved species e.g. 'Ca+2' or 'Cl-' that will be added/subtracted to balance
charge.
- None (default), in which case no charge balancing will be performed either on init or when
equilibrate() is called. Note that in this case, equilibrate() can distort the charge balance!
solvent: Formula of the solvent. Solvents other than water are not supported at this time.
engine: Electrolyte modeling engine to use. See documentation for details on the available engines.
database: path to a .json file (str or Path) or maggma Store instance that
Expand Down Expand Up @@ -171,7 +175,7 @@ def __init__(
self._pE = pE
self._pH = pH
self.pE = self._pE
if isinstance(balance_charge, str) and balance_charge not in ["pH", "pE"]:
if isinstance(balance_charge, str) and balance_charge not in ["pH", "pE", "auto"]:
self.balance_charge = standardize_formula(balance_charge)
else:
self.balance_charge = balance_charge #: Standardized formula of the species used for charge balancing.
Expand Down Expand Up @@ -273,13 +277,19 @@ def __init__(
raise NotImplementedError("Balancing charge via redox (pE) is not yet implemented!")
else:
ions = set().union(*[self.cations, self.anions]) # all ions
if self.balance_charge == "auto":
# add the most abundant ion of the opposite charge
if cb < 0:
self.balance_charge = max(self.cations, key=self.cations.get)
elif cb >0:
self.balance_charge = max(self.anions, key=self.anions.get)

Check warning on line 285 in src/pyEQL/solution.py

View check run for this annotation

Codecov / codecov/patch

src/pyEQL/solution.py#L285

Added line #L285 was not covered by tests
if self.balance_charge not in ions:
raise ValueError(
f"Charge balancing species {self.balance_charge} was not found in the solution!. "
f"Species {ions} were found."
)
z = self.get_property(balance_charge, "charge")
self.components[balance_charge] += -1 * cb / z * self.volume.to("L").magnitude
z = self.get_property(self.balance_charge, "charge")
self.components[self.balance_charge] += -1 * cb / z * self.volume.to("L").magnitude
balanced = True

if not balanced:
Expand Down
17 changes: 17 additions & 0 deletions tests/test_solution.py
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,23 @@ def test_charge_balance(s3, s5, s5_pH, s6, s6_Ca):
assert np.isclose(s6.charge_balance, -0.12)
assert np.isclose(s6_Ca.charge_balance, 0, atol=1e-8)

# test auto charge balance
s=Solution(
[
["Ca+2", "1 mM"], # 2 meq/L
["Mg+2", "5 mM"], # 10 meq/L
["Na+1", "10 mM"], # 10 meq/L
["Ag+1", "10 mM"], # no contribution to alk or hardness
["CO3-2", "6 mM"], # no contribution to alk or hardness
["SO4-2", "60 mM"], # -120 meq/L
["Br-", "20 mM"],
], # -20 meq/L
volume="1 L",
balance_charge="auto",
)
assert s.balance_charge == 'Na[+1]'
assert np.isclose(s.charge_balance, 0, atol=1e-8)


def test_alkalinity_hardness(s3, s5, s6):
assert np.isclose(s3.hardness, 0)
Expand Down

0 comments on commit 800eb88

Please sign in to comment.