29#ifndef ADSIDAC_OUTPUTMERGE_H
30#define ADSIDAC_OUTPUTMERGE_H
42#include <unordered_set>
50namespace fs = std::filesystem;
61 return {
"Scenario info.txt",
"Procedure data.txt",
"Scan data.txt",
68 if (fname == af)
return true;
76 std::vector<int> ranks;
78 for (
auto&
e : fs::directory_iterator(project_dir, ec)) {
79 if (!
e.is_directory())
continue;
80 const std::string n =
e.path().filename().string();
81 if (n.rfind(
"rank_", 0) != 0)
continue;
83 const int r = std::stoi(n.substr(5));
84 if (r > 0) ranks.push_back(r);
87 std::sort(ranks.begin(), ranks.end());
100inline void move_file(
const fs::path& src,
const fs::path& dest,
101 std::unordered_set<std::string>& made_dirs) {
103 if (made_dirs.insert(dest.parent_path().string()).second)
104 fs::create_directories(dest.parent_path(), ec);
106 fs::rename(src, dest, mec);
109 fs::copy(src, dest, fs::copy_options::overwrite_existing, mec);
111 std::cerr <<
"[MERGE] failed to move " << src <<
": " << mec.message() << std::endl;
113 fs::remove(src, mec);
127 const fs::path rank_out = fs::path(project_dir) / (
"rank_" + std::to_string(r)) /
"Output";
128 const fs::path shared_out = fs::path(project_dir) /
"Output";
129 if (!fs::exists(rank_out, ec))
return;
130 std::unordered_set<std::string> made_dirs;
131 for (
const char* kind : {
"ES",
"KPS"}) {
132 const fs::path src_root = rank_out / kind;
133 if (!fs::exists(src_root, ec))
continue;
134 for (
auto& entry : fs::recursive_directory_iterator(src_root, ec)) {
136 if (!entry.is_regular_file())
continue;
137 const std::string fname = entry.path().filename().string();
139 move_file(entry.path(), shared_out / kind / fs::relative(entry.path(), src_root),
154 const fs::path shared_pe = fs::path(project_dir) /
"Output" /
"PE";
155 std::unordered_set<std::string> made_dirs;
156 for (
int r : ranks) {
157 const fs::path src_root = fs::path(project_dir) / (
"rank_" + std::to_string(r))
159 if (!fs::exists(src_root, ec))
continue;
160 for (
auto& entry : fs::recursive_directory_iterator(src_root, ec)) {
162 if (!entry.is_regular_file())
continue;
163 const std::string fname = entry.path().filename().string();
165 move_file(entry.path(), shared_pe / fs::relative(entry.path(), src_root), made_dirs);
185 const std::vector<int>& ranks) {
187 const fs::path shared_out = fs::path(project_dir) /
"Output";
190 const char* kind = is_agg ?
"PE" :
"ES";
191 auto src_of = [&](
int r) -> fs::path {
192 const fs::path in_rank = fs::path(project_dir) / (
"rank_" + std::to_string(r))
193 /
"Output" / kind / base;
195 if (fs::exists(in_rank, e2))
return in_rank;
196 const fs::path staged = shared_out / kind /
"parts" / (base +
".rank_" + std::to_string(r));
197 if (fs::exists(staged, e2))
return staged;
202 if (!src_of(r).empty()) { any =
true;
break; }
204 fs::create_directories(shared_out / kind, ec);
205 std::ofstream out(shared_out / kind / base,
206 std::ios::binary | (is_agg ? std::ios::app : std::ios::trunc));
207 size_t agg_raw = 0, agg_unique = 0;
208 for (
int r : ranks) {
209 const fs::path src = src_of(r);
210 if (src.empty())
continue;
218 agg_raw += raw; agg_unique += uniq;
220 std::ifstream in(src, std::ios::binary);
221 if (in) { out << in.rdbuf(); in.close(); fs::remove(src, ec); }
223 if (is_agg && agg_raw > agg_unique)
224 std::cout <<
"[MERGE] compacted PE aggregates: " << agg_raw <<
" -> "
225 << agg_unique <<
" records across " << ranks.size()
226 <<
" rank dirs" << std::endl;
228 for (
const char* kind : {
"ES",
"PE"}) {
229 const fs::path parts = shared_out / kind /
"parts";
231 if (fs::exists(parts, e2) && fs::is_empty(parts, e2))
232 fs::remove(parts, e2);
239 std::ofstream out(shared_out /
"ES" /
"err messages.txt",
240 std::ios::binary | std::ios::app);
241 std::vector<int> with_zero = ranks;
242 with_zero.insert(with_zero.begin(), 0);
243 for (
int r : with_zero) {
244 for (
const fs::path src : {
245 fs::path(project_dir) / (
"rank_" + std::to_string(r)) /
"err messages.txt",
246 shared_out /
"ES" /
"parts" / (
"err messages.txt.rank_" + std::to_string(r))}) {
247 std::ifstream in(src, std::ios::binary);
248 if (in) { out << in.rdbuf(); in.close(); fs::remove(src, ec); }
266 std::string summary_path = dest_es +
"ES_summary.txt";
267 std::ofstream summary(summary_path, std::ios::out);
268 summary <<
"ES#\tProbability\tEnd_time\tConsequence#\tDescription\n";
270 std::vector<std::pair<int, fs::path>> es_files;
272 if (fs::exists(dest_es, ec))
273 for (
auto& entry : fs::recursive_directory_iterator(dest_es, ec)) {
274 std::string fname = entry.path().filename().string();
275 if (fname.substr(0, 7) ==
"ZESNode" && fname.back() ==
't') {
276 auto start = fname.find(
'_') + 1;
277 auto end = fname.find(
'.');
279 try {
id = std::stoi(fname.substr(start, end - start)); }
280 catch (...) {
continue; }
281 es_files.push_back({id, entry.path()});
284 std::sort(es_files.begin(), es_files.end());
286 for (
auto& [
id, path] : es_files) {
287 std::ifstream fin(path);
289 if (std::getline(fin, line)) {
291 std::istringstream iss(line);
292 std::string es_label;
293 double time_val, probability;
294 int es_id, mother_id, z1, z2, z3;
295 iss >> es_label >> time_val >> es_id >> mother_id >> z1 >> z2 >> z3 >> probability;
296 std::string consequence_line, description;
297 if (std::getline(fin, consequence_line)) {
298 std::istringstream css(consequence_line);
301 css >> tag >> cons_type;
302 if (std::getline(fin, description)) {
303 if (!description.empty() && description.front() ==
'"') description.erase(0, 1);
304 if (!description.empty() && description.back() ==
'"') description.pop_back();
306 summary <<
id <<
'\t' << probability <<
'\t' << time_val <<
'\t'
307 << cons_type <<
'\t' << description <<
'\n';
311 return es_files.size();
327inline void write_manifest(
const std::string& project_dir,
int ranks,
bool aggregate,
328 long sequences,
unsigned long long fingerprint) {
330 const fs::path out = fs::path(project_dir) /
"Output";
331 fs::create_directories(out, ec);
333 m <<
"# adsidac deferred merge v1\n"
334 <<
"ranks " << ranks <<
"\n"
335 <<
"aggregate_pe " << (aggregate ? 1 : 0) <<
"\n"
336 <<
"sequences " << sequences <<
"\n"
337 <<
"config_fingerprint 0x" << std::hex << fingerprint << std::dec <<
"\n";
355 if (key ==
"aggregate_pe" && (m >> val))
return val != 0;
356 m.ignore(std::numeric_limits<std::streamsize>::max(),
'\n');
370 if (!fs::exists(pe_dir, ec))
return false;
373 bool has_zero =
false, has_negative =
false, any =
false;
374 for (
auto& entry : fs::recursive_directory_iterator(pe_dir, ec)) {
375 if (!entry.is_regular_file())
continue;
376 const std::string fname = entry.path().filename().string();
377 std::vector<int> ids;
379 std::vector<std::pair<int, std::string>> recs;
381 for (
auto& [
id, c] : recs) ids.push_back(
id);
382 }
else if (fname.size() > 7 && fname.compare(0, 7,
"ZPENode") == 0
383 && fname.back() ==
't') {
385 ids.push_back(std::stoi(fname.substr(fname.find(
'_') + 1,
386 fname.find(
'.') - fname.find(
'_') - 1)));
387 }
catch (...) {
continue; }
391 if (
id > max_id) max_id = id;
392 if (
id == 0) has_zero =
true;
393 if (
id < 0) has_negative =
true;
394 if (
id >= 0) ++nonneg;
397 return any && has_zero && !has_negative && nonneg ==
static_cast<long>(max_id) + 1;
406 for (
int r : ranks) {
407 const fs::path out = fs::path(project_dir) / (
"rank_" + std::to_string(r)) /
"Output";
408 if (!fs::exists(out, ec))
continue;
409 for (
auto& entry : fs::recursive_directory_iterator(out, ec)) {
411 if (!entry.is_regular_file())
continue;
412 const std::string f = entry.path().filename().string();
413 if (f.rfind(
"ZESNode", 0) == 0 || f.rfind(
"ZKPS", 0) == 0
414 || f.rfind(
"ZDIA", 0) == 0 || f.rfind(
"ZPIF", 0) == 0
440inline std::pair<size_t, size_t>
merge_project(
const std::string& project_dir,
441 bool aggregate,
int threads) {
443 const std::string dest_es = (fs::path(project_dir) /
"Output" /
"ES").
string() +
"/";
444 const std::string dest_pe = (fs::path(project_dir) /
"Output" /
"PE").
string() +
"/";
446 if (threads < 1) threads = 1;
447 std::atomic<size_t> next{0};
449 for (
size_t i = next.fetch_add(1); i < ranks.size(); i = next.fetch_add(1))
452 if (threads == 1 || ranks.size() <= 1) {
455 std::vector<std::thread> pool;
456 const int n = std::min<int>(threads,
static_cast<int>(ranks.size()));
458 for (
int t = 0; t < n; ++t) pool.emplace_back(worker);
459 for (
auto& th : pool) th.join();
467 return {seqs, pe_count};
Pivotal-event output layouts (one file per node, or self-delimiting records in one aggregate per rank...
void move_rank_es_kps(const std::string &project_dir, int r)
Move one rank's ES and KPS files into the shared tree, keeping their paths relative to Output/ES and ...
std::vector< std::string > append_basenames()
Base names of the outputs that are merged by concatenation rather than by moving: the three analysis ...
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...
const char * manifest_basename()
File name of the sentinel written to Output/ by –merge=defer. Its presence means the rank directories...
void concat_appends_from_ranks(const std::string &project_dir, const std::vector< int > &ranks)
Concatenate every rank's append files into the shared Output/.
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...
bool looks_finalized(const std::string &pe_dir)
True when pe_dir already carries the finalized signature: at least one node, ids dense 0....
bool manifest_exists(const std::string &project_dir)
True if <project_dir>/Output/MERGE_PENDING.txt exists.
void write_manifest(const std::string &project_dir, int ranks, bool aggregate, long sequences, unsigned long long fingerprint)
Write the –merge=defer manifest <project_dir>/Output/MERGE_PENDING.txt (Output/ is created if needed;...
bool manifest_aggregate(const std::string &project_dir, bool fallback)
The aggregate_pe flag the deferred run recorded in its manifest.
bool is_append_basename(const std::string &fname)
True if fname (a base name, no directory) is one of append_basenames().
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,...
size_t build_es_summary(const std::string &dest_es)
Rebuild <dest_es>ES_summary.txt by scanning the destination ES tree.
std::vector< int > discover_ranks(const std::string &project_dir)
Worker rank numbers N for which <project_dir>/rank_N is a directory.
void move_rank_pe_files(const std::string &project_dir, const std::vector< int > &ranks)
Rank-ordered move of per-file PE nodes into Output/PE.
void parse_aggregate(const fs::path &path, std::vector< std::pair< int, std::string > > &out)
Parse one aggregate file, appending (id, content) to out in file order, duplicates and negative ids i...
const char * aggregate_basename()
Basename of the aggregate file ("ZPENodes_agg.txt"). It is in the MPI merge's append-files list,...
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.
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...