use custom_error::custom_error;
use good_lp::{constraint, variable, Expression, ProblemVariables, Solver, SolverModel, Variable};
use rust_sbml::{Model, Parameter, Reaction, Species, SpeciesReference};
use std::collections::HashMap;
use std::str::FromStr;
custom_error! {
pub SBMLError
InconsistentModel{ param: String} = "reaction points to {param} but it does not exist in model.parameters",
EmptyParameter{ param: String} = "the parameter {param} exists but it holds no value",
InconsistentObjective{ obj: String} = "model.objective points to {obj}, which could not be found in the model."
}
#[derive(Clone)]
pub struct ModelLp {
pub id: String,
pub name: String,
pub metabolites: HashMap<String, Species>,
pub reactions: HashMap<String, ReactionLp>,
pub variables: HashMap<String, Variable>,
pub config: HashMap<String, Parameter>,
pub objective: String,
pub stoichiometry: HashMap<String, Vec<Expression>>,
}
#[derive(Clone)]
pub struct ReactionLp {
pub lb: f64,
pub ub: f64,
id: String,
reactants: Vec<SpeciesReference>,
products: Vec<SpeciesReference>,
}
impl ReactionLp {
fn from_reaction(
reaction: Reaction,
parameters: &HashMap<String, Parameter>,
) -> Result<ReactionLp, SBMLError> {
Ok(ReactionLp {
id: format!(
"{}_{}",
reaction.id,
match reaction.compartment.as_ref() {
Some(s) => s.to_owned(),
_ => String::from(""),
}
),
lb: match reaction.lower_bound.as_ref() {
Some(s) => parameters
.get(s)
.ok_or(SBMLError::InconsistentModel {
param: s.to_owned(),
})?
.value
.ok_or(SBMLError::EmptyParameter {
param: s.to_owned(),
})?,
_ => match parameters.get("cobra_default_lb") {
Some(param) => param.value.ok_or(SBMLError::EmptyParameter {
param: String::from("cobra_default_lb"),
})?,
_ => -1000.,
},
},
ub: match reaction.upper_bound.as_ref() {
Some(s) => parameters
.get(s)
.ok_or(SBMLError::InconsistentModel {
param: s.to_owned(),
})?
.value
.ok_or(SBMLError::EmptyParameter {
param: s.to_owned(),
})?,
_ => match parameters.get("cobra_default_ub") {
Some(param) => param.value.ok_or(SBMLError::EmptyParameter {
param: String::from("cobra_default_ub"),
})?,
_ => 1000.,
},
},
reactants: reaction.list_of_reactants.species_references,
products: reaction.list_of_products.species_references,
})
}
}
impl ModelLp {
pub fn new(input_sbml: Model) -> Self {
Self::from(input_sbml)
}
fn reac_expr(&self, met: &SpeciesReference, reac: &str, com: f64) -> Expression {
Expression::from(self.variables[reac].to_owned())
* match met.stoichiometry {
Some(val) => com * val,
None => com * 1f64,
}
}
pub fn populate_model(&mut self, problem: &mut ProblemVariables) {
self.add_vars(problem);
let mut stoichiometry = HashMap::<String, Vec<Expression>>::new();
for (reac_id, reaction) in self.reactions.iter() {
reaction.reactants.iter().for_each(|sref| {
let cons = &mut stoichiometry
.entry(sref.species.to_owned())
.or_insert_with(Vec::new);
cons.push(self.reac_expr(sref, reac_id, -1.))
});
reaction.products.iter().for_each(|sref| {
let cons = &mut stoichiometry
.entry(sref.species.to_owned())
.or_insert_with(Vec::new);
cons.push(self.reac_expr(sref, reac_id, 1.));
});
}
self.stoichiometry = stoichiometry;
}
pub fn get_objective(&self) -> Variable {
self.variables[&self.objective]
}
pub fn get_objective_reaction(&mut self) -> Result<&mut ReactionLp, SBMLError> {
self.reactions
.get_mut(&self.objective)
.ok_or(SBMLError::InconsistentObjective {
obj: self.objective.to_owned(),
})
}
pub fn add_constraints<S: Solver>(&self, model: &mut S::Model) {
for (_, cons) in self.stoichiometry.iter() {
model.add_constraint(constraint::eq(cons.iter().sum::<Expression>(), 0.));
}
}
fn add_vars(&mut self, problem: &mut ProblemVariables) {
self.variables = self
.reactions
.iter()
.map(|(id, reac)| {
(
id.to_owned(),
problem.add(variable().min(reac.lb).max(reac.ub)),
)
})
.collect();
}
}
impl FromStr for ModelLp {
type Err = Box<dyn std::error::Error>;
fn from_str(input_sbml: &str) -> Result<Self, Box<dyn std::error::Error>> {
Ok(Self::new(Model::parse(input_sbml)?))
}
}
impl From<Model> for ModelLp {
fn from(mut model: Model) -> ModelLp {
let metabolites = model.species;
let config = model.parameters;
let mut reactions = HashMap::new();
let reac_ids: Vec<String> = model.reactions.iter().map(|(k, _)| k.to_owned()).collect();
for key in reac_ids.iter() {
reactions.insert(
key.to_owned(),
ReactionLp::from_reaction(model.reactions.remove(key).unwrap(), &config).unwrap(),
);
}
let objective = model.objectives.unwrap()[0].to_owned();
let id = match model.id {
Some(s) => s,
_ => "".to_string(),
};
let name = match model.name {
Some(s) => s,
_ => "".to_string(),
};
ModelLp {
id,
name,
metabolites,
reactions,
variables: HashMap::<_, _>::new(),
config,
objective,
stoichiometry: HashMap::<_, _>::new(),
}
}
}