Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 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
4 changes: 4 additions & 0 deletions api/c/indigo/indigo.h
Original file line number Diff line number Diff line change
Expand Up @@ -1002,6 +1002,10 @@ 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* indigoGetFragmentSdf(int item);

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.

Minor: Consider align naming to support consistency across application.

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.

naming updated

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

Expand Down
73 changes: 73 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,10 @@
#include "indigo_savers.h"

#include <ctime>
#include <memory>

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

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

// Counts atoms that live outside the main molecule graph. R-groups are stored
// independently of the molecule's connected components: an R-group fragment is a
// separate sub-molecule whose atoms are not vertices of the main graph, so the
// component iteration never visits them on its own. Returns the number of such
// out-of-graph atoms (currently those held by R-group fragments).
static int countOutOfGraphAtoms(BaseMolecule& mol)

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.

it seems like this function just counts number of all atoms in r-groups

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.

logic updated

{
int count = 0;
for (int i = 1; i <= mol.rgroups.getRGroupCount(); i++)
{
RGroup& rgroup = mol.rgroups.getRGroup(i);
for (int j = rgroup.fragments.begin(); j != rgroup.fragments.end(); j = rgroup.fragments.next(j))
count += rgroup.fragments[j]->vertexCount();
}
return count;
}

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. Each
// component clone also carries the molecule's R-groups, so once at least one
// component is written the out-of-graph (R-group) atoms are written with it.
BaseMolecule& mol = object.getBaseMolecule();
int written_atoms = 0;
IndigoComponentsIter fragments(mol);
while (fragments.hasNext())
{
std::unique_ptr<IndigoObject> fragment(fragments.next());
std::unique_ptr<IndigoObject> clone(fragment->clone());
Comment thread
NikolaiBalabanov marked this conversation as resolved.
Outdated
written_atoms += clone->getBaseMolecule().vertexCount();
IndigoSdfSaver::append(output, *clone);
}

// R-groups and the connected components are independent: an R-group may or may
// not be pulled into a component record. If, after writing every component,
// atoms remain that were not part of any written fragment (e.g. a free R-group
// whose atoms live outside the main graph), emit the whole molecule as one more
// record so the owning structure is preserved instead of being lost. (#1256)
if (written_atoms == 0 && countOutOfGraphAtoms(mol) > 0)

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.

it will make more sense to write each r-group independent if it is does not belong to any existing structure. also, it makes sense to consider not only written_atoms=0 case (e.g. if structure contains r-group assigned to atoms, and r-group outside the graph

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.

logic updated

IndigoSdfSaver::append(output, object);
}

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

CEXPORT const char* indigoGetFragmentSdf(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* indigoGetFragmentSdf(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 getFragmentSdf()
{
dispatcher.setSessionID();
return dispatcher.checkResult(IndigoLib.indigoGetFragmentSdf(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 indigoGetFragmentSdf(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 getFragmentSdf() {
dispatcher.setSessionID();
return Indigo.checkResultString(this, lib.indigoGetFragmentSdf(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.indigoGetFragmentSdf.restype = c_char_p
IndigoLib.lib.indigoGetFragmentSdf.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
14 changes: 14 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,20 @@ def cml(self):

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

def getFragmentSdf(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: connected components for a molecule or
constituent molecules for a reaction. Each fragment is written as a
separate SDF record.

Returns:
str: SDF string
"""

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

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

Expand Down
2 changes: 2 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,2 @@
*** KET to SDF ***
1256-ketR20-from2815.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.getFragmentSdf()
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.getFragmentSdf()
compare_diff(ref_path, filename + ".sdf", sdf)

print("*** KET-monomer library to SDF ***")
Expand Down
36 changes: 36 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,36 @@
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 = ["1256-ketR20-from2815"]

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.

add tests with
2 independent r-groups
1 standard r-group and 2 independent r-group
reaction with 1 and 2 independent r-groups

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.

Could you please provide specific files in ket format to fill the gap?

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.

Some similar data has been added

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.

On top of the cases already requested above, please also add a case with ≥2 disconnected normal components plus one attached R-group (e.g. a simple salt)

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.

Could you please provide specific files in ket format to fill the gap?

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.

Some similar data has been added


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.getFragmentSdf()
compare_diff(ref_path, filename + ".sdf", sdf)
Loading
Loading