Source code for molpher.core.ExplorationTree

# Copyright (c) 2016 Martin Sicho
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

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.morphing import MorphCollector
from molpher.core.morphing.operators import MorphingOperator
from molpher.core.operations import TraverseOper
from molpher.core.operations.TraverseOper import Callback


[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 tuple(ret)
[docs] @staticmethod def create(tree_data=None, source=None, target=None): """ Create an exploration 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 or a molecule directly (uses a copy not the instance itself) :type source: `str` or :class:`~molpher.core.MolpherMol.MolpherMol` :param target: SMILES of the target molecule :type target: `str` or :class:`~molpher.core.MolpherMol.MolpherMol` .. 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: if type(source) == str: source = MolpherMol(source) if type(target) == str: target = MolpherMol(target) ret = super(ExplorationTree, ExplorationTree).create(source, target) elif source: if type(source) == str: source = MolpherMol(source) ret = super(ExplorationTree, ExplorationTree).create(source) else: raise AttributeError('Invalid set of parameters specified.') if not ret: raise RuntimeError('No tree initilized.') ret.callback_class = Callback 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: self_data = self.asData() self_data.param_dict = params self.update(self_data) @property def morphing_operators(self): ret = self.getMorphingOperators() if ret: for x in ret: x.__class__ = MorphingOperator return ret @morphing_operators.setter def morphing_operators(self, opers): self.setMorphingOperators(opers) @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 mol_count(self): def count_mols(mol): count_mols.count += 1 count_mols.count = 0 self.traverse(count_mols) return count_mols.count @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 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) @property def source(self): return self.params["source"] @property def target(self): return self.params["target"]
[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. :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 generateMorphs(self, collectors=None): if not collectors: super(ExplorationTree, self).generateMorphs() else: _collectors = [] for collector in collectors: _collectors.append(MorphCollector(collector)) super(ExplorationTree, self).generateMorphs(_collectors)
[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())
[docs] def fetchPathTo(self, smiles): if self.hasMol(smiles): target = self.fetchMol(smiles) path = [target] while True: parent_smiles = path[-1].parent_smiles if parent_smiles: path.append(self.fetchMol(parent_smiles)) else: break path.reverse() return path else: return None