Bicameral Engine
GPU-resident engine for a magic-research open-world game
Loading...
Searching...
No Matches
gas_reference.h
Go to the documentation of this file.
1// gas_reference.h — 気体(空気・煙・湯気)の流れの CPU リファレンス、段 G1・G2(T-0026・T-0184。07 §2・§2.1・§2.2・ADR-0043)。
2// 1 レベル(0.5 m 角のセルの直方体)だけ。GPU・多重解像度・反応との結合はまだ無い(G3・G4。T-0185・T-0186)。
3//
4// 方式: 圧縮性の有限体積法を陽解法で。セルに保存量(成分ごとの物質量・内部エネルギー・運動量 ×3)を持ち、
5// 面ごとに 1 つの質量の流れ F を計算して、風上側のセルの中身を同じ割合(F ÷ 風上の質量)だけ移す(成分・エネルギー・運動量が一緒に動く)。
6// F = 中心の質量の流れ(両側の運動量の平均)+ 圧力の拡散(両側の圧力の差 ÷ 2c̃。Rusanov の散逸を密度でなく圧力にかけたもの)
7// 移す塊の熱は風上の熱を熱容量の比で分ける(塊の温度 = 風上の温度。丸めの偏りで偽の温度の縞を作らない)
8// G2: 移す塊の成分の割合は MUSCL(整数の勾配の制限。既定は MC)で面へ延ばし(煙のにじみを減らす)、物質量の端数は決定的な乱数で丸める
9// (R6。切り捨てだと微量の成分が動かない。D-428)。どちらも足し引きの形のままなので保存はビット単位のまま
10// 運動量には、面の圧力(両側の平均)の力積を両側へ逆向きに足し引きし、縦の面ごとに重さ −(m − m_基準) g(両側の平均)を足す。
11// 力を先に当て、その運動量で流れを出す(前進・後退の順)。
12// 圧力は理想気体の本物の値 p から、層ごとの基準の静水圧 p_基準 を引いて α 倍する(音速を c̃ に落とす。PGS。Q11 は仮で C)。
13// → 基準の状態(静止大気)では力がちょうど 0 になる(静水圧の釣り合いを離散の式で厳密に。偽の流れが出ない)。
14// 保存: 面の流れも力積も「片方から引いた値をそのまま他方へ足す」だけ(04 R2)。箱の外との出入り(開いた境界・壁の力積・重力)は
15// GasLedger に数える。閉じた箱で成分とエネルギーの合計はビット単位で一定、運動量は「初め + 帳簿」とビット単位で一致する。
16// 順番: 小刻みの初めの状態だけから全部の面を計算し、整数の足し引きで当てる(Jacobi 型。04 R3)。
17//
18// データの流れ: GasConfig → MakeGasCoefficients → MakeHydrostaticReference(層ごとの基準のセル)→ MakeGasBox
19// → StepGas(1 刻み = substeps 回の小刻み)→ 合計・温度・速度を読む(SumGas・DeriveGasCell)。
20// 浮動小数点は使わない(engine/src/sim は検査の対象。04 §4)。
21#pragma once
22
23#include <array>
24#include <cstdint>
25#include <span>
26#include <vector>
27
28namespace bicameral::sim {
29
30 inline constexpr uint32_t GAS_MAX_SPECIES = 4;
31 inline constexpr uint32_t GAS_AXES = 3;
32
33 // 気体の物質(G1 は自前の小さな表。反応の表 RxSpecies とつなぐのは結合の段)
34 struct GasSpecies {
35 uint32_t molarMass = 0; // mg/mol(04 §2)
36 uint32_t heatCapacity = 0; // mJ/(mol·K)
37 int64_t formationEnergy = 0; // H0(J/mol = µJ/µmol)。セルのエネルギー = 熱 + Σ 物質量 × H0
38 };
39
40 // 軸ごとの境界(両端とも同じ)
41 enum class GasBoundary : uint8_t {
42 Wall, // 閉じた壁: 物は通さず、圧力の力積だけ受ける(帳簿へ)
43 Periodic, // 反対側とつながる
44 Open, // 外は層ごとの基準の状態 + 風(無限の溜め。出入りは帳簿へ)
45 };
46
47 // 移す塊の成分の割合を面へ延ばす方法(G2。T-0184・07 §2.2)
48 enum class GasReconstruction : uint8_t {
49 Upwind, // 1 次の風上(G1。風上のセルの割合のまま)
50 Minmod, // MUSCL・minmod の制限(最も控えめ)
51 MonotonizedCentral, // MUSCL・MC の制限(既定。にじみの増え方が minmod の約半分。07 §2.2)
52 };
53
54 struct GasConfig {
55 // --- 箱 ---
56 uint32_t sizeX = 8;
57 uint32_t sizeY = 8;
58 uint32_t sizeZ = 8; // z が上
59 std::array<GasBoundary, GAS_AXES> boundary = {GasBoundary::Wall, GasBoundary::Wall, GasBoundary::Wall};
60
61 // --- 物質 ---
62 uint32_t speciesCount = 0;
63 std::array<GasSpecies, GAS_MAX_SPECIES> species = {};
64
65 // --- 刻み ---
66 uint32_t substeps = 4; // 1 刻み(1/60 s)を何回に分けるか
67 uint32_t soundSpeedMmPerS = 30000; // 落とした音速 c̃(mm/s)。Q11 は仮で C(約 30 m/s。設定で変えられる)
68 bool gravity = true;
69
70 // --- 風(開いた境界の外の速度。2^-20 m/s)---
71 std::array<int32_t, GAS_AXES> windVelocity = {0, 0, 0};
72
73 // --- 成分の輸送(G2。T-0184・07 §2.2)---
75 bool stochasticRounding = true; // 移す物質量の端数を決定的な乱数で丸める(R6)。false は切り捨て(G1)
76 uint64_t randomSeed = 0; // 丸めの乱数の種(世界のシード。R6)
77 };
78
79 // 小刻みの係数(設定から 1 回だけ作る。割り算はここだけで、小刻みの中は掛け算とシフト。04 §6・ADR-0010)
81 std::array<uint64_t, GAS_MAX_SPECIES> massPerAmount = {}; // mg/µmol の Q32
82 uint64_t centralFlow = 0; // (P_左 + P_右)× これ >> 52 = 質量の流れ(mg/小刻み)
83 uint64_t pressureFlow = 0; // (p̃_左 − p̃_右)× これ >> 40 = 質量の流れ(mg/小刻み)
84 uint64_t pressureImpulse = 0; // (p̃_左 + p̃_右)× これ >> 16 = 面の力積(運動量の単位/小刻み)
85 uint64_t gravityImpulse = 0; // (m − m_基準)× これ >> 16 = 重さの力積(運動量の単位/小刻み)
86 uint64_t soundScale = 0; // α = c̃² ÷ c_iso² の Q32(c_iso² = p ÷ ρ は地面の層の基準から。MakeGasBox が入れる)
88 0; // 地面の層の基準の温度(mK)。これより熱いセルは α を T_基準 ÷ T 倍して実効の音速を c̃ に抑える
89 };
90
91 // セル(保存量だけ)。単位はレベル 0(04 §2)
92 struct GasCell {
93 std::array<uint64_t, GAS_MAX_SPECIES> amounts = {}; // µmol
94 int64_t energy = 0; // mJ(熱 + 化学。運動エネルギーは入れない。07 §2.1)
95 std::array<int64_t, GAS_AXES> momentum = {}; // mg·2^-20 m/s
96 };
97
98 // セルから導く値(小刻みの初めに作る)
99 struct GasDerived {
100 uint64_t mass = 0; // mg
101 uint64_t inverseMass = 0; // floor((2^64 − 1) ÷ mass)。風上の割合 = |F| × これ >> 32(Q32)
102 uint64_t inverseAmount = 0; // floor((2^64 − 1) ÷ 全物質量)。成分の割合 = 物質量 × これ >> 32(Q32。MUSCL)
103 uint32_t temperature = 0; // mK
104 int64_t pressure = 0; // µPa(本物の値)
105 int64_t scaledPressure = 0; // p̃ = α(p − p_基準)(µPa)
106 };
107
108 // 箱の外との出入りの帳簿(外から入った量を正)。閉じた箱の検査はこれを差し引く(07 §4)
109 struct GasLedger {
110 std::array<int64_t, GAS_MAX_SPECIES> amounts = {};
111 int64_t energy = 0;
112 std::array<int64_t, GAS_AXES> momentum = {};
113 };
114
115 struct GasTotals {
116 std::array<uint64_t, GAS_MAX_SPECIES> amounts = {};
117 int64_t energy = 0;
118 std::array<int64_t, GAS_AXES> momentum = {};
119 };
120
121 struct GasBox {
122 // --- 設定と基準 ---
125 std::vector<GasCell> reference; // 層(z)ごとの基準の静止したセル
126 std::vector<GasDerived> referenceDerived;
127 std::vector<GasCell> ghost; // 層ごとの開いた境界の外(基準 + 風の運動量)
128
129 // --- 状態 ---
130 std::vector<GasCell> cells; // x が最も速く回る(x + sizeX × (y + sizeY × z))
132 uint64_t tick = 0;
133
134 [[nodiscard]] uint32_t Index(uint32_t x, uint32_t y, uint32_t z) const {
135 return x + config.sizeX * (y + config.sizeY * z);
136 }
137 };
138
139 [[nodiscard]] GasCoefficients MakeGasCoefficients(const GasConfig& config);
140
141 // 温度と物質量からセルを作る(速度 0)。エネルギー = Σ 物質量 × (H0 + 比熱 × T)
142 [[nodiscard]] GasCell MakeGasCell(const GasConfig& config, std::span<const uint64_t> amounts,
143 uint32_t temperatureMilliKelvin);
144
145 // 一定の温度で、下の層の重さの分だけ圧力を下げていく基準の層(z = 0 が地面の圧力)。成分の割合は Q32
146 [[nodiscard]] std::vector<GasCell> MakeHydrostaticReference(const GasConfig& config,
147 std::span<const uint32_t> fractionsQ32,
148 uint32_t temperatureMilliKelvin,
149 uint64_t surfacePressureMicroPascal);
150
151 // 箱を作る: 全部のセルを層の基準 + 風の速度で埋める
152 [[nodiscard]] GasBox MakeGasBox(const GasConfig& config, std::vector<GasCell> reference);
153
154 // セルから導く値(層 z の基準を使う)
155 [[nodiscard]] GasDerived DeriveGasCell(const GasBox& box, const GasCell& cell, uint32_t z);
156
157 // 速度(2^-20 m/s)= 運動量 ÷ 質量
158 [[nodiscard]] int64_t GasVelocity(const GasBox& box, const GasCell& cell, uint32_t axis);
159
160 // 1 刻み(substeps 回の小刻み)
161 void StepGas(GasBox& box);
162
163 [[nodiscard]] GasTotals SumGas(const GasBox& box);
164
165 // 状態のハッシュ(決定性の検査)
166 [[nodiscard]] uint64_t HashGas(const GasBox& box);
167
168} // namespace bicameral::sim
GasDerived DeriveGasCell(const GasBox &box, const GasCell &cell, uint32_t z)
GasCoefficients MakeGasCoefficients(const GasConfig &config)
uint64_t HashGas(const GasBox &box)
int64_t GasVelocity(const GasBox &box, const GasCell &cell, uint32_t axis)
constexpr uint32_t GAS_AXES
void StepGas(GasBox &box)
GasBox MakeGasBox(const GasConfig &config, std::vector< GasCell > reference)
GasTotals SumGas(const GasBox &box)
std::vector< GasCell > MakeHydrostaticReference(const GasConfig &config, std::span< const uint32_t > fractionsQ32, uint32_t temperatureMilliKelvin, uint64_t surfacePressureMicroPascal)
GasCell MakeGasCell(const GasConfig &config, std::span< const uint64_t > amounts, uint32_t temperatureMilliKelvin)
constexpr uint32_t GAS_MAX_SPECIES
uint32_t Index(uint32_t x, uint32_t y, uint32_t z) const
std::vector< GasCell > reference
std::vector< GasCell > ghost
std::vector< GasCell > cells
std::vector< GasDerived > referenceDerived
GasCoefficients coefficients
std::array< uint64_t, GAS_MAX_SPECIES > amounts
std::array< int64_t, GAS_AXES > momentum
std::array< uint64_t, GAS_MAX_SPECIES > massPerAmount
std::array< GasBoundary, GAS_AXES > boundary
std::array< int32_t, GAS_AXES > windVelocity
GasReconstruction reconstruction
std::array< GasSpecies, GAS_MAX_SPECIES > species
std::array< int64_t, GAS_MAX_SPECIES > amounts
std::array< int64_t, GAS_AXES > momentum
std::array< int64_t, GAS_AXES > momentum
std::array< uint64_t, GAS_MAX_SPECIES > amounts