Source code for cana.random_boolean_network

# -*- coding: utf-8 -*-
"""
Random Boolean Network
======================

Methods to generate random ensembles of Boolean networks.

"""
#   Copyright (C) 2021 by
#   Alex Gates <ajgates@indiana.edu>
#   Rion Brattig Correia <rionbr@gmail.com>
#   Thomas Parmer <tjparmer@indiana.edu>
#   All rights reserved.
#   MIT license.
from collections import defaultdict, Counter
import networkx as nx
import random
from cana.boolean_network import BooleanNetwork
import re
from io import StringIO
from cana.utils import output_transitions


[docs]def regular_boolean_network(N=10, K=2, bias=0.5, bias_constraint='soft', keep_constants=True, remove_multiedges=True, niter_remove=1000): """ TODO: description """ din = [K] * N # in-degree distrubtion dout = [K] * N # out-degree distrubtion regular_graph = nx.directed_configuration_model(din, dout) # the configuration graph creates a multigraph with self loops # the self loops are OK, but we should only have one copy of each edge if remove_multiedges: regular_graph = _remove_duplicate_edges(graph=regular_graph, niter_remove=niter_remove) # A dict that contains the network logic {<id>:{'name':<string>,'in':<list-input-node-id>,'out':<list-output-transitions>},..} bn_dict = { node: { 'name': str(node), 'in': sorted([n for n in regular_graph.predecessors(node)]), 'out': random_automata_table(regular_graph.in_degree(node), bias, bias_constraint) } for node in range(N) } return BooleanNetwork.from_dict(bn_dict)
[docs]def er_boolean_network(N=10, p=0.2, bias=0.5, bias_constraint='soft', remove_multiedges=True, niter_remove=1000): """ TODO: description """ er_graph = nx.erdos_renyi_graph(N, p, directed=True) # the configuration graph creates a multigraph with self loops # the self loops are OK, but we should only have one copy of each edge if remove_multiedges: er_graph = _remove_duplicate_edges(graph=er_graph, niter_remove=niter_remove) # A dict that contains the network logic {<id>:{'name':<string>,'in':<list-input-node-id>,'out':<list-output-transitions>},..} bn_dict = { node: { 'name': str(node), 'in': sorted([n for n in er_graph.predecessors(node)]), 'out': random_automata_table(er_graph.in_degree(node), bias, bias_constraint) } for node in range(N) } return BooleanNetwork.from_dict(bn_dict)
[docs]def random_automata_table(indegree, bias, bias_constraint='soft'): """ TODO: description """ if bias_constraint == 'soft': return [int(random.random() < bias) for b in range(2**indegree)] elif bias_constraint == 'hard': n_ones = int(bias * (2**indegree)) output = [0] * (2**indegree - n_ones) + [1] * n_ones random.shuffle(output) return output elif bias_constraint == 'soft_no_constant': output = [int(random.random() < bias) for b in range(2**indegree)] if sum(output) == 0: output[0] = 1 random.shuffle(output) return output
def _remove_duplicate_edges(graph, niter_remove=100): """ TODO: description """ edge_list = list(graph.edges()) edge_frequency = Counter(edge_list) duplicate_edges = [edge for edge, num_edge in edge_frequency.items() if num_edge > 1] iremove_iter = 0 while len(duplicate_edges) > 0 and iremove_iter < niter_remove: for dedge in duplicate_edges: if edge_frequency[dedge] > 1: switch_edge = random.choice(edge_list) # exchange edges graph.remove_edge(dedge[0], dedge[1]) graph.remove_edge(switch_edge[0], switch_edge[1]) graph.add_edge(dedge[0], switch_edge[1]) graph.add_edge(switch_edge[0], dedge[1]) edge_frequency[dedge] -= 1 edge_frequency[switch_edge] -= 1 edge_frequency[(dedge[0], switch_edge[1])] += 1 edge_frequency[(switch_edge[0], dedge[1])] += 1 edge_list = list(graph.edges()) duplicate_edges = [edge for edge, num_edge in edge_frequency.items() if num_edge > 1] iremove_iter += 1 if iremove_iter >= niter_remove: print("Warning: multi-edges were not successfully removed after %s iterations!!" % str(iremove_iter)) return graph
[docs]def from_string_boolean(self, string, keep_constants=True, **kwargs): """ Instanciates a Boolean Network from a Boolean update rules format. Args: string (string) : A boolean update rules format representation of a Boolean Network. Returns: (BooleanNetwork) : The boolean network object. Examples: String should be structured as follow: .. code-block:: text # BOOLEAN RULES (this is a comment) # node_name*=node_input_1 [logic operator] node_input_2 ... NODE3*=NODE1 AND NODE2 ... See also: :func:`from_string` :func:`from_dict` """ logic = defaultdict(dict) # parse lines to receive node names network_file = StringIO(string) line = network_file.readline() i = 0 while line != "": if line[0] == '#': line = network_file.readline() continue logic[i] = { 'name': line.split("*")[0].strip(), 'in': [], 'out': [] } line = network_file.readline() i += 1 # Parse lines again to determine inputs and output sequence network_file = StringIO(string) line = network_file.readline() i = 0 while line != "": if line[0] == '#': line = network_file.readline() continue eval_line = line.split("=")[1] # logical condition to evaluate # RE checks for non-alphanumeric character before/after node name (node names are included in other node names) # Additional characters added to eval_line to avoid start/end of string complications input_names = [logic[node]['name'] for node in logic if re.compile('\W' + logic[node]['name'] + '\W').search('*' + eval_line + '*')] input_nums = [node for input in input_names for node in logic if input == logic[node]['name']] logic[i]['in'] = input_nums # Determine output transitions logic[i]['out'] = output_transitions(eval_line, input_names) line = network_file.readline() i += 1 return self.from_dict(logic, keep_constants=keep_constants, **kwargs)