Example #1
0
    def physical_constraints():
        """
        Produce a CNF expression to enforce physical constraints.

        Ie. There must not be multiple components that occupy a given space.

        """

        # Make internal variables to determine whether a given component is in
        # a particular space.
        occ = {(c, s): wff.Var("{} occ {}".format(c, s))
               for s in board.spaces for c in components}

        # Generate constraints to enforce the definition of `occ`. occ[s, c] is
        # true iff there is a position `p` for `c` which covers `s` such that
        # comp_pos[c, p] is true. The first line handles the forward
        # implication, and the second the converse.
        positions_which_occupy = {(c, s): [p for p in positions[c]
                                                            if s in p.occupies]
                                  for c in components
                                  for s in board.spaces}
        occ_constraints = wff.to_cnf(
                wff.for_all(occ[c, s].iff(wff.exists(comp_pos[c, p]
                                        for p in positions_which_occupy[c, s]))
                    for c in components
                    for s in board.spaces))

        # Enforce that at most one component/jumper can occupy a space.
        jumpers_that_occupy_space = {s:
                                       [j for j in jumpers if s in j.occupies]
                                     for s in board.spaces}

        one_component_per_space = cnf.Expr.all(
                 cnf.at_most_one(
                            {occ[c, s] for c in components} |
                            {j.pres_var for j in jumpers_that_occupy_space[s]})
                    for s in board.spaces)

        # Return all of the above.
        return occ_constraints | one_component_per_space
Example #2
0
    def continuity_constraints():
        """
        Produce a CNF expression to enforce electrical continuity constraints.

        Ie. continuity between terminals that are in a common net, and
        discontinuity between terminals that are in different nets.

        """
        # Produce a dict which maps a terminal `t` and a hole `h` to a list of
        # positions of t.component which have `t` in `h`. Used a couple of
        # times in this function.
        positions_which_have_term_in = {
            (t, h): [p for p in positions[c] if p.terminal_positions[t] == h]
                    for c in components
                    for t in c.terminals
                    for h in board.holes}

        # Produce an adjacency dict for the electrical continuity graph implied
        # by links. Include the variable that must be true for said neighbour
        # to be present.
        neighbours = {h: [(l.get_other(h), l.pres_var) 
                             for l in links if h in l.holes]
                         for h in board.holes} 

        # Make internal variables to indicate whether a hole is connected to a
        # particular terminal. Defined for all holes, and the first terminal in
        # each net. (This is sufficient for validating (dis)continuity
        # constraints.
        term_conn = {(n[0], h): wff.Var("{} conn {}".format(n[0], h))
                        for n in nets
                        for h in board.holes}

        # Also make internal variables to indicate the minimum distance of each
        # hole to the nearest terminal. term_dist[h, i] is true iff there is no
        # path of length `i` or less from hole `h` to a head terminal. (A head
        # terminal is a terminal that is at the start of its net.)
        #
        # In other words, term_dist[h, *] is a unary encoding of the distance
        # to the nearest head terminal. Holes which are not connected to a
        # terminal will take the maximum value len(board.holes). Conversely,
        # holes which are connected will take a value < len(board.holes).
        term_dist = {(h, i): wff.Var("{} dist {}".format(h, i))
                        for h in board.holes
                        for i in range(len(board.holes))}

        # Generate constraints to enforce the definition of `term_conn`. A hole
        # is connected to a particular terminal iff one of its neighbours is
        # connected to the terminal or the terminal is in this hole. The first
        # expression handles the forward implication, whereas the second
        # expression handles the converse.
        term_conn_constraints = wff.to_cnf(
            wff.for_all(
                    term_conn[net[0], h].iff(
                        wff.exists(
                                  wff.add_var(term_conn[net[0], n] & link_pres)
                                           for n, link_pres in neighbours[h]) |
                        wff.exists(comp_pos[net[0].component, p]
                             for p in positions_which_have_term_in[net[0], h]))
                    for net in nets
                    for h in board.holes))
        if _DEBUG:
            print("Term conn constraints: {}".format(
                      term_conn_constraints.stats))

        # Add constraints to enforce the definition of `term_dist[h, 0]`, for
        # all holes `h`. term_hist[h, 0] is false iff a component is positioned
        # such that a head terminal is in hole `h`. The first statement
        # expresses the forward implication, and the second statement expresses
        # the converse.
        zero_term_dist_constraints = wff.to_cnf(
                wff.for_all(
                    (~term_dist[h, 0]).iff(
                        wff.exists(comp_pos[net[0].component, p]
                             for net in nets
                             for p in positions_which_have_term_in[net[0], h]))
                    for h in board.holes))
        if _DEBUG:
            print("Zero term dist constraints: {}".format(
                      zero_term_dist_constraints.stats))

        # Add constraints to enforce the definition of `term_dist[h, i]`, for
        # 0 < 1 < |holes|. term_dist[h, i] is true iff for each neighbour `n`
        # term_dist[n, i - 1] is true. The first statement expresses the
        # forward implication, and the second statement expresses the converse.
        non_zero_term_dist_constraints = wff.to_cnf(
                wff.for_all(
                    term_dist[h, i].iff(
                        wff.for_all(
                            wff.add_var(term_dist[n, i - 1] | ~link_pres)
                                           for n, link_pres in neighbours[h]) &
                        term_dist[h, i - 1])
                    for h in board.holes
                    for i in range(1, len(board.holes))))
        if _DEBUG:
            print("Non-zero term dist contraints: {}".format(
                      non_zero_term_dist_constraints.stats))

        # Add constraints which ensure any terminals are connected to the
        # terminal that's at the head of its net.
        def term_to_net(t):
            l = [net for net in nets if t in net]
            assert len(l) == 1, "Terminal is not in exactly one net"
            return l[0]
        head_term = {t: term_to_net(t)[0]
                            for c in components
                            for t in c.terminals}
        net_continuity_constraints = wff.to_cnf(
            wff.for_all(comp_pos[c, p] >> term_conn[head_term[t], h]
                                  for h in board.holes
                                  for c in components
                                  for t in c.terminals
                                  for p in positions_which_have_term_in[t, h]))
        if _DEBUG:
            print("Net continuity constraints: {}".format(
                      net_continuity_constraints.stats))

        # Add constraints which ensure that no hole is part of more than one
        # net, and if its disconnected from all nets, then it can be part of no
        # net.
        net_discontinuity_constraints = cnf.Expr.all(
                          cnf.at_most_one(
                              {term_conn[net[0], h] for net in nets} |
                              {term_dist[h, len(board.holes) - 1]})
                    for h in board.holes)
        if _DEBUG:
            print("Net discontinuity constraints: {}".format(
                      net_discontinuity_constraints.stats))

        # Return all of the above.
        return (term_conn_constraints |
                zero_term_dist_constraints |
                non_zero_term_dist_constraints |
                net_discontinuity_constraints |
                net_continuity_constraints)