Python API to evaluate ewald Coulomb force

The functionality of OpenMM will (eventually) include everything that one would need to run modern molecular simulation.
User avatar
JZ Most
Posts: 9
Joined: Thu Sep 21, 2023 5:45 am

Re: Python API to evaluate ewald Coulomb force

Post by JZ Most » Fri Jun 28, 2024 3:44 am

Thanks for replying. The values of positions are from 0 to 1e-8m, I transfered them to the unit of angstrom. Thats why I multiple them with 1e+10

User avatar
Peter Eastman
Posts: 2552
Joined: Thu Aug 09, 2007 1:25 pm

Re: Python API to evaluate ewald Coulomb force

Post by Peter Eastman » Fri Jun 28, 2024 8:02 am

Can you provide a complete, working example that demonstrates the problem? The code you posted before doesn't correspond to what you described since it doesn't use a real integrator, doesn't run any dynamics, and never retrieves the positions.

User avatar
JZ Most
Posts: 9
Joined: Thu Sep 21, 2023 5:45 am

Re: Python API to evaluate ewald Coulomb force

Post by JZ Most » Fri Jun 28, 2024 9:01 am

Thanks for replying. The physical system I tried to simulate is 'a cubic system (L = 1e-8m), with 2 layer of fixed charges at x = 0 and x = L/2, and certain number of free ions '. There are two approaches I implemented, one is to use openmm to calculate the PME coulombic and LJ forces and use python random number to simulate the random diffusion, so I can update the positions of free ions every time step. Another is to just use the Langevin integrator to run the simulation to a certain timepoint. The reason for why I did not use a real integrator is that I only want to know the force value. However, no matter which integrator I used (include real integrator), I still have my position array became 0 after 1 time step. So I am a bit confused.

User avatar
Peter Eastman
Posts: 2552
Joined: Thu Aug 09, 2007 1:25 pm

Re: Python API to evaluate ewald Coulomb force

Post by Peter Eastman » Fri Jun 28, 2024 9:07 am

Please provide a complete example I can use to reproduce the problem. Otherwise, I don't really know what you're doing or why it isn't working!

User avatar
JZ Most
Posts: 9
Joined: Thu Sep 21, 2023 5:45 am

Re: Python API to evaluate ewald Coulomb force

Post by JZ Most » Fri Jun 28, 2024 8:15 pm

Sorry about being confusing, please see below,
Initialize the positions

Code: Select all

import numpy as np
from simtk import openmm as mm
from simtk.openmm import app
from simtk.unit import nanometer, picosecond, angstrom, kilojoule_per_mole, kelvin, kilocalorie_per_mole
import openmm.unit as unit
import time
from matplotlib import pyplot as plt

length = 1e-8
num_particles = 200
rho_perm_pos = np.random.rand(36, 3) * length ##fixed charge at x = 0
rho_perm_pos[:,0] = 0

rho_perm_neg = np.random.rand(36, 3) * length ##fixed charge at x = L/2
rho_perm_neg[:,0] = 0.5*length
np.random.seed(42)
positions_pos = np.random.rand(num_particles, 3) * length
positions_neg = np.random.rand(num_particles, 3) * length
positions = np.random.rand(num_particles*2, 3) * length

positions = np.concatenate((positions_neg, rho_perm_neg, positions_pos, rho_perm_pos), axis=0)
PME coulombic forces and LJ forces using Openmm,

Code: Select all

box_size = 100*angstrom

# define a new system
system = mm.System()
system.setDefaultPeriodicBoxVectors((box_size, 0, 0), (0, box_size, 0), (0, 0, box_size))

# define the ion number and charge
num_Na = num_particles+36 ## free ions and 36 fixed
num_Cl = num_particles+36
charge_Na = +1.0 # +
charge_Cl = -1.0 # -
mass_Na = 22.98976
mass_Cl = 35.453

# charge array
charges = np.concatenate([np.full(num_Cl, charge_Cl), np.full(num_Na, charge_Na)])

# add ions to the system
for _ in range(num_Cl):
    system.addParticle(mass_Cl) 
for _ in range(num_Na):
    system.addParticle(mass_Na)
    
