Some testing and analysis of the new Snapshot implementation


In [1]:
import numpy as np
import openpathsampling as paths
import openpathsampling.engines.features as features

from __future__ import print_function

Function to show the generated source code


In [2]:
from IPython.display import Markdown

def code_to_md(snapshot_class):
    md = '```py\n'
    for f, s in snapshot_class.__features__.debug.items():
        if s is not None:
            md += s
        else:
            md += 'def ' + f + '(...):\n    # user defined\n    pass' 
        md += '\n\n'
    md += '```'

    return md

Check generated source code

Generate simple Snapshot without any features using factory


In [3]:
EmptySnap = paths.engines.snapshot.SnapshotFactory('no', [], 'Empty', use_lazy_reversed=False)

Generate Snapshot with overridden .copy method.


In [4]:
@features.base.attach_features([
    features.velocities,
    features.coordinates,
    features.box_vectors,
    features.topology
])
class A(paths.BaseSnapshot):
    def copy(self):
        return 'copy'

Check that subclassing with overridden copy needs more overriding.


In [5]:
try:
    @features.base.attach_features([
    ])
    class B(A):
        pass
except RuntimeWarning as e:
    print(e)
else:
    raise RuntimeError('Should have raised a RUNTIME warning')


Subclassing snapshots with overridden function "copy" is only possible if this function is overridden again, otherwise some features might not be copied. The general practise of overriding is not recommended.

In [6]:
a = A()
assert(a.copy() == 'copy')

In [7]:
#! ignore
Markdown(code_to_md(A))


Out[7]:
def create_empty(self):
    this = cls.__new__(cls)
    this._reversed = None
    return this

@staticmethod
def init_copy(self, velocities=None, coordinates=None, box_vectors=None, topology=None):
    self._reversed = None
    self.topology = topology
    np.copyto(self.velocities, velocities)
    np.copyto(self.coordinates, coordinates)
    np.copyto(self.box_vectors, box_vectors)

def create_reversed(self):
    this = cls.__new__(cls)
    this._reversed = self
    this.coordinates = self.coordinates
    this.box_vectors = self.box_vectors
    this.topology = self.topology
    this.velocities = - self.velocities
    return this

def copy_to(self, target):
    target._reversed = None
    target.topology = self.topology
    np.copyto(target.velocities, self.velocities)
    np.copyto(target.coordinates, self.coordinates)
    np.copyto(target.box_vectors, self.box_vectors)

def init_empty(self):
    self._reversed = None

def copy(...):
    # user defined
    pass

def __init__(self, velocities=None, coordinates=None, box_vectors=None, topology=None):
    self._reversed = None
    self.velocities = velocities
    self.coordinates = coordinates
    self.box_vectors = box_vectors
    self.topology = topology

In [8]:
#! ignore
Markdown(code_to_md(EmptySnap))


Out[8]:
def create_empty(self):
    this = cls.__new__(cls)
    this._reversed = None
    return this

@staticmethod
def init_copy(self):
    self._reversed = None

def create_reversed(self):
    this = cls.__new__(cls)
    this._reversed = self
    return this

def copy_to(self, target):
    target._reversed = None

def init_empty(self):
    self._reversed = None

def copy(self):
    this = cls.__new__(cls)
    this._reversed = None
    return this

def __init__(self):
    self._reversed = None

In [9]:
SuperSnap = paths.engines.snapshot.SnapshotFactory(
    'my', [
        paths.engines.features.coordinates,
        paths.engines.features.box_vectors,
        paths.engines.features.velocities
    ], 'No desc', use_lazy_reversed=False)

In [10]:
#! ignore
Markdown(code_to_md(SuperSnap))


Out[10]:
def create_empty(self):
    this = cls.__new__(cls)
    this._reversed = None
    return this

@staticmethod
def init_copy(self, coordinates=None, box_vectors=None, velocities=None):
    self._reversed = None
    np.copyto(self.coordinates, coordinates)
    np.copyto(self.box_vectors, box_vectors)
    np.copyto(self.velocities, velocities)

def create_reversed(self):
    this = cls.__new__(cls)
    this._reversed = self
    this.coordinates = self.coordinates
    this.box_vectors = self.box_vectors
    this.velocities = - self.velocities
    return this

def copy_to(self, target):
    target._reversed = None
    np.copyto(target.coordinates, self.coordinates)
    np.copyto(target.box_vectors, self.box_vectors)
    np.copyto(target.velocities, self.velocities)

def init_empty(self):
    self._reversed = None

def copy(self):
    this = cls.__new__(cls)
    this._reversed = None
    if self.coordinates is not None:
        this.coordinates = self.coordinates.copy()
    else:
        this.coordinates = self.coordinates
    if self.box_vectors is not None:
        this.box_vectors = self.box_vectors.copy()
    else:
        this.box_vectors = self.box_vectors
    if self.velocities is not None:
        this.velocities = self.velocities.copy()
    else:
        this.velocities = self.velocities
    return this

