Loading...
Searching...
No Matches
ASE_NWCHEM.cpp
Go to the documentation of this file.
2#include "eon/EnvHelpers.hpp"
3#include "eon/EonLogger.h"
4#include "eon/Parameters.h"
5#include "eon/PyGuard.h"
6#include "eon/fpe_handler.h"
7
8#include <atomic>
9#include <format>
10#include <stdexcept>
11#include <string>
12#include <system_error>
13
14#ifdef _WIN32
15#include <process.h>
16#else
17#include <unistd.h>
18#endif
19
20namespace {
21
22std::filesystem::path makeAseWorkDir(const char *prefix) {
23 static std::atomic<long> instanceCount{0};
24#ifdef _WIN32
25 const long pid = static_cast<long>(_getpid());
26#else
27 const long pid = static_cast<long>(getpid());
28#endif
29 std::error_code ec;
30 auto dir = std::filesystem::temp_directory_path(ec);
31 if (ec) {
32 throw std::runtime_error(std::format(
33 "ASE-NWChem: cannot resolve temp directory: {}", ec.message()));
34 }
35 dir /= std::format("{}_{}_{}", prefix, pid, instanceCount.fetch_add(1));
36 std::filesystem::create_directories(dir, ec);
37 if (ec) {
38 throw std::runtime_error(
39 std::format("ASE-NWChem: cannot create work directory {}: {}",
40 dir.string(), ec.message()));
41 }
42 return dir;
43}
44
45} // namespace
46
48 : eonc::Potential(eonc::PotType::ASE_NWCHEM, a_params) {
49 using namespace pybind11::literals;
51 counter = 0;
52 py::module_ sys = py::module_::import("sys");
53 // Fix for gh-184, see
54 // https://github.com/numpy/numpy/issues/20504#issuecomment-985542508
56 fpeh.eat_fpe();
57 ase = py::module_::import("ase");
58 fpeh.restore_fpe();
59 py::module_ ase_nwchem = py::module_::import("ase.calculators.nwchem");
60 py::module_ psutil = py::module_::import("psutil");
61 std::string nwchempth = eonc::helpers::get_value_from_env_or_param(
62 "NWCHEM_COMMAND", a_params.ase_nwchem_options().path, "", "", true);
63 std::string nwc_mult = eonc::helpers::get_value_from_env_or_param(
64 "NWCHEM_MULTIPLICITY", a_params.ase_nwchem_options().multiplicity, "1",
65 "Using 1 as a default multiplicity, i.e. an RHF calculation suitable for "
66 "closed shell molecules, set multiplicity or the "
67 "environment variable NWCHEM_MULTIPLICITY.\n");
68
69 py::object NWCHEM = ase_nwchem.attr("NWChem");
70 size_t nproc{0};
71 auto mult = std::stoi(nwc_mult); // 1 for singlet, 2 for doublet
72
73 if (a_params.ase_nwchem_options().nproc == "auto") {
74 nproc = py::cast<int>(psutil.attr("cpu_count")(false));
75 } else {
76 nproc = std::stoi(a_params.ase_nwchem_options().nproc);
77 }
78
79 // dont_verify so we always get an energy and gradient
80 // mpi_launcher: mpirun (default) or srun on Slurm nodes (issue #193)
81 const std::string &launcher = a_params.ase_nwchem_options().mpi_launcher;
82 std::string mpi_cmd;
83 if (launcher == "srun") {
84 // srun uses -n for tasks; avoid OpenMPI-specific flags.
85 mpi_cmd =
86 std::format("srun -n {} {} PREFIX.nwi > PREFIX.nwo", nproc, nwchempth);
87 } else {
88 // Default and any other launcher treated as OpenMPI-style mpirun -n.
89 mpi_cmd = std::format("{} -n {} {} PREFIX.nwi > PREFIX.nwo", launcher,
90 nproc, nwchempth);
91 }
92
93 if (mult != 1 && mult != 2) {
94 throw std::runtime_error("Unknown spin multiplicity, we support 1 for "
95 "singlet and 2 for doublet ONLY.");
96 }
97
98 // One directory per calculator instance so two LocalInProcess jobs in
99 // the same cwd do not write PREFIX.nwi / PREFIX.nwo over each other.
100 workDir = makeAseWorkDir("eon_ase_nwchem");
101
102 // Common NWCHEM parameters
103 py::dict nwchem_params = py::dict(
104 "label"_a = "_eonpot_engrad",
105 "set"_a = py::dict("geom:dont_verify"_a = true),
106 "command"_a = py::str(mpi_cmd),
107 "memory"_a = py::str(a_params.ase_nwchem_options().memory),
108 "scf"_a =
109 py::dict("nopen"_a = mult - 1,
110 "thresh"_a = a_params.ase_nwchem_options().scf_thresh,
111 "maxiter"_a = a_params.ase_nwchem_options().scf_maxiter),
112 "basis"_a = py::str(a_params.ase_nwchem_options().basis),
113 "task"_a = py::str("gradient"), "directory"_a = workDir.string());
114
115 // Set flag for doublet (mult == 2)
116 if (mult == 2) {
117 nwchem_params["scf"]["uhf"] = py::none();
118 }
119
120 try {
121 this->calc = NWCHEM(**nwchem_params);
122 } catch (...) {
123 std::error_code ec;
124 std::filesystem::remove_all(workDir, ec);
125 workDir.clear();
126 throw;
127 }
128};
129
131 QUILL_LOG_INFO(eonc::log::get(), "[ASENwchem] called potential {} times",
132 counter);
133 if (!workDir.empty()) {
134 std::error_code ec;
135 std::filesystem::remove_all(workDir, ec);
136 }
137}
138
139void ASENwchemPot::force(long nAtoms, const double *R, const int *atomicNrs,
140 double *F, double *U, double *variance,
141 const double *box) {
142 using namespace pybind11::literals;
143 if (variance != nullptr) {
144 *variance = 0.0;
145 }
146 try {
147 const Eigen::Map<const AtomMatrix> positions(R, nAtoms, 3);
148 const Eigen::Map<const RotationMatrix> boxx(box);
149 const Eigen::Map<const Eigen::VectorXi> atmnmrs(atomicNrs, nAtoms);
150 if (!boxx.isIdentity(1e-6)) {
151 QUILL_LOG_WARNING(eonc::log::get(), "ASE-NWChem ignores the simulation "
152 "cell; NWChem SCF is molecular only");
153 }
154 py::object atoms = this->ase.attr("Atoms")("symbols"_a = atmnmrs,
155 "positions"_a = positions);
156 atoms.attr("calc") = this->calc;
157 // atoms.attr("center")();
158 double py_e = py::cast<double>(atoms.attr("get_potential_energy")());
159 Eigen::MatrixXd py_force =
160 py::cast<Eigen::MatrixXd>(atoms.attr("get_forces")());
161
162 // Populate the output parameters
163 *U = py_e;
164 for (long i = 0; i < nAtoms; ++i) {
165 F[3 * i] = py_force(i, 0);
166 F[3 * i + 1] = py_force(i, 1);
167 F[3 * i + 2] = py_force(i, 2);
168 }
169 } catch (py::error_already_set &e) {
170 throw std::runtime_error(std::string("ASE-NWChem Python error: ") +
171 e.what());
172 } catch (const std::exception &e) {
173 throw std::runtime_error(std::string("ASE-NWChem C++ exception: ") +
174 e.what());
175 }
176
177 counter++;
178 return;
179}
void force(long nAtoms, const double *R, const int *atomicNrs, double *F, double *U, double *variance, const double *box) override
virtual ~ASENwchemPot()
ASENwchemPot(const eonc::Parameters &a_params)
std::filesystem::path workDir
Definition ASE_NWCHEM.h:30
py::object calc
Definition ASE_NWCHEM.h:28
py::object ase
Definition ASE_NWCHEM.h:29
size_t counter
Definition ASE_NWCHEM.h:31
const ase_nwchem_options_t & ase_nwchem_options() const
Potential(PotType a_ptype)
Production default: construction-scope registry, else PotRegistry::get().
std::string get_value_from_env_or_param(const char *env_variable, const std::string &param_value, const std::string &default_value, const std::string &warning_message, const bool is_mandatory)
Definition EnvHelpers.cc:7
quill::Logger * get() noexcept
Get or create the default "combi" logger.
Definition EonLogger.h:44
RAII resource manager for the ARTn C library with global synchronization.
void ensure_interpreter()
Definition NbGuard.h:21