Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions api/c/indigo/indigo.h
Original file line number Diff line number Diff line change
Expand Up @@ -1002,6 +1002,12 @@ CEXPORT long long indigoTell64(int handle);

// Saves the molecule to an SDF output stream
CEXPORT int indigoSdfAppend(int output, int item);

// Splits the item into fragments (connected components for a molecule,
// constituent molecules for a reaction) and returns the whole multi-record
// SDF as a string. Single entry point for SDF-by-fragments serialization.
CEXPORT const char* indigoFragmentedSdf(int item);

// Saves the molecule to a multiline SMILES output stream
CEXPORT int indigoSmilesAppend(int output, int item);

Expand Down
112 changes: 112 additions & 0 deletions api/c/indigo/src/indigo_savers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@
#include "indigo_savers.h"

#include <ctime>
#include <memory>
#include <unordered_set>

#include "indigo_molecule.h"
#include "indigo_reaction.h"

#include "base_cpp/output.h"
#include "base_cpp/scanner.h"
Expand Down Expand Up @@ -353,6 +358,99 @@ void IndigoSdfSaver::saveMonomerLibrary(const MonomerTemplateLibrary& monomers_l
_output.flush();
}

// Build the component directly with SKIP_RGROUPS and copyUsedRGroupsFrom()
// instead, so each R-group is copied at most once.
static IndigoObject* makeComponentObject(BaseMolecule& mol, int index)
{
std::unique_ptr<IndigoBaseMolecule> res;
BaseMolecule* newmol;
if (mol.isQueryMolecule())
{
res = std::make_unique<IndigoQueryMolecule>();
newmol = &(((IndigoQueryMolecule*)res.get())->qmol);
}
else
{
res = std::make_unique<IndigoMolecule>();
newmol = &(((IndigoMolecule*)res.get())->mol);
}

Filter filter(mol.getDecomposition().ptr(), Filter::EQ, index);
newmol->makeSubmolecule(mol, filter, 0, 0, SKIP_RGROUPS);
newmol->copyUsedRGroupsFrom(mol);
for (auto it = newmol->properties().begin(); it != newmol->properties().end(); ++it)
res->getProperties().merge(newmol->properties().value(it));

return res.release();
}

// Builds a record holding only R-group `keep_idx` from `mol`, with zero
// main-graph atoms.
static IndigoObject* makeSingleRGroupObject(BaseMolecule& mol, int keep_idx)
{
std::unique_ptr<IndigoBaseMolecule> res;
BaseMolecule* newmol;
if (mol.isQueryMolecule())
{
res = std::make_unique<IndigoQueryMolecule>();
newmol = &(((IndigoQueryMolecule*)res.get())->qmol);
}
else
{
res = std::make_unique<IndigoMolecule>();
newmol = &(((IndigoMolecule*)res.get())->mol);
}

QS_DEF(Array<int>, no_vertices);
no_vertices.clear();
newmol->makeSubmolecule(mol, no_vertices, 0, SKIP_RGROUPS);
newmol->rgroups.getRGroup(keep_idx).copy(mol.rgroups.getRGroup(keep_idx));

return res.release();
}

void IndigoSdfSaver::appendFragments(Output& output, IndigoObject& object)
{
if (IndigoBaseReaction::is(object))
{
// A reaction is split into its constituent molecules.
IndigoReactionIter fragments(object.getBaseReaction(), IndigoReactionIter::MOLECULES);
while (fragments.hasNext())
{
std::unique_ptr<IndigoObject> fragment(fragments.next());
std::unique_ptr<IndigoObject> clone(fragment->clone());
IndigoSdfSaver::append(output, *clone);
}
return;
}

// A molecule is split into the connected components of its main graph.
BaseMolecule& mol = object.getBaseMolecule();
std::unordered_set<int> written_rgroups;
IndigoComponentsIter fragments(mol);
while (fragments.hasNext())
{
std::unique_ptr<IndigoObject> fragment(fragments.next());
std::unique_ptr<IndigoObject> clone(makeComponentObject(mol, fragment->getIndex()));
BaseMolecule& clone_mol = clone->getBaseMolecule();
for (int i = 1; i <= clone_mol.rgroups.getRGroupCount(); i++)
if (clone_mol.rgroups.getRGroup(i).fragments.size() > 0)
written_rgroups.insert(i);
IndigoSdfSaver::append(output, *clone);
}

// Any R-group no component claimed (e.g. a free R-group with no r-site
// reference) does not belong to another structure, so each one is emitted
// independently as its own atom-less record. (#1256)
for (int i = 1; i <= mol.rgroups.getRGroupCount(); i++)
{
if (mol.rgroups.getRGroup(i).fragments.size() == 0 || written_rgroups.count(i))
continue;
std::unique_ptr<IndigoObject> leftover(makeSingleRGroupObject(mol, i));
IndigoSdfSaver::append(output, *leftover);
}
}

CEXPORT int indigoSdfAppend(int output, int molecule)
{
INDIGO_BEGIN
Expand All @@ -365,6 +463,20 @@ CEXPORT int indigoSdfAppend(int output, int molecule)
INDIGO_END(-1);
}

CEXPORT const char* indigoFragmentedSdf(int item)
{
INDIGO_BEGIN
{
IndigoObject& obj = self.getObject(item);
auto& tmp = self.getThreadTmpData();
ArrayOutput out(tmp.string);
IndigoSdfSaver::appendFragments(out, obj);
tmp.string.push(0);
return tmp.string.ptr();
}
INDIGO_END(0);
}

//
// IndigoSmilesSaver
//
Expand Down
4 changes: 4 additions & 0 deletions api/c/indigo/src/indigo_savers.h
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ class IndigoSdfSaver : public IndigoSaver
const char* debugInfo() const override;
static void append(Output& output, IndigoObject& object);
static void appendMolfile(Output& output, IndigoObject& object);
// Splits the object into fragments (connected components for a molecule,
// constituent molecules for a reaction) and writes each one as a separate
// SDF record. Single entry point shared by all SDF-by-fragments consumers.
static void appendFragments(Output& output, IndigoObject& object);
void saveMonomerLibrary(const MonomerTemplateLibrary& monomers_library);

protected:
Expand Down
3 changes: 3 additions & 0 deletions api/dotnet/src/IndigoLib.cs
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,9 @@ public unsafe class IndigoLib
[DllImport("indigo"), SuppressUnmanagedCodeSecurity]
public static extern byte* indigoMonomerLibrary(int lib);

[DllImport("indigo"), SuppressUnmanagedCodeSecurity]
public static extern byte* indigoFragmentedSdf(int item);

[DllImport("indigo"), SuppressUnmanagedCodeSecurity]
public static extern int indigoSaveCdxml(int molecule, int output);

Expand Down
6 changes: 6 additions & 0 deletions api/dotnet/src/IndigoObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,12 @@ public string monomerLibrary()
return dispatcher.checkResult(IndigoLib.indigoMonomerLibrary(self));
}

public string fragmentedSdf()
{
dispatcher.setSessionID();
return dispatcher.checkResult(IndigoLib.indigoFragmentedSdf(self));
}

