Skip to content

Species ​

ChemistryLab.AbstractSpecies Type
julia
abstract type AbstractSpecies end

Abstract base type for all chemical species representations.

All concrete species types (Species, CemSpecies) inherit from this type.

ChemistryLab.AggregateState Type
julia
@enum AggregateState

Enumeration for species aggregate states.

Values

  • AS_UNDEF: undefined state.

  • AS_AQUEOUS: aqueous solution.

  • AS_CRYSTAL: crystalline solid.

  • AS_GAS: gas phase.

  • AS_LIQUID: a pure liquid phase.

  • AS_SURFACE: bound to a surface site — not a bulk phase at all.

Correspondence with ThermoFun

An imported label is matched by name, so ThermoFun's numbering and this enum's are independent and neither constrains the other. The codes below are the ones its files carry, counted over the databases shipped in data/:

ThermoFun codelabeloccurrenceshere
4AS_AQUEOUS2183AS_AQUEOUS
3AS_CRYSTAL870AS_CRYSTAL
0AS_GAS57AS_GAS
1AS_LIQUID1AS_LIQUID
—not stated—AS_UNDEF

AS_SURFACE has no ThermoFun counterpart: no database describes a surface complex, because it is a modeling declaration rather than a substance record.

Both AS_LIQUID and AS_SURFACE are appended rather than inserted, so no existing member changes its integer value. It is here because a shipped database uses it – metallic mercury in slop98-inorganic-thermofun.json – and until it was added that record read as AS_UNDEF, an import silently losing what the file said.

A label with no member to land on takes the fallback, and a fallback is a valid value, so nothing announces the loss. test/databases.jl therefore walks the substances of every shipped database and requires each label to resolve, which is what turns the table above from a claim into a check.

ChemistryLab.CemSpecies Type
julia
struct CemSpecies{T<:Number,S<:Number} <: AbstractSpecies

Cement chemistry species representation using oxide notation.

Fields

  • name::String: human-readable name.

  • symbol::String: species symbol.

  • formula::Formula{T}: atomic composition formula.

  • cemformula::Formula{S}: oxide notation formula.

  • aggregate_state::AggregateState: physical state.

  • class::Class: chemical class.

  • properties::OrderedDict{Symbol,PropertyType}: thermodynamic and other properties.

Examples

julia
julia> s = CemSpecies("C3A"; name="Tricalcium aluminate");

julia> oxides(s)
OrderedDict{Symbol, Int64} with 2 entries:
  :C => 3
  :A => 1
ChemistryLab.CemSpecies Method
julia
CemSpecies(f::AbstractString; name, symbol, aggregate_state, class, properties) -> CemSpecies

Construct a CemSpecies from an oxide formula string.

Examples

julia
julia> s = CemSpecies("C3S"; name="Alite");

julia> oxides(s)
OrderedDict{Symbol, Int64} with 2 entries:
  :C => 3
  :S => 1
ChemistryLab.CemSpecies Method
julia
CemSpecies(s::CemSpecies; kwargs...) -> CemSpecies

Copy constructor for CemSpecies with optional field overrides.

ChemistryLab.CemSpecies Method
julia
CemSpecies(cemformula::Formula; name, symbol, aggregate_state, class, properties) -> CemSpecies

Construct a CemSpecies from an oxide formula.

Arguments

  • cemformula: Formula object in oxide notation.

  • name: species name (default: formula expression).

  • symbol: species symbol (default: formula expression).

  • aggregate_state: physical state (default: AS_UNDEF).

  • class: chemical class (default: SC_UNDEF).

  • properties: property dictionary (default: empty OrderedDict).

ChemistryLab.CemSpecies Method
julia
CemSpecies(s::Species; name, symbol, aggregate_state, class, properties) -> CemSpecies

Convert a Species to CemSpecies by decomposing into oxide notation.

Throws an error if the species cannot be decomposed into cement oxides.

Arguments

  • s: source Species.

  • name: override name (default: keep original).

  • symbol: override symbol (default: keep original).

  • aggregate_state: override state (default: keep original).

  • class: override class (default: keep original).

  • properties: override properties (default: keep original).

ChemistryLab.CemSpecies Method
julia
CemSpecies(oxides::Pair{Symbol,T}...; name, symbol, aggregate_state, class, properties) where {T} -> CemSpecies

Construct a CemSpecies from oxide => coefficient pairs.

