Code Monkey home page Code Monkey logo

openmm-torch's Introduction

GH Actions Status Conda Anaconda Cloud Badge

OpenMM PyTorch Plugin

This is a plugin for OpenMM that allows PyTorch static computation graphs to be used for defining an OpenMM TorchForce object, an OpenMM Force class that computes a contribution to the potential energy or used as a collective variable via CustomCVForce.

To use it, you create a PyTorch model that takes a (nparticles,3) tensor of particle positions (in nanometers) as input and produces energy (in kJ/mol) or the value of the collective variable as output.
The TorchForce provided by this plugin can then use the model to compute energy contributions or apply forces to particles during a simulation. TorchForce also supports the use of global context parameters that can be fed to the model and changed dynamically during runtime.

Installation

Installing with conda

We provide conda packages for Linux and MacOS via conda-forge, which can be installed from the conda-forge channel:

conda install -c conda-forge openmm-torch

If you don't have conda available, we recommend installing Miniconda for Python 3 to provide the conda package manager.

Building from source

This plugin uses CMake as its build system.
Before compiling you must install LibTorch, which is the PyTorch C++ API, by following the instructions at https://pytorch.org. You can then follow these steps:

  1. Create a directory in which to build the plugin.

  2. Run the CMake GUI or ccmake, specifying your new directory as the build directory and the top level directory of this project as the source directory.

  3. Press "Configure". (Do not worry if it produces an error message about not being able to find PyTorch.)

  4. Set OPENMM_DIR to point to the directory where OpenMM is installed. This is needed to locate the OpenMM header files and libraries. If you are unsure of what directory this is, the following script will print it out.

from simtk import openmm
import os
print(os.path.dirname(openmm.version.openmm_library_path))
  1. Set PYTORCH_DIR to point to the directory where you installed the LibTorch.

  2. Set CMAKE_INSTALL_PREFIX to the directory where the plugin should be installed. Usually, this will be the same as OPENMM_DIR, so the plugin will be added to your OpenMM installation.

  3. If you plan to build the OpenCL platform, make sure that OPENCL_INCLUDE_DIR and OPENCL_LIBRARY are set correctly, and that NN_BUILD_OPENCL_LIB is selected.

  4. If you plan to build the CUDA platform, make sure that CUDA_TOOLKIT_ROOT_DIR is set correctly and that NN_BUILD_CUDA_LIB is selected.

  5. Press "Configure" again if necessary, then press "Generate".

  6. Use the build system you selected to build and install the plugin. For example, if you selected Unix Makefiles, type make install to install the plugin, and make PythonInstall to install the Python wrapper.

Using the OpenMM PyTorch plugin

Tutorials

Exporting a PyTorch model for use in OpenMM

The first step is to create a PyTorch model defining the calculation to perform.
It should take particle positions in nanometers (in the form of a torch.Tensor of shape (nparticles,3) as input, and return the potential energy in kJ/mol as a torch.Scalar as output.

The model must then be converted to a TorchScript module and saved to a file.
Converting to TorchScript can usually be done with a single call to torch.jit.script() or torch.jit.trace(), although more complicated models can sometimes require extra steps.
See the PyTorch documentation for details.

Here is a simple Python example that does this for a very simple potential---a harmonic force attracting every particle to the origin:

import torch

class ForceModule(torch.nn.Module):
    """A central harmonic potential as a static compute graph"""
    def forward(self, positions):
        """The forward method returns the energy computed from positions.

        Parameters
        ----------
        positions : torch.Tensor with shape (nparticles,3)
           positions[i,k] is the position (in nanometers) of spatial dimension k of particle i

        Returns
        -------
        potential : torch.Scalar
           The potential energy (in kJ/mol)
        """
        return torch.sum(positions**2)

# Render the compute graph to a TorchScript module
module = torch.jit.script(ForceModule())

# Serialize the compute graph to a file
module.save('model.pt')

To use the exported model in a simulation, create a TorchForce object and add it to your System.
The constructor takes the path to the saved model as an argument.
Alternatively, the scripted module can be provided directly.
For example,

# Create the TorchForce from the serialized compute graph
from openmmtorch import TorchForce
# Construct using a serialized module:
torch_force = TorchForce('model.pt')
# or using an instance of the module:
torch_force = TorchForce(module)

# Add the TorchForce to your System
system.addForce(torch_force)

Defining a model that uses periodic boundary conditions

When defining the model to perform a calculation, you may want to apply periodic boundary conditions.

To do this, call setUsesPeriodicBoundaryConditions(True) on the TorchForce.
The graph is then expected to take a second input, which contains the current periodic box vectors.
You can make use of them in whatever way you want for computing the force.
For example, the following code applies periodic boundary conditions to each particle position to translate all of them into a single periodic cell:

class ForceModule(torch.nn.Module):
    """A central harmonic force with periodic boundary conditions"""
    def forward(self, positions, boxvectors):
        """The forward method returns the energy computed from positions.

        Parameters
        ----------
        positions : torch.Tensor with shape (nparticles,3)
           positions[i,k] is the position (in nanometers) of spatial dimension k of particle i
        boxvectors : torch.tensor with shape (3,3)
           boxvectors[i,k] is the box vector component k (in nanometers) of box vector i

        Returns
        -------
        potential : torch.Scalar
           The potential energy (in kJ/mol)
        """
        # Image articles in rectilinear box
        # NOTE: This will not work for non-orthogonal boxes
        boxsize = boxvectors.diag()
        periodicPositions = positions - torch.floor(positions/boxsize)*boxsize
        # Compute central harmonic potential
        return torch.sum(periodicPositions**2)

Note that this code assumes a rectangular box. Applying periodic boundary conditions with a triclinic box requires a slightly more complicated calculation.

Defining global parameters that can be modified within the Context

The graph can also take arbitrary scalar arguments that are passed in at runtime. For example, this model multiplies the energy by scale, which is passed as an argument to forward().

class ForceModule(torch.nn.Module):
    """A central harmonic force with a user-defined global scale parameter"""
    def forward(self, positions, scale):
        """The forward method returns the energy computed from positions.

        Parameters
        ----------
        positions : torch.Tensor with shape (nparticles,3)
           positions[i,k] is the position (in nanometers) of spatial dimension k of particle i
        scale : torch.Scalar
           A scalar tensor defined by 'TorchForce.addGlobalParameter'.
           Here, it scales the contribution to the potential.
           Note that parameters are passed in the order defined by `TorchForce.addGlobalParameter`, not by name.

        Returns
        -------
        potential : torch.Scalar
           The potential energy (in kJ/mol)
        """
        return scale*torch.sum(positions**2)

When you create the TorchForce, call addGlobalParameter() once for each extra argument.

torch_force.addGlobalParameter('scale', 2.0)

This specifies the name of the parameter and its initial value. The name does not need to match the argument to forward(). All global parameters are simply passed to the model in the order you define them. The advantage of using global parameters is that you can change their values at any time by calling setParameter() on the Context.

context.setParameter('scale', 5.0)

Computing forces in the model

In the examples above, the PyTorch model computes the potential energy. Backpropagation can be used to compute the corresponding forces. That always works, but sometimes you may have a more efficient way to compute the forces than the generic backpropagation algorithm. In that case, you can have the model directly compute forces as well as energy, returning both of them in a tuple. Remember that the force is the negative gradient of the energy.

import torch

class ForceModule(torch.nn.Module):
    """A central harmonic potential that computes both energy and forces."""
    def forward(self, positions):
        """The forward method returns the energy and forces computed from positions.

        Parameters
        ----------
        positions : torch.Tensor with shape (nparticles,3)
           positions[i,k] is the position (in nanometers) of spatial dimension k of particle i

        Returns
        -------
        potential : torch.Scalar
           The potential energy (in kJ/mol)
        forces : torch.Tensor with shape (nparticles,3)
           The force (in kJ/mol/nm) on each particle
        """
        return (torch.sum(positions**2), -2*positions)

When you create the TorchForce, call setOutputsForces() to tell it to expect the model to return forces.

torch_force.setOutputsForces(True)

Computing energy derivatives with respect to global parameters

TorchForce can compute derivatives of the energy with respect to global parameters.. In order to do so the global parameters must be registered as energy derivatives. This is done by calling addEnergyParameterDerivative() for each parameter.

The parameter derivatives can be queried by calling getEnergyParameterDerivatives() on the State object returned by Context.getState(). The result is a dictionary with the parameter names as keys and the derivatives as values.

import torch as pt
from openmmtorch import TorchForce
import openmm as mm


class ForceWithParameters(pt.nn.Module):

    def __init__(self):
        super(ForceWithParameters, self).__init__()

    def forward(self, positions: pt.Tensor, k: pt.Tensor) -> pt.Tensor:
        return k * pt.sum(positions**2)


numParticles = 10
system = mm.System()
for _ in range(numParticles):
    system.addParticle(1.0)

model = pt.jit.script(ForceWithParameters())
tforce = TorchForce(model)
tforce.setOutputsForces(False)
tforce.addGlobalParameter("k", 2.0)
tforce.addEnergyParameterDerivative("k")
system.addForce(tforce)
context = mm.Context(system, mm.VerletIntegrator(1.0))
context.setPositions(pt.rand(numParticles, 3).numpy())
state = context.getState(getParameterDerivatives=True)
dEdk = state.getEnergyParameterDerivatives()["k"]

Recording the model into a CUDA graph

You can ask TorchForce to run the model using CUDA graphs. Not every model will be compatible with this feature, but it can be a significant performance boost for some models. To enable it the CUDA platform must be used and an special property must be provided to TorchForce:

torch_force.setProperty("useCUDAGraphs", "true")
# The property can also be set at construction
torch_force = TorchForce('model.pt', {'useCUDAGraphs': 'true'})

The first time the model is run, it will be compiled (also known as recording) into a CUDA graph. Subsequent runs will use the compiled graph, which can be significantly faster. It is possible that compilation fails, in which case an OpenMMException will be raised. If that happens, you can disable CUDA graphs and try again.

It is required to run the model at least once before recording, in what is known as warmup. By default TorchForce will run the model just a few times before recording, but controlling warmup steps might be desired. In these cases one can set the property CUDAGraphWarmupSteps:

torch_force.setProperty("CUDAGraphWarmupSteps", "12")

List of available properties

Some TorchForce functionalities can be customized by setting properties on an instance of it. Properties can be set at construction or by using setProperty. A property is a pair of key/value strings. For instance:

torch_force = TorchForce('model.pt', {'useCUDAGraphs': 'true'})
#Alternatively setProperty can be used to configure an already created instance.
#torch_force.setProperty("useCUDAGraphs", "true")
print("Current properties:")
for property in torch_force.getProperties():
    print(property.key, property.value)

Currently, the following properties are available:

  1. useCUDAGraphs: Turns on the CUDA graph functionality
  2. CUDAGraphWarmupSteps: When CUDA graphs are being used, controls the number of warmup calls to the model before recording.

License

This is part of the OpenMM molecular simulation toolkit originating from Simbios, the NIH National Center for Physics-Based Simulation of Biological Structures at Stanford, funded under the NIH Roadmap for Medical Research, grant U54 GM072970. See https://simtk.org.

Portions copyright (c) 2018-2020 Stanford University and the Authors.

Authors: Peter Eastman

Contributors: Raimondas Galvelis, Jaime Rodriguez-Guerra, Yaoyi Chen, John D. Chodera

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS, CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

openmm-torch's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

openmm-torch's Issues

OpenMM-Torch 0.5

After fixing the issue with the CUDA context (#47, #49) and adding the ability to output forces directly (#52), it is time for the next release.

@peastman do you agree?

Error on Reference platform when ForceModule has submodules

The following script works on both CUDA and OpenCL platforms, but fails on the Reference platform. It seems to happen when the ForceModule has submodules as in the script.

import torch
import os
from openmmtorch import TorchForce
import simtk.openmm as omm
import simtk.unit as unit
import numpy as np

class ForceModule(torch.nn.Module):
    def __init__(self, num_particles):
        super(ForceModule, self).__init__()
	self.fc = torch.nn.Linear(num_particles*3, 1)

    def forward(self, positions):
        output = self.fc(torch.reshape(positions, [-1]))
        return output

# Render the compute graph to a TorchScript module
num_particles = 10
force_module = ForceModule(num_particles)
scripted_module = torch.jit.script(force_module)

positions = torch.randn(num_particles, 3)
output = force_module(positions)
print("output from force_module: output", output)

output = scripted_module(positions)
print("output from scripted_module: output", output)

# Serialize the compute graph to a file
os.makedirs("./output/test", exist_ok = True)
scripted_module.save('./output/test/model.pt')

torch_force = TorchForce('./output/test/model.pt')

# Add the TorchForce to your System
system = omm.System()
for i in range(num_particles):
    system.addParticle(1.0)
system.addForce(torch_force)

T = 300*unit.kelvin
fricCoef = 1./unit.picoseconds
stepsize = 2. * unit.femtoseconds

CUDA_platform = omm.Platform.getPlatformByName('CUDA')
CUDA_integrator = omm.LangevinMiddleIntegrator(T, fricCoef, stepsize)
context = omm.Context(system, CUDA_integrator, CUDA_platform)
context.setPositions(positions.numpy())
state = context.getState(getEnergy = True)
potential_energy = state.getPotentialEnergy()
print("potential energy from CUDA platform:", potential_energy)

OpenCL_platform = omm.Platform.getPlatformByName('OpenCL')
OpenCL_integrator = omm.LangevinMiddleIntegrator(T, fricCoef, stepsize)
context = omm.Context(system, OpenCL_integrator, OpenCL_platform)
context.setPositions(positions.numpy())
state = context.getState(getEnergy = True)
potential_energy = state.getPotentialEnergy()
print("potential energy from OpenCL platform:", potential_energy)

reference_platform = omm.Platform.getPlatformByName('Reference')
reference_integrator = omm.LangevinMiddleIntegrator(T, fricCoef, stepsize)
context = omm.Context(system, reference_integrator, reference_platform)
context.setPositions(positions.numpy())
state = context.getState(getEnergy = True)
potential_energy = state.getPotentialEnergy()
print("potential energy from reference platform:", potential_energy)

This is the output message:

output from scripted_module: output tensor([0.0880], grad_fn=<AddBackward0>)
potential energy from CUDA platform: 0.08795830607414246 kJ/mol
potential energy from OpenCL platform: 0.08795826137065887 kJ/mol
Traceback (most recent call last):
  File "./script/single_protein_manybody/test.py", line 65, in <module>
    state = context.getState(getEnergy = True)
  File "/home/gridsan/dingxq/.conda/envs/openmm_torch/lib/python3.8/site-packages/simtk/openmm/openmm.py", line 13172, in getState
    state = _openmm.Context_getState(self, types, enforcePeriodicBox, groups_mask)
SystemError: <built-in function Context_getState> returned NULL without setting an error
Segmentation fault

`TorchForce` missing `getNumGlobalParameters()` method

I noticed that while the TorchForce supports addGlobalParameter, it doesn't have getNumGlobalParameters or getGlobalParameterName. Is it possible these could be added? or is there a way to wrap this TorchForce into a CustomCVForce so that I have access to these methods?

Error when trying to create mixed system

Hi,

I've been trying to use ANI-2x to model the ligand during the simulation of a protein-ligand complex. I've been using the code below to set up the system.

prmtop = AmberPrmtopFile(prmtop_file)
inpcrd = AmberInpcrdFile(crd_file)

potential = MLPotential('ani2x')
ml_atoms  = [atom.index for atom in prmtop.topology.atoms() if atom.residue.name == "MOL"]
mm_system = prmtop.createSystem(nonbondedMethod=PME,
nonbondedCutoff=1.0*unit.nanometers, constraints=HBonds, rigidWater=True,
ewaldErrorTolerance=0.0005)
ml_system = potential.createMixedSystem(prmtop.topology, mm_system, ml_atoms)

The last line in the code raises the following error:

Traceback (most recent call last):
  File "/Users/victorprincipe/Local/project2021/local_tests/openmm_ani_test/md_ani.py", line 26, in <module>
    ml_system = potential.createMixedSystem(prmtop.topology, mm_system, ml_atoms)
  File "/Users/victorprincipe/miniconda3/envs/openmmani/lib/python3.9/site-packages/openmmml-1.0-py3.9.egg/openmmml/mlpotential.py", line 278, in createMixedSystem
    self._impl.addForces(topology, newSystem, atomList, forceGroup, **args)
  File "/Users/victorprincipe/miniconda3/envs/openmmani/lib/python3.9/site-packages/openmmml-1.0-py3.9.egg/openmmml/models/anipotential.py", line 85, in addForces
    species = model.species_to_tensor(elements).unsqueeze(0)
  File "/Users/victorprincipe/miniconda3/envs/openmmani/lib/python3.9/site-packages/torchani/models.py", line 165, in species_to_tensor
    return self._species_to_tensor(*args, **kwargs) \
  File "/Users/victorprincipe/miniconda3/envs/openmmani/lib/python3.9/site-packages/torchani/utils.py", line 237, in __call__
    rev = [self.rev_species[s] for s in species]
  File "/Users/victorprincipe/miniconda3/envs/openmmani/lib/python3.9/site-packages/torchani/utils.py", line 237, in <listcomp>
    rev = [self.rev_species[s] for s in species]
KeyError: 'l'

I'm not sure why this is occurring, so any help would be greatly appreciated. I've also attached the prmtop and inpcrd files below. Thanks in advance!

system_files.zip

undefined::reference error during building from source

Hello,
I’m trying to build Opemm-torch from source, and I get an undefined reference error. This error happens when I install Pytroch from PyTorch channel via Conda. I believe this error relates to the compiling of OpenMM with the old C++ ABI. I tried installing OpenMM from source with the new C++11 AB, but still, I got the same error.

Environment config:

  • OpenMM7.7.0 from condaforge
  • PyTorch 1.10.2 from pytoch
  • GNU 8.5.0

Commands for compiling Opemm-torch

export Torch_DIR="$(python -c 'import torch.utils; print(torch.utils.cmake_prefix_path)')"
cmake  ..
ccmake -i ..
make install

Error:

[ 36%] Linking CXX executable ../../TestSerializeTorchForce
CMakeFiles/TestSerializeTorchForce.dir/TestSerializeTorchForce.cpp.o: In function `testSerialization()':
TestSerializeTorchForce.cpp:(.text+0x35a): undefined reference to `OpenMM::throwException(char const*, int, std::string const&)'
TestSerializeTorchForce.cpp:(.text+0x447): undefined reference to `OpenMM::throwException(char const*, int, std::string const&)'
TestSerializeTorchForce.cpp:(.text+0x534): undefined reference to `OpenMM::throwException(char const*, int, std::string const&)'
TestSerializeTorchForce.cpp:(.text+0x665): undefined reference to `OpenMM::throwException(char const*, int, std::string const&)'
TestSerializeTorchForce.cpp:(.text+0x77e): undefined reference to `OpenMM::throwException(char const*, int, std::string const&)'
CMakeFiles/TestSerializeTorchForce.dir/TestSerializeTorchForce.cpp.o:TestSerializeTorchForce.cpp:(.text+0x890): more undefined references to `OpenMM::throwException(char const*, int, std::string const&)' follow
CMakeFiles/TestSerializeTorchForce.dir/TestSerializeTorchForce.cpp.o: In function `void OpenMM::XmlSerializer::serialize<TorchPlugin::TorchForce>(TorchPlugin::TorchForce const*, std::string const&, std::ostream&)':
TestSerializeTorchForce.cpp:(.text._ZN6OpenMM13XmlSerializer9serializeIN11TorchPlugin10TorchForceEEEvPKT_RKSsRSo[_ZN6OpenMM13XmlSerializer9serializeIN11TorchPlugin10TorchForceEEEvPKT_RKSsRSo]+0x66): undefined reference to `OpenMM::SerializationNode::setName(std::string const&)'
TestSerializeTorchForce.cpp:(.text._ZN6OpenMM13XmlSerializer9serializeIN11TorchPlugin10TorchForceEEEvPKT_RKSsRSo[_ZN6OpenMM13XmlSerializer9serializeIN11TorchPlugin10TorchForceEEEvPKT_RKSsRSo]+0xc9): undefined reference to `OpenMM::SerializationNode::hasProperty(std::string const&) const'
TestSerializeTorchForce.cpp:(.text._ZN6OpenMM13XmlSerializer9serializeIN11TorchPlugin10TorchForceEEEvPKT_RKSsRSo[_ZN6OpenMM13XmlSerializer9serializeIN11TorchPlugin10TorchForceEEEvPKT_RKSsRSo]+0x100): undefined reference to `OpenMM::SerializationProxy::getTypeName() const'
TestSerializeTorchForce.cpp:(.text._ZN6OpenMM13XmlSerializer9serializeIN11TorchPlugin10TorchForceEEEvPKT_RKSsRSo[_ZN6OpenMM13XmlSerializer9serializeIN11TorchPlugin10TorchForceEEEvPKT_RKSsRSo]+0x150): undefined reference to `OpenMM::SerializationProxy::getTypeName() const'
TestSerializeTorchForce.cpp:(.text._ZN6OpenMM13XmlSerializer9serializeIN11TorchPlugin10TorchForceEEEvPKT_RKSsRSo[_ZN6OpenMM13XmlSerializer9serializeIN11TorchPlugin10TorchForceEEEvPKT_RKSsRSo]+0x18d): undefined reference to `OpenMM::SerializationNode::setStringProperty(std::string const&, std::string const&)'
../../libOpenMMTorch.so: undefined reference to `OpenMM::Platform::createKernel(std::string const&, OpenMM::ContextImpl&) const'
../../libOpenMMTorch.so: undefined reference to `OpenMM::SerializationNode::setBoolProperty(std::string const&, bool)'
../../libOpenMMTorch.so: undefined reference to `OpenMM::SerializationNode::getDoubleProperty(std::string const&) const'
../../libOpenMMTorch.so: undefined reference to `OpenMM::SerializationNode::setIntProperty(std::string const&, int)'
../../libOpenMMTorch.so: undefined reference to `OpenMM::SerializationNode::getIntProperty(std::string const&) const'
../../libOpenMMTorch.so: undefined reference to `OpenMM::SerializationProxy::SerializationProxy(std::string const&)'
../../libOpenMMTorch.so: undefined reference to `OpenMM::SerializationNode::getBoolProperty(std::string const&) const'
../../libOpenMMTorch.so: undefined reference to `OpenMM::SerializationNode::getStringProperty(std::string const&) const'
../../libOpenMMTorch.so: undefined reference to `OpenMM::SerializationNode::getName() const'
../../libOpenMMTorch.so: undefined reference to `OpenMM::SerializationNode::createChildNode(std::string const&)'
../../libOpenMMTorch.so: undefined reference to `OpenMM::SerializationNode::setDoubleProperty(std::string const&, double)'
../../libOpenMMTorch.so: undefined reference to `OpenMM::SerializationNode::getIntProperty(std::string const&, int) const'
collect2: error: ld returned 1 exit status
make[2]: *** [serialization/tests/CMakeFiles/TestSerializeTorchForce.dir/build.make:117: TestSerializeTorchForce] Error 1
make[1]: *** [CMakeFiles/Makefile2:315: serialization/tests/CMakeFiles/TestSerializeTorchForce.dir/all] Error 2
make: *** [Makefile:160: all] Error 2

Any idea?

Thank you.

Second step fails

I am following steps torchANI tutorial steps. The code fails in the second call to simulation function. Any source of error. I couldn't install mamba in my workstation, and I used conda with pytorch=1.12, pytorch=1.11 throws symbol error.

import openmmtools

ala2 = openmmtools.testsystems.AlanineDipeptideVacuum(constraints=None)

while ala2.system.getNumForces() > 0:
  ala2.system.removeForce(0)

assert ala2.system.getNumConstraints() == 0
assert ala2.system.getNumForces() == 0

atomic_numbers = [atom.element.atomic_number for atom in ala2.topology.atoms()]

import torch as pt
from torchani.models import ANI2x
from NNPOps import OptimizedTorchANI

class NNP(pt.nn.Module):

  def __init__(self, atomic_numbers):

    super().__init__()

    # Store the atomic numbers
    self.atomic_numbers = pt.tensor(atomic_numbers).unsqueeze(0)

    # Create an ANI-2x model
    self.model = ANI2x(periodic_table_index=True)

    # Accelerate the model
    self.model = OptimizedTorchANI(self.model, self.atomic_numbers)

  def forward(self, positions):

    # Prepare the positions
    positions = positions.unsqueeze(0).float() * 10 # nm --> Å

    # Run ANI-2x
    result = self.model((self.atomic_numbers, positions))

    # Get the potential energy
    energy = result.energies[0] * 2625.5 # Hartree --> kJ/mol

    return energy

nnp = NNP(atomic_numbers)

pos = pt.tensor(ala2.positions.tolist())
energy_1 = float(nnp(pos))


from openmmtorch import TorchForce

pt.jit.script(nnp).save('model.pt')
force = TorchForce('model.pt')

ala2.system.addForce(force)
assert ala2.system.getNumForces() == 1


import sys
from openmm import LangevinMiddleIntegrator, BrownianIntegrator
from openmm.app import Simulation, StateDataReporter
from openmm.unit import kelvin, picosecond, femtosecond

temperature = 298.15 * kelvin
frictionCoeff = 1 / picosecond
timeStep = 1 * femtosecond
integrator = LangevinMiddleIntegrator(temperature, frictionCoeff, timeStep)

simulation = Simulation(ala2.topology, ala2.system, integrator)
simulation.context.setPositions(ala2.positions)

reporter = StateDataReporter(file=sys.stdout, reportInterval=1, step=True, time=True, potentialEnergy=True, temperature=True)
simulation.reporters.append(reporter)

from openmm.unit import kilojoule_per_mole
simulation.step(1000)

Warning: importing 'simtk.openmm' is deprecated.  Import 'openmm' instead.
#"Step","Time (ps)","Potential Energy (kJ/mole)","Temperature (K)"
1,0.001,-1301525.4216868656,4.132715143549229
Traceback (most recent call last):
  File "/home/amoradzadeh/Work/OpenMM-Torch/example-openmm-torch.py", line 83, in <module>
    simulation.step(1000)
  File "/home/amoradzadeh/anaconda3/envs/tmp-openmm-torch/lib/python3.9/site-packages/openmm/app/simulation.py", line 141, in step
    self._simulate(endStep=self.currentStep+steps)
  File "/home/amoradzadeh/anaconda3/envs/tmp-openmm-torch/lib/python3.9/site-packages/openmm/app/simulation.py", line 210, in _simulate
    self.integrator.step(stepsToGo)
  File "/home/amoradzadeh/anaconda3/envs/tmp-openmm-torch/lib/python3.9/site-packages/openmm/openmm.py", line 6145, in step
    return _openmm.LangevinMiddleIntegrator_step(self, steps)
openmm.OpenMMException: The following operation failed in the TorchScript interpreter.
Traceback of TorchScript (most recent call last):
  File "<string>", line 57, in <backward op>
            self_scalar_type = self.dtype
            def backward(grad_output):
                grad_self = AD_sum_backward(grad_output, self_size, dim, keepdim).to(self_scalar_type) / AD_safe_size(self_size, dim)
                            ~~~~~~~~~~~~~~~ <--- HERE
                return grad_self, None, None, None
  File "<string>", line 24, in AD_sum_backward
            if not keepdim and len(sizes) > 0:
                if len(dims) == 1:
                    return grad.unsqueeze(dims[0]).expand(sizes)
                           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ <--- HERE
                else:
                    res = AD_unsqueeze_multiple(grad, dims, len(sizes))
RuntimeError: expand(CUDADoubleType{[1, 1]}, size=[1]): the number of sizes provided (1) must be greater or equal to the number of dimensions in the tensor (2)

Simulation explodes after adding a TorchForce that is identical to zero

The following script simulates a simple system with 10 particles. The system has two forces: a simple harmonic bond force and a TorchForce. The forces applied on particles via the TorchForce is made into zeros, so the system with the TorchForce should be identical to that without the TorchForce. Without the TorchForce, simulations of the system is stable. After adding the TorchForce, simulation of the system explodes. This is strange because the TorchForce contributes nothing to the system.

import torch
torch.set_default_dtype(torch.double)
import numpy as np
import simtk.openmm.app  as app
import simtk.openmm as omm
import simtk.unit as unit
from openmmtorch import TorchForce
import copy

class NNForceDihedral(torch.nn.Module):
    def __init__(self, num_dihedrals):
        super(NNForceDihedral, self).__init__()

	self.num_dihedrals = num_dihedrals
	self.fc1 = torch.nn.Linear(2*num_dihedrals, num_dihedrals)
        self.fc2 = torch.nn.Linear(num_dihedrals, 1, bias = False)

    def forward(self, dihedrals):
        x = torch.cat((torch.cos(dihedrals), torch.sin(dihedrals)), -1)
	x = torch.tanh(self.fc1(x))
        output = self.fc2(x)
        output = torch.squeeze(output)
	return output

class ForceModule(torch.nn.Module):
    def __init__(self, num_particles):
        super(ForceModule, self).__init__()
	self.num_particles = num_particles
	self.nnforce_dihedral = NNForceDihedral(num_particles - 3)

	self.index = torch.nn.Parameter(
            torch.LongTensor( [[i, i+1, i+2, i+3] for i in range(num_particles - 3)]),
            requires_grad = False
	)

    def forward(self, positions):
        xyz = torch.unsqueeze(positions, 0)

        xyz_i = torch.index_select(xyz, 1, self.index[:, 0])
        xyz_j = torch.index_select(xyz, 1, self.index[:, 1])
        xyz_k = torch.index_select(xyz, 1, self.index[:, 2])
        xyz_l = torch.index_select(xyz, 1, self.index[:, 3])

        b1 = xyz_i - xyz_j
        b2 = xyz_j - xyz_k
        b3 = xyz_l - xyz_k

        b1_cross_b2 = torch.cross(b1, b2)
        b3_cross_b2 = torch.cross(b3, b2)

        cos_d = torch.norm(b2, dim = -1)*torch.sum(b1_cross_b2*b3_cross_b2, -1)
        sin_d = torch.sum(b2*torch.cross(b3_cross_b2, b1_cross_b2), -1)

        dihedrals = torch.atan2(sin_d, cos_d)
        potential_energy = self.nnforce_dihedral(dihedrals)*torch.tensor(0.0)

        return potential_energy

xyz = torch.tensor([[ 1.357623  , -1.3806132 , -1.2257496 ],
                    [ 1.0385588 , -1.3198054 , -1.4279023 ],
                    [ 0.7484111 , -1.471794  , -1.6183405 ],
                    [ 0.6739166 , -1.2910188 , -1.9467432 ],
                    [ 0.33078226, -1.451046  , -1.9793787 ],
                    [ 0.15436828, -1.2586378 , -1.68369   ],
                    [ 0.42108235, -1.0122279 , -1.616695  ],
                    [ 0.4789558 , -1.1335697 , -1.2618917 ],
                    [ 0.8091399 , -1.080018  , -1.087149  ],
                    [ 0.97696364, -1.3592714 , -0.9014565 ]], requires_grad = True)

system_01 = omm.System()
system_0 = omm.System()
system_1 = omm.System()
for _ in range(10):
    system_01.addParticle(100)
    system_0.addParticle(100)
    system_1.addParticle(100)


harmonic_bond_force = omm.HarmonicBondForce()
for i in range(9):
    harmonic_bond_force.addBond(i, i+1, 0.38, 7e4)

harmonic_bond_force.setForceGroup(0)
system_01.addForce(copy.copy(harmonic_bond_force))
system_0.addForce(copy.copy(harmonic_bond_force))

force_module = ForceModule(10)

scripted_force_module = torch.jit.script(force_module)
scripted_force_module.save(f"./output/test/force_module.pth")
nnforce = TorchForce(f"./output/test/force_module.pth")
nnforce.setForceGroup(1)
system_01.addForce(copy.copy(nnforce))
system_1.addForce(copy.copy(nnforce))


T = 300*unit.kelvin
fricCoef = 1./unit.picoseconds
stepsize = 2. * unit.femtoseconds

## simulation with both forces
print("="*80)
print("Simulate system with both forces")
print("="*80)
integrator_01 = omm.LangevinMiddleIntegrator(T, fricCoef, stepsize)
platform_01 = omm.Platform.getPlatformByName('Reference')
context_01 = omm.Context(system_01, integrator_01, platform_01)
context_01.setPositions(xyz.data.numpy())

for i in range(1000):
    integrator_01.step(1)

    state = context_01.getState(getEnergy = True, getForces = True, groups = set([0]))
    bond_energy = state.getPotentialEnergy().value_in_unit(unit.kilojoule_per_mole)
    bond_force = state.getForces()
    bond_force = np.array(bond_force.value_in_unit(unit.kilojoule_per_mole/unit.nanometer))

    state = context_01.getState(getEnergy = True, getForces = True, groups = set([1]))
    torch_energy = state.getPotentialEnergy().value_in_unit(unit.kilojoule_per_mole)
    torch_force = state.getForces()
    torch_force = np.array(torch_force.value_in_unit(unit.kilojoule_per_mole/unit.nanometer))

    if (i + 1) % 50 == 0:
        print(f"step: {i:>5}, bond_energy: {bond_energy:>10.2f} kJ/mol, torch_energy: {torch_energy:>10.2f} kJ/mol, bond_force[0,0]:", bond_force[0,0], 'torch\
_force[0,0]: ', torch_force[0,0])

## simulation with only harmonic force
print("="*80)
print("Simulate system with just the harminic bond force")
print("="*80)

integrator_0 = omm.LangevinMiddleIntegrator(T, fricCoef, stepsize)
platform_0 = omm.Platform.getPlatformByName('Reference')
context_0 = omm.Context(system_0, integrator_0, platform_0)
context_0.setPositions(xyz.data.numpy())

for i in range(1000):
    integrator_0.step(1)

    state = context_0.getState(getEnergy = True, getForces = True, groups = set([0]))
    bond_energy = state.getPotentialEnergy().value_in_unit(unit.kilojoule_per_mole)
    bond_force = state.getForces()
    bond_force = np.array(bond_force.value_in_unit(unit.kilojoule_per_mole/unit.nanometer))

    if (i + 1) % 50 == 0:
        print(f"step: {i:>5}, bond_energy: {bond_energy:>10.2f} kJ/mol, bond_force[0,0]:", bond_force[0,0])

## simulation with only Torch force
print("="*80)
print("Simulate system with just the Torch force")
print("="*80)

integrator_1 = omm.LangevinMiddleIntegrator(T, fricCoef, stepsize)
platform_1 = omm.Platform.getPlatformByName('Reference')
context_1 = omm.Context(system_1, integrator_1, platform_1)
context_1.setPositions(xyz.data.numpy())

for i in range(1000):
    integrator_1.step(1)

    state = context_1.getState(getEnergy = True, getForces = True, groups = set([1]))
    torch_energy = state.getPotentialEnergy().value_in_unit(unit.kilojoule_per_mole)
    torch_force = state.getForces()
    torch_force = np.array(torch_force.value_in_unit(unit.kilojoule_per_mole/unit.nanometer))

    if (i + 1) % 50 == 0:
        print(f"step: {i:>5}, torch_energy: {torch_energy:>10.2f} kJ/mol, torch_force[0,0]: ", torch_force[0,0])

Versioning?

How are we planning to version openmm-torch, and for users to know which versions of openmm-torch match with OpenMM?

Right now, OpenMM uses x.y.z for version numbers, but without true semantic versioning, meaning API breaking changes can happen at y increments (e.g. 7.6.0 eliminated much of the Amoeba API and broke valid uses of the Python API). openmm-torch is using x.y, where we are just incrementing y for performance enhancement, bugfixes, etc.

How do we want to do versioning going forward, and is there any way to sensibly communicate which versions are intended to be compatible with which versions of OpenMM?

Protein unfolds during hybrid ML/MM simulations

Hi,

I'm trying to run hybrid ML/MM simulations of a protein-ligand system in which the ML model is used for the ligand and a handful of binding site residues. However, certain parts of the protein binding site unfold very quickly, whereas this did not happen when the ML model was used on only the ligand. I was just generally wondering if these ML models can be used for only certain atoms within the same macromolecule, or if they can (currently) only be used for the entirety of a molecule?
Here is a part of the code for the production simulation:

from __future__ import print_function
from simtk.openmm import *
from simtk.openmm.app import *
from parmed.openmm import *
from simtk import unit
from sys import stdout
import sys
from openmmml import MLPotential

def production(prmtop_file, crd_file, positions, velocities):
    prmtop = AmberPrmtopFile(prmtop_file)
    inpcrd = AmberInpcrdFile(crd_file)
    #use ANI-2x potential for ML system
    potential = MLPotential('ani2x')
    #index ligand atoms and binding site residue atoms in the solvated Tyk2+ligand system
    ml_atoms=[atom.index for atom in prmtop.topology.atoms() if atom.residue.id== "289"
             or atom.residue.id== "14"
             or atom.residue.id== "95"
             or atom.residue.id== "138"
             or atom.residue.id== "151"
             or atom.residue.id== "92"
             or atom.residue.id== "94"
             or atom.residue.id== "152"
             or atom.residue.id== "139"
             or atom.residue.id== "22"
             or atom.residue.id== "89"
             or atom.residue.id== "12"
             or atom.residue.id== "24"
             or atom.residue.id== "91"
             or atom.residue.id== "93"
             or atom.residue.id== "15"
             or atom.residue.id== "141"
             or atom.residue.id== "90"
             or atom.residue.id== "71"
             or atom.residue.id== "16"
             or atom.residue.id== "17"
             or atom.residue.id== "39"
             or atom.residue.id== "96"]
    #create traditional MM system from topology file
    mm_system = prmtop.createSystem(nonbondedMethod=PME,
    nonbondedCutoff=1.0*unit.nanometers, constraints=HBonds, rigidWater=True,
    ewaldErrorTolerance=0.0005)
    #apply ANI-2x ML potential to the ligand atoms
    ml_system = potential.createMixedSystem(prmtop.topology, mm_system, ml_atoms)
    # NPT for production:
    ml_system.addForce(MonteCarloBarostat(1*unit.atmospheres, 300*unit.kelvin, 25))

Update CI versions

Update the CI to build and test with the latest version PyTorch and CUDA Toolkit.

conda install help wanted

I am having trouble conda installing the package. Coudl someone help me understand what is the problem.

I need pytorch 1.12 and cudatoolkit 11.3 for my other package to work, that is the only limitation. Is it causing the problem?

image

Making Neighborlist accessible

I trained a model using the neural network from SchNetPack. This is a AtomisticModel, https://github.com/atomistic-machine-learning/schnetpack/blob/master/src/schnetpack/atomistic/model.py. I would like to use this model in OpenMM for molecular modeling in python. The model requires not only the positions of the atoms, but also a neighborlist given a cutoff. Is it possible to acces the internal neighborlist of OpenMM. Otherwise i would need to use the neighborlist implemented in Schnetpack in TorchForce. This list would then update in every iteration, which is of course not necessary.
Therefore the question: Is it possible to solve this in a semi-efficient way, or is is necessary to rebuild the best Schnet model, using NNpops?

Set up continuous integration

@jaimergp : Any ideas on how we might be able to set up CI here now that we are able to install openmm an pytorch from conda-forge?

In principle, we should at least be able to test the Reference and OpenCL platforms, and we may even be able to test the CUDA platform if we wanted to wire in @swails Jenkins until GPU CI becomes available.

Problem with the CUDA platform

Hi Peter,

I met with some issues when running this plugin with the CUDA simulation platform of OpenMM.

The test script looks like this:

import simtk.openmm as mm
import simtk.unit as unit
import openmmnn as nn

system = mm.System()
for i in range(3):
    system.addParticle(1.0)
f = nn.NeuralNetworkForce('central.pt')
system.addForce(f)
integrator = mm.VerletIntegrator(0.001)
platform = mm.Platform.getPlatformByName('CUDA')
context = mm.Context(system, integrator, platform)
positions = [mm.Vec3(3, 0, 0), mm.Vec3(0, 4, 0), mm.Vec3(3, 4, 0)]
context.setPositions(positions)
print(context.getState(getEnergy=True).getPotentialEnergy())

The central.pt file comes from tests/central.pt.

When running with CPU platform, the correct answer was returned; while on the CUDA platform, it ended up with this traceback:

(simu) [yaoyic@yaoyi-pc test-openmm-torch]$ python test_model.py 
Traceback (most recent call last):
  File "test_model.py", line 15, in <module>
    print(context.getState(getEnergy=True).getPotentialEnergy())
  File "/home/yaoyic/miniconda3/envs/simu/lib/python3.7/site-packages/simtk/openmm/openmm.py", line 11060, in getState
    state = _openmm.Context_getState(self, types, enforcePeriodicBox, groups_mask)
Exception: Error invoking kernel: CUDA_ERROR_INVALID_HANDLE (400) 

The testing machine comes with an RTX 2060 super, driver version 430.40, CUDA version 10.1.243, openmm version py37_cuda101_rc_1, and libtorch version 1.3.0.

`TorchForce`-augmented system has inconsistent forces

In attempting to make a TorchForce with a scale factor (as a global parameter) that acts on a subset of particles in a system, I encountered dynamics that nan pretty readily. Upon taking a closer look, I realized that there might be a problem in the way that the force matrix is computed?

To explain, I took a host-guest system (mm_system) from openmmtools, deepcopied it ( ml_system) and to the ml_system added a TorchForce which is a torchani Module that acts only on the guest atoms. To the module, I added a multiplicative scaling term that is defined by a global parameter. Upon defaulting the scale factor to zero, I computed the energies of the two systems (with the same positions) and found their energy discrepancies to be tolerable (a few thousandths of a kcal/mol), but I found that the force matrices are very different (should be non-discrepant, as well). I also noticed that the discrepant entries in the force matrix from the ml_system context don't even align with the particle entries that the TorchForce is acting on. The gist is attached here. Any ideas? or am i doing something wrong?

Also, (not shown), when all of the forces (except the HarmonicBondForce) are removed from the systems, the force matrices do agree, and when the scale factor is nonzero, the force matrix of the ml_system has all zero entries (as expected) except at the entries of the subset of particles on which the TorchForce is acting (which is consistent with what should happen).

Simplify the creation of `TorchForce`

In general, the creation of TorchForce goes like this:

import torch as pt
from openmmtorch import TorchForce

# 1. create a PyTorch module
class Module(pt.nn.Module):
    def __init__(self):
        super().__init__();
    def forward(self, positions):
        return pt.sum(positions)

# 2. save the module to a file
module = Module()
pt.jit.script(module).save('module.pt')

# 3. create a force from the file
force = TorchForce('module.pt')

We could simplify the last two steps by allowing to pass an instance of Module directly to TorchForce:

force = TorchForce(module)

For this, we need to solve #65.

Allow the ML model to compute forces explicitly

Currently the atomic forces are computed implicitly by differentiating the potential energy returned by the ML model.

We should allow the ML model to compute force explicitly (which aren't necessary the derivatives of the potential energy). This would facilitate the implementation of some ML-based enhanced sampling algorithms (openmm/openmm#3268).

Problems on Macs

I recently got an M1 Mac. In trying to get this plugin working on it, I encountered some very rough edges. We should figure out how to resolve them, or else they'll cause a lot of problems for users.

The first roadblock I ran into was a linker error trying to build the plugin: it couldn't find the library libopenblas.dylib (which is required by PyTorch), even though I have the libopenblas conda package installed. With some searching I found this issue which partly explains the problem: the actual library is called libopenblas.0.dylib. The solution suggested there is to create a symlink to it with the other name. That works, but we can't really ask users to do that.

I haven't been able to figure out where it's getting the incorrect name from. It doesn't appear anywhere in the CMake files either for this library or for PyTorch. Running otool -L on the PyTorch libraries shows that all of them refer to it by the correct name. But somehow CMake is getting the incorrect name and putting it in the makefiles it generates.

The second problem I encountered was that it couldn't find libtorch.dylib at runtime, and therefore couldn't load the plugin. This was a library path issue. Most libraries for conda packages are put in the lib directory for the environment, which is automatically added to the library path. But PyTorch instead puts its libraries in site-packages/torch/lib, which is not added automatically. I was able to work around it by setting DYLD_LIBRARY_PATH in the terminal, but that isn't a convenient solution for users.

Positions from Torch GPU Tensors

Hi @peastman,

We were thinking about ways to assign context positions directly from torch GPU tensors in order to speed up batch processing of force and energy calculations for small systems. The kernels in this plugin, like copyInputs, seem to already provide a blueprint and at first sight it looks pretty straightforward to expose something like setContextPositionsFromTensor(Context& context, torch::Tensor& positions) to the API. I believe that this would be a worthwhile extension that could be useful to others. (Of course, it is not directly related to the main purpose of this plugin but I am sure that you have already thought about this.) Do you see any potential caveats/problems that I am currently overlooking?

Tagging @yaoyic

Thanks,
Andreas

Maybe some typos in the code

Hi Peter,

during my compiling the code on the 3rd commit, I found something preventing the make process:
For file platforms/reference/src/ReferenceNeuralNetworkKernels.cpp:

[ 39%] Building CXX object platforms/reference/CMakeFiles/OpenMMNNReference.dir/src/ReferenceNeuralNetworkKernels.cpp.o
/srv/public/yaoyic/openmm-torch/platforms/reference/src/ReferenceNeuralNetworkKernels.cpp: In function ‘std::vector<OpenMM::Vec3>& extractPositions(OpenMM::ContextImpl&)’:
/srv/public/yaoyic/openmm-torch/platforms/reference/src/ReferenceNeuralNetworkKernels.cpp:44:19: error: ‘void*’ is not a pointer-to-object type
     return *data->positions;
                   ^~~~~~~~~
/srv/public/yaoyic/openmm-torch/platforms/reference/src/ReferenceNeuralNetworkKernels.cpp: In function ‘std::vector<OpenMM::Vec3>& extractForces(OpenMM::ContextImpl&)’:
/srv/public/yaoyic/openmm-torch/platforms/reference/src/ReferenceNeuralNetworkKernels.cpp:49:19: error: ‘void*’ is not a pointer-to-object type
     return *data->forces;
                   ^~~~~~
/srv/public/yaoyic/openmm-torch/platforms/reference/src/ReferenceNeuralNetworkKernels.cpp: In function ‘OpenMM::Vec3* extractBoxVectors(OpenMM::ContextImpl&)’:
/srv/public/yaoyic/openmm-torch/platforms/reference/src/ReferenceNeuralNetworkKernels.cpp:54:18: error: invalid conversion from ‘void*’ to ‘OpenMM::Vec3*’ [-fpermissive]
     return data->periodicBoxVectors;
            ~~~~~~^~~~~~~~~~~~~~~~~~
platforms/reference/CMakeFiles/OpenMMNNReference.dir/build.make:75: die Regel für Ziel „platforms/reference/CMakeFiles/OpenMMNNReference.dir/src/ReferenceNeuralNetworkKernels.cpp.o“ scheiterte
make[2]: *** [platforms/reference/CMakeFiles/OpenMMNNReference.dir/src/ReferenceNeuralNetworkKernels.cpp.o] Fehler 1

After comparing to the counter part in the TF plugin, I guess here it should be changed into:

static vector<Vec3>& extractPositions(ContextImpl& context) {
    ReferencePlatform::PlatformData* data = reinterpret_cast<ReferencePlatform::PlatformData*>(context.getPlatformData());
    return *((vector<Vec3>*) data->positions);
}

static vector<Vec3>& extractForces(ContextImpl& context) {
    ReferencePlatform::PlatformData* data = reinterpret_cast<ReferencePlatform::PlatformData*>(context.getPlatformData());
    return *((vector<Vec3>*) data->forces);
}

static Vec3* extractBoxVectors(ContextImpl& context) {
    ReferencePlatform::PlatformData* data = reinterpret_cast<ReferencePlatform::PlatformData*>(context.getPlatformData());
    return (Vec3*) data->periodicBoxVectors;
}

And during make PythonInstall, this popped up:

Error copying file (if different) from "/srv/public/yaoyic/openmm-torch/python/nnpops.py" to "/srv/public/yaoyic/openmm-torch/build/python/nnpops.py".

so I guess this part in file openmm-torch/python/CMakeLists.txt:

COMMAND "${CMAKE_COMMAND}" -E copy_if_different "${CMAKE_CURRENT_SOURCE_DIR}/nnpops.py" "${CMAKE_CURRENT_BINARY_DIR}/nnpops.py"

and this part in file openmm-torch/python/setup.py should be removed:

setup(name='nnpops',
      version='1.0',
      py_modules=['nnpops'],
     )

After changing the files as has been described, I could make the plugin.

Slow speed with torchANI simulations on the GPU

Hi, I benchmarked a simple system with GAFF and torchANI potentials on two different GPUs: GTX Titan X (Maxwell) and RTX 3090 (Ampere). For GAFF, I'm getting a 2x speed on the 3090 compared to Titan X (see table below). However, the two GPUs achieved very similar speeds when running with the torchANI potential. Is this something I should expect for simulations with torchANI potential or openmm-torch in general? Could the molecule be too small to utilize the GPU resources?

GPU GAFF (ns/day) torchANI (ns/day)
GTX Titan X 490 5.9
RTX 3090 970 7.7

My test system is a butane molecule in a water box (1000 water molecules) with the GAFF force field. I minimized and equilibrated the system first with GAFF before running simulations with the torchANI potential. butane-benchmark.zip

Setting velocities trigger an exception

I tried to worn on #59 and got into an issue.

This is a simplified script:

import numpy as np
import torch as pt
from openmm import Context, Platform, System, VerletIntegrator
from openmmtorch import TorchForce

class NNP(pt.nn.Module):
  def __init__(self):
    super().__init__()

  def forward(self, positions):
    return pt.sum(positions)

pt.jit.script(NNP()).save('model.pt')

num_atoms = 3
system = System()
for _ in range(num_atoms):
    system.addParticle(1.0)
system.addForce(TorchForce('model.pt'))

integrator = VerletIntegrator(1.0)

context = Context(system, integrator, Platform.getPlatformByName('CUDA'))
context.setPositions(np.random.rand(num_atoms, 3))
context.setVelocitiesToTemperature(300)

integrator.step(1000)

which triggers an exception:

Warning: importing 'simtk.openmm' is deprecated.  Import 'openmm' instead.
Traceback (most recent call last):
  File "test.py", line 25, in <module>
    context.setVelocitiesToTemperature(300)
  File "/shared2/raimis/opt/miniconda/envs/ot_tut/lib/python3.7/site-packages/openmm/openmm.py", line 16068, in setVelocitiesToTemperature
    return _openmm.Context_setVelocitiesToTemperature(self, *args)
openmm.OpenMMException: The autograd engine was called while holding the GIL. If you are using the C++ API, the autograd engine is an expensive operation that does not require the GIL to be held so you should release it with 'pybind11::gil_scoped_release no_gil;'. If you are not using the C++ API, please report a bug to the pytorch team.
Exception raised from execute at /home/conda/feedstock_root/build_artifacts/pytorch-recipe_1635005512693/work/torch/csrc/autograd/python_engine.cpp:123 (most recent call first):
frame #0: c10::Error::Error(c10::SourceLocation, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >) + 0x6a (0x7f1114a3198a in /shared2/raimis/opt/miniconda/envs/ot_tut/lib/python3.7/site-packages/torch/lib/libc10.so)
frame #1: c10::detail::torchCheckFail(char const*, char const*, unsigned int, char const*) + 0xd4 (0x7f1114a2d494 in /shared2/raimis/opt/miniconda/envs/ot_tut/lib/python3.7/site-packages/torch/lib/libc10.so)
frame #2: torch::autograd::python::PythonEngine::execute(std::vector<torch::autograd::Edge, std::allocator<torch::autograd::Edge> > const&, std::vector<at::Tensor, std::allocator<at::Tensor> > const&, bool, bool, bool, std::vector<torch::autograd::Edge, std::allocator<torch::autograd::Edge> > const&) + 0xb8 (0x7f11606ff718 in /shared2/raimis/opt/miniconda/envs/ot_tut/lib/python3.7/site-packages/torch/lib/libtorch_python.so)
frame #3: <unknown function> + 0x343921a (0x7f115c56f21a in /shared2/raimis/opt/miniconda/envs/ot_tut/lib/python3.7/site-packages/torch/lib/libtorch_cpu.so)
frame #4: torch::autograd::backward(std::vector<at::Tensor, std::allocator<at::Tensor> > const&, std::vector<at::Tensor, std::allocator<at::Tensor> > const&, c10::optional<bool>, bool, std::vector<at::Tensor, std::allocator<at::Tensor> > const&) + 0x6a (0x7f115c5704da in /shared2/raimis/opt/miniconda/envs/ot_tut/lib/python3.7/site-packages/torch/lib/libtorch_cpu.so)
frame #5: <unknown function> + 0x349cb77 (0x7f115c5d2b77 in /shared2/raimis/opt/miniconda/envs/ot_tut/lib/python3.7/site-packages/torch/lib/libtorch_cpu.so)
frame #6: at::Tensor::_backward(c10::ArrayRef<at::Tensor>, c10::optional<at::Tensor> const&, c10::optional<bool>, bool) const + 0x4a (0x7f1159d156ea in /shared2/raimis/opt/miniconda/envs/ot_tut/lib/python3.7/site-packages/torch/lib/libtorch_cpu.so)
frame #7: TorchPlugin::CudaCalcTorchForceKernel::execute(OpenMM::ContextImpl&, bool, bool) + 0xe3f (0x7f10d0a8408f in /shared2/raimis/opt/miniconda/envs/ot_tut/lib/plugins/libOpenMMTorchCUDA.so)
frame #8: OpenMM::ContextImpl::calcForcesAndEnergy(bool, bool, int) + 0xc9 (0x7f10d2d0f6e9 in /shared2/raimis/opt/miniconda/envs/ot_tut/lib/python3.7/site-packages/openmm/../../../libOpenMM.so.7.7)
frame #9: OpenMM::Context::setVelocitiesToTemperature(double, int) + 0xd5 (0x7f10d2d0cdc5 in /shared2/raimis/opt/miniconda/envs/ot_tut/lib/python3.7/site-packages/openmm/../../../libOpenMM.so.7.7)
frame #10: <unknown function> + 0x101324 (0x7f10d3089324 in /shared2/raimis/opt/miniconda/envs/ot_tut/lib/python3.7/site-packages/openmm/_openmm.cpython-37m-x86_64-linux-gnu.so)
<omitting python frames>
frame #25: __libc_start_main + 0xf5 (0x7f119c3c6555 in /lib64/libc.so.6)

The issue happens when setting setting velocities:

context.setVelocitiesToTemperature(300)

If I comment that line, the script works.

Serialization of `TorchForce` is not self-contained

When serializing TorchForce, the file name of the PyTorch model is save rather its content:

from openmmtorch import TorchForce
from openmm import XmlSerializer

force = TorchForce('filename.pt')
print(XmlSerializer.serialize(force))
<?xml version="1.0" ?>
<Force file="filename.pt" forceGroup="0" outputsForces="0" type="TorchForce" usesPeriodic="0" version="1">
	<GlobalParameters/>
</Force>

So, it is a user responsibility to ensure that the right model file is in the right place during desalinization.

Ideally, the serialized object should be self-contained. We should save the file content, not it's name. Or, at least, we should store a hash of the file content and verify it during desalinization.

Allow torch model to be used as Integrator

I wonder if we could also support torch models used as Integrators in OpenMM.

Perhaps something like this could work:

import torch

class IntegratorModule(torch.nn.Module):
    """A BAOAB Langevin integrator"""
    def forward(self, positions, velocities, timestep, temperature):
        """The forward method returns the updated positions and velocities given the current timestep and temperature.

        Parameters
        ----------
        positions : torch.Tensor with shape (nparticles,3)
           positions[i,k] is the position (in nanometers) of spatial dimension k of particle i
        velocities : torch.Tensor with shape (nparticles,3)
           velocities[i,k] is the velocity (in nanometers/picosecond) of spatial dimension k of particle i
        masses : torch.Tensor with shape (nparticles,3)
           masses[i,k] is the position (in amu) of particle i for all k
        timestep : torch.Tensor with shape (,)
            the integration timestep (in femtoseconds)
        temperature : torch.Tensor with shape (,)
            the temperature in kelvin)

        Returns
        -------
        positions : torch.Tensor with shape (nparticles,3)
           positions[i,k] is the position (in nanometers) of spatial dimension k of particle i
        velocities : torch.Tensor with shape (nparticles,3)
           velocities[i,k] is the velocity (in nanometers/picosecond) of spatial dimension k of particle i
        """
        forces = openmm_compute_forces(positions)
        velocities = velocities + (timestep/2) * (forces/masses)
        positions = positions + timestep * velocities
        forces = openmm_compute_forces(positions)
        velocities = velocities + (timestep/2) * (forces/masses)

        return positions, velocities

# Render the compute graph to a TorchScript module
module = torch.jit.script(IntegratorModule())

# Serialize the compute graph to a file
module.save('integrator.pt')

Here, we would have to extend TorchScript with custom Ops openmm_compute_forces, openmm_compute_potential, and openmm_compute_potential_and_forces, which would wrap the normal OpenMM energy/force computation. Optimally, these C++ functions would know when the force or potential did not need to be recomputed (because no particles moved) if called at the end of one step and at the beginning of the next step.

To use the integrator in a simulation, the user would create a TorchIntegrator object that would behave much like a normal Integrator:

from openmm import unit
timestep = 4.0*unit.femtoseconds
temperature = 300*unit.kelvin

# Create the TorchIntegrator from the serialized compute graph
from openmmtorch import TorchIntegrator
torch_integrator = TorchIntegrator('integrator.pt', temperature, timestep)

# Create a Context with the integrator
context = openmm.Context(system, torch_integrator)

# Run some dynamics
torch_integrator.step(100)

# Change the temperature and timestep
torch_integrator.setTemperature(100*unit.kelvin)
torch_integrator.setTimestep(2.0*unit.femtoseconds)

Edit: It would also be important to enable the integrator to modify global parameters, as well as define its own that can be accessed through the OpenMM API. I'm not quite sure how that would work, however.

Error accessing energy

Hi OpenMM team,

First of all, many thanks for your fantastic work on the OpenMM suite of projects!

I have been trying to get OpenMM-Torch to work on a couple of test systems, but I seem to be struggling with a particular error whenever I try to access the energy, be it directly via context.getState(getEnergy=True), or even with the Reporter objects when I use the higher-level API. The error in particular is:

Traceback (most recent call last):
  File "/homes/rubiera/Downloads/FoldSet/bug.py", line 46, in <module>
    print(context.getState(getEnergy=True).getPotentialEnergy())
  File "/data/gull/rubiera/anaconda3/envs/ani/lib/python3.9/site-packages/simtk/openmm/openmm.py", line 4888, in getState
    state = _openmm.Context_getState(self, types, enforcePeriodicBox, groups_mask)
simtk.openmm.OpenMMException: The autograd engine was called while holding the GIL. If you are using the C++ API, the autograd engine is an expensive operation that does not require the GIL to be held so you should release it with 'pybind11::gil_scoped_release no_gil;'. If you are not using the C++ API, please report a bug to the pytorch team.
Exception raised from execute at /tmp/pip-req-build-2handpz9/torch/csrc/autograd/python_engine.cpp:111 (most recent call first):
frame #0: c10::Error::Error(c10::SourceLocation, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >) + 0x6a (0x7f02d3b24dba in /data/gull/rubiera/anaconda3/envs/ani/lib/python3.9/site-packages/torch/lib/libc10.so)
frame #1: c10::detail::torchCheckFail(char const*, char const*, unsigned int, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) + 0xd8 (0x7f02d3b21338 in /data/gull/rubiera/anaconda3/envs/ani/lib/python3.9/site-packages/torch/lib/libc10.so)
frame #2: torch::autograd::python::PythonEngine::execute(std::vector<torch::autograd::Edge, std::allocator<torch::autograd::Edge> > const&, std::vector<at::Tensor, std::allocator<at::Tensor> > const&, bool, bool, bool, std::vector<torch::autograd::Edge, std::allocator<torch::autograd::Edge> > const&) + 0x122 (0x7f0317ef6452 in /data/gull/rubiera/anaconda3/envs/ani/lib/python3.9/site-packages/torch/lib/libtorch_python.so)
frame #3: <unknown function> + 0x2e900f2 (0x7f03144a60f2 in /data/gull/rubiera/anaconda3/envs/ani/lib/python3.9/site-packages/torch/lib/libtorch_cpu.so)
frame #4: torch::autograd::backward(std::vector<at::Tensor, std::allocator<at::Tensor> > const&, std::vector<at::Tensor, std::allocator<at::Tensor> > const&, c10::optional<bool>, bool, std::vector<at::Tensor, std::allocator<at::Tensor> > const&) + 0x6a (0x7f03144a6afa in /data/gull/rubiera/anaconda3/envs/ani/lib/python3.9/site-packages/torch/lib/libtorch_cpu.so)
frame #5: <unknown function> + 0x3497f56 (0x7f0314aadf56 in /data/gull/rubiera/anaconda3/envs/ani/lib/python3.9/site-packages/torch/lib/libtorch_cpu.so)
frame #6: at::Tensor::_backward(c10::ArrayRef<at::Tensor>, c10::optional<at::Tensor> const&, c10::optional<bool>, bool) const + 0x1c9 (0x7f0312b6adb9 in /data/gull/rubiera/anaconda3/envs/ani/lib/python3.9/site-packages/torch/lib/libtorch_cpu.so)
frame #7: TorchPlugin::ReferenceCalcTorchForceKernel::execute(OpenMM::ContextImpl&, bool, bool) + 0x7cd (0x7f029527d85d in /data/gull/rubiera/anaconda3/envs/ani/lib/plugins/libOpenMMTorchReference.so)
frame #8: OpenMM::ContextImpl::calcForcesAndEnergy(bool, bool, int) + 0xca (0x7f0295a0fc8a in /data/gull/rubiera/anaconda3/envs/ani/lib/python3.9/site-packages/simtk/openmm/../../../../libOpenMM.so.7.5)
frame #9: OpenMM::Context::getState(int, bool, int) const + 0x15a (0x7f0295a0e78a in /data/gull/rubiera/anaconda3/envs/ani/lib/python3.9/site-packages/simtk/openmm/../../../../libOpenMM.so.7.5)
frame #10: <unknown function> + 0x169148 (0x7f0295e2c148 in /data/gull/rubiera/anaconda3/envs/ani/lib/python3.9/site-packages/simtk/openmm/_openmm.cpython-39-x86_64-linux-gnu.so)
<omitting python frames>
frame #27: __libc_start_main + 0xf2 (0x7f034d1ec082 in /lib64/libc.so.6)

The code to reproduce this, under OpenMM 7.5 with OpenMM-Torch 1.0, both installed fom conda-forge, is:

import torch
import numpy as np
from sys import stdout
from simtk.unit import *
from simtk.openmm import *
from simtk.openmm.app import *
from openmmtorch import TorchForce


class ForceModule(torch.nn.Module):
    """A central harmonic potential as a static compute graph"""
    def forward(self, positions):
        """The forward method returns the energy computed from positions.

        Parameters
        ----------
        positions : torch.Tensor with shape (nparticles,3)
           positions[i,k] is the position (in nanometers) of spatial dimension k of particle i

        Returns
        -------
        potential : torch.Scalar
           The potential energy (in kJ/mol)
        """
        return torch.sum(positions**2)

# Create system
system = System()
for _ in range(500):
    system.addParticle(1.)
positions = Quantity(np.random.uniform(high=100, size=[500, 3]), angstrom)

# Set up force
module = torch.jit.script(ForceModule())
module.save('model.pt')
torch_force = TorchForce('model.pt')
system.addForce(torch_force)

# Run dynamics
integrator = LangevinIntegrator(300*kelvin, 1/picosecond, 0.001*picosecond)
context = Context(system, integrator)
context.setPositions(positions)
LocalEnergyMinimizer.minimize(context)
for _ in range(100):
    integrator.step(50)
    print(context.getState(getEnergy=True).getPotentialEnergy())

At first glance, the traceback suggests that the error is coming from the Torch library, rather than from the OpenMM bindings. Is there a solution to this problem? Is there a more idiomatic way to access the energy, without conflicting with Torch?

Best wishes,

Multiple simulations on the same GPU are interfering with each other!

If two or more simulations are running on the same GPU and using TorchForce, they produce incorrect results. Other GPU processes (i.e. VMD) also affect TorchForce, but the effects are less deterministic. Typically this manifests as random "explosions" of the system.

Versions:

The problem can be reproduced with:

from sys import stdout
from simtk import unit as u
import simtk.openmm as mm
from simtk.openmm import app

system = mm.System()
system.addParticle(1)

if True:
    import torch
    from openmmtorch import TorchForce

    class Force(torch.nn.Module):
        def forward(self, positions):
            return torch.sum(positions**2)
    torch.jit.script(Force()).save('model.pt')

    system.addForce(TorchForce('model.pt'))

else:
    force = mm.CustomExternalForce('x^2+y^2+z^2')
    force.addParticle(0)
    system.addForce(force)

integrator = mm.VerletIntegrator(1*u.femtosecond)
platform = mm.Platform.getPlatformByName('CUDA')
properties = {'CudaPrecision': 'mixed', 'DeviceIndex': '2'}

simulation = app.Simulation(app.Topology(), system, integrator, platform, properties)
simulation.context.setPositions([[1, 2, 3]])
simulation.reporters.append(app.StateDataReporter(stdout, 1000, step=True, totalEnergy=True, speed=True, separator='\t'))
simulation.step(100000)

If only one simulation is running, the total energy is conserved as expected:

#"Step" "Total Energy (kJ/mole)"        "Speed (ns/day)"
1000    14.000000179612432      0
2000    14.000005361777896      349
3000    14.000001366072164      367
4000    14.000005119669993      362
5000    14.000003331103173      336
6000    14.000002685719684      325
7000    14.000005601208187      328
8000    14.000000713319633      330
9000    14.000007006230264      338
10000   14.000000022274946      344
11000   14.00000683213267       349
12000   14.000000738332375      353
13000   14.00000512025951       350
14000   14.00000300080587       347
15000   14.00000395686458       332
16000   14.000004841749305      324
17000   14.000001304157916      318
18000   14.000006327369546      309
19000   14.000000236521004      303
20000   14.000007625364647      296
...

If two simulations are running, the energy conservation degrades:

#"Step" "Total Energy (kJ/mole)"        "Speed (ns/day)"
1000    14.000159708061787      0
2000    14.000443962602837      345
3000    14.000840991380645      335
4000    14.000969743602514      346
5000    14.001341178967788      344
6000    14.00135998002807       328
7000    14.001780500680749      313
8000    14.001861254934145      303
9000    14.001974845722069      297
10000   14.002129244844477      297
11000   14.002374428011779      299
12000   13.9968509309506        296
13000   14.020662965297788      291
14000   14.044255590957363      290
15000   14.058228920702625      291
16000   14.037448129767334      290
17000   14.030292677709525      283
18000   14.034618070407525      280
19000   14.037064627386965      278
20000   14.034805715731942      274
...
#"Step" "Total Energy (kJ/mole)"        "Speed (ns/day)"
1000    14.007740775328157      0
2000    14.015824312238987      260
3000    14.0087181449308        279
4000    14.022696349015083      284
5000    14.010502216207124      275
6000    14.012584191982722      265
7000    14.026440971902506      260
8000    14.030638631492987      260
9000    14.038191858222456      257
10000   14.043751931821303      252
11000   14.051324684105513      253
12000   14.055297832296048      252
13000   14.060540097186657      249
14000   14.08421743769247       250
15000   14.107282767704632      252
16000   14.101948425425924      253
17000   14.112720479074394      253
18000   14.130359066794343      252
19000   14.136352142606668      252
20000   14.146669114115653      252
...

For reference, I tried the same setup with CustomExternalForce. No problems have been observed!

NNPOps linking issue

openmm-torch must be linked to NNPOps to be able to use it in torch runtime.

We need to find some way to dynamically load NNPOps library so symbols are available at the right now.

@raimis can correct this later

can't install from source following the instructions

Hi everyone!

I am trying to compile openmm-torch to use with the omnia-dev openMM installation, but I keep running into issues.
I think I experience exactly the same issue as described here, but neither of the two solutions outlined here resolve the issue.

The error I get is:

[  3%] Building CXX object CMakeFiles/OpenMMTorch.dir/openmmapi/src/TorchForce.cpp.o
[  6%] Building CXX object CMakeFiles/OpenMMTorch.dir/openmmapi/src/TorchForceImpl.cpp.o
[ 10%] Building CXX object CMakeFiles/OpenMMTorch.dir/serialization/src/TorchForceProxy.cpp.o
[ 13%] Building CXX object CMakeFiles/OpenMMTorch.dir/serialization/src/TorchSerializationProxyRegistration.cpp.o
[ 16%] Linking CXX shared library libOpenMMTorch.so
[ 16%] Built target OpenMMTorch
[ 20%] CMake-copying file /data/shared/software/openmm-torch/tests/central.pt to /data/shared/software/openmm-torch/build/tests/central.pt
[ 23%] CMake-copying file /data/shared/software/openmm-torch/tests/forces.pt to /data/shared/software/openmm-torch/build/tests/forces.pt
[ 26%] CMake-copying file /data/shared/software/openmm-torch/tests/global.pt to /data/shared/software/openmm-torch/build/tests/global.pt
[ 30%] CMake-copying file /data/shared/software/openmm-torch/tests/periodic.pt to /data/shared/software/openmm-torch/build/tests/periodic.pt
[ 30%] Built target CopyTestFiles
[ 33%] Building CXX object serialization/tests/CMakeFiles/TestSerializeTorchForce.dir/TestSerializeTorchForce.cpp.o
[ 36%] Linking CXX executable ../../TestSerializeTorchForce
CMakeFiles/TestSerializeTorchForce.dir/TestSerializeTorchForce.cpp.o: In function `testSerialization()':
TestSerializeTorchForce.cpp:(.text+0x376): undefined reference to `OpenMM::throwException(char const*, int, std::string const&)'
TestSerializeTorchForce.cpp:(.text+0x463): undefined reference to `OpenMM::throwException(char const*, int, std::string const&)'
TestSerializeTorchForce.cpp:(.text+0x550): undefined reference to `OpenMM::throwException(char const*, int, std::string const&)'
TestSerializeTorchForce.cpp:(.text+0x681): undefined reference to `OpenMM::throwException(char const*, int, std::string const&)'
TestSerializeTorchForce.cpp:(.text+0x79a): undefined reference to `OpenMM::throwException(char const*, int, std::string const&)'
CMakeFiles/TestSerializeTorchForce.dir/TestSerializeTorchForce.cpp.o:TestSerializeTorchForce.cpp:(.text+0x8ac): more undefined references to `OpenMM::throwException(char const*, int, std::string const&)' follow
CMakeFiles/TestSerializeTorchForce.dir/TestSerializeTorchForce.cpp.o: In function `void OpenMM::XmlSerializer::serialize<TorchPlugin::TorchForce>(TorchPlugin::TorchForce const*, std::string const&, std::ostream&)':
TestSerializeTorchForce.cpp:(.text._ZN6OpenMM13XmlSerializer9serializeIN11TorchPlugin10TorchForceEEEvPKT_RKSsRSo[_ZN6OpenMM13XmlSerializer9serializeIN11TorchPlugin10TorchForceEEEvPKT_RKSsRSo]+0x66): undefined reference to `OpenMM::SerializationNode::setName(std::string const&)'
TestSerializeTorchForce.cpp:(.text._ZN6OpenMM13XmlSerializer9serializeIN11TorchPlugin10TorchForceEEEvPKT_RKSsRSo[_ZN6OpenMM13XmlSerializer9serializeIN11TorchPlugin10TorchForceEEEvPKT_RKSsRSo]+0xc9): undefined reference to `OpenMM::SerializationNode::hasProperty(std::string const&) const'
TestSerializeTorchForce.cpp:(.text._ZN6OpenMM13XmlSerializer9serializeIN11TorchPlugin10TorchForceEEEvPKT_RKSsRSo[_ZN6OpenMM13XmlSerializer9serializeIN11TorchPlugin10TorchForceEEEvPKT_RKSsRSo]+0x100): undefined reference to `OpenMM::SerializationProxy::getTypeName() const'
TestSerializeTorchForce.cpp:(.text._ZN6OpenMM13XmlSerializer9serializeIN11TorchPlugin10TorchForceEEEvPKT_RKSsRSo[_ZN6OpenMM13XmlSerializer9serializeIN11TorchPlugin10TorchForceEEEvPKT_RKSsRSo]+0x150): undefined reference to `OpenMM::SerializationProxy::getTypeName() const'
TestSerializeTorchForce.cpp:(.text._ZN6OpenMM13XmlSerializer9serializeIN11TorchPlugin10TorchForceEEEvPKT_RKSsRSo[_ZN6OpenMM13XmlSerializer9serializeIN11TorchPlugin10TorchForceEEEvPKT_RKSsRSo]+0x18d): undefined reference to `OpenMM::SerializationNode::setStringProperty(std::string const&, std::string const&)'
../../libOpenMMTorch.so: undefined reference to `OpenMM::SerializationNode::setBoolProperty(std::string const&, bool)'
../../libOpenMMTorch.so: undefined reference to `OpenMM::SerializationNode::getDoubleProperty(std::string const&) const'
../../libOpenMMTorch.so: undefined reference to `torch::jit::load(std::string const&, c10::optional<c10::Device>)'
../../libOpenMMTorch.so: undefined reference to `OpenMM::SerializationProxy::SerializationProxy(std::string const&)'
../../libOpenMMTorch.so: undefined reference to `OpenMM::SerializationNode::getBoolProperty(std::string const&) const'
../../libOpenMMTorch.so: undefined reference to `OpenMM::SerializationNode::getName() const'
../../libOpenMMTorch.so: undefined reference to `OpenMM::SerializationNode::setDoubleProperty(std::string const&, double)'
../../libOpenMMTorch.so: undefined reference to `OpenMM::SerializationNode::getIntProperty(std::string const&, int) const'
../../libOpenMMTorch.so: undefined reference to `OpenMM::Platform::createKernel(std::string const&, OpenMM::ContextImpl&) const'
../../libOpenMMTorch.so: undefined reference to `c10::detail::torchInternalAssertFail(char const*, char const*, unsigned int, char const*, std::string const&)'
../../libOpenMMTorch.so: undefined reference to `OpenMM::SerializationNode::setIntProperty(std::string const&, int)'
../../libOpenMMTorch.so: undefined reference to `OpenMM::SerializationNode::getIntProperty(std::string const&) const'
../../libOpenMMTorch.so: undefined reference to `OpenMM::SerializationNode::getStringProperty(std::string const&) const'
../../libOpenMMTorch.so: undefined reference to `OpenMM::SerializationNode::createChildNode(std::string const&)'
collect2: error: ld returned 1 exit status
make[2]: *** [TestSerializeTorchForce] Error 1
make[1]: *** [serialization/tests/CMakeFiles/TestSerializeTorchForce.dir/all] Error 2
make: *** [all] Error 2

I have used the conda environment outlined here as build environment, I am using gcc (GCC) 7.3.1 20180303 (Red Hat 7.3.1-5) and the correct libtorch version as far as I can tell (libtorch-cxx11-abi-shared-with-deps-1.11.0+cu102.zip).
Any idea what I am doing wrong here? Any help is highly appreciated!

Changing a module's state from context

I am trying to run a metadynamics simulation where the collective variables are defined by a network. The ForceModule has two tensors which contain the heights and centres of the Gaussian bias terms, and I haven't been able to discover a way to add to these from the simulation context.

I've contemplated a few wrong ways, such as recompiling and substituting the module in the system, implementing the bias as a series of forces or abusing global parameters. Is there a right way?

The business part of the module looks like this:

    def forward(self, positions):
        x = self.featurize(positions)
        s = self.net(x)
        return self.bias_potential(s)

    def featurize(self, positions):
        return torch.tensor([
            torch.sqrt(torch.sum(torch.square(positions[j] - positions[i])))
            for (i, j) in self.atom_pairs
            ]).reshape(1, -1)

    def bias_potential(self, s):
        w = torch.zeros(1)
        for hi, si in zip(self.heights, self.centers):
            ds = s - si
            w += hi * torch.exp(-0.5 * ds @ self.inv_sigma @ ds.t()).flatten()
        return w.item()

Conda package

Currently there is no a conda package of OpenMM-Torch, but it would we very useful to simplify deployment.

Problems/progress to make a conda-forge-compatible package:

`make test` fails for multiple tests

I have build openmm-torch from source and I can run the Python example shown in README.
But since I am still having trouble here I wanted to make sure that openmm-torch behaves as expected and I tried to run make test.

(rew) [mwieder@a7srv5 build 💡 ](master)$ make test
Running tests...
Test project /data/shared/software/openmm-torch/build
    Start 1: TestSerializeTorchForce
1/8 Test #1: TestSerializeTorchForce ..........   Passed    0.20 sec
    Start 2: TestReferenceTorchForce
2/8 Test #2: TestReferenceTorchForce ..........***Failed    0.22 sec
    Start 3: TestOpenCLTorchForceSingle
3/8 Test #3: TestOpenCLTorchForceSingle .......***Failed    0.53 sec
    Start 4: TestOpenCLTorchForceMixed
4/8 Test #4: TestOpenCLTorchForceMixed ........***Failed    0.55 sec
    Start 5: TestOpenCLTorchForceDouble
5/8 Test #5: TestOpenCLTorchForceDouble .......***Failed    0.55 sec
    Start 6: TestCudaTorchForceSingle
6/8 Test #6: TestCudaTorchForceSingle .........***Failed    0.40 sec
    Start 7: TestCudaTorchForceMixed
7/8 Test #7: TestCudaTorchForceMixed ..........***Failed    0.37 sec
    Start 8: TestCudaTorchForceDouble
8/8 Test #8: TestCudaTorchForceDouble .........***Failed    0.40 sec

13% tests passed, 7 tests failed out of 8

Total Test time (real) =   3.28 sec

The following tests FAILED:
	  2 - TestReferenceTorchForce (Failed)
	  3 - TestOpenCLTorchForceSingle (Failed)
	  4 - TestOpenCLTorchForceMixed (Failed)
	  5 - TestOpenCLTorchForceDouble (Failed)
	  6 - TestCudaTorchForceSingle (Failed)
	  7 - TestCudaTorchForceMixed (Failed)
	  8 - TestCudaTorchForceDouble (Failed)
Errors while running CTest
Output from these tests are in: /data/shared/software/openmm-torch/build/Testing/Temporary/LastTest.log
Use "--rerun-failed --output-on-failure" to re-run the failed cases verbosely.
make: *** [Makefile:71: test] Error 8

a closer look at the log file shows the same error (until frame 9) for all 7 of the 8 tests:

2/8 Testing: TestReferenceTorchForce
2/8 Test: TestReferenceTorchForce
Command: "/data/shared/software/openmm-torch/build/TestReferenceTorchForce" "single"
Directory: /data/shared/software/openmm-torch/build
"TestReferenceTorchForce" start time: May 18 14:00 CEST
Output:
----------------------------------------------------------
exception: Legacy model format is not supported on mobile.
Exception raised from deserialize at /home/conda/feedstock_root/build_artifacts/pytorch-recipe_1650973827143/work/torch/csrc/jit/serialization/import.cpp:267 (most recent call first):
frame #0: c10::Error::Error(c10::SourceLocation, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >) + 0x68
 (0x7f692001dc78 in /data/shared/software/python_env/anaconda3/envs/rew/lib/python3.9/site-packages/torch/lib/libc10.so)
frame #1: c10::detail::torchCheckFail(char const*, char const*, unsigned int, char const*) + 0xf4 (0x7f691fffbb5b in /data/shared/softwa
re/python_env/anaconda3/envs/rew/lib/python3.9/site-packages/torch/lib/libc10.so)
frame #2: <unknown function> + 0x34c0b44 (0x7f694fa24b44 in /data/shared/software/python_env/anaconda3/envs/rew/lib/python3.9/site-packa
ges/torch/lib/libtorch_cpu.so)
frame #3: torch::jit::load(std::shared_ptr<caffe2::serialize::ReadAdapterInterface>, c10::optional<c10::Device>, std::unordered_map<std:
:__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, st
d::allocator<char> >, std::hash<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::equal_to<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::allocator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > >&) + 0x1c6 (0x7f694fa25b36 in /data/shared/software/python_env/anaconda3/envs/rew/lib/python3.9/site-packages/torch/lib/libtorch_cpu.so)
frame #4: torch::jit::load(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, c10::optional<c10::Device>, std::unordered_map<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::hash<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::equal_to<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::allocator<std::pair<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > > >&) + 0xc7 (0x7f694fa27517 in /data/shared/software/python_env/anaconda3/envs/rew/lib/python3.9/site-packages/torch/lib/libtorch_cpu.so)
frame #5: torch::jit::load(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, c10::optional<c10::Device>) + 0x6c (0x7f694fa275fc in /data/shared/software/python_env/anaconda3/envs/rew/lib/python3.9/site-packages/torch/lib/libtorch_cpu.so)
frame #6: TorchPlugin::TorchForceImpl::initialize(OpenMM::ContextImpl&) + 0x58 (0x7f69535889f8 in /data/shared/software/openmm-torch/build/libOpenMMTorch.so)
frame #7: OpenMM::ContextImpl::initialize() + 0x3c5 (0x7f69530ec6a5 in /data/shared/software/python_env/anaconda3/envs/rew/lib/libOpenMM.so.7.7)
frame #8: OpenMM::Context::Context(OpenMM::System const&, OpenMM::Integrator&, OpenMM::Platform&) + 0x7f (0x7f69530e53ff in /data/shared/software/python_env/anaconda3/envs/rew/lib/libOpenMM.so.7.7)
frame #9: /data/shared/software/openmm-torch/build/TestReferenceTorchForce() [0x403220]
frame #10: /data/shared/software/openmm-torch/build/TestReferenceTorchForce() [0x402d5d]
frame #11: __libc_start_main + 0xf3 (0x7f692008fca3 in /lib64/libc.so.6)
frame #12: /data/shared/software/openmm-torch/build/TestReferenceTorchForce() [0x402dfe]

Do you have any idea what is going on here?

Model tensors not being loaded correctly

I am not sure if this is an issue with how PyTorch saves Python JitScript models and loads them in C++, or if it is openmm-torch specific, but I am having issues with model tensors not being loaded correctly in openmm-torch. This is all tested with openmm-torch 0.03, OpenMM 7.5.1 and pytorch 1.8 from conda-forge.

A simple example is if the torch model includes some parameters that are converted to double precision during initialization:

self.l0 = torch.nn.Linear(n_in, 1).to(torch.double).cuda()

If I initialize the model and pass a positions tensor, self.l0 is still double precision. This is still true if I compile the model with jit script, as well as if I save the compiled model and reload it in python. However if I load the same model with TorchForce and add the force to the OpenMM system, self.l0 has been converted to single precision (even though the positions tensor passed by OpenMM is double). This is also observed when parameters are assigned to different cuda devices, where they will all be assigned to cuda:0 once loaded by openmm-torch.

I have created a repository that reproduce both the issue with precision and cuda devices described above, which can be found here.

Issue with OpenMM-torch installation with NNPOps and TorchANI

Hi,

Following the example , I installed the required packages openmm-torch, TorchAni and NNPOps with the command

conda create -n tmp-test -q -c conda-forge openmm-torch nnpops torchani openmmtools cudatoolkit=11.3

Importing torchani throws "undefined symbol" ImportError:

import torch as pt
from torchani.models import ANI2x
Traceback (most recent call last):
File "", line 1, in
File "/home/venkat/miniconda3/envs/tmp-test/lib/python3.9/site-packages/torchani/init.py", line 34, in
from .aev import AEVComputer
File "/home/venkat/miniconda3/envs/tmp-test/lib/python3.9/site-packages/torchani/aev.py", line 14, in
from . import cuaev # type: ignore # noqa: F401
ImportError: /home/venkat/miniconda3/envs/tmp-test/lib/python3.9/site-packages/torchani/cuaev.cpython-39-x86_64-linux-gnu.so: undefined symbol: _ZNK3c104Type14isSubtypeOfExtERKS0_PSo

Am I right that this is likely due to the different versions of pytorch these dependencies are built with? NNPOps and TorchANI have both been upgraded to work with PyTorch1.11 but openmm-torch seems to be with PyTorch 1.10 and pytorch-gpu-1.10. The above conda environment (tmp-test) has PyTorch1.10 and this makes it incompatible with torchANI and NNPOps and thus returns undefined symbol error when I attempt to import either of these packages. Any idea on how I can solve this and make all these work together?

These are some related topics I found useful: openmm/NNPOps#54, conda-forge/openmm-torch-feedstock#16, conda-forge/openmm-torch-feedstock#17, openmm/openmmexampleplugin#19.

I also tried compiling OpenMM 7.7 and OpenMM-torch from source but not much luck either. I did manage to get OpenMM installed, but then installing openmm-torch gave this following error:

conda create -n py39-nnpops -c conda-forge nnpops python=3.9

(py39-nnpops) venkat@ws:/home/venkat/testing/openmm-torch/build$ make install
Consolidate compiler generated dependencies of target OpenMMTorch
[ 16%] Built target OpenMMTorch
[ 30%] Built target CopyTestFiles
Consolidate compiler generated dependencies of target TestSerializeTorchForce
[ 33%] Linking CXX executable ../../TestSerializeTorchForce
/usr/bin/ld: ../../libOpenMMTorch.so: undefined reference to c10::detail::torchInternalAssertFail(char const*, char const*, unsigned int, char const*, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)' /usr/bin/ld: ../../libOpenMMTorch.so: undefined reference to torch::jit::load(std::__cxx11::basic_string<char, std::char_traits, std::allocator > const&, c10::optionalc10::Device)'
collect2: error: ld returned 1 exit status
make[2]: *** [serialization/tests/CMakeFiles/TestSerializeTorchForce.dir/build.make:112: TestSerializeTorchForce] Error 1
make[1]: *** [CMakeFiles/Makefile2:259: serialization/tests/CMakeFiles/TestSerializeTorchForce.dir/all] Error 2
make: *** [Makefile:146: all] Error 2

