Skip to content

Commit 22fd4b0

Browse files
#3525 [Feature] Expose ability to calculate fully expanded large molecule into chemical structures (#3525)
1 parent bde8c16 commit 22fd4b0

22 files changed

Lines changed: 3977 additions & 10 deletions

api/c/indigo/indigo.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -639,6 +639,8 @@ CEXPORT const char* indigoGetTGroupAlias(int tgroup);
639639

640640
CEXPORT int indigoTransformSCSRtoCTAB(int item);
641641
CEXPORT int indigoTransformCTABtoSCSR(int molecule, int templates);
642+
// [Sapio] FR-48004 Expose expandedMonomersToAtoms to Python API.
643+
CEXPORT int indigoExpandedMonomersToAtoms(int molecule);
642644

643645
CEXPORT int indigoResetCharge(int atom);
644646
CEXPORT int indigoResetExplicitValence(int atom);
@@ -1129,6 +1131,13 @@ CEXPORT int indigoRGroupComposition(int molecule, const char* options);
11291131
*/
11301132
CEXPORT int indigoExpandAbbreviations(int molecule);
11311133

1134+
/*
1135+
* Group pseudoatoms (OH, NH2, etc.): expand to explicit atoms for V3000/molfile
1136+
* interoperability. Call after expandedMonomersToAtoms(). See
1137+
* indigo_group_pseudoatoms_expand.h for the list of labels.
1138+
*/
1139+
CEXPORT int indigoExpandGroupPseudoatoms(int molecule);
1140+
11321141
/* Other */
11331142

11341143
CEXPORT const char* indigoToString(int handle);

api/c/indigo/src/indigo_abbreviations_expand.cpp

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@
1717
***************************************************************************/
1818

1919
#include "indigo_abbreviations.h"
20+
#include "indigo_group_pseudoatoms_expand.h"
2021

2122
#include "base_c/bitarray.h"
23+
#include "base_cpp/array.h"
2224
#include "base_cpp/scanner.h"
2325
#include "molecule/elements.h"
2426
#include "molecule/molecule.h"
@@ -32,6 +34,7 @@
3234
#include "layout/molecule_layout.h"
3335

3436
#include <algorithm>
37+
#include <cstring>
3538

3639
namespace indigo
3740
{
@@ -850,4 +853,115 @@ namespace indigo
850853
}
851854

