Source code for molpher.core.ExplorationTree

import molpher
import warnings

from molpher.core.ExplorationData import ExplorationData
from molpher.core.MolpherMol import MolpherMol
from molpher.core._utils import shorten_repr
from molpher.core.operations import TraverseOper
from molpher.core.operations.callbacks import TraverseCallback

[docs]class Callback(TraverseCallback): """ :param callback: the callable to call every time a molecule is encountered during traversal :type callback: any callable object with one required parameter Basic callback class used to traverse the tree with the `ExplorationTree.traverse()` method. It registers a callable and calls it every time a :term:`morph` is processed. """ def __init__(self, callback): super(Callback, self).__init__() self._callback = callback def __call__(self, morph): """ This method is called by the C++ code. It just calls the registered callback for a :term:`morph` :param morph: morph in the currently processed tree :type morph: `molpher.swig_wrappers.core.MolpherMol` """ morph.__class__ = MolpherMol self._callback(morph)
[docs]class ExplorationTree(molpher.swig_wrappers.core.ExplorationTree): """ This a specialized version of the `molpher.swig_wrappers.core.ExplorationTree` proxy class. It implements some additional functionality for ease of use from Python. .. attention:: This class has no constructor defined. Use the :meth:`create` factory method to obtain instances of this class. .. seealso:: `molpher.swig_wrappers.core.ExplorationTree` """ def __repr__(self): return shorten_repr(ExplorationTree, self) def __init__(self): super(ExplorationTree, self).__init__() @staticmethod def _cast_mols(mols): ret = [ x for x in mols ] for morph in ret: morph.__class__ = MolpherMol return ret @staticmethod
[docs] def create(tree_data=None, source=None, target=None, callback_class=Callback): """ create tree :param tree_data: the morphing parameters (optional if ``source`` and ``target`` are specified) :type tree_data: `molpher.swig_wrappers.core.ExplorationData` (or its derived class), a `dict` of parameters (in the same format as in :class:`~molpher.core.ExplorationData.ExplorationData`'s constructor) or a path to a :term:`XML template` or a :term:`tree snapshot` :param source: SMILES of the source molecule :type source: `str` :param target: SMILES of the target molecule :type target: `str` :param callback_class: the class to use when making a callback using the :meth:`traverse` method :type callback_class: any class derived from `Callback` .. note:: When ``tree_data`` is specified, ``source`` and ``target`` are always ignored. """ ret = None if tree_data and source and target: warnings.warn( "Both tree_data and source and target specified. Using the values in parameters..." , RuntimeWarning ) if tree_data and (isinstance(tree_data, molpher.wrappers.ExplorationData) or type(tree_data) == str): ret = super(ExplorationTree, ExplorationTree).create(tree_data) elif tree_data: _params = ExplorationData(**tree_data) ret = super(ExplorationTree, ExplorationTree).create(_params) elif source and target: ret = super(ExplorationTree, ExplorationTree).create(source, target) else: raise AttributeError('Invalid set of parameters specified.') if not ret: raise RuntimeError('No tree initilized.') ret.callback_class = callback_class ret.__class__ = ExplorationTree return ret
@property def params(self): """ A dictionary representing the current :term:`exploration parameters`. It is possible to assign a new dictionary (or an instance of the `molpher.swig_wrappers.core.ExplorationData` class) to update the current parameters. .. note:: Only parameters defined in the supplied dictionary are changed and if an instance of `molpher.swig_wrappers.core.ExplorationData` is supplied only the parameters are read from it (the tree structure remains the same). :return: current parameters :rtype: `dict` """ data = ExplorationData(other=self.asData()) return data.param_dict @params.setter def params(self, params): if isinstance(params, molpher.wrappers.ExplorationData): self.update(params) else: new_params = self.params new_params.update(params) data = ExplorationData(**new_params) self.update(data) @property def generation_count(self): """ :return: Number of :term:`morph generations <morph generation>` connected to the tree so far. :rtype: `int` """ return self.getGenerationCount() @property def path_found(self): """ :return: `True` if the :term:`target molecule` is present in the tree, `False` otherwise. :rtype: `bool` """ return self.isPathFound() @property def leaves(self): """ :return: the current leaves of the tree :rtype: `tuple` of :class:`~molpher.core.MolpherMol.MolpherMol` instances """ return tuple(self._cast_mols(self.fetchLeaves())) @property def candidates(self): """ :return: the :term:`candidate morphs` (:term:`morphs <morph>` generated by a single call to `generateMorphs()`.) :rtype: `tuple` of :class:`~molpher.core.MolpherMol.MolpherMol` instances """ return tuple(self._cast_mols(self.getCandidateMorphs())) @property def candidates_mask(self): """ A `tuple` of `bool` objects that serve as means of filtering the :term:`candidate morphs`. Each :term:`morph` in `candidates` has a `bool` variable assigned to it in this `tuple` -- only morphs with `True` at the appropriate position are added to the tree when :meth:`extend` is called. It can be changed by assigning a new `tuple` or a call to `setCandidateMorphsMask()`. :return: currently selected :term:`candidate morphs` represented as a `tuple` of `bool` objects :rtype: `tuple` """ return self.getCandidateMorphsMask() @candidates_mask.setter def candidates_mask(self, mask): self.setCandidateMorphsMask(mask) @property def thread_count(self): """ :return: maximum number of threads this instance will use :rtype: `int` """ return self.getThreadCount() @thread_count.setter def thread_count(self, val): self.setThreadCount(val)
[docs] def fetchMol(self, canonSMILES): """ Returns a molecule from the tree using a canonical SMILES string. Raises a `RuntimeError` if the molecule is not found. :param canonSMILES: SMILES string of the molecule to fetch :type canonSMILES: `str` :return: molecule from a tree :rtype: :class:`~molpher.core.MolpherMol.MolpherMol` """ ret = super(ExplorationTree, self).fetchMol(canonSMILES) ret.__class__ = MolpherMol return ret
[docs] def traverse(self, callback, start_mol = None): """ This method can be used to traverse the whole tree structure (or just a subtree) starting from the root to leaves. It takes a callback function that accepts a single required argument and traverses the whole tree starting from its root (or root of a specified subtree -- see ``start_mol``) and calls the supplied callback with with encountered morph as its parameter. .. warning:: The tree traversal is implemented in C++ and the callback to Python is realized using `SWIG's director feature <http://www.swig.org/Doc3.0/Python.html#Python_directors>`_, which makes it possible to keep the implementation concurrent and efficient. However, there is a problem: If a reference to the :py:class:`~molpher.core.MolpherMol.MolpherMol` instance is saved into a variable that outlives the call to the callback function, this reference then becomes invalid when the call is finished. Therefore, doing something like this: .. code-block:: python var = None def callback(morph): if var: print("Previous:", var.smiles) var = morph tree.traverse(callback) will likely result in a segmentation fault upon a second call to the callback function, because the object referenced by ``var`` will no longer refer to valid memory. This is likely a result of SWIG freeing the pointer without taking the existing reference from Python into account. One way to work around this is to just save the SMILES string of the molecule and then fetch the reference to it using the :meth:`fetchMol` method. :param callback: the callback to call :type callback: a callable object that takes a single argument :param start_mol: the root of a subtree to explore as canonical SMILES or :py:class:`~molpher.core.MolpherMol.MolpherMol` instance :type start_mol: `str` or :py:class:`~molpher.core.MolpherMol.MolpherMol` """ if start_mol and type(start_mol) == MolpherMol: TraverseOper(self, self.callback_class(callback), start_mol)() elif start_mol: mol = self.fetchMol(start_mol) TraverseOper(self, self.callback_class(callback), mol)() else: cb = self.callback_class(callback) TraverseOper(self, cb)()
[docs] def asData(self): """ :return: the tree as an :class:`~molpher.core.ExplorationData.ExplorationData` instance :rtype: :class:`~molpher.core.ExplorationData.ExplorationData` """ return ExplorationData(other=super(ExplorationTree, self).asData())