{ "cells": [ { "cell_type": "markdown", "id": "b1bca587", "metadata": {}, "source": [ "# 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", "## What you will learn\n", "You will learn how to:\n", "\n", "- quantify robustness and fragility of Boolean networks under synchronous update,\n", "- interpret basin-level and attractor-level robustness measures,\n", "- perform exact and approximate robustness computations, and\n", "- compute Derrida values as a measure of dynamical sensitivity.\n", "\n", "Together, these tools allow us to assess dynamical stability and resilience of \n", "Boolean network models in a principled and computationally efficient way.\n", "\n", "## Setup" ] }, { "cell_type": "code", "execution_count": 1, "id": "607ec558", "metadata": { "execution": { "iopub.execute_input": "2026-03-14T21:20:27.166807Z", "iopub.status.busy": "2026-03-14T21:20:27.166474Z", "iopub.status.idle": "2026-03-14T21:20:27.918386Z", "shell.execute_reply": "2026-03-14T21:20:27.918051Z" } }, "outputs": [], "source": [ "import boolforge as bf\n", "import pandas as pd" ] }, { "cell_type": "markdown", "id": "34ceb172", "metadata": {}, "source": [ "We reuse the small Boolean network from the previous tutorial as a running example." ] }, { "cell_type": "code", "execution_count": 2, "id": "d4c3d467", "metadata": { "execution": { "iopub.execute_input": "2026-03-14T21:20:27.920419Z", "iopub.status.busy": "2026-03-14T21:20:27.920221Z", "iopub.status.idle": "2026-03-14T21:20:27.922876Z", "shell.execute_reply": "2026-03-14T21:20:27.922601Z" } }, "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 = bf.BooleanNetwork.from_string(string, separator=\"=\")\n", "\n", "print(\"Variables:\", bn.variables)\n", "print(\"Number of nodes:\", bn.N)" ] }, { "cell_type": "markdown", "id": "52e7e926", "metadata": {}, "source": [ "## Exact attractors and robustness measures\n", "\n", "BoolForge provides a single method that computes:\n", "\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": "ec7d1a03", "metadata": { "execution": { "iopub.execute_input": "2026-03-14T21:20:27.924231Z", "iopub.status.busy": "2026-03-14T21:20:27.924135Z", "iopub.status.idle": "2026-03-14T21:20:28.933585Z", "shell.execute_reply": "2026-03-14T21:20:28.933253Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Attractors\n", "NumberOfAttractors\n", "BasinSizes\n", "AttractorID\n", "Coherence\n", "Fragility\n", "BasinCoherences\n", "BasinFragilities\n", "AttractorCoherences\n", "AttractorFragilities\n" ] } ], "source": [ "results_exact = bn.get_attractors_and_robustness_synchronous_exact()\n", "for key in results_exact.keys():\n", " print(key)" ] }, { "cell_type": "markdown", "id": "e6b63411", "metadata": {}, "source": [ "For convenience, information about the dynamics (attractors, basin sizes, etc),\n", "described in detail in the previous tutorial, is also returned by this method." ] }, { "cell_type": "code", "execution_count": 4, "id": "aef8278c", "metadata": { "execution": { "iopub.execute_input": "2026-03-14T21:20:28.935299Z", "iopub.status.busy": "2026-03-14T21:20:28.935067Z", "iopub.status.idle": "2026-03-14T21:20:28.937331Z", "shell.execute_reply": "2026-03-14T21:20:28.937108Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Number of attractors: 3\n", "Attractors (decimal states): [[0], [2, 5], [7]]\n", "Eventual attractor of each state: [0 1 1 2 1 1 2 2]\n", "Basin sizes: [0.125 0.5 0.375]\n" ] } ], "source": [ "print(\"Number of attractors:\", results_exact[\"NumberOfAttractors\"])\n", "print(\"Attractors (decimal states):\", results_exact[\"Attractors\"])\n", "print(\"Eventual attractor of each state:\", results_exact[\"AttractorID\"])\n", "\n", "print(\"Basin sizes:\", results_exact[\"BasinSizes\"])" ] }, { "cell_type": "markdown", "id": "0176d717", "metadata": {}, "source": [ "## Network-, basin- and attractor-level robustness\n", "\n", "Robustness can be resolved at different structural levels. Network-level metrics\n", "report the average robustness of any network state when subjected to perturbation. " ] }, { "cell_type": "code", "execution_count": 5, "id": "1614950d", "metadata": { "execution": { "iopub.execute_input": "2026-03-14T21:20:28.938766Z", "iopub.status.busy": "2026-03-14T21:20:28.938667Z", "iopub.status.idle": "2026-03-14T21:20:28.940387Z", "shell.execute_reply": "2026-03-14T21:20:28.940156Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Overall coherence: 0.3333333333333333\n", "Overall fragility: 0.3333333333333333\n" ] } ], "source": [ "print(\"Overall coherence:\", results_exact[\"Coherence\"])\n", "print(\"Overall fragility:\", results_exact[\"Fragility\"])" ] }, { "cell_type": "markdown", "id": "9de16eb6", "metadata": { "lines_to_next_cell": 2 }, "source": [ "The same robustness metrics, coherence and fragility, can also be averaged\n", "across a smaller set of states, e.g., all states in one basin of attraction, or\n", "an even smaller set of states, e.g., all states that form an attractor." ] }, { "cell_type": "code", "execution_count": 6, "id": "9d5f06b1", "metadata": { "execution": { "iopub.execute_input": "2026-03-14T21:20:28.941676Z", "iopub.status.busy": "2026-03-14T21:20:28.941597Z", "iopub.status.idle": "2026-03-14T21:20:28.946350Z", "shell.execute_reply": "2026-03-14T21:20:28.946128Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Basin-level robustness:\n", " BasinSizes BasinCoherences BasinFragilities\n", "0 0.125 0.000000 0.500000\n", "1 0.500 0.333333 0.333333\n", "2 0.375 0.444444 0.277778\n", "Attractor-level robustness:\n", " AttractorCoherences AttractorFragilities\n", "0 0.000000 0.500000\n", "1 0.333333 0.333333\n", "2 0.666667 0.166667\n" ] } ], "source": [ "df_basins = pd.DataFrame({\n", " \"BasinSizes\": results_exact[\"BasinSizes\"],\n", " \"BasinCoherences\": results_exact[\"BasinCoherences\"],\n", " \"BasinFragilities\": results_exact[\"BasinFragilities\"],\n", "})\n", "\n", "df_attractors = pd.DataFrame({\n", " \"AttractorCoherences\": results_exact[\"AttractorCoherences\"],\n", " \"AttractorFragilities\": results_exact[\"AttractorFragilities\"],\n", "})\n", "\n", "print(\"Basin-level robustness:\")\n", "print(df_basins)\n", "\n", "print(\"Attractor-level robustness:\")\n", "print(df_attractors)" ] }, { "cell_type": "markdown", "id": "7f738549", "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.\n", "\n", "The robustness metrics considered thus far describe how a single perturbation affects\n", "the network dynamics in the long-term, i.e., at the attractor. \n", "These metrics are very meaningful biologically because attractors typically \n", "correspond to cell types of phenotypes.\n", "\n", "It turns out that attractors in biological networks are often less stable \n", "than their basins, a phenomenon explored in detail in Tutorial 10.\n", "\n", "\n", "## 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": "a959b806", "metadata": { "execution": { "iopub.execute_input": "2026-03-14T21:20:28.947562Z", "iopub.status.busy": "2026-03-14T21:20:28.947478Z", "iopub.status.idle": "2026-03-14T21:20:28.959054Z", "shell.execute_reply": "2026-03-14T21:20:28.958813Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Number of attractors (lower bound): 3\n", "Approximate coherence: 0.352\n", "Approximate fragility: 0.324\n" ] } ], "source": [ "results_approx = bn.get_attractors_and_robustness_synchronous(n_simulations=500)\n", "\n", "print(\"Number of attractors (lower bound):\", results_approx[\"NumberOfAttractorsLowerBound\"])\n", "print(\"Approximate coherence:\", results_approx[\"CoherenceApproximation\"])\n", "print(\"Approximate fragility:\", results_approx[\"FragilityApproximation\"])" ] }, { "cell_type": "markdown", "id": "f6a6ca54", "metadata": {}, "source": [ "Even when only using 500 random initial states, the approximate values closely match the exact ones.\n", "For larger networks, these approximations are often the only feasible option." ] }, { "cell_type": "markdown", "id": "41926b6e", "metadata": {}, "source": [ "## Derrida value: dynamical sensitivity\n", "\n", "An older and very popular robustness metric, the Derrida value, \n", "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. \n", "\n", "BoolForge includes routines for the exact calculation and estimation of Derrida values.\n", "For networks with low degree, the exact calculation is strongly preferable. It is faster and more accurate." ] }, { "cell_type": "code", "execution_count": 8, "id": "d0e120d1", "metadata": { "execution": { "iopub.execute_input": "2026-03-14T21:20:28.960328Z", "iopub.status.busy": "2026-03-14T21:20:28.960244Z", "iopub.status.idle": "2026-03-14T21:20:28.962253Z", "shell.execute_reply": "2026-03-14T21:20:28.962036Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Exact Derrida value: 1.0\n", "Approximate Derrida value: 1.0\n" ] } ], "source": [ "derrida_exact = bn.get_derrida_value(exact=True)\n", "derrida_approx = bn.get_derrida_value(n_simulations=2000)\n", "\n", "print(\"Exact Derrida value:\", derrida_exact)\n", "print(\"Approximate Derrida value:\", derrida_approx)" ] }, { "cell_type": "markdown", "id": "fccdf604", "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." ] }, { "cell_type": "markdown", "id": "1597b46a", "metadata": {}, "source": [ "## Summary\n", "\n", "In this tutorial you learned how to:\n", "\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", "In Tutorial 9, we will finally analyze biological Boolean network models and\n", "design ensemble 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 }