diff --git a/.github/workflows/test-2.0.yml b/.github/workflows/test-2.0.yml new file mode 100644 index 000000000..727c2e50b --- /dev/null +++ b/.github/workflows/test-2.0.yml @@ -0,0 +1,84 @@ +on: + pull_request: + branches: + - dev-2.0 + push: + branches: + - dev-2.0 + +defaults: + run: + shell: bash -l {0} + +jobs: + unit_tests: + runs-on: ubuntu-latest + name: "Unit Tests" + strategy: + # NOTE: When changing the test matrix: + # * Update the after_n_builds in codecov.yml to match the number of + # builds in the matrix + # * You may need to create another example run for the notebook tests + # (see examples/ipynbtests.sh; examples/prep_example_data.py). This + # is because Python internals (usually bytecode, which is saved in + # CVs) can differ between minor Python versions. + matrix: + CONDA_PY: + - "3.12" + - "3.11" + - "3.10" + MINIMAL: [""] + include: + - CONDA_PY: "3.10" + MINIMAL: "minimal" + + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 2 + - uses: actions/setup-python@v2 + - uses: conda-incubator/setup-miniconda@v2 + with: + auto-update-conda: true + python-version: ${{ matrix.CONDA_PY }} + miniforge-variant: Mambaforge + - name: "Install requirements" + env: + MINIMAL: ${{ matrix.MINIMAL }} + CONDA_PY: ${{ matrix.CONDA_PY }} + run: | + if [ -z "$MINIMAL" ] ; then + source devtools/conda_install_reqs.sh + else + # In this case we're actually double-installing (not just + # installing requirements here). We prefer this to using the + # conda_install_reqs script so we can test deps coming from PyPI + python -m pip install -e .[test] + fi + python -m pip install autorelease + - name: "Install" + run: | + python -m pip install --no-deps -e . + - name: "Check installation" + run: | + python -c "import openpathsampling; print(openpathsampling.version.full_version)" + - name: "Versions" + run: | + conda info --envs + conda list + - name: "Autorelease check" + run: python devtools/autorelease_check.py + #- name: "DEBUG: enable SSH login" + #uses: mxschmitt/action-tmate@v3 + - name: "Unit Tests" + env: + PY_COLORS: "1" + run: py.test -vv -s --cov=openpathsampling --cov-report xml + - name: "Tests: Experimental" + if: matrix.MINIMAL == '' + run: py.test -vv -s openpathsampling/experimental + #- name: "Report coverage" + #run: bash <(curl -s https://codecov.io/bash) + - name: "Notebook tests" + if: matrix.MINIMAL == '' + run: pushd examples/ && ./ipynbtests.sh || exit 1 && popd diff --git a/examples/ipynbtests.sh b/examples/ipynbtests.sh index 6b307340d..13c941914 100755 --- a/examples/ipynbtests.sh +++ b/examples/ipynbtests.sh @@ -19,8 +19,8 @@ case $PYTHON_VERSION in mistis=$dropbox_base_url/76981cbgxm639m3/toy_mistis_1k_OPS1_py36.nc ;; "3.7") - mstis=$dropbox_base_url/1ulzssv5p4lr61f/toy_mstis_1k_OPS1_py36.nc - mistis=$dropbox_base_url/76981cbgxm639m3/toy_mistis_1k_OPS1_py36.nc + mstis=$dropbox_base_url/s5d76vg5jyf84oa/toy_mstis_1k_OPS1_py37.nc + mistis=$dropbox_base_url/vzyywl3yli3pp8u/toy_mistis_1k_OPS1_py37.nc ;; "3.8") mstis=$dropbox_base_url/8rr0tt25xlm47cs/toy_mstis_1k_OPS1_py38.nc diff --git a/openpathsampling/__init__.py b/openpathsampling/__init__.py index 79e43da7d..329af1855 100644 --- a/openpathsampling/__init__.py +++ b/openpathsampling/__init__.py @@ -91,7 +91,7 @@ RejectedMaxLengthSampleMoveChange ) -from .pathmover import Details, MoveDetails, SampleDetails +from .pathmover import Details from .pathmover import ( RandomChoiceMover, PathMover, ConditionalSequentialMover, diff --git a/openpathsampling/analysis/shooting_point_analysis.py b/openpathsampling/analysis/shooting_point_analysis.py index 916d09517..407757cb5 100644 --- a/openpathsampling/analysis/shooting_point_analysis.py +++ b/openpathsampling/analysis/shooting_point_analysis.py @@ -4,12 +4,7 @@ import warnings from openpathsampling.progress import SimpleProgress - -try: - from collections import abc -except ImportError: - import collections as abc - +from collections import abc # based on http://stackoverflow.com/a/3387975 class TransformedDict(abc.MutableMapping): @@ -77,8 +72,7 @@ def __init__(self, *args, **kwargs): *args, **kwargs) -class ShootingPointAnalysisError(AssertionError): - # TODO this should inherit from a different Error type in OPS 2.0 +class ShootingPointAnalysisError(Exception): pass diff --git a/openpathsampling/collectivevariable.py b/openpathsampling/collectivevariable.py index f4cbc5ad5..d3fe41288 100644 --- a/openpathsampling/collectivevariable.py +++ b/openpathsampling/collectivevariable.py @@ -5,14 +5,7 @@ from openpathsampling.netcdfplus import WeakKeyCache, \ ObjectJSON, create_to_dict, ObjectStore, PseudoAttribute -from openpathsampling.deprecations import (has_deprecations, deprecate, - MSMBUILDER, PYEMMA) - -import sys -if sys.version_info > (3, ): - get_code = lambda func: func.__code__ -else: - get_code = lambda func: func.func_code +get_code = lambda func: func.__code__ # ============================================================================== @@ -583,174 +576,4 @@ def to_dict(self): 'cv_requires_lists': self.cv_requires_lists, 'cv_wrap_numpy_array': self.cv_wrap_numpy_array, 'cv_scalarize_numpy_singletons': self.cv_scalarize_numpy_singletons - } - - -@has_deprecations -@deprecate(MSMBUILDER) -class MSMBFeaturizerCV(CoordinateGeneratorCV): - """A CollectiveVariable that uses an MSMBuilder3 featurizer""" - - def __init__( - self, - name, - featurizer, - topology, - cv_wrap_numpy_array=True, - cv_scalarize_numpy_singletons=True, - **kwargs - ): - """ - - Parameters - ---------- - name - featurizer : msmbuilder.Featurizer, callable - the featurizer used as a callable class - topology : :obj:`openpathsampling.engines.topology.MDTrajTopology` - the mdtraj topology wrapper from OPS that is used to initialize - the featurizer in ``pyemma.coordinates.featurizer(topology)`` - **kwargs : - a dictionary of named arguments which should be given to ``c`` - (for example, the atoms which define a specific distance/angle). - Finally an instance ``instance = cls(**kwargs)`` is created when - the CV is created and using the CV will call - ``instance(snapshots)`` - cv_wrap_numpy_array - cv_scalarize_numpy_singletons - - Notes - ----- - All trajectories or snapshots passed in kwargs will be converted - to mdtraj objects for convenience - """ - - md_kwargs = dict() - md_kwargs.update(kwargs) - - # turn Snapshot and Trajectory into md.trajectory - for key in md_kwargs: - if isinstance(md_kwargs[key], paths.BaseSnapshot): - md_kwargs[key] = md_kwargs[key].to_mdtraj() - elif isinstance(md_kwargs[key], paths.Trajectory): - md_kwargs[key] = md_kwargs[key].to_mdtraj() - - self._instance = featurizer(**md_kwargs) - self.topology = topology - - super(GeneratorCV, self).__init__( - name, - cv_callable=featurizer, - cv_time_reversible=True, - cv_requires_lists=True, - cv_wrap_numpy_array=cv_wrap_numpy_array, - cv_scalarize_numpy_singletons=cv_scalarize_numpy_singletons, - **kwargs - ) - - @property - def featurizer(self): - return self.cv_callable - - def _eval(self, items): - trajectory = paths.Trajectory(items) - - # create an mdtraj trajectory out of it - ptraj = trajectory_to_mdtraj(trajectory, self.topology.mdtraj) - - # run the featurizer - return self._instance.partial_transform(ptraj) - - def to_dict(self): - return { - 'name': self.name, - 'featurizer': ObjectJSON.callable_to_dict(self.featurizer), - 'topology': self.topology, - 'kwargs': self.kwargs, - 'cv_wrap_numpy_array': self.cv_wrap_numpy_array, - 'cv_scalarize_numpy_singletons': self.cv_scalarize_numpy_singletons - } - - -@has_deprecations -@deprecate(PYEMMA) -class PyEMMAFeaturizerCV(MSMBFeaturizerCV): - """Make a CV from a function that takes mdtraj.trajectory as input. - - This is identical to :class:`CoordinateGeneratorCV` except that the - function is called with an :class:`mdraj.Trajetory` object instead of the - :class:`openpathsampling.Trajectory` one using ``fnc(traj.to_mdtraj(), - **kwargs)`` - - """ - - def __init__( - self, - name, - featurizer, - topology, - **kwargs - ): - """ - - Parameters - ---------- - name - featurizer : :class:`pyemma.coordinates.featurizer` - the pyemma featurizer used as a callable class - topology : :obj:`openpathsampling.engines.topology.MDTrajTopology` - the mdtraj topology wrapper from OPS that is used to initialize - the featurizer in ``pyemma.coordinates.featurizer(topology)`` - **kwargs : dict - a dictionary of named arguments which should be given to the - ``featurizer`` (for example, the atoms which define a specific - distance/angle). - Finally an instance ``instance = cls(**kwargs)`` is created when - the CV is created and using the CV will call - ``instance(snapshots)`` - - Notes - ----- - All trajectories or snapshots passed in kwargs will be converted - to mdtraj objects for convenience - """ - - md_kwargs = dict() - md_kwargs.update(kwargs) - - # turn Snapshot and Trajectory into md.trajectory - for key in md_kwargs: - if isinstance(md_kwargs[key], paths.BaseSnapshot): - md_kwargs[key] = md_kwargs[key].to_mdtraj() - elif isinstance(md_kwargs[key], paths.Trajectory): - md_kwargs[key] = md_kwargs[key].to_mdtraj() - - self.topology = topology - - import pyemma.coordinates - self._instance = pyemma.coordinates.featurizer(self.topology.mdtraj) - - featurizer(self._instance, **md_kwargs) - - super(GeneratorCV, self).__init__( - name, - cv_callable=featurizer, - cv_requires_lists=True, - cv_wrap_numpy_array=True, - cv_scalarize_numpy_singletons=True, - **kwargs - ) - - def _eval(self, items): - trajectory = paths.Trajectory(items) - - t = trajectory_to_mdtraj(trajectory, self.topology.mdtraj) - return self._instance.transform(t) - - def to_dict(self): - return { - 'name': self.name, - 'featurizer': ObjectJSON.callable_to_dict(self.featurizer), - 'topology': self.topology, - 'kwargs': self.kwargs - } + } \ No newline at end of file diff --git a/openpathsampling/collectivevariables/__init__.py b/openpathsampling/collectivevariables/__init__.py index 2171968f0..9c2ee1a91 100644 --- a/openpathsampling/collectivevariables/__init__.py +++ b/openpathsampling/collectivevariables/__init__.py @@ -2,8 +2,6 @@ CollectiveVariable, FunctionCV, CoordinateFunctionCV, GeneratorCV, CoordinateGeneratorCV, InVolumeCV, CallableCV, MDTrajFunctionCV, - PyEMMAFeaturizerCV, - MSMBFeaturizerCV, ) from .plumed_wrapper import ( PLUMEDCV, PLUMEDInterface diff --git a/openpathsampling/deprecations.py b/openpathsampling/deprecations.py index ed1f2c058..5be1b287e 100644 --- a/openpathsampling/deprecations.py +++ b/openpathsampling/deprecations.py @@ -125,50 +125,6 @@ def version_tuple_to_string(version_tuple): deprecated_in=(1, 5, 1) ) -OPENMMTOOLS_VERSION = Deprecation( - problem="{OPS} {version} will require OpenMMTools 0.15 or later.", - remedy="Please update OpenMMTools.", - remove_version=(2, 0), - deprecated_in=(0, 9, 6) -) - -SAMPLE_DETAILS = Deprecation( - problem="SampleDetails will be removed in {OPS} {version}.", - remedy="Use generic Details class instead.", - remove_version=(2, 0), - deprecated_in=(0, 9, 3) -) - -MOVE_DETAILS = Deprecation( - problem="MoveDetails will be removed in {OPS} {version}.", - remedy="Use generic Details class instead.", - remove_version=(2, 0), - deprecated_in=(0, 9, 3) -) - -SAVE_RELOAD_OLD_TPS_NETWORK = Deprecation( - problem="Old TPS networks will not be reloaded in {OPS} {version}.", - remedy="This file may not work with {OPS} {version}.", - remove_version=(2, 0), - deprecated_in=(0, 9, 3) -) - -MSMBUILDER = Deprecation( - problem=("MSMBuilder is no longer maintained. " - + "MSMBFeaturizer is no longer officially supported."), - remedy="Create a CoordinateFunctionCV based on MSMBuilderFeaturizers.", - remove_version=(2, 0), - deprecated_in=(1, 1, 0) -) - -PYEMMA = Deprecation( - problem=("PyEMMA is no longer maintained. " - "PyEMMAFeaturizerCV is no longer officially supported."), - remedy="Create a CoordinateFunctionCV to represent the same function.", - remove_version=(2, 0), - deprecated_in=(1, 6, 1) -) - OPENMM_MDTRAJTOPOLOGY = Deprecation( problem=("openpathsampling.engines.openmm.topology.MDTrajTopology " "has been moved."), @@ -241,12 +197,7 @@ def has_deprecations(cls): """Decorator to ensure that docstrings get updated for wrapped class""" for obj in [cls] + list(vars(cls).values()): if callable(obj) and hasattr(obj, '__new_docstring'): - try: - obj.__doc__ = obj.__new_docstring - except AttributeError: - # probably Python 2; we can't update docstring in Py2 - # see https://github.com/Chilipp/docrep/pull/9 and related - pass + obj.__doc__ = obj.__new_docstring del obj.__new_docstring return cls diff --git a/openpathsampling/engines/dynamics_engine.py b/openpathsampling/engines/dynamics_engine.py index e77c123fb..8e4f5818e 100644 --- a/openpathsampling/engines/dynamics_engine.py +++ b/openpathsampling/engines/dynamics_engine.py @@ -18,9 +18,6 @@ logger = logging.getLogger(__name__) -if sys.version_info > (3, ): - basestring = str - # ============================================================================= # SOURCE CONTROL # ============================================================================= @@ -252,9 +249,6 @@ def _check_options(self, options=None): okay_options[variable] = my_options[variable] elif isinstance(my_options[variable], type(default_value)): okay_options[variable] = my_options[variable] - elif isinstance(my_options[variable], basestring) \ - and isinstance(default_value, basestring): - okay_options[variable] = my_options[variable] elif default_value is None: okay_options[variable] = my_options[variable] else: diff --git a/openpathsampling/engines/openmm/engine.py b/openpathsampling/engines/openmm/engine.py index 63209fb1f..4995d35cc 100644 --- a/openpathsampling/engines/openmm/engine.py +++ b/openpathsampling/engines/openmm/engine.py @@ -30,18 +30,9 @@ def restore_custom_integrator_interface(integrator): except ImportError: # pragma: no cover pass # if openmmtools doesn't exist, can't restore interface else: - try: - # openmmtools 0.15 or later - from openmmtools.utils import RestorableOpenMMObject \ - as RestorableObject - except ImportError: # pragma: no cover - # DEPRECATED: remove in 2.0 (support for openmmtools < 0.15) - from openpathsampling.deprecations import OPENMMTOOLS_VERSION - OPENMMTOOLS_VERSION.warn() - from openmmtools.integrators import RestorableIntegrator \ - as RestorableObject - - if RestorableObject.is_restorable(integrator): + # openmmtools 0.15 or later + from openmmtools.utils import RestorableOpenMMObject + if RestorableOpenMMObject.is_restorable(integrator): success = RestorableObject.restore_interface(integrator) logger.debug("Restored interface to integrator: " + str(success)) # this return a bool based on success; we could error on fail, diff --git a/openpathsampling/engines/trajectory.py b/openpathsampling/engines/trajectory.py index 8380dd549..9f6e66876 100644 --- a/openpathsampling/engines/trajectory.py +++ b/openpathsampling/engines/trajectory.py @@ -169,18 +169,10 @@ def is_snapshot_attr(cls, item): ) raise AttributeError(msg) - # ========================================================================== # LIST INHERITANCE FUNCTIONS # ========================================================================== - def __getslice__(self, *args, **kwargs): - ret = list.__getslice__(self, *args, **kwargs) - if type(ret) is list: - ret = Trajectory(ret) - - return ret - # this is intuitive. hash(Trajectory(traj)) == hash(traj) # but hash(LoaderProxy(..., traj.__uuid__)) != hash(traj) diff --git a/openpathsampling/high_level/interface_set.py b/openpathsampling/high_level/interface_set.py index f0c27eebb..ad13fd643 100644 --- a/openpathsampling/high_level/interface_set.py +++ b/openpathsampling/high_level/interface_set.py @@ -216,22 +216,13 @@ def to_dict(self): 'intersect_with': self.intersect_with, 'lambdas': self.lambdas, 'direction': self.direction, - 'volumes': self.volumes} - try: - dct.update({'cv_max': self.cv_max}) - except AttributeError: # pragma: no cover - # reverse compatibility; deprecated in 0.9.5, remove in 2.0 - pass - + 'volumes': self.volumes, + 'cv_max': self.cv_max} return dct def _load_from_dict(self, dct): self.cv = dct['cv'] - try: - self.cv_max = dct['cv_max'] - except KeyError: # pragma: no cover - # reverse compatibility; deprecated in 0.9.4, remove in 2.0 - pass + self.cv_max = dct['cv_max'] self.minvals = dct['minvals'] self.maxvals = dct['maxvals'] self.intersect_with = dct['intersect_with'] diff --git a/openpathsampling/high_level/network.py b/openpathsampling/high_level/network.py index e252348c5..d24eee621 100644 --- a/openpathsampling/high_level/network.py +++ b/openpathsampling/high_level/network.py @@ -203,15 +203,8 @@ def to_dict(self): 'x_sampling_transitions': self._sampling_transitions, 'special_ensembles': self.special_ensembles } - try: - ret_dict['initial_states'] = self.initial_states - ret_dict['final_states'] = self.final_states - except AttributeError: # pragma: no cover - # DEPRECATED: remove for 2.0 - from openpathsampling.deprecations import \ - SAVE_RELOAD_OLD_TPS_NETWORK - SAVE_RELOAD_OLD_TPS_NETWORK.warn() - pass # backward compatibility + ret_dict['initial_states'] = self.initial_states + ret_dict['final_states'] = self.final_states return ret_dict @property @@ -225,18 +218,9 @@ def from_dict(cls, dct): super(GeneralizedTPSNetwork, network).__init__() network._sampling_transitions = dct['x_sampling_transitions'] network.transitions = dct['transitions'] - try: - network.initial_states = dct['initial_states'] - network.final_states = dct['final_states'] - except KeyError: # pragma: no cover - # DEPRECATED: remove for 2.0 - pass # backward compatibility - try: - network.special_ensembles = dct['special_ensembles'] - except KeyError: # pragma: no cover - # DEPRECATED: remove for 2.0 - network.special_ensembles = {None: {}} - # default behavior for backward compatibility + network.initial_states = dct['initial_states'] + network.final_states = dct['final_states'] + network.special_ensembles = dct['special_ensembles'] return network @classmethod @@ -263,7 +247,10 @@ def from_state_pairs(cls, state_pairs, **kwargs): dict_result = { 'x_sampling_transitions': sampling, - 'transitions': transitions + 'transitions': transitions, + 'initial_states': initial_states, + 'final_states': final_states, + 'special_ensembles': {}, } dict_result.update(kwargs) network = cls.from_dict(dict_result) diff --git a/openpathsampling/netcdfplus/version.py b/openpathsampling/netcdfplus/version.py index 952a2d0ae..9094a1ba8 100644 --- a/openpathsampling/netcdfplus/version.py +++ b/openpathsampling/netcdfplus/version.py @@ -1,4 +1,4 @@ -short_version = '1.7.1.dev0' +short_version = '2.0.0.dev0' version = short_version full_version = short_version git_revision = 'alpha' diff --git a/openpathsampling/pathmover.py b/openpathsampling/pathmover.py index 1d7ce7687..a715e724e 100644 --- a/openpathsampling/pathmover.py +++ b/openpathsampling/pathmover.py @@ -17,8 +17,7 @@ from .treelogic import TreeMixin from openpathsampling.deprecations import deprecate, has_deprecations -from openpathsampling.deprecations import (SAMPLE_DETAILS, MOVE_DETAILS, - NEW_SNAPSHOT_KWARG_SELECTOR) +from openpathsampling.deprecations import NEW_SNAPSHOT_KWARG_SELECTOR from future.utils import with_metaclass @@ -2666,28 +2665,3 @@ def __str__(self): else: mystr += str(key) + " = " + str(self.__dict__[key]) + '\n' return mystr - - -@has_deprecations -@deprecate(MOVE_DETAILS) -class MoveDetails(Details): - """Details of the move as applied to a given replica - - Specific move types may have add several other attributes for each - MoveDetails object. For example, shooting moves will also include - information about the shooting point selection, etc. - """ - - def __init__(self, **kwargs): - super(MoveDetails, self).__init__(**kwargs) - - -# leave this for potential backwards compatibility -@has_deprecations -@deprecate(SAMPLE_DETAILS) -class SampleDetails(Details): - """Details of a sample - """ - - def __init__(self, **kwargs): - super(SampleDetails, self).__init__(**kwargs) diff --git a/openpathsampling/sample.py b/openpathsampling/sample.py index 17164f6bf..7dd648a2a 100644 --- a/openpathsampling/sample.py +++ b/openpathsampling/sample.py @@ -9,11 +9,7 @@ from collections import Counter -import sys -if sys.version_info > (3, ): - from collections.abc import Mapping -else: - from collections import Mapping +from collections.abc import Mapping logger = logging.getLogger(__name__) diff --git a/openpathsampling/tests/test_api.py b/openpathsampling/tests/test_api.py index 826257e24..1f1f7f6ff 100644 --- a/openpathsampling/tests/test_api.py +++ b/openpathsampling/tests/test_api.py @@ -6,7 +6,7 @@ # select the file you want to use, or change to the file for your own API # (add more files to ensure that we support 1.0, 1.1, etc.) -API_FILES = ["ops1x0_api.txt"] +API_FILES = [] # we'll establish API 2.0 before release COMBO_ERROR = True @pytest.mark.parametrize('api_file', API_FILES) diff --git a/setup.cfg b/setup.cfg index f79c019be..ae81b8fcc 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = openpathsampling -version = 1.7.1.dev0 +version = 2.0.0.dev0 description = A Python package for path sampling simulations long_description = file: README.md long_description_content_type = text/markdown