Source code for molpher.core.tests.test_api


# 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 os, sys
import unittest
from io import StringIO, BytesIO

from pkg_resources import resource_filename
from rdkit import Chem

from molpher import random_numbers
from molpher.core import ExplorationTree
from molpher.core import MolpherMol
from molpher.core import ExplorationData
from molpher.core import MolpherAtom
from molpher.core.morphing import AtomLibrary
from molpher.core.morphing import Molpher
from molpher.core.morphing.operators import *
from molpher.core.selectors import *

[docs]class TestAPI(unittest.TestCase):
[docs] @staticmethod def getPathToMol(tree, mol): assert tree.hasMol(mol) path = [] current = mol while current.parent_smiles: path.append(current) current = tree.fetchMol(current.parent_smiles) path.reverse() return path
[docs] def setUp(self): random_numbers.set_random_seed(42) self.test_source = 'CCO' self.test_target = 'C1=COC=C1' self.test_dir = os.path.abspath(resource_filename('molpher.core.tests', 'test_files/')) self.test_template_path = os.path.join(self.test_dir, 'test-template.xml') self.cymene_locked = os.path.join(self.test_dir, 'cymene.sdf') self.ethanol_locked = os.path.join(self.test_dir, 'ethanol.sdf') self.propanol = os.path.join(self.test_dir, 'propanol.sdf') self.remove_bond_test_mol = os.path.join(self.test_dir, 'remove_bond_test_mol.sdf') self.alanine = os.path.join(self.test_dir, 'alanine.sdf') self.isopropylphenol = os.path.join(self.test_dir, 'isopropylphenol.sdf') self.contract_bond_test_mol = os.path.join(self.test_dir, 'contract_bond_test_mol.sdf') self.reroute_test_mol = os.path.join(self.test_dir, 'reroute_test_mol.sdf') self.captopril = os.path.join(self.test_dir, 'captopril.sdf')
[docs] def tearDown(self): pass
[docs] def testMolpherAtom(self): carbon = MolpherAtom("C") oxygen = MolpherAtom("O", -1) self.assertEqual(carbon.formal_charge, 0) carbon.formal_charge = 1 self.assertEqual(carbon.formal_charge, 1) self.assertFalse(oxygen.is_locked or carbon.is_locked) oxygen.locking_mask = MolpherAtom.FULL_LOCK self.assertTrue(oxygen.is_locked) self.assertTrue(oxygen.lock_info['FULL_LOCK']) self.assertTrue(bool(oxygen.locking_mask & MolpherAtom.NO_ADDITION)) self.assertTrue(bool(oxygen.locking_mask & MolpherAtom.KEEP_NEIGHBORS)) self.assertTrue(bool(oxygen.locking_mask & MolpherAtom.NO_MUTATION)) self.assertTrue(oxygen.lock_info['UNLOCKED'] == False) carbon.locking_mask = MolpherAtom.NO_MUTATION self.assertTrue(carbon.is_locked) self.assertFalse(carbon.lock_info['FULL_LOCK']) self.assertFalse(bool(carbon.locking_mask & MolpherAtom.NO_ADDITION)) self.assertFalse(bool(carbon.locking_mask & MolpherAtom.KEEP_NEIGHBORS)) self.assertTrue(bool(carbon.locking_mask & MolpherAtom.NO_MUTATION)) self.assertTrue(carbon.lock_info['UNLOCKED'] == False)
[docs] def testMolpherMol(self): mol = MolpherMol(self.test_target) self.assertTrue(mol.asRDMol()) self.assertTrue(mol.asMolBlock()) mol.smiles = 'CCC' self.assertEqual(mol.getSMILES(), 'CCC') copy = mol.copy() copy.sascore = 0.54 self.assertEqual(0.54, copy.sascore) tree = ExplorationTree.create(source=mol.smiles, target='CCCNCCC') tree = ExplorationTree.create(source=mol, target='CCCNCCC') tree = ExplorationTree.create(source=mol, target=MolpherMol('CCCNCCC')) self.assertTrue(tree.hasMol(mol)) def assign(x): tree.fetchMol(mol.smiles).smiles = x self.assertRaises(RuntimeError, assign, 'CCO') # atom locking stuff mol_locked = MolpherMol(self.cymene_locked) open_positions = (0, 2, 3, 9) for idx, atom in enumerate(mol_locked.atoms): if not atom.is_locked: self.assertIn(idx, open_positions) else: self.assertTrue(atom.lock_info['NO_ADDITION']) self.assertFalse(atom.lock_info['UNLOCKED']) self.assertFalse(atom.lock_info['FULL_LOCK']) # test RDKit conversion and locking information transfer rd_mol = mol_locked.asRDMol() output = None if sys.version_info[0] < 3: output = BytesIO() else: output = StringIO() writer = Chem.SDWriter(output) writer.write(rd_mol) writer.close() temp_path = self.test_dir + "/cymene_tmp.sdf" with open(temp_path, "w") as tempfile: tempfile.write(output.getvalue()) new_cymene = MolpherMol(temp_path) os.remove(temp_path) for atm_old, atm_new in zip(mol_locked.atoms, new_cymene.atoms): self.assertTrue(atm_old.locking_mask == atm_new.locking_mask) # test init from RDKit mol_from_rdkit = MolpherMol(other=rd_mol) for atm_old, atm_new in zip(mol_locked.atoms, mol_from_rdkit.atoms): self.assertTrue(atm_old.locking_mask == atm_new.locking_mask)
[docs] def testAtomLibrary(self): default_lib = AtomLibrary.getDefaultLibrary() old_atoms = default_lib.atoms old_probas = default_lib.atom_probabilities self.assertIn("C", [x.symbol for x in default_lib.atoms]) print("Default library atoms:") for atom, proba in zip(default_lib.atoms, default_lib.atom_probabilities): print("Atom:", atom.symbol, "Probability", proba) smbls = ["O", "S"] my_lib = AtomLibrary(smbls) for x in my_lib.atoms: self.assertIn(x.symbol, smbls) AtomLibrary.setDefaultLibrary(my_lib) for x in default_lib.atoms: self.assertIn(x.symbol, smbls) probas = [0.75, 0.25] self.assertRaises(RuntimeError, lambda : AtomLibrary(["C"], probas)) my_lib = AtomLibrary(smbls, probas) AtomLibrary.setDefaultLibrary(my_lib) for idx, atom_info in enumerate(zip(default_lib.atoms, default_lib.atom_probabilities)): self.assertEquals(atom_info[0].symbol, smbls[idx]) self.assertEquals(atom_info[1], probas[idx]) for x in range(200): self.assertIn(default_lib.getRandomAtom().symbol, smbls) AtomLibrary.setDefaultLibrary(AtomLibrary(old_atoms, old_probas)) # put everything back to default old_symbols = [x.symbol for x in old_atoms] for idx, atom_info in enumerate(zip(default_lib.atoms, default_lib.atom_probabilities)): self.assertEquals(atom_info[0].symbol, old_symbols[idx]) self.assertEquals(atom_info[1], old_probas[idx])
[docs] def assertOperatorValid(self, operator, test_mol, gens = 10): print("Testing operator:", operator) if not operator.getOriginal(): self.assertRaises(RuntimeError, operator.setOriginal, None) self.assertRaises(RuntimeError, operator.morph) mol = MolpherMol(self.propanol) operator.setOriginal(mol) orig = operator.getOriginal() self.assertIsNotNone(orig) self.assertIsInstance(orig, MolpherMol) self.assertEqual(orig.getSMILES(), mol.getSMILES()) operator.setOriginal(test_mol) for x in range(gens): mol = operator.morph() if mol: self.assertIsInstance(mol, MolpherMol) print(mol.smiles) operator.setOriginal(mol)
[docs] def testAddAtomOperator(self): cymene_no_add = MolpherMol(self.cymene_locked) add_atom = AddAtom() self.assertOperatorValid(add_atom, cymene_no_add)
[docs] def testAddBondOperator(self): propanol = MolpherMol(self.propanol) add_bond = AddBond() self.assertOperatorValid(add_bond, propanol) add_bond.setOriginal(propanol) open_bonds = add_bond.getOpenBonds() self.assertIsInstance(open_bonds, tuple)
[docs] def testRemoveBondOperator(self): test_mol = MolpherMol(self.remove_bond_test_mol) remove_bond = RemoveBond() self.assertOperatorValid(remove_bond, test_mol) remove_bond.setOriginal(test_mol) open_bonds = remove_bond.getOpenBonds() self.assertIsInstance(open_bonds, tuple)
[docs] def testMutateAtomOperator(self): self.assertOperatorValid(MutateAtom(), MolpherMol(self.isopropylphenol))
[docs] def testInterlayAtomOperator(self): self.assertOperatorValid(InterlayAtom(), MolpherMol(self.isopropylphenol))
[docs] def testRerouteBondOperator(self): self.assertOperatorValid(ContractBond(), MolpherMol(self.contract_bond_test_mol))
[docs] def testContractBondOperator(self): self.assertOperatorValid(RerouteBond(), MolpherMol(self.reroute_test_mol))
[docs] def testMolpher(self): cymene = MolpherMol(self.cymene_locked) operators = [AddAtom(), RemoveAtom()] molpher = Molpher(cymene, operators) molpher() morphs = molpher.getMorphs() self.assertTrue(morphs) self.assertFalse(molpher.getMorphs()) morphs = [] while len(morphs) < 50: morphs.append(molpher.next()) self.assertEqual(50, len(morphs))
[docs] def testMorphingOperator(self): class Identity(MorphingOperator): def morph(self): return self.original.copy() def getName(self): return "Identity" cymene = MolpherMol(self.cymene_locked) operators = [Identity()] molpher = Molpher(cymene, operators) molpher() for morph in molpher.getMorphs(): self.assertEqual(morph.smiles, cymene.smiles)
[docs] def testExplorationData(self): params = ExplorationData( source=self.test_source , target=self.test_target ) params.operators = (OP_ADD_BOND, 'OP_REMOVE_BOND',) self.assertEqual(params.operators, ('OP_ADD_BOND', 'OP_REMOVE_BOND')) params.fingerprint = FP_EXT_ATOM_PAIRS self.assertEqual(params.fingerprint, 'FP_EXT_ATOM_PAIRS') params.fingerprint = 'FP_TOPOLOGICAL_LAYERED_2' self.assertEqual(params.fingerprint, 'FP_TOPOLOGICAL_LAYERED_2') params.similarity = 'SC_COSINE' self.assertEqual(params.similarity, 'SC_COSINE') params.similarity = SC_KULCZYNSKI self.assertEqual(params.similarity, 'SC_KULCZYNSKI') self.assertEqual(params.source.smiles, self.test_source) self.assertEqual(params.target.smiles, self.test_target) params.param_dict = { 'target' : self.test_source , 'operators' : params.param_dict['operators'][:1] } self.assertEqual(params.target.smiles, self.test_source) self.assertEqual(params.operators, ('OP_ADD_BOND',)) params_from_temp = ExplorationData.load(self.test_template_path) self.assertRaises(RuntimeError, lambda : ExplorationData.load('not/a/valid/path')) self.assertEqual(777, params_from_temp.far_produce)
[docs] def testTree(self): mol1 = self.test_source mol2 = self.test_target params_dict = { 'source' : mol1 , 'target' : mol2 , 'operators' : (OP_ADD_BOND, OP_REMOVE_BOND, OP_MUTATE_ATOM) } params = ExplorationData(**params_dict) self.assertRaises(AttributeError, lambda : ExplorationTree()) tree_from_dict = ExplorationTree.create(tree_data=params_dict) tree_from_params = ExplorationTree.create(tree_data=params) tree_from_SMILES = ExplorationTree.create(source=mol1, target=mol2) def test_tree(tree): self.assertEqual(tree.params['source'], mol1) self.assertEqual(tree.params['target'], mol2) test_tree(tree_from_dict) test_tree(tree_from_params) test_tree(tree_from_SMILES) tree = tree_from_params # if we try to set source for non-empty tree, exception should be raised def func(): tree.params = { 'source' : mol2 , 'target' : 'C' } self.assertRaises(RuntimeError, func) tree.thread_count = 1 tree.params = { 'target' : 'C' } self.assertEqual(1, tree.thread_count) self.assertEqual(tree.params['source'], mol1) self.assertEqual(tree.params['target'], 'C') self.assertEqual(tree.params['operators'], params.param_dict['operators']) # we should still have the same opers set tree.params = params; tree.thread_count = 0 # assign the original parameters back self.assertEqual(0, tree.thread_count) self.assertEqual(tree.params['source'], mol1) self.assertEqual(tree.params['target'], mol2) self.assertEqual(tree.params['operators'], params.param_dict['operators']) leaf = tree.leaves[0] self.assertRaises(RuntimeError, lambda : leaf.setSMILES('CCCC')) self.assertTrue(tree.hasMol(leaf)) # self.assertEqual(tree, leaf.tree) # FIXME: add a reliable operator for comparison between trees leaf.setDistToTarget(0.5) self.assertEqual(tree.leaves[0].getDistToTarget(), 0.5) leaf_copy = tree.leaves[0].copy() # self.assertFalse(tree.hasMol(leaf_copy)) # FIXME: add a reliable operator for comparison between trees (this should check both the SMILES and the tree ownership) self.assertEqual(leaf_copy.getDistToTarget(), 0.5) leaf_copy.setDistToTarget(0.7) self.assertEqual(leaf.getDistToTarget(), 0.5) self.assertEqual(tree.leaves[0].getDistToTarget(), 0.5) self.assertEqual(leaf_copy.getDistToTarget(), 0.7)
if __name__ == "__main__": unittest.main()