Skip to content

Repository files navigation

AmorphGen

CI Docs License: MIT PyPI

AmorphGen: Amorphous structure generation via melt-quench MD and random placement.

Documentation: smtg-bham.github.io/AmorphGen


Pipeline overview

AmorphGen exposes three workflows. Pick the one that matches your starting point:

# Workflow CLI flag Starting point
1 Random generation --random-gen Composition only
2 Melt-quench (MQ) (default) or --mq-ensemble Crystalline input
3 Hybrid --hybrid-ensemble Directory of disordered structures

1. Random generation (--random-gen)

Composition  (e.g. "In2O3*16"  or  In=32,O=48)
                           │
   ┌───────────────────────▼────────────────────────┐
   │     Auto-derive  minsep, density, target CN    │
   └───────────────────────┬────────────────────────┘
   ┌───────────────────────▼────────────────────────┐
   │                   Random atoms                 │
   └───────────────────────┬────────────────────────┘
   ┌───────────────────────▼────────────────────────┐
   │          Optional relax  (--relax)             │
   └───────────────────────┬────────────────────────┘
       N amorphous structures  (.xyz / .vasp / .cif)

2. Melt-quench (MQ)

Crystalline input  (POSCAR / .xyz / .cif / .extxyz)
         │
   ┌─────▼──────────────────────────────────────────┐
   │  Stage 1  Structure optimisation               │
   │           optimizer + cell_filter              │
   └─────┬──────────────────────────────────────────┘
         │
   ┌─────▼──────────────────────────────────────────┐
   │  Stage 2  Pre-melt equilibration at T-low      │
   │           NVT/NPT                              │
   └─────┬──────────────────────────────────────────┘
         │
   ┌─────▼──────────────────────────────────────────┐
   │  Stage 3  Melt  –  NPT/NVT heat ramp           │
   │           T-low → T_melt                       │
   └─────┬──────────────────────────────────────────┘
         │
   ┌─────▼──────────────────────────────────────────┐
   │  Stage 4  High-T equilibration   T_melt        │
   │           NVT/NPT                              │
   └─────┬──────────────────────────────────────────┘
         │
   ┌─────▼──────────────────────────────────────────┐
   │  Stage 5  Quench  –  NVT cooling ramp          │
   │           T_melt → T-low                       │
   └─────┬──────────────────────────────────────────┘
         │
   ┌─────▼──────────────────────────────────────────┐
   │  Stage 6  Low-T equilibration   T-low          │
   │           NVT/NPT                              │
   └─────┬──────────────────────────────────────────┘
         │
   ┌─────▼──────────────────────────────────────────┐
   │  Stage 7  Final optimisation (amorphous)       │
   │           optimizer + cell_filter              │
   └─────┬──────────────────────────────────────────┘
         │
   stage7_opt.cif  +  stage7_opt.xyz

--mq-ensemble extends MQ: stages 1–4 run once, then N independent quenches (stages 5–6–7) are launched from snapshots of the stage-4 trajectory.

3. Hybrid: random → MQ stages 4-7 (--hybrid-ensemble)

Directory of disordered structures  (e.g. --random-gen outputs)
         │
   ┌─────▼──────────────────────────────────────────┐
   │  Stage 4  High-T equilibration   T_melt        │
   │           NVT/NPT                              │
   └─────┬──────────────────────────────────────────┘
         │
   ┌─────▼──────────────────────────────────────────┐
   │  Stage 5  Quench  –  NVT cooling ramp          │
   │           T_melt → T-low                       │
   └─────┬──────────────────────────────────────────┘
         │
   ┌─────▼──────────────────────────────────────────┐
   │  Stage 6  Low-T equilibration   T-low          │
   └─────┬──────────────────────────────────────────┘
         │
   ┌─────▼──────────────────────────────────────────┐
   │  Stage 7  Final optimisation (amorphous)       │
   └─────┬──────────────────────────────────────────┘
         │
   N amorphous structures  (one per input)

Hybrid is cheaper than full MQ: it skips the slow heat ramp (Stage 3) by starting from a disordered structure.


Supported backends

AmorphGen supports multiple calculator backends:

Backend Install Model name(s)
MACE pip install amorphgen[mace] mace-mpa-0, mace-mpa-0-medium, mace-omat-0-medium, ... (20+ variants)
CHGNet pip install amorphgen[chgnet] chgnet
SevenNet pip install amorphgen[sevennet] sevennet, 7net-mf-ompa, 7net-l3i5, 7net-omat, 7net-0, ...
Classical built-in (no extra install) lennard-jones, buckingham

