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
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
"""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