Skip to content

Commit

Permalink
Implement conversion from BooleanNetwork to BmaModel.
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrej33 committed Sep 7, 2024
1 parent fec74b5 commit 35959c0
Show file tree
Hide file tree
Showing 8 changed files with 357 additions and 73 deletions.
172 changes: 172 additions & 0 deletions src/_impl_bma_model.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use crate::bma_model::*;
use crate::enums::{RelationshipType, VariableType};
use crate::json_model::JsonBmaModel;
use crate::traits::{JsonSerDe, XmlDe};
use crate::update_fn::bma_fn_tree::BmaFnUpdate;
use crate::update_fn::parser::parse_bma_formula;
use crate::xml_model::XmlBmaModel;
use biodivine_lib_param_bn::{BooleanNetwork, RegulatoryGraph};
Expand Down Expand Up @@ -268,4 +270,174 @@ impl BmaModel {

Ok(bn)
}

/// Construct `BmaModel` instance with a given name from a provided BooleanNetwork `bn`.
///
/// Boolean network must not use function symbols in any of its update functions.
///
/// TODO: for now, we only utilize monotonic regulations (and ignore the rest), and we do not use observability
pub fn from_boolean_network(bn: &BooleanNetwork, name: &str) -> Result<BmaModel, String> {
if bn.num_parameters() > 0 {
return Err("Boolean network with parameters can not be translated.".to_string());
}
// transform variables and update functions
let variables = bn
.variables()
.map(|var_id| {
let formula = if let Some(update_fn) = bn.get_update_function(var_id) {
// we unwrap since we already checked BN has no parameters
let bma_function = BmaFnUpdate::try_from_fn_update(update_fn).unwrap();
Some(bma_function)
} else {
None
};
Variable {
id: var_id.to_index() as u32,
name: bn.get_variable_name(var_id).clone(),
range_from: 0,
range_to: 1,
formula,
}
})
.collect();

// transform regulations into relationships
// TODO: deal with non-monotonic regulations
let relationships = bn
.as_graph()
.regulations()
.filter(|reg| reg.monotonicity.is_some())
.enumerate()
.map(|(idx, reg)| Relationship {
id: idx as u32,
from_variable: reg.regulator.to_index() as u32,
to_variable: reg.target.to_index() as u32,
relationship_type: RelationshipType::from(reg.monotonicity.unwrap()),
})
.collect();

let model = Model {
name: name.to_string(),
variables,
relationships,
};

// each variable gets default layout settings
let layout_vars = bn
.variables()
.map(|var_id| LayoutVariable {
id: var_id.to_index() as u32,
name: bn.get_variable_name(var_id).clone(),
variable_type: VariableType::Default,
container_id: 0,
position_x: 0.0,
position_y: 0.0,
cell_x: None,
cell_y: None,
angle: 0.0,
description: "".to_string(),
})
.collect();

// a single default container for all the variables
let container = Container {
id: 0,
name: "".to_string(),
size: 1,
position_x: 0.0,
position_y: 0.0,
};

let layout = Layout {
variables: layout_vars,
containers: vec![container],
description: "".to_string(),
zoom_level: None,
pan_x: None,
pan_y: None,
};

Ok(BmaModel {
model,
layout,
metadata: HashMap::new(),
})
}
}

