Bicameral Engine
GPU-resident engine for a magic-research open-world game
Loading...
Searching...
No Matches
implicit_conduction.cpp
Go to the documentation of this file.
1// implicit_conduction.cpp — 細かいレベルの熱の陰解法の試作(T-0110 研究)。何をするかは implicit_conduction.h。
2// 1 刻みの順(StepImplicit):
3// 1. 温度 T0 をエネルギーから(mK × 2^16。1 mK より細かい端数も持つので、温度の丸めで反復が止まらない)
4// 2. 近似解 T*: 赤黒の掃き出し(Smooth)/ 木の多重格子の V サイクル(VCycle)。RKL2 は段ごとの面の流れを積算する(Rkl2Flows)
5// 3. 面の流れ F = g (T*_細かい側 − T*_粗い側)。収束していなくて温度が近所の範囲から出るセルは、面を陽解法の流れに戻す(LimitFlows)
6// 4. 流れを両側に足す(ApplyFlows。ADR-0017 の形なので保存はビット単位)
7// 数の幅(04 R8): 面の係数は 4^k で大きくなる(レベル 20 で 2^40 倍)ので 128bit で持ち、反復は「係数 ÷ 対角」の重み(Q48)だけを使う
8// (割り算は段を作る時に 1 回。T-0110 の注意「1/(C + Σc) を刻みの初めに 1 回」)。重みの積は 128bit の途中の値から戻す。
9// 多重格子の段(BuildImplicitGrid): 段 ℓ の最も細かいレベルの節を親のセル(レベル − 1、座標 ÷ 2)へ縮約する = 木の覆われた親のセル。
10// 親の熱容量 = 子の合計、親どうしの面の係数 = 子の面の係数の合計 ÷ 2(区分的に一定の縮約の Galerkin は 2 倍の強さになるので、
11// そのレベルで離散化し直した値に合わせる)。全部の節が「熱容量 ≥ 面の係数の合計」(陽解法でも頭打ちにならない基準のレベルの手前)に
12// なったら止める → V サイクルの深さ = 基準からのレベルの差。
14
15#include <algorithm>
16#include <array>
17#include <map>
18#include <optional>
19#include <tuple>
20
21#include "common/implicit_conduction.hlsli"
22#include "common/multires_conduction.hlsli"
23
24using namespace bicameral::fx;
25using namespace bicameral::multires;
26
27namespace bicameral::sim {
28
29 namespace {
30
31 constexpr uint32_t WEIGHT_SHIFT = IM_WEIGHT_SHIFT; // 式は common/implicit_conduction.hlsli(GPU と共通。T-0117)
32 constexpr uint32_t TEMPERATURE_SHIFT = IM_TEMPERATURE_SHIFT;
33 constexpr int64_t HALF_WEIGHT = int64_t{1} << (WEIGHT_SHIFT - 1);
34 constexpr uint32_t ENERGY_BITS_PER_LEVEL = IM_ENERGY_BITS_PER_LEVEL;
35 constexpr uint32_t STAGE_RATIO_SHIFT = 16; // RKL2 の段の数を決める比の Q
36 constexpr size_t MAX_GRID_LEVELS = 64;
37 constexpr uint8_t COLOR_COUNT = 2;
38 constexpr uint32_t CORRECTION_SCALE_SHIFT = IM_CORRECTION_SCALE_SHIFT; // ImplicitOptions::correctionScale の Q
39
40 constexpr std::array<std::array<int64_t, 3>, 6> FACE_OFFSETS = {
41 {{1, 0, 0}, {-1, 0, 0}, {0, 1, 0}, {0, -1, 0}, {0, 0, 1}, {0, 0, -1}}};
42
43 using CellKey = std::tuple<int32_t, int64_t, int64_t, int64_t>;
44
45 // --- 128bit の小さな道具(係数・対角。負にならない)---
46
47 FxU128 Wide(uint64_t value) {
48 return {.hi = 0, .lo = value};
49 }
50
51 bool IsZero(FxU128 value) {
52 return ImWideIsZero(value);
53 }
54
55 FxU128 WideAdd(FxU128 a, FxU128 b) {
56 return ImWideAdd(a, b);
57 }
58
59 FxU128 WideShiftRight(FxU128 value, uint32_t shift) {
60 return ImWideShiftRight(value, shift);
61 }
62
63 // 桁あふれは R8 の assert
64 FxU128 WideShiftLeft(FxU128 value, uint32_t shift) {
65 if (shift == 0 || IsZero(value))
66 return value;
67
68 FX_ASSERT(shift < 128);
69 if (shift >= 64) {
70 FX_ASSERT(value.hi == 0 && (shift == 64 || (value.lo >> (128 - shift)) == 0));
71 return {.hi = value.lo << (shift - 64), .lo = 0};
72 }
73
74 FX_ASSERT((value.hi >> (64 - shift)) == 0);
75
76 return {.hi = (value.hi << shift) | (value.lo >> (64 - shift)), .lo = value.lo << shift};
77 }
78
79 FxU128 WideMul(FxU128 a, uint64_t b) {
80 const FxU128 low = FxMulU64Full(a.lo, b);
81 const FxU128 high = FxMulU64Full(a.hi, b);
82 FX_ASSERT(high.hi == 0);
83 const FxU128 product = {.hi = low.hi + high.lo, .lo = low.lo};
84 FX_ASSERT(product.hi >= low.hi);
85
86 return product;
87 }
88
89 // 式は common/implicit_conduction.hlsli(GPU の段の組み立てと共通。T-0134)
90 int64_t Weight(FxU128 numerator, FxU128 denominator) {
91 return ImWeight(numerator, denominator);
92 }
93
94 int64_t ShiftRightSigned(int64_t value, uint32_t shift) {
95 return ImShiftRightSigned(value, shift);
96 }
97
98 int64_t FaceFlow(FxU128 coefficient, int64_t difference) {
99 return ImFaceFlow(coefficient, difference);
100 }
101
102 // --- 面を探す ---
103
104 std::map<CellKey, uint32_t> MakeCellIndex(const std::vector<ImplicitCell>& cells) {
105 std::map<CellKey, uint32_t> index;
106 for (uint32_t i = 0; i < cells.size(); ++i) {
107 const ImplicitCell& cell = cells[i];
108 FX_ASSERT(cell.level >= 0 && cell.x >= 0 && cell.y >= 0 && cell.z >= 0 && cell.heatCapacity != 0);
109 const bool added = index.emplace(CellKey{cell.level, cell.x, cell.y, cell.z}, i).second;
110 FX_ASSERT(added);
111 }
112
113 return index;
114 }
115
116 struct Containing {
117 uint32_t index = 0;
118 uint32_t gap = 0;
119 };
120
121 // レベル level の座標 (x, y, z) のセルを含む、レベル level 以下のセル(細かいセルに覆われていれば無い)
122 std::optional<Containing> FindContaining(const std::map<CellKey, uint32_t>& index, int32_t level, int64_t x,
123 int64_t y, int64_t z) {
124 for (int32_t gap = 0; gap <= level; ++gap) {
125 const auto found = index.find(CellKey{level - gap, x >> gap, y >> gap, z >> gap});
126 if (found != index.end())
127 return Containing{.index = found->second, .gap = static_cast<uint32_t>(gap)};
128 }
129
130 return std::nullopt;
131 }
132
133 FxU128 FaceCoefficient(const ImplicitCell& fine, const ImplicitCell& coarse) {
134 const uint64_t conductance = std::min(fine.conductance, coarse.conductance);
135
136 return WideShiftLeft(Wide(conductance * HC_LIMIT_PER_CONDUCTANCE), 2 * static_cast<uint32_t>(fine.level));
137 }
138
140 const std::map<CellKey, uint32_t> index = MakeCellIndex(grid.cells);
141 for (uint32_t i = 0; i < grid.cells.size(); ++i) {
142 const ImplicitCell& cell = grid.cells[i];
143 for (const auto& offset : FACE_OFFSETS) {
144 const int64_t x = cell.x + offset[0];
145 const int64_t y = cell.y + offset[1];
146 const int64_t z = cell.z + offset[2];
147 if (x < 0 || y < 0 || z < 0)
148 continue;
149
150 const std::optional<Containing> neighbor = FindContaining(index, cell.level, x, y, z);
151 if (!neighbor || (neighbor->gap == 0 && neighbor->index < i))
152 continue;
153
154 const ImplicitCell& other = grid.cells[neighbor->index];
155 grid.faces.push_back({.fine = i,
156 .coarse = neighbor->index,
157 .gap = neighbor->gap,
158 .coefficient = FaceCoefficient(cell, other)});
159 }
160 }
161 }
162
163 // --- 多重格子の段 ---
164
165 struct RowEntry {
166 uint32_t neighbor = 0;
167 FxU128 coefficient{};
168 };
169
170 using Rows = std::vector<std::vector<RowEntry>>;
171
172 void AddToRow(std::vector<RowEntry>& row, uint32_t neighbor, FxU128 coefficient) {
173 for (RowEntry& entry : row) {
174 if (entry.neighbor != neighbor)
175 continue;
176
177 entry.coefficient = WideAdd(entry.coefficient, coefficient);
178 return;
179 }
180
181 row.push_back({.neighbor = neighbor, .coefficient = coefficient});
182 }
183
184 void PushNode(ImplicitGridLevel& level, const CellKey& key, FxU128 capacity) {
185 const auto [nodeLevel, x, y, z] = key;
186 level.levels.push_back(nodeLevel);
187 level.x.push_back(x);
188 level.y.push_back(y);
189 level.z.push_back(z);
190 level.colors.push_back(static_cast<uint8_t>((x + y + z) & 1));
191 level.capacities.push_back(capacity);
192 }
193
194 // 行から対角・重み・隣の一覧を作る
195 void FinishLevel(ImplicitGridLevel& level, const Rows& rows) {
196 const size_t count = level.capacities.size();
197 level.diagonals.resize(count);
198 level.selfWeights.resize(count);
199 level.rowStarts.assign(1, 0);
200 for (size_t i = 0; i < count; ++i) {
201 FxU128 diagonal = level.capacities[i];
202 for (const RowEntry& entry : rows[i])
203 diagonal = WideAdd(diagonal, entry.coefficient);
204
205 level.diagonals[i] = diagonal;
206 level.selfWeights[i] = Weight(level.capacities[i], diagonal);
207 for (const RowEntry& entry : rows[i]) {
208 level.neighbors.push_back(entry.neighbor);
209 level.weights.push_back(Weight(entry.coefficient, diagonal));
210 level.coefficients.push_back(entry.coefficient);
211 }
212
213 level.rowStarts.push_back(static_cast<uint32_t>(level.neighbors.size()));
214 }
215 }
216
217 // 段 0: セルそのもの。粗い側の行の係数は細かい側の係数 ÷ 8^d(粗い側の単位)
219 ImplicitGridLevel level;
220 for (const ImplicitCell& cell : grid.cells)
221 PushNode(level, CellKey{cell.level, cell.x, cell.y, cell.z}, Wide(cell.heatCapacity));
222
223 Rows rows(grid.cells.size());
224 for (const ImplicitFace& face : grid.faces) {
225 rows[face.fine].push_back({.neighbor = face.coarse, .coefficient = face.coefficient});
226 rows[face.coarse].push_back(
227 {.neighbor = face.fine,
228 .coefficient = WideShiftRight(face.coefficient, ENERGY_BITS_PER_LEVEL * face.gap)});
229 }
230
231 FinishLevel(level, rows);
232
233 return level;
234 }
235
236 bool NeedsCoarser(const ImplicitGridLevel& level) {
237 return std::ranges::any_of(level.selfWeights, [](int64_t weight) { return weight < HALF_WEIGHT; });
238 }
239
240 // fine の最も細かいレベルの節を親のセルへ縮約した段を作り、fine の parents・restrictWeights を埋める
242 const int32_t finest = *std::ranges::max_element(fine.levels);
243 const size_t count = fine.levels.size();
244 ImplicitGridLevel coarse;
245 std::map<CellKey, uint32_t> index;
246 std::vector<uint8_t> lowered; // 親の節ごと: 縮約してできた節か
247 fine.parents.resize(count);
248 for (size_t i = 0; i < count; ++i) {
249 const bool lower = fine.levels[i] == finest;
250 const CellKey key = lower ? CellKey{finest - 1, fine.x[i] >> 1, fine.y[i] >> 1, fine.z[i] >> 1}
251 : CellKey{fine.levels[i], fine.x[i], fine.y[i], fine.z[i]};
252 const auto [found, added] = index.emplace(key, static_cast<uint32_t>(coarse.levels.size()));
253 if (added) {
254 PushNode(coarse, key, Wide(0));
255 lowered.push_back(lower ? 1 : 0);
256 }
257
258 const uint32_t parent = found->second;
259 const uint32_t shift = lower ? ENERGY_BITS_PER_LEVEL : 0;
260 fine.parents[i] = parent;
261 coarse.capacities[parent] = WideAdd(coarse.capacities[parent],
262 WideShiftRight(fine.capacities[i], shift));
263 }
264
265 Rows rows(coarse.levels.size());
266 for (size_t i = 0; i < count; ++i) {
267 const uint32_t parent = fine.parents[i];
268 const uint32_t shift = lowered[parent] != 0 ? ENERGY_BITS_PER_LEVEL : 0;
269 for (uint32_t k = fine.rowStarts[i]; k < fine.rowStarts[i + 1]; ++k) {
270 const uint32_t other = fine.parents[fine.neighbors[k]];
271 if (other != parent)
272 AddToRow(rows[parent], other, WideShiftRight(fine.coefficients[k], shift));
273 }
274 }
275
276 for (size_t parent = 0; parent < rows.size(); ++parent) {
277 for (RowEntry& entry : rows[parent]) {
278 if (!galerkin && (lowered[parent] != 0 || lowered[entry.neighbor] != 0))
279 entry.coefficient = WideShiftRight(entry.coefficient, 1);
280 }
281 }
282
283 FinishLevel(coarse, rows);
284 fine.restrictWeights.resize(count);
285 for (size_t i = 0; i < count; ++i) {
286 const uint32_t shift = lowered[fine.parents[i]] != 0 ? ENERGY_BITS_PER_LEVEL : 0;
287 fine.restrictWeights[i] = Weight(WideShiftRight(fine.diagonals[i], shift),
288 coarse.diagonals[fine.parents[i]]);
289 }
290
291 return coarse;
292 }
293
294 // --- 反復 ---
295
296 // 右辺 + Σ 隣の重み × 値
297 int64_t Relax(const ImplicitGridLevel& level, const std::vector<int64_t>& rhs,
298 const std::vector<int64_t>& values, size_t node) {
299 int64_t sum = rhs[node];
300 for (uint32_t k = level.rowStarts[node]; k < level.rowStarts[node + 1]; ++k)
301 sum += FxMulShiftS64(level.weights[k], values[level.neighbors[k]], WEIGHT_SHIFT);
302
303 return sum;
304 }
305
306 // 1 色ぶん: 全部の節を掃き出しの初めの値から計算してから書く
307 void SweepColor(const ImplicitGridLevel& level, const std::vector<int64_t>& rhs, uint8_t color,
308 std::vector<int64_t>& values, std::vector<int64_t>& next) {
309 for (size_t i = 0; i < values.size(); ++i) {
310 if (level.colors[i] == color)
311 next[i] = Relax(level, rhs, values, i);
312 }
313
314 for (size_t i = 0; i < values.size(); ++i) {
315 if (level.colors[i] == color)
316 values[i] = next[i];
317 }
318 }
319
320 // 赤黒の掃き出し。同じ色の隣(違うレベルの面ではありうる)は掃き出しの初めの値を読む(順に依存しない)
321 void Smooth(const ImplicitGridLevel& level, const std::vector<int64_t>& rhs, std::vector<int64_t>& values,
322 uint32_t sweeps, ImplicitCost& cost) {
323 std::vector<int64_t> next(values.size());
324 for (uint32_t sweep = 0; sweep < sweeps; ++sweep) {
325 for (uint8_t color = 0; color < COLOR_COUNT; ++color)
326 SweepColor(level, rhs, color, values, next);
327
328 cost.passes += COLOR_COUNT;
329 cost.cellUpdates += values.size();
330 }
331 }
332
333 void VCycle(const ImplicitGrid& grid, size_t depth, const std::vector<int64_t>& rhs,
334 std::vector<int64_t>& values, const ImplicitOptions& options, ImplicitCost& cost) {
335 const ImplicitGridLevel& level = grid.levels[depth];
336 if (depth + 1 == grid.levels.size()) {
337 Smooth(level, rhs, values, options.coarsestSweeps, cost);
338 return;
339 }
340
341 Smooth(level, rhs, values, options.preSmooth, cost);
342
343 // --- 残差を親へ(自分の D で重み付けた和 ÷ 親の D)---
344 const ImplicitGridLevel& coarse = grid.levels[depth + 1];
345 std::vector<int64_t> coarseRhs(coarse.levels.size(), 0);
346 for (size_t i = 0; i < values.size(); ++i) {
347 const int64_t residual = Relax(level, rhs, values, i) - values[i];
348 coarseRhs[level.parents[i]] += FxMulShiftS64(level.restrictWeights[i], residual, WEIGHT_SHIFT);
349 }
350
351 cost.passes += 1;
352 cost.cellUpdates += values.size();
353
354 // --- 親で直しを解いて、子へそのまま足す ---
355 std::vector<int64_t> correction(coarse.levels.size(), 0);
356 VCycle(grid, depth + 1, coarseRhs, correction, options, cost);
357 for (size_t i = 0; i < values.size(); ++i)
358 values[i] += FxMulShiftS64(options.correctionScale, correction[level.parents[i]],
360
361 cost.passes += 1;
362 Smooth(level, rhs, values, options.postSmooth, cost);
363 }
364
365 // 新しい温度の誤差の見込み = 残差(重みで割った形。温度の単位)× D/C が、全部のセルで tolerance 以下か。
366 // 流れ F = g ΔT* で足すので、T* の残差は D/C ≈ 4^Δk 倍になって新しい温度に出る(T-0110 の計測)
367 bool Converged(const ImplicitGridLevel& cells, const std::vector<int64_t>& rhs,
368 const std::vector<int64_t>& values, uint32_t toleranceMillikelvin, ImplicitCost& cost) {
369 bool converged = true;
370 for (size_t i = 0; i < values.size(); ++i) {
371 if (ImExceedsTolerance(Relax(cells, rhs, values, i) - values[i], cells.selfWeights[i],
372 toleranceMillikelvin))
373 converged = false;
374 }
375
376 cost.passes += 1;
377 cost.cellUpdates += values.size();
378
379 return converged;
380 }
381
382 // --- 面の流れ ---
383
384 std::vector<int64_t> Temperatures(const ImplicitGrid& grid) {
385 std::vector<int64_t> temperatures(grid.cells.size());
386 for (size_t i = 0; i < grid.cells.size(); ++i)
387 temperatures[i] = grid.cells[i].startTemperature >= 0 ? grid.cells[i].startTemperature
388 : ImplicitTemperature(grid.cells[i]);
389
390 return temperatures;
391 }
392
393 std::vector<int64_t> Flows(const ImplicitGrid& grid, const std::vector<int64_t>& temperatures) {
394 std::vector<int64_t> flows(grid.faces.size());
395 for (size_t f = 0; f < grid.faces.size(); ++f) {
396 const ImplicitFace& face = grid.faces[f];
397 flows[f] = FaceFlow(face.coefficient, temperatures[face.fine] - temperatures[face.coarse]);
398 }
399
400 return flows;
401 }
402
403 // 流れを両側に(同じレベル: 逆向きに同じ値 / 違うレベル: 粗い側は整数部 + 端数。MrSplitCrossFlow)
404 void ApplyFlows(ImplicitGrid& grid, const std::vector<int64_t>& flows) {
405 for (size_t f = 0; f < grid.faces.size(); ++f) {
406 const ImplicitFace& face = grid.faces[f];
407 ImplicitCell& fine = grid.cells[face.fine];
408 ImplicitCell& coarse = grid.cells[face.coarse];
409 if (face.gap == 0) {
410 fine.energy -= flows[f];
411 coarse.energy += flows[f];
412 continue;
413 }
414
415 const MrCrossTransfer transfer = MrSplitCrossFlow(flows[f], face.gap, coarse.coarseFraction);
416 const MrEnergyDelta sum = MrAddEnergyDelta({.whole = coarse.energy, .fraction = coarse.fraction},
417 transfer.coarseDelta);
418 fine.energy += transfer.fineDelta;
419 coarse.energy = sum.whole;
420 coarse.fraction = sum.fraction;
421 }
422 }
423
424 // --- 安全網: 近似が収束していない時に、温度が刻みの初めの範囲から出るセルの面を陽解法の流れに戻す ---
425 // 範囲 = 全部のセルの T0 の最小〜最大(上は 1 mK の切り上げ)。新しい最低・最高の温度(負のエネルギー・行き過ぎ)は「嘘」なので止める。
426 // 近所の範囲(自分と隣)にすると、細かいレベルでは D/C ≈ 4^Δk 倍に増幅された反復の残差(0.1 mK 程度)で外に出て、
427 // 安全網が連鎖して全部が陽解法(= 遅い)に戻ってしまった(T-0110 の計測)。局所の小さな揺れは精度の問題として物差しで見る。外に出たセルは、
428 // 全部の面を陽解法の流れ(係数を熱容量の上限で頭打ち。T-0019 と同じ。1 面 ≤ C/8 なので 6 面でも凸結合 → 範囲の中)にする。
429 // 面を戻すと隣の合計も変わるので、外に出るセルが無くなるまで繰り返す(戻したセルは二度と外に出ない → 必ず終わる)。
430 // 1 回ごとに「全部のセルを見てから、全部の面を直す」ので順に依存しない。保存は面ごとに 1 つの値なのでビット単位のまま。
431
432 int64_t AddClamped(int64_t a, int64_t b) {
433 return ImAddClamped(a, b);
434 }
435
436 // 陽解法の面の係数(細かい側の単位)= min(係数, 細かい側の C/8, 粗い側の C/8 × 2^d)(ADR-0017)
437 std::vector<int64_t> ExplicitFlows(const ImplicitGrid& grid, const std::vector<int64_t>& start) {
438 std::vector<int64_t> flows(grid.faces.size());
439 for (size_t f = 0; f < grid.faces.size(); ++f) {
440 const ImplicitFace& face = grid.faces[f];
441 const FxU128 coefficient = ImExplicitCoefficient(face.coefficient, grid.cells[face.fine].heatCapacity,
442 grid.cells[face.coarse].heatCapacity, face.gap);
443 flows[f] = FaceFlow(coefficient, start[face.fine] - start[face.coarse]);
444 }
445
446 return flows;
447 }
448
449 struct Bounds {
450 std::vector<int64_t> lowest; // そのセルの単位のエネルギー
451 std::vector<int64_t> highest;
452 std::vector<int64_t> slack; // この量までは外に出てよい
453 };
454
455 // T* は範囲に入れない(多重格子の直しは単調でなく、T* が T0 の範囲の外に出ることがある)
456 Bounds MakeBounds(const ImplicitGrid& grid, const std::vector<int64_t>& start, int64_t slackMillikelvin) {
457 const auto [lowestStart, highestStart] = std::ranges::minmax(start);
458 const int64_t lowest = lowestStart >> TEMPERATURE_SHIFT;
459 const int64_t highest = (highestStart >> TEMPERATURE_SHIFT) + 1;
460 Bounds bounds;
461 bounds.lowest.resize(grid.cells.size());
462 bounds.highest.resize(grid.cells.size());
463 bounds.slack.resize(grid.cells.size());
464 for (size_t i = 0; i < grid.cells.size(); ++i) {
465 const uint64_t capacity = grid.cells[i].heatCapacity;
466 bounds.lowest[i] = ImplicitEnergyFor(capacity, lowest);
467 bounds.highest[i] = ImplicitEnergyFor(capacity, highest);
468 bounds.slack[i] = ImplicitEnergyFor(capacity, slackMillikelvin);
469 }
470
471 return bounds;
472 }
473
474 // 流れを足した後のエネルギー(粗い側は整数部だけ。範囲の判定用)
475 std::vector<int64_t> EnergiesAfter(const ImplicitGrid& grid, const std::vector<int64_t>& flows) {
476 std::vector<int64_t> energies(grid.cells.size());
477 for (size_t i = 0; i < grid.cells.size(); ++i)
478 energies[i] = grid.cells[i].energy;
479
480 for (size_t f = 0; f < grid.faces.size(); ++f) {
481 const ImplicitFace& face = grid.faces[f];
482 energies[face.fine] = AddClamped(energies[face.fine], -flows[f]);
483 energies[face.coarse] = AddClamped(energies[face.coarse],
484 ShiftRightSigned(flows[f], ENERGY_BITS_PER_LEVEL * face.gap));
485 }
486
487 return energies;
488 }
489
490 // 範囲(余裕を含まない)を超えた量。中なら 0 以下
491 int64_t Overrun(int64_t energy, const Bounds& bounds, size_t i) {
492 return ImOverrun(energy, bounds.lowest[i], bounds.highest[i]);
493 }
494
495 // 範囲の外に出たセルに印を付ける(新しく付けたら true)。最初の回は範囲を超えた最大も記録する
496 bool MarkOutside(const ImplicitGrid& grid, const Bounds& bounds, const std::vector<int64_t>& energies,
497 std::vector<uint8_t>& limited, ImplicitCost& cost) {
498 bool changed = false;
499 for (size_t i = 0; i < grid.cells.size(); ++i) {
500 const int64_t over = Overrun(energies[i], bounds, i);
501 if (cost.limitRounds == 0) {
502 const int64_t excess = ImExcessMillikelvin(over, grid.cells[i].heatCapacity);
503 cost.worstExcessMillikelvin = std::max(cost.worstExcessMillikelvin, excess);
504 }
505
506 if (limited[i] != 0 || over <= bounds.slack[i])
507 continue;
508
509 limited[i] = 1;
510 changed = true;
511 cost.limitedCells += 1;
512 }
513
514 return changed;
515 }
516
517 void LimitFlows(const ImplicitGrid& grid, const std::vector<int64_t>& start, int64_t slackMillikelvin,
518 std::vector<int64_t>& flows, ImplicitCost& cost) {
519 const Bounds bounds = MakeBounds(grid, start, slackMillikelvin);
520 std::vector<int64_t> explicitFlows;
521 std::vector<uint8_t> limited(grid.cells.size(), 0);
522 while (true) {
523 const bool changed = MarkOutside(grid, bounds, EnergiesAfter(grid, flows), limited, cost);
524 cost.passes += 1;
525 if (!changed)
526 return;
527
528 // --- 外に出たセルの面を陽解法の流れに ---
529 if (explicitFlows.empty())
530 explicitFlows = ExplicitFlows(grid, start);
531
532 for (size_t f = 0; f < grid.faces.size(); ++f) {
533 const ImplicitFace& face = grid.faces[f];
534 if (limited[face.fine] != 0 || limited[face.coarse] != 0)
535 flows[f] = explicitFlows[f];
536 }
537
538 cost.passes += 1;
539 cost.limitRounds += 1;
540 }
541 }
542
543 // --- RKL2(Meyer, Balsara, Aslam 2014)。段の値 Y_j = Y_0 + 面の流れの積算 Φ_j の発散で持ち、最後の Φ_s を流れとして渡す
544 // (Y の漸化式は Y_0 からの差が L(Y_i) の一次結合なので、Φ も同じ漸化式に従う)---
545
546 struct Rational {
547 uint64_t numerator = 0;
548 uint64_t denominator = 1;
549 };
550
551 // b_j = (j² + j − 2) / (2j(j + 1))(j < 2 は 1/3)
552 Rational Rkl2B(uint64_t j) {
553 if (j < 2)
554 return {.numerator = 1, .denominator = 3};
555
556 return {.numerator = (j * j) + j - 2, .denominator = 2 * j * (j + 1)};
557 }
558
559 struct Rkl2Stage {
560 int64_t mu = 0; // Q48
561 int64_t nu = 0;
562 int64_t muTilde = 0;
563 int64_t gammaTilde = 0;
564 };
565
566 int64_t RationalWeight(FxU128 numerator, FxU128 denominator, bool negative) {
567 const int64_t weight = Weight(numerator, denominator);
568
569 return negative ? -weight : weight;
570 }
571
572 std::vector<Rkl2Stage> Rkl2Stages(uint32_t stageCount) {
573 const uint64_t s = stageCount;
574 const uint64_t span = (s * s) + s - 2; // w1 = 4 / span
575 std::vector<Rkl2Stage> stages(stageCount + 1);
576 stages[1].muTilde = RationalWeight(Wide(4), Wide(3 * span), false);
577 for (uint64_t j = 2; j <= s; ++j) {
578 const Rational b = Rkl2B(j);
579 const Rational previous = Rkl2B(j - 1);
580 const Rational before = Rkl2B(j - 2);
581 const FxU128 muNumerator = WideMul(WideMul(Wide((2 * j) - 1), b.numerator), previous.denominator);
582 const FxU128 muDenominator = WideMul(WideMul(Wide(j), b.denominator), previous.numerator);
583 Rkl2Stage& stage = stages[j];
584 stage.mu = RationalWeight(muNumerator, muDenominator, false);
585 stage.nu = RationalWeight(WideMul(WideMul(Wide(j - 1), b.numerator), before.denominator),
586 WideMul(WideMul(Wide(j), b.denominator), before.numerator), true);
587 stage.muTilde = RationalWeight(WideMul(muNumerator, 4), WideMul(muDenominator, span), false);
588
589 // γ̃_j = −a_{j−1} μ̃_j(a = 1 − b)
590 const FxU128 gammaNumerator = WideMul(WideMul(muNumerator, 4),
591 previous.denominator - previous.numerator);
592 const FxU128 gammaDenominator = WideMul(WideMul(muDenominator, span), previous.denominator);
593 stage.gammaTilde = RationalWeight(gammaNumerator, gammaDenominator, true);
594 }
595
596 return stages;
597 }
598
599 // 安定の条件 s² + s − 2 ≥ 4 max(Σ 面の係数 ÷ 熱容量)を満たす最小の s
600 uint32_t Rkl2StageCount(const ImplicitGridLevel& cells) {
601 int64_t worst = 0;
602 for (size_t i = 0; i < cells.capacities.size(); ++i) {
603 FxU128 sum = Wide(0);
604 for (uint32_t k = cells.rowStarts[i]; k < cells.rowStarts[i + 1]; ++k)
605 sum = WideAdd(sum, cells.coefficients[k]);
606
607 worst = std::max(worst, ImWideRatio(sum, cells.capacities[i], STAGE_RATIO_SHIFT));
608 }
609
610 uint64_t s = 2;
611 while ((((s * s) + s - 2) << STAGE_RATIO_SHIFT) < 4 * static_cast<uint64_t>(worst))
612 ++s;
613
614 return static_cast<uint32_t>(s);
615 }
616
617 // 面の流れの積算 → 温度(Y_0 + 発散 ÷ 熱容量)
618 std::vector<int64_t> StageTemperatures(const ImplicitGrid& grid, const std::vector<int64_t>& start,
619 const std::vector<int64_t>& accumulated) {
620 std::vector<int64_t> heat(grid.cells.size(), 0); // 自分の単位 × 2^16
621 for (size_t f = 0; f < grid.faces.size(); ++f) {
622 const ImplicitFace& face = grid.faces[f];
623 FX_ASSERT((FxAbsU64(accumulated[f]) >> (63 - TEMPERATURE_SHIFT)) == 0);
624 const int64_t scaled = accumulated[f] * (int64_t{1} << TEMPERATURE_SHIFT);
625 heat[face.fine] -= scaled;
626 heat[face.coarse] += ShiftRightSigned(scaled, ENERGY_BITS_PER_LEVEL * face.gap);
627 }
628
629 std::vector<int64_t> temperatures(grid.cells.size());
630 for (size_t i = 0; i < grid.cells.size(); ++i) {
631 const auto capacity = static_cast<int64_t>(grid.cells[i].heatCapacity);
632 temperatures[i] = start[i] + FxDivShiftS64(heat[i], capacity, 32);
633 }
634
635 return temperatures;
636 }
637
638 int64_t MulWeight(int64_t weight, int64_t value) {
639 return FxMulShiftS64(weight, value, WEIGHT_SHIFT);
640 }
641
642 std::vector<int64_t> Rkl2Flows(const ImplicitGrid& grid, const std::vector<int64_t>& start,
643 ImplicitCost& cost) {
644 const uint32_t stageCount = Rkl2StageCount(grid.levels[0]);
645 const std::vector<Rkl2Stage> stages = Rkl2Stages(stageCount);
646 const std::vector<int64_t> startFlows = Flows(grid, start);
647 const size_t faceCount = grid.faces.size();
648 std::vector<int64_t> before(faceCount, 0);
649 std::vector<int64_t> previous(faceCount);
650 for (size_t f = 0; f < faceCount; ++f)
651 previous[f] = MulWeight(stages[1].muTilde, startFlows[f]);
652
653 for (uint32_t j = 2; j <= stageCount; ++j) {
654 const std::vector<int64_t> stageFlows = Flows(grid, StageTemperatures(grid, start, previous));
655 const Rkl2Stage& stage = stages[j];
656 std::vector<int64_t> current(faceCount);
657 for (size_t f = 0; f < faceCount; ++f) {
658 current[f] = MulWeight(stage.mu, previous[f]) + MulWeight(stage.nu, before[f]) +
659 MulWeight(stage.muTilde, stageFlows[f]) + MulWeight(stage.gammaTilde, startFlows[f]);
660 }
661
662 before = std::move(previous);
663 previous = std::move(current);
664 }
665
666 cost.stages = stageCount;
667 cost.passes += 2 * static_cast<uint64_t>(stageCount);
668 cost.cellUpdates += static_cast<uint64_t>(stageCount) * grid.cells.size();
669
670 return previous;
671 }
672
673 } // namespace
674
675 ImplicitGrid BuildImplicitGrid(std::vector<ImplicitCell> cells, bool galerkin) {
676 ImplicitGrid grid;
677 grid.cells = std::move(cells);
678 FindFaces(grid);
679
680 return BuildImplicitGrid(std::move(grid.cells), std::move(grid.faces), galerkin);
681 }
682
683 ImplicitGrid BuildImplicitGrid(std::vector<ImplicitCell> cells, std::vector<ImplicitFace> faces, bool galerkin,
684 const ImplicitGridLimits& limits) {
685 const uint64_t maxNodes = limits.nodes == 0 ? UINT64_MAX : limits.nodes;
686 const uint64_t maxLinks = limits.links == 0 ? UINT64_MAX : limits.links;
687 ImplicitGrid grid;
688 grid.cells = std::move(cells);
689 grid.faces = std::move(faces);
690 grid.levels.push_back(MakeCellLevel(grid));
691 uint64_t nodes = grid.levels.back().levels.size();
692 uint64_t links = grid.levels.back().neighbors.size();
693 while (grid.levels.size() < MAX_GRID_LEVELS && NeedsCoarser(grid.levels.back())) {
694 ImplicitGridLevel coarser = Coarsen(grid.levels.back(), galerkin);
695 if (coarser.levels.size() == grid.levels.back().levels.size())
696 break;
697
698 // --- 上限に入らない段は作らずに止める(T-0178。次の段の隣は今の段の隣より多くならないので、今の段の数で見込む)---
699 const uint64_t lastLinks = grid.levels.back().neighbors.size();
700 if (nodes + coarser.levels.size() > maxNodes || links + lastLinks > maxLinks)
701 break;
702
703 nodes += coarser.levels.size();
704 links += coarser.neighbors.size();
705 grid.levels.push_back(std::move(coarser));
706 }
707
708 return grid;
709 }
710
712 ImplicitCost cost;
713 const std::vector<int64_t> start = Temperatures(grid);
714 if (options.method == ImplicitMethod::Rkl2) {
715 std::vector<int64_t> flows = Rkl2Flows(grid, start, cost);
716 LimitFlows(grid, start, options.limitSlackMillikelvin, flows, cost);
717 ApplyFlows(grid, flows);
718
719 return cost;
720 }
721
722 // --- 段 0 の右辺 = 自分の重み × 刻みの初めの温度。初めの近似 = 刻みの初めの温度 ---
723 const ImplicitGridLevel& cells = grid.levels[0];
724 std::vector<int64_t> rhs(start.size());
725 for (size_t i = 0; i < start.size(); ++i)
726 rhs[i] = MulWeight(cells.selfWeights[i], start[i]);
727
728 std::vector<int64_t> solution = start;
729 if (options.method == ImplicitMethod::RedBlack)
730 Smooth(cells, rhs, solution, options.sweeps, cost);
731 else {
732 for (uint32_t cycle = 0; cycle < options.cycles; ++cycle) {
733 VCycle(grid, 0, rhs, solution, options, cost);
734 cost.cycles += 1;
735 if (options.toleranceMillikelvin != 0 &&
736 Converged(cells, rhs, solution, options.toleranceMillikelvin, cost))
737 break;
738 }
739 }
740
741 std::vector<int64_t> flows = Flows(grid, solution);
742 LimitFlows(grid, start, options.limitSlackMillikelvin, flows, cost);
743 ApplyFlows(grid, flows);
744 cost.passes += 2;
745
746 return cost;
747 }
748
749 int64_t ImplicitTemperature(const ImplicitCell& cell) {
750 return ImTemperature(cell.energy, cell.heatCapacity);
751 }
752
753 int64_t ImplicitEnergyFor(uint64_t heatCapacity, int64_t millikelvin) {
754 return ImEnergyFor(heatCapacity, millikelvin);
755 }
756
758 int32_t finest = 0;
759 for (const ImplicitCell& cell : grid.cells)
760 finest = std::max(finest, cell.level);
761
762 // 2 の補数の 128bit で 2^128 を法として足す(負のエネルギー〔近似の行き過ぎ〕も比べられる。等しいかを見るだけなので桁は気にしない)
763 FxU128 total = Wide(0);
764 for (const ImplicitCell& cell : grid.cells) {
765 const auto shift = static_cast<uint32_t>(finest - cell.level) * ENERGY_BITS_PER_LEVEL;
766 FX_ASSERT(shift < 64);
767 const auto whole = static_cast<uint64_t>(cell.energy);
768 const FxU128 value = shift == 0 ? FxU128{.hi = whole, .lo = cell.fraction}
769 : FxU128{.hi = (whole << shift) | (cell.fraction >> (64 - shift)),
770 .lo = cell.fraction << shift};
771 const FxU128 sum = {.hi = total.hi + value.hi, .lo = total.lo + value.lo};
772 total = {.hi = sum.hi + (sum.lo < total.lo ? 1u : 0u), .lo = sum.lo};
773 }
774
775 return total;
776 }
777
778} // namespace bicameral::sim
std::map< CellKey, uint32_t > MakeCellIndex(const std::vector< ImplicitCell > &cells)
bool Converged(const ImplicitGridLevel &cells, const std::vector< int64_t > &rhs, const std::vector< int64_t > &values, uint32_t toleranceMillikelvin, ImplicitCost &cost)
std::vector< int64_t > Flows(const ImplicitGrid &grid, const std::vector< int64_t > &temperatures)
int64_t Overrun(int64_t energy, const Bounds &bounds, size_t i)
Bounds MakeBounds(const ImplicitGrid &grid, const std::vector< int64_t > &start, int64_t slackMillikelvin)
void LimitFlows(const ImplicitGrid &grid, const std::vector< int64_t > &start, int64_t slackMillikelvin, std::vector< int64_t > &flows, ImplicitCost &cost)
void FinishLevel(ImplicitGridLevel &level, const Rows &rows)
int64_t FaceFlow(FxU128 coefficient, int64_t difference)
int64_t Relax(const ImplicitGridLevel &level, const std::vector< int64_t > &rhs, const std::vector< int64_t > &values, size_t node)
void PushNode(ImplicitGridLevel &level, const CellKey &key, FxU128 capacity)
FxU128 FaceCoefficient(const ImplicitCell &fine, const ImplicitCell &coarse)
ImplicitGridLevel MakeCellLevel(const ImplicitGrid &grid)
int64_t RationalWeight(FxU128 numerator, FxU128 denominator, bool negative)
void VCycle(const ImplicitGrid &grid, size_t depth, const std::vector< int64_t > &rhs, std::vector< int64_t > &values, const ImplicitOptions &options, ImplicitCost &cost)
std::tuple< int32_t, int64_t, int64_t, int64_t > CellKey
void AddToRow(std::vector< RowEntry > &row, uint32_t neighbor, FxU128 coefficient)
std::vector< int64_t > Temperatures(const ImplicitGrid &grid)
bool MarkOutside(const ImplicitGrid &grid, const Bounds &bounds, const std::vector< int64_t > &energies, std::vector< uint8_t > &limited, ImplicitCost &cost)
std::vector< int64_t > ExplicitFlows(const ImplicitGrid &grid, const std::vector< int64_t > &start)
std::vector< int64_t > Rkl2Flows(const ImplicitGrid &grid, const std::vector< int64_t > &start, ImplicitCost &cost)
void SweepColor(const ImplicitGridLevel &level, const std::vector< int64_t > &rhs, uint8_t color, std::vector< int64_t > &values, std::vector< int64_t > &next)
int64_t Weight(FxU128 numerator, FxU128 denominator)
constexpr std::array< std::array< int64_t, 3 >, 6 > FACE_OFFSETS
std::vector< int64_t > StageTemperatures(const ImplicitGrid &grid, const std::vector< int64_t > &start, const std::vector< int64_t > &accumulated)
ImplicitGridLevel Coarsen(ImplicitGridLevel &fine, bool galerkin)
std::optional< Containing > FindContaining(const std::map< CellKey, uint32_t > &index, int32_t level, int64_t x, int64_t y, int64_t z)
void ApplyFlows(ImplicitGrid &grid, const std::vector< int64_t > &flows)
std::vector< int64_t > EnergiesAfter(const ImplicitGrid &grid, const std::vector< int64_t > &flows)
void Smooth(const ImplicitGridLevel &level, const std::vector< int64_t > &rhs, std::vector< int64_t > &values, uint32_t sweeps, ImplicitCost &cost)
std::vector< Rkl2Stage > Rkl2Stages(uint32_t stageCount)
FxU128 ImplicitConservedTotal(const ImplicitGrid &grid)
ImplicitCost StepImplicit(ImplicitGrid &grid, const ImplicitOptions &options)
ImplicitGrid BuildImplicitGrid(std::vector< ImplicitCell > cells, bool galerkin)
int64_t ImplicitTemperature(const ImplicitCell &cell)
int64_t ImplicitEnergyFor(uint64_t heatCapacity, int64_t millikelvin)
std::vector< fx::FxU128 > capacities
std::vector< fx::FxU128 > diagonals
std::vector< fx::FxU128 > coefficients
std::vector< ImplicitGridLevel > levels
std::vector< ImplicitFace > faces
std::vector< ImplicitCell > cells