Example #1
0
if __name__ == '__main__':
    import argparse
    import pcrt

    parser = argparse.ArgumentParser()

    parser.add_argument('filename', type=str)

    args, unknown = parser.parse_known_args()

    (width,
     height), diagonals, nets, constraints, disabled = pcrt.read(args.filename)

    net_coords = {}
    for net in nets:
        for c in net:
            net_coords[c] = True

    const_coords = {}
    for const in constraints:
        for c in const:
            const_coords[c] = True

    dis_coords = []
    for dis in disabled:
        for c in dis:
            dis_coords[c] = True

    print(net_coords)
    print(const_coords)
    print(dis_coords)
Example #2
0
def route_multi(filename,
                monosat_args,
                maxflow_enforcement_level,
                flowgraph_separation_enforcement_style=0,
                graph_separation_enforcement_style=1,
                heuristicEdgeWeights=False,
                draw_solution=True):
    (width,
     height), diagonals, nets, constraints, disabled = pcrt.read(filename)
    print(filename)
    print("Width = %d, Height = %d, %d nets, %d constraints" %
          (width, height, len(nets), len(constraints)))
    if diagonals:
        print(
            "45-degree routing enabled. Warning: 45-degree routing is untested, and may be buggy."
        )
    else:
        print("90-degree routing")

    if (len(monosat_args) > 0):
        args = " ".join(monosat_args)
        print("Monosat args: " + args)
        Monosat().newSolver(args)

    if draw_solution:
        #the dicts here are only used to print the solution at the end. Disable for benchmarking.
        lefts = dict()
        rights = dict()
        ups = dict()
        downs = dict()

        down_lefts = dict()
        up_rights = dict()
        down_rights = dict()
        up_lefts = dict()

    graphs = []
    all_graphs = []
    for _ in nets:
        #for each net to be routed, created a separate symbolic graph.
        #later we will add constraints to force each edge to be enabled in at most one of these graphs
        graphs.append(Graph())

        if heuristicEdgeWeights:
            graphs[-1].assignWeightsTo(
                1
            )  # this enables a heuristic on these graphs, from the RUC paper, which sets assigned edges to zero weight, to encourage edge-reuse in solutions

        all_graphs.append(graphs[-1])
    flow_graph = None
    flow_graph_edges = dict()
    flow_grap_edge_list = collections.defaultdict(list)
    if maxflow_enforcement_level >= 1:
        #if flow constraints are used, create a separate graph which will contain the union of all the edges enabled in the above graphs
        flow_graph = Graph()
        all_graphs.append(flow_graph)

    out_grid = dict()
    print("Building grid")
    in_grid = dict()
    vertex_grid = dict()
    vertices = dict()
    fromGrid = dict()
    for g in all_graphs:
        out_grid[g] = dict()
        in_grid[g] = dict()
        vertex_grid[g] = dict()

    for x in range(width):
        for y in range(height):
            vertices[(x, y)] = []
            for g in all_graphs:
                n = g.addNode("%d_%d" % (x, y))
                out_grid[g][(x, y)] = n

                in_grid[g][(x, y)] = g.addNode("in_%d_%d" % (x, y))
                fromGrid[out_grid[g][(x, y)]] = (x, y)
                fromGrid[in_grid[g][(x, y)]] = (x, y)
                if (g != flow_graph):
                    e = g.addEdge(
                        in_grid[g][(x, y)], out_grid[g][(x, y)],
                        1 if not heuristicEdgeWeights else 1000
                    )  #a large-enough weight, only relevant if heuristicEdgeWeights>0
                else:
                    e = g.addEdge(in_grid[g][(x, y)], out_grid[g][(x, y)],
                                  1)  # add an edge with constant capacity of 1
                vertex_grid[g][(x, y)] = e

                if (g != flow_graph):
                    vertices[(x, y)].append(e)

    print("Adding edges")
    disabled_nodes = set(disabled)
    undirected_edges = dict()
    start_nodes = set()
    end_nodes = set()
    net_nodes = set()
    for net in nets:
        start_nodes.add(net[0])
        #you can pick any of the terminals in the net to be the starting node for the routing constraints;
        #it might be a good idea to randomize this choice
        for n in net[1:]:
            end_nodes.add(n)
        for (x, y) in net:
            net_nodes.add((x, y))

    #create undirected edges between neighbouring nodes
    def addEdge(n, r, diagonal_edge=False):
        e = None
        if n not in disabled_nodes and r not in disabled_nodes:
            if n in net_nodes or r in net_nodes:
                allow_out = True
                allow_in = True
                if n in start_nodes or r in end_nodes:
                    allow_in = False
                if n in end_nodes or r in start_nodes:
                    allow_out = False
                assert (not (allow_in and allow_out))
                if allow_out:
                    #for each net's symbolic graph (g), create an edge
                    edges = []
                    for g in graphs:
                        eg = (g.addEdge(out_grid[g][n], in_grid[g][r])
                              )  # add a _directed_ edge from n to r
                        if e is None:
                            e = eg
                        undirected_edges[eg] = e
                        edges.append(eg)
                        if not diagonal_edge:
                            Assert(eg)

                    if flow_graph is not None:
                        #create the same edge in the flow graph
                        ef = (flow_graph.addEdge(out_grid[flow_graph][n],
                                                 in_grid[flow_graph][r])
                              )  # add a _directed_ edge from n to r

                        if flowgraph_separation_enforcement_style > 0:
                            flow_graph_edges[(n, r)] = ef
                            flow_graph_edges[(r, n)] = ef
                            flow_grap_edge_list[n].append(ef)
                            flow_grap_edge_list[r].append(ef)
                        else:
                            if not diagonal_edge:
                                Assert(ef)
                        edges.append(ef)
                    if (diagonal_edge):
                        AssertEq(*edges)
                elif allow_in:
                    #for each net's symbolic graph (g), create an edge
                    edges = []
                    for g in graphs:
                        eg = (g.addEdge(out_grid[g][r], in_grid[g][n])
                              )  # add a _directed_ edge from n to r
                        if e is None:
                            e = eg
                        undirected_edges[eg] = e
                        edges.append(eg)
                        if not diagonal_edge:
                            Assert(eg)

                    if flow_graph is not None:
                        #create the same edge in the flow graph
                        ef = (flow_graph.addEdge(out_grid[flow_graph][r],
                                                 in_grid[flow_graph][n])
                              )  # add a _directed_ edge from n to r

                        if flowgraph_separation_enforcement_style > 0:
                            flow_graph_edges[(n, r)] = ef
                            flow_graph_edges[(r, n)] = ef
                            flow_grap_edge_list[n].append(ef)
                            flow_grap_edge_list[r].append(ef)
                        else:
                            if not diagonal_edge:
                                Assert(ef)
                        edges.append(ef)
                    if (diagonal_edge):
                        AssertEq(*edges)

                else:
                    e = None
            else:
                edges = []
                #for each net's symbolic graph (g), create an edge in both directions
                for g in graphs:
                    eg = (g.addEdge(out_grid[g][n], in_grid[g][r])
                          )  # add a _directed_ edge from n to r
                    if e is None:
                        e = eg
                    undirected_edges[eg] = e
                    if not diagonal_edge:
                        Assert(eg)
                    eg2 = (g.addEdge(out_grid[g][r], in_grid[g][n]))
                    if not diagonal_edge:
                        Assert(eg2)
                    else:
                        AssertEq(eg, eg2)
                    undirected_edges[eg2] = e  #map e2 to e
                    edges.append(eg)
                if flow_graph is not None:
                    ef = (flow_graph.addEdge(out_grid[flow_graph][r],
                                             in_grid[flow_graph][n])
                          )  # add a _directed_ edge from n to r
                    ef2 = (flow_graph.addEdge(out_grid[flow_graph][n],
                                              in_grid[flow_graph][r])
                           )  # add a _directed_ edge from r to n
                    edges.append(ef)
                    if flowgraph_separation_enforcement_style > 0:
                        AssertEq(ef, ef2)
                        flow_grap_edge_list[n].append(ef)
                        flow_grap_edge_list[r].append(ef)
                        flow_graph_edges[(n, r)] = ef
                        flow_graph_edges[(r, n)] = ef
                    else:
                        if not diagonal_edge:
                            Assert(ef)
                            Assert(ef2)
                    if (diagonal_edge):
                        AssertEq(*edges)

        return e

    #create all the symbolic edges.
    for x in range(width):
        for y in range(height):
            n = (x, y)
            if n in disabled_nodes:
                continue
            if x < width - 1:
                r = (x + 1, y)
                e = addEdge(n, r)
                if e is not None and draw_solution:
                    rights[n] = e
                    lefts[r] = e

            if y < height - 1:
                r = (x, y + 1)
                e = addEdge(n, r)
                if e is not None and draw_solution:
                    downs[n] = e
                    ups[r] = e

            if diagonals:
                #if 45 degree routing is enabled, create diagonal edges here
                diag_up = None
                diag_down = None
                if x < width - 1 and y < height - 1:
                    r = (x + 1, y + 1)
                    e = addEdge(n, r, True)
                    diag_down = e
                    if e is not None and draw_solution:
                        down_rights[n] = e
                        up_lefts[r] = e

                if x > 0 and y < height - 1 and False:
                    r = (x - 1, y + 1)
                    e = addEdge(n, r, True)
                    diag_up = e
                    if e is not None and draw_solution:
                        down_lefts[n] = e
                        up_rights[r] = e
                if diag_up and diag_down:
                    AssertNand(diag_up,
                               diag_down)  #cannot route both diagonals

    vertex_used = None

    #enforce constraints from the .pcrt file
    if len(constraints) > 0:
        print("Enforcing constraints")
        vertex_used = dict()
        for x in range(width):
            for y in range(height):
                vertex_used[(x, y)] = Or(vertices[(
                    x, y
                )])  # A vertex is used exactly if one of its edges is enabled

        for constraint in constraints:
            # a constraint is a list of vertices of which at most one can be used
            vertex_used_list = []
            for node in constraint:
                vertex_used_list.append(vertex_used[node])
            AMO(vertex_used_list)

    uses_bv = (flow_graph and flowgraph_separation_enforcement_style >= 2) or (
        graph_separation_enforcement_style >= 2)

    print("Enforcing separation")
    #force each vertex to be in at most one graph.
    for x in range(width):
        for y in range(height):
            n = (x, y)
            if n not in net_nodes:
                if graph_separation_enforcement_style <= 1:
                    AMO(
                        vertices[n]
                    )  #use at-most-one constraint to force each edge to be assigned to at most on net
                else:
                    #rely on the uniqueness bv encoding below to force at most one graph assign per vertex
                    assert (uses_bv)

                if flow_graph is not None or uses_bv:

                    if vertex_used is None:  #only create this lazily, if needed
                        vertex_used = dict()
                        for x in range(width):
                            for y in range(height):
                                vertex_used[(x, y)] = Or(
                                    vertices[(x, y)]
                                )  # A vertex is used exactly if one of its edges is enabled
                    if flow_graph is not None:
                        #Assert that iff this vertex is in _any_ graph, it must be in the flow graph
                        AssertEq(vertex_used[(x, y)],
                                 vertex_grid[flow_graph][n])

    if uses_bv:
        #Optionally, use a bitvector encoding to determine which graph each edge belongs to (following the RUC paper).
        vertices_bv = dict()
        bvwidth = math.ceil(math.log(len(nets) + 1, 2))
        print("Building BV (width = %d)" % (bvwidth))

        #bv 0 means unused
        for x in range(width):
            for y in range(height):
                #netbv = BitVector(bvwidth)
                netbv = [Var() for _ in range(bvwidth)]
                seen_bit = [
                    False
                ] * bvwidth  # this is just for error checking this script
                for b in range(bvwidth):
                    #if the vertex is not used, set the bv to 0
                    AssertImplies(~vertex_used[(x, y)], ~netbv[b])

                for i in range(len(nets)):
                    net_n = i + 1
                    seen_any_bits = False
                    for b in range(bvwidth):
                        bit = net_n & (1 << b)
                        if (bit):
                            AssertImplies(vertices[(x, y)][i], netbv[b])
                            seen_bit[b] = True
                            seen_any_bits = True
                        else:
                            AssertImplies(vertices[(x, y)][i], ~netbv[b])
                    #AssertImplies(vertices[(x,y)][i],(netbv.eq(net_n)))
                    assert (seen_any_bits)
                if graph_separation_enforcement_style < 3:
                    #rely on the above constraint AssertImplies(~vertex_used[(x, y)], ~netbv[b])
                    #to ensure that illegal values of netbv are disallowed
                    pass
                elif graph_separation_enforcement_style == 3:
                    # directly rule out disallowed bit patterns
                    # len(nets)+1, because 1 is added to each net id for the above calculation (so that 0 can be reserved for no net)
                    for i in range(len(nets) + 1, (1 << bvwidth)):
                        bits = []
                        for b in range(bvwidth):
                            bit = i & (1 << b)
                            if bit > 0:
                                bits.append(netbv[b])
                            else:
                                bits.append(~netbv[b])
                        AssertNand(
                            bits
                        )  #at least one of these bits cannot be assigned this way

                assert (
                    all(seen_bit)
                )  #all bits must have been set to 1 at some point, else the above constraints are buggy
                vertices_bv[(x, y)] = netbv

    #the following constraints are only relevant if maximum flow constraints are being used.
    #these constraints ensure that in the maximum flow graph, edges are not connected between
    #different nets.
    if flow_graph and flowgraph_separation_enforcement_style == 1:
        print("Enforcing (redundant) flow separation")
        #if two neighbouring nodes belong to different graphs, then
        #disable the edge between them.
        for x in range(width):
            for y in range(height):
                n = (x, y)

                if x < width - 1:
                    r = (x + 1, y)
                    if (n, r) in flow_graph_edges:
                        # if either end point is not is disabled, disable this edge... this is not technically required (since flow already cannot pass through unused vertices),
                        # but cheap to enforce and slightly reduces the search space.
                        AssertImplies(
                            Or(Not(vertex_used[n]), Not(vertex_used[r])),
                            Not(flow_graph_edges[(n, r)]))
                        any_same = false()
                        for i in range(len(vertices[n])):
                            # Enable this edge if both vertices belong to the same graph
                            same_graph = And(vertices[n][i], vertices[r][i])
                            AssertImplies(same_graph, flow_graph_edges[(n, r)])
                            any_same = Or(any_same, same_graph)
                            # Assert that if vertices[n] != vertices[r], then flow_graph_edges[(n,r)] = false
                            for j in range(i + 1, len(vertices[r])):
                                # if the end points of this edge belong to different graphs, disable them.
                                AssertImplies(
                                    And(vertices[n][i], vertices[r][j]),
                                    Not(flow_graph_edges[(n, r)]))
                        AssertEq(flow_graph_edges[(n, r)], any_same)

                if y < height - 1:
                    r = (x, y + 1)
                    if (n, r) in flow_graph_edges:
                        # if either end point is not is disabled, disable this edge... this is not technically required (since flow already cannot pass through unused vertices),
                        # but cheap to enforce and slightly reduces the search space.
                        AssertImplies(
                            Or(Not(vertex_used[n]), Not(vertex_used[r])),
                            Not(flow_graph_edges[(n, r)]))
                        any_same = false()
                        for i in range(len(vertices[n])):
                            # Enable this edge if both vertices belong to the same graph
                            same_graph = And(vertices[n][i], vertices[r][i])
                            AssertImplies(same_graph, flow_graph_edges[(n, r)])
                            any_same = Or(any_same, same_graph)

                            # Assert that if vertices[n] != vertices[r], then flow_graph_edges[(n,r)] = false
                            for j in range(i + 1, len(vertices[r])):
                                # if the end points of this edge belong to different graphs, disable them.
                                AssertImplies(
                                    And(vertices[n][i], vertices[r][j]),
                                    Not(flow_graph_edges[(n, r)]))
                        AssertEq(flow_graph_edges[(n, r)], any_same)

    elif flow_graph and flowgraph_separation_enforcement_style == 2:
        print("Enforcing (redundant) BV flow separation")
        for x in range(width):
            for y in range(height):
                n = (x, y)

                if x < width - 1:
                    r = (x + 1, y)
                    if (n, r) in flow_graph_edges:
                        # if either end point is not is disabled, disable this edge... this is not technically required (since flow already cannot pass through unused vertices),
                        # but cheap to enforce and slightly reduces the search space.
                        # AssertImplies(Or(Not(vertex_used[n]),Not(vertex_used[r])),Not(flow_graph_edges[(n, r)]))

                        same_graph = BVEQ(
                            vertices_bv[n], vertices_bv[r]
                        )  # And(vertices[n][i], vertices[r][i])
                        AssertEq(And(vertex_used[n], same_graph),
                                 flow_graph_edges[(n, r)])

                if y < height - 1:
                    r = (x, y + 1)
                    if (n, r) in flow_graph_edges:
                        # if either end point is not is disabled, disable this edge... this is not technically required (since flow already cannot pass through unused vertices),
                        # but cheap to enforce and slightly reduces the search space.
                        # AssertImplies(Or(Not(vertex_used[n]), Not(vertex_used[r])), Not(flow_graph_edges[(n, r)]))
                        same_graph = BVEQ(
                            vertices_bv[n], vertices_bv[r]
                        )  # And(vertices[n][i], vertices[r][i])
                        AssertEq(And(vertex_used[n], same_graph),
                                 flow_graph_edges[(n, r)])

    for i, net in enumerate(nets):
        for n in net:
            Assert(
                vertices[n][i])  #terminal nodes must be assigned to this graph
            if (flow_graph):
                Assert(
                    vertex_grid[flow_graph][n]
                )  #force the terminal nodes to be enabled in the flow graph

    print("Enforcing reachability")
    reachset = dict()
    #In each net's corresponding symbolic graph,
    #enforce that the first terminal of the net
    #reaches each other terminal of the net.
    for i, net in enumerate(nets):
        reachset[i] = dict()
        n1 = net[
            0]  #It is a good idea for all reachability constraints to have a common source
        #as that allows monosat to compute their paths simultaneously, cheaply.
        #Any of the terminals could be chosen to be that source; we use net[0], arbitrarily.
        for n2 in net[1:]:
            g = graphs[i]
            r = g.reaches(in_grid[g][n1], out_grid[g][n2])
            reachset[i][n2] = r
            Assert(r)

            r.setDecisionPriority(1)
            # decide reachability before considering regular variable decisions.
            #That prioritization is required for the RUC heuristics to take effect.

    if maxflow_enforcement_level >= 1:
        print("Enforcing flow constraints")

        #This adds an (optional) redundant maximum flow constraint. While the flow constraint is not by itself powerful
        #enough to enforce a correct routing (and so must be used in conjunction with the routing constraints above),
        #it can allow the solver to prune parts of the search space earlier than the routing constraints alone.

        # add a source and dest node, with 1 capacity from source to each net start vertex, and 1 capacity from each net end vertex to dest
        g = flow_graph
        source = g.addNode()
        dest = g.addNode()
        for net in nets:
            Assert(g.addEdge(source, in_grid[g][net[0]], 1))  # directed edges!
            Assert(g.addEdge(out_grid[g][net[1]], dest, 1))  # directed edges!
        m = g.maxFlowGreaterOrEqualTo(
            source, dest, len(nets))  #create a maximum flow constraint
        Assert(m)  #asser the maximum flow constraint

        #These settings control how the maximum flow constraints interact heuristically
        #with the routing constraints.
        if maxflow_enforcement_level == 3:
            m.setDecisionPriority(1)
            # sometimes make decisions on the maxflow predicate.
        elif maxflow_enforcement_level == 4:
            m.setDecisionPriority(2)
            # always make decisions on the maxflow predicate.
        else:
            m.setDecisionPriority(-1)
            # never make decisions on the maxflow predicate.

    print("Solving...")

    if Solve():

        print("Solved!")
        if draw_solution:
            paths = set()  #collect all the edges that make up the routing
            on_path = dict()
            drawn = set()
            for y in range(height):
                for x in range(width):
                    on_path[(x, y)] = -1

            for i, net in enumerate(nets):
                for n2 in net[1:]:
                    r = reachset[i][n2]
                    g = graphs[i]
                    path = g.getPath(r, True)
                    for e in path:
                        assert (e.value())
                        if e in undirected_edges.keys():
                            e = undirected_edges[e]  #normalize the edge var
                            assert (e in up_rights.values()
                                    or e in down_rights.values()
                                    or e in downs.values()
                                    or e in rights.values())
                            paths.add(e)

            #print the solution to the console
            for y in range(height):
                for x in range(width):
                    n = (x, y)

                    if (x, y) in net_nodes:
                        print("*", end="")
                    else:
                        print(" ", end="")
                    if x < width - 1:
                        r = (x + 1, y)
                        if n in rights:
                            e = rights[n]
                            if e in paths:
                                print("-", end="")
                            else:
                                print(" ", end="")
                        else:
                            print(" ", end="")
                print()
                for x in range(width):
                    n = (x, y)
                    if y < height - 1:
                        r = (x, y + 1)
                        if n in downs:
                            e = downs[n]
                            if e in paths:
                                print("|", end="")
                            else:
                                print(" ", end="")
                        else:
                            print(" ", end="")
                    drew_diag = False
                    if diagonals:
                        if y < height - 1 and x < width - 1:
                            r = (x + 1, y + 1)
                            if n in down_rights:
                                e = down_rights[n]
                                if e in paths:
                                    print("\\", end="")
                                    drew_diag = True
                        if y > 0 and x < width - 1:
                            n = (x, y + 1)
                            r = (x + 1, y)
                            if n in up_rights:
                                e = up_rights[n]
                                if e in paths:
                                    print("/", end="")
                                    assert (not drew_diag)
                                    drew_diag = True

                    if not drew_diag:
                        print(" ", end="")
                print()
            print("s SATISFIABLE")
            sys.exit(10)
        else:
            print("s UNSATISFIABLE")
            sys.exit(20)
