コード例 #1
0
ファイル: tools.py プロジェクト: marscher/openpathsampling
def units_from_snapshot(snapshot):
    """
    Returns a dict of simtk.unit.Unit instances that represent the used units in the snapshot

    Parameters
    ----------
    snapshot : Snapshot
        the snapshot to be used

    Returns
    -------
    units : dict of {str : simtk.unit.Unit }
        representing a dict of string representing a dimension ('length', 'velocity', 'energy') pointing the
        the simtk.unit.Unit to be used
    """

    units = {}
    if snapshot.coordinates is not None:
        if hasattr(snapshot.coordinates, 'unit'):
            units['length'] = snapshot.coordinates.unit
        else:
            units['length'] = u.Unit({})

    if snapshot.potential_energy is not None:
        if hasattr(snapshot.potential_energy, 'unit'):
            units['energy'] = snapshot.potential_energy.unit
        else:
            units['energy'] = u.Unit({})

    if snapshot.velocities is not None:
        if hasattr(snapshot.velocities, 'unit'):
            units['velocity'] = snapshot.velocities.unit
        else:
            units['velocity'] = u.Unit({})

    return units
コード例 #2
0
    def __init__(self, filename, mode=None, fallback=None):
        """
        Create a storage for complex objects in a netCDF file

        Parameters
        ----------
        filename : string
            filename of the netcdf file to be used or created
        mode : str
            the mode of file creation, one of 'w' (write), 'a' (append) or
            'r' (read-only) None, which will append any existing files
            (equal to append), is the default.
        fallback : :class:`openpathsampling.Storage`
            the _fallback_ storage to be loaded from if an object is not present
            in this storage. By default you will not try to resave objects
            that could be found in the fallback. Note that the fall back does
            only work if `use_uuid` is enabled

        Notes
        -----
        A single file can be opened by multiple storages, but only one can be
        used for writing

        """

        if mode is None:
            mode = 'a'

        self.mode = mode

        exists = os.path.isfile(filename)
        if exists and mode == 'a':
            logger.info(
                "Open existing netCDF file '%s' for appending - "
                "appending existing file", filename)
        elif exists and mode == 'w':
            logger.info(
                "Create new netCDF file '%s' for writing - "
                "deleting existing file", filename)
        elif not exists and mode == 'a':
            logger.info(
                "Create new netCDF file '%s' for appending - "
                "appending non-existing file", filename)
        elif not exists and mode == 'w':
            logger.info(
                "Create new netCDF file '%s' for writing - "
                "creating new file", filename)
        elif not exists and mode == 'r':
            logger.info(
                "Open existing netCDF file '%s' for reading - "
                "file does not exist", filename)
            raise RuntimeError("File '%s' does not exist." % filename)
        elif exists and mode == 'r':
            logger.info(
                "Open existing netCDF file '%s' for reading - "
                "reading from existing file", filename)

        self.filename = filename
        self.fallback = fallback

        # this can be set to false to re-store objects present in the fallback
        self.exclude_from_fallback = True

        # this can be set to false to re-store proxies from other stores
        self.exclude_proxy_from_other = False

        # call netCDF4-python to create or open .nc file
        super(NetCDFPlus, self).__init__(filename, mode)

        self._setup_class()

        if mode == 'w':
            logger.info("Setup netCDF file and create variables")

            self.setncattr('format', 'netcdf+')
            self.setncattr('ncplus_version', self._netcdfplus_version_)

            self.write_meta()

            # add shared scalar dimension for everyone
            self.create_dimension('scalar', 1)
            self.create_dimension('pair', 2)

            self.setncattr('use_uuid', 'True')

            self._create_simplifier()

            # create the store that holds stores
            store_stores = NamedObjectStore(ObjectStore)
            store_stores.name = 'stores'
            self.register_store('stores', store_stores)
            self.stores.initialize()
            self.stores.set_caching(True)
            self.update_delegates()

            # now create all storages in subclasses
            self._create_storages()

            self.create_store('attributes', PseudoAttributeStore())

            # call the subclass specific initialization
            self._initialize()

            # this will create all variables in the storage for all new
            # added stores this is often already call inside of _initialize.
            # If not we just make sure
            self.finalize_stores()

            logger.info("Finished setting up netCDF file")

            self.sync()

        elif mode == 'a' or mode == 'r+' or mode == 'r':
            logger.debug("Restore the dict of units from the storage")

            self.check_version()

            # self.reference_by_uuid = hasattr(self, 'use_uuid')
            # self.reference_by_uuid = True

            self._create_simplifier()

            # open the store that contains all stores
            self.register_store('stores', NamedObjectStore(ObjectStore))
            self.stores.set_caching(True)
            self.create_variable_delegate('stores_json')
            self.create_variable_delegate('stores_name')
            self.create_variable_delegate('stores_uuid')

            self.stores.restore()

            # Create a dict of simtk.Unit() instances for all netCDF.Variable()
            for variable_name in self.variables:
                variable = self.variables[variable_name]

                if self.support_simtk_unit:
                    import simtk.unit as u
                    if hasattr(variable, 'unit_simtk'):
                        unit_dict = self.simplifier.from_json(
                            getattr(variable, 'unit_simtk'))
                        if unit_dict is not None:
                            unit = self.simplifier.unit_from_dict(unit_dict)
                        else:
                            unit = self.simplifier.unit_from_dict(u.Unit({}))

                        self.units[str(variable_name)] = unit

            # register all stores that are listed in self.stores

            for store in self.stores:
                if store is not None:
                    logger.debug("Register store %s in the storage" % store.name)
                    self.register_store(store.name, store)
                    store.register(self, store.name)

            self.update_delegates()
            self._restore_storages()

            # only if we have a new style file
            if hasattr(self, 'attributes'):
                for attribute, store in zip(
                        self.attributes,
                        self.attributes.vars['cache']
                ):
                    if store is not None:
                        key_store = self.attributes.key_store(attribute)
                        key_store.attribute_list[attribute] = store

            # call the subclass specific restore in case there is more stuff
            # to prepare
            self._restore()

        self.set_auto_mask(False)
