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 relprop for atom selection and corresponding UT #4841

Open
wants to merge 7 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
10 changes: 10 additions & 0 deletions package/MDAnalysis/core/groups.py
Original file line number Diff line number Diff line change
Expand Up @@ -3254,6 +3254,16 @@ def select_atoms(self, sel, *othersel, periodic=True, rtol=1e-05,
For example, ``prop z >= 5.0`` selects all atoms with z
coordinate greater than 5.0; ``prop abs z <= 5.0`` selects all
atoms within -5.0 <= z <= 5.0.
relprop [abs] *property* *operator* *value* *selection*
selects atoms based on position relative to the center of
geometry (COG) of a given selection, using *property*
**x**, **y**, or **z** coordinate. Supports the **abs**
keyword (for absolute value) and the following
*operators*: **<, >, <=, >=, ==, !=**.
For example, ``relprop z >= 5.0 protein`` selects all atoms
with z coordinate greater than 5.0 relative to the COG
of protein; ``relprop abs z <= 5.0 protein`` selects all
atoms within -5.0 <= z <= 5.0 relative to the COG of protein.
sphzone *radius* *selection*
Selects all atoms that are within *radius* of the center of
geometry of *selection*
Expand Down
49 changes: 49 additions & 0 deletions package/MDAnalysis/core/selection.py
Original file line number Diff line number Diff line change
Expand Up @@ -1384,6 +1384,55 @@
return group[mask]


class RelPropertySelection(PropertySelection):
"""Some of the possible properties:
x, y, z,

ChiahsinChu marked this conversation as resolved.
Show resolved Hide resolved
.. versionadded:: 2.9.0
"""

token = "relprop"
precedence = 1

def __init__(self, parser, tokens):
super().__init__(parser, tokens)
self.sel = parser.parse_expression(self.precedence)
self.periodic = parser.periodic

def _apply(self, group):
try:
values = getattr(group, self.props[self.prop])
except KeyError:
errmsg = f"Expected one of {list(self.props.keys())}"
raise SelectionError(errmsg) from None
except NoDataError:
attr = self.props[self.prop]
errmsg = f"This Universe does not contain {attr} information"
raise SelectionError(errmsg) from None
ChiahsinChu marked this conversation as resolved.
Show resolved Hide resolved

try:
col = {"x": 0, "y": 1, "z": 2}[self.prop]
except KeyError:
errmsg = f"Expected one of x y z for property, got {self.prop}"
raise SelectionError(errmsg) from None
else:
sel = self.sel.apply(group)
ref_value = sel.center_of_geometry().reshape(3).astype(np.float32)
box = group.dimensions if self.periodic else None
if box is not None:
values = distances.minimize_vectors(

Check warning on line 1423 in package/MDAnalysis/core/selection.py

View check run for this annotation

Codecov / codecov/patch

package/MDAnalysis/core/selection.py#L1423

Added line #L1423 was not covered by tests
values - ref_value[None, :], box=box
)[:, col]
else:
values = values[:, col] - ref_value[col]

if self.absolute:
values = np.abs(values)
mask = self.operator(values, self.value)

return group[mask]


class SameSelection(Selection):
"""
Selects all atoms that have the same subkeyword value as any atom in selection
Expand Down
23 changes: 23 additions & 0 deletions testsuite/MDAnalysisTests/core/test_atomselections.py
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,17 @@ def test_prop(self, universe):
assert_equal(len(sel), 3194)
assert_equal(len(sel2), 2001)

def test_relprop(self, universe):
sel1 = universe.select_atoms("relprop z <= 1 index 0")
sel2 = universe.select_atoms("relprop abs z <= 1 index 0")

positions = universe.trajectory[0].positions
ref = positions[0, 2]
mask_1 = (positions[:, 2] - ref) <= 1
assert_equal(len(sel1), np.count_nonzero(mask_1))
mask_2 = np.abs(positions[:, 2] - ref) <= 1
assert_equal(len(sel2), np.count_nonzero(mask_2))

def test_bynum(self, universe):
"Tests the bynum selection, also from AtomGroup instances (Issue 275)"
sel = universe.select_atoms('bynum 5')
Expand Down Expand Up @@ -1110,6 +1121,18 @@ def test_invalid_prop_selection(self, universe):
with pytest.raises(SelectionError, match="Expected one of"):
universe.select_atoms("prop parsnip < 2")

def test_invalid_relprop_selection(self, universe):
with pytest.raises(SelectionError, match="Expected one of"):
universe.select_atoms("relprop parsnip < 2 index 0")
with pytest.raises(SelectionError, match="Unknown selection token"):
universe.select_atoms("relprop z < 2")
with pytest.raises(SelectionError, match="Expected one of"):
universe.select_atoms("relprop resid < 2 index 0")
with pytest.raises(
SelectionError, match="This Universe does not contain"
):
universe.select_atoms("relprop z <= 1 index 0")


def test_segid_and_resid():
u = make_Universe(('segids', 'resids'))
Expand Down
Loading