moonbit-thermochem

Thermochemical property calculations for MoonBit, including NASA polynomials, reaction enthalpy, and flame-temperature estimates.

thermochemistry
nasa-polynomial
combustion
enthalpy
moonbit
moon add Lxxbv/moonbit-thermochem@0.1.3
Download zip
Author
Version
0.1.3
License
Apache-2.0
Last updated
5 hours ago
Downloads
8
README

#moonbit-thermochem

Thermochemical property calculations for MoonBit.

Status: usable 0.1.3 release candidate.

This package evaluates NASA7 heat-capacity, enthalpy, and entropy polynomials; defines species and reactions; parses common reaction equations; checks elemental balance; and includes a small, documented data set for combustion and ammonia-synthesis examples. Values use SI molar units by default: J/mol/K for heat capacity and entropy, and J/mol for enthalpy.

moon add Lxxbv/moonbit-thermochem

The core examples are checked as part of this checkout. Examples using the separate data and parser packages are marked nocheck: MoonBit README doc tests compile the root package and do not load sibling package imports.

#Look Up A Species

The bundled data set contains CH4, O2, N2, CO2, H2O, CO, H2, and NH3 gas species.

let methane = @data.get_species("CH4")
inspect(methane.name, content="CH4")

#Evaluate Heat Capacity

Species::cp_molar selects the NASA7 segment covering the requested temperature. It raises ThermoError::NoThermoSegment outside the species data range.

///|
test "evaluate a NASA7 heat capacity" {
let segment = Nasa7Segment::new(
range=TemperatureRange::new(lower=200.0, upper=3500.0),
a1=3.5,
a2=0.0,
a3=0.0,
a4=0.0,
a5=0.0,
a6=0.0,
a7=0.0,
)
let hydrogen = Species::new(
name="H2",
formula=parse_formula("H2"),
phase=Phase::Gas,
molar_mass=2.016,
formation_enthalpy=0.0,
model=ThermoModel::Nasa7([segment]),
)
inspect(hydrogen.cp_molar(temperature=1200.0) > 0.0, content="true")
}

#Calculate Reaction Enthalpy

Reaction enthalpy is the stoichiometric product enthalpy minus the stoichiometric reactant enthalpy at the same temperature.

///|
test "calculate reaction enthalpy" {
let formula = parse_formula("H2")
let reactant = Species::new(
name="H2",
formula~,
phase=Phase::Gas,
molar_mass=2.016,
formation_enthalpy=0.0,
model=ThermoModel::ConstantEnthalpy(10.0),
)
let product = Species::new(
name="H2O",
formula=parse_formula("H2O"),
phase=Phase::Gas,
molar_mass=18.015,
formation_enthalpy=0.0,
model=ThermoModel::ConstantEnthalpy(4.0),
)
let reaction = Reaction::new(
label="H2 -> H2O",
reactants=[StoichTerm::new(species=reactant, coefficient=1.0)],
products=[StoichTerm::new(species=product, coefficient=1.0)],
)
inspect(reaction.enthalpy(temperature=298.15), content="-6")
}

#Parse And Check Reactions

The root package can parse simple stoichiometric equations with integer or decimal coefficients, then report elemental balance before a reaction is bound to a species data set.

///|
test "parse and check reaction balance" {
let parsed = parse_reaction_equation(
"CH4 + 2 O2 + 7.52 N2 -> CO2 + 2 H2O + 7.52 N2",
)
inspect(parsed.is_elementally_balanced(), content="true")
inspect(parsed.element_balance().length(), content="4")
}

#Formula And Element Utilities

Formula parsing combines repeated symbols and can compute molar mass, per element mass contribution, and mass fraction from the built-in periodic table helpers. The element catalog also exposes period, group, block, standard phase, and a natural-abundance flag for lightweight validation and reporting.

///|
test "formula mass and unit helpers" {
let methane = parse_formula("CH4")
inspect(methane.molar_mass() > 16.0, content="true")
inspect(methane.mass_fraction("C") > 0.7, content="true")
inspect(element_group("O"), content="16")
inspect(j_per_mol_to_kj_per_mol(12500.0), content="12.5")
}

