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 }