Symmetry- and Property-Aware Reinforcement Learning for Crystal Generation
SPARC inversely designs crystal symmetry from a desired physical response.
The animation above presents one of the two design tasks. The target is an in-plane-isotropic dielectric response,
SPARC achieves this by fine-tuning the symmetry-aware crystal diffusion model SymmCD through reinforcement learning.
main.py— Hydra entry point (instantiatespipeline.sparc.SPARCand callsrun_rl)configs/— Hydra configs (base.yaml,model/,pipeline/,reward/,logger/)models/— model suites (suite/symmcd.py,suite/diffcsp.py,suite/mattergen.py) with the SymmCD diffusion model lives undermodels/symmcd/pipeline/,rewards/,memory/— RL loop, reward calculators, replay bufferutils/assets.py— auto-downloads large assets from HuggingFace on first usescripts/— portable helpers + the dielectric run submit scripts (scripts/dielectric_*_b96*.sbatch);scripts/nersc/holds the author's original NERSC scripts (reference only)
Requires uv. The pinned wheels target CUDA 11.8 (cu118); any driver supporting CUDA ≥ 11.8 works.
git clone git@github.com:AngusHsuPhys/sparc_v0.git
cd sparc_v0
uv sync --frozenThis installs torch 2.2.1+cu118, PyG (torch_scatter/sparse/cluster), MatterGen (pinned to v1.0.3), the materials stack (pymatgen / ase / e3nn / atomate2 / mattersim), and sparc itself (editable). Then either source .venv/bin/activate or prefix commands with uv run.
Model checkpoints and datasets (~13 GB) are not in git — they live in the HuggingFace repo AngusHsuPhys/sparc-assets and download automatically the first time each file is needed, landing at the exact paths the configs/checkpoints expect (no config edits required). While the repo is private, authenticate once:
huggingface-cli login # or: export HF_TOKEN=hf_xxxOptionally prefetch (handy before submitting a job, or on a login node that shares the filesystem with offline compute nodes):
python scripts/download_assets.py # ~4.2 GB "core": surrogates + mp_20 + SymmCD base
python scripts/download_assets.py --all # also the ~8.6 GB caches (only needed to re-train surrogates)SPARC also pulls a few third-party weights on first use. Including the MatterSim potential + a MatterGen reference dataset (in the structure filter). These download automatically when online. If your GPU nodes have no internet but share ~/.cache/huggingface (and the repo) with an internet-connected login node, warm everything once on the login node, then run jobs offline:
python scripts/download_assets.py # SPARC assets -> repo paths
python scripts/warm_caches.py # third-party MatterSim / MatterGen
# then in the job: export HF_HUB_OFFLINE=1 SPARC_SKIP_ASSET_DOWNLOAD=1The RL runs behind the paper figures are published in the same repo under runs/<name>/ as lightweight visualization bundles
python scripts/download_paper_runs.py # all 6 runs -> exp_res/<name>/
python scripts/download_paper_runs.py --list # show the run names
python scripts/download_paper_runs.py --runs dielectric_inplane_isotropy_gapgate_mprime_newbg_b96source scripts/env.sh # sets PROJECT_ROOT (required) + runtime dirs
python main.py \
expname=smoke pipeline=sparc model=symmcd reward=band_gap_e3nn \
logger=csv device=cuda eval_size=12 rl_epoch=2 \
model.model_path="$MODEL_PATH" seed=1 deterministic_torch=trueDownloads the SymmCD base + the E3NN band-gap surrogate, samples a few crystals, scores them, runs 2 RL steps, and writes exp_res/smoke/{metrics.csv,samples/}.
Run from the repo root. These are representative commands; the exact published overrides (adaptive space-group knobs, batch sizes, etc.) live in the scripts/*.sbatch submit scripts.
Dielectric — in-plane isotropy (the task shown in the animation above).
Drives ε_xx = ε_yy (in-plane isotropy — the signature of the trigonal/tetragonal/hexagonal families; cubic is excluded by a light z-anisotropy floor), gated by band gap ≥ 0.3 eV so candidates stay non-metallic and worth DFT verification:
python main.py expname=dielectric_inplane_isotropy pipeline=sparc model=symmcd \
reward=fom_inplane_isotropy_gapgate logger=csv device=cuda \
eval_size=30 rl_epoch=120 seed=1 deterministic_torch=true \
model.model_path="$MODEL_PATH" \
model.sample_cfg.generation_batch_size=96 model.sample_cfg.batch_size=96MLIP relaxation is on by default; the no-relax ablation adds +sample_cfg.filter.relax=false, and a DiffCSP-backbone ablation is provided (see scripts/dielectric_inplane_isotropy_gapgate_diffcsp_*.sbatch).
SLME — band gap + solar-cell efficiency (η).
Weighted reward: 0.2·band gap + 0.8·SLME η. The band-gap term is a hard-zero tent centered at 1.3 eV (the Shockley–Queisser optimum), scored with the project's own E3NN band-gap model; η is integrated over the AM1.5G spectrum from that gap up at 0.3 µm thickness (all set in the reward config):
python main.py expname=slme_bgcenter13_eta08 pipeline=sparc model=symmcd \
reward=tsenn_slme_optimate_bgcenter13_eta08_e3nngap logger=csv device=cuda \
eval_size=30 rl_epoch=120 seed=1 deterministic_torch=true \
model.model_path="$MODEL_PATH" \
model.sample_cfg.generation_batch_size=128 model.sample_cfg.batch_size=128 \
model.sample_cfg.finetune_on_representative=trueBand-gap target:
python main.py expname=bandgap pipeline=sparc model=symmcd \
reward=band_gap_e3nn logger=csv device=cuda eval_size=24 rl_epoch=200 \
seed=1 deterministic_torch=true model.model_path="$MODEL_PATH"See LICENSE.
