KiThe 0.3.6

A numerical suite for chemical kinetics and thermodynamics, combustion, heat and mass transfer,chemical engeneering. Work in progress. Advices and contributions will be appreciated
//! Typed index wrappers for species, elements, phases, and reactions.
//!
//! # Purpose
//!
//! This module provides **type-safe index wrappers** that prevent confusion
//! between different kinds of indices in the equilibrium calculation. Instead
//! of passing raw `usize` values (where a species index could be mistaken for
//! an element index), these wrappers enforce correctness at the type level.
//!
//! # Key Structures
//!
//! | Type | Wraps | Purpose |
//! |------|-------|---------|
//! | [`SpeciesId`] | `usize` | Index into species arrays |
//! | [`ElementId`] | `usize` | Index into element arrays |
//! | [`PhaseIndex`] | `usize` | Index into phase arrays |
//! | [`ReactionId`] | `usize` | Index into reaction arrays |
//!
//! All four types are generated by the [`typed_id!`] macro, which provides:
//!
//! - `new(index, upper_bound) -> Result<Self, ReactionExtentError>` — bounds-checked constructor.
//! - `index(self) -> usize` — unwrap to raw index.
//! - `From<X> for usize` — conversion trait.
//!
//! # Dataflow
//!
//! ```text
//!   External code (phase_layout, problem builder)
//!//!     ├── SpeciesId::new(i, n_species)  ──> species array index
//!     ├── ElementId::new(j, n_elements) ──> element array index
//!     ├── PhaseIndex::new(k, n_phases)  ──> phase array index
//!     └── ReactionId::new(r, n_rxns)    ──> reaction array index
//!//!     v
//!   Used in: EquilibriumProblem, EquilibriumComponentDescriptor,
//!            PhaseSet, PhaseManager, equilibrium_workflows
//! ```
//!
//! # Examples
//!
//! ```rust
//! use KiThe::Thermodynamics::ChemEquilibrium::equilibrium_ids::SpeciesId;
//!
//! let species = SpeciesId::new(0, 5).unwrap();
//! assert_eq!(species.index(), 0);
//!
//! // Out-of-bounds returns an error
//! assert!(SpeciesId::new(5, 5).is_err());
//! ```
//!
//! # Non-obvious Details
//!
//! - These IDs are **dense ordered indices**, not semantic identifiers. For
//!   semantic phase identity (e.g., "gas phase" vs "condensed phase"), use
//!   [`PhaseId`](crate::Thermodynamics::phase_layout::PhaseId) from the
//!   `phase_layout` module.
//! - The bounds check in `new()` uses **exclusive upper bound** (like array
//!   indexing), so `SpeciesId::new(5, 5)` is out of bounds for a 5-element array.
//! - The `From<X> for usize` conversion enables ergonomic use with nalgebra
//!   matrix indexing and other `usize`-based APIs.
//!
//! # Related Modules
//!
//! - [`equilibrium_problem`](super::equilibrium_problem) — uses these IDs in problem definition
//! - [`equilibrium_component`](super::equilibrium_component) — component descriptors with typed IDs
//! - [`equilibrium_workflows`](super::equilibrium_workflows) — phase management with PhaseIndex
//!

use crate::Thermodynamics::ChemEquilibrium::equilibrium_nonlinear::ReactionExtentError;

/// Typed index for a species in the prepared equilibrium ordering.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct SpeciesId(usize);

/// Typed index for a chemical element in the prepared equilibrium ordering.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct ElementId(usize);

/// Typed dense index for a physical phase in equilibrium-owned arrays.
///
/// This is deliberately named `PhaseIndex`: semantic phase identity belongs
/// to `Thermodynamics::phase_layout::PhaseId` and must survive at the bridge
/// boundary instead of being replaced by an integer.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct PhaseIndex(usize);

/// Typed index for an independent reaction row.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct ReactionId(usize);

macro_rules! typed_id {
    ($name:ident, $label:literal) => {
        impl $name {
            /// Builds a typed id after validating its upper bound.
            pub fn new(index: usize, upper_bound: usize) -> Result<Self, ReactionExtentError> {
                if index >= upper_bound {
                    return Err(ReactionExtentError::DimensionMismatch(format!(
                        "{} index {index} is out of bounds for {upper_bound} entries",
                        $label
                    )));
                }
                Ok(Self(index))
            }

            /// Returns the zero-based ordered index.
            pub fn index(self) -> usize {
                self.0
            }
        }

        impl From<$name> for usize {
            fn from(value: $name) -> Self {
                value.0
            }
        }
    };
}

typed_id!(SpeciesId, "species");
typed_id!(ElementId, "element");
typed_id!(PhaseIndex, "phase");
typed_id!(ReactionId, "reaction");