#Parse Compact NASA7 Data

The parser accepts a compact, whitespace-separated three-line record: a header followed by LOW and HIGH rows with seven coefficients each. The species name must also be a parseable chemical formula.

let text =
#|H2 G 200.0 1000.0 3500.0
#|LOW 2.34433112 0.00798052075 -0.000019478151 0.0000000201572094 -0.00000000000737611761 -917.935173 0.683010238
#|HIGH 3.3372792 -0.000049402473 0.000000499456778 -0.00000000017958886 0.000000000000020025227 -950.158922 -3.20502331
#|
let species = @parser.parse_nasa7_thermo(text)
inspect(species[0].name, content="H2")

#CLI Demo

Run the deterministic examples from a checkout:

moon run cmd/thermochem -- species CH4 1200 moon run cmd/thermochem -- reaction methane-combustion 298.15 moon run cmd/thermochem -- flame methane-air 298.15 moon run cmd/thermochem -- ammonia 700

For data provenance and scope, see data sources. See design notes for package boundaries and numerical methods.

#Reproducible Benchmark

The benchmarks package evaluates five bundled species at seven temperatures for a fixed 100,000-iteration workload and prints a checksum. The checksum is an integrity guard so a benchmark run cannot silently omit the calculation. Run it with the target supported by your platform:

moon run benchmarks --release --target native moon run benchmarks --release --target js

The benchmark is an end-to-end package workload, not a claim about a single function's throughput. Recordings and the exact command are in benchmark results.

#Continuous Integration

GitHub Actions runs formatting, warnings-as-errors checks, tests, generated API checks, and the reproducible benchmark on Ubuntu, macOS, and Windows. Windows runs the JS target because the current Windows MinGW environment used by the stable MoonBit toolchain does not expose rand_s required by the native runtime; the project itself does not use randomness. Unix runners execute the full target matrix.

#
ThermoError

pub(all) suberror ThermoError {
InvalidTemperatureRange(lower~ : Double, upper~ : Double)
TemperatureOutOfRange(value~ : Double, lower~ : Double, upper~ : Double)
NoThermoSegment(species~ : String, temperature~ : Double)
MissingSpecies(name~ : String)
UnknownElement(symbol~ : String)
ParseError(line~ : Int, column~ : Int, message~ : String)
SolverFailed(message~ : String)
} derive(Eq,
Debug
)

#
ElementBalance

pub(all) struct ElementBalance {
symbol : String
reactant_atoms : Double
product_atoms : Double
difference : Double
} derive(Eq,
Debug
)

#
ElementBalance::is_balanced

fn ElementBalance::is_balanced(self : ElementBalance, tolerance? : Double) -> Bool

#
ElementCount

pub(all) struct ElementCount {
symbol : String
count : Int
} derive(Eq,
Debug
)

#
ElementInfo

pub(all) struct ElementInfo {
symbol : String
name : String
atomic_number : Int
atomic_weight : Double
} derive(Eq,
Debug
)

#
ElementMass

pub(all) struct ElementMass {
symbol : String
count : Int
atomic_weight : Double
total_mass : Double
mass_fraction : Double
} derive(Eq,
Debug
)

#
EnergyUnit

pub(all) enum EnergyUnit {
JoulePerMol
KilojoulePerMol
} derive(Eq,
Debug
)

#
Formula

pub(all) struct Formula {
elements : Array[ElementCount]
} derive(Eq,
Debug
)

#
Formula::count

fn Formula::count(self : Formula, symbol : String) -> Int

#
Formula::element_mass

fn Formula::element_mass(self : Formula, symbol : String) -> Double raise ThermoError

#
Formula::element_masses

fn Formula::element_masses(self : Formula) -> Array[ElementMass] raise ThermoError

#
Formula::mass_fraction

fn Formula::mass_fraction(self : Formula, symbol : String) -> Double raise ThermoError

#
Formula::molar_mass

fn Formula::molar_mass(self : Formula) -> Double raise ThermoError

#
Formula::to_formula_string