public void saveCml(string filename)
{
dispatcher.setSessionID();
Expand Down
2 changes: 2 additions & 0 deletions api/java/indigo/src/main/java/com/epam/indigo/IndigoLib.java
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,8 @@ public interface IndigoLib extends Library {

Pointer indigoJson(int object);

Pointer indigoFragmentedSdf(int item);

Pointer indigoMonomerLibrary(int object);

@SuppressWarnings("checkstyle:Indentation")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,11 @@ public String monomerLibrary() {
return Indigo.checkResultString(this, lib.indigoMonomerLibrary(self));
}

public String fragmentedSdf() {
dispatcher.setSessionID();
return Indigo.checkResultString(this, lib.indigoFragmentedSdf(self));
}

public void saveCml(String filename) {
dispatcher.setSessionID();
Indigo.checkResult(this, lib.indigoSaveCmlToFile(self, filename));
Expand Down
2 changes: 2 additions & 0 deletions api/python/indigo/indigo/indigo_lib.py
Original file line number Diff line number Diff line change
Expand Up @@ -1112,6 +1112,8 @@ def __init__(self) -> None:
IndigoLib.lib.indigoTell.argtypes = [c_int]
IndigoLib.lib.indigoSdfAppend.restype = c_int
IndigoLib.lib.indigoSdfAppend.argtypes = [c_int, c_int]
IndigoLib.lib.indigoFragmentedSdf.restype = c_char_p
IndigoLib.lib.indigoFragmentedSdf.argtypes = [c_int]
IndigoLib.lib.indigoSmilesAppend.restype = c_int
IndigoLib.lib.indigoSmilesAppend.argtypes = [c_int, c_int]
IndigoLib.lib.indigoRdfHeader.restype = c_int
Expand Down
11 changes: 11 additions & 0 deletions api/python/indigo/indigo/indigo_object.py
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,17 @@ def cml(self):

return IndigoLib.checkResultString(self._lib().indigoCml(self.id))

def fragmentedSdf(self):
"""Structure method returns the structure as a string in SDF format,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocker: Let's either add comment in all wrappers or take it off from all places.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it is depended from file. indigo_object.py has comments in this style.

splitting it into fragments.

Returns:
str: SDF string
"""
return IndigoLib.checkResultString(
self._lib().indigoFragmentedSdf(self.id)
)

def saveCdxml(self, filename):
"""Molecule method saves the structure into a CDXML file

Expand Down
8 changes: 8 additions & 0 deletions api/tests/integration/ref/formats/ket_to_sdf_1256.py.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
*** KET to SDF ***
1256-ketR20-from2815.sdf:SUCCEED
1256-query-independent-rgroup.sdf:SUCCEED
1256-salt-attached-rgroup.sdf:SUCCEED
1256-standard-plus-two-independent.sdf:SUCCEED
1256-two-independent-rgroups.sdf:SUCCEED
1256-reaction-one-independent-rgroup.sdf:SUCCEED
1256-reaction-two-independent-rgroups.sdf:SUCCEED
14 changes: 2 additions & 12 deletions api/tests/integration/tests/formats/ket_to_sdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,7 @@ def expect_monomer_library_load_error(root, filename, expected_error):
except IndigoException as e:
print(" %s" % (getIndigoExceptionText(e)))

buffer = indigo.writeBuffer()
sdfSaver = indigo.createSaver(buffer, "sdf")
for frag in ket.iterateComponents():
sdfSaver.append(frag.clone())
sdfSaver.close()
sdf = buffer.toString()
sdf = ket.fragmentedSdf()
compare_diff(ref_path, filename + ".sdf", sdf)

root = joinPathPy("reactions/", __file__)
Expand All @@ -80,12 +75,7 @@ def expect_monomer_library_load_error(root, filename, expected_error):
except IndigoException as e:
print(" %s" % (getIndigoExceptionText(e)))

buffer = indigo.writeBuffer()
sdfSaver = indigo.createSaver(buffer, "sdf")
for mol in ket.iterateMolecules():
sdfSaver.append(mol.clone())
sdfSaver.close()
sdf = buffer.toString()
sdf = ket.fragmentedSdf()
compare_diff(ref_path, filename + ".sdf", sdf)

print("*** KET-monomer library to SDF ***")
Expand Down
64 changes: 64 additions & 0 deletions api/tests/integration/tests/formats/ket_to_sdf_1256.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import os
import sys

sys.path.append(
os.path.normpath(
os.path.join(os.path.abspath(__file__), "..", "..", "..", "common")
)
)
from common.util import compare_diff
from env_indigo import * # noqa

indigo = Indigo()
indigo.setOption("json-saving-pretty", True)
indigo.setOption("molfile-saving-skip-date", True)
indigo.setOption("ignore-stereochemistry-errors", True)

print("*** KET to SDF ***")

root = joinPathPy("molecules/", __file__)
ref_path = joinPathPy("ref/", __file__)

files = [
Comment thread
NikolaiBalabanov marked this conversation as resolved.
"1256-ketR20-from2815",
"1256-salt-attached-rgroup",
"1256-two-independent-rgroups",
"1256-standard-plus-two-independent",
"1256-query-independent-rgroup",
]

files.sort()
for filename in files:
try:
ket = indigo.loadMoleculeFromFile(
os.path.join(root, filename + ".ket")
)
except IndigoException:
ket = indigo.loadQueryMoleculeFromFile(
os.path.join(root, filename + ".ket")
)

sdf = ket.fragmentedSdf()
compare_diff(ref_path, filename + ".sdf", sdf)

root = joinPathPy("reactions/", __file__)
ref_path = joinPathPy("ref/", __file__)

files = [
"1256-reaction-one-independent-rgroup",
"1256-reaction-two-independent-rgroups",
]

files.sort()
for filename in files:
try:
ket = indigo.loadReactionFromFile(
os.path.join(root, filename + ".ket")
)
except IndigoException:
ket = indigo.loadQueryReactionFromFile(
os.path.join(root, filename + ".ket")
)

sdf = ket.fragmentedSdf()
compare_diff(ref_path, filename + ".sdf", sdf)
Loading
Loading