From 214161ba52dfb39562201ae0a4fdcbe14cf112fa Mon Sep 17 00:00:00 2001 From: sroet Date: Wed, 4 Nov 2020 22:22:28 +0100 Subject: [PATCH 01/10] initial try --- contact_map/contact_trajectory.py | 6 +++- contact_map/dask_runner.py | 46 +++++++++++++++++++++++++++++-- 2 files changed, 48 insertions(+), 4 deletions(-) diff --git a/contact_map/contact_trajectory.py b/contact_map/contact_trajectory.py index 4bfb10f..44853bf 100644 --- a/contact_map/contact_trajectory.py +++ b/contact_map/contact_trajectory.py @@ -31,8 +31,11 @@ def __init__(self, trajectory, query=None, haystack=None, cutoff=0.45, super(ContactTrajectory, self).__init__(trajectory.topology, query, haystack, cutoff, n_neighbors_ignored) + self._contact_maps = self._make_contact_maps(trajectory) + + def _make_contact_maps(self, trajectory): contacts = self._build_contacts(trajectory) - self._contact_maps = [ + contact_maps = [ ContactFrequency.from_contacts( topology=self.topology, query=self.query, @@ -46,6 +49,7 @@ def __init__(self, trajectory, query=None, haystack=None, cutoff=0.45, ) for atom_contacts, residue_contacts in zip(*contacts) ] + return contact_maps def __getitem__(self, num): return self._contact_maps[num] diff --git a/contact_map/dask_runner.py b/contact_map/dask_runner.py index 66fd473..8031a43 100644 --- a/contact_map/dask_runner.py +++ b/contact_map/dask_runner.py @@ -4,6 +4,7 @@ from . import frequency_task from .contact_map import ContactFrequency, ContactObject +from .contact_trajectory import ContactTrajectory import mdtraj as md @@ -29,15 +30,19 @@ def dask_run(trajectory, client, run_info): """ slices = frequency_task.default_slices(n_total=len(trajectory), n_workers=len(client.ncores())) + maps = dask_map_load_and_frequency(slices, client, run_info) + freq = client.submit(frequency_task.reduce_all_results, maps) + + return freq.result() + +def dask_map_load_and_frequency(slices, client, run_info): subtrajs = client.map(frequency_task.load_trajectory_task, slices, file_name=run_info['trajectory_file'], **run_info['load_kwargs']) maps = client.map(frequency_task.map_task, subtrajs, parameters=run_info['parameters']) - freq = client.submit(frequency_task.reduce_all_results, maps) - - return freq.result() + return maps class DaskContactFrequency(ContactFrequency): @@ -108,3 +113,38 @@ def run_info(self): return {'parameters': self.parameters, 'trajectory_file': self.filename, 'load_kwargs': self.kwargs} + + +class DaskContactTrajectory(ContactTrajectory): + def __init__(self, client, filename, query=None, haystack=None, + cutoff=0.45, n_neighbors_ignored=2, **kwargs): + self.client = client + self.filename = filename + self.kwargs = kwargs + # We only need a trajectory with the right topology + trajectory = md.load(filename, **kwargs) + + super(DaskContactTrajectory, self).__init__( + trajectory, query, haystack, cutoff, n_neighbors_ignored, + ) + + def _make_contact_maps(self, trajectory): + frames = range(trajectory.n_frames) + maps = dask_map_load_and_frequency(frames, self.client, self.run_info) + return self.client.gather(maps) + + @property + def parameters(self): + return {'query': self.query, + 'haystack': self.haystack, + 'cutoff': self.cutoff, + 'n_neighbors_ignored': self.n_neighbors_ignored} + + @property + def run_info(self): + return {'parameters': self.parameters, + 'trajectory_file': self.filename, + 'load_kwargs': self.kwargs} + + + From 4efe5783ef44d7c409c770b5209850986513e162 Mon Sep 17 00:00:00 2001 From: sroet Date: Mon, 9 Nov 2020 22:21:19 +0100 Subject: [PATCH 02/10] investigate some more timings --- contact_map/dask_runner.py | 23 +++++++++++++++-------- contact_map/frequency_task.py | 20 ++++++++++++++++++++ 2 files changed, 35 insertions(+), 8 deletions(-) diff --git a/contact_map/dask_runner.py b/contact_map/dask_runner.py index 8031a43..547c671 100644 --- a/contact_map/dask_runner.py +++ b/contact_map/dask_runner.py @@ -45,6 +45,15 @@ def dask_map_load_and_frequency(slices, client, run_info): return maps +def dask_map_traj_to_frequency(trajectory, slices, client, run_info): + client.scatter(trajectory) + subtrajs = client.map(frequency_task.slice_trajectory_task, slices, + trajectory=trajectory) + maps = client.map(frequency_task.map_task, subtrajs, + parameters=run_info['parameters']) + return maps + + class DaskContactFrequency(ContactFrequency): """Dask-based parallelization of contact frequency. @@ -89,7 +98,6 @@ def __init__(self, client, filename, query=None, haystack=None, self.client = client self.filename = filename trajectory = md.load(filename, **kwargs) - self.kwargs = kwargs super(DaskContactFrequency, self).__init__( @@ -121,16 +129,18 @@ def __init__(self, client, filename, query=None, haystack=None, self.client = client self.filename = filename self.kwargs = kwargs - # We only need a trajectory with the right topology - trajectory = md.load(filename, **kwargs) + self.trajectory = md.load(filename, **kwargs) super(DaskContactTrajectory, self).__init__( - trajectory, query, haystack, cutoff, n_neighbors_ignored, + self.trajectory, query, haystack, cutoff, n_neighbors_ignored, ) def _make_contact_maps(self, trajectory): frames = range(trajectory.n_frames) - maps = dask_map_load_and_frequency(frames, self.client, self.run_info) + #TODO DON'T run this for 1 frame at a time... + maps = dask_map_traj_to_frequency(self.trajectory, + frames, self.client, + self.run_info) return self.client.gather(maps) @property @@ -145,6 +155,3 @@ def run_info(self): return {'parameters': self.parameters, 'trajectory_file': self.filename, 'load_kwargs': self.kwargs} - - - diff --git a/contact_map/frequency_task.py b/contact_map/frequency_task.py index 0866a81..0c830ac 100644 --- a/contact_map/frequency_task.py +++ b/contact_map/frequency_task.py @@ -86,6 +86,26 @@ def load_trajectory_task(subslice, file_name, **kwargs): """ return md.load(file_name, **kwargs)[subslice] + +def slice_trajectory_task(subslice, trajectory): + """ + Task for slicing a trajectory. Reordered to take per-task variable first. + + Parameters + ---------- + subslice : slice + the slice of the trajectory to use + trajectory : md.Trajectory + trajectory to slice + + Returns + ------- + md.Trajectory : + subtrajectory for this slice + """ + return trajectory[subslice] + + def map_task(subtrajectory, parameters): """Task to be mapped to all subtrajectories. Run ContactFrequency From 8916ddd09ce2a542124eefd7f4176ed2d5f05f61 Mon Sep 17 00:00:00 2001 From: sroet Date: Wed, 11 Nov 2020 23:24:55 +0100 Subject: [PATCH 03/10] DaskContactTrajectory works! and has similar enough speeds --- contact_map/contact_trajectory.py | 49 ++++++++++++++------------ contact_map/dask_runner.py | 58 +++++++++++++++++++------------ contact_map/frequency_task.py | 37 ++++++++++---------- 3 files changed, 81 insertions(+), 63 deletions(-) diff --git a/contact_map/contact_trajectory.py b/contact_map/contact_trajectory.py index 44853bf..bc1b461 100644 --- a/contact_map/contact_trajectory.py +++ b/contact_map/contact_trajectory.py @@ -3,6 +3,32 @@ from .contact_map import ContactFrequency, ContactObject import json +# Split this out of the to prevent code duplication for DaskContactTrajectory +def _build_contacts(contact_object, trajectory): + # atom_contacts, residue_contacts = self._empty_contacts() + atom_contacts = [] + residue_contacts = [] + residue_ignore_atom_idxs = contact_object._residue_ignore_atom_idxs + residue_query_atom_idxs = contact_object.indexer.residue_query_atom_idxs + used_trajectory = contact_object.indexer.slice_trajectory(trajectory) + + + # range(len(trajectory)) avoids recopying topology, as would occur + # in `for frame in trajectory` + for frame_num in range(len(trajectory)): + frame_contacts = contact_object._contact_map(used_trajectory, + frame_num, + residue_query_atom_idxs, + residue_ignore_atom_idxs) + frame_atom_contacts, frame_residue_contacts = frame_contacts + frame_atom_contacts = \ + contact_object.indexer.convert_atom_contacts(frame_atom_contacts) + # TODO unify contact building with something like this? + # atom_contacts, residue_contact = self._update_contacts(...) + atom_contacts.append(frame_atom_contacts) + residue_contacts.append(frame_residue_contacts) + return atom_contacts, residue_contacts + class ContactTrajectory(ContactObject, abc.Sequence): """Track all the contacts over a trajectory, frame-by-frame. @@ -87,28 +113,7 @@ def from_contacts(cls, atom_contacts, residue_contacts, topology, return cls.from_contact_maps(contact_maps) def _build_contacts(self, trajectory): - # atom_contacts, residue_contacts = self._empty_contacts() - atom_contacts = [] - residue_contacts = [] - - residue_ignore_atom_idxs = self._residue_ignore_atom_idxs - residue_query_atom_idxs = self.indexer.residue_query_atom_idxs - used_trajectory = self.indexer.slice_trajectory(trajectory) - - # range(len(trajectory)) avoids recopying topology, as would occur - # in `for frame in trajectory` - for frame_num in range(len(trajectory)): - frame_contacts = self._contact_map(used_trajectory, frame_num, - residue_query_atom_idxs, - residue_ignore_atom_idxs) - frame_atom_contacts, frame_residue_contacts = frame_contacts - frame_atom_contacts = \ - self.indexer.convert_atom_contacts(frame_atom_contacts) - # TODO unify contact building with something like this? - # atom_contacts, residue_contact = self._update_contacts(...) - atom_contacts.append(frame_atom_contacts) - residue_contacts.append(frame_residue_contacts) - return atom_contacts, residue_contacts + return _build_contacts(self, trajectory) def contact_frequency(self): """Create a :class:`.ContactFrequency` from this contact trajectory diff --git a/contact_map/dask_runner.py b/contact_map/dask_runner.py index 547c671..2a7b598 100644 --- a/contact_map/dask_runner.py +++ b/contact_map/dask_runner.py @@ -28,30 +28,22 @@ def dask_run(trajectory, client, run_info): :class:`.ContactFrequency` : total contact frequency for the trajectory """ - slices = frequency_task.default_slices(n_total=len(trajectory), - n_workers=len(client.ncores())) - maps = dask_map_load_and_frequency(slices, client, run_info) + subtrajs = dask_load_and_slice(trajectory, client, run_info) + + maps = client.map(frequency_task.map_task, subtrajs, + parameters=run_info['parameters']) freq = client.submit(frequency_task.reduce_all_results, maps) return freq.result() -def dask_map_load_and_frequency(slices, client, run_info): +def dask_load_and_slice(trajectory, client, run_info): + slices = frequency_task.default_slices(n_total=len(trajectory), + n_workers=len(client.ncores())) subtrajs = client.map(frequency_task.load_trajectory_task, slices, file_name=run_info['trajectory_file'], **run_info['load_kwargs']) - maps = client.map(frequency_task.map_task, subtrajs, - parameters=run_info['parameters']) - return maps - - -def dask_map_traj_to_frequency(trajectory, slices, client, run_info): - client.scatter(trajectory) - subtrajs = client.map(frequency_task.slice_trajectory_task, slices, - trajectory=trajectory) - maps = client.map(frequency_task.map_task, subtrajs, - parameters=run_info['parameters']) - return maps + return subtrajs class DaskContactFrequency(ContactFrequency): @@ -130,18 +122,40 @@ def __init__(self, client, filename, query=None, haystack=None, self.filename = filename self.kwargs = kwargs self.trajectory = md.load(filename, **kwargs) + self.contact_object = ContactObject( + self.trajectory.topology, + query=query, + haystack=haystack, + cutoff=cutoff, + n_neighbors_ignored=n_neighbors_ignored, + ) super(DaskContactTrajectory, self).__init__( self.trajectory, query, haystack, cutoff, n_neighbors_ignored, ) def _make_contact_maps(self, trajectory): - frames = range(trajectory.n_frames) - #TODO DON'T run this for 1 frame at a time... - maps = dask_map_traj_to_frequency(self.trajectory, - frames, self.client, - self.run_info) - return self.client.gather(maps) + subtrajs = dask_load_and_slice(self.trajectory, self.client, + self.run_info) + contact_lists = self.client.map(frequency_task.contacts_per_frame_task, + subtrajs, + contact_object=self.contact_object) + contact_maps = [ + ContactFrequency.from_contacts( + topology=self.topology, + query=self.query, + haystack=self.haystack, + cutoff=self.cutoff, + n_neighbors_ignored=self.n_neighbors_ignored, + atom_contacts=atom_contacts, + residue_contacts=residue_contacts, + n_frames=1, + indexer=self.indexer + ) + for contacts in contact_lists + for atom_contacts, residue_contacts in zip(*contacts.result()) + ] + return contact_maps @property def parameters(self): diff --git a/contact_map/frequency_task.py b/contact_map/frequency_task.py index 0c830ac..83c9825 100644 --- a/contact_map/frequency_task.py +++ b/contact_map/frequency_task.py @@ -22,6 +22,7 @@ import mdtraj as md from contact_map import ContactFrequency +from contact_map.contact_trajectory import _build_contacts def block_slices(n_total, n_per_block): """Determine slices for splitting the input array. @@ -87,25 +88,6 @@ def load_trajectory_task(subslice, file_name, **kwargs): return md.load(file_name, **kwargs)[subslice] -def slice_trajectory_task(subslice, trajectory): - """ - Task for slicing a trajectory. Reordered to take per-task variable first. - - Parameters - ---------- - subslice : slice - the slice of the trajectory to use - trajectory : md.Trajectory - trajectory to slice - - Returns - ------- - md.Trajectory : - subtrajectory for this slice - """ - return trajectory[subslice] - - def map_task(subtrajectory, parameters): """Task to be mapped to all subtrajectories. Run ContactFrequency @@ -123,6 +105,23 @@ def map_task(subtrajectory, parameters): """ return ContactFrequency(subtrajectory, **parameters) + +def contacts_per_frame_task(trajectory, contact_object): + """Task that will mimic ContactTrajectory._build_contacts, but with + a pre-initialized ContactObject instead of `self` to produce the contacts + + + Parameters + ---------- + trajectory : mdtraj.Trajectory + single trajectory segment to calculate contacts for every frame + contactobject : ContactObject + The instance that will replace self in _build_contacts + + """ + return _build_contacts(contact_object, trajectory) + + def reduce_all_results(contacts): """Combine multiple :class:`.ContactFrequency` objects into one From 01b6fe8d9ca8767cadcec4229579c480257cd290 Mon Sep 17 00:00:00 2001 From: sroet Date: Wed, 18 Nov 2020 21:40:45 +0100 Subject: [PATCH 04/10] Do some code refactoring --- contact_map/contact_trajectory.py | 4 ++-- contact_map/dask_runner.py | 23 ++++++----------------- 2 files changed, 8 insertions(+), 19 deletions(-) diff --git a/contact_map/contact_trajectory.py b/contact_map/contact_trajectory.py index bc1b461..bf6fe43 100644 --- a/contact_map/contact_trajectory.py +++ b/contact_map/contact_trajectory.py @@ -27,7 +27,7 @@ def _build_contacts(contact_object, trajectory): # atom_contacts, residue_contact = self._update_contacts(...) atom_contacts.append(frame_atom_contacts) residue_contacts.append(frame_residue_contacts) - return atom_contacts, residue_contacts + return zip(atom_contacts, residue_contacts) class ContactTrajectory(ContactObject, abc.Sequence): """Track all the contacts over a trajectory, frame-by-frame. @@ -73,7 +73,7 @@ def _make_contact_maps(self, trajectory): n_frames=1, indexer=self.indexer ) - for atom_contacts, residue_contacts in zip(*contacts) + for atom_contacts, residue_contacts in contacts ] return contact_maps diff --git a/contact_map/dask_runner.py b/contact_map/dask_runner.py index 2a7b598..c9e7f9c 100644 --- a/contact_map/dask_runner.py +++ b/contact_map/dask_runner.py @@ -134,28 +134,17 @@ def __init__(self, client, filename, query=None, haystack=None, self.trajectory, query, haystack, cutoff, n_neighbors_ignored, ) - def _make_contact_maps(self, trajectory): + def _build_contacts(self, trajectory): subtrajs = dask_load_and_slice(self.trajectory, self.client, self.run_info) contact_lists = self.client.map(frequency_task.contacts_per_frame_task, subtrajs, contact_object=self.contact_object) - contact_maps = [ - ContactFrequency.from_contacts( - topology=self.topology, - query=self.query, - haystack=self.haystack, - cutoff=self.cutoff, - n_neighbors_ignored=self.n_neighbors_ignored, - atom_contacts=atom_contacts, - residue_contacts=residue_contacts, - n_frames=1, - indexer=self.indexer - ) - for contacts in contact_lists - for atom_contacts, residue_contacts in zip(*contacts.result()) - ] - return contact_maps + # Return a generator for this to work out + gen = ((atom_contacts, residue_contacts) + for contacts in contact_lists + for atom_contacts, residue_contacts in contacts.result()) + return gen @property def parameters(self): From a115e183790be0164f67ed84da4fd6d7b500de45 Mon Sep 17 00:00:00 2001 From: sroet Date: Wed, 18 Nov 2020 22:16:56 +0100 Subject: [PATCH 05/10] add and rework dask tests --- contact_map/tests/test_dask_runner.py | 71 +++++++++++++++++---------- 1 file changed, 46 insertions(+), 25 deletions(-) diff --git a/contact_map/tests/test_dask_runner.py b/contact_map/tests/test_dask_runner.py index 3776812..d704db5 100644 --- a/contact_map/tests/test_dask_runner.py +++ b/contact_map/tests/test_dask_runner.py @@ -4,7 +4,10 @@ from .utils import * from contact_map.dask_runner import * -from contact_map import ContactFrequency +from contact_map import ContactFrequency, ContactTrajectory +from collections.abc import Iterable +import mdtraj + def dask_setup_test_cluster(distributed, n_workers=4, n_attempts=3): """Set up a test cluster using dask.distributed. Try up to n_attempts @@ -25,45 +28,63 @@ def dask_setup_test_cluster(distributed, n_workers=4, n_attempts=3): pytest.skip("Failed to set up distributed LocalCluster") -class TestDaskContactFrequency(object): - def test_dask_integration(self): - # this is an integration test to check that dask works +class TestDaskRunners(object): + def setup(self): dask = pytest.importorskip('dask') # pylint: disable=W0612 distributed = pytest.importorskip('dask.distributed') + self.distributed = distributed # Explicitly set only 4 workers on Travis instead of 31 # Fix copied from https://github.com/spencerahill/aospy/pull/220/files - cluster = dask_setup_test_cluster(distributed, n_workers=4) - client = distributed.Client(cluster) - filename = find_testfile("trajectory.pdb") + self.cluster = dask_setup_test_cluster(distributed, n_workers=4) + self.client = distributed.Client(self.cluster) + self.filename = find_testfile("trajectory.pdb") - dask_freq = DaskContactFrequency(client, filename, cutoff=0.075, - n_neighbors_ignored=0) - client.close() - assert dask_freq.n_frames == 5 + def teardown(self): + self.client.shutdown() - def test_dask_atom_slice(self): - # This is an integration test to check that dask works with atom_slice - dask = pytest.importorskip('dask') # pylint: disable=W0612 - distributed = pytest.importorskip('dask.distributed') - # Explicitly set only 4 workers on Travis instead of 31 - # Fix copied from https://github.com/spencerahill/aospy/pull/220/files - cluster = dask_setup_test_cluster(distributed, n_workers=4) - client = distributed.Client(cluster) - filename = find_testfile("trajectory.pdb") + @pytest.mark.parametrize("dask_cls", [DaskContactFrequency, + DaskContactTrajectory]) + def test_dask_integration(self, dask_cls): + dask_freq = dask_cls(self.client, self.filename, cutoff=0.075, + n_neighbors_ignored=0) + if isinstance(dask_freq, ContactFrequency): + assert dask_freq.n_frames == 5 + elif isinstance(dask_freq, ContactTrajectory): + assert len(dask_freq) == 5 - dask_freq0 = DaskContactFrequency(client, filename, query=[3, 4], + def test_dask_atom_slice(self): + dask_freq0 = DaskContactFrequency(self.client, self.filename, + query=[3, 4], haystack=[6, 7], cutoff=0.075, n_neighbors_ignored=0) - client.close() + self.client.close() assert dask_freq0.n_frames == 5 - client = distributed.Client(cluster) + self.client = self.distributed.Client(self.cluster) # Set the slicing of contact frequency (used in the frqeuency task) # to False ContactFrequency._class_use_atom_slice = False - dask_freq1 = DaskContactFrequency(client, filename, query=[3, 4], + dask_freq1 = DaskContactFrequency(self.client, + self.filename, query=[3, 4], haystack=[6, 7], cutoff=0.075, n_neighbors_ignored=0) - client.close() assert dask_freq0._use_atom_slice is True assert dask_freq1._use_atom_slice is False assert dask_freq0 == dask_freq1 + + @pytest.mark.parametrize("dask_cls, norm_cls",[ + (DaskContactFrequency, ContactFrequency), + (DaskContactTrajectory, ContactTrajectory)]) + def test_answer_equal(self, dask_cls, norm_cls): + trj = mdtraj.load(self.filename) + dask_result = dask_cls(self.client, self.filename) + norm_result = norm_cls(trj) + if isinstance(dask_result, Iterable): + for i, j in zip(dask_result, norm_result): + assert i.atom_contacts._counter == j.atom_contacts._counter + assert (i.residue_contacts._counter == + j.residue_contacts._counter) + else: + assert (dask_result.atom_contacts._counter == + norm_result.atom_contacts._counter) + assert (dask_result.residue_contacts._counter == + norm_result.residue_contacts._counter) From 4039a6f6893a2a6565d1a34b5230f22b252f57ea Mon Sep 17 00:00:00 2001 From: sroet Date: Thu, 19 Nov 2020 18:09:57 +0100 Subject: [PATCH 06/10] Add docs and undo unnecessary refactor --- contact_map/contact_trajectory.py | 27 ++++++++++----- contact_map/dask_runner.py | 55 +++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 8 deletions(-) diff --git a/contact_map/contact_trajectory.py b/contact_map/contact_trajectory.py index bf6fe43..046206d 100644 --- a/contact_map/contact_trajectory.py +++ b/contact_map/contact_trajectory.py @@ -1,10 +1,25 @@ from collections import abc, Counter from .contact_map import ContactFrequency, ContactObject -import json + # Split this out of the to prevent code duplication for DaskContactTrajectory def _build_contacts(contact_object, trajectory): + """Make a contact map for every frame in trajectory. + + Parameters + ---------- + contact_object : `ContactObject` + The contact object that will be used to make the contact maps. + trajectory: `mdtraj.Trajectory` + The trajectory for which we will return a contactObject for each frame. + + Returns + ------- + out : list of tuples + list of (atom_contacts, residue_contacts) for each frame in trajectory. + """ + # atom_contacts, residue_contacts = self._empty_contacts() atom_contacts = [] residue_contacts = [] @@ -12,7 +27,6 @@ def _build_contacts(contact_object, trajectory): residue_query_atom_idxs = contact_object.indexer.residue_query_atom_idxs used_trajectory = contact_object.indexer.slice_trajectory(trajectory) - # range(len(trajectory)) avoids recopying topology, as would occur # in `for frame in trajectory` for frame_num in range(len(trajectory)): @@ -29,6 +43,7 @@ def _build_contacts(contact_object, trajectory): residue_contacts.append(frame_residue_contacts) return zip(atom_contacts, residue_contacts) + class ContactTrajectory(ContactObject, abc.Sequence): """Track all the contacts over a trajectory, frame-by-frame. @@ -57,11 +72,8 @@ def __init__(self, trajectory, query=None, haystack=None, cutoff=0.45, super(ContactTrajectory, self).__init__(trajectory.topology, query, haystack, cutoff, n_neighbors_ignored) - self._contact_maps = self._make_contact_maps(trajectory) - - def _make_contact_maps(self, trajectory): contacts = self._build_contacts(trajectory) - contact_maps = [ + self._contact_maps = [ ContactFrequency.from_contacts( topology=self.topology, query=self.query, @@ -75,7 +87,6 @@ def _make_contact_maps(self, trajectory): ) for atom_contacts, residue_contacts in contacts ] - return contact_maps def __getitem__(self, num): return self._contact_maps[num] @@ -306,7 +317,7 @@ def _normal(self): def __next__(self): # if self.max + self.step < self.width: - # to_add, to_sub = self._startup() + # to_add, to_sub = self._startup() if self.max + self.step < self.length: to_add, to_sub = self._normal() else: diff --git a/contact_map/dask_runner.py b/contact_map/dask_runner.py index c9e7f9c..945a2fd 100644 --- a/contact_map/dask_runner.py +++ b/contact_map/dask_runner.py @@ -38,6 +38,23 @@ def dask_run(trajectory, client, run_info): def dask_load_and_slice(trajectory, client, run_info): + """ + Run dask to load a trajectory and make a list of subtraj futures that + can be used for the other analysis + + Parameters + ---------- + trajectory : mdtraj.Trajectory + client : dask.distributed.Client + run_info : dict + Keys are 'trajectory_file' (trajectory filename) and 'load_kwargs' + (additional kwargs passed to md.load) + + Returns + ------- + list of Futures : + A list of futures for loaded subtrajectory + """ slices = frequency_task.default_slices(n_total=len(trajectory), n_workers=len(client.ncores())) subtrajs = client.map(frequency_task.load_trajectory_task, slices, @@ -116,6 +133,44 @@ def run_info(self): class DaskContactTrajectory(ContactTrajectory): + """Dask-based parallelization of contact trajectory. + + The contact trajectory tracks all contacts of a trajectory, frame-by-frame. + See :class:`.ContactTrajectory` for details. This implementation + parallelizes the contact map calculations using + ``dask.distributed``, which must be installed separately to use this + object. + + Notes + ----- + + The interface for this object closely mimics that of the + :class:`.ContactTrajectory` object, with the addition requiring the + ``dask.distributed.Client`` as input. However, there is one important + difference. Whereas :class:`.ContactTrajectory` takes an + ``mdtraj.Trajectory`` object as input, :class:`.DaskContactTrajectory` + takes a file name, plus any extra kwargs that MDTraj needs to load the + file. + + Parameters + ---------- + client : dask.distributed.Client + Client object connected to the dask network. + filename : str + Name of the file where the trajectory is located. File must be + accessible by all workers in the dask network. + query : list of int + Indices of the atoms to be included as query. Default ``None`` + means all atoms. + haystack : list of int + Indices of the atoms to be included as haystack. Default ``None`` + means all atoms. + cutoff : float + Cutoff distance for contacts, in nanometers. Default 0.45. + n_neighbors_ignored : int + Number of neighboring residues (in the same chain) to ignore. + Default 2. + """ def __init__(self, client, filename, query=None, haystack=None, cutoff=0.45, n_neighbors_ignored=2, **kwargs): self.client = client From 664f46338a9aad37bc0aeab4570992516b30cf85 Mon Sep 17 00:00:00 2001 From: sroet Date: Fri, 20 Nov 2020 17:36:36 +0100 Subject: [PATCH 07/10] add DaskContactTrajectory to base __init__ --- contact_map/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contact_map/__init__.py b/contact_map/__init__.py index c8da351..e88e903 100644 --- a/contact_map/__init__.py +++ b/contact_map/__init__.py @@ -22,6 +22,6 @@ ConcurrencePlotter, plot_concurrence ) -from .dask_runner import DaskContactFrequency +from .dask_runner import DaskContactFrequency, DaskContactTrajectory from . import plot_utils From 194d3811d16f0323bee8705a46c62002b92768dd Mon Sep 17 00:00:00 2001 From: sroet Date: Fri, 20 Nov 2020 17:39:26 +0100 Subject: [PATCH 08/10] Add DaskContactTrajectory example and add cluster shutdown to the DaskContactFrequency example --- examples/dask_contact_frequency.ipynb | 53 +++--- examples/dask_contact_trajectory.ipynb | 221 +++++++++++++++++++++++++ 2 files changed, 244 insertions(+), 30 deletions(-) create mode 100644 examples/dask_contact_trajectory.ipynb diff --git a/examples/dask_contact_frequency.ipynb b/examples/dask_contact_frequency.ipynb index bb58479..4c00c5d 100644 --- a/examples/dask_contact_frequency.ipynb +++ b/examples/dask_contact_frequency.ipynb @@ -17,10 +17,10 @@ "metadata": {}, "outputs": [], "source": [ - "%matplotlib inline\n", + "#%matplotlib inline\n", "# dask and distributed are extra installs\n", - "from dask.distributed import Client, LocalCluster\n", - "import contact_map" + "import contact_map\n", + "from dask.distributed import Client, LocalCluster\n" ] }, { @@ -63,25 +63,25 @@ "\n", "\n", "\n", "\n", "\n", "
\n", - "

Client

\n", - "
\n", - "

Cluster

\n", - "
    \n", + "

    Cluster

    \n", + "
      \n", "
    • Workers: 4
    • \n", - "
    • Cores: 4
    • \n", - "
    • Memory: 17.18 GB
    • \n", + "
    • Cores: 12
    • \n", + "
    • Memory: 16.39 GB
    • \n", "
    \n", "
" ], "text/plain": [ - "" + "" ] }, "execution_count": 4, @@ -102,8 +102,8 @@ "name": "stdout", "output_type": "stream", "text": [ - "CPU times: user 954 ms, sys: 341 ms, total: 1.3 s\n", - "Wall time: 5.16 s\n" + "CPU times: user 364 ms, sys: 23.9 ms, total: 388 ms\n", + "Wall time: 3.72 s\n" ] } ], @@ -154,12 +154,14 @@ "outputs": [ { "data": { - "image/png": "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\n", + "image/png": "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\n", "text/plain": [ - "" + "
" ] }, - "metadata": {}, + "metadata": { + "needs_background": "light" + }, "output_type": "display_data" } ], @@ -174,19 +176,10 @@ "metadata": {}, "outputs": [], "source": [ - "# Something like this is supposed to shut down the workers and the scheduler\n", - "# I get it to shut down workers, but not scheduler... and does it all with lots of warnings\n", - "#client.loop.add_callback(client.scheduler.retire_workers, close_workers=True)\n", - "#client.loop.add_callback(client.scheduler.terminate)\n", - "#client.run_on_scheduler(lambda dask_scheduler: dask_scheduler.loop.stop())" + "# Shutdown the cluster and client\n", + "client.shutdown()\n", + "client.close() # This line is needed to prevent a timeout warning after 10 seconds" ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { @@ -205,7 +198,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.3" + "version": "3.8.6" } }, "nbformat": 4, diff --git a/examples/dask_contact_trajectory.ipynb b/examples/dask_contact_trajectory.ipynb new file mode 100644 index 0000000..6abf758 --- /dev/null +++ b/examples/dask_contact_trajectory.ipynb @@ -0,0 +1,221 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Parallel `ContactTrajectory` with Dask\n", + "\n", + "Each frame that makes up a `ContactTrajectory` can have its contact map calculated in parallel. This shows how to use [dask.distributed](https://distributed.readthedocs.io/) to do this (in a similat way as we showed for `ContactFrequency` in `dask_contact_frequency.ipynb`).\n", + "\n", + "We'll look at this using a trajectory of a specific inhibitor during its binding process to GSK3B. This system is also studied in the notebook on contact concurrences (with very similar initial discussion)." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "%matplotlib inline\n", + "# dask and distributed are extra installs\n", + "import contact_map\n", + "from dask.distributed import Client, LocalCluster\n", + "import matplotlib.pyplot as plt\n", + "\n", + "import mdtraj as md\n", + "traj = md.load(\"data/gsk3b_example.h5\")\n", + "\n", + "topology = traj.topology\n", + "yyg = topology.select('resname YYG and element != \"H\"')\n", + "protein = topology.select('protein and element != \"H\"')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "First we need to connect a client to a dask network.\n", + "\n", + "Note that there are several ways to set up the dask computer network and then connect a client to it. See https://distributed.readthedocs.io/en/latest/setup.html. The approach used here creates a `LocalCluster`. Large scale simulations would need other approaches. For clusters, you can manually run a `dask-scheduler` and multiple `dask-worker` commands. By using the same `sched.json`, it is easy to have different workers in different jobs on the cluster's scheduling system." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "c = LocalCluster()\n", + "client = Client(c)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "# if you started on a cluster and the scheduler file is called sched.json\n", + "#client = Client(scheduler_file=\"./sched.json\")" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "
\n", + "

Client

\n", + "\n", + "
\n", + "

Cluster

\n", + "
    \n", + "
  • Workers: 4
  • \n", + "
  • Cores: 12
  • \n", + "
  • Memory: 16.39 GB
  • \n", + "
\n", + "
" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "client" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CPU times: user 383 ms, sys: 34.4 ms, total: 418 ms\n", + "Wall time: 2.76 s\n" + ] + } + ], + "source": [ + "%%time\n", + "freq = contact_map.DaskContactTrajectory(\n", + " client=client,\n", + " query=yyg,\n", + " haystack=protein,\n", + " filename=\"data/gsk3b_example.h5\",\n", + ")\n", + "# top must be given as keyword (passed along to mdtraj.load)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Note that on a single machine (shared memory) this may not improve performance. That is because the single-frame aspect of this calculation is already parallelized with OpenMP, and will therefore use all cores on the machine.\n", + "\n", + "Next we check that we're still getting the same results:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "100" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# did it add up to give us the right number of frames?\n", + "len(freq)" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "# do we get a familiar-looking residue map for rolling averages?\n", + "rolling_frequencies = freq.rolling_frequency(window_size=30, step=14)\n", + "rolling_frequencies\n", + "\n", + "fig, axs = plt.subplots(3, 2, figsize=(12, 10))\n", + "for ax, freq in zip(axs.flatten(), rolling_frequencies):\n", + " freq.residue_contacts.plot_axes(ax=ax)\n", + " ax.set_xlim(*freq.query_residue_range);" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "# Shutdown the cluster and client\n", + "client.shutdown()\n", + "client.close() # This line is needed to prevent a timeout error after 10 seconds" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.6" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} From 2a2dc38ce57e776a51511a438df392e7274e49b5 Mon Sep 17 00:00:00 2001 From: sroet Date: Thu, 11 Mar 2021 19:56:53 +0100 Subject: [PATCH 09/10] update github actions? --- .github/workflows/unit-tests.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index 05d9d68..218b154 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -34,10 +34,12 @@ jobs: steps: - uses: actions/checkout@v2 + with: + fetch-depth: 2 - uses: actions/setup-python@v2 - uses: conda-incubator/setup-miniconda@v2 with: - auto-update-python: true + auto-update-conda: true python-version: ${{ matrix.CONDA_PY }} - name: "Install" env: From 9d569089ecb130b13418d69180663724b57ef56b Mon Sep 17 00:00:00 2001 From: sroet Date: Thu, 11 Mar 2021 20:38:16 +0100 Subject: [PATCH 10/10] split out integrations.ipynb to exporting_data and performance, and add DaskContactTrajectory to the latter --- docs/examples/index.rst | 3 +- examples/dask_contact_trajectory.ipynb | 221 ------- examples/exporting_data.ipynb | 601 ++++++++++++++++++ examples/integrations.ipynb | 842 ------------------------- examples/performance.ipynb | 385 +++++++++++ 5 files changed, 988 insertions(+), 1064 deletions(-) delete mode 100644 examples/dask_contact_trajectory.ipynb create mode 100644 examples/exporting_data.ipynb delete mode 100644 examples/integrations.ipynb create mode 100644 examples/performance.ipynb diff --git a/docs/examples/index.rst b/docs/examples/index.rst index 993699e..50d7ec0 100644 --- a/docs/examples/index.rst +++ b/docs/examples/index.rst @@ -27,7 +27,8 @@ You can also try them out online directly from your browser: |binder|_ nb/comparing_different_structures.ipynb nb/contact_trajectory.ipynb nb/concurrences.ipynb - nb/integrations.ipynb + nb/exporting_data.ipynb + nb/performance.ipynb .. |binder| image:: https://mybinder.org/badge_logo.svg .. _binder: https://mybinder.org/v2/gh/dwhswenson/contact_map/{{ version }}?filepath=%2Fexamples diff --git a/examples/dask_contact_trajectory.ipynb b/examples/dask_contact_trajectory.ipynb deleted file mode 100644 index 6abf758..0000000 --- a/examples/dask_contact_trajectory.ipynb +++ /dev/null @@ -1,221 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Parallel `ContactTrajectory` with Dask\n", - "\n", - "Each frame that makes up a `ContactTrajectory` can have its contact map calculated in parallel. This shows how to use [dask.distributed](https://distributed.readthedocs.io/) to do this (in a similat way as we showed for `ContactFrequency` in `dask_contact_frequency.ipynb`).\n", - "\n", - "We'll look at this using a trajectory of a specific inhibitor during its binding process to GSK3B. This system is also studied in the notebook on contact concurrences (with very similar initial discussion)." - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "%matplotlib inline\n", - "# dask and distributed are extra installs\n", - "import contact_map\n", - "from dask.distributed import Client, LocalCluster\n", - "import matplotlib.pyplot as plt\n", - "\n", - "import mdtraj as md\n", - "traj = md.load(\"data/gsk3b_example.h5\")\n", - "\n", - "topology = traj.topology\n", - "yyg = topology.select('resname YYG and element != \"H\"')\n", - "protein = topology.select('protein and element != \"H\"')" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "First we need to connect a client to a dask network.\n", - "\n", - "Note that there are several ways to set up the dask computer network and then connect a client to it. See https://distributed.readthedocs.io/en/latest/setup.html. The approach used here creates a `LocalCluster`. Large scale simulations would need other approaches. For clusters, you can manually run a `dask-scheduler` and multiple `dask-worker` commands. By using the same `sched.json`, it is easy to have different workers in different jobs on the cluster's scheduling system." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "c = LocalCluster()\n", - "client = Client(c)" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [], - "source": [ - "# if you started on a cluster and the scheduler file is called sched.json\n", - "#client = Client(scheduler_file=\"./sched.json\")" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "\n", - "\n", - "
\n", - "

Client

\n", - "\n", - "
\n", - "

Cluster

\n", - "
    \n", - "
  • Workers: 4
  • \n", - "
  • Cores: 12
  • \n", - "
  • Memory: 16.39 GB
  • \n", - "
\n", - "
" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "client" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "CPU times: user 383 ms, sys: 34.4 ms, total: 418 ms\n", - "Wall time: 2.76 s\n" - ] - } - ], - "source": [ - "%%time\n", - "freq = contact_map.DaskContactTrajectory(\n", - " client=client,\n", - " query=yyg,\n", - " haystack=protein,\n", - " filename=\"data/gsk3b_example.h5\",\n", - ")\n", - "# top must be given as keyword (passed along to mdtraj.load)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Note that on a single machine (shared memory) this may not improve performance. That is because the single-frame aspect of this calculation is already parallelized with OpenMP, and will therefore use all cores on the machine.\n", - "\n", - "Next we check that we're still getting the same results:" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "100" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# did it add up to give us the right number of frames?\n", - "len(freq)" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "# do we get a familiar-looking residue map for rolling averages?\n", - "rolling_frequencies = freq.rolling_frequency(window_size=30, step=14)\n", - "rolling_frequencies\n", - "\n", - "fig, axs = plt.subplots(3, 2, figsize=(12, 10))\n", - "for ax, freq in zip(axs.flatten(), rolling_frequencies):\n", - " freq.residue_contacts.plot_axes(ax=ax)\n", - " ax.set_xlim(*freq.query_residue_range);" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [], - "source": [ - "# Shutdown the cluster and client\n", - "client.shutdown()\n", - "client.close() # This line is needed to prevent a timeout error after 10 seconds" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.8.6" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/examples/exporting_data.ipynb b/examples/exporting_data.ipynb new file mode 100644 index 0000000..8c8ff24 --- /dev/null +++ b/examples/exporting_data.ipynb @@ -0,0 +1,601 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "dried-grass", + "metadata": {}, + "source": [ + "# Exporting data to other tools" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "weird-logistics", + "metadata": {}, + "outputs": [], + "source": [ + "%matplotlib inline\n", + "import matplotlib.pyplot as plt\n", + "import mdtraj as md\n", + "traj = md.load(\"5550217/kras.xtc\", top=\"5550217/kras.pdb\")\n", + "topology = traj.topology" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "paperback-nutrition", + "metadata": {}, + "outputs": [], + "source": [ + "from contact_map import ContactFrequency" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "opening-whole", + "metadata": {}, + "outputs": [], + "source": [ + "traj_contacts = ContactFrequency(traj)" + ] + }, + { + "cell_type": "markdown", + "id": "vocational-petroleum", + "metadata": {}, + "source": [ + "## Exporting contact data\n", + "\n", + "Contact Map Explorer makes it easy for you to get your contact data into other formats, so that you can use the tools you're already familiar with to perform your analysis. Note that the residues and atoms of these report as integers, so what you have is the index of the object in the MDTraj topology," + ] + }, + { + "cell_type": "markdown", + "id": "herbal-forum", + "metadata": {}, + "source": [ + "### SciPy sparse matrix" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "missing-military", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "<219x219 sparse matrix of type ''\n", + "\twith 3034 stored elements in Dictionary Of Keys format>" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "traj_contacts.residue_contacts.sparse_matrix" + ] + }, + { + "cell_type": "markdown", + "id": "assigned-williams", + "metadata": {}, + "source": [ + "### Pandas dataframe" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "therapeutic-windows", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
0123456789...209210211212213214215216217218
0NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN...NaNNaNNaNNaNNaNNaNNaNNaNNaN0.019802
1NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN...NaNNaNNaNNaN0.009901NaNNaNNaNNaNNaN
2NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN...NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN
3NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN...NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN
4NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN...NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN
..................................................................
214NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN...NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN
215NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN...NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN
216NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN...NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN
217NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN...NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN
2180.019802NaNNaNNaNNaNNaNNaNNaNNaNNaN...NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN
\n", + "

219 rows × 219 columns

\n", + "
" + ], + "text/plain": [ + " 0 1 2 3 4 5 6 7 8 9 ... 209 210 \\\n", + "0 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN \n", + "1 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN \n", + "2 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN \n", + "3 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN \n", + "4 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN \n", + ".. ... ... ... ... ... ... ... ... ... ... ... ... ... \n", + "214 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN \n", + "215 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN \n", + "216 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN \n", + "217 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN \n", + "218 0.019802 NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN \n", + "\n", + " 211 212 213 214 215 216 217 218 \n", + "0 NaN NaN NaN NaN NaN NaN NaN 0.019802 \n", + "1 NaN NaN 0.009901 NaN NaN NaN NaN NaN \n", + "2 NaN NaN NaN NaN NaN NaN NaN NaN \n", + "3 NaN NaN NaN NaN NaN NaN NaN NaN \n", + "4 NaN NaN NaN NaN NaN NaN NaN NaN \n", + ".. ... ... ... ... ... ... ... ... \n", + "214 NaN NaN NaN NaN NaN NaN NaN NaN \n", + "215 NaN NaN NaN NaN NaN NaN NaN NaN \n", + "216 NaN NaN NaN NaN NaN NaN NaN NaN \n", + "217 NaN NaN NaN NaN NaN NaN NaN NaN \n", + "218 NaN NaN NaN NaN NaN NaN NaN NaN \n", + "\n", + "[219 rows x 219 columns]" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "traj_contacts.residue_contacts.df" + ] + }, + { + "cell_type": "markdown", + "id": "external-alabama", + "metadata": {}, + "source": [ + "### Direct access to the `Counter`\n", + "\n", + "For `ContactFrequency` and `ContactDifference` you can directly access the internal [collections.Counter](https://docs.python.org/3/library/collections.html#collections.Counter) object that stores the contact frequency. This `Counter` is a mapping of a `frozenset` of the MDTraj indices (`int`s) to the frequency." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "acoustic-radio", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{frozenset({15, 57}): 0.9900990099009901,\n", + " frozenset({150, 153}): 0.9405940594059405,\n", + " frozenset({83, 116}): 1.0,\n", + " frozenset({111, 154}): 0.5841584158415841,\n", + " frozenset({129, 133}): 1.0}" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "counter = traj_contacts.residue_contacts.counter\n", + "dict(list(counter.items())[:5]) # to illustrate a few" + ] + }, + { + "cell_type": "markdown", + "id": "immune-cooper", + "metadata": {}, + "source": [ + "### Python pickle file\n", + "\n", + "The approaches listed above give you a way to export the contact matrix. But when you do that, you lose the information connecting residue contacts to atom contacts. Sometimes you just want to export your data so you can load it up later to continue your analysis. In this case, the `save_to_file` method, which uses Python's `pickle` module, is the best tool." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "african-london", + "metadata": {}, + "outputs": [], + "source": [ + "traj_contacts.save_to_file(\"contacts.p\")" + ] + }, + { + "cell_type": "markdown", + "id": "speaking-research", + "metadata": {}, + "source": [ + "This is also allows you to reload your data. Because the other approaches can't preserve all the information we store, we cannot recreate our objects from, for example, a SciPy sparse matrix." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "bulgarian-massachusetts", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ContactFrequency.from_file(\"contacts.p\")" + ] + }, + { + "cell_type": "markdown", + "id": "close-backup", + "metadata": {}, + "source": [ + "### JSON string\n", + "\n", + "You can also save all the information, including information connecting residue contacts to atom contacts, as a JSON string. This is useful if you need to transfer a `ContactFrequency` to another machine during a parallelized analysis. It can also be written to disk, though the `pickle` format is likely to be more efficient." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "constant-fetish", + "metadata": {}, + "outputs": [], + "source": [ + "json_str = traj_contacts.to_json()" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "modified-indicator", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import json\n", + "ContactFrequency.from_json(json_str)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/integrations.ipynb b/examples/integrations.ipynb deleted file mode 100644 index 14f0257..0000000 --- a/examples/integrations.ipynb +++ /dev/null @@ -1,842 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Integrations with other tools" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "%matplotlib inline\n", - "import matplotlib.pyplot as plt\n", - "import mdtraj as md\n", - "traj = md.load(\"5550217/kras.xtc\", top=\"5550217/kras.pdb\")\n", - "topology = traj.topology" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "from contact_map import ContactFrequency" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [], - "source": [ - "traj_contacts = ContactFrequency(traj)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Exporting contact data\n", - "\n", - "Contact Map Explorer makes it easy for you to get your contact data into other formats, so that you can use the tools you're already familiar with to perform your analysis. Note that the residues and atoms of these report as integers, so what you have is the index of the object in the MDTraj topology," - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### SciPy sparse matrix" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "<219x219 sparse matrix of type ''\n", - "\twith 3034 stored elements in Dictionary Of Keys format>" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "traj_contacts.residue_contacts.sparse_matrix" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Pandas dataframe" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
0123456789...209210211212213214215216217218
0NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN...NaNNaNNaNNaNNaNNaNNaNNaNNaN0.019802
1NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN...NaNNaNNaNNaN0.009901NaNNaNNaNNaNNaN
2NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN...NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN
3NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN...NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN
4NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN...NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN
..................................................................
214NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN...NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN
215NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN...NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN
216NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN...NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN
217NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN...NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN
2180.019802NaNNaNNaNNaNNaNNaNNaNNaNNaN...NaNNaNNaNNaNNaNNaNNaNNaNNaNNaN
\n", - "

219 rows × 219 columns

\n", - "
" - ], - "text/plain": [ - " 0 1 2 3 4 5 6 7 8 9 ... 209 210 \\\n", - "0 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN \n", - "1 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN \n", - "2 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN \n", - "3 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN \n", - "4 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN \n", - ".. ... ... ... ... ... ... ... ... ... ... ... ... ... \n", - "214 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN \n", - "215 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN \n", - "216 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN \n", - "217 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN \n", - "218 0.019802 NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN \n", - "\n", - " 211 212 213 214 215 216 217 218 \n", - "0 NaN NaN NaN NaN NaN NaN NaN 0.019802 \n", - "1 NaN NaN 0.009901 NaN NaN NaN NaN NaN \n", - "2 NaN NaN NaN NaN NaN NaN NaN NaN \n", - "3 NaN NaN NaN NaN NaN NaN NaN NaN \n", - "4 NaN NaN NaN NaN NaN NaN NaN NaN \n", - ".. ... ... ... ... ... ... ... ... \n", - "214 NaN NaN NaN NaN NaN NaN NaN NaN \n", - "215 NaN NaN NaN NaN NaN NaN NaN NaN \n", - "216 NaN NaN NaN NaN NaN NaN NaN NaN \n", - "217 NaN NaN NaN NaN NaN NaN NaN NaN \n", - "218 NaN NaN NaN NaN NaN NaN NaN NaN \n", - "\n", - "[219 rows x 219 columns]" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "traj_contacts.residue_contacts.df" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Direct access to the `Counter`\n", - "\n", - "For `ContactFrequency` and `ContactDifference` you can directly access the internal [collections.Counter](https://docs.python.org/3/library/collections.html#collections.Counter) object that stores the contact frequency. This `Counter` is a mapping of a `frozenset` of the MDTraj indices (`int`s) to the frequency." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{frozenset({15, 57}): 0.9900990099009901,\n", - " frozenset({150, 153}): 0.9405940594059405,\n", - " frozenset({83, 116}): 1.0,\n", - " frozenset({111, 154}): 0.5841584158415841,\n", - " frozenset({129, 133}): 1.0}" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "counter = traj_contacts.residue_contacts.counter\n", - "dict(list(counter.items())[:5]) # to illustrate a few" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Python pickle file\n", - "\n", - "The approaches listed above give you a way to export the contact matrix. But when you do that, you lose the information connecting residue contacts to atom contacts. Sometimes you just want to export your data so you can load it up later to continue your analysis. In this case, the `save_to_file` method, which uses Python's `pickle` module, is the best tool." - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [], - "source": [ - "traj_contacts.save_to_file(\"contacts.p\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This is also allows you to reload your data. Because the other approaches can't preserve all the information we store, we cannot recreate our objects from, for example, a SciPy sparse matrix." - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "ContactFrequency.from_file(\"contacts.p\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### JSON string\n", - "\n", - "You can also save all the information, including information connecting residue contacts to atom contacts, as a JSON string. This is useful if you need to transfer a `ContactFrequency` to another machine during a parallelized analysis. It can also be written to disk, though the `pickle` format is likely to be more efficient." - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [], - "source": [ - "json_str = traj_contacts.to_json()" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "import json\n", - "ContactFrequency.from_json(json_str)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Improving performance\n", - "\n", - "Much of the core computational effort in Contact Map Explorer is performed by MDTraj, which uses OpenMP during the nearest-neighbors calculation. This already provides excellent performance for a bottleneck in the contact map creation process. However, Contact Map Explorer also has a few other tricks to further enhance performance." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Dask\n", - "\n", - "For multi-frame contact maps and contact trajectories, Contact Map Explorer can use Dask to parallelize across frames. Note that Dask is not required to install Contact Map Explorer, so you must install Dask separately to benefit from it.\n", - "\n", - "When using Dask, a few things are different:\n", - "\n", - "1. You need to provide a `distributed.Client` to the `DaskContactFrequency`.\n", - "2. You need to provide the filename (and any other arguments needed by MDTraj, instead of the trajectory itself.\n", - "\n", - "Dask might not give any performance boost on a single machine, but can be very useful if parallelizing across multiple machines. Because this directly takes a `Client`, it is easy to interface this with tools like [dask-jobqueue](https://jobqueue.dask.org/en/latest/)." - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n", - "\n", - "\n", - "\n", - "
\n", - "

Client

\n", - "\n", - "
\n", - "

Cluster

\n", - "
    \n", - "
  • Workers: 4
  • \n", - "
  • Cores: 4
  • \n", - "
  • Memory: 17.18 GB
  • \n", - "
\n", - "
" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 11, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "from contact_map import DaskContactFrequency\n", - "from distributed import Client\n", - "client = Client()\n", - "client" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "CPU times: user 685 ms, sys: 75.1 ms, total: 760 ms\n", - "Wall time: 6.58 s\n" - ] - } - ], - "source": [ - "%%time\n", - "freq = DaskContactFrequency(\n", - " client=client,\n", - " filename=\"5550217/kras.xtc\",\n", - " top=\"5550217/kras.pdb\"\n", - ")\n", - "# top must be given as keyword (passed along to mdtraj.load)" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "101" - ] - }, - "execution_count": 13, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# did it add up to give us the right number of frames?\n", - "freq.n_frames" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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\n", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], - "source": [ - "# do we get a familiar-looking residue map?\n", - "fig, ax = freq.residue_contacts.plot()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### \"Atom slicing\"\n", - "\n", - "One of the internal tricks to improve performance is that we take the MDTraj trajectory that has been provided, and shrink it down to only the atoms that are included in the `query` and `haystack`. We refer to this as \"atom slicing\" (following terminology from MDTraj, although for performance reasons we actually implement it internally).\n", - "\n", - "In most cases, you will want to atom slice. However, there are some cases where atom slicing can slow down your analysis -- mainly if the atoms needed for the contact map are *almost* all the atoms in the trajectory. For this, you can turn atom slicing off." - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": {}, - "outputs": [], - "source": [ - "# use all the atoms except atom 0\n", - "used_atoms = list(range(1, topology.n_atoms))" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "CPU times: user 185 ms, sys: 10.6 ms, total: 196 ms\n", - "Wall time: 196 ms\n" - ] - } - ], - "source": [ - "%%time\n", - "# with atom slicing\n", - "frame_contacts = ContactFrequency(traj[0], query=used_atoms,\n", - " haystack=used_atoms)" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "metadata": {}, - "outputs": [], - "source": [ - "# disable atom slicing\n", - "ContactFrequency._class_use_atom_slice = False" - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "CPU times: user 162 ms, sys: 10.1 ms, total: 173 ms\n", - "Wall time: 189 ms\n" - ] - } - ], - "source": [ - "%%time\n", - "# without atom slicing\n", - "frame_contacts = ContactFrequency(traj[0], query=used_atoms,\n", - " haystack=used_atoms)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Note that this example is the worst case: the overhead for atom slicing occurs only once for an entire trajectory. However, if you're generating many single-frame contact maps, this could be relevant to you." - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.7.3" - }, - "toc": { - "base_numbering": 1, - "nav_menu": {}, - "number_sections": true, - "sideBar": true, - "skip_h1_title": true, - "title_cell": "Table of Contents", - "title_sidebar": "Contents", - "toc_cell": false, - "toc_position": {}, - "toc_section_display": true, - "toc_window_display": false - }, - "varInspector": { - "cols": { - "lenName": 16, - "lenType": 16, - "lenVar": 40 - }, - "kernels_config": { - "python": { - "delete_cmd_postfix": "", - "delete_cmd_prefix": "del ", - "library": "var_list.py", - "varRefreshCmd": "print(var_dic_list())" - }, - "r": { - "delete_cmd_postfix": ") ", - "delete_cmd_prefix": "rm(", - "library": "var_list.r", - "varRefreshCmd": "cat(var_dic_list()) " - } - }, - "types_to_exclude": [ - "module", - "function", - "builtin_function_or_method", - "instance", - "_Feature" - ], - "window_display": false - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/examples/performance.ipynb b/examples/performance.ipynb new file mode 100644 index 0000000..1e7ecbc --- /dev/null +++ b/examples/performance.ipynb @@ -0,0 +1,385 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "satisfactory-stanley", + "metadata": {}, + "source": [ + "# Improving performance" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "rural-wings", + "metadata": {}, + "outputs": [], + "source": [ + "%matplotlib inline\n", + "# dask and distributed are extra installs\n", + "from dask.distributed import Client, LocalCluster\n", + "import matplotlib.pyplot as plt\n", + "import mdtraj as md\n", + "traj = md.load(\"5550217/kras.xtc\", top=\"5550217/kras.pdb\")\n", + "topology = traj.topology" + ] + }, + { + "cell_type": "markdown", + "id": "boxed-fiction", + "metadata": {}, + "source": [ + "Much of the core computational effort in Contact Map Explorer is performed by MDTraj, which uses OpenMP during the nearest-neighbors calculation. This already provides excellent performance for a bottleneck in the contact map creation process. However, Contact Map Explorer also has a few other tricks to further enhance performance." + ] + }, + { + "cell_type": "markdown", + "id": "assigned-cause", + "metadata": {}, + "source": [ + "## Dask\n", + "\n", + "For multi-frame contact maps and contact trajectories, Contact Map Explorer can use Dask to parallelize across frames. Note that Dask is not required to install Contact Map Explorer, so you must install Dask separately to benefit from it.\n", + "\n", + "When using Dask, a few things are different:\n", + "\n", + "1. You need to provide a `distributed.Client` to the `DaskContactFrequency` or `DaskContactTrajectory`.\n", + "2. You need to provide the filename (and any other arguments needed by MDTraj, instead of the trajectory itself.\n", + "\n", + "Dask might not give any performance boost on a single machine, but can be very useful if parallelizing across multiple machines. Because this directly takes a `Client`, it is easy to interface this with tools like [dask-jobqueue](https://jobqueue.dask.org/en/latest/)." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "southeast-cement", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "
\n", + "

Client

\n", + "\n", + "
\n", + "

Cluster

\n", + "
    \n", + "
  • Workers: 4
  • \n", + "
  • Cores: 12
  • \n", + "
  • Memory: 16.39 GB
  • \n", + "
\n", + "
" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from contact_map import DaskContactFrequency, DaskContactTrajectory\n", + "from distributed import Client\n", + "client = Client()\n", + "client" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "practical-burns", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CPU times: user 281 ms, sys: 36.7 ms, total: 317 ms\n", + "Wall time: 3.82 s\n" + ] + } + ], + "source": [ + "%%time\n", + "freq = DaskContactFrequency(\n", + " client=client,\n", + " filename=\"5550217/kras.xtc\",\n", + " top=\"5550217/kras.pdb\"\n", + ")\n", + "# top must be given as keyword (passed along to mdtraj.load)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "binding-planner", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "101" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# did it add up to give us the right number of frames?\n", + "freq.n_frames" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "daily-candy", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "# do we get a familiar-looking residue map?\n", + "fig, ax = freq.residue_contacts.plot()" + ] + }, + { + "cell_type": "markdown", + "id": "minus-delivery", + "metadata": {}, + "source": [ + "The same can be done for a `DaskContactTrajectory`. Here, we use the data from and compare to `contact_trajectory.ipynb`" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "flying-aquatic", + "metadata": {}, + "outputs": [], + "source": [ + "traj_2 = md.load(\"data/gsk3b_example.h5\")\n", + "\n", + "topology_2 = traj_2.topology\n", + "yyg = topology_2.select('resname YYG and element != \"H\"')\n", + "protein = topology_2.select('protein and element != \"H\"')" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "received-switzerland", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CPU times: user 380 ms, sys: 10.7 ms, total: 391 ms\n", + "Wall time: 1.51 s\n" + ] + } + ], + "source": [ + "%%time\n", + "dctraj = DaskContactTrajectory(\n", + " client=client,\n", + " query=yyg,\n", + " haystack=protein,\n", + " filename=\"data/gsk3b_example.h5\",\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "guilty-taylor", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "100" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# did it add up to give us the right number of frames?\n", + "len(dctraj)" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "novel-advance", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "# do we get a familiar-looking residue map for rolling averages?\n", + "rolling_frequencies = dctraj.rolling_frequency(window_size=30, step=14)\n", + "rolling_frequencies\n", + "\n", + "fig, axs = plt.subplots(3, 2, figsize=(12, 10))\n", + "for ax, freq in zip(axs.flatten(), rolling_frequencies):\n", + " freq.residue_contacts.plot_axes(ax=ax)\n", + " ax.set_xlim(*freq.query_residue_range);" + ] + }, + { + "cell_type": "markdown", + "id": "banner-project", + "metadata": {}, + "source": [ + "## \"Atom slicing\"\n", + "\n", + "One of the internal tricks to improve performance is that we take the MDTraj trajectory that has been provided, and shrink it down to only the atoms that are included in the `query` and `haystack`. We refer to this as \"atom slicing\" (following terminology from MDTraj, although for performance reasons we actually implement it internally).\n", + "\n", + "In most cases, you will want to atom slice. However, there are some cases where atom slicing can slow down your analysis -- mainly if the atoms needed for the contact map are *almost* all the atoms in the trajectory. For this, you can turn atom slicing off." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "following-satin", + "metadata": {}, + "outputs": [], + "source": [ + "from contact_map import ContactFrequency" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "worst-journalism", + "metadata": {}, + "outputs": [], + "source": [ + "# use all the atoms except atom 0\n", + "used_atoms = list(range(1, topology.n_atoms))" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "special-insurance", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CPU times: user 314 ms, sys: 3.78 ms, total: 318 ms\n", + "Wall time: 118 ms\n" + ] + } + ], + "source": [ + "%%time\n", + "# with atom slicing\n", + "frame_contacts = ContactFrequency(traj[0], query=used_atoms,\n", + " haystack=used_atoms)" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "advised-russian", + "metadata": {}, + "outputs": [], + "source": [ + "# disable atom slicing\n", + "ContactFrequency._class_use_atom_slice = False" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "robust-aviation", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CPU times: user 461 ms, sys: 20.6 ms, total: 481 ms\n", + "Wall time: 289 ms\n" + ] + } + ], + "source": [ + "%%time\n", + "# without atom slicing\n", + "frame_contacts = ContactFrequency(traj[0], query=used_atoms,\n", + " haystack=used_atoms)" + ] + }, + { + "cell_type": "markdown", + "id": "ready-wallpaper", + "metadata": {}, + "source": [ + "Note that this example is the worst case: the overhead for atom slicing occurs only once for an entire trajectory. However, if you're generating many single-frame contact maps, this could be relevant to you." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}