Skip to content

New analysis testing utilities#1068

Merged
dwhswenson merged 21 commits into
openpathsampling:masterfrom
dwhswenson:test-analysis-utils
Dec 10, 2021
Merged

New analysis testing utilities#1068
dwhswenson merged 21 commits into
openpathsampling:masterfrom
dwhswenson:test-analysis-utils

Conversation

@dwhswenson

@dwhswenson dwhswenson commented Sep 10, 2021

Copy link
Copy Markdown
Member

Writing tests for analysis code is hard. I always tell people that the input to most analysis methods should be a list of steps. But how do you mock up a sequence of steps?

This PR adds some utilities in tests/analysis/utils/ to help create mock simulation results. These utilities have their own test in that same directory.

This was developed because the headache of writing such tests has been blocking both #1026 and #1048 for months. I decided to make a separate task out of creating tools to solve that generic problem.

The approach used here is to use as much of the internal OPS machinery as possible, instead of just reproducing the data it generates (as was done in the OPSPiggybacker). Here, we make use of unittest.patch to inject the data that OPS would have created via simulation. This means that if internal structure of movers (or of Details they return) changes, this code should automatically use that new behavior.

The scope of this PR is only aiming to support one-way shooting moves, path reversal moves, and repex moves, as well as wrapping as with an OrganizeByMoveGroup global strategy. Support for other movers will be added as needed in the future.

@codecov

codecov Bot commented Sep 12, 2021

Copy link
Copy Markdown

Codecov Report

Merging #1068 (bf098d3) into master (9c525b1) will not change coverage.
The diff coverage is n/a.

Impacted file tree graph

@@           Coverage Diff           @@
##           master    #1068   +/-   ##
=======================================
  Coverage   81.56%   81.56%           
=======================================
  Files         140      140           
  Lines       15452    15452           
=======================================
  Hits        12604    12604           
  Misses       2848     2848           

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 9c525b1...bf098d3. Read the comment docs.

@dwhswenson dwhswenson changed the title [WIP] New analysis testing utilities New analysis testing utilities Sep 12, 2021
@dwhswenson dwhswenson marked this pull request as ready for review September 12, 2021 16:18
@dwhswenson

Copy link
Copy Markdown
Member Author

This is ready for review and comment. I will leave it open for at least 48 hours, merging no earlier than Tue 14 Sep 20:00 GMT (16:00 my local).

Coverage doesn't show, since the utils code in inside the tests directory. However, local tests showed coverage for everything except the ImportError that preserves Py27 compatibility.

For what it's worth, I'm already playing with this in a branch where I've mixed this stuff and #1026. Writing tests for #1026 is so easy now.

@sroet sroet left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great,

2 critical points:

  • I am not convinced "make_trajectory" does what it is supposed to,
  • I would like to have access to the final_state in the make_tis_trajectory

Also pointed out the issues my development environment was complaining about, feel free to ignore those

@pytest.fixture
def tis_network():
paths.InterfaceSet._reset()
cv = paths.FunctionCV("x", lambda s: s.xyz[0][0])

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure about "import order", but would these fixture work for functions that require monkey (un)patching?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The CVs used here are not compatible with SimStore, so this should not be used if the intent is to also test SimStore storage.

Of course, that will change in 2.0 when paths.FunctionCV is paths.experimental.storage.CollectiveVariable.

Comment on lines +33 to +41
@pytest.mark.parametrize('bounds', [(-0.1, 1.0), (-0.1, 0.5)])
def test_make_trajectory(bounds):
lower, upper = bounds
traj = make_trajectory(lower, upper)
xvals = traj.xyz[:,0,0]
assert upper <= max(xvals) < upper + 0.1
assert lower <= min(xvals) < lower + 0.1
expected_len = round((upper - lower) / 0.1) + 1
assert len(traj) == expected_len

@sroet sroet Sep 14, 2021

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just an FYI: be careful about using round it uses (the correct) bankers' rounding, which might bite you if you do not expect it. (for the current values this test is fine, but for `bounds=(-0.55, 0) it is not)

>>> round(2.5)
2
>>> round(1.5)
2

