From af4f1e13fa5724bc38dbdc9564ebaeccbc264da7 Mon Sep 17 00:00:00 2001 From: "Ryan M. Richard" Date: Fri, 12 Sep 2025 13:47:30 -0500 Subject: [PATCH 1/2] module for reading grid from file --- src/scf/xc/grid_from_file.cpp | 82 ++++++++++++++++++++++ src/scf/xc/xc.cpp | 5 +- src/scf/xc/xc.hpp | 2 + tests/cxx/unit_tests/xc/grid_from_file.cpp | 57 +++++++++++++++ 4 files changed, 145 insertions(+), 1 deletion(-) create mode 100644 src/scf/xc/grid_from_file.cpp create mode 100644 tests/cxx/unit_tests/xc/grid_from_file.cpp diff --git a/src/scf/xc/grid_from_file.cpp b/src/scf/xc/grid_from_file.cpp new file mode 100644 index 0000000..4418ba1 --- /dev/null +++ b/src/scf/xc/grid_from_file.cpp @@ -0,0 +1,82 @@ +/* + * Copyright 2025 NWChemEx-Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "xc.hpp" +#include +#include +#include + +namespace scf::xc { +namespace { +const auto desc = R"( + +GridFromFile +------------ + +Reads a grid from a file. The file is expected to be a text file with four +columns: x, y, z, weight. Each row of the file corresponds to a grid point. +The columns should be space separated. An example of the file format is: + +.. code-block:: text + + 1.0 2.0 3.0 4.0 + 5.0 6.0 7.0 8.0 + +This would create two grid points: (1.0, 2.0, 3.0) with weight 4.0 and +(5.0, 6.0, 7.0) with weight 8.0. +)"; + +} + +using pt = simde::MolecularGrid; + +MODULE_CTOR(GridFromFile) { + satisfies_property_type(); + + add_input("Path to Grid File"); + + description(desc); +} + +MODULE_RUN(GridFromFile) { + const auto& [molecule] = pt::unwrap_inputs(inputs); + + const auto& path = + inputs.at("Path to Grid File").value(); + + std::ifstream file(path); + if(!file.is_open()) { + throw std::runtime_error("Unable to open file: " + path.string()); + } + + // Create the grid + std::string line; + std::vector grid_points; + while(std::getline(file, line)) { + double x, y, z, weight; + std::stringstream ss(line); + ss >> x >> y >> z >> weight; + grid_points.emplace_back(weight, x, y, z); + } + chemist::Grid grid(grid_points.begin(), grid_points.end()); + + file.close(); + + // Return the grid + auto rv = results(); + return pt::wrap_results(rv, std::move(grid)); +} +} // namespace scf::xc diff --git a/src/scf/xc/xc.cpp b/src/scf/xc/xc.cpp index e74036e..d09bf11 100644 --- a/src/scf/xc/xc.cpp +++ b/src/scf/xc/xc.cpp @@ -18,7 +18,10 @@ #include "xc.hpp" namespace scf::xc { -void load_modules(pluginplay::ModuleManager& mm) { gauxc::load_modules(mm); } +void load_modules(pluginplay::ModuleManager& mm) { + gauxc::load_modules(mm); + mm.add_module("Grid From File"); +} void set_defaults(pluginplay::ModuleManager& mm) { gauxc::set_defaults(mm); } } // namespace scf::xc diff --git a/src/scf/xc/xc.hpp b/src/scf/xc/xc.hpp index 74ade90..9eb684e 100644 --- a/src/scf/xc/xc.hpp +++ b/src/scf/xc/xc.hpp @@ -19,6 +19,8 @@ namespace scf::xc { +DECLARE_MODULE(GridFromFile); + void set_defaults(pluginplay::ModuleManager& mm); void load_modules(pluginplay::ModuleManager& mm); diff --git a/tests/cxx/unit_tests/xc/grid_from_file.cpp b/tests/cxx/unit_tests/xc/grid_from_file.cpp new file mode 100644 index 0000000..f177db0 --- /dev/null +++ b/tests/cxx/unit_tests/xc/grid_from_file.cpp @@ -0,0 +1,57 @@ +/* + * Copyright 2025 NWChemEx-Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "../../test_scf.hpp" +#include +#include + +using namespace scf; + +using pt = simde::MolecularGrid; + +TEST_CASE("GridFromFile") { + pluginplay::ModuleManager mm; + scf::load_modules(mm); + auto& mod = mm.at("Grid From File"); + + auto h2 = test_scf::make_h2(); + + SECTION("file doesn't exist") { + std::filesystem::path p = "non_existent_file.txt"; + mod.change_input("Path to Grid File", p); + REQUIRE_THROWS_AS(mod.run_as(h2), std::runtime_error); + } + + SECTION("read h2_grid.txt") { + std::filesystem::path p = "unit_test_pretend_h2_grid.txt"; + std::ofstream file(p); + file << "1.0 2.0 3.0 4.0\n"; + file << "5.0 6.0 7.0 8.0\n"; + file.close(); + mod.change_input("Path to Grid File", p); + auto grid = mod.run_as(h2); + REQUIRE(grid.size() == 2); + REQUIRE(grid.at(0).weight() == 4.0); + REQUIRE(grid.at(0).point().x() == 1.0); + REQUIRE(grid.at(0).point().y() == 2.0); + REQUIRE(grid.at(0).point().z() == 3.0); + REQUIRE(grid.at(1).weight() == 8.0); + REQUIRE(grid.at(1).point().x() == 5.0); + REQUIRE(grid.at(1).point().y() == 6.0); + REQUIRE(grid.at(1).point().z() == 7.0); + std::filesystem::remove(p); + } +} From f83955524ba99cd74c39726ac48b6e1fba878338 Mon Sep 17 00:00:00 2001 From: "Ryan M. Richard" Date: Fri, 12 Sep 2025 14:52:57 -0500 Subject: [PATCH 2/2] add sanity checks, remove unused variable --- src/scf/xc/grid_from_file.cpp | 9 +++- tests/cxx/unit_tests/xc/grid_from_file.cpp | 56 +++++++++++++++++++++- 2 files changed, 62 insertions(+), 3 deletions(-) diff --git a/src/scf/xc/grid_from_file.cpp b/src/scf/xc/grid_from_file.cpp index 4418ba1..75057e0 100644 --- a/src/scf/xc/grid_from_file.cpp +++ b/src/scf/xc/grid_from_file.cpp @@ -52,7 +52,7 @@ MODULE_CTOR(GridFromFile) { } MODULE_RUN(GridFromFile) { - const auto& [molecule] = pt::unwrap_inputs(inputs); + // const auto& [molecule] = pt::unwrap_inputs(inputs); const auto& path = inputs.at("Path to Grid File").value(); @@ -66,9 +66,14 @@ MODULE_RUN(GridFromFile) { std::string line; std::vector grid_points; while(std::getline(file, line)) { + auto first_character = line.find_first_not_of(" \t\r\n"); + if(first_character == std::string::npos) continue; double x, y, z, weight; std::stringstream ss(line); - ss >> x >> y >> z >> weight; + if(!(ss >> x >> y >> z >> weight) || !(ss >> std::ws).eof()) { + throw std::runtime_error("Malformed grid file line: '" + line + + "'"); + } grid_points.emplace_back(weight, x, y, z); } chemist::Grid grid(grid_points.begin(), grid_points.end()); diff --git a/tests/cxx/unit_tests/xc/grid_from_file.cpp b/tests/cxx/unit_tests/xc/grid_from_file.cpp index f177db0..30c7e56 100644 --- a/tests/cxx/unit_tests/xc/grid_from_file.cpp +++ b/tests/cxx/unit_tests/xc/grid_from_file.cpp @@ -35,10 +35,64 @@ TEST_CASE("GridFromFile") { REQUIRE_THROWS_AS(mod.run_as(h2), std::runtime_error); } - SECTION("read h2_grid.txt") { + SECTION("only three points") { + std::filesystem::path p = "unit_test_pretend_h2_grid.txt"; + std::ofstream file(p); + file << "1.0 2.0 3.0\n"; + file << "5.0 6.0 7.0 8.0\n"; + file.close(); + mod.change_input("Path to Grid File", p); + REQUIRE_THROWS_AS(mod.run_as(h2), std::runtime_error); + std::filesystem::remove(p); + } + + SECTION("five numbers points") { + std::filesystem::path p = "unit_test_pretend_h2_grid.txt"; + std::ofstream file(p); + file << "1.0 2.0 3.0 4.0 5.0\n"; + file << "5.0 6.0 7.0 8.0\n"; + file.close(); + mod.change_input("Path to Grid File", p); + REQUIRE_THROWS_AS(mod.run_as(h2), std::runtime_error); + std::filesystem::remove(p); + } + + SECTION("non-mumeric data") { + std::filesystem::path p = "unit_test_pretend_h2_grid.txt"; + std::ofstream file(p); + file << "1.0 2.0 3.0 hello\n"; + file << "5.0 6.0 7.0 8.0\n"; + file.close(); + mod.change_input("Path to Grid File", p); + REQUIRE_THROWS_AS(mod.run_as(h2), std::runtime_error); + std::filesystem::remove(p); + } + + SECTION("good file") { + std::filesystem::path p = "unit_test_pretend_h2_grid.txt"; + std::ofstream file(p); + file << "1.0 2.0 3.0 4.0\n"; + file << "5.0 6.0 7.0 8.0\n"; + file.close(); + mod.change_input("Path to Grid File", p); + auto grid = mod.run_as(h2); + REQUIRE(grid.size() == 2); + REQUIRE(grid.at(0).weight() == 4.0); + REQUIRE(grid.at(0).point().x() == 1.0); + REQUIRE(grid.at(0).point().y() == 2.0); + REQUIRE(grid.at(0).point().z() == 3.0); + REQUIRE(grid.at(1).weight() == 8.0); + REQUIRE(grid.at(1).point().x() == 5.0); + REQUIRE(grid.at(1).point().y() == 6.0); + REQUIRE(grid.at(1).point().z() == 7.0); + std::filesystem::remove(p); + } + + SECTION("skip blank line") { std::filesystem::path p = "unit_test_pretend_h2_grid.txt"; std::ofstream file(p); file << "1.0 2.0 3.0 4.0\n"; + file << "\n"; file << "5.0 6.0 7.0 8.0\n"; file.close(); mod.change_input("Path to Grid File", p);