Skip to content

Instantly share code, notes, and snippets.

@nitobuendia
Last active November 15, 2020 09:46
Show Gist options
  • Save nitobuendia/35c2f9009c14f2c017b4690813cd7cb0 to your computer and use it in GitHub Desktop.
Save nitobuendia/35c2f9009c14f2c017b4690813cd7cb0 to your computer and use it in GitHub Desktop.
Random Boolean Network
Display the source blob
Display the rendered blob
Raw
{
"nbformat": 4,
"nbformat_minor": 0,
"metadata": {
"colab": {
"name": "Random Boolean Network",
"provenance": [],
"collapsed_sections": [],
"authorship_tag": "ABX9TyPlfeCJXOY8pxGQ7El6qlZf",
"include_colab_link": true
},
"kernelspec": {
"name": "python3",
"display_name": "Python 3"
}
},
"cells": [
{
"cell_type": "markdown",
"metadata": {
"id": "view-in-github",
"colab_type": "text"
},
"source": [
"<a href=\"https://colab.research.google.com/gist/nitobuendia/35c2f9009c14f2c017b4690813cd7cb0/random-boolean-network.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>"
]
},
{
"cell_type": "code",
"metadata": {
"id": "89QLSClMdEdS"
},
"source": [
"# DEPENDENCIES\n",
"import random"
],
"execution_count": 228,
"outputs": []
},
{
"cell_type": "code",
"metadata": {
"id": "7a9mc2skcVo2"
},
"source": [
"# CONFIGURATION\n",
"\n",
"# [int] Number of total nodes to create.\n",
"NUMBER_OF_NODES = 400\n",
"\n",
"# [int] Number of total connections. Must be lesser than NUMBER_OF_NODES.\n",
"NUMBER_OF_CONNECTIONS = 5\n",
"\n",
"# [int] Number of nodes per row for displaying network.\n",
"NUMBER_OF_NODES_PER_ROW = 100\n",
"\n",
"# [set] Supported values. Recommended is: 0, 1.\n",
"ALLOWED_VALUES = frozenset([0, 1])\n",
"\n",
"# [bool] Whether to use same genome instructions.\n",
"# If true, all nodes in the network will use the same genome instructions.\n",
"USE_SAME_GENOME_INSTRUCTIONS = False\n",
"\n",
"# [int] Number of generations to iterate.\n",
"NUMBER_OF_GENERATIONS = 20\n",
"\n",
"# [bool] Whether to stop generations when a stable state is reached.\n",
"STOP_IF_STABLE_GENERATION = True"
],
"execution_count": 229,
"outputs": []
},
{
"cell_type": "code",
"metadata": {
"id": "ZvSFAgX8IxEm"
},
"source": [
"# HELPERS\n",
"\n",
"# Colors to use for each allowed value.\n",
"START_COLOR_STRING = [\n",
" '\\033[32m', # Green.\n",
" '\\033[34m', # Blue.\n",
" '\\033[33m', # Yellow.\n",
" '\\033[31m', # Red.\n",
" '\\033[35m', # Magenta.\n",
" '\\033[90m', # Dark Gray.\n",
" '\\033[30m', # Black.\n",
"]\n",
"END_COLOR_STRING = '\\033[0m'\n",
"\n",
"# Characters to use for each allowed value.\n",
"PRINT_CHARACTERS = [\n",
" ' ',\n",
" 'X'\n",
"]"
],
"execution_count": 230,
"outputs": []
},
{
"cell_type": "code",
"metadata": {
"id": "K3j1XUz8cy4U"
},
"source": [
"class BooleanNode(object):\n",
" \"\"\"Represents a node in the network.\n",
" \n",
" Attributes:\n",
" value: Value of the node.\n",
" connected_nodes: Other nodes to which this node is connected.\n",
" genome_instructions: Instructions to follow to update the node.\n",
" \n",
" Methods:\n",
" set_value: Updates value of node to a given value.\n",
" update_value: Updates value of node based on genome instructions.\n",
" set_genome_instructions: Sets a specific set of genome instructions.\n",
" generate_genome_instructions: Generates a random set of genome instructions.\n",
" \"\"\"\n",
"\n",
" def __init__(self):\n",
" \"\"\"Initializes node with a random value and no connections.\"\"\"\n",
" self.value = random.sample(ALLOWED_VALUES, 1)[0]\n",
" self.connected_nodes = set([])\n",
" self.genome_instructions = {}\n",
" \n",
" def __str__(self):\n",
" \"\"\"Returns node as a string.\"\"\"\n",
" return f'{self.value}'\n",
"\n",
" def _print_str(self):\n",
" \"\"\"Returns the node as a string for printing.\"\"\"\n",
" value = self.value\n",
" if self.value < len(PRINT_CHARACTERS):\n",
" value = PRINT_CHARACTERS[self.value]\n",
"\n",
" if self.value < len(START_COLOR_STRING):\n",
" return f'{START_COLOR_STRING[self.value]}{value}{END_COLOR_STRING}'\n",
"\n",
" return f'{value}'\n",
"\n",
" def _get_genome_string(self):\n",
" \"\"\"Returns value string of connected values.\"\"\"\n",
" genome_string = ''\n",
" for node in self.connected_nodes:\n",
" genome_string += f'{node}'\n",
" return genome_string\n",
"\n",
" def _get_combination_genome_strings(self, genome_length):\n",
" \"\"\"Returns possible genome strings for a given genome length.\"\"\"\n",
" if not genome_length:\n",
" return []\n",
"\n",
" if genome_length == 1:\n",
" return list(ALLOWED_VALUES)\n",
"\n",
" next_values = self._get_combination_genome_strings(genome_length - 1)\n",
"\n",
" possible_values = []\n",
" for value in allowed_values:\n",
" for next_value in next_values:\n",
" possible_values.append(f'{value}{next_value}')\n",
" \n",
" return possible_values\n",
"\n",
" def _get_genome_strings(self):\n",
" \"\"\"Returns list of possible genome strings given connected nodes.\"\"\"\n",
" return self._get_combination_genome_strings(len(self.connected_nodes))\n",
" \n",
" def generate_genome_instructions(self):\n",
" \"\"\"Generates a new set of genome instructions.\"\"\"\n",
" genome_strings = self._get_genome_strings()\n",
"\n",
" self.genome_instructions = {\n",
" genome_string: random.sample(ALLOWED_VALUES, 1)[0]\n",
" for genome_string in genome_strings\n",
" }\n",
"\n",
" def set_genome_instructions(self, genome_instructions):\n",
" \"\"\"Sets genome instructions of this node to given ones.\n",
" \n",
" Args:\n",
" genome_instructions: Genome instructions to set in the node.\n",
" \"\"\"\n",
" self.genome_instructions = genome_instructions\n",
"\n",
" def set_value(self, new_value):\n",
" \"\"\"Sets value of node to a given value.\n",
"\n",
" Args:\n",
" new_value: value to which set the node. Must be one of ALLOWED_VALUES.\n",
" \"\"\"\n",
" if new_value not in ALLOWED_VALUES:\n",
" raise ValueError(f'Value must be one of {ALLOWED_VALUES}.')\n",
" self.value = new_value\n",
"\n",
" def update_value(self):\n",
" \"\"\"Updates value of node based on the genome instructions.\"\"\"\n",
" genome_string = self._get_genome_string()\n",
" if genome_string in self.genome_instructions:\n",
" self.value = self.genome_instructions[genome_string]"
],
"execution_count": 231,
"outputs": []
},
{
"cell_type": "code",
"metadata": {
"id": "xcMvrq2bc_FJ"
},
"source": [
"class BooleanNetwork(object):\n",
" \"\"\"Represents a network of boolean nodes.\n",
" \n",
" Attributes:\n",
" nodes: Boolean Nodes in the network.\n",
"\n",
" Methods:\n",
" update_value: Updates value of all nodes in the network.\n",
" \"\"\"\n",
"\n",
" def __init__(self, number_of_nodes=None, number_of_connections=None):\n",
" \"\"\"Initializes network.\n",
"\n",
" Args:\n",
" number_of_nodes: Number of nodes to create in the network.\n",
" number_of_connections: Number of nodes to which to connect each node.\n",
" \n",
" Raises:\n",
" ValueError: number of connections must be lesser than number of nodes.\n",
" \"\"\"\n",
" # Using values here instead at __init__ to allow to re-run only the\n",
" # configuration and output value. Otherwise, value won't update.\n",
" # This will not support 0 values by design.\n",
" number_of_nodes = (\n",
" NUMBER_OF_NODES if number_of_nodes is None else number_of_nodes)\n",
" number_of_connections = (\n",
" NUMBER_OF_CONNECTIONS\n",
" if number_of_connections is None else number_of_connections)\n",
"\n",
" if number_of_connections >= number_of_nodes:\n",
" raise ValueError(\n",
" 'Number of connections must be lesser than number of nodes.')\n",
"\n",
" self._number_of_nodes = number_of_nodes\n",
" self._number_of_connections = number_of_connections\n",
"\n",
" self.nodes = set()\n",
" self._create_nodes()\n",
" self._connect_nodes()\n",
" self._add_genome_instructions()\n",
"\n",
" def __str__(self):\n",
" \"\"\"Returns network as a string.\"\"\"\n",
" return ''.join([str(node) for node in self.nodes])\n",
" \n",
" def _print_str(self):\n",
" \"\"\"Returns the network as a string for printing.\"\"\"\n",
" network_string = ''\n",
" node_counter = 0\n",
" for node in self.nodes:\n",
" network_string += f'{node._print_str()}'\n",
"\n",
" node_counter += 1\n",
" if node_counter % NUMBER_OF_NODES_PER_ROW == 0:\n",
" network_string += '\\n'\n",
"\n",
" return network_string\n",
"\n",
" def _create_nodes(self):\n",
" \"\"\"Creates all nodes in the network.\"\"\"\n",
" for n in range(self._number_of_nodes):\n",
" new_node = BooleanNode()\n",
" self.nodes.add(new_node)\n",
"\n",
" def _connect_nodes(self):\n",
" \"\"\"Connects nodes in the network.\"\"\"\n",
" for node in self.nodes:\n",
" nodes_copy = self.nodes.copy()\n",
" nodes_copy.remove(node) # Do not connect node to self.\n",
" connections = random.sample(nodes_copy, self._number_of_connections)\n",
" node.connected_nodes = connections\n",
" \n",
" def _add_same_genome_instructions(self):\n",
" \"\"\"Adds the same genome instructions to all the nodes.\"\"\"\n",
" genome_instructions = None\n",
" for node in self.nodes:\n",
" if genome_instructions:\n",
" node.set_genome_instructions(genome_instructions)\n",
" else:\n",
" node.generate_genome_instructions()\n",
" genome_instructions = node.genome_instructions\n",
"\n",
" def _add_genome_instructions(self):\n",
" \"\"\"Adds genome instructions to the nodes.\"\"\"\n",
" if USE_SAME_GENOME_INSTRUCTIONS:\n",
" self._add_same_genome_instructions()\n",
" return\n",
"\n",
" for node in self.nodes:\n",
" node.generate_genome_instructions()\n",
"\n",
" def update_value(self):\n",
" \"\"\"Updates value of the nodes in the network.\"\"\"\n",
" for node in self.nodes:\n",
" node.update_value()\n",
" \n",
" def simulate_generations(self, num_generations=None, stop_if_stable=None):\n",
" \"\"\"Simulates a genome network for given number of generations.\n",
"\n",
" Args:\n",
" num_generations: Number of generations to follow.\n",
" stop_if_stable: Whether to stop if reached a stable generation.\n",
" \"\"\"\n",
" num_generations = (\n",
" NUMBER_OF_GENERATIONS if num_generations is None else num_generations)\n",
" stop_if_stable = (\n",
" STOP_IF_STABLE_GENERATION if stop_if_stable is None else stop_if_stable)\n",
"\n",
" network_string = ''\n",
" for n in range(num_generations):\n",
" new_network_string = self._print_str()\n",
" is_stable = new_network_string == network_string\n",
"\n",
" stable_str = 'yes' if is_stable else 'no'\n",
" print(f'\\033[1mGENERATION {n+1} - stable? {stable_str}\\033[0m')\n",
"\n",
" print(new_network_string, '\\n')\n",
" network_string = new_network_string\n",
" self.update_value()\n",
"\n",
" if is_stable and stop_if_stable:\n",
" break\n"
],
"execution_count": 232,
"outputs": []
},
{
"cell_type": "code",
"metadata": {
"id": "bcy9Qmc7yin5"
},
"source": [
"network = BooleanNetwork()\n",
"network.simulate_generations()"
],
"execution_count": null,
"outputs": []
}
]
}
"""Random Boolean Network"""
# DEPENDENCIES
import random
# CONFIGURATION
# [int] Number of total nodes to create.
NUMBER_OF_NODES = 400
# [int] Number of total connections. Must be lesser than NUMBER_OF_NODES.
NUMBER_OF_CONNECTIONS = 5
# [int] Number of nodes per row for displaying network.
NUMBER_OF_NODES_PER_ROW = 100
# [set] Supported values. Recommended is: 0, 1.
ALLOWED_VALUES = frozenset([0, 1])
# [bool] Whether to use same genome instructions.
# If true, all nodes in the network will use the same genome instructions.
USE_SAME_GENOME_INSTRUCTIONS = False
# [int] Number of generations to iterate.
NUMBER_OF_GENERATIONS = 20
# [bool] Whether to stop generations when a stable state is reached.
STOP_IF_STABLE_GENERATION = True
# HELPERS
# Colors to use for each allowed value.
START_COLOR_STRING = [
'\033[32m', # Green.
'\033[34m', # Blue.
'\033[33m', # Yellow.
'\033[31m', # Red.
'\033[35m', # Magenta.
'\033[90m', # Dark Gray.
'\033[30m', # Black.
]
END_COLOR_STRING = '\033[0m'
# Characters to use for each allowed value.
PRINT_CHARACTERS = [
' ',
'X'
]
class BooleanNode(object):
"""Represents a node in the network.
Attributes:
value: Value of the node.
connected_nodes: Other nodes to which this node is connected.
genome_instructions: Instructions to follow to update the node.
Methods:
set_value: Updates value of node to a given value.
update_value: Updates value of node based on genome instructions.
set_genome_instructions: Sets a specific set of genome instructions.
generate_genome_instructions: Generates a random set of genome instructions.
"""
def __init__(self):
"""Initializes node with a random value and no connections."""
self.value = random.sample(ALLOWED_VALUES, 1)[0]
self.connected_nodes = set([])
self.genome_instructions = {}
def __str__(self):
"""Returns node as a string."""
return f'{self.value}'
def _print_str(self):
"""Returns the node as a string for printing."""
value = self.value
if self.value < len(PRINT_CHARACTERS):
value = PRINT_CHARACTERS[self.value]
if self.value < len(START_COLOR_STRING):
return f'{START_COLOR_STRING[self.value]}{value}{END_COLOR_STRING}'
return f'{value}'
def _get_genome_string(self):
"""Returns value string of connected values."""
genome_string = ''
for node in self.connected_nodes:
genome_string += f'{node}'
return genome_string
def _get_combination_genome_strings(self, genome_length):
"""Returns possible genome strings for a given genome length."""
if not genome_length:
return []
if genome_length == 1:
return list(ALLOWED_VALUES)
next_values = self._get_combination_genome_strings(genome_length - 1)
possible_values = []
for value in allowed_values:
for next_value in next_values:
possible_values.append(f'{value}{next_value}')
return possible_values
def _get_genome_strings(self):
"""Returns list of possible genome strings given connected nodes."""
return self._get_combination_genome_strings(len(self.connected_nodes))
def generate_genome_instructions(self):
"""Generates a new set of genome instructions."""
genome_strings = self._get_genome_strings()
self.genome_instructions = {
genome_string: random.sample(ALLOWED_VALUES, 1)[0]
for genome_string in genome_strings
}
def set_genome_instructions(self, genome_instructions):
"""Sets genome instructions of this node to given ones.
Args:
genome_instructions: Genome instructions to set in the node.
"""
self.genome_instructions = genome_instructions
def set_value(self, new_value):
"""Sets value of node to a given value.
Args:
new_value: value to which set the node. Must be one of ALLOWED_VALUES.
"""
if new_value not in ALLOWED_VALUES:
raise ValueError(f'Value must be one of {ALLOWED_VALUES}.')
self.value = new_value
def update_value(self):
"""Updates value of node based on the genome instructions."""
genome_string = self._get_genome_string()
if genome_string in self.genome_instructions:
self.value = self.genome_instructions[genome_string]
class BooleanNetwork(object):
"""Represents a network of boolean nodes.
Attributes:
nodes: Boolean Nodes in the network.
Methods:
update_value: Updates value of all nodes in the network.
"""
def __init__(self, number_of_nodes=None, number_of_connections=None):
"""Initializes network.
Args:
number_of_nodes: Number of nodes to create in the network.
number_of_connections: Number of nodes to which to connect each node.
Raises:
ValueError: number of connections must be lesser than number of nodes.
"""
# Using values here instead at __init__ to allow to re-run only the
# configuration and output value. Otherwise, value won't update.
# This will not support 0 values by design.
number_of_nodes = (
NUMBER_OF_NODES if number_of_nodes is None else number_of_nodes)
number_of_connections = (
NUMBER_OF_CONNECTIONS
if number_of_connections is None else number_of_connections)
if number_of_connections >= number_of_nodes:
raise ValueError(
'Number of connections must be lesser than number of nodes.')
self._number_of_nodes = number_of_nodes
self._number_of_connections = number_of_connections
self.nodes = set()
self._create_nodes()
self._connect_nodes()
self._add_genome_instructions()
def __str__(self):
"""Returns network as a string."""
return ''.join([str(node) for node in self.nodes])
def _print_str(self):
"""Returns the network as a string for printing."""
network_string = ''
node_counter = 0
for node in self.nodes:
network_string += f'{node._print_str()}'
node_counter += 1
if node_counter % NUMBER_OF_NODES_PER_ROW == 0:
network_string += '\n'
return network_string
def _create_nodes(self):
"""Creates all nodes in the network."""
for n in range(self._number_of_nodes):
new_node = BooleanNode()
self.nodes.add(new_node)
def _connect_nodes(self):
"""Connects nodes in the network."""
for node in self.nodes:
nodes_copy = self.nodes.copy()
nodes_copy.remove(node) # Do not connect node to self.
connections = random.sample(nodes_copy, self._number_of_connections)
node.connected_nodes = connections
def _add_same_genome_instructions(self):
"""Adds the same genome instructions to all the nodes."""
genome_instructions = None
for node in self.nodes:
if genome_instructions:
node.set_genome_instructions(genome_instructions)
else:
node.generate_genome_instructions()
genome_instructions = node.genome_instructions
def _add_genome_instructions(self):
"""Adds genome instructions to the nodes."""
if USE_SAME_GENOME_INSTRUCTIONS:
self._add_same_genome_instructions()
return
for node in self.nodes:
node.generate_genome_instructions()
def update_value(self):
"""Updates value of the nodes in the network."""
for node in self.nodes:
node.update_value()
def simulate_generations(self, num_generations=None, stop_if_stable=None):
"""Simulates a genome network for given number of generations.
Args:
num_generations: Number of generations to follow.
stop_if_stable: Whether to stop if reached a stable generation.
"""
num_generations = (
NUMBER_OF_GENERATIONS if num_generations is None else num_generations)
stop_if_stable = (
STOP_IF_STABLE_GENERATION if stop_if_stable is None else stop_if_stable)
network_string = ''
for n in range(num_generations):
new_network_string = self._print_str()
is_stable = new_network_string == network_string
stable_str = 'yes' if is_stable else 'no'
print(f'\033[1mGENERATION {n+1} - stable? {stable_str}\033[0m')
print(new_network_string, '\n')
network_string = new_network_string
self.update_value()
if is_stable and stop_if_stable:
break
network = BooleanNetwork()
network.simulate_generations()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment