Python bindings#
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.
Installation#
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)
Building from source#
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 |
|
|
|
LAPACK + BLAS |
|
|
|
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 PyPI
Command-line interface#
Installing 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 solvent
Variable-cell optimisation (L-BFGS + ExpCellFilter, periodic systems only):
gfnff crystal.cif --optcell
gfnff crystal.cif --optcell --fmax 0.01
Full option list: gfnff --help
The trajectory file (gfnff.log.extxyz) stores energy and forces in each
frame header, compatible with ASE’s ase gui.
Low-level API (GFNFFCalculator)#
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-PBC
Periodic 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 charge
Force-field versions, custom parameter files and user-supplied molecular graphs are described in parametrisation.md.
ASE Calculator (GFNFF)#
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 / ų, Voigt
Variable-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)
Hessian and vibrations#
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 ASE
get_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 |
|---|---|---|
|
|
Total charge. Also reads |
|
|
Implicit solvent name: |
|
|
Fortran output verbosity (0 = silent, 3 = verbose). |
|
|
Force-field version; see parametrisation.md. |
|
|
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.
Running the tests#
pip install "gfnff[test]"
pytest python/tests/