Examples

Setting Up an HPC System with alomancy add-hpc

Before writing a run config, register your HPC system with the interactive wizard:

alomancy add-hpc

The wizard configures two files:

  • ~/.expyre/config.json — ExPyRe scheduler config (Slurm headers, partitions, scratch directory)

  • ~/.alomancy/hpc_config.yaml — ALomancy profile (venv path, DFT binary paths, node info)

Prerequisite: Add the HPC to ~/.ssh/config first so it is reachable by alias, e.g.:

Host raven
    HostName raven.mpcdf.mpg.de
    User jholl

Verify with: ssh raven hostname

Example walkthrough

=== ALomancy HPC Setup Wizard ===

Configures two files:
  /home/jholl/.expyre/config.json
  /home/jholl/.alomancy/hpc_config.yaml

Before continuing, make sure this HPC is reachable by SSH alias ...

--- ExPyRe System (scheduler config) ---
System name in ~/.expyre/config.json (e.g. 'raven_gpu'): raven_gpu

Available SSH hosts from ~/.ssh/config:
  1) raven
  2) draco
  3) raccoon
  Enter a number to select, or type a hostname directly.
SSH host: 1                          # picks 'raven'

GPU system? [y/N]: y

Scratch/run directory — ExPyRe will create job subdirectories here.
Use a fast scratch filesystem, not your home directory. All results
are automatically synced back to your local machine after each job.
Scratch directory path on remote, e.g. /ptmp/user/alomancy_scratch: /ptmp/jholl/alomancy_scratch

Module/setup commands — press Enter after each command.
Enter on a blank line to finish.
Examples: 'module purge'  'module load python/3.11'  'export OMP_NUM_THREADS=1'
  > module purge
  > module load cuda/12.2 python/3.11
  > export OMP_NUM_THREADS=1
  >

Partitions (at least one required):
  Partition name (e.g. 'general'): gpubig
  Cores per node for 'gpubig': 18
  Max time for 'gpubig' [24:00:00]:
  Max memory for 'gpubig', e.g. 240GB: 120GB
  Add another partition? [y/N]: n

GPU SBATCH options (press Enter to skip each):
  Constraint string, e.g. gpu:
  Gres string, e.g. gpu:a100:1: gpu:a100:1

--- ALomancy HPC Profile ---
  (This name goes in your run YAML: hpc: '<profile_name>')
Profile name [raven_gpu]:
Venv activation command, e.g. source /u/user/.venvs/alomancy/bin/activate: source /u/jholl/.venvs/alomancy/bin/activate
TRITON_CACHE_DIR path for GPU PyTorch JIT cache, or Enter to skip: /u/jholl/.triton_cache

Which partition(s) will this profile use? (comma-separated) [gpubig]:
Ranks per node (usually = cores per node) [18]:
Max memory per node [120GB]:

Concurrency — how many ALomancy jobs should run on this HPC at once?
ExPyRe/Slurm still queue everything submitted; this caps how many are
started (occupying a queue slot) at the same time — the next queued job
starts the instant a running one finishes.
Number of concurrent jobs you wish to have running on this hpc from alomancy [20]:

DFT code on this system? Options: qe / vasp / none
DFT code [none]:

--- Writing config files ---
  /home/jholl/.expyre/config.json  ← added 'raven_gpu'
  /home/jholl/.alomancy/hpc_config.yaml  ← added 'raven_gpu'

--- Remote Installation ---
Install alomancy on this system now? [y/N]: y
Python executable path on remote [/u/jholl/.venvs/alomancy/bin/python]:
  Running: ssh raven '/u/jholl/.venvs/alomancy/bin/python -m pip install alomancy' …
  Done.

Setup complete! Use 'raven_gpu' in your run YAML:
  training:
    hpc: 'raven_gpu'
  structure_generation:
    hpc: 'raven_gpu'
  high_accuracy_evaluation:
    hpc: 'raven_gpu'

The wizard writes a profile like this to ~/.alomancy/hpc_config.yaml:

raven_gpu:
  hpc_name: raven_gpu
  gpu: true
  pre_cmds:
    - source /u/jholl/.venvs/alomancy/bin/activate
    - export TRITON_CACHE_DIR=/u/jholl/.triton_cache
  partitions:
    - gpubig
  node_info:
    ranks_per_system: 18
    ranks_per_node: 18
    threads_per_rank: 1
    max_mem_per_node: 120GB
  max_num_of_concurrent_jobs: 20

