← Documentation
ADS-IDAC Engine 5.0.0
ADSIDAC.cpp
Go to the documentation of this file.
1#include "ADSIDAC.h"
6
9#include "Global/CheckedIO.h"
10
11#include <algorithm>
12#include <fstream>
13#include <sstream>
14#include <memory>
15#include <cassert>
16#include <chrono>
17#include <csignal>
18#include "Global/PEOutput.h"
19#include "Global/OutputMerge.h"
20#include <iomanip>
21#include <thread>
22
23#ifndef _CONSOLE
24#define _CONSOLE
25#endif
26
27using namespace std;
28
29// ---- Solver and callback instances ----
30// solver is created dynamically based on --solver flag
31static std::unique_ptr<ThermalHydraulicSolver> solver_ptr;
32static ThermalHydraulicSolver* solver_raw = nullptr; // raw pointer for extern "C" trampolines
34
35// SIGUSR1 checkpoint request (for HPC wall-time limits)
36static volatile sig_atomic_t checkpoint_requested = 0;
37static void sigusr1_handler(int) { checkpoint_requested = 1; }
38
39// SIGINT (Ctrl+C) interactive interrupt
40static volatile sig_atomic_t sigint_received = 0;
41static void sigint_handler(int) { sigint_received = 1; }
42
43// Show interactive interrupt menu, return user choice (1=continue, 2=checkpoint+exit, 3=exit)
44static int show_interrupt_menu(int seq_completed) {
45 cout << "\n\n=== Simulation Interrupted (Ctrl+C) ===" << endl;
46 cout << " Sequences completed: " << seq_completed << endl;
47 cout << endl;
48 cout << " 1. Continue simulation" << endl;
49 cout << " 2. Save checkpoint and exit" << endl;
50 cout << " 3. Exit without saving" << endl;
51 cout << endl;
52 cout << "Choice [1]: " << flush;
53
54 string line;
55 if (!getline(cin, line) || line.empty()) return 1;
56 if (line[0] == '2') return 2;
57 if (line[0] == '3') return 3;
58 return 1;
59}
60
61// Build a DETCheckpoint from current serial state
62static DETCheckpoint build_serial_checkpoint(int seq_completed) {
63 DETCheckpoint ckpt;
64 ckpt.sequences_completed = seq_completed;
66
67 // Serial execution has no transfer queue: restart_buffers always hold raw
68 // membuf bytes (a resumed compressed-queue checkpoint is stripped at
69 // intake), so serial checkpoints are always raw-marked, whatever
70 // --compress-queue says. The flag records the encoding of the blobs, and
71 // a resume decodes them according to it.
72 ckpt.compress_queue = false;
74
75 // Serialize BranchRecord into blob (avoids copy of unique_ptr members)
77
78 // Collect restart states from restart_info deque (shared_ptr, no copy needed).
79 // Each source-sequence blob is stored ONCE (cid_source maps sibling CIDs
80 // to the shared source) and the live RestartNodeInfos are never mutated.
82 for (auto& ri : g_sim.restart_info) {
83 if (!ri) continue;
84 int cid = ri->getCorrespondingBranchClusterID();
85 if (ckpt.restart_states.find(cid) == ckpt.restart_states.end()) {
86 ckpt.restart_states[cid] = ri;
87 auto cs_it = g_sim.cid_to_source_sequence.find(cid);
88 if (cs_it != g_sim.cid_to_source_sequence.end()) {
89 int seq = cs_it->second;
90 ckpt.cid_source[cid] = seq;
91 if (ckpt.source_blobs.find(seq) == ckpt.source_blobs.end()) {
92 auto buf_it = g_sim.restart_buffers.find(seq);
93 if (buf_it != g_sim.restart_buffers.end()) {
94 ckpt.source_blobs[seq] = buf_it->second; // copy: the live run continues
95 } else {
96 // File-based restart: read from disk. A short/failed
97 // read would put a corrupt blob into the checkpoint
98 // that fails only at resume, so it is fatal here.
99 std::string path = restart_path("restart_seq_" + std::to_string(seq) + ".r");
100 if (!read_file_verified(path, ckpt.source_blobs[seq])) {
101 std::cerr << "FATAL: checkpoint would embed a corrupt restart blob for CID "
102 << cid << " — aborting." << std::endl;
103 std::exit(1);
104 }
105 }
106 }
107 } else if (!ri->relap_restart_buf.empty()) {
108 // Defensive: a rinfo carrying its blob inline (not the serial
109 // layout) still checkpoints correctly.
110 ckpt.cid_source[cid] = cid;
111 ckpt.source_blobs[cid] = ri->relap_restart_buf;
112 }
113 }
114 }
115
116 // Scan branch clusters for unexplored branches
117 for (auto& bc : g_sim.Branch_center.branch_cluster) {
118 int cid = bc->getIDOfThisBranchCluster();
119 int curr = bc->getCurrentBranchNum();
120 int total = static_cast<int>(bc->all_branch.size());
121 for (int bi = curr + 1; bi < total; ++bi) {
122 PendingBranch pb;
123 pb.cid = cid;
124 pb.branch_index = bi;
125 ckpt.pending_tasks.push_back(pb);
126 }
127 }
128
129 // Populate per-task restart states for MPI resume.
130 // branch_record_blobs and task_branch_blobs are left empty: MPI resume
131 // always uses branch_center_blob (the merged blob), so per-task copies
132 // would only duplicate it O(N) times in the checkpoint file.
133 for (auto& pt : ckpt.pending_tasks) {
134 auto ri_it = ckpt.restart_states.find(pt.cid);
135 ckpt.task_restart_states.push_back(ri_it != ckpt.restart_states.end() ? ri_it->second : nullptr);
136 }
137
138 return ckpt;
139}
140
141// Forward declarations for SEQ-FP diagnostic
142static std::string compute_ancestry_path();
143static void log_seq_fingerprint(double term_time);
144
145// --merge-only: consolidate the per-rank outputs of a deferred (or
146// interrupted) merge and exit. Runs serially, never through parallel_init
147// (Setup_Directories would wipe the rank dirs this mode exists to read) and
148// never constructing a Server (viz/occupancy/crew side effects).
149static int run_merge_only(int argc, char* argv[]) {
150 g_sim.SScheduler.parseOptions(argc, argv);
151 const std::string project = g_sim.SScheduler.getProjectDirectory();
152 const std::string pe_dir = (fs::path(project) / "Output" / "PE").string() + "/";
153 const auto ranks = adsidac::merge::discover_ranks(project);
154 const bool has_manifest = adsidac::merge::manifest_exists(project);
155 if (ranks.empty()) {
157 std::cout << "[MERGE-ONLY] Output/ is already finalized and no rank "
158 "directories remain; nothing to do" << std::endl;
159 return 0;
160 }
161 std::cerr << "ERROR: no rank_N directories under " << project
162 << "; nothing to merge" << std::endl;
163 return 1;
164 }
166 if (!adsidac::merge::ranks_hold_outputs(project, ranks)) {
167 std::cout << "[MERGE-ONLY] Output/ is already finalized and the rank "
168 "directories hold no outputs; nothing to do" << std::endl;
169 return 0;
170 }
171 std::cerr << "ERROR: Output/PE already carries a finalized run while rank "
172 "directories still hold outputs. Merging would corrupt the finalized "
173 "tree; move Output/ aside first" << std::endl;
174 return 1;
175 }
176 if (!has_manifest)
177 std::cout << "[MERGE-ONLY] no " << adsidac::merge::manifest_basename()
178 << " manifest: salvage mode (merging whatever the rank directories hold)"
179 << std::endl;
180 const bool aggregate = adsidac::merge::manifest_aggregate(
182 int threads = g_sim.config.merge_threads;
183 if (threads <= 0)
184 threads = std::min(8u, std::max(1u, std::thread::hardware_concurrency()));
185 const auto t0 = std::chrono::steady_clock::now();
186 const auto [seqs, pe_count] = adsidac::merge::merge_project(project, aggregate, threads);
187 const double secs = std::chrono::duration<double>(
188 std::chrono::steady_clock::now() - t0).count();
189 std::cout << "Merged " << seqs << " sequences, " << pe_count << " PE nodes into "
190 << (fs::path(project) / "Output").string() << " in " << std::fixed
191 << std::setprecision(1) << secs << " s (merge-only, " << ranks.size()
192 << " rank dirs, " << threads << " threads)" << std::endl;
193 return 0;
194}
195
196int main(int argc, char* argv[]){
197
198 // Handle --help/--version/--info before MPI_Init so all ranks exit cleanly
199 for (int i = 1; i < argc; ++i) {
200 std::string arg(argv[i]);
201 if (arg == "--help" || arg == "-h" || arg == "--version" || arg == "-v" || arg == "--info" || arg == "-i") {
202 g_sim.SScheduler.parseOptions(argc, argv); // prints and calls exit(0)
203 }
204 }
205
206 // --merge-only is caught before the MPI branch: under mpirun it would run
207 // Setup_Directories and wipe the very rank directories it must read.
208 for (int i = 1; i < argc; ++i) {
209 if (std::string(argv[i]) == "--merge-only") {
210 if (std::getenv("OMPI_COMM_WORLD_SIZE") || std::getenv("PMI_SIZE")) {
211 std::cerr << "ERROR: --merge-only runs serially; launch it without mpirun\n";
212 return 1;
213 }
214 return run_merge_only(argc, argv);
215 }
216 }
217
218#ifdef ENABLE_MPI
219 parallelexec parallel;
220 g_sim.config.is_mpi_environment= (std::getenv("OMPI_COMM_WORLD_SIZE") != NULL) ||
221 (std::getenv("PMI_SIZE") != NULL);
222
224 MPI_Init(&argc, &argv);
225 parallel.parallel_init(&argc, &argv);
226 MPI_Finalize();
227 ParallelUtilities::print_message(PG::rank, "Parallel Execution finished...", true);
228 _exit(0); // Skip global destructors — client ranks have inconsistent state after moves
229 }
230#endif
231
232 // set system mode
233 r5pars_.tsimmode = static_cast<int>(ConsoleMode::Console);
234 int i;
235
236 BranchEndState *es_pt;
237
238 string str, time_begin, time_end;
239 time_t t_current;
240 time(&t_current);
241 time_begin = "";
242 time_begin += ctime(&t_current);
243 // seed random number generator using system time
244 srand((unsigned) time(0));
245 //
246 g_sim.SScheduler.parseOptions(argc, argv);
247 // SScheduler.readInfoFromFile(); // Legacy input parsing option
252 // Register the shared procedures master before any snapshot is
253 // deserialized (the --resume path unpacks crew snapshots before the first
254 // RELAP transient loads the book via onFirstInit). Idempotent.
255 g_sim.Indicator.readInfoFromFile(); // procedures parser needs the panel maps
265
266 // Create solver based on --solver flag (solver_url built by Scheduler::parseOptions)
267 if (!g_sim.config.solver_url.empty()) {
268 solver_ptr = std::make_unique<RestApiSolver>(g_sim.config.solver_url);
269 cout << "Using mock solver: " << g_sim.config.solver_url << std::endl;
270 } else {
271 solver_ptr = std::make_unique<RelapSolver>();
272 }
273 solver_raw = solver_ptr.get();
274 auto& solver = *solver_raw;
275
276 cout << "Serial execution" << std::endl;
277 cout << "Project: " << g_sim.SScheduler.getProjectDirectory() << std::endl;
278 g_sim.fresh_restart = false;
279 int sequences_completed = 0;
280
281 // Register signal handlers
282 signal(SIGUSR1, sigusr1_handler);
283 signal(SIGINT, sigint_handler);
284
285 // Resume from checkpoint if --resume specified
286 if (!g_sim.config.resume_file.empty()) {
287 // The checkpoint record only spans pending lineages, so its max branch
288 // ID can sit BELOW IDs phase 1 already issued (closed subtrees). The PE
289 // filenames are the ground truth of issued IDs: scan their max so the
290 // epoch jump below clears every ID any phase ever issued.
291 int max_issued_bid = -1;
292 bool has_zero = false, has_negative = false;
293 long nonneg_count = 0;
294 { std::string pe_dir = g_sim.SScheduler.getPEDirectory();
295 if (fs::is_directory(pe_dir))
296 for (auto& e : fs::recursive_directory_iterator(pe_dir)) {
297 std::string fname = e.path().filename().string();
298 if (e.is_regular_file() && fname.size() > 7 && fname.substr(0, 7) == "ZPENode"
299 && fname.back() == 't') {
300 // ZPENodes_agg.txt matches this filter; stoi("agg") must not throw.
301 int fid = 0;
302 try { fid = std::stoi(fname.substr(fname.find('_') + 1, fname.find('.') - fname.find('_') - 1)); }
303 catch (...) { continue; }
304 if (fid > max_issued_bid) max_issued_bid = fid;
305 if (fid == 0) has_zero = true;
306 if (fid < 0) has_negative = true;
307 if (fid >= 0) ++nonneg_count;
308 }
309 }
310 }
311 // A finalized Output (end-of-run sequential rename) is dense 0..N-1
312 // with the -999 donor sentinels stripped. A checkpoint-stop state is
313 // sparse (rank-window IDs) and, with work pending, carries the
314 // sentinel. Resuming onto a finalized tree would re-save nodes under
315 // natural IDs next to the renamed files and corrupt it, so refuse.
316 if (has_zero && !has_negative
317 && nonneg_count == static_cast<long>(max_issued_bid) + 1) {
318 std::cerr << "FATAL: the project Output/ already contains a finalized run\n"
319 << " (sequentially renamed PE files). This checkpoint's run has\n"
320 << " completed; resuming would corrupt the merged outputs.\n"
321 << " Move the existing Output/ aside to re-run from this checkpoint."
322 << std::endl;
323 return 1;
324 }
326 // Refuse to resume against changed configuration inputs: the
327 // checkpoint's crew/plant state is meaningful only under the files it
328 // was written with.
329 if (!ckpt.config_matches(g_sim.config_fingerprint)) {
330 std::cerr << "FATAL: checkpoint configuration mismatch.\n"
331 << " checkpoint fingerprint: 0x" << std::hex << ckpt.config_fingerprint << "\n"
332 << " current inputs: 0x" << g_sim.config_fingerprint << std::dec << "\n"
333 << " The input deck, Input/ tree, or ZCtrlPar.txt changed since this\n"
334 << " checkpoint was written. Restore the original inputs or start a fresh run."
335 << std::endl;
336 return 1;
337 }
338 if (ckpt.version >= 3 && ckpt.compress_queue != g_sim.config.compress_queue) {
339 std::cout << "[RESUME] Overriding --compress-queue to match the checkpoint ("
340 << (ckpt.compress_queue ? "on" : "off") << ")" << std::endl;
341 g_sim.config.compress_queue = ckpt.compress_queue;
342 }
343
344 // Restore branch tree from serialized merged blob
346 ckpt.restore_branch_center(g_sim.Branch_center);
349
350 // Set IDs so resumed sequences don't collide with phase 1 outputs.
351 g_sim.Branch_center.setIDOfRecentGeneratedES(ckpt.sequence_counter - 1);
352 int max_cid = -1, max_bid = -1;
353 for (auto& bc : g_sim.Branch_center.branch_cluster) {
354 int cid = bc->getIDOfThisBranchCluster();
355 if (cid > max_cid) max_cid = cid;
356 for (auto& bp : bc->all_branch) {
357 int bid = bp->getIDOfThisBranch();
358 if (bid > max_bid) max_bid = bid;
359 }
360 }
361 // Seed new serial branch IDs in a fresh epoch above every ID any phase
362 // issued (record max AND output-file max): reissuing an ID would
363 // overwrite that node's PE file. Same epoch scheme as MPI resume
364 // (Server::dispatch_resume_tasks), so chained serial/MPI resumes stay
365 // collision-free.
366 {
367 constexpr int kEpochStride = 128 * 100000;
368 int true_max = std::max({max_bid, max_issued_bid, ckpt.max_branch_id, 0});
369 int epoch_base = (true_max / kEpochStride + 1) * kEpochStride;
370 std::cout << "[RESUME] Branch-ID epoch base = " << epoch_base
371 << " (max issued so far: " << true_max << ")" << std::endl;
373 }
375
376 // Restore restart_info deque and restart_buffers.
377 // Blobs are stored once per source: MOVE each into the runtime map the
378 // serial FIFO shares across sibling CIDs (no per-CID duplication, and
379 // no second copy held by the checkpoint object).
380 g_sim.restart_info.clear();
381 g_sim.restart_buffers.clear();
383 for (auto& [cid, ri] : ckpt.restart_states) {
384 g_sim.restart_info.push_back(ri);
385 }
386 for (auto& [cid, seq] : ckpt.cid_source)
387 g_sim.cid_to_source_sequence[cid] = seq;
388 for (auto& [seq, blob] : ckpt.source_blobs)
389 g_sim.restart_buffers[seq] = std::move(blob);
390 ckpt.source_blobs.clear();
391 // Serial execution feeds restart buffers straight into the RELAP5
392 // membuf: no transfer queue strips the queue-layer LZ4 of a compressed
393 // MPI checkpoint, so strip it here. This run is raw from here on (its
394 // own re-checkpoints are stamped raw accordingly).
396 for (auto& [seq, blob] : g_sim.restart_buffers) {
397 if (blob.empty()) continue;
398 auto raw = lz4_decompress(blob.data(), static_cast<int>(blob.size()));
399 blob = std::move(raw);
400 }
402 std::cout << "[RESUME] Stripped queue-layer compression from "
403 << g_sim.restart_buffers.size() << " restart buffers for serial execution" << std::endl;
404 }
405
406 sequences_completed = ckpt.sequences_completed;
407 std::cout << "[RESUME] Loaded: " << ckpt.sequences_completed << " sequences done, "
408 << ckpt.pending_tasks.size() << " pending tasks, "
409 << g_sim.restart_buffers.size() << " restart buffers" << std::endl;
410
412 r5pars_.cnewrst = false;
413 solver.setNewRestart(false);
414 solver.setInputDeck("indeck.i");
415 }
416
417 auto wall_start = std::chrono::steady_clock::now();
418 bool stopped_at_checkpoint = false;
419 do {
421 cout << "*** Sequence " << i << " ****" << endl;
422 g_sim.expected_abort = false;
423 solver.setThermoCacheMode(g_sim.config.thermo_cache_mode);
424 // Always use membuf for RELAP5 I/O — adaptive decision only affects
425 // whether the extracted buffer is kept in RAM or dumped to file.
426 solver.setMembufMode(1);
427 solver.runSequence(engine_callback);
428 // SEQ-FP: log fingerprint for this completed sequence
429 log_seq_fingerprint(solver.currentTime());
430 // Save restart data for FIFO if branches were generated this sequence
432 // Decide storage: keep in memory or dump to file
433 bool store_in_mem = should_use_memory_restart();
435 cout << "[ADAPTIVE] seq=" << i << " RSS=" << get_rss_mb() << "MB limit=" << g_sim.config.memory_limit_mb << "MB -> " << (store_in_mem ? "memory" : "file") << endl;
436 auto buf = solver.checkpoint();
437 if (!buf.empty()) {
438 if (store_in_mem) {
439 g_sim.restart_buffers[i] = std::move(buf);
440 } else {
441 std::string backup = restart_path("restart_seq_" + std::to_string(i) + ".r");
442 write_file_or_die(backup, buf.data(), buf.size());
443 }
444 }
446 }
447 i = remove("out.o");
449 if (!g_sim.expected_abort) { //sequesce termination was not detected
450 es_pt = new BranchEndState();
451 str = "Sequence is terminated for unknown reason.";
452 cout << "Sequence is terminated for unknown reason." << endl;
454 es_pt->setConsequenceDescription(str);
456 }
457 if (r5pars_.cnewrst) {
458 solver.setInputDeck("indeck.i");
459 solver.setNewRestart(false);
460 }
461 // Save per-sequence outputs before restartforanewsequence_ clears them
464 ++sequences_completed;
465
466 // Interactive interrupt menu (Ctrl+C)
467 if (sigint_received) {
468 sigint_received = 0;
469 int choice = show_interrupt_menu(sequences_completed);
470 if (choice == 2) {
471 if (g_sim.config.checkpoint_dir.empty()) {
473 fs::create_directories(g_sim.config.checkpoint_dir);
474 }
475 auto ckpt = build_serial_checkpoint(sequences_completed);
476 std::string path = g_sim.config.checkpoint_dir + "/checkpoint_seq_" + std::to_string(sequences_completed) + ".dat";
477 ckpt.save(path);
478 cout << "Resume with: --resume=" << path << endl;
479 stopped_at_checkpoint = true;
480 break;
481 } else if (choice == 3) {
482 cout << "Exiting without saving." << endl;
483 break;
484 }
485 // choice 1: re-register handler (signal resets to SIG_DFL on some systems)
486 signal(SIGINT, sigint_handler);
487 }
488
489 // Checkpoint save (interval-based or SIGUSR1-triggered)
490 if (!g_sim.config.checkpoint_dir.empty() &&
491 ((g_sim.config.checkpoint_interval > 0 && sequences_completed % g_sim.config.checkpoint_interval == 0) ||
494 auto ckpt = build_serial_checkpoint(sequences_completed);
495 std::string ckpt_path = g_sim.config.checkpoint_dir + "/checkpoint_seq_" + std::to_string(sequences_completed) + ".dat";
496 ckpt.save(ckpt_path);
497 cout << "Resume with: --resume=" << ckpt_path << endl;
499 cout << "[CHECKPOINT] --checkpoint-stop: exiting after " << sequences_completed << " sequences" << endl;
500 stopped_at_checkpoint = true;
501 break;
502 }
503 }
504
506 cout << "terminateall: " << solver.isTerminateAll() << endl;
507 } while (!solver.isTerminateAll());
508 //
509 // Final cleanup of any remaining backup files
510 for (auto& [c, s] : g_sim.cid_to_source_sequence) {
511 std::string bkp = restart_path("restart_seq_" + std::to_string(s) + ".r");
512 if (fs::exists(bkp)) fs::remove(bkp);
513 }
515 std::string shm_r = restart_path("restart.r");
516 if (fs::exists(shm_r)) fs::remove(shm_r);
517 }
519
521
522 // After resume, PENode files may have rank-offset IDs from the MPI checkpoint.
523 // Renumber them sequentially (same logic as Server::merge_outputs).
524 // Only at a true end of run: if this run itself stopped at a checkpoint,
525 // the natural-ID names must survive for the next resume.
526 if (!g_sim.config.resume_file.empty() && !stopped_at_checkpoint
527 && fs::is_directory(g_sim.SScheduler.getPEDirectory())) {
528 // Same helper the MPI merge uses (Global/PEOutput.h) so the two
529 // renumbering paths cannot diverge on layout or on sentinel handling.
530 // finalize_sequential renames in the per-file layout (filename-only
531 // renumber) and falls back to collect+rewrite for aggregate content.
532 const std::string pe_dir = g_sim.SScheduler.getPEDirectory();
534 }
535
540 for (auto &ri : g_sim.restart_info) {
541 ri = nullptr;
542 }
543 g_sim.restart_info.clear();
544 time(&t_current);
545 time_end = "";
546 time_end += ctime(&t_current);
547 g_sim.Aux_info.addRecordWithTime(-999.0, "Start_time:");
548 g_sim.Aux_info.addRecordWithTime(-999.0, time_begin);
549 g_sim.Aux_info.addRecordWithTime(-999.0, "End_time:");
550 g_sim.Aux_info.addRecordWithTime(-999.0, time_end);
553 double wall_seconds = std::chrono::duration<double>(
554 std::chrono::steady_clock::now() - wall_start).count();
555 // Send completion notification (no-op if neither --notify-ntfy nor --notify-discord set)
556 {
557 RunSummary summary;
559 summary.sequences = sequences_completed;
560 summary.wall_seconds = wall_seconds;
561 summary.ranks = 1;
562 summary.solver = g_sim.config.solver_url.empty() ? "relap" : "mock";
565 }
566 cout << "Simulation Finished!\nStart at: " << time_begin << "\nEnd at: "
567 << time_end << endl;
568 return 0;
569}
570
571// ---- extern "C" trampolines ----
572// These are called by Fortran (dtstep.f). They route to the active SolverCallback.
573
574void interfc_() {
575 auto* cb = getActiveSolverCallback();
576 auto* s = getActiveSolver();
577 if (cb && s) cb->onEvaluationPoint(*s);
578}
579
580void actte_() {
581 auto* cb = getActiveSolverCallback();
582 auto* s = getActiveSolver();
583 if (cb && s) cb->onTimestepAction(*s);
584}
585
586void secini_() {
587 auto* cb = getActiveSolverCallback();
588 auto* s = getActiveSolver();
589 if (cb && s) cb->onScenarioStart(*s);
590}
591
592void actonce_() {
593 auto* cb = getActiveSolverCallback();
594 auto* s = getActiveSolver();
595 if (cb && s) cb->onFirstInit(*s);
596}
597
599 auto* cb = getActiveSolverCallback();
600 auto* s = getActiveSolver();
601 if (cb && s) cb->onBranchingPoint(*s);
602}
603
604// These are only called from C++ (not Fortran), but kept as extern "C"
605// for symbol compatibility. They delegate to the engine_callback.
608}
609
612}
613
615 //empty routine for console version.
616}
617
618void setrnode_() {
619 // empty routine for console version
620}
621
622// ---- Utility ----
623
624void outputErrMessageToFile(string str) {
625 ofstream fout("err messages.txt", ios::app);
626 fout << r5pars_.timesno << " " << str << '\n';
627 fout.close();
628}
629
630// ---- SEQ-FP diagnostics ----
631
632static std::string compute_ancestry_path() {
633 if (g_sim.seq_restart_cid == 0) return "root";
634 std::vector<std::string> path_parts;
635 int cur_cid = g_sim.seq_restart_cid;
636 int cur_branch_pos = g_sim.seq_restart_branch_num;
637 while (cur_cid != 0) {
639 if (!bc) break;
640 char buf[64];
641 snprintf(buf, sizeof(buf), "%.4f:%d",
642 bc->getTimeAtWhichThisEventHappen(), cur_branch_pos);
643 path_parts.emplace_back(buf);
644 int mother_cid = bc->getIDOfMotherCluster();
645 if (mother_cid == 0) break;
646 BranchCluster *mother_bc = g_sim.Branch_center.findBrnachCluster(mother_cid);
647 if (!mother_bc) break;
648 int mother_branch_pos = -1;
649 for (int i = 0; i < static_cast<int>(mother_bc->all_branch.size()); ++i) {
650 if (mother_bc->all_branch[i]->getIDOfTheChildBranchCluster() == cur_cid) {
651 mother_branch_pos = i;
652 break;
653 }
654 }
655 cur_branch_pos = (mother_branch_pos >= 0) ? mother_branch_pos : 0;
656 cur_cid = mother_cid;
657 }
658 std::string result;
659 for (int i = static_cast<int>(path_parts.size()) - 1; i >= 0; --i) {
660 if (!result.empty()) result += ">";
661 result += path_parts[i];
662 }
663 return result;
664}
665
666static void log_seq_fingerprint(double term_time) {
667 if (!getenv("ADSIDAC_BRANCH_DIAG")) return;
668 std::string ancestry = compute_ancestry_path();
669 std::string events;
670 for (auto& [t, n] : g_sim.current_seq_events) {
671 if (!events.empty()) events += ",";
672 char buf[64];
673 snprintf(buf, sizeof(buf), "%.4f:%d", t, n);
674 events += buf;
675 }
676 if (events.empty()) events = "none";
677 fprintf(stderr, "[SEQ-FP] ancestry=%s events=%s term=%.4f\n",
678 ancestry.c_str(), events.c_str(), term_time);
679}
void secini_()
Security initialization callback.
Definition: ADSIDAC.cpp:586
int main(int argc, char *argv[])
Definition: ADSIDAC.cpp:196
void outputErrMessageToFile(string str)
Definition: ADSIDAC.cpp:624
void initiationforrestart_()
Initialize restart mode for next sequence.
Definition: ADSIDAC.cpp:610
void interfc_()
Called every RELAP5 timestep from dtstep.f. Triggers crew/diagnosis decisions.
Definition: ADSIDAC.cpp:574
static int run_merge_only(int argc, char *argv[])
Definition: ADSIDAC.cpp:149
static ThermalHydraulicSolver * solver_raw
Definition: ADSIDAC.cpp:32
AdsidacCallback engine_callback
Definition: ADSIDAC.cpp:33
static std::string compute_ancestry_path()
Definition: ADSIDAC.cpp:632
static int show_interrupt_menu(int seq_completed)
Definition: ADSIDAC.cpp:44
static std::unique_ptr< ThermalHydraulicSolver > solver_ptr
Definition: ADSIDAC.cpp:31
static void log_seq_fingerprint(double term_time)
Definition: ADSIDAC.cpp:666
void initiate_branching_()
Definition: ADSIDAC.cpp:598
void setrnode_()
Set restart node information in r5pars_.
Definition: ADSIDAC.cpp:618
static volatile sig_atomic_t sigint_received
Definition: ADSIDAC.cpp:40
void restartforanewsequence_()
Find next unexplored branch, generate indeck.i restart input.
Definition: ADSIDAC.cpp:606
void actonce_()
Execute crew actions once per transient.
Definition: ADSIDAC.cpp:592
static volatile sig_atomic_t checkpoint_requested
Definition: ADSIDAC.cpp:36
void mutiproactonce_()
Definition: ADSIDAC.cpp:614
void actte_()
Execute crew actions at each timestep.
Definition: ADSIDAC.cpp:580
static void sigusr1_handler(int)
Definition: ADSIDAC.cpp:37
static void sigint_handler(int)
Definition: ADSIDAC.cpp:41
static DETCheckpoint build_serial_checkpoint(int seq_completed)
Definition: ADSIDAC.cpp:62
Fortran callback declarations and SimulationContext for ADS-IDAC main().
Engine-side implementation of SolverCallback.
void write_file_or_die(const std::string &path, const char *data, size_t size)
Write an entire buffer to a binary file (truncating it); fatal on any failure.
Definition: CheckedIO.h:42
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
void send_completion_notification(const RunSummary &summary)
Send completion notifications to all configured targets. Non-fatal: prints a warning if curl fails,...
Send completion notifications via ntfy.sh or Discord webhooks.
Output consolidation for the standalone –merge-only invocation (rank discovery, threaded ES/KPS renam...
Pivotal-event output layouts (one file per node, or self-delimiting records in one aggregate per rank...
bool should_use_memory_restart()
Decide whether to use memory mode for this restart operation. memory_limit_mb == 0 → unlimited (alway...
std::string restart_path(const std::string &name)
Get restart file path (shm or local).
std::vector< char > lz4_decompress(const char *data, int size)
LZ4-decompress a buffer that has a 4-byte original-size header.
long get_rss_mb()
Get current process RSS in MB (Linux + macOS, returns 0 elsewhere).
void cleanup_fort_temps()
Clean up Fortran temporary files (fort*) left by RELAP5.
SolverCallback * getActiveSolverCallback()
Get the currently active SolverCallback (set during runSequence). Used by extern "C" trampoline funct...
Definition: RelapSolver.cpp:32
ThermalHydraulicSolver * getActiveSolver()
Get the currently active ThermalHydraulicSolver (set during runSequence). Returns nullptr when no seq...
Definition: RelapSolver.cpp:33
RELAP5 implementation of ThermalHydraulicSolver.
REST API implementation of ThermalHydraulicSolver.
Process-local masters of immutable configuration (state overlay).
SimulationContext g_sim
Global simulation context: the single instance, defined in SimulationContext.cpp.
document e
ADS-IDAC engine's implementation of SolverCallback.
void initiationForRestart(ThermalHydraulicSolver &solver)
Restore the C++ simulation state for the branch about to run.
void restartForNewSequence(ThermalHydraulicSolver &solver)
Serial path: find the next unexplored branch and prepare its restart.
void addRecordWithTime(double, std::string)
int getIDOfMotherCluster()
std::vector< std::unique_ptr< BranchProbability > > all_branch
The alternatives of this branching point; index 0 is the one the generating sequence continues on.
The end state (ES node) that terminates one sequence of the DET.
void setTypeOfConsequence(Consequence)
void setConsequenceDescription(std::string)
void setIDOfRecentGeneratedES(int)
void addAnEndState(BranchEndState *)
Closes the running sequence with an end state and takes ownership of it.
int getIDOfRecentGeneratedES()
void clearBranchRecordAll()
Resets the whole record: clusters, end states, pending forks, the three branch definition tables and ...
void setIDOfRecentGeneratedBranchCluster(int)
void setIDOfRecentGeneratedBranch(int)
void endSimulation()
Writes ES_summary.txt in the ES directory from the end_state vector: a header line ES# Probability En...
std::vector< std::unique_ptr< BranchEndState > > end_state
End states of the finished sequences held by this record.
Definition: BranchRecord.h:286
int getIDOfRecentGeneratedBranch()
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 clearCommunicationPlaceAllPointers()
void readInfoFromFile()
void initiationForSimulation()
void clearOperatorATFinal()
void clearOperatorDMFinal()
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
std::string getProjectDirectory()
Definition: Scheduler.cpp:595
double getSequenceTruncationTime()
Definition: Scheduler.cpp:646
void parseOptions(int argc, char *argv[])
Parses the command line and ZiniADS.txt, fills this scheduler and g_sim.config, and reads ZCtrlPar....
Definition: Scheduler.cpp:59
std::string getPEDirectory()
Definition: Scheduler.cpp:604
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 initiationForSimulation()
Abstract interface for thermal-hydraulic solvers.
double getTimeAtWhichThisEventHappen() const
Definition: Timp.cpp:26
struct @0 r5pars_
int rank
MPI rank of this process.
const char * manifest_basename()
File name of the sentinel written to Output/ by –merge=defer. Its presence means the rank directories...
Definition: OutputMerge.h:55
std::pair< size_t, size_t > merge_project(const std::string &project_dir, bool aggregate, int threads)
The full standalone merge, in this order: ES/KPS moves (threaded over rank dirs), rank-ordered PE mov...
Definition: OutputMerge.h:440
bool looks_finalized(const std::string &pe_dir)
True when pe_dir already carries the finalized signature: at least one node, ids dense 0....
Definition: OutputMerge.h:368
bool manifest_exists(const std::string &project_dir)
True if <project_dir>/Output/MERGE_PENDING.txt exists.
Definition: OutputMerge.h:341
bool manifest_aggregate(const std::string &project_dir, bool fallback)
The aggregate_pe flag the deferred run recorded in its manifest.
Definition: OutputMerge.h:350
bool ranks_hold_outputs(const std::string &project_dir, const std::vector< int > &ranks)
True when any rank_N/Output tree still holds mergeable output files (names starting with ZESNode,...
Definition: OutputMerge.h:404
std::vector< int > discover_ranks(const std::string &project_dir)
Worker rank numbers N for which <project_dir>/rank_N is a directory.
Definition: OutputMerge.h:75
size_t finalize_sequential(const std::string &pe_dir, bool aggregate)
End-of-run finalize: renumber every PE node 0..N-1 in id order, ending in the same layout rewrite_seq...
Definition: PEOutput.h:254
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)
constexpr int ODM
Definition: SimConstants.h:145
constexpr int OAT
Definition: SimConstants.h:144
Complete state of a dynamic event tree run at a sequence boundary.
Definition: DETCheckpoint.h:45
std::map< int, std::vector< char > > source_blobs
source key -> restart blob
Definition: DETCheckpoint.h:71
int sequences_completed
Sequences finished when the checkpoint was taken.
Definition: DETCheckpoint.h:50
uint64_t config_fingerprint
Definition: DETCheckpoint.h:57
std::vector< PendingBranch > pending_tasks
Unexplored branches.
Definition: DETCheckpoint.h:97
std::map< int, std::shared_ptr< RestartNodeInfo > > restart_states
cid -> snapshot; read by serial resume
Definition: DETCheckpoint.h:93
void capture_branch_center(const BranchRecord &br)
Serialize br (Boost binary archive) into branch_center_blob, replacing its previous content.
static DETCheckpoint load(const std::string &path)
Read a checkpoint file.
std::map< int, int > cid_source
Definition: DETCheckpoint.h:72
std::vector< std::shared_ptr< RestartNodeInfo > > task_restart_states
Snapshot per pending task (null = none); read by MPI resume.
Definition: DETCheckpoint.h:98
One unexplored branch recorded in a checkpoint.
Definition: DETCheckpoint.h:25
int cid
Branch cluster ID the branch belongs to.
Definition: DETCheckpoint.h:26
int branch_index
Index of the alternative within the cluster (1..N-1)
Definition: DETCheckpoint.h:27
Summary of a completed simulation run.
Definition: Notification.h:8
std::string solver
Definition: Notification.h:13
std::string project
Definition: Notification.h:9
double truncation_time
Definition: Notification.h:14
double wall_seconds
Definition: Notification.h:11
std::string resume_file
–resume checkpoint file as an absolute path (empty = fresh start)
Definition: SimConfig.h:31
std::string checkpoint_dir
–checkpoint-dir as an absolute path, created at startup (empty = disabled)
Definition: SimConfig.h:28
bool checkpoint_stop
–checkpoint-stop: exit after the first checkpoint written; requires checkpoint_interval > 0
Definition: SimConfig.h:30
int checkpoint_interval
–checkpoint-interval: checkpoint every N completed sequences (0 = disabled); requires checkpoint_dir
Definition: SimConfig.h:29
bool resume_has_end_states
True if the BranchRecord restored by a serial resume held end states (checkpoint written by a serial ...
Definition: SimConfig.h:32
long memory_limit_mb
–memory-limit [MB]: 0 = unlimited; >0 = keep a restart buffer in memory only while process RSS is bel...
Definition: SimConfig.h:21
int is_mpi_environment
1 if OMPI_COMM_WORLD_SIZE or PMI_SIZE is set in the environment, 0 for serial
Definition: SimConfig.h:33
int thermo_cache_mode
0 = reuse cached steam tables (default), 2 = force reload every sequence (–no-thermo-cache)....
Definition: SimConfig.h:19
int merge_threads
–merge-threads for –merge-only, threads over rank dirs (0 or less = min(8, hardware threads))
Definition: SimConfig.h:45
bool compress_queue
–compress-queue (default off): LZ4-compress RELAP restart buffers held and sent between MPI ranks
Definition: SimConfig.h:22
std::string solver_url
REST solver URL (empty = use RELAP5). –solver=mock gives "http://localhost:<--solver-port,...
Definition: SimConfig.h:27
bool memory_restart
In-memory restart. parseOptions() sets it to !–no-memory-restart, so it is TRUE in every run unless –...
Definition: SimConfig.h:20
bool aggregate_pe_output
Definition: SimConfig.h:23
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.
std::map< int, std::vector< char > > restart_buffers
seq_id -> RELAP5 restart buffer
uint64_t config_fingerprint
FNV-1a of immutable config inputs (see ConfigFingerprint.h); 0 = not computed.
CommandControlCenter Command_handler
Command/control dispatch.
bool fresh_restart
One-shot flag for restart init.
bool branches_generated_this_sequence
True if branches generated this seq.
std::map< int, int > cid_to_source_sequence
Maps cluster ID to source sequence.
CommunicationPlace Communication_place
Inter-module communication.
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.
bool expected_abort
Sequence ended abnormally.