Repository: openpathsampling/openpathsampling (language: python)

Issue:
Add FutureWarning for default filesetter external engines
this adds a `FutureWarning` to let users know that in OPS 2.0 the default `FileSetter` will become random string

This was part of the discussion on #1101 
Fix engine props for OpenMM 8.1
In OpenMM 8.1, it seems that we must pass `None` for engine properties. The empty dict is no longer treated the same.


Discussion / hints:

Fix patch:
diff --git a/.github/workflows/autorelease-default-env.sh b/.github/workflows/autorelease-default-env.sh
index 064478c44..4a09393a9 100644
--- a/.github/workflows/autorelease-default-env.sh
+++ b/.github/workflows/autorelease-default-env.sh
@@ -1,4 +1,6 @@
-INSTALL_AUTORELEASE="python -m pip install autorelease==0.3.0"
+# Vendored from Autorelease 0.5.0
+# Update by updating Autorelease and running `autorelease vendor actions`
+INSTALL_AUTORELEASE="python -m pip install autorelease==0.5.0"
 if [ -f autorelease-env.sh ]; then
     source autorelease-env.sh
 fi
diff --git a/.github/workflows/autorelease-deploy.yml b/.github/workflows/autorelease-deploy.yml
index 519223685..73fca082a 100644
--- a/.github/workflows/autorelease-deploy.yml
+++ b/.github/workflows/autorelease-deploy.yml
@@ -1,10 +1,13 @@
-name: Autorelease
+# Vendored from Autorelease 0.5.0
+# Update by updating Autorelease and running `autorelease vendor actions`
+name: "Autorelease Deploy"
 on:
   release:
     types: [published]
 
 jobs:
   deploy_pypi:
+    if: ${{ github.repository == 'openpathsampling/openpathsampling' }}
     runs-on: ubuntu-latest
     name: "Deploy to PyPI"
     steps:
diff --git a/.github/workflows/autorelease-gh-rel.yml b/.github/workflows/autorelease-gh-rel.yml
index 32cd72048..8393d79fc 100644
--- a/.github/workflows/autorelease-gh-rel.yml
+++ b/.github/workflows/autorelease-gh-rel.yml
@@ -1,11 +1,15 @@
-name: Autorelease
+# Vendored from Autorelease 0.5.0
+# Update by updating Autorelease and running `autorelease vendor actions`
+name: "Autorelease Release"
 on:
   push:
     branches:
+      # TODO: this should come from yaml conf
       - stable
 
 jobs:
   release-gh:
+    if: ${{ github.repository == 'openpathsampling/openpathsampling' }}
     runs-on: ubuntu-latest
     name: "Cut release"
     steps:
@@ -31,3 +35,4 @@ jobs:
           autorelease-release --project $PROJECT --version $VERSION --token $AUTORELEASE_TOKEN
         env:
           AUTORELEASE_TOKEN: ${{ secrets.AUTORELEASE_TOKEN }}
+        name: "Cut release"
diff --git a/.github/workflows/autorelease-prep.yml b/.github/workflows/autorelease-prep.yml
index ae965dcf4..d68853a03 100644
--- a/.github/workflows/autorelease-prep.yml
+++ b/.github/workflows/autorelease-prep.yml
@@ -1,9 +1,10 @@
-# File vencdored from Autorelease; specific version information should be in
-# the INSTALL_AUTORELEASE variable in autorelease-default-env.sh
-name: "Autorelease"
+# Vendored from Autorelease 0.5.0
+# Update by updating Autorelease and running `autorelease vendor actions`
+name: "Autorelease testpypi"
 on:
   pull_request:
     branches:
+      # TODO: this should come from yaml conf
       - stable
 
 defaults:
@@ -12,6 +13,7 @@ defaults:
 
 jobs:
   deploy_testpypi:
+    if: ${{ github.repository == 'openpathsampling/openpathsampling' }}
     runs-on: ubuntu-latest
     name: "Deployment test"
     steps:
@@ -47,6 +49,7 @@ jobs:
           repository_url: https://test.pypi.org/legacy/
         name: "Deploy to testpypi"
   test_testpypi:
+    if: ${{ github.repository == 'openpathsampling/openpathsampling' }}
     runs-on: ubuntu-latest
     name: "Test deployed"
     needs: deploy_testpypi
@@ -54,7 +57,7 @@ jobs:
       - uses: actions/checkout@v2
       - uses: actions/setup-python@v2
         with:
-          python-version: "3.x"
+          python-version: "3.11"
       - run: |  # TODO: move this to an action
           source ./.github/workflows/autorelease-default-env.sh
           if [ -f "autorelease-env.sh" ]; then
diff --git a/.github/workflows/check-openmm-rc.yml b/.github/workflows/check-openmm-rc.yml
index 7fddfae61..1d2c0b176 100644
--- a/.github/workflows/check-openmm-rc.yml
+++ b/.github/workflows/check-openmm-rc.yml
@@ -6,23 +6,29 @@ name: "Check for OpenMM RC"
 on:
   schedule:
     - cron: "0 9 * * *"
+  # use this for debugging
+  #pull_request:
+    #branch: master
 
 jobs:
-  check-rc:
+  check_rc:
     if: ${{ github.repository == 'openpathsampling/openpathsampling' }}
     runs-on: ubuntu-latest
     name: "Check for OpenMM RC"
     steps:
       - uses: actions/checkout@v2
-      - uses: dwhswenson/conda-rc-check@main
+      - uses: dwhswenson/conda-rc-check@v1
         id: checkrc
         with:
           channel: conda-forge
           package: openmm
           ndays: 3
           labels: openmm_rc
-      - uses: benc-uk/workflow-dispatch@v1
-        with:
-          workflow: Test OpenMM Release Candidate
-          token: ${{ secrets.DISPATCH_TOKEN }}
-        if: ${{ steps.checkrc.outputs.hasrc == 'True' }}
+    outputs:
+      hasrc: ${{ steps.checkrc.outputs.hasrc }}
+
+  test_rc:
+    needs: check_rc
+    uses: ./.github/workflows/test-openmm-rc.yml
+    secrets: inherit
+    if: ${{ needs.check_rc.outputs.hasrc == 'True' }}
diff --git a/.github/workflows/deploy_docs.yml b/.github/workflows/deploy_docs.yml
index 0e17383dc..518d094a4 100644
--- a/.github/workflows/deploy_docs.yml
+++ b/.github/workflows/deploy_docs.yml
@@ -21,7 +21,8 @@ jobs:
       - uses: conda-incubator/setup-miniconda@v2
         with:
           auto-update-conda: true
-          python-version: 3.9
+          python-version: "3.9"
+          miniforge-variant: Mambaforge
       - name: "Install requirements"
         run: source devtools/conda_install_reqs.sh
       - name: "Install OPS"
diff --git a/CITATIONS.md b/CITATIONS.md
new file mode 100644
index 000000000..9998b3c40
--- /dev/null
+++ b/CITATIONS.md
@@ -0,0 +1,14 @@
+OpenPathSampling was described in a pair of papers published in JCTC:
+
+1. David W.H. Swenson, Jan-Hendrik Prinz, Frank Noé, John D. Chodera, and
+   Peter G. Bolhuis. "OpenPathSampling: A flexible, open framework for path
+   sampling simulations. 1. Basics." J. Chem. Theory Comput. **15**, 813
+   (2019).
+   https://doi.org/10.1021/acs.jctc.8b00626
+2. David W.H. Swenson, Jan-Hendrik Prinz, Frank Noé, John D. Chodera, and
+   Peter G. Bolhuis. "OpenPathSampling: A flexible, open framework for path
+   sampling simulations. 2. Building and Customizing Path Ensembles and
+   Sample Schemes." J. Chem. Theory Comput. **15**, 837 (2019).
+   https://doi.org/10.1021/acs.jctc.8b00627
+
+Both citations: [openpathsampling.bib](https://raw.githubusercontent.com/openpathsampling/openpathsampling/master/docs/openpathsampling.bib) (citation keys ``ops1`` and ``ops2``).
diff --git a/LICENSE b/LICENSE
index 5d7c512a6..598d6aa5a 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
 MIT License
 
-Copyright (c) 2014-2019 the contributors to OpenPathSampling
+Copyright (c) 2014-2022 the contributors to OpenPathSampling
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
diff --git a/devtools/conda_install_reqs.sh b/devtools/conda_install_reqs.sh
index 637822f91..ffbd57286 100644
--- a/devtools/conda_install_reqs.sh
+++ b/devtools/conda_install_reqs.sh
@@ -9,14 +9,23 @@
 
 DEVTOOLS_DIR=`dirname "${BASH_SOURCE[0]}"`
 
-if [ ! -z "$OPS_ENV" ]
-then
-    conda create -q -y --name $OPS_ENV conda future pyyaml python=$CONDA_PY
-    source activate $OPS_ENV
+# use mamba if we can, otherwise use conda
+if ! command -v mamba &> /dev/null; then
+    EXE="conda"
 else
-    conda install -y -q future pyyaml  # ensure that these are available
+    EXE="mamba"
 fi
 
+# If OPS_ENV is defined, we're creating a new environment. Otherwise install
+# into current env.
+if [ ! -z "$OPS_ENV" ]; then
+    INSTALL="create --name $OPS_ENV"
+else
+    INSTALL="install"
+fi
+
+INSTALL_CMD="$EXE $INSTALL -y -q -c conda-forge -c omnia --override-channels"
+
 # for some reason, these approaches to pinning don't always work (but conda
 # always obeys if you explicitly request a pinned version)
 #conda config --env --add pinned_packages python=$CONDA_PY
@@ -30,6 +39,10 @@ INTEGRATIONS=`cat ${DEVTOOLS_DIR}/tested_integrations.txt | tr "\n" " "`
 EXPERIMENTAL=`cat ${DEVTOOLS_DIR}/experimental_reqs.txt | tr "\n" " "`
 PY_INSTALL="python=$CONDA_PY"
 
+# PIP_INSTALLS is used for debugging installation problems -- override the
+# default $REQUIREMENTS, etc. and move some installs to $PIP_INSTALLS
+PIP_INSTALLS=""
+
 echo "PY_INSTALL=$PY_INSTALL"
 echo "REQUIREMENTS=$REQUIREMENTS"
 echo "INTEGRATIONS=$INTEGRATIONS"
@@ -42,5 +55,19 @@ echo "TESTING=$TESTING"
 # situations may come up in the future)
 ALL_PACKAGES="$WORKAROUNDS $REQUIREMENTS $INTEGRATIONS $EXPERIMENTAL $TESTING"
 
-echo "conda install -y -q -c conda-forge -c omnia $PY_INSTALL $ALL_PACKAGES"
-conda install -y -q -c conda-forge -c omnia $PY_INSTALL $ALL_PACKAGES
+echo "$INSTALL_CMD $PY_INSTALL $ALL_PACKAGES"
+if [ -z "$DRY" ]; then
+    $INSTALL_CMD $PY_INSTALL $ALL_PACKAGES
+fi
+
+if [ -n "$OPS_ENV" ] && [ -z "$DRY" ]; then
+    conda activate $OPS_ENV
+fi
+
+# occasional workaround; usually a do-nothing
+if [ -n "$PIP_INSTALLS" ]; then
+    echo "python -m pip install $PIP_INSTALLS"
+    if [ -z "$DRY" ]; then
+        python -m pip install $PIP_INSTALLS
+    fi
+fi
diff --git a/devtools/experimental_reqs.txt b/devtools/experimental_reqs.txt
index bfd6cc288..b1f18ea68 100644
--- a/devtools/experimental_reqs.txt
+++ b/devtools/experimental_reqs.txt
@@ -1,2 +1,1 @@
-sqlalchemy!=1.4.0
-dill
+sqlalchemy>=1.4.1
diff --git a/docs/conf.py b/docs/conf.py
index 17493c7df..1db2ab3d1 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -107,7 +107,7 @@
 
 # General information about the project.
 project = u'OpenPathSampling'
-copyright = u'2014-2021, David W.H. Swenson, Jan-Hendrik Prinz, John Chodera, Peter Bolhuis'
+copyright = u'2014-2022, David W.H. Swenson, Jan-Hendrik Prinz, John Chodera, Peter Bolhuis'
 
 # The version info for the project you're documenting, acts as replacement for
 # |version| and |release|, also used in various other places throughout the
@@ -218,13 +218,20 @@
 html_static_path = ['_static']
 
 # from http://rackerlabs.github.io/docs-rackspace/tools/rtd-tables.html
-html_context = {
-    'css_files': [
-        '_static/theme_overrides.css',  # override wide tables in RTD theme
-        '_static/ipynb.css',  # tweak output of
-        ],
-     }
-
+# BEFORE sphinx_rtd_theme 1.0
+# html_context = {
+#     'css_files': [
+#         '_static/theme_overrides.css',  # override wide tables in RTD theme
+#         '_static/ipynb.css',  # tweak output of
+#         ],
+#      }
+
+# after sphinx_rtd_theme 1.0; see
+# https://github.com/readthedocs/sphinx_rtd_theme/issues/1240
+html_css_files = [
+    'theme_overrides.css',  # override wide tables in RTD theme
+    'ipynb.css',  # tweak output of notebooks
+]
 
 # Add any extra paths that contain custom files (such as robots.txt or
 # .htaccess) here, relative to this directory. These files are copied
diff --git a/docs/copyfiles/copyfiles.py b/docs/copyfiles/copyfiles.py
index 7e6cdb5e9..60e0be5f3 100644
--- a/docs/copyfiles/copyfiles.py
+++ b/docs/copyfiles/copyfiles.py
@@ -33,9 +33,20 @@ def copy_gitlab(source, dest, files):
         print(url)
         urllib.request.urlretrieve(url, dest / f)
 
+def copy_github(source, dest, files):
+    domain = "http://raw.githubusercontent.com/"
+    if not source.endswith("/"):
+        source = source + "/"
+    dest = pathlib.Path(dest)
+    for f in files:
+        url = domain + source + f
+        print(url)
+        urllib.request.urlretrieve(url, dest / f)
+
 DISPATCH = {
     'local': copy_local,
-    'gitlab': copy_gitlab
+    'gitlab': copy_gitlab,
+    'github': copy_github,
 }
 
 def make_copies(fileset):
diff --git a/docs/copyfiles/notebooks.yml b/docs/copyfiles/notebooks.yml
index 0c8ed1a41..30e6b917f 100644
--- a/docs/copyfiles/notebooks.yml
+++ b/docs/copyfiles/notebooks.yml
@@ -11,13 +11,13 @@ copyfiles:
       - toy_model_mstis/toy_mstis_1_setup.ipynb
       - toy_model_mstis/toy_mstis_2_run.ipynb
       - toy_model_mstis/toy_mstis_3_analysis.ipynb
-  - gitlab: http://gitlab.e-cam2020.eu/dwhswenson/ops_tutorial
-    target: examples/ops_tutorial
-    mkdirs:
-      - examples/ops_tutorial
-    files:
-      - 1_tps_sampling_tutorial.ipynb
-      - 2_tps_analysis_tutorial.ipynb
-      - 3_committor_analysis_tutorial.ipynb
-      - 4_mstis_sampling_tutorial.ipynb
-      - 5_advanced_customize_shooting.ipynb
+  #- github: openpathsampling/ops_tutorial/main
+    #target: examples/ops_tutorial
+    #mkdirs:
+      #- examples/ops_tutorial
+    #files:
+      #- 1_tps_sampling_tutorial.ipynb
+      #- 2_tps_analysis_tutorial.ipynb
+      #- 3_committor_analysis_tutorial.ipynb
+      #- 4_mstis_sampling_tutorial.ipynb
+      #- 5_advanced_customize_shooting.ipynb
diff --git a/docs/install.rst b/docs/install.rst
index fc395b7d1..856e2e47c 100644
--- a/docs/install.rst
+++ b/docs/install.rst
@@ -1,103 +1,134 @@
-.. _install:
-
-************
+============
 Installation
-************
+============
+
+This page has instructions on installing OPS, installing software packages
+that integrate with OPS, and testing that your OPS installation is working.
 
-OpenPathSampling currently only works on Mac and Linux. It is tested against
-Python 2.7, 3.6, and 3.7, although there may be some corners of the code
-that aren't Python 3-compatible yet.
+OpenPathSampling currently only works on POSIX systems (macOS and
+UNIX/Linux). It is tested against Python 2.7, 3.7, 3.8, and 3.9.
 
-.. note:: As of OpenPathSampling 1.1, OpenMM will no longer be automatically
-          installed when you install OPS. However, the OpenMM engine will be
-          immediately available if you install OpenMM yourself. See the
-          `OpenMM installation instructions
-          <http://docs.openmm.org/latest/userguide/application.html#installing-openmm>`_
-          for a detailed guide, but ``conda install -c conda-forge -c omnia
-          openmm`` will work for most people. (COMING SOON: details on OPS
-          integrations with other tools.)
+Installing OpenPathSampling
+===========================
 
 .. _install-with-conda:
 
-Standard Install with Conda
-===========================
+Installation with ``conda`` (recommended)
+-----------------------------------------
 
-We recommend using ``conda`` to install OpenPathSampling.  `conda
-<http://www.continuum.io/downloads>`_ is a Python package manager built for
-scientific Python, and which handles binary dependencies seamlessly.  If you
-don't want the full ``conda`` installation, the ``miniconda`` package
-provides much of the convenience of ``conda`` with a smaller footprint.
+OpenPathSampling can be installed with ``conda``, ``pip``, or by using
+``setuptools``. We recommend using ``conda`` because it provides easy
+installation of other tools, including molecular dynamics engines such as
+OpenMM and GROMACS, which most users will want to install when using OPS.
+Conda can be installed either with the `full Anaconda distribution
+<https://www.anaconda.com/products/individual>`_, or with
+the `smaller-footprint miniconda
+<https://docs.conda.io/en/latest/miniconda.html>`_. 
 
-OpenPathSampling is part of the ``conda-forge`` channel in ``conda``.  To
-install the most recent release of OpenPathSampling with conda, use the
-following command ::
+OpenPathSampling can be installed from the ``conda-forge`` channel. To
+install the most recent release, use the command ::
 
   $ conda install -c conda-forge openpathsampling
 
 With that, you should be ready to use OPS!
 
-.. _developer-install-conda:
+Installation with ``pip`` or ``setuptools``
+-------------------------------------------
+
+OPS can also be installed with ``pip`` via the command ``python -m pip install
+openpathsampling``, or the source can be downloaded and installed using
+``setuptools`` and the command ``python setup.py install`` (run from the
+root directory of the source repository). We only recommend these
+installation mechanisms for users who are familiar with these tools.
+
+Developer install
+-----------------
+
+OpenPathSampling can be installed as an "editable" (developer) install
+through standard mechanisms (``python -m pip install -e .`` or ``python setup.py
+develop``). 
+
+Additionally, we provide a script that downloads the OPS source repository,
+uses ``conda`` to install OPS's requirements (including several optional
+integrations) and the installs OPS. Details on using this script are in
+:ref:`quick-dev-install`.
+
 
-Developer Install with Conda
-============================
+Integration with other packages
+===============================
 
-To install a developer version of OPS (using ``conda``), change to a
-directory where you want to OPS code (i.e., if you want the OPS git
-repository at ``directory/openpathsampling/``, change to ``directory/``.)
-Then download the ``conda_ops_dev_install.sh`` and run it ::
+When you install OpenPathSampling, you only get the core of OPS and the
+internal toy dynamics engine. In practice, you'll probably want a more
+powerful MD engine like OpenMM or GROMACS.
 
-  $ curl -OLk https://raw.githubusercontent.com/openpathsampling/openpathsampling/master/devtools/conda_ops_dev_install.sh
-  $ bash conda_ops_dev_install.sh
+To add support for optional packages, all you need to do is install them.
+For example, simply by having OpenMM or GROMACS installed on your system,
+you will automatically have support for those engines within OPS. 
 
-At this point, any changes to the code in that download of the OPS directory
-will be live in your Python installation. You can use experimental code from
-other forks by `adding the fork as a remote
-<https://help.github.com/articles/adding-a-remote/>`_ and checking out a
-branch.  You can combine changes from multiple users by merging them into a
-branch in your local version of the repository.
+The tables below list several packages that provide OPS with extra
+functionality when installed, and include links to their ``conda`` packages.
 
-Additional functionality of the ``conda_ops_dev_install.sh`` script is
-described in :ref:`quick-dev-install`.
+Molecular dynamics engines
+--------------------------
 
-.. _manual-install:
++------------------------+-------------------------------------------------+
+| | **OpenMM**           |                                                 |
+| | |openmm-conda|_      |                                                 |
++------------------------+-------------------------------------------------+
+| | **OpenMMTools**      | OPS has special support for integrators from    |
+| | |openmmtools-conda|_ | OpenMMTools (and we strongly recommend using    |
+|                        | one of the reversible integrators provided      |
+|                        | there, such as VVVR).                           |
++------------------------+-------------------------------------------------+
+| | **GROMACS**          | OPS will work with any installed GROMACS; it    |
+| | |gromacs-conda|_     | does not need to come from ``conda``.           |
++------------------------+-------------------------------------------------+
 
-Manual Installation
-===================
+.. |openmm-conda| image:: https://img.shields.io/conda/vn/conda-forge/openmm
+.. |openmmtools-conda| image:: https://img.shields.io/conda/vn/conda-forge/openmmtools
+.. |gromacs-conda| image:: https://img.shields.io/conda/vn/bioconda/gromacs
 
-If you don't want to use ``conda``, you will have to manually obtain the
-dependencies, which you can see listed under ``install_requires`` in
-``setup.py``. Then you can install from our GitHub repository.
+.. _openmm-conda: https://anaconda.org/conda-forge/openmm
+.. _openmmtools-conda: https://anaconda.org/conda-forge/openmmtools
+.. _gromacs-conda: https://anaconda.org/bioconda/gromacs
 
-Clone the source code repository from GitHub::
+Collective variables
+--------------------
 
-  $ git clone https://github.com/openpathsampling/openpathsampling.git
+In addition to the optional packages listed below, OPS ships with MDTraj
+built-in.
 
-Then, in the directory containing the source code, you can install it with ::
++------------------------+-------------------------------------------------+
+| | **PyEMMA**           |                                                 |
+| | |pyemma-conda|_      |                                                 |
++------------------------+-------------------------------------------------+
+| | **py-PLUMED**        |                                                 |
+| | |plumed-conda|_      |                                                 |
++------------------------+-------------------------------------------------+
 
-  $ python setup.py install
+.. |pyemma-conda| image:: https://img.shields.io/conda/vn/conda-forge/pyemma
+.. |plumed-conda| image:: https://img.shields.io/conda/vn/conda-forge/py-plumed
 
-Or, for a developer install ::
+.. _plumed-conda: https://anaconda.org/conda-forge/py-plumed
+.. _pyemma-conda: https://anaconda.org/conda-forge/pyemma
 
-  $ python setup.py develop
 
 .. _run-tests:
 
-Testing Your Installation
+Testing your installation
 =========================
 
-Running the tests is a great way to verify that everything is working. The
-test suite uses `pytest <http://pytest.org>`_ and, for legacy reasons, also
-requires the `nose <https://nose.readthedocs.org/en/latest/>`_ package. You can pick these up via ``conda`` if you don't already have them. ::
+OpenPathSampling includes a thorough test suite, and running the test suite
+is a good start to troubleshooting any installation problems. The OPS test
+suite requires the packages ``pytest`` and (for legacy reasons) ``nose``.
+These can be  installed with either ``conda`` or ``pip``. For example: ::
 
   $ conda install pytest nose
 
-From the source directory ``openpathsampling/tests``, you can run the tests
-by typing ``py.test`` on the command line. The test suite includes over 900
-individual tests, and runs in around 2-3 minutes.
+Once those are installed, you can run the test suite on your installation
+with the command: ::
 
-Beta testing experimental updates
-=================================
+  $ py.test --pyargs openpathsampling.tests
 
-In rare cases, you may want to test code that hasn't been merged into the
-core of OPS yet. Instructions to install in this case are in the docs for
-:ref:`install-devs`.
+The test suite includes over 1100 individual tests, and runs in around 2-3
+minutes. All tests should either pass or skip.
diff --git a/docs/requirements.txt b/docs/requirements.txt
index 4655d03ac..e835c14a1 100644
--- a/docs/requirements.txt
+++ b/docs/requirements.txt
@@ -1,11 +1,10 @@
 matplotlib
 sphinx
 jupyter
-nbconvert<6  # TODO: remove
+nbconvert>=5
 nbsphinx
 jinja2
 netcdf4
-runipy  # TODO: remove
 openmm
 pytables
 scikit-learn
diff --git a/docs/sphinxext/notebook_sphinxext.py b/docs/sphinxext/notebook_sphinxext.py
index 9202a5b9f..b5cd51140 100644
--- a/docs/sphinxext/notebook_sphinxext.py
+++ b/docs/sphinxext/notebook_sphinxext.py
@@ -15,8 +15,8 @@
 class NotebookDirective(Directive):
     """Insert an evaluated notebook into a document
 
-    This uses runipy and nbconvert to transform a path to an unevaluated notebook
-    into html suitable for embedding in a Sphinx document.
+    This uses nbconvert to transform a path to an unevaluated notebook into
+    html suitable for embedding in a Sphinx document.
     """
     required_arguments = 1
     optional_arguments = 1
@@ -72,7 +72,7 @@ def run(self): # check if there are spaces in the notebook name
 
         skip_exceptions = 'skip_exceptions' in self.options
 
-        print('[NotebookDirective] Comverting %s' % nb_filename)
+        print('[NotebookDirective] Converting %s' % nb_filename)
 #        start = time.time()
 #        evaluated_text = evaluate_notebook(nb_abs_path, dest_path_eval,
 #                                           skip_exceptions=skip_exceptions)
@@ -125,7 +125,7 @@ def nb_to_python(nb_path):
 
 def nb_to_html(nb_path):
     """convert notebook to html"""
-    exporter = HTMLExporter(template_file='full')
+    exporter = HTMLExporter(template_name='full')
     output, resources = exporter.from_filename(nb_path)
     header = output.split('<head>', 1)[1].split('</head>',1)[0]
     body = output.split('<body>', 1)[1].split('</body>',1)[0]
diff --git a/examples/misc/reference-sshooting.data b/examples/misc/reference-sshooting.data
new file mode 100644
index 000000000..d4be9f633
--- /dev/null
+++ b/examples/misc/reference-sshooting.data
@@ -0,0 +1,500 @@
+0.000000 0.000000
+0.001000 0.000000
+0.002000 0.000000
+0.003000 0.000000
+0.004000 0.000000
+0.005000 0.000000
+0.006000 0.000000
+0.007000 0.000000
+0.008000 0.000000
+0.009000 0.000000
+0.010000 0.000000
+0.011000 0.000000
+0.012000 0.000000
+0.013000 0.000000
+0.014000 0.000000
+0.015000 0.000000
+0.016000 0.000000
+0.017000 0.000000
+0.018000 0.000000
+0.019000 0.000000
+0.020000 0.000001
+0.021000 0.000002
+0.022000 0.000002
+0.023000 0.000003
+0.024000 0.000004
+0.025000 0.000005
+0.026000 0.000007
+0.027000 0.000009
+0.028000 0.000012
+0.029000 0.000015
+0.030000 0.000020
+0.031000 0.000026
+0.032000 0.000035
+0.033000 0.000045
+0.034000 0.000055
+0.035000 0.000068
+0.036000 0.000081
+0.037000 0.000097
+0.038000 0.000115
+0.039000 0.000133
+0.040000 0.000155
+0.041000 0.000179
+0.042000 0.000203
+0.043000 0.000230
+0.044000 0.000260
+0.045000 0.000289
+0.046000 0.000321
+0.047000 0.000359
+0.048000 0.000397
+0.049000 0.000437
+0.050000 0.000482
+0.051000 0.000529
+0.052000 0.000578
+0.053000 0.000627
+0.054000 0.000683
+0.055000 0.000738
+0.056000 0.000796
+0.057000 0.000858
+0.058000 0.000926
+0.059000 0.000996
+0.060000 0.001068
+0.061000 0.001142
+0.062000 0.001220
+0.063000 0.001298
+0.064000 0.001382
+0.065000 0.001465
+0.066000 0.001549
+0.067000 0.001638
+0.068000 0.001733
+0.069000 0.001823
+0.070000 0.001921
+0.071000 0.002017
+0.072000 0.002115
+0.073000 0.002217
+0.074000 0.002325
+0.075000 0.002441
+0.076000 0.002556
+0.077000 0.002670
+0.078000 0.002792
+0.079000 0.002913
+0.080000 0.003037
+0.081000 0.003166
+0.082000 0.003299
+0.083000 0.003432
+0.084000 0.003566
+0.085000 0.003705
+0.086000 0.003847
+0.087000 0.003991
+0.088000 0.004132
+0.089000 0.004283
+0.090000 0.004428
+0.091000 0.004579
+0.092000 0.004730
+0.093000 0.004886
+0.094000 0.005048
+0.095000 0.005206
+0.096000 0.005366
+0.097000 0.005534
+0.098000 0.005701
+0.099000 0.005871
+0.100000 0.006042
+0.101000 0.006214
+0.102000 0.006389
+0.103000 0.006568
+0.104000 0.006743
+0.105000 0.006922
+0.106000 0.007103
+0.107000 0.007285
+0.108000 0.007466
+0.109000 0.007649
+0.110000 0.007836
+0.111000 0.008026
+0.112000 0.008215
+0.113000 0.008410
+0.114000 0.008604
+0.115000 0.008799
+0.116000 0.008997
+0.117000 0.009195
+0.118000 0.009400
+0.119000 0.009606
+0.120000 0.009810
+0.121000 0.010019
+0.122000 0.010229
+0.123000 0.010441
+0.124000 0.010656
+0.125000 0.010872
+0.126000 0.011092
+0.127000 0.011313
+0.128000 0.011534
+0.129000 0.011756
+0.130000 0.011972
+0.131000 0.012196
+0.132000 0.012421
+0.133000 0.012645
+0.134000 0.012871
+0.135000 0.013095
+0.136000 0.013325
+0.137000 0.013554
+0.138000 0.013784
+0.139000 0.014014
+0.140000 0.014251
+0.141000 0.014486
+0.142000 0.014724
+0.143000 0.014965
+0.144000 0.015207
+0.145000 0.015448
+0.146000 0.015693
+0.147000 0.015940
+0.148000 0.016187
+0.149000 0.016429
+0.150000 0.016672
+0.151000 0.016921
+0.152000 0.017170
+0.153000 0.017420
+0.154000 0.017675
+0.155000 0.017930
+0.156000 0.018187
+0.157000 0.018447
+0.158000 0.018706
+0.159000 0.018969
+0.160000 0.019234
+0.161000 0.019497
+0.162000 0.019761
+0.163000 0.020023
+0.164000 0.020289
+0.165000 0.020556
+0.166000 0.020819
+0.167000 0.021087
+0.168000 0.021354
+0.169000 0.021621
+0.170000 0.021892
+0.171000 0.022164
+0.172000 0.022433
+0.173000 0.022706
+0.174000 0.022979
+0.175000 0.023256
+0.176000 0.023532
+0.177000 0.023805
+0.178000 0.024084
+0.179000 0.024359
+0.180000 0.024636
+0.181000 0.024914
+0.182000 0.025195
+0.183000 0.025474
+0.184000 0.025754
+0.185000 0.026036
+0.186000 0.026317
+0.187000 0.026599
+0.188000 0.026883
+0.189000 0.027167
+0.190000 0.027451
+0.191000 0.027738
+0.192000 0.028027
+0.193000 0.028311
+0.194000 0.028600
+0.195000 0.028887
+0.196000 0.029178
+0.197000 0.029468
+0.198000 0.029760
+0.199000 0.030056
+0.200000 0.030350
+0.201000 0.030643
+0.202000 0.030936
+0.203000 0.031231
+0.204000 0.031528
+0.205000 0.031825
+0.206000 0.032119
+0.207000 0.032409
+0.208000 0.032702
+0.209000 0.033000
+0.210000 0.033293
+0.211000 0.033589
+0.212000 0.033886
+0.213000 0.034183
+0.214000 0.034482
+0.215000 0.034780
+0.216000 0.035079
+0.217000 0.035378
+0.218000 0.035679
+0.219000 0.035979
+0.220000 0.036279
+0.221000 0.036579
+0.222000 0.036880
+0.223000 0.037179
+0.224000 0.037478
+0.225000 0.037778
+0.226000 0.038079
+0.227000 0.038379
+0.228000 0.038680
+0.229000 0.038980
+0.230000 0.039282
+0.231000 0.039587
+0.232000 0.039890
+0.233000 0.040195
+0.234000 0.040501
+0.235000 0.040803
+0.236000 0.041108
+0.237000 0.041413
+0.238000 0.041717
+0.239000 0.042023
+0.240000 0.042327
+0.241000 0.042635
+0.242000 0.042943
+0.243000 0.043250
+0.244000 0.043556
+0.245000 0.043862
+0.246000 0.044169
+0.247000 0.044476
+0.248000 0.044785
+0.249000 0.045094
+0.250000 0.045403
+0.251000 0.045712
+0.252000 0.046022
+0.253000 0.046330
+0.254000 0.046639
+0.255000 0.046951
+0.256000 0.047261
+0.257000 0.047572
+0.258000 0.047885
+0.259000 0.048198
+0.260000 0.048512
+0.261000 0.048828
+0.262000 0.049141
+0.263000 0.049458
+0.264000 0.049773
+0.265000 0.050088
+0.266000 0.050400
+0.267000 0.050715
+0.268000 0.051030
+0.269000 0.051347
+0.270000 0.051664
+0.271000 0.051979
+0.272000 0.052294
+0.273000 0.052612
+0.274000 0.052929
+0.275000 0.053246
+0.276000 0.053562
+0.277000 0.053882
+0.278000 0.054201
+0.279000 0.054520
+0.280000 0.054840
+0.281000 0.055158
+0.282000 0.055476
+0.283000 0.055797
+0.284000 0.056117
+0.285000 0.056437
+0.286000 0.056757
+0.287000 0.057078
+0.288000 0.057399
+0.289000 0.057722
+0.290000 0.058045
+0.291000 0.058368
+0.292000 0.058687
+0.293000 0.059006
+0.294000 0.059328
+0.295000 0.059649
+0.296000 0.059970
+0.297000 0.060288
+0.298000 0.060607
+0.299000 0.060931
+0.300000 0.061253
+0.301000 0.061572
+0.302000 0.061892
+0.303000 0.062214
+0.304000 0.062534
+0.305000 0.062854
+0.306000 0.063177
+0.307000 0.063501
+0.308000 0.063823
+0.309000 0.064147
+0.310000 0.064469
+0.311000 0.064794
+0.312000 0.065117
+0.313000 0.065442
+0.314000 0.065767
+0.315000 0.066089
+0.316000 0.066414
+0.317000 0.066738
+0.318000 0.067060
+0.319000 0.067382
+0.320000 0.067705
+0.321000 0.068027
+0.322000 0.068352
+0.323000 0.068675
+0.324000 0.068999
+0.325000 0.069326
+0.326000 0.069650
+0.327000 0.069973
+0.328000 0.070295
+0.329000 0.070620
+0.330000 0.070945
+0.331000 0.071268
+0.332000 0.071591
+0.333000 0.071915
+0.334000 0.072237
+0.335000 0.072561
+0.336000 0.072885
+0.337000 0.073208
+0.338000 0.073532
+0.339000 0.073852
+0.340000 0.074179
+0.341000 0.074505
+0.342000 0.074829
+0.343000 0.075153
+0.344000 0.075477
+0.345000 0.075801
+0.346000 0.076127
+0.347000 0.076454
+0.348000 0.076783
+0.349000 0.077107
+0.350000 0.077435
+0.351000 0.077760
+0.352000 0.078085
+0.353000 0.078410
+0.354000 0.078734
+0.355000 0.079060
+0.356000 0.079385
+0.357000 0.079710
+0.358000 0.080036
+0.359000 0.080362
+0.360000 0.080688
+0.361000 0.081012
+0.362000 0.081338
+0.363000 0.081661
+0.364000 0.081988
+0.365000 0.082314
+0.366000 0.082640
+0.367000 0.082966
+0.368000 0.083292
+0.369000 0.083619
+0.370000 0.083948
+0.371000 0.084275
+0.372000 0.084601
+0.373000 0.084927
+0.374000 0.085255
+0.375000 0.085581
+0.376000 0.085910
+0.377000 0.086237
+0.378000 0.086565
+0.379000 0.086894
+0.380000 0.087221
+0.381000 0.087553
+0.382000 0.087881
+0.383000 0.088211
+0.384000 0.088542
+0.385000 0.088873
+0.386000 0.089202
+0.387000 0.089529
+0.388000 0.089858
+0.389000 0.090188
+0.390000 0.090514
+0.391000 0.090841
+0.392000 0.091171
+0.393000 0.091499
+0.394000 0.091826
+0.395000 0.092154
+0.396000 0.092481
+0.397000 0.092809
+0.398000 0.093138
+0.399000 0.093466
+0.400000 0.093793
+0.401000 0.094120
+0.402000 0.094447
+0.403000 0.094776
+0.404000 0.095102
+0.405000 0.095428
+0.406000 0.095756
+0.407000 0.096084
+0.408000 0.096411
+0.409000 0.096739
+0.410000 0.097066
+0.411000 0.097392
+0.412000 0.097718
+0.413000 0.098045
+0.414000 0.098371
+0.415000 0.098701
+0.416000 0.099028
+0.417000 0.099356
+0.418000 0.099685
+0.419000 0.100014
+0.420000 0.100343
+0.421000 0.100671
+0.422000 0.101001
+0.423000 0.101331
+0.424000 0.101657
+0.425000 0.101984
+0.426000 0.102310
+0.427000 0.102638
+0.428000 0.102964
+0.429000 0.103290
+0.430000 0.103618
+0.431000 0.103944
+0.432000 0.104270
+0.433000 0.104598
+0.434000 0.104926
+0.435000 0.105252
+0.436000 0.105579
+0.437000 0.105904
+0.438000 0.106229
+0.439000 0.106554
+0.440000 0.106879
+0.441000 0.107204
+0.442000 0.107531
+0.443000 0.107856
+0.444000 0.108181
+0.445000 0.108507
+0.446000 0.108833
+0.447000 0.109157
+0.448000 0.109482
+0.449000 0.109806
+0.450000 0.110129
+0.451000 0.110453
+0.452000 0.110777
+0.453000 0.111101
+0.454000 0.111427
+0.455000 0.111753
+0.456000 0.112079
+0.457000 0.112405
+0.458000 0.112732
+0.459000 0.113056
+0.460000 0.113382
+0.461000 0.113709
+0.462000 0.114035
+0.463000 0.114363
+0.464000 0.114691
+0.465000 0.115020
+0.466000 0.115346
+0.467000 0.115672
+0.468000 0.115998
+0.469000 0.116323
+0.470000 0.116650
+0.471000 0.116976
+0.472000 0.117303
+0.473000 0.117631
+0.474000 0.117955
+0.475000 0.118281
+0.476000 0.118605
+0.477000 0.118930
+0.478000 0.119256
+0.479000 0.119581
+0.480000 0.119906
+0.481000 0.120232
+0.482000 0.120558
+0.483000 0.120880
+0.484000 0.121206
+0.485000 0.121531
+0.486000 0.121857
+0.487000 0.122181
+0.488000 0.122505
+0.489000 0.122831
+0.490000 0.123156
+0.491000 0.123481
+0.492000 0.123806
+0.493000 0.124132
+0.494000 0.124458
+0.495000 0.124781
+0.496000 0.125104
+0.497000 0.125430
+0.498000 0.125758
+0.499000 0.126085
diff --git a/examples/misc/sshooting-example.ipynb b/examples/misc/sshooting-example.ipynb
new file mode 100644
index 000000000..63bb36aba
--- /dev/null
+++ b/examples/misc/sshooting-example.ipynb
@@ -0,0 +1,813 @@
+{
+ "cells": [
+  {
+   "cell_type": "code",
+   "execution_count": 1,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "%matplotlib inline\n",
+    "import matplotlib.pyplot as plt\n",
+    "import openpathsampling as paths\n",
+    "import openpathsampling.engines.toy as toys\n",
+    "import numpy as np\n",
+    "from openpathsampling import SShootingSimulation\n",
+    "from openpathsampling import SShootingAnalysis\n",
+    "from openpathsampling.engines.toy import DoubleWell\n",
+    "from openpathsampling.engines.toy import OverdampedLangevinIntegrator"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "# S-shooting\n",
+    "\n",
+    "This example demonstrates how an S-shooting simulation is carried out and analyzed in OPS. The theoretical background for S-shooting is provided in the following publication:\n",
+    "\n",
+    "Menzl, G., Singraber, A. & Dellago, C. S-shooting: a Bennett–Chandler-like method for the computation of rate constants from committor trajectories. Faraday Discuss. __195__, 345–364 (2017), https://doi.org/10.1039/C6FD00124F\n",
+    "\n",
+    "Here, we reproduce with OPS the example of a Brownian walker in a double-well potential given in the above publication. Please refer to the paper for a detailed description of nomenclature and simulation setup.\n",
+    "\n",
+    "__Note__: For satisfactory results with good statistics a high number of initial snapshots should be used (~1000+). Unfortunately, with the current performance this will then NOT be a _quick_ example. In order to keep the execution time within a few minutes the number of snapshots was set to a low number (see `n_snapshots` parameter below) but the results will probably not be matching with the reference publication."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## Trajectory sampling\n",
+    "\n",
+    "First, we set up our simple test system, a single particle in a 2-dimensional potential energy surface consisting of two gaussian wells and outer walls. The particle is propagated with an overdamped Langevin integrator."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 2,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Temperature       =  0.25\n",
+      "L                 =  500\n",
+      "n_steps_per_frame =  1\n",
+      "trajectory_length =  500\n"
+     ]
+    }
+   ],
+   "source": [
+    "# Set simulation details.\n",
+    "D              = 1.0     # Diffusion constant.\n",
+    "beta           = 4.0     # Beta.\n",
+    "\n",
+    "dt             = 0.001   # Timestep delta t [time units]\n",
+    "tau            = 0.5     # One-way trajectory length tau [time units] (= maximum correlation function time).\n",
+    "n_corr_points  = 501     # Number of correlation function points (t = 0 counts also).\n",
+    "\n",
+    "A_max          = -0.4    # Upper boundary for region A.\n",
+    "S_min          = -0.1    # Lower boundary for region S.\n",
+    "S_max          = 0.1     # Upper boundary for region S.\n",
+    "B_min          = 0.4     # Lower boundary for region B.\n",
+    "\n",
+    "n_snapshots    = 100     # Number of shooting points (= initial snapshots).\n",
+    "n_per_snapshot = 1       # Number of trajectories generated per shooting point.\n",
+    "\n",
+    "temperature = 1 / beta\n",
+    "L = int(tau / dt)\n",
+    "n_steps_per_frame = int(L / (n_corr_points - 1))\n",
+    "trajectory_length = n_corr_points - 1\n",
+    "\n",
+    "# Sanity checks\n",
+    "if L * dt != tau:\n",
+    "    raise ValueError(\"Total simulation length is not a multiple of time step length:\\n\"\n",
+    "                     \"L * dt = {0:d} * {1:f} = {2:f} != {3:f} = tau.\".format(L, dt, L * dt, tau))\n",
+    "if n_steps_per_frame * (n_corr_points - 1) != L:\n",
+    "    raise ValueError(\"Trajectory length is not a multiple of steps per frame and\"\n",
+    "                     \" number of requested correlation function points:\\n\"\n",
+    "                     \"n_steps_per_frame * (n_corr_points - 1) = {0:d} * {1:d} = {2:d} != \"\n",
+    "                     \"{3:d} = L.\".format(n_steps_per_frame, (n_corr_points - 1),\n",
+    "                                         n_steps_per_frame * (n_corr_points - 1), L))\n",
+    "    \n",
+    "print(\"Temperature       = \", temperature)\n",
+    "print(\"L                 = \", L)\n",
+    "print(\"n_steps_per_frame = \", n_steps_per_frame)\n",
+    "print(\"trajectory_length = \", trajectory_length)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 3,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "pes = DoubleWell([1.0], [1.0])\n",
+    "\n",
+    "topology = toys.Topology(\n",
+    "    n_atoms = 1,\n",
+    "    n_spatial = 1,\n",
+    "    masses = [1.0],\n",
+    "    pes = pes\n",
+    ")\n",
+    "\n",
+    "# D = kB * T / gamma      \n",
+    "#gamma = temperature / D                \n",
+    "#integ = toys.LangevinBAOABIntegrator(dt, temperature, gamma)\n",
+    "integ = OverdampedLangevinIntegrator(dt, temperature, D)\n",
+    "options = {\n",
+    "    'integ' : integ,\n",
+    "    'n_frames_max' : 5000,\n",
+    "    'n_steps_per_frame' : n_steps_per_frame\n",
+    "}\n",
+    "\n",
+    "toy_eng = toys.Engine(\n",
+    "    options = options,\n",
+    "    topology = topology\n",
+    ")\n",
+    "toy_eng.initialized = True\n",
+    "paths.PathMover.engine = toy_eng"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Next the initial snapshots are randomly chosen within the region $S$ according to their Boltzmann weight $e^{-\\beta E(x_0)}$ (rejection sampling). Usually these configurations are available from a preceding simulation, e.g. umbrella sampling. For a biased sampling select $k \\neq 0$ below."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 4,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "def Epot(x):\n",
+    "    toy_eng.positions = np.array([x])\n",
+    "    return pes.V(toy_eng)\n",
+    "\n",
+    "x0 = 0.0\n",
+    "k = 50\n",
+    "#k = 0\n",
+    "def Ebias(x):\n",
+    "    return 0.5 * k * (x - x0)**2"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 5,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "-----------------------------------------------------\n",
+      "Generated 100 initial snapshot positions.\n",
+      "-----------------------------------------------------\n",
+      "Average energy   :  1.0496176591142172\n",
+      "Acceptance ratio :  0.5388888888888889\n",
+      "-----------------------------------------------------\n"
+     ]
+    }
+   ],
+   "source": [
+    "# Choose initial point.\n",
+    "#x = np.random.uniform(S_min, S_max)\n",
+    "x =  0.0\n",
+    "points = [np.array([[x]])]\n",
+    "\n",
+    "# Set displacement and number of MC steps for each new snapshot position.\n",
+    "mcdisp = 0.1\n",
+    "mcsteps = 100\n",
+    "\n",
+    "# Initialize some counters.\n",
+    "mcacc = 0\n",
+    "mcrej = 0\n",
+    "mcmov = 0\n",
+    "E_S = 0.0\n",
+    "\n",
+    "# Loop to create snapshots positions.\n",
+    "for i in range(n_snapshots - 1):\n",
+    "    x = points[-1][0][0]\n",
+    "    # Perform MC steps.\n",
+    "    for m in range(mcsteps):\n",
+    "        xnew = x + mcdisp * np.random.normal()\n",
+    "        Eold = Epot(x) + Ebias(x)\n",
+    "        Enew = Epot(xnew) + Ebias(xnew)\n",
+    "        # Stay always in S.\n",
+    "        if S_min < xnew and xnew < S_max:\n",
+    "            # Metropolis MC scheme.\n",
+    "            if Eold > Enew:\n",
+    "                x = xnew\n",
+    "                E = Enew\n",
+    "                mcacc += 1\n",
+    "            else:\n",
+    "                if np.random.uniform() < np.exp(-beta * (Enew - Eold)):\n",
+    "                    x = xnew\n",
+    "                    E = Enew\n",
+    "                    mcacc += 1\n",
+    "                else:\n",
+    "                    E = Eold\n",
+    "                    mcrej += 1\n",
+    "        else:\n",
+    "            E = Eold\n",
+    "            mcrej += 1\n",
+    "        # Add new energy to average.    \n",
+    "        E_S += E            \n",
+    "        mcmov += 1\n",
+    "    # Append new position to list.\n",
+    "    points.append(np.array([[x]]))\n",
+    "\n",
+    "# Create snapshot template and list of initial snapshots.\n",
+    "template = toys.Snapshot(\n",
+    "    coordinates = np.array([[0.0]]),\n",
+    "    velocities = np.array([[0.0]]),\n",
+    "    engine = toy_eng\n",
+    ")\n",
+    "snapshots = [template.copy_with_replacement(coordinates=point) for point in points]\n",
+    "\n",
+    "print(\"-----------------------------------------------------\")\n",
+    "print(\"Generated\", n_snapshots, \"initial snapshot positions.\")\n",
+    "print( \"-----------------------------------------------------\")\n",
+    "print( \"Average energy   : \", E_S / (n_snapshots * mcsteps))\n",
+    "print( \"Acceptance ratio : \", float(mcacc) / mcmov)\n",
+    "print( \"-----------------------------------------------------\")"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Let's plot the potential energy and the corresponding weight in the $S$ region:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 6,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "<matplotlib.patches.Polygon at 0x7f380a064630>"
+      ]
+     },
+     "execution_count": 6,
+     "metadata": {},
+     "output_type": "execute_result"
+    },
+    {
+     "data": {
+      "image/png": "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\n",
+      "text/plain": [
+       "<Figure size 720x288 with 2 Axes>"
+      ]
+     },
+     "metadata": {
+      "needs_background": "light"
+     },
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "# Plot settings.\n",
+    "delta = 0.01\n",
+    "x_min = -1.5\n",
+    "x_max = 1.5\n",
+    "n_histo = 50\n",
+    "fig = plt.figure(figsize=plt.figaspect(0.4))\n",
+    "\n",
+    "# First plot.\n",
+    "ax = fig.add_subplot(1, 2, 1)\n",
+    "ax.set_xlim(x_min, x_max)\n",
+    "ax.set_xlabel(\"Position\")\n",
+    "ax.set_ylim(0.0, 1.7)\n",
+    "ax.set_ylabel(\"Energy\")\n",
+    "# Epot and Ebias functions.\n",
+    "X = np.linspace(x_min, x_max, num=500)\n",
+    "Y_Epot = np.array([Epot(x) for x in X])\n",
+    "Y_Ebias = np.array([Ebias(x) for x in X])\n",
+    "ax.plot(X, Y_Epot)\n",
+    "ax.plot(X, Y_Ebias)\n",
+    "ax.plot(X, Y_Epot + Y_Ebias)\n",
+    "# Initial snapshot positions.\n",
+    "X_sn = np.array([s.xyz[0][0] for s in snapshots])\n",
+    "Y_sn = np.array([Epot(x) + Ebias(x) for x in X_sn])\n",
+    "ax.plot(X_sn, Y_sn, \"ro\")\n",
+    "# Region background colors.\n",
+    "ax.axvspan(x_min, A_max, alpha=0.5, color='#FEEBA9')\n",
+    "ax.axvspan(S_min, S_max, alpha=0.5, color='#F9AC7E')\n",
+    "ax.axvspan(B_min, x_max, alpha=0.5, color='#B4E2F1')\n",
+    "\n",
+    "# Second plot.\n",
+    "ax = fig.add_subplot(1, 2, 2)\n",
+    "w = S_max - S_min\n",
+    "ax.set_xlim(S_min - 0.1 * w, S_max + 0.1 * w)\n",
+    "ax.set_xlabel(\"Position\")\n",
+    "ax.set_ylabel(\"Number of snapshots\")\n",
+    "# Calculate and plot expected histogram distribution.\n",
+    "X = np.linspace(S_min, S_max, num=500)\n",
+    "Y = np.array([np.exp(-beta * (Epot(x) + Ebias(x))) for x in X])\n",
+    "Y_factor = n_snapshots * w / n_histo / np.trapz(Y, X)\n",
+    "Y_distr = np.array([Y_factor * np.exp(-beta * (Epot(x) + Ebias(x))) for x in X])\n",
+    "ax.plot(X, Y_distr)\n",
+    "# Add histogram of initial snapshot positions.\n",
+    "X_sn = np.array([s.xyz[0][0] for s in snapshots])\n",
+    "ax.hist(X_sn, np.linspace(S_min, S_max, num=n_histo+1), facecolor=\"r\", alpha=0.5)\n",
+    "ax.axvspan(S_min, S_max, alpha=0.5, color='#F9AC7E')"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Furthermore we need a method to define states $A$, $S$ and $B$, we use the $x$-coordinate:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 7,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "def pos1D(snapshot):\n",
+    "    return snapshot.xyz[0][0]\n",
+    "\n",
+    "cv_x = paths.CoordinateFunctionCV(name=\"cv_x\", f=pos1D)\n",
+    "\n",
+    "state_A = paths.CVDefinedVolume(cv_x, -10000, A_max)\n",
+    "state_S = paths.CVDefinedVolume(cv_x, S_min, S_max)\n",
+    "state_B = paths.CVDefinedVolume(cv_x, B_min, 10000)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Finally, we need a randomizer and a storage object:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 8,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "randomizer = paths.NoModification()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 9,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "storage = paths.Storage(\"sshooting-1d.nc\", mode=\"w\", template=template)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Now all ingredients are combined to form the S-shooting simulation:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 10,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "simulation = SShootingSimulation(\n",
+    "    storage = storage,\n",
+    "    engine = toy_eng,\n",
+    "    state_S = state_S,\n",
+    "    randomizer = randomizer,\n",
+    "    initial_snapshots = snapshots,\n",
+    "    trajectory_length = trajectory_length\n",
+    ")"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Upon calling the `run` method the trajectories are harvested and saved."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 11,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Working on snapshot 100 / 100; shot 1 / 1\n",
+      "CPU times: user 3min 59s, sys: 1.03 s, total: 4min\n",
+      "Wall time: 4min\n"
+     ]
+    }
+   ],
+   "source": [
+    "%%time\n",
+    "simulation.run(n_per_snapshot=n_per_snapshot)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Let's plot some of the obtained trajectories:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 12,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "<matplotlib.patches.Polygon at 0x7f3808f50ac8>"
+      ]
+     },
+     "execution_count": 12,
+     "metadata": {},
+     "output_type": "execute_result"
+    },
+    {
+     "data": {
+      "image/png": "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\n",
+      "text/plain": [
+       "<Figure size 432x288 with 1 Axes>"
+      ]
+     },
+     "metadata": {
+      "needs_background": "light"
+     },
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "# Plot settings.\n",
+    "y_min = -1.5\n",
+    "y_max = 1.5\n",
+    "max_traj = 10\n",
+    "fig, ax = plt.subplots()\n",
+    "\n",
+    "# Set axis limits.\n",
+    "ax.set_xlim(0, 2 * tau)\n",
+    "ax.set_xlabel(\"Time\")\n",
+    "ax.set_ylim(y_min, y_max)\n",
+    "ax.set_ylabel(\"Position\")\n",
+    "# Loop over all trajectories and print \"max_traj\" (or less) of them.\n",
+    "for traj in storage.steps[0::max([1, int(len(storage.steps) / float(max_traj))])]:\n",
+    "    # Calculate frame times.\n",
+    "    X = [n_steps_per_frame * dt * i for i in range(0, len(traj.change.trials[-1]))]\n",
+    "    # Get coordinates from trajectories.\n",
+    "    Y = []\n",
+    "    for snapshot in traj.change.trials[-1]:\n",
+    "        for point in snapshot.coordinates:\n",
+    "            Y.append(float(point))\n",
+    "    # Plot trajectories.\n",
+    "    ax.plot(X, Y, \"-\")\n",
+    "# Region background colors.\n",
+    "ax.axhspan(y_min, A_max, alpha=0.5, color='#FEEBA9')\n",
+    "ax.axhspan(S_min, S_max, alpha=0.5, color='#F9AC7E')\n",
+    "ax.axhspan(B_min, y_max, alpha=0.5, color='#B4E2F1')        "
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 13,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "storage.close()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## S-shooting analysis\n",
+    "\n",
+    "Given the data from the S-shooting simulation we can now analyze the harvested trajectories. First, open the data file with the previously generated trajectories:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 14,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "storage = paths.Storage(\"sshooting-1d.nc\", \"r\")"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Define bias function, required as argument for S-shooting analysis:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 15,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "def b(snapshot):\n",
+    "    Ebias = 0.5 * k * (cv_x(snapshot) - x0)**2\n",
+    "    return np.exp(-beta * Ebias)\n",
+    "\n",
+    "cv_b = paths.CoordinateFunctionCV(name=\"cv_b\", f=b)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Now start the S-shooting analysis, this may take a while. If a bias was selected above ($k \\neq 0$), add the bias CV as function argument."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 16,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "application/vnd.jupyter.widget-view+json": {
+       "model_id": "25a6e5c4e3cc464fa40ae455c474f43c",
+       "version_major": 2,
+       "version_minor": 0
+      },
+      "text/plain": [
+       "HBox(children=(FloatProgress(value=0.0), HTML(value='')))"
+      ]
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "\n",
+      "CPU times: user 5min 53s, sys: 260 ms, total: 5min 53s\n",
+      "Wall time: 5min 53s\n"
+     ]
+    }
+   ],
+   "source": [
+    "%%time\n",
+    "results = SShootingAnalysis(steps=storage.steps,\n",
+    "                            states=[state_A, state_B, state_S],\n",
+    "                            bias=cv_b)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "The `calculate_averages` function returns a number of important results, such as these numbers globally averaged over all snapshots and trials:\n",
+    "\n",
+    "`M` ... Total number of harvested trajectory segments: $M$\n",
+    "\n",
+    "`Ns` ... Average number of trajectory points in $S$: $\\left<N_S[x(\\tau)]\\right>_B$\n",
+    "\n",
+    "`I_Bt` ... Average of inverse bias sum over all trajectories: $\\left<\\frac{1}{\\tilde{B}[x(\\tau)]}\\right>_B$\n",
+    "\n",
+    "`Ns_Bt` ... Average of $N_S$ divided by bias sum over all trajectories: $\\left<\\frac{N_S[x(\\tau)]}{\\tilde{B}[x(\\tau)]}\\right>_B$\n",
+    "\n",
+    "`hAhB_Bt` ... Array with average of $h_A(0)h_B(t)$ divided by bias sum over all trajectories: $\\left<\\frac{h_A(0)h_B(t)}{\\tilde{B}[x(\\tau)]}\\right>_B$\n",
+    "\n",
+    "In addition the same quantities are available for each snapshot individually via the returned dictionary (`results_per_snap`):"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 17,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "M     =  50100\n",
+      "Ns    =  43.8349500998004\n",
+      "I_Bt  =  0.05953756540293415\n",
+      "Ns_Bt =  1.3298719678447435\n"
+     ]
+    }
+   ],
+   "source": [
+    "M, Ns, I_Bt, Ns_Bt, hAhB_Bt, results_per_snap = results.calculate_averages()\n",
+    "print(\"M     = \", M)\n",
+    "print(\"Ns    = \", Ns)\n",
+    "print(\"I_Bt  = \", I_Bt)\n",
+    "print(\"Ns_Bt = \", Ns_Bt)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "The per-snapshot results may be accessed like this:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 18,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "M     =  501\n",
+      "Ns    =  10.870259481037925\n",
+      "I_Bt  =  0.1277706224627812\n",
+      "Ns_Bt =  1.3622840344867455\n"
+     ]
+    }
+   ],
+   "source": [
+    "snap = list(results_per_snap.keys())[0]\n",
+    "snap_result = results_per_snap[snap]\n",
+    "print(\"M     = \", snap_result[\"M\"])\n",
+    "print(\"Ns    = \", snap_result[\"Ns\"])\n",
+    "print(\"I_Bt  = \", snap_result[\"I_Bt\"])\n",
+    "print(\"Ns_Bt = \", snap_result[\"Ns_Bt\"])"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "The `hAhB_Bt` array allows us to plot the time correlation function $\\left<h_A(0)h_B(t)\\right>_S$ in the $S$-ensemble and its derivative. We also included reference data from Brownian dynamcis from the reference paper."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 19,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "[<matplotlib.lines.Line2D at 0x7f3807a5e630>]"
+      ]
+     },
+     "execution_count": 19,
+     "metadata": {},
+     "output_type": "execute_result"
+    },
+    {
+     "data": {
+      "image/png": "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\n",
+      "text/plain": [
+       "<Figure size 576x432 with 2 Axes>"
+      ]
+     },
+     "metadata": {
+      "needs_background": "light"
+     },
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "fig, ax = plt.subplots()\n",
+    "fig.set_size_inches(8, 6)\n",
+    "\n",
+    "# Prepare and plot reference data.\n",
+    "ref_data = np.loadtxt(\"reference-sshooting.data\")\n",
+    "ref_gradient = np.gradient(ref_data.T[1], ref_data.T[0][1]-ref_data.T[0][0])\n",
+    "ax.set_xlim([0.0, 0.5])\n",
+    "ax.set_ylim([0.0, 0.1375])\n",
+    "ax.set_xlabel(r't', fontsize=16)\n",
+    "ax.set_ylabel(r'$\\left<h_A(0)h_B(t)\\right>_S$', fontsize=16)\n",
+    "ax.plot(ref_data.T[0], ref_data.T[1], \"C0-\")\n",
+    "ax2 = ax.twinx()\n",
+    "ax2.set_xlim([0.0, 0.5])\n",
+    "ax2.set_ylim([0.0, 0.5])\n",
+    "ax2.set_ylabel(r'$\\frac{d \\left<h_A(0)h_B(t)\\right>_S}{d t}$', fontsize=16)\n",
+    "ax2.plot(ref_data.T[0], ref_gradient, \"C1-\")\n",
+    "\n",
+    "# Prepare and plot S-shooting data.\n",
+    "delta = n_steps_per_frame * dt\n",
+    "X = np.array([delta * i for i in range(trajectory_length + 1)])\n",
+    "Y = np.gradient(hAhB_Bt/I_Bt, delta)\n",
+    "ax.plot(X, hAhB_Bt/I_Bt, \"C0x\", ms=5)\n",
+    "ax2.plot(X, Y, \"C1x\", ms=5)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "The average number of points in the $S$-region in the $S$-ensemble $\\left<N_S[x(\\tau)]\\right>_S$ can be easily computed:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 20,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "22.336687078897658"
+      ]
+     },
+     "execution_count": 20,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "Ns_Bt / I_Bt"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Finally, if the averages $\\left<h_A\\right>$ and $\\left<h_S\\right>$ are provided (see the reference paper) we can plot the derivative of the time correlation function and thus estimate the rate $k_{AB}$ as the plateau value ($k_{AB} = 0.056$)."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 21,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "[<matplotlib.lines.Line2D at 0x7f38087e3ba8>]"
+      ]
+     },
+     "execution_count": 21,
+     "metadata": {},
+     "output_type": "execute_result"
+    },
+    {
+     "data": {
+      "image/png": "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\n",
+      "text/plain": [
+       "<Figure size 576x432 with 1 Axes>"
+      ]
+     },
+     "metadata": {
+      "needs_background": "light"
+     },
+     "output_type": "display_data"
+    }
+   ],
+   "source": [
+    "fig, ax = plt.subplots()\n",
+    "fig.set_size_inches(8, 6)\n",
+    "\n",
+    "X = np.array([delta * i for i in range(trajectory_length + 1)])\n",
+    "C_AB = results.C_AB(hA=0.487, hS=0.00407)\n",
+    "dC_ABdt = np.gradient(C_AB, delta)\n",
+    "ax.set_xlabel(r't', fontsize=14)\n",
+    "ax.set_ylabel(r'$\\frac{d C_{AB}(t)}{d t}$', fontsize=16)\n",
+    "ax.plot(X, dC_ABdt, 'C3-')"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": []
+  }
+ ],
+ "metadata": {
+  "anaconda-cloud": {},
+  "kernelspec": {
+   "display_name": "Python 3",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.6.8"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 1
+}
diff --git a/openpathsampling/__init__.py b/openpathsampling/__init__.py
index f033f68f2..79e43da7d 100644
--- a/openpathsampling/__init__.py
+++ b/openpathsampling/__init__.py
@@ -41,6 +41,7 @@
     ShootingPointAnalysis, SnapshotByCoordinateDict
 )
 
+from .analysis.sshooting_analysis import SShootingAnalysis
 from .analysis.reactive_flux_analysis import (
     ReactiveFluxAnalysis
 )
@@ -114,7 +115,7 @@
 from .pathsimulators import (
     PathSimulator, FullBootstrapping, Bootstrapping, PathSampling, MCStep,
     CommittorSimulation, ReactiveFluxSimulation, DirectSimulation,
-    ShootFromSnapshotsSimulation
+    ShootFromSnapshotsSimulation, SShootingSimulation
 )
 
 from .rng import default_rng
diff --git a/openpathsampling/analysis/sshooting_analysis.py b/openpathsampling/analysis/sshooting_analysis.py
new file mode 100644
index 000000000..afe5cd938
--- /dev/null
+++ b/openpathsampling/analysis/sshooting_analysis.py
@@ -0,0 +1,277 @@
+import logging
+import openpathsampling as paths
+import collections
+import numpy as np
+
+from .shooting_point_analysis import SnapshotByCoordinateDict
+from .shooting_point_analysis import ShootingPointAnalysis
+
+logging.basicConfig()
+logger = logging.getLogger(__name__)
+
+class SShootingAnalysis(ShootingPointAnalysis):
+    """
+    Container and methods for S-shooting analysis.
+
+    Parameters
+    ----------
+    steps : iterable of :class:`.MCStep` or None
+        input MC steps to analyze; if None, no analysis performed
+    states : list of :class:`.Volume`
+        the volumes representing the stable states and the S region, in this
+        order: [A, B, S].
+    bias : :class:`.CollectiveVariable`
+        bias weight function.
+        
+    """
+    def __init__(self, steps, states, bias=None):
+        # SShootingAnalysis is inherited from ShootinPointAnalysis but
+        # overrides the __init__ function. Thus, the call to the
+        # SnapshotByCoordinateDict __init__ function has to be repeated
+        # here (ugly)!
+        SnapshotByCoordinateDict.__init__(self)
+        #super(ShootingPointAnalysis, self).__init__()
+
+        # Definition of states A, B and S.
+        self.state_A = states[0]
+        self.state_B = states[1]
+        self.state_S = states[2]
+
+        # If called without bias create a dummy one.
+        if bias is None:
+            def dummy_bias(x):
+                return 1.0
+            cv_b = paths.CoordinateFunctionCV(name="cv_b", f=dummy_bias)
+            self.bias = cv_b
+        else:
+            self.bias = bias
+
+        # Initialize inconsistency counter.
+        self.count_inconsistent_S = 0
+
+        # Set one-direction shot length l.
+        self.l = steps[0].simulation.trajectory_length
+        
+        # Prepare array for time correlation function.
+        self._CABxhA_hS = None
+
+        # Analyze each trajectory.
+        if steps is not None:
+            self.analyze(steps)
+
+        # Issue a warning if some trajectories were harvested with a different
+        # settings for the S region.
+        if self.count_inconsistent_S > 0:
+            logging.warning("Some trajectories ("
+                            + str(self.count_inconsistent_S) +
+                            " cases) were harvested with a different S region "
+                            "definition than the one provided for the "
+                            "analysis. Trajectories with starting snapshots "
+                            "out of the given S region were ignored.")
+
+    def analyze_single_step(self, step):
+        """
+        Adding a single step.
+
+        Parameters
+        ----------
+        step : :class:`.MCStep`
+            the step to analyze and add to this analysis
+
+        Returns
+        -------
+        list of :class:`.Volume`
+            the states which are identified as new final states from this
+            move
+        """
+
+        # Get key for dictionary entry for this starting snapshot.
+        key = self.step_key(step)
+
+        if key is not None:
+            # Shortcut for current full trajectory (2*l+1 snapshots).
+            trajectory = step.change.trials[-1]
+
+            # Shortcut for forward/backward shot length.
+            l = self.l
+
+            # Check if S region from simulation matches with the one provided as
+            # an argument.
+            if self.state_S != step.simulation.state_S:
+                self.count_inconsistent_S += 1
+                if not self.state_S(trajectory[l]):
+                    return;
+
+            # If dictionary is empty for this snapshot create entry with
+            # defaults:
+            # M ......... Counter for trajectory segments.
+            # I_Bt ...... Sum of 1/Bt[x(tau)] for all trajectory segments.
+            # Ns_Bt ..... Sum of Ns[x(tau)]/Bt[x(tau)] for all trajectory
+            #             segments.
+            # hAhB_Bt ... Array with hA(0)*hB(t)/Bt[x(tau)] for all times t and
+            #             all segments.
+            if not (key in self):
+                self[key] = {"M" : 0,
+                             "Ns" : 0.0,
+                             "I_Bt" : 0.0,
+                             "Ns_Bt" : 0.0,
+                             "hAhB_Bt" : np.zeros(l + 1)}
+
+            # Calculate counter for number of states in S (Ns) and bias sum (Bt)
+            # for first subtrajectory.
+            Ns = 0
+            Bt = 0.0
+            for snapshot in trajectory[0:l+1]:
+                if self.state_S(snapshot):
+                    Ns += 1
+                    Bt += self.bias(snapshot)
+
+            # Loop over all starting points = subtrajectories.
+            for istart in range(l + 1):
+
+                # Update counter for number of states in S (Ns) and bias sum
+                # (Bt)
+                if istart > 0:
+                    # Remove values for lost frame (most left one of the
+                    # previous trajectory).
+                    if self.state_S(trajectory[istart - 1]):
+                        Ns -= 1
+                        Bt -= self.bias(trajectory[istart - 1])
+                    # Add values for new gained frame (frame right of the
+                    # previous trajectory).
+                    if self.state_S(trajectory[istart + l]):
+                        Ns += 1
+                        Bt += self.bias(trajectory[istart + l])
+
+                # Increment trajectory counter and B-average of Ns / Bt.
+                self[key]["M"] += 1
+                self[key]["Ns"] += float(Ns)
+                self[key]["I_Bt"] += 1.0 / Bt
+                self[key]["Ns_Bt"] += Ns / Bt
+
+                # Loop over states in subtrajectory.
+                if self.state_A(trajectory[istart]):
+                    for istep in range(l + 1):
+                        if self.state_B(trajectory[istart + istep]):
+                            self[key]["hAhB_Bt"][istep] += 1.0 / Bt
+                #            self[key]["hAhB_Bt"][istep] += 0.5 / Bt
+                #elif self.state_B(trajectory[istart]):
+                #    for istep in range(l + 1):
+                #        if self.state_A(trajectory[istart + istep]):
+                #            self[key]["hAhB_Bt"][istep] += 0.5 / Bt
+            return [k for k in self[key].keys()]
+        else:
+            return {}
+                             
+    def calculate_averages(self, label_function=None):
+        """Calculate point-by-point and globally averaged results.
+
+        This calculates the average time correlation function <hA(0)hB(t)>
+        and other useful quantities globally and per initial snapshot.
+
+        Parameters
+        ----------
+        label_function : callable
+            the keys for the dictionary that is returned are
+            `label_function(snapshot)`; default `None` gives the snapshot as
+            key.
+
+        Returns
+        -------
+        tuple
+            format is (M, Ns, I_Bt, Ns_Bt, hAhB_Bt, dict)
+            M : float
+                Total number of harvested trajectory segments.
+            Ns : float
+                Average number of trajectory points in S.
+            I_Bt : float
+                Average of inverse bias sum over all trajectories.
+            Ns_Bt : float
+                Average of N_S divided by bias sum over all trajectories.
+            hAhB_Bt : array
+                Array with average of hA(0)hB(t) divided by bias sum
+                over all trajectories.
+            dict : dictionary
+                all the above results for each snapshot separately.
+        """
+        # Set default label_function to identity.
+        if label_function is None:
+            label_function = lambda s : s
+
+        # Initialize result variables.
+        M = 0
+        Ns = 0.0
+        I_Bt = 0.0
+        Ns_Bt = 0.0
+        hAhB_Bt = np.zeros(self.l + 1)
+        results = {}
+
+        # Loop over all keys (this is a dictionary with snapshots
+        # represented by coordinate hashes!).
+        for k in self:
+            # Set the output key (if label_function=None it is the snapshot).
+            out_key = label_function(k)
+
+            # Look up the dictionary with data collected for this snapshot.
+            data_k = self[k]
+
+            # Increment global results.
+            M += data_k["M"]
+            Ns += data_k["Ns"]
+            I_Bt += data_k["I_Bt"]
+            Ns_Bt += data_k["Ns_Bt"]
+            hAhB_Bt += data_k["hAhB_Bt"]
+
+            # Save per-snapshot results.
+            results[out_key] = {"M" : data_k["M"],
+                                "Ns" : data_k["Ns"] / data_k["M"],
+                                "I_Bt" : data_k["I_Bt"] / data_k["M"],
+                                "Ns_Bt" : data_k["Ns_Bt"] / data_k["M"],
+                                "hAhB_Bt" : data_k["hAhB_Bt"] / data_k["M"]}
+        # Average global results.
+        Ns /= M
+        I_Bt /= M
+        Ns_Bt /= M
+        hAhB_Bt /= M
+        self._CABxhA_hS = hAhB_Bt / Ns_Bt * (self.l + 1)
+
+        # Return global quantities and dictionary with per-snapshot values.
+        return (M, Ns, I_Bt, Ns_Bt, hAhB_Bt, results)
+
+    def C_AB(self, hS=1.0, hA=1.0):
+        """Calculate time correlation function C_AB(t).
+
+        Automatically calls calculate_averages() if results are not yet stored
+        internally.
+
+        Parameters
+        ----------
+        hS : float
+            Equilibrium propability for the system being in S, e.g. <hS> from
+            umbrella sampling.
+        hA : float
+            Equilibrium propability for the system being in A, e.g. <hA> from
+            umbrella sampling.
+
+        Returns
+        -------
+        array
+            Time correlation function C_AB(t).
+        """
+        return self.CABxhA_hS * hS / hA
+
+    @property
+    def CABxhA_hS(self):
+        if self._CABxhA_hS is not None:
+            return self._CABxhA_hS
+        else:
+            self.calculate_averages()
+            return self._CABxhA_hS
+
+    def committor(self, *args, **kwargs):
+        raise NotImplementedError("The S-shooting analysis does not"
+            + " calculate the committor.")
+
+    def committor_histogram(self, *args, **kwargs):
+        raise NotImplementedError("The S-shooting analysis does not"
+            + "calculate the committor.")
diff --git a/openpathsampling/collectivevariables/plumed_wrapper.py b/openpathsampling/collectivevariables/plumed_wrapper.py
index 32fb29356..85e700f81 100755
--- a/openpathsampling/collectivevariables/plumed_wrapper.py
+++ b/openpathsampling/collectivevariables/plumed_wrapper.py
@@ -14,7 +14,7 @@
 
 class PLUMEDCV(paths.collectivevariable.CoordinateFunctionCV):
 
-    """Make `CollectiveVariable` computed by PLUMED [1]_ according to the
+    """Make `CollectiveVariable` computed by PLUMED [PLU2a]_ according to the
     command `name`: `definition`, where `name` is a PLUMED label and
     `definition` contains all PLUMED keywords.
     Takes an `openpathsampling.engines.trajectory.Trajectory` as input.
@@ -22,7 +22,7 @@ class PLUMEDCV(paths.collectivevariable.CoordinateFunctionCV):
     References
     ----------
 
-    .. [1] G.A. Tribello, M. Bonomi, D. Branduardi, C. Camilloni, G. Bussi,
+    .. [PLU2a] G.A. Tribello, M. Bonomi, D. Branduardi, C. Camilloni, G. Bussi,
        PLUMED2: New feathers for an old bird, Comp. Phys. Comm. 185, 604
        (2014); https://doi.org/10.1016/j.cpc.2013.09.018
 
@@ -188,7 +188,7 @@ def to_dict(self):
 
 class PLUMEDInterface(StorableNamedObject):
 
-    """Interfaces the Cython PLUMED wrapper [1]_ located at
+    """Interfaces the Cython PLUMED wrapper [PLU2b]_ located at
     `/path/to/plumed2/python` and allows to set and get non-`PLUMEDCV`
     commands (i.e., non-outputting).  This includes groups of atoms, centers
     of mass, include files, etc.  Requires PLUMED development version (see
@@ -198,7 +198,7 @@ class PLUMEDInterface(StorableNamedObject):
     References
     ----------
 
-    .. [1] G.A. Tribello, M. Bonomi, D. Branduardi, C. Camilloni, G. Bussi,
+    .. [PLU2b] G.A. Tribello, M. Bonomi, D. Branduardi, C. Camilloni, G. Bussi,
        PLUMED2: New feathers for an old bird, Comp. Phys. Comm. 185, 604
        (2014); https://doi.org/10.1016/j.cpc.2013.09.018
 
diff --git a/openpathsampling/deprecations.py b/openpathsampling/deprecations.py
index 03ec65a63..f9c343709 100644
--- a/openpathsampling/deprecations.py
+++ b/openpathsampling/deprecations.py
@@ -1,7 +1,6 @@
 from __future__ import print_function
 import sys
 import warnings
-from collections import namedtuple
 from functools import wraps
 from inspect import isclass
 
@@ -14,6 +13,7 @@
     {problem} {remedy}
 """
 
+
 class Deprecation(object):
     """
     Parameters
@@ -105,6 +105,7 @@ def update_docstring(thing_with_docstring, deprecation):
         docs = thing_with_docstring.__doc__
     return docs + deprecation.docstring_message()
 
+
 def version_tuple_to_string(version_tuple):
     """
     Parameters
@@ -168,6 +169,63 @@ def version_tuple_to_string(version_tuple):
     deprecated_in=(1, 5, 0)
 )
 
+SNAPSHOTMODIFIER_PROB_RAT = Deprecation(
+    problem=("This function will raise a NotImplementedError in "
+             "{OPS} {version}."),
+    remedy=("All SnapshotModifier subclasses should override the "
+            "probability_ratio function."),
+    remove_version=(2, 0),
+    deprecated_in=(1, 6, 0)
+)
+
+NEW_SNAPSHOT_SELECTOR = Deprecation(
+    problem=("new_snapshot=None; If snapshot has been copied or modified we "
+             "can't find it in trial_trajectory. This call signature will "
+             "update to "
+             "(old_snapshot, old_trajectory, new_snapshot, new_trajectory) "
+             "in {OPS} {version}. "),
+    remedy=("Call with kwargs and use new_snapshot=old_snapshot if "
+            " old_snapshot is not copied or modified in new_traj"),
+    remove_version=(2, 0),
+    deprecated_in=(1, 6, 0)
+)
+
+NEW_SNAPSHOT_KWARG_SELECTOR = Deprecation(
+    problem=("'new_snapshot' should be a supported keyword in "
+             "selector.probability_ratio(); If snapshot has been copied or "
+             "modified we can't reliably find it in trial_trajectory. This "
+             "keyword must be supported in the expected signature: "
+             "(old_snapshot, old_trajectory, new_snapshot, new_trajectory) "
+             "in {OPS} {version}. "),
+    remedy=("kwarg 'new_snapshot' must to be supported, implement it as "
+            "new_snapshot=old_snapshot if new_traj is not used to calculate "
+            "the weight of old_snapshot"),
+    remove_version=(2, 0),
+    deprecated_in=(1, 6, 0)
+)
+
+NEW_DEFAULT_FILENAME_SETTER =  Deprecation(
+    problem=("The default FilenameSetter for external engines is now a counter"
+             ", but will become a random string. This is more robust against "
+             "accidental overwrites."),
+    remedy=("If you want to keep the old counting behavior, add "
+            '{"filename_setter": '
+            "paths.engines.external_engine.FilenameSetter() } to the 'options'"
+            " of this engine. Otherwise, this behavior will automatically "
+            "change to RandomStringFilenames in OPS 2.0."),
+    remove_version=(2, 0),
+    deprecated_in=(1, 6, 0)
+)
+
+SIMSTORE_CALLABLE_CODEC_STRING_NOT_BYTES = Deprecation(
+    problem=("The storage representation of callables stored by SimStore "
+             "has changed. Some functions stored in this file may not "
+             "work starting in {OPS} {version}"),
+    remedy=("Re-saving the data from this file into another file will "
+            "create something that can be loaded in that version."),
+    remove_version=(2, 0),
+    deprecated_in=(1, 6, 0)
+)
 
 # has_deprecations and deprecate hacks to change docstrings inspired by:
 # https://stackoverflow.com/a/47441572/4205735
@@ -184,6 +242,7 @@ def has_deprecations(cls):
             del obj.__new_docstring
     return cls
 
+
 def deprecate(deprecation):
     """Decorator to deprecate a class/method
 
@@ -211,6 +270,7 @@ def wrapper(*args, **kwargs):
             return wrapper
     return decorator
 
+
 def list_deprecations(version=None, deprecations=None):
     """List deprecations that should have been removed by ``version``
 
diff --git a/openpathsampling/engines/dynamics_engine.py b/openpathsampling/engines/dynamics_engine.py
index 748f5e44f..e77c123fb 100644
--- a/openpathsampling/engines/dynamics_engine.py
+++ b/openpathsampling/engines/dynamics_engine.py
@@ -133,6 +133,7 @@ class DynamicsEngine(StorableNamedObject):
     }
 
     base_snapshot_type = BaseSnapshot
+    clear_snapshot_cache = False
 
     def __init__(self, options=None, descriptor=None):
         """
@@ -377,13 +378,20 @@ def stop_conditions(self, trajectory, continue_conditions=None,
             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)
+        if callable(continue_conditions):
+            continue_conditions = [continue_conditions]
+
+        for condition in continue_conditions:
+            stop = (not condition(trajectory, trusted)) or stop
+            # TODO: Consider short-circuit logic (uncomment code below).
+            # Pros: short circuit will be faster; avoid wasted effort.
+            # Cons: there may be desired side effects from not shorting.
+            # Current thought: No short circuit means that CVs may be
+            # calculated as part of stop conditions here, so keep as is.
+            # (NB: ensembles/volumes use short-circuit logic, so there isn't
+            # really a promise that CVs get calculated.)
+            # if stop:
+            #     return True
 
         return stop
 
@@ -512,7 +520,7 @@ def iter_generate(self, initial, running=None, direction=+1,
                                 int(len(trajectory) / 2)))]
                 elif hasattr(self.on_retry, '__call__'):
                     trajectory = self.on_retry(trajectory)
-            
+
             """ Case of run dying before first output"""
             if len(trajectory) >= 1:
                 if direction > 0:
@@ -533,6 +541,7 @@ def iter_generate(self, initial, running=None, direction=+1,
             log_rate = 10
             has_nan = False
             has_error = False
+            snapshot = None  # so it is in scope
 
             while not stop:
                 if intervals > 0 and frame % intervals == 0:
@@ -545,6 +554,7 @@ def iter_generate(self, initial, running=None, direction=+1,
 
                 # Do integrator x steps
 
+                self._clear_snapshot_cache(snapshot)  # clear old snapshot
                 snapshot = None
 
                 try:
@@ -617,6 +627,7 @@ def iter_generate(self, initial, running=None, direction=+1,
                     stop = self.stop_conditions(trajectory=trajectory,
                                             continue_conditions=running)
 
+            # check what to do if stop is True
             if has_nan:
                 on = self.on_nan
                 if on == 'fail':
@@ -656,10 +667,17 @@ def iter_generate(self, initial, running=None, direction=+1,
         if final_error is not None:
             yield trajectory
             logger.info("Through frame: %d", len(trajectory))
+            self._clear_snapshot_cache(snapshot)
             raise final_error
 
         logger.info("Finished trajectory, length: %d", len(trajectory))
         yield trajectory
+        self._clear_snapshot_cache(snapshot)
+
+    def _clear_snapshot_cache(self, snapshot):
+        if self.clear_snapshot_cache and snapshot is not None:
+            clear_cache = getattr(snapshot, 'clear_cache', lambda: None)
+            clear_cache()
 
     def generate_next_frame(self):
         raise NotImplementedError('Next frame generation must be implemented!')
diff --git a/openpathsampling/engines/external_engine.py b/openpathsampling/engines/external_engine.py
index eae2240dd..09abcfff2 100644
--- a/openpathsampling/engines/external_engine.py
+++ b/openpathsampling/engines/external_engine.py
@@ -1,7 +1,8 @@
 from openpathsampling.netcdfplus import StorableNamedObject
 from openpathsampling.engines.dynamics_engine import DynamicsEngine
 from openpathsampling.engines.snapshot import BaseSnapshot, SnapshotDescriptor
-from openpathsampling.engines.toy import ToySnapshot
+from openpathsampling.deprecations import NEW_DEFAULT_FILENAME_SETTER
+
 import numpy as np
 import os
 
@@ -12,14 +13,16 @@
 import shlex
 import time
 
+from subprocess import PIPE
+
 import sys
 if sys.version_info > (3, ):
     long = int
 
-import linecache
 
 logger = logging.getLogger(__name__)
 
+
 def close_file_descriptors(basename):
     """Close file descriptors for the given filename.
 
@@ -49,6 +52,7 @@ def _debug_open_files(where=None, ext=".trr"):
 
             raise Exception("\n".join(message))
 
+
 def _debug_snapshot_loading(snapshot):
     snapshot.load_details()
     snapshot.clear_cache()
@@ -90,6 +94,7 @@ class RandomStringFilenames(FilenameSetter):
         string containing the allowed characters to return
     """
     _allowed = 'abcdefghijklmnopqrstuvwxyz0123456789'
+
     def __init__(self, length=8, allowed=None):
         super(RandomStringFilenames, self).__init__()
         self.length = length
@@ -99,6 +104,7 @@ def __init__(self, length=8, allowed=None):
     def __call__(self):
         return "".join(np.random.choice(self.allowed, self.length))
 
+
 class _InternalizedEngineProxy(DynamicsEngine):
     """Wrapper that allows snapshots to be "internalized."
 
@@ -134,14 +140,14 @@ class ExternalEngine(DynamicsEngine):
     """
 
     _default_options = {
-        'n_frames_max' : 10000,
-        'name_prefix' : "test",
-        'default_sleep_ms' : 100,
-        'auto_optimize_sleep' : True,
-        'engine_sleep' : 100,
-        'engine_directory' : "",
-        'n_spatial' : 1,
-        'n_atoms' : 1,
+        'n_frames_max': 10000,
+        'name_prefix': "test",
+        'default_sleep_ms': 100,
+        'auto_optimize_sleep': True,
+        'engine_sleep': 100,
+        'engine_directory': "",
+        'n_spatial': 1,
+        'n_atoms': 1,
         'n_poll_per_step': 1,
         'filename_setter': FilenameSetter(),
     }
@@ -161,6 +167,11 @@ def __init__(self, options, descriptor, template,
         self._current_snapshot = template
         self.n_frames_since_start = None
         self.internalized_engine = _InternalizedEngineProxy(self)
+        if 'filename_setter' not in options:
+            # Level 6 is needed to raise it to the initialization of a
+            # gromacs engine. This is a FutureWarning to also warn
+            # users, not only developers.
+            NEW_DEFAULT_FILENAME_SETTER.warn(6, category=FutureWarning)
 
     def to_dict(self):
         dct = super(ExternalEngine, self).to_dict()
@@ -207,25 +218,26 @@ def generate_next_frame(self):
                     next_frame = None
                 else:
                     raise
-            #print self.frame_num, next_frame # DEBUG LOGGER
+            # print self.frame_num, next_frame # DEBUG LOGGER
             now = time.time()
             if next_frame == "partial":
                 if self.proc.poll() is not None:
                     raise RuntimeError("External engine died unexpectedly")
-                time.sleep(0.001) # wait a millisec and rerun
+                time.sleep(0.001)  # wait a millisec and rerun
             elif next_frame is None:
                 if self.proc.poll() is not None:
                     raise RuntimeError("External engine died unexpectedly")
                 logger.debug("Sleeping for {:.2f}ms".format(self.sleep_ms))
                 time.sleep(self.sleep_ms/1000.0)
-            elif isinstance(next_frame, BaseSnapshot): # success
+            elif isinstance(next_frame, BaseSnapshot):  # success
                 self.n_frames_since_start += 1
                 logger.debug("Found frame %d", self.n_frames_since_start)
                 self.current_snapshot = next_frame
                 next_frame_found = True
                 self.frame_num += 1
             else:  # pragma: no cover
-                raise RuntimeError("Strange return value from read_next_frame_from_file")
+                raise RuntimeError("Strange return value from "
+                                   "read_next_frame_from_file")
             if self.auto_optimize_sleep and self.n_frames_since_start > 0:
                 n_poll_per_step = self.options['n_poll_per_step']
                 elapsed = now - self.start_time
@@ -244,21 +256,25 @@ def start(self, snapshot=None):
         self.write_frame_to_file(self.input_file, self.current_snapshot, "w")
         self.prepare()
 
-        cmd = shlex.split(self.engine_command())
         self.start_time = time.time()
         try:
             logger.info(self.engine_command())
-            # TODO: add the ability to have handlers for stdin and stdout
             self.proc = psutil.Popen(shlex.split(self.engine_command()),
-                                     preexec_fn=os.setsid)
+                                     preexec_fn=os.setsid,
+                                     stdout=PIPE,
+                                     stderr=PIPE)
         except OSError:  # pragma: no cover
-            raise  #TODO: need to handle this, but do what?
+            raise  # TODO: need to handle this, but do what?
         else:
             logger.info("Started engine: " + str(self.proc))
 
         if self.first_frame_in_file:
             _ = self.generate_next_frame()  # throw away repeat first frame
 
+    def _communicate(self):
+        # this is primarily for debug purposes
+        return self.proc.communicate()
+
     def stop(self, trajectory):
         super(ExternalEngine, self).stop(trajectory)
         logger.info("total_time {:.4f}".format(time.time() - self.start_time))
@@ -317,4 +333,3 @@ def set_filenames(self, number):
     def engine_command(self):
         """Generates a string for the command to run the engine."""
         raise NotImplementedError()
-
diff --git a/openpathsampling/engines/external_snapshots/snapshot.py b/openpathsampling/engines/external_snapshots/snapshot.py
index 6ff1bdf67..a3c28f2f6 100644
--- a/openpathsampling/engines/external_snapshots/snapshot.py
+++ b/openpathsampling/engines/external_snapshots/snapshot.py
@@ -103,13 +103,11 @@ def set_details(self, xyz, velocities, box_vectors):
         try:
             self.load_details()
         except:
-            pass
-        else:
-            raise RuntimeError("Can't set details if frame already exists.")
-        finally:
             self._xyz = xyz
             self._velocities = velocities
             self._box_vectors = box_vectors
+        else:
+            raise RuntimeError("Can't set details if frame already exists.")
 
     def clear_cache(self):
         """Remove internal details from snapshot.
diff --git a/openpathsampling/engines/features/box_vectors.py b/openpathsampling/engines/features/box_vectors.py
index 0114eacfb..1344014b7 100644
--- a/openpathsampling/engines/features/box_vectors.py
+++ b/openpathsampling/engines/features/box_vectors.py
@@ -3,6 +3,10 @@
 
 dimensions = ['n_spatial']
 
+schema_entries = [
+    ('box_vectors', 'ndarray.float32({n_spatial},{n_spatial})')
+]
+
 
 def netcdfplus_init(store):
     store.create_variable(
diff --git a/openpathsampling/engines/features/topology.py b/openpathsampling/engines/features/topology.py
index 171b40b30..7eb7c072c 100644
--- a/openpathsampling/engines/features/topology.py
+++ b/openpathsampling/engines/features/topology.py
@@ -1,8 +1,8 @@
 """
 Attributes
 ----------
-topology : :class:`openpathsampling.Topolgy`
-    object containing a description of the snapshots topology
+topology : :class:`openpathsampling.Topology`
+    object containing a description of the snapshot's topology
 """
 
 
diff --git a/openpathsampling/engines/gromacs/engine.py b/openpathsampling/engines/gromacs/engine.py
index 6a6303925..614b7d5b1 100644
--- a/openpathsampling/engines/gromacs/engine.py
+++ b/openpathsampling/engines/gromacs/engine.py
@@ -146,6 +146,7 @@ class GromacsEngine(ExternalEngine):
     # use these as CMD.format(e=engine, **engine.options)
     SnapshotClass = ExternalMDSnapshot
     InternalizedSnapshotClass = InternalizedMDSnapshot
+    clear_snapshot_cache = True
     def __init__(self, gro, mdp, top, options, base_dir="", prefix="gmx"):
         self.base_dir = base_dir
         self.gro = os.path.join(base_dir, gro)
diff --git a/openpathsampling/engines/openmm/engine.py b/openpathsampling/engines/openmm/engine.py
index 5601a654e..63209fb1f 100644
--- a/openpathsampling/engines/openmm/engine.py
+++ b/openpathsampling/engines/openmm/engine.py
@@ -286,11 +286,19 @@ def initialize(self, platform=None):
                     platformProperties=self.openmm_properties
                 )
             elif platform is None:
+                # as of OpenMM 8.1, we can't give an empty props dict when
+                # platform is None. This will still raise the internal
+                # OpenMM error is platform is None and properties are
+                # provided.
+                openmm_props = self.openmm_properties
+                if openmm_props == {}:
+                    openmm_props = None
+
                 self._simulation = openmm.app.Simulation(
                     topology=self.topology.mdtraj.to_openmm(),
                     system=self.system,
                     integrator=self.integrator,
-                    platformProperties=self.openmm_properties
+                    platformProperties=openmm_props,
                 )
             else:
                 self._simulation = openmm.app.Simulation(
diff --git a/openpathsampling/engines/openmm/features/masses.py b/openpathsampling/engines/openmm/features/masses.py
index 6c2175348..3774836dd 100644
--- a/openpathsampling/engines/openmm/features/masses.py
+++ b/openpathsampling/engines/openmm/features/masses.py
@@ -15,8 +15,8 @@ def masses_per_mole(snapshot):
         # OpenMM snapshot not from simulation engine (setup snapshot,
         # probably)
         ops_topology = snapshot.topology
-        topology = ops_topology.mdtraj.to_openmm()
-        masses_per_mole = [a.element.mass for a in topology.atoms()]
+        topology = ops_topology.mdtraj
+        masses_per_mole = [a.element.mass * u.dalton for a in topology.atoms]
     else:
         # standard case from simulation
         system = simulation.context.getSystem()
diff --git a/openpathsampling/engines/toy/__init__.py b/openpathsampling/engines/toy/__init__.py
index ed3f6602f..0ac2cd26b 100644
--- a/openpathsampling/engines/toy/__init__.py
+++ b/openpathsampling/engines/toy/__init__.py
@@ -1,6 +1,12 @@
-from .integrators import (LangevinBAOABIntegrator, LeapfrogVerletIntegrator)
-from .pes import Gaussian, HarmonicOscillator, LinearSlope, OuterWalls, \
+from .integrators import (
+    LangevinBAOABIntegrator, LeapfrogVerletIntegrator,
+    OverdampedLangevinIntegrator
+)
+
+from .pes import (
+    Gaussian, HarmonicOscillator, LinearSlope, OuterWalls, DoubleWell,
     PES_Add, PES_Combination, PES_Sub, PES
+)
 
 from .engine import ToyEngine as Engine
 from .engine import ToyEngine
diff --git a/openpathsampling/engines/toy/integrators.py b/openpathsampling/engines/toy/integrators.py
index 041f1e021..0a2df4742 100644
--- a/openpathsampling/engines/toy/integrators.py
+++ b/openpathsampling/engines/toy/integrators.py
@@ -23,7 +23,7 @@ class LeapfrogVerletIntegrator(ToyIntegrator):
     dt : float
         time step
     """
-    
+
     dd = None
 
     def __init__(self, dt):
@@ -128,3 +128,44 @@ def step(self, sys):
         self._OU_update(sys, self.dt)
         self._position_update(sys, 0.5*self.dt)
         self._momentum_update(sys, 0.5*self.dt)
+
+class OverdampedLangevinIntegrator(ToyIntegrator):
+    """Over-damped Langevin integrator
+
+    Simple integrator for over-damped Langevin dynamics.
+
+    Parameters
+    ----------
+    dt : float
+        time step
+    temperature : float
+        temperature
+    D : float
+        diffusion constant
+    """
+
+    dd = None
+
+    def __init__(self, dt, temperature, D):
+        super(OverdampedLangevinIntegrator, self).__init__()
+        self.dt = dt
+        self.temperature = temperature
+        self.beta = 1.0 / temperature
+        self.D = D
+        self.A = D * dt / temperature
+        self.R = np.sqrt(2.0 * D * dt)
+
+    def _position_update(self, sys, mydt):
+        sys.positions += - self.A * np.array(sys.pes.dVdx(sys)) \
+                         + self.R * np.random.normal(size=len(sys.positions))
+
+    def step(self, sys):
+        """
+        Take an MD step. Update in-place.
+
+        Parameters
+        ----------
+        sys : :class:`.ToyEngine`
+            engine contains its state, including velocities and masses
+        """
+        self._position_update(sys, self.dt)
diff --git a/openpathsampling/engines/toy/pes.py b/openpathsampling/engines/toy/pes.py
index 5eddc6ab1..b020456e5 100644
--- a/openpathsampling/engines/toy/pes.py
+++ b/openpathsampling/engines/toy/pes.py
@@ -368,3 +368,65 @@ def dVdx(self, sys):
         """
         # this is independent of the position
         return self._local_dVdx
+
+
+class DoubleWell(PES):
+    r"""Simple double-well potential. Independent in each degree of freedom.
+
+    V(x) = \sum_i A_i * (x_i**2 - x0_i**2)**2
+
+    WARNING: Two minima only in one dimension, otherwise there are more!
+
+    Parameters
+    ----------
+    A : list of float
+        potential prefactor for in each degree of freedom.
+    x0 : list of float
+        minimum position in each degree of freedom.
+    """
+    def __init__(self, A, x0):
+        super(DoubleWell, self).__init__()
+        self.A = np.array(A)
+        self.x0 = np.array(x0)
+
+    def __repr__(self):  # pragma: no cover
+        repr_str = "DoubleWell({obj.A}, {obj.x0})"
+        return repr_str.format(obj=self)
+
+    def to_dict(self):
+        dct = super(DoubleWell, self).to_dict()
+        dct['A'] = dct['A'].tolist()
+        dct['x0'] = dct['x0'].tolist()
+        return dct
+
+    def V(self, sys):
+        """Potential energy
+
+        Parameters
+        ----------
+        sys : :class:`.ToyEngine`
+            engine contains its state, including velocities and masses
+
+        Returns
+        -------
+        float
+            the potential energy
+        """
+        dx2 = sys.positions * sys.positions - self.x0 * self.x0
+        return np.dot(self.A, dx2 * dx2)
+
+    def dVdx(self, sys):
+        """Derivative of potential energy (-force)
+
+        Parameters
+        ----------
+        sys : :class:`.ToyEngine`
+            engine contains its state, including velocities and masses
+
+        Returns
+        -------
+        np.array
+            the derivatives of the potential at this point
+        """
+        dx2 = sys.positions * sys.positions - self.x0 * self.x0
+        return 4 * self.A * sys.positions * dx2
diff --git a/openpathsampling/experimental/simstore/callable_codec.py b/openpathsampling/experimental/simstore/callable_codec.py
index f03bda7a0..d369cc66a 100644
--- a/openpathsampling/experimental/simstore/callable_codec.py
+++ b/openpathsampling/experimental/simstore/callable_codec.py
@@ -1,7 +1,10 @@
 import types
 import dis
 import dill
+import codecs
 from .serialization_helpers import has_uuid, do_import
+from openpathsampling.deprecations \
+        import SIMSTORE_CALLABLE_CODEC_STRING_NOT_BYTES
 
 GLOBALS_ERROR_MESSAGE="""
 The function you tried to save uses information from global scope, which
@@ -125,9 +128,10 @@ def default(self, obj):
             if errors:
                 raise RuntimeError("Cannot store function! \n\n" + errors)
 
+            dillbytes = dill.dumps(obj, recurse=True)
             return {
                 '__callable_name__': obj.__name__,
-                '_dilled': dill.dumps(obj)
+                '_dilled': str(codecs.encode(dillbytes, 'base64'), 'utf-8')
             }
         return obj
 
@@ -138,7 +142,16 @@ def object_hook(self, dct):
             elif '__module__' in dct:
                 func = do_import(dct['__module__'], dct['__callable_name__'])
             elif '_dilled' in dct:
-                func =  dill.loads(dct['_dilled'])
+                dilled = dct['_dilled']
+
+                if isinstance(dilled, bytes):
+                    SIMSTORE_CALLABLE_CODEC_STRING_NOT_BYTES.warn(
+                        category=FutureWarning
+                    )
+                    dilled = str(codecs.encode(dilled, 'base64'), 'utf-8')
+
+                dilledbytes = bytes(dilled, 'utf-8')  # or ascii
+                func =  dill.loads(codecs.decode(dilledbytes, 'base64'))
             else:  # pragma: no cover
                 raise RuntimeError("Error reloading ",
                                    dct['__callable_name__'])
diff --git a/openpathsampling/experimental/simstore/sql_backend.py b/openpathsampling/experimental/simstore/sql_backend.py
index 49cc8e54d..f0e3b9511 100644
--- a/openpathsampling/experimental/simstore/sql_backend.py
+++ b/openpathsampling/experimental/simstore/sql_backend.py
@@ -156,7 +156,7 @@ def __init__(self, filename, mode='r', sql_dialect='sqlite', **kwargs):
 
     def _initialize_from_engine(self, engine):
         self.engine = engine
-        self._metadata = sql.MetaData(bind=self.engine)
+        self._metadata = sql.MetaData()
         self._initialize_with_mode(self.mode)
 
     @property
@@ -298,7 +298,7 @@ def _add_table_to_tables_list(self, table_name, table_schema, cls_):
         module = cls_.__module__
         class_name = cls_.__name__
 
-        with self.engine.connect() as conn:
+        with self.engine.begin() as conn:
             conn.execute(tables.insert().values(name=table_name,
                                                 idx=n_tables,
                                                 module=module,
@@ -319,7 +319,7 @@ def _load_from_table(self, table_name, idx_list):
         with self.engine.connect() as conn:
             for block in grouper(idx_list, self.MAX_SQL_ITEMS):
                 or_stmt = sql.or_(*(table.c.idx == idx for idx in block))
-                sel = table.select(or_stmt)
+                sel = table.select().where(or_stmt)
                 results.extend(list(conn.execute(sel)))
 
         return results
@@ -390,7 +390,7 @@ def register_storable_function(self, table_name, result_type):
 
         self.metadata.create_all(self.engine)
         sfr_result_types = self.metadata.tables['sfr_result_types']
-        with self.engine.connect() as conn:
+        with self.engine.begin() as conn:
             conn.execute(sfr_result_types.insert(),
                          {'uuid': table_name, 'result_type': result_type})
         self.sfr_result_types[table_name] = result_type
@@ -424,7 +424,7 @@ def add_storable_function_results(self, table_name, result_dict):
                    for uuid in unknown_uuids]
         table = self.metadata.tables[table_name]
         if results:
-            with self.engine.connect() as conn:
+            with self.engine.begin() as conn:
                 conn.execute(table.insert(), results)
 
         # update the cache
@@ -480,7 +480,7 @@ def load_storable_function_table(self, table_name):
     def add_tag(self, table_name, name, content):
         table = self.metadata.tables[table_name]
 
-        with self.engine.connect() as conn:
+        with self.engine.begin() as conn:
             conn.execute(table.insert(), [{'name': name,
                                            'content': content}])
 
@@ -502,15 +502,15 @@ def add_to_table(self, table_name, objects):
         # this is if we don't use the UUID in the schema... but doing so
         # would be another option (redundant data, but better sanity checks)
 
-        with self.engine.connect() as conn:
+        with self.engine.begin() as conn:
             conn.execute(table.insert(), objects)
 
         res = []
         uuids = [obj['uuid'] for obj in objects]
         for uuid_block in tools.block(uuids, self.MAX_SQL_ITEMS):
-            sel_uuids_idx = sql.select([table.c.uuid, table.c.idx]).\
+            sel_uuids_idx = sql.select(table.c.uuid, table.c.idx).\
                     where(table.c.uuid.in_(uuid_block))
-            with self.engine.connect() as conn:
+            with self.engine.begin() as conn:
                 res += list(conn.execute(sel_uuids_idx))
 
         uuid_to_rows = {r[0]: r[1] for r in res}
@@ -520,7 +520,7 @@ def add_to_table(self, table_name, objects):
         uuid_insert_dicts = [{'uuid': k, 'table': table_num, 'row':v}
                              for (k, v) in uuid_to_rows.items()]
 
-        with self.engine.connect() as conn:
+        with self.engine.begin() as conn:
             # here we use executemany for performance
             conn.execute(uuid_table.insert(), uuid_insert_dicts)
 
@@ -658,7 +658,7 @@ def table_iterator(self, table_name):
 
     def table_len(self, table_name):
         table = self.metadata.tables[table_name]
-        count_query = sql.select([sql.func.count()]).select_from(table)
+        count_query = sql.select(sql.func.count()).select_from(table)
         with self.engine.connect() as conn:
             results = conn.execute(count_query)
             count_list = [r for r in results]
diff --git a/openpathsampling/experimental/simstore/storable_functions.py b/openpathsampling/experimental/simstore/storable_functions.py
index 99a957614..08743c89e 100644
--- a/openpathsampling/experimental/simstore/storable_functions.py
+++ b/openpathsampling/experimental/simstore/storable_functions.py
@@ -346,6 +346,8 @@ def to_dict(self):
             'func': self.func,  # made into JSON by CallableCodec
             'source': self.source,
             'kwargs': self.kwargs,
+            'period_min': self.period_min,
+            'period_max': self.period_max,
         }
 
     @classmethod
diff --git a/openpathsampling/experimental/simstore/storage.py b/openpathsampling/experimental/simstore/storage.py
index 493a83555..0ce522705 100644
--- a/openpathsampling/experimental/simstore/storage.py
+++ b/openpathsampling/experimental/simstore/storage.py
@@ -69,7 +69,7 @@ def __init__(self, backend, class_info, schema=None,
                                                         {})
 
         # self._pseudo_tables = {table_name: dict()
-                               # for table_name in self.simulation_classes}
+        #                        for table_name in self.simulation_classes}
         self._simulation_objects = {}
         self._pseudo_tables = {table_name: PseudoTable()
                                for table_name in self.simulation_classes}
@@ -159,8 +159,8 @@ def register_schema(self, schema, class_info_list,
         # check validity
         self.class_info.register_info(class_info_list, schema)
         # for info in class_info_list:
-            # info.set_defaults(schema)
-            # self.class_info.add_class_info(info)
+        #     info.set_defaults(schema)
+        #     self.class_info.add_class_info(info)
 
         schema_types = [type_str for attr_list in schema.values()
                         for _, type_str in attr_list]
@@ -213,7 +213,6 @@ def filter_existing_uuids(self, uuid_dict):
 
         return uuid_dict
 
-
     def _uuids_by_table(self, input_uuids, cache, get_table_name):
         # find all UUIDs we need to save with this object
         logger.debug("Listing all objects to save")
@@ -244,7 +243,6 @@ def _unproxy_lazies(self, uuid_mapping):
         uuid_mapping.update({get_uuid(obj): obj for obj in loaded})
         return uuid_mapping
 
-
     def save(self, obj_list, use_cache=True):
         if type(obj_list) is not list:
             obj_list = [obj_list]
@@ -422,7 +420,6 @@ def load(self, input_uuids, allow_lazy=True, force=False):
 
         return new_results
 
-
     def deserialize_uuids(self, ordered_uuids, uuid_to_table,
                           uuid_to_table_row, new_uuids=None):
         # TODO: remove this, replace with SerializationSchema
@@ -479,7 +476,7 @@ def _update_pseudo_tables(self, simulation_objects):
                     my_cls = cls
                     # self._pseudo_tables[key][uuid] = obj
                     # if obj.is_named:
-                        # self._pseudo_tables[key][obj.name] = obj
+                    #     self._pseudo_tables[key][obj.name] = obj
             if my_cls is None:
                 self._pseudo_tables['misc_simulation'].append(obj)
 
@@ -511,7 +508,7 @@ def __getattr__(self, attr):
         elif attr in self._pseudo_tables:
             return self._pseudo_tables[attr]
         else:
-            raise AttributeError("'{}' object has no attribute '{}'"\
+            raise AttributeError("'{}' object has no attribute '{}'"
                                  .format(self.__class__.__name__, attr))
 
 
@@ -563,6 +560,7 @@ def __len__(self):
     def __iter__(self):
         return itertools.chain(self.fixed_cache, self.cache)
 
+
 class StorageTable(abc.Sequence):
     # NOTE: currently you still need to be able to hold the whole table in
     # memory ... at least, with the SQL backend.
@@ -587,14 +585,27 @@ def __iter__(self):
 
     def __getitem__(self, item):
         len_self = len(self)
-        if not (-len_self <= item < len_self):
-            raise IndexError("table index out of range")
-
-        if item < 0:
-            item += len(self)
+        if type(item) is slice:  # Slice is not an acceptable base class
+            unpack_return = False
+            items = (i for i in range(*item.indices(len_self)))
+        elif type(item) is int:
+            unpack_return = True
+            if not (-len_self <= item < len_self):
+                raise IndexError("table index out of range")
+
+            if item < 0:
+                item += len(self)
+            items = [item]
+        else:
+            raise TypeError("Only access via slice or int allowed, "
+                            f"got type {type(item).__name__}.")
 
-        row = self.storage.backend.table_get_item(self.table, item)
-        return self.storage.load([row.uuid])[0]
+        uuids = [self.storage.backend.table_get_item(self.table, item).uuid
+                 for item in items]
+        if unpack_return:
+            return self.storage.load(uuids)[0]
+        else:
+            return self.storage.load(uuids)
 
     def __len__(self):
         return self.storage.backend.table_len(self.table)
@@ -603,6 +614,7 @@ def cache_all(self):
         old_blocksize = self.iter_block_size
         self.iter_block_size = len(self)
         _ = list(iter(self))
+        self.iter_block_size = old_blocksize
 
     def save(self, obj):
         # this is to match with the netcdfplus API
@@ -612,6 +624,7 @@ def save(self, obj):
     # things in the order of the mccycle number -- also, manage special
     # caching
 
+
 class PseudoTable(abc.MutableSequence):
     # TODO: use this in the main code
     # NOTE: This will require that the storage class append to it
@@ -647,7 +660,9 @@ def get_by_uuid(self, uuid):
     def __getitem__(self, item):
         try:
             ret_val = self._sequence[item]
-        except TypeError:
+        except TypeError as err:
+            if type(item) != str:
+                raise err
             uuid = self._name_to_uuid[item]
             ret_val = self.get_by_uuid(uuid)
         return ret_val
diff --git a/openpathsampling/experimental/storage/monkey_patches.py b/openpathsampling/experimental/storage/monkey_patches.py
index eba7bdd89..e9a95741a 100644
--- a/openpathsampling/experimental/storage/monkey_patches.py
+++ b/openpathsampling/experimental/storage/monkey_patches.py
@@ -57,7 +57,21 @@ def inner(cls, dct):
     ]
 }
 
+_UNPATCH_MODULES = [
+    paths.netcdfplus,
+    paths.collectivevariable,
+    paths.collectivevariables,
+    paths
+]
+
+_IS_PATCHED_SAVING = False
+_IS_PATCHED_LOADING = False
+
 def monkey_patch_saving(paths):
+    global _IS_PATCHED_SAVING
+    if _IS_PATCHED_SAVING:
+        return paths
+
     paths.netcdfplus.FunctionPseudoAttribute.to_dict = \
             function_pseudo_attribute_to_dict
     paths.TPSNetwork.to_dict = tuple_keys_to_dict(
@@ -66,9 +80,14 @@ def monkey_patch_saving(paths):
     paths.MISTISNetwork.to_dict = tuple_keys_to_dict(
         paths.MISTISNetwork.to_dict, 'input_transitions'
     )
+    _IS_PATCHED_SAVING = True
     return paths
 
 def monkey_patch_loading(paths):
+    global _IS_PATCHED_LOADING
+    if _IS_PATCHED_LOADING:
+        return paths
+
     paths.CallableCV.from_dict = classmethod(callable_cv_from_dict)
     paths.netcdfplus.FunctionPseudoAttribute.from_dict = \
             classmethod(from_dict_attr_to_class(
@@ -82,6 +101,7 @@ def monkey_patch_loading(paths):
     paths.MISTISNetwork.from_dict = classmethod(tuple_keys_from_dict(
         paths.MISTISNetwork.from_dict, 'input_transitions'
     ))
+    _IS_PATCHED_LOADING = True
     return paths
 
 def monkey_patch_all(paths):
@@ -93,4 +113,15 @@ def unpatch(paths):
     for cls, old in _PREPATCH.items():
         cls.to_dict = old['to']
         cls.from_dict = old['from']
+
+    # we loop over the modules twice as quick-and-dirty solution to avoid
+    # figuring the DAG topological order for imports (should actually in
+    # order leaf to root)
+    for module in _UNPATCH_MODULES * 2:
+        importlib.reload(module)
+
+    global _IS_PATCHED_SAVING
+    global _IS_PATCHED_LOADING
+    _IS_PATCHED_SAVING = False
+    _IS_PATCHED_LOADING = False
     return paths
diff --git a/openpathsampling/experimental/storage/ops_storage.py b/openpathsampling/experimental/storage/ops_storage.py
index f3e07d2c0..351992e6d 100644
--- a/openpathsampling/experimental/storage/ops_storage.py
+++ b/openpathsampling/experimental/storage/ops_storage.py
@@ -47,6 +47,9 @@
     simtk_quantity_codec = None
     SimtkQuantityHandler = None
 
+
+from .mdtraj_json import mdtraj_codecs
+
 import logging
 logger = logging.getLogger(__name__)
 
@@ -77,6 +80,7 @@
 
 # this defines the simulation object serializer for OPS
 EXTRA_CODECS = [simtk_quantity_codec] if simtk_quantity_codec else []
+EXTRA_CODECS += mdtraj_codecs
 EXTRA_HANDLERS = [SimtkQuantityHandler] if SimtkQuantityHandler else []
 CODECS = DEFAULT_CODECS + EXTRA_CODECS
 HANDLERS = DEFAULT_HANDLERS + EXTRA_HANDLERS
diff --git a/openpathsampling/integration_tools.py b/openpathsampling/integration_tools.py
index 5bbdc2740..76fe6ab40 100644
--- a/openpathsampling/integration_tools.py
+++ b/openpathsampling/integration_tools.py
@@ -44,7 +44,7 @@ def _chain_import(*packages):
     HAS_SIMTK_UNIT = True
 
 def error_if_no_simtk_unit(name):
-    return error_if_no(name, "simtk.unit", HAS_SIMTK_UNIT)
+    return error_if_no(name, "openmm.unit or simtk.unit", HAS_SIMTK_UNIT)
 
 # mdtraj ############################################################
 try:
diff --git a/openpathsampling/movechange.py b/openpathsampling/movechange.py
index 3c4c2ce49..8596d332e 100644
--- a/openpathsampling/movechange.py
+++ b/openpathsampling/movechange.py
@@ -489,7 +489,7 @@ def __str__(self):
 
 class NonCanonicalConditionalSequentialMoveChange(
                                             ConditionalSequentialMoveChange):
-    """ Special move change for reactive flux simulation.
+    """ Special move change for reactive flux and S-shooting simulation.
 
     This move change inherits from :class:`.ConditionalSequentialMoveChange`
     and returns the outcome of the last subchange.
diff --git a/openpathsampling/netcdfplus/base.py b/openpathsampling/netcdfplus/base.py
index 4ac06ba22..44f02d0c9 100644
--- a/openpathsampling/netcdfplus/base.py
+++ b/openpathsampling/netcdfplus/base.py
@@ -7,6 +7,7 @@
 import sys
 if sys.version_info > (3, ):
     long = int
+    unicode = str
 
 logger = logging.getLogger(__name__)
 
@@ -413,12 +414,15 @@ def name(self, name):
                 'Objects cannot be renamed to `%s` after is has been saved, '
                 'it is already named `%s`') %
                 (name, self._name))
-        else:
-            if name != self._name:
-                self._name = name
-                logger.debug(
-                    'Nameable object is renamed from `%s` to `%s`' %
-                    (self._name, name))
+
+        if not isinstance(name, (unicode, str)):
+            raise TypeError("Invalid name type: " + type(name).__name__)
+
+        if name != self._name:
+            self._name = name
+            logger.debug(
+                'Nameable object is renamed from `%s` to `%s`' %
+                (self._name, name))
 
     @property
     def is_named(self):
diff --git a/openpathsampling/netcdfplus/dictify.py b/openpathsampling/netcdfplus/dictify.py
index 613bf8bab..a0927ac34 100644
--- a/openpathsampling/netcdfplus/dictify.py
+++ b/openpathsampling/netcdfplus/dictify.py
@@ -40,6 +40,18 @@
     get_callable_name = lambda c: c.func_name
     import builtins
 
+def make_callable_codec(safemode):
+    # starting in Python 3.11, we use SimStore to serialize callables
+    from openpathsampling.experimental.simstore.callable_codec \
+            import CallableCodec
+    only_req = ObjectJSON.prevent_unsafe_modules
+    codec = CallableCodec(settings={
+        'only_allow_required_modules': only_req,
+        'required_modules': ObjectJSON.safe_modules,
+        'safemode': safemode,
+    })
+    return codec
+
 # in Python 3.6 the opcodes have changed width
 if sys.version_info > (3, 6):
     opcode_arg_width = 1
@@ -53,6 +65,7 @@
 else:
     ujson_kwargs = {"reject_bytes": False}
 
+
 class ObjectJSON(object):
     """
     A simple implementation of a pickle algorithm to create object that can be
@@ -294,7 +307,7 @@ def build(self, obj):
                 else:
                     return None
 
-            elif '_marshal' in obj or '_module' in obj:
+            elif '_marshal' in obj or '_module' in obj or '_dilled' in obj:
                 if self.safemode:
                     return None
 
@@ -366,6 +379,11 @@ def callable_to_dict(c):
 
         # if the easy way did not work, try saving it using bytecode
         if ObjectJSON.allow_marshal and callable(c):
+            if sys.version_info > (3, 11):
+                codec = make_callable_codec(safemode=False)
+                return codec.default(c)
+
+            # back to normal for older Python versions
             # use marshal
             global_vars = ObjectJSON._find_var(c, opcode.opmap['LOAD_GLOBAL'])
             import_vars = ObjectJSON._find_var(c, opcode.opmap['IMPORT_NAME'])
@@ -481,6 +499,12 @@ def callable_from_dict(c_dict):
                     imp = importlib.import_module(module)
                     c = getattr(imp, c_dict['_name'])
 
+            elif '_dilled' in c_dict:
+                # safemode has to get handled by original netcdfplus, since
+                # this is a staticmethod
+                codec = make_callable_codec(safemode=False)
+                c = codec.object_hook(c_dict)
+
         return c
 
     @staticmethod
diff --git a/openpathsampling/netcdfplus/version.py b/openpathsampling/netcdfplus/version.py
index fd23c8215..6487150f7 100644
--- a/openpathsampling/netcdfplus/version.py
+++ b/openpathsampling/netcdfplus/version.py
@@ -1,4 +1,4 @@
-short_version = '1.5.2'
+short_version = '1.6.0'
 version = short_version
 full_version = short_version
 git_revision = 'alpha'
diff --git a/openpathsampling/pathmover.py b/openpathsampling/pathmover.py
index a86370188..1d7ce7687 100644
--- a/openpathsampling/pathmover.py
+++ b/openpathsampling/pathmover.py
@@ -17,7 +17,8 @@
 from .treelogic import TreeMixin
 
 from openpathsampling.deprecations import deprecate, has_deprecations
-from openpathsampling.deprecations import SAMPLE_DETAILS, MOVE_DETAILS
+from openpathsampling.deprecations import (SAMPLE_DETAILS, MOVE_DETAILS,
+                                           NEW_SNAPSHOT_KWARG_SELECTOR)
 
 from future.utils import with_metaclass
 
@@ -25,8 +26,9 @@
 init_log = logging.getLogger('openpathsampling.initialization')
 
 
-# TODO: Remove if really not used anymore otherwise might move to utils or tools
-def make_list_of_pairs(l):
+# TODO: Remove if really not used anymore
+# otherwise might move to utils or tools
+def make_list_of_pairs(inlist):
     """
     Converts input from several possible formats into a list of pairs: used
     to clean input for swap-like moves.
@@ -40,34 +42,37 @@ def make_list_of_pairs(l):
 
     Parameters
     ----------
-    l : list
+    inlist : list
         input list, either flat list of length 2N, a list of pairs or None
 
     Returns
     -------
     list of pairs
     """
-    if l is None:
+    if inlist is None:
         return None
 
-    _ = len(l)  # raises TypeError, avoids everything else
+    _ = len(inlist)  # raises TypeError, avoids everything else
 
     # based on first element, decide whether this should be a list of lists
     # or a flat list
     try:
-        _ = len(l[0])
+        _ = len(inlist[0])
         list_of_lists = True
     except TypeError:
         list_of_lists = False
 
     if list_of_lists:
-        for elem in l:
+        for elem in inlist:
             assert len(elem) == 2, "List of lists: inner list length != 2"
-        outlist = l
+        outlist = inlist
     else:
-        assert len(l) % 2 == 0, "Flattened list: length not divisible by 2"
+        assert len(inlist) % 2 == 0, \
+            "Flattened list: length not divisible by 2"
         outlist = [
-            [a, b] for (a, b) in zip(l[slice(0, None, 2)], l[slice(1, None, 2)])
+            [a, b] for (a, b) in zip(
+                inlist[slice(0, None, 2)],
+                inlist[slice(1, None, 2)])
         ]
     # Note that one thing we don't check is whether the items are of the
     # same type. That might be worth doing someday; for now, we trust that
@@ -128,7 +133,7 @@ class PathMover(with_metaclass(abc.ABCMeta, TreeMixin, StorableNamedObject)):
     in the PathMover, but have it be a separate class ~~~DWHS
     """
 
-    #__metaclass__ = abc.ABCMeta
+    # __metaclass__ = abc.ABCMeta
 
     def __init__(self):
         StorableNamedObject.__init__(self)
@@ -228,11 +233,10 @@ def _generate_in_out(self):
             return {
                 InOutSet([])
             }
-        elif len(self.input_ensembles) == 1 and len(self.output_ensembles) == 1:
-            return InOutSet([
-                InOut(
-                    [((self.input_ensembles[0], self.output_ensembles[0], 0), 1)]
-                )])
+        elif (len(self.input_ensembles) == len(self.output_ensembles) == 1):
+            in_ens = self.input_ensembles[0]
+            out_ens = self.output_ensembles[0]
+            return InOutSet([InOut([((in_ens, out_ens, 0), 1)])])
         else:
             # Fallback could be all possibilities, but for now we ask the user!
             raise NotImplementedError(
@@ -699,8 +703,8 @@ def __call__(self, *args):
     def _accept(self, trials):
         """Function to determine the acceptance of a trial
 
-        Defaults to calling the Metropolis acceptance criterion for all returned
-        trial samples. Means all samples most be valid and accepted.
+        Defaults to calling the Metropolis acceptance criterion for all
+        returned trial samples. Means all samples most be valid and accepted.
         """
         return self.metropolis(trials)
 
@@ -741,12 +745,14 @@ class EngineMover(SampleMover):
     # this will store the engine attribute for all subclasses as well
     _included_attr = ['_engine']
 
-    def __init__(self, ensemble, target_ensemble, selector, engine=None):
+    def __init__(self, ensemble, target_ensemble, selector, engine=None,
+                 modifier=None):
         super(EngineMover, self).__init__()
         self.selector = selector
         self.ensemble = ensemble
         self.target_ensemble = target_ensemble
         self._engine = engine
+        self.modifier = modifier or paths.NoModification(as_copy=False)
         self._trust_candidate = True  # can I safely do that?
         # I think that is safe. Note for future: if we come across a bug
         # based on this, an alternative would be to have the move strategy
@@ -798,11 +804,13 @@ def __call__(self, input_sample):
                 input_sample, shooting_index, e.last_trajectory, 'max_length')
 
             if EngineMover.reject_max_length:
-                raise SampleMaxLengthError('Sample with MaxLength', trial, details)
+                raise SampleMaxLengthError('Sample with MaxLength', trial,
+                                           details)
 
         else:
             trial, details = self._build_sample(
-                input_sample, shooting_index, trial_trajectory)
+                input_sample, shooting_index, trial_trajectory,
+                run_details=run_details)
 
         trials = [trial]
         details.update(run_details)
@@ -814,17 +822,35 @@ def _build_sample(
             input_sample,
             shooting_index,
             trial_trajectory,
-            stopping_reason=None
+            stopping_reason=None,
+            run_details={}
             ):
-
+        # TODO OPS 2.0: the passing of run_details is a hack and should be
+        # properly refactored
         initial_trajectory = input_sample.trajectory
 
         if stopping_reason is None:
-            bias = self.selector.probability_ratio(
-                initial_trajectory[shooting_index],
-                initial_trajectory,
-                trial_trajectory
-            )
+            bias = 1.0
+            old_snapshot = initial_trajectory[shooting_index]
+            new_snapshot = run_details.get("modified_shooting_snapshot",
+                                           old_snapshot)
+            # Selector bias
+            try:
+                bias *= self.selector.probability_ratio(
+                    initial_trajectory[shooting_index],
+                    initial_trajectory,
+                    trial_trajectory,
+                    new_snapshot=new_snapshot
+                )
+            except TypeError:
+                bias *= self.selector.probability_ratio(
+                    initial_trajectory[shooting_index],
+                    initial_trajectory,
+                    trial_trajectory)
+                NEW_SNAPSHOT_KWARG_SELECTOR.warn()
+
+            # Modifier bias
+            bias *= self.modifier.probability_ratio(old_snapshot, new_snapshot)
         else:
             bias = 0.0
 
@@ -857,7 +883,7 @@ def _make_forward_trajectory(self, trajectory, shooting_index):
         initial_snapshot = trajectory[shooting_index]  # .copy()
         run_f = paths.PrefixTrajectoryEnsemble(self.target_ensemble,
                                                trajectory[0:shooting_index]
-                                              ).can_append
+                                               ).can_append
         partial_trajectory = self.engine.generate(initial_snapshot,
                                                   running=[run_f])
         trial_trajectory = (trajectory[0:shooting_index] +
@@ -870,7 +896,7 @@ def _make_backward_trajectory(self, trajectory, shooting_index):
         initial_snapshot = trajectory[shooting_index].reversed  # _copy()
         run_f = paths.SuffixTrajectoryEnsemble(self.target_ensemble,
                                                trajectory[shooting_index + 1:]
-                                              ).can_prepend
+                                               ).can_prepend
         partial_trajectory = self.engine.generate(initial_snapshot,
                                                   running=[run_f])
         trial_trajectory = (partial_trajectory.reversed +
@@ -1027,12 +1053,13 @@ def _get_out_ensembles(self):
         return [self.ensemble1, self.ensemble2]
 
     def _generate_in_out(self):
+        ens1 = self.ensemble1
+        ens2 = self.ensemble2
+        move_type = 1 * InOut._use_move_type
         return InOutSet([
-            InOut([
-                ((self.ensemble1, self.ensemble2, 1 * InOut._use_move_type), 1),
-                ((self.ensemble2, self.ensemble1, 1 * InOut._use_move_type), 1)
+            InOut([((ens1, ens2, move_type), 1),
+                   ((ens2, ens1, move_type), 1)])
             ])
-        ])
 
     def __call__(self, sample1, sample2):
         # convert sample to the language used here before
@@ -1111,12 +1138,13 @@ def _get_out_ensembles(self):
         return [self.ensemble1, self.ensemble2]
 
     def _generate_in_out(self):
+        ens1 = self.ensemble1
+        ens2 = self.ensemble2
+        move_type = -1 * InOut._use_move_type
         return InOutSet([
-            InOut([
-                ((self.ensemble1, self.ensemble2, -1 * InOut._use_move_type), 1),
-                ((self.ensemble2, self.ensemble1, -1 * InOut._use_move_type), 1)
+            InOut([((ens1, ens2, move_type), 1),
+                   ((ens2, ens1, move_type), 1)])
             ])
-        ])
 
     def __call__(self, sample1, sample2):
         # convert sample to the language used here before
@@ -1444,16 +1472,18 @@ def __call__(self, rep_sample):
             replica = self.change_replica
 
         logger.info(
-            "Attempting ensemble hop from {e1} to {e2} replica ID {rid}".format(
+            "Attempting ensemble hop from {e1} to {e2} "
+            "replica ID {rid}".format(
                 e1=repr(ens_from), e2=repr(ens_to), rid=repr(replica)))
 
         trajectory = rep_sample.trajectory
         logger.debug("  selected replica: " + str(replica))
         logger.debug("  initial ensemble: " + repr(rep_sample.ensemble))
 
-        logger.info("Hop starts from legal ensemble: "+str(ens_from(trajectory)))
-        logger.info("Hop ends in legal ensemble: "+str(ens_to(trajectory)))
-
+        logger.info("Hop starts from legal ensemble: " +
+                    str(ens_from(trajectory)))
+        logger.info("Hop ends in legal ensemble: " +
+                    str(ens_to(trajectory)))
 
         # TODO: remove this and generalize!!!
         if type(self.bias) is float:
@@ -1917,9 +1947,10 @@ def move(self, sample_set):
         movechanges = []
         for mover in self.movers:
             logger.info(str(self.name)
-                        + " starting mover index " + str(self.movers.index(mover))
+                        + " starting mover index "
+                        + str(self.movers.index(mover))
                         + " (" + mover.name + ")"
-                       )
+                        )
             # Run the sub mover
             movepath = mover.move(subglobal)
             samples = movepath.results
@@ -1948,7 +1979,9 @@ class ConditionalSequentialMover(SequentialMover):
     """
 
     def _generate_in_out(self):
-        return InOutSet(sum([sub.in_out for sub in self.submovers], InOutSet()))
+        return InOutSet(
+            sum([sub.in_out for sub in self.submovers], InOutSet())
+        )
 
     def move(self, sample_set):
         logger.debug("Starting conditional sequential move")
@@ -1973,7 +2006,7 @@ def move(self, sample_set):
 
 
 class NonCanonicalConditionalSequentialMover(ConditionalSequentialMover):
-    """ Special mover for reactive flux simulation.
+    """ Special mover for reactive flux and S-shooting simulation.
 
     This mover inherits from :class:`.ConditionalSequentialMover` and
     alters only the `move` method to return the output of the corresponding
@@ -2107,8 +2140,8 @@ def __init__(self, mover, ensembles):
         self.ensembles = ensembles
 
         if not set(self.mover.output_ensembles) & set(self.ensembles):
-            # little sanity check, if the underlying move will be removed by the
-            # filter throw a warning
+            # little sanity check, if the underlying move will be removed by
+            # the filter throw a warning
             raise ValueError(
                 'Your filter removes the underlying move completely. ' +
                 'Please check your ensembles and submovers!')
@@ -2171,6 +2204,7 @@ def move_core(self, samples):
         )
         return change
 
+
 class OneWayShootingMover(SpecializedRandomChoiceMover):
     """
     One-way (stochastic) shooting mover
@@ -2255,9 +2289,10 @@ def __init__(self, ensemble, selector, modifier, engine=None):
             ensemble=ensemble,
             target_ensemble=ensemble,
             selector=selector,
-            engine=engine
+            engine=engine,
+            modifier=modifier
         )
-        self.modifier = modifier
+        # TODO OPS 2.0: This init signature should be aligned with EngineMover
 
     # required for concrete class; not really used
     @property
@@ -2289,6 +2324,7 @@ def _run(self, trajectory, shooting_index):
         # to override the default implementation in EngineMover
         raise NotImplementedError
 
+
 class ForwardFirstTwoWayShootingMover(AbstractTwoWayShootingMover):
     def _run(self, trajectory, shooting_index):
         """
@@ -2306,7 +2342,8 @@ def _run(self, trajectory, shooting_index):
         trial_trajectory : :class:`.Trajectory`
             the resulting trial trajectory
         details : dict
-            details dictionary (includes modified shooting point)
+            details dictionary (includes modified shooting point and
+            modification bias)
         """
         shoot_str = "Running {sh_dir} from frame {fnum} in [0:{maxt}]"
         logger.info(shoot_str.format(
@@ -2316,6 +2353,7 @@ def _run(self, trajectory, shooting_index):
         ))
 
         original = trajectory[shooting_index]
+        # TODO OPS 2.0: Modification+bias should be done in engine mover
         modified = self.modifier(original)
 
         fwd_partial = self._make_forward_trajectory(trajectory, modified,
@@ -2350,7 +2388,8 @@ def _run(self, trajectory, shooting_index):
         trial_trajectory : :class:`.Trajectory`
             the resulting trial trajectory
         details : dict
-            details dictionary (includes modified shooting point)
+            details dictionary (includes modified shooting point and
+            modification bias)
         """
         shoot_str = "Running {sh_dir} from frame {fnum} in [0:{maxt}]"
         logger.info(shoot_str.format(
@@ -2360,25 +2399,26 @@ def _run(self, trajectory, shooting_index):
         ))
 
         original = trajectory[shooting_index]
+        # TODO OPS 2.0: Modification+bias should be done in engine mover
         modified = self.modifier(original)
 
         bkwd_partial = self._make_backward_trajectory(trajectory, modified,
                                                       shooting_index)
-        #logger.info("Complete backward shot (length " +
-                    #str(len(bkwd_partial)) + ")")
+        # logger.info("Complete backward shot (length " +
+        #             str(len(bkwd_partial)) + ")")
         # TODO: come up with a test that shows why you need mid_traj here;
         # should be a SeqEns with OptionalEnsembles. Exact example is hard!
         mid_traj = bkwd_partial.reversed + trajectory[shooting_index + 1:]
         mid_traj_shoot_idx = len(bkwd_partial) - 1
         fwd_partial = self._make_forward_trajectory(mid_traj, modified,
                                                     mid_traj_shoot_idx)
-        #logger.info("Complete forward shot (length " +
-                    #str(len(fwd_partial)) + ")")
+        # logger.info("Complete forward shot (length " +
+        #             str(len(fwd_partial)) + ")")
 
         # join the two
         trial_trajectory = bkwd_partial.reversed + fwd_partial[1:]
-
         details = {'modified_shooting_snapshot': modified}
+
         return trial_trajectory, details
 
 
@@ -2506,6 +2546,7 @@ def move(self, sample_set):
 
         return change
 
+
 class SingleReplicaMinusMover(MinusMover):
     """
     Minus mover for single replica TIS.
diff --git a/openpathsampling/pathmovers/spring_shooting.py b/openpathsampling/pathmovers/spring_shooting.py
index 17fb21637..e9be10eab 100644
--- a/openpathsampling/pathmovers/spring_shooting.py
+++ b/openpathsampling/pathmovers/spring_shooting.py
@@ -3,6 +3,7 @@
 from openpathsampling.high_level.move_strategy import levels
 from openpathsampling.pathmover import SampleNaNError, SampleMaxLengthError
 import openpathsampling.high_level.move_strategy as move_strategy
+from openpathsampling.deprecations import NEW_SNAPSHOT_SELECTOR
 from functools import reduce
 
 
@@ -171,7 +172,7 @@ def sum_spring_bias(self, delta_max, k_spring):
             return sum(self._spring_biases(delta_max, k_spring))
 
     def probability_ratio(self, snapshot, initial_trajectory,
-                          trial_trajectory):
+                          trial_trajectory, new_snapshot=None):
         """
         Returns the acceptance probability of a trial trajectory, this is 1.0
         as long as a snapshot has been selected that is inside of the
@@ -192,6 +193,8 @@ def probability_ratio(self, snapshot, initial_trajectory,
                 1.0 if anacceptable snapshot has been chosen 0.0 otherwise
         """
         # Check if an acceptable snapshot was selected
+        if new_snapshot is None:
+            NEW_SNAPSHOT_SELECTOR.warn(stacklevel=3)
         if self._acceptable_snapshot:
             return 1.0
         else:
diff --git a/openpathsampling/pathsimulators/__init__.py b/openpathsampling/pathsimulators/__init__.py
index e2061a746..900d8d755 100644
--- a/openpathsampling/pathsimulators/__init__.py
+++ b/openpathsampling/pathsimulators/__init__.py
@@ -6,4 +6,4 @@
     ShootFromSnapshotsSimulation, CommittorSimulation
 )
 from .reactive_flux import ReactiveFluxSimulation
-
+from .sshooting_simulator import SShootingSimulation
\ No newline at end of file
diff --git a/openpathsampling/pathsimulators/sshooting_simulator.py b/openpathsampling/pathsimulators/sshooting_simulator.py
new file mode 100644
index 000000000..a0458ae9c
--- /dev/null
+++ b/openpathsampling/pathsimulators/sshooting_simulator.py
@@ -0,0 +1,95 @@
+import logging
+import openpathsampling as paths
+
+logger = logging.getLogger(__name__)
+from .shoot_snapshots import ShootFromSnapshotsSimulation
+
+class SShootingSimulation(ShootFromSnapshotsSimulation):
+    """ S-Shooting simulations.
+
+    Parameters
+    ----------
+    storage : :class:`.Storage`
+        the file to store simulations in
+    engine : :class:`.DynamicsEngine`
+        the dynamics engine to use to run the simulation
+    state_S : :class:`.Volume`
+        the volume representing saddle region S.
+    randomizer : :class:`.SnapshotModifier`
+        the method used to modify the input snapshot before each shot
+    initial_snapshots : list of :class:`.Snapshot`
+        initial snapshots to use.
+    trajectory_length : int
+        Trajectory length l of backward/forward shot, total length of generated
+        trajectories is 2*l+1. The harvested subtrajectories have length l+1.
+    """
+    def __init__(self, storage, engine=None, state_S=None, randomizer=None,
+                 initial_snapshots=None, trajectory_length=None):
+
+        # Defintion of state S (A and B are only required for analysis).
+        self.state_S = state_S
+
+        # Set forward/backward shot length.
+        self.trajectory_length = trajectory_length
+        l = self.trajectory_length
+
+        # Define backward ensemble:
+        # trajectory starts in S and has fixed length l.
+        backward_ensemble = paths.SequentialEnsemble([
+            paths.LengthEnsemble(l),
+            paths.AllInXEnsemble(state_S) & paths.LengthEnsemble(1)
+        ])
+
+        # Define forward ensemble:
+        # CAUTION: first trajectory is in backward ensemble,
+        # then continues with fixed length l.
+        forward_ensemble = paths.SequentialEnsemble([
+            paths.LengthEnsemble(l),
+            paths.AllInXEnsemble(state_S) & paths.LengthEnsemble(1),
+            paths.LengthEnsemble(l)
+        ])
+
+        super(SShootingSimulation, self).__init__(
+            storage=storage,
+            engine=engine,
+            starting_volume=state_S,
+            forward_ensemble=forward_ensemble,
+            backward_ensemble=backward_ensemble,
+            randomizer=randomizer,
+            initial_snapshots=initial_snapshots
+        )
+
+        # Create backward mover (starting from single point).
+        self.backward_mover = paths.BackwardExtendMover(
+            ensemble=self.starting_ensemble,
+            target_ensemble=self.backward_ensemble
+        )
+
+        # Create forward mover (starting from the backward ensemble).
+        self.forward_mover = paths.ForwardExtendMover(
+            ensemble=self.backward_ensemble,
+            target_ensemble=self.forward_ensemble
+        )
+
+        # Create mover combining forward and backward shooting. No condition
+        # here, shots in both directions are executed in any case.
+        self.mover = paths.NonCanonicalConditionalSequentialMover([
+            self.backward_mover,
+            self.forward_mover
+        ])
+
+    def to_dict(self):
+        ret_dict = {
+            'state_S' : self.state_S,
+            'trajectory_length' : self.trajectory_length
+        }
+        return ret_dict
+
+    @classmethod
+    def from_dict(cls, dct):
+        sshooting = cls.__new__(cls)
+
+        # replace automatically created attributes with stored ones
+        sshooting.state_S = dct['state_S']
+        sshooting.trajectory_length = dct['trajectory_length']
+        return sshooting
diff --git a/openpathsampling/shooting.py b/openpathsampling/shooting.py
index 631a0810f..a4df2d0fa 100644
--- a/openpathsampling/shooting.py
+++ b/openpathsampling/shooting.py
@@ -1,10 +1,9 @@
 import math
 import logging
 
-import numpy as np
-
 from openpathsampling.netcdfplus import StorableNamedObject
 from openpathsampling import default_rng
+from openpathsampling.deprecations import NEW_SNAPSHOT_SELECTOR
 
 logger = logging.getLogger(__name__)
 init_log = logging.getLogger('openpathsampling.initialization')
@@ -34,9 +33,15 @@ def probability(self, snapshot, trajectory):
         else:
             return 0.0
 
-    def probability_ratio(self, snapshot, old_trajectory, new_trajectory):
+    def probability_ratio(self, snapshot, old_trajectory,
+                          new_trajectory, new_snapshot=None):
+        # TODO OPS 2.0: We should probabily alter the order and keyword names
+        # of this call
+        if new_snapshot is None:
+            NEW_SNAPSHOT_SELECTOR.warn(stacklevel=3)
+        new_snapshot = new_snapshot or snapshot
         p_old = self.probability(snapshot, old_trajectory)
-        p_new = self.probability(snapshot, new_trajectory)
+        p_new = self.probability(new_snapshot, new_trajectory)
         return p_new / p_old
 
     def _biases(self, trajectory):
@@ -80,7 +85,7 @@ def pick(self, trajectory):
         rand = self._rng.random() * sum_bias
         idx = 0
         prob = prob_list[0]
-        while prob <= rand and idx < len(prob_list):
+        while prob <= rand and idx < len(prob_list) - 1:
             idx += 1
             prob += prob_list[idx]
 
@@ -171,7 +176,7 @@ def sum_bias(self, trajectory):
 
     def pick(self, trajectory):
         idx = self._rng.integers(self.pad_start,
-                                len(trajectory) - self.pad_end)
+                                 len(trajectory) - self.pad_end)
         return idx
 
 
@@ -229,8 +234,13 @@ def pick(self, trajectory):
     def probability(self, snapshot, trajectory):  # pragma: no cover
         return 1.0  # there's only one choice
 
-    def probability_ratio(self, snapshot, old_trajectory, new_trajectory):
+    def probability_ratio(self, snapshot, old_trajectory,
+                          new_trajectory, new_snapshot=None):
+        # TODO OPS 2.0: alter the order + keywords in the call
         # must be matched by a final-frame selector somewhere
+
+        if new_snapshot is None:
+            NEW_SNAPSHOT_SELECTOR.warn(stacklevel=3)
         return 1.0
 
 
@@ -253,6 +263,11 @@ def pick(self, trajectory):
     def probability(self, snapshot, trajectory):  # pragma: no cover
         return 1.0  # there's only one choice
 
-    def probability_ratio(self, snapshot, old_trajectory, new_trajectory):
+    def probability_ratio(self, snapshot, old_trajectory,
+                          new_trajectory, new_snapshot=None):
+        # TODO OPS 2.0: alter the order + keywords in the call
         # must be matched by a first-frame selector somewhere
+
+        if new_snapshot is None:
+            NEW_SNAPSHOT_SELECTOR.warn(stacklevel=3)
         return 1.0
diff --git a/openpathsampling/snapshot_modifier.py b/openpathsampling/snapshot_modifier.py
index 4f28b5cef..7c195444a 100644
--- a/openpathsampling/snapshot_modifier.py
+++ b/openpathsampling/snapshot_modifier.py
@@ -1,4 +1,3 @@
-import random
 import logging
 import copy
 import abc
@@ -6,8 +5,8 @@
 
 import numpy as np
 
-import openpathsampling as paths
-from openpathsampling.netcdfplus import StorableNamedObject, StorableObject
+from openpathsampling.netcdfplus import StorableNamedObject
+from openpathsampling.deprecations import SNAPSHOTMODIFIER_PROB_RAT
 
 logger = logging.getLogger(__name__)
 
@@ -108,10 +107,33 @@ def apply_to_subset(self, full_array, modified, subset_mask=None):
     def __call__(self, snapshot):
         raise NotImplementedError
 
+    def probability_ratio(self, old_snapshot, new_snapshot):
+        """Only here for backwards compatability, will raise in the future"""
+        SNAPSHOTMODIFIER_PROB_RAT.warn(stacklevel=3)
+        return 1.0
+
+
 class NoModification(SnapshotModifier):
-    """Modifier with no change: returns a copy of the snapshot."""
+    """Modifier with no change: "
+
+    Parameters
+    ----------
+    as_copy : bool, default True
+        if True calls return a copy of the snapshot, else the snapshot itself.
+    """
+    def __init__(self, as_copy=True):
+        # masking is nonsense for no modification, but used in testing so this
+        # is here to conserve API
+        super(NoModification, self).__init__(subset_mask=None)
+        self.as_copy = as_copy
+
     def __call__(self, snapshot):
-        return snapshot.copy()
+        return snapshot.copy() if self.as_copy else snapshot
+
+    def probability_ratio(self, old_snapshot, new_snapshot):
+        """This modifier does not alter the snapshot, so equal probability."""
+        return 1.0
+
 
 class RandomVelocities(SnapshotModifier):
     """Randomize velocities according to the Boltzmann distribution.
@@ -213,6 +235,11 @@ def __call__(self, snapshot):
         new_snap = make_snapshot(snapshot)
         return new_snap
 
+    def probability_ratio(self, old_snapshot, new_snapshot):
+        # Should always be 1.0 becasue it is directly sampled from the right
+        # distribution
+        return 1.0
+
 
 class GeneralizedDirectionModifier(SnapshotModifier):
     """
@@ -275,11 +302,6 @@ def _verify_snapshot(self, snapshot):
                 raise RuntimeError("Cannot use this snapshot modifier with "
                                    "an engine that has constraints.")
 
-        try:
-            box_vectors = snapshot.box_vectors
-        except AttributeError:
-            box_vectors = None
-
         try:
             n_dofs = snapshot.n_degrees_of_freedom
         except AttributeError:
@@ -293,6 +315,11 @@ def _verify_snapshot(self, snapshot):
         # NOTE: none of our engines currently explicitly remove angular
         # (but isn't angular momentum impossible in periodic condensed
         # phase)
+        # try:
+        #     box_vectors = snapshot.box_vectors
+        # except AttributeError:
+        #     box_vectors = None
+
         remove_angular = 0  # if box_vectors is None else 1
         remove_linear = 1 if n_atoms != 1 else 0
         if remove_linear:
@@ -463,6 +490,11 @@ def __call__(self, snapshot):
         # NOTE: no constraint correction here! constraints are not allowed!
         return new_snap
 
+    def probability_ratio(self, old_snapshot, new_snapshot):
+        # No constraints allowed, so this should always be 1.0
+        return 1.0
+
+
 class VelocityDirectionModifier(GeneralizedDirectionModifier):
     """
     Randomly change all the velocities (in the subset masked, at least)
@@ -492,6 +524,7 @@ class VelocityDirectionModifier(GeneralizedDirectionModifier):
     def _select_atoms_to_modify(self, n_subset_atoms):
         return list(range(n_subset_atoms))
 
+
 class SingleAtomVelocityDirectionModifier(GeneralizedDirectionModifier):
     """
     Change a single atom according to the ``delta_v``.
@@ -523,4 +556,3 @@ class SingleAtomVelocityDirectionModifier(GeneralizedDirectionModifier):
     """
     def _select_atoms_to_modify(self, n_subset_atoms):
         return [np.random.choice(range(n_subset_atoms))]
-
diff --git a/openpathsampling/volume.py b/openpathsampling/volume.py
index 17052caf8..2c8d3f888 100644
--- a/openpathsampling/volume.py
+++ b/openpathsampling/volume.py
@@ -429,7 +429,7 @@ def __call__(self, snapshot):
         if self.lambda_min != float('-inf') and self.lambda_min > l:
             return False
 
-        if self.lambda_min != float('inf') and self.lambda_max <= l:
+        if self.lambda_max != float('inf') and self.lambda_max <= l:
             return False
 
         return True
diff --git a/setup.cfg b/setup.cfg
index 93bca6f4d..269ff07dc 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,6 +1,6 @@
 [metadata]
 name = openpathsampling
-version = 1.5.2
+version = 1.6.0
 description = A Python package for path sampling simulations
 long_description = file: README.md
 long_description_content_type = text/markdown
@@ -14,10 +14,9 @@ classifiers =
     Intended Audience :: Developers
     License :: OSI Approved :: MIT License
     Programming Language :: Python
-    Programming Language :: Python :: 2.7
-    Programming Language :: Python :: 3.7
-    Programming Language :: Python :: 3.8
     Programming Language :: Python :: 3.9
+    Programming Language :: Python :: 3.10
+    Programming Language :: Python :: 3.11
     Topic :: Scientific/Engineering :: Bio-Informatics
     Topic :: Scientific/Engineering :: Chemistry
     Topic :: Scientific/Engineering :: Physics
@@ -27,6 +26,8 @@ classifiers =
 
 [options]
 include_package_data = True
+python_requires = >3.7,<3.12
+# pin to <3.12 until nose is removed from testing
 install_requires = 
     future
     psutil
@@ -37,10 +38,13 @@ install_requires =
     svgwrite
     networkx
     matplotlib
-    ujson!=2.*,!=3.*,!=4.0.0,!=4.0.1
+    ujson>=4.0.2
+    dill
     mdtraj
 # mdtraj is not technically required, but we co-package it because it is
 # required for many integrations with other packages
+# dill is technically only required for Python 3.11+, but we'll include it
+# for all Python versions
 packages = find:
 
 [options.extras_require]
@@ -49,10 +53,9 @@ test =
     pytest
     pytest-cov
     coveralls
-    nbval
+    nbval!=0.10.0
 simstore =
-    sqlalchemy!=1.4.0
-    dill
+    sqlalchemy>=1.4.1
 
 [bdist_wheel]
 universal = 1


Test patch:
diff --git a/.autorelease/test-testpypi b/.autorelease/test-testpypi
index fd77e73aa..7c3b02fb2 100644
--- a/.autorelease/test-testpypi
+++ b/.autorelease/test-testpypi
@@ -1,4 +1,4 @@
 #!/bin/bash
 
-python -m pip install pytest nose
+python -m pip install pytest nose sqlalchemy
 py.test --pyargs openpathsampling.tests
diff --git a/.github/workflows/test-openmm-rc.yml b/.github/workflows/test-openmm-rc.yml
index 1d14fb5fb..4f6e72e62 100644
--- a/.github/workflows/test-openmm-rc.yml
+++ b/.github/workflows/test-openmm-rc.yml
@@ -3,6 +3,7 @@
 name: "Test OpenMM Release Candidate"
 on:
   workflow_dispatch:
+  workflow_call:
   # use this for debugging
   #pull_request:
     #branch: master
@@ -25,7 +26,10 @@ jobs:
           auto-update-conda: true
       - name: "Install requirements"
         run: |
-          CONDA_PY=$(python -c "import sys; print('.'.join(str(s) for s in sys.version_info[:2]))")
+          # we'd rather use the default Python version, but for now need to
+          # pin to 3.9 (see openpathsampling/openpathsampling#1093)
+          #CONDA_PY=$(python -c "import sys; print('.'.join(str(s) for s in sys.version_info[:2]))")
+          export CONDA_PY=3.9
           echo "Python version: ${CONDA_PY}"
           source devtools/conda_install_reqs.sh
       - name: "Install OpenMM RC"
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 72257e624..125162039 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -31,13 +31,12 @@ jobs:
       #   CVs) can differ between minor Python versions.
       matrix:
         CONDA_PY:
-          - 3.9
-          - 3.8
-          - 3.7
-          - 2.7
+          - "3.11"
+          - "3.10"
+          - "3.9"
         MINIMAL: [""]
         include:
-          - CONDA_PY: 3.7
+          - CONDA_PY: "3.9"
             MINIMAL: "minimal"
 
     steps:
@@ -49,6 +48,7 @@ jobs:
         with: 
           auto-update-conda: true
           python-version: ${{ matrix.CONDA_PY }}
+          miniforge-variant: Mambaforge
       - name: "Install requirements"
         env:
           MINIMAL: ${{ matrix.MINIMAL }}
@@ -69,6 +69,8 @@ jobs:
         run: 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"
@@ -76,13 +78,8 @@ jobs:
       - name: "Tests: Experimental"
         if: matrix.MINIMAL == '' && matrix.CONDA_PY != '2.7'
         run: py.test openpathsampling/experimental/ -vv -s
-      - name: "Report coverage"
-        run: bash <(curl -s https://codecov.io/bash)
+      - uses: codecov/codecov-action@v2
       - name: "Notebook tests"
         if: matrix.MINIMAL == ''
         run: |
           pushd examples/ && ./ipynbtests.sh || exit 1 && popd
-      - name: "Deploy docs"
-        if: matrix.CONDA_PY == '3.7' && matrix.MINIMAL == ''
-        run: |
-          echo "TODO"
diff --git a/devtools/tested_integrations.txt b/devtools/tested_integrations.txt
index 0cbaab86b..535907ed9 100644
--- a/devtools/tested_integrations.txt
+++ b/devtools/tested_integrations.txt
@@ -1,5 +1,5 @@
 jupyter
-openmm!=7.5.1
+openmm>=7.6
 openmmtools
 py-plumed
 pyemma
diff --git a/examples/ipynbtests.sh b/examples/ipynbtests.sh
index ac2bf7e9a..1e924b058 100755
--- a/examples/ipynbtests.sh
+++ b/examples/ipynbtests.sh
@@ -6,7 +6,8 @@ testfail=0
 PYTHON_VERSION=`python -V 2>&1 | awk '{print $2}' | awk 'BEGIN { FS="." } { print $1 "." $2}'`
 echo "Running tests for Python version: $PYTHON_VERSION"
 
-dropbox_base_url="http://www.dropbox.com/s"
+dropbox_base_url="https://www.dropbox.com/s"
+dropbox_base_url2="https://www.dropbox.com/scl/fi"
 
 case $PYTHON_VERSION in
     "2.7")
@@ -29,12 +30,20 @@ case $PYTHON_VERSION in
         mstis=$dropbox_base_url/8rr0tt25xlm47cs/toy_mstis_1k_OPS1_py38.nc
         mistis=$dropbox_base_url/r3d5s5txbnpste0/toy_mistis_1k_OPS1_py38.nc
         ;;
+    "3.10")
+        mstis=$dropbox_base_url/8rr0tt25xlm47cs/toy_mstis_1k_OPS1_py38.nc
+        mistis=$dropbox_base_url/r3d5s5txbnpste0/toy_mistis_1k_OPS1_py38.nc
+        ;;
+    "3.11")
+        mstis="$dropbox_base_url2/c4idtymcaqvtftigce49c/toy_mstis_1k_OPS1_py311.nc?rlkey=soa6ba7okx66439egjyuscc5l&dl=1"
+        mistis="$dropbox_base_url2/s0o7br93s60q0vez88phr/toy_mistis_1k_OPS1_py311.nc?rlkey=22vdvjovt25bj4a6gh3m4vrvj&dl=1"
+        ;;
     *)
         echo "Unsupported Python version: $PYTHON_VERSION"
 esac
 
-curl -OLk $mstis
-curl -OLk $mistis
+curl -OLk --http1.1 $mstis
+curl -OLk --http1.1 $mistis
 cp `basename $mstis` toy_mstis_1k_OPS1.nc
 cp `basename $mistis` toy_mistis_1k_OPS1.nc
 
diff --git a/examples/tests/test_openmm_integration.ipynb b/examples/tests/test_openmm_integration.ipynb
index 980fa9cf1..7d6967a6a 100644
--- a/examples/tests/test_openmm_integration.ipynb
+++ b/examples/tests/test_openmm_integration.ipynb
@@ -18,21 +18,41 @@
    "cell_type": "code",
    "execution_count": 1,
    "metadata": {
-    "collapsed": true
+    "scrolled": true
    },
    "outputs": [],
    "source": [
     "from __future__ import print_function\n",
     "\n",
     "import openpathsampling as paths\n",
-    "import openmmtools as omt\n",
-    "import simtk.openmm as omm\n",
-    "import simtk.unit as u\n",
+    "\n",
+    "try:\n",
+    "    import openmm as omm\n",
+    "    import openmm.unit as u\n",
+    "except ImportError: # OpenMM < 7.6\n",
+    "    import simtk.openmm as omm\n",
+    "    import simtk.unit as u\n",
+    "\n",
     "import mdtraj as md\n",
     "\n",
     "import openpathsampling.engines.openmm as eng"
    ]
   },
+  {
+   "cell_type": "code",
+   "execution_count": 2,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# NBVAL_IGNORE_OUTPUT\n",
+    "# hide stderr from openmmtools (apparently nbval still gets it)\n",
+    "from os import devnull\n",
+    "from contextlib import redirect_stderr\n",
+    "\n",
+    "with open(devnull, 'w') as dvnl, redirect_stderr(dvnl):\n",
+    "    import openmmtools as omt"
+   ]
+  },
   {
    "cell_type": "markdown",
    "metadata": {},
@@ -42,10 +62,8 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 2,
-   "metadata": {
-    "collapsed": true
-   },
+   "execution_count": 3,
+   "metadata": {},
    "outputs": [],
    "source": [
     "testsystem = omt.testsystems.AlanineDipeptideVacuum()"
@@ -60,18 +78,19 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 3,
+   "execution_count": 4,
    "metadata": {},
    "outputs": [
     {
      "data": {
       "text/plain": [
-       "{'_positions': simtk.unit.quantity.Quantity,\n",
-       " '_system': simtk.openmm.openmm.System,\n",
-       " '_topology': simtk.openmm.app.topology.Topology}"
+       "{'_system': openmm.openmm.System,\n",
+       " '_positions': openmm.unit.quantity.Quantity,\n",
+       " '_topology': openmm.app.topology.Topology,\n",
+       " '_mdtraj_topology': NoneType}"
       ]
      },
-     "execution_count": 3,
+     "execution_count": 4,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -109,10 +128,8 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 4,
-   "metadata": {
-    "collapsed": true
-   },
+   "execution_count": 5,
+   "metadata": {},
    "outputs": [],
    "source": [
     "template = eng.snapshot_from_testsystem(testsystem)"
@@ -134,10 +151,8 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 5,
-   "metadata": {
-    "collapsed": true
-   },
+   "execution_count": 6,
+   "metadata": {},
    "outputs": [],
    "source": [
     "system = testsystem.system"
@@ -159,10 +174,8 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 6,
-   "metadata": {
-    "collapsed": true
-   },
+   "execution_count": 7,
+   "metadata": {},
    "outputs": [],
    "source": [
     "integrator = omm.VerletIntegrator(\n",
@@ -179,10 +192,8 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 7,
-   "metadata": {
-    "collapsed": true
-   },
+   "execution_count": 8,
+   "metadata": {},
    "outputs": [],
    "source": [
     "engine = eng.Engine(\n",
@@ -200,10 +211,8 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 8,
-   "metadata": {
-    "collapsed": true
-   },
+   "execution_count": 9,
+   "metadata": {},
    "outputs": [],
    "source": [
     "traj = engine.generate(template, [paths.LengthEnsemble(10).can_append])"
@@ -211,7 +220,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 9,
+   "execution_count": 10,
    "metadata": {},
    "outputs": [],
    "source": [
@@ -220,35 +229,35 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 10,
+   "execution_count": 11,
    "metadata": {},
    "outputs": [
     {
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "[[ 0.18766722  0.11599677  0.01245   ]\n",
-      " [ 0.19458348  0.22444929  0.00401418]\n",
-      " [ 0.14436732  0.27122673  0.08869708]\n",
-      " [ 0.14886753  0.25747469 -0.08926163]\n",
-      " [ 0.33994535  0.26638278  0.00609841]\n",
-      " [ 0.42709583  0.18970115  0.0438005 ]\n",
-      " [ 0.36886495  0.38998333 -0.03181564]\n",
-      " [ 0.29336688  0.45243087 -0.05633814]\n",
-      " [ 0.49859312  0.45657614 -0.02400122]\n",
-      " [ 0.55814373  0.41515267  0.05735515]\n",
-      " [ 0.57325351  0.43391329 -0.15561193]\n",
-      " [ 0.51911271  0.48312774 -0.23640624]\n",
-      " [ 0.67427617  0.47320506 -0.14414099]\n",
-      " [ 0.58423084  0.32728142 -0.17536463]\n",
-      " [ 0.47495222  0.60669047 -0.00127666]\n",
-      " [ 0.35958397  0.64924192  0.00474685]\n",
-      " [ 0.58359265  0.68379164  0.00902623]\n",
-      " [ 0.6747424   0.64066124  0.00332432]\n",
-      " [ 0.57127428  0.82897234  0.0185309 ]\n",
-      " [ 0.46718934  0.86095452  0.02347786]\n",
-      " [ 0.62214297  0.86301553  0.10872202]\n",
-      " [ 0.61715889  0.87434286 -0.06931634]] nm\n"
+      "[[ 0.18766734  0.11599676  0.01245005]\n",
+      " [ 0.19458354  0.22444928  0.00401417]\n",
+      " [ 0.14436737  0.27122667  0.08869709]\n",
+      " [ 0.14886761  0.25747457 -0.08926168]\n",
+      " [ 0.33994532  0.26638275  0.00609838]\n",
+      " [ 0.42709583  0.18970108  0.04380053]\n",
+      " [ 0.36886492  0.38998333 -0.03181563]\n",
+      " [ 0.29336691  0.4524309  -0.05633818]\n",
+      " [ 0.49859303  0.45657605 -0.02400119]\n",
+      " [ 0.55814379  0.41515264  0.05735509]\n",
+      " [ 0.57325351  0.43391326 -0.1556119 ]\n",
+      " [ 0.51911277  0.48312786 -0.23640618]\n",
+      " [ 0.67427617  0.47320503 -0.14414097]\n",
+      " [ 0.58423084  0.32728145 -0.17536484]\n",
+      " [ 0.47495231  0.60669047 -0.00127665]\n",
+      " [ 0.35958403  0.64924204  0.00474683]\n",
+      " [ 0.58359265  0.6837917   0.00902626]\n",
+      " [ 0.67474234  0.64066118  0.00332438]\n",
+      " [ 0.57127434  0.82897246  0.01853088]\n",
+      " [ 0.46718928  0.86095428  0.02347768]\n",
+      " [ 0.62214309  0.86301523  0.1087221 ]\n",
+      " [ 0.61715913  0.8743425  -0.06931649]] nm\n"
      ]
     }
    ],
@@ -259,15 +268,15 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 11,
+   "execution_count": 12,
    "metadata": {},
    "outputs": [
     {
      "name": "stderr",
      "output_type": "stream",
      "text": [
-      "/Users/dwhs/miniconda3/lib/python3.6/site-packages/mdtraj/utils/validation.py:116: TypeCastPerformanceWarning: Casting unitcell_vectors dtype=float64 to <class 'numpy.float32'> \n",
-      "  TypeCastPerformanceWarning)\n"
+      "/Users/dwhs/mambaforge/envs/dev/lib/python3.9/site-packages/mdtraj/utils/validation.py:115: TypeCastPerformanceWarning: Casting unitcell_vectors dtype=float64 to <class 'numpy.float32'> \n",
+      "  warnings.warn(\"Casting %s dtype=%s to %s \" % (name, val.dtype, dtype),\n"
      ]
     }
    ],
@@ -279,20 +288,20 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 12,
+   "execution_count": 13,
    "metadata": {},
    "outputs": [
     {
      "data": {
       "text/plain": [
-       "array([[ 3.10614586],\n",
-       "       [-3.09586358],\n",
-       "       [ 3.1367557 ],\n",
-       "       [ 3.12832713],\n",
-       "       [ 3.09317851]], dtype=float32)"
+       "array([[ 3.1061456],\n",
+       "       [-3.0958636],\n",
+       "       [ 3.136756 ],\n",
+       "       [ 3.128327 ],\n",
+       "       [ 3.0931773]], dtype=float32)"
       ]
      },
-     "execution_count": 12,
+     "execution_count": 13,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -304,10 +313,8 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 13,
-   "metadata": {
-    "collapsed": true
-   },
+   "execution_count": 14,
+   "metadata": {},
    "outputs": [],
    "source": [
     "st = paths.Storage('engine_store_test.nc', mode='w')"
@@ -315,10 +322,8 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 14,
-   "metadata": {
-    "collapsed": true
-   },
+   "execution_count": 15,
+   "metadata": {},
    "outputs": [],
    "source": [
     "st.engines.save(engine);\n",
@@ -334,16 +339,16 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 15,
+   "execution_count": 16,
    "metadata": {},
    "outputs": [
     {
      "data": {
       "text/plain": [
-       "'{\"_cls\":\"OpenMMEngine\",\"_dict\":{\"system_xml\":\"<?xml version=\\\\\"1.0\\\\\" ?>\\\\n<System openmmVersion=\\\\\"7.1.1\\\\\" type=\\\\\"System\\\\\" version=\\\\\"1\\\\\">\\\\n\\\\t<PeriodicBoxVectors>\\\\n\\\\t\\\\t<A x=\\\\\"2\\\\\" y=\\\\\"0\\\\\" z=\\\\\"0\\\\\"\\\\/>\\\\n\\\\t\\\\t<B x=\\\\\"0\\\\\" y=\\\\\"2\\\\\" z=\\\\\"0\\\\\"\\\\/>\\\\n\\\\t\\\\t<C x=\\\\\"0\\\\\" y=\\\\\"0\\\\\" z=\\\\...'"
+       "'{\"_cls\":\"OpenMMEngine\",\"_dict\":{\"system_xml\":\"<?xml version=\\\\\"1.0\\\\\" ?>\\\\n<System openmmVersion=\\\\\"7.7\\\\\" type=\\\\\"System\\\\\" version=\\\\\"1\\\\\">\\\\n\\\\t<PeriodicBoxVectors>\\\\n\\\\t\\\\t<A x=\\\\\"2\\\\\" y=\\\\\"0\\\\\" z=\\\\\"0\\\\\"\\\\/>\\\\n\\\\t\\\\t<B x=\\\\\"0\\\\\" y=\\\\\"2\\\\\" z=\\\\\"0\\\\\"\\\\/>\\\\n\\\\t\\\\t<C x=\\\\\"0\\\\\" y=\\\\\"0\\\\\" z=\\\\\"2...'"
       ]
      },
-     "execution_count": 15,
+     "execution_count": 16,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -355,10 +360,8 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 16,
-   "metadata": {
-    "collapsed": true
-   },
+   "execution_count": 17,
+   "metadata": {},
    "outputs": [],
    "source": [
     "st.save(traj);"
@@ -366,10 +369,8 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 17,
-   "metadata": {
-    "collapsed": true
-   },
+   "execution_count": 18,
+   "metadata": {},
    "outputs": [],
    "source": [
     "st.close()"
@@ -384,10 +385,8 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 18,
-   "metadata": {
-    "collapsed": true
-   },
+   "execution_count": 19,
+   "metadata": {},
    "outputs": [],
    "source": [
     "st = paths.AnalysisStorage('engine_store_test.nc')"
@@ -395,10 +394,8 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 19,
-   "metadata": {
-    "collapsed": true
-   },
+   "execution_count": 20,
+   "metadata": {},
    "outputs": [],
    "source": [
     "engine = st.engines[0]"
@@ -413,10 +410,8 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 20,
-   "metadata": {
-    "collapsed": true
-   },
+   "execution_count": 21,
+   "metadata": {},
    "outputs": [],
    "source": [
     "template = st.tag['template']"
@@ -424,10 +419,8 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 21,
-   "metadata": {
-    "collapsed": true
-   },
+   "execution_count": 22,
+   "metadata": {},
    "outputs": [],
    "source": [
     "traj = engine.generate(template, [paths.LengthEnsemble(10).can_append])"
@@ -435,7 +428,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 22,
+   "execution_count": 23,
    "metadata": {},
    "outputs": [
     {
@@ -452,10 +445,8 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 23,
-   "metadata": {
-    "collapsed": true
-   },
+   "execution_count": 24,
+   "metadata": {},
    "outputs": [],
    "source": [
     "st.close()"
@@ -470,33 +461,22 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 24,
-   "metadata": {
-    "collapsed": true
-   },
+   "execution_count": 25,
+   "metadata": {},
    "outputs": [],
    "source": [
     "st = paths.Storage('engine_store_test.nc', \"a\")\n",
     "st.save(traj)\n",
     "st.close()"
    ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "metadata": {
-    "collapsed": true
-   },
-   "outputs": [],
-   "source": []
   }
  ],
  "metadata": {
   "anaconda-cloud": {},
   "kernelspec": {
-   "display_name": "Python 3",
+   "display_name": "dev",
    "language": "python",
-   "name": "python3"
+   "name": "dev"
   },
   "language_info": {
    "codemirror_mode": {
@@ -508,7 +488,7 @@
    "name": "python",
    "nbconvert_exporter": "python",
    "pygments_lexer": "ipython3",
-   "version": "3.7.3"
+   "version": "3.9.16"
   },
   "toc": {
    "base_numbering": 1,
diff --git a/openpathsampling/experimental/simstore/test_attribute_handlers.py b/openpathsampling/experimental/simstore/test_attribute_handlers.py
index 244d720a4..48bd265aa 100644
--- a/openpathsampling/experimental/simstore/test_attribute_handlers.py
+++ b/openpathsampling/experimental/simstore/test_attribute_handlers.py
@@ -5,7 +5,7 @@
 from .attribute_handlers import *
 
 class TestStandardHandler(object):
-    def setup(self):
+    def setup_method(self):
         self.obj = {'str': 'foo',
                     'int': 42,
                     'float': 3.14159,
@@ -30,7 +30,7 @@ def test_serialization_cycle(self, type_str):
 
 
 class TestNDArrayHandler(object):
-    def setup(self):
+    def setup_method(self):
         self.data = np.array([[0.0, 1.0, 2.0], [3.0, 4.0, 5.0]])
         self.ndarray_handler = NDArrayHandler(('float32', (2,3)))
 
diff --git a/openpathsampling/experimental/simstore/test_callable_codec.py b/openpathsampling/experimental/simstore/test_callable_codec.py
index be56ef248..a06a90cac 100644
--- a/openpathsampling/experimental/simstore/test_callable_codec.py
+++ b/openpathsampling/experimental/simstore/test_callable_codec.py
@@ -1,6 +1,8 @@
 import pytest
+from unittest.mock import Mock
 from .callable_codec import *
 import dill
+import codecs
 
 import numpy as np
 
@@ -14,7 +16,7 @@ def _globals_using_function(self):
     return _global_var
 
 class TestCallableCodec(object):
-    def setup(self):
+    def setup_method(self):
         self.codec = CallableCodec()
         self.functions = {
             'generic': lambda x: x.xyz[0][0],
@@ -26,8 +28,10 @@ def setup(self):
         for func in ['generic', 'use_known_module', 'use_globals']:
             self.functions[func].__module__ = "__main__"
 
-        dilled = {key: dill.dumps(func)
-                  for key, func in self.functions.items()}
+        dilledbytes = {key: dill.dumps(func, recurse=True)
+                       for key, func in self.functions.items()}
+        dilled = {key: str(codecs.encode(val, 'base64'), 'utf-8')
+                  for key, val in dilledbytes.items()}
         self.dcts = {
             'generic': {
                 '__callable_name__': '<lambda>',
@@ -47,6 +51,16 @@ def setup(self):
             }
             # use_globals never gets serialized
         }
+        self.old_dicts = {
+            'generic': {
+                '__callable_name__': '<lambda>',
+                '_dilled': dilledbytes['generic']
+            },
+            'use_known_module': {
+                '__callable_name__': '_known_module_func',
+                '_dilled': dilledbytes['use_known_module']
+            }
+        }
 
     @pytest.mark.parametrize(
         'func,known_modules,allow_unknown',
@@ -109,6 +123,22 @@ def test_object_hook(self, func, safe):
         if not safemode and func not in untestable:
             assert result == expected
 
+    def test_object_hook_old(self):
+        # TODO: OPS 2 will drop support for the functionality this tests
+        func = 'generic'  # others won't hit this part of the code
+        self.codec.known_modules = ['openpathsampling', 'numpy']
+        self.codec.safemode = False
+        warntext = "representation of callables"
+        with pytest.warns(FutureWarning, match=warntext):
+            result = self.codec.object_hook(self.old_dicts[func])
+
+        # test the function gives a few values the same
+        mock1 = Mock(xyz=[[1.0]])
+        mock2 = Mock(xyz=[[2.0], [3.0]])
+        expected = self.functions[func]
+        assert result(mock1) == expected(mock1) == 1.0
+        assert result(mock2) == expected(mock2) == 2.0
+
     def test_object_hook_not_mine(self):
         dct = {'foo': 'bar'}
         assert self.codec.object_hook(dct) is dct
diff --git a/openpathsampling/experimental/simstore/test_class_info.py b/openpathsampling/experimental/simstore/test_class_info.py
index b2c51d403..ed0de202f 100644
--- a/openpathsampling/experimental/simstore/test_class_info.py
+++ b/openpathsampling/experimental/simstore/test_class_info.py
@@ -42,7 +42,7 @@ def test_init_default_setup(self):
         assert class_info.lookup_result == MockUUIDObject
 
 class SerializationSchemeTester(object):
-    def setup(self):
+    def setup_method(self):
         self.data_obj = all_objects['int']  # doesn't really matter which
         # technically, the next two should behave identically, except
         # name/UUID; but as different types they can be serailized
diff --git a/openpathsampling/experimental/simstore/test_custom_json.py b/openpathsampling/experimental/simstore/test_custom_json.py
index 95192257d..84d5858ed 100644
--- a/openpathsampling/experimental/simstore/test_custom_json.py
+++ b/openpathsampling/experimental/simstore/test_custom_json.py
@@ -54,7 +54,7 @@ def test_not_mine(self):
 
 
 class TestNumpyCoding(CustomJSONCodingTest):
-    def setup(self):
+    def setup_method(self):
         self.codec = numpy_codec
         self.objs = [np.array([[1.0, 0.0], [2.0, 3.2]]),
                      np.array([1, 0])]
@@ -89,7 +89,7 @@ def test_round_trip(self):
 
 
 class TestUUIDCoding(object):
-    def setup(self):
+    def setup_method(self):
         self.codec = uuid_object_codec
         all_objs = test_utils.all_objects
         self.objs = [all_objs['int'], all_objs['str']]
diff --git a/openpathsampling/experimental/simstore/test_dict_serialization_helpers.py b/openpathsampling/experimental/simstore/test_dict_serialization_helpers.py
index 5c4a14d22..9281cc7c5 100644
--- a/openpathsampling/experimental/simstore/test_dict_serialization_helpers.py
+++ b/openpathsampling/experimental/simstore/test_dict_serialization_helpers.py
@@ -18,7 +18,7 @@ def to_dict(self):
         def from_dict(cls, dct):
             return cls(**dct)
 
-    def setup(self):
+    def setup_method(self):
         self.foo = {('a', 'b'): [1, 2], ('c', 'd'): [3, 4]}
         self.bar = 3
         self.obj = self.ExampleObj(self.foo, self.bar)
diff --git a/openpathsampling/experimental/simstore/test_serialization.py b/openpathsampling/experimental/simstore/test_serialization.py
index 902ee0000..3c2a5d4a5 100644
--- a/openpathsampling/experimental/simstore/test_serialization.py
+++ b/openpathsampling/experimental/simstore/test_serialization.py
@@ -12,7 +12,7 @@
 
 
 class TestGenericLazyLoader(object):
-    def setup(self):
+    def setup_method(self):
         original_and_class = {
             'normal': (all_objects['int'], MockUUIDObject)
             # TODO: add iterable and mappable classes
@@ -97,7 +97,7 @@ def test_save_proxy(self):
 
 
 class TestProxyObjectFactory(object):
-    def setup(self):
+    def setup_method(self):
         self.storage = LoadingStorageMock({get_uuid(obj): obj
                                            for obj in all_objects.values()})
 
diff --git a/openpathsampling/experimental/simstore/test_sql_backend.py b/openpathsampling/experimental/simstore/test_sql_backend.py
index 52930817a..d81e1579d 100644
--- a/openpathsampling/experimental/simstore/test_sql_backend.py
+++ b/openpathsampling/experimental/simstore/test_sql_backend.py
@@ -2,7 +2,7 @@
 import pytest
 
 class TestSQLStorageBackend(object):
-    def setup(self):
+    def setup_method(self):
         self._delete_tmp_files()
         self.database = self._default_database
         self.database.debug = True
@@ -49,7 +49,7 @@ def _add_snapshot_data(self):
         return snap_dicts
 
 
-    def teardown(self):
+    def teardown_method(self):
         self._delete_tmp_files()
 
     @property
diff --git a/openpathsampling/experimental/simstore/test_storable_function.py b/openpathsampling/experimental/simstore/test_storable_function.py
index c9507d107..78fccb97a 100644
--- a/openpathsampling/experimental/simstore/test_storable_function.py
+++ b/openpathsampling/experimental/simstore/test_storable_function.py
@@ -71,7 +71,7 @@ def test_wrap_numpy():
         assert isinstance(wrap_numpy(inp), np.ndarray)
 
 class TestStorableFunctionConfig(object):
-    def setup(self):
+    def setup_method(self):
         self.config = StorableFunctionConfig(processors=[
             scalarize_singletons,
             wrap_numpy,
@@ -160,7 +160,7 @@ def test_storable_function_integration(self):
 
 
 class TestStorableFunctionResults(object):
-    def setup(self):
+    def setup_method(self):
         self.cv = StorableFunction(lambda x: x)
         self.cv.__uuid__ = "funcUUID"
         self.mapping = {'UUID1': "foo",
@@ -213,13 +213,13 @@ def test_len(self):
         assert len(self.sfr) == 2
 
     def test_to_dict_from_dict_cycle(self):
-        pass
+        pytest.skip()
 
 
 @mock.patch(_MODULE + '.get_uuid', lambda x: x)
 @mock.patch(_MODULE + '.has_uuid', lambda x: isinstance(x, str))
 class TestStorableFunction(object):
-    def setup(self):
+    def setup_method(self):
         def get_expected(uuid):
             expected = {'uuid': 'eval', 'uuid1': 'other'}
             return expected[uuid]
@@ -246,15 +246,12 @@ def test_is_periodic(self):
 
     def test_gets_source(self):
         pytest.skip()
-        pass
 
     def test_no_source_warning(self):
         pytest.skip()
-        pass
 
     def test_disk_cache_property(self):
         pytest.skip()
-        pass
 
     @pytest.mark.parametrize('mode', ['no-caching', 'analysis',
                                       'production'])
@@ -374,12 +371,21 @@ def test_validate(self, is_valid):
 
 
     def test_to_dict_from_dict_cycle(self):
-        pytest.skip()
-        pass
+        def identity(obj):
+            return obj
+
+        func = StorableFunction(func=identity, period_min=-2, period_max=2,
+                                store_source=True)
+        dct = func.to_dict()
+        assert 'identity' in dct['source']
+        assert dct['period_max'] == 2
+        assert dct['period_min'] == -2
+        obj = StorableFunction.from_dict(dct.copy())
+        reser = obj.to_dict()
+        assert dct == reser
 
     def test_full_serialization_cycle(self):
         pytest.skip()
-        pass
 
     @pytest.mark.parametrize('found_in', ['cache', 'storage', 'eval'])
     def test_analysis_mode_integration(self, found_in):
@@ -388,7 +394,7 @@ def test_analysis_mode_integration(self, found_in):
 
 
 class TestStorageFunctionHandler(object):
-    def setup(self):
+    def setup_method(self):
         self.backend = MockBackend()
         self.storage = mock.NonCallableMock(backend=self.backend)
         self.sf_handler = StorageFunctionHandler(self.storage)
diff --git a/openpathsampling/experimental/simstore/test_storable_function_integration.py b/openpathsampling/experimental/simstore/test_storable_function_integration.py
index 44dc75a0e..83fc42871 100644
--- a/openpathsampling/experimental/simstore/test_storable_function_integration.py
+++ b/openpathsampling/experimental/simstore/test_storable_function_integration.py
@@ -28,6 +28,8 @@
 
 from openpathsampling.netcdfplus import StorableNamedObject
 
+import sqlalchemy.pool
+
 class InputObj(StorableNamedObject):
     pass  # just a thing that has a UUID
 
@@ -285,7 +287,7 @@ def test_multiple_storage_inside_other_object(tmpdir, inputs_and_func):
     container.cv.local_cache.clear()
     assert container.cv.func.call_count == 2
     assert container(inp2) == 'f'
-    assert container.cv.func.call_count == 2 
+    assert container.cv.func.call_count == 2
 
 def test_closing(tmpdir, inputs_and_func):
     inp1, inp2, func, sf, container = inputs_and_func
@@ -300,16 +302,21 @@ def test_closing(tmpdir, inputs_and_func):
     assert not sf.has_handler
 
 def test_storage_file_problem(tmpdir, inputs_and_func):
+    # need to switch back to NullPool in SQLAlchemy 2.0+, otherwise it
+    # appears to cache the old results. See discussion here:
+    # https://github.com/sqlalchemy/sqlalchemy/discussions/9167
     inp1, inp2, func, sf, container = inputs_and_func
     sf_uuid = str(get_uuid(sf))
     st1_fname = tmpdir.join("st1.db")
-    be_1w = SQLStorageBackend(st1_fname, mode='w')
+    be_1w = SQLStorageBackend(st1_fname, mode='w',
+                              poolclass=sqlalchemy.pool.NullPool)
     _store_via_container(be_1w, container, inp1, call_count=1,
                          backend_count=1)
 
     GeneralStorage._known_storages = {}
     storage = GeneralStorage(
-        backend=SQLStorageBackend(st1_fname, mode='r'),
+        backend=SQLStorageBackend(st1_fname, mode='r',
+                                  poolclass=sqlalchemy.pool.NullPool),
         class_info=_serialization,
         schema=_schema
     )
@@ -318,6 +325,7 @@ def test_storage_file_problem(tmpdir, inputs_and_func):
     assert container.cv.func.call_count == 1
     os.remove(st1_fname)  # naughty user behavior
 
+    # this requires using the NullPool, apparently
     with pytest.warns(UserWarning):
         assert container(inp1) == 'f'
     assert container.cv.func.call_count == 2
diff --git a/openpathsampling/experimental/simstore/test_storage.py b/openpathsampling/experimental/simstore/test_storage.py
index ddba5b5e9..623cf0540 100644
--- a/openpathsampling/experimental/simstore/test_storage.py
+++ b/openpathsampling/experimental/simstore/test_storage.py
@@ -3,11 +3,22 @@
 import pytest
 
 from .serialization_helpers import get_uuid
-from .test_utils import all_objects, UnnamedUUID, MockUUIDObject
+from .test_utils import all_objects, UnnamedUUID, MockUUIDObject, MockStorage
+
 
 class TestStorageTable(object):
-    def setup(self):
-        pass
+    def setup_method(self):
+        self.storage = MockStorage()
+        # Override default tables/uuids
+        self.storage.backend.table_names = ["all"]
+        self.obj_list = list(all_objects.values())
+        self.storage.backend.tables = [self.obj_list]
+        self.storage.backend.uuid_table = {
+            e.uuid:
+            self.storage.backend.row_types["uuids"](uuid=e.uuid, table=0,
+                                                    idx=i)
+            for i, e in enumerate(self.obj_list)}
+        self.table = StorageTable(self.storage, "all")
 
     def test_iter(self):
         pytest.skip()
@@ -21,12 +32,31 @@ def test_len(self):
     def test_save(self):
         pytest.skip()
 
+    @pytest.mark.parametrize("s", [slice(None),  # [:]
+                                   slice(5),  # [:5]
+                                   slice(-5),  # [:-5]
+                                   slice(1, 2),  # [1:2]
+                                   slice(100),  # longer slice than possible
+                                   slice(6, 4, -1),  # [6:4:-1]
+                                   slice(5, 5),  # empty
+                                   ])
+    def test_getslice(self, s):
+        truths = self.obj_list[s]
+        tests = self.table[s]
+        for i, j in zip(truths, tests):
+            assert i == j
+
+    def test_bogus_access(self):
+        with pytest.raises(TypeError, match="type tuple"):
+            self.table[(1, 2, 3)]
+
 
 class TestPseudoTable(object):
-    def setup(self):
+    def setup_method(self):
         self.objs = {None: UnnamedUUID(normal_attr=10)}
         self.objs.update(all_objects)
-        self.pseudo_table = PseudoTable(list(self.objs.values()))
+        self.obj_list = list(self.objs.values())
+        self.pseudo_table = PseudoTable(self.obj_list)
 
     @pytest.mark.parametrize('name', ['int', None])
     def test_get_by_uuid(self, name):
@@ -38,12 +68,29 @@ def test_get_by_uuid(self, name):
     @pytest.mark.parametrize('name', ['int', None])
     def test_getitem(self, name):
         obj = self.objs[name]
-        uuid = get_uuid(obj)
         idx = self.pseudo_table.index(obj)
         assert self.pseudo_table[idx] == obj
         if name is not None:
             assert self.pseudo_table[name] == obj
 
+    @pytest.mark.parametrize("s", [slice(None),  # [:]
+                                   slice(5),  # [:5]
+                                   slice(-5),  # [:-5]
+                                   slice(1, 2),  # [1:2]
+                                   slice(100),  # longer slice than possible
+                                   slice(6, 4, -1),  # [6:4:-1]
+                                   slice(5, 5),  # empty
+                                   ])
+    def test_getslice(self, s):
+        truths = self.obj_list[s]
+        tests = self.pseudo_table[s]
+        for i, j in zip(truths, tests):
+            assert i == j
+
+    def test_bogus_access(self):
+        with pytest.raises(TypeError, match="tuple"):
+            self.pseudo_table[(1, 2, 3)]
+
     @pytest.mark.parametrize('name', ['int2', None])
     def test_setitem(self, name):
         value = {'int2': 20, None: 21}[name]
@@ -55,7 +102,7 @@ def test_setitem(self, name):
         self.pseudo_table[len_table-1] = item
         assert item in self.pseudo_table
         assert self.pseudo_table[len_table-1] == item
-        if name != None:
+        if name is not None:
             assert self.pseudo_table[name] == item
 
     @pytest.mark.parametrize('name', ['int2', None])
@@ -69,7 +116,7 @@ def test_append(self, name):
         self.pseudo_table.append(item)
         assert item in self.pseudo_table
         assert self.pseudo_table[len_table] == item
-        if name != None:
+        if name is not None:
             assert self.pseudo_table[name] == item
 
     def test_delitem(self):
@@ -79,7 +126,7 @@ def test_delitem(self):
         del self.pseudo_table[int_idx]
         assert len(self.pseudo_table) == len(all_objects)
         assert self.objs['int'] not in self.pseudo_table
-        with pytest.raises(KeyError):
+        with pytest.raises(KeyError, match='int'):
             self.pseudo_table['int']
 
     def test_len(self):
@@ -96,5 +143,5 @@ def test_insert(self, name):
         self.pseudo_table.insert(len_table, item)
         assert item in self.pseudo_table
         assert self.pseudo_table[len_table] == item
-        if name != None:
+        if name is not None:
             assert self.pseudo_table[name] == item
diff --git a/openpathsampling/experimental/simstore/test_tags_table.py b/openpathsampling/experimental/simstore/test_tags_table.py
index 020145eb2..d81f28e96 100644
--- a/openpathsampling/experimental/simstore/test_tags_table.py
+++ b/openpathsampling/experimental/simstore/test_tags_table.py
@@ -17,7 +17,7 @@ def __init__(self, value):
         self.value = value
 
 class TestTagsTable(object):
-    def setup(self):
+    def setup_method(self):
         json_ser = JSONSerializerDeserializer([uuid_object_codec])
         # TODO: add tags to serialization schema
         serialization_schema = SerializationSchema(
diff --git a/openpathsampling/experimental/simstore/test_tools.py b/openpathsampling/experimental/simstore/test_tools.py
index 7c0df9a24..c467532cd 100644
--- a/openpathsampling/experimental/simstore/test_tools.py
+++ b/openpathsampling/experimental/simstore/test_tools.py
@@ -10,7 +10,7 @@ def test_compare_sets():
     pytest.skip()
 
 class TestGroupBy(object):
-    def setup(self):
+    def setup_method(self):
         pass
 
 class TestListify(object):
@@ -25,7 +25,7 @@ def test_should_not_wrap(self, obj):
 
 
 class TestFlatten(object):
-    def setup(self):
+    def setup_method(self):
         self.result = ['a', 'b', 'c', 'd', 'e', 'f']
         self.list = ['a', 'b', [['c'], ['d'], 'e'], [['f']]]
         self.dict = {11: 'a', 12: 'b',
diff --git a/openpathsampling/experimental/simstore/test_type_ident.py b/openpathsampling/experimental/simstore/test_type_ident.py
index 02b9a8815..02dc2b889 100644
--- a/openpathsampling/experimental/simstore/test_type_ident.py
+++ b/openpathsampling/experimental/simstore/test_type_ident.py
@@ -9,7 +9,7 @@ class TestStandardTyping(object):
     # this tests everything in STANDARD_TYPING; it's a little more
     # integration test than unit, but the individual tests ensure that each
     # unit is tested
-    def setup(self):
+    def setup_method(self):
         self.objects = {
             'int': ('int', 5),
             'float': ('float', 2.3),
diff --git a/openpathsampling/experimental/simstore/test_utils.py b/openpathsampling/experimental/simstore/test_utils.py
index d037f1a4f..4868b7545 100644
--- a/openpathsampling/experimental/simstore/test_utils.py
+++ b/openpathsampling/experimental/simstore/test_utils.py
@@ -53,6 +53,10 @@ def from_dict(cls, dct):
             name = None
         return cls(name=name, **dct)
 
+    @property
+    def uuid(self):
+        return self.__uuid__
+
 class MockUUIDObject(AbstractMockUUIDObject):
     attr_list = ['name', 'normal_attr', 'obj_attr', 'list_attr',
                  'dict_attr', 'lazy_attr']
@@ -142,6 +146,23 @@ def load_table_data(self, uuid_rows):
     def uuid_row_to_table_name(self, row):
         return self.table_names[row.table]
 
+    def table_get_item(self, table, item):
+        idx = self.table_names.index(table)
+        return self.tables[idx][item]
+
+    def table_len(self, table):
+        idx = self.table_names.index(table)
+        return len(self.tables[idx])
+
+
+class MockStorage(object):
+    def __init__(self, backend=None):
+        self.backend = backend or MockBackend()
+
+    def load(self, uuids):
+        uuid_rows = self.backend.load_uuids_table(uuids)
+        return self.backend.load_table_data(uuid_rows)
+
 
 class LoadingStorageMock(object):
     def __init__(self, uuid_dict):
@@ -177,7 +198,7 @@ def create_test_objects():
 
 
 class TestMockBackend(object):
-    def setup(self):
+    def setup_method(self):
         self.backend = MockBackend()
 
     @pytest.mark.parametrize(('obj_name', 'table_idx', 'idx'),
diff --git a/openpathsampling/experimental/storage/test_monkey_patches.py b/openpathsampling/experimental/storage/test_monkey_patches.py
new file mode 100644
index 000000000..60942aab1
--- /dev/null
+++ b/openpathsampling/experimental/storage/test_monkey_patches.py
@@ -0,0 +1,43 @@
+import openpathsampling as paths
+import pytest
+
+from openpathsampling.experimental.storage.monkey_patches import *
+from openpathsampling.experimental.storage.collective_variables import \
+    CollectiveVariable
+
+@pytest.fixture
+def patching_tps_network():
+    cv = CollectiveVariable(lambda x: x.xyz[0][0]).named('cv')
+    state_A = paths.CVDefinedVolume(cv, 0, 1)
+    state_B = paths.CVDefinedVolume(cv, 9, 10)
+    tps_network = paths.TPSNetwork(state_A, state_B)
+    yield tps_network
+
+
+def test_double_patch(patching_tps_network):
+    orig_dict = patching_tps_network.to_dict()
+    global paths
+    paths = monkey_patch_all(paths)
+    try:
+        patched_dict = patching_tps_network.to_dict()
+        paths = monkey_patch_all(paths)
+        double_patched_dict = patching_tps_network.to_dict()
+    finally:
+        unpatch(paths)
+
+    assert orig_dict != patched_dict
+    assert patched_dict == double_patched_dict
+
+
+def test_unpatch(patching_tps_network):
+    orig_dict = patching_tps_network.to_dict()
+    global paths
+    paths = monkey_patch_all(paths)
+    try:
+        patched_dict = patching_tps_network.to_dict()
+    finally:
+        paths = unpatch(paths)
+
+    unpatched_dict = patching_tps_network.to_dict()
+    assert unpatched_dict == orig_dict
+    assert unpatched_dict != patched_dict
diff --git a/openpathsampling/experimental/storage/test_regressions.py b/openpathsampling/experimental/storage/test_regressions.py
new file mode 100644
index 000000000..501b71af6
--- /dev/null
+++ b/openpathsampling/experimental/storage/test_regressions.py
@@ -0,0 +1,18 @@
+# miscellaneous regression testing
+import pytest
+from openpathsampling.tests.test_helpers import data_filename
+from openpathsampling.netcdfplus import StorableNamedObject
+
+from openpathsampling.experimental.storage.ops_storage import ops_class_info
+
+class TrajWrapper(StorableNamedObject):
+    def __init__(self, traj):
+        super(TrajWrapper, self).__init__()
+        self.traj = traj
+
+
+def test_use_mdtraj_codec():
+    # smoke test to ensure we *can* use the MDTraj codecs
+    md = pytest.importorskip("mdtraj")
+    traj = md.load(data_filename("ala_small_traj.pdb"))
+    serialized = ops_class_info.default_info.serializer(TrajWrapper(traj))
diff --git a/openpathsampling/experimental/storage/test_snapshot_integrations.py b/openpathsampling/experimental/storage/test_snapshot_integrations.py
index 138b9eebd..883865870 100644
--- a/openpathsampling/experimental/storage/test_snapshot_integrations.py
+++ b/openpathsampling/experimental/storage/test_snapshot_integrations.py
@@ -7,6 +7,7 @@
 
 import numpy as np
 import openpathsampling as paths
+from openpathsampling.integration_tools import unit
 
 from openpathsampling.tests.test_helpers import data_filename
 
@@ -48,6 +49,10 @@ def _make_gromacs_snap(self):
         )
         return snap
 
+    def _make_gromacs_int_snap(self):
+        snap = self._make_gromacs_snap()
+        return snap.internalize()
+
     def _make_openmm_snap(self):
         mm = pytest.importorskip('simtk.openmm')
         traj_file = data_filename('ala_small_traj.pdb')
@@ -55,14 +60,35 @@ def _make_openmm_snap(self):
         snap = traj[0]
         return snap
 
-    @pytest.mark.parametrize('integration', ['gromacs', 'openmm'])
+    @pytest.mark.parametrize('integration', ['gromacs', 'openmm',
+                                             'gromacs_int'])
     def test_integration(self, integration):
         make_snap = {
             'gromacs': self._make_gromacs_snap,
             'openmm': self._make_openmm_snap,
+            'gromacs_int': self._make_gromacs_int_snap,
         }[integration]
+
         snap = make_snap()
 
+        def get_velocities(snap):
+            return snap.velocities
+
+        def get_box_vectors(snap):
+            return snap.box_vectors
+
+        if integration == 'openmm':
+            # already skipping if OpenMM isn't available, so we can use unit
+            nm = unit.nanometer
+            ps = unit.picosecond
+            def extract_unit(func, units):
+                def inner(snap):
+                    return func(snap).value_in_unit(units)
+                return inner
+
+            get_velocities = extract_unit(get_velocities, nm/ps)
+            get_box_vectors = extract_unit(get_box_vectors, nm)
+
         storage = self._make_storage(mode='w')
         assert not storage.backend.has_table('snapshot0')
         storage.save(snap)
@@ -73,6 +99,9 @@ def test_integration(self, integration):
         assert storage.backend.has_table('snapshot0')
         snap2 = storage.snapshots.tables['snapshot0'][0]
 
+        assert not snap is snap2
         np.testing.assert_array_equal(snap.xyz, snap2.xyz)
-
-
+        np.testing.assert_array_equal(get_velocities(snap),
+                                      get_velocities(snap2))
+        np.testing.assert_array_equal(get_box_vectors(snap),
+                                      get_box_vectors(snap2))
diff --git a/openpathsampling/tests/analysis/__init__.py b/openpathsampling/tests/analysis/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/openpathsampling/tests/analysis/conftest.py b/openpathsampling/tests/analysis/conftest.py
new file mode 100644
index 000000000..8b6a02295
--- /dev/null
+++ b/openpathsampling/tests/analysis/conftest.py
@@ -0,0 +1,48 @@
+# TODO: Currently this is located in tests/analysis/conftest.py. However,
+# this might be more suitable for a higher-level conftest.py, or perhaps it
+# should be moved into `fixtures` subdirectory of tests with the necessary
+# objects imported into the main tests/conftest.py, for use across the test
+# suite.
+import openpathsampling as paths
+
+from openpathsampling.tests.analysis.utils.fixture_classes import (
+    TISSystemFixture, TPSSystemFixture, make_fixture, DEFAULT_CV
+)
+
+
+def unidirectional_tis_network():
+    r"""Fixture for unidirectional TIS with the default (RETIS) scheme.
+
+    This has states defined as initial state :math:`x < 0` and final state
+    :math:`x \ge 10`. The interfaces are at :math:`x=0`, :math:`x=3`, and
+    :math:`x=6`.
+    """
+    paths.InterfaceSet._reset()
+    state_A = paths.CVDefinedVolume(DEFAULT_CV, float("-inf"), 0)
+    state_B = paths.CVDefinedVolume(DEFAULT_CV, 10, float("inf"))
+    interfaces = paths.VolumeInterfaceSet(DEFAULT_CV, float("-inf"),
+                                          [0, 3, 6])
+    network = paths.MISTISNetwork([(state_A, interfaces, state_B)])
+    return network
+
+
+def two_state_tps_network():
+    state_A = paths.CVDefinedVolume(DEFAULT_CV, float("-inf"), 0)
+    state_B = paths.CVDefinedVolume(DEFAULT_CV, 10, float("inf"))
+    network = paths.TPSNetwork(state_A, state_B)
+    return network
+
+
+default_unidirectional_tis = make_fixture(
+    fixture_type=TISSystemFixture,
+    make_network=unidirectional_tis_network,
+    scheme_type=paths.DefaultScheme,
+    state_bounds=(0, 10)
+)
+
+
+default_two_state_tps = make_fixture(
+    fixture_type=TPSSystemFixture,
+    make_network=two_state_tps_network,
+    scheme_type=paths.OneWayShootingMoveScheme,
+)
diff --git a/openpathsampling/tests/analysis/utils/__init__.py b/openpathsampling/tests/analysis/utils/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/openpathsampling/tests/analysis/utils/fixture_classes.py b/openpathsampling/tests/analysis/utils/fixture_classes.py
new file mode 100644
index 000000000..daab5a611
--- /dev/null
+++ b/openpathsampling/tests/analysis/utils/fixture_classes.py
@@ -0,0 +1,151 @@
+"""
+Classes to use as fixtures in pytest.
+
+This module includes tools to create containers that are useful as
+testsystem-style fixtures. That is, there are connected tools for network,
+move scheme, and trajectory generation that may be specific to that network.
+"""
+
+import pytest
+import numpy as np
+import openpathsampling as paths
+from openpathsampling.tests.test_helpers import make_1d_traj
+
+DEFAULT_CV = paths.FunctionCV("x", lambda s: s.xyz[0][0])
+DEFAULT_ENGINE = paths.engines.NoEngine({'n_spatial': 3, 'n_atoms': 1})
+
+
+class TPSSystemFixture(object):
+    """Fixture to use with TPS simulation setups.
+
+    This acts as a container for the network and move scheme for any path
+    sampling fixture, and also provides the method :meth:`.make_trajectory`
+    to make it easy to create trajectory segments.
+
+    Parameters
+    ----------
+    network : :class:`.TransitionNetwork`
+        the network associated with this setup
+    scheme : :class:`.MoveScheme`
+        the move scheme associated with this setup
+    """
+    def __init__(self, network, scheme):
+        self.network = network
+        self.scheme = scheme
+
+    @staticmethod
+    def make_trajectory(lower, upper):
+        """Make a trajectory from `lower` to `upper`.
+
+        This makes a trajectory segment with x-values of frames spread by
+        1.  For each trajectory, some random value epsilon is added (with ``0
+        <= epsilon < 1``), such that the lowest value is ``lower + epsilon``
+        and the highest value is ``upper + epsilon``. This is designed to
+        facilitate test debugging, since all frames from the the same
+        trajectory will have the same value of epsilon.
+
+        Note that inputs here should be integers. This is very specifically
+        for toy models where the boundaries are at integer values (though the
+        CVs are allowed to take on arbitrary floats).
+
+        Parameters
+        ----------
+        lower : int
+            the lower bound x-value of the trajectory
+        upper: int
+            the upper bound x-value of the trajectory (NOTE: trajectories will
+            cross this value, but not cross ``upper + 1``)
+
+        Returns
+        -------
+        :class:`.Trajectory`
+            the trajectory segment
+        """
+        xvals = np.array(range(lower, upper + 1)) + np.random.random()
+        return make_1d_traj(xvals)
+
+
+class TISSystemFixture(TPSSystemFixture):
+    """Fixture to use with simple TIS simulation setups.
+
+    This is a container for the network and move scheme for simple two-state
+    TIS fixture setups. It provides the :meth:`.make_trajectory` and
+    :meth:`.make_tis_trajectory` methods.
+
+    Parameters
+    ----------
+    network : :class:`.TransitionNetwork`
+        the network associated with this setup
+    scheme : :class:`.MoveScheme`
+        the move scheme associated with this setup
+    state_bounds : Tuple[int, int]
+        the bounds (lower, upper) that mark the bounds of the transition
+        region, i.e., the lower number is the upper bound of the initial
+        state, and the upper number is the lower bound of the final state.
+    """
+    def __init__(self, network, scheme, state_bounds):
+        super(TISSystemFixture, self).__init__(network, scheme)
+        self.lower, self.upper = state_bounds
+
+    def make_tis_trajectory(self, cv_max, lower_bound=None):
+        """Make a TIS trajectory with a given maximum x value.
+
+        As with :method:`.make_trajectory`, this will create a trajectory
+        with frames where the x values are separated by 1, with the
+        trajectory offset by a random constant epsilon with `0 <= epsilon <
+        1`, which allows related frames to by identified when debugging
+        analysis tests.
+
+        Parameters
+        ----------
+        cv_max : int
+            maximum allowed value of x; if less than ``self.upper``, then
+            this trajectory will return to the lower state, otherwise it
+            terminates after reaching ``cv_max``.
+        lower_bound: int or None
+            minumum value of x; if None, use the ``self.lower - 1``, where
+            ``self.lower`` is the lower state bound.
+        """
+        if lower_bound is None:
+            lower_bound = self.lower - 1
+
+        increasing = self.make_trajectory(lower_bound, cv_max)
+
+        if cv_max >= self.upper:
+            return increasing
+        else:
+            decreasing = increasing.reversed[1:]
+            return increasing + decreasing
+
+
+def make_fixture(fixture_type, make_network, scheme_type, **kwargs):
+    """Create a pytest fixture for a given setup.
+
+    This will always use ``DEFAULT_ENGINE``, which is a :class:`.NoEngine`
+    with 3 spatial dimensions and 1 atom.
+
+    Parameters
+    ----------
+    fixture_type : type
+        the type of fixture (class object) to create; usually either
+        :class:`.TPSSystemFixture` or :class:`.TISSystemFixture`
+    make_network : Callable[[], :class:`.TransitionNetwork`]
+        callable (taking no arguments) that creates that desired transition
+        network.
+    scheme_type : Callable
+        type of move scheme to create, must take arguments ``network`` and
+        ``engine`` and return move scheme (usually a scheme type, such as
+        :class:`.DefaultScheme` or :class:`.OneWayShootingMoveScheme`).
+    kwargs :
+        additional keyword arguments are passed to ``fixture_type``
+    """
+    # TODO: consider whether to only have the pytest.fixture decoration in
+    # the conftest.py; that may be better for most use cases
+    @pytest.fixture
+    def inner():
+        network = make_network()
+        scheme = scheme_type(network=network, engine=DEFAULT_ENGINE)
+        scheme.move_decision_tree()  # to build the movers
+        fixture = fixture_type(network=network, scheme=scheme, **kwargs)
+        return fixture
+    return inner
diff --git a/openpathsampling/tests/analysis/utils/mock_movers.py b/openpathsampling/tests/analysis/utils/mock_movers.py
new file mode 100644
index 000000000..bd2c8c437
--- /dev/null
+++ b/openpathsampling/tests/analysis/utils/mock_movers.py
@@ -0,0 +1,464 @@
+"""
+Tools for generating sequences of steps for analysis tests.
+
+The idea here is to have client code specify things like which snapshot
+index is the shooting point and what partial trajectory from one-way
+shooting should be, then we patch those things and use the internals of the
+mover to generate the real data.
+
+In this way, we use as much of the actual OPS machinery as possible,
+ensuring, for example, that the details we return are correct.
+
+Note that for the classes here, each instance represents a single move
+(single MC step). This is unlike the PathMover objects in OPS, which
+represents an individual type of move that can be reused for many steps.
+"""
+
+import random
+try:
+    from unittest.mock import Mock, patch
+except ImportError:  # pragma: no cover
+    from mock import Mock, patch
+
+import openpathsampling as paths
+
+
+def _get_only(iterable, condition, error_msg):
+    possibilities = [item for item in iterable if condition(item)]
+    if len(possibilities) != 1:
+        msg = "expected 1 {error_msg}; found {nfound}: {found}".format(
+            error_msg=error_msg,
+            nfound=len(possibilities),
+            found=possibilities
+        )
+        raise AnalysisTestSetupError(msg)
+    return possibilities[0]
+
+
+class AnalysisTestSetupError(Exception):
+    """Raised when an internal error occurs during test setup.
+
+    These usually indicate a problem with test suite, not with the code
+    itself.
+    """
+
+
+def _select_by_input_ensembles(movers, ensembles):
+    """Select a mover by input ensembles.
+
+    Pick a random mover if ensembles is ``None``.
+    """
+    # quick return if we don't actually care about which mover we use
+    if ensembles is None:
+        return random.choice(movers)
+
+    try:
+        signature = tuple(ensembles)
+    except TypeError:
+        signature = tuple([ensembles])
+
+    mover = _get_only(
+        iterable=movers,
+        condition=lambda m: set(m.ensemble_signature[0]) == set(signature),
+        error_msg="mover matching signature {sig}".format(sig=signature)
+    )
+    return mover
+
+
+def _run_patched(mover, patches, inputs):
+    """Run the move with the given patches for the given inputs"""
+    for p in patches:
+        p.start()
+
+    change = mover.move(inputs)
+
+    for p in patches:
+        p.stop()
+    return change
+
+
+class MockRandomChoiceMover(object):
+    """Mock random choice mover to give the desired subchange.
+
+    This differs from the regular mover mocks in that it is only created
+    when we have already determined the subchange that will be generated.
+
+    Parameters
+    ----------
+    random_mover: class:`.RandomChoiceMover`
+        the mover to mock the behavior or
+    change : :class:`.MoveChange`
+        the change that the submover would return
+    """
+    def __init__(self, random_mover, change):
+        self.random_mover = random_mover
+        self.change = change
+
+    def patches(self):
+        mover = self.change.mover
+        try:
+            idx = self.random_mover.submovers.index(mover)
+        except ValueError:
+            raise AnalysisTestSetupError(
+                "mover %s not found as submover of mover %s" %
+                (self.random_mover, mover)
+            )
+        rng_mock = Mock(choice=Mock(return_value=idx))
+        patches = [
+            patch.object(self.random_mover, '_rng', rng_mock),
+            patch.object(mover, 'move', Mock(return_value=self.change))
+        ]
+        return patches
+
+    def __call__(self, inputs):
+        patches = self.patches()
+        return _run_patched(self.random_mover, patches, inputs)
+
+
+class MockMove(object):
+    """Abstract object for mocking a single step of a path simulation.
+    """
+    def __init__(self, scheme, ensembles=None, group_name=None):
+        self.scheme = scheme
+        self.ensembles = ensembles
+        self.group_name = group_name
+
+    def wrap_org_by_group(self, change, inputs):
+        """Wraps as if using an OrganizeByMoveGroup strategy
+        """
+        # extract the root_mover (selects type of move) and the
+        # group_selector (selects a specific move within the move type)
+        root_mover = self.scheme.root_mover
+        group_selector = _get_only(
+            iterable=root_mover.submovers,
+            condition=lambda g: change.mover in g.submovers,
+            error_msg="group containing the mover {mover}".format(
+                mover=change.mover
+            )
+        )
+
+        # make a move change for the inner step (selecting which mover
+        # within the move type)
+        inner_mock = MockRandomChoiceMover(group_selector, change)
+        inner_change = inner_mock(inputs)
+
+        # make a move change for the outer step (selecting which move type
+        # to do from the root_mover)
+        outer_mock = MockRandomChoiceMover(root_mover, inner_change)
+        outer_change = outer_mock(inputs)
+        return outer_change
+
+    def patches(self, mover):
+        """Patches to be used by this mover
+
+        Subclasses will override this when they need to control the mover's
+        behavior.
+
+        Parameters
+        ----------
+        mover : :class:`.PathMover`
+            the path mover to patch
+
+        Returns
+        -------
+        List[unittest.patch]
+            patch objects to use during the move
+        """
+        return []
+
+    def safety_checks(self, change, inputs):
+        """Safety checks to ensure reasonable output.
+
+        Override in subclasses.
+
+        Parameters
+        ----------
+        change : :class:`.MoveChange`
+            move change from this mocked step
+        inputs : :class:`.SampleSet`
+            original input to this mover
+        """
+        pass
+
+    def _do_move(self, mover, inputs):
+        patches = self.patches(mover)
+        return _run_patched(mover, patches, inputs)
+
+    def __call__(self, inputs):
+        mover = _select_by_input_ensembles(
+            movers=self.scheme.movers[self.group_name],
+            ensembles=self.ensembles
+        )
+        change = self._do_move(mover, inputs)
+        self.safety_checks(change, inputs)
+        return change
+
+
+class _MockSingleEnsembleMove(MockMove):
+    def __init__(self, scheme, ensemble, group_name):
+        if ensemble is not None:
+            ensemble = [ensemble]
+        super(_MockSingleEnsembleMove, self).__init__(
+            scheme=scheme,
+            ensembles=ensemble,
+            group_name=group_name
+        )
+
+    @property
+    def ensemble(self):
+        return self.ensembles[0]
+
+
+class _MockOneWayShooting(_MockSingleEnsembleMove):
+    """Abstract mock for one-way shooting.
+
+    Core logic implemented in here (see methods ``patches`` and
+    ``safety_checks``). For usage information, see MockForwardShooting and
+    MockBackwardShooting.
+    """
+    def __init__(self, shooting_index, partial_traj, direction, scheme,
+                 ensemble, accepted, group_name):
+        super(_MockOneWayShooting, self).__init__(scheme=scheme,
+                                                  ensemble=ensemble,
+                                                  group_name=group_name)
+        self.shooting_index = shooting_index
+        self.partial_traj = partial_traj
+        self.direction = direction
+        self.accepted = accepted
+
+    def _generate_mock(self, snapshot, running):
+        return paths.Trajectory([snapshot] + self.partial_traj)
+
+    def patches(self, mover):
+        if self.direction == 'forward':
+            shoot_type = paths.ForwardShootMover
+        elif self.direction == 'backward':
+            shoot_type = paths.BackwardShootMover
+        else:  # pragma: no cover
+            raise AnalysisTestSetupError("Invalid direction: %s" %
+                                         self.direction)
+
+        shooter = _get_only(
+            iterable=mover.submovers,
+            condition=lambda m: isinstance(m, shoot_type),
+            error_msg=(
+                "submover of type {shoot_type};".format(
+                    shoot_type=shoot_type.__class__.__name__)
+                )
+            )
+
+        # patch for the choice of fwd vs bkwd shooter
+        direction_idx = mover.submovers.index(shooter)
+        rng_mock = Mock(choice=Mock(return_value=direction_idx))
+        direction_patch = patch.object(mover, '_rng', rng_mock)
+
+        # patch for the partial trajectory returned by dynamics
+        traj_patch = patch.object(shooter.engine, 'generate',
+                                  self._generate_mock)
+
+        # patch for the shooting point selector
+        pick_patch = patch.object(shooter.selector, 'pick',
+                                  Mock(return_value=self.shooting_index))
+        if self.accepted is not None:
+            value = 1.0 if self.accepted else 0.0
+            probability_ratio_patch = [patch.object(shooter.selector,
+                                                    'probability_ratio',
+                                                    return_value=value)]
+        else:
+            probability_ratio_patch = []
+
+        return ([direction_patch, traj_patch, pick_patch]
+                + probability_ratio_patch)
+
+    def _check_shooting_point_in_trial(self, change, inputs):
+        # was the shooting point included in the trial trajectory? (we can't
+        # know this until after we create the move change)
+        shooting_snapshot = change.canonical.details.shooting_snapshot
+        trial_traj = change.canonical.trials[0].trajectory
+        if shooting_snapshot not in trial_traj:  # pragma: no cover
+            # This should not be possible, because the mock mover adds the
+            # shooting point. Safety against weird failures, though.
+            raise AnalysisTestSetupError("shooting point not included in "
+                                         "partial trajectory")
+
+    def _check_forced_accepted_reasonable(self, change, inputs):
+        trial = change.canonical.trials[0]
+        if self.accepted and not trial.ensemble(trial.trajectory):
+            if change.accepted:  # pragma: no cover
+                raise AnalysisTestSetupError(
+                    "Something when wrong in the mock mover. A step that "
+                    "can not be accepted was accepted."
+                )
+            else:
+                raise AnalysisTestSetupError(
+                    "Step tried to force acceptance on a trial that can "
+                    "not be accepted."
+                )
+
+    def safety_checks(self, change, inputs):
+        self._check_shooting_point_in_trial(change, inputs)
+        self._check_forced_accepted_reasonable(change, inputs)
+
+
+class MockForwardShooting(_MockOneWayShooting):
+    """Mock a forward shooting move.
+
+    Parameters
+    ----------
+    shooting_index : int
+        the index of the shooting point in the input trajectory
+    partial_traj : :class:`.Trajectory`
+        The trajectory to mock as coming from the engine. This is the
+        trajectory *without* the shooting point -- new frames only.
+    scheme : :class:`.MoveScheme`
+        the move scheme used in the mock simulation
+    ensemble : :class:`.Ensemble` or None
+        the ensemble to shoot from. If ``None``, a random mover is selected
+        from the movers in ``group_name``.
+    accepted : bool or None
+        if ``True`` or ``False``, force the acceptance to by true or false,
+        respectively. If ``None`` (default) use the internal logic to
+        determine acceptance. NOTE: This only bypasses the Monte Carlo
+        proposal probability calculation. Moves that are lot legally
+        possible to accept will raise an AnalysisTestSetupError if you try
+        to force them to be accepted.
+    group_name : str
+        the name of the groups to select movers from (default
+        ``'shooting'``).
+    """
+    def __init__(self, shooting_index, partial_traj, scheme, ensemble=None,
+                 accepted=None, group_name='shooting'):
+        super(MockForwardShooting, self).__init__(
+            shooting_index=shooting_index,
+            partial_traj=partial_traj,
+            direction='forward',
+            scheme=scheme,
+            ensemble=ensemble,
+            accepted=accepted,
+            group_name=group_name
+        )
+
+
+class MockBackwardShooting(_MockOneWayShooting):
+    """Mock a backward shooting move.
+
+    Parameters
+    ----------
+    shooting_index : int
+        the index of the shooting point in the input trajectory
+    partial_traj : :class:`.Trajectory`
+        The trajectory to mock as coming from the engine. This is the
+        trajectory *without* the shooting point -- new frames only.
+    scheme : :class:`.MoveScheme`
+        the move scheme used in the mock simulation
+    ensemble : :class:`.Ensemble` or None
+        the ensemble to shoot from. If ``None``, a random mover is selected
+        from the movers in ``group_name``.
+    accepted : bool or None
+        if ``True`` or ``False``, force the acceptance to by true or false,
+        respectively. If ``None`` (default) use the internal logic to
+        determine acceptance. NOTE: This only bypasses the Monte Carlo
+        proposal probability calculation. Moves that are lot legally
+        possible to accept will raise an AnalysisTestSetupError if you try
+        to force them to be accepted.
+    group_name : str
+        the name of the groups to select movers from (default
+        ``'shooting'``).
+    """
+
+    def __init__(self, shooting_index, partial_traj, scheme, ensemble=None,
+                 accepted=None, group_name='shooting'):
+        super(MockBackwardShooting, self).__init__(
+            shooting_index=shooting_index,
+            partial_traj=partial_traj,
+            direction='backward',
+            scheme=scheme,
+            ensemble=ensemble,
+            accepted=accepted,
+            group_name=group_name
+        )
+
+
+class MockRepex(MockMove):
+    """Mock a replica exchange move.
+
+    Parameters
+    ----------
+    scheme : :class:`.MoveScheme`
+        the move scheme used in the mock simulation
+    ensembles : List[:class:`.Ensemble`] or None
+        The input ensembles for the replica exchange move. If ``None``, a
+        random mover is selected.
+    group_name : str
+        the name of the groups to select movers from (default
+        ``'repex'``).
+    """
+    def __init__(self, scheme, ensembles=None, group_name='repex'):
+        super(MockRepex, self).__init__(scheme=scheme,
+                                        ensembles=ensembles,
+                                        group_name=group_name)
+
+
+class MockPathReversal(_MockSingleEnsembleMove):
+    """Mock a path reversal  move.
+
+    Parameters
+    ----------
+    scheme : :class:`.MoveScheme`
+        the move scheme used in the mock simulation
+    ensemble : List[:class:`.Ensemble`] or None
+        The input ensemble for the path reversal move. If ``None``, a
+        random mover is selected.
+    group_name : str
+        the name of the groups to select movers from (default
+        ``'pathreversal'``).
+    """
+    def __init__(self, scheme, ensemble=None, group_name='pathreversal'):
+        super(MockPathReversal, self).__init__(scheme=scheme,
+                                               ensemble=ensemble,
+                                               group_name=group_name)
+
+
+def _do_single_step(init_conds, move, org_by_group):
+    change = move(init_conds)
+    if org_by_group:
+        change = move.wrap_org_by_group(change, init_conds)
+    return change
+
+
+def run_moves(init_conds, moves, org_by_group=True):
+    """Run a sequence of moves.
+
+    Once the individual moves have been mocked up, this runs them to iterate
+    over the steps that you would get from the simulation.
+
+    Since this is a generator, you may want to wrap it in a list when using
+    it.
+
+    Parameters
+    ----------
+    init_conds : :class:`.SampleSet`
+        initial conditions for this run
+    moves : List[:class:`.MockMove`]
+        the moves to run, in the order they will be applied
+    org_by_group : bool
+        whether to wrap each resulting change to include the structural move
+        changes that come from the :class:`.OrganizeByMoveGroupStrategy`.
+
+    Yields
+    ------
+    :class:`.MCStep`
+        the step for each mock move
+    """
+    for stepnum, move in enumerate(moves):
+        change = _do_single_step(init_conds, move,
+                                 org_by_group=org_by_group)
+        new_conds = init_conds.apply_samples(change.results)
+        step = paths.MCStep(
+            simulation=None,
+            mccycle=stepnum,
+            active=new_conds,
+            change=change
+        )
+        yield step
+        init_conds = new_conds
diff --git a/openpathsampling/tests/analysis/utils/test_fixture_classes.py b/openpathsampling/tests/analysis/utils/test_fixture_classes.py
new file mode 100644
index 000000000..a4a768ca6
--- /dev/null
+++ b/openpathsampling/tests/analysis/utils/test_fixture_classes.py
@@ -0,0 +1,39 @@
+import pytest
+
+from openpathsampling.tests.analysis.utils.fixture_classes import *
+
+
+@pytest.mark.parametrize('bounds', [(-1, 10), (-1, 5)])
+def test_make_trajectory(bounds, default_two_state_tps):
+    make_trajectory = default_two_state_tps.make_trajectory
+    lower, upper = bounds
+    traj = make_trajectory(lower, upper)
+    xvals = traj.xyz[:, 0, 0]
+    assert upper <= max(xvals) < upper + 1
+    assert lower <= min(xvals) < lower + 1
+    expected_len = upper - lower + 1
+    assert len(traj) == expected_len
+
+
+@pytest.mark.parametrize('lower_bound', [None, -2])
+def test_make_tis_trajectory(lower_bound, default_unidirectional_tis):
+    make_tis_trajectory = default_unidirectional_tis.make_tis_trajectory
+    cv_max = 5
+    kwargs = {'lower_bound': lower_bound} if lower_bound is not None else {}
+    traj = make_tis_trajectory(cv_max, **kwargs)
+    xvals = traj.xyz[:, 0, 0]
+
+    if lower_bound is None:
+        lower_bound = -1
+
+    assert lower_bound <= min(xvals) < lower_bound + 1
+    assert cv_max <= max(xvals) < cv_max + 1
+    expected_len = 2 * (cv_max - lower_bound) + 1
+    assert len(traj) == expected_len
+
+
+def test_make_tis_trajectory_transition(default_unidirectional_tis):
+    make_tis_trajectory = default_unidirectional_tis.make_tis_trajectory
+    traj = make_tis_trajectory(10)
+    xvals = traj.xyz[:, 0, 0]
+    assert max(xvals) == xvals[-1]
diff --git a/openpathsampling/tests/analysis/utils/test_mock_movers.py b/openpathsampling/tests/analysis/utils/test_mock_movers.py
new file mode 100644
index 000000000..51101c527
--- /dev/null
+++ b/openpathsampling/tests/analysis/utils/test_mock_movers.py
@@ -0,0 +1,331 @@
+import pytest
+
+try:
+    from unittest.mock import Mock
+except ImportError:  # pragma: no cover
+    from mock import Mock
+
+import collections
+import openpathsampling as paths
+
+from openpathsampling.tests.test_helpers import make_1d_traj
+
+from openpathsampling.tests.analysis.utils.fixture_classes import (
+    TPSSystemFixture
+)
+from openpathsampling.tests.analysis.utils.mock_movers import *
+from openpathsampling.tests.analysis.utils.mock_movers import (
+    _select_by_input_ensembles, _get_only
+)
+
+
+def test_get_only():
+    test_list = ['foo', 'bar', 'baz', 'qux']
+    only = _get_only(iterable=test_list,
+                     condition=lambda s: s.startswith('f'),
+                     error_msg="string starting with f")
+    assert only == 'foo'
+
+
+def test_get_only_error():
+    test_list = ['foo', 'bar', 'baz', 'qux']
+    with pytest.raises(AnalysisTestSetupError, match="found 2"):
+        _get_only(test_list,
+                  condition=lambda s: s.startswith('b'),
+                  error_msg="string starting with b")
+
+    with pytest.raises(AnalysisTestSetupError, match="found 0"):
+        _get_only(test_list,
+                  condition=lambda s: s.startswith('z'),
+                  error_msg="string starting with z")
+
+
+@pytest.mark.parametrize('ensemble', [None, 'ensemble'])
+def test_select_by_input_ensembles(default_unidirectional_tis, ensemble):
+    scheme = default_unidirectional_tis.scheme
+    movers = scheme.movers['shooting']
+    if ensemble == 'ensemble':
+        ensemble = movers[0].input_ensembles[0]
+
+    selected = _select_by_input_ensembles(movers, ensemble)
+    assert selected in movers  # this is enough for ensemble=None
+    if ensemble is not None:
+        assert selected == movers[0]
+
+
+def test_random_choice_mover(default_unidirectional_tis):
+    scheme = default_unidirectional_tis.scheme
+    mover = scheme.movers['shooting'][0]
+    target_change = Mock(mover=mover)
+    shooting_choosers = [group for group in scheme.root_mover.submovers
+                         if mover in group.submovers]
+    assert len(shooting_choosers) == 1
+    shooting_chooser = shooting_choosers[0]
+    # make sure we got the right one
+    assert shooting_chooser.name == "ShootingChooser"
+
+    random_choice = MockRandomChoiceMover(shooting_chooser, target_change)
+    change = random_choice('foo')  # exact inputs are ignored
+    assert change.mover is shooting_chooser
+    assert change.subchanges[0] is target_change
+
+
+def test_random_choice_mover_error(default_unidirectional_tis):
+    scheme = default_unidirectional_tis.scheme
+    mover = scheme.movers['repex'][0]
+    target_change = Mock(mover=mover)
+    shoot_mover = scheme.movers['shooting'][0]
+    shooting_choosers = [group for group in scheme.root_mover.submovers
+                         if shoot_mover in group.submovers]
+    assert len(shooting_choosers) == 1
+    shooting_chooser = shooting_choosers[0]
+    # make sure we got the right one
+    assert shooting_chooser.name == "ShootingChooser"
+
+    random_choice = MockRandomChoiceMover(shooting_chooser, target_change)
+    with pytest.raises(AnalysisTestSetupError, match="not found as sub"):
+        random_choice('foo')
+
+
+def _setup_one_way_forward(scheme, accepted):
+    make_trajectory = TPSSystemFixture.make_trajectory
+    partial_traj = make_trajectory(-1, 2).reversed
+    # * ensemble 0 is always accepted because the trial trajectory is
+    #   shorter than the input trajectory
+    # * ensemble 2 is always rejected because the trial trajectory doesn't
+    #   cross the interface (therefore doesn't satisfy the ensemble)
+    ensemble_idx = {True: 0, False: 2}[accepted]
+    ensemble = scheme.network.sampling_ensembles[ensemble_idx]
+    return ensemble, partial_traj
+
+
+def _setup_one_way_backward(scheme, accepted):
+    ensemble = scheme.network.sampling_ensembles[2]
+    # * accepted trajectory always accepted because trial trajectory is
+    #   shorter than the input trajectory
+    # * rejected trajectory always rejected because it is B->B (does not
+    #   satisfy the ensemble)
+    make_trajectory = TPSSystemFixture.make_trajectory
+    partial_traj = {True: make_trajectory(-1, 1).reversed,
+                    False: make_trajectory(-3, 10)}[accepted]
+    return ensemble, partial_traj
+
+
+@pytest.mark.parametrize('direction', ['forward', 'backward'])
+@pytest.mark.parametrize('accepted', [True, False])
+def test_one_way_shooting_move(default_unidirectional_tis, direction,
+                               accepted):
+    scheme = default_unidirectional_tis.scheme
+    t1 = default_unidirectional_tis.make_tis_trajectory(5)
+    t2 = default_unidirectional_tis.make_tis_trajectory(10)
+    init_conds = scheme.initial_conditions_from_trajectories([t1, t2])
+    shooting_idx = 4
+
+    ensemble, partial_traj = {
+        'forward': _setup_one_way_forward(scheme, accepted),
+        'backward': _setup_one_way_backward(scheme, accepted)
+    }[direction]
+
+    initial_traj = init_conds[ensemble].trajectory
+
+    len_expected = {
+        'forward': shooting_idx + 1 + len(partial_traj),
+        'backward': len(partial_traj) + len(initial_traj) - shooting_idx
+    }[direction]
+
+    trial_shooting_idx = {'forward': shooting_idx,
+                          'backward': len(partial_traj)}[direction]
+
+    movetype = {'forward': MockForwardShooting,
+                'backward': MockBackwardShooting}[direction]
+
+    move = movetype(shooting_index=shooting_idx,
+                    partial_traj=partial_traj,
+                    scheme=scheme,
+                    ensemble=ensemble)
+
+    assert move.ensemble == ensemble
+
+    change = move(init_conds)
+
+    # check that this looks like a move change from shooting
+    assert len(change.trials) == 1
+    trial_trajectory = change.trials[0].trajectory
+    shooting_snapshot = change.subchanges[0].details.shooting_snapshot
+
+    # check correctness of the results
+    assert change.accepted is accepted
+    assert len(trial_trajectory) == len_expected
+    assert shooting_snapshot is initial_traj[shooting_idx]
+    assert shooting_snapshot in trial_trajectory
+    assert shooting_snapshot is trial_trajectory[trial_shooting_idx]
+
+
+@pytest.mark.parametrize('accepted', [True, False, None])
+def test_shooting_move_force_accept(default_unidirectional_tis, accepted):
+    # If the a given trial can be either accepted or rejected, then the
+    # resulting change depends on the `accept` parameter of the mock move --
+    # True accepts, False rejects, and None uses the default internal math.
+    # Test this by trying each 25 times on a trial with a 50% acceptance
+    # probability.
+    # only test this with the forward shooting mover, since the logic is
+    # shared with backward
+    scheme = default_unidirectional_tis.scheme
+    init_traj = default_unidirectional_tis.make_tis_trajectory(10)
+    partial_traj = make_1d_traj([9.5] * (len(init_traj)-2) + [10.5])
+    init_conds = scheme.initial_conditions_from_trajectories(init_traj)
+    ensemble = scheme.network.sampling_ensembles[2]
+    shooting_idx = len(init_traj) - 2
+
+    move = MockForwardShooting(shooting_index=shooting_idx,
+                               partial_traj=partial_traj,
+                               accepted=accepted,
+                               scheme=scheme,
+                               ensemble=ensemble)
+
+    n_attempts = 25
+    changes = [move(init_conds) for _ in range(n_attempts)]
+    # TODO: add assert to check that the pick probability is as expected?
+    results = collections.Counter(change.accepted for change in changes)
+    if accepted is not None:
+        assert results[accepted] == n_attempts
+        assert not accepted not in results
+    else:
+        assert results[True] > 0
+        assert results[False] > 0
+        assert results[True] + results[False] == n_attempts
+
+
+def test_reject_nonsense_forced_acceptance(default_unidirectional_tis):
+    # if a user requires that the shooting move be accepted, but the given
+    # trajectory cannot be accepted because it doesn't match the ensemble,
+    # we should raise an error
+    scheme = default_unidirectional_tis.scheme
+    init_traj = default_unidirectional_tis.make_tis_trajectory(10)
+    init_conds = scheme.initial_conditions_from_trajectories(init_traj)
+    # get a setup that should never be accepted
+    ensemble, partial_traj = _setup_one_way_backward(scheme, accepted=False)
+    shooting_idx = 4
+    move = MockBackwardShooting(shooting_index=shooting_idx,
+                                partial_traj=partial_traj,
+                                accepted=True,
+                                scheme=scheme,
+                                ensemble=ensemble)
+
+    with pytest.raises(AnalysisTestSetupError, match="force acceptance"):
+        move(init_conds)
+
+
+@pytest.mark.parametrize('accepted', [True, False])
+def test_repex_move(default_unidirectional_tis, accepted):
+    scheme = default_unidirectional_tis.scheme
+    t1 = default_unidirectional_tis.make_tis_trajectory(4)
+    t2 = default_unidirectional_tis.make_tis_trajectory(10)
+    init_conds = scheme.initial_conditions_from_trajectories([t1, t2])
+    assert init_conds[0].trajectory is t1 and init_conds[1].trajectory is t1
+    assert init_conds[2].trajectory is t2
+
+    if accepted:
+        e2, e1 = scheme.network.sampling_ensembles[:2]
+    else:
+        e2, e1 = scheme.network.sampling_ensembles[1:]
+
+    ensembles = e1, e2
+    move = MockRepex(scheme, ensembles)
+    change = move(init_conds)
+    assert change.accepted is accepted
+    assert isinstance(change.canonical.mover, paths.ReplicaExchangeMover)
+
+
+@pytest.mark.parametrize('accepted', [True, False])
+def test_mock_pathreversal(default_unidirectional_tis, accepted):
+    scheme = default_unidirectional_tis.scheme
+    move = MockPathReversal(scheme)
+    maxval = {True: 6, False: 10}[accepted]
+    init_traj = default_unidirectional_tis.make_tis_trajectory(maxval)
+    init_conds = scheme.initial_conditions_from_trajectories(init_traj)
+    change = move(init_conds)
+    assert change.accepted is accepted
+    assert isinstance(change.canonical.mover, paths.PathReversalMover)
+
+
+@pytest.mark.parametrize('accepted', [True, False])
+def test_wrap_org_by_group(default_unidirectional_tis, accepted):
+    # the specific mover doesn't matter; path reversal is easy
+    scheme = default_unidirectional_tis.scheme
+    move = MockPathReversal(scheme)
+    maxval = {True: 6, False: 10}[accepted]
+    init_traj = default_unidirectional_tis.make_tis_trajectory(maxval)
+    init_conds = scheme.initial_conditions_from_trajectories(init_traj)
+    canonical = move(init_conds)
+    wrapped = move.wrap_org_by_group(canonical, init_conds)
+    assert wrapped.mover is scheme.root_mover
+    assert wrapped.subchanges[0].mover.name == "PathreversalChooser"
+    assert wrapped.canonical is canonical
+    assert wrapped.subchanges[0].subchanges[0] is canonical
+
+
+@pytest.mark.parametrize('accepted', [True, False])
+def test_run_moves_single(default_unidirectional_tis, accepted):
+    # check that a single accepted step gives an MCStep with results that
+    # match the expected active for accepted/rejected steps
+    scheme = default_unidirectional_tis.scheme
+    make_tis_trajectory = default_unidirectional_tis.make_tis_trajectory
+    traj = {True: make_tis_trajectory(5),
+            False: make_tis_trajectory(10)}[accepted]
+    init_conds = scheme.initial_conditions_from_trajectories(traj)
+    ensemble = scheme.network.sampling_ensembles[0]
+    move = MockPathReversal(scheme, ensemble=ensemble)
+    steplist = list(run_moves(init_conds, [move]))
+
+    assert len(steplist) == 1
+    step = steplist[0]
+    assert isinstance(step, paths.MCStep)
+    assert isinstance(step.change.canonical.mover, paths.PathReversalMover)
+    assert step.change.accepted is accepted
+    if accepted:
+        assert step.active[ensemble].trajectory == traj.reversed
+    else:
+        assert step.active[ensemble] is init_conds[ensemble]
+
+
+def test_run_moves_multiple(default_unidirectional_tis):
+    scheme = default_unidirectional_tis.scheme
+    traj = default_unidirectional_tis.make_tis_trajectory(10)
+    make_trajectory = default_unidirectional_tis.make_trajectory
+    init_conds = scheme.initial_conditions_from_trajectories(traj)
+    ensemble = scheme.network.sampling_ensembles[2]
+
+    moves = [
+        # first move is force-accepted (always accepted anyway)
+        MockForwardShooting(
+            shooting_index=8,
+            partial_traj=make_trajectory(8, 10),
+            accepted=True,
+            scheme=scheme,
+            ensemble=ensemble
+        ),
+        # second move is rejected (bad ensemble)
+        MockBackwardShooting(
+            shooting_index=4,
+            partial_traj=make_trajectory(3, 10),
+            scheme=scheme,
+            ensemble=ensemble
+        ),
+        # third move is force accepted
+        MockBackwardShooting(
+            shooting_index=9,
+            partial_traj=make_trajectory(-1, 8).reversed,
+            accepted=True,
+            scheme=scheme,
+            ensemble=ensemble
+        )
+    ]
+
+    steps = list(run_moves(init_conds, moves))
+    initial_traj = init_conds[ensemble].trajectory
+    final_traj = steps[-1].active[ensemble].trajectory
+
+    assert len(steps) == 3
+    assert [step.change.accepted for step in steps] == [True, False, True]
+    assert not initial_traj.is_correlated(final_traj)
diff --git a/openpathsampling/tests/pathsimulators/test_hooks.py b/openpathsampling/tests/pathsimulators/test_hooks.py
index 4ba5c01f3..3a23e94bf 100644
--- a/openpathsampling/tests/pathsimulators/test_hooks.py
+++ b/openpathsampling/tests/pathsimulators/test_hooks.py
@@ -170,7 +170,7 @@ def test_no_step_hook_simulation_hooks(self):
 
 
 class TestSelfOrSimProperty(object):
-    def setup(self):
+    def setup_method(self):
         self.attr_name = "test_attr"
         self.simulation = MagicMock(test_attr="sim_attr")
         self.sans_attr_sans_sim = MagicMock(_test_attr=None, _simulation=None)
@@ -226,7 +226,7 @@ def test_self_or_sim_property_helper_func(self, hook_name):
 
 
 class TestStorageHook(object):
-    def setup(self):
+    def setup_method(self):
         self.storage = MagicMock(spec=["stash", "sync_all"])
         self.old_storage = MagicMock(spec=["save", "sync_all"])
         self.empty_hook = StorageHook()
@@ -267,7 +267,7 @@ def test_after_simulation(self):
 
 
 class TestShootFromSnapshotsOutputHook(object):
-    def setup(self):
+    def setup_method(self):
         if sys.version_info < (3,):
             self.stream = io.BytesIO()
         else:
@@ -295,7 +295,7 @@ def test_before_step(self):
 
 
 class TestSampleSetSanityCheckHook(object):
-    def setup(self):
+    def setup_method(self):
         self.simulation = MagicMock(save_frequency=10)
         self.empty_hook = SampleSetSanityCheckHook()
         self.hook = SampleSetSanityCheckHook(frequency=10)
@@ -319,7 +319,7 @@ def test_after_step(self, step_num):
 
 
 class TestLiveVisualizerHook(object):
-    def setup(self):
+    def setup_method(self):
         self.live_visualizer = MagicMock()
         self.simulation = MagicMock(live_visualizer=self.live_visualizer,
                                     status_update_frequency=2)
@@ -363,7 +363,7 @@ def test_integration(self):
 
 
 class TestPathSamplingOutputHook(object):
-    def setup(self):
+    def setup_method(self):
         if sys.version_info < (3,):
             self.stream = io.BytesIO()
         else:
@@ -434,7 +434,7 @@ def test_after_simulation(self):
 
 
 class TestGraciousKillHook(object):
-    def setup(self):
+    def setup_method(self):
         self.final_call = MagicMock()
         self.nocall_final_call = MagicMock()
         self.simulation = MagicMock(storage=MagicMock(close=MagicMock(),
diff --git a/openpathsampling/tests/test_attribute.py b/openpathsampling/tests/test_attribute.py
index e03d5e96b..88226ee33 100644
--- a/openpathsampling/tests/test_attribute.py
+++ b/openpathsampling/tests/test_attribute.py
@@ -15,7 +15,7 @@
 
 
 class TestFunctionPseudoAttribute(object):
-    def setup(self):
+    def setup_method(self):
         if not md:
             raise SkipTest("mdtraj not installed")
         self.mdtraj = md.load(data_filename("ala_small_traj.pdb"))
@@ -30,7 +30,7 @@ def setup(self):
         if os.path.isfile("myfile.nc"):
             os.remove("myfile.nc")
 
-    def teardown(self):
+    def teardown_method(self):
         if os.path.isfile("myfile.nc"):
             os.remove("myfile.nc")
 
diff --git a/openpathsampling/tests/test_bias_function.py b/openpathsampling/tests/test_bias_function.py
index 00db02909..57196b04a 100644
--- a/openpathsampling/tests/test_bias_function.py
+++ b/openpathsampling/tests/test_bias_function.py
@@ -24,13 +24,13 @@
 
 
 class TestBiasEnsembleTable(object):
-    def setup(self):
+    def setup_method(self):
         # create the network
         paths.InterfaceSet._reset()
         xval = paths.FunctionCV(name="xA", f=lambda s : s.xyz[0][0])
         self.stateA = paths.CVDefinedVolume(xval, -1.0, -0.5).named("A")
         self.stateB = paths.CVDefinedVolume(xval, 0.5, float("inf")).named("B")
-        ifacesA = paths.VolumeInterfaceSet(xval, float(-1.0), 
+        ifacesA = paths.VolumeInterfaceSet(xval, float(-1.0),
                                            [-0.5, -0.4, -0.3, -0.2])
         self.network = paths.MISTISNetwork([
             (self.stateA, ifacesA, self.stateB)
@@ -76,7 +76,7 @@ def test_bias_ensemble_new_to_old(self):
 
         # for old_to_new, the probability of moving outerward depends on the
         # ratio of the probabilities of the two ensembles
-        change_vals = { 
+        change_vals = {
             self.change_01 : 0.5,
             self.change_02 : 0.2,
             self.change_12 : old_div(0.2, 0.5),
@@ -233,13 +233,13 @@ def test_add_biases(self):
         bias_ABC = bias_A + bias_B + bias_C
 
 class TestSRTISBiasFromNetwork(object):
-    def setup(self):
+    def setup_method(self):
         paths.InterfaceSet._reset()
         xval = paths.CoordinateFunctionCV(name="xA",
                                           f=lambda s : s.xyz[0][0])
         self.stateA = paths.CVDefinedVolume(xval, -1.0, -0.5).named("A")
         self.stateB = paths.CVDefinedVolume(xval, 0.5, float("inf")).named("B")
-        self.ifacesA = paths.VolumeInterfaceSet(xval, -1.0, 
+        self.ifacesA = paths.VolumeInterfaceSet(xval, -1.0,
                                                 [-0.5, -0.4, -0.3, -0.2])
         self.ifacesB = paths.VolumeInterfaceSet(xval, [0.5, 0.4, 0.3, 0.2],
                                                 1.0)
diff --git a/openpathsampling/tests/test_channel_analysis.py b/openpathsampling/tests/test_channel_analysis.py
index df9b8915c..708615a1c 100644
--- a/openpathsampling/tests/test_channel_analysis.py
+++ b/openpathsampling/tests/test_channel_analysis.py
@@ -18,7 +18,7 @@
 logging.getLogger('openpathsampling.netcdfplus').setLevel(logging.CRITICAL)
 
 class TestChannelAnalysis(object):
-    def setup(self):
+    def setup_method(self):
         cv = paths.FunctionCV("Id", lambda snap: snap.xyz[0][0])
         self.state_A = paths.CVDefinedVolume(cv, -0.1, 0.1)
         self.state_B = ~paths.CVDefinedVolume(cv, -1.0, 1.0)
diff --git a/openpathsampling/tests/test_collectivevariable.py b/openpathsampling/tests/test_collectivevariable.py
index 7f47b9e68..6d1fe8ec0 100644
--- a/openpathsampling/tests/test_collectivevariable.py
+++ b/openpathsampling/tests/test_collectivevariable.py
@@ -29,7 +29,7 @@
 
 
 class TestFunctionCV(object):
-    def setup(self):
+    def setup_method(self):
         if not md:
             raise SkipTest("mdtraj not installed")
         self.mdtraj = md.load(data_filename("ala_small_traj.pdb"))
@@ -44,7 +44,7 @@ def setup(self):
         if os.path.isfile("myfile.nc"):
             os.remove("myfile.nc")
 
-    def teardown(self):
+    def teardown_method(self):
         if os.path.isfile("myfile.nc"):
             os.remove("myfile.nc")
 
diff --git a/openpathsampling/tests/test_data/tip4p_water.pdb b/openpathsampling/tests/test_data/tip4p_water.pdb
new file mode 100644
index 000000000..eb525ec08
--- /dev/null
+++ b/openpathsampling/tests/test_data/tip4p_water.pdb
@@ -0,0 +1,9 @@
+CRYST1   22.336   22.336   22.336  90.00  90.00  90.00 P 1           1 
+MODEL        0
+ATOM      1  O   HOH C   1      12.324   8.849  14.064  1.00  0.00           O  
+ATOM      2  H1  HOH C   1      13.242   9.113  14.118  1.00  0.00           H  
+ATOM      3  H2  HOH C   1      11.885   9.589  13.645  1.00  0.00           H  
+ATOM      4 M    HOH C   1      12.375   8.956  14.025  1.00  0.00          VS  
+TER       4      HOH C   1
+ENDMDL
+END
diff --git a/openpathsampling/tests/test_deprecations.py b/openpathsampling/tests/test_deprecations.py
index b317474a5..9f21c4bfd 100644
--- a/openpathsampling/tests/test_deprecations.py
+++ b/openpathsampling/tests/test_deprecations.py
@@ -51,7 +51,7 @@ def test_version_tuple_to_string(version_tup, version_str):
     assert version_tuple_to_string(version_tup) == version_str
 
 class TestDeprecation(object):
-    def setup(self):
+    def setup_method(self):
         self.foo, self.bar = make_foo_bar_baz()[0:2]
 
     def test_message(self):
@@ -78,7 +78,7 @@ def test_warn_multiple(self):
 
 
 class TestDeprecationDecorators(object):
-    def setup(self):
+    def setup_method(self):
         self.foo, self.bar, self.baz = make_foo_bar_baz()
 
         @deprecate(self.foo)
diff --git a/openpathsampling/tests/test_dynamicsengine.py b/openpathsampling/tests/test_dynamicsengine.py
index 9ae8a0c23..48927f6db 100644
--- a/openpathsampling/tests/test_dynamicsengine.py
+++ b/openpathsampling/tests/test_dynamicsengine.py
@@ -1,11 +1,17 @@
 from __future__ import absolute_import
 from builtins import object
+import pytest
 import openpathsampling as paths
 
 from nose.tools import (assert_equal, assert_not_equal, raises, assert_true)
 from nose.plugins.skip import SkipTest
 from .test_helpers import make_1d_traj, raises_with_message_like
 
+try:
+    from unittest import mock
+except ImportError:
+    import mock
+
 
 class StupidEngine(paths.engines.DynamicsEngine):
     _default_options = {'random_option': False}
@@ -22,9 +28,20 @@ def property_recovers(self):
             raise AttributeError("Internal error")
         return self.attempted
 
+    def generate_next_frame(self):
+        return make_1d_traj([0.0])[0]
+
+    @property
+    def current_snapshot(self):
+        return make_1d_traj([0.0])[0]
+
+    @current_snapshot.setter
+    def current_snapshot(self, snap):
+        pass
+
 
 class TestDynamicsEngine(object):
-    def setup(self):
+    def setup_method(self):
         options = {'n_frames_max' : 100, 'random_option' : True}
 
         snapshot_dimensions = {
@@ -68,12 +85,27 @@ def test_getattr_does_not_exist(self):
         self.stupid.foo
 
     def test_getattr_dimension(self):
-        assert(self.engine.n_atoms == 1)
-        assert (self.engine.n_spatial == 1)
-        assert(self.stupid.n_atoms == 1)
-        assert (self.stupid.n_spatial == 1)
+        assert self.engine.n_atoms == 1
+        assert self.engine.n_spatial == 1
+        assert self.stupid.n_atoms == 1
+        assert self.stupid.n_spatial == 1
 
     def test_unicode_options(self):
         # regression test: this should NOT raise an error
         options = {'on_nan': u'fail'}
         engine = paths.engines.DynamicsEngine(options, self.descriptor)
+
+    @pytest.mark.parametrize('stoppable', ['first', 'last'])
+    def test_generate_multiple_running_conditions(self, stoppable):
+        # test to ensure that, when multiple continue conditions are
+        # defined, we stop if any of them tell us to stop
+        mock_true_false_true = mock.Mock(side_effect=[True, False, True])
+        mock_true = mock.Mock(return_value=True)
+
+        init_snap = make_1d_traj([0.0])[0]
+        conditions = {'first': [mock_true_false_true, mock_true],
+                      'last': [mock_true, mock_true_false_true]}[stoppable]
+        # this raises an StopIteration (and then RuntimeError) when it
+        # doesn't work
+        traj = self.stupid.generate(init_snap, conditions)
+        assert len(traj) == 2
diff --git a/openpathsampling/tests/test_ensemble.py b/openpathsampling/tests/test_ensemble.py
index f1bfd945b..8c1bb4234 100644
--- a/openpathsampling/tests/test_ensemble.py
+++ b/openpathsampling/tests/test_ensemble.py
@@ -195,7 +195,7 @@ def _run(self, results):
 
 
 class TestPartOutXEnsemble(EnsembleTest):
-    def setup(self):
+    def setup_method(self):
         self.leaveX = PartOutXEnsemble(vol1)
 
     def test_leaveX(self):
@@ -238,11 +238,11 @@ def test_leaveX_0(self):
 
     def test_leaveX_str(self):
         volstr = "{x|Id(x) in [0.1, 0.5]}"
-        assert_equal(self.leaveX.__str__(), 
+        assert_equal(self.leaveX.__str__(),
                      "exists t such that x[t] in (not "+volstr+")")
 
 class TestAllInXEnsemble(EnsembleTest):
-    def setup(self):
+    def setup_method(self):
         self.inX = AllInXEnsemble(vol1)
 
     def test_inX(self):
@@ -306,7 +306,7 @@ def test_inX_str(self):
                      "x[t] in "+volstr+" for all t")
 
 class TestAllOutXEnsemble(EnsembleTest):
-    def setup(self):
+    def setup_method(self):
         self.outX = AllOutXEnsemble(vol1)
 
     def test_outX(self):
@@ -369,7 +369,7 @@ def test_outX_str(self):
                      "x[t] in (not "+volstr+") for all t")
 
 class TestPartInXEnsemble(EnsembleTest):
-    def setup(self):
+    def setup_method(self):
         self.hitX = PartInXEnsemble(vol1)
 
     def test_hitX(self):
@@ -407,7 +407,7 @@ def test_hitX_str(self):
 
 
 class TestSequentialEnsemble(EnsembleTest):
-    def setup(self):
+    def setup_method(self):
         self.inX = AllInXEnsemble(vol1)
         self.outX = AllOutXEnsemble(vol1)
         self.hitX = PartInXEnsemble(vol1)
@@ -533,10 +533,10 @@ def test_can_append_tis(self):
                         'lower_in_out_in_in' : False,
                         'upper_in_out_in_out_in' : False,
                         'lower_in_out_in_out_in' : False
-                    }   
+                    }
         for test in list(results.keys()):
             failmsg = "Failure in "+test+"("+str(ttraj[test])+"): "
-            self._single_test(self.pseudo_tis.can_append, 
+            self._single_test(self.pseudo_tis.can_append,
                                 ttraj[test], results[test], failmsg)
 
     def test_strict_can_append_tis(self):
@@ -559,10 +559,10 @@ def test_strict_can_append_tis(self):
             'lower_in_out_in_in' : False,
             'upper_in_out_in_out_in' : False,
             'lower_in_out_in_out_in' : False
-        }   
+        }
         for test in list(results.keys()):
             failmsg = "Failure in "+test+"("+str(ttraj[test])+"): "
-            self._single_test(self.pseudo_tis.strict_can_append, 
+            self._single_test(self.pseudo_tis.strict_can_append,
                                 ttraj[test], results[test], failmsg)
 
     def test_can_append_pseudominus(self):
@@ -604,10 +604,10 @@ def test_can_append_pseudominus(self):
             'lower_cross_in_cross_in' : False,
             'upper_in_cross_in_cross_in' : False,
             'lower_in_cross_in_cross_in' : False
-        }   
+        }
         for test in list(results.keys()):
             failmsg = "Failure in "+test+"("+str(ttraj[test])+"): "
-            self._single_test(self.pseudo_minus.can_append, 
+            self._single_test(self.pseudo_minus.can_append,
                                 ttraj[test], results[test], failmsg)
 
     def test_strict_can_append_pseudominus(self):
@@ -648,10 +648,10 @@ def test_strict_can_append_pseudominus(self):
             'lower_cross_in_cross_in' : False,
             'upper_in_cross_in_cross_in' : False,
             'lower_in_cross_in_cross_in' : False
-        }   
+        }
         for test in list(results.keys()):
             failmsg = "Failure in "+test+"("+str(ttraj[test])+"): "
-            self._single_test(self.pseudo_minus.strict_can_append, 
+            self._single_test(self.pseudo_minus.strict_can_append,
                                 ttraj[test], results[test], failmsg)
 
     def test_can_prepend_pseudo_tis(self):
@@ -675,10 +675,10 @@ def test_can_prepend_pseudo_tis(self):
             'lower_in_out_in_in' : False,
             'upper_in_out_in_out_in' : False,
             'lower_in_out_in_out_in' : False
-        }   
+        }
         for test in list(results.keys()):
             failmsg = "Failure in "+test+"("+str(ttraj[test])+"): "
-            self._single_test(self.pseudo_tis.can_prepend, 
+            self._single_test(self.pseudo_tis.can_prepend,
                                 ttraj[test], results[test], failmsg)
 
     def test_strict_can_prepend_pseudo_tis(self):
@@ -701,10 +701,10 @@ def test_strict_can_prepend_pseudo_tis(self):
             'lower_in_out_in_in' : False,
             'upper_in_out_in_out_in' : False,
             'lower_in_out_in_out_in' : False
-        }   
+        }
         for test in list(results.keys()):
             failmsg = "Failure in "+test+"("+str(ttraj[test])+"): "
-            self._single_test(self.pseudo_tis.strict_can_prepend, 
+            self._single_test(self.pseudo_tis.strict_can_prepend,
                                 ttraj[test], results[test], failmsg)
 
     def test_can_prepend_pseudo_minus(self):
@@ -738,10 +738,10 @@ def test_can_prepend_pseudo_minus(self):
             'lower_out_in_out_in': True,
             'upper_out_in_in_out_in' : True,
             'lower_out_in_in_out_in' : True
-        }   
+        }
         for test in list(results.keys()):
             failmsg = "Failure in "+test+"("+str(ttraj[test])+"): "
-            self._single_test(self.pseudo_minus.can_prepend, 
+            self._single_test(self.pseudo_minus.can_prepend,
                                 ttraj[test], results[test], failmsg)
 
     def test_strict_can_prepend_pseudo_minus(self):
@@ -775,14 +775,14 @@ def test_strict_can_prepend_pseudo_minus(self):
             'lower_out_in_out_in': True,
             'upper_out_in_in_out_in' : True,
             'lower_out_in_in_out_in' : True
-        }   
+        }
         for test in list(results.keys()):
             failmsg = "Failure in "+test+"("+str(ttraj[test])+"): "
-            self._single_test(self.pseudo_minus.strict_can_prepend, 
+            self._single_test(self.pseudo_minus.strict_can_prepend,
                                 ttraj[test], results[test], failmsg)
 
 
-    
+
     def test_sequential_transition_frames(self):
         """SequentialEnsemble identifies transitions frames correctly"""
         ensemble = SequentialEnsemble([self.inX, self.outX])
@@ -793,7 +793,7 @@ def test_sequential_transition_frames(self):
                   }
         for test in list(results.keys()):
             failmsg = "Failure in "+test+"("+str(ttraj[test])+"): "
-            self._single_test(ensemble.transition_frames, 
+            self._single_test(ensemble.transition_frames,
                                 ttraj[test], results[test], failmsg)
 
     def test_sequential_simple_in_out_call(self):
@@ -806,7 +806,7 @@ def test_sequential_simple_in_out_call(self):
                   }
         for test in list(results.keys()):
             failmsg = "Failure in "+test+"("+str(ttraj[test])+"): "
-            self._single_test(ensemble, 
+            self._single_test(ensemble,
                                 ttraj[test], results[test], failmsg)
 
 
@@ -898,7 +898,7 @@ def test_sequential_generate_first_tis(self):
             logging.getLogger('openpathsampling.ensemble').info(
                 "Testing: "+str(test)
             )
-            self._single_test(ensemble, ttraj[test], 
+            self._single_test(ensemble, ttraj[test],
                               match_results[test], failmsg)
 
         append_results = {
@@ -916,7 +916,7 @@ def test_sequential_generate_first_tis(self):
             logging.getLogger('opentis.ensemble').info(
                 "Testing: "+str(test)
             )
-            self._single_test(ensemble.can_append, ttraj[test], 
+            self._single_test(ensemble.can_append, ttraj[test],
                               append_results[test], failmsg)
 
     def test_sequential_enter_exit(self):
@@ -951,7 +951,7 @@ class TestSequentialEnsembleCombination(EnsembleTest):
     # useful to making sure that the ensemble combination of strict_can_*
     # works correctly, since this is where strict and normal have a
     # distinction
-    def setup(self):
+    def setup_method(self):
         self.ens1 = SequentialEnsemble([
             AllInXEnsemble(vol1) & LengthEnsemble(1),
             AllOutXEnsemble(vol1) & PartOutXEnsemble(vol2),
@@ -1185,7 +1185,7 @@ def test_strict_can_prepend(self):
         self._test_everything(self.combo_and.strict_can_prepend, and_true, False)
 
 class TestTISEnsemble(EnsembleTest):
-    def setup(self):
+    def setup_method(self):
         self.tis = TISEnsemble(vol1, vol3, vol2, op)
         self.traj = ttraj['upper_in_out_cross_out_in']
         self.minl = min(op(self.traj))
@@ -1269,7 +1269,7 @@ def _was_cache_reset(self, cache):
         return cache.contents == { }
 
 class TestEnsembleCache(EnsembleCacheTest):
-    def setup(self):
+    def setup_method(self):
         self.fwd = EnsembleCache(direction=+1)
         self.rev = EnsembleCache(direction=-1)
         self.traj = ttraj['lower_in_out_in_in_out_in']
@@ -1366,7 +1366,7 @@ def test_trajectory_middle_frame_changes(self):
 
 
 class TestSequentialEnsembleCache(EnsembleCacheTest):
-    def setup(self):
+    def setup_method(self):
         self.inX = AllInXEnsemble(vol1)
         self.outX = AllOutXEnsemble(vol1)
         self.length1 = LengthEnsemble(1)
@@ -1499,7 +1499,7 @@ def test_sliced_as_TISEnsemble(self):
             failmsg = "Failure in "+test+"("+tstr(ttraj[test])+"): "
             self._single_test(real_tis, ttraj[test],
                               sequential_tis(ttraj[test]), failmsg)
-            self._single_test(sliced_tis, ttraj[test], 
+            self._single_test(sliced_tis, ttraj[test],
                               sequential_tis(ttraj[test]), failmsg)
 
     def test_slice_outside_trajectory_range(self):
@@ -1601,7 +1601,7 @@ def test_sliced_str(self):
                      "("+inXstr+" in {1:-1})")
 
 class TestOptionalEnsemble(EnsembleTest):
-    def setup(self):
+    def setup_method(self):
         self.start_opt = SequentialEnsemble([
             OptionalEnsemble(AllOutXEnsemble(vol1)),
             AllInXEnsemble(vol1),
@@ -1857,7 +1857,7 @@ def test_optional_str(self):
         assert_equal(opt_inX.__str__(), "{"+inX.__str__()+"} (OPTIONAL)")
 
 class TestPrefixTrajectoryEnsemble(EnsembleTest):
-    def setup(self):
+    def setup_method(self):
         self.inX = AllInXEnsemble(vol1)
 
     def test_bad_start_traj(self):
@@ -1907,7 +1907,7 @@ def test_caching_in_fwdapp_seq(self):
             outX,
             inX,
             outX,
-            inX & length1 
+            inX & length1
         ])
         traj = ttraj['upper_in_out_in_in_out_in']
         ens = PrefixTrajectoryEnsemble(pseudo_minus, traj[0:2])
@@ -1929,7 +1929,7 @@ def test_caching_in_fwdapp_seq(self):
 
 
 class TestSuffixTrajectoryEnsemble(EnsembleTest):
-    def setup(self):
+    def setup_method(self):
         xval = paths.FunctionCV("x", lambda s : s.xyz[0][0])
         vol = paths.CVDefinedVolume(xval, 0.1, 0.5)
         self.inX = AllInXEnsemble(vol)
@@ -1983,7 +1983,7 @@ def test_caching_in_bkwdprep_seq(self):
             self.outX,
             self.inX,
             self.outX,
-            self.inX & length1 
+            self.inX & length1
         ])
         traj = ttraj['upper_in_out_in_in_out_in']
 
@@ -2009,7 +2009,7 @@ def test_caching_in_bkwdprep_seq(self):
         assert_equal(ens._cache_can_prepend.trusted, True)
 
 class TestMinusInterfaceEnsemble(EnsembleTest):
-    def setup(self):
+    def setup_method(self):
         # Mostly we use minus ensembles where the state matches the first
         # interface. We also test the case where that isn't in, in which
         # case there's an interstitial zone. (Only test it for nl=2 to keep
@@ -2338,7 +2338,7 @@ def test_extend_sample_from_trajectories(self):
 
 # TODO: this whole class should become a single test in SeqEns
 class TestSingleEnsembleSequentialEnsemble(EnsembleTest):
-    def setup(self):
+    def setup_method(self):
         #self.inner_ens = AllInXEnsemble(vol1 | vol2)
         self.inner_ens = LengthEnsemble(3) & AllInXEnsemble( vol1 | vol2 )
         self.ens = SequentialEnsemble([self.inner_ens])
@@ -2355,7 +2355,7 @@ def test_it_all(self):
 
 
 class TestEnsembleSplit(EnsembleTest):
-    def setup(self):
+    def setup_method(self):
         self.inA = AllInXEnsemble(vol1)
         self.outA = AllOutXEnsemble(vol1)
 
@@ -2453,7 +2453,7 @@ def test_split(self):
         assert(traj3.subtrajectory_indices(sub_traj) == [2,3,4])
 
 class TestVolumeCombinations(EnsembleTest):
-    def setup(self):
+    def setup_method(self):
         self.outA = paths.AllOutXEnsemble(vol1)
         self.outB = paths.AllOutXEnsemble(~vol2)
         self.outA.special_debug = True
@@ -2475,7 +2475,7 @@ def setup(self):
     def _test_trusted(self, trajectory, function, results,
                       cache_results=None, direction=+1, start_traj_len=1):
         # Tests `trajectory` frame by frame in a forward direction for the
-        # `function`, expecting `results`. Additionally, can take the 
+        # `function`, expecting `results`. Additionally, can take the
 
         if cache_results is None:
             cache_results = {}
@@ -2577,8 +2577,8 @@ def test_call_outA_and_outB(self):
     def test_can_append_outA_or_outB(self):
         self._test_trusted(
             trajectory=self.local_ttraj['upper_out_in_out_out_cross'],
-            function=self.outA_or_outB.can_append, 
-            results=[True, True, True, True, False], 
+            function=self.outA_or_outB.can_append,
+            results=[True, True, True, True, False],
             cache_results={
                 self.outA._cache_can_append : [True, False, False, False, False],
                 self.outB._cache_can_append : [None, True, True, True, False]
diff --git a/openpathsampling/tests/test_external_engine.py b/openpathsampling/tests/test_external_engine.py
index 0b2a8d1c8..a294374db 100644
--- a/openpathsampling/tests/test_external_engine.py
+++ b/openpathsampling/tests/test_external_engine.py
@@ -7,6 +7,7 @@
 from openpathsampling.engines.external_engine import *
 
 import numpy as np
+import pytest
 
 import psutil
 
@@ -83,14 +84,15 @@ def setup_module():
 
 
 def teardown_module():
-    # proc = psutil.Popen("make clean", cwd=engine_dir, shell=True)
-    # proc.wait()
+    # Delete compilation files
+    proc = psutil.Popen(["make", "clean"], cwd=engine_dir)
+    proc.wait()
     for testfile in glob.glob("test*out") + glob.glob("test*inp"):
         os.remove(testfile)
 
 
 class TestExternalEngine(object):
-    def setup(self):
+    def setup_method(self):
         self.descriptor = SnapshotDescriptor.construct(
             snapshot_class=ToySnapshot,
             snapshot_dimensions={'n_spatial': 1,
@@ -100,13 +102,15 @@ def setup(self):
             'n_frames_max': 10000,
             'engine_sleep': 100,
             'name_prefix': "test",
-            'engine_directory': engine_dir
+            'engine_directory': engine_dir,
+            'filename_setter': FilenameSetter()
         }
         fast_options = {
             'n_frames_max': 10000,
             'engine_sleep': 0,
             'name_prefix': "test",
-            'engine_directory': engine_dir
+            'engine_directory': engine_dir,
+            'filename_setter': FilenameSetter()
         }
         self.template = peng.toy.Snapshot(coordinates=np.array([[0.0]]),
                                           velocities=np.array([[1.0]]))
@@ -118,6 +122,16 @@ def setup(self):
                                                  self.template)
         self.ensemble = paths.LengthEnsemble(5)
 
+    def test_deprecation(self):
+        slow_options = {'n_frames_max': 10000,
+                        'engine_sleep': 100,
+                        'name_prefix': "test",
+                        'engine_directory': engine_dir}
+        with pytest.warns(FutureWarning, match='filename_setter'):
+            _ = ExampleExternalEngine(slow_options,
+                                      self.descriptor,
+                                      self.template)
+
     def test_start_stop(self):
         eng = self.fast_engine
         # check that it isn't running yet
diff --git a/openpathsampling/tests/test_external_snapshots.py b/openpathsampling/tests/test_external_snapshots.py
index 8d63304ab..729048d97 100644
--- a/openpathsampling/tests/test_external_snapshots.py
+++ b/openpathsampling/tests/test_external_snapshots.py
@@ -45,7 +45,7 @@ def read_frame_data(self, filename, position):
 
 
 class TestExternalMDSnapshot(object):
-    def setup(self):
+    def setup_method(self):
         self.box = np.array([[1.0, 0.0], [0.0, 1.0]])
         self.vel = np.array([[1.0, 0.0]])
 
diff --git a/openpathsampling/tests/test_gromacs_engine.py b/openpathsampling/tests/test_gromacs_engine.py
index 39eaac993..906666b20 100644
--- a/openpathsampling/tests/test_gromacs_engine.py
+++ b/openpathsampling/tests/test_gromacs_engine.py
@@ -44,7 +44,7 @@
 
 
 class TestGroFileEngine(object):
-    def setup(self):
+    def setup_method(self):
         if not HAS_MDTRAJ:
             pytest.skip("MDTraj not installed.")
         self.gro = os.path.join(data_filename("gromacs_engine"), "conf.gro")
@@ -83,18 +83,21 @@ class TestGromacsEngine(object):
     # conf.gro, md.mdp, topol.top : standard Gromacs input files
     # project_trr/0000000.trr : working file, 4 frames
     # project_trr/0000099.trr : 49 working frames, final frame partial
-    def setup(self):
+    def setup_method(self):
         if not HAS_MDTRAJ:
             pytest.skip("MDTraj not installed.")
         self.test_dir = data_filename("gromacs_engine")
         self.engine = Engine(gro="conf.gro",
                              mdp="md.mdp",
                              top="topol.top",
-                             options={'mdrun_args': '-nt 1'},
+                             options={
+                                 'mdrun_args': '-nt 1',
+                                 'grompp_args': '-maxwarn 2',  # Berendsen
+                             },
                              base_dir=self.test_dir,
                              prefix="project")
 
-    def teardown(self):
+    def teardown_method(self):
         files = ['topol.tpr', 'mdout.mdp', 'initial_frame.trr', 'state.cpt',
                  'state_prev.cpt', 'traj_comp.xtc',
                  self.engine.trajectory_filename(1)]
@@ -220,7 +223,7 @@ def test_engine_command(self):
 
     def test_generate(self):
         if not has_gmx:
-            raise SkipTest("Gromacs 5 (gmx) not found. Skipping test.")
+            pytest.skip("gmx not found. Skipping test.")
 
         if not HAS_MDTRAJ:
             pytest.skip("MDTraj not found. Skipping test.")
@@ -240,7 +243,7 @@ def test_generate(self):
 
     def test_prepare(self):
         if not has_gmx:
-            raise SkipTest("Gromacs 5 (gmx) not found. Skipping test.")
+            pytest.skip("gmx not found. Skipping test.")
         self.engine.set_filenames(0)
         traj_0 = self.engine.trajectory_filename(0)
         snap = self.engine.read_frame_from_file(traj_0, 0)
@@ -265,6 +268,49 @@ def test_open_file_caching(self):
         # files?
         pytest.skip()
 
+    def test_iter_generate_clear_cache(self):
+        # when running with iter_generate, only the most recently generated
+        # snapshot should contain data -- the others should have their cache
+        # cleared
+        if not has_gmx:
+            pytest.skip("gmx not found. Skipping test.")
+        if not HAS_MDTRAJ:
+            pytest.skip("MDTraj not found. Skipping test.")
+
+        def continue_condition(trajectory, trusted=False):
+            if len(trajectory) == 5:
+                return False
+            for snap in trajectory[1:-1]:
+                # the initial snapshot does not get cleared
+                # the final snapshot has not yet been cleared
+                assert snap._xyz is None
+                assert snap._velocities is None
+                assert snap._box_vectors is None
+            # the final snapshot should have been loaded, and not yet
+            # cleared
+            assert trajectory[-1]._xyz is not None
+            assert trajectory[-1]._velocities is not None
+            assert trajectory[-1]._box_vectors is not None
+            return True
+
+        cv_x0 = paths.CoordinateFunctionCV('x0', lambda snap:
+                                           snap.xyz[0][0])
+        in_all_space = paths.AllInXEnsemble(
+            paths.CVDefinedVolume(cv_x0, float("-inf"), float("inf"))
+        )
+
+        traj_0 = self.engine.trajectory_filename(0)
+        snap = self.engine.read_frame_from_file(traj_0, 0)
+        self.engine.filename_setter.reset(0)
+        traj = self.engine.generate(snap, running=[
+            in_all_space.can_append,
+            continue_condition
+        ])
+        assert len(traj) == 5
+        ttraj = md.load(self.engine.trajectory_filename(1),
+                        top=self.engine.gro)
+        assert len(ttraj) < 100
+
     def test_serialization_cycle(self):
         serialized = self.engine.to_dict()
         deserialized = Engine.from_dict(serialized)
@@ -273,7 +319,7 @@ def test_serialization_cycle(self):
 
 
 class TestGromacsExternalMDSnapshot(object):
-    def setup(self):
+    def setup_method(self):
         if not HAS_MDTRAJ:
             pytest.skip("MDTraj not installed.")
 
@@ -293,7 +339,7 @@ def setup(self):
         self.snapshot_shape = (1651, 3)
         self.storage_filename = "gmx_snap.nc"
 
-    def teardown(self):
+    def teardown_method(self):
         if os.path.isfile(self.storage_filename):
             os.remove(self.storage_filename)
 
@@ -378,4 +424,3 @@ def test_internalized_storage(self):
         npt.assert_almost_equal(internalized.xyz, reloaded.xyz)
         assert internalized.__class__ != self.snapshot.__class__
         assert internalized.__class__ == reloaded.__class__
-
diff --git a/openpathsampling/tests/test_histogram.py b/openpathsampling/tests/test_histogram.py
index 0823c4a96..5311d3a46 100644
--- a/openpathsampling/tests/test_histogram.py
+++ b/openpathsampling/tests/test_histogram.py
@@ -45,7 +45,7 @@ def test_histograms_to_pandas_dataframe(self):
 
 
 class TestHistogram(object):
-    def setup(self):
+    def setup_method(self):
         self.data = [1.0, 1.1, 1.2, 1.3, 2.0, 1.4, 2.3, 2.5, 3.1, 3.5]
         self.nbins = 5
         self.bins = [1.0, 1.5, 2.0, 2.5, 3.0, 3.5]
@@ -189,7 +189,7 @@ def test_left_bin_error(self):
 
 
 class TestSparseHistogram(object):
-    def setup(self):
+    def setup_method(self):
         data = [(0.0, 0.1), (0.2, 0.7), (0.3, 0.6), (0.6, 0.9)]
         self.histo = SparseHistogram(bin_widths=(0.5, 0.3),
                                      left_bin_edges=(0.0, -0.1))
@@ -232,7 +232,7 @@ def test_mangled_input(self):
 
 
 class TestHistogramPlotter2D(object):
-    def setup(self):
+    def setup_method(self):
         data = [(0.0, 0.1), (0.2, 0.7), (0.3, 0.6), (0.6, 0.9)]
         histo = SparseHistogram(bin_widths=(0.5, 0.3),
                                 left_bin_edges=(0.0, -0.1))
diff --git a/openpathsampling/tests/test_interface_set.py b/openpathsampling/tests/test_interface_set.py
index a0b932777..8c0bf033b 100644
--- a/openpathsampling/tests/test_interface_set.py
+++ b/openpathsampling/tests/test_interface_set.py
@@ -20,7 +20,7 @@
 logging.getLogger('openpathsampling.netcdfplus').setLevel(logging.CRITICAL)
 
 class TestInterfaceSet(object):
-    def setup(self):
+    def setup_method(self):
         paths.InterfaceSet._reset()
         self.cv = paths.FunctionCV(name="x", f=lambda s: s.xyz[0][0])
         self.lambdas = [0.0, 0.1, 0.2, 0.3]
@@ -104,7 +104,7 @@ def test_bad_sanitize(self):
 
 
 class TestVolumeInterfaceSet(object):
-    def setup(self):
+    def setup_method(self):
         paths.InterfaceSet._reset()
         self.cv = paths.FunctionCV(name="x", f=lambda s: s.xyz[0][0])
         self.increasing_set = paths.VolumeInterfaceSet(cv=self.cv,
@@ -171,7 +171,7 @@ def test_storage(self):
 
 
 class TestPeriodicVolumeInterfaceSet(object):
-    def setup(self):
+    def setup_method(self):
         paths.InterfaceSet._reset()
         self.cv = paths.FunctionCV(name="x", f=lambda s: s.xyz[0][0])
         self.increasing_set = paths.PeriodicVolumeInterfaceSet(
diff --git a/openpathsampling/tests/test_lookup_function.py b/openpathsampling/tests/test_lookup_function.py
index 40b3aef50..1e35b2545 100644
--- a/openpathsampling/tests/test_lookup_function.py
+++ b/openpathsampling/tests/test_lookup_function.py
@@ -14,7 +14,7 @@
 )
 
 class TestLookupFunctionGroup(object):
-    def setup(self):
+    def setup_method(self):
         x1 = [0.0, 1.0, 2.0, 3.0, 4.0]
         y1 = [0.1, 1.3, 1.8, 3.3, 4.2]
         luf1 = LookupFunction(x1, y1)
@@ -75,7 +75,7 @@ def test_append(self):
 
 
 class TestVoxelLookupFunction(object):
-    def setup(self):
+    def setup_method(self):
         counter = collections.Counter({(0,0): 1.0, (0,2): 2.0, (1,4): 4.0,
                                        (-1,-1): 5.0})
         self.lookup = VoxelLookupFunction(left_bin_edges=(-1.0, 1.0),
diff --git a/openpathsampling/tests/test_mdtraj_support.py b/openpathsampling/tests/test_mdtraj_support.py
index d2ca421ac..d1d71def4 100644
--- a/openpathsampling/tests/test_mdtraj_support.py
+++ b/openpathsampling/tests/test_mdtraj_support.py
@@ -27,7 +27,7 @@
 # add tests for topology conversions as well
 
 class TestMDTrajSupport(object):
-    def setup(self):
+    def setup_method(self):
         if not md:
             raise SkipTest("mdtraj not installed")
         self.md_trajectory = md.load(data_filename("ala_small_traj.pdb"))
diff --git a/openpathsampling/tests/test_movescheme.py b/openpathsampling/tests/test_movescheme.py
index b72fe0a46..f58ec3685 100644
--- a/openpathsampling/tests/test_movescheme.py
+++ b/openpathsampling/tests/test_movescheme.py
@@ -92,7 +92,7 @@ def _make_null_mover_step(mccycle, path_sim_mover, null_mover):
 
 
 class TestMoveAcceptanceAnalysis(object):
-    def setup(self):
+    def setup_method(self):
         self.HAS_TQDM = paths.progress.HAS_TQDM
         paths.progress.HAS_TQDM = False
         paths.InterfaceSet._reset()
@@ -146,7 +146,7 @@ def setup(self):
                          'normal': acceptance,
                          'with_null': acceptance_null}
 
-    def teardown(self):
+    def teardown_method(self):
         paths.progress.HAS_TQDM = self.HAS_TQDM
 
     @pytest.mark.parametrize('step_num', [0, 1, 2, 3, 4])
@@ -391,7 +391,7 @@ def test_format_as_text(self, simulation):
 
 
 class TestMoveScheme(object):
-    def setup(self):
+    def setup_method(self):
         paths.InterfaceSet._reset()
         cvA = paths.FunctionCV(name="xA", f=lambda s : s.xyz[0][0])
         cvB = paths.FunctionCV(name="xB", f=lambda s : -s.xyz[0][0])
@@ -421,7 +421,7 @@ def test_append_individuals_default_levels(self):
 
         strats = self.scheme.strategies
         assert_equal(len(list(strats.keys())), 3)
-        pairs = [(levels.MOVER, shootstrat), (levels.SIGNATURE, repexstrat), 
+        pairs = [(levels.MOVER, shootstrat), (levels.SIGNATURE, repexstrat),
                  (levels.GLOBAL, defaultstrat)]
         for (k, v) in pairs:
             assert_in(v, strats[k])
@@ -435,7 +435,7 @@ def test_append_groups_default_levels(self):
 
         strats = self.scheme.strategies
         assert_equal(len(list(strats.keys())), 3)
-        pairs = [(levels.MOVER, shootstrat), (levels.SIGNATURE, repexstrat), 
+        pairs = [(levels.MOVER, shootstrat), (levels.SIGNATURE, repexstrat),
                  (levels.GLOBAL, defaultstrat)]
         for (k, v) in pairs:
             assert_in(v, strats[k])
@@ -470,7 +470,7 @@ def test_append_group_different_custom_levels(self):
         repexstrat = NearestNeighborRepExStrategy()
         defaultstrat = OrganizeByMoveGroupStrategy()
         assert_equal(len(list(self.scheme.strategies.keys())), 0)
-        self.scheme.append([shootstrat, repexstrat, defaultstrat], 
+        self.scheme.append([shootstrat, repexstrat, defaultstrat],
                            [45, 55, 65])
 
         strats = self.scheme.strategies
@@ -659,7 +659,7 @@ def test_select_movers_no_choice_probability(self):
 
     def test_select_movers(self):
         self.scheme.append([
-            OneWayShootingStrategy(), 
+            OneWayShootingStrategy(),
             NearestNeighborRepExStrategy(),
             OrganizeByMoveGroupStrategy()
         ])
@@ -677,7 +677,7 @@ def test_select_movers(self):
 
     def test_n_trials_for_steps(self):
         self.scheme.append([
-            OneWayShootingStrategy(), 
+            OneWayShootingStrategy(),
             NearestNeighborRepExStrategy(),
             OrganizeByMoveGroupStrategy()
         ])
@@ -728,15 +728,15 @@ def test_n_steps_for_trials(self):
 
 
 class TestDefaultScheme(object):
-    def setup(self):
+    def setup_method(self):
         paths.InterfaceSet._reset()
         cvA = paths.FunctionCV(name="xA", f=lambda s : s.xyz[0][0])
         cvB = paths.FunctionCV(name="xB", f=lambda s : -s.xyz[0][0])
         self.stateA = paths.CVDefinedVolume(cvA, float("-inf"), -0.5)
         self.stateB = paths.CVDefinedVolume(cvB, float("-inf"), -0.5)
-        interfacesA = paths.VolumeInterfaceSet(cvA, float("-inf"), 
+        interfacesA = paths.VolumeInterfaceSet(cvA, float("-inf"),
                                                [-0.5, -0.3, -0.1])
-        interfacesB = paths.VolumeInterfaceSet(cvB, float("-inf"), 
+        interfacesB = paths.VolumeInterfaceSet(cvB, float("-inf"),
                                                [-0.5, -0.3, -0.1])
         self.network = paths.MSTISNetwork(
             [(self.stateA, interfacesA),
@@ -862,7 +862,7 @@ def test_default_choice_probability(self):
 
         assert_almost_equal(sum(scheme.choice_probability.values()), 1.0)
 
-        tot_norm = sum([default_group_weights[group] 
+        tot_norm = sum([default_group_weights[group]
                         for group in scheme.movers])
 
         prob_shoot0 = scheme.choice_probability[scheme.movers['shooting'][0]]
@@ -1112,15 +1112,15 @@ def test_initial_conditions_report(self):
 
 
 class TestLockedMoveScheme(object):
-    def setup(self):
+    def setup_method(self):
         paths.InterfaceSet._reset()
         cvA = paths.FunctionCV(name="xA", f=lambda s : s.xyz[0][0])
         cvB = paths.FunctionCV(name="xB", f=lambda s : -s.xyz[0][0])
         self.stateA = paths.CVDefinedVolume(cvA, float("-inf"), -0.5)
         self.stateB = paths.CVDefinedVolume(cvB, float("-inf"), -0.5)
-        interfacesA = paths.VolumeInterfaceSet(cvA, float("-inf"), 
+        interfacesA = paths.VolumeInterfaceSet(cvA, float("-inf"),
                                                [-0.5, -0.3, -0.1])
-        interfacesB = paths.VolumeInterfaceSet(cvB, float("-inf"), 
+        interfacesB = paths.VolumeInterfaceSet(cvB, float("-inf"),
                                                [-0.5, -0.3, -0.1])
         self.network = paths.MSTISNetwork(
             [(self.stateA, interfacesA),
@@ -1175,15 +1175,15 @@ def test_movers_works(self):
 
 
 class TestOneWayShootingMoveScheme(object):
-    def setup(self):
+    def setup_method(self):
         paths.InterfaceSet._reset()
         cvA = paths.FunctionCV(name="xA", f=lambda s : s.xyz[0][0])
         cvB = paths.FunctionCV(name="xB", f=lambda s : -s.xyz[0][0])
         self.stateA = paths.CVDefinedVolume(cvA, float("-inf"), -0.5)
         self.stateB = paths.CVDefinedVolume(cvB, float("-inf"), -0.5)
-        interfacesA = paths.VolumeInterfaceSet(cvA, float("-inf"), 
+        interfacesA = paths.VolumeInterfaceSet(cvA, float("-inf"),
                                                [-0.5, -0.3, -0.1])
-        interfacesB = paths.VolumeInterfaceSet(cvB, float("-inf"), 
+        interfacesB = paths.VolumeInterfaceSet(cvB, float("-inf"),
                                                [-0.5, -0.3, -0.1])
         self.network = paths.MSTISNetwork(
             [(self.stateA, interfacesA),
@@ -1209,7 +1209,7 @@ def test_unused_ensembles(self):
         root = scheme.move_decision_tree()
         unused = scheme.find_unused_ensembles()
         specials = self.network.special_ensembles
-        expected_unused = sum([list(specials[special_type].keys()) 
+        expected_unused = sum([list(specials[special_type].keys())
                                for special_type in specials], [])
         assert_equal(set(expected_unused), set(unused))
 
diff --git a/openpathsampling/tests/test_movestrategy.py b/openpathsampling/tests/test_movestrategy.py
index 62d6fa2c0..8b6985522 100644
--- a/openpathsampling/tests/test_movestrategy.py
+++ b/openpathsampling/tests/test_movestrategy.py
@@ -56,7 +56,7 @@ def test_level_type(self):
 
 
 class MoveStrategyTestSetup(object):
-    def setup(self):
+    def setup_method(self):
         paths.InterfaceSet._reset()
         cvA = paths.FunctionCV(name="xA", f=lambda s: s.xyz[0][0])
         cvB = paths.FunctionCV(name="xB", f=lambda s: -s.xyz[0][0])
@@ -126,8 +126,8 @@ def test_get_ensembles(self):
 
 
 class TestSingleEnsembleMoveStrategy(MoveStrategyTestSetup):
-    def setup(self):
-        super(TestSingleEnsembleMoveStrategy, self).setup()
+    def setup_method(self):
+        super(TestSingleEnsembleMoveStrategy, self).setup_method()
         self.strategy = MockSingleEnsembleMoveStrategy(
             ensembles=None,
             group='test_group',
@@ -356,7 +356,7 @@ def test_make_movers(self):
         # set up the swap pairs
         swap_pairs = []
         for trans in self.network.sampling_transitions:
-            swap_pairs.extend([[trans.ensembles[i], trans.ensembles[i+1]] 
+            swap_pairs.extend([[trans.ensembles[i], trans.ensembles[i+1]]
                                for i in range(len(trans.ensembles)-1)])
         assert_equal(len(swap_pairs), 4)
 
@@ -415,7 +415,7 @@ def test_not_replica_exchange_signature(self):
             input_ensembles=[ens0, ens1],
             output_ensembles=[ens1, ens2]
         )
-        assert_equal(weird_mover.ensemble_signature, 
+        assert_equal(weird_mover.ensemble_signature,
                      ((ens0,ens1),(ens1,ens2)))
         scheme = MoveScheme(self.network)
         scheme.movers['weird'] = [weird_mover]
@@ -436,7 +436,7 @@ def test_noreplace_from(self):
         # groups: both the new and the old.
         scheme = DefaultScheme(self.network)
         scheme.movers ={}
-        scheme.append(EnsembleHopStrategy(replace=False, 
+        scheme.append(EnsembleHopStrategy(replace=False,
                                           group='hop',
                                           from_group='repex'))
         scheme.build_move_decision_tree()
@@ -448,7 +448,7 @@ def test_replace_from(self):
         # remove the old from_group from existence
         scheme = DefaultScheme(self.network)
         scheme.movers ={}
-        scheme.append(EnsembleHopStrategy(replace=True, 
+        scheme.append(EnsembleHopStrategy(replace=True,
                                           group='hop',
                                           from_group='repex'))
         scheme.build_move_decision_tree()
@@ -717,7 +717,7 @@ def test_chooser_mover_weights(self):
         for groupname in list(scheme.movers.keys()):
             for mover in scheme.movers[groupname]:
                 mover_weights[(groupname, mover.ensemble_signature)] = 1.0
-    
+
         m_sig = {}
         for g in scheme.movers:
             for m in scheme.movers[g]:
@@ -756,9 +756,9 @@ def test_make_movers(self):
 
         strategy = OrganizeByMoveGroupStrategy()
         root = strategy.make_movers(scheme)
-        
+
         assert_equal(len(root.movers), 4)
-        names = ['ShootingChooser', 'RepexChooser', 'PathreversalChooser', 
+        names = ['ShootingChooser', 'RepexChooser', 'PathreversalChooser',
                  'MinusChooser']
         name_dict = {root.movers[i].name : i for i in range(len(root.movers))}
         for name in names:
@@ -830,7 +830,7 @@ def test_make_movers_custom_group(self):
         root = strategy.make_movers(scheme)
 
         name_dict = {root.movers[i].name : i for i in range(len(root.movers))}
-        
+
         name = 'BlahblahblahChooser'
         weight = root.weights[name_dict[name]]
         chooser = root.movers[name_dict[name]]
@@ -862,7 +862,7 @@ def test_get_weights_scheme_all_unset(self):
         # check that the number of sigs in a each group matches the number
         # of movers in that group
         for group in list(group_weights.keys()):
-            mover_sigs = [sig for sig in list(mover_weights.keys()) 
+            mover_sigs = [sig for sig in list(mover_weights.keys())
                           if sig[0]==group]
             assert_equal(len(mover_sigs), len(scheme.movers[group]))
 
@@ -883,7 +883,7 @@ def test_get_weights_scheme_all_unset(self):
         assert_equal(group_weights, {'shooting' : 1.0})
         for sig in mover_weights:
             assert_equal(mover_weights[sig], 1.0)
-            assert_in(sig[1], [m.ensemble_signature 
+            assert_in(sig[1], [m.ensemble_signature
                                for m in scheme2.movers[sig[0]]])
 
         for group in list(scheme2.movers.keys()):
@@ -897,18 +897,18 @@ def test_get_weights_both_internal_weights_set(self):
         ensA_sig = ((ensA,),(ensA,))
 
         scheme = MoveScheme(self.network)
-        scheme.append([NearestNeighborRepExStrategy(), 
-                       OneWayShootingStrategy(), 
+        scheme.append([NearestNeighborRepExStrategy(),
+                       OneWayShootingStrategy(),
                        strategy])
         root = scheme.move_decision_tree()
         old_choice_probability = scheme.choice_probability
-        old_shooter_ensA = [m for m in scheme.movers['shooting'] 
+        old_shooter_ensA = [m for m in scheme.movers['shooting']
                             if m.ensemble_signature == ensA_sig][0]
 
         strategy.group_weights['repex'] = 3.0
         strategy.mover_weights[('shooting',ensA_sig)]= 2.0
         scheme.choice_probability = {} # this is unset here
- 
+
         (group_weights, mover_weights) = strategy.get_weights(
             scheme=scheme,
             sorted_movers=scheme.movers,
@@ -938,7 +938,7 @@ def test_get_weights_both_internal_weights_set(self):
                      3.0*len(new_repex_chooser.movers))
 
         new_shoot_chooser = [m for m in root if m.name=="ShootingChooser"][0]
-        new_shooter_ensA = [m for m in scheme.movers['shooting'] 
+        new_shooter_ensA = [m for m in scheme.movers['shooting']
                             if m.ensemble_signature == ensA_sig][0]
         shooter_ensA_idx = new_shoot_chooser.movers.index(new_shooter_ensA)
         assert_equal(new_shoot_chooser.weights[shooter_ensA_idx], 2.0)
@@ -948,8 +948,8 @@ def test_get_weights_both_internal_weights_set(self):
     def test_get_weights_group_weights_set(self):
         strategy = OrganizeByMoveGroupStrategy()
         scheme = MoveScheme(self.network)
-        scheme.append([NearestNeighborRepExStrategy(), 
-                       OneWayShootingStrategy(), 
+        scheme.append([NearestNeighborRepExStrategy(),
+                       OneWayShootingStrategy(),
                        strategy])
         root = scheme.move_decision_tree()
 
@@ -986,12 +986,12 @@ def test_get_weights_group_weights_set(self):
             assert_almost_equal(choice_prob[mover], old_div(1.0,7.0))
         for mover in scheme.movers['repex']:
             assert_almost_equal(choice_prob[mover], old_div(1.0,28.0))
-        
+
 
     def test_get_weights_mover_weights_set(self):
         strategy = OrganizeByMoveGroupStrategy()
         scheme = MoveScheme(self.network)
-        scheme.append([NearestNeighborRepExStrategy(), 
+        scheme.append([NearestNeighborRepExStrategy(),
                        OneWayShootingStrategy(),
                        strategy])
         # build it so there's an old version
@@ -1005,7 +1005,7 @@ def test_get_weights_mover_weights_set(self):
         assert_equal(group_weights, {'shooting' : 1.0, 'repex' : 3.0})
 
         ensA = self.network.sampling_transitions[0].ensembles[0]
-        ensA_sig = ((ensA,),(ensA,)) 
+        ensA_sig = ((ensA,),(ensA,))
         strategy.group_weights = {}
         strategy.mover_weights[('shooting',ensA_sig)] = 2.0
 
@@ -1028,7 +1028,7 @@ def test_get_weights_mover_weights_set(self):
     def test_get_weights_internal_unset_choice_prob_set(self):
         strategy = OrganizeByMoveGroupStrategy()
         scheme = MoveScheme(self.network)
-        scheme.append([NearestNeighborRepExStrategy(), 
+        scheme.append([NearestNeighborRepExStrategy(),
                        OneWayShootingStrategy(),
                        strategy])
         ensA = self.network.sampling_transitions[0].ensembles[0]
@@ -1066,10 +1066,10 @@ def test_get_weights_internal_unset_choice_prob_set(self):
         for group in scheme.movers:
             for mover in scheme.movers[group]:
                 old_mover = [
-                    old for old in old_movers[group] 
+                    old for old in old_movers[group]
                     if mover.ensemble_signature==old.ensemble_signature
                 ][0]
-                assert_almost_equal(new_choice_prob[mover], 
+                assert_almost_equal(new_choice_prob[mover],
                                     old_choice_prob[old_mover])
 
         #print new_choice_prob
@@ -1087,7 +1087,7 @@ def test_get_weights_mover_weights_set_no_shooting(self):
         # with path reversal
         strategy = OrganizeByMoveGroupStrategy()
         scheme = MoveScheme(self.network)
-        scheme.append([NearestNeighborRepExStrategy(), 
+        scheme.append([NearestNeighborRepExStrategy(),
                        PathReversalStrategy(),
                        strategy])
         strategy.group_weights['repex'] = 3.0
@@ -1100,7 +1100,7 @@ def test_get_weights_mover_weights_set_no_shooting(self):
         )
         assert_equal(group_weights, {'pathreversal' : 1.0, 'repex' : 3.0})
         ensA = self.network.sampling_transitions[0].ensembles[0]
-        ensA_sig = ((ensA,),(ensA,)) 
+        ensA_sig = ((ensA,),(ensA,))
         strategy.group_weights = {}
         strategy.mover_weights[('pathreversal',ensA_sig)] = 2.0
 
@@ -1124,8 +1124,8 @@ def test_get_weights_mover_weights_set_no_shooting(self):
 
 class TestOrganizeByEnsembleStrategy(MoveStrategyTestSetup):
     StrategyClass = OrganizeByEnsembleStrategy
-    def setup(self):
-        super(TestOrganizeByEnsembleStrategy, self).setup()
+    def setup_method(self):
+        super(TestOrganizeByEnsembleStrategy, self).setup_method()
         scheme = MoveScheme(self.network)
         ens0 = self.network.sampling_transitions[0].ensembles[0]
         ens1 = self.network.sampling_transitions[0].ensembles[1]
@@ -1142,7 +1142,7 @@ def setup(self):
             paths.ReplicaExchangeMover(ens1, ens2)
         ]
         scheme.movers['pathreversal'] = [
-            paths.PathReversalMover(ensemble=ens) 
+            paths.PathReversalMover(ensemble=ens)
             for ens in [ens0, ens1, ens2]
         ]
         scheme.movers['minus'] = [paths.MinusMover(
@@ -1184,8 +1184,8 @@ def test_choice_probability(self):
         # minus: 1/9
 
         # ens0 moves: shooting (2.0), repex01 (1.0), minus (1.0), rev (1.0)
-        # ens1 moves: shooting (1.0), repex01 (1.0), repex12 (1.0), rev (1.0) 
-        # ens2 moves: shooting (1.0), repex12 (1.0), rev (1.0) 
+        # ens1 moves: shooting (1.0), repex01 (1.0), repex12 (1.0), rev (1.0)
+        # ens2 moves: shooting (1.0), repex12 (1.0), rev (1.0)
         # minus moves: minus (1.0)
 
         # ens0 norm: 5.0
@@ -1197,7 +1197,7 @@ def test_choice_probability(self):
         #   shooting0 = 4/9 * 2.0/5.0 = 8.0/45.0
         #   shooting1 = 2/9 * 1.0/4.0 = 1.0/18.0
         #   shooting2 = 2/9 * 1.0/3.0 = 2.0/27.0
-        #   repex01 = 4/9 * 1.0/5.0 + 2/9 * 1.0/4.0 = 13.0/90.0 
+        #   repex01 = 4/9 * 1.0/5.0 + 2/9 * 1.0/4.0 = 13.0/90.0
         #   repex12 = 2/9 * 1.0/4.0 + 2/9 * 1.0/3.0 = 7.0/54.0
         #   minus = 1/9*1.0 + 4/9*1.0/5.0 = 1.8/9.0
         #   rev0 = 4/9 * 1.0/5.0 = 4.0/45.0
@@ -1242,7 +1242,7 @@ def test_chooser_mover_weights(self):
         (ensemble_weights, mover_weights) = strategy.default_weights(scheme)
 
         for ens in [ens0, ens1, ens2, minus]:
-            chooser_mweights = strategy.chooser_mover_weights(scheme, ens, 
+            chooser_mweights = strategy.chooser_mover_weights(scheme, ens,
                                                               mover_weights)
             for mover in chooser_mweights:
                 assert_in(ens, mover.ensemble_signature[0])
@@ -1265,12 +1265,12 @@ def test_default_weights(self):
 
         (ensemble_weights, mover_weights)= strategy.default_weights(scheme)
 
-        assert_equal(ensemble_weights, 
+        assert_equal(ensemble_weights,
                      {e : 1.0 for e in [ens0, ens1, ens2, minus]})
 
         # get the correct order on repex and minus signatures for testing
-        double_sigs = [m.ensemble_signature 
-                       for m in sum([scheme.movers[g] 
+        double_sigs = [m.ensemble_signature
+                       for m in sum([scheme.movers[g]
                                      for g in ['repex', 'minus']], [])
                       ]
         repex01_sig = reorder_ensemble_signature(((ens0,ens1),(ens0,ens1)),
@@ -1297,7 +1297,7 @@ def test_default_weights(self):
 
         assert_equal(len(mover_ens_sigs), len(list(mover_weights.keys())))
         assert_equal(set(mover_weights.keys()), set(mover_ens_sigs))
-        
+
         expected = {s : 1.0 for s in mover_ens_sigs}
         assert_equal(expected, mover_weights)
 
@@ -1406,10 +1406,10 @@ def test_chooser_mover_weights(self):
         (ens_w, mov_w) = strategy.get_weights(scheme, scheme.movers,
                                               strategy.ensemble_weights,
                                               strategy.mover_weights)
-        scheme.choice_probability = strategy.choice_probability(scheme, 
+        scheme.choice_probability = strategy.choice_probability(scheme,
                                                                 ens_w, mov_w)
         for ens in [ens0, ens1, ens2, minus]:
-            chooser_mweights = strategy.chooser_mover_weights(scheme, ens, 
+            chooser_mweights = strategy.chooser_mover_weights(scheme, ens,
                                                               mover_weights)
             if ens in [ens0, ens1]:
                 assert_equal(len(chooser_mweights), 5)
@@ -1418,7 +1418,7 @@ def test_chooser_mover_weights(self):
             elif ens is ens2:
                 assert_equal(len(chooser_mweights), 4)
 
-            real_movers = [m for m in list(chooser_mweights.keys()) 
+            real_movers = [m for m in list(chooser_mweights.keys())
                            if m != strategy.null_mover]
             for m in real_movers:
                 assert_equal(scheme.choice_probability[m], chooser_mweights[m])
diff --git a/openpathsampling/tests/test_ms_outer_interface.py b/openpathsampling/tests/test_ms_outer_interface.py
index 9ad8e0fac..026a32120 100644
--- a/openpathsampling/tests/test_ms_outer_interface.py
+++ b/openpathsampling/tests/test_ms_outer_interface.py
@@ -18,10 +18,10 @@
 logging.getLogger('openpathsampling.netcdfplus').setLevel(logging.CRITICAL)
 
 class TestMSOuterTISInterface(object):
-    def setup(self):
+    def setup_method(self):
         paths.InterfaceSet._reset()
         self.cv_inc = paths.FunctionCV(name="inc", f=lambda s: s.xyz[0][0])
-        self.cv_dec = paths.FunctionCV(name="dec", 
+        self.cv_dec = paths.FunctionCV(name="dec",
                                         f=lambda s: 1.0-s.xyz[0][0])
         self.lambdas = [0.0, 0.1, 0.2, 0.3]
         self.interfaces_inc = paths.VolumeInterfaceSet(cv=self.cv_inc,
diff --git a/openpathsampling/tests/test_netcdfplus_base.py b/openpathsampling/tests/test_netcdfplus_base.py
new file mode 100644
index 000000000..bda78e0d8
--- /dev/null
+++ b/openpathsampling/tests/test_netcdfplus_base.py
@@ -0,0 +1,18 @@
+import pytest
+
+from openpathsampling.netcdfplus.base import *
+
+class TestStorableNamedObject(object):
+    def setup_method(self):
+        self.obj = StorableNamedObject()
+
+    def test_named(self):
+        assert not self.obj.is_named
+        obj = self.obj.named('foo')
+        assert obj is self.obj
+        assert self.obj.is_named
+        assert self.obj.name == 'foo'
+
+    def test_bad_name_type(self):
+        with pytest.raises(TypeError, match="Invalid name type"):
+            self.obj.named(1)
diff --git a/openpathsampling/tests/test_network.py b/openpathsampling/tests/test_network.py
index 3ab91522f..32810e999 100644
--- a/openpathsampling/tests/test_network.py
+++ b/openpathsampling/tests/test_network.py
@@ -25,7 +25,7 @@
 
 class TestMultipleStateTIS(object):
     # generic class to set up states and ifaces
-    def setup(self):
+    def setup_method(self):
         # need to clear this before each run, otherwise it saves the
         # previous setup
         paths.InterfaceSet._reset()
@@ -91,8 +91,8 @@ def setup(self):
 
 
 class TestMSTISNetwork(TestMultipleStateTIS):
-    def setup(self):
-        super(TestMSTISNetwork, self).setup()
+    def setup_method(self):
+        super(TestMSTISNetwork, self).setup_method()
 
         ifacesA = self.ifacesA[:-1]
         ifacesB = self.ifacesB[:-1]
@@ -241,8 +241,8 @@ def test_autonaming(self):
 
 
 class TestMISTISNetwork(TestMultipleStateTIS):
-    def setup(self):
-        super(TestMISTISNetwork, self).setup()
+    def setup_method(self):
+        super(TestMISTISNetwork, self).setup_method()
 
         ifacesA = self.ifacesA[:-1]
         ifacesB = self.ifacesB[:-1]
@@ -411,7 +411,7 @@ class TestTPSNetwork(object):
     NetworkType = TPSNetwork
     std_kwargs = {}
 
-    def setup(self):
+    def setup_method(self):
         from .test_helpers import CallIdentity
         xval = paths.FunctionCV("xval", lambda snap: snap.xyz[0][0])
         self.stateA = paths.CVDefinedVolume(xval, float("-inf"), -0.5)
diff --git a/openpathsampling/tests/test_openmm_engine.py b/openpathsampling/tests/test_openmm_engine.py
index d3a32ad6b..407534f9d 100644
--- a/openpathsampling/tests/test_openmm_engine.py
+++ b/openpathsampling/tests/test_openmm_engine.py
@@ -73,7 +73,7 @@ def setup_module():
 
 
 class TestOpenMMEngine(object):
-    def setup(self):
+    def setup_method(self):
 
         # OpenMM Integrator
         integrator = mm.LangevinIntegrator(
@@ -102,7 +102,7 @@ def setup(self):
         context.setPositions(template.coordinates)
         context.setVelocities(u.Quantity(zero_array, old_div(u.nanometers, u.picoseconds)))
 
-    def teardown(self):
+    def teardown_method(self):
         pass
 
     def test_sanity(self):
@@ -126,7 +126,7 @@ def test_snapshot_set(self):
         testvel = []
         testpos = []
         for i in range(len(pdb_pos)):
-            testpos.append(list(np.array(pdb_pos[i]) + 
+            testpos.append(list(np.array(pdb_pos[i]) +
                                 np.array([1.0, 1.0, 1.0]))
                           )
             testvel.append([0.1*i, 0.1*i, 0.1*i])
diff --git a/openpathsampling/tests/test_openmm_snapshot.py b/openpathsampling/tests/test_openmm_snapshot.py
index 2b96a7198..48b62c1ef 100644
--- a/openpathsampling/tests/test_openmm_snapshot.py
+++ b/openpathsampling/tests/test_openmm_snapshot.py
@@ -26,7 +26,7 @@
 logging.getLogger('openpathsampling.netcdfplus').setLevel(logging.CRITICAL)
 
 class TestOpenMMSnapshot(object):
-    def setup(self):
+    def setup_method(self):
         if not openmm:
             raise SkipTest("OpenMM not installed")
         if not omt:
@@ -54,6 +54,12 @@ def test_masses_from_file(self):
     def test_masses_from_simulation(self):
         assert len(self.test_snap.masses) == self.n_atoms
 
+    def test_masses_for_virtual_sites(self):
+        snap = omm_engine.snapshot_from_pdb(data_filename("tip4p_water.pdb"))
+        snap_masses = snap.masses
+        # just check that the virtual site has a mass of 0
+        assert snap_masses[3].value_in_unit(snap_masses[3].unit) == 0
+
     def test_n_degrees_of_freedom(self):
         assert self.test_snap.n_degrees_of_freedom == 51
 
diff --git a/openpathsampling/tests/test_path_histogram.py b/openpathsampling/tests/test_path_histogram.py
index 044aa87ba..1b3400f0d 100644
--- a/openpathsampling/tests/test_path_histogram.py
+++ b/openpathsampling/tests/test_path_histogram.py
@@ -26,14 +26,14 @@
 from collections import Counter
 
 class PathHistogramTester(object):
-    def setup(self):
+    def setup_method(self):
         self.HAS_TQDM = paths.progress.HAS_TQDM
         paths.progress.HAS_TQDM = False
         self.trajectory = [(0.1, 0.3), (2.1, 3.1), (1.7, 1.4),
                            (1.6, 0.6), (0.1, 1.4), (2.2, 3.3)]
         self.diag = [(0.25, 0.25), (2.25, 2.25)]
 
-    def teardown(self):
+    def teardown_method(self):
         paths.progress.HAS_TQDM = self.HAS_TQDM
 
     def test_nopertraj(self):
@@ -74,8 +74,8 @@ def test_same_cell(self):
 
 class TestPathHistogramNoInterpolate(PathHistogramTester):
     Interpolator = NoInterpolation
-    def setup(self):
-        super(TestPathHistogramNoInterpolate, self).setup()
+    def setup_method(self):
+        super(TestPathHistogramNoInterpolate, self).setup_method()
         self.expected_bins = [(0, 0), (4, 6), (3, 2),
                               (3, 1), (0, 2), (4, 6)]
 
@@ -89,8 +89,8 @@ def test_diag(self):
 
 class TestPathHistogramSubdivideInterpolate(PathHistogramTester):
     Interpolator = SubdivideInterpolation
-    def setup(self):
-        super(TestPathHistogramSubdivideInterpolate, self).setup()
+    def setup_method(self):
+        super(TestPathHistogramSubdivideInterpolate, self).setup_method()
         self.expected_bins = [
             (0, 0),  # initial
             (0, 1), (1, 1), (1, 2), (1, 3), (2, 3), (2, 4), (3, 4),
@@ -105,8 +105,8 @@ def setup(self):
 
 class TestPathHistogramBesenhamLikeInterpolate(PathHistogramTester):
     Interpolator = BresenhamLikeInterpolation
-    def setup(self):
-        super(TestPathHistogramBesenhamLikeInterpolate, self).setup()
+    def setup_method(self):
+        super(TestPathHistogramBesenhamLikeInterpolate, self).setup_method()
         # NOTE: the (4, 5) in the 1->2 transition is exactly on bin edge;
         # left bin edge is inclusive
         self.expected_bins = [
@@ -121,8 +121,8 @@ def setup(self):
 
 class TestPathHistogramBesenhamInterpolate(PathHistogramTester):
     Interpolator = BresenhamInterpolation
-    def setup(self):
-        super(TestPathHistogramBesenhamInterpolate, self).setup()
+    def setup_method(self):
+        super(TestPathHistogramBesenhamInterpolate, self).setup_method()
         self.expected_bins = [
             (0, 0),  # initial
             (1, 1), (1, 2), (2, 3), (3, 4), (3, 5), (4, 6),  # 0->1
@@ -134,14 +134,14 @@ def setup(self):
 
 class TestPathHistogram(object):
     # tests of fundamental things in PathHistogram, not interpolators
-    def setup(self):
+    def setup_method(self):
         self.HAS_TQDM = paths.progress.HAS_TQDM
         paths.progress.HAS_TQDM = False
         self.trajectory = [(0.1, 0.3), (2.1, 3.1), (1.7, 1.4),
                            (1.6, 0.6), (0.1, 1.4), (2.2, 3.3)]
         self.diag = [(0.25, 0.25), (2.25, 2.25)]
 
-    def teardown(self):
+    def teardown_method(self):
         paths.progress.HAS_TQDM = self.HAS_TQDM
 
     def test_add_with_weight(self):
@@ -199,7 +199,7 @@ def test_add_data_to_histograms_no_weight(self):
 
 
 class TestPathDensityHistogram(object):
-    def setup(self):
+    def setup_method(self):
         self.HAS_TQDM = paths.progress.HAS_TQDM
         paths.progress.HAS_TQDM = False
         id_cv = paths.FunctionCV("Id",
@@ -218,7 +218,7 @@ def setup(self):
         self.traj2 = make_1d_traj([0.6, 0.7])
         # [(2, 1, 0), (2, 1, 1)]
 
-    def teardown(self):
+    def teardown_method(self):
         paths.progress.HAS_TQDM = self.HAS_TQDM
 
     def test_histogram_no_weights(self):
@@ -235,7 +235,7 @@ def test_histogram_no_weights(self):
     def test_histogram_with_weights(self):
         hist = PathDensityHistogram(self.cvs, self.left_bin_edges,
                                     self.bin_widths)
-        counter = hist.histogram([self.traj1, self.traj2], 
+        counter = hist.histogram([self.traj1, self.traj2],
                                  weights=[1.0, 2.0])
         assert_equal(len(counter), 5)
         for bin_label in [(0,0,0), (2,0,0), (1,0,0)]:
diff --git a/openpathsampling/tests/test_pathmover.py b/openpathsampling/tests/test_pathmover.py
index a41f835e0..afda5fcbd 100644
--- a/openpathsampling/tests/test_pathmover.py
+++ b/openpathsampling/tests/test_pathmover.py
@@ -8,11 +8,12 @@
 from builtins import str
 from builtins import range
 from builtins import object
-from nose.plugins.skip import SkipTest
-from nose.tools import (assert_equal, assert_not_equal, raises, assert_true,
-                        assert_in, assert_not_in)
+import logging
 from numpy.testing import assert_allclose
+import numpy as np
+import pytest
 
+import openpathsampling as paths
 from openpathsampling.collectivevariable import FunctionCV
 from openpathsampling.engines.trajectory import Trajectory
 from openpathsampling.ensemble import LengthEnsemble
@@ -22,65 +23,69 @@
 from openpathsampling.shooting import UniformSelector
 from openpathsampling.volume import CVDefinedVolume
 import openpathsampling.engines.toy as toys
-from .test_helpers import CallIdentity, raises_with_message_like
 from .test_helpers import (assert_equal_array_array, items_equal,
-                          make_1d_traj, assert_items_equal,
-                          CalvinistDynamics, assert_same_items, A2BEnsemble)
+                           make_1d_traj, CalvinistDynamics, CallIdentity,
+                           assert_same_items, A2BEnsemble)
 
-#logging.getLogger('openpathsampling.pathmover').setLevel(logging.CRITICAL)
+# logging.getLogger('openpathsampling.pathmover').setLevel(logging.CRITICAL)
 logging.getLogger('openpathsampling.initialization').setLevel(logging.CRITICAL)
 logging.getLogger('openpathsampling.ensemble').setLevel(logging.CRITICAL)
 logging.getLogger('openpathsampling.storage').setLevel(logging.CRITICAL)
 logging.getLogger('openpathsampling.netcdfplus').setLevel(logging.CRITICAL)
 
 
-#logging.getLogger('openpathsampling.pathmover').propagate = False
-#logging.getLogger('openpathsampling.initialization').propagate = False
+# logging.getLogger('openpathsampling.pathmover').propagate = False
+# logging.getLogger('openpathsampling.initialization').propagate = False
 
 class TestMakeListOfPairs(object):
-    def setup(self):
-        self.correct = [ [0, 1], [2, 3], [4, 5] ]
+    def setup_method(self):
+        self.correct = [[0, 1], [2, 3], [4, 5]]
 
-    @raises(TypeError)
     def test_not_iterable_type_error(self):
-        result = make_list_of_pairs(1)
+        with pytest.raises(TypeError, match='no len()'):
+            make_list_of_pairs(1)
 
     def test_list_of_list_pairs(self):
         result = make_list_of_pairs([[0, 1], [2, 3], [4, 5]])
-        assert_equal_array_array(result, self.correct)
+        assert result == self.correct
 
-    @raises(AssertionError)
     def test_list_of_list_notpairs(self):
-        result = make_list_of_pairs([[0], [1], [2]])
+        with pytest.raises(AssertionError, match='inner list length != 2'):
+            make_list_of_pairs([[0], [1], [2]])
 
-    @raises(AssertionError)
     def test_list_not_even(self):
-        result = make_list_of_pairs([0, 1, 2, 3, 4])
+        with pytest.raises(AssertionError, match='not divisible by 2'):
+            make_list_of_pairs([0, 1, 2, 3, 4])
 
     def test_list_even(self):
         result = make_list_of_pairs([0, 1, 2, 3, 4, 5])
         assert_equal_array_array(result, self.correct)
 
     def test_empty(self):
-        assert_equal(make_list_of_pairs(None), None)
+        assert make_list_of_pairs(None) is None
+
 
 def assert_sample_set_accepted(sample_set, results):
     for sample, result in zip(sample_set, results):
-        assert_equal(sample.details.accepted, result)
+        assert sample.details.accepted == result
+
 
 def assert_subchanges_set_accepted(change, results):
     for ch, result in zip(change.subchanges, results):
-        assert_equal(ch.accepted, result)
+        assert ch.accepted == result
+
 
 def assert_choice_of(result, choices):
     for choice in choices:
         if result is choice:
             return
 
-    raise AssertionError("%s is not in list of choices [%s]" % (result, choices))
+    raise AssertionError("%s is not in list of choices [%s]" %
+                         (result, choices))
+
 
 class TestPathMover(object):
-    def setup(self):
+    def setup_method(self):
         self.l1 = LengthEnsemble(1)
         self.l2 = LengthEnsemble(2)
         self.l3 = LengthEnsemble(3)
@@ -107,23 +112,23 @@ def test_select_sample(self):
 
     def test_is_ensemble_change_mover(self):
         pm = IdentityPathMover()
-        assert_equal(pm.is_ensemble_change_mover, False)
-        assert_equal(pm._is_ensemble_change_mover, None)
+        assert pm.is_ensemble_change_mover is False
+        assert pm._is_ensemble_change_mover is None
         pm._is_ensemble_change_mover = True
-        assert_equal(pm.is_ensemble_change_mover, True)
+        assert pm.is_ensemble_change_mover is True
 
     def test_is_canonical(self):
         pm = IdentityPathMover()
-        assert_equal(pm.is_canonical, None)
+        assert pm.is_canonical is None
         pm._is_canonical = True
-        assert_equal(pm.is_canonical, True)
+        assert pm.is_canonical is True
 
 
 class TestShootingMover(object):
-    def setup(self):
+    def setup_method(self):
         self.dyn = CalvinistDynamics([-0.1, 0.1, 0.3, 0.5, 0.7,
                                       -0.1, 0.2, 0.4, 0.6, 0.8])
-        op = FunctionCV("myid", f=lambda snap : snap.coordinates[0][0])
+        op = FunctionCV("myid", f=lambda snap: snap.coordinates[0][0])
         self.stateA = CVDefinedVolume(op, -100, 0.0)
         self.stateB = CVDefinedVolume(op, 0.65, 100)
         self.tps = A2BEnsemble(self.stateA, self.stateB)
@@ -139,8 +144,8 @@ def setup(self):
 
         integ = toys.LeapfrogVerletIntegrator(dt=0.1)
         pes = toys.LinearSlope(m=[0.0], c=[0.0])
-        pes_AA = toys.LinearSlope(m=[10.0], c=[0.0])
-        pes_BB = toys.LinearSlope(m=[-10.0], c=[0.0])
+        # pes_AA = toys.LinearSlope(m=[10.0], c=[0.0])
+        # pes_BB = toys.LinearSlope(m=[-10.0], c=[0.0])
         topology = toys.Topology(n_spatial=1, masses=[1.0], pes=pes)
         self.toy_opts = {'integ': integ,
                          'n_frames_max': 1000,
@@ -163,11 +168,12 @@ def setup(self):
                                           replica=0,
                                           ensemble=self.tps)])
         # setup checks: keep around (commented) for debugging
-        # assert_equal(self.stateA(self.toy_traj[0]), True)
-        # assert_equal(self.stateA(self.toy_traj[1]), False)
-        # assert_equal(self.stateB(self.toy_traj[-2]), False)
-        # assert_equal(self.stateB(self.toy_traj[-1]), True)
-        # assert_equal(self.tps(self.toy_traj), True)
+        # assert self.stateA(self.toy_traj[0]) is True
+        # assert self.stateA(self.toy_traj[1]) is False)
+        # assert self.stateB(self.toy_traj[-2]) is False)
+        # assert self.stateB(self.toy_traj[-1]) is True)
+        # assert self.tps(self.toy_traj) is True)
+
 
 class TestForwardShootMover(TestShootingMover):
     def test_move(self):
@@ -179,10 +185,10 @@ def test_move(self):
         self.dyn.initialized = True
         change = mover.move(self.init_samp)
         newsamp = self.init_samp.apply_samples(change)
-        assert_equal(len(newsamp), 1)
-        assert_equal(change.accepted, True)
-        assert_equal(newsamp[0].ensemble(newsamp[0].trajectory), True)
-        assert_equal(newsamp[0].trajectory, change.trials[0].trajectory)
+        assert len(newsamp) == 1
+        assert change.accepted is True
+        assert newsamp[0].ensemble(newsamp[0].trajectory) is True
+        assert newsamp[0].trajectory == change.trials[0].trajectory
 
     def test_move_toy_engine(self):
         mover = ForwardShootMover(
@@ -192,23 +198,20 @@ def test_move_toy_engine(self):
         )
         change = mover.move(self.toy_samp)
         newsamp = self.toy_samp.apply_samples(change)
-        assert_equal(len(newsamp), 1)
-        assert_equal(change.accepted, True)
+        assert len(newsamp) == 1
+        assert change.accepted is True
         newsamp.sanity_check()
         # the next two prove that we did a forward shot correctly
-        assert_equal(self.toy_samp[0].trajectory[0],
-                     newsamp[0].trajectory[0])
-        assert_not_equal(self.toy_samp[0].trajectory[-1],
-                         newsamp[0].trajectory[-1])
-
+        assert self.toy_samp[0].trajectory[0] == newsamp[0].trajectory[0]
+        assert self.toy_samp[0].trajectory[-1] != newsamp[0].trajectory[-1]
 
     def test_is_ensemble_change_mover(self):
         mover = ForwardShootMover(
             selector=UniformSelector(),
             ensemble=self.tps
         )
+        assert mover.is_ensemble_change_mover is False
 
-        assert_equal(mover.is_ensemble_change_mover, False)
 
 class TestBackwardShootMover(TestShootingMover):
     def test_move(self):
@@ -220,16 +223,14 @@ def test_move(self):
         self.dyn.initialized = True
         change = mover.move(self.init_samp)
         newsamp = self.init_samp.apply_samples(change)
-        assert_equal(len(newsamp), 1)
-        assert_equal(len(self.init_samp[0].trajectory), 8)
-        assert_not_equal(newsamp[0].trajectory[0],
-                         self.init_samp[0].trajectory[0])
-        assert_equal(newsamp[0].trajectory[-1],
-                     self.init_samp[0].trajectory[-1])
-        assert_true(len(newsamp[0].trajectory) <= 8)
-        assert_equal(change.accepted, True)
-        assert_equal(newsamp[0].ensemble(newsamp[0].trajectory), True)
-        assert_equal(newsamp[0].trajectory, change.trials[0].trajectory)
+        assert len(newsamp) == 1
+        assert len(self.init_samp[0].trajectory) == 8
+        assert newsamp[0].trajectory[0] != self.init_samp[0].trajectory[0]
+        assert newsamp[0].trajectory[-1] == self.init_samp[0].trajectory[-1]
+        assert len(newsamp[0].trajectory) <= 8
+        assert change.accepted is True
+        assert newsamp[0].ensemble(newsamp[0].trajectory) is True
+        assert newsamp[0].trajectory == change.trials[0].trajectory
 
     def test_move_toy_engine(self):
         mover = BackwardShootMover(
@@ -239,22 +240,20 @@ def test_move_toy_engine(self):
         )
         change = mover.move(self.toy_samp)
         newsamp = self.toy_samp.apply_samples(change)
-        assert_equal(len(newsamp), 1)
-        assert_equal(change.accepted, True)
+        assert len(newsamp) == 1
+        assert change.accepted is True
         newsamp.sanity_check()
         # the next two prove that we did a backward shot correctly
-        assert_not_equal(self.toy_samp[0].trajectory[0],
-                     newsamp[0].trajectory[0])
-        assert_equal(self.toy_samp[0].trajectory[-1],
-                         newsamp[0].trajectory[-1])
-
+        assert self.toy_samp[0].trajectory[0] != newsamp[0].trajectory[0]
+        assert self.toy_samp[0].trajectory[-1] == newsamp[0].trajectory[-1]
 
     def test_is_ensemble_change_mover(self):
         mover = BackwardShootMover(
             selector=UniformSelector(),
             ensemble=self.tps
         )
-        assert_equal(mover.is_ensemble_change_mover, False)
+        assert mover.is_ensemble_change_mover is False
+
 
 class TestOneWayShootingMover(TestShootingMover):
     def test_mover_initialization(self):
@@ -263,22 +262,35 @@ def test_mover_initialization(self):
             selector=UniformSelector(),
             engine=self.dyn
         )
-        assert_equal(len(mover.movers), 2)
-        assert_equal(isinstance(mover, RandomChoiceMover), True)
-        assert_equal(isinstance(mover, OneWayShootingMover), True)
+        assert len(mover.movers) == 2
+        assert isinstance(mover, RandomChoiceMover)
+        assert isinstance(mover, OneWayShootingMover)
         moverclasses = [m.__class__ for m in mover.movers]
-        assert_equal(ForwardShootMover in moverclasses, True)
-        assert_equal(BackwardShootMover in moverclasses, True)
-        assert_equal(mover.engine, mover.movers[0].engine)
-        assert_equal(mover.engine, mover.movers[1].engine)
-        assert_equal(mover.selector, mover.movers[0].selector)
-        assert_equal(mover.selector, mover.movers[1].selector)
-        assert_equal(mover.ensemble, mover.movers[0].ensemble)
-        assert_equal(mover.ensemble, mover.movers[1].ensemble)
-
-class TestForwardFirstTwoWayShootingMover(TestShootingMover):
-    _MoverType = ForwardFirstTwoWayShootingMover
-    # this allows us to run the exact same tests for backward-first
+        assert ForwardShootMover in moverclasses
+        assert BackwardShootMover in moverclasses
+        # Should the engine asserts check for 'is'?
+        assert mover.engine == mover.movers[0].engine
+        assert mover.engine == mover.movers[1].engine
+        assert mover.selector == mover.movers[0].selector
+        assert mover.selector == mover.movers[1].selector
+        assert mover.ensemble == mover.movers[0].ensemble
+        assert mover.ensemble == mover.movers[1].ensemble
+
+
+class TwoWayShootingMoverTest(TestShootingMover):
+    _MoverType = None
+    # this allows us to run the exact same tests for forward-first and
+    # backward-first
+
+    def test_modifier_default(self):
+        mover = self._MoverType(
+            ensemble=self.tps,
+            selector=UniformSelector(),
+            modifier=None,
+            engine=self.dyn
+        )
+        assert isinstance(mover.modifier, paths.NoModification)
+
     def test_run(self):
         mover = self._MoverType(
             ensemble=self.tps,
@@ -287,18 +299,19 @@ def test_run(self):
             engine=self.dyn
         )
         traj, details = mover._run(self.init_samp[0].trajectory, 4)
-        assert_allclose(traj.xyz[:,0,0], [-0.1, 0.2, 0.4, 0.6, 0.8])
-        assert_equal(list(details.keys()), ['modified_shooting_snapshot'])
-        assert_equal(details['modified_shooting_snapshot'], traj[2])
-        assert_not_in(details['modified_shooting_snapshot'],
-                      self.init_samp[0].trajectory)
+        assert_allclose(traj.xyz[:, 0, 0], [-0.1, 0.2, 0.4, 0.6, 0.8])
+        assert list(details.keys()) == ['modified_shooting_snapshot']
+
+        assert details['modified_shooting_snapshot'] == traj[2]
+        assert (details['modified_shooting_snapshot'] not in
+                self.init_samp[0].trajectory)
 
         traj, details = mover._run(self.init_samp[0].trajectory, 3)
-        assert_allclose(traj.xyz[:,0,0], [-0.1, 0.1, 0.3, 0.5, 0.7])
-        assert_equal(list(details.keys()), ['modified_shooting_snapshot'])
-        assert_equal(details['modified_shooting_snapshot'], traj[2])
-        assert_not_in(details['modified_shooting_snapshot'],
-                      self.init_samp[0].trajectory)
+        assert_allclose(traj.xyz[:, 0, 0], [-0.1, 0.1, 0.3, 0.5, 0.7])
+        assert list(details.keys()) == ['modified_shooting_snapshot']
+        assert details['modified_shooting_snapshot'] == traj[2]
+        assert (details['modified_shooting_snapshot'] not in
+                self.init_samp[0].trajectory)
 
     def test_run_toy(self):
         # mostly smoke test for toy engine integration
@@ -309,10 +322,39 @@ def test_run_toy(self):
             engine=self.toy_engine
         )
         change = mover.move(self.toy_samp)
-        assert_in(change.details.modified_shooting_snapshot,
-                  change.trials[0].trajectory)
-        assert_in(change.details.shooting_snapshot,
-                  change.initial_trajectory)
+        assert (change.details.modified_shooting_snapshot in
+                change.trials[0].trajectory)
+        assert change.details.shooting_snapshot in change.initial_trajectory
+
+    def test_run_toy_0_bias(self):
+        class NoAcceptModification(paths.NoModification):
+            def probability_ratio(self, a, b):
+                return 0.0
+
+        mover = self._MoverType(
+            ensemble=self.tps,
+            selector=UniformSelector(),
+            modifier=NoAcceptModification(),
+            engine=self.toy_engine
+        )
+        change = mover.move(self.toy_samp)
+        assert change.accepted is False
+        assert change.details.metropolis_acceptance == 0.0
+
+    def test_run_toy_inf_bias(self):
+        class InfAcceptModification(paths.NoModification):
+            def probability_ratio(self, a, b):
+                return np.inf
+
+        mover = self._MoverType(
+            ensemble=self.tps,
+            selector=UniformSelector(),
+            modifier=InfAcceptModification(),
+            engine=self.toy_engine
+        )
+        change = mover.move(self.toy_samp)
+        assert change.accepted is True
+        assert change.details.metropolis_acceptance == np.inf
 
     def _setup_early_reject(self, pair):
         zero_vel_traj = paths.Trajectory([
@@ -372,7 +414,7 @@ def _test_early_reject(self, test_ensemble, path_types,
 
             expected_early_reject = path_type in expected_rejections
             ran_full_two_way = ensemble(change.trials[0].trajectory)
-            assert_equal(expected_early_reject, not ran_full_two_way)
+            assert expected_early_reject is not ran_full_two_way
 
     def test_early_reject_tps(self):
         self._test_early_reject(test_ensemble=self.tps,
@@ -407,12 +449,17 @@ def test_sequential_shots(self):
 
         new_change = mover.move(real_sample_set)
         new_traj = new_change.trials[0].trajectory
-        assert_allclose(new_traj.xyz[:,0,0], real_traj.xyz[:,0,0])
+        assert_allclose(new_traj.xyz[:, 0, 0], real_traj.xyz[:, 0, 0])
+
+
+class TestForwardFirstTwoWayShootingMover(TwoWayShootingMoverTest):
+    _MoverType = ForwardFirstTwoWayShootingMover
 
 
-class TestBackwardFirstTwoWayShootingMover(TestForwardFirstTwoWayShootingMover):
+class TestBackwardFirstTwoWayShootingMover(TwoWayShootingMoverTest):
     _MoverType = BackwardFirstTwoWayShootingMover
     # runs the same tests as ForwardFirst
+
     def test_early_reject_tps(self):
         self._test_early_reject(test_ensemble=self.tps,
                                 path_types=['AA', 'BB', 'AB'],
@@ -441,9 +488,9 @@ def test_properties(self):
             modifier=modifier,
             engine=self.dyn
         )
-        assert_equal(mover.ensemble, self.tps)
-        assert_equal(mover.selector, selector)
-        assert_equal(mover.modifier, modifier)
+        assert mover.ensemble is self.tps
+        assert mover.selector is selector
+        assert mover.modifier is modifier
 
     def test_to_dict_from_dict(self):
         mover = TwoWayShootingMover(
@@ -454,16 +501,15 @@ def test_to_dict_from_dict(self):
         )
         dct = mover.to_dict()
         new_mover = mover.from_dict(dct)
-        assert_equal(mover.movers, new_mover.movers)
-        assert_equal(mover.ensemble, new_mover.ensemble)
-        assert_equal(mover.selector, new_mover.selector)
-        assert_equal(mover.modifier, new_mover.modifier)
+        assert mover.movers == new_mover.movers
+        assert mover.ensemble == new_mover.ensemble
+        assert mover.selector == new_mover.selector
+        assert mover.modifier == new_mover.modifier
 
 
 class TestPathReversalMover(object):
-    def setup(self):
-        op = FunctionCV("myid", f=lambda snap :
-                             snap.coordinates[0][0])
+    def setup_method(self):
+        op = FunctionCV("myid", f=lambda snap: snap.coordinates[0][0])
 
         volA = CVDefinedVolume(op, -100, 0.0)
         volB = CVDefinedVolume(op, 1.0, 100)
@@ -474,18 +520,18 @@ def setup(self):
         self.op = op
 
     def test_is_ensemble_change_mover(self):
-        assert_equal(self.move.is_ensemble_change_mover, False)
+        assert self.move.is_ensemble_change_mover is False
 
     def test_AXA_path(self):
         trajAXA = make_1d_traj(coordinates=[-0.1, 0.75, -0.6],
                                velocities=[0.1, 0.05, -0.05])
-        assert_equal(self.tis(trajAXA), True)
+        assert self.tis(trajAXA) is True
         sampAXA = Sample(trajectory=trajAXA,
                          ensemble=self.tis,
                          replica=0)
         gs_AXA = SampleSet([sampAXA])
         change = self.move.move(gs_AXA)
-        assert_equal(change.accepted, True)
+        assert change.accepted is True
 
     def test_A_A_path(self):
         trajA_A = make_1d_traj(coordinates=[-0.3, 0.1, -0.4])
@@ -494,8 +540,7 @@ def test_A_A_path(self):
                          replica=0)
         gs_A_A = SampleSet([sampA_A])
         change = self.move.move(gs_A_A)
-        assert_equal(change.accepted, False)
-
+        assert change.accepted is False
 
     def test_AB_path(self):
         trajAXB = make_1d_traj(coordinates=[-0.2, 0.75, 1.8])
@@ -504,8 +549,7 @@ def test_AB_path(self):
                          replica=0)
         gs_AXB = SampleSet([sampAXB])
         change = self.move.move(gs_AXB)
-        assert_equal(change.accepted, False)
-
+        assert change.accepted is False
 
     def test_BA_path(self):
         trajBXA = make_1d_traj(coordinates=[1.2, 0.7, -0.25])
@@ -514,25 +558,25 @@ def test_BA_path(self):
                          replica=0)
         gs_BXA = SampleSet([sampBXA])
         change = self.move.move(gs_BXA)
-        # print [[v.coordinates[0] for v in t.trajectory] for t in change.trials]
-        assert_equal(change.accepted, True)
+        # print [[v.coordinates[0] for v in t.trajectory]
+        #        for t in change.trials]
+        assert change.accepted is True
 
 
 class TestReplicaIDChangeMover(object):
-    def setup(self):
+    def setup_method(self):
         pass
 
     def test_replica_in_sample_set(self):
-        raise SkipTest
+        pytest.skip("Not implemented")
 
     def test_replica_not_in_sample_set(self):
-        raise SkipTest
+        pytest.skip("Not implemented")
 
 
 class TestReplicaExchangeMover(object):
-    def setup(self):
-        op = FunctionCV("myid", f=lambda snap :
-                             snap.coordinates[0][0])
+    def setup_method(self):
+        op = FunctionCV("myid", f=lambda snap: snap.coordinates[0][0])
 
         state1 = CVDefinedVolume(op, -100, 0.0)
         state2 = CVDefinedVolume(op, 1, 100)
@@ -545,9 +589,12 @@ def setup(self):
         self.traj0 = make_1d_traj([-0.1, 0.2, 0.3, 0.1, -0.2])
         self.traj1 = make_1d_traj([-0.1, 0.1, 0.4, 0.6, 0.3, 0.2, -0.15])
         self.traj2 = make_1d_traj([-0.1, 0.2, 0.3, 0.7, 0.6, 0.4, 0.1, -0.15])
-        self.sampA0 = Sample(replica=0, trajectory=self.traj0, ensemble=self.tisA)
-        self.sampB1 = Sample(replica=1, trajectory=self.traj1, ensemble=self.tisB)
-        self.sampA2 = Sample(replica=2, trajectory=self.traj2, ensemble=self.tisA)
+        self.sampA0 = Sample(replica=0, trajectory=self.traj0,
+                             ensemble=self.tisA)
+        self.sampB1 = Sample(replica=1, trajectory=self.traj1,
+                             ensemble=self.tisB)
+        self.sampA2 = Sample(replica=2, trajectory=self.traj2,
+                             ensemble=self.tisA)
         self.gs_B1A2 = SampleSet([self.sampB1, self.sampA2])
         self.gs_A0B1 = SampleSet([self.sampA0, self.sampB1])
 
@@ -556,7 +603,7 @@ def test_is_ensemble_change_mover(self):
             ensemble1=self.tisA,
             ensemble2=self.tisB
         )
-        assert_equal(repex_AB.is_ensemble_change_mover, True)
+        assert repex_AB.is_ensemble_change_mover is True
 
     def test_repex_ens_rej(self):
         repex_AB = ReplicaExchangeMover(
@@ -567,23 +614,23 @@ def test_repex_ens_rej(self):
         repex_change = repex_AB.move(old_sset)
         samples = repex_change.results
 
-        assert_equal(len(repex_change.results), 0) # since rejected
+        assert len(repex_change.results) == 0  # since rejected
 
         samples_A0B1_ens = repex_change.trials
-        assert_equal(len(samples_A0B1_ens), 2)
-        assert_equal(repex_change.accepted, False)
+        assert len(samples_A0B1_ens) == 2
+        assert repex_change.accepted is False
 
         new_sset = old_sset.apply_samples(samples)
 
-        assert_equal(new_sset[0].trajectory, old_sset[0].trajectory)
-        assert_equal(new_sset[1].trajectory, old_sset[1].trajectory)
+        assert new_sset[0].trajectory == old_sset[0].trajectory
+        assert new_sset[1].trajectory == old_sset[1].trajectory
 
-        A0 = [s for s in samples_A0B1_ens if s.ensemble==self.tisA]
-        assert_equal(len(A0), 1)
-        assert_equal(A0[0].trajectory, self.traj1)
-        B1 = [s for s in samples_A0B1_ens if s.ensemble==self.tisB]
-        assert_equal(len(B1), 1)
-        assert_equal(B1[0].trajectory, self.traj0)
+        A0 = [s for s in samples_A0B1_ens if s.ensemble == self.tisA]
+        assert len(A0) == 1
+        assert A0[0].trajectory == self.traj1
+        B1 = [s for s in samples_A0B1_ens if s.ensemble == self.tisB]
+        assert len(B1) == 1
+        assert B1[0].trajectory == self.traj0
 
     def test_repex_ens_acc(self):
         repex_12 = ReplicaExchangeMover(
@@ -594,25 +641,24 @@ def test_repex_ens_acc(self):
         samples_B2A1_rep = repex_12.move(old_sset)
         change = samples_B2A1_rep
         samples = change.results
-        assert_equal(len(samples), 2)
-
-        assert_equal(change.accepted, True)
+        assert len(samples) == 2
+        assert change.accepted is True
 
         new_sset = old_sset.apply_samples(samples)
 
-        assert_equal(new_sset[1].trajectory, old_sset[1].trajectory)
-        assert_equal(new_sset[2].trajectory, old_sset[2].trajectory)
+        assert new_sset[1].trajectory == old_sset[1].trajectory
+        assert new_sset[2].trajectory == old_sset[2].trajectory
 
-        B2 = [s for s in samples if s.ensemble==self.tisB]
-        assert_equal(len(B2), 1)
-        assert_equal(B2[0].trajectory, self.traj2)
-        A1 = [s for s in samples if s.ensemble==self.tisA]
-        assert_equal(len(A1), 1)
-        assert_equal(A1[0].trajectory, self.traj1)
+        B2 = [s for s in samples if s.ensemble == self.tisB]
+        assert len(B2) == 1
+        assert B2[0].trajectory == self.traj2
+        A1 = [s for s in samples if s.ensemble == self.tisA]
+        assert len(A1) == 1
+        assert A1[0].trajectory == self.traj1
 
 
 class TestRandomChoiceMover(object):
-    def setup(self):
+    def setup_method(self):
         traj = Trajectory([-0.5, 0.7, 1.1])
         op = CallIdentity()
         volA = CVDefinedVolume(op, -100, 0.0)
@@ -635,50 +681,50 @@ def setup(self):
         self.mover = RandomChoiceMover([self.hop_to_tis, self.hop_to_tps])
 
     def test_is_ensemble_change_mover(self):
-        assert_equal(self.mover.is_ensemble_change_mover, True)
+        assert self.mover.is_ensemble_change_mover is True
 
     def test_is_canonical(self):
         for t in range(20):
             change = self.mover.move(self.init_samp)
-            assert_not_equal(change.canonical.mover, self.mover)
+            assert change.canonical.mover != self.mover
             canonical_submovers = 0
             for submover in self.mover.movers:
                 if change.canonical.mover is submover:
                     canonical_submovers += 1
-            assert_equal(canonical_submovers, 1)
+            assert canonical_submovers == 1
 
     def test_random_choice(self):
         # test that both get selected, but that we always return only one
         # sample
-        count = {}
+        # count = {}
         for t in range(100):
             change = self.mover.move(self.init_samp)
-            assert_equal(len(change.results), 1)
-#            try:
-                # Since self is the root mover, mover_path[-1] is self.
-                # That means that mover_path[-2] is the mover that this
-                # mover chose.
-#                count[samples[0].details.mover_path[-2]] += 1
-#            except KeyError:
-#                count[samples[0].details.mover_path[-2]] = 1
-#        assert_equal(len(count.keys()), 2)
+            assert len(change.results) == 1
+
+            # try:
+            #     # Since self is the root mover, mover_path[-1] is self.
+            #     # That means that mover_path[-2] is the mover that this
+            #     # mover chose.
+            #     count[samples[0].details.mover_path[-2]] += 1
+            # except KeyError:
+            #     count[samples[0].details.mover_path[-2]] = 1
+        # assert len(count.keys()) == 2
 
     def test_restricted_by_replica(self):
-        raise SkipTest
+        pytest.skip("Not implemented?")
 
     def test_restricted_by_ensemble(self):
-        raise SkipTest
+        pytest.skip("Not implemented?")
 
 
 class TestRandomAllowedChoiceMover(object):
-    def setup(self):
+    def setup_method(self):
         self.dyn = CalvinistDynamics([-0.1, 0.1, 0.3, 0.5, 0.7,
                                       -0.1, 0.2, 0.4, 0.6, 0.8,
-                                     ])
+                                      ])
         self.dyn.initialized = True
         # SampleMover.engine = self.dyn
-        op = FunctionCV("myid", f=lambda snap :
-                             snap.coordinates[0][0])
+        op = FunctionCV("myid", f=lambda snap: snap.coordinates[0][0])
         stateA = CVDefinedVolume(op, -100, 0.0)
         stateB = CVDefinedVolume(op, 0.65, 100)
         volX = CVDefinedVolume(op, -100, 0.25)
@@ -703,26 +749,23 @@ def setup(self):
                                          engine=self.dyn)
         self.pathrev = PathReversalMover(ensemble=self.ens1)
 
-        ens_dict = {self.ens1 : self.pathrev, self.ens2 : self.shooter}
-        # self.mover = EnsembleDictionaryMover(ens_dict)
         self.mover = RandomAllowedChoiceMover([self.shooter, self.pathrev])
 
     def test_move_single_replica(self):
         sample_set = SampleSet([self.samp1])
         change = self.mover.move(sample_set)
         subchange = change.subchange
-        assert_equal(subchange.mover, self.pathrev)
-        assert_equal(subchange.accepted, True)
-        assert_equal(change.accepted, True)
-        assert_equal(len(subchange.samples), 1)
+        assert subchange.mover == self.pathrev
+        assert subchange.accepted is True
+        assert change.accepted is True
+        assert len(subchange.samples) == 1
 
         sample_set = SampleSet([self.samp2])
         change = self.mover.move(sample_set)
         subchange = change.subchange
-        assert_equal(subchange.mover, self.shooter)
-        assert_equal(subchange.accepted, True)
-        assert_equal(change.accepted, True)
-
+        assert subchange.mover == self.shooter
+        assert subchange.accepted is True
+        assert change.accepted is True
 
     def test_move_multiple_replicas(self):
         sample_set = SampleSet([self.samp1, self.samp2])
@@ -730,48 +773,45 @@ def test_move_multiple_replicas(self):
         for i in range(100):
             change = self.mover.move(sample_set)
             subchange = change.subchange
-            assert_equal(change.accepted, True)
-            assert_equal(subchange.accepted, True)
-            assert_equal(len(subchange.samples), 1)
+            assert change.accepted is True
+            assert subchange.accepted is True
+            assert len(subchange.samples) == 1
             ens = subchange.trials[0].ensemble
             try:
                 count[ens] += 1
             except KeyError:
                 count[ens] = 1
             if ens == self.ens1:
-                assert_equal(subchange.mover, self.pathrev)
+                assert subchange.mover == self.pathrev
             elif ens == self.ens2:
-                assert_equal(subchange.mover, self.shooter)
+                assert subchange.mover == self.shooter
             else:
                 raise AssertionError("Resulting mover unknown!")
-        assert_equal(set(count.keys()), set([self.ens1, self.ens2]))
-
+        assert set(count.keys()) == set([self.ens1, self.ens2])
 
     def test_move_multiple_replicas_weighted_ensembles(self):
         sample_set = SampleSet([self.samp1, self.samp2])
-        ens_dict = {self.ens1 : self.pathrev, self.ens2 : self.shooter}
-        # weighted_mover = EnsembleDictionaryMover(ens_dict, [1.0, 2.0])
-        weighted_mover = RandomAllowedChoiceMover([self.pathrev,
-                                                   self.shooter], [1.0, 2.0])
+        weighted_mover = RandomAllowedChoiceMover([self.pathrev, self.shooter],
+                                                  [1.0, 2.0])
         count = {}
         for i in range(100):
             change = weighted_mover.move(sample_set)
             subchange = change.subchange
-            assert_equal(change.accepted, True)
-            assert_equal(subchange.accepted, True)
-            assert_equal(len(subchange.samples), 1)
+            assert change.accepted is True
+            assert subchange.accepted is True
+            assert len(subchange.samples) == 1
             ens = subchange.trials[0].ensemble
             try:
                 count[ens] += 1
             except KeyError:
                 count[ens] = 1
             if ens == self.ens1:
-                assert_equal(subchange.mover, self.pathrev)
+                assert subchange.mover == self.pathrev
             elif ens == self.ens2:
-                assert_equal(subchange.mover, self.shooter)
+                assert subchange.mover == self.shooter
             else:
                 raise AssertionError("Resulting mover unknown!")
-        assert_equal(set(count.keys()), set([self.ens1, self.ens2]))
+        assert set(count.keys()) == set([self.ens1, self.ens2])
         try:
             assert(count[self.ens1] < count[self.ens2])
         except AssertionError:
@@ -780,7 +820,7 @@ def test_move_multiple_replicas_weighted_ensembles(self):
 
 
 class TestSequentialMover(object):
-    def setup(self):
+    def setup_method(self):
         traj = Trajectory([-0.5, 0.7, 1.1])
         op = CallIdentity()
         volA = CVDefinedVolume(op, -100, 0.0)
@@ -792,27 +832,27 @@ def setup(self):
         len2 = LengthEnsemble(2)
         self.hop_to_tis = RandomAllowedChoiceMover(
             [EnsembleHopMover(*ens) for ens in [[tis, tis],
-                 [tps, tis],
-                 [len3, tis],
-                 [len2, tis]]]
+                                                [tps, tis],
+                                                [len3, tis],
+                                                [len2, tis]]]
         )
         self.hop_to_tps = RandomAllowedChoiceMover(
             [EnsembleHopMover(*ens) for ens in [[tis, tps],
-                 [tps, tps],
-                 [len3, tps],
-                 [len2, tps]]]
+                                                [tps, tps],
+                                                [len3, tps],
+                                                [len2, tps]]]
         )
         self.hop_to_len3 = RandomAllowedChoiceMover(
             [EnsembleHopMover(*ens) for ens in [[tis, len3],
-                 [tps, len3],
-                 [len3, len3],
-                 [len2, len3]]]
+                                                [tps, len3],
+                                                [len3, len3],
+                                                [len2, len3]]]
         )
         self.hop_to_len2 = RandomAllowedChoiceMover(
             [EnsembleHopMover(*ens) for ens in [[tis, len2],
-                 [tps, len2],
-                 [len3, len2],
-                 [len2, len2]]]
+                                                [tps, len2],
+                                                [len3, len2],
+                                                [len2, len2]]]
         )
         self.init_sample = Sample(trajectory=traj,
                                   ensemble=len3,
@@ -833,18 +873,18 @@ def setup(self):
 
     def test_is_ensemble_change_mover(self):
         move = SequentialMover(movers=self.everything_accepted_movers)
-        assert_equal(move.is_ensemble_change_mover, True)
+        assert move.is_ensemble_change_mover is True
 
     def test_everything_accepted(self):
         move = SequentialMover(movers=self.everything_accepted_movers)
         gs = SampleSet(self.init_sample)
         change = move.move(gs)
         samples = change.results
-        assert_equal(len(samples), 3)
+        assert len(samples) == 3
         for subchange in change:
-            assert_equal(subchange.accepted, True)
+            assert subchange.accepted is True
         gs = gs.apply_samples(change)
-        assert_equal(gs[0].ensemble, self.tps)
+        assert gs[0].ensemble == self.tps
 
     def test_first_rejected(self):
         move = SequentialMover(movers=self.first_rejected_movers)
@@ -853,95 +893,105 @@ def test_first_rejected(self):
         samples = change.results
         # @DWHS: This should have two samples since two are accepted
         # and thus applied
-        assert_equal(len(samples), 2)
-        assert_equal(change[0].accepted, False)
-        assert_equal(change[1].accepted, True)
-        assert_equal(change[2].accepted, True)
+        assert len(samples) == 2
+        assert change[0].accepted is False
+        assert change[1].accepted is True
+        assert change[2].accepted is True
         gs = gs.apply_samples(change)
-        assert_equal(gs[0].ensemble, self.tps)
+        assert gs[0].ensemble == self.tps
 
     def test_last_rejected(self):
         move = SequentialMover(movers=self.last_rejected_movers)
         gs = SampleSet(self.init_sample)
         change = move.move(gs)
         samples = change.results
-        assert_equal(len(samples), 2)
+        assert len(samples) == 2
         # @DWHS: I think if the last is rejected then there should only be two
         # samples to be used, since the last one is not accepted and thus
         # discarded (does not mean that it is not stored!!!)
-        assert_equal(change[0].accepted, True)
-        assert_equal(change[1].accepted, True)
-        assert_equal(change[2].accepted, False)
+        assert change[0].accepted is True
+        assert change[1].accepted is True
+        assert change[2].accepted is False
         gs = gs.apply_samples(change)
-        assert_equal(gs[0].ensemble, self.tps)
+        assert gs[0].ensemble == self.tps
 
     def test_restricted_by_replica(self):
-        raise SkipTest
+        pytest.skip("Not implemented?")
 
     def test_restricted_by_ensemble(self):
-        raise SkipTest
+        pytest.skip("Not implemented?")
+
 
 class TestPartialAcceptanceSequentialMover(TestSequentialMover):
     def test_everything_accepted(self):
-        move = PartialAcceptanceSequentialMover(movers=self.everything_accepted_movers)
+        move = PartialAcceptanceSequentialMover(
+            movers=self.everything_accepted_movers
+        )
         gs = SampleSet(self.init_sample)
         change = move.move(gs)
         samples = change.results
-        assert_equal(len(samples), 3)
+        assert len(samples) == 3
         for subchange in change:
-            assert_equal(subchange.accepted, True)
-        assert_equal(len(change.trials,),3)
+            assert subchange.accepted is True
+        assert len(change.trials,) == 3
         gs = gs.apply_samples(change)
-        assert_equal(gs[0].ensemble, self.tps)
+        assert gs[0].ensemble == self.tps
 
     def test_first_rejected(self):
-        move = PartialAcceptanceSequentialMover(movers=self.first_rejected_movers)
+        move = PartialAcceptanceSequentialMover(
+            movers=self.first_rejected_movers
+        )
         gs = SampleSet(self.init_sample)
         change = move.move(gs)
         samples = change.results
         # returns zero sample since even the first is rejected
         # the first one is still stored
-        assert_equal(len(samples), 0)
+        assert len(samples) == 0
         allsamp = change.trials
-        assert_equal(len(allsamp), 1)
-        assert_equal(change[0].accepted, False)
+        assert len(allsamp) == 1
+        assert change[0].accepted is False
         gs = gs.apply_samples(change)
-        assert_equal(gs[0].ensemble, self.len3)
+        assert gs[0].ensemble == self.len3
 
     def test_last_rejected(self):
-        move = PartialAcceptanceSequentialMover(movers=self.last_rejected_movers)
+        move = PartialAcceptanceSequentialMover(
+            movers=self.last_rejected_movers
+        )
         gs = SampleSet(self.init_sample)
         change = move.move(gs)
         samples = change.results
         # @see above, this should return 2 samples. Important the third is
         # still run!
-        assert_equal(len(samples), 2)
+        assert len(samples) == 2
         allsamp = change.trials
-        assert_equal(len(allsamp), 3)
+        assert len(allsamp) == 3
 
-        assert_equal(change[0].accepted, True)
-        assert_equal(change[1].accepted, True)
-        assert_equal(change[2].accepted, False)
+        assert change[0].accepted is True
+        assert change[1].accepted is True
+        assert change[2].accepted is False
         gs = gs.apply_samples(change)
-        assert_equal(gs[0].ensemble, self.tps)
+        assert gs[0].ensemble == self.tps
 
     def test_restricted_by_replica(self):
-        raise SkipTest
+        pytest.skip("Not implemented?")
 
     def test_restricted_by_ensemble(self):
-        raise SkipTest
+        pytest.skip("Not implemented?")
+
 
 class TestConditionalSequentialMover(TestSequentialMover):
     def test_everything_accepted(self):
-        move = ConditionalSequentialMover(movers=self.everything_accepted_movers)
+        move = ConditionalSequentialMover(
+            movers=self.everything_accepted_movers
+        )
         gs = SampleSet(self.init_sample)
         change = move.move(gs)
         samples = change.results
-        assert_equal(len(samples), 3)
+        assert len(samples) == 3
         for ch in change:
-            assert_equal(change.accepted, True)
+            assert change.accepted is True
         gs = gs.apply_samples(change)
-        assert_equal(gs[0].ensemble, self.tps)
+        assert gs[0].ensemble == self.tps
 
     def test_first_rejected(self):
         move = ConditionalSequentialMover(movers=self.first_rejected_movers)
@@ -949,12 +999,12 @@ def test_first_rejected(self):
         change = move.move(gs)
         samples = change.results
         # should be zero since the move is completely rejected
-        assert_equal(len(samples), 0)
+        assert len(samples) == 0
         allsamp = change.trials
-        assert_equal(len(allsamp), 1)
-        assert_equal(change[0].accepted, False)
+        assert len(allsamp) == 1
+        assert change[0].accepted is False
         gs = gs.apply_samples(change)
-        assert_equal(gs[0].ensemble, self.len3)
+        assert gs[0].ensemble == self.len3
 
     def test_last_rejected(self):
         move = ConditionalSequentialMover(movers=self.last_rejected_movers)
@@ -962,28 +1012,28 @@ def test_last_rejected(self):
         change = move.move(gs)
         samples = change.results
         # number of accepted samples is 0 for this type of mover
-        assert_equal(len(samples), 0)
+        assert len(samples) == 0
         allsamp = change.trials
-        assert_equal(len(allsamp), 3)
+        assert len(allsamp) == 3
 
         # check here if last actual samples was false
         # this actually allows to see later if the single samples were
         # accepted or not, even from the change without loading samples
-        assert_equal(change[0].accepted, True)
-        assert_equal(change[1].accepted, True)
-        assert_equal(change[2].accepted, False)
+        assert change[0].accepted is True
+        assert change[1].accepted is True
+        assert change[2].accepted is False
         gs = gs.apply_samples(change)
-        assert_equal(gs[0].ensemble, self.len3)
+        assert gs[0].ensemble == self.len3
 
     def test_restricted_by_replica(self):
-        raise SkipTest
+        pytest.skip("Not implemented?")
 
     def test_restricted_by_ensemble(self):
-        raise SkipTest
+        pytest.skip("Not implemented?")
 
-class SubtrajectorySelectTester(object):
 
-    def setup(self):
+class SubtrajectorySelectTester(object):
+    def setup_method(self):
         op = CallIdentity()
         vol = paths.CVDefinedVolume(op, -0.5, 0.5)
         inX = paths.AllInXEnsemble(vol)
@@ -1011,10 +1061,11 @@ def setup(self):
     def test_paths_in_ensemble(self):
         # more a test of SequentialEnsemble, but also a test of sanity
         # before the real tests
-        assert_equal(self.ensemble(self.traj_with_3_subtrajs), True)
-        assert_equal(self.subensemble(self.subtraj0), True)
-        assert_equal(self.subensemble(self.subtraj1), True)
-        assert_equal(self.subensemble(self.subtraj2), True)
+        assert self.ensemble(self.traj_with_3_subtrajs)
+        assert self.subensemble(self.subtraj0)
+        assert self.subensemble(self.subtraj1)
+        assert self.subensemble(self.subtraj2)
+
 
 class TestRandomSubtrajectorySelectMover(SubtrajectorySelectTester):
     def test_accepts_all(self):
@@ -1026,10 +1077,10 @@ def test_accepts_all(self):
         for t in range(100):
             change = mover.move(self.gs)
             samples = change.results
-            assert_equal(len(samples), 1)
-            assert_equal(self.subensemble, samples[0].ensemble)
-            assert_equal(self.subensemble(samples[0].trajectory), True)
-            assert_equal(self.ensemble(samples[0].trajectory), False)
+            assert len(samples) == 1
+            assert self.subensemble == samples[0].ensemble
+            assert self.subensemble(samples[0].trajectory)
+            assert not self.ensemble(samples[0].trajectory)
             if samples[0].trajectory == self.subtraj0:
                 found[0] = True
             elif samples[0].trajectory == self.subtraj1:
@@ -1038,15 +1089,14 @@ def test_accepts_all(self):
                 found[2] = True
             else:
                 raise RuntimeError("Subtraj unknown!")
-        assert_equal(found[0] and found[1] and found[2], True)
+        assert (found[0] and found[1] and found[2])
 
     def test_is_ensemble_change_mover(self):
         mover = RandomSubtrajectorySelectMover(ensemble=1, sub_ensemble=1)
-        assert_equal(mover.is_ensemble_change_mover, True)
-
+        assert mover.is_ensemble_change_mover is True
 
     def test_nl_fails(self):
-        raise SkipTest
+        pytest.skip("Not implemented?")
 
     def test_nothing_allowed(self):
         mover = RandomSubtrajectorySelectMover(
@@ -1057,9 +1107,10 @@ def test_nothing_allowed(self):
         self.gs[0].trajectory = traj_with_no_subtrajs
         change = mover.move(self.gs)
         samples = change.results
-        assert_equal(len(samples), 0)
+        assert len(samples) == 0
         # print change.samples
-        assert_equal(len(change.samples), 0)
+        assert len(change.samples) == 0
+
 
 class TestFirstSubtrajectorySelectMover(SubtrajectorySelectTester):
     def test_move(self):
@@ -1069,11 +1120,12 @@ def test_move(self):
         )
         change = mover.move(self.gs)
         samples = change.results
-        assert_equal(len(samples), 1)
-        assert_equal(self.subensemble, samples[0].ensemble)
-        assert_equal(self.subensemble(samples[0].trajectory), True)
-        assert_equal(self.ensemble(samples[0].trajectory), False)
-        assert_equal(samples[0].trajectory, self.subtraj0)
+        assert len(samples) == 1
+        assert self.subensemble == samples[0].ensemble
+        assert self.subensemble(samples[0].trajectory)
+        assert not self.ensemble(samples[0].trajectory)
+        assert samples[0].trajectory == self.subtraj0
+
 
 class TestFinalSubtrajectorySelectMover(SubtrajectorySelectTester):
     def test_move(self):
@@ -1083,11 +1135,11 @@ def test_move(self):
         )
         change = mover.move(self.gs)
         samples = change.results
-        assert_equal(len(samples), 1)
-        assert_equal(self.subensemble, samples[0].ensemble)
-        assert_equal(self.subensemble(samples[0].trajectory), True)
-        assert_equal(self.ensemble(samples[0].trajectory), False)
-        assert_equal(samples[0].trajectory, self.subtraj2)
+        assert len(samples) == 1
+        assert self.subensemble == samples[0].ensemble
+        assert self.subensemble(samples[0].trajectory)
+        assert not self.ensemble(samples[0].trajectory)
+        assert samples[0].trajectory == self.subtraj2
 
 # class TestForceEnsembleChangeMover(object):
 #     def setup(self):
@@ -1109,25 +1161,22 @@ def test_move(self):
 #         mover = ForceEnsembleChangeMover(ensembles=[[self.tis, self.len3]])
 #         change = mover.move(self.gs)
 #         samples = change.results
-#         assert_equal(change.details.initial_ensemble(samples[0].trajectory),
-#                      True)
-#         assert_equal(samples[0].ensemble(samples[0].trajectory), True)
-#         assert_equal(samples[0].ensemble, self.len3)
+#         assert change.details.initial_ensemble(samples[0].trajectory)
+#         assert samples[0].ensemble(samples[0].trajectory)
+#         assert samples[0].ensemble == self.len3
 #
 #     def test_not_in_ensemble(self):
 #         mover = ForceEnsembleChangeMover(ensembles=[[self.tis, self.len2]])
 #         change = mover.move(self.gs)
 #         samples = change.results
-#         assert_equal(change.details.initial_ensemble(samples[0].trajectory),
-#                      True)
-#         assert_equal(samples[0].ensemble, self.len2)
-#         assert_equal(samples[0].ensemble(samples[0].trajectory), False)
+#         assert change.details.initial_ensemble(samples[0].trajectory)
+#         assert samples[0].ensemble == self.len2
+#         assert not samples[0].ensemble(samples[0].trajectory)
 
-class TestMinusMover(object):
-    def setup(self):
-        op = FunctionCV("myid", f=lambda snap :
-                             snap.coordinates[0][0])
 
+class TestMinusMover(object):
+    def setup_method(self):
+        op = FunctionCV("myid", f=lambda snap: snap.coordinates[0][0])
         volA = CVDefinedVolume(op, -100, 0.0)
         volB = CVDefinedVolume(op, 1.0, 100)
         volX = CVDefinedVolume(op, -100, 0.25)
@@ -1166,32 +1215,30 @@ def setup(self):
         )
 
     def test_is_ensemble_change_mover(self):
-        assert_equal(self.mover.is_ensemble_change_mover, True)
+        assert self.mover.is_ensemble_change_mover is True
 
     def test_is_canonical(self):
-        assert_equal(self.mover.is_canonical, True)
+        assert self.mover.is_canonical is True
 
     def test_setup_sanity(self):
         # sanity checks to make sure that what we set up makes sense
-        assert_equal(self.minus_sample.ensemble(self.minus_sample.trajectory),
-                    True)
+        assert self.minus_sample.ensemble(self.minus_sample.trajectory)
         first_subtraj = FirstSubtrajectorySelectMover(
             ensemble=self.minus,
             sub_ensemble=self.minus._segment_ensemble
         )
         change = first_subtraj.move(SampleSet(self.minus_sample))
         samples = change.results
-        assert_equal(samples[0].ensemble(samples[0].trajectory), True)
+        assert samples[0].ensemble(samples[0].trajectory)
         final_subtraj = FinalSubtrajectorySelectMover(
             ensemble=self.minus,
             sub_ensemble=self.minus._segment_ensemble
         )
         change = final_subtraj.move(SampleSet(self.minus_sample))
         samples = change.results
-        assert_equal(samples[0].ensemble(samples[0].trajectory), True)
-        assert_equal(samples[0].ensemble, self.minus._segment_ensemble)
-        assert_equal(self.mover.engine, self.dyn)
-
+        assert samples[0].ensemble(samples[0].trajectory)
+        assert samples[0].ensemble == self.minus._segment_ensemble
+        assert self.mover.engine == self.dyn
 
     def test_successful_move(self):
         init_innermost = make_1d_traj(self.list_innermost, [1.0]*5)
@@ -1202,11 +1249,11 @@ def test_successful_move(self):
         )
         gs = SampleSet([init_sample, self.minus_sample])
 
-        extend_forward =  self.list_innermost + [0.12, 0.32, -0.131]
+        extend_forward = self.list_innermost + [0.12, 0.32, -0.131]
         extend_backward = [-0.13, 0.13, 0.33] + self.list_innermost
 
-        assert_equal(self.minus(make_1d_traj(extend_forward)), True)
-        assert_equal(self.minus(make_1d_traj(extend_backward)), True)
+        assert self.minus(make_1d_traj(extend_forward))
+        assert self.minus(make_1d_traj(extend_backward))
 
         seg_dir = {}
         for i in range(100):
@@ -1215,22 +1262,24 @@ def test_successful_move(self):
             assert change.details.extension_trajectory is not None
             samples = change.results
             sub_samples = change.subchange.subchange.results
-            assert_equal(len(samples), 2)
-            assert_equal(len(sub_samples), 4)
-            s_inner = [s for s in sub_samples if s.ensemble==self.innermost]
-            s_minus = [s for s in sub_samples if s.ensemble==self.minus]
-            s_sub = [s for s in sub_samples if s.ensemble==self.minus._segment_ensemble]
-            assert_equal(len(s_inner), 1)
-            assert_equal(len(s_minus), 1)
-            assert_equal(len(s_sub), 2)
+            assert len(samples) == 2
+            assert len(sub_samples) == 4
+            s_inner = [s for s in sub_samples if s.ensemble == self.innermost]
+            s_minus = [s for s in sub_samples if s.ensemble == self.minus]
+            s_sub = [s for s in sub_samples
+                     if s.ensemble == self.minus._segment_ensemble]
+            assert len(s_inner) == 1
+            assert len(s_minus) == 1
+            assert len(s_sub) == 2
 
             for c in change:
-                assert_equal(c.accepted, True)
+                assert c.accepted is True
 
-            assert_equal(change.canonical.mover, self.mover)
+            assert change.canonical.mover == self.mover
 
             key = ""
-            s_inner0_xvals = [s.coordinates[0,0] for s in s_inner[0].trajectory]
+            s_inner0_xvals = [s.coordinates[0, 0]
+                              for s in s_inner[0].trajectory]
             if items_equal(s_inner0_xvals, self.first_segment):
                 key += "1"
             elif items_equal(s_inner0_xvals, self.second_segment):
@@ -1240,7 +1289,8 @@ def test_successful_move(self):
                 raise RuntimeError("Chosen segment neither first nor last!")
 
             # final sample s_minus is accepted
-            s_minus_xvals = [s.coordinates[0,0] for s in s_minus[-1].trajectory]
+            s_minus_xvals = [s.coordinates[0, 0]
+                             for s in s_minus[-1].trajectory]
             if items_equal(s_minus_xvals, extend_forward):
                 key += "f"
             elif items_equal(s_minus_xvals, extend_backward):
@@ -1253,7 +1303,7 @@ def test_successful_move(self):
                 seg_dir[key] += 1
             except KeyError:
                 seg_dir[key] = 1
-        assert_equal(len(list(seg_dir.keys())), 4)
+        assert len(list(seg_dir.keys())) == 4
 
     def test_repex_fails_other_ensemble(self):
         innermost_other_ensemble = make_1d_traj([-0.11, 0.1, -0.12])
@@ -1265,15 +1315,15 @@ def test_repex_fails_other_ensemble(self):
         gs = SampleSet([samp_other_ensemble, self.minus_sample])
 
         change = self.mover.move(gs)
-        assert_equal(len(change.trials), 1)
+        assert len(change.trials) == 1
         assert change.details.segment_swap_samples is not None
         assert change.details.extension_trajectory is None
 
         sub = change.subchange.subchange
-        assert_equal(self.innermost(innermost_other_ensemble), False)
-        assert_equal(sub[0].accepted, True)
-        assert_equal(sub[1].accepted, False)
-        assert_equal(len(sub.trials), 3) # stop after failed repex
+        assert not self.innermost(innermost_other_ensemble)
+        assert sub[0].accepted is True
+        assert sub[1].accepted is False
+        assert len(sub.trials) == 3  # stop after failed repex
         # only one sample which is not a segment
 
     def test_repex_fails_innermost_crosses_state(self):
@@ -1286,13 +1336,13 @@ def test_repex_fails_innermost_crosses_state(self):
         gs = SampleSet([samp_crosses_to_state, self.minus_sample])
 
         change = self.mover.move(gs)
-        assert_equal(len(change.trials), 1) # stop after failed repex
+        assert len(change.trials) == 1  # stop after failed repex
         assert change.details.segment_swap_samples is not None
         assert change.details.extension_trajectory is None
 
         sub = change.subchange.subchange
-        assert_equal(self.innermost(innermost_crosses_to_state), True)
-        assert_equal(len(sub.trials), 3) # stop after failed repex
+        assert self.innermost(innermost_crosses_to_state)
+        assert len(sub.trials) == 3  # stop after failed repex
         assert_subchanges_set_accepted(sub, [True, False, False])
 
     def test_repex_fails_minus_crosses_to_state(self):
@@ -1311,14 +1361,14 @@ def test_repex_fails_minus_crosses_to_state(self):
         )
         gs = SampleSet([badminus_sample, init_sample])
 
-        assert_equal(self.minus(minus_crosses_to_state), True)
+        assert self.minus(minus_crosses_to_state)
 
         change = self.mover.move(gs)
         assert change.details.segment_swap_samples is not None
         assert change.details.extension_trajectory is None
         sub = change.subchange.subchange
-        assert_equal(len(sub.trials), 3)  # stop after failed repex
-        assert_equal(len(change.trials), 1)
+        assert len(sub.trials) == 3  # stop after failed repex
+        assert len(change.trials) == 1
         assert_subchanges_set_accepted(sub, [True, False, False])
 
     def test_extension_fails(self):
@@ -1334,37 +1384,33 @@ def test_extension_fails(self):
             ensemble=self.innermost
         )
 
-        assert_equal(self.innermost(traj_bad_extension), True)
+        assert self.innermost(traj_bad_extension)
 
         gs = SampleSet([self.minus_sample, samp_bad_extension])
         change = self.mover.move(gs)
-        assert_equal(change.accepted, False) # whole minus has failed
+        assert change.accepted is False  # whole minus has failed
 
         sub = change.subchange.subchange
-        assert_equal(len(sub.trials), 4)
+        assert len(sub.trials) == 4
         assert change.details.segment_swap_samples is not None
         assert change.details.extension_trajectory is not None
 
         # after filtering there are only 2 trials
-        assert_equal(len(change.trials), 2)
+        assert len(change.trials) == 2
 
         assert_subchanges_set_accepted(sub, [True] * 2 + [False])
         # first two work and the extension fails
         # this only happens due to length
 
-        assert_equal(
-            len(sub[-1][0].trials[0].trajectory),
-            len(traj_bad_extension)+self.dyn.n_frames_max-1
-        )
+        assert (len(sub[-1][0].trials[0].trajectory) ==
+                len(traj_bad_extension) + self.dyn.n_frames_max-1)
 
         self.dyn.options['on_max_length'] = 'fail'
 
 
 class TestSingleReplicaMinusMover(object):
-    def setup(self):
-        op = FunctionCV("myid", f=lambda snap :
-                             snap.coordinates[0][0])
-
+    def setup_method(self):
+        op = FunctionCV("myid", f=lambda snap: snap.coordinates[0][0])
         volA = CVDefinedVolume(op, -100, 0.0)
         volB = CVDefinedVolume(op, 1.0, 100)
         volX = CVDefinedVolume(op, -100, 0.25)
@@ -1403,7 +1449,7 @@ def setup(self):
         )
 
     def test_is_ensemble_change_mover(self):
-        assert_equal(self.mover.is_ensemble_change_mover, True)
+        assert self.mover.is_ensemble_change_mover is True
 
     def test_successful_move(self):
         init_innermost = make_1d_traj(self.list_innermost, [1.0]*5)
@@ -1414,11 +1460,11 @@ def test_successful_move(self):
         )
         gs = SampleSet([init_sample, self.minus_sample])
 
-        extend_forward =  self.list_innermost + [0.12, 0.32, -0.131]
+        extend_forward = self.list_innermost + [0.12, 0.32, -0.131]
         extend_backward = [-0.13, 0.13, 0.33] + self.list_innermost
 
-        assert_equal(self.minus(make_1d_traj(extend_forward)), True)
-        assert_equal(self.minus(make_1d_traj(extend_backward)), True)
+        assert self.minus(make_1d_traj(extend_forward))
+        assert self.minus(make_1d_traj(extend_backward))
 
         output_forward = [-0.12, 0.12, 0.32, -0.131]
         output_backward = [-0.13, 0.13, 0.33, -0.11]
@@ -1428,22 +1474,23 @@ def test_successful_move(self):
             change = self.mover.move(gs)
             samples = change.results
             sub_samples = change.subchange.subchange.results
-            assert_equal(len(samples), 1)
-            assert_equal(len(sub_samples), 4)
-            s_inner = [s for s in sub_samples if s.ensemble==self.innermost]
-            s_minus = [s for s in sub_samples if s.ensemble==self.minus]
-            s_seg = [s for s in sub_samples if s.ensemble==self.minus._segment_ensemble]
-            assert_equal(len(s_inner), 1) # this is the output
-            assert_equal(len(s_minus), 1) # this is the minus version
-            assert_equal(len(s_seg), 2) # first the selected, then the final
+            assert len(samples) == 1
+            assert len(sub_samples) == 4
+            s_inner = [s for s in sub_samples if s.ensemble == self.innermost]
+            s_minus = [s for s in sub_samples if s.ensemble == self.minus]
+            s_seg = [s for s in sub_samples
+                     if s.ensemble == self.minus._segment_ensemble]
+            assert len(s_inner) == 1  # this is the output
+            assert len(s_minus) == 1  # this is the minus version
+            assert len(s_seg) == 2  # first the selected, then the final
 
             for c in change:
-               assert_equal(c.accepted, True)
+                assert c.accepted is True
 
-            assert_equal(change.canonical.mover, self.mover)
+            assert change.canonical.mover == self.mover
 
             key = ""
-            s_seg0_xvals = [s.coordinates[0,0] for s in s_seg[0].trajectory]
+            s_seg0_xvals = [s.coordinates[0, 0] for s in s_seg[0].trajectory]
             if items_equal(s_seg0_xvals, self.list_innermost):
                 key += "0"
             else:
@@ -1451,29 +1498,25 @@ def test_successful_move(self):
                 raise RuntimeError("Chosen segment neither first nor last!")
 
             # s_minus is the intermediate
-            s_minus_xvals = [s.coordinates[0,0] for s in s_minus[-1].trajectory]
+            s_minus_xvals = [s.coordinates[0, 0]
+                             for s in s_minus[-1].trajectory]
             if items_equal(s_minus_xvals, extend_forward):
                 key += "f"
-                assert_equal(
-                    [s.coordinates[0,0] for s in s_inner[0].trajectory],
-                    output_forward
-                )
+                assert ([s.coordinates[0, 0] for s in s_inner[0].trajectory] ==
+                        output_forward)
             elif items_equal(s_minus_xvals, extend_backward):
                 key += "b"
-                assert_equal(
-                    [s.coordinates[0,0] for s in s_inner[0].trajectory],
-                    output_backward
-                )
+                assert ([s.coordinates[0, 0] for s in s_inner[0].trajectory] ==
+                        output_backward)
             else:
                 print("s_minus_xvals:", s_minus_xvals)
                 raise RuntimeError("Unexpected minus extension result!")
 
-
             try:
                 seg_dir[key] += 1
             except KeyError:
                 seg_dir[key] = 1
-        assert_equal(len(list(seg_dir.keys())), 2)
+        assert len(list(seg_dir.keys())) == 2
 
     def test_first_hop_fails(self):
         crossing_traj = make_1d_traj([-0.11, 0.11, 0.31, 1.01], [1.0]*4)
@@ -1483,12 +1526,12 @@ def test_first_hop_fails(self):
         gs.sanity_check()
 
         change = self.mover.move(gs)
-        assert_equal(change.accepted, False)
-        assert_equal(len(change.results), 0)
+        assert change.accepted is False
+        assert len(change.results) == 0
         sub_trials = change.subchange.subchange.subchange.trials
-        assert_equal(len(sub_trials), 1)
-        assert_equal(sub_trials[0].trajectory, crossing_traj)
-        assert_equal(sub_trials[0].ensemble, self.minus._segment_ensemble)
+        assert len(sub_trials) == 1
+        assert sub_trials[0].trajectory == crossing_traj
+        assert sub_trials[0].ensemble == self.minus._segment_ensemble
 
     def test_extension_fails(self):
         # we use `stop` and not `fail` for max length
@@ -1502,46 +1545,58 @@ def test_extension_fails(self):
             ensemble=self.innermost
         )
 
-        assert_equal(self.innermost(traj_bad_extension), True)
+        assert self.innermost(traj_bad_extension)
 
         gs = SampleSet([self.minus_sample, samp_bad_extension])
         change = self.mover.move(gs)
-        assert_equal(change.accepted, False) # whole minus has failed
+        assert change.accepted is False  # whole minus has failed
 
         #     Minus : Filter  :ChooseFB : CondSeq
         sub = change.subchange.subchange.subchange
-        assert_equal(len(sub.trials), 2)
-        assert_equal(len(change.trials), 0) # no trials survive filtering
+        assert len(sub.trials) == 2
+        assert len(change.trials) == 0  # no trials survive filtering
         assert_subchanges_set_accepted(sub, [True, False])
 
         # first two work and the extension fails
         # this only happens due to length
-        assert_equal(
-            len(sub[-1].trials[0].trajectory),
-            len(traj_bad_extension)+self.dyn.n_frames_max-1
-        )
-
-
+        assert (len(sub[-1].trials[0].trajectory) ==
+                len(traj_bad_extension) + self.dyn.n_frames_max - 1)
         self.dyn.options['on_max_length'] = 'fail'
 
 
 class TestAbstract(object):
-    @raises_with_message_like(TypeError, "Can't instantiate abstract class")
-    def test_abstract_pathmover(self):
-        mover = paths.PathMover()
-
-    @raises_with_message_like(TypeError, "Can't instantiate abstract class")
-    def test_abstract_samplemover(self):
-        mover = paths.SampleMover()
-
-    @raises_with_message_like(TypeError, "Can't instantiate abstract class")
-    def test_abstract_enginemover(self):
-        mover = paths.EngineMover()
-
-    @raises_with_message_like(TypeError, "Can't instantiate abstract class")
-    def test_abstract_selectionmover(self):
-        mover = paths.SelectionMover()
+    @pytest.mark.parametrize("class_name", ["PathMover", "SampleMover",
+                                            "EngineMover", "SelectionMover",
+                                            "SubtrajectorySelectMover"])
+    def test_abstract_mover_classes(self, class_name):
+        abstract_class = getattr(paths, class_name)
+        with pytest.raises(TypeError,
+                           match="Can't instantiate abstract class"):
+            abstract_class()
+
+
+class TestDeprecation(TestShootingMover):
+    def test_no_new_snapshot_kwarg_selector(self):
+        class OldSelector(UniformSelector):
+            def __init__(self):
+                super(OldSelector, self).__init__()
+
+            # Override prob_ratio with old signature
+            def probability_ratio(self, snapshot,
+                                  old_trajectory, new_trajectory):
+                prob = super(OldSelector, self).probability_ratio(
+                    snapshot,
+                    old_trajectory,
+                    new_trajectory,
+                    snapshot
+                )
+                return prob
 
-    @raises_with_message_like(TypeError, "Can't instantiate abstract class")
-    def test_abstract_subtrajectoryselectmover(self):
-        mover = paths.SubtrajectorySelectMover()
+        mover = ForwardShootMover(ensemble=self.tps,
+                                  selector=OldSelector(),
+                                  engine=self.dyn)
+        self.dyn.initialized = True
+        with pytest.warns(DeprecationWarning,
+                          match="'new_snapshot' should be a supported "):
+            a = mover.move(self.init_samp)
+        assert a is not None
diff --git a/openpathsampling/tests/test_pathsimulator.py b/openpathsampling/tests/test_pathsimulator.py
index d38321478..a7cbd242c 100644
--- a/openpathsampling/tests/test_pathsimulator.py
+++ b/openpathsampling/tests/test_pathsimulator.py
@@ -32,7 +32,7 @@ def test_abstract_volume(self):
 
 
 class TestFullBootstrapping(object):
-    def setup(self):
+    def setup_method(self):
         paths.InterfaceSet._reset()
         self.cv = paths.FunctionCV("Id", lambda snap: snap.xyz[0][0])
         cv_neg = paths.FunctionCV("Neg", lambda snap: -snap.xyz[0][0])
@@ -169,7 +169,7 @@ class TestShootFromSnapshotsSimulation(object):
     # for CommittorSimulation. This is just an additional test to show that
     # using different ensembles from the ones used for the committor will
     # also work.
-    def setup(self):
+    def setup_method(self):
         # As a test system, let's use 1D motion on a flat potential. If the
         # velocity is positive, you right the state on the right. If it is
         # negative, you hit the state on the left.
@@ -204,7 +204,7 @@ def setup(self):
         )
         self.simulation.output_stream = open(os.devnull, "w")
 
-    def teardown(self):
+    def teardown_method(self):
         if os.path.isfile(self.filename):
             os.remove(self.filename)
         paths.EngineMover.default_engine = None
@@ -232,7 +232,7 @@ def test_run_arbitrary_ensemble(self):
 
 
 class TestCommittorSimulation(object):
-    def setup(self):
+    def setup_method(self):
         # As a test system, let's use 1D motion on a flat potential. If the
         # velocity is positive, you right the state on the right. If it is
         # negative, you hit the state on the left.
@@ -268,7 +268,7 @@ def setup(self):
                                               initial_snapshots=self.snap0)
         self.simulation.output_stream = open(os.devnull, 'w')
 
-    def teardown(self):
+    def teardown_method(self):
         self.storage.close()
         if os.path.isfile(self.filename):
             os.remove(self.filename)
@@ -425,7 +425,7 @@ def test_randomized_committor(self):
 
 
 class TestReactiveFluxSimulation(object):
-    def setup(self):
+    def setup_method(self):
         # PES is one-dimensional linear slope (y(x) = x)
         pes = toys.LinearSlope(m=[-1.0], c=[0.0])
         # one particle with mass 1.0
@@ -484,7 +484,7 @@ def setup(self):
                               rc=rc)
         self.simulation.output_stream = open(os.devnull, 'w')
 
-    def teardown(self):
+    def teardown_method(self):
         if os.path.isfile(self.filename):
             os.remove(self.filename)
         paths.EngineMover.default_engine = None
@@ -546,7 +546,7 @@ def test_simulation_run(self):
 
 
 class TestDirectSimulation(object):
-    def setup(self):
+    def setup_method(self):
         pes = toys.HarmonicOscillator(A=[1.0], omega=[1.0], x0=[0.0])
         topology = toys.Topology(n_spatial=1, masses=[1.0], pes=pes)
         integrator = toys.LeapfrogVerletIntegrator(0.1)
@@ -780,7 +780,7 @@ def test_sim_with_storage(self):
 
 
 class TestPathSampling(object):
-    def setup(self):
+    def setup_method(self):
         paths.InterfaceSet._reset()
         self.cv = paths.FunctionCV("x", lambda x: x.xyz[0][0])
         self.state_A = paths.CVDefinedVolume(self.cv, float("-inf"), 0.0)
diff --git a/openpathsampling/tests/test_plumed_wrapper.py b/openpathsampling/tests/test_plumed_wrapper.py
index 94703283a..ae3407f20 100755
--- a/openpathsampling/tests/test_plumed_wrapper.py
+++ b/openpathsampling/tests/test_plumed_wrapper.py
@@ -35,7 +35,7 @@
 
 
 class TestPLUMED(object):
-    def setup(self):
+    def setup_method(self):
         if not HAS_PLUMED:
             pytest.skip("PLUMED module not installed.")
         if not HAS_OPENMM:
@@ -55,7 +55,7 @@ def setup(self):
         if os.path.isfile("test.nc"):
             os.remove("test.nc")
 
-    def teardown(self):
+    def teardown_method(self):
         if os.path.isfile("test.nc"):
             os.remove("test.nc")
         root = os.listdir(".")
diff --git a/openpathsampling/tests/test_progress.py b/openpathsampling/tests/test_progress.py
index a47f428df..20d197738 100644
--- a/openpathsampling/tests/test_progress.py
+++ b/openpathsampling/tests/test_progress.py
@@ -14,7 +14,7 @@ def test_silent_progress(capsys):
 
 
 class TestTqdmPartial(object):
-    def setup(self):
+    def setup_method(self):
         _ = pytest.importorskip('tqdm')
         self.tqdm_partial = TqdmPartial(desc="foo")
 
@@ -40,7 +40,7 @@ def test_call(self, capsys):
 
 
 class TestSimpleProgress(object):
-    def setup(self):
+    def setup_method(self):
         self.progress = SimpleProgress()
 
     def test_progress_getter(self):
diff --git a/openpathsampling/tests/test_pyemma_cv.py b/openpathsampling/tests/test_pyemma_cv.py
index 1e3db7fd6..02ab7f2b1 100644
--- a/openpathsampling/tests/test_pyemma_cv.py
+++ b/openpathsampling/tests/test_pyemma_cv.py
@@ -10,7 +10,7 @@
 
 
 class TestPyEMMAFeaturizerCV(object):
-    def setup(self):
+    def setup_method(self):
         self.md_trajectory = mdtraj.load(data_filename("ala_small_traj.pdb"))
         self.ops_trajectory = trajectory_from_mdtraj(self.md_trajectory)
         # output of f.pairs(f.select_Backbone()) in the pyemma_generator
@@ -27,7 +27,7 @@ def pyemma_generator(f):
         self.storage = paths.Storage('delete.nc', 'w')
         self.fname = self.storage.filename
 
-    def teardown(self):
+    def teardown_method(self):
         fname = self.fname
         if self.storage.isopen():
             self.storage.close()
diff --git a/openpathsampling/tests/test_reactive_flux_analysis.py b/openpathsampling/tests/test_reactive_flux_analysis.py
index d2063568c..007ad5ea5 100644
--- a/openpathsampling/tests/test_reactive_flux_analysis.py
+++ b/openpathsampling/tests/test_reactive_flux_analysis.py
@@ -30,7 +30,7 @@
 
 
 class TestReactiveFluxAnalysis(object):
-    def setup(self):
+    def setup_method(self):
         import openpathsampling.engines.toy as toys
         # PES is one-dimensional linear slope (y(x) = -x)
         pes = toys.LinearSlope(m=[-1.0], c=[0.0])
@@ -131,8 +131,9 @@ def setup(self):
         self.simulation.run(n_per_snapshot=1)
         self.analysis = None
 
-    def teardown(self):
+    def teardown_method(self):
         if os.path.isfile(self.filename):
+            self.storage.close()
             os.remove(self.filename)
         paths.EngineMover.default_engine = None
 
@@ -142,8 +143,8 @@ def gradient(self, snapshot):
         return np.array([[1.0]])
 
     def test_analysis(self):
-        self.storage = paths.Storage(self.filename, mode="r")
-        self.analysis = paths.ReactiveFluxAnalysis(steps=self.storage.steps,
+        storage = paths.Storage(self.filename, mode="r")
+        self.analysis = paths.ReactiveFluxAnalysis(steps=storage.steps,
                                                    gradient=self.gradient)
 
         # check wrong analyze_single_step() argument
diff --git a/openpathsampling/tests/test_resampling_statistics.py b/openpathsampling/tests/test_resampling_statistics.py
index 2ef37d927..7a82f029a 100644
--- a/openpathsampling/tests/test_resampling_statistics.py
+++ b/openpathsampling/tests/test_resampling_statistics.py
@@ -13,7 +13,7 @@
 
 class TestResamplingStatistics(object):
     # NOTE: we test the mean_df and std_df functions within this
-    def setup(self):
+    def setup_method(self):
         # order of the column/index labels should not matter
         self.list_AB = ['A', 'B']
         self.list_BA = ['B', 'A']
@@ -86,7 +86,7 @@ def test_percentile(self):
 
 
 class TestBlockResampling(object):
-    def setup(self):
+    def setup_method(self):
         self.samples = list(range(100))
 
     def test_default_initialization(self):
diff --git a/openpathsampling/tests/test_sample.py b/openpathsampling/tests/test_sample.py
index 6966037eb..21a4c4c56 100644
--- a/openpathsampling/tests/test_sample.py
+++ b/openpathsampling/tests/test_sample.py
@@ -15,7 +15,7 @@
 
 
 class TestSampleSet(object):
-    def setup(self):
+    def setup_method(self):
         self.ensA = LengthEnsemble(1)
         self.ensB = LengthEnsemble(2)
         traj0A = Trajectory([0.5])
@@ -103,7 +103,7 @@ def test_setitem_itemexists_ensemble(self):
         testset2 = SampleSet([self.s0A, self.s1A, self.s2B])
         # if we accidentally hit the statistical code, repeating 100 times
         # reduces odds of getting the same replacement each time
-        for i in range(100): 
+        for i in range(100):
             self.testset[self.ensA] = self.s0A
             self.testset.consistency_check()
             assert_equal(len(self.testset), 3)
diff --git a/openpathsampling/tests/test_shared.py b/openpathsampling/tests/test_shared.py
index 8e9422110..b210e7b1c 100644
--- a/openpathsampling/tests/test_shared.py
+++ b/openpathsampling/tests/test_shared.py
@@ -10,7 +10,7 @@
 
 
 class TestStaticContainerStore(object):
-    def teardown(self):
+    def teardown_method(self):
         if os.path.isfile("test.nc"):
             os.remove("test.nc")
 
diff --git a/openpathsampling/tests/test_shooting.py b/openpathsampling/tests/test_shooting.py
index 60ac42c58..9c352a5df 100644
--- a/openpathsampling/tests/test_shooting.py
+++ b/openpathsampling/tests/test_shooting.py
@@ -14,7 +14,7 @@
 
 
 class SelectorTest(object):
-    def setup(self):
+    def setup_method(self):
         self.mytraj = make_1d_traj(coordinates=[-0.5, 0.1, 0.2, 0.3, 0.5],
                                    velocities=[1.0, 1.0, 1.0, 1.0, 1.0])
         self.dyn = CalvinistDynamics([-0.5, -0.4, -0.3, -0.2, -0.1,
@@ -50,10 +50,27 @@ def test_probability(self):
             assert sel.probability(frame, self.mytraj) == \
                     uniform.probability(frame, self.mytraj)
 
+    def test_pick(self):
+        sel = ShootingPointSelector()
+        test_traj_len = 22  # must be a multiple of 2 for this test
+        test_traj = [1 for _ in range(test_traj_len)]
+        # overwrite _biases to make sure we can only pick the last frame
+        sel._biases = lambda traj: [0 for _ in traj[:-1]] + [1]
+        assert sel.pick(test_traj) == test_traj_len - 1
+        # test pick first frame
+        sel._biases = lambda traj: [1] + [0 for _ in traj[1:]]
+        assert sel.pick(test_traj) == 0
+        # and test middle frame (only works if test_traj_len is a multiple of 2)
+        sel._biases = lambda traj: ([0 for _ in traj[:test_traj_len // 2]]
+                                    + [1]
+                                    + [0 for _ in traj[test_traj_len // 2 + 1:]]
+                                    )
+        assert sel.pick(test_traj) == test_traj_len // 2
+
 
 class TestGaussianBiasSelector(SelectorTest):
-    def setup(self):
-        super(TestGaussianBiasSelector, self).setup()
+    def setup_method(self):
+        super(TestGaussianBiasSelector, self).setup_method()
         self.cv = paths.FunctionCV("Id", lambda x: x.xyz[0][0])
         self.sel = GaussianBiasSelector(self.cv, alpha=2.0, l_0=0.25)
         self.f = [
@@ -83,9 +100,10 @@ def test_probability(self, frame):
         expected = pytest.approx(self.f[frame] / norm)
         assert self.sel.probability(traj[frame], traj) == expected
 
+
 class TestBiasedSelector(SelectorTest):
-    def setup(self):
-        super(TestBiasedSelector, self).setup()
+    def setup_method(self):
+        super(TestBiasedSelector, self).setup_method()
         self.f = {
             'gaussian': [
                 0.32465246735834974,  # = exp(-2.0*(-0.5-0.25)**2)
@@ -125,6 +143,7 @@ def test_probability(self, func, frame):
         expected = pytest.approx(f[frame] / norm)
         assert sel.probability(traj[frame], traj) == expected
 
+
 class TestFirstFrameSelector(SelectorTest):
     def test_pick(self):
         sel = FirstFrameSelector()
@@ -183,7 +202,7 @@ def test_f(self):
 
 
 class TestConstrainedSelector(SelectorTest):
-    def setup(self):
+    def setup_method(self):
         cvx = paths.FunctionCV('ID', lambda snap: snap.xyz[0][0])
         vol = paths.CVDefinedVolume(cvx, float('-inf'), 0)
         self.sel = InterfaceConstrainedSelector(vol)
@@ -214,3 +233,33 @@ def test_f(self):
         for idx1, frame in enumerate(mytraj):
             if (idx1 != expected_idx):
                 assert self.sel.f(frame, mytraj) == 0.0
+
+    @pytest.mark.parametrize("new_coord,expected", [(0.11, 1.0), (-0.09, 0.0)])
+    def test_probability_ratio_modified_coordinates(self, new_coord, expected):
+        # Test if probability ratio still works when coordinates are modified
+        mytraj = make_1d_traj(coordinates=[-0.5, -0.4, -0.3, -0.1,
+                                           0.1, 0.2, 0.3, 0.5])
+        idx = self.sel.pick(mytraj)
+        frame = mytraj[idx]
+        # Alter 0.1 to 0.11 and replace the original snapshot with the modded
+        # one
+        mod_frame = frame.copy_with_replacement(coordinates=[[new_coord]])
+        mod_traj = mytraj[:idx]
+        mod_traj += paths.Trajectory([mod_frame])
+        mod_traj += mytraj[idx+1:]
+        prob = self.sel.probability_ratio(frame, mytraj, mod_traj, mod_frame)
+        assert prob == expected
+
+
+class TestDeprecations(object):
+    @pytest.mark.parametrize('selector_class', [ShootingPointSelector,
+                                                FirstFrameSelector,
+                                                FinalFrameSelector])
+    def test_new_snapshot(self, selector_class):
+        selector = selector_class()
+        mytraj = make_1d_traj(coordinates=[-0.5, -0.4, -0.3, -0.1,
+                                           0.1, 0.2, 0.3, 0.5])
+        with pytest.deprecated_call(match='new_snapshot'):
+            _ = selector.probability_ratio(mytraj[1], mytraj, mytraj)
+        # Reset the warning for the next class
+        NEW_SNAPSHOT_SELECTOR.has_warned = False
diff --git a/openpathsampling/tests/test_shooting_point_analysis.py b/openpathsampling/tests/test_shooting_point_analysis.py
index ba999edad..fb2fb5e71 100644
--- a/openpathsampling/tests/test_shooting_point_analysis.py
+++ b/openpathsampling/tests/test_shooting_point_analysis.py
@@ -26,7 +26,7 @@
 
 
 class TestTransformedDict(object):
-    def setup(self):
+    def setup_method(self):
         self.untransformed = {(0, 1): "a", (1, 2): "b", (2, 3): "c"}
         self.transformed = {0: "a", 1: "b", 2: "c"}
         self.hash_function = lambda x: x[0]
@@ -72,7 +72,7 @@ def test_rehash(self):
 
 
 class TestSnapshotByCoordinateDict(object):
-    def setup(self):
+    def setup_method(self):
         self.empty_dict = SnapshotByCoordinateDict()
         coords_A = np.array([[0.0, 0.0]])
         coords_B = np.array([[1.0, 1.0]])
@@ -96,7 +96,7 @@ def test_get_set(self):
 
 
 class TestShootingPointAnalysis(object):
-    def setup(self):
+    def setup_method(self):
         self.HAS_TQDM = paths.progress.HAS_TQDM
         paths.progress.HAS_TQDM = False
         # taken from the TestCommittorSimulation
@@ -145,7 +145,7 @@ def setup(self):
         self.analyzer = ShootingPointAnalysis(self.storage.steps,
                                               [self.left, self.right])
 
-    def teardown(self):
+    def teardown_method(self):
         import os
         paths.progress.HAS_TQDM = self.HAS_TQDM
         self.storage.close()
diff --git a/openpathsampling/tests/test_snapshot_modifier.py b/openpathsampling/tests/test_snapshot_modifier.py
index 2fa2cf59b..89582891e 100644
--- a/openpathsampling/tests/test_snapshot_modifier.py
+++ b/openpathsampling/tests/test_snapshot_modifier.py
@@ -5,11 +5,8 @@
 from past.utils import old_div
 from builtins import object
 import pytest
-from nose.tools import (assert_equal, assert_not_equal, raises,
-                        assert_almost_equal, assert_true)
-from nose.plugins.skip import SkipTest
 from numpy.testing import assert_array_almost_equal
-from .test_helpers import make_1d_traj, u
+from .test_helpers import u
 
 import openpathsampling as paths
 import openpathsampling.engines as peng
@@ -32,9 +29,16 @@
 logging.getLogger('openpathsampling.storage').setLevel(logging.CRITICAL)
 logging.getLogger('openpathsampling.netcdfplus').setLevel(logging.CRITICAL)
 
-class TestNoModification(object):
-    def setup(self):
-        self.modifier = NoModification()
+
+class TestSnapshotModifier(object):
+    def setup_method(self):
+        # TODO OPS 2.0: This subclass is only here for python 2.7 should be
+        # replaced with SnapshotModifier
+        class DummyMod(SnapshotModifier):
+            def __call__(self, a):
+                return a
+        self.Modifier = DummyMod
+        self.modifier = DummyMod()
         self.snapshot_1D = peng.toy.Snapshot(
             coordinates=np.array([0.0, 1.0, 2.0, 3.0]),
             velocities=np.array([0.5, 1.5, 2.5, 3.5])
@@ -54,9 +58,8 @@ def setup(self):
                                  [3.5, 3.6, 3.7]])
         )
 
-    # test methods from the abstract base class along with NoModification
     def test_extract_subset(self):
-        mod = NoModification(subset_mask=[1,2])
+        mod = self.Modifier(subset_mask=[1, 2])
         sub_1Dx = mod.extract_subset(self.snapshot_1D.coordinates)
         assert_array_almost_equal(sub_1Dx, np.array([1.0, 2.0]))
         sub_1Dv = mod.extract_subset(self.snapshot_1D.velocities)
@@ -70,12 +73,12 @@ def test_extract_subset(self):
                                                      [2.5, 2.6, 2.7]]))
 
     def test_apply_to_subset(self):
-        mod = NoModification(subset_mask=[1,2])
+        mod = self.Modifier(subset_mask=[1, 2])
         copy_1Dx = self.snapshot_1D.coordinates.copy()
         new_1Dx = mod.apply_to_subset(copy_1Dx, np.array([-1.0, -2.0]))
         assert_array_almost_equal(new_1Dx, np.array([0.0, -1.0, -2.0, 3.0]))
         # and check that memory points to the right things; orig unchanged
-        assert_true(copy_1Dx is new_1Dx)
+        assert copy_1Dx is new_1Dx
         assert_array_almost_equal(self.snapshot_1D.coordinates,
                                   np.array([0.0, 1.0, 2.0, 3.0]))
 
@@ -88,13 +91,19 @@ def test_apply_to_subset(self):
                                                      [-2.0, -2.1, -2.2],
                                                      [3.0, 3.1, 3.2]]))
         # and check that memory points to the right things; orig unchanged
-        assert_true(copy_3Dx is new_3Dx)
+        assert copy_3Dx is new_3Dx
         assert_array_almost_equal(self.snapshot_3D.coordinates,
                                   np.array([[0.0, 0.1, 0.2],
                                             [1.0, 1.1, 1.2],
                                             [2.0, 2.1, 2.2],
                                             [3.0, 3.1, 3.2]]))
 
+
+class TestNoModification(TestSnapshotModifier):
+    def setup_method(self):
+        super(TestNoModification, self).setup_method()
+        self.modifier = NoModification()
+
     def test_call(self):
         new_1D = self.modifier(self.snapshot_1D)
         assert_array_almost_equal(self.snapshot_1D.coordinates,
@@ -106,14 +115,25 @@ def test_call(self):
                                   new_3D.coordinates)
         assert_array_almost_equal(self.snapshot_3D.velocities,
                                   new_3D.velocities)
-        assert_true(self.snapshot_1D.coordinates is not new_1D.coordinates)
-        assert_true(self.snapshot_1D.velocities is not new_1D.velocities)
-        assert_true(self.snapshot_3D.coordinates is not new_3D.coordinates)
-        assert_true(self.snapshot_3D.velocities is not new_3D.velocities)
+        assert self.snapshot_1D.coordinates is not new_1D.coordinates
+        assert self.snapshot_1D.velocities is not new_1D.velocities
+        assert self.snapshot_3D.coordinates is not new_3D.coordinates
+        assert self.snapshot_3D.velocities is not new_3D.velocities
+
+    def test_call_no_copy(self):
+        mod = NoModification(as_copy=False)
+        new_1D = mod(self.snapshot_1D)
+        assert new_1D is self.snapshot_1D
+        new_3D = mod(self.snapshot_3D)
+        assert new_3D is self.snapshot_3D
+
+    def test_probability_ratio(self):
+        # This should always return 1.0 even for invalid input
+        assert self.modifier.probability_ratio(None, None) == 1.0
 
 
 class TestRandomizeVelocities(object):
-    def setup(self):
+    def setup_method(self):
         # TODO: check against several possibilities, including various
         # combinations of shapes of velocities and masses.
         topology_2x3D = paths.engines.toy.Topology(
@@ -146,51 +166,52 @@ def test_call(self):
         # NOTE: these tests basically check the API. Tests for correctness
         # are in `test_snapshot_modifier.ipynb`, because they are inherently
         # stochastic.
-        randomizer = RandomVelocities(beta=old_div(1.0,5.0))
+        randomizer = RandomVelocities(beta=old_div(1.0, 5.0))
         new_1x2D = randomizer(self.snap_1x2D)
-        assert_equal(new_1x2D.coordinates.shape, new_1x2D.velocities.shape)
-        assert_array_almost_equal(new_1x2D.coordinates,
-                                  self.snap_1x2D.coordinates)
-        assert_true(new_1x2D is not self.snap_1x2D)
-        assert_true(new_1x2D.coordinates is not self.snap_1x2D.coordinates)
-        assert_true(new_1x2D.velocities is not self.snap_1x2D.velocities)
+        assert new_1x2D.coordinates.shape == new_1x2D.velocities.shape
+        assert (pytest.approx(new_1x2D.coordinates) ==
+                self.snap_1x2D.coordinates)
+        assert new_1x2D is not self.snap_1x2D
+        assert new_1x2D.coordinates is not self.snap_1x2D.coordinates
+        assert new_1x2D.velocities is not self.snap_1x2D.velocities
         for val in new_1x2D.velocities.flatten():
-            assert_not_equal(val, 0.0)
+            assert val != 0.0
+        assert randomizer.probability_ratio(self.snap_1x2D, new_1x2D) == 1.0
 
         new_2x3D = randomizer(self.snap_2x3D)
-        assert_equal(new_2x3D.coordinates.shape, new_2x3D.velocities.shape)
+        assert new_2x3D.coordinates.shape == new_2x3D.velocities.shape
         assert_array_almost_equal(new_2x3D.coordinates,
                                   self.snap_2x3D.coordinates)
-        assert_true(new_2x3D is not self.snap_2x3D)
-        assert_true(new_2x3D.coordinates is not self.snap_2x3D.coordinates)
-        assert_true(new_2x3D.velocities is not self.snap_2x3D.velocities)
+        assert new_2x3D is not self.snap_2x3D
+        assert new_2x3D.coordinates is not self.snap_2x3D.coordinates
+        assert new_2x3D.velocities is not self.snap_2x3D.velocities
         for val in new_2x3D.velocities.flatten():
-            assert_not_equal(val, 0.0)
+            assert val != 0.0
 
         new_3x1D = randomizer(self.snap_3x1D)
-        assert_equal(new_3x1D.coordinates.shape, new_3x1D.velocities.shape)
+        assert new_3x1D.coordinates.shape == new_3x1D.velocities.shape
         assert_array_almost_equal(new_3x1D.coordinates,
                                   self.snap_3x1D.coordinates)
-        assert_true(new_3x1D is not self.snap_3x1D)
-        assert_true(new_3x1D.coordinates is not self.snap_3x1D.coordinates)
-        assert_true(new_3x1D.velocities is not self.snap_3x1D.velocities)
+        assert new_3x1D is not self.snap_3x1D
+        assert new_3x1D.coordinates is not self.snap_3x1D.coordinates
+        assert new_3x1D.velocities is not self.snap_3x1D.velocities
         for val in new_3x1D.velocities.flatten():
-            assert_not_equal(val, 0.0)
+            assert val != 0.0
 
     def test_subset_call(self):
-        randomizer = RandomVelocities(beta=old_div(1.0,5.0), subset_mask=[0])
+        randomizer = RandomVelocities(beta=old_div(1.0, 5.0), subset_mask=[0])
         new_2x3D = randomizer(self.snap_2x3D)
-        assert_equal(new_2x3D.coordinates.shape, new_2x3D.velocities.shape)
+        assert new_2x3D.coordinates.shape == new_2x3D.velocities.shape
         assert_array_almost_equal(new_2x3D.coordinates,
                                   self.snap_2x3D.coordinates)
-        assert_true(new_2x3D is not self.snap_2x3D)
-        assert_true(new_2x3D.coordinates is not self.snap_2x3D.coordinates)
-        assert_true(new_2x3D.velocities is not self.snap_2x3D.velocities)
+        assert new_2x3D is not self.snap_2x3D
+        assert new_2x3D.coordinates is not self.snap_2x3D.coordinates
+        assert new_2x3D.velocities is not self.snap_2x3D.velocities
         # show that the unchanged atom is, in fact, unchanged
         assert_array_almost_equal(new_2x3D.velocities[1],
                                   self.snap_2x3D.velocities[1])
         for val in new_2x3D.velocities[0]:
-            assert_not_equal(val, 0.0)
+            assert val != 0.0
 
     def test_no_beta_bad_engine(self):
         engine = self.snap_2x3D.engine
@@ -202,7 +223,7 @@ def test_with_openmm_snapshot(self):
         # note: this is only a smoke test; correctness depends on OpenMM's
         # tests of its constraint approaches.
         if not omt:
-            raise SkipTest("Requires OpenMMTools (not installed)")
+            pytest.skip("Requires OpenMMTools (not installed)")
         test_system = omt.testsystems.AlanineDipeptideVacuum()
         template = omm_engine.snapshot_from_testsystem(test_system)
         engine = omm_engine.Engine(
@@ -219,9 +240,7 @@ def test_with_openmm_snapshot(self):
         assert_array_almost_equal(template.coordinates,
                                   new_snap.coordinates)
         # velocities changed
-        assert_equal(np.isclose(template.velocities,
-                                new_snap.velocities).all(),
-                     False)
+        assert not np.isclose(template.velocities, new_snap.velocities).all()
         engine.generate(new_snap, [lambda x, foo: len(x) <= 4])
 
         # when the engine does have an existing snapshot
@@ -239,15 +258,20 @@ def test_with_openmm_snapshot(self):
         assert_array_almost_equal(template.coordinates,
                                   new_snap.coordinates)
         # velocities changed
-        assert_equal(np.isclose(template.velocities,
-                                new_snap.velocities).all(),
-                     False)
+        assert not np.isclose(template.velocities, new_snap.velocities).all()
+
         # internal snapshot unchanged
-        assert_equal(engine.current_snapshot, zero_snap)
+        assert engine.current_snapshot == zero_snap
         engine.generate(new_snap, [lambda x, foo: len(x) <= 4])
 
+    def test_probability_ratio(self):
+        # Should be sampled correctio, so this has to be 1.0
+        randomizer = RandomVelocities(beta=20)
+        assert randomizer.probability_ratio(None, None) == 1.0
+
+
 class TestGeneralizedDirectionModifier(object):
-    def setup(self):
+    def setup_method(self):
         import openpathsampling.engines.toy as toys
         # applies one delta_v to all atoms
         self.toy_modifier_all = GeneralizedDirectionModifier(1.5)
@@ -274,9 +298,9 @@ def setup(self):
 
         # create the OpenMM versions
         if not omt:
-            raise SkipTest("Requires OpenMMTools (not installed)")
+            pytest.skip("Requires OpenMMTools (not installed)")
         if not u:
-            raise SkipTest("Requires openmm.unit (not installed)")
+            pytest.skip("Requires openmm.unit (not installed)")
         u_vel = old_div(u.nanometer, u.picosecond)
         self.openmm_modifier = GeneralizedDirectionModifier(1.2 * u_vel)
         ad_vacuum = omt.testsystems.AlanineDipeptideVacuum(constraints=None)
@@ -298,13 +322,12 @@ def test_verify_snapshot_toy(self):
     def test_verify_snapshot_openmm(self):
         self.openmm_modifier._verify_snapshot(self.openmm_snap)
 
-    @raises(RuntimeError)
     def test_verify_snapshot_no_dofs(self):
-        assert_true(isinstance(self.test_snap.engine,
-                               omm_engine.tools.OpenMMToolsTestsystemEngine))
-        self.openmm_modifier._verify_snapshot(self.test_snap)
+        assert isinstance(self.test_snap.engine,
+                          omm_engine.tools.OpenMMToolsTestsystemEngine)
+        with pytest.raises(RuntimeError, match="missing n_degrees_of_freedom"):
+            self.openmm_modifier._verify_snapshot(self.test_snap)
 
-    @raises(RuntimeError)
     def test_verify_snapshot_constraints(self):
         ad_vacuum_constr = omt.testsystems.AlanineDipeptideVacuum()
         constrained_engine = omm_engine.Engine(
@@ -315,7 +338,8 @@ def test_verify_snapshot_constraints(self):
         constr_snap = self.test_snap.copy_with_replacement(
             engine=constrained_engine
         )
-        self.openmm_modifier._verify_snapshot(constr_snap)
+        with pytest.raises(RuntimeError, match="constraints"):
+            self.openmm_modifier._verify_snapshot(constr_snap)
 
     def test_verify_engine_constraints(self):
         ad_vacuum_constr = omt.testsystems.AlanineDipeptideVacuum()
@@ -343,7 +367,7 @@ def test_verify_snapshot_box_vectors(self):
             rigid_water=False
         )
         ad_explicit_tmpl = omm_engine.snapshot_from_testsystem(ad_explicit)
-        explicit_engine= omm_engine.Engine(
+        explicit_engine = omm_engine.Engine(
             topology=ad_explicit_tmpl.topology,
             system=ad_explicit.system,
             integrator=omt.integrators.VVVRIntegrator()
@@ -372,7 +396,7 @@ def test_dv_widths_openmm(self):
         results = self.openmm_modifier._dv_widths(n_atoms, n_atoms)
         expected = np.array([1.2] * n_atoms) * u.nanometer / u.picosecond
         for truth, beauty in zip(expected, results):
-            assert_almost_equal(truth, beauty)
+            assert pytest.approx(truth._value) == beauty._value
 
     def test_rescale_linear_momenta_constant_energy_toy(self):
         velocities = np.array([[1.5, -1.0], [-1.0, 2.0], [0.25, -1.0]])
@@ -394,7 +418,7 @@ def test_rescale_linear_momenta_constant_energy_toy(self):
         new_ke = sum(sum(new_momenta * new_vel))
         # tests require that the linear momentum be 0, and KE be correct
         assert_array_almost_equal(total_momenta, np.array([0.0]*2))
-        assert_almost_equal(new_ke, 20.0)
+        assert pytest.approx(new_ke) == 20.0
 
     def test_remove_momentum_rescale_energy_openmm(self):
         # don't actually need to do everything with OpenMM, but do need to
@@ -403,8 +427,10 @@ def test_remove_momentum_rescale_energy_openmm(self):
         u_mass = old_div(u.dalton, u.AVOGADRO_CONSTANT_NA)
         u_energy = old_div(u.kilojoule_per_mole, u.AVOGADRO_CONSTANT_NA)
 
-        velocities = \
-                np.array([[1.5, -1.0], [-1.0, 2.0], [0.25, -1.0]]) * u_vel
+        velocities = np.array([[1.5, -1.0],
+                               [-1.0, 2.0],
+                               [0.25, -1.0]]
+                              ) * u_vel
         masses = np.array([1.0, 1.5, 4.0]) * u_mass
         new_vel = self.openmm_modifier._remove_linear_momentum(
             velocities=velocities,
@@ -427,12 +453,16 @@ def test_remove_momentum_rescale_energy_openmm(self):
         # tests require that the linear momentum be 0, and KE be correct
         assert_array_almost_equal(total_momenta,
                                   np.array([0.0]*2) * u_vel * u_mass)
-        assert_equal(new_ke.unit, (20.0 * u_energy).unit)
-        assert_almost_equal(new_ke._value, (20.0 * u_energy)._value)
+        assert new_ke.unit == (20.0 * u_energy).unit
+        assert pytest.approx(new_ke._value) == (20.0 * u_energy)._value
+
+    def test_probability_ratio(self):
+        # Should always be 1 as KE is conserved
+        assert self.toy_modifier_all.probability_ratio(None, None) == 1.0
 
 
 class TestVelocityDirectionModifier(object):
-    def setup(self):
+    def setup_method(self):
         import openpathsampling.engines.toy as toys
         self.toy_modifier = VelocityDirectionModifier(
             delta_v=[1.0, 2.0],
@@ -457,7 +487,8 @@ def setup(self):
                 remove_linear_momentum=False
             )
             if omt:  # TODO: separate out tests
-                ad_vacuum = omt.testsystems.AlanineDipeptideVacuum(constraints=None)
+                ad_vacuum = omt.testsystems.AlanineDipeptideVacuum(
+                    constraints=None)
                 self.test_snap = omm_engine.snapshot_from_testsystem(ad_vacuum)
                 self.openmm_engine = omm_engine.Engine(
                     topology=self.test_snap.topology,
@@ -467,15 +498,16 @@ def setup(self):
 
                 self.openmm_snap = self.test_snap.copy_with_replacement(
                     engine=self.openmm_engine,
-                    velocities=np.ones(shape=self.test_snap.velocities.shape) * u_vel
+                    velocities=np.ones(
+                        shape=self.test_snap.velocities.shape) * u_vel
                 )
 
     def test_select_atoms_to_modify(self):
-        assert_equal(self.toy_modifier._select_atoms_to_modify(2), [0, 1])
+        assert self.toy_modifier._select_atoms_to_modify(2) == [0, 1]
         if omt:  # TODO: separate out tests
             n_atoms = len(self.openmm_snap.coordinates)
-            assert_equal(self.openmm_modifier._select_atoms_to_modify(n_atoms),
-                         list(range(n_atoms)))
+            assert (self.openmm_modifier._select_atoms_to_modify(n_atoms) ==
+                    list(range(n_atoms)))
 
     def test_call(self):
         new_toy_snap = self.toy_modifier(self.toy_snapshot)
@@ -483,12 +515,12 @@ def test_call(self):
                                   self.toy_snapshot.coordinates)
         new_vel = new_toy_snap.velocities
         old_vel = self.toy_snapshot.velocities
-        same_vel = [np.allclose(new_vel[i], old_vel[i]) 
+        same_vel = [np.allclose(new_vel[i], old_vel[i])
                     for i in range(len(new_vel))]
-        assert_equal(Counter(same_vel), Counter({True: 1, False: 2}))
+        assert Counter(same_vel) == Counter({True: 1, False: 2})
         for new_v, old_v in zip(new_vel, old_vel):
-            assert_almost_equal(sum([v**2 for v in new_v]),
-                                sum([v**2 for v in old_v]))
+            assert (pytest.approx(sum([v**2 for v in new_v])) ==
+                    sum([v**2 for v in old_v]))
 
         if omt:  # TODO: separate out tests
             new_omm_snap = self.openmm_modifier(self.openmm_snap)
@@ -497,17 +529,18 @@ def test_call(self):
                                       self.openmm_snap.coordinates)
             new_vel = new_omm_snap.velocities
             old_vel = self.openmm_snap.velocities
-            same_vel = [np.allclose(new_vel[i], old_vel[i]) 
+            same_vel = [np.allclose(new_vel[i], old_vel[i])
                         for i in range(len(new_vel))]
-            same_vel = [np.allclose(new_vel[i], old_vel[i]) 
+            same_vel = [np.allclose(new_vel[i], old_vel[i])
                         for i in range(len(new_vel))]
-            assert_equal(Counter(same_vel), Counter({False: n_atoms}))
+            assert Counter(same_vel) == Counter({False: n_atoms})
             u_vel_sq = (old_div(u.nanometers, u.picoseconds))**2
             for new_v, old_v in zip(new_vel, old_vel):
-                assert_almost_equal(
-                    sum([(v**2).value_in_unit(u_vel_sq) for v in new_v]),
-                    sum([(v**2).value_in_unit(u_vel_sq) for v in old_v])
-                )
+                assert (pytest.approx(sum([(v**2).value_in_unit(u_vel_sq)
+                                           for v in new_v])
+                                      ) ==
+                        sum([(v**2).value_in_unit(u_vel_sq) for v in old_v])
+                        )
 
     def test_call_with_linear_momentum_fix(self):
         toy_modifier = VelocityDirectionModifier(
@@ -520,7 +553,7 @@ def test_call_with_linear_momentum_fix(self):
         momenta = velocities * new_toy_snap.masses[:, np.newaxis]
         assert_array_almost_equal(sum(momenta), np.array([0.0]*2))
         double_ke = sum(sum(momenta * velocities))
-        assert_almost_equal(double_ke, 86.0)
+        assert pytest.approx(double_ke) == 86.0
 
         if omt:  # TODO: separate out tests
             u_vel = old_div(u.nanometer, u.picosecond)
@@ -538,8 +571,9 @@ def test_call_with_linear_momentum_fix(self):
             assert_array_almost_equal(total_momenta,
                                       np.array([0.0]*3) * u_vel * u_mass)
 
+
 class TestSingleAtomVelocityDirectionModifier(object):
-    def setup(self):
+    def setup_method(self):
         import openpathsampling.engines.toy as toys
         self.toy_modifier = SingleAtomVelocityDirectionModifier(
             delta_v=[1.0, 2.0],
@@ -563,28 +597,30 @@ def setup(self):
                 delta_v=1.2*u_vel,
                 remove_linear_momentum=False
             )
-            ad_vacuum = omt.testsystems.AlanineDipeptideVacuum(constraints=None)
+            ad_vacuum = omt.testsystems.AlanineDipeptideVacuum(
+                constraints=None)
             self.test_snap = omm_engine.snapshot_from_testsystem(ad_vacuum)
             self.openmm_engine = omm_engine.Engine(
                 topology=self.test_snap.topology,
                 system=ad_vacuum.system,
                 integrator=omt.integrators.VVVRIntegrator()
             )
-        
+
             self.openmm_snap = self.test_snap.copy_with_replacement(
                 engine=self.openmm_engine,
-                velocities=np.ones(shape=self.test_snap.velocities.shape) * u_vel
+                velocities=np.ones(
+                    shape=self.test_snap.velocities.shape) * u_vel
             )
 
     def test_select_atoms_to_modify(self):
         selected = self.toy_modifier._select_atoms_to_modify(2)
-        assert_equal(len(selected), 1)
+        assert len(selected) == 1
         selected = [self.toy_modifier._select_atoms_to_modify(2)[0]
                     for i in range(20)]
         count = Counter(selected)
-        assert_equal(set([0, 1]), set(count.keys()))
-        assert_true(count[0] > 0)
-        assert_true(count[1] > 0)
+        assert set([0, 1]) == set(count.keys())
+        assert count[0] > 0
+        assert count[1] > 0
 
     def test_call(self):
         new_toy_snap = self.toy_modifier(self.toy_snapshot)
@@ -592,12 +628,12 @@ def test_call(self):
                                   self.toy_snapshot.coordinates)
         new_vel = new_toy_snap.velocities
         old_vel = self.toy_snapshot.velocities
-        same_vel = [np.allclose(new_vel[i], old_vel[i]) 
+        same_vel = [np.allclose(new_vel[i], old_vel[i])
                     for i in range(len(new_vel))]
-        assert_equal(Counter(same_vel), Counter({True: 2, False: 1}))
+        assert Counter(same_vel) == Counter({True: 2, False: 1})
         for new_v, old_v in zip(new_vel, old_vel):
-            assert_almost_equal(sum([v**2 for v in new_v]),
-                                sum([v**2 for v in old_v]))
+            assert (pytest.approx(sum([v**2 for v in new_v])) ==
+                    sum([v**2 for v in old_v]))
 
         if omt:  # TODO: separate out tests
             new_omm_snap = self.openmm_modifier(self.openmm_snap)
@@ -606,17 +642,16 @@ def test_call(self):
                                       self.openmm_snap.coordinates)
             new_vel = new_omm_snap.velocities
             old_vel = self.openmm_snap.velocities
-            same_vel = [np.allclose(new_vel[i], old_vel[i]) 
+            same_vel = [np.allclose(new_vel[i], old_vel[i])
                         for i in range(len(new_vel))]
-            same_vel = [np.allclose(new_vel[i], old_vel[i]) 
+            same_vel = [np.allclose(new_vel[i], old_vel[i])
                         for i in range(len(new_vel))]
-            assert_equal(Counter(same_vel), Counter({True: n_atoms-1, False: 1}))
+            assert Counter(same_vel) == Counter({True: n_atoms-1, False: 1})
             u_vel_sq = (old_div(u.nanometers, u.picoseconds))**2
             for new_v, old_v in zip(new_vel, old_vel):
-                assert_almost_equal(
-                    sum([(v**2).value_in_unit(u_vel_sq) for v in new_v]),
-                    sum([(v**2).value_in_unit(u_vel_sq) for v in old_v])
-                )
+                assert (pytest.approx(
+                    sum([(v**2).value_in_unit(u_vel_sq) for v in new_v])) ==
+                    sum([(v**2).value_in_unit(u_vel_sq) for v in old_v]))
 
     def test_call_with_linear_momentum_fix(self):
         toy_modifier = SingleAtomVelocityDirectionModifier(
@@ -629,7 +664,7 @@ def test_call_with_linear_momentum_fix(self):
         momenta = velocities * new_toy_snap.masses[:, np.newaxis]
         assert_array_almost_equal(sum(momenta), np.array([0.0]*2))
         double_ke = sum(sum(momenta * velocities))
-        assert_almost_equal(double_ke, 86.0)
+        assert pytest.approx(double_ke) == 86.0
 
         if omt:  # TODO: separate out tests
             u_vel = old_div(u.nanometer, u.picosecond)
@@ -646,3 +681,29 @@ def test_call_with_linear_momentum_fix(self):
             total_momenta = sum(momenta, zero_momentum)
             assert_array_almost_equal(total_momenta,
                                       np.array([0.0]*3) * u_vel * u_mass)
+
+
+class TestSnapshotModifierDeprecations(object):
+    # TODO OPS 2.0: Depr should be completed and this test altered to check for
+    # the error
+    def test_raise_deprecation_prob_ratio(self):
+        class DummyMod(SnapshotModifier):
+            # TODO PY 2.7, don't override __call__ for  PY 3.x
+            def __call__(self, a):
+                pass
+        dummy_mod = DummyMod()
+        with pytest.warns(DeprecationWarning) as warn:
+            a = dummy_mod.probability_ratio(None, None)
+            assert len(warn) == 1
+            assert "NotImplementedError" in str(warn[0])
+        assert a == 1.0
+
+    def test_raise_depr_nomodifier_subset(self):
+        # The warning might be emited before on line 75
+        # (NoModification(subset_mask))
+        # Therefor this will not always trigger
+        pass
+        # with pytest.warns(DeprecationWarning) as warn:
+        #     _ = NoModification(subset_mask="foo")
+        #     assert len(warn) == 1
+        #     assert "subset_mask" in str(warn[0])
diff --git a/openpathsampling/tests/test_spring_shooting.py b/openpathsampling/tests/test_spring_shooting.py
index 1c21f70ec..ff61dd7ad 100644
--- a/openpathsampling/tests/test_spring_shooting.py
+++ b/openpathsampling/tests/test_spring_shooting.py
@@ -14,6 +14,7 @@
     ForwardSpringMover, BackwardSpringMover,
     SpringShootingMover, SpringShootingStrategy,
     SpringShootingMoveScheme)
+from openpathsampling.deprecations import NEW_SNAPSHOT_SELECTOR
 
 
 class FakeStep(object):
@@ -22,7 +23,7 @@ def __init__(self, details):
 
 
 class SelectorTest(object):
-    def setup(self):
+    def setup_method(self):
         self.mytraj = make_1d_traj(coordinates=[-0.5, 0.1, 0.2, 0.3, 0.5],
                                    velocities=[1.0, 1.0, 1.0, 1.0, 1.0])
         self.dyn = CalvinistDynamics([-0.5, -0.4, -0.3, -0.2, -0.1,
@@ -109,10 +110,14 @@ def test_probability_ratio():
         sel = SpringShootingSelector(delta_max=1,
                                      k_spring=1)
         sel._acceptable_snapshot = True
-        ratio = sel.probability_ratio(None, None, None)
+        # TODO OPS 2.0:  Last value here is not None to silence deprecation
+        # warnings, should be set to None after the completion
+        ratio = sel.probability_ratio(None, None, None, 1)
         assert ratio == 1.0
         sel._acceptable_snapshot = False
-        ratio = sel.probability_ratio(None, None, None)
+        # TODO OPS 2.0:  Last value here is not None to silence deprecation
+        # warnings, should be set to None after the completion
+        ratio = sel.probability_ratio(None, None, None, 1)
         assert ratio == 0.0
 
     def test_sanity_breaking_fw_pick(self):
@@ -292,10 +297,19 @@ def test_correct_loading():
         sel.restart_from_step(step)
         assert sel.trial_snapshot == 10
 
+    def test_deprecation(self):
+        sel = self.default_selector
+        # Override to mimick a good pick
+        sel._acceptable_snapshot = True
+        with pytest.warns(DeprecationWarning, match="new_snapshot"):
+            _ = sel.probability_ratio(None, None, None)
+        # Reset warning
+        NEW_SNAPSHOT_SELECTOR.has_warned = False
+
 
 class MoverTest(SelectorTest):
-    def setup(self):
-        super(MoverTest, self).setup()
+    def setup_method(self):
+        super(MoverTest, self).setup_method()
         sel = SpringShootingSelector(delta_max=1, k_spring=0)
         sel._fw_prob_list = [1.0, 0.0, 0.0]
         sel._bw_prob_list = [0.0, 0.0, 1.0]
diff --git a/openpathsampling/tests/test_sshooting_analysis.py b/openpathsampling/tests/test_sshooting_analysis.py
new file mode 100644
index 000000000..93dcb59ac
--- /dev/null
+++ b/openpathsampling/tests/test_sshooting_analysis.py
@@ -0,0 +1,149 @@
+import pytest
+from openpathsampling.tests.test_helpers import data_filename
+import openpathsampling as paths
+import openpathsampling.engines.toy as toys
+import numpy as np
+import os
+
+from openpathsampling.pathsimulators.sshooting_simulator import *
+from openpathsampling.analysis.sshooting_analysis import *
+
+import logging
+logging.getLogger('openpathsampling.initialization').setLevel(logging.CRITICAL)
+logging.getLogger('openpathsampling.storage').setLevel(logging.CRITICAL)
+logging.getLogger('openpathsampling.netcdfplus').setLevel(logging.CRITICAL)
+logging.getLogger('openpathsampling.ensemble').setLevel(logging.CRITICAL)
+logging.getLogger('openpathsampling.engines').setLevel(logging.CRITICAL)
+logging.getLogger('openpathsampling.pathmover').setLevel(logging.CRITICAL)
+logging.getLogger('openpathsampling.sample').setLevel(logging.CRITICAL)
+
+
+class TestSShootingAnalysis(object):
+    def setup_method(self):
+        # PES is one-dimensional negative slope (y(x) = -x)
+        pes = toys.LinearSlope(m=[-1.0], c=[0.0])
+        topology = toys.Topology(n_spatial=1, masses=[1.0], pes=pes)
+        integrator = toys.LeapfrogVerletIntegrator(0.02)
+        options = {
+            'integ' : integrator,
+            'n_frames_max' : 1000,
+            'n_steps_per_frame' : 1
+        }
+        self.engine = toys.Engine(options=options, topology=topology)
+        # test uses snapshots with different velocities
+        # (1) does not ever reach A
+        # (2) goes form A to S to B
+        # (3) goes from B to S to A
+        self.initial_snapshots = [toys.Snapshot(
+                                      coordinates=np.array([[0.0]]),
+                                      velocities=np.array([[1.0]]),
+                                      engine=self.engine),
+                                  toys.Snapshot(
+                                      coordinates=np.array([[0.1]]),
+                                      velocities=np.array([[2.0]]),
+                                      engine=self.engine),
+                                  toys.Snapshot(
+                                      coordinates=np.array([[-0.2]]),
+                                      velocities=np.array([[-2.0]]),
+                                      engine=self.engine)]
+        # trajectory length is set to 100 steps
+        self.l = 100
+        # reaction coordinate is just x coordinate
+        rc = paths.FunctionCV("Id", lambda snap : snap.coordinates[0][0])
+        # state A: [-inf, -1]
+        self.state_A = paths.CVDefinedVolume(rc, float("-inf"), -1.0)
+        # state B: [1, inf]
+        self.state_B = paths.CVDefinedVolume(rc, 1.0, float("inf"))
+        # state S: [-0.5, 0.5]
+        self.state_S = paths.CVDefinedVolume(rc, -0.5, 0.5)
+        # define state labels
+        self.state_labels = {
+            "A" : self.state_A,
+            "B" : self.state_B,
+            "S" : self.state_S,
+            "None" :~(self.state_A | self.state_B | self.state_S)}
+        # velocities are not randomized
+        randomizer = paths.NoModification()
+
+        self.filename = data_filename("sshooting_test.nc")
+        self.storage = paths.Storage(self.filename, mode="w")
+        self.storage.save(self.initial_snapshots)
+
+        self.simulation = SShootingSimulation(
+                              storage=self.storage,
+                              engine=self.engine,
+                              state_S=self.state_S,
+                              randomizer=randomizer,
+                              initial_snapshots=self.initial_snapshots,
+                              trajectory_length=self.l)
+        self.simulation.output_stream = open(os.devnull, 'w')
+        self.simulation.run(n_per_snapshot=1)
+        self.analysis = SShootingAnalysis(steps=self.storage.steps,
+                                          states=[self.state_A,
+                                                  self.state_B,
+                                                  self.state_S])
+
+    def teardown_method(self):
+        if os.path.isfile(self.filename):
+            os.remove(self.filename)
+        paths.EngineMover.default_engine = None
+
+    def test_analysis(self):
+        # check wrong analyze_single_step() argument
+        empty_dict = self.analysis.analyze_single_step(3.1415)
+        assert empty_dict == {}
+
+        # dictionary with three entries returned
+        assert len(self.analysis) == 3
+
+        # run C_AB() in advance, this triggers calculate_averages()
+        cab = self.analysis.C_AB()
+        assert len(cab) == self.l+1
+        assert cab.sum() > 0
+
+        # get total and per-snapshot results
+        M, Ns, I_Bt, Ns_Bt, hAhB_Bt, sres = self.analysis.calculate_averages()
+
+        # check total number of generated subtrajectories
+        assert M == 3 * (self.l + 1)
+        # since we did not use a bias Ns_Bt should be 1.0
+        np.testing.assert_almost_equal(Ns_Bt, 1.0)
+        # now do the same tests on a per-snapshot basis
+        snaps = sres.keys()
+        for snap in snaps:
+            assert sres[snap]["M"] == self.l+1
+            np.testing.assert_almost_equal(sres[snap]["Ns_Bt"], 1.0)
+            # check whether time correlation is only filled for case (2), for
+            # case (1) and (3) the array should be empty.
+            if snap.xyz[0][0] == 0.0:
+                np.testing.assert_almost_equal(
+                    np.array(sres[snap]["hAhB_Bt"]).sum(), 0
+                )
+            elif snap.xyz[0][0] == 0.1:
+                assert np.array(sres[snap]["hAhB_Bt"]).sum() > 0
+            elif snap.xyz[0][0] == -0.2:
+                np.testing.assert_almost_equal(
+                    np.array(sres[snap]["hAhB_Bt"]).sum(), 0
+                )
+
+        # run C_AB() afterwards, this triggers another if clause
+        cab = self.analysis.C_AB()
+        assert len(cab) == self.l+1
+        assert cab.sum() > 0
+
+        # try another analysis with different arguments for complete coverage
+        def dummy_bias(x):
+            return 1.0
+        cv_b = paths.CoordinateFunctionCV(name="cv_b", f=dummy_bias)
+        rc = paths.FunctionCV("Id", lambda snap : snap.coordinates[0][0])
+        self.state_S2 = paths.CVDefinedVolume(rc, -0.55, 0.05)
+        self.analysis2 = SShootingAnalysis(steps=self.storage.steps,
+                                           states=[self.state_A,
+                                                   self.state_B,
+                                                   self.state_S2],
+                                           bias=cv_b)
+
+        with pytest.raises(NotImplementedError):
+            self.analysis.committor()
+        with pytest.raises(NotImplementedError):
+            self.analysis.committor_histogram()
diff --git a/openpathsampling/tests/test_sshooting_simulator.py b/openpathsampling/tests/test_sshooting_simulator.py
new file mode 100644
index 000000000..ab334f75b
--- /dev/null
+++ b/openpathsampling/tests/test_sshooting_simulator.py
@@ -0,0 +1,107 @@
+from openpathsampling.tests.test_helpers import (data_filename)
+import openpathsampling as paths
+import openpathsampling.engines.toy as toys
+from openpathsampling.pathsimulators.sshooting_simulator import SShootingSimulation
+import numpy as np
+import os
+
+class TestSShootingSimulation(object):
+    def setup_method(self):
+        # PES is one-dimensional zero function (y(x) = 0)
+        pes = toys.LinearSlope(m=[0.0], c=[0.0])
+        topology = toys.Topology(n_spatial=1, masses=[1.0], pes=pes)
+        integrator = toys.LeapfrogVerletIntegrator(0.02)
+        options = {
+            'integ' : integrator,
+            'n_frames_max' : 1000,
+            'n_steps_per_frame' : 1
+        }
+        self.engine = toys.Engine(options=options, topology=topology)
+        # test uses snapshots with different velocities
+        self.initial_snapshots = [toys.Snapshot(
+                                      coordinates=np.array([[0.0]]),
+                                      velocities=np.array([[1.0]]),
+                                      engine=self.engine),
+                                  toys.Snapshot(
+                                      coordinates=np.array([[0.1]]),
+                                      velocities=np.array([[-1.0]]),
+                                      engine=self.engine),
+                                  toys.Snapshot(
+                                      coordinates=np.array([[-0.2]]),
+                                      velocities=np.array([[2.0]]),
+                                      engine=self.engine)]
+        # trajectory length is set to 100 steps
+        self.l = 100
+        # reaction coordinate is just x coordinate
+        rc = paths.FunctionCV("Id", lambda snap : snap.coordinates[0][0])
+        # state S: [-0.5, 0.5]
+        self.state_S = paths.CVDefinedVolume(rc, -0.5, 0.5)
+        # define state labels
+        self.state_labels = {
+            "S" : self.state_S,
+            "NotS" :~self.state_S}
+        # velocities are not randomized
+        randomizer = paths.NoModification()
+
+        self.filename = data_filename("sshooting_test.nc")
+        self.storage = paths.Storage(self.filename, mode="w")
+        self.storage.save(self.initial_snapshots)
+
+        self.simulation = SShootingSimulation(
+            storage=self.storage,
+            engine=self.engine,
+            state_S=self.state_S,
+            randomizer=randomizer,
+            initial_snapshots=self.initial_snapshots,
+            trajectory_length=self.l
+        )
+        self.simulation.output_stream = open(os.devnull, 'w')
+
+    def teardown_method(self):
+        if os.path.isfile(self.filename):
+            os.remove(self.filename)
+        paths.EngineMover.default_engine = None
+
+    def test_initialization(self):
+        sim = self.simulation
+        assert len(sim.initial_snapshots) == 3
+        assert isinstance(sim.mover, paths.SequentialMover)
+
+    def test_simulation_run(self):
+        self.simulation.run(n_per_snapshot=1)
+        assert len(self.simulation.storage.steps) == 3
+        sim = self.simulation
+
+        steps = sim.storage.steps
+        # Check if trajectory length has not been altered.
+        assert sim.trajectory_length == self.l
+        # Check if state S has not been altered.
+        assert sim.state_S == self.state_S
+        for i in range(len(steps)):
+            step = steps[i]
+            # there should be two trials
+            assert len(step.change.trials) == 2
+            # backward shot (first trial) should have length l+1
+            assert len(step.change.trials[0].trajectory) == self.l+1
+            # total trajectory (second trial = backward + forward shot should
+            # have length 2*l+1
+            assert len(step.change.trials[1].trajectory) == 2*self.l+1
+            # middle point of trajectory should be initial snapshot
+            t = step.change.trials[1].trajectory
+            np.testing.assert_almost_equal(
+                t[self.l].coordinates, self.initial_snapshots[i].coordinates
+            )
+            # all trajectories have intermediate points in S
+            traj_summary = t.summarize_by_volumes(self.state_labels)
+            assert traj_summary[0][0] == 'NotS'
+            assert traj_summary[1][0] == 'S'
+            assert traj_summary[2][0] == 'NotS'
+            # last mover should be forward mover of simulation
+            expected_mover = self.simulation.forward_mover
+            assert step.change.canonical.mover == expected_mover
+
+    def test_serialization_cycle(self):
+        dct = self.simulation.to_dict()
+        obj = SShootingSimulation.from_dict(dct)
+        dct2 = obj.to_dict()
+        assert dct == dct2
diff --git a/openpathsampling/tests/test_storage.py b/openpathsampling/tests/test_storage.py
index 992c5f039..1c1ebbf01 100644
--- a/openpathsampling/tests/test_storage.py
+++ b/openpathsampling/tests/test_storage.py
@@ -26,7 +26,7 @@
 
 
 class TestStorage(object):
-    def setup(self):
+    def setup_method(self):
         if not md:
             raise SkipTest("mdtraj not installed")
         self.mdtraj = md.load(data_filename("ala_small_traj.pdb"))
@@ -55,7 +55,7 @@ def setup(self):
             engine=self.engine
         )
 
-    def teardown(self):
+    def teardown_method(self):
         if os.path.isfile(self.filename):
             os.remove(self.filename)
 
diff --git a/openpathsampling/tests/test_tis_analysis.py b/openpathsampling/tests/test_tis_analysis.py
index e96f36bec..878937cf3 100644
--- a/openpathsampling/tests/test_tis_analysis.py
+++ b/openpathsampling/tests/test_tis_analysis.py
@@ -100,7 +100,7 @@ def sampling_ensembles_for_transition(self, network, state_A, state_B):
         sampling_AB = network.analysis_to_sampling[analysis_AB][0]
         return sampling_AB.ensembles
 
-    def setup(self):
+    def setup_method(self):
         # set up the trajectories, ensembles, etc. for this test
         self.HAS_TQDM = paths.progress.HAS_TQDM
         paths.progress.HAS_TQDM = False  # turn of progress bars
@@ -161,7 +161,7 @@ def setup(self):
         )
 
 
-    def teardown(self):
+    def teardown_method(self):
         paths.progress.HAS_TQDM = self.HAS_TQDM
 
 
@@ -205,8 +205,8 @@ def test_steps_to_weighted_trajectories(self):
 class TestFluxToPandas(TISAnalysisTester):
     # includes tests for default_flux_sort and flux_matrix_pd
     # as a class to simplify setup of flux objects
-    def setup(self):
-        super(TestFluxToPandas, self).setup()
+    def setup_method(self):
+        super(TestFluxToPandas, self).setup_method()
         interfaces_A = self.mstis.from_state[self.state_A].interfaces
         interfaces_B = self.mstis.from_state[self.state_B].interfaces
         pairs_A = list(itertools.product([self.state_A], interfaces_A))
@@ -255,8 +255,8 @@ def test_flux_matrix_pd_unknown_str(self):
 
 
 class TestDictFlux(TISAnalysisTester):
-    def setup(self):
-        super(TestDictFlux, self).setup()
+    def setup_method(self):
+        super(TestDictFlux, self).setup_method()
         self.innermost_interface_A = \
             self.sampling_ensembles_for_transition(self.mistis,
                                                    self.state_A,
@@ -308,8 +308,8 @@ def test_bad_combine_results(self):
 
 
 class TestMinusMoveFlux(TISAnalysisTester):
-    def setup(self):
-        super(TestMinusMoveFlux, self).setup()
+    def setup_method(self):
+        super(TestMinusMoveFlux, self).setup_method()
 
         a = 0.1  # just a number to simplify the trajectory-making
         minus_move_descriptions = [
@@ -674,8 +674,8 @@ def test_missing_ctp(self):
 
 
 class TestTransitionDictResults(TISAnalysisTester):
-    def setup(self):
-        super(TestTransitionDictResults, self).setup()
+    def setup_method(self):
+        super(TestTransitionDictResults, self).setup_method()
         results_dict = {(self.state_A, self.state_B): 1,
                         (self.state_B, self.state_A): 2}
         self.mistis_transition_dict = TransitionDictResults(
@@ -775,8 +775,8 @@ def _make_tis_analysis(self, network):
         )
         return tis_analysis
 
-    def setup(self):
-        super(TestTISAnalysis, self).setup()
+    def setup_method(self):
+        super(TestTISAnalysis, self).setup_method()
         self.mistis_analysis = self._make_tis_analysis(self.mistis)
         self.mistis_analysis.calculate(self.mistis_steps)
         self.mstis_analysis = self._make_tis_analysis(self.mstis)
diff --git a/openpathsampling/tests/test_toy_dynamics.py b/openpathsampling/tests/test_toy_dynamics.py
index f2c5de107..583ef75c9 100644
--- a/openpathsampling/tests/test_toy_dynamics.py
+++ b/openpathsampling/tests/test_toy_dynamics.py
@@ -28,20 +28,22 @@
 
 def setup_module():
     # set up globals
-    global gaussian, linear, outer, harmonic
+    global gaussian, linear, outer, harmonic, doublewell
     gaussian = toy.Gaussian(6.0, [2.5, 40.0], [0.8, 0.5])
     outer = toy.OuterWalls([1.12, 2.0], [0.2, -0.25])
     linear = toy.LinearSlope([1.5, 0.75], 0.5)
     harmonic = toy.HarmonicOscillator([1.5, 2.0], [0.5, 3.0], [0.25, 0.75])
+    doublewell = toy.DoubleWell([5.0, 2.0], [2.0, 3.0])
     global init_pos, init_vel, sys_mass
     init_pos = np.array([0.7, 0.65])
     init_vel = np.array([0.6, 0.5])
     sys_mass = np.array([1.5, 1.5])
 
+
 # === TESTS FOR TOY POTENTIAL ENERGY SURFACES =============================
 
 class TestHarmonicOscillator(object):
-    def setup(self):
+    def setup_method(self):
         self.positions = init_pos
         self.velocities = init_vel
         self.mass = sys_mass
@@ -61,8 +63,9 @@ def test_dVdx(self):
                                         [0.253125, -2.7]):
             assert_almost_equal(experiment, theory)
 
+
 class TestGaussian(object):
-    def setup(self):
+    def setup_method(self):
         self.positions = init_pos
         self.velocities = init_vel
         self.mass = sys_mass
@@ -77,8 +80,9 @@ def test_dVdx(self):
         # -480*((0.65)-0.5)*exp(-2.5*((0.7)-0.8)^2-40*((0.65)-0.5)^2)
         assert_almost_equal(gaussian.dVdx(self)[1], -28.5502621734)
 
+
 class TestOuterWalls(object):
-    def setup(self):
+    def setup_method(self):
         self.positions = init_pos
         self.velocities = init_vel
         self.mass = sys_mass
@@ -93,8 +97,9 @@ def test_dVdx(self):
         # 6*2.0*(0.65-(-0.25))^5 = 7.08588
         assert_almost_equal(outer.dVdx(self)[1], 7.08588)
 
+
 class TestLinearSlope(object):
-    def setup(self):
+    def setup_method(self):
         self.positions = init_pos
         self.velocities = init_vel
         self.mass = sys_mass
@@ -105,8 +110,30 @@ def test_V(self):
     def test_dVdx(self):
         assert_equal(linear.dVdx(self), [1.5, 0.75])
 
+
+class TestDoubleWell(object):
+    def setup_method(self):
+        self.positions = init_pos
+        self.velocities = init_vel
+        self.mass = sys_mass
+
+    def test_V(self):
+        assert_almost_equal(doublewell.V(self), 208.7475125)
+
+    def test_dVdx(self):
+        for (experiment, theory) in zip(doublewell.dVdx(self),
+                                        [-49.14, -44.603]):
+            assert_almost_equal(experiment, theory)
+
+    def test_serialization_cycle(self):
+        dct = doublewell.to_dict()
+        obj = toy.DoubleWell.from_dict(dct)
+        dct2 = obj.to_dict()
+        assert dct == dct2
+
+
 class TestCombinations(object):
-    def setup(self):
+    def setup_method(self):
         self.positions = init_pos
         self.velocities = init_vel
         self.mass = sys_mass
@@ -115,7 +142,7 @@ def setup(self):
 
     def test_V(self):
         assert_almost_equal(self.simpletest.V(self), 2*2.37918851445)
-        assert_almost_equal(self.fullertest.V(self), 
+        assert_almost_equal(self.fullertest.V(self),
                             2.37918851445 + 1.080382 - 2.0375)
 
     def test_dVdx(self):
@@ -133,7 +160,7 @@ def test_kinetic_energy(self):
 # === TESTS FOR TOY ENGINE OBJECT =========================================
 
 class Test_convert_fcn(object):
-    def test_convert_to_3Ndim(v):
+    def test_convert_to_3Ndim(self):
         raise SkipTest
 
         assert_equal_array_array(toy.convert_to_3Ndim([1.0, 2.0]),
@@ -144,13 +171,13 @@ def test_convert_to_3Ndim(v):
                                  np.array([[1.0, 2.0, 3.0], [4.0, 0.0, 0.0]]))
 
 class TestToyEngine(object):
-    def setup(self):
+    def setup_method(self):
         pes = linear
         integ = toy.LeapfrogVerletIntegrator(dt=0.002)
-        topology=toy.Topology(
-            n_spatial = 2,
-            masses = sys_mass,
-            pes = pes
+        topology = toy.Topology(
+            n_spatial=2,
+            masses=sys_mass,
+            pes=pes
         )
         options={
             'integ' : integ,
@@ -169,7 +196,7 @@ def setup(self):
         sim.n_steps_per_frame = 10
         self.sim = sim
 
-    def teardown(self):
+    def teardown_method(self):
         if os.path.isfile('toy_tmp.nc'):
             os.remove('toy_tmp.nc')
 
@@ -189,11 +216,11 @@ def test_snapshot_get(self):
                            self.sim.positions)
 
     def test_snapshot_set(self):
-        snap = toy.Snapshot(coordinates=np.array([[1,2,3]]),
-                        velocities=np.array([[4,5,6]]))
+        snap = toy.Snapshot(coordinates=np.array([[1, 2, 3]]),
+                            velocities=np.array([[4, 5, 6]]))
         self.sim.current_snapshot = snap
-        assert_items_equal(self.sim.positions, [1,2,3])
-        assert_items_equal(self.sim.velocities, [4,5,6])
+        assert_items_equal(self.sim.positions, [1, 2, 3])
+        assert_items_equal(self.sim.velocities, [4, 5, 6])
 
     def test_generate_next_frame(self):
         # we test correctness by integrating forward, then backward
@@ -218,7 +245,7 @@ def test_generate(self):
 
     def test_generate_n_frames(self):
         self.sim.initialized = True
-        ens = paths.LengthEnsemble(4) # first snap plus n_frames
+        ens = paths.LengthEnsemble(4)  # first snap plus n_frames
         orig = self.sim.current_snapshot.copy()
         traj1 = self.sim.generate(self.sim.current_snapshot, [ens.can_append])
         self.sim.current_snapshot = orig
@@ -226,7 +253,7 @@ def test_generate_n_frames(self):
         assert_equal(len(traj1), len(traj2))
         for (s1, s2) in zip(traj1, traj2):
             # snapshots are not the same object
-            assert_not_equal(s1, s2) 
+            assert_not_equal(s1, s2)
             # however, they have the same values stored in them
             assert_equal(len(s1.coordinates), 1)
             assert_equal(len(s1.coordinates[0]), 2)
@@ -234,8 +261,8 @@ def test_generate_n_frames(self):
             assert_items_equal(s1.velocities[0], s2.velocities[0])
 
     def test_start_with_snapshot(self):
-        snap = toy.Snapshot(coordinates=np.array([1,2]),
-                        velocities=np.array([3,4]))
+        snap = toy.Snapshot(coordinates=np.array([1, 2]),
+                            velocities=np.array([3, 4]))
         self.sim.start(snapshot=snap)
         self.sim.stop([snap])
 
@@ -246,20 +273,20 @@ def test_has_constraints(self):
 # === TESTS FOR TOY INTEGRATORS ===========================================
 
 class TestLeapfrogVerletIntegrator(object):
-    def setup(self):
+    def setup_method(self):
         pes = linear
         integ = toy.LeapfrogVerletIntegrator(dt=0.002)
-        topology=toy.Topology(
-            n_spatial = 2,
-            masses = sys_mass,
-            pes = pes
+        topology = toy.Topology(
+            n_spatial=2,
+            masses=sys_mass,
+            pes=pes
         )
-        options={
-            'integ' : integ,
-            'n_frames_max' : 5}
+        options = {
+            'integ': integ,
+            'n_frames_max': 5
+        }
         sim = toy.Engine(options=options,
-                        topology=topology
-                       )
+                         topology=topology)
 
         template = toy.Snapshot(
             coordinates=init_pos.copy(),
@@ -275,7 +302,7 @@ def setup(self):
 
     def test_momentum_update(self):
         self.sim.integ._momentum_update(self.sim, 0.002)
-        # velocities = init_vel - pes.dVdx(init_pos)/m*dt 
+        # velocities = init_vel - pes.dVdx(init_pos)/m*dt
         #            = [0.6, 0.5] - [1.5, 0.75]/[1.5, 1.5] * 0.002
         #            = [0.598, 0.499]
         assert_equal(self.sim.velocities[0], 0.598)
@@ -295,26 +322,25 @@ def test_step(self):
         self.sim.integ.step(self.sim)
 
 
-
 class TestLangevinBAOABIntegrator(object):
     '''Testing for correctness is hard, since this is a stochastic
     calculation. However, we can at least run tests to make sure nothing
     crashes.'''
-    def setup(self):
+    def setup_method(self):
         pes = linear
         integ = toy.LangevinBAOABIntegrator(dt=0.002, temperature=0.5,
-                                        gamma=1.0)
-        topology=toy.Topology(
-            n_spatial = 2,
-            masses = sys_mass,
-            pes = pes
+                                            gamma=1.0)
+        topology = toy.Topology(
+            n_spatial=2,
+            masses=sys_mass,
+            pes=pes
         )
-        options={
-            'integ' : integ,
-            'n_frames_max' : 5}
+        options = {
+            'integ': integ,
+            'n_frames_max': 5
+        }
         sim = toy.Engine(options=options,
-                        topology=topology
-                       )
+                         topology=topology)
 
         template = toy.Snapshot(
             coordinates=init_pos.copy(),
@@ -342,3 +368,44 @@ def test_OU_update(self):
 
     def test_step(self):
         self.sim.generate_next_frame()
+
+
+class TestOverdampedLangevinIntegrator(object):
+    '''This is only a test if the integrator runs. Because of its stochastic
+    nature we can not actually test its correctness easily.'''
+    def setup_method(self):
+        pes = linear
+        integ = toy.OverdampedLangevinIntegrator(dt=0.001, temperature=4.0,
+                                                 D=1.0)
+        topology = toy.Topology(
+            n_spatial=2,
+            masses=sys_mass,
+            pes=pes
+        )
+        options = {
+            'integ': integ,
+            'n_frames_max': 5
+        }
+        sim = toy.Engine(options=options, topology=topology)
+
+        template = toy.Snapshot(
+            coordinates=init_pos.copy(),
+            velocities=init_vel.copy(),
+            engine=sim
+        )
+
+        sim.positions = init_pos.copy()
+        sim.velocities = init_vel.copy()
+
+        sim.n_steps_per_frame = 10
+        self.sim = sim
+
+    def test_step(self):
+        self.sim.generate_next_frame()
+
+    def test_serialization_cycle(self):
+        integ = self.sim.options['integ']
+        dct = integ.to_dict()
+        obj = toy.OverdampedLangevinIntegrator.from_dict(dct)
+        dct2 = obj.to_dict()
+        assert dct == dct2
diff --git a/openpathsampling/tests/test_toy_features.py b/openpathsampling/tests/test_toy_features.py
index c9ca1ae47..7f952dda6 100644
--- a/openpathsampling/tests/test_toy_features.py
+++ b/openpathsampling/tests/test_toy_features.py
@@ -12,7 +12,7 @@
 logging.getLogger('openpathsampling.netcdfplus').setLevel(logging.CRITICAL)
 
 class TestToySnapshotFeatures(object):
-    def setup(self):
+    def setup_method(self):
         integ = toys.LangevinBAOABIntegrator(dt=0.002,
                                              temperature=1.0,
                                              gamma=2.5)
diff --git a/openpathsampling/tests/test_trajectory.py b/openpathsampling/tests/test_trajectory.py
index 73c69a1aa..5c32c527b 100644
--- a/openpathsampling/tests/test_trajectory.py
+++ b/openpathsampling/tests/test_trajectory.py
@@ -18,7 +18,7 @@
 logging.getLogger('openpathsampling.storage').setLevel(logging.CRITICAL)
 
 class TestSummarizeTrajectoryVolumes(object):
-    def setup(self):
+    def setup_method(self):
         op = paths.FunctionCV("Id", lambda snap : snap.coordinates[0][0])
         vol1 = paths.CVDefinedVolume(op, 0.1, 0.5)
         vol2 = paths.CVDefinedVolume(op, -0.1, 0.7)
@@ -55,7 +55,7 @@ def test_summarize_trajectory_volumes_str(self):
             "A-B-I-X"
         )
         assert_items_equal(
-            self._make_traj("abix").summarize_by_volumes_str(voldict, " blah "), 
+            self._make_traj("abix").summarize_by_volumes_str(voldict, " blah "),
             "A blah B blah I blah X"
         )
 
@@ -84,7 +84,7 @@ def test_summarize_trajectory_volumes_with_nonevol(self):
         )
 
 class TestSubtrajectoryIndices(object):
-    def setup(self):
+    def setup_method(self):
         op = paths.FunctionCV("Id", lambda snap : snap.coordinates[0][0])
         vol1 = paths.CVDefinedVolume(op, 0.1, 0.5)
         vol2 = paths.CVDefinedVolume(op, -0.1, 0.7)
diff --git a/openpathsampling/tests/test_trajectory_transition_analysis.py b/openpathsampling/tests/test_trajectory_transition_analysis.py
index f542af624..b573f1140 100644
--- a/openpathsampling/tests/test_trajectory_transition_analysis.py
+++ b/openpathsampling/tests/test_trajectory_transition_analysis.py
@@ -20,7 +20,7 @@
 logging.getLogger('openpathsampling.netcdfplus').setLevel(logging.CRITICAL)
 
 class TestTrajectorySegmentContainer(object):
-    def setup(self):
+    def setup_method(self):
         op = paths.FunctionCV("Id", lambda snap : snap.coordinates[0][0])
         self.vol1 = paths.CVDefinedVolume(op, 0.1, 0.5)
         self.vol3 = paths.CVDefinedVolume(op, 2.0, 2.5)
@@ -103,7 +103,7 @@ def test_iadd(self):
 
 
 class TestTrajectoryTransitionAnalysis(object):
-    def setup(self):
+    def setup_method(self):
         op = paths.FunctionCV("Id", lambda snap : snap.coordinates[0][0])
         vol1 = paths.CVDefinedVolume(op, 0.1, 0.5)
         vol2 = paths.CVDefinedVolume(op, -0.1, 0.7)
@@ -139,7 +139,7 @@ def _make_traj(self, traj_str):
                             velocities=[1.0]*len(sequence))
 
     def test_analyze_continuous_time(self):
-        resultA = self.analyzer.analyze_continuous_time(self.trajectory, 
+        resultA = self.analyzer.analyze_continuous_time(self.trajectory,
                                                         self.stateA)
         assert_equal(resultA.n_frames.tolist(), [3, 1, 1, 1, 1, 1, 1])
         resultB = self.analyzer.analyze_continuous_time(self.trajectory,
diff --git a/openpathsampling/tests/test_transitions.py b/openpathsampling/tests/test_transitions.py
index b4d5ffff1..eed653d85 100644
--- a/openpathsampling/tests/test_transitions.py
+++ b/openpathsampling/tests/test_transitions.py
@@ -18,7 +18,7 @@
 logging.getLogger('openpathsampling.netcdfplus').setLevel(logging.CRITICAL)
 
 class TestTISTransition(object):
-    def setup(self):
+    def setup_method(self):
         pass
 
     def test_initialization(self):
@@ -28,7 +28,7 @@ def test_ensemble_statistics(self):
         pass
 
 class TestFixedLengthTPSTransition(object):
-    def setup(self):
+    def setup_method(self):
         op = paths.FunctionCV("Id", lambda snap : snap.coordinates[0][0])
         self.stateA = paths.CVDefinedVolume(op, 0.1, 0.5)
         self.stateB = paths.CVDefinedVolume(op, 2.0, 2.5)
@@ -75,7 +75,7 @@ def test_storage(self):
             os.remove(filename)
 
 class TestMinusSidesSummary(object):
-    def setup(self):
+    def setup_method(self):
         op = paths.FunctionCV("Id", lambda snap : snap.coordinates[0][0])
         vol1 = paths.CVDefinedVolume(op, 0.1, 0.5)
         vol2 = paths.CVDefinedVolume(op, -0.1, 0.7)
@@ -110,18 +110,18 @@ def test_normal_minus(self):
                      {"in" : [5], "out" : [4]})
         assert_not_equal(minus_sides_summary(self.traj_axaxa, minus),
                          {"in" : [5], "out" : [3]})
-        assert_equal(minus_sides_summary(self.traj_aixiaixia, minus), 
+        assert_equal(minus_sides_summary(self.traj_aixiaixia, minus),
                      {"in" : [5], "out" : [6] })
-        assert_equal(minus_sides_summary(self.traj_aixixiaxia, minus), 
+        assert_equal(minus_sides_summary(self.traj_aixixiaxia, minus),
                      {"in" : [5], "out" : [10] })
 
     def test_minus_with_interstitial(self):
         minus = paths.MinusInterfaceEnsemble(self.stateA, self.innermost)
         assert_equal(minus_sides_summary(self.traj_axaxa, minus),
                      {"in" : [5], "out" : [4]})
-        assert_equal(minus_sides_summary(self.traj_aixiaixia, minus), 
+        assert_equal(minus_sides_summary(self.traj_aixiaixia, minus),
                      {"in" : [6], "out" : [4] })
-        assert_equal(minus_sides_summary(self.traj_aixixiaxia, minus), 
+        assert_equal(minus_sides_summary(self.traj_aixixiaxia, minus),
                      {"in" : [5], "out" : [8] })
 
     def test_minus_with_multiple_excursion(self):
diff --git a/openpathsampling/tests/test_visit_all_states.py b/openpathsampling/tests/test_visit_all_states.py
index 678d35e2c..6adcd09dc 100644
--- a/openpathsampling/tests/test_visit_all_states.py
+++ b/openpathsampling/tests/test_visit_all_states.py
@@ -56,7 +56,7 @@ def test_extract_info_from_default_report():
 
 
 class TestVisitAllStatesEnsemble(object):
-    def setup(self):
+    def setup_method(self):
         self.cv = paths.FunctionCV("x", lambda x: x.xyz[0][0])
         vol_A = paths.CVDefinedVolume(self.cv, 0.0, 1.0).named("A")
         vol_B = paths.CVDefinedVolume(self.cv, 2.0, 3.0).named("B")
diff --git a/openpathsampling/tests/test_volume.py b/openpathsampling/tests/test_volume.py
index bc7b47055..1fc687796 100644
--- a/openpathsampling/tests/test_volume.py
+++ b/openpathsampling/tests/test_volume.py
@@ -4,13 +4,9 @@
 from __future__ import absolute_import
 
 from builtins import object
-from nose.tools import (assert_equal, assert_not_equal, assert_is, raises,
-                        assert_true, assert_false)
-from nose.plugins.skip import Skip, SkipTest
 from .test_helpers import (CallIdentity, raises_with_message_like,
                            make_1d_traj)
 
-import unittest
 import pytest
 import numpy as np
 
@@ -20,10 +16,12 @@
 
 import openpathsampling as paths
 
+
 class Identity2(CallIdentity):
     def __str__(self):
         return "Id2"
 
+
 def setup_module():
     global op_id, volA, volB, volC, volD, volA2
     op_id = CallIdentity()
@@ -39,28 +37,28 @@ def test_empty_volume(self):
         """Empty volume is well-behaved"""
         empty = volume.EmptyVolume()
         test = 0.1
-        assert_equal(empty(test), False)
-        assert_equal((empty & volA)(test), False)
-        assert_equal((volA & empty)(test), False)
-        assert_equal((empty | volA)(test), True)
-        assert_equal((volA | empty)(test), True)
-        assert_equal((empty & volA).__str__(), "empty")
+        assert empty(test) is False
+        assert (empty & volA)(test) is False
+        assert (volA & empty)(test) is False
+        assert (empty | volA)(test) is True
+        assert (volA | empty)(test) is True
+        assert (empty & volA).__str__() == "empty"
         # assert_is: logical combos with empty should return same obj
-        assert_is((empty - volA), empty)
-        assert_is((volA - empty), volA)
-        assert_is((volA | empty), volA)
-        assert_is((empty | volA), volA)
-        assert_is((volA & empty), empty)
-        assert_is((empty & volA), empty)
-        assert_is((empty ^ volA), volA)
-        assert_is((volA ^ empty), volA)
-        assert_equal((~ empty).__str__(), "all")
+        assert (empty - volA) is empty
+        assert (volA - empty) is volA
+        assert (volA | empty) is volA
+        assert (empty | volA) is volA
+        assert (volA & empty) is empty
+        assert (empty & volA) is empty
+        assert (empty ^ volA) is volA
+        assert (volA ^ empty) is volA
+        assert (~ empty).__str__() == "all"
 
     def test_empty_volume_equality(self):
         empty1 = volume.EmptyVolume()
         empty2 = volume.EmptyVolume()
-        assert_true(empty1 == empty2)
-        assert_false(empty1 != empty2)
+        assert empty1 == empty2
+        assert not (empty1 != empty2)
 
 
 class TestFullVolume(object):
@@ -68,122 +66,122 @@ def test_full_volume(self):
         """Full volume is well-behaved"""
         full = volume.FullVolume()
         test = 0.1
-        assert_equal(full(test), True)
-        assert_equal((full & volA)(test), True)
-        assert_equal((volA & full)(test), True)
-        assert_equal((full | volA)(test), True)
-        assert_equal((volA | full)(test), True)
+        assert full(test) is True
+        assert (full & volA)(test) is True
+        assert (volA & full)(test) is True
+        assert (full | volA)(test) is True
+        assert (volA | full)(test) is True
         # assert_is: logical combos with full should return same obj
-        assert_is((full & volA), volA)
-        assert_is((volA & full), volA)
-        assert_is((full | volA), full)
-        assert_is((volA | full), full)
-        assert_equal((volA - full), volume.EmptyVolume())
-        assert_equal((full - volA), ~ volA)
-        assert_equal((full ^ volA), ~ volA)
-        assert_equal((volA ^ full), ~ volA)
-        assert_equal((volA | full).__str__(), "all")
-        assert_equal((~ full).__str__(), "empty")
+        assert (full & volA) is volA
+        assert (volA & full) is volA
+        assert (full | volA) is full
+        assert (volA | full) is full
+        assert (volA - full) == volume.EmptyVolume()
+        assert (full - volA) == ~ volA
+        assert (full ^ volA) == ~ volA
+        assert (volA ^ full) == ~ volA
+        assert (volA | full).__str__() == "all"
+        assert (~ full).__str__() == "empty"
+
 
 class TestCVDefinedVolume(object):
     def test_upper_boundary(self):
-        assert_equal(volA(0.49), True)
-        assert_equal(volA(0.50), False)
-        assert_equal(volA(0.51), False)
+        assert volA(0.49) is True
+        assert volA(0.50) is False
+        assert volA(0.51) is False
+
+    def test_inf_upper_boundary(self):
+        volX = volume.CVDefinedVolume(op_id, 0, float('inf'))
+        assert volA(float('inf')) is False
+        assert volX(float('inf')) is True
 
     def test_lower_boundary(self):
-        assert_equal(volA(-0.49), True)
-        assert_equal(volA(-0.50), True)
-        assert_equal(volA(-0.51), False)
+        assert volA(-0.49) is True
+        assert volA(-0.50) is True
+        assert volA(-0.51) is False
+
+    def test_inf_lower_boundary(self):
+        volX = volume.CVDefinedVolume(op_id, float('-inf'), 0)
+        assert volA(float('-inf')) is False
+        assert volX(float('-inf')) is True
 
     def test_negation(self):
-        assert_equal((~volA)(0.25), False)
-        assert_equal((~volA)(0.75), True)
-        assert_equal((~volA)(0.5), True)
-        assert_equal((~volA)(-0.5), False)
+        assert (~volA)(0.25) is False
+        assert (~volA)(0.75) is True
+        assert (~volA)(0.5) is True
+        assert (~volA)(-0.5) is False
 
     def test_autocombinations(self):
         # volA tests this in the CVRangeVolumes
-        assert_equal(volA | volA, volA)
-        assert_equal(volA & volA, volA)
-        assert_equal(volA ^ volA, volume.EmptyVolume())
-        assert_equal(volA - volA, volume.EmptyVolume())
+        assert volA | volA is volA
+        assert volA & volA is volA
+        assert volA ^ volA == volume.EmptyVolume()
+        assert volA - volA == volume.EmptyVolume()
         # combo tests this with VolumeCombination of CVRangeVolumes
         combo = (volD - volA)
-        assert_is(combo | combo, combo)
-        assert_is(combo & combo, combo)
-        assert_equal(combo ^ combo, volume.EmptyVolume())
-        assert_equal(combo - combo, volume.EmptyVolume())
-
+        assert combo | combo is combo
+        assert combo & combo is combo
+        assert combo ^ combo == volume.EmptyVolume()
+        assert combo - combo == volume.EmptyVolume()
 
     def test_and_combinations(self):
-        assert_equal((volA & volB), volume.CVDefinedVolume(op_id, 0.25, 0.5))
-        assert_equal((volA & volB)(0.45), True)
-        assert_equal((volA & volB)(0.55), False)
-        assert_equal((volB & volC), volume.EmptyVolume())
+        assert (volA & volB) == volume.CVDefinedVolume(op_id, 0.25, 0.5)
+        assert (volA & volB)(0.45) is True
+        assert (volA & volB)(0.55) is False
+        assert (volB & volC) == volume.EmptyVolume()
         # go to VolumeCombination if order parameter isn't the same
-        assert_equal((volA & volA2),
-                     volume.VolumeCombination(volA, volA2,
-                                              lambda a, b: a and b,
-                                              '{0} and {1}')
-                    )
+        assert (volA & volA2) == volume.VolumeCombination(volA, volA2,
+                                                          lambda a, b: a and b,
+                                                          '{0} and {1}')
 
     def test_or_combinations(self):
-        assert_equal((volA | volB), volume.CVDefinedVolume(op_id, -0.5, 0.75))
-        assert_equal((volB | volC), volume.UnionVolume(volB, volC))
-        assert_equal((volB | volC)(0.0), False)
-        assert_equal((volB | volC)(0.5), True)
-        assert_equal((volB | volC)(-0.5), True)
+        assert (volA | volB) == volume.CVDefinedVolume(op_id, -0.5, 0.75)
+        assert (volB | volC) == volume.UnionVolume(volB, volC)
+        assert (volB | volC)(0.0) is False
+        assert (volB | volC)(0.5) is True
+        assert (volB | volC)(-0.5) is True
 
         # go to VolumeCombination if order parameters isn't the same
-        assert_equal((volA2 | volB),
-                     volume.UnionVolume(volA2, volB))
+        assert (volA2 | volB) == volume.UnionVolume(volA2, volB)
 
     def test_xor_combinations(self):
-        assert_equal((volA ^ volB),
-                     volume.UnionVolume(
-                         volume.CVDefinedVolume(op_id, -0.5, 0.25),
-                         volume.CVDefinedVolume(op_id, 0.5, 0.75)
-                     ))
-        assert_equal((volA ^ volA2),
-                     volume.SymmetricDifferenceVolume(volA, volA2))
+        assert ((volA ^ volB) ==
+                volume.UnionVolume(volume.CVDefinedVolume(op_id, -0.5, 0.25),
+                                   volume.CVDefinedVolume(op_id, 0.5, 0.75)))
+        assert (volA ^ volA2) == volume.SymmetricDifferenceVolume(volA, volA2)
 
     def test_sub_combinations(self):
-        assert_equal((volA - volB), volume.CVDefinedVolume(op_id, -0.5, 0.25))
-        assert_equal((volB - volC), volB)
-        assert_equal((volA - volD), volume.EmptyVolume())
-        assert_equal((volB - volA), volume.CVDefinedVolume(op_id, 0.5, 0.75))
-        assert_equal((volD - volA),
-                     volume.UnionVolume(
-                         volume.CVDefinedVolume(op_id, -0.75, -0.5),
-                         volume.CVDefinedVolume(op_id, 0.5, 0.75)
-                     )
-                    )
-        assert_equal((volA2 - volA),
-                     volume.RelativeComplementVolume(volA2, volA))
+        assert (volA - volB) == volume.CVDefinedVolume(op_id, -0.5, 0.25)
+        assert (volB - volC) == volB
+        assert (volA - volD) == volume.EmptyVolume()
+        assert (volB - volA) == volume.CVDefinedVolume(op_id, 0.5, 0.75)
+        assert ((volD - volA) ==
+                volume.UnionVolume(volume.CVDefinedVolume(op_id, -0.75, -0.5),
+                                   volume.CVDefinedVolume(op_id, 0.5, 0.75)))
+        assert (volA2 - volA) == volume.RelativeComplementVolume(volA2, volA)
 
     def test_str(self):
-        assert_equal(volA.__str__(), "{x|Id(x) in [-0.5, 0.5]}")
-        assert_equal((~volA).__str__(), "(not {x|Id(x) in [-0.5, 0.5]})")
+        assert volA.__str__() == "{x|Id(x) in [-0.5, 0.5]}"
+        assert (~volA).__str__() == "(not {x|Id(x) in [-0.5, 0.5]})"
 
+    @pytest.mark.skipif(not paths.integration_tools.HAS_SIMTK_UNIT,
+                        reason="Needs openmm.unit")
     def test_unit_support(self):
-        if not paths.integration_tools.HAS_SIMTK_UNIT:
-            raise SkipTest
         u = unit
 
         vol = volume.CVDefinedVolume(
             op_id, -0.5 * u.nanometers, 0.25 * u.nanometers)
 
-        assert(vol(-0.25 * u.nanometers))
-        assert(not vol(-0.75 * u.nanometers))
+        assert vol(-0.25 * u.nanometers)
+        assert not vol(-0.75 * u.nanometers)
 
         vol = volume.PeriodicCVDefinedVolume(
             op_id,
             -30 * u.nanometers, 90 * u.nanometers,
             -180 * u.nanometers, 180 * u.nanometers)
 
-        assert (vol(50 * u.nanometers))
-        assert (not vol(-70 * u.nanometers))
+        assert vol(50 * u.nanometers)
+        assert not vol(-70 * u.nanometers)
 
     @staticmethod
     def _vol_for_cv_type(inp):
@@ -228,7 +226,7 @@ def test_get_cv_float(self, inp):
 
 
 class TestCVRangeVolumePeriodic(object):
-    def setup(self):
+    def setup_method(self):
         self.pvolA = volume.PeriodicCVDefinedVolume(op_id, -100, 75)
         self.pvolA_ = volume.PeriodicCVDefinedVolume(op_id, 75, -100)
         self.pvolB = volume.PeriodicCVDefinedVolume(op_id, 50, 100)
@@ -240,175 +238,173 @@ def test_normal(self):
         """min<max and both within periodic domain"""
         lambda_min = -150
         lambda_max = 70
-        vol = volume.PeriodicCVDefinedVolume(op_id,
-                                          lambda_min, lambda_max, -180,180)
-        assert_equal(vol._period_len, 360)
-        assert_equal(vol._period_shift, -180)
-        assert_equal(vol.__str__(),
-                     "{x|(Id(x) - -180) % 360 + -180 in [-150, 70]}")
+        vol = volume.PeriodicCVDefinedVolume(op_id, lambda_min, lambda_max,
+                                             -180, 180)
+        assert vol._period_len == 360
+        assert vol._period_shift == -180
+        assert (vol.__str__() ==
+                "{x|(Id(x) - -180) % 360 + -180 in [-150, 70]}")
         for periodic_image in [-1, 0, 1]:
             # out of state
-            assert_equal(False, vol(lambda_min-1.0 + 360*periodic_image))
-            assert_equal(False, vol(lambda_max+1.0 + 360*periodic_image))
+            assert vol(lambda_min-1.0 + 360*periodic_image) is False
+            assert vol(lambda_max+1.0 + 360*periodic_image) is False
             # in state
-            assert_equal(True, vol(lambda_min+1.0 + 360*periodic_image))
-            assert_equal(True, vol(lambda_max-1.0 + 360*periodic_image))
+            assert vol(lambda_min+1.0 + 360*periodic_image) is True
+            assert vol(lambda_max-1.0 + 360*periodic_image) is True
             # border
-            assert_equal(True, vol(lambda_min + 360*periodic_image))
-            assert_equal(False, vol(lambda_max + 360*periodic_image))
+            assert vol(lambda_min + 360*periodic_image) is True
+            assert vol(lambda_max + 360*periodic_image) is False
 
     def test_normal_implicit(self):
         """min<max, no periodic domain defined"""
         lambda_min = -150
         lambda_max = 70
         vol = volume.PeriodicCVDefinedVolume(op_id,
-                                          lambda_min, lambda_max)
-        assert_equal(vol.__str__(),
-            "{x|Id(x) [periodic] in [-150, 70]}")
+                                             lambda_min, lambda_max)
+        assert vol.__str__() == "{x|Id(x) [periodic] in [-150, 70]}"
         # out of state
-        assert_equal(False, vol(lambda_min-1.0))
-        assert_equal(False, vol(lambda_max+1.0))
+        assert vol(lambda_min-1.0) is False
+        assert vol(lambda_max+1.0) is False
         # in state
-        assert_equal(True, vol(lambda_min+1.0))
-        assert_equal(True, vol(lambda_max-1.0))
+        assert vol(lambda_min+1.0) is True
+        assert vol(lambda_max-1.0) is True
         # border
-        assert_equal(True, vol(lambda_min))
-        assert_equal(False, vol(lambda_max))
+        assert vol(lambda_min) is True
+        assert vol(lambda_max) is False
 
     def test_wrapped_volume(self):
         """max<min and both within periodic domain"""
         lambda_min = 70
         lambda_max = -150
         vol = volume.PeriodicCVDefinedVolume(op_id,
-                                          lambda_min, lambda_max, -180,180)
-        assert_equal(vol.__str__(),
-            "{x|(Id(x) - -180) % 360 + -180 in [-180, -150] union [70, 180]}")
+                                             lambda_min, lambda_max, -180, 180)
+        assert vol.__str__() == ("{x|(Id(x) - -180) % 360 + -180 "
+                                 "in [-180, -150] union [70, 180]}")
         for periodic_image in [-1, 0, 1]:
             # out of state
-            assert_equal(False, vol(lambda_min-1.0 + 360*periodic_image))
-            assert_equal(False, vol(lambda_max+1.0 + 360*periodic_image))
+            assert vol(lambda_min-1.0 + 360*periodic_image) is False
+            assert vol(lambda_max+1.0 + 360*periodic_image) is False
             # in state
-            assert_equal(True, vol(lambda_min+1.0 + 360*periodic_image))
-            assert_equal(True, vol(lambda_max-1.0 + 360*periodic_image))
+            assert vol(lambda_min+1.0 + 360*periodic_image) is True
+            assert vol(lambda_max-1.0 + 360*periodic_image) is True
             # border
-            assert_equal(True, vol(lambda_min + 360*periodic_image))
-            assert_equal(False, vol(lambda_max + 360*periodic_image))
+            assert vol(lambda_min + 360*periodic_image) is True
+            assert vol(lambda_max + 360*periodic_image) is False
 
     def test_wrapped_volume_implicit(self):
         """max<min, no periodic domain defined"""
         lambda_min = 70
         lambda_max = -150
-        vol = volume.PeriodicCVDefinedVolume(op_id,
-                                          lambda_min, lambda_max)
+        vol = volume.PeriodicCVDefinedVolume(op_id, lambda_min, lambda_max)
         # out of state
-        assert_equal(False, vol(lambda_min-1.0))
-        assert_equal(False, vol(lambda_max+1.0))
+        assert vol(lambda_min-1.0) is False
+        assert vol(lambda_max+1.0) is False
         # in state
-        assert_equal(True, vol(lambda_min+1.0))
-        assert_equal(True, vol(lambda_max-1.0))
+        assert vol(lambda_min+1.0) is True
+        assert vol(lambda_max-1.0) is True
         # border
-        assert_equal(True, vol(lambda_min))
-        assert_equal(False, vol(lambda_max))
+        assert vol(lambda_min) is True
+        assert vol(lambda_max) is False
 
     def test_thru_pbc_to_image(self):
         '''max in next periodic domain'''
         lambda_min = 70
         lambda_max = 210
-        vol = volume.PeriodicCVDefinedVolume(op_id,
-                                          lambda_min, lambda_max, -180,180)
-        assert_equal(vol.lambda_max, -150)
+        vol = volume.PeriodicCVDefinedVolume(op_id, lambda_min, lambda_max,
+                                             -180, 180)
+        assert vol.lambda_max == -150
         # assuming that's true, so is everything else
 
-    @raises(Exception)
     def test_volume_bigger_than_bounds(self):
         '''max-min > pbc_range raises Exception'''
-        vol = volume.PeriodicCVDefinedVolume(op_id, 90, 720, -180, 180)
+        with pytest.raises(Exception, match='Range of volume'):
+            volume.PeriodicCVDefinedVolume(op_id, 90, 720, -180, 180)
 
     def test_volume_equals_bounds(self):
         '''max-min == pbc_range allows all points'''
         vol = volume.PeriodicCVDefinedVolume(op_id, 0, 360, -180, 180)
-        assert_equal(vol.__str__(),
-                     "{x|(Id(x) - -180) % 360 + -180 in [-180, 180]}")
-        assert_equal(True, vol(0))
-        assert_equal(True, vol(360))
-        assert_equal(True, vol(-180))
-        assert_equal(True, vol(180))
+        assert vol.__str__() == ("{x|(Id(x) - -180) % 360 + -180 "
+                                 "in [-180, 180]}")
+        assert vol(0) is True
+        assert vol(360) is True
+        assert vol(-180) is True
+        assert vol(180) is True
 
     def test_periodic_and_combos(self):
-        assert_equal((self.pvolA & self.pvolB),
-                     volume.PeriodicCVDefinedVolume(op_id, 50, 75))
-        assert_equal((self.pvolA & self.pvolB)(60), True)
-        assert_equal((self.pvolA & self.pvolB)(80), False)
-        assert_equal((self.pvolB & self.pvolC), volume.EmptyVolume())
-        assert_equal((self.pvolC & self.pvolB), volume.EmptyVolume())
-        assert_is((self.pvolA & self.pvolA), self.pvolA)
-        assert_equal((self.pvolA & self.pvolA_), volume.EmptyVolume())
-        assert_equal((self.pvolE & self.pvolD), self.pvolD)
+        assert ((self.pvolA & self.pvolB) ==
+                volume.PeriodicCVDefinedVolume(op_id, 50, 75))
+        assert (self.pvolA & self.pvolB)(60) is True
+        assert (self.pvolA & self.pvolB)(80) is False
+        assert (self.pvolB & self.pvolC) == volume.EmptyVolume()
+        assert (self.pvolC & self.pvolB) == volume.EmptyVolume()
+        assert (self.pvolA & self.pvolA) is self.pvolA
+        assert (self.pvolA & self.pvolA_) == volume.EmptyVolume()
+        assert (self.pvolE & self.pvolD) == self.pvolD
         # go to special case for cyclic permutation
-        assert_equal((self.pvolB & self.pvolD), self.pvolB)
+        assert (self.pvolB & self.pvolD) == self.pvolB
         # go to special case
-        assert_equal((self.pvolE & self.pvolA_),
-                     volume.UnionVolume(
-                         volume.PeriodicCVDefinedVolume(op_id, -150,-100),
-                         volume.PeriodicCVDefinedVolume(op_id, 75, 150)
-                     )
+        assert ((self.pvolE & self.pvolA_) ==
+                volume.UnionVolume(
+                    volume.PeriodicCVDefinedVolume(op_id, -150, -100),
+                    volume.PeriodicCVDefinedVolume(op_id, 75, 150)
                     )
+                )
         # go to super if needed
-        assert_equal(type(self.pvolA & volA), volume.IntersectionVolume)
+        assert type(self.pvolA & volA) is volume.IntersectionVolume
 
     def test_periodic_or_combos(self):
-        assert_equal((self.pvolA | self.pvolB), self.pvolD)
-        assert_equal((self.pvolA | self.pvolB)(60), True)
-        assert_equal((self.pvolA | self.pvolB)(80), True)
-        assert_equal((self.pvolA | self.pvolB)(125), False)
-        assert_equal((self.pvolB | self.pvolC),
-                     volume.UnionVolume(self.pvolB, self.pvolC))
-        assert_equal((self.pvolC | self.pvolB), 
-                     volume.UnionVolume(self.pvolC, self.pvolB))
-        assert_is((self.pvolA | self.pvolA), self.pvolA)
-        assert_equal((self.pvolA | self.pvolA_), volume.FullVolume())
-        assert_equal((self.pvolE | self.pvolD), self.pvolE)
-        assert_equal((self.pvolB | self.pvolD), self.pvolD)
-        assert_equal((self.pvolE | self.pvolA_), volume.FullVolume())
+        assert (self.pvolA | self.pvolB) == self.pvolD
+        assert (self.pvolA | self.pvolB)(60) is True
+        assert (self.pvolA | self.pvolB)(80) is True
+        assert (self.pvolA | self.pvolB)(125) is False
+        assert ((self.pvolB | self.pvolC) ==
+                volume.UnionVolume(self.pvolB, self.pvolC))
+        assert ((self.pvolC | self.pvolB) ==
+                volume.UnionVolume(self.pvolC, self.pvolB))
+        assert (self.pvolA | self.pvolA) is self.pvolA
+        assert (self.pvolA | self.pvolA_) == volume.FullVolume()
+        assert (self.pvolE | self.pvolD) == self.pvolE
+        assert (self.pvolB | self.pvolD) == self.pvolD
+        assert (self.pvolE | self.pvolA_) == volume.FullVolume()
 
     def test_periodic_xor_combos(self):
-        assert_equal(self.pvolA ^ self.pvolA_, volume.FullVolume())
-        assert_equal(self.pvolA ^ self.pvolA, volume.EmptyVolume())
-        assert_equal(self.pvolE ^ self.pvolD,
-                     volume.UnionVolume(
-                         volume.PeriodicCVDefinedVolume(op_id, -150, -100),
-                         volume.PeriodicCVDefinedVolume(op_id, 100, 150)))
-        assert_equal(self.pvolB ^ self.pvolC, self.pvolB | self.pvolC)
-        assert_equal(self.pvolB ^ self.pvolD,
-                     volume.PeriodicCVDefinedVolume(op_id, -100, 50))
+        assert self.pvolA ^ self.pvolA_ == volume.FullVolume()
+        assert self.pvolA ^ self.pvolA == volume.EmptyVolume()
+        assert (self.pvolE ^ self.pvolD ==
+                volume.UnionVolume(
+                    volume.PeriodicCVDefinedVolume(op_id, -150, -100),
+                    volume.PeriodicCVDefinedVolume(op_id, 100, 150)))
+        assert self.pvolB ^ self.pvolC == self.pvolB | self.pvolC
+        assert (self.pvolB ^ self.pvolD ==
+                volume.PeriodicCVDefinedVolume(op_id, -100, 50))
 
     def test_periodic_not_combos(self):
-        assert_equal(~self.pvolA, self.pvolA_)
-        assert_equal(self.pvolA, ~self.pvolA_)
-        assert_equal(~(self.pvolB | self.pvolC), 
-                     volume.NegatedVolume(self.pvolB | self.pvolC))
-        assert_equal((~(self.pvolB | self.pvolC))(25), True)
-        assert_equal((~(self.pvolB | self.pvolC))(75), False)
-        assert_equal((~(self.pvolB | self.pvolC))(-75), False)
+        assert ~self.pvolA == self.pvolA_
+        assert self.pvolA == ~self.pvolA_
+        assert (~(self.pvolB | self.pvolC) ==
+                volume.NegatedVolume(self.pvolB | self.pvolC))
+        assert (~(self.pvolB | self.pvolC))(25) is True
+        assert (~(self.pvolB | self.pvolC))(75) is False
+        assert (~(self.pvolB | self.pvolC))(-75) is False
 
     def test_periodic_sub_combos(self):
-        assert_equal(self.pvolA - self.pvolA_, self.pvolA)
-        assert_equal(self.pvolA_ - self.pvolA, self.pvolA_)
-        assert_equal(self.pvolD - self.pvolA,
-                     volume.PeriodicCVDefinedVolume(op_id, 75, 100))
-        assert_equal((self.pvolD - self.pvolA)(80), True)
-        assert_equal((self.pvolD - self.pvolA)(50), False)
-        assert_equal(self.pvolB - self.pvolC, self.pvolB)
-        assert_equal(self.pvolA - self.pvolA, volume.EmptyVolume())
-        assert_equal(self.pvolE - self.pvolD,
-                     volume.UnionVolume(
-                         volume.PeriodicCVDefinedVolume(op_id, -150, -100),
-                         volume.PeriodicCVDefinedVolume(op_id, 100, 150)))
-        assert_equal(self.pvolE - self.pvolA_,
-                     volume.PeriodicCVDefinedVolume(op_id, -100, 75))
+        assert self.pvolA - self.pvolA_ is self.pvolA
+        assert self.pvolA_ - self.pvolA is self.pvolA_
+        assert (self.pvolD - self.pvolA ==
+                volume.PeriodicCVDefinedVolume(op_id, 75, 100))
+        assert (self.pvolD - self.pvolA)(80) is True
+        assert (self.pvolD - self.pvolA)(50) is False
+        assert self.pvolB - self.pvolC is self.pvolB
+        assert self.pvolA - self.pvolA == volume.EmptyVolume()
+        assert (self.pvolE - self.pvolD ==
+                volume.UnionVolume(
+                    volume.PeriodicCVDefinedVolume(op_id, -150, -100),
+                    volume.PeriodicCVDefinedVolume(op_id, 100, 150)))
+        assert (self.pvolE - self.pvolA_ ==
+                volume.PeriodicCVDefinedVolume(op_id, -100, 75))
 
 
 class TestAbstract(object):
     @raises_with_message_like(TypeError, "Can't instantiate abstract class")
     def test_abstract_volume(self):
-        mover = volume.Volume()
+        volume.Volume()
diff --git a/openpathsampling/tests/test_wham.py b/openpathsampling/tests/test_wham.py
index 611462258..1ed2a6212 100644
--- a/openpathsampling/tests/test_wham.py
+++ b/openpathsampling/tests/test_wham.py
@@ -19,7 +19,7 @@
 
 
 class TestWHAM(object):
-    def setup(self):
+    def setup_method(self):
         self.exact = [1.0, 0.5, 0.25, 0.125, 0.0625, 0.03125, 0.015625]
         self.iface1 = [2.0, 1.0, 0.5, 0.25, 0.125, 0.0625, 0.0]
         self.iface2 = [1.0, 1.0, 1.0, 0.5, 0.25, 0.125, 0.0625]
@@ -114,7 +114,7 @@ def test_weighted_counts_tis(self):
     def test_generate_lnZ(self):
         guess = [1.0, 1.0, 1.0]
         expected_lnZ = np.log([1.0, old_div(1.0,4.0), old_div(7.0,120.0)])
-        # TODO: I'm not sure the last is log(7/120) 
+        # TODO: I'm not sure the last is log(7/120)
         # however, I got the same result out of the old version, too, and
         # this does combine into the correct result in the end (see
         # test_output_histogram)