Skip to content

Commit

Permalink
Add function table building and extend conversion to BN (still in pro…
Browse files Browse the repository at this point in the history
…gress).
  • Loading branch information
ondrej33 committed Oct 3, 2024
1 parent b799a27 commit 11d3e86
Show file tree
Hide file tree
Showing 2 changed files with 162 additions and 7 deletions.
81 changes: 76 additions & 5 deletions src/model/_impl_bma_model.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
use crate::enums::{RelationshipType, VariableType};
use crate::model::bma_model::*;
use crate::update_fn::bma_fn_tree::BmaFnUpdate;
use biodivine_lib_param_bn::{BooleanNetwork, RegulatoryGraph};
use crate::update_fn::enums::{AggregateFn, ArithOp};

use biodivine_lib_param_bn::{BooleanNetwork, Monotonicity, RegulatoryGraph};

use regex::Regex;
use std::cmp::max;
use std::collections::HashMap;
Expand Down Expand Up @@ -56,17 +59,85 @@ impl BmaModel {
/// regulations. The update functions are transformed using [BmaFnUpdate::to_update_fn].
pub fn to_boolean_network(&self) -> Result<BooleanNetwork, String> {
// TODO: for now, we do not handle multi-valued models

if !self.is_boolean_model() {
return Err("Cannot convert multi-valued model to a Boolean network.".to_string());
}

let graph = self.to_regulatory_graph()?;
let bn = BooleanNetwork::new(graph);
let mut bn = BooleanNetwork::new(graph);

// TODO: this will have to change to handle multi-valued models
let mut max_levels = HashMap::new();
for var in &self.model.variables {
let var_name = BmaModel::canonical_var_name(var);
max_levels.insert(var_name, var.range_to);
}

// add update functions
self.model.variables.iter().for_each(|_var| {
// todo - convert the formula to update functions
});
for var in &self.model.variables {
let var_name = BmaModel::canonical_var_name(var);
let var_id = bn.as_graph().find_variable(&var_name).unwrap();

if var.range_to == 0 {
// We can have zero constants and we must deal with these accordingly.
bn.add_string_update_function(&var_name, "false").unwrap()
}

if let Some(bma_formula) = var.formula.clone() {
let update_fn = bma_formula.to_update_fn(&max_levels);
bn.set_update_function(var_id, Some(update_fn))?;
} else {
// The formula is empty, which means we have to build a default one
// the same way as BMA is doing this.
// We then convert this default BMA expression to a logical formula.

let regulators = bn.regulators(var_id);
if regulators.is_empty() {
// This is an undetermined input, in which case we set it to zero,
// because that's what BMA does.
bn.add_string_update_function(&var_name, "false").unwrap()
}

// We build the default function the same way as BMA does.
let mut positive = Vec::new();
let mut negative = Vec::new();
for regulator in regulators {
let regulator_name = bn.get_variable_name(regulator);
let reg = bn.as_graph().find_regulation(regulator, var_id).unwrap();
// BMA variables must be monotonic
match reg.monotonicity.unwrap() {
Monotonicity::Activation => positive.push(regulator_name),
Monotonicity::Inhibition => negative.push(regulator_name),
}
}

let p_avr = if !positive.is_empty() {
let p_args = positive
.iter()
.map(|x| BmaFnUpdate::mk_variable(x))
.collect();
BmaFnUpdate::mk_aggregation(AggregateFn::Avg, p_args)
} else {
// This does not make much sense, because it means any variable with only negative
// regulators is ALWAYS a constant zero. But this is how BMA seems to be doing it, so
// that's what we are doing as well...
BmaFnUpdate::mk_constant(0)
};
let n_avr = if !negative.is_empty() {
let n_args = negative
.iter()
.map(|x| BmaFnUpdate::mk_variable(x))
.collect();
BmaFnUpdate::mk_aggregation(AggregateFn::Avg, n_args)
} else {
BmaFnUpdate::mk_constant(0)
};
let default_bma_formula = BmaFnUpdate::mk_arithmetic(p_avr, n_avr, ArithOp::Minus);
let update_fn = default_bma_formula.to_update_fn(&max_levels);
bn.set_update_function(var_id, Some(update_fn))?;
}
}

Ok(bn)
}
Expand Down
88 changes: 86 additions & 2 deletions src/update_fn/_impl_to_update_fn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,18 @@ use crate::update_fn::enums::{AggregateFn, ArithOp, Literal, UnaryFn};
use biodivine_lib_param_bn::FnUpdate;
use num_rational::Rational32;
use num_traits::sign::Signed;
use std::collections::HashMap;
use std::collections::{HashMap, HashSet};

impl BmaFnUpdate {
/// Convert the BMA expression into corresponding `FnUpdate` instance of
/// [biodivine_lib_param_bn] library.
///
/// TODO: implementation via explicit construction of the function table
pub fn to_update_fn(&self) -> FnUpdate {
pub fn to_update_fn(&self, max_levels: &HashMap<String, u32>) -> FnUpdate {
let mut variables: Vec<String> = self.collect_variables().into_iter().collect();
variables.sort();
let _function_table = self.build_function_table(&variables, max_levels);

todo!()
}

Expand Down Expand Up @@ -75,6 +79,86 @@ impl BmaFnUpdate {
}
}
}

fn collect_variables(&self) -> HashSet<String> {
match &self.expression_tree {
Expression::Terminal(Literal::Str(name)) => {
let mut set = HashSet::new();
set.insert(name.clone());
set
}
Expression::Terminal(Literal::Int(_)) => HashSet::new(),
Expression::Arithmetic(_, left, right) => {
let left_set = left.collect_variables();
let right_set = right.collect_variables();
left_set.union(&right_set).cloned().collect()
}
Expression::Unary(_, child_node) => child_node.collect_variables(),
Expression::Aggregation(_, arguments) => arguments
.iter()
.map(|arg| arg.collect_variables())
.fold(HashSet::new(), |x, y| x.union(&y).cloned().collect()),
}
}

/// Build a function table that maps all input combinations (valuations) to output values.
pub fn build_function_table(
&self,
variables: &[String],
max_levels: &HashMap<String, u32>,
) -> Vec<(HashMap<String, Rational32>, Rational32)> {
let input_combinations = generate_input_combinations(variables, max_levels);
let mut function_table = Vec::new();

// Evaluate the function for each combination.
for combination in input_combinations {
match self.evaluate_in_valuation(&combination) {
Ok(output_value) => function_table.push((combination, output_value)),
Err(err) => eprintln!("Error evaluating function: {err}"),
}
}

function_table
}
}

/// Generate all possible input combinations for the given variables, respecting their possible levels.
pub fn generate_input_combinations(
variables: &[String],
max_levels: &HashMap<String, u32>,
) -> Vec<HashMap<String, Rational32>> {
let mut results = Vec::new();
let mut current_combination = HashMap::new();
recursive_combinations(
variables,
max_levels,
&mut current_combination,
0,
&mut results,
);
results
}

/// Recursive helper function to generate input combinations.
pub fn recursive_combinations(
variables: &[String],
max_levels: &HashMap<String, u32>,
current: &mut HashMap<String, Rational32>,
index: usize,
results: &mut Vec<HashMap<String, Rational32>>,
) {
if index == variables.len() {
results.push(current.clone());
return;
}

let var_name = &variables[index];
let max_level = max_levels.get(var_name).cloned().unwrap_or(0);

for level in 0..=max_level {
current.insert(var_name.clone(), Rational32::new(level as i32, 1));
recursive_combinations(variables, max_levels, current, index + 1, results);
}
}

#[cfg(test)]
Expand Down

0 comments on commit 11d3e86

Please sign in to comment.