FAU Erlangen-Nürnberg
What “must know” means for an algorithm
Sixteen algorithms, five groups
# in : geometry {R_a}, basis {phi_mu}, initial density D
# out: ground-state energy E, orbitals C
repeat:
F = h + J[D] - K[D] # HF: Coulomb + exact exchange
# KS-DFT: F = h + J[D] + V_xc[rho_D] (functional!)
solve F C = S C eps # generalised eigenproblem
C_occ = eigenvectors of the N_occ lowest eps
D_new = occupation-weighted C_occ @ C_occ.T
if norm(D_new - D) < tol: break
D = mix(D, D_new) # damping / DIIS
return E(D), CMust know
Must know
# in : initial config x, energy E(.), temperature T
# out: samples from the Boltzmann distribution
loop:
x_new = propose(x) # move catalogue: swap,
# displace, volume, ...
dE = E(x_new) - E(x)
if dE <= 0 or rand() < exp(-dE / (kB*T)):
x = x_new # accept
record(x) # ALWAYS record current x,
# also after a rejectionMust know
# in : set of (composition x_i, formation energy Ef_i),
# including elemental references
# out: stability verdict per candidate
hull = lower_convex_hull(points) # composition-energy space
for candidate (x, Ef):
E_ref = hull_energy_at(x) # best linear combination
# of hull phases at x
E_hull = Ef - E_ref
# E_hull <= 0 : on the hull ("stable" at 0 K)
# 0 < E_hull < d : metastable within tolerance dMust know
# in : lattice matrix L, fractional coords {f_i}, cutoff r_c
# out: neighbour list = the crystal graph's edge set
assert r_c < half_min_cell_width(L) # else build a supercell
for atom i:
for atom j, image n in {-1,0,1}^3: # periodic images!
d = norm(L @ (f_j + n - f_i))
if 0 < d <= r_c:
neighbours[i].append((j, n, d))Must know
# Tier 1 -- Magpie (composition only):
feats = [stat(prop[Z] for Z, frac in formula)
for prop in element_tables # mass, X, radius...
for stat in (mean, range, dev)] # fraction-weighted
# Tier 2 -- RDF (structure-aggregated):
for pair (i, j) with PBC distances d_ij <= r_max:
bin d_ij into histogram
g[k] = count[k] / (N * rho * shell_volume(k)) # normalise
# Tier 3 -- local environments: neighbour list (B1)
# + invariant functions of {d_ij, angles}, per atomMust know
# in : crystal graph (B1); node features h_i = embed(Z_i);
# edge features e_ij = RBF(d_ij)
# out: property y (invariant), forces F_i (equivariant)
for layer t in 1..T:
for atom i:
m_i = sum over j in N(i) of
M_t(h_i, h_j, e_ij) # message
h_i = U_t(h_i, m_i) # update
y = R(sum_i h_i) # pooling -> invariant
F_i = -dy/dr_i # forces via autogradMust know
# in : dataset; GROUP labels (chemistry family / prototype)
# out: model + an honest error estimate
train, val, test = group_aware_split(data, by=family)
# NOT row-wise random! (leakage)
best = None
for epoch in 1..max_epochs:
for batch in train:
theta -= lr * grad(loss(batch))
v = evaluate(val)
if v < best: best = v; save_checkpoint()
elif epochs_since_best > patience: break # early stop
report evaluate(test) # touched ONCE, at the very endMust know
# in : dataset with chemistry-family labels
# out: the OPERATIONAL generalisation estimate
for family c in families:
model_c = train(data where family != c)
err[c] = evaluate(model_c, data where family == c)
report per-family table err # the trust artefact
report mean(err) # only WITH the table --
# the mean alone hides
# the worst familyMust know
# in : LARGE unlabelled crystal database;
# small labelled target set
# out: pretrained encoder -> featurizer / fine-tuned model
for batch of unlabelled crystals:
x_corr = corrupt(x) # mask atom types | mask edges
# | perturb positions (denoise)
# | augment for contrastive pair
loss = reconstruct(x | x_corr) # or InfoNCE
update(encoder)
# downstream:
z = encoder(x) # frozen featurizer, OR
fine_tune(encoder + head, small_labelled_set)Must know
# in : training set; K seeds; per-family calibration sets
# out: calibrated mu(x), sigma(x)
models = [train(seed=k) for k in 1..K] # K ~ 5
mu(x) = mean_k models[k](x)
sigma(x) = std_k models[k](x) # disagreement =
# epistemic uncertainty
for family c: # calibrate PER FAMILY
s[c] = fit_scale(val_c) s.t. coverage of
mu +/- z * s[c] * sigma hits nominal
predict: route x to its family, apply s[family(x)]Must know
# in : training X, y; kernel k(.,.); noise sigma_n
# out: predictive mu*, sigma* at any candidate x*
K = k(X, X) + sigma_n**2 * I
Lchol = cholesky(K) # O(n^3) -- the GP's limit
alpha = solve(Lchol, y)
mu_star = k(x_star, X) @ alpha
sigma2_star = k(x_star, x_star) \
- k(x_star, X) @ solve(Lchol, k(X, x_star))Must know
# in : posterior mu(x), sigma(x) over a CANDIDATE POOL;
# current best y_best (maximisation convention)
UCB: alpha(x) = mu(x) + beta * sigma(x)
EI: z = (mu(x) - y_best - xi) / sigma(x)
alpha(x) = (mu(x) - y_best - xi) * Phi(z) \
+ sigma(x) * phi(z) # Phi/phi: normal cdf/pdf
TS: f_tilde ~ posterior_sample() # one draw
alpha(x) = f_tilde(x)
next_experiment = argmax over pool of alpha(x)Must know
# in : candidate pool; constraint set (stoichiometry,
# charge balance, symmetry, E_hull tolerance)
# out: next experiment, guaranteed feasible
F = [x for x in pool if feasible(x)] # FILTER FIRST
# hard checks: integer occupancies, sum ox. states == 0,
# space-group consistency, E_hull <= delta
x_next = argmax over F of alpha(x) # RANK SECOND
# soft variant, when the boundary is fuzzy:
alpha_soft(x) = alpha(x) - beta * dist(x, F)Must know
# in : candidate x; encoder latent phi (C3); train stats;
# calibrated sigma (C4); thresholds tau at the 95th
# percentile of in-distribution validation scores
s1 = min over i of norm(phi(x) - phi(X_i)) # latent NN dist
s2 = mahalanobis(x, mean_X, cov_X) # feature space
s3 = sigma_ens(x) # disagreement
ood = (s1 > tau1) or (s2 > tau2) or (s3 > tau3) # OR=cautious
if sigma(x) small and not ood: COMMIT to synthesis
elif sigma(x) small and ood: REFUSE -> human review
# silent extrapolation!
else: cheap exploratory measurement
audit_log(model, calibration, scores, thresholds, decision)Must know
# in : trained denoiser eps_hat(x, t [, condition c]);
# optional feasibility classifier f_phi (guidance)
# out: candidate crystals (lattice, coords, species)
x_T ~ Normal(0, I) # noise over lattice + fractional
# coords + species logits
for t in T .. 1:
eps = eps_hat(x_t, t, c) # conditioned on
# target property
x_t = denoise_step(x_t, eps)
x_t += eta * grad log f_phi(x_t) # classifier guidance
# = §B constraint knob
snap: species -> one-hot; symmetrise lattice # projection
validate: DFT relax -> E_hull; score S.U.N.Must know
# in : seed data D; candidate pool (database or E1);
# budget; everything above
model = fit(D); calibrate_per_family(model) # C4
while budget_remaining():
mu, sigma = model.predict(pool) # D1/C4
F = feasibility_filter(pool) # D3
trusted = [x for x in F if gate(x) == COMMIT] # D4
if not trusted:
escalate_to_human(); continue
x = argmax over trusted of alpha(mu, sigma) # D2
result = synthesise_and_measure(x) # steps 3-5:
# days + a furnace
D += (x, result)
model = refit(D); recalibrate(); update_thresholds()
audit_log(everything) # per decisionMust know
synthesise_and_measure — days per iteration; everything else is seconds. Plan campaigns around that line.| # | Algorithm | Unit | Feeds skill |
|---|---|---|---|
| A1 | SCF (HF / KS-DFT) | U3 | labels for everything |
| A2 | Velocity Verlet | U4 | 2 (train a surrogate for forces) |
| A3 | Metropolis MC | U5 | 3 (sampling logic of exploration) |
| A4 | Convex hull / \(E_{\text{hull}}\) | U4–U5 | 3 (the acquisition target) |
| B1–B3 | Neighbour list · descriptors · message passing | U6–U7 | 1 (choose a representation) |
| C1–C2 | Training loop · LOCO CV | U8 | 2 (defensible split) |
| C3–C4 | SSL pretraining · ensembles + calibration | U10, §D | 2, 3 |
| D1–D4 | GP · acquisition · constraints · trust gate | §A2, §B, §D | 3 (plan an acquisition) |
| E1–E2 | Diffusion · autonomous loop | U12, §E | 4 (close the loop) |

© Philipp Pelz - Materials Genomics