# NonbondedForce, Particle Mesh Ewald
nonbonded = mm.NonbondedForce()
nonbonded.setNonbondedMethod(mm.NonbondedForce.PME)
nonbonded.setEwaldErrorTolerance(1.0e-4)
nonbonded.setCutoffDistance(50*angstrom)

alpha = 0.052/nanometer
# Set PME parameters (alpha, reciprocal space cutoff)

grid_size_x = 17  # Example FFT grid size for x dimension
grid_size_y = 17  # Example FFT grid size for y dimension
grid_size_z = 17  # Example FFT grid size for z dimension

# Set PME parameters (alpha, reciprocal space cutoff, FFT grid sizes)
nonbonded.setPMEParameters(alpha, grid_size_x, grid_size_y, grid_size_z)

# add the charge to the system
for _ in range(num_Cl):
    nonbonded.addParticle(-1, 0.227 * nanometer, 0.15*4.184 * kilojoule_per_mole)
for _ in range(num_Na):
    nonbonded.addParticle(+1, 0.141 * nanometer, 0.0469*4.184 * kilojoule_per_mole)

# add the force to the system
system.addForce(nonbonded)

# create simulation
dummy_integrator = mm.CustomIntegrator(0)
simulation = app.Simulation(topology=None, system=system, integrator=dummy_integrator, platform=platform)
simulation.context.setPositions(positions*1e+10*angstrom)
state = simulation.context.getState(getForces=True)
forces = state.getForces(asNumpy=True)
From this code, I can get a forces array seem meaningful (my main purpose in this setting is to get force value)

Code: Select all

Quantity(value=array([[ -619.46679688,   468.09777832,   -35.26379395],
       [ -345.91641235,   -78.44809723,    90.83336639],
       [-1286.58483887, -1409.69042969,  3650.68798828],
       ...,
       [ -240.84684753,    78.62600708,   530.60791016],
       [  148.15328979,   503.44345093,   212.35394287],
       [ -301.76553345,  3715.84716797,  -208.17672729]]), unit=kilojoule/(nanometer*mole))
However, when I changed the integrator, (I want to validate with the results by langevin)

Code: Select all

dummy_integrator = mm.LangevinIntegrator(300 * unit.kelvin, 0.1 / unit.picosecond, 0.01 * unit.picoseconds)
simulation = app.Simulation(topology=None, system=system, integrator=dummy_integrator, platform=platform)
simulation.context.setPositions(positions * 1e+10 * angstrom)
num_steps = 1000  # Number of steps to run
simulation.step(num_steps)
state.getPositions(asNumpy=True)
The positions will not be change (but the forces value is the same to Customintegrator(0)) until the 'Particle coordinate is NaN.' Does that mean I did not get the correct value?

User avatar
Peter Eastman
Posts: 2552
Joined: Thu Aug 09, 2007 1:25 pm

Re: Python API to evaluate ewald Coulomb force

Post by Peter Eastman » Fri Jul 05, 2024 11:42 am

Code: Select all

simulation.step(num_steps)
state.getPositions(asNumpy=True)
You don't retrieve a new State object. You call getPositions() on some state you created earlier, though I don't know when or how you created it. That isn't shown in your code. If you want to get the current positions, you need to call getState(getPositions=True).

User avatar
JZ Most
Posts: 9
Joined: Thu Sep 21, 2023 5:45 am

Re: Python API to evaluate ewald Coulomb force

Post by JZ Most » Fri Jul 19, 2024 12:16 pm

Hi Peter,

Thanks dor reply, I am sure I created a state before as

Code: Select all

state = simulation.context.getState(getForces=True)
Now I change the sample code according to your mention.

Code: Select all

length = 1e-8
num_particles = 50
rho_perm_pos = np.random.rand(36, 3) * length # 36 fixed ions at x = 0
rho_perm_pos[:,0] = 0

rho_perm_neg = np.random.rand(36, 3) * length # 36 fixed ions at x = L/2
rho_perm_neg[:,0] = 0.5*length
np.random.seed(42)
positions_pos = np.random.rand(num_particles, 3) * length # free +ve ions
positions_neg = np.random.rand(num_particles, 3) * length # free -ve ions

