Bicameral Engine
GPU-resident engine for a magic-research open-world game
Loading...
Searching...
No Matches
multires_implicit_conduction.cpp
Go to the documentation of this file.
1// multires_implicit_conduction.cpp — 細かいレベルの熱の陰解法(方式②。ADR-0019)を木の伝導につなぐ CPU リファレンス(T-0119。D-434 の案 a)。
2// 面は「流れを計算する側(違うレベルなら細かい側。同じレベルなら両側)」のレベルで分ける: 基準(subcycleBaseLevel)以下は今までの
3// 陽解法(multires_conduction.cpp の ComputeConduction)、基準より細かい(Δk 1〜implicitMaxGap)セルの面はここで陰解法の
4// 1 刻み(StepImplicit)に入れる。それより細かいセルは陽解法のまま(温度の端数の精度で 1 mK の判定に届かない。下の「約束」)。
5// データの流れ: 頁のある・凍っていない本物のブロックのセル(MrCellThermal: 刻みの初めの温度と熱容量)→ 陰解法のセルと面
6// (面の隣は陽解法と同じ MrFindFaceNeighbor)→ StepImplicit(V サイクル → 面の流れ → 安全網 → 両側に足す)→ 変化の表 deltas
7// (呼ぶ側の StepPagedBlock が反応の前に足す)
8// 約束:
9// - 系の未知数は基準より細かいセル。その面の粗い側の基準以下のセルも「境のセル」として入れる(境のセルの基準以下の面は陽解法のまま、
10// 境の面だけこの系で受ける = 面で分けた分割)。どちらも頁のある・凍っていない・熱容量のあるセルだけ。一様なブロック・凍らせた
11// ブロック・熱容量 0 のセルとの面は流れない(陽解法と同じ。一様なブロックは刻みの初めに隣と温度差が無いので頁を持たない)
12// - 熱容量は刻みの初めの値で線形にする(相変化・反応で変わる分は次の刻みの温度に出る)。温度は整数部だけから(陽解法と同じ)
13// - 活性で刻まない(眠っている)頁のブロックも入れる: 陰解法では熱が 1 刻みに 1 セルより遠くへ届くので、全部刻んだ時と同じ系にする
14// (活性 = 全部のビット一致)。変わったブロックは StepPagedBlock が changed にし、次の刻みに起こされる
15// - Δk > implicitMaxGap(既定 8)のセルは陽解法のまま(頭打ちで 1 段ごとに約 1/4 遅い)。T* は mK × 2^16 の整数なので、面の流れの
16// 誤差は約 4^Δk × 2^-16 mK になり、Δk 8 を超えると「新しい温度の誤差 1 mK」で止まらず上限まで回って安全網が陽解法に戻した
17// (たくさんの要求の場面の深さ 26 段で、毎刻み 64 回・14 万セル。T-0119 の計測)。そこの陽解法の面は、陰解法の系のセルにも
18// 刻みの初めの温度から流れを足す(境の面の逆向き)
19// - 影のブロック(覗き窓)は陽解法のまま(中間のセルも刻むので、多重格子の親と重なる。T-0128)
20// - 系の大きさの上限(T-0178。options.implicitLimits。GPU では VRAM に先に取る大きさ): 流れの段の前に MarkImplicitBlocks が、
21// 入れられるブロックを枠の順に「未知数・セルの上限の見込み(ImBlockCellBound)の和」が予算(MakeMultiresImplicitBudget)に入る所まで選ぶ。
22// 選ばれなかったブロックはその刻みだけ陽解法(頭打ち)のまま(QUESTIONS Q22 の案 A。仮〔ユーザー未確認〕)。選ばれたブロックとの
23// 同じレベルの面は、選ばれなかった側が境のセルとしてこの系で受ける(陽解法の側は計算しない。multires_conduction.cpp の CollectCellFaces)。
24// 粗い側・細かい側の面は、今までの境のセル・Δk > implicitMaxGap と同じ形。多重格子の 2 段目からの節・隣が上限に入らなければ、
25// その段を作らずに縮約を止める(BuildImplicitGrid の limits。反復は遅くなりうるが、保存と安全網は同じ)
26// - 保存は面ごとに 1 つの値(ADR-0017 の形)なのでビット単位。端数の枠が無い粗い側へは整数の単位の倍数だけ送る
27// 浮動小数点は使わない(engine/src/sim は検査の対象)。
28#include <algorithm>
29#include <limits>
30
31#include "common/implicit_conduction.hlsli"
32#include "common/multires_conduction.hlsli"
34#include "sim/multires_nest.h"
36
37using namespace bicameral::multires;
38using namespace bicameral::reaction;
39
40namespace bicameral::sim {
41
42 namespace {
43
46
47 constexpr uint32_t NOT_IN_SYSTEM = std::numeric_limits<uint32_t>::max();
48
49 // 陰解法の系(セルの番号 = grid.cells の添字)
51 std::vector<ImplicitCell> cells;
52 std::vector<ImplicitFace> faces;
53
54 // --- セルごと: 木のどこか・足す前の仮のエネルギー ---
55 std::vector<uint32_t> slots;
56 std::vector<uint32_t> indices;
57 std::vector<int64_t> startEnergies;
58
59 std::vector<uint32_t> ids; // [枠 × 512] → セルの番号(無ければ NOT_IN_SYSTEM)
60 };
61
62 // 系に入れられるブロックか: 世界の本物のブロックで、頁があり、凍っていない
63 bool CanJoin(const MultiresNest& nest, std::span<const uint8_t> frozen, uint32_t slot) {
64 const MrBlock& block = nest.blocks[slot];
65 const bool frozenNow = !frozen.empty() && frozen[slot] != 0;
66
67 return slot < nest.capacity.worldBlocks && block.kind == MR_BLOCK_REAL && !MrIsUniform(block) && !frozenNow;
68 }
69
70 // セルを系に足す(熱容量 0 なら足さない)。番号を返す
71 uint32_t AddCell(ImplicitSystem& system, const MultiresNest& nest, CellThermals& thermals, uint32_t slot,
72 uint32_t index) {
73 const size_t address = (size_t{slot} * MR_BLOCK_CELLS) + index;
74 if (system.ids[address] != NOT_IN_SYSTEM)
75 return system.ids[address];
76
77 const MrThermal thermal = thermals.At(slot, index);
78 if (thermal.capacityLimit == 0)
79 return NOT_IN_SYSTEM;
80
81 // --- 熱容量 C = 8 × 上限の係数(レベルによらない数)。仮のエネルギーは刻みの初めの温度に当たる値 ---
82 const MrBlock& block = nest.blocks[slot];
83 ImplicitCell cell;
84 cell.level = block.level;
85 cell.x = block.originX + static_cast<int64_t>(MrCellX(index));
86 cell.y = block.originY + static_cast<int64_t>(MrCellY(index));
87 cell.z = block.originZ + static_cast<int64_t>(MrCellZ(index));
88 cell.heatCapacity = thermal.capacityLimit << IM_ENERGY_BITS_PER_LEVEL;
89 cell.conductance = thermal.conductance;
90 cell.startTemperature = int64_t{thermal.temperature} << IM_TEMPERATURE_SHIFT;
91 cell.energy = ImplicitEnergyFor(cell.heatCapacity, thermal.temperature);
92 cell.coarseFraction = block.fraction != MR_NO_FRACTION;
93
94 const auto id = static_cast<uint32_t>(system.cells.size());
95 system.ids[address] = id;
96 system.cells.push_back(cell);
97 system.slots.push_back(slot);
98 system.indices.push_back(index);
99 system.startEnergies.push_back(cell.energy);
100
101 return id;
102 }
103
104 // 面の係数(細かい側の単位。頭打ちなし)= min(G) × G の係数 × 4^kf(ADR-0019。陽解法の MrConductanceLimit と同じ値)
105 fx::FxU128 FaceCoefficient(const ImplicitCell& fine, const ImplicitCell& coarse) {
106 const uint64_t conductance = fine.conductance < coarse.conductance ? fine.conductance : coarse.conductance;
107
108 return ImWideShiftLeft(ImWide(conductance * HC_LIMIT_PER_CONDUCTANCE),
109 2 * static_cast<uint32_t>(fine.level));
110 }
111
112 // 未知数のセル(基準より細かい)の 6 面。同じレベルの面は番号の小さい側が 1 回だけ足す。粗い側は境のセルとして足す
113 void AddCellFaces(ImplicitSystem& system, const MultiresNest& nest, CellThermals& thermals,
114 std::span<const uint8_t> frozen, uint32_t id) {
115 const CpuTree tree{.nest = &nest};
116 const uint32_t slot = system.slots[id];
117 const uint32_t index = system.indices[id];
118 const MrBlock block = nest.blocks[slot];
119 for (uint32_t face = 0; face < MR_FACES; ++face) {
120 const MrFaceNeighbor neighbor = MrFindFaceNeighbor(tree, slot, block, index, face,
121 nest.capacity.rootLevel);
122 if (neighbor.kind != MR_NEIGHBOR_SAME && neighbor.kind != MR_NEIGHBOR_COARSER)
123 continue;
124
125 if (!CanJoin(nest, frozen, neighbor.slot))
126 continue;
127
128 const uint32_t other = AddCell(system, nest, thermals, neighbor.slot, neighbor.index);
129 if (other == NOT_IN_SYSTEM || (neighbor.kind == MR_NEIGHBOR_SAME && other < id))
130 continue;
131
132 const uint32_t gap = neighbor.kind == MR_NEIGHBOR_SAME ? 0 : neighbor.gap;
133 system.faces.push_back({.fine = id,
134 .coarse = other,
135 .gap = gap,
136 .coefficient = FaceCoefficient(system.cells[id], system.cells[other])});
137 }
138 }
139
140 // 系を作る: まず未知数のセル(MarkImplicitBlocks が選んだブロック)を枠の順に全部足し(番号を先に決める)、次にその面と境のセル
141 ImplicitSystem MakeSystem(const MultiresNest& nest, CellThermals& thermals, std::span<const uint8_t> frozen,
142 std::span<const uint8_t> implicitBlocks) {
143 ImplicitSystem system;
144 system.ids.assign(nest.blocks.size() * MR_BLOCK_CELLS, NOT_IN_SYSTEM);
145 for (uint32_t slot = 0; slot < nest.capacity.worldBlocks; ++slot) {
146 if (!CanJoin(nest, frozen, slot) || !nest_detail::IsImplicitBlock(implicitBlocks, slot))
147 continue;
148
149 for (uint32_t index = 0; index < MR_BLOCK_CELLS; ++index) {
150 if (MrIsSteppedCell(nest.blocks[slot], index))
151 AddCell(system, nest, thermals, slot, index);
152 }
153 }
154
155 const auto unknowns = static_cast<uint32_t>(system.cells.size());
156 for (uint32_t id = 0; id < unknowns; ++id)
157 AddCellFaces(system, nest, thermals, frozen, id);
158
159 return system;
160 }
161
162 // 多重格子の段ごとの節の数と隣の最大(計測用。GPU の ImTail の境〔節 1024・隣 32〕を見る。T-0120)
163 void RecordLevels(MultiresNest& nest, const ImplicitGrid& grid) {
164 for (const ImplicitGridLevel& level : grid.levels) {
165 uint32_t widest = 0;
166 for (size_t node = 0; node + 1 < level.rowStarts.size(); ++node)
167 widest = std::max(widest, level.rowStarts[node + 1] - level.rowStarts[node]);
168
169 nest.implicitLevels.push_back({static_cast<uint32_t>(level.levels.size()), widest});
170 }
171 }
172
173 // 系を作る時の木を写す(試験用。T-0129)
174 void CaptureNest(MultiresNest& nest, std::span<const uint8_t> frozen) {
175 nest.implicitNest.reset();
176 auto copy = std::make_shared<MultiresNest>(nest);
177 copy->captureImplicitNest = false;
178 copy->implicitFrozen.clear();
179 copy->implicitGrid = {};
180 nest.implicitNest = std::move(copy);
181 nest.implicitFrozen.assign(frozen.begin(), frozen.end());
182 }
183
184 // ブロックの未知数のセルの数(刻むセルで熱容量のあるもの。AddCell が足すものと同じ)
185 uint32_t CountUnknowns(const MultiresNest& nest, CellThermals& thermals, uint32_t slot) {
186 uint32_t count = 0;
187 for (uint32_t index = 0; index < MR_BLOCK_CELLS; ++index) {
188 if (MrIsSteppedCell(nest.blocks[slot], index) && thermals.At(slot, index).capacityLimit != 0)
189 ++count;
190 }
191
192 return count;
193 }
194
195 // 選んだ形(ADR-0019): V(2,2)・新しい温度の誤差の見込み 1 mK で止める
197 ImplicitOptions implicit;
199 implicit.cycles = options.implicitMaxCycles;
200 implicit.toleranceMillikelvin = 1;
201
202 return implicit;
203 }
204
205 } // namespace
206
207 namespace nest_detail {
208
209 bool InImplicitConduction(const MultiresNest& nest, uint32_t slot, const MultiresStepOptions& options) {
210 const MrBlock& block = nest.blocks[slot];
211
212 const int32_t finest = options.subcycleBaseLevel + static_cast<int32_t>(options.implicitMaxGap);
213
214 return options.implicitConduction && slot < nest.capacity.worldBlocks && block.kind == MR_BLOCK_REAL &&
215 block.level > options.subcycleBaseLevel && block.level <= finest;
216 }
217
218 bool IsImplicitBlock(std::span<const uint8_t> implicitBlocks, uint32_t slot) {
219 return slot < implicitBlocks.size() && implicitBlocks[slot] != 0;
220 }
221
222 std::vector<uint8_t> MarkImplicitBlocks(MultiresNest& nest, CellThermals& thermals,
223 std::span<const uint8_t> frozen, const MultiresStepOptions& options) {
224 std::vector<uint8_t> marks(nest.blocks.size(), 0);
225 nest.implicitDeferredBlocks = 0;
226 if (!options.implicitConduction)
227 return marks;
228
229 // --- 枠の順に、和が予算に入る所まで(超えたブロックから後は全部陽解法。GPU は同じ和を接頭和で)---
230 FX_ASSERT(options.implicitOverflow == ImplicitOverflow::Explicit); // B・C は未実装(Q22)
232 uint64_t unknowns = 0;
233 uint64_t cells = 0;
234 for (uint32_t slot = 0; slot < nest.capacity.worldBlocks; ++slot) {
235 if (!CanJoin(nest, frozen, slot) || !InImplicitConduction(nest, slot, options))
236 continue;
237
238 const uint32_t count = CountUnknowns(nest, thermals, slot);
239 unknowns += count;
240 cells += ImBlockCellBound(count);
241 if (unknowns > budget.unknowns || cells > budget.cells) {
242 nest.implicitDeferredBlocks += 1;
243 continue;
244 }
245
246 marks[slot] = 1;
247 }
248
249 return marks;
250 }
251
252 void AddImplicitConduction(MultiresNest& nest, CellThermals& thermals, std::span<const uint8_t> frozen,
253 std::span<const uint8_t> implicitBlocks, const MultiresStepOptions& options,
254 std::span<MrEnergyDelta> deltas) {
255 nest.implicitCost = {};
256 nest.implicitCells = 0;
257 nest.implicitLevels.clear();
258 nest.implicitGrid = {};
259 nest.implicitNest.reset();
260 ImplicitSystem system = MakeSystem(nest, thermals, frozen, implicitBlocks);
261 if (system.faces.empty())
262 return;
263
264 if (nest.captureImplicitNest)
265 CaptureNest(nest, frozen);
266
267 // --- 1 刻み解いて、足した後と仮のエネルギーの差を変化の表へ ---
268 const ImplicitGridLimits gridLimits{.nodes = options.implicitLimits.nodes,
269 .links = options.implicitLimits.links};
270 ImplicitGrid grid = BuildImplicitGrid(std::move(system.cells), std::move(system.faces), false, gridLimits);
271 if (nest.captureImplicitGrid)
272 nest.implicitGrid = grid;
273
274 nest.implicitCost = StepImplicit(grid, MakeImplicitOptions(options));
275 nest.implicitCells = static_cast<uint32_t>(grid.cells.size());
276 RecordLevels(nest, grid);
277 for (size_t id = 0; id < grid.cells.size(); ++id) {
278 const ImplicitCell& cell = grid.cells[id];
279 const MrEnergyDelta change = {.whole = cell.energy - system.startEnergies[id],
280 .fraction = cell.fraction};
281 if (MrEnergyDeltaIsZero(change))
282 continue;
283
284 const MrBlock& block = nest.blocks[system.slots[id]];
285 MrEnergyDelta& entry = deltas[PageCellAddress(nest, block.page, system.indices[id])];
286 entry = MrAddEnergyDelta(entry, change);
287 }
288 }
289
290 } // namespace nest_detail
291
292} // namespace bicameral::sim
multires::MrThermal At(uint32_t slot, uint32_t index)
void AddCellFaces(ImplicitSystem &system, const MultiresNest &nest, CellThermals &thermals, std::span< const uint8_t > frozen, uint32_t id)
uint32_t AddCell(ImplicitSystem &system, const MultiresNest &nest, CellThermals &thermals, uint32_t slot, uint32_t index)
void CaptureNest(MultiresNest &nest, std::span< const uint8_t > frozen)
fx::FxU128 FaceCoefficient(const ImplicitCell &fine, const ImplicitCell &coarse)
ImplicitSystem MakeSystem(const MultiresNest &nest, CellThermals &thermals, std::span< const uint8_t > frozen, std::span< const uint8_t > implicitBlocks)
uint32_t CountUnknowns(const MultiresNest &nest, CellThermals &thermals, uint32_t slot)
bool CanJoin(const MultiresNest &nest, std::span< const uint8_t > frozen, uint32_t slot)
size_t PageCellAddress(const MultiresNest &nest, uint32_t page, uint32_t index)
std::vector< uint8_t > MarkImplicitBlocks(MultiresNest &nest, CellThermals &thermals, std::span< const uint8_t > frozen, const MultiresStepOptions &options)
bool InImplicitConduction(const MultiresNest &nest, uint32_t slot, const MultiresStepOptions &options)
bool IsImplicitBlock(std::span< const uint8_t > implicitBlocks, uint32_t slot)
void AddImplicitConduction(MultiresNest &nest, CellThermals &thermals, std::span< const uint8_t > frozen, std::span< const uint8_t > implicitBlocks, const MultiresStepOptions &options, std::span< MrEnergyDelta > deltas)
ImplicitCost StepImplicit(ImplicitGrid &grid, const ImplicitOptions &options)
ImplicitGrid BuildImplicitGrid(std::vector< ImplicitCell > cells, bool galerkin)
constexpr MultiresImplicitBudget MakeMultiresImplicitBudget(const MultiresImplicitLimits &limits)
int64_t ImplicitEnergyFor(uint64_t heatCapacity, int64_t millikelvin)
std::vector< ImplicitGridLevel > levels
std::vector< ImplicitCell > cells
std::vector< uint8_t > implicitFrozen
std::shared_ptr< const MultiresNest > implicitNest
std::vector< multires::MrBlock > blocks
std::vector< std::array< uint32_t, 2 > > implicitLevels
MultiresImplicitLimits implicitLimits