14 using namespace bicameral::fx;
15 using namespace bicameral::reaction;
36 using NameIndex = std::map<std::string, uint32_t, std::less<>>;
40 for (
const char character : name) {
41 hash ^=
static_cast<uint8_t
>(character);
54 const auto byName = [](
const auto& left,
const auto& right) {
55 return left.name < right.name;
57 std::ranges::stable_sort(sorted.
elements, byName);
58 std::ranges::stable_sort(sorted.
species, byName);
59 std::ranges::stable_sort(sorted.
rules, byName);
75 if (!elements.emplace(element.
name,
static_cast<uint32_t
>(elements.size())).second)
76 return std::unexpected(std::format(
"元素 {} が 2 回ある", element.
name));
86 const int64_t sensibleMicrojoules =
static_cast<int64_t
>(species.
heatCapacity) *
95 const size_t elementCount = elements.size();
98 uint64_t molarMass = 0;
100 const auto element = elements.find(part.
element);
101 if (element == elements.end())
102 return std::unexpected(std::format(
"物質 {}: 知らない元素 {}", definition.
name, part.
element));
105 molarMass += uint64_t{part.
count} * table.
elements[element->second].atomicMass;
108 if (definition.
heatCapacity == 0 || molarMass == 0 || molarMass > UINT32_MAX)
109 return std::unexpected(std::format(
"物質 {}: 比熱かモル質量が 0 か大きすぎる", definition.
name));
117 baked.
molarMasses.push_back(
static_cast<uint32_t
>(molarMass));
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));
136 if (
auto result =
BakeSpecies(entry, elements, definition, baked); !result)
137 return std::unexpected(result.error());
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()));
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));
163 if (term.coefficient == 0)
164 return std::unexpected(std::format(
"規則 {}: {} の係数が 0", rule.
name, term.species));
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));
172 ids[result.
count] = found->second;
173 coefficients[result.
count] = term.coefficient;
184 for (
size_t element = 0; element < elementCount; ++element) {
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;
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;
197 return std::unexpected(std::format(
"規則 {}: 元素 {} が釣り合わない(生成物 − 反応物 = {})",
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;
211 for (uint32_t i = 0; i < rule.reactantCount; ++i)
212 enthalpy -= int64_t{rule.reactantCoefficients[i]} * baked.
species[rule.reactants[i]].h0;
220 const auto reactants =
CopyTerms(definition, definition.
reactants, species, rule.reactants,
221 rule.reactantCoefficients);
223 return std::unexpected(reactants.error());
225 const auto products =
CopyTerms(definition, definition.
products, species, rule.products,
226 rule.productCoefficients);
228 return std::unexpected(products.error());
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));
234 rule.reactantCount = reactants->count;
235 rule.productCount = products->count;
236 rule.firstOrderMask = reactants->firstOrderMask;
238 return std::unexpected(balance.error());
255 const auto formation = [&](
const RuleTerm& term) {
261 int64_t standard = 0;
263 standard += formation(term);
266 standard -= formation(term);
273 "規則 {}: 書いた反応熱 {} J/mol が生成エンタルピーの差 {} J/mol と {} J/mol 違う"
288 const FxU128 numerator = {.hi = activation >> 32, .lo = activation << 32};
290 const auto activationOverRt =
static_cast<int64_t
>(FxDivU128By64(numerator, divisor).quotient);
291 const int64_t lnRate = lnConstantPart - activationOverRt;
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;
302 return std::unexpected(std::format(
"規則 {}: 速度が大きすぎる({} K)", definition.
name, kelvin));
304 const uint64_t mantissa = FxExp2Q32(fraction, 31);
306 return (mantissa << 32) |
static_cast<uint32_t
>(
static_cast<int32_t
>(exponent));
312 constexpr int64_t ACTIVATION_ENERGY_MAX = 1000000000;
315 activationEnergy > ACTIVATION_ENERGY_MAX)
316 return std::unexpected(std::format(
"規則 {}: 速度の A が 0 か Ea が範囲の外", definition.
name));
321 while (mantissa % 10 == 0) {
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);
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);
336 return std::unexpected(rate.error());
338 baked.
rates.push_back(*rate);
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)
357 species.ruleCount =
static_cast<uint32_t
>(baked.
ruleIndex.size()) - species.ruleBegin;
376 const auto elements = IndexElements(ordered, baked);
378 return std::unexpected(elements.error());
380 const auto species = BakeAllSpecies(ordered, *elements, baked);
382 return std::unexpected(species.error());
386 if (!ruleNames.emplace(entry.
name,
static_cast<uint32_t
>(ruleNames.size())).second)
387 return std::unexpected(std::format(
"規則 {} が 2 回ある", entry.
name));
389 const auto rule = BakeRule(entry, *species, baked);
391 return std::unexpected(rule.error());
393 if (
auto rates = BakeRates(entry, *rule, baked); !rates)
394 return std::unexpected(rates.error());
396 if (
auto warning = CheckDeclaredEnthalpy(entry, ordered, *species))
399 baked.
rules.push_back(*rule);
403 BuildRuleIndex(baked);
413 template <
typename Cell>
415 int32_t temperatureMilliKelvin) {
417 cell = RxAddSpecies(cell, entry.species, entry.amount);
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);
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;
433 template <
typename Cell>
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;
447 template <
typename 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]);
462 int32_t temperatureMilliKelvin) {
463 return FillReactionCell(table, RxMakeEmptyCell(0), amounts, temperatureMilliKelvin);
467 int32_t temperatureMilliKelvin) {
472 return CountCellElements(table, cell);
476 return CountCellElements(table, cell);
480 return HashCell(cell);
484 return HashCell(cell);
488 uint64_t tickBegin, uint32_t tickCount, uint64_t cellId) {
489 return RxAdvanceLoneCell(table.
View(), lone, worldSeed, tickBegin, tickCount, cellId);
493 uint64_t tick, uint64_t changedTick, uint64_t cellId) {
494 return RxStepCellWait(table.
View(), cell, worldSeed, tick, changedTick, cellId);
498 uint64_t tick, uint64_t changedTick, uint64_t cellId) {
499 return RxStepCellWait(table.
View(), cell, worldSeed, tick, changedTick, cellId);
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)
constexpr uint64_t FNV_PRIME
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)
uint64_t HashName(std::string_view name)
constexpr int64_t DECLARED_ENTHALPY_TOLERANCE_JOULES
std::expected< uint64_t, std::string > BakeRate(const RuleDefinition &definition, int64_t lnConstantPart, uint32_t kelvin)
constexpr int64_t CELL_VOLUME_LOG2
constexpr uint64_t FNV_OFFSET
Cell FillReactionCell(const BakedReactionTable &table, Cell cell, std::span< const SpeciesAmount > amounts, int32_t temperatureMilliKelvin)
void BuildRuleIndex(BakedReactionTable &baked)
uint64_t HashCell(const Cell &cell)
constexpr int64_t STANDARD_TEMPERATURE_MILLIKELVIN
std::expected< NameIndex, std::string > IndexElements(const ReactionTableDefinition &definition, BakedReactionTable &baked)
int64_t ReactionEnthalpy(const RxRule &rule, const BakedReactionTable &baked)
constexpr uint64_t GAS_CONSTANT_NANOJOULES
ReactionTableDefinition CanonicalOrder(const ReactionTableDefinition &definition)
constexpr uint64_t NANOJOULES_PER_JOULE
int64_t ZeroKelvinEnthalpy(const SpeciesDefinition &species)
constexpr int32_t RATE_EXPONENT_MIN
constexpr uint64_t MICROJOULES_PER_JOULE
std::expected< void, std::string > CheckElementBalance(const RuleDefinition &definition, const RxRule &rule, const BakedReactionTable &baked)
constexpr int32_t RATE_EXPONENT_MAX
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)
int32_t preExponentialExponent10
uint64_t preExponentialMantissa
std::vector< std::string > speciesNames
std::vector< std::string > ruleNames
std::vector< uint64_t > rates
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
uint32_t thermalConductivity
int64_t formationEnthalpy