From 5476feea2a4db4a77f7e6f9bd9f2a3444ed53b81 Mon Sep 17 00:00:00 2001 From: elvis Date: Sat, 20 Dec 2025 21:11:42 +0100 Subject: [PATCH] Boolean Fomulas + CNF --- rsprocess/src/boolean.rs | 290 +++++++++++++++++++++++ rsprocess/src/boolean_test.rs | 420 ++++++++++++++++++++++++++++++++++ rsprocess/src/lib.rs | 5 + rsprocess/src/set.rs | 14 ++ rsprocess/src/system.rs | 15 +- rsprocess/src/trace_test.rs | 4 +- 6 files changed, 740 insertions(+), 8 deletions(-) create mode 100644 rsprocess/src/boolean.rs create mode 100644 rsprocess/src/boolean_test.rs diff --git a/rsprocess/src/boolean.rs b/rsprocess/src/boolean.rs new file mode 100644 index 0000000..5f38d41 --- /dev/null +++ b/rsprocess/src/boolean.rs @@ -0,0 +1,290 @@ +use std::collections::BTreeMap; + +use serde::{Deserialize, Serialize}; + +use crate::element::IdType; +use crate::translator::{Formatter, PrintableWithTranslator}; + +#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum BooleanFunction { + #[default] + False, + True, + Not(Box), + Variable(IdType), + And(Box, Box), + Or(Box, Box), +} + +impl PrintableWithTranslator for BooleanFunction { + fn print( + &self, + f: &mut std::fmt::Formatter, + translator: &crate::translator::Translator, + ) -> std::fmt::Result { + use BooleanFunction::*; + match self { + | False => write!(f, "False"), + | True => write!(f, "True"), + | Not(next) => + write!(f, "Not({})", Formatter::from(translator, &**next)), + | Variable(x) => write!(f, "{})", Formatter::from(translator, x)), + | And(next1, next2) => write!( + f, + "And({}, {})", + Formatter::from(translator, &**next1), + Formatter::from(translator, &**next2) + ), + | Or(next1, next2) => write!( + f, + "Or({}, {})", + Formatter::from(translator, &**next1), + Formatter::from(translator, &**next2) + ), + } + } +} + +#[derive( + Default, Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, +)] +pub enum CNFLiteral { + #[default] + False, + True, + Variable { + positive: bool, + variable: IdType, + }, +} + +impl std::ops::Not for CNFLiteral { + type Output = CNFLiteral; + + fn not(self) -> Self::Output { + match self { + | Self::False => Self::True, + | Self::True => Self::False, + | Self::Variable { positive, variable } => Self::Variable { + positive: !positive, + variable, + }, + } + } +} + +impl CNFLiteral { + pub fn evaluate(&self, assignments: &BTreeMap) -> bool { + match self { + | Self::False => false, + | Self::True => true, + | Self::Variable { positive, variable } => + if *positive { + *assignments.get(variable).unwrap_or(&false) + } else { + !*assignments.get(variable).unwrap_or(&false) + }, + } + } +} + +impl PrintableWithTranslator for CNFLiteral { + fn print( + &self, + f: &mut std::fmt::Formatter, + translator: &crate::translator::Translator, + ) -> std::fmt::Result { + use CNFLiteral::*; + match self { + | False => write!(f, "F"), + | True => write!(f, "T"), + | Variable { positive, variable } => + if *positive { + write!(f, "{}", Formatter::from(translator, variable)) + } else { + write!(f, "-{}", Formatter::from(translator, variable)) + }, + } + } +} + +#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +// first vec is And, second in Or +pub struct CNFBooleanFunction { + pub formula: Vec>, +} + +impl PrintableWithTranslator for CNFBooleanFunction { + fn print( + &self, + f: &mut std::fmt::Formatter, + translator: &crate::translator::Translator, + ) -> std::fmt::Result { + let mut it = self.formula.iter().peekable(); + while let Some(or_formula) = it.next() { + let mut or_it = or_formula.iter().peekable(); + write!(f, "(")?; + while let Some(or) = or_it.next() { + if or_it.peek().is_none() { + write!(f, "{}", Formatter::from(translator, or))?; + } else { + write!(f, "{} v ", Formatter::from(translator, or))?; + } + } + if it.peek().is_none() { + write!(f, ")")?; + } else { + writeln!(f, ") ^")?; + } + } + Ok(()) + } +} + +impl From for CNFBooleanFunction { + fn from(source: BooleanFunction) -> Self { + fn morgan(source: CNFBooleanFunction) -> CNFBooleanFunction { + let temp: Vec> = source + .formula + .into_iter() + .map(|f| f.into_iter().map(|l| !l).collect()) + .collect(); + + let lenghts: Vec = temp.iter().map(|f| f.len()).collect(); + let mut position = vec![0; temp.len()]; + + let add_one = |position: &mut Vec| -> bool { + let mut location: usize = 0; + loop { + if location >= position.len() { + return true; + } + position[location] += 1; + if position[location] >= lenghts[location] { + position[location] = 0; + } else { + return false; + } + location += 1; + } + }; + + let mut ret_val = vec![]; + loop { + ret_val.push( + position + .iter() + .enumerate() + .map(|(pos, p)| temp[pos][*p]) + .collect(), + ); + + if add_one(&mut position) { + break; + } + } + CNFBooleanFunction { formula: ret_val } + } + + fn helper_normalize(source: BooleanFunction) -> CNFBooleanFunction { + match &source { + | BooleanFunction::False => CNFBooleanFunction { + formula: vec![vec![CNFLiteral::False]], + }, + | BooleanFunction::True => CNFBooleanFunction { + formula: vec![vec![CNFLiteral::True]], + }, + | BooleanFunction::Variable(v) => CNFBooleanFunction { + formula: vec![vec![CNFLiteral::Variable { + positive: true, + variable: *v, + }]], + }, + | BooleanFunction::And(n1, n2) => { + let n1 = helper_normalize(*n1.clone()); + let n2 = helper_normalize(*n2.clone()); + + CNFBooleanFunction { + formula: n1 + .formula + .into_iter() + .chain(n2.formula) + .collect(), + } + }, + | BooleanFunction::Or(n1, n2) => { + let n1 = helper_normalize(*n1.clone()); + let n2 = helper_normalize(*n2.clone()); + + let mut formulas = vec![]; + for formula1 in n1.formula { + for formula2 in &n2.formula { + formulas.push( + formula1 + .iter() + .chain(formula2.iter()) + .cloned() + .collect(), + ); + } + } + CNFBooleanFunction { formula: formulas } + }, + | BooleanFunction::Not(n) => { + if let BooleanFunction::Not(n) = &**n { + helper_normalize(*n.clone()) + } else { + morgan(helper_normalize(*n.clone())) + } + }, + } + } + + helper_normalize(source) + } +} + +impl CNFBooleanFunction { + pub fn evaluate(&self, assignments: &BTreeMap) -> bool { + self.formula + .iter() + .all(|or_f| or_f.iter().any(|l| l.evaluate(assignments))) + } +} + +#[derive(Default, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct BooleanNetwork { + initial_state: BTreeMap, + update_rules: BTreeMap, +} + +impl PrintableWithTranslator for BooleanNetwork { + fn print( + &self, + f: &mut std::fmt::Formatter, + translator: &crate::translator::Translator, + ) -> std::fmt::Result { + write!(f, "Initial State: ")?; + let mut it = self.initial_state.iter().peekable(); + while let Some((x, b)) = it.next() { + write!(f, "({} -> {})", Formatter::from(translator, x), b)?; + if it.peek().is_some() { + write!(f, ",")?; + } + write!(f, " ")?; + } + + writeln!(f)?; + writeln!(f, "Update Rules:")?; + + for (x, bf) in self.update_rules.iter() { + write!( + f, + "\t{} -> {}", + Formatter::from(translator, x), + Formatter::from(translator, bf) + )?; + } + Ok(()) + } +} diff --git a/rsprocess/src/boolean_test.rs b/rsprocess/src/boolean_test.rs new file mode 100644 index 0000000..0335237 --- /dev/null +++ b/rsprocess/src/boolean_test.rs @@ -0,0 +1,420 @@ +use std::collections::BTreeMap; + +use crate::boolean::{BooleanFunction, CNFBooleanFunction, CNFLiteral}; +use crate::element::IdType; + +macro_rules! cnfl { + ($i:ident) => { + CNFLiteral::$i + }; + ($p:literal, $i:literal) => { + CNFLiteral::Variable { + positive: $p, + variable: $i, + } + }; +} + +macro_rules! boolean { + (False) => (BooleanFunction::False); + (True) => (BooleanFunction::True); + (Variable($i:literal)) => (BooleanFunction::Variable($i)); + (Not($($tail:tt)+)) => (BooleanFunction::Not(Box::new(boolean!($($tail)*)))); + (And(($($tail1:tt)+ ), ($($tail2:tt)+ ))) => ( + BooleanFunction::And( + Box::new(boolean!($($tail1)+)), + Box::new(boolean!($($tail2)+)) + ) + ); + (Or(($($tail1:tt)+ ), ($($tail2:tt)+ ))) => ( + BooleanFunction::Or( + Box::new(boolean!($($tail1)+)), + Box::new(boolean!($($tail2)+)) + ) + ); +} + +#[test] +fn boolean_1() { + let bf = boolean!(False); + let cnf: CNFBooleanFunction = bf.into(); + + assert_eq!(cnf.formula, [[cnfl!(False)]]); +} + +#[test] +fn boolean_2() { + let bf = boolean!(True); + let cnf: CNFBooleanFunction = bf.into(); + + assert_eq!(cnf.formula, [[cnfl!(True)]]); +} + +#[test] +fn boolean_3() { + let bf = boolean!(Variable(1)); + let cnf: CNFBooleanFunction = bf.into(); + + assert_eq!(cnf.formula, [[cnfl!(true, 1)]]); +} + +#[test] +fn boolean_4() { + let bf = boolean!(Not(True)); + let cnf: CNFBooleanFunction = bf.into(); + + assert_eq!(cnf.formula, [[cnfl!(False)]]); +} + +#[test] +fn boolean_5() { + let bf = boolean!(Not(False)); + let cnf: CNFBooleanFunction = bf.into(); + + assert_eq!(cnf.formula, [[cnfl!(True)]]); +} + +#[test] +fn boolean_6() { + let bf = boolean!(Not(False)); + let cnf: CNFBooleanFunction = bf.into(); + + assert_eq!(cnf.formula, [[cnfl!(True)]]); +} + +#[test] +fn boolean_7() { + let bf = boolean!(Not(Not(False))); + let cnf: CNFBooleanFunction = bf.into(); + + assert_eq!(cnf.formula, [[cnfl!(False)]]); +} + +#[test] +fn boolean_8() { + let bf = boolean!(Not(Not(True))); + let cnf: CNFBooleanFunction = bf.into(); + + assert_eq!(cnf.formula, [[cnfl!(True)]]); +} + +#[test] +fn boolean_9() { + let bf = boolean!(Not(Variable(0))); + let cnf: CNFBooleanFunction = bf.into(); + + assert_eq!(cnf.formula, [[cnfl!(false, 0)]]); +} + +#[test] +fn boolean_10() { + let bf = boolean!(Not(Not(Variable(0)))); + let cnf: CNFBooleanFunction = bf.into(); + + assert_eq!(cnf.formula, [[cnfl!(true, 0)]]); +} + +#[test] +fn boolean_11() { + let bf = boolean!(And((True), (True))); + let cnf: CNFBooleanFunction = bf.into(); + + assert_eq!(cnf.formula, [[cnfl!(True)], [cnfl!(True)]]); +} + +#[test] +fn boolean_12() { + let bf = boolean!(And((True), (False))); + let cnf: CNFBooleanFunction = bf.into(); + + assert_eq!(cnf.formula, [[cnfl!(True)], [cnfl!(False)]]); +} + +#[test] +fn boolean_13() { + let bf = boolean!(And((Variable(0)), (False))); + let cnf: CNFBooleanFunction = bf.into(); + + assert_eq!(cnf.formula, [[cnfl!(true, 0)], [cnfl!(False)]]); +} + +#[test] +fn boolean_14() { + let bf = boolean!(And((Not(Variable(0))), (False))); + let cnf: CNFBooleanFunction = bf.into(); + + assert_eq!(cnf.formula, [[cnfl!(false, 0)], [cnfl!(False)]]); +} + +#[test] +fn boolean_15() { + let bf = boolean!(And((Not(Variable(0))), (Not(Not(Variable(1)))))); + let cnf: CNFBooleanFunction = bf.into(); + + assert_eq!(cnf.formula, [[cnfl!(false, 0)], [cnfl!(true, 1)]]); +} + +#[test] +fn boolean_16() { + let bf = boolean!(And((And((True), (True))), (Not(Not(Variable(1)))))); + let cnf: CNFBooleanFunction = bf.into(); + + assert_eq!(cnf.formula, [[cnfl!(True)], [cnfl!(True)], [cnfl!( + true, 1 + )]]); +} + +#[test] +fn boolean_17() { + let bf = boolean!(Not(And((False), (False)))); + let cnf: CNFBooleanFunction = bf.into(); + + assert_eq!(cnf.formula, [[cnfl!(True), cnfl!(True)]]); +} + +#[test] +fn boolean_18() { + let bf = boolean!(Not(And((False), (True)))); + let cnf: CNFBooleanFunction = bf.into(); + + assert_eq!(cnf.formula, [[cnfl!(True), cnfl!(False)]]); +} + +#[test] +fn boolean_19() { + let bf = boolean!(Not(And((False), (Not(True))))); + let cnf: CNFBooleanFunction = bf.into(); + + assert_eq!(cnf.formula, [[cnfl!(True), cnfl!(True)]]); +} + +#[test] +fn boolean_20() { + let bf = boolean!(Not(And((Variable(0)), (Not(True))))); + let cnf: CNFBooleanFunction = bf.into(); + + assert_eq!(cnf.formula, [[cnfl!(false, 0), cnfl!(True)]]); +} + +#[test] +fn boolean_21() { + let bf = boolean!(Not(And((Variable(0)), (Not(And((True), (False))))))); + let cnf: CNFBooleanFunction = bf.into(); + + assert_eq!(cnf.formula, [[cnfl!(false, 0), cnfl!(True)], [ + cnfl!(false, 0), + cnfl!(False) + ]]); +} + +#[test] +fn boolean_22() { + let bf = boolean!(And( + (Or((Variable(0)), (Or((Not(Variable(1))), (Variable(2)))))), + (Or((Not(Variable(3))), (Or((Variable(4)), (Variable(5)))))) + )); + let cnf: CNFBooleanFunction = bf.into(); + + assert_eq!(cnf.formula, [ + [cnfl!(true, 0), cnfl!(false, 1), cnfl!(true, 2)], + [cnfl!(false, 3), cnfl!(true, 4), cnfl!(true, 5)] + ]); +} + +#[test] +fn boolean_23() { + let bf = boolean!(And((Or((Variable(0)), (Variable(1)))), (Variable(2)))); + let cnf: CNFBooleanFunction = bf.into(); + + assert_eq!(cnf.formula, [vec![cnfl!(true, 0), cnfl!(true, 1)], vec![ + cnfl!(true, 2) + ]]); +} + +#[test] +fn boolean_24() { + let bf = boolean!(Or((Variable(0)), (Variable(1)))); + let cnf: CNFBooleanFunction = bf.into(); + + assert_eq!(cnf.formula, [[cnfl!(true, 0), cnfl!(true, 1)]]); +} + +#[test] +fn boolean_25() { + let bf = boolean!(Or((Variable(0)), (Or((Variable(1)), (Variable(2)))))); + let cnf: CNFBooleanFunction = bf.into(); + + assert_eq!(cnf.formula, [[ + cnfl!(true, 0), + cnfl!(true, 1), + cnfl!(true, 2) + ]]); +} + +#[test] +fn boolean_26() { + let bf = boolean!(Or( + (Variable(0)), + (Or((Variable(1)), (Or((Variable(2)), (Variable(3)))))) + )); + let cnf: CNFBooleanFunction = bf.into(); + + assert_eq!(cnf.formula, [[ + cnfl!(true, 0), + cnfl!(true, 1), + cnfl!(true, 2), + cnfl!(true, 3) + ]]); +} + +#[test] +fn boolean_27() { + let bf = boolean!(Or( + (Variable(0)), + (Or( + (Or((Variable(1)), (Variable(2)))), + (Or((Variable(3)), (Variable(4)))) + )) + )); + let cnf: CNFBooleanFunction = bf.into(); + + assert_eq!(cnf.formula, [[ + cnfl!(true, 0), + cnfl!(true, 1), + cnfl!(true, 2), + cnfl!(true, 3), + cnfl!(true, 4) + ]]); +} + +#[test] +fn boolean_28() { + let bf = boolean!(Or((Not(Variable(0))), (Variable(1)))); + let cnf: CNFBooleanFunction = bf.into(); + + assert_eq!(cnf.formula, [[cnfl!(false, 0), cnfl!(true, 1)]]); +} + +#[test] +fn boolean_29() { + let bf = boolean!(Or((Not(Variable(0))), (Not(Not(Variable(1)))))); + let cnf: CNFBooleanFunction = bf.into(); + + assert_eq!(cnf.formula, [[cnfl!(false, 0), cnfl!(true, 1)]]); +} + +#[test] +fn boolean_30() { + let bf = boolean!(Not(Or((False), (False)))); + let cnf: CNFBooleanFunction = bf.into(); + + assert_eq!(cnf.formula, [[cnfl!(True)], [cnfl!(True)]]); +} + +#[test] +fn boolean_31() { + let bf = boolean!(Not(Or((False), (True)))); + let cnf: CNFBooleanFunction = bf.into(); + + assert_eq!(cnf.formula, [[cnfl!(True)], [cnfl!(False)]]); +} + +#[test] +fn boolean_32() { + let bf = boolean!(Not(Or((Variable(0)), (Not(Or((True), (False))))))); + let cnf: CNFBooleanFunction = bf.into(); + + let assignments = BTreeMap::from([(0, false)]); + + assert!(cnf.evaluate(&assignments)); + + let assignments = BTreeMap::from([(0, true)]); + + assert!(!cnf.evaluate(&assignments)); +} + +#[test] +fn boolean_33() { + let bf = boolean!(Not(Or((Variable(0)), (Not(And((True), (False))))))); + let cnf: CNFBooleanFunction = bf.into(); + + let assignments = BTreeMap::from([(0, false)]); + + assert!(!cnf.evaluate(&assignments)); + + let assignments = BTreeMap::from([(0, true)]); + + assert!(!cnf.evaluate(&assignments)); +} + +#[test] +fn boolean_34() { + let bf = boolean!(Or( + (And((Variable(0)), (Variable(1)))), + (Or( + (Or((Variable(2)), (Not(Variable(3))))), + (Not(And((And((Variable(4)), (Not(Variable(5))))), (Variable(6))))) + )) + )); + let cnf: CNFBooleanFunction = bf.into(); + + let assignments: Vec> = (0_u32..128) + .map(|p| { + BTreeMap::from_iter( + (0..8) + .map(|pos| (pos, p >> pos & 1 == 1)) + .collect::>(), + ) + }) + .collect(); + + let correct_results = [ + true, true, true, true, true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, true, true, true, true, + true, true, true, true, false, false, false, true, true, true, true, + true, true, true, true, true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, true, true, true, true, + true, true, true, true, true, true, true, true, true, + ]; + + for (assignment, res) in assignments.iter().zip(correct_results) { + assert_eq!(cnf.evaluate(assignment), res); + } +} + +#[test] +fn boolean_35() { + let bf = boolean!(Or( + (And( + (And( + (Or((Variable(0)), (Or((Variable(1)), (Variable(2)))))), + (Or((And((Variable(0)), (Not(Variable(1))))), (Variable(2)))) + )), + (Not(Variable(2))) + )), + (And((Variable(2)), (Not(Or((Variable(0)), (Variable(1))))))) + )); + let cnf: CNFBooleanFunction = bf.into(); + + let assignments: Vec> = (0_u32..8) + .map(|p| { + BTreeMap::from_iter( + (0..3) + .map(|pos| (pos, p >> pos & 1 == 1)) + .collect::>(), + ) + }) + .collect(); + + let correct_results = + [false, true, false, false, true, false, false, false]; + + for (assignment, res) in assignments.iter().zip(correct_results) { + assert_eq!(cnf.evaluate(assignment), res); + } +} diff --git a/rsprocess/src/lib.rs b/rsprocess/src/lib.rs index 209002b..8ee5deb 100644 --- a/rsprocess/src/lib.rs +++ b/rsprocess/src/lib.rs @@ -13,6 +13,8 @@ pub mod set; pub mod system; pub mod trace; +pub mod boolean; + pub mod dot; pub mod frequency; pub mod graph; @@ -28,3 +30,6 @@ mod set_test; #[cfg(test)] mod trace_test; + +#[cfg(test)] +mod boolean_test; diff --git a/rsprocess/src/set.rs b/rsprocess/src/set.rs index 2507ef8..08525e4 100644 --- a/rsprocess/src/set.rs +++ b/rsprocess/src/set.rs @@ -220,6 +220,14 @@ impl From> for Set { } } +impl FromIterator for Set { + fn from_iter>(iter: T) -> Self { + Self { + identifiers: iter.into_iter().collect(), + } + } +} + impl Set { /// Converts set to positive set. All elements with the same state. pub fn to_positive_set(&self, state: IdState) -> PositiveSet { @@ -775,3 +783,9 @@ impl PositiveSet { ) } } + +impl From for Set { + fn from(value: PositiveSet) -> Self { + value.positives().iter().map(|el| *el.0).collect::<_>() + } +} diff --git a/rsprocess/src/system.rs b/rsprocess/src/system.rs index 0436045..8a67a0b 100644 --- a/rsprocess/src/system.rs +++ b/rsprocess/src/system.rs @@ -742,11 +742,12 @@ impl System { self.context_elements.lock().unwrap().clone() } - pub fn to_single_products(&self) -> Self { let mut new_sys = Self::default(); - new_sys.precomputed_context_elements(self.direct_get_context_elements()); - new_sys.precomputed_product_elements(self.direct_get_product_elements()); + new_sys + .precomputed_context_elements(self.direct_get_context_elements()); + new_sys + .precomputed_product_elements(self.direct_get_product_elements()); new_sys.delta = Arc::clone(&self.delta); new_sys.available_entities = self.available_entities.clone(); @@ -755,9 +756,11 @@ impl System { let mut new_reactions = vec![]; for r in self.reaction_rules.iter() { for el in r.products.iter() { - new_reactions.push(Reaction::from(r.reactants.clone(), - r.inhibitors.clone(), - [*el].into())) + new_reactions.push(Reaction::from( + r.reactants.clone(), + r.inhibitors.clone(), + [*el].into(), + )) } } Arc::new(new_reactions) diff --git a/rsprocess/src/trace_test.rs b/rsprocess/src/trace_test.rs index 8819864..01c4014 100644 --- a/rsprocess/src/trace_test.rs +++ b/rsprocess/src/trace_test.rs @@ -11,7 +11,7 @@ use crate::translator::Translator; fn slice_atoi() { let mut translator = Translator::new(); - let reactions = vec![ + let reactions = [ ( vec!["tgfbr", "stat3", "il6r"], vec!["tbet", "gata3", "foxp3"], @@ -161,7 +161,7 @@ fn slice_positive_atoi() { let mut translator = Translator::new(); let reactions = { - let reactions = vec![ + let reactions = [ ( vec!["tgfbr", "stat3", "il6r"], vec!["tbet", "gata3", "foxp3"],