Loading...
Searching...
No Matches
SocketNWChemPot Class Reference

A potential that gets forces and energy from an NWChem server via a socket. More...

#include <SocketNWChemPot.h>

Inheritance diagram for SocketNWChemPot:

Public Member Functions

 SocketNWChemPot (const Parameters &p)
 ~SocketNWChemPot () override
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.
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.
bool requiresIsolatedMoleculeLayout () const noexcept override
 NWChem socket SCF does not support PBC (#188).
bool isThreadSafe () const noexcept override
 Whether this potential's force() can be called from multiple threads on the SAME instance.
Public Member Functions inherited from eonc::Potential
 Potential (PotType a_ptype)
 Potential (PotType a_ptype, const Parameters &)
 Potential (const Parameters &a_params)
virtual ~Potential ()
std::tuple< double, AtomMatrixget_ef (const AtomMatrix &pos, const VectorXi &atmnrs, const Matrix3d &box)
PotType getType () const
virtual bool isSurrogate () const noexcept
 Whether this is a surrogate (GP) potential.
virtual bool isSharedInstanceThreadSafe () const noexcept
 Conservative gate for sharing one Potential instance across threads.
virtual bool needsPerImageInstance () const noexcept
 Whether NEB should create separate Potential instances per image for true parallel force evaluation.
virtual bool supportsBatchEvaluation () const noexcept
 Whether this potential supports batched evaluation of N systems in a single call.
virtual void forceBatch (long nSystems, long nAtoms, const double *const *positions, const int *const *atomicNrs, double *const *forces, double *energies, double *variances, const double *const *boxes)
 Evaluate forces for N systems in a single call.

Private Member Functions

void setup_server ()
void accept_connection ()
void send_header (const char *msg)
void recv_header (char *buffer)
void send_exact (const void *buffer, size_t n_bytes)
void recv_exact (void *buffer, size_t n_bytes)

Private Attributes

bool unix_socket_mode
std::string server_address
std::string unix_socket_basename
std::string nwchem_settings
int port
int mem_in_gb
bool make_template_input
int listen_fd
int conn_fd
bool is_connected

Static Private Attributes

static constexpr int MSG_LEN = 12
static constexpr double BOHR_IN_ANGSTROM = 0.529177210903
static constexpr double HARTREE_IN_EV = 27.211386245988

Additional Inherited Members

Public Attributes inherited from eonc::Potential
std::atomic< size_t > forceCallCounter
Protected Attributes inherited from eonc::Potential
PotType ptype

Detailed Description

A potential that gets forces and energy from an NWChem server via a socket.

Implements the i-PI socket protocol to communicate with an NWChem executable that has been launched in server mode (e.g., using task scf optimize and the driver socket directive). This class manages a persistent connection to NWChem. Supports both TCP/IP and UNIX domain sockets.

Definition at line 27 of file SocketNWChemPot.h.

Constructor & Destructor Documentation

◆ SocketNWChemPot()

SocketNWChemPot::SocketNWChemPot ( const Parameters & p)
explicit

Definition at line 32 of file SocketNWChemPot.cpp.

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}
std::string unix_socket_basename
std::string server_address
std::string nwchem_settings
struct eonc::Parameters::socket_nwchem_options_t socket_nwchem_options
Potential(PotType a_ptype)
Definition Potential.h:35

◆ ~SocketNWChemPot()

SocketNWChemPot::~SocketNWChemPot ( )
override

Definition at line 74 of file SocketNWChemPot.cpp.

74 {
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}
void send_header(const char *msg)

Member Function Documentation

◆ accept_connection()

void SocketNWChemPot::accept_connection ( )
private

Definition at line 295 of file SocketNWChemPot.cpp.

295 {
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}

◆ force()

void SocketNWChemPot::force ( long N,
const double * R,
const int * atomicNrs,
double * F,
double * U,
double * variance,
const double * box )
overridevirtual

The method called to compute forces and energy.

This method implements the full i-PI handshake for a single geometry:

  1. Checks the client status, handling the NEEDINIT state if necessary.
  2. Sends the new atomic positions and cell (POSDATA).
  3. Polls until the calculation is complete (HAVEDATA).
  4. Retrieves the results (GETFORCE).
Parameters
NThe number of atoms.
RPointer to the beginning of the position array [x1, y1, z1, ...].
atomicNrsPointer to the array of atomic numbers.
FPointer to the beginning of the force array to be populated.
UPointer to the potential energy value to be set.
varianceNot used by this potential.
boxPointer to the 3x3 simulation box matrix.