852855
} // namespace abbreviations
856+
857+
static bool isGroupPseudoatomLabel(const char* alias)
858+
{
859+
for (const char* const* p = GROUP_PSEUDOATOM_EXPAND_LABELS; *p != nullptr; ++p)
860+
{
861+
if (strcmp(alias, *p) == 0)
862+
return true;
863+
}
864+
return false;
865+
}
866+
867+
static bool buildGroupFragment(const char* label, Molecule& fragment, int& attachment_idx)
868+
{
869+
fragment.clear();
870+
attachment_idx = 0;
871+
872+
if (strcmp(label, "OH") == 0)
873+
{
874+
int o = fragment.addAtom(ELEM_O);
875+
int h = fragment.addAtom(ELEM_H);
876+
fragment.addBond(o, h, BOND_SINGLE);
877+
fragment.setAtomXyz(o, 0, 0, 0);
878+
fragment.setAtomXyz(h, 1, 0, 0);
879+
attachment_idx = 0;
880+
return true;
881+
}
882+
if (strcmp(label, "NH2") == 0)
883+
{
884+
int n = fragment.addAtom(ELEM_N);
885+
int h1 = fragment.addAtom(ELEM_H);
886+
int h2 = fragment.addAtom(ELEM_H);
887+
fragment.addBond(n, h1, BOND_SINGLE);
888+
fragment.addBond(n, h2, BOND_SINGLE);
889+
fragment.setAtomXyz(n, 0, 0, 0);
890+
fragment.setAtomXyz(h1, 1, 0, 0);
891+
fragment.setAtomXyz(h2, -0.5f, 0.866f, 0);
892+
attachment_idx = 0;
893+
return true;
894+
}
895+
return false;
896+
}
897+
898+
int expandGroupPseudoatomsInMolecule(Molecule& mol)
899+
{
900+
int count = 0;
901+
QS_DEF(Array<int>, pseudoatoms);
902+
pseudoatoms.clear();
903+
for (int v = mol.vertexBegin(); v != mol.vertexEnd(); v = mol.vertexNext(v))
904+
{
905+
if (mol.isPseudoAtom(v) && isGroupPseudoatomLabel(mol.getPseudoAtom(v)))
906+
pseudoatoms.push(v);
907+
}
908+
if (pseudoatoms.size() == 0)
909+
return 0;
910+
911+
std::sort(pseudoatoms.ptr(), pseudoatoms.ptr() + pseudoatoms.size(), std::greater<int>());
912+
913+
for (int i = 0; i < pseudoatoms.size(); i++)
914+
{
915+
// Step 1 Build fragment
916+
int v = pseudoatoms[i];
917+
const char* alias = mol.getPseudoAtom(v);
918+
Molecule fragment;
919+
int attachment_idx = -1;
920+
if (!buildGroupFragment(alias, fragment, attachment_idx))
921+
continue;
922+
923+
Vec3f pos = mol.getAtomXyz(v);
924+
const Vertex& vertex = mol.getVertex(v);
925+
926+
// Step 2 merge fragment into the main structure. Now we have a
927+
// forest of atoms, disconnected.
928+
QS_DEF(Array<int>, mapping);
929+
mol.mergeWithMolecule(fragment, &mapping);
930+
int ap_new = mapping[attachment_idx];
931+
932+
// Step 3 connect fragment to the main structure.
933+
934+
for (int k = vertex.neiBegin(); k != vertex.neiEnd(); k = vertex.neiNext(k))
935+
{
936+
int nei = vertex.neiVertex(k);
937+
int edge_idx = mol.findEdgeIndex(v, nei);
938+
int order = mol.getBondOrder(edge_idx);
939+
mol.addBond_Silent(nei, ap_new, order);
940+
}
941+
942+
for (int a = 0; a < fragment.vertexCount(); a++)
943+
{
944+
int mapped = mapping[a];
945+
mol.setAtomXyz(mapped, pos.x, pos.y, pos.z);
946+
}
947+
948+
mol.removeAtom(v);
949+
count++;
950+
}
951+
return count;
952+
}
953+
853954
} // namespace indigo
955+
956+
CEXPORT int indigoExpandGroupPseudoatoms(int molecule)
957+
{
958+
INDIGO_BEGIN
959+
{
960+
IndigoObject& obj = self.getObject(molecule);
961+
if (obj.type != IndigoObject::MOLECULE)
962+
throw IndigoError("indigoExpandGroupPseudoatoms(): expected molecule, got %s", obj.debugInfo());
963+
Molecule& mol = obj.getMolecule();
964+
return expandGroupPseudoatomsInMolecule(mol);
965+
}
966+
INDIGO_END(-1);
967+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/****************************************************************************
2+
* Copyright (C) from 2009 to Present EPAM Systems.
3+
*
4+
* This file is part of Indigo toolkit.
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
***************************************************************************/
18+
19+
#ifndef __indigo_group_pseudoatoms_expand_h__
20+
#define __indigo_group_pseudoatoms_expand_h__
21+
22+
#include "molecule/molecule.h"
23+
24+
namespace indigo
25+
{
26+
27+
/**
28+
* Table of group pseudoatom labels that are expanded for V3000/molfile
29+
* interoperability and for proper computation of properties within Indigo
30+
* (e.g. molecular weight; Indigo cannot compute mass for structures that
31+
* contain pseudoatoms). These labels (e.g. "OH", "NH2") appear as atom
32+
* labels in the default monomer library of Ketcher.
33+
* They are not valid single-atom symbols in MOL/V3000; toolkits like RDKit
34+
* expect element symbols only. Expanding them to explicit atoms (O+H,
35+
* N+H+H) allows the molfile to be read by strict parsers and enables
36+
* property calculations.
37+
*
38+
* This table is the single source of truth: add new labels here as they are
39+
* encountered in monomer libraries (e.g. Ketcher's default library). Do not
40+
* duplicate this list elsewhere.
41+
*/
42+
static const char* const GROUP_PSEUDOATOM_EXPAND_LABELS[] = {"OH", "NH2", nullptr};
43+
44+
/**
45+
* Expands group pseudoatoms (see GROUP_PSEUDOATOM_EXPAND_LABELS) in the
46+
* given molecule into explicit atoms and bonds, in place. These pseudoatoms
47+
* are handled because they are defined in Ketcher's default monomer library.
48+
* Expansion is needed both for proper computation of properties in Indigo
49+
* (e.g. molecular weight, which is not defined for pseudoatoms) and for
50+
* V3000/molfile interoperability with downstream toolkits (e.g. RDKit).
51+
* Used after expandedMonomersToAtoms() so that the result is valid for
52+
* export and for property calculations. Distinct from
53+
* indigoExpandAbbreviations(), which expands SMILES-style abbreviations (Me,
54+
* Et, Ph, etc.) from abbreviations.xml.
55+
*
56+
* @param mol Molecule to modify (must be Molecule, not QueryMolecule).
57+
* @return Number of group pseudoatoms expanded.
58+
*/
59+
int expandGroupPseudoatomsInMolecule(Molecule& mol);
60+
61+
} // namespace indigo
62+
63+
#endif

api/c/indigo/src/indigo_molecule.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include "base_cpp/output.h"
2222
#include "base_cpp/scanner.h"
2323
#include "indigo_array.h"
24+
#include "indigo_group_pseudoatoms_expand.h"
2425
#include "indigo_io.h"
2526
#include "indigo_ket_document.h"
2627
#include "indigo_mapping.h"

api/c/indigo/src/indigo_molecule_operations.cpp

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include "base_cpp/output.h"
2121
#include "base_cpp/scanner.h"
2222
#include "indigo_array.h"
23+
#include "indigo_group_pseudoatoms_expand.h"
2324
#include "indigo_io.h"
2425
#include "indigo_ket_document.h"
2526
#include "indigo_mapping.h"
@@ -3168,3 +3169,67 @@ CEXPORT int indigoExpandMonomers(int item)
31683169
}
31693170
INDIGO_END(0);
31703171
}
3172+
3173+
// [Sapio] FR-48004 Expose expandedMonomersToAtoms to Python API.
3174+
// This function fully expands all template atoms (monomers) in a macromolecule to regular atoms.
3175+
// It creates a working copy of the molecule to avoid side effects, marks all template atoms as
3176+
// expanded, then calls the internal expandedMonomersToAtoms() method to perform the actual
3177+
// expansion. The result is a new molecule with all monomers fully expanded to their atomic
3178+
// structures, suitable for molecular weight calculations and compatibility with third-party tools.
3179+
CEXPORT int indigoExpandedMonomersToAtoms(int molecule)
3180+
{
3181+
INDIGO_BEGIN
3182+
{
3183+
BaseMolecule& mol = self.getObject(molecule).getBaseMolecule();
3184+
3185+
// Create a working copy to avoid side effects on the original molecule
3186+
std::unique_ptr<IndigoMolecule> work_mol = std::make_unique<IndigoMolecule>();
3187+
QS_DEF(Array<int>, work_mapping);
3188+
work_mol->mol.clone(mol, 0, &work_mapping);
3189+
3190+
// Set display options on template atoms in the working copy
3191+
// This marks them as expanded so expandedMonomersToAtoms() will process them.
3192+
// We validate each template atom's occurrence index before accessing to detect
3193+
// data corruption issues early.
3194+
for (int v_idx = work_mol->mol.vertexBegin(); v_idx != work_mol->mol.vertexEnd(); v_idx = work_mol->mol.vertexNext(v_idx))
3195+
{
3196+
if (!work_mol->mol.isTemplateAtom(v_idx))
3197+
continue;
3198+
3199+
// Get the template occurrence index - this should not throw since we checked isTemplateAtom()
3200+
int template_occur_idx = work_mol->mol.getTemplateAtomOccurrence(v_idx);
3201+
3202+
// Validate that the occurrence index is valid in the pool
3203+
// If invalid, this indicates data corruption and we should fail rather than silently skip
3204+
if (!work_mol->mol.isValidTemplateOccurrence(template_occur_idx))
3205+
{
3206+
throw IndigoError("expandedMonomersToAtoms: template atom #%d has invalid occurrence index %d (data corruption detected)", v_idx,
3207+
template_occur_idx);
3208+
}
3209+
3210+
// Mark this template atom as expanded
3211+
work_mol->mol.setTemplateAtomDisplayOption(v_idx, DisplayOption::Expanded);
3212+
}
3213+
3214+
// Call expandedMonomersToAtoms on the working copy
3215+
// This method clones internally and returns a molecule with all expanded monomers
3216+
// converted to regular atoms.
3217+
std::unique_ptr<BaseMolecule> expanded = work_mol->mol.expandedMonomersToAtoms();
3218+
3219+
// Create new IndigoMolecule for the final result
3220+
std::unique_ptr<IndigoMolecule> new_mol = std::make_unique<IndigoMolecule>();
3221+
QS_DEF(Array<int>, mapping);
3222+
new_mol->mol.clone(*expanded, 0, &mapping);
3223+
3224+
// Expand group pseudoatoms (OH, NH2, etc.) to explicit atoms for V3000 interoperability
3225+
expandGroupPseudoatomsInMolecule(new_mol->mol);
3226+
3227+
// Copy properties from original molecule
3228+
auto& props = self.getObject(molecule).getProperties();
3229+
new_mol->copyProperties(props);
3230+
3231+
// Add to session and return ID
3232+
return self.addObject(new_mol.release());
3233+
}
3234+
INDIGO_END(-1);
3235+
}