fn Formula::to_formula_string(self : Formula) -> String

#
HeatCapacityUnit

pub(all) enum HeatCapacityUnit {
JoulePerMolKelvin
KilojoulePerMolKelvin
} derive(Eq,
Debug
)

#
Mixture

pub(all) struct Mixture {
amounts : Array[SpeciesAmount]
} derive(Eq,
Debug
)

#
Mixture::new

fn Mixture::new(amounts~ : Array[SpeciesAmount]) -> Mixture

#
Nasa7Segment

pub(all) struct Nasa7Segment {
range : TemperatureRange
a1 : Double
a2 : Double
a3 : Double
a4 : Double
a5 : Double
a6 : Double
a7 : Double
} derive(Eq,
Debug
)

#
Nasa7Segment::cp_over_r

fn Nasa7Segment::cp_over_r(self : Nasa7Segment, temperature : Double) -> Double raise ThermoError

#
Nasa7Segment::h_over_rt

fn Nasa7Segment::h_over_rt(self : Nasa7Segment, temperature : Double) -> Double raise ThermoError

#
Nasa7Segment::new

fn Nasa7Segment::new(range~ : TemperatureRange, a1~ : Double, a2~ : Double, a3~ : Double, a4~ : Double, a5~ : Double, a6~ : Double, a7~ : Double) -> Nasa7Segment

#
Nasa7Segment::s_over_r

fn Nasa7Segment::s_over_r(self : Nasa7Segment, temperature : Double) -> Double raise ThermoError

#
ParsedReaction

pub(all) struct ParsedReaction {
label : String
reactants : Array[ParsedStoichTerm]
products : Array[ParsedStoichTerm]
} derive(Eq,
Debug
)

#
ParsedReaction::element_balance

fn ParsedReaction::element_balance(self : ParsedReaction) -> Array[ElementBalance]

#
ParsedReaction::is_elementally_balanced

fn ParsedReaction::is_elementally_balanced(self : ParsedReaction, tolerance? : Double) -> Bool

#
ParsedReaction::to_reaction

fn ParsedReaction::to_reaction(self : ParsedReaction, lookup : (String) -> Species raise ThermoError) -> Reaction raise ThermoError

#
ParsedStoichTerm

pub(all) struct ParsedStoichTerm {
name : String
formula : Formula
coefficient : Double
} derive(Eq,
Debug
)

#
Phase

pub(all) enum Phase {
Gas
Liquid
Solid
} derive(Eq,
Debug
)

#
Reaction

pub(all) struct Reaction {
label : String
reactants : Array[StoichTerm]
products : Array[StoichTerm]
} derive(Eq,
Debug
)

#
Reaction::element_balance

fn Reaction::element_balance(self : Reaction) -> Array[ElementBalance]

#
Reaction::enthalpy

fn Reaction::enthalpy(self : Reaction, temperature~ : Double) -> Double raise ThermoError

#
Reaction::is_elementally_balanced

fn Reaction::is_elementally_balanced(self : Reaction, tolerance? : Double) -> Bool

#
Reaction::new

fn Reaction::new(label~ : String, reactants~ : Array[StoichTerm], products~ : Array[StoichTerm]) -> Reaction

#
Species

pub(all) struct Species {
name : String
formula : Formula
phase : Phase
molar_mass : Double
formation_enthalpy : Double
model : ThermoModel
} derive(Eq,
Debug
)

#
Species::cp_molar

fn Species::cp_molar(self : Species, temperature~ : Double) -> Double raise ThermoError

#
Species::enthalpy_molar

fn Species::enthalpy_molar(self : Species, temperature~ : Double) -> Double raise ThermoError

#
Species::entropy_molar

fn Species::entropy_molar(self : Species, temperature~ : Double) -> Double raise ThermoError

#
Species::new

fn Species::new(name~ : String, formula~ : Formula, phase~ : Phase, molar_mass~ : Double, formation_enthalpy~ : Double, model~ : ThermoModel) -> Species

#
SpeciesAmount

pub(all) struct SpeciesAmount {
species : Species
amount : Double
} derive(Eq,
Debug
)

