1 //
2 //  Copyright (C) 2021 Greg Landrum
3 //
4 //   @@ All Rights Reserved @@
5 //  This file is part of the RDKit.
6 //  The contents are covered by the terms of the BSD license
7 //  which is included in the file license.txt, found at the root
8 //  of the RDKit source tree.
9 //
10 
11 #include "catch.hpp"
12 
13 #include <GraphMol/RDKitBase.h>
14 #include <GraphMol/SmilesParse/SmilesParse.h>
15 #include <GraphMol/FileParsers/MolSupplier.h>
16 #include <GraphMol/Descriptors/ConnectivityDescriptors.h>
17 #include <GraphMol/Descriptors/PMI.h>
18 
19 using namespace RDKit;
20 
21 TEST_CASE("Kier kappa2", "[2D]") {
22   SECTION("values from https://doi.org/10.1002/qsar.19860050103") {
23     std::vector<std::pair<std::string, double>> data = {
24       // Table 5 from the paper
25       {"c1ccccc15.Cl5", 1.987},
26       {"c1ccccc15.F5", 1.735},
27       {"c1ccccc15.[N+]5(=O)O", 2.259},
28       {"c1ccccc15.C5(=O)C", 2.444},
29 #if 0
30       // expected values from paper (differences are due to hybridization mismatches)
31         {"c1ccccc15.N5(C)C", 2.646},
32         {"c1ccccc15.C5(=O)N", 2.416},
33         {"c1ccccc15.C5(=O)O", 2.416},
34         {"c1ccccc15.S5(=O)(=O)C", 2.617},
35         {"c1ccccc15.O5", 1.756},
36 #else
37       {"c1ccccc15.N5(C)C", 2.53},
38       {"c1ccccc15.C5(=O)N", 2.31},
39       {"c1ccccc15.C5(=O)O", 2.31},
40       {"c1ccccc15.S5(=O)(=O)C", 2.42},
41       {"c1ccccc15.O5", 1.65},
42 #endif
43     };
44     for (const auto &pr : data) {
45       std::unique_ptr<ROMol> m(SmilesToMol(pr.first));
46       REQUIRE(m);
47       auto k2 = Descriptors::calcKappa2(*m);
48       CHECK(k2 == Approx(pr.second).epsilon(0.01));
49     }
50   }
51 }
52 
53 TEST_CASE("Kier Phi", "[2D]") {
54   SECTION("regression-test values from the paper") {
55     std::vector<std::pair<std::string, double>> data = {
56       // Table 1 from the paper
57       {"CCCCCC", 5.00},
58       {"CCCCCCC", 6.00},
59       {"CCCCCCCC", 7.00},
60       {"CCC(C)CC", 3.20},
61       {"CC(C)C(C)C", 2.22},
62       {"CC(C)(C)CC", 1.63},
63       {"C1CCCC1", 0.92},
64       {"C1CCCCC1", 1.54},
65       {"C1CCCCCC1", 2.25},
66       {"CCCCC=C", 4.53},
67       {"C=CCCC=C", 4.07},
68       {"C#CCCCC", 4.21},
69       {"c1ccccc1", 0.91},
70       // additional from Table 2
71       {"C=CCC=CC", 4.09},
72       {"CC=CC=CC", 4.09},
73       {"C1=CCCCC1", 1.31},
74       {"C1=CC=CCC1", 1.1},
75       {"C1=CCC=CC1", 1.1},
76       // additional from Table 3
77       {"CCCCCCCCCC", 9.00},
78       {"CC(C)CCCCC", 5.14},
79       {"CCC(C)CCCC", 5.14},
80       {"CC(C)CCCC", 4.17},
81       {"CCC(C)CCC", 4.17},
82       {"CCC(CC)CCC", 5.14},
83       {"CCC(CC)CC", 4.17},
84       {"CC(C)(C)CCC", 2.34},
85       {"CC(C)C(C)CC", 3.06},
86       {"CCC(C)(C)CC", 2.34},
87       {"CC(C)(C)C(C)C", 1.85},
88       // additional from table 4
89       {"CCOCC", 3.93},
90       {"CCC(=O)CC", 2.73},
91       {"CCc1ccc(CC)cc1", 2.49},
92       {"CCC(O)CC", 3.14},
93       {"CCCC(Cl)(Cl)CCC", 4.69},
94       {"CCC(F)C(F)CC", 3.75},
95 #if 0
96       // expected values from paper (differences are due to hybridization mismatches)
97         {"CCOC(=O)CC", 3.61},
98         {"CCC(=O)Nc1ccc(CC)cc1", 3.65},
99 #else
100       {"CCOC(=O)CC", 3.38},
101       {"CCC(=O)Nc1ccc(CC)cc1", 3.50},
102 #endif
103 
104     };
105     for (const auto &pr : data) {
106       std::unique_ptr<ROMol> m(SmilesToMol(pr.first));
107       REQUIRE(m);
108       auto val = Descriptors::calcPhi(*m);
109       CHECK(val == Approx(pr.second).epsilon(0.01));
110     }
111   }
112 }
113 
114 #ifdef RDK_BUILD_DESCRIPTORS3D
115 TEST_CASE(
116     "Github #4167: SpherocityIndex() not being recalculated for different "
117     "conformers",
118     "[3D]") {
119   std::string pathName = getenv("RDBASE");
120   std::string sdfName =
121       pathName + "/Code/GraphMol/Descriptors/test_data/github4167.sdf";
122   bool sanitize = true;
123   bool removeHs = false;
124   RDKit::SDMolSupplier reader(sdfName, sanitize, removeHs);
125   std::unique_ptr<ROMol> m1(reader.next());
126   std::unique_ptr<ROMol> m2(reader.next());
127   REQUIRE(m1);
128   REQUIRE(m2);
129   m1->addConformer(new RDKit::Conformer(m2->getConformer()), true);
130   REQUIRE(m1->getNumConformers() == 2);
131 
132   {
133     int confId = 0;
134     auto v1_0 = RDKit::Descriptors::spherocityIndex(*m1, confId, true);
135     auto v2 = RDKit::Descriptors::spherocityIndex(*m2, confId, true);
136     confId = 1;
137     auto v1_1 = RDKit::Descriptors::spherocityIndex(*m1, confId, true);
138     CHECK(v1_0 != v1_1);
139     CHECK(v1_1 == v2);
140   }
141   {
142     std::vector<double (*)(const ROMol &, int, bool, bool)> funcs{
143         RDKit::Descriptors::NPR1,
144         RDKit::Descriptors::NPR2,
145         RDKit::Descriptors::PMI1,
146         RDKit::Descriptors::PMI2,
147         RDKit::Descriptors::PMI3,
148         RDKit::Descriptors::radiusOfGyration,
149         RDKit::Descriptors::inertialShapeFactor,
150         RDKit::Descriptors::eccentricity,
151         RDKit::Descriptors::asphericity};
152     for (const auto func : funcs) {
153       bool useAtomMasses = true;
154       bool force = true;
155       int confId = 0;
156       auto v1_0 = func(*m1, confId, useAtomMasses, force);
157       auto v2 = func(*m2, confId, useAtomMasses, force);
158       confId = 1;
159       auto v1_1 = func(*m1, confId, useAtomMasses, force);
160       CHECK(v1_0 != v1_1);
161       CHECK(v1_1 == v2);
162     }
163   }
164   // { // surrogate for NPR1, NPR2, PMI1, PM2,
165   //   int confId = 0;
166   //   auto v1_0 = RDKit::Descriptors::spherocityIndex(*m1, confId, true);
167   //   auto v2 = RDKit::Descriptors::spherocityIndex(*m2, confId, true);
168   //   confId = 1;
169   //   auto v1_1 = RDKit::Descriptors::spherocityIndex(*m1, confId, true);
170   //   CHECK(v1_0 != v1_1);
171   //   CHECK(v1_1 == v2);
172   // }
173 }
174 #endif