Last active
November 15, 2020 09:46
-
-
Save nitobuendia/35c2f9009c14f2c017b4690813cd7cb0 to your computer and use it in GitHub Desktop.
Random Boolean Network
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"""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