pyEQL 1.0.3__py3-none-any.whl → 1.1.1__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/__init__.py +1 -4
- pyEQL/database/pyeql_db.json +2 -2
- pyEQL/engines.py +39 -34
- pyEQL/solution.py +25 -84
- {pyEQL-1.0.3.dist-info → pyEQL-1.1.1.dist-info}/METADATA +3 -3
- {pyEQL-1.0.3.dist-info → pyEQL-1.1.1.dist-info}/RECORD +11 -12
- {pyEQL-1.0.3.dist-info → pyEQL-1.1.1.dist-info}/WHEEL +1 -1
- pyEQL/pint_custom_units.txt +0 -52
- {pyEQL-1.0.3.dist-info → pyEQL-1.1.1.dist-info}/AUTHORS.md +0 -0
- {pyEQL-1.0.3.dist-info → pyEQL-1.1.1.dist-info}/COPYING +0 -0
- {pyEQL-1.0.3.dist-info → pyEQL-1.1.1.dist-info}/LICENSE.txt +0 -0
- {pyEQL-1.0.3.dist-info → pyEQL-1.1.1.dist-info}/top_level.txt +0 -0
pyEQL/__init__.py
CHANGED
|
@@ -37,11 +37,8 @@ ureg = UnitRegistry(cache_folder=":auto:")
|
|
|
37
37
|
# convert "offset units" so that, e.g. Quantity('25 degC') works without error
|
|
38
38
|
# see https://pint.readthedocs.io/en/0.22/user/nonmult.html?highlight=offset#temperature-conversion
|
|
39
39
|
ureg.autoconvert_offset_to_baseunit = True
|
|
40
|
-
# append custom unit definitions and contexts
|
|
41
|
-
fname = files("pyEQL") / "pint_custom_units.txt"
|
|
42
|
-
ureg.load_definitions(fname)
|
|
43
40
|
# activate the "chemistry" context globally
|
|
44
|
-
ureg.enable_contexts("
|
|
41
|
+
ureg.enable_contexts("chemistry")
|
|
45
42
|
# set the default string formatting for pint quantities
|
|
46
43
|
ureg.default_format = "P~"
|
|
47
44
|
|
pyEQL/database/pyeql_db.json
CHANGED
|
@@ -6698,7 +6698,7 @@
|
|
|
6698
6698
|
"n_elements": 1,
|
|
6699
6699
|
"size": {
|
|
6700
6700
|
"radius_ionic": {
|
|
6701
|
-
"value": "0.755",
|
|
6701
|
+
"value": "0.755 Å",
|
|
6702
6702
|
"reference": "pymatgen",
|
|
6703
6703
|
"data_type": "experimental"
|
|
6704
6704
|
},
|
|
@@ -24663,7 +24663,7 @@
|
|
|
24663
24663
|
"n_elements": 1,
|
|
24664
24664
|
"size": {
|
|
24665
24665
|
"radius_ionic": {
|
|
24666
|
-
"value": "0.83",
|
|
24666
|
+
"value": "0.83 Å",
|
|
24667
24667
|
"reference": "pymatgen",
|
|
24668
24668
|
"data_type": "experimental"
|
|
24669
24669
|
},
|
pyEQL/engines.py
CHANGED
|
@@ -11,7 +11,7 @@ import os
|
|
|
11
11
|
import warnings
|
|
12
12
|
from abc import ABC, abstractmethod
|
|
13
13
|
from pathlib import Path
|
|
14
|
-
from typing import Literal
|
|
14
|
+
from typing import TYPE_CHECKING, Literal
|
|
15
15
|
|
|
16
16
|
from phreeqpython import PhreeqPython
|
|
17
17
|
|
|
@@ -26,6 +26,9 @@ SPECIAL_ELEMENTS = ["S", "C", "N", "Cu", "Fe", "Mn"]
|
|
|
26
26
|
|
|
27
27
|
logger = logging.getLogger(__name__)
|
|
28
28
|
|
|
29
|
+
if TYPE_CHECKING:
|
|
30
|
+
from pyEQL import Solution
|
|
31
|
+
|
|
29
32
|
|
|
30
33
|
class EOS(ABC):
|
|
31
34
|
"""
|
|
@@ -38,7 +41,7 @@ class EOS(ABC):
|
|
|
38
41
|
"""
|
|
39
42
|
|
|
40
43
|
@abstractmethod
|
|
41
|
-
def get_activity_coefficient(self, solution, solute):
|
|
44
|
+
def get_activity_coefficient(self, solution: "Solution", solute: str) -> ureg.Quantity:
|
|
42
45
|
"""
|
|
43
46
|
Return the *molal scale* activity coefficient of solute, given a Solution
|
|
44
47
|
object.
|
|
@@ -55,7 +58,7 @@ class EOS(ABC):
|
|
|
55
58
|
"""
|
|
56
59
|
|
|
57
60
|
@abstractmethod
|
|
58
|
-
def get_osmotic_coefficient(self, solution):
|
|
61
|
+
def get_osmotic_coefficient(self, solution: "Solution") -> ureg.Quantity:
|
|
59
62
|
"""
|
|
60
63
|
Return the *molal scale* osmotic coefficient of a Solution.
|
|
61
64
|
|
|
@@ -70,7 +73,7 @@ class EOS(ABC):
|
|
|
70
73
|
"""
|
|
71
74
|
|
|
72
75
|
@abstractmethod
|
|
73
|
-
def get_solute_volume(self):
|
|
76
|
+
def get_solute_volume(self, solution: "Solution") -> ureg.Quantity:
|
|
74
77
|
"""
|
|
75
78
|
Return the volume of only the solutes.
|
|
76
79
|
|
|
@@ -85,7 +88,7 @@ class EOS(ABC):
|
|
|
85
88
|
"""
|
|
86
89
|
|
|
87
90
|
@abstractmethod
|
|
88
|
-
def equilibrate(self, solution):
|
|
91
|
+
def equilibrate(self, solution: "Solution") -> None:
|
|
89
92
|
"""
|
|
90
93
|
Adjust the speciation and pH of a Solution object to achieve chemical equilibrium.
|
|
91
94
|
|
|
@@ -105,25 +108,25 @@ class EOS(ABC):
|
|
|
105
108
|
class IdealEOS(EOS):
|
|
106
109
|
"""Ideal solution equation of state engine."""
|
|
107
110
|
|
|
108
|
-
def get_activity_coefficient(self, solution, solute):
|
|
111
|
+
def get_activity_coefficient(self, solution: "Solution", solute: str) -> ureg.Quantity:
|
|
109
112
|
"""
|
|
110
113
|
Return the *molal scale* activity coefficient of solute, given a Solution
|
|
111
114
|
object.
|
|
112
115
|
"""
|
|
113
116
|
return ureg.Quantity(1, "dimensionless")
|
|
114
117
|
|
|
115
|
-
def get_osmotic_coefficient(self, solution):
|
|
118
|
+
def get_osmotic_coefficient(self, solution: "Solution") -> ureg.Quantity:
|
|
116
119
|
"""
|
|
117
120
|
Return the *molal scale* osmotic coefficient of solute, given a Solution
|
|
118
121
|
object.
|
|
119
122
|
"""
|
|
120
123
|
return ureg.Quantity(1, "dimensionless")
|
|
121
124
|
|
|
122
|
-
def get_solute_volume(self, solution):
|
|
125
|
+
def get_solute_volume(self, solution: "Solution") -> ureg.Quantity:
|
|
123
126
|
"""Return the volume of the solutes."""
|
|
124
127
|
return ureg.Quantity(0, "L")
|
|
125
128
|
|
|
126
|
-
def equilibrate(self, solution):
|
|
129
|
+
def equilibrate(self, solution: "Solution") -> None:
|
|
127
130
|
"""Adjust the speciation of a Solution object to achieve chemical equilibrium."""
|
|
128
131
|
warnings.warn("equilibrate() has no effect in IdealEOS!")
|
|
129
132
|
return
|
|
@@ -138,7 +141,9 @@ class NativeEOS(EOS):
|
|
|
138
141
|
|
|
139
142
|
def __init__(
|
|
140
143
|
self,
|
|
141
|
-
phreeqc_db: Literal[
|
|
144
|
+
phreeqc_db: Literal[
|
|
145
|
+
"phreeqc.dat", "vitens.dat", "wateq4f_PWN.dat", "pitzer.dat", "llnl.dat", "geothermal.dat"
|
|
146
|
+
] = "llnl.dat",
|
|
142
147
|
) -> None:
|
|
143
148
|
"""
|
|
144
149
|
Args:
|
|
@@ -172,7 +177,7 @@ class NativeEOS(EOS):
|
|
|
172
177
|
# store the solution composition to see whether we need to re-instantiate the solution
|
|
173
178
|
self._stored_comp = None
|
|
174
179
|
|
|
175
|
-
def _setup_ppsol(self, solution):
|
|
180
|
+
def _setup_ppsol(self, solution: "Solution") -> None:
|
|
176
181
|
"""Helper method to set up a PhreeqPython solution for subsequent analysis."""
|
|
177
182
|
self._stored_comp = solution.components.copy()
|
|
178
183
|
solv_mass = solution.solvent_mass.to("kg").magnitude
|
|
@@ -244,13 +249,13 @@ class NativeEOS(EOS):
|
|
|
244
249
|
|
|
245
250
|
self.ppsol = ppsol
|
|
246
251
|
|
|
247
|
-
def _destroy_ppsol(self):
|
|
248
|
-
"""Remove the PhreeqPython solution from memory"""
|
|
252
|
+
def _destroy_ppsol(self) -> None:
|
|
253
|
+
"""Remove the PhreeqPython solution from memory."""
|
|
249
254
|
if self.ppsol is not None:
|
|
250
255
|
self.ppsol.forget()
|
|
251
256
|
self.ppsol = None
|
|
252
257
|
|
|
253
|
-
def get_activity_coefficient(self, solution, solute):
|
|
258
|
+
def get_activity_coefficient(self, solution: "Solution", solute: str):
|
|
254
259
|
r"""
|
|
255
260
|
Whenever the appropriate parameters are available, the Pitzer model [may]_ is used.
|
|
256
261
|
If no Pitzer parameters are available, then the appropriate equations are selected
|
|
@@ -329,7 +334,7 @@ class NativeEOS(EOS):
|
|
|
329
334
|
|
|
330
335
|
# show an error if no salt can be found that contains the solute
|
|
331
336
|
if salt is None:
|
|
332
|
-
logger.error("No salts found that contain solute
|
|
337
|
+
logger.error(f"No salts found that contain solute {solute}. Returning unit activity coefficient.")
|
|
333
338
|
return ureg.Quantity(1, "dimensionless")
|
|
334
339
|
|
|
335
340
|
# use the Pitzer model for higher ionic strength, if the parameters are available
|
|
@@ -344,14 +349,14 @@ class NativeEOS(EOS):
|
|
|
344
349
|
# alpha1 and alpha2 based on charge
|
|
345
350
|
if salt.nu_cation >= 2 and salt.nu_anion <= -2:
|
|
346
351
|
if salt.nu_cation >= 3 or salt.nu_anion <= -3:
|
|
347
|
-
alpha1 = 2
|
|
348
|
-
alpha2 = 50
|
|
352
|
+
alpha1 = 2.0
|
|
353
|
+
alpha2 = 50.0
|
|
349
354
|
else:
|
|
350
355
|
alpha1 = 1.4
|
|
351
356
|
alpha2 = 12
|
|
352
357
|
else:
|
|
353
|
-
alpha1 = 2
|
|
354
|
-
alpha2 = 0
|
|
358
|
+
alpha1 = 2.0
|
|
359
|
+
alpha2 = 0.0
|
|
355
360
|
|
|
356
361
|
# determine the average molality of the salt
|
|
357
362
|
# this is necessary for solutions inside e.g. an ion exchange
|
|
@@ -427,7 +432,7 @@ class NativeEOS(EOS):
|
|
|
427
432
|
|
|
428
433
|
return molal
|
|
429
434
|
|
|
430
|
-
def get_osmotic_coefficient(self, solution):
|
|
435
|
+
def get_osmotic_coefficient(self, solution: "Solution") -> ureg.Quantity:
|
|
431
436
|
r"""
|
|
432
437
|
Return the *molal scale* osmotic coefficient of solute, given a Solution
|
|
433
438
|
object.
|
|
@@ -574,7 +579,7 @@ class NativeEOS(EOS):
|
|
|
574
579
|
# this means the solution is empty
|
|
575
580
|
return 1
|
|
576
581
|
|
|
577
|
-
def get_solute_volume(self, solution):
|
|
582
|
+
def get_solute_volume(self, solution: "Solution") -> ureg.Quantity:
|
|
578
583
|
"""Return the volume of the solutes."""
|
|
579
584
|
# identify the predominant salt in the solution
|
|
580
585
|
salt = solution.get_salt()
|
|
@@ -596,14 +601,14 @@ class NativeEOS(EOS):
|
|
|
596
601
|
# alpha1 and alpha2 based on charge
|
|
597
602
|
if salt.nu_cation >= 2 and salt.nu_anion >= 2:
|
|
598
603
|
if salt.nu_cation >= 3 or salt.nu_anion >= 3:
|
|
599
|
-
alpha1 = 2
|
|
600
|
-
alpha2 = 50
|
|
604
|
+
alpha1 = 2.0
|
|
605
|
+
alpha2 = 50.0
|
|
601
606
|
else:
|
|
602
607
|
alpha1 = 1.4
|
|
603
608
|
alpha2 = 12
|
|
604
609
|
else:
|
|
605
|
-
alpha1 = 2
|
|
606
|
-
alpha2 = 0
|
|
610
|
+
alpha1 = 2.0
|
|
611
|
+
alpha2 = 0.0
|
|
607
612
|
|
|
608
613
|
apparent_vol = ac.get_apparent_volume_pitzer(
|
|
609
614
|
solution.ionic_strength,
|
|
@@ -633,7 +638,7 @@ class NativeEOS(EOS):
|
|
|
633
638
|
|
|
634
639
|
pitzer_calc = True
|
|
635
640
|
|
|
636
|
-
logger.debug("Updated solution volume using Pitzer model for solute
|
|
641
|
+
logger.debug(f"Updated solution volume using Pitzer model for solute {salt.formula}")
|
|
637
642
|
|
|
638
643
|
# add the partial molar volume of any other solutes, except for water
|
|
639
644
|
# or the parent salt, which is already accounted for by the Pitzer parameters
|
|
@@ -649,7 +654,7 @@ class NativeEOS(EOS):
|
|
|
649
654
|
part_vol = solution.get_property(solute, "size.molar_volume")
|
|
650
655
|
if part_vol is not None:
|
|
651
656
|
solute_vol += part_vol * ureg.Quantity(mol, "mol")
|
|
652
|
-
logger.debug("Updated solution volume using direct partial molar volume for solute
|
|
657
|
+
logger.debug(f"Updated solution volume using direct partial molar volume for solute {solute}")
|
|
653
658
|
|
|
654
659
|
else:
|
|
655
660
|
logger.warning(
|
|
@@ -658,7 +663,7 @@ class NativeEOS(EOS):
|
|
|
658
663
|
|
|
659
664
|
return solute_vol.to("L")
|
|
660
665
|
|
|
661
|
-
def equilibrate(self, solution):
|
|
666
|
+
def equilibrate(self, solution: "Solution") -> None:
|
|
662
667
|
"""Adjust the speciation of a Solution object to achieve chemical equilibrium."""
|
|
663
668
|
if self.ppsol is not None:
|
|
664
669
|
self.ppsol.forget()
|
|
@@ -699,7 +704,7 @@ class NativeEOS(EOS):
|
|
|
699
704
|
if solution.balance_charge is None:
|
|
700
705
|
pass
|
|
701
706
|
elif solution.balance_charge == "pH":
|
|
702
|
-
solution.components["H+"] += charge_adjust
|
|
707
|
+
solution.components["H+"] += charge_adjust
|
|
703
708
|
elif solution.balance_charge == "pE":
|
|
704
709
|
raise NotImplementedError
|
|
705
710
|
else:
|
|
@@ -734,7 +739,7 @@ class PhreeqcEOS(NativeEOS):
|
|
|
734
739
|
def __init__(
|
|
735
740
|
self,
|
|
736
741
|
phreeqc_db: Literal[
|
|
737
|
-
"vitens.dat", "wateq4f_PWN.dat", "pitzer.dat", "llnl.dat", "geothermal.dat"
|
|
742
|
+
"phreeqc.dat", "vitens.dat", "wateq4f_PWN.dat", "pitzer.dat", "llnl.dat", "geothermal.dat"
|
|
738
743
|
] = "phreeqc.dat",
|
|
739
744
|
) -> None:
|
|
740
745
|
"""
|
|
@@ -751,12 +756,12 @@ class PhreeqcEOS(NativeEOS):
|
|
|
751
756
|
"""
|
|
752
757
|
super().__init__(phreeqc_db=phreeqc_db)
|
|
753
758
|
|
|
754
|
-
def get_activity_coefficient(self, solution, solute):
|
|
759
|
+
def get_activity_coefficient(self, solution: "Solution", solute: str) -> ureg.Quantity:
|
|
755
760
|
"""
|
|
756
761
|
Return the *molal scale* activity coefficient of solute, given a Solution
|
|
757
762
|
object.
|
|
758
763
|
"""
|
|
759
|
-
if self.ppsol is None or solution.components != self._stored_comp:
|
|
764
|
+
if (self.ppsol is None) or (solution.components != self._stored_comp):
|
|
760
765
|
self._destroy_ppsol()
|
|
761
766
|
self._setup_ppsol(solution)
|
|
762
767
|
|
|
@@ -775,7 +780,7 @@ class PhreeqcEOS(NativeEOS):
|
|
|
775
780
|
|
|
776
781
|
return ureg.Quantity(act, "dimensionless")
|
|
777
782
|
|
|
778
|
-
def get_osmotic_coefficient(self, solution):
|
|
783
|
+
def get_osmotic_coefficient(self, solution: "Solution") -> ureg.Quantity:
|
|
779
784
|
"""
|
|
780
785
|
Return the *molal scale* osmotic coefficient of solute, given a Solution
|
|
781
786
|
object.
|
|
@@ -787,7 +792,7 @@ class PhreeqcEOS(NativeEOS):
|
|
|
787
792
|
# TODO - find a way to access or calculate osmotic coefficient
|
|
788
793
|
return ureg.Quantity(1, "dimensionless")
|
|
789
794
|
|
|
790
|
-
def get_solute_volume(self, solution):
|
|
795
|
+
def get_solute_volume(self, solution: "Solution") -> ureg.Quantity:
|
|
791
796
|
"""Return the volume of the solutes."""
|
|
792
797
|
# TODO - phreeqc seems to have no concept of volume, but it does calculate density
|
|
793
798
|
return ureg.Quantity(0, "L")
|
pyEQL/solution.py
CHANGED
|
@@ -92,11 +92,15 @@ class Solution(MSONable):
|
|
|
92
92
|
-7 to +14. The default value corresponds to a pE value typical of natural
|
|
93
93
|
waters in equilibrium with the atmosphere.
|
|
94
94
|
balance_charge: The strategy for balancing charge during init and equilibrium calculations. Valid options
|
|
95
|
-
are
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
95
|
+
are
|
|
96
|
+
- 'pH', which will adjust the solution pH to balance charge,
|
|
97
|
+
- 'auto' which will use the majority cation or anion (i.e., that with the largest concentration)
|
|
98
|
+
as needed,
|
|
99
|
+
- 'pE' (not currently implemented) which will adjust the redox equilibrium to balance charge, or
|
|
100
|
+
the name of a dissolved species e.g. 'Ca+2' or 'Cl-' that will be added/subtracted to balance
|
|
101
|
+
charge.
|
|
102
|
+
- None (default), in which case no charge balancing will be performed either on init or when
|
|
103
|
+
equilibrate() is called. Note that in this case, equilibrate() can distort the charge balance!
|
|
100
104
|
solvent: Formula of the solvent. Solvents other than water are not supported at this time.
|
|
101
105
|
engine: Electrolyte modeling engine to use. See documentation for details on the available engines.
|
|
102
106
|
database: path to a .json file (str or Path) or maggma Store instance that
|
|
@@ -171,7 +175,7 @@ class Solution(MSONable):
|
|
|
171
175
|
self._pE = pE
|
|
172
176
|
self._pH = pH
|
|
173
177
|
self.pE = self._pE
|
|
174
|
-
if isinstance(balance_charge, str) and balance_charge not in ["pH", "pE"]:
|
|
178
|
+
if isinstance(balance_charge, str) and balance_charge not in ["pH", "pE", "auto"]:
|
|
175
179
|
self.balance_charge = standardize_formula(balance_charge)
|
|
176
180
|
else:
|
|
177
181
|
self.balance_charge = balance_charge #: Standardized formula of the species used for charge balancing.
|
|
@@ -273,13 +277,19 @@ class Solution(MSONable):
|
|
|
273
277
|
raise NotImplementedError("Balancing charge via redox (pE) is not yet implemented!")
|
|
274
278
|
else:
|
|
275
279
|
ions = set().union(*[self.cations, self.anions]) # all ions
|
|
280
|
+
if self.balance_charge == "auto":
|
|
281
|
+
# add the most abundant ion of the opposite charge
|
|
282
|
+
if cb <= 0:
|
|
283
|
+
self.balance_charge = max(self.cations, key=self.cations.get)
|
|
284
|
+
elif cb > 0:
|
|
285
|
+
self.balance_charge = max(self.anions, key=self.anions.get)
|
|
276
286
|
if self.balance_charge not in ions:
|
|
277
287
|
raise ValueError(
|
|
278
288
|
f"Charge balancing species {self.balance_charge} was not found in the solution!. "
|
|
279
289
|
f"Species {ions} were found."
|
|
280
290
|
)
|
|
281
|
-
z = self.get_property(balance_charge, "charge")
|
|
282
|
-
self.components[balance_charge] += -1 * cb / z * self.volume.to("L").magnitude
|
|
291
|
+
z = self.get_property(self.balance_charge, "charge")
|
|
292
|
+
self.components[self.balance_charge] += -1 * cb / z * self.volume.to("L").magnitude
|
|
283
293
|
balanced = True
|
|
284
294
|
|
|
285
295
|
if not balanced:
|
|
@@ -1282,81 +1292,12 @@ class Solution(MSONable):
|
|
|
1282
1292
|
Returns:
|
|
1283
1293
|
Nothing. The concentration of solute is modified.
|
|
1284
1294
|
"""
|
|
1285
|
-
#
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
):
|
|
1292
|
-
# store the original volume for later
|
|
1293
|
-
orig_volume = self.volume
|
|
1294
|
-
|
|
1295
|
-
# change the amount of the solute present to match the desired amount
|
|
1296
|
-
self.components[solute] += (
|
|
1297
|
-
ureg.Quantity(amount)
|
|
1298
|
-
.to(
|
|
1299
|
-
"moles",
|
|
1300
|
-
"chem",
|
|
1301
|
-
mw=self.get_property(solute, "molecular_weight"),
|
|
1302
|
-
volume=self.volume,
|
|
1303
|
-
solvent_mass=self.solvent_mass,
|
|
1304
|
-
)
|
|
1305
|
-
.magnitude
|
|
1306
|
-
)
|
|
1307
|
-
|
|
1308
|
-
# set the amount to zero and log a warning if the desired amount
|
|
1309
|
-
# change would result in a negative concentration
|
|
1310
|
-
if self.get_amount(solute, "mol").magnitude < 0:
|
|
1311
|
-
self.logger.error(
|
|
1312
|
-
"Attempted to set a negative concentration for solute %s. Concentration set to 0" % solute
|
|
1313
|
-
)
|
|
1314
|
-
self.set_amount(solute, "0 mol")
|
|
1315
|
-
|
|
1316
|
-
# calculate the volume occupied by all the solutes
|
|
1317
|
-
solute_vol = self._get_solute_volume()
|
|
1318
|
-
|
|
1319
|
-
# determine the volume of solvent that will preserve the original volume
|
|
1320
|
-
target_vol = orig_volume - solute_vol
|
|
1321
|
-
|
|
1322
|
-
# adjust the amount of solvent
|
|
1323
|
-
# volume in L, density in kg/m3 = g/L
|
|
1324
|
-
target_mass = target_vol * ureg.Quantity(self.water_substance.rho, "g/L")
|
|
1325
|
-
|
|
1326
|
-
mw = self.get_property(self.solvent, "molecular_weight")
|
|
1327
|
-
target_mol = target_mass / mw
|
|
1328
|
-
self.components[self.solvent] = target_mol.magnitude
|
|
1329
|
-
|
|
1330
|
-
else:
|
|
1331
|
-
# change the amount of the solute present
|
|
1332
|
-
self.components[solute] += (
|
|
1333
|
-
ureg.Quantity(amount)
|
|
1334
|
-
.to(
|
|
1335
|
-
"moles",
|
|
1336
|
-
"chem",
|
|
1337
|
-
mw=self.get_property(solute, "molecular_weight"),
|
|
1338
|
-
volume=self.volume,
|
|
1339
|
-
solvent_mass=self.solvent_mass,
|
|
1340
|
-
)
|
|
1341
|
-
.magnitude
|
|
1342
|
-
)
|
|
1343
|
-
|
|
1344
|
-
# set the amount to zero and log a warning if the desired amount
|
|
1345
|
-
# change would result in a negative concentration
|
|
1346
|
-
if self.get_amount(solute, "mol").magnitude < 0:
|
|
1347
|
-
self.logger.error(
|
|
1348
|
-
"Attempted to set a negative concentration for solute %s. Concentration set to 0" % solute
|
|
1349
|
-
)
|
|
1350
|
-
self.set_amount(solute, "0 mol")
|
|
1351
|
-
|
|
1352
|
-
# update the volume to account for the space occupied by all the solutes
|
|
1353
|
-
# make sure that there is still solvent present in the first place
|
|
1354
|
-
if self.solvent_mass <= ureg.Quantity(0, "kg"):
|
|
1355
|
-
self.logger.error("All solvent has been depleted from the solution")
|
|
1356
|
-
return
|
|
1357
|
-
|
|
1358
|
-
# set the volume recalculation flag
|
|
1359
|
-
self.volume_update_required = True
|
|
1295
|
+
# Get the current amount of the solute
|
|
1296
|
+
current_amt = self.get_amount(solute, amount.split(" ")[1])
|
|
1297
|
+
if current_amt.magnitude == 0:
|
|
1298
|
+
self.logger.warning(f"Add new solute {solute} to the solution")
|
|
1299
|
+
new_amt = ureg.Quantity(amount) + current_amt
|
|
1300
|
+
self.set_amount(solute, new_amt)
|
|
1360
1301
|
|
|
1361
1302
|
def set_amount(self, solute: str, amount: str):
|
|
1362
1303
|
"""
|
|
@@ -2465,7 +2406,7 @@ class Solution(MSONable):
|
|
|
2465
2406
|
"""
|
|
2466
2407
|
str_filename = str(filename)
|
|
2467
2408
|
if not ("yaml" in str_filename.lower() or "json" in str_filename.lower()):
|
|
2468
|
-
self.logger.error("Invalid file extension entered -
|
|
2409
|
+
self.logger.error("Invalid file extension entered - {str_filename}")
|
|
2469
2410
|
raise ValueError("File extension must be .json or .yaml")
|
|
2470
2411
|
if "yaml" in str_filename.lower():
|
|
2471
2412
|
solution_dict = self.as_dict()
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: pyEQL
|
|
3
|
-
Version: 1.
|
|
4
|
-
Summary: A python
|
|
3
|
+
Version: 1.1.1
|
|
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/
|
|
7
7
|
Project-URL: Repo, https://github.com/KingsburyLab/pyEQL
|
|
@@ -25,7 +25,7 @@ Requires-Dist: numpy <2
|
|
|
25
25
|
Requires-Dist: scipy
|
|
26
26
|
Requires-Dist: pymatgen ==2024.5.1
|
|
27
27
|
Requires-Dist: iapws
|
|
28
|
-
Requires-Dist: monty
|
|
28
|
+
Requires-Dist: monty >=2024.7.12
|
|
29
29
|
Requires-Dist: maggma >=0.67.0
|
|
30
30
|
Requires-Dist: phreeqpython
|
|
31
31
|
Provides-Extra: docs
|
|
@@ -1,27 +1,26 @@
|
|
|
1
|
-
pyEQL/__init__.py,sha256=
|
|
1
|
+
pyEQL/__init__.py,sha256=OCp_PiQEPyVoi1VX0ursBzHJWN6nDS1Id6bTBOgqCYs,1999
|
|
2
2
|
pyEQL/activity_correction.py,sha256=eOixjgTd5hTrTRD5s6aPCCG12lAIH7-lRN0Z1qHu678,37151
|
|
3
|
-
pyEQL/engines.py,sha256=
|
|
3
|
+
pyEQL/engines.py,sha256=9Pfo6YATahB9EUdBpUkE3xBAyDQqp1MjG9LO-zD4fAg,35517
|
|
4
4
|
pyEQL/equilibrium.py,sha256=YCtoAJSgn1WC9NJnc3H4FTJdKQvogsvCuj7HqlKMtww,8307
|
|
5
5
|
pyEQL/functions.py,sha256=nc-Hc61MmW-ELBR1PByJvQnELxM7PZexMHbU_O5-Bnw,10584
|
|
6
|
-
pyEQL/pint_custom_units.txt,sha256=XHmcMlwVvqF9nEW7_e9Xgyq-xWEr-cDYqieas11T3eY,2882
|
|
7
6
|
pyEQL/salt_ion_match.py,sha256=0nCZXmeo67VqcyYWQpPx-81hjSvnsg8HFB3fIyfjW_k,4070
|
|
8
7
|
pyEQL/solute.py,sha256=no00Rc3tRfHmyht4wm2UXA1KZhKC45tWMO5QEkZY6yg,5140
|
|
9
|
-
pyEQL/solution.py,sha256=
|
|
8
|
+
pyEQL/solution.py,sha256=WET68HPDaleEsIxK2ObtAlNR7oolunNU5mNjxNeGG8U,113696
|
|
10
9
|
pyEQL/utils.py,sha256=DWLtNm71qw5j4-jqBp5v3LssEjWgJnVvI6a_H60c5ic,6670
|
|
11
10
|
pyEQL/database/geothermal.dat,sha256=kksnfcBtWdOTpNn4CLXU1Mz16cwas2WuVKpuMU8CaVI,234230
|
|
12
11
|
pyEQL/database/llnl.dat,sha256=jN-a0kfUFbQlYMn2shTVRg1JX_ZhLa-tJ0lLw2YSpLU,751462
|
|
13
12
|
pyEQL/database/phreeqc_license.txt,sha256=8W1r8VxC2kVptIMSU9sDFNASYqN7MdwKEtIWWfjTQuM,2906
|
|
14
|
-
pyEQL/database/pyeql_db.json,sha256
|
|
13
|
+
pyEQL/database/pyeql_db.json,sha256=-7Z8tpAddXhPlvpxms7cFQKL_DSSbemzOzxm6L5vaVk,1080801
|
|
15
14
|
pyEQL/presets/Ringers lactate.yaml,sha256=vtSnuvgALHR27XEjpDzC0xyw5-E6b2FSsF1EUEBiWpw,413
|
|
16
15
|
pyEQL/presets/normal saline.yaml,sha256=i2znhnIeXfNx1iMFFSif7crMRCFRP6xN1m7Wp7USduM,318
|
|
17
16
|
pyEQL/presets/rainwater.yaml,sha256=S0WHZNDfCJyjSSFxNFdkypjn2s3P0jJGCiYIxvi1ibA,337
|
|
18
17
|
pyEQL/presets/seawater.yaml,sha256=oryc1CkhRz20RpWE6uiGiT93HoZnqlB0s-0PmBWr3-U,843
|
|
19
18
|
pyEQL/presets/urine.yaml,sha256=0Njtc-H1fFRo7UhquHdiSTT4z-8VZJ1utDCk02qk28M,679
|
|
20
19
|
pyEQL/presets/wastewater.yaml,sha256=jTTFBpmKxczaEtkCZb0xUULIPZt7wfC8eAJ6rthGnmw,502
|
|
21
|
-
pyEQL-1.
|
|
22
|
-
pyEQL-1.
|
|
23
|
-
pyEQL-1.
|
|
24
|
-
pyEQL-1.
|
|
25
|
-
pyEQL-1.
|
|
26
|
-
pyEQL-1.
|
|
27
|
-
pyEQL-1.
|
|
20
|
+
pyEQL-1.1.1.dist-info/AUTHORS.md,sha256=K9ZLhKFwZ2zLlFXwN62VuUYCpr5T6n4mOUCUHlytTUs,415
|
|
21
|
+
pyEQL-1.1.1.dist-info/COPYING,sha256=Ww2oUywfFTn242v9ksCgQdIVSpcMXJiKKePn0GFm25E,7649
|
|
22
|
+
pyEQL-1.1.1.dist-info/LICENSE.txt,sha256=2Zf1F7RzbpeposgIxUydpurqNCMoMgDi2gAB65_GjwQ,969
|
|
23
|
+
pyEQL-1.1.1.dist-info/METADATA,sha256=5EWAVR_RKxcnBz3XHpl-OOCBfUo2sJMMOyJATkAZXIw,6096
|
|
24
|
+
pyEQL-1.1.1.dist-info/WHEEL,sha256=Wyh-_nZ0DJYolHNn1_hMa4lM7uDedD_RGVwbmTjyItk,91
|
|
25
|
+
pyEQL-1.1.1.dist-info/top_level.txt,sha256=QMOaZjCAm_lS4Njsjh4L0B5aWnJFGQMYKhuH88CG1co,6
|
|
26
|
+
pyEQL-1.1.1.dist-info/RECORD,,
|
pyEQL/pint_custom_units.txt
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
# Units definition file for pint library
|
|
2
|
-
|
|
3
|
-
# This file defines additional units and contexts to enable the pint
|
|
4
|
-
# library to process solution chemistry units such as mol/L and mol/kg.
|
|
5
|
-
|
|
6
|
-
@context(mw=0,volume=0,solvent_mass=0) chemistry = chem
|
|
7
|
-
# mw is the molecular weight of the species
|
|
8
|
-
# volume is the volume of the solution
|
|
9
|
-
# solvent_mass is the mass of solvent in the solution
|
|
10
|
-
|
|
11
|
-
# moles -> mass require the molecular weight
|
|
12
|
-
[substance] -> [mass]: value * mw
|
|
13
|
-
[mass] -> [substance]: value / mw
|
|
14
|
-
|
|
15
|
-
# moles/volume -> mass/volume and moles/mass -> mass / mass
|
|
16
|
-
# require the molecular weight
|
|
17
|
-
[substance] / [volume] -> [mass] / [volume]: value * mw
|
|
18
|
-
[mass] / [volume] -> [substance] / [volume]: value / mw
|
|
19
|
-
[substance] / [mass] -> [mass] / [mass]: value * mw
|
|
20
|
-
[mass] / [mass] -> [substance] / [mass]: value / mw
|
|
21
|
-
|
|
22
|
-
# moles/volume -> moles requires the solution volume
|
|
23
|
-
[substance] / [volume] -> [substance]: value * volume
|
|
24
|
-
[substance] -> [substance] / [volume]: value / volume
|
|
25
|
-
|
|
26
|
-
# moles/mass -> moles requires the solvent (usually water) mass
|
|
27
|
-
[substance] / [mass] -> [substance]: value * solvent_mass
|
|
28
|
-
[substance] -> [substance] / [mass]: value / solvent_mass
|
|
29
|
-
|
|
30
|
-
# moles/mass -> moles/volume require the solvent mass and the volume
|
|
31
|
-
[substance] / [mass] -> [substance]/[volume]: value * solvent_mass / volume
|
|
32
|
-
[substance] / [volume] -> [substance] / [mass]: value / solvent_mass * volume
|
|
33
|
-
|
|
34
|
-
@end
|
|
35
|
-
|
|
36
|
-
@context electricity = elec
|
|
37
|
-
[length] ** 2 * [mass] / [current] ** 2 / [time] ** 3 <-> [length] ** -2 * [mass] **-1 / [current] ** -2 / [time] ** -3: 1 / value
|
|
38
|
-
@end
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
#From the pint documentation:
|
|
42
|
-
|
|
43
|
-
#@context(n=1) spectroscopy = sp
|
|
44
|
-
# # n index of refraction of the medium.
|
|
45
|
-
# [length] <-> [frequency]: speed_of_light / n / value
|
|
46
|
-
# [frequency] -> [energy]: planck_constant * value
|
|
47
|
-
# [energy] -> [frequency]: value / planck_constant
|
|
48
|
-
#@end
|
|
49
|
-
|
|
50
|
-
# The @context directive indicates the beginning of the transformations which are finished by the @end statement. You can optionally specify parameters for the context in parenthesis. All parameters are named and default values are mandatory. Multiple parameters are separated by commas (like in a python function definition). Finally, you provide the name of the context (e.g. spectroscopy) and, optionally, a short version of the name (e.g. sp) separated by an equal sign.
|
|
51
|
-
# Conversions rules are specified by providing source and destination dimensions separated using a colon (:) from the equation. A special variable named value will be replaced by the source quantity. Other names will be looked first in the context arguments and then in registry.
|
|
52
|
-
# A single forward arrow (->) indicates that the equations is used to transform from the first dimension to the second one. A double arrow (<->) is used to indicate that the transformation operates both ways.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|