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
76 changes: 76 additions & 0 deletions src/classes/structure.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,82 @@ void Structure::createBox(const Matrix3 &axes)
box_ = Box::generate(lengths, angles);
}

/*
* Manipulations
*/

// Recursive function for general manipulation
void Structure::recurseLocal(std::set<StructureAtom *> &fragmentAtoms, StructureAtom *i, ManipulationFunction action)
{
if (fragmentAtoms.contains(i))
return;

fragmentAtoms.insert(i);

// Loop over attached atoms, performing minimum image repositioning w.r.t. i, and call the action
for (const auto *b : i->bonds())
{
auto j = b->partner(i);
if (fragmentAtoms.contains(j))
continue;

action(j, box_.minimumImage(j->r(), i->r()));

// Recurse into bound neighbours
recurseLocal(fragmentAtoms, j, action);
}
}
void Structure::recurseLocal(std::set<StructureAtom *> &fragmentAtoms, StructureAtom *i, ConstManipulationFunction action) const
{
if (fragmentAtoms.contains(i))
return;

fragmentAtoms.insert(i);

// Loop over attached atoms, performing minimum image repositioning w.r.t. i, and call the action
for (const auto b : i->bonds())
{
auto j = b->partner(i);
if (fragmentAtoms.contains(j))
continue;

action(j, box_.minimumImage(j->r(), i->r()));

// Recurse into bound neighbours
recurseLocal(fragmentAtoms, j, action);
}
}

// Return atoms in the same fragment as the specified atom, unfolding the fragment at the same time
std::set<StructureAtom *> Structure::getUnfoldedFragment(StructureAtom *containing, ManipulationFunction action)
{
std::set<StructureAtom *> fragmentAtoms;
action(containing, containing->r());
recurseLocal(fragmentAtoms, containing, action);
return fragmentAtoms;
}
std::set<StructureAtom *> Structure::getUnfoldedFragment(StructureAtom *containing, ConstManipulationFunction action) const
{
std::set<StructureAtom *> fragmentAtoms;
action(containing, containing->r());
recurseLocal(fragmentAtoms, containing, action);
return fragmentAtoms;
}

// Un-fold bound fragments in the structure
void Structure::unFold()
{
std::set<StructureAtom *> fragmentAtoms;

for (auto &atom : atoms())
{
if (fragmentAtoms.contains(atom.get()))
break;

fragmentAtoms.merge(getUnfoldedFragment(atom.get(), [](StructureAtom *j, Vector3 rJ) { j->setR(rJ); }));
}
}

/*
* Serialisation
*/
Expand Down
19 changes: 19 additions & 0 deletions src/classes/structure.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include "classes/atom.h"
#include "classes/bond.h"
#include "classes/box.h"
#include <set>
#include <vector>

// StructureAtom
Expand Down Expand Up @@ -116,6 +117,24 @@ class Structure : public Serialisable<>
// Create Box definition from axes matrix
void createBox(const Matrix3 &axes);

/*
* Manipulations
*/
private:
// Typedef for manipulation functions
using ManipulationFunction = std::function<void(StructureAtom *j, Vector3 rJ)>;
using ConstManipulationFunction = std::function<void(const StructureAtom *j, Vector3 rJ)>;
// Recursive function for general manipulation
void recurseLocal(std::set<StructureAtom *> &fragmentAtoms, StructureAtom *i, ManipulationFunction action);
void recurseLocal(std::set<StructureAtom *> &fragmentAtoms, StructureAtom *i, ConstManipulationFunction action) const;
// Return atoms in the same fragment as the specified atom, unfolding the fragment at the same time
std::set<StructureAtom *> getUnfoldedFragment(StructureAtom *containing, ManipulationFunction action);
std::set<StructureAtom *> getUnfoldedFragment(StructureAtom *containing, ConstManipulationFunction action) const;

public:
// Un-fold bound fragments in the structure
void unFold();

/*
* Serialisation
*/
Expand Down
2 changes: 2 additions & 0 deletions src/nodes/registry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
#include "nodes/species.h"
#include "nodes/sq.h"
#include "nodes/subtract.h"
#include "nodes/supercellConfiguration.h"
#include "nodes/vector3Assemble.h"
#include "nodes/vector3Decompose.h"
#include "nodes/voxelDensity.h"
Expand Down Expand Up @@ -133,6 +134,7 @@ void NodeRegistry::instantiateNodeProducers()
{"SQ", makeDerivedNode<SQNode>()},
{"Species", makeDerivedNode<SpeciesNode>()},
{"Subtract", makeDerivedNode<SubtractNode>()},
{"SupercellConfiguration", makeDerivedNode<SupercellConfigurationNode>()},
{"Vector3Assemble", makeDerivedNode<Vector3AssembleNode>()},
{"Vector3Decompose", makeDerivedNode<Vector3DecomposeNode>()},
{"XRaySQ", makeDerivedNode<XRaySQNode>()},
Expand Down
69 changes: 69 additions & 0 deletions src/nodes/supercellConfiguration.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (c) 2026 Team Dissolve and contributors

#include "nodes/supercellConfiguration.h"
#include "math/vector3.h"