コード例 #3
0
    def create_variable(self, var_name,
                        var_type,
                        dimensions,
                        description=None,
                        chunksizes=None,
                        simtk_unit=None,
                        maskable=False):
        """
        Create a new variable in the netCDF storage.

        This is just a helper function to structure the code better and add
        some convenience to creating more complex variables

        Parameters
        ==========
        var_name : str
            The name of the variable to be created
        var_type : str
            The string representing the type of the data stored in the
            variable.  Allowed are strings of native python types in which
            case the variables will be treated as python or a string of the
            form 'numpy.type' which will refer to the numpy data types.
            Numpy is preferred sinec the api to netCDF uses numpy and thus
            it is faster. Possible input strings are
            `int`, `float`, `long`, `str`, `numpy.float32`, `numpy.float64`,
            `numpy.int8`, `numpy.int16`, `numpy.int32`, `numpy.int64`, `json`,
            `obj.<store>`, `lazyobj.<store>`
        dimensions : str or tuple of str
            A tuple representing the dimensions used for the netcdf variable.
            If not specified then the default dimension of the storage is used.
            If the last dimension is `'...'` then it is assumed that the
            objects are of variable length. In netCDF this is usually
            referred to as a VLType.  We will treat is just as another
            dimension, but it can only be the last dimension.
        description : str
            A string describing the variable in a readable form.
        chunksizes : tuple of int
            A tuple of ints per number of dimensions. This specifies in what
            block sizes a variable is stored. Usually for object related stuff
            we want to store everything of one object at once so this is often
            (1, ..., ...)
        simtk_unit : str
            A string representing the units used for this variable. Can be
            used with all var_types although it makes sense only for numeric
            ones.
        maskable : bool, default: False
            If set to `True` the values in this variable can only partially
            exist and if they have not yet been written they are filled with
            a fill_value which is treated as a non-set variable. The created
            variable will interpret this values as `None` when returned
        """

        ncfile = self

        if type(dimensions) is str:
            dimensions = [dimensions]

        dimensions = list(dimensions)

        new_dimensions = dict()
        for ix, dim in enumerate(dimensions):
            if type(dim) is int:
                dimensions[ix] = var_name + '_dim_' + str(ix)
                new_dimensions[dimensions[ix]] = dim

        if dimensions[-1] == '...':
            # last dimension is simply [] so we allow arbitrary length
            # and remove the last dimension
            variable_length = True
            dimensions = dimensions[:-1]
        else:
            variable_length = False

        if var_type == 'obj' or var_type == 'lazyobj':
            dimensions.append('pair')
            if chunksizes is not None:
                chunksizes = tuple(list(chunksizes) + [2])

        nc_type = self.var_type_to_nc_type(var_type)

        for dim_name, size in new_dimensions.items():
            ncfile.create_dimension(dim_name, size)

        dimensions = tuple(dimensions)

        # if chunk sizes are strings then replace it by
        # the actual size of the dimension
        if chunksizes is not None:
            chunksizes = list(chunksizes)
            for ix, dim in enumerate(chunksizes):
                if dim == -1:
                    chunksizes[ix] = len(ncfile.dimensions[dimensions[ix]])

                if type(dim) is str:
                    chunksizes[ix] = len(ncfile.dimensions[dim])

            chunksizes = tuple(chunksizes)

        if variable_length:
            vlen_t = ncfile.createVLType(nc_type, var_name + '_vlen')
            ncvar = ncfile.createVariable(
                var_name, vlen_t, dimensions, chunksizes=chunksizes
            )

            setattr(ncvar, 'var_vlen', 'True')
        else:
            ncvar = ncfile.createVariable(
                var_name, nc_type, dimensions, chunksizes=chunksizes,
            )

        setattr(ncvar, 'var_type', var_type)

        if self.support_simtk_unit and simtk_unit is not None:

            import simtk.unit as u

            if isinstance(simtk_unit, u.Unit):
                unit_instance = simtk_unit
                symbol = unit_instance.get_symbol()
            elif isinstance(simtk_unit, u.BaseUnit):
                unit_instance = u.Unit({simtk_unit: 1.0})
                symbol = unit_instance.get_symbol()
            elif type(simtk_unit) is str and hasattr(u, simtk_unit):
                unit_instance = getattr(u, simtk_unit)
                symbol = unit_instance.get_symbol()
            else:
                raise NotImplementedError(
                    'Unit by abbreviated string representation '
                    'is not yet supported')

            json_unit = self.simplifier.unit_to_json(unit_instance)

            # store the unit in the dict inside the Storage object
            self.units[var_name] = unit_instance

            # Define units for a float variable
            setattr(ncvar, 'unit_simtk', json_unit)
            setattr(ncvar, 'unit', symbol)

        if maskable:
            setattr(ncvar, 'maskable', 'True')

        if description is not None:
            if type(dimensions) is str:
                dim_names = [dimensions]
            else:
                dim_names = ['#ix{0}:{1}'.format(*p) for p in
                             enumerate(dimensions)]

            idx_desc = '[' + ']['.join(dim_names) + ']'
            description = var_name + idx_desc + ' is ' + \
                description.format(idx=dim_names[0], ix=dim_names)

            # Define long (human-readable) names for variables.
            setattr(ncvar, "long_str", description)

        self.update_delegates()

        return ncvar