Also, this test fails if upper is not divisible by 0.1 (bounds=(0, 0.45) fails as does bounds=(0, 0.55)

Comment on lines +68 to +69
xvals = np.arange(lower, upper + 0.01, 0.1) + np.random.random() * 0.1
return make_1d_traj(xvals)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no guarantee that upper is crossed here

say lower=0, upper=0.09 then the xvals become [0+epsilon], which only reaches 0.09 10% of the time.

Comment thread openpathsampling/tests/analysis/utils.py
Comment thread openpathsampling/tests/analysis/utils.py Outdated
Comment on lines +70 to +80

def make_tis_trajectory(cv_max, lower_bound=-0.1):
"""Make a TIS trajectory with a given maximum x value.

"""
increasing = make_trajectory(lower_bound, cv_max)
if cv_max >= 1.0:
return increasing
else:
decreasing = increasing.reversed[1:]
return increasing + decreasing

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The cutoff between switching from increasing to both should probably be user configurable, also 2 blank lines expected and only 1 found

Suggested change
def make_tis_trajectory(cv_max, lower_bound=-0.1):
"""Make a TIS trajectory with a given maximum x value.
"""
increasing = make_trajectory(lower_bound, cv_max)
if cv_max >= 1.0:
return increasing
else:
decreasing = increasing.reversed[1:]
return increasing + decreasing
def make_tis_trajectory(cv_max, lower_bound=-0.1, final_state=1.0):
"""Make a TIS trajectory with a given maximum x value.
"""
increasing = make_trajectory(lower_bound, cv_max)
if cv_max >= final_state:
return increasing
else:
decreasing = increasing.reversed[1:]
return increasing + decreasing

Comment thread openpathsampling/tests/analysis/utils.py
Comment thread openpathsampling/tests/analysis/utils.py Outdated
Comment thread openpathsampling/tests/analysis/utils.py
Comment thread openpathsampling/tests/analysis/utils.py
@dwhswenson

dwhswenson commented Nov 7, 2021

Copy link
Copy Markdown
Member Author

Sorry that this has been on pause for so long. I'll go ahead and take the PEP8 improvements. However, some of the other comments from review made it clear to me that my code was not communicating its purpose well enough, because that purpose was not obvious to @sroet. I've been trying to figure out what the best way to make the purpose more clear would be -- but rather than try to predict was others will think, it's better to just ask!

Fundamental ideas that I don't think have been communicated clearly enough here are mainly about the make_trajectory/make_tis_trajectory methods:

  1. This is a specific set of fixtures intended for analysis, and they are intended to be used together. Specifically, the make_trajectory method here is only intended to be used with the specific network fixture created here. Some of @sroet's concerns deal with the issue that someone might use make_tis_trajectory for a different network.
  2. The trajectories created are designed to fit a style I've tended to use, which discretizes at intervals of 0.1. A number of @sroet's concerns deal with my implicit assumption that the inputs are at intervals of 0.1.

Here are a few options I've considered on how to make these things clearer to others reading the code. If you have options, please suggest!

Regarding point 1:

  • document it in docstrings
  • convert these separate but related fixtures to a single instance of some class, where, e.g., network and scheme might be properties, and make_trajectory is a method specific to the fixture

Regarding point 2:

  • docstrings
  • convert make_trajectory into something that takes integers in the range [-1, 10], which makes the discretization more explicit. This might also involve changing the network itself to actually use the state borders at 0.0, 10.0 instead of 0.0, 1.0.

These are the things I thought of; again, I'm very open to other options. The code in this PR is very much intended for re-use by contributors (please let me make it easier for you to write tests!), so it is extremely important that it is easily understood.

I'm somewhat leaning toward the more complicated solution in both cases (especially point 1, where it enables me to encapsulate the make_trajectory method with the fixture)

Co-authored-by: Sander Roet <sanderroet@hotmail.com>
@sroet

sroet commented Nov 8, 2021

Copy link
Copy Markdown
Member

About point 1:

I agree that if things are only to be used together, they should be grouped together in 1 object (instance or class)

About point 2:
If it is discrete I would force it to indeed only accept and work in integer space

@dwhswenson dwhswenson added this to the 1.6 milestone Nov 8, 2021
This links the make_tis_trajectory method to a fixture class that
depends on the details of the setup. In this way, there's less of
an implicit dependence on a certain kind of setup. Also switches
it so that the input values for trajectories here are based on
integers instead of floats, which should avoid confusion.

Probably still need significant docs here, but the tests seem to
pass locally, so that's good.
@dwhswenson

Copy link
Copy Markdown
Member Author

I ended up doing some pretty significant changes since the last review, so this is probably worth a complete re-review, as opposed to just reviewing changes since last review. I re-organized according to the discussion in #1068 (comment) and following.

Some of the things that are currently in tests/analysis/utils might be moved to tests/utils in the future, because I think they might be useful in testing more broadly (using a consistent network for 2-state TPS and 2-state TIS setups instead of custom setups for every test class).

Here's what's now included in this PR:

  • tests/analysis/utils/fixture_classes.py: This includes the core fixture classes and tools to create fixtures within a conftest.py. These now group the make_trajectory/make_tis_trajectory in an object with the network and scheme, so it should be clear that these are all related.
  • tests/analysis/utils/test_fixture_classes.py: Tests for the stuff in fixture_classes. Note that this actually tests by way of the specific setups used in conftest.py, so technically is also testing that.
  • tests/analysis/utils/mock_movers.py: This is the core of mocking out pathmovers for analysis. Code here is largely unchanged from the previous, but moved to a separate and more clearly-named module.
  • tests/analysis/utils/test_mock_movers.py: Tests for above. This is largely unchanged from previous, except in that the range (-0.1, 1.0) was changed to (-1, 10) and trajectory inputs are now all integers.
  • tests/analysis/conftest.py: Fixtures that will be the most common setups.

@dwhswenson dwhswenson requested a review from sroet December 9, 2021 20:04

@sroet sroet left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some extra imports, and some possible corrections on the docs. LGTM otherwise

Comment thread openpathsampling/tests/analysis/conftest.py Outdated
Comment thread openpathsampling/tests/analysis/utils/mock_movers.py Outdated
Comment thread openpathsampling/tests/analysis/utils/mock_movers.py Outdated
Co-authored-by: Sander Roet <sanderroet@hotmail.com>

@sroet sroet left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, feel free to merge on green

@dwhswenson dwhswenson merged commit ffe5fa0 into openpathsampling:master Dec 10, 2021
@dwhswenson dwhswenson deleted the test-analysis-utils branch December 10, 2021 14:19
@dwhswenson dwhswenson mentioned this pull request Jan 4, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants