Skip to content
Draft
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ changes
*.DS_Store
msvc-env
aqtinstall.log
justfile

# Example files
*.beat
Expand All @@ -51,6 +52,7 @@ aqtinstall.log
*.sq
*.rewrite
tests/data/*.xml
tests/data/test.ANGLE
tests/epsr/D2O-ReferenceData.q
tests/epsr/D2O-ReferenceData.r
tests/epsr/EPSR01-EstSQ-HW-HW.txt
Expand Down
5 changes: 5 additions & 0 deletions src/gui/qml/nodeGraph/GraphView.qml
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@ Pane {
Menu {
title: "Action"

MenuItem {
text: "Average Molecule"

onClicked: graphRoot.rootModel.emplace_back(Math.round(ctxMenuCatcher.mouseX), Math.round(ctxMenuCatcher.mouseY), "AvgMol", "New AvgMol")
}
MenuItem {
text: "Atomic MC"

Expand Down
7 changes: 7 additions & 0 deletions src/io/export/species.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ SpeciesExportFileFormat::SpeciesExportFileFormat(std::string_view filename, Spec
{SpeciesExportFormat::DLPOLY, "dlpoly", "DL_POLY CONFIG File"}});
}

SpeciesExportFileFormat &SpeciesExportFileFormat::operator=(const SpeciesExportFileFormat &other)
{
formats_=other.formats_;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change

return *this;
}

/*
* Export Functions
*/
Expand Down
1 change: 1 addition & 0 deletions src/io/export/species.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ class SpeciesExportFileFormat : public FileAndFormat
};
SpeciesExportFileFormat(std::string_view filename = "", SpeciesExportFormat format = SpeciesExportFormat::XYZ);
~SpeciesExportFileFormat() override = default;
SpeciesExportFileFormat &operator=(const SpeciesExportFileFormat &other);

/*
* Formats
Expand Down
19 changes: 19 additions & 0 deletions src/nodes/avgmol/avgmol.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (c) 2026 Team Dissolve and contributors

#include "nodes/avgmol/avgmol.h"
#include "nodes/constants.h"

AvgMolNode::AvgMolNode(Graph *parentGraph) : Node(parentGraph)
{
addInput("Configuration", "Set target configuration for the module", targetConfiguration_);

addOption("Site", "Target site about which to calculate average species geometry", targetSite_);
addOption("ExportCoordinates", "Whether to save average coordinates to disk", exportFileAndFormat_);

addPointerOutput<const Species>("Average Species", "The species with the average coordinates", averageSpecies_);
Comment on lines +9 to +14
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
addInput("Configuration", "Set target configuration for the module", targetConfiguration_);
addOption("Site", "Target site about which to calculate average species geometry", targetSite_);
addOption("ExportCoordinates", "Whether to save average coordinates to disk", exportFileAndFormat_);
addPointerOutput<const Species>("Average Species", "The species with the average coordinates", averageSpecies_);
// Inputs
addInput("Configuration", "Set target configuration for the module", targetConfiguration_);
// Outputs
addPointerOutput<const Species>("Average Species", "The species with the average coordinates", averageSpecies_);
// Options
addOption("Site", "Target site about which to calculate average species geometry", targetSite_);
addOption("ExportCoordinates", "Whether to save average coordinates to disk", exportFileAndFormat_);

I believe we are currently structuring this in the following way:

}

std::string_view AvgMolNode::type() const { return "AvgMol"; }

std::string_view AvgMolNode::summary() const { return "Calculate Average Molecule"; }
52 changes: 52 additions & 0 deletions src/nodes/avgmol/avgmol.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (c) 2026 Team Dissolve and contributors

#pragma once

#include "io/export/species.h"
#include "math/sampledVector.h"
#include "nodes/node.h"
#include "nodes/parameter.h"

class Configuration;
class SpeciesSite;

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

public:
std::string_view type() const override;
std::string_view summary() const override;

/*
* Definition
*/
private:
// Target configuration
Configuration *targetConfiguration_{nullptr};
// Target site
const SpeciesSite *targetSite_{nullptr};
// Whether to save average coordinates to disk
SpeciesExportFileFormat exportFileAndFormat_;
// Species targeted by module (derived from selected site)
const Species *targetSpecies_{nullptr};
// Local Species representing average of targeted Species
Species averageSpecies_;