#
SpeciesAmount::new

fn SpeciesAmount::new(species~ : Species, amount~ : Double) -> SpeciesAmount

#
StoichTerm

pub(all) struct StoichTerm {
species : Species
coefficient : Double
} derive(Eq,
Debug
)

#
StoichTerm::new

fn StoichTerm::new(species~ : Species, coefficient~ : Double) -> StoichTerm

#
TemperatureRange

pub(all) struct TemperatureRange {
lower : Double
upper : Double
} derive(Eq,
Debug
)

#
TemperatureRange::contains

fn TemperatureRange::contains(self : TemperatureRange, temperature : Double) -> Bool

#
TemperatureRange::new

fn TemperatureRange::new(lower~ : Double, upper~ : Double) -> TemperatureRange raise ThermoError

#
TemperatureRange::validate

fn TemperatureRange::validate(self : TemperatureRange, temperature : Double) -> Unit raise ThermoError

#
TemperatureUnit

pub(all) enum TemperatureUnit {
Kelvin
Celsius
} derive(Eq,
Debug
)

#
ThermoModel

pub(all) enum ThermoModel {
Nasa7(Array[Nasa7Segment])
ConstantEnthalpy(Double)
} derive(Eq,
Debug
)

#
adiabatic_flame_temperature

fn adiabatic_flame_temperature(reaction : Reaction, initial_temperature~ : Double, lower_bound? : Double, upper_bound? : Double) -> Double raise ThermoError

Solves the simplified adiabatic heat balance for final product temperature.

#
atomic_number

fn atomic_number(symbol : String) -> Int raise ThermoError

#
atomic_weight

fn atomic_weight(symbol : String) -> Double raise ThermoError

#
celsius_to_kelvin

fn celsius_to_kelvin(value : Double) -> Double

#
convert_molar_energy

fn convert_molar_energy(value : Double, from~ : EnergyUnit, to~ : EnergyUnit) -> Double

#
convert_molar_heat_capacity

fn convert_molar_heat_capacity(value : Double, from~ : HeatCapacityUnit, to~ : HeatCapacityUnit) -> Double

#
convert_temperature

fn convert_temperature(value : Double, from~ : TemperatureUnit, to~ : TemperatureUnit) -> Double

#
element_block

fn element_block(symbol : String) -> String raise ThermoError

#
element_group

fn element_group(symbol : String) -> Int raise ThermoError

#
element_info

fn element_info(symbol : String) -> ElementInfo raise ThermoError

#
element_is_naturally_abundant

fn element_is_naturally_abundant(symbol : String) -> Bool raise ThermoError

#
element_name

fn element_name(symbol : String) -> String raise ThermoError

#
element_period

fn element_period(symbol : String) -> Int raise ThermoError

#
element_standard_phase

fn element_standard_phase(symbol : String) -> String raise ThermoError

#
is_known_element

fn is_known_element(symbol : String) -> Bool

#
j_per_mol_k_to_kj_per_mol_k

fn j_per_mol_k_to_kj_per_mol_k(value : Double) -> Double

#
j_per_mol_to_kj_per_mol

fn j_per_mol_to_kj_per_mol(value : Double) -> Double

#
kelvin_to_celsius

fn kelvin_to_celsius(value : Double) -> Double

#
kj_per_mol_k_to_j_per_mol_k

fn kj_per_mol_k_to_j_per_mol_k(value : Double) -> Double

#
kj_per_mol_to_j_per_mol

fn kj_per_mol_to_j_per_mol(value : Double) -> Double

#
parse_formula

fn parse_formula(text : String) -> Formula raise ThermoError

#
parse_reaction_equation

fn parse_reaction_equation(text : String) -> ParsedReaction raise ThermoError

#
solve_bisection

fn solve_bisection(lower~ : Double, upper~ : Double, tolerance~ : Double, max_iterations~ : Int, f : (Double) -> Double raise ThermoError) -> Double raise ThermoError

Solves a bracketed scalar root by bisection.

Powered by MoonBit

Site sourceReport issuePackagesBuild queueSkillsStatistics

© 2026 mooncakes.io