// gpr_poc —— POC-B headless 度量 CLI。 // // 串起整条地基:发现 14 通道 .iprb + .ord → assembleGprSurvey → buildGprVolume // → ChunkedVolumeStore::write → buildPyramid → WholeVolumeSource(load), // 在真实/合成数据上输出可测的真实指标(耗时/维度/体积/压缩比/加载/峰值内存)。 // // 子命令: // gpr_poc build [--line 001] [--cellXY 0.2] [--cellZ 0.05] [--out ] [--levels 2] // gpr_poc load // gpr_poc selftest #include #include #include #include #include #include #include #include #include #include #include "Probe.hpp" #include "core/algo/GprVolumeBuilder.hpp" #include "core/algo/IInterpolator.hpp" #include "core/model/GprSurvey.hpp" #include "core/model/ScalarVolumeI16.hpp" #include "data/store/ChunkedVolumeStore.hpp" #include "io/gpr/GprSurveyAssembler.hpp" #include "render/source/WholeVolumeSource.hpp" namespace fs = std::filesystem; using geopro::tools::Probe; using geopro::tools::Stopwatch; namespace { constexpr int kChannels = 14; // ---- 命令行参数解析(极简 --key value)---- struct Args { std::map kv; std::vector positional; std::string get(const std::string& key, const std::string& def) const { auto it = kv.find(key); return it != kv.end() ? it->second : def; } }; Args parseArgs(int argc, char** argv, int start) { Args a; for (int i = start; i < argc; ++i) { std::string tok = argv[i]; if (tok.rfind("--", 0) == 0 && i + 1 < argc) { a.kv[tok.substr(2)] = argv[i + 1]; ++i; } else { a.positional.push_back(tok); } } return a; } // 把一行指标追加写入 last-metrics.txt(与可执行同目录的工具源目录无关, // 写到当前工作目录便于汇总;CSV 风格一行)。 void writeMetricLine(const std::string& line) { std::ofstream f("last-metrics.txt", std::ios::app); if (f) f << line << "\n"; } // 发现某线 14 通道 .iprb(按通道号 A01..A14 排序)+ 该线 .ord。 struct LineFiles { std::vector iprb; // 已按通道号排序 std::string ord; }; LineFiles discoverLine(const std::string& dir, const std::string& line) { LineFiles lf; std::map byChannel; // 通道号 → 路径(自动按号排序) std::string ordPath; for (const auto& e : fs::directory_iterator(dir)) { if (!e.is_regular_file()) continue; const std::string name = e.path().filename().string(); const std::string ext = e.path().extension().string(); // .ord:优先匹配本线(含 "_" 且以 .ord 结尾),否则记下工区任一 .ord 作兜底。 if (ext == ".ord") { if (name.find("_" + line + ".") != std::string::npos) { ordPath = e.path().string(); } else if (ordPath.empty()) { ordPath = e.path().string(); } continue; } // .iprb:匹配 "*__A.iprb"。 if (ext != ".iprb") continue; const std::string tag = "_" + line + "_A"; const std::size_t pos = name.find(tag); if (pos == std::string::npos) continue; const std::size_t numStart = pos + tag.size(); std::size_t numEnd = numStart; while (numEnd < name.size() && std::isdigit(static_cast(name[numEnd]))) { ++numEnd; } if (numEnd == numStart) continue; const int ch = std::stoi(name.substr(numStart, numEnd - numStart)); byChannel[ch] = e.path().string(); } for (const auto& [ch, path] : byChannel) lf.iprb.push_back(path); lf.ord = ordPath; return lf; } // 由 survey 推 GridSpec:X 沿测线,Y 跨通道,Z 深度。 geopro::core::GridSpec specFromSurvey(const geopro::core::GprSurvey& s, double cellXY, double cellZ) { geopro::core::GridSpec spec{}; const double rangeX = (s.ntraces > 1) ? (s.ntraces - 1) * s.dx : 0.0; const double y0 = s.channelY.empty() ? 0.0 : s.channelY.front(); const double y1 = s.channelY.empty() ? 0.0 : s.channelY.back(); const double rangeY = y1 - y0; const double rangeZ = (s.samples > 1) ? (s.samples - 1) * s.dz : 0.0; auto cells = [](double range, double cell) { if (cell <= 0.0) return 1; return static_cast(std::ceil(range / cell)) + 1; }; spec.ox = s.x0; spec.oy = y0; spec.oz = s.z0; spec.dx = cellXY; spec.dy = cellXY; spec.dz = cellZ; spec.nx = cells(rangeX, cellXY); spec.ny = cells(rangeY, cellXY); spec.nz = cells(rangeZ, cellZ); spec.power = 2.0; spec.maxDist = cellXY * 2.0; return spec; } // 落盘 data.bin 体积(所有 data*.bin 之和,含金字塔各级)。 std::int64_t storeDataBytes(const std::string& dir) { std::int64_t total = 0; for (const auto& e : fs::directory_iterator(dir)) { if (!e.is_regular_file()) continue; const std::string name = e.path().filename().string(); if (name.rfind("data", 0) == 0 && e.path().extension().string() == ".bin") { total += static_cast(e.file_size()); } } return total; } int cmdBuild(int argc, char** argv) { const Args a = parseArgs(argc, argv, 2); if (a.positional.empty()) { std::cerr << "用法: gpr_poc build [--line 001] [--cellXY 0.2] " "[--cellZ 0.05] [--out ] [--levels 2]\n"; return 2; } const std::string dir = a.positional[0]; const std::string line = a.get("line", "001"); const double cellXY = std::stod(a.get("cellXY", "0.2")); const double cellZ = std::stod(a.get("cellZ", "0.05")); const int levels = std::stoi(a.get("levels", "2")); const std::string out = a.get("out", (fs::temp_directory_path() / ("gpr_store_" + line)).string()); std::cout << "[build] dir=" << dir << " line=" << line << " cellXY=" << cellXY << " cellZ=" << cellZ << " levels=" << levels << " out=" << out << "\n"; const LineFiles lf = discoverLine(dir, line); std::cout << "[build] 发现通道数=" << lf.iprb.size() << " ord=" << (lf.ord.empty() ? "(无)" : lf.ord) << "\n"; if (lf.iprb.size() != static_cast(kChannels)) { std::cerr << "[build] 警告: 通道数 != " << kChannels << "(仍按发现数继续)\n"; } if (lf.iprb.empty() || lf.ord.empty()) { std::cerr << "[build] 错误: 未发现 .iprb 或 .ord\n"; return 1; } // 1) 装配 Stopwatch swAsm; geopro::core::GprSurvey survey = geopro::io::gpr::assembleGprSurvey(lf.iprb, lf.ord); const double asmMs = swAsm.elapsedMs(); std::cout << "[build] 装配完成 ntraces=" << survey.ntraces << " samples=" << survey.samples << " channels=" << survey.channelY.size() << " dx=" << survey.dx << " dz=" << survey.dz << "\n"; // 2) 建体 const geopro::core::GridSpec spec = specFromSurvey(survey, cellXY, cellZ); std::cout << "[build] GridSpec nx=" << spec.nx << " ny=" << spec.ny << " nz=" << spec.nz << " dx=" << spec.dx << " dy=" << spec.dy << " dz=" << spec.dz << " maxDist=" << spec.maxDist << "\n"; Stopwatch swBuild; geopro::core::BuiltI16 built = geopro::core::buildGprVolume(survey, spec); const double buildMs = swBuild.elapsedMs(); const std::int64_t nx = built.vol.nx(), ny = built.vol.ny(), nz = built.vol.nz(); const std::int64_t voxels = nx * ny * nz; const std::int64_t rawBytes = voxels * 2; // int16 // 3) 落盘 + 金字塔 fs::create_directories(out); Stopwatch swWrite; geopro::data::ChunkedVolumeStore::write(out, built); const double writeMs = swWrite.elapsedMs(); Stopwatch swPyr; { geopro::data::ChunkedVolumeStore store(out); store.buildPyramid(levels); } const double pyrMs = swPyr.elapsedMs(); const std::int64_t dataBytes = storeDataBytes(out); const double ratio = dataBytes > 0 ? static_cast(rawBytes) / dataBytes : 0.0; const double peak = Probe::peakMemMB(); std::cout << "\n=== build 指标 ===\n"; std::cout << "装配耗时(ms) : " << asmMs << "\n"; std::cout << "建体耗时(ms) : " << buildMs << "\n"; std::cout << "落盘耗时(ms) : " << writeMs << "\n"; std::cout << "金字塔耗时(ms) : " << pyrMs << "\n"; std::cout << "体维度 : " << nx << " x " << ny << " x " << nz << "\n"; std::cout << "体素数 : " << voxels << "\n"; std::cout << "原始体积(B) : " << rawBytes << " (" << rawBytes / (1024.0 * 1024.0) << " MB)\n"; std::cout << "data.bin(B) : " << dataBytes << " (" << dataBytes / (1024.0 * 1024.0) << " MB)\n"; std::cout << "压缩比 : " << ratio << " x\n"; std::cout << "峰值内存(MB) : " << peak << "\n"; writeMetricLine( "build,line=" + line + ",cellXY=" + std::to_string(cellXY) + ",cellZ=" + std::to_string(cellZ) + ",nx=" + std::to_string(nx) + ",ny=" + std::to_string(ny) + ",nz=" + std::to_string(nz) + ",voxels=" + std::to_string(voxels) + ",rawB=" + std::to_string(rawBytes) + ",dataB=" + std::to_string(dataBytes) + ",ratio=" + std::to_string(ratio) + ",asmMs=" + std::to_string(asmMs) + ",buildMs=" + std::to_string(buildMs) + ",writeMs=" + std::to_string(writeMs) + ",pyrMs=" + std::to_string(pyrMs) + ",peakMB=" + std::to_string(peak)); return 0; } int cmdLoad(int argc, char** argv) { const Args a = parseArgs(argc, argv, 2); if (a.positional.empty()) { std::cerr << "用法: gpr_poc load \n"; return 2; } const std::string dir = a.positional[0]; std::cout << "[load] storeDir=" << dir << "\n"; Stopwatch sw; geopro::render::WholeVolumeSource src(dir); const double loadMs = sw.elapsedMs(); const auto& m = src.meta(); const std::int64_t voxels = static_cast(m.nx) * m.ny * m.nz; const std::int64_t wholeBytes = voxels * 2; // VTK_SHORT const double peak = Probe::peakMemMB(); std::cout << "\n=== load 指标 ===\n"; std::cout << "加载耗时(ms) : " << loadMs << "\n"; std::cout << "整卷维度 : " << m.nx << " x " << m.ny << " x " << m.nz << "\n"; std::cout << "整卷字节(B) : " << wholeBytes << " (" << wholeBytes / (1024.0 * 1024.0) << " MB)\n"; std::cout << "峰值内存(MB) : " << peak << "\n"; writeMetricLine("load,dir=" + dir + ",nx=" + std::to_string(m.nx) + ",ny=" + std::to_string(m.ny) + ",nz=" + std::to_string(m.nz) + ",wholeB=" + std::to_string(wholeBytes) + ",loadMs=" + std::to_string(loadMs) + ",peakMB=" + std::to_string(peak)); return 0; } // ---- selftest:合成极小数据走完整 build→load 管线 ---- // 写一个极小通道的 .iprb + .iprh(samples 采样、traces 道,值 = base + t + s)。 void writeSyntheticChannel(const fs::path& iprbPath, int samples, int traces, std::int16_t base) { const fs::path iprhPath = fs::path(iprbPath).replace_extension(".iprh"); std::ofstream h(iprhPath); h << "SAMPLES: " << samples << "\n"; h << "LAST TRACE: " << (traces - 1) << "\n"; h << "CHANNELS: 2\n"; h << "TIMEWINDOW: 100.0\n"; h << "SOIL VELOCITY: 100.0\n"; // m/µs → ×1e6 → 1e8 m/s h << "DISTANCE INTERVAL: 0.05\n"; h.close(); std::ofstream b(iprbPath, std::ios::binary); // 布局 [trace*samples + s],s 最快。 for (int t = 0; t < traces; ++t) { for (int s = 0; s < samples; ++s) { const std::int16_t v = static_cast(base + t + s); b.write(reinterpret_cast(&v), sizeof(v)); } } } int cmdSelftest() { std::cout << "[selftest] 构造极小合成 survey(2 通道)...\n"; const fs::path tmp = fs::temp_directory_path() / "gpr_poc_selftest"; std::error_code ec; fs::remove_all(tmp, ec); fs::create_directories(tmp); const int samples = 8; const int traces = 12; // 2 通道 .iprb/.iprh + .ord(末列==1 标记有效通道,第 2 列为横偏 Y)。 writeSyntheticChannel(tmp / "syn_001_A01.iprb", samples, traces, /*base=*/100); writeSyntheticChannel(tmp / "syn_001_A02.iprb", samples, traces, /*base=*/200); { std::ofstream ord(tmp / "syn_001.ord"); ord << "0 0.000000 -1.5 1\n"; ord << "1 1.000000 -1.5 1\n"; } const std::vector iprb = { (tmp / "syn_001_A01.iprb").string(), (tmp / "syn_001_A02.iprb").string()}; const std::string ord = (tmp / "syn_001.ord").string(); bool ok = true; auto check = [&](bool cond, const std::string& msg) { if (!cond) { std::cerr << "[selftest] FAIL: " << msg << "\n"; ok = false; } }; try { // 装配 geopro::core::GprSurvey survey = geopro::io::gpr::assembleGprSurvey(iprb, ord); check(survey.ntraces == traces, "ntraces"); check(survey.samples == samples, "samples"); check(survey.channelY.size() == 2, "channels"); // channelY 升序:A01 偏移 0.0 在前,A02 偏移 1.0 在后。 check(survey.channelY.front() < survey.channelY.back(), "channelY 升序"); // 建体:cellXY 取通道间距 1.0 → ny=2;cellZ 较细确保 nz>1。 const double cellXY = 1.0; const double cellZ = std::max(survey.dz, 1e-12); const geopro::core::GridSpec spec = specFromSurvey(survey, cellXY, cellZ); std::cout << "[selftest] GridSpec " << spec.nx << "x" << spec.ny << "x" << spec.nz << " dz=" << spec.dz << "\n"; check(spec.ny == 2, "ny==2"); geopro::core::BuiltI16 built = geopro::core::buildGprVolume(survey, spec); check(built.vol.nx() == spec.nx, "built nx"); check(built.vol.ny() == spec.ny, "built ny"); check(built.vol.nz() == spec.nz, "built nz"); // 落盘 + 金字塔 const std::string store = (tmp / "store").string(); fs::create_directories(store); geopro::data::ChunkedVolumeStore::write(store, built, /*brick=*/4); { geopro::data::ChunkedVolumeStore s(store); s.buildPyramid(1); check(s.levels() == 2, "金字塔层数==2"); } // 加载整卷,校验维度一致 geopro::render::WholeVolumeSource src(store); check(src.meta().nx == spec.nx, "load nx"); check(src.meta().ny == spec.ny, "load ny"); check(src.meta().nz == spec.nz, "load nz"); // 某体素值合理性:x0/y0 角点应有非 blank 量化值(落格命中首道首通道)。 const std::int16_t q = built.vol.at(0, 0, 0); check(q != geopro::core::ScalarVolumeI16::kBlank, "(0,0,0) 非 blank"); } catch (const std::exception& e) { std::cerr << "[selftest] 异常: " << e.what() << "\n"; ok = false; } fs::remove_all(tmp, ec); std::cout << "[selftest] " << (ok ? "PASS" : "FAIL") << "\n"; return ok ? 0 : 1; } void usage() { std::cerr << "gpr_poc —— POC-B headless 度量 CLI\n" " gpr_poc build [--line 001] [--cellXY 0.2] " "[--cellZ 0.05] [--out ] [--levels 2]\n" " gpr_poc load \n" " gpr_poc selftest\n"; } } // namespace int main(int argc, char** argv) { if (argc < 2) { usage(); return 2; } const std::string cmd = argv[1]; try { if (cmd == "build") return cmdBuild(argc, argv); if (cmd == "load") return cmdLoad(argc, argv); if (cmd == "selftest") return cmdSelftest(); } catch (const std::exception& e) { std::cerr << "错误: " << e.what() << "\n"; return 1; } usage(); return 2; }