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
10 changes: 9 additions & 1 deletion core/indigo-core/graph/graph.h
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,15 @@ namespace indigo
void _calculateSSSRInit();
void _calculateSSSRByCycleBasis(CycleBasis& basis);
void _calculateSSSRAddEdgesAndVertices(const Array<int>& cycle, List<int>& edges, List<int>& vertices);
void _calculateComponents(const std::list<std::unordered_set<int>>& external_neighbors = {{}});
// No external neighbours means an empty list, not a list holding one empty
// set: the two differ to the cache-mode flag below.
void _calculateComponents(const std::list<std::unordered_set<int>>& external_neighbors = {});

// Vertex and edge changes drop the cached decomposition themselves. A heir
// that lets the answer depend on anything else - external neighbours do -
// has to call this when that something changes.
void invalidateComponents();

// This is a bad hack for those who are too lazy to handle the mappings.
// NEVER USE IT.
void _cloneGraph_KeepIndices(const Graph& other);
Expand Down
9 changes: 8 additions & 1 deletion core/indigo-core/graph/src/graph.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -713,7 +713,14 @@ void Graph::_calculateComponents(const std::list<std::unordered_set<int>>& exter
}

_components_valid = true;
_components_used_external = !external_neighbors.empty() && !external_neighbors.front().empty();
// Any set at all counts. Judging by the first one alone made an empty leading set
// read as "computed without external neighbours", recomputing on every call.
_components_used_external = !external_neighbors.empty();
}

void Graph::invalidateComponents()
{
_components_valid = false;
}

int Graph::vertexComponent(int v_idx)
Expand Down
5 changes: 4 additions & 1 deletion core/indigo-core/molecule/base_molecule.h
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,6 @@ namespace indigo
// merging with molecule and cloning procedures
enum
{
SKIP_ALL = 0xFF,
SKIP_CIS_TRANS = 0x01,
SKIP_STEREOCENTERS = 0x02,
SKIP_XYZ = 0x04,
Expand All @@ -137,6 +136,10 @@ namespace indigo
// skipped with them, since a bond without its group is meaningless.
SKIP_ATTACHMENT_GROUPS = 0x100,
SKIP_HAPTIC_BONDS = 0x200,
// Last on purpose: every flag above has to appear here, so a new one is added
// in the line right below itself rather than by recounting a hex literal.
SKIP_ALL = SKIP_CIS_TRANS | SKIP_STEREOCENTERS | SKIP_XYZ | SKIP_RGROUP_FRAGMENTS | SKIP_ATTACHMENT_POINTS | SKIP_TGROUPS |
SKIP_TEMPLATE_ATTACHMENT_POINTS | SKIP_RGROUPS | SKIP_ATTACHMENT_GROUPS | SKIP_HAPTIC_BONDS,
};

class Molecule;
Expand Down
62 changes: 62 additions & 0 deletions core/indigo-core/molecule/ket_keys.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/****************************************************************************
* Copyright (C) from 2009 to Present EPAM Systems.
*
* This file is part of Indigo toolkit.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
***************************************************************************/

#ifndef __ket_keys__
#define __ket_keys__

// Field names of the KET wire format, shared by its readers and writers.
// Covers the haptic bond feature (#3233) only; the rest of the vocabulary is
// still inline. Contract: Task/3233/KET-CONTRACT.md.
//
// A KetKey prefix marks a JSON key. Names without it are KET *values*, in the style
// of KetConnectionSingle / KetConnectionHydro (monomers_defs.h). The prefix also
// keeps both apart from the Ket* classes of ket_objects.h - KetKeyEndpoint1 cannot
// be mistaken for KetConnectionEndPoint.

namespace indigo
{
// Generic keys: each is spelled the same wherever it appears, but says nothing
// about what the object around it is. "type" alone distinguishes a molecule
// node from an rgroup, a monomerTemplate, a query component and an atom list.
inline constexpr const char* KetKeyType = "type";
inline constexpr const char* KetKeyId = "id";
inline constexpr const char* KetKeyAtoms = "atoms";

// Connections of the root.
inline constexpr const char* KetKeyConnections = "connections";
// A haptic connection is discriminated by KetKeyType, the monomer ones carry
// this key instead (KET-CONTRACT.md §5.1). Reading the wrong one silently skips
// the connection.
inline constexpr const char* KetKeyConnectionType = "connectionType";
inline constexpr const char* KetConnectionHaptic = "haptic"; // value of KetKeyType

inline constexpr const char* KetKeyEndpoint1 = "endpoint1";
inline constexpr const char* KetKeyEndpoint2 = "endpoint2";
inline constexpr const char* KetKeyMoleculeId = "moleculeId";
inline constexpr const char* KetKeyAtomId = "atomId";
inline constexpr const char* KetKeyAttachmentGroupId = "attachmentGroupId";

// Attachment groups, declared by a molecule node; a group object uses the
// generic KetKeyId and KetKeyAtoms above.
inline constexpr const char* KetKeyAttachmentGroups = "attachmentGroups";

// Not a key: the prefix a molecule node is referenced by, as in "mol0".
inline constexpr const char* KetMoleculeRefPrefix = "mol";
}

