Repository: openmm/openmm — pull request #4734 (cpp)
Title: Serialization records integration force groups

Fixes #4730.

While fixing this, I realized our handling of force groups for CompoundIntegrator was inconsistent.  The CompoundIntegrator stored a list of force groups, and so did each integrator it contained.  They were set independently and used inconsistently.  Performing integration would use the groups for the currently selected integrator.  Energy minimization would use the groups for the CompoundIntegrator itself.

I therefore changed the behavior so the CompoundIntegrator doesn't have its own independent set of force groups.  `getIntegrationForceGroups()` returns the groups for the currently selected integrator.  `setIntegrationForceGroups()` sets the groups for *all* contained integrators.

Resolved issues:
title: Deepcopying integrators resets integration force groups
body: It seems that serializing an integrator doesn't record the integration force group, which means any deepcopy of an integrator will have them reset to the default (all force groups).

```
import openmm as mm
from openmm import unit as u
from copy import deepcopy

integrator = mm.LangevinMiddleIntegrator(300*u.kelvin, 1/u.picosecond, 0.002*u.picosecond)
integrator.setIntegrationForceGroups({0})
integrator_copy = deepcopy(integrator)
print(integrator.getIntegrationForceGroups())
print(integrator_copy.getIntegrationForceGroups())
```
1
-1

Could this be an oversight?

### Minimal API referenced by tests (typed signatures)
_Added only when tests call brand‑new symbols introduced by the fix; types reflect annotations present in the repo or `Unknown` if not annotated._

- Function in `openmmapi/src/CompoundIntegrator.cpp`: `def int CompoundIntegrator::getIntegrationForceGroups() -> int: ...`
  **Output:** int

- Function in `openmmapi/src/CompoundIntegrator.cpp`: `def void CompoundIntegrator::setIntegrationForceGroups(groups: int) -> void: ...`
  **Inputs:** groups: int
  **Output:** void


```python
def int CompoundIntegrator::getIntegrationForceGroups() -> int: ...
def void CompoundIntegrator::setIntegrationForceGroups(groups) -> void: ...
```

## 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/openmmapi/include/openmm/CompoundIntegrator.h b/openmmapi/include/openmm/CompoundIntegrator.h
index 3839648988..dea22f1fbc 100644
--- a/openmmapi/include/openmm/CompoundIntegrator.h
+++ b/openmmapi/include/openmm/CompoundIntegrator.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) 2015-2020 Stanford University and the Authors.      *
+ * Portions copyright (c) 2015-2024 Stanford University and the Authors.      *
  * Authors: Peter Eastman                                                     *
  * Contributors:                                                              *
  *                                                                            *
