1#
2#  Copyright (c) 2013, Novartis Institutes for BioMedical Research Inc.
3#  Copyright (c) 2021, Greg Landrum
4#  All rights reserved.
5#
6# Redistribution and use in source and binary forms, with or without
7# modification, are permitted provided that the following conditions are
8# met:
9#
10#     * Redistributions of source code must retain the above copyright
11#       notice, this list of conditions and the following disclaimer.
12#     * Redistributions in binary form must reproduce the above
13#       copyright notice, this list of conditions and the following
14#       disclaimer in the documentation and/or other materials provided
15#       with the distribution.
16#     * Neither the name of Novartis Institutes for BioMedical Research Inc.
17#       nor the names of its contributors may be used to endorse or promote
18#       products derived from this software without specific prior written permission.
19#
20# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31#
32# Created by Sereina Riniker, Aug 2013
33""" unit testing code for molecule drawing
34"""
35
36import sys
37import unittest
38import os
39from rdkit import Chem
40from rdkit.RDLogger import logger
41import platform
42try:
43  import matplotlib
44  if platform.system() == "Linux":
45    if not os.environ.get("DISPLAY", None):
46      # Force matplotlib to not use any Xwindows backend.
47      print("Forcing use of Agg renderer", file=sys.stderr)
48      matplotlib.use('Agg')
49except ImportError:
50  matplotlib = None
51
52from rdkit.Chem import Draw
53from rdkit.Chem.Draw import SimilarityMaps as sm
54try:
55  from rdkit.Chem.Draw.mplCanvas import Canvas
56except RuntimeError:
57  Canvas = None
58except ImportError:
59  Canvas = None
60
61logger = logger()
62
63
64class TestCase(unittest.TestCase):
65
66  def setUp(self):
67    self.mol1 = Chem.MolFromSmiles('c1ccccc1')
68    self.mol2 = Chem.MolFromSmiles('c1ccncc1')
69
70  @unittest.skipUnless(Canvas, 'Matplotlib required')
71  def testSimilarityMap(self):
72    # Morgan2 BV
73    refWeights = [0.5, 0.5, 0.5, -0.5, 0.5, 0.5]
74    weights = sm.GetAtomicWeightsForFingerprint(
75      self.mol1, self.mol2, lambda m, i: sm.GetMorganFingerprint(m, i, radius=2, fpType='bv'))
76    for w, r in zip(weights, refWeights):
77      self.assertEqual(w, r)
78
79    _, maxWeight = sm.GetSimilarityMapForFingerprint(
80      self.mol1, self.mol2, lambda m, i: sm.GetMorganFingerprint(m, i, radius=2, fpType='bv'))
81    self.assertEqual(maxWeight, 0.5)
82
83    weights, maxWeight = sm.GetStandardizedWeights(weights)
84    self.assertEqual(maxWeight, 0.5)
85    refWeights = [1.0, 1.0, 1.0, -1.0, 1.0, 1.0]
86    for w, r in zip(weights, refWeights):
87      self.assertEqual(w, r)
88
89    weights = sm.GetAtomicWeightsForFingerprint(
90      self.mol1, self.mol2, lambda m, i: sm.GetMorganFingerprint(m, i, fpType='count'))
91    self.assertTrue(weights[3] < 0)
92    weights = sm.GetAtomicWeightsForFingerprint(
93      self.mol1, self.mol2,
94      lambda m, i: sm.GetMorganFingerprint(m, i, fpType='bv', useFeatures=True))
95    self.assertTrue(weights[3] < 0)
96
97    # hashed AP BV
98    refWeights = [0.09523, 0.17366, 0.17366, -0.23809, 0.17366, 0.17366]
99    weights = sm.GetAtomicWeightsForFingerprint(
100      self.mol1, self.mol2, lambda m, i: sm.GetAPFingerprint(m, i, fpType='bv', nBits=1024))
101    for w, r in zip(weights, refWeights):
102      self.assertAlmostEqual(w, r, 4)
103
104    weights = sm.GetAtomicWeightsForFingerprint(
105      self.mol1, self.mol2, lambda m, i: sm.GetAPFingerprint(m, i, fpType='normal'))
106    self.assertTrue(weights[3] < 0)
107    weights = sm.GetAtomicWeightsForFingerprint(
108      self.mol1, self.mol2, lambda m, i: sm.GetAPFingerprint(m, i, fpType='hashed'))
109    self.assertTrue(weights[3] < 0)
110
111    # hashed TT BV
112    refWeights = [0.5, 0.5, -0.16666, -0.5, -0.16666, 0.5]
113    weights = sm.GetAtomicWeightsForFingerprint(
114      self.mol1, self.mol2,
115      lambda m, i: sm.GetTTFingerprint(m, i, fpType='bv', nBits=1024, nBitsPerEntry=1))
116    for w, r in zip(weights, refWeights):
117      self.assertAlmostEqual(w, r, 4)
118
119    weights = sm.GetAtomicWeightsForFingerprint(
120      self.mol1, self.mol2, lambda m, i: sm.GetTTFingerprint(m, i, fpType='normal'))
121    self.assertTrue(weights[3] < 0)
122    weights = sm.GetAtomicWeightsForFingerprint(
123      self.mol1, self.mol2, lambda m, i: sm.GetTTFingerprint(m, i, fpType='hashed'))
124    self.assertTrue(weights[3] < 0)
125
126    # RDK fingerprint BV
127    refWeights = [0.42105, 0.42105, 0.42105, -0.32895, 0.42105, 0.42105]
128    weights = sm.GetAtomicWeightsForFingerprint(
129      self.mol1, self.mol2, lambda m, i: sm.GetRDKFingerprint(m, i, nBits=1024, nBitsPerHash=1))
130    for w, r in zip(weights, refWeights):
131      self.assertAlmostEqual(w, r, 4)
132
133  @unittest.skipUnless(Canvas, 'Matplotlib required')
134  def testSimilarityMapKWArgs(self):
135    # Morgan2 BV
136    m1 = Chem.MolFromSmiles('CC[C@](F)(Cl)c1ccccc1')
137    m2 = Chem.MolFromSmiles('CC[C@@](F)(Cl)c1ccccc1')
138    weights = sm.GetAtomicWeightsForFingerprint(
139      m1, m2, lambda m, i: sm.GetAPFingerprint(m, atomId=i, includeChirality=False))
140    for w in weights:
141      self.assertAlmostEqual(w, 0.100, 4)
142    weights = sm.GetAtomicWeightsForFingerprint(
143      m1, m2, lambda m, i: sm.GetAPFingerprint(m, atomId=i, includeChirality=True))
144    for i, w in enumerate(weights):
145      if i != 2:
146        self.assertAlmostEqual(w, 0.098, 3)
147      else:
148        self.assertAlmostEqual(w, -0.082, 3)
149
150    weights = sm.GetAtomicWeightsForFingerprint(
151      m1, m2, lambda m, i: sm.GetTTFingerprint(m, atomId=i, includeChirality=False))
152    for w in weights:
153      self.assertTrue(w > 0.0)
154    weights = sm.GetAtomicWeightsForFingerprint(
155      m1, m2, lambda m, i: sm.GetTTFingerprint(m, atomId=i, includeChirality=True))
156    for i, w in enumerate(weights):
157      if i > 4:
158        self.assertTrue(w > 0.0)
159      else:
160        self.assertTrue(w < 0.0)
161
162    weights = sm.GetAtomicWeightsForFingerprint(
163      m1, m2, lambda m, i: sm.GetMorganFingerprint(m, radius=1, atomId=i, useChirality=False))
164    weights2 = sm.GetAtomicWeightsForFingerprint(
165      m1, m2, lambda m, i: sm.GetMorganFingerprint(m, radius=1, atomId=i, useChirality=True))
166    # testing explicit values here seems silly, just check that the contribution of the
167    # chiral center drops:
168    self.assertTrue(weights[2] > weights2[2])
169
170  def testSimilarityMapsMolDraw2D(self):
171    # nothing really sensible to test here, just make sure things run
172    mol = Chem.MolFromSmiles('COc1cccc2cc(C(=O)NCCCCN3CCN(c4cccc5nccnc54)CC3)oc21')
173    refmol = Chem.MolFromSmiles('CCCN(CCCCN1CCN(c2ccccc2OC)CC1)Cc1ccc2ccccc2c1')
174    d = Draw.MolDraw2DSVG(400, 400)
175    d.ClearDrawing()
176    _, maxWeight = sm.GetSimilarityMapForFingerprint(
177      refmol, mol, lambda m, i: sm.GetMorganFingerprint(m, i, radius=2, fpType='bv'), draw2d=d)
178    d.FinishDrawing()
179    with open('similarityMap1_out.svg', 'w+') as outf:
180      outf.write(d.GetDrawingText())
181
182    # Github #2904: make sure we can provide our own colormap as a list:
183    colors = [(0, 1, 0, 0.5), (1, 1, 1), (0, 0, 1, 0.5)]
184    d = Draw.MolDraw2DSVG(400, 400)
185    d.ClearDrawing()
186    _, maxWeight = sm.GetSimilarityMapForFingerprint(
187      refmol, mol, lambda m, i: sm.GetMorganFingerprint(m, i, radius=2, fpType='bv'), draw2d=d,
188      colorMap=colors)
189    d.FinishDrawing()
190    with open('similarityMap1_out2.svg', 'w+') as outf:
191      outf.write(d.GetDrawingText())
192
193    # Github #2904: make sure we can provide our own colormap as a matplotlib colormap:
194    try:
195      from matplotlib import cm
196      d = Draw.MolDraw2DSVG(400, 400)
197      d.ClearDrawing()
198      _, maxWeight = sm.GetSimilarityMapForFingerprint(
199        refmol, mol, lambda m, i: sm.GetMorganFingerprint(m, i, radius=2, fpType='bv'), draw2d=d,
200        colorMap=cm.PiYG)
201      d.FinishDrawing()
202      with open('similarityMap1_out3.svg', 'w+') as outf:
203        outf.write(d.GetDrawingText())
204    except ImportError:
205      pass
206
207
208if __name__ == '__main__':
209  try:
210    import matplotlib
211    from rdkit.Chem.Draw.mplCanvas import Canvas
212  except ImportError:
213    pass
214  except RuntimeError:  # happens with GTK can't initialize
215    pass
216  else:
217    unittest.main()
218