Loading...
Searching...
No Matches
SocketNWChemPot.cpp
Go to the documentation of this file.
2
3#include <cctype>
4#include <cstring>
5#include <fstream>
6#include <iostream>
7#include <stdexcept>
8
9#include <arpa/inet.h>
10#include <sys/socket.h>
11#include <sys/un.h>
12#include <unistd.h>
13
14// Helper to get element symbols from atomic numbers
15using std::this_thread::sleep_for;
16using namespace std::chrono_literals;
17
18namespace {
19const char *elementArray[] = {
20 "Unknown", "H", "He", "Li", "Be", "B", "C", "N", "O", "F", "Ne",
21 "Na", "Mg", "Al", "Si", "P", "S", "Cl", "Ar", "K", "Ca", "Sc",
22 "Ti", "V", "Cr", "Mn", "Fe", "Co", "Ni", "Cu", "Zn", "Ga", "Ge",
23 "As", "Se", "Br", "Kr", "Rb", "Sr", "Y", "Zr", "Nb", "Mo", "Tc",
24 "Ru", "Rh", "Pd", "Ag", "Cd", "In", "Sn", "Sb", "Te", "I", "Xe",
25 "Cs", "Ba", "La", "Ce", "Pr", "Nd", "Pm", "Sm", "Eu", "Gd", "Tb",
26 "Dy", "Ho", "Er", "Tm", "Yb", "Lu", "Hf", "Ta", "W", "Re", "Os",
27 "Ir", "Pt", "Au", "Hg", "Tl", "Pb", "Bi", "Po", "At", "Rn", "Fr",
28 "Ra", "Ac", "Th", "Pa", "U", nullptr};
29char const *atomicNumber2symbol(int n) { return elementArray[n]; }
30} // namespace
31
33 : Potential(PotType::SocketNWChem, p),
34 listen_fd(-1),
35 conn_fd(-1),
36 is_connected(false) {
37
42
43 if (unix_socket_mode) {
45 // NWChem's Fortran i-PI driver truncates the socket name to ~30 chars.
46 // The full path is /tmp/ipi_<basename>, so basename must be short.
48 if (server_address.size() > 30) {
49 std::cerr << "ERROR: UNIX socket path '" << server_address << "' is "
50 << server_address.size()
51 << " characters, which exceeds NWChem's ~30 character limit.\n"
52 << "NWChem will silently truncate it, causing a connection "
53 "failure.\n"
54 << "Shorten unix_socket_path (currently '"
55 << unix_socket_basename << "') to at most " << (30 - 9)
56 << " characters.\n";
57 throw std::runtime_error(
58 "unix_socket_path too long for NWChem (max ~21 chars, got " +
59 std::to_string(unix_socket_basename.size()) + ")");
60 }
61 port = -1;
62 std::cout << "SocketNWChemPot: Initializing in UNIX mode." << std::endl;
63 std::cout << "Listening on socket file: " << server_address << std::endl;
64 } else {
67 std::cout << "SocketNWChemPot: Initializing in TCP mode." << std::endl;
68 std::cout << "Listening on: " << server_address << ":" << port << std::endl;
69 }
70
72}
73
75 if (is_connected) {
76 std::cout << "Closing connection to NWChem client..." << std::endl;
77 try {
78 send_header("EXIT");
79 } catch (...) {
80 // Ignore errors during shutdown
81 }
82 }
83 if (conn_fd >= 0)
84 ::close(conn_fd);
85 if (listen_fd >= 0)
86 ::close(listen_fd);
87 if (unix_socket_mode) {
88 ::unlink(server_address.c_str());
89 }
90}
91
92// =============================================
93// Public Methods
94// =============================================
95
97 const std::string &filename, long N,
98 const std::vector<std::string> &atom_symbols) {
99 std::ofstream outfile(filename);
100 if (!outfile.is_open()) {
101 throw std::runtime_error("Could not open file to write NWChem template: " +
102 filename);
103 }
104
105 outfile << "start nwchem_socket_job\n";
106 outfile << "title \"NWChem Server for eOn\"\n\n";
107 outfile << "memory " << mem_in_gb << " gb\n\n";
108 outfile << "geometry units bohr noautosym nocenter noautoz\n";
109 // This geometry block is only a template for memory allocation.
110 // The atom types and count are what matter.
111 for (long i = 0; i < N; ++i) {
112 outfile << " " << atom_symbols[i] << " 0.0 0.0 " << static_cast<double>(i)
113 << "\n";
114 }
115 outfile << "end\n\n";
116 outfile << "include " << nwchem_settings << "\n\n";
117 outfile << "driver\n";
118 if (unix_socket_mode) {
119 // For the NWChem input, we provide only the basename. NWChem adds the
120 // prefix.
121 outfile << " socket unix " << unix_socket_basename << "\n";
122 } else {
123 outfile << " socket ipi_client " << server_address << ":" << port << "\n";
124 }
125 outfile << "end\n\n";
126 outfile << "task scf optimize\n";
127
128 outfile.close();
129}
130
131void SocketNWChemPot::force(long N, const double *R, const int *atomicNrs,
132 double *F, double *U, double *variance,
133 const double *box) {
134 if (!is_connected) {
135 std::vector<std::string> symbols;
136 symbols.reserve(N);
137 for (long i = 0; i < N; ++i) {
138 symbols.emplace_back(atomicNumber2symbol(atomicNrs[i]));
139 }
141 write_nwchem_template("nwchem_socket.nwi", N, symbols);
142 }
143
144 std::cout << "Waiting for NWChem client connection..." << std::endl;
146 std::cout << "NWChem client connected." << std::endl;
147
148 // 1. eOn acts as the server: after accepting the NWChem client connection,
149 // eOn sends "STATUS" to the client to query its status.
150 char status_buffer[MSG_LEN + 1] = {0};
151 send_header("STATUS");
152
153 // 2. eOn (acting as server) then waits for NWChem (the client) to respond
154 // with "READY".
155 recv_header(status_buffer);
156 if (std::string(status_buffer) != "READY") {
157 throw std::runtime_error(
158 "Handshake failed: NWChem client not READY. It sent: " +
159 std::string(status_buffer));
160 }
161 std::cout << "NWChem server is connected and READY." << std::endl;
162 }
163
164 // Check status for this specific force call
165 char status_buffer[MSG_LEN + 1] = {0};
166 send_header("STATUS");
167 recv_header(status_buffer);
168
169 if (std::string(status_buffer) == "NEEDINIT") {
170 send_header("INIT");
171 // Send dummy INIT payload (bead index, number of bytes in extra string)
172 int32_t init_payload[] = {0, 1}; // bead_index=0, nbytes=1
173 char dummy_byte = 0;
174 send_exact(&init_payload, sizeof(init_payload));
175 send_exact(&dummy_byte,
176 sizeof(dummy_byte)); // No extra string (just a null terminator)
177 send_header("STATUS");
178 recv_header(status_buffer);
179 }
180
181 if (std::string(status_buffer) != "READY") {
182 throw std::runtime_error("NWChem server not ready for new positions!");
183 }
184
185 // Convert positions to Bohr
186 std::vector<double> pos_bohr(N * 3);
187 for (size_t i = 0; i < pos_bohr.size(); ++i) {
188 pos_bohr[i] = R[i] / BOHR_IN_ANGSTROM;
189 }
190
191 // Per i-PI spec, cell and inverse cell must be sent. NWChem does not use
192 // this information for non-periodic calculations, so we send an identity
193 // matrix as a safe, non-transforming placeholder to conform to the protocol.
194 double invcell_T[9] = {1, 0, 0, 0, 1, 0, 0, 0, 1};
195 double cell_T[9] = {1, 0, 0, 0, 1, 0, 0, 0, 1};
196
197 send_header("POSDATA");
198 int32_t nat = N;
199 send_exact(cell_T, sizeof(cell_T));
200 send_exact(invcell_T, sizeof(invcell_T));
201 send_exact(&nat, sizeof(nat));
202 send_exact(pos_bohr.data(), pos_bohr.size() * sizeof(double));
203
204 // Poll for results
205 while (true) {
206 send_header("STATUS");
207 recv_header(status_buffer);
208 if (std::string(status_buffer) == "HAVEDATA") {
209 break;
210 }
211 // A small sleep to prevent busy-waiting that consumes 100% CPU.
212 sleep_for(10ms);
213 }
214
215 // Request and receive results ---
216 send_header("GETFORCE");
217 recv_header(status_buffer);
218 if (std::string(status_buffer) != "FORCEREADY") {
219 throw std::runtime_error("Expected FORCEREADY, got " +
220 std::string(status_buffer));
221 }
222
223 // Unpack the results payload.
224 double energy_ha;
225 int32_t nat_back;
226 std::vector<double> forces_ha_bohr(N * 3);
227 double virial_ha[9];
228 int32_t extra_len;
229
230 recv_exact(&energy_ha, sizeof(energy_ha));
231 recv_exact(&nat_back, sizeof(nat_back));
232 if (nat_back != N)
233 throw std::runtime_error("Atom count mismatch from NWChem");
234 recv_exact(forces_ha_bohr.data(), forces_ha_bohr.size() * sizeof(double));
235 recv_exact(&virial_ha, sizeof(virial_ha));
236 recv_exact(&extra_len, sizeof(extra_len));
237 if (extra_len > 0) {
238 std::vector<char> extra_buf(extra_len);
239 recv_exact(extra_buf.data(), extra_len);
240 }
241
242 // Convert results back to eOn units (eV and Angstrom)
243 *U = energy_ha * HARTREE_IN_EV;
244 for (int i = 0; i < N * 3; ++i) {
245 F[i] = forces_ha_bohr[i] * (HARTREE_IN_EV / BOHR_IN_ANGSTROM);
246 }
247 *variance = 0.0;
248}
249
250// =================================================
251// Private Helper Methods for Socket Communication
252// =================================================
253
255 int domain = unix_socket_mode ? AF_UNIX : AF_INET;
256 listen_fd = socket(domain, SOCK_STREAM, 0);
257 if (listen_fd < 0) {
258 throw std::runtime_error("Failed to create socket.");
259 }
260
261 if (unix_socket_mode) {
262 ::unlink(server_address.c_str()); // Remove stale socket file if it exists
263 sockaddr_un sock_addr{};
264 sock_addr.sun_family = AF_UNIX;
265 strncpy(sock_addr.sun_path, server_address.c_str(),
266 sizeof(sock_addr.sun_path) - 1);
267
268 socklen_t addr_len =
269 sizeof(sock_addr.sun_family) + strlen(sock_addr.sun_path);
270 if (::bind(listen_fd, (struct sockaddr *)&sock_addr, addr_len) < 0) {
271 perror("UNIX bind failed");
272 throw std::runtime_error("Failed to bind UNIX socket.");
273 }
274 } else { // TCP Mode
275 int opt = 1;
276 setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
277 sockaddr_in sock_addr{};
278 sock_addr.sin_family = AF_INET;
279 sock_addr.sin_addr.s_addr = inet_addr(server_address.c_str());
280 sock_addr.sin_port = htons(port);
281
282 if (::bind(listen_fd, (struct sockaddr *)&sock_addr, sizeof(sock_addr)) <
283 0) {
284 perror("TCP bind failed");
285 throw std::runtime_error("Failed to bind TCP socket.");
286 }
287 }
288
289 if (::listen(listen_fd, 1) < 0) {
290 perror("listen() failed");
291 throw std::runtime_error("Socket listen() failed.");
292 }
293}
294
296 conn_fd = accept(listen_fd, nullptr, nullptr);
297 if (conn_fd < 0) {
298 perror("accept() failed");
299 throw std::runtime_error("Failed to accept client connection.");
300 }
301 is_connected = true;
302}
303
304void SocketNWChemPot::send_header(const char *msg) {
305 char buffer[MSG_LEN] = {0};
306 strncpy(buffer, msg, MSG_LEN);
307 send_exact(buffer, MSG_LEN);
308}
309
311 recv_exact(buffer, MSG_LEN);
312 buffer[MSG_LEN] = '\0'; // Null-terminate
313 // Trim trailing whitespace
314 for (int i = MSG_LEN - 1; i >= 0 && isspace((unsigned char)buffer[i]); --i) {
315 buffer[i] = '\0';
316 }
317}
318
319void SocketNWChemPot::send_exact(const void *buffer, size_t n_bytes) {
320 size_t sent = 0;
321 while (sent < n_bytes) {
322 ssize_t n = ::send(conn_fd, (const char *)buffer + sent, n_bytes - sent, 0);
323 if (n <= 0) {
324 throw std::runtime_error(
325 "send_exact failed: connection closed or error.");
326 }
327 sent += n;
328 }
329}
330
331void SocketNWChemPot::recv_exact(void *buffer, size_t n_bytes) {
332 size_t recvd = 0;
333 while (recvd < n_bytes) {
334 ssize_t n = ::recv(conn_fd, (char *)buffer + recvd, n_bytes - recvd, 0);
335 if (n <= 0) {
336 throw std::runtime_error(
337 "recv_exact failed: connection closed or error.");
338 }
339 recvd += n;
340 }
341}
std::string unix_socket_basename
~SocketNWChemPot() override
static constexpr int MSG_LEN
void send_header(const char *msg)
void recv_exact(void *buffer, size_t n_bytes)
void send_exact(const void *buffer, size_t n_bytes)
std::string server_address
static constexpr double BOHR_IN_ANGSTROM
void recv_header(char *buffer)
static constexpr double HARTREE_IN_EV
void write_nwchem_template(const std::string &filename, long N, const std::vector< std::string > &atom_symbols)
Generates an NWChem input file (.nwi) configured to connect to this server.
std::string nwchem_settings
SocketNWChemPot(const Parameters &p)
void force(long N, const double *R, const int *atomicNrs, double *F, double *U, double *variance, const double *box) override
The method called to compute forces and energy.
struct eonc::Parameters::socket_nwchem_options_t socket_nwchem_options
Potential(PotType a_ptype)
Definition Potential.h:35