← Documentation
ADS-IDAC Engine 5.0.0
Client.cpp
Go to the documentation of this file.
1//
2// Created by atezbas on 10/21/24.
3//
4
5#include "Client.h"
6#include "../Global/RestartTrim.h"
7#include "../Global/SharedConfig.h"
8#include "../Solver/RelapSolver.h"
9#include "../Solver/RestApiSolver.h"
10#include "../Global/KeyParameterHistory.h"
11#include "../Scheduler/AnalysisInfoContainer.h"
12#include <csignal>
13#include <unistd.h>
14#include <fstream>
15#include <unordered_set>
16#include "../Global/SimulationContext.h"
17#include "../Global/CheckedIO.h"
18#include "../Global/PEOutput.h"
19using namespace std;
20
21
22// SEQ-FP: log sequence fingerprint after a sequence completes (MPI version).
23static void log_seq_fingerprint_mpi(int rank, double term_time) {
24 if (!getenv("ADSIDAC_BRANCH_DIAG")) return;
25 // Compute ancestry path by walking mother chain
26 std::string ancestry;
27 if (g_sim.seq_restart_cid == 0) {
28 ancestry = "root";
29 } else {
30 std::vector<std::string> parts;
31 int cur_cid = g_sim.seq_restart_cid;
32 int cur_bp = g_sim.seq_restart_branch_num;
33 while (cur_cid != 0) {
35 if (!bc) {
36 fprintf(stderr, "[SEQ-FP] WALK FAIL: CID=%d not found, %zu clusters. CIDs:", cur_cid, g_sim.Branch_center.branch_cluster.size());
37 for (auto& c : g_sim.Branch_center.branch_cluster) fprintf(stderr, " %d", c->getIDOfThisBranchCluster());
38 fprintf(stderr, "\n");
39 break;
40 }
41 char buf[64];
42 snprintf(buf, sizeof(buf), "%.4f:%d", bc->getTimeAtWhichThisEventHappen(), cur_bp);
43 parts.emplace_back(buf);
44 int mother_cid = bc->getIDOfMotherCluster();
45 if (mother_cid == 0) break;
46 BranchCluster *mother_bc = g_sim.Branch_center.findBrnachCluster(mother_cid);
47 if (!mother_bc) break;
48 int mother_bp = 0;
49 for (int i = 0; i < static_cast<int>(mother_bc->all_branch.size()); ++i) {
50 if (mother_bc->all_branch[i]->getIDOfTheChildBranchCluster() == cur_cid) {
51 mother_bp = i; break;
52 }
53 }
54 cur_bp = mother_bp;
55 cur_cid = mother_cid;
56 }
57 for (int i = static_cast<int>(parts.size()) - 1; i >= 0; --i) {
58 if (!ancestry.empty()) ancestry += ">";
59 ancestry += parts[i];
60 }
61 }
62 std::string events;
63 for (auto& [t, n] : g_sim.current_seq_events) {
64 if (!events.empty()) events += ",";
65 char buf[64];
66 snprintf(buf, sizeof(buf), "%.4f:%d", t, n);
67 events += buf;
68 }
69 if (events.empty()) events = "none";
70 fprintf(stderr, "[SEQ-FP] rank=%d ancestry=%s events=%s term=%.4f restart_cid=%d restart_bn=%d\n",
71 rank, ancestry.c_str(), events.c_str(), term_time, g_sim.seq_restart_cid, g_sim.seq_restart_branch_num);
72}
73
76 data.sequence_id = seq_id;
78 for (const auto& rec : g_sim.Aux_info.getRecords()) {
80 ev.time = rec->getTime();
81 ev.statement = rec->getStatement();
82 ev.pe_id = rec->getAssociatedBranchID();
83 data.events.push_back(std::move(ev));
84 }
85 }
87 auto* es = g_sim.Branch_center.end_state.back().get();
88 data.end_state.sequence_id = seq_id;
89 data.end_state.type_of_consequence = static_cast<int>(es->getTypeOfConsequence());
90 data.end_state.mother_branch_id = es->getIDOfTheMotherBranch();
91 data.end_state.consequence_description = es->getConsequenceDescription();
92 data.end_state.time = es->getTimeAtWhichThisEventHappen();
93 data.end_state.probability = es->getProbability();
94 }
95 return data;
96}
97
98static void save_sequence_outputs(int seq_id) {
99 // Set the ES ID so output files use the global sequence ID
100 if (!g_sim.Branch_center.end_state.empty()) {
101 g_sim.Branch_center.end_state.back()->setIDOfThisEndState(seq_id);
102 g_sim.Branch_center.end_state.back()->saveInfoToFile();
103 }
105
108 for (auto& bc : g_sim.Branch_center.branch_cluster)
109 bc->savePENodeToFile();
110}
111
112
113// Path of the run-wide restart base. Lives in the project directory, so it
114// survives a checkpoint/resume in the same project (which the config
115// fingerprint already requires to be unchanged).
116static std::string restart_base_path() {
117 return g_sim.SScheduler.getProjectDirectory() + "/restart_base.bin";
118}
119
121 if (!base_cache_.empty()) return true;
122 std::vector<char> buf;
123 if (read_file_verified(restart_base_path(), buf) && !buf.empty()) {
124 base_cache_ = std::move(buf);
125 return true;
126 }
127 return false;
128}
129
130void Client::restore_buffer(std::vector<char> raw) {
132 // Self-contained (base included). Remember the base for later transfers
133 // that omit it; this is how a rank warms its cache without extra I/O.
135 } else {
136 // Base-less transfer: splice the run-wide base back on. RELAP aborts in
137 // fabend if the first restart record it sees is not a complete one.
138 if (!ensure_base_cache()) {
139 std::cerr << "FATAL: rank " << rank << " received a base-less restart buffer "
140 << "and has no cached base (" << restart_base_path() << " missing) — "
141 << "restoring it would abend in RELAP. Aborting." << std::endl;
142 MPI_Abort(MPI_COMM_WORLD, 1);
143 }
144 auto spliced = adsidac::splice_base(base_cache_, raw);
145 if (spliced.empty()) {
146 std::cerr << "FATAL: rank " << rank << " could not splice the restart base "
147 << "onto a transfer — aborting." << std::endl;
148 MPI_Abort(MPI_COMM_WORLD, 1);
149 }
150 raw = std::move(spliced);
151 }
152 solver_->restore(raw);
153}
154
155void Client::restore_relap_state(const std::string& file_path, bool clear_buf) {
156 if (!restart_info_->relap_restart_buf.empty()) {
157 auto& buf = restart_info_->relap_restart_buf;
159 restore_buffer(lz4_decompress(buf.data(), (int)buf.size()));
160 } else {
161 restore_buffer(buf);
162 }
163 if (clear_buf) {
164 buf.clear();
165 buf.shrink_to_fit();
166 }
167 } else {
168 std::vector<char> buf;
169 if (!read_file_verified(file_path, buf) || buf.empty()) {
170 // Restoring from a missing/short restart file means simulating a
171 // different state than the branch point, so abort the job.
172 std::cerr << "FATAL: cannot restore RELAP5 state from " << file_path
173 << " — aborting." << std::endl;
174 MPI_Abort(MPI_COMM_WORLD, 1);
175 }
177 restore_buffer(lz4_decompress(buf.data(), static_cast<int>(buf.size())));
178 } else {
179 restore_buffer(std::move(buf));
180 }
181 }
182}
183
185 log_seq_fingerprint_mpi(rank, solver_->currentTime());
186 save_sequence_outputs(seq_id);
187 if (viz_outputs_any()) {
188 auto od = extract_sequence_outputs(seq_id);
189 PU::send_object(od, 0, PG::server_output_data, MPI_COMM_WORLD);
190 }
191 SequenceReport report{};
192 report.sequence_id = seq_id;
193 report.parent_sequence_id = parent_seq_id_;
194 report.branch_time = branch_time_;
195 report.end_time = solver_->currentTime();
196 report.num_branches_generated = branches_this_seq_;
197 report.worker_rank = rank;
198 MPI_Send(&report, sizeof(SequenceReport), MPI_BYTE, 0, PG::server_done_with_report, MPI_COMM_WORLD);
199 PU::print_message(rank, "Sent the finished signal to Server", PG::log);
200}
201
203{
204 this->rank = rank;
206
207 // Create solver based on --solver flag (same logic as serial path)
208 if (!g_sim.config.solver_url.empty()) {
209 solver_ = std::make_unique<RestApiSolver>(g_sim.config.solver_url);
210 } else {
211 solver_ = std::make_unique<RelapSolver>();
212 }
213
215
216 // Redirect output directories to per-rank local paths (cwd is rank_N/)
217 std::string local_out = fs::current_path().string() + "/Output/";
219 g_sim.SScheduler.setESDirectory(local_out + "ES/");
220 g_sim.SScheduler.setPEDirectory(local_out + "PE/");
221 g_sim.SScheduler.setKPSDirectory(local_out + "KPS/");
222
226 // The shared procedures master must exist before the first snapshot is
227 // deserialized (peer task, checkpoint gather, or resume): overlay loads
228 // seed definitions from it. Idempotent; onFirstInit reaches the same load
229 // again on the engine's normal path.
230 g_sim.Indicator.readInfoFromFile(); // procedures parser needs the panel maps
231 // Procedures BEFORE the crew, mirroring AdsidacCallback::onFirstInit.
232 // The crew's reason machine resolves Response_Procedure_Step pointers
233 // through All_procedure.procedure_map during its own init, so the map
234 // must be populated first; otherwise the lookup fails with
235 // "Could not find this procedure step" and the RM node gets no pointer.
236 // Procedures init does not depend on the crew knowledge bases, only on the
237 // panel maps read just above.
247}
248
250{
251 // Ignore SIGINT on client ranks — server handles the interrupt menu
252 signal(SIGINT, SIG_IGN);
253 // Same for SIGUSR1, the HPC wall-time checkpoint request. Only rank 0
254 // installs a handler (Server.cpp), but mpirun forwards the signal to EVERY
255 // rank, and the default action would terminate a client (exit 138 =
256 // 128+SIGUSR1). The server orchestrates the checkpoint and drains the
257 // clients over MPI; the clients only need to survive the signal.
258 signal(SIGUSR1, SIG_IGN);
259 while (true)
260 {
261 PU::print_message(rank, "Checking for signals", PG::log);
262 // Blocking MPI_Probe busy-polls in OpenMPI, so an idle client would
263 // occupy a full core and take cycles from ranks running RELAP5.
264 // Mirror the server's Iprobe + usleep idle loop (1 ms poll).
265 int msg_ready = 0;
266 MPI_Iprobe(MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, &msg_ready, &status_client);
267 while (!msg_ready) {
268 usleep(1000);
269 MPI_Iprobe(MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, &msg_ready, &status_client);
270 }
272 {
273 MPI_Recv(nullptr, 0, MPI_INT, status_client.MPI_SOURCE, PG::client_simulate_this, MPI_COMM_WORLD, &status_client);
274 PU::print_message(rank, "Received a new sequence to simulate", PG::log);
275 parent_seq_id_ = -1;
276 branch_time_ = 0.0;
282 }
283 else if (status_client.MPI_TAG == PG::client_restart_run)
284 {
285 fs::remove("out.o");
286
287 restart_info_ = std::make_shared<RestartNodeInfo>();
288 PU::receive_object(*restart_info_, 0, PG::client_restart_run, MPI_COMM_WORLD);
289 // Latch the resumed run's branch-ID epoch (rides in every
290 // RestartNodeInfo; 0 for fresh runs).
291 if (restart_info_->id_epoch_base > g_sim.id_epoch_base)
292 g_sim.id_epoch_base = restart_info_->id_epoch_base;
293
294 branch_center_ = std::make_shared<BranchRecord>();
295 PU::receive_object(*branch_center_, 0, PG::client_restart_run, MPI_COMM_WORLD);
296
297 int path_len;
298 MPI_Recv(&path_len, 1, MPI_INT, 0, PG::client_restart_run,
299 MPI_COMM_WORLD, MPI_STATUS_IGNORE);
300 std::vector<char> path_buf(path_len + 1, '\0');
301 MPI_Recv(path_buf.data(), path_len, MPI_CHAR, 0, PG::client_restart_run,
302 MPI_COMM_WORLD, MPI_STATUS_IGNORE);
303 std::string server_file_path(path_buf.data());
304
305 int seq_id;
306 MPI_Recv(&seq_id, 1, MPI_INT, 0, PG::client_restart_run, MPI_COMM_WORLD,
307 MPI_STATUS_IGNORE);
308
309 restore_relap_state(server_file_path, /*clear_buf=*/true);
310
311 int branch_index;
312 MPI_Recv(&branch_index, 1, MPI_INT, 0, PG::client_restart_run, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
313
315 solver_->setInputDeck("indeck.i");
316 solver_->setNewRestart(false);
317
319 complete_sequence(seq_id);
320 }
321
322 else if (status_client.MPI_TAG == PG::client_peer_restart)
323 {
324 int source_rank = status_client.MPI_SOURCE;
325 fs::remove("out.o");
326
327 restart_info_ = std::make_shared<RestartNodeInfo>();
328 PU::receive_object(*restart_info_, source_rank, PG::client_peer_restart, MPI_COMM_WORLD);
329 // Latch the branch-ID epoch from the donor's snapshot.
330 if (restart_info_->id_epoch_base > g_sim.id_epoch_base)
331 g_sim.id_epoch_base = restart_info_->id_epoch_base;
332 branch_center_ = std::make_shared<BranchRecord>();
333 PU::receive_object(*branch_center_, source_rank, PG::client_peer_restart, MPI_COMM_WORLD);
334
335 int path_len;
336 MPI_Recv(&path_len, 1, MPI_INT, source_rank, PG::client_peer_restart,
337 MPI_COMM_WORLD, MPI_STATUS_IGNORE);
338 std::vector<char> path_buf(path_len + 1, '\0');
339 MPI_Recv(path_buf.data(), path_len, MPI_CHAR, source_rank, PG::client_peer_restart,
340 MPI_COMM_WORLD, MPI_STATUS_IGNORE);
341 std::string file_path(path_buf.data());
342
343 int seq_id;
344 MPI_Recv(&seq_id, 1, MPI_INT, source_rank, PG::client_peer_restart,
345 MPI_COMM_WORLD, MPI_STATUS_IGNORE);
346
347 restore_relap_state(file_path, /*clear_buf=*/true);
348
349 int branch_index;
350 MPI_Recv(&branch_index, 1, MPI_INT, source_rank, PG::client_peer_restart,
351 MPI_COMM_WORLD, MPI_STATUS_IGNORE);
352 MPI_Recv(&parent_seq_id_, 1, MPI_INT, source_rank, PG::client_peer_restart,
353 MPI_COMM_WORLD, MPI_STATUS_IGNORE);
354
355 PU::print_message(rank, "[PEER] Received from C[" + std::to_string(source_rank)
356 + "] seq=" + std::to_string(seq_id) + " bi=" + std::to_string(branch_index), PG::log);
357
358 // Extract branch_time before parallel_restartforanewsequence_ moves clusters
359 int cid = restart_info_->getCorrespondingBranchClusterID();
360 BranchCluster* bc = branch_center_->findBrnachCluster(cid);
363
365 solver_->setInputDeck("indeck.i");
366 solver_->setNewRestart(false);
367
369 complete_sequence(seq_id);
370 }
371
372 else if (status_client.MPI_TAG == PG::client_send_to_peer)
373 {
374 int local_id, target_rank, branch_index, seq_id, parent_seq_id;
375 MPI_Recv(&local_id, 1, MPI_INT, 0, PG::client_send_to_peer, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
376 MPI_Recv(&target_rank, 1, MPI_INT, 0, PG::client_send_to_peer, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
377 MPI_Recv(&branch_index, 1, MPI_INT, 0, PG::client_send_to_peer, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
378 MPI_Recv(&seq_id, 1, MPI_INT, 0, PG::client_send_to_peer, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
379 MPI_Recv(&parent_seq_id, 1, MPI_INT, 0, PG::client_send_to_peer, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
380
381 auto it = held_data_.find(local_id);
382 if (it == held_data_.end()) {
383 // The server already marked the receiver busy for this transfer;
384 // continuing without sending would leave it stranded and the
385 // job would hang at termination. This is an invariant
386 // violation, so abort the job.
387 std::cerr << "FATAL: rank " << rank << " was asked to forward held branch data "
388 << "for lid=" << local_id << " but holds none — aborting." << std::endl;
389 MPI_Abort(MPI_COMM_WORLD, 1);
390 } else {
391 auto& held = it->second;
392 PU::print_message(rank, "[PEER] Deferred send lid=" + std::to_string(local_id)
393 + " -> C[" + std::to_string(target_rank) + "] bi="
394 + std::to_string(branch_index) + " seq=" + std::to_string(seq_id), PG::log);
395
396 PU::send_object(*held.rinfo, target_rank, PG::client_peer_restart, MPI_COMM_WORLD);
397 PU::send_object(*held.branch_record, target_rank, PG::client_peer_restart, MPI_COMM_WORLD);
398 int path_len = static_cast<int>(held.file_path.size());
399 MPI_Send(&path_len, 1, MPI_INT, target_rank, PG::client_peer_restart, MPI_COMM_WORLD);
400 MPI_Send(held.file_path.c_str(), path_len, MPI_CHAR,
401 target_rank, PG::client_peer_restart, MPI_COMM_WORLD);
402 MPI_Send(&seq_id, 1, MPI_INT, target_rank, PG::client_peer_restart, MPI_COMM_WORLD);
403 MPI_Send(&branch_index, 1, MPI_INT, target_rank, PG::client_peer_restart, MPI_COMM_WORLD);
404 MPI_Send(&parent_seq_id, 1, MPI_INT, target_rank, PG::client_peer_restart, MPI_COMM_WORLD);
405
406 held.ref_count--;
407 if (held.ref_count <= 0) held_data_.erase(it);
408 }
409 MPI_Send(nullptr, 0, MPI_INT, 0, PG::server_send_complete, MPI_COMM_WORLD);
410 }
411
412 else if (status_client.MPI_TAG == PG::client_run_held)
413 {
414 int local_id, branch_index, seq_id;
415 MPI_Recv(&local_id, 1, MPI_INT, 0, PG::client_run_held, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
416 MPI_Recv(&branch_index, 1, MPI_INT, 0, PG::client_run_held, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
417 MPI_Recv(&seq_id, 1, MPI_INT, 0, PG::client_run_held, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
418 MPI_Recv(&parent_seq_id_, 1, MPI_INT, 0, PG::client_run_held, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
419
420 auto it = held_data_.find(local_id);
421 if (it == held_data_.end()) {
422 PU::print_message(rank, "[PEER] ERROR: no held data for lid=" + std::to_string(local_id), PG::log);
423 SequenceReport err_report{};
424 err_report.sequence_id = seq_id;
425 err_report.parent_sequence_id = parent_seq_id_;
426 err_report.worker_rank = rank;
427 MPI_Send(&err_report, sizeof(SequenceReport), MPI_BYTE, 0, PG::server_done_with_report, MPI_COMM_WORLD);
428 continue;
429 }
430
431 PU::print_message(rank, "[PEER] Self-run lid=" + std::to_string(local_id)
432 + " bi=" + std::to_string(branch_index) + " seq=" + std::to_string(seq_id), PG::log);
433
434 fs::remove("out.o");
435 auto& held = it->second;
436 restart_info_ = held.rinfo;
437
438 if (held.ref_count <= 1) {
439 branch_center_ = std::move(held.branch_record);
440 } else {
441 branch_center_ = std::make_shared<BranchRecord>();
442 branch_center_->copyBranchRecord(*held.branch_record);
443 }
444
445 restore_relap_state(held.file_path, /*clear_buf=*/held.ref_count <= 1);
446
447 // Extract branch_time before parallel_restartforanewsequence_ moves data
448 int cid = restart_info_->getCorrespondingBranchClusterID();
449 BranchCluster* bc = branch_center_->findBrnachCluster(cid);
452
453 held.ref_count--;
454 if (held.ref_count <= 0) held_data_.erase(it);
455
457 solver_->setInputDeck("indeck.i");
458 solver_->setNewRestart(false);
459
461 complete_sequence(seq_id);
462 }
463
464 else if (status_client.MPI_TAG == PG::client_checkpoint)
465 {
466 MPI_Recv(nullptr, 0, MPI_INT, 0, PG::client_checkpoint, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
467 // Ensure all held rinfo have embedded RELAP5 restart data.
468 // Adaptive memory mode may have written restart data to file instead
469 // of keeping it in relap_restart_buf; read it back before sending.
470 std::vector<int> inflated_lids;
471 for (auto& [lid, held] : held_data_) {
472 if (held.rinfo->relap_restart_buf.empty() && !held.file_path.empty()) {
473 if (!read_file_verified(held.file_path, held.rinfo->relap_restart_buf)) {
474 // A checkpoint with a missing/short blob is corrupt by
475 // construction and would fail only at resume, so abort now.
476 std::cerr << "FATAL: cannot embed restart data for lid=" << lid
477 << " from " << held.file_path << " — aborting." << std::endl;
478 MPI_Abort(MPI_COMM_WORLD, 1);
479 }
480 inflated_lids.push_back(lid);
481 PU::print_message(rank, "[CHECKPOINT] Read "
482 + std::to_string(held.rinfo->relap_restart_buf.size())
483 + " bytes from " + held.file_path, PG::log);
484 }
485 }
486 // Send held_data_ to server for checkpoint
487 int count = static_cast<int>(held_data_.size());
488 MPI_Send(&count, 1, MPI_INT, 0, PG::client_checkpoint, MPI_COMM_WORLD);
489 for (auto& [lid, held] : held_data_) {
490 MPI_Send(&lid, 1, MPI_INT, 0, PG::client_checkpoint, MPI_COMM_WORLD);
491 PU::send_object(*held.rinfo, 0, PG::client_checkpoint, MPI_COMM_WORLD);
492 PU::send_object(*held.branch_record, 0, PG::client_checkpoint, MPI_COMM_WORLD);
493 }
494 // Buffers inflated from file above are needed only for the
495 // checkpoint payload. The file stays on disk for any later peer
496 // transfer or self-run, so drop the in-memory copy.
497 for (int lid : inflated_lids) {
498 auto& buf = held_data_[lid].rinfo->relap_restart_buf;
499 buf.clear();
500 buf.shrink_to_fit();
501 }
502 PU::print_message(rank, "[CHECKPOINT] Sent " + std::to_string(count) + " held entries to server", PG::log);
503 // Continue Work() loop — will get next task or shutdown
504 }
505
506 else if (status_client.MPI_TAG == PG::client_shutdown)
507 {
508 MPI_Recv(nullptr, 0, MPI_INT, status_client.MPI_SOURCE, PG::client_shutdown, MPI_COMM_WORLD, &status_client);
509 PU::print_message(rank, "Shutting down", PG::log);
511 // File-mode holds write restart_<lid>_cid<N>.r transfer files into
512 // the rank dir. One file serves every consumer of its branch point
513 // and receivers read it asynchronously with no ack, so no mid-run
514 // owner can delete it safely. Shutdown means the server has
515 // confirmed global completion, so every read is done and the
516 // files are swept here (one restart image per branch point).
517 {
518 std::error_code ec;
519 for (auto& entry : fs::directory_iterator(".", ec)) {
520 auto fname = entry.path().filename().string();
521 if (fname.rfind("restart_", 0) == 0 &&
522 fname.size() > 2 && fname.compare(fname.size() - 2, 2, ".r") == 0)
523 fs::remove(entry.path(), ec);
524 }
525 }
526 if (g_sim.config.merge_mode == 0) {
527 // Worker self-merge, then rendezvous with the server's merge
528 // barrier. Every rank parses the same argv, so merge_mode is
529 // globally consistent and the barrier count matches.
531 MPI_Barrier(MPI_COMM_WORLD);
532 }
533 return 0;
534 }
535 // break;
536 }
537 // return 0;
538}
539
541{
542 // Worker self-merge (--merge=now): every rank moves its own
543 // unique-ID outputs into the shared Output/ tree and stages its append-file
544 // sources under parts/ for the server's ordered concatenation. ES/KPS names
545 // key on the server-minted global sequence id, so cross-rank collisions are
546 // impossible; per-file PE nodes are left for rank 0, whose ordered renames
547 // preserve the highest-rank-wins semantics for lineage re-saves.
548 std::error_code ec;
549 const fs::path shared_out = fs::path(g_sim.SScheduler.getProjectDirectory()) / "Output";
550 const fs::path local_out = "Output"; // cwd = rank_N
551 if (!fs::exists(local_out, ec)) return;
552 static const std::vector<std::string> append_files = {
553 "Scenario info.txt", "Procedure data.txt", "Scan data.txt",
555 const std::string suffix = ".rank_" + std::to_string(rank);
556 std::unordered_set<std::string> made_dirs;
557 auto move_file = [&](const fs::path& src, const fs::path& dest) {
558 if (made_dirs.insert(dest.parent_path().string()).second)
559 fs::create_directories(dest.parent_path(), ec);
560 std::error_code mec;
561 fs::rename(src, dest, mec);
562 if (mec) {
563 mec.clear();
564 fs::copy(src, dest, fs::copy_options::overwrite_existing, mec);
565 if (!mec) fs::remove(src, mec);
566 }
567 };
568 // Append sources are collected first and staged after the walk: compacting
569 // the PE aggregate creates and renames a temp file, which must not happen
570 // under the feet of the directory iterator.
571 std::vector<std::pair<fs::path, fs::path>> append_moves;
572 for (auto& entry : fs::recursive_directory_iterator(local_out, ec)) {
573 if (ec) break;
574 if (!entry.is_regular_file()) continue;
575 const fs::path rel = fs::relative(entry.path(), local_out);
576 const std::string fname = entry.path().filename().string();
577 bool is_append = false;
578 for (auto& af : append_files)
579 if (fname == af) { is_append = true; break; }
580 if (is_append)
581 append_moves.emplace_back(entry.path(),
582 shared_out / rel.parent_path() / "parts" / (fname + suffix));
583 else if (rel.begin()->string() == "PE")
584 continue; // per-file PE winner selection stays with rank 0
585 else
586 move_file(entry.path(), shared_out / rel);
587 }
588 for (auto& [src, dest] : append_moves) {
589 // Rank-local compaction: last-wins over this rank's own re-saves cuts
590 // the finalize's read volume ~4.5x without changing what collect()
591 // resolves (see PEOutput.h).
592 if (src.filename() == adsidac::pe::aggregate_basename())
594 move_file(src, dest);
595 }
596 // err messages.txt lives in the rank cwd, outside Output/, so the walk
597 // above does not see it; stage it with the other parts.
598 if (fs::exists("err messages.txt", ec))
599 move_file("err messages.txt", shared_out / "ES" / "parts" / ("err messages.txt" + suffix));
600}
601
603{
604 // 1) Find target cluster in received branch_center_ using CID from restart_info_
605 int cid = restart_info_->getCorrespondingBranchClusterID();
606 BranchCluster *bc = branch_center_->findBrnachCluster(cid);
607 long n_count = bc->getNCountForRestartRELAP5();
608 PU::print_message(rank, "CID=" + std::to_string(cid)
609 + " branch_index=" + std::to_string(branch_index)
610 + " n_count=" + std::to_string(n_count), PG::log);
611
612 // 2) Generate indeck.i
613 string content = "100 restart transnt\n101 run\n103 ";
614 content += to_string(n_count);
615 content += "\n.";
616 ofstream fout("indeck.i", ios::out);
617 fout << content;
618 fout.close();
619
620 // 3) Transfer branch_center_ to GLOBAL Branch_center
621 // Delete all existing clusters in global
623
624 // Move clusters to global (branch_center_ is consumed after this)
625 g_sim.Branch_center.branch_cluster = std::move(branch_center_->branch_cluster);
626
627 // Use rank-based offset for branch IDs: each rank gets its own ID space
628 // (rank * 100000) within the current ID epoch. Fresh runs have epoch 0;
629 // resumed runs get an epoch above every phase-1 identity so new IDs
630 // can never collide with identities preserved inside merged PE outputs.
631 int rank_base = g_sim.id_epoch_base + rank * 100000;
635 branch_center_->getIDOfRecentGeneratedES());
637 branch_center_->getIDOfRecentGeneratedBranchCluster());
639 branch_center_->getIDOfMotherCluster());
641 branch_center_->getIDOfMotherBranch());
643 branch_center_->getRecentUnconditionalBranchProbability());
644
645 // 4) Set up for initiationforrestart_()
646 // Re-find target cluster in global (pointers were moved)
648
649 // Set target's Current_branch_num = branch_index - 1
650 // (initiationforrestart_() will advance it to branch_index)
651 target->setCurrentBranchNum(branch_index - 1);
652
653 // Branch ID assignment and mother linkage are handled by initiationforrestart_()
654 // (called via interfc_() when fresh_restart is true). No pre-assignment needed here.
655
656 // Mark ALL other clusters as fully consumed
657 for (auto &gc : g_sim.Branch_center.branch_cluster) {
658 if (gc.get() != target) {
659 gc->setCurrentBranchNum(static_cast<int>(gc->all_branch.size()) - 1);
660 }
661 }
662
663 // Share RestartNodeInfo pointer directly — no copy needed (immutable after creation)
664 g_sim.restart_info.clear();
666
667 // 5) Set global fresh_restart = true
668 g_sim.fresh_restart = true;
669
670 // 6) Clear KPS and Aux_info
673}
674
676{
677 solver_->setThermoCacheMode(g_sim.config.thermo_cache_mode);
678 // Always use membuf for RELAP5 I/O — adaptive decision only affects
679 // whether the extracted buffer is kept in RAM or dumped to file.
680 solver_->setMembufMode(1);
681 solver_->runSequence(callback_);
682}
683
684void Client::hold_branch_data(int cid, std::shared_ptr<RestartNodeInfo> rinfo,
685 BranchRecord& br, const std::string& path, int refs)
686{
687 // Deep-copy BranchRecord via copyBranchRecord (global may change before consumption)
688 auto br_copy = std::make_shared<BranchRecord>();
689 br_copy->copyBranchRecord(br);
690 held_data_[cid] = {rinfo, br_copy, path, refs};
691 PU::print_message(rank, "[PEER] Holding data for CID=" + std::to_string(cid)
692 + " refs=" + std::to_string(refs)
693 + " held_total=" + std::to_string(held_data_.size()), PG::log);
694}
bool read_file_verified(const std::string &path, std::vector< char > &out)
Read an entire binary file into out, verifying the byte count.
Definition: CheckedIO.h:64
static std::string restart_base_path()
Definition: Client.cpp:116
static void save_sequence_outputs(int seq_id)
Definition: Client.cpp:98
static SequenceOutputData extract_sequence_outputs(int seq_id)
Definition: Client.cpp:74
static void log_seq_fingerprint_mpi(int rank, double term_time)
Definition: Client.cpp:23
MPI client (rank > 0): executes simulation sequences.
bool viz_outputs_any()
True if any viz output streaming is enabled.
std::vector< char > lz4_decompress(const char *data, int size)
LZ4-decompress a buffer that has a 4-byte original-size header.
void cleanup_fort_temps()
Clean up Fortran temporary files (fort*) left by RELAP5.
SimulationContext g_sim
Global simulation context: the single instance, defined in SimulationContext.cpp.
const std::vector< std::unique_ptr< AnalysisInfoContainerUnit > > & getRecords() const
int getNCountForRestartRELAP5()
int getIDOfMotherCluster()
void setCurrentBranchNum(int)
Sets the index (within all_branch) of the most recently started alternative. Alternatives with a larg...
std::vector< std::unique_ptr< BranchProbability > > all_branch
The alternatives of this branching point; index 0 is the one the generating sequence continues on.
void setIDOfRecentGeneratedES(int)
void setIDOfMotherBranch(int)
void setIDOfRecentGeneratedBranchCluster(int)
void setIDOfRecentGeneratedBranch(int)
std::vector< std::unique_ptr< BranchEndState > > end_state
End states of the finished sequences held by this record.
Definition: BranchRecord.h:286
int getIDOfRecentGeneratedBranch()
void setRecentUnconditionalBranchProbability(double)
BranchCluster * findBrnachCluster(int)
Finds a cluster by ID, searching from the most recent cluster.
void initiationForSimulation()
Loads the three branch definition tables: procedure action time branches, mental procedure activation...
std::vector< std::unique_ptr< BranchCluster > > branch_cluster
All branching points generated so far, in creation order.
Definition: BranchRecord.h:284
void setIDOfMotherCluster(int)
void execute_relap()
Run one sequence on this client's solver: sets the thermo cache mode from the configuration,...
Definition: Client.cpp:675
void hold_branch_data(int cid, std::shared_ptr< RestartNodeInfo > rinfo, BranchRecord &br, const std::string &path, int refs)
Store branch data for deferred peer transfer or a later self-run.
Definition: Client.cpp:684
AdsidacCallback callback_
Per-rank callback instance.
Definition: Client.h:130
int rank
MPI rank of this client.
Definition: Client.h:127
int Work()
Main client work loop: receive tasks, execute, report back.
Definition: Client.cpp:249
void restore_buffer(std::vector< char > raw)
Splice the base on if raw lacks it (MPI_Abort if impossible), then solver_->restore()
Definition: Client.cpp:130
void self_merge_outputs()
Definition: Client.cpp:540
Client(int rank)
Create the solver, initialize the engine modules and redirect the scheduler's output directories to <...
Definition: Client.cpp:202
MPI_Status status_client
Status of the message being handled by Work()
Definition: Client.h:87
int parent_seq_id_
Parent sequence ID for current run (received from server)
Definition: Client.h:83
double branch_time_
Sim time when parent branched to create this sequence.
Definition: Client.h:84
bool ensure_base_cache()
Load <project>/restart_base.bin if not already resident; false if unavailable.
Definition: Client.cpp:120
int branches_this_seq_
Count of branching events during current sequence.
Definition: Client.h:85
std::unordered_map< int, HeldBranchData > held_data_
Rank-local branch-point id -> data held for deferred peer sends and self-runs.
Definition: Client.h:134
void parallel_restartforanewsequence_(int branch_index)
Install the received branch tree as the global one and prepare the restart of one branch alternative.
Definition: Client.cpp:602
void restore_relap_state(const std::string &file_path, bool clear_buf)
Restore the solver state from restart_info_'s buffer or a restart file.
Definition: Client.cpp:155
std::shared_ptr< RestartNodeInfo > restart_info_
Snapshot of the task being started (immutable after creation)
Definition: Client.h:131
std::unique_ptr< ThermalHydraulicSolver > solver_
Per-rank solver instance.
Definition: Client.h:129
void complete_sequence(int seq_id)
Post-sequence completion: write the sequence's ES, KPS, analysis and PE node files,...
Definition: Client.cpp:184
int local_branch_counter_
Per-client branch-point counter (see getNextLocalBranchID)
Definition: Client.h:128
std::shared_ptr< BranchRecord > branch_center_
Branch record of the task being started (consumed by parallel_restartforanewsequence_)
Definition: Client.h:132
std::vector< char > base_cache_
[header][complete restart base], cached once per rank.
Definition: Client.h:142
void readInfoFromFile()
void initiationForSimulation()
KB knowledge_base
Definition: Operator.h:127
EventSymptomMatrix diagnosis_matrix
Definition: Operator.h:129
void initiationForSimulation()
Definition: Operator.cpp:79
void initiationForSimulation()
Definition: Procedures.cpp:42
void setKPSDirectory(std::string)
Definition: Scheduler.cpp:661
void setESDirectory(std::string)
Definition: Scheduler.cpp:655
std::string getProjectDirectory()
Definition: Scheduler.cpp:595
std::string getESDirectory()
Definition: Scheduler.cpp:601
void setPEDirectory(std::string)
Definition: Scheduler.cpp:658
void initiationForSimulation(ConsoleMode iMode=ConsoleMode::Console)
Prepares the RELAP5 bridge and the working directory for a run. Call it after parseOptions().
Definition: Scheduler.cpp:364
void setOutputDirectory(std::string)
Definition: Scheduler.cpp:652
std::string getKPSDirectory()
Definition: Scheduler.cpp:607
void initiationForSimulation()
double getTimeAtWhichThisEventHappen() const
Definition: Timp.cpp:26
bool log
Enable/disable rank-specific logging.
@ client_send_to_peer
Server -> Client A: send held data to another client.
@ client_run_held
Server -> Client A: run task using own held data.
@ client_shutdown
Server -> Client: no more work, shut down.
@ server_done_with_report
Client -> Server: sequence completed with SequenceReport payload.
@ server_send_complete
Client -> Server: data send finished, client is free again.
@ client_simulate_this
Server -> Client: run the initial (first) sequence.
@ client_restart_run
Server -> Client: run a pending task of a resumed checkpoint with server-provided data.
@ client_peer_restart
Client A -> Client B: direct peer-to-peer restart data.
@ client_checkpoint
Server -> Client: send held_data for checkpoint gather.
@ server_output_data
Client -> Server: SequenceOutputData (before report)
int rank
MPI rank of this process.
void move_file(const fs::path &src, const fs::path &dest, std::unordered_set< std::string > &made_dirs)
Move one file: rename (replacing an existing dest), with copy+remove fallback for EXDEV or exotic fil...
Definition: OutputMerge.h:100
const char * aggregate_basename()
Basename of the aggregate file ("ZPENodes_agg.txt"). It is in the MPI merge's append-files list,...
Definition: PEOutput.h:59
std::pair< size_t, size_t > compact_aggregate(const fs::path &agg_path)
Rewrite one rank's aggregate file last-wins per id, sorted by id.
Definition: PEOutput.h:123
void set_shared_procedures(const Procedures *master)
void set_shared_kb(int operator_type, const KB *master)
void set_shared_control_panel(const ControlPanel *master)
void set_shared_diagnosis_matrix(int operator_type, const EventSymptomMatrix *master)
std::vector< char > splice_base(const std::vector< char > &hdr_base, const std::vector< char > &body)
Put the cached base back in front of a base-less transfer.
Definition: RestartTrim.h:150
std::vector< char > extract_header_and_base(const std::vector< char > &buf)
[header][complete base], the run-wide constant that is cached per rank and published as <project>/res...
Definition: RestartTrim.h:113
bool has_complete_restart(const std::vector< char > &buf)
Does this buffer contain a complete restart dump (the base)?
Definition: RestartTrim.h:105
constexpr int ODM
Definition: SimConstants.h:145
constexpr int OAT
Definition: SimConstants.h:144
Per-sequence output payload sent from client to server via MPI.
std::vector< SequenceOutputEvent > events
SequenceOutputEndState end_state
std::string consequence_description
Lightweight PE event record (extracted from AnalysisInfoContainerUnit).
int pe_id
associated_branch_ID → PE_N
Report sent by client when a sequence completes (for DET visualization).
int merge_mode
Definition: SimConfig.h:38
int thermo_cache_mode
0 = reuse cached steam tables (default), 2 = force reload every sequence (–no-thermo-cache)....
Definition: SimConfig.h:19
bool compress_queue
–compress-queue (default off): LZ4-compress RELAP restart buffers held and sent between MPI ranks
Definition: SimConfig.h:22
bool viz_output_end_states
Stream end states to viz server (–viz-outputs=es or all)
Definition: SimConfig.h:35
bool viz_output_events
Stream PE events to viz server (–viz-outputs=events or all)
Definition: SimConfig.h:34
std::string solver_url
REST solver URL (empty = use RELAP5). –solver=mock gives "http://localhost:<--solver-port,...
Definition: SimConfig.h:27
int seq_restart_branch_num
Branch index within that cluster.
Scheduler SScheduler
Simulation scheduling, parses CLI options.
Procedures All_procedure
Emergency operating procedures.
OperatorAT Action_Taker
Crew action execution model.
KeyParameterHistory KPS
Time-series parameter recording.
int seq_restart_cid
CID of restart-origin cluster.
std::vector< std::pair< double, int > > current_seq_events
(branching_time, num_branches) pairs
ControlPanel Indicator
Control room panel state.
OperatorDM Decision_Maker
Crew decision-making model.
SystemModule Plant
Maps RELAP5 card numbers to C++ objects.
int id_epoch_base
Branch-ID epoch for resumed MPI runs; 0 = fresh run.
bool fresh_restart
One-shot flag for restart init.
AnalysisInfoContainer Aux_info
Analysis records.
BranchRecord Branch_center
Dynamic event tree state.
ConvertToString Translator
Parameter name/value conversion.
std::deque< std::shared_ptr< RestartNodeInfo > > restart_info
Restart checkpoints for FIFO.