MDPOW
MDPOW copied to clipboard
Add ensemble_wrapper decorator function
ensemble_wrapper decorator
To simplify the process of extending AnalysisBase sub-classes to Ensembles I wrote a class decorator function.
Example use:
class BaseTest(AnalysisBase):
def __init__(self, system: mda.Universe):
super(BaseTest, self).__init__(system.trajectory)
self.system = system
def _prepare(self):
self._res_arr = []
def _single_frame(self):
self._res_arr.append(len(self.system.select_atoms('not resname SOL')))
assert self._res_arr[-1] == 42
def _conclude(self):
self.results = self._res_arr
@ensemble_wrapper
class EnsembleBaseTest(BaseTest):
pass
Sim = Ensemble(dirname=self.tmpdir.name, solvents=['water'])
SolvCount = EnsembleBaseTest(Sim).run(stop=10)
It just adds new __init__
, _prepare_ensemble
. _conclude_system
, _conclude_ensemble
, and run
methods that run the base class and collect the results. The prepare and concluded are defined so that the user can modify the data processing of results.
TODO
- [ ] Write more robust tests
Codecov Report
Merging #199 (9afc298) into develop (2088ae4) will increase coverage by
0.26%
. The diff coverage is100.00%
.
@@ Coverage Diff @@
## develop #199 +/- ##
===========================================
+ Coverage 78.93% 79.20% +0.26%
===========================================
Files 12 12
Lines 1709 1731 +22
Branches 254 256 +2
===========================================
+ Hits 1349 1371 +22
Misses 276 276
Partials 84 84
Impacted Files | Coverage Δ | |
---|---|---|
mdpow/analysis/ensemble.py | 96.98% <100.00%> (+0.31%) |
:arrow_up: |
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 2088ae4...9afc298. Read the comment docs.
In order to re-use MDAnalysis analysis classes, I suggest to change the EnsembleAnalysis run method so that it either just runs _single_Universe()
or does the per-frame loop. The idea is that you can quickly re-use standard analysis classes by putting them into _single_Universe()
def _single_universe(self):
analysis = MDAnalysis.analysis.foo.BarAnalysis(....)
analysis.run(**kwargs)
# store results right away
self.result_dict[self._key[0]][self._key[1]][self._key[2]] = analysis.result
(We don't even need _conclude_universe()
.)
What do you think @ALescoulie ? Would this simplify things sufficiently without breaking any of the existing code?
cc @cadeduckworth
I think that could work, I could also do a runtime check to see if _single_frame
is implemented, then call a different run function in each case. I could also write a function that returns a new EnsembleAnalysis
subclass based on an MDAnalysis AnalysisBase
subclass.
Maybe have not implemented methods raise NotImplementedError and try/except?
not sure if this is a good pattern…
Thats what I was thinking then at runtime calling either one of two different run methods, either one that works on each frame, or one that works on each Universe
. It would be more maintainable than a decorator or function, but is a bit more complicated.
I am not sure how much work will get done on this PR. I converted it to draft so that we have something to look at when we think about extending EnsembleAnalysis.