Only install the backend(s) you need. Classical potentials (Lennard-Jones, Buckingham+Coulomb) are built-in and require no GPU. Use amorphgen --list-models to see all available models.

For ensembles on a GPU there is a second execution engine, torch-sim, selected with --engine torchsim. It relaxes, and in the hybrid workflow anneals and quenches, all structures of an ensemble in one batched call instead of one after another. It works with MACE, SevenNet and Lennard-Jones (CHGNet and Buckingham stay on the ASE engine), needs Python 3.12 and the [torchsim] extra, and writes the same files as the ASE engine. See the backends guide for details.

ASE pass-through. AmorphGen wraps each backend's upstream ASE calculator without modifying unit conventions, stress signs, or PBC handling; this energies (eV), forces (eV/Å), stress (eV/ų), and atoms.pbc are inherited directly from the upstream MLIP package. See docs/guides/backends for details.


Installation

Tasks Install Size
generate random structures, analyse trajectories (RDF, CN, S(q), plots), run classical LJ/Buckingham pipelines pip install -e . ~80 MB, no PyTorch
+ MLIP relaxation & melt-quench MD pip install -e ".[mace]" or ".[chgnet]" + PyTorch
+ everything (MACE + CHGNet) pip install -e ".[all]" + PyTorch
+ batched GPU relaxation and MD of ensembles (--engine torchsim) pip install -e ".[mace,torchsim]" (Python 3.12+, CUDA or CPU, no Apple MPS) + torch-sim

With pip, from source (once AmorphGen is on PyPI, pip install "amorphgen[mace,chgnet]" replaces the clone):

git clone https://github.com/SMTG-Bham/AmorphGen.git
cd AmorphGen
pip install -e ".[mace,chgnet]"      # example: MACE + CHGNet
pip install -e ".[all,dev]"          # everything + pytest

With conda, which keeps the install isolated and on HPC provides the CUDA toolchain. Create the environment with conda, then install into it with pip:

conda create -n amorphgen python=3.11
conda activate amorphgen

# from source (current):
git clone https://github.com/SMTG-Bham/AmorphGen.git
cd AmorphGen
pip install -e ".[mace,chgnet]"

# or, once released on PyPI:
pip install "amorphgen[mace,chgnet]"

MLIPs are optional. The base package is deliberately torch-free. Install an MLIP extra only when you need MACE/CHGNet/SevenNet relaxation or melt-quench MD; with no torch present, --device auto resolves to CPU, and calculator-requiring commands fail fast with the exact install line. amorphgen --list-models shows every model with installed/missing markers.

SevenNet needs its own environment. SevenNet depends on e3nn>=0.5, while MACE foundation-model files (mace-mpa-0, ...) were pickled with e3nn==0.4.x and fail to load against the newer e3nn. The [all] extra therefore intentionally excludes SevenNet. To use SevenNet, create a separate conda env:

conda create -n amorphgen-sevennet python=3.11
conda activate amorphgen-sevennet
pip install -e ".[sevennet,chgnet]"

The [full] extra installs MACE+CHGNet+SevenNet in one env but loading MACE foundation models will then fail unless you upgrade mace-torch to a release that supports e3nn 0.5+.

GPU strongly recommended. Use --device cuda or "device": "auto". Device auto-detection only runs when a job starts. On a login node with no GPU, no device message will appear until a stage is launched.


Quick start

Command line

# -- Random generation (no crystal input needed) --
# Generate 10 random In2O3 structures (80 atoms each) and relax with MACE
amorphgen --random-gen --composition "In2O3*16" --relax --device cpu

# Same thing with explicit atom counts
amorphgen --random-gen --composition In=32,O=48 --relax --device cpu

# -- Melt-quench pipeline (from crystalline input) --
# Full 7-stage pipeline with MACE (default)
amorphgen POSCAR --device cuda

# Use CHGNet 
amorphgen POSCAR --model chgnet --device cpu

# List all available models
amorphgen --list-models

# -- Ensembles on a GPU with the torch-sim engine (pip install -e ".[mace,torchsim]") --
# Generate 50 structures and relax them all in one batched call
amorphgen --random-gen --composition "GeO2*192" -n 50 --relax \
    --model mace-mpa-0 --device cuda --engine torchsim -o geo2_seeds/

# Anneal, quench and relax the whole ensemble together (stages 4-7, NVT)
amorphgen --hybrid-ensemble --input-dir geo2_seeds/random_opt/ \
    --config hybrid.yaml --model mace-mpa-0 --device cuda --engine torchsim \
    -o geo2_hybrid/ --resume

--composition accepts two formats:

  • Formula: In2O3*16 (16 formula units = 80 atoms)
  • Atom counts: In=32,O=48 (explicit)

Typical sizes: 40-100 atoms for random generation, 100-500 for melt-quench.

Python API

from amorphgen import MeltQuenchPipeline

# MACE (default)
pipe = MeltQuenchPipeline(
    input_file="POSCAR",
    work_dir="InO_run",
    cfg_override={
        "model":  "mace-mpa-0",
        "device": "cuda",
    },
)
atoms = pipe.run()                  # all 7 stages

# CHGNet
pipe = MeltQuenchPipeline(
    input_file="POSCAR",
    cfg_override={"model": "chgnet"},
)

# SevenNet
pipe = MeltQuenchPipeline(
    input_file="POSCAR",
    cfg_override={"model": "7net-mf-ompa"},
)

# Custom fine-tuned MACE model
pipe = MeltQuenchPipeline(
    input_file="POSCAR",
    cfg_override={"model_path": "/data/models/InO_finetuned.model"},
)

# Run specific stages
pipe.run(stages=[5, 6, 7], input_file="stage4_eq_high.xyz")

YAML configuration

Add seed: 42 at the top level (or --seed 42 on the command line) to make a run reproducible: it seeds the random placement and the velocity initialisation and thermostat noise of every MD stage. Same seed, same CPU and same package versions give bit-identical output; on a GPU, MLIP forces are not bit-reproducible and trajectories diverge after a few thousand steps.

Instead of passing many CLI flags, you can define settings in a YAML file:

# config.yaml
model: mace-mpa-0
device: cuda
default_dtype: float64

opt:
  fmax: 0.01
  max_steps: 1000
  optimizer: LBFGS

melt:
  T_start: 300
  T_end: 3000
  T_step: 100

quench:
  T_start: 3000
  T_end: 300
  T_step: -100
  steps_per_T: 2000
# Use YAML config
amorphgen POSCAR --config config.yaml

# CLI args override YAML values
amorphgen POSCAR --config config.yaml --fmax 0.05 --device cpu
from amorphgen.configs import load_yaml_config
from amorphgen import MeltQuenchPipeline

cfg = load_yaml_config("config.yaml")
pipe = MeltQuenchPipeline(input_file="POSCAR", cfg_override=cfg)
atoms = pipe.run()

Precedence: CLI arguments > YAML config > built-in defaults.

YAML also supports random generation settings:

# random_gen_config.yaml
model: chgnet
device: cpu
default_dtype: float32

opt:
  fmax: 0.05
  max_steps: 500
  cell_filter: cubic

random_gen:
  composition:
    Si: 16
    O: 32
  n_structures: 5
  target_density: 2.2
  target_cn:
    Si: 4
    O: 2
  output_format: vasp
amorphgen --random-gen --config random_gen_config.yaml --work-dir SiO2_sc
amorphgen --batch-opt --input-dir SiO2_sc --work-dir SiO2_sc_opt --config random_gen_config.yaml

See amorphgen/configs/example_config.yaml for all available options.


Random structure generation

Generate random amorphous starting structures:

# Generate 20 random In₂O₃ structures (80 atoms each)
amorphgen --random-gen \
    --composition "In2O3*16" \
    --n-structures 20 \
    --work-dir random_structures/

# Same with explicit atom counts and target density
amorphgen --random-gen \
    --composition In=32,O=48 \
    --target-density 5.5 \
    --n-structures 20

# Generate with relaxation
amorphgen --random-gen \
    --composition "TiO2*16" \
    --n-structures 10 \
    --relax --model mace-mpa-0

# Resume after interruption (skips completed structures)
amorphgen --random-gen \
    --composition "Ga2O3*80" -n 20 \
    --relax --device cuda --format vasp --resume
from amorphgen import generate_random, batch_random

# Single structure
atoms = generate_random({"In": 16, "O": 24})

# Batch generation
batch_random(
    composition={"In": 32, "O": 48},   # atom counts (Python API always uses dict)
    n_structures=20,
    output_dir="random_structures",
)

Two-step workflow: generate then optimise separately

You can decouple generation and optimisation into separate steps. This gives more control over optimisation settings (optimizer, cell filter, precision, convergence) and lets you inspect structures before committing to expensive relaxation.

Step 1: Generate (default, no relaxation):

amorphgen --random-gen \
    --composition Ga=16,O=24 \
    --n-structures 5 \
    --work-dir random_Ga2O3
from amorphgen.pipeline.random_gen import batch_random

paths = batch_random(
    composition={"Ga": 16, "O": 24},
    n_structures=5,
    output_dir="random_Ga2O3",
    relax=False,
    seed=42,
)

Step 2: Batch optimise:

amorphgen --batch-opt \
    --input-dir random_Ga2O3 \
    --work-dir random_Ga2O3_opt \
    --model mace-mpa-0 --device cpu --fmax 0.01
from amorphgen.pipeline.opt_cell import batch_optimize
from amorphgen.utils import get_calculator

calc = get_calculator(model="mace-mpa-0", device="cpu", default_dtype="float64")

batch_optimize(
    input_dir="random_Ga2O3",
    output_dir="random_Ga2O3_opt",
    calc=calc,
)

The --batch-opt mode uses the full opt_cell.run() under the hood, giving you proper logging, trajectory files, configurable optimizer/cell filter, and float64 precision.

Coordination-aware placement

For better short-range order, enable coordination-aware placement with --target-cn. New atoms are biased toward existing under-coordinated sites, and placements that would push any neighbour over its target CN are rejected:

# Coordination-aware placement: atoms placed near under-coordinated sites
amorphgen --random-gen \
    --composition "SiO2*16" \
    --target-density 2.2 \
    --target-cn Si=4,O=2 \
    --work-dir random_SiO2

# With explicit bonding shell distances
amorphgen --random-gen \
    --composition Li=16,Zr=8,Cl=48 \
    --target-cn Zr=6,Li=6 \
    --dmax Zr-Cl=3.2,Li-Cl=3.2 \
    --work-dir random_Li2ZrCl6
from amorphgen.pipeline.random_gen import generate_random

atoms = generate_random(
    composition={"Si": 16, "O": 32},
    target_density=2.2,
    target_cn={"Si": 4, "O": 2},
    seed=42,
)

Coordination-aware placement produces structures with correct coordination from the start, requiring less relaxation to reach the correct topology. Disable it with --no-sc (legacy flag name; the placement is enabled by default whenever --target-cn is set or auto-detected).


Structure analysis

--analyse takes a directory of structures (xyz, extxyz, cif, vasp) and reports density, bond lengths, coordination numbers, bond angles and partial RDFs. The same run can add the structure factor, ring statistics, polyhedral connectivity, Voronoi indices, a close-contact check and a validation against literature ranges. Every quantity that is plotted is also written as a CSV.

# Summary to the terminal
amorphgen --analyse --input-dir optimised_structures/

# Report + figures (RDF, coordination, angles, density) + CSVs
amorphgen --analyse --input-dir optimised_structures/ \
    --save-report report.txt --save-plot plots/

# Neutron S(q) by the direct (Debye) method, with ring statistics,
# corner/edge-sharing analysis and Voronoi indices for Ge
amorphgen --analyse --input-dir optimised_structures/ \
    --sq --sq-weighting neutron --rings --connectivity --voronoi Ge \
    --save-report report.txt --save-plot plots/

# Compare with literature ranges (a-Ga2O3, a-SiO2, a-GeO2, a-HfO2 ship in examples/)
amorphgen --analyse --input-dir optimised_structures/ \
    --reference examples/reference_a_GeO2.yaml

Notes on the options:

  • --sq computes S(q) at the reciprocal-lattice q-vectors of each cell, so the first sharp diffraction peak is resolved without the truncation of a Fourier transform of g(r). Weighting is xray (q-dependent Waasmaier–Kirfel form factors), neutron (Sears scattering lengths) or unweighted; --sq-method ft gives the g(r) transform for comparison. --sq-partials adds the Faber-Ziman partials S_ab(q) of every element pair to the CSV and a second plot. A box of about 20 Å (roughly 500 atoms) is needed to see the FSDP.
  • --pair-panels draws each element pair in its own panel, for g(r) and for the S(q) partials, which is easier to read than one axis for a four-element system like IGZO.
  • --rings counts the shortest ring per network edge, with the network former (Si, Ge, ...) as nodes; --rings Ge-O sets the pair explicitly.
  • --connectivity reports corner-, edge- and face-sharing between cation-centred polyhedra and the fraction of cations in edge-sharing pairs, which separates a corner-sharing network glass from a random packing with the same coordination.
  • --check-dimers flags unphysical close contacts (O–O peroxide, N–N) per structure.
  • --smearing SIGMA sets the Gaussian smearing of g(r) (default 0.05 Å; 0 for the raw histogram). --cutoff is auto-rdf (first minimum of each partial g(r), so every pair gets its own value), auto (radii table), a number in Å, or per-pair overrides such as "In-O=2.6,Zn-O=2.3" that keep auto-rdf for the other pairs. For elements bonded to several partner types (O in IGZO) the report adds the total coordination over all bonded partners.

