From e71456d8078f14d74a9d7a693893775323cfcfe7 Mon Sep 17 00:00:00 2001 From: cadeduckworth Date: Thu, 15 Sep 2022 10:26:04 -0700 Subject: [PATCH 01/24] developing back end run methods * separating functionality for use with _single_universe without iterating frames * retaining functionality for use with _single_frame --- mdpow/analysis/ensemble.py | 49 +++++++++++++++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/mdpow/analysis/ensemble.py b/mdpow/analysis/ensemble.py index bc75f012..43fc3b14 100644 --- a/mdpow/analysis/ensemble.py +++ b/mdpow/analysis/ensemble.py @@ -10,6 +10,7 @@ import MDAnalysis as mda from MDAnalysis.lib.log import ProgressBar from MDAnalysis.exceptions import FileFormatWarning, NoDataError, MissingDataWarning, SelectionError +#add NotImplementedError? from gromacs.utilities import in_dir @@ -504,7 +505,7 @@ def _conclude_ensemble(self): """Run after all trajectories in ensemble are finished""" pass # pragma: no cover - def run(self, start=None, stop=None, step=None): + '''def run(self, start=None, stop=None, step=None): """Runs _single_universe on each system and _single_frame on each frame in the system. @@ -530,6 +531,52 @@ def run(self, start=None, stop=None, step=None): logger.info("Finishing up") self._conclude_ensemble() return self + ''' + def run(self, start=None, stop=None, step=None): + run.start=start + run.stop=stop + run.step=step + try: + self._run_frame(self, start=run.start, stop=run.stop, step=run.step) + except NotImplementedError: + self._run_universe(self, start=run.start, stop=run.stop, step=run.step) + raise + return self + + #having issues with start stop step arguments + #possibly fixed issues with ^arguments 9/14/22 + def _run_universe(self, start=None, stop=None, step=None): + logger.info("Setting up systems") + self._prepare_ensemble() + for self._key in ProgressBar(self._ensemble.keys(), verbose=True): + self._setup_system(self._key, start=start, stop=stop, step=step) + #self._prepare_universe() + self._single_universe() + #self._conclude_universe() + logger.info("Moving to next universe") + logger.info("Finishing up") + self._conclude_ensemble() + return self + + def _run_frame(self, start=None, stop=None, step=None): + logger.info("Setting up systems") + self._prepare_ensemble() + for self._key in ProgressBar(self._ensemble.keys(), verbose=True): + self._setup_system(self._key, start=start, stop=stop, step=step) + #self._prepare_universe() + #self._single_universe() + for i, ts in enumerate(ProgressBar(self._trajectory[self.start:self.stop:self.step], verbose=True, + postfix=f'running system {self._key}')): + self._frame_index = i + self._ts = ts + self.frames[i] = ts.frame + self.times[i] = ts.time + self._single_frame() + #self._conclude_universe() + logger.info("Moving to next universe") + logger.info("Finishing up") + self._conclude_ensemble() + return self @staticmethod def check_groups_from_common_ensemble(groups: List[EnsembleAtomGroup]): From 6c67ec158377a05d5bc08315bfd26ae05972a0e2 Mon Sep 17 00:00:00 2001 From: cadeduckworth Date: Thu, 15 Sep 2022 11:42:54 -0700 Subject: [PATCH 02/24] condensed backend run methods * try/except pattern condensed to one run method * single run method implemented for _single_universe and _single_frame * functionality of original run method retained, while removing frame iteration for _single_universe usage * progress bar still active when using _single_universe, but no frame iteration occurs --- mdpow/analysis/ensemble.py | 60 +++++--------------------------------- 1 file changed, 7 insertions(+), 53 deletions(-) diff --git a/mdpow/analysis/ensemble.py b/mdpow/analysis/ensemble.py index 43fc3b14..167837ee 100644 --- a/mdpow/analysis/ensemble.py +++ b/mdpow/analysis/ensemble.py @@ -10,7 +10,6 @@ import MDAnalysis as mda from MDAnalysis.lib.log import ProgressBar from MDAnalysis.exceptions import FileFormatWarning, NoDataError, MissingDataWarning, SelectionError -#add NotImplementedError? from gromacs.utilities import in_dir @@ -505,8 +504,8 @@ def _conclude_ensemble(self): """Run after all trajectories in ensemble are finished""" pass # pragma: no cover - '''def run(self, start=None, stop=None, step=None): - """Runs _single_universe on each system and _single_frame + def run(self, start=None, stop=None, step=None): + """Runs _single_universe on each system or _single_frame on each frame in the system. First iterates through keys of ensemble, then runs _setup_system @@ -517,62 +516,17 @@ def _conclude_ensemble(self): self._prepare_ensemble() for self._key in ProgressBar(self._ensemble.keys(), verbose=True): self._setup_system(self._key, start=start, stop=stop, step=step) - self._prepare_universe() - self._single_universe() - for i, ts in enumerate(ProgressBar(self._trajectory[self.start:self.stop:self.step], verbose=True, - postfix=f'running system {self._key}')): - self._frame_index = i - self._ts = ts - self.frames[i] = ts.frame - self.times[i] = ts.time - self._single_frame() - self._conclude_universe() - logger.info("Moving to next universe") - logger.info("Finishing up") - self._conclude_ensemble() - return self - ''' - def run(self, start=None, stop=None, step=None): - run.start=start - run.stop=stop - run.step=step - try: - self._run_frame(self, start=run.start, stop=run.stop, step=run.step) - except NotImplementedError: - self._run_universe(self, start=run.start, stop=run.stop, step=run.step) - raise - return self - - #having issues with start stop step arguments - #possibly fixed issues with ^arguments 9/14/22 - def _run_universe(self, start=None, stop=None, step=None): - logger.info("Setting up systems") - self._prepare_ensemble() - for self._key in ProgressBar(self._ensemble.keys(), verbose=True): - self._setup_system(self._key, start=start, stop=stop, step=step) - #self._prepare_universe() - self._single_universe() - #self._conclude_universe() - logger.info("Moving to next universe") - logger.info("Finishing up") - self._conclude_ensemble() - return self - - def _run_frame(self, start=None, stop=None, step=None): - logger.info("Setting up systems") - self._prepare_ensemble() - for self._key in ProgressBar(self._ensemble.keys(), verbose=True): - self._setup_system(self._key, start=start, stop=stop, step=step) - #self._prepare_universe() - #self._single_universe() - for i, ts in enumerate(ProgressBar(self._trajectory[self.start:self.stop:self.step], verbose=True, + try: + self._single_universe() + except NotImplementedError: + for i, ts in enumerate(ProgressBar(self._trajectory[self.start:self.stop:self.step], verbose=True, postfix=f'running system {self._key}')): self._frame_index = i self._ts = ts self.frames[i] = ts.frame self.times[i] = ts.time self._single_frame() - #self._conclude_universe() + raise logger.info("Moving to next universe") logger.info("Finishing up") self._conclude_ensemble() From 7bcdf49b569b28012d868cebe2d21efe7659b3eb Mon Sep 17 00:00:00 2001 From: cadeduckworth Date: Fri, 21 Oct 2022 20:21:56 -0700 Subject: [PATCH 03/24] Addressed change requests by Oliver removed raise added docstrings to describe use of NotImplementedError responded with reason for removing _conclude_universe() next step: updating html documentation following successful testing --- mdpow/analysis/ensemble.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/mdpow/analysis/ensemble.py b/mdpow/analysis/ensemble.py index 167837ee..1fe9c5f7 100644 --- a/mdpow/analysis/ensemble.py +++ b/mdpow/analysis/ensemble.py @@ -511,6 +511,13 @@ def run(self, start=None, stop=None, step=None): First iterates through keys of ensemble, then runs _setup_system which defines the system and trajectory. Then iterates over trajectory frames. + + NotImplementedError will detect whether _single_universe or _single_frame + should be implemented, based on which is defined in the EnsembleAnalysisClass. + Only one of the two aforementioned functions should be defined for the respective + analysis class. For verbose functionality, the analysis will currently show two + iteration bars, where only one of which will actually be iterated, while the other + will load to completion instantaneously, showing the system that is being worked on. """ logger.info("Setting up systems") self._prepare_ensemble() @@ -526,7 +533,6 @@ def run(self, start=None, stop=None, step=None): self.frames[i] = ts.frame self.times[i] = ts.time self._single_frame() - raise logger.info("Moving to next universe") logger.info("Finishing up") self._conclude_ensemble() From a87cb1153cde6315dbf42ac2948cd55bb633ec06 Mon Sep 17 00:00:00 2001 From: cadeduckworth Date: Fri, 21 Oct 2022 20:30:26 -0700 Subject: [PATCH 04/24] fixed indentation error for _single_frame() block --- mdpow/analysis/ensemble.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/mdpow/analysis/ensemble.py b/mdpow/analysis/ensemble.py index 1fe9c5f7..7744a268 100644 --- a/mdpow/analysis/ensemble.py +++ b/mdpow/analysis/ensemble.py @@ -528,11 +528,11 @@ def run(self, start=None, stop=None, step=None): except NotImplementedError: for i, ts in enumerate(ProgressBar(self._trajectory[self.start:self.stop:self.step], verbose=True, postfix=f'running system {self._key}')): - self._frame_index = i - self._ts = ts - self.frames[i] = ts.frame - self.times[i] = ts.time - self._single_frame() + self._frame_index = i + self._ts = ts + self.frames[i] = ts.frame + self.times[i] = ts.time + self._single_frame() logger.info("Moving to next universe") logger.info("Finishing up") self._conclude_ensemble() From 7edde0d7ed4ea9dcee2c9532f5c1a84030e18d42 Mon Sep 17 00:00:00 2001 From: cadeduckworth Date: Fri, 21 Oct 2022 22:13:48 -0700 Subject: [PATCH 05/24] reverting previous tab issue, necessary for normal function --- mdpow/analysis/ensemble.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/mdpow/analysis/ensemble.py b/mdpow/analysis/ensemble.py index 7744a268..7ac78856 100644 --- a/mdpow/analysis/ensemble.py +++ b/mdpow/analysis/ensemble.py @@ -528,11 +528,12 @@ def run(self, start=None, stop=None, step=None): except NotImplementedError: for i, ts in enumerate(ProgressBar(self._trajectory[self.start:self.stop:self.step], verbose=True, postfix=f'running system {self._key}')): - self._frame_index = i - self._ts = ts - self.frames[i] = ts.frame - self.times[i] = ts.time - self._single_frame() + self._frame_index = i + self._ts = ts + self.frames[i] = ts.frame + self.times[i] = ts.time + self._single_frame() + raise logger.info("Moving to next universe") logger.info("Finishing up") self._conclude_ensemble() From 48016c5e6cc44bc5a64b304e7316e1f154a9e6b0 Mon Sep 17 00:00:00 2001 From: cadeduckworth Date: Sat, 22 Oct 2022 18:35:46 -0700 Subject: [PATCH 06/24] fixed errors previously reverted for testing fixed expected indent block error removed raise removed _conclude_universe() --- mdpow/analysis/ensemble.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/mdpow/analysis/ensemble.py b/mdpow/analysis/ensemble.py index 7ac78856..7744a268 100644 --- a/mdpow/analysis/ensemble.py +++ b/mdpow/analysis/ensemble.py @@ -528,12 +528,11 @@ def run(self, start=None, stop=None, step=None): except NotImplementedError: for i, ts in enumerate(ProgressBar(self._trajectory[self.start:self.stop:self.step], verbose=True, postfix=f'running system {self._key}')): - self._frame_index = i - self._ts = ts - self.frames[i] = ts.frame - self.times[i] = ts.time - self._single_frame() - raise + self._frame_index = i + self._ts = ts + self.frames[i] = ts.frame + self.times[i] = ts.time + self._single_frame() logger.info("Moving to next universe") logger.info("Finishing up") self._conclude_ensemble() From fb58f2705260a409704386e3b2707d89cb655dc3 Mon Sep 17 00:00:00 2001 From: cadeduckworth Date: Mon, 24 Oct 2022 23:14:34 -0700 Subject: [PATCH 07/24] doc string format/syntax issue --- mdpow/analysis/dihedral.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mdpow/analysis/dihedral.py b/mdpow/analysis/dihedral.py index 241c1e37..bde4cfcc 100644 --- a/mdpow/analysis/dihedral.py +++ b/mdpow/analysis/dihedral.py @@ -78,7 +78,7 @@ def check_dihedral_inputs(selections): for group in selections: for k in group.keys(): if len(group[k]) != 4: - msg = ''''Dihedral calculations require AtomGroups with + msg = '''Dihedral calculations require AtomGroups with only 4 atoms, %s selected''' % len(group) logger.error(msg) raise SelectionError(msg) From 1f7f2d11f700b1ad9021ea789402d46f8631121e Mon Sep 17 00:00:00 2001 From: cadeduckworth Date: Mon, 24 Oct 2022 23:16:26 -0700 Subject: [PATCH 08/24] checkout from alt repository --- mdpow/analysis/ensemble.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/mdpow/analysis/ensemble.py b/mdpow/analysis/ensemble.py index 167837ee..bbfbe36f 100644 --- a/mdpow/analysis/ensemble.py +++ b/mdpow/analysis/ensemble.py @@ -521,12 +521,11 @@ def run(self, start=None, stop=None, step=None): except NotImplementedError: for i, ts in enumerate(ProgressBar(self._trajectory[self.start:self.stop:self.step], verbose=True, postfix=f'running system {self._key}')): - self._frame_index = i - self._ts = ts - self.frames[i] = ts.frame - self.times[i] = ts.time - self._single_frame() - raise + self._frame_index = i + self._ts = ts + self.frames[i] = ts.frame + self.times[i] = ts.time + self._single_frame() logger.info("Moving to next universe") logger.info("Finishing up") self._conclude_ensemble() From 179603fa764b79666e03991a36c346b1e6028860 Mon Sep 17 00:00:00 2001 From: cadeduckworth Date: Fri, 28 Oct 2022 16:15:20 -0700 Subject: [PATCH 09/24] changed base class function definitions from pass to raise NotImplementedError for _single_frame() and _single_universe() so try/except pattern in run method works properly --- mdpow/analysis/ensemble.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mdpow/analysis/ensemble.py b/mdpow/analysis/ensemble.py index bbfbe36f..de328997 100644 --- a/mdpow/analysis/ensemble.py +++ b/mdpow/analysis/ensemble.py @@ -469,14 +469,14 @@ def _single_universe(self): Run on each universe in the ensemble during when self.run in called. """ - pass # pragma: no cover + raise NotImplementedError # pragma: no cover def _single_frame(self): """Calculate data from a single frame of trajectory Called on each frame for universes in the Ensemble. """ - pass # pragma: no cover + raise NotImplementedError # pragma: no cover def _prepare_ensemble(self): """For establishing data structures used in running From f47c806a805f8b1f18ebb04b498b71a18df60f91 Mon Sep 17 00:00:00 2001 From: cadeduckworth Date: Tue, 1 Nov 2022 21:00:45 -0700 Subject: [PATCH 10/24] adding preliminary test for try-except pattern addition to backend run method, mdpow/tests/test_run_except.py --- mdpow/tests/test_run_except.py | 40 ++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 mdpow/tests/test_run_except.py diff --git a/mdpow/tests/test_run_except.py b/mdpow/tests/test_run_except.py new file mode 100644 index 00000000..7c3559de --- /dev/null +++ b/mdpow/tests/test_run_except.py @@ -0,0 +1,40 @@ +from __future__ import absolute_import + +from . import tempdir as td + +import py.path + +import pybol +import pytest + +from ..analysis.ensemble import Ensemble, EnsembleAnalysis, EnsembleAtomGroup +from ..analysis.dihedral import DihedralAnalysis + +from pkg_resources import resource_filename + +RESOURCES = py.path.local(resource_filename(__name__, 'testing_resources')) +MANIFEST = RESOURCES.join("manifest.yml") + +# https://docs.pytest.org/en/7.2.x/how-to/assert.html#assertraises + +class TestRunExcept(object): + + def setup(self): + self.tmpdir = td.TempDir() + self.m = pybol.Manifest(str(RESOURCES / 'manifest.yml')) + self.m.assemble('example_FEP', self.tmpdir.name) + self.Ens = Ensemble(dirname=self.tmpdir.name, solvents=['water']) + + def teardown(self): + self.tmpdir.dissolve() + + @pytest.mark.xfail(raises=NotImplementedError) + def test_except(self): + dh = self.Ens.select_atoms('name C4 or name C17 or name S2 or name N3') + dh_run = DihedralAnalysis([dh]).run(start=0, stop=4, step=1) + dh_run + + def test_single_universe(self): + dh = self.Ens.select_atoms('name C4 or name C17 or name S2 or name N3') + with pytest.raises(NotImplementedError): + DihedralAnalysis([dh])._single_universe() From 5e023221b332f5a36150bf46ee53609b9b1d94cd Mon Sep 17 00:00:00 2001 From: cadeduckworth Date: Thu, 3 Nov 2022 23:19:38 -0700 Subject: [PATCH 11/24] removed # pragma: no cover from _single_frame() and _single_universe() --- mdpow/analysis/ensemble.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mdpow/analysis/ensemble.py b/mdpow/analysis/ensemble.py index a74d41e0..aac4980b 100644 --- a/mdpow/analysis/ensemble.py +++ b/mdpow/analysis/ensemble.py @@ -469,14 +469,14 @@ def _single_universe(self): Run on each universe in the ensemble during when self.run in called. """ - raise NotImplementedError # pragma: no cover + raise NotImplementedError def _single_frame(self): """Calculate data from a single frame of trajectory Called on each frame for universes in the Ensemble. """ - raise NotImplementedError # pragma: no cover + raise NotImplementedError def _prepare_ensemble(self): """For establishing data structures used in running From 2fc91357530aa672c59eef60f31594064c3e952b Mon Sep 17 00:00:00 2001 From: cadeduckworth Date: Thu, 3 Nov 2022 23:56:55 -0700 Subject: [PATCH 12/24] changed dihedral atom group selection strings in test_dihedral.py to achieve an ordered selection format --- mdpow/tests/test_dihedral.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/mdpow/tests/test_dihedral.py b/mdpow/tests/test_dihedral.py index c54a110c..56b44acf 100644 --- a/mdpow/tests/test_dihedral.py +++ b/mdpow/tests/test_dihedral.py @@ -37,7 +37,7 @@ def teardown(self): self.tmpdir.dissolve() def test_dataframe(self): - dh1 = self.Ens.select_atoms('name C4 or name C17 or name S2 or name N3') + dh1 = self.Ens.select_atoms('name C4', 'name C17', 'name S2', 'name N3') dh_run = DihedralAnalysis([dh1]).run(start=0, stop=4, step=1) results = dh_run.results @@ -49,13 +49,13 @@ def test_dataframe(self): assert i == 'Coulomb' def test_selection_error(self): - dh1 = self.Ens.select_atoms('name C17 or name S2 or name N3') + dh1 = self.Ens.select_atoms('name C17', 'name S2', 'name N3') with pytest.raises(SelectionError): dh_run = DihedralAnalysis([dh1]).run(start=0, stop=4, step=1) def test_results_recursive1(self): - dh1 = self.Ens.select_atoms('name C11 or name C10 or name C9 or name C4') - dh2 = self.Ens.select_atoms('name C11 or name C10 or name C9 or name C4') + dh1 = self.Ens.select_atoms('name C11', 'name C10', 'name C9', 'name C4') + dh2 = self.Ens.select_atoms('name C11', 'name C10', 'name C9', 'name C4') dh_run1 = DihedralAnalysis([dh1]).run(start=0, stop=4, step=1) dh_run2 = DihedralAnalysis([dh2]).run(start=0, stop=4, step=1) @@ -64,8 +64,8 @@ def test_results_recursive1(self): assert dh_run1.results['dihedral'][i] == dh_run2.results['dihedral'][i] def test_results_recursive2(self): - dh1 = self.Ens.select_atoms('name C11 or name C10 or name C9 or name C4') - dh2 = self.Ens.select_atoms('name C8 or name C4 or name C9 or name C10') + dh1 = self.Ens.select_atoms('name C11', 'name C10', 'name C9', 'name C4') + dh2 = self.Ens.select_atoms('name C8', 'name C4', 'name C9', 'name C10') dh_run = DihedralAnalysis([dh1, dh2]).run(start=0, stop=4, step=1) @@ -84,8 +84,8 @@ def test_results_recursive2(self): def test_ValueError_different_ensemble(self): other = Ensemble(dirname=self.tmpdir.name, solvents=['water']) - dh1 = self.Ens.select_atoms('name C11 or name C10 or name C9 or name C4') - dh2 = other.select_atoms('name C8 or name C4 or name C9 or name C10') + dh1 = self.Ens.select_atoms('name C11', 'name C10', 'name C9', 'name C4') + dh2 = other.select_atoms('name C8', 'name C4', 'name C9', 'name C10') with pytest.raises(ValueError, match='Dihedral selections from different Ensembles, '): DihedralAnalysis([dh1, dh2]) From 37cd1d716b10a363c6eb0827172bd91cae543ea0 Mon Sep 17 00:00:00 2001 From: cadeduckworth Date: Fri, 4 Nov 2022 00:16:38 -0700 Subject: [PATCH 13/24] changed dihedral atom group selection order in test_dataframe to match assert atom group order --- mdpow/tests/test_dihedral.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mdpow/tests/test_dihedral.py b/mdpow/tests/test_dihedral.py index 56b44acf..de2f53af 100644 --- a/mdpow/tests/test_dihedral.py +++ b/mdpow/tests/test_dihedral.py @@ -37,7 +37,8 @@ def teardown(self): self.tmpdir.dissolve() def test_dataframe(self): - dh1 = self.Ens.select_atoms('name C4', 'name C17', 'name S2', 'name N3') + #original order C4 C17 S2 N3, ask Oliver if this matters + dh1 = self.Ens.select_atoms('name S2', 'name N3', 'name C4', 'name C17') dh_run = DihedralAnalysis([dh1]).run(start=0, stop=4, step=1) results = dh_run.results From 0dc7e32cd887189c9183ff455d25725c5145599d Mon Sep 17 00:00:00 2001 From: cadeduckworth Date: Fri, 11 Nov 2022 19:20:20 -0700 Subject: [PATCH 14/24] several changes and updates to test_dihedral.py, resulting in passing pytest locally * selection string format updated, final, correct, and consistent with original intended selection order * updated values calculated for circmean, circvar for use in test_results_recursive2() * changed order for arguments in assert_almost_equal() for use in test_results_recursive2() to align with format in numpy.testing docs, (actual, desired) for readability, does not affect accuracy or testing purposes --- mdpow/tests/test_dihedral.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/mdpow/tests/test_dihedral.py b/mdpow/tests/test_dihedral.py index de2f53af..a189ced2 100644 --- a/mdpow/tests/test_dihedral.py +++ b/mdpow/tests/test_dihedral.py @@ -23,9 +23,9 @@ class TestDihedral(object): DG48910_mean = -172.9849512527183 - DG491011_mean = -3.7300197060478695 - DG48910_var = 1490.6576365537262 - DG491011_var = 128.3805265432388 + DG491011_mean = 177.74725233051953 + DG48910_var = 0.20311120667628546 + DG491011_var = 0.006976126708773456 def setup(self): self.tmpdir = td.TempDir() @@ -37,13 +37,12 @@ def teardown(self): self.tmpdir.dissolve() def test_dataframe(self): - #original order C4 C17 S2 N3, ask Oliver if this matters - dh1 = self.Ens.select_atoms('name S2', 'name N3', 'name C4', 'name C17') + dh1 = self.Ens.select_atoms('name C4', 'name C17', 'name S2', 'name N3') dh_run = DihedralAnalysis([dh1]).run(start=0, stop=4, step=1) results = dh_run.results - assert results['selection'][0] == 'S2-N3-C4-C17' + assert results['selection'][0] == 'C4-C17-S2-N3' for s in results['solvent']: assert s == 'water' for i in results['interaction'][:12]: @@ -70,18 +69,18 @@ def test_results_recursive2(self): dh_run = DihedralAnalysis([dh1, dh2]).run(start=0, stop=4, step=1) - dh1_result = dh_run.results.loc[dh_run.results['selection'] == 'C4-C9-C10-C11']['dihedral'] - dh2_result = dh_run.results.loc[dh_run.results['selection'] == 'C4-C8-C9-C10']['dihedral'] + dh1_result = dh_run.results.loc[dh_run.results['selection'] == 'C11-C10-C9-C4']['dihedral'] + dh2_result = dh_run.results.loc[dh_run.results['selection'] == 'C8-C4-C9-C10']['dihedral'] dh1_mean = circmean(dh1_result, high=180, low=-180) dh2_mean = circmean(dh2_result, high=180, low=-180) dh1_var = circvar(dh1_result, high=180, low=-180) dh2_var = circvar(dh2_result, high=180, low=-180) - assert_almost_equal(self.DG48910_mean, dh1_mean, 6) - assert_almost_equal(self.DG48910_var, dh1_var, 6) - assert_almost_equal(self.DG491011_mean, dh2_mean, 6) - assert_almost_equal(self.DG491011_var, dh2_var, 6) + assert_almost_equal(dh1_mean, self.DG48910_mean, 6) + assert_almost_equal(dh1_var, self.DG48910_var, 6) + assert_almost_equal(dh2_mean, self.DG491011_mean, 6) + assert_almost_equal(dh2_var, self.DG491011_var, 6) def test_ValueError_different_ensemble(self): other = Ensemble(dirname=self.tmpdir.name, solvents=['water']) From a0de2d98bec25a15321b47a959f1a04f4c7259e2 Mon Sep 17 00:00:00 2001 From: cadeduckworth Date: Sat, 12 Nov 2022 00:40:34 -0700 Subject: [PATCH 15/24] relocating test to external branch, 214_dihedral issue --- mdpow/tests/test_run_except.py | 40 ---------------------------------- 1 file changed, 40 deletions(-) delete mode 100644 mdpow/tests/test_run_except.py diff --git a/mdpow/tests/test_run_except.py b/mdpow/tests/test_run_except.py deleted file mode 100644 index 7c3559de..00000000 --- a/mdpow/tests/test_run_except.py +++ /dev/null @@ -1,40 +0,0 @@ -from __future__ import absolute_import - -from . import tempdir as td - -import py.path - -import pybol -import pytest - -from ..analysis.ensemble import Ensemble, EnsembleAnalysis, EnsembleAtomGroup -from ..analysis.dihedral import DihedralAnalysis - -from pkg_resources import resource_filename - -RESOURCES = py.path.local(resource_filename(__name__, 'testing_resources')) -MANIFEST = RESOURCES.join("manifest.yml") - -# https://docs.pytest.org/en/7.2.x/how-to/assert.html#assertraises - -class TestRunExcept(object): - - def setup(self): - self.tmpdir = td.TempDir() - self.m = pybol.Manifest(str(RESOURCES / 'manifest.yml')) - self.m.assemble('example_FEP', self.tmpdir.name) - self.Ens = Ensemble(dirname=self.tmpdir.name, solvents=['water']) - - def teardown(self): - self.tmpdir.dissolve() - - @pytest.mark.xfail(raises=NotImplementedError) - def test_except(self): - dh = self.Ens.select_atoms('name C4 or name C17 or name S2 or name N3') - dh_run = DihedralAnalysis([dh]).run(start=0, stop=4, step=1) - dh_run - - def test_single_universe(self): - dh = self.Ens.select_atoms('name C4 or name C17 or name S2 or name N3') - with pytest.raises(NotImplementedError): - DihedralAnalysis([dh])._single_universe() From 273c1af5252e0fcc8c341abe18d2a80cb8dc8298 Mon Sep 17 00:00:00 2001 From: cadeduckworth Date: Sat, 12 Nov 2022 00:58:59 -0700 Subject: [PATCH 16/24] moved raise NotImplementedError test for _single_universe() for DihedralAnalysis to test_dihedral.py --- mdpow/tests/test_dihedral.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/mdpow/tests/test_dihedral.py b/mdpow/tests/test_dihedral.py index c54a110c..efa37417 100644 --- a/mdpow/tests/test_dihedral.py +++ b/mdpow/tests/test_dihedral.py @@ -90,3 +90,8 @@ def test_ValueError_different_ensemble(self): match='Dihedral selections from different Ensembles, '): DihedralAnalysis([dh1, dh2]) + def test_single_universe(self): + dh = self.Ens.select_atoms('name C4', 'name C17', 'name S2', 'name N3') + with pytest.raises(NotImplementedError): + DihedralAnalysis([dh])._single_universe() + From 653210e7e7df7f4aabb256cd7d85d9fc25373e3a Mon Sep 17 00:00:00 2001 From: cadeduckworth Date: Thu, 17 Nov 2022 03:38:26 -0700 Subject: [PATCH 17/24] added explicit backend run method tests for EnsembleAnalysis * test_ensemble.py ** test_ensemble_analysis_run() ** testing NotImplementedError is raised for: *** _single_frame() *** _single_universe() * passes locally --- mdpow/tests/test_ensemble.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/mdpow/tests/test_ensemble.py b/mdpow/tests/test_ensemble.py index 9d8b2462..b09f0302 100644 --- a/mdpow/tests/test_ensemble.py +++ b/mdpow/tests/test_ensemble.py @@ -149,6 +149,21 @@ def _conclude_universe(self): TestRun = TestAnalysis(Sim).run(start=0, step=1, stop=10) assert Sim.keys() == TestRun.key_list + def test_ensemble_analysis_run(self): + class TestAnalysis(EnsembleAnalysis): + def __init__(self, test_ensemble): + super(TestAnalysis, self).__init__(test_ensemble) + + def test_single_frame(self): + with pytest.raises(NotImplementedError) as excinfo: + TestAnalysis._single_frame(self) + assert excinfo.type == 'NotImplementedError' + + def test_single_universe(self): + with pytest.raises(NotImplementedError) as excinfo: + TestAnalysis._single_universe(self) + assert excinfo.type == 'NotImplementedError' + def test_value_error(self): ens = Ensemble(dirname=self.tmpdir.name, solvents=['water']) copy_ens = Ensemble() From 7eff11fc81e7b2e2b947019115d2a1c9c4948542 Mon Sep 17 00:00:00 2001 From: cadeduckworth Date: Thu, 17 Nov 2022 03:53:06 -0700 Subject: [PATCH 18/24] test_dihedral.py reflects same changes as PR#218 --- mdpow/tests/test_dihedral.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mdpow/tests/test_dihedral.py b/mdpow/tests/test_dihedral.py index b0e3be91..21025b58 100644 --- a/mdpow/tests/test_dihedral.py +++ b/mdpow/tests/test_dihedral.py @@ -2,6 +2,8 @@ from . import tempdir as td +import sys + import py.path import pybol @@ -63,6 +65,7 @@ def test_results_recursive1(self): for i in range(len(dh_run1.results['dihedral'])): assert dh_run1.results['dihedral'][i] == dh_run2.results['dihedral'][i] + @pytest.mark.skipif(sys.version_info < (3, 8), reason="scipy circvar gives wrong answers") def test_results_recursive2(self): dh1 = self.Ens.select_atoms('name C11', 'name C10', 'name C9', 'name C4') dh2 = self.Ens.select_atoms('name C8', 'name C4', 'name C9', 'name C10') From cf4bd40c235a31d68d48b35a8d5204226e472e31 Mon Sep 17 00:00:00 2001 From: cadeduckworth Date: Wed, 7 Dec 2022 17:29:22 -0700 Subject: [PATCH 19/24] updated source and html docs and CHANGELOG to reflect changes made for EnsembleAnalysis run methods to _single_frame and _single_universe, including new tests for NotImplementedError usage --- CHANGES | 21 +++++++++++++++ .../source/analysis/ensemble_analysis.txt | 11 ++++++-- mdpow/analysis/ensemble.py | 27 ++++++++++--------- 3 files changed, 44 insertions(+), 15 deletions(-) diff --git a/CHANGES b/CHANGES index 4be47269..84f95c81 100644 --- a/CHANGES +++ b/CHANGES @@ -5,6 +5,27 @@ CHANGES for MDPOW Add summary of changes for each release. Use ISO dates. Reference GitHub issues numbers and PR numbers. +2022-12-07 0.8.1 +cadeduckworth + +Changes + +* for ensemble.EnsembleAnalysis._single_frame() + changed 'pass' to 'raise NotImplementedError' (#216) +* for ensemble.EnsembleAnalysis._single_universe() + changed 'pass' to 'raise NotImplementedError' (#216) +* for ensemble.EnsembleAnalysis.run() changed to detect + and use _single_frame OR _single_universe (#216) +* _prepare_universe and _conclude_universe removed from + EnsembleAnalysis.run() method, no longer needed (per comments, #199) +* defined new test for updated ensemble.EnsembleAnalysis.run() method (#216): + - new test, test_ensemble_analysis_run(), uses TestAnalysis class, + is in test_ensemble.py + - new test checks that NotImplementedError is raised when _single_frame + or _single_universe are not defined, to implement correct method +* source and html docs updated for EnsembleAnalysis + to reflect changes and describe proper usage + 2022-??-?? 0.8.1 cadeduckworth diff --git a/doc/sphinx/source/analysis/ensemble_analysis.txt b/doc/sphinx/source/analysis/ensemble_analysis.txt index 67c6bc21..65fc2c65 100644 --- a/doc/sphinx/source/analysis/ensemble_analysis.txt +++ b/doc/sphinx/source/analysis/ensemble_analysis.txt @@ -15,10 +15,17 @@ Universes and be analyzed as a group. :class:`~mdpow.analysis.ensemble.EnsembleAnalysis` is a class inspired by the :class:`AnalysisBase ` from MDAnalysis which -iterates over the systems in the ensemble and the frames in the systems. It sets up both iterations between -universes and universe frames allowing for analysis to be run on both whole systems and the frames of those +iterates over the systems in the ensemble or the frames in the systems. It sets up iterations between +universes or universe frames allowing for analysis to be run on either whole systems or the frames of those systems. This allows for users to easily run analyses on MDPOW simulations. +NotImplementedError will detect whether _single_universe or _single_frame +should be implemented, based on which is defined in the EnsembleAnalysisClass. +Only one of the two methods should be defined for an EnsembleAnalysisClass. +For verbose functionality, the analysis may show two iteration bars, +where only one of which will actually be iterated, while the other will +load to completion instantaneously, showing the system that is being worked on. + .. autoclass:: mdpow.analysis.ensemble.EnsembleAnalysis :members: diff --git a/mdpow/analysis/ensemble.py b/mdpow/analysis/ensemble.py index aac4980b..7bd9071a 100644 --- a/mdpow/analysis/ensemble.py +++ b/mdpow/analysis/ensemble.py @@ -466,15 +466,23 @@ def _setup_frames(self, trajectory): def _single_universe(self): """Calculations on a single Universe object. - Run on each universe in the ensemble during when - self.run in called. + Run on each universe in the ensemble during when + self.run in called. + + NotImplementedError will detect whether _single_universe + or _single_frame should be implemented, based on which + is defined in the EnsembleAnalysisClass. """ raise NotImplementedError def _single_frame(self): - """Calculate data from a single frame of trajectory + """Calculate data from a single frame of trajectory. - Called on each frame for universes in the Ensemble. + Called on each frame for universes in the Ensemble. + + NotImplementedError will detect whether _single_universe + or _single_frame should be implemented, based on which + is defined in the EnsembleAnalysisClass. """ raise NotImplementedError @@ -509,15 +517,8 @@ def run(self, start=None, stop=None, step=None): on each frame in the system. First iterates through keys of ensemble, then runs _setup_system - which defines the system and trajectory. Then iterates over - trajectory frames. - - NotImplementedError will detect whether _single_universe or _single_frame - should be implemented, based on which is defined in the EnsembleAnalysisClass. - Only one of the two aforementioned functions should be defined for the respective - analysis class. For verbose functionality, the analysis will currently show two - iteration bars, where only one of which will actually be iterated, while the other - will load to completion instantaneously, showing the system that is being worked on. + which defines the system and trajectory. Then iterates over each + system universe or trajectory frames of each universe as defined. """ logger.info("Setting up systems") self._prepare_ensemble() From 1a90ed81c4fe2ae60d089ec3c3daddffcebb64fd Mon Sep 17 00:00:00 2001 From: cadeduckworth Date: Wed, 7 Dec 2022 21:19:09 -0700 Subject: [PATCH 20/24] split EnsembleAnalysis run method test, one test for _single_frame, one test for _single_universe, tested with other error types to confirm coverage --- mdpow/tests/test_ensemble.py | 35 ++++++++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/mdpow/tests/test_ensemble.py b/mdpow/tests/test_ensemble.py index b09f0302..894fa272 100644 --- a/mdpow/tests/test_ensemble.py +++ b/mdpow/tests/test_ensemble.py @@ -149,20 +149,37 @@ def _conclude_universe(self): TestRun = TestAnalysis(Sim).run(start=0, step=1, stop=10) assert Sim.keys() == TestRun.key_list - def test_ensemble_analysis_run(self): + def test_ensemble_analysis_run_frame(self): class TestAnalysis(EnsembleAnalysis): def __init__(self, test_ensemble): super(TestAnalysis, self).__init__(test_ensemble) - def test_single_frame(self): - with pytest.raises(NotImplementedError) as excinfo: - TestAnalysis._single_frame(self) - assert excinfo.type == 'NotImplementedError' + self._ens = test_ensemble + + def _single_universe(self): + pass + + Sim = Ensemble(dirname=self.tmpdir.name, solvents=['water']) + + with pytest.raises(NotImplementedError) as excinfo: + TestAnalysis._single_frame(self) + assert excinfo.type is NotImplementedError + + def test_ensemble_analysis_run_universe(self): + class TestAnalysis(EnsembleAnalysis): + def __init__(self, test_ensemble): + super(TestAnalysis, self).__init__(test_ensemble) + + self._ens = test_ensemble + + def _single_frame(self): + pass - def test_single_universe(self): - with pytest.raises(NotImplementedError) as excinfo: - TestAnalysis._single_universe(self) - assert excinfo.type == 'NotImplementedError' + Sim = Ensemble(dirname=self.tmpdir.name, solvents=['water']) + + with pytest.raises(NotImplementedError) as excinfo: + TestAnalysis._single_universe(self) + assert excinfo.type is NotImplementedError def test_value_error(self): ens = Ensemble(dirname=self.tmpdir.name, solvents=['water']) From 7054bb5f721423ee165262c886f0e26d7c8edf59 Mon Sep 17 00:00:00 2001 From: cadeduckworth Date: Thu, 8 Dec 2022 15:21:45 -0700 Subject: [PATCH 21/24] minor change to run method tests for proper use of example EnsembleAnalysis --- mdpow/tests/test_ensemble.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/mdpow/tests/test_ensemble.py b/mdpow/tests/test_ensemble.py index 894fa272..e361dcb3 100644 --- a/mdpow/tests/test_ensemble.py +++ b/mdpow/tests/test_ensemble.py @@ -160,10 +160,10 @@ def _single_universe(self): pass Sim = Ensemble(dirname=self.tmpdir.name, solvents=['water']) + TestRun = TestAnalysis(Sim) - with pytest.raises(NotImplementedError) as excinfo: - TestAnalysis._single_frame(self) - assert excinfo.type is NotImplementedError + with pytest.raises(NotImplementedError): + TestRun._single_frame() def test_ensemble_analysis_run_universe(self): class TestAnalysis(EnsembleAnalysis): @@ -176,10 +176,10 @@ def _single_frame(self): pass Sim = Ensemble(dirname=self.tmpdir.name, solvents=['water']) + TestRun = TestAnalysis(Sim) - with pytest.raises(NotImplementedError) as excinfo: - TestAnalysis._single_universe(self) - assert excinfo.type is NotImplementedError + with pytest.raises(NotImplementedError): + TestRun._single_universe() def test_value_error(self): ens = Ensemble(dirname=self.tmpdir.name, solvents=['water']) From 19f7c845625f389f4b80e344ac2ca629cb2e8abf Mon Sep 17 00:00:00 2001 From: cadeduckworth Date: Thu, 8 Dec 2022 15:24:40 -0700 Subject: [PATCH 22/24] added and corrected use of sphinx markup for documentation, edited version information in CHANGES --- CHANGES | 18 ++-------- .../source/analysis/ensemble_analysis.txt | 7 ++-- mdpow/analysis/ensemble.py | 34 ++++++++++++------- 3 files changed, 28 insertions(+), 31 deletions(-) diff --git a/CHANGES b/CHANGES index e2d70df6..84f83784 100644 --- a/CHANGES +++ b/CHANGES @@ -5,8 +5,8 @@ CHANGES for MDPOW Add summary of changes for each release. Use ISO 8061 dates. Reference GitHub issues numbers and PR numbers. -2022-12-07 0.8.1 -cadeduckworth +2022-??-?? 0.9.0 +cadeduckworth, orbeckst, VOD555 Changes @@ -18,19 +18,7 @@ Changes and use _single_frame OR _single_universe (#216) * _prepare_universe and _conclude_universe removed from EnsembleAnalysis.run() method, no longer needed (per comments, #199) -* defined new test for updated ensemble.EnsembleAnalysis.run() method (#216): - - new test, test_ensemble_analysis_run(), uses TestAnalysis class, - is in test_ensemble.py - - new test checks that NotImplementedError is raised when _single_frame - or _single_universe are not defined, to implement correct method -* source and html docs updated for EnsembleAnalysis - to reflect changes and describe proper usage - -2022-??-?? 0.8.1 -cadeduckworth, orbeckst, VOD555 - -Changes - + * added support for Python 3.10 * dropped testing on Python 3.6 diff --git a/doc/sphinx/source/analysis/ensemble_analysis.txt b/doc/sphinx/source/analysis/ensemble_analysis.txt index 65fc2c65..693b2c32 100644 --- a/doc/sphinx/source/analysis/ensemble_analysis.txt +++ b/doc/sphinx/source/analysis/ensemble_analysis.txt @@ -19,9 +19,10 @@ iterates over the systems in the ensemble or the frames in the systems. It sets universes or universe frames allowing for analysis to be run on either whole systems or the frames of those systems. This allows for users to easily run analyses on MDPOW simulations. -NotImplementedError will detect whether _single_universe or _single_frame -should be implemented, based on which is defined in the EnsembleAnalysisClass. -Only one of the two methods should be defined for an EnsembleAnalysisClass. +:exc:`NotImplementedError` will detect whether :meth:`~EnsembleAnalysis._single_universe` +or :meth:`~EnsembleAnalysis._single_frame` should be implemented, based on which +is defined in the :class:`~mdpow.analysis.ensemble.EnsembleAnalysis`. Only one of the +two methods should be defined for an :class:`~mdpow.analysis.ensemble.EnsembleAnalysis`. For verbose functionality, the analysis may show two iteration bars, where only one of which will actually be iterated, while the other will load to completion instantaneously, showing the system that is being worked on. diff --git a/mdpow/analysis/ensemble.py b/mdpow/analysis/ensemble.py index 7bd9071a..5d88880f 100644 --- a/mdpow/analysis/ensemble.py +++ b/mdpow/analysis/ensemble.py @@ -466,23 +466,27 @@ def _setup_frames(self, trajectory): def _single_universe(self): """Calculations on a single Universe object. - Run on each universe in the ensemble during when - self.run in called. + Run on each Universe in the Ensemble during when + :meth:`run` in called. - NotImplementedError will detect whether _single_universe - or _single_frame should be implemented, based on which - is defined in the EnsembleAnalysisClass. + :exc:`NotImplementedError` will detect whether + :meth:`~EnsembleAnalysis._single_universe` + or :meth:`~EnsembleAnalysis._single_frame` + should be implemented, based on which is defined + in the :class:`~mdpow.analysis.ensemble.EnsembleAnalysis`. """ raise NotImplementedError def _single_frame(self): """Calculate data from a single frame of trajectory. - Called on each frame for universes in the Ensemble. + Called on each frame for Universes in the Ensemble. - NotImplementedError will detect whether _single_universe - or _single_frame should be implemented, based on which - is defined in the EnsembleAnalysisClass. + :exc:`NotImplementedError` will detect whether + :meth:`~EnsembleAnalysis._single_universe` + or :meth:`~EnsembleAnalysis._single_frame` + should be implemented, based on which is defined + in the :class:`~mdpow.analysis.ensemble.EnsembleAnalysis`. """ raise NotImplementedError @@ -513,12 +517,16 @@ def _conclude_ensemble(self): pass # pragma: no cover def run(self, start=None, stop=None, step=None): - """Runs _single_universe on each system or _single_frame + """Runs :meth:`~EnsembleAnalysis._single_universe` + on each system or :meth:`~EnsembleAnalysis._single_frame` on each frame in the system. - First iterates through keys of ensemble, then runs _setup_system - which defines the system and trajectory. Then iterates over each - system universe or trajectory frames of each universe as defined. + First iterates through keys of ensemble, then runs + :meth:`~EnsembleAnalysis._setup_system`which defines + the system and trajectory. Then iterates over each + system universe or trajectory frames of each universe + as defined by :meth:`~EnsembleAnalysis._single_universe` + or :meth:`~EnsembleAnalysis._single_frame`. """ logger.info("Setting up systems") self._prepare_ensemble() From 40381aa4941c6fc3c98752a41bc3dff7793079ac Mon Sep 17 00:00:00 2001 From: cadeduckworth Date: Thu, 8 Dec 2022 15:34:55 -0700 Subject: [PATCH 23/24] changes to docs for sphinx markup --- mdpow/analysis/ensemble.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/mdpow/analysis/ensemble.py b/mdpow/analysis/ensemble.py index 5d88880f..0fefaca3 100644 --- a/mdpow/analysis/ensemble.py +++ b/mdpow/analysis/ensemble.py @@ -464,10 +464,11 @@ def _setup_frames(self, trajectory): self.times = np.zeros(self.n_frames) def _single_universe(self): - """Calculations on a single Universe object. + """Calculations on a single :class:`MDAnalysis.Universe ` object. - Run on each Universe in the Ensemble during when - :meth:`run` in called. + Run on each :class:`MDAnalysis.Universe ` + in the :class:`~mdpow.analysis.ensemble.Ensemble` + during when :meth:`run` in called. :exc:`NotImplementedError` will detect whether :meth:`~EnsembleAnalysis._single_universe` @@ -480,7 +481,9 @@ def _single_universe(self): def _single_frame(self): """Calculate data from a single frame of trajectory. - Called on each frame for Universes in the Ensemble. + Called on each frame for each + :class:`MDAnalysis.Universe ` + in the :class:`~mdpow.analysis.ensemble.Ensemble`. :exc:`NotImplementedError` will detect whether :meth:`~EnsembleAnalysis._single_universe` From 8870e395246fae7aa6d9c07bb42fbd6ade018776 Mon Sep 17 00:00:00 2001 From: cadeduckworth Date: Sat, 10 Dec 2022 18:32:10 -0700 Subject: [PATCH 24/24] whitespace and docstring formatting --- CHANGES | 1 - mdpow/analysis/dihedral.py | 4 ++-- mdpow/analysis/ensemble.py | 7 ++++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/CHANGES b/CHANGES index 84f83784..1b9f55e7 100644 --- a/CHANGES +++ b/CHANGES @@ -18,7 +18,6 @@ Changes and use _single_frame OR _single_universe (#216) * _prepare_universe and _conclude_universe removed from EnsembleAnalysis.run() method, no longer needed (per comments, #199) - * added support for Python 3.10 * dropped testing on Python 3.6 diff --git a/mdpow/analysis/dihedral.py b/mdpow/analysis/dihedral.py index bde4cfcc..69b3c0a9 100644 --- a/mdpow/analysis/dihedral.py +++ b/mdpow/analysis/dihedral.py @@ -78,8 +78,8 @@ def check_dihedral_inputs(selections): for group in selections: for k in group.keys(): if len(group[k]) != 4: - msg = '''Dihedral calculations require AtomGroups with - only 4 atoms, %s selected''' % len(group) + msg = ("Dihedral calculations require AtomGroups with " + f"only 4 atoms, {len(group)} selected") logger.error(msg) raise SelectionError(msg) diff --git a/mdpow/analysis/ensemble.py b/mdpow/analysis/ensemble.py index 0fefaca3..d4ba40a6 100644 --- a/mdpow/analysis/ensemble.py +++ b/mdpow/analysis/ensemble.py @@ -464,12 +464,13 @@ def _setup_frames(self, trajectory): self.times = np.zeros(self.n_frames) def _single_universe(self): - """Calculations on a single :class:`MDAnalysis.Universe ` object. + """Calculations on a single :class:`MDAnalysis.Universe + ` object. - Run on each :class:`MDAnalysis.Universe ` + Run on each :class:`MDAnalysis.Universe + ` in the :class:`~mdpow.analysis.ensemble.Ensemble` during when :meth:`run` in called. - :exc:`NotImplementedError` will detect whether :meth:`~EnsembleAnalysis._single_universe` or :meth:`~EnsembleAnalysis._single_frame`