biodivine_lib_param_bn/
_impl_boolean_network_to_bnet.rs

1use crate::{BinaryOp, BooleanNetwork, FnUpdate, VariableId};
2use regex::Regex;
3
4impl BooleanNetwork {
5    /// Produce a `.bnet` string representation of this model.
6    ///
7    /// Returns an error if the network is parametrised and thus cannot be converted to `.bnet`.
8    /// Also returns an error if the network contains names which are not supported in `.bnet`,
9    /// such as starting with numbers.
10    ///
11    /// However, you can override this behaviour using `rename_if_necessary`. If this flag is set,
12    /// all invalid names will be prefixed with `_`.
13    pub fn to_bnet(&self, rename_if_necessary: bool) -> Result<String, String> {
14        let mut network = self.clone();
15        // A regex which only matches valid `.bnet` names.
16        let name_re = Regex::new(r"^[a-zA-Z_][a-zA-Z0-9_]*$").unwrap();
17        for var in network.variables() {
18            let name = network.get_variable_name(var);
19            if !name_re.is_match(name) {
20                if rename_if_necessary {
21                    let new_name = format!("_{}", name);
22                    network.as_graph_mut().set_variable_name(var, &new_name)?;
23                } else {
24                    return Err(format!(
25                        "Variable {} cannot be exported to bnet. Please rename it first.",
26                        name
27                    ));
28                }
29            }
30        }
31        let mut model = "targets,factors\n".to_string();
32        for v in network.variables() {
33            let name = network.get_variable_name(v);
34            if let Some(function) = network.get_update_function(v) {
35                let function_string = fn_update_to_bnet_string(v, function, self)?;
36                let line = format!("{}, {}\n", name, function_string);
37                model.push_str(line.as_str());
38            } else {
39                // If there is no update function, we can skip it assuming it has no inputs (constant).
40                if self.regulators(v).is_empty() {
41                    continue;
42                } else {
43                    return Err("Parametrised network cannot be converted to .bnet.".to_string());
44                }
45            }
46        }
47
48        Ok(model)
49    }
50}
51
52fn fn_update_to_bnet_string(
53    var: VariableId,
54    function: &FnUpdate,
55    network: &BooleanNetwork,
56) -> Result<String, String> {
57    Ok(match function {
58        FnUpdate::Var(id) => network.get_variable_name(*id).clone(),
59        FnUpdate::Param(id, args) => {
60            if args.is_empty() {
61                network.get_parameter(*id).get_name().to_string()
62            } else {
63                return Err(
64                    "Networks with free functions cannot be converted to .bnet.".to_string()
65                );
66            }
67        }
68        FnUpdate::Const(value) => {
69            // .bnet does not have constants, but we can simulate a constant like this:
70            let name = network.get_variable_name(var);
71            if *value {
72                format!("({} | !{})", name, name)
73            } else {
74                format!("({} & !{})", name, name)
75            }
76        }
77        FnUpdate::Not(inner) => {
78            format!("!{}", fn_update_to_bnet_string(var, inner, network)?)
79        }
80        FnUpdate::Binary(op, left, right) => {
81            let left = fn_update_to_bnet_string(var, left, network)?;
82            let right = fn_update_to_bnet_string(var, right, network)?;
83            match *op {
84                BinaryOp::And => format!("({} & {})", left, right),
85                BinaryOp::Or => format!("({} | {})", left, right),
86                BinaryOp::Imp => format!("(!{} | {})", left, right),
87                BinaryOp::Iff => format!("(({} & {}) | (!{} & !{}))", left, right, left, right),
88                BinaryOp::Xor => format!("(({} & !{}) | (!{} & {}))", left, right, left, right),
89            }
90        }
91    })
92}
93
94#[cfg(test)]
95mod tests {
96    use crate::BooleanNetwork;
97    use std::convert::TryFrom;
98
99    #[test]
100    fn test_network_to_bnet() {
101        let model = std::fs::read_to_string("aeon_models/g2a_instantiated.aeon").unwrap();
102        let network = BooleanNetwork::try_from(model.as_str()).unwrap();
103        let network_after =
104            BooleanNetwork::try_from_bnet(network.to_bnet(false).unwrap().as_str()).unwrap();
105
106        assert_eq!(network.graph.num_vars(), network_after.graph.num_vars());
107        for v in network.graph.variables() {
108            assert_eq!(
109                network.graph.get_variable(v),
110                network_after.graph.get_variable(v)
111            );
112            assert_eq!(
113                network.graph.regulators(v),
114                network_after.graph.regulators(v)
115            );
116            assert_eq!(
117                network.get_update_function(v),
118                network_after.get_update_function(v)
119            );
120
121            for reg in network.graph.regulators(v) {
122                // .bnet looses information about regulation properties.
123                let r1 = network.graph.find_regulation(reg, v).unwrap();
124                let r2 = network_after.graph.find_regulation(reg, v).unwrap();
125                assert_eq!(r1.regulator, r2.regulator);
126                assert_eq!(r1.target, r2.target);
127            }
128        }
129    }
130
131    #[test]
132    fn test_network_to_bnet_invalid() {
133        let bn = BooleanNetwork::try_from("A -> B \n B -> A").unwrap();
134        // Parametrised network cannot be exported.
135        assert!(bn.to_bnet(false).is_err());
136        let bn = BooleanNetwork::try_from("3A -> B \n B -> 3A \n $B:3A \n $3A:B").unwrap();
137        // Network with names starting with numbers cannot be exported.
138        assert!(bn.to_bnet(false).is_err());
139        assert!(bn.to_bnet(true).is_ok());
140    }
141}