pyEQL 0.5.2__py3-none-any.whl → 1.1.0__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 +50 -43
- pyEQL/activity_correction.py +481 -707
- pyEQL/database/geothermal.dat +5693 -0
- pyEQL/database/llnl.dat +19305 -0
- pyEQL/database/phreeqc_license.txt +54 -0
- pyEQL/database/pyeql_db.json +35902 -0
- pyEQL/engines.py +793 -0
- pyEQL/equilibrium.py +148 -228
- pyEQL/functions.py +121 -416
- pyEQL/pint_custom_units.txt +2 -2
- pyEQL/presets/Ringers lactate.yaml +20 -0
- pyEQL/presets/normal saline.yaml +17 -0
- pyEQL/presets/rainwater.yaml +17 -0
- pyEQL/presets/seawater.yaml +29 -0
- pyEQL/presets/urine.yaml +26 -0
- pyEQL/presets/wastewater.yaml +21 -0
- pyEQL/salt_ion_match.py +53 -284
- pyEQL/solute.py +126 -191
- pyEQL/solution.py +2163 -2090
- pyEQL/utils.py +165 -0
- pyEQL-1.1.0.dist-info/AUTHORS.md +13 -0
- {pyEQL-0.5.2.dist-info → pyEQL-1.1.0.dist-info}/COPYING +1 -1
- pyEQL-0.5.2.dist-info/LICENSE → pyEQL-1.1.0.dist-info/LICENSE.txt +3 -7
- pyEQL-1.1.0.dist-info/METADATA +129 -0
- pyEQL-1.1.0.dist-info/RECORD +27 -0
- {pyEQL-0.5.2.dist-info → pyEQL-1.1.0.dist-info}/WHEEL +2 -1
- pyEQL/chemical_formula.py +0 -1006
- pyEQL/database/Erying_viscosity.tsv +0 -18
- pyEQL/database/Jones_Dole_B.tsv +0 -32
- pyEQL/database/Jones_Dole_B_inorganic_Jenkins.tsv +0 -75
- pyEQL/database/LICENSE +0 -4
- pyEQL/database/dielectric_parameter.tsv +0 -30
- pyEQL/database/diffusion_coefficient.tsv +0 -116
- pyEQL/database/hydrated_radius.tsv +0 -35
- pyEQL/database/ionic_radius.tsv +0 -35
- pyEQL/database/partial_molar_volume.tsv +0 -22
- pyEQL/database/pitzer_activity.tsv +0 -169
- pyEQL/database/pitzer_volume.tsv +0 -132
- pyEQL/database/template.tsv +0 -14
- pyEQL/database.py +0 -300
- pyEQL/elements.py +0 -4552
- pyEQL/logging_system.py +0 -53
- pyEQL/parameter.py +0 -435
- pyEQL/tests/__init__.py +0 -32
- pyEQL/tests/test_activity.py +0 -578
- pyEQL/tests/test_bulk_properties.py +0 -86
- pyEQL/tests/test_chemical_formula.py +0 -279
- pyEQL/tests/test_debye_length.py +0 -79
- pyEQL/tests/test_density.py +0 -106
- pyEQL/tests/test_dielectric.py +0 -153
- pyEQL/tests/test_effective_pitzer.py +0 -276
- pyEQL/tests/test_mixed_electrolyte_activity.py +0 -154
- pyEQL/tests/test_osmotic_coeff.py +0 -99
- pyEQL/tests/test_pyeql_volume_concentration.py +0 -428
- pyEQL/tests/test_salt_matching.py +0 -337
- pyEQL/tests/test_solute_properties.py +0 -251
- pyEQL/water_properties.py +0 -352
- pyEQL-0.5.2.dist-info/AUTHORS +0 -7
- pyEQL-0.5.2.dist-info/METADATA +0 -72
- pyEQL-0.5.2.dist-info/RECORD +0 -47
- pyEQL-0.5.2.dist-info/entry_points.txt +0 -3
- {pyEQL-0.5.2.dist-info → pyEQL-1.1.0.dist-info}/top_level.txt +0 -0
pyEQL/solute.py
CHANGED
|
@@ -1,228 +1,163 @@
|
|
|
1
1
|
"""
|
|
2
|
-
pyEQL Solute class
|
|
2
|
+
pyEQL Solute class.
|
|
3
3
|
|
|
4
|
-
This file contains functions and methods for managing properties of
|
|
4
|
+
This file contains functions and methods for managing properties of
|
|
5
5
|
individual solutes. The Solute class contains methods for accessing
|
|
6
6
|
ONLY those properties that DO NOT depend on solution composition.
|
|
7
7
|
Solute properties such as activity coefficient or concentration
|
|
8
8
|
that do depend on compsition are accessed via Solution class methods.
|
|
9
9
|
|
|
10
|
-
:copyright: 2013-
|
|
10
|
+
:copyright: 2013-2024 by Ryan S. Kingsbury
|
|
11
11
|
:license: LGPL, see LICENSE for more details.
|
|
12
12
|
|
|
13
13
|
"""
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
from pyEQL import unit
|
|
15
|
+
from __future__ import annotations
|
|
17
16
|
|
|
18
|
-
# logging system
|
|
19
17
|
import logging
|
|
20
|
-
|
|
21
|
-
from
|
|
22
|
-
|
|
23
|
-
from pyEQL import paramsDB as db
|
|
18
|
+
import warnings
|
|
19
|
+
from dataclasses import asdict, dataclass, field
|
|
20
|
+
from typing import Literal
|
|
24
21
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
logger.addFilter(unique)
|
|
28
|
-
|
|
29
|
-
# add a handler for console output, since pyEQL is meant to be used interactively
|
|
30
|
-
ch = logging.StreamHandler()
|
|
31
|
-
|
|
32
|
-
# create formatter for the log
|
|
33
|
-
formatter = logging.Formatter("(%(name)s) - %(levelname)s - %(message)s")
|
|
34
|
-
|
|
35
|
-
# add formatter to the handler
|
|
36
|
-
ch.setFormatter(formatter)
|
|
37
|
-
logger.addHandler(ch)
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
class Solute:
|
|
41
|
-
"""
|
|
42
|
-
represent each chemical species as an object containing its formal charge,
|
|
43
|
-
transport numbers, concentration, activity, etc.
|
|
44
|
-
|
|
45
|
-
"""
|
|
22
|
+
import numpy as np
|
|
23
|
+
from pymatgen.core.ion import Ion
|
|
46
24
|
|
|
47
|
-
|
|
48
|
-
"""
|
|
49
|
-
Parameters
|
|
50
|
-
----------
|
|
51
|
-
formula : str
|
|
52
|
-
Chemical formula for the solute.
|
|
53
|
-
Charged species must contain a + or - and (for polyvalent solutes) a number representing the net
|
|
54
|
-
charge (e.g. 'SO4-2').
|
|
55
|
-
amount : str
|
|
56
|
-
The amount of substance in the specified unit system. The string should contain both a quantity and
|
|
57
|
-
a pint-compatible representation of a unit. e.g. '5 mol/kg' or '0.1 g/L'
|
|
58
|
-
volume : pint Quantity
|
|
59
|
-
The volume of the solution
|
|
60
|
-
solvent_mass : pint Quantity
|
|
61
|
-
The mass of solvent in the parent solution.
|
|
62
|
-
parameters : dictionary, optional
|
|
63
|
-
Dictionary of custom parameters, such as diffusion coefficients, transport numbers, etc. Specify
|
|
64
|
-
parameters as key:value pairs separated by commas within curly braces, e.g.
|
|
65
|
-
{diffusion_coeff:5e-10,transport_number:0.8}. The 'key' is the name that will be used to access
|
|
66
|
-
the parameter, the value is its value.
|
|
67
|
-
"""
|
|
68
|
-
# import the chemical formula interpreter module
|
|
69
|
-
import pyEQL.chemical_formula as chem
|
|
70
|
-
|
|
71
|
-
# check that 'formula' is a valid chemical formula
|
|
72
|
-
if not chem.is_valid_formula:
|
|
73
|
-
logger.error("Invalid chemical formula specified.")
|
|
74
|
-
return None
|
|
75
|
-
else:
|
|
76
|
-
self.formula = formula
|
|
77
|
-
|
|
78
|
-
# set molecular weight
|
|
79
|
-
self.mw = chem.get_molecular_weight(formula) * unit("g/mol")
|
|
80
|
-
|
|
81
|
-
# set formal charge
|
|
82
|
-
self.charge = chem.get_formal_charge(formula)
|
|
25
|
+
from pyEQL.utils import standardize_formula
|
|
83
26
|
|
|
84
|
-
|
|
85
|
-
quantity = unit(amount)
|
|
86
|
-
|
|
87
|
-
self.moles = quantity.to(
|
|
88
|
-
"moles", "chem", mw=self.mw, volume=volume, solvent_mass=solvent_mass
|
|
89
|
-
)
|
|
27
|
+
logger = logging.getLogger(__name__)
|
|
90
28
|
|
|
91
|
-
# trigger the function that checks whether parameters already exist for this species, and if not,
|
|
92
|
-
# searches the database files and creates them
|
|
93
|
-
db.search_parameters(self.formula)
|
|
94
29
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
"""
|
|
99
|
-
Return the value of the parameter named 'parameter'
|
|
100
|
-
|
|
101
|
-
"""
|
|
102
|
-
# Search for this solute in the database of parameters
|
|
30
|
+
@dataclass
|
|
31
|
+
class Datum:
|
|
32
|
+
"""Document containing data for a single computed or experimental property."""
|
|
103
33
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
34
|
+
value: str
|
|
35
|
+
reference: str | None = None
|
|
36
|
+
data_type: Literal["computed", "experimental", "fitted", "unknown"] = "unknown"
|
|
107
37
|
|
|
108
|
-
|
|
38
|
+
@property
|
|
39
|
+
def magnitude(self):
|
|
40
|
+
"""Return the numerical value of a Datum."""
|
|
41
|
+
return float(self.value.split(" ")[0])
|
|
109
42
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
See pyEQL.parameters documentation for a description of the arguments
|
|
115
|
-
|
|
116
|
-
"""
|
|
117
|
-
import pyEQL.parameter as pm
|
|
43
|
+
@property
|
|
44
|
+
def unit(self):
|
|
45
|
+
"""Return the unit of a Datum."""
|
|
46
|
+
return self.value.split(" ")[-1]
|
|
118
47
|
|
|
119
|
-
|
|
120
|
-
|
|
48
|
+
@property
|
|
49
|
+
def uncertainty(self):
|
|
50
|
+
"""Return the uncertainty of a Datum."""
|
|
51
|
+
if len(self.value.split(" ")) > 3:
|
|
52
|
+
return float(self.value.split(" ")[2])
|
|
53
|
+
return np.nan
|
|
121
54
|
|
|
122
|
-
def
|
|
123
|
-
"""
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
Parameters
|
|
127
|
-
----------
|
|
128
|
-
None
|
|
129
|
-
|
|
130
|
-
Returns
|
|
131
|
-
-------
|
|
132
|
-
str
|
|
133
|
-
The chemical formula of the solute
|
|
134
|
-
|
|
135
|
-
"""
|
|
136
|
-
return self.formula
|
|
55
|
+
def as_dict(self):
|
|
56
|
+
"""Return a dictionary representation of the Datum."""
|
|
57
|
+
return dict(asdict(self).items())
|
|
137
58
|
|
|
138
|
-
def get_formal_charge(self):
|
|
139
|
-
"""
|
|
140
|
-
Return the formal charge of the solute
|
|
141
|
-
|
|
142
|
-
Parameters
|
|
143
|
-
----------
|
|
144
|
-
None
|
|
145
|
-
|
|
146
|
-
Returns
|
|
147
|
-
-------
|
|
148
|
-
int
|
|
149
|
-
The formal charge of the solute
|
|
150
|
-
|
|
151
|
-
"""
|
|
152
|
-
return self.charge
|
|
153
59
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
----------
|
|
160
|
-
None
|
|
161
|
-
|
|
162
|
-
Returns
|
|
163
|
-
-------
|
|
164
|
-
Quantity
|
|
165
|
-
The molecular weight of the solute, in g/mol
|
|
166
|
-
"""
|
|
167
|
-
return self.mw
|
|
60
|
+
@dataclass
|
|
61
|
+
class Solute:
|
|
62
|
+
"""
|
|
63
|
+
represent each chemical species as an object containing its formal charge,
|
|
64
|
+
transport numbers, concentration, activity, etc.
|
|
168
65
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
Parameters
|
|
174
|
-
----------
|
|
175
|
-
None
|
|
176
|
-
|
|
177
|
-
Returns
|
|
178
|
-
-------
|
|
179
|
-
Quantity
|
|
180
|
-
The number of moles of solute
|
|
181
|
-
|
|
182
|
-
"""
|
|
183
|
-
return self.moles
|
|
66
|
+
Args:
|
|
67
|
+
formula: Chemical formula for the solute. Charged species must contain a + or - and (for polyvalent solutes)
|
|
68
|
+
a number representing the net charge (e.g. 'SO4-2').
|
|
69
|
+
"""
|
|
184
70
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
71
|
+
formula: str
|
|
72
|
+
charge: int
|
|
73
|
+
molecular_weight: str
|
|
74
|
+
elements: list
|
|
75
|
+
chemsys: str
|
|
76
|
+
pmg_ion: Ion
|
|
77
|
+
formula_html: str
|
|
78
|
+
formula_latex: str
|
|
79
|
+
formula_hill: str
|
|
80
|
+
formula_pretty: str
|
|
81
|
+
oxi_state_guesses: dict[str, float]
|
|
82
|
+
n_atoms: int
|
|
83
|
+
n_elements: int
|
|
84
|
+
size: dict = field(
|
|
85
|
+
default_factory=lambda: {
|
|
86
|
+
"radius_ionic": None,
|
|
87
|
+
"radius_hydrated": None,
|
|
88
|
+
"radius_vdw": None,
|
|
89
|
+
"molar_volume": None,
|
|
90
|
+
}
|
|
91
|
+
)
|
|
92
|
+
thermo: dict = field(default_factory=lambda: {"ΔG_hydration": None, "ΔG_formation": None})
|
|
93
|
+
transport: dict = field(default_factory=lambda: {"diffusion_coefficient": None})
|
|
94
|
+
model_parameters: dict = field(
|
|
95
|
+
default_factory=lambda: {
|
|
96
|
+
"activity_pitzer": {"Beta0": None, "Beta1": None, "Beta2": None, "Cphi": None, "Max_C": None},
|
|
97
|
+
"molar_volume_pitzer": {
|
|
98
|
+
"Beta0": None,
|
|
99
|
+
"Beta1": None,
|
|
100
|
+
"Beta2": None,
|
|
101
|
+
"Cphi": None,
|
|
102
|
+
"V_o": None,
|
|
103
|
+
"Max_C": None,
|
|
104
|
+
},
|
|
105
|
+
"viscosity_jones_dole": {"B": None},
|
|
106
|
+
"diffusion_temp_smolyakov": {"a1": None, "a2": None, "d": None},
|
|
107
|
+
}
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
@classmethod
|
|
111
|
+
def from_formula(cls, formula: str):
|
|
112
|
+
"""
|
|
113
|
+
Create an Ion document from a chemical formula. The formula is passed to
|
|
114
|
+
pymatgen.core.Ion.from_formula() and used to populate the basic chemical
|
|
115
|
+
informatics fields (e.g., formula, charge, molecular weight, elements, etc.)
|
|
116
|
+
of the IonDoc.
|
|
117
|
+
"""
|
|
118
|
+
pmg_ion = Ion.from_formula(formula)
|
|
119
|
+
f, factor = pmg_ion.get_reduced_formula_and_factor()
|
|
120
|
+
rform = standardize_formula(formula)
|
|
121
|
+
charge = int(pmg_ion.charge)
|
|
122
|
+
els = [str(el) for el in pmg_ion.elements]
|
|
123
|
+
mw = f"{float(pmg_ion.weight / factor)} g/mol" # weight is a FloatWithUnit
|
|
124
|
+
chemsys = pmg_ion.chemical_system
|
|
125
|
+
# store only the most likely oxi_state guesses
|
|
126
|
+
try:
|
|
127
|
+
oxi_states = pmg_ion.oxi_state_guesses(all_oxi_states=True)[0]
|
|
128
|
+
except (IndexError, ValueError):
|
|
129
|
+
warnings.warn(f"Guessing oxi states failed for {formula}")
|
|
130
|
+
oxi_states = {}
|
|
131
|
+
|
|
132
|
+
return cls(
|
|
133
|
+
rform,
|
|
134
|
+
charge=charge,
|
|
135
|
+
molecular_weight=mw,
|
|
136
|
+
elements=els,
|
|
137
|
+
chemsys=chemsys,
|
|
138
|
+
pmg_ion=pmg_ion,
|
|
139
|
+
formula_html=pmg_ion.to_html_string(),
|
|
140
|
+
formula_latex=pmg_ion.to_latex_string(),
|
|
141
|
+
formula_hill=pmg_ion.hill_formula,
|
|
142
|
+
formula_pretty=pmg_ion.to_pretty_string(),
|
|
143
|
+
oxi_state_guesses=oxi_states,
|
|
144
|
+
n_atoms=int(pmg_ion.num_atoms),
|
|
145
|
+
n_elements=len(els),
|
|
199
146
|
)
|
|
200
147
|
|
|
201
|
-
def
|
|
202
|
-
"""
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
Parameters
|
|
206
|
-
----------
|
|
207
|
-
amount: str quantity
|
|
208
|
-
Desired amount of substance. Must be greater than or equal to
|
|
209
|
-
zero and given in mass or substance units.
|
|
210
|
-
|
|
211
|
-
"""
|
|
212
|
-
quantity = unit(amount)
|
|
213
|
-
self.moles = quantity.to(
|
|
214
|
-
"moles", "chem", mw=self.mw, volume=volume, solvent_mass=solvent_mass
|
|
215
|
-
)
|
|
148
|
+
def as_dict(self):
|
|
149
|
+
"""Return a dictionary representation of the Solute."""
|
|
150
|
+
return dict(asdict(self).items())
|
|
216
151
|
|
|
217
152
|
# set output of the print() statement
|
|
218
|
-
def __str__(self):
|
|
153
|
+
def __str__(self) -> str:
|
|
219
154
|
return (
|
|
220
155
|
"Species "
|
|
221
|
-
+ str(self.
|
|
156
|
+
+ str(self.formula)
|
|
222
157
|
+ " MW="
|
|
223
|
-
+ str(self.
|
|
158
|
+
+ str(self.mw)
|
|
224
159
|
+ " Formal Charge="
|
|
225
|
-
+ str(self.
|
|
160
|
+
+ str(self.charge)
|
|
226
161
|
+ " Amount= "
|
|
227
|
-
+ str(self.
|
|
162
|
+
+ str(self.moles)
|
|
228
163
|
)
|