SupercellConfigurationNode::SupercellConfigurationNode(Graph *parentGraph) : Node(parentGraph)
{
// Inputs
addInput("Configuration", "Target configuration", targetConfiguration_);

// Outputs
addPointerOutput("SupercellConfiguration", "Supercell configuration", supercellConfiguration_);

// Options
addOption("SupercellRepeat", "Integer coefficients by which unit cell will be repeated along its axes", supercellRepeat_);
}

/*
* Definition
*/

std::string_view SupercellConfigurationNode::type() const { return "SupercellConfiguration"; }

std::string_view SupercellConfigurationNode::summary() const
{
return "Create a repeated instance (supercell) of a configuration";
}

/*
* Processing
*/

// Perform processing
NodeConstants::ProcessResult SupercellConfigurationNode::process()
{
supercellConfiguration_.empty();

const auto &box = targetConfiguration_->box();

// Set up configuration
auto supercellLengths = box.axisLengths();
supercellLengths.multiply(supercellRepeat_.x, supercellRepeat_.y, supercellRepeat_.z);
supercellConfiguration_.createBoxAndCells(supercellLengths, box.axisAngles(), false);

// Create images of all molecular unit cell species
for (auto &mol : targetConfiguration_->molecules())
{
const auto *sp = mol->species();

// Loop over cell images
for (auto ix = 0; ix < supercellRepeat_.x; ++ix)
{
for (auto iy = 0; iy < supercellRepeat_.y; ++iy)
{
// Create and translate molecule
for (auto iz = 0; iz < supercellRepeat_.z; ++iz)
supercellConfiguration_.addMolecule(sp)->translate(box.axes() * Vector3(ix, iy, iz));
}
}
}

supercellConfiguration_.updateObjectRelationships();

message("Created ({}, {}, {}) supercell - {} atoms total.\n", supercellRepeat_.x, supercellRepeat_.y, supercellRepeat_.z,
supercellConfiguration_.nAtoms());

return NodeConstants::ProcessResult::Success;
}
39 changes: 39 additions & 0 deletions src/nodes/supercellConfiguration.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (c) 2026 Team Dissolve and contributors

#pragma once

#include "classes/configuration.h"
#include "nodes/node.h"

class SupercellConfigurationNode : public Node
{
public:
SupercellConfigurationNode(Graph *parentGraph);
~SupercellConfigurationNode() override = default;

/*
* Definition
*/
public:
std::string_view type() const override;
std::string_view summary() const override;

/*
* Data
*/
private:
// Input configuration
Configuration *targetConfiguration_{nullptr};
// Supercell repeat
Vector3i supercellRepeat_;
// Supercell configuration
Configuration supercellConfiguration_;

/*
* Processing
*/
protected:
// Perform processing
NodeConstants::ProcessResult process() override;
};
35 changes: 35 additions & 0 deletions tests/classes/structure.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

#include "classes/structure.h"
#include "nodes/calculateBonding.h"
#include "tests/graphData.h"
#include <cmath>
#include <gtest/gtest.h>

namespace UnitTest
Expand Down Expand Up @@ -68,4 +70,37 @@ TEST(StructureTest, Molecule2)
EXPECT_EQ(s.nAtoms(), 0);
}

TEST(StructureTest, Unfold)
{
TestGraph graph;

// Import XYZ node
auto importXYZNode = graph.createNode("ImportXYZStructure");
ASSERT_TRUE(importXYZNode);
ASSERT_TRUE(importXYZNode->setOption("FilePath", std::string("xyz/ch4_folded.xyz")));

// Calculate bonding node
auto calculateBondingNode = graph.createNode("CalculateBonding");
ASSERT_TRUE(calculateBondingNode);

// Connect graph
ASSERT_TRUE(graph.addEdge({"ImportXYZStructure", "Structure", "CalculateBonding", "Structure"}));

// Run
ASSERT_EQ(calculateBondingNode->run(), NodeConstants::ProcessResult::Success);

auto structure = calculateBondingNode->getOutputValue<Structure>("Structure");
ASSERT_TRUE(structure.bonds().size() != 0);
structure.unFold();

// After unfolding, the distances between C and H should all be unity
for (const auto &bond : structure.bonds())
{
auto iR = bond->i()->r();
auto jR = bond->j()->r();
auto distance = abs((iR - jR).magnitude());
ASSERT_NEAR(distance, 1, 10e-9);
}
}
Comment thread
RobBuchananCompPhys marked this conversation as resolved.

} // namespace UnitTest
17 changes: 17 additions & 0 deletions tests/data/xyz/ch4_folded.xyz
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
15
Unnamed001
C 5.000000 0.100000 5.000000 0.000000
H 5.000000 1.100000 5.000000 0.000000
H 5.000000 9.766193 4.057359 0.000000
H 5.816351 9.766193 5.471321 0.000000
H 4.183649 9.766193 5.471321 0.000000
C 0.100000 5.000000 5.000000 0.000000
H 1.100000 5.000000 5.000000 0.000000
H 9.766193 5.000000 5.942641 0.000000
H 9.766193 5.816351 4.528679 0.000000
H 9.766193 4.183649 4.528679 0.000000
C 5.000000 5.000000 0.100000 0.000000
H 5.000000 5.000000 1.100000 0.000000
H 5.000000 5.942641 9.766193 0.000000
H 5.816351 4.528679 9.766193 0.000000
H 4.183649 4.528679 9.766193 0.000000
Loading