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.
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 +165 -0
  21. pyEQL-1.1.0.dist-info/AUTHORS.md +13 -0
  22. {pyEQL-0.5.2.dist-info → pyEQL-1.1.0.dist-info}/COPYING +1 -1
  23. pyEQL-0.5.2.dist-info/LICENSE → pyEQL-1.1.0.dist-info/LICENSE.txt +3 -7
  24. pyEQL-1.1.0.dist-info/METADATA +129 -0
  25. pyEQL-1.1.0.dist-info/RECORD +27 -0
  26. {pyEQL-0.5.2.dist-info → pyEQL-1.1.0.dist-info}/WHEEL +2 -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.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-2020 by Ryan S. Kingsbury
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
- logger = logging.getLogger(__name__)
29
-
30
- unique = Unique()
31
- logger.addFilter(unique)
11
+ import numpy as np
32
12
 
33
- # add a handler for console output, since pyEQL is meant to be used interactively
34
- ch = logging.StreamHandler()
13
+ from pyEQL import Solution, ureg
35
14
 
36
- # create formatter for the log
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(Solution1, Solution2):
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
- Parameters
49
- ----------
50
- Solution1, Solution2 : Solution objects
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
- .. math::
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
- Where :math:`n` is the number of moles of substance, :math:`T` is the temperature in kelvin,
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
- Note that dissociated ions must be counted as separate components,
73
- so a simple salt dissolved in water is a three component solution (cation,
74
- anion, and water).
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
- References
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
- .. [#] Koga, Yoshikata, 2007. *Solution Thermodynamics and its Application to Aqueous Solutions: \
80
- A differential approach.* Elsevier, 2007, pp. 23-37.
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 = Solution1
87
- dilute = Solution2
88
- blend = mix(Solution1, Solution2)
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 not solution.get_amount(solute, "fraction") == 0:
96
- term_list[solution] += solution.get_amount(solute, "mol") * math.log(
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
- unit.R
102
- * temperature.to("K")
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(Solution1, Solution2):
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
- Where :math:`n` is the number of moles of substance, :math:`T` is the temperature in kelvin,
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
- Note that dissociated ions must be counted as separate components,
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
- References
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
- .. [#] Koga, Yoshikata, 2007. *Solution Thermodynamics and its Application to Aqueous Solutions: \
143
- A differential approach.* Elsevier, 2007, pp. 23-37.
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
- Examples
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 = Solution1
150
- dilute = Solution2
151
- blend = mix(Solution1, Solution2)
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 not solution.get_amount(solute, "fraction") == 0:
159
- term_list[solution] += solution.get_amount(solute, "mol") * math.log(
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
- unit.R
165
- * temperature.to("K")
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
- Notes
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
- The general equation representing the equilibrium between an external
193
- electrolyte solution and an ion-exchange medium containing fixed charges
194
- is:[#]_
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
- .. math:: {a_- \\over \\bar a_-}^{1 \\over z_-} {\\bar a_+ \\over a_+}^{1 \\over z_+} \
197
- = exp({\\Delta \\pi \\bar V \\over {RT z_+ \\nu_+}})
127
+ Returns:
128
+ A Solution that has established Donnan equilibrium with the external
129
+ (input) Solution
198
130
 
199
- Where subscripts :math:`+` and :math:`-` indicate the cation and anion, respectively,
200
- the overbar indicates the membrane phase,
201
- :math:`a` represents activity, :math:`z` represents charge, :math:`\\nu` represents the stoichiometric
202
- coefficient, :math:`V` represents the partial molar volume of the salt, and
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
- In addition, electroneutrality must prevail within the membrane phase:
136
+ .. math::
207
137
 
208
- .. math:: \\bar C_+ z_+ + \\bar X + \\bar C_- z_- = 0
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
- Where :math:`C` represents concentration and :math:`X` is the fixed charge concentration
211
- in the membrane or ion exchange phase.
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
- This function solves these two equations simultaneously to arrive at the
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
- NOTE that this treatment is only capable of equilibrating a single salt.
220
- This salt is identified by the get_salt() method.
151
+ .. math:: \bar C_{+} z_{+} + \bar X + \bar C_{-} z_{-} = 0
221
152
 
222
- References
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
- .. [#] Strathmann, Heiner, ed. *Membrane Science and Technology* vol. 9, 2004. \
226
- Chapter 2, p. 51. http://dx.doi.org/10.1016/S0927-5193(04)80033-0
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
- Examples
230
- --------
231
- TODO
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 = unit(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
- if db.has_parameter(salt.formula, "partial_molar_volume"):
256
- item = db.get_parameter(salt.formula, "partial_molar_volume")
257
- molar_volume = item.get_value()
258
- elif db.has_parameter(salt.cation, "partial_molar_volume") and db.has_parameter(
259
- salt.anion, "partial_molar_volume"
260
- ):
261
- cation_vol = solution.get_solute(salt.cation).get_parameter(
262
- "partial_molar_volume"
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 actvities to feed to the nonlinear solver
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
- act_anion_soln / act_anion_mem
332
- ) ** (1 / z_anion) - math.exp(delta_pi * exp_term)
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
@@ -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