コード例 #4
0
    def unit_from_dict(unit_dict):
        unit = units.Unit({})
        for unit_name, unit_multiplication in unit_dict.items():
            unit *= getattr(units, unit_name)**unit_multiplication

        return unit
コード例 #5
0
ファイル: TestUnits.py プロジェクト: msultan/openmm
 def testCompositeUnits(self):
     """ Tests the creation of a composite unit """
     mps = u.Unit({u.meter_base_unit : 1.0, u.second_base_unit : -1.0})
     self.assertTrue(u.is_unit(mps))
     self.assertEqual(str(mps), 'meter/second')
コード例 #6
0
class DynamicsEngine(StorableNamedObject):
    """
    Wraps simulation tool (parameters, storage, etc.)

    Attributes
    ----------
    on_nan : str
        set the behaviour of the engine when `NaN` is detected.
        Possible is

        1.  `fail` will raise an exception `EngineNaNError`
        2.  `retry` will rerun the trajectory in engine.generate, these moves
            do not satisfy detailed balance
    on_error : str
        set the behaviour of the engine when an exception happens.
        Possible is

        1.  `fail` will raise an exception `EngineError`
        2.  `retry` will rerun the trajectory in engine.generate, these moves
            do not satisfy detailed balance
    on_max_length : str
        set the behaviour if the trajectory length is `n_frames_max`.
        If `n_frames_max == 0` this will be ignored and nothing happens.
        Possible is

        1.  `fail` will raise an exception `EngineMaxLengthError`
        2.  `stop` will stop and return the max length trajectory (default)
        3.  `retry` will rerun the trajectory in engine.generate, these moves
            do not satisfy detailed balance
    retries_when_nan : int, default: 2
        the number of retries (if chosen) before an exception is raised
    retries_when_error : int, default: 2
        the number of retries (if chosen) before an exception is raised
    retries_when_max_length : int, default: 0
        the number of retries (if chosen) before an exception is raised
    on_retry : str or callable
        the behaviour when a try is started. Since you have already generated
        some trajectory you might not restart completely. Possibilities are
        1.  `full` will restart completely and use the initial frames (default)
        2.  `50%` will cut the existing in half but keeping at least the initial
        3.  `remove_interval` will remove as many frames as the `interval`
        4.  a callable will be used as a function to generate the new from the
            old trajectories, e.g. `lambda t: t[:10]` would restart with the
            first 10 frames

    Notes
    -----
    Should be considered an abstract class: only its subclasses can be
    instantiated.
    """

    FORWARD = 1
    BACKWARD = -1

    _default_options = {
        'n_frames_max': None,
        'on_max_length': 'fail',
        'on_nan': 'fail',
        'retries_when_nan': 2,
        'retries_when_error': 0,
        'retries_when_max_length': 0,
        'on_retry': 'full',
        'on_error': 'fail'
    }

    units = {
        'length': u.Unit({}),
        'velocity': u.Unit({}),
        'energy': u.Unit({})
    }

    base_snapshot_type = BaseSnapshot

    def __init__(self, options=None, descriptor=None):
        """
        Create an empty DynamicsEngine object

        Notes
        -----
        The purpose of an engine is to create trajectories and keep track
        of the results. The main method is 'generate' to create a
        trajectory, which is a list of snapshots and then can store the in
        the associated storage. In the initialization this storage is
        created as well as the related Trajectory and Snapshot classes are
        initialized.
        """

        super(DynamicsEngine, self).__init__()

        self.descriptor = descriptor
        self._check_options(options)

    @property
    def current_snapshot(self):
        return None

    @current_snapshot.setter
    def current_snapshot(self, snap):
        pass

    def to_dict(self):
        return {'options': self.options, 'descriptor': self.descriptor}

    def _check_options(self, options=None):
        """
        This will register all variables in the options dict as a member
        variable if they are present in either the
        `DynamicsEngine.default_options` or this
        classes default_options, no multiple inheritance is supported!
        It will use values with the priority in the following order
        - DynamicsEngine.default_options
        - self.default_options
        - self.options (usually not used)
        - options (function parameter)
        Parameters are only registered if
        1. the variable name is present in the defaults
        2. the type matches the one in the defaults
        3. for variables with units also the units need to be compatible

        Parameters
        ----------
        options : dict of { str : value }
            A dictionary

        Notes
        -----
        Options are what is necessary to recreate the engine, but not runtime
        variables or independent variables like the actual initialization
        status, the runners or an attached storage.
        If there are non-default options present they will be ignored
        (no error thrown)
        """
        # start with default options from a dynamics engine
        my_options = {}
        okay_options = {}

        # self.default_options overrides default ones from DynamicsEngine
        for variable, value in self.default_options.iteritems():
            my_options[variable] = value

        if hasattr(self, 'options') and self.options is not None:
            # self.options overrides default ones
            for variable, value in self.options.iteritems():
                my_options[variable] = value

        if options is not None:
            # given options override even default and already stored ones
            for variable, value in options.iteritems():
                my_options[variable] = value

        if my_options is not None:
            for variable, default_value in self.default_options.iteritems():
                # create an empty member variable if not yet present
                if not hasattr(self, variable):
                    okay_options[variable] = None

                if variable in my_options:
                    if type(my_options[variable]) is type(default_value):
                        if type(my_options[variable]) is u.Unit:
                            if my_options[variable].unit.is_compatible(
                                    default_value):
                                okay_options[variable] = my_options[variable]
                            else:
                                raise ValueError(
                                    'Unit of option "' + str(variable) +
                                    '" (' + str(my_options[variable].unit) +
                                    ') not compatible to "' +
                                    str(default_value.unit) + '"')

                        elif type(my_options[variable]) is list:
                            if isinstance(my_options[variable][0],
                                          type(default_value[0])):
                                okay_options[variable] = my_options[variable]
                            else:
                                raise \
                                    ValueError(
                                        'List elements for option "' +
                                        str(variable) + '" must be of type "' +
                                        str(type(default_value[0])) + '"')
                        else:
                            okay_options[variable] = my_options[variable]
                    elif isinstance(my_options[variable], type(default_value)):
                        okay_options[variable] = my_options[variable]
                    elif default_value is None:
                        okay_options[variable] = my_options[variable]
                    else:
                        raise ValueError('Type of option "' + str(variable) +
                                         '" (' +
                                         str(type(my_options[variable])) +
                                         ') is not "' +
                                         str(type(default_value)) + '"')

            self.options = okay_options
        else:
            self.options = {}

    def __getattr__(self, item):
        # first, check for errors that might be shadowed in properties
        if item in self.__class__.__dict__:
            # we should have this attribute
            p = self.__class__.__dict__[item]
            if isinstance(p, property):
                # re-run, raise the error inside the property
                try:
                    result = p.fget(self)
                except:
                    raise
                else:
                    # alternately, trust the fixed result with
                    # return result  # miraculously fixed
                    raise AttributeError(
                        "Unknown problem occurred in property" +
                        str(p.fget.func_name) + ": Second attempt returned" +
                        str(result))
            # for now, items in dict that fail with AttributeError will just
            # give the default message; to change, add something here like:
            # raise AttributeError("Something went wrong with " + str(item))

        # see, if the attribute is actually a dimension
        if self.descriptor is not None:
            if item in self.descriptor.dimensions:
                return self.descriptor.dimensions[item]

        # fallback is to look for an option and return it's value
        try:
            return self.options[item]
        except KeyError:
            # convert KeyError to AttributeError
            default_msg = "'{0}' has no attribute '{1}'"
            raise AttributeError(
                (default_msg + ", nor does its options dictionary").format(
                    self.__class__.__name__, item))

    @property
    def dimensions(self):
        if self.descriptor is None:
            return {}
        else:
            return self.descriptor.dimensions

    def set_as_default(self):
        import openpathsampling as p
        p.EngineMover.engine = self

    @property
    def default_options(self):
        default_options = {}
        default_options.update(DynamicsEngine._default_options)
        default_options.update(self._default_options)
        return default_options

    # def strip_units(self, item):
    # """Remove units and set in the standard unit set for this engine.

    # Each engine needs to know how to do its own unit system. The default
    # assumes there is no unit system.

    # Parameters
    # ----------
    # item : object with units
    # the input with units

    # Returns
    # -------
    # float or iterable
    # the result without units, in the engine's specific unit system
    # """
    # return item

    def start(self, snapshot=None):
        if snapshot is not None:
            self.current_snapshot = snapshot

    def stop(self, trajectory):
        """Nothing special needs to be done for direct-control simulations
        when you hit a stop condition."""
        pass

    def stop_conditions(self,
                        trajectory,
                        continue_conditions=None,
                        trusted=True):
        """
        Test whether we can continue; called by generate a couple of times,
        so the logic is separated here.

        Parameters
        ----------
        trajectory : :class:`openpathsampling.trajectory.Trajectory`
            the trajectory we've generated so far
        continue_conditions : (list of) function(Trajectory)
            callable function of a 'Trajectory' that returns True or False.
            If one of these returns False the simulation is stopped.
        trusted : bool
            If `True` (default) the stopping conditions are evaluated
            as trusted.

        Returns
        -------
        bool
            true if the dynamics should be stopped; false otherwise
        """
        stop = False
        if continue_conditions is not None:
            if isinstance(continue_conditions, list):
                for condition in continue_conditions:
                    can_continue = condition(trajectory, trusted)
                    stop = stop or not can_continue
            else:
                stop = not continue_conditions(trajectory, trusted)

        return stop

    def generate(self, snapshot, running=None, direction=+1):
        r"""
        Generate a trajectory consisting of ntau segments of tau_steps in
        between storage of Snapshots.

        Parameters
        ----------
        snapshot : :class:`openpathsampling.snapshot.Snapshot`
            initial coordinates and velocities in form of a Snapshot object
        running : (list of)
        function(:class:`openpathsampling.trajectory.Trajectory`)
            callable function of a 'Trajectory' that returns True or False.
            If one of these returns False the simulation is stopped.
        direction : -1 or +1 (DynamicsEngine.FORWARD or DynamicsEngine.BACKWARD)
            If +1 then this will integrate forward, if -1 it will reversed the
            momenta of the given snapshot and then prepending generated
            snapshots with reversed momenta. This will generate a _reversed_
            trajectory that effectively ends in the initial snapshot

        Returns
        -------    
        trajectory : :class:`openpathsampling.trajectory.Trajectory`
            generated trajectory of initial conditions, including initial
            coordinate set

        Notes
        -----
        If the returned trajectory has length n_frames_max it can still happen
        that it stopped because of the stopping criterion. You need to check
        in that case.
        """

        trajectory = None
        it = self.iter_generate(snapshot,
                                running,
                                direction,
                                intervals=0,
                                max_length=self.options['n_frames_max'])

        for trajectory in it:
            pass

        return trajectory

    def iter_generate(self,
                      initial,
                      running=None,
                      direction=+1,
                      intervals=10,
                      max_length=0):
        r"""
        Return a generator that will generate a trajectory, returning the
        current trajectory in given intervals

        Parameters
        ----------
        initial : :class:`openpathsampling.Snapshot` or
        :class:`openpathsampling.Trajectory`
            initial coordinates and velocities in form of a Snapshot object
            or a trajectory
        running : (list of)
        function(:class:`openpathsampling.trajectory.Trajectory`)
            callable function of a 'Trajectory' that returns True or False.
            If one of these returns False the simulation is stopped.
        direction : -1 or +1 (DynamicsEngine.FORWARD or DynamicsEngine.BACKWARD)
            If +1 then this will integrate forward, if -1 it will reversed the
            momenta of the given snapshot and then prepending generated
            snapshots with reversed momenta. This will generate a _reversed_
            trajectory that effectively ends in the initial snapshot
        intervals : int
            number steps after which the current status is returned. If `0`
            it will run until the end or a keyboard interrupt is detected
        max_length : int
            will limit the simulation length to a number of steps. Default is
            `0` which will run unlimited

        Yields
        ------
        trajectory : :class:`openpathsampling.trajectory.Trajectory`
            generated trajectory of initial conditions, including initial
            coordinate set

        Notes
        -----
        If the returned trajectory has length n_frames_max it can still happen
        that it stopped because of the stopping criterion. You need to check
        in that case.
        """

        if direction == 0:
            raise RuntimeError(
                'direction must be positive (FORWARD) or negative (BACKWARD).')

        try:
            iter(running)
        except TypeError:
            running = [running]

        if hasattr(initial, '__iter__'):
            initial = Trajectory(initial)
        else:
            initial = Trajectory([initial])

        valid = False
        attempt_nan = 0
        attempt_error = 0
        attempt_max_length = 0
        trajectory = initial

        final_error = None
        errors = []

        while not valid and final_error is None:
            if attempt_nan + attempt_error > 1:
                # let's get a new initial trajectory the way the user wants to
                if self.on_retry == 'full':
                    trajectory = initial
                elif self.on_retry == 'remove_interval':
                    trajectory = \
                        trajectory[:max(
                            len(initial),
                            len(trajectory) - intervals)]
                elif self.on_retry == 'keep_half':
                    trajectory = \
                        trajectory[:min(
                            int(len(trajectory) * 0.9),
                            max(
                                len(initial),
                                len(trajectory) / 2))]
                elif hasattr(self.on_retry, '__call__'):
                    trajectory = self.on_retry(trajectory)

            if direction > 0:
                self.current_snapshot = trajectory[-1]
            elif direction < 0:
                # backward simulation needs reversed snapshots
                self.current_snapshot = trajectory[0].reversed

            logger.info("Starting trajectory")
            self.start()

            frame = 0
            # maybe we should stop before we even begin?
            stop = self.stop_conditions(trajectory=trajectory,
                                        continue_conditions=running,
                                        trusted=False)

            log_rate = 10
            has_nan = False
            has_error = False

            while not stop:
                if intervals > 0 and frame % intervals == 0:
                    # return the current status
                    logger.info("Through frame: %d", frame)
                    yield trajectory

                elif frame % log_rate == 0:
                    logger.info("Through frame: %d", frame)

                # Do integrator x steps

                snapshot = None

                try:
                    with DelayedInterrupt():
                        snapshot = self.generate_next_frame()

                        # if self.on_nan != 'ignore' and \
                        if not self.is_valid_snapshot(snapshot):
                            has_nan = True
                            break

                except KeyboardInterrupt as e:
                    # make sure we will report the last state for
                    logger.info('Keyboard interrupt. Shutting down simulation')
                    final_error = e
                    break

                except:
                    # any other error we start a retry
                    e = sys.exc_info()
                    errors.append(e)
                    se = str(e).lower()
                    if 'nan' in se and \
                            ('particle' in se or 'coordinates' in se):
                        # this cannot be ignored because we cannot continue!
                        has_nan = True
                        break
                    else:
                        has_error = True
                        break

                frame += 1

                # Store snapshot and add it to the trajectory.
                # Stores also final frame the last time
                if direction > 0:
                    trajectory.append(snapshot)
                elif direction < 0:
                    trajectory.insert(0, snapshot.reversed)

                if 0 < max_length < len(trajectory):
                    # hit the max length criterion
                    on = self.on_max_length
                    del trajectory[-1]

                    if on == 'fail':
                        final_error = EngineMaxLengthError(
                            'Hit maximal length of %d frames.' %
                            self.options['n_frames_max'], trajectory)
                        break
                    elif on == 'stop':
                        logger.info('Trajectory hit max length. Stopping.')
                        # fail gracefully
                        stop = True
                    elif on == 'retry':
                        attempt_max_length += 1
                        if attempt_max_length > self.retries_when_max_length:
                            if self.on_nan == 'fail':
                                final_error = EngineMaxLengthError(
                                    'Failed to generate trajectory without '
                                    'hitting max length after %d attempts' %
                                    attempt_max_length, trajectory)
                                break

                if stop is False:
                    # Check if we should stop. If not, continue simulation
                    stop = self.stop_conditions(trajectory=trajectory,
                                                continue_conditions=running)

            if has_nan:
                on = self.on_nan
                if on == 'fail':
                    final_error = EngineNaNError('`nan` in snapshot',
                                                 trajectory)
                elif on == 'retry':
                    attempt_nan += 1
                    if attempt_nan > self.retries_when_nan:
                        final_error = EngineNaNError(
                            'Failed to generate trajectory without `nan` '
                            'after %d attempts' % attempt_error, trajectory)

            elif has_error:
                on = self.on_nan
                if on == 'fail':
                    final_error = errors[-1][1]
                    del errors[-1]
                elif on == 'retry':
                    attempt_error += 1
                    if attempt_error > self.retries_when_error:
                        final_error = EngineError(
                            'Failed to generate trajectory without `nan` '
                            'after %d attempts' % attempt_error, trajectory)

            elif stop:
                valid = True

            self.stop(trajectory)

        if errors:
            logger.info('Errors occurred during generation :')
            for no, e in enumerate(errors):
                logger.info('[#%d] %s' % (no, repr(e[1])))

        if final_error is not None:
            yield trajectory
            logger.info("Through frame: %d", len(trajectory))
            raise final_error

        logger.info("Finished trajectory, length: %d", len(trajectory))
        yield trajectory

    def generate_next_frame(self):
        raise NotImplementedError('Next frame generation must be implemented!')

    def generate_n_frames(self, n_frames=1):
        """Generates n_frames, from but not including the current snapshot.
        
        This generates a fixed number of frames at once. If you desire the
        reversed trajectory, you can reverse the returned trajectory.

        Parameters
        ----------
        n_frames : integer
            number of frames to generate

        Returns
        -------
        paths.Trajectory()
            the `n_frames` of the trajectory following (and not including)
            the initial `current_snapshot`
        """
        self.start()
        traj = Trajectory(
            [self.generate_next_frame() for i in range(n_frames)])
        self.stop(traj)
        return traj

    @staticmethod
    def is_valid_snapshot(snapshot):
        """
        Test the snapshot to be valid. Usually not containing nan

        Returns
        -------
        bool : True
            returns `True` if the snapshot is okay to be used
        """
        return True

    @classmethod
    def check_snapshot_type(cls, snapshot):
        if not isinstance(snapshot, cls.base_snapshot_type):
            logger.warning(
                ('This engine is intended for "%s" and derived classes. '
                 'You are using "%s". Make sure that this is intended.') %
                (cls.base_snapshot_type.__name__, snapshot.__class__.__name__))
コード例 #7
0
    def __init__(self, filename, mode=None, units=None):
        """
        Create a storage for complex objects in a netCDF file

        Parameters
        ----------
        filename : string
            filename of the netcdf file to be used or created
        mode : string, default: None
            the mode of file creation, one of 'w' (write), 'a' (append) or 'r' (read-only)
            None, which will append any existing files.
        units : dict of {str : simtk.unit.Unit } or None
            representing a dict of string representing a dimension
            ('length', 'velocity', 'energy') pointing to
            the simtk.unit.Unit to be used. If not `None` it overrides the
            standard units used

        Notes
        -----
        A single file can be opened by multiple storages, but only one can be used for writing

        """

        if mode is None:
            mode = 'a'

        exists = os.path.isfile(filename)
        if exists and mode == 'a':
            logger.info(
                "Open existing netCDF file '%s' for appending - appending existing file",
                filename)
        elif exists and mode == 'w':
            logger.info(
                "Create new netCDF file '%s' for writing - deleting existing file",
                filename)
        elif not exists and mode == 'a':
            logger.info(
                "Create new netCDF file '%s' for appending - appending non-existing file",
                filename)
        elif not exists and mode == 'w':
            logger.info(
                "Create new netCDF file '%s' for writing - creating new file",
                filename)
        elif not exists and mode == 'r':
            logger.info(
                "Open existing netCDF file '%s' for reading - file does not exist",
                filename)
            raise RuntimeError("File '%s' does not exist." % filename)
        elif exists and mode == 'r':
            logger.info(
                "Open existing netCDF file '%s' for reading - reading from existing file",
                filename)

        self.filename = filename

        # call netCDF4-python to create or open .nc file
        super(NetCDFPlus, self).__init__(filename, mode)

        self._setup_class()

        if units is not None:
            self.dimension_units.update(units)

        self._register_storages()
        self.simplifier.update_class_list()

        if mode == 'w':
            logger.info("Setup netCDF file and create variables")

            # add shared scalar dimension for everyone
            self.create_dimension('scalar', 1)

            self._initialize()

            logger.info("Finished setting up netCDF file")

        elif mode == 'a' or mode == 'r+' or mode == 'r':
            logger.debug("Restore the dict of units from the storage")

            # Create a dict of simtk.Unit() instances for all netCDF.Variable()
            for variable_name in self.variables:
                variable = self.variables[variable_name]

                if self.support_simtk_unit:
                    import simtk.unit as u
                    if hasattr(variable, 'unit_simtk'):
                        unit_dict = self.simplifier.from_json(
                            getattr(variable, 'unit_simtk'))
                        if unit_dict is not None:
                            unit = self.simplifier.unit_from_dict(unit_dict)
                        else:
                            unit = self.simplifier.unit_from_dict(u.Unit({}))

                        self.units[str(variable_name)] = unit

            self.update_delegates()

            # After we have restored the units we can load objects from the storage
            self._restore()

        self.sync()
