diff --git a/CMakeLists.txt b/CMakeLists.txt index 65cb306e06..565e944111 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -298,7 +298,6 @@ set(BASIC_LINK_LIBS moduleregistry io export - import neta analyser generator diff --git a/src/classes/dataSource.h b/src/classes/dataSource.h index 0e80f2c2cf..cbb5c6df17 100644 --- a/src/classes/dataSource.h +++ b/src/classes/dataSource.h @@ -5,7 +5,6 @@ #include "base/serialiser.h" #include "io/fileAndFormat.h" -#include "io/import/data2D.h" #include "items/list.h" #include "math/data1D.h" #include "math/data2D.h" diff --git a/src/io/CMakeLists.txt b/src/io/CMakeLists.txt index 9195edecbb..db2d824f55 100644 --- a/src/io/CMakeLists.txt +++ b/src/io/CMakeLists.txt @@ -5,4 +5,3 @@ target_link_libraries(io PRIVATE base) # Subdirectories add_subdirectory(export) -add_subdirectory(import) diff --git a/src/io/import/CMakeLists.txt b/src/io/import/CMakeLists.txt deleted file mode 100644 index a58750bfda..0000000000 --- a/src/io/import/CMakeLists.txt +++ /dev/null @@ -1,11 +0,0 @@ -add_library( - import - data2D.cpp - data2D_cartesian.cpp - data2D.h - data2D.h -) - -target_include_directories(import PRIVATE ${PROJECT_SOURCE_DIR}/src ${PROJECT_BINARY_DIR}/src) - -target_link_libraries(import base nodes) diff --git a/src/io/import/data2D.cpp b/src/io/import/data2D.cpp deleted file mode 100644 index de7adfedde..0000000000 --- a/src/io/import/data2D.cpp +++ /dev/null @@ -1,72 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -// Copyright (c) 2026 Team Dissolve and contributors - -#include "io/import/data2D.h" -#include "base/lineParser.h" -#include "base/sysFunc.h" -#include "keywords/vec3Double.h" - -Data2DImportFileFormat::Data2DImportFileFormat(std::string_view filename, Data2DImportFileFormat::Data2DImportFormat format) - : FileAndFormat(formats_, filename, (int)format) -{ - formats_ = EnumOptions( - "Data2DImportFileFormat", {{Data2DImportFormat::Cartesian, "cartesian", "Cartesian X,Y,f(X,Y) data"}}); - - setUpKeywords(); -} - -/* - * Keyword Options - */ - -// Set up keywords for the format -void Data2DImportFileFormat::setUpKeywords() -{ - keywords_.setOrganisation("Options", "Ranges"); - keywords_.add("XAxis", "Min, max, and delta to assume for x axis", xAxis_, - Vec3Labels::MinMaxDeltaLabels); - keywords_.add("YAxis", "Min, max, and delta to assume for y axis", yAxis_, - Vec3Labels::MinMaxDeltaLabels); -} - -/* - * Import Functions - */ - -// Import Data2D using current filename and format -bool Data2DImportFileFormat::importData(Data2D &data) -{ - // Open file and check that we're OK to proceed importing from it - LineParser parser; - if ((!parser.openInput(filename_)) || (!parser.isFileGoodForReading())) - return Messenger::error("Couldn't open file '{}' for loading Data2D data.\n", filename_); - - // Import the data - auto result = importData(parser, data); - - parser.closeFiles(); - - return result; -} - -// Import Data2D using supplied parser and current format -bool Data2DImportFileFormat::importData(LineParser &parser, Data2D &data) -{ - // Check the format - if (!formatIndex_) - return Messenger::error("No format set for Data2DImportFileFormat so can't import.\n"); - - // Import the data - auto result = false; - switch (formats_.enumerationByIndex(*formatIndex_)) - { - case (Data2DImportFormat::Cartesian): - result = importCartesian(parser, data); - break; - default: - Messenger::exception("Data2D format '{}' import has not been implemented.\n", - formats_.keywordByIndex(*formatIndex_)); - } - - return result; -} diff --git a/src/io/import/data2D.h b/src/io/import/data2D.h deleted file mode 100644 index 1f63e724c5..0000000000 --- a/src/io/import/data2D.h +++ /dev/null @@ -1,65 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -// Copyright (c) 2026 Team Dissolve and contributors - -#pragma once - -#include "base/enumOptions.h" -#include "io/fileAndFormat.h" -#include "math/vector3.h" - -// Forward Declarations -class Data2D; -class Vec3DoubleKeyword; - -// Data2D Import Formats -class Data2DImportFileFormat : public FileAndFormat -{ - public: - // Available Data2D formats - enum class Data2DImportFormat - { - Cartesian - }; - explicit Data2DImportFileFormat(std::string_view filename = "", Data2DImportFormat format = Data2DImportFormat::Cartesian); - ~Data2DImportFileFormat() override = default; - - /* - * Keyword Options - */ - private: - // Min, max, and delta to assume for x axis - Vector3 xAxis_{0.0, 10.0, 0.1}; - // Min, max, and delta to assume for y axis - Vector3 yAxis_{0.0, 10.0, 0.1}; - - private: - // Set up keywords for the format - void setUpKeywords(); - - /* - * Formats - */ - private: - // Format enum options - EnumOptions formats_; - - /* - * Filename / Basename - */ - public: - // Return whether the file must exist - bool fileMustExist() const override { return true; } - - /* - * Data Import - */ - private: - // Import cartesian data from supplied parser - bool importCartesian(LineParser &parser, Data2D &data); - - public: - // Import Data2D using current filename and format - bool importData(Data2D &data); - // Import Data2D using supplied parser and current format - bool importData(LineParser &parser, Data2D &data); -}; diff --git a/src/io/import/data2D_cartesian.cpp b/src/io/import/data2D_cartesian.cpp deleted file mode 100644 index ff018d2453..0000000000 --- a/src/io/import/data2D_cartesian.cpp +++ /dev/null @@ -1,62 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -// Copyright (c) 2026 Team Dissolve and contributors - -#include "base/lineParser.h" -#include "io/import/data2D.h" -#include "math/data2D.h" -#include "math/filters.h" - -// Read cartesian data from specified file -bool Data2DImportFileFormat::importCartesian(LineParser &parser, Data2D &data) -{ - /* - * Cartesian coordinates assume that there are three values per line: x, y, and value. - * The x and y values are assumed to be centre-bin values. - */ - - // Set up our data - const auto xMin = xAxis_.x; - const auto xMax = xAxis_.y; - const auto xDelta = xAxis_.z; - const auto yMin = yAxis_.x; - const auto yMax = yAxis_.y; - const auto yDelta = yAxis_.z; - data.initialise(xMin, xMax, xDelta, yMin, yMax, yDelta); - - // Loop over lines in the file - we expect blocks with three columns (x, y, f(x,y)) - while (!parser.eofOrBlank()) - { - // Read line - if (parser.getArgsDelim() != LineParser::Success) - return false; - - // Check number of arguments - if (parser.nArgs() != 3) - { - Messenger::warn("Skipping bad line: {}...\n", parser.line()); - continue; - } - - double x = parser.argd(0); - double y = parser.argd(1); - auto xBin = (x - xMin) / xDelta; - if ((xBin < 0) || (xBin >= data.xAxis().size())) - { - Messenger::warn("Coordinates x={:e} y={:e} are out-of-range (xBin = {}, nBins = {}).\n", x, y, xBin, - data.xAxis().size()); - continue; - } - auto yBin = (y - yMin) / yDelta; - if ((yBin < 0) || (yBin >= data.yAxis().size())) - { - Messenger::warn("Coordinates x={:e} y={:e} are out-of-range (yBin = {}, nBins = {}).\n", x, y, yBin, - data.yAxis().size()); - continue; - } - - // Set the value - data.value(xBin, yBin) = parser.argd(2); - } - - return true; -} diff --git a/src/nodes/dAngle.cpp b/src/nodes/dAngle.cpp index 1b819397ab..73babf45af 100644 --- a/src/nodes/dAngle.cpp +++ b/src/nodes/dAngle.cpp @@ -37,6 +37,7 @@ void DAngleNode::clearData() {} // Temporary accessors to data for testing const Data1D &DAngleNode::rdfBC() const { return rdfBC_; } const Data1D &DAngleNode::angle() const { return angle_; } +const Histogram2D &DAngleNode::distanceAngleMap() const { return *distanceAngleMap_; } const Data2D &DAngleNode::dAngle() const { return dAngle_; } /* diff --git a/src/nodes/dAngle.h b/src/nodes/dAngle.h index 471738e502..a34a385d44 100644 --- a/src/nodes/dAngle.h +++ b/src/nodes/dAngle.h @@ -56,6 +56,7 @@ class DAngleNode : public Node // Temporary accessors to data for testing const Data1D &rdfBC() const; const Data1D &angle() const; + const Histogram2D &distanceAngleMap() const; const Data2D &dAngle() const; /* diff --git a/src/nodes/importDLPUtilsSurface.cpp b/src/nodes/importDLPUtilsSurface.cpp new file mode 100644 index 0000000000..f4ea23abe6 --- /dev/null +++ b/src/nodes/importDLPUtilsSurface.cpp @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (c) 2026 Team Dissolve and contributors + +#include "nodes/importDLPUtilsSurface.h" +#include "base/lineParser.h" + +ImportDLPUtilsSurfaceNode::ImportDLPUtilsSurfaceNode(Graph *parentGraph) : Node(parentGraph) +{ + // Options + addOption("FilePath", "File path", filePath_); + + // Outputs + addOutput>("Data", "Imported data", data_); +} + +std::string_view ImportDLPUtilsSurfaceNode::type() const { return "ImportDLPUtilsSurface"; } + +std::string_view ImportDLPUtilsSurfaceNode::summary() const { return "Import DLPUtils 2D surface data (.surf)"; } + +NodeConstants::ProcessResult ImportDLPUtilsSurfaceNode::process() +{ + // Create the data + data_.emplace(); + + if (!read(*data_, filePath_)) + return error("Failed to read DLPUtils Surface data from file '{}'.\n", filePath_); + + return NodeConstants::ProcessResult::Success; +} + +// Read data specified +bool ImportDLPUtilsSurfaceNode::read(Data2D &data, std::string filePath) +{ + data.clear(); + + // Open file and check that we're OK to proceed importing from it + LineParser parser; + if ((!parser.openInput(filePath)) || (!parser.isFileGoodForReading())) + return false; + + // Data is in blocks of common Y value, three-columns: x y f(x,y) + std::vector xAxis, yAxis, values; + auto firstLineOfBlock = true; + while (!parser.eofOrBlank()) + { + if (parser.getArgsDelim(LineParser::KeepBlanks) != LineParser::Success) + return false; + + // Is this a blank line inbetween blocks? + if (parser.nArgs() == 0) + { + firstLineOfBlock = true; + continue; + } + + // If this is the first line of the block, re-start x-axis storage and push next y value + if (firstLineOfBlock) + { + xAxis.clear(); + yAxis.push_back(parser.argd(1)); + firstLineOfBlock = false; + } + + // Store the x-axis value + xAxis.push_back(parser.argd(0)); + + // Store the value + values.push_back(parser.argd(2)); + } + + parser.closeFiles(); + + // Validity check on number of points in loaded file + if (xAxis.size() * yAxis.size() != values.size()) + return false; + + // Initialise the array and poke values back in the correct order + + data.initialise(xAxis.size(), yAxis.size(), false); + data.xAxis() = xAxis; + data.yAxis() = yAxis; + auto index = 0; + for (auto y = 0; y < yAxis.size(); ++y) + for (auto x = 0; x < xAxis.size(); ++x) + data.values()[{x, y}] = values[index++]; + + return true; +} \ No newline at end of file diff --git a/src/nodes/importDLPUtilsSurface.h b/src/nodes/importDLPUtilsSurface.h new file mode 100644 index 0000000000..6aa62b74e9 --- /dev/null +++ b/src/nodes/importDLPUtilsSurface.h @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// Copyright (c) 2026 Team Dissolve and contributors + +#pragma once + +#include "math/data2D.h" +#include "nodes/node.h" + +class ImportDLPUtilsSurfaceNode : public Node +{ + public: + ImportDLPUtilsSurfaceNode(Graph *parentGraph); + ~ImportDLPUtilsSurfaceNode() override = default; + + /* + * Definition + */ + public: + std::string_view type() const override; + std::string_view summary() const override; + + /* + * Data + */ + private: + // File path + std::string filePath_; + // Imported data + std::optional data_; + + /* + * Processing + */ + private: + // Run main processing + NodeConstants::ProcessResult process() override; + + public: + // Read data specified + static bool read(Data2D &data, std::string filePath); +}; diff --git a/src/nodes/registry.cpp b/src/nodes/registry.cpp index 3b68ad54bc..d9fc04d336 100644 --- a/src/nodes/registry.cpp +++ b/src/nodes/registry.cpp @@ -28,6 +28,7 @@ #include "nodes/importDLPOLYStructure.h" #include "nodes/importDLPOLYTrajectory.h" #include "nodes/importDLPUtilsPDens.h" +#include "nodes/importDLPUtilsSurface.h" #include "nodes/importEPSRAtoStructure.h" #include "nodes/importMoscitoStructure.h" #include "nodes/importXYData.h" @@ -101,6 +102,7 @@ void NodeRegistry::instantiateNodeProducers() {"ImportDLPOLYStructure", makeDerivedNode()}, {"ImportDLPOLYTrajectory", makeDerivedNode()}, {"ImportDLPUtilsPDens", makeDerivedNode()}, + {"ImportDLPUtilsSurface", makeDerivedNode()}, {"ImportEPSRAtoStructure", makeDerivedNode()}, {"ImportMoscitoStructure", makeDerivedNode()}, {"ImportXYData", makeDerivedNode()}, diff --git a/tests/nodes/dAngle.cpp b/tests/nodes/dAngle.cpp index 774163b37d..2ba86e6ce6 100644 --- a/tests/nodes/dAngle.cpp +++ b/tests/nodes/dAngle.cpp @@ -2,6 +2,8 @@ // Copyright (c) 2026 Team Dissolve and contributors #include "nodes/dAngle.h" +#include "analyser/dataOperator2D.h" +#include "nodes/importDLPUtilsSurface.h" #include "nodes/iterableGraph.h" #include "tests/graphData.h" #include "tests/testData.h" @@ -41,6 +43,16 @@ TEST(DAngleNodeTest, Water) EXPECT_TRUE(DissolveSystemTest::checkData1D(dAngle->angle(), "Angle Distributions", "dlpoly/water267-analysis/water-267-298K.dahist1_02_1_01_02.angle.norm", 1, 2, 3.0e-6)); + + // Test DAngle map - the reference data have not been normalised to account for sin(y) or the spherical shell (RDF) density. + Data2D referenceData; + EXPECT_TRUE( + ImportDLPUtilsSurfaceNode::read(referenceData, "dlpoly/water267-analysis/water-267-298K.dahist1_02_1_01_02.surf")); + auto data = dAngle->distanceAngleMap().accumulatedData(); + DataOperator2D dAngleNormaliser(data); + dAngleNormaliser.divide(267.0); + EXPECT_TRUE(DissolveSystemTest::checkData2D(data, "Distance-Angle Map", referenceData, + "water-267-298K.dahist1_02_1_01_02.surf", 3.0e-3)); } } // namespace UnitTest \ No newline at end of file diff --git a/tests/testData.h b/tests/testData.h index d68f1b41be..3b3b4e74a3 100644 --- a/tests/testData.h +++ b/tests/testData.h @@ -8,6 +8,7 @@ #include "kernels/energy.h" #include "kernels/force.h" #include "main/dissolve.h" +#include "math/data2D.h" #include "math/data3D.h" #include "math/error.h" #include "math/mathFunc.h" @@ -326,6 +327,19 @@ class DissolveSystemTest return checkData1D(dataA, nameA, dataB, filePath, tolerance, errorType); } + // Test Data2D + [[nodiscard]] static bool checkData2D(const Data2D &dataA, std::string_view nameA, const Data2D &dataB, + std::string_view nameB, double tolerance = 5.0e-3, + Error::ErrorType errorType = Error::ErrorType::EuclideanError) + { + // Generate the error estimate and compare against the threshold value + auto error = Error::error(errorType, dataA.values().linearArray(), dataB.values().linearArray()).error; + auto notOK = std::isnan(error) || error > tolerance; + Messenger::print("Data '{}' has error of {:7.3f} with data '{}' and is {} (threshold is {:6.3e})\n\n", nameA, error, + nameB, notOK ? "NOT OK" : "OK", tolerance); + + return !notOK; + } // Test Data3D [[nodiscard]] static bool checkData3D(const Data3D &dataA, std::string_view nameA, const Data3D &dataB, std::string_view nameB, double tolerance = 5.0e-3,