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

Remove ContactMap (allowing rename ContactFrequency => ContactMap) #88

Merged
merged 5 commits into from
Sep 23, 2020
Merged
Show file tree
Hide file tree
Changes from 4 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
2 changes: 1 addition & 1 deletion ci/conda-recipe/meta.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package:
name: contact_map
# add ".dev0" for unreleased versions
version: "0.6.1.dev0"
version: "0.7.0.dev0"

source:
path: ../../
Expand Down
78 changes: 23 additions & 55 deletions contact_map/contact_map.py
Original file line number Diff line number Diff line change
Expand Up @@ -600,61 +600,12 @@ def residue_contacts(self):
n_res, n_res)


class ContactMap(ContactObject):
"""
Contact map (atomic and residue) for a single frame.

.. deprecated:: 0.6.0
``ContactMap`` will be removed in Contact Map Explorer 0.7.0 because
it is redundant with ``ContactFrequency``. For more, see
https://github.com/dwhswenson/contact_map/issues/82.

"""
# Default for use_atom_slice, None tries to be smart
_class_use_atom_slice = None

_deprecation_message=(
"The ContactMap class will be removed in Contact Map Explorer 0.7. "
+ "Use ContactFrequency instead. For more, see: "
+ "https://github.com/dwhswenson/contact_map/issues/82."
)

def __init__(self, frame, query=None, haystack=None, cutoff=0.45,
n_neighbors_ignored=2):
warnings.warn(self._deprecation_message, FutureWarning)
self._frame = frame # TODO: remove this?
super(ContactMap, self).__init__(frame.topology, query, haystack,
cutoff, n_neighbors_ignored)

contact_maps = self.contact_map(frame, 0,
self.indexer.residue_query_atom_idxs,
self._residue_ignore_atom_idxs)
(atom_contacts, self._residue_contacts) = contact_maps
self._atom_contacts = self.indexer.convert_atom_contacts(atom_contacts)

@classmethod
def from_dict(cls, dct):
warnings.warn(cls._deprecation_message, FutureWarning)
return super(ContactMap, cls).from_dict(dct)

# don't need to add deprecation in from_json because it uses from_dict

@classmethod
def from_file(cls, filename):
warnings.warn(cls._deprecation_message, FutureWarning)
return super(ContactMap, cls).from_file(filename)


def __hash__(self):
return hash((super(ContactMap, self).__hash__(),
tuple(self._atom_contacts.items()),
tuple(self._residue_contacts.items())))

def __eq__(self, other):
is_equal = (super(ContactMap, self).__eq__(other)
and self._atom_contacts == other._atom_contacts
and self._residue_contacts == other._residue_contacts)
return is_equal
CONTACT_MAP_ERROR = (
"The ContactMap class has been removed. Please use ContactFrequency."
+ " For more, see: https://github.com/dwhswenson/contact_map/issues/82"
)
def ContactMap(*args, **kwargs): # -no-cov-
raise RuntimeError(CONTACT_MAP_ERROR)


class ContactFrequency(ContactObject):
Expand Down Expand Up @@ -686,9 +637,15 @@ class ContactFrequency(ContactObject):
"""
# Default for use_atom_slice, None tries to be smart
_class_use_atom_slice = None
_pending_dep_msg = (
"ContactFrequency will be renamed to ContactMap in version 0.8. "
+ "Invoking it as ContactFrequency will be deprecated in 0.8. For "
+ "more, see https://github.com/dwhswenson/contact_map/issues/82"
)

def __init__(self, trajectory, query=None, haystack=None, cutoff=0.45,
n_neighbors_ignored=2, frames=None):
warnings.warn(self._pending_dep_msg, PendingDeprecationWarning)
if frames is None:
frames = range(len(trajectory))
self.frames = frames
Expand All @@ -703,13 +660,24 @@ def __init__(self, trajectory, query=None, haystack=None, cutoff=0.45,
def from_contacts(cls, atom_contacts, residue_contacts, n_frames,
topology, query=None, haystack=None, cutoff=0.45,
n_neighbors_ignored=2, indexer=None):
warnings.warn(cls._pending_dep_msg, PendingDeprecationWarning)
obj = super(ContactFrequency, cls).from_contacts(
atom_contacts, residue_contacts, topology, query, haystack,
cutoff, n_neighbors_ignored, indexer
)
obj._n_frames = n_frames
return obj

@classmethod
def from_dict(cls, dct):
warnings.warn(cls._pending_dep_msg, PendingDeprecationWarning)
return super(ContactFrequency, cls).from_dict(dct)

@classmethod
def from_file(cls, filename):
warnings.warn(cls._pending_dep_msg, PendingDeprecationWarning)
return super(ContactFrequency, cls).from_file(filename)

def __hash__(self):
return hash((super(ContactFrequency, self).__hash__(),
tuple(self._atom_contacts.items()),
Expand Down
96 changes: 44 additions & 52 deletions contact_map/tests/test_contact_map.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,11 +113,16 @@ def test_residue_neighborhood():
assert len(residue_neighborhood(res, n=n)) == len_n

@pytest.mark.parametrize("idx", [0, 4])
class TestContactMap(object):
class TestContactObject(object):
# note: these used to be the tests for the separate single-frame
# ContactMap class; however, it includes a lot of good unit tests for
# ContactObject
def setup(self):
self.topology = traj.topology
self.map0 = ContactMap(traj[0], cutoff=0.075, n_neighbors_ignored=0)
self.map4 = ContactMap(traj[4], cutoff=0.075, n_neighbors_ignored=0)
self.map0 = ContactFrequency(traj[0], cutoff=0.075,
n_neighbors_ignored=0)
self.map4 = ContactFrequency(traj[4], cutoff=0.075,
n_neighbors_ignored=0)
self.maps = {0: self.map0, 4: self.map4}

self.expected_atom_contacts = {
Expand Down Expand Up @@ -156,25 +161,6 @@ def test_counters(self, idx):
assert m._residue_contacts == expected
assert m.residue_contacts.counter == expected

@pytest.mark.parametrize('contactcount', [True, False])
def test_from_contacts(self, idx, contactcount):
expected = self.maps[idx]
atom_contact_list = self.expected_atom_contacts[expected]
residue_contact_list = self.expected_residue_contacts[expected]
atom_contacts = counter_of_inner_list(atom_contact_list)
residue_contacts = counter_of_inner_list(residue_contact_list)
if contactcount:
atom_contacts = ContactCount(atom_contacts, self.topology.atom,
10, 10)
residue_contacts = ContactCount(residue_contacts,
self.topology.residue, 5, 5)

cmap = ContactMap.from_contacts(atom_contacts, residue_contacts,
topology=self.topology,
cutoff=0.075,
n_neighbors_ignored=0)
_contact_object_compare(cmap, expected)

def test_to_dict(self, idx):
m = self.maps[idx]
dct = m.to_dict()
Expand All @@ -186,15 +172,15 @@ def test_to_dict(self, idx):
assert dct['n_neighbors_ignored'] == 0

def test_topology_serialization_cycle(self, idx):
m = self.maps[idx]
serialized_topology = ContactMap._serialize_topology(m.topology)
new_top = ContactMap._deserialize_topology(serialized_topology)
assert m.topology == new_top
top = self.maps[idx].topology
serialized_topology = ContactFrequency._serialize_topology(top)
new_top = ContactFrequency._deserialize_topology(serialized_topology)
assert top == new_top

def test_counter_serialization_cycle(self, idx):
m = self.maps[idx]
serialize = ContactMap._serialize_contact_counter
deserialize = ContactMap._deserialize_contact_counter
serialize = ContactFrequency._serialize_contact_counter
deserialize = ContactFrequency._deserialize_contact_counter
serialized_atom_counter = serialize(m._atom_contacts)
serialized_residue_counter = serialize(m._residue_contacts)
new_atom_counter = deserialize(serialized_atom_counter)
Expand All @@ -205,19 +191,19 @@ def test_counter_serialization_cycle(self, idx):
def test_dict_serialization_cycle(self, idx):
m = self.maps[idx]
dct = m.to_dict()
m2 = ContactMap.from_dict(dct)
m2 = ContactFrequency.from_dict(dct)
_contact_object_compare(m, m2)
assert m == m2

def test_json_serialization_cycle(self, idx):
m = self.maps[idx]
json_str = m.to_json()
m2 = ContactMap.from_json(json_str)
m2 = ContactFrequency.from_json(json_str)
_contact_object_compare(m, m2)
assert m == m2

def test_with_ignores(self, idx):
m = ContactMap(traj[idx], cutoff=0.075, n_neighbors_ignored=1)
m = ContactFrequency(traj[idx], cutoff=0.075, n_neighbors_ignored=1)
expected_atom_contacts = {
0: [[1, 4]],
4: [[0, 9], [0, 8], [1, 8], [1, 9], [1, 4], [8, 4], [8, 5]]
Expand Down Expand Up @@ -278,22 +264,22 @@ def test_most_common_atoms_for_contact(self, idx):
def test_saving(self, idx):
m = self.maps[idx]
m.save_to_file(test_file)
m2 = ContactMap.from_file(test_file)
m2 = ContactFrequency.from_file(test_file)
assert m.atom_contacts.counter == m2.atom_contacts.counter
os.remove(test_file)

@pytest.mark.parametrize("use_atom_slice", [True, False, None])
def test_atom_slice(self, idx, use_atom_slice):
# Set class variable before init
class_default = ContactMap._class_use_atom_slice
ContactMap._class_use_atom_slice = use_atom_slice
map0q = ContactMap(traj[0], query=[1, 4, 5, 6], cutoff=0.075,
n_neighbors_ignored=0)
map0h = ContactMap(traj[0], haystack=[1, 4, 5, 6],
cutoff=0.075, n_neighbors_ignored=0)
map0b = ContactMap(traj[0], query=[1, 4, 5, 6],
haystack=[1, 4, 5, 6], cutoff=0.075,
n_neighbors_ignored=0)
class_default = ContactFrequency._class_use_atom_slice
ContactFrequency._class_use_atom_slice = use_atom_slice
map0q = ContactFrequency(traj[0], query=[1, 4, 5, 6], cutoff=0.075,
n_neighbors_ignored=0)
map0h = ContactFrequency(traj[0], haystack=[1, 4, 5, 6],
cutoff=0.075, n_neighbors_ignored=0)
map0b = ContactFrequency(traj[0], query=[1, 4, 5, 6],
haystack=[1, 4, 5, 6], cutoff=0.075,
n_neighbors_ignored=0)
maps = [map0q, map0h, map0b]
atoms = {map0q: list(range(10)),
map0h: list(range(10)),
Expand Down Expand Up @@ -327,7 +313,7 @@ def test_atom_slice(self, idx, use_atom_slice):
assert real_idx == sliced_idx
# Reset class variable (as imports are not redone between function
# calls)
ContactMap._class_use_atom_slice = class_default
ContactFrequency._class_use_atom_slice = class_default

def test_contacts_dict(self, idx):
_check_contacts_dict_names(self.maps[idx])
Expand All @@ -339,8 +325,9 @@ def test_no_unitcell(self, idx):

# Activate atom_slice
atoms = [1, 4, 5, 6]
mapi = ContactMap(temptraj[idx], cutoff=0.075, n_neighbors_ignored=0,
query=atoms, haystack=atoms)
mapi = ContactFrequency(temptraj[idx], cutoff=0.075,
n_neighbors_ignored=0, query=atoms,
haystack=atoms)
expected_atom_contacts = {0: [[1, 4], [4, 6], [5, 6]],
4: [[1, 4], [4, 6], [5, 6]]}
expected = counter_of_inner_list(expected_atom_contacts[idx])
Expand Down Expand Up @@ -501,7 +488,7 @@ def test_hash(self):
def test_saving(self):
m = self.map
m.save_to_file(test_file)
m2 = ContactMap.from_file(test_file)
m2 = ContactFrequency.from_file(test_file)
assert m.atom_contacts.counter == m2.atom_contacts.counter
os.remove(test_file)

Expand Down Expand Up @@ -637,7 +624,8 @@ class TestContactDifference(object):
def test_diff_traj_frame(self):
ttraj = ContactFrequency(traj[0:4], cutoff=0.075,
n_neighbors_ignored=0)
frame = ContactMap(traj[4], cutoff=0.075, n_neighbors_ignored=0)
frame = ContactFrequency(traj[4], cutoff=0.075,
n_neighbors_ignored=0)
expected_atom_count = {
frozenset([0, 8]): -1.0,
frozenset([0, 9]): -1.0,
Expand Down Expand Up @@ -673,7 +661,8 @@ def test_diff_traj_frame(self):
def test_serialization_cycle(self, intermediate):
ttraj = ContactFrequency(traj[0:4], cutoff=0.075,
n_neighbors_ignored=0)
frame = ContactMap(traj[4], cutoff=0.075, n_neighbors_ignored=0)
frame = ContactFrequency(traj[4], cutoff=0.075,
n_neighbors_ignored=0)
diff = ttraj - frame

serializer, deserializer = {
Expand All @@ -687,8 +676,8 @@ def test_serialization_cycle(self, intermediate):
assert diff == reloaded

def test_diff_frame_frame(self):
m0 = ContactMap(traj[0], cutoff=0.075, n_neighbors_ignored=0)
m4 = ContactMap(traj[4], cutoff=0.075, n_neighbors_ignored=0)
m0 = ContactFrequency(traj[0], cutoff=0.075, n_neighbors_ignored=0)
m4 = ContactFrequency(traj[4], cutoff=0.075, n_neighbors_ignored=0)
# one of these simply has more contacts than the other, so to test
# both positive diff and negative diff we flip the sign
diff_1 = m4 - m0
Expand Down Expand Up @@ -719,7 +708,8 @@ def test_diff_frame_frame(self):
def test_contacts_dict(self):
ttraj = ContactFrequency(traj[0:4], cutoff=0.075,
n_neighbors_ignored=0)
frame = ContactMap(traj[4], cutoff=0.075, n_neighbors_ignored=0)
frame = ContactFrequency(traj[4], cutoff=0.075,
n_neighbors_ignored=0)
_check_contacts_dict_names(ttraj - frame)

def test_diff_traj_traj(self):
Expand Down Expand Up @@ -765,7 +755,8 @@ def test_diff_traj_traj(self):
def test_saving(self):
ttraj = ContactFrequency(traj[0:4], cutoff=0.075,
n_neighbors_ignored=0)
frame = ContactMap(traj[4], cutoff=0.075, n_neighbors_ignored=0)
frame = ContactFrequency(traj[4], cutoff=0.075,
n_neighbors_ignored=0)
diff = ttraj - frame

diff.save_to_file(test_file)
Expand All @@ -778,6 +769,7 @@ def test_plot(self):
# smoke test; checks that we cover negative counts in plotting
ttraj = ContactFrequency(traj[0:4], cutoff=0.075,
n_neighbors_ignored=0)
frame = ContactMap(traj[4], cutoff=0.075, n_neighbors_ignored=0)
frame = ContactFrequency(traj[4], cutoff=0.075,
n_neighbors_ignored=0)
diff = ttraj - frame
diff.residue_contacts.plot()
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[metadata]
name = contact_map
version = 0.6.1.dev0
version = 0.7.0.dev0
description = Contact maps based on MDTraj
long_description = file: README.md
long_description_content_type = text/markdown
Expand Down