Workflow Configuration
XtalPaint uses a single configuration object — XtalPaintConfig — to drive both plain-Python and AiiDA-based workflows. This page explains how to build that config, what each field does, and how the same config object is shared between the two execution modes.
Design principles
- Structures are not part of the config. The input structures are passed separately — to the pipeline functions or to
InpaintingWorkGraph.build()— so the same config can be reused across different structure sets. - Presence = enabled,
None= skip — for pipeline stages. Each pipeline stage (candidate_generation,pre_refinement,relaxation) is controlled by its own typed config object; if the field isNonethe stage is omitted. The post-relaxation steps inside the relaxation graph (refinement,uniqueness) are instead toggled with theirinclude_taskflag. - AiiDA options are co-located with their stage. Relaxation AiiDA settings live inside
RelaxationGraphConfig.aiida; pipeline-level AiiDA settings (inpainting, candidate generation, pre-refinement) live inXtalPaintConfig.aiida. - Flat, validated inputs. Pydantic validates every field at construction time so mistakes surface immediately rather than at run time.
Top-level structure
from xtalpaint.inpainting.config_schema import (
XtalPaintConfig,
CandidateGenerationConfig,
InpaintingConfig,
RefinementConfig,
InpaintingRelaxationConfig,
RelaxationGraphConfig,
RelaxationParams,
UniquenessConfig,
RelaxationAiiDAOptions,
AiiDAOptions,
AiiDATaskOptions,
)
config = XtalPaintConfig(
run_inpainting=True, # set False to skip candidate generation + diffusion
candidate_generation=..., # CandidateGenerationConfig | None
pre_refinement=..., # RefinementConfig | None
inpainting=..., # InpaintingConfig (always required)
relaxation=..., # InpaintingRelaxationConfig | None
aiida=..., # AiiDAOptions | None (ignored outside AiiDA)
)
The pipeline runs in this order when a stage is enabled:
candidate_generation → inpainting → pre_refinement → relaxation
├─ constrained pass
├─ full pass (on constrained output)
└─ full_direct pass (on inpainted directly)
each pass: → [refinement] → [uniqueness]
Stage reference
Input structures
Structures are passed alongside the config, not inside it:
from pymatgen.core import Structure
from xtalpaint.data import BatchedStructures
structures = BatchedStructures(
{"host_001": Structure(...), "host_002": Structure(...)}
)
# Plain Python
results = run_inpainting_pipeline(structures=structures, config=config.inpainting)
# AiiDA
wg = InpaintingWorkGraph.build(structures=structures, inputs=config)
Accepted structure inputs:
dict[str, Structure]— plain pymatgen structuresBatchedStructures— XtalPaint's batched wrapper- AiiDA
StructureData/InpaintingStructureData— when running inside AiiDA
Candidate generation
Required when the input structures are plain Structure objects (not yet marked as inpainting targets). Omit this block if your structures are already InpaintingStructureData instances.
candidate_generation=CandidateGenerationConfig(
n_inp=2, # int or dict[str, int] — number of sites to inpaint
element="H", # element to place; dict[str, str] for per-structure control
num_samples=1, # how many candidate sets to generate
)
For per-structure control over the number of sites and element:
candidate_generation=CandidateGenerationConfig(
n_inp={"host_001": 2, "host_002": 4},
element={"host_001": "H", "host_002": "Li"},
)
Inpainting
The core diffusion stage. All sampling parameters live in one flat block.
inpainting=InpaintingConfig(
# Model — provide exactly one of these:
pretrained_name="TD-pos-only", # XtalPaint model (auto-downloaded) or a MatterGen checkpoint
# model_path="/path/to/model_dir", # or point to a local checkpoint directory
# Sampling
predictor_corrector="TD", # see supported combinations below
N_steps=5,
coordinates_snr=0.2,
n_corrector_steps=1,
batch_size=1000,
# Optional
fix_cell=True, # keep unit cell fixed during sampling
record_trajectories=False,
sampling_config_path=None, # override MatterGen sampling config dir
)
Recommended model: TD-pos-only
pretrained_name accepts the XtalPaint models hosted on
Hugging Face (TD-pos-only,
pos-only) as well as MatterGen's own checkpoints. The XtalPaint models are
downloaded automatically and cached the first time they are selected. For
accurate inpainting we recommend the TD-pos-only model — the core
model of XtalPaint — with the time-dependent (TD) predictor-corrector.
Alternatively, download a checkpoint explicitly and pass it as model_path:
from xtalpaint.models import download_pretrained_model
model_path = download_pretrained_model("TD-pos-only")
Selecting predictor_corrector="TD" without the TD-pos-only model (or
pointing model_path at a checkpoint that has not been downloaded) raises
an error with these instructions.
Supported predictor_corrector values — the TD variant requires the TD-pos-only model; the others are used with the pos-only model or a MatterGen checkpoint:
| Key | Description |
|---|---|
baseline |
Standard guided predictor-corrector |
baseline-with-noise |
Custom variant with additional noise |
baseline-store-scores |
Records score function outputs |
repaint-v1 |
RePaint resampling (legacy) |
repaint-v2 |
RePaint resampling (v2) |
TD |
Time-dependent (TD-Paint) variant — requires the TD-pos-only model |
Repaint variants
When using repaint-v1 or repaint-v2, you must also set n_resample_steps and jump_length:
Pre-refinement
Optional symmetry refinement applied after inpainting and before relaxation.
pre_refinement=RefinementConfig(
symprec=0.01, # symmetry precision for SpacegroupAnalyzer
primitive=False, # if True, convert to primitive cell
)
Omit pre_refinement (or set it to None) to skip this step. Unlike the post-relaxation refinement below, this stage is enabled purely by presence — its include_task flag is ignored.
Relaxation
The relaxation stage is split into three layers:
RelaxationParams— the inputs forwarded directly torelax_structures()(MLIP, optimiser, convergence)RelaxationGraphConfig— one relaxation pass:paramsplus the optional post-relaxation steps (refinement,uniqueness) and the AiiDA options (aiida)InpaintingRelaxationConfig— workflow-level orchestration: which passes to run (constrained/full/full_direct), wrapping a sharedrelax_config
Relaxation passes
Three passes can be run independently or in combination:
| Flag | Behaviour | WorkGraph label |
|---|---|---|
constrained=True (default) |
Relax only elements_to_relax |
inpainted_constrained_relaxation |
full=True |
Full relax on the constrained output | pre_relaxed_inpainted_full_relaxation |
full_direct=True |
Full relax directly on inpainted structures | unrelaxed_inpainted_full_relaxation |
full and full_direct together give a direct comparison between relaxing from the raw inpainted geometry versus relaxing from an already-constrained geometry. All passes share the same relax_config; for the full passes, elements_to_relax is dropped automatically.
Post-relaxation steps
refinement and uniqueness run after each active pass, in order: relax → refine → deduplicate. Both are off by default — enable them with include_task=True.
relaxation=InpaintingRelaxationConfig(
# Which passes to run:
constrained=True, # relax only elements_to_relax
full=True, # then do a full relax on that output
full_direct=False, # skip direct full relax
# Shared single-pass configuration:
relax_config=RelaxationGraphConfig(
params=RelaxationParams(
mlip="mattersim",
optimizer="BFGS",
load_path="MatterSim-v1.0.0-1M", # MLIP checkpoint
fmax=0.05,
max_n_steps=500,
elements_to_relax=["H"], # required when constrained=True
return_initial_energies=False,
return_final_forces=False,
),
# Post-relaxation processing (applied to each pass):
refinement=RefinementConfig(include_task=True, symprec=0.01),
uniqueness=UniquenessConfig(
include_task=True,
symprec=0.01,
ltol=0.2,
stol=0.3,
angle_tol=5.0,
),
# AiiDA options (required field; pass None outside AiiDA):
aiida=RelaxationAiiDAOptions(relax_code_label="xtalpaint@hpc"),
),
)
Constraints for constrained
constrained=True requires relax_config.params.elements_to_relax to be set.
full=True requires constrained=True (the full-relax pass operates on the constrained output).
At least one of the three pass flags must be True.
Running without AiiDA
Without AiiDA, pass the inpainting config directly to the pipeline functions, together with the structures. The aiida block is simply not set (and relax_config.aiida is passed as None).
from xtalpaint.inpainting.config_schema import XtalPaintConfig, InpaintingConfig
from xtalpaint.inpainting.inpainting_process import run_inpainting_pipeline
from xtalpaint.utils.relaxation_utils import relax_structures
config = XtalPaintConfig(
candidate_generation=CandidateGenerationConfig(n_inp=2, element="H"),
inpainting=InpaintingConfig(
pretrained_name="TD-pos-only", # auto-downloaded from Hugging Face
predictor_corrector="TD",
N_steps=5,
coordinates_snr=0.2,
n_corrector_steps=1,
batch_size=1000,
sampling_config_path="/path/to/sampling_conf",
),
)
# Run inpainting
results = run_inpainting_pipeline(
structures={"host_001": structure},
config=config.inpainting, # pass InpaintingConfig directly
)
inpainted = results["structures"]
# Optional relaxation, reusing the relaxation params from the config
if config.relaxation is not None:
relaxed_structures, final_energies, *_ = relax_structures(
list(inpainted.values()),
**config.relaxation.relax_config.params.model_dump(),
)
Tip
InpaintingConfig.model_dump(exclude_none=True) produces a plain dict that the pipeline functions also accept, which is convenient when serialising configs to JSON/YAML.
Running with AiiDA
Add the aiida block to XtalPaintConfig for pipeline-level tasks (inpainting, candidate generation, pre-refinement). Relaxation AiiDA options live inside relaxation.relax_config.aiida — this keeps all relaxation settings in one place.
from xtalpaint.inpainting.config_schema import (
AiiDAOptions, AiiDATaskOptions, RelaxationAiiDAOptions,
)
from xtalpaint.aiida.workgraphs.inpainting import InpaintingWorkGraph
config = XtalPaintConfig(
candidate_generation=...,
inpainting=...,
relaxation=InpaintingRelaxationConfig(
constrained=True,
full=True,
relax_config=RelaxationGraphConfig(
params=RelaxationParams(...),
refinement=RefinementConfig(include_task=True),
uniqueness=UniquenessConfig(include_task=True),
# AiiDA options for relaxation tasks live here:
aiida=RelaxationAiiDAOptions(
relax_code_label="xtalpaint@hpc",
relax_options=AiiDATaskOptions(
resources={"num_machines": 2, "num_mpiprocs_per_machine": 8},
withmpi=True,
),
),
),
),
# Pipeline-level AiiDA options (inpainting, candidate gen, pre-refinement):
aiida=AiiDAOptions(
default_code_label="xtalpaint@localhost",
inpainting_options=AiiDATaskOptions(
resources={"num_machines": 1, "num_mpiprocs_per_machine": 4},
max_wallclock_seconds=3600,
withmpi=True,
),
),
)
# Build and submit the WorkGraph
wg = InpaintingWorkGraph.build(structures=structures, inputs=config)
wg.submit()
Code label resolution
- Pipeline tasks (inpainting, candidate generation, pre-refinement): resolved from
XtalPaintConfig.aiida— task-specific label, thendefault_code_labelas fallback. - Relaxation tasks: resolved from
relax_config.aiidawith no fallback between tasks —relax_code_labelis required, andrefinement_code_label/uniqueness_code_labelmust be set explicitly when the correspondinginclude_taskflag is enabled.
AiiDATaskOptions fields
| Field | Type | Default | Description |
|---|---|---|---|
resources |
dict |
{} |
AiiDA scheduler resource dict |
max_wallclock_seconds |
int (optional) |
— | Wall-clock limit |
queue_name |
str (optional) |
— | Scheduler queue/partition |
withmpi |
bool |
False |
Enable MPI-parallel execution |
RelaxationAiiDAOptions fields
| Field | Type | Default | Description |
|---|---|---|---|
relax_code_label |
str |
required | Code label for the relaxation task |
refinement_code_label |
str \| None |
None |
Code label for post-refinement |
uniqueness_code_label |
str \| None |
None |
Code label for the uniqueness filter |
relax_options |
AiiDATaskOptions |
{resources: {}, withmpi: False} |
Scheduler options for the relaxation task |
refinement_options |
AiiDATaskOptions |
{resources: {}, withmpi: False} |
Scheduler options for post-refinement |
uniqueness_options |
AiiDATaskOptions |
{resources: {}, withmpi: False} |
Scheduler options for the uniqueness filter |
Full examples
from pymatgen.core import Structure
from xtalpaint.inpainting.config_schema import (
XtalPaintConfig,
CandidateGenerationConfig,
InpaintingConfig,
RefinementConfig,
InpaintingRelaxationConfig,
RelaxationGraphConfig,
RelaxationParams,
UniquenessConfig,
)
from xtalpaint.inpainting.inpainting_process import run_inpainting_pipeline
structure = Structure.from_file("host.cif")
config = XtalPaintConfig(
candidate_generation=CandidateGenerationConfig(
n_inp=2,
element="H",
),
inpainting=InpaintingConfig(
pretrained_name="TD-pos-only",
predictor_corrector="TD",
N_steps=5,
coordinates_snr=0.2,
n_corrector_steps=1,
batch_size=1000,
),
pre_refinement=RefinementConfig(symprec=0.01),
relaxation=InpaintingRelaxationConfig(
constrained=True,
relax_config=RelaxationGraphConfig(
params=RelaxationParams(
mlip="mattersim",
optimizer="BFGS",
load_path="MatterSim-v1.0.0-1M",
elements_to_relax=["H"],
fmax=0.05,
),
refinement=RefinementConfig(include_task=True, symprec=0.01),
uniqueness=UniquenessConfig(include_task=True),
aiida=None, # plain Python execution
),
),
# no aiida block → plain Python execution
)
results = run_inpainting_pipeline(
structures={"host": structure},
config=config.inpainting,
)
print(results["structures"])
from pymatgen.core import Structure
from xtalpaint.inpainting.config_schema import (
XtalPaintConfig,
CandidateGenerationConfig,
InpaintingConfig,
RefinementConfig,
InpaintingRelaxationConfig,
RelaxationGraphConfig,
RelaxationParams,
UniquenessConfig,
RelaxationAiiDAOptions,
AiiDAOptions,
AiiDATaskOptions,
)
from xtalpaint.aiida.workgraphs.inpainting import InpaintingWorkGraph
from xtalpaint.data import BatchedStructures
structure = Structure.from_file("host.cif")
config = XtalPaintConfig(
candidate_generation=CandidateGenerationConfig(
n_inp=2,
element="H",
),
inpainting=InpaintingConfig(
pretrained_name="TD-pos-only",
predictor_corrector="TD",
N_steps=5,
coordinates_snr=0.2,
n_corrector_steps=1,
batch_size=1000,
),
pre_refinement=RefinementConfig(symprec=0.01),
relaxation=InpaintingRelaxationConfig(
constrained=True,
relax_config=RelaxationGraphConfig(
params=RelaxationParams(
mlip="mattersim",
optimizer="BFGS",
load_path="MatterSim-v1.0.0-1M",
elements_to_relax=["H"],
fmax=0.05,
),
refinement=RefinementConfig(include_task=True, symprec=0.01),
uniqueness=UniquenessConfig(include_task=True),
aiida=RelaxationAiiDAOptions(
relax_code_label="xtalpaint@hpc",
refinement_code_label="xtalpaint@hpc",
uniqueness_code_label="xtalpaint@hpc",
relax_options=AiiDATaskOptions(
resources={"num_machines": 2, "num_mpiprocs_per_machine": 8},
withmpi=True,
),
),
),
),
aiida=AiiDAOptions(
default_code_label="xtalpaint@localhost",
inpainting_options=AiiDATaskOptions(
resources={"num_machines": 1, "num_mpiprocs_per_machine": 4},
withmpi=True,
),
),
)
wg = InpaintingWorkGraph.build(
structures=BatchedStructures({"host": structure}),
inputs=config,
)
wg.submit()
The two snippets are identical except for the aiida= blocks and the entry point. Develop and test workflows locally (without AiiDA) and then promote them to a remote HPC environment by adding the AiiDA blocks — no other changes needed.
Configuration reference summary
| Class | Required fields | Purpose |
|---|---|---|
XtalPaintConfig |
inpainting |
Top-level workflow config |
CandidateGenerationConfig |
n_inp, element |
Generate inpainting masks |
InpaintingConfig |
predictor_corrector, N_steps, coordinates_snr, n_corrector_steps, batch_size, one of pretrained_name/model_path |
Diffusion sampling |
RefinementConfig |
— | Symmetry refinement; toggled with include_task inside RelaxationGraphConfig |
RelaxationGraphConfig |
params, aiida (may be None) |
Single-pass input for relaxation_graph |
InpaintingRelaxationConfig |
relax_config |
Multi-pass relaxation stage (wraps a RelaxationGraphConfig) |
RelaxationParams |
mlip, optimizer, load_path |
Inputs forwarded to relax_structures() |
UniquenessConfig |
— | Deduplication tolerances; toggled with include_task |
RelaxationAiiDAOptions |
relax_code_label |
Code labels + scheduler options for relaxation tasks |
AiiDAOptions |
— | Code labels + scheduler options for pipeline tasks |
AiiDATaskOptions |
— | Resources, wall-clock, MPI flag |
Using relaxation_graph directly
relaxation_graph accepts RelaxationGraphConfig directly, so you can call it outside the inpainting WorkGraph without needing the full InpaintingRelaxationConfig:
from xtalpaint.aiida.workgraphs.relaxation import relaxation_graph
from xtalpaint.inpainting.config_schema import (
RelaxationGraphConfig, RelaxationParams, RefinementConfig,
UniquenessConfig, RelaxationAiiDAOptions, AiiDATaskOptions,
)
relax_cfg = RelaxationGraphConfig(
params=RelaxationParams(
mlip="mattersim",
optimizer="BFGS",
load_path="MatterSim-v1.0.0-1M",
elements_to_relax=["H"], # omit (or set to None) for a full relaxation
),
refinement=RefinementConfig(include_task=True, symprec=0.01),
uniqueness=UniquenessConfig(include_task=True),
aiida=RelaxationAiiDAOptions(
relax_code_label="xtalpaint@hpc",
relax_options=AiiDATaskOptions(withmpi=True, resources={"num_machines": 1}),
),
)
wg = relaxation_graph.build(
structures=my_structures,
relax_config=relax_cfg,
)
wg.submit()
There is no separate constrained switch: the relaxation is constrained whenever params.elements_to_relax is set, and a full relaxation otherwise. To reuse the relaxation settings from an InpaintingRelaxationConfig, pass its relax_config attribute.