pyEQL 1.1.2__py3-none-any.whl → 1.1.3__py3-none-any.whl

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/engines.py CHANGED
@@ -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 we only need to adjust
691
- # for any missing species here.
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
pyEQL/solution.py CHANGED
@@ -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 * (14 - pH))) + "mol/L")
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
- if not np.isclose(cb, 0, atol=1e-8) and self.balance_charge is not None:
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,57 @@ 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
2310
+ # check b^2 - 4ac; are there any real roots?
2311
+ if new_imbalance**2 - 4 * 1 * K_W < 0:
2312
+ self.logger.error("Cannot balance charge by adjusting pH. The imbalance is too large.")
2313
+ return
2314
+ new_hplus = max(
2315
+ [
2316
+ (new_imbalance + np.sqrt(new_imbalance**2 + 4 * 1 * K_W)) / 2,
2317
+ (new_imbalance - np.sqrt(new_imbalance**2 + 4 * 1 * K_W)) / 2,
2318
+ ]
2319
+ )
2320
+ self.set_amount("H+", f"{new_hplus} mol/L")
2321
+ self.set_amount("OH-", f"{K_W/new_hplus} mol/L")
2322
+ assert np.isclose(self.charge_balance, 0, atol=atol), f"{self.charge_balance}"
2323
+ return
2324
+
2325
+ z = self.get_property(self._cb_species, "charge")
2326
+ try:
2327
+ self.add_amount(self._cb_species, f"{-1*cb/z} mol")
2328
+ return
2329
+ except ValueError:
2330
+ # if the concentration is negative, it must mean there is not enough present.
2331
+ # remove everything that's present and log an error.
2332
+ self.components[self._cb_species] = 0
2333
+ self.logger.error(
2334
+ f"There is not enough {self._cb_species} present to balance the charge. Try a different species."
2335
+ )
2336
+ return
2337
+
2296
2338
  def _update_volume(self):
2297
2339
  """Recalculate the solution volume based on composition."""
2298
2340
  self._volume = self._get_solvent_volume() + self._get_solute_volume()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pyEQL
3
- Version: 1.1.2
3
+ Version: 1.1.3
4
4
  Summary: A python interface for solution chemistry
5
5
  Author-email: Ryan Kingsbury <kingsbury@princeton.edu>
6
6
  Project-URL: Docs, https://pyeql.readthedocs.io/
@@ -1,11 +1,11 @@
1
1
  pyEQL/__init__.py,sha256=OCp_PiQEPyVoi1VX0ursBzHJWN6nDS1Id6bTBOgqCYs,1999
2
2
  pyEQL/activity_correction.py,sha256=eOixjgTd5hTrTRD5s6aPCCG12lAIH7-lRN0Z1qHu678,37151
3
- pyEQL/engines.py,sha256=kE4ZtV2Z-zhOwtkH0cbv95oG1CETJmqMOoT0UfDuJeo,35221
3
+ pyEQL/engines.py,sha256=VbdQSPKlNehW96U1XxWYwjTy6k6WDpZZEx_Y4l3qZv4,34686
4
4
  pyEQL/equilibrium.py,sha256=YCtoAJSgn1WC9NJnc3H4FTJdKQvogsvCuj7HqlKMtww,8307
5
5
  pyEQL/functions.py,sha256=nc-Hc61MmW-ELBR1PByJvQnELxM7PZexMHbU_O5-Bnw,10584
6
6
  pyEQL/salt_ion_match.py,sha256=0nCZXmeo67VqcyYWQpPx-81hjSvnsg8HFB3fIyfjW_k,4070
7
7
  pyEQL/solute.py,sha256=no00Rc3tRfHmyht4wm2UXA1KZhKC45tWMO5QEkZY6yg,5140
