Skip to content

Commit 8ec0880

Browse files
pr comments
1 parent 7beccb8 commit 8ec0880

6 files changed

Lines changed: 174 additions & 195 deletions

File tree

src/classes/structure.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ const StructureAtom *Structure::atom(int i) const { return atoms_[i].get(); }
124124
const std::vector<std::unique_ptr<StructureAtom>> &Structure::atoms() const { return atoms_; }
125125
std::vector<std::unique_ptr<StructureAtom>> &Structure::atoms() { return atoms_; }
126126

127-
// Return molecular species coordinates
127+
// Return positional instances
128128
const std::vector<std::vector<Vector3>> &Structure::instances() const { return instances_; }
129129
std::vector<std::vector<Vector3>> &Structure::instances() { return instances_; }
130130

src/nodes/detectMolecules.cpp

Lines changed: 127 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -37,23 +37,9 @@ NodeConstants::ProcessResult DetectMoleculesNode::process()
3737
inputStructure_.unFold();
3838

3939
// Return all discovered molecular fragment index vectors
40-
auto fragmentMap = findMolecularFragments(inputStructure_);
40+
auto fragmentMap = findMolecularFragments();
4141

42-
// Define lambda for capturing any molecular instances we detect
43-
auto appendInstances = [&](std::vector<std::vector<Vector3>> &instances, Structure &detectedMolecularStructure,
44-
const std::vector<int> &fragment) -> bool
45-
{
46-
if (!instances.empty())
47-
{
48-
detectedStructures_.emplace_back(copyStructureAtomsAndBonds(inputStructure_, detectedMolecularStructure, fragment))
49-
.instances() = instances;
50-
51-
return true;
52-
}
53-
return false;
54-
};
55-
56-
// Try selecting within the fragment from the first atom - if this captures all atoms we have a bound framework...
42+
// Check for a single, bound framework fragment
5743
if (fragmentMap.contains(inputStructure_.nAtoms()))
5844
return error(
5945
"Can't create molecular definitions since this unit cell appears to be a continuous framework/network. Consider "
@@ -62,120 +48,64 @@ NodeConstants::ProcessResult DetectMoleculesNode::process()
6248
std::set<const StructureAtom *> atomMask;
6349

6450
for (const auto &[size, fragments] : fragmentMap)
65-
for (const auto &fragment : fragments)
51+
for (const auto &[neta, fragment] : fragments)
6652
{
6753
// Create a provisional structure for the detected fragment
6854
Structure detectedStructure;
6955
detectedStructure.createBox(inputStructure_.box().axes());
7056
std::vector<std::vector<Vector3>> instances;
7157

7258
// Get fragment atoms
73-
auto fragmentAtoms = getFragmentAtoms(inputStructure_, fragment);
59+
auto fragmentAtoms = getFragmentAtoms(fragment);
7460

75-
// Remove fragments that are larger than 50 % of the structure
76-
if (size * 2 > inputStructure_.nAtoms())
61+
if (!neta.has_value())
7762
{
7863
addInstance(instances.emplace_back(), fragmentAtoms);
7964

8065
// Mask these fragment atoms
8166
for (const auto &unmasked : fragmentAtoms)
8267
atomMask.insert(unmasked);
83-
84-
appendInstances(instances, detectedStructure, fragment);
85-
86-
break;
8768
}
88-
89-
for (const auto &fragmentAtom : fragmentAtoms)
90-
{
91-
if (atomMask.contains(fragmentAtom))
92-
continue;
93-
94-
/*
95-
* Best NETA definition
96-
*/
97-
98-
// Set up the return value and bind its contents
99-
NETADefinition bestNETA;
100-
std::vector<const StructureAtom *> rootAtoms;
101-
102-
// Maintain a set of atoms matched by any NETA description we generate
103-
std::set<const StructureAtom *> alreadyMatched;
104-
105-
// Skip this atom?
106-
if (alreadyMatched.find(fragmentAtom) != alreadyMatched.end())
107-
continue;
108-
109-
// Create a NETA definition with this atom as the root
110-
NETADefinition neta;
111-
neta.create(static_cast<const AtomBase *>(fragmentAtom), std::nullopt,
112-
Flags<NETADefinition::NETACreationFlags>(NETADefinition::NETACreationFlags::ExplicitHydrogens,
113-
NETADefinition::NETACreationFlags::IncludeRootElement));
114-
115-
// Apply this match over the whole fragment
116-
std::vector<const StructureAtom *> currentRootAtoms;
117-
for (auto fragmentAtom : fragmentAtoms)
118-
{
119-
if (neta.matches(fragmentAtom))
120-
{
121-
currentRootAtoms.push_back(fragmentAtom);
122-
alreadyMatched.insert(fragmentAtom);
123-
}
124-
}
125-
126-
// Is this a better description?
127-
auto better = false;
128-
if (rootAtoms.empty() || currentRootAtoms.size() < rootAtoms.size())
129-
better = true;
130-
else if (currentRootAtoms.size() == rootAtoms.size())
131-
{
132-
// Replace the current match if there are more bonds on the current atom.
133-
if (fragmentAtom->nBonds() > rootAtoms.front()->nBonds())
134-
better = true;
135-
}
136-
137-
if (better)
138-
{
139-
bestNETA = neta;
140-
rootAtoms = currentRootAtoms;
141-
}
142-
143-
/*
144-
* Get instances
145-
*/
146-
147-
// Get all atoms belonging to fragments from the same fragment size group
148-
auto fragmentSizeGroupAtoms = getFragmentAtoms(inputStructure_, fragments);
149-
150-
// Iterate over all structural atoms, matching their unit cell atoms by NETA
151-
std::vector<std::set<const AtomBase *>> matchedUnitCellAtomSets;
152-
for (const auto &fragmentAtom : fragmentSizeGroupAtoms)
69+
else
70+
for (const auto &fragmentAtom : fragmentAtoms)
15371
{
15472
if (atomMask.contains(fragmentAtom))
15573
continue;
15674

157-
auto matchedPath = neta.matchedPath(fragmentAtom).set();
158-
if (!matchedPath.empty())
159-
{
160-
auto set = matchedUnitCellAtomSets.emplace_back(matchedPath);
75+
// Get all atoms belonging to fragments from the same fragment size group
76+
auto fragmentSizeGroupAtoms = getFragmentAtoms(fragments);
16177

162-
// Mask the current matched fragment atom
163-
for (const auto &matchedAtom : set)
164-
atomMask.insert(static_cast<const StructureAtom *>(matchedAtom));
78+
// Iterate over all structural atoms, matching their unit cell atoms by NETA
79+
std::vector<std::set<const AtomBase *>> matchedUnitCellAtomSets;
80+
for (const auto &fragmentAtom : fragmentSizeGroupAtoms)
81+
{
82+
if (atomMask.contains(fragmentAtom))
83+
continue;
84+
85+
auto matchedPath = neta->matchedPath(fragmentAtom).set();
86+
if (!matchedPath.empty())
87+
{
88+
auto set = matchedUnitCellAtomSets.emplace_back(matchedPath);
89+
90+
// Mask the current matched fragment atom
91+
for (const auto &matchedAtom : set)
92+
atomMask.insert(static_cast<const StructureAtom *>(matchedAtom));
93+
}
16594
}
166-
}
16795

168-
// Loop over matched unit cell atoms, retrieving instances
169-
for (const auto &matchedUnitCellAtoms : matchedUnitCellAtomSets)
170-
{
171-
if (matchedUnitCellAtoms.empty())
172-
continue;
96+
// Loop over matched unit cell atoms, retrieving instances
97+
for (const auto &matchedUnitCellAtoms : matchedUnitCellAtomSets)
98+
{
99+
if (matchedUnitCellAtoms.empty())
100+
continue;
173101

174-
addInstance(instances.emplace_back(), matchedUnitCellAtoms);
102+
addInstance(instances.emplace_back(), matchedUnitCellAtoms);
103+
}
175104
}
176-
}
177105

178-
appendInstances(instances, detectedStructure, fragment);
106+
if (!instances.empty())
107+
detectedStructures_.emplace_back(copyStructureAtomsAndBonds(detectedStructure, fragment)).instances() =
108+
instances;
179109
}
180110

181111
message("Detected {} distinct fragment structures:\n\n", detectedStructures_.size());
@@ -211,22 +141,21 @@ NodeConstants::ProcessResult DetectMoleculesNode::process()
211141
*/
212142

213143
// Copy atom and bond information from one structure to another
214-
Structure &DetectMoleculesNode::copyStructureAtomsAndBonds(const Structure &source, Structure &target,
215-
const std::vector<int> fragmentAtomIndices)
144+
Structure &DetectMoleculesNode::copyStructureAtomsAndBonds(Structure &target, const std::vector<int> fragmentAtomIndices) const
216145
{
217146
// Copy fragment atoms, forming a map of the original indices to the new atom in the structure
218147
std::map<int, StructureAtom *> originalIndexMap;
219148
for (auto fragAtomIndex : fragmentAtomIndices)
220149
{
221-
const auto fragmentAtom = source.atom(fragAtomIndex);
150+
const auto fragmentAtom = inputStructure_.atom(fragAtomIndex);
222151
originalIndexMap[fragAtomIndex] = target.addAtom(fragmentAtom->Z(), fragmentAtom->r(), fragmentAtom->q());
223152
std::cout << std::format("New atom added to structure: {} {}\n", fragAtomIndex, Elements::symbol(fragmentAtom->Z()));
224153
}
225154

226155
// Copy bond information - since our fragment is by definition a bound fragment, we copy all bonds on each atom
227156
for (auto fragAtomIndex : fragmentAtomIndices)
228157
{
229-
const auto fragmentAtom = source.atom(fragAtomIndex);
158+
const auto fragmentAtom = inputStructure_.atom(fragAtomIndex);
230159
for (auto bond : fragmentAtom->bonds())
231160
{
232161
// Add a bond between the new atoms in the detected structure (as long as it doesn't already exist)
@@ -239,7 +168,7 @@ Structure &DetectMoleculesNode::copyStructureAtomsAndBonds(const Structure &sour
239168
}
240169

241170
// Add fragment molecular instance
242-
void DetectMoleculesNode::addInstance(std::vector<Vector3> &targetInstance, const AtomCollection &instanceFragmentAtoms)
171+
void DetectMoleculesNode::addInstance(std::vector<Vector3> &targetInstance, const AtomCollection &instanceFragmentAtoms) const
243172
{
244173
std::visit(
245174
[&](const auto &atoms)
@@ -251,47 +180,119 @@ void DetectMoleculesNode::addInstance(std::vector<Vector3> &targetInstance, cons
251180
}
252181

253182
// Get fragment atoms from either a single set of fragment indices, or in its overloaded form, a vector of fragments
254-
std::vector<const StructureAtom *> DetectMoleculesNode::getFragmentAtoms(const Structure &structure,
255-
const std::vector<int> &fragmentIndices)
183+
std::vector<const StructureAtom *> DetectMoleculesNode::getFragmentAtoms(const std::vector<int> &fragmentIndices) const
256184
{
257185
std::vector<const StructureAtom *> fragmentAtoms;
258186
for (const auto &fragmentAtomIndex : fragmentIndices)
259-
fragmentAtoms.push_back(structure.atom(int(fragmentAtomIndex)));
187+
fragmentAtoms.push_back(inputStructure_.atom(int(fragmentAtomIndex)));
260188

261189
return fragmentAtoms;
262190
}
263191

264192
// Get fragment atoms from either a single set of fragment indices, or in its overloaded form, a vector of fragments
265-
std::vector<const StructureAtom *> DetectMoleculesNode::getFragmentAtoms(const Structure &structure,
266-
const FragmentVector &fragmentIndices)
193+
std::vector<const StructureAtom *> DetectMoleculesNode::getFragmentAtoms(const NETAFragmentVector &fragmentIndices) const
267194
{
268195
std::vector<int> indices;
269196
std::size_t newSize = 0;
270197
for (const auto &v : fragmentIndices)
271198
++newSize;
272199
indices.reserve(newSize);
273-
for (const auto &v : fragmentIndices)
200+
for (const auto &[_, v] : fragmentIndices)
274201
indices.insert(indices.end(), v.begin(), v.end());
275202

276-
return getFragmentAtoms(structure, indices);
203+
return getFragmentAtoms(indices);
277204
}
278205

279206
// Find all molecular fragments
280-
std::map<int, DetectMoleculesNode::FragmentVector> DetectMoleculesNode::findMolecularFragments(const Structure &structure)
207+
std::map<int, DetectMoleculesNode::NETAFragmentVector> DetectMoleculesNode::findMolecularFragments() const
281208
{
282-
std::map<int, FragmentVector> map;
209+
std::map<int, NETAFragmentVector> map;
283210

284-
auto fragment = [structure](int i) { return Fragment<StructureAtom, Bond<StructureAtom>>::get(structure.atoms(), i); };
211+
std::set<int> alreadyInFragment;
285212

286-
for (int i = 0; i < structure.nAtoms(); i++)
213+
for (int i = 0; i < inputStructure_.nAtoms(); i++)
287214
{
288-
auto element = fragment(i);
289-
const int size = element.size();
215+
auto fragmentIndices = Fragment<StructureAtom, Bond<StructureAtom>>::get(inputStructure_.atoms(), i);
216+
217+
// If any indices already within a fragment, continue
218+
std::set<int> fragmentIndicesSet(fragmentIndices.begin(), fragmentIndices.end());
219+
const int nNewIndices = fragmentIndicesSet.size();
220+
fragmentIndicesSet.merge(std::set<int>(alreadyInFragment.begin(), alreadyInFragment.end()));
221+
if (fragmentIndicesSet.size() != (alreadyInFragment.size() + nNewIndices))
222+
continue;
223+
224+
// Register the current fragment indices
225+
for (auto &idx : fragmentIndices)
226+
alreadyInFragment.insert(idx);
227+
228+
// Map fragment size to fragment indices
229+
const int size = fragmentIndices.size();
290230
if (!map.contains(size))
291-
map.emplace(size, FragmentVector{});
231+
map.emplace(size, NETAFragmentVector{});
232+
292233
auto &targetFragments = map[size];
293-
targetFragments.push_back(element);
234+
targetFragments.push_back(
235+
{(size * 2 > inputStructure_.nAtoms()) ? std::optional<NETADefinition>{} : bestNETADefintion(fragmentIndices),
236+
fragmentIndices});
294237
}
295238

296239
return map;
297-
}
240+
}
241+
242+
// Determine best NETA definition for index atoms within a fragment
243+
NETADefinition DetectMoleculesNode::bestNETADefintion(const std::vector<int> &fragmentIndices) const
244+
{
245+
// Find the best NETA definition for this fragment
246+
NETADefinition bestNETA;
247+
std::vector<const StructureAtom *> rootAtoms;
248+
249+
for (const auto &idx : fragmentIndices)
250+
{
251+
auto fragmentAtom = inputStructure_.atom(idx);
252+
253+
// Maintain a set of atoms matched by any NETA description we generate
254+
std::set<const StructureAtom *> alreadyMatched;
255+
256+
// Skip this atom?
257+
if (alreadyMatched.find(fragmentAtom) != alreadyMatched.end())
258+
continue;
259+
260+
// Create a NETA definition with this atom as the root
261+
NETADefinition neta;
262+
neta.create(static_cast<const AtomBase *>(fragmentAtom), std::nullopt,
263+
Flags<NETADefinition::NETACreationFlags>(NETADefinition::NETACreationFlags::ExplicitHydrogens,
264+
NETADefinition::NETACreationFlags::IncludeRootElement));
265+
266+
// Apply this match over the whole fragment
267+
std::vector<const StructureAtom *> currentRootAtoms;
268+
for (auto idx : fragmentIndices)
269+
{
270+
auto fragmentAtom = inputStructure_.atom(idx);
271+
272+
if (neta.matches(fragmentAtom))
273+
{
274+
currentRootAtoms.push_back(fragmentAtom);
275+
alreadyMatched.insert(fragmentAtom);
276+
}
277+
}
278+
279+
// Is this a better description?
280+
auto better = false;
281+
if (rootAtoms.empty() || currentRootAtoms.size() < rootAtoms.size())
282+
better = true;
283+
else if (currentRootAtoms.size() == rootAtoms.size())
284+
{
285+
// Replace the current match if there are more bonds on the current atom.
286+
if (fragmentAtom->nBonds() > rootAtoms.front()->nBonds())
287+
better = true;
288+
}
289+
290+
if (better)
291+
{
292+
bestNETA = neta;
293+
rootAtoms = currentRootAtoms;
294+
}
295+
}
296+
297+
return bestNETA;
298+
}

0 commit comments

Comments
 (0)