def PerfectMatchingPrinciple(G): """Generates the clauses for the graph perfect matching principle. The principle claims that there is a way to select edges to such that all vertices have exactly one incident edge set to 1. Parameters ---------- G : undirected graph """ cnf = CNF() # Describe the formula name = "Perfect Matching Principle" if hasattr(G, 'name'): cnf.header = name + " of graph:\n" + G.name + "\n" + cnf.header else: cnf.header = name + ".\n" + cnf.header def var_name(u, v): if u <= v: return 'x_{{{0},{1}}}'.format(u, v) else: return 'x_{{{0},{1}}}'.format(v, u) # Each vertex has exactly one edge set to one. for v in enumerate_vertices(G): edge_vars = [var_name(u, v) for u in neighbors(G, v)] cnf.add_equal_to(edge_vars, 1) return cnf
def transform_a_literal(self, polarity, varname): """Substitute a literal with a (negated) XOR Arguments: - `polarity`: polarity of the literal - `varname`: fariable to be substituted Returns: a list of clauses """ varname = self._name_vertex_dict[varname] local_vars = neighbors(self._pattern, varname) local_names = ["Y_{{{0}}}".format(i) for i in local_vars] if self._function == 'xor': temp = CNF() temp.add_parity(local_names, 1 if polarity else 0) return list(temp) elif self._function == 'maj': threshold = (len(local_names)+1) // 2 # loose majority if polarity: return list(self.greater_or_equal_constraint(local_names, threshold)) else: return list(self.less_than_constraint(local_names, threshold)) else: raise RuntimeError( "Error: variable compression with invalid function")
def PerfectMatchingPrinciple(G): """Generates the clauses for the graph perfect matching principle. The principle claims that there is a way to select edges to such that all vertices have exactly one incident edge set to 1. Parameters ---------- G : undirected graph """ cnf=CNF() # Describe the formula name="Perfect Matching Principle" if hasattr(G,'name'): cnf.header=name+" of graph:\n"+G.name+"\n"+cnf.header else: cnf.header=name+".\n"+cnf.header def var_name(u,v): if u<=v: return 'x_{{{0},{1}}}'.format(u,v) else: return 'x_{{{0},{1}}}'.format(v,u) # Each vertex has exactly one edge set to one. for v in enumerate_vertices(G): edge_vars = [var_name(u,v) for u in neighbors(G,v)] cnf.add_equal_to(edge_vars,1) return cnf
def transform_a_literal(self, polarity,varname): """Substitute a literal with a (negated) XOR Arguments: - `polarity`: polarity of the literal - `varname`: fariable to be substituted Returns: a list of clauses """ varname = self._name_vertex_dict[varname] local_vars = neighbors(self._pattern,varname) local_names = ["Y_{{{0}}}".format(i) for i in local_vars ] if self._function == 'xor': temp = CNF() temp.add_parity(local_names,1 if polarity else 0) return list(temp) elif self._function == 'maj': threshold = (len(local_names)+1) // 2 # loose majority if polarity: return list(self.greater_or_equal_constraint(local_names, threshold )) else: return list(self.less_than_constraint(local_names, threshold )) else: raise RuntimeError("Error: variable compression with invalid function")
def TseitinFormula(graph,charges=None): """Build a Tseitin formula based on the input graph. Odd charge is put on the first vertex by default, unless other vertices are is specified in input. Arguments: - `graph`: input graph - `charges': odd or even charge for each vertex """ V=enumerate_vertices(graph) if charges==None: charges=[1]+[0]*(len(V)-1) # odd charge on first vertex else: charges = [bool(c) for c in charges] # map to boolean if len(charges)<len(V): charges=charges+[0]*(len(V)-len(charges)) # pad with even charges # init formula tse=CNF() for e in sorted(graph.edges(),key=sorted): tse.add_variable(edgename(e)) # add constraints for v,c in zip(V,charges): # produce all clauses and save half of them names = [ edgename((u,v)) for u in neighbors(graph,v) ] for cls in parity_constraint(names,c): tse.add_clause(list(cls),strict=True) return tse
def EvenColoringFormula(G): """Even coloring formula The formula is defined on a graph :math:`G` and claims that it is possible to split the edges of the graph in two parts, so that each vertex has an equal number of incident edges in each part. The formula is defined on graphs where all vertices have even degree. The formula is satisfiable only on those graphs with an even number of vertices in each connected component [1]_. Arguments --------- G : networkx.Graph a simple undirected graph where all vertices have even degree Raises ------ ValueError if the graph in input has a vertex with odd degree Returns ------- CNF object References ---------- .. [1] Locality and Hard SAT-instances, Klas Markstrom Journal on Satisfiability, Boolean Modeling and Computation 2 (2006) 221-228 """ F = CNF() F.mode_strict() F.header = "Even coloring formula on graph " + G.name + "\n" + F.header def var_name(u, v): if u <= v: return 'x_{{{0},{1}}}'.format(u, v) else: return 'x_{{{0},{1}}}'.format(v, u) for (u, v) in enumerate_edges(G): F.add_variable(var_name(u, v)) # Defined on both side for v in enumerate_vertices(G): if G.degree(v) % 2 == 1: raise ValueError( "Markstrom formulas requires all vertices to have even degree." ) edge_vars = [var_name(u, v) for u in neighbors(G, v)] # F.add_exactly_half_floor would work the same F.add_exactly_half_ceil(edge_vars) return F
def EvenColoringFormula(G): """Even coloring formula The formula is defined on a graph :math:`G` and claims that it is possible to split the edges of the graph in two parts, so that each vertex has an equal number of incident edges in each part. The formula is defined on graphs where all vertices have even degree. The formula is satisfiable only on those graphs with an even number of vertices in each connected component [1]_. Arguments --------- G : networkx.Graph a simple undirected graph where all vertices have even degree Raises ------ ValueError if the graph in input has a vertex with odd degree Returns ------- CNF object References ---------- .. [1] Locality and Hard SAT-instances, Klas Markstrom Journal on Satisfiability, Boolean Modeling and Computation 2 (2006) 221-228 """ F = CNF() F.header = "Even coloring formula on graph " + G.name + "\n" + F.header def var_name(u,v): if u<=v: return 'x_{{{0},{1}}}'.format(u,v) else: return 'x_{{{0},{1}}}'.format(v,u) for (u, v) in enumerate_edges(G): F.add_variable(var_name(u, v)) # Defined on both side for v in enumerate_vertices(G): if G.degree(v) % 2 == 1: raise ValueError("Markstrom formulas requires all vertices to have even degree.") edge_vars = [ var_name(u,v) for u in neighbors(G,v) ] for cls in CNF.equal_to_constraint(edge_vars, len(edge_vars)/2): F.add_clause(cls,strict=True) return F
def TseitinFormula(graph,charges=None): """Build a Tseitin formula based on the input graph. Odd charge is put on the first vertex by default, unless other vertices are is specified in input. Arguments: - `graph`: input graph - `charges': odd or even charge for each vertex """ V=enumerate_vertices(graph) if charges==None: charges=[1]+[0]*(len(V)-1) # odd charge on first vertex else: charges = [bool(c) for c in charges] # map to boolean if len(charges)<len(V): charges=charges+[0]*(len(V)-len(charges)) # pad with even charges # init formula tse=CNF() edgename = { } for (u,v) in sorted(graph.edges(),key=sorted): edgename[(u,v)] = "E_{{{0},{1}}}".format(u,v) edgename[(v,u)] = "E_{{{0},{1}}}".format(u,v) tse.add_variable(edgename[(u,v)]) # add constraints for v,c in zip(V,charges): # produce all clauses and save half of them names = [ edgename[(u,v)] for u in neighbors(graph,v) ] for cls in CNF.parity_constraint(names,c): tse.add_clause(list(cls),strict=True) return tse
def TseitinFormula(graph, charges=None, encoding=None): """Build a Tseitin formula based on the input graph. Odd charge is put on the first vertex by default, unless other vertices are is specified in input. Arguments: - `graph`: input graph - `charges': odd or even charge for each vertex """ V = enumerate_vertices(graph) if charges == None: charges = [1] + [0] * (len(V) - 1) # odd charge on first vertex else: charges = [bool(c) for c in charges] # map to boolean if len(charges) < len(V): charges = charges + [0] * (len(V) - len(charges) ) # pad with even charges # init formula tse = CNF() edgename = {} for (u, v) in sorted(graph.edges(), key=sorted): edgename[(u, v)] = "E_{{{0},{1}}}".format(u, v) edgename[(v, u)] = "E_{{{0},{1}}}".format(u, v) tse.add_variable(edgename[(u, v)]) tse.mode_strict() # add constraints for v, charge in zip(V, charges): # produce all clauses and save half of them names = [edgename[(u, v)] for u in neighbors(graph, v)] if encoding == None: tse.add_parity(names, charge) else: def toArgs(listOfTuples, operator, degree): return list(sum(listOfTuples, ())) + [operator, degree] terms = list(map(lambda x: (1, x), names)) w = len(terms) k = w // 2 if encoding == "extendedPBAnyHelper": for i in range(k): helper = ("xor_helper", i, v) tse.add_variable(helper) terms.append((2, helper)) elif encoding == "extendedPBOneHelper": helpers = list() for i in range(k): helper = ("xor_helper", i, v) helpers.append(helper) tse.add_variable(helper) terms.append(((i + 1) * 2, helper)) tse.add_linear(*toArgs([(1, x) for x in helpers], "<=", 1)) elif encoding == "extendedPBExpHelper": for i in range(math.ceil(math.log(k, 2)) + 1): helper = ("xor_helper", i, v) tse.add_variable(helper) terms.append((2**i * 2, helper)) else: raise ValueError("Invalid encoding '%s'" % encoding) degree = 2 * k + (charge % 2) tse.add_linear(*toArgs(terms, "==", degree)) return tse
def GraphPigeonholePrinciple(graph,functional=False,onto=False): """Graph Pigeonhole Principle CNF formula The graph pigeonhole principle CNF formula, defined on a bipartite graph G=(L,R,E), claims that there is a subset E' of the edges such that every vertex on the left size L has at least one incident edge in E' and every edge on the right side R has at most one incident edge in E'. This is possible only if the graph has a matching of size |L|. There are different variants of this formula, depending on the values of `functional` and `onto` argument. - PHP(G): each left vertex can be incident to multiple edges in E' - FPHP(G): each left vertex must be incident to exaclty one edge in E' - onto-PHP: all right vertices must be incident to some vertex - matching: E' must be a perfect matching between L and R Arguments: - `graph` : bipartite graph - `functional`: add clauses to enforce at most one edge per left vertex - `onto`: add clauses to enforce that any right vertex has one incident edge Remark: the graph vertices must have the 'bipartite' attribute set. Left vertices must have it set to 0 and the right ones to 1. A KeyException is raised otherwise. """ def var_name(p,h): return 'p_{{{0},{1}}}'.format(p,h) if functional: if onto: formula_name="Graph matching" else: formula_name="Graph functional pigeonhole principle" else: if onto: formula_name="Graph onto pigeonhole principle" else: formula_name="Graph pigeonhole principle" gphp=CNF() gphp.header="{0} formula for graph {1}\n".format(formula_name,graph.name) Left, _ = bipartite_sets(graph) for p in Left: for h in neighbors(graph,p): gphp.add_variable(var_name(p,h)) clauses=gphp.sparse_mapping( graph, var_name=var_name, complete = True, injective = True, functional = functional, surjective = onto) for c in clauses: gphp.add_clause(c,strict=True) return gphp
def SubsetCardinalityFormula(B, equalities=False): r"""SubsetCardinalityFormula Consider a bipartite graph :math:`B`. The CNF claims that at least half of the edges incident to each of the vertices on left side of :math:`B` must be zero, while at least half of the edges incident to each vertex on the left side must be one. Variants of these formula on specific families of bipartite graphs have been studied in [1]_, [2]_ and [3]_, and turned out to be difficult for resolution based SAT-solvers. Each variable of the formula is denoted as :math:`x_{i,j}` where :math:`\{i,j\}` is an edge of the bipartite graph. The clauses of the CNF encode the following constraints on the edge variables. For every left vertex i with neighborhood :math:`\Gamma(i)` .. math:: \sum_{j \in \Gamma(i)} x_{i,j} \geq \frac{|\Gamma(i)|}{2} For every right vertex j with neighborhood :math:`\Gamma(j)` .. math:: \sum_{i \in \Gamma(j)} x_{i,j} \leq \frac{|\Gamma(j)|}{2}. If the ``equalities`` flag is true, the constraints are instead represented by equations. .. math:: \sum_{j \in \Gamma(i)} x_{i,j} = \left\lceil \frac{|\Gamma(i)|}{2} \right\rceil .. math:: \sum_{i \in \Gamma(j)} x_{i,j} = \left\lfloor \frac{|\Gamma(j)|}{2} \right\rfloor . Parameters ---------- B : networkx.Graph the graph vertices must have the 'bipartite' attribute set. Left vertices must have it set to 0 and the right ones to 1. A KeyException is raised otherwise. equalities : boolean use equations instead of inequalities to express the cardinality constraints. (default: False) Returns ------- A CNF object References ---------- .. [1] Mladen Miksa and Jakob Nordstrom Long proofs of (seemingly) simple formulas Theory and Applications of Satisfiability Testing--SAT 2014 (2014) .. [2] Ivor Spence sgen1: A generator of small but difficult satisfiability benchmarks Journal of Experimental Algorithmics (2010) .. [3] Allen Van Gelder and Ivor Spence Zero-One Designs Produce Small Hard SAT Instances Theory and Applications of Satisfiability Testing--SAT 2010(2010) """ Left, Right = bipartite_sets(B) ssc = CNF() ssc.header = "Subset cardinality formula for graph {0}\n".format(B.name) ssc.mode_strict() def var_name(u, v): """Compute the variable names.""" if u <= v: return 'x_{{{0},{1}}}'.format(u, v) else: return 'x_{{{0},{1}}}'.format(v, u) for u in Left: for v in neighbors(B, u): ssc.add_variable(var_name(u, v)) for u in Left: edge_vars = [var_name(u, v) for v in neighbors(B, u)] if equalities: ssc.add_exactly_half_ceil(edge_vars) else: ssc.add_loose_majority(edge_vars) for v in Right: edge_vars = [var_name(u, v) for u in neighbors(B, v)] if equalities: ssc.add_exactly_half_floor(edge_vars) else: ssc.add_loose_minority(edge_vars) return ssc
def SubsetCardinalityFormula(B, equalities=False): r"""SubsetCardinalityFormula Consider a bipartite graph :math:`B`. The CNF claims that at least half of the edges incident to each of the vertices on left side of :math:`B` must be zero, while at least half of the edges incident to each vertex on the left side must be one. Variants of these formula on specific families of bipartite graphs have been studied in [1]_, [2]_ and [3]_, and turned out to be difficult for resolution based SAT-solvers. Each variable of the formula is denoted as :math:`x_{i,j}` where :math:`\{i,j\}` is an edge of the bipartite graph. The clauses of the CNF encode the following constraints on the edge variables. For every left vertex i with neighborhood :math:`\Gamma(i)` .. math:: \sum_{j \in \Gamma(i)} x_{i,j} \geq \frac{|\Gamma(i)|}{2} For every right vertex j with neighborhood :math:`\Gamma(j)` .. math:: \sum_{i \in \Gamma(j)} x_{i,j} \leq \frac{|\Gamma(j)|}{2}. If the ``equalities`` flag is true, the constraints are instead represented by equations. .. math:: \sum_{j \in \Gamma(i)} x_{i,j} = \left\lceil \frac{|\Gamma(i)|}{2} \right\rceil .. math:: \sum_{i \in \Gamma(j)} x_{i,j} = \left\lfloor \frac{|\Gamma(j)|}{2} \right\rfloor . Parameters ---------- B : networkx.Graph the graph vertices must have the 'bipartite' attribute set. Left vertices must have it set to 0 and the right ones to 1. A KeyException is raised otherwise. equalities : boolean use equations instead of inequalities to express the cardinality constraints. (default: False) Returns ------- A CNF object References ---------- .. [1] Mladen Miksa and Jakob Nordstrom Long proofs of (seemingly) simple formulas Theory and Applications of Satisfiability Testing--SAT 2014 (2014) .. [2] Ivor Spence sgen1: A generator of small but difficult satisfiability benchmarks Journal of Experimental Algorithmics (2010) .. [3] Allen Van Gelder and Ivor Spence Zero-One Designs Produce Small Hard SAT Instances Theory and Applications of Satisfiability Testing--SAT 2010(2010) """ Left, Right = bipartite_sets(B) ssc = CNF() ssc.header = "Subset cardinality formula for graph {0}\n".format(B.name) def var_name(u, v): """Compute the variable names.""" if u <= v: return "x_{{{0},{1}}}".format(u, v) else: return "x_{{{0},{1}}}".format(v, u) for u in Left: for v in neighbors(B, u): ssc.add_variable(var_name(u, v)) for u in Left: edge_vars = [var_name(u, v) for v in neighbors(B, u)] if equalities: for cls in CNF.exactly_half_ceil(edge_vars): ssc.add_clause(cls, strict=True) else: for cls in CNF.loose_majority_constraint(edge_vars): ssc.add_clause(cls, strict=True) for v in Right: edge_vars = [var_name(u, v) for u in neighbors(B, v)] if equalities: for cls in CNF.exactly_half_floor(edge_vars): ssc.add_clause(cls, strict=True) else: for cls in CNF.loose_minority_constraint(edge_vars): ssc.add_clause(cls, strict=True) return ssc