What k-point mesh does this crystal need?¶
k_points.k_distance.qrf — the quantile random forest published as
q3bye-wep37.
Choosing a k-point mesh is guesswork with a cost on both sides: too coarse and the answer is wrong, too fine and the calculation takes longer than it needed to. This model was fitted against meshes that were converged properly — scanned until the total energy stopped moving — so it can suggest one without you running that scan yourself.
It does not predict the three integers. It predicts a k-distance, and the mesh follows from the reciprocal lattice.
Setup¶
The record holds the forest. The forest's input vector embeds a learned representation from a second published record, so both are needed — that dependency is written into the model record and its digests are checked on load, because a different checkpoint would change the numbers silently.
from pathlib import Path
from urllib.request import urlretrieve
PSDI = "https://data-collections.psdi.ac.uk/api/records"
work = Path("goldilocks-kdistance")
work.mkdir(exist_ok=True)
def fetch(record, name, into=work):
target = into / name
if not target.exists():
print(f"downloading {name} ...")
urlretrieve(f"{PSDI}/{record}/files/{name}/content", target)
return target
forest = fetch("q3bye-wep37", "QRF95.pkl") # 185 MB, once
checkpoint = fetch("m742g-g0k14", "is_metal.ckpt") # 2 MB
atom_init = fetch("m742g-g0k14", "atom_init.json") # 28 KB
for path in (forest, checkpoint, atom_init):
print(f"{path.name:16} {path.stat().st_size / 1e6:8.2f} MB")
QRF95.pkl 185.70 MB is_metal.ckpt 2.01 MB atom_init.json 0.03 MB
model.json describes the artifact in machine-readable form: which runtime
serves it, the 483 feature columns in order, the target contract, and the
digests of everything above. It is part of the record from its next version
onwards; until then it comes from the repository.
import shutil
DEPOSIT = Path("../../deposits/k_points/k_distance/qrf/model.json")
shutil.copy(DEPOSIT, work / "model.json")
print((work / "model.json").read_text()[:180], "...")
{
"artifacts": {
"estimator": "QRF95.pkl",
"estimator_sha256": "2647b976a57637cc34e4874f3b0ff3436a7c032761b774fdd9e56e69fddaffcc"
},
"calibration": null,
"determini ...
from goldilocks_ml.inference import load_model
model = load_model(
work,
artifacts={
"metallicity_checkpoint": checkpoint,
"metallicity_atom_init": atom_init,
},
)
print(model.model_id)
k_points.k_distance.qrf@comp_struct_soap_lattice_metal.v1
Ask it about four crystals¶
One metal and three insulators, built from a space group and a lattice constant.
from pymatgen.core import Lattice, Structure
def cubic(spacegroup, a, species, sites):
return Structure.from_spacegroup(spacegroup, Lattice.cubic(a), species, sites)
ORIGIN = [[0.0, 0.0, 0.0]]
ROCKSALT = [[0.0, 0.0, 0.0], [0.5, 0.5, 0.5]]
crystals = {
"Cu (fcc)": cubic("Fm-3m", 3.615, ["Cu"], ORIGIN),
"Si (diamond)": cubic("Fd-3m", 5.431, ["Si"], ORIGIN),
"MgO (rocksalt)": cubic("Fm-3m", 4.212, ["Mg", "O"], ROCKSALT),
"NaCl (rocksalt)": cubic("Fm-3m", 5.640, ["Na", "Cl"], ROCKSALT),
}
predictions = model.predict_batch(list(crystals.values()))
for name, prediction in zip(crystals, predictions):
print(f"{name:17} k_distance = {float(prediction.value):.4f} 1/angstrom")
Cu (fcc) k_distance = 0.1784 1/angstrom Si (diamond) k_distance = 0.2174 1/angstrom MgO (rocksalt) k_distance = 0.4602 1/angstrom NaCl (rocksalt) k_distance = 0.4920 1/angstrom
Turning that into a mesh¶
The k-distance is the largest spacing you are willing to leave between adjacent k-points. Divide each reciprocal-lattice vector by it and round up:
N_i = ceil(|b_i| / k_distance)
The reciprocal vectors include the factor of 2π. That convention is not optional decoration — a k-distance under the other convention differs by 6.28 and nothing in the number reveals which one it came from, which is why the model declares a target contract rather than a column name:
goldilocks.k_distance.mesh_lower_bound.2pi.v1
import math
def mesh_for(structure, k_distance):
lengths = structure.lattice.reciprocal_lattice.abc # includes 2*pi
return tuple(max(1, math.ceil(length / k_distance)) for length in lengths)
print(f"{'crystal':17} {'k_distance':>11} {'mesh':>12}")
for (name, structure), prediction in zip(crystals.items(), predictions):
value = float(prediction.value)
print(f"{name:17} {value:>11.4f} {str(mesh_for(structure, value)):>12}")
crystal k_distance mesh Cu (fcc) 0.1784 (10, 10, 10) Si (diamond) 0.2174 (6, 6, 6) MgO (rocksalt) 0.4602 (4, 4, 4) NaCl (rocksalt) 0.4920 (3, 3, 3)
Copper gets 10×10×10 and NaCl gets 3×3×3. That is the whole point: a metal has a Fermi surface to resolve and a wide-gap insulator does not, and the model has learned the difference rather than being told it.
Running NaCl at copper's mesh would cost roughly thirty times as many k-points for an answer that does not change.
What else comes back¶
A prediction carries one value, because a consumer can only write one setting into an input file. What the model knows beyond that travels alongside it and is recorded rather than acted on.
prediction = predictions[0]
print("value ", round(float(prediction.value), 4), prediction.details["units"])
print("interval ", [round(x, 4) for x in prediction.details["interval"]])
print("calibrated ", prediction.details["calibrated"])
print("confidence ", prediction.confidence)
for warning in prediction.warnings:
print("warning ", warning)
value 0.1784 1/angstrom interval [0.1055, 0.2925] calibrated False confidence None warning k_points.k_distance.qrf@comp_struct_soap_lattice_metal.v1 records no conformal calibration, so its interval carries no coverage guarantee. The median is unaffected.
Why confidence is empty here¶
The forest predicts three quantiles — 0.05, 0.5, and 0.95 — and publishes the median. The other two are the interval above.
A conformal correction turns that raw interval into one with a provable
coverage level, and confidence is where that level goes. This published
artifact has none recorded: the correction it was used with lives in the
consuming application rather than beside the weights, and it was fitted under a
different rule than current software applies. Declaring it would be a claim
nobody can check.
So the interval comes back as the forest produced it, honestly labelled
calibrated: False. The median is unaffected — the correction only ever
moved the endpoints — so the mesh you get is the one this artifact was
published to give.
Despite the name, QRF95 is not a 95% interval. The 0.05–0.95 quantiles have
nominal 90% central coverage.
What it does not do¶
- It does not prove convergence. It reproduces the protocol it was fitted against: Quantum ESPRESSO SCF single points, SSSP 1.3 PBEsol efficiency pseudopotentials, cold smearing at 0.01 Ry, no magnetic configurations. A different property or protocol needs its own test.
- It is a lower bound on mesh density, not a guarantee. Fermi-surface pockets can be missed, and compounds with gaps below about 0.14 eV were not reliably resolved by the procedure that generated the training targets.
- It has no accuracy figure here. The published record reports none, because the surviving notebook outputs cannot be bound to this exact artifact. That is a provenance gap, recorded rather than papered over.