Configuration reference#
One YAML file drives every command. matmelt init writes a commented starter
version of it; this page is the complete list of what may go in it.
Which file a command reads, and the materials-cli.yaml fallback, are covered
in the CLI reference.
The shape#
run:
name: my-first-run
work_dir: ./materials-work
elements: [Na, Cl]
combination_sizes: [2]
pipeline:
stop_after: extract
retry_budget: 3
backends:
laptop:
kind: local
options:
work_dir: ./materials-jobs
max_parallel: 2
stages:
compose:
backend: laptop
options:
python: python3
structure:
backend: laptop
options:
python: python3
screen:
backend: laptop
options:
python: python3
device: cpu
extract:
backend: laptop
Four top-level sections, and the split between the last two is the architecture
in one file: backends: is where work runs, stages: is what runs, and
a stage names its backend by name only. Moving the expensive stage to a cluster
is an edit to one line of stages:, never to any Python.
run:, backends: and stages: are required. pipeline: may be omitted
entirely — every key in it has a default.
run:#
This run’s identity and scientific inputs.
Key |
Default |
Meaning |
|---|---|---|
|
required |
A short label, shown by |
|
required |
Root of the workspace: the state database and every per-material directory live under it. |
|
required |
The element set |
|
all sizes |
How many elements each seed combines. |
combination_sizes: [2] explores binaries, [2, 3] adds ternaries, and 1 is
legal — it seeds elemental solids. Omitting the key, or writing [], means
every size from 1 to the number of distinct elements, which is the widest
search and grows combinatorially. Sizes are de-duplicated and sorted, and a size
larger than your element count yields nothing (doctor names it rather than
silently enumerating zero seeds).
Enumeration cost is reported before any work starts — by doctor, and by run
in its preflight — so you see the seed count before committing a machine to it.
pipeline:#
How the funnel executes. Every key is optional.
Key |
Default |
Meaning |
|---|---|---|
|
|
Name of the last stage this run executes. |
|
|
Per-stage fault retries before a material goes |
|
|
Age at which a claim counts as orphaned and returns to its queue. Must be |
|
|
Sleep between orchestrator passes. May be |
stop_after must name a stage you configured, and it is mutually exclusive with
run --only (using both is exit 2). A mid-funnel stop is a pause, not a
retirement: materials rest at that stage’s output state, live and claimable, so
raising the value, removing the key, or running matmelt run --only <stage>
continues them in the same workspace. Only a run that executes through the
configured last stage retires its materials as DONE.
retry_budget counts faults — a crashed job, a missing input, an
unreachable backend. It does not apply to science verdicts: a material screened
out by MACE, or a DFTB+ WorkChain that exhausted the plugin’s own healing, fails
terminally at the first attempt, because re-running reproduces it. That
distinction is visible in status, where a fault shows Attempts: 3 and a
verdict shows Attempts: 1.
These three numeric keys reject YAML booleans on purpose: true would otherwise
be coerced to 1 and quietly change the run.
backends:#
A mapping of your own names to compute targets. At least one is required, and
several stages may share one (they then share a single instance, and its
max_parallel applies across them).
Key |
Default |
Meaning |
|---|---|---|
|
required |
Which implementation: |
|
|
That kind’s own settings — free-form here, validated by the backend itself. |
Options are deliberately not part of this schema: each kind needs different
keys, and each backend’s constructor rejects what it cannot use. An invalid
option is a config error naming the backends.<name> path, raised while the run
is being wired rather than after jobs start.
kind: local#
Runs jobs as subprocesses on this machine.
Option |
Default |
Meaning |
|---|---|---|
|
a fresh temp dir |
Root for per-job directories: the wrapper script, both captured streams, the exit-code sentinel, the manifest. |
|
|
How many jobs may run at once. |
|
|
Advertise a GPU in this backend’s capabilities. |
Set work_dir explicitly for any real run. The default is a temporary directory,
which is fine for a smoke test but is not somewhere you want to look for a job’s
stderr afterwards.
kind: ssh_exec#
Runs jobs on another machine over SSH, staging files per job.
Option |
Default |
Meaning |
|---|---|---|
|
required |
An |
|
— |
SSH user, when |
|
|
SSH client to invoke. |
|
|
rsync client to invoke. |
|
— |
Override for |
|
|
Root for per-job remote directories, each removed when the job is released. |
|
|
Skip data staging, assuming the remote sees the job’s |
|
|
Submit with |
|
— |
SLURM partition, when |
|
|
How many jobs may be in flight on this host. |
|
|
Advertise a GPU in this backend’s capabilities. |
Two properties of this backend are load-bearing and easy to assume wrongly.
The remote never receives MatMelt’s code: a job ships the stage’s
self-contained runner script plus the files that stage declared, and nothing
else — there is no package to install there and no PYTHONPATH to arrange. And
data moves by declarative staging by default, so nothing depends on the two
machines sharing a filesystem; shared_filesystem: true is an optimisation you
opt into when they genuinely do.
Authentication is your SSH setup’s business, not MatMelt’s: connections run with
BatchMode=yes, so key-based access must already work non-interactively. No
password is ever read, stored, or logged.
stages:#
The funnel stages, each naming its backend.
Key |
Default |
Meaning |
|---|---|---|
|
required |
The name of an entry under |
|
|
Stage-specific settings, validated by that stage’s constructor. |
The order you write stages in is cosmetic — the funnel order is fixed:
compose → structure → screen → [calculate] → extract
You may configure a prefix or an ordered subsequence of it. calculate is
optional and may be skipped; every other stage before the furthest one you
configure must be present, and compose always anchors the funnel. A config
that skips a required stage is refused by name (missing [structure]) before
anything runs.
Every stage takes a python option: the interpreter, on the backend that
stage is wired to, that has its science package installed. This is what
doctor probes — remotely when the stage runs remotely — and what its install
hints target. It is never assumed to be the interpreter running MatMelt itself.
compose#
Turns one element-combination seed into candidate compositions. Fan-out; needs SMACT.
Option |
Default |
Meaning |
|---|---|---|
|
|
Interpreter on this stage’s backend. |
|
|
Maximum stoichiometric coefficient SMACT explores. Higher finds more exotic stoichiometries and costs combinatorially more. |
|
follows |
Cap for single-element seeds, which emit elemental compositions |
structure#
Turns one composition into candidate crystals, one per symmetry group tried. Fan-out; needs PyXtal.
Option |
Default |
Meaning |
|---|---|---|
|
|
Interpreter on this stage’s backend. |
|
|
Which dimensionalities to generate. A bare |
Each dimensionality sweeps its own full symmetry family:
Tag |
Family |
Groups tried |
|---|---|---|
|
space groups |
1–230 |
|
layer groups |
1–80 |
|
rod groups |
1–75 |
So dimensions: [3D, 2D] tries 310 groups per composition, and costs
accordingly. Candidate ids carry the tag — Cl1Na1_3D-225, Cl1Na1_2D-72 — and
so does each candidate’s structure.json, which surfaces in exported records
under _mcli.provenance.dimension. An unknown tag is refused at load time with
the valid set named. Everything downstream treats every candidate as an ordinary
crystal.
screen#
Relaxes one candidate with MACE and keeps or rejects it. This is the funnel’s
first terminal science verdict. Needs mace-torch.
Option |
Default |
Meaning |
|---|---|---|
|
|
Interpreter on this stage’s backend. |
|
|
A local MACE model file. Empty means the MACE-MP foundation model. |
|
|
Foundation-model size when |
|
|
Device passed to MACE: |
|
|
Torch dtype: |
|
|
Force convergence target, eV/Å. |
|
|
Relaxation step cap. |
float64 is the default because a variable-cell relaxation needs it for a
reliable stress tensor; float32 stays available as an explicit speed trade.
Leaving model_path empty means the foundation model is downloaded to MACE’s
cache on first use. That is disclosed before it happens, never silent:
doctor reports the size and cache location, and run asks for consent
(--yes grants it non-interactively). For a first run, model_size: small
(~32 MB) is a better trade than the default.
calculate — optional#
Runs one screened-in material through DFTB+ via your own AiiDA. Needs
aiida-core, aiida-mydftbp and ase in this stage’s python, plus a working
profile, daemon and code.
Option |
Default |
Meaning |
|---|---|---|
|
required |
Label of your configured AiiDA code for DFTB+, e.g. |
|
required |
Absolute directory of the |
|
|
Interpreter holding your AiiDA installation. |
|
default profile |
AiiDA profile to load. |
|
|
Label of the stored Slater–Koster |
|
— |
Local |
|
|
k-mesh diagonal; exactly three integers ≥ 1. |
|
|
Walltime requested per WorkChain attempt. |
|
code’s default |
Scheduler queue for the WorkChain’s jobs. |
|
|
Seconds between WorkChain status polls. |
This stage is not wired to a remote through ssh_exec: remoteness is
AiiDA’s job here — the computer and queue belong to the code you configured, and
MatMelt’s backend only needs to reach the interpreter that talks to your AiiDA
profile.
extract#
Assembles the final OPTIMADE-shaped record.json, folding in the calculate
result when there is one.
Option |
Default |
Meaning |
|---|---|---|
|
|
Any interpreter — this stage’s runner is standard-library only. |
Extract is the one stage with no science dependency at all. That is a
contract, checked by the test suite, not an accident of the current
implementation: doctor always reports it ready.
env:NAME references#
Any string value may be written as env:VARNAME, and the loader substitutes
that environment variable’s value:
backends:
cluster:
kind: ssh_exec
options:
host: env:CLUSTER_HOST
user: env:CLUSTER_USER
This is how a shareable config file stays free of hostnames, usernames and
anything else you would not commit. An unset variable is a config error naming
both the variable and its YAML path — never an empty string silently substituted.
Substitution happens before validation, so lease_timeout_s: env:LEASE works:
the resolved string is coerced into the typed field.
What the loader rejects#
Every model forbids unknown keys, so a typo is an error rather than a setting that silently does nothing. Field-level problems are reported together, one line each, with dotted YAML paths — you fix them in one pass rather than one per run:
$ matmelt doctor
Config error: matmelt.yaml: 2 configuration errors:
- pipeline.retry_budget: Value error, must be a number, not a boolean
- pipeline.stop_afterr: Extra inputs are not permitted
The cross-reference checks run after every field validates, so a broken reference surfaces once the fields are clean, reported against the file as a whole:
$ matmelt doctor
Config error: matmelt.yaml: 1 configuration error:
- <config>: Value error, stages.screen.backend: references undefined backend 'gpubox'; defined backends: ['laptop']
The checks, beyond each field’s own type:
every
stages.<name>.backendnames a defined backend;pipeline.stop_after, when set, names a configured stage;at least one backend and at least one stage exist;
the configured stages form an ordered subsequence of the funnel, with
calculateoptional;each stage’s
optionsare accepted by that stage’s constructor, and each backend’s by its own.
All of these are exit code 2.