From 69ddbdd522ebc5560d39a1d1461955fb08978d1a Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Sat, 27 Jul 2024 18:34:10 +0000 Subject: [PATCH] Deployed 0e4d857 to latest with MkDocs 1.5.3 and mike 2.1.2 --- latest/reference/converters/openmm/index.html | 2 +- .../converters/openmm/nonbonded/index.html | 430 +++++++++--------- .../converters/openmm/valence/index.html | 4 +- latest/search/search_index.json | 2 +- latest/sitemap.xml.gz | Bin 396 -> 396 bytes 5 files changed, 219 insertions(+), 219 deletions(-) diff --git a/latest/reference/converters/openmm/index.html b/latest/reference/converters/openmm/index.html index 8e7a9d6..ee7b96c 100644 --- a/latest/reference/converters/openmm/index.html +++ b/latest/reference/converters/openmm/index.html @@ -1957,7 +1957,7 @@

parent_idxs = [i + start_idx for i in key.orientation_atom_indices] local_frame_coords = smee.geometry.polar_to_cartesian_coords( - v_sites.parameters[[parameter_idx], :] + v_sites.parameters[[parameter_idx], :].detach() ) origin, x_dir, y_dir = v_sites.weights[parameter_idx] diff --git a/latest/reference/converters/openmm/nonbonded/index.html b/latest/reference/converters/openmm/nonbonded/index.html index 496baa7..276e554 100644 --- a/latest/reference/converters/openmm/nonbonded/index.html +++ b/latest/reference/converters/openmm/nonbonded/index.html @@ -1494,10 +1494,7 @@

Source code in smee/converters/openmm/nonbonded.py -
355
-356
-357
-358
+              
358
 359
 360
 361
@@ -1576,89 +1573,92 @@ 

434 435 436 -437

def convert_custom_vdw_potential(
-    potential: smee.TensorPotential,
-    system: smee.TensorSystem,
-    energy_fn: str,
-    mixing_fn: dict[str, str],
-) -> tuple[openmm.CustomNonbondedForce, openmm.CustomBondForce]:
-    """Converts an arbitrary vdW potential to OpenMM forces.
-
-    The intermolecular interactions are described by a custom nonbonded force, while the
-    intramolecular interactions are described by a custom bond force.
+437
+438
+439
+440
def convert_custom_vdw_potential(
+    potential: smee.TensorPotential,
+    system: smee.TensorSystem,
+    energy_fn: str,
+    mixing_fn: dict[str, str],
+) -> tuple[openmm.CustomNonbondedForce, openmm.CustomBondForce]:
+    """Converts an arbitrary vdW potential to OpenMM forces.
 
-    If the potential has custom mixing rules (i.e. exceptions), a lookup table will be
-    used to store the parameters. Otherwise, the mixing rules will be applied directly
-    in the energy function.
-
-    Args:
-        potential: The potential to convert.
-        system: The system the potential belongs to.
-        energy_fn: The energy function of the potential, written in OpenMM's custom
-            energy function syntax.
-        mixing_fn: A dictionary of mixing rules for each parameter of the potential.
-            The keys are the parameter names, and the values are the mixing rules.
-
-            The mixing rules should be a single expression that can be evaluated using
-            OpenMM's energy function syntax, and should not contain any assignments.
+    The intermolecular interactions are described by a custom nonbonded force, while the
+    intramolecular interactions are described by a custom bond force.
+
+    If the potential has custom mixing rules (i.e. exceptions), a lookup table will be
+    used to store the parameters. Otherwise, the mixing rules will be applied directly
+    in the energy function.
+
+    Args:
+        potential: The potential to convert.
+        system: The system the potential belongs to.
+        energy_fn: The energy function of the potential, written in OpenMM's custom
+            energy function syntax.
+        mixing_fn: A dictionary of mixing rules for each parameter of the potential.
+            The keys are the parameter names, and the values are the mixing rules.
 
-    Examples:
-        For a Lennard-Jones potential using Lorentz-Berthelot mixing rules:
+            The mixing rules should be a single expression that can be evaluated using
+            OpenMM's energy function syntax, and should not contain any assignments.
 
-        >>> energy_fn = "4*epsilon*x6*(x6 - 1.0);x6=x4*x2;x4=x2*x2;x2=x*x;x=sigma/r;"
-        >>> mixing_fn = {
-        ...     "epsilon": "sqrt(epsilon1 * epsilon2)",
-        ...     "sigma": "0.5 * (sigma1 + sigma2)",
-        ... }
-    """
-    energy_fn = re.sub(r"\s+", "", energy_fn)
-    mixing_fn = {k: re.sub(r"\s+", "", v) for k, v in mixing_fn.items()}
-
-    used_parameters, used_attributes = _detect_parameters(
-        potential, energy_fn, mixing_fn
-    )
-    requires_lookup = potential.exceptions is not None
-
-    inter_force = _create_nonbonded_force(
-        potential, system, openmm.CustomNonbondedForce
-    )
-    inter_force.setEnergyFunction(energy_fn)
-
-    intra_force = openmm.CustomBondForce(
-        _prepend_scale_to_energy_fn(energy_fn, _INTRA_SCALE_VAR)
-    )
-    intra_force.addPerBondParameter(_INTRA_SCALE_VAR)
-    intra_force.setUsesPeriodicBoundaryConditions(system.is_periodic)
-
-    for force in [inter_force, intra_force]:
-        for attr in used_attributes:
-            attr_unit = potential.attribute_units[potential.attribute_cols.index(attr)]
-            attr_conv = (
-                (1.0 * attr_unit)
-                .to_openmm()
-                .value_in_unit_system(openmm.unit.md_unit_system)
-            )
-            attr_idx = potential.attribute_cols.index(attr)
-            attr_val = float(potential.attributes[attr_idx]) * attr_conv
-
-            force.addGlobalParameter(attr, attr_val)
-
-    if requires_lookup:
-        _add_parameters_to_vdw_with_lookup(
-            potential, system, energy_fn, mixing_fn, inter_force, intra_force
-        )
-    else:
-        _add_parameters_to_vdw_without_lookup(
-            potential,
-            system,
-            energy_fn,
-            mixing_fn,
-            inter_force,
-            intra_force,
-            used_parameters,
-        )
-
-    return inter_force, intra_force
+    Examples:
+        For a Lennard-Jones potential using Lorentz-Berthelot mixing rules:
+
+        >>> energy_fn = "4*epsilon*x6*(x6 - 1.0);x6=x4*x2;x4=x2*x2;x2=x*x;x=sigma/r;"
+        >>> mixing_fn = {
+        ...     "epsilon": "sqrt(epsilon1 * epsilon2)",
+        ...     "sigma": "0.5 * (sigma1 + sigma2)",
+        ... }
+    """
+    energy_fn = re.sub(r"\s+", "", energy_fn)
+    mixing_fn = {k: re.sub(r"\s+", "", v) for k, v in mixing_fn.items()}
+
+    used_parameters, used_attributes = _detect_parameters(
+        potential, energy_fn, mixing_fn
+    )
+    requires_lookup = potential.exceptions is not None
+
+    inter_force = _create_nonbonded_force(
+        potential, system, openmm.CustomNonbondedForce
+    )
+    inter_force.setEnergyFunction(energy_fn)
+
+    intra_force = openmm.CustomBondForce(
+        _prepend_scale_to_energy_fn(energy_fn, _INTRA_SCALE_VAR)
+    )
+    intra_force.addPerBondParameter(_INTRA_SCALE_VAR)
+    intra_force.setUsesPeriodicBoundaryConditions(system.is_periodic)
+
+    for force in [inter_force, intra_force]:
+        for attr in used_attributes:
+            attr_unit = potential.attribute_units[potential.attribute_cols.index(attr)]
+            attr_conv = (
+                (1.0 * attr_unit)
+                .to_openmm()
+                .value_in_unit_system(openmm.unit.md_unit_system)
+            )
+            attr_idx = potential.attribute_cols.index(attr)
+            attr_val = float(potential.attributes[attr_idx]) * attr_conv
+
+            force.addGlobalParameter(attr, attr_val)
+
+    if requires_lookup:
+        _add_parameters_to_vdw_with_lookup(
+            potential, system, energy_fn, mixing_fn, inter_force, intra_force
+        )
+    else:
+        _add_parameters_to_vdw_without_lookup(
+            potential,
+            system,
+            energy_fn,
+            mixing_fn,
+            inter_force,
+            intra_force,
+            used_parameters,
+        )
+
+    return inter_force, intra_force
 
@@ -1689,10 +1689,7 @@

Source code in smee/converters/openmm/nonbonded.py -
440
-441
-442
-443
+              
443
 444
 445
 446
@@ -1743,61 +1740,64 @@ 

491 492 493 -494

@smee.converters.openmm.potential_converter(
-    smee.PotentialType.VDW, smee.EnergyFn.VDW_LJ
-)
-def convert_lj_potential(
-    potential: smee.TensorPotential, system: smee.TensorSystem
-) -> openmm.NonbondedForce | list[openmm.CustomNonbondedForce | openmm.CustomBondForce]:
-    """Convert a Lennard-Jones potential to an OpenMM force.
-
-    If the potential has custom mixing rules (i.e. exceptions), the interactions will
-    be split into an inter- and intra-molecular force.
-    """
-    energy_fn = "4*epsilon*x6*(x6 - 1.0);x6=x4*x2;x4=x2*x2;x2=x*x;x=sigma/r;"
-    mixing_fn = {
-        "epsilon": "sqrt(epsilon1 * epsilon2)",
-        "sigma": "0.5 * (sigma1 + sigma2)",
-    }
-
-    if potential.exceptions is not None:
-        return list(
-            convert_custom_vdw_potential(potential, system, energy_fn, mixing_fn)
-        )
-
-    force = _create_nonbonded_force(potential, system)
-
-    idx_offset = 0
-
-    for topology, n_copies in zip(system.topologies, system.n_copies, strict=True):
-        parameter_map = topology.parameters[potential.type]
-        parameters = parameter_map.assignment_matrix @ potential.parameters
-
-        for _ in range(n_copies):
-            for epsilon, sigma in parameters:
-                force.addParticle(0.0, sigma * _ANGSTROM, epsilon * _KCAL_PER_MOL)
-
-            for index, (i, j) in enumerate(parameter_map.exclusions):
-                scale = potential.attributes[parameter_map.exclusion_scale_idxs[index]]
+494
+495
+496
+497
@smee.converters.openmm.potential_converter(
+    smee.PotentialType.VDW, smee.EnergyFn.VDW_LJ
+)
+def convert_lj_potential(
+    potential: smee.TensorPotential, system: smee.TensorSystem
+) -> openmm.NonbondedForce | list[openmm.CustomNonbondedForce | openmm.CustomBondForce]:
+    """Convert a Lennard-Jones potential to an OpenMM force.
+
+    If the potential has custom mixing rules (i.e. exceptions), the interactions will
+    be split into an inter- and intra-molecular force.
+    """
+    energy_fn = "4*epsilon*x6*(x6 - 1.0);x6=x4*x2;x4=x2*x2;x2=x*x;x=sigma/r;"
+    mixing_fn = {
+        "epsilon": "sqrt(epsilon1 * epsilon2)",
+        "sigma": "0.5 * (sigma1 + sigma2)",
+    }
+
+    if potential.exceptions is not None:
+        return list(
+            convert_custom_vdw_potential(potential, system, energy_fn, mixing_fn)
+        )
+
+    force = _create_nonbonded_force(potential, system)
+
+    idx_offset = 0
+
+    for topology, n_copies in zip(system.topologies, system.n_copies, strict=True):
+        parameter_map = topology.parameters[potential.type]
+        parameters = parameter_map.assignment_matrix @ potential.parameters.detach()
+
+        for _ in range(n_copies):
+            for epsilon, sigma in parameters:
+                force.addParticle(0.0, sigma * _ANGSTROM, epsilon * _KCAL_PER_MOL)
 
-                eps_i, sig_i = parameters[i, :]
-                eps_j, sig_j = parameters[j, :]
+            for index, (i, j) in enumerate(parameter_map.exclusions):
+                scale = potential.attributes[parameter_map.exclusion_scale_idxs[index]]
 
-                eps, sig = smee.potentials.nonbonded.lorentz_berthelot(
-                    eps_i, eps_j, sig_i, sig_j
-                )
-
-                force.addException(
-                    i + idx_offset,
-                    j + idx_offset,
-                    0.0,
-                    float(sig) * _ANGSTROM,
-                    float(eps * scale) * _KCAL_PER_MOL,
-                )
-
-            idx_offset += topology.n_particles
-
-    return force
+                eps_i, sig_i = parameters[i, :]
+                eps_j, sig_j = parameters[j, :]
+
+                eps, sig = smee.potentials.nonbonded.lorentz_berthelot(
+                    eps_i, eps_j, sig_i, sig_j
+                )
+
+                force.addException(
+                    i + idx_offset,
+                    j + idx_offset,
+                    0.0,
+                    float(sig) * _ANGSTROM,
+                    float(eps * scale) * _KCAL_PER_MOL,
+                )
+
+            idx_offset += topology.n_particles
+
+    return force
 
@@ -1828,10 +1828,7 @@

Source code in smee/converters/openmm/nonbonded.py -
497
-498
-499
-500
+              
500
 501
 502
 503
@@ -1854,33 +1851,36 @@ 

520 521 522 -523

@smee.converters.openmm.potential_converter(
-    smee.PotentialType.VDW, smee.EnergyFn.VDW_DEXP
-)
-def convert_dexp_potential(
-    potential: smee.TensorPotential, system: smee.TensorSystem
-) -> tuple[openmm.CustomNonbondedForce, openmm.CustomBondForce]:
-    """Convert a DEXP potential to OpenMM forces.
-
-    The intermolcular interactions are described by a custom nonbonded force, while the
-    intramolecular interactions are described by a custom bond force.
+523
+524
+525
+526
@smee.converters.openmm.potential_converter(
+    smee.PotentialType.VDW, smee.EnergyFn.VDW_DEXP
+)
+def convert_dexp_potential(
+    potential: smee.TensorPotential, system: smee.TensorSystem
+) -> tuple[openmm.CustomNonbondedForce, openmm.CustomBondForce]:
+    """Convert a DEXP potential to OpenMM forces.
 
-    If the potential has custom mixing rules (i.e. exceptions), a lookup table will be
-    used to store the parameters. Otherwise, the mixing rules will be applied directly
-    in the energy function.
-    """
-    energy_fn = (
-        "epsilon * (repulsion - attraction);"
-        "repulsion  = beta  / (alpha - beta) * exp(alpha * (1 - x));"
-        "attraction = alpha / (alpha - beta) * exp(beta  * (1 - x));"
-        "x = r / r_min;"
-    )
-    mixing_fn = {
-        "epsilon": "sqrt(epsilon1 * epsilon2)",
-        "r_min": "0.5 * (r_min1 + r_min2)",
-    }
-
-    return convert_custom_vdw_potential(potential, system, energy_fn, mixing_fn)
+    The intermolcular interactions are described by a custom nonbonded force, while the
+    intramolecular interactions are described by a custom bond force.
+
+    If the potential has custom mixing rules (i.e. exceptions), a lookup table will be
+    used to store the parameters. Otherwise, the mixing rules will be applied directly
+    in the energy function.
+    """
+    energy_fn = (
+        "epsilon * (repulsion - attraction);"
+        "repulsion  = beta  / (alpha - beta) * exp(alpha * (1 - x));"
+        "attraction = alpha / (alpha - beta) * exp(beta  * (1 - x));"
+        "x = r / r_min;"
+    )
+    mixing_fn = {
+        "epsilon": "sqrt(epsilon1 * epsilon2)",
+        "r_min": "0.5 * (r_min1 + r_min2)",
+    }
+
+    return convert_custom_vdw_potential(potential, system, energy_fn, mixing_fn)
 
@@ -1906,10 +1906,7 @@

Source code in smee/converters/openmm/nonbonded.py -
526
-527
-528
-529
+              
529
 530
 531
 532
@@ -1945,46 +1942,49 @@ 

562 563 564 -565

@smee.converters.openmm.potential_converter(
-    smee.PotentialType.ELECTROSTATICS, smee.EnergyFn.COULOMB
-)
-def convert_coulomb_potential(
-    potential: smee.TensorPotential, system: smee.TensorSystem
-) -> openmm.NonbondedForce:
-    """Convert a Coulomb potential to an OpenMM force."""
-    force = _create_nonbonded_force(potential, system)
-
-    idx_offset = 0
-
-    for topology, n_copies in zip(system.topologies, system.n_copies, strict=True):
-        parameter_map = topology.parameters[potential.type]
-        parameters = parameter_map.assignment_matrix @ potential.parameters
-
-        for _ in range(n_copies):
-            for charge in parameters:
-                force.addParticle(
-                    charge.detach() * openmm.unit.elementary_charge,
-                    1.0 * _ANGSTROM,
-                    0.0 * _KCAL_PER_MOL,
-                )
-
-            for index, (i, j) in enumerate(parameter_map.exclusions):
-                q_i, q_j = parameters[i], parameters[j]
-                q = q_i * q_j
-
-                scale = potential.attributes[parameter_map.exclusion_scale_idxs[index]]
-
-                force.addException(
-                    i + idx_offset,
-                    j + idx_offset,
-                    scale * q,
-                    1.0,
-                    0.0,
-                )
-
-            idx_offset += topology.n_particles
-
-    return force
+565
+566
+567
+568
@smee.converters.openmm.potential_converter(
+    smee.PotentialType.ELECTROSTATICS, smee.EnergyFn.COULOMB
+)
+def convert_coulomb_potential(
+    potential: smee.TensorPotential, system: smee.TensorSystem
+) -> openmm.NonbondedForce:
+    """Convert a Coulomb potential to an OpenMM force."""
+    force = _create_nonbonded_force(potential, system)
+
+    idx_offset = 0
+
+    for topology, n_copies in zip(system.topologies, system.n_copies, strict=True):
+        parameter_map = topology.parameters[potential.type]
+        parameters = parameter_map.assignment_matrix @ potential.parameters.detach()
+
+        for _ in range(n_copies):
+            for charge in parameters:
+                force.addParticle(
+                    charge.detach() * openmm.unit.elementary_charge,
+                    1.0 * _ANGSTROM,
+                    0.0 * _KCAL_PER_MOL,
+                )
+
+            for index, (i, j) in enumerate(parameter_map.exclusions):
+                q_i, q_j = parameters[i], parameters[j]
+                q = q_i * q_j
+
+                scale = potential.attributes[parameter_map.exclusion_scale_idxs[index]]
+
+                force.addException(
+                    i + idx_offset,
+                    j + idx_offset,
+                    scale * q,
+                    1.0,
+                    0.0,
+                )
+
+            idx_offset += topology.n_particles
+
+    return force
 
diff --git a/latest/reference/converters/openmm/valence/index.html b/latest/reference/converters/openmm/valence/index.html index 53da652..b07c727 100644 --- a/latest/reference/converters/openmm/valence/index.html +++ b/latest/reference/converters/openmm/valence/index.html @@ -1428,7 +1428,7 @@

for topology, n_copies in zip(system.topologies, system.n_copies, strict=True): parameters = ( topology.parameters[potential.type].assignment_matrix @ potential.parameters - ) + ).detach() for _ in range(n_copies): atom_idxs = topology.parameters[potential.type].particle_idxs + idx_offset @@ -1526,7 +1526,7 @@

for topology, n_copies in zip(system.topologies, system.n_copies, strict=True): parameters = ( topology.parameters[potential.type].assignment_matrix @ potential.parameters - ) + ).detach() for _ in range(n_copies): atom_idxs = topology.parameters[potential.type].particle_idxs + idx_offset diff --git a/latest/search/search_index.json b/latest/search/search_index.json index 49815dd..bb48beb 100644 --- a/latest/search/search_index.json +++ b/latest/search/search_index.json @@ -1 +1 @@ -{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Overview","text":"SMIRNOFF Energy Evaluations

Differentiably evaluate energies of molecules using SMIRNOFF force fields

The smee framework aims to offer a simple API for differentiably evaluating the energy of SMIRNOFF force fields applied to molecules using pytorch.

The package currently supports evaluating the energy of force fields that contain:

  • Bonds, Angles, ProperTorsions and ImproperTorsions
  • vdW, Electrostatics, ToolkitAM1BCC, LibraryCharges
  • VirtualSites

parameter handlers in addition to limited support for registering custom handlers.

It further supports a number of functional forms included in smirnoff-plugins, namely:

  • DoubleExponential
"},{"location":"#installation","title":"Installation","text":"

This package can be installed using conda (or mamba, a faster version of conda):

mamba install -c conda-forge smee\n

The example notebooks further require you install jupyter, nglview, and smirnoff-plugins:

mamba install -c conda-forge jupyter nglview \"smirnoff-plugins >=0.0.4\"\n
"},{"location":"#getting-started","title":"Getting Started","text":"

To get started, see the examples.

"},{"location":"#copyright","title":"Copyright","text":"

Copyright (c) 2023, Simon Boothroyd

"},{"location":"development/","title":"Development","text":"

To create a development environment, you must have mamba installed.

A development conda environment can be created and activated with:

make env\nconda activate smee\n

To format the codebase:

make format\n

To run the unit tests:

make test\n

To serve the documentation locally:

mkdocs serve\n
"},{"location":"examples/","title":"Examples","text":"

This directory contains a number of examples of how to use smee. They currently include:

  • Evaluating the energy of a water dimer with virtual sites
  • Minimizing the conformer of a molecule
  • Computing the gradient of the energy w.r.t. force field parameters
  • Registering custom parameter handlers
  • Differentiably compute ensemble averages from MD simulations
"},{"location":"examples/compute-energy/","title":"Computing energies using","text":"In\u00a0[1]: Copied!
import openff.toolkit\nimport openff.units\nimport torch\n\nwater = openff.toolkit.Molecule.from_smiles(\"O\")\nwater.generate_conformers(n_conformers=1)\n\nimport openff.interchange\n\ntip4p_interchange = openff.interchange.Interchange.from_smirnoff(\n    openff.toolkit.ForceField(\"tip4p_fb.offxml\"), water.to_topology()\n)\n
import openff.toolkit import openff.units import torch water = openff.toolkit.Molecule.from_smiles(\"O\") water.generate_conformers(n_conformers=1) import openff.interchange tip4p_interchange = openff.interchange.Interchange.from_smirnoff( openff.toolkit.ForceField(\"tip4p_fb.offxml\"), water.to_topology() )
 

The interchange must then be mapped into collections of tensors:

In\u00a0[2]: Copied!
import smee.converters\n\ntip4p_tensor_ff, [tip4p_topology] = smee.converters.convert_interchange(\n    tip4p_interchange\n)\n
import smee.converters tip4p_tensor_ff, [tip4p_topology] = smee.converters.convert_interchange( tip4p_interchange )

Note: The convert_interchange function can take either a single interchange object, or a list of multiple.

These tensors are returned as:

  • a smee.ff.TensorForceField oject: this stores the original values of the force field parameters in tensor form.
  • a list of smee.ff.TensorTopology objects: each 'topology' will store basic information about each input molecule, in addition to matrices defining which parameters were assigned to which elements (e.g. bonds, angles, torsions) and any virtual sites that were added.

To define fuller systems (e.g. dimers, condensed phase systems etc), we need to wrap the topologies in a system object:

In\u00a0[3]: Copied!
tip4p_dimer = smee.TensorSystem([tip4p_topology], n_copies=[2], is_periodic=False)\n
tip4p_dimer = smee.TensorSystem([tip4p_topology], n_copies=[2], is_periodic=False)

This mirrors how GROMACS topology files are defined, whereby individual species are parameterised and then a count of how many of each should be present is specified. This avoids the need to store multiple copies of the same parameters for each copy of a given molecule.

We can then define the coordinates of our dimers:

In\u00a0[4]: Copied!
water_conformer = torch.tensor(water.conformers[0].m_as(openff.units.unit.angstrom))\ntip4p_conformer = smee.add_v_site_coords(\n    tip4p_topology.v_sites, water_conformer, tip4p_tensor_ff\n)\n\ntip4p_conformers = torch.stack(\n    [\n        torch.vstack([tip4p_conformer, tip4p_conformer + torch.tensor(1.5 + i * 0.05)])\n        for i in range(40)\n    ]\n)\ntip4p_conformers.shape\n
water_conformer = torch.tensor(water.conformers[0].m_as(openff.units.unit.angstrom)) tip4p_conformer = smee.add_v_site_coords( tip4p_topology.v_sites, water_conformer, tip4p_tensor_ff ) tip4p_conformers = torch.stack( [ torch.vstack([tip4p_conformer, tip4p_conformer + torch.tensor(1.5 + i * 0.05)]) for i in range(40) ] ) tip4p_conformers.shape Out[4]:
torch.Size([40, 8, 3])

For simplicity, we have created 40 dimer conformers, with a separation ranging from 1.5-3.5 \u00c5 by crudely shifting our initial conformer along the x-axis.

Note: Conformers should either be tensor with a shape of (n_particles, 3) or (n_conformers, n_particles, 3), and units of \u00c5._

From the shape we can clearly see there are 40 conformers, each with coordinates for 8 particles (i.e. 3 for each water molecule + 1 virtual site on each).

The energy each dimer can then be directly evaluated and plotted

In\u00a0[5]: Copied!
tip4p_energies = smee.compute_energy(tip4p_dimer, tip4p_tensor_ff, tip4p_conformers)\n\nfrom matplotlib import pyplot\n\npyplot.plot(tip4p_energies, label=\"tip4p\")\npyplot.ylabel(\"Energy [kcal / mol]\");\n
tip4p_energies = smee.compute_energy(tip4p_dimer, tip4p_tensor_ff, tip4p_conformers) from matplotlib import pyplot pyplot.plot(tip4p_energies, label=\"tip4p\") pyplot.ylabel(\"Energy [kcal / mol]\");

The above can easily be repeated using the TIP3P water model, allowing us to compare the dimer energy curves.

In\u00a0[6]: Copied!
tip3p_interchange = openff.interchange.Interchange.from_smirnoff(\n    openff.toolkit.ForceField(\"tip3p.offxml\"), water.to_topology()\n)\n\ntip3p_tensor_ff, [tip3p_tensor_topology] = smee.converters.convert_interchange(\n    tip3p_interchange\n)\ntip3p_dimer = smee.TensorSystem(\n    [tip3p_tensor_topology], n_copies=[2], is_periodic=False\n)\n\ntip3p_conformers = torch.stack(\n    [\n        torch.vstack([water_conformer, water_conformer + torch.tensor(1.5 + i * 0.05)])\n        for i in range(40)\n    ]\n)\n\ntip3p_energies = smee.compute_energy(tip3p_dimer, tip3p_tensor_ff, tip3p_conformers)\n\npyplot.plot(tip3p_energies, label=\"tip3p\")\npyplot.plot(tip4p_energies, label=\"tip4p\")\n\npyplot.ylabel(\"Energy [kcal / mol]\")\npyplot.legend();\n
tip3p_interchange = openff.interchange.Interchange.from_smirnoff( openff.toolkit.ForceField(\"tip3p.offxml\"), water.to_topology() ) tip3p_tensor_ff, [tip3p_tensor_topology] = smee.converters.convert_interchange( tip3p_interchange ) tip3p_dimer = smee.TensorSystem( [tip3p_tensor_topology], n_copies=[2], is_periodic=False ) tip3p_conformers = torch.stack( [ torch.vstack([water_conformer, water_conformer + torch.tensor(1.5 + i * 0.05)]) for i in range(40) ] ) tip3p_energies = smee.compute_energy(tip3p_dimer, tip3p_tensor_ff, tip3p_conformers) pyplot.plot(tip3p_energies, label=\"tip3p\") pyplot.plot(tip4p_energies, label=\"tip4p\") pyplot.ylabel(\"Energy [kcal / mol]\") pyplot.legend();

A further advantage of storing our applied force field parameters in smee form is the ability to compute only the energy due to a particular handler (e.g. only LJ energies).

In\u00a0[7]: Copied!
tip3p_lj_energies = smee.compute_energy_potential(\n    tip3p_dimer, tip3p_tensor_ff.potentials_by_type[\"vdW\"], tip3p_conformers\n)\ntip4p_lj_energies = smee.compute_energy_potential(\n    tip4p_dimer, tip4p_tensor_ff.potentials_by_type[\"vdW\"], tip4p_conformers\n)\n\npyplot.plot(tip3p_lj_energies, label=\"tip3p\")\npyplot.plot(tip4p_lj_energies, label=\"tip4p\")\n\npyplot.ylabel(\"LJ Energy [kcal / mol]\");\n
tip3p_lj_energies = smee.compute_energy_potential( tip3p_dimer, tip3p_tensor_ff.potentials_by_type[\"vdW\"], tip3p_conformers ) tip4p_lj_energies = smee.compute_energy_potential( tip4p_dimer, tip4p_tensor_ff.potentials_by_type[\"vdW\"], tip4p_conformers ) pyplot.plot(tip3p_lj_energies, label=\"tip3p\") pyplot.plot(tip4p_lj_energies, label=\"tip4p\") pyplot.ylabel(\"LJ Energy [kcal / mol]\");"},{"location":"examples/compute-energy/#computing-energies-using-smee","title":"Computing energies using smee\u00b6","text":"

This example will show how the energy of a water dimer (TIP4P-FB and TIP3P) in multiple conformations can be evaluated using smee framework.

We start by creating the objects that will store our parameterized water dimer. First we will create a single water molecule, and parameterize it using OpenFF Interchange:

"},{"location":"examples/conformer-minimization/","title":"Conformer Minimization","text":"In\u00a0[1]: Copied!
import openff.toolkit\nimport openff.units\nimport torch\n\nmolecule = openff.toolkit.Molecule.from_smiles(\"CC(=O)NC1=CC=C(C=C1)O\")\nmolecule.generate_conformers(n_conformers=1)\n\nconformer = torch.tensor(molecule.conformers[0].m_as(openff.units.unit.angstrom)) * 1.10\nconformer.requires_grad = True\n
import openff.toolkit import openff.units import torch molecule = openff.toolkit.Molecule.from_smiles(\"CC(=O)NC1=CC=C(C=C1)O\") molecule.generate_conformers(n_conformers=1) conformer = torch.tensor(molecule.conformers[0].m_as(openff.units.unit.angstrom)) * 1.10 conformer.requires_grad = True

We specify that the gradient of the conformer is required so that we can optimize it using PyTorch.

Parameterize the molecule using OpenFF Interchange and convert it into a PyTorch tensor representation.

In\u00a0[2]: Copied!
import openff.interchange\n\ninterchange = openff.interchange.Interchange.from_smirnoff(\n    openff.toolkit.ForceField(\"openff-2.1.0.offxml\"),\n    molecule.to_topology(),\n)\n\nimport smee.converters\n\nforce_field, [topology] = smee.converters.convert_interchange(interchange)\n
import openff.interchange interchange = openff.interchange.Interchange.from_smirnoff( openff.toolkit.ForceField(\"openff-2.1.0.offxml\"), molecule.to_topology(), ) import smee.converters force_field, [topology] = smee.converters.convert_interchange(interchange)
 

We can minimize the conformer using any of PyTorch's optimizers.

In\u00a0[3]: Copied!
import smee\n\noptimizer = torch.optim.Adam([conformer], lr=0.02)\n\nfor epoch in range(75):\n    energy = smee.compute_energy(topology, force_field, conformer)\n    energy.backward()\n\n    optimizer.step()\n    optimizer.zero_grad()\n\n    if epoch % 5 == 0 or epoch == 74:\n        print(f\"Epoch {epoch}: E={energy.item()} kcal / mol\")\n
import smee optimizer = torch.optim.Adam([conformer], lr=0.02) for epoch in range(75): energy = smee.compute_energy(topology, force_field, conformer) energy.backward() optimizer.step() optimizer.zero_grad() if epoch % 5 == 0 or epoch == 74: print(f\"Epoch {epoch}: E={energy.item()} kcal / mol\")
Epoch 0: E=102.10968017578125 kcal / mol\nEpoch 5: E=7.088213920593262 kcal / mol\nEpoch 10: E=-18.331130981445312 kcal / mol\nEpoch 15: E=-22.182296752929688 kcal / mol\nEpoch 20: E=-30.369152069091797 kcal / mol\nEpoch 25: E=-36.81045150756836 kcal / mol\nEpoch 30: E=-38.517852783203125 kcal / mol\nEpoch 35: E=-40.50505828857422 kcal / mol\nEpoch 40: E=-42.08476257324219 kcal / mol\nEpoch 45: E=-42.19199752807617 kcal / mol\nEpoch 50: E=-42.37827682495117 kcal / mol\nEpoch 55: E=-42.6767692565918 kcal / mol\nEpoch 60: E=-42.799903869628906 kcal / mol\nEpoch 65: E=-42.94251251220703 kcal / mol\nEpoch 70: E=-43.037200927734375 kcal / mol\nEpoch 74: E=-43.084136962890625 kcal / mol\n

We can then re-store the optimized conformer back into the molecule. Here we add the conformer to the molecule's conformer list, but we could also replace the original conformer.

In\u00a0[4]: Copied!
molecule.add_conformer(conformer.detach().numpy() * openff.units.unit.angstrom)\nmolecule.visualize(backend=\"nglview\")\n
molecule.add_conformer(conformer.detach().numpy() * openff.units.unit.angstrom) molecule.visualize(backend=\"nglview\")
NGLWidget(max_frame=1)
"},{"location":"examples/conformer-minimization/#conformer-minimization","title":"Conformer Minimization\u00b6","text":"

This example will show how to optimize a conformer of paracetamol.

Load in a paracetamol molecule, generate a conformer for it, and perturb the conformer to ensure it needs minimization.

"},{"location":"examples/md-simulations/","title":"Ensemble Averages from MD Simulations","text":"In\u00a0[\u00a0]: Copied!
import openff.interchange\nimport openff.toolkit\n\nimport smee.converters\n\ninterchanges = [\n    openff.interchange.Interchange.from_smirnoff(\n        openff.toolkit.ForceField(\"openff-2.0.0.offxml\"),\n        openff.toolkit.Molecule.from_smiles(smiles).to_topology(),\n    )\n    for smiles in (\"CCO\", \"CO\")\n]\n\ntensor_ff, topologies = smee.converters.convert_interchange(interchanges)\n\n# move the force field to the GPU for faster processing of the simulation\n# trajectories - the system and force field must be on the same device.\ntensor_ff = tensor_ff.to(\"cuda\")\n
import openff.interchange import openff.toolkit import smee.converters interchanges = [ openff.interchange.Interchange.from_smirnoff( openff.toolkit.ForceField(\"openff-2.0.0.offxml\"), openff.toolkit.Molecule.from_smiles(smiles).to_topology(), ) for smiles in (\"CCO\", \"CO\") ] tensor_ff, topologies = smee.converters.convert_interchange(interchanges) # move the force field to the GPU for faster processing of the simulation # trajectories - the system and force field must be on the same device. tensor_ff = tensor_ff.to(\"cuda\")

We will also flag that the vdW parameter gradients are required:

In\u00a0[\u00a0]: Copied!
vdw_potential = tensor_ff.potentials_by_type[\"vdW\"]\nvdw_potential.parameters.requires_grad = True\n
vdw_potential = tensor_ff.potentials_by_type[\"vdW\"] vdw_potential.parameters.requires_grad = True

We then define the full simulation boxes that we wish to simulate:

In\u00a0[\u00a0]: Copied!
import smee\n\n# define a periodic box containing 216 ethanol molecules\nsystem_ethanol = smee.TensorSystem([topologies[0]], [216], is_periodic=True)\nsystem_ethanol = system_ethanol.to(\"cuda\")\n# define a periodic box containing 216 methanol molecules\nsystem_methanol = smee.TensorSystem([topologies[1]], [216], True)\nsystem_methanol = system_methanol.to(\"cuda\")\n# define a periodic box containing 128 ethanol molecules and 128 methanol molecules\nsystem_mixture = smee.TensorSystem(topologies, [128, 128], True)\nsystem_mixture = system_mixture.to(\"cuda\")\n
import smee # define a periodic box containing 216 ethanol molecules system_ethanol = smee.TensorSystem([topologies[0]], [216], is_periodic=True) system_ethanol = system_ethanol.to(\"cuda\") # define a periodic box containing 216 methanol molecules system_methanol = smee.TensorSystem([topologies[1]], [216], True) system_methanol = system_methanol.to(\"cuda\") # define a periodic box containing 128 ethanol molecules and 128 methanol molecules system_mixture = smee.TensorSystem(topologies, [128, 128], True) system_mixture = system_mixture.to(\"cuda\")

A tensor system is simply a wrapper around a set of topology objects that define parameters applied to individual molecules, and the number of copies of that topology that should be present similar to GROMACS topologies. The is_periodic flag indicates whether the system should be simulated in a periodic box.

Here we have also moved the systems onto the GPU. This will allow us to much more rapidly compute ensemble averages from the trajectories, but is not required.

We then also must define the simulation protocol that will be used to run the simulations. This consists of a config object that defines how to generate the system coordinates using PACKMOL, the set of energy minimisations /simulations to run as equilibration, and finally the configuration of the production simulation:

In\u00a0[\u00a0]: Copied!
import tempfile\n\nimport openmm.unit\n\nimport smee.mm\n\ntemperature = 298.15 * openmm.unit.kelvin\npressure = 1.0 * openmm.unit.atmosphere\n\nbeta = 1.0 / (openmm.unit.MOLAR_GAS_CONSTANT_R * temperature)\n\n# we can run an arbitrary number of equilibration simulations / minimizations.\n# all generated data will be discarded, but the final coordinates will be used\n# to initialize the production simulation\nequilibrate_config = [\n    smee.mm.MinimizationConfig(),\n    # short NVT equilibration simulation\n    smee.mm.SimulationConfig(\n        temperature=temperature,\n        pressure=None,\n        n_steps=50000,\n        timestep=1.0 * openmm.unit.femtosecond,\n    ),\n    # short NPT equilibration simulation\n    smee.mm.SimulationConfig(\n        temperature=temperature,\n        pressure=pressure,\n        n_steps=50000,\n        timestep=1.0 * openmm.unit.femtosecond,\n    ),\n]\n# long NPT production simulation\nproduction_config = smee.mm.SimulationConfig(\n    temperature=temperature,\n    pressure=pressure,\n    n_steps=500000,\n    timestep=2.0 * openmm.unit.femtosecond,\n)\n
import tempfile import openmm.unit import smee.mm temperature = 298.15 * openmm.unit.kelvin pressure = 1.0 * openmm.unit.atmosphere beta = 1.0 / (openmm.unit.MOLAR_GAS_CONSTANT_R * temperature) # we can run an arbitrary number of equilibration simulations / minimizations. # all generated data will be discarded, but the final coordinates will be used # to initialize the production simulation equilibrate_config = [ smee.mm.MinimizationConfig(), # short NVT equilibration simulation smee.mm.SimulationConfig( temperature=temperature, pressure=None, n_steps=50000, timestep=1.0 * openmm.unit.femtosecond, ), # short NPT equilibration simulation smee.mm.SimulationConfig( temperature=temperature, pressure=pressure, n_steps=50000, timestep=1.0 * openmm.unit.femtosecond, ), ] # long NPT production simulation production_config = smee.mm.SimulationConfig( temperature=temperature, pressure=pressure, n_steps=500000, timestep=2.0 * openmm.unit.femtosecond, )

We will further define a convenience function that will first simulate the system of interest (storing the trajectory in a temporary directory), and then compute ensemble averages over that trajectory:

In\u00a0[\u00a0]: Copied!
import pathlib\n\nimport torch\n\n\ndef compute_ensemble_averages(\n    system: smee.TensorSystem, force_field: smee.TensorForceField\n) -> dict[str, torch.Tensor]:\n    # computing the ensemble averages is a two step process - we first need to run\n    # an MD simulation using the force field making sure to store the coordinates,\n    # box vectors and kinetic energies\n    coords, box_vectors = smee.mm.generate_system_coords(system, force_field)\n\n    interval = 1000\n\n    # save the simulation output every 1000th frame (2 ps) to a temporary file.\n    # we could also save the trajectory more permanently, but as we do nothing\n    # with it after computing the averages in this example, we simply want to\n    # discard it.\n    with (\n        tempfile.NamedTemporaryFile() as tmp_file,\n        smee.mm.tensor_reporter(tmp_file.name, interval, beta, pressure) as reporter,\n    ):\n        smee.mm.simulate(\n            system,\n            force_field,\n            coords,\n            box_vectors,\n            equilibrate_config,\n            production_config,\n            [reporter],\n        )\n\n        # we can then compute the ensemble averages from the trajectory. generating\n        # the trajectory separately from computing the ensemble averages allows us\n        # to run the simulation in parallel with other simulations more easily, without\n        # having to worry about copying gradients between workers / processes.\n        avgs, stds = smee.mm.compute_ensemble_averages(\n            system, force_field, pathlib.Path(tmp_file.name), temperature, pressure\n        )\n        return avgs\n
import pathlib import torch def compute_ensemble_averages( system: smee.TensorSystem, force_field: smee.TensorForceField ) -> dict[str, torch.Tensor]: # computing the ensemble averages is a two step process - we first need to run # an MD simulation using the force field making sure to store the coordinates, # box vectors and kinetic energies coords, box_vectors = smee.mm.generate_system_coords(system, force_field) interval = 1000 # save the simulation output every 1000th frame (2 ps) to a temporary file. # we could also save the trajectory more permanently, but as we do nothing # with it after computing the averages in this example, we simply want to # discard it. with ( tempfile.NamedTemporaryFile() as tmp_file, smee.mm.tensor_reporter(tmp_file.name, interval, beta, pressure) as reporter, ): smee.mm.simulate( system, force_field, coords, box_vectors, equilibrate_config, production_config, [reporter], ) # we can then compute the ensemble averages from the trajectory. generating # the trajectory separately from computing the ensemble averages allows us # to run the simulation in parallel with other simulations more easily, without # having to worry about copying gradients between workers / processes. avgs, stds = smee.mm.compute_ensemble_averages( system, force_field, pathlib.Path(tmp_file.name), temperature, pressure ) return avgs

Computing the ensemble averages is then as simple as:

In\u00a0[\u00a0]: Copied!
# run simulations of each system and compute ensemble averages over the trajectories\n# of the potential energy, volume, and density\nethanol_avgs = compute_ensemble_averages(system_ethanol, tensor_ff)\nmethanol_avgs = compute_ensemble_averages(system_methanol, tensor_ff)\nmixture_avgs = compute_ensemble_averages(system_mixture, tensor_ff)\n
# run simulations of each system and compute ensemble averages over the trajectories # of the potential energy, volume, and density ethanol_avgs = compute_ensemble_averages(system_ethanol, tensor_ff) methanol_avgs = compute_ensemble_averages(system_methanol, tensor_ff) mixture_avgs = compute_ensemble_averages(system_mixture, tensor_ff)

Each of the returned values is a dictionary of ensemble averages computed over the simulated production trajectory. This currently includes the potential energy, volume, and density of the system.

These averages can be used in a loss function

In\u00a0[\u00a0]: Copied!
# define some MOCK data and loss function\nmock_ethanol_density = 0.789  # g/mL\nmock_methanol_density = 0.791  # g/mL\n\nmock_enthalpy_of_mixing = 0.891  # kcal/mol\n\nloss = (ethanol_avgs[\"density\"] - mock_ethanol_density) ** 2\nloss += (methanol_avgs[\"density\"] - mock_methanol_density) ** 2\n\nmixture_enthalpy = mixture_avgs[\"enthalpy\"] / 256\n\nethanol_enthalpy = ethanol_avgs[\"enthalpy\"] / 128\nmethanol_enthalpy = methanol_avgs[\"enthalpy\"] / 128\n\nenthalpy_of_mixing = mixture_enthalpy - (\n    0.5 * ethanol_enthalpy + 0.5 * methanol_enthalpy\n)\nloss += (enthalpy_of_mixing - mock_enthalpy_of_mixing) ** 2\n
# define some MOCK data and loss function mock_ethanol_density = 0.789 # g/mL mock_methanol_density = 0.791 # g/mL mock_enthalpy_of_mixing = 0.891 # kcal/mol loss = (ethanol_avgs[\"density\"] - mock_ethanol_density) ** 2 loss += (methanol_avgs[\"density\"] - mock_methanol_density) ** 2 mixture_enthalpy = mixture_avgs[\"enthalpy\"] / 256 ethanol_enthalpy = ethanol_avgs[\"enthalpy\"] / 128 methanol_enthalpy = methanol_avgs[\"enthalpy\"] / 128 enthalpy_of_mixing = mixture_enthalpy - ( 0.5 * ethanol_enthalpy + 0.5 * methanol_enthalpy ) loss += (enthalpy_of_mixing - mock_enthalpy_of_mixing) ** 2

and the gradient of this loss function with respect to the force field parameters can be computed through backpropagation:

In\u00a0[\u00a0]: Copied!
loss.backward()\n\nepsilon_col = vdw_potential.parameter_cols.index(\"epsilon\")\nsigma_col = vdw_potential.parameter_cols.index(\"sigma\")\n\nprint(\"VdW \u0190 Gradients\", vdw_potential.parameters.grad[:, epsilon_col])\nprint(\"VdW \u03c3 Gradients\", vdw_potential.parameters.grad[:, sigma_col])\n
loss.backward() epsilon_col = vdw_potential.parameter_cols.index(\"epsilon\") sigma_col = vdw_potential.parameter_cols.index(\"sigma\") print(\"VdW \u0190 Gradients\", vdw_potential.parameters.grad[:, epsilon_col]) print(\"VdW \u03c3 Gradients\", vdw_potential.parameters.grad[:, sigma_col])"},{"location":"examples/md-simulations/#ensemble-averages-from-md-simulations","title":"Ensemble Averages from MD Simulations\u00b6","text":"

This example shows how ensemble averages can be computed from MD simulations, such that their gradient with respect to force field parameters can be computed through backpropagation.

We start by parameterizing the set of molecules that will appear in our simulation boxes:

"},{"location":"examples/parameter-gradients/","title":"Parameter Gradients","text":"In\u00a0[1]: Copied!
import openff.interchange\nimport openff.toolkit\nimport openff.units\nimport torch\n\nimport smee.converters\n\nmolecule = openff.toolkit.Molecule.from_smiles(\"CC(=O)NC1=CC=C(C=C1)O\")\nmolecule.generate_conformers(n_conformers=1)\n\nconformer = torch.tensor(molecule.conformers[0].m_as(openff.units.unit.angstrom))\n\ninterchange = openff.interchange.Interchange.from_smirnoff(\n    openff.toolkit.ForceField(\"openff_unconstrained-2.0.0.offxml\"),\n    molecule.to_topology(),\n)\ntensor_ff, [tensor_topology] = smee.converters.convert_interchange(interchange)\n
import openff.interchange import openff.toolkit import openff.units import torch import smee.converters molecule = openff.toolkit.Molecule.from_smiles(\"CC(=O)NC1=CC=C(C=C1)O\") molecule.generate_conformers(n_conformers=1) conformer = torch.tensor(molecule.conformers[0].m_as(openff.units.unit.angstrom)) interchange = openff.interchange.Interchange.from_smirnoff( openff.toolkit.ForceField(\"openff_unconstrained-2.0.0.offxml\"), molecule.to_topology(), ) tensor_ff, [tensor_topology] = smee.converters.convert_interchange(interchange)
 

We can access the parameters for each SMIRNOFF parameter 'handler' (e.g. vdW, bond, angle, etc.) using the potentials_by_type (or the potentials) attribute of the TensorForceField object.

In\u00a0[2]: Copied!
vdw_potential = tensor_ff.potentials_by_type[\"vdW\"]\nvdw_potential.parameters.requires_grad = True\n
vdw_potential = tensor_ff.potentials_by_type[\"vdW\"] vdw_potential.parameters.requires_grad = True

The gradient of the potential energy with respect to the parameters can then be computed by backpropagating through the energy computation.

In\u00a0[3]: Copied!
import smee\n\nenergy = smee.compute_energy(tensor_topology, tensor_ff, conformer)\nenergy.backward()\n\nfor parameter_key, gradient in zip(\n    vdw_potential.parameter_keys, vdw_potential.parameters.grad.numpy(), strict=True\n):\n    parameter_cols = vdw_potential.parameter_cols\n\n    parameter_grads = \", \".join(\n        f\"dU/d{parameter_col} = {parameter_grad: 8.3f}\"\n        for parameter_col, parameter_grad in zip(parameter_cols, gradient, strict=True)\n    )\n    print(f\"{parameter_key.id.ljust(15)} - {parameter_grads}\")\n
import smee energy = smee.compute_energy(tensor_topology, tensor_ff, conformer) energy.backward() for parameter_key, gradient in zip( vdw_potential.parameter_keys, vdw_potential.parameters.grad.numpy(), strict=True ): parameter_cols = vdw_potential.parameter_cols parameter_grads = \", \".join( f\"dU/d{parameter_col} = {parameter_grad: 8.3f}\" for parameter_col, parameter_grad in zip(parameter_cols, gradient, strict=True) ) print(f\"{parameter_key.id.ljust(15)} - {parameter_grads}\")
[#6X4:1]        - dU/depsilon =   -1.033, dU/dsigma =   -0.139\n[#6:1]          - dU/depsilon =   87.490, dU/dsigma =   34.983\n[#8:1]          - dU/depsilon =   15.846, dU/dsigma =   17.232\n[#7:1]          - dU/depsilon =    0.148, dU/dsigma =    1.187\n[#8X2H1+0:1]    - dU/depsilon =   -0.305, dU/dsigma =    0.558\n[#1:1]-[#6X4]   - dU/depsilon =    7.630, dU/dsigma =    1.404\n[#1:1]-[#7]     - dU/depsilon =   -2.894, dU/dsigma =   -0.074\n[#1:1]-[#6X3]   - dU/depsilon =  137.134, dU/dsigma =   12.129\n[#1:1]-[#8]     - dU/depsilon =  -22.417, dU/dsigma =   -0.001\n
"},{"location":"examples/parameter-gradients/#parameter-gradients","title":"Parameter Gradients\u00b6","text":"

This example will show how the gradient of the potential energy with respect to force field parameters may be computed.

We start be loading and parameterizing the molecule of interest.

"},{"location":"reference/","title":"Index","text":""},{"location":"reference/#smee","title":"smee","text":"

smee

Differentiably evaluate energies of molecules using SMIRNOFF force fields

Modules:

  • converters \u2013

    Convert to / from smee tensor representations.

  • geometry \u2013

    Compute internal coordinates (e.g. bond lengths).

  • mm \u2013

    Compute differentiable ensemble averages using OpenMM and SMEE.

  • potentials \u2013

    Evaluate the potential energy of parameterized topologies.

  • tests \u2013
  • utils \u2013

    General utility functions

Classes:

  • EnergyFn \u2013

    An enumeration of the energy functions supported by smee out of the box.

  • PotentialType \u2013

    An enumeration of the potential types supported by smee out of the box.

  • NonbondedParameterMap \u2013

    A map between atom indices part of a particular valence interaction (e.g.

  • TensorConstraints \u2013

    A tensor representation of a set of distance constraints between pairs of

  • TensorForceField \u2013

    A tensor representation of a SMIRNOFF force field.

  • TensorPotential \u2013

    A tensor representation of a valence SMIRNOFF parameter handler

  • TensorSystem \u2013

    A tensor representation of a 'full' system.

  • TensorTopology \u2013

    A tensor representation of a molecular topology that has been assigned force

  • TensorVSites \u2013

    A tensor representation of a set of virtual sites parameters.

  • ValenceParameterMap \u2013

    A map between atom indices part of a particular valence interaction (e.g.

  • VSiteMap \u2013

    A map between virtual sites that have been added to a topology and their

Functions:

  • add_v_site_coords \u2013

    Appends the coordinates of any virtual sites to a conformer (or batch of

  • compute_v_site_coords \u2013

    Computes the positions of a set of virtual sites relative to a specified

  • compute_energy \u2013

    Computes the potential energy [kcal / mol] of a system / topology in a given

  • compute_energy_potential \u2013

    Computes the potential energy [kcal / mol] due to a SMIRNOFF potential

Attributes:

  • CUTOFF_ATTRIBUTE \u2013

    The attribute that should be used to store the cutoff distance of a potential.

  • SWITCH_ATTRIBUTE \u2013

    The attribute that should be used to store the switch width of a potential, if the

"},{"location":"reference/#smee.CUTOFF_ATTRIBUTE","title":"CUTOFF_ATTRIBUTE module-attribute","text":"
CUTOFF_ATTRIBUTE = 'cutoff'\n

The attribute that should be used to store the cutoff distance of a potential.

"},{"location":"reference/#smee.SWITCH_ATTRIBUTE","title":"SWITCH_ATTRIBUTE module-attribute","text":"
SWITCH_ATTRIBUTE = 'switch_width'\n

The attribute that should be used to store the switch width of a potential, if the potential should use the standard OpenMM switch function.

This attribute should be omitted if the potential should not use a switch function.

"},{"location":"reference/#smee.EnergyFn","title":"EnergyFn","text":"

Bases: _StrEnum

An enumeration of the energy functions supported by smee out of the box.

"},{"location":"reference/#smee.PotentialType","title":"PotentialType","text":"

Bases: _StrEnum

An enumeration of the potential types supported by smee out of the box.

"},{"location":"reference/#smee.NonbondedParameterMap","title":"NonbondedParameterMap dataclass","text":"
NonbondedParameterMap(\n    assignment_matrix: Tensor,\n    exclusions: Tensor,\n    exclusion_scale_idxs: Tensor,\n)\n

A map between atom indices part of a particular valence interaction (e.g. torsion indices) and the corresponding parameter in a TensorPotential

Methods:

  • to \u2013

    Cast this object to the specified device.

Attributes:

  • assignment_matrix (Tensor) \u2013

    A sparse tensor that yields the parameters assigned to each particle in the

  • exclusions (Tensor) \u2013

    Indices of pairs of particles (i.e. atoms or virtual sites) that should

  • exclusion_scale_idxs (Tensor) \u2013

    Indices into the tensor of handler attributes defining the 1-n scaling factors

"},{"location":"reference/#smee.NonbondedParameterMap.assignment_matrix","title":"assignment_matrix instance-attribute","text":"
assignment_matrix: Tensor\n

A sparse tensor that yields the parameters assigned to each particle in the system when multiplied with the corresponding handler parameters, with shape=(n_particles, n_parameters).

"},{"location":"reference/#smee.NonbondedParameterMap.exclusions","title":"exclusions instance-attribute","text":"
exclusions: Tensor\n

Indices of pairs of particles (i.e. atoms or virtual sites) that should have their interactions scaled by some factor with shape=(n_exclusions, 2).

"},{"location":"reference/#smee.NonbondedParameterMap.exclusion_scale_idxs","title":"exclusion_scale_idxs instance-attribute","text":"
exclusion_scale_idxs: Tensor\n

Indices into the tensor of handler attributes defining the 1-n scaling factors with shape=(n_exclusions, 1).

"},{"location":"reference/#smee.NonbondedParameterMap.to","title":"to","text":"
to(\n    device: DeviceType | None = None,\n    precision: Precision | None = None,\n) -> NonbondedParameterMap\n

Cast this object to the specified device.

Source code in smee/_models.py
def to(\n    self, device: DeviceType | None = None, precision: Precision | None = None\n) -> \"NonbondedParameterMap\":\n    \"\"\"Cast this object to the specified device.\"\"\"\n    return NonbondedParameterMap(\n        _cast(self.assignment_matrix, device, precision),\n        _cast(self.exclusions, device, precision),\n        _cast(self.exclusion_scale_idxs, device, precision),\n    )\n
"},{"location":"reference/#smee.TensorConstraints","title":"TensorConstraints dataclass","text":"
TensorConstraints(idxs: Tensor, distances: Tensor)\n

A tensor representation of a set of distance constraints between pairs of atoms.

Methods:

  • to \u2013

    Cast this object to the specified device.

Attributes:

  • idxs (Tensor) \u2013

    The indices of the atoms involved in each constraint with

  • distances (Tensor) \u2013

    The distance [\u00c5] between each pair of atoms with shape=(n_constraints,)

"},{"location":"reference/#smee.TensorConstraints.idxs","title":"idxs instance-attribute","text":"
idxs: Tensor\n

The indices of the atoms involved in each constraint with shape=(n_constraints, 2)

"},{"location":"reference/#smee.TensorConstraints.distances","title":"distances instance-attribute","text":"
distances: Tensor\n

The distance [\u00c5] between each pair of atoms with shape=(n_constraints,)

"},{"location":"reference/#smee.TensorConstraints.to","title":"to","text":"
to(\n    device: DeviceType | None = None,\n    precision: Precision | None = None,\n) -> TensorConstraints\n

Cast this object to the specified device.

Source code in smee/_models.py
def to(\n    self, device: DeviceType | None = None, precision: Precision | None = None\n) -> \"TensorConstraints\":\n    \"\"\"Cast this object to the specified device.\"\"\"\n    return TensorConstraints(\n        _cast(self.idxs, device, precision),\n        _cast(self.distances, device, precision),\n    )\n
"},{"location":"reference/#smee.TensorForceField","title":"TensorForceField dataclass","text":"
TensorForceField(\n    potentials: list[TensorPotential],\n    v_sites: TensorVSites | None = None,\n)\n

A tensor representation of a SMIRNOFF force field.

Methods:

  • to \u2013

    Cast this object to the specified device.

Attributes:

  • potentials (list[TensorPotential]) \u2013

    The terms and associated parameters of the potential energy function.

  • v_sites (TensorVSites | None) \u2013

    Parameters used to add and define the coords of v-sites in the system. The

"},{"location":"reference/#smee.TensorForceField.potentials","title":"potentials instance-attribute","text":"
potentials: list[TensorPotential]\n

The terms and associated parameters of the potential energy function.

"},{"location":"reference/#smee.TensorForceField.v_sites","title":"v_sites class-attribute instance-attribute","text":"
v_sites: TensorVSites | None = None\n

Parameters used to add and define the coords of v-sites in the system. The non-bonded parameters of any v-sites are stored in relevant potentials, e.g. 'vdW' or 'Electrostatics'.

"},{"location":"reference/#smee.TensorForceField.to","title":"to","text":"
to(\n    device: DeviceType | None = None,\n    precision: Precision | None = None,\n) -> TensorForceField\n

Cast this object to the specified device.

Source code in smee/_models.py
def to(\n    self, device: DeviceType | None = None, precision: Precision | None = None\n) -> \"TensorForceField\":\n    \"\"\"Cast this object to the specified device.\"\"\"\n    return TensorForceField(\n        [potential.to(device, precision) for potential in self.potentials],\n        None if self.v_sites is None else self.v_sites.to(device, precision),\n    )\n
"},{"location":"reference/#smee.TensorPotential","title":"TensorPotential dataclass","text":"
TensorPotential(\n    type: str,\n    fn: str,\n    parameters: Tensor,\n    parameter_keys: list[PotentialKey],\n    parameter_cols: tuple[str, ...],\n    parameter_units: tuple[Unit, ...],\n    attributes: Tensor | None = None,\n    attribute_cols: tuple[str, ...] | None = None,\n    attribute_units: tuple[Unit, ...] | None = None,\n    exceptions: dict[tuple[int, int], int] | None = None,\n)\n

A tensor representation of a valence SMIRNOFF parameter handler

Methods:

  • to \u2013

    Cast this object to the specified device.

Attributes:

  • type (str) \u2013

    The type of handler associated with these parameters

  • fn (str) \u2013

    The associated potential energy function

  • parameters (Tensor) \u2013

    The values of the parameters with shape=(n_parameters, n_parameter_cols)

  • parameter_keys (list[PotentialKey]) \u2013

    Unique keys associated with each parameter with length=(n_parameters)

  • parameter_cols (tuple[str, ...]) \u2013

    The names of each column of parameters.

  • parameter_units (tuple[Unit, ...]) \u2013

    The units of each parameter in parameters.

  • attributes (Tensor | None) \u2013

    The attributes defined on a handler such as 1-4 scaling factors with

  • attribute_cols (tuple[str, ...] | None) \u2013

    The names of each column of attributes.

  • attribute_units (tuple[Unit, ...] | None) \u2013

    The units of each attribute in attributes.

  • exceptions (dict[tuple[int, int], int] | None) \u2013

    A lookup for custom cross-interaction parameters that should override any mixing

"},{"location":"reference/#smee.TensorPotential.type","title":"type instance-attribute","text":"
type: str\n

The type of handler associated with these parameters

"},{"location":"reference/#smee.TensorPotential.fn","title":"fn instance-attribute","text":"
fn: str\n

The associated potential energy function

"},{"location":"reference/#smee.TensorPotential.parameters","title":"parameters instance-attribute","text":"
parameters: Tensor\n

The values of the parameters with shape=(n_parameters, n_parameter_cols)

"},{"location":"reference/#smee.TensorPotential.parameter_keys","title":"parameter_keys instance-attribute","text":"
parameter_keys: list[PotentialKey]\n

Unique keys associated with each parameter with length=(n_parameters)

"},{"location":"reference/#smee.TensorPotential.parameter_cols","title":"parameter_cols instance-attribute","text":"
parameter_cols: tuple[str, ...]\n

The names of each column of parameters.

"},{"location":"reference/#smee.TensorPotential.parameter_units","title":"parameter_units instance-attribute","text":"
parameter_units: tuple[Unit, ...]\n

The units of each parameter in parameters.

"},{"location":"reference/#smee.TensorPotential.attributes","title":"attributes class-attribute instance-attribute","text":"
attributes: Tensor | None = None\n

The attributes defined on a handler such as 1-4 scaling factors with shape=(n_attribute_cols,)

"},{"location":"reference/#smee.TensorPotential.attribute_cols","title":"attribute_cols class-attribute instance-attribute","text":"
attribute_cols: tuple[str, ...] | None = None\n

The names of each column of attributes.

"},{"location":"reference/#smee.TensorPotential.attribute_units","title":"attribute_units class-attribute instance-attribute","text":"
attribute_units: tuple[Unit, ...] | None = None\n

The units of each attribute in attributes.

"},{"location":"reference/#smee.TensorPotential.exceptions","title":"exceptions class-attribute instance-attribute","text":"
exceptions: dict[tuple[int, int], int] | None = None\n

A lookup for custom cross-interaction parameters that should override any mixing rules.

Each key should correspond to the indices of the two parameters whose mixing rule should be overridden, and each value the index of the parameter that contains the 'pre-mixed' parameter to use instead.

For now, all exceptions are assumed to be symmetric, i.e. if (a, b) is an exception then (b, a) is also an exception, and so only one of the two should be defined.

As a note of caution, not all potentials (e.g. common valence potentials) support such exceptions, and these are predominantly useful for non-bonded potentials.

"},{"location":"reference/#smee.TensorPotential.to","title":"to","text":"
to(\n    device: DeviceType | None = None,\n    precision: Precision | None = None,\n) -> TensorPotential\n

Cast this object to the specified device.

Source code in smee/_models.py
def to(\n    self, device: DeviceType | None = None, precision: Precision | None = None\n) -> \"TensorPotential\":\n    \"\"\"Cast this object to the specified device.\"\"\"\n    return TensorPotential(\n        self.type,\n        self.fn,\n        _cast(self.parameters, device, precision),\n        self.parameter_keys,\n        self.parameter_cols,\n        self.parameter_units,\n        (\n            None\n            if self.attributes is None\n            else _cast(self.attributes, device, precision)\n        ),\n        self.attribute_cols,\n        self.attribute_units,\n        self.exceptions,\n    )\n
"},{"location":"reference/#smee.TensorSystem","title":"TensorSystem dataclass","text":"
TensorSystem(\n    topologies: list[TensorTopology],\n    n_copies: list[int],\n    is_periodic: bool,\n)\n

A tensor representation of a 'full' system.

Methods:

  • to \u2013

    Cast this object to the specified device.

Attributes:

  • topologies (list[TensorTopology]) \u2013

    The topologies of the individual molecules in the system.

  • n_copies (list[int]) \u2013

    The number of copies of each topology to include in the system.

  • is_periodic (bool) \u2013

    Whether the system is periodic or not.

  • n_atoms (int) \u2013

    The number of atoms in the system.

  • n_v_sites (int) \u2013

    The number of v-sites in the system.

  • n_particles (int) \u2013

    The number of atoms + v-sites in the system.

"},{"location":"reference/#smee.TensorSystem.topologies","title":"topologies instance-attribute","text":"
topologies: list[TensorTopology]\n

The topologies of the individual molecules in the system.

"},{"location":"reference/#smee.TensorSystem.n_copies","title":"n_copies instance-attribute","text":"
n_copies: list[int]\n

The number of copies of each topology to include in the system.

"},{"location":"reference/#smee.TensorSystem.is_periodic","title":"is_periodic instance-attribute","text":"
is_periodic: bool\n

Whether the system is periodic or not.

"},{"location":"reference/#smee.TensorSystem.n_atoms","title":"n_atoms property","text":"
n_atoms: int\n

The number of atoms in the system.

"},{"location":"reference/#smee.TensorSystem.n_v_sites","title":"n_v_sites property","text":"
n_v_sites: int\n

The number of v-sites in the system.

"},{"location":"reference/#smee.TensorSystem.n_particles","title":"n_particles property","text":"
n_particles: int\n

The number of atoms + v-sites in the system.

"},{"location":"reference/#smee.TensorSystem.to","title":"to","text":"
to(\n    device: DeviceType | None = None,\n    precision: Precision | None = None,\n) -> TensorSystem\n

Cast this object to the specified device.

Source code in smee/_models.py
def to(\n    self, device: DeviceType | None = None, precision: Precision | None = None\n) -> \"TensorSystem\":\n    \"\"\"Cast this object to the specified device.\"\"\"\n    return TensorSystem(\n        [topology.to(device, precision) for topology in self.topologies],\n        self.n_copies,\n        self.is_periodic,\n    )\n
"},{"location":"reference/#smee.TensorTopology","title":"TensorTopology dataclass","text":"
TensorTopology(\n    atomic_nums: Tensor,\n    formal_charges: Tensor,\n    bond_idxs: Tensor,\n    bond_orders: Tensor,\n    parameters: dict[str, ParameterMap],\n    v_sites: VSiteMap | None = None,\n    constraints: TensorConstraints | None = None,\n    residue_idxs: list[int] | None = None,\n    residue_ids: list[str] | None = None,\n    chain_idxs: list[int] | None = None,\n    chain_ids: list[str] | None = None,\n)\n

A tensor representation of a molecular topology that has been assigned force field parameters.

Methods:

  • to \u2013

    Cast this object to the specified device.

Attributes:

  • atomic_nums (Tensor) \u2013

    The atomic numbers of each atom in the topology with shape=(n_atoms,)

  • formal_charges (Tensor) \u2013

    The formal charge of each atom in the topology with shape=(n_atoms,)

  • bond_idxs (Tensor) \u2013

    The indices of the atoms involved in each bond with shape=(n_bonds, 2)

  • bond_orders (Tensor) \u2013

    The bond orders of each bond with shape=(n_bonds,)

  • parameters (dict[str, ParameterMap]) \u2013

    The parameters that have been assigned to the topology.

  • v_sites (VSiteMap | None) \u2013

    The v-sites that have been assigned to the topology.

  • constraints (TensorConstraints | None) \u2013

    Distance constraints that should be applied during MD simulations. These

  • residue_idxs (list[int] | None) \u2013

    The index of the residue that each atom in the topology belongs to with

  • residue_ids (list[str] | None) \u2013

    The names of the residues that each atom belongs to with length=n_residues.

  • chain_idxs (list[int] | None) \u2013

    The index of the chain that each atom in the topology belongs to with

  • chain_ids (list[str] | None) \u2013

    The names of the chains that each atom belongs to with length=n_chains.

  • n_atoms (int) \u2013

    The number of atoms in the topology.

  • n_bonds (int) \u2013

    The number of bonds in the topology.

  • n_residues (int) \u2013

    The number of residues in the topology

  • n_chains (int) \u2013

    The number of chains in the topology

  • n_v_sites (int) \u2013

    The number of v-sites in the topology.

  • n_particles (int) \u2013

    The number of atoms + v-sites in the topology.

"},{"location":"reference/#smee.TensorTopology.atomic_nums","title":"atomic_nums instance-attribute","text":"
atomic_nums: Tensor\n

The atomic numbers of each atom in the topology with shape=(n_atoms,)

"},{"location":"reference/#smee.TensorTopology.formal_charges","title":"formal_charges instance-attribute","text":"
formal_charges: Tensor\n

The formal charge of each atom in the topology with shape=(n_atoms,)

"},{"location":"reference/#smee.TensorTopology.bond_idxs","title":"bond_idxs instance-attribute","text":"
bond_idxs: Tensor\n

The indices of the atoms involved in each bond with shape=(n_bonds, 2)

"},{"location":"reference/#smee.TensorTopology.bond_orders","title":"bond_orders instance-attribute","text":"
bond_orders: Tensor\n

The bond orders of each bond with shape=(n_bonds,)

"},{"location":"reference/#smee.TensorTopology.parameters","title":"parameters instance-attribute","text":"
parameters: dict[str, ParameterMap]\n

The parameters that have been assigned to the topology.

"},{"location":"reference/#smee.TensorTopology.v_sites","title":"v_sites class-attribute instance-attribute","text":"
v_sites: VSiteMap | None = None\n

The v-sites that have been assigned to the topology.

"},{"location":"reference/#smee.TensorTopology.constraints","title":"constraints class-attribute instance-attribute","text":"
constraints: TensorConstraints | None = None\n

Distance constraints that should be applied during MD simulations. These will not be used outside of MD simulations.

"},{"location":"reference/#smee.TensorTopology.residue_idxs","title":"residue_idxs class-attribute instance-attribute","text":"
residue_idxs: list[int] | None = None\n

The index of the residue that each atom in the topology belongs to with length=n_atoms.

"},{"location":"reference/#smee.TensorTopology.residue_ids","title":"residue_ids class-attribute instance-attribute","text":"
residue_ids: list[str] | None = None\n

The names of the residues that each atom belongs to with length=n_residues.

"},{"location":"reference/#smee.TensorTopology.chain_idxs","title":"chain_idxs class-attribute instance-attribute","text":"
chain_idxs: list[int] | None = None\n

The index of the chain that each atom in the topology belongs to with length=n_atoms.

"},{"location":"reference/#smee.TensorTopology.chain_ids","title":"chain_ids class-attribute instance-attribute","text":"
chain_ids: list[str] | None = None\n

The names of the chains that each atom belongs to with length=n_chains.

"},{"location":"reference/#smee.TensorTopology.n_atoms","title":"n_atoms property","text":"
n_atoms: int\n

The number of atoms in the topology.

"},{"location":"reference/#smee.TensorTopology.n_bonds","title":"n_bonds property","text":"
n_bonds: int\n

The number of bonds in the topology.

"},{"location":"reference/#smee.TensorTopology.n_residues","title":"n_residues property","text":"
n_residues: int\n

The number of residues in the topology

"},{"location":"reference/#smee.TensorTopology.n_chains","title":"n_chains property","text":"
n_chains: int\n

The number of chains in the topology

"},{"location":"reference/#smee.TensorTopology.n_v_sites","title":"n_v_sites property","text":"
n_v_sites: int\n

The number of v-sites in the topology.

"},{"location":"reference/#smee.TensorTopology.n_particles","title":"n_particles property","text":"
n_particles: int\n

The number of atoms + v-sites in the topology.

"},{"location":"reference/#smee.TensorTopology.to","title":"to","text":"
to(\n    device: DeviceType | None = None,\n    precision: Precision | None = None,\n) -> TensorTopology\n

Cast this object to the specified device.

Source code in smee/_models.py
def to(\n    self, device: DeviceType | None = None, precision: Precision | None = None\n) -> \"TensorTopology\":\n    \"\"\"Cast this object to the specified device.\"\"\"\n    return TensorTopology(\n        self.atomic_nums,\n        self.formal_charges,\n        self.bond_idxs,\n        self.bond_orders,\n        {k: v.to(device, precision) for k, v in self.parameters.items()},\n        None if self.v_sites is None else self.v_sites.to(device, precision),\n        (\n            None\n            if self.constraints is None\n            else self.constraints.to(device, precision)\n        ),\n        self.residue_idxs,\n        self.residue_ids,\n        self.chain_ids,\n    )\n
"},{"location":"reference/#smee.TensorVSites","title":"TensorVSites dataclass","text":"
TensorVSites(\n    keys: List[VirtualSiteKey],\n    weights: list[Tensor],\n    parameters: Tensor,\n)\n

A tensor representation of a set of virtual sites parameters.

Methods:

  • default_units \u2013

    The default units of each v-site parameter.

  • to \u2013

    Cast this object to the specified device.

Attributes:

  • keys (List[VirtualSiteKey]) \u2013

    The unique keys associated with each v-site with length=(n_v_sites)

  • weights (list[Tensor]) \u2013

    A matrix of weights that, when applied to the 'orientiational' atoms, yields a

  • parameters (Tensor) \u2013

    The distance, in-plane and out-of-plane angles with shape=(n_v_sites, 3)

  • parameter_units (dict[str, Unit]) \u2013

    The units of each v-site parameter.

"},{"location":"reference/#smee.TensorVSites.keys","title":"keys instance-attribute","text":"
keys: List[VirtualSiteKey]\n

The unique keys associated with each v-site with length=(n_v_sites)

"},{"location":"reference/#smee.TensorVSites.weights","title":"weights instance-attribute","text":"
weights: list[Tensor]\n

A matrix of weights that, when applied to the 'orientiational' atoms, yields a basis that the virtual site coordinate parameters can be projected onto with shape=(n_v_sites, 3, 3)

"},{"location":"reference/#smee.TensorVSites.parameters","title":"parameters instance-attribute","text":"
parameters: Tensor\n

The distance, in-plane and out-of-plane angles with shape=(n_v_sites, 3)

"},{"location":"reference/#smee.TensorVSites.parameter_units","title":"parameter_units property","text":"
parameter_units: dict[str, Unit]\n

The units of each v-site parameter.

"},{"location":"reference/#smee.TensorVSites.default_units","title":"default_units classmethod","text":"
default_units() -> dict[str, Unit]\n

The default units of each v-site parameter.

Source code in smee/_models.py
@classmethod\ndef default_units(cls) -> dict[str, openff.units.Unit]:\n    \"\"\"The default units of each v-site parameter.\"\"\"\n    return {\n        \"distance\": _ANGSTROM,\n        \"inPlaneAngle\": _RADIANS,\n        \"outOfPlaneAngle\": _RADIANS,\n    }\n
"},{"location":"reference/#smee.TensorVSites.to","title":"to","text":"
to(\n    device: DeviceType | None = None,\n    precision: Precision | None = None,\n) -> TensorVSites\n

Cast this object to the specified device.

Source code in smee/_models.py
def to(\n    self, device: DeviceType | None = None, precision: Precision | None = None\n) -> \"TensorVSites\":\n    \"\"\"Cast this object to the specified device.\"\"\"\n    return TensorVSites(\n        self.keys,\n        [_cast(weight, device, precision) for weight in self.weights],\n        _cast(self.parameters, device, precision),\n    )\n
"},{"location":"reference/#smee.ValenceParameterMap","title":"ValenceParameterMap dataclass","text":"
ValenceParameterMap(\n    particle_idxs: Tensor, assignment_matrix: Tensor\n)\n

A map between atom indices part of a particular valence interaction (e.g. torsion indices) and the corresponding parameter in a TensorPotential

Methods:

  • to \u2013

    Cast this object to the specified device.

Attributes:

  • particle_idxs (Tensor) \u2013

    The indices of the particles (e.g. atoms or virtual sites) involved in an

  • assignment_matrix (Tensor) \u2013

    A sparse tensor that yields the assigned parameters when multiplied with the

"},{"location":"reference/#smee.ValenceParameterMap.particle_idxs","title":"particle_idxs instance-attribute","text":"
particle_idxs: Tensor\n

The indices of the particles (e.g. atoms or virtual sites) involved in an interaction with shape=(n_interactions, n_cols). For a bond n_cols=2, for angles n_cols=3 etc.

"},{"location":"reference/#smee.ValenceParameterMap.assignment_matrix","title":"assignment_matrix instance-attribute","text":"
assignment_matrix: Tensor\n

A sparse tensor that yields the assigned parameters when multiplied with the corresponding handler parameters, with shape=(n_interacting, n_parameters).

"},{"location":"reference/#smee.ValenceParameterMap.to","title":"to","text":"
to(\n    device: DeviceType | None = None,\n    precision: Precision | None = None,\n) -> ValenceParameterMap\n

Cast this object to the specified device.

Source code in smee/_models.py
def to(\n    self, device: DeviceType | None = None, precision: Precision | None = None\n) -> \"ValenceParameterMap\":\n    \"\"\"Cast this object to the specified device.\"\"\"\n    return ValenceParameterMap(\n        _cast(self.particle_idxs, device, precision),\n        _cast(self.assignment_matrix, device, precision),\n    )\n
"},{"location":"reference/#smee.VSiteMap","title":"VSiteMap dataclass","text":"
VSiteMap(\n    keys: list[VirtualSiteKey],\n    key_to_idx: dict[VirtualSiteKey, int],\n    parameter_idxs: Tensor,\n)\n

A map between virtual sites that have been added to a topology and their corresponding 'parameters' used to position them.

Methods:

  • to \u2013

    Cast this object to the specified device.

Attributes:

  • keys (list[VirtualSiteKey]) \u2013

    The keys used to identify each v-site.

  • key_to_idx (dict[VirtualSiteKey, int]) \u2013

    A map between the unique keys associated with each v-site and their index in

  • parameter_idxs (Tensor) \u2013

    The indices of the corresponding v-site parameters with shape=(n_v_sites, 1)

"},{"location":"reference/#smee.VSiteMap.keys","title":"keys instance-attribute","text":"
keys: list[VirtualSiteKey]\n

The keys used to identify each v-site.

"},{"location":"reference/#smee.VSiteMap.key_to_idx","title":"key_to_idx instance-attribute","text":"
key_to_idx: dict[VirtualSiteKey, int]\n

A map between the unique keys associated with each v-site and their index in the topology

"},{"location":"reference/#smee.VSiteMap.parameter_idxs","title":"parameter_idxs instance-attribute","text":"
parameter_idxs: Tensor\n

The indices of the corresponding v-site parameters with shape=(n_v_sites, 1)

"},{"location":"reference/#smee.VSiteMap.to","title":"to","text":"
to(\n    device: DeviceType | None = None,\n    precision: Precision | None = None,\n) -> VSiteMap\n

Cast this object to the specified device.

Source code in smee/_models.py
def to(\n    self, device: DeviceType | None = None, precision: Precision | None = None\n) -> \"VSiteMap\":\n    \"\"\"Cast this object to the specified device.\"\"\"\n    return VSiteMap(\n        self.keys, self.key_to_idx, _cast(self.parameter_idxs, device, precision)\n    )\n
"},{"location":"reference/#smee.add_v_site_coords","title":"add_v_site_coords","text":"
add_v_site_coords(\n    v_sites: VSiteMap,\n    conformer: Tensor,\n    force_field: TensorForceField,\n) -> Tensor\n

Appends the coordinates of any virtual sites to a conformer (or batch of conformers) containing only atomic coordinates.

Notes
  • This function only supports appending v-sites to the end of the list of coordinates, and not interleaving them between existing atomic coordinates.

Parameters:

  • v_sites (VSiteMap) \u2013

    A mapping between the virtual sites to add and their corresponding force field parameters.

  • conformer (Tensor) \u2013

    The conformer(s) to add the virtual sites to with shape=(n_atoms, 3) or shape=(n_batches, n_atoms, 3) and units of [\u00c5].

  • force_field (TensorForceField) \u2013

    The force field containing the virtual site parameters.

Returns:

  • Tensor \u2013

    The full conformer(s) with both atomic and virtual site coordinates [\u00c5] with shape=(n_atoms+n_v_sites, 3) or shape=(n_batches, n_atoms+n_v_sites, 3).

Source code in smee/geometry.py
def add_v_site_coords(\n    v_sites: \"smee.VSiteMap\",\n    conformer: torch.Tensor,\n    force_field: \"smee.TensorForceField\",\n) -> torch.Tensor:\n    \"\"\"Appends the coordinates of any virtual sites to a conformer (or batch of\n    conformers) containing only atomic coordinates.\n\n    Notes:\n        * This function only supports appending v-sites to the end of the list of\n          coordinates, and not interleaving them between existing atomic coordinates.\n\n    Args:\n        v_sites: A mapping between the virtual sites to add and their corresponding\n            force field parameters.\n        conformer: The conformer(s) to add the virtual sites to with\n            ``shape=(n_atoms, 3)`` or ``shape=(n_batches, n_atoms, 3)`` and units of\n            [\u00c5].\n        force_field: The force field containing the virtual site parameters.\n\n    Returns:\n        The full conformer(s) with both atomic and virtual site coordinates [\u00c5] with\n        ``shape=(n_atoms+n_v_sites, 3)`` or ``shape=(n_batches, n_atoms+n_v_sites, 3)``.\n    \"\"\"\n\n    v_site_coords = compute_v_site_coords(v_sites, conformer, force_field)\n\n    return torch.cat([conformer, v_site_coords], dim=(1 if conformer.ndim == 3 else 0))\n
"},{"location":"reference/#smee.compute_v_site_coords","title":"compute_v_site_coords","text":"
compute_v_site_coords(\n    v_sites: VSiteMap,\n    conformer: Tensor,\n    force_field: TensorForceField,\n) -> Tensor\n

Computes the positions of a set of virtual sites relative to a specified conformer or batch of conformers.

Parameters:

  • v_sites (VSiteMap) \u2013

    A mapping between the virtual sites to add and their corresponding force field parameters.

  • conformer (Tensor) \u2013

    The conformer(s) to add the virtual sites to with shape=(n_atoms, 3) or shape=(n_batches, n_atoms, 3) and units of [\u00c5].

  • force_field (TensorForceField) \u2013

    The force field containing the virtual site parameters.

Returns:

  • Tensor \u2013

    A tensor of virtual site positions [\u00c5] with shape=(n_v_sites, 3) or shape=(n_batches, n_v_sites, 3).

Source code in smee/geometry.py
def compute_v_site_coords(\n    v_sites: \"smee.VSiteMap\",\n    conformer: torch.Tensor,\n    force_field: \"smee.TensorForceField\",\n) -> torch.Tensor:\n    \"\"\"Computes the positions of a set of virtual sites relative to a specified\n    conformer or batch of conformers.\n\n    Args:\n        v_sites: A mapping between the virtual sites to add and their corresponding\n            force field parameters.\n        conformer: The conformer(s) to add the virtual sites to with\n            ``shape=(n_atoms, 3)`` or ``shape=(n_batches, n_atoms, 3)`` and units of\n            [\u00c5].\n        force_field: The force field containing the virtual site parameters.\n\n    Returns:\n        A tensor of virtual site positions [\u00c5] with ``shape=(n_v_sites, 3)`` or\n        ``shape=(n_batches, n_v_sites, 3)``.\n    \"\"\"\n\n    is_batched = conformer.ndim == 3\n\n    if not is_batched:\n        conformer = torch.unsqueeze(conformer, 0)\n\n    if len(v_sites.parameter_idxs) > 0:\n        local_frame_coords = force_field.v_sites.parameters[v_sites.parameter_idxs]\n        local_coord_frames = _build_v_site_coord_frames(v_sites, conformer, force_field)\n\n        v_site_coords = _convert_v_site_coords(local_frame_coords, local_coord_frames)\n    else:\n        v_site_coords = smee.utils.zeros_like((len(conformer), 0, 3), other=conformer)\n\n    if not is_batched:\n        v_site_coords = torch.squeeze(v_site_coords, 0)\n\n    return v_site_coords\n
"},{"location":"reference/#smee.compute_energy","title":"compute_energy","text":"
compute_energy(\n    system: TensorSystem | TensorTopology,\n    force_field: TensorForceField,\n    conformer: Tensor,\n    box_vectors: Tensor | None = None,\n) -> Tensor\n

Computes the potential energy [kcal / mol] of a system / topology in a given conformation(s).

Parameters:

  • system (TensorSystem | TensorTopology) \u2013

    The system or topology to compute the potential energy of.

  • force_field (TensorForceField) \u2013

    The force field that defines the potential energy function.

  • conformer (Tensor) \u2013

    The conformer(s) to evaluate the potential at with shape=(n_particles, 3) or shape=(n_confs, n_particles, 3).

  • box_vectors (Tensor | None, default: None ) \u2013

    The box vectors of the system with shape=(3, 3) if the system is periodic, or None if the system is non-periodic.

Returns:

  • Tensor \u2013

    The potential energy of the conformer(s) [kcal / mol].

Source code in smee/potentials/_potentials.py
def compute_energy(\n    system: smee.TensorSystem | smee.TensorTopology,\n    force_field: smee.TensorForceField,\n    conformer: torch.Tensor,\n    box_vectors: torch.Tensor | None = None,\n) -> torch.Tensor:\n    \"\"\"Computes the potential energy [kcal / mol] of a system / topology in a given\n    conformation(s).\n\n    Args:\n        system: The system or topology to compute the potential energy of.\n        force_field: The force field that defines the potential energy function.\n        conformer: The conformer(s) to evaluate the potential at with\n            ``shape=(n_particles, 3)`` or ``shape=(n_confs, n_particles, 3)``.\n        box_vectors: The box vectors of the system with ``shape=(3, 3)`` if the\n            system is periodic, or ``None`` if the system is non-periodic.\n\n    Returns:\n        The potential energy of the conformer(s) [kcal / mol].\n    \"\"\"\n\n    # register the built-in potential energy functions\n    importlib.import_module(\"smee.potentials.nonbonded\")\n    importlib.import_module(\"smee.potentials.valence\")\n\n    system, conformer, box_vectors = _prepare_inputs(system, conformer, box_vectors)\n    pairwise = _precompute_pairwise(system, force_field, conformer, box_vectors)\n\n    energy = smee.utils.zeros_like(\n        conformer.shape[0] if conformer.ndim == 3 else 1, conformer\n    )\n\n    for potential in force_field.potentials:\n        energy += compute_energy_potential(\n            system, potential, conformer, box_vectors, pairwise\n        )\n\n    return energy\n
"},{"location":"reference/#smee.compute_energy_potential","title":"compute_energy_potential","text":"
compute_energy_potential(\n    system: TensorSystem | TensorTopology,\n    potential: TensorPotential,\n    conformer: Tensor,\n    box_vectors: Tensor | None = None,\n    pairwise: Optional[PairwiseDistances] = None,\n) -> Tensor\n

Computes the potential energy [kcal / mol] due to a SMIRNOFF potential handler for a given conformer(s).

Parameters:

  • system (TensorSystem | TensorTopology) \u2013

    The system or topology to compute the potential energy of.

  • potential (TensorPotential) \u2013

    The potential describing the energy function to compute.

  • conformer (Tensor) \u2013

    The conformer(s) to evaluate the potential at with shape=(n_particles, 3) or shape=(n_confs, n_particles, 3).

  • box_vectors (Tensor | None, default: None ) \u2013

    The box vectors of the system with shape=(3, 3) or shape=(n_confs, 3, 3)if the system is periodic, orNone`` if the system is non-periodic.

  • pairwise (Optional[PairwiseDistances], default: None ) \u2013

    Pre-computed pairwise distances between particles in the system.

Returns:

  • Tensor \u2013

    The potential energy of the conformer(s) [kcal / mol].

Source code in smee/potentials/_potentials.py
def compute_energy_potential(\n    system: smee.TensorSystem | smee.TensorTopology,\n    potential: smee.TensorPotential,\n    conformer: torch.Tensor,\n    box_vectors: torch.Tensor | None = None,\n    pairwise: typing.Optional[\"smee.potentials.nonbonded.PairwiseDistances\"] = None,\n) -> torch.Tensor:\n    \"\"\"Computes the potential energy [kcal / mol] due to a SMIRNOFF potential\n    handler for a given conformer(s).\n\n    Args:\n        system: The system or topology to compute the potential energy of.\n        potential: The potential describing the energy function to compute.\n        conformer: The conformer(s) to evaluate the potential at with\n            ``shape=(n_particles, 3)`` or ``shape=(n_confs, n_particles, 3)``.\n        box_vectors: The box vectors of the system with ``shape=(3, 3)`` or\n            shape=(n_confs, 3, 3)`` if the system is periodic, or ``None`` if the system\n            is non-periodic.\n        pairwise: Pre-computed pairwise distances between particles in the system.\n\n    Returns:\n        The potential energy of the conformer(s) [kcal / mol].\n    \"\"\"\n\n    # register the built-in potential energy functions\n    importlib.import_module(\"smee.potentials.nonbonded\")\n    importlib.import_module(\"smee.potentials.valence\")\n\n    system, conformer, box_vectors = _prepare_inputs(system, conformer, box_vectors)\n\n    energy_fn = _POTENTIAL_ENERGY_FUNCTIONS[(potential.type, potential.fn)]\n    energy_fn_spec = inspect.signature(energy_fn)\n\n    energy_fn_kwargs = {}\n\n    if \"box_vectors\" in energy_fn_spec.parameters:\n        energy_fn_kwargs[\"box_vectors\"] = box_vectors\n    if \"pairwise\" in energy_fn_spec.parameters:\n        energy_fn_kwargs[\"pairwise\"] = pairwise\n\n    return energy_fn(system, potential, conformer, **energy_fn_kwargs)\n
"},{"location":"reference/SUMMARY/","title":"SUMMARY","text":"
  • smee
    • converters
      • openff
        • nonbonded
        • valence
      • openmm
        • nonbonded
        • valence
    • geometry
    • mm
    • potentials
      • nonbonded
      • valence
    • utils
"},{"location":"reference/geometry/","title":" geometry","text":""},{"location":"reference/geometry/#smee.geometry","title":"geometry","text":"

Compute internal coordinates (e.g. bond lengths).

Functions:

  • compute_bond_vectors \u2013

    Computes the vectors between each atom pair specified by the atom_indices as

  • compute_angles \u2013

    Computes the angles [rad] between each atom triplet specified by the

  • compute_dihedrals \u2013

    Computes the dihedral angles [rad] between each atom quartet specified by the

  • polar_to_cartesian_coords \u2013

    Converts a set of polar coordinates into cartesian coordinates.

  • compute_v_site_coords \u2013

    Computes the positions of a set of virtual sites relative to a specified

  • add_v_site_coords \u2013

    Appends the coordinates of any virtual sites to a conformer (or batch of

"},{"location":"reference/geometry/#smee.geometry.compute_bond_vectors","title":"compute_bond_vectors","text":"
compute_bond_vectors(\n    conformer: Tensor, atom_indices: Tensor\n) -> tuple[Tensor, Tensor]\n

Computes the vectors between each atom pair specified by the atom_indices as well as their norms.

Parameters:

  • conformer (Tensor) \u2013

    The conformer [\u00c5] to compute the bond vectors for with shape=(n_atoms, 3) or shape=(n_confs, n_atoms, 3).

  • atom_indices (Tensor) \u2013

    The indices of the atoms involved in each bond with shape=(n_bonds, 2)

Returns:

  • tuple[Tensor, Tensor] \u2013

    The bond vectors and their norms [\u00c5].

Source code in smee/geometry.py
def compute_bond_vectors(\n    conformer: torch.Tensor, atom_indices: torch.Tensor\n) -> tuple[torch.Tensor, torch.Tensor]:\n    \"\"\"Computes the vectors between each atom pair specified by the ``atom_indices`` as\n    well as their norms.\n\n    Args:\n        conformer: The conformer [\u00c5] to compute the bond vectors for with\n            ``shape=(n_atoms, 3)`` or ``shape=(n_confs, n_atoms, 3)``.\n        atom_indices: The indices of the atoms involved in each bond with\n            ``shape=(n_bonds, 2)``\n\n    Returns:\n        The bond vectors and their norms [\u00c5].\n    \"\"\"\n\n    if len(atom_indices) == 0:\n        return (\n            smee.utils.tensor_like([], other=conformer),\n            smee.utils.tensor_like([], other=conformer),\n        )\n\n    is_batched = conformer.ndim == 3\n\n    if not is_batched:\n        conformer = torch.unsqueeze(conformer, 0)\n\n    directions = conformer[:, atom_indices[:, 1]] - conformer[:, atom_indices[:, 0]]\n    distances = torch.norm(directions, dim=-1)\n\n    if not is_batched:\n        directions = torch.squeeze(directions, dim=0)\n        distances = torch.squeeze(distances, dim=0)\n\n    return directions, distances\n
"},{"location":"reference/geometry/#smee.geometry.compute_angles","title":"compute_angles","text":"
compute_angles(\n    conformer: Tensor, atom_indices: Tensor\n) -> Tensor\n

Computes the angles [rad] between each atom triplet specified by the atom_indices.

Parameters:

  • conformer (Tensor) \u2013

    The conformer [\u00c5] to compute the angles for with shape=(n_atoms, 3) or shape=(n_confs, n_atoms, 3).

  • atom_indices (Tensor) \u2013

    The indices of the atoms involved in each angle with shape=(n_angles, 3).

Returns:

  • Tensor \u2013

    The valence angles [rad].

Source code in smee/geometry.py
def compute_angles(conformer: torch.Tensor, atom_indices: torch.Tensor) -> torch.Tensor:\n    \"\"\"Computes the angles [rad] between each atom triplet specified by the\n    ``atom_indices``.\n\n    Args:\n        conformer: The conformer [\u00c5] to compute the angles for with\n            ``shape=(n_atoms, 3)`` or ``shape=(n_confs, n_atoms, 3)``.\n        atom_indices: The indices of the atoms involved in each angle with\n            ``shape=(n_angles, 3)``.\n\n    Returns:\n        The valence angles [rad].\n    \"\"\"\n\n    if len(atom_indices) == 0:\n        return smee.utils.tensor_like([], other=conformer)\n\n    is_batched = conformer.ndim == 3\n\n    if not is_batched:\n        conformer = torch.unsqueeze(conformer, 0)\n\n    vector_ab = conformer[:, atom_indices[:, 1]] - conformer[:, atom_indices[:, 0]]\n    vector_ac = conformer[:, atom_indices[:, 1]] - conformer[:, atom_indices[:, 2]]\n\n    # tan theta = sin theta / cos theta\n    #\n    # ||a x b|| = ||a|| ||b|| sin theta\n    #   a . b   = ||a|| ||b|| cos theta\n    #\n    # => tan theta = (a x b) / (a . b)\n    angles = torch.atan2(\n        torch.norm(torch.cross(vector_ab, vector_ac, dim=-1), dim=-1),\n        (vector_ab * vector_ac).sum(dim=-1),\n    )\n\n    if not is_batched:\n        angles = torch.squeeze(angles, dim=0)\n\n    return angles\n
"},{"location":"reference/geometry/#smee.geometry.compute_dihedrals","title":"compute_dihedrals","text":"
compute_dihedrals(\n    conformer: Tensor, atom_indices: Tensor\n) -> Tensor\n

Computes the dihedral angles [rad] between each atom quartet specified by the atom_indices.

Parameters:

  • conformer (Tensor) \u2013

    The conformer [\u00c5] to compute the dihedral angles for with shape=(n_atoms, 3) or shape=(n_confs, n_atoms, 3).

  • atom_indices (Tensor) \u2013

    The indices of the atoms involved in each dihedral angle with shape=(n_dihedrals, 4).

Returns:

  • Tensor \u2013

    The dihedral angles [rad].

Source code in smee/geometry.py
def compute_dihedrals(\n    conformer: torch.Tensor, atom_indices: torch.Tensor\n) -> torch.Tensor:\n    \"\"\"Computes the dihedral angles [rad] between each atom quartet specified by the\n    ``atom_indices``.\n\n    Args:\n        conformer: The conformer [\u00c5] to compute the dihedral angles for with\n            ``shape=(n_atoms, 3)`` or ``shape=(n_confs, n_atoms, 3)``.\n        atom_indices: The indices of the atoms involved in each dihedral angle with\n            ``shape=(n_dihedrals, 4)``.\n\n    Returns:\n        The dihedral angles [rad].\n    \"\"\"\n\n    if len(atom_indices) == 0:\n        return smee.utils.tensor_like([], other=conformer)\n\n    is_batched = conformer.ndim == 3\n\n    if not is_batched:\n        conformer = torch.unsqueeze(conformer, 0)\n\n    # Based on the OpenMM formalism.\n    vector_ab = conformer[:, atom_indices[:, 0]] - conformer[:, atom_indices[:, 1]]\n    vector_cb = conformer[:, atom_indices[:, 2]] - conformer[:, atom_indices[:, 1]]\n    vector_cd = conformer[:, atom_indices[:, 2]] - conformer[:, atom_indices[:, 3]]\n\n    vector_ab_cross_cb = torch.cross(vector_ab, vector_cb, dim=-1)\n    vector_cb_cross_cd = torch.cross(vector_cb, vector_cd, dim=-1)\n\n    vector_cb_norm = torch.norm(vector_cb, dim=-1).unsqueeze(-1)\n\n    y = (\n        torch.cross(vector_ab_cross_cb, vector_cb_cross_cd, dim=-1)\n        * vector_cb\n        / vector_cb_norm\n    ).sum(axis=-1)\n\n    x = (vector_ab_cross_cb * vector_cb_cross_cd).sum(axis=-1)\n\n    phi = torch.atan2(y, x)\n\n    if not is_batched:\n        phi = torch.squeeze(phi, dim=0)\n\n    return phi\n
"},{"location":"reference/geometry/#smee.geometry.polar_to_cartesian_coords","title":"polar_to_cartesian_coords","text":"
polar_to_cartesian_coords(polar_coords: Tensor) -> Tensor\n

Converts a set of polar coordinates into cartesian coordinates.

Parameters:

  • polar_coords (Tensor) \u2013

    The polar coordinates with shape=(n_coords, 3) and with columns of distance [\u00c5], 'in plane angle' [rad] and 'out of plane' angle [rad].

Returns:

  • Tensor \u2013

    An array of the cartesian coordinates with shape=(n_coords, 3) and units of [\u00c5].

Source code in smee/geometry.py
def polar_to_cartesian_coords(polar_coords: torch.Tensor) -> torch.Tensor:\n    \"\"\"Converts a set of polar coordinates into cartesian coordinates.\n\n    Args:\n        polar_coords: The polar coordinates with ``shape=(n_coords, 3)`` and with\n            columns of distance [\u00c5], 'in plane angle' [rad] and 'out of plane'\n            angle [rad].\n\n    Returns:\n        An array of the cartesian coordinates with ``shape=(n_coords, 3)`` and units\n        of [\u00c5].\n    \"\"\"\n\n    d, theta, phi = polar_coords[:, 0], polar_coords[:, 1], polar_coords[:, 2]\n\n    cos_theta = torch.cos(theta)\n    sin_theta = torch.sin(theta)\n\n    cos_phi = torch.cos(phi)\n    sin_phi = torch.sin(phi)\n\n    # Here we use cos(phi) in place of sin(phi) and sin(phi) in place of cos(phi)\n    # this is because we want phi=0 to represent a 0 degree angle from the x-y plane\n    # rather than 0 degrees from the z-axis.\n    coords = torch.stack(\n        [d * cos_theta * cos_phi, d * sin_theta * cos_phi, d * sin_phi], dim=-1\n    )\n    return coords\n
"},{"location":"reference/geometry/#smee.geometry.compute_v_site_coords","title":"compute_v_site_coords","text":"
compute_v_site_coords(\n    v_sites: VSiteMap,\n    conformer: Tensor,\n    force_field: TensorForceField,\n) -> Tensor\n

Computes the positions of a set of virtual sites relative to a specified conformer or batch of conformers.

Parameters:

  • v_sites (VSiteMap) \u2013

    A mapping between the virtual sites to add and their corresponding force field parameters.

  • conformer (Tensor) \u2013

    The conformer(s) to add the virtual sites to with shape=(n_atoms, 3) or shape=(n_batches, n_atoms, 3) and units of [\u00c5].

  • force_field (TensorForceField) \u2013

    The force field containing the virtual site parameters.

Returns:

  • Tensor \u2013

    A tensor of virtual site positions [\u00c5] with shape=(n_v_sites, 3) or shape=(n_batches, n_v_sites, 3).

Source code in smee/geometry.py
def compute_v_site_coords(\n    v_sites: \"smee.VSiteMap\",\n    conformer: torch.Tensor,\n    force_field: \"smee.TensorForceField\",\n) -> torch.Tensor:\n    \"\"\"Computes the positions of a set of virtual sites relative to a specified\n    conformer or batch of conformers.\n\n    Args:\n        v_sites: A mapping between the virtual sites to add and their corresponding\n            force field parameters.\n        conformer: The conformer(s) to add the virtual sites to with\n            ``shape=(n_atoms, 3)`` or ``shape=(n_batches, n_atoms, 3)`` and units of\n            [\u00c5].\n        force_field: The force field containing the virtual site parameters.\n\n    Returns:\n        A tensor of virtual site positions [\u00c5] with ``shape=(n_v_sites, 3)`` or\n        ``shape=(n_batches, n_v_sites, 3)``.\n    \"\"\"\n\n    is_batched = conformer.ndim == 3\n\n    if not is_batched:\n        conformer = torch.unsqueeze(conformer, 0)\n\n    if len(v_sites.parameter_idxs) > 0:\n        local_frame_coords = force_field.v_sites.parameters[v_sites.parameter_idxs]\n        local_coord_frames = _build_v_site_coord_frames(v_sites, conformer, force_field)\n\n        v_site_coords = _convert_v_site_coords(local_frame_coords, local_coord_frames)\n    else:\n        v_site_coords = smee.utils.zeros_like((len(conformer), 0, 3), other=conformer)\n\n    if not is_batched:\n        v_site_coords = torch.squeeze(v_site_coords, 0)\n\n    return v_site_coords\n
"},{"location":"reference/geometry/#smee.geometry.add_v_site_coords","title":"add_v_site_coords","text":"
add_v_site_coords(\n    v_sites: VSiteMap,\n    conformer: Tensor,\n    force_field: TensorForceField,\n) -> Tensor\n

Appends the coordinates of any virtual sites to a conformer (or batch of conformers) containing only atomic coordinates.

Notes
  • This function only supports appending v-sites to the end of the list of coordinates, and not interleaving them between existing atomic coordinates.

Parameters:

  • v_sites (VSiteMap) \u2013

    A mapping between the virtual sites to add and their corresponding force field parameters.

  • conformer (Tensor) \u2013

    The conformer(s) to add the virtual sites to with shape=(n_atoms, 3) or shape=(n_batches, n_atoms, 3) and units of [\u00c5].

  • force_field (TensorForceField) \u2013

    The force field containing the virtual site parameters.

Returns:

  • Tensor \u2013

    The full conformer(s) with both atomic and virtual site coordinates [\u00c5] with shape=(n_atoms+n_v_sites, 3) or shape=(n_batches, n_atoms+n_v_sites, 3).

Source code in smee/geometry.py
def add_v_site_coords(\n    v_sites: \"smee.VSiteMap\",\n    conformer: torch.Tensor,\n    force_field: \"smee.TensorForceField\",\n) -> torch.Tensor:\n    \"\"\"Appends the coordinates of any virtual sites to a conformer (or batch of\n    conformers) containing only atomic coordinates.\n\n    Notes:\n        * This function only supports appending v-sites to the end of the list of\n          coordinates, and not interleaving them between existing atomic coordinates.\n\n    Args:\n        v_sites: A mapping between the virtual sites to add and their corresponding\n            force field parameters.\n        conformer: The conformer(s) to add the virtual sites to with\n            ``shape=(n_atoms, 3)`` or ``shape=(n_batches, n_atoms, 3)`` and units of\n            [\u00c5].\n        force_field: The force field containing the virtual site parameters.\n\n    Returns:\n        The full conformer(s) with both atomic and virtual site coordinates [\u00c5] with\n        ``shape=(n_atoms+n_v_sites, 3)`` or ``shape=(n_batches, n_atoms+n_v_sites, 3)``.\n    \"\"\"\n\n    v_site_coords = compute_v_site_coords(v_sites, conformer, force_field)\n\n    return torch.cat([conformer, v_site_coords], dim=(1 if conformer.ndim == 3 else 0))\n
"},{"location":"reference/utils/","title":" utils","text":""},{"location":"reference/utils/#smee.utils","title":"utils","text":"

General utility functions

Functions:

  • find_exclusions \u2013

    Find all excluded interaction pairs and their associated scaling factor.

  • ones_like \u2013

    Create a tensor of ones with the same device and type as another tensor.

  • zeros_like \u2013

    Create a tensor of zeros with the same device and type as another tensor.

  • tensor_like \u2013

    Create a tensor with the same device and type as another tensor.

  • arange_like \u2013

    Arange a tensor with the same device and type as another tensor.

  • logsumexp \u2013

    Compute the log of the sum of the exponential of the input elements, optionally

  • to_upper_tri_idx \u2013

    Converts pairs of 2D indices to 1D indices in an upper triangular matrix that

  • geometric_mean \u2013

    Computes the geometric mean of two values 'safely'.

Attributes:

  • EPSILON \u2013

    A small epsilon value used to prevent divide by zero errors.

"},{"location":"reference/utils/#smee.utils.EPSILON","title":"EPSILON module-attribute","text":"
EPSILON = 1e-06\n

A small epsilon value used to prevent divide by zero errors.

"},{"location":"reference/utils/#smee.utils.find_exclusions","title":"find_exclusions","text":"
find_exclusions(\n    topology: Topology, v_sites: Optional[VSiteMap] = None\n) -> dict[tuple[int, int], ExclusionType]\n

Find all excluded interaction pairs and their associated scaling factor.

Parameters:

  • topology (Topology) \u2013

    The topology to find the interaction pairs of.

  • v_sites (Optional[VSiteMap], default: None ) \u2013

    Virtual sites that will be added to the topology.

Returns:

  • A dictionary of the form ``{(atom_idx_1, atom_idx_2 \u2013

    scale}``.

Source code in smee/utils.py
def find_exclusions(\n    topology: openff.toolkit.Topology,\n    v_sites: typing.Optional[\"smee.VSiteMap\"] = None,\n) -> dict[tuple[int, int], ExclusionType]:\n    \"\"\"Find all excluded interaction pairs and their associated scaling factor.\n\n    Args:\n        topology: The topology to find the interaction pairs of.\n        v_sites: Virtual sites that will be added to the topology.\n\n    Returns:\n        A dictionary of the form ``{(atom_idx_1, atom_idx_2): scale}``.\n    \"\"\"\n\n    graph = networkx.from_edgelist(\n        tuple(\n            sorted((topology.atom_index(bond.atom1), topology.atom_index(bond.atom2)))\n        )\n        for bond in topology.bonds\n    )\n\n    if v_sites is not None:\n        for v_site_key in v_sites.keys:\n            v_site_idx = v_sites.key_to_idx[v_site_key]\n            parent_idx = v_site_key.orientation_atom_indices[0]\n\n            for neighbour_idx in graph.neighbors(parent_idx):\n                graph.add_edge(v_site_idx, neighbour_idx)\n\n            graph.add_edge(v_site_idx, parent_idx)\n\n    distances = dict(networkx.all_pairs_shortest_path_length(graph, cutoff=5))\n    distance_to_scale = {1: \"scale_12\", 2: \"scale_13\", 3: \"scale_14\", 4: \"scale_15\"}\n\n    exclusions = {}\n\n    for idx_a in distances:\n        for idx_b, distance in distances[idx_a].items():\n            pair = tuple(sorted((idx_a, idx_b)))\n            scale = distance_to_scale.get(distance)\n\n            if scale is None:\n                continue\n\n            assert pair not in exclusions or exclusions[pair] == scale\n            exclusions[pair] = scale\n\n    return exclusions\n
"},{"location":"reference/utils/#smee.utils.ones_like","title":"ones_like","text":"
ones_like(size: _size, other: Tensor) -> Tensor\n

Create a tensor of ones with the same device and type as another tensor.

Source code in smee/utils.py
def ones_like(size: _size, other: torch.Tensor) -> torch.Tensor:\n    \"\"\"Create a tensor of ones with the same device and type as another tensor.\"\"\"\n    return torch.ones(size, dtype=other.dtype, device=other.device)\n
"},{"location":"reference/utils/#smee.utils.zeros_like","title":"zeros_like","text":"
zeros_like(size: _size, other: Tensor) -> Tensor\n

Create a tensor of zeros with the same device and type as another tensor.

Source code in smee/utils.py
def zeros_like(size: _size, other: torch.Tensor) -> torch.Tensor:\n    \"\"\"Create a tensor of zeros with the same device and type as another tensor.\"\"\"\n    return torch.zeros(size, dtype=other.dtype, device=other.device)\n
"},{"location":"reference/utils/#smee.utils.tensor_like","title":"tensor_like","text":"
tensor_like(data: Any, other: Tensor) -> Tensor\n

Create a tensor with the same device and type as another tensor.

Source code in smee/utils.py
def tensor_like(data: typing.Any, other: torch.Tensor) -> torch.Tensor:\n    \"\"\"Create a tensor with the same device and type as another tensor.\"\"\"\n\n    if isinstance(data, torch.Tensor):\n        return data.clone().detach().to(other.device, other.dtype)\n\n    return torch.tensor(data, dtype=other.dtype, device=other.device)\n
"},{"location":"reference/utils/#smee.utils.arange_like","title":"arange_like","text":"
arange_like(end: int, other: Tensor) -> Tensor\n

Arange a tensor with the same device and type as another tensor.

Source code in smee/utils.py
def arange_like(end: int, other: torch.Tensor) -> torch.Tensor:\n    \"\"\"Arange a tensor with the same device and type as another tensor.\"\"\"\n    return torch.arange(end, dtype=other.dtype, device=other.device)\n
"},{"location":"reference/utils/#smee.utils.logsumexp","title":"logsumexp","text":"
logsumexp(\n    a: Tensor,\n    dim: int,\n    keepdim: bool = False,\n    b: Tensor | None = None,\n    return_sign: bool = False,\n) -> Tensor | tuple[Tensor, Tensor]\n

Compute the log of the sum of the exponential of the input elements, optionally with each element multiplied by a scaling factor.

Notes

This should be removed if torch.logsumexp is updated to support scaling factors.

Parameters:

  • a (Tensor) \u2013

    The elements that should be summed over.

  • dim (int) \u2013

    The dimension to sum over.

  • keepdim (bool, default: False ) \u2013

    Whether to keep the summed dimension.

  • b (Tensor | None, default: None ) \u2013

    The scaling factor to multiply each element by.

Returns:

  • Tensor | tuple[Tensor, Tensor] \u2013

    The log of the sum of exponential of the a elements.

Source code in smee/utils.py
def logsumexp(\n    a: torch.Tensor,\n    dim: int,\n    keepdim: bool = False,\n    b: torch.Tensor | None = None,\n    return_sign: bool = False,\n) -> torch.Tensor | tuple[torch.Tensor, torch.Tensor]:\n    \"\"\"Compute the log of the sum of the exponential of the input elements, optionally\n    with each element multiplied by a scaling factor.\n\n    Notes:\n        This should be removed if torch.logsumexp is updated to support scaling factors.\n\n    Args:\n        a: The elements that should be summed over.\n        dim: The dimension to sum over.\n        keepdim: Whether to keep the summed dimension.\n        b: The scaling factor to multiply each element by.\n\n    Returns:\n        The log of the sum of exponential of the a elements.\n    \"\"\"\n    a_type = a.dtype\n\n    if b is None:\n        assert return_sign is False\n        return torch.logsumexp(a, dim, keepdim)\n\n    a = a.double()\n    b = b if b is not None else b.double()\n\n    a, b = torch.broadcast_tensors(a, b)\n\n    if torch.any(b == 0):\n        a[b == 0] = -torch.inf\n\n    a_max = torch.amax(a, dim=dim, keepdim=True)\n\n    if a_max.ndim > 0:\n        a_max[~torch.isfinite(a_max)] = 0\n    elif not torch.isfinite(a_max):\n        a_max = 0\n\n    exp_sum = torch.sum(b * torch.exp(a - a_max), dim=dim, keepdim=keepdim)\n    sign = None\n\n    if return_sign:\n        sign = torch.sign(exp_sum)\n        exp_sum = exp_sum * sign\n\n    ln_exp_sum = torch.log(exp_sum)\n\n    if not keepdim:\n        a_max = torch.squeeze(a_max, dim=dim)\n\n    ln_exp_sum += a_max\n    ln_exp_sum = ln_exp_sum.to(a_type)\n\n    if return_sign:\n        return ln_exp_sum, sign.to(a_type)\n    else:\n        return ln_exp_sum\n
"},{"location":"reference/utils/#smee.utils.to_upper_tri_idx","title":"to_upper_tri_idx","text":"
to_upper_tri_idx(\n    i: Tensor, j: Tensor, n: int, include_diag: bool = False\n) -> Tensor\n

Converts pairs of 2D indices to 1D indices in an upper triangular matrix that excludes the diagonal.

Parameters:

  • i (Tensor) \u2013

    A tensor of the indices along the first axis with shape=(n_pairs,).

  • j (Tensor) \u2013

    A tensor of the indices along the second axis with shape=(n_pairs,).

  • n (int) \u2013

    The size of the matrix.

  • include_diag (bool, default: False ) \u2013

    Whether the diagonal is included in the upper triangular matrix.

Returns:

  • Tensor \u2013

    A tensor of the indices in the upper triangular matrix with shape=(n_pairs * (n_pairs - 1) // 2,).

Source code in smee/utils.py
def to_upper_tri_idx(\n    i: torch.Tensor, j: torch.Tensor, n: int, include_diag: bool = False\n) -> torch.Tensor:\n    \"\"\"Converts pairs of 2D indices to 1D indices in an upper triangular matrix that\n    excludes the diagonal.\n\n    Args:\n        i: A tensor of the indices along the first axis with ``shape=(n_pairs,)``.\n        j: A tensor of the indices along the second axis with ``shape=(n_pairs,)``.\n        n: The size of the matrix.\n        include_diag: Whether the diagonal is included in the upper triangular matrix.\n\n    Returns:\n        A tensor of the indices in the upper triangular matrix with\n        ``shape=(n_pairs * (n_pairs - 1) // 2,)``.\n    \"\"\"\n\n    if not include_diag:\n        assert (i < j).all(), \"i must be less than j\"\n        return (i * (2 * n - i - 1)) // 2 + j - i - 1\n\n    assert (i <= j).all(), \"i must be less than or equal to j\"\n    return (i * (2 * n - i + 1)) // 2 + j - i\n
"},{"location":"reference/utils/#smee.utils.geometric_mean","title":"geometric_mean","text":"
geometric_mean(eps_a: Tensor, eps_b: Tensor) -> Tensor\n

Computes the geometric mean of two values 'safely'.

A small epsilon (smee.utils.EPSILON) is added when computing the gradient in cases where the mean is zero to prevent divide by zero errors.

Parameters:

  • eps_a (Tensor) \u2013

    The first value.

  • eps_b (Tensor) \u2013

    The second value.

Returns:

  • Tensor \u2013

    The geometric mean of the two values.

Source code in smee/utils.py
def geometric_mean(eps_a: torch.Tensor, eps_b: torch.Tensor) -> torch.Tensor:\n    \"\"\"Computes the geometric mean of two values 'safely'.\n\n    A small epsilon (``smee.utils.EPSILON``) is added when computing the gradient in\n    cases where the mean is zero to prevent divide by zero errors.\n\n    Args:\n        eps_a: The first value.\n        eps_b: The second value.\n\n    Returns:\n        The geometric mean of the two values.\n    \"\"\"\n\n    return _SafeGeometricMean.apply(eps_a, eps_b)\n
"},{"location":"reference/converters/","title":"Index","text":""},{"location":"reference/converters/#smee.converters","title":"converters","text":"

Convert to / from smee tensor representations.

Modules:

  • openff \u2013

    Tensor representations of SMIRNOFF force fields.

  • openmm \u2013

    Convert tensor representations into OpenMM systems.

Functions:

  • convert_handlers \u2013

    Convert a set of SMIRNOFF parameter handlers into a set of tensor potentials.

  • convert_interchange \u2013

    Convert a list of interchange objects into tensor potentials.

  • smirnoff_parameter_converter \u2013

    A decorator used to flag a function as being able to convert a parameter handlers

  • convert_to_openmm_force \u2013

    Convert a smee potential to OpenMM forces.

  • convert_to_openmm_system \u2013

    Convert a smee force field and system / topology into an OpenMM system.

  • convert_to_openmm_topology \u2013

    Convert a smee system to an OpenMM topology.

"},{"location":"reference/converters/#smee.converters.convert_handlers","title":"convert_handlers","text":"
convert_handlers(\n    handlers: list[SMIRNOFFCollection],\n    topologies: list[Topology],\n    v_site_maps: list[VSiteMap | None] | None = None,\n    potentials: (\n        list[tuple[TensorPotential, list[ParameterMap]]]\n        | None\n    ) = None,\n) -> list[tuple[TensorPotential, list[ParameterMap]]]\n

Convert a set of SMIRNOFF parameter handlers into a set of tensor potentials.

Parameters:

  • handlers (list[SMIRNOFFCollection]) \u2013

    The SMIRNOFF parameter handler collections for a set of interchange objects to convert.

  • topologies (list[Topology]) \u2013

    The topologies associated with each interchange object.

  • v_site_maps (list[VSiteMap | None] | None, default: None ) \u2013

    The v-site maps associated with each interchange object.

  • potentials (list[tuple[TensorPotential, list[ParameterMap]]] | None, default: None ) \u2013

    Already converted parameter handlers that may be required as dependencies.

Returns:

  • list[tuple[TensorPotential, list[ParameterMap]]] \u2013

    The potential containing the values of the parameters in each handler collection, and a list of maps (one per topology) between molecule elements (e.g. bond indices) and parameter indices.

Examples:

>>> from openff.toolkit import ForceField, Molecule\n>>> from openff.interchange import Interchange\n>>>\n>>> force_field = ForceField(\"openff_unconstrained-2.0.0.offxml\")\n>>> molecules = [Molecule.from_smiles(\"CCO\"), Molecule.from_smiles(\"CC\")]\n>>>\n>>> interchanges = [\n...     Interchange.from_smirnoff(force_field, molecule.to_topology())\n...     for molecule in molecules\n... ]\n>>> vdw_handlers = [\n...     interchange.collections[\"vdW\"] for interchange in interchanges\n... ]\n>>>\n>>> vdw_potential, applied_vdw_parameters = convert_handlers(interchanges)\n
Source code in smee/converters/openff/_openff.py
def convert_handlers(\n    handlers: list[openff.interchange.smirnoff.SMIRNOFFCollection],\n    topologies: list[openff.toolkit.Topology],\n    v_site_maps: list[smee.VSiteMap | None] | None = None,\n    potentials: (\n        list[tuple[smee.TensorPotential, list[smee.ParameterMap]]] | None\n    ) = None,\n) -> list[tuple[smee.TensorPotential, list[smee.ParameterMap]]]:\n    \"\"\"Convert a set of SMIRNOFF parameter handlers into a set of tensor potentials.\n\n    Args:\n        handlers: The SMIRNOFF parameter handler collections for a set of interchange\n            objects to convert.\n        topologies: The topologies associated with each interchange object.\n        v_site_maps: The v-site maps associated with each interchange object.\n        potentials: Already converted parameter handlers that may be required as\n            dependencies.\n\n    Returns:\n        The potential containing the values of the parameters in each handler\n        collection, and a list of maps (one per topology) between molecule elements\n        (e.g. bond indices) and parameter indices.\n\n    Examples:\n\n        >>> from openff.toolkit import ForceField, Molecule\n        >>> from openff.interchange import Interchange\n        >>>\n        >>> force_field = ForceField(\"openff_unconstrained-2.0.0.offxml\")\n        >>> molecules = [Molecule.from_smiles(\"CCO\"), Molecule.from_smiles(\"CC\")]\n        >>>\n        >>> interchanges = [\n        ...     Interchange.from_smirnoff(force_field, molecule.to_topology())\n        ...     for molecule in molecules\n        ... ]\n        >>> vdw_handlers = [\n        ...     interchange.collections[\"vdW\"] for interchange in interchanges\n        ... ]\n        >>>\n        >>> vdw_potential, applied_vdw_parameters = convert_handlers(interchanges)\n    \"\"\"\n    importlib.import_module(\"smee.converters.openff.nonbonded\")\n    importlib.import_module(\"smee.converters.openff.valence\")\n\n    handler_types = {handler.type for handler in handlers}\n    assert len(handler_types) == 1, \"multiple handler types found\"\n    handler_type = next(iter(handler_types))\n\n    assert len(handlers) == len(topologies), \"mismatched number of topologies\"\n\n    if handler_type not in _CONVERTERS:\n        raise NotImplementedError(f\"{handler_type} handlers is not yet supported.\")\n\n    converter = _CONVERTERS[handler_type]\n    converter_spec = inspect.signature(converter.fn)\n    converter_kwargs = {}\n\n    if \"topologies\" in converter_spec.parameters:\n        converter_kwargs[\"topologies\"] = topologies\n    if \"v_site_maps\" in converter_spec.parameters:\n        assert v_site_maps is not None, \"v-site maps must be provided\"\n        converter_kwargs[\"v_site_maps\"] = v_site_maps\n\n    potentials_by_type = (\n        {}\n        if potentials is None\n        else {potential.type: (potential, maps) for potential, maps in potentials}\n    )\n\n    dependencies = {}\n    depends_on = converter.depends_on if converter.depends_on is not None else []\n\n    if len(depends_on) > 0:\n        missing_deps = {dep for dep in depends_on if dep not in potentials_by_type}\n        assert len(missing_deps) == 0, \"missing dependencies\"\n\n        dependencies = {dep: potentials_by_type[dep] for dep in depends_on}\n        assert \"dependencies\" in converter_spec.parameters, \"dependencies not accepted\"\n\n    if \"dependencies\" in converter_spec.parameters:\n        converter_kwargs[\"dependencies\"] = dependencies\n\n    converted = converter.fn(handlers, **converter_kwargs)\n    converted = [converted] if not isinstance(converted, list) else converted\n\n    converted_by_type = {\n        potential.type: (potential, maps) for potential, maps in converted\n    }\n    assert len(converted_by_type) == len(converted), \"duplicate potentials found\"\n\n    potentials_by_type = {\n        **{\n            potential.type: (potential, maps)\n            for potential, maps in potentials_by_type.values()\n            if potential.type not in depends_on\n            and potential.type not in converted_by_type\n        },\n        **converted_by_type,\n    }\n\n    return [*potentials_by_type.values()]\n
"},{"location":"reference/converters/#smee.converters.convert_interchange","title":"convert_interchange","text":"
convert_interchange(\n    interchange: Interchange | list[Interchange],\n) -> tuple[TensorForceField, list[TensorTopology]]\n

Convert a list of interchange objects into tensor potentials.

Parameters:

  • interchange (Interchange | list[Interchange]) \u2013

    The list of (or singile) interchange objects to convert into tensor potentials.

Returns:

  • tuple[TensorForceField, list[TensorTopology]] \u2013

    The tensor force field containing the parameters of each handler, and a list (one per interchange) of objects mapping molecule elements (e.g. bonds, angles) to corresponding handler parameters.

Examples:

>>> from openff.toolkit import ForceField, Molecule\n>>> from openff.interchange import Interchange\n>>>\n>>> force_field = ForceField(\"openff_unconstrained-2.0.0.offxml\")\n>>> molecules = [Molecule.from_smiles(\"CCO\"), Molecule.from_smiles(\"CC\")]\n>>>\n>>> interchanges = [\n...     Interchange.from_smirnoff(force_field, molecule.to_topology())\n...     for molecule in molecules\n... ]\n>>>\n>>> tensor_ff, tensor_topologies = convert_interchange(interchanges)\n
Source code in smee/converters/openff/_openff.py
def convert_interchange(\n    interchange: openff.interchange.Interchange | list[openff.interchange.Interchange],\n) -> tuple[smee.TensorForceField, list[smee.TensorTopology]]:\n    \"\"\"Convert a list of interchange objects into tensor potentials.\n\n    Args:\n        interchange: The list of (or singile) interchange objects to convert into\n            tensor potentials.\n\n    Returns:\n        The tensor force field containing the parameters of each handler, and a list\n        (one per interchange) of objects mapping molecule elements (e.g. bonds, angles)\n        to corresponding handler parameters.\n\n    Examples:\n\n        >>> from openff.toolkit import ForceField, Molecule\n        >>> from openff.interchange import Interchange\n        >>>\n        >>> force_field = ForceField(\"openff_unconstrained-2.0.0.offxml\")\n        >>> molecules = [Molecule.from_smiles(\"CCO\"), Molecule.from_smiles(\"CC\")]\n        >>>\n        >>> interchanges = [\n        ...     Interchange.from_smirnoff(force_field, molecule.to_topology())\n        ...     for molecule in molecules\n        ... ]\n        >>>\n        >>> tensor_ff, tensor_topologies = convert_interchange(interchanges)\n    \"\"\"\n    importlib.import_module(\"smee.converters.openff.nonbonded\")\n    importlib.import_module(\"smee.converters.openff.valence\")\n\n    interchanges = (\n        [interchange]\n        if isinstance(interchange, openff.interchange.Interchange)\n        else interchange\n    )\n    topologies = []\n\n    handler_types = {\n        handler_type\n        for interchange in interchanges\n        for handler_type in interchange.collections\n    }\n    handlers_by_type = {handler_type: [] for handler_type in sorted(handler_types)}\n\n    for interchange in interchanges:\n        for handler_type in handlers_by_type:\n            handler = (\n                None\n                if handler_type not in interchange.collections\n                else interchange.collections[handler_type]\n            )\n            handlers_by_type[handler_type].append(handler)\n\n        topologies.append(interchange.topology)\n\n    v_sites, v_site_maps = None, [None] * len(topologies)\n\n    if \"VirtualSites\" in handlers_by_type:\n        v_sites, v_site_maps = _convert_v_sites(\n            handlers_by_type.pop(\"VirtualSites\"), topologies\n        )\n\n    constraints = [None] * len(topologies)\n\n    if \"Constraints\" in handlers_by_type:\n        constraints = _convert_constraints(handlers_by_type.pop(\"Constraints\"))\n\n    conversion_order = _resolve_conversion_order([*handlers_by_type])\n    converted = []\n\n    for handler_type in conversion_order:\n        handlers = handlers_by_type[handler_type]\n\n        if (\n            sum(len(handler.potentials) for handler in handlers if handler is not None)\n            == 0\n        ):\n            continue\n\n        converted = convert_handlers(handlers, topologies, v_site_maps, converted)\n\n    # handlers may either return multiple potentials, or condense multiple already\n    # converted potentials into a single one (e.g. electrostatics into some polarizable\n    # potential)\n    potentials = []\n    parameter_maps_by_handler = {}\n\n    for potential, parameter_maps in converted:\n        potentials.append(potential)\n        parameter_maps_by_handler[potential.type] = parameter_maps\n\n    tensor_topologies = [\n        _convert_topology(\n            topology,\n            {\n                potential.type: parameter_maps_by_handler[potential.type][i]\n                for potential in potentials\n            },\n            v_site_maps[i],\n            constraints[i],\n        )\n        for i, topology in enumerate(topologies)\n    ]\n\n    tensor_force_field = smee.TensorForceField(potentials, v_sites)\n    return tensor_force_field, tensor_topologies\n
"},{"location":"reference/converters/#smee.converters.smirnoff_parameter_converter","title":"smirnoff_parameter_converter","text":"
smirnoff_parameter_converter(\n    type_: str,\n    default_units: dict[str, Unit],\n    depends_on: list[str] | None = None,\n)\n

A decorator used to flag a function as being able to convert a parameter handlers parameters into tensors.

Parameters:

  • type_ (str) \u2013

    The type of parameter handler that the decorated function can convert.

  • default_units (dict[str, Unit]) \u2013

    The default units of each parameter in the handler.

  • depends_on (list[str] | None, default: None ) \u2013

    The names of other handlers that this handler depends on. When set, the convert function should additionally take in a list of the already converted potentials and return a new list of potentials that should either include or replace the original potentials.

Source code in smee/converters/openff/_openff.py
def smirnoff_parameter_converter(\n    type_: str,\n    default_units: dict[str, openff.units.Unit],\n    depends_on: list[str] | None = None,\n):\n    \"\"\"A decorator used to flag a function as being able to convert a parameter handlers\n    parameters into tensors.\n\n    Args:\n        type_: The type of parameter handler that the decorated function can convert.\n        default_units: The default units of each parameter in the handler.\n        depends_on: The names of other handlers that this handler depends on. When set,\n            the convert function should additionally take in a list of the already\n            converted potentials and return a new list of potentials that should either\n            include or replace the original potentials.\n    \"\"\"\n\n    def parameter_converter_inner(func):\n        if type_ in _CONVERTERS:\n            raise KeyError(f\"A {type_} converter is already registered.\")\n\n        _CONVERTERS[type_] = _Converter(func, default_units, depends_on)\n\n        return func\n\n    return parameter_converter_inner\n
"},{"location":"reference/converters/#smee.converters.convert_to_openmm_force","title":"convert_to_openmm_force","text":"
convert_to_openmm_force(\n    potential: TensorPotential, system: TensorSystem\n) -> list[Force]\n

Convert a smee potential to OpenMM forces.

Some potentials may return multiple forces, e.g. a vdW potential may return one force containing intermolecular interactions and another containing intramolecular interactions.

See Also

potential_converter: for how to define a converter function.

Parameters:

  • potential (TensorPotential) \u2013

    The potential to convert.

  • system (TensorSystem) \u2013

    The system to convert.

Returns:

  • list[Force] \u2013

    The OpenMM force(s).

Source code in smee/converters/openmm/_openmm.py
def convert_to_openmm_force(\n    potential: smee.TensorPotential, system: smee.TensorSystem\n) -> list[openmm.Force]:\n    \"\"\"Convert a ``smee`` potential to OpenMM forces.\n\n    Some potentials may return multiple forces, e.g. a vdW potential may return one\n    force containing intermolecular interactions and another containing intramolecular\n    interactions.\n\n    See Also:\n        potential_converter: for how to define a converter function.\n\n    Args:\n        potential: The potential to convert.\n        system: The system to convert.\n\n    Returns:\n        The OpenMM force(s).\n    \"\"\"\n    # register the built-in converter functions\n    importlib.import_module(\"smee.converters.openmm.nonbonded\")\n    importlib.import_module(\"smee.converters.openmm.valence\")\n\n    potential = potential.to(\"cpu\")\n    system = system.to(\"cpu\")\n\n    if potential.exceptions is not None and potential.type != \"vdW\":\n        raise NotImplementedError(\"exceptions are only supported for vdW potentials\")\n\n    converter_key = (str(potential.type), str(potential.fn))\n\n    if converter_key not in _CONVERTER_FUNCTIONS:\n        raise NotImplementedError(\n            f\"cannot convert type={potential.type} fn={potential.fn} to an OpenMM force\"\n        )\n\n    forces = _CONVERTER_FUNCTIONS[converter_key](potential, system)\n    return forces if isinstance(forces, (list, tuple)) else [forces]\n
"},{"location":"reference/converters/#smee.converters.convert_to_openmm_system","title":"convert_to_openmm_system","text":"
convert_to_openmm_system(\n    force_field: TensorForceField,\n    system: TensorSystem | TensorTopology,\n) -> System\n

Convert a smee force field and system / topology into an OpenMM system.

Parameters:

  • force_field (TensorForceField) \u2013

    The force field parameters.

  • system (TensorSystem | TensorTopology) \u2013

    The system / topology to convert.

Returns:

  • System \u2013

    The OpenMM system.

Source code in smee/converters/openmm/_openmm.py
def convert_to_openmm_system(\n    force_field: smee.TensorForceField,\n    system: smee.TensorSystem | smee.TensorTopology,\n) -> openmm.System:\n    \"\"\"Convert a ``smee`` force field and system / topology into an OpenMM system.\n\n    Args:\n        force_field: The force field parameters.\n        system: The system / topology to convert.\n\n    Returns:\n        The OpenMM system.\n    \"\"\"\n\n    system: smee.TensorSystem = (\n        system\n        if isinstance(system, smee.TensorSystem)\n        else smee.TensorSystem([system], [1], False)\n    )\n\n    force_field = force_field.to(\"cpu\")\n    system = system.to(\"cpu\")\n\n    omm_forces = {\n        potential_type: convert_to_openmm_force(potential, system)\n        for potential_type, potential in force_field.potentials_by_type.items()\n    }\n    omm_system = create_openmm_system(system, force_field.v_sites)\n\n    if (\n        \"Electrostatics\" in omm_forces\n        and \"vdW\" in omm_forces\n        and len(omm_forces[\"vdW\"]) == 1\n        and isinstance(omm_forces[\"vdW\"][0], openmm.NonbondedForce)\n    ):\n        (electrostatic_force,) = omm_forces.pop(\"Electrostatics\")\n        (vdw_force,) = omm_forces.pop(\"vdW\")\n\n        nonbonded_force = _combine_nonbonded(vdw_force, electrostatic_force)\n        omm_system.addForce(nonbonded_force)\n\n    for forces in omm_forces.values():\n        for force in forces:\n            omm_system.addForce(force)\n\n    _apply_constraints(omm_system, system)\n\n    return omm_system\n
"},{"location":"reference/converters/#smee.converters.convert_to_openmm_topology","title":"convert_to_openmm_topology","text":"
convert_to_openmm_topology(\n    system: TensorSystem | TensorTopology,\n) -> Topology\n

Convert a smee system to an OpenMM topology.

Notes

Virtual sites are given the element symbol \"X\" and atomic number 82.

Parameters:

  • system (TensorSystem | TensorTopology) \u2013

    The system to convert.

Returns:

  • Topology \u2013

    The OpenMM topology.

Source code in smee/converters/openmm/_openmm.py
def convert_to_openmm_topology(\n    system: smee.TensorSystem | smee.TensorTopology,\n) -> openmm.app.Topology:\n    \"\"\"Convert a ``smee`` system to an OpenMM topology.\n\n    Notes:\n        Virtual sites are given the element symbol \"X\" and atomic number 82.\n\n    Args:\n        system: The system to convert.\n\n    Returns:\n        The OpenMM topology.\n    \"\"\"\n    system: smee.TensorSystem = (\n        system\n        if isinstance(system, smee.TensorSystem)\n        else smee.TensorSystem([system], [1], False)\n    )\n\n    omm_topology = openmm.app.Topology()\n\n    for topology, n_copies in zip(system.topologies, system.n_copies, strict=True):\n        chain = omm_topology.addChain()\n\n        is_water = topology.n_atoms == 3 and sorted(\n            int(v) for v in topology.atomic_nums\n        ) == [1, 1, 8]\n\n        residue_name = \"WAT\" if is_water else \"UNK\"\n\n        for _ in range(n_copies):\n            residue = omm_topology.addResidue(residue_name, chain)\n            element_counter = collections.defaultdict(int)\n\n            atoms = {}\n\n            for i, atomic_num in enumerate(topology.atomic_nums):\n                element = openmm.app.Element.getByAtomicNumber(int(atomic_num))\n                element_counter[element.symbol] += 1\n\n                name = element.symbol + (\n                    \"\"\n                    if element_counter[element.symbol] == 1 and element.symbol != \"H\"\n                    else f\"{element_counter[element.symbol]}\"\n                )\n                atoms[i] = omm_topology.addAtom(name, element, residue)\n\n            for _ in range(topology.n_v_sites):\n                omm_topology.addAtom(\n                    \"X\", openmm.app.Element.getByAtomicNumber(82), residue\n                )\n\n            for bond_idxs, bond_order in zip(\n                topology.bond_idxs, topology.bond_orders, strict=True\n            ):\n                idx_a, idx_b = int(bond_idxs[0]), int(bond_idxs[1])\n\n                bond_order = int(bond_order)\n                bond_type = {\n                    1: openmm.app.Single,\n                    2: openmm.app.Double,\n                    3: openmm.app.Triple,\n                }[bond_order]\n\n                omm_topology.addBond(atoms[idx_a], atoms[idx_b], bond_type, bond_order)\n\n    return omm_topology\n
"},{"location":"reference/converters/openff/","title":"Index","text":""},{"location":"reference/converters/openff/#smee.converters.openff","title":"openff","text":"

Tensor representations of SMIRNOFF force fields.

Modules:

  • nonbonded \u2013

    Convert SMIRNOFF non-bonded parameters into tensors.

  • valence \u2013

    Convert SMIRNOFF valence parameters into tensors.

Functions:

  • convert_handlers \u2013

    Convert a set of SMIRNOFF parameter handlers into a set of tensor potentials.

  • convert_interchange \u2013

    Convert a list of interchange objects into tensor potentials.

  • smirnoff_parameter_converter \u2013

    A decorator used to flag a function as being able to convert a parameter handlers

"},{"location":"reference/converters/openff/#smee.converters.openff.convert_handlers","title":"convert_handlers","text":"
convert_handlers(\n    handlers: list[SMIRNOFFCollection],\n    topologies: list[Topology],\n    v_site_maps: list[VSiteMap | None] | None = None,\n    potentials: (\n        list[tuple[TensorPotential, list[ParameterMap]]]\n        | None\n    ) = None,\n) -> list[tuple[TensorPotential, list[ParameterMap]]]\n

Convert a set of SMIRNOFF parameter handlers into a set of tensor potentials.

Parameters:

  • handlers (list[SMIRNOFFCollection]) \u2013

    The SMIRNOFF parameter handler collections for a set of interchange objects to convert.

  • topologies (list[Topology]) \u2013

    The topologies associated with each interchange object.

  • v_site_maps (list[VSiteMap | None] | None, default: None ) \u2013

    The v-site maps associated with each interchange object.

  • potentials (list[tuple[TensorPotential, list[ParameterMap]]] | None, default: None ) \u2013

    Already converted parameter handlers that may be required as dependencies.

Returns:

  • list[tuple[TensorPotential, list[ParameterMap]]] \u2013

    The potential containing the values of the parameters in each handler collection, and a list of maps (one per topology) between molecule elements (e.g. bond indices) and parameter indices.

Examples:

>>> from openff.toolkit import ForceField, Molecule\n>>> from openff.interchange import Interchange\n>>>\n>>> force_field = ForceField(\"openff_unconstrained-2.0.0.offxml\")\n>>> molecules = [Molecule.from_smiles(\"CCO\"), Molecule.from_smiles(\"CC\")]\n>>>\n>>> interchanges = [\n...     Interchange.from_smirnoff(force_field, molecule.to_topology())\n...     for molecule in molecules\n... ]\n>>> vdw_handlers = [\n...     interchange.collections[\"vdW\"] for interchange in interchanges\n... ]\n>>>\n>>> vdw_potential, applied_vdw_parameters = convert_handlers(interchanges)\n
Source code in smee/converters/openff/_openff.py
def convert_handlers(\n    handlers: list[openff.interchange.smirnoff.SMIRNOFFCollection],\n    topologies: list[openff.toolkit.Topology],\n    v_site_maps: list[smee.VSiteMap | None] | None = None,\n    potentials: (\n        list[tuple[smee.TensorPotential, list[smee.ParameterMap]]] | None\n    ) = None,\n) -> list[tuple[smee.TensorPotential, list[smee.ParameterMap]]]:\n    \"\"\"Convert a set of SMIRNOFF parameter handlers into a set of tensor potentials.\n\n    Args:\n        handlers: The SMIRNOFF parameter handler collections for a set of interchange\n            objects to convert.\n        topologies: The topologies associated with each interchange object.\n        v_site_maps: The v-site maps associated with each interchange object.\n        potentials: Already converted parameter handlers that may be required as\n            dependencies.\n\n    Returns:\n        The potential containing the values of the parameters in each handler\n        collection, and a list of maps (one per topology) between molecule elements\n        (e.g. bond indices) and parameter indices.\n\n    Examples:\n\n        >>> from openff.toolkit import ForceField, Molecule\n        >>> from openff.interchange import Interchange\n        >>>\n        >>> force_field = ForceField(\"openff_unconstrained-2.0.0.offxml\")\n        >>> molecules = [Molecule.from_smiles(\"CCO\"), Molecule.from_smiles(\"CC\")]\n        >>>\n        >>> interchanges = [\n        ...     Interchange.from_smirnoff(force_field, molecule.to_topology())\n        ...     for molecule in molecules\n        ... ]\n        >>> vdw_handlers = [\n        ...     interchange.collections[\"vdW\"] for interchange in interchanges\n        ... ]\n        >>>\n        >>> vdw_potential, applied_vdw_parameters = convert_handlers(interchanges)\n    \"\"\"\n    importlib.import_module(\"smee.converters.openff.nonbonded\")\n    importlib.import_module(\"smee.converters.openff.valence\")\n\n    handler_types = {handler.type for handler in handlers}\n    assert len(handler_types) == 1, \"multiple handler types found\"\n    handler_type = next(iter(handler_types))\n\n    assert len(handlers) == len(topologies), \"mismatched number of topologies\"\n\n    if handler_type not in _CONVERTERS:\n        raise NotImplementedError(f\"{handler_type} handlers is not yet supported.\")\n\n    converter = _CONVERTERS[handler_type]\n    converter_spec = inspect.signature(converter.fn)\n    converter_kwargs = {}\n\n    if \"topologies\" in converter_spec.parameters:\n        converter_kwargs[\"topologies\"] = topologies\n    if \"v_site_maps\" in converter_spec.parameters:\n        assert v_site_maps is not None, \"v-site maps must be provided\"\n        converter_kwargs[\"v_site_maps\"] = v_site_maps\n\n    potentials_by_type = (\n        {}\n        if potentials is None\n        else {potential.type: (potential, maps) for potential, maps in potentials}\n    )\n\n    dependencies = {}\n    depends_on = converter.depends_on if converter.depends_on is not None else []\n\n    if len(depends_on) > 0:\n        missing_deps = {dep for dep in depends_on if dep not in potentials_by_type}\n        assert len(missing_deps) == 0, \"missing dependencies\"\n\n        dependencies = {dep: potentials_by_type[dep] for dep in depends_on}\n        assert \"dependencies\" in converter_spec.parameters, \"dependencies not accepted\"\n\n    if \"dependencies\" in converter_spec.parameters:\n        converter_kwargs[\"dependencies\"] = dependencies\n\n    converted = converter.fn(handlers, **converter_kwargs)\n    converted = [converted] if not isinstance(converted, list) else converted\n\n    converted_by_type = {\n        potential.type: (potential, maps) for potential, maps in converted\n    }\n    assert len(converted_by_type) == len(converted), \"duplicate potentials found\"\n\n    potentials_by_type = {\n        **{\n            potential.type: (potential, maps)\n            for potential, maps in potentials_by_type.values()\n            if potential.type not in depends_on\n            and potential.type not in converted_by_type\n        },\n        **converted_by_type,\n    }\n\n    return [*potentials_by_type.values()]\n
"},{"location":"reference/converters/openff/#smee.converters.openff.convert_interchange","title":"convert_interchange","text":"
convert_interchange(\n    interchange: Interchange | list[Interchange],\n) -> tuple[TensorForceField, list[TensorTopology]]\n

Convert a list of interchange objects into tensor potentials.

Parameters:

  • interchange (Interchange | list[Interchange]) \u2013

    The list of (or singile) interchange objects to convert into tensor potentials.

Returns:

  • tuple[TensorForceField, list[TensorTopology]] \u2013

    The tensor force field containing the parameters of each handler, and a list (one per interchange) of objects mapping molecule elements (e.g. bonds, angles) to corresponding handler parameters.

Examples:

>>> from openff.toolkit import ForceField, Molecule\n>>> from openff.interchange import Interchange\n>>>\n>>> force_field = ForceField(\"openff_unconstrained-2.0.0.offxml\")\n>>> molecules = [Molecule.from_smiles(\"CCO\"), Molecule.from_smiles(\"CC\")]\n>>>\n>>> interchanges = [\n...     Interchange.from_smirnoff(force_field, molecule.to_topology())\n...     for molecule in molecules\n... ]\n>>>\n>>> tensor_ff, tensor_topologies = convert_interchange(interchanges)\n
Source code in smee/converters/openff/_openff.py
def convert_interchange(\n    interchange: openff.interchange.Interchange | list[openff.interchange.Interchange],\n) -> tuple[smee.TensorForceField, list[smee.TensorTopology]]:\n    \"\"\"Convert a list of interchange objects into tensor potentials.\n\n    Args:\n        interchange: The list of (or singile) interchange objects to convert into\n            tensor potentials.\n\n    Returns:\n        The tensor force field containing the parameters of each handler, and a list\n        (one per interchange) of objects mapping molecule elements (e.g. bonds, angles)\n        to corresponding handler parameters.\n\n    Examples:\n\n        >>> from openff.toolkit import ForceField, Molecule\n        >>> from openff.interchange import Interchange\n        >>>\n        >>> force_field = ForceField(\"openff_unconstrained-2.0.0.offxml\")\n        >>> molecules = [Molecule.from_smiles(\"CCO\"), Molecule.from_smiles(\"CC\")]\n        >>>\n        >>> interchanges = [\n        ...     Interchange.from_smirnoff(force_field, molecule.to_topology())\n        ...     for molecule in molecules\n        ... ]\n        >>>\n        >>> tensor_ff, tensor_topologies = convert_interchange(interchanges)\n    \"\"\"\n    importlib.import_module(\"smee.converters.openff.nonbonded\")\n    importlib.import_module(\"smee.converters.openff.valence\")\n\n    interchanges = (\n        [interchange]\n        if isinstance(interchange, openff.interchange.Interchange)\n        else interchange\n    )\n    topologies = []\n\n    handler_types = {\n        handler_type\n        for interchange in interchanges\n        for handler_type in interchange.collections\n    }\n    handlers_by_type = {handler_type: [] for handler_type in sorted(handler_types)}\n\n    for interchange in interchanges:\n        for handler_type in handlers_by_type:\n            handler = (\n                None\n                if handler_type not in interchange.collections\n                else interchange.collections[handler_type]\n            )\n            handlers_by_type[handler_type].append(handler)\n\n        topologies.append(interchange.topology)\n\n    v_sites, v_site_maps = None, [None] * len(topologies)\n\n    if \"VirtualSites\" in handlers_by_type:\n        v_sites, v_site_maps = _convert_v_sites(\n            handlers_by_type.pop(\"VirtualSites\"), topologies\n        )\n\n    constraints = [None] * len(topologies)\n\n    if \"Constraints\" in handlers_by_type:\n        constraints = _convert_constraints(handlers_by_type.pop(\"Constraints\"))\n\n    conversion_order = _resolve_conversion_order([*handlers_by_type])\n    converted = []\n\n    for handler_type in conversion_order:\n        handlers = handlers_by_type[handler_type]\n\n        if (\n            sum(len(handler.potentials) for handler in handlers if handler is not None)\n            == 0\n        ):\n            continue\n\n        converted = convert_handlers(handlers, topologies, v_site_maps, converted)\n\n    # handlers may either return multiple potentials, or condense multiple already\n    # converted potentials into a single one (e.g. electrostatics into some polarizable\n    # potential)\n    potentials = []\n    parameter_maps_by_handler = {}\n\n    for potential, parameter_maps in converted:\n        potentials.append(potential)\n        parameter_maps_by_handler[potential.type] = parameter_maps\n\n    tensor_topologies = [\n        _convert_topology(\n            topology,\n            {\n                potential.type: parameter_maps_by_handler[potential.type][i]\n                for potential in potentials\n            },\n            v_site_maps[i],\n            constraints[i],\n        )\n        for i, topology in enumerate(topologies)\n    ]\n\n    tensor_force_field = smee.TensorForceField(potentials, v_sites)\n    return tensor_force_field, tensor_topologies\n
"},{"location":"reference/converters/openff/#smee.converters.openff.smirnoff_parameter_converter","title":"smirnoff_parameter_converter","text":"
smirnoff_parameter_converter(\n    type_: str,\n    default_units: dict[str, Unit],\n    depends_on: list[str] | None = None,\n)\n

A decorator used to flag a function as being able to convert a parameter handlers parameters into tensors.

Parameters:

  • type_ (str) \u2013

    The type of parameter handler that the decorated function can convert.

  • default_units (dict[str, Unit]) \u2013

    The default units of each parameter in the handler.

  • depends_on (list[str] | None, default: None ) \u2013

    The names of other handlers that this handler depends on. When set, the convert function should additionally take in a list of the already converted potentials and return a new list of potentials that should either include or replace the original potentials.

Source code in smee/converters/openff/_openff.py
def smirnoff_parameter_converter(\n    type_: str,\n    default_units: dict[str, openff.units.Unit],\n    depends_on: list[str] | None = None,\n):\n    \"\"\"A decorator used to flag a function as being able to convert a parameter handlers\n    parameters into tensors.\n\n    Args:\n        type_: The type of parameter handler that the decorated function can convert.\n        default_units: The default units of each parameter in the handler.\n        depends_on: The names of other handlers that this handler depends on. When set,\n            the convert function should additionally take in a list of the already\n            converted potentials and return a new list of potentials that should either\n            include or replace the original potentials.\n    \"\"\"\n\n    def parameter_converter_inner(func):\n        if type_ in _CONVERTERS:\n            raise KeyError(f\"A {type_} converter is already registered.\")\n\n        _CONVERTERS[type_] = _Converter(func, default_units, depends_on)\n\n        return func\n\n    return parameter_converter_inner\n
"},{"location":"reference/converters/openff/nonbonded/","title":" nonbonded","text":""},{"location":"reference/converters/openff/nonbonded/#smee.converters.openff.nonbonded","title":"nonbonded","text":"

Convert SMIRNOFF non-bonded parameters into tensors.

Functions:

  • convert_nonbonded_handlers \u2013

    Convert a list of SMIRNOFF non-bonded handlers into a tensor potential and

"},{"location":"reference/converters/openff/nonbonded/#smee.converters.openff.nonbonded.convert_nonbonded_handlers","title":"convert_nonbonded_handlers","text":"
convert_nonbonded_handlers(\n    handlers: list[SMIRNOFFCollection],\n    handler_type: str,\n    topologies: list[Topology],\n    v_site_maps: list[VSiteMap | None],\n    parameter_cols: tuple[str, ...],\n    attribute_cols: tuple[str, ...] | None = None,\n    has_exclusions: bool = True,\n) -> tuple[TensorPotential, list[NonbondedParameterMap]]\n

Convert a list of SMIRNOFF non-bonded handlers into a tensor potential and associated parameter maps.

Notes

This function assumes that all parameters come from the same force field

Parameters:

  • handlers (list[SMIRNOFFCollection]) \u2013

    The list of SMIRNOFF non-bonded handlers to convert.

  • handler_type (str) \u2013

    The type of non-bonded handler being converted.

  • topologies (list[Topology]) \u2013

    The topologies associated with each handler.

  • v_site_maps (list[VSiteMap | None]) \u2013

    The virtual site maps associated with each handler.

  • parameter_cols (tuple[str, ...]) \u2013

    The ordering of the parameter array columns.

  • attribute_cols (tuple[str, ...] | None, default: None ) \u2013

    The handler attributes to include in the potential in addition to the intra-molecular scaling factors.

  • has_exclusions (bool, default: True ) \u2013

    Whether the handlers are excepted to define exclusions.

Returns:

  • tuple[TensorPotential, list[NonbondedParameterMap]] \u2013

    The potential containing tensors of the parameter values, and a list of parameter maps which map the parameters to the interactions they apply to.

Source code in smee/converters/openff/nonbonded.py
def convert_nonbonded_handlers(\n    handlers: list[openff.interchange.smirnoff.SMIRNOFFCollection],\n    handler_type: str,\n    topologies: list[openff.toolkit.Topology],\n    v_site_maps: list[smee.VSiteMap | None],\n    parameter_cols: tuple[str, ...],\n    attribute_cols: tuple[str, ...] | None = None,\n    has_exclusions: bool = True,\n) -> tuple[smee.TensorPotential, list[smee.NonbondedParameterMap]]:\n    \"\"\"Convert a list of SMIRNOFF non-bonded handlers into a tensor potential and\n    associated parameter maps.\n\n    Notes:\n        This function assumes that all parameters come from the same force field\n\n    Args:\n        handlers: The list of SMIRNOFF non-bonded handlers to convert.\n        handler_type: The type of non-bonded handler being converted.\n        topologies: The topologies associated with each handler.\n        v_site_maps: The virtual site maps associated with each handler.\n        parameter_cols: The ordering of the parameter array columns.\n        attribute_cols: The handler attributes to include in the potential *in addition*\n            to the intra-molecular scaling factors.\n        has_exclusions: Whether the handlers are excepted to define exclusions.\n\n    Returns:\n        The potential containing tensors of the parameter values, and a list of\n        parameter maps which map the parameters to the interactions they apply to.\n    \"\"\"\n    attribute_cols = attribute_cols if attribute_cols is not None else []\n\n    assert len(topologies) == len(handlers), \"topologies and handlers must match\"\n    assert len(v_site_maps) == len(handlers), \"v-site maps and handlers must match\"\n\n    if has_exclusions:\n        attribute_cols = (\n            \"scale_12\",\n            \"scale_13\",\n            \"scale_14\",\n            \"scale_15\",\n            *attribute_cols,\n        )\n\n    potential = smee.converters.openff._openff._handlers_to_potential(\n        handlers,\n        handler_type,\n        parameter_cols,\n        attribute_cols,\n    )\n\n    parameter_key_to_idx = {\n        parameter_key: i for i, parameter_key in enumerate(potential.parameter_keys)\n    }\n    attribute_to_idx = {column: i for i, column in enumerate(potential.attribute_cols)}\n\n    parameter_maps = []\n\n    for handler, topology, v_site_map in zip(\n        handlers, topologies, v_site_maps, strict=True\n    ):\n        assignment_map = collections.defaultdict(lambda: collections.defaultdict(float))\n\n        n_particles = topology.n_atoms + (\n            0 if v_site_map is None else len(v_site_map.keys)\n        )\n\n        for topology_key, parameter_key in handler.key_map.items():\n            if isinstance(topology_key, openff.interchange.models.VirtualSiteKey):\n                continue\n\n            atom_idx = topology_key.atom_indices[0]\n            assignment_map[atom_idx][parameter_key_to_idx[parameter_key]] += 1.0\n\n        for topology_key, parameter_key in handler.key_map.items():\n            if not isinstance(topology_key, openff.interchange.models.VirtualSiteKey):\n                continue\n\n            v_site_idx = v_site_map.key_to_idx[topology_key]\n\n            if parameter_key.associated_handler != \"Electrostatics\":\n                assignment_map[v_site_idx][parameter_key_to_idx[parameter_key]] += 1.0\n            else:\n                for i, atom_idx in enumerate(topology_key.orientation_atom_indices):\n                    mult_key = copy.deepcopy(parameter_key)\n                    mult_key.mult = i\n\n                    assignment_map[atom_idx][parameter_key_to_idx[mult_key]] += 1.0\n                    assignment_map[v_site_idx][parameter_key_to_idx[mult_key]] += -1.0\n\n        assignment_matrix = torch.zeros(\n            (n_particles, len(potential.parameters)), dtype=torch.float64\n        )\n\n        for particle_idx in assignment_map:\n            for parameter_idx, count in assignment_map[particle_idx].items():\n                assignment_matrix[particle_idx, parameter_idx] = count\n\n        if has_exclusions:\n            exclusion_to_scale = smee.utils.find_exclusions(topology, v_site_map)\n            exclusions = torch.tensor([*exclusion_to_scale])\n            exclusion_scale_idxs = torch.tensor(\n                [[attribute_to_idx[scale]] for scale in exclusion_to_scale.values()],\n                dtype=torch.int64,\n            )\n        else:\n            exclusions = torch.zeros((0, 2), dtype=torch.int64)\n            exclusion_scale_idxs = torch.zeros((0, 1), dtype=torch.int64)\n\n        parameter_map = smee.NonbondedParameterMap(\n            assignment_matrix=assignment_matrix.to_sparse(),\n            exclusions=exclusions,\n            exclusion_scale_idxs=exclusion_scale_idxs,\n        )\n        parameter_maps.append(parameter_map)\n\n    return potential, parameter_maps\n
"},{"location":"reference/converters/openff/valence/","title":" valence","text":""},{"location":"reference/converters/openff/valence/#smee.converters.openff.valence","title":"valence","text":"

Convert SMIRNOFF valence parameters into tensors.

Functions:

  • convert_valence_handlers \u2013

    Convert a list of SMIRNOFF valence handlers into a tensor potential and

"},{"location":"reference/converters/openff/valence/#smee.converters.openff.valence.convert_valence_handlers","title":"convert_valence_handlers","text":"
convert_valence_handlers(\n    handlers: list[SMIRNOFFCollection],\n    handler_type: str,\n    parameter_cols: tuple[str, ...],\n) -> tuple[TensorPotential, list[ValenceParameterMap]]\n

Convert a list of SMIRNOFF valence handlers into a tensor potential and associated parameter maps.

Notes

This function assumes that all parameters come from the same force field

Parameters:

  • handlers (list[SMIRNOFFCollection]) \u2013

    The list of SMIRNOFF valence handlers to convert.

  • handler_type (str) \u2013

    The type of valence handler being converted.

  • parameter_cols (tuple[str, ...]) \u2013

    The ordering of the parameter array columns.

Returns:

  • tuple[TensorPotential, list[ValenceParameterMap]] \u2013

    The potential containing tensors of the parameter values, and a list of parameter maps which map the parameters to the interactions they apply to.

Source code in smee/converters/openff/valence.py
def convert_valence_handlers(\n    handlers: list[openff.interchange.smirnoff.SMIRNOFFCollection],\n    handler_type: str,\n    parameter_cols: tuple[str, ...],\n) -> tuple[smee.TensorPotential, list[smee.ValenceParameterMap]]:\n    \"\"\"Convert a list of SMIRNOFF valence handlers into a tensor potential and\n    associated parameter maps.\n\n    Notes:\n        This function assumes that all parameters come from the same force field\n\n    Args:\n        handlers: The list of SMIRNOFF valence handlers to convert.\n        handler_type: The type of valence handler being converted.\n        parameter_cols: The ordering of the parameter array columns.\n\n    Returns:\n        The potential containing tensors of the parameter values, and a list of\n        parameter maps which map the parameters to the interactions they apply to.\n    \"\"\"\n    potential = smee.converters.openff._openff._handlers_to_potential(\n        handlers, handler_type, parameter_cols, None\n    )\n\n    parameter_key_to_idx = {\n        parameter_key: i for i, parameter_key in enumerate(potential.parameter_keys)\n    }\n    parameter_maps = []\n\n    for handler in handlers:\n        particle_idxs = [topology_key.atom_indices for topology_key in handler.key_map]\n\n        assignment_matrix = torch.zeros(\n            (len(particle_idxs), len(potential.parameters)), dtype=torch.float64\n        )\n\n        for i, parameter_key in enumerate(handler.key_map.values()):\n            assignment_matrix[i, parameter_key_to_idx[parameter_key]] += 1.0\n\n        parameter_map = smee.ValenceParameterMap(\n            torch.tensor(particle_idxs), assignment_matrix.to_sparse()\n        )\n        parameter_maps.append(parameter_map)\n\n    return potential, parameter_maps\n
"},{"location":"reference/converters/openmm/","title":"Index","text":""},{"location":"reference/converters/openmm/#smee.converters.openmm","title":"openmm","text":"

Convert tensor representations into OpenMM systems.

Modules:

  • nonbonded \u2013

    Convert non-bonded potentials to OpenMM forces.

  • valence \u2013

    Convert valence potentials to OpenMM forces.

Functions:

  • convert_to_openmm_force \u2013

    Convert a smee potential to OpenMM forces.

  • convert_to_openmm_system \u2013

    Convert a smee force field and system / topology into an OpenMM system.

  • convert_to_openmm_topology \u2013

    Convert a smee system to an OpenMM topology.

  • create_openmm_system \u2013

    Create an empty OpenMM system from a smee system.

  • potential_converter \u2013

    A decorator used to flag a function as being able to convert a tensor potential

"},{"location":"reference/converters/openmm/#smee.converters.openmm.convert_to_openmm_force","title":"convert_to_openmm_force","text":"
convert_to_openmm_force(\n    potential: TensorPotential, system: TensorSystem\n) -> list[Force]\n

Convert a smee potential to OpenMM forces.

Some potentials may return multiple forces, e.g. a vdW potential may return one force containing intermolecular interactions and another containing intramolecular interactions.

See Also

potential_converter: for how to define a converter function.

Parameters:

  • potential (TensorPotential) \u2013

    The potential to convert.

  • system (TensorSystem) \u2013

    The system to convert.

Returns:

  • list[Force] \u2013

    The OpenMM force(s).

Source code in smee/converters/openmm/_openmm.py
def convert_to_openmm_force(\n    potential: smee.TensorPotential, system: smee.TensorSystem\n) -> list[openmm.Force]:\n    \"\"\"Convert a ``smee`` potential to OpenMM forces.\n\n    Some potentials may return multiple forces, e.g. a vdW potential may return one\n    force containing intermolecular interactions and another containing intramolecular\n    interactions.\n\n    See Also:\n        potential_converter: for how to define a converter function.\n\n    Args:\n        potential: The potential to convert.\n        system: The system to convert.\n\n    Returns:\n        The OpenMM force(s).\n    \"\"\"\n    # register the built-in converter functions\n    importlib.import_module(\"smee.converters.openmm.nonbonded\")\n    importlib.import_module(\"smee.converters.openmm.valence\")\n\n    potential = potential.to(\"cpu\")\n    system = system.to(\"cpu\")\n\n    if potential.exceptions is not None and potential.type != \"vdW\":\n        raise NotImplementedError(\"exceptions are only supported for vdW potentials\")\n\n    converter_key = (str(potential.type), str(potential.fn))\n\n    if converter_key not in _CONVERTER_FUNCTIONS:\n        raise NotImplementedError(\n            f\"cannot convert type={potential.type} fn={potential.fn} to an OpenMM force\"\n        )\n\n    forces = _CONVERTER_FUNCTIONS[converter_key](potential, system)\n    return forces if isinstance(forces, (list, tuple)) else [forces]\n
"},{"location":"reference/converters/openmm/#smee.converters.openmm.convert_to_openmm_system","title":"convert_to_openmm_system","text":"
convert_to_openmm_system(\n    force_field: TensorForceField,\n    system: TensorSystem | TensorTopology,\n) -> System\n

Convert a smee force field and system / topology into an OpenMM system.

Parameters:

  • force_field (TensorForceField) \u2013

    The force field parameters.

  • system (TensorSystem | TensorTopology) \u2013

    The system / topology to convert.

Returns:

  • System \u2013

    The OpenMM system.

Source code in smee/converters/openmm/_openmm.py
def convert_to_openmm_system(\n    force_field: smee.TensorForceField,\n    system: smee.TensorSystem | smee.TensorTopology,\n) -> openmm.System:\n    \"\"\"Convert a ``smee`` force field and system / topology into an OpenMM system.\n\n    Args:\n        force_field: The force field parameters.\n        system: The system / topology to convert.\n\n    Returns:\n        The OpenMM system.\n    \"\"\"\n\n    system: smee.TensorSystem = (\n        system\n        if isinstance(system, smee.TensorSystem)\n        else smee.TensorSystem([system], [1], False)\n    )\n\n    force_field = force_field.to(\"cpu\")\n    system = system.to(\"cpu\")\n\n    omm_forces = {\n        potential_type: convert_to_openmm_force(potential, system)\n        for potential_type, potential in force_field.potentials_by_type.items()\n    }\n    omm_system = create_openmm_system(system, force_field.v_sites)\n\n    if (\n        \"Electrostatics\" in omm_forces\n        and \"vdW\" in omm_forces\n        and len(omm_forces[\"vdW\"]) == 1\n        and isinstance(omm_forces[\"vdW\"][0], openmm.NonbondedForce)\n    ):\n        (electrostatic_force,) = omm_forces.pop(\"Electrostatics\")\n        (vdw_force,) = omm_forces.pop(\"vdW\")\n\n        nonbonded_force = _combine_nonbonded(vdw_force, electrostatic_force)\n        omm_system.addForce(nonbonded_force)\n\n    for forces in omm_forces.values():\n        for force in forces:\n            omm_system.addForce(force)\n\n    _apply_constraints(omm_system, system)\n\n    return omm_system\n
"},{"location":"reference/converters/openmm/#smee.converters.openmm.convert_to_openmm_topology","title":"convert_to_openmm_topology","text":"
convert_to_openmm_topology(\n    system: TensorSystem | TensorTopology,\n) -> Topology\n

Convert a smee system to an OpenMM topology.

Notes

Virtual sites are given the element symbol \"X\" and atomic number 82.

Parameters:

  • system (TensorSystem | TensorTopology) \u2013

    The system to convert.

Returns:

  • Topology \u2013

    The OpenMM topology.

Source code in smee/converters/openmm/_openmm.py
def convert_to_openmm_topology(\n    system: smee.TensorSystem | smee.TensorTopology,\n) -> openmm.app.Topology:\n    \"\"\"Convert a ``smee`` system to an OpenMM topology.\n\n    Notes:\n        Virtual sites are given the element symbol \"X\" and atomic number 82.\n\n    Args:\n        system: The system to convert.\n\n    Returns:\n        The OpenMM topology.\n    \"\"\"\n    system: smee.TensorSystem = (\n        system\n        if isinstance(system, smee.TensorSystem)\n        else smee.TensorSystem([system], [1], False)\n    )\n\n    omm_topology = openmm.app.Topology()\n\n    for topology, n_copies in zip(system.topologies, system.n_copies, strict=True):\n        chain = omm_topology.addChain()\n\n        is_water = topology.n_atoms == 3 and sorted(\n            int(v) for v in topology.atomic_nums\n        ) == [1, 1, 8]\n\n        residue_name = \"WAT\" if is_water else \"UNK\"\n\n        for _ in range(n_copies):\n            residue = omm_topology.addResidue(residue_name, chain)\n            element_counter = collections.defaultdict(int)\n\n            atoms = {}\n\n            for i, atomic_num in enumerate(topology.atomic_nums):\n                element = openmm.app.Element.getByAtomicNumber(int(atomic_num))\n                element_counter[element.symbol] += 1\n\n                name = element.symbol + (\n                    \"\"\n                    if element_counter[element.symbol] == 1 and element.symbol != \"H\"\n                    else f\"{element_counter[element.symbol]}\"\n                )\n                atoms[i] = omm_topology.addAtom(name, element, residue)\n\n            for _ in range(topology.n_v_sites):\n                omm_topology.addAtom(\n                    \"X\", openmm.app.Element.getByAtomicNumber(82), residue\n                )\n\n            for bond_idxs, bond_order in zip(\n                topology.bond_idxs, topology.bond_orders, strict=True\n            ):\n                idx_a, idx_b = int(bond_idxs[0]), int(bond_idxs[1])\n\n                bond_order = int(bond_order)\n                bond_type = {\n                    1: openmm.app.Single,\n                    2: openmm.app.Double,\n                    3: openmm.app.Triple,\n                }[bond_order]\n\n                omm_topology.addBond(atoms[idx_a], atoms[idx_b], bond_type, bond_order)\n\n    return omm_topology\n
"},{"location":"reference/converters/openmm/#smee.converters.openmm.create_openmm_system","title":"create_openmm_system","text":"
create_openmm_system(\n    system: TensorSystem, v_sites: TensorVSites | None\n) -> System\n

Create an empty OpenMM system from a smee system.

Source code in smee/converters/openmm/_openmm.py
def create_openmm_system(\n    system: smee.TensorSystem, v_sites: smee.TensorVSites | None\n) -> openmm.System:\n    \"\"\"Create an empty OpenMM system from a ``smee`` system.\"\"\"\n    v_sites = None if v_sites is None else v_sites.to(\"cpu\")\n    system = system.to(\"cpu\")\n\n    omm_system = openmm.System()\n\n    for topology, n_copies in zip(system.topologies, system.n_copies, strict=True):\n        for _ in range(n_copies):\n            start_idx = omm_system.getNumParticles()\n\n            for atomic_num in topology.atomic_nums:\n                mass = openmm.app.Element.getByAtomicNumber(int(atomic_num)).mass\n                omm_system.addParticle(mass)\n\n            if topology.v_sites is None:\n                continue\n\n            for _ in range(topology.n_v_sites):\n                omm_system.addParticle(0.0)\n\n            for key, parameter_idx in zip(\n                topology.v_sites.keys, topology.v_sites.parameter_idxs, strict=True\n            ):\n                system_idx = start_idx + topology.v_sites.key_to_idx[key]\n                assert system_idx >= start_idx\n\n                parent_idxs = [i + start_idx for i in key.orientation_atom_indices]\n\n                local_frame_coords = smee.geometry.polar_to_cartesian_coords(\n                    v_sites.parameters[[parameter_idx], :]\n                )\n                origin, x_dir, y_dir = v_sites.weights[parameter_idx]\n\n                v_site = openmm.LocalCoordinatesSite(\n                    parent_idxs,\n                    origin.numpy(),\n                    x_dir.numpy(),\n                    y_dir.numpy(),\n                    local_frame_coords.numpy().flatten() * _ANGSTROM_TO_NM,\n                )\n\n                omm_system.setVirtualSite(system_idx, v_site)\n\n    return omm_system\n
"},{"location":"reference/converters/openmm/#smee.converters.openmm.potential_converter","title":"potential_converter","text":"
potential_converter(\n    handler_type: str, energy_expression: str\n)\n

A decorator used to flag a function as being able to convert a tensor potential of a given type and energy function to an OpenMM force.

Source code in smee/converters/openmm/_openmm.py
def potential_converter(handler_type: str, energy_expression: str):\n    \"\"\"A decorator used to flag a function as being able to convert a tensor potential\n    of a given type and energy function to an OpenMM force.\n    \"\"\"\n\n    def _openmm_converter_inner(func):\n        if (handler_type, energy_expression) in _CONVERTER_FUNCTIONS:\n            raise KeyError(\n                f\"An OpenMM converter function is already defined for \"\n                f\"handler={handler_type} fn={energy_expression}.\"\n            )\n\n        _CONVERTER_FUNCTIONS[(str(handler_type), str(energy_expression))] = func\n        return func\n\n    return _openmm_converter_inner\n
"},{"location":"reference/converters/openmm/nonbonded/","title":" nonbonded","text":""},{"location":"reference/converters/openmm/nonbonded/#smee.converters.openmm.nonbonded","title":"nonbonded","text":"

Convert non-bonded potentials to OpenMM forces.

Functions:

  • convert_custom_vdw_potential \u2013

    Converts an arbitrary vdW potential to OpenMM forces.

  • convert_lj_potential \u2013

    Convert a Lennard-Jones potential to an OpenMM force.

  • convert_dexp_potential \u2013

    Convert a DEXP potential to OpenMM forces.

  • convert_coulomb_potential \u2013

    Convert a Coulomb potential to an OpenMM force.

"},{"location":"reference/converters/openmm/nonbonded/#smee.converters.openmm.nonbonded.convert_custom_vdw_potential","title":"convert_custom_vdw_potential","text":"
convert_custom_vdw_potential(\n    potential: TensorPotential,\n    system: TensorSystem,\n    energy_fn: str,\n    mixing_fn: dict[str, str],\n) -> tuple[CustomNonbondedForce, CustomBondForce]\n

Converts an arbitrary vdW potential to OpenMM forces.

The intermolecular interactions are described by a custom nonbonded force, while the intramolecular interactions are described by a custom bond force.

If the potential has custom mixing rules (i.e. exceptions), a lookup table will be used to store the parameters. Otherwise, the mixing rules will be applied directly in the energy function.

Parameters:

  • potential (TensorPotential) \u2013

    The potential to convert.

  • system (TensorSystem) \u2013

    The system the potential belongs to.

  • energy_fn (str) \u2013

    The energy function of the potential, written in OpenMM's custom energy function syntax.

  • mixing_fn (dict[str, str]) \u2013

    A dictionary of mixing rules for each parameter of the potential. The keys are the parameter names, and the values are the mixing rules.

    The mixing rules should be a single expression that can be evaluated using OpenMM's energy function syntax, and should not contain any assignments.

Examples:

For a Lennard-Jones potential using Lorentz-Berthelot mixing rules:

>>> energy_fn = \"4*epsilon*x6*(x6 - 1.0);x6=x4*x2;x4=x2*x2;x2=x*x;x=sigma/r;\"\n>>> mixing_fn = {\n...     \"epsilon\": \"sqrt(epsilon1 * epsilon2)\",\n...     \"sigma\": \"0.5 * (sigma1 + sigma2)\",\n... }\n
Source code in smee/converters/openmm/nonbonded.py
def convert_custom_vdw_potential(\n    potential: smee.TensorPotential,\n    system: smee.TensorSystem,\n    energy_fn: str,\n    mixing_fn: dict[str, str],\n) -> tuple[openmm.CustomNonbondedForce, openmm.CustomBondForce]:\n    \"\"\"Converts an arbitrary vdW potential to OpenMM forces.\n\n    The intermolecular interactions are described by a custom nonbonded force, while the\n    intramolecular interactions are described by a custom bond force.\n\n    If the potential has custom mixing rules (i.e. exceptions), a lookup table will be\n    used to store the parameters. Otherwise, the mixing rules will be applied directly\n    in the energy function.\n\n    Args:\n        potential: The potential to convert.\n        system: The system the potential belongs to.\n        energy_fn: The energy function of the potential, written in OpenMM's custom\n            energy function syntax.\n        mixing_fn: A dictionary of mixing rules for each parameter of the potential.\n            The keys are the parameter names, and the values are the mixing rules.\n\n            The mixing rules should be a single expression that can be evaluated using\n            OpenMM's energy function syntax, and should not contain any assignments.\n\n    Examples:\n        For a Lennard-Jones potential using Lorentz-Berthelot mixing rules:\n\n        >>> energy_fn = \"4*epsilon*x6*(x6 - 1.0);x6=x4*x2;x4=x2*x2;x2=x*x;x=sigma/r;\"\n        >>> mixing_fn = {\n        ...     \"epsilon\": \"sqrt(epsilon1 * epsilon2)\",\n        ...     \"sigma\": \"0.5 * (sigma1 + sigma2)\",\n        ... }\n    \"\"\"\n    energy_fn = re.sub(r\"\\s+\", \"\", energy_fn)\n    mixing_fn = {k: re.sub(r\"\\s+\", \"\", v) for k, v in mixing_fn.items()}\n\n    used_parameters, used_attributes = _detect_parameters(\n        potential, energy_fn, mixing_fn\n    )\n    requires_lookup = potential.exceptions is not None\n\n    inter_force = _create_nonbonded_force(\n        potential, system, openmm.CustomNonbondedForce\n    )\n    inter_force.setEnergyFunction(energy_fn)\n\n    intra_force = openmm.CustomBondForce(\n        _prepend_scale_to_energy_fn(energy_fn, _INTRA_SCALE_VAR)\n    )\n    intra_force.addPerBondParameter(_INTRA_SCALE_VAR)\n    intra_force.setUsesPeriodicBoundaryConditions(system.is_periodic)\n\n    for force in [inter_force, intra_force]:\n        for attr in used_attributes:\n            attr_unit = potential.attribute_units[potential.attribute_cols.index(attr)]\n            attr_conv = (\n                (1.0 * attr_unit)\n                .to_openmm()\n                .value_in_unit_system(openmm.unit.md_unit_system)\n            )\n            attr_idx = potential.attribute_cols.index(attr)\n            attr_val = float(potential.attributes[attr_idx]) * attr_conv\n\n            force.addGlobalParameter(attr, attr_val)\n\n    if requires_lookup:\n        _add_parameters_to_vdw_with_lookup(\n            potential, system, energy_fn, mixing_fn, inter_force, intra_force\n        )\n    else:\n        _add_parameters_to_vdw_without_lookup(\n            potential,\n            system,\n            energy_fn,\n            mixing_fn,\n            inter_force,\n            intra_force,\n            used_parameters,\n        )\n\n    return inter_force, intra_force\n
"},{"location":"reference/converters/openmm/nonbonded/#smee.converters.openmm.nonbonded.convert_lj_potential","title":"convert_lj_potential","text":"
convert_lj_potential(\n    potential: TensorPotential, system: TensorSystem\n) -> (\n    NonbondedForce\n    | list[CustomNonbondedForce | CustomBondForce]\n)\n

Convert a Lennard-Jones potential to an OpenMM force.

If the potential has custom mixing rules (i.e. exceptions), the interactions will be split into an inter- and intra-molecular force.

Source code in smee/converters/openmm/nonbonded.py
@smee.converters.openmm.potential_converter(\n    smee.PotentialType.VDW, smee.EnergyFn.VDW_LJ\n)\ndef convert_lj_potential(\n    potential: smee.TensorPotential, system: smee.TensorSystem\n) -> openmm.NonbondedForce | list[openmm.CustomNonbondedForce | openmm.CustomBondForce]:\n    \"\"\"Convert a Lennard-Jones potential to an OpenMM force.\n\n    If the potential has custom mixing rules (i.e. exceptions), the interactions will\n    be split into an inter- and intra-molecular force.\n    \"\"\"\n    energy_fn = \"4*epsilon*x6*(x6 - 1.0);x6=x4*x2;x4=x2*x2;x2=x*x;x=sigma/r;\"\n    mixing_fn = {\n        \"epsilon\": \"sqrt(epsilon1 * epsilon2)\",\n        \"sigma\": \"0.5 * (sigma1 + sigma2)\",\n    }\n\n    if potential.exceptions is not None:\n        return list(\n            convert_custom_vdw_potential(potential, system, energy_fn, mixing_fn)\n        )\n\n    force = _create_nonbonded_force(potential, system)\n\n    idx_offset = 0\n\n    for topology, n_copies in zip(system.topologies, system.n_copies, strict=True):\n        parameter_map = topology.parameters[potential.type]\n        parameters = parameter_map.assignment_matrix @ potential.parameters\n\n        for _ in range(n_copies):\n            for epsilon, sigma in parameters:\n                force.addParticle(0.0, sigma * _ANGSTROM, epsilon * _KCAL_PER_MOL)\n\n            for index, (i, j) in enumerate(parameter_map.exclusions):\n                scale = potential.attributes[parameter_map.exclusion_scale_idxs[index]]\n\n                eps_i, sig_i = parameters[i, :]\n                eps_j, sig_j = parameters[j, :]\n\n                eps, sig = smee.potentials.nonbonded.lorentz_berthelot(\n                    eps_i, eps_j, sig_i, sig_j\n                )\n\n                force.addException(\n                    i + idx_offset,\n                    j + idx_offset,\n                    0.0,\n                    float(sig) * _ANGSTROM,\n                    float(eps * scale) * _KCAL_PER_MOL,\n                )\n\n            idx_offset += topology.n_particles\n\n    return force\n
"},{"location":"reference/converters/openmm/nonbonded/#smee.converters.openmm.nonbonded.convert_dexp_potential","title":"convert_dexp_potential","text":"
convert_dexp_potential(\n    potential: TensorPotential, system: TensorSystem\n) -> tuple[CustomNonbondedForce, CustomBondForce]\n

Convert a DEXP potential to OpenMM forces.

The intermolcular interactions are described by a custom nonbonded force, while the intramolecular interactions are described by a custom bond force.

If the potential has custom mixing rules (i.e. exceptions), a lookup table will be used to store the parameters. Otherwise, the mixing rules will be applied directly in the energy function.

Source code in smee/converters/openmm/nonbonded.py
@smee.converters.openmm.potential_converter(\n    smee.PotentialType.VDW, smee.EnergyFn.VDW_DEXP\n)\ndef convert_dexp_potential(\n    potential: smee.TensorPotential, system: smee.TensorSystem\n) -> tuple[openmm.CustomNonbondedForce, openmm.CustomBondForce]:\n    \"\"\"Convert a DEXP potential to OpenMM forces.\n\n    The intermolcular interactions are described by a custom nonbonded force, while the\n    intramolecular interactions are described by a custom bond force.\n\n    If the potential has custom mixing rules (i.e. exceptions), a lookup table will be\n    used to store the parameters. Otherwise, the mixing rules will be applied directly\n    in the energy function.\n    \"\"\"\n    energy_fn = (\n        \"epsilon * (repulsion - attraction);\"\n        \"repulsion  = beta  / (alpha - beta) * exp(alpha * (1 - x));\"\n        \"attraction = alpha / (alpha - beta) * exp(beta  * (1 - x));\"\n        \"x = r / r_min;\"\n    )\n    mixing_fn = {\n        \"epsilon\": \"sqrt(epsilon1 * epsilon2)\",\n        \"r_min\": \"0.5 * (r_min1 + r_min2)\",\n    }\n\n    return convert_custom_vdw_potential(potential, system, energy_fn, mixing_fn)\n
"},{"location":"reference/converters/openmm/nonbonded/#smee.converters.openmm.nonbonded.convert_coulomb_potential","title":"convert_coulomb_potential","text":"
convert_coulomb_potential(\n    potential: TensorPotential, system: TensorSystem\n) -> NonbondedForce\n

Convert a Coulomb potential to an OpenMM force.

Source code in smee/converters/openmm/nonbonded.py
@smee.converters.openmm.potential_converter(\n    smee.PotentialType.ELECTROSTATICS, smee.EnergyFn.COULOMB\n)\ndef convert_coulomb_potential(\n    potential: smee.TensorPotential, system: smee.TensorSystem\n) -> openmm.NonbondedForce:\n    \"\"\"Convert a Coulomb potential to an OpenMM force.\"\"\"\n    force = _create_nonbonded_force(potential, system)\n\n    idx_offset = 0\n\n    for topology, n_copies in zip(system.topologies, system.n_copies, strict=True):\n        parameter_map = topology.parameters[potential.type]\n        parameters = parameter_map.assignment_matrix @ potential.parameters\n\n        for _ in range(n_copies):\n            for charge in parameters:\n                force.addParticle(\n                    charge.detach() * openmm.unit.elementary_charge,\n                    1.0 * _ANGSTROM,\n                    0.0 * _KCAL_PER_MOL,\n                )\n\n            for index, (i, j) in enumerate(parameter_map.exclusions):\n                q_i, q_j = parameters[i], parameters[j]\n                q = q_i * q_j\n\n                scale = potential.attributes[parameter_map.exclusion_scale_idxs[index]]\n\n                force.addException(\n                    i + idx_offset,\n                    j + idx_offset,\n                    scale * q,\n                    1.0,\n                    0.0,\n                )\n\n            idx_offset += topology.n_particles\n\n    return force\n
"},{"location":"reference/converters/openmm/valence/","title":" valence","text":""},{"location":"reference/converters/openmm/valence/#smee.converters.openmm.valence","title":"valence","text":"

Convert valence potentials to OpenMM forces.

Functions:

  • convert_bond_potential \u2013

    Convert a harmonic bond potential to a corresponding OpenMM force.

  • convert_torsion_potential \u2013

    Convert a torsion potential to a corresponding OpenMM force.

"},{"location":"reference/converters/openmm/valence/#smee.converters.openmm.valence.convert_bond_potential","title":"convert_bond_potential","text":"
convert_bond_potential(\n    potential: TensorPotential, system: TensorSystem\n) -> HarmonicBondForce\n

Convert a harmonic bond potential to a corresponding OpenMM force.

Source code in smee/converters/openmm/valence.py
@smee.converters.openmm.potential_converter(\n    smee.PotentialType.BONDS, smee.EnergyFn.BOND_HARMONIC\n)\ndef convert_bond_potential(\n    potential: smee.TensorPotential, system: smee.TensorSystem\n) -> openmm.HarmonicBondForce:\n    \"\"\"Convert a harmonic bond potential to a corresponding OpenMM force.\"\"\"\n    force = openmm.HarmonicBondForce()\n\n    idx_offset = 0\n\n    for topology, n_copies in zip(system.topologies, system.n_copies, strict=True):\n        parameters = (\n            topology.parameters[potential.type].assignment_matrix @ potential.parameters\n        )\n\n        for _ in range(n_copies):\n            atom_idxs = topology.parameters[potential.type].particle_idxs + idx_offset\n\n            for (i, j), (constant, length) in zip(atom_idxs, parameters, strict=True):\n                force.addBond(\n                    i,\n                    j,\n                    length * _ANGSTROM,\n                    constant * _KCAL_PER_MOL / _ANGSTROM**2,\n                )\n\n            idx_offset += topology.n_particles\n\n    return force\n
"},{"location":"reference/converters/openmm/valence/#smee.converters.openmm.valence.convert_torsion_potential","title":"convert_torsion_potential","text":"
convert_torsion_potential(\n    potential: TensorPotential, system: TensorSystem\n) -> PeriodicTorsionForce\n

Convert a torsion potential to a corresponding OpenMM force.

Source code in smee/converters/openmm/valence.py
@smee.converters.openmm.potential_converter(\n    smee.PotentialType.PROPER_TORSIONS, smee.EnergyFn.TORSION_COSINE\n)\n@smee.converters.openmm.potential_converter(\n    smee.PotentialType.IMPROPER_TORSIONS, smee.EnergyFn.TORSION_COSINE\n)\ndef convert_torsion_potential(\n    potential: smee.TensorPotential, system: smee.TensorSystem\n) -> openmm.PeriodicTorsionForce:\n    \"\"\"Convert a torsion potential to a corresponding OpenMM force.\"\"\"\n    force = openmm.PeriodicTorsionForce()\n\n    idx_offset = 0\n\n    for topology, n_copies in zip(system.topologies, system.n_copies, strict=True):\n        parameters = (\n            topology.parameters[potential.type].assignment_matrix @ potential.parameters\n        )\n\n        for _ in range(n_copies):\n            atom_idxs = topology.parameters[potential.type].particle_idxs + idx_offset\n\n            for (idx_i, idx_j, idx_k, idx_l), (\n                constant,\n                periodicity,\n                phase,\n                idivf,\n            ) in zip(atom_idxs, parameters, strict=True):\n                force.addTorsion(\n                    idx_i,\n                    idx_j,\n                    idx_k,\n                    idx_l,\n                    int(periodicity),\n                    phase * _RADIANS,\n                    constant / idivf * _KCAL_PER_MOL,\n                )\n\n            idx_offset += topology.n_particles\n\n    return force\n
"},{"location":"reference/mm/","title":" mm","text":""},{"location":"reference/mm/#smee.mm","title":"mm","text":"

Compute differentiable ensemble averages using OpenMM and SMEE.

Classes:

  • GenerateCoordsConfig \u2013

    Configure how coordinates should be generated for a system using PACKMOL.

  • MinimizationConfig \u2013

    Configure how a system should be energy minimized.

  • NotEnoughSamplesError \u2013

    An error raised when an ensemble average is attempted with too few samples.

  • TensorReporter \u2013

    A reporter which stores coords, box vectors, reduced potentials and kinetic

Functions:

  • generate_system_coords \u2013

    Generate coordinates for a system of molecules using PACKMOL.

  • simulate \u2013

    Simulate a SMEE system of molecules or topology.

  • compute_ensemble_averages \u2013

    Compute ensemble average of the potential energy, volume, density,

  • reweight_ensemble_averages \u2013

    Compute the ensemble average of the potential energy, volume, density,

  • tensor_reporter \u2013

    Create a TensorReporter capable of writing frames to a file.

  • unpack_frames \u2013

    Unpack frames saved by a TensorReporter.

"},{"location":"reference/mm/#smee.mm.GenerateCoordsConfig","title":"GenerateCoordsConfig pydantic-model","text":"

Bases: BaseModel

Configure how coordinates should be generated for a system using PACKMOL.

"},{"location":"reference/mm/#smee.mm.MinimizationConfig","title":"MinimizationConfig pydantic-model","text":"

Bases: BaseModel

Configure how a system should be energy minimized.

"},{"location":"reference/mm/#smee.mm.NotEnoughSamplesError","title":"NotEnoughSamplesError","text":"

Bases: ValueError

An error raised when an ensemble average is attempted with too few samples.

"},{"location":"reference/mm/#smee.mm.TensorReporter","title":"TensorReporter","text":"
TensorReporter(\n    output_file: BinaryIO,\n    report_interval: int,\n    beta: Quantity,\n    pressure: Quantity | None,\n)\n

A reporter which stores coords, box vectors, reduced potentials and kinetic energy using msgpack.

report_interval: The interval (in steps) at which to write frames.\nbeta: The inverse temperature the simulation is being run at.\npressure: The pressure the simulation is being run at, or None if NVT /\n    vacuum.\n
Source code in smee/mm/_reporters.py
def __init__(\n    self,\n    output_file: typing.BinaryIO,\n    report_interval: int,\n    beta: openmm.unit.Quantity,\n    pressure: openmm.unit.Quantity | None,\n):\n    \"\"\"\n\n    Args:\n        output_file: The file to write the frames to.\n        report_interval: The interval (in steps) at which to write frames.\n        beta: The inverse temperature the simulation is being run at.\n        pressure: The pressure the simulation is being run at, or None if NVT /\n            vacuum.\n    \"\"\"\n    self._output_file = output_file\n    self._report_interval = report_interval\n\n    self._beta = beta\n    self._pressure = (\n        None if pressure is None else pressure * openmm.unit.AVOGADRO_CONSTANT_NA\n    )\n
"},{"location":"reference/mm/#smee.mm.generate_system_coords","title":"generate_system_coords","text":"
generate_system_coords(\n    system: TensorSystem,\n    force_field: TensorForceField | None,\n    config: Optional[GenerateCoordsConfig] = None,\n) -> tuple[Quantity, Quantity]\n

Generate coordinates for a system of molecules using PACKMOL.

Parameters:

  • system (TensorSystem) \u2013

    The system to generate coordinates for.

  • force_field (TensorForceField | None) \u2013

    The force field that describes the geometry of any virtual sites.

  • config (Optional[GenerateCoordsConfig], default: None ) \u2013

    Configuration of how to generate the system coordinates.

Returns:

  • tuple[Quantity, Quantity] \u2013

    The coordinates with shape=(n_atoms, 3) and box vectors with shape=(3, 3)

Source code in smee/mm/_mm.py
def generate_system_coords(\n    system: smee.TensorSystem,\n    force_field: smee.TensorForceField | None,\n    config: typing.Optional[\"smee.mm.GenerateCoordsConfig\"] = None,\n) -> tuple[openmm.unit.Quantity, openmm.unit.Quantity]:\n    \"\"\"Generate coordinates for a system of molecules using PACKMOL.\n\n    Args:\n        system: The system to generate coordinates for.\n        force_field: The force field that describes the geometry of any virtual sites.\n        config: Configuration of how to generate the system coordinates.\n\n    Raises:\n        * PACKMOLRuntimeError\n\n    Returns:\n        The coordinates with ``shape=(n_atoms, 3)`` and box vectors with\n        ``shape=(3, 3)``\n    \"\"\"\n\n    system = system.to(\"cpu\")\n    force_field = None if force_field is None else force_field.to(\"cpu\")\n\n    config = config if config is not None else smee.mm.GenerateCoordsConfig()\n\n    box_size = _approximate_box_size(system, config)\n\n    with tempfile.TemporaryDirectory() as tmp_dir:\n        tmp_dir = pathlib.Path(tmp_dir)\n\n        for i, topology in enumerate(system.topologies):\n            xyz = _topology_to_xyz(topology, force_field)\n            (tmp_dir / f\"{i}.xyz\").write_text(xyz)\n\n        input_file = tmp_dir / \"input.txt\"\n        input_file.write_text(\n            _generate_packmol_input(system.n_copies, box_size, config)\n        )\n\n        with input_file.open(\"r\") as file:\n            result = subprocess.run(\n                \"packmol\", stdin=file, capture_output=True, text=True, cwd=tmp_dir\n            )\n\n        if result.returncode != 0 or not result.stdout.find(\"Success!\") > 0:\n            raise PACKMOLRuntimeError(result.stdout)\n\n        output_lines = (tmp_dir / \"output.xyz\").read_text().splitlines()\n\n    coordinates = (\n        numpy.array(\n            [\n                [float(coordinate) for coordinate in coordinate_line.split()[1:]]\n                for coordinate_line in output_lines[2:]\n                if len(coordinate_line) > 0\n            ]\n        )\n        * openmm.unit.angstrom\n    )\n\n    box_vectors = numpy.eye(3) * (box_size + config.padding)\n    return coordinates, box_vectors\n
"},{"location":"reference/mm/#smee.mm.simulate","title":"simulate","text":"
simulate(\n    system: TensorSystem | TensorTopology,\n    force_field: TensorForceField,\n    coords: Quantity,\n    box_vectors: Quantity | None,\n    equilibrate_configs: list[\n        Union[MinimizationConfig, SimulationConfig]\n    ],\n    production_config: SimulationConfig,\n    production_reporters: list[Any] | None = None,\n    apply_hmr: bool = False,\n) -> State\n

Simulate a SMEE system of molecules or topology.

Parameters:

  • system (TensorSystem | TensorTopology) \u2013

    The system / topology to simulate.

  • force_field (TensorForceField) \u2013

    The force field to simulate with.

  • coords (Quantity) \u2013

    The coordinates [\u00c5] to use for the simulation. This should be a unit wrapped numpy array with shape=(n_atoms, 3).

  • box_vectors (Quantity | None) \u2013

    The box vectors [\u00c5] to use for the simulation if periodic. This should be a unit wrapped numpy array with shape=(3, 3).

  • equilibrate_configs (list[Union[MinimizationConfig, SimulationConfig]]) \u2013

    A list of configurations defining the steps to run for equilibration. No data will be stored from these simulations.

  • production_config (SimulationConfig) \u2013

    The configuration defining the production simulation to run.

  • production_reporters (list[Any] | None, default: None ) \u2013

    A list of additional OpenMM reporters to use for the production simulation.

  • apply_hmr (bool, default: False ) \u2013

    Whether to apply Hydrogen Mass Repartitioning to the system prior to simulation.

Source code in smee/mm/_mm.py
def simulate(\n    system: smee.TensorSystem | smee.TensorTopology,\n    force_field: smee.TensorForceField,\n    coords: openmm.unit.Quantity,\n    box_vectors: openmm.unit.Quantity | None,\n    equilibrate_configs: list[\n        typing.Union[\"smee.mm.MinimizationConfig\", \"smee.mm.SimulationConfig\"]\n    ],\n    production_config: \"smee.mm.SimulationConfig\",\n    production_reporters: list[typing.Any] | None = None,\n    apply_hmr: bool = False,\n) -> openmm.State:\n    \"\"\"Simulate a SMEE system of molecules or topology.\n\n    Args:\n        system: The system / topology to simulate.\n        force_field: The force field to simulate with.\n        coords: The coordinates [\u00c5] to use for the simulation. This should be\n            a unit wrapped numpy array with ``shape=(n_atoms, 3)``.\n        box_vectors: The box vectors [\u00c5] to use for the simulation if periodic. This\n            should be a unit wrapped numpy array with ``shape=(3, 3)``.\n        equilibrate_configs: A list of configurations defining the steps to run for\n            equilibration. No data will be stored from these simulations.\n        production_config: The configuration defining the production simulation to run.\n        production_reporters: A list of additional OpenMM reporters to use for the\n            production simulation.\n        apply_hmr: Whether to apply Hydrogen Mass Repartitioning to the system prior\n            to simulation.\n    \"\"\"\n\n    assert isinstance(coords.value_in_unit(openmm.unit.angstrom), numpy.ndarray)\n    assert isinstance(box_vectors.value_in_unit(openmm.unit.angstrom), numpy.ndarray)\n\n    force_field = force_field.to(\"cpu\")\n\n    system: smee.TensorSystem = (\n        system\n        if isinstance(system, smee.TensorSystem)\n        else smee.TensorSystem([system], [1], False)\n    ).to(\"cpu\")\n\n    requires_pbc = any(\n        config.pressure is not None\n        for config in equilibrate_configs + [production_config]\n        if isinstance(config, smee.mm.SimulationConfig)\n    )\n\n    if not system.is_periodic and requires_pbc:\n        raise ValueError(\"pressure cannot be specified for a non-periodic system\")\n\n    platform = _get_platform(system.is_periodic)\n\n    omm_state = coords, box_vectors\n\n    omm_system = smee.converters.convert_to_openmm_system(force_field, system)\n    omm_topology = smee.converters.convert_to_openmm_topology(system)\n\n    if apply_hmr:\n        _apply_hmr(omm_system, system)\n\n    for i, config in enumerate(equilibrate_configs):\n        _LOGGER.info(f\"running equilibration step {i + 1} / {len(equilibrate_configs)}\")\n\n        if isinstance(config, smee.mm.MinimizationConfig):\n            omm_state = _energy_minimize(omm_system, omm_state, platform, config)\n\n        elif isinstance(config, smee.mm.SimulationConfig):\n            omm_state = _run_simulation(\n                omm_system, omm_topology, omm_state, platform, config, None\n            )\n        else:\n            raise NotImplementedError\n\n        _LOGGER.info(_get_state_log(omm_state))\n\n    _LOGGER.info(\"running production simulation\")\n    omm_state = _run_simulation(\n        omm_system,\n        omm_topology,\n        omm_state,\n        platform,\n        production_config,\n        production_reporters,\n    )\n    _LOGGER.info(_get_state_log(omm_state))\n\n    return omm_state\n
"},{"location":"reference/mm/#smee.mm.compute_ensemble_averages","title":"compute_ensemble_averages","text":"
compute_ensemble_averages(\n    system: TensorSystem,\n    force_field: TensorForceField,\n    frames_path: Path,\n    temperature: Quantity,\n    pressure: Quantity | None,\n) -> tuple[dict[str, Tensor], dict[str, Tensor]]\n

Compute ensemble average of the potential energy, volume, density, and enthalpy (if running NPT) over an MD trajectory.

Parameters:

  • system (TensorSystem) \u2013

    The system to simulate.

  • force_field (TensorForceField) \u2013

    The force field to use.

  • frames_path (Path) \u2013

    The path to the trajectory to compute the average over.

  • temperature (Quantity) \u2013

    The temperature that the trajectory was simulated at.

  • pressure (Quantity | None) \u2013

    The pressure that the trajectory was simulated at.

Returns:

  • tuple[dict[str, Tensor], dict[str, Tensor]] \u2013

    A dictionary containing the ensemble averages of the potential energy [kcal/mol], volume [\u00c5^3], density [g/mL], and enthalpy [kcal/mol], and a dictionary containing their standard deviations.

Source code in smee/mm/_ops.py
def compute_ensemble_averages(\n    system: smee.TensorSystem,\n    force_field: smee.TensorForceField,\n    frames_path: pathlib.Path,\n    temperature: openmm.unit.Quantity,\n    pressure: openmm.unit.Quantity | None,\n) -> tuple[dict[str, torch.Tensor], dict[str, torch.Tensor]]:\n    \"\"\"Compute ensemble average of the potential energy, volume, density,\n    and enthalpy (if running NPT) over an MD trajectory.\n\n    Args:\n        system: The system to simulate.\n        force_field: The force field to use.\n        frames_path: The path to the trajectory to compute the average over.\n        temperature: The temperature that the trajectory was simulated at.\n        pressure: The pressure that the trajectory was simulated at.\n\n    Returns:\n        A dictionary containing the ensemble averages of the potential energy\n        [kcal/mol], volume [\u00c5^3], density [g/mL], and enthalpy [kcal/mol],\n        and a dictionary containing their standard deviations.\n    \"\"\"\n    tensors, parameter_lookup, attribute_lookup, has_v_sites = _pack_force_field(\n        force_field\n    )\n\n    beta = 1.0 / (openmm.unit.MOLAR_GAS_CONSTANT_R * temperature)\n    beta = beta.value_in_unit(openmm.unit.kilocalorie_per_mole**-1)\n\n    if pressure is not None:\n        pressure = (pressure * openmm.unit.AVOGADRO_CONSTANT_NA).value_in_unit(\n            openmm.unit.kilocalorie_per_mole / openmm.unit.angstrom**3\n        )\n\n    kwargs: _EnsembleAverageKwargs = {\n        \"force_field\": force_field,\n        \"parameter_lookup\": parameter_lookup,\n        \"attribute_lookup\": attribute_lookup,\n        \"has_v_sites\": has_v_sites,\n        \"system\": system,\n        \"frames_path\": frames_path,\n        \"beta\": beta,\n        \"pressure\": pressure,\n    }\n\n    *avg_outputs, columns = _EnsembleAverageOp.apply(kwargs, *tensors)\n\n    avg_values = avg_outputs[: len(avg_outputs) // 2]\n    avg_std = avg_outputs[len(avg_outputs) // 2 :]\n\n    return (\n        {column: avg for avg, column in zip(avg_values, columns, strict=True)},\n        {column: avg for avg, column in zip(avg_std, columns, strict=True)},\n    )\n
"},{"location":"reference/mm/#smee.mm.reweight_ensemble_averages","title":"reweight_ensemble_averages","text":"
reweight_ensemble_averages(\n    system: TensorSystem,\n    force_field: TensorForceField,\n    frames_path: Path,\n    temperature: Quantity,\n    pressure: Quantity | None,\n    min_samples: int = 50,\n) -> dict[str, Tensor]\n

Compute the ensemble average of the potential energy, volume, density, and enthalpy (if running NPT) by re-weighting an existing MD trajectory.

Parameters:

  • system (TensorSystem) \u2013

    The system that was simulated.

  • force_field (TensorForceField) \u2013

    The new force field to use.

  • frames_path (Path) \u2013

    The path to the trajectory to compute the average over.

  • temperature (Quantity) \u2013

    The temperature that the trajectory was simulated at.

  • pressure (Quantity | None) \u2013

    The pressure that the trajectory was simulated at.

  • min_samples (int, default: 50 ) \u2013

    The minimum number of samples required to compute the average.

Raises:

  • NotEnoughSamplesError \u2013

    If the number of effective samples is less than min_samples.

Returns:

  • dict[str, Tensor] \u2013

    A dictionary containing the ensemble averages of the potential energy [kcal/mol], volume [\u00c5^3], density [g/mL], and enthalpy [kcal/mol].

Source code in smee/mm/_ops.py
def reweight_ensemble_averages(\n    system: smee.TensorSystem,\n    force_field: smee.TensorForceField,\n    frames_path: pathlib.Path,\n    temperature: openmm.unit.Quantity,\n    pressure: openmm.unit.Quantity | None,\n    min_samples: int = 50,\n) -> dict[str, torch.Tensor]:\n    \"\"\"Compute the ensemble average of the potential energy, volume, density,\n    and enthalpy (if running NPT) by re-weighting an existing MD trajectory.\n\n    Args:\n        system: The system that was simulated.\n        force_field: The new force field to use.\n        frames_path: The path to the trajectory to compute the average over.\n        temperature: The temperature that the trajectory was simulated at.\n        pressure: The pressure that the trajectory was simulated at.\n        min_samples: The minimum number of samples required to compute the average.\n\n    Raises:\n        NotEnoughSamplesError: If the number of effective samples is less than\n            ``min_samples``.\n\n    Returns:\n        A dictionary containing the ensemble averages of the potential energy\n        [kcal/mol], volume [\u00c5^3], density [g/mL], and enthalpy [kcal/mol].\n    \"\"\"\n    tensors, parameter_lookup, attribute_lookup, has_v_sites = _pack_force_field(\n        force_field\n    )\n\n    beta = 1.0 / (openmm.unit.MOLAR_GAS_CONSTANT_R * temperature)\n    beta = beta.value_in_unit(openmm.unit.kilocalorie_per_mole**-1)\n\n    if pressure is not None:\n        pressure = (pressure * openmm.unit.AVOGADRO_CONSTANT_NA).value_in_unit(\n            openmm.unit.kilocalorie_per_mole / openmm.unit.angstrom**3\n        )\n\n    kwargs: _ReweightAverageKwargs = {\n        \"force_field\": force_field,\n        \"parameter_lookup\": parameter_lookup,\n        \"attribute_lookup\": attribute_lookup,\n        \"has_v_sites\": has_v_sites,\n        \"system\": system,\n        \"frames_path\": frames_path,\n        \"beta\": beta,\n        \"pressure\": pressure,\n        \"min_samples\": min_samples,\n    }\n\n    *avg_outputs, columns = _ReweightAverageOp.apply(kwargs, *tensors)\n    return {column: avg for avg, column in zip(avg_outputs, columns, strict=True)}\n
"},{"location":"reference/mm/#smee.mm.tensor_reporter","title":"tensor_reporter","text":"
tensor_reporter(\n    output_path: PathLike,\n    report_interval: int,\n    beta: Quantity,\n    pressure: Quantity | None,\n) -> TensorReporter\n

Create a TensorReporter capable of writing frames to a file.

Parameters:

  • output_path (PathLike) \u2013

    The path to write the frames to.

  • report_interval (int) \u2013

    The interval (in steps) at which to write frames.

  • beta (Quantity) \u2013

    The inverse temperature the simulation is being run at.

  • pressure (Quantity | None) \u2013

    The pressure the simulation is being run at, or None if NVT / vacuum.

Source code in smee/mm/_reporters.py
@contextlib.contextmanager\ndef tensor_reporter(\n    output_path: os.PathLike,\n    report_interval: int,\n    beta: openmm.unit.Quantity,\n    pressure: openmm.unit.Quantity | None,\n) -> TensorReporter:\n    \"\"\"Create a ``TensorReporter`` capable of writing frames to a file.\n\n    Args:\n        output_path: The path to write the frames to.\n        report_interval: The interval (in steps) at which to write frames.\n        beta: The inverse temperature the simulation is being run at.\n        pressure: The pressure the simulation is being run at, or ``None`` if NVT /\n            vacuum.\n    \"\"\"\n    with open(output_path, \"wb\") as output_file:\n        reporter = TensorReporter(output_file, report_interval, beta, pressure)\n        yield reporter\n
"},{"location":"reference/mm/#smee.mm.unpack_frames","title":"unpack_frames","text":"
unpack_frames(\n    file: BinaryIO,\n) -> Generator[tuple[Tensor, Tensor, float], None, None]\n

Unpack frames saved by a TensorReporter.

Source code in smee/mm/_reporters.py
def unpack_frames(\n    file: typing.BinaryIO,\n) -> typing.Generator[tuple[torch.Tensor, torch.Tensor, float], None, None]:\n    \"\"\"Unpack frames saved by a ``TensorReporter``.\"\"\"\n\n    unpacker = msgpack.Unpacker(file, object_hook=_decoder)\n\n    for frame in unpacker:\n        yield frame\n
"},{"location":"reference/potentials/","title":"Index","text":""},{"location":"reference/potentials/#smee.potentials","title":"potentials","text":"

Evaluate the potential energy of parameterized topologies.

Modules:

  • nonbonded \u2013

    Non-bonded potential energy functions.

  • valence \u2013

    Valence potential energy functions.

Functions:

  • broadcast_exceptions \u2013

    Returns the indices of the parameters that should be used to model interactions

  • broadcast_idxs \u2013

    Broadcasts the particle indices of each topology for a given potential

  • broadcast_parameters \u2013

    Returns parameters for the full system by broadcasting and stacking the

  • compute_energy \u2013

    Computes the potential energy [kcal / mol] of a system / topology in a given

  • compute_energy_potential \u2013

    Computes the potential energy [kcal / mol] due to a SMIRNOFF potential

  • potential_energy_fn \u2013

    A decorator used to flag a function as being able to compute the potential for a

"},{"location":"reference/potentials/#smee.potentials.broadcast_exceptions","title":"broadcast_exceptions","text":"
broadcast_exceptions(\n    system: TensorSystem,\n    potential: TensorPotential,\n    idxs_a: Tensor,\n    idxs_b: Tensor,\n) -> tuple[Tensor, Tensor]\n

Returns the indices of the parameters that should be used to model interactions between pairs of particles

Parameters:

  • system (TensorSystem) \u2013

    The system.

  • potential (TensorPotential) \u2013

    The potential whose parameters should be broadcast.

  • idxs_a (Tensor) \u2013

    The indices of the first particle in each interaction with shape=(n_interactions,).

  • idxs_b (Tensor) \u2013

    The indices of the second particle in each interaction with shape=(n_interactions,).

Returns:

  • tuple[Tensor, Tensor] \u2013

    The indices of the interactions that require an exception, and the parameters to use for those interactions.

Source code in smee/potentials/_potentials.py
def broadcast_exceptions(\n    system: smee.TensorSystem,\n    potential: smee.TensorPotential,\n    idxs_a: torch.Tensor,\n    idxs_b: torch.Tensor,\n) -> tuple[torch.Tensor, torch.Tensor]:\n    \"\"\"Returns the indices of the parameters that should be used to model interactions\n    between pairs of particles\n\n    Args:\n        system: The system.\n        potential: The potential whose parameters should be broadcast.\n        idxs_a: The indices of the first particle in each interaction with\n            ``shape=(n_interactions,)``.\n        idxs_b: The indices of the second particle in each interaction with\n            ``shape=(n_interactions,)``.\n\n    Returns:\n        The indices of the interactions that require an exception, and the parameters\n        to use for those interactions.\n    \"\"\"\n    assert potential.exceptions is not None\n\n    parameter_idxs = []\n\n    for topology, n_copies in zip(system.topologies, system.n_copies, strict=True):\n        parameter_map = topology.parameters[potential.type]\n\n        if isinstance(parameter_map, smee.ValenceParameterMap):\n            raise NotImplementedError(\"valence exceptions are not supported\")\n\n        # check that each particle is assigned to exactly one parameter\n        assignment_dense = parameter_map.assignment_matrix.to_dense()\n\n        if not (assignment_dense.abs().sum(axis=-1) == 1).all():\n            raise NotImplementedError(\n                f\"exceptions can only be used when each particle is assigned exactly \"\n                f\"one {potential.type} parameter\"\n            )\n\n        assigned_idxs = assignment_dense.argmax(axis=-1)\n\n        n_particles = len(assigned_idxs)\n\n        assigned_idxs = torch.broadcast_to(\n            assigned_idxs[None, :], (n_copies, n_particles)\n        ).flatten()\n\n        parameter_idxs.append(assigned_idxs)\n\n    if len(parameter_idxs) == 0:\n        return torch.zeros((0,), dtype=torch.int64), torch.zeros(\n            (0, 0, len(potential.parameter_cols))\n        )\n\n    parameter_idxs = torch.concat(parameter_idxs)\n    parameter_idxs_a = parameter_idxs[idxs_a]\n    parameter_idxs_b = parameter_idxs[idxs_b]\n\n    if len({(min(i, j), max(i, j)) for i, j in potential.exceptions}) != len(\n        potential.exceptions\n    ):\n        raise NotImplementedError(\"cannot define different exceptions for i-j and j-i\")\n\n    exception_lookup = torch.full(\n        (len(potential.parameters), len(potential.parameters)), -1\n    )\n\n    for (i, j), v in potential.exceptions.items():\n        exception_lookup[(min(i, j), max(i, j))] = v\n        exception_lookup[(max(i, j), min(i, j))] = v\n\n    exceptions_parameter_idxs = exception_lookup[parameter_idxs_a, parameter_idxs_b]\n    exception_mask = exceptions_parameter_idxs >= 0\n\n    exceptions = potential.parameters[exceptions_parameter_idxs[exception_mask]]\n    exception_idxs = exception_mask.nonzero().flatten()\n\n    return exception_idxs, exceptions\n
"},{"location":"reference/potentials/#smee.potentials.broadcast_idxs","title":"broadcast_idxs","text":"
broadcast_idxs(\n    system: TensorSystem, potential: TensorPotential\n) -> Tensor\n

Broadcasts the particle indices of each topology for a given potential to the full system.

Parameters:

  • system (TensorSystem) \u2013

    The system.

  • potential (TensorPotential) \u2013

    The potential.

Returns:

  • Tensor \u2013

    The indices with shape (n_interactions, n_interacting_particles) where n_interacting_particles is 2 for bonds, 3 for angles, etc.

Source code in smee/potentials/_potentials.py
def broadcast_idxs(\n    system: smee.TensorSystem, potential: smee.TensorPotential\n) -> torch.Tensor:\n    \"\"\"Broadcasts the particle indices of each topology for a given potential\n    to the full system.\n\n    Args:\n        system: The system.\n        potential: The potential.\n\n    Returns:\n        The indices with shape ``(n_interactions, n_interacting_particles)`` where\n        ``n_interacting_particles`` is 2 for bonds, 3 for angles, etc.\n    \"\"\"\n\n    idx_offset = 0\n\n    per_topology_idxs = []\n\n    for topology, n_copies in zip(system.topologies, system.n_copies, strict=True):\n        parameter_map = topology.parameters[potential.type]\n        n_interacting_particles = parameter_map.particle_idxs.shape[-1]\n\n        idxs = parameter_map.particle_idxs\n\n        offset = (\n            idx_offset + smee.utils.arange_like(n_copies, idxs) * topology.n_particles\n        )\n\n        if len(idxs) > 0:\n            idxs = offset[:, None, None] + idxs[None, :, :]\n            per_topology_idxs.append(idxs.reshape(-1, n_interacting_particles))\n\n        idx_offset += n_copies * topology.n_particles\n\n    return (\n        torch.zeros((0, 0))\n        if len(per_topology_idxs) == 0\n        else torch.vstack(per_topology_idxs)\n    )\n
"},{"location":"reference/potentials/#smee.potentials.broadcast_parameters","title":"broadcast_parameters","text":"
broadcast_parameters(\n    system: TensorSystem, potential: TensorPotential\n) -> Tensor\n

Returns parameters for the full system by broadcasting and stacking the parameters of each topology.

Parameters:

  • system (TensorSystem) \u2013

    The system.

  • potential (TensorPotential) \u2013

    The potential whose parameters should be broadcast.

Returns:

  • Tensor \u2013

    The parameters for the full system with shape=(n_parameters, n_parameter_cols).

Source code in smee/potentials/_potentials.py
def broadcast_parameters(\n    system: smee.TensorSystem, potential: smee.TensorPotential\n) -> torch.Tensor:\n    \"\"\"Returns parameters for the full system by broadcasting and stacking the\n    parameters of each topology.\n\n    Args:\n        system: The system.\n        potential: The potential whose parameters should be broadcast.\n\n    Returns:\n        The parameters for the full system with\n        ``shape=(n_parameters, n_parameter_cols)``.\n    \"\"\"\n\n    parameters = []\n\n    for topology, n_copies in zip(system.topologies, system.n_copies, strict=True):\n        parameter_map = topology.parameters[potential.type]\n\n        topology_parameters = parameter_map.assignment_matrix @ potential.parameters\n\n        n_interactions = len(topology_parameters)\n        n_cols = len(potential.parameter_cols)\n\n        topology_parameters = torch.broadcast_to(\n            topology_parameters[None, :, :],\n            (n_copies, n_interactions, n_cols),\n        ).reshape(-1, n_cols)\n\n        parameters.append(topology_parameters)\n\n    return (\n        torch.zeros((0, len(potential.parameter_cols)))\n        if len(parameters) == 0\n        else torch.vstack(parameters)\n    )\n
"},{"location":"reference/potentials/#smee.potentials.compute_energy","title":"compute_energy","text":"
compute_energy(\n    system: TensorSystem | TensorTopology,\n    force_field: TensorForceField,\n    conformer: Tensor,\n    box_vectors: Tensor | None = None,\n) -> Tensor\n

Computes the potential energy [kcal / mol] of a system / topology in a given conformation(s).

Parameters:

  • system (TensorSystem | TensorTopology) \u2013

    The system or topology to compute the potential energy of.

  • force_field (TensorForceField) \u2013

    The force field that defines the potential energy function.

  • conformer (Tensor) \u2013

    The conformer(s) to evaluate the potential at with shape=(n_particles, 3) or shape=(n_confs, n_particles, 3).

  • box_vectors (Tensor | None, default: None ) \u2013

    The box vectors of the system with shape=(3, 3) if the system is periodic, or None if the system is non-periodic.

Returns:

  • Tensor \u2013

    The potential energy of the conformer(s) [kcal / mol].

Source code in smee/potentials/_potentials.py
def compute_energy(\n    system: smee.TensorSystem | smee.TensorTopology,\n    force_field: smee.TensorForceField,\n    conformer: torch.Tensor,\n    box_vectors: torch.Tensor | None = None,\n) -> torch.Tensor:\n    \"\"\"Computes the potential energy [kcal / mol] of a system / topology in a given\n    conformation(s).\n\n    Args:\n        system: The system or topology to compute the potential energy of.\n        force_field: The force field that defines the potential energy function.\n        conformer: The conformer(s) to evaluate the potential at with\n            ``shape=(n_particles, 3)`` or ``shape=(n_confs, n_particles, 3)``.\n        box_vectors: The box vectors of the system with ``shape=(3, 3)`` if the\n            system is periodic, or ``None`` if the system is non-periodic.\n\n    Returns:\n        The potential energy of the conformer(s) [kcal / mol].\n    \"\"\"\n\n    # register the built-in potential energy functions\n    importlib.import_module(\"smee.potentials.nonbonded\")\n    importlib.import_module(\"smee.potentials.valence\")\n\n    system, conformer, box_vectors = _prepare_inputs(system, conformer, box_vectors)\n    pairwise = _precompute_pairwise(system, force_field, conformer, box_vectors)\n\n    energy = smee.utils.zeros_like(\n        conformer.shape[0] if conformer.ndim == 3 else 1, conformer\n    )\n\n    for potential in force_field.potentials:\n        energy += compute_energy_potential(\n            system, potential, conformer, box_vectors, pairwise\n        )\n\n    return energy\n
"},{"location":"reference/potentials/#smee.potentials.compute_energy_potential","title":"compute_energy_potential","text":"
compute_energy_potential(\n    system: TensorSystem | TensorTopology,\n    potential: TensorPotential,\n    conformer: Tensor,\n    box_vectors: Tensor | None = None,\n    pairwise: Optional[PairwiseDistances] = None,\n) -> Tensor\n

Computes the potential energy [kcal / mol] due to a SMIRNOFF potential handler for a given conformer(s).

Parameters:

  • system (TensorSystem | TensorTopology) \u2013

    The system or topology to compute the potential energy of.

  • potential (TensorPotential) \u2013

    The potential describing the energy function to compute.

  • conformer (Tensor) \u2013

    The conformer(s) to evaluate the potential at with shape=(n_particles, 3) or shape=(n_confs, n_particles, 3).

  • box_vectors (Tensor | None, default: None ) \u2013

    The box vectors of the system with shape=(3, 3) or shape=(n_confs, 3, 3)if the system is periodic, orNone`` if the system is non-periodic.

  • pairwise (Optional[PairwiseDistances], default: None ) \u2013

    Pre-computed pairwise distances between particles in the system.

Returns:

  • Tensor \u2013

    The potential energy of the conformer(s) [kcal / mol].

Source code in smee/potentials/_potentials.py
def compute_energy_potential(\n    system: smee.TensorSystem | smee.TensorTopology,\n    potential: smee.TensorPotential,\n    conformer: torch.Tensor,\n    box_vectors: torch.Tensor | None = None,\n    pairwise: typing.Optional[\"smee.potentials.nonbonded.PairwiseDistances\"] = None,\n) -> torch.Tensor:\n    \"\"\"Computes the potential energy [kcal / mol] due to a SMIRNOFF potential\n    handler for a given conformer(s).\n\n    Args:\n        system: The system or topology to compute the potential energy of.\n        potential: The potential describing the energy function to compute.\n        conformer: The conformer(s) to evaluate the potential at with\n            ``shape=(n_particles, 3)`` or ``shape=(n_confs, n_particles, 3)``.\n        box_vectors: The box vectors of the system with ``shape=(3, 3)`` or\n            shape=(n_confs, 3, 3)`` if the system is periodic, or ``None`` if the system\n            is non-periodic.\n        pairwise: Pre-computed pairwise distances between particles in the system.\n\n    Returns:\n        The potential energy of the conformer(s) [kcal / mol].\n    \"\"\"\n\n    # register the built-in potential energy functions\n    importlib.import_module(\"smee.potentials.nonbonded\")\n    importlib.import_module(\"smee.potentials.valence\")\n\n    system, conformer, box_vectors = _prepare_inputs(system, conformer, box_vectors)\n\n    energy_fn = _POTENTIAL_ENERGY_FUNCTIONS[(potential.type, potential.fn)]\n    energy_fn_spec = inspect.signature(energy_fn)\n\n    energy_fn_kwargs = {}\n\n    if \"box_vectors\" in energy_fn_spec.parameters:\n        energy_fn_kwargs[\"box_vectors\"] = box_vectors\n    if \"pairwise\" in energy_fn_spec.parameters:\n        energy_fn_kwargs[\"pairwise\"] = pairwise\n\n    return energy_fn(system, potential, conformer, **energy_fn_kwargs)\n
"},{"location":"reference/potentials/#smee.potentials.potential_energy_fn","title":"potential_energy_fn","text":"
potential_energy_fn(\n    handler_type: str, energy_expression: str\n)\n

A decorator used to flag a function as being able to compute the potential for a specific handler and its associated energy expression.

Source code in smee/potentials/_potentials.py
def potential_energy_fn(handler_type: str, energy_expression: str):\n    \"\"\"A decorator used to flag a function as being able to compute the potential for a\n    specific handler and its associated energy expression.\"\"\"\n\n    def _potential_function_inner(func):\n        if (handler_type, energy_expression) in _POTENTIAL_ENERGY_FUNCTIONS:\n            raise KeyError(\n                f\"A potential energy function is already defined for \"\n                f\"handler={handler_type} fn={energy_expression}.\"\n            )\n\n        _POTENTIAL_ENERGY_FUNCTIONS[(handler_type, energy_expression)] = func\n        return func\n\n    return _potential_function_inner\n
"},{"location":"reference/potentials/nonbonded/","title":" nonbonded","text":""},{"location":"reference/potentials/nonbonded/#smee.potentials.nonbonded","title":"nonbonded","text":"

Non-bonded potential energy functions.

Classes:

  • PairwiseDistances \u2013

    A container for the pairwise distances between all particles, possibly within

Functions:

  • compute_pairwise_scales \u2013

    Returns the scale factor for each pair of particles in the system by

  • compute_pairwise \u2013

    Computes all pairwise distances between particles in the system.

  • prepare_lrc_types \u2013

    Finds the unique vdW interactions present in a system, ready to use

  • lorentz_berthelot \u2013

    Computes the Lorentz-Berthelot combination rules for the given parameters.

  • compute_lj_energy \u2013

    Computes the potential energy [kcal / mol] of the vdW interactions using the

  • compute_dexp_energy \u2013

    Compute the potential energy [kcal / mol] of the vdW interactions using the

  • compute_coulomb_energy \u2013

    Computes the potential energy [kcal / mol] of the electrostatic interactions

"},{"location":"reference/potentials/nonbonded/#smee.potentials.nonbonded.PairwiseDistances","title":"PairwiseDistances","text":"

Bases: NamedTuple

A container for the pairwise distances between all particles, possibly within a given cutoff.

Attributes:

  • idxs (Tensor) \u2013

    The particle indices of each pair with shape=(n_pairs, 2).

  • deltas (Tensor) \u2013

    The vector between each pair with shape=(n_pairs, 3).

  • distances (Tensor) \u2013

    The distance between each pair with shape=(n_pairs,).

  • cutoff (Tensor | None) \u2013

    The cutoff used when computing the distances.

"},{"location":"reference/potentials/nonbonded/#smee.potentials.nonbonded.PairwiseDistances.idxs","title":"idxs instance-attribute","text":"
idxs: Tensor\n

The particle indices of each pair with shape=(n_pairs, 2).

"},{"location":"reference/potentials/nonbonded/#smee.potentials.nonbonded.PairwiseDistances.deltas","title":"deltas instance-attribute","text":"
deltas: Tensor\n

The vector between each pair with shape=(n_pairs, 3).

"},{"location":"reference/potentials/nonbonded/#smee.potentials.nonbonded.PairwiseDistances.distances","title":"distances instance-attribute","text":"
distances: Tensor\n

The distance between each pair with shape=(n_pairs,).

"},{"location":"reference/potentials/nonbonded/#smee.potentials.nonbonded.PairwiseDistances.cutoff","title":"cutoff class-attribute instance-attribute","text":"
cutoff: Tensor | None = None\n

The cutoff used when computing the distances.

"},{"location":"reference/potentials/nonbonded/#smee.potentials.nonbonded.compute_pairwise_scales","title":"compute_pairwise_scales","text":"
compute_pairwise_scales(\n    system: TensorSystem, potential: TensorPotential\n) -> Tensor\n

Returns the scale factor for each pair of particles in the system by broadcasting and stacking the exclusions of each topology.

Parameters:

  • system (TensorSystem) \u2013

    The system.

  • potential (TensorPotential) \u2013

    The potential containing the scale factors to broadcast.

Returns:

  • Tensor \u2013

    The scales for each pair of particles as a flattened upper triangular matrix with shape=(n_particles * (n_particles - 1) / 2,).

Source code in smee/potentials/nonbonded.py
def compute_pairwise_scales(\n    system: smee.TensorSystem, potential: smee.TensorPotential\n) -> torch.Tensor:\n    \"\"\"Returns the scale factor for each pair of particles in the system by\n    broadcasting and stacking the exclusions of each topology.\n\n    Args:\n        system: The system.\n        potential: The potential containing the scale factors to broadcast.\n\n    Returns:\n        The scales for each pair of particles as a flattened upper triangular matrix\n        with ``shape=(n_particles * (n_particles - 1) / 2,)``.\n    \"\"\"\n\n    n_particles = system.n_particles\n    n_pairs = (n_particles * (n_particles - 1)) // 2\n\n    exclusion_idxs, exclusion_scales = _broadcast_exclusions(system, potential)\n\n    pair_scales = smee.utils.ones_like(n_pairs, other=potential.parameters)\n\n    if len(exclusion_idxs) > 0:\n        exclusion_idxs, _ = exclusion_idxs.sort(dim=1)  # ensure upper triangle\n\n        pair_idxs = smee.utils.to_upper_tri_idx(\n            exclusion_idxs[:, 0], exclusion_idxs[:, 1], n_particles\n        )\n        pair_scales[pair_idxs] = exclusion_scales\n\n    return pair_scales\n
"},{"location":"reference/potentials/nonbonded/#smee.potentials.nonbonded.compute_pairwise","title":"compute_pairwise","text":"
compute_pairwise(\n    system: TensorSystem,\n    conformer: Tensor,\n    box_vectors: Tensor | None,\n    cutoff: Tensor,\n) -> PairwiseDistances\n

Computes all pairwise distances between particles in the system.

Notes

If the system is not periodic, no cutoff and no PBC will be applied.

Parameters:

  • system (TensorSystem) \u2013

    The system to compute the distances for.

  • conformer (Tensor) \u2013

    The conformer [\u00c5] to evaluate the potential at with shape=(n_confs, n_particles, 3) or shape=(n_particles, 3).

  • box_vectors (Tensor | None) \u2013

    The box vectors [\u00c5] of the system with shape=(n_confs3, 3) or shape=(3, 3) if the system is periodic, or None otherwise.

  • cutoff (Tensor) \u2013

    The cutoff [\u00c5] to apply for periodic systems.

Returns:

  • PairwiseDistances \u2013

    The pairwise distances between each pair of particles within the cutoff.

Source code in smee/potentials/nonbonded.py
def compute_pairwise(\n    system: smee.TensorSystem,\n    conformer: torch.Tensor,\n    box_vectors: torch.Tensor | None,\n    cutoff: torch.Tensor,\n) -> PairwiseDistances:\n    \"\"\"Computes all pairwise distances between particles in the system.\n\n    Notes:\n        If the system is not periodic, no cutoff and no PBC will be applied.\n\n    Args:\n        system: The system to compute the distances for.\n        conformer: The conformer [\u00c5] to evaluate the potential at with\n            ``shape=(n_confs, n_particles, 3)`` or ``shape=(n_particles, 3)``.\n        box_vectors: The box vectors [\u00c5] of the system with ``shape=(n_confs3, 3)``\n            or ``shape=(3, 3)`` if the system is periodic, or ``None`` otherwise.\n        cutoff: The cutoff [\u00c5] to apply for periodic systems.\n\n    Returns:\n        The pairwise distances between each pair of particles within the cutoff.\n    \"\"\"\n    if system.is_periodic:\n        return _compute_pairwise_periodic(conformer, box_vectors, cutoff)\n    else:\n        return _compute_pairwise_non_periodic(conformer)\n
"},{"location":"reference/potentials/nonbonded/#smee.potentials.nonbonded.prepare_lrc_types","title":"prepare_lrc_types","text":"
prepare_lrc_types(\n    system: TensorSystem, potential: TensorPotential\n) -> tuple[Tensor, Tensor, Tensor]\n

Finds the unique vdW interactions present in a system, ready to use for computing the long range dispersion correction.

Parameters:

  • system (TensorSystem) \u2013

    The system to prepare the types for.

  • potential (TensorPotential) \u2013

    The potential to prepare the types for.

Returns:

  • tuple[Tensor, Tensor, Tensor] \u2013

    Two tensors containing the i and j indices into potential.paramaters of each unique interaction parameter excluding i==j, the number of ii interactions with shape=(n_params,), and the numbers of ij interactions with shape=(len(idxs_i),).

Source code in smee/potentials/nonbonded.py
def prepare_lrc_types(\n    system: smee.TensorSystem,\n    potential: smee.TensorPotential,\n) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:\n    \"\"\"Finds the unique vdW interactions present in a system, ready to use\n    for computing the long range dispersion correction.\n\n    Args:\n        system: The system to prepare the types for.\n        potential: The potential to prepare the types for.\n\n    Returns:\n        Two tensors containing the i and j indices into ``potential.paramaters`` of\n        each unique interaction parameter excluding ``i==j``, the number of ``ii``\n        interactions with ``shape=(n_params,)``, and the numbers of ``ij`` interactions\n        with ``shape=(len(idxs_i),)``.\n    \"\"\"\n    n_by_type = collections.defaultdict(int)\n\n    for topology, n_copies in zip(system.topologies, system.n_copies, strict=True):\n        parameter_counts = topology.parameters[\"vdW\"].assignment_matrix.abs().sum(dim=0)\n\n        for key, count in zip(potential.parameter_keys, parameter_counts, strict=True):\n            n_by_type[key] += count.item() * n_copies\n\n    counts = smee.utils.tensor_like(\n        [n_by_type[key] for key in potential.parameter_keys], potential.parameters\n    )\n\n    n_ii_interactions = (counts * (counts + 1.0)) / 2.0\n\n    idxs_i, idxs_j = torch.triu_indices(len(counts), len(counts), 1)\n    n_ij_interactions = counts[idxs_i] * counts[idxs_j]\n\n    idxs_ii = torch.arange(len(counts))\n\n    idxs_i = torch.cat([idxs_i, idxs_ii])\n    idxs_j = torch.cat([idxs_j, idxs_ii])\n\n    n_ij_interactions = torch.cat([n_ij_interactions, n_ii_interactions])\n\n    return idxs_i, idxs_j, n_ij_interactions\n
"},{"location":"reference/potentials/nonbonded/#smee.potentials.nonbonded.lorentz_berthelot","title":"lorentz_berthelot","text":"
lorentz_berthelot(\n    epsilon_a: Tensor,\n    epsilon_b: Tensor,\n    sigma_a: Tensor,\n    sigma_b: Tensor,\n) -> tuple[Tensor, Tensor]\n

Computes the Lorentz-Berthelot combination rules for the given parameters.

Notes

A 'safe' geometric mean is used to avoid NaNs when the parameters are zero. This will yield non-analytic gradients in some cases.

Parameters:

  • epsilon_a (Tensor) \u2013

    The epsilon [kcal / mol] values of the first particle in each pair with shape=(n_pairs, 1).

  • epsilon_b (Tensor) \u2013

    The epsilon [kcal / mol] values of the second particle in each pair with shape=(n_pairs, 1).

  • sigma_a (Tensor) \u2013

    The sigma [kcal / mol] values of the first particle in each pair with shape=(n_pairs, 1).

  • sigma_b (Tensor) \u2013

    The sigma [kcal / mol] values of the second particle in each pair with shape=(n_pairs, 1).

Returns:

  • tuple[Tensor, Tensor] \u2013

    The epsilon [kcal / mol] and sigma [\u00c5] values of each pair, each with shape=(n_pairs, 1).

Source code in smee/potentials/nonbonded.py
def lorentz_berthelot(\n    epsilon_a: torch.Tensor,\n    epsilon_b: torch.Tensor,\n    sigma_a: torch.Tensor,\n    sigma_b: torch.Tensor,\n) -> tuple[torch.Tensor, torch.Tensor]:\n    \"\"\"Computes the Lorentz-Berthelot combination rules for the given parameters.\n\n    Notes:\n        A 'safe' geometric mean is used to avoid NaNs when the parameters are zero.\n        This will yield non-analytic gradients in some cases.\n\n    Args:\n        epsilon_a: The epsilon [kcal / mol] values of the first particle in each pair\n            with ``shape=(n_pairs, 1)``.\n        epsilon_b: The epsilon [kcal / mol] values of the second particle in each pair\n            with ``shape=(n_pairs, 1)``.\n        sigma_a: The sigma [kcal / mol] values of the first particle in each pair\n            with ``shape=(n_pairs, 1)``.\n        sigma_b: The sigma [kcal / mol] values of the second particle in each pair\n            with ``shape=(n_pairs, 1)``.\n\n    Returns:\n        The epsilon [kcal / mol] and sigma [\u00c5] values of each pair, each with\n        ``shape=(n_pairs, 1)``.\n    \"\"\"\n    return smee.utils.geometric_mean(epsilon_a, epsilon_b), 0.5 * (sigma_a + sigma_b)\n
"},{"location":"reference/potentials/nonbonded/#smee.potentials.nonbonded.compute_lj_energy","title":"compute_lj_energy","text":"
compute_lj_energy(\n    system: TensorSystem,\n    potential: TensorPotential,\n    conformer: Tensor,\n    box_vectors: Tensor | None = None,\n    pairwise: PairwiseDistances | None = None,\n) -> Tensor\n

Computes the potential energy [kcal / mol] of the vdW interactions using the standard Lennard-Jones potential.

Notes
  • No cutoff / switching function will be applied if the system is not periodic.
  • A switching function will only be applied if the potential has a switch_width attribute.

Parameters:

  • system (TensorSystem) \u2013

    The system to compute the energy for.

  • potential (TensorPotential) \u2013

    The potential energy function to evaluate.

  • conformer (Tensor) \u2013

    The conformer [\u00c5] to evaluate the potential at with shape=(n_confs, n_particles, 3) or shape=(n_particles, 3).

  • box_vectors (Tensor | None, default: None ) \u2013

    The box vectors [\u00c5] of the system with shape=(n_confs, 3, 3) or shape=(3, 3) if the system is periodic, or None otherwise.

  • pairwise (PairwiseDistances | None, default: None ) \u2013

    The pre-computed pairwise distances between each pair of particles in the system. If none, these will be computed within the function.

Returns:

  • Tensor \u2013

    The computed potential energy [kcal / mol] with shape=(n_confs,) if the input conformer has a batch dimension, or shape=() otherwise.

Source code in smee/potentials/nonbonded.py
@smee.potentials.potential_energy_fn(smee.PotentialType.VDW, smee.EnergyFn.VDW_LJ)\ndef compute_lj_energy(\n    system: smee.TensorSystem,\n    potential: smee.TensorPotential,\n    conformer: torch.Tensor,\n    box_vectors: torch.Tensor | None = None,\n    pairwise: PairwiseDistances | None = None,\n) -> torch.Tensor:\n    \"\"\"Computes the potential energy [kcal / mol] of the vdW interactions using the\n    standard Lennard-Jones potential.\n\n    Notes:\n        * No cutoff / switching function will be applied if the system is not\n          periodic.\n        * A switching function will only be applied if the potential has a\n          ``switch_width`` attribute.\n\n    Args:\n        system: The system to compute the energy for.\n        potential: The potential energy function to evaluate.\n        conformer: The conformer [\u00c5] to evaluate the potential at with\n            ``shape=(n_confs, n_particles, 3)`` or ``shape=(n_particles, 3)``.\n        box_vectors: The box vectors [\u00c5] of the system with ``shape=(n_confs, 3, 3)``\n            or ``shape=(3, 3)`` if the system is periodic, or ``None`` otherwise.\n        pairwise: The pre-computed pairwise distances between each pair of particles\n            in the system. If none, these will be computed within the function.\n\n    Returns:\n        The computed potential energy [kcal / mol] with ``shape=(n_confs,)`` if the\n        input conformer has a batch dimension, or ``shape=()`` otherwise.\n    \"\"\"\n\n    box_vectors = None if not system.is_periodic else box_vectors\n\n    cutoff = potential.attributes[potential.attribute_cols.index(smee.CUTOFF_ATTRIBUTE)]\n\n    pairwise = (\n        pairwise\n        if pairwise is not None\n        else compute_pairwise(system, conformer, box_vectors, cutoff)\n    )\n\n    if system.is_periodic and not torch.isclose(pairwise.cutoff, cutoff):\n        raise ValueError(\"the pairwise cutoff does not match the potential.\")\n\n    parameters = smee.potentials.broadcast_parameters(system, potential)\n    pair_scales = compute_pairwise_scales(system, potential)\n\n    pairs_1d = smee.utils.to_upper_tri_idx(\n        pairwise.idxs[:, 0], pairwise.idxs[:, 1], len(parameters)\n    )\n    pair_scales = pair_scales[pairs_1d]\n\n    eps_column = potential.parameter_cols.index(\"epsilon\")\n    sig_column = potential.parameter_cols.index(\"sigma\")\n\n    eps, sig = lorentz_berthelot(\n        parameters[pairwise.idxs[:, 0], eps_column],\n        parameters[pairwise.idxs[:, 1], eps_column],\n        parameters[pairwise.idxs[:, 0], sig_column],\n        parameters[pairwise.idxs[:, 1], sig_column],\n    )\n\n    if potential.exceptions is not None:\n        exception_idxs, exceptions = smee.potentials.broadcast_exceptions(\n            system, potential, pairwise.idxs[:, 0], pairwise.idxs[:, 1]\n        )\n\n        eps, sig = eps.clone(), sig.clone()  # prevent in-place modification\n\n        eps[exception_idxs] = exceptions[:, eps_column]\n        sig[exception_idxs] = exceptions[:, sig_column]\n\n    x = (sig / pairwise.distances) ** 6\n    energies = pair_scales * 4.0 * eps * (x * (x - 1.0))\n\n    if not system.is_periodic:\n        return energies.sum(-1)\n\n    switch_fn, switch_width = _compute_switch_fn(potential, pairwise)\n    energies *= switch_fn\n\n    energy = energies.sum(-1)\n    energy += _compute_lj_lrc(\n        system,\n        potential.to(precision=\"double\"),\n        switch_width.double(),\n        pairwise.cutoff.double(),\n        torch.det(box_vectors),\n    )\n\n    return energy\n
"},{"location":"reference/potentials/nonbonded/#smee.potentials.nonbonded.compute_dexp_energy","title":"compute_dexp_energy","text":"
compute_dexp_energy(\n    system: TensorSystem,\n    potential: TensorPotential,\n    conformer: Tensor,\n    box_vectors: Tensor | None = None,\n    pairwise: PairwiseDistances | None = None,\n) -> Tensor\n

Compute the potential energy [kcal / mol] of the vdW interactions using the double-exponential potential.

Notes
  • No cutoff function will be applied if the system is not periodic.

Parameters:

  • system (TensorSystem) \u2013

    The system to compute the energy for.

  • potential (TensorPotential) \u2013

    The potential energy function to evaluate.

  • conformer (Tensor) \u2013

    The conformer [\u00c5] to evaluate the potential at with shape=(n_confs, n_particles, 3) or shape=(n_particles, 3).

  • box_vectors (Tensor | None, default: None ) \u2013

    The box vectors [\u00c5] of the system with shape=(n_confs, 3, 3) or shape=(3, 3) if the system is periodic, or None otherwise.

  • pairwise (PairwiseDistances | None, default: None ) \u2013

    Pre-computed distances between each pair of particles in the system.

Returns:

  • Tensor \u2013

    The evaluated potential energy [kcal / mol].

Source code in smee/potentials/nonbonded.py
@smee.potentials.potential_energy_fn(smee.PotentialType.VDW, smee.EnergyFn.VDW_DEXP)\ndef compute_dexp_energy(\n    system: smee.TensorSystem,\n    potential: smee.TensorPotential,\n    conformer: torch.Tensor,\n    box_vectors: torch.Tensor | None = None,\n    pairwise: PairwiseDistances | None = None,\n) -> torch.Tensor:\n    \"\"\"Compute the potential energy [kcal / mol] of the vdW interactions using the\n    double-exponential potential.\n\n    Notes:\n        * No cutoff function will be applied if the system is not periodic.\n\n    Args:\n        system: The system to compute the energy for.\n        potential: The potential energy function to evaluate.\n        conformer: The conformer [\u00c5] to evaluate the potential at with\n            ``shape=(n_confs, n_particles, 3)`` or ``shape=(n_particles, 3)``.\n        box_vectors: The box vectors [\u00c5] of the system with ``shape=(n_confs, 3, 3)``\n            or ``shape=(3, 3)`` if the system is periodic, or ``None`` otherwise.\n        pairwise: Pre-computed distances between each pair of particles\n            in the system.\n\n    Returns:\n        The evaluated potential energy [kcal / mol].\n    \"\"\"\n    box_vectors = None if not system.is_periodic else box_vectors\n\n    cutoff = potential.attributes[potential.attribute_cols.index(smee.CUTOFF_ATTRIBUTE)]\n\n    pairwise = (\n        pairwise\n        if pairwise is not None\n        else compute_pairwise(system, conformer, box_vectors, cutoff)\n    )\n\n    if system.is_periodic and not torch.isclose(pairwise.cutoff, cutoff):\n        raise ValueError(\"the pairwise cutoff does not match the potential.\")\n\n    parameters = smee.potentials.broadcast_parameters(system, potential)\n    pair_scales = compute_pairwise_scales(system, potential)\n\n    pairs_1d = smee.utils.to_upper_tri_idx(\n        pairwise.idxs[:, 0], pairwise.idxs[:, 1], len(parameters)\n    )\n    pair_scales = pair_scales[pairs_1d]\n\n    eps_column = potential.parameter_cols.index(\"epsilon\")\n    r_min_column = potential.parameter_cols.index(\"r_min\")\n\n    eps, r_min = smee.potentials.nonbonded.lorentz_berthelot(\n        parameters[pairwise.idxs[:, 0], eps_column],\n        parameters[pairwise.idxs[:, 1], eps_column],\n        parameters[pairwise.idxs[:, 0], r_min_column],\n        parameters[pairwise.idxs[:, 1], r_min_column],\n    )\n\n    if potential.exceptions is not None:\n        exception_idxs, exceptions = smee.potentials.broadcast_exceptions(\n            system, potential, pairwise.idxs[:, 0], pairwise.idxs[:, 1]\n        )\n\n        eps, r_min = eps.clone(), r_min.clone()  # prevent in-place modification\n\n        eps[exception_idxs] = exceptions[:, eps_column]\n        r_min[exception_idxs] = exceptions[:, r_min_column]\n\n    alpha = potential.attributes[potential.attribute_cols.index(\"alpha\")]\n    beta = potential.attributes[potential.attribute_cols.index(\"beta\")]\n\n    x = pairwise.distances / r_min\n\n    energies_repulsion = beta / (alpha - beta) * torch.exp(alpha * (1.0 - x))\n    energies_attraction = alpha / (alpha - beta) * torch.exp(beta * (1.0 - x))\n\n    energies = pair_scales * eps * (energies_repulsion - energies_attraction)\n\n    if not system.is_periodic:\n        return energies.sum(-1)\n\n    switch_fn, switch_width = _compute_switch_fn(potential, pairwise)\n    energies *= switch_fn\n\n    energy = energies.sum(-1)\n\n    energy += _compute_dexp_lrc(\n        system,\n        potential.to(precision=\"double\"),\n        switch_width.double(),\n        pairwise.cutoff.double(),\n        torch.det(box_vectors),\n    )\n\n    return energy\n
"},{"location":"reference/potentials/nonbonded/#smee.potentials.nonbonded.compute_coulomb_energy","title":"compute_coulomb_energy","text":"
compute_coulomb_energy(\n    system: TensorSystem,\n    potential: TensorPotential,\n    conformer: Tensor,\n    box_vectors: Tensor | None = None,\n    pairwise: PairwiseDistances | None = None,\n) -> Tensor\n

Computes the potential energy [kcal / mol] of the electrostatic interactions using the Coulomb potential.

Notes
  • No cutoff will be applied if the system is not periodic.
  • PME will be used to compute the energy if the system is periodic.

Parameters:

  • system (TensorSystem) \u2013

    The system to compute the energy for.

  • potential (TensorPotential) \u2013

    The potential energy function to evaluate.

  • conformer (Tensor) \u2013

    The conformer [\u00c5] to evaluate the potential at with shape=(n_confs, n_particles, 3) or shape=(n_particles, 3).

  • box_vectors (Tensor | None, default: None ) \u2013

    The box vectors [\u00c5] of the system with shape=(n_confs, 3, 3) or shape=(3, 3) if the system is periodic, or None otherwise.

  • pairwise (PairwiseDistances | None, default: None ) \u2013

    The pre-computed pairwise distances between each pair of particles in the system. If none, these will be computed within the function.

Returns:

  • Tensor \u2013

    The computed potential energy [kcal / mol] with shape=(n_confs,) if the input conformer has a batch dimension, or shape=() otherwise.

Source code in smee/potentials/nonbonded.py
@smee.potentials.potential_energy_fn(\n    smee.PotentialType.ELECTROSTATICS, smee.EnergyFn.COULOMB\n)\ndef compute_coulomb_energy(\n    system: smee.TensorSystem,\n    potential: smee.TensorPotential,\n    conformer: torch.Tensor,\n    box_vectors: torch.Tensor | None = None,\n    pairwise: PairwiseDistances | None = None,\n) -> torch.Tensor:\n    \"\"\"Computes the potential energy [kcal / mol] of the electrostatic interactions\n    using the Coulomb potential.\n\n    Notes:\n        * No cutoff will be applied if the system is not periodic.\n        * PME will be used to compute the energy if the system is periodic.\n\n    Args:\n        system: The system to compute the energy for.\n        potential: The potential energy function to evaluate.\n        conformer: The conformer [\u00c5] to evaluate the potential at with\n            ``shape=(n_confs, n_particles, 3)`` or ``shape=(n_particles, 3)``.\n        box_vectors: The box vectors [\u00c5] of the system with ``shape=(n_confs, 3, 3)``\n            or ``shape=(3, 3)`` if the system is periodic, or ``None`` otherwise.\n        pairwise: The pre-computed pairwise distances between each pair of particles\n            in the system. If none, these will be computed within the function.\n\n    Returns:\n        The computed potential energy [kcal / mol] with ``shape=(n_confs,)`` if the\n        input conformer has a batch dimension, or ``shape=()`` otherwise.\n    \"\"\"\n\n    box_vectors = None if not system.is_periodic else box_vectors\n\n    cutoff = potential.attributes[potential.attribute_cols.index(smee.CUTOFF_ATTRIBUTE)]\n\n    pairwise = (\n        pairwise\n        if pairwise is not None\n        else compute_pairwise(system, conformer, box_vectors, cutoff)\n    )\n\n    if system.is_periodic and not torch.isclose(pairwise.cutoff, cutoff):\n        raise ValueError(\"the distance cutoff does not match the potential.\")\n\n    if potential.exceptions is not None:\n        raise NotImplementedError(\"exceptions are not supported for charges.\")\n\n    if system.is_periodic:\n        return _compute_coulomb_energy_periodic(\n            system, conformer, box_vectors, potential, pairwise\n        )\n    else:\n        return _compute_coulomb_energy_non_periodic(system, potential, pairwise)\n
"},{"location":"reference/potentials/valence/","title":" valence","text":""},{"location":"reference/potentials/valence/#smee.potentials.valence","title":"valence","text":"

Valence potential energy functions.

Functions:

  • compute_harmonic_bond_energy \u2013

    Compute the potential energy [kcal / mol] of a set of bonds for a given

  • compute_harmonic_angle_energy \u2013

    Compute the potential energy [kcal / mol] of a set of valence angles for a given

  • compute_cosine_proper_torsion_energy \u2013

    Compute the potential energy [kcal / mol] of a set of proper torsions

  • compute_cosine_improper_torsion_energy \u2013

    Compute the potential energy [kcal / mol] of a set of improper torsions

"},{"location":"reference/potentials/valence/#smee.potentials.valence.compute_harmonic_bond_energy","title":"compute_harmonic_bond_energy","text":"
compute_harmonic_bond_energy(\n    system: TensorSystem,\n    potential: TensorPotential,\n    conformer: Tensor,\n) -> Tensor\n

Compute the potential energy [kcal / mol] of a set of bonds for a given conformer using a harmonic potential of the form 1/2 * k * (r - length) ** 2

Parameters:

  • system (TensorSystem) \u2013

    The system to compute the energy for.

  • potential (TensorPotential) \u2013

    The potential energy function to evaluate.

  • conformer (Tensor) \u2013

    The conformer [\u00c5] to evaluate the potential at with shape=(n_confs, n_particles, 3) or shape=(n_particles, 3).

Returns:

  • Tensor \u2013

    The computed potential energy [kcal / mol].

Source code in smee/potentials/valence.py
@smee.potentials.potential_energy_fn(\n    smee.PotentialType.BONDS, smee.EnergyFn.BOND_HARMONIC\n)\ndef compute_harmonic_bond_energy(\n    system: smee.TensorSystem,\n    potential: smee.TensorPotential,\n    conformer: torch.Tensor,\n) -> torch.Tensor:\n    \"\"\"Compute the potential energy [kcal / mol] of a set of bonds for a given\n    conformer using a harmonic potential of the form ``1/2 * k * (r - length) ** 2``\n\n    Args:\n        system: The system to compute the energy for.\n        potential: The potential energy function to evaluate.\n        conformer: The conformer [\u00c5] to evaluate the potential at with\n            ``shape=(n_confs, n_particles, 3)`` or ``shape=(n_particles, 3)``.\n\n    Returns:\n        The computed potential energy [kcal / mol].\n    \"\"\"\n\n    parameters = smee.potentials.broadcast_parameters(system, potential)\n    particle_idxs = smee.potentials.broadcast_idxs(system, potential)\n\n    _, distances = smee.geometry.compute_bond_vectors(conformer, particle_idxs)\n\n    k = parameters[:, potential.parameter_cols.index(\"k\")]\n    length = parameters[:, potential.parameter_cols.index(\"length\")]\n\n    return (0.5 * k * (distances - length) ** 2).sum(-1)\n
"},{"location":"reference/potentials/valence/#smee.potentials.valence.compute_harmonic_angle_energy","title":"compute_harmonic_angle_energy","text":"
compute_harmonic_angle_energy(\n    system: TensorSystem,\n    potential: TensorPotential,\n    conformer: Tensor,\n) -> Tensor\n

Compute the potential energy [kcal / mol] of a set of valence angles for a given conformer using a harmonic potential of the form 1/2 * k * (theta - angle) ** 2

Parameters:

  • system (TensorSystem) \u2013

    The system to compute the energy for.

  • potential (TensorPotential) \u2013

    The potential energy function to evaluate.

  • conformer (Tensor) \u2013

    The conformer [\u00c5] to evaluate the potential at with shape=(n_confs, n_particles, 3) or shape=(n_particles, 3).

Returns:

  • Tensor \u2013

    The computed potential energy [kcal / mol].

Source code in smee/potentials/valence.py
@smee.potentials.potential_energy_fn(\n    smee.PotentialType.ANGLES, smee.EnergyFn.ANGLE_HARMONIC\n)\ndef compute_harmonic_angle_energy(\n    system: smee.TensorSystem,\n    potential: smee.TensorPotential,\n    conformer: torch.Tensor,\n) -> torch.Tensor:\n    \"\"\"Compute the potential energy [kcal / mol] of a set of valence angles for a given\n    conformer using a harmonic potential of the form ``1/2 * k * (theta - angle) ** 2``\n\n    Args:\n        system: The system to compute the energy for.\n        potential: The potential energy function to evaluate.\n        conformer: The conformer [\u00c5] to evaluate the potential at with\n            ``shape=(n_confs, n_particles, 3)`` or ``shape=(n_particles, 3)``.\n\n    Returns:\n        The computed potential energy [kcal / mol].\n    \"\"\"\n\n    parameters = smee.potentials.broadcast_parameters(system, potential)\n    particle_idxs = smee.potentials.broadcast_idxs(system, potential)\n\n    theta = smee.geometry.compute_angles(conformer, particle_idxs)\n\n    k = parameters[:, potential.parameter_cols.index(\"k\")]\n    angle = parameters[:, potential.parameter_cols.index(\"angle\")]\n\n    return (0.5 * k * (theta - angle) ** 2).sum(-1)\n
"},{"location":"reference/potentials/valence/#smee.potentials.valence.compute_cosine_proper_torsion_energy","title":"compute_cosine_proper_torsion_energy","text":"
compute_cosine_proper_torsion_energy(\n    system: TensorSystem,\n    potential: TensorPotential,\n    conformer: Tensor,\n) -> Tensor\n

Compute the potential energy [kcal / mol] of a set of proper torsions for a given conformer using a cosine potential of the form:

k*(1+cos(periodicity*theta-phase))

Parameters:

  • system (TensorSystem) \u2013

    The system to compute the energy for.

  • potential (TensorPotential) \u2013

    The potential energy function to evaluate.

  • conformer (Tensor) \u2013

    The conformer [\u00c5] to evaluate the potential at with shape=(n_confs, n_particles, 3) or shape=(n_particles, 3).

Returns:

  • Tensor \u2013

    The computed potential energy [kcal / mol].

Source code in smee/potentials/valence.py
@smee.potentials.potential_energy_fn(\n    smee.PotentialType.PROPER_TORSIONS, smee.EnergyFn.TORSION_COSINE\n)\ndef compute_cosine_proper_torsion_energy(\n    system: smee.TensorSystem,\n    potential: smee.TensorPotential,\n    conformer: torch.Tensor,\n) -> torch.Tensor:\n    \"\"\"Compute the potential energy [kcal / mol] of a set of proper torsions\n    for a given conformer using a cosine potential of the form:\n\n    `k*(1+cos(periodicity*theta-phase))`\n\n    Args:\n        system: The system to compute the energy for.\n        potential: The potential energy function to evaluate.\n        conformer: The conformer [\u00c5] to evaluate the potential at with\n            ``shape=(n_confs, n_particles, 3)`` or ``shape=(n_particles, 3)``.\n\n    Returns:\n        The computed potential energy [kcal / mol].\n    \"\"\"\n    return _compute_cosine_torsion_energy(system, potential, conformer)\n
"},{"location":"reference/potentials/valence/#smee.potentials.valence.compute_cosine_improper_torsion_energy","title":"compute_cosine_improper_torsion_energy","text":"
compute_cosine_improper_torsion_energy(\n    system: TensorSystem,\n    potential: TensorPotential,\n    conformer: Tensor,\n) -> Tensor\n

Compute the potential energy [kcal / mol] of a set of improper torsions for a given conformer using a cosine potential of the form:

k*(1+cos(periodicity*theta-phase))

Parameters:

  • system (TensorSystem) \u2013

    The system to compute the energy for.

  • potential (TensorPotential) \u2013

    The potential energy function to evaluate.

  • conformer (Tensor) \u2013

    The conformer [\u00c5] to evaluate the potential at with shape=(n_confs, n_particles, 3) or shape=(n_particles, 3).

Returns:

  • Tensor \u2013

    The computed potential energy [kcal / mol].

Source code in smee/potentials/valence.py
@smee.potentials.potential_energy_fn(\n    smee.PotentialType.IMPROPER_TORSIONS, smee.EnergyFn.TORSION_COSINE\n)\ndef compute_cosine_improper_torsion_energy(\n    system: smee.TensorSystem,\n    potential: smee.TensorPotential,\n    conformer: torch.Tensor,\n) -> torch.Tensor:\n    \"\"\"Compute the potential energy [kcal / mol] of a set of improper torsions\n    for a given conformer using a cosine potential of the form:\n\n    `k*(1+cos(periodicity*theta-phase))`\n\n    Args:\n        system: The system to compute the energy for.\n        potential: The potential energy function to evaluate.\n        conformer: The conformer [\u00c5] to evaluate the potential at with\n            ``shape=(n_confs, n_particles, 3)`` or ``shape=(n_particles, 3)``.\n\n    Returns:\n        The computed potential energy [kcal / mol].\n    \"\"\"\n    return _compute_cosine_torsion_energy(system, potential, conformer)\n
"}]} \ No newline at end of file +{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Overview","text":"SMIRNOFF Energy Evaluations

Differentiably evaluate energies of molecules using SMIRNOFF force fields

The smee framework aims to offer a simple API for differentiably evaluating the energy of SMIRNOFF force fields applied to molecules using pytorch.

The package currently supports evaluating the energy of force fields that contain:

  • Bonds, Angles, ProperTorsions and ImproperTorsions
  • vdW, Electrostatics, ToolkitAM1BCC, LibraryCharges
  • VirtualSites

parameter handlers in addition to limited support for registering custom handlers.

It further supports a number of functional forms included in smirnoff-plugins, namely:

  • DoubleExponential
"},{"location":"#installation","title":"Installation","text":"

This package can be installed using conda (or mamba, a faster version of conda):

mamba install -c conda-forge smee\n

The example notebooks further require you install jupyter, nglview, and smirnoff-plugins:

mamba install -c conda-forge jupyter nglview \"smirnoff-plugins >=0.0.4\"\n
"},{"location":"#getting-started","title":"Getting Started","text":"

To get started, see the examples.

"},{"location":"#copyright","title":"Copyright","text":"

Copyright (c) 2023, Simon Boothroyd

"},{"location":"development/","title":"Development","text":"

To create a development environment, you must have mamba installed.

A development conda environment can be created and activated with:

make env\nconda activate smee\n

To format the codebase:

make format\n

To run the unit tests:

make test\n

To serve the documentation locally:

mkdocs serve\n
"},{"location":"examples/","title":"Examples","text":"

This directory contains a number of examples of how to use smee. They currently include:

  • Evaluating the energy of a water dimer with virtual sites
  • Minimizing the conformer of a molecule
  • Computing the gradient of the energy w.r.t. force field parameters
  • Registering custom parameter handlers
  • Differentiably compute ensemble averages from MD simulations
"},{"location":"examples/compute-energy/","title":"Computing energies using","text":"In\u00a0[1]: Copied!
import openff.toolkit\nimport openff.units\nimport torch\n\nwater = openff.toolkit.Molecule.from_smiles(\"O\")\nwater.generate_conformers(n_conformers=1)\n\nimport openff.interchange\n\ntip4p_interchange = openff.interchange.Interchange.from_smirnoff(\n    openff.toolkit.ForceField(\"tip4p_fb.offxml\"), water.to_topology()\n)\n
import openff.toolkit import openff.units import torch water = openff.toolkit.Molecule.from_smiles(\"O\") water.generate_conformers(n_conformers=1) import openff.interchange tip4p_interchange = openff.interchange.Interchange.from_smirnoff( openff.toolkit.ForceField(\"tip4p_fb.offxml\"), water.to_topology() )
 

The interchange must then be mapped into collections of tensors:

In\u00a0[2]: Copied!
import smee.converters\n\ntip4p_tensor_ff, [tip4p_topology] = smee.converters.convert_interchange(\n    tip4p_interchange\n)\n
import smee.converters tip4p_tensor_ff, [tip4p_topology] = smee.converters.convert_interchange( tip4p_interchange )

Note: The convert_interchange function can take either a single interchange object, or a list of multiple.

These tensors are returned as:

  • a smee.ff.TensorForceField oject: this stores the original values of the force field parameters in tensor form.
  • a list of smee.ff.TensorTopology objects: each 'topology' will store basic information about each input molecule, in addition to matrices defining which parameters were assigned to which elements (e.g. bonds, angles, torsions) and any virtual sites that were added.

To define fuller systems (e.g. dimers, condensed phase systems etc), we need to wrap the topologies in a system object:

In\u00a0[3]: Copied!
tip4p_dimer = smee.TensorSystem([tip4p_topology], n_copies=[2], is_periodic=False)\n
tip4p_dimer = smee.TensorSystem([tip4p_topology], n_copies=[2], is_periodic=False)

This mirrors how GROMACS topology files are defined, whereby individual species are parameterised and then a count of how many of each should be present is specified. This avoids the need to store multiple copies of the same parameters for each copy of a given molecule.

We can then define the coordinates of our dimers:

In\u00a0[4]: Copied!
water_conformer = torch.tensor(water.conformers[0].m_as(openff.units.unit.angstrom))\ntip4p_conformer = smee.add_v_site_coords(\n    tip4p_topology.v_sites, water_conformer, tip4p_tensor_ff\n)\n\ntip4p_conformers = torch.stack(\n    [\n        torch.vstack([tip4p_conformer, tip4p_conformer + torch.tensor(1.5 + i * 0.05)])\n        for i in range(40)\n    ]\n)\ntip4p_conformers.shape\n
water_conformer = torch.tensor(water.conformers[0].m_as(openff.units.unit.angstrom)) tip4p_conformer = smee.add_v_site_coords( tip4p_topology.v_sites, water_conformer, tip4p_tensor_ff ) tip4p_conformers = torch.stack( [ torch.vstack([tip4p_conformer, tip4p_conformer + torch.tensor(1.5 + i * 0.05)]) for i in range(40) ] ) tip4p_conformers.shape Out[4]:
torch.Size([40, 8, 3])

For simplicity, we have created 40 dimer conformers, with a separation ranging from 1.5-3.5 \u00c5 by crudely shifting our initial conformer along the x-axis.

Note: Conformers should either be tensor with a shape of (n_particles, 3) or (n_conformers, n_particles, 3), and units of \u00c5._

From the shape we can clearly see there are 40 conformers, each with coordinates for 8 particles (i.e. 3 for each water molecule + 1 virtual site on each).

The energy each dimer can then be directly evaluated and plotted

In\u00a0[5]: Copied!
tip4p_energies = smee.compute_energy(tip4p_dimer, tip4p_tensor_ff, tip4p_conformers)\n\nfrom matplotlib import pyplot\n\npyplot.plot(tip4p_energies, label=\"tip4p\")\npyplot.ylabel(\"Energy [kcal / mol]\");\n
tip4p_energies = smee.compute_energy(tip4p_dimer, tip4p_tensor_ff, tip4p_conformers) from matplotlib import pyplot pyplot.plot(tip4p_energies, label=\"tip4p\") pyplot.ylabel(\"Energy [kcal / mol]\");

The above can easily be repeated using the TIP3P water model, allowing us to compare the dimer energy curves.

In\u00a0[6]: Copied!
tip3p_interchange = openff.interchange.Interchange.from_smirnoff(\n    openff.toolkit.ForceField(\"tip3p.offxml\"), water.to_topology()\n)\n\ntip3p_tensor_ff, [tip3p_tensor_topology] = smee.converters.convert_interchange(\n    tip3p_interchange\n)\ntip3p_dimer = smee.TensorSystem(\n    [tip3p_tensor_topology], n_copies=[2], is_periodic=False\n)\n\ntip3p_conformers = torch.stack(\n    [\n        torch.vstack([water_conformer, water_conformer + torch.tensor(1.5 + i * 0.05)])\n        for i in range(40)\n    ]\n)\n\ntip3p_energies = smee.compute_energy(tip3p_dimer, tip3p_tensor_ff, tip3p_conformers)\n\npyplot.plot(tip3p_energies, label=\"tip3p\")\npyplot.plot(tip4p_energies, label=\"tip4p\")\n\npyplot.ylabel(\"Energy [kcal / mol]\")\npyplot.legend();\n
tip3p_interchange = openff.interchange.Interchange.from_smirnoff( openff.toolkit.ForceField(\"tip3p.offxml\"), water.to_topology() ) tip3p_tensor_ff, [tip3p_tensor_topology] = smee.converters.convert_interchange( tip3p_interchange ) tip3p_dimer = smee.TensorSystem( [tip3p_tensor_topology], n_copies=[2], is_periodic=False ) tip3p_conformers = torch.stack( [ torch.vstack([water_conformer, water_conformer + torch.tensor(1.5 + i * 0.05)]) for i in range(40) ] ) tip3p_energies = smee.compute_energy(tip3p_dimer, tip3p_tensor_ff, tip3p_conformers) pyplot.plot(tip3p_energies, label=\"tip3p\") pyplot.plot(tip4p_energies, label=\"tip4p\") pyplot.ylabel(\"Energy [kcal / mol]\") pyplot.legend();

A further advantage of storing our applied force field parameters in smee form is the ability to compute only the energy due to a particular handler (e.g. only LJ energies).

In\u00a0[7]: Copied!
tip3p_lj_energies = smee.compute_energy_potential(\n    tip3p_dimer, tip3p_tensor_ff.potentials_by_type[\"vdW\"], tip3p_conformers\n)\ntip4p_lj_energies = smee.compute_energy_potential(\n    tip4p_dimer, tip4p_tensor_ff.potentials_by_type[\"vdW\"], tip4p_conformers\n)\n\npyplot.plot(tip3p_lj_energies, label=\"tip3p\")\npyplot.plot(tip4p_lj_energies, label=\"tip4p\")\n\npyplot.ylabel(\"LJ Energy [kcal / mol]\");\n
tip3p_lj_energies = smee.compute_energy_potential( tip3p_dimer, tip3p_tensor_ff.potentials_by_type[\"vdW\"], tip3p_conformers ) tip4p_lj_energies = smee.compute_energy_potential( tip4p_dimer, tip4p_tensor_ff.potentials_by_type[\"vdW\"], tip4p_conformers ) pyplot.plot(tip3p_lj_energies, label=\"tip3p\") pyplot.plot(tip4p_lj_energies, label=\"tip4p\") pyplot.ylabel(\"LJ Energy [kcal / mol]\");"},{"location":"examples/compute-energy/#computing-energies-using-smee","title":"Computing energies using smee\u00b6","text":"

This example will show how the energy of a water dimer (TIP4P-FB and TIP3P) in multiple conformations can be evaluated using smee framework.

We start by creating the objects that will store our parameterized water dimer. First we will create a single water molecule, and parameterize it using OpenFF Interchange:

"},{"location":"examples/conformer-minimization/","title":"Conformer Minimization","text":"In\u00a0[1]: Copied!
import openff.toolkit\nimport openff.units\nimport torch\n\nmolecule = openff.toolkit.Molecule.from_smiles(\"CC(=O)NC1=CC=C(C=C1)O\")\nmolecule.generate_conformers(n_conformers=1)\n\nconformer = torch.tensor(molecule.conformers[0].m_as(openff.units.unit.angstrom)) * 1.10\nconformer.requires_grad = True\n
import openff.toolkit import openff.units import torch molecule = openff.toolkit.Molecule.from_smiles(\"CC(=O)NC1=CC=C(C=C1)O\") molecule.generate_conformers(n_conformers=1) conformer = torch.tensor(molecule.conformers[0].m_as(openff.units.unit.angstrom)) * 1.10 conformer.requires_grad = True

We specify that the gradient of the conformer is required so that we can optimize it using PyTorch.

Parameterize the molecule using OpenFF Interchange and convert it into a PyTorch tensor representation.

In\u00a0[2]: Copied!
import openff.interchange\n\ninterchange = openff.interchange.Interchange.from_smirnoff(\n    openff.toolkit.ForceField(\"openff-2.1.0.offxml\"),\n    molecule.to_topology(),\n)\n\nimport smee.converters\n\nforce_field, [topology] = smee.converters.convert_interchange(interchange)\n
import openff.interchange interchange = openff.interchange.Interchange.from_smirnoff( openff.toolkit.ForceField(\"openff-2.1.0.offxml\"), molecule.to_topology(), ) import smee.converters force_field, [topology] = smee.converters.convert_interchange(interchange)
 

We can minimize the conformer using any of PyTorch's optimizers.

In\u00a0[3]: Copied!
import smee\n\noptimizer = torch.optim.Adam([conformer], lr=0.02)\n\nfor epoch in range(75):\n    energy = smee.compute_energy(topology, force_field, conformer)\n    energy.backward()\n\n    optimizer.step()\n    optimizer.zero_grad()\n\n    if epoch % 5 == 0 or epoch == 74:\n        print(f\"Epoch {epoch}: E={energy.item()} kcal / mol\")\n
import smee optimizer = torch.optim.Adam([conformer], lr=0.02) for epoch in range(75): energy = smee.compute_energy(topology, force_field, conformer) energy.backward() optimizer.step() optimizer.zero_grad() if epoch % 5 == 0 or epoch == 74: print(f\"Epoch {epoch}: E={energy.item()} kcal / mol\")
Epoch 0: E=102.10968017578125 kcal / mol\nEpoch 5: E=7.088213920593262 kcal / mol\nEpoch 10: E=-18.331130981445312 kcal / mol\nEpoch 15: E=-22.182296752929688 kcal / mol\nEpoch 20: E=-30.369152069091797 kcal / mol\nEpoch 25: E=-36.81045150756836 kcal / mol\nEpoch 30: E=-38.517852783203125 kcal / mol\nEpoch 35: E=-40.50505828857422 kcal / mol\nEpoch 40: E=-42.08476257324219 kcal / mol\nEpoch 45: E=-42.19199752807617 kcal / mol\nEpoch 50: E=-42.37827682495117 kcal / mol\nEpoch 55: E=-42.6767692565918 kcal / mol\nEpoch 60: E=-42.799903869628906 kcal / mol\nEpoch 65: E=-42.94251251220703 kcal / mol\nEpoch 70: E=-43.037200927734375 kcal / mol\nEpoch 74: E=-43.084136962890625 kcal / mol\n

We can then re-store the optimized conformer back into the molecule. Here we add the conformer to the molecule's conformer list, but we could also replace the original conformer.

In\u00a0[4]: Copied!
molecule.add_conformer(conformer.detach().numpy() * openff.units.unit.angstrom)\nmolecule.visualize(backend=\"nglview\")\n
molecule.add_conformer(conformer.detach().numpy() * openff.units.unit.angstrom) molecule.visualize(backend=\"nglview\")
NGLWidget(max_frame=1)
"},{"location":"examples/conformer-minimization/#conformer-minimization","title":"Conformer Minimization\u00b6","text":"

This example will show how to optimize a conformer of paracetamol.

Load in a paracetamol molecule, generate a conformer for it, and perturb the conformer to ensure it needs minimization.

"},{"location":"examples/md-simulations/","title":"Ensemble Averages from MD Simulations","text":"In\u00a0[\u00a0]: Copied!
import openff.interchange\nimport openff.toolkit\n\nimport smee.converters\n\ninterchanges = [\n    openff.interchange.Interchange.from_smirnoff(\n        openff.toolkit.ForceField(\"openff-2.0.0.offxml\"),\n        openff.toolkit.Molecule.from_smiles(smiles).to_topology(),\n    )\n    for smiles in (\"CCO\", \"CO\")\n]\n\ntensor_ff, topologies = smee.converters.convert_interchange(interchanges)\n\n# move the force field to the GPU for faster processing of the simulation\n# trajectories - the system and force field must be on the same device.\ntensor_ff = tensor_ff.to(\"cuda\")\n
import openff.interchange import openff.toolkit import smee.converters interchanges = [ openff.interchange.Interchange.from_smirnoff( openff.toolkit.ForceField(\"openff-2.0.0.offxml\"), openff.toolkit.Molecule.from_smiles(smiles).to_topology(), ) for smiles in (\"CCO\", \"CO\") ] tensor_ff, topologies = smee.converters.convert_interchange(interchanges) # move the force field to the GPU for faster processing of the simulation # trajectories - the system and force field must be on the same device. tensor_ff = tensor_ff.to(\"cuda\")

We will also flag that the vdW parameter gradients are required:

In\u00a0[\u00a0]: Copied!
vdw_potential = tensor_ff.potentials_by_type[\"vdW\"]\nvdw_potential.parameters.requires_grad = True\n
vdw_potential = tensor_ff.potentials_by_type[\"vdW\"] vdw_potential.parameters.requires_grad = True

We then define the full simulation boxes that we wish to simulate:

In\u00a0[\u00a0]: Copied!
import smee\n\n# define a periodic box containing 216 ethanol molecules\nsystem_ethanol = smee.TensorSystem([topologies[0]], [216], is_periodic=True)\nsystem_ethanol = system_ethanol.to(\"cuda\")\n# define a periodic box containing 216 methanol molecules\nsystem_methanol = smee.TensorSystem([topologies[1]], [216], True)\nsystem_methanol = system_methanol.to(\"cuda\")\n# define a periodic box containing 128 ethanol molecules and 128 methanol molecules\nsystem_mixture = smee.TensorSystem(topologies, [128, 128], True)\nsystem_mixture = system_mixture.to(\"cuda\")\n
import smee # define a periodic box containing 216 ethanol molecules system_ethanol = smee.TensorSystem([topologies[0]], [216], is_periodic=True) system_ethanol = system_ethanol.to(\"cuda\") # define a periodic box containing 216 methanol molecules system_methanol = smee.TensorSystem([topologies[1]], [216], True) system_methanol = system_methanol.to(\"cuda\") # define a periodic box containing 128 ethanol molecules and 128 methanol molecules system_mixture = smee.TensorSystem(topologies, [128, 128], True) system_mixture = system_mixture.to(\"cuda\")

A tensor system is simply a wrapper around a set of topology objects that define parameters applied to individual molecules, and the number of copies of that topology that should be present similar to GROMACS topologies. The is_periodic flag indicates whether the system should be simulated in a periodic box.

Here we have also moved the systems onto the GPU. This will allow us to much more rapidly compute ensemble averages from the trajectories, but is not required.

We then also must define the simulation protocol that will be used to run the simulations. This consists of a config object that defines how to generate the system coordinates using PACKMOL, the set of energy minimisations /simulations to run as equilibration, and finally the configuration of the production simulation:

In\u00a0[\u00a0]: Copied!
import tempfile\n\nimport openmm.unit\n\nimport smee.mm\n\ntemperature = 298.15 * openmm.unit.kelvin\npressure = 1.0 * openmm.unit.atmosphere\n\nbeta = 1.0 / (openmm.unit.MOLAR_GAS_CONSTANT_R * temperature)\n\n# we can run an arbitrary number of equilibration simulations / minimizations.\n# all generated data will be discarded, but the final coordinates will be used\n# to initialize the production simulation\nequilibrate_config = [\n    smee.mm.MinimizationConfig(),\n    # short NVT equilibration simulation\n    smee.mm.SimulationConfig(\n        temperature=temperature,\n        pressure=None,\n        n_steps=50000,\n        timestep=1.0 * openmm.unit.femtosecond,\n    ),\n    # short NPT equilibration simulation\n    smee.mm.SimulationConfig(\n        temperature=temperature,\n        pressure=pressure,\n        n_steps=50000,\n        timestep=1.0 * openmm.unit.femtosecond,\n    ),\n]\n# long NPT production simulation\nproduction_config = smee.mm.SimulationConfig(\n    temperature=temperature,\n    pressure=pressure,\n    n_steps=500000,\n    timestep=2.0 * openmm.unit.femtosecond,\n)\n
import tempfile import openmm.unit import smee.mm temperature = 298.15 * openmm.unit.kelvin pressure = 1.0 * openmm.unit.atmosphere beta = 1.0 / (openmm.unit.MOLAR_GAS_CONSTANT_R * temperature) # we can run an arbitrary number of equilibration simulations / minimizations. # all generated data will be discarded, but the final coordinates will be used # to initialize the production simulation equilibrate_config = [ smee.mm.MinimizationConfig(), # short NVT equilibration simulation smee.mm.SimulationConfig( temperature=temperature, pressure=None, n_steps=50000, timestep=1.0 * openmm.unit.femtosecond, ), # short NPT equilibration simulation smee.mm.SimulationConfig( temperature=temperature, pressure=pressure, n_steps=50000, timestep=1.0 * openmm.unit.femtosecond, ), ] # long NPT production simulation production_config = smee.mm.SimulationConfig( temperature=temperature, pressure=pressure, n_steps=500000, timestep=2.0 * openmm.unit.femtosecond, )

We will further define a convenience function that will first simulate the system of interest (storing the trajectory in a temporary directory), and then compute ensemble averages over that trajectory:

In\u00a0[\u00a0]: Copied!
import pathlib\n\nimport torch\n\n\ndef compute_ensemble_averages(\n    system: smee.TensorSystem, force_field: smee.TensorForceField\n) -> dict[str, torch.Tensor]:\n    # computing the ensemble averages is a two step process - we first need to run\n    # an MD simulation using the force field making sure to store the coordinates,\n    # box vectors and kinetic energies\n    coords, box_vectors = smee.mm.generate_system_coords(system, force_field)\n\n    interval = 1000\n\n    # save the simulation output every 1000th frame (2 ps) to a temporary file.\n    # we could also save the trajectory more permanently, but as we do nothing\n    # with it after computing the averages in this example, we simply want to\n    # discard it.\n    with (\n        tempfile.NamedTemporaryFile() as tmp_file,\n        smee.mm.tensor_reporter(tmp_file.name, interval, beta, pressure) as reporter,\n    ):\n        smee.mm.simulate(\n            system,\n            force_field,\n            coords,\n            box_vectors,\n            equilibrate_config,\n            production_config,\n            [reporter],\n        )\n\n        # we can then compute the ensemble averages from the trajectory. generating\n        # the trajectory separately from computing the ensemble averages allows us\n        # to run the simulation in parallel with other simulations more easily, without\n        # having to worry about copying gradients between workers / processes.\n        avgs, stds = smee.mm.compute_ensemble_averages(\n            system, force_field, pathlib.Path(tmp_file.name), temperature, pressure\n        )\n        return avgs\n
import pathlib import torch def compute_ensemble_averages( system: smee.TensorSystem, force_field: smee.TensorForceField ) -> dict[str, torch.Tensor]: # computing the ensemble averages is a two step process - we first need to run # an MD simulation using the force field making sure to store the coordinates, # box vectors and kinetic energies coords, box_vectors = smee.mm.generate_system_coords(system, force_field) interval = 1000 # save the simulation output every 1000th frame (2 ps) to a temporary file. # we could also save the trajectory more permanently, but as we do nothing # with it after computing the averages in this example, we simply want to # discard it. with ( tempfile.NamedTemporaryFile() as tmp_file, smee.mm.tensor_reporter(tmp_file.name, interval, beta, pressure) as reporter, ): smee.mm.simulate( system, force_field, coords, box_vectors, equilibrate_config, production_config, [reporter], ) # we can then compute the ensemble averages from the trajectory. generating # the trajectory separately from computing the ensemble averages allows us # to run the simulation in parallel with other simulations more easily, without # having to worry about copying gradients between workers / processes. avgs, stds = smee.mm.compute_ensemble_averages( system, force_field, pathlib.Path(tmp_file.name), temperature, pressure ) return avgs

Computing the ensemble averages is then as simple as:

In\u00a0[\u00a0]: Copied!
# run simulations of each system and compute ensemble averages over the trajectories\n# of the potential energy, volume, and density\nethanol_avgs = compute_ensemble_averages(system_ethanol, tensor_ff)\nmethanol_avgs = compute_ensemble_averages(system_methanol, tensor_ff)\nmixture_avgs = compute_ensemble_averages(system_mixture, tensor_ff)\n
# run simulations of each system and compute ensemble averages over the trajectories # of the potential energy, volume, and density ethanol_avgs = compute_ensemble_averages(system_ethanol, tensor_ff) methanol_avgs = compute_ensemble_averages(system_methanol, tensor_ff) mixture_avgs = compute_ensemble_averages(system_mixture, tensor_ff)

Each of the returned values is a dictionary of ensemble averages computed over the simulated production trajectory. This currently includes the potential energy, volume, and density of the system.

These averages can be used in a loss function

In\u00a0[\u00a0]: Copied!
# define some MOCK data and loss function\nmock_ethanol_density = 0.789  # g/mL\nmock_methanol_density = 0.791  # g/mL\n\nmock_enthalpy_of_mixing = 0.891  # kcal/mol\n\nloss = (ethanol_avgs[\"density\"] - mock_ethanol_density) ** 2\nloss += (methanol_avgs[\"density\"] - mock_methanol_density) ** 2\n\nmixture_enthalpy = mixture_avgs[\"enthalpy\"] / 256\n\nethanol_enthalpy = ethanol_avgs[\"enthalpy\"] / 128\nmethanol_enthalpy = methanol_avgs[\"enthalpy\"] / 128\n\nenthalpy_of_mixing = mixture_enthalpy - (\n    0.5 * ethanol_enthalpy + 0.5 * methanol_enthalpy\n)\nloss += (enthalpy_of_mixing - mock_enthalpy_of_mixing) ** 2\n
# define some MOCK data and loss function mock_ethanol_density = 0.789 # g/mL mock_methanol_density = 0.791 # g/mL mock_enthalpy_of_mixing = 0.891 # kcal/mol loss = (ethanol_avgs[\"density\"] - mock_ethanol_density) ** 2 loss += (methanol_avgs[\"density\"] - mock_methanol_density) ** 2 mixture_enthalpy = mixture_avgs[\"enthalpy\"] / 256 ethanol_enthalpy = ethanol_avgs[\"enthalpy\"] / 128 methanol_enthalpy = methanol_avgs[\"enthalpy\"] / 128 enthalpy_of_mixing = mixture_enthalpy - ( 0.5 * ethanol_enthalpy + 0.5 * methanol_enthalpy ) loss += (enthalpy_of_mixing - mock_enthalpy_of_mixing) ** 2

and the gradient of this loss function with respect to the force field parameters can be computed through backpropagation:

In\u00a0[\u00a0]: Copied!
loss.backward()\n\nepsilon_col = vdw_potential.parameter_cols.index(\"epsilon\")\nsigma_col = vdw_potential.parameter_cols.index(\"sigma\")\n\nprint(\"VdW \u0190 Gradients\", vdw_potential.parameters.grad[:, epsilon_col])\nprint(\"VdW \u03c3 Gradients\", vdw_potential.parameters.grad[:, sigma_col])\n
loss.backward() epsilon_col = vdw_potential.parameter_cols.index(\"epsilon\") sigma_col = vdw_potential.parameter_cols.index(\"sigma\") print(\"VdW \u0190 Gradients\", vdw_potential.parameters.grad[:, epsilon_col]) print(\"VdW \u03c3 Gradients\", vdw_potential.parameters.grad[:, sigma_col])"},{"location":"examples/md-simulations/#ensemble-averages-from-md-simulations","title":"Ensemble Averages from MD Simulations\u00b6","text":"

This example shows how ensemble averages can be computed from MD simulations, such that their gradient with respect to force field parameters can be computed through backpropagation.

We start by parameterizing the set of molecules that will appear in our simulation boxes:

"},{"location":"examples/parameter-gradients/","title":"Parameter Gradients","text":"In\u00a0[1]: Copied!
import openff.interchange\nimport openff.toolkit\nimport openff.units\nimport torch\n\nimport smee.converters\n\nmolecule = openff.toolkit.Molecule.from_smiles(\"CC(=O)NC1=CC=C(C=C1)O\")\nmolecule.generate_conformers(n_conformers=1)\n\nconformer = torch.tensor(molecule.conformers[0].m_as(openff.units.unit.angstrom))\n\ninterchange = openff.interchange.Interchange.from_smirnoff(\n    openff.toolkit.ForceField(\"openff_unconstrained-2.0.0.offxml\"),\n    molecule.to_topology(),\n)\ntensor_ff, [tensor_topology] = smee.converters.convert_interchange(interchange)\n
import openff.interchange import openff.toolkit import openff.units import torch import smee.converters molecule = openff.toolkit.Molecule.from_smiles(\"CC(=O)NC1=CC=C(C=C1)O\") molecule.generate_conformers(n_conformers=1) conformer = torch.tensor(molecule.conformers[0].m_as(openff.units.unit.angstrom)) interchange = openff.interchange.Interchange.from_smirnoff( openff.toolkit.ForceField(\"openff_unconstrained-2.0.0.offxml\"), molecule.to_topology(), ) tensor_ff, [tensor_topology] = smee.converters.convert_interchange(interchange)
 

We can access the parameters for each SMIRNOFF parameter 'handler' (e.g. vdW, bond, angle, etc.) using the potentials_by_type (or the potentials) attribute of the TensorForceField object.

In\u00a0[2]: Copied!
vdw_potential = tensor_ff.potentials_by_type[\"vdW\"]\nvdw_potential.parameters.requires_grad = True\n
vdw_potential = tensor_ff.potentials_by_type[\"vdW\"] vdw_potential.parameters.requires_grad = True

The gradient of the potential energy with respect to the parameters can then be computed by backpropagating through the energy computation.

In\u00a0[3]: Copied!
import smee\n\nenergy = smee.compute_energy(tensor_topology, tensor_ff, conformer)\nenergy.backward()\n\nfor parameter_key, gradient in zip(\n    vdw_potential.parameter_keys, vdw_potential.parameters.grad.numpy(), strict=True\n):\n    parameter_cols = vdw_potential.parameter_cols\n\n    parameter_grads = \", \".join(\n        f\"dU/d{parameter_col} = {parameter_grad: 8.3f}\"\n        for parameter_col, parameter_grad in zip(parameter_cols, gradient, strict=True)\n    )\n    print(f\"{parameter_key.id.ljust(15)} - {parameter_grads}\")\n
import smee energy = smee.compute_energy(tensor_topology, tensor_ff, conformer) energy.backward() for parameter_key, gradient in zip( vdw_potential.parameter_keys, vdw_potential.parameters.grad.numpy(), strict=True ): parameter_cols = vdw_potential.parameter_cols parameter_grads = \", \".join( f\"dU/d{parameter_col} = {parameter_grad: 8.3f}\" for parameter_col, parameter_grad in zip(parameter_cols, gradient, strict=True) ) print(f\"{parameter_key.id.ljust(15)} - {parameter_grads}\")
[#6X4:1]        - dU/depsilon =   -1.033, dU/dsigma =   -0.139\n[#6:1]          - dU/depsilon =   87.490, dU/dsigma =   34.983\n[#8:1]          - dU/depsilon =   15.846, dU/dsigma =   17.232\n[#7:1]          - dU/depsilon =    0.148, dU/dsigma =    1.187\n[#8X2H1+0:1]    - dU/depsilon =   -0.305, dU/dsigma =    0.558\n[#1:1]-[#6X4]   - dU/depsilon =    7.630, dU/dsigma =    1.404\n[#1:1]-[#7]     - dU/depsilon =   -2.894, dU/dsigma =   -0.074\n[#1:1]-[#6X3]   - dU/depsilon =  137.134, dU/dsigma =   12.129\n[#1:1]-[#8]     - dU/depsilon =  -22.417, dU/dsigma =   -0.001\n
"},{"location":"examples/parameter-gradients/#parameter-gradients","title":"Parameter Gradients\u00b6","text":"

This example will show how the gradient of the potential energy with respect to force field parameters may be computed.

We start be loading and parameterizing the molecule of interest.

"},{"location":"reference/","title":"Index","text":""},{"location":"reference/#smee","title":"smee","text":"

smee

Differentiably evaluate energies of molecules using SMIRNOFF force fields

Modules:

  • converters \u2013

    Convert to / from smee tensor representations.

  • geometry \u2013

    Compute internal coordinates (e.g. bond lengths).

  • mm \u2013

    Compute differentiable ensemble averages using OpenMM and SMEE.

  • potentials \u2013

    Evaluate the potential energy of parameterized topologies.

  • tests \u2013
  • utils \u2013

    General utility functions

Classes:

  • EnergyFn \u2013

    An enumeration of the energy functions supported by smee out of the box.

  • PotentialType \u2013

    An enumeration of the potential types supported by smee out of the box.

  • NonbondedParameterMap \u2013

    A map between atom indices part of a particular valence interaction (e.g.

  • TensorConstraints \u2013

    A tensor representation of a set of distance constraints between pairs of

  • TensorForceField \u2013

    A tensor representation of a SMIRNOFF force field.

  • TensorPotential \u2013

    A tensor representation of a valence SMIRNOFF parameter handler

  • TensorSystem \u2013

    A tensor representation of a 'full' system.

  • TensorTopology \u2013

    A tensor representation of a molecular topology that has been assigned force

  • TensorVSites \u2013

    A tensor representation of a set of virtual sites parameters.

  • ValenceParameterMap \u2013

    A map between atom indices part of a particular valence interaction (e.g.

  • VSiteMap \u2013

    A map between virtual sites that have been added to a topology and their

Functions:

  • add_v_site_coords \u2013

    Appends the coordinates of any virtual sites to a conformer (or batch of

  • compute_v_site_coords \u2013

    Computes the positions of a set of virtual sites relative to a specified

  • compute_energy \u2013

    Computes the potential energy [kcal / mol] of a system / topology in a given

  • compute_energy_potential \u2013

    Computes the potential energy [kcal / mol] due to a SMIRNOFF potential

Attributes:

  • CUTOFF_ATTRIBUTE \u2013

    The attribute that should be used to store the cutoff distance of a potential.

  • SWITCH_ATTRIBUTE \u2013

    The attribute that should be used to store the switch width of a potential, if the

"},{"location":"reference/#smee.CUTOFF_ATTRIBUTE","title":"CUTOFF_ATTRIBUTE module-attribute","text":"
CUTOFF_ATTRIBUTE = 'cutoff'\n

The attribute that should be used to store the cutoff distance of a potential.

"},{"location":"reference/#smee.SWITCH_ATTRIBUTE","title":"SWITCH_ATTRIBUTE module-attribute","text":"
SWITCH_ATTRIBUTE = 'switch_width'\n

The attribute that should be used to store the switch width of a potential, if the potential should use the standard OpenMM switch function.

This attribute should be omitted if the potential should not use a switch function.

"},{"location":"reference/#smee.EnergyFn","title":"EnergyFn","text":"

Bases: _StrEnum

An enumeration of the energy functions supported by smee out of the box.

"},{"location":"reference/#smee.PotentialType","title":"PotentialType","text":"

Bases: _StrEnum

An enumeration of the potential types supported by smee out of the box.

"},{"location":"reference/#smee.NonbondedParameterMap","title":"NonbondedParameterMap dataclass","text":"
NonbondedParameterMap(\n    assignment_matrix: Tensor,\n    exclusions: Tensor,\n    exclusion_scale_idxs: Tensor,\n)\n

A map between atom indices part of a particular valence interaction (e.g. torsion indices) and the corresponding parameter in a TensorPotential

Methods:

  • to \u2013

    Cast this object to the specified device.

Attributes:

  • assignment_matrix (Tensor) \u2013

    A sparse tensor that yields the parameters assigned to each particle in the

  • exclusions (Tensor) \u2013

    Indices of pairs of particles (i.e. atoms or virtual sites) that should

  • exclusion_scale_idxs (Tensor) \u2013

    Indices into the tensor of handler attributes defining the 1-n scaling factors

"},{"location":"reference/#smee.NonbondedParameterMap.assignment_matrix","title":"assignment_matrix instance-attribute","text":"
assignment_matrix: Tensor\n

A sparse tensor that yields the parameters assigned to each particle in the system when multiplied with the corresponding handler parameters, with shape=(n_particles, n_parameters).

"},{"location":"reference/#smee.NonbondedParameterMap.exclusions","title":"exclusions instance-attribute","text":"
exclusions: Tensor\n

Indices of pairs of particles (i.e. atoms or virtual sites) that should have their interactions scaled by some factor with shape=(n_exclusions, 2).

"},{"location":"reference/#smee.NonbondedParameterMap.exclusion_scale_idxs","title":"exclusion_scale_idxs instance-attribute","text":"
exclusion_scale_idxs: Tensor\n

Indices into the tensor of handler attributes defining the 1-n scaling factors with shape=(n_exclusions, 1).

"},{"location":"reference/#smee.NonbondedParameterMap.to","title":"to","text":"
to(\n    device: DeviceType | None = None,\n    precision: Precision | None = None,\n) -> NonbondedParameterMap\n

Cast this object to the specified device.

Source code in smee/_models.py
def to(\n    self, device: DeviceType | None = None, precision: Precision | None = None\n) -> \"NonbondedParameterMap\":\n    \"\"\"Cast this object to the specified device.\"\"\"\n    return NonbondedParameterMap(\n        _cast(self.assignment_matrix, device, precision),\n        _cast(self.exclusions, device, precision),\n        _cast(self.exclusion_scale_idxs, device, precision),\n    )\n
"},{"location":"reference/#smee.TensorConstraints","title":"TensorConstraints dataclass","text":"
TensorConstraints(idxs: Tensor, distances: Tensor)\n

A tensor representation of a set of distance constraints between pairs of atoms.

Methods:

  • to \u2013

    Cast this object to the specified device.

Attributes:

  • idxs (Tensor) \u2013

    The indices of the atoms involved in each constraint with

  • distances (Tensor) \u2013

    The distance [\u00c5] between each pair of atoms with shape=(n_constraints,)

"},{"location":"reference/#smee.TensorConstraints.idxs","title":"idxs instance-attribute","text":"
idxs: Tensor\n

The indices of the atoms involved in each constraint with shape=(n_constraints, 2)

"},{"location":"reference/#smee.TensorConstraints.distances","title":"distances instance-attribute","text":"
distances: Tensor\n

The distance [\u00c5] between each pair of atoms with shape=(n_constraints,)

"},{"location":"reference/#smee.TensorConstraints.to","title":"to","text":"
to(\n    device: DeviceType | None = None,\n    precision: Precision | None = None,\n) -> TensorConstraints\n

Cast this object to the specified device.

Source code in smee/_models.py
def to(\n    self, device: DeviceType | None = None, precision: Precision | None = None\n) -> \"TensorConstraints\":\n    \"\"\"Cast this object to the specified device.\"\"\"\n    return TensorConstraints(\n        _cast(self.idxs, device, precision),\n        _cast(self.distances, device, precision),\n    )\n
"},{"location":"reference/#smee.TensorForceField","title":"TensorForceField dataclass","text":"
TensorForceField(\n    potentials: list[TensorPotential],\n    v_sites: TensorVSites | None = None,\n)\n

A tensor representation of a SMIRNOFF force field.

Methods:

  • to \u2013

    Cast this object to the specified device.

Attributes:

  • potentials (list[TensorPotential]) \u2013

    The terms and associated parameters of the potential energy function.

  • v_sites (TensorVSites | None) \u2013

    Parameters used to add and define the coords of v-sites in the system. The

"},{"location":"reference/#smee.TensorForceField.potentials","title":"potentials instance-attribute","text":"
potentials: list[TensorPotential]\n

The terms and associated parameters of the potential energy function.

"},{"location":"reference/#smee.TensorForceField.v_sites","title":"v_sites class-attribute instance-attribute","text":"
v_sites: TensorVSites | None = None\n

Parameters used to add and define the coords of v-sites in the system. The non-bonded parameters of any v-sites are stored in relevant potentials, e.g. 'vdW' or 'Electrostatics'.

"},{"location":"reference/#smee.TensorForceField.to","title":"to","text":"
to(\n    device: DeviceType | None = None,\n    precision: Precision | None = None,\n) -> TensorForceField\n

Cast this object to the specified device.

Source code in smee/_models.py
def to(\n    self, device: DeviceType | None = None, precision: Precision | None = None\n) -> \"TensorForceField\":\n    \"\"\"Cast this object to the specified device.\"\"\"\n    return TensorForceField(\n        [potential.to(device, precision) for potential in self.potentials],\n        None if self.v_sites is None else self.v_sites.to(device, precision),\n    )\n
"},{"location":"reference/#smee.TensorPotential","title":"TensorPotential dataclass","text":"
TensorPotential(\n    type: str,\n    fn: str,\n    parameters: Tensor,\n    parameter_keys: list[PotentialKey],\n    parameter_cols: tuple[str, ...],\n    parameter_units: tuple[Unit, ...],\n    attributes: Tensor | None = None,\n    attribute_cols: tuple[str, ...] | None = None,\n    attribute_units: tuple[Unit, ...] | None = None,\n    exceptions: dict[tuple[int, int], int] | None = None,\n)\n

A tensor representation of a valence SMIRNOFF parameter handler

Methods:

  • to \u2013

    Cast this object to the specified device.

Attributes:

  • type (str) \u2013

    The type of handler associated with these parameters

  • fn (str) \u2013

    The associated potential energy function

  • parameters (Tensor) \u2013

    The values of the parameters with shape=(n_parameters, n_parameter_cols)

  • parameter_keys (list[PotentialKey]) \u2013

    Unique keys associated with each parameter with length=(n_parameters)

  • parameter_cols (tuple[str, ...]) \u2013

    The names of each column of parameters.

  • parameter_units (tuple[Unit, ...]) \u2013

    The units of each parameter in parameters.

  • attributes (Tensor | None) \u2013

    The attributes defined on a handler such as 1-4 scaling factors with

  • attribute_cols (tuple[str, ...] | None) \u2013

    The names of each column of attributes.

  • attribute_units (tuple[Unit, ...] | None) \u2013

    The units of each attribute in attributes.

  • exceptions (dict[tuple[int, int], int] | None) \u2013

    A lookup for custom cross-interaction parameters that should override any mixing

"},{"location":"reference/#smee.TensorPotential.type","title":"type instance-attribute","text":"
type: str\n

The type of handler associated with these parameters

"},{"location":"reference/#smee.TensorPotential.fn","title":"fn instance-attribute","text":"
fn: str\n

The associated potential energy function

"},{"location":"reference/#smee.TensorPotential.parameters","title":"parameters instance-attribute","text":"
parameters: Tensor\n

The values of the parameters with shape=(n_parameters, n_parameter_cols)

"},{"location":"reference/#smee.TensorPotential.parameter_keys","title":"parameter_keys instance-attribute","text":"
parameter_keys: list[PotentialKey]\n

Unique keys associated with each parameter with length=(n_parameters)

"},{"location":"reference/#smee.TensorPotential.parameter_cols","title":"parameter_cols instance-attribute","text":"
parameter_cols: tuple[str, ...]\n

The names of each column of parameters.

"},{"location":"reference/#smee.TensorPotential.parameter_units","title":"parameter_units instance-attribute","text":"
parameter_units: tuple[Unit, ...]\n

The units of each parameter in parameters.

"},{"location":"reference/#smee.TensorPotential.attributes","title":"attributes class-attribute instance-attribute","text":"
attributes: Tensor | None = None\n

The attributes defined on a handler such as 1-4 scaling factors with shape=(n_attribute_cols,)

"},{"location":"reference/#smee.TensorPotential.attribute_cols","title":"attribute_cols class-attribute instance-attribute","text":"
attribute_cols: tuple[str, ...] | None = None\n

The names of each column of attributes.

"},{"location":"reference/#smee.TensorPotential.attribute_units","title":"attribute_units class-attribute instance-attribute","text":"
attribute_units: tuple[Unit, ...] | None = None\n

The units of each attribute in attributes.

"},{"location":"reference/#smee.TensorPotential.exceptions","title":"exceptions class-attribute instance-attribute","text":"
exceptions: dict[tuple[int, int], int] | None = None\n

A lookup for custom cross-interaction parameters that should override any mixing rules.

Each key should correspond to the indices of the two parameters whose mixing rule should be overridden, and each value the index of the parameter that contains the 'pre-mixed' parameter to use instead.

For now, all exceptions are assumed to be symmetric, i.e. if (a, b) is an exception then (b, a) is also an exception, and so only one of the two should be defined.

As a note of caution, not all potentials (e.g. common valence potentials) support such exceptions, and these are predominantly useful for non-bonded potentials.

"},{"location":"reference/#smee.TensorPotential.to","title":"to","text":"
to(\n    device: DeviceType | None = None,\n    precision: Precision | None = None,\n) -> TensorPotential\n

Cast this object to the specified device.

Source code in smee/_models.py
def to(\n    self, device: DeviceType | None = None, precision: Precision | None = None\n) -> \"TensorPotential\":\n    \"\"\"Cast this object to the specified device.\"\"\"\n    return TensorPotential(\n        self.type,\n        self.fn,\n        _cast(self.parameters, device, precision),\n        self.parameter_keys,\n        self.parameter_cols,\n        self.parameter_units,\n        (\n            None\n            if self.attributes is None\n            else _cast(self.attributes, device, precision)\n        ),\n        self.attribute_cols,\n        self.attribute_units,\n        self.exceptions,\n    )\n
"},{"location":"reference/#smee.TensorSystem","title":"TensorSystem dataclass","text":"
TensorSystem(\n    topologies: list[TensorTopology],\n    n_copies: list[int],\n    is_periodic: bool,\n)\n

A tensor representation of a 'full' system.

Methods:

  • to \u2013

    Cast this object to the specified device.

Attributes:

  • topologies (list[TensorTopology]) \u2013

    The topologies of the individual molecules in the system.

  • n_copies (list[int]) \u2013

    The number of copies of each topology to include in the system.

  • is_periodic (bool) \u2013

    Whether the system is periodic or not.

  • n_atoms (int) \u2013

    The number of atoms in the system.

  • n_v_sites (int) \u2013

    The number of v-sites in the system.

  • n_particles (int) \u2013

    The number of atoms + v-sites in the system.

"},{"location":"reference/#smee.TensorSystem.topologies","title":"topologies instance-attribute","text":"
topologies: list[TensorTopology]\n

The topologies of the individual molecules in the system.

"},{"location":"reference/#smee.TensorSystem.n_copies","title":"n_copies instance-attribute","text":"
n_copies: list[int]\n

The number of copies of each topology to include in the system.

"},{"location":"reference/#smee.TensorSystem.is_periodic","title":"is_periodic instance-attribute","text":"
is_periodic: bool\n

Whether the system is periodic or not.

"},{"location":"reference/#smee.TensorSystem.n_atoms","title":"n_atoms property","text":"
n_atoms: int\n

The number of atoms in the system.

"},{"location":"reference/#smee.TensorSystem.n_v_sites","title":"n_v_sites property","text":"
n_v_sites: int\n

The number of v-sites in the system.

"},{"location":"reference/#smee.TensorSystem.n_particles","title":"n_particles property","text":"
n_particles: int\n

The number of atoms + v-sites in the system.

"},{"location":"reference/#smee.TensorSystem.to","title":"to","text":"
to(\n    device: DeviceType | None = None,\n    precision: Precision | None = None,\n) -> TensorSystem\n

Cast this object to the specified device.

Source code in smee/_models.py
def to(\n    self, device: DeviceType | None = None, precision: Precision | None = None\n) -> \"TensorSystem\":\n    \"\"\"Cast this object to the specified device.\"\"\"\n    return TensorSystem(\n        [topology.to(device, precision) for topology in self.topologies],\n        self.n_copies,\n        self.is_periodic,\n    )\n
"},{"location":"reference/#smee.TensorTopology","title":"TensorTopology dataclass","text":"
TensorTopology(\n    atomic_nums: Tensor,\n    formal_charges: Tensor,\n    bond_idxs: Tensor,\n    bond_orders: Tensor,\n    parameters: dict[str, ParameterMap],\n    v_sites: VSiteMap | None = None,\n    constraints: TensorConstraints | None = None,\n    residue_idxs: list[int] | None = None,\n    residue_ids: list[str] | None = None,\n    chain_idxs: list[int] | None = None,\n    chain_ids: list[str] | None = None,\n)\n

A tensor representation of a molecular topology that has been assigned force field parameters.

Methods:

  • to \u2013

    Cast this object to the specified device.

Attributes:

  • atomic_nums (Tensor) \u2013

    The atomic numbers of each atom in the topology with shape=(n_atoms,)

  • formal_charges (Tensor) \u2013

    The formal charge of each atom in the topology with shape=(n_atoms,)

  • bond_idxs (Tensor) \u2013

    The indices of the atoms involved in each bond with shape=(n_bonds, 2)

  • bond_orders (Tensor) \u2013

    The bond orders of each bond with shape=(n_bonds,)

  • parameters (dict[str, ParameterMap]) \u2013

    The parameters that have been assigned to the topology.

  • v_sites (VSiteMap | None) \u2013

    The v-sites that have been assigned to the topology.

  • constraints (TensorConstraints | None) \u2013

    Distance constraints that should be applied during MD simulations. These

  • residue_idxs (list[int] | None) \u2013

    The index of the residue that each atom in the topology belongs to with

  • residue_ids (list[str] | None) \u2013

    The names of the residues that each atom belongs to with length=n_residues.

  • chain_idxs (list[int] | None) \u2013

    The index of the chain that each atom in the topology belongs to with

  • chain_ids (list[str] | None) \u2013

    The names of the chains that each atom belongs to with length=n_chains.

  • n_atoms (int) \u2013

    The number of atoms in the topology.

  • n_bonds (int) \u2013

    The number of bonds in the topology.

  • n_residues (int) \u2013

    The number of residues in the topology

  • n_chains (int) \u2013

    The number of chains in the topology

  • n_v_sites (int) \u2013

    The number of v-sites in the topology.

  • n_particles (int) \u2013

    The number of atoms + v-sites in the topology.

"},{"location":"reference/#smee.TensorTopology.atomic_nums","title":"atomic_nums instance-attribute","text":"
atomic_nums: Tensor\n

The atomic numbers of each atom in the topology with shape=(n_atoms,)

"},{"location":"reference/#smee.TensorTopology.formal_charges","title":"formal_charges instance-attribute","text":"
formal_charges: Tensor\n

The formal charge of each atom in the topology with shape=(n_atoms,)

"},{"location":"reference/#smee.TensorTopology.bond_idxs","title":"bond_idxs instance-attribute","text":"
bond_idxs: Tensor\n

The indices of the atoms involved in each bond with shape=(n_bonds, 2)

"},{"location":"reference/#smee.TensorTopology.bond_orders","title":"bond_orders instance-attribute","text":"
bond_orders: Tensor\n

The bond orders of each bond with shape=(n_bonds,)

"},{"location":"reference/#smee.TensorTopology.parameters","title":"parameters instance-attribute","text":"
parameters: dict[str, ParameterMap]\n

The parameters that have been assigned to the topology.

"},{"location":"reference/#smee.TensorTopology.v_sites","title":"v_sites class-attribute instance-attribute","text":"
v_sites: VSiteMap | None = None\n

The v-sites that have been assigned to the topology.

"},{"location":"reference/#smee.TensorTopology.constraints","title":"constraints class-attribute instance-attribute","text":"
constraints: TensorConstraints | None = None\n

Distance constraints that should be applied during MD simulations. These will not be used outside of MD simulations.

"},{"location":"reference/#smee.TensorTopology.residue_idxs","title":"residue_idxs class-attribute instance-attribute","text":"
residue_idxs: list[int] | None = None\n

The index of the residue that each atom in the topology belongs to with length=n_atoms.

"},{"location":"reference/#smee.TensorTopology.residue_ids","title":"residue_ids class-attribute instance-attribute","text":"
residue_ids: list[str] | None = None\n

The names of the residues that each atom belongs to with length=n_residues.

"},{"location":"reference/#smee.TensorTopology.chain_idxs","title":"chain_idxs class-attribute instance-attribute","text":"
chain_idxs: list[int] | None = None\n

The index of the chain that each atom in the topology belongs to with length=n_atoms.

"},{"location":"reference/#smee.TensorTopology.chain_ids","title":"chain_ids class-attribute instance-attribute","text":"
chain_ids: list[str] | None = None\n

The names of the chains that each atom belongs to with length=n_chains.

"},{"location":"reference/#smee.TensorTopology.n_atoms","title":"n_atoms property","text":"
n_atoms: int\n

The number of atoms in the topology.

"},{"location":"reference/#smee.TensorTopology.n_bonds","title":"n_bonds property","text":"
n_bonds: int\n

The number of bonds in the topology.

"},{"location":"reference/#smee.TensorTopology.n_residues","title":"n_residues property","text":"
n_residues: int\n

The number of residues in the topology

"},{"location":"reference/#smee.TensorTopology.n_chains","title":"n_chains property","text":"
n_chains: int\n

The number of chains in the topology

"},{"location":"reference/#smee.TensorTopology.n_v_sites","title":"n_v_sites property","text":"
n_v_sites: int\n

The number of v-sites in the topology.

"},{"location":"reference/#smee.TensorTopology.n_particles","title":"n_particles property","text":"
n_particles: int\n

The number of atoms + v-sites in the topology.

"},{"location":"reference/#smee.TensorTopology.to","title":"to","text":"
to(\n    device: DeviceType | None = None,\n    precision: Precision | None = None,\n) -> TensorTopology\n

Cast this object to the specified device.

Source code in smee/_models.py
def to(\n    self, device: DeviceType | None = None, precision: Precision | None = None\n) -> \"TensorTopology\":\n    \"\"\"Cast this object to the specified device.\"\"\"\n    return TensorTopology(\n        self.atomic_nums,\n        self.formal_charges,\n        self.bond_idxs,\n        self.bond_orders,\n        {k: v.to(device, precision) for k, v in self.parameters.items()},\n        None if self.v_sites is None else self.v_sites.to(device, precision),\n        (\n            None\n            if self.constraints is None\n            else self.constraints.to(device, precision)\n        ),\n        self.residue_idxs,\n        self.residue_ids,\n        self.chain_ids,\n    )\n
"},{"location":"reference/#smee.TensorVSites","title":"TensorVSites dataclass","text":"
TensorVSites(\n    keys: List[VirtualSiteKey],\n    weights: list[Tensor],\n    parameters: Tensor,\n)\n

A tensor representation of a set of virtual sites parameters.

Methods:

  • default_units \u2013

    The default units of each v-site parameter.

  • to \u2013

    Cast this object to the specified device.

Attributes:

  • keys (List[VirtualSiteKey]) \u2013

    The unique keys associated with each v-site with length=(n_v_sites)

  • weights (list[Tensor]) \u2013

    A matrix of weights that, when applied to the 'orientiational' atoms, yields a

  • parameters (Tensor) \u2013

    The distance, in-plane and out-of-plane angles with shape=(n_v_sites, 3)

  • parameter_units (dict[str, Unit]) \u2013

    The units of each v-site parameter.

"},{"location":"reference/#smee.TensorVSites.keys","title":"keys instance-attribute","text":"
keys: List[VirtualSiteKey]\n

The unique keys associated with each v-site with length=(n_v_sites)

"},{"location":"reference/#smee.TensorVSites.weights","title":"weights instance-attribute","text":"
weights: list[Tensor]\n

A matrix of weights that, when applied to the 'orientiational' atoms, yields a basis that the virtual site coordinate parameters can be projected onto with shape=(n_v_sites, 3, 3)

"},{"location":"reference/#smee.TensorVSites.parameters","title":"parameters instance-attribute","text":"
parameters: Tensor\n

The distance, in-plane and out-of-plane angles with shape=(n_v_sites, 3)

"},{"location":"reference/#smee.TensorVSites.parameter_units","title":"parameter_units property","text":"
parameter_units: dict[str, Unit]\n

The units of each v-site parameter.

"},{"location":"reference/#smee.TensorVSites.default_units","title":"default_units classmethod","text":"
default_units() -> dict[str, Unit]\n

The default units of each v-site parameter.

Source code in smee/_models.py
@classmethod\ndef default_units(cls) -> dict[str, openff.units.Unit]:\n    \"\"\"The default units of each v-site parameter.\"\"\"\n    return {\n        \"distance\": _ANGSTROM,\n        \"inPlaneAngle\": _RADIANS,\n        \"outOfPlaneAngle\": _RADIANS,\n    }\n
"},{"location":"reference/#smee.TensorVSites.to","title":"to","text":"
to(\n    device: DeviceType | None = None,\n    precision: Precision | None = None,\n) -> TensorVSites\n

Cast this object to the specified device.

Source code in smee/_models.py
def to(\n    self, device: DeviceType | None = None, precision: Precision | None = None\n) -> \"TensorVSites\":\n    \"\"\"Cast this object to the specified device.\"\"\"\n    return TensorVSites(\n        self.keys,\n        [_cast(weight, device, precision) for weight in self.weights],\n        _cast(self.parameters, device, precision),\n    )\n
"},{"location":"reference/#smee.ValenceParameterMap","title":"ValenceParameterMap dataclass","text":"
ValenceParameterMap(\n    particle_idxs: Tensor, assignment_matrix: Tensor\n)\n

A map between atom indices part of a particular valence interaction (e.g. torsion indices) and the corresponding parameter in a TensorPotential

Methods:

  • to \u2013

    Cast this object to the specified device.

Attributes:

  • particle_idxs (Tensor) \u2013

    The indices of the particles (e.g. atoms or virtual sites) involved in an

  • assignment_matrix (Tensor) \u2013

    A sparse tensor that yields the assigned parameters when multiplied with the

"},{"location":"reference/#smee.ValenceParameterMap.particle_idxs","title":"particle_idxs instance-attribute","text":"
particle_idxs: Tensor\n

The indices of the particles (e.g. atoms or virtual sites) involved in an interaction with shape=(n_interactions, n_cols). For a bond n_cols=2, for angles n_cols=3 etc.

"},{"location":"reference/#smee.ValenceParameterMap.assignment_matrix","title":"assignment_matrix instance-attribute","text":"
assignment_matrix: Tensor\n

A sparse tensor that yields the assigned parameters when multiplied with the corresponding handler parameters, with shape=(n_interacting, n_parameters).

"},{"location":"reference/#smee.ValenceParameterMap.to","title":"to","text":"
to(\n    device: DeviceType | None = None,\n    precision: Precision | None = None,\n) -> ValenceParameterMap\n

Cast this object to the specified device.

Source code in smee/_models.py
def to(\n    self, device: DeviceType | None = None, precision: Precision | None = None\n) -> \"ValenceParameterMap\":\n    \"\"\"Cast this object to the specified device.\"\"\"\n    return ValenceParameterMap(\n        _cast(self.particle_idxs, device, precision),\n        _cast(self.assignment_matrix, device, precision),\n    )\n
"},{"location":"reference/#smee.VSiteMap","title":"VSiteMap dataclass","text":"
VSiteMap(\n    keys: list[VirtualSiteKey],\n    key_to_idx: dict[VirtualSiteKey, int],\n    parameter_idxs: Tensor,\n)\n

A map between virtual sites that have been added to a topology and their corresponding 'parameters' used to position them.

Methods:

  • to \u2013

    Cast this object to the specified device.

Attributes:

  • keys (list[VirtualSiteKey]) \u2013

    The keys used to identify each v-site.

  • key_to_idx (dict[VirtualSiteKey, int]) \u2013

    A map between the unique keys associated with each v-site and their index in

  • parameter_idxs (Tensor) \u2013

    The indices of the corresponding v-site parameters with shape=(n_v_sites, 1)

"},{"location":"reference/#smee.VSiteMap.keys","title":"keys instance-attribute","text":"
keys: list[VirtualSiteKey]\n

The keys used to identify each v-site.

"},{"location":"reference/#smee.VSiteMap.key_to_idx","title":"key_to_idx instance-attribute","text":"
key_to_idx: dict[VirtualSiteKey, int]\n

A map between the unique keys associated with each v-site and their index in the topology

"},{"location":"reference/#smee.VSiteMap.parameter_idxs","title":"parameter_idxs instance-attribute","text":"
parameter_idxs: Tensor\n

The indices of the corresponding v-site parameters with shape=(n_v_sites, 1)

"},{"location":"reference/#smee.VSiteMap.to","title":"to","text":"
to(\n    device: DeviceType | None = None,\n    precision: Precision | None = None,\n) -> VSiteMap\n

Cast this object to the specified device.

Source code in smee/_models.py
def to(\n    self, device: DeviceType | None = None, precision: Precision | None = None\n) -> \"VSiteMap\":\n    \"\"\"Cast this object to the specified device.\"\"\"\n    return VSiteMap(\n        self.keys, self.key_to_idx, _cast(self.parameter_idxs, device, precision)\n    )\n
"},{"location":"reference/#smee.add_v_site_coords","title":"add_v_site_coords","text":"
add_v_site_coords(\n    v_sites: VSiteMap,\n    conformer: Tensor,\n    force_field: TensorForceField,\n) -> Tensor\n

Appends the coordinates of any virtual sites to a conformer (or batch of conformers) containing only atomic coordinates.

Notes
  • This function only supports appending v-sites to the end of the list of coordinates, and not interleaving them between existing atomic coordinates.

Parameters:

  • v_sites (VSiteMap) \u2013

    A mapping between the virtual sites to add and their corresponding force field parameters.

  • conformer (Tensor) \u2013

    The conformer(s) to add the virtual sites to with shape=(n_atoms, 3) or shape=(n_batches, n_atoms, 3) and units of [\u00c5].

  • force_field (TensorForceField) \u2013

    The force field containing the virtual site parameters.

Returns:

  • Tensor \u2013

    The full conformer(s) with both atomic and virtual site coordinates [\u00c5] with shape=(n_atoms+n_v_sites, 3) or shape=(n_batches, n_atoms+n_v_sites, 3).

Source code in smee/geometry.py
def add_v_site_coords(\n    v_sites: \"smee.VSiteMap\",\n    conformer: torch.Tensor,\n    force_field: \"smee.TensorForceField\",\n) -> torch.Tensor:\n    \"\"\"Appends the coordinates of any virtual sites to a conformer (or batch of\n    conformers) containing only atomic coordinates.\n\n    Notes:\n        * This function only supports appending v-sites to the end of the list of\n          coordinates, and not interleaving them between existing atomic coordinates.\n\n    Args:\n        v_sites: A mapping between the virtual sites to add and their corresponding\n            force field parameters.\n        conformer: The conformer(s) to add the virtual sites to with\n            ``shape=(n_atoms, 3)`` or ``shape=(n_batches, n_atoms, 3)`` and units of\n            [\u00c5].\n        force_field: The force field containing the virtual site parameters.\n\n    Returns:\n        The full conformer(s) with both atomic and virtual site coordinates [\u00c5] with\n        ``shape=(n_atoms+n_v_sites, 3)`` or ``shape=(n_batches, n_atoms+n_v_sites, 3)``.\n    \"\"\"\n\n    v_site_coords = compute_v_site_coords(v_sites, conformer, force_field)\n\n    return torch.cat([conformer, v_site_coords], dim=(1 if conformer.ndim == 3 else 0))\n
"},{"location":"reference/#smee.compute_v_site_coords","title":"compute_v_site_coords","text":"
compute_v_site_coords(\n    v_sites: VSiteMap,\n    conformer: Tensor,\n    force_field: TensorForceField,\n) -> Tensor\n

Computes the positions of a set of virtual sites relative to a specified conformer or batch of conformers.

Parameters:

  • v_sites (VSiteMap) \u2013

    A mapping between the virtual sites to add and their corresponding force field parameters.

  • conformer (Tensor) \u2013

    The conformer(s) to add the virtual sites to with shape=(n_atoms, 3) or shape=(n_batches, n_atoms, 3) and units of [\u00c5].

  • force_field (TensorForceField) \u2013

    The force field containing the virtual site parameters.

Returns:

  • Tensor \u2013

    A tensor of virtual site positions [\u00c5] with shape=(n_v_sites, 3) or shape=(n_batches, n_v_sites, 3).

Source code in smee/geometry.py
def compute_v_site_coords(\n    v_sites: \"smee.VSiteMap\",\n    conformer: torch.Tensor,\n    force_field: \"smee.TensorForceField\",\n) -> torch.Tensor:\n    \"\"\"Computes the positions of a set of virtual sites relative to a specified\n    conformer or batch of conformers.\n\n    Args:\n        v_sites: A mapping between the virtual sites to add and their corresponding\n            force field parameters.\n        conformer: The conformer(s) to add the virtual sites to with\n            ``shape=(n_atoms, 3)`` or ``shape=(n_batches, n_atoms, 3)`` and units of\n            [\u00c5].\n        force_field: The force field containing the virtual site parameters.\n\n    Returns:\n        A tensor of virtual site positions [\u00c5] with ``shape=(n_v_sites, 3)`` or\n        ``shape=(n_batches, n_v_sites, 3)``.\n    \"\"\"\n\n    is_batched = conformer.ndim == 3\n\n    if not is_batched:\n        conformer = torch.unsqueeze(conformer, 0)\n\n    if len(v_sites.parameter_idxs) > 0:\n        local_frame_coords = force_field.v_sites.parameters[v_sites.parameter_idxs]\n        local_coord_frames = _build_v_site_coord_frames(v_sites, conformer, force_field)\n\n        v_site_coords = _convert_v_site_coords(local_frame_coords, local_coord_frames)\n    else:\n        v_site_coords = smee.utils.zeros_like((len(conformer), 0, 3), other=conformer)\n\n    if not is_batched:\n        v_site_coords = torch.squeeze(v_site_coords, 0)\n\n    return v_site_coords\n
"},{"location":"reference/#smee.compute_energy","title":"compute_energy","text":"
compute_energy(\n    system: TensorSystem | TensorTopology,\n    force_field: TensorForceField,\n    conformer: Tensor,\n    box_vectors: Tensor | None = None,\n) -> Tensor\n

Computes the potential energy [kcal / mol] of a system / topology in a given conformation(s).

Parameters:

  • system (TensorSystem | TensorTopology) \u2013

    The system or topology to compute the potential energy of.

  • force_field (TensorForceField) \u2013

    The force field that defines the potential energy function.

  • conformer (Tensor) \u2013

    The conformer(s) to evaluate the potential at with shape=(n_particles, 3) or shape=(n_confs, n_particles, 3).

  • box_vectors (Tensor | None, default: None ) \u2013

    The box vectors of the system with shape=(3, 3) if the system is periodic, or None if the system is non-periodic.

Returns:

  • Tensor \u2013

    The potential energy of the conformer(s) [kcal / mol].

Source code in smee/potentials/_potentials.py
def compute_energy(\n    system: smee.TensorSystem | smee.TensorTopology,\n    force_field: smee.TensorForceField,\n    conformer: torch.Tensor,\n    box_vectors: torch.Tensor | None = None,\n) -> torch.Tensor:\n    \"\"\"Computes the potential energy [kcal / mol] of a system / topology in a given\n    conformation(s).\n\n    Args:\n        system: The system or topology to compute the potential energy of.\n        force_field: The force field that defines the potential energy function.\n        conformer: The conformer(s) to evaluate the potential at with\n            ``shape=(n_particles, 3)`` or ``shape=(n_confs, n_particles, 3)``.\n        box_vectors: The box vectors of the system with ``shape=(3, 3)`` if the\n            system is periodic, or ``None`` if the system is non-periodic.\n\n    Returns:\n        The potential energy of the conformer(s) [kcal / mol].\n    \"\"\"\n\n    # register the built-in potential energy functions\n    importlib.import_module(\"smee.potentials.nonbonded\")\n    importlib.import_module(\"smee.potentials.valence\")\n\n    system, conformer, box_vectors = _prepare_inputs(system, conformer, box_vectors)\n    pairwise = _precompute_pairwise(system, force_field, conformer, box_vectors)\n\n    energy = smee.utils.zeros_like(\n        conformer.shape[0] if conformer.ndim == 3 else 1, conformer\n    )\n\n    for potential in force_field.potentials:\n        energy += compute_energy_potential(\n            system, potential, conformer, box_vectors, pairwise\n        )\n\n    return energy\n
"},{"location":"reference/#smee.compute_energy_potential","title":"compute_energy_potential","text":"
compute_energy_potential(\n    system: TensorSystem | TensorTopology,\n    potential: TensorPotential,\n    conformer: Tensor,\n    box_vectors: Tensor | None = None,\n    pairwise: Optional[PairwiseDistances] = None,\n) -> Tensor\n

Computes the potential energy [kcal / mol] due to a SMIRNOFF potential handler for a given conformer(s).

Parameters:

  • system (TensorSystem | TensorTopology) \u2013

    The system or topology to compute the potential energy of.

  • potential (TensorPotential) \u2013

    The potential describing the energy function to compute.

  • conformer (Tensor) \u2013

    The conformer(s) to evaluate the potential at with shape=(n_particles, 3) or shape=(n_confs, n_particles, 3).

  • box_vectors (Tensor | None, default: None ) \u2013

    The box vectors of the system with shape=(3, 3) or shape=(n_confs, 3, 3)if the system is periodic, orNone`` if the system is non-periodic.

  • pairwise (Optional[PairwiseDistances], default: None ) \u2013

    Pre-computed pairwise distances between particles in the system.

Returns:

  • Tensor \u2013

    The potential energy of the conformer(s) [kcal / mol].

Source code in smee/potentials/_potentials.py
def compute_energy_potential(\n    system: smee.TensorSystem | smee.TensorTopology,\n    potential: smee.TensorPotential,\n    conformer: torch.Tensor,\n    box_vectors: torch.Tensor | None = None,\n    pairwise: typing.Optional[\"smee.potentials.nonbonded.PairwiseDistances\"] = None,\n) -> torch.Tensor:\n    \"\"\"Computes the potential energy [kcal / mol] due to a SMIRNOFF potential\n    handler for a given conformer(s).\n\n    Args:\n        system: The system or topology to compute the potential energy of.\n        potential: The potential describing the energy function to compute.\n        conformer: The conformer(s) to evaluate the potential at with\n            ``shape=(n_particles, 3)`` or ``shape=(n_confs, n_particles, 3)``.\n        box_vectors: The box vectors of the system with ``shape=(3, 3)`` or\n            shape=(n_confs, 3, 3)`` if the system is periodic, or ``None`` if the system\n            is non-periodic.\n        pairwise: Pre-computed pairwise distances between particles in the system.\n\n    Returns:\n        The potential energy of the conformer(s) [kcal / mol].\n    \"\"\"\n\n    # register the built-in potential energy functions\n    importlib.import_module(\"smee.potentials.nonbonded\")\n    importlib.import_module(\"smee.potentials.valence\")\n\n    system, conformer, box_vectors = _prepare_inputs(system, conformer, box_vectors)\n\n    energy_fn = _POTENTIAL_ENERGY_FUNCTIONS[(potential.type, potential.fn)]\n    energy_fn_spec = inspect.signature(energy_fn)\n\n    energy_fn_kwargs = {}\n\n    if \"box_vectors\" in energy_fn_spec.parameters:\n        energy_fn_kwargs[\"box_vectors\"] = box_vectors\n    if \"pairwise\" in energy_fn_spec.parameters:\n        energy_fn_kwargs[\"pairwise\"] = pairwise\n\n    return energy_fn(system, potential, conformer, **energy_fn_kwargs)\n
"},{"location":"reference/SUMMARY/","title":"SUMMARY","text":"
  • smee
    • converters
      • openff
        • nonbonded
        • valence
      • openmm
        • nonbonded
        • valence
    • geometry
    • mm
    • potentials
      • nonbonded
      • valence
    • utils
"},{"location":"reference/geometry/","title":" geometry","text":""},{"location":"reference/geometry/#smee.geometry","title":"geometry","text":"

Compute internal coordinates (e.g. bond lengths).

Functions:

  • compute_bond_vectors \u2013

    Computes the vectors between each atom pair specified by the atom_indices as

  • compute_angles \u2013

    Computes the angles [rad] between each atom triplet specified by the

  • compute_dihedrals \u2013

    Computes the dihedral angles [rad] between each atom quartet specified by the

  • polar_to_cartesian_coords \u2013

    Converts a set of polar coordinates into cartesian coordinates.

  • compute_v_site_coords \u2013

    Computes the positions of a set of virtual sites relative to a specified

  • add_v_site_coords \u2013

    Appends the coordinates of any virtual sites to a conformer (or batch of

"},{"location":"reference/geometry/#smee.geometry.compute_bond_vectors","title":"compute_bond_vectors","text":"
compute_bond_vectors(\n    conformer: Tensor, atom_indices: Tensor\n) -> tuple[Tensor, Tensor]\n

Computes the vectors between each atom pair specified by the atom_indices as well as their norms.

Parameters:

  • conformer (Tensor) \u2013

    The conformer [\u00c5] to compute the bond vectors for with shape=(n_atoms, 3) or shape=(n_confs, n_atoms, 3).

  • atom_indices (Tensor) \u2013

    The indices of the atoms involved in each bond with shape=(n_bonds, 2)

Returns:

  • tuple[Tensor, Tensor] \u2013

    The bond vectors and their norms [\u00c5].

Source code in smee/geometry.py
def compute_bond_vectors(\n    conformer: torch.Tensor, atom_indices: torch.Tensor\n) -> tuple[torch.Tensor, torch.Tensor]:\n    \"\"\"Computes the vectors between each atom pair specified by the ``atom_indices`` as\n    well as their norms.\n\n    Args:\n        conformer: The conformer [\u00c5] to compute the bond vectors for with\n            ``shape=(n_atoms, 3)`` or ``shape=(n_confs, n_atoms, 3)``.\n        atom_indices: The indices of the atoms involved in each bond with\n            ``shape=(n_bonds, 2)``\n\n    Returns:\n        The bond vectors and their norms [\u00c5].\n    \"\"\"\n\n    if len(atom_indices) == 0:\n        return (\n            smee.utils.tensor_like([], other=conformer),\n            smee.utils.tensor_like([], other=conformer),\n        )\n\n    is_batched = conformer.ndim == 3\n\n    if not is_batched:\n        conformer = torch.unsqueeze(conformer, 0)\n\n    directions = conformer[:, atom_indices[:, 1]] - conformer[:, atom_indices[:, 0]]\n    distances = torch.norm(directions, dim=-1)\n\n    if not is_batched:\n        directions = torch.squeeze(directions, dim=0)\n        distances = torch.squeeze(distances, dim=0)\n\n    return directions, distances\n
"},{"location":"reference/geometry/#smee.geometry.compute_angles","title":"compute_angles","text":"
compute_angles(\n    conformer: Tensor, atom_indices: Tensor\n) -> Tensor\n

Computes the angles [rad] between each atom triplet specified by the atom_indices.

Parameters:

  • conformer (Tensor) \u2013

    The conformer [\u00c5] to compute the angles for with shape=(n_atoms, 3) or shape=(n_confs, n_atoms, 3).

  • atom_indices (Tensor) \u2013

    The indices of the atoms involved in each angle with shape=(n_angles, 3).

Returns:

  • Tensor \u2013

    The valence angles [rad].

Source code in smee/geometry.py
def compute_angles(conformer: torch.Tensor, atom_indices: torch.Tensor) -> torch.Tensor:\n    \"\"\"Computes the angles [rad] between each atom triplet specified by the\n    ``atom_indices``.\n\n    Args:\n        conformer: The conformer [\u00c5] to compute the angles for with\n            ``shape=(n_atoms, 3)`` or ``shape=(n_confs, n_atoms, 3)``.\n        atom_indices: The indices of the atoms involved in each angle with\n            ``shape=(n_angles, 3)``.\n\n    Returns:\n        The valence angles [rad].\n    \"\"\"\n\n    if len(atom_indices) == 0:\n        return smee.utils.tensor_like([], other=conformer)\n\n    is_batched = conformer.ndim == 3\n\n    if not is_batched:\n        conformer = torch.unsqueeze(conformer, 0)\n\n    vector_ab = conformer[:, atom_indices[:, 1]] - conformer[:, atom_indices[:, 0]]\n    vector_ac = conformer[:, atom_indices[:, 1]] - conformer[:, atom_indices[:, 2]]\n\n    # tan theta = sin theta / cos theta\n    #\n    # ||a x b|| = ||a|| ||b|| sin theta\n    #   a . b   = ||a|| ||b|| cos theta\n    #\n    # => tan theta = (a x b) / (a . b)\n    angles = torch.atan2(\n        torch.norm(torch.cross(vector_ab, vector_ac, dim=-1), dim=-1),\n        (vector_ab * vector_ac).sum(dim=-1),\n    )\n\n    if not is_batched:\n        angles = torch.squeeze(angles, dim=0)\n\n    return angles\n
"},{"location":"reference/geometry/#smee.geometry.compute_dihedrals","title":"compute_dihedrals","text":"
compute_dihedrals(\n    conformer: Tensor, atom_indices: Tensor\n) -> Tensor\n

Computes the dihedral angles [rad] between each atom quartet specified by the atom_indices.

Parameters:

  • conformer (Tensor) \u2013

    The conformer [\u00c5] to compute the dihedral angles for with shape=(n_atoms, 3) or shape=(n_confs, n_atoms, 3).

  • atom_indices (Tensor) \u2013

    The indices of the atoms involved in each dihedral angle with shape=(n_dihedrals, 4).

Returns:

  • Tensor \u2013

    The dihedral angles [rad].

Source code in smee/geometry.py
def compute_dihedrals(\n    conformer: torch.Tensor, atom_indices: torch.Tensor\n) -> torch.Tensor:\n    \"\"\"Computes the dihedral angles [rad] between each atom quartet specified by the\n    ``atom_indices``.\n\n    Args:\n        conformer: The conformer [\u00c5] to compute the dihedral angles for with\n            ``shape=(n_atoms, 3)`` or ``shape=(n_confs, n_atoms, 3)``.\n        atom_indices: The indices of the atoms involved in each dihedral angle with\n            ``shape=(n_dihedrals, 4)``.\n\n    Returns:\n        The dihedral angles [rad].\n    \"\"\"\n\n    if len(atom_indices) == 0:\n        return smee.utils.tensor_like([], other=conformer)\n\n    is_batched = conformer.ndim == 3\n\n    if not is_batched:\n        conformer = torch.unsqueeze(conformer, 0)\n\n    # Based on the OpenMM formalism.\n    vector_ab = conformer[:, atom_indices[:, 0]] - conformer[:, atom_indices[:, 1]]\n    vector_cb = conformer[:, atom_indices[:, 2]] - conformer[:, atom_indices[:, 1]]\n    vector_cd = conformer[:, atom_indices[:, 2]] - conformer[:, atom_indices[:, 3]]\n\n    vector_ab_cross_cb = torch.cross(vector_ab, vector_cb, dim=-1)\n    vector_cb_cross_cd = torch.cross(vector_cb, vector_cd, dim=-1)\n\n    vector_cb_norm = torch.norm(vector_cb, dim=-1).unsqueeze(-1)\n\n    y = (\n        torch.cross(vector_ab_cross_cb, vector_cb_cross_cd, dim=-1)\n        * vector_cb\n        / vector_cb_norm\n    ).sum(axis=-1)\n\n    x = (vector_ab_cross_cb * vector_cb_cross_cd).sum(axis=-1)\n\n    phi = torch.atan2(y, x)\n\n    if not is_batched:\n        phi = torch.squeeze(phi, dim=0)\n\n    return phi\n
"},{"location":"reference/geometry/#smee.geometry.polar_to_cartesian_coords","title":"polar_to_cartesian_coords","text":"
polar_to_cartesian_coords(polar_coords: Tensor) -> Tensor\n

Converts a set of polar coordinates into cartesian coordinates.

Parameters:

  • polar_coords (Tensor) \u2013

    The polar coordinates with shape=(n_coords, 3) and with columns of distance [\u00c5], 'in plane angle' [rad] and 'out of plane' angle [rad].

Returns:

  • Tensor \u2013

    An array of the cartesian coordinates with shape=(n_coords, 3) and units of [\u00c5].

Source code in smee/geometry.py
def polar_to_cartesian_coords(polar_coords: torch.Tensor) -> torch.Tensor:\n    \"\"\"Converts a set of polar coordinates into cartesian coordinates.\n\n    Args:\n        polar_coords: The polar coordinates with ``shape=(n_coords, 3)`` and with\n            columns of distance [\u00c5], 'in plane angle' [rad] and 'out of plane'\n            angle [rad].\n\n    Returns:\n        An array of the cartesian coordinates with ``shape=(n_coords, 3)`` and units\n        of [\u00c5].\n    \"\"\"\n\n    d, theta, phi = polar_coords[:, 0], polar_coords[:, 1], polar_coords[:, 2]\n\n    cos_theta = torch.cos(theta)\n    sin_theta = torch.sin(theta)\n\n    cos_phi = torch.cos(phi)\n    sin_phi = torch.sin(phi)\n\n    # Here we use cos(phi) in place of sin(phi) and sin(phi) in place of cos(phi)\n    # this is because we want phi=0 to represent a 0 degree angle from the x-y plane\n    # rather than 0 degrees from the z-axis.\n    coords = torch.stack(\n        [d * cos_theta * cos_phi, d * sin_theta * cos_phi, d * sin_phi], dim=-1\n    )\n    return coords\n
"},{"location":"reference/geometry/#smee.geometry.compute_v_site_coords","title":"compute_v_site_coords","text":"
compute_v_site_coords(\n    v_sites: VSiteMap,\n    conformer: Tensor,\n    force_field: TensorForceField,\n) -> Tensor\n

Computes the positions of a set of virtual sites relative to a specified conformer or batch of conformers.

Parameters:

  • v_sites (VSiteMap) \u2013

    A mapping between the virtual sites to add and their corresponding force field parameters.

  • conformer (Tensor) \u2013

    The conformer(s) to add the virtual sites to with shape=(n_atoms, 3) or shape=(n_batches, n_atoms, 3) and units of [\u00c5].

  • force_field (TensorForceField) \u2013

    The force field containing the virtual site parameters.

Returns:

  • Tensor \u2013

    A tensor of virtual site positions [\u00c5] with shape=(n_v_sites, 3) or shape=(n_batches, n_v_sites, 3).

Source code in smee/geometry.py
def compute_v_site_coords(\n    v_sites: \"smee.VSiteMap\",\n    conformer: torch.Tensor,\n    force_field: \"smee.TensorForceField\",\n) -> torch.Tensor:\n    \"\"\"Computes the positions of a set of virtual sites relative to a specified\n    conformer or batch of conformers.\n\n    Args:\n        v_sites: A mapping between the virtual sites to add and their corresponding\n            force field parameters.\n        conformer: The conformer(s) to add the virtual sites to with\n            ``shape=(n_atoms, 3)`` or ``shape=(n_batches, n_atoms, 3)`` and units of\n            [\u00c5].\n        force_field: The force field containing the virtual site parameters.\n\n    Returns:\n        A tensor of virtual site positions [\u00c5] with ``shape=(n_v_sites, 3)`` or\n        ``shape=(n_batches, n_v_sites, 3)``.\n    \"\"\"\n\n    is_batched = conformer.ndim == 3\n\n    if not is_batched:\n        conformer = torch.unsqueeze(conformer, 0)\n\n    if len(v_sites.parameter_idxs) > 0:\n        local_frame_coords = force_field.v_sites.parameters[v_sites.parameter_idxs]\n        local_coord_frames = _build_v_site_coord_frames(v_sites, conformer, force_field)\n\n        v_site_coords = _convert_v_site_coords(local_frame_coords, local_coord_frames)\n    else:\n        v_site_coords = smee.utils.zeros_like((len(conformer), 0, 3), other=conformer)\n\n    if not is_batched:\n        v_site_coords = torch.squeeze(v_site_coords, 0)\n\n    return v_site_coords\n
"},{"location":"reference/geometry/#smee.geometry.add_v_site_coords","title":"add_v_site_coords","text":"
add_v_site_coords(\n    v_sites: VSiteMap,\n    conformer: Tensor,\n    force_field: TensorForceField,\n) -> Tensor\n

Appends the coordinates of any virtual sites to a conformer (or batch of conformers) containing only atomic coordinates.

Notes
  • This function only supports appending v-sites to the end of the list of coordinates, and not interleaving them between existing atomic coordinates.

Parameters:

  • v_sites (VSiteMap) \u2013

    A mapping between the virtual sites to add and their corresponding force field parameters.

  • conformer (Tensor) \u2013

    The conformer(s) to add the virtual sites to with shape=(n_atoms, 3) or shape=(n_batches, n_atoms, 3) and units of [\u00c5].

  • force_field (TensorForceField) \u2013

    The force field containing the virtual site parameters.

Returns:

  • Tensor \u2013

    The full conformer(s) with both atomic and virtual site coordinates [\u00c5] with shape=(n_atoms+n_v_sites, 3) or shape=(n_batches, n_atoms+n_v_sites, 3).

Source code in smee/geometry.py
def add_v_site_coords(\n    v_sites: \"smee.VSiteMap\",\n    conformer: torch.Tensor,\n    force_field: \"smee.TensorForceField\",\n) -> torch.Tensor:\n    \"\"\"Appends the coordinates of any virtual sites to a conformer (or batch of\n    conformers) containing only atomic coordinates.\n\n    Notes:\n        * This function only supports appending v-sites to the end of the list of\n          coordinates, and not interleaving them between existing atomic coordinates.\n\n    Args:\n        v_sites: A mapping between the virtual sites to add and their corresponding\n            force field parameters.\n        conformer: The conformer(s) to add the virtual sites to with\n            ``shape=(n_atoms, 3)`` or ``shape=(n_batches, n_atoms, 3)`` and units of\n            [\u00c5].\n        force_field: The force field containing the virtual site parameters.\n\n    Returns:\n        The full conformer(s) with both atomic and virtual site coordinates [\u00c5] with\n        ``shape=(n_atoms+n_v_sites, 3)`` or ``shape=(n_batches, n_atoms+n_v_sites, 3)``.\n    \"\"\"\n\n    v_site_coords = compute_v_site_coords(v_sites, conformer, force_field)\n\n    return torch.cat([conformer, v_site_coords], dim=(1 if conformer.ndim == 3 else 0))\n
"},{"location":"reference/utils/","title":" utils","text":""},{"location":"reference/utils/#smee.utils","title":"utils","text":"

General utility functions

Functions:

  • find_exclusions \u2013

    Find all excluded interaction pairs and their associated scaling factor.

  • ones_like \u2013

    Create a tensor of ones with the same device and type as another tensor.

  • zeros_like \u2013

    Create a tensor of zeros with the same device and type as another tensor.

  • tensor_like \u2013

    Create a tensor with the same device and type as another tensor.

  • arange_like \u2013

    Arange a tensor with the same device and type as another tensor.

  • logsumexp \u2013

    Compute the log of the sum of the exponential of the input elements, optionally

  • to_upper_tri_idx \u2013

    Converts pairs of 2D indices to 1D indices in an upper triangular matrix that

  • geometric_mean \u2013

    Computes the geometric mean of two values 'safely'.

Attributes:

  • EPSILON \u2013

    A small epsilon value used to prevent divide by zero errors.

"},{"location":"reference/utils/#smee.utils.EPSILON","title":"EPSILON module-attribute","text":"
EPSILON = 1e-06\n

A small epsilon value used to prevent divide by zero errors.

"},{"location":"reference/utils/#smee.utils.find_exclusions","title":"find_exclusions","text":"
find_exclusions(\n    topology: Topology, v_sites: Optional[VSiteMap] = None\n) -> dict[tuple[int, int], ExclusionType]\n

Find all excluded interaction pairs and their associated scaling factor.

Parameters:

  • topology (Topology) \u2013

    The topology to find the interaction pairs of.

  • v_sites (Optional[VSiteMap], default: None ) \u2013

    Virtual sites that will be added to the topology.

Returns:

  • A dictionary of the form ``{(atom_idx_1, atom_idx_2 \u2013

    scale}``.

Source code in smee/utils.py
def find_exclusions(\n    topology: openff.toolkit.Topology,\n    v_sites: typing.Optional[\"smee.VSiteMap\"] = None,\n) -> dict[tuple[int, int], ExclusionType]:\n    \"\"\"Find all excluded interaction pairs and their associated scaling factor.\n\n    Args:\n        topology: The topology to find the interaction pairs of.\n        v_sites: Virtual sites that will be added to the topology.\n\n    Returns:\n        A dictionary of the form ``{(atom_idx_1, atom_idx_2): scale}``.\n    \"\"\"\n\n    graph = networkx.from_edgelist(\n        tuple(\n            sorted((topology.atom_index(bond.atom1), topology.atom_index(bond.atom2)))\n        )\n        for bond in topology.bonds\n    )\n\n    if v_sites is not None:\n        for v_site_key in v_sites.keys:\n            v_site_idx = v_sites.key_to_idx[v_site_key]\n            parent_idx = v_site_key.orientation_atom_indices[0]\n\n            for neighbour_idx in graph.neighbors(parent_idx):\n                graph.add_edge(v_site_idx, neighbour_idx)\n\n            graph.add_edge(v_site_idx, parent_idx)\n\n    distances = dict(networkx.all_pairs_shortest_path_length(graph, cutoff=5))\n    distance_to_scale = {1: \"scale_12\", 2: \"scale_13\", 3: \"scale_14\", 4: \"scale_15\"}\n\n    exclusions = {}\n\n    for idx_a in distances:\n        for idx_b, distance in distances[idx_a].items():\n            pair = tuple(sorted((idx_a, idx_b)))\n            scale = distance_to_scale.get(distance)\n\n            if scale is None:\n                continue\n\n            assert pair not in exclusions or exclusions[pair] == scale\n            exclusions[pair] = scale\n\n    return exclusions\n
"},{"location":"reference/utils/#smee.utils.ones_like","title":"ones_like","text":"
ones_like(size: _size, other: Tensor) -> Tensor\n

Create a tensor of ones with the same device and type as another tensor.

Source code in smee/utils.py
def ones_like(size: _size, other: torch.Tensor) -> torch.Tensor:\n    \"\"\"Create a tensor of ones with the same device and type as another tensor.\"\"\"\n    return torch.ones(size, dtype=other.dtype, device=other.device)\n
"},{"location":"reference/utils/#smee.utils.zeros_like","title":"zeros_like","text":"
zeros_like(size: _size, other: Tensor) -> Tensor\n

Create a tensor of zeros with the same device and type as another tensor.

Source code in smee/utils.py
def zeros_like(size: _size, other: torch.Tensor) -> torch.Tensor:\n    \"\"\"Create a tensor of zeros with the same device and type as another tensor.\"\"\"\n    return torch.zeros(size, dtype=other.dtype, device=other.device)\n
"},{"location":"reference/utils/#smee.utils.tensor_like","title":"tensor_like","text":"
tensor_like(data: Any, other: Tensor) -> Tensor\n

Create a tensor with the same device and type as another tensor.

Source code in smee/utils.py
def tensor_like(data: typing.Any, other: torch.Tensor) -> torch.Tensor:\n    \"\"\"Create a tensor with the same device and type as another tensor.\"\"\"\n\n    if isinstance(data, torch.Tensor):\n        return data.clone().detach().to(other.device, other.dtype)\n\n    return torch.tensor(data, dtype=other.dtype, device=other.device)\n
"},{"location":"reference/utils/#smee.utils.arange_like","title":"arange_like","text":"
arange_like(end: int, other: Tensor) -> Tensor\n

Arange a tensor with the same device and type as another tensor.

Source code in smee/utils.py
def arange_like(end: int, other: torch.Tensor) -> torch.Tensor:\n    \"\"\"Arange a tensor with the same device and type as another tensor.\"\"\"\n    return torch.arange(end, dtype=other.dtype, device=other.device)\n
"},{"location":"reference/utils/#smee.utils.logsumexp","title":"logsumexp","text":"
logsumexp(\n    a: Tensor,\n    dim: int,\n    keepdim: bool = False,\n    b: Tensor | None = None,\n    return_sign: bool = False,\n) -> Tensor | tuple[Tensor, Tensor]\n

Compute the log of the sum of the exponential of the input elements, optionally with each element multiplied by a scaling factor.

Notes

This should be removed if torch.logsumexp is updated to support scaling factors.

Parameters:

  • a (Tensor) \u2013

    The elements that should be summed over.

  • dim (int) \u2013

    The dimension to sum over.

  • keepdim (bool, default: False ) \u2013

    Whether to keep the summed dimension.

  • b (Tensor | None, default: None ) \u2013

    The scaling factor to multiply each element by.

Returns:

  • Tensor | tuple[Tensor, Tensor] \u2013

    The log of the sum of exponential of the a elements.

Source code in smee/utils.py
def logsumexp(\n    a: torch.Tensor,\n    dim: int,\n    keepdim: bool = False,\n    b: torch.Tensor | None = None,\n    return_sign: bool = False,\n) -> torch.Tensor | tuple[torch.Tensor, torch.Tensor]:\n    \"\"\"Compute the log of the sum of the exponential of the input elements, optionally\n    with each element multiplied by a scaling factor.\n\n    Notes:\n        This should be removed if torch.logsumexp is updated to support scaling factors.\n\n    Args:\n        a: The elements that should be summed over.\n        dim: The dimension to sum over.\n        keepdim: Whether to keep the summed dimension.\n        b: The scaling factor to multiply each element by.\n\n    Returns:\n        The log of the sum of exponential of the a elements.\n    \"\"\"\n    a_type = a.dtype\n\n    if b is None:\n        assert return_sign is False\n        return torch.logsumexp(a, dim, keepdim)\n\n    a = a.double()\n    b = b if b is not None else b.double()\n\n    a, b = torch.broadcast_tensors(a, b)\n\n    if torch.any(b == 0):\n        a[b == 0] = -torch.inf\n\n    a_max = torch.amax(a, dim=dim, keepdim=True)\n\n    if a_max.ndim > 0:\n        a_max[~torch.isfinite(a_max)] = 0\n    elif not torch.isfinite(a_max):\n        a_max = 0\n\n    exp_sum = torch.sum(b * torch.exp(a - a_max), dim=dim, keepdim=keepdim)\n    sign = None\n\n    if return_sign:\n        sign = torch.sign(exp_sum)\n        exp_sum = exp_sum * sign\n\n    ln_exp_sum = torch.log(exp_sum)\n\n    if not keepdim:\n        a_max = torch.squeeze(a_max, dim=dim)\n\n    ln_exp_sum += a_max\n    ln_exp_sum = ln_exp_sum.to(a_type)\n\n    if return_sign:\n        return ln_exp_sum, sign.to(a_type)\n    else:\n        return ln_exp_sum\n
"},{"location":"reference/utils/#smee.utils.to_upper_tri_idx","title":"to_upper_tri_idx","text":"
to_upper_tri_idx(\n    i: Tensor, j: Tensor, n: int, include_diag: bool = False\n) -> Tensor\n

Converts pairs of 2D indices to 1D indices in an upper triangular matrix that excludes the diagonal.

Parameters:

  • i (Tensor) \u2013

    A tensor of the indices along the first axis with shape=(n_pairs,).

  • j (Tensor) \u2013

    A tensor of the indices along the second axis with shape=(n_pairs,).

  • n (int) \u2013

    The size of the matrix.

  • include_diag (bool, default: False ) \u2013

    Whether the diagonal is included in the upper triangular matrix.

Returns:

  • Tensor \u2013

    A tensor of the indices in the upper triangular matrix with shape=(n_pairs * (n_pairs - 1) // 2,).

Source code in smee/utils.py
def to_upper_tri_idx(\n    i: torch.Tensor, j: torch.Tensor, n: int, include_diag: bool = False\n) -> torch.Tensor:\n    \"\"\"Converts pairs of 2D indices to 1D indices in an upper triangular matrix that\n    excludes the diagonal.\n\n    Args:\n        i: A tensor of the indices along the first axis with ``shape=(n_pairs,)``.\n        j: A tensor of the indices along the second axis with ``shape=(n_pairs,)``.\n        n: The size of the matrix.\n        include_diag: Whether the diagonal is included in the upper triangular matrix.\n\n    Returns:\n        A tensor of the indices in the upper triangular matrix with\n        ``shape=(n_pairs * (n_pairs - 1) // 2,)``.\n    \"\"\"\n\n    if not include_diag:\n        assert (i < j).all(), \"i must be less than j\"\n        return (i * (2 * n - i - 1)) // 2 + j - i - 1\n\n    assert (i <= j).all(), \"i must be less than or equal to j\"\n    return (i * (2 * n - i + 1)) // 2 + j - i\n
"},{"location":"reference/utils/#smee.utils.geometric_mean","title":"geometric_mean","text":"
geometric_mean(eps_a: Tensor, eps_b: Tensor) -> Tensor\n

Computes the geometric mean of two values 'safely'.

A small epsilon (smee.utils.EPSILON) is added when computing the gradient in cases where the mean is zero to prevent divide by zero errors.

Parameters:

  • eps_a (Tensor) \u2013

    The first value.

  • eps_b (Tensor) \u2013

    The second value.

Returns:

  • Tensor \u2013

    The geometric mean of the two values.

Source code in smee/utils.py
def geometric_mean(eps_a: torch.Tensor, eps_b: torch.Tensor) -> torch.Tensor:\n    \"\"\"Computes the geometric mean of two values 'safely'.\n\n    A small epsilon (``smee.utils.EPSILON``) is added when computing the gradient in\n    cases where the mean is zero to prevent divide by zero errors.\n\n    Args:\n        eps_a: The first value.\n        eps_b: The second value.\n\n    Returns:\n        The geometric mean of the two values.\n    \"\"\"\n\n    return _SafeGeometricMean.apply(eps_a, eps_b)\n
"},{"location":"reference/converters/","title":"Index","text":""},{"location":"reference/converters/#smee.converters","title":"converters","text":"

Convert to / from smee tensor representations.

Modules:

  • openff \u2013

    Tensor representations of SMIRNOFF force fields.

  • openmm \u2013

    Convert tensor representations into OpenMM systems.

Functions:

  • convert_handlers \u2013

    Convert a set of SMIRNOFF parameter handlers into a set of tensor potentials.

  • convert_interchange \u2013

    Convert a list of interchange objects into tensor potentials.

  • smirnoff_parameter_converter \u2013

    A decorator used to flag a function as being able to convert a parameter handlers

  • convert_to_openmm_force \u2013

    Convert a smee potential to OpenMM forces.

  • convert_to_openmm_system \u2013

    Convert a smee force field and system / topology into an OpenMM system.

  • convert_to_openmm_topology \u2013

    Convert a smee system to an OpenMM topology.

"},{"location":"reference/converters/#smee.converters.convert_handlers","title":"convert_handlers","text":"
convert_handlers(\n    handlers: list[SMIRNOFFCollection],\n    topologies: list[Topology],\n    v_site_maps: list[VSiteMap | None] | None = None,\n    potentials: (\n        list[tuple[TensorPotential, list[ParameterMap]]]\n        | None\n    ) = None,\n) -> list[tuple[TensorPotential, list[ParameterMap]]]\n

Convert a set of SMIRNOFF parameter handlers into a set of tensor potentials.

Parameters:

  • handlers (list[SMIRNOFFCollection]) \u2013

    The SMIRNOFF parameter handler collections for a set of interchange objects to convert.

  • topologies (list[Topology]) \u2013

    The topologies associated with each interchange object.

  • v_site_maps (list[VSiteMap | None] | None, default: None ) \u2013

    The v-site maps associated with each interchange object.

  • potentials (list[tuple[TensorPotential, list[ParameterMap]]] | None, default: None ) \u2013

    Already converted parameter handlers that may be required as dependencies.

Returns:

  • list[tuple[TensorPotential, list[ParameterMap]]] \u2013

    The potential containing the values of the parameters in each handler collection, and a list of maps (one per topology) between molecule elements (e.g. bond indices) and parameter indices.

Examples:

>>> from openff.toolkit import ForceField, Molecule\n>>> from openff.interchange import Interchange\n>>>\n>>> force_field = ForceField(\"openff_unconstrained-2.0.0.offxml\")\n>>> molecules = [Molecule.from_smiles(\"CCO\"), Molecule.from_smiles(\"CC\")]\n>>>\n>>> interchanges = [\n...     Interchange.from_smirnoff(force_field, molecule.to_topology())\n...     for molecule in molecules\n... ]\n>>> vdw_handlers = [\n...     interchange.collections[\"vdW\"] for interchange in interchanges\n... ]\n>>>\n>>> vdw_potential, applied_vdw_parameters = convert_handlers(interchanges)\n
Source code in smee/converters/openff/_openff.py
def convert_handlers(\n    handlers: list[openff.interchange.smirnoff.SMIRNOFFCollection],\n    topologies: list[openff.toolkit.Topology],\n    v_site_maps: list[smee.VSiteMap | None] | None = None,\n    potentials: (\n        list[tuple[smee.TensorPotential, list[smee.ParameterMap]]] | None\n    ) = None,\n) -> list[tuple[smee.TensorPotential, list[smee.ParameterMap]]]:\n    \"\"\"Convert a set of SMIRNOFF parameter handlers into a set of tensor potentials.\n\n    Args:\n        handlers: The SMIRNOFF parameter handler collections for a set of interchange\n            objects to convert.\n        topologies: The topologies associated with each interchange object.\n        v_site_maps: The v-site maps associated with each interchange object.\n        potentials: Already converted parameter handlers that may be required as\n            dependencies.\n\n    Returns:\n        The potential containing the values of the parameters in each handler\n        collection, and a list of maps (one per topology) between molecule elements\n        (e.g. bond indices) and parameter indices.\n\n    Examples:\n\n        >>> from openff.toolkit import ForceField, Molecule\n        >>> from openff.interchange import Interchange\n        >>>\n        >>> force_field = ForceField(\"openff_unconstrained-2.0.0.offxml\")\n        >>> molecules = [Molecule.from_smiles(\"CCO\"), Molecule.from_smiles(\"CC\")]\n        >>>\n        >>> interchanges = [\n        ...     Interchange.from_smirnoff(force_field, molecule.to_topology())\n        ...     for molecule in molecules\n        ... ]\n        >>> vdw_handlers = [\n        ...     interchange.collections[\"vdW\"] for interchange in interchanges\n        ... ]\n        >>>\n        >>> vdw_potential, applied_vdw_parameters = convert_handlers(interchanges)\n    \"\"\"\n    importlib.import_module(\"smee.converters.openff.nonbonded\")\n    importlib.import_module(\"smee.converters.openff.valence\")\n\n    handler_types = {handler.type for handler in handlers}\n    assert len(handler_types) == 1, \"multiple handler types found\"\n    handler_type = next(iter(handler_types))\n\n    assert len(handlers) == len(topologies), \"mismatched number of topologies\"\n\n    if handler_type not in _CONVERTERS:\n        raise NotImplementedError(f\"{handler_type} handlers is not yet supported.\")\n\n    converter = _CONVERTERS[handler_type]\n    converter_spec = inspect.signature(converter.fn)\n    converter_kwargs = {}\n\n    if \"topologies\" in converter_spec.parameters:\n        converter_kwargs[\"topologies\"] = topologies\n    if \"v_site_maps\" in converter_spec.parameters:\n        assert v_site_maps is not None, \"v-site maps must be provided\"\n        converter_kwargs[\"v_site_maps\"] = v_site_maps\n\n    potentials_by_type = (\n        {}\n        if potentials is None\n        else {potential.type: (potential, maps) for potential, maps in potentials}\n    )\n\n    dependencies = {}\n    depends_on = converter.depends_on if converter.depends_on is not None else []\n\n    if len(depends_on) > 0:\n        missing_deps = {dep for dep in depends_on if dep not in potentials_by_type}\n        assert len(missing_deps) == 0, \"missing dependencies\"\n\n        dependencies = {dep: potentials_by_type[dep] for dep in depends_on}\n        assert \"dependencies\" in converter_spec.parameters, \"dependencies not accepted\"\n\n    if \"dependencies\" in converter_spec.parameters:\n        converter_kwargs[\"dependencies\"] = dependencies\n\n    converted = converter.fn(handlers, **converter_kwargs)\n    converted = [converted] if not isinstance(converted, list) else converted\n\n    converted_by_type = {\n        potential.type: (potential, maps) for potential, maps in converted\n    }\n    assert len(converted_by_type) == len(converted), \"duplicate potentials found\"\n\n    potentials_by_type = {\n        **{\n            potential.type: (potential, maps)\n            for potential, maps in potentials_by_type.values()\n            if potential.type not in depends_on\n            and potential.type not in converted_by_type\n        },\n        **converted_by_type,\n    }\n\n    return [*potentials_by_type.values()]\n
"},{"location":"reference/converters/#smee.converters.convert_interchange","title":"convert_interchange","text":"
convert_interchange(\n    interchange: Interchange | list[Interchange],\n) -> tuple[TensorForceField, list[TensorTopology]]\n

Convert a list of interchange objects into tensor potentials.

Parameters:

  • interchange (Interchange | list[Interchange]) \u2013

    The list of (or singile) interchange objects to convert into tensor potentials.

Returns:

  • tuple[TensorForceField, list[TensorTopology]] \u2013

    The tensor force field containing the parameters of each handler, and a list (one per interchange) of objects mapping molecule elements (e.g. bonds, angles) to corresponding handler parameters.

Examples:

>>> from openff.toolkit import ForceField, Molecule\n>>> from openff.interchange import Interchange\n>>>\n>>> force_field = ForceField(\"openff_unconstrained-2.0.0.offxml\")\n>>> molecules = [Molecule.from_smiles(\"CCO\"), Molecule.from_smiles(\"CC\")]\n>>>\n>>> interchanges = [\n...     Interchange.from_smirnoff(force_field, molecule.to_topology())\n...     for molecule in molecules\n... ]\n>>>\n>>> tensor_ff, tensor_topologies = convert_interchange(interchanges)\n
Source code in smee/converters/openff/_openff.py
def convert_interchange(\n    interchange: openff.interchange.Interchange | list[openff.interchange.Interchange],\n) -> tuple[smee.TensorForceField, list[smee.TensorTopology]]:\n    \"\"\"Convert a list of interchange objects into tensor potentials.\n\n    Args:\n        interchange: The list of (or singile) interchange objects to convert into\n            tensor potentials.\n\n    Returns:\n        The tensor force field containing the parameters of each handler, and a list\n        (one per interchange) of objects mapping molecule elements (e.g. bonds, angles)\n        to corresponding handler parameters.\n\n    Examples:\n\n        >>> from openff.toolkit import ForceField, Molecule\n        >>> from openff.interchange import Interchange\n        >>>\n        >>> force_field = ForceField(\"openff_unconstrained-2.0.0.offxml\")\n        >>> molecules = [Molecule.from_smiles(\"CCO\"), Molecule.from_smiles(\"CC\")]\n        >>>\n        >>> interchanges = [\n        ...     Interchange.from_smirnoff(force_field, molecule.to_topology())\n        ...     for molecule in molecules\n        ... ]\n        >>>\n        >>> tensor_ff, tensor_topologies = convert_interchange(interchanges)\n    \"\"\"\n    importlib.import_module(\"smee.converters.openff.nonbonded\")\n    importlib.import_module(\"smee.converters.openff.valence\")\n\n    interchanges = (\n        [interchange]\n        if isinstance(interchange, openff.interchange.Interchange)\n        else interchange\n    )\n    topologies = []\n\n    handler_types = {\n        handler_type\n        for interchange in interchanges\n        for handler_type in interchange.collections\n    }\n    handlers_by_type = {handler_type: [] for handler_type in sorted(handler_types)}\n\n    for interchange in interchanges:\n        for handler_type in handlers_by_type:\n            handler = (\n                None\n                if handler_type not in interchange.collections\n                else interchange.collections[handler_type]\n            )\n            handlers_by_type[handler_type].append(handler)\n\n        topologies.append(interchange.topology)\n\n    v_sites, v_site_maps = None, [None] * len(topologies)\n\n    if \"VirtualSites\" in handlers_by_type:\n        v_sites, v_site_maps = _convert_v_sites(\n            handlers_by_type.pop(\"VirtualSites\"), topologies\n        )\n\n    constraints = [None] * len(topologies)\n\n    if \"Constraints\" in handlers_by_type:\n        constraints = _convert_constraints(handlers_by_type.pop(\"Constraints\"))\n\n    conversion_order = _resolve_conversion_order([*handlers_by_type])\n    converted = []\n\n    for handler_type in conversion_order:\n        handlers = handlers_by_type[handler_type]\n\n        if (\n            sum(len(handler.potentials) for handler in handlers if handler is not None)\n            == 0\n        ):\n            continue\n\n        converted = convert_handlers(handlers, topologies, v_site_maps, converted)\n\n    # handlers may either return multiple potentials, or condense multiple already\n    # converted potentials into a single one (e.g. electrostatics into some polarizable\n    # potential)\n    potentials = []\n    parameter_maps_by_handler = {}\n\n    for potential, parameter_maps in converted:\n        potentials.append(potential)\n        parameter_maps_by_handler[potential.type] = parameter_maps\n\n    tensor_topologies = [\n        _convert_topology(\n            topology,\n            {\n                potential.type: parameter_maps_by_handler[potential.type][i]\n                for potential in potentials\n            },\n            v_site_maps[i],\n            constraints[i],\n        )\n        for i, topology in enumerate(topologies)\n    ]\n\n    tensor_force_field = smee.TensorForceField(potentials, v_sites)\n    return tensor_force_field, tensor_topologies\n
"},{"location":"reference/converters/#smee.converters.smirnoff_parameter_converter","title":"smirnoff_parameter_converter","text":"
smirnoff_parameter_converter(\n    type_: str,\n    default_units: dict[str, Unit],\n    depends_on: list[str] | None = None,\n)\n

A decorator used to flag a function as being able to convert a parameter handlers parameters into tensors.

Parameters:

  • type_ (str) \u2013

    The type of parameter handler that the decorated function can convert.

  • default_units (dict[str, Unit]) \u2013

    The default units of each parameter in the handler.

  • depends_on (list[str] | None, default: None ) \u2013

    The names of other handlers that this handler depends on. When set, the convert function should additionally take in a list of the already converted potentials and return a new list of potentials that should either include or replace the original potentials.

Source code in smee/converters/openff/_openff.py
def smirnoff_parameter_converter(\n    type_: str,\n    default_units: dict[str, openff.units.Unit],\n    depends_on: list[str] | None = None,\n):\n    \"\"\"A decorator used to flag a function as being able to convert a parameter handlers\n    parameters into tensors.\n\n    Args:\n        type_: The type of parameter handler that the decorated function can convert.\n        default_units: The default units of each parameter in the handler.\n        depends_on: The names of other handlers that this handler depends on. When set,\n            the convert function should additionally take in a list of the already\n            converted potentials and return a new list of potentials that should either\n            include or replace the original potentials.\n    \"\"\"\n\n    def parameter_converter_inner(func):\n        if type_ in _CONVERTERS:\n            raise KeyError(f\"A {type_} converter is already registered.\")\n\n        _CONVERTERS[type_] = _Converter(func, default_units, depends_on)\n\n        return func\n\n    return parameter_converter_inner\n
"},{"location":"reference/converters/#smee.converters.convert_to_openmm_force","title":"convert_to_openmm_force","text":"
convert_to_openmm_force(\n    potential: TensorPotential, system: TensorSystem\n) -> list[Force]\n

Convert a smee potential to OpenMM forces.

Some potentials may return multiple forces, e.g. a vdW potential may return one force containing intermolecular interactions and another containing intramolecular interactions.

See Also

potential_converter: for how to define a converter function.

Parameters:

  • potential (TensorPotential) \u2013

    The potential to convert.

  • system (TensorSystem) \u2013

    The system to convert.

Returns:

  • list[Force] \u2013

    The OpenMM force(s).

Source code in smee/converters/openmm/_openmm.py
def convert_to_openmm_force(\n    potential: smee.TensorPotential, system: smee.TensorSystem\n) -> list[openmm.Force]:\n    \"\"\"Convert a ``smee`` potential to OpenMM forces.\n\n    Some potentials may return multiple forces, e.g. a vdW potential may return one\n    force containing intermolecular interactions and another containing intramolecular\n    interactions.\n\n    See Also:\n        potential_converter: for how to define a converter function.\n\n    Args:\n        potential: The potential to convert.\n        system: The system to convert.\n\n    Returns:\n        The OpenMM force(s).\n    \"\"\"\n    # register the built-in converter functions\n    importlib.import_module(\"smee.converters.openmm.nonbonded\")\n    importlib.import_module(\"smee.converters.openmm.valence\")\n\n    potential = potential.to(\"cpu\")\n    system = system.to(\"cpu\")\n\n    if potential.exceptions is not None and potential.type != \"vdW\":\n        raise NotImplementedError(\"exceptions are only supported for vdW potentials\")\n\n    converter_key = (str(potential.type), str(potential.fn))\n\n    if converter_key not in _CONVERTER_FUNCTIONS:\n        raise NotImplementedError(\n            f\"cannot convert type={potential.type} fn={potential.fn} to an OpenMM force\"\n        )\n\n    forces = _CONVERTER_FUNCTIONS[converter_key](potential, system)\n    return forces if isinstance(forces, (list, tuple)) else [forces]\n
"},{"location":"reference/converters/#smee.converters.convert_to_openmm_system","title":"convert_to_openmm_system","text":"
convert_to_openmm_system(\n    force_field: TensorForceField,\n    system: TensorSystem | TensorTopology,\n) -> System\n

Convert a smee force field and system / topology into an OpenMM system.

Parameters:

  • force_field (TensorForceField) \u2013

    The force field parameters.

  • system (TensorSystem | TensorTopology) \u2013

    The system / topology to convert.

Returns:

  • System \u2013

    The OpenMM system.

Source code in smee/converters/openmm/_openmm.py
def convert_to_openmm_system(\n    force_field: smee.TensorForceField,\n    system: smee.TensorSystem | smee.TensorTopology,\n) -> openmm.System:\n    \"\"\"Convert a ``smee`` force field and system / topology into an OpenMM system.\n\n    Args:\n        force_field: The force field parameters.\n        system: The system / topology to convert.\n\n    Returns:\n        The OpenMM system.\n    \"\"\"\n\n    system: smee.TensorSystem = (\n        system\n        if isinstance(system, smee.TensorSystem)\n        else smee.TensorSystem([system], [1], False)\n    )\n\n    force_field = force_field.to(\"cpu\")\n    system = system.to(\"cpu\")\n\n    omm_forces = {\n        potential_type: convert_to_openmm_force(potential, system)\n        for potential_type, potential in force_field.potentials_by_type.items()\n    }\n    omm_system = create_openmm_system(system, force_field.v_sites)\n\n    if (\n        \"Electrostatics\" in omm_forces\n        and \"vdW\" in omm_forces\n        and len(omm_forces[\"vdW\"]) == 1\n        and isinstance(omm_forces[\"vdW\"][0], openmm.NonbondedForce)\n    ):\n        (electrostatic_force,) = omm_forces.pop(\"Electrostatics\")\n        (vdw_force,) = omm_forces.pop(\"vdW\")\n\n        nonbonded_force = _combine_nonbonded(vdw_force, electrostatic_force)\n        omm_system.addForce(nonbonded_force)\n\n    for forces in omm_forces.values():\n        for force in forces:\n            omm_system.addForce(force)\n\n    _apply_constraints(omm_system, system)\n\n    return omm_system\n
"},{"location":"reference/converters/#smee.converters.convert_to_openmm_topology","title":"convert_to_openmm_topology","text":"
convert_to_openmm_topology(\n    system: TensorSystem | TensorTopology,\n) -> Topology\n

Convert a smee system to an OpenMM topology.

Notes

Virtual sites are given the element symbol \"X\" and atomic number 82.

Parameters:

  • system (TensorSystem | TensorTopology) \u2013

    The system to convert.

Returns:

  • Topology \u2013

    The OpenMM topology.

Source code in smee/converters/openmm/_openmm.py
def convert_to_openmm_topology(\n    system: smee.TensorSystem | smee.TensorTopology,\n) -> openmm.app.Topology:\n    \"\"\"Convert a ``smee`` system to an OpenMM topology.\n\n    Notes:\n        Virtual sites are given the element symbol \"X\" and atomic number 82.\n\n    Args:\n        system: The system to convert.\n\n    Returns:\n        The OpenMM topology.\n    \"\"\"\n    system: smee.TensorSystem = (\n        system\n        if isinstance(system, smee.TensorSystem)\n        else smee.TensorSystem([system], [1], False)\n    )\n\n    omm_topology = openmm.app.Topology()\n\n    for topology, n_copies in zip(system.topologies, system.n_copies, strict=True):\n        chain = omm_topology.addChain()\n\n        is_water = topology.n_atoms == 3 and sorted(\n            int(v) for v in topology.atomic_nums\n        ) == [1, 1, 8]\n\n        residue_name = \"WAT\" if is_water else \"UNK\"\n\n        for _ in range(n_copies):\n            residue = omm_topology.addResidue(residue_name, chain)\n            element_counter = collections.defaultdict(int)\n\n            atoms = {}\n\n            for i, atomic_num in enumerate(topology.atomic_nums):\n                element = openmm.app.Element.getByAtomicNumber(int(atomic_num))\n                element_counter[element.symbol] += 1\n\n                name = element.symbol + (\n                    \"\"\n                    if element_counter[element.symbol] == 1 and element.symbol != \"H\"\n                    else f\"{element_counter[element.symbol]}\"\n                )\n                atoms[i] = omm_topology.addAtom(name, element, residue)\n\n            for _ in range(topology.n_v_sites):\n                omm_topology.addAtom(\n                    \"X\", openmm.app.Element.getByAtomicNumber(82), residue\n                )\n\n            for bond_idxs, bond_order in zip(\n                topology.bond_idxs, topology.bond_orders, strict=True\n            ):\n                idx_a, idx_b = int(bond_idxs[0]), int(bond_idxs[1])\n\n                bond_order = int(bond_order)\n                bond_type = {\n                    1: openmm.app.Single,\n                    2: openmm.app.Double,\n                    3: openmm.app.Triple,\n                }[bond_order]\n\n                omm_topology.addBond(atoms[idx_a], atoms[idx_b], bond_type, bond_order)\n\n    return omm_topology\n
"},{"location":"reference/converters/openff/","title":"Index","text":""},{"location":"reference/converters/openff/#smee.converters.openff","title":"openff","text":"

Tensor representations of SMIRNOFF force fields.

Modules:

  • nonbonded \u2013

    Convert SMIRNOFF non-bonded parameters into tensors.

  • valence \u2013

    Convert SMIRNOFF valence parameters into tensors.

Functions:

  • convert_handlers \u2013

    Convert a set of SMIRNOFF parameter handlers into a set of tensor potentials.

  • convert_interchange \u2013

    Convert a list of interchange objects into tensor potentials.

  • smirnoff_parameter_converter \u2013

    A decorator used to flag a function as being able to convert a parameter handlers

"},{"location":"reference/converters/openff/#smee.converters.openff.convert_handlers","title":"convert_handlers","text":"
convert_handlers(\n    handlers: list[SMIRNOFFCollection],\n    topologies: list[Topology],\n    v_site_maps: list[VSiteMap | None] | None = None,\n    potentials: (\n        list[tuple[TensorPotential, list[ParameterMap]]]\n        | None\n    ) = None,\n) -> list[tuple[TensorPotential, list[ParameterMap]]]\n

Convert a set of SMIRNOFF parameter handlers into a set of tensor potentials.

Parameters:

  • handlers (list[SMIRNOFFCollection]) \u2013

    The SMIRNOFF parameter handler collections for a set of interchange objects to convert.

  • topologies (list[Topology]) \u2013

    The topologies associated with each interchange object.

  • v_site_maps (list[VSiteMap | None] | None, default: None ) \u2013

    The v-site maps associated with each interchange object.

  • potentials (list[tuple[TensorPotential, list[ParameterMap]]] | None, default: None ) \u2013

    Already converted parameter handlers that may be required as dependencies.

Returns:

  • list[tuple[TensorPotential, list[ParameterMap]]] \u2013

    The potential containing the values of the parameters in each handler collection, and a list of maps (one per topology) between molecule elements (e.g. bond indices) and parameter indices.

Examples:

>>> from openff.toolkit import ForceField, Molecule\n>>> from openff.interchange import Interchange\n>>>\n>>> force_field = ForceField(\"openff_unconstrained-2.0.0.offxml\")\n>>> molecules = [Molecule.from_smiles(\"CCO\"), Molecule.from_smiles(\"CC\")]\n>>>\n>>> interchanges = [\n...     Interchange.from_smirnoff(force_field, molecule.to_topology())\n...     for molecule in molecules\n... ]\n>>> vdw_handlers = [\n...     interchange.collections[\"vdW\"] for interchange in interchanges\n... ]\n>>>\n>>> vdw_potential, applied_vdw_parameters = convert_handlers(interchanges)\n
Source code in smee/converters/openff/_openff.py
def convert_handlers(\n    handlers: list[openff.interchange.smirnoff.SMIRNOFFCollection],\n    topologies: list[openff.toolkit.Topology],\n    v_site_maps: list[smee.VSiteMap | None] | None = None,\n    potentials: (\n        list[tuple[smee.TensorPotential, list[smee.ParameterMap]]] | None\n    ) = None,\n) -> list[tuple[smee.TensorPotential, list[smee.ParameterMap]]]:\n    \"\"\"Convert a set of SMIRNOFF parameter handlers into a set of tensor potentials.\n\n    Args:\n        handlers: The SMIRNOFF parameter handler collections for a set of interchange\n            objects to convert.\n        topologies: The topologies associated with each interchange object.\n        v_site_maps: The v-site maps associated with each interchange object.\n        potentials: Already converted parameter handlers that may be required as\n            dependencies.\n\n    Returns:\n        The potential containing the values of the parameters in each handler\n        collection, and a list of maps (one per topology) between molecule elements\n        (e.g. bond indices) and parameter indices.\n\n    Examples:\n\n        >>> from openff.toolkit import ForceField, Molecule\n        >>> from openff.interchange import Interchange\n        >>>\n        >>> force_field = ForceField(\"openff_unconstrained-2.0.0.offxml\")\n        >>> molecules = [Molecule.from_smiles(\"CCO\"), Molecule.from_smiles(\"CC\")]\n        >>>\n        >>> interchanges = [\n        ...     Interchange.from_smirnoff(force_field, molecule.to_topology())\n        ...     for molecule in molecules\n        ... ]\n        >>> vdw_handlers = [\n        ...     interchange.collections[\"vdW\"] for interchange in interchanges\n        ... ]\n        >>>\n        >>> vdw_potential, applied_vdw_parameters = convert_handlers(interchanges)\n    \"\"\"\n    importlib.import_module(\"smee.converters.openff.nonbonded\")\n    importlib.import_module(\"smee.converters.openff.valence\")\n\n    handler_types = {handler.type for handler in handlers}\n    assert len(handler_types) == 1, \"multiple handler types found\"\n    handler_type = next(iter(handler_types))\n\n    assert len(handlers) == len(topologies), \"mismatched number of topologies\"\n\n    if handler_type not in _CONVERTERS:\n        raise NotImplementedError(f\"{handler_type} handlers is not yet supported.\")\n\n    converter = _CONVERTERS[handler_type]\n    converter_spec = inspect.signature(converter.fn)\n    converter_kwargs = {}\n\n    if \"topologies\" in converter_spec.parameters:\n        converter_kwargs[\"topologies\"] = topologies\n    if \"v_site_maps\" in converter_spec.parameters:\n        assert v_site_maps is not None, \"v-site maps must be provided\"\n        converter_kwargs[\"v_site_maps\"] = v_site_maps\n\n    potentials_by_type = (\n        {}\n        if potentials is None\n        else {potential.type: (potential, maps) for potential, maps in potentials}\n    )\n\n    dependencies = {}\n    depends_on = converter.depends_on if converter.depends_on is not None else []\n\n    if len(depends_on) > 0:\n        missing_deps = {dep for dep in depends_on if dep not in potentials_by_type}\n        assert len(missing_deps) == 0, \"missing dependencies\"\n\n        dependencies = {dep: potentials_by_type[dep] for dep in depends_on}\n        assert \"dependencies\" in converter_spec.parameters, \"dependencies not accepted\"\n\n    if \"dependencies\" in converter_spec.parameters:\n        converter_kwargs[\"dependencies\"] = dependencies\n\n    converted = converter.fn(handlers, **converter_kwargs)\n    converted = [converted] if not isinstance(converted, list) else converted\n\n    converted_by_type = {\n        potential.type: (potential, maps) for potential, maps in converted\n    }\n    assert len(converted_by_type) == len(converted), \"duplicate potentials found\"\n\n    potentials_by_type = {\n        **{\n            potential.type: (potential, maps)\n            for potential, maps in potentials_by_type.values()\n            if potential.type not in depends_on\n            and potential.type not in converted_by_type\n        },\n        **converted_by_type,\n    }\n\n    return [*potentials_by_type.values()]\n
"},{"location":"reference/converters/openff/#smee.converters.openff.convert_interchange","title":"convert_interchange","text":"
convert_interchange(\n    interchange: Interchange | list[Interchange],\n) -> tuple[TensorForceField, list[TensorTopology]]\n

Convert a list of interchange objects into tensor potentials.

Parameters:

  • interchange (Interchange | list[Interchange]) \u2013

    The list of (or singile) interchange objects to convert into tensor potentials.

Returns:

  • tuple[TensorForceField, list[TensorTopology]] \u2013

    The tensor force field containing the parameters of each handler, and a list (one per interchange) of objects mapping molecule elements (e.g. bonds, angles) to corresponding handler parameters.

Examples:

>>> from openff.toolkit import ForceField, Molecule\n>>> from openff.interchange import Interchange\n>>>\n>>> force_field = ForceField(\"openff_unconstrained-2.0.0.offxml\")\n>>> molecules = [Molecule.from_smiles(\"CCO\"), Molecule.from_smiles(\"CC\")]\n>>>\n>>> interchanges = [\n...     Interchange.from_smirnoff(force_field, molecule.to_topology())\n...     for molecule in molecules\n... ]\n>>>\n>>> tensor_ff, tensor_topologies = convert_interchange(interchanges)\n
Source code in smee/converters/openff/_openff.py
def convert_interchange(\n    interchange: openff.interchange.Interchange | list[openff.interchange.Interchange],\n) -> tuple[smee.TensorForceField, list[smee.TensorTopology]]:\n    \"\"\"Convert a list of interchange objects into tensor potentials.\n\n    Args:\n        interchange: The list of (or singile) interchange objects to convert into\n            tensor potentials.\n\n    Returns:\n        The tensor force field containing the parameters of each handler, and a list\n        (one per interchange) of objects mapping molecule elements (e.g. bonds, angles)\n        to corresponding handler parameters.\n\n    Examples:\n\n        >>> from openff.toolkit import ForceField, Molecule\n        >>> from openff.interchange import Interchange\n        >>>\n        >>> force_field = ForceField(\"openff_unconstrained-2.0.0.offxml\")\n        >>> molecules = [Molecule.from_smiles(\"CCO\"), Molecule.from_smiles(\"CC\")]\n        >>>\n        >>> interchanges = [\n        ...     Interchange.from_smirnoff(force_field, molecule.to_topology())\n        ...     for molecule in molecules\n        ... ]\n        >>>\n        >>> tensor_ff, tensor_topologies = convert_interchange(interchanges)\n    \"\"\"\n    importlib.import_module(\"smee.converters.openff.nonbonded\")\n    importlib.import_module(\"smee.converters.openff.valence\")\n\n    interchanges = (\n        [interchange]\n        if isinstance(interchange, openff.interchange.Interchange)\n        else interchange\n    )\n    topologies = []\n\n    handler_types = {\n        handler_type\n        for interchange in interchanges\n        for handler_type in interchange.collections\n    }\n    handlers_by_type = {handler_type: [] for handler_type in sorted(handler_types)}\n\n    for interchange in interchanges:\n        for handler_type in handlers_by_type:\n            handler = (\n                None\n                if handler_type not in interchange.collections\n                else interchange.collections[handler_type]\n            )\n            handlers_by_type[handler_type].append(handler)\n\n        topologies.append(interchange.topology)\n\n    v_sites, v_site_maps = None, [None] * len(topologies)\n\n    if \"VirtualSites\" in handlers_by_type:\n        v_sites, v_site_maps = _convert_v_sites(\n            handlers_by_type.pop(\"VirtualSites\"), topologies\n        )\n\n    constraints = [None] * len(topologies)\n\n    if \"Constraints\" in handlers_by_type:\n        constraints = _convert_constraints(handlers_by_type.pop(\"Constraints\"))\n\n    conversion_order = _resolve_conversion_order([*handlers_by_type])\n    converted = []\n\n    for handler_type in conversion_order:\n        handlers = handlers_by_type[handler_type]\n\n        if (\n            sum(len(handler.potentials) for handler in handlers if handler is not None)\n            == 0\n        ):\n            continue\n\n        converted = convert_handlers(handlers, topologies, v_site_maps, converted)\n\n    # handlers may either return multiple potentials, or condense multiple already\n    # converted potentials into a single one (e.g. electrostatics into some polarizable\n    # potential)\n    potentials = []\n    parameter_maps_by_handler = {}\n\n    for potential, parameter_maps in converted:\n        potentials.append(potential)\n        parameter_maps_by_handler[potential.type] = parameter_maps\n\n    tensor_topologies = [\n        _convert_topology(\n            topology,\n            {\n                potential.type: parameter_maps_by_handler[potential.type][i]\n                for potential in potentials\n            },\n            v_site_maps[i],\n            constraints[i],\n        )\n        for i, topology in enumerate(topologies)\n    ]\n\n    tensor_force_field = smee.TensorForceField(potentials, v_sites)\n    return tensor_force_field, tensor_topologies\n
"},{"location":"reference/converters/openff/#smee.converters.openff.smirnoff_parameter_converter","title":"smirnoff_parameter_converter","text":"
smirnoff_parameter_converter(\n    type_: str,\n    default_units: dict[str, Unit],\n    depends_on: list[str] | None = None,\n)\n

A decorator used to flag a function as being able to convert a parameter handlers parameters into tensors.

Parameters:

  • type_ (str) \u2013

    The type of parameter handler that the decorated function can convert.

  • default_units (dict[str, Unit]) \u2013

    The default units of each parameter in the handler.

  • depends_on (list[str] | None, default: None ) \u2013

    The names of other handlers that this handler depends on. When set, the convert function should additionally take in a list of the already converted potentials and return a new list of potentials that should either include or replace the original potentials.

Source code in smee/converters/openff/_openff.py
def smirnoff_parameter_converter(\n    type_: str,\n    default_units: dict[str, openff.units.Unit],\n    depends_on: list[str] | None = None,\n):\n    \"\"\"A decorator used to flag a function as being able to convert a parameter handlers\n    parameters into tensors.\n\n    Args:\n        type_: The type of parameter handler that the decorated function can convert.\n        default_units: The default units of each parameter in the handler.\n        depends_on: The names of other handlers that this handler depends on. When set,\n            the convert function should additionally take in a list of the already\n            converted potentials and return a new list of potentials that should either\n            include or replace the original potentials.\n    \"\"\"\n\n    def parameter_converter_inner(func):\n        if type_ in _CONVERTERS:\n            raise KeyError(f\"A {type_} converter is already registered.\")\n\n        _CONVERTERS[type_] = _Converter(func, default_units, depends_on)\n\n        return func\n\n    return parameter_converter_inner\n
"},{"location":"reference/converters/openff/nonbonded/","title":" nonbonded","text":""},{"location":"reference/converters/openff/nonbonded/#smee.converters.openff.nonbonded","title":"nonbonded","text":"

Convert SMIRNOFF non-bonded parameters into tensors.

Functions:

  • convert_nonbonded_handlers \u2013

    Convert a list of SMIRNOFF non-bonded handlers into a tensor potential and

"},{"location":"reference/converters/openff/nonbonded/#smee.converters.openff.nonbonded.convert_nonbonded_handlers","title":"convert_nonbonded_handlers","text":"
convert_nonbonded_handlers(\n    handlers: list[SMIRNOFFCollection],\n    handler_type: str,\n    topologies: list[Topology],\n    v_site_maps: list[VSiteMap | None],\n    parameter_cols: tuple[str, ...],\n    attribute_cols: tuple[str, ...] | None = None,\n    has_exclusions: bool = True,\n) -> tuple[TensorPotential, list[NonbondedParameterMap]]\n

Convert a list of SMIRNOFF non-bonded handlers into a tensor potential and associated parameter maps.

Notes

This function assumes that all parameters come from the same force field

Parameters:

  • handlers (list[SMIRNOFFCollection]) \u2013

    The list of SMIRNOFF non-bonded handlers to convert.

  • handler_type (str) \u2013

    The type of non-bonded handler being converted.

  • topologies (list[Topology]) \u2013

    The topologies associated with each handler.

  • v_site_maps (list[VSiteMap | None]) \u2013

    The virtual site maps associated with each handler.

  • parameter_cols (tuple[str, ...]) \u2013

    The ordering of the parameter array columns.

  • attribute_cols (tuple[str, ...] | None, default: None ) \u2013

    The handler attributes to include in the potential in addition to the intra-molecular scaling factors.

  • has_exclusions (bool, default: True ) \u2013

    Whether the handlers are excepted to define exclusions.

Returns:

  • tuple[TensorPotential, list[NonbondedParameterMap]] \u2013

    The potential containing tensors of the parameter values, and a list of parameter maps which map the parameters to the interactions they apply to.

Source code in smee/converters/openff/nonbonded.py
def convert_nonbonded_handlers(\n    handlers: list[openff.interchange.smirnoff.SMIRNOFFCollection],\n    handler_type: str,\n    topologies: list[openff.toolkit.Topology],\n    v_site_maps: list[smee.VSiteMap | None],\n    parameter_cols: tuple[str, ...],\n    attribute_cols: tuple[str, ...] | None = None,\n    has_exclusions: bool = True,\n) -> tuple[smee.TensorPotential, list[smee.NonbondedParameterMap]]:\n    \"\"\"Convert a list of SMIRNOFF non-bonded handlers into a tensor potential and\n    associated parameter maps.\n\n    Notes:\n        This function assumes that all parameters come from the same force field\n\n    Args:\n        handlers: The list of SMIRNOFF non-bonded handlers to convert.\n        handler_type: The type of non-bonded handler being converted.\n        topologies: The topologies associated with each handler.\n        v_site_maps: The virtual site maps associated with each handler.\n        parameter_cols: The ordering of the parameter array columns.\n        attribute_cols: The handler attributes to include in the potential *in addition*\n            to the intra-molecular scaling factors.\n        has_exclusions: Whether the handlers are excepted to define exclusions.\n\n    Returns:\n        The potential containing tensors of the parameter values, and a list of\n        parameter maps which map the parameters to the interactions they apply to.\n    \"\"\"\n    attribute_cols = attribute_cols if attribute_cols is not None else []\n\n    assert len(topologies) == len(handlers), \"topologies and handlers must match\"\n    assert len(v_site_maps) == len(handlers), \"v-site maps and handlers must match\"\n\n    if has_exclusions:\n        attribute_cols = (\n            \"scale_12\",\n            \"scale_13\",\n            \"scale_14\",\n            \"scale_15\",\n            *attribute_cols,\n        )\n\n    potential = smee.converters.openff._openff._handlers_to_potential(\n        handlers,\n        handler_type,\n        parameter_cols,\n        attribute_cols,\n    )\n\n    parameter_key_to_idx = {\n        parameter_key: i for i, parameter_key in enumerate(potential.parameter_keys)\n    }\n    attribute_to_idx = {column: i for i, column in enumerate(potential.attribute_cols)}\n\n    parameter_maps = []\n\n    for handler, topology, v_site_map in zip(\n        handlers, topologies, v_site_maps, strict=True\n    ):\n        assignment_map = collections.defaultdict(lambda: collections.defaultdict(float))\n\n        n_particles = topology.n_atoms + (\n            0 if v_site_map is None else len(v_site_map.keys)\n        )\n\n        for topology_key, parameter_key in handler.key_map.items():\n            if isinstance(topology_key, openff.interchange.models.VirtualSiteKey):\n                continue\n\n            atom_idx = topology_key.atom_indices[0]\n            assignment_map[atom_idx][parameter_key_to_idx[parameter_key]] += 1.0\n\n        for topology_key, parameter_key in handler.key_map.items():\n            if not isinstance(topology_key, openff.interchange.models.VirtualSiteKey):\n                continue\n\n            v_site_idx = v_site_map.key_to_idx[topology_key]\n\n            if parameter_key.associated_handler != \"Electrostatics\":\n                assignment_map[v_site_idx][parameter_key_to_idx[parameter_key]] += 1.0\n            else:\n                for i, atom_idx in enumerate(topology_key.orientation_atom_indices):\n                    mult_key = copy.deepcopy(parameter_key)\n                    mult_key.mult = i\n\n                    assignment_map[atom_idx][parameter_key_to_idx[mult_key]] += 1.0\n                    assignment_map[v_site_idx][parameter_key_to_idx[mult_key]] += -1.0\n\n        assignment_matrix = torch.zeros(\n            (n_particles, len(potential.parameters)), dtype=torch.float64\n        )\n\n        for particle_idx in assignment_map:\n            for parameter_idx, count in assignment_map[particle_idx].items():\n                assignment_matrix[particle_idx, parameter_idx] = count\n\n        if has_exclusions:\n            exclusion_to_scale = smee.utils.find_exclusions(topology, v_site_map)\n            exclusions = torch.tensor([*exclusion_to_scale])\n            exclusion_scale_idxs = torch.tensor(\n                [[attribute_to_idx[scale]] for scale in exclusion_to_scale.values()],\n                dtype=torch.int64,\n            )\n        else:\n            exclusions = torch.zeros((0, 2), dtype=torch.int64)\n            exclusion_scale_idxs = torch.zeros((0, 1), dtype=torch.int64)\n\n        parameter_map = smee.NonbondedParameterMap(\n            assignment_matrix=assignment_matrix.to_sparse(),\n            exclusions=exclusions,\n            exclusion_scale_idxs=exclusion_scale_idxs,\n        )\n        parameter_maps.append(parameter_map)\n\n    return potential, parameter_maps\n
"},{"location":"reference/converters/openff/valence/","title":" valence","text":""},{"location":"reference/converters/openff/valence/#smee.converters.openff.valence","title":"valence","text":"

Convert SMIRNOFF valence parameters into tensors.

Functions:

  • convert_valence_handlers \u2013

    Convert a list of SMIRNOFF valence handlers into a tensor potential and

"},{"location":"reference/converters/openff/valence/#smee.converters.openff.valence.convert_valence_handlers","title":"convert_valence_handlers","text":"
convert_valence_handlers(\n    handlers: list[SMIRNOFFCollection],\n    handler_type: str,\n    parameter_cols: tuple[str, ...],\n) -> tuple[TensorPotential, list[ValenceParameterMap]]\n

Convert a list of SMIRNOFF valence handlers into a tensor potential and associated parameter maps.

Notes

This function assumes that all parameters come from the same force field

Parameters:

  • handlers (list[SMIRNOFFCollection]) \u2013

    The list of SMIRNOFF valence handlers to convert.

  • handler_type (str) \u2013

    The type of valence handler being converted.

  • parameter_cols (tuple[str, ...]) \u2013

    The ordering of the parameter array columns.

Returns:

  • tuple[TensorPotential, list[ValenceParameterMap]] \u2013

    The potential containing tensors of the parameter values, and a list of parameter maps which map the parameters to the interactions they apply to.

Source code in smee/converters/openff/valence.py
def convert_valence_handlers(\n    handlers: list[openff.interchange.smirnoff.SMIRNOFFCollection],\n    handler_type: str,\n    parameter_cols: tuple[str, ...],\n) -> tuple[smee.TensorPotential, list[smee.ValenceParameterMap]]:\n    \"\"\"Convert a list of SMIRNOFF valence handlers into a tensor potential and\n    associated parameter maps.\n\n    Notes:\n        This function assumes that all parameters come from the same force field\n\n    Args:\n        handlers: The list of SMIRNOFF valence handlers to convert.\n        handler_type: The type of valence handler being converted.\n        parameter_cols: The ordering of the parameter array columns.\n\n    Returns:\n        The potential containing tensors of the parameter values, and a list of\n        parameter maps which map the parameters to the interactions they apply to.\n    \"\"\"\n    potential = smee.converters.openff._openff._handlers_to_potential(\n        handlers, handler_type, parameter_cols, None\n    )\n\n    parameter_key_to_idx = {\n        parameter_key: i for i, parameter_key in enumerate(potential.parameter_keys)\n    }\n    parameter_maps = []\n\n    for handler in handlers:\n        particle_idxs = [topology_key.atom_indices for topology_key in handler.key_map]\n\n        assignment_matrix = torch.zeros(\n            (len(particle_idxs), len(potential.parameters)), dtype=torch.float64\n        )\n\n        for i, parameter_key in enumerate(handler.key_map.values()):\n            assignment_matrix[i, parameter_key_to_idx[parameter_key]] += 1.0\n\n        parameter_map = smee.ValenceParameterMap(\n            torch.tensor(particle_idxs), assignment_matrix.to_sparse()\n        )\n        parameter_maps.append(parameter_map)\n\n    return potential, parameter_maps\n
"},{"location":"reference/converters/openmm/","title":"Index","text":""},{"location":"reference/converters/openmm/#smee.converters.openmm","title":"openmm","text":"

Convert tensor representations into OpenMM systems.

Modules:

  • nonbonded \u2013

    Convert non-bonded potentials to OpenMM forces.

  • valence \u2013

    Convert valence potentials to OpenMM forces.

Functions:

  • convert_to_openmm_force \u2013

    Convert a smee potential to OpenMM forces.

  • convert_to_openmm_system \u2013

    Convert a smee force field and system / topology into an OpenMM system.

  • convert_to_openmm_topology \u2013

    Convert a smee system to an OpenMM topology.

  • create_openmm_system \u2013

    Create an empty OpenMM system from a smee system.

  • potential_converter \u2013

    A decorator used to flag a function as being able to convert a tensor potential

"},{"location":"reference/converters/openmm/#smee.converters.openmm.convert_to_openmm_force","title":"convert_to_openmm_force","text":"
convert_to_openmm_force(\n    potential: TensorPotential, system: TensorSystem\n) -> list[Force]\n

Convert a smee potential to OpenMM forces.

Some potentials may return multiple forces, e.g. a vdW potential may return one force containing intermolecular interactions and another containing intramolecular interactions.

See Also

potential_converter: for how to define a converter function.

Parameters:

  • potential (TensorPotential) \u2013

    The potential to convert.

  • system (TensorSystem) \u2013

    The system to convert.

Returns:

  • list[Force] \u2013

    The OpenMM force(s).

Source code in smee/converters/openmm/_openmm.py
def convert_to_openmm_force(\n    potential: smee.TensorPotential, system: smee.TensorSystem\n) -> list[openmm.Force]:\n    \"\"\"Convert a ``smee`` potential to OpenMM forces.\n\n    Some potentials may return multiple forces, e.g. a vdW potential may return one\n    force containing intermolecular interactions and another containing intramolecular\n    interactions.\n\n    See Also:\n        potential_converter: for how to define a converter function.\n\n    Args:\n        potential: The potential to convert.\n        system: The system to convert.\n\n    Returns:\n        The OpenMM force(s).\n    \"\"\"\n    # register the built-in converter functions\n    importlib.import_module(\"smee.converters.openmm.nonbonded\")\n    importlib.import_module(\"smee.converters.openmm.valence\")\n\n    potential = potential.to(\"cpu\")\n    system = system.to(\"cpu\")\n\n    if potential.exceptions is not None and potential.type != \"vdW\":\n        raise NotImplementedError(\"exceptions are only supported for vdW potentials\")\n\n    converter_key = (str(potential.type), str(potential.fn))\n\n    if converter_key not in _CONVERTER_FUNCTIONS:\n        raise NotImplementedError(\n            f\"cannot convert type={potential.type} fn={potential.fn} to an OpenMM force\"\n        )\n\n    forces = _CONVERTER_FUNCTIONS[converter_key](potential, system)\n    return forces if isinstance(forces, (list, tuple)) else [forces]\n
"},{"location":"reference/converters/openmm/#smee.converters.openmm.convert_to_openmm_system","title":"convert_to_openmm_system","text":"
convert_to_openmm_system(\n    force_field: TensorForceField,\n    system: TensorSystem | TensorTopology,\n) -> System\n

Convert a smee force field and system / topology into an OpenMM system.

Parameters:

  • force_field (TensorForceField) \u2013

    The force field parameters.

  • system (TensorSystem | TensorTopology) \u2013

    The system / topology to convert.

Returns:

  • System \u2013

    The OpenMM system.

Source code in smee/converters/openmm/_openmm.py
def convert_to_openmm_system(\n    force_field: smee.TensorForceField,\n    system: smee.TensorSystem | smee.TensorTopology,\n) -> openmm.System:\n    \"\"\"Convert a ``smee`` force field and system / topology into an OpenMM system.\n\n    Args:\n        force_field: The force field parameters.\n        system: The system / topology to convert.\n\n    Returns:\n        The OpenMM system.\n    \"\"\"\n\n    system: smee.TensorSystem = (\n        system\n        if isinstance(system, smee.TensorSystem)\n        else smee.TensorSystem([system], [1], False)\n    )\n\n    force_field = force_field.to(\"cpu\")\n    system = system.to(\"cpu\")\n\n    omm_forces = {\n        potential_type: convert_to_openmm_force(potential, system)\n        for potential_type, potential in force_field.potentials_by_type.items()\n    }\n    omm_system = create_openmm_system(system, force_field.v_sites)\n\n    if (\n        \"Electrostatics\" in omm_forces\n        and \"vdW\" in omm_forces\n        and len(omm_forces[\"vdW\"]) == 1\n        and isinstance(omm_forces[\"vdW\"][0], openmm.NonbondedForce)\n    ):\n        (electrostatic_force,) = omm_forces.pop(\"Electrostatics\")\n        (vdw_force,) = omm_forces.pop(\"vdW\")\n\n        nonbonded_force = _combine_nonbonded(vdw_force, electrostatic_force)\n        omm_system.addForce(nonbonded_force)\n\n    for forces in omm_forces.values():\n        for force in forces:\n            omm_system.addForce(force)\n\n    _apply_constraints(omm_system, system)\n\n    return omm_system\n
"},{"location":"reference/converters/openmm/#smee.converters.openmm.convert_to_openmm_topology","title":"convert_to_openmm_topology","text":"
convert_to_openmm_topology(\n    system: TensorSystem | TensorTopology,\n) -> Topology\n

Convert a smee system to an OpenMM topology.

Notes

Virtual sites are given the element symbol \"X\" and atomic number 82.

Parameters:

  • system (TensorSystem | TensorTopology) \u2013

    The system to convert.

Returns:

  • Topology \u2013

    The OpenMM topology.

Source code in smee/converters/openmm/_openmm.py
def convert_to_openmm_topology(\n    system: smee.TensorSystem | smee.TensorTopology,\n) -> openmm.app.Topology:\n    \"\"\"Convert a ``smee`` system to an OpenMM topology.\n\n    Notes:\n        Virtual sites are given the element symbol \"X\" and atomic number 82.\n\n    Args:\n        system: The system to convert.\n\n    Returns:\n        The OpenMM topology.\n    \"\"\"\n    system: smee.TensorSystem = (\n        system\n        if isinstance(system, smee.TensorSystem)\n        else smee.TensorSystem([system], [1], False)\n    )\n\n    omm_topology = openmm.app.Topology()\n\n    for topology, n_copies in zip(system.topologies, system.n_copies, strict=True):\n        chain = omm_topology.addChain()\n\n        is_water = topology.n_atoms == 3 and sorted(\n            int(v) for v in topology.atomic_nums\n        ) == [1, 1, 8]\n\n        residue_name = \"WAT\" if is_water else \"UNK\"\n\n        for _ in range(n_copies):\n            residue = omm_topology.addResidue(residue_name, chain)\n            element_counter = collections.defaultdict(int)\n\n            atoms = {}\n\n            for i, atomic_num in enumerate(topology.atomic_nums):\n                element = openmm.app.Element.getByAtomicNumber(int(atomic_num))\n                element_counter[element.symbol] += 1\n\n                name = element.symbol + (\n                    \"\"\n                    if element_counter[element.symbol] == 1 and element.symbol != \"H\"\n                    else f\"{element_counter[element.symbol]}\"\n                )\n                atoms[i] = omm_topology.addAtom(name, element, residue)\n\n            for _ in range(topology.n_v_sites):\n                omm_topology.addAtom(\n                    \"X\", openmm.app.Element.getByAtomicNumber(82), residue\n                )\n\n            for bond_idxs, bond_order in zip(\n                topology.bond_idxs, topology.bond_orders, strict=True\n            ):\n                idx_a, idx_b = int(bond_idxs[0]), int(bond_idxs[1])\n\n                bond_order = int(bond_order)\n                bond_type = {\n                    1: openmm.app.Single,\n                    2: openmm.app.Double,\n                    3: openmm.app.Triple,\n                }[bond_order]\n\n                omm_topology.addBond(atoms[idx_a], atoms[idx_b], bond_type, bond_order)\n\n    return omm_topology\n
"},{"location":"reference/converters/openmm/#smee.converters.openmm.create_openmm_system","title":"create_openmm_system","text":"
create_openmm_system(\n    system: TensorSystem, v_sites: TensorVSites | None\n) -> System\n

Create an empty OpenMM system from a smee system.

Source code in smee/converters/openmm/_openmm.py
def create_openmm_system(\n    system: smee.TensorSystem, v_sites: smee.TensorVSites | None\n) -> openmm.System:\n    \"\"\"Create an empty OpenMM system from a ``smee`` system.\"\"\"\n    v_sites = None if v_sites is None else v_sites.to(\"cpu\")\n    system = system.to(\"cpu\")\n\n    omm_system = openmm.System()\n\n    for topology, n_copies in zip(system.topologies, system.n_copies, strict=True):\n        for _ in range(n_copies):\n            start_idx = omm_system.getNumParticles()\n\n            for atomic_num in topology.atomic_nums:\n                mass = openmm.app.Element.getByAtomicNumber(int(atomic_num)).mass\n                omm_system.addParticle(mass)\n\n            if topology.v_sites is None:\n                continue\n\n            for _ in range(topology.n_v_sites):\n                omm_system.addParticle(0.0)\n\n            for key, parameter_idx in zip(\n                topology.v_sites.keys, topology.v_sites.parameter_idxs, strict=True\n            ):\n                system_idx = start_idx + topology.v_sites.key_to_idx[key]\n                assert system_idx >= start_idx\n\n                parent_idxs = [i + start_idx for i in key.orientation_atom_indices]\n\n                local_frame_coords = smee.geometry.polar_to_cartesian_coords(\n                    v_sites.parameters[[parameter_idx], :].detach()\n                )\n                origin, x_dir, y_dir = v_sites.weights[parameter_idx]\n\n                v_site = openmm.LocalCoordinatesSite(\n                    parent_idxs,\n                    origin.numpy(),\n                    x_dir.numpy(),\n                    y_dir.numpy(),\n                    local_frame_coords.numpy().flatten() * _ANGSTROM_TO_NM,\n                )\n\n                omm_system.setVirtualSite(system_idx, v_site)\n\n    return omm_system\n
"},{"location":"reference/converters/openmm/#smee.converters.openmm.potential_converter","title":"potential_converter","text":"
potential_converter(\n    handler_type: str, energy_expression: str\n)\n

A decorator used to flag a function as being able to convert a tensor potential of a given type and energy function to an OpenMM force.

Source code in smee/converters/openmm/_openmm.py
def potential_converter(handler_type: str, energy_expression: str):\n    \"\"\"A decorator used to flag a function as being able to convert a tensor potential\n    of a given type and energy function to an OpenMM force.\n    \"\"\"\n\n    def _openmm_converter_inner(func):\n        if (handler_type, energy_expression) in _CONVERTER_FUNCTIONS:\n            raise KeyError(\n                f\"An OpenMM converter function is already defined for \"\n                f\"handler={handler_type} fn={energy_expression}.\"\n            )\n\n        _CONVERTER_FUNCTIONS[(str(handler_type), str(energy_expression))] = func\n        return func\n\n    return _openmm_converter_inner\n
"},{"location":"reference/converters/openmm/nonbonded/","title":" nonbonded","text":""},{"location":"reference/converters/openmm/nonbonded/#smee.converters.openmm.nonbonded","title":"nonbonded","text":"

Convert non-bonded potentials to OpenMM forces.

Functions:

  • convert_custom_vdw_potential \u2013

    Converts an arbitrary vdW potential to OpenMM forces.

  • convert_lj_potential \u2013

    Convert a Lennard-Jones potential to an OpenMM force.

  • convert_dexp_potential \u2013

    Convert a DEXP potential to OpenMM forces.

  • convert_coulomb_potential \u2013

    Convert a Coulomb potential to an OpenMM force.

"},{"location":"reference/converters/openmm/nonbonded/#smee.converters.openmm.nonbonded.convert_custom_vdw_potential","title":"convert_custom_vdw_potential","text":"
convert_custom_vdw_potential(\n    potential: TensorPotential,\n    system: TensorSystem,\n    energy_fn: str,\n    mixing_fn: dict[str, str],\n) -> tuple[CustomNonbondedForce, CustomBondForce]\n

Converts an arbitrary vdW potential to OpenMM forces.

The intermolecular interactions are described by a custom nonbonded force, while the intramolecular interactions are described by a custom bond force.

If the potential has custom mixing rules (i.e. exceptions), a lookup table will be used to store the parameters. Otherwise, the mixing rules will be applied directly in the energy function.

Parameters:

  • potential (TensorPotential) \u2013

    The potential to convert.

  • system (TensorSystem) \u2013

    The system the potential belongs to.

  • energy_fn (str) \u2013

    The energy function of the potential, written in OpenMM's custom energy function syntax.

  • mixing_fn (dict[str, str]) \u2013

    A dictionary of mixing rules for each parameter of the potential. The keys are the parameter names, and the values are the mixing rules.

    The mixing rules should be a single expression that can be evaluated using OpenMM's energy function syntax, and should not contain any assignments.

Examples:

For a Lennard-Jones potential using Lorentz-Berthelot mixing rules:

>>> energy_fn = \"4*epsilon*x6*(x6 - 1.0);x6=x4*x2;x4=x2*x2;x2=x*x;x=sigma/r;\"\n>>> mixing_fn = {\n...     \"epsilon\": \"sqrt(epsilon1 * epsilon2)\",\n...     \"sigma\": \"0.5 * (sigma1 + sigma2)\",\n... }\n
Source code in smee/converters/openmm/nonbonded.py
def convert_custom_vdw_potential(\n    potential: smee.TensorPotential,\n    system: smee.TensorSystem,\n    energy_fn: str,\n    mixing_fn: dict[str, str],\n) -> tuple[openmm.CustomNonbondedForce, openmm.CustomBondForce]:\n    \"\"\"Converts an arbitrary vdW potential to OpenMM forces.\n\n    The intermolecular interactions are described by a custom nonbonded force, while the\n    intramolecular interactions are described by a custom bond force.\n\n    If the potential has custom mixing rules (i.e. exceptions), a lookup table will be\n    used to store the parameters. Otherwise, the mixing rules will be applied directly\n    in the energy function.\n\n    Args:\n        potential: The potential to convert.\n        system: The system the potential belongs to.\n        energy_fn: The energy function of the potential, written in OpenMM's custom\n            energy function syntax.\n        mixing_fn: A dictionary of mixing rules for each parameter of the potential.\n            The keys are the parameter names, and the values are the mixing rules.\n\n            The mixing rules should be a single expression that can be evaluated using\n            OpenMM's energy function syntax, and should not contain any assignments.\n\n    Examples:\n        For a Lennard-Jones potential using Lorentz-Berthelot mixing rules:\n\n        >>> energy_fn = \"4*epsilon*x6*(x6 - 1.0);x6=x4*x2;x4=x2*x2;x2=x*x;x=sigma/r;\"\n        >>> mixing_fn = {\n        ...     \"epsilon\": \"sqrt(epsilon1 * epsilon2)\",\n        ...     \"sigma\": \"0.5 * (sigma1 + sigma2)\",\n        ... }\n    \"\"\"\n    energy_fn = re.sub(r\"\\s+\", \"\", energy_fn)\n    mixing_fn = {k: re.sub(r\"\\s+\", \"\", v) for k, v in mixing_fn.items()}\n\n    used_parameters, used_attributes = _detect_parameters(\n        potential, energy_fn, mixing_fn\n    )\n    requires_lookup = potential.exceptions is not None\n\n    inter_force = _create_nonbonded_force(\n        potential, system, openmm.CustomNonbondedForce\n    )\n    inter_force.setEnergyFunction(energy_fn)\n\n    intra_force = openmm.CustomBondForce(\n        _prepend_scale_to_energy_fn(energy_fn, _INTRA_SCALE_VAR)\n    )\n    intra_force.addPerBondParameter(_INTRA_SCALE_VAR)\n    intra_force.setUsesPeriodicBoundaryConditions(system.is_periodic)\n\n    for force in [inter_force, intra_force]:\n        for attr in used_attributes:\n            attr_unit = potential.attribute_units[potential.attribute_cols.index(attr)]\n            attr_conv = (\n                (1.0 * attr_unit)\n                .to_openmm()\n                .value_in_unit_system(openmm.unit.md_unit_system)\n            )\n            attr_idx = potential.attribute_cols.index(attr)\n            attr_val = float(potential.attributes[attr_idx]) * attr_conv\n\n            force.addGlobalParameter(attr, attr_val)\n\n    if requires_lookup:\n        _add_parameters_to_vdw_with_lookup(\n            potential, system, energy_fn, mixing_fn, inter_force, intra_force\n        )\n    else:\n        _add_parameters_to_vdw_without_lookup(\n            potential,\n            system,\n            energy_fn,\n            mixing_fn,\n            inter_force,\n            intra_force,\n            used_parameters,\n        )\n\n    return inter_force, intra_force\n
"},{"location":"reference/converters/openmm/nonbonded/#smee.converters.openmm.nonbonded.convert_lj_potential","title":"convert_lj_potential","text":"
convert_lj_potential(\n    potential: TensorPotential, system: TensorSystem\n) -> (\n    NonbondedForce\n    | list[CustomNonbondedForce | CustomBondForce]\n)\n

Convert a Lennard-Jones potential to an OpenMM force.

If the potential has custom mixing rules (i.e. exceptions), the interactions will be split into an inter- and intra-molecular force.

Source code in smee/converters/openmm/nonbonded.py
@smee.converters.openmm.potential_converter(\n    smee.PotentialType.VDW, smee.EnergyFn.VDW_LJ\n)\ndef convert_lj_potential(\n    potential: smee.TensorPotential, system: smee.TensorSystem\n) -> openmm.NonbondedForce | list[openmm.CustomNonbondedForce | openmm.CustomBondForce]:\n    \"\"\"Convert a Lennard-Jones potential to an OpenMM force.\n\n    If the potential has custom mixing rules (i.e. exceptions), the interactions will\n    be split into an inter- and intra-molecular force.\n    \"\"\"\n    energy_fn = \"4*epsilon*x6*(x6 - 1.0);x6=x4*x2;x4=x2*x2;x2=x*x;x=sigma/r;\"\n    mixing_fn = {\n        \"epsilon\": \"sqrt(epsilon1 * epsilon2)\",\n        \"sigma\": \"0.5 * (sigma1 + sigma2)\",\n    }\n\n    if potential.exceptions is not None:\n        return list(\n            convert_custom_vdw_potential(potential, system, energy_fn, mixing_fn)\n        )\n\n    force = _create_nonbonded_force(potential, system)\n\n    idx_offset = 0\n\n    for topology, n_copies in zip(system.topologies, system.n_copies, strict=True):\n        parameter_map = topology.parameters[potential.type]\n        parameters = parameter_map.assignment_matrix @ potential.parameters.detach()\n\n        for _ in range(n_copies):\n            for epsilon, sigma in parameters:\n                force.addParticle(0.0, sigma * _ANGSTROM, epsilon * _KCAL_PER_MOL)\n\n            for index, (i, j) in enumerate(parameter_map.exclusions):\n                scale = potential.attributes[parameter_map.exclusion_scale_idxs[index]]\n\n                eps_i, sig_i = parameters[i, :]\n                eps_j, sig_j = parameters[j, :]\n\n                eps, sig = smee.potentials.nonbonded.lorentz_berthelot(\n                    eps_i, eps_j, sig_i, sig_j\n                )\n\n                force.addException(\n                    i + idx_offset,\n                    j + idx_offset,\n                    0.0,\n                    float(sig) * _ANGSTROM,\n                    float(eps * scale) * _KCAL_PER_MOL,\n                )\n\n            idx_offset += topology.n_particles\n\n    return force\n
"},{"location":"reference/converters/openmm/nonbonded/#smee.converters.openmm.nonbonded.convert_dexp_potential","title":"convert_dexp_potential","text":"
convert_dexp_potential(\n    potential: TensorPotential, system: TensorSystem\n) -> tuple[CustomNonbondedForce, CustomBondForce]\n

Convert a DEXP potential to OpenMM forces.

The intermolcular interactions are described by a custom nonbonded force, while the intramolecular interactions are described by a custom bond force.

If the potential has custom mixing rules (i.e. exceptions), a lookup table will be used to store the parameters. Otherwise, the mixing rules will be applied directly in the energy function.

Source code in smee/converters/openmm/nonbonded.py
@smee.converters.openmm.potential_converter(\n    smee.PotentialType.VDW, smee.EnergyFn.VDW_DEXP\n)\ndef convert_dexp_potential(\n    potential: smee.TensorPotential, system: smee.TensorSystem\n) -> tuple[openmm.CustomNonbondedForce, openmm.CustomBondForce]:\n    \"\"\"Convert a DEXP potential to OpenMM forces.\n\n    The intermolcular interactions are described by a custom nonbonded force, while the\n    intramolecular interactions are described by a custom bond force.\n\n    If the potential has custom mixing rules (i.e. exceptions), a lookup table will be\n    used to store the parameters. Otherwise, the mixing rules will be applied directly\n    in the energy function.\n    \"\"\"\n    energy_fn = (\n        \"epsilon * (repulsion - attraction);\"\n        \"repulsion  = beta  / (alpha - beta) * exp(alpha * (1 - x));\"\n        \"attraction = alpha / (alpha - beta) * exp(beta  * (1 - x));\"\n        \"x = r / r_min;\"\n    )\n    mixing_fn = {\n        \"epsilon\": \"sqrt(epsilon1 * epsilon2)\",\n        \"r_min\": \"0.5 * (r_min1 + r_min2)\",\n    }\n\n    return convert_custom_vdw_potential(potential, system, energy_fn, mixing_fn)\n
"},{"location":"reference/converters/openmm/nonbonded/#smee.converters.openmm.nonbonded.convert_coulomb_potential","title":"convert_coulomb_potential","text":"
convert_coulomb_potential(\n    potential: TensorPotential, system: TensorSystem\n) -> NonbondedForce\n

Convert a Coulomb potential to an OpenMM force.

Source code in smee/converters/openmm/nonbonded.py
@smee.converters.openmm.potential_converter(\n    smee.PotentialType.ELECTROSTATICS, smee.EnergyFn.COULOMB\n)\ndef convert_coulomb_potential(\n    potential: smee.TensorPotential, system: smee.TensorSystem\n) -> openmm.NonbondedForce:\n    \"\"\"Convert a Coulomb potential to an OpenMM force.\"\"\"\n    force = _create_nonbonded_force(potential, system)\n\n    idx_offset = 0\n\n    for topology, n_copies in zip(system.topologies, system.n_copies, strict=True):\n        parameter_map = topology.parameters[potential.type]\n        parameters = parameter_map.assignment_matrix @ potential.parameters.detach()\n\n        for _ in range(n_copies):\n            for charge in parameters:\n                force.addParticle(\n                    charge.detach() * openmm.unit.elementary_charge,\n                    1.0 * _ANGSTROM,\n                    0.0 * _KCAL_PER_MOL,\n                )\n\n            for index, (i, j) in enumerate(parameter_map.exclusions):\n                q_i, q_j = parameters[i], parameters[j]\n                q = q_i * q_j\n\n                scale = potential.attributes[parameter_map.exclusion_scale_idxs[index]]\n\n                force.addException(\n                    i + idx_offset,\n                    j + idx_offset,\n                    scale * q,\n                    1.0,\n                    0.0,\n                )\n\n            idx_offset += topology.n_particles\n\n    return force\n
"},{"location":"reference/converters/openmm/valence/","title":" valence","text":""},{"location":"reference/converters/openmm/valence/#smee.converters.openmm.valence","title":"valence","text":"

Convert valence potentials to OpenMM forces.

Functions:

  • convert_bond_potential \u2013

    Convert a harmonic bond potential to a corresponding OpenMM force.

  • convert_torsion_potential \u2013

    Convert a torsion potential to a corresponding OpenMM force.

"},{"location":"reference/converters/openmm/valence/#smee.converters.openmm.valence.convert_bond_potential","title":"convert_bond_potential","text":"
convert_bond_potential(\n    potential: TensorPotential, system: TensorSystem\n) -> HarmonicBondForce\n

Convert a harmonic bond potential to a corresponding OpenMM force.

Source code in smee/converters/openmm/valence.py
@smee.converters.openmm.potential_converter(\n    smee.PotentialType.BONDS, smee.EnergyFn.BOND_HARMONIC\n)\ndef convert_bond_potential(\n    potential: smee.TensorPotential, system: smee.TensorSystem\n) -> openmm.HarmonicBondForce:\n    \"\"\"Convert a harmonic bond potential to a corresponding OpenMM force.\"\"\"\n    force = openmm.HarmonicBondForce()\n\n    idx_offset = 0\n\n    for topology, n_copies in zip(system.topologies, system.n_copies, strict=True):\n        parameters = (\n            topology.parameters[potential.type].assignment_matrix @ potential.parameters\n        ).detach()\n\n        for _ in range(n_copies):\n            atom_idxs = topology.parameters[potential.type].particle_idxs + idx_offset\n\n            for (i, j), (constant, length) in zip(atom_idxs, parameters, strict=True):\n                force.addBond(\n                    i,\n                    j,\n                    length * _ANGSTROM,\n                    constant * _KCAL_PER_MOL / _ANGSTROM**2,\n                )\n\n            idx_offset += topology.n_particles\n\n    return force\n
"},{"location":"reference/converters/openmm/valence/#smee.converters.openmm.valence.convert_torsion_potential","title":"convert_torsion_potential","text":"
convert_torsion_potential(\n    potential: TensorPotential, system: TensorSystem\n) -> PeriodicTorsionForce\n

Convert a torsion potential to a corresponding OpenMM force.

Source code in smee/converters/openmm/valence.py
@smee.converters.openmm.potential_converter(\n    smee.PotentialType.PROPER_TORSIONS, smee.EnergyFn.TORSION_COSINE\n)\n@smee.converters.openmm.potential_converter(\n    smee.PotentialType.IMPROPER_TORSIONS, smee.EnergyFn.TORSION_COSINE\n)\ndef convert_torsion_potential(\n    potential: smee.TensorPotential, system: smee.TensorSystem\n) -> openmm.PeriodicTorsionForce:\n    \"\"\"Convert a torsion potential to a corresponding OpenMM force.\"\"\"\n    force = openmm.PeriodicTorsionForce()\n\n    idx_offset = 0\n\n    for topology, n_copies in zip(system.topologies, system.n_copies, strict=True):\n        parameters = (\n            topology.parameters[potential.type].assignment_matrix @ potential.parameters\n        ).detach()\n\n        for _ in range(n_copies):\n            atom_idxs = topology.parameters[potential.type].particle_idxs + idx_offset\n\n            for (idx_i, idx_j, idx_k, idx_l), (\n                constant,\n                periodicity,\n                phase,\n                idivf,\n            ) in zip(atom_idxs, parameters, strict=True):\n                force.addTorsion(\n                    idx_i,\n                    idx_j,\n                    idx_k,\n                    idx_l,\n                    int(periodicity),\n                    phase * _RADIANS,\n                    constant / idivf * _KCAL_PER_MOL,\n                )\n\n            idx_offset += topology.n_particles\n\n    return force\n
"},{"location":"reference/mm/","title":" mm","text":""},{"location":"reference/mm/#smee.mm","title":"mm","text":"

Compute differentiable ensemble averages using OpenMM and SMEE.

Classes:

  • GenerateCoordsConfig \u2013

    Configure how coordinates should be generated for a system using PACKMOL.

  • MinimizationConfig \u2013

    Configure how a system should be energy minimized.

  • NotEnoughSamplesError \u2013

    An error raised when an ensemble average is attempted with too few samples.

  • TensorReporter \u2013

    A reporter which stores coords, box vectors, reduced potentials and kinetic

Functions:

  • generate_system_coords \u2013

    Generate coordinates for a system of molecules using PACKMOL.

  • simulate \u2013

    Simulate a SMEE system of molecules or topology.

  • compute_ensemble_averages \u2013

    Compute ensemble average of the potential energy, volume, density,

  • reweight_ensemble_averages \u2013

    Compute the ensemble average of the potential energy, volume, density,

  • tensor_reporter \u2013

    Create a TensorReporter capable of writing frames to a file.

  • unpack_frames \u2013

    Unpack frames saved by a TensorReporter.

"},{"location":"reference/mm/#smee.mm.GenerateCoordsConfig","title":"GenerateCoordsConfig pydantic-model","text":"

Bases: BaseModel

Configure how coordinates should be generated for a system using PACKMOL.

"},{"location":"reference/mm/#smee.mm.MinimizationConfig","title":"MinimizationConfig pydantic-model","text":"

Bases: BaseModel

Configure how a system should be energy minimized.

"},{"location":"reference/mm/#smee.mm.NotEnoughSamplesError","title":"NotEnoughSamplesError","text":"

Bases: ValueError

An error raised when an ensemble average is attempted with too few samples.

"},{"location":"reference/mm/#smee.mm.TensorReporter","title":"TensorReporter","text":"
TensorReporter(\n    output_file: BinaryIO,\n    report_interval: int,\n    beta: Quantity,\n    pressure: Quantity | None,\n)\n

A reporter which stores coords, box vectors, reduced potentials and kinetic energy using msgpack.

report_interval: The interval (in steps) at which to write frames.\nbeta: The inverse temperature the simulation is being run at.\npressure: The pressure the simulation is being run at, or None if NVT /\n    vacuum.\n
Source code in smee/mm/_reporters.py
def __init__(\n    self,\n    output_file: typing.BinaryIO,\n    report_interval: int,\n    beta: openmm.unit.Quantity,\n    pressure: openmm.unit.Quantity | None,\n):\n    \"\"\"\n\n    Args:\n        output_file: The file to write the frames to.\n        report_interval: The interval (in steps) at which to write frames.\n        beta: The inverse temperature the simulation is being run at.\n        pressure: The pressure the simulation is being run at, or None if NVT /\n            vacuum.\n    \"\"\"\n    self._output_file = output_file\n    self._report_interval = report_interval\n\n    self._beta = beta\n    self._pressure = (\n        None if pressure is None else pressure * openmm.unit.AVOGADRO_CONSTANT_NA\n    )\n
"},{"location":"reference/mm/#smee.mm.generate_system_coords","title":"generate_system_coords","text":"
generate_system_coords(\n    system: TensorSystem,\n    force_field: TensorForceField | None,\n    config: Optional[GenerateCoordsConfig] = None,\n) -> tuple[Quantity, Quantity]\n

Generate coordinates for a system of molecules using PACKMOL.

Parameters:

  • system (TensorSystem) \u2013

    The system to generate coordinates for.

  • force_field (TensorForceField | None) \u2013

    The force field that describes the geometry of any virtual sites.

  • config (Optional[GenerateCoordsConfig], default: None ) \u2013

    Configuration of how to generate the system coordinates.

Returns:

  • tuple[Quantity, Quantity] \u2013

    The coordinates with shape=(n_atoms, 3) and box vectors with shape=(3, 3)

Source code in smee/mm/_mm.py
def generate_system_coords(\n    system: smee.TensorSystem,\n    force_field: smee.TensorForceField | None,\n    config: typing.Optional[\"smee.mm.GenerateCoordsConfig\"] = None,\n) -> tuple[openmm.unit.Quantity, openmm.unit.Quantity]:\n    \"\"\"Generate coordinates for a system of molecules using PACKMOL.\n\n    Args:\n        system: The system to generate coordinates for.\n        force_field: The force field that describes the geometry of any virtual sites.\n        config: Configuration of how to generate the system coordinates.\n\n    Raises:\n        * PACKMOLRuntimeError\n\n    Returns:\n        The coordinates with ``shape=(n_atoms, 3)`` and box vectors with\n        ``shape=(3, 3)``\n    \"\"\"\n\n    system = system.to(\"cpu\")\n    force_field = None if force_field is None else force_field.to(\"cpu\")\n\n    config = config if config is not None else smee.mm.GenerateCoordsConfig()\n\n    box_size = _approximate_box_size(system, config)\n\n    with tempfile.TemporaryDirectory() as tmp_dir:\n        tmp_dir = pathlib.Path(tmp_dir)\n\n        for i, topology in enumerate(system.topologies):\n            xyz = _topology_to_xyz(topology, force_field)\n            (tmp_dir / f\"{i}.xyz\").write_text(xyz)\n\n        input_file = tmp_dir / \"input.txt\"\n        input_file.write_text(\n            _generate_packmol_input(system.n_copies, box_size, config)\n        )\n\n        with input_file.open(\"r\") as file:\n            result = subprocess.run(\n                \"packmol\", stdin=file, capture_output=True, text=True, cwd=tmp_dir\n            )\n\n        if result.returncode != 0 or not result.stdout.find(\"Success!\") > 0:\n            raise PACKMOLRuntimeError(result.stdout)\n\n        output_lines = (tmp_dir / \"output.xyz\").read_text().splitlines()\n\n    coordinates = (\n        numpy.array(\n            [\n                [float(coordinate) for coordinate in coordinate_line.split()[1:]]\n                for coordinate_line in output_lines[2:]\n                if len(coordinate_line) > 0\n            ]\n        )\n        * openmm.unit.angstrom\n    )\n\n    box_vectors = numpy.eye(3) * (box_size + config.padding)\n    return coordinates, box_vectors\n
"},{"location":"reference/mm/#smee.mm.simulate","title":"simulate","text":"
simulate(\n    system: TensorSystem | TensorTopology,\n    force_field: TensorForceField,\n    coords: Quantity,\n    box_vectors: Quantity | None,\n    equilibrate_configs: list[\n        Union[MinimizationConfig, SimulationConfig]\n    ],\n    production_config: SimulationConfig,\n    production_reporters: list[Any] | None = None,\n    apply_hmr: bool = False,\n) -> State\n

Simulate a SMEE system of molecules or topology.

Parameters:

  • system (TensorSystem | TensorTopology) \u2013

    The system / topology to simulate.

  • force_field (TensorForceField) \u2013

    The force field to simulate with.

  • coords (Quantity) \u2013

    The coordinates [\u00c5] to use for the simulation. This should be a unit wrapped numpy array with shape=(n_atoms, 3).

  • box_vectors (Quantity | None) \u2013

    The box vectors [\u00c5] to use for the simulation if periodic. This should be a unit wrapped numpy array with shape=(3, 3).

  • equilibrate_configs (list[Union[MinimizationConfig, SimulationConfig]]) \u2013

    A list of configurations defining the steps to run for equilibration. No data will be stored from these simulations.

  • production_config (SimulationConfig) \u2013

    The configuration defining the production simulation to run.

  • production_reporters (list[Any] | None, default: None ) \u2013

    A list of additional OpenMM reporters to use for the production simulation.

  • apply_hmr (bool, default: False ) \u2013

    Whether to apply Hydrogen Mass Repartitioning to the system prior to simulation.

Source code in smee/mm/_mm.py
def simulate(\n    system: smee.TensorSystem | smee.TensorTopology,\n    force_field: smee.TensorForceField,\n    coords: openmm.unit.Quantity,\n    box_vectors: openmm.unit.Quantity | None,\n    equilibrate_configs: list[\n        typing.Union[\"smee.mm.MinimizationConfig\", \"smee.mm.SimulationConfig\"]\n    ],\n    production_config: \"smee.mm.SimulationConfig\",\n    production_reporters: list[typing.Any] | None = None,\n    apply_hmr: bool = False,\n) -> openmm.State:\n    \"\"\"Simulate a SMEE system of molecules or topology.\n\n    Args:\n        system: The system / topology to simulate.\n        force_field: The force field to simulate with.\n        coords: The coordinates [\u00c5] to use for the simulation. This should be\n            a unit wrapped numpy array with ``shape=(n_atoms, 3)``.\n        box_vectors: The box vectors [\u00c5] to use for the simulation if periodic. This\n            should be a unit wrapped numpy array with ``shape=(3, 3)``.\n        equilibrate_configs: A list of configurations defining the steps to run for\n            equilibration. No data will be stored from these simulations.\n        production_config: The configuration defining the production simulation to run.\n        production_reporters: A list of additional OpenMM reporters to use for the\n            production simulation.\n        apply_hmr: Whether to apply Hydrogen Mass Repartitioning to the system prior\n            to simulation.\n    \"\"\"\n\n    assert isinstance(coords.value_in_unit(openmm.unit.angstrom), numpy.ndarray)\n    assert isinstance(box_vectors.value_in_unit(openmm.unit.angstrom), numpy.ndarray)\n\n    force_field = force_field.to(\"cpu\")\n\n    system: smee.TensorSystem = (\n        system\n        if isinstance(system, smee.TensorSystem)\n        else smee.TensorSystem([system], [1], False)\n    ).to(\"cpu\")\n\n    requires_pbc = any(\n        config.pressure is not None\n        for config in equilibrate_configs + [production_config]\n        if isinstance(config, smee.mm.SimulationConfig)\n    )\n\n    if not system.is_periodic and requires_pbc:\n        raise ValueError(\"pressure cannot be specified for a non-periodic system\")\n\n    platform = _get_platform(system.is_periodic)\n\n    omm_state = coords, box_vectors\n\n    omm_system = smee.converters.convert_to_openmm_system(force_field, system)\n    omm_topology = smee.converters.convert_to_openmm_topology(system)\n\n    if apply_hmr:\n        _apply_hmr(omm_system, system)\n\n    for i, config in enumerate(equilibrate_configs):\n        _LOGGER.info(f\"running equilibration step {i + 1} / {len(equilibrate_configs)}\")\n\n        if isinstance(config, smee.mm.MinimizationConfig):\n            omm_state = _energy_minimize(omm_system, omm_state, platform, config)\n\n        elif isinstance(config, smee.mm.SimulationConfig):\n            omm_state = _run_simulation(\n                omm_system, omm_topology, omm_state, platform, config, None\n            )\n        else:\n            raise NotImplementedError\n\n        _LOGGER.info(_get_state_log(omm_state))\n\n    _LOGGER.info(\"running production simulation\")\n    omm_state = _run_simulation(\n        omm_system,\n        omm_topology,\n        omm_state,\n        platform,\n        production_config,\n        production_reporters,\n    )\n    _LOGGER.info(_get_state_log(omm_state))\n\n    return omm_state\n
"},{"location":"reference/mm/#smee.mm.compute_ensemble_averages","title":"compute_ensemble_averages","text":"
compute_ensemble_averages(\n    system: TensorSystem,\n    force_field: TensorForceField,\n    frames_path: Path,\n    temperature: Quantity,\n    pressure: Quantity | None,\n) -> tuple[dict[str, Tensor], dict[str, Tensor]]\n

Compute ensemble average of the potential energy, volume, density, and enthalpy (if running NPT) over an MD trajectory.

Parameters:

  • system (TensorSystem) \u2013

    The system to simulate.

  • force_field (TensorForceField) \u2013

    The force field to use.

  • frames_path (Path) \u2013

    The path to the trajectory to compute the average over.

  • temperature (Quantity) \u2013

    The temperature that the trajectory was simulated at.

  • pressure (Quantity | None) \u2013

    The pressure that the trajectory was simulated at.

Returns:

  • tuple[dict[str, Tensor], dict[str, Tensor]] \u2013

    A dictionary containing the ensemble averages of the potential energy [kcal/mol], volume [\u00c5^3], density [g/mL], and enthalpy [kcal/mol], and a dictionary containing their standard deviations.

Source code in smee/mm/_ops.py
def compute_ensemble_averages(\n    system: smee.TensorSystem,\n    force_field: smee.TensorForceField,\n    frames_path: pathlib.Path,\n    temperature: openmm.unit.Quantity,\n    pressure: openmm.unit.Quantity | None,\n) -> tuple[dict[str, torch.Tensor], dict[str, torch.Tensor]]:\n    \"\"\"Compute ensemble average of the potential energy, volume, density,\n    and enthalpy (if running NPT) over an MD trajectory.\n\n    Args:\n        system: The system to simulate.\n        force_field: The force field to use.\n        frames_path: The path to the trajectory to compute the average over.\n        temperature: The temperature that the trajectory was simulated at.\n        pressure: The pressure that the trajectory was simulated at.\n\n    Returns:\n        A dictionary containing the ensemble averages of the potential energy\n        [kcal/mol], volume [\u00c5^3], density [g/mL], and enthalpy [kcal/mol],\n        and a dictionary containing their standard deviations.\n    \"\"\"\n    tensors, parameter_lookup, attribute_lookup, has_v_sites = _pack_force_field(\n        force_field\n    )\n\n    beta = 1.0 / (openmm.unit.MOLAR_GAS_CONSTANT_R * temperature)\n    beta = beta.value_in_unit(openmm.unit.kilocalorie_per_mole**-1)\n\n    if pressure is not None:\n        pressure = (pressure * openmm.unit.AVOGADRO_CONSTANT_NA).value_in_unit(\n            openmm.unit.kilocalorie_per_mole / openmm.unit.angstrom**3\n        )\n\n    kwargs: _EnsembleAverageKwargs = {\n        \"force_field\": force_field,\n        \"parameter_lookup\": parameter_lookup,\n        \"attribute_lookup\": attribute_lookup,\n        \"has_v_sites\": has_v_sites,\n        \"system\": system,\n        \"frames_path\": frames_path,\n        \"beta\": beta,\n        \"pressure\": pressure,\n    }\n\n    *avg_outputs, columns = _EnsembleAverageOp.apply(kwargs, *tensors)\n\n    avg_values = avg_outputs[: len(avg_outputs) // 2]\n    avg_std = avg_outputs[len(avg_outputs) // 2 :]\n\n    return (\n        {column: avg for avg, column in zip(avg_values, columns, strict=True)},\n        {column: avg for avg, column in zip(avg_std, columns, strict=True)},\n    )\n
"},{"location":"reference/mm/#smee.mm.reweight_ensemble_averages","title":"reweight_ensemble_averages","text":"
reweight_ensemble_averages(\n    system: TensorSystem,\n    force_field: TensorForceField,\n    frames_path: Path,\n    temperature: Quantity,\n    pressure: Quantity | None,\n    min_samples: int = 50,\n) -> dict[str, Tensor]\n

Compute the ensemble average of the potential energy, volume, density, and enthalpy (if running NPT) by re-weighting an existing MD trajectory.

Parameters:

  • system (TensorSystem) \u2013

    The system that was simulated.

  • force_field (TensorForceField) \u2013

    The new force field to use.

  • frames_path (Path) \u2013

    The path to the trajectory to compute the average over.

  • temperature (Quantity) \u2013

    The temperature that the trajectory was simulated at.

  • pressure (Quantity | None) \u2013

    The pressure that the trajectory was simulated at.

  • min_samples (int, default: 50 ) \u2013

    The minimum number of samples required to compute the average.

Raises:

  • NotEnoughSamplesError \u2013

    If the number of effective samples is less than min_samples.

Returns:

  • dict[str, Tensor] \u2013

    A dictionary containing the ensemble averages of the potential energy [kcal/mol], volume [\u00c5^3], density [g/mL], and enthalpy [kcal/mol].

Source code in smee/mm/_ops.py
def reweight_ensemble_averages(\n    system: smee.TensorSystem,\n    force_field: smee.TensorForceField,\n    frames_path: pathlib.Path,\n    temperature: openmm.unit.Quantity,\n    pressure: openmm.unit.Quantity | None,\n    min_samples: int = 50,\n) -> dict[str, torch.Tensor]:\n    \"\"\"Compute the ensemble average of the potential energy, volume, density,\n    and enthalpy (if running NPT) by re-weighting an existing MD trajectory.\n\n    Args:\n        system: The system that was simulated.\n        force_field: The new force field to use.\n        frames_path: The path to the trajectory to compute the average over.\n        temperature: The temperature that the trajectory was simulated at.\n        pressure: The pressure that the trajectory was simulated at.\n        min_samples: The minimum number of samples required to compute the average.\n\n    Raises:\n        NotEnoughSamplesError: If the number of effective samples is less than\n            ``min_samples``.\n\n    Returns:\n        A dictionary containing the ensemble averages of the potential energy\n        [kcal/mol], volume [\u00c5^3], density [g/mL], and enthalpy [kcal/mol].\n    \"\"\"\n    tensors, parameter_lookup, attribute_lookup, has_v_sites = _pack_force_field(\n        force_field\n    )\n\n    beta = 1.0 / (openmm.unit.MOLAR_GAS_CONSTANT_R * temperature)\n    beta = beta.value_in_unit(openmm.unit.kilocalorie_per_mole**-1)\n\n    if pressure is not None:\n        pressure = (pressure * openmm.unit.AVOGADRO_CONSTANT_NA).value_in_unit(\n            openmm.unit.kilocalorie_per_mole / openmm.unit.angstrom**3\n        )\n\n    kwargs: _ReweightAverageKwargs = {\n        \"force_field\": force_field,\n        \"parameter_lookup\": parameter_lookup,\n        \"attribute_lookup\": attribute_lookup,\n        \"has_v_sites\": has_v_sites,\n        \"system\": system,\n        \"frames_path\": frames_path,\n        \"beta\": beta,\n        \"pressure\": pressure,\n        \"min_samples\": min_samples,\n    }\n\n    *avg_outputs, columns = _ReweightAverageOp.apply(kwargs, *tensors)\n    return {column: avg for avg, column in zip(avg_outputs, columns, strict=True)}\n
"},{"location":"reference/mm/#smee.mm.tensor_reporter","title":"tensor_reporter","text":"
tensor_reporter(\n    output_path: PathLike,\n    report_interval: int,\n    beta: Quantity,\n    pressure: Quantity | None,\n) -> TensorReporter\n

Create a TensorReporter capable of writing frames to a file.

Parameters:

  • output_path (PathLike) \u2013

    The path to write the frames to.

  • report_interval (int) \u2013

    The interval (in steps) at which to write frames.

  • beta (Quantity) \u2013

    The inverse temperature the simulation is being run at.

  • pressure (Quantity | None) \u2013

    The pressure the simulation is being run at, or None if NVT / vacuum.

Source code in smee/mm/_reporters.py
@contextlib.contextmanager\ndef tensor_reporter(\n    output_path: os.PathLike,\n    report_interval: int,\n    beta: openmm.unit.Quantity,\n    pressure: openmm.unit.Quantity | None,\n) -> TensorReporter:\n    \"\"\"Create a ``TensorReporter`` capable of writing frames to a file.\n\n    Args:\n        output_path: The path to write the frames to.\n        report_interval: The interval (in steps) at which to write frames.\n        beta: The inverse temperature the simulation is being run at.\n        pressure: The pressure the simulation is being run at, or ``None`` if NVT /\n            vacuum.\n    \"\"\"\n    with open(output_path, \"wb\") as output_file:\n        reporter = TensorReporter(output_file, report_interval, beta, pressure)\n        yield reporter\n
"},{"location":"reference/mm/#smee.mm.unpack_frames","title":"unpack_frames","text":"
unpack_frames(\n    file: BinaryIO,\n) -> Generator[tuple[Tensor, Tensor, float], None, None]\n

Unpack frames saved by a TensorReporter.

Source code in smee/mm/_reporters.py
def unpack_frames(\n    file: typing.BinaryIO,\n) -> typing.Generator[tuple[torch.Tensor, torch.Tensor, float], None, None]:\n    \"\"\"Unpack frames saved by a ``TensorReporter``.\"\"\"\n\n    unpacker = msgpack.Unpacker(file, object_hook=_decoder)\n\n    for frame in unpacker:\n        yield frame\n
"},{"location":"reference/potentials/","title":"Index","text":""},{"location":"reference/potentials/#smee.potentials","title":"potentials","text":"

Evaluate the potential energy of parameterized topologies.

Modules:

  • nonbonded \u2013

    Non-bonded potential energy functions.

  • valence \u2013

    Valence potential energy functions.

Functions:

  • broadcast_exceptions \u2013

    Returns the indices of the parameters that should be used to model interactions

  • broadcast_idxs \u2013

    Broadcasts the particle indices of each topology for a given potential

  • broadcast_parameters \u2013

    Returns parameters for the full system by broadcasting and stacking the

  • compute_energy \u2013

    Computes the potential energy [kcal / mol] of a system / topology in a given

  • compute_energy_potential \u2013

    Computes the potential energy [kcal / mol] due to a SMIRNOFF potential

  • potential_energy_fn \u2013

    A decorator used to flag a function as being able to compute the potential for a

"},{"location":"reference/potentials/#smee.potentials.broadcast_exceptions","title":"broadcast_exceptions","text":"
broadcast_exceptions(\n    system: TensorSystem,\n    potential: TensorPotential,\n    idxs_a: Tensor,\n    idxs_b: Tensor,\n) -> tuple[Tensor, Tensor]\n

Returns the indices of the parameters that should be used to model interactions between pairs of particles

Parameters:

  • system (TensorSystem) \u2013

    The system.

  • potential (TensorPotential) \u2013

    The potential whose parameters should be broadcast.

  • idxs_a (Tensor) \u2013

    The indices of the first particle in each interaction with shape=(n_interactions,).

  • idxs_b (Tensor) \u2013

    The indices of the second particle in each interaction with shape=(n_interactions,).

Returns:

  • tuple[Tensor, Tensor] \u2013

    The indices of the interactions that require an exception, and the parameters to use for those interactions.

Source code in smee/potentials/_potentials.py
def broadcast_exceptions(\n    system: smee.TensorSystem,\n    potential: smee.TensorPotential,\n    idxs_a: torch.Tensor,\n    idxs_b: torch.Tensor,\n) -> tuple[torch.Tensor, torch.Tensor]:\n    \"\"\"Returns the indices of the parameters that should be used to model interactions\n    between pairs of particles\n\n    Args:\n        system: The system.\n        potential: The potential whose parameters should be broadcast.\n        idxs_a: The indices of the first particle in each interaction with\n            ``shape=(n_interactions,)``.\n        idxs_b: The indices of the second particle in each interaction with\n            ``shape=(n_interactions,)``.\n\n    Returns:\n        The indices of the interactions that require an exception, and the parameters\n        to use for those interactions.\n    \"\"\"\n    assert potential.exceptions is not None\n\n    parameter_idxs = []\n\n    for topology, n_copies in zip(system.topologies, system.n_copies, strict=True):\n        parameter_map = topology.parameters[potential.type]\n\n        if isinstance(parameter_map, smee.ValenceParameterMap):\n            raise NotImplementedError(\"valence exceptions are not supported\")\n\n        # check that each particle is assigned to exactly one parameter\n        assignment_dense = parameter_map.assignment_matrix.to_dense()\n\n        if not (assignment_dense.abs().sum(axis=-1) == 1).all():\n            raise NotImplementedError(\n                f\"exceptions can only be used when each particle is assigned exactly \"\n                f\"one {potential.type} parameter\"\n            )\n\n        assigned_idxs = assignment_dense.argmax(axis=-1)\n\n        n_particles = len(assigned_idxs)\n\n        assigned_idxs = torch.broadcast_to(\n            assigned_idxs[None, :], (n_copies, n_particles)\n        ).flatten()\n\n        parameter_idxs.append(assigned_idxs)\n\n    if len(parameter_idxs) == 0:\n        return torch.zeros((0,), dtype=torch.int64), torch.zeros(\n            (0, 0, len(potential.parameter_cols))\n        )\n\n    parameter_idxs = torch.concat(parameter_idxs)\n    parameter_idxs_a = parameter_idxs[idxs_a]\n    parameter_idxs_b = parameter_idxs[idxs_b]\n\n    if len({(min(i, j), max(i, j)) for i, j in potential.exceptions}) != len(\n        potential.exceptions\n    ):\n        raise NotImplementedError(\"cannot define different exceptions for i-j and j-i\")\n\n    exception_lookup = torch.full(\n        (len(potential.parameters), len(potential.parameters)), -1\n    )\n\n    for (i, j), v in potential.exceptions.items():\n        exception_lookup[(min(i, j), max(i, j))] = v\n        exception_lookup[(max(i, j), min(i, j))] = v\n\n    exceptions_parameter_idxs = exception_lookup[parameter_idxs_a, parameter_idxs_b]\n    exception_mask = exceptions_parameter_idxs >= 0\n\n    exceptions = potential.parameters[exceptions_parameter_idxs[exception_mask]]\n    exception_idxs = exception_mask.nonzero().flatten()\n\n    return exception_idxs, exceptions\n
"},{"location":"reference/potentials/#smee.potentials.broadcast_idxs","title":"broadcast_idxs","text":"
broadcast_idxs(\n    system: TensorSystem, potential: TensorPotential\n) -> Tensor\n

Broadcasts the particle indices of each topology for a given potential to the full system.

Parameters:

  • system (TensorSystem) \u2013

    The system.

  • potential (TensorPotential) \u2013

    The potential.

Returns:

  • Tensor \u2013

    The indices with shape (n_interactions, n_interacting_particles) where n_interacting_particles is 2 for bonds, 3 for angles, etc.

Source code in smee/potentials/_potentials.py
def broadcast_idxs(\n    system: smee.TensorSystem, potential: smee.TensorPotential\n) -> torch.Tensor:\n    \"\"\"Broadcasts the particle indices of each topology for a given potential\n    to the full system.\n\n    Args:\n        system: The system.\n        potential: The potential.\n\n    Returns:\n        The indices with shape ``(n_interactions, n_interacting_particles)`` where\n        ``n_interacting_particles`` is 2 for bonds, 3 for angles, etc.\n    \"\"\"\n\n    idx_offset = 0\n\n    per_topology_idxs = []\n\n    for topology, n_copies in zip(system.topologies, system.n_copies, strict=True):\n        parameter_map = topology.parameters[potential.type]\n        n_interacting_particles = parameter_map.particle_idxs.shape[-1]\n\n        idxs = parameter_map.particle_idxs\n\n        offset = (\n            idx_offset + smee.utils.arange_like(n_copies, idxs) * topology.n_particles\n        )\n\n        if len(idxs) > 0:\n            idxs = offset[:, None, None] + idxs[None, :, :]\n            per_topology_idxs.append(idxs.reshape(-1, n_interacting_particles))\n\n        idx_offset += n_copies * topology.n_particles\n\n    return (\n        torch.zeros((0, 0))\n        if len(per_topology_idxs) == 0\n        else torch.vstack(per_topology_idxs)\n    )\n
"},{"location":"reference/potentials/#smee.potentials.broadcast_parameters","title":"broadcast_parameters","text":"
broadcast_parameters(\n    system: TensorSystem, potential: TensorPotential\n) -> Tensor\n

Returns parameters for the full system by broadcasting and stacking the parameters of each topology.

Parameters:

  • system (TensorSystem) \u2013

    The system.

  • potential (TensorPotential) \u2013

    The potential whose parameters should be broadcast.

Returns:

  • Tensor \u2013

    The parameters for the full system with shape=(n_parameters, n_parameter_cols).

Source code in smee/potentials/_potentials.py
def broadcast_parameters(\n    system: smee.TensorSystem, potential: smee.TensorPotential\n) -> torch.Tensor:\n    \"\"\"Returns parameters for the full system by broadcasting and stacking the\n    parameters of each topology.\n\n    Args:\n        system: The system.\n        potential: The potential whose parameters should be broadcast.\n\n    Returns:\n        The parameters for the full system with\n        ``shape=(n_parameters, n_parameter_cols)``.\n    \"\"\"\n\n    parameters = []\n\n    for topology, n_copies in zip(system.topologies, system.n_copies, strict=True):\n        parameter_map = topology.parameters[potential.type]\n\n        topology_parameters = parameter_map.assignment_matrix @ potential.parameters\n\n        n_interactions = len(topology_parameters)\n        n_cols = len(potential.parameter_cols)\n\n        topology_parameters = torch.broadcast_to(\n            topology_parameters[None, :, :],\n            (n_copies, n_interactions, n_cols),\n        ).reshape(-1, n_cols)\n\n        parameters.append(topology_parameters)\n\n    return (\n        torch.zeros((0, len(potential.parameter_cols)))\n        if len(parameters) == 0\n        else torch.vstack(parameters)\n    )\n
"},{"location":"reference/potentials/#smee.potentials.compute_energy","title":"compute_energy","text":"
compute_energy(\n    system: TensorSystem | TensorTopology,\n    force_field: TensorForceField,\n    conformer: Tensor,\n    box_vectors: Tensor | None = None,\n) -> Tensor\n

Computes the potential energy [kcal / mol] of a system / topology in a given conformation(s).

Parameters:

  • system (TensorSystem | TensorTopology) \u2013

    The system or topology to compute the potential energy of.

  • force_field (TensorForceField) \u2013

    The force field that defines the potential energy function.

  • conformer (Tensor) \u2013

    The conformer(s) to evaluate the potential at with shape=(n_particles, 3) or shape=(n_confs, n_particles, 3).

  • box_vectors (Tensor | None, default: None ) \u2013

    The box vectors of the system with shape=(3, 3) if the system is periodic, or None if the system is non-periodic.

Returns:

  • Tensor \u2013

    The potential energy of the conformer(s) [kcal / mol].

Source code in smee/potentials/_potentials.py
def compute_energy(\n    system: smee.TensorSystem | smee.TensorTopology,\n    force_field: smee.TensorForceField,\n    conformer: torch.Tensor,\n    box_vectors: torch.Tensor | None = None,\n) -> torch.Tensor:\n    \"\"\"Computes the potential energy [kcal / mol] of a system / topology in a given\n    conformation(s).\n\n    Args:\n        system: The system or topology to compute the potential energy of.\n        force_field: The force field that defines the potential energy function.\n        conformer: The conformer(s) to evaluate the potential at with\n            ``shape=(n_particles, 3)`` or ``shape=(n_confs, n_particles, 3)``.\n        box_vectors: The box vectors of the system with ``shape=(3, 3)`` if the\n            system is periodic, or ``None`` if the system is non-periodic.\n\n    Returns:\n        The potential energy of the conformer(s) [kcal / mol].\n    \"\"\"\n\n    # register the built-in potential energy functions\n    importlib.import_module(\"smee.potentials.nonbonded\")\n    importlib.import_module(\"smee.potentials.valence\")\n\n    system, conformer, box_vectors = _prepare_inputs(system, conformer, box_vectors)\n    pairwise = _precompute_pairwise(system, force_field, conformer, box_vectors)\n\n    energy = smee.utils.zeros_like(\n        conformer.shape[0] if conformer.ndim == 3 else 1, conformer\n    )\n\n    for potential in force_field.potentials:\n        energy += compute_energy_potential(\n            system, potential, conformer, box_vectors, pairwise\n        )\n\n    return energy\n
"},{"location":"reference/potentials/#smee.potentials.compute_energy_potential","title":"compute_energy_potential","text":"
compute_energy_potential(\n    system: TensorSystem | TensorTopology,\n    potential: TensorPotential,\n    conformer: Tensor,\n    box_vectors: Tensor | None = None,\n    pairwise: Optional[PairwiseDistances] = None,\n) -> Tensor\n

Computes the potential energy [kcal / mol] due to a SMIRNOFF potential handler for a given conformer(s).

Parameters:

  • system (TensorSystem | TensorTopology) \u2013

    The system or topology to compute the potential energy of.

  • potential (TensorPotential) \u2013

    The potential describing the energy function to compute.

  • conformer (Tensor) \u2013

    The conformer(s) to evaluate the potential at with shape=(n_particles, 3) or shape=(n_confs, n_particles, 3).

  • box_vectors (Tensor | None, default: None ) \u2013

    The box vectors of the system with shape=(3, 3) or shape=(n_confs, 3, 3)if the system is periodic, orNone`` if the system is non-periodic.

  • pairwise (Optional[PairwiseDistances], default: None ) \u2013

    Pre-computed pairwise distances between particles in the system.

Returns:

  • Tensor \u2013

    The potential energy of the conformer(s) [kcal / mol].

Source code in smee/potentials/_potentials.py
def compute_energy_potential(\n    system: smee.TensorSystem | smee.TensorTopology,\n    potential: smee.TensorPotential,\n    conformer: torch.Tensor,\n    box_vectors: torch.Tensor | None = None,\n    pairwise: typing.Optional[\"smee.potentials.nonbonded.PairwiseDistances\"] = None,\n) -> torch.Tensor:\n    \"\"\"Computes the potential energy [kcal / mol] due to a SMIRNOFF potential\n    handler for a given conformer(s).\n\n    Args:\n        system: The system or topology to compute the potential energy of.\n        potential: The potential describing the energy function to compute.\n        conformer: The conformer(s) to evaluate the potential at with\n            ``shape=(n_particles, 3)`` or ``shape=(n_confs, n_particles, 3)``.\n        box_vectors: The box vectors of the system with ``shape=(3, 3)`` or\n            shape=(n_confs, 3, 3)`` if the system is periodic, or ``None`` if the system\n            is non-periodic.\n        pairwise: Pre-computed pairwise distances between particles in the system.\n\n    Returns:\n        The potential energy of the conformer(s) [kcal / mol].\n    \"\"\"\n\n    # register the built-in potential energy functions\n    importlib.import_module(\"smee.potentials.nonbonded\")\n    importlib.import_module(\"smee.potentials.valence\")\n\n    system, conformer, box_vectors = _prepare_inputs(system, conformer, box_vectors)\n\n    energy_fn = _POTENTIAL_ENERGY_FUNCTIONS[(potential.type, potential.fn)]\n    energy_fn_spec = inspect.signature(energy_fn)\n\n    energy_fn_kwargs = {}\n\n    if \"box_vectors\" in energy_fn_spec.parameters:\n        energy_fn_kwargs[\"box_vectors\"] = box_vectors\n    if \"pairwise\" in energy_fn_spec.parameters:\n        energy_fn_kwargs[\"pairwise\"] = pairwise\n\n    return energy_fn(system, potential, conformer, **energy_fn_kwargs)\n
"},{"location":"reference/potentials/#smee.potentials.potential_energy_fn","title":"potential_energy_fn","text":"
potential_energy_fn(\n    handler_type: str, energy_expression: str\n)\n

A decorator used to flag a function as being able to compute the potential for a specific handler and its associated energy expression.

Source code in smee/potentials/_potentials.py
def potential_energy_fn(handler_type: str, energy_expression: str):\n    \"\"\"A decorator used to flag a function as being able to compute the potential for a\n    specific handler and its associated energy expression.\"\"\"\n\n    def _potential_function_inner(func):\n        if (handler_type, energy_expression) in _POTENTIAL_ENERGY_FUNCTIONS:\n            raise KeyError(\n                f\"A potential energy function is already defined for \"\n                f\"handler={handler_type} fn={energy_expression}.\"\n            )\n\n        _POTENTIAL_ENERGY_FUNCTIONS[(handler_type, energy_expression)] = func\n        return func\n\n    return _potential_function_inner\n
"},{"location":"reference/potentials/nonbonded/","title":" nonbonded","text":""},{"location":"reference/potentials/nonbonded/#smee.potentials.nonbonded","title":"nonbonded","text":"

Non-bonded potential energy functions.

Classes:

  • PairwiseDistances \u2013

    A container for the pairwise distances between all particles, possibly within

Functions:

  • compute_pairwise_scales \u2013

    Returns the scale factor for each pair of particles in the system by

  • compute_pairwise \u2013

    Computes all pairwise distances between particles in the system.

  • prepare_lrc_types \u2013

    Finds the unique vdW interactions present in a system, ready to use

  • lorentz_berthelot \u2013

    Computes the Lorentz-Berthelot combination rules for the given parameters.

  • compute_lj_energy \u2013

    Computes the potential energy [kcal / mol] of the vdW interactions using the

  • compute_dexp_energy \u2013

    Compute the potential energy [kcal / mol] of the vdW interactions using the

  • compute_coulomb_energy \u2013

    Computes the potential energy [kcal / mol] of the electrostatic interactions

"},{"location":"reference/potentials/nonbonded/#smee.potentials.nonbonded.PairwiseDistances","title":"PairwiseDistances","text":"

Bases: NamedTuple

A container for the pairwise distances between all particles, possibly within a given cutoff.

Attributes:

  • idxs (Tensor) \u2013

    The particle indices of each pair with shape=(n_pairs, 2).

  • deltas (Tensor) \u2013

    The vector between each pair with shape=(n_pairs, 3).

  • distances (Tensor) \u2013

    The distance between each pair with shape=(n_pairs,).

  • cutoff (Tensor | None) \u2013

    The cutoff used when computing the distances.

"},{"location":"reference/potentials/nonbonded/#smee.potentials.nonbonded.PairwiseDistances.idxs","title":"idxs instance-attribute","text":"
idxs: Tensor\n

The particle indices of each pair with shape=(n_pairs, 2).

"},{"location":"reference/potentials/nonbonded/#smee.potentials.nonbonded.PairwiseDistances.deltas","title":"deltas instance-attribute","text":"
deltas: Tensor\n

The vector between each pair with shape=(n_pairs, 3).

"},{"location":"reference/potentials/nonbonded/#smee.potentials.nonbonded.PairwiseDistances.distances","title":"distances instance-attribute","text":"
distances: Tensor\n

The distance between each pair with shape=(n_pairs,).

"},{"location":"reference/potentials/nonbonded/#smee.potentials.nonbonded.PairwiseDistances.cutoff","title":"cutoff class-attribute instance-attribute","text":"
cutoff: Tensor | None = None\n

The cutoff used when computing the distances.

"},{"location":"reference/potentials/nonbonded/#smee.potentials.nonbonded.compute_pairwise_scales","title":"compute_pairwise_scales","text":"
compute_pairwise_scales(\n    system: TensorSystem, potential: TensorPotential\n) -> Tensor\n

Returns the scale factor for each pair of particles in the system by broadcasting and stacking the exclusions of each topology.

Parameters:

  • system (TensorSystem) \u2013

    The system.

  • potential (TensorPotential) \u2013

    The potential containing the scale factors to broadcast.

Returns:

  • Tensor \u2013

    The scales for each pair of particles as a flattened upper triangular matrix with shape=(n_particles * (n_particles - 1) / 2,).

Source code in smee/potentials/nonbonded.py
def compute_pairwise_scales(\n    system: smee.TensorSystem, potential: smee.TensorPotential\n) -> torch.Tensor:\n    \"\"\"Returns the scale factor for each pair of particles in the system by\n    broadcasting and stacking the exclusions of each topology.\n\n    Args:\n        system: The system.\n        potential: The potential containing the scale factors to broadcast.\n\n    Returns:\n        The scales for each pair of particles as a flattened upper triangular matrix\n        with ``shape=(n_particles * (n_particles - 1) / 2,)``.\n    \"\"\"\n\n    n_particles = system.n_particles\n    n_pairs = (n_particles * (n_particles - 1)) // 2\n\n    exclusion_idxs, exclusion_scales = _broadcast_exclusions(system, potential)\n\n    pair_scales = smee.utils.ones_like(n_pairs, other=potential.parameters)\n\n    if len(exclusion_idxs) > 0:\n        exclusion_idxs, _ = exclusion_idxs.sort(dim=1)  # ensure upper triangle\n\n        pair_idxs = smee.utils.to_upper_tri_idx(\n            exclusion_idxs[:, 0], exclusion_idxs[:, 1], n_particles\n        )\n        pair_scales[pair_idxs] = exclusion_scales\n\n    return pair_scales\n
"},{"location":"reference/potentials/nonbonded/#smee.potentials.nonbonded.compute_pairwise","title":"compute_pairwise","text":"
compute_pairwise(\n    system: TensorSystem,\n    conformer: Tensor,\n    box_vectors: Tensor | None,\n    cutoff: Tensor,\n) -> PairwiseDistances\n

Computes all pairwise distances between particles in the system.

Notes

If the system is not periodic, no cutoff and no PBC will be applied.

Parameters:

  • system (TensorSystem) \u2013

    The system to compute the distances for.

  • conformer (Tensor) \u2013

    The conformer [\u00c5] to evaluate the potential at with shape=(n_confs, n_particles, 3) or shape=(n_particles, 3).

  • box_vectors (Tensor | None) \u2013

    The box vectors [\u00c5] of the system with shape=(n_confs3, 3) or shape=(3, 3) if the system is periodic, or None otherwise.

  • cutoff (Tensor) \u2013

    The cutoff [\u00c5] to apply for periodic systems.

Returns:

  • PairwiseDistances \u2013

    The pairwise distances between each pair of particles within the cutoff.

Source code in smee/potentials/nonbonded.py
def compute_pairwise(\n    system: smee.TensorSystem,\n    conformer: torch.Tensor,\n    box_vectors: torch.Tensor | None,\n    cutoff: torch.Tensor,\n) -> PairwiseDistances:\n    \"\"\"Computes all pairwise distances between particles in the system.\n\n    Notes:\n        If the system is not periodic, no cutoff and no PBC will be applied.\n\n    Args:\n        system: The system to compute the distances for.\n        conformer: The conformer [\u00c5] to evaluate the potential at with\n            ``shape=(n_confs, n_particles, 3)`` or ``shape=(n_particles, 3)``.\n        box_vectors: The box vectors [\u00c5] of the system with ``shape=(n_confs3, 3)``\n            or ``shape=(3, 3)`` if the system is periodic, or ``None`` otherwise.\n        cutoff: The cutoff [\u00c5] to apply for periodic systems.\n\n    Returns:\n        The pairwise distances between each pair of particles within the cutoff.\n    \"\"\"\n    if system.is_periodic:\n        return _compute_pairwise_periodic(conformer, box_vectors, cutoff)\n    else:\n        return _compute_pairwise_non_periodic(conformer)\n
"},{"location":"reference/potentials/nonbonded/#smee.potentials.nonbonded.prepare_lrc_types","title":"prepare_lrc_types","text":"
prepare_lrc_types(\n    system: TensorSystem, potential: TensorPotential\n) -> tuple[Tensor, Tensor, Tensor]\n

Finds the unique vdW interactions present in a system, ready to use for computing the long range dispersion correction.

Parameters:

  • system (TensorSystem) \u2013

    The system to prepare the types for.

  • potential (TensorPotential) \u2013

    The potential to prepare the types for.

Returns:

  • tuple[Tensor, Tensor, Tensor] \u2013

    Two tensors containing the i and j indices into potential.paramaters of each unique interaction parameter excluding i==j, the number of ii interactions with shape=(n_params,), and the numbers of ij interactions with shape=(len(idxs_i),).

Source code in smee/potentials/nonbonded.py
def prepare_lrc_types(\n    system: smee.TensorSystem,\n    potential: smee.TensorPotential,\n) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:\n    \"\"\"Finds the unique vdW interactions present in a system, ready to use\n    for computing the long range dispersion correction.\n\n    Args:\n        system: The system to prepare the types for.\n        potential: The potential to prepare the types for.\n\n    Returns:\n        Two tensors containing the i and j indices into ``potential.paramaters`` of\n        each unique interaction parameter excluding ``i==j``, the number of ``ii``\n        interactions with ``shape=(n_params,)``, and the numbers of ``ij`` interactions\n        with ``shape=(len(idxs_i),)``.\n    \"\"\"\n    n_by_type = collections.defaultdict(int)\n\n    for topology, n_copies in zip(system.topologies, system.n_copies, strict=True):\n        parameter_counts = topology.parameters[\"vdW\"].assignment_matrix.abs().sum(dim=0)\n\n        for key, count in zip(potential.parameter_keys, parameter_counts, strict=True):\n            n_by_type[key] += count.item() * n_copies\n\n    counts = smee.utils.tensor_like(\n        [n_by_type[key] for key in potential.parameter_keys], potential.parameters\n    )\n\n    n_ii_interactions = (counts * (counts + 1.0)) / 2.0\n\n    idxs_i, idxs_j = torch.triu_indices(len(counts), len(counts), 1)\n    n_ij_interactions = counts[idxs_i] * counts[idxs_j]\n\n    idxs_ii = torch.arange(len(counts))\n\n    idxs_i = torch.cat([idxs_i, idxs_ii])\n    idxs_j = torch.cat([idxs_j, idxs_ii])\n\n    n_ij_interactions = torch.cat([n_ij_interactions, n_ii_interactions])\n\n    return idxs_i, idxs_j, n_ij_interactions\n
"},{"location":"reference/potentials/nonbonded/#smee.potentials.nonbonded.lorentz_berthelot","title":"lorentz_berthelot","text":"
lorentz_berthelot(\n    epsilon_a: Tensor,\n    epsilon_b: Tensor,\n    sigma_a: Tensor,\n    sigma_b: Tensor,\n) -> tuple[Tensor, Tensor]\n

Computes the Lorentz-Berthelot combination rules for the given parameters.

Notes

A 'safe' geometric mean is used to avoid NaNs when the parameters are zero. This will yield non-analytic gradients in some cases.

Parameters:

  • epsilon_a (Tensor) \u2013

    The epsilon [kcal / mol] values of the first particle in each pair with shape=(n_pairs, 1).

  • epsilon_b (Tensor) \u2013

    The epsilon [kcal / mol] values of the second particle in each pair with shape=(n_pairs, 1).

  • sigma_a (Tensor) \u2013

    The sigma [kcal / mol] values of the first particle in each pair with shape=(n_pairs, 1).

  • sigma_b (Tensor) \u2013

    The sigma [kcal / mol] values of the second particle in each pair with shape=(n_pairs, 1).

Returns:

  • tuple[Tensor, Tensor] \u2013

    The epsilon [kcal / mol] and sigma [\u00c5] values of each pair, each with shape=(n_pairs, 1).

Source code in smee/potentials/nonbonded.py
def lorentz_berthelot(\n    epsilon_a: torch.Tensor,\n    epsilon_b: torch.Tensor,\n    sigma_a: torch.Tensor,\n    sigma_b: torch.Tensor,\n) -> tuple[torch.Tensor, torch.Tensor]:\n    \"\"\"Computes the Lorentz-Berthelot combination rules for the given parameters.\n\n    Notes:\n        A 'safe' geometric mean is used to avoid NaNs when the parameters are zero.\n        This will yield non-analytic gradients in some cases.\n\n    Args:\n        epsilon_a: The epsilon [kcal / mol] values of the first particle in each pair\n            with ``shape=(n_pairs, 1)``.\n        epsilon_b: The epsilon [kcal / mol] values of the second particle in each pair\n            with ``shape=(n_pairs, 1)``.\n        sigma_a: The sigma [kcal / mol] values of the first particle in each pair\n            with ``shape=(n_pairs, 1)``.\n        sigma_b: The sigma [kcal / mol] values of the second particle in each pair\n            with ``shape=(n_pairs, 1)``.\n\n    Returns:\n        The epsilon [kcal / mol] and sigma [\u00c5] values of each pair, each with\n        ``shape=(n_pairs, 1)``.\n    \"\"\"\n    return smee.utils.geometric_mean(epsilon_a, epsilon_b), 0.5 * (sigma_a + sigma_b)\n
"},{"location":"reference/potentials/nonbonded/#smee.potentials.nonbonded.compute_lj_energy","title":"compute_lj_energy","text":"
compute_lj_energy(\n    system: TensorSystem,\n    potential: TensorPotential,\n    conformer: Tensor,\n    box_vectors: Tensor | None = None,\n    pairwise: PairwiseDistances | None = None,\n) -> Tensor\n

Computes the potential energy [kcal / mol] of the vdW interactions using the standard Lennard-Jones potential.

Notes
  • No cutoff / switching function will be applied if the system is not periodic.
  • A switching function will only be applied if the potential has a switch_width attribute.

Parameters:

  • system (TensorSystem) \u2013

    The system to compute the energy for.

  • potential (TensorPotential) \u2013

    The potential energy function to evaluate.

  • conformer (Tensor) \u2013

    The conformer [\u00c5] to evaluate the potential at with shape=(n_confs, n_particles, 3) or shape=(n_particles, 3).

  • box_vectors (Tensor | None, default: None ) \u2013

    The box vectors [\u00c5] of the system with shape=(n_confs, 3, 3) or shape=(3, 3) if the system is periodic, or None otherwise.

  • pairwise (PairwiseDistances | None, default: None ) \u2013

    The pre-computed pairwise distances between each pair of particles in the system. If none, these will be computed within the function.

Returns:

  • Tensor \u2013

    The computed potential energy [kcal / mol] with shape=(n_confs,) if the input conformer has a batch dimension, or shape=() otherwise.

Source code in smee/potentials/nonbonded.py
@smee.potentials.potential_energy_fn(smee.PotentialType.VDW, smee.EnergyFn.VDW_LJ)\ndef compute_lj_energy(\n    system: smee.TensorSystem,\n    potential: smee.TensorPotential,\n    conformer: torch.Tensor,\n    box_vectors: torch.Tensor | None = None,\n    pairwise: PairwiseDistances | None = None,\n) -> torch.Tensor:\n    \"\"\"Computes the potential energy [kcal / mol] of the vdW interactions using the\n    standard Lennard-Jones potential.\n\n    Notes:\n        * No cutoff / switching function will be applied if the system is not\n          periodic.\n        * A switching function will only be applied if the potential has a\n          ``switch_width`` attribute.\n\n    Args:\n        system: The system to compute the energy for.\n        potential: The potential energy function to evaluate.\n        conformer: The conformer [\u00c5] to evaluate the potential at with\n            ``shape=(n_confs, n_particles, 3)`` or ``shape=(n_particles, 3)``.\n        box_vectors: The box vectors [\u00c5] of the system with ``shape=(n_confs, 3, 3)``\n            or ``shape=(3, 3)`` if the system is periodic, or ``None`` otherwise.\n        pairwise: The pre-computed pairwise distances between each pair of particles\n            in the system. If none, these will be computed within the function.\n\n    Returns:\n        The computed potential energy [kcal / mol] with ``shape=(n_confs,)`` if the\n        input conformer has a batch dimension, or ``shape=()`` otherwise.\n    \"\"\"\n\n    box_vectors = None if not system.is_periodic else box_vectors\n\n    cutoff = potential.attributes[potential.attribute_cols.index(smee.CUTOFF_ATTRIBUTE)]\n\n    pairwise = (\n        pairwise\n        if pairwise is not None\n        else compute_pairwise(system, conformer, box_vectors, cutoff)\n    )\n\n    if system.is_periodic and not torch.isclose(pairwise.cutoff, cutoff):\n        raise ValueError(\"the pairwise cutoff does not match the potential.\")\n\n    parameters = smee.potentials.broadcast_parameters(system, potential)\n    pair_scales = compute_pairwise_scales(system, potential)\n\n    pairs_1d = smee.utils.to_upper_tri_idx(\n        pairwise.idxs[:, 0], pairwise.idxs[:, 1], len(parameters)\n    )\n    pair_scales = pair_scales[pairs_1d]\n\n    eps_column = potential.parameter_cols.index(\"epsilon\")\n    sig_column = potential.parameter_cols.index(\"sigma\")\n\n    eps, sig = lorentz_berthelot(\n        parameters[pairwise.idxs[:, 0], eps_column],\n        parameters[pairwise.idxs[:, 1], eps_column],\n        parameters[pairwise.idxs[:, 0], sig_column],\n        parameters[pairwise.idxs[:, 1], sig_column],\n    )\n\n    if potential.exceptions is not None:\n        exception_idxs, exceptions = smee.potentials.broadcast_exceptions(\n            system, potential, pairwise.idxs[:, 0], pairwise.idxs[:, 1]\n        )\n\n        eps, sig = eps.clone(), sig.clone()  # prevent in-place modification\n\n        eps[exception_idxs] = exceptions[:, eps_column]\n        sig[exception_idxs] = exceptions[:, sig_column]\n\n    x = (sig / pairwise.distances) ** 6\n    energies = pair_scales * 4.0 * eps * (x * (x - 1.0))\n\n    if not system.is_periodic:\n        return energies.sum(-1)\n\n    switch_fn, switch_width = _compute_switch_fn(potential, pairwise)\n    energies *= switch_fn\n\n    energy = energies.sum(-1)\n    energy += _compute_lj_lrc(\n        system,\n        potential.to(precision=\"double\"),\n        switch_width.double(),\n        pairwise.cutoff.double(),\n        torch.det(box_vectors),\n    )\n\n    return energy\n
"},{"location":"reference/potentials/nonbonded/#smee.potentials.nonbonded.compute_dexp_energy","title":"compute_dexp_energy","text":"
compute_dexp_energy(\n    system: TensorSystem,\n    potential: TensorPotential,\n    conformer: Tensor,\n    box_vectors: Tensor | None = None,\n    pairwise: PairwiseDistances | None = None,\n) -> Tensor\n

Compute the potential energy [kcal / mol] of the vdW interactions using the double-exponential potential.

Notes
  • No cutoff function will be applied if the system is not periodic.

Parameters:

  • system (TensorSystem) \u2013

    The system to compute the energy for.

  • potential (TensorPotential) \u2013

    The potential energy function to evaluate.

  • conformer (Tensor) \u2013

    The conformer [\u00c5] to evaluate the potential at with shape=(n_confs, n_particles, 3) or shape=(n_particles, 3).

  • box_vectors (Tensor | None, default: None ) \u2013

    The box vectors [\u00c5] of the system with shape=(n_confs, 3, 3) or shape=(3, 3) if the system is periodic, or None otherwise.

  • pairwise (PairwiseDistances | None, default: None ) \u2013

    Pre-computed distances between each pair of particles in the system.

Returns:

  • Tensor \u2013

    The evaluated potential energy [kcal / mol].

Source code in smee/potentials/nonbonded.py
@smee.potentials.potential_energy_fn(smee.PotentialType.VDW, smee.EnergyFn.VDW_DEXP)\ndef compute_dexp_energy(\n    system: smee.TensorSystem,\n    potential: smee.TensorPotential,\n    conformer: torch.Tensor,\n    box_vectors: torch.Tensor | None = None,\n    pairwise: PairwiseDistances | None = None,\n) -> torch.Tensor:\n    \"\"\"Compute the potential energy [kcal / mol] of the vdW interactions using the\n    double-exponential potential.\n\n    Notes:\n        * No cutoff function will be applied if the system is not periodic.\n\n    Args:\n        system: The system to compute the energy for.\n        potential: The potential energy function to evaluate.\n        conformer: The conformer [\u00c5] to evaluate the potential at with\n            ``shape=(n_confs, n_particles, 3)`` or ``shape=(n_particles, 3)``.\n        box_vectors: The box vectors [\u00c5] of the system with ``shape=(n_confs, 3, 3)``\n            or ``shape=(3, 3)`` if the system is periodic, or ``None`` otherwise.\n        pairwise: Pre-computed distances between each pair of particles\n            in the system.\n\n    Returns:\n        The evaluated potential energy [kcal / mol].\n    \"\"\"\n    box_vectors = None if not system.is_periodic else box_vectors\n\n    cutoff = potential.attributes[potential.attribute_cols.index(smee.CUTOFF_ATTRIBUTE)]\n\n    pairwise = (\n        pairwise\n        if pairwise is not None\n        else compute_pairwise(system, conformer, box_vectors, cutoff)\n    )\n\n    if system.is_periodic and not torch.isclose(pairwise.cutoff, cutoff):\n        raise ValueError(\"the pairwise cutoff does not match the potential.\")\n\n    parameters = smee.potentials.broadcast_parameters(system, potential)\n    pair_scales = compute_pairwise_scales(system, potential)\n\n    pairs_1d = smee.utils.to_upper_tri_idx(\n        pairwise.idxs[:, 0], pairwise.idxs[:, 1], len(parameters)\n    )\n    pair_scales = pair_scales[pairs_1d]\n\n    eps_column = potential.parameter_cols.index(\"epsilon\")\n    r_min_column = potential.parameter_cols.index(\"r_min\")\n\n    eps, r_min = smee.potentials.nonbonded.lorentz_berthelot(\n        parameters[pairwise.idxs[:, 0], eps_column],\n        parameters[pairwise.idxs[:, 1], eps_column],\n        parameters[pairwise.idxs[:, 0], r_min_column],\n        parameters[pairwise.idxs[:, 1], r_min_column],\n    )\n\n    if potential.exceptions is not None:\n        exception_idxs, exceptions = smee.potentials.broadcast_exceptions(\n            system, potential, pairwise.idxs[:, 0], pairwise.idxs[:, 1]\n        )\n\n        eps, r_min = eps.clone(), r_min.clone()  # prevent in-place modification\n\n        eps[exception_idxs] = exceptions[:, eps_column]\n        r_min[exception_idxs] = exceptions[:, r_min_column]\n\n    alpha = potential.attributes[potential.attribute_cols.index(\"alpha\")]\n    beta = potential.attributes[potential.attribute_cols.index(\"beta\")]\n\n    x = pairwise.distances / r_min\n\n    energies_repulsion = beta / (alpha - beta) * torch.exp(alpha * (1.0 - x))\n    energies_attraction = alpha / (alpha - beta) * torch.exp(beta * (1.0 - x))\n\n    energies = pair_scales * eps * (energies_repulsion - energies_attraction)\n\n    if not system.is_periodic:\n        return energies.sum(-1)\n\n    switch_fn, switch_width = _compute_switch_fn(potential, pairwise)\n    energies *= switch_fn\n\n    energy = energies.sum(-1)\n\n    energy += _compute_dexp_lrc(\n        system,\n        potential.to(precision=\"double\"),\n        switch_width.double(),\n        pairwise.cutoff.double(),\n        torch.det(box_vectors),\n    )\n\n    return energy\n
"},{"location":"reference/potentials/nonbonded/#smee.potentials.nonbonded.compute_coulomb_energy","title":"compute_coulomb_energy","text":"
compute_coulomb_energy(\n    system: TensorSystem,\n    potential: TensorPotential,\n    conformer: Tensor,\n    box_vectors: Tensor | None = None,\n    pairwise: PairwiseDistances | None = None,\n) -> Tensor\n

Computes the potential energy [kcal / mol] of the electrostatic interactions using the Coulomb potential.

Notes
  • No cutoff will be applied if the system is not periodic.
  • PME will be used to compute the energy if the system is periodic.

Parameters:

  • system (TensorSystem) \u2013

    The system to compute the energy for.

  • potential (TensorPotential) \u2013

    The potential energy function to evaluate.

  • conformer (Tensor) \u2013

    The conformer [\u00c5] to evaluate the potential at with shape=(n_confs, n_particles, 3) or shape=(n_particles, 3).

  • box_vectors (Tensor | None, default: None ) \u2013

    The box vectors [\u00c5] of the system with shape=(n_confs, 3, 3) or shape=(3, 3) if the system is periodic, or None otherwise.

  • pairwise (PairwiseDistances | None, default: None ) \u2013

    The pre-computed pairwise distances between each pair of particles in the system. If none, these will be computed within the function.

Returns:

  • Tensor \u2013

    The computed potential energy [kcal / mol] with shape=(n_confs,) if the input conformer has a batch dimension, or shape=() otherwise.

Source code in smee/potentials/nonbonded.py
@smee.potentials.potential_energy_fn(\n    smee.PotentialType.ELECTROSTATICS, smee.EnergyFn.COULOMB\n)\ndef compute_coulomb_energy(\n    system: smee.TensorSystem,\n    potential: smee.TensorPotential,\n    conformer: torch.Tensor,\n    box_vectors: torch.Tensor | None = None,\n    pairwise: PairwiseDistances | None = None,\n) -> torch.Tensor:\n    \"\"\"Computes the potential energy [kcal / mol] of the electrostatic interactions\n    using the Coulomb potential.\n\n    Notes:\n        * No cutoff will be applied if the system is not periodic.\n        * PME will be used to compute the energy if the system is periodic.\n\n    Args:\n        system: The system to compute the energy for.\n        potential: The potential energy function to evaluate.\n        conformer: The conformer [\u00c5] to evaluate the potential at with\n            ``shape=(n_confs, n_particles, 3)`` or ``shape=(n_particles, 3)``.\n        box_vectors: The box vectors [\u00c5] of the system with ``shape=(n_confs, 3, 3)``\n            or ``shape=(3, 3)`` if the system is periodic, or ``None`` otherwise.\n        pairwise: The pre-computed pairwise distances between each pair of particles\n            in the system. If none, these will be computed within the function.\n\n    Returns:\n        The computed potential energy [kcal / mol] with ``shape=(n_confs,)`` if the\n        input conformer has a batch dimension, or ``shape=()`` otherwise.\n    \"\"\"\n\n    box_vectors = None if not system.is_periodic else box_vectors\n\n    cutoff = potential.attributes[potential.attribute_cols.index(smee.CUTOFF_ATTRIBUTE)]\n\n    pairwise = (\n        pairwise\n        if pairwise is not None\n        else compute_pairwise(system, conformer, box_vectors, cutoff)\n    )\n\n    if system.is_periodic and not torch.isclose(pairwise.cutoff, cutoff):\n        raise ValueError(\"the distance cutoff does not match the potential.\")\n\n    if potential.exceptions is not None:\n        raise NotImplementedError(\"exceptions are not supported for charges.\")\n\n    if system.is_periodic:\n        return _compute_coulomb_energy_periodic(\n            system, conformer, box_vectors, potential, pairwise\n        )\n    else:\n        return _compute_coulomb_energy_non_periodic(system, potential, pairwise)\n
"},{"location":"reference/potentials/valence/","title":" valence","text":""},{"location":"reference/potentials/valence/#smee.potentials.valence","title":"valence","text":"

Valence potential energy functions.

Functions:

  • compute_harmonic_bond_energy \u2013

    Compute the potential energy [kcal / mol] of a set of bonds for a given

  • compute_harmonic_angle_energy \u2013

    Compute the potential energy [kcal / mol] of a set of valence angles for a given

  • compute_cosine_proper_torsion_energy \u2013

    Compute the potential energy [kcal / mol] of a set of proper torsions

  • compute_cosine_improper_torsion_energy \u2013

    Compute the potential energy [kcal / mol] of a set of improper torsions

"},{"location":"reference/potentials/valence/#smee.potentials.valence.compute_harmonic_bond_energy","title":"compute_harmonic_bond_energy","text":"
compute_harmonic_bond_energy(\n    system: TensorSystem,\n    potential: TensorPotential,\n    conformer: Tensor,\n) -> Tensor\n

Compute the potential energy [kcal / mol] of a set of bonds for a given conformer using a harmonic potential of the form 1/2 * k * (r - length) ** 2

Parameters:

  • system (TensorSystem) \u2013

    The system to compute the energy for.

  • potential (TensorPotential) \u2013

    The potential energy function to evaluate.

  • conformer (Tensor) \u2013

    The conformer [\u00c5] to evaluate the potential at with shape=(n_confs, n_particles, 3) or shape=(n_particles, 3).

Returns:

  • Tensor \u2013

    The computed potential energy [kcal / mol].

Source code in smee/potentials/valence.py
@smee.potentials.potential_energy_fn(\n    smee.PotentialType.BONDS, smee.EnergyFn.BOND_HARMONIC\n)\ndef compute_harmonic_bond_energy(\n    system: smee.TensorSystem,\n    potential: smee.TensorPotential,\n    conformer: torch.Tensor,\n) -> torch.Tensor:\n    \"\"\"Compute the potential energy [kcal / mol] of a set of bonds for a given\n    conformer using a harmonic potential of the form ``1/2 * k * (r - length) ** 2``\n\n    Args:\n        system: The system to compute the energy for.\n        potential: The potential energy function to evaluate.\n        conformer: The conformer [\u00c5] to evaluate the potential at with\n            ``shape=(n_confs, n_particles, 3)`` or ``shape=(n_particles, 3)``.\n\n    Returns:\n        The computed potential energy [kcal / mol].\n    \"\"\"\n\n    parameters = smee.potentials.broadcast_parameters(system, potential)\n    particle_idxs = smee.potentials.broadcast_idxs(system, potential)\n\n    _, distances = smee.geometry.compute_bond_vectors(conformer, particle_idxs)\n\n    k = parameters[:, potential.parameter_cols.index(\"k\")]\n    length = parameters[:, potential.parameter_cols.index(\"length\")]\n\n    return (0.5 * k * (distances - length) ** 2).sum(-1)\n
"},{"location":"reference/potentials/valence/#smee.potentials.valence.compute_harmonic_angle_energy","title":"compute_harmonic_angle_energy","text":"
compute_harmonic_angle_energy(\n    system: TensorSystem,\n    potential: TensorPotential,\n    conformer: Tensor,\n) -> Tensor\n

Compute the potential energy [kcal / mol] of a set of valence angles for a given conformer using a harmonic potential of the form 1/2 * k * (theta - angle) ** 2

Parameters:

  • system (TensorSystem) \u2013

    The system to compute the energy for.

  • potential (TensorPotential) \u2013

    The potential energy function to evaluate.

  • conformer (Tensor) \u2013

    The conformer [\u00c5] to evaluate the potential at with shape=(n_confs, n_particles, 3) or shape=(n_particles, 3).

Returns:

  • Tensor \u2013

    The computed potential energy [kcal / mol].

Source code in smee/potentials/valence.py
@smee.potentials.potential_energy_fn(\n    smee.PotentialType.ANGLES, smee.EnergyFn.ANGLE_HARMONIC\n)\ndef compute_harmonic_angle_energy(\n    system: smee.TensorSystem,\n    potential: smee.TensorPotential,\n    conformer: torch.Tensor,\n) -> torch.Tensor:\n    \"\"\"Compute the potential energy [kcal / mol] of a set of valence angles for a given\n    conformer using a harmonic potential of the form ``1/2 * k * (theta - angle) ** 2``\n\n    Args:\n        system: The system to compute the energy for.\n        potential: The potential energy function to evaluate.\n        conformer: The conformer [\u00c5] to evaluate the potential at with\n            ``shape=(n_confs, n_particles, 3)`` or ``shape=(n_particles, 3)``.\n\n    Returns:\n        The computed potential energy [kcal / mol].\n    \"\"\"\n\n    parameters = smee.potentials.broadcast_parameters(system, potential)\n    particle_idxs = smee.potentials.broadcast_idxs(system, potential)\n\n    theta = smee.geometry.compute_angles(conformer, particle_idxs)\n\n    k = parameters[:, potential.parameter_cols.index(\"k\")]\n    angle = parameters[:, potential.parameter_cols.index(\"angle\")]\n\n    return (0.5 * k * (theta - angle) ** 2).sum(-1)\n
"},{"location":"reference/potentials/valence/#smee.potentials.valence.compute_cosine_proper_torsion_energy","title":"compute_cosine_proper_torsion_energy","text":"
compute_cosine_proper_torsion_energy(\n    system: TensorSystem,\n    potential: TensorPotential,\n    conformer: Tensor,\n) -> Tensor\n

Compute the potential energy [kcal / mol] of a set of proper torsions for a given conformer using a cosine potential of the form:

k*(1+cos(periodicity*theta-phase))

Parameters:

  • system (TensorSystem) \u2013

    The system to compute the energy for.

  • potential (TensorPotential) \u2013

    The potential energy function to evaluate.

  • conformer (Tensor) \u2013

    The conformer [\u00c5] to evaluate the potential at with shape=(n_confs, n_particles, 3) or shape=(n_particles, 3).

Returns:

  • Tensor \u2013

    The computed potential energy [kcal / mol].

Source code in smee/potentials/valence.py
@smee.potentials.potential_energy_fn(\n    smee.PotentialType.PROPER_TORSIONS, smee.EnergyFn.TORSION_COSINE\n)\ndef compute_cosine_proper_torsion_energy(\n    system: smee.TensorSystem,\n    potential: smee.TensorPotential,\n    conformer: torch.Tensor,\n) -> torch.Tensor:\n    \"\"\"Compute the potential energy [kcal / mol] of a set of proper torsions\n    for a given conformer using a cosine potential of the form:\n\n    `k*(1+cos(periodicity*theta-phase))`\n\n    Args:\n        system: The system to compute the energy for.\n        potential: The potential energy function to evaluate.\n        conformer: The conformer [\u00c5] to evaluate the potential at with\n            ``shape=(n_confs, n_particles, 3)`` or ``shape=(n_particles, 3)``.\n\n    Returns:\n        The computed potential energy [kcal / mol].\n    \"\"\"\n    return _compute_cosine_torsion_energy(system, potential, conformer)\n
"},{"location":"reference/potentials/valence/#smee.potentials.valence.compute_cosine_improper_torsion_energy","title":"compute_cosine_improper_torsion_energy","text":"
compute_cosine_improper_torsion_energy(\n    system: TensorSystem,\n    potential: TensorPotential,\n    conformer: Tensor,\n) -> Tensor\n

Compute the potential energy [kcal / mol] of a set of improper torsions for a given conformer using a cosine potential of the form:

k*(1+cos(periodicity*theta-phase))

Parameters:

  • system (TensorSystem) \u2013

    The system to compute the energy for.

  • potential (TensorPotential) \u2013

    The potential energy function to evaluate.

  • conformer (Tensor) \u2013

    The conformer [\u00c5] to evaluate the potential at with shape=(n_confs, n_particles, 3) or shape=(n_particles, 3).

Returns:

  • Tensor \u2013

    The computed potential energy [kcal / mol].

Source code in smee/potentials/valence.py
@smee.potentials.potential_energy_fn(\n    smee.PotentialType.IMPROPER_TORSIONS, smee.EnergyFn.TORSION_COSINE\n)\ndef compute_cosine_improper_torsion_energy(\n    system: smee.TensorSystem,\n    potential: smee.TensorPotential,\n    conformer: torch.Tensor,\n) -> torch.Tensor:\n    \"\"\"Compute the potential energy [kcal / mol] of a set of improper torsions\n    for a given conformer using a cosine potential of the form:\n\n    `k*(1+cos(periodicity*theta-phase))`\n\n    Args:\n        system: The system to compute the energy for.\n        potential: The potential energy function to evaluate.\n        conformer: The conformer [\u00c5] to evaluate the potential at with\n            ``shape=(n_confs, n_particles, 3)`` or ``shape=(n_particles, 3)``.\n\n    Returns:\n        The computed potential energy [kcal / mol].\n    \"\"\"\n    return _compute_cosine_torsion_energy(system, potential, conformer)\n
"}]} \ No newline at end of file diff --git a/latest/sitemap.xml.gz b/latest/sitemap.xml.gz index e9b85766ce0e866d2f6541135bd53a2df8aa801f..e10461cd57c2f5af3ad90f87574c557de4be2b7f 100644 GIT binary patch delta 15 WcmeBS?qOz=@8;lGXuFZEjS&DJS_At4 delta 15 WcmeBS?qOz=@8;kLx8BIs#s~l%lmo8-