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.
- 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 +211 -0
- pyEQL-1.0.3.dist-info/AUTHORS.md +13 -0
- {pyEQL-0.5.2.dist-info → pyEQL-1.0.3.dist-info}/COPYING +1 -1
- pyEQL-0.5.2.dist-info/LICENSE → pyEQL-1.0.3.dist-info/LICENSE.txt +3 -7
- pyEQL-1.0.3.dist-info/METADATA +131 -0
- pyEQL-1.0.3.dist-info/RECORD +27 -0
- {pyEQL-0.5.2.dist-info → pyEQL-1.0.3.dist-info}/WHEEL +1 -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.0.3.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
'@module': pyEQL.solution
|
|
2
|
+
'@class': Solution
|
|
3
|
+
'@version': 0.0.post1.dev699+g0764b1c
|
|
4
|
+
solutes:
|
|
5
|
+
H2O(aq): 55.2313771443148 mol
|
|
6
|
+
Na[+1]: 0.13 mol
|
|
7
|
+
Cl[-1]: 0.109 mol
|
|
8
|
+
H5(CO)3[-1]: 0.028 mol
|
|
9
|
+
K[+1]: 0.004 mol
|
|
10
|
+
Ca[+2]: 0.0015 mol
|
|
11
|
+
H[+1]: 3.162277660168379e-07 mol
|
|
12
|
+
OH[-1]: 3.162277660168379e-08 mol
|
|
13
|
+
volume: 1 l
|
|
14
|
+
temperature: 298.15 K
|
|
15
|
+
pressure: 1 atm
|
|
16
|
+
pH: 6.5
|
|
17
|
+
pE: 8.5
|
|
18
|
+
balance_charge:
|
|
19
|
+
solvent: H2O(aq)
|
|
20
|
+
engine: native
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
'@module': pyEQL.solution
|
|
2
|
+
'@class': Solution
|
|
3
|
+
'@version': 0.0.post1.dev699+g0764b1c
|
|
4
|
+
solutes:
|
|
5
|
+
H2O(aq): 55.19703719794332 mol
|
|
6
|
+
Na[+1]: 0.154 mol
|
|
7
|
+
Cl[-1]: 0.154 mol
|
|
8
|
+
H[+1]: 1e-07 mol
|
|
9
|
+
OH[-1]: 1e-07 mol
|
|
10
|
+
volume: 1 l
|
|
11
|
+
temperature: 298.15 K
|
|
12
|
+
pressure: 1 atm
|
|
13
|
+
pH: 7.0
|
|
14
|
+
pE: 8.5
|
|
15
|
+
balance_charge:
|
|
16
|
+
solvent: H2O(aq)
|
|
17
|
+
engine: native
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
'@module': pyEQL.solution
|
|
2
|
+
'@class': Solution
|
|
3
|
+
'@version': 0.0.post1.dev699+g0764b1c
|
|
4
|
+
solutes:
|
|
5
|
+
H2O(aq): 55.34454944845822 mol
|
|
6
|
+
HCO3[-1]: 3.162277660168379e-06 mol
|
|
7
|
+
H[+1]: 1e-06 mol
|
|
8
|
+
OH[-1]: 1e-08 mol
|
|
9
|
+
CO3[-2]: 1e-09 mol
|
|
10
|
+
volume: 1 l
|
|
11
|
+
temperature: 298.15 K
|
|
12
|
+
pressure: 1 atm
|
|
13
|
+
pH: 6.0
|
|
14
|
+
pE: 8.5
|
|
15
|
+
balance_charge:
|
|
16
|
+
solvent: H2O(aq)
|
|
17
|
+
engine: native
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
'@module': pyEQL.solution
|
|
2
|
+
'@class': Solution
|
|
3
|
+
'@version': 0.0.post1.dev699+g0764b1c
|
|
4
|
+
solutes:
|
|
5
|
+
H2O(aq): 55.34455401423017 mol
|
|
6
|
+
Cl[-1]: 0.54425785619973 mol
|
|
7
|
+
Na[+1]: 0.4675827371496296 mol
|
|
8
|
+
Mg[+2]: 0.05266118052346798 mol
|
|
9
|
+
SO4[-2]: 0.02815187344845402 mol
|
|
10
|
+
Ca[+2]: 0.010251594148212317 mol
|
|
11
|
+
K[+1]: 0.010177468379526855 mol
|
|
12
|
+
HCO3[-1]: 0.0017126511769261989 mol
|
|
13
|
+
Br[-1]: 0.0008395244921424561 mol
|
|
14
|
+
B(OH)3(aq): 0.0003134669156396757 mol
|
|
15
|
+
CO3[-2]: 0.00023825904349479544 mol
|
|
16
|
+
B(OH)4(aq): 0.0001005389715937341 mol
|
|
17
|
+
Sr[+2]: 9.046483353663284e-05 mol
|
|
18
|
+
F[-1]: 6.822478260456777e-05 mol
|
|
19
|
+
CO2(aq): 9.515218476861173e-06 mol
|
|
20
|
+
OH[-1]: 8.207436858780224e-06 mol
|
|
21
|
+
H[+1]: 7.943282347242822e-09 mol
|
|
22
|
+
volume: 1.0080615264452506 l
|
|
23
|
+
temperature: 298.15 K
|
|
24
|
+
pressure: 1 atm
|
|
25
|
+
pH: 8.103487039827968
|
|
26
|
+
pE: 8.5
|
|
27
|
+
balance_charge:
|
|
28
|
+
solvent: H2O(aq)
|
|
29
|
+
engine: native
|
pyEQL/presets/urine.yaml
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
'@module': pyEQL.solution
|
|
2
|
+
'@class': Solution
|
|
3
|
+
'@version': 0.0.post1.dev699+g0764b1c
|
|
4
|
+
solutes:
|
|
5
|
+
H2O(aq): 55.135679438263864 mol
|
|
6
|
+
H4CN2O(aq): 0.333026615820163 mol
|
|
7
|
+
Na[+1]: 0.26098565526795925 mol
|
|
8
|
+
Cl[-1]: 0.05359207965475418 mol
|
|
9
|
+
K[+1]: 0.038364839391994025 mol
|
|
10
|
+
H4N[+1]: 0.027718552470665455 mol
|
|
11
|
+
SO4[-2]: 0.018737781405042127 mol
|
|
12
|
+
PO4[-3]: 0.012635387918307416 mol
|
|
13
|
+
H7C4N3O(aq): 0.008840335409397701 mol
|
|
14
|
+
HCO3[-1]: 0.004916675462052772 mol
|
|
15
|
+
Mg[+2]: 0.004114379757251594 mol
|
|
16
|
+
H4C5N4O3(aq): 0.0017845430730997621 mol
|
|
17
|
+
H[+1]: 1e-07 mol
|
|
18
|
+
OH[-1]: 1e-07 mol
|
|
19
|
+
volume: 1 l
|
|
20
|
+
temperature: 298.15 K
|
|
21
|
+
pressure: 1 atm
|
|
22
|
+
pH: 7.0
|
|
23
|
+
pE: 8.5
|
|
24
|
+
balance_charge:
|
|
25
|
+
solvent: H2O(aq)
|
|
26
|
+
engine: native
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
'@module': pyEQL.solution
|
|
2
|
+
'@class': Solution
|
|
3
|
+
'@version': 0.0.post1.dev699+g0764b1c
|
|
4
|
+
solutes:
|
|
5
|
+
H2O(aq): 55.342123269143364 mol
|
|
6
|
+
CH3COOH(aq): 0.006827420786931851 mol
|
|
7
|
+
Cl[-1]: 0.0016641751050686824 mol
|
|
8
|
+
H3N(aq): 0.0014268501490265714 mol
|
|
9
|
+
K[+1]: 0.0004092249535146029 mol
|
|
10
|
+
SO4[-2]: 0.00027065684251727516 mol
|
|
11
|
+
PO4[-3]: 8.002412348261364e-05 mol
|
|
12
|
+
H[+1]: 1e-07 mol
|
|
13
|
+
OH[-1]: 1e-07 mol
|
|
14
|
+
volume: 1 l
|
|
15
|
+
temperature: 298.15 K
|
|
16
|
+
pressure: 1 atm
|
|
17
|
+
pH: 7.0
|
|
18
|
+
pE: 8.5
|
|
19
|
+
balance_charge:
|
|
20
|
+
solvent: H2O(aq)
|
|
21
|
+
engine: native
|
pyEQL/salt_ion_match.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"""
|
|
2
|
-
pyEQL salt matching library
|
|
2
|
+
pyEQL salt matching library.
|
|
3
3
|
|
|
4
4
|
This file contains functions that allow a pyEQL Solution object composed of
|
|
5
5
|
individual species (usually ions) to be mapped to a solution of one or more
|
|
@@ -7,72 +7,51 @@ salts. This mapping is necessary because some parameters (such as activity
|
|
|
7
7
|
coefficient data) can only be determined for salts (e.g. NaCl) and not individual
|
|
8
8
|
species (e.g. Na+)
|
|
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
|
-
# logging system
|
|
15
|
-
import logging
|
|
16
14
|
|
|
17
|
-
|
|
18
|
-
from
|
|
15
|
+
from monty.json import MSONable
|
|
16
|
+
from pymatgen.core.ion import Ion
|
|
19
17
|
|
|
20
|
-
|
|
18
|
+
from pyEQL.utils import standardize_formula
|
|
21
19
|
|
|
22
|
-
logger = logging.getLogger(__name__)
|
|
23
20
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
# add a handler for console output, since pyEQL is meant to be used interactively
|
|
28
|
-
ch = logging.StreamHandler()
|
|
29
|
-
|
|
30
|
-
# create formatter for the log
|
|
31
|
-
formatter = logging.Formatter("(%(name)s) - %(levelname)s - %(message)s")
|
|
32
|
-
|
|
33
|
-
# add formatter to the handler
|
|
34
|
-
ch.setFormatter(formatter)
|
|
35
|
-
logger.addHandler(ch)
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
class Salt:
|
|
39
|
-
"""
|
|
40
|
-
Class to represent a salt.
|
|
41
|
-
"""
|
|
42
|
-
|
|
43
|
-
def __init__(self, cation, anion):
|
|
44
|
-
self.cation = cation
|
|
45
|
-
self.anion = anion
|
|
21
|
+
class Salt(MSONable):
|
|
22
|
+
"""Class to represent a salt."""
|
|
46
23
|
|
|
24
|
+
def __init__(self, cation, anion) -> None:
|
|
47
25
|
"""
|
|
48
|
-
Create a
|
|
49
|
-
|
|
26
|
+
Create a Salt object based on its component ions.
|
|
27
|
+
|
|
50
28
|
Parameters:
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
Chemical formula of the cation and anion, respectively
|
|
54
|
-
|
|
29
|
+
cation, anion: (str) Chemical formula of the cation and anion, respectively.
|
|
30
|
+
|
|
55
31
|
Returns:
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
32
|
+
Salt : An object representing the properties of the salt
|
|
33
|
+
|
|
59
34
|
Examples:
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
'MgCl2'
|
|
66
|
-
|
|
35
|
+
>>> Salt('Na+','Cl-').formula
|
|
36
|
+
'NaCl'
|
|
37
|
+
|
|
38
|
+
>>> Salt('Mg++','Cl-').formula
|
|
39
|
+
'MgCl2'
|
|
67
40
|
"""
|
|
41
|
+
# create pymatgen Ion objects
|
|
42
|
+
pmg_cat = Ion.from_formula(cation)
|
|
43
|
+
pmg_an = Ion.from_formula(anion)
|
|
44
|
+
# standardize the cation and anion formulas
|
|
45
|
+
self.cation = standardize_formula(cation)
|
|
46
|
+
self.anion = standardize_formula(anion)
|
|
68
47
|
|
|
69
48
|
# get the charges on cation and anion
|
|
70
|
-
self.z_cation =
|
|
71
|
-
self.z_anion =
|
|
49
|
+
self.z_cation = pmg_cat.charge
|
|
50
|
+
self.z_anion = pmg_an.charge
|
|
72
51
|
|
|
73
52
|
# assign stoichiometric coefficients by finding a common multiple
|
|
74
|
-
self.nu_cation = abs(self.z_anion)
|
|
75
|
-
self.nu_anion = abs(self.z_cation)
|
|
53
|
+
self.nu_cation = int(abs(self.z_anion))
|
|
54
|
+
self.nu_anion = int(abs(self.z_cation))
|
|
76
55
|
|
|
77
56
|
# if both coefficients are the same, set each to one
|
|
78
57
|
if self.nu_cation == self.nu_anion:
|
|
@@ -83,261 +62,51 @@ class Salt:
|
|
|
83
62
|
salt_formula = ""
|
|
84
63
|
if self.nu_cation > 1:
|
|
85
64
|
# add parentheses if the cation is a polyatomic ion
|
|
86
|
-
if len(
|
|
65
|
+
if len(pmg_cat.elements) > 1:
|
|
87
66
|
salt_formula += "("
|
|
88
|
-
salt_formula +=
|
|
67
|
+
salt_formula += self.cation.split("[")[0]
|
|
89
68
|
salt_formula += ")"
|
|
90
69
|
else:
|
|
91
|
-
salt_formula +=
|
|
70
|
+
salt_formula += self.cation.split("[")[0]
|
|
92
71
|
salt_formula += str(self.nu_cation)
|
|
93
72
|
else:
|
|
94
|
-
salt_formula +=
|
|
73
|
+
salt_formula += self.cation.split("[")[0]
|
|
95
74
|
|
|
96
75
|
if self.nu_anion > 1:
|
|
97
76
|
# add parentheses if the anion is a polyatomic ion
|
|
98
|
-
if len(
|
|
77
|
+
if len(pmg_an.elements) > 1:
|
|
99
78
|
salt_formula += "("
|
|
100
|
-
salt_formula +=
|
|
79
|
+
salt_formula += self.anion.split("[")[0]
|
|
101
80
|
salt_formula += ")"
|
|
102
81
|
else:
|
|
103
|
-
salt_formula +=
|
|
82
|
+
salt_formula += self.anion.split("[")[0]
|
|
104
83
|
salt_formula += str(self.nu_anion)
|
|
105
84
|
else:
|
|
106
|
-
salt_formula +=
|
|
85
|
+
salt_formula += self.anion.split("[")[0]
|
|
107
86
|
|
|
108
87
|
self.formula = salt_formula
|
|
109
88
|
|
|
89
|
+
# TODO - consider whether this should be adjusted to be based on total concentrations or not
|
|
90
|
+
# NOTE: speciating the solution results in a decrease in the overall ionic strength, because some of the
|
|
91
|
+
# Mg+2 is converted to monovalent complexes like MgOH+. Hence, the activity coefficients deviate a bit from
|
|
92
|
+
# the published values.
|
|
110
93
|
def get_effective_molality(self, ionic_strength):
|
|
111
|
-
"""
|
|
94
|
+
r"""Calculate the effective molality according to [mistry]_.
|
|
112
95
|
|
|
113
|
-
.. math:: 2 I
|
|
96
|
+
.. math:: 2 I \over (\nu_+ z_+^2 + \nu_- z_- ^2)
|
|
114
97
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
The ionic strength of the parent solution, mol/kg
|
|
98
|
+
Args:
|
|
99
|
+
ionic_strength: Quantity
|
|
100
|
+
The ionic strength of the parent solution, mol/kg
|
|
119
101
|
|
|
120
|
-
Returns
|
|
121
|
-
|
|
122
|
-
Quantity: the effective molality of the salt in the parent solution
|
|
102
|
+
Returns:
|
|
103
|
+
Quantity: the effective molality of the salt in the parent solution
|
|
123
104
|
|
|
124
|
-
References
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
for mixed electrolyte solutions with comparison to seawater. \
|
|
129
|
-
Desalination 2013, 318, 34–47.
|
|
105
|
+
References:
|
|
106
|
+
.. [mistry] Mistry, K. H.; Hunter, H. a.; Lienhard V, J. H. Effect of composition and nonideal solution behavior
|
|
107
|
+
on desalination calculations for mixed electrolyte solutions with comparison to seawater. Desalination
|
|
108
|
+
2013, 318, 34-47.
|
|
130
109
|
"""
|
|
131
|
-
m_effective = (
|
|
132
|
-
2
|
|
133
|
-
* ionic_strength
|
|
134
|
-
/ (self.nu_cation * self.z_cation ** 2 + self.nu_anion * self.z_anion ** 2)
|
|
135
|
-
)
|
|
110
|
+
m_effective = 2 * ionic_strength / (self.nu_cation * self.z_cation**2 + self.nu_anion * self.z_anion**2)
|
|
136
111
|
|
|
137
112
|
return m_effective.to("mol/kg")
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
def _sort_components(Solution, type="all"):
|
|
141
|
-
"""
|
|
142
|
-
Sort the components of a solution in descending order (by mol).
|
|
143
|
-
|
|
144
|
-
Parameters:
|
|
145
|
-
----------
|
|
146
|
-
Solution : Solution object
|
|
147
|
-
type : The type of component to be sorted. Defaults to 'all' for all
|
|
148
|
-
solutes. Other valid arguments are 'cations' and 'anions' which
|
|
149
|
-
return sorted lists of cations and anions, respectively.
|
|
150
|
-
|
|
151
|
-
Returns:
|
|
152
|
-
-------
|
|
153
|
-
A list whose keys are the component names (formulas) and whose
|
|
154
|
-
values are the component objects themselves
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
"""
|
|
158
|
-
formula_list = []
|
|
159
|
-
|
|
160
|
-
# populate a list with component names
|
|
161
|
-
for item in Solution.components:
|
|
162
|
-
if type == "all":
|
|
163
|
-
formula_list.append(item)
|
|
164
|
-
elif type == "cations":
|
|
165
|
-
if Solution.get_solute(item).get_formal_charge() > 0:
|
|
166
|
-
formula_list.append(item)
|
|
167
|
-
elif type == "anions":
|
|
168
|
-
if Solution.get_solute(item).get_formal_charge() < 0:
|
|
169
|
-
formula_list.append(item)
|
|
170
|
-
|
|
171
|
-
# populate a dictionary with formula:concentration pairs
|
|
172
|
-
mol_list = {}
|
|
173
|
-
for item in formula_list:
|
|
174
|
-
mol_list.update({item: Solution.get_amount(item, "mol")})
|
|
175
|
-
|
|
176
|
-
return sorted(formula_list, key=mol_list.__getitem__, reverse=True)
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
def identify_salt(Solution):
|
|
180
|
-
"""
|
|
181
|
-
Analyze the components of a solution and identify the salt that most closely
|
|
182
|
-
approximates it.
|
|
183
|
-
(e.g., if a solution contains 0.5 mol/kg of Na+ and Cl-, plus traces of H+
|
|
184
|
-
and OH-, the matched salt is 0.5 mol/kg NaCl)
|
|
185
|
-
|
|
186
|
-
Create a Salt object for this salt.
|
|
187
|
-
|
|
188
|
-
Returns:
|
|
189
|
-
-------
|
|
190
|
-
A Salt object.
|
|
191
|
-
"""
|
|
192
|
-
# sort the components by moles
|
|
193
|
-
sort_list = _sort_components(Solution)
|
|
194
|
-
|
|
195
|
-
# default to returning water as the salt
|
|
196
|
-
cation = "H+"
|
|
197
|
-
anion = "OH-"
|
|
198
|
-
|
|
199
|
-
# return water if there are no solutes
|
|
200
|
-
if len(sort_list) < 3 and sort_list[0] == "H2O":
|
|
201
|
-
logger.info("Salt matching aborted because there are not enough solutes.")
|
|
202
|
-
return Salt(cation, anion)
|
|
203
|
-
|
|
204
|
-
# warn if something other than water is the predominant component
|
|
205
|
-
if sort_list[0] != "H2O":
|
|
206
|
-
logger.warning("H2O is not the most prominent component")
|
|
207
|
-
|
|
208
|
-
# take the dominant cation and anion and assemble a salt from them
|
|
209
|
-
for item in sort_list:
|
|
210
|
-
if chem.get_formal_charge(item) > 0 and cation == "H+":
|
|
211
|
-
cation = item
|
|
212
|
-
elif chem.get_formal_charge(item) < 0 and anion == "OH-":
|
|
213
|
-
anion = item
|
|
214
|
-
else:
|
|
215
|
-
pass
|
|
216
|
-
|
|
217
|
-
# assemble the salt
|
|
218
|
-
return Salt(cation, anion)
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
def generate_salt_list(Solution, unit="mol/kg"):
|
|
222
|
-
"""
|
|
223
|
-
Generate a list of salts that represents the ionic composition of a
|
|
224
|
-
solution.
|
|
225
|
-
|
|
226
|
-
Returns:
|
|
227
|
-
-------
|
|
228
|
-
dict
|
|
229
|
-
A dictionary of Salt objects, keyed to the formula of the salt.
|
|
230
|
-
|
|
231
|
-
"""
|
|
232
|
-
salt_list = {}
|
|
233
|
-
|
|
234
|
-
# sort the cations and anions by moles
|
|
235
|
-
cation_list = _sort_components(Solution, type="cations")
|
|
236
|
-
anion_list = _sort_components(Solution, type="anions")
|
|
237
|
-
|
|
238
|
-
# iterate through the lists of ions
|
|
239
|
-
# create salts by matching the equivalent concentrations of cations
|
|
240
|
-
# and anions along the way
|
|
241
|
-
len_cat = len(cation_list)
|
|
242
|
-
len_an = len(anion_list)
|
|
243
|
-
|
|
244
|
-
# start with the first cation and anion
|
|
245
|
-
index_cat = 0
|
|
246
|
-
index_an = 0
|
|
247
|
-
|
|
248
|
-
# calculate the equivalent concentrations of each ion
|
|
249
|
-
c1 = Solution.get_amount(cation_list[index_cat], unit) * chem.get_formal_charge(
|
|
250
|
-
cation_list[index_cat]
|
|
251
|
-
)
|
|
252
|
-
a1 = Solution.get_amount(anion_list[index_an], unit) * abs(
|
|
253
|
-
chem.get_formal_charge(anion_list[index_an])
|
|
254
|
-
)
|
|
255
|
-
|
|
256
|
-
while index_cat < len_cat and index_an < len_an:
|
|
257
|
-
# if the cation concentration is greater, there will be leftover cations
|
|
258
|
-
if c1 > a1:
|
|
259
|
-
# create the salt
|
|
260
|
-
x = Salt(cation_list[index_cat], anion_list[index_an])
|
|
261
|
-
# there will be leftover cation, so use the anion amount
|
|
262
|
-
amount = a1 / abs(x.z_anion)
|
|
263
|
-
# add it to the list
|
|
264
|
-
salt_list.update({x: amount})
|
|
265
|
-
# adjust the amounts of the respective ions
|
|
266
|
-
c1 = c1 - a1
|
|
267
|
-
# move to the next anion
|
|
268
|
-
index_an += 1
|
|
269
|
-
try:
|
|
270
|
-
a1 = Solution.get_amount(anion_list[index_an], unit) * abs(
|
|
271
|
-
chem.get_formal_charge(anion_list[index_an])
|
|
272
|
-
)
|
|
273
|
-
except IndexError:
|
|
274
|
-
continue
|
|
275
|
-
# if the anion concentration is greater, there will be leftover anions
|
|
276
|
-
if c1 < a1:
|
|
277
|
-
# create the salt
|
|
278
|
-
x = Salt(cation_list[index_cat], anion_list[index_an])
|
|
279
|
-
# there will be leftover anion, so use the cation amount
|
|
280
|
-
amount = c1 / x.z_cation
|
|
281
|
-
# add it to the list
|
|
282
|
-
salt_list.update({x: amount})
|
|
283
|
-
# calculate the leftover cation amount
|
|
284
|
-
a1 = a1 - c1
|
|
285
|
-
# move to the next cation
|
|
286
|
-
index_cat += 1
|
|
287
|
-
try:
|
|
288
|
-
c1 = Solution.get_amount(
|
|
289
|
-
cation_list[index_cat], unit
|
|
290
|
-
) * chem.get_formal_charge(cation_list[index_cat])
|
|
291
|
-
except IndexError:
|
|
292
|
-
continue
|
|
293
|
-
if c1 == a1:
|
|
294
|
-
# create the salt
|
|
295
|
-
x = Salt(cation_list[index_cat], anion_list[index_an])
|
|
296
|
-
# there will be nothing leftover, so it doesn't matter which ion you use
|
|
297
|
-
amount = c1 / x.z_cation
|
|
298
|
-
# add it to the list
|
|
299
|
-
salt_list.update({x: amount})
|
|
300
|
-
# move to the next cation and anion
|
|
301
|
-
index_an += 1
|
|
302
|
-
index_cat += 1
|
|
303
|
-
try:
|
|
304
|
-
c1 = Solution.get_amount(
|
|
305
|
-
cation_list[index_cat], unit
|
|
306
|
-
) * chem.get_formal_charge(cation_list[index_cat])
|
|
307
|
-
a1 = Solution.get_amount(anion_list[index_an], unit) * abs(
|
|
308
|
-
chem.get_formal_charge(anion_list[index_an])
|
|
309
|
-
)
|
|
310
|
-
except IndexError:
|
|
311
|
-
continue
|
|
312
|
-
|
|
313
|
-
return salt_list
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
def _trim_formal_charge(formula):
|
|
317
|
-
"""
|
|
318
|
-
remove the formal charge from a chemical formula
|
|
319
|
-
|
|
320
|
-
Examples:
|
|
321
|
-
--------
|
|
322
|
-
>>> _trim_formal_charge('Fe+++')
|
|
323
|
-
'Fe'
|
|
324
|
-
>>> _trim_formal_charge('SO4-2')
|
|
325
|
-
'SO4'
|
|
326
|
-
>>> _trim_formal_charge('Na+')
|
|
327
|
-
'Na'
|
|
328
|
-
|
|
329
|
-
"""
|
|
330
|
-
charge = chem.get_formal_charge(formula)
|
|
331
|
-
output = ""
|
|
332
|
-
if charge > 0:
|
|
333
|
-
output = formula.split("+")[0]
|
|
334
|
-
elif charge < 0:
|
|
335
|
-
output = formula.split("-")[0]
|
|
336
|
-
|
|
337
|
-
return output
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
# TODO - turn doctest back on when the nosigint error is gone
|
|
341
|
-
# if __name__ == "__main__":
|
|
342
|
-
# import doctest
|
|
343
|
-
# doctest.testmod()
|