Files written by --save-plot DIR: analysis_rdf, analysis_cn, analysis_angles, analysis_density, and with the matching flag analysis_sq, analysis_sq_partials, analysis_rdf_panels, analysis_cn_total, analysis_rings, analysis_connectivity, analysis_voronoi, each as PNG (and PDF with --save-pdf) plus CSV.

Worked example: a multi-cation oxide (a-IGZO)

Four elements give ten element pairs, three different cation sizes and an oxygen that is shared between them. One command covers it:

amorphgen --analyse --input-dir igzo_final/ \
    --sq --sq-partials --pair-panels \
    --total-cn O --total-cn "O:In+Ga" \
    --save-report report.txt --save-plot plots/

What to read in the output:

  • The header lists the cutoff in force for every pair. auto-rdf gives each pair its own value from the first minimum of its g(r) (Ga–O 2.03, Zn–O 2.25, In–O 2.47 Å here). One number for all pairs would count second-shell oxygens around the small Ga cation, so if you override, do it per pair: --cutoff "In-O=2.6" keeps auto-rdf for the rest.
  • Bonding coordination numbers covers the cation–O pairs (Ga–O 3.9, In–O 5.1, Zn–O 3.9) and, because O has three partner types, a Total coordination block with O-(Ga+In+Zn). Cation–cation and O–O contacts are listed apart as Non-bonded contacts and never enter the coordination or the angles.
  • --total-cn adds any total you name: O counts all bonded partners, O:In+Ga only the two larger cations.
  • --sq-partials prints the first peak of each Faber-Ziman partial S_ab(q) and writes them next to the total S(q). The partials do not depend on --sq-weighting; the weighting only combines them into the total.
  • --pair-panels puts each pair in its own panel for g(r) and for S_ab(q), which is easier to read than ten curves on one axis.

Files this writes in plots/: analysis_rdf (all partials plus g(r)_Total in the CSV), analysis_rdf_panels.png, analysis_cn (Ga–O, In–O, Zn–O and the O total), analysis_cn_total (the requested totals), analysis_sq (total S(q) with s_<pair> columns), analysis_sq_partials.png, analysis_sq_partials_panels.png, analysis_angles and analysis_density.

The same analysis from Python:

from amorphgen.analysis import StructureAnalyser

sa = StructureAnalyser("optimised_structures/")      # a directory or a list of files
print(sa.summary())
rdf  = sa.rdf(pair="Ge-O", sigma=0.05)              # r, g_r
sq   = sa.structure_factor_direct(weighting="neutron", sigma_q=0.05, partials=True)
rings = sa.ring_statistics()                         # ring_sizes, counts, fractions
conn  = sa.polyhedral_connectivity()                 # corner/edge/face sharing
sa.save_report("report.txt")
sa.plot(output_dir="plots/")

The analysis guide in the documentation covers the S(q) conventions and the reference-YAML format.


Generating multiple independent structures (batch quench)

Step 1: Run Stages 1–4 with snapshot sampling

amorphgen POSCAR \
    --stages 1 2 3 4 \
    --eq-high-steps 100000 \
    --work-dir melt_run/

Step 2: Batch quench N independent runs from snapshots

amorphgen --batch-quench \
    --snapshot-dir snapshots/ \
    --n-runs 20 --select uniform \
    --batch-stages 5 6 7 \
    --work-dir batch_run/
from amorphgen.pipeline import batch_quench

results = batch_quench.run(
    snapshot_files=snapshot_paths,
    n_runs=20,
    select="uniform",
    work_dir="batch_run",
)

Each run gets its own subdirectory: batch_run/run_0000/, batch_run/run_0001/, …

Resuming an interrupted batch

If a batch job times out, resubmit with --resume; already-completed runs are skipped:

amorphgen --batch-quench \
    --snapshot-dir snapshots/ \
    --n-runs 20 --select uniform \
    --resume \
    --work-dir batch_run/

Hybrid workflow: random generation → high-T equilibration → batch quench

An alternative approach combines random structure generation with high-temperature equilibration to skip the slow heating stage:

Random structure (target density)
    │
    ▼
Optimise (positions only - preserves density)
    │
    ▼
Equilibrate at T_melt (NVT, 20+ ps)
    │
    ├── snapshot 0 ──→ Quench → Low-T eq → Opt → amorphous_0
    ├── snapshot 1 ──→ Quench → Low-T eq → Opt → amorphous_1
    └── ...
from amorphgen.pipeline.random_gen import generate_random
from amorphgen.pipeline.opt_cell import run as opt_run
from amorphgen.pipeline.equilibrate import run as eq_run
from amorphgen import MeltQuenchPipeline

# Step 1: Generate random structure (auto minsep from Shannon radii)
atoms = generate_random(
    composition={"Ti": 8, "O": 16},
    target_density=3.2,       # optional, auto-estimated if omitted
    target_cn={"Ti": 6},      # optional, enables coordination-aware placement + CN-aware radii
)

# Step 2: Optimise (positions only)
calc = get_calculator(model="chgnet", device="cpu")
optimised = opt_run(atoms, cfg_override={"opt": {"fmax": 0.1}}, calc=calc)

# Step 3: Equilibrate at 2000 K
liquid = eq_run(optimised, cfg_override={
    "eq_high": {"ensemble": "NVT", "T": 2000, "steps": 10000, "timestep": 0.5},
}, calc=calc, stage="high")

# Step 4: Extract snapshots and batch quench (Stages 5 → 6 → 7)
for snap_file in snapshot_files:
    pipe = MeltQuenchPipeline(input_file=snap_file, work_dir=run_dir,
        cfg_override={"model": "chgnet", "device": "cpu"})
    pipe.run(stages=[5, 6, 7])

See Tutorial 5 for a complete working example.


Ensemble choice

Stage Default Override flag
Stage 2 pre-melt eq NVT --eq-premelt-ensemble NPT
Stage 3 melt NPT --melt-ensemble NVT
Stage 4 high-T eq NVT --eq-high-ensemble NPT
Stage 5 quench NVT --quench-ensemble NPT
Stage 6 low-T eq NVT --eq-low-ensemble NPT

Heating / cooling rate

amorphgen POSCAR \
    --melt-T-step 100 --melt-steps-per-T 1000     # 100 K/ps heating
    --quench-T-step -50 --quench-steps-per-T 2000  # 25 K/ps cooling

Common cooling rates:

Rate --quench-steps-per-T Time (3000 → 300 K)
200 K/ps 500 ~13 ps
100 K/ps (default) 1000 ~27 ps
10 K/ps 10000 ~270 ps
1 K/ps 100000 ~2700 ps

Trajectory format

Format Extension Notes
extxyz .xyz Default. ASE extended XYZ (cell + energy + forces). Readable by OVITO, VESTA, ASE.
xyz .xyz Plain XYZ (positions only)
traj .traj ASE binary
lammps-dump .dump LAMMPS text dump

Available models

Name Backend Notes
mace-mpa-0 MACE default (MPTrj + sAlex)
mace-omat-0-medium MACE OMAT, excellent phonons (ASL license)
mace-matpes-r2scan MACE MATPES, r²SCAN functional (ASL license)
chgnet CHGNet Charge-informed, good CPU speed
7net-mf-ompa SevenNet Multi-fidelity foundation, OMat+MPtrj+Alexandria
lennard-jones Classical Pair potential, no GPU needed
buckingham Classical Buckingham + Coulomb (Wolf summation), no GPU needed
amorphgen --list-models   # full table of all models grouped by backend

Full configuration reference

All defaults are in amorphgen/configs/default_config.py. Override any value via cfg_override:

