Skip to content

OpenMM Backend

The OpenMMBackend is Q2MM's most versatile backend, supporting both Harmonic and MM3 functional forms. It runs in-process via the OpenMM Python API, avoiding subprocess overhead.


Installation

OpenMM is available via conda-forge:

conda install -c conda-forge openmm

Or with pip:

pip install openmm

For GPU support (via CUDA), install the CUDA plugin package:

pip install OpenMM-CUDA-12

This provides CUDA plugin binaries that JIT-compile kernels via NVRTC, supporting all NVIDIA architectures including Blackwell (RTX 5090). Works on Linux, WSL2, and native Windows — requires an NVIDIA GPU and a compatible driver (≥ 535).

WSL2 recommended for GPU benchmarks

For GPU benchmarks and the full CUDA stack (JAX CUDA + JAX-MD + OpenMM CUDA), WSL2 is the recommended environment on Windows. Native Windows supports OpenMM CUDA but not JAX CUDA or JAX-MD.

Avoid OpenCL for GPU acceleration

OpenCL on modern NVIDIA GPUs (e.g. RTX 5090) gives very poor GPU utilisation (~14%). Always prefer CUDA over OpenCL when an NVIDIA GPU is available. The auto-detection order (CUDA > OpenCL > CPU) ensures CUDA is selected first when both plugins are installed.

Verify installation

import openmm
print(openmm.version.full_version)
print(openmm.Platform.getNumPlatforms(), "platforms available")

Platform detection

OpenMMBackend auto-detects the fastest available compute platform:

Priority Platform Notes
1 CUDA Requires NVIDIA GPU + CUDA toolkit
2 OpenCL AMD/Intel GPUs
3 CPU Multi-threaded, available everywhere
4 Reference Single-threaded, for debugging only

Override with the platform_name constructor parameter if needed.


Supported energy terms

Term Harmonic Mode MM3 Mode
Bonds ✅ Harmonic ✅ Cubic/quartic
Angles ✅ Harmonic ✅ Sextic
Torsions
Improper torsions
vdW (LJ 12-6)
vdW (Buckingham exp-6)
Electrostatics
1-4 scaling ✅ AMBER (ε/2) None (MM3)

Configuration

from q2mm.backends.mm import OpenMMBackend

backend = OpenMMBackend(
    platform_name=None,   # auto-detect (CUDA > OpenCL > CPU > Reference)
    precision=None,       # "single", "mixed", or "double" (GPU only; default: "mixed")
)
Parameter Type Default Description
platform_name str \| None None Force a specific OpenMM platform
precision str \| None None GPU precision mode; ignored on CPU

Runtime parameter updates

OpenMMBackend.prepare(PreparationRequest(...)) returns a prepared session that owns a reusable OpenMM Context. Each typed request carries a full parameter vector, and the session updates the context's force parameters without rebuilding the system. This makes iterative optimization fast while keeping all evaluations behind the typed prepared-session contract.


Capabilities

Prepared-session operation Supported Notes
energy(EnergyRequest) Returns kcal/mol
minimize(MinimizationRequest) OpenMM L-BFGS minimizer
hessian(HessianRequest) Numerical (finite-difference)
frequencies(FrequencyRequest) From numerical Hessian
parameter_gradient(ParameterGradientRequest) Exact for bond/angle/torsion; vdW via finite differences
Capability.REUSABLE_STATE Prepared session reuses the OpenMM context

Serialization

Standalone force-field XML can be written with q2mm.io.save_openmm_xml(force_field, path, molecule=...). The new backend surface does not expose a generic System XML exporter; prepared sessions are for typed evaluations, not file I/O.


Limitations

  • Numerical Hessianshessian() uses finite differences, not analytical second derivatives. Accurate but slower than JAX's analytical Hessian.
  • Partial analytical gradientsparameter_gradient() provides exact gradients for bond, angle, and torsion parameters via OpenMM global-parameter derivatives. vdW parameter gradients are supplemented via central finite differences.
  • No improper torsions — not yet implemented.
  • No electrostatics — charge optimization is not supported.

Example

from q2mm.backends.contracts import EnergyRequest, FrequencyRequest, PreparationRequest
from q2mm.backends.mm.openmm import OpenMMBackend
from q2mm.io.amber import load_amber_frcmod
from q2mm.io.xyz import load_xyz

# Load molecule and force field
mol = load_xyz("molecule.xyz")
ff = load_amber_frcmod("params.frcmod")

backend = OpenMMBackend()
session = backend.prepare(PreparationRequest(case_id="example", molecule=mol, force_field=ff))
params = session.layout.vector(ff)

# Single-point energy
e = session.energy(EnergyRequest(parameters=params)).energy
print(f"Energy: {e:.4f} kcal/mol")

# Frequencies
freqs = session.frequencies(FrequencyRequest(parameters=params)).frequencies
print(f"Frequencies: {freqs}")

See also