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/PyGuard.h"
5#include "eon/fpe_handler.h"
6
7#include <atomic>
8#include <format>
9#include <stdexcept>
10#include <string>
11#include <system_error>
12
13#ifdef _WIN32
14#include <process.h>
15#else
16#include <unistd.h>
17#endif
18
19namespace {
20
21std::filesystem::path makeAseWorkDir(const char *prefix) {
22 static std::atomic<long> instanceCount{0};
23#ifdef _WIN32
24 const long pid = static_cast<long>(_getpid());
25#else
26 const long pid = static_cast<long>(getpid());
27#endif
28 std::error_code ec;
29 auto dir = std::filesystem::temp_directory_path(ec);
30 if (ec) {
31 throw std::runtime_error(std::format(
32 "ASE-NWChem: cannot resolve temp directory: {}", ec.message()));
33 }
34 dir /= std::format("{}_{}_{}", prefix, pid, instanceCount.fetch_add(1));
35 std::filesystem::create_directories(dir, ec);
36 if (ec) {
37 throw std::runtime_error(
38 std::format("ASE-NWChem: cannot create work directory {}: {}",
39 dir.string(), ec.message()));
40 }
41 return dir;
42}
43
44} // namespace
45
46// TODO(rg): Clean this up.
48 : Potential(PotType::ASE_NWCHEM, a_params) {
50 counter = 0;
51 py::module_ sys = py::module_::import("sys");
52 // Fix for gh-184, see
53 // https://github.com/numpy/numpy/issues/20504#issuecomment-985542508
55 fpeh.eat_fpe();
56 ase = py::module_::import("ase");
57 fpeh.restore_fpe();
58 py::module_ ase_nwchem = py::module_::import("ase.calculators.nwchem");
59 py::module_ psutil = py::module_::import("psutil");
60 std::string nwchempth = eonc::helpers::get_value_from_env_or_param(
61 "NWCHEM_COMMAND", a_params.ase_nwchem_options.path, "", "", true);
62 std::string nwc_mult = eonc::helpers::get_value_from_env_or_param(
63 "NWCHEM_MULTIPLICITY", a_params.ase_nwchem_options.multiplicity, "1",
64 "Using 1 as a default multiplicity, i.e. an RHF calculation suitable for "
65 "closed shell molecules, set multiplicity or the "
66 "environment variable NWCHEM_MULTIPLICITY.\n");
67
68 // Set up NWCHEM arguments
69 // TODO(rg): Stop hardcoding these
70 py::object NWCHEM = ase_nwchem.attr("NWChem");
71 size_t nproc{0};
72 auto mult = std::stoi(nwc_mult); // 1 for singlet, 2 for doublet
73
74 // TODO(rg): Use
75 if (a_params.ase_nwchem_options.nproc == "auto") {
76 nproc = py::cast<int>(psutil.attr("cpu_count")(false));
77 } else {
78 nproc = std::stoi(a_params.ase_nwchem_options.nproc);
79 }
80
81 // dont_verify so we always get an energy and gradient
82 // mpi_launcher: mpirun (default) or srun on Slurm nodes (issue #193)
83 const std::string &launcher = a_params.ase_nwchem_options.mpi_launcher;
84 std::string mpi_cmd;
85 if (launcher == "srun") {
86 // srun uses -n for tasks; avoid OpenMPI-specific flags.
87 mpi_cmd =
88 std::format("srun -n {} {} PREFIX.nwi > PREFIX.nwo", nproc, nwchempth);
89 } else {
90 // Default and any other launcher treated as OpenMPI-style mpirun -n.
91 mpi_cmd = std::format("{} -n {} {} PREFIX.nwi > PREFIX.nwo", launcher,
92 nproc, nwchempth);
93 }
94
95 if (mult != 1 && mult != 2) {
96 throw std::runtime_error("Unknown spin multiplicity, we support 1 for "
97 "singlet and 2 for doublet ONLY.");
98 }
99
100 // One directory per calculator instance so two LocalInProcess jobs in
101 // the same cwd do not write PREFIX.nwi / PREFIX.nwo over each other.
102 workDir = makeAseWorkDir("eon_ase_nwchem");
103
104 // Common NWCHEM parameters
105 py::dict nwchem_params = py::dict(
106 "label"_a = "_eonpot_engrad",
107 "set"_a = py::dict("geom:dont_verify"_a = true),
108 "command"_a = py::str(mpi_cmd), "memory"_a = py::str("2 gb"),
109 "scf"_a = 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("3-21G"), "task"_a = py::str("gradient"),
113 "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 variance = nullptr;
143 try {
144 AtomMatrix positions = AtomMatrix::Map(const_cast<double *>(R), nAtoms, 3);
145 RotationMatrix boxx = RotationMatrix::Map(const_cast<double *>(box), 3, 3);
146 Eigen::VectorXi atmnmrs =
147 Eigen::Map<Eigen::VectorXi>(const_cast<int *>(atomicNrs), nAtoms);
148 // XXX: NWChem refuses to perform SCF for anything but a molecule, so no box
149 // or pbc can be passed
150 py::object atoms = this->ase.attr("Atoms")("symbols"_a = atmnmrs,
151 "positions"_a = positions);
152 atoms.attr("calc") = this->calc;
153 // atoms.attr("center")();
154 double py_e = py::cast<double>(atoms.attr("get_potential_energy")());
155 Eigen::MatrixXd py_force =
156 py::cast<Eigen::MatrixXd>(atoms.attr("get_forces")());
157
158 // Populate the output parameters
159 *U = py_e;
160 for (long i = 0; i < nAtoms; ++i) {
161 F[3 * i] = py_force(i, 0);
162 F[3 * i + 1] = py_force(i, 1);
163 F[3 * i + 2] = py_force(i, 2);
164 }
165 } catch (py::error_already_set &e) {
166 throw std::runtime_error(std::string("ASE-NWChem Python error: ") +
167 e.what());
168 } catch (const std::exception &e) {
169 throw std::runtime_error(std::string("ASE-NWChem C++ exception: ") +
170 e.what());
171 }
172
173 counter++;
174 return;
175}
Eigen::Matrix< double, 3, 3, eOnStorageOrder > RotationMatrix
Definition Eigen.h:38
Eigen::Matrix< double, Eigen::Dynamic, 3, eOnStorageOrder > AtomMatrix
Definition Eigen.h:37
void force(long nAtoms, const double *R, const int *atomicNrs, double *F, double *U, double *variance, const double *box) override
virtual ~ASENwchemPot()
std::filesystem::path workDir
Definition ASE_NWCHEM.h:31
py::object calc
Definition ASE_NWCHEM.h:29
py::object ase
Definition ASE_NWCHEM.h:30
ASENwchemPot(const Parameters &a_params)
size_t counter
Definition ASE_NWCHEM.h:32
struct eonc::Parameters::ase_nwchem_options_t ase_nwchem_options
Potential(PotType a_ptype)
Definition Potential.h:35
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
void ensure_interpreter()
Definition NbGuard.h:21