api/cpp/src/IndigoBaseMolecule.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
***************************************************************************/
1818

1919
#include "IndigoBaseMolecule.h"
20+
#include "IndigoMolecule.h"
2021
#include "IndigoSession.h"
2122

2223
#include <indigo.h>
@@ -47,3 +48,9 @@ IndigoAtom IndigoBaseMolecule::getAtom(int atomIndex) const
4748
session()->setSessionId();
4849
return IndigoAtom(session()->_checkResult(indigoGetAtom(id(), atomIndex)), session());
4950
}
51+
52+
IndigoMolecule IndigoBaseMolecule::expandedMonomersToAtoms() const
53+
{
54+
session()->setSessionId();
55+
return IndigoMolecule(session()->_checkResult(indigoExpandedMonomersToAtoms(id())), session());
56+
}

api/cpp/src/IndigoBaseMolecule.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323

2424
namespace indigo_cpp
2525
{
26+
class IndigoMolecule;
27+
2628
class IndigoBaseMolecule : public IndigoChemicalStructure
2729
{
2830
protected:
@@ -38,5 +40,8 @@ namespace indigo_cpp
3840
std::string ctfile() const override;
3941

4042
IndigoAtom getAtom(int atomIndex) const;
43+
44+
/** Converts expanded template atoms (monomers) to regular atoms. Returns a new molecule. */
45+
IndigoMolecule expandedMonomersToAtoms() const;
4146
};
4247
}

