Dragaming
Dragaming

Reputation: 41

Creating DOT edges in pygraphviz from a binary tree

I'm creating a symbolic regression genetic algorithm, and having trouble with converting this to DOT-compliant format. Each solution is represented as a binary tree structure with the following class:

class Node:
    def __init__(self,val):
        self.left = None
        self.right = None
        self.data = val

At each node, there is either an operation ('mul' for multiply, 'add', 'sub', etc.) or a value (either a static number or the form of 'val' which is replaced at evaluation, basically like an 'x' or 'y').

I am attempting to visualise the solutions after they have been generated. My current code can generate a linear format of the tree, but it is rather difficult to read. My DOT code generates each of the nodes with a unique ID, but I cannot work out how to link each of the nodes recursively.

Code

def transcribe(root,DOT=False):
    temp = None
    if (DOT):
        temp = Graph(comment='Solution',format='svg')
        DOTForm(root,temp,0)
        return temp
    else:
        temp = []
        programForm(root,temp)
        return temp

def programForm (root,array):
    if root:
        array.append(root.data)
        programForm(root.left,array)
        programForm(root.right,array)

def DOTForm (root,dot,increment):
    if root:
        dot.node(str(increment),str(root.data))
        increment += 1
        increment = DOTForm(root.left,dot,increment)
        increment = DOTForm(root.right,dot,increment)
    return increment

Output:

>>> print(transcribe(root)) # This works fine
['mul', 'mul', 5, 2, ['ifn', 'val'], 'mul', -10, 'val', 'mul', 10, 'val']

>>> print(transcribe(root,True)) # Nodes work okay
// Solution
graph {
    0 [label=mul]
    1 [label=mul]
    2 [label=5]
    3 [label=2]
    4 [label="['ifn', 'val']"]
    5 [label=mul]
    6 [label=-10]
    7 [label=val]
    8 [label=mul]
    9 [label=10]
    10 [label=val]
}

Expected output

// Solution
graph {
    0 [label=mul]
    0 -- 1
    0 -- 4
    1 [label=mul]
    1 -- 2
    2 [label=5]
    1 -- 3
    3 [label=2]
    4 [label="['ifn', 'val']"]
    4 -- 5
    4 -- 8
    5 [label=mul]
    5 -- 6
    6 [label=-10]
    5 -- 7
    7 [label=val]
    8 [label=mul]
    8 -- 9
    9 [label=10]
    8 -- 10
    10 [label=val]
}

I'm honestly not sure how to go about this. I've tried several combinations of the above recursion to try and add working edges, but these have all come up short. For example:

Code

def DOTForm (root,dot,increment):
    if root:
        dot.node(str(increment),str(root.data))
        parent = increment
        increment += 1
        increment = DOTForm(root.left,dot,increment)
        dot.edge(str(parent),str(increment))
        increment = DOTForm(root.right,dot,increment)
        dot.edge(str(parent),str(increment))
    return increment

Output

// Solution
graph {
    0 [label=mul]
    1 [label=mul]
    2 [label=5]
    2 -- 3
    2 -- 3
    1 -- 3
    3 [label=2]
    3 -- 4
    3 -- 4
    1 -- 4
    0 -- 4
    4 [label="['ifn', 'val']"]
    5 [label=mul]
    6 [label=-10]
    6 -- 7
    6 -- 7
    5 -- 7
    7 [label=val]
    7 -- 8
    7 -- 8
    5 -- 8
    4 -- 8
    8 [label=mul]
    9 [label=10]
    9 -- 10
    9 -- 10
    8 -- 10
    10 [label=val]
    10 -- 11
    10 -- 11
    8 -- 11
    4 -- 11
    0 -- 11
}

I'm new to recursion, so I have no idea how to link parents and children as such when the ID is not necessarily sequential. Thank you for any help you might offer.

Upvotes: 0

Views: 451

Answers (1)

Dragaming
Dragaming

Reputation: 41

For anyone looking for the answer, here it is. I believe my problem was that I wasn't keeping the parent ID consistent, which resulted in repeated or missing IDs.

Code:

def DOTForm (root,dot,increment,parent):
    if root:
        temp = root.data # To avoid repeated memory calls
        if (isinstance(temp,list)): # This is just formatting for my data structure
            dot.node(str(increment),str(temp[0]+" "+str(temp[1])),shape="box")
        elif(isinstance(temp,str)):
            dot.node(str(increment),str(temp),shape="box")
        else:
            dot.node(str(increment),str(temp))
        del temp # Just so it doesn't interfere with following recursions
        parent = increment
        increment += 1
        if root.left:
            dot.edge(str(parent),str(increment))
        increment = DOTForm(root.left,dot,increment,parent)
        if root.right:
            dot.edge(str(parent),str(increment))
        increment = DOTForm(root.right,dot,increment,parent)
    return increment # Return the increased increment counter

Upvotes: 0

Related Questions