Bicameral Engine
GPU-resident engine for a magic-research open-world game
Loading...
Searching...
No Matches
probe_trace.cpp
Go to the documentation of this file.
1// probe_trace.cpp — 伝導の連鎖のトレースの木・CPU リファレンスの予想・食い違いの場所(T-0087)。使い方と木の組み方は probe_trace.h。
2#include "sim/probe_trace.h"
3
4#include <algorithm>
5#include <format>
6#include <fstream>
7#include <map>
8#include <set>
9
10#include "core/aliases.h"
11
12namespace bicameral::sim {
13 namespace {
14
17 using Coordinates = std::array<uint32_t, 3>;
18
19 // --- 座標 ---
20
21 Coordinates BlockCoordinates(uint32_t block) {
22 return {block % PROBE_BLOCKS_PER_AXIS, (block / PROBE_BLOCKS_PER_AXIS) % PROBE_BLOCKS_PER_AXIS,
23 block / (PROBE_BLOCKS_PER_AXIS * PROBE_BLOCKS_PER_AXIS)};
24 }
25
27 return {cell % PROBE_GRID_SIZE, (cell / PROBE_GRID_SIZE) % PROBE_GRID_SIZE, cell / PROBE_SLICE_CELL_COUNT};
28 }
29
30 std::string FormatCoordinates(const Coordinates& coordinates) {
31 return std::format("({}, {}, {})", coordinates[0], coordinates[1], coordinates[2]);
32 }
33
34 // 自分と 6 面の隣(格子の中)のブロック。並びは probe_conduct.hlsl の FaceOffset と同じ(比べる前に並べるので順は効かない)
35 std::vector<uint32_t> BlockAndNeighbors(uint32_t block) {
36 constexpr auto AXIS = static_cast<int32_t>(PROBE_BLOCKS_PER_AXIS);
37 constexpr std::array<std::array<int32_t, 3>, PROBE_WAKE_MAX_RECORDS> OFFSETS = {
38 {{0, 0, 0}, {-1, 0, 0}, {1, 0, 0}, {0, -1, 0}, {0, 1, 0}, {0, 0, -1}, {0, 0, 1}}};
39 const Coordinates center = BlockCoordinates(block);
40
41 std::vector<uint32_t> blocks;
42 for (const auto& offset : OFFSETS) {
43 const int32_t x = static_cast<int32_t>(center[0]) + offset[0];
44 const int32_t y = static_cast<int32_t>(center[1]) + offset[1];
45 const int32_t z = static_cast<int32_t>(center[2]) + offset[2];
46 if (x < 0 || y < 0 || z < 0 || x >= AXIS || y >= AXIS || z >= AXIS)
47 continue;
48
49 blocks.push_back(
50 ProbeBlockIndex(static_cast<uint32_t>(x), static_cast<uint32_t>(y), static_cast<uint32_t>(z)));
51 }
52
53 return blocks;
54 }
55
56 // --- 範囲(graph_trace.hlsli の GtWantsTick・GtWantsPlace と同じ判定)---
57
58 bool WantsTick(const GraphTraceFilter& filter, uint64_t tick) {
59 return filter.enabled && tick >= filter.tickBegin && tick < filter.tickEnd;
60 }
61
62 bool WantsBlock(const GraphTraceFilter& filter, uint32_t block) {
63 const Coordinates place = BlockCoordinates(block);
64 for (uint32_t axis = 0; axis < 3; ++axis) {
65 if (place[axis] < filter.boxMin[axis] || place[axis] >= filter.boxMax[axis])
66 return false;
67 }
68
69 return true;
70 }
71
72 // --- 木(1 刻みぶん)---
73
74 struct TickTree {
75 std::map<uint32_t, std::vector<uint32_t>> pokes; // 根のブロック → つついたセル
76 std::map<uint32_t, uint32_t> selfWakes; // 根のブロック → 自分を起こした数(一覧に入った回数)
77 std::set<uint32_t> roots;
78 std::map<uint32_t, uint32_t> parents; // 起こされたブロック → 親(起こした根のうち一番小さい番号)
79 std::map<uint32_t, uint32_t> conducted; // 計算したブロック → 結果(PROBE_BLOCK_FLAG_*)
80 };
81
82 // (返すと std::map のムーブが noexcept でないので、呼ぶ側の物に書く)
83 void BuildTickTree(std::span<const GraphTraceRecord> records, TickTree& tree) {
84 for (const GraphTraceRecord& record : records) {
85 if (record.kind == PROBE_TRACE_POKE) {
86 tree.pokes[record.subject].push_back(record.object);
87 tree.roots.insert(record.subject);
88 } else if (record.kind == PROBE_TRACE_WAKE) {
89 tree.roots.insert(record.subject);
90 if (record.subject == record.object)
91 ++tree.selfWakes[record.subject];
92
93 const auto [parent, inserted] = tree.parents.try_emplace(record.object, record.subject);
94 if (!inserted)
95 parent->second = std::min(parent->second, record.subject);
96 } else if (record.kind == PROBE_TRACE_CONDUCT)
97 tree.conducted[record.subject] = record.object;
98 }
99 }
100
101 std::string FormatChild(const TickTree& tree, uint32_t block) {
102 const auto conducted = tree.conducted.find(block);
103 const char* state = conducted == tree.conducted.end() ? "(計算の記録なし)"
104 : (conducted->second & PROBE_BLOCK_FLAG_CHANGED) != 0 ? "変わった"
105 : (conducted->second & PROBE_BLOCK_FLAG_POSSIBLE) != 0 ? "変わらない・次の刻みも計算"
106 : "変わらない";
107
108 return std::format(" → {} {} {}\n", block, FormatCoordinates(BlockCoordinates(block)), state);
109 }
110
111 std::string FormatRoot(const TickTree& tree, uint32_t root, uint64_t tick,
112 const std::set<uint32_t>& changedBefore, bool previousTraced) {
113 std::string origin;
114 if (const auto poked = tree.pokes.find(root); poked != tree.pokes.end()) {
115 origin = "つつき";
116 for (const uint32_t cell : poked->second)
117 origin += " セル " + FormatCoordinates(CellCoordinates(cell));
118 } else if (changedBefore.contains(root))
119 origin = std::format("刻み {} で変わった(か、まだ進めた)", tick - 1);
120 else
121 origin = previousTraced ? "(前の刻みで変わった記録なし。箱の外から)" : "(前の刻みは記録していない)";
122
123 const auto self = tree.selfWakes.find(root);
124 const std::string repeat = self != tree.selfWakes.end() && self->second > 1
125 ? std::format(" ×{}", self->second)
126 : "";
127
128 return std::format(" 根 {} {}: {}{}\n", root, FormatCoordinates(BlockCoordinates(root)), origin, repeat);
129 }
130
131 std::string FormatTick(uint64_t tick, const TickTree& tree, const std::set<uint32_t>& changedBefore,
132 bool previousTraced, size_t recordCount) {
133 const auto changed = rng::count_if(
134 tree.conducted, [](const auto& entry) { return (entry.second & PROBE_BLOCK_FLAG_CHANGED) != 0; });
135 std::string text = std::format("刻み {}: 根 {}・計算 {} ブロック(変わった {})・記録 {}\n", tick,
136 tree.roots.size(), tree.conducted.size(), changed, recordCount);
137
138 // --- 根ごとに、自分が親の子 ---
139 for (const uint32_t root : tree.roots) {
140 text += FormatRoot(tree, root, tick, changedBefore, previousTraced);
141 for (const auto& [block, parent] : tree.parents) {
142 if (parent == root)
143 text += FormatChild(tree, block);
144 }
145 }
146
147 // --- 起こした記録が無いのに計算したもの(範囲の決め方では起きないはず)---
148 bool orphanHeader = false;
149 for (const auto& [block, flags] : tree.conducted) {
150 if (tree.parents.contains(block))
151 continue;
152
153 if (!orphanHeader)
154 text += " (起こした記録なし)\n";
155
156 orphanHeader = true;
157 text += FormatChild(tree, block);
158 }
159
160 return text;
161 }
162
163 // ブロック block の中で食い違うセルの数。最初のもの(ブロックの中の z, y, x の順)を first に
164 // セル index の抽出の語(温度と見る物質 3 つ)が全部同じか
165 bool SameCellWords(std::span<const uint32_t> gpuCells, std::span<const uint32_t> cpuCells, uint32_t index) {
166 const size_t base = size_t{index} * PROBE_EXTRACTION_CELL_WORDS;
167
168 return rng::equal(gpuCells.subspan(base, PROBE_EXTRACTION_CELL_WORDS),
169 cpuCells.subspan(base, PROBE_EXTRACTION_CELL_WORDS));
170 }
171
172 uint32_t CountBlockDifferences(uint32_t block, std::span<const uint32_t> gpuCells,
173 std::span<const uint32_t> cpuCells, Coordinates& first) {
174 constexpr uint32_t CELLS_PER_BLOCK = PROBE_BLOCK_SIZE * PROBE_BLOCK_SIZE * PROBE_BLOCK_SIZE;
175 const Coordinates origin = BlockCoordinates(block);
176 uint32_t differences = 0;
177
178 for (uint32_t local = 0; local < CELLS_PER_BLOCK; ++local) {
179 const Coordinates cell = {origin[0] * PROBE_BLOCK_SIZE + local % PROBE_BLOCK_SIZE,
180 origin[1] * PROBE_BLOCK_SIZE + (local / PROBE_BLOCK_SIZE) % PROBE_BLOCK_SIZE,
181 origin[2] * PROBE_BLOCK_SIZE + local / (PROBE_BLOCK_SIZE * PROBE_BLOCK_SIZE)};
182 const uint32_t index = ProbeCellIndex(cell[0], cell[1], cell[2]);
183 if (SameCellWords(gpuCells, cpuCells, index))
184 continue;
185
186 if (differences == 0)
187 first = cell;
188
189 ++differences;
190 }
191
192 return differences;
193 }
194
195 std::string FormatRecord(const GraphTraceRecord& record) {
196 const char* kind = record.kind == PROBE_TRACE_POKE ? "つつき"
197 : record.kind == PROBE_TRACE_WAKE ? "起こす"
198 : record.kind == PROBE_TRACE_CONDUCT ? "計算"
199 : "?";
200
201 return std::format("刻み {} {} 主 {} 従 {}", record.tick, kind, record.subject, record.object);
202 }
203
204 } // namespace
205
206 gpu::GraphTraceFilter ProbeTraceFilterForCells(uint64_t tickBegin, uint64_t tickEnd,
207 std::array<uint32_t, 3> cellMin, std::array<uint32_t, 3> cellMax,
208 uint32_t capacity) {
209 GraphTraceFilter filter{.enabled = true, .capacity = capacity, .tickBegin = tickBegin, .tickEnd = tickEnd};
210 for (uint32_t axis = 0; axis < 3; ++axis) {
211 const uint32_t last = std::min(cellMax[axis], PROBE_GRID_SIZE);
212 filter.boxMin[axis] = std::min(cellMin[axis], last) / PROBE_BLOCK_SIZE;
213 filter.boxMax[axis] = (last + PROBE_BLOCK_SIZE - 1) / PROBE_BLOCK_SIZE; // セルを含むブロックまで
214 }
215
216 return filter;
217 }
218
219 std::string FormatProbeTrace(std::span<const gpu::GraphTraceRecord> sorted) {
220 std::string text;
221 std::set<uint32_t> changedBefore; // 前の刻みで値が変わったブロック(記録にあるもの)
222 std::optional<uint64_t> previousTick;
223
224 for (size_t begin = 0; begin < sorted.size();) {
225 const uint64_t tick = sorted[begin].tick;
226 size_t end = begin;
227 while (end < sorted.size() && sorted[end].tick == tick)
228 ++end;
229
230 const std::span<const GraphTraceRecord> records = sorted.subspan(begin, end - begin);
231 TickTree tree;
232 BuildTickTree(records, tree);
233 const bool previousTraced = previousTick == tick - 1;
234 if (!previousTraced)
235 changedBefore.clear();
236
237 text += FormatTick(tick, tree, changedBefore, previousTraced, records.size());
238
239 changedBefore.clear();
240 for (const auto& [block, flags] : tree.conducted) {
241 if (flags != 0)
242 changedBefore.insert(block); // 変わったか、まだ進めた(次の刻みの根)
243 }
244
245 previousTick = tick;
246 begin = end;
247 }
248
249 return text;
250 }
251
252 std::expected<void, std::string> WriteProbeTraceFile(const fs::path& path,
253 std::vector<gpu::GraphTraceRecord> records,
254 const gpu::GraphTraceFilter& filter, uint64_t droppedCount) {
255 gpu::SortGraphTrace(records);
256 std::ofstream file(path, std::ios::binary);
257 if (!file)
258 return std::unexpected("トレースのファイルを開けない");
259
260 // --- 見出し: 範囲(場所はブロックの座標)と、欠けているかどうか ---
261 file << "# 伝導の連鎖のトレース(T-0087。読み方は engine/src/sim/probe_trace.h)\n"
262 << std::format("# 刻み [{}, {})・ブロックの箱 [{}, {}, {}] 〜 [{}, {}, {})・記録 {} 件", filter.tickBegin,
263 filter.tickEnd, filter.boxMin[0], filter.boxMin[1], filter.boxMin[2], filter.boxMax[0],
264 filter.boxMax[1], filter.boxMax[2], records.size())
265 << (droppedCount > 0 ? std::format("・容量を越えて落とした {} 件(欠けている)\n", droppedCount) : "\n")
266 << FormatProbeTrace(records);
267
268 if (!file)
269 return std::unexpected("トレースのファイルに書けない");
270
271 return {};
272 }
273
274 std::vector<gpu::GraphTraceRecord> UniqueProbeTrace(std::vector<gpu::GraphTraceRecord> records) {
275 gpu::SortGraphTrace(records);
276 const auto duplicates = rng::unique(records);
277 records.erase(duplicates.begin(), duplicates.end());
278
279 return records;
280 }
281
282 void AppendExpectedProbeTrace(const gpu::GraphTraceFilter& filter, uint64_t tick,
283 std::span<const ProbeCommand> commands, std::span<const uint8_t> flagsBefore,
284 std::span<const uint8_t> flagsAfter, std::vector<gpu::GraphTraceRecord>& expected) {
285 if (!WantsTick(filter, tick))
286 return;
287
288 // --- つつき(適用の単位と同じく、格子の中のものだけ)→ 根になる ---
289 std::vector<uint8_t> seeds(flagsBefore.begin(), flagsBefore.end());
290 for (const ProbeCommand& command : commands) {
291 const uint32_t x = command.payload[0];
292 const uint32_t y = command.payload[1];
293 const uint32_t z = command.payload[2];
294 if (command.targetTick != tick || command.type != PROBE_COMMAND_TYPE_POKE || x >= PROBE_GRID_SIZE ||
295 y >= PROBE_GRID_SIZE || z >= PROBE_GRID_SIZE) {
296 continue;
297 }
298
299 const uint32_t block = ProbeBlockOfCell(x, y, z);
300 seeds[block] = 1;
301 if (WantsBlock(filter, block))
302 expected.push_back(
303 {.tick = tick, .kind = PROBE_TRACE_POKE, .subject = block, .object = ProbeCellIndex(x, y, z)});
304 }
305
306 // --- 根が起こす(自分と 6 面の隣)→ 起こされたブロックを計算する ---
307 std::vector<uint8_t> scheduled(PROBE_BLOCK_COUNT, 0);
308 for (uint32_t source = 0; source < PROBE_BLOCK_COUNT; ++source) {
309 if (seeds[source] == 0)
310 continue;
311
312 const bool sourceWanted = WantsBlock(filter, source);
313 for (const uint32_t target : BlockAndNeighbors(source)) {
314 scheduled[target] = 1;
315 if (sourceWanted || WantsBlock(filter, target))
316 expected.push_back({.tick = tick, .kind = PROBE_TRACE_WAKE, .subject = source, .object = target});
317 }
318 }
319
320 for (uint32_t block = 0; block < PROBE_BLOCK_COUNT; ++block) {
321 if (scheduled[block] != 0 && WantsBlock(filter, block)) {
322 expected.push_back(
323 {.tick = tick, .kind = PROBE_TRACE_CONDUCT, .subject = block, .object = flagsAfter[block]});
324 }
325 }
326 }
327
328 std::optional<std::string> FirstProbeTraceMismatch(std::span<const gpu::GraphTraceRecord> gpu,
329 std::span<const gpu::GraphTraceRecord> cpu) {
330 const auto [gpuAt, cpuAt] = rng::mismatch(gpu, cpu);
331 if (gpuAt == gpu.end() && cpuAt == cpu.end())
332 return std::nullopt;
333
334 const std::string gpuText = gpuAt == gpu.end() ? "(終わり)" : FormatRecord(*gpuAt);
335 const std::string cpuText = cpuAt == cpu.end() ? "(終わり)" : FormatRecord(*cpuAt);
336
337 return std::format("トレースの {} 件目で食い違った: GPU {} / CPU {}", gpuAt - gpu.begin(), gpuText, cpuText);
338 }
339
340 std::optional<uint64_t> FirstDivergentTick(std::span<const ProbeTickHash> gpu, std::span<const ProbeTickHash> cpu) {
341 for (const ProbeTickHash& gpuTick : gpu) {
342 const auto cpuTick = rng::find(cpu, gpuTick.tick, &ProbeTickHash::tick);
343 if (cpuTick == cpu.end())
344 continue;
345
346 if (cpuTick->hash != gpuTick.hash || cpuTick->energy != gpuTick.energy)
347 return gpuTick.tick;
348 }
349
350 return std::nullopt;
351 }
352
353 std::optional<ProbeDivergence> FindCellDivergence(uint64_t tick, std::span<const uint32_t> gpuCells,
354 std::span<const uint32_t> cpuCells) {
355 constexpr size_t WORDS = size_t{PROBE_CELL_COUNT} * PROBE_EXTRACTION_CELL_WORDS;
356 if (gpuCells.size() < WORDS || cpuCells.size() < WORDS)
357 return std::nullopt;
358
359 std::optional<ProbeDivergence> divergence;
360 uint32_t differingCells = 0;
361 uint32_t differingBlocks = 0;
362
363 // ブロックの番号の順に。最初に食い違ったブロックの、最初のセルを残す
364 for (uint32_t block = 0; block < PROBE_BLOCK_COUNT; ++block) {
365 Coordinates first{};
366 const uint32_t differences = CountBlockDifferences(block, gpuCells, cpuCells, first);
367 if (differences == 0)
368 continue;
369
370 differingCells += differences;
371 ++differingBlocks;
372 if (!divergence) {
373 const uint32_t index = ProbeCellIndex(first[0], first[1], first[2]);
374 divergence = ProbeDivergence{.tick = tick,
375 .block = block,
376 .cell = first,
377 .gpuValue = gpuCells[size_t{index} * PROBE_EXTRACTION_CELL_WORDS],
378 .cpuValue = cpuCells[size_t{index} * PROBE_EXTRACTION_CELL_WORDS]};
379 }
380 }
381
382 if (divergence) {
383 divergence->differingCells = differingCells;
384 divergence->differingBlocks = differingBlocks;
385 }
386
387 return divergence;
388 }
389
390 std::string FormatProbeDivergence(const ProbeDivergence& divergence) {
391 return std::format(
392 "S({}) で CPU リファレンスと食い違った: 最初のブロック {} {}・セル {} 温度 GPU {} CPU {} mK"
393 "(食い違ったセル {}・ブロック {})",
394 divergence.tick, divergence.block, FormatCoordinates(BlockCoordinates(divergence.block)),
395 FormatCoordinates(divergence.cell), divergence.gpuValue, divergence.cpuValue, divergence.differingCells,
396 divergence.differingBlocks);
397 }
398
399} // namespace bicameral::sim
void SortGraphTrace(std::vector< GraphTraceRecord > &records)
bool SameCellWords(std::span< const uint32_t > gpuCells, std::span< const uint32_t > cpuCells, uint32_t index)
bool WantsBlock(const GraphTraceFilter &filter, uint32_t block)
void BuildTickTree(std::span< const GraphTraceRecord > records, TickTree &tree)
uint32_t CountBlockDifferences(uint32_t block, std::span< const uint32_t > gpuCells, std::span< const uint32_t > cpuCells, Coordinates &first)
std::string FormatRecord(const GraphTraceRecord &record)
bool WantsTick(const GraphTraceFilter &filter, uint64_t tick)
std::vector< uint32_t > BlockAndNeighbors(uint32_t block)
std::string FormatCoordinates(const Coordinates &coordinates)
std::string FormatTick(uint64_t tick, const TickTree &tree, const std::set< uint32_t > &changedBefore, bool previousTraced, size_t recordCount)
std::string FormatChild(const TickTree &tree, uint32_t block)
std::string FormatRoot(const TickTree &tree, uint32_t root, uint64_t tick, const std::set< uint32_t > &changedBefore, bool previousTraced)
std::optional< ProbeDivergence > FindCellDivergence(uint64_t tick, std::span< const uint32_t > gpuCells, std::span< const uint32_t > cpuCells)
std::optional< uint64_t > FirstDivergentTick(std::span< const ProbeTickHash > gpu, std::span< const ProbeTickHash > cpu)
void AppendExpectedProbeTrace(const gpu::GraphTraceFilter &filter, uint64_t tick, std::span< const ProbeCommand > commands, std::span< const uint8_t > flagsBefore, std::span< const uint8_t > flagsAfter, std::vector< gpu::GraphTraceRecord > &expected)
std::string FormatProbeDivergence(const ProbeDivergence &divergence)
std::vector< gpu::GraphTraceRecord > UniqueProbeTrace(std::vector< gpu::GraphTraceRecord > records)
std::string FormatProbeTrace(std::span< const gpu::GraphTraceRecord > sorted)
std::optional< std::string > FirstProbeTraceMismatch(std::span< const gpu::GraphTraceRecord > gpu, std::span< const gpu::GraphTraceRecord > cpu)
Command ProbeCommand
Definition probe_sim.h:53
std::expected< void, std::string > WriteProbeTraceFile(const fs::path &path, std::vector< gpu::GraphTraceRecord > records, const gpu::GraphTraceFilter &filter, uint64_t droppedCount)
gpu::GraphTraceFilter ProbeTraceFilterForCells(uint64_t tickBegin, uint64_t tickEnd, std::array< uint32_t, 3 > cellMin, std::array< uint32_t, 3 > cellMax, uint32_t capacity)
std::array< uint32_t, 3 > boxMax
Definition graph_trace.h:43
std::array< uint32_t, 3 > boxMin
Definition graph_trace.h:42
std::array< uint32_t, 3 > cell
Definition probe_trace.h:75
std::map< uint32_t, std::vector< uint32_t > > pokes