I am not sure if this is related to openmm/openmmexampleplugin#19, where @peastman refers to ABI inconsistency. I changed the flag "-D_GLIBCXX_USE_CXX11_ABI=0" to "-D_GLIBCXX_USE_CXX11_ABI=1" in my TorchConfig.cmake (libtorch/share/cmake/Torch/TorchConfig.cmake) hoping that would solve the installation issue but in vain.

Could you please help me with this. I'll be happy to provide any additional information if needed to reproduce this error. I am working on a Linux (Debian) workstation and with python 3.9.

Error when building from source

I followed the tutorial to build the plugins from source and had the following error when I run "make install".

Scanning dependencies of target CopyTestFiles
[ 3%] CMake-copying file /home/xqding/Downloads/openmm-torch-v.03/tests/central.pt to /home/xqding/Downloads/build_openmm_torch/tests/central.pt
[ 6%] CMake-copying file /home/xqding/Downloads/openmm-torch-v.03/tests/global.pt to /home/xqding/Downloads/build_openmm_torch/tests/global.pt
[ 10%] CMake-copying file /home/xqding/Downloads/openmm-torch-v.03/tests/periodic.pt to /home/xqding/Downloads/build_openmm_torch/tests/periodic.pt
[ 10%] Built target CopyTestFiles
Scanning dependencies of target OpenMMTorch
[ 13%] Building CXX object CMakeFiles/OpenMMTorch.dir/openmmapi/src/TorchForce.cpp.o
[ 17%] Building CXX object CMakeFiles/OpenMMTorch.dir/openmmapi/src/TorchForceImpl.cpp.o
[ 20%] Building CXX object CMakeFiles/OpenMMTorch.dir/serialization/src/TorchForceProxy.cpp.o
[ 24%] Building CXX object CMakeFiles/OpenMMTorch.dir/serialization/src/TorchSerializationProxyRegistration.cpp.o
make[2]: *** No rule to make target /usr/local/cuda/lib64/libnvToolsExt.so', needed by libOpenMMTorch.so'. Stop.
make[1]: *** [CMakeFiles/OpenMMTorch.dir/all] Error 2
make: *** [all] Error

