Repository: openmm/openmm — pull request #4319 (cpp)
Title: Fixes to Python API for ATMForce

Fixes #4313.

Resolved issues:
title: `ATMForce.getParticleParameters` python wrapper requires displacement vectors
body: The python wrapper around the `getParticleParameters` method on the new `ATMForce` seems to want you to pass in two `Vec3` pointers that presumably the C++ layer will then update with the displacements set for particle `i`.

Was this the intention, or should the exposed python version of the method take only an index and instead return the `Vec3` displacements?

## 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/wrappers/python/src/swig_doxygen/swigInputConfig.py b/wrappers/python/src/swig_doxygen/swigInputConfig.py
index 24b598ae98..4c110b7be8 100755
--- a/wrappers/python/src/swig_doxygen/swigInputConfig.py
+++ b/wrappers/python/src/swig_doxygen/swigInputConfig.py
@@ -521,4 +521,5 @@
 ("ATMForce", "getDefaultUmax") :  ('unit.kilojoule_per_mole', ()),
 ("ATMForce", "getDefaultUbcore") :  ('unit.kilojoule_per_mole', ()),
 ("ATMForce", "getDefaultAcore") :  (None, ()),
+("ATMForce", "getParticleParameters") : (None, ("unit.nanometer", "unit.nanometer")),
 }
diff --git a/wrappers/python/src/swig_doxygen/swig_lib/python/typemaps.i b/wrappers/python/src/swig_doxygen/swig_lib/python/typemaps.i
index fba646d820..f33f09cf2c 100644
--- a/wrappers/python/src/swig_doxygen/swig_lib/python/typemaps.i
+++ b/wrappers/python/src/swig_doxygen/swig_lib/python/typemaps.i
@@ -537,16 +537,23 @@ int Py_SequenceToVecVecVecDouble(PyObject* obj, std::vector<std::vector<std::vec
 %typemap(argout) const std::vector<Vec3>& {
 }
 
-
+/* The following typemaps handle the ways a Vec3 can be returned from a function. */
 %typemap(out, fragment="Vec3_to_PyVec3") Vec3 {
     $result = Vec3_to_PyVec3(*$1);
 }
 
-
 %typemap(out, fragment="Vec3_to_PyVec3") const Vec3& {
     $result = Vec3_to_PyVec3(*$1);
 }
 
+%typemap(in, numinputs=0) Vec3& OUTPUT (Vec3 temp) {
+    $1 = &temp;
+}
+
+%typemap(argout, fragment="Vec3_to_PyVec3") Vec3& OUTPUT {
+    %append_output(Vec3_to_PyVec3(*$1));
+}
+
 /* Convert C++ (Vec3&, Vec3&, Vec3&) object to python tuple or tuples */
 %typemap(argout, fragment="Vec3_to_PyVec3") (Vec3& a, Vec3& b, Vec3& c) {
     PyObject* pyVec1 = Vec3_to_PyVec3(*$1);


Test patch:
diff --git a/wrappers/python/tests/TestAPIUnits.py b/wrappers/python/tests/TestAPIUnits.py
index f2d0deedff..d7e75f8bad 100644
--- a/wrappers/python/tests/TestAPIUnits.py
+++ b/wrappers/python/tests/TestAPIUnits.py
@@ -526,6 +526,23 @@ def testCustomManyParticleForce(self):
         self.assertEqual(force.getParticleParameters(1)[0][0], 20)
         self.assertEqual(force.getParticleParameters(2)[0][0], 30*4.184)
 
+    def testATMForce(self):
+        """Tests the ATMForce API features"""
+        force = ATMForce(0.1, 0.2, 0.3, 0.4, 0.5, 0.7, 0.6, 0.8, -1.0);
+        force.addParticle(Vec3(1, 2, 3), Vec3(4, 5, 6))
+        self.assertEqual(0.1, force.getGlobalParameterDefaultValue(0))
+        self.assertEqual(0.2, force.getGlobalParameterDefaultValue(1))
+        self.assertEqual(0.3, force.getGlobalParameterDefaultValue(2))
+        self.assertEqual(0.4, force.getGlobalParameterDefaultValue(3))
+        self.assertEqual(0.5, force.getGlobalParameterDefaultValue(4))
+        self.assertEqual(0.7, force.getGlobalParameterDefaultValue(5))
+        self.assertEqual(0.6, force.getGlobalParameterDefaultValue(6))
+        self.assertEqual(0.8, force.getGlobalParameterDefaultValue(7))
+        self.assertEqual(-1.0, force.getGlobalParameterDefaultValue(8))
+        d1, d0 = force.getParticleParameters(0)
+        self.assertEqual(Vec3(1, 2, 3)*nanometers, d1)
+        self.assertEqual(Vec3(4, 5, 6)*nanometers, d0)
+
     def testDrudeForce(self):
         """ Tests the DrudeForce API features """
         force = DrudeForce()