positions = np.concatenate((positions_neg, rho_perm_neg, positions_pos, rho_perm_pos), axis=0)

box_size = 100*angstrom

# define a new system
system = mm.System()

system.setDefaultPeriodicBoxVectors((box_size, 0, 0), (0, box_size, 0), (0, 0, box_size))

# define the ion number and charge
num_Na = num_particles+36
num_Cl = num_particles+36
charge_Na = +1.0 # +
charge_Cl = -1.0 # -
mass_Na = 22.98976
mass_Cl = 35.453

# charge array
charges = np.concatenate([np.full(num_Cl, charge_Cl), np.full(num_Na, charge_Na)])

# add ions to the system
for _ in range(num_particles):
    system.addParticle(mass_Cl) # assume the mass is 1 for simplicity
for _ in range(36):
    system.addParticle(0) # assume the mass is 1 for simplicity
for _ in range(num_particles):
    system.addParticle(mass_Cl) # assume the mass is 1 for simplicity
for _ in range(36):
    system.addParticle(0) # assume the mass is 1 for simplicity
    


# NonbondedForce, Particle Mesh Ewald
nonbonded = mm.NonbondedForce()
nonbonded.setNonbondedMethod(mm.NonbondedForce.PME)
nonbonded.setEwaldErrorTolerance(1.0e-4)
nonbonded.setCutoffDistance(50*angstrom)

epsilon = 1.0e-4  # Ewald error tolerance

alpha = 0.052/nanometer
# Set PME parameters (alpha, reciprocal space cutoff)

grid_size_x = 17  # Example FFT grid size for x dimension
grid_size_y = 17  # Example FFT grid size for y dimension
grid_size_z = 17  # Example FFT grid size for z dimension

# Set PME parameters (alpha, reciprocal space cutoff, FFT grid sizes)
nonbonded.setPMEParameters(alpha, grid_size_x, grid_size_y, grid_size_z)

# add the charge to the system and ignore VDW
for _ in range(num_Cl):
    nonbonded.addParticle(-1, 0.440 * nanometer, 0.4928 * kilojoule_per_mole)
for _ in range(num_Na):
    nonbonded.addParticle(+1, 0.325 * nanometer, 0.544 * kilojoule_per_mole)

# add the force to the system
system.addForce(nonbonded)

dummy_integrator = mm.LangevinIntegrator(300 * unit.kelvin, 1 / unit.picosecond, 0.01 * unit.picoseconds)
simulation = app.Simulation(topology=None, system=system, integrator=dummy_integrator, platform=platform)
simulation.context.setPositions(positions * 1e+10 * angstrom)
num_steps = 10000 # Number of steps to run
simulation.step(num_steps)

state = simulation.context.getState(getForces=True, getPositions=True)
ps = state.getPositions(asNumpy=True)
I set the timestep as 1e-2 picoseconds, but the simulation will crush due to some of the position become Nah. If I further decrease the time step to 1e-4 picoseconds, the simulation will not return a error, but mosst of the positions of free ions eventually accumulate at the fixed charge. I think I define something wrong and seems like the LJ forces did not apply.

User avatar
Peter Eastman
Posts: 2552
Joined: Thu Aug 09, 2007 1:25 pm

Re: Python API to evaluate ewald Coulomb force

Post by Peter Eastman » Fri Jul 19, 2024 12:50 pm

Some of your settings are very usual. For example, you set the cutoff distance to 50 A, which is huge. Values around 10 A are more typical. A timestep of 0.01 ps is larger than people typically use, but 1e-4 ps is much smaller than normally used. Setting all the particles to random initial positions is also dangerous, since there could easily be two particles right on top of each other. That's probably the reason you need such a small step size to make it stable. It's better to spread them out more evenly. At the very least, call simulation.minimizeEnergy() to resolve the largest forces before you try to simulate.

There are two methods of setting Ewald parameters. One is to let it automatically pick them based on the cutoff distance and error tolerance. The other is to set them manually. You're trying to do both at once. You first call setEwaldErrorTolerance(), but then setPMEParameters() which overrides it and means the error tolerance you set is ignored.

How close to each other do the charges end up?

POST REPLY