Source code for pyretis.core.forcefieldhelp
# Copyright (c) 2026, PyRETIS Development Team.
# Distributed under the LGPLv2.1+ License. See LICENSE for more info.
"""Forcefield evaluation helpers (system-type-agnostic).
These free functions let callers compute / update the potential and
forces of a :class:`.System` without going through harness-specific
``system.potential_and_force()`` style methods. The functions operate
purely through the System bridge surface (``system.vpot`` / ``system.force``
/ ``system.virial``), so they work transparently on both the harness
System and the snapshot System.
The corresponding harness ``System`` methods delegate to these
helpers; new code should call the free functions directly, since
the harness methods go away in A3.2c.
"""
import logging
logger = logging.getLogger(__name__) # pylint: disable=invalid-name
logger.addHandler(logging.NullHandler())
__all__ = [
'update_force',
'update_potential',
'update_potential_and_force',
'evaluate_force',
'evaluate_potential',
'evaluate_potential_and_force',
'rescale_system_velocities',
]
[docs]def update_force(system, forcefield):
"""Evaluate forces via the forcefield and store on the system.
Parameters
----------
system : object like :class:`.System`
The system to update. Must expose ``force`` and (optionally)
``virial`` via the bridge surface.
forcefield : object like :class:`.ForceField`
The forcefield used to evaluate the forces.
Returns
-------
force : numpy.ndarray
The freshly computed force array (also written to
``system.force``).
virial : numpy.ndarray
The virial tensor (also written to ``system.virial`` when the
system carries one).
"""
force, virial = forcefield.evaluate_force(system)
system.force = force
if hasattr(system, 'virial'):
system.virial = virial
return force, virial
[docs]def update_potential(system, forcefield):
"""Evaluate the potential energy and store it on the system.
Parameters
----------
system : object like :class:`.System`
The system to update.
forcefield : object like :class:`.ForceField`
The forcefield used to evaluate the potential.
Returns
-------
float
The potential energy (also written to ``system.vpot``).
"""
vpot = forcefield.evaluate_potential(system)
system.vpot = vpot
return vpot
[docs]def update_potential_and_force(system, forcefield):
"""Evaluate potential, force, and virial and store them on the system.
Parameters
----------
system : object like :class:`.System`
The system to update.
forcefield : object like :class:`.ForceField`
The forcefield used.
Returns
-------
vpot : float
The potential energy.
force : numpy.ndarray
The force array.
virial : numpy.ndarray
The virial tensor.
"""
vpot, force, virial = forcefield.evaluate_potential_and_force(system)
system.vpot = vpot
system.force = force
if hasattr(system, 'virial'):
system.virial = virial
return vpot, force, virial
[docs]def evaluate_force(system, forcefield):
"""Return forces/virial *without* updating the system.
Parameters
----------
system : object like :class:`.System`
The system being evaluated. Not mutated.
forcefield : object like :class:`.ForceField`
The forcefield used.
Returns
-------
force : numpy.ndarray
Force array.
virial : numpy.ndarray
Virial tensor.
"""
return forcefield.evaluate_force(system)
[docs]def evaluate_potential(system, forcefield):
"""Return the potential energy *without* updating the system.
Parameters
----------
system : object like :class:`.System`
The system being evaluated. Not mutated.
forcefield : object like :class:`.ForceField`
The forcefield used.
Returns
-------
float
The potential energy.
"""
return forcefield.evaluate_potential(system)
[docs]def evaluate_potential_and_force(system, forcefield):
"""Return potential, force, and virial *without* updating the system.
Parameters
----------
system : object like :class:`.System`
The system being evaluated. Not mutated.
forcefield : object like :class:`.ForceField`
The forcefield used.
Returns
-------
vpot : float
Potential energy.
force : numpy.ndarray
Force array.
virial : numpy.ndarray
Virial tensor.
"""
return forcefield.evaluate_potential_and_force(system)
[docs]def rescale_system_velocities(system, target_energy, forcefield=None,
external=False):
"""Re-scale ``system.vel`` so total energy matches ``target_energy``.
Free-function form of the soon-to-go
``System.rescale_velocities(energy, external=...)`` method.
Parameters
----------
system : object like :class:`.System`
The system whose velocities should be rescaled in place via
the bridge surface (``system.vel`` / ``system.mass`` /
``system.vpot``).
target_energy : float
Desired total energy (kinetic + potential).
forcefield : object like :class:`.ForceField`, optional
Required when ``external`` is False; used to recompute
``system.vpot`` before the rescale. Ignored when ``external``
is True (the existing ``system.vpot`` is used).
external : bool, optional
Mirrors the ``external`` flag of the legacy method. When
True, use the cached ``system.vpot``; when False, recompute
it via the forcefield first.
"""
# Local import: rescale_velocities (the lower-level helper) lives
# in particlefunctions and indirectly imports core.system through
# type hints in older docstrings, so we delay it.
from pyretis.core.particlefunctions import rescale_velocities
if not external:
if forcefield is None:
raise ValueError(
'rescale_system_velocities needs a forcefield when '
'external=False; pass system.forcefield or set '
'external=True to use the cached vpot.'
)
vpot = update_potential(system, forcefield)
else:
vpot = system.vpot
new_vel, ok = rescale_velocities(
system.vel, system.mass, vpot, target_energy
)
if ok:
system.vel = new_vel