View Javadoc
1   // ******************************************************************************
2   //
3   // Title:       Force Field X.
4   // Description: Force Field X - Software for Molecular Biophysics.
5   // Copyright:   Copyright (c) Michael J. Schnieders 2001-2025.
6   //
7   // This file is part of Force Field X.
8   //
9   // Force Field X is free software; you can redistribute it and/or modify it
10  // under the terms of the GNU General Public License version 3 as published by
11  // the Free Software Foundation.
12  //
13  // Force Field X is distributed in the hope that it will be useful, but WITHOUT
14  // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
15  // FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
16  // details.
17  //
18  // You should have received a copy of the GNU General Public License along with
19  // Force Field X; if not, write to the Free Software Foundation, Inc., 59 Temple
20  // Place, Suite 330, Boston, MA 02111-1307 USA
21  //
22  // Linking this library statically or dynamically with other modules is making a
23  // combined work based on this library. Thus, the terms and conditions of the
24  // GNU General Public License cover the whole combination.
25  //
26  // As a special exception, the copyright holders of this library give you
27  // permission to link this library with independent modules to produce an
28  // executable, regardless of the license terms of these independent modules, and
29  // to copy and distribute the resulting executable under terms of your choice,
30  // provided that you also meet, for each linked independent module, the terms
31  // and conditions of the license of that module. An independent module is a
32  // module which is not derived from or based on this library. If you modify this
33  // library, you may extend this exception to your version of the library, but
34  // you are not obligated to do so. If you do not wish to do so, delete this
35  // exception statement from your version.
36  //
37  // ******************************************************************************
38  package ffx.openmm;
39  
40  import edu.uiowa.jopenmm.OpenMMUtils;
41  import org.junit.After;
42  import org.junit.BeforeClass;
43  import org.junit.Test;
44  
45  import static edu.uiowa.jopenmm.OpenMMLibrary.OpenMM_State_DataType.OpenMM_State_Energy;
46  import static org.junit.Assert.assertEquals;
47  
48  /**
49   * Simple unit tests to exercise creating a System with a HarmonicBondForce,
50   * creating a Context with a Langevin integrator on the OpenCL platform,
51   * and validating potential energy before and after a few MD steps.
52   */
53  public class OpenMMHarmonicBondTest {
54  
55    private Context context;
56    private Integrator integrator;
57    private System system;
58  
59    @BeforeClass
60    public static void init() {
61      OpenMMUtils.init();
62      String pluginDirectory = OpenMMUtils.getPluginDirectory();
63      Platform.loadPluginsFromDirectory(pluginDirectory);
64    }
65  
66    @After
67    public void tearDown() {
68      // Destroy in safe order to free native resources if they were created.
69      try {
70        if (context != null) context.destroy();
71      } catch (Exception ignored) {
72      }
73      try {
74        if (integrator != null) integrator.destroy();
75      } catch (Exception ignored) {
76      }
77      try {
78        if (system != null) system.destroy();
79      } catch (Exception ignored) {
80      }
81    }
82  
83    @Test
84    public void testHarmonicBondEnergyAtEquilibriumOpenCL() {
85      Platform platform = new Platform("Reference");
86  
87      // Build a simple 2-particle system with a single harmonic bond.
88      system = new System();
89      double mass = 12.0; // amu (arbitrary positive mass)
90      system.addParticle(mass);
91      system.addParticle(mass);
92  
93      HarmonicBondForce bondForce = new HarmonicBondForce();
94      double r0 = 0.1; // nm
95      double k = 1000.0; // kJ/mol/nm^2
96      bondForce.addBond(0, 1, r0, k);
97      system.addForce(bondForce);
98  
99      // Use a Langevin integrator at 0 K to avoid thermal energy.
100     double dt = 0.001; // ps
101     double temperature = 0.0; // K
102     double gamma = 1.0; // 1/ps
103     integrator = new LangevinIntegrator(dt, temperature, gamma);
104 
105     // Create the context on OpenCL.
106     context = new Context(system, integrator, platform);
107 
108     // Set positions at equilibrium distance along x-axis: energy should be ~0.
109     double[] posEq = new double[]{0.0, 0.0, 0.0, r0, 0.0, 0.0};
110     context.setPositions(posEq);
111 
112     State state0 = context.getState(OpenMM_State_Energy, 0);
113     double e0 = state0.getPotentialEnergy();
114     // Expect near-zero potential energy at equilibrium. Allow tiny numerical tolerance.
115     assertEquals(0.0, e0, 1.0e-6);
116 
117     // Step a few times and re-check energy remains near zero.
118     integrator.step(10);
119     State state1 = context.getState(OpenMM_State_Energy, 0);
120     double e1 = state1.getPotentialEnergy();
121     assertEquals(0.0, e1, 1.0e-5);
122   }
123 
124   @Test
125   public void testHarmonicBondEnergyDisplacedOpenCL() {
126     Platform platform = new Platform("Reference");
127 
128     // Build a simple 2-particle system with a single harmonic bond.
129     system = new System();
130     double mass = 12.0; // amu
131     system.addParticle(mass);
132     system.addParticle(mass);
133 
134     HarmonicBondForce bondForce = new HarmonicBondForce();
135     double r0 = 0.1; // nm
136     double k = 1000.0; // kJ/mol/nm^2
137     bondForce.addBond(0, 1, r0, k);
138     system.addForce(bondForce);
139 
140     // Use a Langevin integrator at 0 K to avoid thermal noise.
141     double dt = 0.001; // ps
142     double temperature = 0.0; // K
143     double gamma = 1.0; // 1/ps
144     integrator = new LangevinIntegrator(dt, temperature, gamma);
145 
146     // Create the context on OpenCL.
147     context = new Context(system, integrator, platform);
148 
149     // Displace the bond slightly from equilibrium by +dr along x.
150     double dr = 0.01; // nm
151     double[] pos = new double[]{0.0, 0.0, 0.0, r0 + dr, 0.0, 0.0};
152     context.setPositions(pos);
153 
154     // Analytical harmonic energy: 0.5 * k * dr^2
155     double expected = 0.5 * k * dr * dr;
156 
157     State state0 = context.getState(OpenMM_State_Energy, 0);
158     double e0 = state0.getPotentialEnergy();
159     assertEquals(expected, e0, 1.0e-6);
160 
161     // Take a few steps; at 0 K the system should relax slightly toward minimum,
162     // but small dt and friction may keep energy close; just ensure finite and non-negative.
163     integrator.step(5);
164     State state1 = context.getState(OpenMM_State_Energy, 0);
165     double e1 = state1.getPotentialEnergy();
166     // Energy should remain non-negative and near the same order of magnitude.
167     assertEquals(expected, e1, 1.0e-3);
168   }
169 }