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/functions.py
CHANGED
|
@@ -1,245 +1,180 @@
|
|
|
1
1
|
"""
|
|
2
|
-
pyEQL functions that take Solution objects as inputs or return Solution objects
|
|
2
|
+
pyEQL functions that take Solution objects as inputs or return Solution objects.
|
|
3
3
|
|
|
4
|
-
:copyright: 2013-
|
|
4
|
+
:copyright: 2013-2024 by Ryan S. Kingsbury
|
|
5
5
|
:license: LGPL, see LICENSE for more details.
|
|
6
6
|
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
|
-
# Dependencies
|
|
10
|
-
# import libraries for scientific functions
|
|
11
|
-
import math
|
|
12
|
-
|
|
13
|
-
# internal pyEQL imports
|
|
14
|
-
import pyEQL
|
|
15
|
-
|
|
16
|
-
# import the parameters database
|
|
17
|
-
from pyEQL import paramsDB as db
|
|
18
|
-
|
|
19
|
-
# the pint unit registry
|
|
20
|
-
from pyEQL import unit
|
|
21
|
-
|
|
22
|
-
# add a filter to emit only unique log messages to the handler
|
|
23
|
-
from pyEQL.logging_system import Unique
|
|
24
|
-
|
|
25
|
-
# logging system
|
|
26
9
|
import logging
|
|
27
10
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
unique = Unique()
|
|
31
|
-
logger.addFilter(unique)
|
|
11
|
+
import numpy as np
|
|
32
12
|
|
|
33
|
-
|
|
34
|
-
ch = logging.StreamHandler()
|
|
13
|
+
from pyEQL import Solution, ureg
|
|
35
14
|
|
|
36
|
-
|
|
37
|
-
formatter = logging.Formatter("(%(name)s) - %(levelname)s - %(message)s")
|
|
38
|
-
|
|
39
|
-
# add formatter to the handler
|
|
40
|
-
ch.setFormatter(formatter)
|
|
41
|
-
logger.addHandler(ch)
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
42
16
|
|
|
43
17
|
|
|
44
|
-
def gibbs_mix(
|
|
45
|
-
"""
|
|
18
|
+
def gibbs_mix(solution1: Solution, solution2: Solution):
|
|
19
|
+
r"""
|
|
46
20
|
Return the Gibbs energy change associated with mixing two solutions.
|
|
47
21
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
The two solutions to be mixed.
|
|
22
|
+
Args:
|
|
23
|
+
solution1: a solution to be mixed.
|
|
24
|
+
solution2: a solution to be mixed.
|
|
52
25
|
|
|
53
|
-
Returns
|
|
54
|
-
-------
|
|
55
|
-
Quantity
|
|
26
|
+
Returns:
|
|
56
27
|
The change in Gibbs energy associated with complete mixing of the
|
|
57
28
|
Solutions, in Joules.
|
|
58
29
|
|
|
59
|
-
Notes
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
The Gibbs energy of mixing is calculated as follows: [#]_
|
|
30
|
+
Notes:
|
|
31
|
+
The Gibbs energy of mixing is calculated as follows
|
|
63
32
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
\\Delta_{mix} G = \\sum_i (n_c + n_d) R T \\ln a_b - \\sum_i n_c R T \\ln a_c - \\sum_i n_d R T \\ln a_d
|
|
33
|
+
.. math::
|
|
67
34
|
|
|
68
|
-
|
|
69
|
-
and subscripts :math:`b`, :math:`c`, and :math:`d` refer to the concentrated, dilute, and blended
|
|
70
|
-
Solutions, respectively.
|
|
35
|
+
\Delta_{mix} G = \sum_i {(n_c + n_d) R T \ln a_b} - \sum_i {n_c R T \ln a_c} - \sum_i {n_d R T \ln a_d}
|
|
71
36
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
37
|
+
Where :math:`n` is the number of moles of substance, :math:`T` is the temperature in kelvin,
|
|
38
|
+
and subscripts :math:`b`, :math:`c`, and :math:`d` refer to the concentrated, dilute, and blended
|
|
39
|
+
Solutions, respectively.
|
|
75
40
|
|
|
76
|
-
|
|
77
|
-
|
|
41
|
+
Note that dissociated ions must be counted as separate components,
|
|
42
|
+
so a simple salt dissolved in water is a three component solution (cation,
|
|
43
|
+
anion, and water).
|
|
78
44
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
Examples
|
|
83
|
-
--------
|
|
45
|
+
References:
|
|
46
|
+
Koga, Yoshikata, 2007. Solution Thermodynamics and its Application to Aqueous Solutions:
|
|
47
|
+
A differential approach. Elsevier, 2007, pp. 23-37.
|
|
84
48
|
|
|
85
49
|
"""
|
|
86
|
-
concentrate =
|
|
87
|
-
dilute =
|
|
88
|
-
blend =
|
|
50
|
+
concentrate = solution1
|
|
51
|
+
dilute = solution2
|
|
52
|
+
blend = solution1 + solution2
|
|
89
53
|
term_list = {concentrate: 0, dilute: 0, blend: 0}
|
|
90
|
-
temperature = blend.get_temperature()
|
|
91
54
|
|
|
92
55
|
# calculate the entropy change and number of moles solute for each solution
|
|
93
56
|
for solution in term_list:
|
|
94
57
|
for solute in solution.components:
|
|
95
|
-
if
|
|
96
|
-
term_list[solution] += solution.get_amount(solute, "mol") *
|
|
97
|
-
solution.get_activity(solute)
|
|
98
|
-
)
|
|
58
|
+
if solution.get_amount(solute, "fraction") != 0:
|
|
59
|
+
term_list[solution] += solution.get_amount(solute, "mol") * np.log(solution.get_activity(solute))
|
|
99
60
|
|
|
100
|
-
return (
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
* (term_list[blend] - term_list[concentrate] - term_list[dilute])
|
|
104
|
-
).to("J")
|
|
61
|
+
return (ureg.R * blend.temperature.to("K") * (term_list[blend] - term_list[concentrate] - term_list[dilute])).to(
|
|
62
|
+
"J"
|
|
63
|
+
)
|
|
105
64
|
|
|
106
65
|
|
|
107
|
-
def entropy_mix(
|
|
108
|
-
"""
|
|
109
|
-
Return the ideal mixing entropy associated with mixing two solutions
|
|
66
|
+
def entropy_mix(solution1: Solution, solution2: Solution):
|
|
67
|
+
r"""
|
|
68
|
+
Return the ideal mixing entropy associated with mixing two solutions.
|
|
110
69
|
|
|
111
|
-
Parameters
|
|
112
|
-
|
|
113
|
-
Solution1, Solution2 : Solution objects
|
|
114
|
-
The two solutions to be mixed.
|
|
70
|
+
Parameters:
|
|
71
|
+
solution1, solution2: The two solutions to be mixed.
|
|
115
72
|
|
|
116
|
-
Returns
|
|
117
|
-
-------
|
|
118
|
-
Quantity
|
|
73
|
+
Returns:
|
|
119
74
|
The ideal mixing entropy associated with complete mixing of the
|
|
120
75
|
Solutions, in Joules.
|
|
121
76
|
|
|
122
|
-
Notes
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
The ideal entropy of mixing is calculated as follows:[#]_
|
|
126
|
-
|
|
127
|
-
.. math::
|
|
128
|
-
|
|
129
|
-
\\Delta_{mix} S = \\sum_i (n_c + n_d) R T \\ln x_b - \\sum_i n_c R T \\ln x_c - \\sum_i n_d R T \ln x_d
|
|
77
|
+
Notes:
|
|
78
|
+
The ideal entropy of mixing is calculated as follows
|
|
130
79
|
|
|
131
|
-
|
|
132
|
-
and subscripts :math:`b`, :math:`c`, and :math:`d` refer to the concentrated, dilute, and blended
|
|
133
|
-
Solutions, respectively.
|
|
80
|
+
.. math::
|
|
134
81
|
|
|
135
|
-
|
|
136
|
-
so a simple salt dissolved in water is a three component solution (cation,
|
|
137
|
-
anion, and water).
|
|
82
|
+
\Delta_{mix} S = \sum_i {(n_c + n_d) R T \ln x_b} - \sum_i {n_c R T \ln x_c} - \sum_i {n_d R T \ln x_d}
|
|
138
83
|
|
|
139
|
-
|
|
140
|
-
|
|
84
|
+
Where :math:`n` is the number of moles of substance, :math:`T` is the temperature in kelvin,
|
|
85
|
+
and subscripts :math:`b`, :math:`c`, and :math:`d` refer to the concentrated, dilute, and blended
|
|
86
|
+
Solutions, respectively.
|
|
141
87
|
|
|
142
|
-
|
|
143
|
-
|
|
88
|
+
Note that dissociated ions must be counted as separate components,
|
|
89
|
+
so a simple salt dissolved in water is a three component solution (cation,
|
|
90
|
+
anion, and water).
|
|
144
91
|
|
|
145
|
-
|
|
146
|
-
|
|
92
|
+
References:
|
|
93
|
+
Koga, Yoshikata, 2007. *Solution Thermodynamics and its Application to Aqueous Solutions:
|
|
94
|
+
A differential approach.* Elsevier, 2007, pp. 23-37.
|
|
147
95
|
|
|
148
96
|
"""
|
|
149
|
-
concentrate =
|
|
150
|
-
dilute =
|
|
151
|
-
blend =
|
|
97
|
+
concentrate = solution1
|
|
98
|
+
dilute = solution2
|
|
99
|
+
blend = solution1 + solution2
|
|
152
100
|
term_list = {concentrate: 0, dilute: 0, blend: 0}
|
|
153
|
-
temperature = blend.get_temperature()
|
|
154
101
|
|
|
155
102
|
# calculate the entropy change and number of moles solute for each solution
|
|
156
103
|
for solution in term_list:
|
|
157
104
|
for solute in solution.components:
|
|
158
|
-
if
|
|
159
|
-
term_list[solution] += solution.get_amount(solute, "mol") *
|
|
105
|
+
if solution.get_amount(solute, "fraction") != 0:
|
|
106
|
+
term_list[solution] += solution.get_amount(solute, "mol") * np.log(
|
|
160
107
|
solution.get_amount(solute, "fraction")
|
|
161
108
|
)
|
|
162
109
|
|
|
163
|
-
return (
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
* (term_list[blend] - term_list[concentrate] - term_list[dilute])
|
|
167
|
-
).to("J")
|
|
168
|
-
|
|
110
|
+
return (ureg.R * blend.temperature.to("K") * (term_list[blend] - term_list[concentrate] - term_list[dilute])).to(
|
|
111
|
+
"J"
|
|
112
|
+
)
|
|
169
113
|
|
|
170
|
-
def donnan_eql(solution, fixed_charge):
|
|
171
|
-
"""
|
|
172
|
-
Return a solution object in equilibrium with fixed_charge
|
|
173
|
-
|
|
174
|
-
Parameters
|
|
175
|
-
----------
|
|
176
|
-
Solution : Solution object
|
|
177
|
-
The external solution to be brought into equilibrium with the fixed
|
|
178
|
-
charges
|
|
179
|
-
fixed_charge : str quantity
|
|
180
|
-
String representing the concentration of fixed charges, including sign.
|
|
181
|
-
May be specified in mol/L or mol/kg units. e.g. '1 mol/kg'
|
|
182
|
-
|
|
183
|
-
Returns
|
|
184
|
-
-------
|
|
185
|
-
Solution
|
|
186
|
-
A solution that has established Donnan equilibrium with the external
|
|
187
|
-
(input) Solution
|
|
188
114
|
|
|
189
|
-
|
|
190
|
-
|
|
115
|
+
def donnan_eql(solution: Solution, fixed_charge: str):
|
|
116
|
+
r"""
|
|
117
|
+
Return a solution object in equilibrium with fixed_charge.
|
|
191
118
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
119
|
+
Args:
|
|
120
|
+
solution : Solution object
|
|
121
|
+
The external solution to be brought into equilibrium with the fixed
|
|
122
|
+
charges
|
|
123
|
+
fixed_charge : str quantity
|
|
124
|
+
String representing the concentration of fixed charges, including sign.
|
|
125
|
+
May be specified in mol/L or mol/kg units. e.g. '1 mol/kg'
|
|
195
126
|
|
|
196
|
-
|
|
197
|
-
|
|
127
|
+
Returns:
|
|
128
|
+
A Solution that has established Donnan equilibrium with the external
|
|
129
|
+
(input) Solution
|
|
198
130
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
:math:`\\Delta \\pi` is the difference in osmotic pressure between the membrane and the
|
|
204
|
-
solution phase.
|
|
131
|
+
Notes:
|
|
132
|
+
The general equation representing the equilibrium between an external
|
|
133
|
+
electrolyte solution and an ion-exchange medium containing fixed charges
|
|
134
|
+
is
|
|
205
135
|
|
|
206
|
-
|
|
136
|
+
.. math::
|
|
207
137
|
|
|
208
|
-
|
|
138
|
+
\big(\frac{a_{-}}{\bar a_{-}} \big)^(\frac{1}{z_{-})
|
|
139
|
+
\big(\frac{\bar a_{+}}{a_{+}}\big)^(\frac{1}{z_{+})
|
|
140
|
+
\exp(\frac{\Delta \pi \bar V}{RT z_{+} \nu_{+}})
|
|
209
141
|
|
|
210
|
-
|
|
211
|
-
|
|
142
|
+
Where subscripts :math:`+` and :math:`-` indicate the cation and anion, respectively,
|
|
143
|
+
the overbar indicates the membrane phase,
|
|
144
|
+
:math:`a` represents activity, :math:`z` represents charge, :math:`\nu` represents the stoichiometric
|
|
145
|
+
coefficient, :math:`V` represents the partial molar volume of the salt, and
|
|
146
|
+
:math:`\Delta \pi` is the difference in osmotic pressure between the membrane and the
|
|
147
|
+
solution phase.
|
|
212
148
|
|
|
213
|
-
|
|
214
|
-
concentrations of the cation and anion in the membrane phase. It returns
|
|
215
|
-
a solution equal to the input solution except that the concentrations of
|
|
216
|
-
the predominant cation and anion have been adjusted according to this
|
|
217
|
-
equilibrium.
|
|
149
|
+
In addition, electroneutrality must prevail within the membrane phase:
|
|
218
150
|
|
|
219
|
-
|
|
220
|
-
This salt is identified by the get_salt() method.
|
|
151
|
+
.. math:: \bar C_{+} z_{+} + \bar X + \bar C_{-} z_{-} = 0
|
|
221
152
|
|
|
222
|
-
|
|
223
|
-
|
|
153
|
+
Where :math:`C` represents concentration and :math:`X` is the fixed charge concentration
|
|
154
|
+
in the membrane or ion exchange phase.
|
|
224
155
|
|
|
225
|
-
|
|
226
|
-
|
|
156
|
+
This function solves these two equations simultaneously to arrive at the
|
|
157
|
+
concentrations of the cation and anion in the membrane phase. It returns
|
|
158
|
+
a solution equal to the input solution except that the concentrations of
|
|
159
|
+
the predominant cation and anion have been adjusted according to this
|
|
160
|
+
equilibrium.
|
|
227
161
|
|
|
162
|
+
NOTE that this treatment is only capable of equilibrating a single salt.
|
|
163
|
+
This salt is identified by the get_salt() method.
|
|
228
164
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
165
|
+
References:
|
|
166
|
+
Strathmann, Heiner, ed. *Membrane Science and Technology* vol. 9, 2004. Chapter 2, p. 51.
|
|
167
|
+
http://dx.doi.org/10.1016/S0927-5193(04)80033-0
|
|
232
168
|
|
|
233
|
-
See Also
|
|
234
|
-
|
|
235
|
-
get_salt
|
|
169
|
+
See Also:
|
|
170
|
+
get_salt()
|
|
236
171
|
|
|
237
172
|
"""
|
|
238
173
|
# identify the salt
|
|
239
174
|
salt = solution.get_salt()
|
|
240
175
|
|
|
241
176
|
# convert fixed_charge in to a quantity
|
|
242
|
-
fixed_charge =
|
|
177
|
+
fixed_charge = ureg.Quantity(fixed_charge)
|
|
243
178
|
|
|
244
179
|
# identify variables from the external solution
|
|
245
180
|
conc_cation_soln = solution.get_amount(salt.cation, str(fixed_charge.units))
|
|
@@ -252,24 +187,15 @@ def donnan_eql(solution, fixed_charge):
|
|
|
252
187
|
|
|
253
188
|
# get the partial molar volume for the salt, or calculate it from the ions
|
|
254
189
|
# TODO - consider how to incorporate pitzer parameters
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
"
|
|
263
|
-
|
|
264
|
-
anion_vol = solution.get_solute(salt.anion).get_parameter(
|
|
265
|
-
"partial_molar_volume"
|
|
266
|
-
)
|
|
267
|
-
molar_volume = cation_vol + anion_vol
|
|
268
|
-
else:
|
|
269
|
-
logger.error(
|
|
270
|
-
"Required partial molar volume information not available. Aborting."
|
|
271
|
-
)
|
|
272
|
-
return None
|
|
190
|
+
molar_volume = solution.get_property(salt.formula, "size.molar_volume")
|
|
191
|
+
if molar_volume is None:
|
|
192
|
+
cation_vol = solution.get_property(salt.cation, "size.molar_volume")
|
|
193
|
+
anion_vol = solution.get_property(salt.anion, "size.molar_volume")
|
|
194
|
+
if cation_vol is not None and anion_vol is not None:
|
|
195
|
+
molar_volume = cation_vol + anion_vol
|
|
196
|
+
else:
|
|
197
|
+
logger.error("Required partial molar volume information not available. Aborting.")
|
|
198
|
+
return None
|
|
273
199
|
|
|
274
200
|
# initialize the equilibrated solution - start with a direct copy of the
|
|
275
201
|
# input / external solution
|
|
@@ -280,33 +206,24 @@ def donnan_eql(solution, fixed_charge):
|
|
|
280
206
|
return donnan_soln
|
|
281
207
|
|
|
282
208
|
# define a function representing the donnan equilibrium as a function
|
|
283
|
-
# of the two unknown
|
|
209
|
+
# of the two unknown activities to feed to the nonlinear solver
|
|
284
210
|
|
|
285
211
|
# the stuff in the term below doesn't change on iteration, so calculate it up-front
|
|
286
212
|
# assign it the correct units and extract the magnitude for a performance gain
|
|
287
|
-
exp_term = (
|
|
288
|
-
(molar_volume / (unit.R * solution.get_temperature() * z_cation * nu_cation))
|
|
289
|
-
.to("1/Pa")
|
|
290
|
-
.magnitude
|
|
291
|
-
)
|
|
213
|
+
exp_term = (molar_volume / (ureg.R * solution.temperature * z_cation * nu_cation)).to("1/Pa").magnitude
|
|
292
214
|
|
|
293
215
|
def donnan_solve(x):
|
|
294
|
-
"""Where x is the magnitude of co-ion concentration
|
|
295
|
-
"""
|
|
216
|
+
"""Where x is the magnitude of co-ion concentration."""
|
|
296
217
|
# solve for the counter-ion concentration by enforcing electroneutrality
|
|
297
218
|
# using only floats / ints here instead of quantities helps performance
|
|
298
219
|
if fixed_charge.magnitude >= 0:
|
|
299
220
|
# counter-ion is the anion
|
|
300
221
|
conc_cation_mem = x / abs(z_cation)
|
|
301
|
-
conc_anion_mem = (
|
|
302
|
-
-(conc_cation_mem * z_cation + fixed_charge.magnitude) / z_anion
|
|
303
|
-
)
|
|
222
|
+
conc_anion_mem = -(conc_cation_mem * z_cation + fixed_charge.magnitude) / z_anion
|
|
304
223
|
elif fixed_charge.magnitude < 0:
|
|
305
224
|
# counter-ion is the cation
|
|
306
225
|
conc_anion_mem = x / abs(z_anion)
|
|
307
|
-
conc_cation_mem = (
|
|
308
|
-
-(conc_anion_mem * z_anion + fixed_charge.magnitude) / z_cation
|
|
309
|
-
)
|
|
226
|
+
conc_cation_mem = -(conc_anion_mem * z_anion + fixed_charge.magnitude) / z_cation
|
|
310
227
|
|
|
311
228
|
# match the units given for fixed_charge
|
|
312
229
|
units = str(fixed_charge.units)
|
|
@@ -322,14 +239,11 @@ def donnan_eql(solution, fixed_charge):
|
|
|
322
239
|
|
|
323
240
|
# compute the difference in osmotic pressure
|
|
324
241
|
# using the magnitudes here helps performance
|
|
325
|
-
delta_pi =
|
|
326
|
-
donnan_soln.get_osmotic_pressure().magnitude
|
|
327
|
-
- solution.get_osmotic_pressure().magnitude
|
|
328
|
-
)
|
|
242
|
+
delta_pi = donnan_soln.osmotic_pressure.magnitude - solution.osmotic_pressure.magnitude
|
|
329
243
|
|
|
330
|
-
return (act_cation_mem / act_cation_soln) ** (1 / z_cation) * (
|
|
331
|
-
|
|
332
|
-
)
|
|
244
|
+
return (act_cation_mem / act_cation_soln) ** (1 / z_cation) * (act_anion_soln / act_anion_mem) ** (
|
|
245
|
+
1 / z_anion
|
|
246
|
+
) - np.exp(delta_pi * exp_term)
|
|
333
247
|
|
|
334
248
|
# solve the function above using one of scipy's nonlinear solvers
|
|
335
249
|
|
|
@@ -351,212 +265,3 @@ def donnan_eql(solution, fixed_charge):
|
|
|
351
265
|
|
|
352
266
|
# return the equilibrated solution
|
|
353
267
|
return donnan_soln
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
def mix(Solution1, Solution2):
|
|
357
|
-
"""
|
|
358
|
-
Mix two solutions together
|
|
359
|
-
|
|
360
|
-
Returns a new Solution object that results from the mixing of Solution1
|
|
361
|
-
and Solution2
|
|
362
|
-
|
|
363
|
-
Parameters
|
|
364
|
-
----------
|
|
365
|
-
Solution1, Solution2 : Solution objects
|
|
366
|
-
The two solutions to be mixed.
|
|
367
|
-
|
|
368
|
-
Returns
|
|
369
|
-
-------
|
|
370
|
-
Solution
|
|
371
|
-
A Solution object representing the mixed solution.
|
|
372
|
-
|
|
373
|
-
"""
|
|
374
|
-
# check to see if the two solutions have the same solvent
|
|
375
|
-
if not Solution1.solvent_name == Solution2.solvent_name:
|
|
376
|
-
logger.error(
|
|
377
|
-
"mix() function does not support solutions with different solvents. Aborting."
|
|
378
|
-
)
|
|
379
|
-
|
|
380
|
-
if not Solution1.solvent_name == "H2O" or Solution1.solvent_name == "water":
|
|
381
|
-
logger.error("mix() function does not support non-water solvents. Aborting.")
|
|
382
|
-
|
|
383
|
-
# set the pressure for the new solution
|
|
384
|
-
p1 = Solution1.get_pressure()
|
|
385
|
-
t1 = Solution1.get_temperature()
|
|
386
|
-
v1 = Solution1.get_volume()
|
|
387
|
-
p2 = Solution2.get_pressure()
|
|
388
|
-
t2 = Solution2.get_temperature()
|
|
389
|
-
v2 = Solution2.get_volume()
|
|
390
|
-
|
|
391
|
-
# check to see if the solutions have the same temperature and pressure
|
|
392
|
-
if not p1 == p2:
|
|
393
|
-
logger.info(
|
|
394
|
-
"mix() function called between two solutions of different pressure. Pressures will be averaged "
|
|
395
|
-
"(weighted by volume)"
|
|
396
|
-
)
|
|
397
|
-
|
|
398
|
-
blend_pressure = str((p1 * v1 + p2 * v2) / (v1 + v2))
|
|
399
|
-
|
|
400
|
-
if not t1 == t2:
|
|
401
|
-
logger.info(
|
|
402
|
-
"mix() function called between two solutions of different temperature. Temperatures will be averaged "
|
|
403
|
-
"(weighted by volume)"
|
|
404
|
-
)
|
|
405
|
-
|
|
406
|
-
blend_temperature = str((t1 * v1 + t2 * v2) / (v1 + v2))
|
|
407
|
-
|
|
408
|
-
# retrieve the amount of each component in the parent solution and
|
|
409
|
-
# store in a list.
|
|
410
|
-
mix_species = {}
|
|
411
|
-
for item in Solution1.components:
|
|
412
|
-
mix_species.update({item: str(Solution1.get_amount(item, "mol"))})
|
|
413
|
-
for item in Solution2.components:
|
|
414
|
-
if item in mix_species:
|
|
415
|
-
new_amt = str(unit(mix_species[item]) + Solution2.get_amount(item, "mol"))
|
|
416
|
-
mix_species.update({item: new_amt})
|
|
417
|
-
else:
|
|
418
|
-
mix_species.update({item: Solution2.get_amount(item, "mol")})
|
|
419
|
-
|
|
420
|
-
# create an empty solution for the mixture
|
|
421
|
-
Blend = pyEQL.Solution(temperature=blend_temperature, pressure=blend_pressure)
|
|
422
|
-
|
|
423
|
-
# set or add the appropriate amount of all the components
|
|
424
|
-
for item in mix_species.keys():
|
|
425
|
-
if item in Blend.components:
|
|
426
|
-
# if already present (e.g. H2O, H+), modify the amount
|
|
427
|
-
Blend.set_amount(item, mix_species[item])
|
|
428
|
-
else:
|
|
429
|
-
# if not already present, add the component
|
|
430
|
-
Blend.add_solute(item, mix_species[item])
|
|
431
|
-
|
|
432
|
-
return Blend
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
def autogenerate(solution=""):
|
|
436
|
-
"""
|
|
437
|
-
This method provides a quick way to create Solution objects representing
|
|
438
|
-
commonly-encountered solutions, such as seawater, rainwater, and wastewater.
|
|
439
|
-
|
|
440
|
-
Parameters
|
|
441
|
-
----------
|
|
442
|
-
solution : str
|
|
443
|
-
String representing the desired solution
|
|
444
|
-
Valid entries are 'seawater', 'rainwater',
|
|
445
|
-
'wastewater',and 'urine'
|
|
446
|
-
|
|
447
|
-
Returns
|
|
448
|
-
-------
|
|
449
|
-
Solution
|
|
450
|
-
A pyEQL Solution object.
|
|
451
|
-
|
|
452
|
-
Notes
|
|
453
|
-
-----
|
|
454
|
-
The following sections explain the different solution options:
|
|
455
|
-
|
|
456
|
-
- '' - empty solution, equivalent to pyEQL.Solution()
|
|
457
|
-
- 'rainwater' - pure water in equilibrium with atmospheric CO2 at pH 6
|
|
458
|
-
- 'seawater' or 'SW'- Standard Seawater. See Table 4 of the Reference for Composition [#]_
|
|
459
|
-
- 'wastewater' or 'WW' - medium strength domestic wastewater. See Table 3-18 of [#]_
|
|
460
|
-
- 'urine' - typical human urine. See Table 3-15 of [#]_
|
|
461
|
-
- 'normal saline' or 'NS' - normal saline solution used in medicine [#]_
|
|
462
|
-
- 'Ringers lacatate' or 'RL' - Ringer's lactate solution used in medicine [#]_
|
|
463
|
-
|
|
464
|
-
References
|
|
465
|
-
----------
|
|
466
|
-
.. [#] Millero, Frank J. "The composition of Standard Seawater and the definition of \
|
|
467
|
-
the Reference-Composition Salinity Scale." *Deep-sea Research. Part I* 55(1), 2008, 50-72.
|
|
468
|
-
|
|
469
|
-
.. [#] Metcalf & Eddy, Inc. et al. *Wastewater Engineering: Treatment and Resource Recovery*, 5th Ed. \
|
|
470
|
-
McGraw-Hill, 2013.
|
|
471
|
-
|
|
472
|
-
.. [#] https://en.wikipedia.org/wiki/Saline_(medicine)
|
|
473
|
-
|
|
474
|
-
.. [#] https://en.wikipedia.org/wiki/Ringer%27s_lactate_solution
|
|
475
|
-
|
|
476
|
-
"""
|
|
477
|
-
|
|
478
|
-
if solution == "":
|
|
479
|
-
temperature = "25 degC"
|
|
480
|
-
pressure = "1 atm"
|
|
481
|
-
pH = 7
|
|
482
|
-
solutes = []
|
|
483
|
-
elif solution == "seawater" or solution == "SW":
|
|
484
|
-
temperature = "25 degC"
|
|
485
|
-
pressure = "1 atm"
|
|
486
|
-
pH = 8.1
|
|
487
|
-
solutes = [
|
|
488
|
-
["Na+", "10.78145 g/kg"],
|
|
489
|
-
["Mg+2", "1.28372 g/kg"],
|
|
490
|
-
["Ca+2", "0.41208 g/kg"],
|
|
491
|
-
["K+", "0.39910 g/kg"],
|
|
492
|
-
["Sr+2", "0.00795 g/kg"],
|
|
493
|
-
["Cl-", "19.35271 g/kg"],
|
|
494
|
-
["SO4-2", "2.71235 g/kg"],
|
|
495
|
-
["HCO3-", "0.10481 g/kg"],
|
|
496
|
-
["Br-", "0.06728 g/kg"],
|
|
497
|
-
["CO3-2", "0.01434 g/kg"],
|
|
498
|
-
["B(OH)4", "0.00795 g/kg"],
|
|
499
|
-
["F-", "0.00130 g/kg"],
|
|
500
|
-
["OH-", "0.00014 g/kg"],
|
|
501
|
-
["B(OH)3", "0.01944 g/kg"],
|
|
502
|
-
["CO2", "0.00042 g/kg"],
|
|
503
|
-
]
|
|
504
|
-
elif solution == "rainwater":
|
|
505
|
-
temperature = "25 degC"
|
|
506
|
-
pressure = "1 atm"
|
|
507
|
-
pH = 6
|
|
508
|
-
solutes = [["HCO3-", "10^-5.5 mol/L"], ["CO3-2", "10^-9 mol/L"]]
|
|
509
|
-
elif solution == "wastewater" or solution == "WW":
|
|
510
|
-
temperature = "25 degC"
|
|
511
|
-
pressure = "1 atm"
|
|
512
|
-
pH = 7
|
|
513
|
-
solutes = [
|
|
514
|
-
["NH3", "24.3 mg/L"],
|
|
515
|
-
["PO4-3", "7.6 mg/L"],
|
|
516
|
-
["C6H12O6", "410 mg/L"],
|
|
517
|
-
["K+", "16 mg/L"],
|
|
518
|
-
["Cl-", "59 mg/L"],
|
|
519
|
-
["SO4-2", "26 mg/L"],
|
|
520
|
-
]
|
|
521
|
-
logger.warning("Total organic carbon in wastewater is approximated as glucose")
|
|
522
|
-
elif solution == "urine":
|
|
523
|
-
temperature = "25 degC"
|
|
524
|
-
pressure = "1 atm"
|
|
525
|
-
pH = 7
|
|
526
|
-
solutes = [
|
|
527
|
-
["CON2H4", "20,000 mg/L"],
|
|
528
|
-
["C4H7N3O", "1,000 mg/L"],
|
|
529
|
-
["C5H4N4O3", "300 mg/L"],
|
|
530
|
-
["NH4+", "500 mg/L"],
|
|
531
|
-
["HCO3-", "300 mg/L"],
|
|
532
|
-
["NH4+", "500 mg/L"],
|
|
533
|
-
["Mg+2", "100 mg/L"],
|
|
534
|
-
["PO4-3", "1200 mg/L"],
|
|
535
|
-
["Na+", "6000 mg/L"],
|
|
536
|
-
["K+", "1500 mg/L"],
|
|
537
|
-
["Cl-", "1900 mg/L"],
|
|
538
|
-
["SO4-2", "1800 mg/L"],
|
|
539
|
-
]
|
|
540
|
-
elif solution == "normal saline" or solution == "NS":
|
|
541
|
-
temperature = "25 degC"
|
|
542
|
-
pressure = "1 atm"
|
|
543
|
-
pH = 7
|
|
544
|
-
solutes = [["Na+", "154 mmol/L"], ["Cl-", "154 mmol/L"]]
|
|
545
|
-
elif solution == "Ringers lactate" or solution == "RL":
|
|
546
|
-
temperature = "25 degC"
|
|
547
|
-
pressure = "1 atm"
|
|
548
|
-
pH = 6.5
|
|
549
|
-
solutes = [
|
|
550
|
-
["Na+", "130 mmol/L"],
|
|
551
|
-
["Cl-", "109 mmol/L"],
|
|
552
|
-
["K+", "4 mmol/L"],
|
|
553
|
-
["Ca+2", "1.5 mmol/L"],
|
|
554
|
-
["C3H5O3-", "28 mmol/L"],
|
|
555
|
-
]
|
|
556
|
-
else:
|
|
557
|
-
logger.error("Invalid solution entered - %s" % solution)
|
|
558
|
-
return None
|
|
559
|
-
|
|
560
|
-
sol = pyEQL.Solution(solutes, temperature=temperature, pressure=pressure, pH=pH)
|
|
561
|
-
|
|
562
|
-
return sol
|
pyEQL/pint_custom_units.txt
CHANGED
|
@@ -7,12 +7,12 @@
|
|
|
7
7
|
# mw is the molecular weight of the species
|
|
8
8
|
# volume is the volume of the solution
|
|
9
9
|
# solvent_mass is the mass of solvent in the solution
|
|
10
|
-
|
|
10
|
+
|
|
11
11
|
# moles -> mass require the molecular weight
|
|
12
12
|
[substance] -> [mass]: value * mw
|
|
13
13
|
[mass] -> [substance]: value / mw
|
|
14
14
|
|
|
15
|
-
# moles/volume -> mass/volume and moles/mass -> mass / mass
|
|
15
|
+
# moles/volume -> mass/volume and moles/mass -> mass / mass
|
|
16
16
|
# require the molecular weight
|
|
17
17
|
[substance] / [volume] -> [mass] / [volume]: value * mw
|
|
18
18
|
[mass] / [volume] -> [substance] / [volume]: value / mw
|