コード例 #8
0
class DynamicsEngine(StorableNamedObject):
    '''
    Wraps simulation tool (parameters, storage, etc.)

    Notes
    -----
    Should be considered an abstract class: only its subclasses can be
    instantiated.
    '''

    FORWARD = 1
    BACKWARD = -1

    _default_options = {'n_frames_max': None, 'timestep': None}

    units = {
        'length': u.Unit({}),
        'velocity': u.Unit({}),
        'energy': u.Unit({})
    }

    def __init__(self, options=None, template=None):
        '''
        Create an empty DynamicsEngine object
        
        Notes
        -----
        The purpose of an engine is to create trajectories and keep track
        of the results. The main method is 'generate' to create a
        trajectory, which is a list of snapshots and then can store the in
        the associated storage. In the initialization this storage is
        created as well as the related Trajectory and Snapshot classes are
        initialized.
        '''

        super(DynamicsEngine, self).__init__()

        self.template = template

        # Trajectories need to know the engine as a hack to get the topology.
        # Better would be a link to the topology directly. This is needed to create
        # mdtraj.Trajectory() objects

        # TODO: Remove this and put the logic outside of the engine. The engine in trajectory is only
        # used to get the solute indices which should depend on the topology anyway
        # Trajectory.engine = self

        self._check_options(options)

        # as default set a newly generated engine as the default engine
        self.set_as_default()

    def _check_options(self, options=None):
        """
        This will register all variables in the options dict as a member variable if
        they are present in either the DynamicsEngine.default_options or this
        classes default_options, no multiple inheritance is supported!
        It will use values with the priority in the following order
        - DynamicsEngine.default_options
        - self.default_options
        - self.options (usually not used)
        - options (function parameter)
        Parameters are only registered if
        1. the variable name is present in the defaults
        2. the type matches the one in the defaults
        3. for variables with units also the units need to be compatible

        Parameters
        ----------
        options : dict of { str : value }
            A dictionary

        Notes
        -----
        Options are what is necessary to recreate the engine, but not runtime variables or independent
        variables like the actual initialization status, the runners or an attached storage.
        If there are non-default options present they will be ignored (no error thrown)
        """
        # start with default options from a dynamics engine
        my_options = {}
        okay_options = {}

        # self.default_options overrides default ones from DynamicsEngine
        for variable, value in self.default_options.iteritems():
            my_options[variable] = value

        if hasattr(self, 'options') and self.options is not None:
            # self.options overrides default ones
            for variable, value in self.options.iteritems():
                my_options[variable] = value

        if options is not None:
            # given options override even default and already stored ones
            for variable, value in options.iteritems():
                my_options[variable] = value

        if my_options is not None:
            for variable, default_value in self.default_options.iteritems():
                # create an empty member variable if not yet present
                if not hasattr(self, variable):
                    okay_options[variable] = None

                if variable in my_options:
                    if type(my_options[variable]) is type(default_value):
                        if type(my_options[variable]) is u.Unit:
                            if my_options[variable].unit.is_compatible(
                                    default_value):
                                okay_options[variable] = my_options[variable]
                            else:
                                raise ValueError(
                                    'Unit of option "' + str(variable) +
                                    '" (' + str(my_options[variable].unit) +
                                    ') not compatible to "' +
                                    str(default_value.unit) + '"')

                        elif type(my_options[variable]) is list:
                            if type(my_options[variable][0]) is type(
                                    default_value[0]):
                                okay_options[variable] = my_options[variable]
                            else:
                                raise ValueError('List elements for option "' +
                                                 str(variable) +
                                                 '" must be of type "' +
                                                 str(type(default_value[0])) +
                                                 '"')
                        else:
                            okay_options[variable] = my_options[variable]
                    elif isinstance(type(my_options[variable]),
                                    type(default_value)):
                        okay_options[variable] = my_options[variable]
                    elif default_value is None:
                        okay_options[variable] = my_options[variable]
                    else:
                        raise ValueError('Type of option "' + str(variable) +
                                         '" (' +
                                         str(type(my_options[variable])) +
                                         ') is not "' +
                                         str(type(default_value)) + '"')

            self.options = okay_options
        else:
            self.options = {}

    def __getattr__(self, item):
        # default is to look for an option and return it's value
        return self.options[item]

    @property
    def topology(self):
        return self.template.topology

    @property
    def n_atoms(self):
        return self.topology.n_atoms

    @property
    def n_spatial(self):
        return self.topology.n_spatial

    def to_dict(self):
        return {'options': self.options, 'template': self.template}

    def set_as_default(self):
        paths.EngineMover.engine = self

    @property
    def default_options(self):
        default_options = {}
        default_options.update(DynamicsEngine._default_options)
        default_options.update(self._default_options)
        return default_options

    def start(self, snapshot=None):
        if snapshot is not None:
            self.current_snapshot = snapshot

    def stop(self, trajectory):
        """Nothing special needs to be done for direct-control simulations
        when you hit a stop condition."""
        pass

    def stop_conditions(self,
                        trajectory,
                        continue_conditions=None,
                        trusted=True):
        """
        Test whether we can continue; called by generate a couple of times,
        so the logic is separated here.

        Parameters
        ----------
        trajectory : Trajectory
            the trajectory we've generated so far
        continue_conditions : list of function(Trajectory)
            callable function of a 'Trajectory' that returns True or False.
            If one of these returns False the simulation is stopped.

        Returns
        -------
        boolean:
            true if the dynamics should be stopped; false otherwise
        """
        stop = False
        if continue_conditions is not None:
            for condition in continue_conditions:
                can_continue = condition(trajectory, trusted)
                stop = stop or not can_continue
        return stop

    def generate_forward(self, snapshot, ensemble):
        """
        Generate a potential trajectory in ensemble simulating forward in time
        """

        return self.generate(snapshot, ensemble.can_append, direction=+1)

    def generate_backward(self, snapshot, ensemble):
        """
        Generate a potential trajectory in ensemble simulating forward in time
        """

        return self.generate(snapshot, ensemble.can_prepend, direction=-1)

    def generate(self, snapshot, running=None, direction=+1):
        r"""
        Generate a trajectory consisting of ntau segments of tau_steps in
        between storage of Snapshots.

        Parameters
        ----------
        snapshot : Snapshot 
            initial coordinates and velocities in form of a Snapshot object
        running : (list of) function(Trajectory)
            callable function of a 'Trajectory' that returns True or False.
            If one of these returns False the simulation is stopped.
        direction : -1 or +1 (DynamicsEngine.FORWARD or DynamicsEngine.BACKWARD)
            If +1 then this will integrate forward, if -1 it will reversed the
            momenta of the given snapshot and then prepending generated snapshots
            with reversed momenta. This will generate a _reversed_ trajectory that
            effectively ends in the initial snapshot

        Returns
        -------    
        trajectory : Trajectory
            generated trajectory of initial conditions, including initial
            coordinate set

        Notes
        -----
        If the returned trajectory has length n_frames_max it can still happen
        that it stopped because of the stopping criterion. You need to check
        in that case.
        """

        if direction == 0:
            raise RuntimeError(
                'direction must be positive (FORWARD) or negative (BACKWARD).')

        try:
            iter(running)
        except:
            running = [running]

        trajectory = paths.Trajectory()

        if direction > 0:
            self.current_snapshot = snapshot
        elif direction < 0:
            # backward simulation needs reversed snapshots
            self.current_snapshot = snapshot.reversed

        self.start()

        # Store initial state for each trajectory segment in trajectory.
        trajectory.append(snapshot)

        frame = 0
        # maybe we should stop before we even begin?
        stop = self.stop_conditions(trajectory=trajectory,
                                    continue_conditions=running,
                                    trusted=False)

        logger.info("Starting trajectory")
        log_freq = 10  # TODO: set this from a singleton class
        while stop == False:
            if self.options.get('n_frames_max', None) is not None:
                if len(trajectory) >= self.options['n_frames_max']:
                    break

            # Do integrator x steps
            snapshot = self.generate_next_frame()
            frame += 1
            if frame % log_freq == 0:
                logger.info("Through frame: %d", frame)

            # Store snapshot and add it to the trajectory. Stores also
            # final frame the last time
            if direction > 0:
                trajectory.append(snapshot)
            elif direction < 0:
                # We are simulating forward and just build in backwards order
                trajectory.prepend(snapshot.reversed)

            # Check if we should stop. If not, continue simulation
            stop = self.stop_conditions(trajectory=trajectory,
                                        continue_conditions=running)

        # exit the while loop once we must stop, so we call the engine's
        # stop function (which should manage any end-of-trajectory
        # cleanup)
        self.stop(trajectory)
        logger.info("Finished trajectory, length: %d", frame)
        return trajectory

    def generate_next_frame(self):
        raise NotImplementedError('Next frame generation must be implemented!')
コード例 #9
0
 def get_unit(self, dimension):
     """
     Return a simtk.Unit instance from the unit_system the is of the specified dimension, e.g. length, time
     """
     return u.Unit(
         {self.unit_system.base_units[u.BaseDimension(dimension)]: 1.0})