From 4c4eb47a26f4806c3e7c2415e382382e4b741d57 Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Sun, 10 Nov 2024 14:43:20 +0000 Subject: [PATCH] Deployed cb2c648 to latest with MkDocs 1.6.1 and mike 2.1.2 --- latest/reference/mm/index.html | 374 ++++++++++++++++++-------------- latest/search/search_index.json | 2 +- 2 files changed, 216 insertions(+), 160 deletions(-) diff --git a/latest/reference/mm/index.html b/latest/reference/mm/index.html index 2f569d9..fce1808 100644 --- a/latest/reference/mm/index.html +++ b/latest/reference/mm/index.html @@ -2541,21 +2541,29 @@

#

generate_dg_solv_data(
     solute: TensorTopology,
-    solvent: TensorTopology,
-    force_field: TensorForceField,
-    temperature: Quantity = 298.15 * kelvin,
-    pressure: Quantity = 1.0 * atmosphere,
-    vacuum_protocol: Optional[EquilibriumProtocol] = None,
-    solvent_protocol: Optional[EquilibriumProtocol] = None,
-    n_solvent: int = 216,
-    output_dir: Path | None = None,
-)
+    solvent_a: TensorTopology | None,
+    solvent_b: TensorTopology | None,
+    force_field: TensorForceField,
+    temperature: Quantity = 298.15 * kelvin,
+    pressure: Quantity = 1.0 * atmosphere,
+    solvent_a_protocol: Optional[
+        EquilibriumProtocol
+    ] = None,
+    solvent_b_protocol: Optional[
+        EquilibriumProtocol
+    ] = None,
+    n_solvent_a: int = 216,
+    n_solvent_b: int = 216,
+    output_dir: Path | None = None,
+)
 

Run a solvation free energy calculation using absolv, and saves the output such that a differentiable free energy can be computed.

+

The free energy will correspond to the free energy of transferring a solute from +solvent A to solvent B.

Parameters:

@@ -2569,11 +2577,19 @@

  • - solvent - (TensorTopology) + solvent_a + (TensorTopology | None) + – +
    +

    The topology of solvent A, or None if solvent A is vacuum.

    +
    +
  • +
  • + solvent_b + (TensorTopology | None) –
    -

    The solvent topology.

    +

    The topology of solvent B, or None if solvent B is vacuum.

  • @@ -2605,33 +2621,43 @@

  • - vacuum_protocol + solvent_a_protocol (Optional[EquilibriumProtocol], default: None ) –
    -

    The protocol to use for the vacuum phase.

    +

    The protocol to use to decouple the solute in solvent A.

  • - solvent_protocol + solvent_b_protocol (Optional[EquilibriumProtocol], default: None ) –
    -

    The protocol to use for the solvent phase.

    +

    The protocol to use to decouple the solute in solvent B.

  • - n_solvent + n_solvent_a (int, default: 216 ) –
    -

    The number of solvent molecules to use.

    +

    The number of solvent A molecules to use.

    +
    +
  • +
  • + n_solvent_b + (int, default: + 216 +) + – +
    +

    The number of solvent B molecules to use.

  • @@ -2648,21 +2674,7 @@

    Source code in smee/mm/_fe.py -
     51
    - 52
    - 53
    - 54
    - 55
    - 56
    - 57
    - 58
    - 59
    - 60
    - 61
    - 62
    - 63
    - 64
    - 65
    +              
     65
      66
      67
      68
    @@ -2776,135 +2788,179 @@ 

    176 177 178 -179

    def generate_dg_solv_data(
    -    solute: smee.TensorTopology,
    -    solvent: smee.TensorTopology,
    -    force_field: smee.TensorForceField,
    -    temperature: openmm.unit.Quantity = 298.15 * openmm.unit.kelvin,
    -    pressure: openmm.unit.Quantity = 1.0 * openmm.unit.atmosphere,
    -    vacuum_protocol: typing.Optional["absolv.config.EquilibriumProtocol"] = None,
    -    solvent_protocol: typing.Optional["absolv.config.EquilibriumProtocol"] = None,
    -    n_solvent: int = 216,
    -    output_dir: pathlib.Path | None = None,
    -):
    -    """Run a solvation free energy calculation using ``absolv``, and saves the output
    -    such that a differentiable free energy can be computed.
    -
    -    Args:
    -        solute: The solute topology.
    -        solvent: The solvent topology.
    -        force_field: The force field to parameterize the system with.
    -        temperature: The temperature to simulate at.
    -        pressure: The pressure to simulate at.
    -        vacuum_protocol: The protocol to use for the vacuum phase.
    -        solvent_protocol: The protocol to use for the solvent phase.
    -        n_solvent: The number of solvent molecules to use.
    -        output_dir: The directory to write the output FEP data to.
    -    """
    -    import absolv.config
    -    import absolv.runner
    -    import femto.md.config
    -    import femto.md.system
    +179
    +180
    +181
    +182
    +183
    +184
    +185
    +186
    +187
    +188
    +189
    +190
    +191
    +192
    +193
    +194
    +195
    +196
    +197
    +198
    +199
    +200
    +201
    +202
    +203
    +204
    +205
    +206
    +207
    +208
    def generate_dg_solv_data(
    +    solute: smee.TensorTopology,
    +    solvent_a: smee.TensorTopology | None,
    +    solvent_b: smee.TensorTopology | None,
    +    force_field: smee.TensorForceField,
    +    temperature: openmm.unit.Quantity = 298.15 * openmm.unit.kelvin,
    +    pressure: openmm.unit.Quantity = 1.0 * openmm.unit.atmosphere,
    +    solvent_a_protocol: typing.Optional["absolv.config.EquilibriumProtocol"] = None,
    +    solvent_b_protocol: typing.Optional["absolv.config.EquilibriumProtocol"] = None,
    +    n_solvent_a: int = 216,
    +    n_solvent_b: int = 216,
    +    output_dir: pathlib.Path | None = None,
    +):
    +    """Run a solvation free energy calculation using ``absolv``, and saves the output
    +    such that a differentiable free energy can be computed.
     
    -    output_dir = pathlib.Path.cwd() if output_dir is None else output_dir
    -
    -    if vacuum_protocol is None:
    -        vacuum_protocol = absolv.config.EquilibriumProtocol(
    -            production_protocol=absolv.config.HREMDProtocol(
    -                n_steps_per_cycle=500,
    -                n_cycles=2000,
    -                integrator=femto.md.config.LangevinIntegrator(
    -                    timestep=1.0 * openmm.unit.femtosecond
    -                ),
    -                trajectory_interval=1,
    -            ),
    -            lambda_sterics=absolv.config.DEFAULT_LAMBDA_STERICS_VACUUM,
    -            lambda_electrostatics=absolv.config.DEFAULT_LAMBDA_ELECTROSTATICS_VACUUM,
    -        )
    -    if solvent_protocol is None:
    -        solvent_protocol = absolv.config.EquilibriumProtocol(
    -            production_protocol=absolv.config.HREMDProtocol(
    -                n_steps_per_cycle=500,
    -                n_cycles=1000,
    -                integrator=femto.md.config.LangevinIntegrator(
    -                    timestep=4.0 * openmm.unit.femtosecond
    -                ),
    -                trajectory_interval=1,
    -                trajectory_enforce_pbc=True,
    -            ),
    -            lambda_sterics=absolv.config.DEFAULT_LAMBDA_STERICS_SOLVENT,
    -            lambda_electrostatics=absolv.config.DEFAULT_LAMBDA_ELECTROSTATICS_SOLVENT,
    -        )
    -
    -    config = absolv.config.Config(
    -        temperature=temperature,
    -        pressure=pressure,
    -        alchemical_protocol_a=vacuum_protocol,
    -        alchemical_protocol_b=solvent_protocol,
    -    )
    -
    -    solute_mol = openff.toolkit.Molecule.from_rdkit(
    -        smee.mm._utils.topology_to_rdkit(solute),
    -        allow_undefined_stereo=True,
    -    )
    -    solvent_mol = openff.toolkit.Molecule.from_rdkit(
    -        smee.mm._utils.topology_to_rdkit(solvent),
    -        allow_undefined_stereo=True,
    -    )
    -
    -    system_config = absolv.config.System(
    -        solutes={solute_mol.to_smiles(mapped=True): 1},
    -        solvent_a=None,
    -        solvent_b={solvent_mol.to_smiles(mapped=True): n_solvent},
    -    )
    -
    -    topologies = {
    -        "solvent-a": smee.TensorSystem([solute], [1], is_periodic=False),
    -        "solvent-b": smee.TensorSystem(
    -            [solute, solvent], [1, n_solvent], is_periodic=True
    -        ),
    -    }
    -    pressures = {
    -        "solvent-a": None,
    -        "solvent-b": pressure.value_in_unit(openmm.unit.atmosphere),
    -    }
    -
    -    for phase, topology in topologies.items():
    -        state = {
    -            "system": topology,
    -            "temperature": temperature.value_in_unit(openmm.unit.kelvin),
    -            "pressure": pressures[phase],
    -        }
    -
    -        (output_dir / phase).mkdir(exist_ok=True, parents=True)
    -        (output_dir / phase / "state.pkl").write_bytes(pickle.dumps(state))
    -
    -    def _parameterize(
    -        top, coords, phase: typing.Literal["solvent-a", "solvent-b"]
    -    ) -> openmm.System:
    -        return smee.converters.convert_to_openmm_system(force_field, topologies[phase])
    -
    -    prepared_system_a, prepared_system_b = absolv.runner.setup(
    -        system_config, config, _parameterize
    -    )
    -
    -    femto.md.system.apply_hmr(
    -        prepared_system_a.system,
    -        parmed.openmm.load_topology(prepared_system_a.topology.to_openmm()),
    -    )
    -    femto.md.system.apply_hmr(
    -        prepared_system_b.system,
    -        parmed.openmm.load_topology(prepared_system_a.topology.to_openmm()),
    -    )
    -
    -    result = absolv.runner.run_eq(
    -        config, prepared_system_a, prepared_system_b, "CUDA", output_dir, parallel=True
    -    )
    +    The free energy will correspond to the free energy of transferring a solute from
    +    solvent A to solvent B.
    +
    +    Args:
    +        solute: The solute topology.
    +        solvent_a: The topology of solvent A, or ``None`` if solvent A is vacuum.
    +        solvent_b: The topology of solvent B, or ``None`` if solvent B is vacuum.
    +        force_field: The force field to parameterize the system with.
    +        temperature: The temperature to simulate at.
    +        pressure: The pressure to simulate at.
    +        solvent_a_protocol: The protocol to use to decouple the solute in solvent A.
    +        solvent_b_protocol: The protocol to use to decouple the solute in solvent B.
    +        n_solvent_a: The number of solvent A molecules to use.
    +        n_solvent_b: The number of solvent B molecules to use.
    +        output_dir: The directory to write the output FEP data to.
    +    """
    +    import absolv.config
    +    import absolv.runner
    +    import femto.md.config
    +    import femto.md.system
    +
    +    output_dir = pathlib.Path.cwd() if output_dir is None else output_dir
    +
    +    vacuum_protocol = absolv.config.EquilibriumProtocol(
    +        production_protocol=absolv.config.HREMDProtocol(
    +            n_steps_per_cycle=500,
    +            n_cycles=2000,
    +            integrator=femto.md.config.LangevinIntegrator(
    +                timestep=1.0 * openmm.unit.femtosecond
    +            ),
    +            trajectory_interval=1,
    +        ),
    +        lambda_sterics=absolv.config.DEFAULT_LAMBDA_STERICS_VACUUM,
    +        lambda_electrostatics=absolv.config.DEFAULT_LAMBDA_ELECTROSTATICS_VACUUM,
    +    )
    +    solution_protocol = absolv.config.EquilibriumProtocol(
    +        production_protocol=absolv.config.HREMDProtocol(
    +            n_steps_per_cycle=500,
    +            n_cycles=1000,
    +            integrator=femto.md.config.LangevinIntegrator(
    +                timestep=4.0 * openmm.unit.femtosecond
    +            ),
    +            trajectory_interval=1,
    +            trajectory_enforce_pbc=True,
    +        ),
    +        lambda_sterics=absolv.config.DEFAULT_LAMBDA_STERICS_SOLVENT,
    +        lambda_electrostatics=absolv.config.DEFAULT_LAMBDA_ELECTROSTATICS_SOLVENT,
    +    )
    +
    +    config = absolv.config.Config(
    +        temperature=temperature,
    +        pressure=pressure,
    +        alchemical_protocol_a=solvent_a_protocol
    +        if solvent_a_protocol is not None
    +        else (vacuum_protocol if solvent_a is None else solution_protocol),
    +        alchemical_protocol_b=solvent_b_protocol
    +        if solvent_b_protocol is not None
    +        else (vacuum_protocol if solvent_b is None else solution_protocol),
    +    )
    +
    +    solute_mol = openff.toolkit.Molecule.from_rdkit(
    +        smee.mm._utils.topology_to_rdkit(solute),
    +        allow_undefined_stereo=True,
    +    )
    +
    +    system_config = absolv.config.System(
    +        solutes={solute_mol.to_smiles(mapped=True): 1},
    +        solvent_a=_to_solvent_dict(solvent_a, n_solvent_a),
    +        solvent_b=_to_solvent_dict(solvent_b, n_solvent_b),
    +    )
    +
    +    topologies = {
    +        "solvent-a": smee.TensorSystem([solute], [1], is_periodic=False)
    +        if solvent_a is None
    +        else smee.TensorSystem([solute, solvent_a], [1, n_solvent_a], is_periodic=True),
    +        "solvent-b": smee.TensorSystem([solute], [1], is_periodic=False)
    +        if solvent_b is None
    +        else smee.TensorSystem([solute, solvent_b], [1, n_solvent_b], is_periodic=True),
    +    }
    +    pressures = {
    +        "solvent-a": None
    +        if solvent_a is None
    +        else pressure.value_in_unit(openmm.unit.atmosphere),
    +        "solvent-b": None
    +        if solvent_b is None
    +        else pressure.value_in_unit(openmm.unit.atmosphere),
    +    }
    +
    +    for phase, topology in topologies.items():
    +        state = {
    +            "system": topology,
    +            "temperature": temperature.value_in_unit(openmm.unit.kelvin),
    +            "pressure": pressures[phase],
    +        }
     
    -    solvent_b_output = _extract_pure_solvent(force_field, output_dir / "solvent-b")
    -    torch.save(solvent_b_output, output_dir / "solvent-b" / "pure.pt")
    +        (output_dir / phase).mkdir(exist_ok=True, parents=True)
    +        (output_dir / phase / "state.pkl").write_bytes(pickle.dumps(state))
     
    -    return result
    +    def _parameterize(
    +        top, coords, phase: typing.Literal["solvent-a", "solvent-b"]
    +    ) -> openmm.System:
    +        return smee.converters.convert_to_openmm_system(force_field, topologies[phase])
    +
    +    prepared_system_a, prepared_system_b = absolv.runner.setup(
    +        system_config, config, _parameterize
    +    )
    +
    +    femto.md.system.apply_hmr(
    +        prepared_system_a.system,
    +        parmed.openmm.load_topology(prepared_system_a.topology.to_openmm()),
    +    )
    +    femto.md.system.apply_hmr(
    +        prepared_system_b.system,
    +        parmed.openmm.load_topology(prepared_system_a.topology.to_openmm()),
    +    )
    +
    +    result = absolv.runner.run_eq(
    +        config, prepared_system_a, prepared_system_b, "CUDA", output_dir, parallel=True
    +    )
    +
    +    if solvent_a is not None:
    +        solvent_a_output = _extract_pure_solvent(force_field, output_dir / "solvent-a")
    +        torch.save(solvent_a_output, output_dir / "solvent-a" / "pure.pt")
    +    if solvent_b is not None:
    +        solvent_b_output = _extract_pure_solvent(force_field, output_dir / "solvent-b")
    +        torch.save(solvent_b_output, output_dir / "solvent-b" / "pure.pt")
    +
    +    return result
     
    diff --git a/latest/search/search_index.json b/latest/search/search_index.json index 013a193..8f419cc 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":"

    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).

    Modules:

    • smee \u2013

      Differentiably evaluate energies of molecules using SMIRNOFF force fields

    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

    Modules:

    • smee \u2013

      Differentiably evaluate energies of molecules using SMIRNOFF force fields

    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_ffxml \u2013

      Convert a SMEE force field and system to OpenMM force field XML

    • 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.

    • ffxml_converter \u2013

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

    "},{"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    constraints: (\n        list[TensorConstraints | None] | 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.

    • constraints (list[TensorConstraints | None] | None, default: None ) \u2013

      Any distance constraints between atoms.

    • 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    constraints: list[smee.TensorConstraints | None] | None = 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        constraints: Any distance constraints between atoms.\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    constraints = [None] * len(topologies) if constraints is None else constraints\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    if \"constraints\" in converter_spec.parameters:\n        constraint_idxs = [[] if v is None else v.idxs.tolist() for v in constraints]\n        unique_idxs = [{tuple(sorted(idxs)) for idxs in v} for v in constraint_idxs]\n\n        converter_kwargs[\"constraints\"] = unique_idxs\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(\n            handlers, topologies, v_site_maps, converted, constraints\n        )\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_ffxml","title":"convert_to_openmm_ffxml","text":"
    convert_to_openmm_ffxml(\n    force_field: TensorForceField,\n    system: TensorSystem | TensorTopology,\n) -> list[str]\n

    Convert a SMEE force field and system to OpenMM force field XML representations.

    Parameters:

    • force_field (TensorForceField) \u2013

      The force field to convert.

    • system (TensorSystem | TensorTopology) \u2013

      The system to convert.

    Returns:

    • list[str] \u2013

      One OpenMM force field XML representation per topology in the system.

    Source code in smee/converters/openmm/_ff.py
    def convert_to_openmm_ffxml(\n    force_field: smee.TensorForceField, system: smee.TensorSystem | smee.TensorTopology\n) -> list[str]:\n    \"\"\"Convert a SMEE force field and system to OpenMM force field XML\n    representations.\n\n    Args:\n        force_field: The force field to convert.\n        system: The system to convert.\n\n    Returns:\n        One OpenMM force field XML representation per topology in the system.\n    \"\"\"\n    if isinstance(system, smee.TensorTopology):\n        system = smee.TensorSystem([system], [1], False)\n\n    element_counts: dict[str, int] = collections.defaultdict(int)\n\n    ffxml_contents = []\n\n    for top in system.topologies:\n        top_ffxml = _convert_to_openmm_ffxml(force_field, top, element_counts)\n        ffxml_contents.append(top_ffxml)\n\n    return ffxml_contents\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 name \"X{i}\".

    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 name \"X{i}\".\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 = \"HOH\" 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 i in range(topology.n_v_sites):\n                omm_topology.addAtom(f\"X{i + 1}\", None, residue)\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/#smee.converters.ffxml_converter","title":"ffxml_converter","text":"
    ffxml_converter(\n    potential_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 field XML representation.

    The decorated function should take a smee.TensorPotential, and the associated smee.ParameterMap and list of atom types, and return a xml.etree.ElementTree representing the potential.

    Source code in smee/converters/openmm/_ff.py
    def ffxml_converter(potential_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 field XML representation.\n\n    The decorated function should take a `smee.TensorPotential`, and\n    the associated `smee.ParameterMap` and list of atom types, and return a\n    ``xml.etree.ElementTree`` representing the potential.\n    \"\"\"\n\n    def _openmm_converter_inner(func):\n        if (potential_type, energy_expression) in _CONVERTER_FUNCTIONS:\n            raise KeyError(\n                f\"An OpenMM converter function is already defined for \"\n                f\"handler={potential_type} fn={energy_expression}.\"\n            )\n\n        _CONVERTER_FUNCTIONS[(str(potential_type), str(energy_expression))] = func\n        return func\n\n    return _openmm_converter_inner\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    constraints: (\n        list[TensorConstraints | None] | 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.

    • constraints (list[TensorConstraints | None] | None, default: None ) \u2013

      Any distance constraints between atoms.

    • 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    constraints: list[smee.TensorConstraints | None] | None = 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        constraints: Any distance constraints between atoms.\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    constraints = [None] * len(topologies) if constraints is None else constraints\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    if \"constraints\" in converter_spec.parameters:\n        constraint_idxs = [[] if v is None else v.idxs.tolist() for v in constraints]\n        unique_idxs = [{tuple(sorted(idxs)) for idxs in v} for v in constraint_idxs]\n\n        converter_kwargs[\"constraints\"] = unique_idxs\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(\n            handlers, topologies, v_site_maps, converted, constraints\n        )\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.

    Modules:

    • smee \u2013

      Differentiably evaluate energies of molecules using SMIRNOFF force fields

    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.

    Modules:

    • smee \u2013

      Differentiably evaluate energies of molecules using SMIRNOFF force fields

    Functions:

    • strip_constrained_bonds \u2013

      Remove bonded interactions between distance-constrained atoms.

    • strip_constrained_angles \u2013

      Remove angle interactions between angles where all three atoms are constrained

    • 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.strip_constrained_bonds","title":"strip_constrained_bonds","text":"
    strip_constrained_bonds(\n    parameter_maps: list[ValenceParameterMap],\n    constraints: list[set[tuple[int, int]]],\n)\n

    Remove bonded interactions between distance-constrained atoms.

    Parameters:

    • parameter_maps (list[ValenceParameterMap]) \u2013

      The parameter maps to strip.

    • constraints (list[set[tuple[int, int]]]) \u2013

      The distanced constrained bonds to exclude for each parameter map.

    Source code in smee/converters/openff/valence.py
    def strip_constrained_bonds(\n    parameter_maps: list[smee.ValenceParameterMap],\n    constraints: list[set[tuple[int, int]]],\n):\n    \"\"\"Remove bonded interactions between distance-constrained atoms.\n\n    Args:\n        parameter_maps: The parameter maps to strip.\n        constraints: The distanced constrained bonds to exclude for each parameter map.\n    \"\"\"\n\n    for parameter_map, bonds_to_exclude in zip(\n        parameter_maps, constraints, strict=True\n    ):\n        bonds_to_exclude = {tuple(sorted(idxs)) for idxs in bonds_to_exclude}\n\n        bond_idxs = [\n            tuple(sorted(idxs)) for idxs in parameter_map.particle_idxs.tolist()\n        ]\n        include = [idxs not in bonds_to_exclude for idxs in bond_idxs]\n\n        parameter_map.particle_idxs = parameter_map.particle_idxs[include]\n        parameter_map.assignment_matrix = parameter_map.assignment_matrix.to_dense()[\n            include, :\n        ].to_sparse()\n
    "},{"location":"reference/converters/openff/valence/#smee.converters.openff.valence.strip_constrained_angles","title":"strip_constrained_angles","text":"
    strip_constrained_angles(\n    parameter_maps: list[ValenceParameterMap],\n    constraints: list[set[tuple[int, int]]],\n)\n

    Remove angle interactions between angles where all three atoms are constrained with distance constraints.

    Parameters:

    • parameter_maps (list[ValenceParameterMap]) \u2013

      The parameter maps to strip.

    • constraints (list[set[tuple[int, int]]]) \u2013

      The distanced constrained bonds to exclude for each parameter map.

    Source code in smee/converters/openff/valence.py
    def strip_constrained_angles(\n    parameter_maps: list[smee.ValenceParameterMap],\n    constraints: list[set[tuple[int, int]]],\n):\n    \"\"\"Remove angle interactions between angles where all three atoms are constrained\n    with distance constraints.\n\n    Args:\n        parameter_maps: The parameter maps to strip.\n        constraints: The distanced constrained bonds to exclude for each parameter map.\n    \"\"\"\n\n    def is_constrained(idxs_, excluded):\n        bonds = {\n            tuple(sorted([idxs_[0], idxs_[1]])),\n            tuple(sorted([idxs_[0], idxs_[2]])),\n            tuple(sorted([idxs_[1], idxs_[2]])),\n        }\n        return len(bonds & excluded) == 3\n\n    for parameter_map, bonds_to_exclude in zip(\n        parameter_maps, constraints, strict=True\n    ):\n        bonds_to_exclude = {tuple(sorted(idxs)) for idxs in bonds_to_exclude}\n\n        angle_idxs = parameter_map.particle_idxs.tolist()\n        include = [not is_constrained(idxs, bonds_to_exclude) for idxs in angle_idxs]\n\n        parameter_map.particle_idxs = parameter_map.particle_idxs[include]\n        parameter_map.assignment_matrix = parameter_map.assignment_matrix.to_dense()[\n            include, :\n        ].to_sparse()\n
    "},{"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_ffxml \u2013

      Convert a SMEE force field and system to OpenMM force field XML

    • ffxml_converter \u2013

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

    • 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_ffxml","title":"convert_to_openmm_ffxml","text":"
    convert_to_openmm_ffxml(\n    force_field: TensorForceField,\n    system: TensorSystem | TensorTopology,\n) -> list[str]\n

    Convert a SMEE force field and system to OpenMM force field XML representations.

    Parameters:

    • force_field (TensorForceField) \u2013

      The force field to convert.

    • system (TensorSystem | TensorTopology) \u2013

      The system to convert.

    Returns:

    • list[str] \u2013

      One OpenMM force field XML representation per topology in the system.

    Source code in smee/converters/openmm/_ff.py
    def convert_to_openmm_ffxml(\n    force_field: smee.TensorForceField, system: smee.TensorSystem | smee.TensorTopology\n) -> list[str]:\n    \"\"\"Convert a SMEE force field and system to OpenMM force field XML\n    representations.\n\n    Args:\n        force_field: The force field to convert.\n        system: The system to convert.\n\n    Returns:\n        One OpenMM force field XML representation per topology in the system.\n    \"\"\"\n    if isinstance(system, smee.TensorTopology):\n        system = smee.TensorSystem([system], [1], False)\n\n    element_counts: dict[str, int] = collections.defaultdict(int)\n\n    ffxml_contents = []\n\n    for top in system.topologies:\n        top_ffxml = _convert_to_openmm_ffxml(force_field, top, element_counts)\n        ffxml_contents.append(top_ffxml)\n\n    return ffxml_contents\n
    "},{"location":"reference/converters/openmm/#smee.converters.openmm.ffxml_converter","title":"ffxml_converter","text":"
    ffxml_converter(\n    potential_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 field XML representation.

    The decorated function should take a smee.TensorPotential, and the associated smee.ParameterMap and list of atom types, and return a xml.etree.ElementTree representing the potential.

    Source code in smee/converters/openmm/_ff.py
    def ffxml_converter(potential_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 field XML representation.\n\n    The decorated function should take a `smee.TensorPotential`, and\n    the associated `smee.ParameterMap` and list of atom types, and return a\n    ``xml.etree.ElementTree`` representing the potential.\n    \"\"\"\n\n    def _openmm_converter_inner(func):\n        if (potential_type, energy_expression) in _CONVERTER_FUNCTIONS:\n            raise KeyError(\n                f\"An OpenMM converter function is already defined for \"\n                f\"handler={potential_type} fn={energy_expression}.\"\n            )\n\n        _CONVERTER_FUNCTIONS[(str(potential_type), str(energy_expression))] = func\n        return func\n\n    return _openmm_converter_inner\n
    "},{"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 name \"X{i}\".

    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 name \"X{i}\".\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 = \"HOH\" 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 i in range(topology.n_v_sites):\n                omm_topology.addAtom(f\"X{i + 1}\", None, residue)\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.

    Modules:

    • smee \u2013

      Differentiably evaluate energies of molecules using SMIRNOFF force fields

    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.

    Modules:

    • smee \u2013

      Differentiably evaluate energies of molecules using SMIRNOFF force fields

    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.

    • SimulationConfig \u2013
    • 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_dg_solv_data \u2013

      Run a solvation free energy calculation using absolv, and saves the output

    • generate_system_coords \u2013

      Generate coordinates for a system of molecules using PACKMOL.

    • simulate \u2013

      Simulate a SMEE system of molecules or topology.

    • compute_dg_solv \u2013

      Computes \u2206G_solv from existing FEP data.

    • compute_ensemble_averages \u2013

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

    • reweight_dg_solv \u2013

      Computes \u2206G_solv by re-weighting existing FEP data.

    • 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.

    Fields:

    • target_density (OpenMMQuantity[_GRAMS_PER_ML])
    • scale_factor (float)
    • padding (OpenMMQuantity[angstrom])
    • tolerance (OpenMMQuantity[angstrom])
    • seed (int | None)
    "},{"location":"reference/mm/#smee.mm.GenerateCoordsConfig.target_density","title":"target_density pydantic-field","text":"
    target_density: OpenMMQuantity[_GRAMS_PER_ML] = (\n    0.95 * _GRAMS_PER_ML\n)\n

    Target mass density for final system with units compatible with g / mL.

    "},{"location":"reference/mm/#smee.mm.GenerateCoordsConfig.scale_factor","title":"scale_factor pydantic-field","text":"
    scale_factor: float = 1.1\n

    The amount to scale the approximate box size by to help alleviate issues with packing larger molecules.

    "},{"location":"reference/mm/#smee.mm.GenerateCoordsConfig.padding","title":"padding pydantic-field","text":"
    padding: OpenMMQuantity[angstrom] = 2.0 * angstrom\n

    The amount of padding to add to the final box size to help alleviate PBC issues.

    "},{"location":"reference/mm/#smee.mm.GenerateCoordsConfig.tolerance","title":"tolerance pydantic-field","text":"
    tolerance: OpenMMQuantity[angstrom] = 2.0 * angstrom\n

    The minimum spacing between molecules during packing.

    "},{"location":"reference/mm/#smee.mm.GenerateCoordsConfig.seed","title":"seed pydantic-field","text":"
    seed: int | None = None\n

    The random seed to use when generating the coordinates.

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

    Bases: BaseModel

    Configure how a system should be energy minimized.

    Fields:

    • tolerance (OpenMMQuantity[_KCAL_PER_MOL / _ANGSTROM])
    • max_iterations (int)
    "},{"location":"reference/mm/#smee.mm.MinimizationConfig.tolerance","title":"tolerance pydantic-field","text":"
    tolerance: OpenMMQuantity[_KCAL_PER_MOL / _ANGSTROM] = (\n    10.0 * _KCAL_PER_MOL / _ANGSTROM\n)\n

    Minimization will be halted once the root-mean-square value of all force components reaches this tolerance.

    "},{"location":"reference/mm/#smee.mm.MinimizationConfig.max_iterations","title":"max_iterations pydantic-field","text":"
    max_iterations: int = 0\n

    The maximum number of iterations to perform. If 0, minimization will continue until the tolerance is met.

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

    Bases: BaseModel

    Fields:

    • temperature (OpenMMQuantity[kelvin])
    • pressure (OpenMMQuantity[atmospheres] | None)
    • n_steps (int)
    • timestep (OpenMMQuantity[femtoseconds])
    • friction_coeff (OpenMMQuantity[1.0 / picoseconds])
    "},{"location":"reference/mm/#smee.mm.SimulationConfig.temperature","title":"temperature pydantic-field","text":"
    temperature: OpenMMQuantity[kelvin]\n

    The temperature to simulate at.

    "},{"location":"reference/mm/#smee.mm.SimulationConfig.pressure","title":"pressure pydantic-field","text":"
    pressure: OpenMMQuantity[atmospheres] | None\n

    The pressure to simulate at, or none to run in NVT.

    "},{"location":"reference/mm/#smee.mm.SimulationConfig.n_steps","title":"n_steps pydantic-field","text":"
    n_steps: int\n

    The number of steps to simulate for.

    "},{"location":"reference/mm/#smee.mm.SimulationConfig.timestep","title":"timestep pydantic-field","text":"
    timestep: OpenMMQuantity[femtoseconds] = 2.0 * femtoseconds\n

    The timestep to use during the simulation.

    "},{"location":"reference/mm/#smee.mm.SimulationConfig.friction_coeff","title":"friction_coeff pydantic-field","text":"
    friction_coeff: OpenMMQuantity[1.0 / picoseconds] = (\n    1.0 / picoseconds\n)\n

    The integrator friction coefficient.

    "},{"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_dg_solv_data","title":"generate_dg_solv_data","text":"
    generate_dg_solv_data(\n    solute: TensorTopology,\n    solvent: TensorTopology,\n    force_field: TensorForceField,\n    temperature: Quantity = 298.15 * kelvin,\n    pressure: Quantity = 1.0 * atmosphere,\n    vacuum_protocol: Optional[EquilibriumProtocol] = None,\n    solvent_protocol: Optional[EquilibriumProtocol] = None,\n    n_solvent: int = 216,\n    output_dir: Path | None = None,\n)\n

    Run a solvation free energy calculation using absolv, and saves the output such that a differentiable free energy can be computed.

    Parameters:

    • solute (TensorTopology) \u2013

      The solute topology.

    • solvent (TensorTopology) \u2013

      The solvent topology.

    • force_field (TensorForceField) \u2013

      The force field to parameterize the system with.

    • temperature (Quantity, default: 298.15 * kelvin ) \u2013

      The temperature to simulate at.

    • pressure (Quantity, default: 1.0 * atmosphere ) \u2013

      The pressure to simulate at.

    • vacuum_protocol (Optional[EquilibriumProtocol], default: None ) \u2013

      The protocol to use for the vacuum phase.

    • solvent_protocol (Optional[EquilibriumProtocol], default: None ) \u2013

      The protocol to use for the solvent phase.

    • n_solvent (int, default: 216 ) \u2013

      The number of solvent molecules to use.

    • output_dir (Path | None, default: None ) \u2013

      The directory to write the output FEP data to.

    Source code in smee/mm/_fe.py
    def generate_dg_solv_data(\n    solute: smee.TensorTopology,\n    solvent: smee.TensorTopology,\n    force_field: smee.TensorForceField,\n    temperature: openmm.unit.Quantity = 298.15 * openmm.unit.kelvin,\n    pressure: openmm.unit.Quantity = 1.0 * openmm.unit.atmosphere,\n    vacuum_protocol: typing.Optional[\"absolv.config.EquilibriumProtocol\"] = None,\n    solvent_protocol: typing.Optional[\"absolv.config.EquilibriumProtocol\"] = None,\n    n_solvent: int = 216,\n    output_dir: pathlib.Path | None = None,\n):\n    \"\"\"Run a solvation free energy calculation using ``absolv``, and saves the output\n    such that a differentiable free energy can be computed.\n\n    Args:\n        solute: The solute topology.\n        solvent: The solvent topology.\n        force_field: The force field to parameterize the system with.\n        temperature: The temperature to simulate at.\n        pressure: The pressure to simulate at.\n        vacuum_protocol: The protocol to use for the vacuum phase.\n        solvent_protocol: The protocol to use for the solvent phase.\n        n_solvent: The number of solvent molecules to use.\n        output_dir: The directory to write the output FEP data to.\n    \"\"\"\n    import absolv.config\n    import absolv.runner\n    import femto.md.config\n    import femto.md.system\n\n    output_dir = pathlib.Path.cwd() if output_dir is None else output_dir\n\n    if vacuum_protocol is None:\n        vacuum_protocol = absolv.config.EquilibriumProtocol(\n            production_protocol=absolv.config.HREMDProtocol(\n                n_steps_per_cycle=500,\n                n_cycles=2000,\n                integrator=femto.md.config.LangevinIntegrator(\n                    timestep=1.0 * openmm.unit.femtosecond\n                ),\n                trajectory_interval=1,\n            ),\n            lambda_sterics=absolv.config.DEFAULT_LAMBDA_STERICS_VACUUM,\n            lambda_electrostatics=absolv.config.DEFAULT_LAMBDA_ELECTROSTATICS_VACUUM,\n        )\n    if solvent_protocol is None:\n        solvent_protocol = absolv.config.EquilibriumProtocol(\n            production_protocol=absolv.config.HREMDProtocol(\n                n_steps_per_cycle=500,\n                n_cycles=1000,\n                integrator=femto.md.config.LangevinIntegrator(\n                    timestep=4.0 * openmm.unit.femtosecond\n                ),\n                trajectory_interval=1,\n                trajectory_enforce_pbc=True,\n            ),\n            lambda_sterics=absolv.config.DEFAULT_LAMBDA_STERICS_SOLVENT,\n            lambda_electrostatics=absolv.config.DEFAULT_LAMBDA_ELECTROSTATICS_SOLVENT,\n        )\n\n    config = absolv.config.Config(\n        temperature=temperature,\n        pressure=pressure,\n        alchemical_protocol_a=vacuum_protocol,\n        alchemical_protocol_b=solvent_protocol,\n    )\n\n    solute_mol = openff.toolkit.Molecule.from_rdkit(\n        smee.mm._utils.topology_to_rdkit(solute),\n        allow_undefined_stereo=True,\n    )\n    solvent_mol = openff.toolkit.Molecule.from_rdkit(\n        smee.mm._utils.topology_to_rdkit(solvent),\n        allow_undefined_stereo=True,\n    )\n\n    system_config = absolv.config.System(\n        solutes={solute_mol.to_smiles(mapped=True): 1},\n        solvent_a=None,\n        solvent_b={solvent_mol.to_smiles(mapped=True): n_solvent},\n    )\n\n    topologies = {\n        \"solvent-a\": smee.TensorSystem([solute], [1], is_periodic=False),\n        \"solvent-b\": smee.TensorSystem(\n            [solute, solvent], [1, n_solvent], is_periodic=True\n        ),\n    }\n    pressures = {\n        \"solvent-a\": None,\n        \"solvent-b\": pressure.value_in_unit(openmm.unit.atmosphere),\n    }\n\n    for phase, topology in topologies.items():\n        state = {\n            \"system\": topology,\n            \"temperature\": temperature.value_in_unit(openmm.unit.kelvin),\n            \"pressure\": pressures[phase],\n        }\n\n        (output_dir / phase).mkdir(exist_ok=True, parents=True)\n        (output_dir / phase / \"state.pkl\").write_bytes(pickle.dumps(state))\n\n    def _parameterize(\n        top, coords, phase: typing.Literal[\"solvent-a\", \"solvent-b\"]\n    ) -> openmm.System:\n        return smee.converters.convert_to_openmm_system(force_field, topologies[phase])\n\n    prepared_system_a, prepared_system_b = absolv.runner.setup(\n        system_config, config, _parameterize\n    )\n\n    femto.md.system.apply_hmr(\n        prepared_system_a.system,\n        parmed.openmm.load_topology(prepared_system_a.topology.to_openmm()),\n    )\n    femto.md.system.apply_hmr(\n        prepared_system_b.system,\n        parmed.openmm.load_topology(prepared_system_a.topology.to_openmm()),\n    )\n\n    result = absolv.runner.run_eq(\n        config, prepared_system_a, prepared_system_b, \"CUDA\", output_dir, parallel=True\n    )\n\n    solvent_b_output = _extract_pure_solvent(force_field, output_dir / \"solvent-b\")\n    torch.save(solvent_b_output, output_dir / \"solvent-b\" / \"pure.pt\")\n\n    return result\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_dg_solv","title":"compute_dg_solv","text":"
    compute_dg_solv(\n    force_field: TensorForceField, fep_dir: Path\n) -> Tensor\n

    Computes \u2206G_solv from existing FEP data.

    Notes

    It is assumed that FEP data was generated using the same force field as force_field, and using generate_dg_solv_data

    Parameters:

    • force_field (TensorForceField) \u2013

      The force field used to generate the FEP data.

    • fep_dir (Path) \u2013

      The directory containing the FEP data.

    Returns:

    • Tensor \u2013

      \u2206G_solv [kcal/mol].

    Source code in smee/mm/_ops.py
    def compute_dg_solv(\n    force_field: smee.TensorForceField, fep_dir: pathlib.Path\n) -> torch.Tensor:\n    \"\"\"Computes \u2206G_solv from existing FEP data.\n\n    Notes:\n        It is assumed that FEP data was generated using the same force field as\n        ``force_field``, and using ``generate_dg_solv_data``\n\n    Args:\n        force_field: The force field used to generate the FEP data.\n        fep_dir: The directory containing the FEP data.\n\n    Returns:\n        \u2206G_solv [kcal/mol].\n    \"\"\"\n\n    tensors, parameter_lookup, attribute_lookup, has_v_sites = _pack_force_field(\n        force_field\n    )\n\n    kwargs = {\n        \"force_field\": force_field,\n        \"parameter_lookup\": parameter_lookup,\n        \"attribute_lookup\": attribute_lookup,\n        \"has_v_sites\": has_v_sites,\n        \"fep_dir\": fep_dir,\n    }\n    return _ComputeDGSolv.apply(kwargs, *tensors)\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_dg_solv","title":"reweight_dg_solv","text":"
    reweight_dg_solv(\n    force_field: TensorForceField,\n    fep_dir: Path,\n    dg_0: Tensor,\n    min_samples: int = 50,\n) -> tuple[Tensor, float]\n

    Computes \u2206G_solv by re-weighting existing FEP data.

    Notes

    It is assumed that FEP data was generated using generate_dg_solv_data.

    Parameters:

    • force_field (TensorForceField) \u2013

      The force field to reweight to.

    • fep_dir (Path) \u2013

      The directory containing the FEP data.

    • dg_0 (Tensor) \u2013

      \u2206G_solv [kcal/mol] computed with the force field used to generate the FEP data.

    • min_samples (int, default: 50 ) \u2013

      The minimum number of effective samples required to re-weight.

    Raises:

    • NotEnoughSamplesError \u2013

      If the number of effective samples is less than min_samples.

    Returns:

    • tuple[Tensor, float] \u2013

      The re-weighted \u2206G_solv [kcal/mol], and the minimum number of effective samples between the two phases.

    Source code in smee/mm/_ops.py
    def reweight_dg_solv(\n    force_field: smee.TensorForceField,\n    fep_dir: pathlib.Path,\n    dg_0: torch.Tensor,\n    min_samples: int = 50,\n) -> tuple[torch.Tensor, float]:\n    \"\"\"Computes \u2206G_solv by re-weighting existing FEP data.\n\n    Notes:\n        It is assumed that FEP data was generated using ``generate_dg_solv_data``.\n\n    Args:\n        force_field: The force field to reweight to.\n        fep_dir: The directory containing the FEP data.\n        dg_0: \u2206G_solv [kcal/mol] computed with the force field used to generate the\n            FEP data.\n        min_samples: The minimum number of effective samples required to re-weight.\n\n    Raises:\n        NotEnoughSamplesError: If the number of effective samples is less than\n            ``min_samples``.\n\n    Returns:\n        The re-weighted \u2206G_solv [kcal/mol], and the minimum number of effective samples\n        between the two phases.\n    \"\"\"\n    tensors, parameter_lookup, attribute_lookup, has_v_sites = _pack_force_field(\n        force_field\n    )\n\n    kwargs = {\n        \"force_field\": force_field,\n        \"parameter_lookup\": parameter_lookup,\n        \"attribute_lookup\": attribute_lookup,\n        \"has_v_sites\": has_v_sites,\n        \"fep_dir\": fep_dir,\n        \"dg_0\": dg_0,\n    }\n\n    dg, n_eff = _ReweightDGSolv.apply(kwargs, *tensors)\n\n    if n_eff < min_samples:\n        raise NotEnoughSamplesError\n\n    return dg, n_eff\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.

    Modules:

    • smee \u2013

      Differentiably evaluate energies of molecules using SMIRNOFF force fields

    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.

    Modules:

    • smee \u2013

      Differentiably evaluate energies of molecules using SMIRNOFF force fields

    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":"

    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).

    Modules:

    • smee \u2013

      Differentiably evaluate energies of molecules using SMIRNOFF force fields

    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

    Modules:

    • smee \u2013

      Differentiably evaluate energies of molecules using SMIRNOFF force fields

    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_ffxml \u2013

      Convert a SMEE force field and system to OpenMM force field XML

    • 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.

    • ffxml_converter \u2013

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

    "},{"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    constraints: (\n        list[TensorConstraints | None] | 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.

    • constraints (list[TensorConstraints | None] | None, default: None ) \u2013

      Any distance constraints between atoms.

    • 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    constraints: list[smee.TensorConstraints | None] | None = 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        constraints: Any distance constraints between atoms.\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    constraints = [None] * len(topologies) if constraints is None else constraints\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    if \"constraints\" in converter_spec.parameters:\n        constraint_idxs = [[] if v is None else v.idxs.tolist() for v in constraints]\n        unique_idxs = [{tuple(sorted(idxs)) for idxs in v} for v in constraint_idxs]\n\n        converter_kwargs[\"constraints\"] = unique_idxs\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(\n            handlers, topologies, v_site_maps, converted, constraints\n        )\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_ffxml","title":"convert_to_openmm_ffxml","text":"
    convert_to_openmm_ffxml(\n    force_field: TensorForceField,\n    system: TensorSystem | TensorTopology,\n) -> list[str]\n

    Convert a SMEE force field and system to OpenMM force field XML representations.

    Parameters:

    • force_field (TensorForceField) \u2013

      The force field to convert.

    • system (TensorSystem | TensorTopology) \u2013

      The system to convert.

    Returns:

    • list[str] \u2013

      One OpenMM force field XML representation per topology in the system.

    Source code in smee/converters/openmm/_ff.py
    def convert_to_openmm_ffxml(\n    force_field: smee.TensorForceField, system: smee.TensorSystem | smee.TensorTopology\n) -> list[str]:\n    \"\"\"Convert a SMEE force field and system to OpenMM force field XML\n    representations.\n\n    Args:\n        force_field: The force field to convert.\n        system: The system to convert.\n\n    Returns:\n        One OpenMM force field XML representation per topology in the system.\n    \"\"\"\n    if isinstance(system, smee.TensorTopology):\n        system = smee.TensorSystem([system], [1], False)\n\n    element_counts: dict[str, int] = collections.defaultdict(int)\n\n    ffxml_contents = []\n\n    for top in system.topologies:\n        top_ffxml = _convert_to_openmm_ffxml(force_field, top, element_counts)\n        ffxml_contents.append(top_ffxml)\n\n    return ffxml_contents\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 name \"X{i}\".

    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 name \"X{i}\".\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 = \"HOH\" 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 i in range(topology.n_v_sites):\n                omm_topology.addAtom(f\"X{i + 1}\", None, residue)\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/#smee.converters.ffxml_converter","title":"ffxml_converter","text":"
    ffxml_converter(\n    potential_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 field XML representation.

    The decorated function should take a smee.TensorPotential, and the associated smee.ParameterMap and list of atom types, and return a xml.etree.ElementTree representing the potential.

    Source code in smee/converters/openmm/_ff.py
    def ffxml_converter(potential_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 field XML representation.\n\n    The decorated function should take a `smee.TensorPotential`, and\n    the associated `smee.ParameterMap` and list of atom types, and return a\n    ``xml.etree.ElementTree`` representing the potential.\n    \"\"\"\n\n    def _openmm_converter_inner(func):\n        if (potential_type, energy_expression) in _CONVERTER_FUNCTIONS:\n            raise KeyError(\n                f\"An OpenMM converter function is already defined for \"\n                f\"handler={potential_type} fn={energy_expression}.\"\n            )\n\n        _CONVERTER_FUNCTIONS[(str(potential_type), str(energy_expression))] = func\n        return func\n\n    return _openmm_converter_inner\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    constraints: (\n        list[TensorConstraints | None] | 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.

    • constraints (list[TensorConstraints | None] | None, default: None ) \u2013

      Any distance constraints between atoms.

    • 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    constraints: list[smee.TensorConstraints | None] | None = 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        constraints: Any distance constraints between atoms.\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    constraints = [None] * len(topologies) if constraints is None else constraints\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    if \"constraints\" in converter_spec.parameters:\n        constraint_idxs = [[] if v is None else v.idxs.tolist() for v in constraints]\n        unique_idxs = [{tuple(sorted(idxs)) for idxs in v} for v in constraint_idxs]\n\n        converter_kwargs[\"constraints\"] = unique_idxs\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(\n            handlers, topologies, v_site_maps, converted, constraints\n        )\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.

    Modules:

    • smee \u2013

      Differentiably evaluate energies of molecules using SMIRNOFF force fields

    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.

    Modules:

    • smee \u2013

      Differentiably evaluate energies of molecules using SMIRNOFF force fields

    Functions:

    • strip_constrained_bonds \u2013

      Remove bonded interactions between distance-constrained atoms.

    • strip_constrained_angles \u2013

      Remove angle interactions between angles where all three atoms are constrained

    • 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.strip_constrained_bonds","title":"strip_constrained_bonds","text":"
    strip_constrained_bonds(\n    parameter_maps: list[ValenceParameterMap],\n    constraints: list[set[tuple[int, int]]],\n)\n

    Remove bonded interactions between distance-constrained atoms.

    Parameters:

    • parameter_maps (list[ValenceParameterMap]) \u2013

      The parameter maps to strip.

    • constraints (list[set[tuple[int, int]]]) \u2013

      The distanced constrained bonds to exclude for each parameter map.

    Source code in smee/converters/openff/valence.py
    def strip_constrained_bonds(\n    parameter_maps: list[smee.ValenceParameterMap],\n    constraints: list[set[tuple[int, int]]],\n):\n    \"\"\"Remove bonded interactions between distance-constrained atoms.\n\n    Args:\n        parameter_maps: The parameter maps to strip.\n        constraints: The distanced constrained bonds to exclude for each parameter map.\n    \"\"\"\n\n    for parameter_map, bonds_to_exclude in zip(\n        parameter_maps, constraints, strict=True\n    ):\n        bonds_to_exclude = {tuple(sorted(idxs)) for idxs in bonds_to_exclude}\n\n        bond_idxs = [\n            tuple(sorted(idxs)) for idxs in parameter_map.particle_idxs.tolist()\n        ]\n        include = [idxs not in bonds_to_exclude for idxs in bond_idxs]\n\n        parameter_map.particle_idxs = parameter_map.particle_idxs[include]\n        parameter_map.assignment_matrix = parameter_map.assignment_matrix.to_dense()[\n            include, :\n        ].to_sparse()\n
    "},{"location":"reference/converters/openff/valence/#smee.converters.openff.valence.strip_constrained_angles","title":"strip_constrained_angles","text":"
    strip_constrained_angles(\n    parameter_maps: list[ValenceParameterMap],\n    constraints: list[set[tuple[int, int]]],\n)\n

    Remove angle interactions between angles where all three atoms are constrained with distance constraints.

    Parameters:

    • parameter_maps (list[ValenceParameterMap]) \u2013

      The parameter maps to strip.

    • constraints (list[set[tuple[int, int]]]) \u2013

      The distanced constrained bonds to exclude for each parameter map.

    Source code in smee/converters/openff/valence.py
    def strip_constrained_angles(\n    parameter_maps: list[smee.ValenceParameterMap],\n    constraints: list[set[tuple[int, int]]],\n):\n    \"\"\"Remove angle interactions between angles where all three atoms are constrained\n    with distance constraints.\n\n    Args:\n        parameter_maps: The parameter maps to strip.\n        constraints: The distanced constrained bonds to exclude for each parameter map.\n    \"\"\"\n\n    def is_constrained(idxs_, excluded):\n        bonds = {\n            tuple(sorted([idxs_[0], idxs_[1]])),\n            tuple(sorted([idxs_[0], idxs_[2]])),\n            tuple(sorted([idxs_[1], idxs_[2]])),\n        }\n        return len(bonds & excluded) == 3\n\n    for parameter_map, bonds_to_exclude in zip(\n        parameter_maps, constraints, strict=True\n    ):\n        bonds_to_exclude = {tuple(sorted(idxs)) for idxs in bonds_to_exclude}\n\n        angle_idxs = parameter_map.particle_idxs.tolist()\n        include = [not is_constrained(idxs, bonds_to_exclude) for idxs in angle_idxs]\n\n        parameter_map.particle_idxs = parameter_map.particle_idxs[include]\n        parameter_map.assignment_matrix = parameter_map.assignment_matrix.to_dense()[\n            include, :\n        ].to_sparse()\n
    "},{"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_ffxml \u2013

      Convert a SMEE force field and system to OpenMM force field XML

    • ffxml_converter \u2013

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

    • 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_ffxml","title":"convert_to_openmm_ffxml","text":"
    convert_to_openmm_ffxml(\n    force_field: TensorForceField,\n    system: TensorSystem | TensorTopology,\n) -> list[str]\n

    Convert a SMEE force field and system to OpenMM force field XML representations.

    Parameters:

    • force_field (TensorForceField) \u2013

      The force field to convert.

    • system (TensorSystem | TensorTopology) \u2013

      The system to convert.

    Returns:

    • list[str] \u2013

      One OpenMM force field XML representation per topology in the system.

    Source code in smee/converters/openmm/_ff.py
    def convert_to_openmm_ffxml(\n    force_field: smee.TensorForceField, system: smee.TensorSystem | smee.TensorTopology\n) -> list[str]:\n    \"\"\"Convert a SMEE force field and system to OpenMM force field XML\n    representations.\n\n    Args:\n        force_field: The force field to convert.\n        system: The system to convert.\n\n    Returns:\n        One OpenMM force field XML representation per topology in the system.\n    \"\"\"\n    if isinstance(system, smee.TensorTopology):\n        system = smee.TensorSystem([system], [1], False)\n\n    element_counts: dict[str, int] = collections.defaultdict(int)\n\n    ffxml_contents = []\n\n    for top in system.topologies:\n        top_ffxml = _convert_to_openmm_ffxml(force_field, top, element_counts)\n        ffxml_contents.append(top_ffxml)\n\n    return ffxml_contents\n
    "},{"location":"reference/converters/openmm/#smee.converters.openmm.ffxml_converter","title":"ffxml_converter","text":"
    ffxml_converter(\n    potential_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 field XML representation.

    The decorated function should take a smee.TensorPotential, and the associated smee.ParameterMap and list of atom types, and return a xml.etree.ElementTree representing the potential.

    Source code in smee/converters/openmm/_ff.py
    def ffxml_converter(potential_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 field XML representation.\n\n    The decorated function should take a `smee.TensorPotential`, and\n    the associated `smee.ParameterMap` and list of atom types, and return a\n    ``xml.etree.ElementTree`` representing the potential.\n    \"\"\"\n\n    def _openmm_converter_inner(func):\n        if (potential_type, energy_expression) in _CONVERTER_FUNCTIONS:\n            raise KeyError(\n                f\"An OpenMM converter function is already defined for \"\n                f\"handler={potential_type} fn={energy_expression}.\"\n            )\n\n        _CONVERTER_FUNCTIONS[(str(potential_type), str(energy_expression))] = func\n        return func\n\n    return _openmm_converter_inner\n
    "},{"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 name \"X{i}\".

    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 name \"X{i}\".\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 = \"HOH\" 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 i in range(topology.n_v_sites):\n                omm_topology.addAtom(f\"X{i + 1}\", None, residue)\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.

    Modules:

    • smee \u2013

      Differentiably evaluate energies of molecules using SMIRNOFF force fields

    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.

    Modules:

    • smee \u2013

      Differentiably evaluate energies of molecules using SMIRNOFF force fields

    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.

    • SimulationConfig \u2013
    • 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_dg_solv_data \u2013

      Run a solvation free energy calculation using absolv, and saves the output

    • generate_system_coords \u2013

      Generate coordinates for a system of molecules using PACKMOL.

    • simulate \u2013

      Simulate a SMEE system of molecules or topology.

    • compute_dg_solv \u2013

      Computes \u2206G_solv from existing FEP data.

    • compute_ensemble_averages \u2013

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

    • reweight_dg_solv \u2013

      Computes \u2206G_solv by re-weighting existing FEP data.

    • 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.

    Fields:

    • target_density (OpenMMQuantity[_GRAMS_PER_ML])
    • scale_factor (float)
    • padding (OpenMMQuantity[angstrom])
    • tolerance (OpenMMQuantity[angstrom])
    • seed (int | None)
    "},{"location":"reference/mm/#smee.mm.GenerateCoordsConfig.target_density","title":"target_density pydantic-field","text":"
    target_density: OpenMMQuantity[_GRAMS_PER_ML] = (\n    0.95 * _GRAMS_PER_ML\n)\n

    Target mass density for final system with units compatible with g / mL.

    "},{"location":"reference/mm/#smee.mm.GenerateCoordsConfig.scale_factor","title":"scale_factor pydantic-field","text":"
    scale_factor: float = 1.1\n

    The amount to scale the approximate box size by to help alleviate issues with packing larger molecules.

    "},{"location":"reference/mm/#smee.mm.GenerateCoordsConfig.padding","title":"padding pydantic-field","text":"
    padding: OpenMMQuantity[angstrom] = 2.0 * angstrom\n

    The amount of padding to add to the final box size to help alleviate PBC issues.

    "},{"location":"reference/mm/#smee.mm.GenerateCoordsConfig.tolerance","title":"tolerance pydantic-field","text":"
    tolerance: OpenMMQuantity[angstrom] = 2.0 * angstrom\n

    The minimum spacing between molecules during packing.

    "},{"location":"reference/mm/#smee.mm.GenerateCoordsConfig.seed","title":"seed pydantic-field","text":"
    seed: int | None = None\n

    The random seed to use when generating the coordinates.

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

    Bases: BaseModel

    Configure how a system should be energy minimized.

    Fields:

    • tolerance (OpenMMQuantity[_KCAL_PER_MOL / _ANGSTROM])
    • max_iterations (int)
    "},{"location":"reference/mm/#smee.mm.MinimizationConfig.tolerance","title":"tolerance pydantic-field","text":"
    tolerance: OpenMMQuantity[_KCAL_PER_MOL / _ANGSTROM] = (\n    10.0 * _KCAL_PER_MOL / _ANGSTROM\n)\n

    Minimization will be halted once the root-mean-square value of all force components reaches this tolerance.

    "},{"location":"reference/mm/#smee.mm.MinimizationConfig.max_iterations","title":"max_iterations pydantic-field","text":"
    max_iterations: int = 0\n

    The maximum number of iterations to perform. If 0, minimization will continue until the tolerance is met.

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

    Bases: BaseModel

    Fields:

    • temperature (OpenMMQuantity[kelvin])
    • pressure (OpenMMQuantity[atmospheres] | None)
    • n_steps (int)
    • timestep (OpenMMQuantity[femtoseconds])
    • friction_coeff (OpenMMQuantity[1.0 / picoseconds])
    "},{"location":"reference/mm/#smee.mm.SimulationConfig.temperature","title":"temperature pydantic-field","text":"
    temperature: OpenMMQuantity[kelvin]\n

    The temperature to simulate at.

    "},{"location":"reference/mm/#smee.mm.SimulationConfig.pressure","title":"pressure pydantic-field","text":"
    pressure: OpenMMQuantity[atmospheres] | None\n

    The pressure to simulate at, or none to run in NVT.

    "},{"location":"reference/mm/#smee.mm.SimulationConfig.n_steps","title":"n_steps pydantic-field","text":"
    n_steps: int\n

    The number of steps to simulate for.

    "},{"location":"reference/mm/#smee.mm.SimulationConfig.timestep","title":"timestep pydantic-field","text":"
    timestep: OpenMMQuantity[femtoseconds] = 2.0 * femtoseconds\n

    The timestep to use during the simulation.

    "},{"location":"reference/mm/#smee.mm.SimulationConfig.friction_coeff","title":"friction_coeff pydantic-field","text":"
    friction_coeff: OpenMMQuantity[1.0 / picoseconds] = (\n    1.0 / picoseconds\n)\n

    The integrator friction coefficient.

    "},{"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_dg_solv_data","title":"generate_dg_solv_data","text":"
    generate_dg_solv_data(\n    solute: TensorTopology,\n    solvent_a: TensorTopology | None,\n    solvent_b: TensorTopology | None,\n    force_field: TensorForceField,\n    temperature: Quantity = 298.15 * kelvin,\n    pressure: Quantity = 1.0 * atmosphere,\n    solvent_a_protocol: Optional[\n        EquilibriumProtocol\n    ] = None,\n    solvent_b_protocol: Optional[\n        EquilibriumProtocol\n    ] = None,\n    n_solvent_a: int = 216,\n    n_solvent_b: int = 216,\n    output_dir: Path | None = None,\n)\n

    Run a solvation free energy calculation using absolv, and saves the output such that a differentiable free energy can be computed.

    The free energy will correspond to the free energy of transferring a solute from solvent A to solvent B.

    Parameters:

    • solute (TensorTopology) \u2013

      The solute topology.

    • solvent_a (TensorTopology | None) \u2013

      The topology of solvent A, or None if solvent A is vacuum.

    • solvent_b (TensorTopology | None) \u2013

      The topology of solvent B, or None if solvent B is vacuum.

    • force_field (TensorForceField) \u2013

      The force field to parameterize the system with.

    • temperature (Quantity, default: 298.15 * kelvin ) \u2013

      The temperature to simulate at.

    • pressure (Quantity, default: 1.0 * atmosphere ) \u2013

      The pressure to simulate at.

    • solvent_a_protocol (Optional[EquilibriumProtocol], default: None ) \u2013

      The protocol to use to decouple the solute in solvent A.

    • solvent_b_protocol (Optional[EquilibriumProtocol], default: None ) \u2013

      The protocol to use to decouple the solute in solvent B.

    • n_solvent_a (int, default: 216 ) \u2013

      The number of solvent A molecules to use.

    • n_solvent_b (int, default: 216 ) \u2013

      The number of solvent B molecules to use.

    • output_dir (Path | None, default: None ) \u2013

      The directory to write the output FEP data to.

    Source code in smee/mm/_fe.py
    def generate_dg_solv_data(\n    solute: smee.TensorTopology,\n    solvent_a: smee.TensorTopology | None,\n    solvent_b: smee.TensorTopology | None,\n    force_field: smee.TensorForceField,\n    temperature: openmm.unit.Quantity = 298.15 * openmm.unit.kelvin,\n    pressure: openmm.unit.Quantity = 1.0 * openmm.unit.atmosphere,\n    solvent_a_protocol: typing.Optional[\"absolv.config.EquilibriumProtocol\"] = None,\n    solvent_b_protocol: typing.Optional[\"absolv.config.EquilibriumProtocol\"] = None,\n    n_solvent_a: int = 216,\n    n_solvent_b: int = 216,\n    output_dir: pathlib.Path | None = None,\n):\n    \"\"\"Run a solvation free energy calculation using ``absolv``, and saves the output\n    such that a differentiable free energy can be computed.\n\n    The free energy will correspond to the free energy of transferring a solute from\n    solvent A to solvent B.\n\n    Args:\n        solute: The solute topology.\n        solvent_a: The topology of solvent A, or ``None`` if solvent A is vacuum.\n        solvent_b: The topology of solvent B, or ``None`` if solvent B is vacuum.\n        force_field: The force field to parameterize the system with.\n        temperature: The temperature to simulate at.\n        pressure: The pressure to simulate at.\n        solvent_a_protocol: The protocol to use to decouple the solute in solvent A.\n        solvent_b_protocol: The protocol to use to decouple the solute in solvent B.\n        n_solvent_a: The number of solvent A molecules to use.\n        n_solvent_b: The number of solvent B molecules to use.\n        output_dir: The directory to write the output FEP data to.\n    \"\"\"\n    import absolv.config\n    import absolv.runner\n    import femto.md.config\n    import femto.md.system\n\n    output_dir = pathlib.Path.cwd() if output_dir is None else output_dir\n\n    vacuum_protocol = absolv.config.EquilibriumProtocol(\n        production_protocol=absolv.config.HREMDProtocol(\n            n_steps_per_cycle=500,\n            n_cycles=2000,\n            integrator=femto.md.config.LangevinIntegrator(\n                timestep=1.0 * openmm.unit.femtosecond\n            ),\n            trajectory_interval=1,\n        ),\n        lambda_sterics=absolv.config.DEFAULT_LAMBDA_STERICS_VACUUM,\n        lambda_electrostatics=absolv.config.DEFAULT_LAMBDA_ELECTROSTATICS_VACUUM,\n    )\n    solution_protocol = absolv.config.EquilibriumProtocol(\n        production_protocol=absolv.config.HREMDProtocol(\n            n_steps_per_cycle=500,\n            n_cycles=1000,\n            integrator=femto.md.config.LangevinIntegrator(\n                timestep=4.0 * openmm.unit.femtosecond\n            ),\n            trajectory_interval=1,\n            trajectory_enforce_pbc=True,\n        ),\n        lambda_sterics=absolv.config.DEFAULT_LAMBDA_STERICS_SOLVENT,\n        lambda_electrostatics=absolv.config.DEFAULT_LAMBDA_ELECTROSTATICS_SOLVENT,\n    )\n\n    config = absolv.config.Config(\n        temperature=temperature,\n        pressure=pressure,\n        alchemical_protocol_a=solvent_a_protocol\n        if solvent_a_protocol is not None\n        else (vacuum_protocol if solvent_a is None else solution_protocol),\n        alchemical_protocol_b=solvent_b_protocol\n        if solvent_b_protocol is not None\n        else (vacuum_protocol if solvent_b is None else solution_protocol),\n    )\n\n    solute_mol = openff.toolkit.Molecule.from_rdkit(\n        smee.mm._utils.topology_to_rdkit(solute),\n        allow_undefined_stereo=True,\n    )\n\n    system_config = absolv.config.System(\n        solutes={solute_mol.to_smiles(mapped=True): 1},\n        solvent_a=_to_solvent_dict(solvent_a, n_solvent_a),\n        solvent_b=_to_solvent_dict(solvent_b, n_solvent_b),\n    )\n\n    topologies = {\n        \"solvent-a\": smee.TensorSystem([solute], [1], is_periodic=False)\n        if solvent_a is None\n        else smee.TensorSystem([solute, solvent_a], [1, n_solvent_a], is_periodic=True),\n        \"solvent-b\": smee.TensorSystem([solute], [1], is_periodic=False)\n        if solvent_b is None\n        else smee.TensorSystem([solute, solvent_b], [1, n_solvent_b], is_periodic=True),\n    }\n    pressures = {\n        \"solvent-a\": None\n        if solvent_a is None\n        else pressure.value_in_unit(openmm.unit.atmosphere),\n        \"solvent-b\": None\n        if solvent_b is None\n        else pressure.value_in_unit(openmm.unit.atmosphere),\n    }\n\n    for phase, topology in topologies.items():\n        state = {\n            \"system\": topology,\n            \"temperature\": temperature.value_in_unit(openmm.unit.kelvin),\n            \"pressure\": pressures[phase],\n        }\n\n        (output_dir / phase).mkdir(exist_ok=True, parents=True)\n        (output_dir / phase / \"state.pkl\").write_bytes(pickle.dumps(state))\n\n    def _parameterize(\n        top, coords, phase: typing.Literal[\"solvent-a\", \"solvent-b\"]\n    ) -> openmm.System:\n        return smee.converters.convert_to_openmm_system(force_field, topologies[phase])\n\n    prepared_system_a, prepared_system_b = absolv.runner.setup(\n        system_config, config, _parameterize\n    )\n\n    femto.md.system.apply_hmr(\n        prepared_system_a.system,\n        parmed.openmm.load_topology(prepared_system_a.topology.to_openmm()),\n    )\n    femto.md.system.apply_hmr(\n        prepared_system_b.system,\n        parmed.openmm.load_topology(prepared_system_a.topology.to_openmm()),\n    )\n\n    result = absolv.runner.run_eq(\n        config, prepared_system_a, prepared_system_b, \"CUDA\", output_dir, parallel=True\n    )\n\n    if solvent_a is not None:\n        solvent_a_output = _extract_pure_solvent(force_field, output_dir / \"solvent-a\")\n        torch.save(solvent_a_output, output_dir / \"solvent-a\" / \"pure.pt\")\n    if solvent_b is not None:\n        solvent_b_output = _extract_pure_solvent(force_field, output_dir / \"solvent-b\")\n        torch.save(solvent_b_output, output_dir / \"solvent-b\" / \"pure.pt\")\n\n    return result\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_dg_solv","title":"compute_dg_solv","text":"
    compute_dg_solv(\n    force_field: TensorForceField, fep_dir: Path\n) -> Tensor\n

    Computes \u2206G_solv from existing FEP data.

    Notes

    It is assumed that FEP data was generated using the same force field as force_field, and using generate_dg_solv_data

    Parameters:

    • force_field (TensorForceField) \u2013

      The force field used to generate the FEP data.

    • fep_dir (Path) \u2013

      The directory containing the FEP data.

    Returns:

    • Tensor \u2013

      \u2206G_solv [kcal/mol].

    Source code in smee/mm/_ops.py
    def compute_dg_solv(\n    force_field: smee.TensorForceField, fep_dir: pathlib.Path\n) -> torch.Tensor:\n    \"\"\"Computes \u2206G_solv from existing FEP data.\n\n    Notes:\n        It is assumed that FEP data was generated using the same force field as\n        ``force_field``, and using ``generate_dg_solv_data``\n\n    Args:\n        force_field: The force field used to generate the FEP data.\n        fep_dir: The directory containing the FEP data.\n\n    Returns:\n        \u2206G_solv [kcal/mol].\n    \"\"\"\n\n    tensors, parameter_lookup, attribute_lookup, has_v_sites = _pack_force_field(\n        force_field\n    )\n\n    kwargs = {\n        \"force_field\": force_field,\n        \"parameter_lookup\": parameter_lookup,\n        \"attribute_lookup\": attribute_lookup,\n        \"has_v_sites\": has_v_sites,\n        \"fep_dir\": fep_dir,\n    }\n    return _ComputeDGSolv.apply(kwargs, *tensors)\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_dg_solv","title":"reweight_dg_solv","text":"
    reweight_dg_solv(\n    force_field: TensorForceField,\n    fep_dir: Path,\n    dg_0: Tensor,\n    min_samples: int = 50,\n) -> tuple[Tensor, float]\n

    Computes \u2206G_solv by re-weighting existing FEP data.

    Notes

    It is assumed that FEP data was generated using generate_dg_solv_data.

    Parameters:

    • force_field (TensorForceField) \u2013

      The force field to reweight to.

    • fep_dir (Path) \u2013

      The directory containing the FEP data.

    • dg_0 (Tensor) \u2013

      \u2206G_solv [kcal/mol] computed with the force field used to generate the FEP data.

    • min_samples (int, default: 50 ) \u2013

      The minimum number of effective samples required to re-weight.

    Raises:

    • NotEnoughSamplesError \u2013

      If the number of effective samples is less than min_samples.

    Returns:

    • tuple[Tensor, float] \u2013

      The re-weighted \u2206G_solv [kcal/mol], and the minimum number of effective samples between the two phases.

    Source code in smee/mm/_ops.py
    def reweight_dg_solv(\n    force_field: smee.TensorForceField,\n    fep_dir: pathlib.Path,\n    dg_0: torch.Tensor,\n    min_samples: int = 50,\n) -> tuple[torch.Tensor, float]:\n    \"\"\"Computes \u2206G_solv by re-weighting existing FEP data.\n\n    Notes:\n        It is assumed that FEP data was generated using ``generate_dg_solv_data``.\n\n    Args:\n        force_field: The force field to reweight to.\n        fep_dir: The directory containing the FEP data.\n        dg_0: \u2206G_solv [kcal/mol] computed with the force field used to generate the\n            FEP data.\n        min_samples: The minimum number of effective samples required to re-weight.\n\n    Raises:\n        NotEnoughSamplesError: If the number of effective samples is less than\n            ``min_samples``.\n\n    Returns:\n        The re-weighted \u2206G_solv [kcal/mol], and the minimum number of effective samples\n        between the two phases.\n    \"\"\"\n    tensors, parameter_lookup, attribute_lookup, has_v_sites = _pack_force_field(\n        force_field\n    )\n\n    kwargs = {\n        \"force_field\": force_field,\n        \"parameter_lookup\": parameter_lookup,\n        \"attribute_lookup\": attribute_lookup,\n        \"has_v_sites\": has_v_sites,\n        \"fep_dir\": fep_dir,\n        \"dg_0\": dg_0,\n    }\n\n    dg, n_eff = _ReweightDGSolv.apply(kwargs, *tensors)\n\n    if n_eff < min_samples:\n        raise NotEnoughSamplesError\n\n    return dg, n_eff\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.

    Modules:

    • smee \u2013

      Differentiably evaluate energies of molecules using SMIRNOFF force fields

    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.

    Modules:

    • smee \u2013

      Differentiably evaluate energies of molecules using SMIRNOFF force fields

    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