Task: Don't allow LJPME when LJ interactions use `CustomNonbondedForce` (openmm/openmm#5044)

org: openmm
repo: openmm
pr_number: 5044
issue_title: Don't allow LJPME when LJ interactions use `CustomNonbondedForce`
issue_body: Updates Amber, CHARMM, Desmond, `ForceField`, and GROMACS loaders to raise `ValueError` if LJPME is requested on a system that requires `CustomNonbondedForce` to implement its Lennard-Jones interactions.  See https://github.com/openmm/openmm/discussions/5039#discussioncomment-14077098.  This is usually because of NBFIX, but it can also be due to non-standard mixing rules in Desmond and GROMACS.
resolved_issues: title: Don't allow LJPME when LJ interactions use `CustomNonbondedForce`
body: ### Issue Description

**Problem:**
When using Lennard-Jones Particle Mesh Ewald (LJPME) as the nonbonded method in systems that require `CustomNonbondedForce` to handle Lennard-Jones interactions, the current implementation does not properly handle cases involving non-standard mixing rules or NBFIX terms. This can lead to incorrect behavior or unsupported configurations without clear feedback to the user.

**Expected Behavior:**
The system should detect when LJPME is requested in scenarios where it is incompatible, such as when non-standard mixing rules or NBFIX terms are present. In such cases, the system should raise a clear and descriptive error, informing the user that LJPME is not supported in these configurations. This ensures that users are aware of the limitation and can adjust their setup accordingly.

## 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/wrappers/python/openmm/app/charmmpsffile.py b/wrappers/python/openmm/app/charmmpsffile.py
index 46f9a176ac..126a2467e3 100644
--- a/wrappers/python/openmm/app/charmmpsffile.py
+++ b/wrappers/python/openmm/app/charmmpsffile.py
@@ -1217,6 +1217,8 @@ def _is_bond_in_water(bond):
                 force.addParticle(atm.charge, sigma_scale*atm.type.rmin*length_conv,
                                   abs(atm.type.epsilon*ene_conv))
         else:
+            if nonbondedMethod is ff.LJPME:
+                raise ValueError('LJPME is not supported when NBFIX terms are present')
             for atm in self.atom_list:
                 force.addParticle(atm.charge, 1.0, 0.0)
             # Now add the custom nonbonded force that implements NBFIX. First
diff --git a/wrappers/python/openmm/app/desmonddmsfile.py b/wrappers/python/openmm/app/desmonddmsfile.py
index c85d026655..33dab4b97d 100644
--- a/wrappers/python/openmm/app/desmonddmsfile.py
+++ b/wrappers/python/openmm/app/desmonddmsfile.py
@@ -368,12 +368,14 @@ def createSystem(self, nonbondedMethod=ff.NoCutoff, nonbondedCutoff=1.0*nanomete
         nb.setEwaldErrorTolerance(ewaldErrorTolerance)
         if cnb is not None:
             nb.setUseDispersionCorrection(False)
-            if nonbondedMethod in (ff.CutoffPeriodic, ff.Ewald, ff.PME, ff.LJPME):
+            if nonbondedMethod in (ff.CutoffPeriodic, ff.Ewald, ff.PME):
                 cnb.setNonbondedMethod(methodMap[ff.CutoffPeriodic])
                 cnb.setCutoffDistance(nonbondedCutoff)
             elif nonbondedMethod == ff.CutoffNonPeriodic:
                 cnb.setNonbondedMethod(methodMap[ff.CutoffNonPeriodic])
                 cnb.setCutoffDistance(nonbondedCutoff)
+            elif nonbondedMethod is ff.LJPME:
+                raise ValueError('LJPME is not supported with OPLS combining rules')
             else:
                 cnb.setNonbondedMethod(methodMap[ff.NoCutoff])
             cnb.setUseSwitchingFunction(False)
diff --git a/wrappers/python/openmm/app/forcefield.py b/wrappers/python/openmm/app/forcefield.py
index 5f8fa8c44c..9d7131a89c 100644
--- a/wrappers/python/openmm/app/forcefield.py
+++ b/wrappers/python/openmm/app/forcefield.py
@@ -2836,12 +2836,14 @@ def createForce(self, sys, data, nonbondedMethod, nonbondedCutoff, args):
         self.force.addTabulatedFunction('bcoef', mm.Discrete2DFunction(numLjTypes, numLjTypes, bcoef))
         self.force.addPerParticleParameter('type')
         self.force.setName('LennardJones')
-        if nonbondedMethod in [CutoffPeriodic, Ewald, PME, LJPME]:
+        if nonbondedMethod in [CutoffPeriodic, Ewald, PME]:
             self.force.setNonbondedMethod(mm.CustomNonbondedForce.CutoffPeriodic)
         elif nonbondedMethod is NoCutoff:
             self.force.setNonbondedMethod(mm.CustomNonbondedForce.NoCutoff)
         elif nonbondedMethod is CutoffNonPeriodic:
             self.force.setNonbondedMethod(mm.CustomNonbondedForce.CutoffNonPeriodic)
+        elif nonbondedMethod is LJPME:
+            raise ValueError('LJPME is not supported by LennardJonesForce')
         else:
             raise AssertionError('Unrecognized nonbonded method [%s]' % nonbondedMethod)
         if args['switchDistance'] is not None:
diff --git a/wrappers/python/openmm/app/gromacstopfile.py b/wrappers/python/openmm/app/gromacstopfile.py
index 343c8ff9f2..dec167c0a7 100644
--- a/wrappers/python/openmm/app/gromacstopfile.py
+++ b/wrappers/python/openmm/app/gromacstopfile.py
@@ -1239,6 +1239,8 @@ def convertParams(params):
             nb.setUseSwitchingFunction(True)
             nb.setSwitchingDistance(switchDistance)
 
+        if not (lj is None and ljnbfix is None) and nonbondedMethod is ff.LJPME:
+            raise ValueError('LJPME is not supported with active nonbond_params or combining rules other than 2')
         if lj is not None:
             methodMap = {ff.NoCutoff:mm.CustomNonbondedForce.NoCutoff,
                          ff.CutoffNonPeriodic:mm.CustomNonbondedForce.CutoffNonPeriodic,
diff --git a/wrappers/python/openmm/app/internal/amber_file_parser.py b/wrappers/python/openmm/app/internal/amber_file_parser.py
index 55cbb83b26..1425de068d 100644
--- a/wrappers/python/openmm/app/internal/amber_file_parser.py
+++ b/wrappers/python/openmm/app/internal/amber_file_parser.py
@@ -1037,6 +1037,8 @@ def readAmberSystem(topology, prmtop_filename=None, prmtop_loader=None, shake=No
 
     # Copy the exceptions as exclusions to the CustomNonbondedForce if we have
     # NBFIX terms
+    if nbfix and nonbondedMethod == 'LJPME':
+        raise ValueError('LJPME is not supported with modified off-diagonal Lennard-Jones coefficients')
     if nbfix or has_1264:
         for i in range(force.getNumExceptions()):
             ii, jj, chg, sig, eps = force.getExceptionParameters(i)