Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BooleanNode.from_partial_lut with bias and effective connectivity criteria #46

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
203 changes: 161 additions & 42 deletions cana/boolean_network.py

Large diffs are not rendered by default.

324 changes: 307 additions & 17 deletions cana/boolean_node.py

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion cana/canalization/boolean_canalization.py
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,7 @@ def _expand_ts_logic(two_symbols, permut_indexes):
_implicant = np.copy(implicant)
_implicant[idxs] = vals
# Insert to list of logics if not already there
if not (_implicant.tolist() in logics):
if _implicant.tolist() not in logics:
logics.append(_implicant.tolist())
Q.append(_implicant.tolist())
return logics
Expand Down
4 changes: 2 additions & 2 deletions cana/control/sc.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ def _trim_unnecessary_edges(matching_digraph):

def _enumerate_maximum_matchings_iter(G, U, V, M, D, matchings_list):
""" """
if len(G) > 0 and not (D is None):
if len(G) > 0 and D is not None:
# find the cycles in the matching digraph
cycles = [c for c in nx.simple_cycles(D)]

Expand Down Expand Up @@ -246,7 +246,7 @@ def _enumerate_maximum_matchings_iter(G, U, V, M, D, matchings_list):

elif len(G) > 0:
path = _find_path_length_two(G, V, M)
if not (path is None):
if path is not None:
# swap edges in the path
e, Mprime = _swap_edges_in_path(path, M)
# this creates a new maximum matching, see if we already have it or add it
Expand Down
1 change: 1 addition & 0 deletions cana/datasets/bio.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@


"""

# Copyright (C) 2021 by
# Alex Gates <[email protected]>
# Rion Brattig Correia <[email protected]>
Expand Down
128 changes: 128 additions & 0 deletions cana/drawing/plot_look_up_table.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import matplotlib.pyplot as plt
from matplotlib.text import Text
from matplotlib.patches import Rectangle
from matplotlib.collections import PatchCollection
from IPython.display import display


def plot_look_up_table(n):
"""
Plot the Look-Up Table of a BooleanNode

Parameters
----------
n : BooleanNode
The BooleanNode to plot the Look-Up Table

Returns
-------
None
"""
# Check if n.inputs has any values
if not n.inputs:
return print('No inputs to plot')

# Init values from BooleanNode
k = n.k if n.k>=1 else 1
inputs = n.inputs if not n.constant else [n.name]
inputlabels = [n.network.get_node_name(i)[0] if n.network is not None else i for i in inputs]
LUT = n.look_up_table().sort_index(ascending=False)
# Count number of F in the LUT
n_fs = LUT.shape[0]
# Schemata Cell Width and spacing
cwidth = 60.
cxspace = 0
cyspace = 6
border = 1
sepcxspace = 21
# sepcyspace = 15
dpi = 150.
# Margins
top, right, bottom, left, hs = 120, 25, 25, 60, 25
# Axes Width & Height
ax1width = ((k*(cwidth+cxspace))+sepcxspace+(cwidth))
ax1height = (n_fs*(cwidth+cyspace)-cyspace)
# Figure Width & Height
fwidth = (left + ax1width + hs + right)
fheight = (bottom + ax1height + top)
# Percentages for Axes location
_ax1w = ((ax1width*100) / fwidth) / 100
_ax1h = ((ax1height*100) / fheight) / 100
_bottom = ((bottom*100) / fheight) / 100
_left = ((left*100) / fwidth) / 100
_hs = ((hs*100) / fwidth) / 100
# Init Figure
fig = plt.figure(figsize=(fwidth/dpi,fheight/dpi), facecolor='w', dpi=dpi)
ax1 = fig.add_axes((_left,_bottom,_ax1w,_ax1h), aspect=1, label='LUT')

### LUT Plot ###

yticks = []
patches = []
x,y = 0.,0.
#
for i,r in LUT.iterrows():
ins = str(r['In:'])
out = r['Out:']
x = 0.
xticks = []
for input in ins:
if input == '0':
facecolor = 'white'
textcolor = 'black'
elif input == '1':
facecolor = 'black'
textcolor = 'white'
text = '{label:s}'.format(label=input)
ax1.add_artist(Text(x+cwidth/2,y+cwidth/10*4, text=text, color=textcolor, va='center', ha='center',fontsize=14,family='serif'))
r = Rectangle((x,y), width=cwidth, height=cwidth, facecolor=facecolor, edgecolor='black')
patches.append(r)
xticks.append(x+cwidth/2)
x += cwidth + cxspace

x += sepcxspace
r = Rectangle((x,y), width=cwidth, height=cwidth, facecolor='black' if (out==1) else 'white', edgecolor='black')
ax1.add_artist(Text(x-(sepcxspace/2)-(cxspace/2),y+cwidth/10*4, text=':', color='black', va='center', ha='center',fontsize=14,weight='bold',family='serif'))
ax1.add_artist(Text(x+(cwidth/2),y+cwidth/10*4, text=out, color='white' if (out==1) else 'black', va='center', ha='center',fontsize=14,family='serif'))
patches.append(r)
xticks.append(x+cwidth/2)
yticks.append(y+cwidth/2)
y += cwidth + cyspace

#y += sepcyspace

ax1.add_collection(PatchCollection(patches, match_original=True))
#
ax1.set_yticks(yticks)
ax1.set_yticklabels([r"$f_{%d}$"%(i+1) for i in range(n_fs)[::-1]], fontsize=14)
ax1.set_xticks(xticks)
ax1.set_xticklabels(inputlabels + ['%s'%(n.name)], rotation=90, fontsize=14)
#
ax1.xaxis.tick_top()
# Remove Tick
ax1.tick_params(which='major',pad=7)
for tic in ax1.xaxis.get_major_ticks():
tic.tick1On = tic.tick2On = False
for tic in ax1.yaxis.get_major_ticks():
tic.tick1On = tic.tick2On = False
# Remove Border
ax1.spines['top'].set_visible(False)
ax1.spines['right'].set_visible(False)
ax1.spines['bottom'].set_visible(False)
ax1.spines['left'].set_visible(False)
# Limits
ax1.set_xlim(-border,ax1width+border)
ax1.set_ylim(-border,ax1height+border)
#ax1.invert_yaxis()

# FileName
filename = n.name
filename = filename.replace('/','_')
filename = filename.replace(',','_')

## Display
display(fig)

plt.close()


3 changes: 3 additions & 0 deletions cana/drawing/schema_vis.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ def plot_schemata(n, plotTS=True):
# Init values from BooleanNode
k = n.k if n.k >= 1 else 1
outputs = np.array(n.outputs)

if "?" in outputs: # check if there are any '?' in the output.
raise ValueError("Error (plot_schemata()): The output contains '?'")
if np.all(outputs[0] == outputs):
return False
inputs = n.inputs if not n.constant else [n.name]
Expand Down
65 changes: 61 additions & 4 deletions cana/random_boolean_network.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,25 @@ def regular_boolean_network(
niter_remove=1000,
):
"""
TODO: description
Generate a random regular boolean network.

Args:
N (int) : Number of nodes in the network.
K (int) : Degree of each node in the network.
bias (float) : Bias for the output transitions.
bias_constraint (str) : Constraint for the bias. Options are 'soft', 'hard', 'soft_no_constant'.
keep_constants (bool) : Keep constant nodes.
remove_multiedges (bool) : Remove multi-edges.
niter_remove (int) : Number of iterations to try to remove duplicate edges.

Returns:
(BooleanNetwork) : The boolean network object.

Examples:
A regular boolean network with 10 nodes, each with 2 inputs and a bias of 0.5.

>>> bn = regular_boolean_network(N=10, K=2, bias=0.5)

"""
din = [K] * N # in-degree distrubtion
dout = [K] * N # out-degree distrubtion
Expand Down Expand Up @@ -72,7 +90,24 @@ def er_boolean_network(
niter_remove=1000,
):
"""
TODO: description
Generate a random Erdos-Renyi boolean network.

Args:
N (int) : Number of nodes in the network.
p (float) : Probability for edge creation.
bias (float) : Bias for the output transitions.
bias_constraint (str) : Constraint for the bias. Options are 'soft', 'hard', 'soft_no_constant'.
remove_multiedges (bool) : Remove multi-edges.
niter_remove (int) : Number of iterations to try to remove duplicate edges.

Returns:
(BooleanNetwork) : The boolean network object.

Examples:
A random Erdos-Renyi boolean network with 10 nodes and a probability of 0.2.

>>> bn = er_boolean_network(N=10, p=0.2)

"""
er_graph = nx.erdos_renyi_graph(N, p, directed=True)

Expand All @@ -98,7 +133,21 @@ def er_boolean_network(

def random_automata_table(indegree, bias, bias_constraint="soft"):
"""
TODO: description
Generate a random automata table.

Args:
indegree (int) : Number of inputs.
bias (float) : Bias for the output transitions.
bias_constraint (str) : Constraint for the bias. Options are 'soft', 'hard', 'soft_no_constant'.

Returns:
(list) : A list of output transitions.

Examples:
A random automata table with 2 inputs and a bias of 0.5.

>>> random_automata_table(indegree=2, bias=0.5)

"""
if bias_constraint == "soft":
return [int(random.random() < bias) for b in range(2**indegree)]
Expand All @@ -119,7 +168,15 @@ def random_automata_table(indegree, bias, bias_constraint="soft"):

def _remove_duplicate_edges(graph, niter_remove=100):
"""
TODO: description
Remove duplicate edges from a graph.

Args:
graph (nx.DiGraph) : A directed graph.
niter_remove (int) : Number of iterations to try to remove duplicate edges.

Returns:
(nx.DiGraph) : A directed graph without duplicate edges.

"""
edge_list = list(graph.edges())
edge_frequency = Counter(edge_list)
Expand Down
81 changes: 81 additions & 0 deletions cana/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -327,3 +327,84 @@ def input_monotone(outputs, input_idx, activation=1):
)

return all(monotone_configs)


def fill_out_lut(partial_lut, verbose=False):
"""
Fill out a partial LUT with missing entries.

Args:
partial_lut (list) : A list of tuples where each tuple is a pair of a binary string and a value.
fill_missing_output (bool) : If True, missing output values are filled with random 0 or 1. If False, missing output values are filled with '?'.




Returns:
(list) : A list of tuples where each tuple is a pair of a binary string and a value.

Example:
>>> fill_out_lut([('00', 0), ('01', 0), ('1-', 1), ('11', 1)])
[('00', 0), ('01', 0), ('10', 1), ('11', 1)]

# TODO: [SRI] generate LUT from two symbol schemata, with a specified ratio of wildcard symbols
# TODO: [SRI] use examples COSA rule, GKL rule where you fill up LUT based on the annihilation inputs and see if it matches with the rules plus bias.
"""

# Check if all the input entries of the partial LUT are of the same length.
if len(set([len(x[0]) for x in partial_lut])) != 1:
raise ValueError(
"All the input entries of the partial LUT must be of the same length."
)

k = len(partial_lut[0][0])

all_states = dict(partial_lut)

for entry in partial_lut:
if not all([x in ["0", "1", "-", "#", "2", "x"] for x in entry[0]]):
raise ValueError(
"All the input entries of the partial LUT must be valid binary strings."
)

elif any([x in ["-", "#", "2", "x"] for x in entry[0]]):
missing_data_indices = [
i for i, x in enumerate(entry[0]) if x in ["-", "#", "x"]
]
table = []
output_list_permutations = []

for i in range(2 ** len(missing_data_indices)):
row = [int(x) for x in bin(i)[2:].zfill(len(missing_data_indices))]
table.append(row)
output_list_permutations.append(entry[0])
for j in range(len(missing_data_indices)):
output_list_permutations[i] = (
output_list_permutations[i][: missing_data_indices[j]]
+ str(table[i][j])
+ output_list_permutations[i][missing_data_indices[j] + 1 :]
)
del all_states[entry[0]]

for perm in output_list_permutations:
if perm in all_states and all_states[perm] != entry[1]:
print("Clashing output values for entry:", perm)
all_states[perm] = "!"
else:
all_states[perm] = entry[1]

for i in range(2**k):
state = bin(i)[2:].zfill(k)
if state not in all_states:
all_states[state] = "?"

if verbose:
# Print a statement if there are any missing values '?' in the LUT. Else print a statement that the LUT is complete.
if "?" in all_states.values():
print("The LUT is incomplete. Missing values are represented by '?'")
else:
print("The LUT is complete.")

all_states = sorted(all_states.items(), key=lambda x: x[0])

return all_states
Loading
Loading