Unverified Commit 693756ba authored by Marc Schuh's avatar Marc Schuh Committed by GitHub
Browse files

Increase describeNextReport readability and allow requesting more values (#4671)

* added type checking for Simulation.step()

* changed how to check if step is an integer number

* allow for dicts to be returned from Reporter.describeNextReport
remove deprecated getState parameters ( #4437 )

* convert old format into new format

* update docstring

* nested set comprehension to set.union

* Allow 'periodic':None
update describeNextReport in all occurrences in the code

* debug

* update documentation

* add a reporter for energyParameterDerivative

* Revert "add a reporter for energyParameterDerivative"

This reverts commit 1d44dc3f60153defb6252ab56a3b85350fa24826.

* Edit documentation
parent e370c346
......@@ -137,7 +137,7 @@ Here is the definition of the :class:`ForceReporter` class:
def describeNextReport(self, simulation):
steps = self._reportInterval - simulation.currentStep%self._reportInterval
return (steps, False, False, True, False, None)
return = {'steps': steps, 'periodic': None, 'include':['forces']}
def report(self, simulation, state):
forces = state.getForces().value_in_unit(kilojoules/mole/nanometer)
......@@ -157,18 +157,14 @@ We then have two methods that every reporter must implement:
:meth:`describeNextReport()` and :meth:`report()`. A Simulation object
periodically calls :meth:`describeNextReport()` on each of its reporters to
find out when that reporter will next generate a report, and what information
will be needed to generate it. The return value should be a six element tuple,
whose elements are as follows:
will be needed to generate it. The return value should be a dictionary with the following content:
* The number of time steps until the next report. We calculate this as
* :code:`steps` (int): The number of time steps until the next report. We calculate this as
*(report interval)*\ -\ *(current step)*\ %\ *(report interval)*\ . For
example, if we want a report every 100 steps and the simulation is currently on
step 530, we will return 100-(530%100) = 70.
* Whether the next report will need particle positions.
* Whether the next report will need particle velocities.
* Whether the next report will need forces.
* Whether the next report will need energies.
* Whether the positions should be wrapped to the periodic box. If None, it will
* :code:`include` (list of strings): The types of information that need to be included in the next report. The values in the list correspond to arguments to :meth:`Context.getState()`. Allowed values include :code:`'positions'`, :code:`'velocities'`, :code:`'forces'`, :code:`'energy'`, :code:`'parameters'`, :code:`'parameterDerivatives'`, and :code:`'integratorParameters'`.
* :code:`periodic` (bool, optional): Whether the positions should be wrapped to the periodic box. If None or not set, it will
automatically decide whether to wrap positions based on whether the System uses
periodic boundary conditions.
......
......@@ -110,14 +110,11 @@ class CheckpointReporter(object):
Returns
-------
tuple
A five element tuple. The first element is the number of steps
until the next report. The remaining elements specify whether
that report will require positions, velocities, forces, and
energies respectively.
dict
A dictionary describing the required information for the next report
"""
steps = self._reportInterval - simulation.currentStep%self._reportInterval
return (steps, False, False, False, False)
return {'steps':steps, 'periodic':None, 'include':[]}
def report(self, simulation, state):
"""Generate a report.
......
......@@ -78,15 +78,11 @@ class DCDReporter(object):
Returns
-------
tuple
A six element tuple. The first element is the number of steps
until the next report. The next four elements specify whether
that report will require positions, velocities, forces, and
energies respectively. The final element specifies whether
positions should be wrapped to lie in a single periodic box.
dict
A dictionary describing the required information for the next report
"""
steps = self._reportInterval - simulation.currentStep%self._reportInterval
return (steps, True, False, False, False, self._enforcePeriodicBox)
return {'steps':steps, 'periodic':self._enforcePeriodicBox, 'include':['positions']}
def report(self, simulation, state):
"""Generate a report.
......
......@@ -67,14 +67,10 @@ class DebuggingReporter(object):
Returns
-------
tuple
A six element tuple. The first element is the number of steps
until the next report. The next four elements specify whether
that report will require positions, velocities, forces, and
energies respectively. The final element specifies whether
positions should be wrapped to lie in a single periodic box.
dict
A dictionary describing the required information for the next report
"""
return (1, True, True, True, False, False)
return {'steps':1, 'periodic':False, 'include':['positions', 'velocities', 'forces']}
def report(self, simulation, state):
"""Generate a report.
......
......@@ -77,15 +77,11 @@ class PDBReporter(object):
Returns
-------
tuple
A six element tuple. The first element is the number of steps
until the next report. The next four elements specify whether
that report will require positions, velocities, forces, and
energies respectively. The final element specifies whether
positions should be wrapped to lie in a single periodic box.
dict
A dictionary describing the required information for the next report
"""
steps = self._reportInterval - simulation.currentStep%self._reportInterval
return (steps, True, False, False, False, self._enforcePeriodicBox)
return {'steps':steps, 'periodic':self._enforcePeriodicBox, 'include':['positions']}
def report(self, simulation, state):
"""Generate a report.
......
......@@ -176,7 +176,10 @@ class SimulatedTempering(object):
steps2 = st.reportInterval - simulation.currentStep%st.reportInterval
steps = min(steps1, steps2)
isUpdateAttempt = (steps1 == steps)
return (steps, False, isUpdateAttempt, False, isUpdateAttempt)
if isUpdateAttempt:
return {'steps': steps, 'periodic':None, 'include':['velocities', 'energy']}
else:
return {'steps': steps, 'periodic':None, 'include':[]}
def report(self, simulation, state):
st = self.st
......
......@@ -206,9 +206,23 @@ class Simulation(object):
anyReport = False
for i, reporter in enumerate(self.reporters):
nextReport[i] = reporter.describeNextReport(self)
if nextReport[i][0] > 0 and nextReport[i][0] <= nextSteps:
nextSteps = nextReport[i][0]
report = reporter.describeNextReport(self)
# convert to new dict format if the report is in the old tuple format
if isinstance(report, tuple):
report_dict = {'steps': report[0]}
if len(report) > 5:
report_dict['periodic'] = report[5]
includes = ['positions', 'velocities', 'forces', 'energy']
report_dict['include'] = [includes[i] for i in range(4) if report[i+1]]
report = report_dict
nextReport[i] = report
steps = nextReport[i]['steps']
if steps > 0 and steps <= nextSteps:
nextSteps = steps
anyReport = True
stepsToGo = nextSteps
while stepsToGo > 10:
......@@ -226,19 +240,16 @@ class Simulation(object):
unwrapped = []
either = []
for reporter, report in zip(self.reporters, nextReport):
if report[0] == nextSteps:
if len(report) > 5:
wantWrap = report[5]
if wantWrap is None:
wantWrap = self._usesPBC
else:
wantWrap = self._usesPBC
if not report[1]:
if report['steps'] == nextSteps:
wantWrap = self._usesPBC if report.get('periodic') is None else report['periodic']
if not 'positions' in report['include']: # if no positions are requested, we don't care about pbc
either.append((reporter, report))
elif wantWrap:
wrapped.append((reporter, report))
else:
unwrapped.append((reporter, report))
if len(wrapped) > len(unwrapped):
wrapped += either
else:
......@@ -252,23 +263,22 @@ class Simulation(object):
self._generate_reports(unwrapped, False)
def _generate_reports(self, reports, periodic):
getPositions = False
getVelocities = False
getForces = False
getEnergy = False
for reporter, next in reports:
if next[1]:
getPositions = True
if next[2]:
getVelocities = True
if next[3]:
getForces = True
if next[4]:
getEnergy = True
state = self.context.getState(positions=getPositions, velocities=getVelocities, forces=getForces,
energy=getEnergy, parameters=True, enforcePeriodicBox=periodic,
groups=self.context.getIntegrator().getIntegrationForceGroups())
for reporter, next in reports:
'''Generate reports for all requested reporters
Parameters
----------
reports : list of tuples
each tuple in the list contains a reporter object and a description of the next report produced by reporter.describeNextReport().
Note that the old format of returning a tuple from reporter.describeNextReport() is not supported in this function.
periodic : bool
Specifies whether particle positions should be translated so the center of every molecule lies in the same periodic box.
'''
includes = set.union(*[set(report[1]['include']) for report in reports])
includeArgs = {property:True for property in includes}
state = self.context.getState(groups=self.context.getIntegrator().getIntegrationForceGroups(), enforcePeriodicBox=periodic, parameters=True, **includeArgs)
for reporter, nextReport in reports:
reporter.report(self, state)
def saveCheckpoint(self, file):
......
......@@ -158,6 +158,7 @@ class StateDataReporter(object):
self._needsVelocities = False
self._needsForces = False
self._needEnergy = potentialEnergy or kineticEnergy or totalEnergy or temperature
self._includes = ['energy'] if self._needEnergy else []
def describeNextReport(self, simulation):
"""Get information about the next report this object will generate.
......@@ -169,14 +170,11 @@ class StateDataReporter(object):
Returns
-------
tuple
A five element tuple. The first element is the number of steps
until the next report. The remaining elements specify whether
that report will require positions, velocities, forces, and
energies respectively.
dict
A dictionary describing the required information for the next report
"""
steps = self._reportInterval - simulation.currentStep%self._reportInterval
return (steps, self._needsPositions, self._needsVelocities, self._needsForces, self._needEnergy)
return {'steps':steps, 'periodic':None, 'include':self._includes}
def report(self, simulation, state):
"""Generate a report.
......
......@@ -42,15 +42,11 @@ class XTCReporter(object):
Returns
-------
tuple
A six element tuple. The first element is the number of steps
until the next report. The next four elements specify whether
that report will require positions, velocities, forces, and
energies respectively. The final element specifies whether
positions should be wrapped to lie in a single periodic box.
dict
A dictionary describing the required information for the next report
"""
steps = self._reportInterval - simulation.currentStep % self._reportInterval
return (steps, True, False, False, False, self._enforcePeriodicBox)
steps = self._reportInterval - simulation.currentStep%self._reportInterval
return {'steps':steps, 'periodic':self._enforcePeriodicBox, 'include':['positions']}
def report(self, simulation, state):
"""Generate a report.
......
......@@ -179,7 +179,7 @@ class TestSimulation(unittest.TestCase):
def describeNextReport(self, simulation):
steps = self.interval - simulation.currentStep%self.interval
return (steps, True, False, False, False, self.periodic)
return {'steps':steps, 'periodic':self.periodic, 'include':['positions']}
def report(self, simulation, state):
state2 = simulation.context.getState(getPositions=True, enforcePeriodicBox=self.periodic)
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment