@@ -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