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
//! # Regulatory graphs
//!
//! One of the most basic concepts in computational biology are *regulatory graphs* (or networks).
//! A regulatory graph is a directed graph where the vertices represent some biological entities
//! and the edges correspond to some high-level abstraction of interaction between the entities.
//! Such edges are called regulations.
//!
//! For example, let's say we have a system with three chemical species, `A`, `B`, and `C`, such
//! that they all depend cyclically on each other (`A` influences `B`, which influences `C`,
//! which influences `A`), and additionally, we also have `C` influencing `B`. This relationship
//! can be represented by a graph:
//!
//! ```mermaid
//! graph LR
//! a(A)
//! b(B)
//! c(C)
//! a-->b
//! b-->c
//! c-->a
//! c-->b
//! ```
//!
//! We can then easily construct such graph in Rust:
//!
//! ```rust
//! use biodivine_lib_param_bn::RegulatoryGraph;
//! use biodivine_lib_param_bn::Monotonicity::{Activation, Inhibition};
//! // RegulatoryGraph expects `String` names, so we have to convert `&str` to `String`.
//! let mut rg = RegulatoryGraph::new(vec!["A".into(), "B".into(), "C".into()]);
//! // Notice that `add_regulation` can return an error if
//! // the edge already exists or uses invalid variable names.
//! rg.add_regulation("A", "B", true, Some(Activation))?;
//! rg.add_regulation("B", "C", true, Some(Activation))?;
//! rg.add_regulation("C", "A", true, Some(Activation))?;
//! rg.add_regulation("C", "B", false, Some(Inhibition))?;
//! # Ok::<(), String>(())
//! ```
//!
//! When adding regulations, we see an extra Boolean argument: *observability*. Observability
//! indicates whether the regulation has some effect on the target variable (in this case, we
//! require all regulations to have effect except for `C -> B`). Furthermore, for each regulation,
//! we can specify its monotonicity, which is also a common property of biological networks.
//! If a regulation is an *activation*, increasing the regulator increases the value of the
//! target species. For *inhibitions*, the effect is reversed.
//!
//! We can include this information in the visual representation of the graph as well:
//!
//! ```mermaid
//! graph LR
//!     a(A)
//!     b(B)
//!     c(C)
//!     a-- + -->b
//!     b-- + -->c
//!     c-- + -->a
//!     c-. - .->b
//! ```
//!
//! Note that here, we use `+` and `-` for activation and inhibition, and dotted arrow for
//! non-observable regulation. In other materials, you can often see other visual langauge, such
//! as different arrow heads, or color-coded arrows.
//!
//! ## Basic Manipulation of Regulatory Graphs
//!
//! At the moment, there is no way to add, remove, or edit variables once the `RegulatoryGraph`
//! is created. However, as we have seen, you can add new regulations. To easily represent
//! each variable in the graph, we use `VariableId`, which is simply a typesafe variable index.
//! The advantage of `VariableId` is that it implements `Copy` and is therefore much easier to
//! pass around than `String` names.
//! You can then also use corresponding `VariableId` to find regulations.
//!
//! ```rust
//! # use biodivine_lib_param_bn::{RegulatoryGraph, VariableId};
//! # use biodivine_lib_param_bn::Monotonicity::{Activation, Inhibition};
//! # let mut rg = RegulatoryGraph::new(vec!["A".into(), "B".into(), "C".into()]);
//! # rg.add_regulation("A", "B", true, Some(Activation))?;
//! # rg.add_regulation("B", "C", true, Some(Activation))?;
//! # rg.add_regulation("C", "A", true, Some(Activation))?;
//! # rg.add_regulation("C", "B", false, Some(Inhibition))?;
//! let id_a = rg.find_variable("A").unwrap();
//! let id_b = rg.find_variable("B").unwrap();
//! let id_c = rg.find_variable("C").unwrap();
//!
//! assert_eq!("B", rg.get_variable(id_b).get_name());
//! // There are some shortcuts for most commonly used functionality:
//! assert_eq!("A", rg.get_variable_name(id_a));
//! // You can also index the RegulatoryGraph using variable ids:
//! assert_eq!(rg.get_variable_name(id_c), rg[id_c].get_name());
//!
//! // Each regulation has a regulator, target, observability, and monotonicity.
//! assert!(rg.find_regulation(id_c, id_b).is_some());
//! assert!(rg.find_regulation(id_a, id_b).unwrap().is_observable());
//!
//! // You can also iterate through all variables and regulations of the network.
//! for v in rg.variables() {   // v: VariableId
//!     println!("Variable {:?} has name {}.", v, rg[v]);
//! }
//! for r in rg.regulations() { // r: Regulation
//!     println!("Regulation from {} to {}.", rg[r.get_regulator()], rg[r.get_target()]);
//! }
//! # Ok::<(), String>(())
//! ```
//!
//! ## Advanced Functions on Regulatory Graphs
//!
//! Aside from reading basic structure of the graph, we can also obtain some more complex
//! information from the graph structure:
//!
//! ```rust
//! # use biodivine_lib_param_bn::{RegulatoryGraph, VariableId};
//! # use biodivine_lib_param_bn::Monotonicity::{Activation, Inhibition};
//! # use std::collections::HashSet;
//! # let mut rg = RegulatoryGraph::new(vec!["A".into(), "B".into(), "C".into()]);
//! # rg.add_regulation("A", "B", true, Some(Activation))?;
//! # rg.add_regulation("B", "C", true, Some(Activation))?;
//! # rg.add_regulation("C", "A", true, Some(Activation))?;
//! # rg.add_regulation("C", "B", false, Some(Inhibition))?;
//! # let id_a = rg.find_variable("A").unwrap();
//! # let id_b = rg.find_variable("B").unwrap();
//! # let id_c = rg.find_variable("C").unwrap();
//! // Find all predecessors/successors of a node:
//! assert_eq!(vec![id_c], rg.regulators(id_a));
//! assert_eq!(vec![id_a, id_b], rg.targets(id_c));
//! // This can be used (for example) to test for input/output variables:
//! let is_input = rg.regulators(id_b).is_empty();
//! assert!(!is_input);
//!
//! // Find all transitive successors/predecessors of a node:
//! // (in our simple graph, all variables depend on each other transitively)
//! let all_variables: HashSet<VariableId> = rg.variables().collect();
//! assert_eq!(all_variables, rg.transitive_regulators(id_a));
//! assert_eq!(all_variables, rg.transitive_targets(id_b));
//! // Using this, we can (for example) test if two variables are independent:
//! let a_independent_of_b = !rg.transitive_regulators(id_a).contains(&id_b);
//! assert!(!a_independent_of_b);
//!
//! // Finally, we can compute all strongly connected components of the regulatory graph:
//! let components: Vec<HashSet<VariableId>> = rg.components();
//! assert_eq!(1, components.len());
//! assert_eq!(all_variables, components.into_iter().next().unwrap());
//! // (in our case, the whole graph is one component)
//! # Ok::<(), String>(())
//! ```
//!
//! ## String Serialisation
//!
//! To safely transfer and store regulatory graphs, we implement a simple string format.
//! Each regulation is stored as an "arrow string" (such as `A -> B`) on a separate line.
//!
//! Each arrow string starts with `-`, then followed by `>`, `|`, or `?`, corresponding
//! to activation, inhibition or unspecified monotonicity. Finally, if a regulation is not
//! observable, the arrow string also contains one more `?`.
//!
//! ```rust
//! # use biodivine_lib_param_bn::{RegulatoryGraph, VariableId};
//! # use biodivine_lib_param_bn::Monotonicity::{Activation, Inhibition};
//! # use std::collections::HashSet;
//! # use std::convert::TryFrom;
//! # let mut rg = RegulatoryGraph::new(vec!["A".into(), "B".into(), "C".into()]);
//! # rg.add_regulation("A", "B", true, Some(Activation))?;
//! # rg.add_regulation("B", "C", true, Some(Activation))?;
//! # rg.add_regulation("C", "A", true, Some(Activation))?;
//! # rg.add_regulation("C", "B", false, Some(Inhibition))?;
//! # let id_a = rg.find_variable("A").unwrap();
//! # let id_b = rg.find_variable("B").unwrap();
//! # let id_c = rg.find_variable("C").unwrap();
//! let mut rg_2 = RegulatoryGraph::try_from(r"
//!     A -> B
//!     B -> C
//!     C -> A
//!     C -|? B
//! ")?;
//!
//! assert_eq!(rg, rg_2);
//!
//! // You can also use the arrow string representation to add new regulations:
//! rg_2.add_string_regulation("A -?? C")?;
//! assert_ne!(rg, rg_2);
//!
//! // This representation corresponds to the default `Display` format:
//! let rg_string = format!("{}", rg);
//! assert_eq!(rg, RegulatoryGraph::try_from(rg_string.as_str())?);
//! # Ok::<(), String>(())
//! ```
//!
//! In the next chapter, you can learn how to turn a `RegulatoryGraph` into `BooleanNetwork` by
//! adding (partial) Boolean update functions.
//!