Source code for matmelt.commands.init
"""The ``init`` command: scaffold a starter config, then check the environment (5.1c).
Two halves, both promises from the 2026-07-08 policy: the user gets a
commented, immediately-valid ``matmelt.yaml`` to edit (never
overwriting one that exists -- exit 1, file untouched), and then the doctor
checks run against it so the very first contact with the tool names exactly
what is missing and the exact install command for it.
The scaffold always lands before the checks run: an environment with missing
science packages still gets its config file (that is the normal first-install
experience -- the report tells the user what to do next), and the exit code
is the doctor's own verdict.
"""
from __future__ import annotations
from pathlib import Path
from matmelt.commands.doctor import run_doctor
from matmelt.ui.console import render_error, render_init_created
#: The starter config: valid as written (``doctor`` runs on it immediately),
#: with the knobs a first run needs visible and the remote story sketched in
#: comments. Science interpreters are per-stage options -- the user points
#: each stage at the python that has its package.
TEMPLATE = """\
# MatMelt configuration (scaffolded by `matmelt init`).
# Any string value may be an environment reference: "env:NAME".
run:
name: my-first-run
work_dir: ./materials-work # run workspace: state DB + per-material dirs
elements: [Na, Cl] # the element set the funnel explores
# combination_sizes: [2, 3] # [2] = binaries, [2, 3] adds ternaries, ...;
# # 1 = single elements (elemental solids).
# # Omitted = ALL sizes 1..len(elements).
pipeline:
stop_after: extract # last stage this run executes. A mid-funnel
# # stop is a pause: materials rest at that
# # stage's output, resumable by a later run --
# # only the funnel's last stage retires DONE.
retry_budget: 3 # per-stage fault retries before FAILED
# lease_timeout_s: 900 # orphaned-claim recovery age (seconds)
# pass_interval_s: 1.0 # sleep between orchestrator passes
backends: # the WHERE: machines that run jobs
laptop:
kind: local
options:
work_dir: ./materials-jobs # backend job scratch
max_parallel: 2 # jobs in flight at once
# A remote over SSH (inputs are staged per job; the remote only needs the
# science packages in remote_python's venv -- never this tool):
# cluster:
# kind: ssh_exec
# options:
# host: cluster.example.org
# user: env:CLUSTER_USER
# remote_python: /opt/science-venv/bin/python
# max_parallel: 4
stages: # the WHAT: the funnel, in order
compose: # SMACT charge-balanced compositions
backend: laptop
options:
python: python3 # interpreter with smact installed
threshold: 2 # max SMACT stoichiometric coefficient
# max_unary_atoms: 4 # single-element seeds emit X1..Xn allotrope
# # stoichiometries (default: threshold)
structure: # PyXtal symmetry-constrained candidates
backend: laptop
options: {python: python3} # interpreter with pyxtal installed
screen: # MACE/ASE variable-cell relaxation
backend: laptop
options:
python: python3 # interpreter with mace-torch installed
device: cpu # cpu | cuda
# model_path: /path/to/mace.model # unset = MACE-MP foundation model
# # (offered as a consented download)
# calculate: # OPTIONAL higher-fidelity DFTB+ stage.
# backend: laptop # Runs on YOUR OWN AiiDA: the stage's python
# options: # needs aiida-core + aiida-mydftbp installed,
# # and a running AiiDA profile + daemon.
# python: python3 # interpreter with aiida-core + aiida-mydftbp
# code_label: dftbplus@cluster # REQUIRED: `verdi code list` shows yours
# skf_prefix: /opt/skf/ # REQUIRED: abs dir of .skf files where DFTB+ runs
# # profile: my-profile # AiiDA profile (unset = your default)
# # skf_local_path: ./skf # local .skf dir to create the SKF node from
# # skf_label: ptbp_complete_set # label of the stored SKF FolderData
# # kpoints: [4, 4, 4] # SupercellFolding k-mesh
# # max_wallclock_seconds: 3600
# # queue_name: dftb # scheduler queue/partition
extract: # OPTIMADE-shaped record.json (stdlib only)
backend: laptop
"""
[docs]
def run_init(config_path: str) -> int:
"""Scaffold a starter config, then diagnose it.
Parameters
----------
config_path
Where to write. An existing file is never overwritten.
Returns
-------
int
``1`` when the file exists or cannot be written; otherwise whatever
``doctor`` concludes about the freshly-written config.
Notes
-----
The scaffold lands **before** the checks run, so a user whose environment
is not ready still gets a config to edit rather than nothing at all.
The template is valid as written -- a test wires it through
``load_config`` and ``build_runtime``, so it can never rot into an example
that does not load.
"""
path = Path(config_path)
if path.exists():
render_error(
f"init: {path} already exists; refusing to overwrite it. "
f"Edit it directly, or pass --config to scaffold elsewhere."
)
return 1
try:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(TEMPLATE, encoding="utf-8")
except OSError as exc:
render_error(f"init: cannot write {path}: {exc}")
return 1
render_init_created(str(path))
return run_doctor(config_path)