{
"cells": [
{
"cell_type": "markdown",
"id": "ccf0d0a1",
"metadata": {},
"source": [
"# BoolForge Tutorial #8: Perturbation and sensitivity analysis of Boolean networks\n",
"\n",
"In this tutorial, we study how Boolean networks respond to perturbations.\n",
"Rather than implementing perturbations manually, we leverage BoolForge’s\n",
"built-in robustness and sensitivity measures.\n",
"\n",
"You will learn how to:\n",
"- quantify robustness and fragility of Boolean networks under synchronous update,\n",
"- interpret basin-level and attractor-level robustness measures,\n",
"- compare exact and approximate robustness computations, and\n",
"- compute Derrida values as a measure of dynamical sensitivity.\n",
"\n",
"These tools allow us to assess dynamical stability and resilience of Boolean\n",
"network models in a principled and computationally efficient way.\n",
"\n",
"---\n",
"## 0. Setup"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "dcbf8be9",
"metadata": {
"execution": {
"iopub.execute_input": "2026-01-15T17:24:00.228785Z",
"iopub.status.busy": "2026-01-15T17:24:00.228529Z",
"iopub.status.idle": "2026-01-15T17:24:00.902351Z",
"shell.execute_reply": "2026-01-15T17:24:00.902099Z"
}
},
"outputs": [],
"source": [
"import boolforge\n",
"import numpy as np\n",
"import pandas as pd\n",
"import matplotlib.pyplot as plt"
]
},
{
"cell_type": "markdown",
"id": "67e82465",
"metadata": {},
"source": [
"---\n",
"## 1. A running example Boolean network\n",
"\n",
"We reuse the small Boolean network from the previous tutorial."
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "7c79b9f9",
"metadata": {
"execution": {
"iopub.execute_input": "2026-01-15T17:24:00.903839Z",
"iopub.status.busy": "2026-01-15T17:24:00.903697Z",
"iopub.status.idle": "2026-01-15T17:24:00.905966Z",
"shell.execute_reply": "2026-01-15T17:24:00.905729Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Variables: ['x' 'y' 'z']\n",
"Number of nodes: 3\n"
]
}
],
"source": [
"string = \"\"\"\n",
"x = y\n",
"y = x OR z\n",
"z = y\n",
"\"\"\"\n",
"\n",
"bn = boolforge.BooleanNetwork.from_string(string, separator=\"=\")\n",
"\n",
"print(\"Variables:\", bn.variables)\n",
"print(\"Number of nodes:\", bn.N)"
]
},
{
"cell_type": "markdown",
"id": "56837b88",
"metadata": {},
"source": [
"---\n",
"## 2. Exact attractors and robustness measures\n",
"\n",
"BoolForge provides a single method that computes:\n",
"- all attractors,\n",
"- basin sizes,\n",
"- overall network coherence and fragility,\n",
"- basin-level coherence and fragility, and\n",
"- attractor-level coherence and fragility.\n",
"\n",
"These quantities are defined via systematic single-bit perturbations\n",
"in the Boolean hypercube and can be computed *exactly* for small networks."
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "981aaff1",
"metadata": {
"execution": {
"iopub.execute_input": "2026-01-15T17:24:00.907100Z",
"iopub.status.busy": "2026-01-15T17:24:00.907018Z",
"iopub.status.idle": "2026-01-15T17:24:01.608782Z",
"shell.execute_reply": "2026-01-15T17:24:01.608580Z"
}
},
"outputs": [
{
"data": {
"text/plain": [
"dict_keys(['Attractors', 'NumberOfAttractors', 'BasinSizes', 'AttractorID', 'Coherence', 'Fragility', 'BasinCoherence', 'BasinFragility', 'AttractorCoherence', 'AttractorFragility'])"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"results_exact = bn.get_attractors_and_robustness_measures_synchronous_exact()\n",
"results_exact.keys()"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "2c0f1b8c",
"metadata": {
"execution": {
"iopub.execute_input": "2026-01-15T17:24:01.647980Z",
"iopub.status.busy": "2026-01-15T17:24:01.647796Z",
"iopub.status.idle": "2026-01-15T17:24:01.650048Z",
"shell.execute_reply": "2026-01-15T17:24:01.649811Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Number of attractors: 3\n",
"Attractors (decimal states): [[0], [2, 5], [7]]\n",
"Eventual attractor: [0 1 1 2 1 1 2 2]\n",
"Basin sizes: [0.125 0.5 0.375]\n",
"Overall coherence: 0.3333333333333333\n",
"Overall fragility: 0.3333333333333333\n"
]
}
],
"source": [
"print(\"Number of attractors:\", results_exact[\"NumberOfAttractors\"])\n",
"print(\"Attractors (decimal states):\", results_exact[\"Attractors\"])\n",
"print(\"Eventual attractor:\", results_exact[\"AttractorID\"])\n",
"\n",
"print(\"Basin sizes:\", results_exact[\"BasinSizes\"])\n",
"print(\"Overall coherence:\", results_exact[\"Coherence\"])\n",
"print(\"Overall fragility:\", results_exact[\"Fragility\"])"
]
},
{
"cell_type": "markdown",
"id": "8dd8cafc",
"metadata": {},
"source": [
"---\n",
"## 3. Basin-level and attractor-level robustness\n",
"\n",
"Robustness can be resolved at different structural levels.\n",
"We now inspect basin-specific and attractor-specific measures."
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "935aeb94",
"metadata": {
"execution": {
"iopub.execute_input": "2026-01-15T17:24:01.651205Z",
"iopub.status.busy": "2026-01-15T17:24:01.651114Z",
"iopub.status.idle": "2026-01-15T17:24:01.656837Z",
"shell.execute_reply": "2026-01-15T17:24:01.656648Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Basin-level robustness:\n"
]
},
{
"data": {
"text/html": [
"
\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" BasinSize | \n",
" BasinCoherence | \n",
" BasinFragility | \n",
"
\n",
" \n",
" \n",
" \n",
" | 0 | \n",
" 0.125 | \n",
" 0.000000 | \n",
" 0.500000 | \n",
"
\n",
" \n",
" | 1 | \n",
" 0.500 | \n",
" 0.333333 | \n",
" 0.333333 | \n",
"
\n",
" \n",
" | 2 | \n",
" 0.375 | \n",
" 0.444444 | \n",
" 0.277778 | \n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
" BasinSize BasinCoherence BasinFragility\n",
"0 0.125 0.000000 0.500000\n",
"1 0.500 0.333333 0.333333\n",
"2 0.375 0.444444 0.277778"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Attractor-level robustness:\n"
]
},
{
"data": {
"text/html": [
"\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" AttractorCoherence | \n",
" AttractorFragility | \n",
"
\n",
" \n",
" \n",
" \n",
" | 0 | \n",
" 0.000000 | \n",
" 0.500000 | \n",
"
\n",
" \n",
" | 1 | \n",
" 0.333333 | \n",
" 0.333333 | \n",
"
\n",
" \n",
" | 2 | \n",
" 0.666667 | \n",
" 0.166667 | \n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
" AttractorCoherence AttractorFragility\n",
"0 0.000000 0.500000\n",
"1 0.333333 0.333333\n",
"2 0.666667 0.166667"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"df_basins = pd.DataFrame({\n",
" \"BasinSize\": results_exact[\"BasinSizes\"],\n",
" \"BasinCoherence\": results_exact[\"BasinCoherence\"],\n",
" \"BasinFragility\": results_exact[\"BasinFragility\"],\n",
"})\n",
"\n",
"df_attractors = pd.DataFrame({\n",
" \"AttractorCoherence\": results_exact[\"AttractorCoherence\"],\n",
" \"AttractorFragility\": results_exact[\"AttractorFragility\"],\n",
"})\n",
"\n",
"print(\"Basin-level robustness:\")\n",
"display(df_basins)\n",
"\n",
"print(\"Attractor-level robustness:\")\n",
"display(df_attractors)"
]
},
{
"cell_type": "markdown",
"id": "0bd4fe14",
"metadata": {},
"source": [
"Interpretation:\n",
"\n",
"- **Coherence** measures the fraction of single-bit perturbations that do *not*\n",
" change the final attractor.\n",
"- **Fragility** measures how much the attractor state changes *when* a perturbation\n",
" does lead to a different attractor.\n",
"\n",
"Importantly, attractors are often less stable than their basins,\n",
"a phenomenon explored in detail in Tutorial #10."
]
},
{
"cell_type": "markdown",
"id": "06ad5301",
"metadata": {},
"source": [
"---\n",
"## 4. Visualization of basin robustness"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "a87cc73b",
"metadata": {
"execution": {
"iopub.execute_input": "2026-01-15T17:24:01.657885Z",
"iopub.status.busy": "2026-01-15T17:24:01.657803Z",
"iopub.status.idle": "2026-01-15T17:24:01.700256Z",
"shell.execute_reply": "2026-01-15T17:24:01.700037Z"
}
},
"outputs": [
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAAAjsAAAHHCAYAAABZbpmkAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjUsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvWftoOwAAAAlwSFlzAAAPYQAAD2EBqD+naQAAN7NJREFUeJzt3QeUFFXe/vEfDMyMpEFEQBAZCRIUAQnzB0ysKCoGXhOiCCLiorCroKxgABEVE4gBxQS4hgWzriAGgi6CIgMqKqgEAVlJBkCQNNT/PPe83W9PT88wuWfufD/ntNI11dW3qqurnr6hqlwQBIEBAAB4qny8CwAAAFCUCDsAAMBrhB0AAOA1wg4AAPAaYQcAAHiNsAMAALxG2AEAAF4j7AAAAK8RdgAAgNcIO/DevHnzrFy5cvbqq68W6/ueeuqp7lGYPv/8c+vUqZNVrlzZrdMXX3xhxU3ve8cdd4SfT5061U378ccfi3Tdc3LdddfZ6aefbqWBttXgwYPjXQzEYT+NNGvWLKtSpYpt2bIlLu9f1hB2EFPoBJbd49NPP41r+e655x578803rSzZt2+fXXzxxfbrr7/aQw89ZM8//7w1aNDASoP//ve/LiAVRThbs2aNPfPMM3bLLbcU+rKBWHbt2uX2Z/2Qyq8zzzzTGjdubGPHji3UsiG2CtlMB5w777zTjj766CzT9SWNd9i56KKLrEePHlZSvf/++4W6vFWrVtnatWvt6aeftquvvtri5c8//7QKFSrkad0VdkaPHm2pqanWunXrQi3Pww8/7PbRLl26FOpygZzCjvZnKUjN0F//+le76aab3LKqVq1aiCVENMIOcnTWWWdZu3bt4l2MUikxMbFQl7d582b3/+rVqx903p07d7qmrqKQnJxc7OueU23Xiy++aAMHDrSyoCg/VxS/Cy+80P72t7/ZK6+8YldddVW8i+M1mrFQIKNGjbLy5cvb7NmzM02/5ppr3Anvyy+/dM/37t1rI0eOtLZt21pKSoo7YJ900kk2d+7cLMs8cOCA+7XesmVLd2I9/PDDXZXv4sWL3d/VjKaD/nPPPRduVrvyyisPWtaMjAzX1FGnTh33/uedd56tX78+0zz/+c9/XFPRUUcdZUlJSVa/fn0bMmSIq82ItHHjRuvXr58deeSRbr4jjjjCzj///Bz7rYT6Dr388st29913u9dq/U477TRbuXJljmXX+p1yyinu3yqflhNatv6mtn/V/Jx99tnuF+Lll1+ep/URHXBbtGjhynTcccfZG2+84Zat2pic+uzEErnuWu/27du7f2ubhT4zNZVq/6lYsWLMfgvahxTsdu/ene37zJ8/37Zu3Wpdu3bN8rdHH33Ujj32WKtUqZIdeuihLrS/9NJL7m/a71QGrWM0zaO/LVy4MNP23bBhg6tJ1L+1T+oXufapvOy7kdQMq+2sz0XlVB+OSNrGKse3335rl112mVuHE0880f1t//79NmbMGGvUqJF7vT4j7dt79uzJtAxNP+ecc9x26tChgytTw4YN7Z///GeW8qxevdrtKzVq1HDb7P/9v/9nM2bMyDRPrP5Zkft2ZLPODz/84E7m+r7pfbW/X3rppbZt27Ys7x1d5ljf5+y+T9OnTz/o91qeeuopt70OOeQQty303YiWm+OU1l2fq6hGJrQ/R34nVqxY4WqetS217tr33n777SzvV6tWLTv++OPtrbfeynGboOCo2UGOdGDSySSSvtiHHXaY+/dtt91m//73v61///62bNkyd6J97733XFOLDsatWrVy823fvt31q+jVq5cNGDDAduzYYc8++6x169bNFi1alKlpQ8vSQVW1Smqu0YFdByb1E9JBQ31VNF0HLJ0QRQexg1HAUNlvvvlmV0syYcIEd5JUPxIdAEMnfFVRX3vttW4dVTadNH/66Sf3txAdxL/55hv3q0wHZy3vgw8+sHXr1mUJB9HuvfdeFxB1stT2vf/++104+eyzz3Ks7q5Xr55rvvv73//uwkPt2rXDf9c20rbUyfDBBx90J6u8rI9Oaj179nQnafUh+O2339znoPcsqObNm7vmUJ1E9Hnp5CHqaK3y6m86YUV22tVJRx3KtZ1zqklasGCB+0zbtGmTabr2P20nnXCuv/56F5i++uort40VHHTSVPBTrdD//M//ZHqtpml/6tixY3iaQo22b1pamtu+H374oY0bN87Np22b2303ROHj9ddfdx2r9Z155JFH3Lpq/wl9t0IUQJo0aeI++yAI3DQtW2Ff63fjjTe69dLntnz58iwBTkFa86lsffv2tcmTJ7swoRO6QpZs2rTJfR7aV7TdVAYtX8FBn0P0NjoYfX7aXgpf+o4oiCgsvvPOO/b777+7IFFYcvO91rFG3yGt4w033OCCndZNYUT7QUhujlMKOk888YT73LVdLrjgAvdahRbRcaFz587uuzN8+HAXmPQDR0H5tddey7It9TmUtf6HcREAMUyZMkVH1ZiPpKSkTPMuW7YsSExMDK6++urgt99+C+rVqxe0a9cu2LdvX3ie/fv3B3v27Mn0Os1bu3bt4KqrrgpPmzNnjnuPv//971nKdODAgfC/K1euHPTt2zdX6zJ37ly3TJVr+/bt4ekvv/yym/7www+Hp+3atSvL68eOHRuUK1cuWLt2bbjcet0DDzyQ4/uecsop7hFdjubNm2faFnp/Tdd2zM16vPLKK5mmazto+vDhw7O8JjfrIy1btgyOPPLIYMeOHeFp8+bNc8tt0KBBptdr2qhRo7LsK2vWrMl23T///HM3j+aN1rFjxyAtLS3TtNdff93Nr3XOSe/evYPDDjssy/Tzzz8/OPbYY3N87YgRI9y+/Pvvv4enbd68OahQoUKm9Qtt3zvvvDPT69u0aRO0bds2z/uu5tH3ZeXKleFpX375pZv+6KOPhqepDJrWq1evTMv64osv3HR93yLddNNNbrrKEaLPTtM+/vjjTOuo9b7xxhvD02644QY333/+85/wNO0LRx99dJCamhpkZGRk+1lH7puhz2vp0qUx99XcUJljfbez+z4d7Hu9d+/eoFatWkHr1q0zfe+eeuopN1/kMnN7nNqyZUuW70HIaaed5r5Pu3fvzvT5d+rUKWjSpEmW+e+55x63rE2bNuV6GyHvaMZCjiZOnOhqLCIf7777bqZ5VBWv6lz9ItIvINUE6VdhZCfWhISEcD8OVfVrRJF+9erX7pIlS8Lz6ZePfqWpeSOaphdEnz59MnUC1K9dNT/NnDkzPC30S1DUVKZ10a9BnaOWLl0ankfromp01YDklZpyIvu0hGo69GuzICJrGPKyPuo8rFo5bR810YSo2Uw1PUVN76uaCTXDRdau6Bd3qOkuO7/88otr3omm5i/VXmmofk7vq5qHyEsSqIZJ+2Xv3r2zzB/dL0ifW+Rnlpd9VzUPkbWRqhWoVq1azH0g+n1D++vQoUMzTVcNj0Q3PalpMrSPiWommjZtmum9tEzVlIaayUT7gmri1GyjprS8CNXcqJZXtUVF6WDfazUhqsZH2zHye6faregaptwep7Kj+efMmWOXXHKJqxXS900P7ac6NqppTzVckUL7b3QNOgoXYQc50gFQB+bIR6xRL8OGDXNNVqrq1cFeB9hoCkA6qKtZQtXkOujqwBzZhq8TXt26dV31cmFTU0D0CUijyiL7H6gZQQdBvX+ob0bohBsqp/pI3HfffS70qSnp5JNPdk1R6seTG+o/E+tgl5/gFKJgqT4R0XKzPhrhld0Iu+IYdafmM21TBZxQudTcoaa93ATcUNNOJDVpaH21/+pzHzRokH3yySeZ5mnWrJlrDgy9r+jf6qsSvd6h/jfRn1vkZ5aXfTd6H4i1vJDo0ZD6vNQMGl1GNRUp5IU+z7y8l16jABSrCTL097xQmRXG9AOoZs2a7kSvH04H669TFN/rUNmj51NfMfVfys9xKjtqMtT+ePvtt7vXRT5CITg00CB6/y3ojznkjLCDQqFfifrVIqoliPbCCy+4k65+zaoNXJ0xVUv0l7/8xf2CKgnUL0MXptOBTSdLtaOrjOqDIZHlVLv/999/7/pJ6KCog5tODKHakpzo12NuT9q5pbCgE2B+1yeedOJVJ9pQ6FBNi2pcYtWuRNPJKFZA0Gfx3Xff2bRp01xthWpd9P/oWhfVCnz00UeuFkhhRX1rYr1vdp9ZfuVlH4isnYuU25NjYe5v2b1ndEdtUZ8m9ZNS52F1iFdfIPUR0rYurPcobAU9ToXmUX+86Brx0CM6pIb2X4VCFB3CDgpMX3AdIFQNrwPbv/71L9f5MpJOYPoVpelXXHGF+6WnWqLokTY6yKhZRdXBOcnPr6BQGIs82OuXWKhDsUKaAowO0goHGl2lMurXeiwqq5oOdE2Zr7/+2nXK1GtLityuT+jChLFGhB1slFhuHezzUuhQWdXspNCjDsehzrM5Ue2MThaxfnWrY6hqjaZMmeJquLp37+46s0bucxodpDCgfVbvq1/7ek1+5HbfLSh9XvrORe/P6mSszr/5udCkXqNwGE2jikJ/j6yF1PtEyq7mR82gGsTw8ccfu47aasKZNGlSjmXRe0QvP6f3ONj3OlT26Pl02QJdkDI/x6ns9udQTZH2o+ga8dAj+no6KoOCTnTNIQoXYQcFNn78eDcqRkM7NQJLfULUfySyDTr06zLy16T6aYSG94ZoRIrmCV2wK1Lka3Uii3VAzImG26odPfLA9vPPP7uRM9mVUf/WUOJI6oMQK6TpIBY99Deecrs+Cj/qd6Xt88cff4Snq8YjVi1dfoSuDZPdZ6bPQAd8NQ/qfXNTqyMaMaV1Sk9PzzRdfSQiqR+GmlY1r05yIXpPvbd+0SvsaJh4fn9h53bfLShdXkA06ij6eygKdflZppqgI7+P6uOl77RCQ6hZOtTPSOElssZF80XSqCb1dYkOPqp9PNh3RO+hGjb9eAhRs2as4eS5+V6rv42ChEJW5DJVwxm9P+b2OBUa7Rj9eg0l10i/J5980pUhWqxLLGjfjRz5h6LB0HPkSP1SQr/uIinQ6FeMhrqqCUc1O+eee274IKIhmhpWqyGXomYK/VrSsEsdjPVrRgcfHUQjT7DqD6RfVBqKq19iOvnoV6x+FepvoeHJGq6p4b86wOtkrT4CGhacE/WlUFOGOgjrV7BOFqpS1hDTUC2BDrSqgtYvUNVUqfkjuplENRC6No46Iar86i+j4b5apmoKSorcro9oWLNqfjRkVttH8zz22GMuBEV+Pvmlcqg/iT5zhUKFH31eof4o+iWsbaf31AlHQ39zQ5+nmrK0L6ipIeSMM85wfVi0PupXpf1Uy9a+F/3LWrVK6tQqCuv5ldt9t6DUN05DyBUwdLJVHywFFfU10fDm/FxJWkOkVbulgKDmJn1XtDx9T7XPhJpIVdumPk0jRoxwNViaT02F0cFGnXS1vho2f8wxx7i/65IR+mwVCnOiYfUKLNp++o6peVFhNLvLSxzse61966677nJDz7WPqOZO66Uav+g+O7k9TqlpUdPUoV3rpzLou6KH+iapPAp3KoPeQ+VSYFITXujaY6H+O2rqU58yFLF8jOBCGR96HhpCrGGa7du3d0OWI4fvRg6nnj59enjopYZYaliphr1q2O4777zjhphGD23WcjWsu1mzZm6I7uGHHx6cddZZQXp6enieFStWBCeffHJwyCGHuPfJaRh6aIjqv/71LzfcWMNQ9bru3btnGn4t3377bdC1a9egSpUqQc2aNYMBAwaEhwWHhk1v3bo1GDRokCufhsCnpKS4odMa8pqbobLRw3E1jDe7Ydm5HXqucsSSm/UJmTZtmlsnfT7HHXdc8PbbbwcXXnihm1bQoefy1ltvBS1atHBDu2O9/6JFi9z0M844I8gLDfVu3LhxpmlPPvmk2z80LF3r06hRo2DYsGHBtm3bsrxeQ40PPfRQ9zn++eefWf6e3fYNDQ3P676r12j/OdiQ69DyNcw5mi7rMHr0aDc0vGLFikH9+vXdvh053Dm0TO3n0WJ9PqtWrQouuuiioHr16kFycnLQoUMH9x2Npvm0T2m7akj2LbfcEnzwwQeZhp6vXr3aDdXWdteyatSoEXTp0iX48MMPg9wYN26cG1Ku9+jcuXOwePHibL9Pufley+OPP+62l5apS2NoOH70MvNynFqwYIG79IA+5+jvhLZRnz59gjp16rjPR+tyzjnnBK+++mqmZTzxxBNBpUqVMg2dR9Eop/8UdaACUDqFLqKmjpVFTb949X5qllANSV46x6sWS7WQqnHLK9U6qHZQNZPqlIrSQZd+UC2WLo4ZqpkrbdQ3Tc1eurEvihZ9dgC4fizRTRE6mSiAFORGh3mhqx5ruHjoirS5pWYCXR1YV6bOD41SU18KNWcBxUUjvdTcqSZBFD367ABwfXo0UkQdg1XLoX5a6qugfi9FfZNN3W5EF61THxT188jPjS51+f68UsdT9ZdQPx39wj7YBQyBwqQ+SYXRHw65Q9gB4Ib7qtO3LgKnWg4FDnXQVG1J9L2aCpvunaQOnBoRFGskU1FRQFLHVzWdha49BMBPce2zo+GLDzzwgBt6p2F6GtGi0QQ5UdW6rsypm63pcvK6hkNu7ngNAADKprj22dF1HDSMUkP1ckPDAPVrU53SdEdbXcVWwxR1/xUAAIBYSsxoLF2R8mA1O7oKrC59r6vVhujaHLrWhDp7AQAAlOo+O7ookzpRRtLlvFXDkx1drTPyip2hO9mqHwI3XgMAoHRQ3Yyulq1BFNH3AvQq7Oiu0roaaiQ916XJdaO5WDfM040ai7PTIwAAKDq6dciRRx7pb9jJD13DQB2aQ3TDwKOOOsptLF0+HwAAlHyq2NDApOhbvngXdnTNDw1RjaTnCi2xanUkKSnJPaLpNYQdAABKl/x0QSlVV1DWnWFnz56daZouY88dYwEAQIkMO7p6pIaQ6xEaWq5/r1u3LtwEFXkJd13JVffB+cc//uGu8Pr444+7u2oPGTIkbusAAABKtriGncWLF7vLtOsh6lujf48cOdI914UGQ8FHjj76aDf0XLU5uj7PuHHj3BVfNSILAACgRF9npzg7OKWkpLiOyvTZAQDA//N3qeqzAwAAkFeEHQAA4DXCDgAA8BphBwAAeI2wAwAAvEbYAQAAXiPsAAAArxF2AACA1wg7AADAa4QdAADgNcIOAADwGmEHAAB4jbADAAC8RtgBAABeI+wAAACvEXYAAIDXCDsAAMBrhB0AAOA1wg4AAPAaYQcAAHiNsAMAALxG2AEAAF4j7AAAAK8RdgAAgNcIOwAAwGuEHQAA4DXCDgAA8BphBwAAeI2wAwAAvEbYAQAAXiPsAAAArxF2AACA1wg7AADAa4QdAADgNcIOAADwGmEHAAB4jbADAAC8RtgBAABeI+wAAACvEXYAAIDXCDsAAMBrhB0AAOA1wg4AAPAaYQcAAHiNsAMAALxG2AEAAF4j7AAAAK8RdgAAgNcIOwAAwGuEHQAA4DXCDgAA8BphBwAAeI2wAwAAvEbYAQAAXiPsAAAArxF2AACA1wg7AADAa4QdAADgNcIOAADwGmEHAAB4jbADAAC8RtgBAABeI+wAAACvxT3sTJw40VJTUy05OdnS0tJs0aJFOc4/YcIEa9q0qR1yyCFWv359GzJkiO3evbvYygsAAEqXuIad6dOn29ChQ23UqFG2ZMkSa9WqlXXr1s02b94cc/6XXnrJhg8f7uZfvny5Pfvss24Zt9xyS7GXHQAAlA5xDTvjx4+3AQMGWL9+/axFixY2adIkq1Spkk2ePDnm/AsWLLDOnTvbZZdd5mqDzjjjDOvVq9dBa4MAAEDZFbews3fvXktPT7euXbv+X2HKl3fPFy5cGPM1nTp1cq8JhZvVq1fbzJkz7eyzz872ffbs2WPbt2/P9AAAAGVHhXi98datWy0jI8Nq166dabqer1ixIuZrVKOj15144okWBIHt37/fBg4cmGMz1tixY2306NGFXn4AAFA6xL2Dcl7MmzfP7rnnHnv88cddH5/XX3/dZsyYYWPGjMn2NSNGjLBt27aFH+vXry/WMgMAgDJas1OzZk1LSEiwTZs2ZZqu53Xq1In5mttvv92uuOIKu/rqq93zli1b2s6dO+2aa66xW2+91TWDRUtKSnIPAABQNsWtZicxMdHatm1rs2fPDk87cOCAe96xY8eYr9m1a1eWQKPAJGrWAgAAKDE1O6Jh53379rV27dpZhw4d3DV0VFOj0VnSp08fq1evnut3I+eee64bwdWmTRt3TZ6VK1e62h5ND4UeAACAEhN2evbsaVu2bLGRI0faxo0brXXr1jZr1qxwp+V169Zlqsm57bbbrFy5cu7/GzZssMMPP9wFnbvvvjuOawEAAEqyckEZa//R0POUlBTXWblatWrxLg4AACji83epGo0FAACQV4QdAADgNcIOAADwGmEHAAB4jbADAAC8RtgBAABeI+wAAACvEXYAAIDXCDsAAMBrhB0AAOA1wg4AAPAaYQcAAHiNsAMAALxG2AEAAF4j7AAAAK8RdgAAgNcIOwAAwGuEHQAA4DXCDgAA8BphBwAAeI2wAwAAvEbYAQAAXiPsAAAArxF2AACA1wg7AADAa4QdAADgNcIOAADwGmEHAAB4jbADAAC8RtgBAABeI+wAAACvEXYAAIDXCDsAAMBrhB0AAOA1wg4AAPAaYQcAAHiNsAMAALxG2AEAAF4j7AAAAK8RdgAAgNcIOwAAwGuEHQAA4DXCDgAA8BphBwAAeI2wAwAAvEbYAQAAXiPsAAAArxF2AACA1wg7AADAa4QdAADgNcIOAADwGmEHAAB4jbADAAC8RtgBAABeI+wAAACvEXYAAIDXCDsAAMBrhB0AAOA1wg4AAPAaYQcAAHiNsAMAALxG2AEAAF4j7AAAAK/FPexMnDjRUlNTLTk52dLS0mzRokU5zv/777/boEGD7IgjjrCkpCQ75phjbObMmcVWXgAAULpUiOebT58+3YYOHWqTJk1yQWfChAnWrVs3++6776xWrVpZ5t+7d6+dfvrp7m+vvvqq1atXz9auXWvVq1ePS/kBAEDJVy4IgiBeb66A0759e3vsscfc8wMHDlj9+vXtb3/7mw0fPjzL/ApFDzzwgK1YscIqVqyYr/fcvn27paSk2LZt26xatWoFXgcAAFD0CnL+jlszlmpp0tPTrWvXrv9XmPLl3fOFCxfGfM3bb79tHTt2dM1YtWvXtuOOO87uuecey8jIyPZ99uzZ4zZQ5AMAAJQdcQs7W7dudSFFoSWSnm/cuDHma1avXu2ar/Q69dO5/fbbbdy4cXbXXXdl+z5jx451STD0UM0RAAAoO+LeQTkv1Myl/jpPPfWUtW3b1nr27Gm33nqra97KzogRI1yVV+ixfv36Yi0zAAAoox2Ua9asaQkJCbZp06ZM0/W8Tp06MV+jEVjqq6PXhTRv3tzVBKlZLDExMctrNGJLDwAAUDbFrWZHwUS1M7Nnz85Uc6Pn6pcTS+fOnW3lypVuvpDvv//ehaBYQQcAACCuzVgadv7000/bc889Z8uXL7drr73Wdu7caf369XN/79Onj2uGCtHff/31V7v++utdyJkxY4broKwOywAAACXuOjvqc7NlyxYbOXKka4pq3bq1zZo1K9xped26dW6EVog6F7/33ns2ZMgQO/744911dhR8br755jiuBQAAKMniep2deOA6OwAAlD6l8jo7AAAAxYGwAwAAvEbYAQAAXiPsAAAArxF2AACA1/IVdnQtHAAAAG/Djq6Dc9VVV9n8+fMLv0QAAADxDjsvvPCCu5LxX/7yFzvmmGPs3nvvtf/+97+FWS4AAID4hZ0ePXrYm2++aRs2bLCBAwfaSy+9ZA0aNLBzzjnHXn/9ddu/f3/hlA4AAKCkXEH50UcftWHDhrm7j+uO5gpBw4cPt0qVKllJwhWUAQAofQpy/i7QvbE2bdrkbuI5depUW7t2rV100UXWv39/++mnn+y+++6zTz/91N5///2CvAUAAECB5CvsqKlqypQp7qacLVq0sOuuu8569+5t1atXD8/TqVMna968ecFKBwAAEI+w069fP7v00kvtk08+sfbt28ecp27dunbrrbcWtHwAAADF32dn165dJa4vTm7RZwcAgNKn2O96XrVqVdu8eXOW6b/88oslJCTkZ5EAAABFIl9hJ7vKoD179lhiYmJBywQAABCfPjuPPPKI+3+5cuXsmWeesSpVqoT/lpGRYR9//LE1a9as8EoHAABQnGHnoYceCtfsTJo0KVOTlWp0UlNT3XQAAIBSGXbWrFnj/t+lSxc3/PzQQw8tqnIBAADEb+j53LlzC+fdAQAASkrYGTp0qI0ZM8YqV67s/p2T8ePHF0bZAAAAii/sLF261Pbt2xf+d3bUeRkAAMC7G4GWFlxUEACA0qfYLyoIAADgXTPWBRdckOuFaqQWAABAqQo7qjoCAADwNuxMmTKlaEsCAABQBOizAwAAvJbrmp0TTjjBZs+e7a6a3KZNmxyHmC9ZsqSwygcAAFA8Yef888+3pKQk9+8ePXoU7F0BAACKCdfZKWSpw2cU+jJRuvx4b/d4FwEAvLOd6+wAAAAU4o1A1W8nVp8dTUtOTrbGjRvblVdeaf369cvP4gEAAOIbdkaOHGl33323nXXWWdahQwc3bdGiRTZr1iwbNGiQrVmzxq699lrbv3+/DRgwoPBKCwAAUBxhZ/78+XbXXXfZwIEDM01/8skn7f3337fXXnvNjj/+eHvkkUcIOwAAIK7y1Wfnvffes65du2aZftppp7m/ydlnn22rV68ueAkBAACKO+zUqFHD/v3vf2eZrmn6m+zcudOqVq1akLIBAADEpxnr9ttvd31y5s6dG+6z8/nnn9vMmTNt0qRJ7vkHH3xgp5xySsFLCAAAUNxhR/1wWrRoYY899lj4DudNmza1jz76yDp16uSe33jjjQUpFwAAQPzCjnTu3Nk9AAAAvAw7Ibt377a9e/dmmlYUVyYGAAAotg7Ku3btssGDB1utWrWscuXK7iKDkQ8AAIBSHXaGDRtmc+bMsSeeeMLdHPSZZ56x0aNHW926de2f//xn4ZcSAACgOJuxNMRcoebUU091t4Q46aST3C0iGjRoYC+++KJdfvnl+S0PAABA/Gt2fv31V2vYsGG4f46ey4knnmgff/xx4ZYQAACguMOOgo7ufyXNmjWzl19+OVzjU7169YKUBwAAIP5hR01XX375pfv38OHDbeLEie5u50OGDHH9eQAAAEp1nx2FmhDdI2vFihWWnp7u+u3oBqAAAACltmZn37597oafP/zwQ3iaOiZfcMEFBB0AAFD6w07FihXtq6++KprSAAAAlIQ+O71797Znn322sMsCAABQMvrs7N+/3yZPnmwffvihtW3b1l1FOdL48eMLq3wAAADFF3ZWr15tqamp9vXXX9sJJ5zgpn3//feZ5ilXrlzBSgQAABCvsNOkSRP7+eefbe7cue55z5497ZFHHrHatWsXZpkAAADi02cnCIJMz999913buXNn4ZUGAACgJHRQzi78AAAAlOqwo/440X1y6KMDAAC86bOjmpwrr7zSkpKS3PPdu3fbwIEDs4zGev311wu3lAAAAMURdvr27ZvlejsAAADehJ0pU6YUXUkAAABKWgdlAACAko6wAwAAvEbYAQAAXisRYWfixInuNhTJycmWlpZmixYtytXrpk2b5oa+9+jRo8jLCAAASqe4h53p06fb0KFDbdSoUbZkyRJr1aqVdevWzTZv3pzj63788Ue76aab7KSTTiq2sgIAgNIn7mFHd0gfMGCA9evXz1q0aGGTJk2ySpUqubuqZycjI8Muv/xyGz16tDVs2LBYywsAAEqXuIadvXv3Wnp6unXt2vX/ClS+vHu+cOHCbF935513Wq1atax///4HfY89e/bY9u3bMz0AAEDZEdews3XrVldLE33XdD3fuHFjzNfMnz/fnn32WXv66adz9R5jx461lJSU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",
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"fig, ax = plt.subplots()\n",
"\n",
"ax.bar(\n",
" np.arange(len(results_exact[\"BasinSizes\"])),\n",
" results_exact[\"BasinFragility\"],\n",
" label=\"Basin fragility\",\n",
")\n",
"ax.set_xlabel(\"Basin index\")\n",
"ax.set_ylabel(\"Fragility\")\n",
"ax.set_title(\"Exact basin fragility (synchronous update)\")\n",
"ax.set_ylim(0, 1)\n",
"\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"id": "0f90f7ad",
"metadata": {},
"source": [
"---\n",
"## 5. Approximate robustness for larger networks\n",
"\n",
"For larger networks, exact enumeration of all 2^N states is infeasible.\n",
"BoolForge therefore provides a Monte Carlo approximation that samples\n",
"random initial conditions and perturbations."
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "5565a8e8",
"metadata": {
"execution": {
"iopub.execute_input": "2026-01-15T17:24:01.701277Z",
"iopub.status.busy": "2026-01-15T17:24:01.701197Z",
"iopub.status.idle": "2026-01-15T17:24:01.711084Z",
"shell.execute_reply": "2026-01-15T17:24:01.710895Z"
}
},
"outputs": [
{
"data": {
"text/plain": [
"dict_keys(['Attractors', 'LowerBoundOfNumberOfAttractors', 'BasinSizesApproximation', 'CoherenceApproximation', 'FragilityApproximation', 'FinalHammingDistanceApproximation', 'BasinCoherenceApproximation', 'BasinFragilityApproximation', 'AttractorCoherence', 'AttractorFragility'])"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"results_approx = bn.get_attractors_and_robustness_measures_synchronous(\n",
" number_different_IC=500\n",
")\n",
"\n",
"results_approx.keys()"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "6a99dc15",
"metadata": {
"execution": {
"iopub.execute_input": "2026-01-15T17:24:01.712076Z",
"iopub.status.busy": "2026-01-15T17:24:01.712005Z",
"iopub.status.idle": "2026-01-15T17:24:01.713613Z",
"shell.execute_reply": "2026-01-15T17:24:01.713409Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Lower bound on number of attractors: 3\n",
"Approximate coherence: 0.37\n",
"Approximate fragility: 0.315\n",
"Final Hamming distance approximation: 0.315\n"
]
}
],
"source": [
"print(\"Lower bound on number of attractors:\", results_approx[\"LowerBoundOfNumberOfAttractors\"])\n",
"print(\"Approximate coherence:\", results_approx[\"CoherenceApproximation\"])\n",
"print(\"Approximate fragility:\", results_approx[\"FragilityApproximation\"])\n",
"print(\"Final Hamming distance approximation:\",\n",
" results_approx[\"FinalHammingDistanceApproximation\"])"
]
},
{
"cell_type": "markdown",
"id": "74890b67",
"metadata": {},
"source": [
"Even for this small network, the approximate values closely match the exact ones.\n",
"For larger networks, these approximations are often the only feasible option."
]
},
{
"cell_type": "markdown",
"id": "743d8c44",
"metadata": {},
"source": [
"---\n",
"## 6. Derrida value: dynamical sensitivity\n",
"\n",
"The Derrida value measures how perturbations *propagate* after one synchronous update.\n",
"It is defined as the expected Hamming distance between updated states that initially\n",
"differed in exactly one bit."
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "72d0783a",
"metadata": {
"execution": {
"iopub.execute_input": "2026-01-15T17:24:01.714589Z",
"iopub.status.busy": "2026-01-15T17:24:01.714530Z",
"iopub.status.idle": "2026-01-15T17:24:02.852991Z",
"shell.execute_reply": "2026-01-15T17:24:02.852759Z"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Exact Derrida value: 1.0\n",
"Approximate Derrida value: 0.9685\n"
]
}
],
"source": [
"derrida_exact = bn.get_derrida_value(EXACT=True)\n",
"derrida_approx = bn.get_derrida_value(nsim=2000)\n",
"\n",
"print(\"Exact Derrida value:\", derrida_exact)\n",
"print(\"Approximate Derrida value:\", derrida_approx)"
]
},
{
"cell_type": "markdown",
"id": "c5b97681",
"metadata": {},
"source": [
"Interpretation:\n",
"\n",
"- Small Derrida values indicate ordered, stable dynamics.\n",
"- Large Derrida values indicate sensitive or chaotic dynamics.\n",
"\n",
"Derrida values are closely related to average sensitivity of the update functions,\n",
"and provide a complementary notion of robustness to basin-based measures."
]
},
{
"cell_type": "markdown",
"id": "f069ee38",
"metadata": {},
"source": [
"---\n",
"## 7. Summary and outlook\n",
"\n",
"In this tutorial you learned how to:\n",
"- compute exact robustness measures for small Boolean networks,\n",
"- interpret coherence and fragility at network, basin, and attractor levels,\n",
"- approximate robustness measures for larger networks, and\n",
"- assess dynamical sensitivity using the Derrida value.\n",
"\n",
"**Next steps:**\n",
"In Tutorial #9, we will move from global robustness measures to\n",
"*trajectory-based* sensitivity analysis, including damage spreading,\n",
"Hamming distance dynamics, and time-resolved perturbation experiments."
]
}
],
"metadata": {
"jupytext": {
"cell_metadata_filter": "-all",
"main_language": "python",
"notebook_metadata_filter": "-all"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.12.3"
}
},
"nbformat": 4,
"nbformat_minor": 5
}