Task: Properly handle and test multiple registration of atom types without elements (openmm/openmm#4795)

org: openmm
repo: openmm
pr_number: 4795
issue_title: Properly handle and test multiple registration of atom types without elements
issue_body: `ForceField` allows multiple atom type definitions with the same name if their parameters are identical and forbids them if they differ.  `registerAtomType()` allows atom types' elements to be `None` (by not specifying an `element` attribute in the `<Type>`): https://github.com/openmm/openmm/blob/39808f12cb7dc0465748cc3884b50594ef568523/wrappers/python/openmm/app/forcefield.py#L452-L456 but this is not handled correctly in the duplicate checking code, which assumes that the element is not `None` and that the attribute is always present:
https://github.com/openmm/openmm/blob/39808f12cb7dc0465748cc3884b50594ef568523/wrappers/python/openmm/app/forcefield.py#L447-L449 This came up in trying to split a Drude force field, which uses atom types without elements for the Drude particles, into multiple files.

The change here should fix this problem.  It also adds several tests for the duplicate checking functionality, which was not previously covered by the test suite.  Do the tests make sense (do they match the expected behavior)?
resolved_issues: title: Properly handle and test multiple registration of atom types without elements
body: When using the `ForceField` functionality, multiple registrations of atom types with the same name are allowed only if their parameters are identical. However, the current implementation does not correctly handle cases where the `element` attribute is absent or set to `None`. This leads to incorrect duplicate checking behavior, particularly for atom types without elements, such as those used for Drude particles in specialized force fields. 

The expected behavior is that the system should allow multiple registrations of atom types with identical definitions, even when the `element` attribute is not specified or is set to `None`. Duplicate registrations with differing parameters should still be disallowed. Additionally, the duplicate checking functionality should be robust and consistently enforce these rules.

## 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

Reference solution:
fix_patch: diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml
index f5b240ee6b..decb51552c 100644
--- a/.github/workflows/CI.yml
+++ b/.github/workflows/CI.yml
@@ -212,7 +212,7 @@ jobs:
           ccache -z
 
       - name: "Restore ccache"
-        uses: actions/cache@v3.3.2
+        uses: actions/cache@v4
         with:
           path: .ccache
           key: ccache-${{ secrets.CACHE_VERSION }}-${{ steps.prepare-ccache.outputs.key }}-${{ steps.prepare-ccache.outputs.timestamp }}
@@ -354,7 +354,7 @@ jobs:
           ccache -z
 
       - name: "Restore ccache"
-        uses: actions/cache@v3.3.2
+        uses: actions/cache@v4
         with:
           path: .ccache
           key: ccache-${{ secrets.CACHE_VERSION }}-${{ steps.prepare-ccache.outputs.key }}-${{ steps.prepare-ccache.outputs.timestamp }}
@@ -495,7 +495,7 @@ jobs:
         echo "timestamp=$(date +%Y%m%d-%H%M%S)" >> $GITHUB_OUTPUT
 
     - name: "Restore ccache"
-      uses: actions/cache@v3.3.2
+      uses: actions/cache@v4
       with:
         path: .ccache
         key: ccache-${{ secrets.CACHE_VERSION }}-${{ steps.prepare-ccache.outputs.key }}-${{ steps.prepare-ccache.outputs.timestamp }}
@@ -567,7 +567,7 @@ jobs:
           ccache -z
 
       - name: "Restore ccache"
-        uses: actions/cache@v3.3.2
+        uses: actions/cache@v4
         with:
           path: .ccache
           key: ccache-${{ secrets.CACHE_VERSION }}-${{ steps.prepare-ccache.outputs.key }}-${{ steps.prepare-ccache.outputs.timestamp }}
diff --git a/wrappers/python/openmm/app/forcefield.py b/wrappers/python/openmm/app/forcefield.py
index c33446d53f..40364aef07 100644
--- a/wrappers/python/openmm/app/forcefield.py
+++ b/wrappers/python/openmm/app/forcefield.py
@@ -444,7 +444,8 @@ def registerAtomType(self, parameters):
         if name in self._atomTypes:
             #  allow multiple registrations of the same atom type provided the definitions are identical
             existing = self._atomTypes[name]
-            if existing.atomClass == parameters['class'] and existing.mass == float(parameters['mass']) and existing.element.symbol == parameters['element']:
+            elementsMatch = ((existing.element is None and 'element' not in parameters) or (existing.element is not None and 'element' in parameters and existing.element.symbol == parameters['element']))
+            if existing.atomClass == parameters['class'] and existing.mass == float(parameters['mass']) and elementsMatch:
                 return
             raise ValueError('Found multiple definitions for atom type: '+name)
         atomClass = parameters['class']