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
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)