diff --git a/core/indigo-core/molecule/src/molfile_saver.cpp b/core/indigo-core/molecule/src/molfile_saver.cpp index db45ca31c3..6d638c69c1 100644 --- a/core/indigo-core/molecule/src/molfile_saver.cpp +++ b/core/indigo-core/molecule/src/molfile_saver.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include "base_cpp/locale_guard.h" #include "base_cpp/output.h" @@ -787,11 +788,29 @@ void MolfileSaver::_writeCtab(Output& output, BaseMolecule& mol, bool query) output.writeStringCR("M V30 END BOND"); + QS_DEF(Array, sgs_sorted); + _checkSGroupIndices(mol, sgs_sorted); + + //[Sapio] [CHEMBUGS-184] S-GROUP needs to be before COLLECTION when S GROUP is used. + const bool has_sgroups = (mol.countSGroups() > 0); MoleculeStereocenters& stereocenters = mol.stereocenters; + const bool has_collection = (stereocenters.begin() != stereocenters.end() || mol.hasHighlighting() || mol.custom_collections.size() > 0); + const bool sgroup_collection_order_reversed = (has_sgroups && has_collection); + + // Default order: COLLECTION before SGROUP. + // When sgroup_collection_order_reversed, write SGROUP then COLLECTION (CT spec) page 22. + std::string collection_str; + StringOutput collection_buf(collection_str); + Output* collection_dest_ptr; + if (sgroup_collection_order_reversed) + collection_dest_ptr = static_cast(&collection_buf); + else + collection_dest_ptr = &output; + Output& collection_dest = *collection_dest_ptr; - if (stereocenters.begin() != stereocenters.end() || mol.hasHighlighting()) + if (has_collection) { - output.writeStringCR("M V30 BEGIN COLLECTION"); + collection_dest.writeStringCR("M V30 BEGIN COLLECTION"); QS_DEF(Array, processed); @@ -832,7 +851,7 @@ void MolfileSaver::_writeCtab(Output& output, BaseMolecule& mol, bool query) out.printf(" %d", _atom_mapping[list[j]]); out.writeChar(')'); - _writeMultiString(output, buf.ptr(), buf.size()); + _writeMultiString(collection_dest, buf.ptr(), buf.size()); } if (mol.hasHighlighting()) @@ -848,7 +867,7 @@ void MolfileSaver::_writeCtab(Output& output, BaseMolecule& mol, bool query) out.printf(" %d", _bond_mapping[i]); out.writeChar(')'); - _writeMultiString(output, buf.ptr(), buf.size()); + _writeMultiString(collection_dest, buf.ptr(), buf.size()); } if (mol.countHighlightedAtoms() > 0) { @@ -859,7 +878,7 @@ void MolfileSaver::_writeCtab(Output& output, BaseMolecule& mol, bool query) out.printf(" %d", _atom_mapping[i]); out.writeChar(')'); - _writeMultiString(output, buf.ptr(), buf.size()); + _writeMultiString(collection_dest, buf.ptr(), buf.size()); } } if (mol.custom_collections.size() > 0) @@ -868,17 +887,14 @@ void MolfileSaver::_writeCtab(Output& output, BaseMolecule& mol, bool query) { ArrayOutput out(buf); out.printf("%s", mol.custom_collections.at(i)); - _writeMultiString(output, buf.ptr(), buf.size()); + _writeMultiString(collection_dest, buf.ptr(), buf.size()); } } - output.writeStringCR("M V30 END COLLECTION"); + collection_dest.writeStringCR("M V30 END COLLECTION"); } - QS_DEF(Array, sgs_sorted); - _checkSGroupIndices(mol, sgs_sorted); - - if (mol.countSGroups() > 0) + if (has_sgroups) { MoleculeSGroups* sgroups = &mol.sgroups; int idx = 1; @@ -1074,6 +1090,9 @@ void MolfileSaver::_writeCtab(Output& output, BaseMolecule& mol, bool query) _removeImplicitSGroups(mol, implicit_sgroups_indexes); } + if (sgroup_collection_order_reversed) + output.write(collection_str.data(), static_cast(collection_str.size())); + output.writeStringCR("M V30 END CTAB"); int n_rgroups = mol.rgroups.getRGroupCount(); diff --git a/core/indigo-core/tests/common.cpp b/core/indigo-core/tests/common.cpp index 9dafb72df0..4a442bce02 100644 --- a/core/indigo-core/tests/common.cpp +++ b/core/indigo-core/tests/common.cpp @@ -1,4 +1,6 @@ #include "common.h" +#include "reaction/reaction.h" +#include "reaction/reaction_auto_loader.h" #include "reaction/reaction_cml_saver.h" #include "reaction/reaction_json_saver.h" #include "reaction/rxnfile_saver.h" diff --git a/core/indigo-core/tests/tests/molecule.cpp b/core/indigo-core/tests/tests/molecule.cpp index 35630248fb..3d569d1ca2 100644 --- a/core/indigo-core/tests/tests/molecule.cpp +++ b/core/indigo-core/tests/tests/molecule.cpp @@ -24,11 +24,13 @@ #include #include #include +#include #include #include #include #include "common.h" +#include "molecule/elements.h" using namespace std; using namespace indigo; @@ -352,3 +354,250 @@ TEST_F(IndigoCoreMoleculeTest, dearomatize_smarts) // printf("%s", sm.c_str()); EXPECT_STREQ("c1-c=c-c=c-c=1", sm.c_str()); } + +// [Sapio] [CHEMBUGS-184] Stereo reaction molecules (from former stereo_reaction.rxn): load as mol blocks to verify parsing. +namespace +{ + const char* const stereoReactionReactant1 = R"( + Mrv2305 05232323372D + + 18 19 0 0 0 0 999 V2000 + -6.9420 -0.0902 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + -7.6565 -0.5027 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + -7.6565 -1.3278 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + -6.9420 -1.7402 0.0000 N 0 0 0 0 0 0 0 0 0 0 0 0 + -6.2275 -1.3278 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + -6.2275 -0.5027 0.0000 N 0 0 0 0 0 0 0 0 0 0 0 0 + -5.5357 0.0000 0.0000 C 0 0 2 0 0 0 0 0 0 0 0 0 + -4.7511 -0.2549 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0 + -4.2661 0.4125 0.0000 C 0 0 1 0 0 0 0 0 0 0 0 0 + -4.7510 1.0800 0.0000 C 0 0 2 0 0 0 0 0 0 0 0 0 + -5.5356 0.8250 0.0000 C 0 0 1 0 0 0 0 0 0 0 0 0 + -3.4411 0.4125 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + -6.2031 1.3100 0.0000 F 0 0 0 0 0 0 0 0 0 0 0 0 + -4.4961 1.8646 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0 + -5.4066 1.6399 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + -8.3709 -1.7403 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0 + -5.5130 -1.7403 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0 + -2.8891 1.0256 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0 + 1 2 2 0 0 0 0 + 2 3 1 0 0 0 0 + 9 10 1 0 0 0 0 + 10 11 1 0 0 0 0 + 7 11 1 0 0 0 0 + 9 12 1 1 0 0 0 + 3 16 2 0 0 0 0 + 5 17 2 0 0 0 0 + 8 9 1 0 0 0 0 + 7 8 1 0 0 0 0 + 10 14 1 6 0 0 0 + 12 18 1 0 0 0 0 + 11 15 1 1 0 0 0 + 11 13 1 6 0 0 0 + 1 6 1 0 0 0 0 + 5 6 1 0 0 0 0 + 7 6 1 1 0 0 0 + 3 4 1 0 0 0 0 + 4 5 1 0 0 0 0 +M STY 1 1 SUP +M SAL 1 1 15 +M SBL 1 1 13 +M SMT 1 Me +M SAP 1 1 15 11 1 +M END +)"; + const char* const stereoReactionReactant2 = R"( + Mrv2305 05232323372D + + 24 26 0 0 0 0 999 V2000 + -4.7105 -3.1000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + -1.1382 -0.2125 0.0000 Cl 0 0 0 0 0 0 0 0 0 0 0 0 + -4.7106 -2.2750 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0 + -3.9961 -1.8625 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + -3.9961 -1.0375 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + -3.2816 -0.6250 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + -2.5671 -1.0375 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + -2.5671 -1.8625 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + -3.2816 -2.2750 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + -1.8527 -0.6250 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + -1.8527 0.2000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + -2.5672 0.6126 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + -2.5671 1.4375 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + -1.8527 1.8500 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + -1.1382 1.4376 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + -1.1382 0.6125 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + -1.1382 -1.0375 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + -0.4237 -0.6250 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 0.2907 -1.0375 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 0.2907 -1.8625 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 1.0052 -2.2750 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0 + 1.7197 -1.8625 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + -0.4237 -2.2750 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + -1.1382 -1.8625 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 10 2 1 0 0 0 0 + 1 3 1 0 0 0 0 + 3 4 1 0 0 0 0 + 4 5 2 0 0 0 0 + 4 9 1 0 0 0 0 + 5 6 1 0 0 0 0 + 6 7 2 0 0 0 0 + 7 8 1 0 0 0 0 + 7 10 1 0 0 0 0 + 8 9 2 0 0 0 0 + 10 11 1 0 0 0 0 + 10 17 1 0 0 0 0 + 11 12 2 0 0 0 0 + 11 16 1 0 0 0 0 + 12 13 1 0 0 0 0 + 13 14 2 0 0 0 0 + 14 15 1 0 0 0 0 + 15 16 2 0 0 0 0 + 17 18 2 0 0 0 0 + 17 24 1 0 0 0 0 + 18 19 1 0 0 0 0 + 19 20 2 0 0 0 0 + 20 21 1 0 0 0 0 + 20 23 1 0 0 0 0 + 21 22 1 0 0 0 0 + 23 24 2 0 0 0 0 +M STY 1 1 SUP +M SAL 1 15 1 3 4 5 6 7 8 9 10 11 12 13 14 15 16 +M SAL 1 8 17 18 19 20 21 22 23 24 +M SBL 1 1 1 +M SMT 1 DMTr +M SAP 1 1 10 2 1 +M END +)"; + const char* const stereoReactionProduct = R"( + Mrv2305 05232323372D + + 41 45 0 0 0 0 999 V2000 + 2.9291 -0.1300 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 2.2146 -0.5425 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 2.2146 -1.3676 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 2.9291 -1.7800 0.0000 N 0 0 0 0 0 0 0 0 0 0 0 0 + 3.6436 -1.3676 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 3.6436 -0.5425 0.0000 N 0 0 0 0 0 0 0 0 0 0 0 0 + 4.3354 -0.0398 0.0000 C 0 0 2 0 0 0 0 0 0 0 0 0 + 5.1200 -0.2947 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0 + 5.6049 0.3727 0.0000 C 0 0 1 0 0 0 0 0 0 0 0 0 + 5.1200 1.0402 0.0000 C 0 0 2 0 0 0 0 0 0 0 0 0 + 4.3354 0.7852 0.0000 C 0 0 1 0 0 0 0 0 0 0 0 0 + 6.4299 0.3727 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 3.6680 1.2701 0.0000 F 0 0 0 0 0 0 0 0 0 0 0 0 + 5.3749 1.8248 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0 + 4.4645 1.6000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 1.5002 -1.7801 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0 + 4.3581 -1.7801 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0 + 6.9820 0.9858 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0 + 11.5195 1.7003 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 11.1070 0.9858 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0 + 10.2820 0.9858 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 9.8695 0.2713 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 9.0445 0.2713 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 8.6320 0.9858 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 9.0445 1.7003 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 9.8695 1.7003 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 7.8070 0.9858 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 7.3945 0.2713 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 7.8070 -0.4432 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 7.3945 -1.1576 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 6.5695 -1.1576 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 6.1570 -0.4432 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 6.5695 0.2713 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 7.3945 1.7003 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 6.5695 1.7003 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 6.1570 2.4147 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 6.5695 3.1292 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 6.1570 3.8437 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0 + 5.3320 3.8437 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 7.3945 3.1292 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 7.8070 2.4147 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 + 1 2 2 0 0 0 0 + 2 3 1 0 0 0 0 + 9 10 1 0 0 0 0 + 10 11 1 0 0 0 0 + 7 11 1 0 0 0 0 + 9 12 1 1 0 0 0 + 3 16 2 0 0 0 0 + 5 17 2 0 0 0 0 + 8 9 1 0 0 0 0 + 7 8 1 0 0 0 0 + 10 14 1 6 0 0 0 + 12 18 1 0 0 0 0 + 11 15 1 1 0 0 0 + 11 13 1 6 0 0 0 + 1 6 1 0 0 0 0 + 5 6 1 0 0 0 0 + 7 6 1 1 0 0 0 + 3 4 1 0 0 0 0 + 4 5 1 0 0 0 0 + 18 27 1 0 0 0 0 + 19 20 1 0 0 0 0 + 20 21 1 0 0 0 0 + 21 22 2 0 0 0 0 + 21 26 1 0 0 0 0 + 22 23 1 0 0 0 0 + 23 24 2 0 0 0 0 + 24 25 1 0 0 0 0 + 24 27 1 0 0 0 0 + 25 26 2 0 0 0 0 + 27 28 1 0 0 0 0 + 27 34 1 0 0 0 0 + 28 29 2 0 0 0 0 + 28 33 1 0 0 0 0 + 29 30 1 0 0 0 0 + 30 31 2 0 0 0 0 + 31 32 1 0 0 0 0 + 32 33 2 0 0 0 0 + 34 35 2 0 0 0 0 + 34 41 1 0 0 0 0 + 35 36 1 0 0 0 0 + 36 37 2 0 0 0 0 + 37 38 1 0 0 0 0 + 37 40 1 0 0 0 0 + 38 39 1 0 0 0 0 + 40 41 2 0 0 0 0 +M STY 2 1 SUP 2 SUP +M SAL 1 1 15 +M SBL 1 1 13 +M SMT 1 Me +M SAP 1 1 15 11 1 +M SAL 2 15 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 +M SAL 2 8 34 35 36 37 38 39 40 41 +M SBL 2 1 20 +M SMT 2 DMTr +M SAP 2 1 27 18 1 +M END +)"; +} + +TEST_F(IndigoCoreMoleculeTest, stereoReactionReactant1) +{ + Molecule mol; + BufferScanner scanner(stereoReactionReactant1); + MolfileLoader loader(scanner); + loader.loadMolecule(mol); + EXPECT_EQ(18, mol.vertexCount()); + EXPECT_EQ(19, mol.edgeCount()); +} + +TEST_F(IndigoCoreMoleculeTest, stereoReactionReactant2) +{ + Molecule mol; + BufferScanner scanner(stereoReactionReactant2); + MolfileLoader loader(scanner); + loader.loadMolecule(mol); + EXPECT_EQ(24, mol.vertexCount()); + EXPECT_EQ(26, mol.edgeCount()); +} + +TEST_F(IndigoCoreMoleculeTest, stereoReactionProduct) +{ + Molecule mol; + BufferScanner scanner(stereoReactionProduct); + MolfileLoader loader(scanner); + loader.loadMolecule(mol); + EXPECT_EQ(41, mol.vertexCount()); + EXPECT_EQ(45, mol.edgeCount()); +} \ No newline at end of file