1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
//! # Boolean Networks
//!
//! A (parametrised) `BooleanNetwork` is an extension of a `RegulatoryGraph` that can specify
//! for each variable a Boolean update function. This update function can be used to generate
//! a state-transition graph of the network. There are different techniques for generating
//! such state-transition graphs. Later, you will learn about an *asynchronous* state-transition
//! graph used in this library, but other variants (such as *synchronous*) are also possible.
//!
//! Typically, a `BooleanNetwork` has a fixed update function for each variable. For example,
//! let's consider this simple graph:
//!
//! ```mermaid
//! graph LR
//!     a(A)
//!     b(B)
//!     c(C)
//!     a-- + -->b
//!     b-- + -->a
//!     c-- + -->a
//!     c-. - .->b
//!     a-- - -->a
//! ```
//!
//! We can extend this graph to a `BooleanNetwork` by assigning each variable an update function
//! (we use the `$` symbol to denote a function declaration):
//!
//! ```text
//! $A: C | (!A & B)
//! $B: A
//! $C: true
//! ```
//!
//! Notice that C has no regulators, hence its update function has to be constant. Also, `C -> B`
//! is not observable, hence `C` does not have to appear in the function of `A`. Finally, `A` appears
//! with a negation in `$A` because it regulates itself negatively. However, the choice of logical
//! connectives (`|` and `&`) is arbitrary.
//!
//! We can of course write all of this in Rust:
//!
//! ```rust
//! use biodivine_lib_param_bn::{RegulatoryGraph, BooleanNetwork, FnUpdate};
//! use std::convert::TryFrom;
//!
//! // First, we build the regulatory graph of the network.
//! let rg = RegulatoryGraph::try_from(r"
//!     A -> B
//!     B -> A
//!     C -> A
//!     C -|? B
//!     A -| A
//! ").unwrap();
//! let id_a = rg.find_variable("A").unwrap();
//! let id_b = rg.find_variable("B").unwrap();
//! let id_c = rg.find_variable("C").unwrap();
//!
//! // Now create a Boolean network and add update functions for each variable.
//! let mut bn = BooleanNetwork::new(rg);
//! assert!(bn.get_update_function(id_a).is_none());
//! // We can use standard Boolean operators `&`, `|`, `=>`, `<=>`, `^` and `!`.
//! // Names are alphanumeric strings (starting with a letter), possibly containing `_`.
//! bn.add_string_update_function("A", "C | (!A & B)")?;
//! bn.add_string_update_function("B", "A")?;
//! bn.add_string_update_function("C", "true")?;
//!
//! // We can also test that the functions are properly set.
//! assert_eq!(&Some(FnUpdate::Const(true)), bn.get_update_function(id_c));
//! assert_eq!(&Some(FnUpdate::Var(id_a)), bn.get_update_function(id_b));
//! # Ok::<(), String>(())
//! ```
//!
//! Normally, this would be the end of intro to Boolean networks, however, there is one more
//! important aspect that we need to discuss that is usually not available in other BN packages.
//!
//! ## Parametrised Boolean Networks
//!
//! As you might have noticed above, a `BooleanNetwork` is also valid if it does not have any
//! update functions set. If a Boolean network is missing some update functions, we say that
//! such network is *parametrised*. Intuitively, this means that there is some uncertainty
//! in *how* the update functions of the network behave. In fact, we will later show how to
//! introduce uncertainty even only into *parts* of an update function. But for now, let us only
//! focus on cases where the entire update function is unknown.
//!
//! To each parametrised `BooleanNetwork` corresponds a collection of fully specified Boolean
//! networks. We call these fully specified networks *parametrisations*, or *colors*. More
//! formally, a parametrisation assigns values to the rows of each update function table, thus
//! making the network fully specified, and color is just a more abstract concept from graph
//! theory which we can apply also outside of the domain of Boolean networks.
//!
//! Note that the structure of the `RegulatoryGraph` significantly limits the space of all possible
//! parametrisations. Only update functions that would be valid with respect to the specified graph
//! are considered (i.e. functions must depend only on the declared regulators and must follow
//! the declared monotonicity and observability rules).
//!
//! In our example, update function of `C` can thus be only `true` or `false`. Update function
//! of `B` can in fact be `A`, `A & !C`, and `A | !C` (since `C` is not declared as necessarily
//! observable, we actually also consider both cases where `C` can have impact, but also the one
//! where it does not). For the update function of `A`, there are 9 possible valid
//! update functions.
//!
//! When the update function for a variable `X` is completely unknown as in this case, we say
//! that `X` is *implicitly* parametrised.
//!
//! ### Uninterpreted Boolean Functions as Parameters
//!
//! If only part of the update function is unknown, we can use *explicit* parameters, or
//! *uninterpreted Boolean functions*, to describe such behaviour. Formally, every uninterpreted
//! Boolean function has a fixed name and arity, but its behaviour is otherwise unspecified.
//!
//! For example, for variable `A` from above, we can write:
//! ```text
//! $A: C | f(A, B)
//! ```
//!
//! Here, `f` is an uninterpreted function of artiy `2` (we can also use uninterpreted constants,
//! which are simply functions of arity `0`). This way, we have just exactly specified the
//! dependence of `A` on `C`, while leaving the dependence on `A` and `B` unknown. Note that `f`
//! can only take values which follow the properties of the regulatory graph, just as we had for
//! the implicitly parametrised case above (so in this case, there are two possible
//! instantiations of `f`).
//!
//! When the update function of a variable `X` contains such uninterpreted functions, we say `X`
//! is *explicitly* parametrised and the uninterpreted functions are its explicit parameters.
//! Note that such explicit parameter can be shared accross variables. By using a parameter with
//! the same name in two different update functions, you declare that this part of the two
//! functions is identical.
//!
//! > *At the moment, arguments of each explicit parameter must be variables (complex Boolean
//! > expressions are not allowed). However, this restriction may be lifted in the future.*
//!
//! In Rust, we can capture this in the following way:
//!
//! ```rust
//! # use biodivine_lib_param_bn::{RegulatoryGraph, BooleanNetwork, FnUpdate, ParameterId};
//! # use std::convert::TryFrom;
//! #
//! # // First, we build the regulatory graph of the network.
//! # let rg = RegulatoryGraph::try_from(r"
//! #     A -> B
//! #     B -> A
//! #     C -> A
//! #     C -|? B
//! #     A -| A
//! # ").unwrap();
//! # let id_a = rg.find_variable("A").unwrap();
//! # let id_b = rg.find_variable("B").unwrap();
//! # let id_c = rg.find_variable("C").unwrap();
//! // Use the same graph as above...
//! let mut bn = BooleanNetwork::new(rg);
//! assert!(bn.get_update_function(id_a).is_none());
//! // Every explicit parameter must be declared before you use it in a function.
//! let id_f: ParameterId = bn.add_parameter("f", 2)?;
//! // `ParameterId` indexes `BooleanNetwork` just as `VariableId` would.
//! assert_eq!("f", bn[id_f].get_name());
//! assert_eq!(2, bn[id_f].get_arity());
//! bn.add_string_update_function("A", "C | f(A, B)")?;
//!
//! let params: Vec<ParameterId> = bn.get_update_function(id_a).as_ref().unwrap().collect_parameters();
//! assert_eq!(vec![id_f], params);
//! # Ok::<(), String>(())
//! ```
//!
//! Overall, this mechanism provides a very powerful framework for capturing uncertainty
//! in Boolean networks.
//!
//! ## String serialisation and other features
//!
//! To store and share the `BooleanNetwork` objects, you can use a similar format to
//! that used by the `RegulatoryGraph`. The only difference is that now each line can be either
//! a regulation, or a function declaration (the order of declarations does not matter).
//! Furthermore, you can prefix a line with `#` to mark that line as a comment which will be
//! skipped by the parser. Together, this is what we refer to as the `.aeon` format.
//!
//! ```rust
//! use biodivine_lib_param_bn::BooleanNetwork;
//! use std::convert::TryFrom;
//! // Notice that C is implicitly parametrised since we do not specify an update function for it.
//! // Also, explicit parameters are not declared, they are inferred from declared functions.
//! let bn = BooleanNetwork::try_from(r"
//!     A -> B
//!     C -|? B
//!     ## Update function of variable B:
//!     $B: A
//!     C -> A
//!     B -> A
//!     A -| A
//!     $A: C | f(A, B)
//! ").unwrap();
//!
//! // The same format is also used by the default `Display` implementation.
//! // However, note that the comments are not preserved!
//! assert_eq!(bn, BooleanNetwork::try_from(bn.to_string().as_str()).unwrap())
//! ```
//!
//! Aside from this, `BooleanNetwork` allows you to access the underlying `RegulatoryGraph`
//! by calling `BooleanNetwork.as_graph()`. But in fact, a lot of the commonly used methods
//! of the `RegulatoryGraph` are also implemented for the `BooleanNetwork` (e.g.
//! `get_variable_name` or `regulators`), so the conversion often isn't necessary.
//!
//!