Examples

julia
julia> s = CemSpecies(:C => 3, :S => 2; name="C3S2");

julia> oxides(s)
OrderedDict{Symbol, Int64} with 2 entries:
  :C => 3
  :S => 2
ChemistryLab.CemSpecies Method
julia
CemSpecies(; expr, name, symbol, aggregate_state, class, properties) -> CemSpecies

Construct a CemSpecies from keyword arguments with an oxide formula string.

ChemistryLab.CemSpecies Method
julia
CemSpecies(oxides::AbstractDict{Symbol,T}, charge=0; name, symbol, aggregate_state, class, properties) where {T} -> CemSpecies

Construct a CemSpecies from an oxide composition dictionary.

Arguments

  • oxides: dictionary mapping oxide symbols to stoichiometric coefficients.

  • charge: formal charge (default 0).

  • name: species name (default: computed from formula).

  • symbol: species symbol (default: name).

  • aggregate_state: physical state (default: AS_UNDEF).

  • class: chemical class (default: SC_UNDEF).

  • properties: property dictionary (default: empty OrderedDict).

ChemistryLab.CemSpecies Method
julia
CemSpecies{S}(s::CemSpecies; kwargs...) where {S} -> CemSpecies{S}

Construct a CemSpecies with a specific coefficient type from another CemSpecies.

ChemistryLab.CemSpecies Method
julia
CemSpecies{S,T}(s::CemSpecies; kwargs...) where {S,T} -> CemSpecies{S,T}

Construct a CemSpecies with specific coefficient types from another CemSpecies.

ChemistryLab.Class Type
julia
@enum Class

Enumeration for species chemical classes.

Values

  • SC_UNDEF: undefined class.

  • SC_AQSOLVENT: aqueous solvent.

  • SC_AQSOLUTE: aqueous solute.

  • SC_COMPONENT: component.

  • SC_GASFLUID: gas or fluid.

  • SC_SSENDMEMBER: end-member of a solid solution phase.

  • SC_SURFCOMPLEX: a species occupying a surface site — the free site itself as much as an occupied one, since both take part in the site mixing and both consume the family's site budget.

ChemistryLab.Species Type
julia
struct Species{T<:Number} <: AbstractSpecies

Standard chemical species representation using atomic composition.

Fields

  • name::String: human-readable name.

  • symbol::String: species symbol.

  • formula::Formula{T}: chemical formula with stoichiometric coefficients.

  • aggregate_state::AggregateState: physical state.

  • class::Class: chemical class.

  • properties::OrderedDict{Symbol,PropertyType}: thermodynamic and other properties.

Examples

julia
julia> s = Species("H2O"; name="Water", aggregate_state=AS_AQUEOUS);

julia> atoms(s)
OrderedDict{Symbol, Int64} with 2 entries:
  :H => 2
  :O => 1
ChemistryLab.Species Method
julia
Species(f::AbstractString; name, symbol, aggregate_state, class, properties) -> Species

Construct a Species from a formula string.

Arguments

  • f: formula string to parse.

  • name: species name (default: f).

  • symbol: species symbol (default: f).

  • aggregate_state: physical state (default: AS_UNDEF).

  • class: chemical class (default: SC_UNDEF).

  • properties: property dictionary (default: empty OrderedDict).

Examples

julia
julia> s = Species("Ca+2"; aggregate_state=AS_AQUEOUS);

julia> charge(s)
2
ChemistryLab.Species Method
julia
Species(s::CemSpecies; name, symbol, aggregate_state, class, properties) -> Species

Convert a CemSpecies to Species using atomic composition.

Arguments

  • s: source CemSpecies.

  • name: override name (default: keep original).

  • symbol: override symbol (default: keep original).

  • aggregate_state: override state (default: keep original).

  • class: override class (default: keep original).

  • properties: override properties (default: keep original).

ChemistryLab.Species Method
julia
Species(formula::Formula; name, symbol, aggregate_state, class, properties) -> Species

Construct a Species from a Formula object.

Arguments

  • formula: Formula object with atomic composition.

  • name: species name (default: formula expression).

  • symbol: species symbol (default: formula expression).

  • aggregate_state: physical state (default: AS_UNDEF).

  • class: chemical class (default: SC_UNDEF).

  • properties: property dictionary (default: empty OrderedDict).

Examples