#[cfg(test)]
mod tests {
use crate::bma_model::BmaModel;
use crate::enums::RelationshipType;
use biodivine_lib_param_bn::BooleanNetwork;

#[test]
fn test_from_boolean_network_aeon() {
let aeon_model = r#"
$A: A & !B
$B: A
B -| A
A -> A
A -> B
"#;
let bn = BooleanNetwork::try_from(aeon_model).unwrap();

let bma_model = BmaModel::from_boolean_network(&bn, "Test Model").unwrap();

/* === VARIABLES AND UPDATE FUNCTIONS === */

assert_eq!(bma_model.model.variables.len(), 2);
let var_a_bma = &bma_model.model.variables[0];
let var_b_bma = &bma_model.model.variables[1];

assert_eq!(var_a_bma.name, "A");
assert!(var_a_bma.formula.is_some());
let formula_a = var_a_bma.formula.as_ref().unwrap().to_string();
assert_eq!(formula_a, "(var(0) * (1 - var(1)))");

assert_eq!(var_b_bma.name, "B");
assert!(var_b_bma.formula.is_some());
let formula_b = var_b_bma.formula.as_ref().unwrap().to_string();
assert_eq!(formula_b, "var(0)");

/* === RELATIONSHIPS === */

assert_eq!(bma_model.model.relationships.len(), 3);
let rel_b_inhibits_a = &bma_model.model.relationships[0];
let rel_a_self_activates = &bma_model.model.relationships[1];
let rel_a_activates_b = &bma_model.model.relationships[2];
assert_eq!(rel_b_inhibits_a.from_variable, 1); // B -| A
assert_eq!(rel_b_inhibits_a.to_variable, 0);
assert_eq!(
rel_b_inhibits_a.relationship_type,
RelationshipType::Inhibitor
);

assert_eq!(rel_a_self_activates.from_variable, 0); // A -> A
assert_eq!(rel_a_self_activates.to_variable, 0);
assert_eq!(
rel_a_self_activates.relationship_type,
RelationshipType::Activator
);

assert_eq!(rel_a_activates_b.from_variable, 0); // A -> B
assert_eq!(rel_a_activates_b.to_variable, 1);
assert_eq!(
rel_a_activates_b.relationship_type,
RelationshipType::Activator
);

/* === LAYOUT === */

assert_eq!(bma_model.layout.variables.len(), 2);
let layout_var_a = &bma_model.layout.variables[0];
let layout_var_b = &bma_model.layout.variables[1];
assert_eq!(layout_var_a.name, "A");
assert_eq!(layout_var_b.name, "B");

// Verify that there is a default container
assert_eq!(bma_model.layout.containers.len(), 1);
let container = &bma_model.layout.containers[0];
assert_eq!(container.id, 0);
}
}
6 changes: 3 additions & 3 deletions src/bma_model.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::enums::{RelationshipType, VariableType};
use crate::update_fn::bma_fn_tree::BmaFnNode;
use crate::update_fn::bma_fn_tree::BmaFnUpdate;
use serde::{Deserialize, Serialize, Serializer};
use serde_with::skip_serializing_none;
use std::collections::HashMap;
Expand Down Expand Up @@ -33,7 +33,7 @@ pub struct Variable {
pub range_from: u32,
pub range_to: u32,
#[serde(serialize_with = "serialize_update_fn")]
pub formula: Option<BmaFnNode>,
pub formula: Option<BmaFnUpdate>,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
Expand Down Expand Up @@ -85,7 +85,7 @@ pub struct Container {
pub position_y: f64,
}

fn serialize_update_fn<S>(update_fn: &Option<BmaFnNode>, s: S) -> Result<S::Ok, S::Error>
fn serialize_update_fn<S>(update_fn: &Option<BmaFnUpdate>, s: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
Expand Down
13 changes: 11 additions & 2 deletions src/enums.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
use biodivine_lib_param_bn::Monotonicity;
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq)]
pub enum VariableType {
Default,
Constant,
MembraneReceptor,
}

#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq)]
pub enum RelationshipType {
Activator,
Inhibitor,
Expand All @@ -22,3 +22,12 @@ impl From<RelationshipType> for Monotonicity {
}
}
}

impl From<Monotonicity> for RelationshipType {
fn from(val: Monotonicity) -> Self {
match val {
Monotonicity::Activation => RelationshipType::Activator,
Monotonicity::Inhibition => RelationshipType::Inhibitor,
}
}
}
91 changes: 91 additions & 0 deletions src/update_fn/_impl_from_update_fn.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
use crate::update_fn::bma_fn_tree::BmaFnUpdate;
use crate::update_fn::enums::ArithOp;
use biodivine_lib_param_bn::{BinaryOp, FnUpdate};

