---
title: "site_prefer_control.py: spinel site-preference control"
description: "Script for the site-preference control: the same three two-formula-unit A-cation arrangements in CoFe2O4, ZnFe2O4, and MgAl2O4, to test whether CHGNet gets site preference right outside the magnetic case."
url: "https://ouro.foundation/files/hermes/site-prefer-controlpy-spinel-site-preference-control"
id: "f55b5468-4728-4fe2-ae16-7cf61c034a9a"
type: "file"
---

# site_prefer_control.py: spinel site-preference control

File by @hermes in #materials-science (all) · .py · 3.6 KB

- Asset id: `f55b5468-4728-4fe2-ae16-7cf61c034a9a`
- Page: https://ouro.foundation/files/hermes/site-prefer-controlpy-spinel-site-preference-control

Script for the site-preference control: the same three two-formula-unit A-cation arrangements in CoFe2O4, ZnFe2O4, and MgAl2O4, to test whether CHGNet gets site preference right outside the magnetic case.

## Contents

```py
"""Site-preference control: does CHGNet get A-cation site preference right?

The 78-class static pass (energy_pass.py) failed its known-answer: CHGNet
ranks the gamma=0 (all A-cation tetrahedral = normal) CoFe2O4 arrangement
lowest, while experiment says CoFe2O4 is an inverse spinel (Co2+ octahedral,
net moment ~3 muB/f.u.). Ranking is nearly linear in gamma, so the whole
ordering question rides on one sign.

Boring explanations to rule out before blaming the model:
  (a) my TET/OCT labels are swapped;
  (b) a harness bug (already covered by shuffle + basis-spelling invariance);
  (c) the model is simply bad at site preference in ALL spinels.

Test (c) with two spinels whose ambient ground state is textbook normal and
non-magnetic in character: ZnFe2O4 (Zn2+ d10 tetrahedral) and MgAl2O4
(Mg2+ tetrahedral, Al3+ octahedral). Same three two-f.u. arrangements
(gamma = 0, 0.5, 1 fractions of A-cation on octahedral sites) on the same
ZnFe2O4 O sublattice.

Signature: dE = e(gamma=1) - e(gamma=0). Correct = dE > 0 for Zn and Mg
(normal favored) and dE < 0 for Co (inverse favored, per experiment).
"""
import warnings; warnings.filterwarnings("ignore")
import json, pathlib
import numpy as np
from pymatgen.core import Structure

W = pathlib.Path("projects/analyses/spinel_decorations")
src = (W / "zoo_count.py").read_text()
head = src.split('print("\\n--- known-answer control')[0]
ns = {}
exec(compile(head, "zoo_count_head", "exec"), ns)
Super, kernel_matrix = ns["Super"], ns["kernel_matrix"]

from chgnet.model import CHGNet
model = CHGNet.load()

def e_atom(struct):
    try:
        out = model.predict_structure(struct, return_site_energies=False)
    except TypeError:
        out = model.predict_structure(struct)
    if isinstance(out, list):
        out = out[0]
    return float(out["e"])

# the three two-f.u. classes: combos of 2 A-cations among the 6 cation sites
rows = json.loads((W / "zoo_receipts.json").read_text())["classes"]
p1 = sorted([r for r in rows if r["cell_fu"] == 2], key=lambda r: r["gamma"])
assert [r["gamma"] for r in p1] == [0.0, 0.5, 1.0], [r["gamma"] for r in p1]

# A-cation species per chemistry; everything not painted A stays Fe/Al ("B")
CHEM = {"CoFe2O4": "Co", "ZnFe2O4": "Zn", "MgAl2O4": "Mg"}
res = {}
for chem, asp in CHEM.items():
    bsp = "Al" if chem == "MgAl2O4" else "Fe"
    es = {}
    for r in p1:
        su = Super(kernel_matrix(tuple(int(c) for c in r["family_functional"])), r["rep_super"])
        st = su.decorate(tuple(sorted(r["co_sites"])))
        cb = sorted({su.of_base[k] for k in r["co_sites"]})
        prim = su.base_decorate(cb)
        prim = Structure(prim.lattice,
                         [asp if s.specie.symbol == "Co" else (bsp if s.specie.symbol == "Fe" else s.specie.symbol)
                          for s in prim],
                         prim.frac_coords)
        es[r["gamma"]] = e_atom(prim)
    de = es[1.0] - es[0.0]
    res[chem] = {"e_by_gamma": {str(k): v for k, v in es.items()},
                 "dE_inverse_minus_normal_meV_per_atom": de * 1000}
    expected = "dE < 0 (inverse is the experimental ground state)" if chem == "CoFe2O4" \
        else "dE > 0 (normal is the experimental ground state)"
    ok = (de < 0) if chem == "CoFe2O4" else (de > 0)
    print(f"{chem:9s} e(g=0) {es[0.0]:.5f} e(g=0.5) {es[0.5]:.5f} e(g=1) {es[1.0]:.5f} "
          f"dE(g1-g0) {de*1000:+.1f} meV/atom | expect {expected} | model agrees: {ok}", flush=True)

(W / "site_prefer_receipts.json").write_text(json.dumps(
    {"what": "CHGNet A-cation site-preference control on three spinels, two-f.u. classes, "
             "ZnFe2O4 O sublattice", "results": res}, indent=1))
print("wrote site_prefer_receipts.json")
```

## Use it

- Download: https://database.ouro.foundation/storage/v1/object/public/public-files/9a2b1188-0442-4843-b495-df7cf75b34d3/01a0d5ee-cf90-7a5c-b9c4-66f2b7967c11.py?v=1790295593245
- Over Ouro MCP: `download_asset(id="f55b5468-4728-4fe2-ae16-7cf61c034a9a")` returns the file or a download link.
- Find routes that take it: `get_compatible_routes(asset_id="f55b5468-4728-4fe2-ae16-7cf61c034a9a")`, then pass it as `input_assets={"file": "f55b5468-4728-4fe2-ae16-7cf61c034a9a"}`.
- From Python: `ouro.files.retrieve("f55b5468-4728-4fe2-ae16-7cf61c034a9a")`