julia
julia> f = Formula("NaCl");

julia> s = Species(f; name="Sodium chloride", aggregate_state=AS_CRYSTAL);

julia> name(s)
"Sodium chloride"
ChemistryLab.Species Method
julia
Species(s::Species; kwargs...) -> Species

Copy constructor for Species with optional field overrides.

ChemistryLab.Species Method
julia
Species(atoms::Pair{Symbol,T}...; name, symbol, aggregate_state, class, properties) where {T} -> Species

Construct a Species from element => coefficient pairs.

Examples

julia
julia> s = Species(:H => 2, :O => 1; name="Water");

julia> atoms(s)
OrderedDict{Symbol, Int64} with 2 entries:
  :H => 2
  :O => 1
ChemistryLab.Species Method
julia
Species(; expr, name, symbol, aggregate_state, class, properties) -> Species

Construct a Species from keyword arguments.

Arguments

  • expr: formula string to parse (default: "").

  • name: species name (default: expr).

  • symbol: species symbol (default: expr).

  • aggregate_state: physical state (default: AS_UNDEF).

  • class: chemical class (default: SC_UNDEF).

  • properties: property dictionary (default: empty OrderedDict).

ChemistryLab.Species Method
julia
Species(atoms::AbstractDict{Symbol,T}, charge=0; name, symbol, aggregate_state, class, properties) where {T} -> Species

Construct a Species from an atomic composition dictionary.

Arguments

  • atoms: dictionary mapping element symbols to stoichiometric coefficients.

  • charge: formal charge (default 0).

  • name: species name (default: computed from formula).

  • symbol: species symbol (default: name).

  • aggregate_state: physical state (default: AS_UNDEF).

  • class: chemical class (default: SC_UNDEF).

  • properties: property dictionary (default: empty OrderedDict).

ChemistryLab.Species Method
julia
Species{T}(s::Species; kwargs...) where {T} -> Species{T}

Construct a Species with a specific coefficient type from another Species.

Base.getindex Method
julia
Base.getindex(s::AbstractSpecies, i::Symbol) -> Any

Access species components, atoms, or properties by symbol key.

Returns 0 if the key is not found.

Examples

julia
julia> s = Species("H2O");

julia> s[:H]
2

julia> s[:N]
0
Base.getproperty Method
julia
Base.getproperty(s::AbstractSpecies, sym::Symbol) -> Any

Access species fields or registered properties.

Throws an error if the symbol is neither a field nor a property.

Base.hash Method
julia
Base.hash(s::AbstractSpecies, h::UInt) -> UInt

Hash a species on exactly what Base.isequal compares — formula, aggregate state, class, and _identity_symbol — which is what Dict and Set require of the pair.

It used to hash the stored symbol while isequal ignored it altogether, so two species that compared equal could land in different buckets.

Base.haskey Method
julia
Base.haskey(s::AbstractSpecies, sym::Symbol) -> Bool

Check if a property key exists in the species properties dictionary.

Base.isequal Method
julia
Base.isequal(s1::AbstractSpecies, s2::AbstractSpecies) -> Bool

Whether two species are the same species: same formula, aggregate state, class, and whatever the symbol adds to that.

Why the symbol is part of it

Because the first three do not separate polymorphs, and a polymorph is a different substance. Calcite and aragonite are both CaCO3, both AS_CRYSTAL, both SC_COMPONENT; in CEMDATA18 their standard Gibbs energies differ by 821 J/mol, which at 298 K is 0.33 in ln K — the whole difference in solubility between them. Without the symbol they compared equal, and a Dict keyed by species could not tell them apart.

It also restores the invariant isequal ⟹ hash, which Base.hash had always broken by including the symbol when this did not: Dict(calcite => 1) raised KeyError on aragonite while calcite == aragonite said true.

Two spellings of one formula remain one species, because _identity_symbol drops a symbol that merely spells the formula instead of comparing it as text.

Examples

julia
julia> s1 = Species("H2O"; aggregate_state=AS_AQUEOUS);

julia> s2 = Species("H₂O"; aggregate_state=AS_AQUEOUS);

julia> s1 == s2
true

julia> calcite = Species("CaCO3"; symbol="Cal", aggregate_state=AS_CRYSTAL);

julia> aragonite = Species("CaCO3"; symbol="Arg", aggregate_state=AS_CRYSTAL);

julia> calcite == aragonite
false

