Bicameral Engine
GPU-resident engine for a magic-research open-world game
Loading...
Searching...
No Matches
reaction_table.cpp
Go to the documentation of this file.
1// reaction_table.cpp — 反応表のベイク(定義 → 整数の表)と、セルを作る・調べる道具(02 §1〜3。T-0014)。
2// ベイクは整数の数学ライブラリ(shaders/common/fixed.hlsli)だけで計算する。CRT の exp・log を使わないので、
3// どの機械でベイクしても同じ表になる(04 R7。Mod やホットリロードは利用者の機械でベイクされる)。
4// 定義の並びにも依らない(T-0021・ADR-0032): 名前のバイト順に並べ直してから ID を振る。
6
7#include <algorithm>
8#include <bit>
9#include <format>
10#include <map>
11
12namespace bicameral::sim {
13
14 using namespace bicameral::fx;
15 using namespace bicameral::reaction;
16
17 namespace {
18
19 // --- 物理の定数(整数)---
20 // 気体定数 R = 8.314462618 J/(mol·K)(nJ/(mol·K) で正確に)
21 constexpr uint64_t GAS_CONSTANT_NANOJOULES = 8314462618;
22 constexpr uint64_t NANOJOULES_PER_JOULE = 1000000000;
23 constexpr uint64_t MICROJOULES_PER_JOULE = 1000000;
24 // 生成エンタルピーの基準の温度 298.15 K(mK)。比熱(mJ/(mol·K))× これ = µJ/mol
25 constexpr int64_t STANDARD_TEMPERATURE_MILLIKELVIN = 298150;
26 // 速度の表の値が 0 と見なせる 2 の指数(これより小さい k は、1 刻みの端数 2^-32 にも届かない)
27 constexpr int32_t RATE_EXPONENT_MIN = -4096;
28 constexpr int32_t RATE_EXPONENT_MAX = 4096;
29 // レベル 0 のセルの体積 = (0.5 m)^3 = 2^-3 m³(units.hlsli の CELL_SIZE_LEVEL0)
30 constexpr int64_t CELL_VOLUME_LOG2 = -3;
31 constexpr uint64_t FNV_OFFSET = 0xcbf29ce484222325ULL;
32 constexpr uint64_t FNV_PRIME = 0x100000001b3ULL;
33 // 文献の反応熱と生成エンタルピーの差がこれより大きく違えば警告(文献の値は 0.01〜1 kJ/mol の桁で丸められている)
34 constexpr int64_t DECLARED_ENTHALPY_TOLERANCE_JOULES = 1000;
35
36 using NameIndex = std::map<std::string, uint32_t, std::less<>>;
37
38 uint64_t HashName(std::string_view name) {
39 uint64_t hash = FNV_OFFSET;
40 for (const char character : name) {
41 hash ^= static_cast<uint8_t>(character);
42 hash *= FNV_PRIME;
43 }
44
45 return hash;
46 }
47
48 // --- 並び(T-0021・ADR-0032)---
49
50 // 元素・物質・規則・規則の項を名前のバイト順に並べた写し。ID はこの順で振るので、定義の並びに依らない。
51 // 名前の重なりは並べた後も隣に残り、索引を作るときに誤りになる
53 ReactionTableDefinition sorted = definition;
54 const auto byName = [](const auto& left, const auto& right) {
55 return left.name < right.name;
56 };
57 std::ranges::stable_sort(sorted.elements, byName);
58 std::ranges::stable_sort(sorted.species, byName);
59 std::ranges::stable_sort(sorted.rules, byName);
60
61 for (RuleDefinition& rule : sorted.rules) {
62 std::ranges::stable_sort(rule.reactants, {}, &RuleTerm::species);
63 std::ranges::stable_sort(rule.products, {}, &RuleTerm::species);
64 }
65
66 return sorted;
67 }
68
69 // --- 元素と物質 ---
70
71 std::expected<NameIndex, std::string> IndexElements(const ReactionTableDefinition& definition,
72 BakedReactionTable& baked) {
73 NameIndex elements;
74 for (const ElementDefinition& element : definition.elements) {
75 if (!elements.emplace(element.name, static_cast<uint32_t>(elements.size())).second)
76 return std::unexpected(std::format("元素 {} が 2 回ある", element.name));
77
78 baked.elementNames.push_back(element.name);
79 }
80
81 return elements;
82 }
83
84 // H0(0 K 基準の化学のエネルギー、J/mol)= 生成エンタルピー − 比熱 × 298.15 K
85 int64_t ZeroKelvinEnthalpy(const SpeciesDefinition& species) {
86 const int64_t sensibleMicrojoules = static_cast<int64_t>(species.heatCapacity) *
88
89 return species.formationEnthalpy -
90 FxDivS64(sensibleMicrojoules, static_cast<int64_t>(MICROJOULES_PER_JOULE));
91 }
92
93 std::expected<void, std::string> BakeSpecies(const SpeciesDefinition& definition, const NameIndex& elements,
94 const ReactionTableDefinition& table, BakedReactionTable& baked) {
95 const size_t elementCount = elements.size();
96 const size_t base = baked.speciesElements.size();
97 baked.speciesElements.resize(base + elementCount, 0);
98 uint64_t molarMass = 0;
99 for (const ElementCount& part : definition.composition) {
100 const auto element = elements.find(part.element);
101 if (element == elements.end())
102 return std::unexpected(std::format("物質 {}: 知らない元素 {}", definition.name, part.element));
103
104 baked.speciesElements[base + element->second] += part.count;
105 molarMass += uint64_t{part.count} * table.elements[element->second].atomicMass;
106 }
107
108 if (definition.heatCapacity == 0 || molarMass == 0 || molarMass > UINT32_MAX)
109 return std::unexpected(std::format("物質 {}: 比熱かモル質量が 0 か大きすぎる", definition.name));
110
111 baked.species.push_back({.h0 = ZeroKelvinEnthalpy(definition),
112 .heatCapacity = definition.heatCapacity,
113 .ruleBegin = 0,
114 .ruleCount = 0,
115 .conductivity = definition.thermalConductivity});
116 baked.speciesNames.push_back(definition.name);
117 baked.molarMasses.push_back(static_cast<uint32_t>(molarMass));
118
119 return {};
120 }
121
122 std::expected<NameIndex, std::string> BakeAllSpecies(const ReactionTableDefinition& definition,
123 const NameIndex& elements, BakedReactionTable& baked) {
124 // ID 0 は「無し」
125 baked.species.push_back({});
126 baked.speciesNames.emplace_back();
127 baked.molarMasses.push_back(0);
128 baked.speciesElements.resize(elements.size(), 0);
129
130 NameIndex species;
131 for (const SpeciesDefinition& entry : definition.species) {
132 const auto id = static_cast<uint32_t>(baked.species.size());
133 if (!species.emplace(entry.name, id).second)
134 return std::unexpected(std::format("物質 {} が 2 回ある", entry.name));
135
136 if (auto result = BakeSpecies(entry, elements, definition, baked); !result)
137 return std::unexpected(result.error());
138 }
139
140 return species;
141 }
142
143 // --- 規則 ---
144
145 struct TermsResult {
146 uint32_t count = 0;
147 uint32_t firstOrderMask = 0;
148 };
149
150 // 反応物か生成物の並びを規則に書き写す(ids・coefficients は RxRule の配列)
151 std::expected<TermsResult, std::string> CopyTerms(const RuleDefinition& rule, std::span<const RuleTerm> terms,
152 const NameIndex& species, std::span<uint32_t> ids,
153 std::span<uint32_t> coefficients) {
154 if (terms.empty() || terms.size() > ids.size())
155 return std::unexpected(std::format("規則 {}: 反応物・生成物は 1〜{} 個", rule.name, ids.size()));
156
157 TermsResult result;
158 for (const RuleTerm& term : terms) {
159 const auto found = species.find(term.species);
160 if (found == species.end())
161 return std::unexpected(std::format("規則 {}: 知らない物質 {}", rule.name, term.species));
162
163 if (term.coefficient == 0)
164 return std::unexpected(std::format("規則 {}: {} の係数が 0", rule.name, term.species));
165
166 if (std::find(ids.begin(), ids.begin() + result.count, found->second) != ids.begin() + result.count)
167 return std::unexpected(std::format("規則 {}: {} が 2 回ある", rule.name, term.species));
168
169 if (term.firstOrder)
170 result.firstOrderMask |= 1u << result.count;
171
172 ids[result.count] = found->second;
173 coefficients[result.count] = term.coefficient;
174 result.count += 1;
175 }
176
177 return result;
178 }
179
180 // 元素の釣り合い(02 §2 の 1): 反応物と生成物で、元素ごとの数が完全に一致する
181 std::expected<void, std::string> CheckElementBalance(const RuleDefinition& definition, const RxRule& rule,
182 const BakedReactionTable& baked) {
183 const size_t elementCount = baked.elementNames.size();
184 for (size_t element = 0; element < elementCount; ++element) {
185 int64_t balance = 0;
186 for (uint32_t i = 0; i < rule.reactantCount; ++i) {
187 const uint32_t count = baked.speciesElements[(rule.reactants[i] * elementCount) + element];
188 balance -= int64_t{rule.reactantCoefficients[i]} * count;
189 }
190
191 for (uint32_t i = 0; i < rule.productCount; ++i) {
192 const uint32_t count = baked.speciesElements[(rule.products[i] * elementCount) + element];
193 balance += int64_t{rule.productCoefficients[i]} * count;
194 }
195
196 if (balance != 0) {
197 return std::unexpected(std::format("規則 {}: 元素 {} が釣り合わない(生成物 − 反応物 = {})",
198 definition.name, baked.elementNames[element], balance));
199 }
200 }
201
202 return {};
203 }
204
205 // 反応熱(02 §2 の 2): 生成物の H0 − 反応物の H0。手で書かないので、エネルギーの釣り合いは構造的に守られる
206 int64_t ReactionEnthalpy(const RxRule& rule, const BakedReactionTable& baked) {
207 int64_t enthalpy = 0;
208 for (uint32_t i = 0; i < rule.productCount; ++i)
209 enthalpy += int64_t{rule.productCoefficients[i]} * baked.species[rule.products[i]].h0;
210
211 for (uint32_t i = 0; i < rule.reactantCount; ++i)
212 enthalpy -= int64_t{rule.reactantCoefficients[i]} * baked.species[rule.reactants[i]].h0;
213
214 return enthalpy;
215 }
216
217 std::expected<RxRule, std::string> BakeRule(const RuleDefinition& definition, const NameIndex& species,
218 const BakedReactionTable& baked) {
219 RxRule rule{};
220 const auto reactants = CopyTerms(definition, definition.reactants, species, rule.reactants,
221 rule.reactantCoefficients);
222 if (!reactants)
223 return std::unexpected(reactants.error());
224
225 const auto products = CopyTerms(definition, definition.products, species, rule.products,
226 rule.productCoefficients);
227 if (!products)
228 return std::unexpected(products.error());
229
230 const int firstOrderCount = std::popcount(reactants->firstOrderMask);
231 if (firstOrderCount < 1 || firstOrderCount > 2)
232 return std::unexpected(std::format("規則 {}: 次数 1 の反応物は 1〜2 個", definition.name));
233
234 rule.reactantCount = reactants->count;
235 rule.productCount = products->count;
236 rule.firstOrderMask = reactants->firstOrderMask;
237 if (auto balance = CheckElementBalance(definition, rule, baked); !balance)
238 return std::unexpected(balance.error());
239
240 rule.reactionEnthalpy = ReactionEnthalpy(rule, baked);
241 rule.key = HashName(definition.name);
242
243 return rule;
244 }
245
246 // 文献の反応熱(書いた規則だけ)と、生成エンタルピーの差(298.15 K)を比べる(02 §2 の 2)。食い違えば警告の文を返す。
247 // 表は生成エンタルピーの差を使う(手で書いた値は使わない)ので、食い違いはエネルギーの保存を壊さない
248 std::optional<std::string> CheckDeclaredEnthalpy(const RuleDefinition& rule,
249 const ReactionTableDefinition& definition,
250 const NameIndex& species) {
251 if (!rule.declaredReactionEnthalpy)
252 return std::nullopt;
253
254 // 物質 ID は 1 から(0 は「無し」)。項の物質は BakeRule が確かめ済み
255 const auto formation = [&](const RuleTerm& term) {
256 const SpeciesDefinition& entry = definition.species[species.find(term.species)->second - 1];
257
258 return int64_t{term.coefficient} * entry.formationEnthalpy;
259 };
260
261 int64_t standard = 0;
262 for (const RuleTerm& term : rule.products)
263 standard += formation(term);
264
265 for (const RuleTerm& term : rule.reactants)
266 standard -= formation(term);
267
268 const int64_t difference = *rule.declaredReactionEnthalpy - standard;
270 return std::nullopt;
271
272 return std::format(
273 "規則 {}: 書いた反応熱 {} J/mol が生成エンタルピーの差 {} J/mol と {} J/mol 違う"
274 "(表は生成エンタルピーの差を使う)",
275 rule.name, *rule.declaredReactionEnthalpy, standard, difference);
276 }
277
278 // --- 速度の表 ---
279
280 // 温度 kelvin(K)での 1 刻みあたりの速度の係数を、仮数 32bit × 2^指数 に詰める(0 K は 0)。
281 // ln k = ln A − Ea / RT − ln 60(1 刻み)+ (n − 1)(ln 1e-6 − ln V)(µmol とセルの体積への換算。n = 次数 1 の反応物の数)
282 std::expected<uint64_t, std::string> BakeRate(const RuleDefinition& definition, int64_t lnConstantPart,
283 uint32_t kelvin) {
284 if (kelvin == 0)
285 return 0;
286
287 const uint64_t activation = static_cast<uint64_t>(definition.rate.activationEnergy) * NANOJOULES_PER_JOULE;
288 const FxU128 numerator = {.hi = activation >> 32, .lo = activation << 32};
289 const uint64_t divisor = GAS_CONSTANT_NANOJOULES * kelvin;
290 const auto activationOverRt = static_cast<int64_t>(FxDivU128By64(numerator, divisor).quotient);
291 const int64_t lnRate = lnConstantPart - activationOverRt;
292
293 // ln → log2 → 整数の部分と端数に分け、端数を 2^端数(Q31 の仮数)にする
294 const int64_t log2Rate = FxMulShiftS64(lnRate, static_cast<int64_t>(FX_LOG2E_Q62), 62);
295 const int64_t integerPart = log2Rate >> 32; // 算術シフト = 下への切り捨て
296 const auto fraction = static_cast<int64_t>(static_cast<uint64_t>(log2Rate) & FX_LOW32_MASK);
297 const int64_t exponent = integerPart - 31;
298 if (exponent < RATE_EXPONENT_MIN)
299 return 0;
300
301 if (exponent > RATE_EXPONENT_MAX)
302 return std::unexpected(std::format("規則 {}: 速度が大きすぎる({} K)", definition.name, kelvin));
303
304 const uint64_t mantissa = FxExp2Q32(fraction, 31);
305
306 return (mantissa << 32) | static_cast<uint32_t>(static_cast<int32_t>(exponent));
307 }
308
309 std::expected<void, std::string> BakeRates(const RuleDefinition& definition, const RxRule& rule,
310 BakedReactionTable& baked) {
311 // Ea の上限は nJ にして 64bit に収まる範囲(約 1.8e10 J/mol)より十分小さく
312 constexpr int64_t ACTIVATION_ENERGY_MAX = 1000000000;
313 const int64_t activationEnergy = definition.rate.activationEnergy;
314 if (definition.rate.preExponentialMantissa == 0 || activationEnergy < 0 ||
315 activationEnergy > ACTIVATION_ENERGY_MAX)
316 return std::unexpected(std::format("規則 {}: 速度の A が 0 か Ea が範囲の外", definition.name));
317
318 // A の仮数の末尾の 0 を指数へ移す(28e18 と 280e17 が同じ表になる。T-0021)
319 uint64_t mantissa = definition.rate.preExponentialMantissa;
320 int64_t exponent10 = definition.rate.preExponentialExponent10;
321 while (mantissa % 10 == 0) {
322 mantissa /= 10;
323 exponent10 += 1;
324 }
325
326 const int64_t ln10 = FxLnU64(10);
327 const int64_t ln2 = FxLnU64(2);
328 const int64_t lnPreExponential = FxLnU64(mantissa) + (exponent10 * ln10);
329 const int64_t lnPerTick = -FxLnU64(TICKS_PER_SECOND);
330 const int64_t lnAmountPerVolume = (-6 * ln10) - (CELL_VOLUME_LOG2 * ln2);
331 const int64_t order = std::popcount(rule.firstOrderMask);
332 const int64_t lnConstantPart = lnPreExponential + lnPerTick + ((order - 1) * lnAmountPerVolume);
333 for (uint32_t kelvin = 0; kelvin < RX_RATE_TABLE_KELVINS; ++kelvin) {
334 const auto rate = BakeRate(definition, lnConstantPart, kelvin);
335 if (!rate)
336 return std::unexpected(rate.error());
337
338 baked.rates.push_back(*rate);
339 }
340
341 return {};
342 }
343
344 // 規則を「ID が最小の反応物」の索引に入れる(1 つの規則は 1 つの物質の一覧にだけ入る = 重複なく引ける)
346 const auto ruleCount = static_cast<uint32_t>(baked.rules.size());
347 for (uint32_t speciesId = 1; speciesId < baked.species.size(); ++speciesId) {
348 RxSpecies& species = baked.species[speciesId];
349 species.ruleBegin = static_cast<uint32_t>(baked.ruleIndex.size());
350 for (uint32_t ruleId = 0; ruleId < ruleCount; ++ruleId) {
351 const RxRule& rule = baked.rules[ruleId];
352 const uint32_t* reactantsEnd = rule.reactants + rule.reactantCount;
353 if (*std::min_element(rule.reactants, reactantsEnd) == speciesId)
354 baked.ruleIndex.push_back(ruleId);
355 }
356
357 species.ruleCount = static_cast<uint32_t>(baked.ruleIndex.size()) - species.ruleBegin;
358 }
359
360 // GPU のバッファを 0 バイトにしないため
361 if (baked.ruleIndex.empty())
362 baked.ruleIndex.push_back(0);
363 }
364
365 } // namespace
366
367 uint32_t BakedReactionTable::SpeciesId(std::string_view name) const {
368 const auto found = std::find(speciesNames.begin() + 1, speciesNames.end(), name);
369
370 return found == speciesNames.end() ? 0 : static_cast<uint32_t>(found - speciesNames.begin());
371 }
372
373 std::expected<BakedReactionTable, std::string> BakeReactionTable(const ReactionTableDefinition& definition) {
374 const ReactionTableDefinition ordered = CanonicalOrder(definition);
375 BakedReactionTable baked;
376 const auto elements = IndexElements(ordered, baked);
377 if (!elements)
378 return std::unexpected(elements.error());
379
380 const auto species = BakeAllSpecies(ordered, *elements, baked);
381 if (!species)
382 return std::unexpected(species.error());
383
384 NameIndex ruleNames;
385 for (const RuleDefinition& entry : ordered.rules) {
386 if (!ruleNames.emplace(entry.name, static_cast<uint32_t>(ruleNames.size())).second)
387 return std::unexpected(std::format("規則 {} が 2 回ある", entry.name));
388
389 const auto rule = BakeRule(entry, *species, baked);
390 if (!rule)
391 return std::unexpected(rule.error());
392
393 if (auto rates = BakeRates(entry, *rule, baked); !rates)
394 return std::unexpected(rates.error());
395
396 if (auto warning = CheckDeclaredEnthalpy(entry, ordered, *species))
397 baked.warnings.push_back(*warning);
398
399 baked.rules.push_back(*rule);
400 baked.ruleNames.push_back(entry.name);
401 }
402
403 BuildRuleIndex(baked);
404
405 return baked;
406 }
407
408 // --- セル ---
409
410 namespace {
411
412 // 成分を足して温度からエネルギーを決める(Cell = RxCell か RxWideCell。cell は空のセル)
413 template <typename Cell>
414 Cell FillReactionCell(const BakedReactionTable& table, Cell cell, std::span<const SpeciesAmount> amounts,
415 int32_t temperatureMilliKelvin) {
416 for (const SpeciesAmount& entry : amounts)
417 cell = RxAddSpecies(cell, entry.species, entry.amount);
418
419 // 熱(µJ)= 熱容量(nJ/K)× 温度(mK)÷ 1e6
420 const int64_t chemical = RxChemicalEnergy(table.View(), cell);
421 const uint64_t heatCapacity = RxHeatCapacity(table.View(), cell);
422 const FxU128 product = FxMulU64Full(heatCapacity, static_cast<uint64_t>(temperatureMilliKelvin));
423 const auto heat = static_cast<int64_t>(FxDivU128By64(product, RX_MILLIKELVIN_NUMERATOR).quotient);
424
425 // mJ に切り上げる(C++ の割り算は 0 方向なので、負の数はそのまま切り上げになる)
426 const int64_t total = chemical + heat;
427 const auto perMillijoule = static_cast<int64_t>(RX_MICROJOULES_PER_MILLIJOULE);
428 cell.energy = total >= 0 ? (total + perMillijoule - 1) / perMillijoule : total / perMillijoule;
429
430 return cell;
431 }
432
433 template <typename Cell>
434 std::vector<uint64_t> CountCellElements(const BakedReactionTable& table, const Cell& cell) {
435 const size_t elementCount = table.elementNames.size();
436 std::vector<uint64_t> counts(elementCount, 0);
437 for (uint32_t i = 0; i < cell.speciesCount; ++i) {
438 for (size_t element = 0; element < elementCount; ++element) {
439 const uint32_t perSpecies = table.speciesElements[(cell.species[i] * elementCount) + element];
440 counts[element] += cell.amounts[i] * perSpecies;
441 }
442 }
443
444 return counts;
445 }
446
447 template <typename Cell>
448 uint64_t HashCell(const Cell& cell) {
449 uint64_t hash = FxHashCombine(0, static_cast<uint64_t>(cell.energy));
450 hash = FxHashCombine(hash, cell.speciesCount);
451 for (uint32_t i = 0; i < cell.speciesCount; ++i) {
452 hash = FxHashCombine(hash, cell.species[i]);
453 hash = FxHashCombine(hash, cell.amounts[i]);
454 }
455
456 return hash;
457 }
458
459 } // namespace
460
461 RxCell MakeReactionCell(const BakedReactionTable& table, std::span<const SpeciesAmount> amounts,
462 int32_t temperatureMilliKelvin) {
463 return FillReactionCell(table, RxMakeEmptyCell(0), amounts, temperatureMilliKelvin);
464 }
465
466 RxWideCell MakeWideReactionCell(const BakedReactionTable& table, std::span<const SpeciesAmount> amounts,
467 int32_t temperatureMilliKelvin) {
468 return FillReactionCell(table, RxMakeEmptyWideCell(0), amounts, temperatureMilliKelvin);
469 }
470
471 std::vector<uint64_t> CountElements(const BakedReactionTable& table, const RxCell& cell) {
472 return CountCellElements(table, cell);
473 }
474
475 std::vector<uint64_t> CountElements(const BakedReactionTable& table, const RxWideCell& cell) {
476 return CountCellElements(table, cell);
477 }
478
479 uint64_t HashReactionCell(const RxCell& cell) {
480 return HashCell(cell);
481 }
482
483 uint64_t HashReactionCell(const RxWideCell& cell) {
484 return HashCell(cell);
485 }
486
487 RxLoneCell AdvanceLoneReactionCell(const BakedReactionTable& table, const RxLoneCell& lone, uint64_t worldSeed,
488 uint64_t tickBegin, uint32_t tickCount, uint64_t cellId) {
489 return RxAdvanceLoneCell(table.View(), lone, worldSeed, tickBegin, tickCount, cellId);
490 }
491
492 RxWaitStep StepReactionCellWait(const BakedReactionTable& table, const RxCell& cell, uint64_t worldSeed,
493 uint64_t tick, uint64_t changedTick, uint64_t cellId) {
494 return RxStepCellWait(table.View(), cell, worldSeed, tick, changedTick, cellId);
495 }
496
497 RxWideWaitStep StepReactionCellWait(const BakedReactionTable& table, const RxWideCell& cell, uint64_t worldSeed,
498 uint64_t tick, uint64_t changedTick, uint64_t cellId) {
499 return RxStepCellWait(table.View(), cell, worldSeed, tick, changedTick, cellId);
500 }
501
502} // namespace bicameral::sim
RxWideCell RxMakeEmptyWideCell(int64_t energy)
RxWaitStepOf< RxWideCell > RxWideWaitStep
std::vector< uint64_t > CountCellElements(const BakedReactionTable &table, const Cell &cell)
std::expected< void, std::string > BakeRates(const RuleDefinition &definition, const RxRule &rule, BakedReactionTable &baked)
std::expected< TermsResult, std::string > CopyTerms(const RuleDefinition &rule, std::span< const RuleTerm > terms, const NameIndex &species, std::span< uint32_t > ids, std::span< uint32_t > coefficients)
std::expected< NameIndex, std::string > BakeAllSpecies(const ReactionTableDefinition &definition, const NameIndex &elements, BakedReactionTable &baked)
std::optional< std::string > CheckDeclaredEnthalpy(const RuleDefinition &rule, const ReactionTableDefinition &definition, const NameIndex &species)
std::expected< RxRule, std::string > BakeRule(const RuleDefinition &definition, const NameIndex &species, const BakedReactionTable &baked)
std::expected< uint64_t, std::string > BakeRate(const RuleDefinition &definition, int64_t lnConstantPart, uint32_t kelvin)
Cell FillReactionCell(const BakedReactionTable &table, Cell cell, std::span< const SpeciesAmount > amounts, int32_t temperatureMilliKelvin)
std::expected< NameIndex, std::string > IndexElements(const ReactionTableDefinition &definition, BakedReactionTable &baked)
int64_t ReactionEnthalpy(const RxRule &rule, const BakedReactionTable &baked)
ReactionTableDefinition CanonicalOrder(const ReactionTableDefinition &definition)
int64_t ZeroKelvinEnthalpy(const SpeciesDefinition &species)
std::expected< void, std::string > CheckElementBalance(const RuleDefinition &definition, const RxRule &rule, const BakedReactionTable &baked)
std::expected< void, std::string > BakeSpecies(const SpeciesDefinition &definition, const NameIndex &elements, const ReactionTableDefinition &table, BakedReactionTable &baked)
std::map< std::string, uint32_t, std::less<> > NameIndex
uint64_t HashReactionCell(const RxCell &cell)
RxCell MakeReactionCell(const BakedReactionTable &table, std::span< const SpeciesAmount > amounts, int32_t temperatureMilliKelvin)
RxLoneCell AdvanceLoneReactionCell(const BakedReactionTable &table, const RxLoneCell &lone, uint64_t worldSeed, uint64_t tickBegin, uint32_t tickCount, uint64_t cellId)
std::expected< BakedReactionTable, std::string > BakeReactionTable(const ReactionTableDefinition &definition)
RxWaitStep StepReactionCellWait(const BakedReactionTable &table, const RxCell &cell, uint64_t worldSeed, uint64_t tick, uint64_t changedTick, uint64_t cellId)
std::vector< uint64_t > CountElements(const BakedReactionTable &table, const RxCell &cell)
RxWideCell MakeWideReactionCell(const BakedReactionTable &table, std::span< const SpeciesAmount > amounts, int32_t temperatureMilliKelvin)
std::vector< std::string > speciesNames
std::vector< std::string > ruleNames
std::vector< uint32_t > ruleIndex
std::vector< std::string > warnings
uint32_t SpeciesId(std::string_view name) const
std::vector< uint32_t > speciesElements
std::vector< reaction::RxSpecies > species
std::vector< reaction::RxRule > rules
std::vector< uint32_t > molarMasses
std::vector< std::string > elementNames
ReactionTableView View() const
std::vector< ElementDefinition > elements
std::vector< SpeciesDefinition > species
std::vector< RuleDefinition > rules
std::vector< RuleTerm > reactants
std::vector< RuleTerm > products
std::optional< int64_t > declaredReactionEnthalpy
std::vector< ElementCount > composition