And this entry to ~/.expyre/config.json:

{
  "systems": {
    "raven_gpu": {
      "host": "raven",
      "remsh_cmd": "ssh",
      "scheduler": "slurm",
      "header": [
        "#SBATCH --no-requeue",
        "#SBATCH --nodes={num_nodes}",
        "#SBATCH --cpus-per-task={num_cores}",
        "#SBATCH --gres=gpu:a100:1"
      ],
      "commands": [
        "module purge",
        "module load cuda/12.2 python/3.11",
        "export OMP_NUM_THREADS=1"
      ],
      "partitions": {
        "gpubig": {"num_cores": 18, "max_time": "24:00:00", "max_mem": "120GB"}
      },
      "rundir": "/ptmp/jholl/scratch"
    }
  }
}

Run alomancy add-hpc again to add more profiles (e.g. a separate CPU profile for DFT). Existing entries in both files are preserved.

DFT paths and pseudo_dict: The wizard writes flat keys (pwx_path, pp_path, vasp_path) directly into the profile. The pseudo_dict (element → UPF/POTCAR mapping) must be added manually to ~/.alomancy/hpc_config.yaml after the wizard finishes, because it is per-element and changes between projects.


Basic Usage

Here’s a simple example of running an active learning workflow:

from alomancy import ALomancy

# The config picks everything: the AL skeleton (general.al_workflow) and
# every module (training.trainer, structure_generation.generator,
# high_accuracy_evaluation.evaluator). Every run setting (start_from,
# num_of_al_loops, verbose, ...) lives under the YAML's `general:` section.
ALomancy("standard_config.yaml").run()

Configuration File

The configuration YAML file defines all the stages of the active learning workflow. Here’s a complete example:

general:
  al_workflow: "committee_uncertainty"
  elements: ["H", "O"]   # atomic symbols, not atomic numbers
  num_of_structures_per_loop: 50   # structures selected for DFT each loop
  dataset_kwargs:
    target_config_types:
      - "IsolatedAtom"
      - "init_dimer"
    test_ratio: 0.1
  committee_uncertainty_kwargs:
    num_of_models_in_committee: 5

initialization:
  name: "initialization"
  max_time: "2H"
  mp_kwargs:
    max_num_of_atoms: 20
    mp_max_energy_above_hull: 0.1
  dimer_kwargs:
    num_of_dimers_per_combo: 10
  trimer_kwargs:
    num_of_trimers_per_combo: 5
  amorphous_kwargs:
    num_of_amorphous_structures: 300
    num_of_atoms_per_amorphous: 20
  stretch_compress_targets_kwargs:
    num_stretch_compress_per_mp: 5
  hpc: 'my_hpc'

training:
  name: "training"
  trainer: "mace"
  max_time: "5H"
  hpc: 'my_gpu_hpc'

structure_generation:
  name: "structure_generation"
  generator: "md"   # "md" (default) or "ezga"
  num_of_structures_to_generate: 500   # candidate pool; default 10x per loop
  max_time: "10H"
  hpc: 'my_gpu_hpc'

high_accuracy_evaluation:
  name: "high_accuracy_evaluation"
  evaluator: "qe"   # "qe" (default) or "vasp"
  max_time: "30m"
  hpc: 'my_cpu_hpc'   # concurrency is set on the HPC profile, see max_num_of_concurrent_jobs above

