Agent skill
candidate-generator
Generate inorganic crystal structure candidates for computational materials discovery workflows. Use this skill whenever the user wants to build, explore, or diversify a pool of inorganic structures for DFT screening, high-throughput calculations, machine learning dataset construction, or property-guided search. This skill covers the full candidate generation pipeline - seed structure creation -> chemical space exploration -> configurational ordering -> defect generation -> ensemble augmentation.
Install this agent skill to your Project
npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/other/other/candidate-generator
SKILL.md
Inorganic Candidate Generation
This skill guides the systematic generation of inorganic crystal structure candidates using a suite of seven pymatgen-based tools. The methodology is: prototype → explore chemistry → resolve disorder → add defects → augment, selecting the appropriate branch(es) for the discovery goal.
The core philosophy: candidate generation is a funnel. Start broad (many chemistries,
many configurations), then narrow using physical filters (charge neutrality, Ewald energy,
thermodynamic stability from MP). Always track structures in the ASE database using
ase_store_result so nothing is recomputed.
Tool Catalogue
1. pymatgen_prototype_builder — Seed Structure
Builds an ideal crystal from a spacegroup number/symbol, species list, and lattice parameters. This is the entry point for any workflow that starts from scratch rather than an existing structure.
Key parameters:
spacegroup: int (1–230) or Hermann-Mauguin symbol, e.g.225or"Fm-3m"species: list of element symbols (['La', 'Mn', 'O', 'O', 'O']) or Wyckoff dictlattice_parameters:[a, b, c, alpha, beta, gamma]in Å and degrees;[a]works for cubicwyckoff_positions: optional dict mapping Wyckoff labels to species/coordsoutput_format:'dict'(default, pass to other tools),'poscar','cif','ase'
Returns: structures[i].structure — pass directly to substitution, enumeration, or defect tools.
wyckoff_positions proximity gotcha: Passing a Wyckoff dict (e.g. {'1a': 'Ba', '1b': 'Ti', '3c': 'O'}) can raise
"sites less than 0.01 Å apart" for multi-species prototypes where pymatgen auto-generates
overlapping fractional coords. Preferred approach: supply explicit species and coords lists
instead, and use validate_proximity=False when debugging a new prototype before finalising
lattice parameters.
2. pymatgen_substitution_generator — Chemical Space Exploration
Replaces elements in existing structures. Best for isostructural analogue screening across a fixed lattice topology when charge balance is not strictly required.
Key parameters:
substitutions:{'Li': 'Na'}(full swap),{'Li': ['Na', 'K']}(one variant per replacement),{'Li': {'replace_with': 'Na', 'fraction': 0.5}}(50 % doping)n_structures: variants to generate per substitution combination (default 5). For deterministic full swaps (fraction=1.0) set this to 1 — higher values only produce identical duplicates. Total output =n_structures × num_combinations, capped bymax_attempts.max_attempts: hard cap on total output count (default 50). If you supply N substitution options with n_structures=k, setmax_attempts ≥ N × kor outputs will be silently truncated. Example: 8 B-site metals with n_structures=1 needsmax_attempts=8(or higher); with n_structures=3 needsmax_attempts=24.enforce_charge_neutrality: setTruefor ionic materialssite_selector:'all','wyckoff_4a','coordination_6', etc.
When to use over ion_exchange_generator: when you want exploratory doping without
strict stoichiometry adjustment and charge neutrality is handled manually or checked post-hoc.
3. pymatgen_ion_exchange_generator — Charge-Neutral Substitution
Replaces a mobile ion (e.g. Li⁺) with one or more ions, automatically adjusting stoichiometry so that total ionic charge is conserved. Only charge-neutral structures are returned by default.
Key parameters:
replace_ion: element to replace, e.g.'Li'with_ions:['Na', 'K'](equal weight) or{'Na': 0.6, 'Mg': 0.4}(weighted split)exchange_fraction: fraction of sites to exchange (0–1), default1.0allow_oxidation_state_change:False(default) = only neutral structures returnedmax_structures: cap on returned structures per input (default 10)
Prototypical use cases: Li → Na/K battery cathode analogues, Ca²⁺ → La³⁺ doping in oxides.
4. pymatgen_enumeration_generator — Exhaustive Ordering of Disordered Structures
Takes structures with fractional site occupancies and returns all symmetry-inequivalent ordered supercell approximants up to a cell-size limit, ranked by Ewald energy or cell size.
Key parameters:
min_cell_size/max_cell_size: supercell multiplier range (1–8); keepmax_cell_size ≤ 4for binaries,≤ 2for ternaries to avoid combinatorial explosionn_structures: max ordered structures returned per input (default 20, max 500)sort_by:'ewald'(default, lowest energy first),'num_sites','random'add_oxidation_states: auto-assign oxidation states for Ewald ranking (defaultTrue)refine_structure: re-symmetrize before enumeration (recommended, defaultTrue)
Requires: enumlib on PATH — install with pip install enumlib or conda install -c conda-forge enumlib.
When to use over sqs_generator: when you need the complete ordered-configuration pool,
want to identify the ground-state ordering, or are building a cluster expansion training set.
5. pymatgen_sqs_generator — Special Quasirandom Structures
Finds a small ordered supercell whose Warren-Cowley pair correlations best mimic a perfectly random alloy. Returns the single best quasirandom approximant per input, not the full ordered-configuration space.
Key parameters:
supercell_size: target formula units in SQS cell (default 8; use 8–16 for binary, 12–24 for ternary)supercell_matrix: explicit[nx, ny, nz]or 3×3 matrix (overridessupercell_size)n_structures: independent SQS candidates per input (default 3); ranked bysqs_errorn_mc_steps: Monte Carlo steps per candidate (default 50 000; increase for multicomponent)n_shells: correlation shells in objective function (default 4)seed: set for reproducibilityuse_mcsqs: use ATATmcsqsbinary if available (better quality for large systems)
When to use over enumeration_generator: target system is a solid solution / high-entropy
material where disorder is the physical state being modelled, not a defect to be minimised.
6. pymatgen_defect_generator — Point Defect Supercells
Takes a perfect bulk host structure and generates one supercell per symmetry-inequivalent defect site. Supports vacancies, substitutional dopants, and interstitials.
Key parameters:
vacancy_species:['Li', 'O']— generate V_Li, V_O defectssubstitution_species:{'Fe': ['Mn', 'Co']}— Mn_Fe and Co_Fe substitutionalsinterstitial_species:['Li']— find void sites and insert Licharge_states:{'V_Li': [-1, 0, 1]}— metadata only; structures are always neutral geometrysupercell_min_atoms: target atoms in defect supercell (default 64; 64–128 for plane-wave DFT)inequivalent_only:True(default) — generate only symmetry-distinct defects
Downstream: feed outputs to pymatgen_perturbation_generator to rattle defect geometries,
or save directly to the ASE database via ase_store_result.
7. pymatgen_perturbation_generator — Structural Ensemble / Augmentation
Applies random atomic displacements ("rattling") and/or lattice strain to create ensembles of perturbed structures. Does not change composition.
Key parameters:
displacement_max: max displacement per atom in Å (default 0.1; typical range 0.05–0.2)strain_percent:None(off), scalar (uniform),[min, max](random range), or 6-element Voigt tensor[e_xx, e_yy, e_zz, e_xy, e_xz, e_yz]n_structures: perturbed copies per input (default 10, max 200)seed: for reproducibility
Primary uses:
- Provide DFT starting geometries that are not stuck at a symmetry saddle point
- Augment ML training datasets with off-equilibrium configurations
- Generate strained cells for elastic property screening
Workflow Phases
Phase 1: Seed Structure
Start here if no structure exists yet.
pymatgen_prototype_builder(
spacegroup=225, # Fm-3m (rock-salt)
species=['Li', 'O'],
lattice_parameters=[4.33] # cubic: [a]
)
If a known structure already exists (from mp_get_material_properties, a CIF file, or the
ASE database), skip this step and pass that structure directly.
Common prototypes:
| Prototype | SG # | Symbol | Example |
|---|---|---|---|
| Rock-salt | 225 | Fm-3m | NaCl, LiF, MgO |
| Perovskite | 221 | Pm-3m | BaTiO₃, SrTiO₃ |
| Spinel | 227 | Fd-3m | MgAl₂O₄, LiMn₂O₄ |
| Layered oxide (α-NaFeO₂) | 166 | R-3m | LiCoO₂, LiNiO₂ |
| Olivine | 62 | Pnma | LiFePO₄, LiMnPO₄ |
| Rutile | 136 | P4₂/mnm | TiO₂, SnO₂ |
| Wurtzite | 186 | P6₃mc | ZnO, GaN |
| Fluorite | 225 | Fm-3m | CaF₂, CeO₂ |
Phase 2: Chemical Space Exploration
Choose the branch based on whether charge-neutrality must be enforced:
Branch A — Exploratory (charge balance not enforced):
pymatgen_substitution_generator(
input_structures=seed_structure,
substitutions={'Li': ['Na', 'K', 'Rb'], 'Fe': ['Mn', 'Co', 'Ni']},
n_structures=10,
enforce_charge_neutrality=False
)
Use when: screening isostructural analogues, building diverse training sets.
Branch B — Charge-neutral (ionic materials):
pymatgen_ion_exchange_generator(
input_structures=seed_structure,
replace_ion='Li',
with_ions={'Na': 0.5, 'Mg': 0.5},
exchange_fraction=1.0,
max_structures=20
)
Use when: battery cathode analogues, any case where the oxidation-state bookkeeping must be exact.
Both branches accept lists of input structures — pipe multiple seeds through in one call.
Phase 3: Resolve Disorder (if structures have partial occupancies)
If Phase 2 produced or if you started from a disordered structure:
Ground-state search (small cells, complete enumeration):
pymatgen_enumeration_generator(
input_structures=disordered_structs,
max_cell_size=4,
n_structures=50,
sort_by='ewald'
)
Solid-solution modelling (large / high-entropy systems):
pymatgen_sqs_generator(
input_structures=disordered_struct,
supercell_size=16,
n_structures=5,
n_mc_steps=200000,
seed=42
)
Decision rule:
- Enumeration when you need all low-energy orderings or a CE training set.
- SQS when disorder is the target state (e.g. (Li,Na)₀.₅CoO₂ solid solution).
- For high-entropy systems (≥ 4 mixing species), prefer SQS; enumeration becomes
intractable above
max_cell_size=2.
Phase 4: Defect Generation (optional branch)
Fork off from any ordered structure to study point defects:
pymatgen_defect_generator(
input_structure=ordered_structure,
vacancy_species=['Li'],
substitution_species={'Fe': ['Mn', 'Co']},
interstitial_species=['Li'],
charge_states={'V_Li': [-1, 0, 1]},
supercell_min_atoms=128
)
Important: Pass only a single, ordered, defect-free host structure. The tool generates one supercell per inequivalent defect site automatically — do not pre-expand the cell.
Phase 5: Perturbation / Augmentation
Apply to any structure from Phases 1–4 to:
- Break symmetry before DFT relaxation (avoid false saddle-point convergence)
- Augment ML training datasets
- Probe elastic and thermal response
pymatgen_perturbation_generator(
input_structures=ordered_or_defect_structures,
displacement_max=0.1,
strain_percent=[-2.0, 2.0],
n_structures=20,
seed=0
)
For defect geometries, use displacement_max=0.05–0.1 Å (subtle rattling). For
ML data augmentation, 0.1–0.2 Å with random strain is typical.
Connecting to the Rest of the Workflow
Saving to the ASE Database
Always store generated structures so they can be queried later without regeneration:
ase_store_result(
db_path='candidates.db',
atoms_dict=structure['structure'], # MUST use output_format='ase' — see note below
key_value_pairs={
'generator': 'substitution',
'compound': structure['formula'], # NOT 'formula' — see reserved keys below
'campaign': 'cathode_screen_2026',
'source_structure': 'LiCoO2_mp-24850'
}
)
output_format must be 'ase' when feeding into ase_store_result:
ase_store_result requires ASE-native keys (numbers, positions, cell, pbc), which
are only produced when the upstream pymatgen tool is called with output_format='ase'.
Using the default output_format='dict' produces a pymatgen Structure.as_dict() object
(with @module, @class, sites, lattice, etc.) that will be rejected with:
"atoms_dict missing required keys: ['numbers']".
Always set output_format='ase' on any pymatgen tool whose result goes directly to ase_store_result.
ASE reserved key names — never use these in key_value_pairs:
ASE's db.write() will raise ValueError: Bad key for any of the following built-in column
names: id, unique_id, ctime, mtime, user, calculator, energy, forces,
stress, magmoms, charges, cell, pbc, natoms, formula, mass, volume,
spacegroup. Use unambiguous alternatives e.g. compound instead of formula,
sg_num instead of spacegroup, uid instead of unique_id.
Query existing candidates before generating new ones to avoid duplication:
ase_query_db(db_path='candidates.db', property_filters={'campaign': 'cathode_screen_2026'})
Filtering with Materials Project
After chemical space exploration, cross-check compositions against the MP convex hull before running expensive DFT:
mp_search_materials(
formula='NaCoO2',
is_stable=True
)
Discard compositions that are far above the hull (energy_above_hull > 0.1 eV/atom) unless the target is metastable phases.
Output Format Routing
| Downstream tool | Recommended output_format |
|---|---|
| Another pymatgen tool | 'dict' (default) |
| VASP / CP2K / Quantum ESPRESSO | 'poscar' or 'cif' |
ASE database (ase_store_result) |
'ase' |
| CIF archive / visualisation | 'cif' |
Common Patterns
Isostructural Analogue Screen
# 1. Build rock-salt seed
seed = pymatgen_prototype_builder(spacegroup=225, species=['Li','O'], lattice_parameters=[4.33])
# 2. Swap Li site: Li → Na, K, Rb; O site: O → S, Se
variants = pymatgen_substitution_generator(
input_structures=seed['structures'][0]['structure'],
substitutions={'Li': ['Na', 'K', 'Rb'], 'O': ['S', 'Se', 'O']},
n_structures=15
)
# 3. Filter by MP stability and store survivors
for s in variants['structures']:
mp_results = mp_search_materials(formula=s['formula'])
if mp_results['count'] > 0:
ase_store_result(db_path='screen.db', atoms_dict=s['structure'],
key_value_pairs={'formula': s['formula'], 'campaign': 'rocksalt_screen'})
Li → Na Battery Analogue
licoo2 = mp_get_material_properties('mp-24850') # LiCoO2
struct_dict = licoo2['properties'][0]['structure']
exchanged = pymatgen_ion_exchange_generator(
input_structures=struct_dict,
replace_ion='Li',
with_ions=['Na'],
exchange_fraction=1.0,
max_structures=5
)
High-Entropy Oxide SQS
# Build a rocksalt with 5-component mixing on the cation sublattice
# Input: disordered structure with occupancies {Mg:0.2, Co:0.2, Ni:0.2, Cu:0.2, Zn:0.2}
sqs = pymatgen_sqs_generator(
input_structures=disordered_cif,
supercell_size=20,
n_structures=5,
n_mc_steps=500000,
seed=7
)
# Best SQS is sqs['structures'][0] (sorted by sqs_error)
Ground-State Ordering Search
# Li₀.₅CoO₂ starting from partially delithiated structure with site occupancies
ordered_candidates = pymatgen_enumeration_generator(
input_structures=disordered_struct,
max_cell_size=4,
n_structures=100,
sort_by='ewald'
)
# Top 10 by Ewald energy are the most plausible ground-state orderings
top10 = ordered_candidates['structures'][:10]
Defect-Engineered Cathode
# Start from a relaxed ordered LiMnO2 structure
defect_cells = pymatgen_defect_generator(
input_structure=limno2_dict,
vacancy_species=['Li'],
substitution_species={'Mn': ['Fe', 'Ni', 'Co']},
supercell_min_atoms=96
)
# Rattle each defect cell before DFT relaxation
for dc in defect_cells['structures']:
perturbed = pymatgen_perturbation_generator(
input_structures=dc,
displacement_max=0.08,
n_structures=3,
seed=1
)
for p in perturbed['structures']:
ase_store_result(db_path='defects.db', atoms_dict=p,
key_value_pairs={'defect_label': dc['metadata']['defect_label']})
Decision Guide
Need a new structure from scratch?
└─► pymatgen_prototype_builder
Have an existing structure, want new chemistries?
├─ Charge balance is critical (ionic material)?
│ └─► pymatgen_ion_exchange_generator
└─ Exploratory / charge balance not enforced?
└─► pymatgen_substitution_generator
Structure has partial occupancies / is disordered?
├─ Want ALL orderings / ground-state search / CE training?
│ └─► pymatgen_enumeration_generator (max_cell_size ≤ 4 for binaries)
└─ Modelling disorder itself (solid solution / high-entropy)?
└─► pymatgen_sqs_generator
Need point defect supercells?
└─► pymatgen_defect_generator
Need an ensemble / perturbed copies of any structure?
└─► pymatgen_perturbation_generator
Pitfalls and Gotchas
enumeration_generator hangs or never returns
Cause: max_cell_size too large for the number of mixing species. Combinatorial explosion.
Fix: reduce max_cell_size to 2–3, or switch to sqs_generator for multicomponent systems.
enumeration_generator fails on Windows
Cause: The tool requires the enum.x (enumlib) binary on PATH. enumlib is not available
natively on Windows — it can only be installed inside WSL.
Fix: On Windows, fall back to sqs_generator (no binary dependency; uses a built-in MC
backend). If ground-state enumeration is essential, run via WSL:
wsl conda install -c conda-forge enumlib then add the WSL binary path to the Windows PATH.
Never bypass MCP tools by calling pymatgen directly in scripts
Cause: Writing script code that calls pymatgen transformation classes (e.g.
EnumerateStructureTransformation) directly risks passing incorrect kwargs as the internal
API evolves (e.g. check_ordered_structures vs check_ordered_symmetry), and it loses
the error handling and platform abstraction that the MCP tools provide.
Fix: Always use the designated MCP tool (pymatgen_enumeration_generator,
pymatgen_substitution_generator, etc.). Only drop to direct pymatgen code when an MCP
tool explicitly cannot accomplish the task (e.g. manual supercell construction).
substitution_generator silently truncates output when many options are given
Cause: max_attempts (default 50) is a hard cap on total output. With a list of N
substitution options and n_structures=k, the tool attempts N×k structures but stops at
max_attempts, silently dropping the remainder with no error.
Symptom: count in the result is less than N×k; some substitution options are missing.
Fix: Always set max_attempts = n_structures × len(substitution_options) explicitly.
Also set n_structures=1 for deterministic full swaps (fraction=1.0) — higher values
just add identical duplicates and inflate the attempt count unnecessarily.
substitution_generator fractional doping silently becomes a full swap on single-site cells
Cause: fraction=0.5 on a sublattice with only one site cannot produce a partial occupancy
(you cannot remove half an atom). The tool falls back to replacing the whole site, returning
the same result as fraction=1.0 with no warning.
Symptom: The output formula shows a complete element swap even though fraction < 1.0 was
requested; the output is an ordered structure, not a disordered one.
Fix: Ensure the input cell has ≥ 2 sites of the target species before using fractional
substitution. For a single-site primitive cell (e.g. a 5-atom perovskite with one B-site),
either (a) build a supercell first so multiple target sites exist, or (b) manually construct
the disordered structure dict with explicit partial occupancies
({"element": "Fe", "occu": 0.5}, {"element": "Co", "occu": 0.5} on the same site)
and pass it directly to sqs_generator or enumeration_generator.
ion_exchange_generator returns zero structures
Cause: No charge-neutral solution exists at the requested stoichiometry.
Fix: Try different exchange_fraction values, use allow_oxidation_state_change=True to debug,
or verify oxidation state assumptions with mp_get_material_properties.
defect_generator creates excessively large supercells
Cause: supercell_min_atoms is high (default 64) relative to a primitive cell with few atoms.
Fix: Lower supercell_min_atoms (e.g. 32 for quick tests), or supply an explicit supercell_matrix.
prototype_builder raises proximity error
Cause: The chosen lattice parameters place atoms too close together.
Fix: Check against experimental / MP values. Temporarily set validate_proximity=False to
retrieve the structure and inspect it before adjusting parameters.
Duplicate structures in candidate pool
Cause: Multiple generation paths converge on the same composition and topology.
Fix: Query ase_query_db and use unique_key in ase_store_result to deduplicate on
formula + source_structure hash before running DFT.
sqs_generator produces poor SQS quality (high sqs_error)
Cause: Too few MC steps or too small a supercell for the target composition.
Fix: Increase n_mc_steps (try 200 000–500 000) and supercell_size (16–24), or install
ATAT and set use_mcsqs=True.
Quick Reference
| Task | Tool | Critical parameters |
|---|---|---|
| Build from spacegroup | prototype_builder |
spacegroup, species, lattice_parameters |
| Isostructural analogues | substitution_generator |
substitutions, n_structures |
| Charge-neutral ion swap | ion_exchange_generator |
replace_ion, with_ions, exchange_fraction |
| Enumerate all orderings | enumeration_generator |
max_cell_size ≤ 4, sort_by='ewald' |
| Best quasirandom cell | sqs_generator |
supercell_size, n_mc_steps, n_structures |
| Point defect supercells | defect_generator |
vacancy/substitution/interstitial_species, supercell_min_atoms |
| Rattle / strain ensemble | perturbation_generator |
displacement_max, strain_percent, n_structures |
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
Didn't find tool you were looking for?