English | δΈζ
MLFCS is an ASE-first Python library for calculating symmetry-reduced force constants from atomic forces. It supports second and arbitrarily higher orders through one order-parameterized pipeline, with third- and fourth-order calculations as the primary production-validated paths. Fifth and higher orders can also be calculated and exported through the generic sparse HDF5 format; practical size is determined by cluster count, cutoff, supercell size, and available memory.
Finite differences use ASE, NumPy, and SciPy only, so an external calculator retains complete control over CPU or GPU execution. The joint fitter supports CPU and GPU: a CUDA-enabled JAX installation accelerates only the dense Wick feature kernel, while geometry, symmetry, sparse constraints, and final solving remain host-side. Memory is controlled through symmetry reduction, contiguous sparse arrays, lazy dense materialization, constraint null-space coordinates, and bounded feature tiles. Static JAX buffers and compiled kernels are prepared once per fit and reused for training, validation, and diagnostics.
The base package does not prescribe how forces are generated. Structures can be evaluated with any user-owned ASE Calculator or dispatched to an external workflow. The native SSCHA module combines q-space quantum harmonic sampling with the MLFCS Gram fitter to calculate temperature-dependent effective second-order force constants.
MLFCS provides a Python API only; it has no CLI.
Development branch: The joint force-only FC2--FCn fitting API described below is currently developed on the
devbranch. The stablemainbranch retains the finite-difference API and common force-constant I/O. This label will be removed when fitting validation is promoted.
An order-n force constant is the nth derivative of potential energy with respect to atomic
displacements, or equivalently an (n-1)-fold derivative of force. MLFCS computes it as follows:
ASE primitive structure
β
βΌ
deterministic supercell and neighbor cutoff
β
βΌ
space-group and permutation-reduced cluster orbits
β
βΌ
recursive central-difference displacement plan
β
βΌ
user-provided forces
β
βΌ
sparse symmetry reconstruction and optional sum-rule projection
β
βΌ
ForceConstants β HDF5 / ShengBTE / phonopy-compatible formats
Space-group symmetry, force-constant index permutations, and stabilizer constraints reduce each cluster tensor to independent components. Only those components are sampled. The reconstructed result remains sparse until a dense representation is explicitly requested.
The acoustic sum rule (ASR) is imposed as a constrained projection in the independent orbit parameter space:
sum over one atom index of Phi(i1, ..., in) = 0
All constraint systems use a sparse, matrix-free LSMR projection. Harmonic calculations may also enable the optional Born-Huang rotational sum rules; see Sum rules.
- One API and one numerical pipeline for
order >= 2. - Production-tested third- and fourth-order force constants.
- Analytic FC4 validation against an independently differentiated FCC Morse energy, including second-order finite-difference step convergence.
- End-to-end second- and fifth-order validation.
- ASE
Atomsand ASECalculatorat the public boundary. - External, checkpoint-friendly
sow()/reap()workflow. - Optional direct-calculator zero-step extrapolation with configurable even-power degree.
- Stable configuration IDs and explicit atom-order mappings.
- Joint periodic-image cluster cutoff geometry.
- Recursive central-difference stencils.
- CPU/GPU fitting with persistent JAX Wick-feature kernels; finite differences stay host-side.
- Reusable JAX JIT and batched contractions for high-rank fitting throughput.
- Contiguous sparse arrays, matrix-free actions, and lazy materialization to reduce peak memory.
- Displacement-key deduplication to reduce expensive calculator evaluations.
- Joint force-only FC2--FCn fitting with Wick-orthogonalized features and Taylor-compatible force-constant output.
- Strict translational ASR using sparse matrix-free LSMR.
- Optional Born-Huang rotational sum rules for FC2.
- Generic sparse HDF5 for any order.
- ShengBTE output for orders 3 and 4.
- Full dense phonopy text output for order 2.
- Native quantum/classical SSCHA with arbitrary ASE calculators.
MLFCS requires Python 3.12 or newer. Install and run it with uv:
uv syncRunnable API examples are available in examples/:
basic_fc2.pyruns FC2 directly with ASE's built-in EMT calculator;vasp_external_fc3.pyimplements a complete external VASPsow/ force collection /reapworkflow;nep89_orders.pyevaluates one or more orders with a user-supplied NEP89 model through calorine's ASE calculator.
Calculator packages such as calorine or MACE are intentionally not base dependencies. Install the calculator required by your application separately.
| Quantity | Unit |
|---|---|
| Cell, positions, and displacements | Γ |
| Forces | eV/Γ |
Order-n force constants |
eV/Γ βΏ |
| Positive cutoff | Γ |
| Negative integer cutoff | Neighbor shell |
None cutoff |
Maximum radius enumerable by the supercell |
JAX numerical kernels use 64-bit floating point.
Before calculating force constants, decide which program will consume them and establish its
primitive-cell and supercell conventions first. For the most reliable workflow, use the primitive
cell and reference supercell generated or validated by that downstream program, then pass those
same ASE Atoms objects to MLFCS. MLFCS can validate and convert equivalent primitive and
supercell representations, including atom reorderings and integral basis changes, but starting
from the downstream program's own structures avoids unnecessary mapping ambiguity at the final
export boundary.
sow() does not write files or run a DFT program. It returns an ordered Python list of displaced
ASE Atoms objects. The user chooses how to serialize and calculate them: ASE can write each
structure as POSCAR-xxx for VASP or in another calculator's input format, after which the jobs
can be submitted by any local scheduler. When the calculations finish, use ASE to read each
result, extract its forces, restore the sow order (or key the forces by configuration ID), and
pass only those forces to reap(). If this positional order is guaranteed, configuration IDs
are not required.
For example, a positional VASP workflow is:
from pathlib import Path
import numpy as np
from ase.io import read, write
from mlfcs import ForceConstantCalculation
calculation = ForceConstantCalculation(
read("POSCAR"),
order=3,
reference=read("reference-supercell.vasp"),
cutoff=-5,
displacement=0.01,
symprec=1e-5,
)
# 1. sow(): obtain the displaced ASE structures in the exact reap order.
structures = calculation.sow()
Path("vasp-jobs").mkdir(exist_ok=True)
for configuration_id, atoms in enumerate(structures):
job = Path("vasp-jobs") / f"POSCAR-{configuration_id + 1:03d}"
job.mkdir(exist_ok=True)
write(job / "POSCAR", atoms, format="vasp", direct=True, vasp5=True)
# 2. The user supplies INCAR, KPOINTS, POTCAR and submits every directory.
# MLFCS does not launch or configure VASP.
# 3. Read completed results with ASE in the same filename/order convention.
forces = []
for configuration_id in range(len(structures)):
job = Path("vasp-jobs") / f"POSCAR-{configuration_id + 1:03d}"
completed = read(job / "vasprun.xml", index=-1)
forces.append(completed.get_forces())
forces = np.asarray(forces)
# 4. reap(): the force at forces[i] must belong to structures[i].
fc3 = calculation.reap(forces, acoustic_sum_rule=True)
fc3.write("fc3.h5", format="hdf5")For Quantum ESPRESSO, ABINIT, CP2K, or another external program, replace only the ASE write()
and read() formats and provide that program's required input parameters. The sow/reap contract
is unchanged. Positional reap() needs no metadata when file names and returned forces preserve
the exact sow order. File formats such as POSCAR do not preserve the Python atoms.info metadata,
so a manifest containing the filename-to-configuration-ID relation is recommended
for out-of-order jobs, restarts, long-term archives, and accidental-dataset detection. The complete
vasp_external_fc3.py example implements this optional safety
layer, force collection, missing-result checks, and final export; see the
external VASP workflow guide.
The force array must have shape:
(len(calculation.sow()), len(calculation.supercell), 3)
While the structures remain as ASE objects, every displaced structure also contains optional audit metadata:
atoms.info["mlfcs_configuration_id"]
atoms.info["mlfcs_atom_order"]
atoms.arrays["mlfcs_displacement"]When jobs return out of order, read them in any order and pass a mapping keyed by the original zero-based configuration ID:
fc3 = calculation.reap(forces_by_configuration_id)Missing or extra IDs, invalid shapes, non-finite values, and plan-hash mismatches are rejected.
calculator = make_my_ase_calculator()
fc3 = calculation.run(
calculator,
progress=lambda done, total: print(f"{done}/{total}"),
)Calculator evaluation is serial by design to avoid multiplying the memory used by large machine
learning potentials. Use sow() / reap() when external parallelism or checkpointing is needed.
Direct ASE-calculator runs can optionally extrapolate the selected order to zero displacement:
fc4 = calculation.run(
calculator,
derivative_backend="extrapolate",
extrapolation_spacing=0.005,
extrapolation_side_steps=2,
extrapolation_degree=1,
)For a central displacement of 0.03 Γ
, this samples 0.02, 0.025, 0.03, 0.035, and
0.04 Γ
. The default degree 1 fits D(h) = D0 + c2 hΒ²; higher degrees fit additional even
powers. This backend is intentionally available only through run(), not external sow() /
reap(). See Zero-step extrapolation.
For explicit checkpointing:
forces = calculation.evaluate(calculator)
np.savez_compressed("forces.npz", forces=forces)
fc3 = calculation.reap(forces)Stage reporting is enabled by default for sow(), reap(), and direct ASE-calculator runs.
It reports symmetry, cluster, displacement-plan, ASR, and force-evaluation progress using values
already computed by the calculation. Pass verbose=False to silence all stage and cutoff output.
report_cutoff=False suppresses only the detailed neighbor-shell lines.
The same constructor is used for all supported orders:
fc2_calculation = ForceConstantCalculation(atoms, order=2, cutoff=-6)
fc4_calculation = ForceConstantCalculation(atoms, order=4, cutoff=-3)
fc5_calculation = ForceConstantCalculation(atoms, order=5, cutoff=-1)
full_calculation = ForceConstantCalculation(atoms, order=3, cutoff=None)A positive cutoff is a radius in Γ
. A negative integer selects a one-based neighbor shell. For
example, cutoff=-8 selects the eighth shell. cutoff=None selects the maximum radius that the
current finite supercell can enumerate; it does not mean an unbounded interaction range. MLFCS
reports both the supercell capacity and the selected radius:
Supercell neighbor limit: maximum shell = 33, maximum cutoff radius = 15.7504983443 Γ
Selected neighbor cutoff: shell = 8, cutoff radius = 7.5419604204 Γ
The first line is a capacity diagnostic for the finite supercell. The second line is the cutoff
actually used. Requests beyond the enumerable capacity are rejected. Use report_cutoff=False
to suppress both lines.
Higher orders grow combinatorially through cluster combinations, tensor components, permutations, and finite-difference signs. Use small cutoffs first and monitor configuration count and memory.
The reference supercell order is authoritative. sow() returns that exact order, and every force
array passed to reap() must use it unchanged. There is no internal/grouped atom-order mode and
MLFCS never silently reorders external forces or fitting snapshots. Construct a calculation either
from an integer general supercell matrix or from an explicit reference frame:
calculation = ForceConstantCalculation(primitive, reference=reference_supercell, order=3)To build that reference explicitly, use the stable public helper:
from mlfcs.tools import build_supercell
reference_supercell = build_supercell(primitive, [[2, 1, 0], [0, 2, 0], [0, 0, 1]])This helper defaults to phonopy old-style atom ordering while retaining MLFCS's
supercell-matrix convention. Pass ordering="thirdorder" explicitly for the
former cell-major order. ordering="phonopy_snf" is reserved for future
compatibility and currently reports that it is not implemented. An explicitly
provided reference_supercell is never reordered.
Format writers create any required format-specific ordering only at the export boundary.
For independently reordered snapshots, call mlfcs.align_structures(reference, atoms) explicitly;
it returns the aligned structure and its maximum periodic matching residual.
ASR is enabled by default:
constrained = calculation.reap(forces, acoustic_sum_rule=True)
raw = calculation.reap(forces, acoustic_sum_rule=False)The constrained result is the nearest solution in independent parameter space that satisfies translational invariance. Permutation symmetry supplies equivalent constraints on the other atom axes.
Born-Huang and Huang conditions are explicit FC2-only postprocessing, shared by finite
difference and fitting results. The strict default is strength=1.0; FC3 and higher orders are
not changed:
constrained = result.enforce_harmonic_constraints(
born_huang=True,
huang=True,
)The projector always enforces FC2 ASR, uses all tied nearest periodic images, and reports its
residuals and correction. strength in [0, 1] scales only the Born-Huang/Huang correction.
See Sum rules.
The output format is always explicit:
fc2.write("FORCE_CONSTANTS", format="phonopy")
fc2.write("fc2.hdf5", format="phonopy_hdf5")
fc3.write("fc3.h5", format="hdf5")
fc3.write("fc3.hdf5", format="phono3py_hdf5")
fc3.write("FORCE_CONSTANTS_3RD", format="shengbte")
fc4.write("FORCE_CONSTANTS_4TH", format="shengbte")
fc234.write("force_constants.xml", format="alamode")Read the native sparse HDF5 format through the matching public API:
from mlfcs import read_hdf5
fc234 = read_hdf5("fc3.h5")| Format | Orders | Representation |
|---|---|---|
hdf5 |
Any | Native v2 lattice-labelled sparse IFCs (sites, translation representatives, Cartesian tensors) |
shengbte |
3 and 4 | Symmetry-closed translation-based text blocks |
phonopy |
2 | Full dense supercell FC2 text |
phonopy_hdf5 |
2 | Phonopy-compatible full-supercell force_constants HDF5 |
phono3py_hdf5 |
3 | Phono3py-compatible full-supercell fc3 HDF5 |
alamode |
2--4 | Combined ALAMODE FCSXML document |
ShengBTE output writes the symmetry-closed cluster support carried by the reconstructed sparse result and resolves its lattice residues to jointly compatible minimum images.
The phonopy and phono3py HDF5 writers preserve the explicit reference-supercell order and stream
one first-atom slab at a time. They therefore do not materialize the full FC3 in memory. The native
hdf5 format is native schema v2: it stores primitive and reference structures, their verified
mapping, and lattice-labelled sparse IFCs. Older native schemas are intentionally unsupported.
ALAMODE XML preserves the exact atom order of fc.supercell. Primitive-atom identities and
translation mappings come exclusively from MLFCS's primitive_index and cell_translation
metadata; export does not ask spglib or ALAMODE to rediscover or reorder the cell. Use order=2,
3, or 4 to write one available order, or omit it to combine all available FC2--FC4 orders.
See the ALAMODE XML guide for the mapping and periodic-image contract.
Sparse HDF5 is recommended for high orders. Dense materialization is explicit and emits a warning above the default 2 GB advisory budget:
fc5.write("fc5.h5", format="hdf5")
dense = fc5.materialize(5)
dense = fc5.materialize(5, max_bytes=None) # explicitly disable the warning budgetThe independent mlfcs.anharmonic.sscha module fits a temperature-dependent effective harmonic FC2 from
thermally sampled forces:
from mlfcs.anharmonic.sscha import SSCHA
sscha = SSCHA(
atoms,
reference=read("reference-supercell.vasp"),
temperature=300,
statistics="quantum",
snapshots=1000,
max_iterations=10,
random_seed=42,
)
sscha.run(calculator)
sscha.use_average(last=5)
sscha.write("fc2-300K.hdf5", format="hdf5")Iteration zero fits an initial FC2 from small random Cartesian displacements. Each subsequent
iteration samples the canonical harmonic ensemble in compact-FC2 q space and refits FC2 with the
native streamed-Gram fitter. Thus max_iterations=10 performs one initialization and ten updates.
External per-iteration execution is also supported:
structures = sscha.sow()
result = sscha.reap(
forces_by_configuration_id,
energies=energies_by_configuration_id,
reference_energy=equilibrium_supercell_energy,
)Forces are sufficient for FC2 fitting. Energies are required only for the free-energy estimate.
Completed iterations and sampling diagnostics are stored in sscha.history. Phonopy-compatible
text and HDF5 output use the shared MLFCS writers without requiring phonopy at runtime.
Canonical iterations also report the relative FC2 update, while the trial sampling Hamiltonian
remains an internal detail.
This is a stochastic effective-harmonic method, not an explicit FC3 bubble or FC4 loop calculation. See the SSCHA guide for details.
- Third and fourth order are the main production-tested finite-difference paths.
- Higher orders use the same implementation but can become prohibitively expensive.
- ShengBTE output is limited to orders 3 and 4.
- Explicit FC3 bubble and FC4 loop self-energies are not implemented.
- Non-analytic electrostatic corrections are not part of the base reconstruction pipeline.
- SSCHA stopping criteria are controlled by the caller; no universal automatic convergence threshold is imposed.
- Full bilingual docs: English site and δΈζ site. See runnable examples and development validation
All commands use uv and tests run serially:
uv sync
uv run pytest
uv run ruff check src tests examples
uv run ruff format --check src tests examples
uv buildLocal tests are deterministic unit and public-API regressions. Material comparisons and third-party
transport workflows are documented under examples/ and are run manually. CI only builds
the bilingual documentation sites. The test organization is documented in tests/README.md.
The current development version is 4.0.0a2 (4.0 alpha 2). See CHANGELOG.md for release notes and
CONTRIBUTING.md for the development workflow.
MLFCS is distributed under the GNU General Public License v3.0 or later. Adapted third-party components and redistributed reference data are documented in Third-party terms for the ALAMODE adapter are retained directly in its source module.