Skip to content

Commit 6d5b688

Browse files
add minimal test for oxy evo
1 parent 1dd1457 commit 6d5b688

3 files changed

Lines changed: 227 additions & 9 deletions

File tree

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,4 +39,4 @@ repos:
3939
- id: codespell
4040
stages: [pre-commit, commit-msg]
4141
exclude_types: [json, bib, svg]
42-
args: [--ignore-words-list, "mater,fwe,te"]
42+
args: [--ignore-words-list, "mater,fwe,te,alo"]

mp_api/client/core/_oxygen_evolution.py

Lines changed: 158 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from collections.abc import Sequence
66
from io import StringIO
77
from pathlib import Path
8+
from warnings import warn
89

910
import numpy as np
1011
import pandas as pd
@@ -29,7 +30,7 @@ class OxygenEvolution:
2930

3031
def __init__(
3132
self,
32-
cache_file: Path | None = None, # DEFAULT_CACHE_FILE,
33+
cache_file: Path | None = DEFAULT_CACHE_FILE,
3334
):
3435
self._spline_pars = None
3536
self.cache_file = cache_file
@@ -57,9 +58,6 @@ def get_chempot_temp_data(
5758
meas_p : float
5859
The O2 partial pressure at which the experimental data was collected.
5960
Per NIST, this is 0.1 MPa
60-
mu_o2_ref : float | None = None
61-
If a float, the O2 chemical potential at 0K.
62-
If None, this is determined from MP's data.
6361
6462
Returns:
6563
-----------
@@ -96,11 +94,10 @@ def get_chempot_temp_data(
9694

9795
return temp_K, mu_mu0
9896

99-
@property
10097
def mu_to_temp_spline_params(self):
10198
if self._spline_pars is None:
102-
temp, mu_m0 = self.get_chempot_temp_data()
103-
99+
temp = np.array(NIST_JANAF_O2_MU_T["temperature"])
100+
mu_m0 = np.array(NIST_JANAF_O2_MU_T["mu-mu_0K"])
104101
# spline needs the x vars to be in increasing order
105102
mu_sort_idx = np.argsort(mu_m0)
106103
self._spline_pars = make_splrep(mu_m0[mu_sort_idx], temp[mu_sort_idx])
@@ -117,7 +114,17 @@ def mu_to_temp_spline(
117114
mu : np.ndarray
118115
The O2 chemical potentials to sample at.
119116
"""
120-
return splev(np.array(mu), self.mu_to_temp_spline_params)
117+
mu_arr = np.array(mu)
118+
if np.any(
119+
(mu_arr < min(NIST_JANAF_O2_MU_T["mu-mu_0K"]))
120+
| (mu_arr > max(NIST_JANAF_O2_MU_T["mu-mu_0K"]))
121+
):
122+
warn(
123+
"Some of the input chemical potential values are "
124+
"outside the fitting range - extrapolation will be inaccurate.",
125+
stacklevel=2,
126+
)
127+
return splev(mu_arr, self.mu_to_temp_spline_params())
121128

122129
@staticmethod
123130
def stairstep(x, y, z):
@@ -196,3 +203,146 @@ def get_oxygen_evolution_from_phase_diagram(
196203
data["mu"] - mu_0K
197204
)
198205
return oxy_evo_data
206+
207+
208+
# This data is generated by running:
209+
# `OxygenEvolution().get_chempot_temp_data()`
210+
# The contents of `DEFAULT_CACHE_FILE` will contain this data.
211+
# Because the last update of the JANAF tables was in 1998
212+
# (writing in 2025), we see no need to dynamically retrieve this data
213+
NIST_JANAF_O2_MU_T = {
214+
"mu-mu_0K": [
215+
0.0,
216+
-0.15766752295964667,
217+
-0.35716109254410505,
218+
-0.4633062592086276,
219+
-0.5684747265209704,
220+
-0.5725679617587883,
221+
-0.6845046823072302,
222+
-0.7988365855732343,
223+
-0.9153600136580551,
224+
-1.0338982557640533,
225+
-1.2764843857253183,
226+
-1.5256155519745132,
227+
-1.7804988878829335,
228+
-2.0404658980577506,
229+
-2.304982822611666,
230+
-2.5736050343764276,
231+
-2.8459749660488933,
232+
-3.1217816895393757,
233+
-3.4007764623761245,
234+
-3.6827416348963578,
235+
-3.9674927231001944,
236+
-4.254892918628172,
237+
-4.544805413120827,
238+
-4.837112053904078,
239+
-5.13170297971957,
240+
-5.428488021421295,
241+
-5.7273687184475115,
242+
-6.028299468011739,
243+
-6.3311932102488635,
244+
-6.635957703158943,
245+
-6.9425981288768055,
246+
-7.2509807883238855,
247+
-7.561092207949628,
248+
-7.872879529978791,
249+
-8.186271240950743,
250+
-8.501236248056516,
251+
-8.817722729947826,
252+
-9.135721358781987,
253+
-9.4551295282894,
254+
-9.775953457031855,
255+
-10.098137177953216,
256+
-10.421667217502918,
257+
-10.746499009321454,
258+
-11.072592132757157,
259+
-11.399910312866226,
260+
-11.728421420412731,
261+
-12.058141001801161,
262+
-12.389003762132672,
263+
-12.720990009294923,
264+
-13.05408419688343,
265+
-13.388322599841986,
266+
-13.723603648327966,
267+
-14.059921123779567,
268+
-14.397323738264184,
269+
-14.735764852568353,
270+
-15.075250685253877,
271+
-15.415683812185982,
272+
-15.757182386038748,
273+
-16.099653128385278,
274+
-16.44311262205701,
275+
-16.787639635503336,
276+
-17.13308492324121,
277+
-17.47953139942788,
278+
-17.826947971254377,
279+
-18.175365731529666,
280+
],
281+
"temperature": [
282+
0.0,
283+
100.0,
284+
200.0,
285+
250.0,
286+
298.15,
287+
300.0,
288+
350.0,
289+
400.0,
290+
450.0,
291+
500.0,
292+
600.0,
293+
700.0,
294+
800.0,
295+
900.0,
296+
1000.0,
297+
1100.0,
298+
1200.0,
299+
1300.0,
300+
1400.0,
301+
1500.0,
302+
1600.0,
303+
1700.0,
304+
1800.0,
305+
1900.0,
306+
2000.0,
307+
2100.0,
308+
2200.0,
309+
2300.0,
310+
2400.0,
311+
2500.0,
312+
2600.0,
313+
2700.0,
314+
2800.0,
315+
2900.0,
316+
3000.0,
317+
3100.0,
318+
3200.0,
319+
3300.0,
320+
3400.0,
321+
3500.0,
322+
3600.0,
323+
3700.0,
324+
3800.0,
325+
3900.0,
326+
4000.0,
327+
4100.0,
328+
4200.0,
329+
4300.0,
330+
4400.0,
331+
4500.0,
332+
4600.0,
333+
4700.0,
334+
4800.0,
335+
4900.0,
336+
5000.0,
337+
5100.0,
338+
5200.0,
339+
5300.0,
340+
5400.0,
341+
5500.0,
342+
5600.0,
343+
5700.0,
344+
5800.0,
345+
5900.0,
346+
6000.0,
347+
],
348+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
"""Test the oxygen evolution analysis features in the client."""
2+
3+
import pytest
4+
import numpy as np
5+
from scipy.interpolate import splev
6+
7+
from pymatgen.analysis.phase_diagram import PhaseDiagram
8+
from pymatgen.core import Composition
9+
from pymatgen.entries.computed_entries import ComputedEntry
10+
11+
from mp_api.client.core._oxygen_evolution import NIST_JANAF_O2_MU_T, OxygenEvolution
12+
13+
14+
def test_interp():
15+
oxyevo = OxygenEvolution()
16+
17+
# Spline interpolation is exact for all fit points:
18+
assert np.allclose(
19+
oxyevo.mu_to_temp_spline(np.array(NIST_JANAF_O2_MU_T["mu-mu_0K"])),
20+
NIST_JANAF_O2_MU_T["temperature"],
21+
)
22+
23+
# mu(T) is a generally decreasing function, at least for fit range
24+
deriv = splev(
25+
np.linspace(
26+
min(NIST_JANAF_O2_MU_T["mu-mu_0K"]),
27+
max(NIST_JANAF_O2_MU_T["mu-mu_0K"]),
28+
1000,
29+
),
30+
oxyevo.mu_to_temp_spline_params(),
31+
der=1,
32+
)
33+
assert len(deriv[deriv > 0]) == 0
34+
35+
# ensure user is warned when data is outside fit range
36+
for badval in (1.0, -50.0):
37+
with pytest.warns(UserWarning, match="outside the fitting range"):
38+
oxyevo.mu_to_temp_spline(badval)
39+
40+
41+
def test_oxy_evo():
42+
"""Very fake Al-O chemical system for testing purposes."""
43+
entries = [
44+
ComputedEntry({"Al": 1}, -2.0),
45+
ComputedEntry({"O": 1}, -5.0),
46+
ComputedEntry({"Al": 1, "O": 1}, -25.0),
47+
ComputedEntry({"Al": 2, "O": 3}, -55.0),
48+
ComputedEntry({"Al": 3, "O": 2}, -5.0),
49+
]
50+
phase_diag = PhaseDiagram(entries)
51+
52+
ref_comp = Composition({"Al": 4, "O": 1})
53+
evo = OxygenEvolution().get_oxygen_evolution_from_phase_diagram(
54+
phase_diag, [ref_comp]
55+
)
56+
assert ref_comp.formula in evo
57+
assert np.allclose(evo[ref_comp.formula]["evolution"], [0.5, 0.5, -1.5])
58+
assert np.allclose(evo[ref_comp.formula]["temperature"], [5949.70500942, 0.0, 0.0])
59+
assert all(
60+
str(evo[ref_comp.formula]["reaction"][idx]) == rxn
61+
for idx, rxn in enumerate(
62+
[
63+
"2 Al4O -> 8 Al + O2",
64+
"2 Al4O -> 8 Al + O2",
65+
"2 Al4O + 3 O2 -> 8 AlO",
66+
]
67+
)
68+
)

0 commit comments

Comments
 (0)