8
- pyEQL/solution.py,sha256=vtC2xRUt1y2wTv_L1o7Qs3wr8b_X7wvkUsp56499baQ,113898
8
+ pyEQL/solution.py,sha256=LwlfY6Q19QEcY8MJpP_mcJgEw0ohNaGfAqAiqhdPmU8,116022
9
9
  pyEQL/utils.py,sha256=DWLtNm71qw5j4-jqBp5v3LssEjWgJnVvI6a_H60c5ic,6670
10
10
  pyEQL/database/geothermal.dat,sha256=kksnfcBtWdOTpNn4CLXU1Mz16cwas2WuVKpuMU8CaVI,234230
11
11
  pyEQL/database/llnl.dat,sha256=jN-a0kfUFbQlYMn2shTVRg1JX_ZhLa-tJ0lLw2YSpLU,751462
@@ -17,10 +17,10 @@ pyEQL/presets/rainwater.yaml,sha256=S0WHZNDfCJyjSSFxNFdkypjn2s3P0jJGCiYIxvi1ibA,
17
17
  pyEQL/presets/seawater.yaml,sha256=oryc1CkhRz20RpWE6uiGiT93HoZnqlB0s-0PmBWr3-U,843
18
18
  pyEQL/presets/urine.yaml,sha256=0Njtc-H1fFRo7UhquHdiSTT4z-8VZJ1utDCk02qk28M,679
19
19
  pyEQL/presets/wastewater.yaml,sha256=jTTFBpmKxczaEtkCZb0xUULIPZt7wfC8eAJ6rthGnmw,502
20
- pyEQL-1.1.2.dist-info/AUTHORS.md,sha256=K9ZLhKFwZ2zLlFXwN62VuUYCpr5T6n4mOUCUHlytTUs,415
21
- pyEQL-1.1.2.dist-info/COPYING,sha256=Ww2oUywfFTn242v9ksCgQdIVSpcMXJiKKePn0GFm25E,7649
22
- pyEQL-1.1.2.dist-info/LICENSE.txt,sha256=2Zf1F7RzbpeposgIxUydpurqNCMoMgDi2gAB65_GjwQ,969
23
- pyEQL-1.1.2.dist-info/METADATA,sha256=1mxAOqZ5yuBRO4iDo2RZN7gpAcJMXYOfY_KKFPFMzQQ,6096
24
- pyEQL-1.1.2.dist-info/WHEEL,sha256=Wyh-_nZ0DJYolHNn1_hMa4lM7uDedD_RGVwbmTjyItk,91
25
- pyEQL-1.1.2.dist-info/top_level.txt,sha256=QMOaZjCAm_lS4Njsjh4L0B5aWnJFGQMYKhuH88CG1co,6
26
- pyEQL-1.1.2.dist-info/RECORD,,
20
+ pyEQL-1.1.3.dist-info/AUTHORS.md,sha256=K9ZLhKFwZ2zLlFXwN62VuUYCpr5T6n4mOUCUHlytTUs,415
21
+ pyEQL-1.1.3.dist-info/COPYING,sha256=Ww2oUywfFTn242v9ksCgQdIVSpcMXJiKKePn0GFm25E,7649
22
+ pyEQL-1.1.3.dist-info/LICENSE.txt,sha256=2Zf1F7RzbpeposgIxUydpurqNCMoMgDi2gAB65_GjwQ,969
23
+ pyEQL-1.1.3.dist-info/METADATA,sha256=aDLyZrhXd2laVUiJ6drgt4RFSvlptOPxLEo9xrkaR40,6096
24
+ pyEQL-1.1.3.dist-info/WHEEL,sha256=Wyh-_nZ0DJYolHNn1_hMa4lM7uDedD_RGVwbmTjyItk,91
25
+ pyEQL-1.1.3.dist-info/top_level.txt,sha256=QMOaZjCAm_lS4Njsjh4L0B5aWnJFGQMYKhuH88CG1co,6
26
+ pyEQL-1.1.3.dist-info/RECORD,,
File without changes
File without changes