pyEQL 0.5.2__py3-none-any.whl → 1.0.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.
Files changed (62) hide show
  1. pyEQL/__init__.py +50 -43
  2. pyEQL/activity_correction.py +481 -707
  3. pyEQL/database/geothermal.dat +5693 -0
  4. pyEQL/database/llnl.dat +19305 -0
  5. pyEQL/database/phreeqc_license.txt +54 -0
  6. pyEQL/database/pyeql_db.json +35902 -0
  7. pyEQL/engines.py +793 -0
  8. pyEQL/equilibrium.py +148 -228
  9. pyEQL/functions.py +121 -416
  10. pyEQL/pint_custom_units.txt +2 -2
  11. pyEQL/presets/Ringers lactate.yaml +20 -0
  12. pyEQL/presets/normal saline.yaml +17 -0
  13. pyEQL/presets/rainwater.yaml +17 -0
  14. pyEQL/presets/seawater.yaml +29 -0
  15. pyEQL/presets/urine.yaml +26 -0
  16. pyEQL/presets/wastewater.yaml +21 -0
  17. pyEQL/salt_ion_match.py +53 -284
  18. pyEQL/solute.py +126 -191
  19. pyEQL/solution.py +2163 -2090
  20. pyEQL/utils.py +211 -0
  21. pyEQL-1.0.3.dist-info/AUTHORS.md +13 -0
  22. {pyEQL-0.5.2.dist-info → pyEQL-1.0.3.dist-info}/COPYING +1 -1
  23. pyEQL-0.5.2.dist-info/LICENSE → pyEQL-1.0.3.dist-info/LICENSE.txt +3 -7
  24. pyEQL-1.0.3.dist-info/METADATA +131 -0
  25. pyEQL-1.0.3.dist-info/RECORD +27 -0
  26. {pyEQL-0.5.2.dist-info → pyEQL-1.0.3.dist-info}/WHEEL +1 -1
  27. pyEQL/chemical_formula.py +0 -1006
  28. pyEQL/database/Erying_viscosity.tsv +0 -18
  29. pyEQL/database/Jones_Dole_B.tsv +0 -32
  30. pyEQL/database/Jones_Dole_B_inorganic_Jenkins.tsv +0 -75
  31. pyEQL/database/LICENSE +0 -4
  32. pyEQL/database/dielectric_parameter.tsv +0 -30
  33. pyEQL/database/diffusion_coefficient.tsv +0 -116
  34. pyEQL/database/hydrated_radius.tsv +0 -35
  35. pyEQL/database/ionic_radius.tsv +0 -35
  36. pyEQL/database/partial_molar_volume.tsv +0 -22
  37. pyEQL/database/pitzer_activity.tsv +0 -169
  38. pyEQL/database/pitzer_volume.tsv +0 -132
  39. pyEQL/database/template.tsv +0 -14
  40. pyEQL/database.py +0 -300
  41. pyEQL/elements.py +0 -4552
  42. pyEQL/logging_system.py +0 -53
  43. pyEQL/parameter.py +0 -435
  44. pyEQL/tests/__init__.py +0 -32
  45. pyEQL/tests/test_activity.py +0 -578
  46. pyEQL/tests/test_bulk_properties.py +0 -86
  47. pyEQL/tests/test_chemical_formula.py +0 -279
  48. pyEQL/tests/test_debye_length.py +0 -79
  49. pyEQL/tests/test_density.py +0 -106
  50. pyEQL/tests/test_dielectric.py +0 -153
  51. pyEQL/tests/test_effective_pitzer.py +0 -276
  52. pyEQL/tests/test_mixed_electrolyte_activity.py +0 -154
  53. pyEQL/tests/test_osmotic_coeff.py +0 -99
  54. pyEQL/tests/test_pyeql_volume_concentration.py +0 -428
  55. pyEQL/tests/test_salt_matching.py +0 -337
  56. pyEQL/tests/test_solute_properties.py +0 -251
  57. pyEQL/water_properties.py +0 -352
  58. pyEQL-0.5.2.dist-info/AUTHORS +0 -7
  59. pyEQL-0.5.2.dist-info/METADATA +0 -72
  60. pyEQL-0.5.2.dist-info/RECORD +0 -47
  61. pyEQL-0.5.2.dist-info/entry_points.txt +0 -3
  62. {pyEQL-0.5.2.dist-info → pyEQL-1.0.3.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-2020 by Ryan S. Kingsbury
10
+ :copyright: 2013-2024 by Ryan S. Kingsbury
11
11
  :license: LGPL, see LICENSE for more details.
12
12
 
13
13
  """
14
14
 
15
- # the pint unit registry
16
- from pyEQL import unit
15
+ from __future__ import annotations
17
16
 
18
- # logging system
19
17
  import logging
20
- # add a filter to emit only unique log messages to the handler
21
- from pyEQL.logging_system import Unique
22
- # import the parameters database
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
- logger = logging.getLogger(__name__)
26
- unique = Unique()
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
- def __init__(self, formula, amount, volume, solvent_mass, parameters={}):
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
- # translate the 'amount' string into a pint Quantity
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
- def get_parameter(
96
- self, parameter, temperature=None, pressure=None, ionic_strength=None
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
- # TODO - handle the case where multiple parameters with same name exist. Need function
105
- # to compare specified conditions and choose the most appropriate parameter
106
- param = db.get_parameter(self.formula, parameter)
34
+ value: str
35
+ reference: str | None = None
36
+ data_type: Literal["computed", "experimental", "fitted", "unknown"] = "unknown"
107
37
 
108
- return param.get_value(temperature, pressure, ionic_strength)
38
+ @property
39
+ def magnitude(self):
40
+ """Return the numerical value of a Datum."""
41
+ return float(self.value.split(" ")[0])
109
42
 
110
- def add_parameter(self, name, magnitude, units="", **kwargs):
111
- """
112
- Add a parameter to the parameters database for a solute
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
- newparam = pm.Parameter(name, magnitude, units, **kwargs)
120
- db.add_parameter(self.get_name(), newparam)
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 get_name(self):
123
- """
124
- Return the name (formula) of the solute
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
- def get_molecular_weight(self):
155
- """
156
- Return the molecular weight of the solute
157
-
158
- Parameters
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
- def get_moles(self):
170
- """
171
- Return the moles of solute in the solution
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
- def add_moles(self, amount, volume, solvent_mass):
186
- """
187
- Increase or decrease the amount of a substance present in the solution
188
-
189
- Parameters
190
- ----------
191
- amount: str quantity
192
- Amount of substance to add. Must be in mass or substance units.
193
- Negative values indicate subtraction of material.
194
-
195
- """
196
- quantity = unit(amount)
197
- self.moles += quantity.to(
198
- "moles", "chem", mw=self.mw, volume=volume, solvent_mass=solvent_mass
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 set_moles(self, amount, volume, solvent_mass):
202
- """
203
- Set the amount of a substance present in the solution
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.get_name())
156
+ + str(self.formula)
222
157
  + " MW="
223
- + str(self.get_molecular_weight())
158
+ + str(self.mw)
224
159
  + " Formal Charge="
225
- + str(self.get_formal_charge())
160
+ + str(self.charge)
226
161
  + " Amount= "
227
- + str(self.get_moles())
162
+ + str(self.moles)
228
163
  )