Download analysis/code/test_leveling.py from ekwan16/MagNET: direct link, hf CLI and curl.
- Browser
- Download file 10.2 kB
-
https://huggingface.co/ekwan16/MagNET/resolve/fe222d1894afb2d954fe8d2e67e70731a006c8d2/analysis/code/test_leveling.py
- Command line
-
hf download hf://ekwan16/MagNET@fe222d1894afb2d954fe8d2e67e70731a006c8d2/analysis/code/test_leveling.py
-
curl -L -o test_leveling.py https://huggingface.co/ekwan16/MagNET/resolve/fe222d1894afb2d954fe8d2e67e70731a006c8d2/analysis/code/test_leveling.py
10.2 kB
| """ | |
| Tests for the leveling-effect analysis core (leveling.py). | |
| Most tests build tiny synthetic inputs in the exact on-disk formats (so the fixtures | |
| double as a spec of what the loaders expect) and need no large data. Two opt-in tests | |
| run against the real delta22.hdf5 and the Kaupp NS372 spreadsheet if they are present. | |
| Run: pytest test_leveling.py -q | |
| Requires: pytest, numpy, h5py, pandas, openpyxl, scikit-learn. | |
| """ | |
| import os | |
| import numpy as np | |
| import h5py | |
| import openpyxl | |
| import pytest | |
| from fixed_point import MISSING_MARKER as _MISSING_MARKER | |
| from leveling import ( | |
| _decode_fixed_point, | |
| analyze_nucleus, | |
| read_nucleus, | |
| load_delta22, | |
| load_ns372, | |
| load_ns372_1h_convergence, | |
| convergence_statistics, | |
| DELTA22_REF, | |
| ) | |
| SCALE = 1e4 | |
| def _enc(arr): | |
| """Encode floats the way the real file does: int32 = round(value * 1e4), NaN -> marker.""" | |
| a = np.asarray(arr, dtype=np.float64) | |
| mask = np.isnan(a) | |
| out = np.zeros(a.shape, dtype=np.int32) | |
| out[~mask] = np.rint(a[~mask] * SCALE).astype(np.int32) | |
| out[mask] = _MISSING_MARKER | |
| return out | |
| # --------------------------------------------------------------------------- | |
| # fixed-point decoding | |
| # --------------------------------------------------------------------------- | |
| def test_decode_round_trip_and_marker(): | |
| values = np.array([0.0, 207.07, -3.5, np.nan, 12345.6789], dtype=np.float64) | |
| decoded = _decode_fixed_point(_enc(values)) | |
| assert np.isnan(decoded[3]) | |
| np.testing.assert_allclose(decoded[~np.isnan(decoded)], | |
| values[~np.isnan(values)], atol=1e-4) | |
| def test_decode_passes_through_float(): | |
| # an older plain-decimal copy must read unchanged (decode is a no-op on floats) | |
| values = np.array([1.0, 2.5, -7.25], dtype=np.float64) | |
| np.testing.assert_array_equal(_decode_fixed_point(values), values) | |
| def test_decode_handles_int64_and_no_marker(): | |
| # a re-encode at int64 must decode like int32 (any integer dtype), and an | |
| # integer array with no missing marker must round-trip with no spurious blanks | |
| vals = np.array([10000, 20000, -35000], dtype=np.int64) # = 1.0, 2.0, -3.5 | |
| out = _decode_fixed_point(vals) | |
| np.testing.assert_allclose(out, [1.0, 2.0, -3.5]) | |
| assert not np.isnan(out).any() | |
| # --------------------------------------------------------------------------- | |
| # leveling analysis on a synthetic, perfectly-leveled matrix | |
| # --------------------------------------------------------------------------- | |
| def test_analyze_nucleus_detects_leveling(): | |
| # build methods that are affine transforms of one base signal plus tiny noise: | |
| # after scaling they are interchangeable, so PC1 should dominate and every | |
| # pairwise correlation should be near 1. | |
| rng = np.random.RandomState(0) | |
| base = rng.normal(size=60) | |
| slopes = np.linspace(0.8, 1.3, 8) | |
| offsets = np.linspace(-5, 5, 8) | |
| cols = [a * base + b + 0.01 * rng.normal(size=60) | |
| for a, b in zip(slopes, offsets)] | |
| M = np.column_stack(cols) | |
| methods = [f"m{i}" for i in range(8)] | |
| res = analyze_nucleus(M, methods, ref=M[:, 0]) | |
| assert res['ev'][0] > 0.99 # PC1 carries essentially all variance | |
| assert res['r_min'] > 0.99 # worst pairwise correlation still near 1 | |
| assert set(res['scaled_rmse']) == set(methods) | |
| assert 'r2' in res['par'] and 'theta' in res['par'] | |
| # --------------------------------------------------------------------------- | |
| # delta22 loader against a synthetic int32 HDF5 in the real layout | |
| # --------------------------------------------------------------------------- | |
| DELTA22_FUNCS = ['hf', 'blyp_d3bj', 'bp86_d3bj', 'b97d3_d3bj', 'tpsstpss_d3bj', | |
| 'b3lyp_d3bj', 'pbe0_d3bj', 'm062x_d3', 'wb97xd', 'wp04', 'wc04', | |
| 'B2PLYP', 'mPW2PLYP', 'B2GP_PLYP', 'dsd_pbep86', 'revdsd_pbep86', | |
| 'mp2', 'dlpno_mp2'] | |
| def _make_synthetic_delta22(path): | |
| # one gas method label per functional. most sit at pcSseg3, but mp2 is only at | |
| # pcSseg2 and hf only at pcSseg1, so the loader's largest-basis fallback ladder | |
| # (pcSseg3 then pcSseg2 then pcSseg1) is exercised. | |
| basis_for = {f: "pcSseg3" for f in DELTA22_FUNCS} | |
| basis_for["mp2"] = "pcSseg2" | |
| basis_for["hf"] = "pcSseg1" | |
| names = [f"{f},{basis_for[f]},gas,none" for f in DELTA22_FUNCS] | |
| n_methods = len(names) | |
| with h5py.File(path, "w") as f: | |
| f.create_dataset("conventional_nmr_method_names", | |
| data=np.array(names, dtype=h5py.string_dtype())) | |
| # one solute, atoms H, C, H, C, N (N is ignored: not 1H/13C) | |
| z = np.array([1, 6, 1, 6, 7], np.uint8) | |
| g = f.create_group("solutes/mol1") | |
| g.create_dataset("atomic_numbers", data=z) | |
| # conventional_shieldings[method, geometry, atom]; geometry 1 = PBE0/cc-pVTZ | |
| base = np.array([30.0, 150.0, 31.0, 151.0, 200.0]) | |
| cs = np.zeros((n_methods, 2, len(z))) | |
| for mi in range(n_methods): | |
| cs[mi, 0] = base + 0.5 * mi # aimnet2 geometry (unused) | |
| cs[mi, 1] = base + 0.01 * mi # pbe0_tz geometry (used) | |
| g.create_dataset("stationary_and_pcm/conventional_shieldings", data=_enc(cs)) | |
| return base | |
| def test_load_delta22_synthetic(tmp_path): | |
| p = tmp_path / "delta22.hdf5" | |
| base = _make_synthetic_delta22(str(p)) | |
| out = load_delta22(str(p)) | |
| assert set(out) == {"1H", "13C"} | |
| # 1 solute has two H sites (atoms 0,2) and two C sites (atoms 1,3) | |
| assert out["1H"]["M"].shape == (2, len(DELTA22_FUNCS)) | |
| assert out["13C"]["M"].shape == (2, len(DELTA22_FUNCS)) | |
| assert DELTA22_REF in out["1H"]["methods"] | |
| # every functional was selected via the basis-fallback ladder, so no method is missing | |
| assert not np.isnan(out["1H"]["M"]).any() | |
| assert not np.isnan(out["13C"]["M"]).any() | |
| # decoded values must match what we wrote on the pbe0_tz geometry; pick the | |
| # DSD-PBEP86 method column for the first H site (atom 0) | |
| ref_method_index = DELTA22_FUNCS.index("dsd_pbep86") | |
| expected = base[0] + 0.01 * ref_method_index | |
| col = out["1H"]["methods"].index(DELTA22_REF) | |
| np.testing.assert_allclose(out["1H"]["M"][0, col], expected, atol=1e-4) | |
| # --------------------------------------------------------------------------- | |
| # NS372 sheet parsing against a synthetic Kaupp-style worksheet | |
| # --------------------------------------------------------------------------- | |
| def _make_synthetic_kaupp_sheet(path): | |
| wb = openpyxl.Workbook() | |
| ws = wb.active | |
| ws.title = "S6 - 1H Shieldings" | |
| # row 1: functional headers from column 7; row 2: tau sub-labels | |
| # two methods: SVWN (no tau) and TPSS (tau = tauC), matching leveling.METHODS keys | |
| ws.cell(1, 7, "SVWN") | |
| ws.cell(1, 8, "TPSS"); ws.cell(2, 8, "τC") | |
| # data rows from row 4: col1 nucleus (sticky), col2 molecule, col4 CCSD(T) ref | |
| rows = [("1H", "molA", 30.0, 30.1, 30.2), | |
| ("1H", "molB", 28.0, 28.2, 28.1), | |
| ("1H", "molC", 25.0, 25.3, 25.1)] | |
| for i, (nuc, mol, ref, svwn, tpss) in enumerate(rows): | |
| r = 4 + i | |
| ws.cell(r, 1, nuc) | |
| ws.cell(r, 2, mol) | |
| ws.cell(r, 4, ref) | |
| ws.cell(r, 7, svwn) | |
| ws.cell(r, 8, tpss) | |
| wb.save(path) | |
| def test_read_nucleus_synthetic(tmp_path): | |
| p = tmp_path / "kaupp.xlsx" | |
| _make_synthetic_kaupp_sheet(str(p)) | |
| wb = openpyxl.load_workbook(str(p), data_only=True) | |
| ref, data = read_nucleus(wb, "S6 - 1H Shieldings") | |
| np.testing.assert_allclose(ref, [30.0, 28.0, 25.0]) | |
| assert ("SVWN", None) in data | |
| assert ("TPSS", "τC") in data | |
| np.testing.assert_allclose(data[("SVWN", None)], [30.1, 28.2, 25.3]) | |
| np.testing.assert_allclose(data[("TPSS", "τC")], [30.2, 28.1, 25.1]) | |
| # --------------------------------------------------------------------------- | |
| # opt-in tests against the real files (skipped when absent, e.g. in CI) | |
| # --------------------------------------------------------------------------- | |
| def _find(*candidates): | |
| for c in candidates: | |
| if c and os.path.exists(c): | |
| return c | |
| return None | |
| _HERE = os.path.dirname(os.path.abspath(__file__)) | |
| import paths | |
| _REAL_DELTA22 = _find( | |
| paths.dataset_file("delta22", file=__file__), # deposit copy, then in-repo data/delta22/ | |
| os.path.join(_HERE, "delta22.hdf5"), | |
| os.path.join(_HERE, "delta22", "delta22.hdf5"), | |
| ) | |
| _REAL_KAUPP = _find( | |
| os.path.join(_HERE, "..", "..", "data", "ns372", "ct1c00919_si_002.xlsx"), | |
| ) | |
| def test_real_delta22_reproduces_leveling(): | |
| out = load_delta22(_REAL_DELTA22) | |
| assert out["1H"]["M"].shape == (145, 18) | |
| assert out["13C"]["M"].shape == (76, 18) | |
| res = analyze_nucleus(out["1H"]["M"], out["1H"]["methods"], out["1H"]["ref"]) | |
| assert res['ev'][0] > 0.99 # PC1 dominance on the real proton data | |
| def test_real_ns372_loads_all_nuclei(): | |
| out = load_ns372(_REAL_KAUPP) | |
| assert set(out) == {"1H", "11B", "13C", "15N", "17O", "19F", "31P", "33S"} | |
| res = analyze_nucleus(out["13C"]["M"], out["13C"]["methods"], out["13C"]["ref"]) | |
| assert res['ev'][0] > 0.99 | |
| def test_real_ns372_1h_convergence_stats(): | |
| # Figure 2D input: the 1H slice reduces to 44 methods vs the CCSD(T) reference, | |
| # and each method's slope/scaled RMSE lands in the expected physical range. | |
| cc = load_ns372_1h_convergence(_REAL_KAUPP) | |
| assert cc.shape == (124, 45) # 44 methods + CCSD(T) | |
| assert "CCSD(T)" in cc.columns and "HF" in cc.columns # 'Hartree-Fock' header renamed | |
| res = convergence_statistics(cc).set_index("Method") | |
| assert len(res) == 44 | |
| assert (res["Slope"].between(0.9, 1.15)).all() # near the y=x limit | |
| assert (res["RMSE (scaled)"] > 0).all() | |
| # the double hybrid sits closest to the electronic-structure limit | |
| assert res["RMSE (scaled)"].idxmin() == "DSD-PBEP86" | |
| assert res.loc["DSD-PBEP86", "RMSE (scaled)"] < 0.1 | |