use crate::RxnId;
use serde::{Deserialize, Serialize};
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SeedStatus {
Approved,
Corrected,
NotAssessed,
Removed,
#[default]
None,
}
impl SeedStatus {
pub fn is_usable(self) -> bool {
matches!(self, SeedStatus::Approved | SeedStatus::Corrected)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Reversibility {
Forward,
Backward,
Reversible,
}
impl Reversibility {
pub fn from_code(c: char) -> Option<Self> {
match c {
'>' => Some(Self::Forward),
'<' => Some(Self::Backward),
'=' => Some(Self::Reversible),
_ => None,
}
}
pub fn code(self) -> char {
match self {
Self::Forward => '>',
Self::Backward => '<',
Self::Reversible => '=',
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Reaction {
pub id: RxnId,
pub name: String,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub ec: Vec<String>,
pub lb: f64,
pub ub: f64,
#[serde(default)]
pub obj_coef: f64,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub gpr_raw: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub subsystem: Option<String>,
#[serde(default)]
pub seed_status: SeedStatus,
#[serde(default)]
pub is_exchange: bool,
#[serde(default)]
pub is_biomass: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub gs_origin: Option<i8>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub bitscore: Option<f32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub weight: Option<f32>,
}
impl Reaction {
pub fn new(id: impl Into<RxnId>, name: impl Into<String>, lb: f64, ub: f64) -> Self {
Self {
id: id.into(),
name: name.into(),
ec: Vec::new(),
lb,
ub,
obj_coef: 0.0,
gpr_raw: None,
subsystem: None,
seed_status: SeedStatus::None,
is_exchange: false,
is_biomass: false,
gs_origin: None,
bitscore: None,
weight: None,
}
}
pub fn reversibility(&self) -> Reversibility {
match (self.lb < 0.0, self.ub > 0.0) {
(true, true) => Reversibility::Reversible,
(false, true) => Reversibility::Forward,
(true, false) => Reversibility::Backward,
(false, false) => Reversibility::Forward,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn reversibility_roundtrip() {
for c in ['>', '<', '='] {
let r = Reversibility::from_code(c).unwrap();
assert_eq!(r.code(), c);
}
assert!(Reversibility::from_code('?').is_none());
}
#[test]
fn reversibility_from_bounds() {
assert_eq!(Reaction::new("r", "r", -1000.0, 1000.0).reversibility(), Reversibility::Reversible);
assert_eq!(Reaction::new("r", "r", 0.0, 1000.0).reversibility(), Reversibility::Forward);
assert_eq!(Reaction::new("r", "r", -1000.0, 0.0).reversibility(), Reversibility::Backward);
}
}