sbml-sim 0.1.2

Simulator for SBML models.
use mathml_rs::MathNode;
use sbml_rs::{self, Model};
use std::collections::HashMap;

use crate::LocalParameter;

#[derive(Debug, Clone)]
pub struct Reaction {
    pub id: String,
    pub reversible: bool,
    pub compartment: Option<String>,
    pub sbo_term: Option<String>,
    pub reactants: HashMap<String, SpeciesReference>,
    pub unbound_reactants: HashMap<String, UnboundSpeciesReference>,
    pub products: HashMap<String, SpeciesReference>,
    pub unbound_products: HashMap<String, UnboundSpeciesReference>,
    pub modifiers: Vec<String>,
    pub kinetic_law: Vec<MathNode>,
    pub local_parameters: HashMap<String, LocalParameter>,
}

impl Reaction {
    pub fn from(sbml_reaction: &sbml_rs::Reaction, model: &Model) -> Self {
        let id = sbml_reaction
            .id
            .as_ref()
            .expect("No ID found for Reaction.")
            .to_owned();
        let reversible = sbml_reaction
            .reversible
            .expect("Reversible attribute is mandatory on Reactions.");
        let mut compartment = None;
        if let Some(value) = &sbml_reaction.compartment {
            compartment = Some(value.to_owned());
        }
        let mut sbo_term = None;
        if let Some(value) = &sbml_reaction.sbo_term {
            sbo_term = Some(value.to_owned());
        }

        let mut reaction = Reaction {
            id,
            reversible,
            compartment,
            sbo_term,
            reactants: HashMap::new(),
            unbound_reactants: HashMap::new(),
            products: HashMap::new(),
            unbound_products: HashMap::new(),
            modifiers: Vec::new(),
            kinetic_law: Vec::new(),
            local_parameters: HashMap::new(),
        };

        reaction.parse_reactants(sbml_reaction, model);
        reaction.parse_products(sbml_reaction, model);
        reaction.parse_modifiers(sbml_reaction, model);
        reaction.parse_kinetic_law(sbml_reaction, model);
        reaction.parse_local_parameters(sbml_reaction, model);

        reaction
    }

    pub fn parse_reactants(&mut self, reaction: &sbml_rs::Reaction, model: &Model) {
        for reactant in &reaction.reactants(model) {
            if let Some(id) = &reactant.species {
                match SpeciesReference::from(&reactant) {
                    Ok(bound_reactant) => {
                        self.reactants.insert(id.clone(), bound_reactant);
                    }
                    Err(unbound_reactant) => {
                        self.unbound_reactants.insert(id.clone(), unbound_reactant);
                    }
                }
            }
        }
    }

    pub fn parse_products(&mut self, reaction: &sbml_rs::Reaction, model: &Model) {
        for product in &reaction.products(model) {
            if let Some(id) = &product.species {
                match SpeciesReference::from(&product) {
                    Ok(bound_product) => {
                        self.products.insert(id.clone(), bound_product);
                    }
                    Err(unbound_product) => {
                        self.unbound_products.insert(id.clone(), unbound_product);
                    }
                }
            }
        }
    }

    pub fn parse_modifiers(&mut self, reaction: &sbml_rs::Reaction, model: &Model) {
        for modifier in reaction.modifiers(model) {
            self.modifiers.push(modifier);
        }
    }

    pub fn parse_kinetic_law(&mut self, reaction: &sbml_rs::Reaction, model: &Model) {
        if let Some(kinetic_law) = reaction.kinetic_law(model) {
            self.kinetic_law = kinetic_law.nodes;
        } else {
            panic!("Can't simulate a reaction without a kinetic law.")
        }
    }

    pub fn parse_local_parameters(&mut self, reaction: &sbml_rs::Reaction, model: &Model) {
        for local_parameter in reaction.local_parameters(model) {
            if let Some(id) = &local_parameter.id {
                self.local_parameters
                    .insert(id.clone(), LocalParameter::from(&local_parameter));
            }
        }
    }

    pub fn local_parameter_values(&self) -> HashMap<String, f64> {
        let mut hm: HashMap<String, f64> = HashMap::new();
        for (id, local_parameter) in &self.local_parameters {
            hm.insert(id.clone(), local_parameter.value);
        }
        hm
    }
}

#[derive(Debug, Clone)]
pub struct UnboundSpeciesReference {
    pub id: Option<String>,
    pub constant: bool,
    pub species: String,
    pub sbo_term: Option<String>,
}

impl UnboundSpeciesReference {
    pub fn from(sp_ref: &sbml_rs::SpeciesReference) -> Self {
        let mut id = None;
        if let Some(value) = &sp_ref.id {
            id = Some(value.to_owned());
        }
        let constant = sp_ref
            .constant
            .expect("Constant attribute is mandatory on SpeciesReferences.");
        let species = sp_ref
            .species
            .clone()
            .expect("Species attribute is mandatory on SpeciesReferences.");
        let mut sbo_term = None;
        if let Some(value) = &sp_ref.sbo_term {
            sbo_term = Some(value.to_owned());
        }

        UnboundSpeciesReference {
            id,
            constant,
            species,
            sbo_term,
        }
    }

    pub fn to_bound(&self, stoichiometry: f64) -> SpeciesReference {
        SpeciesReference {
            id: self.id.clone(),
            constant: self.constant,
            species: self.species.clone(),
            sbo_term: self.sbo_term.clone(),
            stoichiometry,
        }
    }
}

#[derive(Clone, Debug)]
pub struct SpeciesReference {
    pub id: Option<String>,
    pub stoichiometry: f64,
    pub constant: bool,
    pub species: String,
    pub sbo_term: Option<String>,
}

impl SpeciesReference {
    pub fn from(sp_ref: &sbml_rs::SpeciesReference) -> Result<Self, UnboundSpeciesReference> {
        let unbound = UnboundSpeciesReference::from(sp_ref);
        if let Some(stoich) = sp_ref.stoichiometry {
            Ok(unbound.to_bound(stoich))
        } else {
            Err(unbound)
        }
    }
}