def __init__(self, cnf, B, function='xor'): """Build a new CNF obtained by substituting a XOR to the variables of the original CNF. Parameters ---------- cnf : CNF the original cnf formula B : networkx.Graph a bipartite graph. The right side must have the number of vertices equal to the number of original variables function: string Select which faction is used for the compression. It must be one among 'xor' or 'maj'. """ if function not in ['xor','maj']: raise ValueError("Function specification for variable compression must be either 'xor' or 'maj'.") Left,Right = bipartite_sets(B) if len(Right) != len(list(cnf.variables())): raise ValueError("Right side of the graph must match the variable numbers of the CNF.") self._pattern = B self._function = function for n,v in zip(cnf.variables(),Right): self._name_vertex_dict[n]=v super(VariableCompression,self).__init__(cnf, new_variables = ["Y_{{{0}}}".format(i) for i in Left]) self._header="Variable {}-compression from {} to {} variables\n\n".format(function,len(Right),len(Left)) \ +self._header
def test_bshift(self): G = self.parse(["--bshift", "10","9"]) self.assertEqual(G.order(),19) left, right = bipartite_sets(G) self.assertEqual(len(left),10) self.assertEqual(len(right),9) for v in left: self.assertEqual(G.degree(v),0) G = self.parse(["--bshift", "10","10","1","2","4","8"]) self.assertEqual(G.order(),20) left, right = bipartite_sets(G) self.assertEqual(len(left),10) self.assertEqual(len(right),10) for v in left: self.assertEqual(G.degree(v),4)
def test_bm(self): G = self.parse(["--bm", "10", "9", "15"]) self.assertEqual(G.order(), 19) self.assertEqual(G.size(), 15) left, right = bipartite_sets(G) self.assertEqual(len(left), 10) self.assertEqual(len(right), 9)
def test_readGraph_kthlist_bipartite(self): G = readGraph(sio(kthlist_bipartite), graph_type="bipartite", file_format="kthlist") self.assertEqual(G.order(), 5) L, R = bipartite_sets(G) self.assertEqual(len(L), 2) self.assertEqual(len(R), 3)
def test_bshift(self): G = self.parse(["--bshift", "10", "9"]) self.assertEqual(G.order(), 19) left, right = bipartite_sets(G) self.assertEqual(len(left), 10) self.assertEqual(len(right), 9) for v in left: self.assertEqual(G.degree(v), 0) G = self.parse(["--bshift", "10", "10", "1", "2", "4", "8"]) self.assertEqual(G.order(), 20) left, right = bipartite_sets(G) self.assertEqual(len(left), 10) self.assertEqual(len(right), 10) for v in left: self.assertEqual(G.degree(v), 4)
def test_bm(self): G = self.parse(["--bm", "10", "9", "15"]) self.assertEqual(G.order(),19) self.assertEqual(G.size(),15) left, right = bipartite_sets(G) self.assertEqual(len(left),10) self.assertEqual(len(right),9)
def test_readGraph_kthlist_bipartite(self): G = readGraph(sio(kthlist_bipartite), graph_type='bipartite', file_format='kthlist') self.assertEqual(G.order(), 5) L, R = bipartite_sets(G) self.assertEqual(len(L), 2) self.assertEqual(len(R), 3)
def test_bd(self): G = self.parse(["--bd", "10", "9", "3"]) self.assertEqual(G.order(), 19) self.assertEqual(G.size(), 30) left, right = bipartite_sets(G) self.assertEqual(len(left), 10) self.assertEqual(len(right), 9) for v in left: self.assertEqual(G.degree(v), 3)
def test_bd(self): G = self.parse(["--bd", "10", "9", "3"]) self.assertEqual(G.order(),19) self.assertEqual(G.size(),30) left, right = bipartite_sets(G) self.assertEqual(len(left),10) self.assertEqual(len(right),9) for v in left: self.assertEqual(G.degree(v),3)
def test_bregular(self): G = self.parse(["--bregular", "10", "8", "4"]) self.assertEqual(G.order(),18) self.assertEqual(G.size(),40) left, right = bipartite_sets(G) self.assertEqual(len(left),10) self.assertEqual(len(right),8) for v in left: self.assertEqual(G.degree(v),4) for v in right: self.assertEqual(G.degree(v),5)
def test_bregular(self): G = self.parse(["--bregular", "10", "8", "4"]) self.assertEqual(G.order(), 18) self.assertEqual(G.size(), 40) left, right = bipartite_sets(G) self.assertEqual(len(left), 10) self.assertEqual(len(right), 8) for v in left: self.assertEqual(G.degree(v), 4) for v in right: self.assertEqual(G.degree(v), 5)
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 test_bp(self): G = self.parse(["--bp", "10", "9", "0.5"]) self.assertEqual(G.order(),19) left, right = bipartite_sets(G) self.assertEqual(len(left),10) self.assertEqual(len(right),9)
def SparseStoneFormula(D,B): """Sparse Stone formulas This is a variant of the :py:func:`StoneFormula`. See that for a description of the formula. This variant is such that each vertex has only a small selection of which stone can go to that vertex. In particular which stones are allowed on each vertex is specified by a bipartite graph :math:`B` on which the left vertices represent the vertices of DAG :math:`D` and the right vertices are the stones. If a vertex of :math:`D` correspond to the left vertex :math:`v` in :math:`B`, then its neighbors describe which stones are allowed for it. The vertices in :math:`D` do not need to have the same name as the one on the left side of :math:`B`. It is only important that the number of vertices in :math:`D` is the same as the vertices in the left side of :math:`B`. In that case the element at position :math:`i` in the ordered sequence ``enumerate_vertices(D)`` corresponds to the element of rank :math:`i` in the sequence of left side vertices of :math:`B` according to the output of ``Left, Right = bipartite_sets(B)``. Standard :py:func:`StoneFormula` is essentially equivalent to a sparse stone formula where :math:`B` is the complete graph. Parameters ---------- D : a directed acyclic graph it should be a directed acyclic graph. B : bipartite graph Raises ------ ValueError if :math:`D` is not a directed acyclic graph ValueError if :math:`B` is not a bipartite graph ValueError when size differs between :math:`D` and the left side of :math:`B` See Also -------- StoneFormula """ if not is_dag(D): raise ValueError("Stone formulas are defined only for directed acyclic graphs.") if not has_bipartition(B): raise ValueError("Vertices to stones mapping must be specified with a bipartite graph") Left, Right = bipartite_sets(B) nstones = len(Right) if len(Left) != D.order(): raise ValueError("Formula requires the bipartite left side to match #vertices of the DAG.") cnf = CNF() if hasattr(D, 'name'): cnf.header = "Sparse Stone formula of: " + D.name + "\nwith " + str(nstones) + " stones\n" + cnf.header else: cnf.header = "Sparse Stone formula with " + str(nstones) + " stones\n" + cnf.header # add variables in the appropriate order vertices=enumerate_vertices(D) stones=range(1,nstones+1) mapping = cnf.unary_mapping(vertices,stones,sparsity_pattern=B) stone_formula_helper(cnf,D,mapping) return cnf
def test_bp(self): G = self.parse(["--bp", "10", "9", "0.5"]) self.assertEqual(G.order(), 19) left, right = bipartite_sets(G) self.assertEqual(len(left), 10) self.assertEqual(len(right), 9)
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, Right = bipartite_sets(graph) mapping = unary_mapping(Left,Right, sparsity_pattern=graph, var_name=var_name, injective = True, functional = functional, surjective = onto) gphp.mode_unchecked() mapping.load_variables_to_formula(gphp) mapping.load_clauses_to_formula(gphp) gphp.mode_default() return gphp
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, Right = bipartite_sets(graph) mapping = unary_mapping(Left, Right, sparsity_pattern=graph, var_name=var_name, injective=True, functional=functional, surjective=onto) gphp.mode_unchecked() mapping.load_variables_to_formula(gphp) mapping.load_clauses_to_formula(gphp) gphp.mode_default() return gphp
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" Left, Right = bipartite_sets(graph) # Clause generator def _GPHP_clause_generator(G,functional,onto): # Pigeon axioms for p in Left: for C in greater_or_equal_constraint([var_name(p,h) for h in G.adj[p]], 1): yield C # Onto axioms if onto: for h in Right: for C in greater_or_equal_constraint([var_name(p,h) for p in G.adj[h]], 1): yield C # No conflicts axioms for h in Right: for C in less_or_equal_constraint([var_name(p,h) for p in G.adj[h]],1): yield C # Function axioms if functional: for p in Left: for C in less_or_equal_constraint([var_name(p,h) for h in G.adj[p]],1): yield C gphp=CNF() gphp.header="{0} formula for graph {1}\n".format(formula_name,graph.name) for p in Left: for h in graph.adj[p]: gphp.add_variable(var_name(p,h)) clauses=_GPHP_clause_generator(graph,functional,onto) for c in clauses: gphp.add_clause(c,strict=True) return gphp
def SparseStoneFormula(D, B): """Sparse Stone formulas This is a variant of the :py:func:`StoneFormula`. See that for a description of the formula. This variant is such that each vertex has only a small selection of which stone can go to that vertex. In particular which stones are allowed on each vertex is specified by a bipartite graph :math:`B` on which the left vertices represent the vertices of DAG :math:`D` and the right vertices are the stones. If a vertex of :math:`D` correspond to the left vertex :math:`v` in :math:`B`, then its neighbors describe which stones are allowed for it. The vertices in :math:`D` do not need to have the same name as the one on the left side of :math:`B`. It is only important that the number of vertices in :math:`D` is the same as the vertices in the left side of :math:`B`. In that case the element at position :math:`i` in the ordered sequence ``enumerate_vertices(D)`` corresponds to the element of rank :math:`i` in the sequence of left side vertices of :math:`B` according to the output of ``Left, Right = bipartite_sets(B)``. Standard :py:func:`StoneFormula` is essentially equivalent to a sparse stone formula where :math:`B` is the complete graph. Parameters ---------- D : a directed acyclic graph it should be a directed acyclic graph. B : bipartite graph Raises ------ ValueError if :math:`D` is not a directed acyclic graph ValueError if :math:`B` is not a bipartite graph ValueError when size differs between :math:`D` and the left side of :math:`B` See Also -------- StoneFormula """ if not is_dag(D): raise ValueError( "Stone formulas are defined only for directed acyclic graphs.") if not has_bipartition(B): raise ValueError( "Vertices to stones mapping must be specified with a bipartite graph" ) Left, Right = bipartite_sets(B) nstones = len(Right) if len(Left) != D.order(): raise ValueError( "Formula requires the bipartite left side to match #vertices of the DAG." ) cnf = CNF() if hasattr(D, 'name'): cnf.header = "Sparse Stone formula of: " + D.name + "\nwith " + str( nstones) + " stones\n" + cnf.header else: cnf.header = "Sparse Stone formula with " + str( nstones) + " stones\n" + cnf.header # add variables in the appropriate order vertices = enumerate_vertices(D) stones = list(range(1, nstones + 1)) mapping = cnf.unary_mapping(vertices, stones, sparsity_pattern=B) stone_formula_helper(cnf, D, mapping) return cnf
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 e in B.edges(u): ssc.add_variable(var_name(*e)) for u in Left: edge_vars = [ var_name(*e) for e in B.edges(u) ] if equalities: for cls in exactly_half_ceil(edge_vars): ssc.add_clause(cls,strict=True) else: for cls in loose_majority_constraint(edge_vars): ssc.add_clause(cls,strict=True) for v in Right: edge_vars = [ var_name(*e) for e in B.edges(v) ] if equalities: for cls in exactly_half_floor(edge_vars): ssc.add_clause(cls,strict=True) else: for cls in loose_minority_constraint(edge_vars): ssc.add_clause(cls,strict=True) return ssc