MLIP force calculations (experimental)¶
Experimental feature
Everything on this page describes an experimental capability. The API, data flow, and supported calculators may change without prior notice in future releases. Use MLIP forces for rapid screening only; always validate supercell convergence with DFT before using a result in production.
Architecture¶
When ML_forces=True the workflow replaces the two PwBaseWorkChain SCF calls
with two PythonJob tasks from aiida-pythonjob. All other logic remains
unchanged: supercell generation, Voronoi muon placement, iterative enlargement,
and convergence checking all work identically.
Normal DFT path: MLIP path:
run_pw_double_scf → run_ase_double_forces
(PwBaseWorkChain) (PythonJob × 2)
The PythonJob executes a Python closure (forces_function) on the remote
compute resource. The closure:
- Receives an ASE
Atomsstructure as input. - Calls the user-supplied
callback_calculator()to instantiate the MLIP. - Attaches the calculator to the structure and evaluates forces.
- Returns
{'energy': float, 'forces': list}(plain Python types for pickle safety across numpy versions).
The callback calculator pattern¶
All imports must be inside the callback function because the closure is pickled and shipped to the remote worker. The function must be importable without side effects.
def get_mace_calculator():
import torch
torch.serialization.add_safe_globals([slice]) # required for MACE
from mace.calculators import mace_mp
return mace_mp(model="medium", device="cpu", default_dtype="float64")
Pass it to get_builder_from_protocol:
builder = IsolatedImpurityWorkChain.get_builder_from_protocol(
structure=structure,
ML_forces=True,
pythonjob_code=pythonjob_code,
callback_calculator=get_mace_calculator,
model_name="mace-mp-medium",
)
The model_name string is stored in the PythonJob for provenance labelling.
Supported MLIP calculators¶
Any ASE calculator can be used. The table shows examples that have been tested:
| Model | Package | Notes |
|---|---|---|
| MACE-MP | mace-torch |
Universal potential, general-purpose |
| MACE-polar | mace-torch (nightly) |
Polar materials variant |
| MatterSim | mattersim |
Works around pkg_resources import in __version__ |
| CHGNet | chgnet |
Materials Project universal potential |
| EMT | ase (built-in) |
Testing only, not physically meaningful |
Adding a new MLIP calculator¶
- Write a callback function following the pattern above.
- Make sure all imports are inside the function body.
- If the model has non-picklable internal state, consider wrapping it in a factory function that re-creates the calculator on each call.
- Test locally:
calc = get_my_calculator()
from ase.build import bulk
atoms = bulk("Si") * 2
atoms.calc = calc
print(atoms.get_forces())
Charge handling with MLIPs¶
When charge_supercell=True the workflow sets:
Not all MLIP backends read atoms.info["charge"]. Models that support
charge-aware evaluation (e.g. some versions of MACE) may handle this correctly;
others will silently ignore it. Always check the documentation of your chosen MLIP
if you need a physically meaningful charged supercell.
prepare_ase_pythonjob_forces_inputs¶
Module: aiida_impuritysupercellconv.pythonjobs.forces
Helper function that prepares the complete pythonjob input dictionary for one
force calculation run. Called internally by get_builder_from_protocol when
ML_forces=True.
from aiida_impuritysupercellconv.pythonjobs.forces import prepare_ase_pythonjob_forces_inputs
inputs = prepare_ase_pythonjob_forces_inputs(
structure=structure,
callback_calculator=get_mace_calculator,
pythonjob_code=pythonjob_code,
pythonjob_metadata={
'options': {
'resources': {'num_machines': 1, 'num_mpiprocs_per_machine': 1},
'max_wallclock_seconds': 1800,
}
},
charged_supercell=True,
model_name="mace-mp-medium",
)
| Parameter | Type | Description |
|---|---|---|
structure |
StructureData or ase.Atoms |
Input structure |
callback_calculator |
callable | Function returning an ASE calculator |
pythonjob_code |
orm.Code |
The configured PythonJob code |
pythonjob_metadata |
dict | Scheduler options for the PythonJob |
pythonjob_inputs |
dict, optional | Additional pythonjob inputs |
charged_supercell |
bool | If True, sets atoms.info["charge"] = 1 |
model_name |
str, optional | Label stored in output for provenance |
Returns a dict ready to assign to builder.pythonjob.
Known limitations¶
- The
pythonjobnamespace has no protocol-based setup (noget_builder_from_protocolforPythonJob); resource configuration must be done manually. - There is no automatic restart if an MLIP
PythonJobfails mid-iteration. The fullIsolatedImpurityWorkChainmust be resubmitted. - Force units from ASE calculators are assumed to be eV/Å; ensure your MLIP returns forces in these units.
- Models requiring GPU execution need a compute resource with a GPU and the appropriate
pythonjobcode configured for that environment.