Configuration Key Descriptions

  • general: Settings shared across the whole workflow. al_workflow selects which AL skeleton ALomancy(config) builds ("committee_uncertainty", the default, "random_selection" or "novelty_selection"). elements (atomic symbols, e.g. ["C", "O"]) is the single shared source of element identity. num_of_structures_per_loop (default 50) is how many structures the selector sends to DFT each loop. committee_uncertainty_kwargs holds everything specific to this AL skeleton: num_of_models_in_committee (how many committee members are trained in parallel), target_config_types (which config types count toward the train/test split), and test_ratio (the split between test and training data).

  • initialization: Generates initial training and test sets. Supports Materials Project structures, dimers, trimers, amorphous structures, and stretched/compressed MP structures — each namespaced under its own *_kwargs (mp_kwargs, dimer_kwargs, trimer_kwargs, amorphous_kwargs, stretch_compress_targets_kwargs, isolated_atom_kwargs), each with its own enabled flag (default true).

  • training: Trains an ensemble (committee) of interatomic potentials. trainer selects the registered mlip_trainer backend: "mace" (default) or "sevennet". Backend-specific settings go under mace_kwargs / sevennet_kwargs (SevenNet’s own model/train/data input sections; requires sevenn on the training nodes). Complete, tested configs for each workflow and backend are in examples/configs/.

  • structure_generation: Generates candidate structures for labeling. num_of_structures_to_generate (default 10 × general.num_of_structures_per_loop) is the size of the candidate pool; it must be at least num_of_structures_per_loop (error) and should be at least twice it (warning). generator selects the registered structure_generator backend ("md", the default, or "ezga" for genetic-algorithm search); uncertainty is measured as force standard deviation across the committee. MD parameters (steps, temperature, timestep_fs, friction, ensemble, pressure) go under md_kwargs:

    structure_generation:
      name: "structure_generation"
      generator: "md"
      num_of_structures_to_generate: 500   # candidate pool; default 10x per loop
      max_time: "10H"
      md_kwargs:
        steps: 20000
        temperature: 1200
        timestep_fs: 0.5
        friction: 0.002
        ensemble: "npt"   # "nvt" (default, fixed cell) or "npt" (variable cell)
        pressure: 0.0      # GPa; only used when ensemble is "npt"
      hpc: 'my_gpu_hpc'
    

    ensemble: "nvt" runs fixed-cell Langevin dynamics (the default). ensemble: "npt" runs ASE’s LangevinBAOAB integrator with a barostat targeting pressure (GPa), letting the cell shape and volume fluctuate — useful when candidate structures should sample compressed/expanded states rather than just the seed cell’s fixed volume.

  • high_accuracy_evaluation: Performs high-accuracy DFT evaluation on selected structures. The evaluator key selects the registered dft_evaluator backend: "qe" (Quantum Espresso, default) or "vasp". Submission concurrency (how many jobs run at once) is controlled by max_num_of_concurrent_jobs on the HPC profile (~/.alomancy/hpc_config.yaml, default 20) — a property of the HPC system/account, not a per-workflow-phase setting; see Deprecations for the removed max_batch_size fallback. If QE-specific keys (e.g. pwx_path) appear in a VASP config or vice versa, a warning is logged and the mismatched keys are ignored.

Using VASP as the DFT Backend

To switch from Quantum Espresso to VASP, set evaluator: "vasp" in the high_accuracy_evaluation block and replace the QE-specific HPC keys:

high_accuracy_evaluation:
  name: "high_accuracy_evaluation"
  evaluator: "vasp"
  max_time: "30m"
  vasp_kwargs:                # INCAR overrides (optional)
    encut: 600
    ediff: 1.0e-7
  hpc:
    hpc_name: "raven"
    vasp_path: "/path/to/vasp_std"
    pp_path: "/path/to/potpaw_PBE"  # sets VASP_PP_PATH before each calculation
    pseudo_dict:              # element → POTCAR suffix
      H: ""
      O: "_GW"
    node_info:
      ranks_per_system: 72
      ranks_per_node: 36
      threads_per_rank: 1
      max_mem_per_node: "90G"
    max_num_of_concurrent_jobs: 20   # jobs started at once on this HPC (default 20)
    partitions: ["cpu"]
    pre_cmds: ["module load vasp"]

pp_path and VASP_PP_PATH: ASE’s Vasp calculator locates POTCAR files via the VASP_PP_PATH environment variable, not a constructor argument. create_vasp_calc_object sets os.environ["VASP_PP_PATH"] = hpc["pp_path"] before building the calculator, so pp_path must point at the directory containing potpaw_PBE (or potpaw_LDA), matching what module load vasp would otherwise export. Elements without a pseudo_dict override fall back to the default (unsuffixed) POTCAR under that path — if that directory doesn’t carry a given element (e.g. Pd), VASP fails with No pseudopotential for <element>!.

QE configs work unchanged — evaluator: "qe" is the default and can be omitted.

Adding a New Backend

Users never pick a workflow class in Python — ALomancy(config) builds the skeleton named by general.al_workflow (new skeletons: see writing_a_workflow.md), and each pluggable category (mlip_trainer, structure_generator, dft_evaluator, initialiser) is a module registered against a name in src/alomancy/registry.py, resolved lazily from config at runtime (training.trainer, structure_generation.generator, high_accuracy_evaluation.evaluator).

