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.
@@ -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
@@ -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
+ ]
@@ -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
+ )