Skip to content

Tinker Engine

tinker

Tinker molecular mechanics engine backend.

Wraps Tinker executables (analyze, minimize, vibrate) for MM calculations with MM3 and other force fields.

Requires: Tinker binaries on PATH or configured via tinker_dir parameter. Download from: https://dasher.wustl.edu/tinker/

TinkerEngine

TinkerEngine(tinker_dir: str = None, params_file: str = None, bond_tolerance: float = 1.3)

Bases: MMEngine

Molecular mechanics engine using Tinker.

Parameters:

Name Type Description Default
tinker_dir str

Path to Tinker bin directory (auto-detected if None)

None
params_file str

Path to MM3 parameter file (auto-detected if None)

None
bond_tolerance float

Distance multiplier for bond detection. Two atoms are bonded when their distance is within bond_tolerance * (r_cov_A + r_cov_B). Default 1.3.

1.3

Initialize the Tinker engine.

Parameters:

Name Type Description Default
tinker_dir str

Path to Tinker bin directory. Auto-detected if None.

None
params_file str

Path to MM3 parameter file. Auto-detected if None.

None
bond_tolerance float

Distance multiplier for bond detection. Two atoms are bonded when their distance is within bond_tolerance * (r_cov_A + r_cov_B).

1.3

Raises:

Type Description
FileNotFoundError

If Tinker binaries or the MM3 parameter file cannot be found.

Source code in q2mm/backends/mm/tinker.py
def __init__(self, tinker_dir: str = None, params_file: str = None, bond_tolerance: float = 1.3):
    """Initialize the Tinker engine.

    Args:
        tinker_dir: Path to Tinker bin directory. Auto-detected if
            ``None``.
        params_file: Path to MM3 parameter file. Auto-detected if
            ``None``.
        bond_tolerance: Distance multiplier for bond detection. Two
            atoms are bonded when their distance is within
            ``bond_tolerance * (r_cov_A + r_cov_B)``.

    Raises:
        FileNotFoundError: If Tinker binaries or the MM3 parameter file
            cannot be found.
    """
    self._tinker_dir = tinker_dir or _find_tinker_dir()
    self._bond_tolerance = bond_tolerance
    if self._tinker_dir is None:
        raise FileNotFoundError(
            "Tinker not found. Install from https://dasher.wustl.edu/tinker/ or pass tinker_dir parameter."
        )

    if params_file is None:
        # Try common locations for MM3 params
        candidates = [
            os.path.join(os.path.dirname(self._tinker_dir), "params", "mm3.prm"),
            os.path.join(self._tinker_dir, "mm3.prm"),
        ]
        for c in candidates:
            if os.path.isfile(c):
                params_file = c
                break
    self._params_file = params_file
    if self._params_file is None:
        raise FileNotFoundError(
            "MM3 parameter file not found. Provide params_file parameter "
            "or place mm3.prm alongside the Tinker bin directory."
        )

name property

name: str

Human-readable engine name.

Returns:

Name Type Description
str str

"Tinker".

supported_functional_forms

supported_functional_forms() -> frozenset[str]

Tinker with MM3 params supports MM3 functional forms only.

Returns:

Type Description
frozenset[str]

frozenset[str]: {"mm3"}.

Source code in q2mm/backends/mm/tinker.py
def supported_functional_forms(self) -> frozenset[str]:
    """Tinker with MM3 params supports MM3 functional forms only.

    Returns:
        frozenset[str]: ``{"mm3"}``.
    """
    return frozenset({"mm3"})

is_available

is_available() -> bool

Check if Tinker analyze executable is accessible.

Returns:

Name Type Description
bool bool

True if the executable can be located.

Source code in q2mm/backends/mm/tinker.py
def is_available(self) -> bool:
    """Check if Tinker ``analyze`` executable is accessible.

    Returns:
        bool: ``True`` if the executable can be located.
    """
    try:
        _exe(self._tinker_dir, "analyze")
        return True
    except FileNotFoundError:
        return False

energy

energy(structure, forcefield=None) -> float

Calculate MM energy in kcal/mol.

Parameters:

Name Type Description Default
structure str | Q2MMMolecule

Path to XYZ file or :class:Q2MMMolecule.

required
forcefield ForceField | dict | None

Force field or atom-type mapping. Uses default MM3 types if None.

None

Returns:

Name Type Description
float float

Total potential energy in kcal/mol.

Raises:

Type Description
RuntimeError

If the energy cannot be parsed from Tinker output.

Source code in q2mm/backends/mm/tinker.py
def energy(self, structure, forcefield=None) -> float:
    """Calculate MM energy in kcal/mol.

    Args:
        structure (str | Q2MMMolecule): Path to XYZ file or :class:`Q2MMMolecule`.
        forcefield (ForceField | dict | None): Force field or atom-type mapping. Uses default MM3
            types if ``None``.

    Returns:
        float: Total potential energy in kcal/mol.

    Raises:
        RuntimeError: If the energy cannot be parsed from Tinker output.
    """
    with tempfile.TemporaryDirectory(prefix="q2mm_tinker_") as workdir:
        txyz = self._write_tinker_xyz(structure, forcefield, workdir)
        result = self._run_tinker("analyze", txyz, ["E"])
        for line in result.stdout.split("\n"):
            if "Total Potential Energy" in line:
                return float(line.split(":")[1].split()[0])
    raise RuntimeError(f"Could not parse energy from Tinker output:\n{result.stdout}")

minimize

minimize(structure, forcefield=None, rms_grad: float = 0.01) -> tuple

Energy-minimize structure.

Parameters:

Name Type Description Default
structure str | Q2MMMolecule

Path to XYZ file or :class:Q2MMMolecule.

required
forcefield ForceField | dict | None

Force field or atom-type mapping. Uses default MM3 types if None.

None
rms_grad float

RMS gradient convergence criterion in kcal/mol/Å.

0.01

Returns:

Type Description
tuple

tuple[float, list[str], np.ndarray]: (energy, atoms, coords) where energy is in kcal/mol and coords are in Å.

Raises:

Type Description
RuntimeError

If the energy cannot be parsed from output or the minimized coordinate file is not found.

Source code in q2mm/backends/mm/tinker.py
def minimize(self, structure, forcefield=None, rms_grad: float = 0.01) -> tuple:
    """Energy-minimize structure.

    Args:
        structure (str | Q2MMMolecule): Path to XYZ file or :class:`Q2MMMolecule`.
        forcefield (ForceField | dict | None): Force field or atom-type mapping. Uses default MM3
            types if ``None``.
        rms_grad: RMS gradient convergence criterion in kcal/mol/Å.

    Returns:
        tuple[float, list[str], np.ndarray]: ``(energy, atoms, coords)``
            where energy is in kcal/mol and coords are in Å.

    Raises:
        RuntimeError: If the energy cannot be parsed from output or the
            minimized coordinate file is not found.
    """
    with tempfile.TemporaryDirectory(prefix="q2mm_tinker_") as workdir:
        txyz = self._write_tinker_xyz(structure, forcefield, workdir)
        result = self._run_tinker("minimize", txyz, [str(rms_grad)])

        # Parse final energy
        energy = None
        for line in result.stdout.split("\n"):
            if "Final Function Value" in line:
                energy = float(line.split(":")[1].strip().split()[0])

        if energy is None:
            raise RuntimeError(f"Could not parse energy from Tinker minimize output:\n{result.stdout}")

        # Read minimized coordinates from .xyz_2 output
        min_xyz = txyz + "_2"
        if not os.path.exists(min_xyz):
            raise RuntimeError(f"Minimized file not found: {min_xyz}")

        atoms = []
        coords = []
        with open(min_xyz) as f:
            lines = f.readlines()
        for line in lines[1:]:
            parts = line.split()
            if len(parts) >= 6:
                atoms.append(parts[1])
                coords.append([float(parts[2]), float(parts[3]), float(parts[4])])

        return energy, atoms, np.array(coords)

hessian

hessian(structure, forcefield=None) -> ndarray

Calculate MM Hessian matrix.

Note

Full Hessian extraction from Tinker requires the testhess program. Use :meth:frequencies for vibrational analysis instead.

Parameters:

Name Type Description Default
structure str | Q2MMMolecule

Path to XYZ file or :class:Q2MMMolecule.

required
forcefield ForceField | dict | None

Force field or atom-type mapping.

None

Returns:

Type Description
ndarray

np.ndarray: Not implemented — always raises.

Raises:

Type Description
NotImplementedError

Always raised; use :meth:frequencies instead.

Source code in q2mm/backends/mm/tinker.py
def hessian(self, structure, forcefield=None) -> np.ndarray:
    """Calculate MM Hessian matrix.

    Note:
        Full Hessian extraction from Tinker requires the ``testhess``
        program. Use :meth:`frequencies` for vibrational analysis instead.

    Args:
        structure (str | Q2MMMolecule): Path to XYZ file or :class:`Q2MMMolecule`.
        forcefield (ForceField | dict | None): Force field or atom-type mapping.

    Returns:
        np.ndarray: Not implemented — always raises.

    Raises:
        NotImplementedError: Always raised; use :meth:`frequencies`
            instead.
    """
    raise NotImplementedError("Full Hessian extraction not yet implemented. Use frequencies() instead.")

frequencies

frequencies(structure, forcefield=None) -> list[float]

Calculate vibrational frequencies in cm⁻¹.

Parameters:

Name Type Description Default
structure str | Q2MMMolecule

Path to XYZ file or :class:Q2MMMolecule.

required
forcefield ForceField | dict | None

Force field or atom-type mapping. Uses default MM3 types if None.

None

Returns:

Type Description
list[float]

list[float]: Vibrational frequencies in cm⁻¹.

Source code in q2mm/backends/mm/tinker.py
def frequencies(self, structure, forcefield=None) -> list[float]:
    """Calculate vibrational frequencies in cm⁻¹.

    Args:
        structure (str | Q2MMMolecule): Path to XYZ file or :class:`Q2MMMolecule`.
        forcefield (ForceField | dict | None): Force field or atom-type mapping. Uses default MM3
            types if ``None``.

    Returns:
        list[float]: Vibrational frequencies in cm⁻¹.
    """
    with tempfile.TemporaryDirectory(prefix="q2mm_tinker_") as workdir:
        txyz = self._write_tinker_xyz(structure, forcefield, workdir)
        result = self._run_tinker("vibrate", txyz, stdin="A\n")
        freqs = []
        for m in re.finditer(r"Frequency\s+([-\d.]+)\s+cm-1", result.stdout):
            freqs.append(float(m.group(1)))
        return freqs