def __init__(self, coordinates=None, box_vectors=None, velocities=None):
    self._reversed = None
    self.coordinates = coordinates
    self.box_vectors = box_vectors
    self.velocities = velocities

In [11]:
MegaSnap = paths.engines.snapshot.SnapshotFactory(
    'mega', [
        paths.engines.features.statics,
        paths.engines.features.kinetics,
        paths.engines.features.engine
    ], 'Long desc', use_lazy_reversed=False)

In [12]:
#! ignore
Markdown(code_to_md(MegaSnap))


Out[12]:
def create_empty(self):
    this = cls.__new__(cls)
    this._lazy = {}
    this._reversed = None
    return this

@staticmethod
def init_copy(self, statics=None, kinetics=None, is_reversed=False, engine=None):
    self._lazy = {
       cls.statics : statics,
       cls.kinetics : kinetics,
    }
    self._reversed = None
    self.is_reversed = is_reversed
    self.engine = engine

def create_reversed(self):
    this = cls.__new__(cls)
    this._lazy = {
       cls.statics : self._lazy[cls.statics],
       cls.kinetics : self._lazy[cls.kinetics],
    }
    this._reversed = self
    this.engine = self.engine
    this.is_reversed = not self.is_reversed
    return this

def copy_to(self, target):
    target._lazy = {
       cls.statics : self._lazy[cls.statics],
       cls.kinetics : self._lazy[cls.kinetics],
    }
    target._reversed = None
    target.is_reversed = self.is_reversed
    target.engine = self.engine

def init_empty(self):
    self._lazy = {}
    self._reversed = None

def copy(self):
    this = cls.__new__(cls)
    this._lazy = {
       cls.statics : self._lazy[cls.statics],
       cls.kinetics : self._lazy[cls.kinetics],
    }
    this._reversed = None
    this.is_reversed = self.is_reversed
    this.engine = self.engine
    return this

def __init__(self, statics=None, kinetics=None, is_reversed=False, engine=None):
    self._lazy = {
       cls.statics : statics,
       cls.kinetics : kinetics,
    }
    self._reversed = None
    self.is_reversed = is_reversed
    self.engine = engine

Test subclassing


In [13]:
@features.base.attach_features([
])
class HyperSnap(MegaSnap):
    pass

Test subclassing with redundant features (should work / be ignored)


In [14]:
@features.base.attach_features([
    paths.engines.features.statics,
])
class HyperSnap(MegaSnap):
    pass

Test subclassing with conflicting features (should not work)


In [15]:
try:
    @features.base.attach_features([
        paths.engines.features.statics,
        paths.engines.features.coordinates
    ])
    class HyperSnap(MegaSnap):
        pass
except RuntimeWarning as e:
    print(e)
else:
    raise RuntimeError('Should have raised a RUNTIME warning')


Collision: Property "xyz" already exists.

In [16]:
#! ignore
Markdown(code_to_md(paths.engines.openmm.MDSnapshot))


Out[16]:
def create_empty(self):
    this = cls.__new__(cls)
    this._reversed = None
    return this

@staticmethod
def init_copy(self, velocities=None, coordinates=None, box_vectors=None, topology=None):
    self._reversed = None
    self.topology = topology
    np.copyto(self.velocities, velocities)
    np.copyto(self.coordinates, coordinates)
    np.copyto(self.box_vectors, box_vectors)

def create_reversed(self):
    this = cls.__new__(cls)
    this._reversed = self
    this.coordinates = self.coordinates
    this.box_vectors = self.box_vectors
    this.topology = self.topology
    this.velocities = - self.velocities
    return this

def copy_to(self, target):
    target._reversed = None
    target.topology = self.topology
    np.copyto(target.velocities, self.velocities)
    np.copyto(target.coordinates, self.coordinates)
    np.copyto(target.box_vectors, self.box_vectors)

def init_empty(self):
    self._reversed = None

def copy(self):
    this = cls.__new__(cls)
    this._reversed = None
    this.topology = self.topology
    if self.velocities is not None:
        this.velocities = self.velocities.copy()
    else:
        this.velocities = self.velocities
    if self.coordinates is not None:
        this.coordinates = self.coordinates.copy()
    else:
        this.coordinates = self.coordinates
    if self.box_vectors is not None:
        this.box_vectors = self.box_vectors.copy()
    else:
        this.box_vectors = self.box_vectors
    return this

def __init__(self, velocities=None, coordinates=None, box_vectors=None, topology=None):
    self._reversed = None
    self.velocities = velocities
    self.coordinates = coordinates
    self.box_vectors = box_vectors
    self.topology = topology

In [ ]: