1 #define BOOST_TEST_MODULE Test_Coordgen
2 
3 #include <boost/filesystem.hpp>
4 #include <boost/test/unit_test.hpp>
5 #include <unordered_set>
6 
7 #include "../CoordgenFragmenter.h"
8 #include "../sketcherMinimizer.h"
9 #include "../sketcherMinimizerMaths.h"
10 #include "../sketcherMinimizerStretchInteraction.h"
11 #include "../sketcherMinimizerBendInteraction.h"
12 #include "../sketcherMaeReading.h"
13 #include "coordgenBasicSMILES.h"
14 
15 #include "maeparser/MaeConstants.hpp"
16 #include "maeparser/Reader.hpp"
17 
18 using std::unordered_set;
19 using namespace schrodinger;
20 
21 const boost::filesystem::path test_samples_path(TEST_SAMPLES_PATH);
22 
23 namespace
24 {
25 std::map<sketcherMinimizerAtom*, int>
getReportingIndices(sketcherMinimizerMolecule & mol)26 getReportingIndices(sketcherMinimizerMolecule& mol)
27 {
28     std::map<sketcherMinimizerAtom*, int> fakeIndices;
29     int index = 0;
30     for (auto& atom : mol.getAtoms()) {
31         fakeIndices.emplace(atom, ++index);
32     }
33     return fakeIndices;
34 }
35 
36 bool areBondsNearIdeal(sketcherMinimizerMolecule& mol,
37                        std::map<sketcherMinimizerAtom*, int>& indices,
38                        std::set<std::pair <int, int> > skip = std::set<std::pair <int, int> > ())
39 {
40     const float targetBondLength = BONDLENGTH * BONDLENGTH;
41     const auto tolerance = static_cast<float>(targetBondLength * 0.1);
42 
43     bool passed = true;
44     for (auto& bond : mol.getBonds()) {
45         auto bondPair = std::pair<int, int> (indices[bond->getStartAtom()], indices[bond->getEndAtom()]);
46         if (skip.find(bondPair) != skip.end()) {
47             continue;
48         }
49         auto& startCoordinates = bond->getStartAtom()->getCoordinates();
50         auto& endCoordinates = bond->getEndAtom()->getCoordinates();
51 
52         const auto sq_distance = sketcherMinimizerMaths::squaredDistance(
53                                                                          startCoordinates, endCoordinates);
54         const auto deviation = sq_distance - targetBondLength;
55         if (deviation < -tolerance || deviation > tolerance) {
56             std::cerr << "Bond" << indices[bond->getStartAtom()] << '-'
57             << indices[bond->getEndAtom()] << " has length "
58             << sq_distance << " (" << targetBondLength << ")\n";
59             passed = false;
60         }
61     }
62     return passed;
63 }
64 
noCrossingBonds(sketcherMinimizerMolecule & mol,std::map<sketcherMinimizerAtom *,int> & indices)65 bool noCrossingBonds(sketcherMinimizerMolecule& mol,
66                          std::map<sketcherMinimizerAtom*, int>& indices)
67 {
68     bool passed = true;
69     for (auto& bond : mol.getBonds()) {
70         for (auto& bond2 : mol.getBonds()) {
71             if (bond == bond2) continue;
72             if (bond->getStartAtom() == bond2->getStartAtom()) continue;
73             if (bond->getStartAtom() == bond2->getEndAtom()) continue;
74             if (bond->getEndAtom() == bond2->getStartAtom()) continue;
75             if (bond->getEndAtom() == bond2->getEndAtom()) continue;
76 
77             auto& startCoordinates1 = bond->getStartAtom()->getCoordinates();
78             auto& endCoordinates1 = bond->getEndAtom()->getCoordinates();
79             auto& startCoordinates2 = bond2->getStartAtom()->getCoordinates();
80             auto& endCoordinates2 = bond2->getEndAtom()->getCoordinates();
81 
82             if (sketcherMinimizerMaths::intersectionOfSegments(startCoordinates1,
83                                                                endCoordinates1,
84                                                                startCoordinates2,
85                                                                endCoordinates2)) {
86                 std::cerr << "Bond" << indices[bond->getStartAtom()] << '-'
87                 << indices[bond->getEndAtom()] << " intersects bond "
88                 << indices[bond2->getStartAtom()]<< '-'
89                 << indices[bond2->getEndAtom()]<<")\n";
90                 passed = false;
91             }
92         }
93     }
94     return passed;
95 }
96 
97 } // namespace
98 
99 
operator ""_smiles(const char * smiles,size_t len)100 static sketcherMinimizerMolecule* operator"" _smiles(const char * smiles, size_t len)
101 {
102     return approxSmilesParse({smiles, len});
103 }
104 
BOOST_AUTO_TEST_CASE(SampleTest)105 BOOST_AUTO_TEST_CASE(SampleTest)
106 {
107     // A small sample test showing how to import a molecule from a .mae file and
108     // initialize the minimizer with it.
109 
110     const std::string testfile = (test_samples_path / "test.mae").string();
111 
112     mae::Reader r(testfile);
113     auto block = r.next(mae::CT_BLOCK);
114     BOOST_REQUIRE(block != nullptr);
115 
116     auto* mol = mol_from_mae_block(*block);
117     BOOST_REQUIRE(mol != nullptr);
118     BOOST_REQUIRE_EQUAL(mol->getAtoms().size(), 26);
119     BOOST_REQUIRE_EQUAL(mol->getBonds().size(), 26);
120 
121     sketcherMinimizer minimizer;
122     minimizer.initialize(mol); // minimizer takes ownership of mol
123     minimizer.runGenerateCoordinates();
124 
125     for (auto& atom : mol->getAtoms()) {
126         auto c = atom->getCoordinates();
127 
128         // This is best we can do with checking: there are precision and
129         // rounding issues depending on platform and environment.
130         BOOST_CHECK(c.x() != 0 || c.y() != 0);
131     }
132 
133     auto indices = getReportingIndices(*mol);
134     BOOST_CHECK(areBondsNearIdeal(*mol, indices));
135 }
136 
137 
BOOST_AUTO_TEST_CASE(TemplateTest)138 BOOST_AUTO_TEST_CASE(TemplateTest)
139 {
140     ///
141     // Do the structures in the templates file get the same coordinates that
142     // were supplied in the templates file?
143 
144     const boost::filesystem::path source_dir(SOURCE_DIR);
145     const std::string templates_file = (source_dir / "templates.mae").string();
146 
147     // Known issues. See issue #52
148     const unordered_set<size_t> no_match = {1, 8, 19, 20, 22, 32, 43, 53, 65, 66, 67};
149     // 32 is odd. minimization removes atoms?? But it matches??
150     const unordered_set<size_t> match_incorrectly = {18, 27};
151 
152     mae::Reader r(templates_file);
153     std::shared_ptr<mae::Block> b;
154     size_t template_index = 0;
155     while ((b = r.next(mae::CT_BLOCK)) != nullptr) {
156         if (no_match.count(template_index) > 0) {
157             ++template_index;
158             continue;
159         }
160 
161         auto* mol = mol_from_mae_block(*b);
162         BOOST_REQUIRE(mol != nullptr);
163         const auto original_atom_count = mol->getAtoms().size();
164 
165         sketcherMinimizer minimizer;
166         minimizer.setTemplateFileDir(source_dir.string());
167 
168         minimizer.initialize(mol); // minimizer takes ownership of mol
169         minimizer.runGenerateCoordinates();
170 
171         BOOST_CHECK_EQUAL(original_atom_count, mol->getAtoms().size());
172 
173         bool any_rigid = false;
174         bool all_rigid = true;
175         for (auto a: mol->getAtoms()) {
176             if (a->rigid) {
177                 any_rigid = true;
178             } else {
179                 all_rigid = false;
180             }
181         }
182         const bool matches_incorrectly = match_incorrectly.count(template_index) > 0;
183         if (!any_rigid) {
184             BOOST_CHECK_MESSAGE(any_rigid, "No template found for " << template_index);
185         } else if (!matches_incorrectly) {
186             BOOST_CHECK_MESSAGE(all_rigid, "Not all atoms templated for " << template_index);
187         }
188 
189         ++template_index;
190     }
191 }
192 
BOOST_AUTO_TEST_CASE(ClearWedgesTest)193 BOOST_AUTO_TEST_CASE(ClearWedgesTest)
194 {
195     // test that when writing stereochemistry we first clear the existing one
196 
197     const std::string testfile = (test_samples_path / "testChirality.mae").string();
198 
199     mae::Reader r(testfile);
200     auto block = r.next(mae::CT_BLOCK);
201     BOOST_REQUIRE(block != nullptr);
202 
203     auto* mol = mol_from_mae_block(*block);
204     BOOST_REQUIRE(mol != nullptr);
205 
206      /*set wedges on all bonds*/
207     mol->getBonds().at(0)->hasStereochemistryDisplay = true;
208     mol->getBonds().at(1)->hasStereochemistryDisplay = true;
209     mol->getBonds().at(2)->hasStereochemistryDisplay = true;
210     mol->getBonds().at(3)->hasStereochemistryDisplay = true;
211 
212     /*set chirality on the atom*/
213     auto carbon = mol->getAtoms().at(0);
214     BOOST_REQUIRE_EQUAL(carbon->atomicNumber, 6);
215     carbon->hasStereochemistrySet = true;
216     carbon->isR = true;
217 
218     /*run minimization*/
219     sketcherMinimizer minimizer;
220     minimizer.initialize(mol); // minimizer takes ownership of mol
221     minimizer.runGenerateCoordinates();
222 
223     /*make sure that previous wedges are reset and only 2 are now used*/
224     BOOST_REQUIRE_EQUAL(mol->getBonds().at(0)->hasStereochemistryDisplay, false);
225     BOOST_REQUIRE_EQUAL(mol->getBonds().at(1)->hasStereochemistryDisplay, true);
226     BOOST_REQUIRE_EQUAL(mol->getBonds().at(2)->hasStereochemistryDisplay, false);
227     BOOST_REQUIRE_EQUAL(mol->getBonds().at(3)->hasStereochemistryDisplay, true);
228 }
229 
230 
BOOST_AUTO_TEST_CASE(DisableMetalZOBs)231 BOOST_AUTO_TEST_CASE(DisableMetalZOBs)
232 {
233     // Load a molecule with a single bond to a metal. Make sure that disabling the automatic conversion
234     // to zob behaves as expected
235 
236     const std::string testfile = (test_samples_path / "nonterminalMetalZobs.mae").string();
237 
238     mae::Reader r(testfile);
239     auto block = r.next(mae::CT_BLOCK);
240     BOOST_REQUIRE(block != nullptr);
241 
242     auto* mol = mol_from_mae_block(*block);
243     BOOST_REQUIRE(mol != nullptr);
244 
245     sketcherMinimizer minimizer;
246     minimizer.setTreatNonterminalBondsToMetalAsZOBs(false);
247     auto Al = mol->getAtoms().at(0);
248     auto N = mol->getAtoms().at(1);
249     //make sure we got the right atoms
250     BOOST_REQUIRE_EQUAL(Al->atomicNumber, 13);
251     BOOST_REQUIRE_EQUAL(N->atomicNumber, 7);
252 
253     minimizer.initialize(mol); // minimizer takes ownership of mol
254     minimizer.runGenerateCoordinates();
255     auto bondLength = (Al->coordinates - N->coordinates).length();
256     auto expectedLength = 50.f;
257     auto tolerance = 2.f;
258     BOOST_REQUIRE ((bondLength > expectedLength-tolerance) && (bondLength < expectedLength+tolerance));
259     auto indices = getReportingIndices(*mol);
260     BOOST_CHECK(areBondsNearIdeal(*mol, indices));
261 }
262 
263 
BOOST_AUTO_TEST_CASE(terminalMetalZOBs)264 BOOST_AUTO_TEST_CASE(terminalMetalZOBs)
265 {
266     // Load a molecule with a single bond to a terminal metal. Make sure the bondlength is consistant with a terminal bond
267 
268     const std::string testfile = (test_samples_path / "metalZobs.mae").string();
269 
270     mae::Reader r(testfile);
271     auto block = r.next(mae::CT_BLOCK);
272     BOOST_REQUIRE(block != nullptr);
273 
274     auto* mol = mol_from_mae_block(*block);
275     BOOST_REQUIRE(mol != nullptr);
276 
277     sketcherMinimizer minimizer;
278     auto Al = mol->getAtoms().at(0);
279     auto N = mol->getAtoms().at(1);
280     //make sure we got the right atoms
281     BOOST_REQUIRE_EQUAL(Al->atomicNumber, 13);
282     BOOST_REQUIRE_EQUAL(N->atomicNumber, 7);
283 
284     minimizer.initialize(mol); // minimizer takes ownership of mol
285     minimizer.runGenerateCoordinates();
286     auto bondLength = (Al->coordinates - N->coordinates).length();
287     auto expectedLength = 50.f;
288     auto tolerance = 2.f;
289     BOOST_REQUIRE ((bondLength > expectedLength-tolerance) && (bondLength < expectedLength+tolerance));
290     auto indices = getReportingIndices(*mol);
291     BOOST_CHECK(areBondsNearIdeal(*mol, indices));
292 }
293 
BOOST_AUTO_TEST_CASE(testMinimizedRingsShape)294 BOOST_AUTO_TEST_CASE(testMinimizedRingsShape)
295 {
296     // minimize a complex macrocycle. Make sure rings have the shape of regular polygons
297 
298     const std::string testfile = (test_samples_path / "macrocycle.mae").string();
299 
300     mae::Reader r(testfile);
301     auto block = r.next(mae::CT_BLOCK);
302     BOOST_REQUIRE(block != nullptr);
303 
304     auto* mol = mol_from_mae_block(*block);
305     BOOST_REQUIRE(mol != nullptr);
306 
307     sketcherMinimizer minimizer;
308 
309 
310     minimizer.initialize(mol); // minimizer takes ownership of mol
311     minimizer.runGenerateCoordinates();
312     //check the length of every non macrocycle-ring bond
313     int bondsN = 0;
314     for (auto interaction : minimizer.getStretchInteractions()) {
315         auto ring = sketcherMinimizer::sameRing(interaction->atom1, interaction->atom2);
316         if (ring && !ring->isMacrocycle()) {
317             auto expectedLength = 50.f;
318             auto tolerance = 2.f;
319             auto bondLength = (interaction->atom1->coordinates - interaction->atom2->coordinates).length();
320             BOOST_REQUIRE ((bondLength > expectedLength-tolerance) && (bondLength < expectedLength+tolerance));
321             bondsN++;
322         }
323     }
324 //    check the angles
325     int anglesN = 0;
326     for (auto interaction : minimizer.getBendInteractions()) {
327         if (interaction->isRing) {
328             auto ring = sketcherMinimizer::sameRing(interaction->atom1, interaction->atom2, interaction->atom3);
329             BOOST_REQUIRE (ring != nullptr);
330             BOOST_REQUIRE (!ring->isMacrocycle());
331             auto expectedValue = interaction->restV;
332             auto tolerance = 2.f;
333             auto angle = interaction->angle();
334             BOOST_REQUIRE ((angle > expectedValue-tolerance) && (angle < expectedValue+tolerance));
335             anglesN++;
336         }
337     }
338     BOOST_REQUIRE (anglesN  == 32);
339 }
340 
341 
BOOST_AUTO_TEST_CASE(testPolyominoCoordinatesOfSubstituent)342 BOOST_AUTO_TEST_CASE(testPolyominoCoordinatesOfSubstituent)
343 {
344     Polyomino p;
345     p.addHex(hexCoords(0, 0));
346     auto substCoords =
347         p.coordinatesOfSubstituent(vertexCoords(1, 0, 0));
348     BOOST_REQUIRE(substCoords == vertexCoords(1, -1, -1));
349 
350     p.addHex(hexCoords(1, 0));
351     substCoords = p.coordinatesOfSubstituent(vertexCoords(1, 0, 0));
352     BOOST_REQUIRE(substCoords == vertexCoords(0, 0, -1));
353 }
354 
BOOST_AUTO_TEST_CASE(testPolyominoSameAs)355 BOOST_AUTO_TEST_CASE(testPolyominoSameAs)
356 {
357     // identity
358     Polyomino p1;
359     p1.addHex(hexCoords(0, 0));
360     p1.addHex(hexCoords(1, 0));
361     p1.addHex(hexCoords(2, 0));
362     p1.addHex(hexCoords(0, 1));
363     BOOST_REQUIRE(p1.isTheSameAs(p1));
364 
365     // order-independence
366     Polyomino p2;
367     p2.addHex(hexCoords(2, 0));
368     p2.addHex(hexCoords(0, 0));
369     p2.addHex(hexCoords(0, 1));
370     p2.addHex(hexCoords(1, 0));
371     BOOST_REQUIRE(p1.isTheSameAs(p2));
372     BOOST_REQUIRE(p2.isTheSameAs(p1));
373 
374     // translation
375     Polyomino p3;
376     p3.addHex(hexCoords(4, 2));
377     p3.addHex(hexCoords(5, 2));
378     p3.addHex(hexCoords(6, 2));
379     p3.addHex(hexCoords(4, 3));
380     BOOST_REQUIRE(p1.isTheSameAs(p3));
381     BOOST_REQUIRE(p3.isTheSameAs(p1));
382 
383     // rotation
384     Polyomino p4;
385     p4.addHex(hexCoords(0, 0));
386     p4.addHex(hexCoords(-1, 0));
387     p4.addHex(hexCoords(-2, 0));
388     p4.addHex(hexCoords(0, -1));
389     BOOST_REQUIRE(p1.isTheSameAs(p4));
390     BOOST_REQUIRE(p4.isTheSameAs(p1));
391 
392     // symmetry
393     Polyomino p5;
394     p5.addHex(hexCoords(0, 0));
395     p5.addHex(hexCoords(0, 1));
396     p5.addHex(hexCoords(0, 2));
397     p5.addHex(hexCoords(1, 0));
398     BOOST_REQUIRE(!p1.isTheSameAs(p5));
399     BOOST_REQUIRE(!p5.isTheSameAs(p1));
400 
401     // different number of points
402     Polyomino p6;
403     p6.addHex(hexCoords(1, 0));
404     p6.addHex(hexCoords(2, 0));
405     p6.addHex(hexCoords(0, 1));
406     BOOST_REQUIRE(!p1.isTheSameAs(p6));
407     BOOST_REQUIRE(!p6.isTheSameAs(p1));
408 }
409 
BOOST_AUTO_TEST_CASE(testGetDoubleBondConstraints)410 BOOST_AUTO_TEST_CASE(testGetDoubleBondConstraints)
411 {
412     /*
413      test that getDoubleBondConstraints behaves as expected:
414      notably don't set up interactions for double bonds that are also part of
415      a small ring.
416      CRDGEN-160
417      */
418     sketcherMinimizer min;
419     CoordgenFragmentBuilder fragmentBuilder;
420     CoordgenMacrocycleBuilder macrocycleBuilder;
421 
422     const std::string testfile = (test_samples_path / "test_mol.mae").string();
423     mae::Reader r(testfile);
424     auto block = r.next(mae::CT_BLOCK);
425     BOOST_REQUIRE(block != nullptr);
426 
427     auto* mol = mol_from_mae_block(*block);
428     BOOST_REQUIRE(mol != nullptr);
429 
430     min.initialize(mol); // minimizer takes ownership of mol
431     for (auto molecule : min.getMolecules()) {
432         for (auto ring : molecule->getRings()) {
433             std::vector<sketcherMinimizerAtom*> atoms =
434                 fragmentBuilder.orderRingAtoms(ring);
435             std::vector<doubleBondConstraint> constraints =
436                 macrocycleBuilder.getDoubleBondConstraints(atoms);
437             BOOST_REQUIRE(constraints.size() == 0);
438         }
439     }
440 }
441 
BOOST_AUTO_TEST_CASE(testClockwiseOrderedSubstituents)442 BOOST_AUTO_TEST_CASE(testClockwiseOrderedSubstituents)
443 {
444     auto mol = "CN(C)C"_smiles;
445 
446     sketcherMinimizer minimizer;
447     minimizer.initialize(mol); // minimizer takes ownership of mol
448     minimizer.runGenerateCoordinates();
449 
450     const auto& atoms = minimizer.getAtoms();
451     sketcherMinimizerAtom* center = atoms.at(0);
452     sketcherMinimizerAtom* neigh1 = atoms.at(1);
453     sketcherMinimizerAtom* neigh2 = atoms.at(2);
454     sketcherMinimizerAtom* neigh3 = atoms.at(3);
455     BOOST_REQUIRE_EQUAL(center->getAtomicNumber(), 7);
456 
457     sketcherMinimizerPointF origin(0, 0);
458     sketcherMinimizerPointF above(0, 50);
459     sketcherMinimizerPointF left(-50, 0);
460     sketcherMinimizerPointF right(50, 0);
461 
462     center->coordinates = origin;
463     neigh1->coordinates = above;
464     neigh2->coordinates = left;
465     neigh3->coordinates = right;
466 
467     auto orderedNeighbors =
468         center->clockwiseOrderedNeighbors();
469 
470     BOOST_REQUIRE((orderedNeighbors[0]->coordinates - above).length() == 0);
471     BOOST_REQUIRE((orderedNeighbors[1]->coordinates - left).length() == 0);
472     BOOST_REQUIRE((orderedNeighbors[2]->coordinates - right).length() == 0);
473 
474     BOOST_REQUIRE_EQUAL(orderedNeighbors[0], neigh1);
475     BOOST_REQUIRE_EQUAL(orderedNeighbors[1], neigh2);
476     BOOST_REQUIRE_EQUAL(orderedNeighbors[2], neigh3);
477 
478     neigh1->coordinates = above;
479     neigh3->coordinates = left;
480     neigh2->coordinates = right;
481 
482     orderedNeighbors = center->clockwiseOrderedNeighbors();
483 
484     BOOST_REQUIRE((orderedNeighbors[0]->coordinates - above).length() == 0);
485     BOOST_REQUIRE((orderedNeighbors[1]->coordinates - left).length() == 0);
486     BOOST_REQUIRE((orderedNeighbors[2]->coordinates - right).length() == 0);
487 
488     BOOST_REQUIRE_EQUAL(orderedNeighbors[0], neigh1);
489     BOOST_REQUIRE_EQUAL(orderedNeighbors[1], neigh3);
490     BOOST_REQUIRE_EQUAL(orderedNeighbors[2], neigh2);
491 }
492 
BOOST_AUTO_TEST_CASE(testbicyclopentane)493 BOOST_AUTO_TEST_CASE(testbicyclopentane)
494 {
495     /*
496      test that bicyclo(1,1,1)pentane is rendered without clashes between the bridge carbons
497      CRDGEN-270
498      */
499 
500     auto mol = "C1C2CC1C2"_smiles;
501     sketcherMinimizer minimizer;
502     minimizer.initialize(mol); // minimizer takes ownership of mol
503     minimizer.runGenerateCoordinates();
504 
505     const auto& atoms = minimizer.getAtoms();
506     auto bridgeAtom1 = atoms.at(1);
507     auto bridgeAtom2 = atoms.at(2);
508     auto bridgeAtom3 = atoms.at(3);
509     BOOST_TEST(bridgeAtom1->neighbors.size() == 2);
510     BOOST_TEST(bridgeAtom2->neighbors.size() == 2);
511     BOOST_TEST(bridgeAtom3->neighbors.size() == 2);
512     auto distance1 = (bridgeAtom1->getCoordinates() - bridgeAtom2->getCoordinates()).length();
513     auto distance2 = (bridgeAtom1->getCoordinates() - bridgeAtom3->getCoordinates()).length();
514     auto distance3 = (bridgeAtom2->getCoordinates() - bridgeAtom3->getCoordinates()).length();
515     auto minimumDistance = 15.f;
516     BOOST_TEST(distance1 > minimumDistance);
517     BOOST_TEST(distance2 > minimumDistance);
518     BOOST_TEST(distance3 > minimumDistance);
519 }
520 
BOOST_AUTO_TEST_CASE(testFusedRings)521 BOOST_AUTO_TEST_CASE(testFusedRings)
522 {
523     /*
524      CRDGEN271, CRDGEN-272
525      */
526 
527     std::vector<std::string> smiles {"C1CCC23CCCCC2CC3C1",
528         "C1=CC2C3CC4C(CC3NC3CCCC(C32)N1)NC1CCCC2C1C4CCN2"};
529     for (auto smile : smiles) {
530         auto mol = approxSmilesParse(smile);
531         sketcherMinimizer minimizer;
532         minimizer.initialize(mol); // minimizer takes ownership of mol
533         minimizer.runGenerateCoordinates();
534         auto indices = getReportingIndices(*mol);
535         BOOST_CHECK(areBondsNearIdeal(*mol, indices));
536         BOOST_CHECK(noCrossingBonds(*mol, indices));
537     }
538 }
539 
540 
BOOST_AUTO_TEST_CASE(testTemplates)541 BOOST_AUTO_TEST_CASE(testTemplates)
542 {
543     auto mol = "C12CC3CC(CC3C1)C2"_smiles;
544     sketcherMinimizer minimizer;
545     minimizer.initialize(mol); // minimizer takes ownership of mol
546     minimizer.runGenerateCoordinates();
547     auto indices = getReportingIndices(*mol);
548     //template has two stretched bonds
549     std::set<std::pair<int, int> > skip;
550     skip.insert(std::pair<int, int> (2, 5));
551     skip.insert(std::pair<int, int> (6, 2));
552     BOOST_CHECK(areBondsNearIdeal(*mol, indices, skip));
553 }
554 
555 
BOOST_AUTO_TEST_CASE(testRingComplex)556 BOOST_AUTO_TEST_CASE(testRingComplex)
557 {
558     auto mol = "CC1CC2CCCC(C3CC4CCC(C3)C4C)C2O1"_smiles;
559     sketcherMinimizer minimizer;
560     minimizer.initialize(mol); // minimizer takes ownership of mol
561     minimizer.runGenerateCoordinates();
562     auto indices = getReportingIndices(*mol);
563     BOOST_CHECK(noCrossingBonds(*mol, indices));
564 }
565 
BOOST_AUTO_TEST_CASE(testCoordgenFragmenter)566 BOOST_AUTO_TEST_CASE(testCoordgenFragmenter)
567 {
568     /*
569      Test that a molecule is fragmented as expected and fragments are given the
570      correct flags. Atoms 3-8 and 11 are constrained.
571                             6
572                           /   \
573      1 -- 2 -- 3 -- 4 -- 5     7 -- 8 -- 9 -- 10
574                           \   /
575                             11
576     */
577     auto mol = "CCCCC1CC(CCC)C1"_smiles;
578     auto atoms = mol->getAtoms();
579     for (int i = 3; i <= 8; ++i) {
580         atoms[i-1]->constrained = true;
581     }
582     atoms[10]->constrained = true;
583     auto atom_map = getReportingIndices(*mol);
584 
585     sketcherMinimizer minimizer;
586     minimizer.initialize(mol);
587     CoordgenFragmenter::splitIntoFragments(mol);
588 
589     std::vector<std::set<int>> expected_fragments = {{1, 2}, {3}, {4}, {5, 6, 7, 11}, {8}, {9, 10}};
590     std::vector<std::set<int>> actual_fragments;
591     for (auto fragment : mol->_fragments) {
592         std::set<int> fragment_idices;
593         for (auto at : fragment->getAtoms()) {
594             fragment_idices.insert(atom_map[at]);
595         }
596         actual_fragments.push_back(fragment_idices);
597     }
598     std::sort(actual_fragments.begin(), actual_fragments.end());
599 
600     // Check that the fragmenting is correct
601     BOOST_REQUIRE_EQUAL(actual_fragments.size(), expected_fragments.size());
602     for (size_t i = 0; i < actual_fragments.size(); ++i) {
603         BOOST_CHECK_EQUAL_COLLECTIONS(expected_fragments[i].begin(), expected_fragments[i].end(), actual_fragments[i].begin(), actual_fragments[i].end());
604     }
605 
606     // Fragment containing atoms (1, 2)
607     BOOST_TEST(atoms[0]->fragment->constrained == false);
608     BOOST_TEST(atoms[0]->fragment->constrainedFlip == false);
609 
610     // Fragment containing atom 3. Flip should not be constrained since
611     // the fragment does not have any constrained child fragments
612     BOOST_TEST(atoms[2]->fragment->constrained == true);
613     BOOST_TEST(atoms[2]->fragment->constrainedFlip == false);
614 
615     // Fragment containing atom 4. Flip should be constrained since fragment
616     // has a child fragment that is constrained
617     BOOST_TEST(atoms[3]->fragment->constrained == true);
618     BOOST_TEST(atoms[3]->fragment->constrainedFlip == true);
619 
620     // Fragment containing atoms (5, 6, 7, 11)
621     BOOST_TEST(atoms[4]->fragment->constrained == true);
622     BOOST_TEST(atoms[4]->fragment->constrainedFlip == true);
623 
624     // Fragment containing atom 8
625     BOOST_TEST(atoms[7]->fragment->constrained == true);
626     BOOST_TEST(atoms[7]->fragment->constrainedFlip == false);
627 
628     // Fragment containing atoms (9, 10)
629     BOOST_TEST(atoms[8]->fragment->constrained == false);
630     BOOST_TEST(atoms[8]->fragment->constrainedFlip == false);
631 }
632