Skip to content

Initialize useOptimizedPme so pre-evaluation PME queries do not throw bad_cast - #5486

Open
barlowa124 wants to merge 1 commit into
openmm:masterfrom
barlowa124:fix/cpu-pme-params-uninitialized
Open

barlowa124 wants to merge 1 commit into
openmm:masterfrom
barlowa124:fix/cpu-pme-params-uninitialized

Conversation

@barlowa124

@barlowa124 barlowa124 commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

useOptimizedPme was read before execute() ever set it. Initialized it to false.


The section below was generated with AI assistance (Devin) and checked locally before submission.

Fixes #5392.

CpuCalcNonbondedForceKernel::useOptimizedPme was only assigned inside execute(). Calling getPMEParametersInContext() before the first force or energy evaluation read an indeterminate value. When it read as nonzero, getPMEParameters() downcast an empty Kernel and threw std::bad_cast. The flag is now initialized to false in the constructor, so pre-evaluation queries return the parameters computed by initialize(). execute() reassigns the flag on every call, so the initialized value never affects the evaluation path.

Verification of the fix

Every read and write of useOptimizedPme was audited. It is written only inside execute(), first unconditionally reset to false, then set from supportsKernels for PME and LJPME. It is read in three places, inside execute() (safe, since it is set earlier in the same function) and in getPMEParameters() and getLJPMEParameters(), the pre-evaluation crash path.

The else-branches the fix makes reachable before evaluation return fields that initialize() already sets (nonbondedMethod, ewaldAlpha, gridSize, and for LJPME ewaldDispersionAlpha and dispersionGridSize), and initialize() runs at Context creation, before any public query is possible.

The constructor init list was audited against every member read between initialize() and execute(). useOptimizedPme was the only member left uninitialized. optimizedPme and optimizedDispersionPme default-construct empty and are only dereferenced under the flag. CpuCalcConstantPotentialForceKernel already handles this correctly by guarding with hasInitializedPme, which is constructor-initialized.

false is the correct initial value because execute() unconditionally reassigns the flag, so the initial value is only ever consulted by the pre-evaluation query path, where the non-optimized parameters are the right answer.

One pre-existing oddity was noticed but left alone as out of scope. getLJPMEParameters() casts optimizedDispersionPme to CalcPmeReciprocalForceKernel, not CalcDispersionPmeReciprocalForceKernel. It works because both expose getPMEParameters through a compatible interface, and the branch is unreachable until LJPME supports the optimized path.

Test

Added testPMEParametersBeforeEvaluation to tests/TestEwald.h, following the issue's reproduction pattern. It evaluates and deletes one Context, then queries parameters on a second Context before evaluation. It asserts the returned alpha and grid dimensions against NonbondedForceImpl::calcPMEParameters. Whether the test trips on the unfixed code is environment-dependent, matching the nondeterminism reported in the issue. On the fixed code it confirms the parameters are correct.

TestCpuEwald and TestReferenceEwald pass locally on this branch.

The flag was only assigned inside execute(), so getPMEParameters() read an
indeterminate value when getPMEParametersInContext() was called before any
force or energy evaluation.  When it read as nonzero, the kernel downcast an
empty Kernel and threw std::bad_cast.  On machines where the read happened to
be zero the call returned correct values, which made the bug hard to
reproduce.

Initialize the flag to false so the method returns the parameters computed
during initialize() until the optimized kernel exists.

Fixes openmm#5392.
@barlowa124
barlowa124 marked this pull request as ready for review October 5, 2026 01:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

std::bad_cast from NonbondedForce.GetParametersInContext in CPU platform

1 participant