@@ -158,6 +158,23 @@ class OPENMM_EXPORT CompoundIntegrator : public Integrator {
      * @param steps   the number of time steps to take
      */
     void step(int steps);
+    /**
+     * Get which force groups to use for integration.  By default, all force groups
+     * are included.  This is interpreted as a set of bit flags: the forces from group i
+     * will be included if (groups&(1<<i)) != 0.
+     * 
+     * This method returns the integration force groups for the current Integator.
+     */
+    int getIntegrationForceGroups() const;
+    /**
+     * Set which force groups to use for integration.  By default, all force groups
+     * are included.  This is interpreted as a set of bit flags: the forces from group i
+     * will be included if (groups&(1<<i)) != 0.
+     * 
+     * Calling this method sets the integration force groups for all Integrators
+     * containined in this CompoundIntegrator.
+     */
+    void setIntegrationForceGroups(int groups);
 protected:
     /**
      * This will be called by the Context when it is created.  It informs the Integrator
diff --git a/openmmapi/src/CompoundIntegrator.cpp b/openmmapi/src/CompoundIntegrator.cpp
index d75760276d..aa22a245ab 100644
--- a/openmmapi/src/CompoundIntegrator.cpp
+++ b/openmmapi/src/CompoundIntegrator.cpp
@@ -6,7 +6,7 @@
  * Biological Structures at Stanford, funded under the NIH Roadmap for        *
  * Medical Research, grant U54 GM072970. See https://simtk.org.               *
  *                                                                            *
- * Portions copyright (c) 2015-2020 Stanford University and the Authors.      *
+ * Portions copyright (c) 2015-2024 Stanford University and the Authors.      *
  * Authors: Peter Eastman                                                     *
  * Contributors:                                                              *
  *                                                                            *
@@ -99,6 +99,15 @@ void CompoundIntegrator::step(int steps) {
     integrators[currentIntegrator]->step(steps);
 }
 
+int CompoundIntegrator::getIntegrationForceGroups() const {
+    return integrators[currentIntegrator]->getIntegrationForceGroups();
+}
+
+void CompoundIntegrator::setIntegrationForceGroups(int groups) {
+    for (int i = 0; i < integrators.size(); i++)
+        integrators[i]->setIntegrationForceGroups(groups);
+}
+
 void CompoundIntegrator::initialize(ContextImpl& context) {
     if (integrators.size() == 0)
         throw OpenMMException("CompoundIntegrator must contain at least one Integrator");
diff --git a/plugins/drude/serialization/src/DrudeLangevinIntegratorProxy.cpp b/plugins/drude/serialization/src/DrudeLangevinIntegratorProxy.cpp
index 1952ab05b4..ddf3d3f49c 100644
--- a/plugins/drude/serialization/src/DrudeLangevinIntegratorProxy.cpp
+++ b/plugins/drude/serialization/src/DrudeLangevinIntegratorProxy.cpp
@@ -6,7 +6,7 @@
  * Biological Structures at Stanford, funded under the NIH Roadmap for        *
  * Medical Research, grant U54 GM072970. See https://simtk.org.               *
  *                                                                            *
- * Portions copyright (c) 2013 Stanford University and the Authors.           *
+ * Portions copyright (c) 2013-2024 Stanford University and the Authors.      *
  * Authors: Peter Eastman                                                     *
  * Contributors:                                                              *
  *                                                                            *
@@ -50,6 +50,7 @@ void DrudeLangevinIntegratorProxy::serialize(const void* object, SerializationNo
     node.setDoubleProperty("drudeTemperature", integrator.getDrudeTemperature());
     node.setDoubleProperty("drudeFriction", integrator.getDrudeFriction());
     node.setIntProperty("randomSeed", integrator.getRandomNumberSeed());
+    node.setIntProperty("integrationForceGroups", integrator.getIntegrationForceGroups());
 }
 
 void* DrudeLangevinIntegratorProxy::deserialize(const SerializationNode& node) const {
@@ -60,5 +61,6 @@ void* DrudeLangevinIntegratorProxy::deserialize(const SerializationNode& node) c
             node.getDoubleProperty("drudeFriction"), node.getDoubleProperty("stepSize"));
     integrator->setConstraintTolerance(node.getDoubleProperty("constraintTolerance"));
     integrator->setRandomNumberSeed(node.getIntProperty("randomSeed"));
+    integrator->setIntegrationForceGroups(node.getIntProperty("integrationForceGroups", 0xFFFFFFFF));
     return integrator;
 }
\ No newline at end of file
diff --git a/plugins/drude/serialization/src/DrudeNoseHooverIntegratorProxy.cpp b/plugins/drude/serialization/src/DrudeNoseHooverIntegratorProxy.cpp
index 452d16d136..62a745913f 100644
--- a/plugins/drude/serialization/src/DrudeNoseHooverIntegratorProxy.cpp
+++ b/plugins/drude/serialization/src/DrudeNoseHooverIntegratorProxy.cpp
@@ -6,7 +6,7 @@
  * Biological Structures at Stanford, funded under the NIH Roadmap for        *
  * Medical Research, grant U54 GM072970. See https://simtk.org.               *
  *                                                                            *
- * Portions copyright (c) 2010 Stanford University and the Authors.           *
+ * Portions copyright (c) 2010-2024 Stanford University and the Authors.      *
  * Authors: Andrew C. Simmonett, Andreas Krämer                               *
  * Contributors:                                                              *
  *                                                                            *
@@ -57,6 +57,7 @@ void DrudeNoseHooverIntegratorProxy::serialize(const void* object, Serialization
     node.setIntProperty("chainLength", integrator.getThermostat().getChainLength());
     node.setIntProperty("numMTS", integrator.getThermostat().getNumMultiTimeSteps());
     node.setIntProperty("numYS", integrator.getThermostat().getNumYoshidaSuzukiTimeSteps());
+    node.setIntProperty("integrationForceGroups", integrator.getIntegrationForceGroups());
 }
 
 void* DrudeNoseHooverIntegratorProxy::deserialize(const SerializationNode& node) const {
@@ -75,6 +76,7 @@ void* DrudeNoseHooverIntegratorProxy::deserialize(const SerializationNode& node)
     );
     integrator->setConstraintTolerance(node.getDoubleProperty("constraintTolerance"));
     integrator->setMaximumPairDistance(node.getDoubleProperty("maximumPairDistance"));
+    integrator->setIntegrationForceGroups(node.getIntProperty("integrationForceGroups", 0xFFFFFFFF));
 
     return integrator;
 }
diff --git a/serialization/src/BrownianIntegratorProxy.cpp b/serialization/src/BrownianIntegratorProxy.cpp
index 132d00e9f3..725305b13e 100644
--- a/serialization/src/BrownianIntegratorProxy.cpp
+++ b/serialization/src/BrownianIntegratorProxy.cpp
@@ -6,7 +6,7 @@
  * Biological Structures at Stanford, funded under the NIH Roadmap for        *
  * Medical Research, grant U54 GM072970. See https://simtk.org.               *
  *                                                                            *
- * Portions copyright (c) 2010 Stanford University and the Authors.           *
+ * Portions copyright (c) 2010-2024 Stanford University and the Authors.      *
  * Authors: Peter Eastman, Yutong Zhao                                        *
  * Contributors:                                                              *
  *                                                                            *
@@ -47,6 +47,7 @@ void BrownianIntegratorProxy::serialize(const void* object, SerializationNode& n
     node.setDoubleProperty("temperature", integrator.getTemperature());
     node.setDoubleProperty("friction", integrator.getFriction());
     node.setIntProperty("randomSeed", integrator.getRandomNumberSeed());
+    node.setIntProperty("integrationForceGroups", integrator.getIntegrationForceGroups());
 }
 
 void* BrownianIntegratorProxy::deserialize(const SerializationNode& node) const {
@@ -57,5 +58,6 @@ void* BrownianIntegratorProxy::deserialize(const SerializationNode& node) const
                                                             node.getDoubleProperty("stepSize"));
     integrator->setConstraintTolerance(node.getDoubleProperty("constraintTolerance"));
     integrator->setRandomNumberSeed(node.getIntProperty("randomSeed"));
+    integrator->setIntegrationForceGroups(node.getIntProperty("integrationForceGroups", 0xFFFFFFFF));
     return integrator;
 }
\ No newline at end of file
diff --git a/serialization/src/CustomIntegratorProxy.cpp b/serialization/src/CustomIntegratorProxy.cpp
index 86ab647835..ca370fb5a5 100644
--- a/serialization/src/CustomIntegratorProxy.cpp
+++ b/serialization/src/CustomIntegratorProxy.cpp
@@ -65,6 +65,7 @@ void CustomIntegratorProxy::serialize(const void* object, SerializationNode& nod
         functions.createChildNode("Function", &integrator.getTabulatedFunction(i)).setStringProperty("name", integrator.getTabulatedFunctionName(i));
     node.setStringProperty("kineticEnergyExpression", integrator.getKineticEnergyExpression());
     node.setIntProperty("randomSeed", integrator.getRandomNumberSeed());
+    node.setIntProperty("integrationForceGroups", integrator.getIntegrationForceGroups());
     node.setDoubleProperty("stepSize", integrator.getStepSize());
     node.setDoubleProperty("constraintTolerance", integrator.getConstraintTolerance());
 }
@@ -124,6 +125,7 @@ void* CustomIntegratorProxy::deserialize(const SerializationNode& node) const {
     }
     integrator->setKineticEnergyExpression(node.getStringProperty("kineticEnergyExpression"));
     integrator->setRandomNumberSeed(node.getIntProperty("randomSeed"));
+    integrator->setIntegrationForceGroups(node.getIntProperty("integrationForceGroups", 0xFFFFFFFF));
     integrator->setConstraintTolerance(node.getDoubleProperty("constraintTolerance"));
     return integrator;
 }
diff --git a/serialization/src/LangevinIntegratorProxy.cpp b/serialization/src/LangevinIntegratorProxy.cpp
index e978b6ea17..0773503eb9 100644
--- a/serialization/src/LangevinIntegratorProxy.cpp
+++ b/serialization/src/LangevinIntegratorProxy.cpp
@@ -6,7 +6,7 @@
  * Biological Structures at Stanford, funded under the NIH Roadmap for        *
  * Medical Research, grant U54 GM072970. See https://simtk.org.               *
  *                                                                            *
- * Portions copyright (c) 2010 Stanford University and the Authors.           *
+ * Portions copyright (c) 2010-2024 Stanford University and the Authors.      *
  * Authors: Peter Eastman, Yutong Zhao                                        *
  * Contributors:                                                              *
  *                                                                            *
@@ -47,6 +47,7 @@ void LangevinIntegratorProxy::serialize(const void* object, SerializationNode& n
     node.setDoubleProperty("temperature", integrator.getTemperature());
     node.setDoubleProperty("friction", integrator.getFriction());
     node.setIntProperty("randomSeed", integrator.getRandomNumberSeed());
+    node.setIntProperty("integrationForceGroups", integrator.getIntegrationForceGroups());
 }
 
 void* LangevinIntegratorProxy::deserialize(const SerializationNode& node) const {
@@ -57,5 +58,6 @@ void* LangevinIntegratorProxy::deserialize(const SerializationNode& node) const
                                                             node.getDoubleProperty("stepSize"));
     integrator->setConstraintTolerance(node.getDoubleProperty("constraintTolerance"));
     integrator->setRandomNumberSeed(node.getIntProperty("randomSeed"));
+    integrator->setIntegrationForceGroups(node.getIntProperty("integrationForceGroups", 0xFFFFFFFF));
     return integrator;
 }
\ No newline at end of file
diff --git a/serialization/src/LangevinMiddleIntegratorProxy.cpp b/serialization/src/LangevinMiddleIntegratorProxy.cpp
index 4d6d664e20..7516df850f 100644
--- a/serialization/src/LangevinMiddleIntegratorProxy.cpp
+++ b/serialization/src/LangevinMiddleIntegratorProxy.cpp
@@ -6,7 +6,7 @@
  * Biological Structures at Stanford, funded under the NIH Roadmap for        *
  * Medical Research, grant U54 GM072970. See https://simtk.org.               *
  *                                                                            *
- * Portions copyright (c) 2010-2019 Stanford University and the Authors.      *
+ * Portions copyright (c) 2010-2024 Stanford University and the Authors.      *
  * Authors: Peter Eastman, Yutong Zhao                                        *
  * Contributors:                                                              *
  *                                                                            *
@@ -47,6 +47,7 @@ void LangevinMiddleIntegratorProxy::serialize(const void* object, SerializationN
     node.setDoubleProperty("temperature", integrator.getTemperature());
     node.setDoubleProperty("friction", integrator.getFriction());
     node.setIntProperty("randomSeed", integrator.getRandomNumberSeed());
+    node.setIntProperty("integrationForceGroups", integrator.getIntegrationForceGroups());
 }
 
 void* LangevinMiddleIntegratorProxy::deserialize(const SerializationNode& node) const {
@@ -56,5 +57,6 @@ void* LangevinMiddleIntegratorProxy::deserialize(const SerializationNode& node)
             node.getDoubleProperty("friction"), node.getDoubleProperty("stepSize"));
     integrator->setConstraintTolerance(node.getDoubleProperty("constraintTolerance"));
     integrator->setRandomNumberSeed(node.getIntProperty("randomSeed"));
+    integrator->setIntegrationForceGroups(node.getIntProperty("integrationForceGroups", 0xFFFFFFFF));
     return integrator;
 }
\ No newline at end of file
diff --git a/serialization/src/NoseHooverIntegratorProxy.cpp b/serialization/src/NoseHooverIntegratorProxy.cpp
index a181d09bef..620281e02d 100644
--- a/serialization/src/NoseHooverIntegratorProxy.cpp
+++ b/serialization/src/NoseHooverIntegratorProxy.cpp
@@ -6,7 +6,7 @@
  * Biological Structures at Stanford, funded under the NIH Roadmap for        *
  * Medical Research, grant U54 GM072970. See https://simtk.org.               *
  *                                                                            *
- * Portions copyright (c) 2010 Stanford University and the Authors.           *
+ * Portions copyright (c) 2010-2024 Stanford University and the Authors.      *
  * Authors: Andrew C. Simmonett, Andreas Krämer                               *
  * Contributors:                                                              *
  *                                                                            *
@@ -47,6 +47,7 @@ void NoseHooverIntegratorProxy::serialize(const void* object, SerializationNode&
     node.setDoubleProperty("constraintTolerance", integrator.getConstraintTolerance());
     node.setDoubleProperty("maximumPairDistance", integrator.getMaximumPairDistance());
     node.setBoolProperty("hasSubsystemThermostats", integrator.hasSubsystemThermostats());
+    node.setIntProperty("integrationForceGroups", integrator.getIntegrationForceGroups());
     if (integrator.hasSubsystemThermostats()) {
         // Serialize all thermostats separately
         for (int i = 0; i < integrator.getNumThermostats(); i++){
@@ -123,6 +124,7 @@ void* NoseHooverIntegratorProxy::deserialize(const SerializationNode& node) cons
     }
     integrator->setConstraintTolerance(node.getDoubleProperty("constraintTolerance"));
     integrator->setMaximumPairDistance(node.getDoubleProperty("maximumPairDistance"));
+    integrator->setIntegrationForceGroups(node.getIntProperty("integrationForceGroups", 0xFFFFFFFF));
 
     return integrator;
 }
diff --git a/serialization/src/VariableLangevinIntegratorProxy.cpp b/serialization/src/VariableLangevinIntegratorProxy.cpp
index 079cd54636..6a7c8c3389 100644
--- a/serialization/src/VariableLangevinIntegratorProxy.cpp
+++ b/serialization/src/VariableLangevinIntegratorProxy.cpp
@@ -6,7 +6,7 @@
  * Biological Structures at Stanford, funded under the NIH Roadmap for        *
  * Medical Research, grant U54 GM072970. See https://simtk.org.               *
  *                                                                            *
- * Portions copyright (c) 2010-2019 Stanford University and the Authors.      *
+ * Portions copyright (c) 2010-2024 Stanford University and the Authors.      *
  * Authors: Peter Eastman, Yutong Zhao                                        *
  * Contributors:                                                              *
  *                                                                            *
@@ -48,6 +48,7 @@ void VariableLangevinIntegratorProxy::serialize(const void* object, Serializatio
     node.setDoubleProperty("errorTol", integrator.getErrorTolerance());
     node.setDoubleProperty("maxStepSize", integrator.getMaximumStepSize());
     node.setIntProperty("randomSeed", integrator.getRandomNumberSeed());
+    node.setIntProperty("integrationForceGroups", integrator.getIntegrationForceGroups());
 }
 
 void* VariableLangevinIntegratorProxy::deserialize(const SerializationNode& node) const {
@@ -60,6 +61,7 @@ void* VariableLangevinIntegratorProxy::deserialize(const SerializationNode& node
     integrator->setStepSize(node.getDoubleProperty("stepSize"));
     integrator->setConstraintTolerance(node.getDoubleProperty("constraintTolerance"));
     integrator->setRandomNumberSeed(node.getIntProperty("randomSeed"));
+    integrator->setIntegrationForceGroups(node.getIntProperty("integrationForceGroups", 0xFFFFFFFF));
     if (version > 1)
         integrator->setMaximumStepSize(node.getDoubleProperty("maxStepSize"));
     return integrator;
diff --git a/serialization/src/VariableVerletIntegratorProxy.cpp b/serialization/src/VariableVerletIntegratorProxy.cpp
index 957db3eaad..a6162d1e1a 100644
--- a/serialization/src/VariableVerletIntegratorProxy.cpp
+++ b/serialization/src/VariableVerletIntegratorProxy.cpp
@@ -6,7 +6,7 @@
  * Biological Structures at Stanford, funded under the NIH Roadmap for        *
  * Medical Research, grant U54 GM072970. See https://simtk.org.               *
  *                                                                            *
- * Portions copyright (c) 2010-2019 Stanford University and the Authors.      *
+ * Portions copyright (c) 2010-2024 Stanford University and the Authors.      *
  * Authors: Peter Eastman, Yutong Zhao                                        *
  * Contributors:                                                              *
  *                                                                            *
@@ -45,6 +45,7 @@ void VariableVerletIntegratorProxy::serialize(const void* object, SerializationN
     node.setDoubleProperty("maxStepSize", integrator.getMaximumStepSize());
     node.setDoubleProperty("stepSize", integrator.getStepSize());
     node.setDoubleProperty("constraintTolerance", integrator.getConstraintTolerance());
+    node.setIntProperty("integrationForceGroups", integrator.getIntegrationForceGroups());
 }
 
 void* VariableVerletIntegratorProxy::deserialize(const SerializationNode& node) const {
@@ -54,6 +55,7 @@ void* VariableVerletIntegratorProxy::deserialize(const SerializationNode& node)
     VariableVerletIntegrator *integrator = new VariableVerletIntegrator(node.getDoubleProperty("errorTol"));
     integrator->setStepSize(node.getDoubleProperty("stepSize"));
     integrator->setConstraintTolerance(node.getDoubleProperty("constraintTolerance"));
+    integrator->setIntegrationForceGroups(node.getIntProperty("integrationForceGroups", 0xFFFFFFFF));
     if (version > 1)
         integrator->setMaximumStepSize(node.getDoubleProperty("maxStepSize"));
     return integrator;
diff --git a/serialization/src/VerletIntegratorProxy.cpp b/serialization/src/VerletIntegratorProxy.cpp
index 53f7f77fe8..c3e4a35e98 100644
--- a/serialization/src/VerletIntegratorProxy.cpp
+++ b/serialization/src/VerletIntegratorProxy.cpp
@@ -6,7 +6,7 @@
  * Biological Structures at Stanford, funded under the NIH Roadmap for        *
  * Medical Research, grant U54 GM072970. See https://simtk.org.               *
  *                                                                            *
- * Portions copyright (c) 2010 Stanford University and the Authors.           *
+ * Portions copyright (c) 2010-2024 Stanford University and the Authors.      *
  * Authors: Peter Eastman, Yutong Zhao                                        *
  * Contributors:                                                              *
  *                                                                            *
@@ -44,6 +44,7 @@ void VerletIntegratorProxy::serialize(const void* object, SerializationNode& nod
     const VerletIntegrator& integrator = *reinterpret_cast<const VerletIntegrator*>(object);
     node.setDoubleProperty("stepSize", integrator.getStepSize());
     node.setDoubleProperty("constraintTolerance", integrator.getConstraintTolerance());
+    node.setIntProperty("integrationForceGroups", integrator.getIntegrationForceGroups());
 }
 
 void* VerletIntegratorProxy::deserialize(const SerializationNode& node) const {
@@ -51,5 +52,6 @@ void* VerletIntegratorProxy::deserialize(const SerializationNode& node) const {
         throw OpenMMException("Unsupported version number");
     VerletIntegrator *integrator = new VerletIntegrator(node.getDoubleProperty("stepSize"));
     integrator->setConstraintTolerance(node.getDoubleProperty("constraintTolerance"));
+    integrator->setIntegrationForceGroups(node.getIntProperty("integrationForceGroups", 0xFFFFFFFF));
     return integrator;
 }
\ No newline at end of file


Test patch:
diff --git a/plugins/drude/serialization/tests/TestSerializeDrudeLangevinIntegrator.cpp b/plugins/drude/serialization/tests/TestSerializeDrudeLangevinIntegrator.cpp
index d11e4f4fca..df366aabce 100644
--- a/plugins/drude/serialization/tests/TestSerializeDrudeLangevinIntegrator.cpp
+++ b/plugins/drude/serialization/tests/TestSerializeDrudeLangevinIntegrator.cpp
@@ -6,7 +6,7 @@
  * Biological Structures at Stanford, funded under the NIH Roadmap for        *
  * Medical Research, grant U54 GM072970. See https://simtk.org.               *
  *                                                                            *
- * Portions copyright (c) 2013 Stanford University and the Authors.           *
+ * Portions copyright (c) 2013-2024 Stanford University and the Authors.      *
  * Authors: Peter Eastman                                                     *
  * Contributors:                                                              *
  *                                                                            *
@@ -46,6 +46,7 @@ void testSerialization() {
 
     DrudeLangevinIntegrator integ1(301.1, 0.95, 10.5, 1.2, 0.001);
     integ1.setRandomNumberSeed(18);
+    integ1.setIntegrationForceGroups(3);
 
     // Serialize and then deserialize it.
 
@@ -61,6 +62,7 @@ void testSerialization() {
     ASSERT_EQUAL(integ1.getDrudeTemperature(), integ2.getDrudeTemperature());
     ASSERT_EQUAL(integ1.getDrudeFriction(), integ2.getDrudeFriction());
     ASSERT_EQUAL(integ1.getRandomNumberSeed(), integ2.getRandomNumberSeed());
+    ASSERT_EQUAL(integ1.getIntegrationForceGroups(), integ2.getIntegrationForceGroups());
 }
 
 int main() {
diff --git a/plugins/drude/serialization/tests/TestSerializeDrudeNoseHooverIntegrator.cpp b/plugins/drude/serialization/tests/TestSerializeDrudeNoseHooverIntegrator.cpp
index fd6859c35b..696f89a612 100644
--- a/plugins/drude/serialization/tests/TestSerializeDrudeNoseHooverIntegrator.cpp
+++ b/plugins/drude/serialization/tests/TestSerializeDrudeNoseHooverIntegrator.cpp
@@ -6,7 +6,7 @@
  * Biological Structures at Stanford, funded under the NIH Roadmap for        *
  * Medical Research, grant U54 GM072970. See https://simtk.org.               *
  *                                                                            *
- * Portions copyright (c) 2010-2014 Stanford University and the Authors.      *
+ * Portions copyright (c) 2010-2024 Stanford University and the Authors.      *
  * Authors: Andrew C. Simmonett and Andreas Kraemer
  * Contributors:                                                              *
  *                                                                            *
@@ -51,6 +51,7 @@ void assertIntegratorsEqual(const DrudeNoseHooverIntegrator& integrator1, const
     ASSERT_EQUAL(integrator1.getConstraintTolerance(), integrator2.getConstraintTolerance());
     ASSERT_EQUAL(integrator1.getMaximumPairDistance(), integrator2.getMaximumPairDistance());
     ASSERT_EQUAL(integrator1.getNumThermostats(), integrator2.getNumThermostats());
+    ASSERT_EQUAL(integrator1.getIntegrationForceGroups(), integrator2.getIntegrationForceGroups());
     for (int i = 0; i < integrator1.getNumThermostats(); i++) {
         const auto &thermostat1 = integrator1.getThermostat(i);
         const auto &thermostat2 = integrator2.getThermostat(i);
@@ -84,6 +85,7 @@ void testSerialization() {
     system.addForce(new DrudeForce());
     for (int i=0; i<10; i++) system.addParticle(1.0);
     DrudeNoseHooverIntegrator integrator(331, 0.21, 1.1, 0.1, 0.004, 5, 5, 5);
+    integrator.setIntegrationForceGroups(3);
 
     // Serialize and then deserialize it.
     stringstream buffer2;
diff --git a/serialization/tests/TestSerializeIntegrator.cpp b/serialization/tests/TestSerializeIntegrator.cpp
index 2663de097a..4bafd0377f 100644
--- a/serialization/tests/TestSerializeIntegrator.cpp
+++ b/serialization/tests/TestSerializeIntegrator.cpp
@@ -52,17 +52,21 @@ using namespace std;
 
 void testSerializeVerletIntegrator() {
     VerletIntegrator *intg = new VerletIntegrator(0.00342);
+    intg->setIntegrationForceGroups(3);
     stringstream ss;
     XmlSerializer::serialize<Integrator>(intg, "VerletIntegrator", ss);
     VerletIntegrator *intg2 = dynamic_cast<VerletIntegrator*>(XmlSerializer::deserialize<Integrator>(ss));
     ASSERT_EQUAL(intg->getConstraintTolerance(), intg2->getConstraintTolerance());
     ASSERT_EQUAL(intg->getStepSize(), intg2->getStepSize());
+    ASSERT_EQUAL(intg->getIntegrationForceGroups(), intg2->getIntegrationForceGroups());
     delete intg;
     delete intg2;
 }
 
 void testSerializeLangevinIntegrator() {
     LangevinIntegrator *intg = new LangevinIntegrator(372.4, 1.234, 0.0018);
+    intg->setRandomNumberSeed(10);
+    intg->setIntegrationForceGroups(3);
     stringstream ss;
     XmlSerializer::serialize<Integrator>(intg, "LangevinIntegrator", ss);
     LangevinIntegrator *intg2 = dynamic_cast<LangevinIntegrator*>(XmlSerializer::deserialize<Integrator>(ss));
@@ -71,12 +75,15 @@ void testSerializeLangevinIntegrator() {
     ASSERT_EQUAL(intg->getTemperature(), intg2->getTemperature());
     ASSERT_EQUAL(intg->getFriction(), intg2->getFriction());
     ASSERT_EQUAL(intg->getRandomNumberSeed(), intg2->getRandomNumberSeed());
+    ASSERT_EQUAL(intg->getIntegrationForceGroups(), intg2->getIntegrationForceGroups());
     delete intg;
     delete intg2;
 }
 
 void testSerializeLangevinMiddleIntegrator() {
     LangevinMiddleIntegrator *intg = new LangevinMiddleIntegrator(372.4, 1.234, 0.0018);
+    intg->setRandomNumberSeed(10);
+    intg->setIntegrationForceGroups(3);
     stringstream ss;
     XmlSerializer::serialize<Integrator>(intg, "LangevinMiddleIntegrator", ss);
     LangevinMiddleIntegrator *intg2 = dynamic_cast<LangevinMiddleIntegrator*>(XmlSerializer::deserialize<Integrator>(ss));
@@ -85,12 +92,15 @@ void testSerializeLangevinMiddleIntegrator() {
     ASSERT_EQUAL(intg->getTemperature(), intg2->getTemperature());
     ASSERT_EQUAL(intg->getFriction(), intg2->getFriction());
     ASSERT_EQUAL(intg->getRandomNumberSeed(), intg2->getRandomNumberSeed());
+    ASSERT_EQUAL(intg->getIntegrationForceGroups(), intg2->getIntegrationForceGroups());
     delete intg;
     delete intg2;
 }
 
 void testSerializeBrownianIntegrator() {
     BrownianIntegrator *intg = new BrownianIntegrator(243.1, 3.234, 0.0021);
+    intg->setRandomNumberSeed(10);
+    intg->setIntegrationForceGroups(3);
     stringstream ss;
     XmlSerializer::serialize<Integrator>(intg, "BrownianIntegrator", ss);
     BrownianIntegrator *intg2 = dynamic_cast<BrownianIntegrator*>(XmlSerializer::deserialize<Integrator>(ss));
@@ -99,6 +109,7 @@ void testSerializeBrownianIntegrator() {
     ASSERT_EQUAL(intg->getTemperature(), intg2->getTemperature());
     ASSERT_EQUAL(intg->getFriction(), intg2->getFriction());
     ASSERT_EQUAL(intg->getRandomNumberSeed(), intg2->getRandomNumberSeed());
+    ASSERT_EQUAL(intg->getIntegrationForceGroups(), intg2->getIntegrationForceGroups());
     delete intg;
     delete intg2;
 }
@@ -106,6 +117,7 @@ void testSerializeBrownianIntegrator() {
 void testSerializeVariableVerletIntegrator() {
     VariableVerletIntegrator intg(0.04234);
     intg.setMaximumStepSize(0.32);
+    intg.setIntegrationForceGroups(3);
     stringstream ss;
     XmlSerializer::serialize<Integrator>(&intg, "VariableVerletIntegrator", ss);
     VariableVerletIntegrator *intg2 = dynamic_cast<VariableVerletIntegrator*>(XmlSerializer::deserialize<Integrator>(ss));
@@ -113,12 +125,15 @@ void testSerializeVariableVerletIntegrator() {
     ASSERT_EQUAL(intg.getStepSize(), intg2->getStepSize());
     ASSERT_EQUAL(intg.getErrorTolerance(), intg2->getErrorTolerance());
     ASSERT_EQUAL(intg.getMaximumStepSize(), intg2->getMaximumStepSize());
+    ASSERT_EQUAL(intg.getIntegrationForceGroups(), intg2->getIntegrationForceGroups());
     delete intg2;
 }
 
 void testSerializeVariableLangevinIntegrator() {
     VariableLangevinIntegrator intg(243.1, 3.234, 0.0021);
     intg.setMaximumStepSize(0.32);
+    intg.setRandomNumberSeed(10);
+    intg.setIntegrationForceGroups(3);
     stringstream ss;
     XmlSerializer::serialize<Integrator>(&intg, "VariableLangevinIntegrator", ss);
     VariableLangevinIntegrator *intg2 = dynamic_cast<VariableLangevinIntegrator*>(XmlSerializer::deserialize<Integrator>(ss));
@@ -129,11 +144,14 @@ void testSerializeVariableLangevinIntegrator() {
     ASSERT_EQUAL(intg.getTemperature(), intg2->getTemperature());
     ASSERT_EQUAL(intg.getRandomNumberSeed(), intg2->getRandomNumberSeed());
     ASSERT_EQUAL(intg.getMaximumStepSize(), intg2->getMaximumStepSize());
+    ASSERT_EQUAL(intg.getIntegrationForceGroups(), intg2->getIntegrationForceGroups());
     delete intg2;
 }
 
 void testSerializeCustomIntegrator() {
     CustomIntegrator *intg = new CustomIntegrator(0.002234);
+    intg->setRandomNumberSeed(10);
+    intg->setIntegrationForceGroups(3);
     intg->addPerDofVariable("temp",0);
     vector<Vec3> initialValues(123);
     for(int i = 0; i < 123; i++)
@@ -209,6 +227,7 @@ void testSerializeCustomIntegrator() {
     }
     ASSERT_EQUAL(intg->getKineticEnergyExpression(), intg2->getKineticEnergyExpression());
     ASSERT_EQUAL(intg->getRandomNumberSeed(), intg2->getRandomNumberSeed());
+    ASSERT_EQUAL(intg->getIntegrationForceGroups(), intg2->getIntegrationForceGroups());
     ASSERT_EQUAL(intg->getStepSize(), intg2->getStepSize());
     ASSERT_EQUAL(intg->getConstraintTolerance(), intg2->getConstraintTolerance());
     ASSERT_EQUAL(intg->getNumTabulatedFunctions(), intg2->getNumTabulatedFunctions());
@@ -233,10 +252,12 @@ void testSerializeCompoundIntegrator() {
     integ.addIntegrator(new LangevinIntegrator(372.4, 1.234, 0.0018));
     integ.addIntegrator(new VerletIntegrator(0.002));
     integ.setCurrentIntegrator(1);
+    integ.setIntegrationForceGroups(3);
     stringstream ss;
     XmlSerializer::serialize<Integrator>(&integ, "CompoundIntegrator", ss);
     CompoundIntegrator *integ2 = dynamic_cast<CompoundIntegrator*>(XmlSerializer::deserialize<Integrator>(ss));
     ASSERT_EQUAL(integ.getCurrentIntegrator(), integ2->getCurrentIntegrator());
+    ASSERT_EQUAL(integ.getIntegrationForceGroups(), integ2->getIntegrationForceGroups());
     LangevinIntegrator& langevin1 = dynamic_cast<LangevinIntegrator&>(integ.getIntegrator(0));
     LangevinIntegrator& langevin2 = dynamic_cast<LangevinIntegrator&>(integ2->getIntegrator(0));
     ASSERT_EQUAL(langevin1.getConstraintTolerance(), langevin2.getConstraintTolerance());
diff --git a/serialization/tests/TestSerializeNoseHooverIntegrator.cpp b/serialization/tests/TestSerializeNoseHooverIntegrator.cpp
index 4a244b5be0..ef23e036c3 100644
--- a/serialization/tests/TestSerializeNoseHooverIntegrator.cpp
+++ b/serialization/tests/TestSerializeNoseHooverIntegrator.cpp
@@ -45,6 +45,7 @@ void assertIntegratorsEqual(const NoseHooverIntegrator& integrator1, const NoseH
     ASSERT_EQUAL(integrator1.getConstraintTolerance(), integrator2.getConstraintTolerance());
     ASSERT_EQUAL(integrator1.getMaximumPairDistance(), integrator2.getMaximumPairDistance());
     ASSERT_EQUAL(integrator1.getNumThermostats(), integrator2.getNumThermostats());
+    ASSERT_EQUAL(integrator1.getIntegrationForceGroups(), integrator2.getIntegrationForceGroups());
     for (int i = 0; i < integrator1.getNumThermostats(); i++) {
         const auto &thermostat1 = integrator1.getThermostat(i);
         const auto &thermostat2 = integrator2.getThermostat(i);
@@ -82,6 +83,7 @@ void testSerialization() {
     integrator_sub.addSubsystemThermostat(
         {0,1,2,3,4,7}, {{0,7}}, 301.1, 1.1, 1.2, 1.3, 9, 2, 5
     );
+    integrator_sub.setIntegrationForceGroups(3);
 
     // Serialize and then deserialize it.