Implements eonc::Potential.

Definition at line 131 of file SocketNWChemPot.cpp.

133 {
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}
static constexpr int MSG_LEN
void recv_exact(void *buffer, size_t n_bytes)
void send_exact(const void *buffer, size_t n_bytes)
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.

◆ isThreadSafe()

bool SocketNWChemPot::isThreadSafe ( ) const
inlinenodiscardoverridevirtualnoexcept

Whether this potential's force() can be called from multiple threads on the SAME instance.

Python-based potentials return false. Potentials with internal mutex (MetatomicPotential) return true but serialize internally – use needsPerImageInstance() to check if separate instances would enable true parallelism.

Reimplemented from eonc::Potential.

Definition at line 66 of file SocketNWChemPot.h.

66{ return false; }

◆ recv_exact()

void SocketNWChemPot::recv_exact ( void * buffer,
size_t n_bytes )
private

Definition at line 331 of file SocketNWChemPot.cpp.

331 {
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}

◆ recv_header()

void SocketNWChemPot::recv_header ( char * buffer)
private

Definition at line 310 of file SocketNWChemPot.cpp.

310 {
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}

◆ requiresIsolatedMoleculeLayout()

bool SocketNWChemPot::requiresIsolatedMoleculeLayout ( ) const
inlinenodiscardoverridevirtualnoexcept

NWChem socket SCF does not support PBC (#188).

Reimplemented from eonc::Potential.

Definition at line 63 of file SocketNWChemPot.h.

63 {
64 return true;
65 }

◆ send_exact()

void SocketNWChemPot::send_exact ( const void * buffer,
size_t n_bytes )
private

Definition at line 319 of file SocketNWChemPot.cpp.

319 {
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}

◆ send_header()

void SocketNWChemPot::send_header ( const char * msg)
private

Definition at line 304 of file SocketNWChemPot.cpp.

304 {
305 char buffer[MSG_LEN] = {0};
306 strncpy(buffer, msg, MSG_LEN);
307 send_exact(buffer, MSG_LEN);
308}

◆ setup_server()

void SocketNWChemPot::setup_server ( )
private

Definition at line 254 of file SocketNWChemPot.cpp.

254 {
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}

◆ write_nwchem_template()

void SocketNWChemPot::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.

Parameters
filenameThe path to the output file (e.g., "nwchem_socket.nwi").
NThe number of atoms.
atom_symbolsA vector of atom symbols (e.g., {"O", "H", "H"}).

Definition at line 96 of file SocketNWChemPot.cpp.

98 {
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}

Member Data Documentation

◆ BOHR_IN_ANGSTROM

double SocketNWChemPot::BOHR_IN_ANGSTROM = 0.529177210903
staticconstexprprivate

Definition at line 98 of file SocketNWChemPot.h.

◆ conn_fd

int SocketNWChemPot::conn_fd
private

Definition at line 92 of file SocketNWChemPot.h.

◆ HARTREE_IN_EV

double SocketNWChemPot::HARTREE_IN_EV = 27.211386245988
staticconstexprprivate

Definition at line 99 of file SocketNWChemPot.h.

◆ is_connected

bool SocketNWChemPot::is_connected
private

Definition at line 93 of file SocketNWChemPot.h.

◆ listen_fd

int SocketNWChemPot::listen_fd
private

Definition at line 91 of file SocketNWChemPot.h.

◆ make_template_input

bool SocketNWChemPot::make_template_input
private

Definition at line 88 of file SocketNWChemPot.h.

◆ mem_in_gb

int SocketNWChemPot::mem_in_gb
private

Definition at line 86 of file SocketNWChemPot.h.

◆ MSG_LEN

int SocketNWChemPot::MSG_LEN = 12
staticconstexprprivate

Definition at line 96 of file SocketNWChemPot.h.

◆ nwchem_settings

std::string SocketNWChemPot::nwchem_settings
private

Definition at line 85 of file SocketNWChemPot.h.

◆ port

int SocketNWChemPot::port
private

Definition at line 86 of file SocketNWChemPot.h.

◆ server_address

std::string SocketNWChemPot::server_address
private

Definition at line 81 of file SocketNWChemPot.h.

◆ unix_socket_basename

std::string SocketNWChemPot::unix_socket_basename
private

Definition at line 83 of file SocketNWChemPot.h.

◆ unix_socket_mode

bool SocketNWChemPot::unix_socket_mode
private

Definition at line 79 of file SocketNWChemPot.h.


The documentation for this class was generated from the following files: