From d6dc2930803c5f70f53dcf1692f2f93d917a7cc1 Mon Sep 17 00:00:00 2001 From: Christoph Mueller Date: Fri, 19 Dec 2025 12:35:03 +0000 Subject: [PATCH 01/44] Added unit test prototype for testing OutputINCHI_StereoLayer --- INCHI-1-SRC/INCHI_BASE/src/ichiprt1.c | 41 ++++++++++--------- .../tests/test_unit/test_ichiprt1.cpp | 30 ++++++++++++++ .../tests/test_unit/test_inchi_dll_b.cpp | 2 + 3 files changed, 53 insertions(+), 20 deletions(-) create mode 100644 INCHI-1-TEST/tests/test_unit/test_ichiprt1.cpp diff --git a/INCHI-1-SRC/INCHI_BASE/src/ichiprt1.c b/INCHI-1-SRC/INCHI_BASE/src/ichiprt1.c index 38f7debe..6ed41b1c 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/ichiprt1.c +++ b/INCHI-1-SRC/INCHI_BASE/src/ichiprt1.c @@ -150,11 +150,11 @@ static int OutputINCHI_PolymerLayer( CANON_GLOBALS *pCG, INCHI_IOSTREAM *out_fil INCHI_OUT_CTL *io, char *pLF, char *pTAB ); static int OutputINCHI_PolymerLayer_SingleUnit( OAD_PolymerUnit *u, int bPolymers, - int total_star_atoms, + int total_star_atoms, int *n_used_stars, - OAD_AtProps *aprops, + OAD_AtProps *aprops, int *cano_nums, - ORIG_ATOM_DATA *orig_inp_data, + ORIG_ATOM_DATA *orig_inp_data, ORIG_STRUCT *pOrigStruct, INCHI_IOS_STRING *strbuf ); static int OutputAUXINFO_HeaderAndNormalization_type( CANON_GLOBALS *pCG, @@ -1160,7 +1160,7 @@ int OutputINChI1( CANON_GLOBALS *pCG, io.n_pzz = orig_inp_data->polymer->n_pzz; } } - + io.bPolymers = ip->bPolymers; @@ -2640,7 +2640,7 @@ int WriteOrigBonds( CANON_GLOBALS *pCG, num_trans = 0; /* djb-rwth: ignoring LLVM warning: variable used to store function return value */ nNeighOrder[0] = 0; } - for (kk = 0; kk < at[j].valence; kk++) + for (kk = 0; kk < at[j].valence; kk++) { k = nNeighOrder[kk]; j2 = at[j].neighbor[k]; @@ -3121,7 +3121,8 @@ void set_line_separators( int bINChIOutputOptions, char **pLF, char **pTAB ) int bPlainTabbedOutput = 0 != ( bINChIOutputOptions & INCHI_OUT_TABBED_OUTPUT ) && bPlainText && !bPlainTextCommnts; - *pTAB = bPlainTabbedOutput ? "\t" : "\n"; + const char* tab = bPlainTabbedOutput ? "\t" : "\n"; + strcpy(*pTAB, tab); } #else *pTAB = "\n"; @@ -3908,7 +3909,7 @@ static int OutputINCHI_PolymerLayer( CANON_GLOBALS *pCG, nat = orig_inp_data->num_inp_atoms; num_inp_bonds = orig_inp_data->num_inp_bonds; - + if (pOrigStruct && !pOrigStruct->polymer) { return 0; @@ -4145,7 +4146,7 @@ static int OutputINCHI_PolymerLayer_SingleUnit( OAD_PolymerUnit *u, a2 = u->blist[1]; a3 = u->blist[2]; a4 = u->blist[3]; - + if (is_in_the_ilist( u->alist, a1, u->na )) { tmp = a2; @@ -4172,7 +4173,7 @@ static int OutputINCHI_PolymerLayer_SingleUnit( OAD_PolymerUnit *u, /* The first printed is the crossing bond pointing to more senior CRU end ("head") */ swap = (OAD_Polymer_IsFirstAtomRankLower(a2, a4, aprops) == 1); } - + if (swap) { inchi_strbuf_printf( strbuf, "(%-d-%-d,%-d-%-d)", a3, a4, a1, a2 ); @@ -4288,8 +4289,8 @@ static int OutputINCHI_PolymerLayer_SingleUnit( OAD_PolymerUnit *u, here at1, at2 is the most senior bond, and at1 is more senior than at2 all other pairs at3,at4, at5,at6, ... are sorted just in increasing - order of first number in pair, then second one, e.g.: at3frame_shift_scheme != FSS_NONE && u->nbkbonds >= 1 && u->cap1 >= 1 && u->cap2 >= 1) @@ -5046,7 +5047,7 @@ void EditINCHI_HidePolymerZz(INCHI_IOSTREAM *out, int n_pzz, int n_zy) eol_was_consumed = 0, pre_eol = 0, nonprt_sym = 0, nonprt_prev = 0; - if (n_zy > 0) + if (n_zy > 0) { /* We have some placeholder pseudo atoms which should not be removed below (if anyway they are allowed) */ if (n_pzz == 0) @@ -5131,7 +5132,7 @@ void EditINCHI_HidePolymerZz(INCHI_IOSTREAM *out, int n_pzz, int n_zy) AT_NUMB ia = (AT_NUMB) inchi_strtol(p, &q, 10); /* make compiler happy: */ /* djb-rwth: removing redundant code; ignoring LLVM warning: variable used to store function return value */ if (*q != '-') { - skip = 1; + skip = 1; } } } @@ -5283,7 +5284,7 @@ int CountPseudoElementInFormula( const char *pseudo, char *s ) /* djb-rwth: igno char prev = '/'; /* - format is + format is [sequence of] [.[int_mult[Zz[int_index]]]] */ @@ -5291,8 +5292,8 @@ int CountPseudoElementInFormula( const char *pseudo, char *s ) /* djb-rwth: igno { return 0; } - - p = s; + + p = s; while (*p) /*for (p = s ; *p; p++)*/ { @@ -5317,7 +5318,7 @@ int CountPseudoElementInFormula( const char *pseudo, char *s ) /* djb-rwth: igno { mult = (int)inchi_strtol(p, &q, 10); p = q; - prev = *q--; + prev = *q--; continue; } else @@ -5326,7 +5327,7 @@ int CountPseudoElementInFormula( const char *pseudo, char *s ) /* djb-rwth: igno } if (!mult) { - break; + break; } } else if (*p== pseudo[1] && prev== pseudo[0]) @@ -5446,7 +5447,7 @@ int MergeZzInHillFormula(INCHI_IOS_STRING *strbuf) { inchi_free(scopy); /* djb-rwth: avoiding memory leak */ return -1; /* failed */ - } + } memcpy(scopy, strbuf->pStr, strbuf->nAllocatedLength); stmp = (char *)inchi_calloc((long long)strbuf->nAllocatedLength + 1, sizeof(char)); /* djb-rwth: cast operator added */ if (!stmp) @@ -5458,7 +5459,7 @@ int MergeZzInHillFormula(INCHI_IOS_STRING *strbuf) inchi_strbuf_reset(strbuf); p0 = scopy; p = p0; - do + do { /* djb-rwth: removing redundant code */ pend = strchr(p, '.'); diff --git a/INCHI-1-TEST/tests/test_unit/test_ichiprt1.cpp b/INCHI-1-TEST/tests/test_unit/test_ichiprt1.cpp new file mode 100644 index 00000000..97e99207 --- /dev/null +++ b/INCHI-1-TEST/tests/test_unit/test_ichiprt1.cpp @@ -0,0 +1,30 @@ +#include +#include +#include +#include +#include +#include +#include + +extern "C" +{ +// #include "../../../INCHI-1-SRC/INCHI_BASE/src/ichitime.h" +// #include "../../../INCHI-1-SRC/INCHI_BASE/src/ichicant.h" +// #include "../../../INCHI-1-SRC/INCHI_BASE/src/ichi_io.h" +#include "../../../INCHI-1-SRC/INCHI_BASE/src/ichiprt1.c" +} + + +TEST(ichiprt1_testing, test_OutputINCHI_StereoLayer) +{ + + + // int OutputINCHI_StereoLayer( CANON_GLOBALS *pCG, + // INCHI_IOSTREAM *out_file, + // INCHI_IOS_STRING *strbuf, + // INCHI_OUT_CTL *io, + // char *pLF, + // char *pTAB ) + + +} diff --git a/INCHI-1-TEST/tests/test_unit/test_inchi_dll_b.cpp b/INCHI-1-TEST/tests/test_unit/test_inchi_dll_b.cpp index 1118c7fd..d0a311fd 100644 --- a/INCHI-1-TEST/tests/test_unit/test_inchi_dll_b.cpp +++ b/INCHI-1-TEST/tests/test_unit/test_inchi_dll_b.cpp @@ -88,5 +88,7 @@ TEST(inchi_dll_b_testing, test_MakeINCHIFromMolfileText) // Works with other V3000 molfile. ASSERT_EQ(MakeINCHIFromMolfileText(ethanol, options, poutput), 0); + poutput->szLog = nullptr; + poutput->szMessage = nullptr; FreeINCHI(poutput); } From e5f44036bbbeec41ac9286b157b8d520aa31315a Mon Sep 17 00:00:00 2001 From: Christoph Mueller Date: Fri, 19 Dec 2025 13:09:47 +0000 Subject: [PATCH 02/44] Added new unit-tests --- INCHI-1-SRC/INCHI_BASE/src/ichiprt1.c | 3 +- .../tests/test_unit/test_ichiprt1.cpp | 53 ++++++++++++++++++- 2 files changed, 53 insertions(+), 3 deletions(-) diff --git a/INCHI-1-SRC/INCHI_BASE/src/ichiprt1.c b/INCHI-1-SRC/INCHI_BASE/src/ichiprt1.c index 6ed41b1c..672ba198 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/ichiprt1.c +++ b/INCHI-1-SRC/INCHI_BASE/src/ichiprt1.c @@ -3121,8 +3121,7 @@ void set_line_separators( int bINChIOutputOptions, char **pLF, char **pTAB ) int bPlainTabbedOutput = 0 != ( bINChIOutputOptions & INCHI_OUT_TABBED_OUTPUT ) && bPlainText && !bPlainTextCommnts; - const char* tab = bPlainTabbedOutput ? "\t" : "\n"; - strcpy(*pTAB, tab); + *pTAB = bPlainTabbedOutput ? (char*)"\t" : (char*)"\n"; } #else *pTAB = "\n"; diff --git a/INCHI-1-TEST/tests/test_unit/test_ichiprt1.cpp b/INCHI-1-TEST/tests/test_unit/test_ichiprt1.cpp index 97e99207..bc89d735 100644 --- a/INCHI-1-TEST/tests/test_unit/test_ichiprt1.cpp +++ b/INCHI-1-TEST/tests/test_unit/test_ichiprt1.cpp @@ -10,7 +10,7 @@ extern "C" { // #include "../../../INCHI-1-SRC/INCHI_BASE/src/ichitime.h" // #include "../../../INCHI-1-SRC/INCHI_BASE/src/ichicant.h" -// #include "../../../INCHI-1-SRC/INCHI_BASE/src/ichi_io.h" +#include "../../../INCHI-1-SRC/INCHI_BASE/src/ichi_io.h" #include "../../../INCHI-1-SRC/INCHI_BASE/src/ichiprt1.c" } @@ -18,6 +18,17 @@ extern "C" TEST(ichiprt1_testing, test_OutputINCHI_StereoLayer) { + // Prepare minimal dummy arguments + CANON_GLOBALS cg = {0}; + INCHI_IOSTREAM out_file = {0}; + INCHI_IOS_STRING strbuf = {0}; + INCHI_OUT_CTL io = {0}; + char lf[] = "\n"; + char tab[] = "\t"; + + // Initialize string buffer (simulate allocation) + // inchi_strbuf_init(&strbuf, 128, 128); + inchi_strbuf_init(&strbuf, INCHI_STRBUF_INITIAL_SIZE, INCHI_STRBUF_SIZE_INCREMENT); // int OutputINCHI_StereoLayer( CANON_GLOBALS *pCG, // INCHI_IOSTREAM *out_file, @@ -26,5 +37,45 @@ TEST(ichiprt1_testing, test_OutputINCHI_StereoLayer) // char *pLF, // char *pTAB ) + // Call the function under test + int ret = OutputINCHI_StereoLayer(&cg, &out_file, &strbuf, &io, lf, tab); + + // Check that the function returns 0 (success) or expected error code + // (You may need to adjust this depending on the actual implementation) + EXPECT_EQ(ret, 0); + + // Clean up + inchi_strbuf_close(&strbuf); + +} + +TEST(ichiprt1_testing, test_set_line_separators) +{ + + // void set_line_separators( int bINChIOutputOptions, char **pLF, char **pTAB ) + + char *lf = nullptr; + char *tab = nullptr; + + // Plain text comments option + set_line_separators(INCHI_OUT_PLAIN_TEXT_COMMENTS, &lf, &tab); + EXPECT_STREQ(lf, "\n"); + EXPECT_STREQ(tab, "\n"); + + // Tabbed output option + set_line_separators(INCHI_OUT_PLAIN_TEXT | INCHI_OUT_TABBED_OUTPUT, &lf, &tab); + EXPECT_STREQ(lf, "\0"); + EXPECT_STREQ(tab, "\t"); + + // Plain text only + set_line_separators(INCHI_OUT_PLAIN_TEXT, &lf, &tab); + EXPECT_STREQ(lf, "\0"); + EXPECT_STREQ(tab, "\n"); + + // No options + set_line_separators(0, &lf, &tab); + EXPECT_STREQ(lf, "\0"); + EXPECT_STREQ(tab, "\n"); + } From 3cff56f999ef90783974f383db1898bf3606d663 Mon Sep 17 00:00:00 2001 From: Christoph Mueller Date: Mon, 22 Dec 2025 09:50:38 +0000 Subject: [PATCH 03/44] Changed gtest test_suite names --- INCHI-1-TEST/tests/test_unit/test_ichiprt1.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/INCHI-1-TEST/tests/test_unit/test_ichiprt1.cpp b/INCHI-1-TEST/tests/test_unit/test_ichiprt1.cpp index bc89d735..db96f282 100644 --- a/INCHI-1-TEST/tests/test_unit/test_ichiprt1.cpp +++ b/INCHI-1-TEST/tests/test_unit/test_ichiprt1.cpp @@ -15,7 +15,7 @@ extern "C" } -TEST(ichiprt1_testing, test_OutputINCHI_StereoLayer) +TEST(test_ichiprt1, test_OutputINCHI_StereoLayer) { // Prepare minimal dummy arguments @@ -49,7 +49,7 @@ TEST(ichiprt1_testing, test_OutputINCHI_StereoLayer) } -TEST(ichiprt1_testing, test_set_line_separators) +TEST(test_ichiprt1, test_set_line_separators) { // void set_line_separators( int bINChIOutputOptions, char **pLF, char **pTAB ) From bc49dcaa467c4c0d21d254d69394964c6517d9d0 Mon Sep 17 00:00:00 2001 From: Christoph Mueller Date: Mon, 22 Dec 2025 14:32:56 +0000 Subject: [PATCH 04/44] New units for enh stereochemistry --- INCHI-1-SRC/INCHI_BASE/src/ichimake.c | 25 ++-- INCHI-1-SRC/INCHI_BASE/src/ichiprt1.c | 18 +-- .../tests/test_unit/test_ichimake.cpp | 90 ++++++++++++++ .../tests/test_unit/test_ichiprt1.cpp | 110 +++++++++++++++++- .../tests/test_unit/test_ichiprt2.cpp | 68 +++++++++++ .../tests/test_unit/test_ichiprt3.cpp | 52 +++++++++ 6 files changed, 341 insertions(+), 22 deletions(-) create mode 100644 INCHI-1-TEST/tests/test_unit/test_ichimake.cpp create mode 100644 INCHI-1-TEST/tests/test_unit/test_ichiprt2.cpp create mode 100644 INCHI-1-TEST/tests/test_unit/test_ichiprt3.cpp diff --git a/INCHI-1-SRC/INCHI_BASE/src/ichimake.c b/INCHI-1-SRC/INCHI_BASE/src/ichimake.c index e478e55b..37ddab42 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/ichimake.c +++ b/INCHI-1-SRC/INCHI_BASE/src/ichimake.c @@ -608,22 +608,21 @@ int GetSp3RelRacAbs(const INChI* pINChI, INChI_Stereo* Stereo) nRet = SP3_REL; #endif } - else - if (pINChI->nFlags & INCHI_FLAG_RAC_STEREO) - { + else if (pINChI->nFlags & INCHI_FLAG_RAC_STEREO) + { #if ( REL_RAC_STEREO_IGN_1_SC == 1 ) - if (1 < Stereo->nNumberOfStereoCenters) - { - nRet = SP3_REL; - } -#else - nRet = SP3_RAC; -#endif - } - else + if (1 < Stereo->nNumberOfStereoCenters) { - nRet = SP3_ABS; + nRet = SP3_REL; } +#else + nRet = SP3_RAC; +#endif + } + else + { + nRet = SP3_ABS; + } } else #if ( REL_RAC_STEREO_IGN_1_SC == 1 ) diff --git a/INCHI-1-SRC/INCHI_BASE/src/ichiprt1.c b/INCHI-1-SRC/INCHI_BASE/src/ichiprt1.c index 672ba198..23aba0d8 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/ichiprt1.c +++ b/INCHI-1-SRC/INCHI_BASE/src/ichiprt1.c @@ -3359,11 +3359,9 @@ int OutputINCHI_StereoLayer( CANON_GLOBALS *pCG, char *pTAB ) { - { - int i; - i = INChI_SegmentAction( io->sDifSegs[io->nCurINChISegment][DIFS_t_SATOMS] ); /* djb-rwth: ignoring LLVM warning: variable used to store function return value */ - /* djb-rwth: removing redundant code */ - } + // int i; + // i = INChI_SegmentAction( io->sDifSegs[io->nCurINChISegment][DIFS_t_SATOMS] ); /* djb-rwth: ignoring LLVM warning: variable used to store function return value */ + // /* djb-rwth: removing redundant code */ if (INChI_SegmentAction( io->sDifSegs[io->nCurINChISegment][DIFS_b_SBONDS] ) || INChI_SegmentAction( io->sDifSegs[io->nCurINChISegment][DIFS_t_SATOMS] ) || @@ -3432,7 +3430,10 @@ int OutputINCHI_StereoLayer( CANON_GLOBALS *pCG, } else { - if (io->bPlainTextTags == 1) inchi_ios_print_nodisplay( out_file, "/" ); /* sp3 */ + if (io->bPlainTextTags == 1) + { + inchi_ios_print_nodisplay( out_file, "/" ); /* sp3 */ + } } /* bStereoAbsInverted[io->iCurTautMode] */ @@ -3490,7 +3491,10 @@ int OutputINCHI_StereoLayer( CANON_GLOBALS *pCG, } else { - if (io->bPlainTextTags == 1) inchi_ios_print_nodisplay( out_file, "////" ); /* sp3, sp2, abs-inv, stereo.type */ + if (io->bPlainTextTags == 1) + { + inchi_ios_print_nodisplay( out_file, "////" ); /* sp3, sp2, abs-inv, stereo.type */ + } } return 0; diff --git a/INCHI-1-TEST/tests/test_unit/test_ichimake.cpp b/INCHI-1-TEST/tests/test_unit/test_ichimake.cpp new file mode 100644 index 00000000..94a2c69a --- /dev/null +++ b/INCHI-1-TEST/tests/test_unit/test_ichimake.cpp @@ -0,0 +1,90 @@ +#include +#include +#include +#include +#include +#include +#include + +extern "C" +{ +#include "../../../INCHI-1-SRC/INCHI_BASE/src/ichimake.c" +#include "../../../INCHI-1-SRC/INCHI_BASE/src/ichi_io.h" +} + +TEST(test_ichimake, test_GetSp3RelRacAbs_none) +{ + + INChI inchi = {0}; + INChI_Stereo stereo = {0}; + + stereo.nNumberOfStereoCenters = 0; + + // pINChI->bDeleted + // Stereo->nCompInv2Abs + // 0: No inversion (structure unchanged by inversion) + // Positive integer (>0): Indicates inversion changes the structure (normal/absolute stereo) + // Negative integer (<0): Indicates inversion changes the structure (inverted/absolute stereo) + + //int GetSp3RelRacAbs(const INChI* pINChI, INChI_Stereo* Stereo); + + int result = GetSp3RelRacAbs(&inchi, &stereo); + + EXPECT_EQ(result, SP3_NONE); + +} + +TEST(test_ichimake, test_GetSp3RelRacAbs_sp3) +{ + INChI inchi = {0}; + INChI_Stereo stereo = {0}; + + stereo.nNumberOfStereoCenters = 1; + stereo.nCompInv2Abs = 0; + + int result = GetSp3RelRacAbs(&inchi, &stereo); + + EXPECT_EQ(result, SP3_ONLY); +} + +TEST(test_ichimake, test_GetSp3RelRacAbs_rel) +{ + INChI inchi = {0}; + INChI_Stereo stereo = {0}; + + inchi.nFlags = INCHI_FLAG_REL_STEREO; + stereo.nNumberOfStereoCenters = 1; + stereo.nCompInv2Abs = 1; + + int result = GetSp3RelRacAbs(&inchi, &stereo); + + EXPECT_EQ(result, SP3_REL); +} + +TEST(test_ichimake, test_GetSp3RelRacAbs_rac) +{ + INChI inchi = {0}; + INChI_Stereo stereo = {0}; + + inchi.nFlags = INCHI_FLAG_RAC_STEREO; + stereo.nNumberOfStereoCenters = 1; + stereo.nCompInv2Abs = 1; + + int result = GetSp3RelRacAbs(&inchi, &stereo); + + EXPECT_EQ(result, SP3_RAC); +} + +TEST(test_ichimake, test_GetSp3RelRacAbs_abs) +{ + INChI inchi = {0}; + INChI_Stereo stereo = {0}; + + inchi.nFlags = 0x0111; + stereo.nNumberOfStereoCenters = 1; + stereo.nCompInv2Abs = 1; + + int result = GetSp3RelRacAbs(&inchi, &stereo); + + EXPECT_EQ(result, SP3_ABS); +} diff --git a/INCHI-1-TEST/tests/test_unit/test_ichiprt1.cpp b/INCHI-1-TEST/tests/test_unit/test_ichiprt1.cpp index db96f282..866b0e98 100644 --- a/INCHI-1-TEST/tests/test_unit/test_ichiprt1.cpp +++ b/INCHI-1-TEST/tests/test_unit/test_ichiprt1.cpp @@ -8,8 +8,6 @@ extern "C" { -// #include "../../../INCHI-1-SRC/INCHI_BASE/src/ichitime.h" -// #include "../../../INCHI-1-SRC/INCHI_BASE/src/ichicant.h" #include "../../../INCHI-1-SRC/INCHI_BASE/src/ichi_io.h" #include "../../../INCHI-1-SRC/INCHI_BASE/src/ichiprt1.c" } @@ -49,6 +47,114 @@ TEST(test_ichiprt1, test_OutputINCHI_StereoLayer) } +TEST(test_ichiprt1, OutputINCHI_StereoLayer_s1) +{ + // Prepare minimal input structures + CANON_GLOBALS cg = {0}; + INCHI_IOSTREAM out_file = {0}; + INCHI_IOS_STRING strbuf = {0}; + INCHI_OUT_CTL io = {0}; + + // Set up io to trigger the /s (stereo type) segment + io.sDifSegs[0][DIFS_s_STYPE] = DIFV_OUTPUT_FILL_T; // nonzero triggers INChI_SegmentAction + + + io.nCurINChISegment = 0; + io.iCurTautMode = 0; + io.bPlainTextTags = 1; + io.bRelativeStereo[0] = 0; + io.bRacemicStereo[0] = 0; + io.bAlways = 0; + io.nTag = 2; // plain text + io.bTag1 = IL_STER; + io.bTag2 = IL_STER | IL_TYPS; + + // Allocate strbuf + char buf[16] = {0}; + strbuf.pStr = buf; + strbuf.nAllocatedLength = sizeof(buf); + strbuf.nUsedLength = 0; + + // Call function + int ret = OutputINCHI_StereoLayer(&cg, &out_file, &strbuf, &io, (char*)"", (char*)""); + + // The stereo type should be "s1" (plain tag + abs) + EXPECT_EQ(std::string(strbuf.pStr), "/s1"); + EXPECT_EQ(ret, 0); +} + +TEST(test_ichiprt1, OutputINCHI_StereoLayer_s2) +{ + // Prepare minimal input structures + CANON_GLOBALS cg = {0}; + INCHI_IOSTREAM out_file = {0}; + INCHI_IOS_STRING strbuf = {0}; + INCHI_OUT_CTL io = {0}; + + // Set up io to trigger the /s (stereo type) segment + io.sDifSegs[0][DIFS_s_STYPE] = DIFV_OUTPUT_FILL_T; // nonzero triggers INChI_SegmentAction + + + io.nCurINChISegment = 0; + io.iCurTautMode = 0; + io.bPlainTextTags = 1; + io.bRelativeStereo[0] = 1; + io.bRacemicStereo[0] = 0; + io.bAlways = 0; + io.nTag = 2; // plain text + io.bTag1 = IL_STER; + io.bTag2 = IL_STER | IL_TYPS; + + // Allocate strbuf + char buf[16] = {0}; + strbuf.pStr = buf; + strbuf.nAllocatedLength = sizeof(buf); + strbuf.nUsedLength = 0; + + // Call function + int ret = OutputINCHI_StereoLayer(&cg, &out_file, &strbuf, &io, (char*)"", (char*)""); + + // The stereo type should be "s1" (plain tag + abs) + EXPECT_EQ(std::string(strbuf.pStr), "/s2"); + EXPECT_EQ(ret, 0); +} + +TEST(test_ichiprt1, OutputINCHI_StereoLayer_s3) +{ + // Prepare minimal input structures + CANON_GLOBALS cg = {0}; + INCHI_IOSTREAM out_file = {0}; + INCHI_IOS_STRING strbuf = {0}; + INCHI_OUT_CTL io = {0}; + + // Set up io to trigger the /s (stereo type) segment + io.sDifSegs[0][DIFS_s_STYPE] = DIFV_OUTPUT_FILL_T; // nonzero triggers INChI_SegmentAction + + + io.nCurINChISegment = 0; + io.iCurTautMode = 0; + io.bPlainTextTags = 1; + io.bRelativeStereo[0] = 0; + io.bRacemicStereo[0] = 1; + io.bAlways = 0; + io.nTag = 2; // plain text + io.bTag1 = IL_STER; + io.bTag2 = IL_STER | IL_TYPS; + + // Allocate strbuf + char buf[16] = {0}; + strbuf.pStr = buf; + strbuf.nAllocatedLength = sizeof(buf); + strbuf.nUsedLength = 0; + + // Call function + int ret = OutputINCHI_StereoLayer(&cg, &out_file, &strbuf, &io, (char*)"", (char*)""); + + // The stereo type should be "s1" (plain tag + abs) + EXPECT_EQ(std::string(strbuf.pStr), "/s3"); + EXPECT_EQ(ret, 0); +} + TEST(test_ichiprt1, test_set_line_separators) { diff --git a/INCHI-1-TEST/tests/test_unit/test_ichiprt2.cpp b/INCHI-1-TEST/tests/test_unit/test_ichiprt2.cpp new file mode 100644 index 00000000..18be185a --- /dev/null +++ b/INCHI-1-TEST/tests/test_unit/test_ichiprt2.cpp @@ -0,0 +1,68 @@ +#include +#include + +extern "C" +{ +#include "../../../INCHI-1-SRC/INCHI_BASE/src/ichiprt2.c" +#include "../../../INCHI-1-SRC/INCHI_BASE/src/ichi_io.h" +} + +TEST(test_ichiprt2, MakeStereoString_outputs_expected_sp3_string) +{ + // Prepare input arrays for 8 stereo centers + AT_NUMB at1[8] = {3,4,5,6,7,8,9,10}; + AT_NUMB at2[8] = {0}; // Not used in this test, can be NULL + // S_CHAR parity[8] = {1,-1,1,1,-1,1,1,-1}; // +,-,+,+,-,+,+,- + // S_CHAR parity[8] = {-1, -1, 1, 1, -1, 1, 1, -1}; + S_CHAR parity[8] = {1,1,2,2,1,2,2,1}; + + // Prepare strbuf (empty) + INCHI_IOS_STRING strbuf = {0}; + char buf[128] = {0}; + strbuf.pStr = buf; + strbuf.nAllocatedLength = sizeof(buf); + strbuf.nUsedLength = 0; + + // Call MakeStereoString + int bOverflow = 0; + int ret = MakeStereoString(at1, NULL, parity, 0, 8, &strbuf, 0, &bOverflow); + + // Check that strbuf contains the expected substring + EXPECT_EQ(strbuf.nUsedLength, strlen(strbuf.pStr)); + EXPECT_NE(std::string(strbuf.pStr).find("3-,4-,5+,6+,7-,8+,9+,10-"), std::string::npos); + EXPECT_EQ(strbuf.pStr[0], '3'); // Should start with 3- + EXPECT_EQ(bOverflow, 0); +} + +TEST(test_ichiprt2, MakeMult_mult_gt_1_appends_number_and_delim) +{ + INCHI_IOS_STRING strbuf = {0}; + char buf[32] = {0}; + strbuf.pStr = buf; + strbuf.nAllocatedLength = sizeof(buf); + strbuf.nUsedLength = 0; + + int bOverflow = 0; + int ret = MakeMult(5, "-", &strbuf, 0, &bOverflow); + + EXPECT_EQ(std::string(strbuf.pStr), "5-"); + EXPECT_EQ(ret, 2); // "3-".length() == 2, but MakeMult returns n (number of chars written) + EXPECT_EQ(bOverflow, 0); +} + +TEST(test_ichiprt2, MakeMult_mult_eq_1_does_nothing) +{ + INCHI_IOS_STRING strbuf = {0}; + char buf[32] = {0}; + strcpy(buf, "start"); + strbuf.pStr = buf; + strbuf.nAllocatedLength = sizeof(buf); + strbuf.nUsedLength = strlen(buf); + + int bOverflow = 0; + int ret = MakeMult(1, "-", &strbuf, 0, &bOverflow); + + EXPECT_EQ(std::string(strbuf.pStr), "start"); + EXPECT_EQ(ret, 0); + EXPECT_EQ(bOverflow, 0); +} diff --git a/INCHI-1-TEST/tests/test_unit/test_ichiprt3.cpp b/INCHI-1-TEST/tests/test_unit/test_ichiprt3.cpp new file mode 100644 index 00000000..4b1a8afc --- /dev/null +++ b/INCHI-1-TEST/tests/test_unit/test_ichiprt3.cpp @@ -0,0 +1,52 @@ +#include +#include + +extern "C" +{ +#include "../../../INCHI-1-SRC/INCHI_BASE/src/ichiprt3.c" +#include "../../../INCHI-1-SRC/INCHI_BASE/src/ichi_io.h" +} + +TEST(test_ichiprt3, test_str_Sp3_outputs_expected_sp3_string) +{ + // Prepare INChI_Stereo with 8 stereo centers and their parities + INChI_Stereo stereo = {0}; + + stereo.nNumberOfStereoCenters = 8; + stereo.t_parity = (S_CHAR*)inchi_calloc((long long)stereo.nNumberOfStereoCenters, sizeof(S_CHAR)); + stereo.nNumber = (AT_NUMB*)inchi_calloc((long long)stereo.nNumberOfStereoCenters, sizeof(AT_NUMB)); + + int numbers[8] = {3,4,5,6,7,8,9,10}; + S_CHAR parities[8] = {1,1,2,2,1,2,2,1}; + + + for (int i = 0; i < 8; i++) { + stereo.nNumber[i] = numbers[i]; + stereo.t_parity[i] = parities[i]; + } + + // Prepare INChI and INCHI_SORT + INChI inchi = {0}; + inchi.nNumberOfAtoms = 10; + inchi.Stereo = &stereo; + + INCHI_SORT sort = {0}; + sort.pINChI[0] = &inchi; + + // Prepare strbuf (empty) + INCHI_IOS_STRING strbuf = {0}; + char buf[128] = {0}; + strbuf.pStr = buf; + strbuf.nAllocatedLength = sizeof(buf); + strbuf.nUsedLength = 0; + + // Call str_Sp3 + int bOverflow = 0; + int ret = str_Sp3(&sort, NULL, &strbuf, &bOverflow, 0, 0, 1, 0, 0, 0, 0); + + // Check that strbuf contains the expected substring + EXPECT_EQ(strbuf.nUsedLength, strlen(strbuf.pStr)); + EXPECT_NE(std::string(strbuf.pStr).find("3-,4-,5+,6+,7-,8+,9+,10-"), std::string::npos); + EXPECT_EQ(strbuf.pStr[0], '3'); // Should start with 3- + EXPECT_EQ(bOverflow, 0); +} From 0cf17b972fac9244099be9b952105e80ae56de3f Mon Sep 17 00:00:00 2001 From: Christoph Mueller Date: Tue, 23 Dec 2025 15:31:33 +0000 Subject: [PATCH 05/44] Added/corrected unit tests for enh stereo --- .../tests/test_unit/test_ichimain.cpp | 1 - .../tests/test_unit/test_ichiprt1.cpp | 121 +++++++++++++++--- .../tests/test_unit/test_ichiprt2.cpp | 30 ++--- .../tests/test_unit/test_ichiprt3.cpp | 41 +++--- 4 files changed, 138 insertions(+), 55 deletions(-) diff --git a/INCHI-1-TEST/tests/test_unit/test_ichimain.cpp b/INCHI-1-TEST/tests/test_unit/test_ichimain.cpp index b6f266bb..ea2c2618 100644 --- a/INCHI-1-TEST/tests/test_unit/test_ichimain.cpp +++ b/INCHI-1-TEST/tests/test_unit/test_ichimain.cpp @@ -191,7 +191,6 @@ TEST(test_ichimain, test_CalcAndPrintINCHIAndINCHIKEY) { PINChI_Aux2* pINChI_Aux[INCHI_NUM] = {}; ORIG_ATOM_DATA prep_inp_data = {}; INCHI_IOS_STRING *strbuf = new INCHI_IOS_STRING; - memset(strbuf, 0, sizeof(*strbuf)); inchi_strbuf_init(strbuf, INCHI_STRBUF_INITIAL_SIZE, INCHI_STRBUF_SIZE_INCREMENT); unsigned long pulTotalProcessingTime = 0; diff --git a/INCHI-1-TEST/tests/test_unit/test_ichiprt1.cpp b/INCHI-1-TEST/tests/test_unit/test_ichiprt1.cpp index 866b0e98..b11d45bc 100644 --- a/INCHI-1-TEST/tests/test_unit/test_ichiprt1.cpp +++ b/INCHI-1-TEST/tests/test_unit/test_ichiprt1.cpp @@ -10,6 +10,9 @@ extern "C" { #include "../../../INCHI-1-SRC/INCHI_BASE/src/ichi_io.h" #include "../../../INCHI-1-SRC/INCHI_BASE/src/ichiprt1.c" +#include "../../../INCHI-1-SRC/INCHI_BASE/src/strutil.c" +#include "../../../INCHI-1-SRC/INCHI_BASE/src/ichimake.h" +#include "../../../INCHI-1-SRC/INCHI_BASE/src/ichicano.h" } @@ -27,6 +30,7 @@ TEST(test_ichiprt1, test_OutputINCHI_StereoLayer) // Initialize string buffer (simulate allocation) // inchi_strbuf_init(&strbuf, 128, 128); inchi_strbuf_init(&strbuf, INCHI_STRBUF_INITIAL_SIZE, INCHI_STRBUF_SIZE_INCREMENT); + inchi_ios_init(&out_file, INCHI_IOS_TYPE_STRING, nullptr); // int OutputINCHI_StereoLayer( CANON_GLOBALS *pCG, // INCHI_IOSTREAM *out_file, @@ -47,7 +51,7 @@ TEST(test_ichiprt1, test_OutputINCHI_StereoLayer) } -TEST(test_ichiprt1, OutputINCHI_StereoLayer_s1) +TEST(test_ichiprt1, test_OutputINCHI_StereoLayer_s1) { // Prepare minimal input structures CANON_GLOBALS cg = {0}; @@ -58,7 +62,6 @@ TEST(test_ichiprt1, OutputINCHI_StereoLayer_s1) // Set up io to trigger the /s (stereo type) segment io.sDifSegs[0][DIFS_s_STYPE] = DIFV_OUTPUT_FILL_T; // nonzero triggers INChI_SegmentAction - io.nCurINChISegment = 0; io.iCurTautMode = 0; io.bPlainTextTags = 1; @@ -69,11 +72,8 @@ TEST(test_ichiprt1, OutputINCHI_StereoLayer_s1) io.bTag1 = IL_STER; io.bTag2 = IL_STER | IL_TYPS; - // Allocate strbuf - char buf[16] = {0}; - strbuf.pStr = buf; - strbuf.nAllocatedLength = sizeof(buf); - strbuf.nUsedLength = 0; + inchi_strbuf_init(&strbuf, INCHI_STRBUF_INITIAL_SIZE, INCHI_STRBUF_SIZE_INCREMENT); + inchi_ios_init(&out_file, INCHI_IOS_TYPE_STRING, nullptr); // Call function int ret = OutputINCHI_StereoLayer(&cg, &out_file, &strbuf, &io, (char*)"", (char*)""); @@ -81,9 +81,12 @@ TEST(test_ichiprt1, OutputINCHI_StereoLayer_s1) // The stereo type should be "s1" (plain tag + abs) EXPECT_EQ(std::string(strbuf.pStr), "/s1"); EXPECT_EQ(ret, 0); + + inchi_strbuf_close(&strbuf); + inchi_ios_close(&out_file); } -TEST(test_ichiprt1, OutputINCHI_StereoLayer_s2) +TEST(test_ichiprt1, test_OutputINCHI_StereoLayer_s2) { // Prepare minimal input structures CANON_GLOBALS cg = {0}; @@ -105,11 +108,8 @@ TEST(test_ichiprt1, OutputINCHI_StereoLayer_s2) io.bTag1 = IL_STER; io.bTag2 = IL_STER | IL_TYPS; - // Allocate strbuf - char buf[16] = {0}; - strbuf.pStr = buf; - strbuf.nAllocatedLength = sizeof(buf); - strbuf.nUsedLength = 0; + inchi_strbuf_init(&strbuf, INCHI_STRBUF_INITIAL_SIZE, INCHI_STRBUF_SIZE_INCREMENT); + inchi_ios_init(&out_file, INCHI_IOS_TYPE_STRING, nullptr); // Call function int ret = OutputINCHI_StereoLayer(&cg, &out_file, &strbuf, &io, (char*)"", (char*)""); @@ -117,9 +117,12 @@ TEST(test_ichiprt1, OutputINCHI_StereoLayer_s2) // The stereo type should be "s1" (plain tag + abs) EXPECT_EQ(std::string(strbuf.pStr), "/s2"); EXPECT_EQ(ret, 0); + + inchi_strbuf_close(&strbuf); + inchi_ios_close(&out_file); } -TEST(test_ichiprt1, OutputINCHI_StereoLayer_s3) +TEST(test_ichiprt1, test_OutputINCHI_StereoLayer_s3) { // Prepare minimal input structures CANON_GLOBALS cg = {0}; @@ -141,11 +144,8 @@ TEST(test_ichiprt1, OutputINCHI_StereoLayer_s3) io.bTag1 = IL_STER; io.bTag2 = IL_STER | IL_TYPS; - // Allocate strbuf - char buf[16] = {0}; - strbuf.pStr = buf; - strbuf.nAllocatedLength = sizeof(buf); - strbuf.nUsedLength = 0; + inchi_strbuf_init(&strbuf, INCHI_STRBUF_INITIAL_SIZE, INCHI_STRBUF_SIZE_INCREMENT); + inchi_ios_init(&out_file, INCHI_IOS_TYPE_STRING, nullptr); // Call function int ret = OutputINCHI_StereoLayer(&cg, &out_file, &strbuf, &io, (char*)"", (char*)""); @@ -153,6 +153,89 @@ TEST(test_ichiprt1, OutputINCHI_StereoLayer_s3) // The stereo type should be "s1" (plain tag + abs) EXPECT_EQ(std::string(strbuf.pStr), "/s3"); EXPECT_EQ(ret, 0); + + inchi_strbuf_close(&strbuf); + inchi_ios_close(&out_file); +} + +TEST(test_ichiprt1, test_OutputINCHI_StereoLayer_enh_stereo_1) +{ + // --- Setup CANON_GLOBALS and INChI/INChI_Stereo for enhanced stereo --- + CANON_GLOBALS cg = {0}; + INCHI_IOSTREAM out_file = {0}; + INCHI_IOS_STRING strbuf = {0}; + INCHI_OUT_CTL io = {0}; + + int num_at = 8; + int num_bonds = 0; + static AT_NUMB numbers[8] = {3,4,5,6,7,8,9,10}; + static S_CHAR parities[8] = {1,1,2,2,1,2,2,1};// -,-,+,+,-,+,+,- + + static S_CHAR m_values[1] = {0}; // /m0 + static S_CHAR s_values[1] = {1}; // /s1 + + // static INChI_Stereo stereo = {0}; + + static INChI_Stereo *stereo = Alloc_INChI_Stereo( num_at, num_bonds ); + + stereo->nNumberOfStereoCenters = 8; + + for (int i = 0; i < num_at; i++) { + stereo->nNumber[i] = numbers[i]; + stereo->t_parity[i] = parities[i]; + } + + stereo->nCompInv2Abs = 1; + stereo->nNumberOfStereoBonds = 0; + stereo->b_parity = NULL; + + // INChI *inchi = new INChI; + // inchi->nNumberOfAtoms = num_at; + // inchi->Stereo = stereo; + + int found_num_bonds = 0; + int found_num_isotopic = 0; + inp_ATOM *at = CreateInpAtom(num_at); + static INChI *inchi = Alloc_INChI(at, num_at, &found_num_bonds, &found_num_isotopic, 0); + + inchi->nNumberOfAtoms = num_at; + inchi->Stereo = stereo; + + static INCHI_SORT sort = {0}; + sort.pINChI[0] = inchi; + + io.pINChISort = &sort; + + // --- Setup io to trigger stereo output --- + io.num_components = 1; + io.bRelativeStereo[0] = 0; + io.bRacemicStereo[0] = 1; + + io.sDifSegs[0][DIFS_t_SATOMS] = DIFV_OUTPUT_FILL_T; + io.sDifSegs[0][DIFS_m_SP3INV] = DIFV_OUTPUT_FILL_T; + io.sDifSegs[0][DIFS_s_STYPE] = DIFV_OUTPUT_FILL_T; + io.nCurINChISegment = 0; + io.iCurTautMode = 0; + io.bPlainTextTags = 1; + io.nTag = 2; + io.bTag1 = IL_STER; + io.bTag2 = IL_STER | IL_TYPS; + io.bOutType = OUT_TN; + + inchi_strbuf_init(&strbuf, INCHI_STRBUF_INITIAL_SIZE, INCHI_STRBUF_SIZE_INCREMENT); + inchi_ios_init(&out_file, INCHI_IOS_TYPE_STRING, nullptr); + + int ret = OutputINCHI_StereoLayer(&cg, &out_file, &strbuf, &io, (char*)"", (char*)""); + + EXPECT_EQ(std::string(out_file.s.pStr), "//t3-,4-,5+,6+,7-,8+,9+,10-/m0/s3/"); + EXPECT_EQ(ret, 0); + + inchi_strbuf_close(&strbuf); + inchi_ios_close(&out_file); + + FreeInpAtom(&at); + // Free_INChI(&inchi); + Free_INChI_Stereo(stereo); } TEST(test_ichiprt1, test_set_line_separators) diff --git a/INCHI-1-TEST/tests/test_unit/test_ichiprt2.cpp b/INCHI-1-TEST/tests/test_unit/test_ichiprt2.cpp index 18be185a..0800ef1b 100644 --- a/INCHI-1-TEST/tests/test_unit/test_ichiprt2.cpp +++ b/INCHI-1-TEST/tests/test_unit/test_ichiprt2.cpp @@ -11,36 +11,31 @@ TEST(test_ichiprt2, MakeStereoString_outputs_expected_sp3_string) { // Prepare input arrays for 8 stereo centers AT_NUMB at1[8] = {3,4,5,6,7,8,9,10}; - AT_NUMB at2[8] = {0}; // Not used in this test, can be NULL - // S_CHAR parity[8] = {1,-1,1,1,-1,1,1,-1}; // +,-,+,+,-,+,+,- - // S_CHAR parity[8] = {-1, -1, 1, 1, -1, 1, 1, -1}; S_CHAR parity[8] = {1,1,2,2,1,2,2,1}; // Prepare strbuf (empty) INCHI_IOS_STRING strbuf = {0}; - char buf[128] = {0}; - strbuf.pStr = buf; - strbuf.nAllocatedLength = sizeof(buf); - strbuf.nUsedLength = 0; + + inchi_strbuf_init(&strbuf, INCHI_STRBUF_INITIAL_SIZE, INCHI_STRBUF_SIZE_INCREMENT); // Call MakeStereoString int bOverflow = 0; - int ret = MakeStereoString(at1, NULL, parity, 0, 8, &strbuf, 0, &bOverflow); + int ret = MakeStereoString(at1, nullptr, parity, 0, 8, &strbuf, 0, &bOverflow); // Check that strbuf contains the expected substring EXPECT_EQ(strbuf.nUsedLength, strlen(strbuf.pStr)); EXPECT_NE(std::string(strbuf.pStr).find("3-,4-,5+,6+,7-,8+,9+,10-"), std::string::npos); EXPECT_EQ(strbuf.pStr[0], '3'); // Should start with 3- EXPECT_EQ(bOverflow, 0); + EXPECT_EQ(ret, 24); + + inchi_strbuf_close(&strbuf); } TEST(test_ichiprt2, MakeMult_mult_gt_1_appends_number_and_delim) { INCHI_IOS_STRING strbuf = {0}; - char buf[32] = {0}; - strbuf.pStr = buf; - strbuf.nAllocatedLength = sizeof(buf); - strbuf.nUsedLength = 0; + inchi_strbuf_init(&strbuf, INCHI_STRBUF_INITIAL_SIZE, INCHI_STRBUF_SIZE_INCREMENT); int bOverflow = 0; int ret = MakeMult(5, "-", &strbuf, 0, &bOverflow); @@ -48,16 +43,15 @@ TEST(test_ichiprt2, MakeMult_mult_gt_1_appends_number_and_delim) EXPECT_EQ(std::string(strbuf.pStr), "5-"); EXPECT_EQ(ret, 2); // "3-".length() == 2, but MakeMult returns n (number of chars written) EXPECT_EQ(bOverflow, 0); + + inchi_strbuf_close(&strbuf); } TEST(test_ichiprt2, MakeMult_mult_eq_1_does_nothing) { INCHI_IOS_STRING strbuf = {0}; - char buf[32] = {0}; - strcpy(buf, "start"); - strbuf.pStr = buf; - strbuf.nAllocatedLength = sizeof(buf); - strbuf.nUsedLength = strlen(buf); + inchi_strbuf_init(&strbuf, INCHI_STRBUF_INITIAL_SIZE, INCHI_STRBUF_SIZE_INCREMENT); + inchi_strbuf_printf(&strbuf, "start"); int bOverflow = 0; int ret = MakeMult(1, "-", &strbuf, 0, &bOverflow); @@ -65,4 +59,6 @@ TEST(test_ichiprt2, MakeMult_mult_eq_1_does_nothing) EXPECT_EQ(std::string(strbuf.pStr), "start"); EXPECT_EQ(ret, 0); EXPECT_EQ(bOverflow, 0); + + inchi_strbuf_close(&strbuf); } diff --git a/INCHI-1-TEST/tests/test_unit/test_ichiprt3.cpp b/INCHI-1-TEST/tests/test_unit/test_ichiprt3.cpp index 4b1a8afc..2ea24423 100644 --- a/INCHI-1-TEST/tests/test_unit/test_ichiprt3.cpp +++ b/INCHI-1-TEST/tests/test_unit/test_ichiprt3.cpp @@ -4,41 +4,39 @@ extern "C" { #include "../../../INCHI-1-SRC/INCHI_BASE/src/ichiprt3.c" +#include "../../../INCHI-1-SRC/INCHI_BASE/src/strutil.c" #include "../../../INCHI-1-SRC/INCHI_BASE/src/ichi_io.h" } TEST(test_ichiprt3, test_str_Sp3_outputs_expected_sp3_string) { - // Prepare INChI_Stereo with 8 stereo centers and their parities - INChI_Stereo stereo = {0}; - - stereo.nNumberOfStereoCenters = 8; - stereo.t_parity = (S_CHAR*)inchi_calloc((long long)stereo.nNumberOfStereoCenters, sizeof(S_CHAR)); - stereo.nNumber = (AT_NUMB*)inchi_calloc((long long)stereo.nNumberOfStereoCenters, sizeof(AT_NUMB)); - int numbers[8] = {3,4,5,6,7,8,9,10}; S_CHAR parities[8] = {1,1,2,2,1,2,2,1}; - - - for (int i = 0; i < 8; i++) { - stereo.nNumber[i] = numbers[i]; - stereo.t_parity[i] = parities[i]; + int num_at = 8; + static INChI_Stereo *stereo = Alloc_INChI_Stereo( num_at, 0 ); + stereo->nNumberOfStereoCenters = num_at; + + // INChI_Stereo stereo = {0}; + // stereo.nNumberOfStereoCenters = 8; + // stereo.t_parity = (S_CHAR*)inchi_calloc((long long)stereo.nNumberOfStereoCenters, sizeof(S_CHAR)); + // stereo.nNumber = (AT_NUMB*)inchi_calloc((long long)stereo.nNumberOfStereoCenters, sizeof(AT_NUMB)); + + for (int i = 0; i < num_at; i++) { + stereo->nNumber[i] = numbers[i]; + stereo->t_parity[i] = parities[i]; } // Prepare INChI and INCHI_SORT INChI inchi = {0}; inchi.nNumberOfAtoms = 10; - inchi.Stereo = &stereo; + inchi.Stereo = stereo; INCHI_SORT sort = {0}; sort.pINChI[0] = &inchi; // Prepare strbuf (empty) INCHI_IOS_STRING strbuf = {0}; - char buf[128] = {0}; - strbuf.pStr = buf; - strbuf.nAllocatedLength = sizeof(buf); - strbuf.nUsedLength = 0; + inchi_strbuf_init(&strbuf, INCHI_STRBUF_INITIAL_SIZE, INCHI_STRBUF_SIZE_INCREMENT); // Call str_Sp3 int bOverflow = 0; @@ -46,7 +44,14 @@ TEST(test_ichiprt3, test_str_Sp3_outputs_expected_sp3_string) // Check that strbuf contains the expected substring EXPECT_EQ(strbuf.nUsedLength, strlen(strbuf.pStr)); - EXPECT_NE(std::string(strbuf.pStr).find("3-,4-,5+,6+,7-,8+,9+,10-"), std::string::npos); + // EXPECT_NE(std::string(strbuf.pStr).find("3-,4-,5+,6+,7-,8+,9+,10-"), std::string::npos); + EXPECT_EQ(std::string(strbuf.pStr), "3-,4-,5+,6+,7-,8+,9+,10-"); EXPECT_EQ(strbuf.pStr[0], '3'); // Should start with 3- EXPECT_EQ(bOverflow, 0); + + EXPECT_EQ(ret, 24); + + inchi_strbuf_close(&strbuf); + + Free_INChI_Stereo(stereo); } From e0b38cb75fe49084685631d7f462024ff92bde62 Mon Sep 17 00:00:00 2001 From: Christoph Mueller Date: Tue, 30 Dec 2025 12:57:14 +0000 Subject: [PATCH 06/44] Added unit tests for stereo-layer tags --- INCHI-1-SRC/INCHI_BASE/src/ichiprt1.c | 2 +- .../tests/test_unit/test_ichiprt1.cpp | 147 +++++++++++++++--- .../tests/test_unit/test_ichiprt2.cpp | 4 - .../tests/test_unit/test_ichiprt3.cpp | 4 +- 4 files changed, 124 insertions(+), 33 deletions(-) diff --git a/INCHI-1-SRC/INCHI_BASE/src/ichiprt1.c b/INCHI-1-SRC/INCHI_BASE/src/ichiprt1.c index 23aba0d8..839e8ed5 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/ichiprt1.c +++ b/INCHI-1-SRC/INCHI_BASE/src/ichiprt1.c @@ -3493,7 +3493,7 @@ int OutputINCHI_StereoLayer( CANON_GLOBALS *pCG, { if (io->bPlainTextTags == 1) { - inchi_ios_print_nodisplay( out_file, "////" ); /* sp3, sp2, abs-inv, stereo.type */ + inchi_ios_print_nodisplay( out_file, "////" ); /* sp2, sp3, abs-inv, stereo.type */ } } diff --git a/INCHI-1-TEST/tests/test_unit/test_ichiprt1.cpp b/INCHI-1-TEST/tests/test_unit/test_ichiprt1.cpp index b11d45bc..df6348f2 100644 --- a/INCHI-1-TEST/tests/test_unit/test_ichiprt1.cpp +++ b/INCHI-1-TEST/tests/test_unit/test_ichiprt1.cpp @@ -19,7 +19,6 @@ extern "C" TEST(test_ichiprt1, test_OutputINCHI_StereoLayer) { - // Prepare minimal dummy arguments CANON_GLOBALS cg = {0}; INCHI_IOSTREAM out_file = {0}; INCHI_IOS_STRING strbuf = {0}; @@ -27,8 +26,6 @@ TEST(test_ichiprt1, test_OutputINCHI_StereoLayer) char lf[] = "\n"; char tab[] = "\t"; - // Initialize string buffer (simulate allocation) - // inchi_strbuf_init(&strbuf, 128, 128); inchi_strbuf_init(&strbuf, INCHI_STRBUF_INITIAL_SIZE, INCHI_STRBUF_SIZE_INCREMENT); inchi_ios_init(&out_file, INCHI_IOS_TYPE_STRING, nullptr); @@ -39,11 +36,8 @@ TEST(test_ichiprt1, test_OutputINCHI_StereoLayer) // char *pLF, // char *pTAB ) - // Call the function under test int ret = OutputINCHI_StereoLayer(&cg, &out_file, &strbuf, &io, lf, tab); - // Check that the function returns 0 (success) or expected error code - // (You may need to adjust this depending on the actual implementation) EXPECT_EQ(ret, 0); // Clean up @@ -53,14 +47,12 @@ TEST(test_ichiprt1, test_OutputINCHI_StereoLayer) TEST(test_ichiprt1, test_OutputINCHI_StereoLayer_s1) { - // Prepare minimal input structures CANON_GLOBALS cg = {0}; INCHI_IOSTREAM out_file = {0}; INCHI_IOS_STRING strbuf = {0}; INCHI_OUT_CTL io = {0}; - // Set up io to trigger the /s (stereo type) segment - io.sDifSegs[0][DIFS_s_STYPE] = DIFV_OUTPUT_FILL_T; // nonzero triggers INChI_SegmentAction + io.sDifSegs[0][DIFS_s_STYPE] = DIFV_OUTPUT_FILL_T; io.nCurINChISegment = 0; io.iCurTautMode = 0; @@ -75,10 +67,8 @@ TEST(test_ichiprt1, test_OutputINCHI_StereoLayer_s1) inchi_strbuf_init(&strbuf, INCHI_STRBUF_INITIAL_SIZE, INCHI_STRBUF_SIZE_INCREMENT); inchi_ios_init(&out_file, INCHI_IOS_TYPE_STRING, nullptr); - // Call function int ret = OutputINCHI_StereoLayer(&cg, &out_file, &strbuf, &io, (char*)"", (char*)""); - // The stereo type should be "s1" (plain tag + abs) EXPECT_EQ(std::string(strbuf.pStr), "/s1"); EXPECT_EQ(ret, 0); @@ -88,15 +78,12 @@ TEST(test_ichiprt1, test_OutputINCHI_StereoLayer_s1) TEST(test_ichiprt1, test_OutputINCHI_StereoLayer_s2) { - // Prepare minimal input structures CANON_GLOBALS cg = {0}; INCHI_IOSTREAM out_file = {0}; INCHI_IOS_STRING strbuf = {0}; INCHI_OUT_CTL io = {0}; - // Set up io to trigger the /s (stereo type) segment - io.sDifSegs[0][DIFS_s_STYPE] = DIFV_OUTPUT_FILL_T; // nonzero triggers INChI_SegmentAction - + io.sDifSegs[0][DIFS_s_STYPE] = DIFV_OUTPUT_FILL_T; io.nCurINChISegment = 0; io.iCurTautMode = 0; @@ -111,10 +98,8 @@ TEST(test_ichiprt1, test_OutputINCHI_StereoLayer_s2) inchi_strbuf_init(&strbuf, INCHI_STRBUF_INITIAL_SIZE, INCHI_STRBUF_SIZE_INCREMENT); inchi_ios_init(&out_file, INCHI_IOS_TYPE_STRING, nullptr); - // Call function int ret = OutputINCHI_StereoLayer(&cg, &out_file, &strbuf, &io, (char*)"", (char*)""); - // The stereo type should be "s1" (plain tag + abs) EXPECT_EQ(std::string(strbuf.pStr), "/s2"); EXPECT_EQ(ret, 0); @@ -124,15 +109,13 @@ TEST(test_ichiprt1, test_OutputINCHI_StereoLayer_s2) TEST(test_ichiprt1, test_OutputINCHI_StereoLayer_s3) { - // Prepare minimal input structures CANON_GLOBALS cg = {0}; INCHI_IOSTREAM out_file = {0}; INCHI_IOS_STRING strbuf = {0}; INCHI_OUT_CTL io = {0}; - // Set up io to trigger the /s (stereo type) segment - io.sDifSegs[0][DIFS_s_STYPE] = DIFV_OUTPUT_FILL_T; // nonzero triggers INChI_SegmentAction + io.sDifSegs[0][DIFS_s_STYPE] = DIFV_OUTPUT_FILL_T; io.nCurINChISegment = 0; io.iCurTautMode = 0; @@ -147,10 +130,8 @@ TEST(test_ichiprt1, test_OutputINCHI_StereoLayer_s3) inchi_strbuf_init(&strbuf, INCHI_STRBUF_INITIAL_SIZE, INCHI_STRBUF_SIZE_INCREMENT); inchi_ios_init(&out_file, INCHI_IOS_TYPE_STRING, nullptr); - // Call function int ret = OutputINCHI_StereoLayer(&cg, &out_file, &strbuf, &io, (char*)"", (char*)""); - // The stereo type should be "s1" (plain tag + abs) EXPECT_EQ(std::string(strbuf.pStr), "/s3"); EXPECT_EQ(ret, 0); @@ -160,7 +141,6 @@ TEST(test_ichiprt1, test_OutputINCHI_StereoLayer_s3) TEST(test_ichiprt1, test_OutputINCHI_StereoLayer_enh_stereo_1) { - // --- Setup CANON_GLOBALS and INChI/INChI_Stereo for enhanced stereo --- CANON_GLOBALS cg = {0}; INCHI_IOSTREAM out_file = {0}; INCHI_IOS_STRING strbuf = {0}; @@ -206,11 +186,11 @@ TEST(test_ichiprt1, test_OutputINCHI_StereoLayer_enh_stereo_1) io.pINChISort = &sort; - // --- Setup io to trigger stereo output --- io.num_components = 1; io.bRelativeStereo[0] = 0; io.bRacemicStereo[0] = 1; + io.sDifSegs[0][DIFS_b_SBONDS] = DIFV_OUTPUT_FILL_T; io.sDifSegs[0][DIFS_t_SATOMS] = DIFV_OUTPUT_FILL_T; io.sDifSegs[0][DIFS_m_SP3INV] = DIFV_OUTPUT_FILL_T; io.sDifSegs[0][DIFS_s_STYPE] = DIFV_OUTPUT_FILL_T; @@ -227,7 +207,7 @@ TEST(test_ichiprt1, test_OutputINCHI_StereoLayer_enh_stereo_1) int ret = OutputINCHI_StereoLayer(&cg, &out_file, &strbuf, &io, (char*)"", (char*)""); - EXPECT_EQ(std::string(out_file.s.pStr), "//t3-,4-,5+,6+,7-,8+,9+,10-/m0/s3/"); + EXPECT_EQ(std::string(out_file.s.pStr), "/t3-,4-,5+,6+,7-,8+,9+,10-/m0/s3/"); EXPECT_EQ(ret, 0); inchi_strbuf_close(&strbuf); @@ -268,3 +248,120 @@ TEST(test_ichiprt1, test_set_line_separators) } + +TEST(test_ichiprt1, test_szGetTag_basic) +{ + + // nTag = 1: XML label (szXmlLabel) + // nTag = 2: Plain text label (szPlainLabel) + // nTag = 3: Plain label with comments (sometimes) + + // bTag: A bitmask indicating which tag (or tags) to look up in the INCHI_TAG array. + + char szTag[64] = {0}; + int bAlways = -1; + + EXPECT_STREQ(szGetTag(IdentLbl, 0, 0, szTag, &bAlways, 0), "???"); //fixed-H + EXPECT_STREQ(szTag, "???"); + +} + +TEST(test_ichiprt1, test_szGetTag_returns_unknown_for_invalid_index) +{ + char szTag[64] = {0}; + int bAlways = -1; + // nTag = 0, bTag = 0, should return "???" + EXPECT_STREQ(szGetTag(IdentLbl, 0, 0, szTag, &bAlways, 0), "???"); + EXPECT_STREQ(szTag, "???"); +} + +TEST(test_ichiprt1, test_szGetTag_stereo_1) +{ + char szTag[64] = {0}; + int bAlways = -1; + // nTag = 2 (plain text), bTag = IL_STER (stereo) + EXPECT_STREQ(szGetTag(IdentLbl, 2, IL_STER, szTag, &bAlways, 0), "/"); + EXPECT_STREQ(szTag, "/"); +} + +TEST(test_ichiprt1, test_szGetTag_stereo_2) +{ + char szTag[64] = {0}; + int bAlways = -1; + // nTag = 2 (plain text), bTag = IL_STER (stereo) + EXPECT_STREQ(szGetTag(IdentLbl, 1, IL_STER, szTag, &bAlways, 0), "stereo"); + EXPECT_STREQ(szTag, "stereo"); +} + +TEST(test_ichiprt1, test_szGetTag_stereo_3) +{ + char szTag[64] = {0}; + int bAlways = -1; + // nTag = 2 (plain text), bTag = IL_STER (stereo) + EXPECT_STREQ(szGetTag(IdentLbl, 2, IL_SP3S, szTag, &bAlways, 0), "/t"); + EXPECT_STREQ(szTag, "/t"); +} + +TEST(test_ichiprt1, test_szGetTag_stereo_4) +{ + char szTag[64] = {0}; + int bAlways = -1; + // nTag = 2 (plain text), bTag = IL_STER (stereo) + EXPECT_STREQ(szGetTag(IdentLbl, 2, IL_INVS, szTag, &bAlways, 0), "/m"); + EXPECT_STREQ(szTag, "/m"); +} + +TEST(test_ichiprt1, test_szGetTag_stereo_5) +{ + char szTag[64] = {0}; + int bAlways = -1; + // nTag = 2 (plain text), bTag = IL_STER (stereo) + EXPECT_STREQ(szGetTag(IdentLbl, 2, IL_TYPS, szTag, &bAlways, 0), "/s"); + EXPECT_STREQ(szTag, "/s"); +} + +TEST(test_ichiprt1, test_szGetTag_stereo_6) +{ + char szTag[64] = {0}; + int bAlways = -1; + // nTag = 2 (plain text), bTag = IL_STER (stereo) + EXPECT_STREQ(szGetTag(IdentLbl, 1, IL_TYPS, szTag, &bAlways, 0), "type"); + EXPECT_STREQ(szTag, "type"); +} + +TEST(test_ichiprt1, test_szGetTag_hfixed) +{ + char szTag[64] = {0}; + int bAlways = -1; + // nTag = 2 (plain text), bTag = IL_STER (stereo) + EXPECT_STREQ(szGetTag(IdentLbl, 1, IL_FIXH, szTag, &bAlways, 0), "fixed-H"); + EXPECT_STREQ(szTag, "fixed-H"); +} + +TEST(test_ichiprt1, test_szGetTag_returns_xml_label) +{ + char szTag[64] = {0}; + int bAlways = -1; + // nTag = 1 (XML), bTag = IL_CONN (connections) + EXPECT_STREQ(szGetTag(IdentLbl, 1, IL_CONN, szTag, &bAlways, 0), "connections"); + EXPECT_STREQ(szTag, "connections"); +} + +TEST(test_ichiprt1, test_szGetTag_sets_bAlways) +{ + char szTag[64] = {0}; + int bAlways = -1; + // nTag = 2 (plain text), bTag = IL_FML_ (formula), which has bAlwaysOutput = 1 + szGetTag(IdentLbl, 2, IL_FML_, szTag, &bAlways, 0); + EXPECT_EQ(bAlways, -1); + EXPECT_STREQ(szTag, "/"); +} + +TEST(test_ichiprt1, test_szGetTag_charge) +{ + char szTag[64] = {0}; + int bAlways = -1; + // nTag = 2 (plain text), bTag = IL_CHRG (charge) + EXPECT_STREQ(szGetTag(IdentLbl, 2, IL_CHRG, szTag, &bAlways, 0), "/q"); + EXPECT_STREQ(szTag, "/q"); +} diff --git a/INCHI-1-TEST/tests/test_unit/test_ichiprt2.cpp b/INCHI-1-TEST/tests/test_unit/test_ichiprt2.cpp index 0800ef1b..ef5ed7fc 100644 --- a/INCHI-1-TEST/tests/test_unit/test_ichiprt2.cpp +++ b/INCHI-1-TEST/tests/test_unit/test_ichiprt2.cpp @@ -9,20 +9,16 @@ extern "C" TEST(test_ichiprt2, MakeStereoString_outputs_expected_sp3_string) { - // Prepare input arrays for 8 stereo centers AT_NUMB at1[8] = {3,4,5,6,7,8,9,10}; S_CHAR parity[8] = {1,1,2,2,1,2,2,1}; - // Prepare strbuf (empty) INCHI_IOS_STRING strbuf = {0}; inchi_strbuf_init(&strbuf, INCHI_STRBUF_INITIAL_SIZE, INCHI_STRBUF_SIZE_INCREMENT); - // Call MakeStereoString int bOverflow = 0; int ret = MakeStereoString(at1, nullptr, parity, 0, 8, &strbuf, 0, &bOverflow); - // Check that strbuf contains the expected substring EXPECT_EQ(strbuf.nUsedLength, strlen(strbuf.pStr)); EXPECT_NE(std::string(strbuf.pStr).find("3-,4-,5+,6+,7-,8+,9+,10-"), std::string::npos); EXPECT_EQ(strbuf.pStr[0], '3'); // Should start with 3- diff --git a/INCHI-1-TEST/tests/test_unit/test_ichiprt3.cpp b/INCHI-1-TEST/tests/test_unit/test_ichiprt3.cpp index 2ea24423..edbb4de2 100644 --- a/INCHI-1-TEST/tests/test_unit/test_ichiprt3.cpp +++ b/INCHI-1-TEST/tests/test_unit/test_ichiprt3.cpp @@ -26,7 +26,6 @@ TEST(test_ichiprt3, test_str_Sp3_outputs_expected_sp3_string) stereo->t_parity[i] = parities[i]; } - // Prepare INChI and INCHI_SORT INChI inchi = {0}; inchi.nNumberOfAtoms = 10; inchi.Stereo = stereo; @@ -34,11 +33,10 @@ TEST(test_ichiprt3, test_str_Sp3_outputs_expected_sp3_string) INCHI_SORT sort = {0}; sort.pINChI[0] = &inchi; - // Prepare strbuf (empty) INCHI_IOS_STRING strbuf = {0}; inchi_strbuf_init(&strbuf, INCHI_STRBUF_INITIAL_SIZE, INCHI_STRBUF_SIZE_INCREMENT); - // Call str_Sp3 + int bOverflow = 0; int ret = str_Sp3(&sort, NULL, &strbuf, &bOverflow, 0, 0, 1, 0, 0, 0, 0); From 2e9f95ca877ce64890ef26781ae88f5d9384248f Mon Sep 17 00:00:00 2001 From: Christoph Mueller Date: Mon, 5 Jan 2026 14:54:21 +0000 Subject: [PATCH 07/44] Added unit tests for m-layer --- INCHI-1-SRC/INCHI_BASE/src/ichiprt1.c | 9 +- INCHI-1-SRC/INCHI_BASE/src/ichiprt3.c | 15 +- .../tests/test_unit/test_ichiprt1.cpp | 141 +++++++++++++++--- .../tests/test_unit/test_ichiprt2.cpp | 15 ++ .../tests/test_unit/test_ichiprt3.cpp | 87 +++++++++++ 5 files changed, 238 insertions(+), 29 deletions(-) diff --git a/INCHI-1-SRC/INCHI_BASE/src/ichiprt1.c b/INCHI-1-SRC/INCHI_BASE/src/ichiprt1.c index 839e8ed5..c5664be9 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/ichiprt1.c +++ b/INCHI-1-SRC/INCHI_BASE/src/ichiprt1.c @@ -3375,7 +3375,6 @@ int OutputINCHI_StereoLayer( CANON_GLOBALS *pCG, /* sp2 */ - /*if ( bStereoSp2[io->iCurTautMode] )*/ if ((io->nSegmAction = INChI_SegmentAction( io->sDifSegs[io->nCurINChISegment][DIFS_b_SBONDS] ))) /* djb-rwth: addressing LLVM warning */ { szGetTag( IdentLbl, io->nTag, io->bTag2 = io->bTag1 | IL_DBND, io->szTag2, &io->bAlways, 1 ); @@ -3406,7 +3405,7 @@ int OutputINCHI_StereoLayer( CANON_GLOBALS *pCG, /* sp3 */ - /*if ( bStereoSp3[io->iCurTautMode] )*/ + /* t-layer */ if ((io->nSegmAction = INChI_SegmentAction( io->sDifSegs[io->nCurINChISegment][DIFS_t_SATOMS] ))) /* djb-rwth: addressing LLVM warning */ { io->bRelRac = io->bRelativeStereo[io->iCurTautMode] || io->bRacemicStereo[io->iCurTautMode]; @@ -3436,9 +3435,7 @@ int OutputINCHI_StereoLayer( CANON_GLOBALS *pCG, } } - /* bStereoAbsInverted[io->iCurTautMode] */ - - /* if ( bStereoAbs[io->iCurTautMode] ) */ + /* m-layer */ if ((io->nSegmAction = INChI_SegmentAction( io->sDifSegs[io->nCurINChISegment][DIFS_m_SP3INV] ))) /* djb-rwth: addressing LLVM warning */ { szGetTag( IdentLbl, io->nTag, io->bTag2 = io->bTag1 | IL_INVS, io->szTag2, &io->bAlways, 1 ); @@ -3466,7 +3463,7 @@ int OutputINCHI_StereoLayer( CANON_GLOBALS *pCG, /* stereo type */ - /*if ( io->bRacemicStereo[io->iCurTautMode] || io->bRelativeStereo[io->iCurTautMode] || bStereoAbs[io->iCurTautMode] )*/ + /* s-layer */ if ((io->nSegmAction = INChI_SegmentAction( io->sDifSegs[io->nCurINChISegment][DIFS_s_STYPE] ))) /* djb-rwth: addressing LLVM warning */ { const char *p_stereo = io->bRelativeStereo[io->iCurTautMode] ? x_rel : diff --git a/INCHI-1-SRC/INCHI_BASE/src/ichiprt3.c b/INCHI-1-SRC/INCHI_BASE/src/ichiprt3.c index 6f51233f..309384ce 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/ichiprt3.c +++ b/INCHI-1-SRC/INCHI_BASE/src/ichiprt3.c @@ -1184,10 +1184,15 @@ int str_Sp3( INCHI_SORT *pINChISort, return ( strbuf->nUsedLength - nUsedLength0 ); } - -/**************************************************************************** - Output abs stero inversion substring of the whole structure InChI string -****************************************************************************/ +/** + * @brief Output absolute stereo inversion substring of the whole structure InChI string + * @param pINChISort Pointer to INCHI_SORT array + * @param strbuf Pointer to string buffer + * @param bOverflow Overflow flag + * @param bOutType Output type + * @param num_components Number of connected components + * @return Length of the added substring (return value '0', '1', or '.' in strbuf) + */ int str_StereoAbsInv( INCHI_SORT *pINChISort, INCHI_IOS_STRING *strbuf, int *bOverflow, @@ -4131,7 +4136,7 @@ int bin_AuxTautTrans( INCHI_SORT *pINChISort, if (nTrans_n && nTrans_s) { /* new ordering number for original non-tautomeric component number is->ord_number */ - nTrans_n[is->ord_number] = i + 1; /*nTrans_t[is2->ord_number] =*/ + nTrans_n[is->ord_number] = i + 1; /*nTrans_t[is2->ord_number] =*/ } } } diff --git a/INCHI-1-TEST/tests/test_unit/test_ichiprt1.cpp b/INCHI-1-TEST/tests/test_unit/test_ichiprt1.cpp index df6348f2..419f4217 100644 --- a/INCHI-1-TEST/tests/test_unit/test_ichiprt1.cpp +++ b/INCHI-1-TEST/tests/test_unit/test_ichiprt1.cpp @@ -45,6 +45,114 @@ TEST(test_ichiprt1, test_OutputINCHI_StereoLayer) } +TEST(test_ichiprt1, test_OutputINCHI_StereoLayer_m0) +{ + CANON_GLOBALS cg = {0}; + INCHI_IOSTREAM out_file = {0}; + INCHI_IOS_STRING strbuf = {0}; + INCHI_OUT_CTL io = {0}; + + io.sDifSegs[0][DIFS_m_SP3INV] = DIFV_OUTPUT_FILL_T; + + io.nCurINChISegment = 0; + io.iCurTautMode = 0; + io.bPlainTextTags = 1; + io.bRelativeStereo[0] = 0; + io.bRacemicStereo[0] = 0; + io.bAlways = 0; + io.nTag = 2; // plain text + io.bTag1 = IL_STER; + io.bTag2 = IL_STER | IL_TYPS; + io.bOverflow = 0; + io.bOutType = OUT_TN; + io.num_components = 1; + + inchi_strbuf_init(&strbuf, INCHI_STRBUF_INITIAL_SIZE, INCHI_STRBUF_SIZE_INCREMENT); + inchi_ios_init(&out_file, INCHI_IOS_TYPE_STRING, nullptr); + + int num_at = 2; + + int found_num_bonds = 0; + int found_num_isotopic = 0; + inp_ATOM *at = CreateInpAtom(num_at); + INChI *inchi = Alloc_INChI(at, num_at, &found_num_bonds, &found_num_isotopic, 0); + inchi->Stereo->nCompInv2Abs = 1; + inchi->nNumberOfAtoms = num_at; + + INCHI_SORT *inchi_sort = (INCHI_SORT*)calloc(1, sizeof(INCHI_SORT)); + inchi_sort->pINChI[TAUT_YES] = inchi; + + io.pINChISort = inchi_sort; + + int ret = OutputINCHI_StereoLayer(&cg, &out_file, &strbuf, &io, (char*)"", (char*)""); + + EXPECT_EQ(std::string(strbuf.pStr), "/m0"); + EXPECT_EQ(ret, 0); + + inchi_strbuf_close(&strbuf); + inchi_ios_close(&out_file); + + Free_INChI( &inchi ); + + FreeInpAtom(&at); + + free(inchi_sort); +} + +TEST(test_ichiprt1, test_OutputINCHI_StereoLayer_m1) +{ + CANON_GLOBALS cg = {0}; + INCHI_IOSTREAM out_file = {0}; + INCHI_IOS_STRING strbuf = {0}; + INCHI_OUT_CTL io = {0}; + + io.sDifSegs[0][DIFS_m_SP3INV] = DIFV_OUTPUT_FILL_T; + + io.nCurINChISegment = 0; + io.iCurTautMode = 0; + io.bPlainTextTags = 1; + io.bRelativeStereo[0] = 0; + io.bRacemicStereo[0] = 0; + io.bAlways = 0; + io.nTag = 2; // plain text + io.bTag1 = IL_STER; + io.bTag2 = IL_STER | IL_TYPS; + io.bOverflow = 0; + io.bOutType = OUT_TN; + io.num_components = 1; + + inchi_strbuf_init(&strbuf, INCHI_STRBUF_INITIAL_SIZE, INCHI_STRBUF_SIZE_INCREMENT); + inchi_ios_init(&out_file, INCHI_IOS_TYPE_STRING, nullptr); + + int num_at = 2; + + int found_num_bonds = 0; + int found_num_isotopic = 0; + inp_ATOM *at = CreateInpAtom(num_at); + INChI *inchi = Alloc_INChI(at, num_at, &found_num_bonds, &found_num_isotopic, 0); + inchi->Stereo->nCompInv2Abs = -1; + inchi->nNumberOfAtoms = num_at; + + INCHI_SORT *inchi_sort = (INCHI_SORT*)calloc(1, sizeof(INCHI_SORT)); + inchi_sort->pINChI[TAUT_YES] = inchi; + + io.pINChISort = inchi_sort; + + int ret = OutputINCHI_StereoLayer(&cg, &out_file, &strbuf, &io, (char*)"", (char*)""); + + EXPECT_EQ(std::string(strbuf.pStr), "/m1"); + EXPECT_EQ(ret, 0); + + inchi_strbuf_close(&strbuf); + inchi_ios_close(&out_file); + + Free_INChI( &inchi ); + + FreeInpAtom(&at); + + free(inchi_sort); +} + TEST(test_ichiprt1, test_OutputINCHI_StereoLayer_s1) { CANON_GLOBALS cg = {0}; @@ -137,6 +245,7 @@ TEST(test_ichiprt1, test_OutputINCHI_StereoLayer_s3) inchi_strbuf_close(&strbuf); inchi_ios_close(&out_file); + } TEST(test_ichiprt1, test_OutputINCHI_StereoLayer_enh_stereo_1) @@ -151,23 +260,8 @@ TEST(test_ichiprt1, test_OutputINCHI_StereoLayer_enh_stereo_1) static AT_NUMB numbers[8] = {3,4,5,6,7,8,9,10}; static S_CHAR parities[8] = {1,1,2,2,1,2,2,1};// -,-,+,+,-,+,+,- - static S_CHAR m_values[1] = {0}; // /m0 - static S_CHAR s_values[1] = {1}; // /s1 - // static INChI_Stereo stereo = {0}; - static INChI_Stereo *stereo = Alloc_INChI_Stereo( num_at, num_bonds ); - - stereo->nNumberOfStereoCenters = 8; - - for (int i = 0; i < num_at; i++) { - stereo->nNumber[i] = numbers[i]; - stereo->t_parity[i] = parities[i]; - } - - stereo->nCompInv2Abs = 1; - stereo->nNumberOfStereoBonds = 0; - stereo->b_parity = NULL; // INChI *inchi = new INChI; // inchi->nNumberOfAtoms = num_at; @@ -179,7 +273,19 @@ TEST(test_ichiprt1, test_OutputINCHI_StereoLayer_enh_stereo_1) static INChI *inchi = Alloc_INChI(at, num_at, &found_num_bonds, &found_num_isotopic, 0); inchi->nNumberOfAtoms = num_at; - inchi->Stereo = stereo; + + inchi->Stereo->nNumberOfStereoCenters = num_at; + + for (int i = 0; i < num_at; i++) { + inchi->Stereo->nNumber[i] = numbers[i]; + inchi->Stereo->t_parity[i] = parities[i]; + } + + inchi->Stereo->nCompInv2Abs = 1; + inchi->Stereo->nNumberOfStereoBonds = 0; + inchi->Stereo->b_parity = NULL; + + static INCHI_SORT sort = {0}; sort.pINChI[0] = inchi; @@ -214,8 +320,7 @@ TEST(test_ichiprt1, test_OutputINCHI_StereoLayer_enh_stereo_1) inchi_ios_close(&out_file); FreeInpAtom(&at); - // Free_INChI(&inchi); - Free_INChI_Stereo(stereo); + Free_INChI(&inchi); } TEST(test_ichiprt1, test_set_line_separators) diff --git a/INCHI-1-TEST/tests/test_unit/test_ichiprt2.cpp b/INCHI-1-TEST/tests/test_unit/test_ichiprt2.cpp index ef5ed7fc..2244420d 100644 --- a/INCHI-1-TEST/tests/test_unit/test_ichiprt2.cpp +++ b/INCHI-1-TEST/tests/test_unit/test_ichiprt2.cpp @@ -43,6 +43,21 @@ TEST(test_ichiprt2, MakeMult_mult_gt_1_appends_number_and_delim) inchi_strbuf_close(&strbuf); } +TEST(test_ichiprt2, MakeMult_mult_2) +{ + INCHI_IOS_STRING strbuf = {0}; + inchi_strbuf_init(&strbuf, INCHI_STRBUF_INITIAL_SIZE, INCHI_STRBUF_SIZE_INCREMENT); + + int bOverflow = 0; + int ret = MakeMult(10, "+", &strbuf, 0, &bOverflow); + + EXPECT_EQ(std::string(strbuf.pStr), "10+"); + EXPECT_EQ(ret, 3); // "3-".length() == 2, but MakeMult returns n (number of chars written) + EXPECT_EQ(bOverflow, 0); + + inchi_strbuf_close(&strbuf); +} + TEST(test_ichiprt2, MakeMult_mult_eq_1_does_nothing) { INCHI_IOS_STRING strbuf = {0}; diff --git a/INCHI-1-TEST/tests/test_unit/test_ichiprt3.cpp b/INCHI-1-TEST/tests/test_unit/test_ichiprt3.cpp index edbb4de2..be614485 100644 --- a/INCHI-1-TEST/tests/test_unit/test_ichiprt3.cpp +++ b/INCHI-1-TEST/tests/test_unit/test_ichiprt3.cpp @@ -6,6 +6,7 @@ extern "C" #include "../../../INCHI-1-SRC/INCHI_BASE/src/ichiprt3.c" #include "../../../INCHI-1-SRC/INCHI_BASE/src/strutil.c" #include "../../../INCHI-1-SRC/INCHI_BASE/src/ichi_io.h" +#include "../../../INCHI-1-SRC/INCHI_BASE/src/ichimake.h" } TEST(test_ichiprt3, test_str_Sp3_outputs_expected_sp3_string) @@ -53,3 +54,89 @@ TEST(test_ichiprt3, test_str_Sp3_outputs_expected_sp3_string) Free_INChI_Stereo(stereo); } + +TEST(test_ichiprt3, test_str_StereoAbsInv_1) +{ + + int num_at = 2; + int num_bonds = 1; + + + + int found_num_bonds = 0; + int found_num_isotopic = 0; + inp_ATOM *at = CreateInpAtom(num_at); + static INChI *inchi = Alloc_INChI(at, num_at, &found_num_bonds, &found_num_isotopic, 0); + inchi->Stereo->nCompInv2Abs = -1; + inchi->nNumberOfAtoms = num_at; + + INCHI_SORT *inchi_sort = (INCHI_SORT*)calloc(1, sizeof(INCHI_SORT)); + inchi_sort->pINChI[TAUT_YES] = inchi; + + INCHI_IOS_STRING strbuf = {0}; + inchi_strbuf_init(&strbuf, INCHI_STRBUF_INITIAL_SIZE, INCHI_STRBUF_SIZE_INCREMENT); + + int bOverflow = 0; + int bOutType = OUT_TN; + int num_components = 1; + + // int test_ret0 = HAS_T(inchi_sort); + // int test_ret1 = GET_II( bOutType, inchi_sort); + + int ret = str_StereoAbsInv(inchi_sort, &strbuf, &bOverflow, bOutType, num_components); + + EXPECT_EQ(ret, 1); + EXPECT_EQ(std::string(strbuf.pStr), "1"); + EXPECT_EQ(bOverflow, 0); + + inchi_strbuf_close(&strbuf); + + Free_INChI(&inchi); + + FreeInpAtom(&at); + + free(inchi_sort); +} + +TEST(test_ichiprt3, test_str_StereoAbsInv_2) +{ + + int num_at = 2; + int num_bonds = 1; + + int found_num_bonds = 0; + int found_num_isotopic = 0; + inp_ATOM *at = CreateInpAtom(num_at); + static INChI *inchi = Alloc_INChI(at, num_at, &found_num_bonds, &found_num_isotopic, 0); + + inchi->Stereo->nCompInv2Abs = 1; + + inchi->nNumberOfAtoms = num_at; + + INCHI_SORT *inchi_sort = (INCHI_SORT*)calloc(1, sizeof(INCHI_SORT)); + inchi_sort->pINChI[TAUT_YES] = inchi; + + INCHI_IOS_STRING strbuf = {0}; + inchi_strbuf_init(&strbuf, INCHI_STRBUF_INITIAL_SIZE, INCHI_STRBUF_SIZE_INCREMENT); + + int bOverflow = 0; + int bOutType = OUT_TN; + int num_components = 1; + + // int test_ret0 = HAS_T(inchi_sort); + // int test_ret1 = GET_II( bOutType, inchi_sort); + + int ret = str_StereoAbsInv(inchi_sort, &strbuf, &bOverflow, bOutType, num_components); + + EXPECT_EQ(ret, 1); + EXPECT_EQ(std::string(strbuf.pStr), "0"); + EXPECT_EQ(bOverflow, 0); + + inchi_strbuf_close(&strbuf); + + Free_INChI(&inchi); + + FreeInpAtom(&at); + + free(inchi_sort); +} From 01ca15392e749934b24e0d7efafda80d729ece6e Mon Sep 17 00:00:00 2001 From: Christoph Mueller Date: Wed, 7 Jan 2026 13:46:22 +0000 Subject: [PATCH 08/44] Added doxygen comments --- INCHI-1-SRC/INCHI_BASE/src/ichiprt1.c | 26 +++++++++++++++----------- INCHI-1-SRC/INCHI_BASE/src/ichiprt3.c | 19 +++++++++++++++---- 2 files changed, 30 insertions(+), 15 deletions(-) diff --git a/INCHI-1-SRC/INCHI_BASE/src/ichiprt1.c b/INCHI-1-SRC/INCHI_BASE/src/ichiprt1.c index c5664be9..830aabc9 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/ichiprt1.c +++ b/INCHI-1-SRC/INCHI_BASE/src/ichiprt1.c @@ -3347,10 +3347,17 @@ int OutputINCHI_ChargeAndRemovedAddedProtonsLayers( CANON_GLOBALS *pCG, return 0; } - -/**************************************************************************** -Output InChI: stereo layer with sublayers -****************************************************************************/ +/** + * @brief Output InChI: stereo layer with sublayers. + * + * @param pCG Pointer to the CANON_GLOBALS structure containing global canonicalization data. + * @param out_file Pointer to the INCHI_IOSTREAM output stream where the stereo layer will be written. + * @param strbuf Pointer to an INCHI_IOS_STRING buffer used for string formatting and output. + * @param io Pointer to the INCHI_OUT_CTL structure containing output control and state information. + * @param pLF Pointer to a string used as the line feed (end-of-line) character(s). + * @param pTAB Pointer to a string used as the tab or separator character(s). + * @return Returns 0 on success, or a non-zero error code on failure. + */ int OutputINCHI_StereoLayer( CANON_GLOBALS *pCG, INCHI_IOSTREAM *out_file, INCHI_IOS_STRING *strbuf, @@ -3359,10 +3366,6 @@ int OutputINCHI_StereoLayer( CANON_GLOBALS *pCG, char *pTAB ) { - // int i; - // i = INChI_SegmentAction( io->sDifSegs[io->nCurINChISegment][DIFS_t_SATOMS] ); /* djb-rwth: ignoring LLVM warning: variable used to store function return value */ - // /* djb-rwth: removing redundant code */ - if (INChI_SegmentAction( io->sDifSegs[io->nCurINChISegment][DIFS_b_SBONDS] ) || INChI_SegmentAction( io->sDifSegs[io->nCurINChISegment][DIFS_t_SATOMS] ) || INChI_SegmentAction( io->sDifSegs[io->nCurINChISegment][DIFS_m_SP3INV] ) || @@ -3375,7 +3378,7 @@ int OutputINCHI_StereoLayer( CANON_GLOBALS *pCG, /* sp2 */ - if ((io->nSegmAction = INChI_SegmentAction( io->sDifSegs[io->nCurINChISegment][DIFS_b_SBONDS] ))) /* djb-rwth: addressing LLVM warning */ + if ((io->nSegmAction = INChI_SegmentAction( io->sDifSegs[io->nCurINChISegment][DIFS_b_SBONDS] ))) { szGetTag( IdentLbl, io->nTag, io->bTag2 = io->bTag1 | IL_DBND, io->szTag2, &io->bAlways, 1 ); inchi_strbuf_reset( strbuf ); @@ -3464,7 +3467,7 @@ int OutputINCHI_StereoLayer( CANON_GLOBALS *pCG, /* stereo type */ /* s-layer */ - if ((io->nSegmAction = INChI_SegmentAction( io->sDifSegs[io->nCurINChISegment][DIFS_s_STYPE] ))) /* djb-rwth: addressing LLVM warning */ + if ((io->nSegmAction = INChI_SegmentAction( io->sDifSegs[io->nCurINChISegment][DIFS_s_STYPE] ))) { const char *p_stereo = io->bRelativeStereo[io->iCurTautMode] ? x_rel : io->bRacemicStereo[io->iCurTautMode] ? x_rac : x_abs; @@ -3499,7 +3502,8 @@ int OutputINCHI_StereoLayer( CANON_GLOBALS *pCG, /**************************************************************************** -Output InChI: isotopic layer and sublayers ****************************************************************************/ +Output InChI: isotopic layer and sublayers +****************************************************************************/ int OutputINCHI_IsotopicLayer( CANON_GLOBALS *pCG, INCHI_IOSTREAM *out_file, INCHI_IOS_STRING *strbuf, diff --git a/INCHI-1-SRC/INCHI_BASE/src/ichiprt3.c b/INCHI-1-SRC/INCHI_BASE/src/ichiprt3.c index 309384ce..eb338ddd 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/ichiprt3.c +++ b/INCHI-1-SRC/INCHI_BASE/src/ichiprt3.c @@ -718,10 +718,21 @@ int str_FixedH_atoms( INCHI_SORT *pINChISort, return ( strbuf->nUsedLength - nUsedLength0 ); } - -/**************************************************************************** - Produce double bond stereo substring of the whole structure InChI string. -****************************************************************************/ +/** + * @brief Produce double bond stereo substring of the whole structure InChI string. + * + * @param pINChISort Pointer to the primary INCHI_SORT structure containing input data. + * @param pINChISort2 Pointer to the secondary INCHI_SORT structure, used for comparison or additional data. + * @param strbuf Pointer to an INCHI_IOS_STRING buffer where the output string will be stored. + * @param bOverflow Pointer to an integer flag that will be set if the output overflows the buffer. + * @param bOutType Output type flag specifying the format or type of output. + * @param TAUT_MODE Tautomer mode flag indicating how tautomers are handled. + * @param num_components Number of components to process. + * @param bSecondNonTautPass Flag indicating if this is the second pass for non-tautomeric processing. + * @param bOmitRepetitions Flag to omit repeated entries in the output. + * @param bUseMulipliers Flag to use multipliers in the output representation. + * @return Returns number of characters written to strbuf + */ int str_Sp2( INCHI_SORT *pINChISort, INCHI_SORT *pINChISort2, INCHI_IOS_STRING *strbuf, From ddd3c7a32b1e11c61488690fb65c85366d156dc2 Mon Sep 17 00:00:00 2001 From: Christoph Mueller Date: Thu, 8 Jan 2026 15:58:52 +0000 Subject: [PATCH 09/44] Added unit tests and initial modification for enh stereo --- INCHI-1-SRC/INCHI_BASE/src/ichidrp.h | 1 + INCHI-1-SRC/INCHI_BASE/src/ichimake.h | 5 + INCHI-1-SRC/INCHI_BASE/src/ichiprt1.c | 239 +++++++++++++++++- INCHI-1-SRC/INCHI_BASE/src/ichiprt2.c | 73 +++++- INCHI-1-SRC/INCHI_BASE/src/strutil.h | 4 +- .../tests/test_unit/test_ichimain.cpp | 3 +- .../tests/test_unit/test_ichiprt1.cpp | 178 +++++++++++-- .../test_ichiprt1_enhancedStereo.cpp | 104 ++++++++ .../tests/test_unit/test_ichiprt2.cpp | 110 ++++++++ .../tests/test_unit/test_ichiprt3.cpp | 142 ++++++++++- 10 files changed, 811 insertions(+), 48 deletions(-) create mode 100644 INCHI-1-TEST/tests/test_unit/test_ichiprt1_enhancedStereo.cpp diff --git a/INCHI-1-SRC/INCHI_BASE/src/ichidrp.h b/INCHI-1-SRC/INCHI_BASE/src/ichidrp.h index ff555c93..7e0e7673 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/ichidrp.h +++ b/INCHI-1-SRC/INCHI_BASE/src/ichidrp.h @@ -187,6 +187,7 @@ typedef struct tagInputParms { int bNoWarnings; /* v. 1.06+ suppress warning messages */ int bHideInChI; /* v. 1.06+ Do not print InChI itself */ + int bEnhancedStereo; /* */ INCHI_MODE bTautFlags; diff --git a/INCHI-1-SRC/INCHI_BASE/src/ichimake.h b/INCHI-1-SRC/INCHI_BASE/src/ichimake.h index 4f7f9db5..0669703b 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/ichimake.h +++ b/INCHI-1-SRC/INCHI_BASE/src/ichimake.h @@ -208,6 +208,11 @@ const char *EquString( int EquVal ); INCHI_IOS_STRING *buf, int nCtMode, int *bOverflow ); + int MakeEnhStereoString( int **enh_stereo, + int nof_units, + INCHI_IOS_STRING *strbuf, + int nCtMode, + int *bOverflow ); int MakeCRVString( ORIG_INFO *OrigInfo, int nLenCT, int bAddDelim, diff --git a/INCHI-1-SRC/INCHI_BASE/src/ichiprt1.c b/INCHI-1-SRC/INCHI_BASE/src/ichiprt1.c index 830aabc9..da530ed5 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/ichiprt1.c +++ b/INCHI-1-SRC/INCHI_BASE/src/ichiprt1.c @@ -129,6 +129,13 @@ static int OutputINCHI_StereoLayer( CANON_GLOBALS *pCG, INCHI_OUT_CTL *io, char *pLF, char *pTAB ); +static int OutputINCHI_StereoLayer_EnhancedStereo( CANON_GLOBALS *pCG, + INCHI_IOSTREAM *out_file, + INCHI_IOS_STRING *strbuf, + INCHI_OUT_CTL *io, + ORIG_ATOM_DATA *orig_inp_data, + char *pLF, + char *pTAB ); static int OutputINCHI_IsotopicLayer( CANON_GLOBALS *pCG, INCHI_IOSTREAM *out_file, INCHI_IOS_STRING *strbuf, @@ -1032,14 +1039,29 @@ int OutputINChI2( CANON_GLOBALS *pCG, return ret; } - -/* */ -/* OutputINChI1( ... ) */ -/* */ -/* Main actual worker which serializes InChI to string. */ -/* */ -/* Called from OutputINChI2( ... ) and from itself */ -/* */ +/** + * @brief Main actual worker which serializes InChI to string. Called from OutputINChI2( ... ) and from itself. + * + * @param pCG Pointer to global canonicalization data. + * @param strbuf Pointer to the output string buffer. + * @param pINChISortTautAndNonTaut2 Array of pointers to INCHI_SORT structures for tautomeric and non-tautomeric forms. + * @param INCHI_basic_or_INCHI_reconnected Flag indicating basic or reconnected InChI output. + * @param orig_inp_data Pointer to original atom data. + * @param pOrigStruct Pointer to the original structure data. + * @param ip Pointer to input parameters. + * @param bDisconnectedCoord Flag for disconnected metal coordination. + * @param bOutputType Output type (tautomeric/non-tautomeric/both). + * @param bINChIOutputOptions Bitmask of output options. + * @param num_components2 Array of component counts for each structure type. + * @param num_non_taut2 Array of non-tautomeric component counts. + * @param num_taut2 Array of tautomeric component counts. + * @param out_file Output stream for InChI string. + * @param log_file Output stream for logging. + * @param num_input_struct Number of input structures. + * @param pSortPrintINChIFlags Pointer to flags controlling sorting and printing. + * @param save_opt_bits Encoded bits for saved InChI creation options. + * @return int + */ int OutputINChI1( CANON_GLOBALS *pCG, INCHI_IOS_STRING *strbuf, INCHI_SORT *pINChISortTautAndNonTaut2[][TAUT_NUM], @@ -1753,7 +1775,14 @@ int OutputINChI1( CANON_GLOBALS *pCG, } /* InChI output: stereo (non-isotopic) */ - intermediate_result = OutputINCHI_StereoLayer( pCG, out_file, strbuf, &io, pLF, pTAB ); + // intermediate_result = OutputINCHI_StereoLayer( pCG, out_file, strbuf, &io, pLF, pTAB ); + + if (ip->bEnhancedStereo) { + intermediate_result = OutputINCHI_StereoLayer_EnhancedStereo( pCG, out_file, strbuf, &io, orig_inp_data, pLF, pTAB ); + } else { + intermediate_result = OutputINCHI_StereoLayer( pCG, out_file, strbuf, &io, pLF, pTAB ); + } + if (intermediate_result != 0) goto exit_function; @@ -3500,6 +3529,198 @@ int OutputINCHI_StereoLayer( CANON_GLOBALS *pCG, return 0; } +/** + * @brief Output InChI: stereo layer with sublayers. + * + * @param pCG Pointer to the CANON_GLOBALS structure containing global canonicalization data. + * @param out_file Pointer to the INCHI_IOSTREAM output stream where the stereo layer will be written. + * @param strbuf Pointer to an INCHI_IOS_STRING buffer used for string formatting and output. + * @param io Pointer to the INCHI_OUT_CTL structure containing output control and state information. + * @param orig_inp_data Pointer to the ORIG_ATOM_DATA containing e.g. atom information. + * @param pLF Pointer to a string used as the line feed (end-of-line) character(s). + * @param pTAB Pointer to a string used as the tab or separator character(s). + * @return Returns 0 on success, or a non-zero error code on failure. + */ +int OutputINCHI_StereoLayer_EnhancedStereo( CANON_GLOBALS *pCG, + INCHI_IOSTREAM *out_file, + INCHI_IOS_STRING *strbuf, + INCHI_OUT_CTL *io, + ORIG_ATOM_DATA *orig_inp_data, + char *pLF, + char *pTAB ) +{ + + if (INChI_SegmentAction( io->sDifSegs[io->nCurINChISegment][DIFS_b_SBONDS] ) || + INChI_SegmentAction( io->sDifSegs[io->nCurINChISegment][DIFS_t_SATOMS] ) || + INChI_SegmentAction( io->sDifSegs[io->nCurINChISegment][DIFS_m_SP3INV] ) || + INChI_SegmentAction( io->sDifSegs[io->nCurINChISegment][DIFS_s_STYPE] )) + { + + /* stereo */ + + szGetTag( IdentLbl, io->nTag, io->bTag1 = IL_STER | io->bFhTag, io->szTag1, &io->bAlways, 1 ); + + /* sp2 */ + + if ((io->nSegmAction = INChI_SegmentAction( io->sDifSegs[io->nCurINChISegment][DIFS_b_SBONDS] ))) + { + szGetTag( IdentLbl, io->nTag, io->bTag2 = io->bTag1 | IL_DBND, io->szTag2, &io->bAlways, 1 ); + inchi_strbuf_reset( strbuf ); + io->tot_len = 0; + if (INCHI_SEGM_FILL == io->nSegmAction) + { + io->tot_len = str_Sp2( io->pINChISort, io->pINChISort2, strbuf, &io->bOverflow, + io->bOutType, io->TAUT_MODE, io->num_components, + io->bSecondNonTautPass, io->bOmitRepetitions, io->bUseMulipliers ); + + io->bNonTautNonIsoIdentifierNotEmpty += io->bSecondNonTautPass; + } + + if (str_LineEnd( io->szTag2, &io->bOverflow, strbuf, -io->nSegmAction, io->bPlainTextTags )) + { + return 1; + } + inchi_ios_print_nodisplay( out_file, "%s%s", strbuf->pStr, pLF ); + } + else + { + if (io->bPlainTextTags == 1) + { + inchi_ios_print_nodisplay( out_file, "/" ); /* sp2 */ + } + } + + /* sp3 */ + + /* t-layer */ + if ((io->nSegmAction = INChI_SegmentAction( io->sDifSegs[io->nCurINChISegment][DIFS_t_SATOMS] ))) /* djb-rwth: addressing LLVM warning */ + { + io->bRelRac = io->bRelativeStereo[io->iCurTautMode] || io->bRacemicStereo[io->iCurTautMode]; + szGetTag( IdentLbl, io->nTag, io->bTag2 = io->bTag1 | IL_SP3S, io->szTag2, &io->bAlways, 1 ); + inchi_strbuf_reset( strbuf ); + io->tot_len = 0; + if (INCHI_SEGM_FILL == io->nSegmAction) + { + io->tot_len = str_Sp3( io->pINChISort, io->pINChISort2, strbuf, &io->bOverflow, + io->bOutType, io->TAUT_MODE, io->num_components, io->bRelRac, + io->bSecondNonTautPass, io->bOmitRepetitions, io->bUseMulipliers ); + + io->bNonTautNonIsoIdentifierNotEmpty += io->bSecondNonTautPass; + } + + if (str_LineEnd( io->szTag2, &io->bOverflow, strbuf, -io->nSegmAction, io->bPlainTextTags )) + { + return 2; + } + inchi_ios_print_nodisplay( out_file, "%s%s", strbuf->pStr, pLF ); + } + else + { + if (io->bPlainTextTags == 1) + { + inchi_ios_print_nodisplay( out_file, "/" ); /* sp3 */ + } + } + + /* m-layer */ + if ((io->nSegmAction = INChI_SegmentAction( io->sDifSegs[io->nCurINChISegment][DIFS_m_SP3INV] ))) /* djb-rwth: addressing LLVM warning */ + { + szGetTag( IdentLbl, io->nTag, io->bTag2 = io->bTag1 | IL_INVS, io->szTag2, &io->bAlways, 1 ); + inchi_strbuf_reset( strbuf ); + io->tot_len = 0; + if (INCHI_SEGM_FILL == io->nSegmAction) + { + io->tot_len = str_StereoAbsInv( io->pINChISort, strbuf, + &io->bOverflow, io->bOutType, io->num_components ); + io->bNonTautNonIsoIdentifierNotEmpty += io->bSecondNonTautPass; + } + + if (str_LineEnd( io->szTag2, &io->bOverflow, strbuf, -io->nSegmAction, io->bPlainTextTags )) + { + return 3; + } + inchi_ios_print_nodisplay( out_file, "%s%s", strbuf->pStr, pLF ); + } + else + { + if (io->bPlainTextTags == 1) + { + inchi_ios_print_nodisplay( out_file, "/" ); /* stereo-abs-inv */ + } + } + + /* stereo type */ + + /* s-layer */ + if ((io->nSegmAction = INChI_SegmentAction( io->sDifSegs[io->nCurINChISegment][DIFS_s_STYPE] ))) + { + + const char *p_stereo; + if (io->bRelativeStereo[io->iCurTautMode]) { + p_stereo = x_rel; + } else if (io->bRacemicStereo[io->iCurTautMode]) { + p_stereo = x_rac; + } else { + p_stereo = x_abs; + } + + szGetTag( IdentLbl, io->nTag, io->bTag2 = io->bTag1 | IL_TYPS, io->szTag2, &io->bAlways, 1 ); + inchi_strbuf_reset( strbuf ); + io->tot_len = 0; + if (INCHI_SEGM_FILL == io->nSegmAction) + { + io->tot_len += MakeDelim( x_abs, strbuf, &io->bOverflow ); // s1 + io->tot_len += MakeEnhStereoString( + orig_inp_data->v3000->lists_steabs, + orig_inp_data->v3000->n_steabs, + strbuf, + 0, + &io->bOverflow + ); + + + io->tot_len += MakeDelim( x_rel, strbuf, &io->bOverflow ); // s2 + io->tot_len += MakeEnhStereoString( + orig_inp_data->v3000->lists_sterel, + orig_inp_data->v3000->n_sterel, + strbuf, + 0, + &io->bOverflow + ); + + + io->tot_len += MakeDelim( x_rac, strbuf, &io->bOverflow ); // s3 + io->tot_len += MakeEnhStereoString( + orig_inp_data->v3000->lists_sterac, + orig_inp_data->v3000->n_sterac, + strbuf, + 0, + &io->bOverflow + ); + + io->bNonTautNonIsoIdentifierNotEmpty += io->bSecondNonTautPass; + } + if (str_LineEnd( io->szTag2, &io->bOverflow, strbuf, -io->nSegmAction, io->bPlainTextTags )) + { + return 1; + } + inchi_ios_print_nodisplay( out_file, "%s%s", strbuf->pStr, pLF ); + } + if (io->bPlainTextTags == 1) + { + inchi_ios_print_nodisplay( out_file, "/" ); /* no abs, inv or racemic stereo */ + } + } + else + { + if (io->bPlainTextTags == 1) + { + inchi_ios_print_nodisplay( out_file, "////" ); /* sp2, sp3, abs-inv, stereo.type */ + } + } + + return 0; +} /**************************************************************************** Output InChI: isotopic layer and sublayers diff --git a/INCHI-1-SRC/INCHI_BASE/src/ichiprt2.c b/INCHI-1-SRC/INCHI_BASE/src/ichiprt2.c index 51ce948c..2a40880e 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/ichiprt2.c +++ b/INCHI-1-SRC/INCHI_BASE/src/ichiprt2.c @@ -471,6 +471,49 @@ int MakeMult( int mult, return 0; } +int MakeMult_EnhStereo( int mult, + const char *szTailingDelim, + INCHI_IOS_STRING *buf, + int nCtMode, + int *bOverflow ) +{ + char szValue[2048]; + int len = 0, len_delim, n; + + if (*bOverflow) + { + return 0; + } + if (nCtMode & CT_MODE_ABC_NUMBERS) + { + len += MakeAbcNumber( szValue, ( int )sizeof( szValue ), NULL, mult ); + } + else + { + len += MakeDecNumber( szValue, ( int )sizeof( szValue ), NULL, mult ); + } + len_delim = (int) strlen( szTailingDelim ); + + if (len + len_delim < ( int )sizeof( szValue )) + { + strcpy(szValue + len, szTailingDelim); + n = inchi_strbuf_printf( buf, "%s", szValue ); + if (-1 == n) *bOverflow |= 1; + return n; + /* + len += len_delim; + if ( len < nLen_szLinearCT ) + { + strcpy( szLinearCT, szValue ); + return len; + }*/ + } + + *bOverflow |= 1; + + return 0; +} + /****************************************************************************/ int MakeDelim( const char *szTailingDelim, @@ -1464,7 +1507,7 @@ int MakeCRVString( ORIG_INFO *OrigInfo, } /* radical */ if (OrigInfo[k].cRadical) - { + { if (len >= 2047) /* djb-rwth: fixing coverity CID #499515 */ { len = 2047; @@ -2116,6 +2159,34 @@ int MakeStereoString( AT_NUMB *at1, return nLen; } +int MakeEnhStereoString( int **enh_stereo, + int nof_units, + INCHI_IOS_STRING *strbuf, + int nCtMode, + int *bOverflow ) +{ + int tot_len = 0; + + if (nof_units > 0) { + tot_len += MakeDelim( "(", strbuf, bOverflow ); + for (int i = 0; i < nof_units; i++) { + for (int j = 0; j < enh_stereo[i][1]; j++) { + + MakeMult_EnhStereo( enh_stereo[i][2 + j], "", strbuf, nCtMode, bOverflow ); + + if ((j + 1) < enh_stereo[i][1]) { + tot_len += MakeDelim( ",", strbuf, bOverflow ); + } + } + if (i + 1 < nof_units) { + tot_len += MakeDelim( ";", strbuf, bOverflow ); + } + } + tot_len += MakeDelim( ")", strbuf, bOverflow ); + } + return tot_len; +} + #ifdef ALPHA_BASE #if ( ALPHA_BASE != 27 ) diff --git a/INCHI-1-SRC/INCHI_BASE/src/strutil.h b/INCHI-1-SRC/INCHI_BASE/src/strutil.h index 14af75ba..2fbec84b 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/strutil.h +++ b/INCHI-1-SRC/INCHI_BASE/src/strutil.h @@ -407,9 +407,9 @@ extern "C" * @brief Compares stereo information of two structures * * @param s1 Pointer to stereo information of first structure - * @param eql1 Flag for stereo information check + * @param eql1 Flag for stereo information check (EQL_EXISTS, EQL_SP3, EQL_SP3_INV, EQL_SP2) * @param s2 Pointer to stereo information of second structure - * @param eql2 Flag for stereo information check + * @param eql2 Flag for stereo information check (EQL_EXISTS, EQL_SP3, EQL_SP3_INV, EQL_SP2) * @param bRelRac Flag to compare racemic stereo information * @return int 0 if unequal, 1 if equal */ diff --git a/INCHI-1-TEST/tests/test_unit/test_ichimain.cpp b/INCHI-1-TEST/tests/test_unit/test_ichimain.cpp index ea2c2618..1160233e 100644 --- a/INCHI-1-TEST/tests/test_unit/test_ichimain.cpp +++ b/INCHI-1-TEST/tests/test_unit/test_ichimain.cpp @@ -317,7 +317,8 @@ TEST(test_ichimain, test_ProcessMultipleInputFiles_2mol_files) char tmpl[] = "../../../../../INCHI-1-TEST/tests/test_unit/fixtures/inchi_mol_test_XXXXXX"; char *tmpd = mkdtemp(tmpl); - ASSERT_NE(tmpd, nullptr); + + EXPECT_NE(tmpd, nullptr); std::vector dist_paths; for (auto cur_filename : input_mols) { diff --git a/INCHI-1-TEST/tests/test_unit/test_ichiprt1.cpp b/INCHI-1-TEST/tests/test_unit/test_ichiprt1.cpp index 419f4217..67b5aefc 100644 --- a/INCHI-1-TEST/tests/test_unit/test_ichiprt1.cpp +++ b/INCHI-1-TEST/tests/test_unit/test_ichiprt1.cpp @@ -45,6 +45,84 @@ TEST(test_ichiprt1, test_OutputINCHI_StereoLayer) } +TEST(test_ichiprt1, test_OutputINCHI_StereoLayer_b_1) +{ + int num_at = 2; + int found_num_bonds = 0; + int found_num_isotopic = 0; + + CANON_GLOBALS cg = {0}; + INCHI_IOSTREAM out_file = {0}; + INCHI_IOS_STRING strbuf = {0}; + INCHI_OUT_CTL io = {0}; + INCHI_SORT inchi_sort = {0}; + inp_ATOM *atoms = CreateInpAtom(num_at); + + atoms[0].valence = 1; + // atoms[0].el_number = (U_CHAR)get_periodic_table_number("C"); + // atoms[0].orig_at_number = 4; + // atoms[0].neighbor[0] = 3; + // atoms[0].bond_type[0] = 2; + + atoms[1].valence = 1; + // atoms[1].el_number = (U_CHAR)get_periodic_table_number("C"); + // atoms[1].orig_at_number = 3; + // atoms[1].neighbor[0] = 4; + // atoms[1].bond_type[0] = 2; + + INChI *inchi = Alloc_INChI(atoms, num_at, &found_num_bonds, &found_num_isotopic, 0); + + inchi->nNumberOfAtoms = num_at; + + int bond_atom1[1] = {4}; + int bond_atom2[1] = {3}; + S_CHAR b_parity[1] = {1}; // 1 = -, 2 = + + + for (int i = 0; i < found_num_bonds; i++) { + inchi->Stereo->nBondAtom1[i] = bond_atom1[i]; + inchi->Stereo->nBondAtom2[i] = bond_atom2[i]; + inchi->Stereo->b_parity[i] = b_parity[i]; + } + + // inchi->Stereo->nCompInv2Abs = 1; + inchi->Stereo->nNumberOfStereoBonds = 1; + // inchi->Stereo->nNumberOfStereoCenters = 0; + + inchi_sort.pINChI[0] = inchi; + + io.pINChISort = &inchi_sort; + io.num_components = 1; + + // io.bRelativeStereo[0] = 0; + // io.bRacemicStereo[0] = 0; + + io.sDifSegs[0][DIFS_b_SBONDS] = DIFV_OUTPUT_FILL_T; // b + // io.sDifSegs[0][DIFS_t_SATOMS] = DIFV_OUTPUT_FILL_T; // t + // io.sDifSegs[0][DIFS_m_SP3INV] = DIFV_OUTPUT_FILL_T; // m + // io.sDifSegs[0][DIFS_s_STYPE] = DIFV_OUTPUT_FILL_T; // s + io.nCurINChISegment = 0; + io.iCurTautMode = 0; + io.bPlainTextTags = 2; + io.nTag = 2; + io.bTag1 = IL_STER; + io.bTag2 = IL_STER | IL_TYPS; + io.bOutType = OUT_TN; + + inchi_strbuf_init(&strbuf, INCHI_STRBUF_INITIAL_SIZE, INCHI_STRBUF_SIZE_INCREMENT); + inchi_ios_init(&out_file, INCHI_IOS_TYPE_STRING, nullptr); + + int ret = OutputINCHI_StereoLayer(&cg, &out_file, &strbuf, &io, (char*)"", (char*)""); + + EXPECT_EQ(std::string(out_file.s.pStr), "/b4-3-"); + EXPECT_EQ(ret, 0); + + inchi_strbuf_close(&strbuf); + inchi_ios_close(&out_file); + + FreeInpAtom(&atoms); + Free_INChI(&inchi); +} + TEST(test_ichiprt1, test_OutputINCHI_StereoLayer_m0) { CANON_GLOBALS cg = {0}; @@ -224,7 +302,6 @@ TEST(test_ichiprt1, test_OutputINCHI_StereoLayer_s3) io.sDifSegs[0][DIFS_s_STYPE] = DIFV_OUTPUT_FILL_T; - io.nCurINChISegment = 0; io.iCurTautMode = 0; io.bPlainTextTags = 1; @@ -248,32 +325,86 @@ TEST(test_ichiprt1, test_OutputINCHI_StereoLayer_s3) } -TEST(test_ichiprt1, test_OutputINCHI_StereoLayer_enh_stereo_1) +TEST(test_ichiprt1, test_OutputINCHI_StereoLayer_layers_t) { + + int num_at = 8; + int found_num_bonds = 0; + int found_num_isotopic = 0; + CANON_GLOBALS cg = {0}; INCHI_IOSTREAM out_file = {0}; INCHI_IOS_STRING strbuf = {0}; INCHI_OUT_CTL io = {0}; + INCHI_SORT inchi_sort = {0}; + AT_NUMB numbers[8] = {3,4,5,6,7,8,9,10}; + S_CHAR parities[8] = {1,1,2,2,1,2,2,1};// -,-,+,+,-,+,+,- + inp_ATOM *at = CreateInpAtom(num_at); + INChI *inchi = Alloc_INChI(at, num_at, &found_num_bonds, &found_num_isotopic, 0); - int num_at = 8; - int num_bonds = 0; - static AT_NUMB numbers[8] = {3,4,5,6,7,8,9,10}; - static S_CHAR parities[8] = {1,1,2,2,1,2,2,1};// -,-,+,+,-,+,+,- + inchi->nNumberOfAtoms = num_at; + inchi->Stereo->nCompInv2Abs = 1; + inchi->Stereo->nNumberOfStereoBonds = 0; + inchi->Stereo->nNumberOfStereoCenters = num_at; + + for (int i = 0; i < num_at; i++) { + inchi->Stereo->nNumber[i] = numbers[i]; + inchi->Stereo->t_parity[i] = parities[i]; + } + + inchi_sort.pINChI[0] = inchi; + + io.pINChISort = &inchi_sort; + io.num_components = 1; + io.bRelativeStereo[0] = 0; + io.bRacemicStereo[0] = 1; + // io.sDifSegs[0][DIFS_b_SBONDS] = DIFV_OUTPUT_FILL_T; // b + io.sDifSegs[0][DIFS_t_SATOMS] = DIFV_OUTPUT_FILL_T; // t + // io.sDifSegs[0][DIFS_m_SP3INV] = DIFV_OUTPUT_FILL_T; // m + // io.sDifSegs[0][DIFS_s_STYPE] = DIFV_OUTPUT_FILL_T; // s + io.nCurINChISegment = 0; + io.iCurTautMode = 0; + io.bPlainTextTags = 2; /* 0 => no plain tags, 1=> plain text tags, 2=>plaintext tags without consecutive // */ + io.nTag = 2; + io.bTag1 = IL_STER; + io.bTag2 = IL_STER | IL_TYPS; + io.bOutType = OUT_TN; + inchi_strbuf_init(&strbuf, INCHI_STRBUF_INITIAL_SIZE, INCHI_STRBUF_SIZE_INCREMENT); + inchi_ios_init(&out_file, INCHI_IOS_TYPE_STRING, nullptr); + + int ret = OutputINCHI_StereoLayer(&cg, &out_file, &strbuf, &io, (char*)"", (char*)""); + + EXPECT_EQ(std::string(out_file.s.pStr), "/t3-,4-,5+,6+,7-,8+,9+,10-"); + EXPECT_EQ(ret, 0); + inchi_strbuf_close(&strbuf); + inchi_ios_close(&out_file); + FreeInpAtom(&at); + Free_INChI(&inchi); +} - // INChI *inchi = new INChI; - // inchi->nNumberOfAtoms = num_at; - // inchi->Stereo = stereo; +TEST(test_ichiprt1, test_OutputINCHI_StereoLayer_layers_t_m_s) +{ + int num_at = 8; int found_num_bonds = 0; int found_num_isotopic = 0; + + CANON_GLOBALS cg = {0}; + INCHI_IOSTREAM out_file = {0}; + INCHI_IOS_STRING strbuf = {0}; + INCHI_OUT_CTL io = {0}; + INCHI_SORT inchi_sort = {0}; + AT_NUMB numbers[8] = {3,4,5,6,7,8,9,10}; + S_CHAR parities[8] = {1,1,2,2,1,2,2,1};// -,-,+,+,-,+,+,- inp_ATOM *at = CreateInpAtom(num_at); - static INChI *inchi = Alloc_INChI(at, num_at, &found_num_bonds, &found_num_isotopic, 0); + INChI *inchi = Alloc_INChI(at, num_at, &found_num_bonds, &found_num_isotopic, 0); inchi->nNumberOfAtoms = num_at; - + inchi->Stereo->nCompInv2Abs = 1; + inchi->Stereo->nNumberOfStereoBonds = 0; inchi->Stereo->nNumberOfStereoCenters = num_at; for (int i = 0; i < num_at; i++) { @@ -281,28 +412,19 @@ TEST(test_ichiprt1, test_OutputINCHI_StereoLayer_enh_stereo_1) inchi->Stereo->t_parity[i] = parities[i]; } - inchi->Stereo->nCompInv2Abs = 1; - inchi->Stereo->nNumberOfStereoBonds = 0; - inchi->Stereo->b_parity = NULL; - - - - static INCHI_SORT sort = {0}; - sort.pINChI[0] = inchi; - - io.pINChISort = &sort; + inchi_sort.pINChI[0] = inchi; + io.pINChISort = &inchi_sort; io.num_components = 1; io.bRelativeStereo[0] = 0; io.bRacemicStereo[0] = 1; - - io.sDifSegs[0][DIFS_b_SBONDS] = DIFV_OUTPUT_FILL_T; - io.sDifSegs[0][DIFS_t_SATOMS] = DIFV_OUTPUT_FILL_T; - io.sDifSegs[0][DIFS_m_SP3INV] = DIFV_OUTPUT_FILL_T; - io.sDifSegs[0][DIFS_s_STYPE] = DIFV_OUTPUT_FILL_T; + io.sDifSegs[0][DIFS_b_SBONDS] = DIFV_OUTPUT_FILL_T; // b + io.sDifSegs[0][DIFS_t_SATOMS] = DIFV_OUTPUT_FILL_T; // t + io.sDifSegs[0][DIFS_m_SP3INV] = DIFV_OUTPUT_FILL_T; // m + io.sDifSegs[0][DIFS_s_STYPE] = DIFV_OUTPUT_FILL_T; // s io.nCurINChISegment = 0; io.iCurTautMode = 0; - io.bPlainTextTags = 1; + io.bPlainTextTags = 2; /* 0 => no plain tags, 1=> plain text tags, 2=>plaintext tags without consecutive // */ io.nTag = 2; io.bTag1 = IL_STER; io.bTag2 = IL_STER | IL_TYPS; @@ -313,7 +435,7 @@ TEST(test_ichiprt1, test_OutputINCHI_StereoLayer_enh_stereo_1) int ret = OutputINCHI_StereoLayer(&cg, &out_file, &strbuf, &io, (char*)"", (char*)""); - EXPECT_EQ(std::string(out_file.s.pStr), "/t3-,4-,5+,6+,7-,8+,9+,10-/m0/s3/"); + EXPECT_EQ(std::string(out_file.s.pStr), "/t3-,4-,5+,6+,7-,8+,9+,10-/m0/s3"); EXPECT_EQ(ret, 0); inchi_strbuf_close(&strbuf); diff --git a/INCHI-1-TEST/tests/test_unit/test_ichiprt1_enhancedStereo.cpp b/INCHI-1-TEST/tests/test_unit/test_ichiprt1_enhancedStereo.cpp new file mode 100644 index 00000000..c33fda8d --- /dev/null +++ b/INCHI-1-TEST/tests/test_unit/test_ichiprt1_enhancedStereo.cpp @@ -0,0 +1,104 @@ +#include +#include +#include +#include +#include +#include +#include + +extern "C" +{ +#include "../../../INCHI-1-SRC/INCHI_BASE/src/ichi_io.h" +#include "../../../INCHI-1-SRC/INCHI_BASE/src/ichiprt1.c" +#include "../../../INCHI-1-SRC/INCHI_BASE/src/strutil.c" +#include "../../../INCHI-1-SRC/INCHI_BASE/src/ichimake.h" +#include "../../../INCHI-1-SRC/INCHI_BASE/src/ichicano.h" +} + + +TEST(test_ichiprt1_enhancedStereo, test_OutputINCHI_StereoLayer_enhanced_stereo_1) +{ + + CANON_GLOBALS cg = {0}; + INCHI_IOSTREAM out_file = {0}; + INCHI_IOS_STRING strbuf = {0}; + INCHI_OUT_CTL io = {0}; + ORIG_ATOM_DATA *oad; + + oad = (ORIG_ATOM_DATA *)inchi_calloc(1, sizeof(ORIG_ATOM_DATA)); + oad->v3000 = (OAD_V3000 *)inchi_calloc(1, sizeof(OAD_V3000)); + + oad->v3000->n_collections = 5; + + oad->v3000->n_steabs = 1; + + oad->v3000->lists_steabs = (int **)inchi_calloc(1, sizeof(int*)); + for (int i = 0; i < oad->v3000->n_steabs; i++) { + oad->v3000->lists_steabs[i] = (int *)inchi_calloc(1, sizeof(int)); + } + + // STEABS ATOMS=(2 4 5) + oad->v3000->lists_steabs[0][0] = 1; // - not used + oad->v3000->lists_steabs[0][1] = 2; // number of members in collection + oad->v3000->lists_steabs[0][2] = 4; // member atom numbers + oad->v3000->lists_steabs[0][3] = 5; // member atom numbers + + // STERAC2 ATOMS=(1 1) + oad->v3000->n_sterac = 2; + + oad->v3000->lists_sterac = (int **)inchi_calloc(1, sizeof(int*)); + for (int i = 0; i < oad->v3000->n_sterac; i++) { + oad->v3000->lists_sterac[i] = (int *)inchi_calloc(1, sizeof(int)); + } + + oad->v3000->lists_sterac[0][0] = 2; // n from "STERACn" tag + oad->v3000->lists_sterac[0][1] = 1; // number of members in collection + oad->v3000->lists_sterac[0][2] = 1; // member atom numbers + + // STERAC1 ATOMS=(2 2 3) + oad->v3000->lists_sterac[1][0] = 1; // STERAC1 ATOMS=(2 2 3) + oad->v3000->lists_sterac[1][1] = 2; // number of members in collection + oad->v3000->lists_sterac[1][2] = 2; // member atom numbers + oad->v3000->lists_sterac[1][3] = 3; // member atom numbers + + oad->v3000->n_sterel = 2; + oad->v3000->lists_sterel = (int **)inchi_calloc(1, sizeof(int*)); + for (int i = 0; i < oad->v3000->n_sterel; i++) { + oad->v3000->lists_sterel[i] = (int *)inchi_calloc(1, sizeof(int)); + } + + // STEREL1 ATOMS=(2 12 13) + oad->v3000->lists_sterel[0][0] = 1; // n from "STERELn" tag + oad->v3000->lists_sterel[0][1] = 2; // number of members in collection + oad->v3000->lists_sterel[0][2] = 12; // member atom numbers + oad->v3000->lists_sterel[0][3] = 13; // member atom numbers + + // STEREL2 ATOMS=(1 14) + oad->v3000->lists_sterel[1][0] = 2; // n from "STERELn" tag + oad->v3000->lists_sterel[1][1] = 1; // number of members in collection + oad->v3000->lists_sterel[1][2] = 14; // member atom numbers + + io.sDifSegs[0][DIFS_s_STYPE] = DIFV_OUTPUT_FILL_T; + + io.nCurINChISegment = 0; + io.iCurTautMode = 0; + io.bPlainTextTags = 1; + io.bRelativeStereo[0] = 0; + io.bRacemicStereo[0] = 0; + io.bAlways = 0; + io.nTag = 2; // plain text + io.bTag1 = IL_STER; + io.bTag2 = IL_STER | IL_TYPS; + + inchi_strbuf_init(&strbuf, INCHI_STRBUF_INITIAL_SIZE, INCHI_STRBUF_SIZE_INCREMENT); + inchi_ios_init(&out_file, INCHI_IOS_TYPE_STRING, nullptr); + + int ret = OutputINCHI_StereoLayer_EnhancedStereo(&cg, &out_file, &strbuf, &io, oad, (char*)"", (char*)""); + + EXPECT_EQ(std::string(strbuf.pStr), "/s1(4,5)2(12,13;14)3(1;2,3)"); + EXPECT_EQ(ret, 0); + + inchi_strbuf_close(&strbuf); + inchi_ios_close(&out_file); +} + diff --git a/INCHI-1-TEST/tests/test_unit/test_ichiprt2.cpp b/INCHI-1-TEST/tests/test_unit/test_ichiprt2.cpp index 2244420d..e9d9d4a8 100644 --- a/INCHI-1-TEST/tests/test_unit/test_ichiprt2.cpp +++ b/INCHI-1-TEST/tests/test_unit/test_ichiprt2.cpp @@ -21,6 +21,7 @@ TEST(test_ichiprt2, MakeStereoString_outputs_expected_sp3_string) EXPECT_EQ(strbuf.nUsedLength, strlen(strbuf.pStr)); EXPECT_NE(std::string(strbuf.pStr).find("3-,4-,5+,6+,7-,8+,9+,10-"), std::string::npos); + EXPECT_EQ(std::string(strbuf.pStr), "3-,4-,5+,6+,7-,8+,9+,10-"); EXPECT_EQ(strbuf.pStr[0], '3'); // Should start with 3- EXPECT_EQ(bOverflow, 0); EXPECT_EQ(ret, 24); @@ -73,3 +74,112 @@ TEST(test_ichiprt2, MakeMult_mult_eq_1_does_nothing) inchi_strbuf_close(&strbuf); } + +TEST(test_ichiprt2, Eql_INChI_Stereo_sp2_equal) +{ + // Setup two identical SP2 stereo objects + INChI_Stereo s1 = {0}, s2 = {0}; + int num_bonds = 2; + AT_NUMB bond_atom1[2] = {1, 3}; + AT_NUMB bond_atom2[2] = {2, 4}; + S_CHAR b_parity[2] = {1, 2}; + + s1.nNumberOfStereoBonds = num_bonds; + s1.nBondAtom1 = bond_atom1; + s1.nBondAtom2 = bond_atom2; + s1.b_parity = b_parity; + + s2.nNumberOfStereoBonds = num_bonds; + s2.nBondAtom1 = bond_atom1; + s2.nBondAtom2 = bond_atom2; + s2.b_parity = b_parity; + + // Should be equal + EXPECT_EQ(Eql_INChI_Stereo(&s1, EQL_SP2, &s2, EQL_SP2, 0), 1); +} + +TEST(test_ichiprt2, Eql_INChI_Stereo_sp2_not_equal) +{ + // Setup two different SP2 stereo objects + INChI_Stereo s1 = {0}, s2 = {0}; + AT_NUMB bond_atom1_1[2] = {1, 3}; + AT_NUMB bond_atom2_1[2] = {2, 4}; + S_CHAR b_parity_1[2] = {1, 2}; + + AT_NUMB bond_atom1_2[2] = {1, 5}; + AT_NUMB bond_atom2_2[2] = {2, 6}; + S_CHAR b_parity_2[2] = {1, 1}; + + s1.nNumberOfStereoBonds = 2; + s1.nBondAtom1 = bond_atom1_1; + s1.nBondAtom2 = bond_atom2_1; + s1.b_parity = b_parity_1; + + s2.nNumberOfStereoBonds = 2; + s2.nBondAtom1 = bond_atom1_2; + s2.nBondAtom2 = bond_atom2_2; + s2.b_parity = b_parity_2; + + // Should not be equal + EXPECT_EQ(Eql_INChI_Stereo(&s1, EQL_SP2, &s2, EQL_SP2, 0), 0); +} + +TEST(test_ichiprt2, Eql_INChI_Stereo_sp2_exists) +{ + // s1 has stereo, s2 is nullptr, eql2 is EQL_EXISTS + INChI_Stereo s1 = {0}; + AT_NUMB bond_atom1[1] = {1}; + AT_NUMB bond_atom2[1] = {2}; + S_CHAR b_parity[1] = {1}; + + s1.nNumberOfStereoBonds = 1; + s1.nBondAtom1 = bond_atom1; + s1.nBondAtom2 = bond_atom2; + s1.b_parity = b_parity; + + EXPECT_EQ(Eql_INChI_Stereo(&s1, EQL_SP2, nullptr, EQL_EXISTS, 0), 1); +} + +TEST(test_ichiprt2, Eql_INChI_Stereo_sp3_equal) +{ + // Setup two identical SP3 stereo objects + INChI_Stereo s1 = {0}, s2 = {0}; + int num_centers = 2; + AT_NUMB nNumber1[2] = {5, 6}; + S_CHAR t_parity1[2] = {1, 2}; + + s1.nNumberOfStereoCenters = num_centers; + s1.nNumber = nNumber1; + s1.t_parity = t_parity1; + + s2.nNumberOfStereoCenters = num_centers; + s2.nNumber = nNumber1; + s2.t_parity = t_parity1; + + // Should be equal + EXPECT_EQ(Eql_INChI_Stereo(&s1, EQL_SP3, &s2, EQL_SP3, 0), 1); +} + +TEST(test_ichiprt2, Eql_INChI_Stereo_sp3_not_equal) +{ + // Setup two different SP3 stereo objects + INChI_Stereo s1 = {0}, s2 = {0}; + int num_centers = 2; + AT_NUMB nNumber1[2] = {5, 6}; + S_CHAR t_parity1[2] = {1, 2}; + + AT_NUMB nNumber2[2] = {7, 8}; + S_CHAR t_parity2[2] = {2, 1}; + + s1.nNumberOfStereoCenters = num_centers; + s1.nNumber = nNumber1; + s1.t_parity = t_parity1; + + s2.nNumberOfStereoCenters = num_centers; + s2.nNumber = nNumber2; + s2.t_parity = t_parity2; + + // Should not be equal + EXPECT_EQ(Eql_INChI_Stereo(&s1, EQL_SP3, &s2, EQL_SP3, 0), 0); +} + diff --git a/INCHI-1-TEST/tests/test_unit/test_ichiprt3.cpp b/INCHI-1-TEST/tests/test_unit/test_ichiprt3.cpp index be614485..c98a9b9f 100644 --- a/INCHI-1-TEST/tests/test_unit/test_ichiprt3.cpp +++ b/INCHI-1-TEST/tests/test_unit/test_ichiprt3.cpp @@ -9,10 +9,132 @@ extern "C" #include "../../../INCHI-1-SRC/INCHI_BASE/src/ichimake.h" } +TEST(test_ichiprt3, test_str_Sp2_outputs_expected_sp2_string_1) +{ + int num_at = 2; + int num_bonds = 1; + int bond_atom1[1] = {4}; + int bond_atom2[1] = {3}; + S_CHAR b_parity[1] = {1}; // 1 = -, 2 = + + + INChI_Stereo* stereo = Alloc_INChI_Stereo(num_at, num_bonds); + stereo->nNumberOfStereoBonds = num_bonds; + for (int i = 0; i < num_bonds; i++) { + stereo->nBondAtom1[i] = bond_atom1[i]; + stereo->nBondAtom2[i] = bond_atom2[i]; + stereo->b_parity[i] = b_parity[i]; + } + + INChI inchi = {0}; + inchi.nNumberOfAtoms = num_at; + inchi.Stereo = stereo; + + INCHI_SORT inchi_sort = {0}; + inchi_sort.pINChI[0] = &inchi; + + INCHI_IOS_STRING strbuf = {0}; + inchi_strbuf_init(&strbuf, INCHI_STRBUF_INITIAL_SIZE, INCHI_STRBUF_SIZE_INCREMENT); + + int bOverflow = 0; + int bOutputType = OUT_N1; // OUT_TN + int taut_mode = 0; // CT_MODE_ABC_NUMBERS + int num_components = 1; + + int bSecondNonTautPass = 0; + int bOmitRepetitions = 0; + int bUseMulipliers = 1; + + int ret = str_Sp2( + &inchi_sort, + NULL, + &strbuf, + &bOverflow, + bOutputType, + taut_mode, + num_components, + bSecondNonTautPass, + bOmitRepetitions, + bUseMulipliers + ); + + // The expected output is "1-2Z,3-4E" + EXPECT_EQ(strbuf.nUsedLength, strlen(strbuf.pStr)); + EXPECT_EQ(std::string(strbuf.pStr), "4-3-"); + EXPECT_EQ(strbuf.pStr[0], '4'); + EXPECT_EQ(bOverflow, 0); + + // The return value should be the length of the output string + EXPECT_EQ(ret, 4); + + inchi_strbuf_close(&strbuf); + Free_INChI_Stereo(stereo); +} + +TEST(test_ichiprt3, test_str_Sp2_outputs_expected_sp2_string_2) +{ + int num_at = 4; + int num_bonds = 2; + int bond_atom1[2] = {1, 3}; + int bond_atom2[2] = {2, 4}; + S_CHAR b_parity[2] = {1, 2}; // 1 = -, 2 = + + + INChI_Stereo* stereo = Alloc_INChI_Stereo(num_at, num_bonds); + stereo->nNumberOfStereoBonds = num_bonds; + for (int i = 0; i < num_bonds; i++) { + stereo->nBondAtom1[i] = bond_atom1[i]; + stereo->nBondAtom2[i] = bond_atom2[i]; + stereo->b_parity[i] = b_parity[i]; + } + + INChI inchi = {0}; + inchi.nNumberOfAtoms = num_at; + inchi.Stereo = stereo; + + INCHI_SORT inchi_sort = {0}; + inchi_sort.pINChI[0] = &inchi; + + INCHI_IOS_STRING strbuf = {0}; + inchi_strbuf_init(&strbuf, INCHI_STRBUF_INITIAL_SIZE, INCHI_STRBUF_SIZE_INCREMENT); + + int bOverflow = 0; + int bOutputType = OUT_N1; // OUT_TN + int taut_mode = 0; // CT_MODE_ABC_NUMBERS + int num_components = 1; + + int bSecondNonTautPass = 0; + int bOmitRepetitions = 0; + int bUseMulipliers = 1; + + int ret = str_Sp2( + &inchi_sort, + NULL, + &strbuf, + &bOverflow, + bOutputType, + taut_mode, + num_components, + bSecondNonTautPass, + bOmitRepetitions, + bUseMulipliers + ); + + // The expected output is "1-2Z,3-4E" + EXPECT_EQ(strbuf.nUsedLength, strlen(strbuf.pStr)); + EXPECT_EQ(std::string(strbuf.pStr), "1-2-,3-4+"); + EXPECT_EQ(strbuf.pStr[0], '1'); + EXPECT_EQ(bOverflow, 0); + + // The return value should be the length of the output string + EXPECT_EQ(ret, 9); + + inchi_strbuf_close(&strbuf); + Free_INChI_Stereo(stereo); +} + TEST(test_ichiprt3, test_str_Sp3_outputs_expected_sp3_string) { int numbers[8] = {3,4,5,6,7,8,9,10}; - S_CHAR parities[8] = {1,1,2,2,1,2,2,1}; + S_CHAR parities[8] = {1,1,2,2,1,2,2,1}; // 1 = -, 2 = + int num_at = 8; static INChI_Stereo *stereo = Alloc_INChI_Stereo( num_at, 0 ); stereo->nNumberOfStereoCenters = num_at; @@ -31,21 +153,27 @@ TEST(test_ichiprt3, test_str_Sp3_outputs_expected_sp3_string) inchi.nNumberOfAtoms = 10; inchi.Stereo = stereo; - INCHI_SORT sort = {0}; - sort.pINChI[0] = &inchi; + INCHI_SORT inchi_sort = {0}; + inchi_sort.pINChI[0] = &inchi; INCHI_IOS_STRING strbuf = {0}; inchi_strbuf_init(&strbuf, INCHI_STRBUF_INITIAL_SIZE, INCHI_STRBUF_SIZE_INCREMENT); int bOverflow = 0; - int ret = str_Sp3(&sort, NULL, &strbuf, &bOverflow, 0, 0, 1, 0, 0, 0, 0); + int num_components = 1; + + //io->bRelRac = io->bRelativeStereo[io->iCurTautMode] || io->bRacemicStereo[io->iCurTautMode]; + int bIsotopicRelativeStereo = 0; // INCHI_FLAG_REL_STEREO + int bIsotopicRacemicStereo = 0; // INCHI_FLAG_RAC_STEREO + + int bRelRac = bIsotopicRelativeStereo || bIsotopicRacemicStereo; + + int ret = str_Sp3(&inchi_sort, NULL, &strbuf, &bOverflow, 0, 0, num_components, bRelRac, 0, 0, 0); - // Check that strbuf contains the expected substring EXPECT_EQ(strbuf.nUsedLength, strlen(strbuf.pStr)); - // EXPECT_NE(std::string(strbuf.pStr).find("3-,4-,5+,6+,7-,8+,9+,10-"), std::string::npos); EXPECT_EQ(std::string(strbuf.pStr), "3-,4-,5+,6+,7-,8+,9+,10-"); - EXPECT_EQ(strbuf.pStr[0], '3'); // Should start with 3- + EXPECT_EQ(strbuf.pStr[0], '3'); EXPECT_EQ(bOverflow, 0); EXPECT_EQ(ret, 24); From 892521aebfb366743541bb164a218c5c1b5a21e0 Mon Sep 17 00:00:00 2001 From: Christoph Mueller Date: Fri, 9 Jan 2026 14:40:58 +0000 Subject: [PATCH 10/44] Added functions for changing parities based on abs, rel, rac --- INCHI-1-SRC/INCHI_BASE/src/ichimain.h | 2 +- INCHI-1-SRC/INCHI_BASE/src/ichimake.h | 5 +- INCHI-1-SRC/INCHI_BASE/src/ichiprt1.c | 94 ++++++++++--------- INCHI-1-SRC/INCHI_BASE/src/ichiprt2.c | 34 ++++++- INCHI-1-SRC/INCHI_BASE/src/strutil.c | 88 ++++++++++++++--- INCHI-1-SRC/INCHI_BASE/src/strutil.h | 22 +++++ INCHI-1-SRC/INCHI_BASE/src/util.c | 85 +++++++++-------- .../test_ichiprt1_enhancedStereo.cpp | 39 +++++++- .../tests/test_unit/test_ichiprt3.cpp | 6 +- 9 files changed, 263 insertions(+), 112 deletions(-) diff --git a/INCHI-1-SRC/INCHI_BASE/src/ichimain.h b/INCHI-1-SRC/INCHI_BASE/src/ichimain.h index 779d6b3e..c7f45018 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/ichimain.h +++ b/INCHI-1-SRC/INCHI_BASE/src/ichimain.h @@ -125,7 +125,7 @@ typedef struct tagINCHI_OUT_CTL int iCurTautMode; int num_components; - int nNumRemovedProtons; + int nNumRemovedProtons; int nTag; int bTag1; int bTag2; diff --git a/INCHI-1-SRC/INCHI_BASE/src/ichimake.h b/INCHI-1-SRC/INCHI_BASE/src/ichimake.h index 0669703b..03d55237 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/ichimake.h +++ b/INCHI-1-SRC/INCHI_BASE/src/ichimake.h @@ -208,7 +208,10 @@ const char *EquString( int EquVal ); INCHI_IOS_STRING *buf, int nCtMode, int *bOverflow ); - int MakeEnhStereoString( int **enh_stereo, + int MakeEnhStereoString( INCHI_SORT *pINChISort, + int bOutType, + int num_components, + int **enh_stereo, int nof_units, INCHI_IOS_STRING *strbuf, int nCtMode, diff --git a/INCHI-1-SRC/INCHI_BASE/src/ichiprt1.c b/INCHI-1-SRC/INCHI_BASE/src/ichiprt1.c index da530ed5..928f7fb9 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/ichiprt1.c +++ b/INCHI-1-SRC/INCHI_BASE/src/ichiprt1.c @@ -1637,6 +1637,11 @@ int OutputINChI1( CANON_GLOBALS *pCG, io.bChargesRadVal[ii] |= 1; } } + + if (ip->bEnhancedStereo) + { + set_EnhancedStereo_t_m_layers(orig_inp_data, pINChI, pINChI_Aux); + } } } if (bCompExists) @@ -1775,14 +1780,11 @@ int OutputINChI1( CANON_GLOBALS *pCG, } /* InChI output: stereo (non-isotopic) */ - // intermediate_result = OutputINCHI_StereoLayer( pCG, out_file, strbuf, &io, pLF, pTAB ); - if (ip->bEnhancedStereo) { intermediate_result = OutputINCHI_StereoLayer_EnhancedStereo( pCG, out_file, strbuf, &io, orig_inp_data, pLF, pTAB ); } else { intermediate_result = OutputINCHI_StereoLayer( pCG, out_file, strbuf, &io, pLF, pTAB ); } - if (intermediate_result != 0) goto exit_function; @@ -3396,9 +3398,9 @@ int OutputINCHI_StereoLayer( CANON_GLOBALS *pCG, { if (INChI_SegmentAction( io->sDifSegs[io->nCurINChISegment][DIFS_b_SBONDS] ) || - INChI_SegmentAction( io->sDifSegs[io->nCurINChISegment][DIFS_t_SATOMS] ) || - INChI_SegmentAction( io->sDifSegs[io->nCurINChISegment][DIFS_m_SP3INV] ) || - INChI_SegmentAction( io->sDifSegs[io->nCurINChISegment][DIFS_s_STYPE] )) + INChI_SegmentAction( io->sDifSegs[io->nCurINChISegment][DIFS_t_SATOMS] ) || + INChI_SegmentAction( io->sDifSegs[io->nCurINChISegment][DIFS_m_SP3INV] ) || + INChI_SegmentAction( io->sDifSegs[io->nCurINChISegment][DIFS_s_STYPE] )) { /* stereo */ @@ -3415,8 +3417,8 @@ int OutputINCHI_StereoLayer( CANON_GLOBALS *pCG, if (INCHI_SEGM_FILL == io->nSegmAction) { io->tot_len = str_Sp2( io->pINChISort, io->pINChISort2, strbuf, &io->bOverflow, - io->bOutType, io->TAUT_MODE, io->num_components, - io->bSecondNonTautPass, io->bOmitRepetitions, io->bUseMulipliers ); + io->bOutType, io->TAUT_MODE, io->num_components, + io->bSecondNonTautPass, io->bOmitRepetitions, io->bUseMulipliers ); io->bNonTautNonIsoIdentifierNotEmpty += io->bSecondNonTautPass; } @@ -3448,7 +3450,7 @@ int OutputINCHI_StereoLayer( CANON_GLOBALS *pCG, { io->tot_len = str_Sp3( io->pINChISort, io->pINChISort2, strbuf, &io->bOverflow, io->bOutType, io->TAUT_MODE, io->num_components, io->bRelRac, - io->bSecondNonTautPass, io->bOmitRepetitions, io->bUseMulipliers ); + io->bSecondNonTautPass, io->bOmitRepetitions, io->bUseMulipliers ); io->bNonTautNonIsoIdentifierNotEmpty += io->bSecondNonTautPass; } @@ -3475,7 +3477,7 @@ int OutputINCHI_StereoLayer( CANON_GLOBALS *pCG, if (INCHI_SEGM_FILL == io->nSegmAction) { io->tot_len = str_StereoAbsInv( io->pINChISort, strbuf, - &io->bOverflow, io->bOutType, io->num_components ); + &io->bOverflow, io->bOutType, io->num_components ); io->bNonTautNonIsoIdentifierNotEmpty += io->bSecondNonTautPass; } @@ -3541,19 +3543,20 @@ int OutputINCHI_StereoLayer( CANON_GLOBALS *pCG, * @param pTAB Pointer to a string used as the tab or separator character(s). * @return Returns 0 on success, or a non-zero error code on failure. */ -int OutputINCHI_StereoLayer_EnhancedStereo( CANON_GLOBALS *pCG, - INCHI_IOSTREAM *out_file, - INCHI_IOS_STRING *strbuf, - INCHI_OUT_CTL *io, - ORIG_ATOM_DATA *orig_inp_data, - char *pLF, - char *pTAB ) +int OutputINCHI_StereoLayer_EnhancedStereo( + CANON_GLOBALS *pCG, + INCHI_IOSTREAM *out_file, + INCHI_IOS_STRING *strbuf, + INCHI_OUT_CTL *io, + ORIG_ATOM_DATA *orig_inp_data, + char *pLF, + char *pTAB ) { if (INChI_SegmentAction( io->sDifSegs[io->nCurINChISegment][DIFS_b_SBONDS] ) || - INChI_SegmentAction( io->sDifSegs[io->nCurINChISegment][DIFS_t_SATOMS] ) || - INChI_SegmentAction( io->sDifSegs[io->nCurINChISegment][DIFS_m_SP3INV] ) || - INChI_SegmentAction( io->sDifSegs[io->nCurINChISegment][DIFS_s_STYPE] )) + INChI_SegmentAction( io->sDifSegs[io->nCurINChISegment][DIFS_t_SATOMS] ) || + INChI_SegmentAction( io->sDifSegs[io->nCurINChISegment][DIFS_m_SP3INV] ) || + INChI_SegmentAction( io->sDifSegs[io->nCurINChISegment][DIFS_s_STYPE] )) { /* stereo */ @@ -3570,8 +3573,8 @@ int OutputINCHI_StereoLayer_EnhancedStereo( CANON_GLOBALS *pCG, if (INCHI_SEGM_FILL == io->nSegmAction) { io->tot_len = str_Sp2( io->pINChISort, io->pINChISort2, strbuf, &io->bOverflow, - io->bOutType, io->TAUT_MODE, io->num_components, - io->bSecondNonTautPass, io->bOmitRepetitions, io->bUseMulipliers ); + io->bOutType, io->TAUT_MODE, io->num_components, + io->bSecondNonTautPass, io->bOmitRepetitions, io->bUseMulipliers ); io->bNonTautNonIsoIdentifierNotEmpty += io->bSecondNonTautPass; } @@ -3603,7 +3606,7 @@ int OutputINCHI_StereoLayer_EnhancedStereo( CANON_GLOBALS *pCG, { io->tot_len = str_Sp3( io->pINChISort, io->pINChISort2, strbuf, &io->bOverflow, io->bOutType, io->TAUT_MODE, io->num_components, io->bRelRac, - io->bSecondNonTautPass, io->bOmitRepetitions, io->bUseMulipliers ); + io->bSecondNonTautPass, io->bOmitRepetitions, io->bUseMulipliers ); io->bNonTautNonIsoIdentifierNotEmpty += io->bSecondNonTautPass; } @@ -3670,33 +3673,36 @@ int OutputINCHI_StereoLayer_EnhancedStereo( CANON_GLOBALS *pCG, if (INCHI_SEGM_FILL == io->nSegmAction) { io->tot_len += MakeDelim( x_abs, strbuf, &io->bOverflow ); // s1 - io->tot_len += MakeEnhStereoString( - orig_inp_data->v3000->lists_steabs, - orig_inp_data->v3000->n_steabs, - strbuf, - 0, - &io->bOverflow - ); + io->tot_len += MakeEnhStereoString( io->pINChISort, + io->bOutType, + io->num_components, + orig_inp_data->v3000->lists_steabs, + orig_inp_data->v3000->n_steabs, + strbuf, + 0, + &io->bOverflow); io->tot_len += MakeDelim( x_rel, strbuf, &io->bOverflow ); // s2 - io->tot_len += MakeEnhStereoString( - orig_inp_data->v3000->lists_sterel, - orig_inp_data->v3000->n_sterel, - strbuf, - 0, - &io->bOverflow - ); + io->tot_len += MakeEnhStereoString( io->pINChISort, + io->bOutType, + io->num_components, + orig_inp_data->v3000->lists_sterel, + orig_inp_data->v3000->n_sterel, + strbuf, + 0, + &io->bOverflow); io->tot_len += MakeDelim( x_rac, strbuf, &io->bOverflow ); // s3 - io->tot_len += MakeEnhStereoString( - orig_inp_data->v3000->lists_sterac, - orig_inp_data->v3000->n_sterac, - strbuf, - 0, - &io->bOverflow - ); + io->tot_len += MakeEnhStereoString( io->pINChISort, + io->bOutType, + io->num_components, + orig_inp_data->v3000->lists_sterac, + orig_inp_data->v3000->n_sterac, + strbuf, + 0, + &io->bOverflow); io->bNonTautNonIsoIdentifierNotEmpty += io->bSecondNonTautPass; } diff --git a/INCHI-1-SRC/INCHI_BASE/src/ichiprt2.c b/INCHI-1-SRC/INCHI_BASE/src/ichiprt2.c index 2a40880e..0960fdc1 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/ichiprt2.c +++ b/INCHI-1-SRC/INCHI_BASE/src/ichiprt2.c @@ -2159,20 +2159,46 @@ int MakeStereoString( AT_NUMB *at1, return nLen; } -int MakeEnhStereoString( int **enh_stereo, +int MakeEnhStereoString( INCHI_SORT *pINChISort, + int bOutType, + int num_components, + int **enh_stereo, int nof_units, INCHI_IOS_STRING *strbuf, int nCtMode, int *bOverflow ) { int tot_len = 0; + int ii, nUsedLength0; + INCHI_SORT *is, *is0; + INChI_Stereo *Stereo; + INChI *pINChI; + INChI_Aux *pAux; + + if (nof_units <= 0) { + return 0; + } + + is = NULL; + is0 = pINChISort; + + /* For each connected component... */ + for (int cur_c = 0; !*bOverflow && cur_c < num_components; cur_c++) + { + + is = is0 + cur_c; + // pINChI = ( 0 <= ( ii = GET_II( bOutType, is ) ) ) ? is->pINChI[ii] : NULL; + pAux = ( 0 <= ( ii = GET_II( bOutType, is ) ) ) ? is->pINChI_Aux[ii] : NULL; - if (nof_units > 0) { tot_len += MakeDelim( "(", strbuf, bOverflow ); for (int i = 0; i < nof_units; i++) { for (int j = 0; j < enh_stereo[i][1]; j++) { - MakeMult_EnhStereo( enh_stereo[i][2 + j], "", strbuf, nCtMode, bOverflow ); + int orig_atom_num = enh_stereo[i][2 + j]; + int canon_atom_num = get_canonical_atom_number(pAux, orig_atom_num); + + //tot_len += MakeMult_EnhStereo( enh_stereo[i][2 + j], "", strbuf, nCtMode, bOverflow ); + tot_len += MakeMult_EnhStereo( canon_atom_num, "", strbuf, nCtMode, bOverflow ); if ((j + 1) < enh_stereo[i][1]) { tot_len += MakeDelim( ",", strbuf, bOverflow ); @@ -2183,7 +2209,9 @@ int MakeEnhStereoString( int **enh_stereo, } } tot_len += MakeDelim( ")", strbuf, bOverflow ); + } + return tot_len; } diff --git a/INCHI-1-SRC/INCHI_BASE/src/strutil.c b/INCHI-1-SRC/INCHI_BASE/src/strutil.c index 89f8d7d1..4df9e9ed 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/strutil.c +++ b/INCHI-1-SRC/INCHI_BASE/src/strutil.c @@ -1096,12 +1096,12 @@ int remove_ion_pairs( int num_atoms, inp_ATOM *at ) num_N_minus++; } #ifdef FIX_P_IV_Plus_O_Minus - num_P_IV_plus += a->el_number != EL_NUMBER_N && + num_P_IV_plus += a->el_number != EL_NUMBER_N && chrg == 1 && - a->valence == 4 && + a->valence == 4 && a->chem_bonds_valence == 4; /* added 2010-03-17 DT */ -#endif - break; +#endif + break; } } else if (!chrg && a->chem_bonds_valence + NUMH( a, 0 ) == 2 && @@ -3674,7 +3674,7 @@ int move_explicit_Hcation( inp_ATOM *at, /* connect H to at[iat] */ val = at[iat].valence; - + #pragma warning (push) #pragma warning (disable: 6386) if (val < MAXVAL) @@ -4269,6 +4269,70 @@ int cmp_components( const void *a1, const void *a2 ) return ret; } +int get_canonical_atom_number( INChI_Aux *aux, + int orig_atom_num) +{ + for (int canon_num = 1; canon_num <= aux->nNumberOfAtoms; canon_num++) { + if (aux->nOrigAtNosInCanonOrd[canon_num - 1] == orig_atom_num) { + return canon_num; // Found canonical atom number + } + } + return -1; // Not found +} + + +int set_EnhancedStereo_t_m_layers( ORIG_ATOM_DATA *orig_inp_data, + INChI *inchi, + INChI_Aux *aux) +{ + int ret = 0; + + if (orig_inp_data->v3000->n_steabs > 0) + { + + int **enh_stereo = orig_inp_data->v3000->lists_steabs; + for (int i = 0; i < orig_inp_data->v3000->n_steabs; i++) { + for (int j = 0; j < enh_stereo[i][1]; j++) { + int orig_atom_num = enh_stereo[i][2 + j]; + AT_NUMB canon_atom_num = (AT_NUMB)get_canonical_atom_number(aux, orig_atom_num); + + const AT_NUMB *atom_idx_pos = is_in_the_list(inchi->Stereo->nNumber, canon_atom_num, inchi->Stereo->nNumberOfStereoCenters); + if (atom_idx_pos) + { + int idx = (int)(*atom_idx_pos); + if (inchi->Stereo->t_parity[idx] == 2) + { + inchi->Stereo->t_parity[idx] = 1; + inchi->Stereo->nCompInv2Abs = -1; + } + } + } + } + } + // orig_inp_data->v3000->n_steabs; + // orig_inp_data->v3000->lists_steabs; + + // pINChI->Stereo->nCompInv2Abs; + // -1 --> 1 + // 1 --> 0 + + // orig_inp_data->v3000->n_sterel; + // orig_inp_data->v3000->lists_sterel; + + // orig_inp_data->v3000->n_sterac; + // orig_inp_data->v3000->lists_sterac; + + // get_canonical_atom_number; + + + // pINChI->Stereo->nNumber; + // pINChI->Stereo->t_parity + // 1 --> - + // 2 --> + + + + return ret; +} /**************************************************************************** Set the (disconnected) component numbers in ORIG_ATOM_DATA 'at[*].component' @@ -4324,7 +4388,7 @@ int MarkDisconnectedComponents( ORIG_ATOM_DATA *orig_at_data, nPrevAtom = (AT_NUMB*)inchi_calloc(num_at, sizeof(nPrevAtom[0])); iNeigh = (S_CHAR*)inchi_calloc(num_at, sizeof(iNeigh[0])); - if (!nNewCompNumber || !nPrevAtom || !iNeigh) /* nNewCompNumber: for non-recursive DFS only: */ + if (!nNewCompNumber || !nPrevAtom || !iNeigh) /* nNewCompNumber: for non-recursive DFS only: */ { goto exit_function; } @@ -4333,7 +4397,7 @@ int MarkDisconnectedComponents( ORIG_ATOM_DATA *orig_at_data, /* Mark and count; avoid deep DFS recursion: it may make verifying software unhappy */ /* nNewCompNumber[i] will contain new component number for atoms at[i], i=0..num_at-1 */ - + for (j = 0; j < num_at; j++) { if (!nNewCompNumber[j]) @@ -4709,7 +4773,7 @@ int Free_INChI_Members( INChI *pINChI ) qzfree(pINChI->IsotopicAtom); qzfree(pINChI->IsotopicTGroup); qzfree(pINChI->nPossibleLocationsOfIsotopicH); - qzfree( pINChI->Stereo ); + qzfree( pINChI->Stereo ); qzfree( pINChI->StereoIsotopic ); qzfree( pINChI->szHillFormula ); } @@ -5004,7 +5068,7 @@ void CompAtomData_GetNumMapping( COMP_ATOM_DATA *adata, int *orig_num, int *curr ****************************************************************************/ int imat_new( int m, int n, int ***a ) { - int i; + int i; if (m == 0 || n == 0) { return 0; @@ -5106,7 +5170,7 @@ subgraf *subgraf_new( ORIG_ATOM_DATA *orig_inp_data, sg->orig2node[sg->nodes[i]] = i; } - /* Create and fill subgraph adjacency matrix based on nodes/orig atom numbers + /* Create and fill subgraph adjacency matrix based on nodes/orig atom numbers and connections stored in orig_inp_data */ sg->adj = (subgraf_edge **) inchi_calloc( nnodes, sizeof( subgraf_edge * ) ); if (!sg->adj) @@ -5266,7 +5330,7 @@ void subgraf_pathfinder_free( subgraf_pathfinder *spf ) /**************************************************************************** - Find path(s) from subgraf node spf->start to spf->end + Find path(s) from subgraf node spf->start to spf->end and fill bonds[nbonds] and atoms[natoms] Do not traverse through supplied forbidden edges (if not zero/NULL) ****************************************************************************/ @@ -5298,7 +5362,7 @@ void subgraf_pathfinder_run( subgraf_pathfinder *spf, continue; } if (nforbidden && forbidden) - { + { skip = 0; for (f = 0; f < nforbidden; f++) { diff --git a/INCHI-1-SRC/INCHI_BASE/src/strutil.h b/INCHI-1-SRC/INCHI_BASE/src/strutil.h index 2fbec84b..b57aed17 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/strutil.h +++ b/INCHI-1-SRC/INCHI_BASE/src/strutil.h @@ -57,6 +57,28 @@ extern "C" struct tagTautomerGroupsInfo; struct tagCANON_GLOBALS; + /** + * @brief Set the enhanced stereochemistry for t- and m-layers + * + * @param orig_inp_data + * @param inchi + * @param aux + * @return int + */ + int set_EnhancedStereo_t_m_layers(ORIG_ATOM_DATA *orig_inp_data, + INChI *inchi, + INChI_Aux *aux); + + /** + * @brief Get the canonical atom number object + * + * @param aux Pointer to INChI_Aux data structure + * @param orig_atom_num Original atom number + * @return Returns canonical atom number if found, -1 if not + */ + int get_canonical_atom_number(INChI_Aux *aux, + int orig_atom_num); + /** * @brief Extract one (connected) component * diff --git a/INCHI-1-SRC/INCHI_BASE/src/util.c b/INCHI-1-SRC/INCHI_BASE/src/util.c index 72a518bb..e72f89e8 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/util.c +++ b/INCHI-1-SRC/INCHI_BASE/src/util.c @@ -263,10 +263,10 @@ const ELDATA ElData[] = { "Zy", 0, 0, 0.000000000, 0 , 0, 1, {{0,}, {0,}, {1,}, {0,}, {0,} }}, { "Zz", 0, 0, 0.000000000, 0 , 0, 1, {{0,}, {0,}, {1,}, {0,}, {0,} }}, #ifdef INCHI_ZFRAG -{ "Zu", 0, 0, 0.000000000, 0 , 0, 1, {{0,}, {0,}, {1,}, {0,}, {0,} }}, -{ "Zv", 0, 0, 0.000000000, 0 , 0, 1, {{0,}, {0,}, {2,}, {0,}, {0,} }}, -{ "Zw", 0, 0, 0.000000000, 0 , 0, 1, {{0,}, {0,}, {3,}, {0,}, {0,} }}, -{ "Zx", 0, 0, 0.000000000, 0 , 0, 1, {{0,}, {0,}, {1,2,}, {0,}, {0,} }}, +{ "Zu", 0, 0, 0.000000000, 0 , 0, 1, {{0,}, {0,}, {1,}, {0,}, {0,} }}, +{ "Zv", 0, 0, 0.000000000, 0 , 0, 1, {{0,}, {0,}, {2,}, {0,}, {0,} }}, +{ "Zw", 0, 0, 0.000000000, 0 , 0, 1, {{0,}, {0,}, {3,}, {0,}, {0,} }}, +{ "Zx", 0, 0, 0.000000000, 0 , 0, 1, {{0,}, {0,}, {1,2,}, {0,}, {0,} }}, #endif { "", 0, 0, 0.000000000, 0 , 0, 0, {{0,}, {0,}, {0,}, {0,}, {0,} }}, @@ -376,36 +376,36 @@ int get_periodic_table_number( const char* elname ) } /* the single letter (common) elements */ - if (!elname[1]) + if (!elname[1]) { - switch (elname[0]) + switch (elname[0]) { - case 'H': - return EL_NUMBER_H; + case 'H': + return EL_NUMBER_H; break; - case 'B': - return EL_NUMBER_B; + case 'B': + return EL_NUMBER_B; break; - case 'C': - return EL_NUMBER_C; + case 'C': + return EL_NUMBER_C; break; - case 'N': - return EL_NUMBER_N; + case 'N': + return EL_NUMBER_N; break; - case 'O': - return EL_NUMBER_O; + case 'O': + return EL_NUMBER_O; break; - case 'P': - return EL_NUMBER_P; + case 'P': + return EL_NUMBER_P; break; - case 'S': - return EL_NUMBER_S; + case 'S': + return EL_NUMBER_S; break; - case 'F': - return EL_NUMBER_F; + case 'F': + return EL_NUMBER_F; break; - case 'I': - return EL_NUMBER_I; + case 'I': + return EL_NUMBER_I; break; } } @@ -1065,7 +1065,6 @@ AT_NUMB *is_in_the_list( AT_NUMB *pathAtom, AT_NUMB nNextAtom, int nPathLen ) return nPathLen ? pathAtom : NULL; } - /**************************************************************************** Check if integer is in the list ****************************************************************************/ @@ -1150,12 +1149,12 @@ int num_of_H( inp_ATOM *at, int iat ) /****************************************************************************/ U_CHAR ion_el_group( int el ) { - switch ( el ) + switch ( el ) { case EL_NUMBER_C: /* fallthrough */ -#if ( FIX_REM_ION_PAIRS_Si_BUG == 1 ) +#if ( FIX_REM_ION_PAIRS_Si_BUG == 1 ) case EL_NUMBER_SI: -#endif +#endif return EL_NUMBER_C; case EL_NUMBER_N: /* fallthrough */ case EL_NUMBER_P: @@ -1507,17 +1506,17 @@ int MakeRemovedProtonsString( int nNumRemovedProtons, /****************************************************************************/ int get_endpoint_valence( U_CHAR el_number ) -{ - switch (el_number) +{ + switch (el_number) { case EL_NUMBER_O: /* fallthrough */ - case EL_NUMBER_S: - case EL_NUMBER_SE: - case EL_NUMBER_TE: + case EL_NUMBER_S: + case EL_NUMBER_SE: + case EL_NUMBER_TE: return 2; - case EL_NUMBER_N: + case EL_NUMBER_N: return 3; - default: + default: return 0; } } @@ -1529,13 +1528,13 @@ int get_endpoint_valence( U_CHAR el_number ) /****************************************************************************/ int get_endpoint_valence_KET( U_CHAR el_number ) { - switch (el_number) + switch (el_number) { - case EL_NUMBER_C: + case EL_NUMBER_C: return 4; - case EL_NUMBER_O: + case EL_NUMBER_O: return 2; - default: + default: return 0; } } @@ -1768,7 +1767,7 @@ int mystrncpy( char *target, const char *source, unsigned maxlen ) } /* giallu: PR #163 */ - /* Find actual source length first to limit memchr search */ + /* Find actual source length first to limit memchr search */ source_len = (unsigned)strlen(source); if (source_len < maxlen) @@ -1777,12 +1776,12 @@ int mystrncpy( char *target, const char *source, unsigned maxlen ) len = source_len; } else if ((p = (const char*)memchr(source, 0, maxlen))) /* djb-rwth: addressing LLVM warning */ - { + { /* maxlen does not include the found zero termination */ len = (int) ( p - source ); } else - { + { /* reduced length does not include one more byte for zero termination */ len = maxlen - 1; } @@ -1816,7 +1815,7 @@ char* lrtrim( char *p, int* nLen ) len -= i; /* djb-rwth: variable has to be decreased before memmove */ (memmove)(p, p + i, ((long long)len + 1)); /* djb-rwth: now cast operator can be added */ } - + for (; 0 < len && is_ascii( p[len - 1] ) && isspace( p[len - 1] ); len--) { ; diff --git a/INCHI-1-TEST/tests/test_unit/test_ichiprt1_enhancedStereo.cpp b/INCHI-1-TEST/tests/test_unit/test_ichiprt1_enhancedStereo.cpp index c33fda8d..fffb967b 100644 --- a/INCHI-1-TEST/tests/test_unit/test_ichiprt1_enhancedStereo.cpp +++ b/INCHI-1-TEST/tests/test_unit/test_ichiprt1_enhancedStereo.cpp @@ -29,7 +29,6 @@ TEST(test_ichiprt1_enhancedStereo, test_OutputINCHI_StereoLayer_enhanced_stereo_ oad->v3000 = (OAD_V3000 *)inchi_calloc(1, sizeof(OAD_V3000)); oad->v3000->n_collections = 5; - oad->v3000->n_steabs = 1; oad->v3000->lists_steabs = (int **)inchi_calloc(1, sizeof(int*)); @@ -78,8 +77,41 @@ TEST(test_ichiprt1_enhancedStereo, test_OutputINCHI_StereoLayer_enhanced_stereo_ oad->v3000->lists_sterel[1][1] = 1; // number of members in collection oad->v3000->lists_sterel[1][2] = 14; // member atom numbers - io.sDifSegs[0][DIFS_s_STYPE] = DIFV_OUTPUT_FILL_T; + INCHI_SORT *inchi_sort = (INCHI_SORT*)inchi_calloc(1, sizeof(INCHI_SORT)); + + int num_at = 8; + int num_iso_at = 1; + int alloc_mode = 0; + int bOrigatomflag = 0; + int found_num_bonds = 0; + int found_num_isotopic = 0; + + inp_ATOM *atoms = CreateInpAtom(num_at); + INChI *inchi = Alloc_INChI(atoms, num_at, &found_num_bonds, &found_num_isotopic, 0); + inchi->nNumberOfAtoms = num_at; + + INChI_Aux *pAux = Alloc_INChI_Aux(num_at, num_iso_at, alloc_mode, bOrigatomflag); + pAux->nNumberOfAtoms = num_at; + pAux->nOrigAtNosInCanonOrd = (AT_NUMB*)inchi_calloc(num_at, sizeof(AT_NUMB)); + + // canonical atom i+1 maps to original atom i+1 + pAux->nOrigAtNosInCanonOrd[0] = 4; + pAux->nOrigAtNosInCanonOrd[1] = 5; + pAux->nOrigAtNosInCanonOrd[2] = 1; + pAux->nOrigAtNosInCanonOrd[3] = 2; + pAux->nOrigAtNosInCanonOrd[4] = 3; + pAux->nOrigAtNosInCanonOrd[5] = 12; + pAux->nOrigAtNosInCanonOrd[6] = 13; + pAux->nOrigAtNosInCanonOrd[7] = 14; + + inchi_sort->pINChI[0] = inchi; + inchi_sort->pINChI_Aux[0] = pAux; + io.pINChISort = inchi_sort; + + io.bOutType = OUT_TN; + io.num_components = 1; + io.sDifSegs[0][DIFS_s_STYPE] = DIFV_OUTPUT_FILL_T; io.nCurINChISegment = 0; io.iCurTautMode = 0; io.bPlainTextTags = 1; @@ -95,7 +127,8 @@ TEST(test_ichiprt1_enhancedStereo, test_OutputINCHI_StereoLayer_enhanced_stereo_ int ret = OutputINCHI_StereoLayer_EnhancedStereo(&cg, &out_file, &strbuf, &io, oad, (char*)"", (char*)""); - EXPECT_EQ(std::string(strbuf.pStr), "/s1(4,5)2(12,13;14)3(1;2,3)"); + // EXPECT_EQ(std::string(strbuf.pStr), "/s1(4,5)2(12,13;14)3(1;2,3)"); + EXPECT_EQ(std::string(strbuf.pStr), "/s1(1,2)2(6,7;8)3(3;4,5)"); EXPECT_EQ(ret, 0); inchi_strbuf_close(&strbuf); diff --git a/INCHI-1-TEST/tests/test_unit/test_ichiprt3.cpp b/INCHI-1-TEST/tests/test_unit/test_ichiprt3.cpp index c98a9b9f..b2cf3692 100644 --- a/INCHI-1-TEST/tests/test_unit/test_ichiprt3.cpp +++ b/INCHI-1-TEST/tests/test_unit/test_ichiprt3.cpp @@ -188,13 +188,10 @@ TEST(test_ichiprt3, test_str_StereoAbsInv_1) int num_at = 2; int num_bonds = 1; - - - int found_num_bonds = 0; int found_num_isotopic = 0; inp_ATOM *at = CreateInpAtom(num_at); - static INChI *inchi = Alloc_INChI(at, num_at, &found_num_bonds, &found_num_isotopic, 0); + INChI *inchi = Alloc_INChI(at, num_at, &found_num_bonds, &found_num_isotopic, 0); inchi->Stereo->nCompInv2Abs = -1; inchi->nNumberOfAtoms = num_at; @@ -231,7 +228,6 @@ TEST(test_ichiprt3, test_str_StereoAbsInv_2) int num_at = 2; int num_bonds = 1; - int found_num_bonds = 0; int found_num_isotopic = 0; inp_ATOM *at = CreateInpAtom(num_at); From 65e5eb5c1ff15d749a0e018c3497c7a281f8c3bf Mon Sep 17 00:00:00 2001 From: Christoph Mueller Date: Mon, 12 Jan 2026 13:59:11 +0000 Subject: [PATCH 11/44] prototypes and unit-test for t and m-layer changes --- INCHI-1-SRC/INCHI_BASE/src/strutil.c | 96 ++++++++++- .../test_unit/test_strutil_enhancedStereo.cpp | 156 ++++++++++++++++++ INCHI-1-TEST/tests/test_unit/test_util.cpp | 16 -- 3 files changed, 247 insertions(+), 21 deletions(-) create mode 100644 INCHI-1-TEST/tests/test_unit/test_strutil_enhancedStereo.cpp diff --git a/INCHI-1-SRC/INCHI_BASE/src/strutil.c b/INCHI-1-SRC/INCHI_BASE/src/strutil.c index 4df9e9ed..9226bd22 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/strutil.c +++ b/INCHI-1-SRC/INCHI_BASE/src/strutil.c @@ -4274,12 +4274,17 @@ int get_canonical_atom_number( INChI_Aux *aux, { for (int canon_num = 1; canon_num <= aux->nNumberOfAtoms; canon_num++) { if (aux->nOrigAtNosInCanonOrd[canon_num - 1] == orig_atom_num) { - return canon_num; // Found canonical atom number + return canon_num; } } - return -1; // Not found + return -1; } +int cmp_asc(const void *a, const void *b) { + AT_NUMB va = *(const AT_NUMB*)a; + AT_NUMB vb = *(const AT_NUMB*)b; + return (va > vb) - (va < vb); // returns -1, 0, or 1 +} int set_EnhancedStereo_t_m_layers( ORIG_ATOM_DATA *orig_inp_data, INChI *inchi, @@ -4292,11 +4297,16 @@ int set_EnhancedStereo_t_m_layers( ORIG_ATOM_DATA *orig_inp_data, int **enh_stereo = orig_inp_data->v3000->lists_steabs; for (int i = 0; i < orig_inp_data->v3000->n_steabs; i++) { - for (int j = 0; j < enh_stereo[i][1]; j++) { - int orig_atom_num = enh_stereo[i][2 + j]; + int nof_atoms = enh_stereo[i][1]; + + if (nof_atoms == 1) { + // + >> - + // - >> - ? + + int orig_atom_num = enh_stereo[i][2]; AT_NUMB canon_atom_num = (AT_NUMB)get_canonical_atom_number(aux, orig_atom_num); - const AT_NUMB *atom_idx_pos = is_in_the_list(inchi->Stereo->nNumber, canon_atom_num, inchi->Stereo->nNumberOfStereoCenters); + AT_NUMB *atom_idx_pos = is_in_the_list(inchi->Stereo->nNumber, canon_atom_num, inchi->Stereo->nNumberOfStereoCenters); if (atom_idx_pos) { int idx = (int)(*atom_idx_pos); @@ -4306,7 +4316,83 @@ int set_EnhancedStereo_t_m_layers( ORIG_ATOM_DATA *orig_inp_data, inchi->Stereo->nCompInv2Abs = -1; } } + + } else if (nof_atoms == 2) { + // smaller atom number gets - + // -- >> -- + // -+ >> -+ + // +- >> -+ + // ++ >> -+ + + int orig_atom_num_1 = enh_stereo[i][2]; + AT_NUMB canon_atom_num_1 = (AT_NUMB)get_canonical_atom_number(aux, orig_atom_num_1); + AT_NUMB *atom_idx_pos_1 = is_in_the_list(inchi->Stereo->nNumber, canon_atom_num_1, inchi->Stereo->nNumberOfStereoCenters); + int idx_1 = -1; + + int orig_atom_num_2 = enh_stereo[i][3]; + AT_NUMB canon_atom_num_2 = (AT_NUMB)get_canonical_atom_number(aux, orig_atom_num_2); + AT_NUMB *atom_idx_pos_2 = is_in_the_list(inchi->Stereo->nNumber, canon_atom_num_2, inchi->Stereo->nNumberOfStereoCenters); + int idx_2 = -1; + + if (atom_idx_pos_1 && atom_idx_pos_2) + { + int idx_1 = (int)(*atom_idx_pos_1); + int idx_2 = (int)(*atom_idx_pos_2); + + if (canon_atom_num_1 > canon_atom_num_2) { + idx_2 = (int)(*atom_idx_pos_1); + idx_1 = (int)(*atom_idx_pos_2); + } + + if (inchi->Stereo->t_parity[idx_1] == 2) + { + inchi->Stereo->t_parity[idx_1] = 1; + inchi->Stereo->nCompInv2Abs = -1; + } + if (inchi->Stereo->t_parity[idx_2] == 1) + { + inchi->Stereo->t_parity[idx_2] = 2; + inchi->Stereo->nCompInv2Abs = -1; + } + } + } else { + // ---, ----, ....? + + // AT_NUMB *arr_cat = (AT_NUMB*)inchi_calloc(nof_atoms, sizeof(AT_NUMB)); + + // for (int j = 0; j < nof_atoms; j++) { + // int orig_atom_num = enh_stereo[i][2 + j]; + // AT_NUMB canon_atom_num = (AT_NUMB)get_canonical_atom_number(aux, orig_atom_num); + + // arr_cat[j] = canon_atom_num; + // } + // qsort(arr_cat, nof_atoms, sizeof(AT_NUMB), cmp_asc); + + // AT_NUMB *arr_cat_idx = (AT_NUMB*)inchi_calloc(nof_atoms, sizeof(AT_NUMB)); + // for (int j = 0; j < nof_atoms; j++) { + // AT_NUMB cur_atom = arr_cat[j]; + // AT_NUMB *atom_idx_pos = is_in_the_list(inchi->Stereo->nNumber, cur_atom, inchi->Stereo->nNumberOfStereoCenters); + // arr_cat_idx[j] = -1; + // if (atom_idx_pos) + // { + // arr_cat_idx[j] = atom_idx_pos[0]; + // printf("arr idx %d\n", arr_cat_idx[j]); + // } + // } + + // int idx = (int)(*atom_idx_pos); + // if (inchi->Stereo->t_parity[idx] == 2) + // { + // inchi->Stereo->t_parity[idx] = 1; + // inchi->Stereo->nCompInv2Abs = -1; + // } + + // free(arr_cat_idx); + // free(arr_cat); + } + + } } // orig_inp_data->v3000->n_steabs; diff --git a/INCHI-1-TEST/tests/test_unit/test_strutil_enhancedStereo.cpp b/INCHI-1-TEST/tests/test_unit/test_strutil_enhancedStereo.cpp new file mode 100644 index 00000000..8acabf84 --- /dev/null +++ b/INCHI-1-TEST/tests/test_unit/test_strutil_enhancedStereo.cpp @@ -0,0 +1,156 @@ +#include + +extern "C" +{ +#include "../../../INCHI-1-SRC/INCHI_BASE/src/strutil.h" +#include "../../../INCHI-1-SRC/INCHI_BASE/src/ichi_io.h" +// #include "../../../INCHI-1-SRC/INCHI_BASE/src/inpdef.h" +} + + +TEST(test_strutil_enhancedStereo, test_set_EnhancedStereo_t_m_layers_1) +{ + + INCHI_IOSTREAM input_stream; + + const char *molblock = + "enhanc_stereo1 \n" + " ACD/LABS08242216132D \n" + " \n" + " 0 0 0 0 0 0 0 0 0 0999 V3000 \n" + "M V30 BEGIN CTAB \n" + "M V30 COUNTS 18 17 0 0 1 \n" + "M V30 BEGIN ATOM \n" + "M V30 1 C 3424.1946 -1936.7935 0 0 \n" + "M V30 2 C 3352.3145 -1895.2935 0 0 \n" + "M V30 3 C 3280.4346 -1936.7935 0 0 \n" + "M V30 4 C 3208.5542 -1895.2935 0 0 \n" + "M V30 5 C 3136.6743 -1936.7935 0 0 \n" + "M V30 6 C 3064.7944 -1895.2935 0 0 \n" + "M V30 7 Br 3136.6743 -2019.7935 0 0 \n" + "M V30 8 Cl 3208.5542 -1812.2935 0 0 \n" + "M V30 9 Cl 3280.4346 -2019.7935 0 0 \n" + "M V30 10 Cl 3352.3145 -1812.2935 0 0 \n" + "M V30 11 Cl 3424.1946 -2019.7935 0 0 \n" + "M V30 12 C 3496.075 -1895.2935 0 0 \n" + "M V30 13 C 3567.9548 -1936.7942 0 0 \n" + "M V30 14 C 3639.835 -1895.2944 0 0 \n" + "M V30 15 C 3711.7148 -1936.7942 0 0 \n" + "M V30 16 Cl 3639.835 -1812.2944 0 0 \n" + "M V30 17 Cl 3567.9548 -2019.7942 0 0 \n" + "M V30 18 Cl 3496.075 -1812.2937 0 0 \n" + "M V30 END ATOM \n" + "M V30 BEGIN BOND \n" + "M V30 1 1 1 2 \n" + "M V30 2 1 1 11 CFG=3 \n" + "M V30 3 1 1 12 \n" + "M V30 4 1 2 3 \n" + "M V30 5 1 2 10 CFG=1 \n" + "M V30 6 1 3 4 \n" + "M V30 7 1 3 9 CFG=1 \n" + "M V30 8 1 4 5 \n" + "M V30 9 1 4 8 CFG=1 \n" + "M V30 10 1 5 6 \n" + "M V30 11 1 5 7 CFG=1 \n" + "M V30 12 1 12 13 \n" + "M V30 13 1 12 18 CFG=3 \n" + "M V30 14 1 13 14 \n" + "M V30 15 1 13 17 CFG=1 \n" + "M V30 16 1 14 15 \n" + "M V30 17 1 14 16 CFG=1 \n" + "M V30 END BOND \n" + "M V30 BEGIN COLLECTION \n" + "M V30 MDLV30/STERAC2 ATOMS=(1 1) \n" + "M V30 MDLV30/STERAC1 ATOMS=(2 2 3) \n" + "M V30 MDLV30/STEABS ATOMS=(2 4 5) \n" + "M V30 MDLV30/STEREL1 ATOMS=(2 12 13) \n" + "M V30 MDLV30/STEREL2 ATOMS=(1 14) \n" + "M V30 END COLLECTION \n" + "M V30 END CTAB \n" + "M END \n"; + + inchi_ios_init(&input_stream, INCHI_IOS_TYPE_STRING, nullptr); + inchi_ios_print_nodisplay(&input_stream, molblock); + + ORIG_ATOM_DATA orig_inp_data = {}; + int bMergeAllInputStructures = 0; + int bGetOrigCoord = 0; + int bDoNotAddH = 0; + int treat_polymers = 0; + int treat_NPZz = 0; + const char *pSdfLabel = nullptr; + char *pSdfValue = nullptr; + unsigned long *lSdfId = nullptr; + long *lMolfileNumber = nullptr; + INCHI_MODE pInpAtomFlags = 0; + int err = 0; + char *pStrErr = nullptr; + int bNoWarnings = 0; + + int ret = CreateOrigInpDataFromMolfile( + &input_stream, + &orig_inp_data, + bMergeAllInputStructures, + bGetOrigCoord, + bDoNotAddH, + treat_polymers, + treat_NPZz, + pSdfLabel, + pSdfValue, + lSdfId, + lMolfileNumber, + &pInpAtomFlags, + &err, + pStrErr, + bNoWarnings); + + + + int num_at = orig_inp_data.num_inp_atoms; + + int found_num_bonds = 0; + int found_num_isotopic = 0; + + inp_ATOM *at = CreateInpAtom(num_at); + INChI *inchi = Alloc_INChI(at, num_at, &found_num_bonds, &found_num_isotopic, 0); + + + AT_NUMB at1[9] = {1,3,4,5,6,7,8,9,10}; + S_CHAR parity[9] = {2,1,1,2,2,1,2,2,1}; + inchi->Stereo->t_parity = parity; + inchi->Stereo->nNumber = at1; + inchi->Stereo->nNumberOfStereoCenters = 9; + + inchi->Stereo->nCompInv2Abs = -1; + inchi->nNumberOfAtoms = num_at; + + int nAllocMode = 0; + int bOrigCoord = 0; + + INChI_Aux *pAux = Alloc_INChI_Aux( num_at, + 0, // inp_cur_data->num_isotopic, + nAllocMode, + bOrigCoord ); + + + pAux->nNumberOfAtoms = 9; + + pAux->nOrigAtNosInCanonOrd = (AT_NUMB*)inchi_calloc(num_at, sizeof(AT_NUMB)); + + pAux->nOrigAtNosInCanonOrd[0] = 4; + pAux->nOrigAtNosInCanonOrd[1] = 5; + pAux->nOrigAtNosInCanonOrd[2] = 1; + pAux->nOrigAtNosInCanonOrd[3] = 2; + pAux->nOrigAtNosInCanonOrd[4] = 3; + pAux->nOrigAtNosInCanonOrd[5] = 12; + pAux->nOrigAtNosInCanonOrd[6] = 13; + pAux->nOrigAtNosInCanonOrd[7] = 14; + + std::cout << "Test started\n"; + ret = set_EnhancedStereo_t_m_layers(&orig_inp_data, inchi, pAux); + std::cout << "Test finished\n"; + + EXPECT_EQ(ret, 1); + + +} diff --git a/INCHI-1-TEST/tests/test_unit/test_util.cpp b/INCHI-1-TEST/tests/test_unit/test_util.cpp index 037caa99..f4eb26ca 100644 --- a/INCHI-1-TEST/tests/test_unit/test_util.cpp +++ b/INCHI-1-TEST/tests/test_unit/test_util.cpp @@ -978,19 +978,3 @@ TEST(test_util, test_extract_stereo_info_from_inchi_string) EXPECT_EQ(found_stereo2, 0); } - -// TEST(test_util, test_extract_all_backbone_bonds_from_inchi_string) -// { - -// // int extract_all_backbone_bonds_from_inchi_string(char *sinchi, int *n_all_bkb_orig, int *orig, int *all_bkb_orig); - -// char inchi1[] = "InChI=1S/C2H6/c1-2/h1-2H3"; - -// int n_all_bkb_orig[2] = {0, 0}; -// int orig[2] = {0, 1}; -// int all_bkb_orig[2] = {0, 0}; - -// int ret1 = extract_all_backbone_bonds_from_inchi_string(inchi1, n_all_bkb_orig, orig, all_bkb_orig); - -// EXPECT_EQ(ret1, 0); -// } From 0555b4f1a574aba96a02935135ff073b1241e8c4 Mon Sep 17 00:00:00 2001 From: Christoph Mueller Date: Wed, 21 Jan 2026 17:15:38 +0000 Subject: [PATCH 12/44] added code for parity change (t+m-layer) --- INCHI-1-SRC/INCHI_BASE/src/strutil.c | 153 +++++++-------------------- 1 file changed, 38 insertions(+), 115 deletions(-) diff --git a/INCHI-1-SRC/INCHI_BASE/src/strutil.c b/INCHI-1-SRC/INCHI_BASE/src/strutil.c index 9226bd22..83e64a32 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/strutil.c +++ b/INCHI-1-SRC/INCHI_BASE/src/strutil.c @@ -4280,142 +4280,65 @@ int get_canonical_atom_number( INChI_Aux *aux, return -1; } -int cmp_asc(const void *a, const void *b) { - AT_NUMB va = *(const AT_NUMB*)a; - AT_NUMB vb = *(const AT_NUMB*)b; - return (va > vb) - (va < vb); // returns -1, 0, or 1 -} - -int set_EnhancedStereo_t_m_layers( ORIG_ATOM_DATA *orig_inp_data, - INChI *inchi, - INChI_Aux *aux) +int invert_parities(INChI *inchi, + INChI_Aux *aux, + int **list_atoms, + int nof_lists, + int is_absolute) { - int ret = 0; - if (orig_inp_data->v3000->n_steabs > 0) + if (nof_lists > 0) { + for (int i = 0; i < nof_lists; i++) { + int nof_atoms = list_atoms[i][1]; - int **enh_stereo = orig_inp_data->v3000->lists_steabs; - for (int i = 0; i < orig_inp_data->v3000->n_steabs; i++) { - int nof_atoms = enh_stereo[i][1]; - - if (nof_atoms == 1) { - // + >> - - // - >> - ? - - int orig_atom_num = enh_stereo[i][2]; + AT_NUMB min_c_atom_num = (AT_NUMB)9999999; + for (int j = 0; j < nof_atoms; j++) { + int orig_atom_num = list_atoms[i][2 + j]; AT_NUMB canon_atom_num = (AT_NUMB)get_canonical_atom_number(aux, orig_atom_num); - - AT_NUMB *atom_idx_pos = is_in_the_list(inchi->Stereo->nNumber, canon_atom_num, inchi->Stereo->nNumberOfStereoCenters); - if (atom_idx_pos) - { - int idx = (int)(*atom_idx_pos); - if (inchi->Stereo->t_parity[idx] == 2) - { - inchi->Stereo->t_parity[idx] = 1; - inchi->Stereo->nCompInv2Abs = -1; - } + if (canon_atom_num < min_c_atom_num) { + min_c_atom_num = canon_atom_num; } + } - } else if (nof_atoms == 2) { - // smaller atom number gets - - // -- >> -- - // -+ >> -+ - // +- >> -+ - // ++ >> -+ - - int orig_atom_num_1 = enh_stereo[i][2]; - AT_NUMB canon_atom_num_1 = (AT_NUMB)get_canonical_atom_number(aux, orig_atom_num_1); - AT_NUMB *atom_idx_pos_1 = is_in_the_list(inchi->Stereo->nNumber, canon_atom_num_1, inchi->Stereo->nNumberOfStereoCenters); - int idx_1 = -1; - - int orig_atom_num_2 = enh_stereo[i][3]; - AT_NUMB canon_atom_num_2 = (AT_NUMB)get_canonical_atom_number(aux, orig_atom_num_2); - AT_NUMB *atom_idx_pos_2 = is_in_the_list(inchi->Stereo->nNumber, canon_atom_num_2, inchi->Stereo->nNumberOfStereoCenters); - int idx_2 = -1; - - if (atom_idx_pos_1 && atom_idx_pos_2) - { - int idx_1 = (int)(*atom_idx_pos_1); - int idx_2 = (int)(*atom_idx_pos_2); + int needs_inversion = (inchi->Stereo->t_parity[min_c_atom_num] == 2); + if (needs_inversion) { - if (canon_atom_num_1 > canon_atom_num_2) { - idx_2 = (int)(*atom_idx_pos_1); - idx_1 = (int)(*atom_idx_pos_2); + for (int j = 0; j < nof_atoms; j++) { + int orig_atom_num = list_atoms[i][2 + j]; + AT_NUMB canon_atom_num = (AT_NUMB)get_canonical_atom_number(aux, orig_atom_num); + if (inchi->Stereo->t_parity[canon_atom_num] == 2) { + inchi->Stereo->t_parity[canon_atom_num] = 1; + } else if(inchi->Stereo->t_parity[canon_atom_num] == 1) { + inchi->Stereo->t_parity[canon_atom_num] = 2; } + } - if (inchi->Stereo->t_parity[idx_1] == 2) - { - inchi->Stereo->t_parity[idx_1] = 1; - inchi->Stereo->nCompInv2Abs = -1; - } - if (inchi->Stereo->t_parity[idx_2] == 1) - { - inchi->Stereo->t_parity[idx_2] = 2; - inchi->Stereo->nCompInv2Abs = -1; - } + if (is_absolute) { + inchi->Stereo->nCompInv2Abs = -1; } } else { - // ---, ----, ....? - - // AT_NUMB *arr_cat = (AT_NUMB*)inchi_calloc(nof_atoms, sizeof(AT_NUMB)); - - // for (int j = 0; j < nof_atoms; j++) { - // int orig_atom_num = enh_stereo[i][2 + j]; - // AT_NUMB canon_atom_num = (AT_NUMB)get_canonical_atom_number(aux, orig_atom_num); - - // arr_cat[j] = canon_atom_num; - // } - // qsort(arr_cat, nof_atoms, sizeof(AT_NUMB), cmp_asc); - - // AT_NUMB *arr_cat_idx = (AT_NUMB*)inchi_calloc(nof_atoms, sizeof(AT_NUMB)); - // for (int j = 0; j < nof_atoms; j++) { - // AT_NUMB cur_atom = arr_cat[j]; - // AT_NUMB *atom_idx_pos = is_in_the_list(inchi->Stereo->nNumber, cur_atom, inchi->Stereo->nNumberOfStereoCenters); - // arr_cat_idx[j] = -1; - // if (atom_idx_pos) - // { - // arr_cat_idx[j] = atom_idx_pos[0]; - // printf("arr idx %d\n", arr_cat_idx[j]); - // } - // } - - // int idx = (int)(*atom_idx_pos); - // if (inchi->Stereo->t_parity[idx] == 2) - // { - // inchi->Stereo->t_parity[idx] = 1; - // inchi->Stereo->nCompInv2Abs = -1; - // } - - // free(arr_cat_idx); - // free(arr_cat); - + if(is_absolute && inchi->Stereo->t_parity[min_c_atom_num] == 1) { + inchi->Stereo->nCompInv2Abs = 0; + } } - - } } - // orig_inp_data->v3000->n_steabs; - // orig_inp_data->v3000->lists_steabs; - - // pINChI->Stereo->nCompInv2Abs; - // -1 --> 1 - // 1 --> 0 - // orig_inp_data->v3000->n_sterel; - // orig_inp_data->v3000->lists_sterel; +} - // orig_inp_data->v3000->n_sterac; - // orig_inp_data->v3000->lists_sterac; +int set_EnhancedStereo_t_m_layers( ORIG_ATOM_DATA *orig_inp_data, + INChI *inchi, + INChI_Aux *aux) +{ + int ret = 0; - // get_canonical_atom_number; + int ret_abs = invert_parities(inchi, aux, orig_inp_data->v3000->lists_steabs, orig_inp_data->v3000->n_steabs, 1); - // pINChI->Stereo->nNumber; - // pINChI->Stereo->t_parity - // 1 --> - - // 2 --> + + int ret_rel = invert_parities(inchi, aux, orig_inp_data->v3000->lists_sterel, orig_inp_data->v3000->n_sterel, 0); + int ret_rac = invert_parities(inchi, aux, orig_inp_data->v3000->lists_sterac, orig_inp_data->v3000->n_sterac, 0); return ret; } From 79885842388ae79c2934b30ec5005ca30c2175c1 Mon Sep 17 00:00:00 2001 From: Christoph Mueller Date: Thu, 22 Jan 2026 16:02:52 +0000 Subject: [PATCH 13/44] added unit test and param for enh stereochem --- CMakeLists.txt | 4 +- INCHI-1-SRC/INCHI_BASE/src/ichiparm.c | 20 ++++- INCHI-1-SRC/INCHI_BASE/src/ichiprt2.c | 28 ++++++- INCHI-1-SRC/INCHI_BASE/src/strutil.c | 18 ++--- .../tests/test_unit/test_enhancedStereo.cpp | 75 +++++++++++++++++++ 5 files changed, 125 insertions(+), 20 deletions(-) create mode 100644 INCHI-1-TEST/tests/test_unit/test_enhancedStereo.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 2b324705..5ac0ecc7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,8 +6,8 @@ set(CMAKE_C_STANDARD 99) set(CMAKE_CXX_STANDARD 11) set(CMAKE_POSITION_INDEPENDENT_CODE ON) -# set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address") -# set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address") +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address -O0 -g") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -O0 -g") include(FetchContent) FetchContent_Declare( diff --git a/INCHI-1-SRC/INCHI_BASE/src/ichiparm.c b/INCHI-1-SRC/INCHI_BASE/src/ichiparm.c index ffc6e1a9..0a2a4204 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/ichiparm.c +++ b/INCHI-1-SRC/INCHI_BASE/src/ichiparm.c @@ -122,7 +122,8 @@ int set_common_options_by_parg(const char* pArg, int* pbNPZz, int* pbNoWarnings, int* pbMergeHash, - int* pbHideInChI); + int* pbHideInChI, + int* pbEnhancedStereochemistry); /**************************************************************************** @@ -165,7 +166,8 @@ int set_common_options_by_parg(const char* pArg, int* pbNPZz, int* pbNoWarnings, int* pbMergeHash, - int* pbHideInChI + int* pbHideInChI, + int* pbEnhancedStereochemistry ) { int got = 0; @@ -266,6 +268,11 @@ int set_common_options_by_parg(const char* pArg, (*pbLooseTSACheck) = 1; got = 1; } + else if (!inchi_stricmp(pArg, "EnhancedStereochemistry")) + { + *pbEnhancedStereochemistry = 1; + got = 1; + } #ifndef USE_STDINCHI_API /* These options DO TURN OFF Std flag */ @@ -643,6 +650,7 @@ int ReadCommandLineParms(int argc, int bAcidTautomerism = (DISCONNECT_SALTS == 1) ? (TEST_REMOVE_S_ATOMS == 1 ? 2 : 1) : 0; int bUnchargedAcidTaut = (CHARGED_SALTS_ONLY == 0); int bMergeSaltTGroups = (DISCONNECT_SALTS == 1); + int bEnhancedStereochemistry = 0; #if ( MIN_SB_RING_SIZE > 0 ) int nMinDbRinSize = MIN_SB_RING_SIZE, mdbr = 0; #endif @@ -795,7 +803,8 @@ int ReadCommandLineParms(int argc, &bLargeMolecules, &bPolymers, &bFoldPolymerSRU, &bFrameShiftScheme, &bStereoAtZz, &bNPZz, - &bNoWarnings, &bMergeHash, &bHideInChI); + &bNoWarnings, &bMergeHash, &bHideInChI, + &bEnhancedStereochemistry); if (got) { ; @@ -1243,7 +1252,8 @@ int ReadCommandLineParms(int argc, &bLargeMolecules, &bPolymers, &bFoldPolymerSRU, &bFrameShiftScheme, &bStereoAtZz, &bNPZz, - &bNoWarnings, &bMergeHash, &bHideInChI); + &bNoWarnings, &bMergeHash, &bHideInChI, + &bEnhancedStereochemistry); if (got) { @@ -2120,6 +2130,8 @@ int ReadCommandLineParms(int argc, ip->bINChIOutputOptions2 = bINChIOutputOptions2; + ip->bEnhancedStereo = bEnhancedStereochemistry; + return 0; } diff --git a/INCHI-1-SRC/INCHI_BASE/src/ichiprt2.c b/INCHI-1-SRC/INCHI_BASE/src/ichiprt2.c index 0960fdc1..977a995f 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/ichiprt2.c +++ b/INCHI-1-SRC/INCHI_BASE/src/ichiprt2.c @@ -2159,6 +2159,12 @@ int MakeStereoString( AT_NUMB *at1, return nLen; } +int compare_ints(const void *a, const void *b) { + int arg1 = *(const int *)a; + int arg2 = *(const int *)b; + return (arg1 > arg2) - (arg1 < arg2); +} + int MakeEnhStereoString( INCHI_SORT *pINChISort, int bOutType, int num_components, @@ -2192,20 +2198,34 @@ int MakeEnhStereoString( INCHI_SORT *pINChISort, tot_len += MakeDelim( "(", strbuf, bOverflow ); for (int i = 0; i < nof_units; i++) { - for (int j = 0; j < enh_stereo[i][1]; j++) { - int orig_atom_num = enh_stereo[i][2 + j]; + int *atom_numbers = &enh_stereo[i][2]; + int nof_atoms = enh_stereo[i][1]; + int c_atom_numbers[nof_atoms]; + for (int j = 0; j < nof_atoms; j++) { + + int orig_atom_num = atom_numbers[j]; //enh_stereo[i][2 + j]; int canon_atom_num = get_canonical_atom_number(pAux, orig_atom_num); + c_atom_numbers[j] = canon_atom_num; + + } + + if (nof_atoms > 1) { + qsort(c_atom_numbers, nof_atoms, sizeof(int), compare_ints); + } + for (int j = 0; j < nof_atoms; j++) { //tot_len += MakeMult_EnhStereo( enh_stereo[i][2 + j], "", strbuf, nCtMode, bOverflow ); - tot_len += MakeMult_EnhStereo( canon_atom_num, "", strbuf, nCtMode, bOverflow ); + tot_len += MakeMult_EnhStereo( c_atom_numbers[j], "", strbuf, nCtMode, bOverflow ); if ((j + 1) < enh_stereo[i][1]) { tot_len += MakeDelim( ",", strbuf, bOverflow ); } } if (i + 1 < nof_units) { - tot_len += MakeDelim( ";", strbuf, bOverflow ); + // tot_len += MakeDelim( ";", strbuf, bOverflow ); + tot_len += MakeDelim( ")", strbuf, bOverflow ); + tot_len += MakeDelim( "(", strbuf, bOverflow ); } } tot_len += MakeDelim( ")", strbuf, bOverflow ); diff --git a/INCHI-1-SRC/INCHI_BASE/src/strutil.c b/INCHI-1-SRC/INCHI_BASE/src/strutil.c index 83e64a32..f7759614 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/strutil.c +++ b/INCHI-1-SRC/INCHI_BASE/src/strutil.c @@ -4287,6 +4287,10 @@ int invert_parities(INChI *inchi, int is_absolute) { + // parities + // 2 = + + // 1 = - + if (nof_lists > 0) { for (int i = 0; i < nof_lists; i++) { @@ -4300,9 +4304,11 @@ int invert_parities(INChI *inchi, min_c_atom_num = canon_atom_num; } } + int min_c_atom_parity = inchi->Stereo->t_parity[min_c_atom_num]; - int needs_inversion = (inchi->Stereo->t_parity[min_c_atom_num] == 2); - if (needs_inversion) { + if ((is_absolute == 1) && (min_c_atom_parity == 1)) { + inchi->Stereo->nCompInv2Abs = 0; + } else if (min_c_atom_parity == 2) { for (int j = 0; j < nof_atoms; j++) { int orig_atom_num = list_atoms[i][2 + j]; @@ -4317,14 +4323,9 @@ int invert_parities(INChI *inchi, if (is_absolute) { inchi->Stereo->nCompInv2Abs = -1; } - } else { - if(is_absolute && inchi->Stereo->t_parity[min_c_atom_num] == 1) { - inchi->Stereo->nCompInv2Abs = 0; - } } } } - } int set_EnhancedStereo_t_m_layers( ORIG_ATOM_DATA *orig_inp_data, @@ -4333,11 +4334,8 @@ int set_EnhancedStereo_t_m_layers( ORIG_ATOM_DATA *orig_inp_data, { int ret = 0; - int ret_abs = invert_parities(inchi, aux, orig_inp_data->v3000->lists_steabs, orig_inp_data->v3000->n_steabs, 1); - int ret_rel = invert_parities(inchi, aux, orig_inp_data->v3000->lists_sterel, orig_inp_data->v3000->n_sterel, 0); - int ret_rac = invert_parities(inchi, aux, orig_inp_data->v3000->lists_sterac, orig_inp_data->v3000->n_sterac, 0); return ret; diff --git a/INCHI-1-TEST/tests/test_unit/test_enhancedStereo.cpp b/INCHI-1-TEST/tests/test_unit/test_enhancedStereo.cpp new file mode 100644 index 00000000..73d985bd --- /dev/null +++ b/INCHI-1-TEST/tests/test_unit/test_enhancedStereo.cpp @@ -0,0 +1,75 @@ +#include + +extern "C" +{ +#include "../../../INCHI-1-SRC/INCHI_BASE/src/inchi_api.h" +} + +TEST(test_enhancedStereo, test_EnhancedStereochemistry_1) +{ + const char *molblock = + "enhanc_stereo1 \n" + " ACD/LABS08242216132D \n" + " \n" + " 0 0 0 0 0 0 0 0 0 0999 V3000 \n" + "M V30 BEGIN CTAB \n" + "M V30 COUNTS 18 17 0 0 1 \n" + "M V30 BEGIN ATOM \n" + "M V30 1 C 3424.1946 -1936.7935 0 0 \n" + "M V30 2 C 3352.3145 -1895.2935 0 0 \n" + "M V30 3 C 3280.4346 -1936.7935 0 0 \n" + "M V30 4 C 3208.5542 -1895.2935 0 0 \n" + "M V30 5 C 3136.6743 -1936.7935 0 0 \n" + "M V30 6 C 3064.7944 -1895.2935 0 0 \n" + "M V30 7 Br 3136.6743 -2019.7935 0 0 \n" + "M V30 8 Cl 3208.5542 -1812.2935 0 0 \n" + "M V30 9 Cl 3280.4346 -2019.7935 0 0 \n" + "M V30 10 Cl 3352.3145 -1812.2935 0 0 \n" + "M V30 11 Cl 3424.1946 -2019.7935 0 0 \n" + "M V30 12 C 3496.075 -1895.2935 0 0 \n" + "M V30 13 C 3567.9548 -1936.7942 0 0 \n" + "M V30 14 C 3639.835 -1895.2944 0 0 \n" + "M V30 15 C 3711.7148 -1936.7942 0 0 \n" + "M V30 16 Cl 3639.835 -1812.2944 0 0 \n" + "M V30 17 Cl 3567.9548 -2019.7942 0 0 \n" + "M V30 18 Cl 3496.075 -1812.2937 0 0 \n" + "M V30 END ATOM \n" + "M V30 BEGIN BOND \n" + "M V30 1 1 1 2 \n" + "M V30 2 1 1 11 CFG=3 \n" + "M V30 3 1 1 12 \n" + "M V30 4 1 2 3 \n" + "M V30 5 1 2 10 CFG=1 \n" + "M V30 6 1 3 4 \n" + "M V30 7 1 3 9 CFG=1 \n" + "M V30 8 1 4 5 \n" + "M V30 9 1 4 8 CFG=1 \n" + "M V30 10 1 5 6 \n" + "M V30 11 1 5 7 CFG=1 \n" + "M V30 12 1 12 13 \n" + "M V30 13 1 12 18 CFG=3 \n" + "M V30 14 1 13 14 \n" + "M V30 15 1 13 17 CFG=1 \n" + "M V30 16 1 14 15 \n" + "M V30 17 1 14 16 CFG=1 \n" + "M V30 END BOND \n" + "M V30 BEGIN COLLECTION \n" + "M V30 MDLV30/STERAC2 ATOMS=(1 1) \n" + "M V30 MDLV30/STERAC1 ATOMS=(2 2 3) \n" + "M V30 MDLV30/STEABS ATOMS=(2 4 5) \n" + "M V30 MDLV30/STEREL1 ATOMS=(2 12 13) \n" + "M V30 MDLV30/STEREL2 ATOMS=(1 14) \n" + "M V30 END COLLECTION \n" + "M V30 END CTAB \n" + "M END \n"; + + char options[] = "-EnhancedStereochemistry"; + inchi_Output output; + inchi_Output *poutput = &output; + const char expected_inchi[] = "InChI=1S/2C5H5.Fe/c2*1-2-4-5-3-1;/h2*1-5H;/q2*-1;"; + + EXPECT_EQ(MakeINCHIFromMolfileText(molblock, options, poutput), 1); + EXPECT_STREQ(poutput->szInChI, expected_inchi); + + FreeINCHI(poutput); +} From 2d12b3b2f3517772fff7653203c1c1c85d5427b8 Mon Sep 17 00:00:00 2001 From: Christoph Mueller Date: Fri, 23 Jan 2026 12:15:04 +0000 Subject: [PATCH 14/44] fixed parity change bug for enhanced stereochemistry --- INCHI-1-SRC/INCHI_BASE/src/ichiprt3.c | 20 ++++++-- INCHI-1-SRC/INCHI_BASE/src/mol2atom.c | 11 +++++ INCHI-1-SRC/INCHI_BASE/src/strutil.c | 39 ++++++++++++--- .../tests/test_unit/test_enhancedStereo.cpp | 2 +- .../test_ichiprt1_enhancedStereo.cpp | 48 +++++++++++++------ .../tests/test_unit/test_ichiprt3.cpp | 2 + .../test_unit/test_strutil_enhancedStereo.cpp | 28 +++++++---- 7 files changed, 114 insertions(+), 36 deletions(-) diff --git a/INCHI-1-SRC/INCHI_BASE/src/ichiprt3.c b/INCHI-1-SRC/INCHI_BASE/src/ichiprt3.c index eb338ddd..a17b0324 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/ichiprt3.c +++ b/INCHI-1-SRC/INCHI_BASE/src/ichiprt3.c @@ -961,10 +961,22 @@ int str_Sp2( INCHI_SORT *pINChISort, return ( strbuf->nUsedLength - nUsedLength0 ); } - -/**************************************************************************** - Produce tetrahedral stereo substring of the whole structure InChI string. -****************************************************************************/ +/** + * @brief Produce tetrahedral stereo substring of the whole structure InChI string. + * + * @param pINChISort Pointer to the primary INCHI_SORT structure containing input data. + * @param pINChISort2 Pointer to the secondary INCHI_SORT structure, used for comparison or additional data. + * @param strbuf Pointer to an INCHI_IOS_STRING buffer where the output string will be stored. + * @param bOverflow Pointer to an integer flag that will be set if the output overflows the buffer. + * @param bOutType Output type flag specifying the format or type of output. + * @param TAUT_MODE Tautomer mode flag indicating how tautomers are handled. + * @param num_components The number of components to process. + * @param bRelRac Flag for relative or racemic stereochemistry. + * @param bSecondNonTautPass Flag indicating if this is the second pass for non-tautomeric processing. + * @param bOmitRepetitions Flag to omit repeated entries in the output. + * @param bUseMulipliers Flag to use multipliers in the output representation. + * @return int + */ int str_Sp3( INCHI_SORT *pINChISort, INCHI_SORT *pINChISort2, INCHI_IOS_STRING *strbuf, diff --git a/INCHI-1-SRC/INCHI_BASE/src/mol2atom.c b/INCHI-1-SRC/INCHI_BASE/src/mol2atom.c index ca519c0a..6f1dd8e4 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/mol2atom.c +++ b/INCHI-1-SRC/INCHI_BASE/src/mol2atom.c @@ -1337,6 +1337,15 @@ void FreeExtOrigAtData(OAD_Polymer *pd, OAD_V3000 *v3k) } /****************************************************************************/ +static int cmp_int_list_first_elem(const void *a, const void *b) +{ + int * const *list_a = (int * const *)a; + int * const *list_b = (int * const *)b; + int val_a = (*list_a)[0]; + int val_b = (*list_b)[0]; + return (val_a > val_b) - (val_a < val_b); +} + int SetExtOrigAtDataByMolfileExtInput(MOL_FMT_DATA *mfdata, OAD_Polymer **ppPolymer, OAD_V3000 **ppV3000, @@ -1568,6 +1577,7 @@ int SetExtOrigAtDataByMolfileExtInput(MOL_FMT_DATA *mfdata, lst[k] = mol_lst[k]; } } + qsort(pv->lists_sterac, mpv->n_sterac, sizeof(int *), cmp_int_list_first_elem); } if (mpv->n_sterel && mpv->sterel) { @@ -1593,6 +1603,7 @@ int SetExtOrigAtDataByMolfileExtInput(MOL_FMT_DATA *mfdata, lst[k] = mol_lst[k]; } } + qsort(pv->lists_sterel, mpv->n_sterel, sizeof(int *), cmp_int_list_first_elem); } } diff --git a/INCHI-1-SRC/INCHI_BASE/src/strutil.c b/INCHI-1-SRC/INCHI_BASE/src/strutil.c index f7759614..86efdb93 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/strutil.c +++ b/INCHI-1-SRC/INCHI_BASE/src/strutil.c @@ -4280,6 +4280,18 @@ int get_canonical_atom_number( INChI_Aux *aux, return -1; } +int get_parity_idx_from_canonical_atom_number( int canon_atom_num, + AT_NUMB *nNumber, + int nof_atoms) +{ + for (int i = 0; i < nof_atoms; i++) { + if (nNumber[i] == canon_atom_num) { + return i; + } + } + return -1; +} + int invert_parities(INChI *inchi, INChI_Aux *aux, int **list_atoms, @@ -4291,6 +4303,8 @@ int invert_parities(INChI *inchi, // 2 = + // 1 = - + S_CHAR *t_parity = inchi->Stereo->t_parity; + if (nof_lists > 0) { for (int i = 0; i < nof_lists; i++) { @@ -4304,19 +4318,30 @@ int invert_parities(INChI *inchi, min_c_atom_num = canon_atom_num; } } - int min_c_atom_parity = inchi->Stereo->t_parity[min_c_atom_num]; + int min_c_parity_idx = get_parity_idx_from_canonical_atom_number(min_c_atom_num, + inchi->Stereo->nNumber, + inchi->Stereo->nNumberOfStereoCenters); + + if (min_c_parity_idx == -1) { + // return -1; + printf("ERROR %d: cannot find parity idx for atom %d %d\n", __LINE__, min_c_atom_num, min_c_parity_idx); + } + int min_c_atom_parity = t_parity[min_c_parity_idx]; if ((is_absolute == 1) && (min_c_atom_parity == 1)) { - inchi->Stereo->nCompInv2Abs = 0; + inchi->Stereo->nCompInv2Abs = 1; } else if (min_c_atom_parity == 2) { for (int j = 0; j < nof_atoms; j++) { int orig_atom_num = list_atoms[i][2 + j]; AT_NUMB canon_atom_num = (AT_NUMB)get_canonical_atom_number(aux, orig_atom_num); - if (inchi->Stereo->t_parity[canon_atom_num] == 2) { - inchi->Stereo->t_parity[canon_atom_num] = 1; - } else if(inchi->Stereo->t_parity[canon_atom_num] == 1) { - inchi->Stereo->t_parity[canon_atom_num] = 2; + int parity_idx = get_parity_idx_from_canonical_atom_number(canon_atom_num, + inchi->Stereo->nNumber, + inchi->Stereo->nNumberOfStereoCenters); + if (t_parity[parity_idx] == 2) { + t_parity[parity_idx] = 1; + } else if(t_parity[parity_idx] == 1) { + t_parity[parity_idx] = 2; } } @@ -4335,8 +4360,8 @@ int set_EnhancedStereo_t_m_layers( ORIG_ATOM_DATA *orig_inp_data, int ret = 0; int ret_abs = invert_parities(inchi, aux, orig_inp_data->v3000->lists_steabs, orig_inp_data->v3000->n_steabs, 1); - int ret_rel = invert_parities(inchi, aux, orig_inp_data->v3000->lists_sterel, orig_inp_data->v3000->n_sterel, 0); int ret_rac = invert_parities(inchi, aux, orig_inp_data->v3000->lists_sterac, orig_inp_data->v3000->n_sterac, 0); + int ret_rel = invert_parities(inchi, aux, orig_inp_data->v3000->lists_sterel, orig_inp_data->v3000->n_sterel, 0); return ret; } diff --git a/INCHI-1-TEST/tests/test_unit/test_enhancedStereo.cpp b/INCHI-1-TEST/tests/test_unit/test_enhancedStereo.cpp index 73d985bd..1e6b5042 100644 --- a/INCHI-1-TEST/tests/test_unit/test_enhancedStereo.cpp +++ b/INCHI-1-TEST/tests/test_unit/test_enhancedStereo.cpp @@ -66,7 +66,7 @@ TEST(test_enhancedStereo, test_EnhancedStereochemistry_1) char options[] = "-EnhancedStereochemistry"; inchi_Output output; inchi_Output *poutput = &output; - const char expected_inchi[] = "InChI=1S/2C5H5.Fe/c2*1-2-4-5-3-1;/h2*1-5H;/q2*-1;"; + const char expected_inchi[] = "InChI=1S/C10H14BrCl7/c1-3(11)5(13)7(15)9(17)10(18)8(16)6(14)4(2)12/h3-10H,1-2H3/t3-,4-,5+,6-,7-,8-,9+,10-/m0/s1(3,5)2(6,8)(4)3(7,9)(10)"; EXPECT_EQ(MakeINCHIFromMolfileText(molblock, options, poutput), 1); EXPECT_STREQ(poutput->szInChI, expected_inchi); diff --git a/INCHI-1-TEST/tests/test_unit/test_ichiprt1_enhancedStereo.cpp b/INCHI-1-TEST/tests/test_unit/test_ichiprt1_enhancedStereo.cpp index fffb967b..7c5f7bef 100644 --- a/INCHI-1-TEST/tests/test_unit/test_ichiprt1_enhancedStereo.cpp +++ b/INCHI-1-TEST/tests/test_unit/test_ichiprt1_enhancedStereo.cpp @@ -31,10 +31,8 @@ TEST(test_ichiprt1_enhancedStereo, test_OutputINCHI_StereoLayer_enhanced_stereo_ oad->v3000->n_collections = 5; oad->v3000->n_steabs = 1; - oad->v3000->lists_steabs = (int **)inchi_calloc(1, sizeof(int*)); - for (int i = 0; i < oad->v3000->n_steabs; i++) { - oad->v3000->lists_steabs[i] = (int *)inchi_calloc(1, sizeof(int)); - } + oad->v3000->lists_steabs = (int **)inchi_calloc(oad->v3000->n_steabs, sizeof(int*)); + oad->v3000->lists_steabs[0] = (int *)inchi_calloc(2+2, sizeof(int)); // STEABS ATOMS=(2 4 5) oad->v3000->lists_steabs[0][0] = 1; // - not used @@ -45,10 +43,9 @@ TEST(test_ichiprt1_enhancedStereo, test_OutputINCHI_StereoLayer_enhanced_stereo_ // STERAC2 ATOMS=(1 1) oad->v3000->n_sterac = 2; - oad->v3000->lists_sterac = (int **)inchi_calloc(1, sizeof(int*)); - for (int i = 0; i < oad->v3000->n_sterac; i++) { - oad->v3000->lists_sterac[i] = (int *)inchi_calloc(1, sizeof(int)); - } + oad->v3000->lists_sterac = (int **)inchi_calloc(oad->v3000->n_sterac, sizeof(int*)); + oad->v3000->lists_sterac[0] = (int *)inchi_calloc(2 + 1, sizeof(int)); + oad->v3000->lists_sterac[1] = (int *)inchi_calloc(2 + 2, sizeof(int)); oad->v3000->lists_sterac[0][0] = 2; // n from "STERACn" tag oad->v3000->lists_sterac[0][1] = 1; // number of members in collection @@ -61,10 +58,9 @@ TEST(test_ichiprt1_enhancedStereo, test_OutputINCHI_StereoLayer_enhanced_stereo_ oad->v3000->lists_sterac[1][3] = 3; // member atom numbers oad->v3000->n_sterel = 2; - oad->v3000->lists_sterel = (int **)inchi_calloc(1, sizeof(int*)); - for (int i = 0; i < oad->v3000->n_sterel; i++) { - oad->v3000->lists_sterel[i] = (int *)inchi_calloc(1, sizeof(int)); - } + oad->v3000->lists_sterel = (int **)inchi_calloc(oad->v3000->n_sterel, sizeof(int*)); + oad->v3000->lists_sterel[0] = (int *)inchi_calloc(2 + 2, sizeof(int)); + oad->v3000->lists_sterel[1] = (int *)inchi_calloc(2 + 1, sizeof(int)); // STEREL1 ATOMS=(2 12 13) oad->v3000->lists_sterel[0][0] = 1; // n from "STERELn" tag @@ -93,7 +89,7 @@ TEST(test_ichiprt1_enhancedStereo, test_OutputINCHI_StereoLayer_enhanced_stereo_ INChI_Aux *pAux = Alloc_INChI_Aux(num_at, num_iso_at, alloc_mode, bOrigatomflag); pAux->nNumberOfAtoms = num_at; - pAux->nOrigAtNosInCanonOrd = (AT_NUMB*)inchi_calloc(num_at, sizeof(AT_NUMB)); + // pAux->nOrigAtNosInCanonOrd = (AT_NUMB*)inchi_calloc(num_at, sizeof(AT_NUMB)); // canonical atom i+1 maps to original atom i+1 pAux->nOrigAtNosInCanonOrd[0] = 4; @@ -128,9 +124,33 @@ TEST(test_ichiprt1_enhancedStereo, test_OutputINCHI_StereoLayer_enhanced_stereo_ int ret = OutputINCHI_StereoLayer_EnhancedStereo(&cg, &out_file, &strbuf, &io, oad, (char*)"", (char*)""); // EXPECT_EQ(std::string(strbuf.pStr), "/s1(4,5)2(12,13;14)3(1;2,3)"); - EXPECT_EQ(std::string(strbuf.pStr), "/s1(1,2)2(6,7;8)3(3;4,5)"); + EXPECT_EQ(std::string(strbuf.pStr), "/s1(1,2)2(6,7)(8)3(3)(4,5)"); EXPECT_EQ(ret, 0); + for (int i = 0; i < oad->v3000->n_steabs; i++) { + inchi_free(oad->v3000->lists_steabs[i]); + } + inchi_free(oad->v3000->lists_steabs); + + for (int i = 0; i < oad->v3000->n_sterel; i++) { + inchi_free(oad->v3000->lists_sterel[i]); + } + inchi_free(oad->v3000->lists_sterel); + + for (int i = 0; i < oad->v3000->n_sterac; i++) { + inchi_free(oad->v3000->lists_sterac[i]); + } + inchi_free(oad->v3000->lists_sterac); + + inchi_free(oad->v3000); + inchi_free(oad); + + inchi_free(inchi_sort); + + FreeInpAtom(&atoms); + Free_INChI_Aux(&pAux); + Free_INChI(&inchi); + inchi_strbuf_close(&strbuf); inchi_ios_close(&out_file); } diff --git a/INCHI-1-TEST/tests/test_unit/test_ichiprt3.cpp b/INCHI-1-TEST/tests/test_unit/test_ichiprt3.cpp index b2cf3692..c4b51c77 100644 --- a/INCHI-1-TEST/tests/test_unit/test_ichiprt3.cpp +++ b/INCHI-1-TEST/tests/test_unit/test_ichiprt3.cpp @@ -68,6 +68,7 @@ TEST(test_ichiprt3, test_str_Sp2_outputs_expected_sp2_string_1) inchi_strbuf_close(&strbuf); Free_INChI_Stereo(stereo); + inchi_free(stereo); } TEST(test_ichiprt3, test_str_Sp2_outputs_expected_sp2_string_2) @@ -129,6 +130,7 @@ TEST(test_ichiprt3, test_str_Sp2_outputs_expected_sp2_string_2) inchi_strbuf_close(&strbuf); Free_INChI_Stereo(stereo); + inchi_free(stereo); } TEST(test_ichiprt3, test_str_Sp3_outputs_expected_sp3_string) diff --git a/INCHI-1-TEST/tests/test_unit/test_strutil_enhancedStereo.cpp b/INCHI-1-TEST/tests/test_unit/test_strutil_enhancedStereo.cpp index 8acabf84..07b1b288 100644 --- a/INCHI-1-TEST/tests/test_unit/test_strutil_enhancedStereo.cpp +++ b/INCHI-1-TEST/tests/test_unit/test_strutil_enhancedStereo.cpp @@ -72,7 +72,7 @@ TEST(test_strutil_enhancedStereo, test_set_EnhancedStereo_t_m_layers_1) inchi_ios_init(&input_stream, INCHI_IOS_TYPE_STRING, nullptr); inchi_ios_print_nodisplay(&input_stream, molblock); - ORIG_ATOM_DATA orig_inp_data = {}; + ORIG_ATOM_DATA *orig_inp_data = (ORIG_ATOM_DATA*)inchi_calloc(1, sizeof(ORIG_ATOM_DATA)); int bMergeAllInputStructures = 0; int bGetOrigCoord = 0; int bDoNotAddH = 0; @@ -89,7 +89,7 @@ TEST(test_strutil_enhancedStereo, test_set_EnhancedStereo_t_m_layers_1) int ret = CreateOrigInpDataFromMolfile( &input_stream, - &orig_inp_data, + orig_inp_data, bMergeAllInputStructures, bGetOrigCoord, bDoNotAddH, @@ -106,7 +106,7 @@ TEST(test_strutil_enhancedStereo, test_set_EnhancedStereo_t_m_layers_1) - int num_at = orig_inp_data.num_inp_atoms; + int num_at = orig_inp_data->num_inp_atoms; int found_num_bonds = 0; int found_num_isotopic = 0; @@ -114,13 +114,14 @@ TEST(test_strutil_enhancedStereo, test_set_EnhancedStereo_t_m_layers_1) inp_ATOM *at = CreateInpAtom(num_at); INChI *inchi = Alloc_INChI(at, num_at, &found_num_bonds, &found_num_isotopic, 0); + AT_NUMB at_tmp[] = {0,1,2,3,4,5,6,7,8}; + S_CHAR parity_tmp[] = {2,1,1,2,2,1,2,2,1}; + for (int i = 0; i < 9; i++) { + inchi->Stereo->nNumber[i] = at_tmp[i]; + inchi->Stereo->t_parity[i] = parity_tmp[i]; + } - AT_NUMB at1[9] = {1,3,4,5,6,7,8,9,10}; - S_CHAR parity[9] = {2,1,1,2,2,1,2,2,1}; - inchi->Stereo->t_parity = parity; - inchi->Stereo->nNumber = at1; inchi->Stereo->nNumberOfStereoCenters = 9; - inchi->Stereo->nCompInv2Abs = -1; inchi->nNumberOfAtoms = num_at; @@ -135,7 +136,7 @@ TEST(test_strutil_enhancedStereo, test_set_EnhancedStereo_t_m_layers_1) pAux->nNumberOfAtoms = 9; - pAux->nOrigAtNosInCanonOrd = (AT_NUMB*)inchi_calloc(num_at, sizeof(AT_NUMB)); + // pAux->nOrigAtNosInCanonOrd = (AT_NUMB*)inchi_calloc(num_at, sizeof(AT_NUMB)); pAux->nOrigAtNosInCanonOrd[0] = 4; pAux->nOrigAtNosInCanonOrd[1] = 5; @@ -147,10 +148,17 @@ TEST(test_strutil_enhancedStereo, test_set_EnhancedStereo_t_m_layers_1) pAux->nOrigAtNosInCanonOrd[7] = 14; std::cout << "Test started\n"; - ret = set_EnhancedStereo_t_m_layers(&orig_inp_data, inchi, pAux); + ret = set_EnhancedStereo_t_m_layers(orig_inp_data, inchi, pAux); std::cout << "Test finished\n"; EXPECT_EQ(ret, 1); + FreeOrigAtData(orig_inp_data); + inchi_free(orig_inp_data); + Free_INChI_Aux(&pAux); + Free_INChI(&inchi); + FreeInpAtom(&at); + + inchi_ios_free_str(&input_stream); } From 0784a9888683cb5196d5f886db769193d81b8b93 Mon Sep 17 00:00:00 2001 From: Christoph Mueller Date: Fri, 23 Jan 2026 15:13:22 +0000 Subject: [PATCH 15/44] Removed inchi warning about enh stereo and added test for multiple fragmets --- CMakeLists.txt | 10 +- INCHI-1-SRC/INCHI_BASE/src/ichiprt2.c | 3 + INCHI-1-SRC/INCHI_BASE/src/mol_fmt3.c | 14 +- INCHI-1-SRC/INCHI_BASE/src/strutil.c | 17 ++ .../tests/test_unit/test_enhancedStereo.cpp | 220 +++++++++++++++++- INCHI-1-TEST/tests/test_unit/test_inpdef.cpp | 14 +- 6 files changed, 261 insertions(+), 17 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5ac0ecc7..6b78c4d8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,8 +6,14 @@ set(CMAKE_C_STANDARD 99) set(CMAKE_CXX_STANDARD 11) set(CMAKE_POSITION_INDEPENDENT_CODE ON) -set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address -O0 -g") -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -O0 -g") +# set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address -O0 -g") +# set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -O0 -g") + +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O0 -g") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O0 -g") + +# set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -fsanitize=address -O0 -g") +# set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fsanitize=address -O0 -g") include(FetchContent) FetchContent_Declare( diff --git a/INCHI-1-SRC/INCHI_BASE/src/ichiprt2.c b/INCHI-1-SRC/INCHI_BASE/src/ichiprt2.c index 977a995f..045694f5 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/ichiprt2.c +++ b/INCHI-1-SRC/INCHI_BASE/src/ichiprt2.c @@ -2206,6 +2206,9 @@ int MakeEnhStereoString( INCHI_SORT *pINChISort, int orig_atom_num = atom_numbers[j]; //enh_stereo[i][2 + j]; int canon_atom_num = get_canonical_atom_number(pAux, orig_atom_num); + if (canon_atom_num == -1) { + printf("Error: could not find canonical atom number for original atom number %d\n", orig_atom_num); + } c_atom_numbers[j] = canon_atom_num; } diff --git a/INCHI-1-SRC/INCHI_BASE/src/mol_fmt3.c b/INCHI-1-SRC/INCHI_BASE/src/mol_fmt3.c index 2369663a..c2debef5 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/mol_fmt3.c +++ b/INCHI-1-SRC/INCHI_BASE/src/mol_fmt3.c @@ -842,13 +842,13 @@ int MolfileV3000ReadCollections(MOL_FMT_CTAB *ctab, goto err_fin; } - /* Error: No V3000 Collection end marker */ - if (ctab->v3000->n_steabs || - ctab->v3000->n_sterel || - ctab->v3000->n_sterac) - { - AddErrorMessage(pStrErr, "V3000 enhanced stereo read/stored but ignored"); - } + // /* Error: No V3000 Collection end marker */ + // if (ctab->v3000->n_steabs || + // ctab->v3000->n_sterel || + // ctab->v3000->n_sterac) + // { + // AddErrorMessage(pStrErr, "V3000 enhanced stereo read/stored but ignored"); + // } err_fin: diff --git a/INCHI-1-SRC/INCHI_BASE/src/strutil.c b/INCHI-1-SRC/INCHI_BASE/src/strutil.c index 86efdb93..db569af9 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/strutil.c +++ b/INCHI-1-SRC/INCHI_BASE/src/strutil.c @@ -4318,6 +4318,11 @@ int invert_parities(INChI *inchi, min_c_atom_num = canon_atom_num; } } + if (min_c_atom_num == (AT_NUMB)9999999) { + // return -1; + printf("ERROR %d: cannot find min canonical atom num\n", __LINE__); + continue; + } int min_c_parity_idx = get_parity_idx_from_canonical_atom_number(min_c_atom_num, inchi->Stereo->nNumber, inchi->Stereo->nNumberOfStereoCenters); @@ -4325,6 +4330,18 @@ int invert_parities(INChI *inchi, if (min_c_parity_idx == -1) { // return -1; printf("ERROR %d: cannot find parity idx for atom %d %d\n", __LINE__, min_c_atom_num, min_c_parity_idx); + for (int j = 0; j < nof_atoms; j++) { + int orig_atom_num = list_atoms[i][2 + j]; + AT_NUMB canon_atom_num = (AT_NUMB)get_canonical_atom_number(aux, orig_atom_num); + int parity_idx = get_parity_idx_from_canonical_atom_number(canon_atom_num, + inchi->Stereo->nNumber, + inchi->Stereo->nNumberOfStereoCenters); + printf(" atom %d -> canon %d -> parity idx %d -> parity %d\n", + orig_atom_num, + canon_atom_num, + parity_idx, + (parity_idx != -1) ? t_parity[parity_idx] : -1); + } } int min_c_atom_parity = t_parity[min_c_parity_idx]; diff --git a/INCHI-1-TEST/tests/test_unit/test_enhancedStereo.cpp b/INCHI-1-TEST/tests/test_unit/test_enhancedStereo.cpp index 1e6b5042..1294638a 100644 --- a/INCHI-1-TEST/tests/test_unit/test_enhancedStereo.cpp +++ b/INCHI-1-TEST/tests/test_unit/test_enhancedStereo.cpp @@ -68,8 +68,226 @@ TEST(test_enhancedStereo, test_EnhancedStereochemistry_1) inchi_Output *poutput = &output; const char expected_inchi[] = "InChI=1S/C10H14BrCl7/c1-3(11)5(13)7(15)9(17)10(18)8(16)6(14)4(2)12/h3-10H,1-2H3/t3-,4-,5+,6-,7-,8-,9+,10-/m0/s1(3,5)2(6,8)(4)3(7,9)(10)"; - EXPECT_EQ(MakeINCHIFromMolfileText(molblock, options, poutput), 1); + EXPECT_EQ(MakeINCHIFromMolfileText(molblock, options, poutput), 0); EXPECT_STREQ(poutput->szInChI, expected_inchi); + poutput->szLog = nullptr; + poutput->szMessage = nullptr; + + FreeINCHI(poutput); +} + +TEST(test_enhancedStereo, test_EnhancedStereochemistry_2_mols) +{ + const char *molblock = + "test_mol \n" + " -INDIGO-01232613552D \n" + " \n" + " 0 0 0 0 0 0 0 0 0 0 0 V3000 \n" + "M V30 BEGIN CTAB \n" + "M V30 COUNTS 36 34 0 0 0 \n" + "M V30 BEGIN ATOM \n" + "M V30 1 C 5.83301 -3.275 0.0 0 CFG=1 \n" + "M V30 2 C 4.96699 -2.775 0.0 0 CFG=1 \n" + "M V30 3 C 4.10096 -3.275 0.0 0 CFG=2 \n" + "M V30 4 C 3.23493 -2.775 0.0 0 CFG=1 \n" + "M V30 5 C 2.36891 -3.275 0.0 0 CFG=2 \n" + "M V30 6 C 1.50289 -2.775 0.0 0 \n" + "M V30 7 Br 2.36891 -4.275 0.0 0 \n" + "M V30 8 Cl 3.23493 -1.775 0.0 0 \n" + "M V30 9 Cl 4.10096 -4.275 0.0 0 \n" + "M V30 10 Cl 4.96699 -1.775 0.0 0 \n" + "M V30 11 Cl 5.83301 -4.275 0.0 0 \n" + "M V30 12 C 6.69904 -2.775 0.0 0 CFG=1 \n" + "M V30 13 C 7.56506 -3.275 0.0 0 CFG=1 \n" + "M V30 14 C 8.43109 -2.77501 0.0 0 CFG=2 \n" + "M V30 15 C 9.29711 -3.275 0.0 0 \n" + "M V30 16 Cl 8.43109 -1.77501 0.0 0 \n" + "M V30 17 Cl 7.56506 -4.275 0.0 0 \n" + "M V30 18 Cl 6.69904 -1.775 0.0 0 \n" + "M V30 19 C 6.13302 -7.62502 0.0 0 CFG=2 \n" + "M V30 20 C 5.26698 -7.12498 0.0 0 CFG=1 \n" + "M V30 21 C 4.40101 -7.62502 0.0 0 CFG=2 \n" + "M V30 22 C 3.53496 -7.12498 0.0 0 CFG=1 \n" + "M V30 23 C 2.66891 -7.62502 0.0 0 CFG=2 \n" + "M V30 24 C 6.99899 -7.12498 0.0 0 CFG=2 \n" + "M V30 25 C 7.86504 -7.62502 0.0 0 CFG=2 \n" + "M V30 26 C 8.73109 -7.12498 0.0 0 CFG=2 \n" + "M V30 27 C 1.80295 -7.12498 0.0 0 \n" + "M V30 28 Br 2.66891 -8.62501 0.0 0 \n" + "M V30 29 Cl 3.53496 -6.12499 0.0 0 \n" + "M V30 30 Cl 4.40101 -8.62501 0.0 0 \n" + "M V30 31 Cl 5.26698 -6.12499 0.0 0 \n" + "M V30 32 Cl 6.13302 -8.62501 0.0 0 \n" + "M V30 33 C 9.59705 -7.62502 0.0 0 \n" + "M V30 34 Cl 8.73109 -6.12499 0.0 0 \n" + "M V30 35 Cl 7.86504 -8.62501 0.0 0 \n" + "M V30 36 Cl 6.99899 -6.12499 0.0 0 \n" + "M V30 END ATOM \n" + "M V30 BEGIN BOND \n" + "M V30 1 1 1 2 \n" + "M V30 2 1 1 11 CFG=3 \n" + "M V30 3 1 1 12 \n" + "M V30 4 1 2 3 \n" + "M V30 5 1 2 10 CFG=1 \n" + "M V30 6 1 3 4 \n" + "M V30 7 1 3 9 CFG=1 \n" + "M V30 8 1 4 5 \n" + "M V30 9 1 4 8 CFG=1 \n" + "M V30 10 1 5 6 \n" + "M V30 11 1 5 7 CFG=1 \n" + "M V30 12 1 12 13 \n" + "M V30 13 1 12 18 CFG=3 \n" + "M V30 14 1 13 14 \n" + "M V30 15 1 13 17 CFG=1 \n" + "M V30 16 1 14 15 \n" + "M V30 17 1 14 16 CFG=1 \n" + "M V30 18 1 19 20 \n" + "M V30 19 1 20 21 \n" + "M V30 20 1 21 22 \n" + "M V30 21 1 22 23 \n" + "M V30 22 1 19 24 \n" + "M V30 23 1 24 25 \n" + "M V30 24 1 25 26 \n" + "M V30 25 1 23 27 \n" + "M V30 26 1 23 28 CFG=1 \n" + "M V30 27 1 22 29 CFG=1 \n" + "M V30 28 1 21 30 CFG=1 \n" + "M V30 29 1 20 31 CFG=1 \n" + "M V30 30 1 19 32 CFG=3 \n" + "M V30 31 1 26 33 \n" + "M V30 32 1 26 34 CFG=1 \n" + "M V30 33 1 25 35 CFG=3 \n" + "M V30 34 1 24 36 CFG=1 \n" + "M V30 END BOND \n" + "M V30 BEGIN COLLECTION \n" + "M V30 MDLV30/STERAC2 ATOMS=(1 1) \n" + "M V30 MDLV30/STERAC1 ATOMS=(2 2 3) \n" + "M V30 MDLV30/STEABS ATOMS=(4 4 5 22 23) \n" + "M V30 MDLV30/STEREL1 ATOMS=(2 12 13) \n" + "M V30 MDLV30/STEREL2 ATOMS=(1 14) \n" + "M V30 MDLV30/STERAC4 ATOMS=(1 19) \n" + "M V30 MDLV30/STERAC3 ATOMS=(2 20 21) \n" + "M V30 MDLV30/STEREL3 ATOMS=(2 24 25) \n" + "M V30 MDLV30/STEREL4 ATOMS=(1 26) \n" + "M V30 END COLLECTION \n" + "M V30 END CTAB \n" + "M END \n"; + + char options[] = "-EnhancedStereochemistry"; + inchi_Output output; + inchi_Output *poutput = &output; + const char expected_inchi[] = "InChI=1S/C10H14BrCl7/c1-3(11)5(13)7(15)9(17)10(18)8(16)6(14)4(2)12/h3-10H,1-2H3/t3-,4-,5+,6-,7-,8-,9+,10-/m0/s1(3,5)2(6,8)(4)3(7,9)(10)"; + + EXPECT_EQ(MakeINCHIFromMolfileText(molblock, options, poutput), 0); + EXPECT_STREQ(poutput->szInChI, expected_inchi); + + poutput->szLog = nullptr; + poutput->szMessage = nullptr; + + FreeINCHI(poutput); +} + +TEST(test_enhancedStereo, test_EnhancedStereochemistry_3_mols_molblock_wrong) +{ + const char *molblock = + "my_test_mol \n" + " -INDIGO-01232613442D \n" + " \n" + " 0 0 0 0 0 0 0 0 0 0 0 V3000 \n" + "M V30 BEGIN CTAB \n" + "M V30 COUNTS 36 34 0 0 0 \n" + "M V30 BEGIN ATOM \n" + "M V30 1 C 5.83301 -3.275 0.0 0 CFG=1 \n" + "M V30 2 C 4.96699 -2.775 0.0 0 CFG=1 \n" + "M V30 3 C 4.10096 -3.275 0.0 0 CFG=2 \n" + "M V30 4 C 3.23493 -2.775 0.0 0 CFG=1 \n" + "M V30 5 C 2.36891 -3.275 0.0 0 CFG=2 \n" + "M V30 6 C 1.50289 -2.775 0.0 0 \n" + "M V30 7 Br 2.36891 -4.275 0.0 0 \n" + "M V30 8 Cl 3.23493 -1.775 0.0 0 \n" + "M V30 9 Cl 4.10096 -4.275 0.0 0 \n" + "M V30 10 Cl 4.96699 -1.775 0.0 0 \n" + "M V30 11 Cl 5.83301 -4.275 0.0 0 \n" + "M V30 12 C 6.69904 -2.775 0.0 0 CFG=1 \n" + "M V30 13 C 7.56506 -3.275 0.0 0 CFG=1 \n" + "M V30 14 C 8.43109 -2.77501 0.0 0 CFG=2 \n" + "M V30 15 C 9.29711 -3.275 0.0 0 \n" + "M V30 16 Cl 8.43109 -1.77501 0.0 0 \n" + "M V30 17 Cl 7.56506 -4.275 0.0 0 \n" + "M V30 18 Cl 6.69904 -1.775 0.0 0 \n" + "M V30 19 C 6.13302 -7.62502 0.0 0 CFG=2 \n" + "M V30 20 C 5.26698 -7.12498 0.0 0 CFG=1 \n" + "M V30 21 C 4.40101 -7.62502 0.0 0 CFG=2 \n" + "M V30 22 C 3.53496 -7.12498 0.0 0 CFG=1 \n" + "M V30 23 C 2.66891 -7.62502 0.0 0 CFG=2 \n" + "M V30 24 C 6.99899 -7.12498 0.0 0 CFG=2 \n" + "M V30 25 C 7.86504 -7.62502 0.0 0 CFG=2 \n" + "M V30 26 C 8.73109 -7.12498 0.0 0 CFG=2 \n" + "M V30 27 C 1.80295 -7.12498 0.0 0 \n" + "M V30 28 Br 2.66891 -8.62501 0.0 0 \n" + "M V30 29 Cl 3.53496 -6.12499 0.0 0 \n" + "M V30 30 Cl 4.40101 -8.62501 0.0 0 \n" + "M V30 31 Cl 5.26698 -6.12499 0.0 0 \n" + "M V30 32 Cl 6.13302 -8.62501 0.0 0 \n" + "M V30 33 C 9.59705 -7.62502 0.0 0 \n" + "M V30 34 Cl 8.73109 -6.12499 0.0 0 \n" + "M V30 35 Cl 7.86504 -8.62501 0.0 0 \n" + "M V30 36 Cl 6.99899 -6.12499 0.0 0 \n" + "M V30 END ATOM \n" + "M V30 BEGIN BOND \n" + "M V30 1 1 1 2 \n" + "M V30 2 1 1 11 CFG=3 \n" + "M V30 3 1 1 12 \n" + "M V30 4 1 2 3 \n" + "M V30 5 1 2 10 CFG=1 \n" + "M V30 6 1 3 4 \n" + "M V30 7 1 3 9 CFG=1 \n" + "M V30 8 1 4 5 \n" + "M V30 9 1 4 8 CFG=1 \n" + "M V30 10 1 5 6 \n" + "M V30 11 1 5 7 CFG=1 \n" + "M V30 12 1 12 13 \n" + "M V30 13 1 12 18 CFG=3 \n" + "M V30 14 1 13 14 \n" + "M V30 15 1 13 17 CFG=1 \n" + "M V30 16 1 14 15 \n" + "M V30 17 1 14 16 CFG=1 \n" + "M V30 18 1 19 20 \n" + "M V30 19 1 20 21 \n" + "M V30 20 1 21 22 \n" + "M V30 21 1 22 23 \n" + "M V30 22 1 19 24 \n" + "M V30 23 1 24 25 \n" + "M V30 24 1 25 26 \n" + "M V30 25 1 23 27 \n" + "M V30 26 1 23 28 CFG=1 \n" + "M V30 27 1 22 29 CFG=1 \n" + "M V30 28 1 21 30 CFG=1 \n" + "M V30 29 1 20 31 CFG=1 \n" + "M V30 30 1 19 32 CFG=3 \n" + "M V30 31 1 26 33 \n" + "M V30 32 1 26 34 CFG=1 \n" + "M V30 33 1 25 35 CFG=3 \n" + "M V30 34 1 24 36 CFG=1 \n" + "M V30 END BOND \n" + "M V30 BEGIN COLLECTION \n" + "M V30 MDLV30/STERAC2 ATOMS=(2 1 19) \n" + "M V30 MDLV30/STERAC1 ATOMS=(4 2 3 20 21) \n" + "M V30 MDLV30/STEABS ATOMS=(4 4 5 22 23) \n" + "M V30 MDLV30/STEREL1 ATOMS=(4 12 13 24 25) \n" + "M V30 MDLV30/STEREL2 ATOMS=(2 14 26) \n" + "M V30 END COLLECTION \n" + "M V30 END CTAB \n" + "M END \n"; + + char options[] = "-EnhancedStereochemistry"; + inchi_Output output; + inchi_Output *poutput = &output; + const char expected_inchi[] = "InChI=1S/C10H14BrCl7/c1-3(11)5(13)7(15)9(17)10(18)8(16)6(14)4(2)12/h3-10H,1-2H3/t3-,4-,5+,6-,7-,8-,9+,10-/m0/s1(3,5)2(6,8)(4)3(7,9)(10)"; + + // EXPECT_EQ(MakeINCHIFromMolfileText(molblock, options, poutput), 1); + // EXPECT_STREQ(poutput->szInChI, expected_inchi); + FreeINCHI(poutput); } diff --git a/INCHI-1-TEST/tests/test_unit/test_inpdef.cpp b/INCHI-1-TEST/tests/test_unit/test_inpdef.cpp index 62df9f7d..b886314d 100644 --- a/INCHI-1-TEST/tests/test_unit/test_inpdef.cpp +++ b/INCHI-1-TEST/tests/test_unit/test_inpdef.cpp @@ -135,15 +135,15 @@ TEST(test_inpdef, test_CreateOrigInpDataFromMolfile_v3000_sgroup) // STERAC2 ATOMS=(1 1) EXPECT_EQ(orig_at_data.v3000->n_sterac, 2); - EXPECT_EQ(orig_at_data.v3000->lists_sterac[0][0], 2); // n from "STERACn" tag - EXPECT_EQ(orig_at_data.v3000->lists_sterac[0][1], 1); // number of members in collection - EXPECT_EQ(orig_at_data.v3000->lists_sterac[0][2], 1); // member atom numbers + EXPECT_EQ(orig_at_data.v3000->lists_sterac[1][0], 2); // n from "STERACn" tag + EXPECT_EQ(orig_at_data.v3000->lists_sterac[1][1], 1); // number of members in collection + EXPECT_EQ(orig_at_data.v3000->lists_sterac[1][2], 1); // member atom numbers // STERAC1 ATOMS=(2 2 3) - EXPECT_EQ(orig_at_data.v3000->lists_sterac[1][0], 1); // STERAC1 ATOMS=(2 2 3) - EXPECT_EQ(orig_at_data.v3000->lists_sterac[1][1], 2); // number of members in collection - EXPECT_EQ(orig_at_data.v3000->lists_sterac[1][2], 2); // member atom numbers - EXPECT_EQ(orig_at_data.v3000->lists_sterac[1][3], 3); // member atom numbers + EXPECT_EQ(orig_at_data.v3000->lists_sterac[0][0], 1); // STERAC1 ATOMS=(2 2 3) + EXPECT_EQ(orig_at_data.v3000->lists_sterac[0][1], 2); // number of members in collection + EXPECT_EQ(orig_at_data.v3000->lists_sterac[0][2], 2); // member atom numbers + EXPECT_EQ(orig_at_data.v3000->lists_sterac[0][3], 3); // member atom numbers EXPECT_EQ(orig_at_data.v3000->n_sterel, 2); From 9dacaa2bd6fbbeac6e101f972e9529fb9809941c Mon Sep 17 00:00:00 2001 From: Christoph Mueller Date: Mon, 26 Jan 2026 13:38:37 +0000 Subject: [PATCH 16/44] fixed s-layer duplication and unknown canonical atom mapping --- CMakeLists.txt | 7 +- .../INCHI_API/libinchi/src/CMakeLists.txt | 10 +- INCHI-1-SRC/INCHI_BASE/src/ichimake.h | 12 +- INCHI-1-SRC/INCHI_BASE/src/ichiprt1.c | 57 ++---- INCHI-1-SRC/INCHI_BASE/src/ichiprt2.c | 165 ++++++++++++++---- INCHI-1-SRC/INCHI_BASE/src/strutil.c | 34 ++-- 6 files changed, 179 insertions(+), 106 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6b78c4d8..cb2d5133 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,11 +6,8 @@ set(CMAKE_C_STANDARD 99) set(CMAKE_CXX_STANDARD 11) set(CMAKE_POSITION_INDEPENDENT_CODE ON) -# set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address -O0 -g") -# set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -O0 -g") - -set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O0 -g") -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O0 -g") +# set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address -O0 -g" CACHE STRING "Flags" FORCE) +# set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -O0 -g" CACHE STRING "Flags" FORCE) # set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -fsanitize=address -O0 -g") # set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fsanitize=address -O0 -g") diff --git a/INCHI-1-SRC/INCHI_API/libinchi/src/CMakeLists.txt b/INCHI-1-SRC/INCHI_API/libinchi/src/CMakeLists.txt index 33648da5..79952a6a 100644 --- a/INCHI-1-SRC/INCHI_API/libinchi/src/CMakeLists.txt +++ b/INCHI-1-SRC/INCHI_API/libinchi/src/CMakeLists.txt @@ -8,8 +8,10 @@ target_compile_features(libinchi_compiler_flags INTERFACE c_std_11) set(gcc_like_cxx "$") set(msvc_cxx "$") target_compile_options(libinchi_compiler_flags INTERFACE - "$<${gcc_like_cxx}:$>" - "$<${msvc_cxx}:$>" + # "$<${gcc_like_cxx}:$>" + # "$<${msvc_cxx}:$>" + "$<${gcc_like_cxx}:$>" + "$<${msvc_cxx}:$>" ) set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/lib/static") @@ -151,7 +153,7 @@ if(MATH_LIBRARY) target_link_libraries(libinchi PUBLIC ${MATH_LIBRARY}) endif() -target_compile_definitions(libinchi PRIVATE +target_compile_definitions(libinchi PRIVATE fPIC COMPILE_ANSI_ONLY TARGET_API_LIB @@ -170,4 +172,4 @@ if(UNIX AND NOT APPLE) target_link_options(libinchi PRIVATE "LINKER:--version-script=${P_LIBINCHI_MAP}/libinchi.map,-z,relro") elseif(WIN32 AND NOT MSVC) target_link_options(libinchi PRIVATE "LINKER:--version-script=${P_LIBINCHI_MAP}/libinchi.map") -endif() \ No newline at end of file +endif() diff --git a/INCHI-1-SRC/INCHI_BASE/src/ichimake.h b/INCHI-1-SRC/INCHI_BASE/src/ichimake.h index 03d55237..421fd2ca 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/ichimake.h +++ b/INCHI-1-SRC/INCHI_BASE/src/ichimake.h @@ -208,14 +208,20 @@ const char *EquString( int EquVal ); INCHI_IOS_STRING *buf, int nCtMode, int *bOverflow ); - int MakeEnhStereoString( INCHI_SORT *pINChISort, + int MakeEnhStereoString( INChI_Aux *pAux, int bOutType, - int num_components, int **enh_stereo, - int nof_units, + int nof_stereo_groups, INCHI_IOS_STRING *strbuf, int nCtMode, int *bOverflow ); + int MakeSlayerString( ORIG_ATOM_DATA *orig_inp_data, + INCHI_SORT *pINChISort, + int bOutType, + int num_components, + INCHI_IOS_STRING *strbuf, + int nCtMode, + int *bOverflow ); int MakeCRVString( ORIG_INFO *OrigInfo, int nLenCT, int bAddDelim, diff --git a/INCHI-1-SRC/INCHI_BASE/src/ichiprt1.c b/INCHI-1-SRC/INCHI_BASE/src/ichiprt1.c index 928f7fb9..b4c62cae 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/ichiprt1.c +++ b/INCHI-1-SRC/INCHI_BASE/src/ichiprt1.c @@ -3658,51 +3658,30 @@ int OutputINCHI_StereoLayer_EnhancedStereo( if ((io->nSegmAction = INChI_SegmentAction( io->sDifSegs[io->nCurINChISegment][DIFS_s_STYPE] ))) { - const char *p_stereo; - if (io->bRelativeStereo[io->iCurTautMode]) { - p_stereo = x_rel; - } else if (io->bRacemicStereo[io->iCurTautMode]) { - p_stereo = x_rac; - } else { - p_stereo = x_abs; - } + // const char *p_stereo; + // if (io->bRelativeStereo[io->iCurTautMode]) { + // p_stereo = x_rel; + // } else if (io->bRacemicStereo[io->iCurTautMode]) { + // p_stereo = x_rac; + // } else { + // p_stereo = x_abs; + // } szGetTag( IdentLbl, io->nTag, io->bTag2 = io->bTag1 | IL_TYPS, io->szTag2, &io->bAlways, 1 ); inchi_strbuf_reset( strbuf ); io->tot_len = 0; if (INCHI_SEGM_FILL == io->nSegmAction) { - io->tot_len += MakeDelim( x_abs, strbuf, &io->bOverflow ); // s1 - io->tot_len += MakeEnhStereoString( io->pINChISort, - io->bOutType, - io->num_components, - orig_inp_data->v3000->lists_steabs, - orig_inp_data->v3000->n_steabs, - strbuf, - 0, - &io->bOverflow); - - - io->tot_len += MakeDelim( x_rel, strbuf, &io->bOverflow ); // s2 - io->tot_len += MakeEnhStereoString( io->pINChISort, - io->bOutType, - io->num_components, - orig_inp_data->v3000->lists_sterel, - orig_inp_data->v3000->n_sterel, - strbuf, - 0, - &io->bOverflow); - - - io->tot_len += MakeDelim( x_rac, strbuf, &io->bOverflow ); // s3 - io->tot_len += MakeEnhStereoString( io->pINChISort, - io->bOutType, - io->num_components, - orig_inp_data->v3000->lists_sterac, - orig_inp_data->v3000->n_sterac, - strbuf, - 0, - &io->bOverflow); + + io->tot_len += MakeSlayerString( + orig_inp_data, + io->pINChISort, + io->bOutType, + io->num_components, + strbuf, + 0, + &io->bOverflow + ); io->bNonTautNonIsoIdentifierNotEmpty += io->bSecondNonTautPass; } diff --git a/INCHI-1-SRC/INCHI_BASE/src/ichiprt2.c b/INCHI-1-SRC/INCHI_BASE/src/ichiprt2.c index 045694f5..36207e85 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/ichiprt2.c +++ b/INCHI-1-SRC/INCHI_BASE/src/ichiprt2.c @@ -2165,30 +2165,91 @@ int compare_ints(const void *a, const void *b) { return (arg1 > arg2) - (arg1 < arg2); } -int MakeEnhStereoString( INCHI_SORT *pINChISort, +int MakeEnhStereoString( INChI_Aux *pAux, int bOutType, - int num_components, int **enh_stereo, - int nof_units, + int nof_stereo_groups, INCHI_IOS_STRING *strbuf, int nCtMode, int *bOverflow ) { int tot_len = 0; + + if (nof_stereo_groups <= 0) { + return 0; + } + + for (int i = 0; i < nof_stereo_groups; i++) { + int count_found_atoms = 0; + const int *atom_numbers = &enh_stereo[i][2]; + int nof_atoms = enh_stereo[i][1]; + int c_atom_numbers[nof_atoms]; + for (int j = 0; j < nof_atoms; j++) { + + int orig_atom_num = atom_numbers[j]; + int canon_atom_num = get_canonical_atom_number(pAux, orig_atom_num); + if (canon_atom_num != -1) { + count_found_atoms++; + } + c_atom_numbers[j] = canon_atom_num; + } + + if (count_found_atoms == 0) { + continue; + } + + if (nof_atoms > 1) { + qsort(c_atom_numbers, nof_atoms, sizeof(int), compare_ints); + } + + tot_len += MakeDelim( "(", strbuf, bOverflow ); + for (int j = 0; j < nof_atoms; j++) { + if (c_atom_numbers[j] == -1) { + continue; + } + tot_len += MakeMult_EnhStereo( c_atom_numbers[j], "", strbuf, nCtMode, bOverflow ); + + if ((j + 1) < enh_stereo[i][1]) { + tot_len += MakeDelim( ",", strbuf, bOverflow ); + } + } + tot_len += MakeDelim( ")", strbuf, bOverflow ); + + } + + return tot_len; +} + +int MakeSlayerString( ORIG_ATOM_DATA *orig_inp_data, + INCHI_SORT *pINChISort, + int bOutType, + int num_components, + INCHI_IOS_STRING *strbuf, + int nCtMode, + int *bOverflow ) +{ + + int tot_len = 0; int ii, nUsedLength0; INCHI_SORT *is, *is0; INChI_Stereo *Stereo; INChI *pINChI; INChI_Aux *pAux; - if (nof_units <= 0) { - return 0; + int DICT_SIZE = 100; + char *dictionary[DICT_SIZE]; // array of string pointers + int counts[DICT_SIZE]; // parallel array of counts + + for (int i = 0; i < DICT_SIZE; i++) { + dictionary[i] = NULL; + counts[i] = 0; } is = NULL; is0 = pINChISort; - /* For each connected component... */ + INCHI_IOS_STRING tmpbuf = {0}; + for (int cur_c = 0; !*bOverflow && cur_c < num_components; cur_c++) { @@ -2196,49 +2257,77 @@ int MakeEnhStereoString( INCHI_SORT *pINChISort, // pINChI = ( 0 <= ( ii = GET_II( bOutType, is ) ) ) ? is->pINChI[ii] : NULL; pAux = ( 0 <= ( ii = GET_II( bOutType, is ) ) ) ? is->pINChI_Aux[ii] : NULL; - tot_len += MakeDelim( "(", strbuf, bOverflow ); - for (int i = 0; i < nof_units; i++) { - - int *atom_numbers = &enh_stereo[i][2]; - int nof_atoms = enh_stereo[i][1]; - int c_atom_numbers[nof_atoms]; - for (int j = 0; j < nof_atoms; j++) { - - int orig_atom_num = atom_numbers[j]; //enh_stereo[i][2 + j]; - int canon_atom_num = get_canonical_atom_number(pAux, orig_atom_num); - if (canon_atom_num == -1) { - printf("Error: could not find canonical atom number for original atom number %d\n", orig_atom_num); - } - c_atom_numbers[j] = canon_atom_num; - + inchi_strbuf_init(&tmpbuf, INCHI_STRBUF_INITIAL_SIZE, INCHI_STRBUF_SIZE_INCREMENT); + + tot_len += MakeDelim( "1", &tmpbuf, bOverflow ); // s1 + tot_len += MakeEnhStereoString( pAux, + bOutType, + orig_inp_data->v3000->lists_steabs, + orig_inp_data->v3000->n_steabs, + &tmpbuf, + nCtMode, + bOverflow); + + tot_len += MakeDelim( "2", &tmpbuf, bOverflow ); // s2 + tot_len += MakeEnhStereoString( pAux, + bOutType, + orig_inp_data->v3000->lists_sterel, + orig_inp_data->v3000->n_sterel, + &tmpbuf, + 0, + bOverflow); + + tot_len += MakeDelim( "3", &tmpbuf, bOverflow ); // s3 + + tot_len += MakeEnhStereoString( pAux, + bOutType, + orig_inp_data->v3000->lists_sterac, + orig_inp_data->v3000->n_sterac, + &tmpbuf, + 0, + bOverflow); + + + + int found = 0; + for (int i = 0; i < DICT_SIZE; i++) { + if (dictionary[i] && strcmp(tmpbuf.pStr, dictionary[i]) == 0) { + counts[i]++; + found = 1; + break; } - - if (nof_atoms > 1) { - qsort(c_atom_numbers, nof_atoms, sizeof(int), compare_ints); + } + if (!found) { + for (int i = 0; i < DICT_SIZE; i++) { + if (dictionary[i] == NULL) { + dictionary[i] = strdup(tmpbuf.pStr); // remember to free later + counts[i] = 1; + break; + } } + } + inchi_strbuf_close(&tmpbuf); + } - for (int j = 0; j < nof_atoms; j++) { - //tot_len += MakeMult_EnhStereo( enh_stereo[i][2 + j], "", strbuf, nCtMode, bOverflow ); - tot_len += MakeMult_EnhStereo( c_atom_numbers[j], "", strbuf, nCtMode, bOverflow ); - - if ((j + 1) < enh_stereo[i][1]) { - tot_len += MakeDelim( ",", strbuf, bOverflow ); - } + int count = 0; + for (int i = 0; i < DICT_SIZE; i++) { + if (dictionary[i]) { + if (count > 0) { + tot_len += MakeDelim( ";", strbuf, bOverflow ); } - if (i + 1 < nof_units) { - // tot_len += MakeDelim( ";", strbuf, bOverflow ); - tot_len += MakeDelim( ")", strbuf, bOverflow ); - tot_len += MakeDelim( "(", strbuf, bOverflow ); + if (counts[i] > 1) { + tot_len += inchi_strbuf_printf(strbuf, "%d*%s", counts[i], dictionary[i]); + } else { + tot_len += inchi_strbuf_printf(strbuf, "%s", dictionary[i]); } + inchi_free(dictionary[i]); + count++; } - tot_len += MakeDelim( ")", strbuf, bOverflow ); - } return tot_len; } - #ifdef ALPHA_BASE #if ( ALPHA_BASE != 27 ) #error ALPHA_BASE definitions mismatch diff --git a/INCHI-1-SRC/INCHI_BASE/src/strutil.c b/INCHI-1-SRC/INCHI_BASE/src/strutil.c index db569af9..bea34ac0 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/strutil.c +++ b/INCHI-1-SRC/INCHI_BASE/src/strutil.c @@ -4320,29 +4320,29 @@ int invert_parities(INChI *inchi, } if (min_c_atom_num == (AT_NUMB)9999999) { // return -1; - printf("ERROR %d: cannot find min canonical atom num\n", __LINE__); + // printf("ERROR %d: cannot find min canonical atom num\n", __LINE__); continue; } int min_c_parity_idx = get_parity_idx_from_canonical_atom_number(min_c_atom_num, inchi->Stereo->nNumber, inchi->Stereo->nNumberOfStereoCenters); - if (min_c_parity_idx == -1) { - // return -1; - printf("ERROR %d: cannot find parity idx for atom %d %d\n", __LINE__, min_c_atom_num, min_c_parity_idx); - for (int j = 0; j < nof_atoms; j++) { - int orig_atom_num = list_atoms[i][2 + j]; - AT_NUMB canon_atom_num = (AT_NUMB)get_canonical_atom_number(aux, orig_atom_num); - int parity_idx = get_parity_idx_from_canonical_atom_number(canon_atom_num, - inchi->Stereo->nNumber, - inchi->Stereo->nNumberOfStereoCenters); - printf(" atom %d -> canon %d -> parity idx %d -> parity %d\n", - orig_atom_num, - canon_atom_num, - parity_idx, - (parity_idx != -1) ? t_parity[parity_idx] : -1); - } - } + // if (min_c_parity_idx == -1) { + // // return -1; + // printf("ERROR %d: cannot find parity idx for atom %d %d\n", __LINE__, min_c_atom_num, min_c_parity_idx); + // for (int j = 0; j < nof_atoms; j++) { + // int orig_atom_num = list_atoms[i][2 + j]; + // AT_NUMB canon_atom_num = (AT_NUMB)get_canonical_atom_number(aux, orig_atom_num); + // int parity_idx = get_parity_idx_from_canonical_atom_number(canon_atom_num, + // inchi->Stereo->nNumber, + // inchi->Stereo->nNumberOfStereoCenters); + // printf(" atom %d -> canon %d -> parity idx %d -> parity %d\n", + // orig_atom_num, + // canon_atom_num, + // parity_idx, + // (parity_idx != -1) ? t_parity[parity_idx] : -1); + // } + // } int min_c_atom_parity = t_parity[min_c_parity_idx]; if ((is_absolute == 1) && (min_c_atom_parity == 1)) { From 7c62770a38f89d923497d8bc092ffbaece0a7680 Mon Sep 17 00:00:00 2001 From: Christoph Mueller Date: Tue, 27 Jan 2026 10:41:07 +0000 Subject: [PATCH 17/44] Fixed enh stereochem, added more unit-tests and modified CMakeLists for debug testing --- CMakeLists.txt | 6 - .../INCHI_API/libinchi/src/CMakeLists.txt | 17 +- INCHI-1-SRC/INCHI_BASE/src/ichi_io.c | 2 +- INCHI-1-SRC/INCHI_BASE/src/ichimake.h | 1 + INCHI-1-SRC/INCHI_BASE/src/ichiprt2.c | 30 +- INCHI-1-SRC/INCHI_BASE/src/strutil.c | 36 +- INCHI-1-TEST/tests/test_unit/CMakeLists.txt | 6 + .../tests/test_unit/test_enhancedStereo.cpp | 403 +++++++++++++++++- .../test_unit/test_strutil_enhancedStereo.cpp | 2 +- 9 files changed, 463 insertions(+), 40 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index cb2d5133..e2b7e9f6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,12 +6,6 @@ set(CMAKE_C_STANDARD 99) set(CMAKE_CXX_STANDARD 11) set(CMAKE_POSITION_INDEPENDENT_CODE ON) -# set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address -O0 -g" CACHE STRING "Flags" FORCE) -# set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -O0 -g" CACHE STRING "Flags" FORCE) - -# set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -fsanitize=address -O0 -g") -# set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fsanitize=address -O0 -g") - include(FetchContent) FetchContent_Declare( googletest diff --git a/INCHI-1-SRC/INCHI_API/libinchi/src/CMakeLists.txt b/INCHI-1-SRC/INCHI_API/libinchi/src/CMakeLists.txt index 79952a6a..2d7cca9b 100644 --- a/INCHI-1-SRC/INCHI_API/libinchi/src/CMakeLists.txt +++ b/INCHI-1-SRC/INCHI_API/libinchi/src/CMakeLists.txt @@ -8,10 +8,15 @@ target_compile_features(libinchi_compiler_flags INTERFACE c_std_11) set(gcc_like_cxx "$") set(msvc_cxx "$") target_compile_options(libinchi_compiler_flags INTERFACE - # "$<${gcc_like_cxx}:$>" - # "$<${msvc_cxx}:$>" - "$<${gcc_like_cxx}:$>" - "$<${msvc_cxx}:$>" + # "$<$:<${gcc_like_cxx}:$>" + # "$<$:<${msvc_cxx}:$>" + # "$<$:<${gcc_like_cxx}:$>" + # "$<$:<${msvc_cxx}:$>" + + "$<$,$>:-g;-O1;-c;-fno-strict-aliasing;-Wno-all>" + "$<$,$>:-W3;-MT;-O2>" + "$<$,$>:-g;-O0;-fsanitize=address;-c;-fno-strict-aliasing;-Wno-all>" + "$<$,$>:-W3;-MT;-g;-O0>" ) set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/lib/static") @@ -167,6 +172,10 @@ string(REGEX REPLACE "/RTC(su|[1su])" "" CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAG set_target_properties(libinchi PROPERTIES PREFIX "") # set_target_properties(libinchi PROPERTIES SOVERSION 1.07 PREFIX "") +target_link_options(libinchi PRIVATE + "$<$,$>:-fsanitize=address>" +) + if(UNIX AND NOT APPLE) # set(CMAKE_SHARED_LINKER_FLAGS "-Wl,--version-script=${P_LIBINCHI_MAP}/libinchi.map,-z,relro") target_link_options(libinchi PRIVATE "LINKER:--version-script=${P_LIBINCHI_MAP}/libinchi.map,-z,relro") diff --git a/INCHI-1-SRC/INCHI_BASE/src/ichi_io.c b/INCHI-1-SRC/INCHI_BASE/src/ichi_io.c index 0575eace..3c0bdba9 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/ichi_io.c +++ b/INCHI-1-SRC/INCHI_BASE/src/ichi_io.c @@ -1679,7 +1679,7 @@ int inchi_strbuf_addline(INCHI_IOS_STRING* buf, /****************************************************************************/ /* djb-rwth: placed as a global variable to avoid function buffer issues */ char it_buffer[32767]; -int _inchi_trace(char* format, ...) +int _inchi_trace(const char* format, ...) { /* TCHAR buffer[32767]; diff --git a/INCHI-1-SRC/INCHI_BASE/src/ichimake.h b/INCHI-1-SRC/INCHI_BASE/src/ichimake.h index 421fd2ca..d3015ea6 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/ichimake.h +++ b/INCHI-1-SRC/INCHI_BASE/src/ichimake.h @@ -209,6 +209,7 @@ const char *EquString( int EquVal ); int nCtMode, int *bOverflow ); int MakeEnhStereoString( INChI_Aux *pAux, + char* conf_stereo_string, int bOutType, int **enh_stereo, int nof_stereo_groups, diff --git a/INCHI-1-SRC/INCHI_BASE/src/ichiprt2.c b/INCHI-1-SRC/INCHI_BASE/src/ichiprt2.c index 36207e85..8426e640 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/ichiprt2.c +++ b/INCHI-1-SRC/INCHI_BASE/src/ichiprt2.c @@ -2165,7 +2165,8 @@ int compare_ints(const void *a, const void *b) { return (arg1 > arg2) - (arg1 < arg2); } -int MakeEnhStereoString( INChI_Aux *pAux, +int MakeEnhStereoString( INChI_Aux *pAux, + char* conf_stereo_string, int bOutType, int **enh_stereo, int nof_stereo_groups, @@ -2174,11 +2175,13 @@ int MakeEnhStereoString( INChI_Aux *pAux, int *bOverflow ) { int tot_len = 0; - - if (nof_stereo_groups <= 0) { + int count_added = 0; + if (nof_stereo_groups < 1) { return 0; } + tot_len += MakeDelim( conf_stereo_string, strbuf, bOverflow ); + for (int i = 0; i < nof_stereo_groups; i++) { int count_found_atoms = 0; const int *atom_numbers = &enh_stereo[i][2]; @@ -2208,13 +2211,22 @@ int MakeEnhStereoString( INChI_Aux *pAux, continue; } tot_len += MakeMult_EnhStereo( c_atom_numbers[j], "", strbuf, nCtMode, bOverflow ); + count_added++; if ((j + 1) < enh_stereo[i][1]) { tot_len += MakeDelim( ",", strbuf, bOverflow ); } } tot_len += MakeDelim( ")", strbuf, bOverflow ); + } + if (count_added == 0) { + if (strbuf && strbuf->nUsedLength > 0) { + strbuf->nUsedLength--; + strbuf->pStr[strbuf->nUsedLength] = '\0'; + } + + tot_len = tot_len - 1; } return tot_len; @@ -2232,7 +2244,7 @@ int MakeSlayerString( ORIG_ATOM_DATA *orig_inp_data, int tot_len = 0; int ii, nUsedLength0; INCHI_SORT *is, *is0; - INChI_Stereo *Stereo; + // INChI_Stereo *Stereo; INChI *pINChI; INChI_Aux *pAux; @@ -2259,8 +2271,9 @@ int MakeSlayerString( ORIG_ATOM_DATA *orig_inp_data, inchi_strbuf_init(&tmpbuf, INCHI_STRBUF_INITIAL_SIZE, INCHI_STRBUF_SIZE_INCREMENT); - tot_len += MakeDelim( "1", &tmpbuf, bOverflow ); // s1 + // s1 tot_len += MakeEnhStereoString( pAux, + "1", bOutType, orig_inp_data->v3000->lists_steabs, orig_inp_data->v3000->n_steabs, @@ -2268,8 +2281,9 @@ int MakeSlayerString( ORIG_ATOM_DATA *orig_inp_data, nCtMode, bOverflow); - tot_len += MakeDelim( "2", &tmpbuf, bOverflow ); // s2 + // s2 tot_len += MakeEnhStereoString( pAux, + "2", bOutType, orig_inp_data->v3000->lists_sterel, orig_inp_data->v3000->n_sterel, @@ -2277,9 +2291,9 @@ int MakeSlayerString( ORIG_ATOM_DATA *orig_inp_data, 0, bOverflow); - tot_len += MakeDelim( "3", &tmpbuf, bOverflow ); // s3 - + // s3 tot_len += MakeEnhStereoString( pAux, + "3", bOutType, orig_inp_data->v3000->lists_sterac, orig_inp_data->v3000->n_sterac, diff --git a/INCHI-1-SRC/INCHI_BASE/src/strutil.c b/INCHI-1-SRC/INCHI_BASE/src/strutil.c index bea34ac0..4d20d72e 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/strutil.c +++ b/INCHI-1-SRC/INCHI_BASE/src/strutil.c @@ -4327,22 +4327,23 @@ int invert_parities(INChI *inchi, inchi->Stereo->nNumber, inchi->Stereo->nNumberOfStereoCenters); - // if (min_c_parity_idx == -1) { + if (min_c_parity_idx == -1) { + continue; // // return -1; - // printf("ERROR %d: cannot find parity idx for atom %d %d\n", __LINE__, min_c_atom_num, min_c_parity_idx); - // for (int j = 0; j < nof_atoms; j++) { - // int orig_atom_num = list_atoms[i][2 + j]; - // AT_NUMB canon_atom_num = (AT_NUMB)get_canonical_atom_number(aux, orig_atom_num); - // int parity_idx = get_parity_idx_from_canonical_atom_number(canon_atom_num, - // inchi->Stereo->nNumber, - // inchi->Stereo->nNumberOfStereoCenters); - // printf(" atom %d -> canon %d -> parity idx %d -> parity %d\n", - // orig_atom_num, - // canon_atom_num, - // parity_idx, - // (parity_idx != -1) ? t_parity[parity_idx] : -1); - // } - // } + // printf("ERROR %d: cannot find parity idx for atom %d %d\n", __LINE__, min_c_atom_num, min_c_parity_idx); + // for (int j = 0; j < nof_atoms; j++) { + // int orig_atom_num = list_atoms[i][2 + j]; + // AT_NUMB canon_atom_num = (AT_NUMB)get_canonical_atom_number(aux, orig_atom_num); + // int parity_idx = get_parity_idx_from_canonical_atom_number(canon_atom_num, + // inchi->Stereo->nNumber, + // inchi->Stereo->nNumberOfStereoCenters); + // printf(" atom %d -> canon %d -> parity idx %d -> parity %d\n", + // orig_atom_num, + // canon_atom_num, + // parity_idx, + // (parity_idx != -1) ? t_parity[parity_idx] : -1); + // } + } int min_c_atom_parity = t_parity[min_c_parity_idx]; if ((is_absolute == 1) && (min_c_atom_parity == 1)) { @@ -4355,6 +4356,11 @@ int invert_parities(INChI *inchi, int parity_idx = get_parity_idx_from_canonical_atom_number(canon_atom_num, inchi->Stereo->nNumber, inchi->Stereo->nNumberOfStereoCenters); + + if (parity_idx == -1) { + continue; + } + if (t_parity[parity_idx] == 2) { t_parity[parity_idx] = 1; } else if(t_parity[parity_idx] == 1) { diff --git a/INCHI-1-TEST/tests/test_unit/CMakeLists.txt b/INCHI-1-TEST/tests/test_unit/CMakeLists.txt index 248af435..0419209c 100644 --- a/INCHI-1-TEST/tests/test_unit/CMakeLists.txt +++ b/INCHI-1-TEST/tests/test_unit/CMakeLists.txt @@ -13,6 +13,12 @@ file(GLOB TEST_SOURCES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/test_*.cpp foreach(test_src ${TEST_SOURCES}) get_filename_component(test_name "${test_src}" NAME_WE) add_executable(${test_name} ${test_src}) + target_compile_options(${test_name} PRIVATE + "$<$:-g;-O0;-fsanitize=address>" + ) + target_link_options(${test_name} PRIVATE + "$<$:-g;-O0;-fsanitize=address>" + ) target_link_libraries(${test_name} PRIVATE gtest_main gmock_main test_dependencies libinchi) add_test(NAME ${test_name} COMMAND $) endforeach() diff --git a/INCHI-1-TEST/tests/test_unit/test_enhancedStereo.cpp b/INCHI-1-TEST/tests/test_unit/test_enhancedStereo.cpp index 1294638a..448cee6b 100644 --- a/INCHI-1-TEST/tests/test_unit/test_enhancedStereo.cpp +++ b/INCHI-1-TEST/tests/test_unit/test_enhancedStereo.cpp @@ -177,7 +177,7 @@ TEST(test_enhancedStereo, test_EnhancedStereochemistry_2_mols) char options[] = "-EnhancedStereochemistry"; inchi_Output output; inchi_Output *poutput = &output; - const char expected_inchi[] = "InChI=1S/C10H14BrCl7/c1-3(11)5(13)7(15)9(17)10(18)8(16)6(14)4(2)12/h3-10H,1-2H3/t3-,4-,5+,6-,7-,8-,9+,10-/m0/s1(3,5)2(6,8)(4)3(7,9)(10)"; + const char expected_inchi[] = "InChI=1S/2C10H14BrCl7/c2*1-3(11)5(13)7(15)9(17)10(18)8(16)6(14)4(2)12/h2*3-10H,1-2H3/t2*3-,4-,5+,6-,7-,8-,9+,10-/m00/s2*1(3,5)2(6,8)(4)3(7,9)(10)"; EXPECT_EQ(MakeINCHIFromMolfileText(molblock, options, poutput), 0); EXPECT_STREQ(poutput->szInChI, expected_inchi); @@ -188,7 +188,288 @@ TEST(test_enhancedStereo, test_EnhancedStereochemistry_2_mols) FreeINCHI(poutput); } -TEST(test_enhancedStereo, test_EnhancedStereochemistry_3_mols_molblock_wrong) +TEST(test_enhancedStereo, test_EnhancedStereochemistry_3_mols) +{ + const char *molblock = + "test mol \n" + " -INDIGO-01272609172D \n" + " \n" + " 0 0 0 0 0 0 0 0 0 0 0 V3000 \n" + "M V30 BEGIN CTAB \n" + "M V30 COUNTS 44 41 0 0 0 \n" + "M V30 BEGIN ATOM \n" + "M V30 1 C 5.48304 -3.77499 0.0 0 CFG=1 \n" + "M V30 2 C 4.61702 -3.27499 0.0 0 CFG=1 \n" + "M V30 3 C 3.75099 -3.77499 0.0 0 CFG=2 \n" + "M V30 4 C 2.88496 -3.27499 0.0 0 CFG=1 \n" + "M V30 5 C 2.01894 -3.77499 0.0 0 CFG=2 \n" + "M V30 6 C 1.15292 -3.27499 0.0 0 \n" + "M V30 7 Br 2.01894 -4.77499 0.0 0 \n" + "M V30 8 Cl 2.88496 -2.27499 0.0 0 \n" + "M V30 9 Cl 3.75099 -4.77499 0.0 0 \n" + "M V30 10 Cl 4.61702 -2.27499 0.0 0 \n" + "M V30 11 Cl 5.48304 -4.77499 0.0 0 \n" + "M V30 12 C 6.34907 -3.27499 0.0 0 CFG=1 \n" + "M V30 13 C 7.21509 -3.77499 0.0 0 CFG=1 \n" + "M V30 14 C 8.08112 -3.275 0.0 0 CFG=2 \n" + "M V30 15 C 8.94714 -3.77499 0.0 0 \n" + "M V30 16 Cl 8.08112 -2.275 0.0 0 \n" + "M V30 17 Cl 7.21509 -4.77499 0.0 0 \n" + "M V30 18 Cl 6.34907 -2.27499 0.0 0 \n" + "M V30 19 C 5.78305 -8.12502 0.0 0 CFG=2 \n" + "M V30 20 C 4.91701 -7.62498 0.0 0 CFG=1 \n" + "M V30 21 C 4.05104 -8.12502 0.0 0 CFG=2 \n" + "M V30 22 C 3.18499 -7.62498 0.0 0 CFG=1 \n" + "M V30 23 C 2.31894 -8.12502 0.0 0 CFG=2 \n" + "M V30 24 C 6.64902 -7.62498 0.0 0 CFG=2 \n" + "M V30 25 C 7.51507 -8.12502 0.0 0 CFG=2 \n" + "M V30 26 C 8.38112 -7.62498 0.0 0 CFG=2 \n" + "M V30 27 C 1.45298 -7.62498 0.0 0 \n" + "M V30 28 Br 2.31894 -9.12501 0.0 0 \n" + "M V30 29 Cl 3.18499 -6.62499 0.0 0 \n" + "M V30 30 Cl 4.05104 -9.12501 0.0 0 \n" + "M V30 31 Cl 4.91701 -6.62499 0.0 0 \n" + "M V30 32 Cl 5.78305 -9.12501 0.0 0 \n" + "M V30 33 C 9.24708 -8.12502 0.0 0 \n" + "M V30 34 Cl 8.38112 -6.62499 0.0 0 \n" + "M V30 35 Cl 7.51507 -9.12501 0.0 0 \n" + "M V30 36 Cl 6.64902 -6.62499 0.0 0 \n" + "M V30 37 C 14.367 -4.45 0.0 0 CFG=1 \n" + "M V30 38 C 15.233 -3.95 0.0 0 CFG=2 \n" + "M V30 39 C 16.099 -4.45 0.0 0 \n" + "M V30 40 C 15.233 -2.95 0.0 0 \n" + "M V30 41 C 13.501 -3.95 0.0 0 CFG=1 \n" + "M V30 42 C 12.6349 -4.45 0.0 0 \n" + "M V30 43 C 13.501 -2.95 0.0 0 \n" + "M V30 44 C 14.367 -5.45 0.0 0 \n" + "M V30 END ATOM \n" + "M V30 BEGIN BOND \n" + "M V30 1 1 1 2 \n" + "M V30 2 1 1 11 CFG=3 \n" + "M V30 3 1 1 12 \n" + "M V30 4 1 2 3 \n" + "M V30 5 1 2 10 CFG=1 \n" + "M V30 6 1 3 4 \n" + "M V30 7 1 3 9 CFG=1 \n" + "M V30 8 1 4 5 \n" + "M V30 9 1 4 8 CFG=1 \n" + "M V30 10 1 5 6 \n" + "M V30 11 1 5 7 CFG=1 \n" + "M V30 12 1 12 13 \n" + "M V30 13 1 12 18 CFG=3 \n" + "M V30 14 1 13 14 \n" + "M V30 15 1 13 17 CFG=1 \n" + "M V30 16 1 14 15 \n" + "M V30 17 1 14 16 CFG=1 \n" + "M V30 18 1 19 20 \n" + "M V30 19 1 20 21 \n" + "M V30 20 1 21 22 \n" + "M V30 21 1 22 23 \n" + "M V30 22 1 19 24 \n" + "M V30 23 1 24 25 \n" + "M V30 24 1 25 26 \n" + "M V30 25 1 23 27 \n" + "M V30 26 1 23 28 CFG=1 \n" + "M V30 27 1 22 29 CFG=1 \n" + "M V30 28 1 21 30 CFG=1 \n" + "M V30 29 1 20 31 CFG=1 \n" + "M V30 30 1 19 32 CFG=3 \n" + "M V30 31 1 26 33 \n" + "M V30 32 1 26 34 CFG=1 \n" + "M V30 33 1 25 35 CFG=3 \n" + "M V30 34 1 24 36 CFG=1 \n" + "M V30 35 1 37 38 \n" + "M V30 36 1 38 39 \n" + "M V30 37 1 38 40 CFG=1 \n" + "M V30 38 1 37 41 \n" + "M V30 39 1 41 42 \n" + "M V30 40 1 41 43 CFG=1 \n" + "M V30 41 1 37 44 CFG=3 \n" + "M V30 END BOND \n" + "M V30 BEGIN COLLECTION \n" + "M V30 MDLV30/STERAC2 ATOMS=(1 1) \n" + "M V30 MDLV30/STERAC1 ATOMS=(2 2 3) \n" + "M V30 MDLV30/STEABS ATOMS=(5 4 5 22 23 38) \n" + "M V30 MDLV30/STEREL1 ATOMS=(2 12 13) \n" + "M V30 MDLV30/STEREL2 ATOMS=(1 14) \n" + "M V30 MDLV30/STERAC4 ATOMS=(1 19) \n" + "M V30 MDLV30/STERAC3 ATOMS=(2 20 21) \n" + "M V30 MDLV30/STEREL3 ATOMS=(2 24 25) \n" + "M V30 MDLV30/STEREL4 ATOMS=(1 26) \n" + "M V30 MDLV30/STERAC5 ATOMS=(2 37 41) \n" + "M V30 END COLLECTION \n" + "M V30 END CTAB \n" + "M END \n"; + + + char options[] = "-EnhancedStereochemistry"; + inchi_Output output; + inchi_Output *poutput = &output; + const char expected_inchi[] = "InChI=1S/2C10H14BrCl7.C8H18/c2*1-3(11)5(13)7(15)9(17)10(18)8(16)6(14)4(2)12;1-6(2)8(5)7(3)4/h2*3-10H,1-2H3;6-8H,1-5H3/t2*3-,4-,5+,6-,7-,8-,9+,10-;/m00./s2*1(3,5)2(6,8)(4)3(7,9)(10);1(6)3(7,8)"; + + EXPECT_EQ(MakeINCHIFromMolfileText(molblock, options, poutput), 0); + EXPECT_STREQ(poutput->szInChI, expected_inchi); + + poutput->szLog = nullptr; + poutput->szMessage = nullptr; + + FreeINCHI(poutput); +} + +TEST(test_enhancedStereo, test_EnhancedStereochemistry_4_mols) +{ + const char *molblock = + "test_mol \n" + " -INDIGO-01272609502D \n" + " \n" + " 0 0 0 0 0 0 0 0 0 0 0 V3000 \n" + "M V30 BEGIN CTAB \n" + "M V30 COUNTS 57 53 0 0 0 \n" + "M V30 BEGIN ATOM \n" + "M V30 1 C 5.48304 -3.77499 0.0 0 CFG=1 \n" + "M V30 2 C 4.61702 -3.27499 0.0 0 CFG=1 \n" + "M V30 3 C 3.75099 -3.77499 0.0 0 CFG=2 \n" + "M V30 4 C 2.88496 -3.27499 0.0 0 CFG=1 \n" + "M V30 5 C 2.01894 -3.77499 0.0 0 CFG=2 \n" + "M V30 6 C 1.15292 -3.27499 0.0 0 \n" + "M V30 7 Br 2.01894 -4.77499 0.0 0 \n" + "M V30 8 Cl 2.88496 -2.27499 0.0 0 \n" + "M V30 9 Cl 3.75099 -4.77499 0.0 0 \n" + "M V30 10 Cl 4.61702 -2.27499 0.0 0 \n" + "M V30 11 Cl 5.48304 -4.77499 0.0 0 \n" + "M V30 12 C 6.34907 -3.27499 0.0 0 CFG=1 \n" + "M V30 13 C 7.21509 -3.77499 0.0 0 CFG=1 \n" + "M V30 14 C 8.08112 -3.275 0.0 0 CFG=2 \n" + "M V30 15 C 8.94714 -3.77499 0.0 0 \n" + "M V30 16 Cl 8.08112 -2.275 0.0 0 \n" + "M V30 17 Cl 7.21509 -4.77499 0.0 0 \n" + "M V30 18 Cl 6.34907 -2.27499 0.0 0 \n" + "M V30 19 C 5.78305 -8.12502 0.0 0 CFG=2 \n" + "M V30 20 C 4.91701 -7.62498 0.0 0 CFG=1 \n" + "M V30 21 C 4.05104 -8.12502 0.0 0 CFG=2 \n" + "M V30 22 C 3.18499 -7.62498 0.0 0 CFG=1 \n" + "M V30 23 C 2.31894 -8.12502 0.0 0 CFG=2 \n" + "M V30 24 C 6.64902 -7.62498 0.0 0 CFG=2 \n" + "M V30 25 C 7.51507 -8.12502 0.0 0 CFG=2 \n" + "M V30 26 C 8.38112 -7.62498 0.0 0 CFG=2 \n" + "M V30 27 C 1.45298 -7.62498 0.0 0 \n" + "M V30 28 Br 2.31894 -9.12501 0.0 0 \n" + "M V30 29 Cl 3.18499 -6.62499 0.0 0 \n" + "M V30 30 Cl 4.05104 -9.12501 0.0 0 \n" + "M V30 31 Cl 4.91701 -6.62499 0.0 0 \n" + "M V30 32 Cl 5.78305 -9.12501 0.0 0 \n" + "M V30 33 C 9.24708 -8.12502 0.0 0 \n" + "M V30 34 Cl 8.38112 -6.62499 0.0 0 \n" + "M V30 35 Cl 7.51507 -9.12501 0.0 0 \n" + "M V30 36 Cl 6.64902 -6.62499 0.0 0 \n" + "M V30 37 C 14.367 -4.45 0.0 0 CFG=1 \n" + "M V30 38 C 15.233 -3.95 0.0 0 CFG=2 \n" + "M V30 39 C 16.099 -4.45 0.0 0 \n" + "M V30 40 C 15.233 -2.95 0.0 0 \n" + "M V30 41 C 13.501 -3.95 0.0 0 CFG=1 \n" + "M V30 42 C 12.6349 -4.45 0.0 0 \n" + "M V30 43 C 13.501 -2.95 0.0 0 \n" + "M V30 44 C 14.367 -5.45 0.0 0 \n" + "M V30 45 C 11.7689 -3.95 0.0 0 \n" + "M V30 46 C 11.417 -7.75 0.0 0 \n" + "M V30 47 C 12.283 -7.25 0.0 0 CFG=2 \n" + "M V30 48 C 13.149 -7.75 0.0 0 \n" + "M V30 49 C 14.0151 -7.25 0.0 0 CFG=2 \n" + "M V30 50 C 14.8811 -7.75 0.0 0 \n" + "M V30 51 C 13.149 -8.75 0.0 0 CFG=2 \n" + "M V30 52 C 12.283 -9.25 0.0 0 \n" + "M V30 53 C 14.0151 -9.25 0.0 0 CFG=2 \n" + "M V30 54 C 14.0151 -10.25 0.0 0 \n" + "M V30 55 C 14.8811 -8.75 0.0 0 \n" + "M V30 56 C 14.0151 -6.25 0.0 0 \n" + "M V30 57 C 12.283 -6.25 0.0 0 \n" + "M V30 END ATOM \n" + "M V30 BEGIN BOND \n" + "M V30 1 1 1 2 \n" + "M V30 2 1 1 11 CFG=3 \n" + "M V30 3 1 1 12 \n" + "M V30 4 1 2 3 \n" + "M V30 5 1 2 10 CFG=1 \n" + "M V30 6 1 3 4 \n" + "M V30 7 1 3 9 CFG=1 \n" + "M V30 8 1 4 5 \n" + "M V30 9 1 4 8 CFG=1 \n" + "M V30 10 1 5 6 \n" + "M V30 11 1 5 7 CFG=1 \n" + "M V30 12 1 12 13 \n" + "M V30 13 1 12 18 CFG=3 \n" + "M V30 14 1 13 14 \n" + "M V30 15 1 13 17 CFG=1 \n" + "M V30 16 1 14 15 \n" + "M V30 17 1 14 16 CFG=1 \n" + "M V30 18 1 19 20 \n" + "M V30 19 1 20 21 \n" + "M V30 20 1 21 22 \n" + "M V30 21 1 22 23 \n" + "M V30 22 1 19 24 \n" + "M V30 23 1 24 25 \n" + "M V30 24 1 25 26 \n" + "M V30 25 1 23 27 \n" + "M V30 26 1 23 28 CFG=1 \n" + "M V30 27 1 22 29 CFG=1 \n" + "M V30 28 1 21 30 CFG=1 \n" + "M V30 29 1 20 31 CFG=1 \n" + "M V30 30 1 19 32 CFG=3 \n" + "M V30 31 1 26 33 \n" + "M V30 32 1 26 34 CFG=1 \n" + "M V30 33 1 25 35 CFG=3 \n" + "M V30 34 1 24 36 CFG=1 \n" + "M V30 35 1 37 38 \n" + "M V30 36 1 38 39 \n" + "M V30 37 1 38 40 CFG=1 \n" + "M V30 38 1 37 41 \n" + "M V30 39 1 41 42 \n" + "M V30 40 1 41 43 CFG=1 \n" + "M V30 41 1 37 44 CFG=3 \n" + "M V30 42 1 42 45 \n" + "M V30 43 1 46 47 \n" + "M V30 44 1 47 48 \n" + "M V30 45 1 48 49 \n" + "M V30 46 1 49 50 \n" + "M V30 47 1 48 51 \n" + "M V30 48 1 51 52 CFG=1 \n" + "M V30 49 1 51 53 \n" + "M V30 50 1 53 54 \n" + "M V30 51 1 53 55 CFG=1 \n" + "M V30 52 1 49 56 CFG=1 \n" + "M V30 53 1 47 57 CFG=1 \n" + "M V30 END BOND \n" + "M V30 BEGIN COLLECTION \n" + "M V30 MDLV30/STERAC2 ATOMS=(1 1) \n" + "M V30 MDLV30/STERAC1 ATOMS=(2 2 3) \n" + "M V30 MDLV30/STEABS ATOMS=(9 4 5 22 23 38 47 49 51 53) \n" + "M V30 MDLV30/STEREL1 ATOMS=(2 12 13) \n" + "M V30 MDLV30/STEREL2 ATOMS=(1 14) \n" + "M V30 MDLV30/STERAC4 ATOMS=(1 19) \n" + "M V30 MDLV30/STERAC3 ATOMS=(2 20 21) \n" + "M V30 MDLV30/STEREL3 ATOMS=(2 24 25) \n" + "M V30 MDLV30/STEREL4 ATOMS=(1 26) \n" + "M V30 MDLV30/STERAC5 ATOMS=(2 37 41) \n" + "M V30 END COLLECTION \n" + "M V30 END CTAB \n" + "M END \n"; + + + char options[] = "-EnhancedStereochemistry"; + inchi_Output output; + inchi_Output *poutput = &output; + const char expected_inchi[] = "InChI=1S/C12H26.2C10H14BrCl7.C9H20/c1-8(2)11(7)12(9(3)4)10(5)6;2*1-3(11)5(13)7(15)9(17)10(18)8(16)6(14)4(2)12;1-6-8(4)9(5)7(2)3/h8-12H,1-7H3;2*3-10H,1-2H3;7-9H,6H2,1-5H3/t11-;2*3-,4-,5+,6-,7-,8-,9+,10-;8-,9+/m1001/s1(8,9,10,11);2*1(3,5)2(6,8)(4)3(7,9)(10);1(7)3(8,9)"; + + EXPECT_EQ(MakeINCHIFromMolfileText(molblock, options, poutput), 0); + EXPECT_STREQ(poutput->szInChI, expected_inchi); + + poutput->szLog = nullptr; + poutput->szMessage = nullptr; + + FreeINCHI(poutput); +} + +TEST(test_enhancedStereo, test_EnhancedStereochemistry_2_mols_inter_enhstereo_grps) { const char *molblock = "my_test_mol \n" @@ -284,10 +565,122 @@ TEST(test_enhancedStereo, test_EnhancedStereochemistry_3_mols_molblock_wrong) char options[] = "-EnhancedStereochemistry"; inchi_Output output; inchi_Output *poutput = &output; - const char expected_inchi[] = "InChI=1S/C10H14BrCl7/c1-3(11)5(13)7(15)9(17)10(18)8(16)6(14)4(2)12/h3-10H,1-2H3/t3-,4-,5+,6-,7-,8-,9+,10-/m0/s1(3,5)2(6,8)(4)3(7,9)(10)"; + const char expected_inchi[] = "InChI=1S/2C10H14BrCl7/c2*1-3(11)5(13)7(15)9(17)10(18)8(16)6(14)4(2)12/h2*3-10H,1-2H3/t2*3-,4-,5+,6-,7-,8-,9+,10-/m00/s2*1(3,5)2(6,8)(4)3(7,9)(10)"; + + EXPECT_EQ(MakeINCHIFromMolfileText(molblock, options, poutput), 0); + EXPECT_STREQ(poutput->szInChI, expected_inchi); + + poutput->szLog = nullptr; + poutput->szMessage = nullptr; + + FreeINCHI(poutput); +} + +TEST(test_enhancedStereo, test_EnhancedStereochemistry_2_different_mols_inter_enhstereo_grps) +{ + const char *molblock = + "test_mols \n" + " -INDIGO-01272610042D \n" + " \n" + " 0 0 0 0 0 0 0 0 0 0 0 V3000 \n" + "M V30 BEGIN CTAB \n" + "M V30 COUNTS 37 35 0 0 0 \n" + "M V30 BEGIN ATOM \n" + "M V30 1 C 5.43304 -3.47499 0.0 0 CFG=1 \n" + "M V30 2 C 4.56702 -2.97499 0.0 0 CFG=1 \n" + "M V30 3 C 3.70099 -3.47499 0.0 0 CFG=2 \n" + "M V30 4 C 2.83496 -2.97499 0.0 0 CFG=1 \n" + "M V30 5 C 1.96894 -3.47499 0.0 0 CFG=2 \n" + "M V30 6 C 1.10292 -2.97499 0.0 0 \n" + "M V30 7 Br 1.96894 -4.47499 0.0 0 \n" + "M V30 8 Cl 2.83496 -1.97499 0.0 0 \n" + "M V30 9 Cl 3.70099 -4.47499 0.0 0 \n" + "M V30 10 Cl 4.56702 -1.97499 0.0 0 \n" + "M V30 11 Cl 5.43304 -4.47499 0.0 0 \n" + "M V30 12 C 6.29907 -2.97499 0.0 0 CFG=1 \n" + "M V30 13 C 7.16509 -3.47499 0.0 0 CFG=1 \n" + "M V30 14 C 8.03112 -2.975 0.0 0 CFG=2 \n" + "M V30 15 C 8.89714 -3.47499 0.0 0 \n" + "M V30 16 Cl 8.03112 -1.975 0.0 0 \n" + "M V30 17 Cl 7.16509 -4.47499 0.0 0 \n" + "M V30 18 Cl 6.29907 -1.97499 0.0 0 \n" + "M V30 19 C 5.73305 -7.82502 0.0 0 CFG=2 \n" + "M V30 20 C 4.86701 -7.32498 0.0 0 CFG=1 \n" + "M V30 21 C 4.00104 -7.82502 0.0 0 CFG=2 \n" + "M V30 22 C 3.13499 -7.32498 0.0 0 CFG=1 \n" + "M V30 23 C 2.26894 -7.82502 0.0 0 CFG=2 \n" + "M V30 24 C 6.59902 -7.32498 0.0 0 CFG=2 \n" + "M V30 25 C 7.46507 -7.82502 0.0 0 CFG=2 \n" + "M V30 26 C 8.33112 -7.32498 0.0 0 CFG=2 \n" + "M V30 27 C 1.40298 -7.32498 0.0 0 \n" + "M V30 28 Br 2.26894 -8.82501 0.0 0 \n" + "M V30 29 Cl 3.13499 -6.32499 0.0 0 \n" + "M V30 30 Cl 4.00104 -8.82501 0.0 0 \n" + "M V30 31 Cl 4.86701 -6.32499 0.0 0 \n" + "M V30 32 Cl 5.73305 -8.82501 0.0 0 \n" + "M V30 33 C 9.19708 -7.82502 0.0 0 \n" + "M V30 34 Cl 8.33112 -6.32499 0.0 0 \n" + "M V30 35 Cl 7.46507 -8.82501 0.0 0 \n" + "M V30 36 Cl 6.59902 -6.32499 0.0 0 \n" + "M V30 37 C 9.19702 -8.82502 0.0 0 \n" + "M V30 END ATOM \n" + "M V30 BEGIN BOND \n" + "M V30 1 1 1 2 \n" + "M V30 2 1 1 11 CFG=3 \n" + "M V30 3 1 1 12 \n" + "M V30 4 1 2 3 \n" + "M V30 5 1 2 10 CFG=1 \n" + "M V30 6 1 3 4 \n" + "M V30 7 1 3 9 CFG=1 \n" + "M V30 8 1 4 5 \n" + "M V30 9 1 4 8 CFG=1 \n" + "M V30 10 1 5 6 \n" + "M V30 11 1 5 7 CFG=1 \n" + "M V30 12 1 12 13 \n" + "M V30 13 1 12 18 CFG=3 \n" + "M V30 14 1 13 14 \n" + "M V30 15 1 13 17 CFG=1 \n" + "M V30 16 1 14 15 \n" + "M V30 17 1 14 16 CFG=1 \n" + "M V30 18 1 19 20 \n" + "M V30 19 1 20 21 \n" + "M V30 20 1 21 22 \n" + "M V30 21 1 22 23 \n" + "M V30 22 1 19 24 \n" + "M V30 23 1 24 25 \n" + "M V30 24 1 25 26 \n" + "M V30 25 1 23 27 \n" + "M V30 26 1 23 28 CFG=1 \n" + "M V30 27 1 22 29 CFG=1 \n" + "M V30 28 1 21 30 CFG=1 \n" + "M V30 29 1 20 31 CFG=1 \n" + "M V30 30 1 19 32 CFG=3 \n" + "M V30 31 1 26 33 \n" + "M V30 32 1 26 34 CFG=1 \n" + "M V30 33 1 25 35 CFG=3 \n" + "M V30 34 1 24 36 CFG=1 \n" + "M V30 35 1 33 37 \n" + "M V30 END BOND \n" + "M V30 BEGIN COLLECTION \n" + "M V30 MDLV30/STERAC2 ATOMS=(2 1 19) \n" + "M V30 MDLV30/STERAC1 ATOMS=(4 2 3 20 21) \n" + "M V30 MDLV30/STEABS ATOMS=(4 4 5 22 23) \n" + "M V30 MDLV30/STEREL1 ATOMS=(4 12 13 24 25) \n" + "M V30 MDLV30/STEREL2 ATOMS=(2 14 26) \n" + "M V30 END COLLECTION \n" + "M V30 END CTAB \n" + "M END \n"; + + char options[] = "-EnhancedStereochemistry"; + inchi_Output output; + inchi_Output *poutput = &output; + const char expected_inchi[] = "InChI=1S/C11H16BrCl7.C10H14BrCl7/c1-3-5(13)7(15)9(17)11(19)10(18)8(16)6(14)4(2)12;1-3(11)5(13)7(15)9(17)10(18)8(16)6(14)4(2)12/h4-11H,3H2,1-2H3;3-10H,1-2H3/t4-,5-,6+,7-,8-,9-,10+,11-;3-,4-,5+,6-,7-,8-,9+,10-/m00/s1(4,6)2(7,9)(5)3(8,10)(11);1(3,5)2(6,8)(4)3(7,9)(10)"; - // EXPECT_EQ(MakeINCHIFromMolfileText(molblock, options, poutput), 1); - // EXPECT_STREQ(poutput->szInChI, expected_inchi); + EXPECT_EQ(MakeINCHIFromMolfileText(molblock, options, poutput), 0); + EXPECT_STREQ(poutput->szInChI, expected_inchi); + + poutput->szLog = nullptr; + poutput->szMessage = nullptr; FreeINCHI(poutput); } diff --git a/INCHI-1-TEST/tests/test_unit/test_strutil_enhancedStereo.cpp b/INCHI-1-TEST/tests/test_unit/test_strutil_enhancedStereo.cpp index 07b1b288..0ff3c8ee 100644 --- a/INCHI-1-TEST/tests/test_unit/test_strutil_enhancedStereo.cpp +++ b/INCHI-1-TEST/tests/test_unit/test_strutil_enhancedStereo.cpp @@ -151,7 +151,7 @@ TEST(test_strutil_enhancedStereo, test_set_EnhancedStereo_t_m_layers_1) ret = set_EnhancedStereo_t_m_layers(orig_inp_data, inchi, pAux); std::cout << "Test finished\n"; - EXPECT_EQ(ret, 1); + EXPECT_EQ(ret, 0); FreeOrigAtData(orig_inp_data); inchi_free(orig_inp_data); From 5766fec46338cf8da859938c4162df1602cf22e6 Mon Sep 17 00:00:00 2001 From: Christoph Mueller Date: Tue, 27 Jan 2026 11:02:53 +0000 Subject: [PATCH 18/44] Adjusted assert to account for missing enh stereo chem message --- INCHI-1-TEST/tests/test_executable/test_io.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/INCHI-1-TEST/tests/test_executable/test_io.py b/INCHI-1-TEST/tests/test_executable/test_io.py index d507960f..dd38d1ed 100644 --- a/INCHI-1-TEST/tests/test_executable/test_io.py +++ b/INCHI-1-TEST/tests/test_executable/test_io.py @@ -59,7 +59,8 @@ def test_executable_rejects_more_than_999_atoms( result = run_inchi_exe(molfile_v3000_more_than_999_atoms_and_bonds) assert ( - "Error 170 (no InChI; V3000 enhanced stereo read/stored but ignored; Too many atoms [did you forget 'LargeMolecules' switch?])" + # "Error 170 (no InChI; V3000 enhanced stereo read/stored but ignored; Too many atoms [did you forget 'LargeMolecules' switch?])" + "Error 170 (no InChI; Too many atoms [did you forget 'LargeMolecules' switch?])" in result.stderr ) @@ -72,7 +73,8 @@ def test_executable_accepts_more_than_999_atoms_with_flag( ) assert ( - "Error 170 (no InChI; V3000 enhanced stereo read/stored but ignored; Too many atoms [did you forget 'LargeMolecules' switch?])" + # "Error 170 (no InChI; V3000 enhanced stereo read/stored but ignored; Too many atoms [did you forget 'LargeMolecules' switch?])" + "Error 170 (no InChI; Too many atoms [did you forget 'LargeMolecules' switch?])" not in result.stderr ) assert "Experimental mode: Up to 32766 atoms per structure" in result.stderr From 4ddd959dc6a35c55117a98f2b7588f3a5a83877f Mon Sep 17 00:00:00 2001 From: Christoph Mueller Date: Wed, 28 Jan 2026 08:22:15 +0000 Subject: [PATCH 19/44] added fix if no collection available, standard functionality will be applied --- .../INCHI_API/libinchi/src/CMakeLists.txt | 4 +- INCHI-1-SRC/INCHI_BASE/src/ichimake.h | 4 +- INCHI-1-SRC/INCHI_BASE/src/ichiprt1.c | 39 +++++++++---------- INCHI-1-SRC/INCHI_BASE/src/ichiprt2.c | 29 +++++++------- 4 files changed, 39 insertions(+), 37 deletions(-) diff --git a/INCHI-1-SRC/INCHI_API/libinchi/src/CMakeLists.txt b/INCHI-1-SRC/INCHI_API/libinchi/src/CMakeLists.txt index 2d7cca9b..43928ff5 100644 --- a/INCHI-1-SRC/INCHI_API/libinchi/src/CMakeLists.txt +++ b/INCHI-1-SRC/INCHI_API/libinchi/src/CMakeLists.txt @@ -5,8 +5,8 @@ project(LibInChI_API VERSION 1.7) add_library(libinchi_compiler_flags INTERFACE) target_compile_features(libinchi_compiler_flags INTERFACE c_std_11) -set(gcc_like_cxx "$") -set(msvc_cxx "$") +# set(gcc_like_cxx "$") +# set(msvc_cxx "$") target_compile_options(libinchi_compiler_flags INTERFACE # "$<$:<${gcc_like_cxx}:$>" # "$<$:<${msvc_cxx}:$>" diff --git a/INCHI-1-SRC/INCHI_BASE/src/ichimake.h b/INCHI-1-SRC/INCHI_BASE/src/ichimake.h index d3015ea6..8bc4faf4 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/ichimake.h +++ b/INCHI-1-SRC/INCHI_BASE/src/ichimake.h @@ -208,8 +208,8 @@ const char *EquString( int EquVal ); INCHI_IOS_STRING *buf, int nCtMode, int *bOverflow ); - int MakeEnhStereoString( INChI_Aux *pAux, - char* conf_stereo_string, + int MakeEnhStereoString( INChI_Aux *pAux, + const char* conf_stereo_string, int bOutType, int **enh_stereo, int nof_stereo_groups, diff --git a/INCHI-1-SRC/INCHI_BASE/src/ichiprt1.c b/INCHI-1-SRC/INCHI_BASE/src/ichiprt1.c index b4c62cae..99726d8b 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/ichiprt1.c +++ b/INCHI-1-SRC/INCHI_BASE/src/ichiprt1.c @@ -3657,31 +3657,30 @@ int OutputINCHI_StereoLayer_EnhancedStereo( /* s-layer */ if ((io->nSegmAction = INChI_SegmentAction( io->sDifSegs[io->nCurINChISegment][DIFS_s_STYPE] ))) { - - // const char *p_stereo; - // if (io->bRelativeStereo[io->iCurTautMode]) { - // p_stereo = x_rel; - // } else if (io->bRacemicStereo[io->iCurTautMode]) { - // p_stereo = x_rac; - // } else { - // p_stereo = x_abs; - // } + const char *p_stereo = io->bRelativeStereo[io->iCurTautMode] ? x_rel : + io->bRacemicStereo[io->iCurTautMode] ? x_rac : x_abs; szGetTag( IdentLbl, io->nTag, io->bTag2 = io->bTag1 | IL_TYPS, io->szTag2, &io->bAlways, 1 ); - inchi_strbuf_reset( strbuf ); + inchi_strbuf_reset( strbuf ); io->tot_len = 0; + io->tot_len = 0; if (INCHI_SEGM_FILL == io->nSegmAction) { - - io->tot_len += MakeSlayerString( - orig_inp_data, - io->pINChISort, - io->bOutType, - io->num_components, - strbuf, - 0, - &io->bOverflow - ); + if (orig_inp_data->v3000->n_steabs > 0 || + orig_inp_data->v3000->n_sterel > 0 || + orig_inp_data->v3000->n_sterac > 0) { + io->tot_len += MakeSlayerString( + orig_inp_data, + io->pINChISort, + io->bOutType, + io->num_components, + strbuf, + io->TAUT_MODE, + &io->bOverflow + ); + } else { + ( io->tot_len ) += MakeDelim( p_stereo, strbuf, &io->bOverflow ); + } io->bNonTautNonIsoIdentifierNotEmpty += io->bSecondNonTautPass; } diff --git a/INCHI-1-SRC/INCHI_BASE/src/ichiprt2.c b/INCHI-1-SRC/INCHI_BASE/src/ichiprt2.c index 8426e640..3fbca441 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/ichiprt2.c +++ b/INCHI-1-SRC/INCHI_BASE/src/ichiprt2.c @@ -2166,7 +2166,7 @@ int compare_ints(const void *a, const void *b) { } int MakeEnhStereoString( INChI_Aux *pAux, - char* conf_stereo_string, + const char* conf_stereo_string, int bOutType, int **enh_stereo, int nof_stereo_groups, @@ -2242,9 +2242,15 @@ int MakeSlayerString( ORIG_ATOM_DATA *orig_inp_data, { int tot_len = 0; - int ii, nUsedLength0; - INCHI_SORT *is, *is0; - // INChI_Stereo *Stereo; + int ii; + + const char* x_abs = "1"; + const char* x_rel = "2"; + const char* x_rac = "3"; + + INCHI_SORT *is = NULL; + INCHI_SORT *is0 = pINChISort; + INChI *pINChI; INChI_Aux *pAux; @@ -2257,9 +2263,6 @@ int MakeSlayerString( ORIG_ATOM_DATA *orig_inp_data, counts[i] = 0; } - is = NULL; - is0 = pINChISort; - INCHI_IOS_STRING tmpbuf = {0}; for (int cur_c = 0; !*bOverflow && cur_c < num_components; cur_c++) @@ -2273,7 +2276,7 @@ int MakeSlayerString( ORIG_ATOM_DATA *orig_inp_data, // s1 tot_len += MakeEnhStereoString( pAux, - "1", + x_abs, bOutType, orig_inp_data->v3000->lists_steabs, orig_inp_data->v3000->n_steabs, @@ -2283,22 +2286,22 @@ int MakeSlayerString( ORIG_ATOM_DATA *orig_inp_data, // s2 tot_len += MakeEnhStereoString( pAux, - "2", + x_rel, bOutType, orig_inp_data->v3000->lists_sterel, orig_inp_data->v3000->n_sterel, &tmpbuf, - 0, + nCtMode, bOverflow); // s3 tot_len += MakeEnhStereoString( pAux, - "3", + x_rac, bOutType, orig_inp_data->v3000->lists_sterac, orig_inp_data->v3000->n_sterac, &tmpbuf, - 0, + nCtMode, bOverflow); @@ -2314,7 +2317,7 @@ int MakeSlayerString( ORIG_ATOM_DATA *orig_inp_data, if (!found) { for (int i = 0; i < DICT_SIZE; i++) { if (dictionary[i] == NULL) { - dictionary[i] = strdup(tmpbuf.pStr); // remember to free later + dictionary[i] = strdup(tmpbuf.pStr); counts[i] = 1; break; } From 7e2da23920c2c087bc744cb7d2b983dd73b6157d Mon Sep 17 00:00:00 2001 From: Christoph Mueller Date: Thu, 29 Jan 2026 16:57:01 +0000 Subject: [PATCH 20/44] added m-layer change to 0 if no abs center present --- INCHI-1-SRC/INCHI_BASE/src/strutil.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/INCHI-1-SRC/INCHI_BASE/src/strutil.c b/INCHI-1-SRC/INCHI_BASE/src/strutil.c index 4d20d72e..b5d4a985 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/strutil.c +++ b/INCHI-1-SRC/INCHI_BASE/src/strutil.c @@ -4386,6 +4386,12 @@ int set_EnhancedStereo_t_m_layers( ORIG_ATOM_DATA *orig_inp_data, int ret_rac = invert_parities(inchi, aux, orig_inp_data->v3000->lists_sterac, orig_inp_data->v3000->n_sterac, 0); int ret_rel = invert_parities(inchi, aux, orig_inp_data->v3000->lists_sterel, orig_inp_data->v3000->n_sterel, 0); + if ((orig_inp_data->v3000->n_steabs == 0) && + (orig_inp_data->v3000->n_sterel > 0 || + orig_inp_data->v3000->n_sterac)) { + inchi->Stereo->nCompInv2Abs = 1; + } + return ret; } From 37ad158ebda2e67f63a784b2bf99cb1f3f593ff2 Mon Sep 17 00:00:00 2001 From: Christoph Mueller Date: Fri, 30 Jan 2026 08:16:27 +0000 Subject: [PATCH 21/44] changed m layer based on feedback (no abs) --- INCHI-1-SRC/INCHI_BASE/src/strutil.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/INCHI-1-SRC/INCHI_BASE/src/strutil.c b/INCHI-1-SRC/INCHI_BASE/src/strutil.c index b5d4a985..7c00fefb 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/strutil.c +++ b/INCHI-1-SRC/INCHI_BASE/src/strutil.c @@ -4303,6 +4303,10 @@ int invert_parities(INChI *inchi, // 2 = + // 1 = - + // m - layer + // -1 = m0 + // 1 = m1 + S_CHAR *t_parity = inchi->Stereo->t_parity; if (nof_lists > 0) @@ -4347,7 +4351,7 @@ int invert_parities(INChI *inchi, int min_c_atom_parity = t_parity[min_c_parity_idx]; if ((is_absolute == 1) && (min_c_atom_parity == 1)) { - inchi->Stereo->nCompInv2Abs = 1; + // inchi->Stereo->nCompInv2Abs = 1; } else if (min_c_atom_parity == 2) { for (int j = 0; j < nof_atoms; j++) { @@ -4369,7 +4373,7 @@ int invert_parities(INChI *inchi, } if (is_absolute) { - inchi->Stereo->nCompInv2Abs = -1; + inchi->Stereo->nCompInv2Abs = -1; //m1 } } } @@ -4389,7 +4393,7 @@ int set_EnhancedStereo_t_m_layers( ORIG_ATOM_DATA *orig_inp_data, if ((orig_inp_data->v3000->n_steabs == 0) && (orig_inp_data->v3000->n_sterel > 0 || orig_inp_data->v3000->n_sterac)) { - inchi->Stereo->nCompInv2Abs = 1; + inchi->Stereo->nCompInv2Abs = 1; //m0 } return ret; From e6fb59cb2bd550cba7845a5a022a65062f892dbc Mon Sep 17 00:00:00 2001 From: Christoph Mueller Date: Fri, 30 Jan 2026 10:09:12 +0000 Subject: [PATCH 22/44] Bugfix for mol v2 files --- INCHI-1-SRC/INCHI_BASE/src/strutil.c | 9 ++++ .../tests/test_unit/test_enhancedStereo.cpp | 46 +++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/INCHI-1-SRC/INCHI_BASE/src/strutil.c b/INCHI-1-SRC/INCHI_BASE/src/strutil.c index 7c00fefb..7d00cb3b 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/strutil.c +++ b/INCHI-1-SRC/INCHI_BASE/src/strutil.c @@ -4307,6 +4307,10 @@ int invert_parities(INChI *inchi, // -1 = m0 // 1 = m1 + if (list_atoms == NULL) { + return 1; + } + S_CHAR *t_parity = inchi->Stereo->t_parity; if (nof_lists > 0) @@ -4386,6 +4390,11 @@ int set_EnhancedStereo_t_m_layers( ORIG_ATOM_DATA *orig_inp_data, { int ret = 0; + if (!orig_inp_data->v3000) + { + return 1; + } + int ret_abs = invert_parities(inchi, aux, orig_inp_data->v3000->lists_steabs, orig_inp_data->v3000->n_steabs, 1); int ret_rac = invert_parities(inchi, aux, orig_inp_data->v3000->lists_sterac, orig_inp_data->v3000->n_sterac, 0); int ret_rel = invert_parities(inchi, aux, orig_inp_data->v3000->lists_sterel, orig_inp_data->v3000->n_sterel, 0); diff --git a/INCHI-1-TEST/tests/test_unit/test_enhancedStereo.cpp b/INCHI-1-TEST/tests/test_unit/test_enhancedStereo.cpp index 448cee6b..15b2dcc4 100644 --- a/INCHI-1-TEST/tests/test_unit/test_enhancedStereo.cpp +++ b/INCHI-1-TEST/tests/test_unit/test_enhancedStereo.cpp @@ -5,6 +5,52 @@ extern "C" #include "../../../INCHI-1-SRC/INCHI_BASE/src/inchi_api.h" } +TEST(test_enhancedStereo, test_EnhancedStereochemistry_no_collection_information) +{ + const char *molblock = + "test_mol_2 \n" + " Ketcher 1302610202D 1 1.00000 0.00000 0 \n" + " \n" + " 13 12 0 0 0 0 0 0 0 0999 V2000 \n" + " 2.9420 -4.1000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 3.8080 -3.6000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 4.6740 -4.1000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 5.5401 -3.6000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 6.4061 -4.1000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 7.2721 -3.6000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 8.1381 -4.1000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 6.4061 -5.1000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 5.5401 -2.6000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 5.5401 -5.6000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 5.5401 -6.6000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 6.4061 -2.1000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 6.4061 -1.1000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 1 2 1 0 0 0 \n" + " 2 3 1 0 0 0 \n" + " 3 4 1 0 0 0 \n" + " 4 5 1 0 0 0 \n" + " 5 6 1 0 0 0 \n" + " 6 7 1 0 0 0 \n" + " 5 8 1 0 0 0 \n" + " 4 9 1 0 0 0 \n" + " 8 10 1 0 0 0 \n" + " 10 11 1 0 0 0 \n" + " 9 12 1 0 0 0 \n" + " 12 13 1 0 0 0 \n" + "M END \n"; + + char options[] = "-EnhancedStereochemistry"; + inchi_Output output; + inchi_Output *poutput = &output; + const char expected_inchi[] = "InChI=1S/C13H28/c1-5-9-12(8-4)13(10-6-2)11-7-3/h12-13H,5-11H2,1-4H3"; + + EXPECT_EQ(MakeINCHIFromMolfileText(molblock, options, poutput), 1); + EXPECT_STREQ(poutput->szInChI, expected_inchi); + + FreeINCHI(poutput); +} + + TEST(test_enhancedStereo, test_EnhancedStereochemistry_1) { const char *molblock = From 99628bcf97bb078a279eedb09207ca2fe2afd12f Mon Sep 17 00:00:00 2001 From: Christoph Mueller Date: Fri, 30 Jan 2026 13:45:52 +0000 Subject: [PATCH 23/44] changed InChI-Prefix to InChI=1B (beta) --- INCHI-1-SRC/INCHI_BASE/src/ichiprt1.c | 4 ++++ .../tests/test_unit/test_enhancedStereo.cpp | 16 ++++++++-------- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/INCHI-1-SRC/INCHI_BASE/src/ichiprt1.c b/INCHI-1-SRC/INCHI_BASE/src/ichiprt1.c index 99726d8b..b9e5d014 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/ichiprt1.c +++ b/INCHI-1-SRC/INCHI_BASE/src/ichiprt1.c @@ -1728,6 +1728,10 @@ int OutputINChI1( CANON_GLOBALS *pCG, { is_beta = 1; } + else if (ip->bEnhancedStereo) + { + is_beta = 1; + } OutputINCHI_VersionAndKind( out_file, strbuf, bINChIOutputOptions, is_beta, pLF, pTAB ); } diff --git a/INCHI-1-TEST/tests/test_unit/test_enhancedStereo.cpp b/INCHI-1-TEST/tests/test_unit/test_enhancedStereo.cpp index 15b2dcc4..7127c739 100644 --- a/INCHI-1-TEST/tests/test_unit/test_enhancedStereo.cpp +++ b/INCHI-1-TEST/tests/test_unit/test_enhancedStereo.cpp @@ -5,7 +5,7 @@ extern "C" #include "../../../INCHI-1-SRC/INCHI_BASE/src/inchi_api.h" } -TEST(test_enhancedStereo, test_EnhancedStereochemistry_no_collection_information) +TEST(test_enhancedStereo, test_EnhancedStereochemistry_molfile_v2) { const char *molblock = "test_mol_2 \n" @@ -42,7 +42,7 @@ TEST(test_enhancedStereo, test_EnhancedStereochemistry_no_collection_information char options[] = "-EnhancedStereochemistry"; inchi_Output output; inchi_Output *poutput = &output; - const char expected_inchi[] = "InChI=1S/C13H28/c1-5-9-12(8-4)13(10-6-2)11-7-3/h12-13H,5-11H2,1-4H3"; + const char expected_inchi[] = "InChI=1B/C13H28/c1-5-9-12(8-4)13(10-6-2)11-7-3/h12-13H,5-11H2,1-4H3"; EXPECT_EQ(MakeINCHIFromMolfileText(molblock, options, poutput), 1); EXPECT_STREQ(poutput->szInChI, expected_inchi); @@ -112,7 +112,7 @@ TEST(test_enhancedStereo, test_EnhancedStereochemistry_1) char options[] = "-EnhancedStereochemistry"; inchi_Output output; inchi_Output *poutput = &output; - const char expected_inchi[] = "InChI=1S/C10H14BrCl7/c1-3(11)5(13)7(15)9(17)10(18)8(16)6(14)4(2)12/h3-10H,1-2H3/t3-,4-,5+,6-,7-,8-,9+,10-/m0/s1(3,5)2(6,8)(4)3(7,9)(10)"; + const char expected_inchi[] = "InChI=1B/C10H14BrCl7/c1-3(11)5(13)7(15)9(17)10(18)8(16)6(14)4(2)12/h3-10H,1-2H3/t3-,4-,5+,6-,7-,8-,9+,10-/m0/s1(3,5)2(6,8)(4)3(7,9)(10)"; EXPECT_EQ(MakeINCHIFromMolfileText(molblock, options, poutput), 0); EXPECT_STREQ(poutput->szInChI, expected_inchi); @@ -223,7 +223,7 @@ TEST(test_enhancedStereo, test_EnhancedStereochemistry_2_mols) char options[] = "-EnhancedStereochemistry"; inchi_Output output; inchi_Output *poutput = &output; - const char expected_inchi[] = "InChI=1S/2C10H14BrCl7/c2*1-3(11)5(13)7(15)9(17)10(18)8(16)6(14)4(2)12/h2*3-10H,1-2H3/t2*3-,4-,5+,6-,7-,8-,9+,10-/m00/s2*1(3,5)2(6,8)(4)3(7,9)(10)"; + const char expected_inchi[] = "InChI=1B/2C10H14BrCl7/c2*1-3(11)5(13)7(15)9(17)10(18)8(16)6(14)4(2)12/h2*3-10H,1-2H3/t2*3-,4-,5+,6-,7-,8-,9+,10-/m00/s2*1(3,5)2(6,8)(4)3(7,9)(10)"; EXPECT_EQ(MakeINCHIFromMolfileText(molblock, options, poutput), 0); EXPECT_STREQ(poutput->szInChI, expected_inchi); @@ -351,7 +351,7 @@ TEST(test_enhancedStereo, test_EnhancedStereochemistry_3_mols) char options[] = "-EnhancedStereochemistry"; inchi_Output output; inchi_Output *poutput = &output; - const char expected_inchi[] = "InChI=1S/2C10H14BrCl7.C8H18/c2*1-3(11)5(13)7(15)9(17)10(18)8(16)6(14)4(2)12;1-6(2)8(5)7(3)4/h2*3-10H,1-2H3;6-8H,1-5H3/t2*3-,4-,5+,6-,7-,8-,9+,10-;/m00./s2*1(3,5)2(6,8)(4)3(7,9)(10);1(6)3(7,8)"; + const char expected_inchi[] = "InChI=1B/2C10H14BrCl7.C8H18/c2*1-3(11)5(13)7(15)9(17)10(18)8(16)6(14)4(2)12;1-6(2)8(5)7(3)4/h2*3-10H,1-2H3;6-8H,1-5H3/t2*3-,4-,5+,6-,7-,8-,9+,10-;/m00./s2*1(3,5)2(6,8)(4)3(7,9)(10);1(6)3(7,8)"; EXPECT_EQ(MakeINCHIFromMolfileText(molblock, options, poutput), 0); EXPECT_STREQ(poutput->szInChI, expected_inchi); @@ -504,7 +504,7 @@ TEST(test_enhancedStereo, test_EnhancedStereochemistry_4_mols) char options[] = "-EnhancedStereochemistry"; inchi_Output output; inchi_Output *poutput = &output; - const char expected_inchi[] = "InChI=1S/C12H26.2C10H14BrCl7.C9H20/c1-8(2)11(7)12(9(3)4)10(5)6;2*1-3(11)5(13)7(15)9(17)10(18)8(16)6(14)4(2)12;1-6-8(4)9(5)7(2)3/h8-12H,1-7H3;2*3-10H,1-2H3;7-9H,6H2,1-5H3/t11-;2*3-,4-,5+,6-,7-,8-,9+,10-;8-,9+/m1001/s1(8,9,10,11);2*1(3,5)2(6,8)(4)3(7,9)(10);1(7)3(8,9)"; + const char expected_inchi[] = "InChI=1B/C12H26.2C10H14BrCl7.C9H20/c1-8(2)11(7)12(9(3)4)10(5)6;2*1-3(11)5(13)7(15)9(17)10(18)8(16)6(14)4(2)12;1-6-8(4)9(5)7(2)3/h8-12H,1-7H3;2*3-10H,1-2H3;7-9H,6H2,1-5H3/t11-;2*3-,4-,5+,6-,7-,8-,9+,10-;8-,9+/m1001/s1(8,9,10,11);2*1(3,5)2(6,8)(4)3(7,9)(10);1(7)3(8,9)"; EXPECT_EQ(MakeINCHIFromMolfileText(molblock, options, poutput), 0); EXPECT_STREQ(poutput->szInChI, expected_inchi); @@ -611,7 +611,7 @@ TEST(test_enhancedStereo, test_EnhancedStereochemistry_2_mols_inter_enhstereo_gr char options[] = "-EnhancedStereochemistry"; inchi_Output output; inchi_Output *poutput = &output; - const char expected_inchi[] = "InChI=1S/2C10H14BrCl7/c2*1-3(11)5(13)7(15)9(17)10(18)8(16)6(14)4(2)12/h2*3-10H,1-2H3/t2*3-,4-,5+,6-,7-,8-,9+,10-/m00/s2*1(3,5)2(6,8)(4)3(7,9)(10)"; + const char expected_inchi[] = "InChI=1B/2C10H14BrCl7/c2*1-3(11)5(13)7(15)9(17)10(18)8(16)6(14)4(2)12/h2*3-10H,1-2H3/t2*3-,4-,5+,6-,7-,8-,9+,10-/m00/s2*1(3,5)2(6,8)(4)3(7,9)(10)"; EXPECT_EQ(MakeINCHIFromMolfileText(molblock, options, poutput), 0); EXPECT_STREQ(poutput->szInChI, expected_inchi); @@ -720,7 +720,7 @@ TEST(test_enhancedStereo, test_EnhancedStereochemistry_2_different_mols_inter_en char options[] = "-EnhancedStereochemistry"; inchi_Output output; inchi_Output *poutput = &output; - const char expected_inchi[] = "InChI=1S/C11H16BrCl7.C10H14BrCl7/c1-3-5(13)7(15)9(17)11(19)10(18)8(16)6(14)4(2)12;1-3(11)5(13)7(15)9(17)10(18)8(16)6(14)4(2)12/h4-11H,3H2,1-2H3;3-10H,1-2H3/t4-,5-,6+,7-,8-,9-,10+,11-;3-,4-,5+,6-,7-,8-,9+,10-/m00/s1(4,6)2(7,9)(5)3(8,10)(11);1(3,5)2(6,8)(4)3(7,9)(10)"; + const char expected_inchi[] = "InChI=1B/C11H16BrCl7.C10H14BrCl7/c1-3-5(13)7(15)9(17)11(19)10(18)8(16)6(14)4(2)12;1-3(11)5(13)7(15)9(17)10(18)8(16)6(14)4(2)12/h4-11H,3H2,1-2H3;3-10H,1-2H3/t4-,5-,6+,7-,8-,9-,10+,11-;3-,4-,5+,6-,7-,8-,9+,10-/m00/s1(4,6)2(7,9)(5)3(8,10)(11);1(3,5)2(6,8)(4)3(7,9)(10)"; EXPECT_EQ(MakeINCHIFromMolfileText(molblock, options, poutput), 0); EXPECT_STREQ(poutput->szInChI, expected_inchi); From 2ca5281db198f63903501d03c9c4e64780bf0831 Mon Sep 17 00:00:00 2001 From: Christoph Mueller Date: Mon, 2 Feb 2026 13:56:30 +0000 Subject: [PATCH 24/44] added unit tests and doxygen for functions used in enhanced stereochemistry --- INCHI-1-SRC/INCHI_BASE/src/ichimake.h | 5 +- INCHI-1-SRC/INCHI_BASE/src/ichiprt1.c | 4 +- INCHI-1-SRC/INCHI_BASE/src/ichiprt2.c | 106 +- INCHI-1-SRC/INCHI_BASE/src/strutil.c | 167 +- INCHI-1-SRC/INCHI_BASE/src/strutil.h | 33 +- .../fixtures/enh_stereo_test_file_1.sdf | 1016 +++++++++ .../fixtures/enh_stereo_test_file_2.sdf | 1972 +++++++++++++++++ .../tests/test_unit/test_enhancedStereo.cpp | 101 +- .../tests/test_unit/test_ichiprt2.cpp | 201 ++ .../test_unit/test_strutil_enhancedStereo.cpp | 108 +- 10 files changed, 3608 insertions(+), 105 deletions(-) create mode 100644 INCHI-1-TEST/tests/test_unit/fixtures/enh_stereo_test_file_1.sdf create mode 100644 INCHI-1-TEST/tests/test_unit/fixtures/enh_stereo_test_file_2.sdf diff --git a/INCHI-1-SRC/INCHI_BASE/src/ichimake.h b/INCHI-1-SRC/INCHI_BASE/src/ichimake.h index 8bc4faf4..fd35db2a 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/ichimake.h +++ b/INCHI-1-SRC/INCHI_BASE/src/ichimake.h @@ -209,18 +209,17 @@ const char *EquString( int EquVal ); int nCtMode, int *bOverflow ); int MakeEnhStereoString( INChI_Aux *pAux, + INCHI_IOS_STRING *strbuf, const char* conf_stereo_string, - int bOutType, int **enh_stereo, int nof_stereo_groups, - INCHI_IOS_STRING *strbuf, int nCtMode, int *bOverflow ); int MakeSlayerString( ORIG_ATOM_DATA *orig_inp_data, INCHI_SORT *pINChISort, + INCHI_IOS_STRING *strbuf, int bOutType, int num_components, - INCHI_IOS_STRING *strbuf, int nCtMode, int *bOverflow ); int MakeCRVString( ORIG_INFO *OrigInfo, diff --git a/INCHI-1-SRC/INCHI_BASE/src/ichiprt1.c b/INCHI-1-SRC/INCHI_BASE/src/ichiprt1.c index b9e5d014..e829c84c 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/ichiprt1.c +++ b/INCHI-1-SRC/INCHI_BASE/src/ichiprt1.c @@ -3536,7 +3536,7 @@ int OutputINCHI_StereoLayer( CANON_GLOBALS *pCG, } /** - * @brief Output InChI: stereo layer with sublayers. + * @brief Output InChI: stereo layer with sublayers for enhanced stereochemistry (absolute, relative, racemic). * * @param pCG Pointer to the CANON_GLOBALS structure containing global canonicalization data. * @param out_file Pointer to the INCHI_IOSTREAM output stream where the stereo layer will be written. @@ -3676,9 +3676,9 @@ int OutputINCHI_StereoLayer_EnhancedStereo( io->tot_len += MakeSlayerString( orig_inp_data, io->pINChISort, + strbuf, io->bOutType, io->num_components, - strbuf, io->TAUT_MODE, &io->bOverflow ); diff --git a/INCHI-1-SRC/INCHI_BASE/src/ichiprt2.c b/INCHI-1-SRC/INCHI_BASE/src/ichiprt2.c index 3fbca441..4d6d620b 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/ichiprt2.c +++ b/INCHI-1-SRC/INCHI_BASE/src/ichiprt2.c @@ -471,14 +471,26 @@ int MakeMult( int mult, return 0; } -int MakeMult_EnhStereo( int mult, - const char *szTailingDelim, - INCHI_IOS_STRING *buf, - int nCtMode, - int *bOverflow ) +/** + * @brief Adds the number to the string buffer. + * + * @param number Input number to be added + * @param szTailingDelim Pointer to the trailing delimiter string + * @param buf Pointer to the output string buffer + * @param nCtMode Mode flag for string representation + * @param bOverflow Pointer to overflow flag + * @return Returns the number of characters added to the buffer + */ +int MakeNumber_EnhStereo( int number, + const char *szTailingDelim, + INCHI_IOS_STRING *buf, + int nCtMode, + int *bOverflow ) { char szValue[2048]; - int len = 0, len_delim, n; + int len = 0; + int len_delim; + int n; if (*bOverflow) { @@ -486,11 +498,11 @@ int MakeMult_EnhStereo( int mult, } if (nCtMode & CT_MODE_ABC_NUMBERS) { - len += MakeAbcNumber( szValue, ( int )sizeof( szValue ), NULL, mult ); + len += MakeAbcNumber( szValue, ( int )sizeof( szValue ), NULL, number ); } else { - len += MakeDecNumber( szValue, ( int )sizeof( szValue ), NULL, mult ); + len += MakeDecNumber( szValue, ( int )sizeof( szValue ), NULL, number ); } len_delim = (int) strlen( szTailingDelim ); @@ -500,13 +512,6 @@ int MakeMult_EnhStereo( int mult, n = inchi_strbuf_printf( buf, "%s", szValue ); if (-1 == n) *bOverflow |= 1; return n; - /* - len += len_delim; - if ( len < nLen_szLinearCT ) - { - strcpy( szLinearCT, szValue ); - return len; - }*/ } *bOverflow |= 1; @@ -2159,23 +2164,50 @@ int MakeStereoString( AT_NUMB *at1, return nLen; } +/** + * @brief Compares two integers for qsort. + * + * @param a First integer pointer. + * @param b Second integer pointer. + * @return Returns negative, zero, or positive value based on comparison. + */ int compare_ints(const void *a, const void *b) { int arg1 = *(const int *)a; int arg2 = *(const int *)b; return (arg1 > arg2) - (arg1 < arg2); } +/** + * @brief Creates the enhanced stereochemistry string for the s - layer. + * + * @param pAux Pointer to the INCHI_AUX structure. + * @param conf_stereo_string Pointer to the configuration stereochemistry string (abs, rel, rac). + * @param enh_stereo Pointer to list of enhanced stereochemistry groups. + * @param nof_stereo_groups Number of enhanced stereochemistry groups. + * @param strbuf Pointer to the output string buffer. + * @param nCtMode Mode flag for string representation. + * @param bOverflow Pointer to overflow flag. + * @return Returns the length of the created string. + */ int MakeEnhStereoString( INChI_Aux *pAux, + INCHI_IOS_STRING *strbuf, const char* conf_stereo_string, - int bOutType, int **enh_stereo, int nof_stereo_groups, - INCHI_IOS_STRING *strbuf, int nCtMode, int *bOverflow ) { int tot_len = 0; int count_added = 0; + + if (pAux == NULL) { + return 0; + } + + if (enh_stereo == NULL) { + return 0; + } + if (nof_stereo_groups < 1) { return 0; } @@ -2210,7 +2242,7 @@ int MakeEnhStereoString( INChI_Aux *pAux, if (c_atom_numbers[j] == -1) { continue; } - tot_len += MakeMult_EnhStereo( c_atom_numbers[j], "", strbuf, nCtMode, bOverflow ); + tot_len += MakeNumber_EnhStereo( c_atom_numbers[j], "", strbuf, nCtMode, bOverflow ); count_added++; if ((j + 1) < enh_stereo[i][1]) { @@ -2232,11 +2264,23 @@ int MakeEnhStereoString( INChI_Aux *pAux, return tot_len; } +/** + * @brief Creates the string for the s - layer based on the enhanced stereochemistry information. + * + * @param orig_inp_data Pointer to the original atom data. + * @param pINChISort Pointer to the INCHI_SORT structure. + * @param bOutType Output type flag. + * @param num_components Number of components in the molecule. + * @param strbuf Pointer to the output string buffer. + * @param nCtMode Mode flag for string representation. + * @param bOverflow Pointer to overflow flag. + * @return Returns the length of the created string. + */ int MakeSlayerString( ORIG_ATOM_DATA *orig_inp_data, INCHI_SORT *pINChISort, + INCHI_IOS_STRING *strbuf, int bOutType, int num_components, - INCHI_IOS_STRING *strbuf, int nCtMode, int *bOverflow ) { @@ -2248,11 +2292,11 @@ int MakeSlayerString( ORIG_ATOM_DATA *orig_inp_data, const char* x_rel = "2"; const char* x_rac = "3"; - INCHI_SORT *is = NULL; - INCHI_SORT *is0 = pINChISort; + const INCHI_SORT *is = NULL; + const INCHI_SORT *is0 = pINChISort; - INChI *pINChI; - INChI_Aux *pAux; + // INChI *pINChI = NULL; + INChI_Aux *pAux = NULL; int DICT_SIZE = 100; char *dictionary[DICT_SIZE]; // array of string pointers @@ -2276,36 +2320,31 @@ int MakeSlayerString( ORIG_ATOM_DATA *orig_inp_data, // s1 tot_len += MakeEnhStereoString( pAux, + &tmpbuf, x_abs, - bOutType, orig_inp_data->v3000->lists_steabs, orig_inp_data->v3000->n_steabs, - &tmpbuf, nCtMode, bOverflow); // s2 tot_len += MakeEnhStereoString( pAux, + &tmpbuf, x_rel, - bOutType, orig_inp_data->v3000->lists_sterel, orig_inp_data->v3000->n_sterel, - &tmpbuf, nCtMode, bOverflow); // s3 tot_len += MakeEnhStereoString( pAux, + &tmpbuf, x_rac, - bOutType, orig_inp_data->v3000->lists_sterac, orig_inp_data->v3000->n_sterac, - &tmpbuf, nCtMode, bOverflow); - - int found = 0; for (int i = 0; i < DICT_SIZE; i++) { if (dictionary[i] && strcmp(tmpbuf.pStr, dictionary[i]) == 0) { @@ -2326,6 +2365,7 @@ int MakeSlayerString( ORIG_ATOM_DATA *orig_inp_data, inchi_strbuf_close(&tmpbuf); } + // String deduplication based on dictionary and counts int count = 0; for (int i = 0; i < DICT_SIZE; i++) { if (dictionary[i]) { @@ -2333,9 +2373,9 @@ int MakeSlayerString( ORIG_ATOM_DATA *orig_inp_data, tot_len += MakeDelim( ";", strbuf, bOverflow ); } if (counts[i] > 1) { - tot_len += inchi_strbuf_printf(strbuf, "%d*%s", counts[i], dictionary[i]); + tot_len = inchi_strbuf_printf(strbuf, "%d*%s", counts[i], dictionary[i]); } else { - tot_len += inchi_strbuf_printf(strbuf, "%s", dictionary[i]); + tot_len = inchi_strbuf_printf(strbuf, "%s", dictionary[i]); } inchi_free(dictionary[i]); count++; diff --git a/INCHI-1-SRC/INCHI_BASE/src/strutil.c b/INCHI-1-SRC/INCHI_BASE/src/strutil.c index 7d00cb3b..8be6ccdd 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/strutil.c +++ b/INCHI-1-SRC/INCHI_BASE/src/strutil.c @@ -4269,9 +4269,24 @@ int cmp_components( const void *a1, const void *a2 ) return ret; } -int get_canonical_atom_number( INChI_Aux *aux, +/** + * @brief Get the canonical atom number from original atom number + * + * @param aux Pointer to INChI auxiliary data + * @param orig_atom_num Original atom number + * @return Returns the canonical atom number, or -1 if not found + */ +int get_canonical_atom_number( const INChI_Aux *aux, int orig_atom_num) { + if (aux == NULL) { + return -1; + } + + if (aux->nOrigAtNosInCanonOrd == NULL) { + return -1; + } + for (int canon_num = 1; canon_num <= aux->nNumberOfAtoms; canon_num++) { if (aux->nOrigAtNosInCanonOrd[canon_num - 1] == orig_atom_num) { return canon_num; @@ -4280,10 +4295,26 @@ int get_canonical_atom_number( INChI_Aux *aux, return -1; } +/** + * @brief Get the parity idx from canonical atom number object + * + * @param canon_atom_num Canonical atom number + * @param nNumber Pointer to array of canonical atom numbers + * @param nof_atoms Number of atoms + * @return Returns the parity index, or -1 if not found + */ int get_parity_idx_from_canonical_atom_number( int canon_atom_num, - AT_NUMB *nNumber, + const AT_NUMB *nNumber, int nof_atoms) { + if (nNumber == NULL) { + return -1; + } + + if (nof_atoms <= 0) { + return -1; + } + for (int i = 0; i < nof_atoms; i++) { if (nNumber[i] == canon_atom_num) { return i; @@ -4292,14 +4323,25 @@ int get_parity_idx_from_canonical_atom_number( int canon_atom_num, return -1; } -int invert_parities(INChI *inchi, - INChI_Aux *aux, +/** + * @brief Invert the parities for enhanced stereochemistry t- and m-layers + * + * @param inchi Pointer to INChI structure + * @param aux Pointer to INChI auxiliary data + * @param list_atoms Pointer to list of atom lists for abs, rel or rac information + * @param nof_lists Number of lists + * @param is_absolute Flag indicating if processing absolute stereochemistry + * @return Returns 0 on success, 1 if list_atoms is NULL + */ + +int invert_parities(const INChI *inchi, + const INChI_Aux *aux, int **list_atoms, int nof_lists, int is_absolute) { - // parities + // t - layer: parities // 2 = + // 1 = - @@ -4311,79 +4353,82 @@ int invert_parities(INChI *inchi, return 1; } - S_CHAR *t_parity = inchi->Stereo->t_parity; + if (inchi->Stereo == NULL || + inchi->Stereo->t_parity == NULL) { + return 1; + } - if (nof_lists > 0) + if (nof_lists == 0) { - for (int i = 0; i < nof_lists; i++) { - int nof_atoms = list_atoms[i][1]; + return 1; + } - AT_NUMB min_c_atom_num = (AT_NUMB)9999999; - for (int j = 0; j < nof_atoms; j++) { - int orig_atom_num = list_atoms[i][2 + j]; - AT_NUMB canon_atom_num = (AT_NUMB)get_canonical_atom_number(aux, orig_atom_num); - if (canon_atom_num < min_c_atom_num) { - min_c_atom_num = canon_atom_num; - } - } - if (min_c_atom_num == (AT_NUMB)9999999) { - // return -1; - // printf("ERROR %d: cannot find min canonical atom num\n", __LINE__); - continue; - } - int min_c_parity_idx = get_parity_idx_from_canonical_atom_number(min_c_atom_num, - inchi->Stereo->nNumber, - inchi->Stereo->nNumberOfStereoCenters); + S_CHAR *t_parity = inchi->Stereo->t_parity; - if (min_c_parity_idx == -1) { - continue; - // // return -1; - // printf("ERROR %d: cannot find parity idx for atom %d %d\n", __LINE__, min_c_atom_num, min_c_parity_idx); - // for (int j = 0; j < nof_atoms; j++) { - // int orig_atom_num = list_atoms[i][2 + j]; - // AT_NUMB canon_atom_num = (AT_NUMB)get_canonical_atom_number(aux, orig_atom_num); - // int parity_idx = get_parity_idx_from_canonical_atom_number(canon_atom_num, - // inchi->Stereo->nNumber, - // inchi->Stereo->nNumberOfStereoCenters); - // printf(" atom %d -> canon %d -> parity idx %d -> parity %d\n", - // orig_atom_num, - // canon_atom_num, - // parity_idx, - // (parity_idx != -1) ? t_parity[parity_idx] : -1); - // } + for (int i = 0; i < nof_lists; i++) { + int nof_atoms = list_atoms[i][1]; + + AT_NUMB min_c_atom_num = (AT_NUMB)9999999; + for (int j = 0; j < nof_atoms; j++) { + int orig_atom_num = list_atoms[i][2 + j]; + AT_NUMB canon_atom_num = (AT_NUMB)get_canonical_atom_number(aux, orig_atom_num); + if (canon_atom_num < min_c_atom_num) { + min_c_atom_num = canon_atom_num; } - int min_c_atom_parity = t_parity[min_c_parity_idx]; + } + if (min_c_atom_num == (AT_NUMB)9999999) { + // return -1; + // printf("ERROR %d: cannot find min canonical atom num\n", __LINE__); + continue; + } + int min_c_parity_idx = get_parity_idx_from_canonical_atom_number(min_c_atom_num, + inchi->Stereo->nNumber, + inchi->Stereo->nNumberOfStereoCenters); - if ((is_absolute == 1) && (min_c_atom_parity == 1)) { - // inchi->Stereo->nCompInv2Abs = 1; - } else if (min_c_atom_parity == 2) { + if (min_c_parity_idx == -1) { + continue; + } + int min_c_atom_parity = t_parity[min_c_parity_idx]; - for (int j = 0; j < nof_atoms; j++) { - int orig_atom_num = list_atoms[i][2 + j]; - AT_NUMB canon_atom_num = (AT_NUMB)get_canonical_atom_number(aux, orig_atom_num); - int parity_idx = get_parity_idx_from_canonical_atom_number(canon_atom_num, - inchi->Stereo->nNumber, - inchi->Stereo->nNumberOfStereoCenters); + if ((is_absolute == 1) && (min_c_atom_parity == 1)) { + // inchi->Stereo->nCompInv2Abs = 1; + } else if (min_c_atom_parity == 2) { - if (parity_idx == -1) { - continue; - } + for (int j = 0; j < nof_atoms; j++) { + int orig_atom_num = list_atoms[i][2 + j]; + AT_NUMB canon_atom_num = (AT_NUMB)get_canonical_atom_number(aux, orig_atom_num); + int parity_idx = get_parity_idx_from_canonical_atom_number(canon_atom_num, + inchi->Stereo->nNumber, + inchi->Stereo->nNumberOfStereoCenters); - if (t_parity[parity_idx] == 2) { - t_parity[parity_idx] = 1; - } else if(t_parity[parity_idx] == 1) { - t_parity[parity_idx] = 2; - } + if (parity_idx == -1) { + continue; } - if (is_absolute) { - inchi->Stereo->nCompInv2Abs = -1; //m1 + if (t_parity[parity_idx] == 2) { + t_parity[parity_idx] = 1; + } else if(t_parity[parity_idx] == 1) { + t_parity[parity_idx] = 2; } } + + if (is_absolute) { + inchi->Stereo->nCompInv2Abs = -1; //m1 + } } } + return 0; } +/** + * @brief Set the enhanced stereochemistry information for t- and m-layers + * + * @param orig_inp_data Pointer to original input atom data + * @param inchi Pointer to INChI structure + * @param aux Pointer to INChI auxiliary data + * @return Retruns 1 if not V3000, otherwise 0 + */ + int set_EnhancedStereo_t_m_layers( ORIG_ATOM_DATA *orig_inp_data, INChI *inchi, INChI_Aux *aux) diff --git a/INCHI-1-SRC/INCHI_BASE/src/strutil.h b/INCHI-1-SRC/INCHI_BASE/src/strutil.h index b57aed17..fc6ff812 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/strutil.h +++ b/INCHI-1-SRC/INCHI_BASE/src/strutil.h @@ -76,8 +76,37 @@ extern "C" * @param orig_atom_num Original atom number * @return Returns canonical atom number if found, -1 if not */ - int get_canonical_atom_number(INChI_Aux *aux, - int orig_atom_num); + int get_canonical_atom_number( const INChI_Aux *aux, + int orig_atom_num); + + /** + * @brief Get the parity index from canonical atom number + * + * @param canon_atom_num Canonical atom number + * @param nNumber Pointer to atom number array + * @param nof_atoms Number of atoms + * @return Returns parity index if found, -1 if not + */ + int get_parity_idx_from_canonical_atom_number( int canon_atom_num, + const AT_NUMB *nNumber, + int nof_atoms); + + /** + * @brief Invert the parities for enhanced stereochemistry t- and m-layers + * + * @param inchi Pointer to INChI structure + * @param aux Pointer to INChI auxiliary data + * @param list_atoms Pointer to list of atom lists for abs, rel or rac information + * @param nof_lists Number of lists + * @param is_absolute Flag indicating if processing absolute stereochemistry + * @return Returns 0 on success, 1 if list_atoms is NULL + */ + + int invert_parities(const INChI *inchi, + const INChI_Aux *aux, + int **list_atoms, + int nof_lists, + int is_absolute); /** * @brief Extract one (connected) component diff --git a/INCHI-1-TEST/tests/test_unit/fixtures/enh_stereo_test_file_1.sdf b/INCHI-1-TEST/tests/test_unit/fixtures/enh_stereo_test_file_1.sdf new file mode 100644 index 00000000..1bffa89f --- /dev/null +++ b/INCHI-1-TEST/tests/test_unit/fixtures/enh_stereo_test_file_1.sdf @@ -0,0 +1,1016 @@ +Structure drawings enhanced stereochemistry.sdf + ChemDraw01282608392D + + 0 0 0 0 0 0 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 7 6 0 0 1 +M V30 BEGIN ATOM +M V30 1 C -1.429260 -0.206189 0.000000 0 +M V30 2 C -0.714790 0.206311 0.000000 0 +M V30 3 O -0.714790 1.031557 0.000000 0 +M V30 4 C -0.000107 -0.206312 0.000000 0 +M V30 5 C 0.714577 0.206311 0.000000 0 +M V30 6 C 1.429260 -0.206312 0.000000 0 +M V30 7 N -0.000107 -1.031557 0.000000 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 1 1 2 +M V30 2 1 2 3 CFG=1 +M V30 3 1 2 4 +M V30 4 1 4 5 +M V30 5 1 5 6 +M V30 6 1 4 7 CFG=1 +M V30 END BOND +M V30 END CTAB +M END +$$$$ +Structure drawings enhanced stereochemistry.sdf + ChemDraw01282608392D + + 0 0 0 0 0 0 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 5 4 0 0 0 +M V30 BEGIN ATOM +M V30 1 C 1.071918 -0.206311 0.000000 0 +M V30 2 C 0.357235 -0.618934 0.000000 0 +M V30 3 C -0.357448 -0.206311 0.000000 0 +M V30 4 O -0.357448 0.618934 0.000000 0 +M V30 5 C -1.071918 -0.618811 0.000000 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 1 2 1 +M V30 2 1 3 2 +M V30 3 1 3 4 CFG=1 +M V30 4 1 5 3 +M V30 END BOND +M V30 BEGIN COLLECTION +M V30 MDLV30/STEREL1 ATOMS=(1 3) +M V30 END COLLECTION +M V30 END CTAB +M END +$$$$ +Structure drawings enhanced stereochemistry.sdf + ChemDraw01282608392D + + 0 0 0 0 0 0 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 7 6 0 0 0 +M V30 BEGIN ATOM +M V30 1 C 1.429260 -0.206311 0.000000 0 +M V30 2 C 0.714576 0.206311 0.000000 0 +M V30 3 C -0.000107 -0.206311 0.000000 0 +M V30 4 N -0.000107 -1.031556 0.000000 0 +M V30 5 C -0.714790 0.206311 0.000000 0 +M V30 6 O -0.714790 1.031556 0.000000 0 +M V30 7 C -1.429260 -0.206189 0.000000 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 1 2 1 +M V30 2 1 3 2 +M V30 3 1 3 4 CFG=1 +M V30 4 1 5 3 +M V30 5 1 5 6 CFG=1 +M V30 6 1 7 5 +M V30 END BOND +M V30 BEGIN COLLECTION +M V30 MDLV30/STEREL1 ATOMS=(2 3 5) +M V30 END COLLECTION +M V30 END CTAB +M END +$$$$ +Structure drawings enhanced stereochemistry.sdf + ChemDraw01282608392D + + 0 0 0 0 0 0 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 5 4 0 0 1 +M V30 BEGIN ATOM +M V30 1 C 1.071919 -0.206311 0.000000 0 +M V30 2 C 0.357235 -0.618934 0.000000 0 +M V30 3 C -0.357448 -0.206311 0.000000 0 +M V30 4 O -0.357448 0.618934 0.000000 0 +M V30 5 C -1.071919 -0.618811 0.000000 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 1 2 1 +M V30 2 1 3 2 +M V30 3 1 3 4 CFG=1 +M V30 4 1 5 3 +M V30 END BOND +M V30 END CTAB +M END +$$$$ +Structure drawings enhanced stereochemistry.sdf + ChemDraw01282608392D + + 0 0 0 0 0 0 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 7 6 0 0 0 +M V30 BEGIN ATOM +M V30 1 C 1.429261 -0.206311 0.000000 0 +M V30 2 C 0.714577 0.206311 0.000000 0 +M V30 3 C -0.000106 -0.206311 0.000000 0 +M V30 4 N -0.000106 -1.031556 0.000000 0 +M V30 5 C -0.714790 0.206311 0.000000 0 +M V30 6 O -0.714790 1.031556 0.000000 0 +M V30 7 C -1.429261 -0.206189 0.000000 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 1 2 1 +M V30 2 1 3 2 +M V30 3 1 3 4 CFG=1 +M V30 4 1 5 3 +M V30 5 1 5 6 CFG=1 +M V30 6 1 7 5 +M V30 END BOND +M V30 BEGIN COLLECTION +M V30 MDLV30/STEREL2 ATOMS=(1 3) +M V30 MDLV30/STEREL1 ATOMS=(1 5) +M V30 END COLLECTION +M V30 END CTAB +M END +$$$$ +Structure drawings enhanced stereochemistry.sdf + ChemDraw01282608392D + + 0 0 0 0 0 0 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 7 6 0 0 0 +M V30 BEGIN ATOM +M V30 1 C 1.429261 -0.206311 0.000000 0 +M V30 2 C 0.714578 0.206311 0.000000 0 +M V30 3 C -0.000106 -0.206311 0.000000 0 +M V30 4 N -0.000106 -1.031556 0.000000 0 +M V30 5 C -0.714790 0.206311 0.000000 0 +M V30 6 O -0.714790 1.031556 0.000000 0 +M V30 7 C -1.429261 -0.206189 0.000000 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 1 2 1 +M V30 2 1 3 2 +M V30 3 1 3 4 +M V30 4 1 5 3 +M V30 5 1 5 6 +M V30 6 1 7 5 +M V30 END BOND +M V30 END CTAB +M END +$$$$ +Structure drawings enhanced stereochemistry.sdf + ChemDraw01282608392D + + 0 0 0 0 0 0 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 7 6 0 0 0 +M V30 BEGIN ATOM +M V30 1 N -0.000106 -1.031556 0.000000 0 +M V30 2 C -0.000106 -0.206311 0.000000 0 +M V30 3 C 0.714578 0.206311 0.000000 0 +M V30 4 C 1.429261 -0.206311 0.000000 0 +M V30 5 C -0.714789 0.206311 0.000000 0 +M V30 6 O -0.714789 1.031556 0.000000 0 +M V30 7 C -1.429261 -0.206189 0.000000 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 1 2 1 CFG=1 +M V30 2 1 2 3 +M V30 3 1 3 4 +M V30 4 1 5 2 +M V30 5 1 5 6 CFG=1 +M V30 6 1 7 5 +M V30 END BOND +M V30 BEGIN COLLECTION +M V30 MDLV30/STERAC1 ATOMS=(2 2 5) +M V30 END COLLECTION +M V30 END CTAB +M END +$$$$ +Structure drawings enhanced stereochemistry.sdf + ChemDraw01282608392D + + 0 0 0 0 0 0 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 5 4 0 0 0 +M V30 BEGIN ATOM +M V30 1 C 1.071919 -0.206311 0.000000 0 +M V30 2 C 0.357236 -0.618934 0.000000 0 +M V30 3 C -0.357448 -0.206311 0.000000 0 +M V30 4 O -0.357448 0.618934 0.000000 0 +M V30 5 C -1.071919 -0.618811 0.000000 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 1 2 1 +M V30 2 1 3 2 +M V30 3 1 3 4 CFG=1 +M V30 4 1 5 3 +M V30 END BOND +M V30 BEGIN COLLECTION +M V30 MDLV30/STERAC1 ATOMS=(1 3) +M V30 END COLLECTION +M V30 END CTAB +M END +$$$$ +Structure drawings enhanced stereochemistry.sdf + ChemDraw01282608392D + + 0 0 0 0 0 0 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 5 4 0 0 0 +M V30 BEGIN ATOM +M V30 1 C 1.071919 -0.206311 0.000000 0 +M V30 2 C 0.357236 -0.618934 0.000000 0 +M V30 3 C -0.357448 -0.206311 0.000000 0 +M V30 4 O -0.357448 0.618934 0.000000 0 +M V30 5 C -1.071919 -0.618811 0.000000 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 1 2 1 +M V30 2 1 3 2 +M V30 3 1 3 4 +M V30 4 1 5 3 +M V30 END BOND +M V30 END CTAB +M END +$$$$ +Structure drawings enhanced stereochemistry.sdf + ChemDraw01282608392D + + 0 0 0 0 0 0 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 7 6 0 0 0 +M V30 BEGIN ATOM +M V30 1 N -0.000106 -1.031556 0.000000 0 +M V30 2 C -0.000106 -0.206311 0.000000 0 +M V30 3 C 0.714578 0.206311 0.000000 0 +M V30 4 C 1.429261 -0.206311 0.000000 0 +M V30 5 C -0.714789 0.206311 0.000000 0 +M V30 6 O -0.714789 1.031556 0.000000 0 +M V30 7 C -1.429261 -0.206189 0.000000 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 1 2 1 CFG=1 +M V30 2 1 2 3 +M V30 3 1 3 4 +M V30 4 1 5 2 +M V30 5 1 5 6 CFG=1 +M V30 6 1 7 5 +M V30 END BOND +M V30 BEGIN COLLECTION +M V30 MDLV30/STERAC2 ATOMS=(1 2) +M V30 MDLV30/STERAC1 ATOMS=(1 5) +M V30 END COLLECTION +M V30 END CTAB +M END +$$$$ +Structure drawings enhanced stereochemistry.sdf + ChemDraw01282608392D + + 0 0 0 0 0 0 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 9 9 0 0 0 +M V30 BEGIN ATOM +M V30 1 C -0.412473 1.071585 0.000000 0 +M V30 2 C -0.824982 0.357119 0.000000 0 +M V30 3 C -0.412509 -0.357346 0.000000 0 +M V30 4 C 0.412473 -0.357346 0.000000 0 +M V30 5 C 0.824982 0.357119 0.000000 0 +M V30 6 C 0.412509 1.071585 0.000000 0 +M V30 7 O 1.649713 0.357119 0.000000 0 +M V30 8 O 0.824839 -1.071585 0.000000 0 +M V30 9 O -1.649713 0.357119 0.000000 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 1 1 2 +M V30 2 1 2 3 +M V30 3 1 3 4 +M V30 4 1 4 5 +M V30 5 1 5 6 +M V30 6 1 1 6 +M V30 7 1 5 7 CFG=1 +M V30 8 1 4 8 CFG=1 +M V30 9 1 2 9 CFG=1 +M V30 END BOND +M V30 BEGIN COLLECTION +M V30 MDLV30/STEABS ATOMS=(2 2 5) +M V30 MDLV30/STEREL1 ATOMS=(1 4) +M V30 END COLLECTION +M V30 END CTAB +M END +$$$$ +Structure drawings enhanced stereochemistry.sdf + ChemDraw01282608392D + + 0 0 0 0 0 0 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 9 9 0 0 1 +M V30 BEGIN ATOM +M V30 1 O 0.824838 -1.071585 0.000000 0 +M V30 2 C 0.412472 -0.357346 0.000000 0 +M V30 3 C -0.412509 -0.357346 0.000000 0 +M V30 4 C -0.824982 0.357119 0.000000 0 +M V30 5 O -1.649714 0.357119 0.000000 0 +M V30 6 C -0.412473 1.071585 0.000000 0 +M V30 7 C 0.412509 1.071585 0.000000 0 +M V30 8 C 0.824982 0.357119 0.000000 0 +M V30 9 O 1.649714 0.357119 0.000000 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 1 2 1 +M V30 2 1 3 2 +M V30 3 1 4 3 +M V30 4 1 4 5 CFG=1 +M V30 5 1 6 4 +M V30 6 1 6 7 +M V30 7 1 8 7 +M V30 8 1 8 9 CFG=1 +M V30 9 1 2 8 +M V30 END BOND +M V30 END CTAB +M END +$$$$ +Structure drawings enhanced stereochemistry.sdf + ChemDraw01282608392D + + 0 0 0 0 0 0 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 9 9 0 0 0 +M V30 BEGIN ATOM +M V30 1 O 0.824838 -1.071585 0.000000 0 +M V30 2 C 0.412472 -0.357346 0.000000 0 +M V30 3 C -0.412509 -0.357346 0.000000 0 +M V30 4 C -0.824982 0.357119 0.000000 0 +M V30 5 O -1.649714 0.357119 0.000000 0 +M V30 6 C -0.412473 1.071585 0.000000 0 +M V30 7 C 0.412509 1.071585 0.000000 0 +M V30 8 C 0.824982 0.357119 0.000000 0 +M V30 9 O 1.649714 0.357119 0.000000 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 1 2 1 CFG=1 +M V30 2 1 3 2 +M V30 3 1 4 3 +M V30 4 1 4 5 CFG=1 +M V30 5 1 6 4 +M V30 6 1 6 7 +M V30 7 1 8 7 +M V30 8 1 8 9 CFG=1 +M V30 9 1 2 8 +M V30 END BOND +M V30 BEGIN COLLECTION +M V30 MDLV30/STEREL1 ATOMS=(2 2 8) +M V30 MDLV30/STEABS ATOMS=(1 4) +M V30 END COLLECTION +M V30 END CTAB +M END +$$$$ +Structure drawings enhanced stereochemistry.sdf + ChemDraw01282608392D + + 0 0 0 0 0 0 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 9 9 0 0 1 +M V30 BEGIN ATOM +M V30 1 O 0.824838 -1.071585 0.000000 0 +M V30 2 C 0.412473 -0.357346 0.000000 0 +M V30 3 C -0.412508 -0.357346 0.000000 0 +M V30 4 C -0.824981 0.357119 0.000000 0 +M V30 5 O -1.649713 0.357119 0.000000 0 +M V30 6 C -0.412472 1.071585 0.000000 0 +M V30 7 C 0.412510 1.071585 0.000000 0 +M V30 8 C 0.824983 0.357119 0.000000 0 +M V30 9 O 1.649713 0.357119 0.000000 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 1 2 1 +M V30 2 1 3 2 +M V30 3 1 4 3 +M V30 4 1 4 5 CFG=1 +M V30 5 1 6 4 +M V30 6 1 6 7 +M V30 7 1 8 7 +M V30 8 1 8 9 +M V30 9 1 2 8 +M V30 END BOND +M V30 END CTAB +M END +$$$$ +Structure drawings enhanced stereochemistry.sdf + ChemDraw01282608392D + + 0 0 0 0 0 0 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 9 9 0 0 0 +M V30 BEGIN ATOM +M V30 1 O 0.824839 -1.071585 0.000000 0 +M V30 2 C 0.412473 -0.357346 0.000000 0 +M V30 3 C -0.412508 -0.357346 0.000000 0 +M V30 4 C -0.824981 0.357119 0.000000 0 +M V30 5 O -1.649713 0.357119 0.000000 0 +M V30 6 C -0.412472 1.071585 0.000000 0 +M V30 7 C 0.412510 1.071585 0.000000 0 +M V30 8 C 0.824982 0.357119 0.000000 0 +M V30 9 O 1.649713 0.357119 0.000000 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 1 2 1 CFG=1 +M V30 2 1 3 2 +M V30 3 1 4 3 +M V30 4 1 4 5 CFG=1 +M V30 5 1 6 4 +M V30 6 1 6 7 +M V30 7 1 8 7 +M V30 8 1 8 9 CFG=3 +M V30 9 1 2 8 +M V30 END BOND +M V30 BEGIN COLLECTION +M V30 MDLV30/STEREL1 ATOMS=(2 2 8) +M V30 MDLV30/STEABS ATOMS=(1 4) +M V30 END COLLECTION +M V30 END CTAB +M END +$$$$ +Structure drawings enhanced stereochemistry.sdf + ChemDraw01282608392D + + 0 0 0 0 0 0 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 9 8 0 0 0 +M V30 BEGIN ATOM +M V30 1 C 1.071919 -0.206312 0.000000 0 +M V30 2 C 0.357236 0.206310 0.000000 0 +M V30 3 C -0.357448 -0.206312 0.000000 0 +M V30 4 N -0.357448 -1.031557 0.000000 0 +M V30 5 C -1.072131 0.206310 0.000000 0 +M V30 6 O -1.072131 1.031557 0.000000 0 +M V30 7 C -1.786602 -0.206188 0.000000 0 +M V30 8 C 0.357236 1.031557 0.000000 0 +M V30 9 C 1.786602 0.206310 0.000000 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 1 2 1 +M V30 2 1 3 2 +M V30 3 1 3 4 CFG=1 +M V30 4 1 5 3 +M V30 5 1 5 6 CFG=1 +M V30 6 1 7 5 +M V30 7 1 2 8 CFG=1 +M V30 8 1 1 9 +M V30 END BOND +M V30 BEGIN COLLECTION +M V30 MDLV30/STEREL1 ATOMS=(2 2 3) +M V30 MDLV30/STEABS ATOMS=(1 5) +M V30 END COLLECTION +M V30 END CTAB +M END +$$$$ +Structure drawings enhanced stereochemistry.sdf + ChemDraw01282608392D + + 0 0 0 0 0 0 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 9 8 0 0 1 +M V30 BEGIN ATOM +M V30 1 C 1.786602 0.206312 0.000000 0 +M V30 2 C 1.071918 -0.206312 0.000000 0 +M V30 3 C 0.357235 0.206312 0.000000 0 +M V30 4 C 0.357235 1.031557 0.000000 0 +M V30 5 C -0.357448 -0.206312 0.000000 0 +M V30 6 N -0.357448 -1.031557 0.000000 0 +M V30 7 C -1.072132 0.206312 0.000000 0 +M V30 8 O -1.072132 1.031557 0.000000 0 +M V30 9 C -1.786602 -0.206188 0.000000 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 1 2 1 +M V30 2 1 3 2 +M V30 3 1 3 4 +M V30 4 1 5 3 +M V30 5 1 5 6 +M V30 6 1 7 5 +M V30 7 1 7 8 CFG=1 +M V30 8 1 9 7 +M V30 END BOND +M V30 END CTAB +M END +$$$$ +Structure drawings enhanced stereochemistry.sdf + ChemDraw01282608392D + + 0 0 0 0 0 0 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 9 8 0 0 0 +M V30 BEGIN ATOM +M V30 1 C 1.786602 0.206312 0.000000 0 +M V30 2 C 1.071919 -0.206312 0.000000 0 +M V30 3 C 0.357236 0.206312 0.000000 0 +M V30 4 C 0.357236 1.031557 0.000000 0 +M V30 5 C -0.357448 -0.206312 0.000000 0 +M V30 6 N -0.357448 -1.031557 0.000000 0 +M V30 7 C -1.072131 0.206312 0.000000 0 +M V30 8 O -1.072131 1.031557 0.000000 0 +M V30 9 C -1.786602 -0.206188 0.000000 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 1 2 1 +M V30 2 1 3 2 +M V30 3 1 3 4 CFG=3 +M V30 4 1 5 3 +M V30 5 1 5 6 CFG=1 +M V30 6 1 7 5 +M V30 7 1 7 8 CFG=1 +M V30 8 1 9 7 +M V30 END BOND +M V30 BEGIN COLLECTION +M V30 MDLV30/STEREL1 ATOMS=(2 3 5) +M V30 MDLV30/STEABS ATOMS=(1 7) +M V30 END COLLECTION +M V30 END CTAB +M END +$$$$ +Structure drawings enhanced stereochemistry.sdf + ChemDraw01282608392D + + 0 0 0 0 0 0 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 9 9 0 0 0 +M V30 BEGIN ATOM +M V30 1 O 0.824839 -1.071585 0.000000 0 +M V30 2 C 0.412473 -0.357346 0.000000 0 +M V30 3 C -0.412508 -0.357346 0.000000 0 +M V30 4 C -0.824981 0.357119 0.000000 0 +M V30 5 O -1.649713 0.357119 0.000000 0 +M V30 6 C -0.412472 1.071585 0.000000 0 +M V30 7 C 0.412510 1.071585 0.000000 0 +M V30 8 C 0.824982 0.357119 0.000000 0 +M V30 9 O 1.649713 0.357119 0.000000 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 1 2 1 CFG=1 +M V30 2 1 3 2 +M V30 3 1 4 3 +M V30 4 1 4 5 CFG=1 +M V30 5 1 6 4 +M V30 6 1 6 7 +M V30 7 1 8 7 +M V30 8 1 8 9 CFG=1 +M V30 9 1 2 8 +M V30 END BOND +M V30 BEGIN COLLECTION +M V30 MDLV30/STEREL1 ATOMS=(1 2) +M V30 MDLV30/STEABS ATOMS=(1 4) +M V30 MDLV30/STEREL2 ATOMS=(1 8) +M V30 END COLLECTION +M V30 END CTAB +M END +$$$$ +Structure drawings enhanced stereochemistry.sdf + ChemDraw01282608392D + + 0 0 0 0 0 0 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 7 6 0 0 0 +M V30 BEGIN ATOM +M V30 1 C -1.429261 -0.206189 0.000000 0 +M V30 2 C -0.714790 0.206311 0.000000 0 +M V30 3 C -0.000107 -0.206311 0.000000 0 +M V30 4 N -0.000107 -1.031556 0.000000 0 +M V30 5 C 0.714577 0.206311 0.000000 0 +M V30 6 C 1.429261 -0.206311 0.000000 0 +M V30 7 O -0.714790 1.031556 0.000000 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 1 1 2 +M V30 2 1 2 3 +M V30 3 1 3 4 CFG=1 +M V30 4 1 3 5 +M V30 5 1 5 6 +M V30 6 1 2 7 CFG=1 +M V30 END BOND +M V30 BEGIN COLLECTION +M V30 MDLV30/STEABS ATOMS=(1 2) +M V30 MDLV30/STERAC1 ATOMS=(1 3) +M V30 END COLLECTION +M V30 END CTAB +M END +$$$$ +Structure drawings enhanced stereochemistry.sdf + ChemDraw01282608392D + + 0 0 0 0 0 0 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 7 6 0 0 0 +M V30 BEGIN ATOM +M V30 1 C 1.429260 -0.206311 0.000000 0 +M V30 2 C 0.714577 0.206311 0.000000 0 +M V30 3 C -0.000107 -0.206311 0.000000 0 +M V30 4 N -0.000107 -1.031556 0.000000 0 +M V30 5 C -0.714790 0.206311 0.000000 0 +M V30 6 O -0.714790 1.031556 0.000000 0 +M V30 7 C -1.429260 -0.206189 0.000000 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 1 2 1 +M V30 2 1 3 2 +M V30 3 1 3 4 +M V30 4 1 5 3 +M V30 5 1 5 6 CFG=1 +M V30 6 1 7 5 +M V30 END BOND +M V30 BEGIN COLLECTION +M V30 MDLV30/STERAC1 ATOMS=(1 5) +M V30 END COLLECTION +M V30 END CTAB +M END +$$$$ +Structure drawings enhanced stereochemistry.sdf + ChemDraw01282608392D + + 0 0 0 0 0 0 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 9 9 0 0 0 +M V30 BEGIN ATOM +M V30 1 O 0.824838 -1.071585 0.000000 0 +M V30 2 C 0.412472 -0.357346 0.000000 0 +M V30 3 C -0.412509 -0.357346 0.000000 0 +M V30 4 C -0.824982 0.357119 0.000000 0 +M V30 5 O -1.649714 0.357119 0.000000 0 +M V30 6 C -0.412473 1.071585 0.000000 0 +M V30 7 C 0.412509 1.071585 0.000000 0 +M V30 8 C 0.824982 0.357119 0.000000 0 +M V30 9 O 1.649714 0.357119 0.000000 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 1 2 1 CFG=1 +M V30 2 1 3 2 +M V30 3 1 4 3 +M V30 4 1 4 5 CFG=1 +M V30 5 1 6 4 +M V30 6 1 6 7 +M V30 7 1 8 7 +M V30 8 1 8 9 CFG=3 +M V30 9 1 2 8 +M V30 END BOND +M V30 BEGIN COLLECTION +M V30 MDLV30/STERAC1 ATOMS=(2 2 8) +M V30 MDLV30/STEABS ATOMS=(1 4) +M V30 END COLLECTION +M V30 END CTAB +M END +$$$$ +Structure drawings enhanced stereochemistry.sdf + ChemDraw01282608392D + + 0 0 0 0 0 0 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 9 9 0 0 0 +M V30 BEGIN ATOM +M V30 1 O 0.824839 -1.071585 0.000000 0 +M V30 2 C 0.412473 -0.357346 0.000000 0 +M V30 3 C -0.412509 -0.357346 0.000000 0 +M V30 4 C -0.824982 0.357119 0.000000 0 +M V30 5 O -1.649713 0.357119 0.000000 0 +M V30 6 C -0.412473 1.071585 0.000000 0 +M V30 7 C 0.412509 1.071585 0.000000 0 +M V30 8 C 0.824982 0.357119 0.000000 0 +M V30 9 O 1.649713 0.357119 0.000000 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 1 2 1 CFG=1 +M V30 2 1 3 2 +M V30 3 1 4 3 +M V30 4 1 4 5 CFG=1 +M V30 5 1 6 4 +M V30 6 1 6 7 +M V30 7 1 8 7 +M V30 8 1 8 9 CFG=1 +M V30 9 1 2 8 +M V30 END BOND +M V30 BEGIN COLLECTION +M V30 MDLV30/STERAC1 ATOMS=(2 2 8) +M V30 MDLV30/STEABS ATOMS=(1 4) +M V30 END COLLECTION +M V30 END CTAB +M END +$$$$ +Structure drawings enhanced stereochemistry.sdf + ChemDraw01282608392D + + 0 0 0 0 0 0 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 9 9 0 0 0 +M V30 BEGIN ATOM +M V30 1 O 0.824839 -1.071585 0.000000 0 +M V30 2 C 0.412473 -0.357346 0.000000 0 +M V30 3 C -0.412508 -0.357346 0.000000 0 +M V30 4 C -0.824981 0.357119 0.000000 0 +M V30 5 O -1.649713 0.357119 0.000000 0 +M V30 6 C -0.412472 1.071585 0.000000 0 +M V30 7 C 0.412510 1.071585 0.000000 0 +M V30 8 C 0.824982 0.357119 0.000000 0 +M V30 9 O 1.649713 0.357119 0.000000 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 1 2 1 +M V30 2 1 3 2 +M V30 3 1 4 3 +M V30 4 1 4 5 CFG=1 +M V30 5 1 6 4 +M V30 6 1 6 7 +M V30 7 1 8 7 +M V30 8 1 8 9 +M V30 9 1 2 8 +M V30 END BOND +M V30 BEGIN COLLECTION +M V30 MDLV30/STERAC1 ATOMS=(1 4) +M V30 END COLLECTION +M V30 END CTAB +M END +$$$$ +Structure drawings enhanced stereochemistry.sdf + ChemDraw01282608392D + + 0 0 0 0 0 0 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 9 8 0 0 0 +M V30 BEGIN ATOM +M V30 1 C 1.786602 0.206312 0.000000 0 +M V30 2 C 1.071918 -0.206310 0.000000 0 +M V30 3 C 0.357235 0.206312 0.000000 0 +M V30 4 C 0.357235 1.031557 0.000000 0 +M V30 5 C -0.357448 -0.206310 0.000000 0 +M V30 6 N -0.357448 -1.031557 0.000000 0 +M V30 7 C -1.072132 0.206312 0.000000 0 +M V30 8 O -1.072132 1.031557 0.000000 0 +M V30 9 C -1.786602 -0.206188 0.000000 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 1 2 1 +M V30 2 1 3 2 +M V30 3 1 3 4 +M V30 4 1 5 3 +M V30 5 1 5 6 +M V30 6 1 7 5 +M V30 7 1 7 8 CFG=1 +M V30 8 1 9 7 +M V30 END BOND +M V30 BEGIN COLLECTION +M V30 MDLV30/STERAC1 ATOMS=(1 7) +M V30 END COLLECTION +M V30 END CTAB +M END +$$$$ +Structure drawings enhanced stereochemistry.sdf + ChemDraw01282608392D + + 0 0 0 0 0 0 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 9 8 0 0 0 +M V30 BEGIN ATOM +M V30 1 C 1.786602 0.206312 0.000000 0 +M V30 2 C 1.071918 -0.206310 0.000000 0 +M V30 3 C 0.357235 0.206312 0.000000 0 +M V30 4 C 0.357235 1.031557 0.000000 0 +M V30 5 C -0.357448 -0.206310 0.000000 0 +M V30 6 N -0.357448 -1.031557 0.000000 0 +M V30 7 C -1.072132 0.206312 0.000000 0 +M V30 8 O -1.072132 1.031557 0.000000 0 +M V30 9 C -1.786602 -0.206188 0.000000 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 1 2 1 +M V30 2 1 3 2 +M V30 3 1 3 4 CFG=3 +M V30 4 1 5 3 +M V30 5 1 5 6 CFG=1 +M V30 6 1 7 5 +M V30 7 1 7 8 CFG=1 +M V30 8 1 9 7 +M V30 END BOND +M V30 BEGIN COLLECTION +M V30 MDLV30/STERAC1 ATOMS=(2 3 5) +M V30 MDLV30/STEABS ATOMS=(1 7) +M V30 END COLLECTION +M V30 END CTAB +M END +$$$$ +Structure drawings enhanced stereochemistry.sdf + ChemDraw01282608392D + + 0 0 0 0 0 0 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 9 8 0 0 0 +M V30 BEGIN ATOM +M V30 1 C 1.786602 0.206311 0.000000 0 +M V30 2 C 1.071919 -0.206312 0.000000 0 +M V30 3 C 0.357236 0.206311 0.000000 0 +M V30 4 C 0.357236 1.031557 0.000000 0 +M V30 5 C -0.357448 -0.206312 0.000000 0 +M V30 6 N -0.357448 -1.031557 0.000000 0 +M V30 7 C -1.072132 0.206311 0.000000 0 +M V30 8 O -1.072132 1.031557 0.000000 0 +M V30 9 C -1.786602 -0.206188 0.000000 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 1 2 1 +M V30 2 1 3 2 +M V30 3 1 3 4 CFG=1 +M V30 4 1 5 3 +M V30 5 1 5 6 CFG=1 +M V30 6 1 7 5 +M V30 7 1 7 8 CFG=1 +M V30 8 1 9 7 +M V30 END BOND +M V30 BEGIN COLLECTION +M V30 MDLV30/STERAC1 ATOMS=(2 3 5) +M V30 MDLV30/STEABS ATOMS=(1 7) +M V30 END COLLECTION +M V30 END CTAB +M END +$$$$ +Structure drawings enhanced stereochemistry.sdf + ChemDraw01282608392D + + 0 0 0 0 0 0 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 9 9 0 0 0 +M V30 BEGIN ATOM +M V30 1 O 0.824839 -1.071585 0.000000 0 +M V30 2 C 0.412473 -0.357346 0.000000 0 +M V30 3 C -0.412509 -0.357346 0.000000 0 +M V30 4 C -0.824981 0.357119 0.000000 0 +M V30 5 O -1.649713 0.357119 0.000000 0 +M V30 6 C -0.412472 1.071585 0.000000 0 +M V30 7 C 0.412510 1.071585 0.000000 0 +M V30 8 C 0.824982 0.357119 0.000000 0 +M V30 9 O 1.649713 0.357119 0.000000 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 1 2 1 CFG=1 +M V30 2 1 3 2 +M V30 3 1 4 3 +M V30 4 1 4 5 CFG=1 +M V30 5 1 6 4 +M V30 6 1 6 7 +M V30 7 1 8 7 +M V30 8 1 8 9 CFG=1 +M V30 9 1 2 8 +M V30 END BOND +M V30 BEGIN COLLECTION +M V30 MDLV30/STERAC1 ATOMS=(1 2) +M V30 MDLV30/STEABS ATOMS=(1 4) +M V30 MDLV30/STERAC2 ATOMS=(1 8) +M V30 END COLLECTION +M V30 END CTAB +M END +$$$$ +Structure drawings enhanced stereochemistry.sdf + ChemDraw01282608392D + + 0 0 0 0 0 0 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 9 9 0 0 0 +M V30 BEGIN ATOM +M V30 1 O 0.824839 -1.071585 0.000000 0 +M V30 2 C 0.412473 -0.357346 0.000000 0 +M V30 3 C -0.412508 -0.357346 0.000000 0 +M V30 4 C -0.824981 0.357119 0.000000 0 +M V30 5 O -1.649713 0.357119 0.000000 0 +M V30 6 C -0.412472 1.071585 0.000000 0 +M V30 7 C 0.412510 1.071585 0.000000 0 +M V30 8 C 0.824982 0.357119 0.000000 0 +M V30 9 O 1.649713 0.357119 0.000000 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 1 2 1 CFG=1 +M V30 2 1 3 2 +M V30 3 1 4 3 +M V30 4 1 4 5 CFG=1 +M V30 5 1 6 4 +M V30 6 1 6 7 +M V30 7 1 8 7 +M V30 8 1 8 9 CFG=1 +M V30 9 1 2 8 +M V30 END BOND +M V30 BEGIN COLLECTION +M V30 MDLV30/STERAC1 ATOMS=(1 2) +M V30 MDLV30/STEABS ATOMS=(1 4) +M V30 MDLV30/STEREL1 ATOMS=(1 8) +M V30 END COLLECTION +M V30 END CTAB +M END +$$$$ +Structure drawings enhanced stereochemistry.sdf + ChemDraw01282608392D + + 0 0 0 0 0 0 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 7 6 0 0 1 +M V30 BEGIN ATOM +M V30 1 C 1.429261 -0.206312 0.000000 0 +M V30 2 C 0.714577 0.206310 0.000000 0 +M V30 3 C -0.000106 -0.206312 0.000000 0 +M V30 4 N -0.000106 -1.031557 0.000000 0 +M V30 5 C -0.714790 0.206310 0.000000 0 +M V30 6 O -0.714790 1.031557 0.000000 0 +M V30 7 C -1.429261 -0.206188 0.000000 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 1 2 1 +M V30 2 1 3 2 +M V30 3 1 3 4 +M V30 4 1 5 3 +M V30 5 1 5 6 CFG=1 +M V30 6 1 7 5 +M V30 END BOND +M V30 END CTAB +M END +$$$$ +Structure drawings enhanced stereochemistry.sdf + ChemDraw01282608392D + + 0 0 0 0 0 0 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 7 6 0 0 0 +M V30 BEGIN ATOM +M V30 1 N -0.000106 -1.031556 0.000000 0 +M V30 2 C -0.000106 -0.206312 0.000000 0 +M V30 3 C 0.714577 0.206311 0.000000 0 +M V30 4 C 1.429261 -0.206312 0.000000 0 +M V30 5 C -0.714789 0.206311 0.000000 0 +M V30 6 O -0.714789 1.031556 0.000000 0 +M V30 7 C -1.429261 -0.206189 0.000000 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 1 2 1 +M V30 2 1 2 3 +M V30 3 1 3 4 +M V30 4 1 5 2 +M V30 5 1 5 6 CFG=1 +M V30 6 1 7 5 +M V30 END BOND +M V30 BEGIN COLLECTION +M V30 MDLV30/STERAC1 ATOMS=(1 5) +M V30 END COLLECTION +M V30 END CTAB +M END +$$$$ +Structure drawings enhanced stereochemistry.sdf + ChemDraw01282608392D + + 0 0 0 0 0 0 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 7 6 0 0 0 +M V30 BEGIN ATOM +M V30 1 N -0.000106 -1.031556 0.000000 0 +M V30 2 C -0.000106 -0.206312 0.000000 0 +M V30 3 C 0.714577 0.206311 0.000000 0 +M V30 4 C 1.429261 -0.206312 0.000000 0 +M V30 5 C -0.714789 0.206311 0.000000 0 +M V30 6 O -0.714789 1.031556 0.000000 0 +M V30 7 C -1.429261 -0.206189 0.000000 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 1 2 1 +M V30 2 1 2 3 +M V30 3 1 3 4 +M V30 4 1 5 2 +M V30 5 1 5 6 CFG=1 +M V30 6 1 7 5 +M V30 END BOND +M V30 BEGIN COLLECTION +M V30 MDLV30/STEREL1 ATOMS=(1 5) +M V30 END COLLECTION +M V30 END CTAB +M END +$$$$ +Structure drawings enhanced stereochemistry.sdf + ChemDraw01282608392D + + 0 0 0 0 0 0 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 9 9 0 0 0 +M V30 BEGIN ATOM +M V30 1 O 0.824839 -1.071585 0.000000 0 +M V30 2 C 0.412473 -0.357346 0.000000 0 +M V30 3 C -0.412508 -0.357346 0.000000 0 +M V30 4 C -0.824981 0.357119 0.000000 0 +M V30 5 O -1.649713 0.357119 0.000000 0 +M V30 6 C -0.412472 1.071585 0.000000 0 +M V30 7 C 0.412510 1.071585 0.000000 0 +M V30 8 C 0.824982 0.357119 0.000000 0 +M V30 9 O 1.649713 0.357119 0.000000 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 1 2 1 +M V30 2 1 3 2 +M V30 3 1 4 3 +M V30 4 1 4 5 +M V30 5 1 6 4 +M V30 6 1 6 7 +M V30 7 1 8 7 +M V30 8 1 8 9 +M V30 9 1 2 8 +M V30 END BOND +M V30 END CTAB +M END +$$$$ diff --git a/INCHI-1-TEST/tests/test_unit/fixtures/enh_stereo_test_file_2.sdf b/INCHI-1-TEST/tests/test_unit/fixtures/enh_stereo_test_file_2.sdf new file mode 100644 index 00000000..ef407954 --- /dev/null +++ b/INCHI-1-TEST/tests/test_unit/fixtures/enh_stereo_test_file_2.sdf @@ -0,0 +1,1972 @@ + + ACD/LABS08242216132D + + 0 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 18 17 0 0 1 +M V30 BEGIN ATOM +M V30 1 C 3424.1946 -1936.7935 0 0 +M V30 2 C 3352.3145 -1895.2935 0 0 +M V30 3 C 3280.4346 -1936.7935 0 0 +M V30 4 C 3208.5542 -1895.2935 0 0 +M V30 5 C 3136.6743 -1936.7935 0 0 +M V30 6 C 3064.7944 -1895.2935 0 0 +M V30 7 Br 3136.6743 -2019.7935 0 0 +M V30 8 Cl 3208.5542 -1812.2935 0 0 +M V30 9 Cl 3280.4346 -2019.7935 0 0 +M V30 10 Cl 3352.3145 -1812.2935 0 0 +M V30 11 Cl 3424.1946 -2019.7935 0 0 +M V30 12 C 3496.075 -1895.2935 0 0 +M V30 13 C 3567.9548 -1936.7942 0 0 +M V30 14 C 3639.835 -1895.2944 0 0 +M V30 15 C 3711.7148 -1936.7942 0 0 +M V30 16 Cl 3639.835 -1812.2944 0 0 +M V30 17 Cl 3567.9548 -2019.7942 0 0 +M V30 18 Cl 3496.075 -1812.2937 0 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 1 1 2 +M V30 2 1 1 11 CFG=3 +M V30 3 1 1 12 +M V30 4 1 2 3 +M V30 5 1 2 10 CFG=1 +M V30 6 1 3 4 +M V30 7 1 3 9 CFG=1 +M V30 8 1 4 5 +M V30 9 1 4 8 CFG=1 +M V30 10 1 5 6 +M V30 11 1 5 7 CFG=1 +M V30 12 1 12 13 +M V30 13 1 12 18 CFG=3 +M V30 14 1 13 14 +M V30 15 1 13 17 CFG=1 +M V30 16 1 14 15 +M V30 17 1 14 16 CFG=1 +M V30 END BOND +M V30 BEGIN COLLECTION +M V30 MDLV30/STERAC2 ATOMS=(1 1) +M V30 MDLV30/STERAC1 ATOMS=(2 2 3) +M V30 MDLV30/STEABS ATOMS=(2 4 5) +M V30 MDLV30/STEREL1 ATOMS=(2 12 13) +M V30 MDLV30/STEREL2 ATOMS=(1 14) +M V30 END COLLECTION +M V30 END CTAB +M END +> +InChI=1S/C10H14BrCl7/c1-3(11)5(13)7(15)9(17)10(18)8(16)6(14)4(2)12/h3-10H,1-2H3/t3-,4-,5+,6+,7-,8+,9+,10-/m0/s1 + +> +InChI=1S/C10H14BrCl7/c1-3(11)5(13)7(15)9(17)10(18)8(16)6(14)4(2)12/h3-10H,1-2H3/t3-,4-,5+,6-,7-,8-,9+,10-/m0/s1(3,5)2(4)(6,8)3(7,9)(10) + +> +1EN + +$$$$ + + ACD/LABS08242216132D + + 0 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 18 17 0 0 1 +M V30 BEGIN ATOM +M V30 1 C 2617.5557 -1933.6348 0 0 +M V30 2 C 2545.6758 -1892.1348 0 0 +M V30 3 C 2473.7957 -1933.6348 0 0 +M V30 4 C 2401.9155 -1892.1348 0 0 +M V30 5 C 2330.0354 -1933.6348 0 0 +M V30 6 C 2258.1553 -1892.1348 0 0 +M V30 7 Br 2330.0354 -2016.6348 0 0 +M V30 8 Cl 2401.9155 -1809.1348 0 0 +M V30 9 Cl 2473.7957 -2016.6348 0 0 +M V30 10 Cl 2545.6758 -1809.1348 0 0 +M V30 11 Cl 2617.5557 -2016.6348 0 0 +M V30 12 C 2689.436 -1892.1348 0 0 +M V30 13 C 2761.3159 -1933.6355 0 0 +M V30 14 C 2833.1958 -1892.1357 0 0 +M V30 15 C 2905.0759 -1933.6355 0 0 +M V30 16 Cl 2833.1958 -1809.1357 0 0 +M V30 17 Cl 2761.3159 -2016.6355 0 0 +M V30 18 Cl 2689.436 -1809.135 0 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 1 1 2 +M V30 2 1 1 11 CFG=1 +M V30 3 1 1 12 +M V30 4 1 2 3 +M V30 5 1 2 10 CFG=1 +M V30 6 1 3 4 +M V30 7 1 3 9 CFG=1 +M V30 8 1 4 5 +M V30 9 1 4 8 CFG=1 +M V30 10 1 5 6 +M V30 11 1 5 7 CFG=1 +M V30 12 1 12 13 +M V30 13 1 12 18 CFG=3 +M V30 14 1 13 14 +M V30 15 1 13 17 CFG=1 +M V30 16 1 14 15 +M V30 17 1 14 16 CFG=1 +M V30 END BOND +M V30 BEGIN COLLECTION +M V30 MDLV30/STERAC2 ATOMS=(1 1) +M V30 MDLV30/STERAC1 ATOMS=(2 2 3) +M V30 MDLV30/STEABS ATOMS=(2 4 5) +M V30 MDLV30/STEREL1 ATOMS=(2 12 13) +M V30 MDLV30/STEREL2 ATOMS=(1 14) +M V30 END COLLECTION +M V30 END CTAB +M END +> +InChI=1S/C10H14BrCl7/c1-3(11)5(13)7(15)9(17)10(18)8(16)6(14)4(2)12/h3-10H,1-2H3/t3-,4-,5+,6+,7-,8+,9+,10+/m0/s1 + +> +InChI=1S/C10H14BrCl7/c1-3(11)5(13)7(15)9(17)10(18)8(16)6(14)4(2)12/h3-10H,1-2H3/t3-,4-,5+,6-,7-,8-,9+,10-/m0/s1(3,5)2(4)(6,8)3(7,9)(10) + +> +1EN + +$$$$ + + ACD/LABS08242216132D + + 0 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 18 17 0 0 1 +M V30 BEGIN ATOM +M V30 1 C 1652.5557 -1930.6348 0 0 +M V30 2 C 1580.6757 -1889.1348 0 0 +M V30 3 C 1508.7957 -1930.6348 0 0 +M V30 4 C 1436.9154 -1889.1348 0 0 +M V30 5 C 1365.0354 -1930.6348 0 0 +M V30 6 C 1293.1554 -1889.1348 0 0 +M V30 7 Br 1365.0354 -2013.6348 0 0 +M V30 8 Cl 1436.9154 -1806.1348 0 0 +M V30 9 Cl 1508.7957 -2013.6348 0 0 +M V30 10 Cl 1580.6757 -1806.1348 0 0 +M V30 11 Cl 1652.5557 -2013.6348 0 0 +M V30 12 C 1724.436 -1889.1348 0 0 +M V30 13 C 1796.3159 -1930.6355 0 0 +M V30 14 C 1868.196 -1889.1357 0 0 +M V30 15 C 1940.0759 -1930.6355 0 0 +M V30 16 Cl 1868.196 -1806.1357 0 0 +M V30 17 Cl 1796.3159 -2013.6355 0 0 +M V30 18 Cl 1724.436 -1806.135 0 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 1 1 2 +M V30 2 1 1 11 CFG=3 +M V30 3 1 1 12 +M V30 4 1 2 3 +M V30 5 1 2 10 CFG=1 +M V30 6 1 3 4 +M V30 7 1 3 9 CFG=1 +M V30 8 1 4 5 +M V30 9 1 4 8 CFG=1 +M V30 10 1 5 6 +M V30 11 1 5 7 CFG=1 +M V30 12 1 12 13 +M V30 13 1 12 18 CFG=1 +M V30 14 1 13 14 +M V30 15 1 13 17 CFG=3 +M V30 16 1 14 15 +M V30 17 1 14 16 CFG=1 +M V30 END BOND +M V30 BEGIN COLLECTION +M V30 MDLV30/STERAC2 ATOMS=(1 1) +M V30 MDLV30/STERAC1 ATOMS=(2 2 3) +M V30 MDLV30/STEABS ATOMS=(2 4 5) +M V30 MDLV30/STEREL1 ATOMS=(2 12 13) +M V30 MDLV30/STEREL2 ATOMS=(1 14) +M V30 END COLLECTION +M V30 END CTAB +M END +> +InChI=1S/C10H14BrCl7/c1-3(11)5(13)7(15)9(17)10(18)8(16)6(14)4(2)12/h3-10H,1-2H3/t3-,4-,5+,6-,7-,8-,9+,10-/m0/s1 + +> +InChI=1S/C10H14BrCl7/c1-3(11)5(13)7(15)9(17)10(18)8(16)6(14)4(2)12/h3-10H,1-2H3/t3-,4-,5+,6-,7-,8-,9+,10-/m0/s1(3,5)2(4)(6,8)3(7,9)(10) + +> +1EN + +$$$$ + + ACD/LABS08242216132D + + 0 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 18 17 0 0 1 +M V30 BEGIN ATOM +M V30 1 C 845.9167 -1927.4761 0 0 +M V30 2 C 774.0367 -1885.9761 0 0 +M V30 3 C 702.1567 -1927.4761 0 0 +M V30 4 C 630.2765 -1885.9761 0 0 +M V30 5 C 558.3965 -1927.4761 0 0 +M V30 6 C 486.5165 -1885.9761 0 0 +M V30 7 Br 558.3965 -2010.4761 0 0 +M V30 8 Cl 630.2765 -1802.9761 0 0 +M V30 9 Cl 702.1567 -2010.4761 0 0 +M V30 10 Cl 774.0367 -1802.9761 0 0 +M V30 11 Cl 845.9167 -2010.4761 0 0 +M V30 12 C 917.7971 -1885.9761 0 0 +M V30 13 C 989.677 -1927.4768 0 0 +M V30 14 C 1061.557 -1885.9771 0 0 +M V30 15 C 1133.437 -1927.4768 0 0 +M V30 16 Cl 1061.557 -1802.9771 0 0 +M V30 17 Cl 989.677 -2010.4768 0 0 +M V30 18 Cl 917.7971 -1802.9763 0 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 1 1 2 +M V30 2 1 1 11 CFG=1 +M V30 3 1 1 12 +M V30 4 1 2 3 +M V30 5 1 2 10 CFG=1 +M V30 6 1 3 4 +M V30 7 1 3 9 CFG=1 +M V30 8 1 4 5 +M V30 9 1 4 8 CFG=1 +M V30 10 1 5 6 +M V30 11 1 5 7 CFG=1 +M V30 12 1 12 13 +M V30 13 1 12 18 CFG=1 +M V30 14 1 13 14 +M V30 15 1 13 17 CFG=3 +M V30 16 1 14 15 +M V30 17 1 14 16 CFG=1 +M V30 END BOND +M V30 BEGIN COLLECTION +M V30 MDLV30/STERAC2 ATOMS=(1 1) +M V30 MDLV30/STERAC1 ATOMS=(2 2 3) +M V30 MDLV30/STEABS ATOMS=(2 4 5) +M V30 MDLV30/STEREL1 ATOMS=(2 12 13) +M V30 MDLV30/STEREL2 ATOMS=(1 14) +M V30 END COLLECTION +M V30 END CTAB +M END +> +InChI=1S/C10H14BrCl7/c1-3(11)5(13)7(15)9(17)10(18)8(16)6(14)4(2)12/h3-10H,1-2H3/t3-,4-,5+,6-,7-,8-,9+,10+/m0/s1 + +> +InChI=1S/C10H14BrCl7/c1-3(11)5(13)7(15)9(17)10(18)8(16)6(14)4(2)12/h3-10H,1-2H3/t3-,4-,5+,6-,7-,8-,9+,10-/m0/s1(3,5)2(4)(6,8)3(7,9)(10) + +> +1EN + +$$$$ + + ACD/LABS08242216132D + + 0 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 18 17 0 0 1 +M V30 BEGIN ATOM +M V30 1 C 3436.5557 -1571.6348 0 0 +M V30 2 C 3364.6758 -1530.1348 0 0 +M V30 3 C 3292.7957 -1571.6348 0 0 +M V30 4 C 3220.9155 -1530.1348 0 0 +M V30 5 C 3149.0354 -1571.6348 0 0 +M V30 6 C 3077.1553 -1530.1348 0 0 +M V30 7 Br 3149.0354 -1654.6346 0 0 +M V30 8 Cl 3220.9155 -1447.1348 0 0 +M V30 9 Cl 3292.7957 -1654.6346 0 0 +M V30 10 Cl 3364.6758 -1447.1348 0 0 +M V30 11 Cl 3436.5557 -1654.6346 0 0 +M V30 12 C 3508.436 -1530.1349 0 0 +M V30 13 C 3580.3159 -1571.6355 0 0 +M V30 14 C 3652.1958 -1530.1356 0 0 +M V30 15 C 3724.0759 -1571.6355 0 0 +M V30 16 Cl 3652.1958 -1447.1356 0 0 +M V30 17 Cl 3580.3159 -1654.6355 0 0 +M V30 18 Cl 3508.436 -1447.135 0 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 1 1 2 +M V30 2 1 1 11 CFG=3 +M V30 3 1 1 12 +M V30 4 1 2 3 +M V30 5 1 2 10 CFG=1 +M V30 6 1 3 4 +M V30 7 1 3 9 CFG=1 +M V30 8 1 4 5 +M V30 9 1 4 8 CFG=1 +M V30 10 1 5 6 +M V30 11 1 5 7 CFG=1 +M V30 12 1 12 13 +M V30 13 1 12 18 CFG=3 +M V30 14 1 13 14 +M V30 15 1 13 17 CFG=1 +M V30 16 1 14 15 +M V30 17 1 14 16 CFG=3 +M V30 END BOND +M V30 BEGIN COLLECTION +M V30 MDLV30/STERAC2 ATOMS=(1 1) +M V30 MDLV30/STERAC1 ATOMS=(2 2 3) +M V30 MDLV30/STEABS ATOMS=(2 4 5) +M V30 MDLV30/STEREL1 ATOMS=(2 12 13) +M V30 MDLV30/STEREL2 ATOMS=(1 14) +M V30 END COLLECTION +M V30 END CTAB +M END +> +InChI=1S/C10H14BrCl7/c1-3(11)5(13)7(15)9(17)10(18)8(16)6(14)4(2)12/h3-10H,1-2H3/t3-,4+,5+,6+,7-,8+,9+,10-/m0/s1 + +> +InChI=1S/C10H14BrCl7/c1-3(11)5(13)7(15)9(17)10(18)8(16)6(14)4(2)12/h3-10H,1-2H3/t3-,4-,5+,6-,7-,8-,9+,10-/m0/s1(3,5)2(4)(6,8)3(7,9)(10) + +> +1EN + +$$$$ + + ACD/LABS08242216132D + + 0 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 18 17 0 0 1 +M V30 BEGIN ATOM +M V30 1 C 2629.9167 -1568.4761 0 0 +M V30 2 C 2558.0366 -1526.9761 0 0 +M V30 3 C 2486.1567 -1568.4761 0 0 +M V30 4 C 2414.2764 -1526.9761 0 0 +M V30 5 C 2342.3965 -1568.4761 0 0 +M V30 6 C 2270.5166 -1526.9761 0 0 +M V30 7 Br 2342.3965 -1651.476 0 0 +M V30 8 Cl 2414.2764 -1443.9761 0 0 +M V30 9 Cl 2486.1567 -1651.476 0 0 +M V30 10 Cl 2558.0366 -1443.9761 0 0 +M V30 11 Cl 2629.9167 -1651.476 0 0 +M V30 12 C 2701.7971 -1526.9762 0 0 +M V30 13 C 2773.677 -1568.4768 0 0 +M V30 14 C 2845.5571 -1526.9769 0 0 +M V30 15 C 2917.437 -1568.4768 0 0 +M V30 16 Cl 2845.5571 -1443.9769 0 0 +M V30 17 Cl 2773.677 -1651.4768 0 0 +M V30 18 Cl 2701.7971 -1443.9763 0 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 1 1 2 +M V30 2 1 1 11 CFG=1 +M V30 3 1 1 12 +M V30 4 1 2 3 +M V30 5 1 2 10 CFG=1 +M V30 6 1 3 4 +M V30 7 1 3 9 CFG=1 +M V30 8 1 4 5 +M V30 9 1 4 8 CFG=1 +M V30 10 1 5 6 +M V30 11 1 5 7 CFG=1 +M V30 12 1 12 13 +M V30 13 1 12 18 CFG=3 +M V30 14 1 13 14 +M V30 15 1 13 17 CFG=1 +M V30 16 1 14 15 +M V30 17 1 14 16 CFG=3 +M V30 END BOND +M V30 BEGIN COLLECTION +M V30 MDLV30/STERAC2 ATOMS=(1 1) +M V30 MDLV30/STERAC1 ATOMS=(2 2 3) +M V30 MDLV30/STEABS ATOMS=(2 4 5) +M V30 MDLV30/STEREL1 ATOMS=(2 12 13) +M V30 MDLV30/STEREL2 ATOMS=(1 14) +M V30 END COLLECTION +M V30 END CTAB +M END +> +InChI=1S/C10H14BrCl7/c1-3(11)5(13)7(15)9(17)10(18)8(16)6(14)4(2)12/h3-10H,1-2H3/t3-,4+,5+,6+,7-,8+,9+,10+/m0/s1 + +> +InChI=1S/C10H14BrCl7/c1-3(11)5(13)7(15)9(17)10(18)8(16)6(14)4(2)12/h3-10H,1-2H3/t3-,4-,5+,6-,7-,8-,9+,10-/m0/s1(3,5)2(4)(6,8)3(7,9)(10) + +> +1EN + +$$$$ + + ACD/LABS08242216132D + + 0 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 18 17 0 0 1 +M V30 BEGIN ATOM +M V30 1 C 1664.9167 -1565.4761 0 0 +M V30 2 C 1593.0367 -1523.9761 0 0 +M V30 3 C 1521.1567 -1565.4761 0 0 +M V30 4 C 1449.2765 -1523.9761 0 0 +M V30 5 C 1377.3965 -1565.4761 0 0 +M V30 6 C 1305.5165 -1523.9761 0 0 +M V30 7 Br 1377.3965 -1648.476 0 0 +M V30 8 Cl 1449.2765 -1440.9761 0 0 +M V30 9 Cl 1521.1567 -1648.476 0 0 +M V30 10 Cl 1593.0367 -1440.9761 0 0 +M V30 11 Cl 1664.9167 -1648.476 0 0 +M V30 12 C 1736.7971 -1523.9762 0 0 +M V30 13 C 1808.677 -1565.4768 0 0 +M V30 14 C 1880.5569 -1523.9769 0 0 +M V30 15 C 1952.437 -1565.4768 0 0 +M V30 16 Cl 1880.5569 -1440.9769 0 0 +M V30 17 Cl 1808.677 -1648.4768 0 0 +M V30 18 Cl 1736.7971 -1440.9763 0 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 1 1 2 +M V30 2 1 1 11 CFG=3 +M V30 3 1 1 12 +M V30 4 1 2 3 +M V30 5 1 2 10 CFG=1 +M V30 6 1 3 4 +M V30 7 1 3 9 CFG=1 +M V30 8 1 4 5 +M V30 9 1 4 8 CFG=1 +M V30 10 1 5 6 +M V30 11 1 5 7 CFG=1 +M V30 12 1 12 13 +M V30 13 1 12 18 CFG=1 +M V30 14 1 13 14 +M V30 15 1 13 17 CFG=3 +M V30 16 1 14 15 +M V30 17 1 14 16 CFG=3 +M V30 END BOND +M V30 BEGIN COLLECTION +M V30 MDLV30/STERAC2 ATOMS=(1 1) +M V30 MDLV30/STERAC1 ATOMS=(2 2 3) +M V30 MDLV30/STEABS ATOMS=(2 4 5) +M V30 MDLV30/STEREL1 ATOMS=(2 12 13) +M V30 MDLV30/STEREL2 ATOMS=(1 14) +M V30 END COLLECTION +M V30 END CTAB +M END +> +InChI=1S/C10H14BrCl7/c1-3(11)5(13)7(15)9(17)10(18)8(16)6(14)4(2)12/h3-10H,1-2H3/t3-,4+,5+,6-,7-,8-,9+,10-/m0/s1 + +> +InChI=1S/C10H14BrCl7/c1-3(11)5(13)7(15)9(17)10(18)8(16)6(14)4(2)12/h3-10H,1-2H3/t3-,4-,5+,6-,7-,8-,9+,10-/m0/s1(3,5)2(4)(6,8)3(7,9)(10) + +> +1EN + +$$$$ + + ACD/LABS08242216132D + + 0 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 18 17 0 0 1 +M V30 BEGIN ATOM +M V30 1 C 858.2778 -1562.3174 0 0 +M V30 2 C 786.3978 -1520.8174 0 0 +M V30 3 C 714.5178 -1562.3174 0 0 +M V30 4 C 642.6376 -1520.8174 0 0 +M V30 5 C 570.7576 -1562.3174 0 0 +M V30 6 C 498.8776 -1520.8174 0 0 +M V30 7 Br 570.7576 -1645.3173 0 0 +M V30 8 Cl 642.6376 -1437.8174 0 0 +M V30 9 Cl 714.5178 -1645.3173 0 0 +M V30 10 Cl 786.3978 -1437.8174 0 0 +M V30 11 Cl 858.2778 -1645.3173 0 0 +M V30 12 C 930.1582 -1520.8175 0 0 +M V30 13 C 1002.0381 -1562.3181 0 0 +M V30 14 C 1073.9181 -1520.8182 0 0 +M V30 15 C 1145.7981 -1562.3181 0 0 +M V30 16 Cl 1073.9181 -1437.8182 0 0 +M V30 17 Cl 1002.0381 -1645.3181 0 0 +M V30 18 Cl 930.1582 -1437.8176 0 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 1 1 2 +M V30 2 1 1 11 CFG=1 +M V30 3 1 1 12 +M V30 4 1 2 3 +M V30 5 1 2 10 CFG=1 +M V30 6 1 3 4 +M V30 7 1 3 9 CFG=1 +M V30 8 1 4 5 +M V30 9 1 4 8 CFG=1 +M V30 10 1 5 6 +M V30 11 1 5 7 CFG=1 +M V30 12 1 12 13 +M V30 13 1 12 18 CFG=1 +M V30 14 1 13 14 +M V30 15 1 13 17 CFG=3 +M V30 16 1 14 15 +M V30 17 1 14 16 CFG=3 +M V30 END BOND +M V30 BEGIN COLLECTION +M V30 MDLV30/STERAC2 ATOMS=(1 1) +M V30 MDLV30/STERAC1 ATOMS=(2 2 3) +M V30 MDLV30/STEABS ATOMS=(2 4 5) +M V30 MDLV30/STEREL1 ATOMS=(2 12 13) +M V30 MDLV30/STEREL2 ATOMS=(1 14) +M V30 END COLLECTION +M V30 END CTAB +M END +> +InChI=1S/C10H14BrCl7/c1-3(11)5(13)7(15)9(17)10(18)8(16)6(14)4(2)12/h3-10H,1-2H3/t3-,4+,5+,6-,7-,8-,9+,10+/m0/s1 + +> +InChI=1S/C10H14BrCl7/c1-3(11)5(13)7(15)9(17)10(18)8(16)6(14)4(2)12/h3-10H,1-2H3/t3-,4-,5+,6-,7-,8-,9+,10-/m0/s1(3,5)2(4)(6,8)3(7,9)(10) + +> +1EN + +$$$$ + + ACD/LABS08242216132D + + 0 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 18 17 0 0 1 +M V30 BEGIN ATOM +M V30 1 C 3428.5557 -1046.6348 0 0 +M V30 2 C 3356.6758 -1005.1348 0 0 +M V30 3 C 3284.7957 -1046.6348 0 0 +M V30 4 C 3212.9155 -1005.1348 0 0 +M V30 5 C 3141.0354 -1046.6348 0 0 +M V30 6 C 3069.1553 -1005.1348 0 0 +M V30 7 Br 3141.0354 -1129.6346 0 0 +M V30 8 Cl 3212.9155 -922.1348 0 0 +M V30 9 Cl 3284.7957 -1129.6346 0 0 +M V30 10 Cl 3356.6758 -922.1348 0 0 +M V30 11 Cl 3428.5557 -1129.6346 0 0 +M V30 12 C 3500.436 -1005.1349 0 0 +M V30 13 C 3572.3159 -1046.6355 0 0 +M V30 14 C 3644.196 -1005.1356 0 0 +M V30 15 C 3716.0759 -1046.6355 0 0 +M V30 16 Cl 3644.196 -922.1356 0 0 +M V30 17 Cl 3572.3159 -1129.6355 0 0 +M V30 18 Cl 3500.436 -922.135 0 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 1 1 2 +M V30 2 1 1 11 CFG=3 +M V30 3 1 1 12 +M V30 4 1 2 3 +M V30 5 1 2 10 CFG=3 +M V30 6 1 3 4 +M V30 7 1 3 9 CFG=3 +M V30 8 1 4 5 +M V30 9 1 4 8 CFG=1 +M V30 10 1 5 6 +M V30 11 1 5 7 CFG=1 +M V30 12 1 12 13 +M V30 13 1 12 18 CFG=3 +M V30 14 1 13 14 +M V30 15 1 13 17 CFG=1 +M V30 16 1 14 15 +M V30 17 1 14 16 CFG=1 +M V30 END BOND +M V30 BEGIN COLLECTION +M V30 MDLV30/STERAC2 ATOMS=(1 1) +M V30 MDLV30/STERAC1 ATOMS=(2 2 3) +M V30 MDLV30/STEABS ATOMS=(2 4 5) +M V30 MDLV30/STEREL1 ATOMS=(2 12 13) +M V30 MDLV30/STEREL2 ATOMS=(1 14) +M V30 END COLLECTION +M V30 END CTAB +M END +> +InChI=1S/C10H14BrCl7/c1-3(11)5(13)7(15)9(17)10(18)8(16)6(14)4(2)12/h3-10H,1-2H3/t3-,4-,5+,6+,7+,8+,9-,10-/m0/s1 + +> +InChI=1S/C10H14BrCl7/c1-3(11)5(13)7(15)9(17)10(18)8(16)6(14)4(2)12/h3-10H,1-2H3/t3-,4-,5+,6-,7-,8-,9+,10-/m0/s1(3,5)2(4)(6,8)3(7,9)(10) + +> +1EN + +$$$$ + + ACD/LABS08242216132D + + 0 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 18 17 0 0 1 +M V30 BEGIN ATOM +M V30 1 C 2621.9167 -1043.4761 0 0 +M V30 2 C 2550.0366 -1001.9761 0 0 +M V30 3 C 2478.1567 -1043.4761 0 0 +M V30 4 C 2406.2764 -1001.9761 0 0 +M V30 5 C 2334.3965 -1043.4761 0 0 +M V30 6 C 2262.5166 -1001.9761 0 0 +M V30 7 Br 2334.3965 -1126.476 0 0 +M V30 8 Cl 2406.2764 -918.9761 0 0 +M V30 9 Cl 2478.1567 -1126.476 0 0 +M V30 10 Cl 2550.0366 -918.9761 0 0 +M V30 11 Cl 2621.9167 -1126.476 0 0 +M V30 12 C 2693.7971 -1001.9762 0 0 +M V30 13 C 2765.677 -1043.4768 0 0 +M V30 14 C 2837.5571 -1001.9769 0 0 +M V30 15 C 2909.437 -1043.4768 0 0 +M V30 16 Cl 2837.5571 -918.9769 0 0 +M V30 17 Cl 2765.677 -1126.4768 0 0 +M V30 18 Cl 2693.7971 -918.9763 0 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 1 1 2 +M V30 2 1 1 11 CFG=1 +M V30 3 1 1 12 +M V30 4 1 2 3 +M V30 5 1 2 10 CFG=3 +M V30 6 1 3 4 +M V30 7 1 3 9 CFG=3 +M V30 8 1 4 5 +M V30 9 1 4 8 CFG=1 +M V30 10 1 5 6 +M V30 11 1 5 7 CFG=1 +M V30 12 1 12 13 +M V30 13 1 12 18 CFG=3 +M V30 14 1 13 14 +M V30 15 1 13 17 CFG=1 +M V30 16 1 14 15 +M V30 17 1 14 16 CFG=1 +M V30 END BOND +M V30 BEGIN COLLECTION +M V30 MDLV30/STERAC2 ATOMS=(1 1) +M V30 MDLV30/STERAC1 ATOMS=(2 2 3) +M V30 MDLV30/STEABS ATOMS=(2 4 5) +M V30 MDLV30/STEREL1 ATOMS=(2 12 13) +M V30 MDLV30/STEREL2 ATOMS=(1 14) +M V30 END COLLECTION +M V30 END CTAB +M END +> +InChI=1S/C10H14BrCl7/c1-3(11)5(13)7(15)9(17)10(18)8(16)6(14)4(2)12/h3-10H,1-2H3/t3-,4-,5+,6+,7+,8+,9-,10+/m0/s1 + +> +InChI=1S/C10H14BrCl7/c1-3(11)5(13)7(15)9(17)10(18)8(16)6(14)4(2)12/h3-10H,1-2H3/t3-,4-,5+,6-,7-,8-,9+,10-/m0/s1(3,5)2(4)(6,8)3(7,9)(10) + +> +1EN + +$$$$ + + ACD/LABS08242216132D + + 0 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 18 17 0 0 1 +M V30 BEGIN ATOM +M V30 1 C 1656.9167 -1040.4761 0 0 +M V30 2 C 1585.0367 -998.9761 0 0 +M V30 3 C 1513.1567 -1040.4761 0 0 +M V30 4 C 1441.2765 -998.9761 0 0 +M V30 5 C 1369.3965 -1040.4761 0 0 +M V30 6 C 1297.5165 -998.9761 0 0 +M V30 7 Br 1369.3965 -1123.476 0 0 +M V30 8 Cl 1441.2765 -915.9761 0 0 +M V30 9 Cl 1513.1567 -1123.476 0 0 +M V30 10 Cl 1585.0367 -915.9761 0 0 +M V30 11 Cl 1656.9167 -1123.476 0 0 +M V30 12 C 1728.7971 -998.9762 0 0 +M V30 13 C 1800.677 -1040.4768 0 0 +M V30 14 C 1872.5571 -998.9769 0 0 +M V30 15 C 1944.437 -1040.4768 0 0 +M V30 16 Cl 1872.5571 -915.9769 0 0 +M V30 17 Cl 1800.677 -1123.4768 0 0 +M V30 18 Cl 1728.7971 -915.9763 0 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 1 1 2 +M V30 2 1 1 11 CFG=3 +M V30 3 1 1 12 +M V30 4 1 2 3 +M V30 5 1 2 10 CFG=3 +M V30 6 1 3 4 +M V30 7 1 3 9 CFG=3 +M V30 8 1 4 5 +M V30 9 1 4 8 CFG=1 +M V30 10 1 5 6 +M V30 11 1 5 7 CFG=1 +M V30 12 1 12 13 +M V30 13 1 12 18 CFG=1 +M V30 14 1 13 14 +M V30 15 1 13 17 CFG=3 +M V30 16 1 14 15 +M V30 17 1 14 16 CFG=1 +M V30 END BOND +M V30 BEGIN COLLECTION +M V30 MDLV30/STERAC2 ATOMS=(1 1) +M V30 MDLV30/STERAC1 ATOMS=(2 2 3) +M V30 MDLV30/STEABS ATOMS=(2 4 5) +M V30 MDLV30/STEREL1 ATOMS=(2 12 13) +M V30 MDLV30/STEREL2 ATOMS=(1 14) +M V30 END COLLECTION +M V30 END CTAB +M END +> +InChI=1S/C10H14BrCl7/c1-3(11)5(13)7(15)9(17)10(18)8(16)6(14)4(2)12/h3-10H,1-2H3/t3-,4-,5+,6-,7+,8-,9-,10-/m0/s1 + +> +InChI=1S/C10H14BrCl7/c1-3(11)5(13)7(15)9(17)10(18)8(16)6(14)4(2)12/h3-10H,1-2H3/t3-,4-,5+,6-,7-,8-,9+,10-/m0/s1(3,5)2(4)(6,8)3(7,9)(10) + +> +1EN + +$$$$ + + ACD/LABS08242216132D + + 0 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 18 17 0 0 1 +M V30 BEGIN ATOM +M V30 1 C 850.2778 -1037.3174 0 0 +M V30 2 C 778.3978 -995.8174 0 0 +M V30 3 C 706.5178 -1037.3174 0 0 +M V30 4 C 634.6376 -995.8174 0 0 +M V30 5 C 562.7576 -1037.3174 0 0 +M V30 6 C 490.8776 -995.8174 0 0 +M V30 7 Br 562.7576 -1120.3173 0 0 +M V30 8 Cl 634.6376 -912.8174 0 0 +M V30 9 Cl 706.5178 -1120.3173 0 0 +M V30 10 Cl 778.3978 -912.8174 0 0 +M V30 11 Cl 850.2778 -1120.3173 0 0 +M V30 12 C 922.1582 -995.8175 0 0 +M V30 13 C 994.0381 -1037.3181 0 0 +M V30 14 C 1065.9181 -995.8182 0 0 +M V30 15 C 1137.7981 -1037.3181 0 0 +M V30 16 Cl 1065.9181 -912.8182 0 0 +M V30 17 Cl 994.0381 -1120.3181 0 0 +M V30 18 Cl 922.1582 -912.8176 0 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 1 1 2 +M V30 2 1 1 11 CFG=1 +M V30 3 1 1 12 +M V30 4 1 2 3 +M V30 5 1 2 10 CFG=3 +M V30 6 1 3 4 +M V30 7 1 3 9 CFG=3 +M V30 8 1 4 5 +M V30 9 1 4 8 CFG=1 +M V30 10 1 5 6 +M V30 11 1 5 7 CFG=1 +M V30 12 1 12 13 +M V30 13 1 12 18 CFG=1 +M V30 14 1 13 14 +M V30 15 1 13 17 CFG=3 +M V30 16 1 14 15 +M V30 17 1 14 16 CFG=1 +M V30 END BOND +M V30 BEGIN COLLECTION +M V30 MDLV30/STERAC2 ATOMS=(1 1) +M V30 MDLV30/STERAC1 ATOMS=(2 2 3) +M V30 MDLV30/STEABS ATOMS=(2 4 5) +M V30 MDLV30/STEREL1 ATOMS=(2 12 13) +M V30 MDLV30/STEREL2 ATOMS=(1 14) +M V30 END COLLECTION +M V30 END CTAB +M END +> +InChI=1S/C10H14BrCl7/c1-3(11)5(13)7(15)9(17)10(18)8(16)6(14)4(2)12/h3-10H,1-2H3/t3-,4-,5+,6-,7+,8-,9-,10+/m0/s1 + +> +InChI=1S/C10H14BrCl7/c1-3(11)5(13)7(15)9(17)10(18)8(16)6(14)4(2)12/h3-10H,1-2H3/t3-,4-,5+,6-,7-,8-,9+,10-/m0/s1(3,5)2(4)(6,8)3(7,9)(10) + +> +1EN + +$$$$ + + ACD/LABS08242216132D + + 0 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 18 17 0 0 1 +M V30 BEGIN ATOM +M V30 1 C 3440.9167 -681.4761 0 0 +M V30 2 C 3369.0366 -639.976 0 0 +M V30 3 C 3297.1567 -681.4761 0 0 +M V30 4 C 3225.2764 -639.976 0 0 +M V30 5 C 3153.3965 -681.4761 0 0 +M V30 6 C 3081.5166 -639.976 0 0 +M V30 7 Br 3153.3965 -764.476 0 0 +M V30 8 Cl 3225.2764 -556.976 0 0 +M V30 9 Cl 3297.1567 -764.476 0 0 +M V30 10 Cl 3369.0366 -556.976 0 0 +M V30 11 Cl 3440.9167 -764.476 0 0 +M V30 12 C 3512.7971 -639.9762 0 0 +M V30 13 C 3584.677 -681.4769 0 0 +M V30 14 C 3656.5569 -639.9769 0 0 +M V30 15 C 3728.437 -681.4769 0 0 +M V30 16 Cl 3656.5569 -556.9769 0 0 +M V30 17 Cl 3584.677 -764.4768 0 0 +M V30 18 Cl 3512.7971 -556.9764 0 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 1 1 2 +M V30 2 1 1 11 CFG=3 +M V30 3 1 1 12 +M V30 4 1 2 3 +M V30 5 1 2 10 CFG=3 +M V30 6 1 3 4 +M V30 7 1 3 9 CFG=3 +M V30 8 1 4 5 +M V30 9 1 4 8 CFG=1 +M V30 10 1 5 6 +M V30 11 1 5 7 CFG=1 +M V30 12 1 12 13 +M V30 13 1 12 18 CFG=3 +M V30 14 1 13 14 +M V30 15 1 13 17 CFG=1 +M V30 16 1 14 15 +M V30 17 1 14 16 CFG=3 +M V30 END BOND +M V30 BEGIN COLLECTION +M V30 MDLV30/STERAC2 ATOMS=(1 1) +M V30 MDLV30/STERAC1 ATOMS=(2 2 3) +M V30 MDLV30/STEABS ATOMS=(2 4 5) +M V30 MDLV30/STEREL1 ATOMS=(2 12 13) +M V30 MDLV30/STEREL2 ATOMS=(1 14) +M V30 END COLLECTION +M V30 END CTAB +M END +> +InChI=1S/C10H14BrCl7/c1-3(11)5(13)7(15)9(17)10(18)8(16)6(14)4(2)12/h3-10H,1-2H3/t3-,4+,5+,6+,7+,8+,9-,10-/m0/s1 + +> +InChI=1S/C10H14BrCl7/c1-3(11)5(13)7(15)9(17)10(18)8(16)6(14)4(2)12/h3-10H,1-2H3/t3-,4-,5+,6-,7-,8-,9+,10-/m0/s1(3,5)2(4)(6,8)3(7,9)(10) + +> +1EN + +$$$$ + + ACD/LABS08242216132D + + 0 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 18 17 0 0 1 +M V30 BEGIN ATOM +M V30 1 C 2634.2778 -678.3174 0 0 +M V30 2 C 2562.3979 -636.8173 0 0 +M V30 3 C 2490.5178 -678.3174 0 0 +M V30 4 C 2418.6377 -636.8173 0 0 +M V30 5 C 2346.7576 -678.3174 0 0 +M V30 6 C 2274.8774 -636.8173 0 0 +M V30 7 Br 2346.7576 -761.3173 0 0 +M V30 8 Cl 2418.6377 -553.8173 0 0 +M V30 9 Cl 2490.5178 -761.3173 0 0 +M V30 10 Cl 2562.3979 -553.8173 0 0 +M V30 11 Cl 2634.2778 -761.3173 0 0 +M V30 12 C 2706.1582 -636.8175 0 0 +M V30 13 C 2778.0381 -678.3182 0 0 +M V30 14 C 2849.918 -636.8182 0 0 +M V30 15 C 2921.7981 -678.3182 0 0 +M V30 16 Cl 2849.918 -553.8182 0 0 +M V30 17 Cl 2778.0381 -761.3181 0 0 +M V30 18 Cl 2706.1582 -553.8177 0 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 1 1 2 +M V30 2 1 1 11 CFG=1 +M V30 3 1 1 12 +M V30 4 1 2 3 +M V30 5 1 2 10 CFG=3 +M V30 6 1 3 4 +M V30 7 1 3 9 CFG=3 +M V30 8 1 4 5 +M V30 9 1 4 8 CFG=1 +M V30 10 1 5 6 +M V30 11 1 5 7 CFG=1 +M V30 12 1 12 13 +M V30 13 1 12 18 CFG=3 +M V30 14 1 13 14 +M V30 15 1 13 17 CFG=1 +M V30 16 1 14 15 +M V30 17 1 14 16 CFG=3 +M V30 END BOND +M V30 BEGIN COLLECTION +M V30 MDLV30/STERAC2 ATOMS=(1 1) +M V30 MDLV30/STERAC1 ATOMS=(2 2 3) +M V30 MDLV30/STEABS ATOMS=(2 4 5) +M V30 MDLV30/STEREL1 ATOMS=(2 12 13) +M V30 MDLV30/STEREL2 ATOMS=(1 14) +M V30 END COLLECTION +M V30 END CTAB +M END +> +InChI=1S/C10H14BrCl7/c1-3(11)5(13)7(15)9(17)10(18)8(16)6(14)4(2)12/h3-10H,1-2H3/t3-,4+,5+,6+,7+,8+,9-,10+/m0/s1 + +> +InChI=1S/C10H14BrCl7/c1-3(11)5(13)7(15)9(17)10(18)8(16)6(14)4(2)12/h3-10H,1-2H3/t3-,4-,5+,6-,7-,8-,9+,10-/m0/s1(3,5)2(4)(6,8)3(7,9)(10) + +> +1EN + +$$$$ + + ACD/LABS08242216132D + + 0 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 18 17 0 0 1 +M V30 BEGIN ATOM +M V30 1 C 1669.2778 -675.3174 0 0 +M V30 2 C 1597.3978 -633.8173 0 0 +M V30 3 C 1525.5178 -675.3174 0 0 +M V30 4 C 1453.6376 -633.8173 0 0 +M V30 5 C 1381.7576 -675.3174 0 0 +M V30 6 C 1309.8776 -633.8173 0 0 +M V30 7 Br 1381.7576 -758.3173 0 0 +M V30 8 Cl 1453.6376 -550.8173 0 0 +M V30 9 Cl 1525.5178 -758.3173 0 0 +M V30 10 Cl 1597.3978 -550.8173 0 0 +M V30 11 Cl 1669.2778 -758.3173 0 0 +M V30 12 C 1741.1582 -633.8175 0 0 +M V30 13 C 1813.0381 -675.3182 0 0 +M V30 14 C 1884.918 -633.8182 0 0 +M V30 15 C 1956.7981 -675.3182 0 0 +M V30 16 Cl 1884.918 -550.8182 0 0 +M V30 17 Cl 1813.0381 -758.3181 0 0 +M V30 18 Cl 1741.1582 -550.8177 0 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 1 1 2 +M V30 2 1 1 11 CFG=3 +M V30 3 1 1 12 +M V30 4 1 2 3 +M V30 5 1 2 10 CFG=3 +M V30 6 1 3 4 +M V30 7 1 3 9 CFG=3 +M V30 8 1 4 5 +M V30 9 1 4 8 CFG=1 +M V30 10 1 5 6 +M V30 11 1 5 7 CFG=1 +M V30 12 1 12 13 +M V30 13 1 12 18 CFG=1 +M V30 14 1 13 14 +M V30 15 1 13 17 CFG=3 +M V30 16 1 14 15 +M V30 17 1 14 16 CFG=3 +M V30 END BOND +M V30 BEGIN COLLECTION +M V30 MDLV30/STERAC2 ATOMS=(1 1) +M V30 MDLV30/STERAC1 ATOMS=(2 2 3) +M V30 MDLV30/STEABS ATOMS=(2 4 5) +M V30 MDLV30/STEREL1 ATOMS=(2 12 13) +M V30 MDLV30/STEREL2 ATOMS=(1 14) +M V30 END COLLECTION +M V30 END CTAB +M END +> +InChI=1S/C10H14BrCl7/c1-3(11)5(13)7(15)9(17)10(18)8(16)6(14)4(2)12/h3-10H,1-2H3/t3-,4+,5+,6-,7+,8-,9-,10-/m0/s1 + +> +InChI=1S/C10H14BrCl7/c1-3(11)5(13)7(15)9(17)10(18)8(16)6(14)4(2)12/h3-10H,1-2H3/t3-,4-,5+,6-,7-,8-,9+,10-/m0/s1(3,5)2(4)(6,8)3(7,9)(10) + +> +1EN + +$$$$ + + ACD/LABS08242216132D + + 0 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 18 17 0 0 1 +M V30 BEGIN ATOM +M V30 1 C 719.8806 -405.6257 0 0 +M V30 2 C 638.4742 -358.6256 0 0 +M V30 3 C 557.0679 -405.6257 0 0 +M V30 4 C 475.6614 -358.6256 0 0 +M V30 5 C 394.255 -405.6257 0 0 +M V30 6 C 312.8487 -358.6256 0 0 +M V30 7 Br 394.255 -499.6256 0 0 +M V30 8 Cl 475.6614 -264.6255 0 0 +M V30 9 Cl 557.0679 -499.6256 0 0 +M V30 10 Cl 638.4742 -264.6255 0 0 +M V30 11 Cl 719.8806 -499.6256 0 0 +M V30 12 C 801.2872 -358.6258 0 0 +M V30 13 C 882.6934 -405.6266 0 0 +M V30 14 C 964.0997 -358.6266 0 0 +M V30 15 C 1045.5061 -405.6266 0 0 +M V30 16 Cl 964.0997 -264.6266 0 0 +M V30 17 Cl 882.6934 -499.6266 0 0 +M V30 18 Cl 801.2872 -264.626 0 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 1 1 2 +M V30 2 1 1 11 CFG=1 +M V30 3 1 1 12 +M V30 4 1 2 3 +M V30 5 1 2 10 CFG=3 +M V30 6 1 3 4 +M V30 7 1 3 9 CFG=3 +M V30 8 1 4 5 +M V30 9 1 4 8 CFG=1 +M V30 10 1 5 6 +M V30 11 1 5 7 CFG=1 +M V30 12 1 12 13 +M V30 13 1 12 18 CFG=1 +M V30 14 1 13 14 +M V30 15 1 13 17 CFG=3 +M V30 16 1 14 15 +M V30 17 1 14 16 CFG=3 +M V30 END BOND +M V30 BEGIN COLLECTION +M V30 MDLV30/STERAC2 ATOMS=(1 1) +M V30 MDLV30/STERAC1 ATOMS=(2 2 3) +M V30 MDLV30/STEABS ATOMS=(2 4 5) +M V30 MDLV30/STEREL1 ATOMS=(2 12 13) +M V30 MDLV30/STEREL2 ATOMS=(1 14) +M V30 END COLLECTION +M V30 END CTAB +M END +> +InChI=1S/C10H14BrCl7/c1-3(11)5(13)7(15)9(17)10(18)8(16)6(14)4(2)12/h3-10H,1-2H3/t3-,4+,5+,6-,7+,8-,9-,10+/m0/s1 + +> +InChI=1S/C10H14BrCl7/c1-3(11)5(13)7(15)9(17)10(18)8(16)6(14)4(2)12/h3-10H,1-2H3/t3-,4-,5+,6-,7-,8-,9+,10-/m0/s1(3,5)2(4)(6,8)3(7,9)(10) + +> +1EN + +$$$$ + + ACD/LABS08242216132D + + 0 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 18 17 0 0 1 +M V30 BEGIN ATOM +M V30 1 C 1789.6389 -266.1587 0 0 +M V30 2 C 1717.7589 -224.6586 0 0 +M V30 3 C 1645.8789 -266.1587 0 0 +M V30 4 C 1573.9987 -224.6586 0 0 +M V30 5 C 1502.1187 -266.1587 0 0 +M V30 6 C 1430.2386 -224.6586 0 0 +M V30 7 Br 1502.1187 -349.1586 0 0 +M V30 8 Cl 1573.9987 -141.6586 0 0 +M V30 9 Cl 1645.8789 -349.1586 0 0 +M V30 10 Cl 1717.7589 -141.6586 0 0 +M V30 11 Cl 1789.6389 -349.1586 0 0 +M V30 12 C 1861.5193 -224.6588 0 0 +M V30 13 C 1933.3992 -266.1595 0 0 +M V30 14 C 2005.2792 -224.6595 0 0 +M V30 15 C 2077.1592 -266.1595 0 0 +M V30 16 Cl 2005.2792 -141.6595 0 0 +M V30 17 Cl 1933.3992 -349.1595 0 0 +M V30 18 Cl 1861.5193 -141.659 0 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 1 1 2 +M V30 2 1 1 11 CFG=1 +M V30 3 1 1 12 +M V30 4 1 2 3 +M V30 5 1 2 10 CFG=3 +M V30 6 1 3 4 +M V30 7 1 3 9 CFG=3 +M V30 8 1 4 5 +M V30 9 1 4 8 CFG=3 +M V30 10 1 5 6 +M V30 11 1 5 7 CFG=3 +M V30 12 1 12 13 +M V30 13 1 12 18 CFG=3 +M V30 14 1 13 14 +M V30 15 1 13 17 CFG=1 +M V30 16 1 14 15 +M V30 17 1 14 16 CFG=3 +M V30 END BOND +M V30 BEGIN COLLECTION +M V30 MDLV30/STERAC2 ATOMS=(1 1) +M V30 MDLV30/STEABS ATOMS=(2 2 3) +M V30 MDLV30/STERAC1 ATOMS=(2 4 5) +M V30 MDLV30/STEREL2 ATOMS=(2 12 13) +M V30 MDLV30/STEREL1 ATOMS=(1 14) +M V30 END COLLECTION +M V30 END CTAB +M END +> +InChI=1S/C10H14BrCl7/c1-3(11)5(13)7(15)9(17)10(18)8(16)6(14)4(2)12/h3-10H,1-2H3/t3-,4-,5+,6-,7-,8-,9+,10-/m1/s1 + +> +InChI=1S/C10H14BrCl7/c1-3(11)5(13)7(15)9(17)10(18)8(16)6(14)4(2)12/h3-10H,1-2H3/t3-,4-,5+,6-,7-,8-,9+,10-/m1/s1(7,9)2(4)(6,8)3(3,5)(10) + +> +2EN + +$$$$ + + ACD/LABS08242216132D + + 0 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 18 17 0 0 1 +M V30 BEGIN ATOM +M V30 1 C 1789.6389 -266.1587 0 0 +M V30 2 C 1717.7589 -224.6586 0 0 +M V30 3 C 1645.8789 -266.1587 0 0 +M V30 4 C 1573.9987 -224.6586 0 0 +M V30 5 C 1502.1187 -266.1587 0 0 +M V30 6 C 1430.2386 -224.6586 0 0 +M V30 7 Br 1502.1187 -349.1586 0 0 +M V30 8 Cl 1573.9987 -141.6586 0 0 +M V30 9 Cl 1645.8789 -349.1586 0 0 +M V30 10 Cl 1717.7589 -141.6586 0 0 +M V30 11 Cl 1789.6389 -349.1586 0 0 +M V30 12 C 1861.5193 -224.6588 0 0 +M V30 13 C 1933.3992 -266.1595 0 0 +M V30 14 C 2005.2792 -224.6595 0 0 +M V30 15 C 2077.1592 -266.1595 0 0 +M V30 16 Cl 2005.2792 -141.6595 0 0 +M V30 17 Cl 1933.3992 -349.1595 0 0 +M V30 18 Cl 1861.5193 -141.659 0 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 1 1 2 +M V30 2 1 1 11 CFG=1 +M V30 3 1 1 12 +M V30 4 1 2 3 +M V30 5 1 2 10 CFG=3 +M V30 6 1 3 4 +M V30 7 1 3 9 CFG=3 +M V30 8 1 4 5 +M V30 9 1 4 8 CFG=3 +M V30 10 1 5 6 +M V30 11 1 5 7 CFG=3 +M V30 12 1 12 13 +M V30 13 1 12 18 CFG=3 +M V30 14 1 13 14 +M V30 15 1 13 17 CFG=1 +M V30 16 1 14 15 +M V30 17 1 14 16 CFG=3 +M V30 END BOND +M V30 BEGIN COLLECTION +M V30 MDLV30/STERAC1 ATOMS=(1 1) +M V30 MDLV30/STEABS ATOMS=(2 2 3) +M V30 MDLV30/STERAC2 ATOMS=(2 4 5) +M V30 MDLV30/STEREL2 ATOMS=(2 12 13) +M V30 MDLV30/STEREL1 ATOMS=(1 14) +M V30 END COLLECTION +M V30 END CTAB +M END +> +InChI=1S/C10H14BrCl7/c1-3(11)5(13)7(15)9(17)10(18)8(16)6(14)4(2)12/h3-10H,1-2H3/t3-,4+,5+,6-,7+,8-,9-,10+/m0/s1 + +> +InChI=1S/C10H14BrCl7/c1-3(11)5(13)7(15)9(17)10(18)8(16)6(14)4(2)12/h3-10H,1-2H3/t3-,4-,5+,6-,7-,8-,9+,10-/m1/s1(7,9)2(4)(6,8)3(3,5)(10) + +> +2EN + +$$$$ + + ACD/LABS08242216132D + + 0 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 18 17 0 0 1 +M V30 BEGIN ATOM +M V30 1 C 1789.6389 -266.1587 0 0 +M V30 2 C 1717.7589 -224.6586 0 0 +M V30 3 C 1645.8789 -266.1587 0 0 +M V30 4 C 1573.9987 -224.6586 0 0 +M V30 5 C 1502.1187 -266.1587 0 0 +M V30 6 C 1430.2386 -224.6586 0 0 +M V30 7 Br 1502.1187 -349.1586 0 0 +M V30 8 Cl 1573.9987 -141.6586 0 0 +M V30 9 Cl 1645.8789 -349.1586 0 0 +M V30 10 Cl 1717.7589 -141.6586 0 0 +M V30 11 Cl 1789.6389 -349.1586 0 0 +M V30 12 C 1861.5193 -224.6588 0 0 +M V30 13 C 1933.3992 -266.1595 0 0 +M V30 14 C 2005.2792 -224.6595 0 0 +M V30 15 C 2077.1592 -266.1595 0 0 +M V30 16 Cl 2005.2792 -141.6595 0 0 +M V30 17 Cl 1933.3992 -349.1595 0 0 +M V30 18 Cl 1861.5193 -141.659 0 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 1 1 2 +M V30 2 1 1 11 CFG=1 +M V30 3 1 1 12 +M V30 4 1 2 3 +M V30 5 1 2 10 CFG=3 +M V30 6 1 3 4 +M V30 7 1 3 9 CFG=3 +M V30 8 1 4 5 +M V30 9 1 4 8 CFG=3 +M V30 10 1 5 6 +M V30 11 1 5 7 CFG=3 +M V30 12 1 12 13 +M V30 13 1 12 18 CFG=3 +M V30 14 1 13 14 +M V30 15 1 13 17 CFG=1 +M V30 16 1 14 15 +M V30 17 1 14 16 CFG=3 +M V30 END BOND +M V30 BEGIN COLLECTION +M V30 MDLV30/STERAC1 ATOMS=(1 1) +M V30 MDLV30/STEABS ATOMS=(2 2 3) +M V30 MDLV30/STERAC2 ATOMS=(2 4 5) +M V30 MDLV30/STEREL1 ATOMS=(2 12 13) +M V30 MDLV30/STEREL2 ATOMS=(1 14) +M V30 END COLLECTION +M V30 END CTAB +M END +> +InChI=1S/C10H14BrCl7/c1-3(11)5(13)7(15)9(17)10(18)8(16)6(14)4(2)12/h3-10H,1-2H3/t3-,4+,5+,6-,7+,8-,9-,10+/m0/s1 + +> +InChI=1S/C10H14BrCl7/c1-3(11)5(13)7(15)9(17)10(18)8(16)6(14)4(2)12/h3-10H,1-2H3/t3-,4-,5+,6-,7-,8-,9+,10-/m1/s1(7,9)2(4)(6,8)3(3,5)(10) + +> +2EN + +$$$$ + + ACD/LABS08242216132D + + 0 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 18 17 0 0 1 +M V30 BEGIN ATOM +M V30 1 C 1789.6389 -266.1587 0 0 +M V30 2 C 1717.7589 -224.6586 0 0 +M V30 3 C 1645.8789 -266.1587 0 0 +M V30 4 C 1573.9987 -224.6586 0 0 +M V30 5 C 1502.1187 -266.1587 0 0 +M V30 6 C 1430.2386 -224.6586 0 0 +M V30 7 Br 1502.1187 -349.1586 0 0 +M V30 8 Cl 1573.9987 -141.6586 0 0 +M V30 9 Cl 1645.8789 -349.1586 0 0 +M V30 10 Cl 1717.7589 -141.6586 0 0 +M V30 11 Cl 1789.6389 -349.1586 0 0 +M V30 12 C 1861.5193 -224.6588 0 0 +M V30 13 C 1933.3992 -266.1595 0 0 +M V30 14 C 2005.2792 -224.6595 0 0 +M V30 15 C 2077.1592 -266.1595 0 0 +M V30 16 Cl 2005.2792 -141.6595 0 0 +M V30 17 Cl 1933.3992 -349.1595 0 0 +M V30 18 Cl 1861.5193 -141.659 0 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 1 1 2 +M V30 2 1 1 11 CFG=3 +M V30 3 1 1 12 +M V30 4 1 2 3 +M V30 5 1 2 10 CFG=3 +M V30 6 1 3 4 +M V30 7 1 3 9 CFG=3 +M V30 8 1 4 5 +M V30 9 1 4 8 CFG=1 +M V30 10 1 5 6 +M V30 11 1 5 7 CFG=1 +M V30 12 1 12 13 +M V30 13 1 12 18 CFG=3 +M V30 14 1 13 14 +M V30 15 1 13 17 CFG=1 +M V30 16 1 14 15 +M V30 17 1 14 16 CFG=3 +M V30 END BOND +M V30 BEGIN COLLECTION +M V30 MDLV30/STERAC1 ATOMS=(1 1) +M V30 MDLV30/STEABS ATOMS=(2 2 3) +M V30 MDLV30/STERAC2 ATOMS=(2 4 5) +M V30 MDLV30/STEREL1 ATOMS=(2 12 13) +M V30 MDLV30/STEREL2 ATOMS=(1 14) +M V30 END COLLECTION +M V30 END CTAB +M END +> +InChI=1S/C10H14BrCl7/c1-3(11)5(13)7(15)9(17)10(18)8(16)6(14)4(2)12/h3-10H,1-2H3/t3-,4+,5+,6-,7+,8-,9-,10+/m0/s1 + +> +InChI=1S/C10H14BrCl7/c1-3(11)5(13)7(15)9(17)10(18)8(16)6(14)4(2)12/h3-10H,1-2H3/t3-,4-,5+,6-,7-,8-,9+,10-/m1/s1(7,9)2(4)(6,8)3(3,5)(10) + +> +2EN + +$$$$ + + ACD/LABS08242216132D + + 0 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 18 17 0 0 1 +M V30 BEGIN ATOM +M V30 1 C 1789.6389 -266.1587 0 0 +M V30 2 C 1717.7589 -224.6586 0 0 +M V30 3 C 1645.8789 -266.1587 0 0 +M V30 4 C 1573.9987 -224.6586 0 0 +M V30 5 C 1502.1187 -266.1587 0 0 +M V30 6 C 1430.2386 -224.6586 0 0 +M V30 7 Br 1502.1187 -349.1586 0 0 +M V30 8 Cl 1573.9987 -141.6586 0 0 +M V30 9 Cl 1645.8789 -349.1586 0 0 +M V30 10 Cl 1717.7589 -141.6586 0 0 +M V30 11 Cl 1789.6389 -349.1586 0 0 +M V30 12 C 1861.5193 -224.6588 0 0 +M V30 13 C 1933.3992 -266.1595 0 0 +M V30 14 C 2005.2792 -224.6595 0 0 +M V30 15 C 2077.1592 -266.1595 0 0 +M V30 16 Cl 2005.2792 -141.6595 0 0 +M V30 17 Cl 1933.3992 -349.1595 0 0 +M V30 18 Cl 1861.5193 -141.659 0 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 1 1 2 +M V30 2 1 1 11 CFG=1 +M V30 3 1 1 12 +M V30 4 1 2 3 +M V30 5 1 2 10 CFG=3 +M V30 6 1 3 4 +M V30 7 1 3 9 CFG=3 +M V30 8 1 4 5 +M V30 9 1 4 8 CFG=1 +M V30 10 1 5 6 +M V30 11 1 5 7 CFG=1 +M V30 12 1 12 13 +M V30 13 1 12 18 CFG=1 +M V30 14 1 13 14 +M V30 15 1 13 17 CFG=3 +M V30 16 1 14 15 +M V30 17 1 14 16 CFG=3 +M V30 END BOND +M V30 BEGIN COLLECTION +M V30 MDLV30/STERAC1 ATOMS=(1 1) +M V30 MDLV30/STEABS ATOMS=(2 2 3) +M V30 MDLV30/STERAC2 ATOMS=(2 4 5) +M V30 MDLV30/STEREL1 ATOMS=(2 12 13) +M V30 MDLV30/STEREL2 ATOMS=(1 14) +M V30 END COLLECTION +M V30 END CTAB +M END +> +InChI=1S/C10H14BrCl7/c1-3(11)5(13)7(15)9(17)10(18)8(16)6(14)4(2)12/h3-10H,1-2H3/t3-,4+,5+,6-,7+,8-,9-,10+/m0/s1 + +> +InChI=1S/C10H14BrCl7/c1-3(11)5(13)7(15)9(17)10(18)8(16)6(14)4(2)12/h3-10H,1-2H3/t3-,4-,5+,6-,7-,8-,9+,10-/m1/s1(7,9)2(4)(6,8)3(3,5)(10) + +> +2EN + +$$$$ + + ACD/LABS08242216132D + + 0 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 8 8 0 0 1 +M V30 BEGIN ATOM +M V30 1 C 858.7786 -570.2803 0 0 +M V30 2 C 907.3196 -535.0132 0 0 +M V30 3 C 877.3196 -627.3436 0 0 +M V30 4 O 801.7153 -551.7392 0 0 +M V30 5 O 907.3197 -475.0132 0 0 +M V30 6 C 955.8607 -570.2803 0 0 +M V30 7 Cl 1012.9241 -551.7394 0 0 +M V30 8 C 937.3196 -627.3438 0 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 1 1 2 +M V30 2 1 1 3 +M V30 3 1 1 4 CFG=1 +M V30 4 1 2 5 CFG=1 +M V30 5 1 2 6 +M V30 6 1 3 8 +M V30 7 1 6 7 CFG=1 +M V30 8 1 6 8 +M V30 END BOND +M V30 BEGIN COLLECTION +M V30 MDLV30/STEREL1 ATOMS=(3 1 2 6) +M V30 END COLLECTION +M V30 END CTAB +M END +> +InChI=1S/C5H9ClO2/c6-3-1-2-4(7)5(3)8/h3-5,7-8H,1-2H2/t3-,4+,5-/m0/s1 + +> +InChI=1/C5H9ClO2/c6-3-1-2-4(7)5(3)8/h3-5,7-8H,1-2H2/t3-,4+,5-/m0/s2 + +> +3EN + +$$$$ + + ACD/LABS08242216132D + + 0 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 8 8 0 0 1 +M V30 BEGIN ATOM +M V30 1 C 413.74 -573.2996 0 0 +M V30 2 C 462.2811 -538.0325 0 0 +M V30 3 C 432.2809 -630.3629 0 0 +M V30 4 O 356.6766 -554.7585 0 0 +M V30 5 O 462.2811 -478.0325 0 0 +M V30 6 C 510.822 -573.2996 0 0 +M V30 7 Cl 567.8854 -554.7587 0 0 +M V30 8 C 492.281 -630.363 0 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 1 1 2 +M V30 2 1 1 3 +M V30 3 1 1 4 CFG=3 +M V30 4 1 2 5 CFG=3 +M V30 5 1 2 6 +M V30 6 1 3 8 +M V30 7 1 6 7 CFG=3 +M V30 8 1 6 8 +M V30 END BOND +M V30 BEGIN COLLECTION +M V30 MDLV30/STEREL1 ATOMS=(3 1 2 6) +M V30 END COLLECTION +M V30 END CTAB +M END +> +InChI=1/C5H9ClO2/c6-3-1-2-4(7)5(3)8/h3-5,7-8H,1-2H2/t3-,4+,5-/m1/s1 + +> +InChI=1/C5H9ClO2/c6-3-1-2-4(7)5(3)8/h3-5,7-8H,1-2H2/t3-,4+,5-/m0/s2 + +> +3EN + +$$$$ + + ACD/LABS08242216132D + + 0 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 8 8 0 0 1 +M V30 BEGIN ATOM +M V30 1 C 1839.8173 -572.261 0 0 +M V30 2 C 1888.3582 -536.9939 0 0 +M V30 3 C 1858.3582 -629.3243 0 0 +M V30 4 O 1782.7539 -553.7199 0 0 +M V30 5 O 1888.3583 -476.9939 0 0 +M V30 6 C 1936.8992 -572.261 0 0 +M V30 7 Cl 1993.9626 -553.7202 0 0 +M V30 8 C 1918.358 -629.3245 0 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 1 1 2 +M V30 2 1 1 3 +M V30 3 1 1 4 CFG=1 +M V30 4 1 2 5 CFG=1 +M V30 5 1 2 6 +M V30 6 1 3 8 +M V30 7 1 6 7 CFG=1 +M V30 8 1 6 8 +M V30 END BOND +M V30 BEGIN COLLECTION +M V30 MDLV30/STERAC1 ATOMS=(3 1 2 6) +M V30 END COLLECTION +M V30 END CTAB +M END +> +InChI=1S/C5H9ClO2/c6-3-1-2-4(7)5(3)8/h3-5,7-8H,1-2H2/t3-,4+,5-/m0/s1 + +> +InChI=1/C5H9ClO2/c6-3-1-2-4(7)5(3)8/h3-5,7-8H,1-2H2/t3-,4+,5-/m0/s3 + +> +4EN + +$$$$ + + ACD/LABS08242216132D + + 0 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 8 8 0 0 1 +M V30 BEGIN ATOM +M V30 1 C 1394.7787 -575.2803 0 0 +M V30 2 C 1443.3196 -540.0132 0 0 +M V30 3 C 1413.3196 -632.3436 0 0 +M V30 4 O 1337.7153 -556.7392 0 0 +M V30 5 O 1443.3197 -480.0132 0 0 +M V30 6 C 1491.8606 -575.2803 0 0 +M V30 7 Cl 1548.9241 -556.7394 0 0 +M V30 8 C 1473.3195 -632.3438 0 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 1 1 2 +M V30 2 1 1 3 +M V30 3 1 1 4 CFG=3 +M V30 4 1 2 5 CFG=3 +M V30 5 1 2 6 +M V30 6 1 3 8 +M V30 7 1 6 7 CFG=3 +M V30 8 1 6 8 +M V30 END BOND +M V30 BEGIN COLLECTION +M V30 MDLV30/STERAC1 ATOMS=(3 1 2 6) +M V30 END COLLECTION +M V30 END CTAB +M END +> +InChI=1S/C5H9ClO2/c6-3-1-2-4(7)5(3)8/h3-5,7-8H,1-2H2/t3-,4+,5-/m1/s1 + +> +InChI=1/C5H9ClO2/c6-3-1-2-4(7)5(3)8/h3-5,7-8H,1-2H2/t3-,4+,5-/m0/s3 + +> +4EN + +$$$$ + + ACD/LABS08242216132D + + 0 0 0 0 0 0 0 0 0 0 0 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 11 10 0 0 0 +M V30 BEGIN ATOM +M V30 1 C 5.15734 -5.49189 0.0 0 +M V30 2 C 6.02876 -5.00137 0.0 0 CFG=2 +M V30 3 C 6.88929 -5.51079 0.0 0 +M V30 4 C 7.76071 -5.02026 0.0 0 CFG=2 +M V30 5 C 8.62123 -5.52968 0.0 0 +M V30 6 C 9.49266 -5.03916 0.0 0 CFG=2 +M V30 7 C 10.3532 -5.54857 0.0 0 +M V30 8 Cl 6.03968 -4.00143 0.0 0 +M V30 9 Br 7.77162 -4.02032 0.0 0 +M V30 10 O 9.50357 -4.03922 0.0 0 +M V30 11 C 4.29682 -4.98247 0.0 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 1 1 2 +M V30 2 1 1 11 +M V30 3 1 2 3 +M V30 4 1 2 8 CFG=1 +M V30 5 1 3 4 +M V30 6 1 4 5 +M V30 7 1 4 9 CFG=1 +M V30 8 1 5 6 +M V30 9 1 6 7 +M V30 10 1 6 10 CFG=1 +M V30 END BOND +M V30 BEGIN COLLECTION +M V30 MDLV30/STEABS ATOMS=(3 2 4 6) +M V30 END COLLECTION +M V30 END CTAB +M END +> +InChI=1S/C7H14BrClO/c1-5(9)3-7(8)4-6(2)10/h5-7,10H,3-4H2,1-2H3/t5-,6+,7-/m1/s1 + +> +InChI=1S/C7H14BrClO/c1-5(9)3-7(8)4-6(2)10/h5-7,10H,3-4H2,1-2H3/t5-,6+,7-/m1/s1 + +> +5EN + +$$$$ + + ACD/LABS08242216132D + + 0 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 10 9 0 0 1 +M V30 BEGIN ATOM +M V30 1 C 250 -700 0 0 +M V30 2 C 302.2857 -670.5686 0 0 +M V30 3 C 353.9169 -701.1337 0 0 +M V30 4 C 406.2026 -671.7023 0 0 +M V30 5 C 457.8337 -702.2673 0 0 +M V30 6 C 510.1194 -672.8359 0 0 +M V30 7 C 561.7506 -703.4009 0 0 +M V30 8 Cl 302.9404 -610.5721 0 0 +M V30 9 Br 406.8572 -611.7058 0 0 +M V30 10 O 510.7741 -612.8395 0 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 1 1 2 +M V30 2 1 2 3 +M V30 3 1 2 8 CFG=1 +M V30 4 1 3 4 +M V30 5 1 4 5 +M V30 6 1 4 9 CFG=1 +M V30 7 1 5 6 +M V30 8 1 6 7 +M V30 9 1 6 10 CFG=3 +M V30 END BOND +M V30 BEGIN COLLECTION +M V30 MDLV30/STEABS ATOMS=(1 2) +M V30 MDLV30/STEREL1 ATOMS=(1 4) +M V30 MDLV30/STERAC1 ATOMS=(1 6) +M V30 END COLLECTION +M V30 END CTAB +M END +> +InChI=1S/C7H14BrClO/c1-5(9)3-7(8)4-6(2)10/h5-7,10H,3-4H2,1-2H3/t5-,6-,7-/m1/s1 + +> +InChI=1S/C7H14BrClO/c1-5(9)3-7(8)4-6(2)10/h5-7,10H,3-4H2,1-2H3/t5-,6-,7-/m1/s1(5)2(7)3(6) + +> +6EN + +$$$$ + + ACD/LABS08242216132D + + 0 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 10 9 0 0 1 +M V30 BEGIN ATOM +M V30 1 C 250 -700 0 0 +M V30 2 C 302.2857 -670.5686 0 0 +M V30 3 C 353.9169 -701.1337 0 0 +M V30 4 C 406.2026 -671.7023 0 0 +M V30 5 C 457.8337 -702.2673 0 0 +M V30 6 C 510.1194 -672.8359 0 0 +M V30 7 C 561.7506 -703.4009 0 0 +M V30 8 Cl 302.9404 -610.5721 0 0 +M V30 9 Br 406.8572 -611.7058 0 0 +M V30 10 O 510.7741 -612.8395 0 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 1 1 2 +M V30 2 1 2 3 +M V30 3 1 2 8 CFG=1 +M V30 4 1 3 4 +M V30 5 1 4 5 +M V30 6 1 4 9 CFG=3 +M V30 7 1 5 6 +M V30 8 1 6 7 +M V30 9 1 6 10 CFG=3 +M V30 END BOND +M V30 BEGIN COLLECTION +M V30 MDLV30/STEABS ATOMS=(1 2) +M V30 MDLV30/STEREL1 ATOMS=(1 4) +M V30 MDLV30/STERAC1 ATOMS=(1 6) +M V30 END COLLECTION +M V30 END CTAB +M END +> +InChI=1S/C7H14BrClO/c1-5(9)3-7(8)4-6(2)10/h5-7,10H,3-4H2,1-2H3/t5-,6-,7+/m1/s1 + +> +InChI=1S/C7H14BrClO/c1-5(9)3-7(8)4-6(2)10/h5-7,10H,3-4H2,1-2H3/t5-,6-,7-/m1/s1(5)2(7)3(6) + +> +6EN + +$$$$ + + ACD/LABS08242216132D + + 0 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 10 9 0 0 1 +M V30 BEGIN ATOM +M V30 1 C 250 -700 0 0 +M V30 2 C 302.2857 -670.5686 0 0 +M V30 3 C 353.9169 -701.1337 0 0 +M V30 4 C 406.2026 -671.7023 0 0 +M V30 5 C 457.8337 -702.2673 0 0 +M V30 6 C 510.1194 -672.8359 0 0 +M V30 7 C 561.7506 -703.4009 0 0 +M V30 8 Cl 302.9404 -610.5721 0 0 +M V30 9 Br 406.8572 -611.7058 0 0 +M V30 10 O 510.7741 -612.8395 0 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 1 1 2 +M V30 2 1 2 3 +M V30 3 1 2 8 CFG=1 +M V30 4 1 3 4 +M V30 5 1 4 5 +M V30 6 1 4 9 CFG=3 +M V30 7 1 5 6 +M V30 8 1 6 7 +M V30 9 1 6 10 CFG=1 +M V30 END BOND +M V30 BEGIN COLLECTION +M V30 MDLV30/STEABS ATOMS=(1 2) +M V30 MDLV30/STEREL1 ATOMS=(1 4) +M V30 MDLV30/STERAC1 ATOMS=(1 6) +M V30 END COLLECTION +M V30 END CTAB +M END +> +InChI=1S/C7H14BrClO/c1-5(9)3-7(8)4-6(2)10/h5-7,10H,3-4H2,1-2H3/t5-,6+,7+/m1/s1 + +> +InChI=1S/C7H14BrClO/c1-5(9)3-7(8)4-6(2)10/h5-7,10H,3-4H2,1-2H3/t5-,6-,7-/m1/s1(5)2(7)3(6) + +> +6EN + +$$$$ + + ACD/LABS08242216132D + + 0 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 10 9 0 0 1 +M V30 BEGIN ATOM +M V30 1 C 250 -700 0 0 +M V30 2 C 302.2857 -670.5686 0 0 +M V30 3 C 353.9169 -701.1337 0 0 +M V30 4 C 406.2026 -671.7023 0 0 +M V30 5 C 457.8337 -702.2673 0 0 +M V30 6 C 510.1194 -672.8359 0 0 +M V30 7 C 561.7506 -703.4009 0 0 +M V30 8 Cl 302.9404 -610.5721 0 0 +M V30 9 Br 406.8572 -611.7058 0 0 +M V30 10 O 510.7741 -612.8395 0 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 1 1 2 +M V30 2 1 2 3 +M V30 3 1 2 8 CFG=1 +M V30 4 1 3 4 +M V30 5 1 4 5 +M V30 6 1 4 9 CFG=1 +M V30 7 1 5 6 +M V30 8 1 6 7 +M V30 9 1 6 10 CFG=1 +M V30 END BOND +M V30 BEGIN COLLECTION +M V30 MDLV30/STEABS ATOMS=(1 2) +M V30 MDLV30/STEREL1 ATOMS=(1 4) +M V30 MDLV30/STERAC1 ATOMS=(1 6) +M V30 END COLLECTION +M V30 END CTAB +M END +> +InChI=1S/C7H14BrClO/c1-5(9)3-7(8)4-6(2)10/h5-7,10H,3-4H2,1-2H3/t5-,6+,7-/m1/s1 + +> +InChI=1S/C7H14BrClO/c1-5(9)3-7(8)4-6(2)10/h5-7,10H,3-4H2,1-2H3/t5-,6-,7-/m1/s1(5)2(7)3(6) + +> +6EN + +$$$$ + + ACD/LABS08242216132D + + 0 0 0 0 0 0 0 0 0 0 0 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 11 10 0 0 0 +M V30 BEGIN ATOM +M V30 1 C 3.40734 -5.94189 0.0 0 +M V30 2 C 4.27876 -5.45137 0.0 0 CFG=2 +M V30 3 C 5.13929 -5.96079 0.0 0 +M V30 4 C 6.01071 -5.47026 0.0 0 CFG=2 +M V30 5 C 6.87123 -5.97968 0.0 0 +M V30 6 C 7.74266 -5.48916 0.0 0 CFG=2 +M V30 7 C 8.60318 -5.99857 0.0 0 +M V30 8 Cl 4.28968 -4.45143 0.0 0 +M V30 9 Br 6.02162 -4.47032 0.0 0 +M V30 10 O 7.75357 -4.48922 0.0 0 +M V30 11 C 2.54682 -5.43247 0.0 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 1 1 2 +M V30 2 1 1 11 +M V30 3 1 2 3 +M V30 4 1 2 8 CFG=1 +M V30 5 1 3 4 +M V30 6 1 4 5 +M V30 7 1 4 9 CFG=1 +M V30 8 1 5 6 +M V30 9 1 6 7 +M V30 10 1 6 10 CFG=1 +M V30 END BOND +M V30 BEGIN COLLECTION +M V30 MDLV30/STEABS ATOMS=(3 2 4 6) +M V30 END COLLECTION +M V30 END CTAB +M END +> +InChI=1S/C8H16BrClO/c1-3-8(10)5-7(9)4-6(2)11/h6-8,11H,3-5H2,1-2H3/t6-,7-,8+/m0/s1 + +> +InChI=1S/C8H16BrClO/c1-3-8(10)5-7(9)4-6(2)11/h6-8,11H,3-5H2,1-2H3/t6-,7-,8+/m0/s1 + +> +7EN + +$$$$ + + ACD/LABS08242216132D + + 0 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 11 10 0 0 1 +M V30 BEGIN ATOM +M V30 1 C 1012 -1609 0 0 +M V30 2 C 1064.2856 -1579.5686 0 0 +M V30 3 C 1115.9169 -1610.1337 0 0 +M V30 4 C 1168.2026 -1580.7023 0 0 +M V30 5 C 1219.8337 -1611.2673 0 0 +M V30 6 C 1272.1194 -1581.8359 0 0 +M V30 7 C 1323.7506 -1612.4009 0 0 +M V30 8 Cl 1064.9404 -1519.5721 0 0 +M V30 9 Br 1168.8572 -1520.7058 0 0 +M V30 10 O 1272.7742 -1521.8395 0 0 +M V30 11 C 960.3689 -1578.4348 0 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 1 1 2 +M V30 2 1 1 11 +M V30 3 1 2 3 +M V30 4 1 2 8 CFG=3 +M V30 5 1 3 4 +M V30 6 1 4 5 +M V30 7 1 4 9 CFG=3 +M V30 8 1 5 6 +M V30 9 1 6 7 +M V30 10 1 6 10 CFG=3 +M V30 END BOND +M V30 BEGIN COLLECTION +M V30 MDLV30/STEREL1 ATOMS=(2 2 4) +M V30 MDLV30/STERAC1 ATOMS=(1 6) +M V30 END COLLECTION +M V30 END CTAB +M END +> +InChI=1S/C8H16BrClO/c1-3-8(10)5-7(9)4-6(2)11/h6-8,11H,3-5H2,1-2H3/t6-,7-,8+/m1/s1 + +> +InChI=1S/C8H16BrClO/c1-3-8(10)5-7(9)4-6(2)11/h6-8,11H,3-5H2,1-2H3/t6-,7-,8+/m0/s2(7,8)3(6) + +> +8EN + +$$$$ + + ACD/LABS08242216132D + + 0 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 11 10 0 0 1 +M V30 BEGIN ATOM +M V30 1 C 1012 -1609 0 0 +M V30 2 C 1064.2856 -1579.5686 0 0 +M V30 3 C 1115.9169 -1610.1337 0 0 +M V30 4 C 1168.2026 -1580.7023 0 0 +M V30 5 C 1219.8337 -1611.2673 0 0 +M V30 6 C 1272.1194 -1581.8359 0 0 +M V30 7 C 1323.7506 -1612.4009 0 0 +M V30 8 Cl 1064.9404 -1519.5721 0 0 +M V30 9 Br 1168.8572 -1520.7058 0 0 +M V30 10 O 1272.7742 -1521.8395 0 0 +M V30 11 C 960.3689 -1578.4348 0 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 1 1 2 +M V30 2 1 1 11 +M V30 3 1 2 3 +M V30 4 1 2 8 CFG=1 +M V30 5 1 3 4 +M V30 6 1 4 5 +M V30 7 1 4 9 CFG=1 +M V30 8 1 5 6 +M V30 9 1 6 7 +M V30 10 1 6 10 CFG=3 +M V30 END BOND +M V30 BEGIN COLLECTION +M V30 MDLV30/STEREL1 ATOMS=(2 2 4) +M V30 MDLV30/STERAC1 ATOMS=(1 6) +M V30 END COLLECTION +M V30 END CTAB +M END +> +InChI=1S/C8H16BrClO/c1-3-8(10)5-7(9)4-6(2)11/h6-8,11H,3-5H2,1-2H3/t6-,7+,8-/m1/s1 + +> +InChI=1S/C8H16BrClO/c1-3-8(10)5-7(9)4-6(2)11/h6-8,11H,3-5H2,1-2H3/t6-,7-,8+/m0/s2(7,8)3(6) + +> +8EN + +$$$$ + + ACD/LABS08242216132D + + 0 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 11 10 0 0 1 +M V30 BEGIN ATOM +M V30 1 C 1012 -1609 0 0 +M V30 2 C 1064.2856 -1579.5686 0 0 +M V30 3 C 1115.9169 -1610.1337 0 0 +M V30 4 C 1168.2026 -1580.7023 0 0 +M V30 5 C 1219.8337 -1611.2673 0 0 +M V30 6 C 1272.1194 -1581.8359 0 0 +M V30 7 C 1323.7506 -1612.4009 0 0 +M V30 8 Cl 1064.9404 -1519.5721 0 0 +M V30 9 Br 1168.8572 -1520.7058 0 0 +M V30 10 O 1272.7742 -1521.8395 0 0 +M V30 11 C 960.3689 -1578.4348 0 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 1 1 2 +M V30 2 1 1 11 +M V30 3 1 2 3 +M V30 4 1 2 8 CFG=3 +M V30 5 1 3 4 +M V30 6 1 4 5 +M V30 7 1 4 9 CFG=3 +M V30 8 1 5 6 +M V30 9 1 6 7 +M V30 10 1 6 10 CFG=1 +M V30 END BOND +M V30 BEGIN COLLECTION +M V30 MDLV30/STEREL1 ATOMS=(2 2 4) +M V30 MDLV30/STERAC1 ATOMS=(1 6) +M V30 END COLLECTION +M V30 END CTAB +M END +> +InChI=1S/C8H16BrClO/c1-3-8(10)5-7(9)4-6(2)11/h6-8,11H,3-5H2,1-2H3/t6-,7+,8-/m0/s1 + +> +InChI=1S/C8H16BrClO/c1-3-8(10)5-7(9)4-6(2)11/h6-8,11H,3-5H2,1-2H3/t6-,7-,8+/m0/s2(7,8)3(6) + +> +8EN + +$$$$ + + ACD/LABS08242216132D + + 0 0 0 0 0 0 0 0 0 0999 V3000 +M V30 BEGIN CTAB +M V30 COUNTS 11 10 0 0 1 +M V30 BEGIN ATOM +M V30 1 C 1016 -1151 0 0 +M V30 2 C 1068.2856 -1121.5686 0 0 +M V30 3 C 1119.9169 -1152.1337 0 0 +M V30 4 C 1172.2026 -1122.7023 0 0 +M V30 5 C 1223.8337 -1153.2673 0 0 +M V30 6 C 1276.1194 -1123.8359 0 0 +M V30 7 C 1327.7506 -1154.4009 0 0 +M V30 8 Cl 1068.9404 -1061.5721 0 0 +M V30 9 Br 1172.8572 -1062.7058 0 0 +M V30 10 O 1276.7742 -1063.8395 0 0 +M V30 11 C 964.3689 -1120.4348 0 0 +M V30 END ATOM +M V30 BEGIN BOND +M V30 1 1 1 2 +M V30 2 1 1 11 +M V30 3 1 2 3 +M V30 4 1 2 8 CFG=1 +M V30 5 1 3 4 +M V30 6 1 4 5 +M V30 7 1 4 9 CFG=1 +M V30 8 1 5 6 +M V30 9 1 6 7 +M V30 10 1 6 10 CFG=1 +M V30 END BOND +M V30 BEGIN COLLECTION +M V30 MDLV30/STEREL1 ATOMS=(2 2 4) +M V30 MDLV30/STERAC1 ATOMS=(1 6) +M V30 END COLLECTION +M V30 END CTAB +M END +> +InChI=1S/C8H16BrClO/c1-3-8(10)5-7(9)4-6(2)11/h6-8,11H,3-5H2,1-2H3/t6-,7-,8+/m0/s1 + +> +InChI=1S/C8H16BrClO/c1-3-8(10)5-7(9)4-6(2)11/h6-8,11H,3-5H2,1-2H3/t6-,7-,8+/m0/s2(7,8)3(6) + +> +8EN + +$$$$ diff --git a/INCHI-1-TEST/tests/test_unit/test_enhancedStereo.cpp b/INCHI-1-TEST/tests/test_unit/test_enhancedStereo.cpp index 7127c739..247a8c97 100644 --- a/INCHI-1-TEST/tests/test_unit/test_enhancedStereo.cpp +++ b/INCHI-1-TEST/tests/test_unit/test_enhancedStereo.cpp @@ -1,4 +1,5 @@ #include +#include extern "C" { @@ -50,7 +51,6 @@ TEST(test_enhancedStereo, test_EnhancedStereochemistry_molfile_v2) FreeINCHI(poutput); } - TEST(test_enhancedStereo, test_EnhancedStereochemistry_1) { const char *molblock = @@ -730,3 +730,102 @@ TEST(test_enhancedStereo, test_EnhancedStereochemistry_2_different_mols_inter_en FreeINCHI(poutput); } + +TEST(test_enhancedStereo, test_EnhancedStereochemistry_test_file_1) +{ + + const char* inchi_filename = "../../../../../INCHI-1-TEST/tests/test_unit/fixtures/enh_stereo_test_file_1.sdf"; + std::ifstream file_inchi(inchi_filename); + + bool file_is_open = file_inchi.is_open(); + EXPECT_EQ(true, file_is_open); + + // Read the whole file into a string + std::stringstream buffer; + buffer << file_inchi.rdbuf(); + std::string file_content = buffer.str(); + + // Split on "$$$$" + std::vector molblocks; + size_t pos = 0; + size_t prev = 0; + const std::string delimiter = "$$$$"; + while ((pos = file_content.find(delimiter, prev)) != std::string::npos) { + std::string mol = file_content.substr(prev, pos - prev); + // Optionally trim whitespace + size_t first_non_ws = mol.find_first_not_of(" \t\r\n"); + if (first_non_ws != std::string::npos) { + mol = mol.substr(first_non_ws); + molblocks.push_back(mol); + } + prev = pos + delimiter.length(); + } + // Add the last block if any + std::string mol = file_content.substr(prev); + size_t first_non_ws = mol.find_first_not_of(" \t\r\n"); + if (first_non_ws != std::string::npos) { + mol = mol.substr(first_non_ws); + molblocks.push_back(mol); + } + + std::vector list_expected_inchis = { + "InChI=1B/C5H13NO/c1-3-5(6)4(2)7/h4-5,7H,3,6H2,1-2H3/t4-,5-/m1/s1", + "InChI=1B/C4H10O/c1-3-4(2)5/h4-5H,3H2,1-2H3/t4-/m0/s2(4)", + "InChI=1B/C5H13NO/c1-3-5(6)4(2)7/h4-5,7H,3,6H2,1-2H3/t4-,5-/m0/s2(4,5)", + "InChI=1B/C4H10O/c1-3-4(2)5/h4-5H,3H2,1-2H3/t4-/m1/s1", + "InChI=1B/C5H13NO/c1-3-5(6)4(2)7/h4-5,7H,3,6H2,1-2H3/t4-,5-/m0/s2(4)(5)", + "InChI=1B/C5H13NO/c1-3-5(6)4(2)7/h4-5,7H,3,6H2,1-2H3", + "InChI=1B/C5H13NO/c1-3-5(6)4(2)7/h4-5,7H,3,6H2,1-2H3/t4-,5-/m0/s3(4,5)", + "InChI=1B/C4H10O/c1-3-4(2)5/h4-5H,3H2,1-2H3/t4-/m0/s3(4)", + "InChI=1B/C4H10O/c1-3-4(2)5/h4-5H,3H2,1-2H3", + "InChI=1B/C5H13NO/c1-3-5(6)4(2)7/h4-5,7H,3,6H2,1-2H3/t4-,5-/m0/s3(4)(5)", + "InChI=1B/C6H12O3/c7-4-1-2-5(8)6(9)3-4/h4-9H,1-3H2/t4-,5+,6-/m0/s1(4,5)2(6)", + "InChI=1B/C6H12O3/c7-4-1-2-5(8)6(9)3-4/h4-9H,1-3H2/t4-,5+,6?/m0/s1", + "InChI=1B/C6H12O3/c7-4-1-2-5(8)6(9)3-4/h4-9H,1-3H2/t4-,5-,6+/m0/s1(4)2(5,6)", + "InChI=1B/C6H12O3/c7-4-1-2-5(8)6(9)3-4/h4-9H,1-3H2/t4-,5?,6?/m0/s1", + "InChI=1B/C6H12O3/c7-4-1-2-5(8)6(9)3-4/h4-9H,1-3H2/t4-,5-,6-/m0/s1(4)2(5,6)", + "InChI=1B/C7H17NO/c1-4-5(2)7(8)6(3)9/h5-7,9H,4,8H2,1-3H3/t5-,6-,7+/m1/s1(6)2(5,7)", + "InChI=1B/C7H17NO/c1-4-5(2)7(8)6(3)9/h5-7,9H,4,8H2,1-3H3/t5?,6-,7?/m1/s1", + "InChI=1B/C7H17NO/c1-4-5(2)7(8)6(3)9/h5-7,9H,4,8H2,1-3H3/t5-,6-,7-/m1/s1(6)2(5,7)", + "InChI=1B/C6H12O3/c7-4-1-2-5(8)6(9)3-4/h4-9H,1-3H2/t4-,5-,6-/m0/s1(4)2(6)(5)", + "InChI=1B/C5H13NO/c1-3-5(6)4(2)7/h4-5,7H,3,6H2,1-2H3/t4-,5-/m1/s1(4)3(5)", + "InChI=1B/C5H13NO/c1-3-5(6)4(2)7/h4-5,7H,3,6H2,1-2H3/t4-,5?/m0/s3(4)", + "InChI=1B/C6H12O3/c7-4-1-2-5(8)6(9)3-4/h4-9H,1-3H2/t4-,5-,6-/m0/s1(4)3(5,6)", + "InChI=1B/C6H12O3/c7-4-1-2-5(8)6(9)3-4/h4-9H,1-3H2/t4-,5-,6+/m0/s1(4)3(5,6)", + "InChI=1B/C6H12O3/c7-4-1-2-5(8)6(9)3-4/h4-9H,1-3H2/t4-,5?,6?/m0/s3(4)", + "InChI=1B/C7H17NO/c1-4-5(2)7(8)6(3)9/h5-7,9H,4,8H2,1-3H3/t5?,6-,7?/m0/s3(6)", + "InChI=1B/C7H17NO/c1-4-5(2)7(8)6(3)9/h5-7,9H,4,8H2,1-3H3/t5-,6-,7-/m1/s1(6)3(5,7)", + "InChI=1B/C7H17NO/c1-4-5(2)7(8)6(3)9/h5-7,9H,4,8H2,1-3H3/t5-,6-,7+/m1/s1(6)3(5,7)", + "InChI=1B/C6H12O3/c7-4-1-2-5(8)6(9)3-4/h4-9H,1-3H2/t4-,5-,6-/m0/s1(4)3(6)(5)", + "InChI=1B/C6H12O3/c7-4-1-2-5(8)6(9)3-4/h4-9H,1-3H2/t4-,5-,6-/m0/s1(4)2(5)3(6)", + "InChI=1B/C5H13NO/c1-3-5(6)4(2)7/h4-5,7H,3,6H2,1-2H3/t4-,5?/m1/s1", + "InChI=1B/C5H13NO/c1-3-5(6)4(2)7/h4-5,7H,3,6H2,1-2H3/t4-,5?/m0/s3(4)", + "InChI=1B/C5H13NO/c1-3-5(6)4(2)7/h4-5,7H,3,6H2,1-2H3/t4-,5?/m0/s2(4)", + "InChI=1B/C6H12O3/c7-4-1-2-5(8)6(9)3-4/h4-9H,1-3H2", + }; + + int nof_inchis = 33; + + EXPECT_EQ(nof_inchis, molblocks.size()); + EXPECT_EQ(nof_inchis, list_expected_inchis.size()); + + char options[] = "-EnhancedStereochemistry"; + for (int i = 0; i < nof_inchis; ++i) { + inchi_Output output; + inchi_Output* poutput = &output; + int ret = MakeINCHIFromMolfileText(molblocks[i].c_str(), options, poutput); + // Use your expected return code and InChI string + if (poutput->szLog && strlen(poutput->szLog) > 0) { + printf("ret: %d %s\n", i, list_expected_inchis[i].c_str()); + printf("%s\n", poutput->szLog); + EXPECT_EQ(ret, 1); + } else { + EXPECT_EQ(ret, 0); + } + + EXPECT_STREQ(poutput->szInChI, list_expected_inchis[i].c_str()); + FreeINCHI(poutput); + } + + file_inchi.close(); +} diff --git a/INCHI-1-TEST/tests/test_unit/test_ichiprt2.cpp b/INCHI-1-TEST/tests/test_unit/test_ichiprt2.cpp index e9d9d4a8..64e5c066 100644 --- a/INCHI-1-TEST/tests/test_unit/test_ichiprt2.cpp +++ b/INCHI-1-TEST/tests/test_unit/test_ichiprt2.cpp @@ -183,3 +183,204 @@ TEST(test_ichiprt2, Eql_INChI_Stereo_sp3_not_equal) EXPECT_EQ(Eql_INChI_Stereo(&s1, EQL_SP3, &s2, EQL_SP3, 0), 0); } +TEST(test_ichiprt2, test_compare_ints_basic) +{ + int a = 5, b = 10, c = 5; + + // a < b + EXPECT_LT(compare_ints(&a, &b), 0); + + // b > a + EXPECT_GT(compare_ints(&b, &a), 0); + + // a == c + EXPECT_EQ(compare_ints(&a, &c), 0); +} + +TEST(test_ichiprt2, MakeNumber_EnhStereo_decimal) +{ + INCHI_IOS_STRING strbuf = {0}; + inchi_strbuf_init(&strbuf, INCHI_STRBUF_INITIAL_SIZE, INCHI_STRBUF_SIZE_INCREMENT); + + int bOverflow = 0; + int nCtMode = 0; // decimal mode + + int n = MakeNumber_EnhStereo(42, ",", &strbuf, nCtMode, &bOverflow); + + EXPECT_EQ(bOverflow, 0); + EXPECT_EQ(std::string(strbuf.pStr), "42,"); + EXPECT_EQ(n, 3); + + inchi_strbuf_close(&strbuf); +} + +TEST(test_ichiprt2, MakeNumber_EnhStereo_abc) +{ + INCHI_IOS_STRING strbuf = {0}; + inchi_strbuf_init(&strbuf, INCHI_STRBUF_INITIAL_SIZE, INCHI_STRBUF_SIZE_INCREMENT); + int bOverflow = 0; + int nCtMode = CT_MODE_ABC_NUMBERS; // alphabetic mode + + int n = MakeNumber_EnhStereo(28, ";", &strbuf, nCtMode, &bOverflow); + + EXPECT_EQ(bOverflow, 0); + // 28 in base-27 is "aa", so expect "Aa;" + EXPECT_EQ(std::string(strbuf.pStr), "Aa;"); + EXPECT_EQ(n, 3); + + inchi_strbuf_close(&strbuf); +} + +TEST(test_ichiprt2, MakeEnhStereoString_basic) +{ + // Setup INChI_Aux with 3 atoms, canonical numbers 1, 2, 3 + INChI_Aux aux = {0}; + AT_NUMB orig_atoms[] = {1, 2, 3}; + aux.nNumberOfAtoms = 3; + aux.nOrigAtNosInCanonOrd = orig_atoms; + + // Enhanced stereo group: one group, 3 atoms (original numbers 1,2,3) + int group1[] = {0, 3, 1, 2, 3}; // [unused, n_atoms, orig_atom1, orig_atom2, orig_atom3] + int* enh_stereo[1] = {group1}; + + INCHI_IOS_STRING strbuf = {0}; + inchi_strbuf_init(&strbuf, INCHI_STRBUF_INITIAL_SIZE, INCHI_STRBUF_SIZE_INCREMENT); + int bOverflow = 0; + int nCtMode = 0; + + int len = MakeEnhStereoString(&aux, &strbuf, "1", enh_stereo, 1, nCtMode, &bOverflow); + + EXPECT_EQ(bOverflow, 0); + EXPECT_EQ(std::string(strbuf.pStr), "1(1,2,3)"); + EXPECT_EQ(len, 8); + + inchi_strbuf_close(&strbuf); +} + +TEST(test_ichiprt2, MakeEnhStereoString_multiple_groups) +{ + INChI_Aux aux = {0}; + AT_NUMB orig_atoms[] = {1, 2, 3, 4}; + aux.nNumberOfAtoms = 4; + aux.nOrigAtNosInCanonOrd = orig_atoms; + + int group1[] = {0, 2, 1, 2}; // [unused, n_atoms, orig_atom1, orig_atom2] + int group2[] = {0, 2, 3, 4}; // [unused, n_atoms, orig_atom3, orig_atom4] + int* enh_stereo[2] = {group1, group2}; + + INCHI_IOS_STRING strbuf = {0}; + inchi_strbuf_init(&strbuf, INCHI_STRBUF_INITIAL_SIZE, INCHI_STRBUF_SIZE_INCREMENT); + int bOverflow = 0; + int nCtMode = 0; + + int len = MakeEnhStereoString(&aux, &strbuf, "2", enh_stereo, 2, nCtMode, &bOverflow); + + EXPECT_EQ(bOverflow, 0); + EXPECT_EQ(std::string(strbuf.pStr), "2(1,2)(3,4)"); + EXPECT_EQ(len, 11); + + inchi_strbuf_close(&strbuf); +} + +TEST(test_ichiprt2, MakeEnhStereoString_empty_group) +{ + INChI_Aux aux = {0}; + AT_NUMB orig_atoms[] = {1, 2, 3}; + aux.nNumberOfAtoms = 3; + aux.nOrigAtNosInCanonOrd = orig_atoms; + + // Group with no valid atoms + int group1[] = {0, 2, 99, 100}; // [unused, n_atoms, orig_atom99, orig_atom100] + int* enh_stereo[1] = {group1}; + + INCHI_IOS_STRING strbuf = {0}; + inchi_strbuf_init(&strbuf, INCHI_STRBUF_INITIAL_SIZE, INCHI_STRBUF_SIZE_INCREMENT); + int bOverflow = 0; + int nCtMode = 0; + + int len = MakeEnhStereoString(&aux, &strbuf, "3", enh_stereo, 1, nCtMode, &bOverflow); + + EXPECT_EQ(bOverflow, 0); + EXPECT_EQ(std::string(strbuf.pStr), ""); + EXPECT_EQ(len, 0); + + inchi_strbuf_close(&strbuf); +} + +TEST(test_ichiprt2, MakeSlayerString_basic) +{ + // Setup minimal ORIG_ATOM_DATA with V3000 stereo lists + ORIG_ATOM_DATA *oad; + + oad = (ORIG_ATOM_DATA *)inchi_calloc(1, sizeof(ORIG_ATOM_DATA)); + + OAD_V3000 *v3000; + + v3000 = (OAD_V3000 *)inchi_calloc(1, sizeof(OAD_V3000)); + + // One absolute group: 2 atoms (original numbers 1,2) + int group_abs[] = {0, 2, 1, 2}; + v3000->n_steabs = 1; + v3000->lists_steabs = (int**)inchi_calloc(1, sizeof(int*)); + v3000->lists_steabs[0] = group_abs; + + // One relative group: 1 atom (original number 3) + int group_rel[] = {0, 1, 3}; + v3000->n_sterel = 1; + v3000->lists_sterel = (int**)inchi_calloc(1, sizeof(int*)); + v3000->lists_sterel[0] = group_rel; + + // No racemic groups + v3000->n_sterac = 0; + + oad->v3000 = v3000; + + // Setup INCHI_SORT and INChI_Aux + + INCHI_SORT *inchi_sort = (INCHI_SORT*)inchi_calloc(1, sizeof(INCHI_SORT)); + + int num_at = 8; + int num_iso_at = 1; + int alloc_mode = 0; + int bOrigatomflag = 0; + int found_num_bonds = 0; + int found_num_isotopic = 0; + + inp_ATOM *atoms = CreateInpAtom(num_at); + INChI *inchi = Alloc_INChI(atoms, num_at, &found_num_bonds, &found_num_isotopic, 0); + inchi->nNumberOfAtoms = num_at; + + INChI_Aux *pAux = Alloc_INChI_Aux(num_at, num_iso_at, alloc_mode, bOrigatomflag); + pAux->nNumberOfAtoms = 3; + pAux->nOrigAtNosInCanonOrd[0] = 1; + pAux->nOrigAtNosInCanonOrd[1] = 2; + pAux->nOrigAtNosInCanonOrd[2] = 3; + + inchi_sort->pINChI_Aux[0] = pAux; + inchi_sort->pINChI[0] = inchi; + + INCHI_IOS_STRING strbuf = {0}; + inchi_strbuf_init(&strbuf, INCHI_STRBUF_INITIAL_SIZE, INCHI_STRBUF_SIZE_INCREMENT); + int bOverflow = 0; + int nCtMode = 0; + + int len = MakeSlayerString(oad, inchi_sort, &strbuf, OUT_TN, 1, nCtMode, &bOverflow); + + EXPECT_EQ(bOverflow, 0); + // Should produce: 1(1,2)2(3) + EXPECT_EQ(std::string(strbuf.pStr), "1(1,2)2(3)"); + EXPECT_EQ(len, 10); + + inchi_free(oad->v3000->lists_steabs); + inchi_free(oad->v3000->lists_sterel); + inchi_free(oad->v3000); + inchi_free(oad); + + inchi_free(inchi_sort); + + inchi_strbuf_close(&strbuf); + + FreeInpAtom(&atoms); + Free_INChI_Aux(&pAux); + Free_INChI(&inchi); +} diff --git a/INCHI-1-TEST/tests/test_unit/test_strutil_enhancedStereo.cpp b/INCHI-1-TEST/tests/test_unit/test_strutil_enhancedStereo.cpp index 0ff3c8ee..328f50f1 100644 --- a/INCHI-1-TEST/tests/test_unit/test_strutil_enhancedStereo.cpp +++ b/INCHI-1-TEST/tests/test_unit/test_strutil_enhancedStereo.cpp @@ -4,10 +4,8 @@ extern "C" { #include "../../../INCHI-1-SRC/INCHI_BASE/src/strutil.h" #include "../../../INCHI-1-SRC/INCHI_BASE/src/ichi_io.h" -// #include "../../../INCHI-1-SRC/INCHI_BASE/src/inpdef.h" } - TEST(test_strutil_enhancedStereo, test_set_EnhancedStereo_t_m_layers_1) { @@ -104,7 +102,7 @@ TEST(test_strutil_enhancedStereo, test_set_EnhancedStereo_t_m_layers_1) pStrErr, bNoWarnings); - + EXPECT_EQ(ret, orig_inp_data->num_inp_atoms); int num_at = orig_inp_data->num_inp_atoms; @@ -162,3 +160,107 @@ TEST(test_strutil_enhancedStereo, test_set_EnhancedStereo_t_m_layers_1) inchi_ios_free_str(&input_stream); } + +TEST(test_strutil_enhancedStereo, test_get_canonical_atom_number_1) +{ + + INChI_Aux aux; + int n_atoms = 5; + AT_NUMB orig_atoms[] = {10, 20, 30, 40, 50}; + aux.nNumberOfAtoms = n_atoms; + aux.nOrigAtNosInCanonOrd = orig_atoms; + + // Should return 1-based canonical atom number for each original atom number + EXPECT_EQ(get_canonical_atom_number(&aux, 10), 1); + EXPECT_EQ(get_canonical_atom_number(&aux, 20), 2); + EXPECT_EQ(get_canonical_atom_number(&aux, 30), 3); + EXPECT_EQ(get_canonical_atom_number(&aux, 40), 4); + EXPECT_EQ(get_canonical_atom_number(&aux, 50), 5); + + // Should return -1 for atom numbers not present + EXPECT_EQ(get_canonical_atom_number(&aux, 99), -1); + EXPECT_EQ(get_canonical_atom_number(&aux, 0), -1); + +} + +TEST(test_strutil_enhancedStereo, test_get_parity_idx_from_canonical_atom_number) +{ + + // Example: canonical atom numbers for a molecule with 4 stereo centers + AT_NUMB nNumber[] = {3, 1, 4, 2}; + int nof_atoms = 4; + + // Should return the index where the canonical atom number is found + EXPECT_EQ(get_parity_idx_from_canonical_atom_number(3, nNumber, nof_atoms), 0); + EXPECT_EQ(get_parity_idx_from_canonical_atom_number(1, nNumber, nof_atoms), 1); + EXPECT_EQ(get_parity_idx_from_canonical_atom_number(4, nNumber, nof_atoms), 2); + EXPECT_EQ(get_parity_idx_from_canonical_atom_number(2, nNumber, nof_atoms), 3); + + // Should return -1 for a canonical atom number not present + EXPECT_EQ(get_parity_idx_from_canonical_atom_number(5, nNumber, nof_atoms), -1); + EXPECT_EQ(get_parity_idx_from_canonical_atom_number(0, nNumber, nof_atoms), -1); +} + +TEST(test_strutil_enhancedStereo, invert_parities_basic) +{ + // Setup INChI_Stereo + INChI_Stereo stereo; + AT_NUMB nNumber[] = {1, 2, 3}; + S_CHAR t_parity[] = {2, 1, 2}; // 2=+, 1=- + stereo.nNumber = nNumber; + stereo.t_parity = t_parity; + stereo.nNumberOfStereoCenters = 3; + stereo.nCompInv2Abs = 1; + + // Setup INChI + INChI inchi = {0}; + inchi.Stereo = &stereo; + + // Setup INChI_Aux + INChI_Aux aux = {0}; + AT_NUMB orig_atoms[] = {1, 2, 3}; + aux.nNumberOfAtoms = 3; + aux.nOrigAtNosInCanonOrd = orig_atoms; + + // Setup list_atoms: one group, 3 atoms (original numbers 1,2,3) + int group1[] = {0, 3, 1, 2, 3}; // [unused, n_atoms, orig_atom1, orig_atom2, orig_atom3] + int* lists[1] = {group1}; + + // Call invert_parities for absolute group + int ret = invert_parities(&inchi, &aux, lists, 1, 1); + + EXPECT_EQ(ret, 0); + + EXPECT_EQ(stereo.t_parity[0], 1); + EXPECT_EQ(stereo.t_parity[1], 2); + EXPECT_EQ(stereo.t_parity[2], 1); + + EXPECT_EQ(stereo.nCompInv2Abs, -1); + + t_parity[0] = 2; + t_parity[1] = 1; + t_parity[2] = 2; + + stereo.nCompInv2Abs = 1; + + ret = invert_parities(&inchi, &aux, lists, 1, 0); + + EXPECT_EQ(ret, 0); + + EXPECT_EQ(stereo.t_parity[0], 1); + EXPECT_EQ(stereo.t_parity[1], 2); + EXPECT_EQ(stereo.t_parity[2], 1); + + EXPECT_EQ(stereo.nCompInv2Abs, 1); + + ret = invert_parities(&inchi, &aux, lists, 1, 0); + + EXPECT_EQ(ret, 0); + + EXPECT_EQ(stereo.t_parity[0], 1); + EXPECT_EQ(stereo.t_parity[1], 2); + EXPECT_EQ(stereo.t_parity[2], 1); + + EXPECT_EQ(stereo.nCompInv2Abs, 1); +} + From 588644623810fc69c83e138c95545bc8c91d23e4 Mon Sep 17 00:00:00 2001 From: Christoph Mueller Date: Mon, 2 Feb 2026 14:08:25 +0000 Subject: [PATCH 25/44] added unit test fix for log message in MakeINCHIFromMolfileText --- INCHI-1-TEST/tests/test_unit/test_enhancedStereo.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/INCHI-1-TEST/tests/test_unit/test_enhancedStereo.cpp b/INCHI-1-TEST/tests/test_unit/test_enhancedStereo.cpp index 247a8c97..1862bb9d 100644 --- a/INCHI-1-TEST/tests/test_unit/test_enhancedStereo.cpp +++ b/INCHI-1-TEST/tests/test_unit/test_enhancedStereo.cpp @@ -816,9 +816,12 @@ TEST(test_enhancedStereo, test_EnhancedStereochemistry_test_file_1) int ret = MakeINCHIFromMolfileText(molblocks[i].c_str(), options, poutput); // Use your expected return code and InChI string if (poutput->szLog && strlen(poutput->szLog) > 0) { - printf("ret: %d %s\n", i, list_expected_inchis[i].c_str()); - printf("%s\n", poutput->szLog); - EXPECT_EQ(ret, 1); + if (strstr(poutput->szLog, "Warning")) { + printf("log: %s", poutput->szLog); + EXPECT_EQ(ret, 1); + } else { + EXPECT_EQ(ret, 0); + } } else { EXPECT_EQ(ret, 0); } From 556adde0a9fdbc78f3a213c179a4b8a1589ed5a9 Mon Sep 17 00:00:00 2001 From: Christoph Mueller Date: Mon, 2 Feb 2026 14:44:36 +0000 Subject: [PATCH 26/44] bug when switching from debug and release... --- .../tests/test_unit/test_enhancedStereo.cpp | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/INCHI-1-TEST/tests/test_unit/test_enhancedStereo.cpp b/INCHI-1-TEST/tests/test_unit/test_enhancedStereo.cpp index 1862bb9d..29ec554c 100644 --- a/INCHI-1-TEST/tests/test_unit/test_enhancedStereo.cpp +++ b/INCHI-1-TEST/tests/test_unit/test_enhancedStereo.cpp @@ -810,9 +810,13 @@ TEST(test_enhancedStereo, test_EnhancedStereochemistry_test_file_1) EXPECT_EQ(nof_inchis, list_expected_inchis.size()); char options[] = "-EnhancedStereochemistry"; + + for (int i = 0; i < nof_inchis; ++i) { + inchi_Output output; inchi_Output* poutput = &output; + int ret = MakeINCHIFromMolfileText(molblocks[i].c_str(), options, poutput); // Use your expected return code and InChI string if (poutput->szLog && strlen(poutput->szLog) > 0) { @@ -827,8 +831,23 @@ TEST(test_enhancedStereo, test_EnhancedStereochemistry_test_file_1) } EXPECT_STREQ(poutput->szInChI, list_expected_inchis[i].c_str()); + + if (poutput->szLog) { + free(poutput->szLog); + poutput->szLog = nullptr; + } + if (poutput->szMessage) { + free(poutput->szMessage); + poutput->szMessage = nullptr; + } + // poutput->szMessage = nullptr; + // poutput->szInChI = nullptr; + FreeINCHI(poutput); } + + + file_inchi.close(); } From 7d1fde3dcbef4047502928db4f6103d3b219d760 Mon Sep 17 00:00:00 2001 From: Christoph Mueller Date: Tue, 3 Feb 2026 10:17:37 +0000 Subject: [PATCH 27/44] Fixed unit test for testing batch test_file_1 (sdf) --- .../tests/test_unit/test_enhancedStereo.cpp | 47 +++++++++---------- 1 file changed, 21 insertions(+), 26 deletions(-) diff --git a/INCHI-1-TEST/tests/test_unit/test_enhancedStereo.cpp b/INCHI-1-TEST/tests/test_unit/test_enhancedStereo.cpp index 29ec554c..5709463a 100644 --- a/INCHI-1-TEST/tests/test_unit/test_enhancedStereo.cpp +++ b/INCHI-1-TEST/tests/test_unit/test_enhancedStereo.cpp @@ -4,6 +4,7 @@ extern "C" { #include "../../../INCHI-1-SRC/INCHI_BASE/src/inchi_api.h" +#include "../../../INCHI-1-SRC/INCHI_BASE/src/mode.h" } TEST(test_enhancedStereo, test_EnhancedStereochemistry_molfile_v2) @@ -734,16 +735,17 @@ TEST(test_enhancedStereo, test_EnhancedStereochemistry_2_different_mols_inter_en TEST(test_enhancedStereo, test_EnhancedStereochemistry_test_file_1) { - const char* inchi_filename = "../../../../../INCHI-1-TEST/tests/test_unit/fixtures/enh_stereo_test_file_1.sdf"; - std::ifstream file_inchi(inchi_filename); + // const char* inchi_filename = "../../../../../INCHI-1-TEST/tests/test_unit/fixtures/enh_stereo_test_file_1.sdf"; + const char* inchi_filename = "/workspaces/InChI/INCHI-1-TEST/tests/test_unit/fixtures/enh_stereo_test_file_1.sdf"; - bool file_is_open = file_inchi.is_open(); - EXPECT_EQ(true, file_is_open); + std::ifstream file_inchi(inchi_filename, std::ios::binary); + ASSERT_TRUE(file_inchi.is_open()); // Read the whole file into a string std::stringstream buffer; buffer << file_inchi.rdbuf(); std::string file_content = buffer.str(); + file_inchi.close(); // Split on "$$$$" std::vector molblocks; @@ -811,43 +813,36 @@ TEST(test_enhancedStereo, test_EnhancedStereochemistry_test_file_1) char options[] = "-EnhancedStereochemistry"; - for (int i = 0; i < nof_inchis; ++i) { inchi_Output output; inchi_Output* poutput = &output; + poutput->szLog = nullptr; + poutput->szMessage = nullptr; + poutput->szInChI = nullptr; + int ret = MakeINCHIFromMolfileText(molblocks[i].c_str(), options, poutput); - // Use your expected return code and InChI string - if (poutput->szLog && strlen(poutput->szLog) > 0) { - if (strstr(poutput->szLog, "Warning")) { - printf("log: %s", poutput->szLog); - EXPECT_EQ(ret, 1); - } else { - EXPECT_EQ(ret, 0); - } - } else { - EXPECT_EQ(ret, 0); - } + + EXPECT_LT(ret, 2); EXPECT_STREQ(poutput->szInChI, list_expected_inchis[i].c_str()); if (poutput->szLog) { - free(poutput->szLog); + inchi_free(poutput->szLog); poutput->szLog = nullptr; } if (poutput->szMessage) { - free(poutput->szMessage); + inchi_free(poutput->szMessage); poutput->szMessage = nullptr; } - // poutput->szMessage = nullptr; - // poutput->szInChI = nullptr; + if (poutput->szInChI) { + inchi_free(poutput->szInChI); + poutput->szInChI = nullptr; + } + // // poutput->szMessage = nullptr; + // // poutput->szInChI = nullptr; - FreeINCHI(poutput); + // FreeINCHI(poutput); } - - - - - file_inchi.close(); } From 6f163206b81cbd1c1d3ce05622cebe68bfb08e18 Mon Sep 17 00:00:00 2001 From: Christoph Mueller Date: Tue, 3 Feb 2026 10:32:59 +0000 Subject: [PATCH 28/44] changed directory --- INCHI-1-TEST/tests/test_unit/test_enhancedStereo.cpp | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/INCHI-1-TEST/tests/test_unit/test_enhancedStereo.cpp b/INCHI-1-TEST/tests/test_unit/test_enhancedStereo.cpp index 5709463a..a4f06fda 100644 --- a/INCHI-1-TEST/tests/test_unit/test_enhancedStereo.cpp +++ b/INCHI-1-TEST/tests/test_unit/test_enhancedStereo.cpp @@ -735,8 +735,8 @@ TEST(test_enhancedStereo, test_EnhancedStereochemistry_2_different_mols_inter_en TEST(test_enhancedStereo, test_EnhancedStereochemistry_test_file_1) { - // const char* inchi_filename = "../../../../../INCHI-1-TEST/tests/test_unit/fixtures/enh_stereo_test_file_1.sdf"; - const char* inchi_filename = "/workspaces/InChI/INCHI-1-TEST/tests/test_unit/fixtures/enh_stereo_test_file_1.sdf"; + const char* inchi_filename = "../../../../../INCHI-1-TEST/tests/test_unit/fixtures/enh_stereo_test_file_1.sdf"; + // const char* inchi_filename = "/workspaces/InChI/INCHI-1-TEST/tests/test_unit/fixtures/enh_stereo_test_file_1.sdf"; std::ifstream file_inchi(inchi_filename, std::ios::binary); ASSERT_TRUE(file_inchi.is_open()); @@ -840,9 +840,5 @@ TEST(test_enhancedStereo, test_EnhancedStereochemistry_test_file_1) inchi_free(poutput->szInChI); poutput->szInChI = nullptr; } - // // poutput->szMessage = nullptr; - // // poutput->szInChI = nullptr; - - // FreeINCHI(poutput); } } From 5cd2aa7891199b048a795477dbf71eb173b807f9 Mon Sep 17 00:00:00 2001 From: Christoph Mueller Date: Wed, 4 Feb 2026 13:48:30 +0000 Subject: [PATCH 29/44] added another unit test for testing enh stereo grps stretching over 2 fragments --- CMakeLists.txt | 1 - .../tests/test_unit/test_enhancedStereo.cpp | 71 ++++++++++++++++++- 2 files changed, 70 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e2b7e9f6..6c407d4c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,4 +22,3 @@ enable_testing() add_subdirectory(INCHI-1-SRC/INCHI_EXE/inchi-1/src) add_subdirectory(INCHI-1-SRC/INCHI_API/libinchi/src) add_subdirectory(INCHI-1-TEST/tests/test_unit) - diff --git a/INCHI-1-TEST/tests/test_unit/test_enhancedStereo.cpp b/INCHI-1-TEST/tests/test_unit/test_enhancedStereo.cpp index a4f06fda..e35b9cb7 100644 --- a/INCHI-1-TEST/tests/test_unit/test_enhancedStereo.cpp +++ b/INCHI-1-TEST/tests/test_unit/test_enhancedStereo.cpp @@ -516,7 +516,7 @@ TEST(test_enhancedStereo, test_EnhancedStereochemistry_4_mols) FreeINCHI(poutput); } -TEST(test_enhancedStereo, test_EnhancedStereochemistry_2_mols_inter_enhstereo_grps) +TEST(test_enhancedStereo, test_EnhancedStereochemistry_2_mols_inter_enhstereo_grps_1) { const char *molblock = "my_test_mol \n" @@ -623,6 +623,75 @@ TEST(test_enhancedStereo, test_EnhancedStereochemistry_2_mols_inter_enhstereo_gr FreeINCHI(poutput); } +TEST(test_enhancedStereo, test_EnhancedStereochemistry_2_mols_inter_enhstereo_grps_2) +{ + const char *molblock = + "test_mol \n" + " -INDIGO-02042609012D \n" + " \n" + " 0 0 0 0 0 0 0 0 0 0 0 V3000 \n" + "M V30 BEGIN CTAB \n" + "M V30 COUNTS 18 16 0 0 0 \n" + "M V30 BEGIN ATOM \n" + "M V30 1 C 3.06699 -3.575 0.0 0 \n" + "M V30 2 C 3.93301 -3.075 0.0 0 \n" + "M V30 3 C 4.79904 -3.575 0.0 0 CFG=1 \n" + "M V30 4 C 5.66506 -3.075 0.0 0 CFG=2 \n" + "M V30 5 C 6.53109 -3.575 0.0 0 \n" + "M V30 6 C 7.39711 -3.075 0.0 0 \n" + "M V30 7 C 4.79904 -4.575 0.0 0 \n" + "M V30 8 C 5.66506 -2.075 0.0 0 \n" + "M V30 9 C 3.06699 -8.475 0.0 0 \n" + "M V30 10 C 3.93301 -7.975 0.0 0 CFG=2 \n" + "M V30 11 C 4.79904 -8.475 0.0 0 CFG=2 \n" + "M V30 12 C 5.66506 -7.975 0.0 0 \n" + "M V30 13 C 6.53109 -8.475 0.0 0 \n" + "M V30 14 C 3.93301 -6.975 0.0 0 \n" + "M V30 15 C 4.79904 -9.475 0.0 0 \n" + "M V30 16 C 7.39711 -7.975 0.0 0 \n" + "M V30 17 C 8.26314 -8.475 0.0 0 \n" + "M V30 18 C 9.12916 -7.975 0.0 0 \n" + "M V30 END ATOM \n" + "M V30 BEGIN BOND \n" + "M V30 1 1 1 2 \n" + "M V30 2 1 2 3 \n" + "M V30 3 1 3 4 \n" + "M V30 4 1 4 5 \n" + "M V30 5 1 5 6 \n" + "M V30 6 1 3 7 CFG=1 \n" + "M V30 7 1 4 8 CFG=1 \n" + "M V30 8 1 9 10 \n" + "M V30 9 1 10 11 \n" + "M V30 10 1 11 12 \n" + "M V30 11 1 12 13 \n" + "M V30 12 1 10 14 CFG=1 \n" + "M V30 13 1 11 15 CFG=3 \n" + "M V30 14 1 13 16 \n" + "M V30 15 1 16 17 \n" + "M V30 16 1 17 18 \n" + "M V30 END BOND \n" + "M V30 BEGIN COLLECTION \n" + "M V30 MDLV30/STEABS ATOMS=(2 3 11) \n" + "M V30 MDLV30/STERAC1 ATOMS=(2 4 10) \n" + "M V30 END COLLECTION \n" + "M V30 END CTAB \n" + "M END \n"; + + + char options[] = "-EnhancedStereochemistry"; + inchi_Output output; + inchi_Output *poutput = &output; + const char expected_inchi[] = "InChI=1B/C10H22.C8H18/c1-5-6-7-8-10(4)9(2)3;1-5-7(3)8(4)6-2/h9-10H,5-8H2,1-4H3;7-8H,5-6H2,1-4H3/t10-;7-,8-/m00/s1(10)3(9);1(7)3(8)"; + + EXPECT_EQ(MakeINCHIFromMolfileText(molblock, options, poutput), 0); + EXPECT_STREQ(poutput->szInChI, expected_inchi); + + poutput->szLog = nullptr; + poutput->szMessage = nullptr; + + FreeINCHI(poutput); +} + TEST(test_enhancedStereo, test_EnhancedStereochemistry_2_different_mols_inter_enhstereo_grps) { const char *molblock = From 8b07bff7d634e42e292fef52fcc25cfbd4a5b45b Mon Sep 17 00:00:00 2001 From: Christoph Mueller Date: Thu, 5 Feb 2026 12:49:45 +0000 Subject: [PATCH 30/44] added atropisomer test for enh stereo --- .../tests/test_unit/test_enhancedStereo.cpp | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/INCHI-1-TEST/tests/test_unit/test_enhancedStereo.cpp b/INCHI-1-TEST/tests/test_unit/test_enhancedStereo.cpp index e35b9cb7..45ca436c 100644 --- a/INCHI-1-TEST/tests/test_unit/test_enhancedStereo.cpp +++ b/INCHI-1-TEST/tests/test_unit/test_enhancedStereo.cpp @@ -124,6 +124,72 @@ TEST(test_enhancedStereo, test_EnhancedStereochemistry_1) FreeINCHI(poutput); } +TEST(test_enhancedStereo, test_EnhancedStereochemistry_2) +{ + const char *molblock = + "test_mol_atropismer_1 \n" + " -INDIGO-02052611532D \n" + " \n" + " 0 0 0 0 0 0 0 0 0 0 0 V3000 \n" + "M V30 BEGIN CTAB \n" + "M V30 COUNTS 16 17 0 0 0 \n" + "M V30 BEGIN ATOM \n" + "M V30 1 C 6.11597 -6.27499 0.0 0 \n" + "M V30 2 C 5.24996 -5.775 0.0 0 \n" + "M V30 3 C 4.38395 -6.27499 0.0 0 \n" + "M V30 4 C 4.38395 -7.27499 0.0 0 \n" + "M V30 5 C 5.24996 -7.77498 0.0 0 \n" + "M V30 6 C 6.11597 -7.27499 0.0 0 \n" + "M V30 7 C 6.11605 -4.27501 0.0 0 \n" + "M V30 8 C 5.24996 -4.775 0.0 0 \n" + "M V30 9 C 4.38395 -4.27501 0.0 0 \n" + "M V30 10 C 4.38395 -3.27501 0.0 0 \n" + "M V30 11 C 6.11605 -3.27501 0.0 0 \n" + "M V30 12 C 5.25004 -2.77502 0.0 0 \n" + "M V30 13 Br 6.98205 -4.775 0.0 0 \n" + "M V30 14 Cl 3.51795 -4.775 0.0 0 \n" + "M V30 15 Cl 3.51795 -5.77492 0.0 0 \n" + "M V30 16 Br 6.98205 -5.775 0.0 0 \n" + "M V30 END ATOM \n" + "M V30 BEGIN BOND \n" + "M V30 1 1 1 6 \n" + "M V30 2 2 1 2 \n" + "M V30 3 1 2 3 CFG=1 \n" + "M V30 4 1 2 8 \n" + "M V30 5 2 3 4 \n" + "M V30 6 1 5 4 \n" + "M V30 7 2 5 6 \n" + "M V30 8 1 8 7 CFG=1 \n" + "M V30 9 2 7 11 \n" + "M V30 10 2 8 9 \n" + "M V30 11 1 9 10 \n" + "M V30 12 2 10 12 \n" + "M V30 13 1 12 11 \n" + "M V30 14 1 7 13 \n" + "M V30 15 1 9 14 \n" + "M V30 16 1 3 15 \n" + "M V30 17 1 1 16 \n" + "M V30 END BOND \n" + "M V30 BEGIN COLLECTION \n" + "M V30 MDLV30/STEABS ATOMS=(2 2 8) \n" + "M V30 END COLLECTION \n" + "M V30 END CTAB \n" + "M END \n"; + + char options[] = "-EnhancedStereochemistry"; + inchi_Output output; + inchi_Output *poutput = &output; + const char expected_inchi[] = "InChI=1B/C10H14BrCl7/c1-3(11)5(13)7(15)9(17)10(18)8(16)6(14)4(2)12/h3-10H,1-2H3/t3-,4-,5+,6-,7-,8-,9+,10-/m0/s1(3,5)2(6,8)(4)3(7,9)(10)"; + + EXPECT_EQ(MakeINCHIFromMolfileText(molblock, options, poutput), 0); + EXPECT_STREQ(poutput->szInChI, expected_inchi); + + poutput->szLog = nullptr; + poutput->szMessage = nullptr; + + FreeINCHI(poutput); +} + TEST(test_enhancedStereo, test_EnhancedStereochemistry_2_mols) { const char *molblock = From 04a1c5bc962b5193406d1bcf0c3a1cf4c94cb5ac Mon Sep 17 00:00:00 2001 From: Christoph Mueller Date: Thu, 5 Feb 2026 14:09:38 +0000 Subject: [PATCH 31/44] added flags for gcov: needs to be run with cmake -DCOVERAGE=ON -B CMake_build/full_build --- CMakeLists.txt | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6c407d4c..d8d7c572 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,6 +2,18 @@ cmake_minimum_required(VERSION 3.21 FATAL_ERROR) project(InChI C CXX) +option(COVERAGE "Enable coverage reporting" OFF) + +if(COVERAGE) + message(STATUS "Building with code coverage enabled") + if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") + add_compile_options(--coverage -O0 -g) + add_link_options(--coverage) + else() + message(WARNING "Coverage only supported with GCC or Clang") + endif() +endif() + set(CMAKE_C_STANDARD 99) set(CMAKE_CXX_STANDARD 11) set(CMAKE_POSITION_INDEPENDENT_CODE ON) @@ -22,3 +34,12 @@ enable_testing() add_subdirectory(INCHI-1-SRC/INCHI_EXE/inchi-1/src) add_subdirectory(INCHI-1-SRC/INCHI_API/libinchi/src) add_subdirectory(INCHI-1-TEST/tests/test_unit) + +if(COVERAGE) + add_custom_target(coverage + COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure + COMMAND find . -name '*.gcno' -exec gcov -b -c {} + + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + COMMENT "Running tests and generating .gcov coverage reports" + ) +endif() From cf6d7ce3c5de92fe4382f70875783192b760c629 Mon Sep 17 00:00:00 2001 From: Christoph Mueller Date: Thu, 5 Feb 2026 14:10:07 +0000 Subject: [PATCH 32/44] fixed unit test for atropisomer in enh stereo --- INCHI-1-TEST/tests/test_unit/test_enhancedStereo.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/INCHI-1-TEST/tests/test_unit/test_enhancedStereo.cpp b/INCHI-1-TEST/tests/test_unit/test_enhancedStereo.cpp index 45ca436c..08ccf305 100644 --- a/INCHI-1-TEST/tests/test_unit/test_enhancedStereo.cpp +++ b/INCHI-1-TEST/tests/test_unit/test_enhancedStereo.cpp @@ -179,7 +179,7 @@ TEST(test_enhancedStereo, test_EnhancedStereochemistry_2) char options[] = "-EnhancedStereochemistry"; inchi_Output output; inchi_Output *poutput = &output; - const char expected_inchi[] = "InChI=1B/C10H14BrCl7/c1-3(11)5(13)7(15)9(17)10(18)8(16)6(14)4(2)12/h3-10H,1-2H3/t3-,4-,5+,6-,7-,8-,9+,10-/m0/s1(3,5)2(6,8)(4)3(7,9)(10)"; + const char expected_inchi[] = "InChI=1B/C12H6Br2Cl2/c13-7-3-1-5-9(15)11(7)12-8(14)4-2-6-10(12)16/h1-6H"; EXPECT_EQ(MakeINCHIFromMolfileText(molblock, options, poutput), 0); EXPECT_STREQ(poutput->szInChI, expected_inchi); From 3fad65a8f2536b14f1d9cdf8f624d2b9b88bc499 Mon Sep 17 00:00:00 2001 From: Christoph Mueller Date: Fri, 6 Feb 2026 08:18:07 +0000 Subject: [PATCH 33/44] added 2 unit tests with faulty molblocks --- .../tests/test_unit/test_enhancedStereo.cpp | 104 +++++++++++++++++- 1 file changed, 103 insertions(+), 1 deletion(-) diff --git a/INCHI-1-TEST/tests/test_unit/test_enhancedStereo.cpp b/INCHI-1-TEST/tests/test_unit/test_enhancedStereo.cpp index 08ccf305..f01d8a39 100644 --- a/INCHI-1-TEST/tests/test_unit/test_enhancedStereo.cpp +++ b/INCHI-1-TEST/tests/test_unit/test_enhancedStereo.cpp @@ -124,7 +124,7 @@ TEST(test_enhancedStereo, test_EnhancedStereochemistry_1) FreeINCHI(poutput); } -TEST(test_enhancedStereo, test_EnhancedStereochemistry_2) +TEST(test_enhancedStereo, test_EnhancedStereochemistry_2_atropisomer) { const char *molblock = "test_mol_atropismer_1 \n" @@ -190,6 +190,108 @@ TEST(test_enhancedStereo, test_EnhancedStereochemistry_2) FreeINCHI(poutput); } +TEST(test_enhancedStereo, test_EnhancedStereochemistry_3_empty_collection_info) +{ + const char *molblock = + "broken_mol? \n" + " -INDIGO-02062608442D \n" + " \n" + " 0 0 0 0 0 0 0 0 0 0 0 V3000 \n" + "M V30 BEGIN CTAB \n" + "M V30 COUNTS 9 8 0 0 0 \n" + "M V30 BEGIN ATOM \n" + "M V30 1 C 4.64199 -4.9 0.0 0 \n" + "M V30 2 C 5.50801 -4.4 0.0 0 CFG=2 \n" + "M V30 3 C 6.37404 -4.9 0.0 0 CFG=2 \n" + "M V30 4 C 7.24006 -4.4 0.0 0 CFG=2 \n" + "M V30 5 C 8.10609 -4.9 0.0 0 \n" + "M V30 6 C 6.37404 -5.9 0.0 0 \n" + "M V30 7 C 5.50801 -3.4 0.0 0 \n" + "M V30 8 C 8.97211 -4.4 0.0 0 \n" + "M V30 9 C 7.24006 -3.4 0.0 0 \n" + "M V30 END ATOM \n" + "M V30 BEGIN BOND \n" + "M V30 1 1 1 2 \n" + "M V30 2 1 2 3 \n" + "M V30 3 1 3 4 \n" + "M V30 4 1 4 5 \n" + "M V30 5 1 3 6 CFG=3 \n" + "M V30 6 1 2 7 CFG=1 \n" + "M V30 7 1 5 8 \n" + "M V30 8 1 4 9 CFG=1 \n" + "M V30 END BOND \n" + "M V30 BEGIN COLLECTION \n" + "M V30 MDLV30/STERAC1 ATOMS=(1 ) \n" + "M V30 MDLV30/STEABS ATOMS=(2 ) \n" + "M V30 END COLLECTION \n" + "M V30 END CTAB \n" + "M END \n"; + + char options[] = "-EnhancedStereochemistry"; + inchi_Output output; + inchi_Output *poutput = &output; + const char expected_inchi[] = "InChI=1B/C9H20/c1-6-8(4)9(5)7(2)3/h7-9H,6H2,1-5H3/t8-,9+/m0"; + + EXPECT_EQ(MakeINCHIFromMolfileText(molblock, options, poutput), 0); + EXPECT_STREQ(poutput->szInChI, expected_inchi); + + poutput->szLog = nullptr; + poutput->szMessage = nullptr; + + FreeINCHI(poutput); +} + +TEST(test_enhancedStereo, test_EnhancedStereochemistry_3_wrong_atoms_in_collection) +{ + const char *molblock = + "broken_mol? \n" + " -INDIGO-02062608442D \n" + " \n" + " 0 0 0 0 0 0 0 0 0 0 0 V3000 \n" + "M V30 BEGIN CTAB \n" + "M V30 COUNTS 9 8 0 0 0 \n" + "M V30 BEGIN ATOM \n" + "M V30 1 C 4.64199 -4.9 0.0 0 \n" + "M V30 2 C 5.50801 -4.4 0.0 0 CFG=2 \n" + "M V30 3 C 6.37404 -4.9 0.0 0 CFG=2 \n" + "M V30 4 C 7.24006 -4.4 0.0 0 CFG=2 \n" + "M V30 5 C 8.10609 -4.9 0.0 0 \n" + "M V30 6 C 6.37404 -5.9 0.0 0 \n" + "M V30 7 C 5.50801 -3.4 0.0 0 \n" + "M V30 8 C 8.97211 -4.4 0.0 0 \n" + "M V30 9 C 7.24006 -3.4 0.0 0 \n" + "M V30 END ATOM \n" + "M V30 BEGIN BOND \n" + "M V30 1 1 1 2 \n" + "M V30 2 1 2 3 \n" + "M V30 3 1 3 4 \n" + "M V30 4 1 4 5 \n" + "M V30 5 1 3 6 CFG=3 \n" + "M V30 6 1 2 7 CFG=1 \n" + "M V30 7 1 5 8 \n" + "M V30 8 1 4 9 CFG=1 \n" + "M V30 END BOND \n" + "M V30 BEGIN COLLECTION \n" + "M V30 MDLV30/STERAC1 ATOMS=(1 -2) \n" + "M V30 MDLV30/STEABS ATOMS=(2 13 43) \n" + "M V30 END COLLECTION \n" + "M V30 END CTAB \n" + "M END \n"; + + char options[] = "-EnhancedStereochemistry"; + inchi_Output output; + inchi_Output *poutput = &output; + const char expected_inchi[] = "InChI=1B/C9H20/c1-6-8(4)9(5)7(2)3/h7-9H,6H2,1-5H3/t8-,9+/m0"; + + EXPECT_EQ(MakeINCHIFromMolfileText(molblock, options, poutput), 0); + EXPECT_STREQ(poutput->szInChI, expected_inchi); + + poutput->szLog = nullptr; + poutput->szMessage = nullptr; + + FreeINCHI(poutput); +} + TEST(test_enhancedStereo, test_EnhancedStereochemistry_2_mols) { const char *molblock = From b4446a062f4f6be03b3fde25edb91d47546f3577 Mon Sep 17 00:00:00 2001 From: Christoph Mueller Date: Fri, 6 Feb 2026 08:18:51 +0000 Subject: [PATCH 34/44] added code for handling empty data --- INCHI-1-SRC/INCHI_BASE/src/strutil.c | 28 ++++++++++++++++++++-------- INCHI-1-SRC/INCHI_BASE/src/strutil.h | 6 +++--- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/INCHI-1-SRC/INCHI_BASE/src/strutil.c b/INCHI-1-SRC/INCHI_BASE/src/strutil.c index 8be6ccdd..6d04ab20 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/strutil.c +++ b/INCHI-1-SRC/INCHI_BASE/src/strutil.c @@ -4353,11 +4353,6 @@ int invert_parities(const INChI *inchi, return 1; } - if (inchi->Stereo == NULL || - inchi->Stereo->t_parity == NULL) { - return 1; - } - if (nof_lists == 0) { return 1; @@ -4429,9 +4424,9 @@ int invert_parities(const INChI *inchi, * @return Retruns 1 if not V3000, otherwise 0 */ -int set_EnhancedStereo_t_m_layers( ORIG_ATOM_DATA *orig_inp_data, - INChI *inchi, - INChI_Aux *aux) +int set_EnhancedStereo_t_m_layers( const ORIG_ATOM_DATA *orig_inp_data, + const INChI *inchi, + const INChI_Aux *aux) { int ret = 0; @@ -4440,6 +4435,23 @@ int set_EnhancedStereo_t_m_layers( ORIG_ATOM_DATA *orig_inp_data, return 1; } + if (inchi == NULL || aux == NULL) + { + return 1; + } + + if (inchi->Stereo == NULL || + inchi->Stereo->t_parity == NULL || + inchi->Stereo->nNumber == NULL || + inchi->Stereo->nNumberOfStereoCenters <= 0) { + return 1; + } + + if (aux->nOrigAtNosInCanonOrd == NULL || + aux->nNumberOfAtoms <= 0) { + return 1; + } + int ret_abs = invert_parities(inchi, aux, orig_inp_data->v3000->lists_steabs, orig_inp_data->v3000->n_steabs, 1); int ret_rac = invert_parities(inchi, aux, orig_inp_data->v3000->lists_sterac, orig_inp_data->v3000->n_sterac, 0); int ret_rel = invert_parities(inchi, aux, orig_inp_data->v3000->lists_sterel, orig_inp_data->v3000->n_sterel, 0); diff --git a/INCHI-1-SRC/INCHI_BASE/src/strutil.h b/INCHI-1-SRC/INCHI_BASE/src/strutil.h index fc6ff812..df950476 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/strutil.h +++ b/INCHI-1-SRC/INCHI_BASE/src/strutil.h @@ -65,9 +65,9 @@ extern "C" * @param aux * @return int */ - int set_EnhancedStereo_t_m_layers(ORIG_ATOM_DATA *orig_inp_data, - INChI *inchi, - INChI_Aux *aux); + int set_EnhancedStereo_t_m_layers(const ORIG_ATOM_DATA *orig_inp_data, + const INChI *inchi, + const INChI_Aux *aux); /** * @brief Get the canonical atom number object From 0ecfdf332b25f0f5a6d2a74b34ba6b505a0aa9cd Mon Sep 17 00:00:00 2001 From: Christoph Mueller Date: Mon, 9 Feb 2026 07:23:47 +0000 Subject: [PATCH 35/44] changed comments --- INCHI-1-SRC/INCHI_BASE/src/ichiprt2.c | 2 +- INCHI-1-SRC/INCHI_BASE/src/strutil.c | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/INCHI-1-SRC/INCHI_BASE/src/ichiprt2.c b/INCHI-1-SRC/INCHI_BASE/src/ichiprt2.c index 4d6d620b..ca0752e4 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/ichiprt2.c +++ b/INCHI-1-SRC/INCHI_BASE/src/ichiprt2.c @@ -2300,7 +2300,7 @@ int MakeSlayerString( ORIG_ATOM_DATA *orig_inp_data, int DICT_SIZE = 100; char *dictionary[DICT_SIZE]; // array of string pointers - int counts[DICT_SIZE]; // parallel array of counts + int counts[DICT_SIZE]; // array of counts for (int i = 0; i < DICT_SIZE; i++) { dictionary[i] = NULL; diff --git a/INCHI-1-SRC/INCHI_BASE/src/strutil.c b/INCHI-1-SRC/INCHI_BASE/src/strutil.c index 6d04ab20..98dfbb5c 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/strutil.c +++ b/INCHI-1-SRC/INCHI_BASE/src/strutil.c @@ -4333,7 +4333,6 @@ int get_parity_idx_from_canonical_atom_number( int canon_atom_num, * @param is_absolute Flag indicating if processing absolute stereochemistry * @return Returns 0 on success, 1 if list_atoms is NULL */ - int invert_parities(const INChI *inchi, const INChI_Aux *aux, int **list_atoms, @@ -4423,7 +4422,6 @@ int invert_parities(const INChI *inchi, * @param aux Pointer to INChI auxiliary data * @return Retruns 1 if not V3000, otherwise 0 */ - int set_EnhancedStereo_t_m_layers( const ORIG_ATOM_DATA *orig_inp_data, const INChI *inchi, const INChI_Aux *aux) From d2fd2be51e67fff817c9b50db714106a26deae44 Mon Sep 17 00:00:00 2001 From: Christoph Mueller Date: Mon, 9 Feb 2026 09:50:02 +0000 Subject: [PATCH 36/44] clean up unit test code --- .../tests/test_unit/test_ichiprt2.cpp | 18 +--- .../tests/test_unit/test_ichiprt3.cpp | 8 -- INCHI-1-TEST/tests/test_unit/test_inpdef.cpp | 17 ---- INCHI-1-TEST/tests/test_unit/test_mol_fmt.cpp | 95 ++----------------- INCHI-1-TEST/tests/test_unit/test_strutil.cpp | 3 - .../test_unit/test_strutil_enhancedStereo.cpp | 4 - 6 files changed, 13 insertions(+), 132 deletions(-) diff --git a/INCHI-1-TEST/tests/test_unit/test_ichiprt2.cpp b/INCHI-1-TEST/tests/test_unit/test_ichiprt2.cpp index 64e5c066..fdb7f5ab 100644 --- a/INCHI-1-TEST/tests/test_unit/test_ichiprt2.cpp +++ b/INCHI-1-TEST/tests/test_unit/test_ichiprt2.cpp @@ -22,7 +22,7 @@ TEST(test_ichiprt2, MakeStereoString_outputs_expected_sp3_string) EXPECT_EQ(strbuf.nUsedLength, strlen(strbuf.pStr)); EXPECT_NE(std::string(strbuf.pStr).find("3-,4-,5+,6+,7-,8+,9+,10-"), std::string::npos); EXPECT_EQ(std::string(strbuf.pStr), "3-,4-,5+,6+,7-,8+,9+,10-"); - EXPECT_EQ(strbuf.pStr[0], '3'); // Should start with 3- + EXPECT_EQ(strbuf.pStr[0], '3'); EXPECT_EQ(bOverflow, 0); EXPECT_EQ(ret, 24); @@ -38,7 +38,7 @@ TEST(test_ichiprt2, MakeMult_mult_gt_1_appends_number_and_delim) int ret = MakeMult(5, "-", &strbuf, 0, &bOverflow); EXPECT_EQ(std::string(strbuf.pStr), "5-"); - EXPECT_EQ(ret, 2); // "3-".length() == 2, but MakeMult returns n (number of chars written) + EXPECT_EQ(ret, 2); EXPECT_EQ(bOverflow, 0); inchi_strbuf_close(&strbuf); @@ -53,7 +53,7 @@ TEST(test_ichiprt2, MakeMult_mult_2) int ret = MakeMult(10, "+", &strbuf, 0, &bOverflow); EXPECT_EQ(std::string(strbuf.pStr), "10+"); - EXPECT_EQ(ret, 3); // "3-".length() == 2, but MakeMult returns n (number of chars written) + EXPECT_EQ(ret, 3); EXPECT_EQ(bOverflow, 0); inchi_strbuf_close(&strbuf); @@ -309,14 +309,10 @@ TEST(test_ichiprt2, MakeEnhStereoString_empty_group) TEST(test_ichiprt2, MakeSlayerString_basic) { - // Setup minimal ORIG_ATOM_DATA with V3000 stereo lists - ORIG_ATOM_DATA *oad; - oad = (ORIG_ATOM_DATA *)inchi_calloc(1, sizeof(ORIG_ATOM_DATA)); + ORIG_ATOM_DATA *oad = (ORIG_ATOM_DATA *)inchi_calloc(1, sizeof(ORIG_ATOM_DATA)); - OAD_V3000 *v3000; - - v3000 = (OAD_V3000 *)inchi_calloc(1, sizeof(OAD_V3000)); + OAD_V3000 *v3000 = (OAD_V3000 *)inchi_calloc(1, sizeof(OAD_V3000)); // One absolute group: 2 atoms (original numbers 1,2) int group_abs[] = {0, 2, 1, 2}; @@ -336,7 +332,6 @@ TEST(test_ichiprt2, MakeSlayerString_basic) oad->v3000 = v3000; // Setup INCHI_SORT and INChI_Aux - INCHI_SORT *inchi_sort = (INCHI_SORT*)inchi_calloc(1, sizeof(INCHI_SORT)); int num_at = 8; @@ -367,7 +362,6 @@ TEST(test_ichiprt2, MakeSlayerString_basic) int len = MakeSlayerString(oad, inchi_sort, &strbuf, OUT_TN, 1, nCtMode, &bOverflow); EXPECT_EQ(bOverflow, 0); - // Should produce: 1(1,2)2(3) EXPECT_EQ(std::string(strbuf.pStr), "1(1,2)2(3)"); EXPECT_EQ(len, 10); @@ -375,11 +369,9 @@ TEST(test_ichiprt2, MakeSlayerString_basic) inchi_free(oad->v3000->lists_sterel); inchi_free(oad->v3000); inchi_free(oad); - inchi_free(inchi_sort); inchi_strbuf_close(&strbuf); - FreeInpAtom(&atoms); Free_INChI_Aux(&pAux); Free_INChI(&inchi); diff --git a/INCHI-1-TEST/tests/test_unit/test_ichiprt3.cpp b/INCHI-1-TEST/tests/test_unit/test_ichiprt3.cpp index c4b51c77..c5b8def9 100644 --- a/INCHI-1-TEST/tests/test_unit/test_ichiprt3.cpp +++ b/INCHI-1-TEST/tests/test_unit/test_ichiprt3.cpp @@ -57,7 +57,6 @@ TEST(test_ichiprt3, test_str_Sp2_outputs_expected_sp2_string_1) bUseMulipliers ); - // The expected output is "1-2Z,3-4E" EXPECT_EQ(strbuf.nUsedLength, strlen(strbuf.pStr)); EXPECT_EQ(std::string(strbuf.pStr), "4-3-"); EXPECT_EQ(strbuf.pStr[0], '4'); @@ -119,7 +118,6 @@ TEST(test_ichiprt3, test_str_Sp2_outputs_expected_sp2_string_2) bUseMulipliers ); - // The expected output is "1-2Z,3-4E" EXPECT_EQ(strbuf.nUsedLength, strlen(strbuf.pStr)); EXPECT_EQ(std::string(strbuf.pStr), "1-2-,3-4+"); EXPECT_EQ(strbuf.pStr[0], '1'); @@ -141,11 +139,6 @@ TEST(test_ichiprt3, test_str_Sp3_outputs_expected_sp3_string) static INChI_Stereo *stereo = Alloc_INChI_Stereo( num_at, 0 ); stereo->nNumberOfStereoCenters = num_at; - // INChI_Stereo stereo = {0}; - // stereo.nNumberOfStereoCenters = 8; - // stereo.t_parity = (S_CHAR*)inchi_calloc((long long)stereo.nNumberOfStereoCenters, sizeof(S_CHAR)); - // stereo.nNumber = (AT_NUMB*)inchi_calloc((long long)stereo.nNumberOfStereoCenters, sizeof(AT_NUMB)); - for (int i = 0; i < num_at; i++) { stereo->nNumber[i] = numbers[i]; stereo->t_parity[i] = parities[i]; @@ -165,7 +158,6 @@ TEST(test_ichiprt3, test_str_Sp3_outputs_expected_sp3_string) int bOverflow = 0; int num_components = 1; - //io->bRelRac = io->bRelativeStereo[io->iCurTautMode] || io->bRacemicStereo[io->iCurTautMode]; int bIsotopicRelativeStereo = 0; // INCHI_FLAG_REL_STEREO int bIsotopicRacemicStereo = 0; // INCHI_FLAG_RAC_STEREO diff --git a/INCHI-1-TEST/tests/test_unit/test_inpdef.cpp b/INCHI-1-TEST/tests/test_unit/test_inpdef.cpp index ac139b6f..64b42c81 100644 --- a/INCHI-1-TEST/tests/test_unit/test_inpdef.cpp +++ b/INCHI-1-TEST/tests/test_unit/test_inpdef.cpp @@ -71,23 +71,6 @@ TEST(test_inpdef, test_CreateOrigInpDataFromMolfile_v3000_sgroup) inchi_ios_init(&input_stream, INCHI_IOS_TYPE_STRING, nullptr); inchi_ios_print_nodisplay(&input_stream, molblock); - // int ret = CreateOrigInpDataFromMolfile( - // INCHI_IOSTREAM *inp_file, - // ORIG_ATOM_DATA *orig_at_data, - // int bMergeAllInputStructures, - // int bGetOrigCoord, - // int bDoNotAddH, - // int treat_polymers, - // int treat_NPZz, - // const char *pSdfLabel, - // char *pSdfValue, - // unsigned long *lSdfId, - // long *lMolfileNumber, - // INCHI_MODE *pInpAtomFlags, - // int *err, - // char *pStrErr, - // int bNoWarnings); - ORIG_ATOM_DATA orig_at_data = {}; int bMergeAllInputStructures = 0; int bGetOrigCoord = 0; diff --git a/INCHI-1-TEST/tests/test_unit/test_mol_fmt.cpp b/INCHI-1-TEST/tests/test_unit/test_mol_fmt.cpp index 23003716..cdf361e6 100644 --- a/INCHI-1-TEST/tests/test_unit/test_mol_fmt.cpp +++ b/INCHI-1-TEST/tests/test_unit/test_mol_fmt.cpp @@ -28,8 +28,6 @@ TEST(test_mol_fmt, test_MolfileStrnread) TEST(test_mol_fmt, test_MolfileReadField) { - // int MolfileReadField(void *data, int field_len, int data_type, char **line_ptr) - // SEG FAULT? // EXPECT_EQ(MolfileReadField(NULL, 0, MOL_FMT_STRING_DATA, NULL), 0); // no data, no line pointer @@ -110,11 +108,6 @@ TEST(test_mol_fmt, test_MolfileReadField) TEST(test_mol_fmt, test_MolfileV3000ReadField) { - - // int MolfileV3000ReadField(void *data, - // int data_type, - // char **line_ptr); - int n_coll = 0; char source_data[] = "2 ATOMS=(1 1)"; @@ -362,31 +355,19 @@ TEST(test_mol_fmt, test_MolfileHasNoChemStruc) TEST(test_mol_fmt, test_FreeMolfileData) { + MOL_FMT_DATA *mfdata = (MOL_FMT_DATA *)calloc(1, sizeof(MOL_FMT_DATA)); - MOL_FMT_DATA *mfdata; + MOL_FMT_HEADER_BLOCK *hdr = (MOL_FMT_HEADER_BLOCK *)calloc(1, sizeof(MOL_FMT_HEADER_BLOCK)); - MOL_FMT_HEADER_BLOCK *hdr; + MOL_FMT_CTAB *ctab = (MOL_FMT_CTAB *)calloc(1, sizeof(MOL_FMT_CTAB)); + MOL_FMT_ATOM *atoms = (MOL_FMT_ATOM *)calloc(1, sizeof(MOL_FMT_ATOM)); + MOL_FMT_BOND *bonds = (MOL_FMT_BOND *)calloc(1, sizeof(MOL_FMT_BOND)); - MOL_FMT_CTAB *ctab; - MOL_FMT_BOND *bonds; - MOL_FMT_ATOM *atoms; + MOL_FMT_SGROUPS *sgroups = (MOL_FMT_SGROUPS *)calloc(1, sizeof(MOL_FMT_SGROUPS)); - MOL_FMT_SGROUPS *sgroups; - MOL_COORD *coords; - MOL_FMT_v3000 *v3000; + MOL_COORD *coords = (MOL_COORD *)calloc(1, sizeof(MOL_COORD)); - mfdata = (MOL_FMT_DATA *)calloc(1, sizeof(MOL_FMT_DATA)); - - hdr = (MOL_FMT_HEADER_BLOCK *)calloc(1, sizeof(MOL_FMT_HEADER_BLOCK)); - - ctab = (MOL_FMT_CTAB *)calloc(1, sizeof(MOL_FMT_CTAB)); - atoms = (MOL_FMT_ATOM *)calloc(1, sizeof(MOL_FMT_ATOM)); - bonds = (MOL_FMT_BOND *)calloc(1, sizeof(MOL_FMT_BOND)); - - sgroups = (MOL_FMT_SGROUPS *)calloc(1, sizeof(MOL_FMT_SGROUPS)); - - coords = (MOL_COORD *)calloc(1, sizeof(MOL_COORD)); - v3000 = (MOL_FMT_v3000 *)calloc(1, sizeof(MOL_FMT_v3000)); + MOL_FMT_v3000 *v3000 = (MOL_FMT_v3000 *)calloc(1, sizeof(MOL_FMT_v3000)); mfdata->hdr = *hdr; @@ -472,21 +453,6 @@ TEST(test_mol_fmt, test_ReadMolfile_v2000) inchi_ios_init(&input_stream, INCHI_IOS_TYPE_STRING, nullptr); inchi_ios_print_nodisplay(&input_stream, molblock); - // MOL_FMT_DATA *ReadMolfile(INCHI_IOSTREAM *inp_file, - // MOL_FMT_HEADER_BLOCK *OnlyHeaderBlock, - // MOL_FMT_CTAB *OnlyCTab, - // int bGetOrigCoord, - // int treat_polymers, - // int treat_NPZz, - // char *pname, - // int lname, - // unsigned long *Id, - // const char *pSdfLabel, - // char *pSdfValue, - // int *err, - // char *pStrErr, - // int bNoWarnings) - INCHI_IOSTREAM *inp_file = &input_stream; MOL_FMT_HEADER_BLOCK *OnlyHeaderBlock = nullptr; MOL_FMT_CTAB *OnlyCTab = nullptr; @@ -599,21 +565,6 @@ TEST(test_mol_fmt, test_ReadMolfile_v3000) inchi_ios_init(&input_stream, INCHI_IOS_TYPE_STRING, nullptr); inchi_ios_print_nodisplay(&input_stream, molblock); - // MOL_FMT_DATA *ReadMolfile(INCHI_IOSTREAM *inp_file, - // MOL_FMT_HEADER_BLOCK *OnlyHeaderBlock, - // MOL_FMT_CTAB *OnlyCTab, - // int bGetOrigCoord, - // int treat_polymers, - // int treat_NPZz, - // char *pname, - // int lname, - // unsigned long *Id, - // const char *pSdfLabel, - // char *pSdfValue, - // int *err, - // char *pStrErr, - // int bNoWarnings) - INCHI_IOSTREAM *inp_file = &input_stream; MOL_FMT_HEADER_BLOCK *OnlyHeaderBlock = nullptr; MOL_FMT_CTAB *OnlyCTab = nullptr; @@ -696,21 +647,6 @@ TEST(test_mol_fmt, test_ReadMolfile_v3000_collection_1) inchi_ios_init(&input_stream, INCHI_IOS_TYPE_STRING, nullptr); inchi_ios_print_nodisplay(&input_stream, molblock); - // MOL_FMT_DATA *ReadMolfile(INCHI_IOSTREAM *inp_file, - // MOL_FMT_HEADER_BLOCK *OnlyHeaderBlock, - // MOL_FMT_CTAB *OnlyCTab, - // int bGetOrigCoord, - // int treat_polymers, - // int treat_NPZz, - // char *pname, - // int lname, - // unsigned long *Id, - // const char *pSdfLabel, - // char *pSdfValue, - // int *err, - // char *pStrErr, - // int bNoWarnings) - INCHI_IOSTREAM *inp_file = &input_stream; MOL_FMT_HEADER_BLOCK *OnlyHeaderBlock = nullptr; MOL_FMT_CTAB *OnlyCTab = nullptr; @@ -829,21 +765,6 @@ TEST(test_mol_fmt, test_ReadMolfile_v3000_collection_2) inchi_ios_init(&input_stream, INCHI_IOS_TYPE_STRING, nullptr); inchi_ios_print_nodisplay(&input_stream, molblock); - // MOL_FMT_DATA *ReadMolfile(INCHI_IOSTREAM *inp_file, - // MOL_FMT_HEADER_BLOCK *OnlyHeaderBlock, - // MOL_FMT_CTAB *OnlyCTab, - // int bGetOrigCoord, - // int treat_polymers, - // int treat_NPZz, - // char *pname, - // int lname, - // unsigned long *Id, - // const char *pSdfLabel, - // char *pSdfValue, - // int *err, - // char *pStrErr, - // int bNoWarnings) - INCHI_IOSTREAM *inp_file = &input_stream; MOL_FMT_HEADER_BLOCK *OnlyHeaderBlock = nullptr; MOL_FMT_CTAB *OnlyCTab = nullptr; diff --git a/INCHI-1-TEST/tests/test_unit/test_strutil.cpp b/INCHI-1-TEST/tests/test_unit/test_strutil.cpp index f09afffe..3654e48d 100644 --- a/INCHI-1-TEST/tests/test_unit/test_strutil.cpp +++ b/INCHI-1-TEST/tests/test_unit/test_strutil.cpp @@ -13,7 +13,6 @@ TEST(test_strutil, test_ExtractConnectedComponent) inp_ATOM *new_mol = CreateInpAtom(num_atoms); inp_ATOM *cmp_mol = CreateInpAtom(num_atoms); - // int ExtractConnectedComponent(inp_ATOM *at, int num_at, int component_number, inp_ATOM *component_at) EXPECT_EQ(ExtractConnectedComponent(nullptr, 0, test_component_number, nullptr), 0); for (int i = 0; i < num_atoms; i++) @@ -34,7 +33,6 @@ TEST(test_strutil, test_SetConnectedComponentNumber) int test_component_number = 23; inp_ATOM *new_mol = CreateInpAtom(num_atoms); - // int SetConnectedComponentNumber( inp_ATOM *at, int num_at, int component_number ) EXPECT_EQ(SetConnectedComponentNumber(new_mol, num_atoms, test_component_number), 0); for (int i = 0; i < num_atoms; i++) @@ -55,7 +53,6 @@ TEST(test_strutil, test_UnMarkRingSystemsInp) new_mol[i].nRingSystem = i + 1; } - // int UnMarkRingSystemsInp( inp_ATOM *at, int num_atoms ) EXPECT_EQ(UnMarkRingSystemsInp(new_mol, num_atoms), 0); for (int i = 0; i < num_atoms; i++) diff --git a/INCHI-1-TEST/tests/test_unit/test_strutil_enhancedStereo.cpp b/INCHI-1-TEST/tests/test_unit/test_strutil_enhancedStereo.cpp index 328f50f1..b4b3bb0c 100644 --- a/INCHI-1-TEST/tests/test_unit/test_strutil_enhancedStereo.cpp +++ b/INCHI-1-TEST/tests/test_unit/test_strutil_enhancedStereo.cpp @@ -134,8 +134,6 @@ TEST(test_strutil_enhancedStereo, test_set_EnhancedStereo_t_m_layers_1) pAux->nNumberOfAtoms = 9; - // pAux->nOrigAtNosInCanonOrd = (AT_NUMB*)inchi_calloc(num_at, sizeof(AT_NUMB)); - pAux->nOrigAtNosInCanonOrd[0] = 4; pAux->nOrigAtNosInCanonOrd[1] = 5; pAux->nOrigAtNosInCanonOrd[2] = 1; @@ -145,9 +143,7 @@ TEST(test_strutil_enhancedStereo, test_set_EnhancedStereo_t_m_layers_1) pAux->nOrigAtNosInCanonOrd[6] = 13; pAux->nOrigAtNosInCanonOrd[7] = 14; - std::cout << "Test started\n"; ret = set_EnhancedStereo_t_m_layers(orig_inp_data, inchi, pAux); - std::cout << "Test finished\n"; EXPECT_EQ(ret, 0); From 9d4f720825421a09384444530164dc851a942723 Mon Sep 17 00:00:00 2001 From: Christoph Mueller Date: Wed, 18 Feb 2026 12:07:29 +0000 Subject: [PATCH 37/44] removed sorting of stereo groups by index --- INCHI-1-SRC/INCHI_BASE/src/mol2atom.c | 10 ---------- INCHI-1-TEST/tests/test_unit/test_inpdef.cpp | 14 +++++++------- 2 files changed, 7 insertions(+), 17 deletions(-) diff --git a/INCHI-1-SRC/INCHI_BASE/src/mol2atom.c b/INCHI-1-SRC/INCHI_BASE/src/mol2atom.c index e9359f66..ed84245c 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/mol2atom.c +++ b/INCHI-1-SRC/INCHI_BASE/src/mol2atom.c @@ -1337,14 +1337,6 @@ void FreeExtOrigAtData(OAD_Polymer *pd, OAD_V3000 *v3k) } /****************************************************************************/ -static int cmp_int_list_first_elem(const void *a, const void *b) -{ - int * const *list_a = (int * const *)a; - int * const *list_b = (int * const *)b; - int val_a = (*list_a)[0]; - int val_b = (*list_b)[0]; - return (val_a > val_b) - (val_a < val_b); -} int SetExtOrigAtDataByMolfileExtInput(MOL_FMT_DATA *mfdata, OAD_Polymer **ppPolymer, @@ -1578,7 +1570,6 @@ int SetExtOrigAtDataByMolfileExtInput(MOL_FMT_DATA *mfdata, lst[k] = mol_lst[k]; } } - qsort(pv->lists_sterac, mpv->n_sterac, sizeof(int *), cmp_int_list_first_elem); } if (mpv->n_sterel && mpv->sterel) { @@ -1604,7 +1595,6 @@ int SetExtOrigAtDataByMolfileExtInput(MOL_FMT_DATA *mfdata, lst[k] = mol_lst[k]; } } - qsort(pv->lists_sterel, mpv->n_sterel, sizeof(int *), cmp_int_list_first_elem); } } diff --git a/INCHI-1-TEST/tests/test_unit/test_inpdef.cpp b/INCHI-1-TEST/tests/test_unit/test_inpdef.cpp index 64b42c81..d3a0228b 100644 --- a/INCHI-1-TEST/tests/test_unit/test_inpdef.cpp +++ b/INCHI-1-TEST/tests/test_unit/test_inpdef.cpp @@ -118,15 +118,15 @@ TEST(test_inpdef, test_CreateOrigInpDataFromMolfile_v3000_sgroup) // STERAC2 ATOMS=(1 1) EXPECT_EQ(orig_at_data.v3000->n_sterac, 2); - EXPECT_EQ(orig_at_data.v3000->lists_sterac[1][0], 2); // n from "STERACn" tag - EXPECT_EQ(orig_at_data.v3000->lists_sterac[1][1], 1); // number of members in collection - EXPECT_EQ(orig_at_data.v3000->lists_sterac[1][2], 1); // member atom numbers + EXPECT_EQ(orig_at_data.v3000->lists_sterac[0][0], 2); // n from "STERACn" tag + EXPECT_EQ(orig_at_data.v3000->lists_sterac[0][1], 1); // number of members in collection + EXPECT_EQ(orig_at_data.v3000->lists_sterac[0][2], 1); // member atom numbers // STERAC1 ATOMS=(2 2 3) - EXPECT_EQ(orig_at_data.v3000->lists_sterac[0][0], 1); // STERAC1 ATOMS=(2 2 3) - EXPECT_EQ(orig_at_data.v3000->lists_sterac[0][1], 2); // number of members in collection - EXPECT_EQ(orig_at_data.v3000->lists_sterac[0][2], 2); // member atom numbers - EXPECT_EQ(orig_at_data.v3000->lists_sterac[0][3], 3); // member atom numbers + EXPECT_EQ(orig_at_data.v3000->lists_sterac[1][0], 1); // STERAC1 ATOMS=(2 2 3) + EXPECT_EQ(orig_at_data.v3000->lists_sterac[1][1], 2); // number of members in collection + EXPECT_EQ(orig_at_data.v3000->lists_sterac[1][2], 2); // member atom numbers + EXPECT_EQ(orig_at_data.v3000->lists_sterac[1][3], 3); // member atom numbers EXPECT_EQ(orig_at_data.v3000->n_sterel, 2); From 5e193b4e38addb055327f597de11ceed4119ad92 Mon Sep 17 00:00:00 2001 From: "Jan C. Brammer" Date: Mon, 20 Oct 2025 06:50:00 +0000 Subject: [PATCH 38/44] Release v1.07.5 --- .github/workflows/release.yml | 11 ++++++++--- INCHI-1-DOC/CHANGELOG.md | 2 +- INCHI-1-DOC/CHANGELOG.pdf | Bin 132717 -> 165223 bytes 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3284b352..8c108d9b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -3,8 +3,6 @@ name: Release on: workflow_dispatch: push: - # TODO: Remove branches trigger. - branches: [ release-automation ] # Trigger this workflow by pushing a release tag. # Make sure that `HEAD` is pointing to the commit you want to release. # Then run `git tag -a v -m "Release version (--
)" && git push && git push --tags`. @@ -108,7 +106,7 @@ jobs: api-token: ${{ secrets.SIGNPATH_API_TOKEN }} organization-id: 656f8204-f8c5-4028-bd48-f832f5f89b31 project-slug: InChI - signing-policy-slug: test-signing + signing-policy-slug: release-signing github-artifact-id: ${{ steps.upload-unsigned-artifacts.outputs.artifact-id }} wait-for-completion: true output-artifact-directory: ${{ env.RELEASE_DIR }}-signed @@ -126,6 +124,13 @@ jobs: contents: write steps: + + - uses: actions/checkout@v4 + # git repository needs to be checked out (including tags, hence `fetch-depth: 0`), + # otherwise the `--verify-tag` flag to `gh release create` fails. + with: + fetch-depth: 0 + - name: Download artifacts uses: actions/download-artifact@v4 with: diff --git a/INCHI-1-DOC/CHANGELOG.md b/INCHI-1-DOC/CHANGELOG.md index b8ecda7d..788976df 100644 --- a/INCHI-1-DOC/CHANGELOG.md +++ b/INCHI-1-DOC/CHANGELOG.md @@ -1,6 +1,6 @@ # Change log -## v1.07.5 2025-??-?? +## v1.07.5 2026-02-17 ### Changed diff --git a/INCHI-1-DOC/CHANGELOG.pdf b/INCHI-1-DOC/CHANGELOG.pdf index 00376b748529df298fb34eadf3c6639c58a53d9e..543e0d2c567cb15e7010825b29c4587725a19d5e 100644 GIT binary patch literal 165223 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¬=N@Q2saM{GT-c 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 From fdb33c4380d78d6b5f147a99aaab5e6051822a18 Mon Sep 17 00:00:00 2001 From: Christoph Mueller Date: Wed, 18 Feb 2026 13:55:03 +0000 Subject: [PATCH 39/44] changed sorting of stereo groups to first canonical atom number in stereo group arrays --- INCHI-1-SRC/INCHI_BASE/src/ichiprt2.c | 60 +++++++++++++------ .../tests/test_unit/test_enhancedStereo.cpp | 16 ++--- 2 files changed, 50 insertions(+), 26 deletions(-) diff --git a/INCHI-1-SRC/INCHI_BASE/src/ichiprt2.c b/INCHI-1-SRC/INCHI_BASE/src/ichiprt2.c index 4ff3e05c..be6d9432 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/ichiprt2.c +++ b/INCHI-1-SRC/INCHI_BASE/src/ichiprt2.c @@ -2177,6 +2177,12 @@ int compare_ints(const void *a, const void *b) { return (arg1 > arg2) - (arg1 < arg2); } +int compare_third_value(const void *a, const void *b) { + const int *arr1 = *(const int **)a; + const int *arr2 = *(const int **)b; + return arr1[2] - arr2[2]; +} + /** * @brief Creates the enhanced stereochemistry string for the s - layer. * @@ -2214,44 +2220,62 @@ int MakeEnhStereoString( INChI_Aux *pAux, tot_len += MakeDelim( conf_stereo_string, strbuf, bOverflow ); + int **enh_stereo_canon = (int**)inchi_calloc(nof_stereo_groups, sizeof(int*)); for (int i = 0; i < nof_stereo_groups; i++) { - int count_found_atoms = 0; const int *atom_numbers = &enh_stereo[i][2]; + int count_found_atoms = 0; int nof_atoms = enh_stereo[i][1]; - int c_atom_numbers[nof_atoms]; + // int c_atom_numbers[nof_atoms]; + enh_stereo_canon[i] = (int*)inchi_calloc(nof_atoms + 2, sizeof(int)); + enh_stereo_canon[i][0] = nof_atoms; for (int j = 0; j < nof_atoms; j++) { int orig_atom_num = atom_numbers[j]; int canon_atom_num = get_canonical_atom_number(pAux, orig_atom_num); if (canon_atom_num != -1) { count_found_atoms++; + } else { + canon_atom_num = INT_MAX; } - c_atom_numbers[j] = canon_atom_num; - } - - if (count_found_atoms == 0) { - continue; + // c_atom_numbers[j] = canon_atom_num; + enh_stereo_canon[i][j + 2] = canon_atom_num; } + enh_stereo_canon[i][1] = count_found_atoms; if (nof_atoms > 1) { - qsort(c_atom_numbers, nof_atoms, sizeof(int), compare_ints); + // qsort(c_atom_numbers, nof_atoms, sizeof(int), compare_ints); + qsort(&enh_stereo_canon[i][2], nof_atoms, sizeof(int), compare_ints); } + } - tot_len += MakeDelim( "(", strbuf, bOverflow ); - for (int j = 0; j < nof_atoms; j++) { - if (c_atom_numbers[j] == -1) { - continue; - } - tot_len += MakeNumber_EnhStereo( c_atom_numbers[j], "", strbuf, nCtMode, bOverflow ); - count_added++; + qsort(enh_stereo_canon, nof_stereo_groups, sizeof(int*), compare_third_value); - if ((j + 1) < enh_stereo[i][1]) { - tot_len += MakeDelim( ",", strbuf, bOverflow ); + for (int i = 0; i < nof_stereo_groups; i++) { + int nof_atoms = enh_stereo_canon[i][0]; + int nof_found_atoms = enh_stereo_canon[i][1]; + + if (nof_found_atoms > 0) { + tot_len += MakeDelim( "(", strbuf, bOverflow ); + for (int j = 0; j < nof_found_atoms; j++) { //nof_atoms + // if (enh_stereo_canon[i][j + 2] == INT_MAX) { + // continue; + // } + tot_len += MakeNumber_EnhStereo( enh_stereo_canon[i][j + 2], "", strbuf, nCtMode, bOverflow ); + count_added++; + + if ((j + 1) < nof_found_atoms) { //enh_stereo[i][1] + tot_len += MakeDelim( ",", strbuf, bOverflow ); + } } + tot_len += MakeDelim( ")", strbuf, bOverflow ); } - tot_len += MakeDelim( ")", strbuf, bOverflow ); } + for (int i = 0; i < nof_stereo_groups; i++) { + inchi_free(enh_stereo_canon[i]); + } + inchi_free(enh_stereo_canon); + if (count_added == 0) { if (strbuf && strbuf->nUsedLength > 0) { strbuf->nUsedLength--; diff --git a/INCHI-1-TEST/tests/test_unit/test_enhancedStereo.cpp b/INCHI-1-TEST/tests/test_unit/test_enhancedStereo.cpp index f01d8a39..8abf3547 100644 --- a/INCHI-1-TEST/tests/test_unit/test_enhancedStereo.cpp +++ b/INCHI-1-TEST/tests/test_unit/test_enhancedStereo.cpp @@ -113,7 +113,7 @@ TEST(test_enhancedStereo, test_EnhancedStereochemistry_1) char options[] = "-EnhancedStereochemistry"; inchi_Output output; inchi_Output *poutput = &output; - const char expected_inchi[] = "InChI=1B/C10H14BrCl7/c1-3(11)5(13)7(15)9(17)10(18)8(16)6(14)4(2)12/h3-10H,1-2H3/t3-,4-,5+,6-,7-,8-,9+,10-/m0/s1(3,5)2(6,8)(4)3(7,9)(10)"; + const char expected_inchi[] = "InChI=1B/C10H14BrCl7/c1-3(11)5(13)7(15)9(17)10(18)8(16)6(14)4(2)12/h3-10H,1-2H3/t3-,4-,5+,6-,7-,8-,9+,10-/m0/s1(3,5)2(4)(6,8)3(7,9)(10)"; EXPECT_EQ(MakeINCHIFromMolfileText(molblock, options, poutput), 0); EXPECT_STREQ(poutput->szInChI, expected_inchi); @@ -392,7 +392,7 @@ TEST(test_enhancedStereo, test_EnhancedStereochemistry_2_mols) char options[] = "-EnhancedStereochemistry"; inchi_Output output; inchi_Output *poutput = &output; - const char expected_inchi[] = "InChI=1B/2C10H14BrCl7/c2*1-3(11)5(13)7(15)9(17)10(18)8(16)6(14)4(2)12/h2*3-10H,1-2H3/t2*3-,4-,5+,6-,7-,8-,9+,10-/m00/s2*1(3,5)2(6,8)(4)3(7,9)(10)"; + const char expected_inchi[] = "InChI=1B/2C10H14BrCl7/c2*1-3(11)5(13)7(15)9(17)10(18)8(16)6(14)4(2)12/h2*3-10H,1-2H3/t2*3-,4-,5+,6-,7-,8-,9+,10-/m00/s2*1(3,5)2(4)(6,8)3(7,9)(10)"; EXPECT_EQ(MakeINCHIFromMolfileText(molblock, options, poutput), 0); EXPECT_STREQ(poutput->szInChI, expected_inchi); @@ -520,7 +520,7 @@ TEST(test_enhancedStereo, test_EnhancedStereochemistry_3_mols) char options[] = "-EnhancedStereochemistry"; inchi_Output output; inchi_Output *poutput = &output; - const char expected_inchi[] = "InChI=1B/2C10H14BrCl7.C8H18/c2*1-3(11)5(13)7(15)9(17)10(18)8(16)6(14)4(2)12;1-6(2)8(5)7(3)4/h2*3-10H,1-2H3;6-8H,1-5H3/t2*3-,4-,5+,6-,7-,8-,9+,10-;/m00./s2*1(3,5)2(6,8)(4)3(7,9)(10);1(6)3(7,8)"; + const char expected_inchi[] = "InChI=1B/2C10H14BrCl7.C8H18/c2*1-3(11)5(13)7(15)9(17)10(18)8(16)6(14)4(2)12;1-6(2)8(5)7(3)4/h2*3-10H,1-2H3;6-8H,1-5H3/t2*3-,4-,5+,6-,7-,8-,9+,10-;/m00./s2*1(3,5)2(4)(6,8)3(7,9)(10);1(6)3(7,8)"; EXPECT_EQ(MakeINCHIFromMolfileText(molblock, options, poutput), 0); EXPECT_STREQ(poutput->szInChI, expected_inchi); @@ -673,7 +673,7 @@ TEST(test_enhancedStereo, test_EnhancedStereochemistry_4_mols) char options[] = "-EnhancedStereochemistry"; inchi_Output output; inchi_Output *poutput = &output; - const char expected_inchi[] = "InChI=1B/C12H26.2C10H14BrCl7.C9H20/c1-8(2)11(7)12(9(3)4)10(5)6;2*1-3(11)5(13)7(15)9(17)10(18)8(16)6(14)4(2)12;1-6-8(4)9(5)7(2)3/h8-12H,1-7H3;2*3-10H,1-2H3;7-9H,6H2,1-5H3/t11-;2*3-,4-,5+,6-,7-,8-,9+,10-;8-,9+/m1001/s1(8,9,10,11);2*1(3,5)2(6,8)(4)3(7,9)(10);1(7)3(8,9)"; + const char expected_inchi[] = "InChI=1B/C12H26.2C10H14BrCl7.C9H20/c1-8(2)11(7)12(9(3)4)10(5)6;2*1-3(11)5(13)7(15)9(17)10(18)8(16)6(14)4(2)12;1-6-8(4)9(5)7(2)3/h8-12H,1-7H3;2*3-10H,1-2H3;7-9H,6H2,1-5H3/t11-;2*3-,4-,5+,6-,7-,8-,9+,10-;8-,9+/m1001/s1(8,9,10,11);2*1(3,5)2(4)(6,8)3(7,9)(10);1(7)3(8,9)"; EXPECT_EQ(MakeINCHIFromMolfileText(molblock, options, poutput), 0); EXPECT_STREQ(poutput->szInChI, expected_inchi); @@ -780,7 +780,7 @@ TEST(test_enhancedStereo, test_EnhancedStereochemistry_2_mols_inter_enhstereo_gr char options[] = "-EnhancedStereochemistry"; inchi_Output output; inchi_Output *poutput = &output; - const char expected_inchi[] = "InChI=1B/2C10H14BrCl7/c2*1-3(11)5(13)7(15)9(17)10(18)8(16)6(14)4(2)12/h2*3-10H,1-2H3/t2*3-,4-,5+,6-,7-,8-,9+,10-/m00/s2*1(3,5)2(6,8)(4)3(7,9)(10)"; + const char expected_inchi[] = "InChI=1B/2C10H14BrCl7/c2*1-3(11)5(13)7(15)9(17)10(18)8(16)6(14)4(2)12/h2*3-10H,1-2H3/t2*3-,4-,5+,6-,7-,8-,9+,10-/m00/s2*1(3,5)2(4)(6,8)3(7,9)(10)"; EXPECT_EQ(MakeINCHIFromMolfileText(molblock, options, poutput), 0); EXPECT_STREQ(poutput->szInChI, expected_inchi); @@ -958,7 +958,7 @@ TEST(test_enhancedStereo, test_EnhancedStereochemistry_2_different_mols_inter_en char options[] = "-EnhancedStereochemistry"; inchi_Output output; inchi_Output *poutput = &output; - const char expected_inchi[] = "InChI=1B/C11H16BrCl7.C10H14BrCl7/c1-3-5(13)7(15)9(17)11(19)10(18)8(16)6(14)4(2)12;1-3(11)5(13)7(15)9(17)10(18)8(16)6(14)4(2)12/h4-11H,3H2,1-2H3;3-10H,1-2H3/t4-,5-,6+,7-,8-,9-,10+,11-;3-,4-,5+,6-,7-,8-,9+,10-/m00/s1(4,6)2(7,9)(5)3(8,10)(11);1(3,5)2(6,8)(4)3(7,9)(10)"; + const char expected_inchi[] = "InChI=1B/C11H16BrCl7.C10H14BrCl7/c1-3-5(13)7(15)9(17)11(19)10(18)8(16)6(14)4(2)12;1-3(11)5(13)7(15)9(17)10(18)8(16)6(14)4(2)12/h4-11H,3H2,1-2H3;3-10H,1-2H3/t4-,5-,6+,7-,8-,9-,10+,11-;3-,4-,5+,6-,7-,8-,9+,10-/m00/s1(4,6)2(5)(7,9)3(8,10)(11);1(3,5)2(4)(6,8)3(7,9)(10)"; EXPECT_EQ(MakeINCHIFromMolfileText(molblock, options, poutput), 0); EXPECT_STREQ(poutput->szInChI, expected_inchi); @@ -1026,7 +1026,7 @@ TEST(test_enhancedStereo, test_EnhancedStereochemistry_test_file_1) "InChI=1B/C7H17NO/c1-4-5(2)7(8)6(3)9/h5-7,9H,4,8H2,1-3H3/t5-,6-,7+/m1/s1(6)2(5,7)", "InChI=1B/C7H17NO/c1-4-5(2)7(8)6(3)9/h5-7,9H,4,8H2,1-3H3/t5?,6-,7?/m1/s1", "InChI=1B/C7H17NO/c1-4-5(2)7(8)6(3)9/h5-7,9H,4,8H2,1-3H3/t5-,6-,7-/m1/s1(6)2(5,7)", - "InChI=1B/C6H12O3/c7-4-1-2-5(8)6(9)3-4/h4-9H,1-3H2/t4-,5-,6-/m0/s1(4)2(6)(5)", + "InChI=1B/C6H12O3/c7-4-1-2-5(8)6(9)3-4/h4-9H,1-3H2/t4-,5-,6-/m0/s1(4)2(5)(6)", "InChI=1B/C5H13NO/c1-3-5(6)4(2)7/h4-5,7H,3,6H2,1-2H3/t4-,5-/m1/s1(4)3(5)", "InChI=1B/C5H13NO/c1-3-5(6)4(2)7/h4-5,7H,3,6H2,1-2H3/t4-,5?/m0/s3(4)", "InChI=1B/C6H12O3/c7-4-1-2-5(8)6(9)3-4/h4-9H,1-3H2/t4-,5-,6-/m0/s1(4)3(5,6)", @@ -1035,7 +1035,7 @@ TEST(test_enhancedStereo, test_EnhancedStereochemistry_test_file_1) "InChI=1B/C7H17NO/c1-4-5(2)7(8)6(3)9/h5-7,9H,4,8H2,1-3H3/t5?,6-,7?/m0/s3(6)", "InChI=1B/C7H17NO/c1-4-5(2)7(8)6(3)9/h5-7,9H,4,8H2,1-3H3/t5-,6-,7-/m1/s1(6)3(5,7)", "InChI=1B/C7H17NO/c1-4-5(2)7(8)6(3)9/h5-7,9H,4,8H2,1-3H3/t5-,6-,7+/m1/s1(6)3(5,7)", - "InChI=1B/C6H12O3/c7-4-1-2-5(8)6(9)3-4/h4-9H,1-3H2/t4-,5-,6-/m0/s1(4)3(6)(5)", + "InChI=1B/C6H12O3/c7-4-1-2-5(8)6(9)3-4/h4-9H,1-3H2/t4-,5-,6-/m0/s1(4)3(5)(6)", "InChI=1B/C6H12O3/c7-4-1-2-5(8)6(9)3-4/h4-9H,1-3H2/t4-,5-,6-/m0/s1(4)2(5)3(6)", "InChI=1B/C5H13NO/c1-3-5(6)4(2)7/h4-5,7H,3,6H2,1-2H3/t4-,5?/m1/s1", "InChI=1B/C5H13NO/c1-3-5(6)4(2)7/h4-5,7H,3,6H2,1-2H3/t4-,5?/m0/s3(4)", From 5c55589ce67d612cbd3d6d310752a59ac106a155 Mon Sep 17 00:00:00 2001 From: Christoph Mueller Date: Thu, 19 Feb 2026 09:17:32 +0000 Subject: [PATCH 40/44] Added comments and minor clean up --- INCHI-1-SRC/INCHI_BASE/src/ichiprt2.c | 40 +++++++++++++++++++++------ INCHI-1-SRC/INCHI_BASE/src/strutil.c | 13 ++++----- 2 files changed, 37 insertions(+), 16 deletions(-) diff --git a/INCHI-1-SRC/INCHI_BASE/src/ichiprt2.c b/INCHI-1-SRC/INCHI_BASE/src/ichiprt2.c index be6d9432..8f8a6a6c 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/ichiprt2.c +++ b/INCHI-1-SRC/INCHI_BASE/src/ichiprt2.c @@ -521,6 +521,14 @@ int MakeNumber_EnhStereo( int number, /****************************************************************************/ +/** + * @brief Adds the delimiter to the string buffer if it is not empty and there is no overflow. + * + * @param szTailingDelim Pointer to the trailing delimiter string + * @param buf Pointer to the output string buffer + * @param bOverflow Pointer to overflow flag + * @return Returns the number of characters added to the buffer, or 0 if the delimiter is empty or there is an overflow + */ int MakeDelim( const char *szTailingDelim, INCHI_IOS_STRING *buf, int *bOverflow ) @@ -2177,6 +2185,13 @@ int compare_ints(const void *a, const void *b) { return (arg1 > arg2) - (arg1 < arg2); } +/** + * @brief Compares the third value of two integer arrays for qsort. Used to sort enhanced stereochemistry groups based on the canonical atom number of the first atom in the group. + * + * @param a First integer array pointer. + * @param b Second integer array pointer. + * @return Returns negative, zero, or positive value based on comparison. + */ int compare_third_value(const void *a, const void *b) { const int *arr1 = *(const int **)a; const int *arr2 = *(const int **)b; @@ -2221,11 +2236,18 @@ int MakeEnhStereoString( INChI_Aux *pAux, tot_len += MakeDelim( conf_stereo_string, strbuf, bOverflow ); int **enh_stereo_canon = (int**)inchi_calloc(nof_stereo_groups, sizeof(int*)); + + // Converts the original atom numbers in the enhanced stereochemistry groups to canonical atom numbers + // and sorts the atoms within each group based on their canonical atom numbers. This ensures that the order of + // atoms in the string representation is consistent and does not depend on the order of atoms in the input data. + // enh_stereo_canon is a 2D array where each row corresponds to an enhanced stereochemistry group. The first element + // of each row (index 0) stores the number of atoms in the group, the second element (index 1) stores the count of found + // atoms, and the remaining elements (starting from index 2) store the canonical atom numbers of the atoms in the group. for (int i = 0; i < nof_stereo_groups; i++) { const int *atom_numbers = &enh_stereo[i][2]; int count_found_atoms = 0; int nof_atoms = enh_stereo[i][1]; - // int c_atom_numbers[nof_atoms]; + enh_stereo_canon[i] = (int*)inchi_calloc(nof_atoms + 2, sizeof(int)); enh_stereo_canon[i][0] = nof_atoms; for (int j = 0; j < nof_atoms; j++) { @@ -2237,33 +2259,34 @@ int MakeEnhStereoString( INChI_Aux *pAux, } else { canon_atom_num = INT_MAX; } - // c_atom_numbers[j] = canon_atom_num; + enh_stereo_canon[i][j + 2] = canon_atom_num; } enh_stereo_canon[i][1] = count_found_atoms; if (nof_atoms > 1) { - // qsort(c_atom_numbers, nof_atoms, sizeof(int), compare_ints); qsort(&enh_stereo_canon[i][2], nof_atoms, sizeof(int), compare_ints); } } + // Sorts the enhanced stereochemistry groups based on the canonical atom number of the first atom in the group. + // This ensures that the groups are always in a consistent order in the string representation, regardless of the + // order they were added to the input data. qsort(enh_stereo_canon, nof_stereo_groups, sizeof(int*), compare_third_value); + // Creates the string for the stereo group based on the canonical atom numbers of the atoms in the group. If no + // atoms were found for the group, it will not be added to the string. for (int i = 0; i < nof_stereo_groups; i++) { int nof_atoms = enh_stereo_canon[i][0]; int nof_found_atoms = enh_stereo_canon[i][1]; if (nof_found_atoms > 0) { tot_len += MakeDelim( "(", strbuf, bOverflow ); - for (int j = 0; j < nof_found_atoms; j++) { //nof_atoms - // if (enh_stereo_canon[i][j + 2] == INT_MAX) { - // continue; - // } + for (int j = 0; j < nof_found_atoms; j++) { tot_len += MakeNumber_EnhStereo( enh_stereo_canon[i][j + 2], "", strbuf, nCtMode, bOverflow ); count_added++; - if ((j + 1) < nof_found_atoms) { //enh_stereo[i][1] + if ((j + 1) < nof_found_atoms) { tot_len += MakeDelim( ",", strbuf, bOverflow ); } } @@ -2276,6 +2299,7 @@ int MakeEnhStereoString( INChI_Aux *pAux, } inchi_free(enh_stereo_canon); + // Removes the last value for the stereo group (1,2,3) if no atoms were added to the string if (count_added == 0) { if (strbuf && strbuf->nUsedLength > 0) { strbuf->nUsedLength--; diff --git a/INCHI-1-SRC/INCHI_BASE/src/strutil.c b/INCHI-1-SRC/INCHI_BASE/src/strutil.c index b25b7902..cfaf9e8e 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/strutil.c +++ b/INCHI-1-SRC/INCHI_BASE/src/strutil.c @@ -42,6 +42,7 @@ #include #include #include +#include #include "mode.h" @@ -4362,7 +4363,7 @@ int invert_parities(const INChI *inchi, for (int i = 0; i < nof_lists; i++) { int nof_atoms = list_atoms[i][1]; - AT_NUMB min_c_atom_num = (AT_NUMB)9999999; + AT_NUMB min_c_atom_num = (AT_NUMB)INT_MAX; for (int j = 0; j < nof_atoms; j++) { int orig_atom_num = list_atoms[i][2 + j]; AT_NUMB canon_atom_num = (AT_NUMB)get_canonical_atom_number(aux, orig_atom_num); @@ -4370,9 +4371,7 @@ int invert_parities(const INChI *inchi, min_c_atom_num = canon_atom_num; } } - if (min_c_atom_num == (AT_NUMB)9999999) { - // return -1; - // printf("ERROR %d: cannot find min canonical atom num\n", __LINE__); + if (min_c_atom_num == (AT_NUMB)INT_MAX) { continue; } int min_c_parity_idx = get_parity_idx_from_canonical_atom_number(min_c_atom_num, @@ -4382,11 +4381,9 @@ int invert_parities(const INChI *inchi, if (min_c_parity_idx == -1) { continue; } - int min_c_atom_parity = t_parity[min_c_parity_idx]; - if ((is_absolute == 1) && (min_c_atom_parity == 1)) { - // inchi->Stereo->nCompInv2Abs = 1; - } else if (min_c_atom_parity == 2) { + int min_c_atom_parity = t_parity[min_c_parity_idx]; + if (min_c_atom_parity == 2) { for (int j = 0; j < nof_atoms; j++) { int orig_atom_num = list_atoms[i][2 + j]; From d0e2798d999425e4e8b9e9a8f604ed912e3a1ae6 Mon Sep 17 00:00:00 2001 From: Christoph Mueller Date: Thu, 19 Feb 2026 10:08:30 +0000 Subject: [PATCH 41/44] Added unit test for same molecules with different AND group numbering --- .../tests/test_unit/test_enhancedStereo.cpp | 148 ++++++++++++++++++ 1 file changed, 148 insertions(+) diff --git a/INCHI-1-TEST/tests/test_unit/test_enhancedStereo.cpp b/INCHI-1-TEST/tests/test_unit/test_enhancedStereo.cpp index 8abf3547..7988d02b 100644 --- a/INCHI-1-TEST/tests/test_unit/test_enhancedStereo.cpp +++ b/INCHI-1-TEST/tests/test_unit/test_enhancedStereo.cpp @@ -969,6 +969,154 @@ TEST(test_enhancedStereo, test_EnhancedStereochemistry_2_different_mols_inter_en FreeINCHI(poutput); } +TEST(test_enhancedStereo, test_EnhancedStereochemistry_differing_AND_groups_of_same_molecule_1) +{ + const char *molblock1 = + "test mol \n" + " -INDIGO-02192610442D \n" + " \n" + " 0 0 0 0 0 0 0 0 0 0 0 V3000 \n" + "M V30 BEGIN CTAB \n" + "M V30 COUNTS 7 6 0 0 0 \n" + "M V30 BEGIN ATOM \n" + "M V30 1 O 1.54199 -5.725 0.0 0 \n" + "M V30 2 C 2.40801 -5.225 0.0 0 \n" + "M V30 3 C 3.27404 -5.725 0.0 0 CFG=1 \n" + "M V30 4 C 4.14006 -5.225 0.0 0 CFG=2 \n" + "M V30 5 C 5.00609 -5.725 0.0 0 \n" + "M V30 6 Br 4.14006 -4.225 0.0 0 \n" + "M V30 7 Cl 3.27404 -6.725 0.0 0 \n" + "M V30 END ATOM \n" + "M V30 BEGIN BOND \n" + "M V30 1 1 1 2 \n" + "M V30 2 1 2 3 \n" + "M V30 3 1 3 4 \n" + "M V30 4 1 4 5 \n" + "M V30 5 1 4 6 CFG=1 \n" + "M V30 6 1 3 7 CFG=1 \n" + "M V30 END BOND \n" + "M V30 BEGIN COLLECTION \n" + "M V30 MDLV30/STERAC1 ATOMS=(1 3) \n" + "M V30 MDLV30/STERAC2 ATOMS=(1 4) \n" + "M V30 END COLLECTION \n" + "M V30 END CTAB \n" + "M END \n"; + + const char *molblock2 = + "test mol \n" + " -INDIGO-02192610462D \n" + " \n" + " 0 0 0 0 0 0 0 0 0 0 0 V3000 \n" + "M V30 BEGIN CTAB \n" + "M V30 COUNTS 7 6 0 0 0 \n" + "M V30 BEGIN ATOM \n" + "M V30 1 O 1.54199 -5.725 0.0 0 \n" + "M V30 2 C 2.40801 -5.225 0.0 0 \n" + "M V30 3 C 3.27404 -5.725 0.0 0 CFG=1 \n" + "M V30 4 C 4.14006 -5.225 0.0 0 CFG=2 \n" + "M V30 5 C 5.00609 -5.725 0.0 0 \n" + "M V30 6 Br 4.14006 -4.225 0.0 0 \n" + "M V30 7 Cl 3.27404 -6.725 0.0 0 \n" + "M V30 END ATOM \n" + "M V30 BEGIN BOND \n" + "M V30 1 1 1 2 \n" + "M V30 2 1 2 3 \n" + "M V30 3 1 3 4 \n" + "M V30 4 1 4 5 \n" + "M V30 5 1 4 6 CFG=1 \n" + "M V30 6 1 3 7 CFG=1 \n" + "M V30 END BOND \n" + "M V30 BEGIN COLLECTION \n" + "M V30 MDLV30/STERAC2 ATOMS=(1 3) \n" + "M V30 MDLV30/STERAC1 ATOMS=(1 4) \n" + "M V30 END COLLECTION \n" + "M V30 END CTAB \n" + "M END \n"; + + char options[] = "-EnhancedStereochemistry"; + const char expected_inchi[] = "InChI=1B/C4H8BrClO/c1-3(5)4(6)2-7/h3-4,7H,2H2,1H3/t3-,4-/m0/s3(3)(4)"; + + inchi_Output output1; + inchi_Output *poutput1 = &output1; + EXPECT_EQ(MakeINCHIFromMolfileText(molblock1, options, poutput1), 0); + EXPECT_STREQ(poutput1->szInChI, expected_inchi); + + inchi_Output output2; + inchi_Output *poutput2 = &output2; + EXPECT_EQ(MakeINCHIFromMolfileText(molblock2, options, poutput2), 0); + EXPECT_STREQ(poutput2->szInChI, expected_inchi); + + poutput1->szLog = nullptr; + poutput1->szMessage = nullptr; + FreeINCHI(poutput1); + + poutput2->szLog = nullptr; + poutput2->szMessage = nullptr; + FreeINCHI(poutput2); +} + +TEST(test_enhancedStereo, test_EnhancedStereochemistry_differing_AND_groups_of_same_molecule_2) +{ + const char *molblock = + "test_mol \n" + " -INDIGO-02192611042D \n" + " \n" + " 0 0 0 0 0 0 0 0 0 0 0 V3000 \n" + "M V30 BEGIN CTAB \n" + "M V30 COUNTS 14 12 0 0 0 \n" + "M V30 BEGIN ATOM \n" + "M V30 1 O 4.21699 -6.575 0.0 0 \n" + "M V30 2 C 5.08301 -6.075 0.0 0 \n" + "M V30 3 C 5.94904 -6.575 0.0 0 CFG=1 \n" + "M V30 4 C 6.81506 -6.075 0.0 0 CFG=2 \n" + "M V30 5 C 7.68109 -6.575 0.0 0 \n" + "M V30 6 Br 6.81506 -5.075 0.0 0 \n" + "M V30 7 Cl 5.94904 -7.575 0.0 0 \n" + "M V30 8 O 11.2179 -6.4 0.0 0 \n" + "M V30 9 C 12.084 -5.9 0.0 0 \n" + "M V30 10 C 12.95 -6.4 0.0 0 CFG=1 \n" + "M V30 11 C 13.816 -5.9 0.0 0 CFG=2 \n" + "M V30 12 C 14.6821 -6.4 0.0 0 \n" + "M V30 13 Br 13.816 -4.9 0.0 0 \n" + "M V30 14 Cl 12.95 -7.4 0.0 0 \n" + "M V30 END ATOM \n" + "M V30 BEGIN BOND \n" + "M V30 1 1 1 2 \n" + "M V30 2 1 2 3 \n" + "M V30 3 1 3 4 \n" + "M V30 4 1 4 5 \n" + "M V30 5 1 4 6 CFG=1 \n" + "M V30 6 1 3 7 CFG=1 \n" + "M V30 7 1 8 9 \n" + "M V30 8 1 9 10 \n" + "M V30 9 1 10 11 \n" + "M V30 10 1 11 12 \n" + "M V30 11 1 11 13 CFG=1 \n" + "M V30 12 1 10 14 CFG=1 \n" + "M V30 END BOND \n" + "M V30 BEGIN COLLECTION \n" + "M V30 MDLV30/STERAC2 ATOMS=(1 3) \n" + "M V30 MDLV30/STERAC1 ATOMS=(1 4) \n" + "M V30 MDLV30/STERAC3 ATOMS=(1 10) \n" + "M V30 MDLV30/STERAC4 ATOMS=(1 11) \n" + "M V30 END COLLECTION \n" + "M V30 END CTAB \n" + "M END \n"; + + char options[] = "-EnhancedStereochemistry"; + inchi_Output output; + inchi_Output *poutput = &output; + const char expected_inchi[] = "InChI=1B/2C4H8BrClO/c2*1-3(5)4(6)2-7/h2*3-4,7H,2H2,1H3/t2*3-,4-/m00/s2*3(3)(4)"; + + EXPECT_EQ(MakeINCHIFromMolfileText(molblock, options, poutput), 0); + EXPECT_STREQ(poutput->szInChI, expected_inchi); + + poutput->szLog = nullptr; + poutput->szMessage = nullptr; + + FreeINCHI(poutput); +} + TEST(test_enhancedStereo, test_EnhancedStereochemistry_test_file_1) { From bad9cf696abce049f7343b20cffa9d5619b6786f Mon Sep 17 00:00:00 2001 From: Christoph Mueller Date: Thu, 19 Feb 2026 10:47:19 +0000 Subject: [PATCH 42/44] Fix for Release #68 test run (github action workflow) --- INCHI-1-SRC/INCHI_BASE/src/ichiprt2.c | 4 ++-- INCHI-1-TEST/tests/test_unit/test_enhancedStereo.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/INCHI-1-SRC/INCHI_BASE/src/ichiprt2.c b/INCHI-1-SRC/INCHI_BASE/src/ichiprt2.c index 8f8a6a6c..773a3a5a 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/ichiprt2.c +++ b/INCHI-1-SRC/INCHI_BASE/src/ichiprt2.c @@ -2271,7 +2271,7 @@ int MakeEnhStereoString( INChI_Aux *pAux, // Sorts the enhanced stereochemistry groups based on the canonical atom number of the first atom in the group. // This ensures that the groups are always in a consistent order in the string representation, regardless of the - // order they were added to the input data. + // order they were added to the input data (e.g. AND1, AND2, ... or OR1, OR2, ...). qsort(enh_stereo_canon, nof_stereo_groups, sizeof(int*), compare_third_value); // Creates the string for the stereo group based on the canonical atom numbers of the atoms in the group. If no @@ -2346,7 +2346,7 @@ int MakeSlayerString( ORIG_ATOM_DATA *orig_inp_data, // INChI *pINChI = NULL; INChI_Aux *pAux = NULL; - int DICT_SIZE = 100; + const int DICT_SIZE = 100; char *dictionary[DICT_SIZE]; // array of string pointers int counts[DICT_SIZE]; // array of counts diff --git a/INCHI-1-TEST/tests/test_unit/test_enhancedStereo.cpp b/INCHI-1-TEST/tests/test_unit/test_enhancedStereo.cpp index 7988d02b..0a771802 100644 --- a/INCHI-1-TEST/tests/test_unit/test_enhancedStereo.cpp +++ b/INCHI-1-TEST/tests/test_unit/test_enhancedStereo.cpp @@ -127,7 +127,7 @@ TEST(test_enhancedStereo, test_EnhancedStereochemistry_1) TEST(test_enhancedStereo, test_EnhancedStereochemistry_2_atropisomer) { const char *molblock = - "test_mol_atropismer_1 \n" + "test_mol_atropisomer_1 \n" " -INDIGO-02052611532D \n" " \n" " 0 0 0 0 0 0 0 0 0 0 0 V3000 \n" From f63ffb00d4d11c4d681eba1f673d8ad8c03b3f50 Mon Sep 17 00:00:00 2001 From: Christoph Mueller Date: Thu, 19 Feb 2026 10:59:17 +0000 Subject: [PATCH 43/44] Fix for Release [#68](https://github.com/IUPAC-InChI/InChI/issues/68) test run (github action workflow) --- INCHI-1-SRC/INCHI_BASE/src/ichiprt2.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/INCHI-1-SRC/INCHI_BASE/src/ichiprt2.c b/INCHI-1-SRC/INCHI_BASE/src/ichiprt2.c index 773a3a5a..e14143f0 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/ichiprt2.c +++ b/INCHI-1-SRC/INCHI_BASE/src/ichiprt2.c @@ -2347,8 +2347,8 @@ int MakeSlayerString( ORIG_ATOM_DATA *orig_inp_data, INChI_Aux *pAux = NULL; const int DICT_SIZE = 100; - char *dictionary[DICT_SIZE]; // array of string pointers - int counts[DICT_SIZE]; // array of counts + char **dictionary = (char**)inchi_calloc(DICT_SIZE, sizeof(char*)); + int *counts = (int*)inchi_calloc(DICT_SIZE, sizeof(int)); for (int i = 0; i < DICT_SIZE; i++) { dictionary[i] = NULL; @@ -2430,6 +2430,9 @@ int MakeSlayerString( ORIG_ATOM_DATA *orig_inp_data, } } + inchi_free(dictionary); + inchi_free(counts); + return tot_len; } From faa56a839fbab9211bd7519d15e3cd8de5719bab Mon Sep 17 00:00:00 2001 From: Christoph Mueller Date: Thu, 19 Feb 2026 11:11:28 +0000 Subject: [PATCH 44/44] Fix for Release [(https://github.com/IUPAC-InChI/InChI/issues/68)] test run (github action workflow) --- INCHI-1-SRC/INCHI_BASE/src/ichiprt2.c | 13 ++++++------- INCHI-1-SRC/INCHI_BASE/src/mode.h | 12 +++++++----- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/INCHI-1-SRC/INCHI_BASE/src/ichiprt2.c b/INCHI-1-SRC/INCHI_BASE/src/ichiprt2.c index e14143f0..cef4b29c 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/ichiprt2.c +++ b/INCHI-1-SRC/INCHI_BASE/src/ichiprt2.c @@ -2346,11 +2346,10 @@ int MakeSlayerString( ORIG_ATOM_DATA *orig_inp_data, // INChI *pINChI = NULL; INChI_Aux *pAux = NULL; - const int DICT_SIZE = 100; - char **dictionary = (char**)inchi_calloc(DICT_SIZE, sizeof(char*)); - int *counts = (int*)inchi_calloc(DICT_SIZE, sizeof(int)); + char **dictionary = (char**)inchi_calloc(ENH_STEREO_DICT_SIZE, sizeof(char*)); + int *counts = (int*)inchi_calloc(ENH_STEREO_DICT_SIZE, sizeof(int)); - for (int i = 0; i < DICT_SIZE; i++) { + for (int i = 0; i < ENH_STEREO_DICT_SIZE; i++) { dictionary[i] = NULL; counts[i] = 0; } @@ -2394,7 +2393,7 @@ int MakeSlayerString( ORIG_ATOM_DATA *orig_inp_data, bOverflow); int found = 0; - for (int i = 0; i < DICT_SIZE; i++) { + for (int i = 0; i < ENH_STEREO_DICT_SIZE; i++) { if (dictionary[i] && strcmp(tmpbuf.pStr, dictionary[i]) == 0) { counts[i]++; found = 1; @@ -2402,7 +2401,7 @@ int MakeSlayerString( ORIG_ATOM_DATA *orig_inp_data, } } if (!found) { - for (int i = 0; i < DICT_SIZE; i++) { + for (int i = 0; i < ENH_STEREO_DICT_SIZE; i++) { if (dictionary[i] == NULL) { dictionary[i] = strdup(tmpbuf.pStr); counts[i] = 1; @@ -2415,7 +2414,7 @@ int MakeSlayerString( ORIG_ATOM_DATA *orig_inp_data, // String deduplication based on dictionary and counts int count = 0; - for (int i = 0; i < DICT_SIZE; i++) { + for (int i = 0; i < ENH_STEREO_DICT_SIZE; i++) { if (dictionary[i]) { if (count > 0) { tot_len += MakeDelim( ";", strbuf, bOverflow ); diff --git a/INCHI-1-SRC/INCHI_BASE/src/mode.h b/INCHI-1-SRC/INCHI_BASE/src/mode.h index a83bbbc6..638b1c8c 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/mode.h +++ b/INCHI-1-SRC/INCHI_BASE/src/mode.h @@ -376,7 +376,7 @@ extern "C" { /* InChI and sometimes resulted in memory corruption on */ /* accessing array of len == NUM_H_ISOTOPES that is 3. */ -#define FIX_OLEAN_SPIRO_CHIRALITY_DETECTION_BUG 1 +#define FIX_OLEAN_SPIRO_CHIRALITY_DETECTION_BUG 1 /* (2018-05-03) thanks, DT; fix for 'olean spiro chirality' */ @@ -438,7 +438,7 @@ extern "C" { #define FIX_NP_MINUS_BUG 1 /* 2010-03-11 DT Fix for bug reported by Timo Boehme */ - /* in normalization procedure for some structures containing N2(+) fragment */ + /* in normalization procedure for some structures containing N2(+) fragment */ /* which may result in producing different InChI strings for the same */ /* molecule, depending on original order of the atomic numbers */ @@ -478,7 +478,7 @@ extern "C" { #define TAUT_PT_06_00 1 /* tautomerism rule PT_06_00 */ #define TAUT_PT_39_00 1 /* tautomerism rule PT_39_00 */ #define TAUT_PT_13_00 1 /* tautomerism rule PT_13_00 */ -#define TAUT_PT_18_00 1 /* tautomerism rule PT_18_00 */ +#define TAUT_PT_18_00 1 /* tautomerism rule PT_18_00 */ #ifdef BUILD_WITH_ENG_OPTIONS #define UNDERIVATIZE 1 /* split to possible underivatized fragments */ @@ -634,6 +634,8 @@ extern "C" { /* 0=> allow other definitions (below) to be active */ #define ONE_BAD_SB_NEIGHBOR 1 /* 1 => allow 1 "bad" bond type neighbor to a stereobond atom. 2004-06-02 */ +#define ENH_STEREO_DICT_SIZE 100 /* 100 => size of the dictionary for enhanced stereo */ + /* more stereo settings */ #define BREAK_ONE_MORE_SC_TIE 1 /* break one more tie when comparing possible stereocenter neighbors */ #define BREAK_ALSO_NEIGH_TIE 0 /* post 1.12Beta 2004-08-20: if fixed neighbor has equ neighbors, fix the one with smaller canon. rank */ @@ -1245,8 +1247,8 @@ do {\ #define POLYMERS_MODERN 1 /* v. 1.06+ way to treat polymers with Zz */ #define POLYMERS_LEGACY 2 /* v. 1.05 mode, no explicit Zz (internally they are here) */ #define POLYMERS_LEGACY_PLUS 3 /* v. 1.05 mode with an addition of that in all - frame-shiftable-bistar-CRUs their backbone bonds - are reordered in descending seniority order. + frame-shiftable-bistar-CRUs their backbone bonds + are reordered in descending seniority order. Used as hidden 1st pass in 1.06 treatment */