Adding a new backend means writing a new module that implements the category’s expected entry points — typically output_paths(config, *, base_name, name, ...) and read_existing_result(config, *, base_name, name, ...) for the skeleton’s restart mechanism, plus the category-specific worker function (train, generate, or sp/go) — and registering it in registry.py. See any existing module under mlip/ (e.g. mlip/mace/trainer.py), structure_generation/ (structure_generation/md/md_wfl.py, structure_generation/ezga/generate_structures.py), or high_accuracy_evaluation/dft/ (run_qe.py, run_vasp.py) for the pattern to follow.

Starting From Existing Data

External or earlier datasets are imported with general.start_from instead of being generated from scratch. For example, a single xyz file:

general:
  al_workflow: "committee_uncertainty"
  elements: ["H", "O"]
  start_from:
    xyz:
      - "path/to/external_structures.xyz"
      - "path/to/another_dataset.xyz"
  dataset_kwargs:
    test_ratio: 0.1
    target_config_types: ["liquid"]

Structures don’t need ALomancy’s own keys: config_type, REF_energy and REF_forces are mapped from common equivalents (or named explicitly with start_from.metadata_map). See Starting a run for all start modes and the full rules.

Filtering the Training and Test Sets

Poor-quality DFT structures, such as near-collision MD frames with huge forces, can be kept out of either split without deleting them from the database. general.train_filter and general.test_filter take the same keys:

general:
  train_filter:
    max_force: 100.0                  # eV/Å; the default. null disables it
    formation_energy_per_atom: null   # off by default
  test_filter:                        # off by default
    max_force: 20.0
    formation_energy_per_atom: [null, 1.0]   # eV/atom; null bound = open
  • max_force excludes structures whose largest per-atom force is at or above the value.

  • formation_energy_per_atom keeps only structures inside [min, max] (inclusive), measured against the database’s IsolatedAtom energies: (E − Σ E_isolated) / n_atoms, the same definition as the parity plots. Structures containing an element without an IsolatedAtom energy are not energy-filtered, and a warning says so.

Excluded structures are flagged (is_quality_filtered, with the reason in quality_filter_reasons) and left out of train_set.xyz/test_set.xyz, training and test metrics. The flags are recomputed every loop, so loosening or removing a filter brings them back.

Separately, high_accuracy_evaluation.force_ceiling (default 100.0 eV/Å) controls how far AL-generated structures are relaxed before their DFT labels are kept: each is relaxed until its largest force is at most the ceiling. null means a single-point calculation with no relaxation. Initialization structures are not affected.

MACE Committee Predictions in the GlobalDatabase

After each AL loop’s MACE committee training finishes on the remote GPU node, ALomancy evaluates every committee model on the training and test sets before returning from the remote job and saves the per-structure predictions to train_pred.xyz / test_pred.xyz inside each fit directory. These files are synced back to your local machine by ExPyRe alongside the model files.

The skeleton then reads those files locally and stores the predicted energies and forces in the GlobalDatabase via store_model_predictions — no model loading or local GPU required. Parity plots (plot_dft_vs_model) read from the DB first, then fall back to the eval xyz files, and never run local inference.

Resuming from before v0.4.2: loops trained without the post-training eval step will not have train_pred.xyz / test_pred.xyz. The sentinel mace_predictions.done is written and those loops are skipped gracefully; parity plots show “No predictions available” for them. Only future loops (trained with the updated remote package) will have predictions stored.

The stored metadata keys follow the pattern (generalized to model_* since the trainer backend is no longer assumed to be MACE — see the module registry):

model_energy_loop_{N}_fit_{i}   # predicted energy (eV, raw)
model_forces_loop_{N}_fit_{i}   # predicted forces ([[fx,fy,fz], ...], eV/Å)

You can retrieve predictions programmatically:

from alomancy.database.global_database import GlobalDatabase

db = GlobalDatabase("results/global_database")

# Returns {"train": (e_dft, e_pred, f_dft, f_pred), "test": (...)}
# where each element is a numpy array; e values are per-atom (eV/atom)
preds = db.get_model_predictions(loop_idx=0, fit_idx=0)

e_dft, e_pred, f_dft, f_pred = preds["train"]
print(f"Train energy MAE: {abs(e_dft - e_pred).mean():.4f} eV/atom")

Predictions are guarded by a results/al_loop_{N}/mace_predictions.done sentinel so they are not recomputed on restart. If you need to regenerate predictions (e.g. after installing a different model), delete that sentinel file.