Example #3
0
def route(filename, monosat_args, use_maxflow=False, draw_solution=True):
    (width,
     height), diagonals, nets, constraints, disabled = pcrt.read(filename)
    print(filename)
    print("Width = %d, Height = %d, %d nets, %d constraints, %d disabled" %
          (width, height, len(nets), len(constraints), len(disabled)))
    if diagonals:
        print("45-degree routing")
    else:
        print("90-degree routing")

    for net in nets:
        if (len(net) != 2):
            raise Exception(
                "router.py  only supports routing nets with exactly 2 vertices. Use router_multi.py for routing nets with 2+ vertices."
            )

    if (len(monosat_args) > 0):
        args = " ".join(monosat_args)
        print("MonoSAT args: " + args)
        Monosat().init(args)

    g = Graph()

    print("Building grid")
    edges = dict()
    grid = dict()

    for x in range(width):
        for y in range(height):
            n = g.addNode("%d_%d" % (x, y))
            grid[(x, y)] = n
            edges[(x, y)] = []

    disabled_nodes = set(disabled)
    undirected_edges = dict()
    #create undirected edges between neighbouring nodes
    if draw_solution:
        #the dicts here are only used to print the solution at the end. Disable for benchmarking.
        lefts = dict()
        rights = dict()
        ups = dict()
        downs = dict()

        down_lefts = dict()
        up_rights = dict()
        down_rights = dict()
        up_lefts = dict()

    start_nodes = set()
    end_nodes = set()
    net_nodes = set()
    for net in nets:
        assert (len(net) == 2)
        start_nodes.add(net[0])
        end_nodes.add(net[1])
        for (x, y) in net:
            net_nodes.add((x, y))

    #create undirected edges between neighbouring nodes
    def makeEdge(n, r):
        e = None
        if n not in disabled_nodes and r not in disabled_nodes:
            if n in net_nodes or r in net_nodes:
                #only add directed edges for start/end nodes.
                allow_out = True
                allow_in = True
                if n in start_nodes or r in end_nodes:
                    allow_in = False
                if n in end_nodes or r in start_nodes:
                    allow_out = False
                assert (not (allow_in and allow_out))
                if allow_out:
                    e = g.addEdge(grid[n],
                                  grid[r])  #add a _directed_ edge from n to r
                    undirected_edges[e] = e
                elif allow_in:
                    e = g.addEdge(grid[r],
                                  grid[n])  # add a _directed_ edge from r to n
                    undirected_edges[e] = e
                else:
                    e = None
            else:
                #add undirected edges for other nodes in the grid
                e = g.addEdge(
                    grid[n], grid[r]
                )  #in monosat, undirected edges are specified by creating
                undirected_edges[e] = e
                e2 = g.addEdge(
                    grid[r], grid[n]
                )  #directed edges in both directions, and making them equal
                undirected_edges[e2] = e
                AssertEq(e, e2)
            if e is not None:
                edges[n].append(e)
                edges[r].append(e)
        return e

    for x in range(width):
        for y in range(height):
            n = (x, y)
            if n in disabled_nodes:
                continue

            if x < width - 1:
                r = (x + 1, y)
                e = makeEdge(n, r)
                if e is not None and draw_solution:
                    rights[n] = e
                    lefts[r] = e

            if y < height - 1:
                r = (x, y + 1)
                e = makeEdge(n, r)
                if e is not None and draw_solution:
                    downs[n] = e
                    ups[r] = e

            if diagonals:
                #if 45 degree routing is enabled
                diag_up = None
                diag_down = None
                if x < width - 1 and y < height - 1:
                    r = (x + 1, y + 1)
                    e = makeEdge(n, r)
                    diag_down = e
                    if e is not None and draw_solution:
                        down_rights[n] = e
                        up_lefts[r] = e

                if x > 0 and y < height - 1 and False:
                    r = (x - 1, y + 1)
                    e = makeEdge(n, r)
                    diag_up = e
                    if e is not None and draw_solution:
                        down_lefts[n] = e
                        up_rights[r] = e
                if diag_up and diag_down:
                    AssertNand(diag_up,
                               diag_down)  #cannot route both diagonals

    if len(constraints) > 0:
        print("Enforcing constraints")
        vertex_used = dict()
        for x in range(width):
            for y in range(height):
                vertex_used[(x, y)] = Or(edges[(
                    x, y
                )])  # A vertex is used exactly if one of its edges is enabled

        for constraint in constraints:

            if len(constraint) > 1:
                #a constraint is a list of vertices of which at most one can be used
                vertex_used_list = []
                for node in constraint:
                    vertex_used_list.append(vertex_used[node])
                if (len(vertex_used_list) < 20):
                    for a, b in itertools.combinations(vertex_used_list, 2):
                        AssertOr(
                            ~a, ~b
                        )  #in every distinct pair of edges, at least one must be false
                else:
                    AssertAtMostOne(
                        vertex_used_list
                    )  #use more expensive, but more scalable, built-in AMO theory

    print("Enforcing net separation")
    #enforce that at any node that is not a starting/ending node, either exactly 2, or exactly no, edges are enabled
    #this approach ensures acyclicity, and also ensures that nodes from one net do not reach any other net.
    #However, this approach cannot be used to route trees.
    for x in range(width):
        for y in range(height):
            n = (x, y)

            edge_list = edges[n]
            if n in net_nodes:
                #n is a start or end node in the net.
                #no constraint is required, as only directed edges are available at start/end nodes
                pass
            else:
                #n is not a start or end node; either exactly 2, or exactly 0, adjacent edges must be enabled
                #There are many ways to actually enforce that constraint, not clear what the best option is
                #This is slightly complicated by edge nodes/corner nodes having fewer neighbours.

                if len(edge_list) > 2:
                    AssertNand(edge_list)  #At least one edge must be disabled

                if len(edge_list) >= 4:
                    #All but two edges must be disabled.
                    #This uses up to ~28 constraints. It might be better to implement this using PB constraints instead.
                    disabled_two = false()
                    for pair in itertools.combinations(edge_list,
                                                       len(edge_list) - 2):
                        disabled_two = Or(disabled_two, Nor(pair))
                    Assert(disabled_two)

    print("Enforcing net routing")
    #enforce reachability using MonoSAT's builtin reachability constraints
    #notice that unreachability between different nets does not need to be explicitly enforced here, because of the
    #edge constraints above.
    reach_constraints = []
    for net in nets:
        assert (len(net) == 2)
        n1 = net[0]
        n2 = net[1]
        r = g.reaches(grid[n1], grid[n2])
        reach_constraints.append(r)
        Assert(r)
        r.setDecisionPriority(1)
        #decide reachability before considering regular variable decisions

    #optional: use a maximum flow constraint, as an overapprox of disjoint reachability.
    if use_maxflow:
        print("Enforcing maxflow constraint")
        #add a source and dest node, with 1 capacity from source to each net start vertex, and 1 capacity from each net end vertex to dest
        source = g.addNode()
        dest = g.addNode()
        for net in nets:
            Assert(g.addEdge(source, grid[net[0]], 1))  #directed edges!
            Assert(g.addEdge(grid[net[1]], dest, 1))  #directed edges!
        m = g.maxFlowGreaterOrEqualTo(source, dest, len(nets))
        Assert(m)
        m.setDecisionPriority(-1)
        # never make decisions on the maxflow predicate.

    print("Solving...")
    if Solve():
        print("Solved!")
        if draw_solution:
            paths = set()  #collect all the edges that make up the routing
            for r in reach_constraints:
                path = g.getPath(r, True)
                for e in path:
                    assert (e.value())
                    if e in undirected_edges.keys():
                        e = undirected_edges[e]  #normalize the edge var
                        paths.add(e)

            #print the solution to the console
            for y in range(height):
                for x in range(width):
                    n = (x, y)

                    if (x, y) in net_nodes:
                        print("*", end="")
                    else:
                        print(" ", end="")
                    if x < width - 1:
                        r = (x + 1, y)
                        if n in rights:
                            e = rights[n]
                            if e in paths:
                                print("-", end="")
                            else:
                                print(" ", end="")
                        else:
                            print(" ", end="")
                print()
                for x in range(width):
                    n = (x, y)
                    if y < height - 1:
                        r = (x, y + 1)
                        if n in downs:
                            e = downs[n]
                            if e in paths:
                                print("|", end="")
                            else:
                                print(" ", end="")
                        else:
                            print(" ", end="")
                    drew_diag = False
                    if diagonals:
                        if y < height - 1 and x < width - 1:
                            r = (x + 1, y + 1)
                            if n in down_rights:
                                e = down_rights[n]
                                if e in paths:
                                    print("\\", end="")
                                    drew_diag = True
                        if y > 0 and x < width - 1:
                            n = (x, y + 1)
                            r = (x + 1, y)
                            if n in up_rights:
                                e = up_rights[n]
                                if e in paths:
                                    print("/", end="")
                                    assert (not drew_diag)
                                    drew_diag = True

                    if not drew_diag:
                        print(" ", end="")
                print()
        print("s SATISFIABLE")
        sys.exit(10)
    else:
        print("s UNSATISFIABLE")
        sys.exit(20)
