Skip to content

Commit

Permalink
Merge pull request #88 from dwhswenson/remove_map
Browse files Browse the repository at this point in the history
Remove `ContactMap` (allowing rename `ContactFrequency` => `ContactMap`)
  • Loading branch information
dwhswenson committed Sep 23, 2020
2 parents 0753f08 + a9fd84a commit a22e862
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 109 deletions.
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

0 comments on commit a22e862

Please sign in to comment.