Repository: openmm/openmm — pull request #5091 (cpp)
Title: Create a new dictionary every time `_TemplateAtomData.__init__()` is called without `parameters`

See #5075.

Resolved issues:
title: Create a new dictionary every time `_TemplateAtomData.__init__()` is called without `parameters`
body: When creating a new instance of a template atom in the system, the parameter dictionary is shared across multiple instances if no specific parameters are provided. This behavior can lead to unintended side effects, as modifications to the dictionary in one instance will affect all other instances that share it. This issue arises because the same dictionary object is reused by default.

The expected behavior is that each new instance of a template atom should have its own distinct parameter dictionary, even when no parameters are explicitly provided. This ensures that changes to the parameters of one instance do not inadvertently impact other instances.

## 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/openmm/app/forcefield.py b/wrappers/python/openmm/app/forcefield.py
index a3226b7248..5c872ea240 100644
--- a/wrappers/python/openmm/app/forcefield.py
+++ b/wrappers/python/openmm/app/forcefield.py
@@ -744,11 +744,11 @@ def areParametersIdentical(self, template2, matchingAtoms, matchingAtoms2):
 
     class _TemplateAtomData(object):
         """Inner class used to encapsulate data about an atom in a residue template definition."""
-        def __init__(self, name, type, element, parameters={}):
+        def __init__(self, name, type, element, parameters=None):
             self.name = name
             self.type = type
             self.element = element
-            self.parameters = parameters
+            self.parameters = {} if parameters is None else parameters
             self.bondedTo = []
             self.externalBonds = 0
 


Test patch:
diff --git a/wrappers/python/tests/TestForceField.py b/wrappers/python/tests/TestForceField.py
index c0f0d34c2b..aa7bfbb404 100644
--- a/wrappers/python/tests/TestForceField.py
+++ b/wrappers/python/tests/TestForceField.py
@@ -804,6 +804,9 @@ def test_generateTemplatesForUnmatchedResidues(self):
         forcefield = ForceField('amber99sb.xml', 'tip3p.xml', StringIO(simple_ffxml_contents))
         # Get list of unique unmatched residues.
         [templates, residues] = forcefield.generateTemplatesForUnmatchedResidues(pdb.topology)
+        # Make sure template atom parameter dictionaries are distinct objects.
+        parameters = [atom.parameters for template in templates for atom in template.atoms]
+        self.assertEqual(len(set(map(id, parameters))), len(parameters))
         # Add residue templates to forcefield.
         for template in templates:
             # Replace atom types.