api/dotnet/src/IndigoLib.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System.Runtime.InteropServices;
1+
using System.Runtime.InteropServices;
22
using System.Security;
33

44
namespace com.epam.indigo
@@ -1335,6 +1335,12 @@ public static extern int indigoSetSGroupBrackets(int sgroup, int brk_style, floa
13351335
[DllImport("indigo"), SuppressUnmanagedCodeSecurity]
13361336
public static extern int indigoExpandAbbreviations(int structure);
13371337

1338+
[DllImport("indigo"), SuppressUnmanagedCodeSecurity]
1339+
public static extern int indigoExpandGroupPseudoatoms(int molecule);
1340+
1341+
[DllImport("indigo"), SuppressUnmanagedCodeSecurity]
1342+
public static extern int indigoExpandedMonomersToAtoms(int molecule);
1343+
13381344
[DllImport("indigo"), SuppressUnmanagedCodeSecurity]
13391345
public static extern int indigoIterateTautomers(int structure, string parameters);
13401346

api/dotnet/src/IndigoObject.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System;
1+
using System;
22
using System.Collections;
33

44
namespace com.epam.indigo
@@ -2320,6 +2320,16 @@ public int expandAbbreviations()
23202320
return dispatcher.checkResult(IndigoLib.indigoExpandAbbreviations(self));
23212321
}
23222322

2323+
/// <summary>
2324+
/// Converts expanded template atoms (monomers) to regular atoms.
2325+
/// Returns a new molecule; the original is not modified.
2326+
/// </summary>
2327+
public IndigoObject expandedMonomersToAtoms()
2328+
{
2329+
dispatcher.setSessionID();
2330+
return new IndigoObject(dispatcher, dispatcher.checkResult(IndigoLib.indigoExpandedMonomersToAtoms(self)));
2331+
}
2332+
23232333
public int nameToStructure(string name, string parameters)
23242334
{
23252335
dispatcher.setSessionID();

api/java/indigo/src/main/java/com/epam/indigo/IndigoLib.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -956,6 +956,10 @@ int indigoCreateEdgeSubmolecule(
956956

957957
int indigoExpandAbbreviations(int structure);
958958

959+
int indigoExpandGroupPseudoatoms(int molecule);
960+
961+
int indigoExpandedMonomersToAtoms(int molecule);
962+
959963
int indigoIterateTautomers(int structure, String params);
960964

961965
int indigoNameToStructure(String name, String params);

0 commit comments

Comments
 (0)