In the file build_openmm_torch/CMakeFiles/OpenMMTorch.dir/build.make, the dependency objects of libOpenMMTorch.so are listed as

libOpenMMTorch.so: CMakeFiles/OpenMMTorch.dir/openmmapi/src/TorchForce.cpp.o
libOpenMMTorch.so: CMakeFiles/OpenMMTorch.dir/openmmapi/src/TorchForceImpl.cpp.o
libOpenMMTorch.so: CMakeFiles/OpenMMTorch.dir/serialization/src/TorchForceProxy.cpp.o
libOpenMMTorch.so: CMakeFiles/OpenMMTorch.dir/serialization/src/TorchSerializationProxyRegistration.cpp.o
libOpenMMTorch.so: CMakeFiles/OpenMMTorch.dir/build.make
libOpenMMTorch.so: /home/xqding/apps/libtorch-1.3.0/lib/libtorch.so
libOpenMMTorch.so: /home/xqding/apps/libtorch-1.3.0/lib/libc10.so
libOpenMMTorch.so: /export/apps/CentOS7/cuda/10.0/lib64/stubs/libcuda.so
libOpenMMTorch.so: /export/apps/CentOS7/cuda/10.0/lib64/libnvrtc.so
libOpenMMTorch.so: /export/apps/CentOS7/cuda/10.0/lib64/libnvToolsExt.so
libOpenMMTorch.so: /export/apps/CentOS7/cuda/10.0/lib64/libcudart.so
libOpenMMTorch.so: /home/xqding/apps/libtorch-1.3.0/lib/libc10_cuda.so
libOpenMMTorch.so: /home/xqding/apps/libtorch-1.3.0/lib/libc10_cuda.so
libOpenMMTorch.so: /home/xqding/apps/libtorch-1.3.0/lib/libc10.so
libOpenMMTorch.so: /export/apps/CentOS7/cuda/10.0/lib64/libcudart.so
libOpenMMTorch.so: /usr/local/cuda/lib64/libnvToolsExt.so
libOpenMMTorch.so: /usr/local/cuda/lib64/libcudart.so
libOpenMMTorch.so: /export/apps/CentOS7/cuda/10.0/lib64/libcufft.so
libOpenMMTorch.so: /export/apps/CentOS7/cuda/10.0/lib64/libcurand.so
libOpenMMTorch.so: /export/apps/CentOS7/cudnn/9.2/lib64
libOpenMMTorch.so: /usr/local/cuda/lib64/libculibos.a
libOpenMMTorch.so: /usr/local/cuda/lib64/libculibos.a
libOpenMMTorch.so: /export/apps/CentOS7/cuda/10.0/lib64/libcublas.so