private:
SampledVector x_, y_, z_;
// Ensure arrays are the correct size for the current target Species
void updateArrays();
// Update the local species with the coordinates from the supplied arrays
void updateSpecies();

/*
* Processing
*/
public:
// Run main processing
NodeConstants::ProcessResult process() override;
};
102 changes: 102 additions & 0 deletions src/nodes/avgmol/process.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (c) 2026 Team Dissolve and contributors

#include "nodes/avgmol/avgmol.h"
#include "nodes/constants.h"

void AvgMolNode::updateArrays()
{
auto requiredSize = targetSpecies_ ? targetSpecies_->nAtoms() : -1;

if (requiredSize > 0)
{
if (x_.values().size() == requiredSize && y_.values().size() == requiredSize && z_.values().size() == requiredSize)
Messenger::print("Using existing coordinate arrays for average species.\n");
else
{
Messenger::print("Initialising arrays for average molecule: size = {}\n", requiredSize);
x_.initialise(requiredSize);
y_.initialise(requiredSize);
z_.initialise(requiredSize);
}
}
else
{
x_.clear();
y_.clear();
z_.clear();
}
}

void AvgMolNode::updateSpecies()
{
for (auto &&[i, rx, ry, rz] : zip(averageSpecies_.atoms(), x_.values(), y_.values(), z_.values()))
averageSpecies_.setAtomCoordinates(&i, {rx, ry, rz});
}

NodeConstants::ProcessResult AvgMolNode::process()
{
// Grab Box pointer
const auto *box = targetConfiguration_->box();

// Get the target site
if (!targetSite_)
{
Messenger::error("No target site defined.\n");
return NodeConstants::ProcessResult::Failed;
}

// Get site parent species
targetSpecies_ = targetSite_->parent();

Messenger::print("AvgMol: Target site (species) is {} ({}).\n", targetSite_->name(), targetSpecies_->name());
if (exportFileAndFormat_.hasFilename())
Messenger::print("AvgMol: Coordinates will be exported to '{}' ({}).\n", exportFileAndFormat_.filename(),
exportFileAndFormat_.formatDescription());

Messenger::print("\n");

// Update arrays
updateArrays();

// Get the site stack
const auto *stack = targetConfiguration_->siteStack(targetSite_);

// Loop over sites
std::vector<double> rx(targetSpecies_->nAtoms()), ry(targetSpecies_->nAtoms()), rz(targetSpecies_->nAtoms());
Vector3 r;
for (auto n = 0; n < stack->nSites(); ++n)
{
const auto &s = stack->site(n);
assert(s.molecule()->species() == targetSpecies_);

// Get axes and take inverse
auto inverseAxes = s.axes();
inverseAxes.invert();

// Loop over atoms, taking delta position with origin, and rotating into local axes
for (auto &&[i, x, y, z] : zip(s.molecule()->atoms(), rx, ry, rz))
{
r = inverseAxes * box->minimumVector(s.origin(), i->r());
x = r.x;
y = r.y;
z = r.z;
}

// Accumulate positions
x_ += rx;
y_ += ry;
z_ += rz;
}

updateSpecies();

// Export data?
if (exportFileAndFormat_.hasFilename())
{
if (!exportFileAndFormat_.exportData(&averageSpecies_))
return NodeConstants::ProcessResult::Failed;
}

return NodeConstants::ProcessResult::Success;
}
2 changes: 2 additions & 0 deletions src/nodes/registry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include "nodes/add.h"
#include "nodes/angle.h"
#include "nodes/atomicMC/atomicMC.h"
#include "nodes/avgmol/avgmol.h"
#include "nodes/bragg.h"
#include "nodes/configuration.h"
#include "nodes/data1DImport.h"
Expand Down Expand Up @@ -54,6 +55,7 @@ void NodeRegistry::instantiateNodeProducers()
producers_ = {{"Add", makeDerivedNode<AddNode>()},
{"Angle", makeDerivedNode<AngleNode>()},
{"AtomicMC", makeDerivedNode<AtomicMCNode>()},
{"AvgMol", makeDerivedNode<AvgMolNode>()},
{"Bragg", makeDerivedNode<BraggNode>()},
{"Configuration", makeDerivedNode<ConfigurationNode>()},
{"Data1DImport", makeDerivedNode<Data1DImportNode>()},
Expand Down
1 change: 1 addition & 0 deletions tests/nodes/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
dissolve_add_test(SRC angle.cpp)
dissolve_add_test(SRC atomicMC.cpp)
dissolve_add_test(SRC avgMol.cpp)
dissolve_add_test(SRC bragg.cpp)
dissolve_add_test(SRC broadening.cpp)
dissolve_add_test(SRC flow.cpp)
Expand Down
66 changes: 66 additions & 0 deletions tests/nodes/avgMol.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (c) 2026 Team Dissolve and contributors

#include "nodes/avgmol/avgmol.h"
#include "classes/speciesSite.h"
#include "io/export/species.h"
#include "io/import/trajectory.h"
#include "nodes/iterableGraph.h"
#include "tests/graphData.h"
#include "tests/testData.h"
#include <gtest/gtest.h>

namespace UnitTest
{
TEST(AvgMolNodeTest, Water)
{
GraphTestData data;
createWaterGraph(&data.graphRoot, 267);

// Create an iterator
auto iterator = dynamic_cast<IterableGraph *>(data.graphRoot.createNode("Iterator"));
ASSERT_TRUE(iterator);

// Create a dynamic input from the graph's existing Insert node
EXPECT_TRUE(data.graphRoot.addEdge({"Insert", "Configuration", "Iterator", "Configuration"}));

// Within the iterator create an ImportTrajectory node
auto importTrajectory = iterator->createNode("ImportConfigurationTrajectory");
ASSERT_TRUE(importTrajectory);
ASSERT_TRUE(importTrajectory->setOption<std::string>("FilePath", "dlpoly/water267-analysis/water-267-298K.xyz"));
ASSERT_TRUE(importTrajectory->setOption<TrajectoryImportFileFormat::TrajectoryImportFormat>(
"FileFormat", TrajectoryImportFileFormat::TrajectoryImportFormat::XYZ));
ASSERT_TRUE(iterator->addEdge({"Inputs", "Configuration", "ImportConfigurationTrajectory", "Configuration"}));

// Add average molecule module to the iterator
auto avg = dynamic_cast<AvgMolNode *>(iterator->createNode("AvgMol"));
ASSERT_TRUE(avg);

auto *water = data.graphRoot.findNode("Water")->getOutputValue<const Species *>("Species");
ASSERT_TRUE(water);
auto *configuration = data.graphRoot.findNode("Bulk")->getOutputValue<Configuration *>("Configuration");
ASSERT_TRUE(configuration);
SpeciesExportFileFormat exporter("test.AVERAGE");
ASSERT_TRUE(avg->setOption("ExportCoordinates", exporter));
auto site = water->findSite("Origin");
ASSERT_TRUE(site);
ASSERT_TRUE(avg->setOption("Site", water->findSite("Origin")));

ASSERT_TRUE(iterator->addEdge({"ImportConfigurationTrajectory", "Configuration", "AvgMol", "Configuration"}));

// Run from the iterator node explicitly
ASSERT_TRUE(iterator->setOption<Number>("N", 95));
ASSERT_EQ(iterator->run(), NodeConstants::ProcessResult::Success);

// Data1DExportFileFormat exporter("test.ANGLE");
// exporter.exportData(angle->rdfBC());
// EXPECT_TRUE(DissolveSystemTest::checkData1D(
// angle->rdfBC(), "B-C RDF",
// {"dlpoly/water267-analysis/water-267-298K.aardf_21_23_inter_sum", Data1DImportFileFormat::Data1DImportFormat::XY},
// 2.0e-2));
// EXPECT_TRUE(DissolveSystemTest::checkData1D(angle->angleABC(), "A-B-C angle",
// {"dlpoly/water267-analysis/water-267-298K.dahist1_02_1_01_02.angle.norm",
// Data1DImportFileFormat::Data1DImportFormat::XY},
// 6.0e-5));
}
} // namespace UnitTest