Search code examples
pythonboolean-logicboolean-expressiontruthtable

Truth table with Boolean Functions


I am trying to generate a Truth Table using PANDAS in python. I have been given a Boolean Network with 3 external nodes (U1,U2,U3) and 6 internal nodes (v1,v2,v3,v4,v5,v6). I have created a table with all the possible combinations of the 3 external nodes which are 2^3 = 8.

import pandas as pd
import itertools

in_comb = list(itertools.product([0,1], repeat = 3))
df = pd.DataFrame(in_comb)
df.columns = ['U1','U2','U3']
df.index += 1
U1 U2 U3
0 0 0
0 0 1
0 1 0
0 1 1
1 0 0
1 1 0
1 0 1
1 1 1

And I also have created the same table but with all the possible combinations of the 6 internal nodes which are 2^6 = 64 combinations.

The functions for each node were also given

v1(t+1) = U1(t)
v2(t+1) = v1(t) and U2(t)
v3(t+1) = v2(t) and v5(t)
v5(t+1) = not U3(t)
v6(t+1) = v5(t) or v3(t) 

The truth table has to be done with PANDAS and it has to show all the combinations with each combination possible.

For example.

v1 v2 v3 v4 v5 v6 0 0 0 0 0 1 0 1 0
0 0 0 0 0 0 000010 000000 000010
0 0 0 0 0 1
0 0 0 0 1 0
0 0 0 0 1 1

The table above is an example of how the end product should be. Where the [0 0 0] is the first combination of the external nodes.

I am confused as to how to compute the functions of each gene and how to filter the data and end up with a new table like the one here.

Here I attach an image of the problem I want to solve:

Image of problem


Solution

  • What you seem to have missed is the fact that you don't only have 3 inputs to your network, as the "old state" is also considered an input - that's what a feedback combinational network does, it turns the old state + input into new state (and often output).

    This means that you have 3+6 inputs, for 2^9=512 combinations. Not very easy to understand when printed, but still possible. I modified your code to print this (beware that I'm quite new to pandas, so this code can definitely be improved)

    import pandas as pd
    import pandas as pd
    import itertools
    
    #list of (u, v) pairs (3 and 6 elements)
    # uses bools instead of ints
    inputs = list((row[0:3],row[3:]) for row in itertools.product([False,True], repeat = 9))
    
    def new_state(u, v):
        # implement the itnernal nodes
        return (
            u[0],
            v[0] and u[1],
            v[1] and v[4],
            v[2],
            not u[2],
            v[4] or v[2]
        )
    
    new_states = list(new_state(u, v) for u,v in inputs)
    # unzip inputs to (u,v), add new_states
    raw_rows = zip(*zip(*inputs), new_states)
    
    def format_boolvec(v):
        """Format a tuple of bools like (False, False, True, False) into a string like "0010" """
        return "".join('1' if b else '0' for b in v)
    
    formatted_rows = list(map(lambda row: list(map(format_boolvec, row)), raw_rows))
    
    df = pd.DataFrame(formatted_rows)
    df.columns = ['U', "v(t)", "v(t+1)"]
    df.index += 1
    
    df
    

    The heart of it is the function new_state that takes the (u, v) pair of input & old state and produces the resulting new state. It's a direct translation of your specification.

    I modified your itertools.product line to use bools, produce length-9 results and split them to 3+6 length tuples. To still print in your format, I added the format_boolvec(v) function. Other than that, it should be very easy to follow, but fell free to comment if you need more explanation.

    To find an input sequence from a given start state to a given end state, you could do it yourself by hand, but it's tedious. I recommend using a graph algorithm, which is easy to implement since we also know the length of the desired path, so we don't need any fancy algorithms like Bellman-Ford or Dijkstra's - we need to just generate all length=3 paths and filter for the endpoint.

    # to find desired inputs
    # treat each state as a node in a graph
    # (think of visual graph transition diagrams)
    # and add edges between them labeled with the inputs
    # find all l=3 paths, and check the end nodes
    
    nodes = {format_boolvec(prod): {} for prod in itertools.product([False,True], repeat = 6)}
    
    for index, row in df.iterrows():
        nodes[row['v(t)']][row['U']] = row['v(t+1)']
    
    # we now built the graph, only need to find a path from start state to end state
    
    def prefix_paths(prefix, paths):
        # aux helper function for all_length_n_paths
        for path, endp in paths:
            yield ([prefix]+path, endp)
    
    def all_length_n_paths(graph, start_node, n):
        """Return all length n paths from a given starting point
        Yield tuples (path, endpoint) where path is a list of strings of the inputs, and endpoint is the end of the path.
        Uses internal recursion to generate paths"""
        if n == 0:
            yield ([], start_node)
            return
        for inp, nextstate in graph[start_node].items():
            yield from prefix_paths(inp, all_length_n_paths(graph, nextstate, n-1))
    
    
    # just iterate over all length=3 paths starting at 101100 and print it if it end's up at 011001
    for path, end in all_length_n_paths(nodes, "101100", 3):
        if end=="011001":
            print(path)
    
    

    This code should also be easy to follow, maybe except that iterator syntax.

    The result is not just one, but 3 different paths:

    ['100', '110', '011']
    ['101', '110', '011']
    ['111', '110', '011']