Skip to content

Commit

Permalink
Interactions API (#377)
Browse files Browse the repository at this point in the history
* Add new Interactions module to API for persistent interaction lines.

* Add `workspace_updated` callback for `update_workspace` calls

* Add `current_molecule` and `current_conformer` properties to `Complex`
  • Loading branch information
mjrosengrant authored Nov 17, 2023
1 parent bcc243a commit ba44d0a
Show file tree
Hide file tree
Showing 23 changed files with 712 additions and 34 deletions.
12 changes: 12 additions & 0 deletions nanome/_internal/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ class Commands(CommandEnum):
bonds_add_done = auto()
dssp_add_done = auto()
complex_updated = auto()
workspace_updated = auto()
selection_changed = auto()
compute_hbonds_done = auto()

Expand Down Expand Up @@ -93,6 +94,10 @@ class Commands(CommandEnum):
set_shape_result = auto()
delete_shape_result = auto()

# Interactions
create_interactions_result = auto()
get_interactions_response = auto()

# Other
add_volume_done = auto()
load_file_done = auto()
Expand Down Expand Up @@ -174,6 +179,12 @@ class Messages(CommandEnum):
set_shape = auto()
delete_shape = auto()

# Interactions
create_interactions = auto()
get_interactions = auto()
delete_interactions = auto()
interactions_calc_done = auto()

# Other
add_volume = auto()
open_url = auto()
Expand Down Expand Up @@ -201,6 +212,7 @@ class IntegrationCommands(CommandEnum):
import_file = auto()
export_smiles = auto()
import_smiles = auto()
run_interactions = auto()


class Permissions(CommandEnum):
Expand Down
6 changes: 3 additions & 3 deletions nanome/_internal/network/plugin_network.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def __init__(self, plugin, session_id, queue_in, queue_out, serializer, plugin_i
self._serializer._plugin_id = plugin_id
self._plugin_id = plugin_id
self._command_id = 0
self.__version_table = version_table
self._version_table = version_table

CachedImageField.session = session_id
PluginNetwork._instance = self
Expand Down Expand Up @@ -62,7 +62,7 @@ def send_connect(cls, code, arg):

@classmethod
def send(cls, code, arg, expects_response):
return cls.__send(code, cls._instance.__version_table, arg, expects_response)
return cls.__send(code, cls._instance._version_table, arg, expects_response)

@classmethod
def __send(cls, code, version_table, arg, expects_response):
Expand Down Expand Up @@ -100,7 +100,7 @@ def _receive(self):
return False

received_object, command_hash, request_id = self._serializer.deserialize_command(
payload, self.__version_table)
payload, self._version_table)
if received_object == None and command_hash == None and request_id == None:
return True # Happens if deserialize_command returns None, an error message is already displayed in that case

Expand Down
1 change: 1 addition & 0 deletions nanome/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@
from . import shapes
from . import macro
from . import user
from . import interactions
1 change: 1 addition & 0 deletions nanome/api/integration/integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ def __init__(self):
self.import_file = None
self.export_smiles = None
self.import_smiles = None
self.run_interactions = None

def _call(self, name, request):
callback = getattr(self, name, None)
Expand Down
17 changes: 16 additions & 1 deletion nanome/api/integration/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,20 @@ def deserialize(self, version, context):
return strings


class RunInteractionsSerializer(TypeSerializer):
def version(self):
return 0

def name(self):
return "RunInteractions"

def serialize(self, version, value, context):
pass

def deserialize(self, version, context):
return


class RemoveHydrogenSerializer(TypeSerializer):
def __init__(self):
self.array_serializer = ArrayField()
Expand Down Expand Up @@ -299,7 +313,8 @@ class IntegrationSerializer(TypeSerializer):
Hashes.IntegrationHashes[IntegrationCommands.import_file]: ImportFileSerializer(),
Hashes.IntegrationHashes[IntegrationCommands.generate_molecule_image]: GenerateMoleculeImageSerializer(),
Hashes.IntegrationHashes[IntegrationCommands.export_smiles]: ExportSmilesSerializer(),
Hashes.IntegrationHashes[IntegrationCommands.import_smiles]: ImportSmilesSerializer()
Hashes.IntegrationHashes[IntegrationCommands.import_smiles]: ImportSmilesSerializer(),
Hashes.IntegrationHashes[IntegrationCommands.run_interactions]: RunInteractionsSerializer()
}

