{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"A quick tutorial showing the features of BoNesis for the synthesis of Most Permissive Boolean Networks from network architecture and dynamical constraints."
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"import bonesis\n",
"import pandas as pd\n",
"from colomoto_jupyter import tabulate"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The synthesis uses two inputs:\n",
"1. the domain of BNs, which can be a single BN, or specified by an influence graph\n",
"2. a table (dictionnary) specifying the (partial) observations of the systems\n",
"\n",
"## Synthesis from influence graph and dynamical constraints\n",
"\n",
"### Influence graph\n",
"\n",
"Let us define an influence graph from a list of pairwise interactions, with a sign."
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"influences = [\n",
"(\"Pax6\",\"Pax6\",dict(sign=1)),\n",
"(\"Pax6\",\"Hes5\",dict(sign=1)),\n",
"(\"Pax6\",\"Mash1\",dict(sign=1)),\n",
"(\"Hes5\",\"Mash1\",dict(sign=-1)),\n",
"(\"Hes5\",\"Scl\",dict(sign=1)),\n",
"(\"Hes5\",\"Olig2\",dict(sign=1)),\n",
"(\"Hes5\",\"Stat3\",dict(sign=1)),\n",
"(\"Mash1\",\"Hes5\",dict(sign=-1)),\n",
"(\"Mash1\",\"Zic1\",dict(sign=1)),\n",
"(\"Mash1\",\"Brn2\",dict(sign=1)),\n",
"(\"Zic1\",\"Tuj1\",dict(sign=1)),\n",
"(\"Brn2\",\"Tuj1\",dict(sign=1)),\n",
"(\"Scl\",\"Olig2\",dict(sign=-1)),\n",
"(\"Scl\",\"Stat3\",dict(sign=1)),\n",
"(\"Olig2\",\"Scl\",dict(sign=-1)),\n",
"(\"Olig2\",\"Myt1L\",dict(sign=1)),\n",
"(\"Olig2\",\"Sox8\",dict(sign=1)),\n",
"(\"Olig2\",\"Brn2\",dict(sign=-1)),\n",
"(\"Stat3\",\"Aldh1L1\",dict(sign=1)),\n",
"(\"Myt1L\",\"Tuj1\",dict(sign=1)),\n",
"]"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"# computing graph layout...\n"
]
},
{
"data": {
"image/svg+xml": [
"\n",
"\n",
"\n",
"\n",
"\n"
],
"text/plain": [
""
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"dom1 = bonesis.InfluenceGraph(influences)\n",
"dom1"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Here, `dom1` delimits any BN that uses *at most* the listed influences, with the right sign. Thus, some solutions may use only a subset of this influence graph.\n",
"\n",
"If you want to enforce BNs using *all* the given influences, use the option `exact=True`:"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [],
"source": [
"dom2 = bonesis.InfluenceGraph(influences, exact=True)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"For influence graphs with large in-degrees, it is necessary to specify a bound on the number of clauses in the disjunction normal form (DNF) of the BNs with the `maxclause` argument. See `help(bonesis.InfluenceGraph)` for other options."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Observations\n",
"\n",
"They are specified by a Python dictionnary associating observation names to observed states of a subset of nodes:"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"
\n",
"\n",
"
\n",
" \n",
"
\n",
"
\n",
"
Pax6
\n",
"
Hes5
\n",
"
Mash1
\n",
"
Scl
\n",
"
Olig2
\n",
"
Stat3
\n",
"
Zic1
\n",
"
Brn2
\n",
"
Tuj1
\n",
"
Myt1L
\n",
"
Sox8
\n",
"
Aldh1L1
\n",
"
\n",
" \n",
" \n",
"
\n",
"
zero
\n",
"
0
\n",
"
0
\n",
"
0
\n",
"
0
\n",
"
0
\n",
"
0
\n",
"
0
\n",
"
0
\n",
"
0
\n",
"
0
\n",
"
0
\n",
"
0
\n",
"
\n",
"
\n",
"
init
\n",
"
1
\n",
"
0
\n",
"
0
\n",
"
0
\n",
"
0
\n",
"
0
\n",
"
0
\n",
"
0
\n",
"
0
\n",
"
0
\n",
"
0
\n",
"
0
\n",
"
\n",
"
\n",
"
tM
\n",
"
1
\n",
"
\n",
"
\n",
"
0
\n",
"
0
\n",
"
\n",
"
\n",
"
\n",
"
0
\n",
"
\n",
"
0
\n",
"
0
\n",
"
\n",
"
\n",
"
fT
\n",
"
1
\n",
"
\n",
"
\n",
"
\n",
"
\n",
"
\n",
"
1
\n",
"
1
\n",
"
1
\n",
"
\n",
"
0
\n",
"
0
\n",
"
\n",
"
\n",
"
tO
\n",
"
1
\n",
"
\n",
"
\n",
"
0
\n",
"
1
\n",
"
\n",
"
\n",
"
\n",
"
0
\n",
"
\n",
"
0
\n",
"
0
\n",
"
\n",
"
\n",
"
fMS
\n",
"
1
\n",
"
\n",
"
\n",
"
\n",
"
\n",
"
\n",
"
0
\n",
"
0
\n",
"
0
\n",
"
\n",
"
1
\n",
"
0
\n",
"
\n",
"
\n",
"
tS
\n",
"
1
\n",
"
\n",
"
\n",
"
1
\n",
"
0
\n",
"
\n",
"
\n",
"
\n",
"
0
\n",
"
\n",
"
0
\n",
"
0
\n",
"
\n",
"
\n",
"
fA
\n",
"
1
\n",
"
\n",
"
\n",
"
\n",
"
\n",
"
\n",
"
0
\n",
"
0
\n",
"
0
\n",
"
\n",
"
0
\n",
"
1
\n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
" Pax6 Hes5 Mash1 Scl Olig2 Stat3 Zic1 Brn2 Tuj1 Myt1L Sox8 Aldh1L1\n",
"zero 0 0 0 0 0 0 0 0 0 0 0 0\n",
"init 1 0 0 0 0 0 0 0 0 0 0 0\n",
"tM 1 0 0 0 0 0\n",
"fT 1 1 1 1 0 0\n",
"tO 1 0 1 0 0 0\n",
"fMS 1 0 0 0 1 0\n",
"tS 1 1 0 0 0 0\n",
"fA 1 0 0 0 0 1"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"data = {\n",
" \"zero\": {n: 0 for n in dom1}, # all nodes are 0\n",
" \"init\": {n: 1 if n == \"Pax6\" else 0 for n in dom1}, # all nodes are 0 but Pax6\n",
" \"tM\": {\"Pax6\": 1, \"Tuj1\": 0, \"Scl\": 0, \"Aldh1L1\": 0, \"Olig2\": 0, \"Sox8\": 0},\n",
" \"fT\": {\"Pax6\": 1, \"Tuj1\": 1, \"Brn2\": 1, \"Zic1\": 1, \"Aldh1L1\": 0, \"Sox8\": 0},\n",
" \"tO\": {\"Pax6\": 1, \"Tuj1\": 0 ,\"Scl\": 0, \"Aldh1L1\": 0, \"Olig2\": 1, \"Sox8\": 0}, \n",
" \"fMS\": {\"Pax6\": 1, \"Tuj1\": 0, \"Zic1\": 0, \"Brn2\": 0, \"Aldh1L1\": 0, \"Sox8\": 1},\n",
" \"tS\": {\"Pax6\": 1, \"Tuj1\": 0, \"Scl\": 1, \"Aldh1L1\": 0, \"Olig2\": 0, \"Sox8\": 0},\n",
" \"fA\": {\"Pax6\": 1, \"Tuj1\": 0, \"Zic1\": 0, \"Brn2\": 0, \"Aldh1L1\": 1, \"Sox8\": 0},\n",
"}\n",
"pd.DataFrame.from_dict(data, orient=\"index\").fillna('')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Dynamical properties"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [],
"source": [
"bo = bonesis.BoNesis(dom1, data)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The `data` dictionnary specifies *observations* that can be used to constraint *configurations* (or states) of the network.\n",
"\n",
"There are two shortcuts for binding a configuration to an observation:\n",
"`~bo.obs(\"A\")` is a unique pre-defined configuration bound to the observation `\"A\"`;\n",
"`+bo.obs(\"A\")` returns a *new* configuration bound to `\"A\"`. Thus in the following code:\n",
"```python\n",
"cfg1 = ~bo.obs(\"A\")\n",
"cfg2 = ~bo.obs(\"A\")\n",
"cfg3 = +bo.obs(\"A\")\n",
"cfg4 = +bo.obs(\"A\")\n",
"```\n",
"`cfg1` and `cfg2` refers to the *same* configuration; whereas `cfg3` and `cfg4` *may* be different.\n",
"\n",
"### Attractor constraints\n",
"\n",
"We detail two kind of attractor constraints: fixed points and trap spaces. Both are specified with the `fixed` predicate, which, depending on the argument will enforce the existence of one of the two kinds of attractor.\n",
"\n",
"#### Fixed points\n",
"\n",
"When giving a configuration as argument, `fixed` ensures that the configuration is a fixed point:"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [],
"source": [
"bo.fixed(~bo.obs(\"fA\"))\n",
"bo.fixed(~bo.obs(\"fMS\"));"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Trap spaces\n",
"\n",
"A trap space specification is given by an observation, which enforces that all the nodes in the given observations can never change of value (thus any reachable attractor have these nodes fixed):"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [],
"source": [
"fT_tp = bo.fixed(bo.obs(\"fT\"))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Reachability constraints\n",
"\n",
"Reachability relates to the presence or absence of trajectory between two configurations.\n",
"\n",
"#### Existence of trajectory\n",
"\n",
"They can be specified using the `reach` function, or equivalently the `>=` operator between two configurations. The right-hand side can also be a `fixed` constraint."
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [],
"source": [
"~bo.obs(\"init\") >= ~bo.obs(\"tM\") >= fT_tp\n",
"~bo.obs(\"init\") >= ~bo.obs(\"tO\") >= ~bo.obs(\"fMS\")\n",
"~bo.obs(\"init\") >= ~bo.obs(\"tS\") >= ~bo.obs(\"fA\");"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Absence of trajectory\n",
"\n",
"They can be specified using the `nonreach` function, or equivalently the `/` operator between two configurations. The right-hand side can also be a `fixed` constraint."
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [],
"source": [
"~bo.obs(\"zero\") / fT_tp\n",
"~bo.obs(\"zero\") / ~bo.obs(\"fMS\")\n",
"~bo.obs(\"zero\") / ~bo.obs(\"fA\");"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Enumeration of compatible BNs\n",
"\n",
"Enumerations of solutions are done through iterators. The basic one being the `boolean_networks` which returns `mpbn.MPBooleanNetwork` objects."
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Grounding...done in 0.0s\n",
"Aldh1L1 <- Stat3\n",
"Brn2 <- Mash1\n",
"Hes5 <- !Mash1\n",
"Mash1 <- Pax6&!Hes5\n",
"Myt1L <- 1\n",
"Olig2 <- !Scl&Hes5\n",
"Pax6 <- Pax6\n",
"Scl <- Hes5&!Olig2\n",
"Sox8 <- Olig2\n",
"Stat3 <- Scl\n",
"Tuj1 <- Zic1\n",
"Zic1 <- Mash1\n",
"\n",
"Aldh1L1 <- Stat3\n",
"Brn2 <- Mash1\n",
"Hes5 <- !Mash1\n",
"Mash1 <- Pax6&!Hes5\n",
"Myt1L <- 0\n",
"Olig2 <- !Scl&Hes5\n",
"Pax6 <- Pax6\n",
"Scl <- Hes5&!Olig2\n",
"Sox8 <- Olig2\n",
"Stat3 <- Scl\n",
"Tuj1 <- Zic1\n",
"Zic1 <- Mash1\n",
"\n"
]
}
],
"source": [
"for bn in bo.boolean_networks(limit=2): # limit is optional\n",
" print(bn)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Display as a table:"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Grounding...done in 0.0s\n"
]
},
{
"data": {
"text/html": [
"