pipe = MeltQuenchPipeline(
    input_file="POSCAR",
    work_dir="my_run",
    cfg_override={
        "model":       "mace-mpa-0",  # or "chgnet", "7net-mf-ompa", "buckingham", etc.
        "model_path":  None,           # path to local .model file (overrides model)
        "device":      "auto",         # "cuda", "cpu", or "auto"
        "traj_format": "extxyz",       # "extxyz", "xyz", "traj", "lammps-dump"

        "opt": {
            "fmax": 0.01,
            "max_steps": 1000,
        },
        "eq_premelt": {
            "ensemble": "NVT",
            "T": 300,
            "steps": 100000,       # 50 ps at 0.5 fs timestep
            "timestep": 0.5,
            "friction": 0.01,
        },
        "melt": {
            "ensemble": "NPT",
            "T_start": 300, "T_end": 3000,
            "T_step": 100, "steps_per_T": 1000,
            "timestep": 0.5,
            "friction": 0.01, "ttime": 25.0,
        },
        "eq_high": {
            "ensemble": "NVT",
            "T": 3000,
            "steps": 10000,
            "timestep": 0.5,
            "friction": 0.01,
        },
        "quench": {
            "ensemble": "NVT",
            "T_start": 3000, "T_end": 300,
            "T_step": -100, "steps_per_T": 1000,
            "timestep": 0.5,
            "friction": 0.01, "ttime": 25.0,
        },
        "eq_low": {
            "ensemble": "NVT",
            "T": 300,
            "steps": 10000,
            "timestep": 0.5,
            "friction": 0.01,
        },
    },
)

Output files

Stage Trajectory Final structure Log
1 stage1_opt.traj stage1_opt.cif + stage1_opt.xyz stage1_opt.log
2 stage2_eq_traj.xyz stage2_eq.xyz stage2_eq.log
3 stage3_melt_traj.xyz stage3_melted.xyz stage3_melt.log
4 stage4_eq_traj.xyz stage4_eq.xyz stage4_eq.log
5 stage5_quench_traj.xyz stage5_quenched.xyz stage5_quench.log
6 stage6_eq_traj.xyz stage6_eq.xyz stage6_eq.log
7 stage7_opt.traj stage7_opt.cif + stage7_opt.xyz stage7_opt.log

Tutorials

Start here:

Tutorial Description
Tutorial 1 Quick-start tutorial: orientation: what it does, the three workflows, decision tree, one live demo (random + CHGNet relax on a-SiO₂)

Workflow tutorials (each reports the wall time measured on the CPU it was validated on):

Tutorial Description
Tutorial 2 Zero-config random gen: composition is the only input; auto-derive minsep, density, target CN, oxidation state across 8 material classes (Si, SiO₂, In₂O₃, CdTe, AlN, LiCl, TiO₂, Cu). Each structure is CHGNet-relaxed and saved to output_T2/
Tutorial 3 Explicit control + ensemble analysis: the opposite end of T2: hand-picked minsep (from crystalline bond lengths) and target density (from cited amorphous-thin-film references), 5-structure ensembles per system, quantitative RDF / energy / CN / bond-angle analysis vs the crystalline reference (In₂O₃, TiO₂, Al₂O₃, Ga₂O₃; MACE-MPA-0)
Tutorial 4 Full 7-stage melt-quench from crystalline SiO₂ (CHGNet on CPU; flip the backend toggle for MACE on GPU)
Tutorial 5 Hybrid workflow: random gen → high-T equilibration → batch quench (TiO₂)
Tutorial 6 Classical potential (Buckingham+Coulomb) relaxation, no GPU needed (SiO₂, Al₂O₃, TiO₂)

Application case studies (these assume you have done the workflow tutorials):

Tutorial Description
Tutorial 7 Defect chemistry: O–O peroxide dimer dissociation kinetics in amorphous In₂O₃, with Arrhenius temperature scan

Package layout

AmorphGen/
├── .github/workflows/
│   └── test.yml                    ← CI (pytest on 3.10/3.11/3.12)
├── amorphgen/
│   ├── __init__.py                 ← v1.0.0rc4
│   ├── cli.py                      ← CLI entry point (amorphgen command)
│   ├── configs/
│   │   ├── default_config.py       ← all default parameters
│   │   ├── yaml_config.py          ← YAML config loader
│   │   └── example_config.yaml     ← example YAML with all options
│   ├── pipeline/
│   │   ├── run_pipeline.py         ← MeltQuenchPipeline orchestrator
│   │   ├── opt_cell.py             ← Stages 1 & 7 (optimisation) + batch_optimize()
│   │   ├── equilibrate.py          ← Stages 2, 4, 6 (constant-T equilibration)
│   │   ├── melt_cell.py            ← Stage 3 (heat ramp)
│   │   ├── quench.py               ← Stage 5 (cool ramp)
│   │   ├── batch_quench.py         ← batch runner: Stages 5 → 6 → 7 on N snapshots
│   │   └── random_gen.py           ← random + coordination-aware placement
│   └── utils/
│       ├── analysis.py             ← StructureAnalyser (density, CN, RDF, angles)
│       ├── calculators.py          ← multi-backend calculator factory
│       ├── radii.py                ← Shannon/metallic radii, minsep, density estimation
│       └── common.py               ← dynamics builder, logger, trajectory writer
├── paper/
│   ├── paper.md                    ← JOSS draft
│   └── paper.bib
├── test/                           ← 114 tests (4 skipped without --run-mace)
├── pyproject.toml
├── LICENSE                         ← MIT
└── README.md