def version(self):
Expand Down
19 changes: 19 additions & 0 deletions nanome/api/interactions/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from . import *
from nanome._internal.enums import Commands, Messages
from nanome.util import simple_callbacks
from .interaction import Interaction # noqa: F401
from . import serializers, messages # noqa: F401


registered_commands = [
(Commands.create_interactions_result, messages.CreateInteractions(), simple_callbacks.simple_callback_arg_unpack),
(Commands.get_interactions_response, messages.GetInteractions(), simple_callbacks.simple_callback_arg),
]


registered_messages = [
(Messages.create_interactions, messages.CreateInteractions()),
(Messages.get_interactions, messages.GetInteractions()),
(Messages.delete_interactions, messages.DeleteInteractions()),
(Messages.interactions_calc_done, messages.InteractionsCalcDone()),
]
142 changes: 142 additions & 0 deletions nanome/api/interactions/interaction.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import nanome
from nanome.util import enums
from nanome._internal.network import PluginNetwork
from nanome._internal.enums import Messages


class Interaction(object):
"""
| Class representing a chemical interaction.
:param kind: Enumerator representing the kind of interaction to create
:type kind: :class:`~nanome.util.enums.InteractionType`
:param atom1_idx: Array of integers representing the indices of atoms in group 1
:type atom1_idx: List[int]
:param atom2_idx: Array of integers representing the indices of atoms in group 2
:type atom2_idx: List[int]
:param atom1_conf: Optional conformation for all atoms in group 1
:type atom1_conf: int
:param atom2_conf: Optional conformation for all atoms in group 2
:type atom2_conf: int
"""

def __init__(self, kind=None, atom1_idx_arr=None, atom2_idx_arr=None, atom1_conf=None, atom2_conf=None, visible=True):
self.index = -1
self.kind = kind
self.atom1_idx_arr = atom1_idx_arr
self.atom2_idx_arr = atom2_idx_arr
self.atom1_conformation = atom1_conf
self.atom2_conformation = atom2_conf
self.visible = visible

def upload(self, done_callback=None):
"""
| Upload the interaction to the Nanome App
"""
return self._upload(done_callback)

@classmethod
def upload_multiple(cls, interactions, done_callback=None):
"""
| Upload multiple interactions to the Nanome App
"""
return cls._upload_multiple(interactions, done_callback)

def destroy(self):
"""
| Remove the interaction from the Nanome App and destroy it.
"""
return self._destroy()

@classmethod
def destroy_multiple(cls, interactions):
"""
| Remove multiple interactions from the Nanome App and destroy them.
"""
return cls._destroy_multiple(interactions)

@classmethod
def get(cls, done_callback=None, complexes_idx=None, molecules_idx=None, chains_idx=None,
residues_idx=None, atom_idx=None, type_filter=None):
"""
| Get interactions from Nanome App
| If no structure index is given, all interactions in workspace will be returned
| If any combination of indices is given, all interactions for these sturctures will be returned
:param done_callback: Callback called with the list of interactions received from Nanome
:type done_callback: Callable[List[:class:`~nanome.api.interaction`]]
:param ***_idx: Index or array of indices for a structure type
:type ***_idx: int or List[int]
:param type_filter: Filter to return only one type of interaction
:type type_filter: :class:`~nanome.util.enums.InteractionKind`
"""
args = (
complexes_idx if isinstance(complexes_idx, list) else [complexes_idx] if isinstance(complexes_idx, int) else [],
molecules_idx if isinstance(molecules_idx, list) else [molecules_idx] if isinstance(molecules_idx, int) else [],
chains_idx if isinstance(chains_idx, list) else [chains_idx] if isinstance(chains_idx, int) else [],
residues_idx if isinstance(residues_idx, list) else [residues_idx] if isinstance(residues_idx, int) else [],
atom_idx if isinstance(atom_idx, list) else [atom_idx] if isinstance(atom_idx, int) else [],
type_filter if type_filter is not None else enums.InteractionKind.All
)
return cls._get_interactions(args, done_callback)

def _upload(self, done_callback=None):

def set_callback(line_index):
self.index = line_index
if done_callback is not None:
done_callback(line_index)

id = PluginNetwork._instance.send(Messages.create_interactions, [self], True)
result = nanome.PluginInstance._save_callback(id, set_callback if done_callback else None)
is_async_plugin = nanome.PluginInstance._instance.is_async
if done_callback is None and is_async_plugin:
result.real_set_result = result.set_result
result.set_result = lambda line_index: set_callback(line_index)
def done_callback(line_index): return result.real_set_result(line_index)
return result

@classmethod
def _upload_multiple(cls, interactions, done_callback=None):

def set_callback(indices):
if type(indices) is int:
indices = [indices]
for index, interaction in zip(indices, interactions):
interaction.index = index
if done_callback is not None:
done_callback(indices)

id = PluginNetwork._instance.send(Messages.create_interactions, interactions, True)
result = nanome.PluginInstance._save_callback(id, set_callback if done_callback else None)
if done_callback is None and nanome.PluginInstance._instance.is_async:
result.real_set_result = result.set_result
result.set_result = lambda indices: set_callback(indices)
def done_callback(indices): return result.real_set_result(indices)
return result

def _destroy(self):
PluginNetwork._instance.send(Messages.delete_interactions, [self.index], False)

@classmethod
def _destroy_multiple(cls, interactions):
indices = [x.index for x in interactions]
PluginNetwork._instance.send(Messages.delete_interactions, indices, False)

@classmethod
def _get_interactions(cls, args, done_callback):
def set_callback(interactions=None):
interactions = interactions or []
done_callback(interactions)

id = PluginNetwork._instance.send(Messages.get_interactions, args, True)
fut = nanome.PluginInstance._save_callback(id, set_callback if done_callback else None)
if done_callback is None and nanome.PluginInstance._instance.is_async:
fut.real_set_result = fut.set_result
fut.set_result = lambda interaction_list: set_callback(interaction_list)
def done_callback(interaction_lines): return fut.real_set_result(interaction_lines)
return fut

@classmethod
def signal_calculation_done(cls):
PluginNetwork._instance.send(Messages.interactions_calc_done, None, False)
76 changes: 76 additions & 0 deletions nanome/api/interactions/messages.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import logging
from nanome._internal import serializer_fields
from . import serializers

logger = logging.getLogger(__name__)


class CreateInteractions(serializer_fields.TypeSerializer):
def __init__(self):
self._interaction = serializers.InteractionSerializer()
self._interaction_array = serializer_fields.ArrayField()
self._interaction_array.set_type(self._interaction)

def version(self):
return 1

def name(self):
return "CreateInteractions"

def serialize(self, version, value, context):
context.write_using_serializer(self._interaction_array, value)

def deserialize(self, version, context):
return context.read_long_array()


class DeleteInteractions(serializer_fields.TypeSerializer):
def version(self):
return 1

def name(self):
return "DeleteInteractions"

def serialize(self, version, value, context):
context.write_long_array(value)

def deserialize(self, version, context):
raise NotImplementedError()


class GetInteractions(serializer_fields.TypeSerializer):
def __init__(self):
self._interaction = serializers.InteractionSerializer()
self._interaction_array = serializer_fields.ArrayField()
self._interaction_array.set_type(self._interaction)

def version(self):
return 1

def name(self):
return "GetInteractions"

def serialize(self, version, value, context):
context.write_long_array(value[0])
context.write_long_array(value[1])
context.write_long_array(value[2])
context.write_long_array(value[3])
context.write_long_array(value[4])
context.write_byte(int(value[5]))

def deserialize(self, version, context):
return context.read_using_serializer(self._interaction_array)


class InteractionsCalcDone(serializer_fields.TypeSerializer):
def version(self):
return 0

def name(self):
return "InteractionsCalcDone"

def serialize(self, version, value, context):
pass

def deserialize(self, version, context):
raise NotImplementedError()
Loading

0 comments on commit ba44d0a

Please sign in to comment.