Repository: openmm/openmm — pull request #4317 (cpp)
Title: Serialization proxy for ATMForce did not record the particles

Fixes #4316.

Resolved issues:
title: `ATMForce` SIGSEGV's after copying / serialization
body: Copying (or serializing / deserializing) a system that contains an ATM force, and then using that system in a context yields a segfault:

```
import openmm

nb_force = openmm.NonbondedForce()
nb_force.addParticle(0.0, 1.0, 1.0)

atm_force = openmm.ATMForce(0.0, 0.0, 0.0, 0.0, 0.0, 100.0, 50.0, 1.0 / 16.0, 1)
atm_force.addParticle(openmm.Vec3(0.0, 0.0, 0.0))
atm_force.addForce(nb_force)

system = openmm.System()
system.addParticle(1.0)
system.addForce(atm_force)

system = openmm.XmlSerializer.deserialize(openmm.XmlSerializer.serialize(system))

context = openmm.Context(system, openmm.VerletIntegrator(1.0))
context.setPositions([openmm.Vec3(0.0, 0.0, 0.0)] * openmm.unit.angstrom)

print(context.getState(getEnergy=True).getPotentialEnergy(), flush=True)
```

removing the `system = openmm.XmlSerializer.deserialize(openmm.XmlSerializer.serialize(system))` line yields an energy of 0.0 as exepcted without any segfaults.

## IMPORTANT INSTRUCTIONS

1. If you edit a python file, you MUST run 'make PythonInstall' inside the build directory
2. If you edit a c++ file, you MUST run 'make -j4 && make install" inside the build directory

Hints:

Fix patch:
diff --git a/serialization/src/ATMForceProxy.cpp b/serialization/src/ATMForceProxy.cpp
index d86cbd4a50..75d70fb6f8 100644
--- a/serialization/src/ATMForceProxy.cpp
+++ b/serialization/src/ATMForceProxy.cpp
@@ -60,6 +60,13 @@ void ATMForceProxy::serialize(const void* object, SerializationNode& node) const
         SerializationNode& f = forces.createChildNode("Force");
         f.createChildNode("Force", &force.getForce(i));
     }
+    SerializationNode& particles = node.createChildNode("Particles");
+    for (int i = 0; i < force.getNumParticles(); i++) {
+        Vec3 d1, d0;
+        force.getParticleParameters(i, d1, d0);
+        particles.createChildNode("Particle").setDoubleProperty("d1x", d1[0]).setDoubleProperty("d1y", d1[1]).setDoubleProperty("d1z", d1[2])
+                .setDoubleProperty("d0x", d0[0]).setDoubleProperty("d0y", d0[1]).setDoubleProperty("d0z", d0[2]);
+    }
 }
 
 void* ATMForceProxy::deserialize(const SerializationNode& node) const {
@@ -80,6 +87,10 @@ void* ATMForceProxy::deserialize(const SerializationNode& node) const {
         const SerializationNode& forces = node.getChildNode("Forces");
         for (auto& f : forces.getChildren())
             force->addForce(f.getChildren()[0].decodeObject<Force>());
+        const SerializationNode& particles = node.getChildNode("Particles");
+        for (auto& p : particles.getChildren())
+            force->addParticle(Vec3(p.getDoubleProperty("d1x"), p.getDoubleProperty("d1y"), p.getDoubleProperty("d1z")),
+                               Vec3(p.getDoubleProperty("d0x"), p.getDoubleProperty("d0y"), p.getDoubleProperty("d0z")));
         return force;
     }
     catch (...) {


Test patch:
diff --git a/serialization/tests/TestSerializeATMForce.cpp b/serialization/tests/TestSerializeATMForce.cpp
index e55f2fe5f4..641ff0d46a 100644
--- a/serialization/tests/TestSerializeATMForce.cpp
+++ b/serialization/tests/TestSerializeATMForce.cpp
@@ -55,6 +55,8 @@ void testSerialization() {
     HarmonicAngleForce* v2 = new HarmonicAngleForce();
     v2->addAngle(3, 11, 15, 0.4, 0.2);
     force.addForce(v2);
+    force.addParticle(Vec3(1, 2, 3));
+    force.addParticle(Vec3(0, 0, -1), Vec3(3, 2, 1));
 
     // Serialize and then deserialize it.
 
@@ -83,6 +85,14 @@ void testSerialization() {
         XmlSerializer::serialize<Force>(&force2.getForce(i), "Force", buffer2);
         ASSERT_EQUAL(buffer1.str(), buffer2.str());
     }
+    ASSERT_EQUAL(force.getNumParticles(), force2.getNumParticles());
+    for (int i = 0; i < force.getNumParticles(); i++) {
+        Vec3 d1a, d1b, d0a, d0b;
+        force.getParticleParameters(i, d1a, d0a);
+        force2.getParticleParameters(i, d1b, d0b);
+        ASSERT_EQUAL_VEC(d1a, d1b, 0.0);
+        ASSERT_EQUAL_VEC(d0a, d0b, 0.0);
+    }
 }
 
 int main() {