Skip to content

Commit

Permalink
MAINT: in1d -> isin (#4255)
Browse files Browse the repository at this point in the history
* MAINT: in1d -> isin

* replace deprecated `in1d` with `isin` calls per
numpy/numpy#24445, as part
of effort to remain NumPy `2.0` compliant

* the testsuite seems happy locally--if there are
any cases where `in1d` was potentially receiving
arrays with `ndim > 1`, we may want to add `ravel()`
to the outputs in these replacements just to be safe,
but I've assumed the testsuite has us covered

* for now, I've intentionally not adjusted the
docstring of our Cython `_in2d`, which refers
to `in1d`; seems less critical for now

* DOC: PR 4255 revisions

* add an appropriate `CHANGELOG` entry
per reviewer request

* Update package/CHANGELOG

---------

Co-authored-by: Oliver Beckstein <orbeckst@gmail.com>
  • Loading branch information
tylerjereddy and orbeckst authored Aug 27, 2023
1 parent 8923c4a commit d5a30e9
Show file tree
Hide file tree
Showing 8 changed files with 42 additions and 40 deletions.
4 changes: 3 additions & 1 deletion package/CHANGELOG
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ The rules for this file:
* release numbers follow "Semantic Versioning" http://semver.org

------------------------------------------------------------------------------
??/??/?? IAlibay, hmacdope, pillose, jaclark5
??/??/?? IAlibay, hmacdope, pillose, jaclark5, tylerjereddy

* 2.7.0

Expand All @@ -23,6 +23,8 @@ Fixes
Enhancements

Changes
* NumPy `in1d` replaced with `isin` in preparation for NumPy
`2.0` (PR #4255)
* Update documentation for SurvivalProbabilty to be more clear
(Issue #4247)
* Cython DEF statments have been replaced with compile time integer constants
Expand Down
2 changes: 1 addition & 1 deletion package/MDAnalysis/analysis/align.py
Original file line number Diff line number Diff line change
Expand Up @@ -1437,7 +1437,7 @@ def get_atoms_byres(g, match_mask=np.logical_not(mismatch_mask)):
good = ag.residues.resids[match_mask] # resid for each residue
resids = ag.resids # resid for each atom
# boolean array for all matching atoms
ix_good = np.in1d(resids, good)
ix_good = np.isin(resids, good)
return ag[ix_good]

_ag1 = get_atoms_byres(ag1)
Expand Down
8 changes: 4 additions & 4 deletions package/MDAnalysis/analysis/hydrogenbonds/hbond_analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -680,16 +680,16 @@ def _filter_atoms(self, donors, acceptors):
# Find donors in G1 and acceptors in G2
mask[
np.logical_and(
np.in1d(donors.indices, group1.indices),
np.in1d(acceptors.indices, group2.indices)
np.isin(donors.indices, group1.indices),
np.isin(acceptors.indices, group2.indices)
)
] = True

# Find acceptors in G1 and donors in G2
mask[
np.logical_and(
np.in1d(acceptors.indices, group1.indices),
np.in1d(donors.indices, group2.indices)
np.isin(acceptors.indices, group1.indices),
np.isin(donors.indices, group2.indices)
)
] = True

Expand Down
4 changes: 2 additions & 2 deletions package/MDAnalysis/core/groups.py
Original file line number Diff line number Diff line change
Expand Up @@ -422,7 +422,7 @@ def get_connections(self, typename, outside=True):
indices = self.atoms.ix_array
except AttributeError: # if self is an Atom
indices = self.ix_array
seen = [np.in1d(col, indices) for col in ugroup._bix.T]
seen = [np.isin(col, indices) for col in ugroup._bix.T]
mask = func(seen, axis=0)
return ugroup[mask]

Expand Down Expand Up @@ -2154,7 +2154,7 @@ def subtract(self, other):
.. versionadded:: 0.16
"""
o_ix = other.ix_array
in_other = np.in1d(self.ix, o_ix) # mask of in self.ix AND other
in_other = np.isin(self.ix, o_ix) # mask of in self.ix AND other
return self[~in_other] # ie inverse of previous mask

@_only_same_level
Expand Down
46 changes: 23 additions & 23 deletions package/MDAnalysis/core/selection.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ def _apply(self, group):
lsel = self.lsel.apply(group)

# Mask which lsel indices appear in rsel
mask = np.in1d(rsel.indices, lsel.indices)
mask = np.isin(rsel.indices, lsel.indices)
# and mask rsel according to that
return rsel[mask]

Expand Down Expand Up @@ -267,7 +267,7 @@ class NotSelection(UnarySelection):

def _apply(self, group):
notsel = self.sel.apply(group)
return group[~np.in1d(group.indices, notsel.indices)]
return group[~np.isin(group.indices, notsel.indices)]


class GlobalSelection(UnarySelection):
Expand All @@ -292,7 +292,7 @@ class ByResSelection(UnarySelection):
def _apply(self, group):
res = self.sel.apply(group)
unique_res = unique_int_1d(res.resindices)
mask = np.in1d(group.resindices, unique_res)
mask = np.isin(group.resindices, unique_res)

return group[mask]

Expand All @@ -312,7 +312,7 @@ def _apply(self, group):
indices = []
sel = self.sel.apply(group)
# All atoms in group that aren't in sel
sys = group[~np.in1d(group.indices, sel.indices)]
sys = group[~np.isin(group.indices, sel.indices)]

if not sys or not sel:
return sys[[]]
Expand Down Expand Up @@ -372,7 +372,7 @@ def _apply(self, group):
indices = []
sel = self.sel.apply(group)
# All atoms in group that aren't in sel
sys = group[~np.in1d(group.indices, sel.indices)]
sys = group[~np.isin(group.indices, sel.indices)]

if not sys or not sel:
return sys[[]]
Expand All @@ -389,7 +389,7 @@ def _apply(self, group):
sys_ind_outer = np.sort(np.unique(pairs_outer[:,1]))
if pairs_inner.size > 0:
sys_ind_inner = np.sort(np.unique(pairs_inner[:,1]))
indices = sys_ind_outer[~np.in1d(sys_ind_outer, sys_ind_inner)]
indices = sys_ind_outer[~np.isin(sys_ind_outer, sys_ind_inner)]
else:
indices = sys_ind_outer

Expand Down Expand Up @@ -577,9 +577,9 @@ def _apply(self, group):

idx = []
# left side
idx.append(bix[:, 0][np.in1d(bix[:, 1], grpidx)])
idx.append(bix[:, 0][np.isin(bix[:, 1], grpidx)])
# right side
idx.append(bix[:, 1][np.in1d(bix[:, 0], grpidx)])
idx.append(bix[:, 1][np.isin(bix[:, 0], grpidx)])

idx = np.union1d(*idx)

Expand All @@ -603,7 +603,7 @@ def __init__(self, parser, tokens):
raise ValueError(errmsg) from None

def _apply(self, group):
mask = np.in1d(group.indices, self.grp.indices)
mask = np.isin(group.indices, self.grp.indices)
return group[mask]


Expand Down Expand Up @@ -657,7 +657,7 @@ def _apply(self, group):
# atomname indices for members of this group
nmidx = nmattr.nmidx[getattr(group, self.level)]

return group[np.in1d(nmidx, matches)]
return group[np.isin(nmidx, matches)]


class AromaticSelection(Selection):
Expand Down Expand Up @@ -743,7 +743,7 @@ def _apply(self, group):
# flatten all matches and remove duplicated indices
indices = np.unique([idx for match in matches for idx in match])
# create boolean mask for atoms based on index
mask = np.in1d(range(group.n_atoms), indices)
mask = np.isin(range(group.n_atoms), indices)
return group[mask]


Expand Down Expand Up @@ -1053,7 +1053,7 @@ def _apply(self, group):
# index of each atom's resname
nmidx = resname_attr.nmidx[group.resindices]
# intersect atom's resname index and matches to prot_res
return group[np.in1d(nmidx, matches)]
return group[np.isin(nmidx, matches)]


class NucleicSelection(Selection):
Expand Down Expand Up @@ -1089,7 +1089,7 @@ def _apply(self, group):

matches = [ix for (nm, ix) in resnames.namedict.items()
if nm in self.nucl_res]
mask = np.in1d(nmidx, matches)
mask = np.isin(nmidx, matches)

return group[mask]

Expand All @@ -1116,13 +1116,13 @@ def _apply(self, group):
name_matches = [ix for (nm, ix) in atomnames.namedict.items()
if nm in self.bb_atoms]
nmidx = atomnames.nmidx[group.ix]
group = group[np.in1d(nmidx, name_matches)]
group = group[np.isin(nmidx, name_matches)]

# filter by resnames
resname_matches = [ix for (nm, ix) in resnames.namedict.items()
if nm in self.prot_res]
nmidx = resnames.nmidx[group.resindices]
group = group[np.in1d(nmidx, resname_matches)]
group = group[np.isin(nmidx, resname_matches)]

return group.unique

Expand All @@ -1149,13 +1149,13 @@ def _apply(self, group):
name_matches = [ix for (nm, ix) in atomnames.namedict.items()
if nm in self.bb_atoms]
nmidx = atomnames.nmidx[group.ix]
group = group[np.in1d(nmidx, name_matches)]
group = group[np.isin(nmidx, name_matches)]

# filter by resnames
resname_matches = [ix for (nm, ix) in resnames.namedict.items()
if nm in self.nucl_res]
nmidx = resnames.nmidx[group.resindices]
group = group[np.in1d(nmidx, resname_matches)]
group = group[np.isin(nmidx, resname_matches)]

return group.unique

Expand Down Expand Up @@ -1187,13 +1187,13 @@ def _apply(self, group):
name_matches = [ix for (nm, ix) in atomnames.namedict.items()
if nm in self.base_atoms]
nmidx = atomnames.nmidx[group.ix]
group = group[np.in1d(nmidx, name_matches)]
group = group[np.isin(nmidx, name_matches)]

# filter by resnames
resname_matches = [ix for (nm, ix) in resnames.namedict.items()
if nm in self.nucl_res]
nmidx = resnames.nmidx[group.resindices]
group = group[np.in1d(nmidx, resname_matches)]
group = group[np.isin(nmidx, resname_matches)]

return group.unique

Expand All @@ -1217,13 +1217,13 @@ def _apply(self, group):
name_matches = [ix for (nm, ix) in atomnames.namedict.items()
if nm in self.sug_atoms]
nmidx = atomnames.nmidx[group.ix]
group = group[np.in1d(nmidx, name_matches)]
group = group[np.isin(nmidx, name_matches)]

# filter by resnames
resname_matches = [ix for (nm, ix) in resnames.namedict.items()
if nm in self.nucl_res]
nmidx = resnames.nmidx[group.resindices]
group = group[np.in1d(nmidx, resname_matches)]
group = group[np.isin(nmidx, resname_matches)]

return group.unique

Expand Down Expand Up @@ -1408,7 +1408,7 @@ def _apply(self, group):
# indices are same as fragment(s) indices
allfrags = functools.reduce(lambda x, y: x + y, res.fragments)

mask = np.in1d(group.indices, allfrags.indices)
mask = np.isin(group.indices, allfrags.indices)
return group[mask]
# [xyz] must come before self.prop_trans lookups too!
try:
Expand All @@ -1419,7 +1419,7 @@ def _apply(self, group):
# KeyError at this point is impossible!
attrname = self.prop_trans[self.prop]
vals = getattr(res, attrname)
mask = np.in1d(getattr(group, attrname), vals)
mask = np.isin(getattr(group, attrname), vals)

return group[mask]
else:
Expand Down
6 changes: 3 additions & 3 deletions package/MDAnalysis/core/topologyattrs.py
Original file line number Diff line number Diff line change
Expand Up @@ -1234,7 +1234,7 @@ def chi1_selection(residue, n_name='N', ca_name='CA', cb_name='CB',
"""
names = [n_name, ca_name, cb_name, cg_name]
atnames = residue.atoms.names
ags = [residue.atoms[np.in1d(atnames, n.split())] for n in names]
ags = [residue.atoms[np.isin(atnames, n.split())] for n in names]
if any(len(ag) != 1 for ag in ags):
return None
return sum(ags)
Expand Down Expand Up @@ -1267,13 +1267,13 @@ def chi1_selections(residues, n_name='N', ca_name='CA', cb_name='CB',
"""
results = np.array([None]*len(residues))
names = [n_name, ca_name, cb_name, cg_name]
keep = [all(sum(np.in1d(r.atoms.names, n.split())) == 1
keep = [all(sum(np.isin(r.atoms.names, n.split())) == 1
for n in names) for r in residues]
keepix = np.where(keep)[0]
residues = residues[keep]

atnames = residues.atoms.names
ags = [residues.atoms[np.in1d(atnames, n.split())] for n in names]
ags = [residues.atoms[np.isin(atnames, n.split())] for n in names]
results[keepix] = [sum(atoms) for atoms in zip(*ags)]
return list(results)

Expand Down
2 changes: 1 addition & 1 deletion package/MDAnalysis/core/topologyobjects.py
Original file line number Diff line number Diff line change
Expand Up @@ -694,7 +694,7 @@ def atomgroup_intersection(self, ag, **kwargs):
atom_idx = ag.indices
# Create a list of boolean arrays,
# each representing a column of bond indices.
seen = [np.in1d(col, atom_idx) for col in self._bix.T]
seen = [np.isin(col, atom_idx) for col in self._bix.T]

# Create final boolean mask by summing across rows
mask = func(seen, axis=0)
Expand Down
10 changes: 5 additions & 5 deletions testsuite/MDAnalysisTests/core/test_groups.py
Original file line number Diff line number Diff line change
Expand Up @@ -1646,7 +1646,7 @@ def test_connection_from_atoms_not_outside(self, tpr, typename,
cxns = ag.get_connections(typename, outside=False)
assert len(cxns) == n_atoms
indices = np.ravel(cxns.to_indices())
assert np.all(np.in1d(indices, ag.indices))
assert np.all(np.isin(indices, ag.indices))

@pytest.mark.parametrize("typename, n_atoms", [
("bonds", 13),
Expand All @@ -1658,7 +1658,7 @@ def test_connection_from_atoms_outside(self, tpr, typename, n_atoms):
cxns = ag.get_connections(typename, outside=True)
assert len(cxns) == n_atoms
indices = np.ravel(cxns.to_indices())
assert not np.all(np.in1d(indices, ag.indices))
assert not np.all(np.isin(indices, ag.indices))

def test_invalid_connection_error(self, tpr):
with pytest.raises(AttributeError, match="does not contain"):
Expand Down Expand Up @@ -1708,7 +1708,7 @@ def test_connection_from_residues_not_outside(self, tpr, typename,
cxns = ag.get_connections(typename, outside=False)
assert len(cxns) == n_atoms
indices = np.ravel(cxns.to_indices())
assert np.all(np.in1d(indices, ag.atoms.indices))
assert np.all(np.isin(indices, ag.atoms.indices))

@pytest.mark.parametrize("typename, n_atoms", [
("bonds", 158),
Expand All @@ -1720,7 +1720,7 @@ def test_connection_from_residues_outside(self, tpr, typename, n_atoms):
cxns = ag.get_connections(typename, outside=True)
assert len(cxns) == n_atoms
indices = np.ravel(cxns.to_indices())
assert not np.all(np.in1d(indices, ag.atoms.indices))
assert not np.all(np.isin(indices, ag.atoms.indices))

def test_invalid_connection_error(self, tpr):
with pytest.raises(AttributeError, match="does not contain"):
Expand All @@ -1746,7 +1746,7 @@ def test_topologygroup_gets_connections_inside(tpr, typename, n_inside):
cxns = getattr(ag, typename)
assert len(cxns) == n_inside
indices = np.ravel(cxns.to_indices())
assert np.all(np.in1d(indices, ag.indices))
assert np.all(np.isin(indices, ag.indices))


@pytest.mark.parametrize("typename, n_outside", [
Expand Down

0 comments on commit d5a30e9

Please sign in to comment.