Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add timeseries for all readers #3890

Merged
merged 21 commits into from
Nov 11, 2022
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions package/MDAnalysis/coordinates/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -980,6 +980,77 @@ def __repr__(self):
nframes=self.n_frames,
natoms=self.n_atoms
))

def timeseries(self, asel: Optional['AtomGroup']=None,
start: Optional[int]=None, stop: Optional[int]=None,
step: Optional[int]=None,
order: Optional[str]='fac') -> np.ndarray:
"""Return a subset of coordinate data for an AtomGroup
Parameters
----------
asel : AtomGroup (optional)
hmacdope marked this conversation as resolved.
Show resolved Hide resolved
The :class:`~MDAnalysis.core.groups.AtomGroup` to read the
coordinates from. Defaults to ``None``, in which case the full set of
coordinate data is returned.
start : int (optional)
Begin reading the trajectory at frame index `start` (where 0 is the index
of the first frame in the trajectory); the default ``None`` starts
at the beginning.
stop : int (optional)
End reading the trajectory at frame index `stop`-1, i.e, `stop` is excluded.
The trajectory is read to the end with the default ``None``.
step : int (optional)
Step size for reading; the default ``None`` is equivalent to 1 and means to
read every frame.
order : str (optional)
the order/shape of the return data array, corresponding
to (a)tom, (f)rame, (c)oordinates all six combinations
of 'a', 'f', 'c' are allowed ie "fac" - return array
where the shape is (frame, number of atoms,
coordinates)

See Also
--------
:class:`MDAnalysis.coordinates.memory`


.. versionadded:: 2.4.0
"""
start, stop, step = self.check_slice_indices(start, stop, step)
nframes = len(range(start, stop, step))

if asel is not None:
if len(asel) == 0:
raise NoDataError(
hmacdope marked this conversation as resolved.
Show resolved Hide resolved
"Timeseries requires at least one atom to analyze")
atom_numbers = asel.indices
natoms = len(atom_numbers)
hmacdope marked this conversation as resolved.
Show resolved Hide resolved
else:
natoms = self.n_atoms
atom_numbers = np.arange(natoms)

# allocate output array in 'fac' (_default_timeseries_order) order
hmacdope marked this conversation as resolved.
Show resolved Hide resolved
coordinates = np.empty((nframes, natoms, 3), dtype=np.float32)
for i, ts in enumerate(self[start:stop:step]):
coordinates[i, :] = ts.positions[atom_numbers]

# switch axes around
default_order = 'fac'
if order != default_order:
try:
newidx = [default_order.index(i) for i in order]
except ValueError:
raise ValueError(f"Unrecognized order key in {order}, "
"must be permutation of 'fac'")

try:
coordinates = np.moveaxis(coordinates, newidx, [0, 1, 2])
except ValueError:
errmsg = ("Repeated or missing keys passed to argument "
f"`order`: {order}, each key must be used once")
raise ValueError(errmsg)
return coordinates

hmacdope marked this conversation as resolved.
Show resolved Hide resolved

# TODO: Change order of aux_spec and auxdata for 3.0 release, cf. Issue #3811
def add_auxiliary(self,
Expand Down
27 changes: 27 additions & 0 deletions testsuite/MDAnalysisTests/coordinates/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,33 @@ def test_pickle_reader(self, reader):
assert_equal(reader.ts, reader_p.ts,
"Timestep is changed after pickling")


hmacdope marked this conversation as resolved.
Show resolved Hide resolved
@pytest.mark.parametrize('order', ('fac', 'fca', 'afc', 'acf', 'caf', 'cfa'))
def test_timeseries_shape(self, reader, order):
timeseries = reader.timeseries(order=order)
a_index = order.index('a')
f_index = order.index('f')
c_index = order.index('c')
assert(timeseries.shape[a_index] == reader.n_atoms)
assert(timeseries.shape[f_index] == len(reader))
assert(timeseries.shape[c_index] == 3)

@pytest.mark.parametrize('slice', ([0,2,1], [0,10,2], [0,10,3]))
def test_timeseries_values(self, reader, slice):
ts_positions = []
if isinstance(reader, mda.coordinates.memory.MemoryReader):
pytest.xfail("MemoryReader uses deprecated stop inclusive"
hmacdope marked this conversation as resolved.
Show resolved Hide resolved
" indexing, see Issue #3893")
if slice[1] > len(reader):
pytest.skip("too few frames in reader")
for i in range(slice[0], slice[1], slice[2]):
ts = reader[i]
ts_positions.append(ts.positions.copy())
positions = np.asarray(ts_positions)
timeseries = reader.timeseries(start=slice[0], stop=slice[1],
step=slice[2], order='fac')
assert_allclose(timeseries, positions)

hmacdope marked this conversation as resolved.
Show resolved Hide resolved

class MultiframeReaderTest(BaseReaderTest):
def test_last_frame(self, ref, reader):
Expand Down
9 changes: 9 additions & 0 deletions testsuite/MDAnalysisTests/coordinates/test_reader_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,15 @@ def test_raises_StopIteration(self, reader):
with pytest.raises(StopIteration):
next(reader)

@pytest.mark.parametrize('order', ['turnip', 'abc'])
def test_timeseries_raises_unknown_order_key(self, reader, order):
with pytest.raises(ValueError, match="Unrecognized order key"):
reader.timeseries(order=order)

@pytest.mark.parametrize('order', ['faac', 'affc', 'afcc', ''])
def test_timeseries_raises_incorrect_order_key(self, reader, order):
with pytest.raises(ValueError, match="Repeated or missing keys"):
reader.timeseries(order=order)

class _Multi(_TestReader):
n_frames = 10
Expand Down