impl BmaFnUpdate {
pub fn try_from_fn_update(fn_update: &FnUpdate) -> Result<Self, String> {
// update functions with function symbols are not allowed
if !fn_update.collect_parameters().is_empty() {
return Err("Update function with function symbols can not be translated.".to_string());
}
Self::try_from_fn_update_rec(fn_update)
}

/// Recursively converts the `FnUpdate` Boolean formula into a corresponding `BmaFnUpdate`
/// real-number expression.
pub fn try_from_fn_update_rec(fn_update: &FnUpdate) -> Result<BmaFnUpdate, String> {
let res = match fn_update {
FnUpdate::Const(val) => BmaFnUpdate::mk_constant(if *val { 1 } else { 0 }),
FnUpdate::Var(var_id) => {
BmaFnUpdate::mk_variable(&format!("var({})", var_id.to_index()))
}
FnUpdate::Not(child) => {
// NOT: map !A to (1 - A)
let child_expr = Self::try_from_fn_update_rec(child)?;
let one_node = BmaFnUpdate::mk_constant(1);
BmaFnUpdate::mk_arithmetic(one_node, child_expr, ArithOp::Minus)
}
FnUpdate::Binary(op, left, right) => {
let left_expr = Self::try_from_fn_update_rec(left)?;
let right_expr = Self::try_from_fn_update_rec(right)?;

match op {
// AND: map A && B to A * B
BinaryOp::And => {
BmaFnUpdate::mk_arithmetic(left_expr, right_expr, ArithOp::Times)
}
// OR: map A || B to A + B - A * B
BinaryOp::Or => {
let sum_expr = BmaFnUpdate::mk_arithmetic(
left_expr.clone(),
right_expr.clone(),
ArithOp::Add,
);
let prod_expr =
BmaFnUpdate::mk_arithmetic(left_expr, right_expr, ArithOp::Times);
BmaFnUpdate::mk_arithmetic(sum_expr, prod_expr, ArithOp::Minus)
}
// XOR: map A ^ B to A + B - 2 * (A * B)
BinaryOp::Xor => {
let sum_expr = BmaFnUpdate::mk_arithmetic(
left_expr.clone(),
right_expr.clone(),
ArithOp::Add,
);
let prod_expr =
BmaFnUpdate::mk_arithmetic(left_expr, right_expr, ArithOp::Times);
let two_prod_expr = BmaFnUpdate::mk_arithmetic(
BmaFnUpdate::mk_constant(2),
prod_expr,
ArithOp::Times,
);
BmaFnUpdate::mk_arithmetic(sum_expr, two_prod_expr, ArithOp::Minus)
}
// IFF: map A <-> B to 1 - (A ^ B)
BinaryOp::Iff => {
let xor_expr = BmaFnUpdate::try_from_fn_update_rec(&FnUpdate::Binary(
BinaryOp::Xor,
left.clone(),
right.clone(),
))?;
let one_node = BmaFnUpdate::mk_constant(1);
BmaFnUpdate::mk_arithmetic(one_node, xor_expr, ArithOp::Minus)
}
// IMP: map A -> B to 1 - A + A * B
BinaryOp::Imp => {
let not_left_expr = BmaFnUpdate::mk_arithmetic(
BmaFnUpdate::mk_constant(1),
left_expr.clone(),
ArithOp::Minus,
);
let prod_expr =
BmaFnUpdate::mk_arithmetic(left_expr, right_expr, ArithOp::Times);
BmaFnUpdate::mk_arithmetic(not_left_expr, prod_expr, ArithOp::Add)
}
}
}
_ => Err("Unsupported operator.")?,
};
Ok(res)
}
}
9 changes: 9 additions & 0 deletions src/update_fn/_impl_to_update_fn.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
use crate::update_fn::bma_fn_tree::BmaFnUpdate;
use biodivine_lib_param_bn::FnUpdate;

impl BmaFnUpdate {
pub fn to_update_fn(&self) -> FnUpdate {
// TODO: implementation via explicit construction of the function table
todo!()
}
}
Loading

0 comments on commit 35959c0

Please sign in to comment.