Page 1 of 1

Loop over trajectory to decompose OpenMM energies

Posted: Mon Apr 03, 2023 12:38 pm
by mdpoleto
Hi,

I want to loop over a MD trajectory written by OpenMM to get the decomposed energy groups and write them to a file. I am using ParmEd's (pmd.openmm.energy_decomposition_system()) to read a single coordinate file as shown below:

Code: Select all

top   = 'ala.c36.psf'
coord = 'ala.c36.crd'
traj  = '100frames.dcd'

psf  = CharmmPsfFile(top)
crd  = CharmmCrdFile(coord)

box = 2.9*nanometer #
psf.setBox(box,box,box)
system = psf.createSystem(charmm_params, nonbondedMethod=PME, nonbondedCutoff=1.2*nanometers, switchDistance=1.0*nanometer, ewaldErrorTolerance = 0.0005, constraints=HBonds)

integrator = LangevinIntegrator(298, 1/picosecond, 0.002)
simulation = Simulation(psf.topology, system, integrator)
simulation.context.setPositions(crd.positions)

# print out energy and forces
positions = pmd.load_file(coord)
energies = pmd.openmm.energy_decomposition_system(positions,system)

for term, ener in energies:
    print(str(term) + ": " + str(ener))
Does anyone know a way to use MDtraj or MDAnalysis to update the coordinates of the systems and use them to update parmed positions object? I am trying to connect the dots here but I feel I am missing something.

Re: Loop over trajectory to decompose OpenMM energies

Posted: Mon Apr 03, 2023 1:39 pm
by peastman
Your code is a little misleading:
positions = pmd.load_file(coord)
The value returned by load_file(), which you pass as the first argument to energy_decomposition_system(), is actually a Structure, not a set of positions. It assumes you have already set the positions by calling setPositions() on the Context.

You can just loop over frames, call setPositions() for each one, and then ask ParmEd to produce an energy decomposition. For example, if you have a MDTraj trajectory you can do something like this.

Code: Select all

for frame in range(traj.n_frames):
    simulation.context.setPositions(traj.xyz[frame])
    energies = pmd.openmm.energy_decomposition_system(structure, system)
    # Do something with the result
Make sure the positions are in the correct units.

Re: Loop over trajectory to decompose OpenMM energies

Posted: Mon Apr 03, 2023 2:24 pm
by mdpoleto
Hi Peter,

Thanks for your input and correction. However, I don't see how the Structure object is updated with the new positions. The simulation context is being updated, but I don't think that is being passed to Structure. I tested your suggestion with the code below:

Code: Select all

top   = 'ala.c36.psf'
coord = 'ala.c36.crd'
traj  = '100frames.dcd'

psf  = CharmmPsfFile(top)
crd  = CharmmCrdFile(coord)

box = 2.9*nanometer #
psf.setBox(box,box,box)
system = psf.createSystem(charmm_params, nonbondedMethod=PME, nonbondedCutoff=1.2*nanometers, switchDistance=1.0*nanometer, ewaldErrorTolerance = 0.0005, constraints=HBonds)

integrator = LangevinIntegrator(298, 1/picosecond, 0.002)
simulation = Simulation(psf.topology, system, integrator)
simulation.context.setPositions(crd.positions)

#####
# Initial evaluation
state = simulation.context.getState(getEnergy=True)
potenergy = state.getPotentialEnergy().value_in_unit(kilojoules_per_mole)

energies = pmd.openmm.energy_decomposition_system(stru, system)

totalpotenergy_array  = []
for term, energy in energies:
    energy = energy*4.184
    totalpotenergy_array.append(energy)
    
totalpotenergy  = np.sum(totalpotenergy_array)
print("Initial", potenergy,totalpotenergy)

####
# Loop over trajectory
traj = md.load(dcd_file, top=psf_file)
for frame in range(traj.n_frames):
    newpositions = traj.xyz[frame]
    simulation.context.setPositions(newpositions)

    state = simulation.context.getState(getEnergy=True)
    potenergy = state.getPotentialEnergy().value_in_unit(kilojoules_per_mole)

    energies = pmd.openmm.energy_decomposition_system(stru, system)

    totalpotenergy_array  = []

    for term, energy in energies:
        energy = energy*4.184
        totalpotenergy_array.append(energy)
    
    totalpotenergy  = np.sum(totalpotenergy_array)
    print(frame, potenergy, totalpotenergy)
If I run this, what I get is this comparison:

Code: Select all

Initial  -29602.05819440112   -29602.055461647367
0        -32901.96444440112   -29602.047649147367
1        -31255.77694440112   -29602.047649147367
2        -30556.41756940112   -29602.063274147367
3        -29760.02694440112   -29602.055705787992
4        -29603.23006940112   -29602.055461647367
5        -29794.88631940112   -29602.055705787992
6        -29388.67538190112   -29602.055705787992
7        -29687.15975690112   -29602.055461647367
8        -29786.55819440112   -29602.063518287992
9        -29441.23006940112   -29602.063274147367
Am I missing something?