HPC (SLURM) example

#!/bin/bash
#SBATCH --job-name=amorphgen
#SBATCH --gres=gpu:1
#SBATCH --ntasks=1
#SBATCH --cpus-per-task=4
#SBATCH --mem=32G
#SBATCH --time=4:00:00

source /path/to/conda/env/bin/activate

amorphgen /abs/path/to/In2O3_POSCAR \
    --model mace-mpa-0 \
    --device cuda \
    --work-dir /scratch/InO_amorphous \
    --melt-T-end 2500 \
    --quench-T-start 2500

An ensemble of many structures on one GPU is quicker with the torch-sim engine, which batches the structures and, together with --resume, can be resubmitted into a short queue until it finishes. Outputs are written after every chunk and MD trajectories every 100 steps, so a walltime kill costs at most one relaxation chunk or 100 MD steps:

#!/bin/bash
#SBATCH --job-name=amorphgen_ens
#SBATCH --gres=gpu:1
#SBATCH --ntasks=1
#SBATCH --cpus-per-task=4
#SBATCH --mem=48G
#SBATCH --time=1:00:00
export PYTHONUNBUFFERED=1            # progress in the log while the job runs

source /path/to/venv/bin/activate    # Python 3.12 with amorphgen[mace,torchsim]

amorphgen --hybrid-ensemble --input-dir /scratch/geo2_seeds/random_opt/ \
    --config hybrid.yaml --model mace-mpa-0 --device cuda \
    --engine torchsim --batch-size auto \
    --work-dir /scratch/geo2_hybrid --resume

Ready-made BlueBEAR scripts for generation arrays, batched relaxation, batched MD and the GPU test suite are in examples/.


Dependencies

Package Purpose
ase MD engine, optimisers, I/O
numpy Array operations
scipy Vectorized erfc for Coulomb (classical)
torch GPU backend (MLIP + optional classical GPU)
mace-torch MACE calculator (optional)
chgnet CHGNet calculator (optional)
sevenn SevenNet calculator (optional)
torch-sim-atomistic Batched GPU engine for ensembles, --engine torchsim (optional, Python 3.12+)

Citation

If you use AmorphGen in your research, please cite the package and the foundation model(s) you used.

AmorphGen:

@misc{amorphgen,
  author = {Kaewmeechai, Chaiyawat and Scanlon, David O.},
  title  = {AmorphGen: A Python package for amorphous structure generation
            with machine-learning and classical interatomic potentials},
  year   = {2026},
  url    = {https://github.com/SMTG-Bham/AmorphGen}
}

A Zenodo DOI for tagged releases will be added on first stable release.

Foundation potentials (cite the one you used):

MACE-MP:

@article{batatia2023foundation,
  title   = {A foundation model for atomistic materials chemistry},
  author  = {Ilyes Batatia and others},
  year    = {2023},
  eprint  = {2401.00096},
  archivePrefix = {arXiv},
}

CHGNet:

@article{deng2023chgnet,
  title   = {CHGNet as a pretrained universal neural network potential for charge-informed atomistic modelling},
  author  = {Bowen Deng and others},
  journal = {Nature Machine Intelligence},
  year    = {2023},
}

SevenNet:

@article{park2024sevennet,
  title   = {Scalable parallel algorithm for graph neural network interatomic potentials in molecular dynamics simulations},
  author  = {Park, Yutack and Kim, Jaesun and Hwang, Seungwoo and Han, Seungwu},
  journal = {Journal of Chemical Theory and Computation},
  year    = {2024},
}

License

MIT

Development notes

Parts of this codebase were developed with assistance from an AI tool, Anthropic's Claude (Opus 4.8), for code drafting, refactoring, and documentation. All AI-assisted code was reviewed, tested, and validated by the authors, who take full responsibility for the contents of this repository.

Releases

Packages

Contributors

Languages