approximate 0.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.
- approximate/Data/fixed_ox_states.csv +42 -0
- approximate/Data/variable_ox_states_srps.csv +171 -0
- approximate/__init__.py +31 -0
- approximate/capacity.py +169 -0
- approximate/core.py +976 -0
- approximate/feature_engineering/__init__.py +21 -0
- approximate/feature_engineering/base.py +68 -0
- approximate/feature_engineering/electronegativity_features.py +175 -0
- approximate/feature_engineering/element_properties.py +150 -0
- approximate/feature_engineering/element_property_features.py +48 -0
- approximate/feature_engineering/feature_extractor.py +119 -0
- approximate/feature_engineering/ionic_radius_features.py +49 -0
- approximate/feature_engineering/layered_ox_features.py +98 -0
- approximate/feature_engineering/magnetic_moment_features.py +122 -0
- approximate/feature_engineering/registry.py +15 -0
- approximate/feature_engineering/stats_expander.py +38 -0
- approximate/feature_engineering/utils.py +112 -0
- approximate/feature_engineering/valence_features.py +100 -0
- approximate/plotting.py +113 -0
- approximate/sweep.py +111 -0
- approximate-0.1.0.dist-info/METADATA +202 -0
- approximate-0.1.0.dist-info/RECORD +25 -0
- approximate-0.1.0.dist-info/WHEEL +5 -0
- approximate-0.1.0.dist-info/licenses/LICENSE +21 -0
- approximate-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
Element,Ox State
|
|
2
|
+
H,1
|
|
3
|
+
Li,1
|
|
4
|
+
Na,1
|
|
5
|
+
K,1
|
|
6
|
+
Rb,1
|
|
7
|
+
Cs,1
|
|
8
|
+
Fr,1
|
|
9
|
+
Be,2
|
|
10
|
+
Mg,2
|
|
11
|
+
Ca,2
|
|
12
|
+
Sr,2
|
|
13
|
+
Ba,2
|
|
14
|
+
Ra,2
|
|
15
|
+
Y,3
|
|
16
|
+
La,3
|
|
17
|
+
Gd,3
|
|
18
|
+
Lu,3
|
|
19
|
+
Ac,3
|
|
20
|
+
Th,4
|
|
21
|
+
Lr,3
|
|
22
|
+
Zr,4
|
|
23
|
+
Hf,4
|
|
24
|
+
Zn,2
|
|
25
|
+
Cd,2
|
|
26
|
+
Hg,2
|
|
27
|
+
B,3
|
|
28
|
+
Al,3
|
|
29
|
+
Ga,3
|
|
30
|
+
O,-2
|
|
31
|
+
Po,4
|
|
32
|
+
F,-1
|
|
33
|
+
Cl,-1
|
|
34
|
+
Br,-1
|
|
35
|
+
I,-1
|
|
36
|
+
At,-1
|
|
37
|
+
T,2
|
|
38
|
+
D,3
|
|
39
|
+
Er,3
|
|
40
|
+
In,3
|
|
41
|
+
Ho,3
|
|
42
|
+
P,5
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
Element,Ox,Red,SRP (V)
|
|
2
|
+
Sc,3,2,-2.3
|
|
3
|
+
Sc,3,0,-2.09
|
|
4
|
+
Sc,2,0,-2
|
|
5
|
+
Ti,2,0,-1.63
|
|
6
|
+
Ti,3,0,-1.37
|
|
7
|
+
Ti,3,2,-0.37
|
|
8
|
+
Ti,4,3,0.10
|
|
9
|
+
Ce,3,0,-2.336
|
|
10
|
+
Ce,4,3,1.71
|
|
11
|
+
Pr,3,0,-2.353
|
|
12
|
+
Nd,3,2,-2.7
|
|
13
|
+
Nd,3,0,-2.323
|
|
14
|
+
Nd,2,0,-2.1
|
|
15
|
+
Pm,3,2,-2.6
|
|
16
|
+
Pm,3,0,-2.3
|
|
17
|
+
Pm,2,0,-2.2
|
|
18
|
+
Eu,2,0,-2.812
|
|
19
|
+
Eu,3,0,-1.991
|
|
20
|
+
Eu,3,2,-0.35
|
|
21
|
+
Tb,3,0,-2.28
|
|
22
|
+
Dy,3,2,-2.6
|
|
23
|
+
Dy,3,0,-2.295
|
|
24
|
+
Dy,2,0,-2.2
|
|
25
|
+
Tm,2,0,-2.4
|
|
26
|
+
Tm,3,0,-2.319
|
|
27
|
+
Tm,3,2,-2.2
|
|
28
|
+
Yb,2,0,-2.76
|
|
29
|
+
Yb,3,0,-2.19
|
|
30
|
+
Yb,3,2,-1.05
|
|
31
|
+
Pa,4,3,-1.9
|
|
32
|
+
Pa,4,0,-1.49
|
|
33
|
+
Pa,3,0,-1.34
|
|
34
|
+
U,3,0,-1.642
|
|
35
|
+
U,4,3,-0.577
|
|
36
|
+
Np,3,0,-1.768
|
|
37
|
+
Np,4,3,0.157
|
|
38
|
+
Pu,3,2,-2.8
|
|
39
|
+
Pu,3,0,-1.978
|
|
40
|
+
Pu,2,0,-1.6
|
|
41
|
+
Pu,4,3,1.006
|
|
42
|
+
Am,3,2,-2.3
|
|
43
|
+
Am,3,0,-2.048
|
|
44
|
+
Am,2,0,-1.9
|
|
45
|
+
Cm,3,0,-2.04
|
|
46
|
+
Bk,3,2,-2.8
|
|
47
|
+
Bk,3,0,-1.98
|
|
48
|
+
Bk,2,0,-1.6
|
|
49
|
+
Bk,4,3,1.67
|
|
50
|
+
Cf,2,0,-2.12
|
|
51
|
+
Cf,3,0,-1.94
|
|
52
|
+
Cf,3,2,-1.6
|
|
53
|
+
Es,2,0,-2.23
|
|
54
|
+
Es,3,0,-1.91
|
|
55
|
+
Es,3,2,-1.3
|
|
56
|
+
Fm,2,0,-2.3
|
|
57
|
+
Fm,3,0,-1.89
|
|
58
|
+
Fm,3,2,-1.1
|
|
59
|
+
Md,2,0,-2.4
|
|
60
|
+
Md,3,0,-1.65
|
|
61
|
+
Md,3,2,-0.1
|
|
62
|
+
No,2,0,-2.5
|
|
63
|
+
No,3,0,-1.2
|
|
64
|
+
No,3,2,1.4
|
|
65
|
+
V,2,0,-1.13
|
|
66
|
+
V,3,2,-0.26
|
|
67
|
+
V,4,3,0.337
|
|
68
|
+
V,5,4,1.001
|
|
69
|
+
Nb,3,0,-0.733
|
|
70
|
+
Nb,5,0,-0.69
|
|
71
|
+
Nb,5,3,-0.646
|
|
72
|
+
Ta,5,0,-0.752
|
|
73
|
+
Ta,3,0,-0.6
|
|
74
|
+
Cr,2,0,-0.89
|
|
75
|
+
Cr,3,0,-0.74
|
|
76
|
+
Cr,3,2,-0.42
|
|
77
|
+
Cr,6,3,1.36
|
|
78
|
+
Mo,4,0,-0.152
|
|
79
|
+
Mo,6,0,0.082
|
|
80
|
+
Mo,6,4,0.2
|
|
81
|
+
W,4,0,-1.54
|
|
82
|
+
W,6,0,-0.091
|
|
83
|
+
W,6,4,0.01
|
|
84
|
+
Mn,2,0,-1.182
|
|
85
|
+
Mn,4,3,0.98
|
|
86
|
+
Mn,3,2,1.56
|
|
87
|
+
Mn,7,4,2.09
|
|
88
|
+
Tc,4,0,0.272
|
|
89
|
+
Tc,7,0,0.427
|
|
90
|
+
Tc,7,4,0.738
|
|
91
|
+
Re,4,0,0.276
|
|
92
|
+
Re,7,0,0.376
|
|
93
|
+
Re,7,4,0.51
|
|
94
|
+
Fe,2,0,-0.44
|
|
95
|
+
Fe,3,2,0.771
|
|
96
|
+
Ru,4,3,0.58
|
|
97
|
+
Ru,3,0,0.6
|
|
98
|
+
Ru,8,4,1.4
|
|
99
|
+
Os,4,0,0.65
|
|
100
|
+
Os,8,0,0.838
|
|
101
|
+
Os,8,4,0.964
|
|
102
|
+
Co,2,0,-0.282
|
|
103
|
+
Co,3,0,0.003
|
|
104
|
+
Co,4,3,1.42
|
|
105
|
+
Co,3,2,1.92
|
|
106
|
+
Ir,4,3,0.4
|
|
107
|
+
Ir,4,0,0.73
|
|
108
|
+
Ir,3,0,0.84
|
|
109
|
+
Ni,2,0,-0.236
|
|
110
|
+
Ni,4,0,0.055
|
|
111
|
+
Ni,3,2,1.753
|
|
112
|
+
Ni,4,3,2.05
|
|
113
|
+
Pd,2,0,0.915
|
|
114
|
+
Pd,4,2,1.27
|
|
115
|
+
Pt,4,0,0.92
|
|
116
|
+
Pt,4,2,1.01
|
|
117
|
+
Pt,2,0,1.18
|
|
118
|
+
Cu,2,1,0.161
|
|
119
|
+
Cu,2,0,0.339
|
|
120
|
+
Cu,1,0,0.518
|
|
121
|
+
Ag,1,0,0.7993
|
|
122
|
+
Ag,2,1,1.98
|
|
123
|
+
Au,3,1,1.41
|
|
124
|
+
Au,3,0,1.52
|
|
125
|
+
Au,1,0,1.69
|
|
126
|
+
Sm,2,0,-2.68
|
|
127
|
+
Sm,3,0,-2.304
|
|
128
|
+
Sm,3,2,-1.55
|
|
129
|
+
Tl,1,0,-0.336
|
|
130
|
+
Tl,3,0,0.741
|
|
131
|
+
Tl,3,1,1.28
|
|
132
|
+
C,4,2,-0.07
|
|
133
|
+
C,4,0,0.2073
|
|
134
|
+
C,2,0,0.5184
|
|
135
|
+
Si,4,0,-0.931
|
|
136
|
+
Si,0,-4,-0.147
|
|
137
|
+
Ge,4,2,-2.07
|
|
138
|
+
Ge,4,0,-0.104
|
|
139
|
+
Ge,2,0,0.11
|
|
140
|
+
Sn,2,0,-0.141
|
|
141
|
+
Sn,4,0,-0.117
|
|
142
|
+
Sn,4,2,-0.094
|
|
143
|
+
Pb,2,0,-0.126
|
|
144
|
+
Pb,4,0,1.23
|
|
145
|
+
Pb,4,2,1.458
|
|
146
|
+
Bi,3,0,0.308
|
|
147
|
+
Bi,5,3,2
|
|
148
|
+
S,0,-2,0.144
|
|
149
|
+
S,6,4,0.158
|
|
150
|
+
S,6,0,0.333
|
|
151
|
+
S,4,0,0.458
|
|
152
|
+
Te,0,-2,-0.46
|
|
153
|
+
Te,4,0,0.529
|
|
154
|
+
Te,6,4,0.84
|
|
155
|
+
P,3,0,-0.402
|
|
156
|
+
P,5,0,-0.377
|
|
157
|
+
P,5,3,-0.3
|
|
158
|
+
P,0,-3,-0.088
|
|
159
|
+
Sb,3,0,0.231
|
|
160
|
+
Sb,5,3,0.54
|
|
161
|
+
As,3,0,0.234
|
|
162
|
+
As,5,3,0.566
|
|
163
|
+
N,0,-3,-3.334
|
|
164
|
+
N,3,-3,-1.887
|
|
165
|
+
N,3,0,1.447
|
|
166
|
+
Rh,3,0,0.76
|
|
167
|
+
Rh,4,0,0.83
|
|
168
|
+
Rh,4,3,1.3
|
|
169
|
+
Se,0,-2,-0.114
|
|
170
|
+
Se,4,0,0.739
|
|
171
|
+
Se,6,4,1.094
|
approximate/__init__.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""ApprOxiMate: composition-only mixed oxidation state estimation.
|
|
2
|
+
|
|
3
|
+
from approximate import charge_balance
|
|
4
|
+
r = charge_balance("Fe3O4")
|
|
5
|
+
"""
|
|
6
|
+
from .capacity import CapacityResult, capacity, molar_mass
|
|
7
|
+
from .sweep import SweepResult, alkali_sweep
|
|
8
|
+
from .core import (
|
|
9
|
+
BalanceResult,
|
|
10
|
+
ElementState,
|
|
11
|
+
charge_balance,
|
|
12
|
+
final_charge,
|
|
13
|
+
oxidation_states,
|
|
14
|
+
parse_formula,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
__version__ = "0.1.0"
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
"charge_balance",
|
|
21
|
+
"oxidation_states",
|
|
22
|
+
"final_charge",
|
|
23
|
+
"parse_formula",
|
|
24
|
+
"capacity",
|
|
25
|
+
"molar_mass",
|
|
26
|
+
"CapacityResult",
|
|
27
|
+
"alkali_sweep",
|
|
28
|
+
"SweepResult",
|
|
29
|
+
"BalanceResult",
|
|
30
|
+
"ElementState",
|
|
31
|
+
]
|
approximate/capacity.py
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"""Theoretical and charge-balance-adjusted capacity.
|
|
2
|
+
|
|
3
|
+
Pure Python (only depends on approximate.core), so it also runs in the
|
|
4
|
+
browser via Pyodide for the ApprOxiMate website.
|
|
5
|
+
|
|
6
|
+
>>> from approximate import capacity
|
|
7
|
+
>>> c = capacity("NaFe0.5Co0.5O2")
|
|
8
|
+
>>> round(c.q_theoretical, 1), round(c.q_adjusted, 1)
|
|
9
|
+
(238.5, 119.2)
|
|
10
|
+
|
|
11
|
+
Assumptions:
|
|
12
|
+
* one electron transferred per alkali ion removed;
|
|
13
|
+
* molar mass is that of the starting (fully alkali-loaded) formula.
|
|
14
|
+
"""
|
|
15
|
+
from dataclasses import asdict, dataclass
|
|
16
|
+
|
|
17
|
+
from .core import charge_balance, parse_formula
|
|
18
|
+
|
|
19
|
+
#: Faraday constant in C/mol (exact, 2019 SI redefinition).
|
|
20
|
+
FARADAY = 96485.33212
|
|
21
|
+
|
|
22
|
+
ALKALI_METALS = ("Li", "Na", "K", "Rb", "Cs")
|
|
23
|
+
|
|
24
|
+
# Standard atomic weights (g/mol): IUPAC/CIAAW 2013 values with the 2015
|
|
25
|
+
# ytterbium revision, as distributed by the mendeleev package.
|
|
26
|
+
# J. Meija et al., "Atomic weights of the elements 2013 (IUPAC Technical
|
|
27
|
+
# Report)", Pure Appl. Chem. 88, 265-291 (2016). doi:10.1515/pac-2015-0305
|
|
28
|
+
# CIAAW, "Standard atomic weight of ytterbium revised", Chem. Int. 37(5-6),
|
|
29
|
+
# 26 (2015). doi:10.1515/ci-2015-0512
|
|
30
|
+
# Values are kept identical to mendeleev (checked in tests/test_capacity.py) so
|
|
31
|
+
# that capacities agree with the feature engineering, which uses mendeleev.
|
|
32
|
+
ATOMIC_MASS = {
|
|
33
|
+
"H": 1.008, "He": 4.002602, "Li": 6.94, "Be": 9.0121831, "B": 10.81, "C": 12.011,
|
|
34
|
+
"N": 14.007, "O": 15.999, "F": 18.998403163, "Ne": 20.1797, "Na": 22.98976928,
|
|
35
|
+
"Mg": 24.305, "Al": 26.9815385, "Si": 28.085, "P": 30.973761998, "S": 32.06, "Cl": 35.45,
|
|
36
|
+
"Ar": 39.948, "K": 39.0983, "Ca": 40.078, "Sc": 44.955908, "Ti": 47.867, "V": 50.9415,
|
|
37
|
+
"Cr": 51.9961, "Mn": 54.938044, "Fe": 55.845, "Co": 58.933194, "Ni": 58.6934,
|
|
38
|
+
"Cu": 63.546, "Zn": 65.38, "Ga": 69.723, "Ge": 72.63, "As": 74.921595, "Se": 78.971,
|
|
39
|
+
"Br": 79.904, "Kr": 83.798, "Rb": 85.4678, "Sr": 87.62, "Y": 88.90584, "Zr": 91.224,
|
|
40
|
+
"Nb": 92.90637, "Mo": 95.95, "Tc": 97.90721, "Ru": 101.07, "Rh": 102.9055, "Pd": 106.42,
|
|
41
|
+
"Ag": 107.8682, "Cd": 112.414, "In": 114.818, "Sn": 118.71, "Sb": 121.76, "Te": 127.6,
|
|
42
|
+
"I": 126.90447, "Xe": 131.293, "Cs": 132.90545196, "Ba": 137.327, "La": 138.90547,
|
|
43
|
+
"Ce": 140.116, "Pr": 140.90766, "Nd": 144.242, "Pm": 144.91276, "Sm": 150.36,
|
|
44
|
+
"Eu": 151.964, "Gd": 157.25, "Tb": 158.92535, "Dy": 162.5, "Ho": 164.93033,
|
|
45
|
+
"Er": 167.259, "Tm": 168.93422, "Yb": 173.045, "Lu": 174.9668, "Hf": 178.49,
|
|
46
|
+
"Ta": 180.94788, "W": 183.84, "Re": 186.207, "Os": 190.23, "Ir": 192.217, "Pt": 195.084,
|
|
47
|
+
"Au": 196.966569, "Hg": 200.592, "Tl": 204.38, "Pb": 207.2, "Bi": 208.9804, "Po": 209.0,
|
|
48
|
+
"At": 210.0, "Rn": 222.0, "Fr": 223.0, "Ra": 226.0, "Ac": 227.0, "Th": 232.0377,
|
|
49
|
+
"Pa": 231.03588, "U": 238.02891, "Np": 237.0, "Pu": 244.0, "Am": 243.0, "Cm": 247.0,
|
|
50
|
+
"Bk": 247.0, "Cf": 251.0, "Es": 252.0, "Fm": 257.0, "Md": 258.0, "No": 259.0,
|
|
51
|
+
"Lr": 262.0, "Rf": 267.0, "Db": 268.0, "Sg": 271.0, "Bh": 274.0, "Hs": 269.0,
|
|
52
|
+
"Mt": 276.0, "Ds": 281.0, "Rg": 281.0, "Cn": 285.0, "Nh": 286.0, "Fl": 289.0,
|
|
53
|
+
"Mc": 288.0, "Lv": 293.0, "Ts": 294.0, "Og": 294.0,
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@dataclass(frozen=True)
|
|
58
|
+
class CapacityResult:
|
|
59
|
+
"""Result of capacity().
|
|
60
|
+
|
|
61
|
+
Attributes:
|
|
62
|
+
alkali: the alkali metal being removed.
|
|
63
|
+
full: alkali per formula unit in the starting formula.
|
|
64
|
+
removable: alkali that can be removed before the formula can no
|
|
65
|
+
longer be charge balanced.
|
|
66
|
+
remaining: alkali left at that limit (full - removable).
|
|
67
|
+
molar_mass: molar mass of the starting formula, g/mol.
|
|
68
|
+
q_theoretical: capacity if all alkali is removed, mAh/g.
|
|
69
|
+
q_adjusted: capacity limited by charge balance, mAh/g.
|
|
70
|
+
"""
|
|
71
|
+
alkali: str
|
|
72
|
+
full: float
|
|
73
|
+
removable: float
|
|
74
|
+
remaining: float
|
|
75
|
+
molar_mass: float
|
|
76
|
+
q_theoretical: float
|
|
77
|
+
q_adjusted: float
|
|
78
|
+
|
|
79
|
+
@property
|
|
80
|
+
def per_alkali(self) -> float:
|
|
81
|
+
"""mAh/g per formula unit of alkali removed."""
|
|
82
|
+
return self.q_theoretical / self.full
|
|
83
|
+
|
|
84
|
+
def to_dict(self) -> dict:
|
|
85
|
+
return asdict(self)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _as_parsed(formula):
|
|
89
|
+
if isinstance(formula, dict):
|
|
90
|
+
return dict(formula)
|
|
91
|
+
parsed = parse_formula(str(formula).strip())
|
|
92
|
+
if not parsed:
|
|
93
|
+
raise ValueError(f"Could not parse formula {formula!r} (unknown element?)")
|
|
94
|
+
return parsed
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def molar_mass(formula) -> float:
|
|
98
|
+
"""Molar mass in g/mol of a formula string or {element: amount} dict."""
|
|
99
|
+
parsed = _as_parsed(formula)
|
|
100
|
+
missing = [el for el in parsed if el not in ATOMIC_MASS]
|
|
101
|
+
if missing:
|
|
102
|
+
raise ValueError(f"No atomic mass for {', '.join(missing)}")
|
|
103
|
+
return sum(ATOMIC_MASS[el] * q for el, q in parsed.items())
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _pick_alkali(parsed):
|
|
107
|
+
present = [el for el in ALKALI_METALS if parsed.get(el, 0) > 0]
|
|
108
|
+
if len(present) != 1:
|
|
109
|
+
found = ", ".join(present) if present else "none"
|
|
110
|
+
raise ValueError(f"Specify alkali= (alkali metals found: {found})")
|
|
111
|
+
return present[0]
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def capacity(formula, alkali=None, interval=0.01, tol=1e-6, faraday=FARADAY):
|
|
115
|
+
"""Theoretical vs charge-balance-adjusted capacity (mAh/g).
|
|
116
|
+
|
|
117
|
+
Steps the alkali amount down from its full value in `interval` steps and
|
|
118
|
+
stops at the first composition that is no longer charge neutral
|
|
119
|
+
(|final charge| > tol). The last neutral composition sets how much
|
|
120
|
+
alkali is removable.
|
|
121
|
+
|
|
122
|
+
Q_theoretical = full * F / (3.6 * M)
|
|
123
|
+
Q_adjusted = removable * F / (3.6 * M)
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
formula: formula string or {element: amount} dict.
|
|
127
|
+
alkali: alkali metal to remove. If None, the single alkali metal
|
|
128
|
+
in the formula is used.
|
|
129
|
+
interval: step size in alkali per formula unit (paper: 0.01).
|
|
130
|
+
tol: charge tolerance for "still balanced".
|
|
131
|
+
faraday: Faraday constant, C/mol.
|
|
132
|
+
|
|
133
|
+
Raises:
|
|
134
|
+
ValueError: unparsable formula, unknown element, or alkali not present.
|
|
135
|
+
"""
|
|
136
|
+
parsed = _as_parsed(formula)
|
|
137
|
+
if alkali is None:
|
|
138
|
+
alkali = _pick_alkali(parsed)
|
|
139
|
+
full = parsed.get(alkali, 0)
|
|
140
|
+
if not full or full <= 0:
|
|
141
|
+
raise ValueError(f"{alkali} not found in formula")
|
|
142
|
+
mass = molar_mass(parsed)
|
|
143
|
+
|
|
144
|
+
n_steps = max(1, int(round(full / interval)))
|
|
145
|
+
step = -full / n_steps
|
|
146
|
+
last_ok = full
|
|
147
|
+
for k in range(n_steps + 1):
|
|
148
|
+
# same values as np.linspace(full, 0, n_steps + 1), endpoint exactly 0
|
|
149
|
+
remaining = 0.0 if k == n_steps else full + k * step
|
|
150
|
+
try:
|
|
151
|
+
r = charge_balance({**parsed, alkali: remaining})
|
|
152
|
+
charge = r.final_charge if r is not None else float("nan")
|
|
153
|
+
except Exception:
|
|
154
|
+
charge = float("nan")
|
|
155
|
+
if charge != charge or abs(charge) > tol: # NaN or unbalanced
|
|
156
|
+
break
|
|
157
|
+
last_ok = remaining
|
|
158
|
+
|
|
159
|
+
per_unit = faraday / (3.6 * mass)
|
|
160
|
+
removable = full - last_ok
|
|
161
|
+
return CapacityResult(
|
|
162
|
+
alkali=alkali,
|
|
163
|
+
full=float(full),
|
|
164
|
+
removable=removable,
|
|
165
|
+
remaining=last_ok,
|
|
166
|
+
molar_mass=mass,
|
|
167
|
+
q_theoretical=full * per_unit,
|
|
168
|
+
q_adjusted=removable * per_unit,
|
|
169
|
+
)
|