Re: Loop over trajectory to decompose OpenMM energies

Posted: Mon Apr 03, 2023 5:22 pm
by peastman
It looks like there are two different functions: energy_decomposition(), which uses the positions already stored in the Context, and energy_decomposition_system(), which takes them from the Structure. Can you just set them directly?

Code: Select all

structure.positions = traj.xyz[frame]

Re: Loop over trajectory to decompose OpenMM energies

Posted: Mon Apr 03, 2023 5:40 pm
by mdpoleto
Not really.

Code: Select all

stru.positions = traj.xyz[frame]
AttributeError: can't set attribute
I also tried to use:

Code: Select all

energies = pmd.openmm.energy_decomposition(stru, simulation.context)
But I get the following:

Code: Select all

File "/home/mdpoleto/Documents/Softwares/anaconda3/envs/openmm7.7/lib/python3.9/site-packages/parmed/openmm/utils.py", line 43, in energy_decomposition
    force_group_names[gp] = all_names[gp]
KeyError: 0

Re: Loop over trajectory to decompose OpenMM energies

Posted: Mon Apr 03, 2023 6:01 pm
by peastman
I don't know what the intended idiom is then. Since this is a ParmEd question, I suggest asking at https://github.com/ParmEd/ParmEd/issues.

Alternatively you could decompose the energies directly through OpenMM rather than using ParmEd. See this cookbook entry for a description of how to do that.

Re: Loop over trajectory to decompose OpenMM energies

Posted: Mon Apr 10, 2023 11:50 am
by mdpoleto
Hi,

I just want to update the topic with my final code. I checked that forces from this code matches the forces calculated with the CHARMM software.

Code: Select all

import simtk.openmm as mm
from simtk.openmm import *
from simtk.openmm.app import *
from simtk.unit import *
import mdtraj as md

import os, argparse
import numpy as np

def forcegroupify(system):
    forcegroups = {}
    for i in range(system.getNumForces()):
        force = system.getForce(i)
        force.setForceGroup(i)
        forcename = force.getName()
        forcegroups[forcename] = i
    return forcegroups


psffile = "ala.c36.psf"
crdfile = "ala.c36.crd"
dcdfile = "ala.c36.dcd"

# reading and parsing topology and coordinates
psf = CharmmPsfFile(psffile)
crd = CharmmCrdFile(crdfile)

# applying boxvectors to psf
box = 2.9*nanometer #
psf.setBox(box,box,box)

# reading parameters and creating system
system = psf.createSystem(charmm_params, nonbondedMethod=PME, nonbondedCutoff=1.2*nanometers, switchDistance=1.0*nanometer, ewaldErrorTolerance = 0.0005, constraints=HBonds)

# making sure each ForceGroup has its own index
forcegroups = {}
for i in range(system.getNumForces()):
    force = system.getForce(i)
    force.setForceGroup(i)
    forcename = force.getName()
    forcegroups[forcename] = i

integrator = LangevinIntegrator(temperature, 1/picosecond, dt)
simulation = Simulation(psf.topology, system, integrator)
simulation.context.setPositions(crd.positions)

######################################
# Calculating Energies for entire trajectory
traj = md.load(cmd.dcd, top=cmd.psf)

print("\n>>> Reading and analyzing:")
for frame in range(traj.n_frames):
    print("> Frame: " + str(frame))
    
    # update simulation context with new positions
    simulation.context.setPositions(traj.xyz[frame])

    # update simulation context with new box vectors
    a, b, c = traj.unitcell_vectors[frame]
    simulation.context.setPeriodicBoxVectors(*(a,b,c))

    # get updated energies
    state = simulation.context.getState(getEnergy=True)
    potenergy = getPotentialEnergy(state)
    
    for f, i in forcegroups.items():
        energy = simulation.context.getState(getEnergy=True, groups=2**i).getPotentialEnergy()
        energy = energy.value_in_unit(kilojoules_per_mole) # remove unit
        print(f,i)


Re: Loop over trajectory to decompose OpenMM energies

Posted: Mon Apr 10, 2023 12:55 pm
by peastman
Thanks! By the way, your code can be simplified a bit. Instead of

Code: Select all

    for i in range(system.getNumForces()):
        force = system.getForce(i)
        force.setForceGroup(i)
        forcename = force.getName()
        forcegroups[forcename] = i
you can write

Code: Select all

    for i, force in enumerate(system.getForces()):
        force.setForceGroup(i)
        forcegroups[force.getName()] = i