From 47974ddeff4f87c8d886c936e510dad2d3c41c9e Mon Sep 17 00:00:00 2001 From: Christoph Mueller Date: Thu, 19 Feb 2026 12:17:22 +0000 Subject: [PATCH 01/11] Created first unit test --- .../tests/test_unit/test_atropisomers.cpp | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 INCHI-1-TEST/tests/test_unit/test_atropisomers.cpp diff --git a/INCHI-1-TEST/tests/test_unit/test_atropisomers.cpp b/INCHI-1-TEST/tests/test_unit/test_atropisomers.cpp new file mode 100644 index 00000000..98148b11 --- /dev/null +++ b/INCHI-1-TEST/tests/test_unit/test_atropisomers.cpp @@ -0,0 +1,53 @@ +#include +#include + +extern "C" +{ +#include "../../../INCHI-1-SRC/INCHI_BASE/src/inchi_api.h" +#include "../../../INCHI-1-SRC/INCHI_BASE/src/mode.h" +} + +TEST(test_atropisomers, test_Atropisomers_molfile_v2) +{ + 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=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); + + FreeINCHI(poutput); +} From 26e466bf80553d848b10f41b592e3e2f858e91c1 Mon Sep 17 00:00:00 2001 From: Christoph Mueller Date: Thu, 19 Feb 2026 12:43:23 +0000 Subject: [PATCH 02/11] Added parameter "-Atropisomers" to enable calling atropisomer-specific routines --- INCHI-1-SRC/INCHI_BASE/src/ichidrp.h | 3 ++- INCHI-1-SRC/INCHI_BASE/src/ichiparm.c | 18 ++++++++++++++---- INCHI-1-SRC/INCHI_BASE/src/ichiprt1.c | 9 +++++++++ INCHI-1-SRC/INCHI_BASE/src/strutil.c | 15 +++++++++++++++ INCHI-1-SRC/INCHI_BASE/src/strutil.h | 12 ++++++++++++ .../tests/test_unit/test_atropisomers.cpp | 4 ++-- 6 files changed, 54 insertions(+), 7 deletions(-) diff --git a/INCHI-1-SRC/INCHI_BASE/src/ichidrp.h b/INCHI-1-SRC/INCHI_BASE/src/ichidrp.h index 7e0e7673..b34df3bd 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/ichidrp.h +++ b/INCHI-1-SRC/INCHI_BASE/src/ichidrp.h @@ -187,7 +187,8 @@ typedef struct tagInputParms { int bNoWarnings; /* v. 1.06+ suppress warning messages */ int bHideInChI; /* v. 1.06+ Do not print InChI itself */ - int bEnhancedStereo; + int bEnhancedStereo; /* v. 1.0?+ enable enhanced stereochemistry */ + int Atropisomers; /* v. 1.0?+ enable atropisomeric stereochemistry */ /* */ INCHI_MODE bTautFlags; diff --git a/INCHI-1-SRC/INCHI_BASE/src/ichiparm.c b/INCHI-1-SRC/INCHI_BASE/src/ichiparm.c index dcf4d9b8..d83652e3 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/ichiparm.c +++ b/INCHI-1-SRC/INCHI_BASE/src/ichiparm.c @@ -123,7 +123,8 @@ int set_common_options_by_parg(const char* pArg, int* pbNoWarnings, int* pbMergeHash, int* pbHideInChI, - int* pbEnhancedStereochemistry); + int* pbEnhancedStereochemistry, + int* pbAtropisomers); /**************************************************************************** @@ -167,7 +168,8 @@ int set_common_options_by_parg(const char* pArg, int* pbNoWarnings, int* pbMergeHash, int* pbHideInChI, - int* pbEnhancedStereochemistry + int* pbEnhancedStereochemistry, + int* pbAtropisomers ) { int got = 0; @@ -273,6 +275,11 @@ int set_common_options_by_parg(const char* pArg, *pbEnhancedStereochemistry = 1; got = 1; } + else if (!inchi_stricmp(pArg, "Atropisomers")) + { + *pbAtropisomers = 1; + got = 1; + } #ifndef USE_STDINCHI_API /* These options DO TURN OFF Std flag */ @@ -651,6 +658,7 @@ int ReadCommandLineParms(int argc, int bUnchargedAcidTaut = (CHARGED_SALTS_ONLY == 0); int bMergeSaltTGroups = (DISCONNECT_SALTS == 1); int bEnhancedStereochemistry = 0; + int bAtropisomers = 0; #if ( MIN_SB_RING_SIZE > 0 ) int nMinDbRinSize = MIN_SB_RING_SIZE, mdbr = 0; #endif @@ -804,7 +812,7 @@ int ReadCommandLineParms(int argc, &bFoldPolymerSRU, &bFrameShiftScheme, &bStereoAtZz, &bNPZz, &bNoWarnings, &bMergeHash, &bHideInChI, - &bEnhancedStereochemistry); + &bEnhancedStereochemistry, &bAtropisomers); if (got) { ; @@ -1253,7 +1261,7 @@ int ReadCommandLineParms(int argc, &bFoldPolymerSRU, &bFrameShiftScheme, &bStereoAtZz, &bNPZz, &bNoWarnings, &bMergeHash, &bHideInChI, - &bEnhancedStereochemistry); + &bEnhancedStereochemistry, &bAtropisomers); if (got) { @@ -2132,6 +2140,8 @@ int ReadCommandLineParms(int argc, ip->bEnhancedStereo = bEnhancedStereochemistry; + ip->Atropisomers = bAtropisomers; + return 0; } diff --git a/INCHI-1-SRC/INCHI_BASE/src/ichiprt1.c b/INCHI-1-SRC/INCHI_BASE/src/ichiprt1.c index 0ee22261..0b82fd73 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/ichiprt1.c +++ b/INCHI-1-SRC/INCHI_BASE/src/ichiprt1.c @@ -1642,6 +1642,11 @@ int OutputINChI1( CANON_GLOBALS *pCG, { set_EnhancedStereo_t_m_layers(orig_inp_data, pINChI, pINChI_Aux); } + + if (ip->Atropisomers) + { + set_Atropisomer_t_m_layers(orig_inp_data, pINChI, pINChI_Aux); + } } } if (bCompExists) @@ -1732,6 +1737,10 @@ int OutputINChI1( CANON_GLOBALS *pCG, { is_beta = 1; } + else if (ip->Atropisomers) + { + is_beta = 1; + } OutputINCHI_VersionAndKind( out_file, strbuf, bINChIOutputOptions, is_beta, pLF, pTAB ); } diff --git a/INCHI-1-SRC/INCHI_BASE/src/strutil.c b/INCHI-1-SRC/INCHI_BASE/src/strutil.c index cfaf9e8e..88018bc3 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/strutil.c +++ b/INCHI-1-SRC/INCHI_BASE/src/strutil.c @@ -4460,6 +4460,21 @@ int set_EnhancedStereo_t_m_layers( const ORIG_ATOM_DATA *orig_inp_data, return ret; } +/** + * @brief Set t- and m-layers object for atropisomer stereochemistry + * + * @param orig_inp_data Pointer to original input atom data + * @param inchi Pointer to INChI structure + * @param aux Pointer to INChI auxiliary data + * @return int + */ +int set_Atropisomer_t_m_layers( const ORIG_ATOM_DATA *orig_inp_data, + const INChI *inchi, + const INChI_Aux *aux) +{ + +} + /**************************************************************************** Set the (disconnected) component numbers in ORIG_ATOM_DATA 'at[*].component' NB: components are (stable) sorted by number of heavy atoms diff --git a/INCHI-1-SRC/INCHI_BASE/src/strutil.h b/INCHI-1-SRC/INCHI_BASE/src/strutil.h index df950476..2c75024d 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/strutil.h +++ b/INCHI-1-SRC/INCHI_BASE/src/strutil.h @@ -69,6 +69,18 @@ extern "C" const INChI *inchi, const INChI_Aux *aux); + /** + * @brief Set t- and m-layers object for atropisomer stereochemistry + * + * @param orig_inp_data Pointer to original input atom data + * @param inchi Pointer to INChI structure + * @param aux Pointer to INChI auxiliary data + * @return int + */ + int set_Atropisomer_t_m_layers(const ORIG_ATOM_DATA *orig_inp_data, + const INChI *inchi, + const INChI_Aux *aux); + /** * @brief Get the canonical atom number object * diff --git a/INCHI-1-TEST/tests/test_unit/test_atropisomers.cpp b/INCHI-1-TEST/tests/test_unit/test_atropisomers.cpp index 98148b11..7d12ead9 100644 --- a/INCHI-1-TEST/tests/test_unit/test_atropisomers.cpp +++ b/INCHI-1-TEST/tests/test_unit/test_atropisomers.cpp @@ -7,7 +7,7 @@ extern "C" #include "../../../INCHI-1-SRC/INCHI_BASE/src/mode.h" } -TEST(test_atropisomers, test_Atropisomers_molfile_v2) +TEST(test_atropisomers, test_dummy) { const char *molblock = "test_mol_2 \n" @@ -41,7 +41,7 @@ TEST(test_atropisomers, test_Atropisomers_molfile_v2) " 12 13 1 0 0 0 \n" "M END \n"; - char options[] = "-EnhancedStereochemistry"; + char options[] = "-Atropisomers"; inchi_Output output; inchi_Output *poutput = &output; 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"; From 3e6bf4dc9652d18ad2d5ea0eea128f7de41bc7ee Mon Sep 17 00:00:00 2001 From: Christoph Mueller Date: Thu, 19 Feb 2026 15:39:24 +0000 Subject: [PATCH 03/11] Added simple code for atom detection --- INCHI-1-SRC/INCHI_BASE/src/ichimake.c | 12 ++++ INCHI-1-SRC/INCHI_BASE/src/strutil.c | 46 ++++++++++++ .../tests/test_unit/test_atropisomers.cpp | 70 +++++++++++-------- 3 files changed, 97 insertions(+), 31 deletions(-) diff --git a/INCHI-1-SRC/INCHI_BASE/src/ichimake.c b/INCHI-1-SRC/INCHI_BASE/src/ichimake.c index 0826571d..916e772b 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/ichimake.c +++ b/INCHI-1-SRC/INCHI_BASE/src/ichimake.c @@ -3901,6 +3901,18 @@ int Create_INChI(CANON_GLOBALS* pCG, /*fix_odd_things( num_atoms, out_at );*/ #if ( FIND_RING_SYSTEMS == 1 ) MarkRingSystemsInp(out_at, num_atoms, 0); + + if (ip->Atropisomers) { + for (i = 0; i < num_atoms; i++) { + if (out_at[i].nRingSystem > 0) { + orig_inp_data->at[i].nRingSystem = out_at[i].nRingSystem; + } + if (out_at[i].nNumAtInRingSystem > 0) { + orig_inp_data->at[i].nNumAtInRingSystem = out_at[i].nNumAtInRingSystem; + } + } + } + #endif /* duplicate the preprocessed structure so that all supplied out_norm_data[]->at buffers are filled */ if (out_at != out_norm_data[TAUT_YES]->at && out_norm_data[TAUT_YES]->at) diff --git a/INCHI-1-SRC/INCHI_BASE/src/strutil.c b/INCHI-1-SRC/INCHI_BASE/src/strutil.c index 88018bc3..1e7a1758 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/strutil.c +++ b/INCHI-1-SRC/INCHI_BASE/src/strutil.c @@ -4472,7 +4472,53 @@ int set_Atropisomer_t_m_layers( const ORIG_ATOM_DATA *orig_inp_data, const INChI *inchi, const INChI_Aux *aux) { + int ret = 0; + + if (orig_inp_data == NULL) + { + return 1; + } + + if (inchi == NULL || aux == NULL) + { + return 1; + } + + if (aux->nOrigAtNosInCanonOrd == NULL || + aux->nNumberOfAtoms <= 0) { + return 1; + } + + for (int i = 0; i < orig_inp_data->num_inp_atoms; i++) { + // Canonical atom number: i (0-based) + // Access atom properties, e.g.: + int num_neighbors = orig_inp_data->at[i].valence; + AT_NUMB *neighbors = orig_inp_data->at[i].neighbor; // array of neighbor indices + int is_in_ring_1 = orig_inp_data->at[i].nRingSystem; // ring membership flag + int is_in_ring_2 = orig_inp_data->at[i].nNumAtInRingSystem; // ring membership flag + // int is_in_ring_3 = orig_inp_data->at[i].ring + + // Example: print neighbors + printf("Atom %d (ring %d %d) neighbors:", i + 1, is_in_ring_1, is_in_ring_2); // 1-based for display + if (is_in_ring_2 > 1) { + for (int j = 0; j < num_neighbors; j++) { + if (orig_inp_data->at[neighbors[j]].nNumAtInRingSystem > 1 && + orig_inp_data->at[neighbors[j]].nRingSystem != orig_inp_data->at[i].nRingSystem) { + printf(" %d %d", neighbors[j] + 1, orig_inp_data->at[i].bond_stereo); + } + + } + } + + printf("\n"); + // Example: print ring membership + // if (is_in_ring) { + // printf("Atom %d is in a ring\n", i + 1); + // } + } + + return ret; } /**************************************************************************** diff --git a/INCHI-1-TEST/tests/test_unit/test_atropisomers.cpp b/INCHI-1-TEST/tests/test_unit/test_atropisomers.cpp index 7d12ead9..0de52541 100644 --- a/INCHI-1-TEST/tests/test_unit/test_atropisomers.cpp +++ b/INCHI-1-TEST/tests/test_unit/test_atropisomers.cpp @@ -10,41 +10,49 @@ extern "C" TEST(test_atropisomers, test_dummy) { 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"; + "test mol \n" + " Ketcher 2192614182D 1 1.00000 0.00000 0 \n" + " \n" + " 16 17 0 0 1 0 0 0 0 0999 V2000 \n" + " 6.7160 -7.9750 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 5.8500 -7.4750 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 4.9840 -7.9750 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 4.9840 -8.9750 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 5.8500 -9.4750 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 6.7160 -8.9750 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 6.7160 -5.9750 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 5.8500 -6.4750 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 4.9840 -5.9750 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 4.9840 -4.9750 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 6.7160 -4.9750 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 5.8500 -4.4750 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 7.5821 -6.4750 0.0000 Br 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 4.1179 -6.4750 0.0000 Cl 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 4.1179 -7.4749 0.0000 Cl 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 7.5821 -7.4750 0.0000 Br 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 1 6 1 0 0 0 0 \n" + " 1 2 2 0 0 0 0 \n" + " 2 3 1 1 0 0 0 \n" + " 2 8 1 0 0 0 0 \n" + " 3 4 2 0 0 0 0 \n" + " 5 4 1 0 0 0 0 \n" + " 5 6 2 0 0 0 0 \n" + " 8 7 1 1 0 0 0 \n" + " 7 11 2 0 0 0 0 \n" + " 8 9 2 0 0 0 0 \n" + " 9 10 1 0 0 0 0 \n" + " 10 12 2 0 0 0 0 \n" + " 12 11 1 0 0 0 0 \n" + " 7 13 1 0 0 0 0 \n" + " 9 14 1 0 0 0 0 \n" + " 3 15 1 0 0 0 0 \n" + " 1 16 1 0 0 0 0 \n" + "M END \n"; char options[] = "-Atropisomers"; inchi_Output output; inchi_Output *poutput = &output; - 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"; + 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), 1); EXPECT_STREQ(poutput->szInChI, expected_inchi); From b17b70705b40e16d862fe4305340c96c6a13dc8e Mon Sep 17 00:00:00 2001 From: Christoph Mueller Date: Fri, 20 Feb 2026 14:24:26 +0000 Subject: [PATCH 04/11] Added first prototype for atom+bond identification for atropisomers --- INCHI-1-SRC/INCHI_BASE/src/ichimake.c | 10 + INCHI-1-SRC/INCHI_BASE/src/strutil.c | 87 +++-- .../tests/test_unit/test_atropisomers.cpp | 367 +++++++++++++++++- 3 files changed, 440 insertions(+), 24 deletions(-) diff --git a/INCHI-1-SRC/INCHI_BASE/src/ichimake.c b/INCHI-1-SRC/INCHI_BASE/src/ichimake.c index 916e772b..bd652ecb 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/ichimake.c +++ b/INCHI-1-SRC/INCHI_BASE/src/ichimake.c @@ -3910,6 +3910,16 @@ int Create_INChI(CANON_GLOBALS* pCG, if (out_at[i].nNumAtInRingSystem > 0) { orig_inp_data->at[i].nNumAtInRingSystem = out_at[i].nNumAtInRingSystem; } + if (out_at[i].nBlockSystem > 0) { + orig_inp_data->at[i].nBlockSystem = out_at[i].nBlockSystem; + } + for (int j = 0; j < out_at[i].valence; j++) { + if (out_at[i].bond_stereo[j] > 0) { + orig_inp_data->at[i].bond_stereo[j] = out_at[i].bond_stereo[j]; + // printf("%d %d\n", i, out_at[i].bond_stereo[j]); + } + + } } } diff --git a/INCHI-1-SRC/INCHI_BASE/src/strutil.c b/INCHI-1-SRC/INCHI_BASE/src/strutil.c index 1e7a1758..b26e8688 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/strutil.c +++ b/INCHI-1-SRC/INCHI_BASE/src/strutil.c @@ -4489,34 +4489,77 @@ int set_Atropisomer_t_m_layers( const ORIG_ATOM_DATA *orig_inp_data, return 1; } + printf("-------------------\n"); + for (int i = 0; i < orig_inp_data->num_inp_atoms; i++) { - // Canonical atom number: i (0-based) - // Access atom properties, e.g.: - int num_neighbors = orig_inp_data->at[i].valence; - AT_NUMB *neighbors = orig_inp_data->at[i].neighbor; // array of neighbor indices - int is_in_ring_1 = orig_inp_data->at[i].nRingSystem; // ring membership flag - int is_in_ring_2 = orig_inp_data->at[i].nNumAtInRingSystem; // ring membership flag - // int is_in_ring_3 = orig_inp_data->at[i].ring - - // Example: print neighbors - printf("Atom %d (ring %d %d) neighbors:", i + 1, is_in_ring_1, is_in_ring_2); // 1-based for display - if (is_in_ring_2 > 1) { - for (int j = 0; j < num_neighbors; j++) { - if (orig_inp_data->at[neighbors[j]].nNumAtInRingSystem > 1 && - orig_inp_data->at[neighbors[j]].nRingSystem != orig_inp_data->at[i].nRingSystem) { - printf(" %d %d", neighbors[j] + 1, orig_inp_data->at[i].bond_stereo); - } + const inp_ATOM atom_i = orig_inp_data->at[i]; + + int num_neighbors_i = atom_i.valence; + const AT_NUMB *neighbors = atom_i.neighbor; + // printf("Atom %d with %d neighbors; ring system id %d; num atoms in ring %d\n", i + 1, num_neighbors, orig_inp_data->at[i].nRingSystem, orig_inp_data->at[i].nNumAtInRingSystem); + if (num_neighbors_i == 3) { + for (int j = 0; j < num_neighbors_i; j++) { + const inp_ATOM atom_j = orig_inp_data->at[neighbors[j]]; + int num_neighbors_j = atom_j.valence; + + if (num_neighbors_j == 3 && + atom_i.bond_stereo[j] == 0 && + atom_i.bond_type[j] == 1) { + + if (atom_i.nNumAtInRingSystem > 1 || + atom_j.nNumAtInRingSystem > 1) { + + int nof_wedge_bonds_i = 0; + int has_double_bond_i = 0; + for (int k = 0; k < num_neighbors_i; k++) { + if (atom_i.bond_stereo[k] == 1 || + atom_i.bond_stereo[k] == 4 || + atom_i.bond_stereo[k] == 6) { + nof_wedge_bonds_i++; + } + if (atom_i.bond_type[k] == 2) { + has_double_bond_i = 1; + } + } + int nof_wedge_bonds_j = 0; + int has_double_bond_j = 0; + for (int k = 0; k < num_neighbors_j; k++) { + if (atom_j.bond_stereo[k] == 1 || + atom_j.bond_stereo[k] == 4 || + atom_j.bond_stereo[k] == 6) { + nof_wedge_bonds_j++; + } + if (atom_j.bond_type[k] == 2) { + has_double_bond_j = 1; + } + } + + if (nof_wedge_bonds_i > 0 || + nof_wedge_bonds_j > 0) { + + // if(atom_i.nRingSystem != atom_j.nRingSystem) { + // printf("Atropisomer candidate: Atom %d with neighbor %d\n", i + 1, neighbors[j] + 1); + // } else { + if (has_double_bond_i || has_double_bond_j) { + printf("Atropisomer candidate: atom %d with atom %d; bond type %d; bond stereo %d\n", + i + 1, neighbors[j] + 1, atom_i.bond_type[j], atom_i.bond_stereo[j]); + printf("atom type %d %d\n", atom_i.el_number, atom_j.el_number); + printf("has double bond %d %d\n", has_double_bond_i, has_double_bond_j); + printf("ring info #atoms in rings %d %d\n", atom_i.nNumAtInRingSystem, atom_j.nNumAtInRingSystem); + printf("ring info ring ids %d %d\n", atom_i.nRingSystem, atom_j.nRingSystem); + printf("ring info block ids %d %d\n", atom_i.nBlockSystem, atom_j.nBlockSystem); + } + // } + } + } + + } } } + } - printf("\n"); - // Example: print ring membership - // if (is_in_ring) { - // printf("Atom %d is in a ring\n", i + 1); - // } - } return ret; } diff --git a/INCHI-1-TEST/tests/test_unit/test_atropisomers.cpp b/INCHI-1-TEST/tests/test_unit/test_atropisomers.cpp index 0de52541..c8eded60 100644 --- a/INCHI-1-TEST/tests/test_unit/test_atropisomers.cpp +++ b/INCHI-1-TEST/tests/test_unit/test_atropisomers.cpp @@ -7,10 +7,10 @@ extern "C" #include "../../../INCHI-1-SRC/INCHI_BASE/src/mode.h" } -TEST(test_atropisomers, test_dummy) +TEST(test_atropisomers, test_dummy_1) { const char *molblock = - "test mol \n" + "atropisomer test mol \n" " Ketcher 2192614182D 1 1.00000 0.00000 0 \n" " \n" " 16 17 0 0 1 0 0 0 0 0999 V2000 \n" @@ -59,3 +59,366 @@ TEST(test_atropisomers, test_dummy) FreeINCHI(poutput); } + +TEST(test_atropisomers, test_dummy_2) +{ + const char *molblock = + "atropisomer test mol \n" + " Ketcher 2202610402D 1 1.00000 0.00000 0 \n" + " \n" + " 25 28 0 0 1 0 0 0 0 0999 V2000 \n" + " 6.2229 -3.4324 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 8.0096 -3.6108 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 7.1896 -3.0101 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 7.8699 -4.5953 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 6.0956 -4.4735 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 6.9105 -5.0541 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 6.0587 -6.8139 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 7.8275 -6.8325 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 6.8777 -6.3069 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 7.9085 -7.7852 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 6.1259 -7.8397 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 7.0613 -8.3249 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 8.4672 -5.1980 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 8.4510 -6.2674 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 9.4062 -5.3893 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 9.4009 -6.0976 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 10.3049 -4.9122 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 10.2817 -6.5709 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 10.9561 -5.7358 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 10.5172 -3.9146 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 10.4810 -7.5491 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 5.4299 -2.8268 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 4.5119 -3.2022 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 5.2852 -8.3732 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 4.3939 -7.8951 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 3 1 2 0 0 0 \n" + " 1 5 1 0 0 0 \n" + " 5 6 2 0 0 0 \n" + " 6 4 1 1 0 0 \n" + " 4 2 2 0 0 0 \n" + " 9 7 1 1 0 0 \n" + " 7 11 2 0 0 0 \n" + " 12 10 2 0 0 0 \n" + " 10 8 1 0 0 0 \n" + " 8 9 2 0 0 0 \n" + " 6 9 1 0 0 0 \n" + " 4 13 1 0 0 0 \n" + " 8 14 1 0 0 0 \n" + " 13 15 1 0 0 0 \n" + " 14 16 1 0 0 0 \n" + " 15 16 1 0 0 0 \n" + " 15 17 1 0 0 0 \n" + " 16 18 1 0 0 0 \n" + " 17 19 1 0 0 0 \n" + " 18 19 1 0 0 0 \n" + " 17 20 1 6 0 0 \n" + " 18 21 1 1 0 0 \n" + " 1 22 1 0 0 0 \n" + " 22 23 1 0 0 0 \n" + " 11 24 1 0 0 0 \n" + " 24 25 1 0 0 0 \n" + " 12 11 1 1 0 0 \n" + " 3 2 1 1 0 0 \n" + "M END \n"; + + + char options[] = "-Atropisomers"; + inchi_Output output; + inchi_Output *poutput = &output; + const char expected_inchi[] = "InChI=1B/C20H22O3/c1-21-17-5-3-13-7-15-11-23-12-16(15)8-14-4-6-18(22-2)10-20(14)19(13)9-17/h3-6,9-10,15-16H,7-8,11-12H2,1-2H3"; + + EXPECT_EQ(MakeINCHIFromMolfileText(molblock, options, poutput), 1); + EXPECT_STREQ(poutput->szInChI, expected_inchi); + + FreeINCHI(poutput); +} + +TEST(test_atropisomers, test_dummy_3) +{ + const char *molblock = + "atropisomer test mol \n" + " Ketcher 2202610482D 1 1.00000 0.00000 0 \n" + " \n" + " 22 25 0 0 1 0 0 0 0 0999 V2000 \n" + " 2.8848 -2.2501 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 4.6152 -2.2496 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 3.7516 -1.7500 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 4.6152 -3.2505 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 2.8848 -3.2550 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 3.7538 -3.7500 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 5.4796 -1.7512 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 6.3468 -2.2515 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 5.4858 -3.7529 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 6.3490 -3.2475 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 2.8348 -5.3751 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 4.5652 -5.3746 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 3.7016 -4.8750 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 4.5652 -6.3755 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 2.8348 -6.3800 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 3.7038 -6.8750 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 5.4296 -4.8762 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 6.2968 -5.3765 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 5.4358 -6.8779 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 6.2990 -6.3725 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 7.2171 -3.7439 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 7.1622 -4.8755 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 3 1 2 0 0 0 \n" + " 1 5 1 0 0 0 \n" + " 5 6 2 0 0 0 \n" + " 6 4 1 0 0 0 \n" + " 4 2 1 0 0 0 \n" + " 2 3 1 0 0 0 \n" + " 4 9 2 0 0 0 \n" + " 9 10 1 1 0 0 \n" + " 10 8 1 0 0 0 \n" + " 7 2 2 0 0 0 \n" + " 13 11 2 0 0 0 \n" + " 11 15 1 0 0 0 \n" + " 15 16 2 0 0 0 \n" + " 16 14 1 0 0 0 \n" + " 14 12 1 0 0 0 \n" + " 12 13 1 0 0 0 \n" + " 14 19 2 0 0 0 \n" + " 19 20 1 0 0 0 \n" + " 20 18 2 0 0 0 \n" + " 18 17 1 0 0 0 \n" + " 17 12 2 0 0 0 \n" + " 9 17 1 0 0 0 \n" + " 10 21 1 0 0 0 \n" + " 18 22 1 0 0 0 \n" + " 7 8 1 1 0 0 \n" + "M END \n"; + + char options[] = "-Atropisomers"; + inchi_Output output; + inchi_Output *poutput = &output; + const char expected_inchi[] = "InChI=1B/C20H16O2/c21-17-11-9-13-5-1-3-7-15(13)19(17)20-16-8-4-2-6-14(16)10-12-18(20)22/h1-11,18,21-22H,12H2"; + + EXPECT_EQ(MakeINCHIFromMolfileText(molblock, options, poutput), 1); + EXPECT_STREQ(poutput->szInChI, expected_inchi); + + FreeINCHI(poutput); +} + +TEST(test_atropisomers, test_dummy_4) +{ + const char *molblock = + "atropisomer test mol \n" + " Ketcher 2202610562D 1 1.00000 0.00000 0 \n" + " \n" + " 24 25 0 0 1 0 0 0 0 0999 V2000 \n" + " 9.1250 -2.5500 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 9.9910 -3.0500 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 9.9910 -4.0500 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 9.1250 -4.5500 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 8.2590 -4.0500 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 8.2590 -3.0500 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 7.1288 -7.8500 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 6.2598 -7.3550 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 7.9902 -7.3505 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 7.1266 -5.8500 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 7.9902 -6.3496 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 6.2598 -6.3501 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 7.1272 -4.8500 0.0000 N 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 6.2612 -4.3500 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 6.2612 -3.3500 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 5.3952 -4.8500 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 4.5292 -4.3500 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 5.3939 -5.8499 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 8.8564 -5.8500 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 8.8564 -4.8500 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 9.7224 -6.3500 0.0000 N 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 4.8939 -6.7160 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 5.8939 -4.9839 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 4.5279 -5.3499 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 2 1 1 0 0 0 \n" + " 3 2 1 0 0 0 \n" + " 4 3 1 0 0 0 \n" + " 5 4 1 0 0 0 \n" + " 6 5 1 0 0 0 \n" + " 1 6 1 0 0 0 \n" + " 11 10 2 0 0 0 \n" + " 9 11 1 0 0 0 \n" + " 7 9 2 0 0 0 \n" + " 8 7 1 0 0 0 \n" + " 12 8 2 0 0 0 \n" + " 10 12 1 1 0 0 \n" + " 10 13 1 0 0 0 \n" + " 13 5 1 0 0 0 \n" + " 13 14 1 6 0 0 \n" + " 14 15 2 0 0 0 \n" + " 14 16 1 0 0 0 \n" + " 16 17 2 0 0 0 \n" + " 12 18 1 0 0 0 \n" + " 11 19 1 0 0 0 \n" + " 19 20 2 0 0 0 0 \n" + " 19 21 1 0 0 0 0 \n" + " 22 18 1 0 0 0 0 \n" + " 18 23 1 0 0 0 0 \n" + " 18 24 1 0 0 0 0 \n" + "M STY 1 1 SUP \n" + "M SLB 1 1 1 \n" + "M SAP 1 1 19 0 \n" + "M SAL 1 3 19 20 21 \n" + "M SBL 1 1 20 \n" + "M SMT 1 CONH2 \n" + "M STY 1 2 SUP \n" + "M SLB 1 2 2 \n" + "M SAP 2 1 18 0 \n" + "M SAL 2 4 18 22 23 24 \n" + "M SBL 2 1 19 \n" + "M SMT 2 tBu \n" + "M END \n"; + + + char options[] = "-Atropisomers"; + inchi_Output output; + inchi_Output *poutput = &output; + const char expected_inchi[] = "InChI=1B/C20H28N2O2/c1-5-17(23)22(14-10-7-6-8-11-14)18-15(19(21)24)12-9-13-16(18)20(2,3)4/h5,9,12-14H,1,6-8,10-11H2,2-4H3,(H2,21,24)"; + + EXPECT_EQ(MakeINCHIFromMolfileText(molblock, options, poutput), 1); + EXPECT_STREQ(poutput->szInChI, expected_inchi); + + FreeINCHI(poutput); +} + +TEST(test_atropisomers, test_dummy_5_no_wedge_bonds) +{ + const char *molblock = + "atropisomer test mol \n" + " Ketcher 2202613492D 1 1.00000 0.00000 0 \n" + " \n" + " 33 37 0 0 0 0 0 0 0 0999 V2000 \n" + " -4.6637 -1.0934 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " -3.9945 -1.8366 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " -4.2024 -2.8147 0.0000 N 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " -3.3364 -3.3147 0.0000 N 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " -2.5933 -2.6456 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " -3.0000 -1.7321 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " -2.5000 -0.8660 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " -3.0000 -0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " -2.5000 0.8660 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " -1.5000 0.8660 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " -1.0000 -0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " -1.5000 -0.8660 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 0.5000 0.8660 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 0.0000 1.7321 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " -1.0000 1.7321 0.0000 N 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 1.4781 0.6581 0.0000 N 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 1.5827 -0.3364 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 2.4487 -0.8364 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 0.6691 -0.7431 0.0000 N 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 0.6342 -1.7425 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " -0.2487 -2.2120 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " -0.2836 -3.2114 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 0.5644 -3.7413 0.0000 N 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 1.4474 -3.2718 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 1.4823 -2.2725 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 2.3652 -1.8030 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 3.2133 -2.3329 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " -1.0968 -1.6821 0.0000 F 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 2.2213 1.3272 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " -4.0000 -0.0000 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " -4.5000 0.8660 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " -3.2319 -4.3092 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 2 0 0 0 \n" + " 3 4 1 0 0 0 \n" + " 4 5 1 0 0 0 \n" + " 5 6 2 0 0 0 \n" + " 2 6 1 0 0 0 \n" + " 6 7 1 0 0 0 \n" + " 7 8 1 0 0 0 \n" + " 8 9 2 0 0 0 \n" + " 9 10 1 0 0 0 \n" + " 10 11 2 0 0 0 \n" + " 11 12 1 0 0 0 \n" + " 7 12 2 0 0 0 \n" + " 11 13 1 0 0 0 \n" + " 13 14 2 0 0 0 \n" + " 14 15 1 0 0 0 \n" + " 15 16 2 0 0 0 \n" + " 10 16 1 0 0 0 \n" + " 14 17 1 0 0 0 \n" + " 17 18 1 0 0 0 \n" + " 18 19 2 0 0 0 \n" + " 18 20 1 0 0 0 \n" + " 13 20 1 0 0 0 \n" + " 20 21 1 0 0 0 \n" + " 21 22 1 0 0 0 \n" + " 22 23 2 0 0 0 \n" + " 23 24 1 0 0 0 \n" + " 24 25 2 0 0 0 \n" + " 25 26 1 0 0 0 \n" + " 21 26 2 0 0 0 \n" + " 26 27 1 0 0 0 \n" + " 27 28 1 0 0 0 \n" + " 22 29 1 0 0 0 \n" + " 17 30 1 0 0 0 \n" + " 8 31 1 0 0 0 \n" + " 31 32 1 0 0 0 \n" + " 4 33 1 0 0 0 \n" + "M END \n"; + + char options[] = "-Atropisomers"; + inchi_Output output; + inchi_Output *poutput = &output; + const char expected_inchi[] = "InChI=1B/C23H21FN6O3/c1-12-15(11-28(2)27-12)13-6-14-17(7-19(13)32-4)26-9-18-21(14)30(23(31)29(18)3)22-16(24)8-25-10-20(22)33-5/h6-11H,1-5H3"; + + EXPECT_EQ(MakeINCHIFromMolfileText(molblock, options, poutput), 1); + EXPECT_STREQ(poutput->szInChI, expected_inchi); + + FreeINCHI(poutput); +} + +TEST(test_atropisomers, test_dummy_6_no_atropisomer_1) +{ + const char *molblock = + "non-atropisomer test mol \n" + " Ketcher 2202614 02D 1 1.00000 0.00000 0 \n" + " \n" + " 14 15 0 0 1 0 0 0 0 0999 V2000 \n" + " -0.6402 -0.4251 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 1.0902 -0.4246 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 0.2266 0.0750 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 1.0902 -1.4255 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " -0.6402 -1.4300 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 0.2288 -1.9250 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 1.9546 0.0738 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 2.8218 -0.4265 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 1.9608 -1.9279 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 2.8240 -1.4225 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 1.9548 1.0738 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 0.2272 1.0750 0.0000 Br 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 0.2327 -2.9250 0.0000 Br 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 1.9640 -2.9279 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 3 1 1 0 0 0 \n" + " 1 5 2 0 0 0 \n" + " 5 6 1 0 0 0 \n" + " 6 4 2 0 0 0 \n" + " 4 2 1 0 0 0 \n" + " 2 3 2 0 0 0 \n" + " 4 9 1 6 0 0 \n" + " 9 10 2 0 0 0 \n" + " 10 8 1 0 0 0 \n" + " 8 7 2 0 0 0 \n" + " 7 11 1 0 0 0 \n" + " 3 12 1 0 0 0 \n" + " 6 13 1 0 0 0 \n" + " 9 14 1 0 0 0 \n" + " 2 7 1 1 0 0 \n" + "M END \n"; + + char options[] = "-Atropisomers"; + inchi_Output output; + inchi_Output *poutput = &output; + const char expected_inchi[] = "InChI=1B"; + + EXPECT_EQ(MakeINCHIFromMolfileText(molblock, options, poutput), 1); + EXPECT_STREQ(poutput->szInChI, expected_inchi); + + FreeINCHI(poutput); +} + From 7c036b42073685ddd56715c1db36361a4750786e Mon Sep 17 00:00:00 2001 From: Christoph Mueller Date: Mon, 23 Feb 2026 13:35:31 +0000 Subject: [PATCH 05/11] added unit tests and files for ring detection --- INCHI-1-SRC/INCHI_BASE/src/ring_detection.c | 0 INCHI-1-SRC/INCHI_BASE/src/ring_detection.h | 0 INCHI-1-SRC/INCHI_BASE/src/runichi3.c | 64 +++++------ .../tests/test_unit/test_atropisomers.cpp | 106 +++++++++++++++++- .../tests/test_unit/test_enhancedStereo.cpp | 47 ++++++++ 5 files changed, 184 insertions(+), 33 deletions(-) create mode 100644 INCHI-1-SRC/INCHI_BASE/src/ring_detection.c create mode 100644 INCHI-1-SRC/INCHI_BASE/src/ring_detection.h diff --git a/INCHI-1-SRC/INCHI_BASE/src/ring_detection.c b/INCHI-1-SRC/INCHI_BASE/src/ring_detection.c new file mode 100644 index 00000000..e69de29b diff --git a/INCHI-1-SRC/INCHI_BASE/src/ring_detection.h b/INCHI-1-SRC/INCHI_BASE/src/ring_detection.h new file mode 100644 index 00000000..e69de29b diff --git a/INCHI-1-SRC/INCHI_BASE/src/runichi3.c b/INCHI-1-SRC/INCHI_BASE/src/runichi3.c index b326e28d..cb5d880b 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/runichi3.c +++ b/INCHI-1-SRC/INCHI_BASE/src/runichi3.c @@ -245,7 +245,7 @@ int OrigAtData_Duplicate( ORIG_ATOM_DATA *new_orig_atom, - new_orig_atom->szCoord = NULL; + new_orig_atom->szCoord = NULL; if (orig_atom->szCoord) { new_orig_atom->szCoord = (MOL_COORD *) inchi_calloc(orig_nat, sizeof(new_orig_atom->szCoord[0])); @@ -255,10 +255,10 @@ int OrigAtData_Duplicate( ORIG_ATOM_DATA *new_orig_atom, } memcpy(new_orig_atom->szCoord, orig_atom->szCoord, orig_nat * sizeof(new_orig_atom->szCoord[0])); } - + /* Arrays that are not to be copied */ - + new_orig_atom->nEquLabels = NULL; new_orig_atom->nSortedOrder = NULL; @@ -1238,10 +1238,10 @@ OAD_PolymerUnit* OAD_PolymerUnit_New( int maxatoms, u2->blist[k] = blist->item[k]; } } - + } u2->bkbonds = NULL; - + exit_function: if (err) @@ -1528,7 +1528,7 @@ int OAD_ValidatePolymerAndPseudoElementData( ORIG_ATOM_DATA *orig_at_data, /* Assign polymer type and subunits type and check polymer data for consistency */ /* djb-rwth: addressing coverity ID #499497 -- TREAT_ERR properly used in all cases */ - + orig_at_data->valid_polymer = 0; if (treat_polymers && pd) { @@ -1595,29 +1595,29 @@ int OAD_ValidatePolymerAndPseudoElementData( ORIG_ATOM_DATA *orig_at_data, OAD_PolymerUnit_SetEndsAndCaps( u, orig_at_data, &err, pStrErr ); /* Reveal and store CRU caps and ends('stars and partners') - Also set `unit->cap1_is_undef`, `unit->cap2_is_undef`, `unit->cyclizable` + Also set `unit->cap1_is_undef`, `unit->cap2_is_undef`, `unit->cyclizable` */ if (err) { goto exit_function; } - + /* Set possibly missing unit parameters */ u->nbkbonds = 0; u->cyclizable = CLOSING_SRU_NOT_APPLICABLE; u->cyclized = 0; } - + OAD_ValidateAndSortOutPseudoElementAtoms( orig_at_data, treat_polymers, bNPZz, &err, pStrErr ); /* Here we: Make more polymer and pseudoatom data checks Convert both "*" and "Zz" temporarily to "Zy" (polymer-unrelated interal pseudoatoms) - If applicable, check each CRU and back-convert "Zy" to "Zz" (polymer-related + If applicable, check each CRU and back-convert "Zy" to "Zz" (polymer-related pseudoelement atoms) if they are for valid bi-undef-end CRU */ - + if (err) { /* already treated TREAT_ERR( err, 9040, "Improper pseudoelement atoms" ); */ @@ -1995,7 +1995,7 @@ int OAD_Polymer_CyclizeCloseableUnits( ORIG_ATOM_DATA *orig_at_data, /* Find stars and their partners */ OAD_PolymerUnit_SetEndsAndCaps( unit, orig_at_data, &err, pStrErr ); /* Reveal and store CRU caps and ends('stars and partners') - Also set `unit->cap1_is_undef`, `unit->cap2_is_undef`, `unit->cyclizable` + Also set `unit->cap1_is_undef`, `unit->cap2_is_undef`, `unit->cyclizable` */ if (err) { @@ -2157,7 +2157,7 @@ void OAD_PolymerUnit_UnlinkCapsAndConnectEndAtoms( OAD_PolymerUnit *unit, { unit->cyclized = 1; } - + return; } @@ -2258,8 +2258,8 @@ void OAD_PolymerUnit_FindEndsAndCaps( OAD_PolymerUnit *unit, *err = 0; return; } - - + + /**************************************************************************** Reveal and store CRU caps and ends ('stars and partners') ****************************************************************************/ @@ -2486,7 +2486,7 @@ int OAD_Polymer_PrepareWorkingSet( OAD_Polymer *p, } - /* Sort all units in modified alist's lexicographic order + /* Sort all units in modified alist's lexicographic order (modification is: longer list always go first ) */ for (i = 0; i < p->n; i++) { @@ -2582,7 +2582,7 @@ int OrigAtData_RemoveBond( int this_atom, int *num_inp_bonds ) { int del = 0; - + if (at && (this_atom >= 0) && (other_atom >= 0)) /* djb-rwth: fixing oss-fuzz issue #68329, #68286 */ { del = OrigAtData_RemoveHalfBond(this_atom, other_atom, at, bond_type, bond_stereo); @@ -2844,14 +2844,14 @@ void OAD_CollectFragmentBondsAndAtoms( ORIG_ATOM_DATA *orig_at_data, goto exit_function; } - spf->seen[0] = spf->start; + spf->seen[0] = spf->start; spf->nseen = 1; *n_fragbonds = 0; *n_fragatoms = 0; - subgraf_pathfinder_run( spf, + subgraf_pathfinder_run( spf, nforbidden, forbidden_orig, /* this corrects cinnectivity of subgraf... */ - n_fragbonds, fragbonds, + n_fragbonds, fragbonds, n_fragatoms, fragatoms); @@ -2884,7 +2884,7 @@ void OAD_Polymer_FindBackbones( ORIG_ATOM_DATA *at_data, continue; } - OAD_CollectBackboneBonds( at_data, + OAD_CollectBackboneBonds( at_data, at_data->polymer->units[i]->na, at_data->polymer->units[i]->alist, at_data->polymer->units[i]->end_atom1, @@ -2991,10 +2991,10 @@ void OAD_CollectBackboneAtoms(ORIG_ATOM_DATA *at_data, goto exit_function; } - spf->seen[0] = spf->start; spf->nseen = 1; + spf->seen[0] = spf->start; spf->nseen = 1; nbkbonds = 0; *nbkatoms = 0; - + subgraf_pathfinder_run(spf, 0, NULL, &nbkbonds, bkbonds, nbkatoms, bkatoms); subgraf_free(sg); @@ -3007,7 +3007,7 @@ void OAD_CollectBackboneAtoms(ORIG_ATOM_DATA *at_data, { imat_free(maxbkbonds, bkbonds); bkbonds = NULL; - } + } return; } @@ -3097,7 +3097,7 @@ int OAD_CollectReachableAtoms( ORIG_ATOM_DATA *orig_at_data, { inchi_free(atnums); } - + return ret; } @@ -3107,7 +3107,7 @@ int OAD_CollectReachableAtoms( ORIG_ATOM_DATA *orig_at_data, (for polymer CRU, these are the bonds potentially involved in frame shift) ****************************************************************************/ void OAD_CollectBackboneBonds(ORIG_ATOM_DATA *at_data, - int na, + int na, int *alist, int end_atom1, int end_atom2, @@ -3128,7 +3128,7 @@ void OAD_CollectBackboneBonds(ORIG_ATOM_DATA *at_data, /* unit->cyclizable = CLOSING_SRU_NOT_APPLICABLE; */ return; } - start = sg->orig2node[end_atom1]; + start = sg->orig2node[end_atom1]; end = sg->orig2node[end_atom2]; #if 0 if (start > end) @@ -3145,8 +3145,8 @@ void OAD_CollectBackboneBonds(ORIG_ATOM_DATA *at_data, /*unit->cyclizable = CLOSING_SRU_NOT_APPLICABLE;*/ return; } - spf->seen[0] = spf->start; - spf->nseen = 1; + spf->seen[0] = spf->start; + spf->nseen = 1; *nbkbonds = 0; subgraf_pathfinder_run( spf, 0, NULL, nbkbonds, @@ -3745,7 +3745,7 @@ void OAD_PolymerUnit_DebugTrace( OAD_PolymerUnit *u ) ITRACE_("}\n"); } } - + return; } @@ -4423,7 +4423,7 @@ void OAD_ValidateAndSortOutPseudoElementAtoms( ORIG_ATOM_DATA *orig_at_data, TREAT_ERR(*err, (70 + 5), "Invalid element(s):"); TREAT_ERR(*err, (70 + 5), orig_at_data->at[k].elname); continue; -#endif +#endif } is_star = !strcmp( orig_at_data->at[k].elname, "*" ); if (!is_star) @@ -4517,4 +4517,4 @@ int Inp_Atom_GetBondType(inp_ATOM *at, int iatom1, int iatom2) } return -1; -} \ No newline at end of file +} diff --git a/INCHI-1-TEST/tests/test_unit/test_atropisomers.cpp b/INCHI-1-TEST/tests/test_unit/test_atropisomers.cpp index c8eded60..cd1916b0 100644 --- a/INCHI-1-TEST/tests/test_unit/test_atropisomers.cpp +++ b/INCHI-1-TEST/tests/test_unit/test_atropisomers.cpp @@ -373,7 +373,110 @@ TEST(test_atropisomers, test_dummy_5_no_wedge_bonds) FreeINCHI(poutput); } -TEST(test_atropisomers, test_dummy_6_no_atropisomer_1) +TEST(test_atropisomers, test_dummy_6_two_atropisomer_bonds) +{ + const char *molblock = + "test mol atropisomer \n" + " -INDIGO-02232612272D \n" + " \n" + " 0 0 0 0 0 0 0 0 0 0 0 V3000 \n" + "M V30 BEGIN CTAB \n" + "M V30 COUNTS 35 38 0 0 0 \n" + "M V30 BEGIN ATOM \n" + "M V30 1 C 7.64682 -5.78999 0.0 0 \n" + "M V30 2 C 9.47015 -5.85956 0.0 0 \n" + "M V30 3 C 8.58333 -5.30104 0.0 0 \n" + "M V30 4 C 9.42738 -6.91249 0.0 0 \n" + "M V30 5 C 7.60313 -6.84054 0.0 0 \n" + "M V30 6 C 8.49613 -7.40423 0.0 0 \n" + "M V30 7 C 7.83065 -2.75985 0.0 0 \n" + "M V30 8 C 9.58077 -2.8408 0.0 0 \n" + "M V30 9 C 8.73115 -2.29671 0.0 0 \n" + "M V30 10 C 9.53681 -3.85648 0.0 0 \n" + "M V30 11 C 7.7836 -3.77279 0.0 0 \n" + "M V30 12 C 8.64006 -4.32505 0.0 0 \n" + "M V30 13 Br 6.90205 -7.15024 0.0 0 \n" + "M V30 14 Br 10.3254 -7.4481 0.0 0 \n" + "M V30 15 Br 6.93185 -4.22329 0.0 0 \n" + "M V30 16 Br 10.4302 -4.38621 0.0 0 \n" + "M V30 17 Cl 10.4134 -5.37898 0.0 0 \n" + "M V30 18 Cl 6.83923 -5.25149 0.0 0 \n" + "M V30 19 C 8.55343 -8.50701 0.0 0 \n" + "M V30 20 C 7.7261 -9.10432 0.0 0 \n" + "M V30 21 C 7.02177 -10.6261 0.0 0 \n" + "M V30 22 C 7.81179 -10.0708 0.0 0 \n" + "M V30 23 C 6.13959 -10.2141 0.0 0 \n" + "M V30 24 C 6.84915 -8.69665 0.0 0 \n" + "M V30 25 C 6.05278 -9.25201 0.0 0 \n" + "M V30 26 C 5.14001 -8.79074 0.0 0 \n" + "M V30 27 C 3.39085 -8.89047 0.0 0 \n" + "M V30 28 C 4.29687 -9.34501 0.0 0 \n" + "M V30 29 C 3.33353 -7.88488 0.0 0 \n" + "M V30 30 C 5.08172 -7.77981 0.0 0 \n" + "M V30 31 C 4.18147 -7.33019 0.0 0 \n" + "M V30 32 Br 5.94146 -7.18944 0.0 0 \n" + "M V30 33 Br 4.36521 -10.2925 0.0 0 \n" + "M V30 34 Cl 5.34468 -10.7571 0.0 0 \n" + "M V30 35 Cl 6.77442 -7.90166 0.0 0 \n" + "M V30 END ATOM \n" + "M V30 BEGIN BOND \n" + "M V30 1 1 3 1 CFG=1 \n" + "M V30 2 2 1 5 \n" + "M V30 3 1 5 6 \n" + "M V30 4 2 6 4 \n" + "M V30 5 1 4 2 \n" + "M V30 6 2 2 3 \n" + "M V30 7 2 9 7 \n" + "M V30 8 1 7 11 \n" + "M V30 9 2 11 12 \n" + "M V30 10 1 12 10 CFG=1 \n" + "M V30 11 2 10 8 \n" + "M V30 12 1 8 9 \n" + "M V30 13 1 12 3 \n" + "M V30 14 1 5 13 \n" + "M V30 15 1 4 14 \n" + "M V30 16 1 11 15 \n" + "M V30 17 1 10 16 \n" + "M V30 18 1 2 17 \n" + "M V30 19 1 1 18 \n" + "M V30 20 1 6 19 \n" + "M V30 21 1 19 20 \n" + "M V30 22 2 22 20 \n" + "M V30 23 1 20 24 \n" + "M V30 24 2 24 25 \n" + "M V30 25 1 25 23 CFG=1 \n" + "M V30 26 2 23 21 \n" + "M V30 27 1 21 22 \n" + "M V30 28 1 25 26 \n" + "M V30 29 2 28 26 \n" + "M V30 30 1 26 30 CFG=1 \n" + "M V30 31 2 30 31 \n" + "M V30 32 1 31 29 \n" + "M V30 33 2 29 27 \n" + "M V30 34 1 27 28 \n" + "M V30 35 1 30 32 \n" + "M V30 36 1 28 33 \n" + "M V30 37 1 23 34 \n" + "M V30 38 1 24 35 \n" + "M V30 END BOND \n" + "M V30 BEGIN COLLECTION \n" + "M V30 MDLV30/STEABS ATOMS=(4 3 12 25 26) \n" + "M V30 END COLLECTION \n" + "M V30 END CTAB \n" + "M END \n"; + + char options[] = "-Atropisomers"; + inchi_Output output; + inchi_Output *poutput = &output; + const char expected_inchi[] = "InChI=1B"; + + EXPECT_EQ(MakeINCHIFromMolfileText(molblock, options, poutput), 1); + EXPECT_STREQ(poutput->szInChI, expected_inchi); + + FreeINCHI(poutput); +} + +TEST(test_atropisomers, test_dummy_7_no_atropisomer_1) { const char *molblock = "non-atropisomer test mol \n" @@ -422,3 +525,4 @@ TEST(test_atropisomers, test_dummy_6_no_atropisomer_1) FreeINCHI(poutput); } + diff --git a/INCHI-1-TEST/tests/test_unit/test_enhancedStereo.cpp b/INCHI-1-TEST/tests/test_unit/test_enhancedStereo.cpp index 0a771802..5ee2cf51 100644 --- a/INCHI-1-TEST/tests/test_unit/test_enhancedStereo.cpp +++ b/INCHI-1-TEST/tests/test_unit/test_enhancedStereo.cpp @@ -1117,6 +1117,53 @@ TEST(test_enhancedStereo, test_EnhancedStereochemistry_differing_AND_groups_of_s FreeINCHI(poutput); } +TEST(test_enhancedStereo, test_EnhancedStereochemistry_abs_center_test_1) +{ + const char *molblock = + "test mol \n" + " -INDIGO-02232607492D \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 7.38571 -6.2125 0.0 0 \n" + "M V30 2 C 8.25174 -5.7125 0.0 0 \n" + "M V30 3 C 9.11776 -6.2125 0.0 0 CFG=1 \n" + "M V30 4 C 9.98379 -5.7125 0.0 0 CFG=2 \n" + "M V30 5 Cl 9.11776 -7.2125 0.0 0 \n" + "M V30 6 C 10.8498 -6.2125 0.0 0 \n" + "M V30 7 Br 9.98379 -4.7125 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 3 5 CFG=1 \n" + "M V30 5 1 4 6 \n" + "M V30 6 1 4 7 CFG=1 \n" + "M V30 END BOND \n" + "M V30 BEGIN COLLECTION \n" + "M V30 MDLV30/STEABS ATOMS=(2 3 4) \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/C4H8BrClO/c1-3(5)4(6)2-7/h3-4,7H,2H2,1H3/t3-,4-/m0/s1(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 7f34dbaaecbf90f0123c538d11e765f4a417489d Mon Sep 17 00:00:00 2001 From: Christoph Mueller Date: Wed, 25 Feb 2026 13:04:53 +0000 Subject: [PATCH 06/11] added parameter fix --- INCHI-1-SRC/INCHI_BASE/src/ichiparm.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/INCHI-1-SRC/INCHI_BASE/src/ichiparm.c b/INCHI-1-SRC/INCHI_BASE/src/ichiparm.c index 7d5f2cf7..16f24c04 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/ichiparm.c +++ b/INCHI-1-SRC/INCHI_BASE/src/ichiparm.c @@ -124,7 +124,8 @@ int set_common_options_by_parg(const char* pArg, int* pbMergeHash, int* pbHideInChI, int* pbMolecularInorganics, /* @nnuk */ - int* pbEnhancedStereochemistry); + int* pbEnhancedStereochemistry, + int* pbAtropisomers); /**************************************************************************** @@ -169,7 +170,8 @@ int set_common_options_by_parg(const char* pArg, int* pbMergeHash, int* pbHideInChI, int* pbMolecularInorganics, /*@nnuk*/ - int* pbEnhancedStereochemistry + int* pbEnhancedStereochemistry, + int* pbAtropisomers ) { int got = 0; @@ -822,7 +824,7 @@ int ReadCommandLineParms(int argc, &bLargeMolecules, &bPolymers, &bFoldPolymerSRU, &bFrameShiftScheme, &bStereoAtZz, &bNPZz, - &bNoWarnings, &bMergeHash, &bHideInChI, &bMolecularInorganics, &bEnhancedStereochemistry); + &bNoWarnings, &bMergeHash, &bHideInChI, &bMolecularInorganics, &bEnhancedStereochemistry, &bAtropisomers); if (got) { ; @@ -1270,7 +1272,7 @@ int ReadCommandLineParms(int argc, &bLargeMolecules, &bPolymers, &bFoldPolymerSRU, &bFrameShiftScheme, &bStereoAtZz, &bNPZz, - &bNoWarnings, &bMergeHash, &bHideInChI, &bMolecularInorganics, &bEnhancedStereochemistry); + &bNoWarnings, &bMergeHash, &bHideInChI, &bMolecularInorganics, &bEnhancedStereochemistry, &bAtropisomers); if ( got ) { From 518ec8b4e281a29fc5b74f773ead50adec1f52cc Mon Sep 17 00:00:00 2001 From: Christoph Mueller Date: Wed, 25 Feb 2026 14:18:37 +0000 Subject: [PATCH 07/11] add ring detection prototype --- INCHI-1-SRC/INCHI_BASE/src/ring_detection.c | 104 ++++++++++++++++++++ INCHI-1-SRC/INCHI_BASE/src/ring_detection.h | 13 +++ 2 files changed, 117 insertions(+) diff --git a/INCHI-1-SRC/INCHI_BASE/src/ring_detection.c b/INCHI-1-SRC/INCHI_BASE/src/ring_detection.c index e69de29b..beadec6a 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/ring_detection.c +++ b/INCHI-1-SRC/INCHI_BASE/src/ring_detection.c @@ -0,0 +1,104 @@ + +#include "mode.h" +#include "ring_detection.h" + + + // int ring_ids[MAX_RINGS_PER_ATOM]; + // int ring_count; + // int parent_id; // For BFS traversal + // int distance; + + +// Helper: Check if all atoms in ring 'child' are present in ring 'potential_parent' +bool is_subset(Ring* child, Ring* potential_parent) { + if (child->size >= potential_parent->size) return false; + + for (int i = 0; i < child->size; i++) { + bool found = false; + for (int j = 0; j < potential_parent->size; j++) { + if (child->atom_ids[i] == potential_parent->atom_ids[j]) { + found = true; + break; + } + } + if (!found) return false; + } + return true; +} + +// Logic to assign parent_id based on containment +void determine_ring_hierarchy(RingResult* rr) { + for (int i = 0; i < rr->count; i++) { + rr->rings[i].parent_id = -1; // Default + + int smallest_parent_size = 1e6; + + for (int j = 0; j < rr->count; j++) { + if (i == j) continue; + + if (is_subset(&rr->rings[i], &rr->rings[j])) { + // We want the most immediate parent (the smallest ring that contains it) + if (rr->rings[j].size < smallest_parent_size) { + rr->rings[i].parent_id = rr->rings[j].id; + smallest_parent_size = rr->rings[j].size; + } + } + } + } +} + +Ring create_ring_struct(Atom* atoms, int u_id, int v_id, int ring_id) { + int temp_path[MAX_NEIGHBORS * 5]; + int size = 0; + temp_path[size++] = v_id; + int curr = u_id; + while (curr != -1) { + temp_path[size++] = curr; + if (curr == v_id) break; + curr = atoms[curr].parent_id; + } + + Ring r; + r.id = ring_id; + r.size = size; + r.parent_id = -1; + r.atom_ids = (int*)malloc(sizeof(int) * size); + for (int i = 0; i < size; i++) { + r.atom_ids[i] = temp_path[i]; + atoms[temp_path[i]].ring_ids[atoms[temp_path[i]].ring_count++] = ring_id; + } + return r; +} + +RingResult find_unique_rings(Atom* atoms, int num_atoms) { + RingResult rr; + rr.rings = (Ring*)malloc(sizeof(Ring) * num_atoms); + rr.count = 0; + + for (int i = 0; i < num_atoms; i++) { + atoms[i].distance = -1; + atoms[i].parent_id = -1; + } + + int queue[MAX_NEIGHBORS * 10], head = 0, tail = 0; + atoms[0].distance = 0; + queue[tail++] = 0; + + while (head < tail) { + Atom* u = &atoms[queue[head++]]; + for (int i = 0; i < u->neighbor_count; i++) { + Atom* v = u->neighbors[i]; + if (v->distance == -1) { + v->distance = u->distance + 1; + v->parent_id = u->id; + queue[tail++] = v->id; + } else if (v->id != u->parent_id && u->distance >= v->distance) { + rr.rings[rr.count] = create_ring_struct(atoms, u->id, v->id, rr.count); + rr.count++; + } + } + } + + determine_ring_hierarchy(&rr); + return rr; +} diff --git a/INCHI-1-SRC/INCHI_BASE/src/ring_detection.h b/INCHI-1-SRC/INCHI_BASE/src/ring_detection.h index e69de29b..9526be87 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/ring_detection.h +++ b/INCHI-1-SRC/INCHI_BASE/src/ring_detection.h @@ -0,0 +1,13 @@ + + +typedef struct Ring { + int id; + int* atom_ids; + int size; + int parent_id; // Added: ID of the larger ring containing this one +} Ring; + +typedef struct { + Ring* rings; + int count; +} RingResult; From cc0f40836d70513273b862b9a9e92941b5e47ea3 Mon Sep 17 00:00:00 2001 From: Christoph Mueller Date: Thu, 26 Feb 2026 15:26:11 +0000 Subject: [PATCH 08/11] added new ring detection based on hortons algorithm --- .../INCHI_API/libinchi/src/CMakeLists.txt | 2 + INCHI-1-SRC/INCHI_BASE/src/ichimake.c | 23 +- INCHI-1-SRC/INCHI_BASE/src/inpdef.h | 6 + INCHI-1-SRC/INCHI_BASE/src/ring_detection.c | 309 ++++++++++++++---- INCHI-1-SRC/INCHI_BASE/src/ring_detection.h | 17 +- INCHI-1-SRC/INCHI_BASE/src/strutil.c | 17 +- .../INCHI_EXE/inchi-1/src/CMakeLists.txt | 2 + 7 files changed, 293 insertions(+), 83 deletions(-) diff --git a/INCHI-1-SRC/INCHI_API/libinchi/src/CMakeLists.txt b/INCHI-1-SRC/INCHI_API/libinchi/src/CMakeLists.txt index 8fd3b273..3bb4733b 100644 --- a/INCHI-1-SRC/INCHI_API/libinchi/src/CMakeLists.txt +++ b/INCHI-1-SRC/INCHI_API/libinchi/src/CMakeLists.txt @@ -127,6 +127,8 @@ target_sources(libinchi PRIVATE ${P_BASE}/permutation_util.c ${P_BASE}/readinch.c ${P_BASE}/readinch.h + ${P_BASE}/ring_detection.c + ${P_BASE}/ring_detection.h ${P_BASE}/runichi.c ${P_BASE}/runichi2.c ${P_BASE}/runichi3.c diff --git a/INCHI-1-SRC/INCHI_BASE/src/ichimake.c b/INCHI-1-SRC/INCHI_BASE/src/ichimake.c index bd652ecb..b8e86825 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/ichimake.c +++ b/INCHI-1-SRC/INCHI_BASE/src/ichimake.c @@ -54,6 +54,8 @@ #include "bcf_s.h" +#include "ring_detection.h" + /* Local functions */ @@ -3903,16 +3905,20 @@ int Create_INChI(CANON_GLOBALS* pCG, MarkRingSystemsInp(out_at, num_atoms, 0); if (ip->Atropisomers) { + RingResult *ring_result = find_rings(out_at, num_atoms); + + print_ring_result(ring_result); + + free_ring_result(ring_result); + for (i = 0; i < num_atoms; i++) { - if (out_at[i].nRingSystem > 0) { - orig_inp_data->at[i].nRingSystem = out_at[i].nRingSystem; - } - if (out_at[i].nNumAtInRingSystem > 0) { - orig_inp_data->at[i].nNumAtInRingSystem = out_at[i].nNumAtInRingSystem; - } - if (out_at[i].nBlockSystem > 0) { - orig_inp_data->at[i].nBlockSystem = out_at[i].nBlockSystem; + if (out_at[i].ring_count > 0) { + orig_inp_data->at[i].ring_count = out_at[i].ring_count; + for (int j = 0; j < out_at[i].ring_count; j++) { + orig_inp_data->at[i].ring_ids[j] = out_at[i].ring_ids[j]; + } } + for (int j = 0; j < out_at[i].valence; j++) { if (out_at[i].bond_stereo[j] > 0) { orig_inp_data->at[i].bond_stereo[j] = out_at[i].bond_stereo[j]; @@ -3921,6 +3927,7 @@ int Create_INChI(CANON_GLOBALS* pCG, } } + } #endif diff --git a/INCHI-1-SRC/INCHI_BASE/src/inpdef.h b/INCHI-1-SRC/INCHI_BASE/src/inpdef.h index 2981528e..ac6062a9 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/inpdef.h +++ b/INCHI-1-SRC/INCHI_BASE/src/inpdef.h @@ -100,6 +100,9 @@ typedef S_SHORT ST_CAP_FLOW; #define SB_PARITY_1(X) (X & SB_PARITY_MASK) /**< refers to connected structure */ #define SB_PARITY_2(X) (((X) >> SB_PARITY_SHFT) & SB_PARITY_MASK) /**< refers to connected structure */ +#define RS_MAX_RINGS_PER_ATOM 10 + + /** * @brief Structure describing an input atom * @@ -190,6 +193,9 @@ typedef struct tagInputAtom AT_NUMB nNumAtInRingSystem; AT_NUMB nBlockSystem; + int ring_ids[RS_MAX_RINGS_PER_ATOM]; + int ring_count; + #if (FIND_RINS_SYSTEMS_DISTANCES == 1) AT_NUMB nDistanceFromTerminal; /* terminal atom or ring system has 1, next has 2, etc. */ #endif diff --git a/INCHI-1-SRC/INCHI_BASE/src/ring_detection.c b/INCHI-1-SRC/INCHI_BASE/src/ring_detection.c index beadec6a..e8fdf478 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/ring_detection.c +++ b/INCHI-1-SRC/INCHI_BASE/src/ring_detection.c @@ -1,104 +1,285 @@ #include "mode.h" +#include "inpdef.h" #include "ring_detection.h" +int is_subset(Ring* child, + Ring* potential_parent) { - // int ring_ids[MAX_RINGS_PER_ATOM]; - // int ring_count; - // int parent_id; // For BFS traversal - // int distance; - - -// Helper: Check if all atoms in ring 'child' are present in ring 'potential_parent' -bool is_subset(Ring* child, Ring* potential_parent) { - if (child->size >= potential_parent->size) return false; + if (child->size >= potential_parent->size) { + return 0; + } for (int i = 0; i < child->size; i++) { - bool found = false; + int found = 0; for (int j = 0; j < potential_parent->size; j++) { if (child->atom_ids[i] == potential_parent->atom_ids[j]) { - found = true; + found = 1; break; } } - if (!found) return false; + if (!found) { + return 0; + } } - return true; + return 1; } -// Logic to assign parent_id based on containment void determine_ring_hierarchy(RingResult* rr) { for (int i = 0; i < rr->count; i++) { - rr->rings[i].parent_id = -1; // Default + rr->rings[i].parent_id = -1; int smallest_parent_size = 1e6; for (int j = 0; j < rr->count; j++) { - if (i == j) continue; + if (i == j) { + continue; + } if (is_subset(&rr->rings[i], &rr->rings[j])) { - // We want the most immediate parent (the smallest ring that contains it) if (rr->rings[j].size < smallest_parent_size) { rr->rings[i].parent_id = rr->rings[j].id; smallest_parent_size = rr->rings[j].size; + rr->rings[j].child_ids = (int*)inchi_realloc(rr->rings[j].child_ids, (rr->rings[j].child_count + 1) * sizeof(int)); + rr->rings[j].child_ids[rr->rings[j].child_count] = rr->rings[i].id; + rr->rings[j].child_count++; } } } } } -Ring create_ring_struct(Atom* atoms, int u_id, int v_id, int ring_id) { - int temp_path[MAX_NEIGHBORS * 5]; - int size = 0; - temp_path[size++] = v_id; - int curr = u_id; - while (curr != -1) { - temp_path[size++] = curr; - if (curr == v_id) break; - curr = atoms[curr].parent_id; - } - - Ring r; - r.id = ring_id; - r.size = size; - r.parent_id = -1; - r.atom_ids = (int*)malloc(sizeof(int) * size); - for (int i = 0; i < size; i++) { - r.atom_ids[i] = temp_path[i]; - atoms[temp_path[i]].ring_ids[atoms[temp_path[i]].ring_count++] = ring_id; - } - return r; +int get_ring_atom_overlap(const Ring *r1, const Ring *r2) { + int overlap = 0; + for (int i = 0; i < r1->size; i++) { + for (int j = 0; j < r2->size; j++) { + if (r1->atom_ids[i] == r2->atom_ids[j]) { + overlap++; + } + } + } + return overlap; } -RingResult find_unique_rings(Atom* atoms, int num_atoms) { - RingResult rr; - rr.rings = (Ring*)malloc(sizeof(Ring) * num_atoms); - rr.count = 0; - - for (int i = 0; i < num_atoms; i++) { - atoms[i].distance = -1; - atoms[i].parent_id = -1; - } - - int queue[MAX_NEIGHBORS * 10], head = 0, tail = 0; - atoms[0].distance = 0; - queue[tail++] = 0; - - while (head < tail) { - Atom* u = &atoms[queue[head++]]; - for (int i = 0; i < u->neighbor_count; i++) { - Atom* v = u->neighbors[i]; - if (v->distance == -1) { - v->distance = u->distance + 1; - v->parent_id = u->id; - queue[tail++] = v->id; - } else if (v->id != u->parent_id && u->distance >= v->distance) { - rr.rings[rr.count] = create_ring_struct(atoms, u->id, v->id, rr.count); - rr.count++; +int get_number_of_overlapping_rings(const Ring *r1, const Ring *r2) { + + int count = 0; + for (int i = 0; i < r1->child_count; i++) { + for (int j = 0; j < r2->child_count; j++) { + if (r1->child_ids[i] == r2->child_ids[j]) { + count++; } } } + return count; +} + +int get_number_of_common_rings(const inp_ATOM *atoms, + int num_atoms, + int atom1, + int atom2) { + + if (atoms == NULL || num_atoms <= 0) { + return 0; // Invalid input + } + + if (atom1 >= num_atoms || atom2 >= num_atoms) { + return 0; // Invalid atom indices + } + int count = 0; + for (int i = 0; i < atoms[atom1].ring_count; i++) { + int ring_id1 = atoms[atom1].ring_ids[i]; + for (int j = 0; j < atoms[atom2].ring_count; j++) { + int ring_id2 = atoms[atom2].ring_ids[j]; + if (ring_id1 == ring_id2) { + count++; + } + } + } + return count; +} + +void print_ring_result(const RingResult *rr) { + + printf("Number of rings: %d\n", rr->count); + + for (int i = 0; i < rr->count; i++) { + const Ring *r = &rr->rings[i]; + printf("Ring ID: %2d, Size: %2d, nof fused ring %2d, parent %2d, Atoms: ", + r->id, r->size, r->nof_unique_fused_ring, r->parent_id); + for (int j = 0; j < r->size; j++) { + printf("%d ", r->atom_ids[j]); + } + printf("\n"); + printf(" Child Ring IDs: "); + for(int j = 0; j < r->child_count; j++) { + printf(" %2d ", r->child_ids[j]); + } + printf("\n"); + } +} + +void free_ring_result(RingResult *rr) { + + if (rr == NULL) { + return; + } + + for (int i = 0; i < rr->count; i++) { + inchi_free(rr->rings[i].atom_ids); + inchi_free(rr->rings[i].child_ids); + } + inchi_free(rr->rings); + inchi_free(rr); +} + +void determine_fused_rings(RingResult* rr) { + + for (int nof_rings = 0; nof_rings < 100; nof_rings++) { + + for (int i = 0; i < rr->count; i++) { + Ring *r1 = &rr->rings[i]; + + if (nof_rings == r1->child_count) { + if (r1->child_count == 0) { + r1->nof_unique_fused_ring = 1; + } + if (r1->parent_id != -1) { + Ring *parent = &rr->rings[r1->parent_id]; + parent->nof_unique_fused_ring+=r1->nof_unique_fused_ring; + } + } + } + } +} + +void *create_new_ring(RingResult *rr, + inp_ATOM *atoms, + int *path, + int path_len) { + + // Ring *r = &rr->rings[rr->count]; //(Ring*)inchi_calloc(1, sizeof(Ring)); + rr->rings = (Ring*)inchi_realloc(rr->rings, (rr->count + 1) * sizeof(Ring)); + Ring *r = &rr->rings[rr->count]; + + r->id = rr->count; + r->size = path_len; + r->parent_id = -1; + r->child_count = 0; + r->child_ids = NULL; + r->nof_unique_fused_ring = 0; + r->atom_ids = (int*)inchi_calloc(path_len, sizeof(int)); + for (int i = 0; i < path_len; i++) { + r->atom_ids[i] = path[i]; + atoms[path[i]].ring_ids[atoms[path[i]].ring_count] = rr->count; + atoms[path[i]].ring_count++; + } + + rr->rings[rr->count] = *r; + rr->count++; +} + +int is_new_ring(RingResult *rr, + int *path, + int path_len) { + + for (int i = 0; i < rr->count; ++i) { + if (rr->rings[i].size != path_len) continue; + int match = 1; + for (int j = 0; j < path_len; ++j) { + int found = 0; + for (int k = 0; k < path_len; ++k) { + if (rr->rings[i].atom_ids[k] == path[j]) { + found = 1; + break; + } + } + if (!found) { + match = 0; + break; + } + } + if (match) return 0; + } + return 1; +} + +void dfs(RingResult *rr, + inp_ATOM* atoms, + int **adj, + int num_atoms, + int start, + int curr, + int *visited, + int *path, + int path_len) { + + visited[curr] = 1; + path[path_len] = curr; + path_len++; + + for (int i = 0; i < num_atoms; ++i) { + if (adj[curr][i]) { + if (i == start && path_len > 2) { + if (is_new_ring(rr, path, path_len)) { + + create_new_ring(rr, atoms, path, path_len); + + } + } else if (!visited[i]) { + // dfs(start, i, visited, path, path_len); + dfs(rr, atoms, adj, num_atoms, start, i, visited, path, path_len); + } + } + } + visited[curr] = 0; + + +} + +RingResult *find_rings(inp_ATOM* atoms, + int num_atoms) { + + if (atoms == NULL || num_atoms <= 0) { + return NULL; // Invalid input + } + + RingResult *rr = (RingResult*)inchi_calloc(1, sizeof(RingResult)); + rr->rings = NULL; //(Ring*)inchi_calloc(num_atoms * 10, sizeof(Ring)); + rr->count = 0; + + int visited[num_atoms]; + int path[num_atoms]; + int **adj = (int**)inchi_calloc(num_atoms, sizeof(int*)); //[num_atoms][num_atoms]; // Adjacency matrix + + for (int i = 0; i < num_atoms; ++i) { + adj[i] = (int*)inchi_calloc(num_atoms, sizeof(int)); + visited[i] = 0; + path[i] = -1; + + inp_ATOM *atom_i = &atoms[i]; + for (int j = 0; j < atom_i->valence; j++) { + int neighbor = atom_i->neighbor[j]; + adj[i][neighbor] = 1; + // adj[neighbor][i] = 1; // Undirected graph + } + } + + for (int i = 0; i < num_atoms; ++i) { + dfs(rr, atoms, adj, num_atoms, i, i, visited, path, 0); + } + + + determine_ring_hierarchy(rr); + + determine_fused_rings(rr); + + for (int i = 0; i < num_atoms; ++i) { + inchi_free(adj[i]); + } + inchi_free(adj); - determine_ring_hierarchy(&rr); return rr; } + + diff --git a/INCHI-1-SRC/INCHI_BASE/src/ring_detection.h b/INCHI-1-SRC/INCHI_BASE/src/ring_detection.h index 9526be87..f6bde465 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/ring_detection.h +++ b/INCHI-1-SRC/INCHI_BASE/src/ring_detection.h @@ -2,12 +2,27 @@ typedef struct Ring { int id; - int* atom_ids; + int *atom_ids; int size; + int nof_unique_fused_ring; int parent_id; // Added: ID of the larger ring containing this one + int *child_ids; + int child_count; } Ring; typedef struct { Ring* rings; int count; } RingResult; + + +RingResult *find_rings(inp_ATOM* atoms, int num_atoms); + +int get_number_of_common_rings(const inp_ATOM* atoms, + int num_atoms, + int atom1, + int atom2); + +void print_ring_result(const RingResult *rr); + +void free_ring_result(RingResult *rr); diff --git a/INCHI-1-SRC/INCHI_BASE/src/strutil.c b/INCHI-1-SRC/INCHI_BASE/src/strutil.c index 9109585d..e50b91e3 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/strutil.c +++ b/INCHI-1-SRC/INCHI_BASE/src/strutil.c @@ -55,6 +55,8 @@ #include "bcf_s.h" +#include "ring_detection.h" + /* Added fix to remove_ion_pairs() -- 2010-03-17 DT */ #define FIX_P_IV_Plus_O_Minus @@ -7198,8 +7200,8 @@ int set_Atropisomer_t_m_layers( const ORIG_ATOM_DATA *orig_inp_data, atom_i.bond_stereo[j] == 0 && atom_i.bond_type[j] == 1) { - if (atom_i.nNumAtInRingSystem > 1 || - atom_j.nNumAtInRingSystem > 1) { + if (atom_i.ring_count > 0 || + atom_j.ring_count > 0) { int nof_wedge_bonds_i = 0; int has_double_bond_i = 0; @@ -7229,19 +7231,14 @@ int set_Atropisomer_t_m_layers( const ORIG_ATOM_DATA *orig_inp_data, if (nof_wedge_bonds_i > 0 || nof_wedge_bonds_j > 0) { - // if(atom_i.nRingSystem != atom_j.nRingSystem) { - // printf("Atropisomer candidate: Atom %d with neighbor %d\n", i + 1, neighbors[j] + 1); - // } else { - if (has_double_bond_i || has_double_bond_j) { + if ((has_double_bond_i || has_double_bond_j)) { printf("Atropisomer candidate: atom %d with atom %d; bond type %d; bond stereo %d\n", i + 1, neighbors[j] + 1, atom_i.bond_type[j], atom_i.bond_stereo[j]); printf("atom type %d %d\n", atom_i.el_number, atom_j.el_number); printf("has double bond %d %d\n", has_double_bond_i, has_double_bond_j); - printf("ring info #atoms in rings %d %d\n", atom_i.nNumAtInRingSystem, atom_j.nNumAtInRingSystem); - printf("ring info ring ids %d %d\n", atom_i.nRingSystem, atom_j.nRingSystem); - printf("ring info block ids %d %d\n", atom_i.nBlockSystem, atom_j.nBlockSystem); + printf("nof shared rings %d\n", get_number_of_common_rings(orig_inp_data->at, orig_inp_data->num_inp_atoms, i, neighbors[j])); + } - // } } } diff --git a/INCHI-1-SRC/INCHI_EXE/inchi-1/src/CMakeLists.txt b/INCHI-1-SRC/INCHI_EXE/inchi-1/src/CMakeLists.txt index 8b83d19d..fbc1d5d7 100644 --- a/INCHI-1-SRC/INCHI_EXE/inchi-1/src/CMakeLists.txt +++ b/INCHI-1-SRC/INCHI_EXE/inchi-1/src/CMakeLists.txt @@ -102,6 +102,8 @@ target_sources(inchi-1 PRIVATE ${P_BASE}/permutation_util.c ${P_BASE}/readinch.c ${P_BASE}/readinch.h + ${P_BASE}/ring_detection.c + ${P_BASE}/ring_detection.h ${P_BASE}/runichi.c ${P_BASE}/runichi2.c ${P_BASE}/runichi3.c From b33addafb50f7a2d55681255c9258bf809239cb1 Mon Sep 17 00:00:00 2001 From: Christoph Mueller Date: Fri, 27 Feb 2026 14:16:07 +0000 Subject: [PATCH 09/11] Added ringsystem funcationalities and unit tests --- INCHI-1-SRC/INCHI_BASE/src/ichimake.c | 31 +- INCHI-1-SRC/INCHI_BASE/src/inpdef.h | 6 +- INCHI-1-SRC/INCHI_BASE/src/ring_detection.c | 288 +++++++---- INCHI-1-SRC/INCHI_BASE/src/ring_detection.h | 23 +- INCHI-1-SRC/INCHI_BASE/src/strutil.c | 37 +- .../tests/test_unit/test_atropisomers.cpp | 450 +++++++++++++++--- 6 files changed, 660 insertions(+), 175 deletions(-) diff --git a/INCHI-1-SRC/INCHI_BASE/src/ichimake.c b/INCHI-1-SRC/INCHI_BASE/src/ichimake.c index b8e86825..aae3114e 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/ichimake.c +++ b/INCHI-1-SRC/INCHI_BASE/src/ichimake.c @@ -3905,11 +3905,32 @@ int Create_INChI(CANON_GLOBALS* pCG, MarkRingSystemsInp(out_at, num_atoms, 0); if (ip->Atropisomers) { - RingResult *ring_result = find_rings(out_at, num_atoms); + RingSystems *ring_result = find_rings(out_at, num_atoms); - print_ring_result(ring_result); + // print_ring_result(ring_result); - free_ring_result(ring_result); + for (i = 0; i < num_atoms; i++) { + out_at[i].fused_partner_atom_id = -1; + } + for (i = 0; i < num_atoms; i++) { + for (int j = i + 1; j < num_atoms; j++) { + // printf("%d %d\n", i, j); + if(is_fused_ring_pivot(ring_result, out_at, i, j)) { + // printf(">>> Found pivot atom pair: %d, %d\n", i, j); + out_at[i].fused_partner_atom_id = j; + out_at[j].fused_partner_atom_id = i; + } + } + } + + if (ring_result != NULL) { + // orig_inp_data->ring_id_to_size = (int*)inchi_calloc(ring_result->count, sizeof(int)); + for (i = 0; i < ring_result->count; i++) { + orig_inp_data->ring_id_to_size[i] = ring_result->rings[i].size; + } + } + + free_ring_system(ring_result); for (i = 0; i < num_atoms; i++) { if (out_at[i].ring_count > 0) { @@ -3924,8 +3945,10 @@ int Create_INChI(CANON_GLOBALS* pCG, orig_inp_data->at[i].bond_stereo[j] = out_at[i].bond_stereo[j]; // printf("%d %d\n", i, out_at[i].bond_stereo[j]); } - } + // if (out_at[i].fused_partner_atom_id > -1) { + orig_inp_data->at[i].fused_partner_atom_id = out_at[i].fused_partner_atom_id; + } } diff --git a/INCHI-1-SRC/INCHI_BASE/src/inpdef.h b/INCHI-1-SRC/INCHI_BASE/src/inpdef.h index ac6062a9..e4d47bf4 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/inpdef.h +++ b/INCHI-1-SRC/INCHI_BASE/src/inpdef.h @@ -101,7 +101,7 @@ typedef S_SHORT ST_CAP_FLOW; #define SB_PARITY_2(X) (((X) >> SB_PARITY_SHFT) & SB_PARITY_MASK) /**< refers to connected structure */ #define RS_MAX_RINGS_PER_ATOM 10 - +#define RS_MAX_NOF_RINGS 1000 /** * @brief Structure describing an input atom @@ -196,6 +196,8 @@ typedef struct tagInputAtom int ring_ids[RS_MAX_RINGS_PER_ATOM]; int ring_count; + int fused_partner_atom_id; + #if (FIND_RINS_SYSTEMS_DISTANCES == 1) AT_NUMB nDistanceFromTerminal; /* terminal atom or ring system has 1, next has 2, etc. */ #endif @@ -467,6 +469,8 @@ typedef struct tagOrigAtom int valid_polymer; int n_zy; /* number of non-polymeric pseudoatoms (Zy) */ + int ring_id_to_size[RS_MAX_NOF_RINGS]; + } ORIG_ATOM_DATA; /** diff --git a/INCHI-1-SRC/INCHI_BASE/src/ring_detection.c b/INCHI-1-SRC/INCHI_BASE/src/ring_detection.c index e8fdf478..7d40f2d2 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/ring_detection.c +++ b/INCHI-1-SRC/INCHI_BASE/src/ring_detection.c @@ -25,25 +25,26 @@ int is_subset(Ring* child, return 1; } -void determine_ring_hierarchy(RingResult* rr) { - for (int i = 0; i < rr->count; i++) { - rr->rings[i].parent_id = -1; +void determine_ring_hierarchy(RingSystems* rs) { + for (int i = 0; i < rs->count; i++) { + rs->rings[i].parent_id = -1; int smallest_parent_size = 1e6; - for (int j = 0; j < rr->count; j++) { + for (int j = 0; j < rs->count; j++) { if (i == j) { continue; } - if (is_subset(&rr->rings[i], &rr->rings[j])) { - if (rr->rings[j].size < smallest_parent_size) { - rr->rings[i].parent_id = rr->rings[j].id; - smallest_parent_size = rr->rings[j].size; - rr->rings[j].child_ids = (int*)inchi_realloc(rr->rings[j].child_ids, (rr->rings[j].child_count + 1) * sizeof(int)); - rr->rings[j].child_ids[rr->rings[j].child_count] = rr->rings[i].id; - rr->rings[j].child_count++; - } + if (is_subset(&rs->rings[i], &rs->rings[j])) { + // if (rs->rings[j].size < smallest_parent_size) { + rs->rings[i].parent_id = rs->rings[j].id; + // smallest_parent_size = rs->rings[j].size; + + rs->rings[j].child_ids = (int*)inchi_realloc(rs->rings[j].child_ids, (rs->rings[j].child_count + 1) * sizeof(int)); + rs->rings[j].child_ids[rs->rings[j].child_count] = rs->rings[i].id; + rs->rings[j].child_count++; + // } } } } @@ -74,122 +75,165 @@ int get_number_of_overlapping_rings(const Ring *r1, const Ring *r2) { return count; } -int get_number_of_common_rings(const inp_ATOM *atoms, - int num_atoms, - int atom1, - int atom2) { +int get_number_of_atomic_rings_from_atom(const RingSystems *rs, + int atom_id) { - if (atoms == NULL || num_atoms <= 0) { + if (atom_id < 0) { return 0; // Invalid input } - if (atom1 >= num_atoms || atom2 >= num_atoms) { - return 0; // Invalid atom indices - } int count = 0; - for (int i = 0; i < atoms[atom1].ring_count; i++) { - int ring_id1 = atoms[atom1].ring_ids[i]; - for (int j = 0; j < atoms[atom2].ring_count; j++) { - int ring_id2 = atoms[atom2].ring_ids[j]; - if (ring_id1 == ring_id2) { - count++; + for (int i = 0; i < rs->count; i++) { + const Ring *cur_ring = &rs->rings[i]; + if (cur_ring->child_count == 0) { + for (int j = 0; j < cur_ring->size; j++) { + if (cur_ring->atom_ids[j] == atom_id) { + count++; + } } } } return count; } -void print_ring_result(const RingResult *rr) { - - printf("Number of rings: %d\n", rr->count); - - for (int i = 0; i < rr->count; i++) { - const Ring *r = &rr->rings[i]; - printf("Ring ID: %2d, Size: %2d, nof fused ring %2d, parent %2d, Atoms: ", - r->id, r->size, r->nof_unique_fused_ring, r->parent_id); - for (int j = 0; j < r->size; j++) { - printf("%d ", r->atom_ids[j]); +int is_atom_in_ring(const Ring *r, int atom_id) { + for (int i = 0; i < r->size; i++) { + if (r->atom_ids[i] == atom_id) { + return 1; } - printf("\n"); - printf(" Child Ring IDs: "); - for(int j = 0; j < r->child_count; j++) { - printf(" %2d ", r->child_ids[j]); + } + return 0; +} + +int are_atoms_in_same_small_ring(const inp_ATOM* atoms, + const int *ring_id_to_size, + int atom_id1, int atom_id2, + int max_ring_size) { + + inp_ATOM atom1 = atoms[atom_id1]; + inp_ATOM atom2 = atoms[atom_id2]; + + for (int i = 0; i < atom1.ring_count; i++) { + int ring_id1 = atom1.ring_ids[i]; + if (ring_id_to_size[ring_id1] <= max_ring_size) { + for (int j = 0; j < atom2.ring_count; j++) { + int ring_id2 = atom2.ring_ids[j]; + if (ring_id1 == ring_id2) { + return 1; + } + } } - printf("\n"); } + return 0; } -void free_ring_result(RingResult *rr) { +void print_ring(const Ring *r) { + printf("Ring ID: %d, Size: %d, nof fused ring %d, parent %d, Atoms: ", + r->id, r->size, r->nof_atomic_rings, r->parent_id); + for (int i = 0; i < r->size; i++) { + printf("%d ", r->atom_ids[i]); + } + printf("\n"); + printf(" Child Ring IDs: "); + for(int i = 0; i < r->child_count; i++) { + printf(" %d ", r->child_ids[i]); + } + printf("\n"); +} + +void print_ring_result(const RingSystems *rs) { + + printf("Number of rings: %d\n", rs->count); + + for (int i = 0; i < rs->count; i++) { + const Ring *r = &rs->rings[i]; + print_ring(r); + } +} + +void free_ring_system(RingSystems *rs) { - if (rr == NULL) { + if (rs == NULL) { return; } - for (int i = 0; i < rr->count; i++) { - inchi_free(rr->rings[i].atom_ids); - inchi_free(rr->rings[i].child_ids); + for (int i = 0; i < rs->count; i++) { + inchi_free(rs->rings[i].atom_ids); + inchi_free(rs->rings[i].child_ids); } - inchi_free(rr->rings); - inchi_free(rr); + inchi_free(rs->rings); + inchi_free(rs); } -void determine_fused_rings(RingResult* rr) { +int sub_ring_counter(RingSystems* rs, const Ring *r, int *ring_counter) { + if (r->child_count == 0) { + ring_counter[r->id] = 1; + } else { + for (int i = 0; i < r->child_count; i++) { + sub_ring_counter(rs, &rs->rings[r->child_ids[i]], ring_counter); + } + } +} - for (int nof_rings = 0; nof_rings < 100; nof_rings++) { +void determine_fused_rings(RingSystems* rs) { - for (int i = 0; i < rr->count; i++) { - Ring *r1 = &rr->rings[i]; + for (int i = 0; i < rs->count; i++) { + Ring *cur_ring = &rs->rings[i]; - if (nof_rings == r1->child_count) { - if (r1->child_count == 0) { - r1->nof_unique_fused_ring = 1; - } - if (r1->parent_id != -1) { - Ring *parent = &rr->rings[r1->parent_id]; - parent->nof_unique_fused_ring+=r1->nof_unique_fused_ring; - } + int ring_counter[rs->count]; + for (int j = 0; j < rs->count; j++) { + ring_counter[j] = 0; + } + sub_ring_counter(rs, cur_ring, ring_counter); + + int count = 0; + for (int j = 0; j < rs->count; j++) { + if (ring_counter[j] > 0) { + count++; } } + + cur_ring->nof_atomic_rings = count; } } -void *create_new_ring(RingResult *rr, +void *create_new_ring(RingSystems *rs, inp_ATOM *atoms, int *path, int path_len) { - // Ring *r = &rr->rings[rr->count]; //(Ring*)inchi_calloc(1, sizeof(Ring)); - rr->rings = (Ring*)inchi_realloc(rr->rings, (rr->count + 1) * sizeof(Ring)); - Ring *r = &rr->rings[rr->count]; + // Ring *r = &rs->rings[rs->count]; //(Ring*)inchi_calloc(1, sizeof(Ring)); + rs->rings = (Ring*)inchi_realloc(rs->rings, (rs->count + 1) * sizeof(Ring)); + Ring *r = &rs->rings[rs->count]; - r->id = rr->count; + r->id = rs->count; r->size = path_len; r->parent_id = -1; r->child_count = 0; r->child_ids = NULL; - r->nof_unique_fused_ring = 0; + r->nof_atomic_rings = 0; r->atom_ids = (int*)inchi_calloc(path_len, sizeof(int)); for (int i = 0; i < path_len; i++) { r->atom_ids[i] = path[i]; - atoms[path[i]].ring_ids[atoms[path[i]].ring_count] = rr->count; + atoms[path[i]].ring_ids[atoms[path[i]].ring_count] = rs->count; atoms[path[i]].ring_count++; } - rr->rings[rr->count] = *r; - rr->count++; + rs->rings[rs->count] = *r; + rs->count++; } -int is_new_ring(RingResult *rr, +int is_new_ring(RingSystems *rs, int *path, int path_len) { - for (int i = 0; i < rr->count; ++i) { - if (rr->rings[i].size != path_len) continue; + for (int i = 0; i < rs->count; ++i) { + if (rs->rings[i].size != path_len) continue; int match = 1; for (int j = 0; j < path_len; ++j) { int found = 0; for (int k = 0; k < path_len; ++k) { - if (rr->rings[i].atom_ids[k] == path[j]) { + if (rs->rings[i].atom_ids[k] == path[j]) { found = 1; break; } @@ -204,7 +248,7 @@ int is_new_ring(RingResult *rr, return 1; } -void dfs(RingResult *rr, +void dfs(RingSystems *rs, inp_ATOM* atoms, int **adj, int num_atoms, @@ -221,14 +265,14 @@ void dfs(RingResult *rr, for (int i = 0; i < num_atoms; ++i) { if (adj[curr][i]) { if (i == start && path_len > 2) { - if (is_new_ring(rr, path, path_len)) { + if (is_new_ring(rs, path, path_len)) { - create_new_ring(rr, atoms, path, path_len); + create_new_ring(rs, atoms, path, path_len); } } else if (!visited[i]) { // dfs(start, i, visited, path, path_len); - dfs(rr, atoms, adj, num_atoms, start, i, visited, path, path_len); + dfs(rs, atoms, adj, num_atoms, start, i, visited, path, path_len); } } } @@ -237,16 +281,88 @@ void dfs(RingResult *rr, } -RingResult *find_rings(inp_ATOM* atoms, - int num_atoms) { +int is_fused_ring_pivot(const RingSystems *rs, + const inp_ATOM * atoms, + int atom_id1, int atom_id2) { + + if (atoms == NULL || rs == NULL || atom_id1 < 0 || atom_id2 < 0) { + return 0; // Invalid input + } + + int are_neighbours = 0; + + const inp_ATOM *atom1 = &atoms[atom_id1]; + const inp_ATOM *atom2 = &atoms[atom_id2]; + + if (atom1 == NULL || atom2 == NULL) { + return 0; // Invalid input + } + + if (atom1->valence <= 2 || atom2->valence <= 2) { + return 0; + } + + for (int i = 0; i < atom1->valence; i++) { + if (atom1->neighbor[i] == atom_id2) { + are_neighbours = 1; + break; + } + } + if (get_number_of_atomic_rings_from_atom(rs, atom_id1) < 2 && + get_number_of_atomic_rings_from_atom(rs, atom_id2) < 2) { + return 0; + } + + if (are_neighbours) { + for (int i = 0; i < rs->count; i++) { + const Ring *r = &rs->rings[i]; + int found_atom1 = 0; + int found_atom2 = 0; + for (int j = 0; j < r->size; j++) { + if (r->atom_ids[j] == atom_id1) { + found_atom1 = 1; + } + if (r->atom_ids[j] == atom_id2) { + found_atom2 = 1; + } + } + if (found_atom1 && found_atom2) { + if (r->nof_atomic_rings == 2) { + // printf("atom1 %d atom2 %d ring id %d nof atomic rings %d\n", + // atom_id1, atom_id2, r->id, r->nof_atomic_rings); + // print_ring(r); + int count = 0; + for (int j = 0; j < r->child_count; j++) { + const Ring *child_ring = &rs->rings[r->child_ids[j]]; + if (is_atom_in_ring(child_ring, atom_id1) && is_atom_in_ring(child_ring, atom_id2)) { + count++; + } + } + if (count == r->child_count) { + // printf("atom1 %d atom2 %d ring id %d nof atomic rings %d\n", + // atom_id1, atom_id2, r->id, r->nof_atomic_rings); + // print_ring(r); + // printf(">>> Found pivot atom pair: %d, %d\n", atom_id1, atom_id2); + return 1; + } + } + } + } + } + + return 0; +} + +RingSystems *find_rings(inp_ATOM* atoms, + int num_atoms) { if (atoms == NULL || num_atoms <= 0) { return NULL; // Invalid input } - RingResult *rr = (RingResult*)inchi_calloc(1, sizeof(RingResult)); - rr->rings = NULL; //(Ring*)inchi_calloc(num_atoms * 10, sizeof(Ring)); - rr->count = 0; + RingSystems *rs = (RingSystems*)inchi_calloc(1, sizeof(RingSystems)); + rs->rings = NULL; //(Ring*)inchi_calloc(num_atoms * 10, sizeof(Ring)); + rs->count = 0; int visited[num_atoms]; int path[num_atoms]; @@ -266,20 +382,20 @@ RingResult *find_rings(inp_ATOM* atoms, } for (int i = 0; i < num_atoms; ++i) { - dfs(rr, atoms, adj, num_atoms, i, i, visited, path, 0); + dfs(rs, atoms, adj, num_atoms, i, i, visited, path, 0); } - determine_ring_hierarchy(rr); + determine_ring_hierarchy(rs); - determine_fused_rings(rr); + determine_fused_rings(rs); for (int i = 0; i < num_atoms; ++i) { inchi_free(adj[i]); } inchi_free(adj); - return rr; + return rs; } diff --git a/INCHI-1-SRC/INCHI_BASE/src/ring_detection.h b/INCHI-1-SRC/INCHI_BASE/src/ring_detection.h index f6bde465..4330a657 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/ring_detection.h +++ b/INCHI-1-SRC/INCHI_BASE/src/ring_detection.h @@ -1,10 +1,9 @@ - typedef struct Ring { int id; int *atom_ids; int size; - int nof_unique_fused_ring; + int nof_atomic_rings; int parent_id; // Added: ID of the larger ring containing this one int *child_ids; int child_count; @@ -13,16 +12,20 @@ typedef struct Ring { typedef struct { Ring* rings; int count; -} RingResult; +} RingSystems; + +RingSystems *find_rings(inp_ATOM* atoms, int num_atoms); -RingResult *find_rings(inp_ATOM* atoms, int num_atoms); +int is_fused_ring_pivot(const RingSystems *rs, + const inp_ATOM * atoms, + int atom_id1, int atom_id2); -int get_number_of_common_rings(const inp_ATOM* atoms, - int num_atoms, - int atom1, - int atom2); +void print_ring_result(const RingSystems *rs); -void print_ring_result(const RingResult *rr); +void free_ring_system(RingSystems *rs); -void free_ring_result(RingResult *rr); +int are_atoms_in_same_small_ring(const inp_ATOM* atoms, + const int *ring_id_to_size, + int atom_id1, int atom_id2, + int max_ring_size); diff --git a/INCHI-1-SRC/INCHI_BASE/src/strutil.c b/INCHI-1-SRC/INCHI_BASE/src/strutil.c index e50b91e3..c4d9d056 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/strutil.c +++ b/INCHI-1-SRC/INCHI_BASE/src/strutil.c @@ -7188,12 +7188,26 @@ int set_Atropisomer_t_m_layers( const ORIG_ATOM_DATA *orig_inp_data, const inp_ATOM atom_i = orig_inp_data->at[i]; + // if (atom_i.is_fused_pivot_atom == 0) { + // continue; + // } + int num_neighbors_i = atom_i.valence; const AT_NUMB *neighbors = atom_i.neighbor; // printf("Atom %d with %d neighbors; ring system id %d; num atoms in ring %d\n", i + 1, num_neighbors, orig_inp_data->at[i].nRingSystem, orig_inp_data->at[i].nNumAtInRingSystem); if (num_neighbors_i == 3) { for (int j = 0; j < num_neighbors_i; j++) { + + if (i >= neighbors[j]) { + continue; + } + const inp_ATOM atom_j = orig_inp_data->at[neighbors[j]]; + + // if (atom_j.is_fused_pivot_atom == 0) { + // continue; + // } + int num_neighbors_j = atom_j.valence; if (num_neighbors_j == 3 && @@ -7232,12 +7246,23 @@ int set_Atropisomer_t_m_layers( const ORIG_ATOM_DATA *orig_inp_data, nof_wedge_bonds_j > 0) { if ((has_double_bond_i || has_double_bond_j)) { - printf("Atropisomer candidate: atom %d with atom %d; bond type %d; bond stereo %d\n", - i + 1, neighbors[j] + 1, atom_i.bond_type[j], atom_i.bond_stereo[j]); - printf("atom type %d %d\n", atom_i.el_number, atom_j.el_number); - printf("has double bond %d %d\n", has_double_bond_i, has_double_bond_j); - printf("nof shared rings %d\n", get_number_of_common_rings(orig_inp_data->at, orig_inp_data->num_inp_atoms, i, neighbors[j])); - + if ((atom_i.fused_partner_atom_id != j && + atom_j.fused_partner_atom_id != i) || + (atom_i.fused_partner_atom_id == -1 || + atom_j.fused_partner_atom_id == -1)) { + + if (are_atoms_in_same_small_ring(orig_inp_data->at, + orig_inp_data->ring_id_to_size, + i, neighbors[j], + 6) == 0) { + printf(">>> is atropisomer\n"); + printf("infos: atom %d with atom %d; bond type %d; bond stereo %d\n", + i, neighbors[j], atom_i.bond_type[j], atom_i.bond_stereo[j]); + printf("atom type %d %d\n", atom_i.el_number, atom_j.el_number); + printf("has double bond %d %d\n", has_double_bond_i, has_double_bond_j); + printf("fused pivot atoms %d %d\n", atom_i.fused_partner_atom_id, atom_j.fused_partner_atom_id); + } + } } } } diff --git a/INCHI-1-TEST/tests/test_unit/test_atropisomers.cpp b/INCHI-1-TEST/tests/test_unit/test_atropisomers.cpp index cd1916b0..d238aba4 100644 --- a/INCHI-1-TEST/tests/test_unit/test_atropisomers.cpp +++ b/INCHI-1-TEST/tests/test_unit/test_atropisomers.cpp @@ -201,76 +201,75 @@ TEST(test_atropisomers, test_dummy_3) FreeINCHI(poutput); } -TEST(test_atropisomers, test_dummy_4) +TEST(test_atropisomers, test_dummy_4_atypical_no_2_rings) { const char *molblock = - "atropisomer test mol \n" - " Ketcher 2202610562D 1 1.00000 0.00000 0 \n" - " \n" - " 24 25 0 0 1 0 0 0 0 0999 V2000 \n" - " 9.1250 -2.5500 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" - " 9.9910 -3.0500 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" - " 9.9910 -4.0500 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" - " 9.1250 -4.5500 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" - " 8.2590 -4.0500 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" - " 8.2590 -3.0500 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" - " 7.1288 -7.8500 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" - " 6.2598 -7.3550 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" - " 7.9902 -7.3505 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" - " 7.1266 -5.8500 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" - " 7.9902 -6.3496 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" - " 6.2598 -6.3501 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" - " 7.1272 -4.8500 0.0000 N 0 0 0 0 0 0 0 0 0 0 0 0 \n" - " 6.2612 -4.3500 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" - " 6.2612 -3.3500 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0 \n" - " 5.3952 -4.8500 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" - " 4.5292 -4.3500 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" - " 5.3939 -5.8499 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" - " 8.8564 -5.8500 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" - " 8.8564 -4.8500 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0 \n" - " 9.7224 -6.3500 0.0000 N 0 0 0 0 0 0 0 0 0 0 0 0 \n" - " 4.8939 -6.7160 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" - " 5.8939 -4.9839 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" - " 4.5279 -5.3499 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" - " 2 1 1 0 0 0 \n" - " 3 2 1 0 0 0 \n" - " 4 3 1 0 0 0 \n" - " 5 4 1 0 0 0 \n" - " 6 5 1 0 0 0 \n" - " 1 6 1 0 0 0 \n" - " 11 10 2 0 0 0 \n" - " 9 11 1 0 0 0 \n" - " 7 9 2 0 0 0 \n" - " 8 7 1 0 0 0 \n" - " 12 8 2 0 0 0 \n" - " 10 12 1 1 0 0 \n" - " 10 13 1 0 0 0 \n" - " 13 5 1 0 0 0 \n" - " 13 14 1 6 0 0 \n" - " 14 15 2 0 0 0 \n" - " 14 16 1 0 0 0 \n" - " 16 17 2 0 0 0 \n" - " 12 18 1 0 0 0 \n" - " 11 19 1 0 0 0 \n" - " 19 20 2 0 0 0 0 \n" - " 19 21 1 0 0 0 0 \n" - " 22 18 1 0 0 0 0 \n" - " 18 23 1 0 0 0 0 \n" - " 18 24 1 0 0 0 0 \n" - "M STY 1 1 SUP \n" - "M SLB 1 1 1 \n" - "M SAP 1 1 19 0 \n" - "M SAL 1 3 19 20 21 \n" - "M SBL 1 1 20 \n" - "M SMT 1 CONH2 \n" - "M STY 1 2 SUP \n" - "M SLB 1 2 2 \n" - "M SAP 2 1 18 0 \n" - "M SAL 2 4 18 22 23 24 \n" - "M SBL 2 1 19 \n" - "M SMT 2 tBu \n" - "M END \n"; - + "atropisomer atypical no 2 rings \n" + " Ketcher 2272612 82D 1 1.00000 0.00000 0 \n" + " \n" + " 24 25 0 0 1 0 0 0 0 0999 V2000 \n" + " 6.7791 -6.8681 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 8.4871 -6.8676 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 7.6347 -6.3744 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 8.4871 -7.8557 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 6.7791 -7.8601 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 7.6369 -8.3488 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 8.8265 -3.8573 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 8.8265 -4.8444 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 9.6814 -5.3380 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 10.5363 -4.8444 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 10.5363 -3.8573 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 9.6814 -3.3637 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 7.6353 -5.3873 0.0000 N 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 6.8053 -4.8531 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 6.8529 -3.8671 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 5.9276 -5.3048 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 5.0975 -4.7705 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 9.3422 -6.3745 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 5.9243 -6.3744 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 9.3422 -5.3874 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 10.1971 -6.8680 0.0000 N 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 5.4307 -7.2293 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 6.4179 -5.5196 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 5.0694 -5.8809 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 3 1 1 1 0 0 \n" + " 1 5 2 0 0 0 \n" + " 5 6 1 0 0 0 \n" + " 6 4 2 0 0 0 \n" + " 4 2 1 0 0 0 \n" + " 2 3 2 0 0 0 \n" + " 12 7 1 0 0 0 \n" + " 7 8 1 0 0 0 \n" + " 8 9 1 0 0 0 \n" + " 9 10 1 0 0 0 \n" + " 10 11 1 0 0 0 \n" + " 11 12 1 0 0 0 \n" + " 3 13 1 0 0 0 \n" + " 13 8 1 0 0 0 \n" + " 13 14 1 6 0 0 \n" + " 14 15 2 0 0 0 \n" + " 14 16 1 0 0 0 \n" + " 16 17 2 0 0 0 \n" + " 2 18 1 0 0 0 \n" + " 1 19 1 0 0 0 \n" + " 18 20 2 0 0 0 \n" + " 18 21 1 0 0 0 \n" + " 22 19 1 0 0 0 \n" + " 19 23 1 0 0 0 \n" + " 19 24 1 0 0 0 \n" + "M STY 1 1 SUP \n" + "M SLB 1 1 1 \n" + "M SAP 1 1 18 0 \n" + "M SAL 1 3 18 20 21 \n" + "M SBL 1 1 19 \n" + "M SMT 1 CONH2 \n" + "M STY 1 2 SUP \n" + "M SLB 1 2 2 \n" + "M SAP 2 1 19 0 \n" + "M SAL 2 4 19 22 23 24 \n" + "M SBL 2 1 20 \n" + "M SMT 2 tBu \n" + "M END \n"; char options[] = "-Atropisomers"; inchi_Output output; @@ -525,4 +524,319 @@ TEST(test_atropisomers, test_dummy_7_no_atropisomer_1) FreeINCHI(poutput); } +TEST(test_atropisomers, test_dummy_8_no_atropisomer) +{ + const char *molblock = + "no atropisomer \n" + " Ketcher 2272613152D 1 1.00000 0.00000 0 \n" + " \n" + " 19 22 0 0 1 0 0 0 0 0999 V2000 \n" + " 1.7386 -5.4033 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 3.4728 -5.4028 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 2.6073 -4.9019 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 3.4728 -6.4059 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 1.7386 -6.4105 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 2.6095 -6.9066 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 4.3393 -4.9032 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 5.2084 -5.4047 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 4.3455 -6.9095 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 5.2106 -6.4028 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 6.0695 -4.9058 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 6.9354 -5.4029 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 6.0800 -6.9019 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 6.9398 -6.3960 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 4.3470 -3.9142 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 6.0676 -3.9065 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 5.2026 -3.4159 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 4.3487 -7.9117 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 6.0854 -7.9042 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 3 1 2 0 0 0 \n" + " 1 5 1 0 0 0 \n" + " 5 6 2 0 0 0 \n" + " 6 4 1 0 0 0 \n" + " 4 2 2 0 0 0 \n" + " 2 3 1 0 0 0 \n" + " 4 9 1 0 0 0 \n" + " 9 10 2 0 0 0 \n" + " 10 8 1 0 0 0 \n" + " 8 7 2 0 0 0 \n" + " 7 2 1 0 0 0 \n" + " 10 13 1 1 0 0 \n" + " 13 14 2 0 0 0 \n" + " 14 12 1 0 0 0 \n" + " 12 11 2 0 0 0 \n" + " 11 16 1 0 0 0 \n" + " 16 17 2 0 0 0 \n" + " 17 15 1 0 0 0 \n" + " 15 7 1 0 0 0 \n" + " 9 18 1 0 0 0 \n" + " 13 19 1 0 0 0 \n" + " 8 11 1 6 0 0 \n" + "M END \n"; + + char options[] = "-Atropisomers"; + inchi_Output output; + inchi_Output *poutput = &output; + const char expected_inchi[] = "InChI=1B"; + + EXPECT_EQ(MakeINCHIFromMolfileText(molblock, options, poutput), 1); + EXPECT_STREQ(poutput->szInChI, expected_inchi); + + FreeINCHI(poutput); +} + +TEST(test_atropisomers, test_dummy_9_no_atropisomer) +{ + const char *molblock = + "no atropisomer \n" + " Ketcher 2272613262D 1 1.00000 0.00000 0 \n" + " \n" + " 11 12 0 0 1 0 0 0 0 0999 V2000 \n" + " 10.0575 -5.5286 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 11.7917 -5.5281 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 10.9262 -5.0272 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 11.7917 -6.5312 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 10.0575 -6.5357 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 10.9284 -7.0319 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 13.3281 -6.0349 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 12.7485 -5.2267 0.0000 N 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 12.7368 -6.8411 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 10.9323 -8.0341 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 13.0431 -7.7954 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 3 1 1 0 0 0 \n" + " 1 5 2 0 0 0 \n" + " 5 6 1 0 0 0 \n" + " 6 4 2 0 0 0 \n" + " 4 2 1 0 0 0 \n" + " 2 3 1 1 0 0 \n" + " 4 9 1 1 0 0 \n" + " 9 7 2 0 0 0 \n" + " 7 8 1 0 0 0 \n" + " 8 2 2 0 0 0 \n" + " 6 10 1 0 0 0 \n" + " 9 11 1 0 0 0 \n" + "M END \n"; + + char options[] = "-Atropisomers"; + inchi_Output output; + inchi_Output *poutput = &output; + const char expected_inchi[] = "InChI=1B"; + + EXPECT_EQ(MakeINCHIFromMolfileText(molblock, options, poutput), 1); + EXPECT_STREQ(poutput->szInChI, expected_inchi); + + FreeINCHI(poutput); +} + +TEST(test_atropisomers, test_dummy_10_no_atropisomer) +{ + const char *molblock = + "no atropisomer \n" + " Ketcher 2272613352D 1 1.00000 0.00000 0 \n" + " \n" + " 13 15 0 0 1 0 0 0 0 0999 V2000 \n" + " 6.1045 -3.7191 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 7.8417 -4.0800 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 7.0986 -3.3949 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 7.5866 -5.0469 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 5.8981 -4.7379 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 6.6718 -5.3556 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 5.9176 -6.9529 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 7.5935 -6.6481 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 6.6749 -6.3496 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 7.8562 -7.5835 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 6.1260 -7.9570 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 7.1229 -8.2742 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 8.2186 -5.8385 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 3 1 2 0 0 0 \n" + " 1 5 1 0 0 0 \n" + " 5 6 2 0 0 0 \n" + " 6 4 1 1 0 0 \n" + " 4 2 2 0 0 0 \n" + " 9 7 1 1 0 0 \n" + " 7 11 2 0 0 0 \n" + " 12 10 2 0 0 0 \n" + " 10 8 1 0 0 0 \n" + " 8 9 2 0 0 0 \n" + " 6 9 1 0 0 0 \n" + " 4 13 1 0 0 0 \n" + " 13 8 1 0 0 0 \n" + " 3 2 1 1 0 0 \n" + " 12 11 1 1 0 0 \n" + "M END \n"; + + char options[] = "-Atropisomers"; + inchi_Output output; + inchi_Output *poutput = &output; + const char expected_inchi[] = "InChI=1B"; + + EXPECT_EQ(MakeINCHIFromMolfileText(molblock, options, poutput), 1); + EXPECT_STREQ(poutput->szInChI, expected_inchi); + FreeINCHI(poutput); +} + +TEST(test_atropisomers, test_dummy_11_no_atropisomer_3_fragments) +{ + const char *molblock = + "no atropisomers 3 fragments \n" + " Ketcher 2272613322D 1 1.00000 0.00000 0 \n" + " \n" + " 43 50 0 0 1 0 0 0 0 0999 V2000 \n" + " 6.1045 -3.7191 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 7.8417 -4.0800 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 7.0986 -3.3949 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 7.5866 -5.0469 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 5.8981 -4.7379 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 6.6718 -5.3556 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 5.9176 -6.9529 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 7.5935 -6.6481 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 6.6749 -6.3496 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 7.8562 -7.5835 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 6.1260 -7.9570 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 7.1229 -8.2742 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 8.2186 -5.8385 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 9.5037 -3.9296 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 11.2375 -3.9224 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 10.3782 -3.4263 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 11.2430 -4.9363 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 9.5000 -4.9492 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 10.3729 -5.4522 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 9.5486 -6.9885 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 11.2820 -6.9498 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 10.3915 -6.4693 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 11.3184 -7.9397 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 9.5860 -7.9925 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 10.4799 -8.4673 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 12.1187 -5.4213 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 12.1416 -6.4195 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 14.4400 -3.7870 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 16.1721 -3.9293 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 15.3591 -3.3591 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 16.0862 -4.9426 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 14.3481 -4.8068 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 15.1682 -5.3798 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 14.1926 -6.8106 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 15.9249 -6.9467 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 15.0897 -6.3821 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 15.8590 -7.9312 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 14.1208 -7.8014 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 14.9645 -8.3690 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 16.9116 -5.5047 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 16.8379 -6.5024 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 13.4463 -5.2285 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 13.3619 -6.2344 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 3 1 2 0 0 0 \n" + " 1 5 1 0 0 0 \n" + " 5 6 2 0 0 0 \n" + " 6 4 1 1 0 0 \n" + " 4 2 2 0 0 0 \n" + " 9 7 1 1 0 0 \n" + " 7 11 2 0 0 0 \n" + " 12 10 2 0 0 0 \n" + " 10 8 1 0 0 0 \n" + " 8 9 2 0 0 0 \n" + " 6 9 1 0 0 0 \n" + " 4 13 1 0 0 0 \n" + " 13 8 1 0 0 0 \n" + " 16 14 2 0 0 0 \n" + " 14 18 1 0 0 0 \n" + " 18 19 2 0 0 0 \n" + " 19 17 1 1 0 0 \n" + " 17 15 2 0 0 0 \n" + " 22 20 1 1 0 0 \n" + " 20 24 2 0 0 0 \n" + " 25 23 2 0 0 0 \n" + " 23 21 1 0 0 0 \n" + " 21 22 2 0 0 0 \n" + " 19 22 1 0 0 0 \n" + " 17 26 1 0 0 0 \n" + " 26 27 2 0 0 0 \n" + " 27 21 1 0 0 0 \n" + " 30 28 2 0 0 0 \n" + " 28 32 1 0 0 0 \n" + " 32 33 2 0 0 0 \n" + " 33 31 1 1 0 0 \n" + " 31 29 2 0 0 0 \n" + " 36 34 1 1 0 0 \n" + " 34 38 2 0 0 0 \n" + " 39 37 2 0 0 0 \n" + " 37 35 1 0 0 0 \n" + " 35 36 2 0 0 0 \n" + " 33 36 1 0 0 0 \n" + " 31 40 1 0 0 0 \n" + " 35 41 1 0 0 0 \n" + " 41 40 2 0 0 0 \n" + " 32 42 1 0 0 0 \n" + " 42 43 2 0 0 0 \n" + " 43 34 1 0 0 0 \n" + " 3 2 1 1 0 0 \n" + " 12 11 1 1 0 0 \n" + " 16 15 1 1 0 0 \n" + " 25 24 1 1 0 0 \n" + " 30 29 1 1 0 0 \n" + " 39 38 1 1 0 0 \n" + "M END \n"; + + char options[] = "-Atropisomers"; + inchi_Output output; + inchi_Output *poutput = &output; + const char expected_inchi[] = "InChI=1B"; + + EXPECT_EQ(MakeINCHIFromMolfileText(molblock, options, poutput), 1); + EXPECT_STREQ(poutput->szInChI, expected_inchi); + + FreeINCHI(poutput); +} + +TEST(test_atropisomers, test_dummy_12_atropisomer) +{ + const char *molblock = + "atropisomer test mol \n" + " Ketcher 2272615132D 1 1.00000 0.00000 0 \n" + " \n" + " 15 17 0 0 1 0 0 0 0 0999 V2000 \n" + " 5.4357 -3.2991 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 7.1745 -3.2986 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 6.3067 -2.7965 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 7.1745 -4.3043 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 5.4357 -4.3088 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 6.3090 -4.8062 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 5.4357 -6.6150 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 7.1745 -6.6145 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 6.3067 -6.1125 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 7.1745 -7.6203 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 5.4357 -7.6248 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 6.3090 -8.1222 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 8.0449 -6.1125 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 8.0452 -4.8059 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 8.2722 -5.4088 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 3 1 2 0 0 0 \n" + " 1 5 1 0 0 0 \n" + " 5 6 2 0 0 0 \n" + " 6 4 1 1 0 0 \n" + " 4 2 2 0 0 0 \n" + " 9 7 1 1 0 0 \n" + " 7 11 2 0 0 0 \n" + " 12 10 2 0 0 0 \n" + " 10 8 1 0 0 0 \n" + " 8 9 2 0 0 0 \n" + " 6 9 1 0 0 0 \n" + " 8 13 1 0 0 0 \n" + " 4 14 1 0 0 0 \n" + " 14 15 1 0 0 0 \n" + " 13 15 1 0 0 0 \n" + " 3 2 1 1 0 0 \n" + " 12 11 1 1 0 0 \n" + "M END \n"; + + char options[] = "-Atropisomers"; + inchi_Output output; + inchi_Output *poutput = &output; + const char expected_inchi[] = "InChI=1B"; + + EXPECT_EQ(MakeINCHIFromMolfileText(molblock, options, poutput), 1); + EXPECT_STREQ(poutput->szInChI, expected_inchi); + + FreeINCHI(poutput); +} From 46e4a6a3816368155c38f964270ea2576e36813e Mon Sep 17 00:00:00 2001 From: Christoph Mueller Date: Mon, 2 Mar 2026 14:14:10 +0000 Subject: [PATCH 10/11] refactored ring detection and atropisomer detection code --- .../INCHI_API/libinchi/src/CMakeLists.txt | 2 + INCHI-1-SRC/INCHI_BASE/src/atropisomers.c | 132 +++++++++++++++ INCHI-1-SRC/INCHI_BASE/src/atropisomers.h | 9 + INCHI-1-SRC/INCHI_BASE/src/ichimake.c | 43 ++--- INCHI-1-SRC/INCHI_BASE/src/inpdef.h | 12 +- INCHI-1-SRC/INCHI_BASE/src/ring_detection.c | 51 ++++-- INCHI-1-SRC/INCHI_BASE/src/ring_detection.h | 20 ++- INCHI-1-SRC/INCHI_BASE/src/strutil.c | 154 +++++------------- .../INCHI_EXE/inchi-1/src/CMakeLists.txt | 2 + .../tests/test_unit/test_atropisomers.cpp | 119 ++++++++++++-- 10 files changed, 364 insertions(+), 180 deletions(-) create mode 100644 INCHI-1-SRC/INCHI_BASE/src/atropisomers.c create mode 100644 INCHI-1-SRC/INCHI_BASE/src/atropisomers.h diff --git a/INCHI-1-SRC/INCHI_API/libinchi/src/CMakeLists.txt b/INCHI-1-SRC/INCHI_API/libinchi/src/CMakeLists.txt index 3bb4733b..45bae4c8 100644 --- a/INCHI-1-SRC/INCHI_API/libinchi/src/CMakeLists.txt +++ b/INCHI-1-SRC/INCHI_API/libinchi/src/CMakeLists.txt @@ -54,6 +54,8 @@ target_sources(libinchi PRIVATE ${P_IXA}/ixa_read_mol.c ${P_IXA}/ixa_status.c ${P_IXA}/ixa_status.h + ${P_BASE}/atropisomers.h + ${P_BASE}/atropisomers.c ${P_BASE}/bcf_s.h ${P_BASE}/bcf_s.c ${P_BASE}/extr_ct.h diff --git a/INCHI-1-SRC/INCHI_BASE/src/atropisomers.c b/INCHI-1-SRC/INCHI_BASE/src/atropisomers.c new file mode 100644 index 00000000..5a7d0c3c --- /dev/null +++ b/INCHI-1-SRC/INCHI_BASE/src/atropisomers.c @@ -0,0 +1,132 @@ + + +#include "inpdef.h" +#include "ring_detection.h" + + +void find_atropisomeric_atoms_and_bonds(inp_ATOM* out_at, + int num_atoms, + RingSystems *ring_result, + ORIG_ATOM_DATA *orig_inp_data, + int *fused_atom_partner) { + + + printf("-------------------\n"); + + for (int i = 0; i < num_atoms; i++) { + + int atom_id1 = i; + const inp_ATOM atom_i = out_at[atom_id1]; + + int num_neighbors_i = atom_i.valence; + + const AT_NUMB *neighbors = atom_i.neighbor; + + if (num_neighbors_i == 3) { + for (int j = 0; j < num_neighbors_i; j++) { + + int atom_id2 = neighbors[j]; + + if (atom_id1 >= atom_id2) { + continue; + } + + const inp_ATOM atom_j = out_at[atom_id2]; + + + + int num_neighbors_j = atom_j.valence; + + if (num_neighbors_j == 3 && + atom_i.bond_stereo[j] == 0 && + atom_i.bond_type[j] == 1) { + + if (ring_result->atom_to_ring_mapping[atom_id1].ring_count > 0 || + ring_result->atom_to_ring_mapping[atom_id2].ring_count > 0) { + + int nof_wedge_bonds_i = 0; + int has_double_bond_i = 0; + for (int k = 0; k < num_neighbors_i; k++) { + if (atom_i.bond_stereo[k] == 1 || + atom_i.bond_stereo[k] == 4 || + atom_i.bond_stereo[k] == 6) { + nof_wedge_bonds_i++; + } + if (atom_i.bond_type[k] == 2) { + has_double_bond_i = 1; + } + } + int nof_wedge_bonds_j = 0; + int has_double_bond_j = 0; + for (int k = 0; k < num_neighbors_j; k++) { + if (atom_j.bond_stereo[k] == 1 || + atom_j.bond_stereo[k] == 4 || + atom_j.bond_stereo[k] == 6) { + nof_wedge_bonds_j++; + } + if (atom_j.bond_type[k] == 2) { + has_double_bond_j = 1; + } + } + + if (nof_wedge_bonds_i > 0 || + nof_wedge_bonds_j > 0) { + + if ((has_double_bond_i || has_double_bond_j)) { + if ((fused_atom_partner[atom_id1] != j && + fused_atom_partner[atom_id2] != i) || + (fused_atom_partner[atom_id1] == -1 || + fused_atom_partner[atom_id2] == -1)) { + + if (are_atoms_in_same_small_ring(out_at, + ring_result, + atom_id1, atom_id2, + 6) == 0) { + printf(">>> is atropisomer\n"); + printf("infos: atom %d with atom %d; bond type %d; bond stereo %d\n", + atom_id1, atom_id2, atom_i.bond_type[j], atom_i.bond_stereo[j]); + // printf("atom type %d %d\n", atom_i.el_number, atom_j.el_number); + // printf("has double bond %d %d\n", has_double_bond_i, has_double_bond_j); + // printf("fused pivot atoms %d %d\n", fused_atom_partner[atom_id1], fused_atom_partner[atom_id2]); + + //TODO set atoms with atropisomeric bonds in out_at + orig_inp_data->is_atropisomer = 1; + } + } + } + } + } + } + } + } + } + + for (int i = 0; i < ring_result->count; i++) { + const Ring *cur_ring = &ring_result->rings[i]; + if (cur_ring->size >= 8) { + int count_single_bonds = 0; + int count_double_bonds = 0; + for (int j = 0; j < cur_ring->size; j++) { + int atom_id1 = cur_ring->atom_ids[j]; + int atom_id2 = cur_ring->atom_ids[(j + 1) % cur_ring->size]; + const inp_ATOM atom1 = out_at[atom_id1]; + for (int k = 0; k < atom1.valence; k++) { + if (atom1.neighbor[k] == atom_id2) { + if (atom1.bond_type[k] == 1) { + count_single_bonds++; + } else if (atom1.bond_type[k] == 2) { + count_double_bonds++; + } + } + } + } + + if (cur_ring->is_fused_ring == 0) { + printf(">>> ring id %d size %d nof single bonds %d nof double bonds %d fused ring %d\n", + cur_ring->id, cur_ring->size, + count_single_bonds, count_double_bonds, + cur_ring->is_fused_ring); + } + } + } +} diff --git a/INCHI-1-SRC/INCHI_BASE/src/atropisomers.h b/INCHI-1-SRC/INCHI_BASE/src/atropisomers.h new file mode 100644 index 00000000..db4eabc7 --- /dev/null +++ b/INCHI-1-SRC/INCHI_BASE/src/atropisomers.h @@ -0,0 +1,9 @@ + +#include "inpdef.h" +#include "ring_detection.h" + +void find_atropisomeric_atoms_and_bonds(inp_ATOM* out_at, + int num_atoms, + RingSystems *ring_result, + ORIG_ATOM_DATA *orig_inp_data, + int *fused_atom_partner); diff --git a/INCHI-1-SRC/INCHI_BASE/src/ichimake.c b/INCHI-1-SRC/INCHI_BASE/src/ichimake.c index aae3114e..7a088797 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/ichimake.c +++ b/INCHI-1-SRC/INCHI_BASE/src/ichimake.c @@ -54,6 +54,7 @@ #include "bcf_s.h" +#include "atropisomers.h" #include "ring_detection.h" /* @@ -3904,53 +3905,35 @@ int Create_INChI(CANON_GLOBALS* pCG, #if ( FIND_RING_SYSTEMS == 1 ) MarkRingSystemsInp(out_at, num_atoms, 0); + orig_inp_data->is_atropisomer = 0; if (ip->Atropisomers) { RingSystems *ring_result = find_rings(out_at, num_atoms); // print_ring_result(ring_result); - + int fused_atom_partner[num_atoms]; for (i = 0; i < num_atoms; i++) { - out_at[i].fused_partner_atom_id = -1; + // out_at[i].fused_partner_atom_id = -1; + fused_atom_partner[i] = -1; } for (i = 0; i < num_atoms; i++) { for (int j = i + 1; j < num_atoms; j++) { - // printf("%d %d\n", i, j); if(is_fused_ring_pivot(ring_result, out_at, i, j)) { - // printf(">>> Found pivot atom pair: %d, %d\n", i, j); - out_at[i].fused_partner_atom_id = j; - out_at[j].fused_partner_atom_id = i; + fused_atom_partner[i] = j; + fused_atom_partner[j] = i; } } } - if (ring_result != NULL) { - // orig_inp_data->ring_id_to_size = (int*)inchi_calloc(ring_result->count, sizeof(int)); - for (i = 0; i < ring_result->count; i++) { - orig_inp_data->ring_id_to_size[i] = ring_result->rings[i].size; - } - } - - free_ring_system(ring_result); - - for (i = 0; i < num_atoms; i++) { - if (out_at[i].ring_count > 0) { - orig_inp_data->at[i].ring_count = out_at[i].ring_count; - for (int j = 0; j < out_at[i].ring_count; j++) { - orig_inp_data->at[i].ring_ids[j] = out_at[i].ring_ids[j]; - } - } + find_atropisomeric_atoms_and_bonds(out_at, num_atoms, ring_result, orig_inp_data, fused_atom_partner); - for (int j = 0; j < out_at[i].valence; j++) { - if (out_at[i].bond_stereo[j] > 0) { - orig_inp_data->at[i].bond_stereo[j] = out_at[i].bond_stereo[j]; - // printf("%d %d\n", i, out_at[i].bond_stereo[j]); - } + //map values to orig_inp_data + if (orig_inp_data->is_atropisomer) { + for (i = 0; i < num_atoms; i++) { + //todo set atoms with atropisomeric bonds in orig_atom_data } - // if (out_at[i].fused_partner_atom_id > -1) { - orig_inp_data->at[i].fused_partner_atom_id = out_at[i].fused_partner_atom_id; - } + free_ring_system(ring_result); } #endif diff --git a/INCHI-1-SRC/INCHI_BASE/src/inpdef.h b/INCHI-1-SRC/INCHI_BASE/src/inpdef.h index e4d47bf4..ec3c5823 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/inpdef.h +++ b/INCHI-1-SRC/INCHI_BASE/src/inpdef.h @@ -100,9 +100,6 @@ typedef S_SHORT ST_CAP_FLOW; #define SB_PARITY_1(X) (X & SB_PARITY_MASK) /**< refers to connected structure */ #define SB_PARITY_2(X) (((X) >> SB_PARITY_SHFT) & SB_PARITY_MASK) /**< refers to connected structure */ -#define RS_MAX_RINGS_PER_ATOM 10 -#define RS_MAX_NOF_RINGS 1000 - /** * @brief Structure describing an input atom * @@ -193,11 +190,6 @@ typedef struct tagInputAtom AT_NUMB nNumAtInRingSystem; AT_NUMB nBlockSystem; - int ring_ids[RS_MAX_RINGS_PER_ATOM]; - int ring_count; - - int fused_partner_atom_id; - #if (FIND_RINS_SYSTEMS_DISTANCES == 1) AT_NUMB nDistanceFromTerminal; /* terminal atom or ring system has 1, next has 2, etc. */ #endif @@ -467,9 +459,9 @@ typedef struct tagOrigAtom OAD_Polymer *polymer; OAD_V3000 *v3000; int valid_polymer; - int n_zy; /* number of non-polymeric pseudoatoms (Zy) */ + int n_zy; /* number of non-polymeric pseudoatoms (Zy) */ - int ring_id_to_size[RS_MAX_NOF_RINGS]; + int is_atropisomer; /* flag indicating whether the structure is an atropisomer; it is set to 1 if the structure has been identified as an atropisomer during input processing, and 0 otherwise */ } ORIG_ATOM_DATA; diff --git a/INCHI-1-SRC/INCHI_BASE/src/ring_detection.c b/INCHI-1-SRC/INCHI_BASE/src/ring_detection.c index 7d40f2d2..11d4b0b1 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/ring_detection.c +++ b/INCHI-1-SRC/INCHI_BASE/src/ring_detection.c @@ -1,8 +1,10 @@ #include "mode.h" #include "inpdef.h" + #include "ring_detection.h" + int is_subset(Ring* child, Ring* potential_parent) { @@ -106,18 +108,18 @@ int is_atom_in_ring(const Ring *r, int atom_id) { } int are_atoms_in_same_small_ring(const inp_ATOM* atoms, - const int *ring_id_to_size, + const RingSystems *rs, int atom_id1, int atom_id2, int max_ring_size) { inp_ATOM atom1 = atoms[atom_id1]; inp_ATOM atom2 = atoms[atom_id2]; - for (int i = 0; i < atom1.ring_count; i++) { - int ring_id1 = atom1.ring_ids[i]; - if (ring_id_to_size[ring_id1] <= max_ring_size) { - for (int j = 0; j < atom2.ring_count; j++) { - int ring_id2 = atom2.ring_ids[j]; + for (int i = 0; i < rs->atom_to_ring_mapping[atom_id1].ring_count; i++) { + int ring_id1 = rs->atom_to_ring_mapping[atom_id1].ring_ids[i]; + if (rs->rings[ring_id1].size <= max_ring_size) { + for (int j = 0; j < rs->atom_to_ring_mapping[atom_id2].ring_count; j++) { + int ring_id2 = rs->atom_to_ring_mapping[atom_id2].ring_ids[j]; if (ring_id1 == ring_id2) { return 1; } @@ -162,6 +164,11 @@ void free_ring_system(RingSystems *rs) { inchi_free(rs->rings[i].child_ids); } inchi_free(rs->rings); + + for (int i = 0; i < rs->num_atoms; i++) { + inchi_free(rs->atom_to_ring_mapping[i].ring_ids); + } + inchi_free(rs->atom_to_ring_mapping); inchi_free(rs); } @@ -212,11 +219,33 @@ void *create_new_ring(RingSystems *rs, r->child_count = 0; r->child_ids = NULL; r->nof_atomic_rings = 0; + r->is_fused_ring = 0; r->atom_ids = (int*)inchi_calloc(path_len, sizeof(int)); for (int i = 0; i < path_len; i++) { r->atom_ids[i] = path[i]; - atoms[path[i]].ring_ids[atoms[path[i]].ring_count] = rs->count; - atoms[path[i]].ring_count++; + + rs->atom_to_ring_mapping[path[i]].atom_id = path[i]; + rs->atom_to_ring_mapping[path[i]].ring_ids = (int*)inchi_realloc(rs->atom_to_ring_mapping[path[i]].ring_ids, + (rs->atom_to_ring_mapping[path[i]].ring_count + 1) * sizeof(int)); + rs->atom_to_ring_mapping[path[i]].ring_ids[rs->atom_to_ring_mapping[path[i]].ring_count] = r->id; + rs->atom_to_ring_mapping[path[i]].ring_count++; + + inp_ATOM atom = atoms[path[i]]; + for (int j = i + 1; j < path_len; j++) { + // prev, i, next + int prev_atom_id = path[i - 1 < 0 ? path_len - 1 : i - 1]; + int cur_atom_id = path[i]; + int next_atom_id = path[i + 1 >= path_len ? 0 : i + 1]; + int other_atom_id = path[j]; + for (int k = 0; k < atom.valence; k++) { + if (atom.neighbor[k] == prev_atom_id || atom.neighbor[k] == next_atom_id) { + continue; + } else if (atom.neighbor[k] == other_atom_id) + { + r->is_fused_ring = 1; + } + } + } } rs->rings[rs->count] = *r; @@ -277,8 +306,6 @@ void dfs(RingSystems *rs, } } visited[curr] = 0; - - } int is_fused_ring_pivot(const RingSystems *rs, @@ -363,6 +390,8 @@ RingSystems *find_rings(inp_ATOM* atoms, RingSystems *rs = (RingSystems*)inchi_calloc(1, sizeof(RingSystems)); rs->rings = NULL; //(Ring*)inchi_calloc(num_atoms * 10, sizeof(Ring)); rs->count = 0; + rs->num_atoms = num_atoms; + rs->atom_to_ring_mapping = (Atom2RingMapping*)inchi_calloc(num_atoms, sizeof(Atom2RingMapping)); int visited[num_atoms]; int path[num_atoms]; @@ -373,7 +402,7 @@ RingSystems *find_rings(inp_ATOM* atoms, visited[i] = 0; path[i] = -1; - inp_ATOM *atom_i = &atoms[i]; + const inp_ATOM *atom_i = &atoms[i]; for (int j = 0; j < atom_i->valence; j++) { int neighbor = atom_i->neighbor[j]; adj[i][neighbor] = 1; diff --git a/INCHI-1-SRC/INCHI_BASE/src/ring_detection.h b/INCHI-1-SRC/INCHI_BASE/src/ring_detection.h index 4330a657..6e3db9c4 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/ring_detection.h +++ b/INCHI-1-SRC/INCHI_BASE/src/ring_detection.h @@ -1,4 +1,9 @@ +#ifndef _RING_DETECTION_H_ +#define _RING_DETECTION_H_ + +#define RS_MAX_RINGS_PER_ATOM 30 + typedef struct Ring { int id; int *atom_ids; @@ -7,14 +12,22 @@ typedef struct Ring { int parent_id; // Added: ID of the larger ring containing this one int *child_ids; int child_count; + int is_fused_ring; } Ring; +typedef struct Atom2RingMapping { + int atom_id; + int *ring_ids; + int ring_count; +} Atom2RingMapping; + typedef struct { Ring* rings; int count; + Atom2RingMapping* atom_to_ring_mapping; + int num_atoms; } RingSystems; - RingSystems *find_rings(inp_ATOM* atoms, int num_atoms); int is_fused_ring_pivot(const RingSystems *rs, @@ -26,6 +39,9 @@ void print_ring_result(const RingSystems *rs); void free_ring_system(RingSystems *rs); int are_atoms_in_same_small_ring(const inp_ATOM* atoms, - const int *ring_id_to_size, + const RingSystems *rs, int atom_id1, int atom_id2, int max_ring_size); + + +#endif diff --git a/INCHI-1-SRC/INCHI_BASE/src/strutil.c b/INCHI-1-SRC/INCHI_BASE/src/strutil.c index c4d9d056..e11f79c9 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/strutil.c +++ b/INCHI-1-SRC/INCHI_BASE/src/strutil.c @@ -55,8 +55,6 @@ #include "bcf_s.h" -#include "ring_detection.h" - /* Added fix to remove_ion_pairs() -- 2010-03-17 DT */ #define FIX_P_IV_Plus_O_Minus @@ -7104,21 +7102,22 @@ int invert_parities(const INChI *inchi, return 0; } + /** - * @brief Set the enhanced stereochemistry information for t- and m-layers + * @brief Set t- and m-layers object for atropisomer stereochemistry * * @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 + * @return int */ -int set_EnhancedStereo_t_m_layers( const ORIG_ATOM_DATA *orig_inp_data, - const INChI *inchi, - const INChI_Aux *aux) +int set_Atropisomer_t_m_layers( const ORIG_ATOM_DATA *orig_inp_data, + const INChI *inchi, + const INChI_Aux *aux) { int ret = 0; - if (!orig_inp_data->v3000) + if (orig_inp_data == NULL) { return 1; } @@ -7128,46 +7127,42 @@ int set_EnhancedStereo_t_m_layers( const ORIG_ATOM_DATA *orig_inp_data, 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); + //TODO + // - t layer parities for atropisomers + // -> t-parity[atom] = 1 (-) + // - m layer for atropisomers + // -> enantiomeric atropisomers: m1 (inchi->Stereo->nCompInv2Abs = -1; //m1) + // -> diastereomeric atropisomers: m0 (inchi->Stereo->nCompInv2Abs = 1; //m0) + // -> check number of stereocenters: if #stereocenter >= 2 - 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; //m0 + if (orig_inp_data->is_atropisomer) { + printf(">>>>> TODO set t- and m-layers for atropisomers\n"); } + return ret; } /** - * @brief Set t- and m-layers object for atropisomer stereochemistry + * @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 int + * @return Retruns 1 if not V3000, otherwise 0 */ -int set_Atropisomer_t_m_layers( const ORIG_ATOM_DATA *orig_inp_data, - const INChI *inchi, - const 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; - if (orig_inp_data == NULL) + if (!orig_inp_data->v3000) { return 1; } @@ -7177,103 +7172,28 @@ int set_Atropisomer_t_m_layers( const ORIG_ATOM_DATA *orig_inp_data, 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; } - printf("-------------------\n"); - - for (int i = 0; i < orig_inp_data->num_inp_atoms; i++) { - - const inp_ATOM atom_i = orig_inp_data->at[i]; - - // if (atom_i.is_fused_pivot_atom == 0) { - // continue; - // } - - int num_neighbors_i = atom_i.valence; - const AT_NUMB *neighbors = atom_i.neighbor; - // printf("Atom %d with %d neighbors; ring system id %d; num atoms in ring %d\n", i + 1, num_neighbors, orig_inp_data->at[i].nRingSystem, orig_inp_data->at[i].nNumAtInRingSystem); - if (num_neighbors_i == 3) { - for (int j = 0; j < num_neighbors_i; j++) { - - if (i >= neighbors[j]) { - continue; - } - - const inp_ATOM atom_j = orig_inp_data->at[neighbors[j]]; - - // if (atom_j.is_fused_pivot_atom == 0) { - // continue; - // } - - int num_neighbors_j = atom_j.valence; - - if (num_neighbors_j == 3 && - atom_i.bond_stereo[j] == 0 && - atom_i.bond_type[j] == 1) { - - if (atom_i.ring_count > 0 || - atom_j.ring_count > 0) { - - int nof_wedge_bonds_i = 0; - int has_double_bond_i = 0; - for (int k = 0; k < num_neighbors_i; k++) { - if (atom_i.bond_stereo[k] == 1 || - atom_i.bond_stereo[k] == 4 || - atom_i.bond_stereo[k] == 6) { - nof_wedge_bonds_i++; - } - if (atom_i.bond_type[k] == 2) { - has_double_bond_i = 1; - } - } - int nof_wedge_bonds_j = 0; - int has_double_bond_j = 0; - for (int k = 0; k < num_neighbors_j; k++) { - if (atom_j.bond_stereo[k] == 1 || - atom_j.bond_stereo[k] == 4 || - atom_j.bond_stereo[k] == 6) { - nof_wedge_bonds_j++; - } - if (atom_j.bond_type[k] == 2) { - has_double_bond_j = 1; - } - } - - if (nof_wedge_bonds_i > 0 || - nof_wedge_bonds_j > 0) { - - if ((has_double_bond_i || has_double_bond_j)) { - if ((atom_i.fused_partner_atom_id != j && - atom_j.fused_partner_atom_id != i) || - (atom_i.fused_partner_atom_id == -1 || - atom_j.fused_partner_atom_id == -1)) { - - if (are_atoms_in_same_small_ring(orig_inp_data->at, - orig_inp_data->ring_id_to_size, - i, neighbors[j], - 6) == 0) { - printf(">>> is atropisomer\n"); - printf("infos: atom %d with atom %d; bond type %d; bond stereo %d\n", - i, neighbors[j], atom_i.bond_type[j], atom_i.bond_stereo[j]); - printf("atom type %d %d\n", atom_i.el_number, atom_j.el_number); - printf("has double bond %d %d\n", has_double_bond_i, has_double_bond_j); - printf("fused pivot atoms %d %d\n", atom_i.fused_partner_atom_id, atom_j.fused_partner_atom_id); - } - } - } - } - } + 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); - } - } - } + 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; //m0 } - - return ret; } diff --git a/INCHI-1-SRC/INCHI_EXE/inchi-1/src/CMakeLists.txt b/INCHI-1-SRC/INCHI_EXE/inchi-1/src/CMakeLists.txt index fbc1d5d7..187f0cb8 100644 --- a/INCHI-1-SRC/INCHI_EXE/inchi-1/src/CMakeLists.txt +++ b/INCHI-1-SRC/INCHI_EXE/inchi-1/src/CMakeLists.txt @@ -26,6 +26,8 @@ add_executable(inchi-1) target_sources(inchi-1 PRIVATE main.c + ${P_BASE}/atropisomers.h + ${P_BASE}/atropisomers.c ${P_BASE}/ichimain.c ${P_BASE}/dispstru.c ${P_BASE}/dispstru.h diff --git a/INCHI-1-TEST/tests/test_unit/test_atropisomers.cpp b/INCHI-1-TEST/tests/test_unit/test_atropisomers.cpp index d238aba4..d449352c 100644 --- a/INCHI-1-TEST/tests/test_unit/test_atropisomers.cpp +++ b/INCHI-1-TEST/tests/test_unit/test_atropisomers.cpp @@ -52,7 +52,7 @@ TEST(test_atropisomers, test_dummy_1) char options[] = "-Atropisomers"; inchi_Output output; inchi_Output *poutput = &output; - 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"; + const char expected_inchi[] = "InChI=1B"; EXPECT_EQ(MakeINCHIFromMolfileText(molblock, options, poutput), 1); EXPECT_STREQ(poutput->szInChI, expected_inchi); @@ -126,7 +126,7 @@ TEST(test_atropisomers, test_dummy_2) char options[] = "-Atropisomers"; inchi_Output output; inchi_Output *poutput = &output; - const char expected_inchi[] = "InChI=1B/C20H22O3/c1-21-17-5-3-13-7-15-11-23-12-16(15)8-14-4-6-18(22-2)10-20(14)19(13)9-17/h3-6,9-10,15-16H,7-8,11-12H2,1-2H3"; + const char expected_inchi[] = "InChI=1B"; EXPECT_EQ(MakeINCHIFromMolfileText(molblock, options, poutput), 1); EXPECT_STREQ(poutput->szInChI, expected_inchi); @@ -193,7 +193,7 @@ TEST(test_atropisomers, test_dummy_3) char options[] = "-Atropisomers"; inchi_Output output; inchi_Output *poutput = &output; - const char expected_inchi[] = "InChI=1B/C20H16O2/c21-17-11-9-13-5-1-3-7-15(13)19(17)20-16-8-4-2-6-14(16)10-12-18(20)22/h1-11,18,21-22H,12H2"; + const char expected_inchi[] = "InChI=1B"; EXPECT_EQ(MakeINCHIFromMolfileText(molblock, options, poutput), 1); EXPECT_STREQ(poutput->szInChI, expected_inchi); @@ -274,7 +274,7 @@ TEST(test_atropisomers, test_dummy_4_atypical_no_2_rings) char options[] = "-Atropisomers"; inchi_Output output; inchi_Output *poutput = &output; - const char expected_inchi[] = "InChI=1B/C20H28N2O2/c1-5-17(23)22(14-10-7-6-8-11-14)18-15(19(21)24)12-9-13-16(18)20(2,3)4/h5,9,12-14H,1,6-8,10-11H2,2-4H3,(H2,21,24)"; + const char expected_inchi[] = "InChI=1B"; EXPECT_EQ(MakeINCHIFromMolfileText(molblock, options, poutput), 1); EXPECT_STREQ(poutput->szInChI, expected_inchi); @@ -282,7 +282,7 @@ TEST(test_atropisomers, test_dummy_4_atypical_no_2_rings) FreeINCHI(poutput); } -TEST(test_atropisomers, test_dummy_5_no_wedge_bonds) +TEST(test_atropisomers, test_dummy_5_no_atropisomer_no_wedge_bonds) { const char *molblock = "atropisomer test mol \n" @@ -516,7 +516,7 @@ TEST(test_atropisomers, test_dummy_7_no_atropisomer_1) char options[] = "-Atropisomers"; inchi_Output output; inchi_Output *poutput = &output; - const char expected_inchi[] = "InChI=1B"; + const char expected_inchi[] = "InChI=1B/C12H10Br2/c1-7-3-4-8(2)12-10(14)6-5-9(13)11(7)12/h3-6H,1-2H3"; EXPECT_EQ(MakeINCHIFromMolfileText(molblock, options, poutput), 1); EXPECT_STREQ(poutput->szInChI, expected_inchi); @@ -577,7 +577,7 @@ TEST(test_atropisomers, test_dummy_8_no_atropisomer) char options[] = "-Atropisomers"; inchi_Output output; inchi_Output *poutput = &output; - const char expected_inchi[] = "InChI=1B"; + const char expected_inchi[] = "InChI=1B/C18H14O/c1-11-7-8-13-9-10-19-18-15-6-4-3-5-14(15)12(2)16(11)17(13)18/h3-10H,1-2H3"; EXPECT_EQ(MakeINCHIFromMolfileText(molblock, options, poutput), 1); EXPECT_STREQ(poutput->szInChI, expected_inchi); @@ -620,7 +620,7 @@ TEST(test_atropisomers, test_dummy_9_no_atropisomer) char options[] = "-Atropisomers"; inchi_Output output; inchi_Output *poutput = &output; - const char expected_inchi[] = "InChI=1B"; + const char expected_inchi[] = "InChI=1B/C9H9NO/c1-6-3-4-11-9-8(6)7(2)5-10-9/h3-5H,1-2H3"; EXPECT_EQ(MakeINCHIFromMolfileText(molblock, options, poutput), 1); EXPECT_STREQ(poutput->szInChI, expected_inchi); @@ -668,7 +668,7 @@ TEST(test_atropisomers, test_dummy_10_no_atropisomer) char options[] = "-Atropisomers"; inchi_Output output; inchi_Output *poutput = &output; - const char expected_inchi[] = "InChI=1B"; + const char expected_inchi[] = "InChI=1B/C13H10/c1-3-7-12-10(5-1)9-11-6-2-4-8-13(11)12/h1-8H,9H2"; EXPECT_EQ(MakeINCHIFromMolfileText(molblock, options, poutput), 1); EXPECT_STREQ(poutput->szInChI, expected_inchi); @@ -781,7 +781,7 @@ TEST(test_atropisomers, test_dummy_11_no_atropisomer_3_fragments) char options[] = "-Atropisomers"; inchi_Output output; inchi_Output *poutput = &output; - const char expected_inchi[] = "InChI=1B"; + const char expected_inchi[] = "InChI=1B/C16H10.C14H10.C13H10/c1-3-11-7-9-13-5-2-6-14-10-8-12(4-1)15(11)16(13)14;1-3-7-13-11(5-1)9-10-12-6-2-4-8-14(12)13;1-3-7-12-10(5-1)9-11-6-2-4-8-13(11)12/h1-10H;1-10H;1-8H,9H2"; EXPECT_EQ(MakeINCHIFromMolfileText(molblock, options, poutput), 1); EXPECT_STREQ(poutput->szInChI, expected_inchi); @@ -840,3 +840,102 @@ TEST(test_atropisomers, test_dummy_12_atropisomer) FreeINCHI(poutput); } + +TEST(test_atropisomers, test_dummy_13_atropisomer_Caryophyllene) +{ + const char *molblock = + "5281515 Caryophyllene \n" + " -OEChem-03022602172D \n" + " \n" + " 39 40 0 1 0 0 0 0 0999 V2000 \n" + " 2.9665 1.2303 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 2.7095 0.2553 0.0000 C 0 0 1 0 0 0 0 0 0 0 0 0 \n" + " 3.6754 -0.0036 0.0000 C 0 0 2 0 0 0 0 0 0 0 0 0 \n" + " 3.9404 0.9693 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 2.4507 -0.7107 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 3.2233 2.1967 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 2.0000 1.4871 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 4.5415 0.4964 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 2.9507 -1.5767 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 5.4075 -0.0036 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 3.8167 -1.0767 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 5.5488 -1.0767 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 4.5415 1.4964 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 4.6827 -1.5767 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 3.8167 -0.0767 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 1.8880 0.4736 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 3.7864 -0.8463 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 4.5388 0.8072 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 4.1017 1.5679 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 1.9137 -0.4007 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 2.0123 -1.1491 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 3.8225 2.0375 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 3.3825 2.7959 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 2.6241 2.3559 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 2.1592 2.0863 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 1.4008 1.6463 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 1.8408 0.8879 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 2.4137 -1.8867 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 3.2607 -2.1136 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 6.0251 -0.0576 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 5.5939 0.5877 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 5.7860 -1.6495 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 6.1476 -0.9162 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 5.0784 1.8064 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 4.0045 1.8064 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 4.6827 -2.1967 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 4.4367 -0.0767 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 3.8167 0.5433 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 3.1967 -0.0767 0.0000 H 0 0 0 0 0 0 0 0 0 0 0 0 \n" + " 1 2 1 0 0 0 0 \n" + " 1 4 1 0 0 0 0 \n" + " 1 6 1 0 0 0 0 \n" + " 1 7 1 0 0 0 0 \n" + " 2 3 1 0 0 0 0 \n" + " 2 5 1 0 0 0 0 \n" + " 2 16 1 6 0 0 0 \n" + " 3 4 1 0 0 0 0 \n" + " 3 8 1 0 0 0 0 \n" + " 3 17 1 1 0 0 0 \n" + " 4 18 1 0 0 0 0 \n" + " 4 19 1 0 0 0 0 \n" + " 5 9 1 0 0 0 0 \n" + " 5 20 1 0 0 0 0 \n" + " 5 21 1 0 0 0 0 \n" + " 6 22 1 0 0 0 0 \n" + " 6 23 1 0 0 0 0 \n" + " 6 24 1 0 0 0 0 \n" + " 7 25 1 0 0 0 0 \n" + " 7 26 1 0 0 0 0 \n" + " 7 27 1 0 0 0 0 \n" + " 8 10 1 0 0 0 0 \n" + " 8 13 2 0 0 0 0 \n" + " 9 11 1 0 0 0 0 \n" + " 9 28 1 0 0 0 0 \n" + " 9 29 1 0 0 0 0 \n" + " 10 12 1 0 0 0 0 \n" + " 10 30 1 0 0 0 0 \n" + " 10 31 1 0 0 0 0 \n" + " 11 14 2 0 0 0 0 \n" + " 11 15 1 0 0 0 0 \n" + " 12 14 1 0 0 0 0 \n" + " 12 32 1 0 0 0 0 \n" + " 12 33 1 0 0 0 0 \n" + " 13 34 1 0 0 0 0 \n" + " 13 35 1 0 0 0 0 \n" + " 14 36 1 0 0 0 0 \n" + " 15 37 1 0 0 0 0 \n" + " 15 38 1 0 0 0 0 \n" + " 15 39 1 0 0 0 0 \n" + "M END \n"; + + char options[] = "-Atropisomers"; + inchi_Output output; + inchi_Output *poutput = &output; + const char expected_inchi[] = "InChI=1B"; + + EXPECT_EQ(MakeINCHIFromMolfileText(molblock, options, poutput), 1); + EXPECT_STREQ(poutput->szInChI, expected_inchi); + + FreeINCHI(poutput); +} From d1e8cab8377c5f145534d294c4744c54bebd062d Mon Sep 17 00:00:00 2001 From: Christoph Mueller Date: Tue, 3 Mar 2026 14:23:51 +0000 Subject: [PATCH 11/11] clean up atropisomer code --- INCHI-1-SRC/INCHI_BASE/src/atropisomers.c | 1 + INCHI-1-SRC/INCHI_BASE/src/ring_detection.c | 108 +++++++++++++++++--- INCHI-1-SRC/INCHI_BASE/src/ring_detection.h | 1 + 3 files changed, 97 insertions(+), 13 deletions(-) diff --git a/INCHI-1-SRC/INCHI_BASE/src/atropisomers.c b/INCHI-1-SRC/INCHI_BASE/src/atropisomers.c index 5a7d0c3c..5592afda 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/atropisomers.c +++ b/INCHI-1-SRC/INCHI_BASE/src/atropisomers.c @@ -79,6 +79,7 @@ void find_atropisomeric_atoms_and_bonds(inp_ATOM* out_at, fused_atom_partner[atom_id2] == -1)) { if (are_atoms_in_same_small_ring(out_at, + num_atoms, ring_result, atom_id1, atom_id2, 6) == 0) { diff --git a/INCHI-1-SRC/INCHI_BASE/src/ring_detection.c b/INCHI-1-SRC/INCHI_BASE/src/ring_detection.c index 11d4b0b1..9b7732ef 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/ring_detection.c +++ b/INCHI-1-SRC/INCHI_BASE/src/ring_detection.c @@ -8,6 +8,15 @@ int is_subset(Ring* child, Ring* potential_parent) { + if (child == NULL || potential_parent == NULL) { + return 0; + } + + if (child->atom_ids == NULL || potential_parent->atom_ids == NULL) { + return 0; + } + + if (child->size >= potential_parent->size) { return 0; } @@ -28,6 +37,15 @@ int is_subset(Ring* child, } void determine_ring_hierarchy(RingSystems* rs) { + + if (rs == NULL) { + return; + } + + if (rs->rings == NULL) { + return; + } + for (int i = 0; i < rs->count; i++) { rs->rings[i].parent_id = -1; @@ -39,14 +57,11 @@ void determine_ring_hierarchy(RingSystems* rs) { } if (is_subset(&rs->rings[i], &rs->rings[j])) { - // if (rs->rings[j].size < smallest_parent_size) { rs->rings[i].parent_id = rs->rings[j].id; - // smallest_parent_size = rs->rings[j].size; rs->rings[j].child_ids = (int*)inchi_realloc(rs->rings[j].child_ids, (rs->rings[j].child_count + 1) * sizeof(int)); rs->rings[j].child_ids[rs->rings[j].child_count] = rs->rings[i].id; rs->rings[j].child_count++; - // } } } } @@ -54,6 +69,15 @@ void determine_ring_hierarchy(RingSystems* rs) { int get_ring_atom_overlap(const Ring *r1, const Ring *r2) { int overlap = 0; + + if (r1 == NULL || r2 == NULL) { + return overlap; + } + + if (r1->atom_ids == NULL || r2->atom_ids == NULL) { + return overlap; + } + for (int i = 0; i < r1->size; i++) { for (int j = 0; j < r2->size; j++) { if (r1->atom_ids[i] == r2->atom_ids[j]) { @@ -67,6 +91,15 @@ int get_ring_atom_overlap(const Ring *r1, const Ring *r2) { int get_number_of_overlapping_rings(const Ring *r1, const Ring *r2) { int count = 0; + + if (r1 == NULL || r2 == NULL) { + return count; + } + + if (r1->child_ids == NULL || r2->child_ids == NULL) { + return count; + } + for (int i = 0; i < r1->child_count; i++) { for (int j = 0; j < r2->child_count; j++) { if (r1->child_ids[i] == r2->child_ids[j]) { @@ -80,8 +113,16 @@ int get_number_of_overlapping_rings(const Ring *r1, const Ring *r2) { int get_number_of_atomic_rings_from_atom(const RingSystems *rs, int atom_id) { + if (rs == NULL) { + return 0; + } + + if (rs->rings == NULL) { + return 0; + } + if (atom_id < 0) { - return 0; // Invalid input + return 0; } int count = 0; @@ -99,6 +140,15 @@ int get_number_of_atomic_rings_from_atom(const RingSystems *rs, } int is_atom_in_ring(const Ring *r, int atom_id) { + + if (r == NULL) { + return 0; + } + + if (r->atom_ids == NULL) { + return 0; + } + for (int i = 0; i < r->size; i++) { if (r->atom_ids[i] == atom_id) { return 1; @@ -108,12 +158,29 @@ int is_atom_in_ring(const Ring *r, int atom_id) { } int are_atoms_in_same_small_ring(const inp_ATOM* atoms, + int num_atoms, const RingSystems *rs, int atom_id1, int atom_id2, int max_ring_size) { - inp_ATOM atom1 = atoms[atom_id1]; - inp_ATOM atom2 = atoms[atom_id2]; + if (atoms == NULL) { + return 0; + } + + if (rs == NULL) { + return 0; + } + + if (rs->atom_to_ring_mapping == NULL) { + return 0; + } + + if (atom_id1 >= num_atoms || atom_id2 >= num_atoms) { + return 0; + } + + const inp_ATOM atom1 = atoms[atom_id1]; + const inp_ATOM atom2 = atoms[atom_id2]; for (int i = 0; i < rs->atom_to_ring_mapping[atom_id1].ring_count; i++) { int ring_id1 = rs->atom_to_ring_mapping[atom_id1].ring_ids[i]; @@ -130,6 +197,11 @@ int are_atoms_in_same_small_ring(const inp_ATOM* atoms, } void print_ring(const Ring *r) { + + if (r == NULL) { + return; + } + printf("Ring ID: %d, Size: %d, nof fused ring %d, parent %d, Atoms: ", r->id, r->size, r->nof_atomic_rings, r->parent_id); for (int i = 0; i < r->size; i++) { @@ -145,6 +217,10 @@ void print_ring(const Ring *r) { void print_ring_result(const RingSystems *rs) { + if (rs == NULL) { + return; + } + printf("Number of rings: %d\n", rs->count); for (int i = 0; i < rs->count; i++) { @@ -184,6 +260,14 @@ int sub_ring_counter(RingSystems* rs, const Ring *r, int *ring_counter) { void determine_fused_rings(RingSystems* rs) { + if (rs == NULL) { + return; + } + + if (rs->rings == NULL) { + return 0; + } + for (int i = 0; i < rs->count; i++) { Ring *cur_ring = &rs->rings[i]; @@ -209,8 +293,9 @@ void *create_new_ring(RingSystems *rs, int *path, int path_len) { - // Ring *r = &rs->rings[rs->count]; //(Ring*)inchi_calloc(1, sizeof(Ring)); + rs->rings = (Ring*)inchi_realloc(rs->rings, (rs->count + 1) * sizeof(Ring)); + Ring *r = &rs->rings[rs->count]; r->id = rs->count; @@ -384,18 +469,18 @@ RingSystems *find_rings(inp_ATOM* atoms, int num_atoms) { if (atoms == NULL || num_atoms <= 0) { - return NULL; // Invalid input + return NULL; } RingSystems *rs = (RingSystems*)inchi_calloc(1, sizeof(RingSystems)); - rs->rings = NULL; //(Ring*)inchi_calloc(num_atoms * 10, sizeof(Ring)); + rs->rings = NULL; rs->count = 0; rs->num_atoms = num_atoms; rs->atom_to_ring_mapping = (Atom2RingMapping*)inchi_calloc(num_atoms, sizeof(Atom2RingMapping)); int visited[num_atoms]; int path[num_atoms]; - int **adj = (int**)inchi_calloc(num_atoms, sizeof(int*)); //[num_atoms][num_atoms]; // Adjacency matrix + int **adj = (int**)inchi_calloc(num_atoms, sizeof(int*)); for (int i = 0; i < num_atoms; ++i) { adj[i] = (int*)inchi_calloc(num_atoms, sizeof(int)); @@ -406,7 +491,6 @@ RingSystems *find_rings(inp_ATOM* atoms, for (int j = 0; j < atom_i->valence; j++) { int neighbor = atom_i->neighbor[j]; adj[i][neighbor] = 1; - // adj[neighbor][i] = 1; // Undirected graph } } @@ -414,9 +498,7 @@ RingSystems *find_rings(inp_ATOM* atoms, dfs(rs, atoms, adj, num_atoms, i, i, visited, path, 0); } - determine_ring_hierarchy(rs); - determine_fused_rings(rs); for (int i = 0; i < num_atoms; ++i) { diff --git a/INCHI-1-SRC/INCHI_BASE/src/ring_detection.h b/INCHI-1-SRC/INCHI_BASE/src/ring_detection.h index 6e3db9c4..f965baf2 100644 --- a/INCHI-1-SRC/INCHI_BASE/src/ring_detection.h +++ b/INCHI-1-SRC/INCHI_BASE/src/ring_detection.h @@ -39,6 +39,7 @@ void print_ring_result(const RingSystems *rs); void free_ring_system(RingSystems *rs); int are_atoms_in_same_small_ring(const inp_ATOM* atoms, + int num_atoms, const RingSystems *rs, int atom_id1, int atom_id2, int max_ring_size);