#endif
51 changes: 14 additions & 37 deletions core/indigo-core/molecule/molecule_attachment_groups.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,16 +42,6 @@ namespace indigo
class DLLEXPORT AttachmentGroup : public Reusable
{
public:
// Attributes of the anchor atom that the file formats put the charge and
// the radical of the pi system on, and which is absorbed on loading.
// Carried through unchanged: never recomputed and never spread over the
// member atoms, so a structure charged either way comes back as it came in.
struct Anchor
{
int charge = 0;
int radical = 0;
};

AttachmentGroup();
~AttachmentGroup() override;

Expand All @@ -75,28 +65,16 @@ namespace indigo
// the whole group, never a truncated one.
bool remapAtoms(const Array<int>& atom_mapping);

const Anchor& anchor() const
{
return _anchor;
}
void setAnchor(const Anchor& anchor)
{
_anchor = anchor;
}

private:
void _reset(); // the one place the fields are listed; not virtual — called from the constructor

std::vector<int> _atoms;
Anchor _anchor;
};

// The attachment groups of one molecule.
//
// Every removal path of BaseMolecule must reach this class: see
// onAtomsRemoved(). A single group is removed through
// BaseMolecule::removeAttachmentGroup(), which also drops the haptic bonds
// that address it.
// Knows nothing of the haptic bonds that address its groups by index, so every
// operation that drops or renumbers a group reports that through an out-param
// and leaves propagating it to BaseMolecule.
class DLLEXPORT MoleculeAttachmentGroups
{
public:
Expand All @@ -116,15 +94,16 @@ namespace indigo
bool hasGroup(int idx) const;
int groupCount() const;

// Removes the group. Indices of the other groups are unaffected — callers
// hold group indices across such calls.
private:
friend class BaseMolecule;
// Indices of the other groups are unaffected — callers hold group indices
// across such calls.
void removeGroup(int idx);

// Must be called when the molecule removes atoms, with the same mapping
// BaseMolecule::removeAtoms builds (-1 = removed). Groups that lose a
// member are dropped whole; their indices are reported in `removed_groups`
// because the haptic bonds that reference them must go too, and this
// container does not know about them.
public:
// Takes the mapping BaseMolecule::removeAtoms builds (-1 = removed). A group
// that loses a member is dropped whole, never truncated; `removed_groups`
// receives the indices of those dropped.
void onAtomsRemoved(const Array<int>& atom_mapping, Array<int>& removed_groups);

// Index walk over the live groups, mirroring the pool: end() is one
Expand All @@ -136,11 +115,9 @@ namespace indigo
void clear();
bool isEmpty() const;

// Copies the groups of `other` that survive a submolecule mapping (-1
// means "dropped"), under the same all-or-nothing rule as remapAtoms().
// `group_mapping` receives, per source group index, the index it got here
// or -1: the haptic bonds of the same merge address their groups by index
// and have no other way to follow them.
// Copies the groups of `other` that survive `atom_mapping` (-1 = dropped),
// under the same all-or-nothing rule as remapAtoms(). `group_mapping`
// receives, per source group index, the index it got here or -1.
void mergeWithSubmolecule(const MoleculeAttachmentGroups& other, const Array<int>& atom_mapping, Array<int>& group_mapping);

private:
Expand Down
27 changes: 11 additions & 16 deletions core/indigo-core/molecule/molecule_haptic_bonds.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,13 @@ namespace indigo
{
class MoleculeAttachmentGroups;

// A bond whose ends are not restricted to single atoms. Never an edge of the
// graph: it appears in neither vertices() nor edges(), so no traversal of the
// core has to learn to skip it.
// A bond whose ends are not restricted to single atoms.
// Never an edge of the graph: appears in neither vertices() nor edges().
class DLLEXPORT HapticBond : public Reusable
{
public:
// One end of the bond: a single atom, or an attachment group acting as
// one. Exactly one of the two is set, which is why this is a type and not
// a pair of indices.
// One end of the bond: a single atom, or an attachment group acting as one.
// Exactly one of the two is set.
class Endpoint
{
public:
Expand All @@ -75,7 +73,6 @@ namespace indigo
HapticBond(const HapticBond&) = delete;
HapticBond& operator=(const HapticBond&) = delete;

// Reusable: restore the default-constructed state.
void reuse() override;

// Initializing form used by the pool's add(begin, end, type).
Expand Down Expand Up @@ -116,9 +113,8 @@ namespace indigo
};

// The haptic bonds of one molecule.
//
// Every removal path of BaseMolecule must reach this class: see
// onAtomsRemoved() and onGroupRemoved().
// Reached from the removal paths of BaseMolecule through onAtomsRemoved() and
// onGroupRemoved(); a bond never outlives an endpoint.
class DLLEXPORT MoleculeHapticBonds
{
public:
Expand Down Expand Up @@ -155,14 +151,13 @@ namespace indigo
int firstAtom(int idx, const MoleculeAttachmentGroups& groups) const;
int lastAtom(int idx, const MoleculeAttachmentGroups& groups) const;

// Must be called when the molecule removes atoms, with the same mapping
// BaseMolecule::removeAtoms builds (-1 = removed). A bond that loses an
// endpoint atom is dropped whole.
// Takes the mapping BaseMolecule::removeAtoms builds (-1 = removed). A bond
// that loses an endpoint atom is dropped whole.
void onAtomsRemoved(const Array<int>& atom_mapping);

// Must be called for every attachment group the molecule removes: the
// group pool recycles freed indices, so a bond left behind would silently
// attach itself to the next group to take the index.
// Must be called for every attachment group the molecule removes: the group
// pool recycles freed indices, so a bond left behind would silently attach
// itself to the next group to take that index.
void onGroupRemoved(int group_idx);

// Copies the bonds of `other` that survive both mappings (-1 means
Expand Down
10 changes: 10 additions & 0 deletions core/indigo-core/molecule/molecule_json_loader.h
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,16 @@ namespace indigo
void parseBonds(const rapidjson::Value& bonds, BaseMolecule& mol);
void parseHighlight(const rapidjson::Value& highlight, BaseMolecule& mol);
void parseSGroups(const rapidjson::Value& sgroups, BaseMolecule& mol);

// Adds the node's attachment groups to the already-merged molecule, taking
// the atom mapping of that merge. Returns the KET id -> group index map
// that haptic connections are resolved through.
std::map<std::string, int> parseAttachmentGroups(const rapidjson::Value& groups, BaseMolecule& mol, const Array<int>& atom_mapping);

static HapticBond::Endpoint resolveHapticEndpoint(const rapidjson::Value& endpoint, const PtrArray<Array<int>>& mol_mappings,
const std::vector<std::map<std::string, int>>& ag_mappings);
void loadHapticConnection(const rapidjson::Value& connection, BaseMolecule& mol, const PtrArray<Array<int>>& mol_mappings,
const std::vector<std::map<std::string, int>>& ag_mappings);
void parseProperties(const rapidjson::Value& props, BaseMolecule& mol);
void setStereoFlagPosition(const rapidjson::Value& pos, int fragment_index, BaseMolecule& mol);
void handleSGroup(SGroup& sgroup, const std::unordered_set<int>& atoms, BaseMolecule& bmol);
Expand Down
14 changes: 14 additions & 0 deletions core/indigo-core/molecule/molecule_json_saver.h
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,21 @@ namespace indigo
protected:
void saveMoleculeReference(int mol_id, JsonWriter& writer);
void saveEndpoint(BaseMolecule& mol, const std::string& ep, int beg_idx, int end_idx, JsonWriter& writer, bool hydrogen = false);
void saveAtomEndpoint(const std::string& ep, int atom_idx, JsonWriter& writer);
int getMonomerNumber(int mon_idx);

// Distributes the groups of the whole molecule over the saved nodes: a group
// is declared by the node owning its atoms, while a haptic bond is a root
// connection and may cross the component split.
void collectAttachmentGroups(BaseMolecule& mol);
void saveAttachmentGroups(BaseMolecule& mol, int mol_id, JsonWriter& writer);
void saveHapticConnections(BaseMolecule& mol, JsonWriter& writer);
void saveHapticEndpoint(const std::string& ep, const HapticBond::Endpoint& endpoint, JsonWriter& writer);
// Whether anything of the molecule's haptic bonds reaches the KET file.
static bool hasHapticConnections(BaseMolecule& mol);
// Molecule node and KET id a group is addressed by.
std::pair<int, int> attachmentGroupRef(int group_idx) const;

void writeFloat(JsonWriter& writer, float f_value);
void writePos(JsonWriter& writer, const Vec3f& pos);

Expand Down Expand Up @@ -114,6 +127,7 @@ namespace indigo
std::vector<std::unique_ptr<BaseMolecule>> _no_template_molecules;
PtrArray<Array<int>> _mappings;
std::unordered_map<int, int> _atom_to_mol_id;
std::vector<std::vector<int>> _mol_attachment_groups;
std::optional<std::reference_wrapper<ReactionMultistepDetector>> _rmd;

private:
Expand Down
38 changes: 33 additions & 5 deletions core/indigo-core/molecule/src/base_molecule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -477,14 +477,20 @@ void BaseMolecule::_mergeWithSubmolecule_Sub(BaseMolecule& mol, const Array<int>
mergeSGroupsWithSubmolecule(mol, mapping, edge_mapping);

// attachment groups and the haptic bonds that address them by index
QS_DEF(Array<int>, group_mapping);
if (!(skip_flags & SKIP_ATTACHMENT_GROUPS))
{
QS_DEF(Array<int>, group_mapping);
attachment_groups.mergeWithSubmolecule(mol.attachment_groups, mapping, group_mapping);
if (!(skip_flags & SKIP_HAPTIC_BONDS))
haptic_bonds.mergeWithSubmolecule(mol.haptic_bonds, mapping, group_mapping);
else
{
// No group survives, so the bonds that address one go with them - while an
// atom-to-atom bond, which has no group at all, is none of that flag's business.
group_mapping.clear_resize(mol.attachment_groups.end());
group_mapping.fffill();
}

if (!(skip_flags & SKIP_HAPTIC_BONDS))
haptic_bonds.mergeWithSubmolecule(mol.haptic_bonds, mapping, group_mapping);

// highlighting
highlightSubmolecule(mol, mapping.ptr(), false);

Expand Down Expand Up @@ -1110,11 +1116,28 @@ void BaseMolecule::_checkHapticEndpoint(const HapticBond::Endpoint& endpoint)
{
if (!attachment_groups.hasGroup(endpoint.index()))
throw Error("haptic bond refers to a non-existent attachment group %d", endpoint.index());
if (attachment_groups.group(endpoint.index()).atoms().empty())

const auto& atoms = attachment_groups.group(endpoint.index()).atoms();
if (atoms.empty())
throw Error("haptic bond refers to an empty attachment group %d", endpoint.index());

// The group takes any int as a member, and this is the only place where it
// meets the molecule: an index nobody owns would reach the connectivity sets
// and every consumer of them.
for (int atom : atoms)
{
if (!hasVertex(atom))
throw Error("attachment group %d holds a non-existent atom %d", endpoint.index(), atom);
if (isTemplateAtom(atom))
throw Error("attachment group %d holds template atom %d", endpoint.index(), atom);
}
}
else if (!hasVertex(endpoint.index()))
throw Error("haptic bond refers to a non-existent atom %d", endpoint.index());
// A template atom stands for a whole monomer and is written as a node of its
// own, so no format has a way to address it as one end of a haptic bond.
else if (isTemplateAtom(endpoint.index()))
throw Error("a haptic bond cannot reach template atom %d", endpoint.index());
}

int BaseMolecule::addHapticBond(HapticBond::Endpoint begin, HapticBond::Endpoint end, int type)
Expand All @@ -1139,6 +1162,10 @@ int BaseMolecule::addHapticBond(HapticBond::Endpoint begin, HapticBond::Endpoint
throw Error("a haptic bond needs two different atoms");

const int idx = haptic_bonds.add(begin, end, type);
// A haptic bond holds atoms together without an edge, so the decomposition
// depends on it while the graph, which drops the cache on its own, does not
// notice the change at all.
invalidateComponents();
updateEditRevision();
return idx;
}
Expand All @@ -1147,6 +1174,7 @@ void BaseMolecule::removeAttachmentGroup(int idx)
{
attachment_groups.removeGroup(idx);
haptic_bonds.onGroupRemoved(idx);
invalidateComponents();
updateEditRevision();
}

Expand Down
Loading
Loading