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

name

required

A short label, shown by status and results.

work_dir

required

Root of the workspace: the state database and every per-material directory live under it.

elements

required

The element set compose enumerates over. At least one, none empty.

combination_sizes

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

stop_after

null

Name of the last stage this run executes.

retry_budget

3

Per-stage fault retries before a material goes FAILED. Minimum 1.

lease_timeout_s

900.0

Age at which a claim counts as orphaned and returns to its queue. Must be > 0.

pass_interval_s

1.0

Sleep between orchestrator passes. May be 0.

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

kind

required

Which implementation: local, ssh_exec, or another registered kind.

options

{}

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

work_dir

a fresh temp dir

Root for per-job directories: the wrapper script, both captured streams, the exit-code sentinel, the manifest.

max_parallel

1

How many jobs may run at once.

gpu

false

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

host

required

An ~/.ssh/config alias, a hostname, or user@host.

user

—

SSH user, when host carries none.

ssh_binary

ssh

SSH client to invoke.

rsync_binary

rsync

rsync client to invoke.

remote_python

—

Override for argv[0] remotely. Normally unnecessary — the stage’s own python option already names the remote interpreter — but this wins when both are set.

remote_work_dir

/tmp/matmelt-jobs

Root for per-job remote directories, each removed when the job is released.

shared_filesystem

false

Skip data staging, assuming the remote sees the job’s cwd at the same absolute path.

use_slurm

false

Submit with sbatch and track the SLURM job id instead of a PID.

partition

—

SLURM partition, when use_slurm is on.

max_parallel

1

How many jobs may be in flight on this host.

gpu

false

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

backend

required

The name of an entry under backends:.

options

{}

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

python

python3

Interpreter on this stage’s backend.

threshold

2

Maximum stoichiometric coefficient SMACT explores. Higher finds more exotic stoichiometries and costs combinatorially more.

max_unary_atoms

follows threshold

Cap for single-element seeds, which emit elemental compositions X1..Xn. Set it separately to reach, say, C60 without enlarging the multi-element search.

structure#

Turns one composition into candidate crystals, one per symmetry group tried. Fan-out; needs PyXtal.

Option

Default

Meaning

python

python3

Interpreter on this stage’s backend.

dimensions

[3D]

Which dimensionalities to generate. A bare 3D works as well as a list.

Each dimensionality sweeps its own full symmetry family:

Tag

Family

Groups tried

3D

space groups

1–230

2D

layer groups

1–80

1D

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

python

python3

Interpreter on this stage’s backend.

model_path

""

A local MACE model file. Empty means the MACE-MP foundation model.

model_size

medium

Foundation-model size when model_path is empty: small, medium or large.

device

cpu

Device passed to MACE: cpu, cuda, mps or auto.

dtype

float64

Torch dtype: float64 or float32.

fmax

0.05

Force convergence target, eV/Å.

max_steps

200

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

code_label

required

Label of your configured AiiDA code for DFTB+, e.g. dftbplus@cluster. verdi code list shows them.

skf_prefix

required

Absolute directory of the .skf files on the machine where DFTB+ runs, baked into the HSD input.

python

python3

Interpreter holding your AiiDA installation.

profile

default profile

AiiDA profile to load.

skf_label

ptbp_complete_set

Label of the stored Slater–Koster FolderData node.

skf_local_path

—

Local .skf directory used to create that node when no node with skf_label exists. Without it, a missing node is a loud, actionable failure rather than a silent stall.

kpoints

[4, 4, 4]

k-mesh diagonal; exactly three integers ≥ 1.

max_wallclock_seconds

3600

Walltime requested per WorkChain attempt.

queue_name

code’s default

Scheduler queue for the WorkChain’s jobs.

poll_interval_s

30.0

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

python

python3

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>.backend names 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 calculate optional;

  • each stage’s options are accepted by that stage’s constructor, and each backend’s by its own.

All of these are exit code 2.