It is strange that libOpenMMTorch.so depends on both /export/apps/CentOS7/cuda/10.0/lib64/libnvToolsExt.so and /usr/local/cuda/lib64/libnvToolsExt.so, because I already set the CUDA library path to /export/apps/CentOS7/cuda/10.0 in configuration.

Any suggestions on how to fix it? I also attached the CMakeCache.txt file in case it is helpful.

Thanks!
CMakeCache.txt

Make Install Errors

Hi, I followed the instructions in README, generated a Makefile but failed during make install.
I am running on Ubuntu. The error messages was as follows,

[ 35%] Building CXX object platforms/reference/CMakeFiles/OpenMMTorchReference.dir/src/ReferenceTorchKernelFactory.cpp.o
[ 39%] Building CXX object platforms/reference/CMakeFiles/OpenMMTorchReference.dir/src/ReferenceTorchKernels.cpp.o
~/openmm-torch/platforms/reference/src/ReferenceTorchKernels.cpp: In function ‘std::vectorOpenMM::Vec3& extractPositions(OpenMM::ContextImpl&)’:
~/openmm-torch/platforms/reference/src/ReferenceTorchKernels.cpp:44:19: error: ‘void*’ is not a pointer-to-object type
return data->positions;
^~~~~~~~~
~/openmm-torch/platforms/reference/src/ReferenceTorchKernels.cpp: In function ‘std::vectorOpenMM::Vec3& extractForces(OpenMM::ContextImpl&)’:
~/openmm-torch/platforms/reference/src/ReferenceTorchKernels.cpp:49:19: error: ‘void
’ is not a pointer-to-object type
return data->forces;
^~~~~~
~/openmm-torch/platforms/reference/src/ReferenceTorchKernels.cpp: In function ‘OpenMM::Vec3
extractBoxVectors(OpenMM::ContextImpl&)’:
~/openmm-torch/platforms/reference/src/ReferenceTorchKernels.cpp:54:18: error: invalid conversion from ‘void*’ to ‘OpenMM::Vec3*’ [-fpermissive]
return data->periodicBoxVectors;
~~~~~~^~~~~~~~~~~~~~~~~~
platforms/reference/CMakeFiles/OpenMMTorchReference.dir/build.make:86: recipe for target 'platforms/reference/CMakeFiles/OpenMMTorchReference.dir/src/ReferenceTorchKernels.cpp.o' failed
make[2]: *** [platforms/reference/CMakeFiles/OpenMMTorchReference.dir/src/ReferenceTorchKernels.cpp.o] Error 1

It seems that the pointer points to void in the source code of ReferenceTorchKernels.cpp. Any suggestions? Thanks.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.