See also: Base.hash, _identity_symbol.

Base.promote_rule Method
julia
Base.promote_rule(::Type{Species}, ::Type{<:AbstractSpecies}) -> Type{Species}

Define promotion rule to convert AbstractSpecies to Species.

Base.setindex! Method
julia
Base.setindex!(s::AbstractSpecies, value, i::Symbol)

Set a property value for the species.

Base.setproperty! Method
julia
Base.setproperty!(s::AbstractSpecies, sym::Symbol, value)

Set a property value, preventing direct modification of structural fields.

Throws an error if attempting to modify a structural field directly.

Base.show Method
julia
Base.show(io::IO, s::CemSpecies)

Compact single-line representation of a CemSpecies.

Base.show Method
julia
Base.show(io::IO, ::MIME"text/plain", s::CemSpecies)

Detailed multi-line REPL display for CemSpecies.

Base.show Method
julia
Base.show(io::IO, ::MIME"text/plain", s::Species)

Detailed multi-line REPL display for Species.

Base.show Method
julia
Base.show(io::IO, s::Species)

Compact single-line representation of a Species.

ChemistryLab._identity_symbol Method
julia
_identity_symbol(s::AbstractSpecies) -> Union{Nothing, String}

What the symbol contributes to a species' identity: nothing when it is merely a spelling of the species' own formula, and the symbol itself otherwise.

Why identity cannot just read symbol

Because a symbol serves two masters. It is the lookup key — cs["H2O"] indexes a ChemicalSystem by it, so it must stay the string the caller typed — and it is the only thing that separates two substances sharing a formula, a state and a class. Rewriting the stored symbol satisfies the second and breaks the first: measured, cs["H2O"] raised KeyError and ChemicalSystem(sp, ["H2O", "NaCl"]) could no longer name its components.

Why nothing, and not a canonical spelling

The obvious move is to replace a derived symbol by unicode(formula(s)). It is wrong, and measurably so: unicode is not constant on the equality classes of Formula, which are composition and charge.

julia
Formula("e")    == Formula("e-")     yet unicode is "e"    and "e⁻"
Formula("Ca+2") == Formula("Ca⁺²")   yet unicode is "Ca²⁺" and "Ca⁺²"

Two species that are the same substance, both with derived symbols, would then have carried different identity symbols and compared unequal. So a derived symbol must contribute nothing rather than a canonical form: the formula it spells is already compared, and spelling it twice cannot add information.

expr, phreeqc and unicode are stored fields of a Formula, so the three tests below are string comparisons, not re-parsing.

What it returns

speciessymbolidentity symbol
Species("H2O")"H2O"nothing — a spelling of its formula
Species("H₂O")"H₂O"nothing — the same species
ELECTRON"e-"nothing — and Species("e") likewise
Species("CaCO3"; symbol="Cal")"Cal""Cal" — names a polymorph
Species("CaCO3"; symbol="Arg")"Arg""Arg" — names the other one
ChemistryLab.aggregate_state Method
julia
aggregate_state(s::AbstractSpecies) -> AggregateState

Return the aggregate state of the species.

Examples

julia
julia> s1 = Species("H2O"; aggregate_state=AS_AQUEOUS);

julia> aggregate_state(s1) == AS_AQUEOUS
true
ChemistryLab.apply Method
julia
apply(func::Function, s::S, args...; kwargs...) where {S<:AbstractSpecies} -> S

Apply a function element-wise to all numeric components and properties of a species.

Arguments

  • func: function to apply.

  • s: source species.

  • args...: additional arguments for func.

  • kwargs...: keyword arguments (including potential overrides for name, symbol, etc.).

Returns

  • New species with transformed values.

Handles Quantity types, attempting to preserve dimensions when possible.

ChemistryLab.atoms Method
julia
atoms(s::AbstractSpecies) -> OrderedDict{Symbol,Number}

Return the atomic composition (element => coefficient) of the species.

ChemistryLab.atoms_charge Method
julia
atoms_charge(s::AbstractSpecies) -> OrderedDict{Symbol,Number}

Return atomic composition including the charge as a :Zz key if non-zero.

Examples

julia
julia> s = Species("Ca+2");

julia> atoms_charge(s)
OrderedDict{Symbol, Int64} with 2 entries:
  :Ca => 1
  :Zz => 2
