Skip to content

Stoichiometric Matrix

ChemistryLab.item_order Method
julia
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
julia
same_components(::AbstractVector{<:AbstractSpecies}) -> Function

Return the function to extract components from species vectors.

Returns atoms_charge for Species vectors, oxides_charge for CemSpecies vectors.

ChemistryLab.speciation Method
julia
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...) -> SubArray

Filter 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_list containing the matching species.

Examples

julia
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
julia
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
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
 :O
ChemistryLab.StoichMatrix Type
julia
    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 of candidate_primaries containing atoms which are not in species (default: true).

Examples

julia
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 │     │     11
│  H⁺ │     │  1-1 │     │    -1-2
│ CO₂ │     │    │     │   111
└─────┴─────┴────┴─────┴─────┴───────┴───────┘

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  0
ChemistryLab.StoichMatrix Type
julia
    StoichMatrix{T,P}

Container holding a stoichiometric matrix A together with the primaries (independent components) and the full species vector.

Fields

julia
- `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
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
julia
    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
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 │     │    │     │   111
│  H │   211 │     │     1 │       │
│  O │   1 │    │   1233
│ Zz │     │  1-1 │     │    -1-2
└────┴─────┴────┴─────┴─────┴───────┴───────┘
ChemistryLab._exact_rank Method
julia
_exact_rank(A) -> Int

The 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
julia
_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:

julia
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
julia
    mass_matrix(SM::StoichMatrix)

Construct a StoichMatrix with mass correspondence instead of molar stoichiometry.

Arguments

  • SM: StoichMatrix.

Examples

julia
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 │     │     11
│  H⁺ │     │  1-1 │     │    -1-2
│ CO₂ │     │    │     │   111
└─────┴─────┴────┴─────┴─────┴───────┴───────┘

julia> mass_matrix(SM)
┌─────┬─────┬─────┬────────────┬─────┬────────────┬────────────┐
│     │ H₂O │  H⁺ │        OH⁻ │ CO₂ │      HCO₃⁻ │      CO₃²⁻ │
├─────┼─────┼─────┼────────────┼─────┼────────────┼────────────┤
│ H₂O │ 1.0 │     │    1.05927 │     │    0.295250.30021
│  H⁺ │     │ 1.0-0.0592697 │     │ -0.0165203-0.0335955
│ CO₂ │     │     │            │ 1.00.721270.733386
└─────┴─────┴─────┴────────────┴─────┴────────────┴────────────┘

julia> CSM = CanonicalStoichMatrix([H₂O, H⁺, OH⁻, CO₂, HCO₃⁻, CO₃²⁻])
┌────┬─────┬────┬─────┬─────┬───────┬───────┐
│    │ H₂O │ H⁺ │ OH⁻ │ CO₂ │ HCO₃⁻ │ CO₃²⁻ │
├────┼─────┼────┼─────┼─────┼───────┼───────┤
│  C │     │    │     │   111
│  H │   211 │     │     1 │       │
│  O │   1 │    │   1233
│ Zz │     │  1-1 │     │    -1-2
└────┴─────┴────┴─────┴─────┴───────┴───────┘

julia> mass_matrix(CSM)
┌────┬──────────┬─────┬───────────┬──────────┬───────────┬──────────┐
│    │      H₂O │  H⁺ │       OH⁻ │      CO₂ │     HCO₃⁻ │    CO₃²⁻ │
├────┼──────────┼─────┼───────────┼──────────┼───────────┼──────────┤
│  C │          │     │           │ 0.2729210.196850.200157
│  H │ 0.1119071.00.0592697 │          │ 0.0165203 │          │
│  O │ 0.888093 │     │   0.940730.7270790.786630.799843
│ Zz │          │     │           │          │           │          │
└────┴──────────┴─────┴───────────┴──────────┴───────────┴──────────┘
ChemistryLab.pprint Method
julia
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
julia
    pprint(reactions::AbstractVector{<:Reaction})

Pretty print a list of reactions.

Arguments

  • SM: StoichMatrix.

Examples

julia
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 │     │     11
│  H⁺ │     │  1-1 │     │    -1-2
│ CO₂ │     │    │     │   111
└─────┴─────┴────┴─────┴─────┴───────┴───────┘

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
julia
    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
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 │     │     11
│  H⁺ │     │  1-1 │     │    -1-2
│ CO₂ │     │    │     │   111
└─────┴─────┴────┴─────┴─────┴───────┴───────┘

julia> pull_primaries(SM)
┌─────┬─────┬────┬─────┬─────┬───────┬───────┐
│     │ H₂O │ H⁺ │ CO₂ │ OH⁻ │ HCO₃⁻ │ CO₃²⁻ │
├─────┼─────┼────┼─────┼─────┼───────┼───────┤
│ H₂O │   1 │    │     │   111
│  H⁺ │     │  1 │     │  -1-1-2
│ CO₂ │     │    │   1 │     │     11
└─────┴─────┴────┴─────┴─────┴───────┴───────┘

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   1
ChemistryLab.push_primaries Method
julia
    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
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 │     │     11
│  H⁺ │     │  1-1 │     │    -1-2
│ CO₂ │     │    │     │   111
└─────┴─────┴────┴─────┴─────┴───────┴───────┘

julia> push_primaries(SM)
┌─────┬─────┬───────┬───────┬─────┬────┬─────┐
│     │ OH⁻ │ HCO₃⁻ │ CO₃²⁻ │ H₂O │ H⁺ │ CO₂ │
├─────┼─────┼───────┼───────┼─────┼────┼─────┤
│ H₂O │   1111 │    │     │
│  H⁺ │  -1-1-2 │     │  1 │     │
│ CO₂ │     │     11 │     │    │   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