Example #4
0
def route_multi(filename,
                monosat_args,
                maxflow_enforcement_level,
                flowgraph_separation_enforcement_style=0,
                graph_separation_enforcement_style=1,
                heuristicEdgeWeights=False):
    (width,
     height), diagonals, nets, constraints, disabled = pcrt.read(filename)
    print(filename)
    print("Width = %d, Height = %d, %d nets, %d constraints" %
          (width, height, len(nets), len(constraints)))
    if diagonals:
        print(
            "45-degree routing enabled. Warning: 45-degree routing is untested, and may be buggy."
        )
    else:
        print("90-degree routing")

    if (len(monosat_args) > 0):
        args = " ".join(monosat_args)
        print("Monosat args: " + args)
        Monosat().init(args)

    graphs = []
    all_graphs = []
    for _ in nets:
        # for each net to be routed, created a separate symbolic graph.
        # later we will add constraints to force each edge to be enabled in at
        # most one of these graphs
        graphs.append(Graph())

        if heuristicEdgeWeights:
            # this enables a heuristic on these graphs, from the RUC paper,
            # which sets assigned edges to zero weight, to encourage edge-reuse
            # in solutions
            graphs[-1].assignWeightsTo(1)

        all_graphs.append(graphs[-1])

    flow_graph = None
    flow_graph_edges = dict()
    flow_grap_edge_list = collections.defaultdict(list)
    if maxflow_enforcement_level >= 1:
        # if flow constraints are used, create a separate graph which will
        # contain the union of all the edges enabled in the above graphs
        flow_graph = Graph()
        all_graphs.append(flow_graph)

    print("Building grid")
    out_grid = dict()
    in_grid = dict()
    vertex_grid = dict()
    vertices = dict()
    fromGrid = dict()
    for g in all_graphs:
        out_grid[g] = dict()
        in_grid[g] = dict()
        vertex_grid[g] = dict()

    for x in range(width):
        for y in range(height):
            vertices[(x, y)] = []
            for g in all_graphs:
                out_node = g.addNode("%d_%d" % (x, y))
                out_grid[g][(x, y)] = out_node
                fromGrid[out_node] = (x, y)

                in_node = g.addNode("in_%d_%d" % (x, y))
                in_grid[g][(x, y)] = in_node
                fromGrid[in_node] = (x, y)

                if (g != flow_graph):
                    # a large-enough weight, only relevant if
                    # heuristicEdgeWeights > 0
                    weight = 1 if not heuristicEdgeWeights else 1000
                    edge = g.addEdge(in_node, out_node, weight)
                else:
                    # add an edge with constant capacity of 1
                    edge = g.addEdge(in_node, out_node, 1)
                vertex_grid[g][(x, y)] = edge

                if (g != flow_graph):
                    vertices[(x, y)].append(edge)

    print("Adding edges")
    disabled_nodes = set(disabled)
    undirected_edges = dict()
    start_nodes = set()
    end_nodes = set()
    net_nodes = set()
    for net in nets:
        start_nodes.add(net[0])
        # you can pick any of the terminals in the net to be the starting node
        # for the routing constraints; it might be a good idea to randomize this
        # choice
        for n in net[1:]:
            end_nodes.add(n)
        for (x, y) in net:
            net_nodes.add((x, y))

    # create undirected edges between neighbouring nodes
    def addEdge(n, r, diagonal_edge=False):
        e = None
        if n not in disabled_nodes and r not in disabled_nodes:
            if n in net_nodes or r in net_nodes:
                allow_out = True
                allow_in = True
                if n in start_nodes or r in end_nodes:
                    allow_in = False
                if n in end_nodes or r in start_nodes:
                    allow_out = False
                assert (not (allow_in and allow_out))
                if allow_out:
                    # for each net's symbolic graph (g), create an edge
                    edges = []
                    for g in graphs:
                        # add a _directed_ edge from n to r
                        eg = (g.addEdge(out_grid[g][n], in_grid[g][r]))
                        if e is None:
                            e = eg
                        undirected_edges[eg] = e
                        edges.append(eg)
                        if not diagonal_edge:
                            Assert(eg)

                    if flow_graph is not None:
                        # create the same edge in the flow graph
                        # add a _directed_ edge from n to r
                        ef = (flow_graph.addEdge(out_grid[flow_graph][n],
                                                 in_grid[flow_graph][r]))

                        if flowgraph_separation_enforcement_style > 0:
                            flow_graph_edges[(n, r)] = ef
                            flow_graph_edges[(r, n)] = ef
                            flow_grap_edge_list[n].append(ef)
                            flow_grap_edge_list[r].append(ef)
                        else:
                            if not diagonal_edge:
                                Assert(ef)
                        edges.append(ef)
                    if (diagonal_edge):
                        AssertEq(*edges)
                elif allow_in:
                    # for each net's symbolic graph (g), create an edge
                    edges = []
                    for g in graphs:
                        # add a _directed_ edge from n to r
                        eg = (g.addEdge(out_grid[g][r], in_grid[g][n]))
                        if e is None:
                            e = eg
                        undirected_edges[eg] = e
                        edges.append(eg)
                        if not diagonal_edge:
                            Assert(eg)

                    if flow_graph is not None:
                        # create the same edge in the flow graph
                        # add a _directed_ edge from n to r
                        ef = (flow_graph.addEdge(out_grid[flow_graph][r],
                                                 in_grid[flow_graph][n]))

                        if flowgraph_separation_enforcement_style > 0:
                            flow_graph_edges[(n, r)] = ef
                            flow_graph_edges[(r, n)] = ef
                            flow_grap_edge_list[n].append(ef)
                            flow_grap_edge_list[r].append(ef)
                        else:
                            if not diagonal_edge:
                                Assert(ef)
                        edges.append(ef)
                    if (diagonal_edge):
                        AssertEq(*edges)

                else:
                    e = None
            else:
                edges = []
                # for each net's symbolic graph (g), create an edge in both
                # directions
                for g in graphs:
                    # add a _directed_ edge from n to r
                    eg = (g.addEdge(out_grid[g][n], in_grid[g][r]))
                    if e is None:
                        e = eg
                    undirected_edges[eg] = e
                    if not diagonal_edge:
                        Assert(eg)
                    eg2 = (g.addEdge(out_grid[g][r], in_grid[g][n]))
                    if not diagonal_edge:
                        Assert(eg2)
                    else:
                        AssertEq(eg, eg2)
                    undirected_edges[eg2] = e  # map e2 to e
                    edges.append(eg)
                if flow_graph is not None:
                    # add a _directed_ edge from n to r
                    ef = (flow_graph.addEdge(out_grid[flow_graph][r],
                                             in_grid[flow_graph][n]))
                    # add a _directed_ edge from r to n
                    ef2 = (flow_graph.addEdge(out_grid[flow_graph][n],
                                              in_grid[flow_graph][r]))
                    edges.append(ef)
                    if flowgraph_separation_enforcement_style > 0:
                        AssertEq(ef, ef2)
                        flow_grap_edge_list[n].append(ef)
                        flow_grap_edge_list[r].append(ef)
                        flow_graph_edges[(n, r)] = ef
                        flow_graph_edges[(r, n)] = ef
                    else:
                        if not diagonal_edge:
                            Assert(ef)
                            Assert(ef2)
                    if (diagonal_edge):
                        AssertEq(*edges)

        return e

    # create all the symbolic edges.
    for x in range(width):
        for y in range(height):
            n = (x, y)
            if n in disabled_nodes:
                continue
            if x < width - 1:
                r = (x + 1, y)
                e = addEdge(n, r)

            if y < height - 1:
                r = (x, y + 1)
                e = addEdge(n, r)

            if diagonals:
                # if 45 degree routing is enabled, create diagonal edges here
                diag_up = None
                diag_down = None
                if x < width - 1 and y < height - 1:
                    r = (x + 1, y + 1)
                    e = addEdge(n, r, True)
                    diag_down = e

                if x > 0 and y < height - 1 and False:
                    r = (x - 1, y + 1)
                    e = addEdge(n, r, True)
                    diag_up = e

                if diag_up and diag_down:
                    AssertNand(diag_up,
                               diag_down)  #cannot route both diagonals

    vertex_used = None

    # enforce constraints from the .pcrt file
    if len(constraints) > 0:
        print("Enforcing constraints")
        vertex_used = dict()
        for x in range(width):
            for y in range(height):
                # A vertex is used exactly if one of its edges is enabled
                vertex_used[(x, y)] = Or(vertices[(x, y)])

        for constraint in constraints:
            # a constraint is a list of vertices of which at most one can be used
            vertex_used_list = []
            for node in constraint:
                vertex_used_list.append(vertex_used[node])
            AMO(vertex_used_list)

    uses_bv = (flow_graph and flowgraph_separation_enforcement_style >= 2) or \
              (graph_separation_enforcement_style >= 2)

    print("Enforcing separation")
    #force each vertex to be in at most one graph.
    for x in range(width):
        for y in range(height):
            n = (x, y)
            if n not in net_nodes:
                if graph_separation_enforcement_style <= 1:
                    # use at-most-one constraint to force each edge to be
                    # assigned to at most on net
                    AMO(vertices[n])
                else:
                    # rely on the uniqueness bv encoding below to force at most
                    # one graph assign per vertex
                    assert (uses_bv)

                if flow_graph is not None or uses_bv:

                    if vertex_used is None:
                        # only create this lazily, if needed
                        vertex_used = dict()
                        for x in range(width):
                            for y in range(height):
                                # A vertex is used exactly if one of its edges
                                # is enabled
                                vertex_used[(x, y)] = Or(vertices[(x, y)])
                    if flow_graph is not None:
                        # Assert that iff this vertex is in _any_ graph, it must
                        # be in the flow graph
                        AssertEq(vertex_used[(x, y)],
                                 vertex_grid[flow_graph][n])

    if uses_bv:
        # Optionally, use a bitvector encoding to determine which graph each
        # edge belongs to (following the RUC paper).
        vertices_bv = dict()
        bvwidth = math.ceil(math.log(len(nets) + 1, 2))
        print("Building BV (width = %d)" % (bvwidth))

        # bv 0 means unused
        for x in range(width):
            for y in range(height):
                # netbv = BitVector(bvwidth)
                netbv = [Var() for _ in range(bvwidth)]
                # this is just for error checking this script
                seen_bit = [False] * bvwidth
                for b in range(bvwidth):
                    # if the vertex is not used, set the bv to 0
                    AssertImplies(~vertex_used[(x, y)], ~netbv[b])

                for i in range(len(nets)):
                    net_n = i + 1
                    seen_any_bits = False
                    for b in range(bvwidth):
                        bit = net_n & (1 << b)
                        if (bit):
                            AssertImplies(vertices[(x, y)][i], netbv[b])
                            seen_bit[b] = True
                            seen_any_bits = True
                        else:
                            AssertImplies(vertices[(x, y)][i], ~netbv[b])
                    # AssertImplies(vertices[(x,y)][i],(netbv.eq(net_n)))
                    assert (seen_any_bits)
                if graph_separation_enforcement_style < 3:
                    # rely on the above constraint
                    # AssertImplies(~vertex_used[(x, y)], ~netbv[b])
                    # to ensure that illegal values of netbv are disallowed
                    pass
                elif graph_separation_enforcement_style == 3:
                    # directly rule out disallowed bit patterns
                    # len(nets)+1, because 1 is added to each net id for the
                    # above calculation (so that 0 can be reserved for no net)
                    for i in range(len(nets) + 1, (1 << bvwidth)):
                        bits = []
                        for b in range(bvwidth):
                            bit = i & (1 << b)
                            if bit > 0:
                                bits.append(netbv[b])
                            else:
                                bits.append(~netbv[b])
                        # at least one of these bits cannot be assigned this way
                        AssertNand(bits)

                # all bits must have been set to 1 at some point, else the above
                # constraints are buggy
                assert (all(seen_bit))
                vertices_bv[(x, y)] = netbv

    # the following constraints are only relevant if maximum flow constraints
    # are being used. These constraints ensure that in the maximum flow graph,
    # edges are not connected between different nets.
    if flow_graph and flowgraph_separation_enforcement_style == 1:
        print("Enforcing (redundant) flow separation")
        # if two neighbouring nodes belong to different graphs, then
        # disable the edge between them.
        for x in range(width):
            for y in range(height):
                n = (x, y)

                if x < width - 1:
                    r = (x + 1, y)
                    if (n, r) in flow_graph_edges:
                        # if either end point is not is disabled, disable this
                        # edge... this is not technically required (since flow
                        # already cannot pass through unused vertices), but
                        # cheap to enforce and slightly reduces the search
                        # space.
                        AssertImplies(
                            Or(Not(vertex_used[n]), Not(vertex_used[r])),
                            Not(flow_graph_edges[(n, r)]))
                        any_same = false()
                        for i in range(len(vertices[n])):
                            # Enable this edge if both vertices belong to the
                            # same graph
                            same_graph = And(vertices[n][i], vertices[r][i])
                            AssertImplies(same_graph, flow_graph_edges[(n, r)])
                            any_same = Or(any_same, same_graph)
                            # Assert that if vertices[n] != vertices[r], then
                            # flow_graph_edges[(n, r)] = false
                            for j in range(i + 1, len(vertices[r])):
                                # if the end points of this edge belong to
                                # different graphs, disable them.
                                AssertImplies(
                                    And(vertices[n][i], vertices[r][j]),
                                    Not(flow_graph_edges[(n, r)]))
                        AssertEq(flow_graph_edges[(n, r)], any_same)

                if y < height - 1:
                    r = (x, y + 1)
                    if (n, r) in flow_graph_edges:
                        # if either end point is not is disabled, disable this
                        # edge... this is not technically required (since flow
                        # already cannot pass through unused vertices), but
                        # cheap to enforce and slightly reduces the search space.
                        AssertImplies(
                            Or(Not(vertex_used[n]), Not(vertex_used[r])),
                            Not(flow_graph_edges[(n, r)]))
                        any_same = false()
                        for i in range(len(vertices[n])):
                            # Enable this edge if both vertices belong to the
                            # same graph
                            same_graph = And(vertices[n][i], vertices[r][i])
                            AssertImplies(same_graph, flow_graph_edges[(n, r)])
                            any_same = Or(any_same, same_graph)

                            # Assert that if vertices[n] != vertices[r], then
                            # flow_graph_edges[(n,r)] = false
                            for j in range(i + 1, len(vertices[r])):
                                # if the end points of this edge belong to
                                # different graphs, disable them.
                                AssertImplies(
                                    And(vertices[n][i], vertices[r][j]),
                                    Not(flow_graph_edges[(n, r)]))
                        AssertEq(flow_graph_edges[(n, r)], any_same)

    elif flow_graph and flowgraph_separation_enforcement_style == 2:
        print("Enforcing (redundant) BV flow separation")
        for x in range(width):
            for y in range(height):
                n = (x, y)

                if x < width - 1:
                    r = (x + 1, y)
                    if (n, r) in flow_graph_edges:
                        # if either end point is not is disabled, disable this
                        # edge... this is not technically required (since flow
                        # already cannot pass through unused vertices), but
                        # cheap to enforce and slightly reduces the search space.
                        # AssertImplies(Or(Not(vertex_used[n]),Not(vertex_used[r])),
                        # Not(flow_graph_edges[(n, r)]))

                        # And(vertices[n][i], vertices[r][i])
                        same_graph = BVEQ(vertices_bv[n], vertices_bv[r])
                        AssertEq(And(vertex_used[n], same_graph),
                                 flow_graph_edges[(n, r)])

                if y < height - 1:
                    r = (x, y + 1)
                    if (n, r) in flow_graph_edges:
                        # if either end point is not is disabled, disable this
                        # edge... this is not technically required (since flow
                        # already cannot pass through unused vertices),
                        # but cheap to enforce and slightly reduces the search
                        # space.
                        # AssertImplies(Or(Not(vertex_used[n]), Not(vertex_used[r])),
                        # Not(flow_graph_edges[(n, r)]))

                        # And(vertices[n][i], vertices[r][i])
                        same_graph = BVEQ(vertices_bv[n], vertices_bv[r])
                        AssertEq(And(vertex_used[n], same_graph),
                                 flow_graph_edges[(n, r)])

    for i, net in enumerate(nets):
        for n in net:
            # terminal nodes must be assigned to this graph
            Assert(vertices[n][i])
            if (flow_graph):
                # force the terminal nodes to be enabled in the flow graph
                Assert(vertex_grid[flow_graph][n])

    print("Enforcing reachability")
    reachset = dict()
    # In each net's corresponding symbolic graph, enforce that the first
    # terminal of the net reaches each other terminal of the net.
    for i, net in enumerate(nets):
        reachset[i] = dict()
        n1 = net[0]
        # It is a good idea for all reachability constraints to have a common
        # source as that allows monosat to compute their paths simultaneously,
        # cheaply. Any of the terminals could be chosen to be that source; we
        # use net[0], arbitrarily.
        for n2 in net[1:]:
            g = graphs[i]
            r = g.reaches(in_grid[g][n1], out_grid[g][n2])
            reachset[i][n2] = r
            Assert(r)

            # decide reachability before considering regular variable decisions.
            r.setDecisionPriority(1)
            # That prioritization is required for the RUC heuristics to take
            # effect.

    if maxflow_enforcement_level >= 1:
        print("Enforcing flow constraints")

        # This adds an (optional) redundant maximum flow constraint. While the
        # flow constraint is not by itself powerful enough to enforce a correct
        # routing (and so must be used in conjunction with the routing
        # constraints above), it can allow the solver to prune parts of the
        # search space earlier than the routing constraints alone.

        # add a source and dest node, with 1 capacity from source to each net
        # start vertex, and 1 capacity from each net end vertex to dest
        g = flow_graph
        source = g.addNode()
        dest = g.addNode()
        for net in nets:
            Assert(g.addEdge(source, in_grid[g][net[0]], 1))  # directed edges!
            Assert(g.addEdge(out_grid[g][net[1]], dest, 1))  # directed edges!
        # create a maximum flow constraint
        m = g.maxFlowGreaterOrEqualTo(source, dest, len(nets))
        Assert(m)  # assert the maximum flow constraint

        # These settings control how the maximum flow constraints interact
        # heuristically with the routing constraints.
        if maxflow_enforcement_level == 3:
            # sometimes make decisions on the maxflow predicate.
            m.setDecisionPriority(1)
        elif maxflow_enforcement_level == 4:
            # always make decisions on the maxflow predicate.
            m.setDecisionPriority(2)
        else:
            # never make decisions on the maxflow predicate.
            m.setDecisionPriority(-1)

    print("Solving...")

    if Solve():

        def vid(x, y):
            return str(int(y) * width + int(x))

        def reduce_linestring(linestring):
            if len(linestring.coords) < 3:
                return linestring
            coords = [linestring.coords[0]]
            for i, end in enumerate(linestring.coords[2:]):
                start = linestring.coords[i]
                test = linestring.coords[i + 1]
                if not LineString([start, end]).contains(Point(test)):
                    coords.append(test)
            coords.append(linestring.coords[-1])
            return LineString(coords)

        print("Solved!")

        filename = filename.split('.')
        filename[-1] = 'out.pcrt'
        filename = '.'.join(filename)

        nets_coords = []
        for i, net in enumerate(nets):
            nets_coords.append(set())
            for n2 in net[1:]:
                r = reachset[i][n2]
                g = graphs[i]
                path = g.getPath(r)
                for n in path:
                    nets_coords[-1].add(fromGrid[n])

        nets_lines = []
        for i, net_coord in enumerate(nets_coords):
            #print('net_coord:', [vid(x, y) for x, y in net_coord])
            nets_lines.append([])

            lines = []
            for a, b in itertools.combinations(net_coord, 2):
                if Point(a).distance(Point(b)) == 1:
                    lines.append(LineString([a, b]))
            mls = linemerge(MultiLineString(lines))
            if not isinstance(mls, MultiLineString):
                line = reduce_linestring(mls).coords
                #print('line:', [vid(x, y) for x, y in line])
                nets_lines[-1].append(line)
            else:
                for line in mls:
                    line = reduce_linestring(line).coords
                    #print('line:', [vid(x, y) for x, y in line])
                    nets_lines[-1].append(line)

        nets = nets_lines
        with open(filename, 'w') as f:
            print('G', width, height, file=f)
            for net in nets:
                segs = [
                    ','.join([vid(x, y) for x, y in net_seg])
                    for net_seg in net
                ]
                print('N', *segs, file=f)

        print("s SATISFIABLE")
    else:
        print("s UNSATISFIABLE")
        sys.exit(1)