{ "cells": [ { "cell_type": "markdown", "id": "6bc6790a", "metadata": {}, "source": [ "# Random Boolean function generation\n", "\n", "This tutorial focuses on the random generation of Boolean functions with\n", "prescribed properties, enabling large-scale computational studies.\n", "\n", "Controlled random Boolean function generation enables:\n", "\n", "1. Null model comparisons: Are biological regulatory rules special?\n", "2. Ensemble studies: How do structural properties affect dynamical properties?\n", "3. Theoretical predictions: Derive expected values for function properties\n", "\n", "## What you will learn\n", "In this tutorial you will learn how to generate random Boolean functions with:\n", "\n", "- specified canalizing properties (depth, layer structure),\n", "- bias, absolute bias, or a specific Hamming weight,\n", "- linearity constraints,\n", "- degeneracy constraints.\n", "\n", "It is strongly recommended to complete the previous tutorials first.\n", "\n", "## Setup" ] }, { "cell_type": "code", "execution_count": 1, "id": "982fecf5", "metadata": { "execution": { "iopub.execute_input": "2026-03-31T14:27:03.464071Z", "iopub.status.busy": "2026-03-31T14:27:03.463988Z", "iopub.status.idle": "2026-03-31T14:27:04.353495Z", "shell.execute_reply": "2026-03-31T14:27:04.353221Z" }, "lines_to_next_cell": 2 }, "outputs": [], "source": [ "import boolforge as bf\n", "import numpy as np\n", "import matplotlib.pyplot as plt" ] }, { "cell_type": "markdown", "id": "e5831fbe", "metadata": {}, "source": [ "## Generating random Boolean functions\n", "\n", "The function `random_function(n, *args)` generates a random $n$-input\n", "Boolean function subject to optional constraints. By default, it generates a\n", "*non-degenerate* function, meaning that all variables are essential." ] }, { "cell_type": "code", "execution_count": 2, "id": "bbd5ade0", "metadata": { "execution": { "iopub.execute_input": "2026-03-31T14:27:04.354835Z", "iopub.status.busy": "2026-03-31T14:27:04.354720Z", "iopub.status.idle": "2026-03-31T14:27:04.627308Z", "shell.execute_reply": "2026-03-31T14:27:04.627067Z" }, "lines_to_next_cell": 2 }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "x0\tx1\tx2\t|\tf_random_non_degenerate\n", "-------------------------------------------------------\n", "0\t0\t0\t|\t0\n", "0\t0\t1\t|\t0\n", "0\t1\t0\t|\t1\n", "0\t1\t1\t|\t0\n", "1\t0\t0\t|\t0\n", "1\t0\t1\t|\t1\n", "1\t1\t0\t|\t1\n", "1\t1\t1\t|\t1\n", "Is f degenerate? False\n", "Activities of f: [0.5 0.5 0.5]\n", "Edge effectiveness of f: [0.625, 0.625, 0.75]\n" ] } ], "source": [ "n = 3\n", "f = bf.random_function(n)\n", "\n", "bf.display_truth_table(f, labels=\"f_random_non_degenerate\")\n", "\n", "print(\"Is f degenerate?\", f.is_degenerate())\n", "print(\"Activities of f:\", f.get_activities(exact=True))\n", "print(\"Edge effectiveness of f:\", f.get_edge_effectiveness())" ] }, { "cell_type": "markdown", "id": "25ca9ef4", "metadata": { "lines_to_next_cell": 2 }, "source": [ "The rest of this tutorial describes the various constraints. \n", "Each constraint defines a specific family of n-input Boolean functions, \n", "from which `random_function(n,*args)` samples *uniformly at random*. \n", "That is, each function satisfying a given set of constraints is selected with equal probability." ] }, { "cell_type": "markdown", "id": "fc34e260", "metadata": {}, "source": [ "## Parity functions\n", "\n", "Setting `parity=True` generates *parity* functions, also known as non-degenerate *linear* functions." ] }, { "cell_type": "code", "execution_count": 3, "id": "b6f145b7", "metadata": { "execution": { "iopub.execute_input": "2026-03-31T14:27:04.628550Z", "iopub.status.busy": "2026-03-31T14:27:04.628418Z", "iopub.status.idle": "2026-03-31T14:27:04.630850Z", "shell.execute_reply": "2026-03-31T14:27:04.630655Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "x0\tx1\tx2\t|\tf_linear\n", "----------------------------------------\n", "0\t0\t0\t|\t1\n", "0\t0\t1\t|\t0\n", "0\t1\t0\t|\t0\n", "0\t1\t1\t|\t1\n", "1\t0\t0\t|\t0\n", "1\t0\t1\t|\t1\n", "1\t1\t0\t|\t1\n", "1\t1\t1\t|\t0\n", "Activities: [1. 1. 1.]\n", "Edge effectiveness: [1.0, 1.0, 1.0]\n", "Normalized average sensitivity: 1.0\n", "Canalizing strength: 0.0\n" ] } ], "source": [ "f = bf.random_function(n, parity=True)\n", "\n", "bf.display_truth_table(f, labels=\"f_linear\")\n", "\n", "print(\"Activities:\", f.get_activities(exact=True))\n", "print(\"Edge effectiveness:\", f.get_edge_effectiveness())\n", "print(\"Normalized average sensitivity:\", f.get_average_sensitivity(exact=True))\n", "print(\"Canalizing strength:\", f.get_canalizing_strength())" ] }, { "cell_type": "markdown", "id": "813e23af", "metadata": {}, "source": [ "Parity functions are the only Boolean functions with activity 1 (for all variables),\n", "normalized average sensitivity 1 and canalizing strength 0.\n", "\n", "## Functions with prescribed canalizing properties\n", "\n", "If `parity=False` (default), canalizing properties can be specified via `layer_structure`\n", "and `depth`.\n", "\n", "### Functions with prescribed canalizing layer structure\n", "\n", "The canalizing layer structure can be specified via `layer_structure`. \n", "This vector describes the number of conditionally canalizing variables\n", "in each layer of the randomly generated function. \n", "\n", "- If the optional argument `exact_depth=True` (default is False), \n", "then `layer_structure` describes the *exact* layer structure, i.e., the core function cannot be canalizing.\n", "- If `exact_depth=False` (the default), it is possible that the core function is canalizing\n", "(meaning that the last described layer in `layer_structure` may contain more \n", "conditionally canalizing variables, or that there are additional canalizing layers). \n", "\n", "Before generating any random function, `random_function()` goes through a number of checks \n", "ensuring that the provided optional arguments make sense. \n", "For example, it checks that the provided layer structure $(k_1,\\ldots,k_r)$ satisfies\n", "\n", "- $k_i\\geq 1$, \n", "- $k_1 + \\cdots + k_r \\leq n$, and\n", "- if $k_1 + \\cdots + k_r = n$, then $k_r \\geq 2$ because the last layer of a nested canalizing function must always contain two or more variables." ] }, { "cell_type": "code", "execution_count": 4, "id": "1b487722", "metadata": { "execution": { "iopub.execute_input": "2026-03-31T14:27:04.631902Z", "iopub.status.busy": "2026-03-31T14:27:04.631830Z", "iopub.status.idle": "2026-03-31T14:27:04.634833Z", "shell.execute_reply": "2026-03-31T14:27:04.634657Z" }, "lines_to_next_cell": 2 }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "x0\tx1\tx2\t|\tf\tg\th\tk\n", "---------------------------------------------------------\n", "0\t0\t0\t|\t0\t1\t1\t0\n", "0\t0\t1\t|\t0\t0\t1\t0\n", "0\t1\t0\t|\t0\t1\t1\t1\n", "0\t1\t1\t|\t0\t1\t1\t0\n", "1\t0\t0\t|\t1\t0\t1\t0\n", "1\t0\t1\t|\t0\t1\t1\t0\n", "1\t1\t0\t|\t0\t1\t1\t1\n", "1\t1\t1\t|\t0\t1\t0\t1\n", "Canalizing depth of f: 3\n", "Layer structure of f: [3]\n", "Number of layers of f: 1\n", "Core function of f: [1]\n", "\n", "Canalizing depth of g: 1\n", "Layer structure of g: [1]\n", "Number of layers of g: 1\n", "Core function of g: [1 0 0 1]\n", "\n", "Canalizing depth of h: 3\n", "Layer structure of h: [3]\n", "Number of layers of h: 1\n", "Core function of h: [0]\n", "\n", "Canalizing depth of k: 3\n", "Layer structure of k: [1, 2]\n", "Number of layers of k: 2\n", "Core function of k: [0]\n", "\n" ] } ], "source": [ "f = bf.random_function(n, layer_structure=[1])\n", "g = bf.random_function(n, layer_structure=[1], exact_depth=True)\n", "h = bf.random_function(n, layer_structure=[3])\n", "k = bf.random_function(n, layer_structure=[1, 2])\n", "\n", "labels = [\"f\", \"g\", \"h\", \"k\"]\n", "bf.display_truth_table(f, g, h, k, labels=labels)\n", "\n", "for func, label in zip([f, g, h, k], labels):\n", " info = func.get_layer_structure()\n", " print(f\"Canalizing depth of {label}: {func.get_canalizing_depth()}\")\n", " print(f\"Layer structure of {label}: {info['LayerStructure']}\")\n", " print(f\"Number of layers of {label}: {info['NumberOfLayers']}\")\n", " print(f\"Core function of {label}: {info['CoreFunction']}\")\n", " print()" ] }, { "cell_type": "markdown", "id": "9f977bb6", "metadata": {}, "source": [ "Repeated evaluation of this block of code shows that the canalizing depth of `f` is either 1 or 3\n", "(note that a canalizing depth of $n-1$ is never possible for a non-degenerate function). \n", "On the contrary, the canalizing depth of `g` is always 1 because we set `exact_depth=True`. \n", "The 2-input core function of `g` is one of the two parity functions, each with 50% probability. \n", "Likewise, the core function for the other functions is simply [0] or [1], each with 50% probability. \n", "Functions `h` and `k` are nested canalizing, i.e., their canalizing depth is 3. \n", "Their layer structure is exactly as specified.\n", "\n", "### Functions with prescribed canalizing depth\n", "\n", "If we do not care about the specific layer structure but only about the canalizing depth, \n", "we specify the optional argument `depth` instead of `layer_structure`." ] }, { "cell_type": "code", "execution_count": 5, "id": "129ed488", "metadata": { "execution": { "iopub.execute_input": "2026-03-31T14:27:04.635843Z", "iopub.status.busy": "2026-03-31T14:27:04.635787Z", "iopub.status.idle": "2026-03-31T14:27:04.638226Z", "shell.execute_reply": "2026-03-31T14:27:04.638037Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "x0\tx1\tx2\t|\tf\tg\th\tk\n", "---------------------------------------------------------\n", "0\t0\t0\t|\t0\t0\t1\t1\n", "0\t0\t1\t|\t0\t1\t1\t1\n", "0\t1\t0\t|\t0\t0\t0\t1\n", "0\t1\t1\t|\t1\t0\t1\t0\n", "1\t0\t0\t|\t0\t1\t1\t1\n", "1\t0\t1\t|\t1\t1\t1\t1\n", "1\t1\t0\t|\t1\t1\t1\t0\n", "1\t1\t1\t|\t0\t0\t0\t0\n", "Canalizing depth of f: 0\n", "\n", "Canalizing depth of g: 0\n", "\n", "Canalizing depth of h: 1\n", "\n", "Canalizing depth of k: 3\n", "\n" ] } ], "source": [ "# any function has at least canalizing depth 0 so this is the same as bf.random_function(n)\n", "f = bf.random_function(n,depth=0)\n", "\n", "# a random non-canalizing function\n", "g = bf.random_function(n,depth=0,exact_depth=True)\n", "\n", "# a random canalizing function\n", "h = bf.random_function(n,depth=1)\n", "\n", "# a random nested canalizing function\n", "k = bf.random_function(n,depth=n)\n", "\n", "labels = [\"f\", \"g\", \"h\", \"k\"]\n", "bf.display_truth_table(f, g, h, k, labels=labels)\n", "\n", "for func, label in zip([f, g, h, k], labels):\n", " print(f\"Canalizing depth of {label}: {func.get_canalizing_depth()}\")\n", " print()" ] }, { "cell_type": "markdown", "id": "7b163b08", "metadata": {}, "source": [ "Repeated evaluation of this block of code shows that the canalizing depth of `f` can be 0, 1, or 3. \n", "Note that specifying `depth=0` without `exact_depth=True` does not restrict the space of functions at all. \n", "On the contrary, the canalizing depth of `g` is always 0 (i.e., g does not contain any canalizing variables) because we set `exact_depth=True`.\n", "Function `h` is canalizing and may be nested canalizing (because we specified that the minimal canalizing depth is 1), \n", "and `k` is always nested canalizing (i.e., it has canalizing depth $n=3$).\n", "\n", "We remember: If `exact_depth=True`, `depth` is interpreted as exact canalizing depth. \n", "Otherwise (default), `depth` is interpreted as minimal canalizing depth. \n", "For example,\n", "\n", "- `depth=1`: \"At least 1-canalizing\" (could be 2,3,...,n-canalizing)\n", "- `depth=1, exact_depth=True`: \"Exactly 1-canalizing\" (not 2,3,...,n-canalizing)" ] }, { "cell_type": "markdown", "id": "08056ef3", "metadata": {}, "source": [ "## Allowing degenerate functions\n", "\n", "It is possible that an n-input Boolean function does not depend on all its variables.\n", "For example, the function $f(x,y) = x$ depends on $x$ but not on $y$. \n", "*By default, such degenerate functions are never generated by `random_function()`*.\n", "To enable the generation of possibly degenerate functions, we set `allow_degenerate_functions=True`.\n", "Although hardly of any practical value, we can even restrict the random generation to \n", "degenerate functions only, using `bf.generate.random_degenerate_function(n,*args)`. \n", "\n", "Note: When generating random canalizing functions, the value of `allow_degenerate_functions`\n", "is ignored. The non-canalizing core function is constructed to depend on all of its variables\n", "so that the number of essential variables equals the specified value. Otherwise degeneracy would\n", "reduce the number of essential variables and confound analyses of random Boolean networks,\n", "especially when the degree is small.\n", "\n", "Since degenerate functions occur much more frequently at low degree, we set `n=2`, \n", "generate a large number of random, possibly degenerate functions and \n", "compare a histogram of the observed number of essential variables to the expected proportions." ] }, { "cell_type": "code", "execution_count": 6, "id": "53f550d1", "metadata": { "execution": { "iopub.execute_input": "2026-03-31T14:27:04.639280Z", "iopub.status.busy": "2026-03-31T14:27:04.639226Z", "iopub.status.idle": "2026-03-31T14:27:04.911860Z", "shell.execute_reply": "2026-03-31T14:27:04.911663Z" }, "lines_to_next_cell": 2 }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Error: [ 0.0008 -0.0038 0.003 ]\n" ] }, { "data": { "image/png": "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ESNGSJs2bWTWrFly7733yooVK+Sbb76R119/3eJXAgAAcgPLw02vXr3k9OnTMn78eFMU3LBhQ1m7dq2zaPj48eNmBpVDixYtZPny5fLss8/KM888IzVq1DAzperVq2fhq4DS4T9dryj9MCCQU/gOwmp8B3OHoLSszKkCAADwE5Yv4gcAAOBNhBsAAGArhBsAAGArhBsAAGArhBt4zfz58yUsLExCQkIkIiJCdu3aZXWTECC2bNkinTt3NiuX6srjme01B/iKLlfStGlTKVq0qJQpU0a6du0qhw4dsrpZAYtwA6+IiYkxaxbpFMi9e/dKeHi42dA0Pj7e6qYhAOjaWPqd04ANWGHz5s0yZMgQ2blzp1lY9sqVK9KhQwfz3UTOYyo4vEJ7avSvlldeecW50rTurzJs2DAZO3as1c1DANGemw8//ND85QxYRddv0x4cDT2tW7e2ujkBh54b3LTLly/Lnj17JDIy0nlMF17Uyzt27LC0bQBghYSEBPNvyZIlrW5KQCLc4KadOXNGUlJSnKtKO+hlXXUaAAKJ9lw/8cQT0rJlS1bPD9TtFwAAsBOtvfn+++9l69atVjclYBFucNNKly4tefPmlVOnTrkd18vlypWzrF0AkNOGDh0qq1evNjP4KlWqZHVzAhbDUrhpwcHB0rhxY1m/fr1bt6xebt68uaVtA4CcoHNzNNhoMfuGDRukatWqVjcpoNFzA6/QaeBRUVHSpEkTadasmcydO9dMgYyOjra6aQgAFy9elCNHjjgvHz16VPbv32+KOW+99VZL24bAGYpavny5fPTRR2atG0e9YbFixaRgwYJWNy/gMBUcXqPTwGfOnGn+o27YsKH85z//MVPEAV/btGmTtGvX7prjGriXLFliSZsQeEsQZOSNN96QRx99NMfbE+gINwAAwFaouQEAALZCuAEAALZCuAEAALZCuAEAALZCuAEAALZCuAEAALZCuAEAALZCuAEAALZCuAH80LFjx8yKqLrFQG7x008/ye233y4hISFmhWo7rX6s7/X58+ezfJ+2bdvKE088IVYICwsz259kla7gXLx48eveZuLEibb6TGF/hBsgG3Q5df3Bmz59utvxVatWZboMu91NmDBBChcuLIcOHXLbRNWfZBRKWrRoISdPnjR7BPmD3bt3y7/+9S+rmwFYinADZJP2UMyYMUPOnTsndnH58uVs3/eXX36RO+64Q6pUqSKlSpUSO+16X65cuVwfWh2f3S233CKFChWyujmApQg3QDZFRkaaH71p06Z51J2vQwY6dODaC9S1a1d54YUXpGzZsmaIYPLkyXL16lUZM2aM2dm6UqVKZgO+jIaCtGdBg1a9evVk8+bNbtd///330qlTJylSpIh57EceeUTOnDnj1lMxdOhQ01tRunRp6dixY4avIzU11bRJ21GgQAHzmtauXeu8Xn/49+zZY26j5/V1Z/Y4+n5VrVrV7JQcHh4uK1eudF6vQfGhhx4yP9B6fY0aNZyvW3+8ta3ly5c3r1dDlOt7r8NGAwYMMPcNDQ2VO++8Uw4cOHDNZ/HWW2+Z9197Yh588EG5cOGC83PQ92/evHnmNehJh//SD0v9+eef0rt3b6lYsaIJEfXr15d33nlHsurnn382j6efnas5c+ZItWrVzPmUlBTp37+/832qWbOmaZcrx/dm6tSpUqFCBXObjIalZs+ebdqovWqVK1eWxx9/3Oyinp72Our7re+tfg9iY2Ov+zr+93//V2rXrm1uX6tWLXn11Ved193oswJ8jXADZFPevHlNIHn55ZflxIkTN/VYGzZskD/++EO2bNlifox0iOd//ud/pESJEvL111/LoEGD5LHHHrvmeTT8jBo1Svbt2yfNmzeXzp07mx9fpT/G+gPfqFEj+eabb0wYOXXqlPTs2dPtMd58803TO7Ft2zZZuHBhhu3TH9ZZs2bJSy+9JN9++6358evSpYscPnzYXK/DNnXr1jVt0fOjR4/O8HH0B27p0qXmeQ4ePChPPvmkPPzww85Q9txzz8kPP/wgn332mfz444+yYMECE7qU7jL/8ccfy7vvvmuGvpYtW+YWEh944AGJj48399Wgddttt0n79u3l7Nmzbr1L+iO+evVqc9LndQwt6mvU93DgwIHmNehJw0B6f//9tzRu3FjWrFljwqMOAWlo3LVrV5Y+63/+85/SpEkT035XerlPnz7OEKhB8r333jPvx/jx4+WZZ54xr92VDv/pe/HFF1+Y15ORPHnymPdO32/9rPW79tRTT7nd5tKlSyYk6Wej3wP97mjwy4y2Vduk99HPSf870M9OHz8rnxXgc7orOADPREVFpd13333m/O23357Wr18/c/7DDz9Mc/3PasKECWnh4eFu950zZ05alSpV3B5LL6ek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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "n = 2\n", "n_simulations = 10000\n", "\n", "count_essential = np.zeros(n + 1, dtype=int)\n", "\n", "for _ in range(n_simulations):\n", " f = bf.random_function(n, allow_degenerate_functions=True)\n", " count_essential[f.get_number_of_essential_variables()] += 1\n", "\n", "expected = np.array([2 / 16, 4 / 16, 10 / 16])\n", "\n", "x = np.arange(n + 1)\n", "width = 0.4\n", "\n", "fig, ax = plt.subplots()\n", "ax.bar(x - width / 2, count_essential / n_simulations, width=width, label=\"observed\")\n", "ax.bar(x + width / 2, expected, width=width, label=\"expected\")\n", "ax.legend(frameon=False)\n", "ax.set_xticks(x)\n", "ax.set_xlabel(\"Number of essential variables\")\n", "ax.set_ylabel(f\"Proportion of {n}-input functions\")\n", "\n", "print(\"Error:\", count_essential / n_simulations - expected)\n", "plt.show()" ] }, { "cell_type": "markdown", "id": "9737590a", "metadata": {}, "source": [ "## Functions with prescribed Hamming weight\n", "\n", "The Hamming weight of a Boolean function is the number of ones in its truth table.\n", "BoolForge allows for the generation of random n-input functions with a specific Hamming weight $w\\in\\{0,1,\\ldots,2^n\\}$.\n", "The additional optional parameters `allow_degenerate_functions` and `exact_depth` \n", "specify whether degenerate and canalizing functions are allowed.\n", "By default, canalizing functions are allowed, while degenerate functions are not. \n", "Since all functions with Hamming weight $w\\in\\{0,1,2^n-1,2^n\\}$ are canalizing, \n", "we require $2\\leq w\\leq 2^n-2$ whenever canalizing functions are not permissible (i.e., whenever `exact_depth=True`)." ] }, { "cell_type": "code", "execution_count": 7, "id": "4cef0998", "metadata": { "execution": { "iopub.execute_input": "2026-03-31T14:27:04.912904Z", "iopub.status.busy": "2026-03-31T14:27:04.912842Z", "iopub.status.idle": "2026-03-31T14:27:04.915225Z", "shell.execute_reply": "2026-03-31T14:27:04.915026Z" }, "lines_to_next_cell": 2 }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "x0\tx1\tx2\t|\tf\tg\th\n", "-------------------------------------------------\n", "0\t0\t0\t|\t1\t0\t1\n", "0\t0\t1\t|\t1\t1\t0\n", "0\t1\t0\t|\t0\t1\t0\n", "0\t1\t1\t|\t1\t0\t0\n", "1\t0\t0\t|\t0\t1\t0\n", "1\t0\t1\t|\t1\t0\t1\n", "1\t1\t0\t|\t0\t1\t0\n", "1\t1\t1\t|\t1\t1\t0\n", "Hamming weight of f: 5\n", "Canalizing depth of f: 3\n", "Number of essential variables of f: 3\n", "\n", "Hamming weight of g: 5\n", "Canalizing depth of g: 0\n", "Number of essential variables of g: 3\n", "\n", "Hamming weight of h: 2\n", "Canalizing depth of h: 1\n", "Number of essential variables of h: 3\n", "\n" ] } ], "source": [ "n = 3\n", "\n", "f = bf.random_function(n, hamming_weight=5)\n", "g = bf.random_function(n, hamming_weight=5, exact_depth=True)\n", "h = bf.random_function(n, hamming_weight=2, allow_degenerate_functions=True)\n", "\n", "labels = [\"f\", \"g\", \"h\"]\n", "bf.display_truth_table(f, g, h, labels=labels)\n", "\n", "for func, label in zip([f, g, h], labels):\n", " print(f\"Hamming weight of {label}: {func.hamming_weight}\")\n", " print(f\"Canalizing depth of {label}: {func.get_canalizing_depth()}\")\n", " print(f\"Number of essential variables of {label}: {func.get_number_of_essential_variables()}\")\n", " print()" ] }, { "cell_type": "markdown", "id": "142d0ea2", "metadata": { "lines_to_next_cell": 2 }, "source": [ "## Biased and absolutely biased functions\n", "\n", "While specifying the Hamming weight fixes the exact number of 1s in the truth table of a generated function, \n", "specifying the bias or absolute bias acts slightly differently. \n", "The bias $p$ describes the probability of selecting a 1 at any position in the truth table and can be modified using the optional argument `bias`. \n", "Instead of specifying the bias, the absolute bias may also be specified. \n", "Unbiased functions contain an equal number of ones and zeros in their truth table \n", "and have an absolute bias of $0$, the default.\n", "If, for example, we set `absolute_bias=0.5` and specify to use absolute bias (`use_absolute_bias=True`, default is False), \n", "the bias used to generate the function is either 0.25 or 0.75, both with probability 50%. \n", "Generally, if we set `use_absolute_bias=True; absolute_bias=a` for $a\\in [0,1]$, \n", "the bias is either $(1+a)/2$ or $(1-a)/2$, both with probability 50%. \n", "\n", "To display these different modes, we repeatedly generate random Boolean functions \n", "under three different constraints (`f` with bias $p=0.75$, `g` with absolute bias 0.5, and `h` an unbiased function, i.e., with bias $p=0.5$), \n", "and compare the empirical Hamming weight distribution of the three families of functions." ] }, { "cell_type": "code", "execution_count": 8, "id": "1cb97667", "metadata": { "execution": { "iopub.execute_input": "2026-03-31T14:27:04.916257Z", "iopub.status.busy": "2026-03-31T14:27:04.916196Z", "iopub.status.idle": "2026-03-31T14:27:05.422624Z", "shell.execute_reply": "2026-03-31T14:27:05.422398Z" }, "lines_to_next_cell": 2 }, "outputs": [ { "data": { "image/png": "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", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "n = 4\n", "n_simulations = 10000\n", "\n", "counts = np.zeros((3, 2**n + 1), dtype=int)\n", "\n", "for _ in range(n_simulations):\n", " f = bf.random_function(n, bias=0.75)\n", " g = bf.random_function(n, absolute_bias=0.5, use_absolute_bias=True)\n", " h = bf.random_function(n, absolute_bias=0.5) #absolute_bias ignored!\n", "\n", " counts[0, f.hamming_weight] += 1\n", " counts[1, g.hamming_weight] += 1\n", " counts[2, h.hamming_weight] += 1\n", "\n", "labels = [\"bias = 0.75\", \"absolute bias = 0.5\", \"random (bias = 0.5)\"]\n", "x = np.arange(2**n + 1)\n", "width = 0.3\n", "\n", "fig, ax = plt.subplots()\n", "for i in range(3):\n", " ax.bar(x - width + i * width, counts[i] / n_simulations, width=width, label=labels[i])\n", "\n", "ax.legend(frameon=False)\n", "ax.set_xticks(x)\n", "ax.set_xlabel(\"Hamming weight\")\n", "ax.set_ylabel(f\"Proportion of {n}-input functions\")\n", "plt.show()" ] }, { "cell_type": "markdown", "id": "5cb91989", "metadata": { "lines_to_next_cell": 2 }, "source": [ "This plot exemplifies the difference between bias and absolute bias:\n", "\n", "- Specifying the bias shifts the mode of the Hamming weight distribution to the value of `bias`.\n", "- Specifying the absolute bias yields random functions with a bimodal Hamming weight distribution. \n", "\n", "Note that `absolute_bias=0.5` is ignored in the generation of `h`. \n", "If the value of `absolute_bias` should be used, this must be specified via `use_absolute_bias=True`.\n", "By default, the value of `bias` (default 0.5) is used.\n", "\n", "In the above plot, we notice a lack of functions with Hamming weight 0 and $16=2^n$.\n", "These constant functions are degenerate and thus not generated unless we set `allow_degenerate_functions=True`, \n", "which as we see below, slightly modifies the resulting Hamming weight distributions." ] }, { "cell_type": "code", "execution_count": 9, "id": "832188e6", "metadata": { "execution": { "iopub.execute_input": "2026-03-31T14:27:05.423648Z", "iopub.status.busy": "2026-03-31T14:27:05.423587Z", "iopub.status.idle": "2026-03-31T14:27:05.912397Z", "shell.execute_reply": "2026-03-31T14:27:05.912175Z" }, "lines_to_next_cell": 2 }, "outputs": [ { "data": { "image/png": "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C4K0SXANzKU0atTIFdD26NHDdu3aZfXq1bPevXtfcZuUdfH5fBd1O12KghZlgTRk37+p/khBnmqCQsHfpadaoUmTJrnuN41GC37PEvt+XE0XmLrW0qRJY3v37o2xX/eDs2OXolFuylpt3rz5sp8DANGIAMgDVMOjLMwLL7zgvqSVAVBGIy6LFy8O3FZRsbIgOj64W0pdTx9++KErYH7nnXfcfh2jTIkCBz9laRTkqBsmLjqXanv8jh49alu3br3oC1vZnWAqftbrKVmy5EVbXF1ZfmrfyZMnY7xWBR2qjYlN3Up634YNG2Z33HGH3XjjjXGOtorv/YivCyw4aItri68LTN13lStXdnVMfspI6X5whudS9F4qWCxQoMBlPwcAohFdYB6gwmSNytKXs774lGVQDU1888xoNJMCmpEjR7pAyV8Loy40fQmrfub06dM2ffr0QHCk0VCqv1EwoG6j/fv325NPPmkPP/xwvN1fGjGm4mVlp1Tno/MryxFMdTX6kld3muav0WvRcRrFdv3119s///lPF/QouFm7dq0NHjw43vdBmZtOnTq5QFAFzWqvap7iCpp0bgUdmmdHgYvOHbvLKKH3IxxdYCpi1/urbknN/aOASwFn8Dw/Kg4vVKiQDR061N1XndNtt93mgsPDhw+7OYSUtXrkkUeuuB0AEA3IAHmAvuCnTJnisjmq91GXjb4I46KMhzbVy/z888/21VdfBUY2KSDo27evK6itVauWC1Z0XlFtzcyZM103mwqFFZgo26RC6PjoXLVr13bBTOPGjV0NTYkSJWIc8/rrr9vs2bNdlkZdN6KuKQUbs2bNctfSF7yCtbjqeIKpPQru1PZWrVpZkyZNAkPu48rsKDjTaDbV++g9UQFxsITej3BQm9UGBV6aLkAZIxWpBweYCm6Ds2oKYDV0XoHZ3Xff7bJsCxcudK8JALwslS92EQbcl0T27NndqBnVgAAAUtYq7awG701HE/H9TQYIAAB4DgEQAADwHAIgAADgOQRAAADAcyIiANLQaw131iR21apVsyVLlsR7rCby07wsGg6tTWshxT5edd0aKaMh31qkUsdokjwAAICICICmTp3q5jfRnCxaUkDDrzXMOa5J50QLWT7wwAM2b948twikhkdrGYI///wzcMwrr7zi1m3Satma8VjLM+icWioBAAAg2YfBK+OjuVz888VodlsFNZpEL77J+mLPbKtMkJ6vSeD0cjSbrlY09y9LoOFwmitF87q0bt36onNoEjttwcPo1AaGwQNAaDEMHuGUYobBa2ZeTc6nLqpAg1KndveV3bkcWvxS60f5Z9jVUgp79uyJcU69GQq04junZs3VMf4trqURAABA9EjWAEirkiuDE3upBN1XEHM5nn32WZfx8Qc8/ucl5pyazVfRon/T6uAAACB6pei1wLQ8gZYeUF3Q1awCrjWmtAEAAG9I1gBIa0xp/aS9e/fG2K/7+fPnT/C5WhNJAdCcOXPcWkx+/ufpHMErXuu+1k8Kp3ITyllSWdNujUWy9u3bu8U3p02blizX1yKsWv/q+eefd/c1yvDpp592W3xSpUplX3zxhVuTzCuUhdW6YBqAcN111yV3cwDAG11gWkxSq2lrtW8/FUHrfvXq1eN9nkZ5aWVuLQSplbGDFStWzAVBwedUUZRGgyV0TkQPrQz/zTff2FNPPZWo52kR0UaNGlmk0ijGJ554wnLlymVZsmSx5s2bX/THQ1yBqAK74O2uu+6K8UeIBg9oFCYAeEmyD4PXEHjN7TNhwgRbv369de3a1Y4fP24dOnRwj+vDWTU6fsOHD7d+/frZe++95/6qV12PtmPHjrnH9QGvv/IHDx7sVjJfs2aNO4fqhLz0l/3lFKBHqzfffNNatGjhgoTEUOAcyV2hPXr0sK+//tqtUD9//nzbtWuXNWvW7JLPU8Cj4M6/ffTRRzEe1+/apEmT7NChQ2FsPQBElmQPgFq1auW6szRxobqoVq1a5TI7/iLm7du3uw9tv7ffftt9ef/zn/90XVz+Tefwe+aZZ9ww+kcffdQNsVdwpHNeTZ1QSlenTh3r1q2bCw71V7/mRZIRI0ZYuXLl3FxJGv32+OOPB4JJ0dQBOXLksJkzZ7ouJQUV/i9UPxWyK5DVccpO6P2PPbuCphlQRiZv3rzu36FmzZq2dOnSwOOq41LwqutUqlTJTWBZt25dNx/Ut99+666tIY1t2rRxI//io7Z8+umndu+991702N9//+3mkNJrLVSokJuAM5iuH9xlpwL7G264wTJnzmzFixd3gbdGHAZnmu68807LmjWra5uymcuWLbNwUHH+u+++6/699L7oWuPHj7eFCxfa4sWLE3yugjoFd/5N00YEK1OmjPsDQd1/AOAVyR4Aib6Yt23b5r4k1VWlIevBX4z6Evb7448/3Jdr7G3gwIExvshefPFFlxlSt4HqhPRF5nXKsqnbccGCBW6SSP+0A5o08rfffnOPf//99y6ACaaAQwHmBx98YD/++KMLSv1zLMnrr7/u/o2Ulfv5559dJiH2l6nO+dlnn7lrqN6kZMmSLgiLnXXQv6PmdNIXu0bjtWzZ0kaNGmWTJ0+2GTNm2KxZs1yGJz6//vqrCxZid43Kq6++6ibaXLlypZtjqnv37jZ79ux4z6XARq9r3bp1Nnr0aJepHDlyZODxBx980NXNKJDTdA46Z7p06eI9n7rXFEDGtykQiY/Or+AreHqHG2+80a6//vpLThmh3yEFnqVLl3YZ1oMHD150TNWqVe2nn35K8DwAEE1S9CgwJE6pUqVc/VSw4KJgdSmq67BLly7273//O7BfX7wKmEqUKBEIWBVg+ilAUTelvztGxyqT46cuTWXuFEz4a2wUTCj4UFajT58+gWN1/Ro1arjbnTp1cufdsmWLy8CIMn+aBVzZmbgokFZhvb7wY9N5/ZNrKiBWIKiA5h//+Eec53rhhRdivDcK+jTq0B8gKhBU2xWI+N/fhPz3v/+1kydPxvt4QsGTgnkFr8qyJWbKCGXr9O+i2ji9jyoK17+Bgia9T37KACkwBACvIADyEHWbxKbsmCaC3LBhgysWP3funMuaKeujrh/R//3Bj6jL0b9UibIt6g4LztqlTZvWZWD83WD64lUQ5Q9s/F/2yjqo7itY8Ig+fbn7u5+C9yW0VpwCDHX5KAsYW+wieN1X8JbQMi3Kjqn96hbUexM8s6i6/R555BGXGVNmRnVHwe9TbOp2S2rBM5+rq1Pvr9qorFC9evUCj6nLMaGuRQCINhHRBYakodqXYOpOvOeee9yXorqn1M3ir4sJLpKOnZlQcBGuFVSCr6XrxHVtjRSMj+qb9EV+tUXeypCoi+vuu++26dOnu+zIv/71rxjnVXedug4bN27sug41nDyhOpqr6QJT7Y6urakFEjtlRDAFk3qPNm/eHGO/uiLz5MljXqUpLBLaAEQfMkAepoBHwYRqeFQLJB9//HGizqGlQ5QRUu1WrVq13D5lSnTuW265xd1XxsFfe1SkSBG3Txkh1c4kNC/PlfDP9aS6ndjzPsUuFtZ9FVfHRTVIaquCnuDutdjUlaZNI7RUYK3C5Pvvvz/kXWDK3ulxTe+g4e+yceNG1w2XmOkddu7c6WqAgufIkrVr17pCeQDwCgIgD1MhsgIRFRVr1FRwcXRiqJhYk1KqBkb1MBqpFJypUOZJxbeql9GabSrcVS2SMjWq8wklZTEUeKkYO3YApNen62o6BNUfaTi5Cqvjotei4EI1PxpJqOOCszsKZPR6VJOk+hoFFgro/MFJqLvAFGjqvVK3m95DdcVppKOCn9tuuy1wnN5/dWkqCFO33aBBg1yblCVSV57ql/wF6H76d1DA+vLLL19x+wAgpSEA8tDszLFpRJSCFc2tpGJjZXD05al5kxKjV69erg6oXbt2LpPUsWNH9wWs+iA/BUjKNmmGZg1HV42QCqVjD8kOBdXlTJw40RVrx26nhqkrKFAAodceHAgEa9Kkicvq6BwanahuLg2D9482VAGxMil6r9QNpW4lFRvr3OGigm29vwpo1Ca1PbhY3Z8V8r/vaqNGxWnknQJSFTo3aNDATSIaPN/Rl19+6YLSO+64I2xtB4BIk8oXrmKOFEzFwPqLW18kwUWvSBmUndGQbxUxM/v3pSmDpDmaNMeSV12qziel/XETyYo+F3fW1e+PYY1T1HWQcr+/KYJG1NGIJmWAtM4VEqb3SJkr1S8BgJfQBYaoREHv5VHXXeyJLwHAC8gAAQAAzyEAAgAAnkMABAAAPIcACAAAeA4BEAAA8BwCIAAA4DkEQAAAwHMIgAAAgOcQAAEAAM8hAAIAAJ5DAAQAADwnJAHQ4cOHQ3EaAACAyAyAhg8fblOnTg3cb9mypeXKlcsKFSpkq1evDnX7AAAAkn81+LFjx9qkSZPc7dmzZ7vt22+/tY8//tj69Oljs2bNCn0rAQBhU/S5GQk+/sewxknWFiBiA6A9e/ZY4cKF3e3p06e7DFCDBg2saNGiVq1atXC0EQAAIHm7wHLmzGk7duxwt7/77jurX7++u+3z+ez8+fOhbR0AAEAkZICaNWtmbdq0sVKlStnBgwetUaNGbv/KlSutZMmS4WgjAABA8gZAI0eOdN1dygK98sorliVLFrd/9+7d9vjjj4e2dQAAAJEQAKVLl8569+590f4ePXqEqk0AELXKTSiX4ONr2q1JsrYAXpboAEg2bdpk8+bNs3379tmFCxdiPNa/f/9QtQ0AACAyAqBx48ZZ165dLXfu3JY/f35LlSpV4DHdJgACAABRFwANHjzYhgwZYs8++2x4WgQAdBUBiLRh8H/99Ze1aNEiPK0BAACIxABIwQ+zPQMAAE91gWmun379+tnixYutXLlyblRYsKeeeiqU7QMAAEj+AOidd95xc//Mnz/fbcFUBE0ABAAAoi4A2rp1a3haAgAAEKk1QMG0/pc2AACAqA+AJk6c6Op/MmXK5Lby5cvbBx98EPrWAQAAREIX2IgRI1wRdLdu3axGjRpu388//2xdunSxAwcOsCQGAACIvgDozTfftLffftvatm0b2NekSRMrU6aMDRw4kAAIAABEXxeYVn2//fbbL9qvfXoMAAAg6gIgzQP08ccfX7R/6tSpVqpUqVC1CwAAIHK6wAYNGmStWrWyH3/8MVADtGDBAps7d26cgREAAECKzwA1b97cfvnlF7ca/LRp09ym20uWLLH7778/PK0EAABIzgyQVK5c2T788MNQtgMAACCyAqCjR49atmzZArcT4j8OAAAgRQdAOXPmdCO88ubNazly5HBrfsWmGaG1//z58+FoJwAAQNIGQN9//71de+217va8efNCd3UAAIBIDYBq164duF2sWDErXLjwRVkgZYB27NgR+hYCAAAk9ygwBUD79++/aP+hQ4fcYwAAAFEXAPlrfWI7duyYZcyYMVTtAgAASP5h8D179nT/V/CjxVAzZ84ceEyFz5obqGLFiuFpJQAAQHIEQCtXrgxkgNasWWPp06cPPKbbFSpUsN69e4eybQAAAMkbAPlHf3Xo0MFGjx7NfD8AAMA7NUCjRo2yc+fOxVkEfalJEgEAAFJkANS6dWubMmXKRfu1EKoeAwAAiLoASMXOd95550X769Sp4x4DAACIugDo9OnTcXaBnT171k6ePBmqdgEAAEROAFS1alV75513Lto/duxYt0o8AABA1IwC8xs8eLDVr1/fVq9ebfXq1XP75s6da0uXLrVZs2aFo40AAADJmwGqUaOGLVq0yK0HpsLnr7/+2kqWLGm//vqr3XHHHaFtHQAAQCRkgEQzPk+aNCn0rQEAAIjUAOjChQu2efNm27dvn7sdrFatWqFqGwAAQGQEQIsXL7Y2bdrYtm3b3LIYwbROmNYFAwAAiKoAqEuXLlalShWbMWOGFShQIM6V4QEAAKIqANq0aZN9+umnrvAZAADAE6PAqlWr5up/AAAAPJMBevLJJ61Xr162Z88eK1eunKVL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S5cudavBz5o1y9UDXXPNNTEeZwV4AEgCA7Mn/Hix65OqJYB3V4MHAABISVgNHgASoehzMxJ8/I9hjZOsLQCSIAC6cOGCvfrqq/bVV1/ZmTNnrF69em5CRIa+AwCQNAjAkyEAGjJkiA0cONDq16/vgp7Ro0fbvn377L333gthcwAkO2pLAHjAZY8Cmzhxov373/92sz1PmzbNrQavyRCVGbpaY8aMsaJFi7rZpKtVq2ZLlixJ8PhPPvnEzUek41WI/c0338R4vH379pYqVaoYm2avBgAASFQAtH37drfqu58yQQosdu3adVXv5NSpU61nz56uO23FihVueY2GDRu67FJcFi5c6Gae7tSpk61cudLuu+8+t61duzbGcQp4du/eHdg++uijq2onAADwYACkeX+UcYk9MaJmh74aI0aMcLNKd+jQwW6++WYbO3asZc6cOd6uNXW9Kbjp06eP3XTTTfbSSy/ZLbfcYm+99VaM4zJkyGD58+cPbDlz5ryqdgIAAA/WAPl8Pte1pMDCT7NCd+nSJcZcQImZB0jF1MuXL7e+ffsG9qVOndpllxYtWhTnc7RfGaNgyhipWy7YDz/8YHnz5nWBT926dW3w4MGWK1euOM95+vRpt/kdPXr0sl8DAACI4gCoXbt2F+176KGHruriBw4csPPnz1u+fPli7Nd9/0Krse3ZsyfO47XfTxmiZs2aWbFixWzLli32/PPPW6NGjVzwlCZNmovOOXToUBs0aNBVvRYAABCFAVBKmv+ndevWgdsqki5fvryVKFHCZYU0fD82ZaCCs0rKABUuXDjJ2gsAACJ8LbBQyp07t8vI7N27N8Z+3VfdTly0PzHHS/Hixd21Nm/eHOfj6tbLli1bjA0AAESvZA2A0qdPb5UrV3Zri/lpWL3uV69ePc7naH/w8TJ79ux4j5edO3fawYMHrUCBAiFsPQAASKmSNQASdT2NGzfOJkyYYOvXr7euXbva8ePH3agwadu2bYwi6e7du9t3331nr7/+uqsT0uSMy5Yts27durnHjx075kaILV682P744w8XLDVt2tRKlizpiqUBAAASvRZYqLVq1cr2799v/fv3d4XMFStWdAGOv9BZ8w9pZJjf7bffbpMnT7YXXnjBFTeXKlXKjQArW7ase1xdar/++qsLqA4fPmwFCxa0Bg0auOHywSPYAACAdyV7ACTK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W8flrbfesrJlywbu+48fO3ZsYF/9+vXthRdeCNz/8ssv7ZZbbnHXKF68uFuV+9y5c/F2gV1um5YvX+4e13t/++23B1Zif//99901Vq9e7Z6nTfsAXIwACECinTp1yipXrmwzZsywtWvX2qOPPmoPP/ywLVmyJMZxEyZMsNy5c7v9Coa6du1qLVq0cF/aK1assAYNGrjnBQcz/m4cBRwKCHbs2GEtW7a0UaNG2eTJk901Z82aZW+++WaCbXz99dddkLBy5Up7/PHH3bX9gcLRo0ft3nvvdYGb2qHus2effTbB89WuXdvWrVtn+/fvd/fnz5/vXpuCNjl79qwtWrTIBWPy008/Wdu2ba179+7uef/5z39cMDJkyJA4z5+YNv3rX/9yr2/ZsmWWNm1aF+hJq1atrFevXlamTBnXvaZN+wDEIbmXowcQOdq1a+dLkyaN75prromxZcyY0aePi7/++ive5zZu3NjXq1evwP3atWv7atasGbh/7tw5d66HH344sG/37t3uvIsWLXL3582b5+7PmTMncMzQoUPdvi1btgT2PfbYY76GDRvGuFb37t0D94sUKeJ76KGHAvcvXLjgy5s3r+/tt9929/X/XLly+U6ePBk4Zty4ce46K1eujPP16Rx6zieffOLuV6xY0bUtf/787v7PP//sS5cune/48ePufr169Xwvv/xyjHN88MEHvgIFCgTu63pffPHFZbcprvdnxowZbp//eQMGDPBVqFAhztcA4P+QAQIQw5133um6XIK3//73vzGOOX/+vMtQKFuh+pssWbK4bqvt27fHOK58+fKB22nSpLFcuXK55/ipW0zUxRXf83SMunrUhRS8L/ZzYgs+h7qCVG/jf44yQXpcXU1+VatWTfB8OketWrVcxkfF4MrqKLN0+vRp27Bhg8sI3Xrrra6tom6oF1980b03/q1z584uKxM745XYNgW/tgIFCrj/X+r9ABATRdAAYrjmmmtcbU6wnTt3xrj/6quv2ujRo123lAIaPUc1OGfOnIlxXLp06S4KIoL36b5cuHAh3ufFfo5/X+znxHYlz7kUdW+plkjdW6pRUj2SPyhSAKRuMr9jx465epxmzZpddJ7gIOdKXM57CCBhZIAAJNqCBQusadOm9tBDD1mFChVcduZ//ud/LKUoXbq0rVmzxmVv/JYuXXrJ5/nrgD755JNArY/+P2fOHPee+PeJip+V1VEwGXtLnTp1yNoUW/r06V2GDkDCCIAAJFqpUqVs9uzZrkh5/fr19thjj9nevXstpdAoMmVMVLyt9qv77rXXXouRUYmv60kj2lSMHRwAabSWApcaNWoEju3fv79NnDjRZYF+++03d50pU6bEGCUWijbFphFwW7dudV2XBw4ciBFQAfg/BEAAEk1f4spwNGzY0AUAqq+JawLFSKWuq6+//toFCRp2rlFVClgu1T2lQOSOO+5w/69Zs2YgKNL5NOJMXYF+em+mT5/uRqypNui2226zkSNHWpEiRULaptiaN29ud911l6vlypMnz0XTEwD4X6lUCf3/bwOAZ02aNMnNT3TkyBE3/1AkiMQ2AdGCImgAnqTuKdUuaTJHjdjSnDuabyg5A41IbBMQrQiAAHjSnj17XBeT/q+h5JqgMb5JCr3cJiBa0QUGAAA8hyJoAADgOQRAAADAcwiAAACA5xAAAQAAzyEAAgAAnkMABAAAPIcACAAAeA4BEAAAMK/5f4SxyXmeNdz5AAAAAElFTkSuQmCC", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "counts[:] = 0\n", "\n", "for _ in range(n_simulations):\n", " f = bf.random_function(\n", " n, bias=0.75, allow_degenerate_functions=True\n", " )\n", " g = bf.random_function(\n", " n, absolute_bias=0.5, use_absolute_bias=True, allow_degenerate_functions=True\n", " )\n", " h = bf.random_function(\n", " n, allow_degenerate_functions=True\n", " )\n", "\n", " counts[0, f.hamming_weight] += 1\n", " counts[1, g.hamming_weight] += 1\n", " counts[2, h.hamming_weight] += 1\n", "\n", "fig, ax = plt.subplots()\n", "for i in range(3):\n", " ax.bar(x - width + i * width, counts[i] / n_simulations, width=width, label=labels[i])\n", " \n", "ax.legend(frameon=False)\n", "ax.set_xticks(x)\n", "ax.set_xlabel(\"Hamming weight\")\n", "ax.set_ylabel(f\"Proportion of {n}-input functions\")\n", "plt.show()" ] }, { "cell_type": "markdown", "id": "0f558434", "metadata": { "lines_to_next_cell": 2 }, "source": [ "## Summary\n", "\n", "This tutorial demonstrated how BoolForge enables uniform random generation of\n", "Boolean functions under flexible constraints. \n", "Different constraints define fundamentally different ensembles, and being\n", "explicit about these choices is essential for a correct generation and interpretation of\n", "computational results.\n", "\n", "Next steps: The next tutorial exemplifies how these function-level ensembles can be used\n", "to uncover new insights into biological regulatory networks, as well as the relationship between network structure and dynamics." ] }, { "cell_type": "markdown", "id": "c1e13aa0", "metadata": {}, "source": [ "## Common pitfalls\n", "\n", "- `absolute_bias` has no effect unless `use_absolute_bias=True`.\n", "- `depth=0` without `exact_depth=True` does not restrict the function space since any Boolean function is at least 0-canalizing.\n", "- Constant functions and other degenerate functions that do not depend on all inputs are only generated if `allow_degenerate_functions=True`. \n", " If `layer_structure` or `depth>0` are provided, canalizing functions are generated, which always depend on all inputs. That is, in these cases\n", " a possible parameter choice of `allow_degenerate_functions=True` is ignored.\n", "- For larger $n$ (e.g., $n>5$), set `allow_degenerate_functions=True` to avoid expensive degeneracy tests. \n", " Almost all functions in many variables are non-degenerate." ] } ], "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 }