Python bindings are provided through a ctypes-based interface.
The shared library is bundled into a binary wheel, so no Fortran or C compiler
is needed at install time.
Binary wheels for Linux (x86_64, aarch64) and macOS (arm64) are published on PyPI:
pip install gfnff # library only
pip install "gfnff[ase]" # + ASE (enables the CLI and the ASE calculator)The source build compiles the Fortran library on your machine. The following system packages must be present before running pip:
| Dependency | Example (Debian/Ubuntu) | Example (Fedora/RHEL) | Example (macOS) |
|---|---|---|---|
| Fortran compiler | apt install gfortran |
dnf install gcc-gfortran |
brew install gcc |
| LAPACK + BLAS | apt install libopenblas-dev |
dnf install openblas-devel |
brew install openblas |
| CMake ≥ 3.21 | installed by pip automatically | ← | ← |
Once those are in place:
pip install ".[ase]" # from a checkout
pip install "gfnff[ase]" --no-binary gfnff # force source build from PyPIInstalling gfnff[ase] places a gfnff executable on your PATH.
gfnff <input> [options]
The input file is read by ASE, so any format it supports works (xyz, extxyz, POSCAR, cif, …).
Singlepoint (default):
gfnff molecule.xyz
gfnff molecule.xyz --chrg -1
gfnff molecule.xyz --alpb h2o # implicit solvation (--solv is an alias)Geometry optimisation (L-BFGS via ASE, cell fixed, writes gfnff.log.extxyz):
gfnff molecule.xyz --opt
gfnff molecule.xyz --opt --fmax 0.05 # looser convergence, eV/Å
gfnff molecule.xyz --opt --outfile path.xyz # custom trajectory file
gfnff molecule.xyz --opt --alpb acetone # optimise in solventVariable-cell optimisation (L-BFGS + ExpCellFilter, periodic systems only):
gfnff crystal.cif --optcell
gfnff crystal.cif --optcell --fmax 0.01Full option list: gfnff --help
The trajectory file (gfnff.log.extxyz) stores energy and forces in each
frame header, compatible with ASE's ase gui.
GFNFFCalculator mirrors the C API one-to-one.
All quantities use the same units as the library itself: Bohr for coordinates and lattice, Hartree for energy, Eh/Bohr for gradients, and Hartree for the stress tensor.
import numpy as np
from gfnff import GFNFFCalculator
# Atomic numbers and coordinates in Bohr
numbers = np.array([6, 8, 1, 1], dtype=np.int32) # CO + 2 H
positions = np.array([[0, 0, 0], [2.1, 0, 0],
[-1.0, 0, 0], [3.1, 0, 0]], dtype=np.float64)
with GFNFFCalculator(numbers, positions, charge=0, printlevel=0) as calc:
energy, gradient, sigma = calc.singlepoint(numbers, positions)
print(f"Energy: {energy:.6f} Eh")
print(f"Gradient shape: {gradient.shape}") # (nat, 3)
print(f"Stress tensor:\n{sigma}") # (3, 3), Hartree; zero for non-PBCPeriodic systems use a separate initialiser:
calc = GFNFFCalculator(
numbers, positions,
lattice=lattice_bohr, # shape (3, 3), rows are lattice vectors
npbc=3,
)Partial charges are those of the last singlepoint, where they are obtained as part of the energy evaluation:
with GFNFFCalculator(numbers, positions) as calc:
calc.singlepoint(numbers, positions)
q = calc.charges() # (nat,), in e; sums to the total chargeForce-field versions, custom parameter files and user-supplied molecular graphs are described in parametrisation.md.
GFNFF is a fully compatible ASE Calculator.
It handles unit conversion automatically (Å ↔ Bohr, eV ↔ Hartree).
Implemented properties: energy, forces, stress, charges.
from ase.build import molecule
from gfnff import GFNFF
atoms = molecule("caffeine")
atoms.calc = GFNFF()
energy = atoms.get_potential_energy() # eV
forces = atoms.get_forces() # eV / Å, shape (nat, 3)
stress = atoms.get_stress() # eV / ų, Voigt [xx,yy,zz,yz,xz,xy]; zero for non-PBC
charges = atoms.get_charges() # e, shape (nat,)Periodic systems work the same way; provide an atoms object with cell and pbc set:
from ase.io import read
from gfnff import GFNFF
atoms = read("quartz.cif")
atoms.calc = GFNFF()
print(atoms.get_potential_energy()) # eV / unit cell
print(atoms.get_stress()) # eV / ų, VoigtVariable-cell relaxation via ASE's ExpCellFilter:
from ase.filters import ExpCellFilter
from ase.optimize import LBFGS
opt = LBFGS(ExpCellFilter(atoms))
opt.run(fmax=0.01)The Hessian is an explicit method rather than one of implemented_properties,
so it is never computed as a side effect of an MD or optimisation step:
hessian = atoms.calc.get_hessian(atoms) # (3N, 3N), eV / Ų
vib = atoms.calc.get_vibrations(atoms) # ase.vibrations.VibrationsData
vib.get_energies() # frequencies, normal modes,
vib.get_modes() # thermochemistry, all from ASEget_vibrations() passes the analytic Hessian directly to ASE, which avoids the 6N
displaced singlepoints that ase.vibrations.Vibrations would otherwise run. Results
for an unchanged geometry are cached, so asking for frequencies and modes does
not evaluate it twice. Non-periodic systems only.
Additional options:
| Parameter | Default | Description |
|---|---|---|
charge |
0 |
Total charge. Also reads atoms.info["charge"] (takes precedence). |
solvent |
"" |
Implicit solvent name: "h2o", "acetone", "chcl3", … (molecular systems only) |
printlevel |
0 |
Fortran output verbosity (0 = silent, 3 = verbose). |
version |
None |
Force-field version; see parametrisation.md. |
parametrisation |
None |
Path to a parameter file, overlaid on that version. |
Changing any of these with calc.set(...) rebuilds the underlying force field
rather than reusing the cached result.
pip install "gfnff[test]"
pytest python/tests/