ChemistryLab.cemformula Method
julia
cemformula(s::CemSpecies) -> Formula

Return the oxide notation formula of the cement species.

ChemistryLab.charge Method
julia
charge(s::AbstractSpecies) -> Int8

Return the formal charge of the species.

Examples

julia
julia> s1 = Species("Ca(HSiO3)+");

julia> charge(s1) == 1
true
ChemistryLab.check_mendeleev Method
julia
check_mendeleev(s::AbstractSpecies) -> Bool

Validate that all element symbols in the species exist in the periodic table.

ChemistryLab.class Method
julia
class(s::AbstractSpecies) -> Class

Return the chemical class of the species.

ChemistryLab.colored Method
julia
colored(s::CemSpecies) -> String

Return the colored terminal representation of the cement formula.

ChemistryLab.colored Method
julia
colored(s::Species) -> String

Return the colored terminal representation of the species formula.

ChemistryLab.complete_thermo_functions! Method
julia
complete_thermo_functions!(s::AbstractSpecies)

Populate thermodynamic properties (Cp⁰, ΔₐH⁰, S⁰, ΔₐG⁰, V⁰) from parameters in s.properties.

If thermo_params dictionary is present in properties, it initializes thermodynamic functions using :thermo_method (e.g., "cp_ft_equation", "solute_hkf88_reaktoro") or scalar defaults. New thermodynamic models are registered by dispatching build_thermo_functions(Val(:model_name), params).

ChemistryLab.components Method
julia
components(s::CemSpecies) -> OrderedDict{Symbol,Number}

Return the components of a CemSpecies (oxide composition with charge).

ChemistryLab.components Method
julia
components(s::Species) -> OrderedDict{Symbol,Number}

Return the components of a Species (atomic composition with charge).

ChemistryLab.expr Method
julia
expr(s::CemSpecies) -> String

Return the expression string of the cement formula.

ChemistryLab.expr Method
julia
expr(s::Species) -> String

Return the original expression string of the species formula.

Examples

julia
julia> expr(Species("H2O"; name="Water", aggregate_state=AS_AQUEOUS))
"H2O"

julia> expr(Species("H2O"; name="Water", aggregate_state=AS_AQUEOUS)) == expr(Formula("H2O"))
true
ChemistryLab.find_species Function
julia
find_species(s::AbstractString, species_list=nothing, S::Type{<:AbstractSpecies}=Species; aggregate_state=AS_UNDEF, class=SC_UNDEF) -> AbstractSpecies

Find or construct a species from a string identifier.

Arguments

  • s: species identifier string (formula, symbol, or name).

  • species_list: optional list of species to search (default: nothing constructs new species).

  • S: species type to construct if not found (default: Species).

  • aggregate_state: filter by aggregate state (default: AS_UNDEF, no filter).

  • class: filter by chemical class (default: SC_UNDEF, no filter).

Returns

  • Matching species from list, or newly constructed species if not found.

The function searches by symbol, PHREEQC format, Unicode format, formula expression, and name. If multiple matches are found, a warning is displayed and the first match is returned.

Examples

julia
julia> species_list = [
           Species("H2O"; aggregate_state=AS_AQUEOUS), Species("H2O"; aggregate_state=AS_GAS)
       ];

julia> s = find_species("H2O", species_list; aggregate_state=AS_AQUEOUS);

julia> aggregate_state(s)
AS_AQUEOUS::AggregateState = 1
ChemistryLab.formula Method
julia
formula(s::AbstractSpecies) -> Formula

Return the Formula object associated with the species.

Examples

julia
julia> s1 = Species("H2O"; aggregate_state=AS_AQUEOUS);

julia> formula(s1) == Formula("H2O")
true
ChemistryLab.mainformula Method
julia
mainformula(s::CemSpecies) -> Formula

Return the main formula representation (oxide notation) for the cement species.

ChemistryLab.mainformula Method
julia
mainformula(s::Species) -> Formula

Return the main formula representation for the species.

ChemistryLab.mendeleev_filter Method
julia
mendeleev_filter(s::AbstractSpecies) -> Union{AbstractSpecies,Nothing}

Return the species if valid according to Mendeleev check, otherwise nothing.

ChemistryLab.name Method
julia
name(s::AbstractSpecies) -> String

Return the name of the species.

Examples

julia
julia> s1 = Species("H2O"; aggregate_state=AS_AQUEOUS);

julia> s1.name == "H2O"
true
ChemistryLab.ordered_dict_with_default Method
julia
ordered_dict_with_default(gen, key_type, val_type) -> OrderedDict

Create an OrderedDict from a generator, ensuring proper typing even when empty.

ChemistryLab.oxides Method
julia
oxides(s::CemSpecies) -> OrderedDict{Symbol,Number}

Return the oxide composition of the cement species.

ChemistryLab.oxides_charge Method
julia
oxides_charge(s::CemSpecies) -> OrderedDict{Symbol,Number}

Return oxide composition including the charge as a :Zz key if non-zero.

ChemistryLab.phreeqc Method
julia
phreeqc(s::CemSpecies) -> String

Return the PHREEQC-compatible representation of the cement formula.

ChemistryLab.phreeqc Method
julia
phreeqc(s::Species) -> String

Return the PHREEQC-compatible representation of the species formula.

ChemistryLab.pprint Method
julia
pprint(s::CemSpecies)

Pretty-print a CemSpecies to standard output using the same multi-line layout as the MIME "text/plain" show method.

Arguments

  • s : CemSpecies instance to print.

Returns

  • nothing (side-effect: formatted output to stdout).
ChemistryLab.pprint Method
julia
pprint(s::Species)

Pretty-print a Species to standard output using the same multi-line layout as the MIME "text/plain" show method.

Arguments

  • s : Species instance to print.

Returns

  • nothing (side-effect: formatted output to stdout).
ChemistryLab.properties Method
julia
properties(s::AbstractSpecies) -> OrderedDict{Symbol,PropertyType}

Return the properties dictionary of the species.

ChemistryLab.symbol Method
julia
symbol(s::AbstractSpecies) -> String

Return the symbol of the species.

ChemistryLab.unicode Method
julia
unicode(s::CemSpecies) -> String

Return the Unicode representation of the cement formula.

ChemistryLab.unicode Method
julia
unicode(s::Species) -> String

Return the Unicode pretty representation of the species formula.

ChemistryLab.with_aggregate_state Method
julia
with_aggregate_state(s::AbstractSpecies, a::AggregateState) -> AbstractSpecies

Return a copy of s in aggregate state a, everything else preserved.

The counterpart of with_class, and it exists for the same reason: a species is immutable, and a database record has to be requalified before it can join a phase that the database knows nothing about. SiteFamily uses it to put its members in AS_SURFACE, which is what keeps them out of the index sets the solver reads as "a pure mineral phase".

Examples

julia
julia> s = Species("XsOH"; aggregate_state=AS_CRYSTAL, class=SC_COMPONENT);

julia> aggregate_state(with_aggregate_state(s, AS_SURFACE))
AS_SURFACE::AggregateState = 5
ChemistryLab.with_class Method
julia
with_class(s::Species, c::Class) -> Species

Return a copy of s with its class set to c. All other fields (name, symbol, formula, aggregate state, properties) are preserved unchanged.

Useful to requalify database species as SC_SSENDMEMBER before grouping them into a SolidSolutionPhase, since Species is immutable.

Examples

julia
julia> s = Species("CaCO3"; aggregate_state=AS_CRYSTAL, class=SC_COMPONENT);

julia> class(s)
SC_COMPONENT::Class = 3

julia> s2 = with_class(s, SC_SSENDMEMBER);

julia> class(s2)
SC_SSENDMEMBER::Class = 5
ChemistryLab.with_symbol Method
julia
with_symbol(s::Species, sym::AbstractString) -> Species

The same species under a different symbol, everything else shared.

Exists for one purpose: a miscibility gap needs the same substance present twice, as two coexisting compositions, and a formulation carrying one amount per species can only express that if the species appears twice. ChemicalSystem uses this to build the extra copies a SolidSolutionPhase declared with instances > 1 asks for — see SolidSolutionPhase.

The copy shares the formula and the whole property dictionary, so the two carry byte-identical thermodynamic data: they are one substance under two labels, not two substances. Only the label distinguishes them, and it is what keeps the solid-solution groups disjoint.

Examples

julia
julia> s = Species("CaCO3"; aggregate_state=AS_CRYSTAL, class=SC_COMPONENT);

julia> s2 = with_symbol(s, "CaCO3#2");

julia> symbol(s2), atoms(s2) == atoms(s)
("CaCO3#2", true)