| """Tests for analysis/code/delta50_residuals.py. |
| |
| Synthetic tests check residual selection and statistics on a hand-built shieldings dict. Opt-in |
| tests run against the real delta50.hdf5 and reproduce the paper's DELTA50 observation: MagNET-Zero |
| tracks its DFT reference to well under 0.5 ppm (1H) and a few ppm (13C), with nitromethane, |
| nitroethane, and 2-methyl-2-nitropropane the significant 13C outliers (up to ~7 ppm). |
| """ |
| import os |
| import sys |
|
|
| import numpy as np |
| import pytest |
|
|
| HERE = os.path.dirname(os.path.abspath(__file__)) |
| sys.path.insert(0, os.path.join(HERE, "..", "..", "data", "delta50")) |
| sys.path.insert(0, HERE) |
| import paths |
|
|
| import delta50_residuals as D |
|
|
|
|
| def make_shieldings(): |
| """Flat shieldings dict like the reader's all_atoms: H, C, C, H, N. MagNET-Zero is one array |
| (WP04 quality at H, wB97X-D quality at C); the DFT reference is split by nucleus.""" |
| return { |
| "atomic_numbers": np.array([1, 6, 6, 1, 7]), |
| "shielding_wp04_pcSseg2": np.array([30.5, 150.5, 151.5, 31.5, 200.5]), |
| "shielding_wb97xd_pcSseg2": np.array([30.7, 150.7, 151.7, 31.7, 200.7]), |
| "nn_magnet_zero": np.array([30.6, 150.6, 151.6, 31.3, np.nan]), |
| } |
|
|
|
|
| def test_residuals_select_nucleus_against_the_right_dft_level(): |
| s = make_shieldings() |
| h = D.residuals(s, "H") |
| np.testing.assert_allclose(sorted(h), sorted([30.5 - 30.6, 31.5 - 31.3])) |
| c = D.residuals(s, "C") |
| np.testing.assert_allclose(sorted(c), sorted([150.7 - 150.6, 151.7 - 151.6])) |
| assert len(h) == 2 and len(c) == 2 |
|
|
|
|
| def test_residual_stats(): |
| errors = np.array([-0.1, 0.2, 0.05, -0.05]) |
| st = D.residual_stats(errors) |
| assert st["n"] == 4 |
| assert st["rmse"] == pytest.approx(np.sqrt(np.mean(errors ** 2))) |
| assert st["mae"] == pytest.approx(np.mean(np.abs(errors))) |
| assert st["max_ae"] == pytest.approx(0.2) |
|
|
|
|
| def test_residual_stats_empty(): |
| st = D.residual_stats(np.array([])) |
| assert st["n"] == 0 and np.isnan(st["rmse"]) |
|
|
|
|
| |
|
|
| REAL = paths.dataset_file("delta50", file=__file__) |
|
|
|
|
| @pytest.mark.skipif(not os.path.exists(REAL), reason="real delta50.hdf5 not present") |
| def test_reproduces_delta50_magnet_zero_vs_dft_residuals(): |
| summary = D.residual_summary(REAL) |
| |
| assert summary["H"]["rmse"] == pytest.approx(0.055, abs=0.01) |
| assert summary["H"]["max_ae"] == pytest.approx(0.469, abs=0.02) |
| |
| assert summary["C"]["rmse"] == pytest.approx(0.607, abs=0.03) |
| assert summary["C"]["max_ae"] == pytest.approx(6.654, abs=0.05) |
|
|
|
|
| @pytest.mark.skipif(not os.path.exists(REAL), reason="real delta50.hdf5 not present") |
| def test_named_13c_outliers_are_the_three_nitroalkanes(): |
| per_mol = D.per_molecule_max_residual(REAL) |
| top3 = sorted(per_mol, key=lambda nm: -per_mol[nm]["C"])[:3] |
| assert set(top3) == {"nitromethane", "nitroethane", "2-methyl-2-nitropropane"} |
| |
| assert per_mol["nitromethane"]["C"] == pytest.approx(6.654, abs=0.05) |
| |
| assert per_mol["nitrobenzene"]["C"] < 0.5 |
|
|
|
|
| |
| |
| |
| _GOLDEN_MULTISET = { |
| "cyclohexanone": {"C": [25.02, 27.03, 27.03, 42.0, 42.0, 212.15]}, |
| "methyl acetate": {"C": [20.71, 51.62, 171.55]}, |
| "2-methyl-2-nitropropane": {"C": [27.87, 27.87, 27.87, 85.06]}, |
| "n,n-dimethylformamide": {"C": [31.44, 36.48, 162.52]}, |
| "n,n-dimethylacetamide": {"C": [21.58, 35.2, 38.05, 170.66]}, |
| "2-methyl-2-butene": {"C": [13.41, 17.32, 25.63, 118.44, 132.1]}, |
| } |
| |
| |
| _GOLDEN_ATOM = { |
| "n,n-dimethylformamide": {0: 162.52, 3: 31.44, 4: 36.48}, |
| "n,n-dimethylacetamide": {1: 170.66, 3: 35.2, 4: 38.05, 5: 21.58}, |
| "2-methyl-2-butene": {0: 17.32, 1: 132.1, 2: 118.44, 3: 13.41, 4: 25.63}, |
| "cyclohexanone": {1: 212.15, 2: 42.0, 3: 42.0, 4: 27.03, 5: 27.03, 6: 25.02}, |
| } |
|
|
|
|
| @pytest.mark.skipif(not os.path.exists(REAL), reason="real delta50.hdf5 not present") |
| def test_experimental_multiset_matches_published_delta50(): |
| from decode_delta50 import Delta50 |
| with Delta50(REAL) as ds: |
| for name, expected in _GOLDEN_MULTISET.items(): |
| m = ds.molecule_by_name(name) |
| got = sorted(round(float(x), 2) for x in m["experimental_shift"][m["atomic_numbers"] == 6] |
| if np.isfinite(x)) |
| assert got == sorted(expected["C"]), name |
|
|
|
|
| @pytest.mark.skipif(not os.path.exists(REAL), reason="real delta50.hdf5 not present") |
| def test_experimental_swap_sensitive_carbons_pinned_to_atoms(): |
| from decode_delta50 import Delta50 |
| with Delta50(REAL) as ds: |
| for name, atoms in _GOLDEN_ATOM.items(): |
| m = ds.molecule_by_name(name) |
| for idx, shift in atoms.items(): |
| assert m["atomic_numbers"][idx] == 6, (name, idx) |
| assert m["experimental_shift"][idx] == pytest.approx(shift, abs=1e-3), (name, idx) |
|
|
|
|
| @pytest.mark.skipif(not os.path.exists(REAL), reason="real delta50.hdf5 not present") |
| def test_experimental_shift_present_and_agrees_with_dft(): |
| from decode_delta50 import Delta50 |
| with Delta50(REAL) as ds: |
| atoms = ds.all_atoms() |
| z = atoms["atomic_numbers"] |
| exp = atoms["experimental_shift"] |
| |
| assert np.isfinite(exp[(z == 1) | (z == 6)]).all() |
| assert np.isnan(exp[(z != 1) & (z != 6)]).all() |
| |
| |
| |
| for zz, key, tol in ((1, "shielding_wp04_pcSseg2", 0.5), (6, "shielding_wb97xd_pcSseg2", 5.0)): |
| m = (z == zz) & np.isfinite(exp) |
| sigma = atoms[key][m] |
| A = np.vstack([sigma, np.ones(m.sum())]).T |
| coef, *_ = np.linalg.lstsq(A, exp[m], rcond=None) |
| rmse = np.sqrt(np.mean((A @ coef - exp[m]) ** 2)) |
| assert rmse < tol, f"Z={zz} scaled-DFT-vs-exp RMSE {rmse:.3f} too large" |
|
|