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