Repository: openmm/openmm — pull request #3508 (cpp)
Title: Max Drude distance defaults to 0.02 nm

This is part of #2513.

Resolved issues:
title: Max Drude distance defaults to 0.02 nm
body: ### Issue Description

**Problem:**
The default maximum distance a Drude particle can move from its parent particle is not explicitly defined, leading to potential confusion for users. Previously, the default value was set to 0, which disabled the hard wall constraint. This lack of clarity could result in unintended behavior or instability in simulations that rely on Drude integrators.

**Expected Behavior:**
The default maximum distance for Drude particles should be explicitly defined as 0.02 nm. This ensures that the hard wall constraint is enabled by default, improving simulation stability and providing a clear, consistent behavior for users. Users should be able to override this default value if needed, but the system should default to the defined constraint for better usability and reliability.

## 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/plugins/drude/openmmapi/include/openmm/DrudeIntegrator.h b/plugins/drude/openmmapi/include/openmm/DrudeIntegrator.h
index 2fc0a95a28..628dcfd38f 100644
--- a/plugins/drude/openmmapi/include/openmm/DrudeIntegrator.h
+++ b/plugins/drude/openmmapi/include/openmm/DrudeIntegrator.h
@@ -9,7 +9,7 @@
  * Biological Structures at Stanford, funded under the NIH Roadmap for        *
  * Medical Research, grant U54 GM072970. See https://simtk.org.               *
  *                                                                            *
- * Portions copyright (c) 2008-2021 Stanford University and the Authors.      *
+ * Portions copyright (c) 2008-2022 Stanford University and the Authors.      *
  * Authors: Peter Eastman                                                     *
  * Contributors:                                                              *
  *                                                                            *
@@ -72,12 +72,12 @@ class OPENMM_EXPORT_DRUDE DrudeIntegrator : public Integrator {
     void setDrudeTemperature(double temp);
     /**
      * Get the maximum distance a Drude particle can ever move from its parent particle, measured in nm.  This is implemented
-     * with a hard wall constraint.  If this distance is set to 0 (the default), the hard wall constraint is omitted.
+     * with a hard wall constraint.  The default value is 0.02.  If this distance is set to 0, the hard wall constraint is omitted.
      */
     double getMaxDrudeDistance() const;
     /**
      * Set the maximum distance a Drude particle can ever move from its parent particle, measured in nm.  This is implemented
-     * with a hard wall constraint.  If this distance is set to 0 (the default), the hard wall constraint is omitted.
+     * with a hard wall constraint.  The default value is 0.02.  If this distance is set to 0, the hard wall constraint is omitted.
      */
     void setMaxDrudeDistance(double distance);
     /**
diff --git a/plugins/drude/openmmapi/include/openmm/DrudeLangevinIntegrator.h b/plugins/drude/openmmapi/include/openmm/DrudeLangevinIntegrator.h
index 8c5083b798..8f39c6e075 100644
--- a/plugins/drude/openmmapi/include/openmm/DrudeLangevinIntegrator.h
+++ b/plugins/drude/openmmapi/include/openmm/DrudeLangevinIntegrator.h
@@ -47,7 +47,7 @@ namespace OpenMM {
  *
  * This integrator can optionally set an upper limit on how far any Drude particle is ever allowed to
  * get from its parent particle.  This can sometimes help to improve stability.  The limit is enforced
- * with a hard wall constraint.
+ * with a hard wall constraint.  By default the limit is set to 0.02 nm.
  * 
  * This Integrator requires the System to include a DrudeForce, which it uses to identify the Drude
  * particles.
diff --git a/plugins/drude/openmmapi/include/openmm/DrudeNoseHooverIntegrator.h b/plugins/drude/openmmapi/include/openmm/DrudeNoseHooverIntegrator.h
index 087eef7136..475b3378fc 100644
--- a/plugins/drude/openmmapi/include/openmm/DrudeNoseHooverIntegrator.h
+++ b/plugins/drude/openmmapi/include/openmm/DrudeNoseHooverIntegrator.h
@@ -45,6 +45,10 @@ namespace OpenMM {
  * A second thermostat, typically with a much lower temperature, is applied to the relative internal
  * displacement of each pair.
  *
+ * This integrator can optionally set an upper limit on how far any Drude particle is ever allowed to
+ * get from its parent particle.  This can sometimes help to improve stability.  The limit is enforced
+ * with a hard wall constraint.  By default the limit is set to 0.02 nm.
+ *
  * This Integrator requires the System to include a DrudeForce, which it uses to identify the Drude
  * particles.
  */
diff --git a/plugins/drude/openmmapi/src/DrudeLangevinIntegrator.cpp b/plugins/drude/openmmapi/src/DrudeLangevinIntegrator.cpp
index 6e48cf5327..793f65c593 100644
--- a/plugins/drude/openmmapi/src/DrudeLangevinIntegrator.cpp
+++ b/plugins/drude/openmmapi/src/DrudeLangevinIntegrator.cpp
@@ -52,7 +52,7 @@ DrudeLangevinIntegrator::DrudeLangevinIntegrator(double temperature, double fric
     setFriction(frictionCoeff);
     setDrudeTemperature(drudeTemperature);
     setDrudeFriction(drudeFrictionCoeff);
-    setMaxDrudeDistance(0);
+    setMaxDrudeDistance(0.02);
     setStepSize(stepSize);
     setConstraintTolerance(1e-5);
     setRandomNumberSeed(0);
diff --git a/plugins/drude/openmmapi/src/DrudeNoseHooverIntegrator.cpp b/plugins/drude/openmmapi/src/DrudeNoseHooverIntegrator.cpp
index 911bd504c1..6a2943a374 100644
--- a/plugins/drude/openmmapi/src/DrudeNoseHooverIntegrator.cpp
+++ b/plugins/drude/openmmapi/src/DrudeNoseHooverIntegrator.cpp
@@ -54,10 +54,8 @@ namespace OpenMM {
 DrudeNoseHooverIntegrator::DrudeNoseHooverIntegrator(double temperature, double collisionFrequency, 
                                                      double drudeTemperature, double drudeCollisionFrequency,
                                                      double stepSize, int chainLength, int numMTS, int numYoshidaSuzuki) :
-    NoseHooverIntegrator(stepSize),
-    drudeTemperature(drudeTemperature)
-{
-    setMaxDrudeDistance(0);
+            NoseHooverIntegrator(stepSize), drudeTemperature(drudeTemperature) {
+    setMaxDrudeDistance(0.02);
     hasSubsystemThermostats_ = false;
     addSubsystemThermostat(std::vector<int>(), std::vector<std::pair<int, int>>(), temperature,
                            collisionFrequency, drudeTemperature, drudeCollisionFrequency,
diff --git a/plugins/drude/openmmapi/src/DrudeSCFIntegrator.cpp b/plugins/drude/openmmapi/src/DrudeSCFIntegrator.cpp
index 92cb0454ea..04c751b870 100644
--- a/plugins/drude/openmmapi/src/DrudeSCFIntegrator.cpp
+++ b/plugins/drude/openmmapi/src/DrudeSCFIntegrator.cpp
@@ -48,6 +48,7 @@ DrudeSCFIntegrator::DrudeSCFIntegrator(double stepSize) : DrudeIntegrator(stepSi
     setStepSize(stepSize);
     setMinimizationErrorTolerance(0.1);
     setConstraintTolerance(1e-5);
+    setMaxDrudeDistance(0.0);
 }
 
 void DrudeSCFIntegrator::initialize(ContextImpl& contextRef) {
@@ -64,6 +65,8 @@ void DrudeSCFIntegrator::initialize(ContextImpl& contextRef) {
         }
     if (force == NULL)
         throw OpenMMException("The System does not contain a DrudeForce");
+    if (getMaxDrudeDistance() != 0.0)
+        throw OpenMMException("DrudeSCFIntegrator does not currently support setting max Drude distance");
     context = &contextRef;
     owner = &contextRef.getOwner();
     kernel = context->getPlatform().createKernel(IntegrateDrudeSCFStepKernel::Name(), contextRef);


Test patch:
diff --git a/wrappers/python/tests/TestAPIUnits.py b/wrappers/python/tests/TestAPIUnits.py
index 6c404754c8..5522bcc452 100644
--- a/wrappers/python/tests/TestAPIUnits.py
+++ b/wrappers/python/tests/TestAPIUnits.py
@@ -1051,7 +1051,7 @@ def testDrudeLangevinIntegrator(self):
         self.assertEqual(integrator.getStepSize(), 0.1*femtosecond)
         integrator.setStepSize(0.0005)
         self.assertEqual(integrator.getStepSize(), 0.0005*picosecond)
-        self.assertEqual(integrator.getMaxDrudeDistance(), 0*nanometer)
+        self.assertEqual(integrator.getMaxDrudeDistance(), 0.02*nanometer)
         integrator.setMaxDrudeDistance(0.05)
         self.assertEqual(integrator.getMaxDrudeDistance(), 0.05*nanometer)