Stoichiometric Matrix
ChemistryLab.StoichMatrixChemistryLab.StoichMatrixChemistryLab.CanonicalStoichMatrixChemistryLab._exact_rankChemistryLab._rref_rationalChemistryLab.item_orderChemistryLab.mass_matrixChemistryLab.pprintChemistryLab.pprintChemistryLab.pull_primariesChemistryLab.push_primariesChemistryLab.same_componentsChemistryLab.speciationChemistryLab.union_atoms
ChemistryLab.item_order Method
item_order(::AbstractVector{<:AbstractSpecies}) -> Vector{Symbol}Return the ordering vector for components.
Returns ATOMIC_ORDER for Species vectors, OXIDE_ORDER for CemSpecies vectors.
ChemistryLab.same_components Method
same_components(::AbstractVector{<:AbstractSpecies}) -> FunctionReturn the function to extract components from species vectors.
Returns atoms_charge for Species vectors, oxides_charge for CemSpecies vectors.
ChemistryLab.speciation Method
speciation(species_list, atoms_list::AbstractVector{Symbol}; kwargs...) -> SubArray
speciation(species_list, short_species_list::AbstractVector{<:AbstractSpecies}; kwargs...) -> SubArray
speciation(species_list, short_species_list_symbols::AbstractVector{<:AbstractString}; kwargs...) -> SubArrayFilter a list of species to those whose atomic composition is a subset of the given atoms (or of the atoms found in short_species_list).
Arguments
species_list: full list of species to filter.atoms_list: vector of atom symbols that define the chemical space.short_species_list: seed species whose union of atoms defines the chemical space.short_species_list_symbols: string symbols of seed species.
Keyword arguments
aggregate_state: filter by aggregate state (default: all states).class: filter by species class (default: all classes).exclude_species: species (or symbols) to always exclude.include_species: species (or symbols) to always include regardless of composition.
Returns
- A view (SubArray) of
species_listcontaining the matching species.
Examples
julia> all_species = [Species("H2O"), Species("OH-"), Species("H+"), Species("Ca+2"), Species("CaCO3")];
julia> speciation(all_species, [:H, :O])
3-element view(::Vector{Species{Int64}}, [1, 2, 3]) with eltype Species{Int64}:
H₂O {H₂O} [H₂O ◆ H2O]
OH⁻ {OH⁻} [OH⁻ ◆ OH-]
H⁺ {H⁺} [H⁺ ◆ H+]ChemistryLab.union_atoms Function
union_atoms(atom_dicts::AbstractVector{<:AbstractDict}, order_vec=ATOMIC_ORDER) -> Vector{Symbol}Compute the union of all keys from dictionaries, sorted by a given order.
Arguments
atom_dicts: vector of dictionaries (e.g., atomic compositions).order_vec: ordering vector for sorting keys (default: ATOMIC_ORDER).
Returns
- Sorted vector of unique symbols appearing in any dictionary.
Examples
julia> d1 = OrderedDict(:H => 2, :O => 1);
julia> d2 = OrderedDict(:C => 1, :O => 2);
julia> union_atoms([d1, d2], ATOMIC_ORDER)
3-element Vector{Symbol}:
:C
:H
:OChemistryLab.StoichMatrix Type
StoichMatrix(species, candidate_primaries=species; involve_all_atoms=true)Construct a StoichMatrix from a list of species and a list of candidate primary species (by default the list of species itself).
Arguments
species: list of species.candidate_primaries: list of candidate primary species (default:species).involve_all_atoms: if true the algorithm is allowed to use species ofcandidate_primariescontaining atoms which are not inspecies(default: true).
Examples
julia> H₂O, H⁺, OH⁻, CO₂, HCO₃⁻, CO₃²⁻ = Species.(split("H₂O H⁺ OH⁻ CO₂ HCO₃⁻ CO₃²⁻"))
6-element Vector{Species{Int64}}:
H₂O {H₂O} [H₂O ◆ H2O]
H⁺ {H⁺} [H⁺ ◆ H+]
OH⁻ {OH⁻} [OH⁻ ◆ OH-]
CO₂ {CO₂} [CO₂ ◆ CO2]
HCO₃⁻ {HCO₃⁻} [HCO₃⁻ ◆ HCO3-]
CO₃²⁻ {CO₃²⁻} [CO₃²⁻ ◆ CO3-2]
julia> SM = StoichMatrix([H₂O, H⁺, OH⁻, CO₂, HCO₃⁻, CO₃²⁻])
┌─────┬─────┬────┬─────┬─────┬───────┬───────┐
│ │ H₂O │ H⁺ │ OH⁻ │ CO₂ │ HCO₃⁻ │ CO₃²⁻ │
├─────┼─────┼────┼─────┼─────┼───────┼───────┤
│ H₂O │ 1 │ │ 1 │ │ 1 │ 1 │
│ H⁺ │ │ 1 │ -1 │ │ -1 │ -2 │
│ CO₂ │ │ │ │ 1 │ 1 │ 1 │
└─────┴─────┴────┴─────┴─────┴───────┴───────┘
julia> SM.N # nullspace (columns are vectors of the kernel of the stoichiometric matrix)
6×3 Matrix{Int64}:
-1 -1 -1
1 1 2
1 0 0
0 -1 -1
0 1 0
0 0 1
julia> SM.A * SM.N
3×3 Matrix{Int64}:
0 0 0
0 0 0
0 0 0ChemistryLab.StoichMatrix Type
StoichMatrix{T,P}Container holding a stoichiometric matrix A together with the primaries (independent components) and the full species vector.
Fields
- `A`: matrix (components × species) of stoichiometric coefficients.
- `primaries`: vector of independent components (Symbols or `Species`).
- `species`: vector of `Species` corresponding to the columns of `A`.
- `N`: nullspace matrix — columns are vectors of the kernel of `A`.
Built analytically when primaries ⊆ species (trivial case); otherwise computed via
RREF over ℚ with integer coefficients (cleared denominators, reduced by GCD).Examples
julia> species = [Species("H₂O"), Species("OH⁻"), Species("H⁺")]
3-element Vector{Species{Int64}}:
H₂O {H₂O} [H₂O ◆ H2O]
OH⁻ {OH⁻} [OH⁻ ◆ OH-]
H⁺ {H⁺} [H⁺ ◆ H+]
julia> SM = StoichMatrix(species)
┌─────┬─────┬─────┬────┐
│ │ H₂O │ OH⁻ │ H⁺ │
├─────┼─────┼─────┼────┤
│ H₂O │ 1 │ │ 1 │
│ OH⁻ │ │ 1 │ -1 │
└─────┴─────┴─────┴────┘ChemistryLab.CanonicalStoichMatrix Method
CanonicalStoichMatrix(species)Construct a StoichMatrix from a list of species i.e. the matrix giving the stoichiometric coefficients of the species in columns with respect to the atoms in rows.
Arguments
species: list of species.
Examples
julia> H₂O, H⁺, OH⁻, CO₂, HCO₃⁻, CO₃²⁻ = Species.(split("H₂O H⁺ OH⁻ CO₂ HCO₃⁻ CO₃²⁻"))
6-element Vector{Species{Int64}}:
H₂O {H₂O} [H₂O ◆ H2O]
H⁺ {H⁺} [H⁺ ◆ H+]
OH⁻ {OH⁻} [OH⁻ ◆ OH-]
CO₂ {CO₂} [CO₂ ◆ CO2]
HCO₃⁻ {HCO₃⁻} [HCO₃⁻ ◆ HCO3-]
CO₃²⁻ {CO₃²⁻} [CO₃²⁻ ◆ CO3-2]
julia> CSM = CanonicalStoichMatrix([H₂O, H⁺, OH⁻, CO₂, HCO₃⁻, CO₃²⁻])
┌────┬─────┬────┬─────┬─────┬───────┬───────┐
│ │ H₂O │ H⁺ │ OH⁻ │ CO₂ │ HCO₃⁻ │ CO₃²⁻ │
├────┼─────┼────┼─────┼─────┼───────┼───────┤
│ C │ │ │ │ 1 │ 1 │ 1 │
│ H │ 2 │ 1 │ 1 │ │ 1 │ │
│ O │ 1 │ │ 1 │ 2 │ 3 │ 3 │
│ Zz │ │ 1 │ -1 │ │ -1 │ -2 │
└────┴─────┴────┴─────┴─────┴───────┴───────┘ChemistryLab._exact_rank Method
_exact_rank(A) -> IntThe rank of A computed exactly, by rational row reduction.
Why this is not a refinement of rank
LinearAlgebra.rank counts singular values above a tolerance, so it answers a combinatorial question — how many species are independent — with a numerical comparison. On a matrix of element counts the answer is never in doubt mathematically, but the computation runs through a LAPACK SVD, and a singular value sitting near the threshold decides differently on different CPUs and different LAPACK builds.
That is not hypothetical here. The decision this rank feeds — whether the charge row survives as an independent conservation law — is a boolean, and when it flips the electron's column becomes identically zero: a half-reaction then "balances" with no electron in it, and no redox potential can be written at all. The package's CI showed exactly that, green on one Julia and red on another from the same source, which is the signature of a threshold deciding a question that has an exact answer.
Falls back to the floating-point rank for a matrix that cannot be rationalized, which in this package means a symbolic element type.
ChemistryLab._rref_rational Method
_rref_rational(A) -> (R, pivot_cols)Reduced row echelon form of A over Rational{BigInt}, with the list of pivot columns.
Why exact rationals rather than fraction-free elimination
The package used a Bareiss (fraction-free) forward pass here, on the strength of Bareiss's theorem that the division ÷ prev is exact. It is not exact once pivots are skipped, and the failure is silent because ÷ on BigInt truncates rather than throwing. Measured, on a 5×6 integer matrix of rank 4 — smallest singular value exactly zero, every 5×5 minor singular:
two of the divisions left a non-zero remainder, the eliminated column kept a
stray 1, the pivot count came out 5, and the null space came back one vector
short.A null space one vector short is not a slow answer, it is a wrong conservation law. The matrices this runs on are small — a few dozen atom rows by a few hundred species — so exact rational elimination costs nothing that matters and cannot truncate. Correctness first; the fraction-free version can come back the day it is both needed and right.
ChemistryLab.mass_matrix Method
mass_matrix(SM::StoichMatrix)Construct a StoichMatrix with mass correspondence instead of molar stoichiometry.
Arguments
SM: StoichMatrix.
Examples
julia> H₂O, H⁺, OH⁻, CO₂, HCO₃⁻, CO₃²⁻ = Species.(split("H₂O H⁺ OH⁻ CO₂ HCO₃⁻ CO₃²⁻"))
6-element Vector{Species{Int64}}:
H₂O {H₂O} [H₂O ◆ H2O]
H⁺ {H⁺} [H⁺ ◆ H+]
OH⁻ {OH⁻} [OH⁻ ◆ OH-]
CO₂ {CO₂} [CO₂ ◆ CO2]
HCO₃⁻ {HCO₃⁻} [HCO₃⁻ ◆ HCO3-]
CO₃²⁻ {CO₃²⁻} [CO₃²⁻ ◆ CO3-2]
julia> SM = StoichMatrix([H₂O, H⁺, OH⁻, CO₂, HCO₃⁻, CO₃²⁻])
┌─────┬─────┬────┬─────┬─────┬───────┬───────┐
│ │ H₂O │ H⁺ │ OH⁻ │ CO₂ │ HCO₃⁻ │ CO₃²⁻ │
├─────┼─────┼────┼─────┼─────┼───────┼───────┤
│ H₂O │ 1 │ │ 1 │ │ 1 │ 1 │
│ H⁺ │ │ 1 │ -1 │ │ -1 │ -2 │
│ CO₂ │ │ │ │ 1 │ 1 │ 1 │
└─────┴─────┴────┴─────┴─────┴───────┴───────┘
julia> mass_matrix(SM)
┌─────┬─────┬─────┬────────────┬─────┬────────────┬────────────┐
│ │ H₂O │ H⁺ │ OH⁻ │ CO₂ │ HCO₃⁻ │ CO₃²⁻ │
├─────┼─────┼─────┼────────────┼─────┼────────────┼────────────┤
│ H₂O │ 1.0 │ │ 1.05927 │ │ 0.29525 │ 0.30021 │
│ H⁺ │ │ 1.0 │ -0.0592697 │ │ -0.0165203 │ -0.0335955 │
│ CO₂ │ │ │ │ 1.0 │ 0.72127 │ 0.733386 │
└─────┴─────┴─────┴────────────┴─────┴────────────┴────────────┘
julia> CSM = CanonicalStoichMatrix([H₂O, H⁺, OH⁻, CO₂, HCO₃⁻, CO₃²⁻])
┌────┬─────┬────┬─────┬─────┬───────┬───────┐
│ │ H₂O │ H⁺ │ OH⁻ │ CO₂ │ HCO₃⁻ │ CO₃²⁻ │
├────┼─────┼────┼─────┼─────┼───────┼───────┤
│ C │ │ │ │ 1 │ 1 │ 1 │
│ H │ 2 │ 1 │ 1 │ │ 1 │ │
│ O │ 1 │ │ 1 │ 2 │ 3 │ 3 │
│ Zz │ │ 1 │ -1 │ │ -1 │ -2 │
└────┴─────┴────┴─────┴─────┴───────┴───────┘
julia> mass_matrix(CSM)
┌────┬──────────┬─────┬───────────┬──────────┬───────────┬──────────┐
│ │ H₂O │ H⁺ │ OH⁻ │ CO₂ │ HCO₃⁻ │ CO₃²⁻ │
├────┼──────────┼─────┼───────────┼──────────┼───────────┼──────────┤
│ C │ │ │ │ 0.272921 │ 0.19685 │ 0.200157 │
│ H │ 0.111907 │ 1.0 │ 0.0592697 │ │ 0.0165203 │ │
│ O │ 0.888093 │ │ 0.94073 │ 0.727079 │ 0.78663 │ 0.799843 │
│ Zz │ │ │ │ │ │ │
└────┴──────────┴─────┴───────────┴──────────┴───────────┴──────────┘ChemistryLab.pprint Method
pprint(A::AbstractMatrix, indep_comp_names::AbstractVector, dep_comp_names::AbstractVector)Print a stoichiometric matrix with colored formatting.
Arguments
A: stoichiometric matrix.indep_comp_names: row labels (independent components).dep_comp_names: column labels (dependent components).
Uses text highlighters to color positive (red), negative (blue), and zero (concealed) values.
ChemistryLab.pprint Method
pprint(reactions::AbstractVector{<:Reaction})Pretty print a list of reactions.
Arguments
SM: StoichMatrix.
Examples
julia> H₂O, H⁺, OH⁻, CO₂, HCO₃⁻, CO₃²⁻ = Species.(split("H₂O H⁺ OH⁻ CO₂ HCO₃⁻ CO₃²⁻"))
6-element Vector{Species{Int64}}:
H₂O {H₂O} [H₂O ◆ H2O]
H⁺ {H⁺} [H⁺ ◆ H+]
OH⁻ {OH⁻} [OH⁻ ◆ OH-]
CO₂ {CO₂} [CO₂ ◆ CO2]
HCO₃⁻ {HCO₃⁻} [HCO₃⁻ ◆ HCO3-]
CO₃²⁻ {CO₃²⁻} [CO₃²⁻ ◆ CO3-2]
julia> SM = StoichMatrix([H₂O, H⁺, OH⁻, CO₂, HCO₃⁻, CO₃²⁻])
┌─────┬─────┬────┬─────┬─────┬───────┬───────┐
│ │ H₂O │ H⁺ │ OH⁻ │ CO₂ │ HCO₃⁻ │ CO₃²⁻ │
├─────┼─────┼────┼─────┼─────┼───────┼───────┤
│ H₂O │ 1 │ │ 1 │ │ 1 │ 1 │
│ H⁺ │ │ 1 │ -1 │ │ -1 │ -2 │
│ CO₂ │ │ │ │ 1 │ 1 │ 1 │
└─────┴─────┴────┴─────┴─────┴───────┴───────┘
julia> pprint(reactions(SM))
OH⁻ │ H₂O = OH⁻ + H⁺
HCO₃⁻ │ H₂O + CO₂ = HCO₃⁻ + H⁺
CO₃²⁻ │ H₂O + CO₂ = CO₃²⁻ + 2H⁺ChemistryLab.pull_primaries Method
pull_primaries(SM::StoichMatrix)Construct a StoichMatrix by reordering the species such that the primaries appear first (works only if primaries are contained within species).
Arguments
SM: StoichMatrix.
Examples
julia> H₂O, H⁺, OH⁻, CO₂, HCO₃⁻, CO₃²⁻ = Species.(split("H₂O H⁺ OH⁻ CO₂ HCO₃⁻ CO₃²⁻"))
6-element Vector{Species{Int64}}:
H₂O {H₂O} [H₂O ◆ H2O]
H⁺ {H⁺} [H⁺ ◆ H+]
OH⁻ {OH⁻} [OH⁻ ◆ OH-]
CO₂ {CO₂} [CO₂ ◆ CO2]
HCO₃⁻ {HCO₃⁻} [HCO₃⁻ ◆ HCO3-]
CO₃²⁻ {CO₃²⁻} [CO₃²⁻ ◆ CO3-2]
julia> SM = StoichMatrix([H₂O, H⁺, OH⁻, CO₂, HCO₃⁻, CO₃²⁻])
┌─────┬─────┬────┬─────┬─────┬───────┬───────┐
│ │ H₂O │ H⁺ │ OH⁻ │ CO₂ │ HCO₃⁻ │ CO₃²⁻ │
├─────┼─────┼────┼─────┼─────┼───────┼───────┤
│ H₂O │ 1 │ │ 1 │ │ 1 │ 1 │
│ H⁺ │ │ 1 │ -1 │ │ -1 │ -2 │
│ CO₂ │ │ │ │ 1 │ 1 │ 1 │
└─────┴─────┴────┴─────┴─────┴───────┴───────┘
julia> pull_primaries(SM)
┌─────┬─────┬────┬─────┬─────┬───────┬───────┐
│ │ H₂O │ H⁺ │ CO₂ │ OH⁻ │ HCO₃⁻ │ CO₃²⁻ │
├─────┼─────┼────┼─────┼─────┼───────┼───────┤
│ H₂O │ 1 │ │ │ 1 │ 1 │ 1 │
│ H⁺ │ │ 1 │ │ -1 │ -1 │ -2 │
│ CO₂ │ │ │ 1 │ │ 1 │ 1 │
└─────┴─────┴────┴─────┴─────┴───────┴───────┘
julia> pull_primaries(SM).N
6×3 Matrix{Int64}:
-1 -1 -1
1 1 2
0 -1 -1
1 0 0
0 1 0
0 0 1ChemistryLab.push_primaries Method
push_primaries(SM::StoichMatrix)Construct a StoichMatrix by reordering the species such that the primaries appear at the end (works only if primaries are contained within species).
Arguments
SM: StoichMatrix.
Examples
julia> H₂O, H⁺, OH⁻, CO₂, HCO₃⁻, CO₃²⁻ = Species.(split("H₂O H⁺ OH⁻ CO₂ HCO₃⁻ CO₃²⁻"))
6-element Vector{Species{Int64}}:
H₂O {H₂O} [H₂O ◆ H2O]
H⁺ {H⁺} [H⁺ ◆ H+]
OH⁻ {OH⁻} [OH⁻ ◆ OH-]
CO₂ {CO₂} [CO₂ ◆ CO2]
HCO₃⁻ {HCO₃⁻} [HCO₃⁻ ◆ HCO3-]
CO₃²⁻ {CO₃²⁻} [CO₃²⁻ ◆ CO3-2]
julia> SM = StoichMatrix([H₂O, H⁺, OH⁻, CO₂, HCO₃⁻, CO₃²⁻])
┌─────┬─────┬────┬─────┬─────┬───────┬───────┐
│ │ H₂O │ H⁺ │ OH⁻ │ CO₂ │ HCO₃⁻ │ CO₃²⁻ │
├─────┼─────┼────┼─────┼─────┼───────┼───────┤
│ H₂O │ 1 │ │ 1 │ │ 1 │ 1 │
│ H⁺ │ │ 1 │ -1 │ │ -1 │ -2 │
│ CO₂ │ │ │ │ 1 │ 1 │ 1 │
└─────┴─────┴────┴─────┴─────┴───────┴───────┘
julia> push_primaries(SM)
┌─────┬─────┬───────┬───────┬─────┬────┬─────┐
│ │ OH⁻ │ HCO₃⁻ │ CO₃²⁻ │ H₂O │ H⁺ │ CO₂ │
├─────┼─────┼───────┼───────┼─────┼────┼─────┤
│ H₂O │ 1 │ 1 │ 1 │ 1 │ │ │
│ H⁺ │ -1 │ -1 │ -2 │ │ 1 │ │
│ CO₂ │ │ 1 │ 1 │ │ │ 1 │
└─────┴─────┴───────┴───────┴─────┴────┴─────┘
julia> push_primaries(SM).N
6×3 Matrix{Int64}:
1 0 0
0 1 0
0 0 1
-1 -1 -1
1 1 2
0 -1 -1