예제 #1
0
def CountingPrinciple(M,p):
    """Generates the clauses for the counting matching principle.
    
    The principle claims that there is a way to partition M in sets of
    size p each.

    Arguments:
    - `M`  : size of the domain
    - `p`  : size of each class

    """
    cnf=CNF()

    # Describe the formula
    name="Counting Principle: {0} divided in parts of size {1}.".format(M,p)
    cnf.header=name+"\n"+cnf.header

    def var_name(tpl):
        return "Y_{{"+",".join("{0}".format(v) for v in tpl)+"}}"

    # Incidence lists
    incidence=[[] for _ in range(M)]
    for tpl in combinations(range(M),p):
        for i in tpl:
            incidence[i].append(tpl)
    
    # Each element of the domain is in exactly one part.
    for el in range(M):

        edge_vars = [var_name(tpl) for tpl in incidence[el]]

        for cls in CNF.equal_to_constraint(edge_vars,1):
            cnf.add_clause(cls)

    return cnf
예제 #2
0
def PerfectMatchingPrinciple(graph):
    """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.

    Arguments:
    - `graph`  : undirected graph

    """
    cnf=CNF()

    # Describe the formula
    name="Perfect Matching Principle"
    
    if hasattr(graph,'name'):
        cnf.header=name+" of graph:\n"+graph.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 graph.nodes():

        edge_vars = [var_name(u,v) for u in graph.adj[v]]

        for cls in equal_to_constraint(edge_vars,1):
            cnf.add_clause(cls)

    return cnf
예제 #3
0
def HiddenPigeonholePrinciple(pigeons, items, holes):
    """Hidden Pigeonhole Principle CNF formula

    Each pigeon has multiple items that should be fit into a hole, however each
    hole can only contain items of at most one pigeon.

    It is called hidden pigeon hole principle as it is only adding more
    constraints to the origianal pigeon hole principle, which makes it difficult
    to detect "the right" at-most-one constraints.

    Arguments:
    - `pigeons`: number of pigeons
    - `items`: number of items per pigeon
    - `holes`:   number of holes
    """
    def var(p, i, h):
        return 'p_{{{0},{1},{2}}}'.format(p, h, i)

    php = CNF()
    php.header = "hidden pigeon hole principle formula for {0} pigeons, each having {2} items ,and {1} holes\n".format(pigeons, holes, items)\
        + php.header

    for p in range(pigeons):
        for i in range(items):
            php.add_clause([(True, var(p, i, h)) for h in range(holes)])

    for h in range(holes):
        for p1 in range(pigeons):
            for p2 in range(p1):
                for i1 in range(items):
                    for i2 in range(items):
                        php.add_clause([(False, var(p1, i1, h)),
                                        (False, var(p2, i2, h))])

    return php
예제 #4
0
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
예제 #5
0
def RandomKCNF(k, n, m, seed=None, planted_assignments=[]):
    """Build a random k-CNF

    Sample :math:`m` clauses over :math:`n` variables, each of width
    :math:`k`, uniformly at random. The sampling is done without
    repetition, meaning that whenever a randomly picked clause is
    already in the CNF, it is sampled again.

    Parameters
    ----------
    k : int
       width of each clause

    n : int
       number of variables to choose from. The resulting CNF object
       will contain n variables even if some are not mentioned in the clauses.

    m : int
       number of clauses to generate

    seed : hashable object
       seed of the random generator

    planted_assignments : iterable(dict), optional 
       a set of total/partial assigments such that all clauses in the formula 
       will be satisfied by all of them.

    Returns
    -------
    a CNF object

    Raises
    ------
    ValueError
        when some paramenter is negative, or when k>n.

    """
    if seed:
        random.seed(seed)


    if n<0 or m<0 or k<0:
        raise ValueError("Parameters must be non-negatives.")

    if k>n:
        raise ValueError("Clauses cannot have more {} literals.".format(n))

    F = CNF()
    F.header = "Random {}-CNF over {} variables and {} clauses\n".format(k,n,m) + F.header

    indices = xrange(1,n+1)
    for i in indices:
        F.add_variable('x_{0}'.format(i))
    try:
        for clause in sample_clauses(k, indices, m, planted_assignments):
            F.add_clause(list(clause), strict=True)
    except ValueError:
        raise ValueError("There are fewer clauses available than the number requested")

    return F
예제 #6
0
def CountingPrinciple(M, p):
    """Generates the clauses for the counting matching principle.
    
    The principle claims that there is a way to partition M in sets of
    size p each.

    Arguments:
    - `M`  : size of the domain
    - `p`  : size of each class

    """
    cnf = CNF()

    # Describe the formula
    name = "Counting Principle: {0} divided in parts of size {1}.".format(M, p)
    cnf.header = name + "\n" + cnf.header

    def var_name(tpl):
        return "Y_{{" + ",".join("{0}".format(v) for v in tpl) + "}}"

    # Incidence lists
    incidence = [[] for _ in range(M)]
    for tpl in combinations(list(range(M)), p):
        for i in tpl:
            incidence[i].append(tpl)

    # Each element of the domain is in exactly one part.
    for el in range(M):

        edge_vars = [var_name(tpl) for tpl in incidence[el]]

        for cls in CNF.equal_to_constraint(edge_vars, 1):
            cnf.add_clause(cls)

    return cnf
예제 #7
0
def BinaryPigeonholePrinciple(pigeons,holes):
    """Binary Pigeonhole Principle CNF formula

    The pigeonhole principle claims that no M pigeons can sit in
    N pigeonholes without collision if M>N. This formula encodes the
    principle using binary strings to identify the holes.

    Parameters
    ----------
    pigeon : int 
       number of pigeons
    holes : int 
       number of holes
    """

    bphp=CNF()
    bphp.header="Binary Pigeonhole Principle for {0} pigeons and {1} holes\n".format(pigeons,holes)\
                 + bphp.header
    
    bphpgen=bphp.binary_mapping(xrange(1,pigeons+1), xrange(1,holes+1), injective = True)

    for v in bphpgen.variables():
        bphp.add_variable(v)
        
    for c in bphpgen.clauses():
        bphp.add_clause(c,strict=True)

    return bphp
예제 #8
0
 def test_opposite_literals(self):
     F = CNF()
     F.auto_add_variables = True
     F.allow_opposite_literals = True
     F.add_clause([(True, "S"), (False, "T"), (False, "S")])
     F.allow_opposite_literals = False
     self.assertRaises(ValueError, F.add_clause, [(True, "T"), (True, "V"),
                                                  (False, "T")])
예제 #9
0
 def test_opposite_literals(self):
     F=CNF()
     F.auto_add_variables = True
     F.allow_opposite_literals = True
     F.add_clause([(True,"S"),(False,"T"),(False,"S")])
     F.allow_opposite_literals = False
     self.assertRaises(ValueError, F.add_clause,
                       [(True,"T"),(True,"V"),(False,"T")])
예제 #10
0
 def test_one_clause(self) :
     opb="""\
     * #variable= 4 #constraint= 1
     *
     +1 x1 +1 x2 -1 x3 -1 x4 >= -1;
     """
     F=CNF()
     F.add_clause([(True,"a"),(True,"b"),(False,"c"),(False,"d")])
     self.assertCnfEqualsOPB(F,opb)
예제 #11
0
 def test_one_clause(self):
     opb = """\
     * #variable= 4 #constraint= 1
     *
     +1 x1 +1 x2 -1 x3 -1 x4 >= -1;
     """
     F = CNF()
     F.add_clause([(True, "a"), (True, "b"), (False, "c"), (False, "d")])
     self.assertCnfEqualsOPB(F, opb)
예제 #12
0
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
예제 #13
0
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
예제 #14
0
 def test_strict_clause_insertion(self):
     F = CNF()
     F.mode_strict()
     F.add_variable("S")
     F.add_variable("T")
     F.add_variable("U")
     self.assertTrue(len(list(F.variables())) == 3)
     F.add_clause([(True, "S"), (False, "T")])
     F.add_clause([(True, "T"), (False, "U")])
     self.assertRaises(ValueError, F.add_clause, [(True, "T"),
                                                  (False, "V")])
예제 #15
0
 def test_strict_clause_insertion(self):
     F=CNF()
     F.mode_strict()
     F.add_variable("S")
     F.add_variable("T")
     F.add_variable("U")
     self.assertTrue(len(list(F.variables()))==3)
     F.add_clause([(True,"S"),(False,"T")])
     F.add_clause([(True,"T"),(False,"U")])
     self.assertRaises(ValueError, F.add_clause,
                       [(True,"T"),(False,"V")])
예제 #16
0
def GraphIsomorphism(G1, G2):
    """Graph Isomorphism formula

    The formula is the CNF encoding of the statement that two simple
    graphs G1 and G2 are isomorphic.

    Parameters
    ----------
    G1 : networkx.Graph
        an undirected graph object
    G2 : networkx.Graph
        an undirected graph object

    Returns
    -------
    A CNF formula which is satiafiable if and only if graphs G1 and G2
    are isomorphic.

    """
    F = CNF()
    F.header = "Graph Isomorphism problem between graphs " +\
               G1.name + " and " + G2.name + "\n" + F.header
    F.mode_strict()

    U = enumerate_vertices(G1)
    V = enumerate_vertices(G2)
    var = _graph_isomorphism_var

    for (u, v) in product(U, V):
        F.add_variable(var(u, v))

    # Defined on both side
    for u in U:
        F.add_clause([(True, var(u, v)) for v in V])

    for v in V:
        F.add_clause([(True, var(u, v)) for u in U])

    # Injective on both sides
    for u in U:
        for v1, v2 in combinations(V, 2):
            F.add_clause([(False, var(u, v1)), (False, var(u, v2))])
    for v in V:
        for u1, u2 in combinations(U, 2):
            F.add_clause([(False, var(u1, v)), (False, var(u2, v))])

    # Edge consistency
    for u1, u2 in combinations(U, 2):
        for v1, v2 in combinations(V, 2):
            if G1.has_edge(u1, u2) != G2.has_edge(v1, v2):
                F.add_clause([(False, var(u1, v1)), (False, var(u2, v2))])
                F.add_clause([(False, var(u1, v2)), (False, var(u2, v1))])

    return F
예제 #17
0
파일: ramsey.py 프로젝트: prwang/cnfgen
def PythagoreanTriples(N):
    """There is a Pythagorean triples free coloring on N 
    
    The formula claims that it is possible to bicolor the numbers from
    1 to :math:`N` so that there  is no monochromatic triplet 
    :math:`(x,y,z)` so that :math:`x^2+y^2=z^2`.

    Parameters
    ----------
    N  : int
         size of the interval

    Return
    ------
    A CNF object

    Raises
    ------
    ValueError
       Parameters are not positive integers

    References
    ----------
    .. [1] M. J. Heule, O. Kullmann, and V. W. Marek. 
           Solving and verifying the boolean pythagorean triples problem via cube-and-conquer. 
           arXiv preprint arXiv:1605.00723, 2016.
    """

    ptn = CNF()

    ptn.header = dedent("""
It is possible to bicolor the numbers from
1 to {} so that there  is no monochromatic triplets
(x,y,z) such that x^2+y^2=z^2

""".format(N)) + ptn.header

    def V(i):
        return "x_{{{}}}".format(i)

    # Variables represent the coloring of the number
    for i in range(1, N + 1):
        ptn.add_variable(V(i))

    for x, y in combinations(list(range(1, N + 1)), 2):
        z = int(sqrt(x**2 + y**2))
        if z <= N and z**2 == x**2 + y**2:
            ptn.add_clause([(True, V(x)), (True, V(y)), (True, V(z))],
                           strict=True)
            ptn.add_clause([(False, V(x)), (False, V(y)), (False, V(z))],
                           strict=True)

    return ptn
예제 #18
0
 def test_auto_add_variables(self):
     F = CNF()
     F.auto_add_variables = True
     F.add_variable("S")
     F.add_variable("U")
     self.assertTrue(len(list(F.variables())) == 2)
     F.add_clause([(True, "S"), (False, "T")])
     self.assertTrue(len(list(F.variables())) == 3)
     F.auto_add_variables = False
     F.add_clause([(True, "T"), (False, "U")])
     self.assertRaises(ValueError, F.add_clause, [(True, "T"),
                                                  (False, "V")])
예제 #19
0
 def test_auto_add_variables(self):
     F=CNF()
     F.auto_add_variables = True
     F.add_variable("S")
     F.add_variable("U")
     self.assertTrue(len(list(F.variables()))==2)
     F.add_clause([(True,"S"),(False,"T")])
     self.assertTrue(len(list(F.variables()))==3)
     F.auto_add_variables = False
     F.add_clause([(True,"T"),(False,"U")])
     self.assertRaises(ValueError, F.add_clause,
                       [(True,"T"),(False,"V")])
예제 #20
0
def PythagoreanTriples(N):
    """There is a Pythagorean triples free coloring on N 
    
    The formula claims that it is possible to bicolor the numbers from
    1 to :math:`N` so that there  is no monochromatic triplet 
    :math:`(x,y,z)` so that :math:`x^2+y^2=z^2`.

    Parameters
    ----------
    N  : int
         size of the interval

    Return
    ------
    A CNF object

    Raises
    ------
    ValueError
       Parameters are not positive integers

    References
    ----------
    .. [1] M. J. Heule, O. Kullmann, and V. W. Marek. 
           Solving and verifying the boolean pythagorean triples problem via cube-and-conquer. 
           arXiv preprint arXiv:1605.00723, 2016.
    """

    ptn=CNF()

    ptn.header=dedent("""
It is possible to bicolor the numbers from
1 to {} so that there  is no monochromatic triplets
(x,y,z) such that x^2+y^2=z^2

""".format(N)) + ptn.header

    def V(i):
        return "x_{{{}}}".format(i)

    # Variables represent the coloring of the number
    for i in xrange(1,N+1):
        ptn.add_variable(V(i))

        
    for x,y in combinations(range(1,N+1),2):
        z = int(sqrt(x**2 +  y**2))
        if z <=N and z**2 == x**2 + y**2:
            ptn.add_clause([ (True, V(x)), (True, V(y)), (True, V(z))])
            ptn.add_clause([ (False,V(x)), (False,V(y)), (False,V(z))])

    return ptn
예제 #21
0
파일: subgraph.py 프로젝트: lizzij/LCProj
def BinaryCliqueFormula(G, k):
    """Test whether a graph has a k-clique.

    Given a graph :math:`G` and a non negative value :math:`k`, the
    CNF formula claims that :math:`G` contains a :math:`k`-clique.
    This formula uses the binary encoding, in the sense that the
    clique elements are indexed by strings of bits.

    Parameters
    ----------
    G : networkx.Graph
        a simple graph
    k : a non negative integer
        clique size

    Returns
    -------
    a CNF object

    """
    F = CNF()
    F.header = "Binary {0}-clique formula\n".format(k) + F.header

    clauses_gen = F.binary_mapping(xrange(1, k + 1),
                                   G.nodes(),
                                   injective=True,
                                   nondecreasing=True)

    for v in clauses_gen.variables():
        F.add_variable(v)

    for c in clauses_gen.clauses():
        F.add_clause(c, strict=True)

    for (i1, i2), (v1, v2) in product(combinations(xrange(1, k + 1), 2),
                                      combinations(G.nodes(), 2)):

        if not G.has_edge(v1, v2):
            F.add_clause(clauses_gen.forbid_image(i1, v1) +
                         clauses_gen.forbid_image(i2, v2),
                         strict=True)

    return F
예제 #22
0
def RamseyLowerBoundFormula(s, k, N):
    """Formula claiming that Ramsey number r(s,k) > N

    Arguments:
    - `s`: independent set size
    - `k`: clique size
    - `N`: vertices
    """

    ram = CNF()
    ram.mode_strict()

    ram.header = dedent("""\
        CNF encoding of the claim that there is a graph of %d vertices
        with no independent set of size %d and no clique of size %d
        """ % (N, s, k)) + ram.header

    #
    # One variable per edge (indices are ordered)
    #
    for edge in combinations(range(1, N+1), 2):
        ram.add_variable('e_{{{0},{1}}}'.format(*edge))

    #
    # No independent set of size s
    #
    for vertex_set in combinations(range(1, N+1), s):
        clause = []
        for edge in combinations(vertex_set, 2):
            clause += [(True, 'e_{{{0},{1}}}'.format(*edge))]
        ram.add_clause(clause)

    #
    # No clique of size k
    #
    for vertex_set in combinations(range(1, N+1), k):
        clause = []
        for edge in combinations(vertex_set, 2):
            clause += [(False, 'e_{{{0},{1}}}'.format(*edge))]
        ram.add_clause(clause)

    return ram
예제 #23
0
def RamseyLowerBoundFormula(s,k,N):
    """Formula claiming that Ramsey number r(s,k) > N

    Arguments:
    - `s`: independent set size
    - `k`: clique size
    - `N`: vertices
    """

    ram=CNF()
    ram.mode_strict()

    ram.header=dedent("""\
        CNF encoding of the claim that there is a graph of %d vertices
        with no independent set of size %d and no clique of size %d
        """ % (N,s,k)) + ram.header

    #
    # One variable per edge (indices are ordered)
    #
    for edge in combinations(xrange(1,N+1),2):
        ram.add_variable('e_{{{0},{1}}}'.format(*edge))
    
    #
    # No independent set of size s
    #
    for vertex_set in combinations(xrange(1,N+1),s):
        clause=[]
        for edge in combinations(vertex_set,2):
            clause += [(True,'e_{{{0},{1}}}'.format(*edge))]
        ram.add_clause(clause)

    #
    # No clique of size k
    #
    for vertex_set in combinations(xrange(1,N+1),k):
        clause=[]
        for edge in combinations(vertex_set,2):
            clause+=[(False,'e_{{{0},{1}}}'.format(*edge))]
        ram.add_clause(clause)

    return ram
예제 #24
0
def PebblingFormula(digraph):
    """Pebbling formula

    Build a pebbling formula from the directed graph. If the graph has
    an `ordered_vertices` attribute, then it is used to enumerate the
    vertices (and the corresponding variables).

    Arguments:
    - `digraph`: directed acyclic graph.
    """
    if not is_dag(digraph):
        raise ValueError(
            "Pebbling formula is defined only for directed acyclic graphs")

    peb = CNF()
    peb.mode_unchecked()

    if hasattr(digraph, 'name'):
        peb.header = "Pebbling formula of: "+digraph.name+"\n\n"+peb.header
    else:
        peb.header = "Pebbling formula\n\n"+peb.header

    # add variables in the appropriate order
    vertices = enumerate_vertices(digraph)
    position = dict((v, i) for (i, v) in enumerate(vertices))

    for v in vertices:
        peb.add_variable(
            v, description="There is a pebble on vertex ${}$".format(v))

    # add the clauses
    for v in vertices:

        # If predecessors are pebbled the vertex must be pebbled
        pred = sorted(digraph.predecessors(v), key=lambda x: position[x])
        peb.add_clause([(False, p) for p in pred]+[(True, v)])

        if digraph.out_degree(v) == 0:  # the sink
            peb.add_clause([(False, v)])

    return peb
예제 #25
0
def BinaryCliqueFormula(G,k):
    """Test whether a graph has a k-clique.

    Given a graph :math:`G` and a non negative value :math:`k`, the
    CNF formula claims that :math:`G` contains a :math:`k`-clique.
    This formula uses the binary encoding, in the sense that the
    clique elements are indexed by strings of bits.

    Parameters
    ----------
    G : networkx.Graph
        a simple graph
    k : a non negative integer
        clique size

    Returns
    -------
    a CNF object

    """
    F=CNF()
    F.header="Binary {0}-clique formula\n".format(k) + F.header
    
    clauses_gen=F.binary_mapping(xrange(1,k+1), G.nodes(),
                                 injective = True,
                                 nondecreasing = True)

    for v in clauses_gen.variables():
        F.add_variable(v)
        
    for c in clauses_gen.clauses():
        F.add_clause(c,strict=True)

    for (i1,i2),(v1,v2) in product(combinations(xrange(1,k+1),2),
                                   combinations(G.nodes(),2)):
    
        if not G.has_edge(v1,v2):
            F.add_clause( clauses_gen.forbid_image(i1,v1) + clauses_gen.forbid_image(i2,v2),strict=True)

    return F
예제 #26
0
def PebblingFormula(digraph):
    """Pebbling formula

    Build a pebbling formula from the directed graph. If the graph has
    an `ordered_vertices` attribute, then it is used to enumerate the
    vertices (and the corresponding variables).

    Arguments:
    - `digraph`: directed acyclic graph.
    """
    if not is_dag(digraph):
        raise ValueError("Pebbling formula is defined only for directed acyclic graphs")

    peb=CNF()
    peb.mode_unchecked()

    if hasattr(digraph,'name'):
        peb.header="Pebbling formula of: "+digraph.name+"\n\n"+peb.header
    else:
        peb.header="Pebbling formula\n\n"+peb.header

    # add variables in the appropriate order
    vertices=enumerate_vertices(digraph)
    position=dict((v,i) for (i,v) in enumerate(vertices))

    for v in vertices:
        peb.add_variable(v,description="There is a pebble on vertex ${}$".format(v))

    # add the clauses
    for v in vertices:

        # If predecessors are pebbled the vertex must be pebbled
        pred=sorted(digraph.predecessors(v),key=lambda x:position[x])
        peb.add_clause([(False,p) for p in pred]+[(True,v)])

        if digraph.out_degree(v)==0: #the sink
            peb.add_clause([(False,v)])

    return peb
예제 #27
0
파일: tseitin.py 프로젝트: prwang/cnfgen
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
예제 #28
0
def VertexCover(G,d):
    F=CNF()

    def D(v):
        return "x_{{{0}}}".format(v)

    def N(v):
        return tuple(sorted([ e for e in G.edges(v) ]))

    # Fix the vertex order
    V=enumerate_vertices(G)

    # Create variables
    for v in V:
        F.add_variable(D(v))

    # Not too many true variables
    F.add_less_or_equal([D(v) for v in V],d)

    # Every edge must have a true D variable
    for e in G.edges():
        F.add_clause([ (True,D(v)) for v in e])
        
    return F
예제 #29
0
def DominatingSetOPB(G,d,tiling,seed):
    F=CNF()

    def D(v):
        return "x_{{{0}}}".format(v)

    def N(v):
        return tuple(sorted([ v ] + [ u for u in G.neighbors(v) ]))

    # Fix the vertex order
    V=enumerate_vertices(G)

    #avgdegree=sum(len(set(N(v))) for v in V)/len(V)
    #d=len(V)/(avgdegree+1)
    
    # Create variables
    for v in V:
        F.add_variable(D(v))

    # Not too many true variables
    if not tiling:
        F.add_less_or_equal([D(v) for v in V],d)

    # Every neighborhood must have a true D variable
    neighborhoods = sorted( set(N(v) for v in V) )
    for N in neighborhoods:
        if tiling:
            F.add_equal_to([D(v) for v in N], 1)
        else:
            F.add_clause([(True,D(v)) for v in N])

    # Set some vertex to true
    if seed:
        F.add_clause([(True,D(V[0]))])
        
    return F
예제 #30
0
def VertexCover(G, d):
    F = CNF()

    def D(v):
        return "x_{{{0}}}".format(v)

    def N(v):
        return tuple(sorted([e for e in G.edges(v)]))

    # Fix the vertex order
    V = enumerate_vertices(G)

    # Create variables
    for v in V:
        F.add_variable(D(v))

    # Not too many true variables
    F.add_less_or_equal([D(v) for v in V], d)

    # Every edge must have a true D variable
    for e in G.edges():
        F.add_clause([(True, D(v)) for v in e])

    return F
예제 #31
0
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)]

        for cls in CNF.equal_to_constraint(edge_vars, 1):
            cnf.add_clause(cls)

    return cnf
예제 #32
0
def CliqueColoring(n,k,c):
    r"""Clique-coloring CNF formula 

    The formula claims that a graph :math:`G` with :math:`n` vertices
    simultaneously contains a clique of size :math:`k` and a coloring
    of size :math:`c`.

    If :math:`k = c + 1` then the formula is clearly unsatisfiable,
    and it is the only known example of a formula hard for cutting
    planes proof system. [1]_

    Variables :math:`e_{u,v}` to encode the edges of the graph.
    
    Variables :math:`q_{i,v}` encode a function from :math:`[k]` to
    :math:`[n]` that represents a clique.
    
    Variables :math:`r_{v,\ell}` encode a function from :math:`[n]` to
    :math:`[c]` that represents a coloring.
     
    Parameters
    ----------
    n : number of vertices in the graph
    k : size of the clique
    c : size of the coloring

    Returns
    -------
    A CNF object

    References
    ----------
    .. [1] Pavel Pudlak.
           Lower bounds for resolution and cutting plane proofs and
           monotone computations.
           Journal of Symbolic Logic (1997)

    """

    def E(u,v):
        "Name of an edge variable"
        return 'e_{{{0},{1}}}'.format(min(u,v),max(u,v))
    
    def Q(i,v):
        "Name of an edge variable"
        return 'q_{{{0},{1}}}'.format(i,v)

    def R(v,ell):
        "Name of an coloring variable"
        return 'r_{{{0},{1}}}'.format(v,ell)
    
    formula=CNF()
    formula.header="There is a graph of {0} vertices with a {1}-clique".format(n,k)+\
        " and a {0}-coloring\n\n".format(c)\
        + formula.header

    # Edge variables
    for u in range(1,n+1):
        for v in range(u+1,n+1):
            formula.add_variable(E(u,v))
    # Clique encoding variables
    for i in range(1,k+1):
        for v in range(1,n+1):
            formula.add_variable(Q(i,v))
    # Coloring encoding variables
    for v in range(1,n+1):
        for ell in range(1,c+1):
            formula.add_variable(R(v,ell))

    # some vertex is i'th member of clique
    for k in range(1,k+1):
        for cl in CNF.equal_to_constraint([Q(k,v) for v in range(1,n+1)], 1):
            formula.add_clause(cl,strict=True)

    # clique members are connected by edges
    for v in range(1,n+1):
        for i,j in combinations(list(range(1,k+1)),2):
            formula.add_clause([(False, Q(i,v)), (False, Q(j,v))], strict=True)
    for u,v in combinations(list(range(1,n+1)),2):
        for i,j in permutations(list(range(1,k+1)),2):
            formula.add_clause([(True, E(u,v)), (False, Q(i,u)), (False, Q(j,v))],
                               strict=True)

    # every vertex v has exactly one colour
    for v in range(1,n+1):
        for cl in CNF.equal_to_constraint([R(v,ell) for ell in range(1,c+1)], 1):
            formula.add_clause(cl,strict=True)

    # neighbours have distinct colours
    for u,v in combinations(list(range(1,n+1)),2):
        for ell in range(1,c+1):
            formula.add_clause([(False, E(u,v)), (False, R(u,ell)), (False, R(v,ell))],
                               strict=True)
    return formula
예제 #33
0
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
예제 #34
0
def SubgraphFormula(graph,templates):
    """Test whether a graph contains one of the templates.

    Given a graph :math:`G` and a sequence of template graphs
    :math:`H_1`, :math:`H_2`, ..., :math:`H_t`, the CNF formula claims
    that :math:`G` contains an isomorphic copy of at least one of the
    template graphs.

    E.g. when :math:`H_1` is the complete graph of :math:`k` vertices
    and it is the only template, the formula claims that :math:`G`
    contains a :math:`k`-clique.

    Parameters
    ----------
    graph : networkx.Graph
        a simple graph
    templates : list-like object
        a sequence of graphs.

    Returns
    -------
    a CNF object
    """

    F=CNF()

    # One of the templates is chosen to be the subgraph
    if len(templates)==0:
        return F
    elif len(templates)==1:
        selectors=[]
    elif len(templates)==2:
        selectors=['c']
    else:
        selectors=['c_{{{}}}'.format(i) for i in range(len(templates))]

    for s in selectors:
        F.add_variable(s)
        
    if len(selectors)>1:

        # Exactly one of the graphs must be selected as subgraph
        F.add_clause([(True,v) for v in selectors],strict=True)

        for (a,b) in combinations(selectors):
            F.add_clause( [ (False,a), (False,b) ], strict=True )

    # comment the formula accordingly
    if len(selectors)>1:
        F.header=dedent("""\
                 CNF encoding of the claim that a graph contains one among
                 a family of {0} possible subgraphs.
                 """.format(len(templates))) + F.header
    else:
        F.header=dedent("""\
                 CNF encoding of the claim that a graph contains an induced
                 copy of a subgraph.
                 """.format(len(templates)))  + F.header

    # A subgraph is chosen
    N=graph.order()
    k=max([s.order() for s in templates])

    for i,j in product(range(k),range(N)):
        F.add_variable("S_{{{0}}}{{{1}}}".format(i,j))

    # each vertex has an image...
    for i in range(k):
        F.add_clause([(True,"S_{{{0}}}{{{1}}}".format(i,j)) for j in range(N)],strict=True)

    # ...and exactly one
    for i,(a,b) in product(range(k),combinations(range(N),2)):
        F.add_clause([(False,"S_{{{0}}}{{{1}}}".format(i,a)),
                      (False,"S_{{{0}}}{{{1}}}".format(i,b))  ], strict = True)

 
    # Mapping is strictly monotone increasing (so it is also injective)
    localmaps = product(combinations(range(k),2),
                        combinations_with_replacement(range(N),2))

    for (a,b),(i,j) in localmaps:
        F.add_clause([(False,"S_{{{0}}}{{{1}}}".format(min(a,b),max(i,j))),
                      (False,"S_{{{0}}}{{{1}}}".format(max(a,b),min(i,j)))  ],strict=True)


    # The selectors choose a template subgraph.  A mapping must map
    # edges to edges and non-edges to non-edges for the active
    # template.

    if len(templates)==1:

        activation_prefixes = [[]]

    elif len(templates)==2:

        activation_prefixes = [[(True,selectors[0])],[(False,selectors[0])]]

    else:
        activation_prefixes = [[(True,v)] for v in selectors]


    # maps must preserve the structure of the template graph
    gV = graph.nodes()

    for i in range(len(templates)):


        k  = templates[i].order()
        tV = templates[i].nodes()

        localmaps = product(combinations(range(k),2),
                            combinations(range(N),2))


        for (i1,i2),(j1,j2) in localmaps:

            # check if this mapping is compatible
            tedge=templates[i].has_edge(tV[i1],tV[i2])
            gedge=graph.has_edge(gV[j1],gV[j2])
            if tedge == gedge: continue

            # if it is not, add the corresponding
            F.add_clause(activation_prefixes[i] + \
                         [(False,"S_{{{0}}}{{{1}}}".format(min(i1,i2),min(j1,j2))),
                          (False,"S_{{{0}}}{{{1}}}".format(max(i1,i2),max(j1,j2))) ],strict=True)

    return F
예제 #35
0
 def test_clause_number(self):
     F = CNF()
     F.add_clause([(False, 'x')])
     F.add_clause([(True, 'x'), (False, 'y')])
     self.assertEqual(len(F), len(list(F)))
예제 #36
0
 def test_clause_number(self) :
     F=CNF()
     F.add_clause([(False, 'x')])
     F.add_clause([(True, 'x'), (False, 'y')])
     self.assertEqual(len(F),len(list(F)))
예제 #37
0
def PigeonholePrinciple(pigeons,holes,functional=False,onto=False):
    """Pigeonhole Principle CNF formula

    The pigeonhole  principle claims  that no M  pigeons can sit  in N
    pigeonholes  without collision  if M>N.   The  counterpositive CNF
    formulation  requires  such mapping  to  be  satisfied. There  are
    different  variants of this  formula, depending  on the  values of
    `functional` and `onto` argument.

    - PHP: pigeon can sit in multiple holes
    - FPHP: each pigeon sits in exactly one hole
    - onto-PHP: pigeon can  sit in multiple holes, every  hole must be
                covered.
    - Matching: one-to-one bijection between pigeons and holes.

    Arguments:
    - `pigeon`: number of pigeons
    - `hole`:   number of holes
    - `functional`: add clauses to enforce at most one hole per pigeon
    - `onto`: add clauses to enforce that any hole must have a pigeon

    >>> print(PigeonholePrinciple(4,3).dimacs(export_header=False))
    p cnf 12 22
    1 2 3 0
    4 5 6 0
    7 8 9 0
    10 11 12 0
    -1 -4 0
    -1 -7 0
    -1 -10 0
    -4 -7 0
    -4 -10 0
    -7 -10 0
    -2 -5 0
    -2 -8 0
    -2 -11 0
    -5 -8 0
    -5 -11 0
    -8 -11 0
    -3 -6 0
    -3 -9 0
    -3 -12 0
    -6 -9 0
    -6 -12 0
    -9 -12 0
    """

    def var_name(p,h):
        return 'p_{{{0},{1}}}'.format(p,h)
    
    if functional:
        if onto:
            formula_name="Matching"
        else:
            formula_name="Functional pigeonhole principle"
    else:
        if onto:
            formula_name="Onto pigeonhole principle"
        else:
            formula_name="Pigeonhole principle"
            
    php=CNF()
    php.header="{0} formula for {1} pigeons and {2} holes\n".format(formula_name,pigeons,holes)\
        + php.header

    for p in xrange(1,pigeons+1):
        for h in xrange(1,holes+1):
            php.add_variable(var_name(p,h))
    
    clauses=php.unary_mapping(
        xrange(1,pigeons+1),
        xrange(1,holes+1),
        var_name,
        complete = True,
        injective = True,
        functional = functional,
        surjective = onto)
    for c in clauses:
        php.add_clause(c,strict=True)

    return php
예제 #38
0
def DominatingSet(G, d, alternative=False):
    r"""Generates the clauses for a dominating set for G of size <= d

    The formula encodes the fact that the graph :math:`G` has
    a dominating set of size :math:`d`. This means that it is possible
    to pick at most :math:`d` vertices in :math:`V(G)` so that all remaining
    vertices have distance at most one from the selected ones.

    Parameters
    ----------
    G : networkx.Graph
        a simple undirected graph
    d : a positive int
        the size limit for the dominating set
    alternative : bool
        use an alternative construction that 
        is provably hard from resolution proofs.

    Returns
    -------
    CNF
       the CNF encoding for dominating of size :math:`\leq d` for graph :math:`G`

    """
    F = CNF()

    if not isinstance(d, int) or d < 1:
        ValueError("Parameter \"d\" is expected to be a positive integer")

    # Describe the formula
    name = "{}-dominating set".format(d)

    if hasattr(G, 'name'):
        F.header = name + " of graph:\n" + G.name + ".\n\n" + F.header
    else:
        F.header = name + ".\n\n" + F.header

    # Fix the vertex order
    V = enumerate_vertices(G)

    def D(v):
        return "x_{{{0}}}".format(v)

    def M(v, i):
        return "g_{{{0},{1}}}".format(v, i)

    def N(v):
        return tuple(sorted([v] + [u for u in G.neighbors(v)]))

    # Create variables
    for v in V:
        F.add_variable(D(v))
    for i, v in product(list(range(1, d + 1)), V):
        F.add_variable(M(v, i))

    # No two (active) vertices map to the same index
    if alternative:
        for u, v in combinations(V, 2):
            for i in range(1, d + 1):
                F.add_clause([(False, D(u)), (False, D(v)), (False, M(u, i)),
                              (False, M(v, i))])
    else:
        for i in range(1, d + 1):
            for c in CNF.less_or_equal_constraint([M(v, i) for v in V], 1):
                F.add_clause(c)

    # (Active) Vertices in the sequence are not repeated
    if alternative:
        for v in V:
            for i, j in combinations(list(range(1, d + 1)), 2):
                F.add_clause([(False, D(v)), (False, M(v, i)), (False, M(v,
                                                                         j))])
    else:
        for i, j in combinations_with_replacement(list(range(1, d + 1)), 2):
            i, j = min(i, j), max(i, j)
            for u, v in combinations(V, 2):
                u, v = max(u, v), min(u, v)
                F.add_clause([(False, M(u, i)), (False, M(v, j))])

    # D(v) = M(v,1) or M(v,2) or ... or M(v,d)
    if not alternative:
        for i, v in product(list(range(1, d + 1)), V):
            F.add_clause([(False, M(v, i)), (True, D(v))])
    for v in V:
        F.add_clause([(False, D(v))] + [(True, M(v, i))
                                        for i in range(1, d + 1)])

    # Every neighborhood must have a true D variable
    neighborhoods = sorted(set(N(v) for v in V))
    for N in neighborhoods:
        F.add_clause([(True, D(v)) for v in N])

    return F
예제 #39
0
def VertexCover(G,k, alternative = False):
    r"""Generates the clauses for a vertex cover for G of size <= k

    Parameters
    ----------
    G : networkx.Graph
        a simple undirected graph
    k : a positive int
        the size limit for the vertex cover

    Returns
    -------
    CNF
       the CNF encoding for vertex cover of size :math:`\leq k` for graph :math:`G`

    """
    F=CNF()

    if not isinstance(k,int) or k<1:
        ValueError("Parameter \"k\" is expected to be a positive integer")

    # Describe the formula
    name="{}-vertex cover".format(k)

    if hasattr(G,'name'):
        F.header=name+" of graph:\n"+G.name+".\n\n"+F.header
    else:
        F.header=name+".\n\n"+F.header

    # Fix the vertex order
    V=enumerate_vertices(G)
    E=enumerate_edges(G)

    def D(v):
        return "x_{{{0}}}".format(v)

    def M(v,i):
        return "g_{{{0},{1}}}".format(v,i)

    def N(v):
        return tuple(sorted([ v ] + [ u for u in G.neighbors(v) ]))

    # Create variables
    for v in V:
        F.add_variable(D(v))
    for i,v in product(range(1,k+1),V):
        F.add_variable(M(v,i))

    # No two (active) vertices map to the same index
    for i in range(1,k+1):
        for c in CNF.less_or_equal_constraint([M(v,i) for v in V],1):
            F.add_clause(c)

    # (Active) Vertices in the sequence are not repeated
    for i,j in combinations_with_replacement(range(1,k+1),2):
        i,j = min(i,j),max(i,j)
        for u,v in combinations(V,2):
            u,v = max(u,v),min(u,v)
            F.add_clause([(False,M(u,i)),(False,M(v,j))])

    # D(v) = M(v,1) or M(v,2) or ... or M(v,k)
    for i,v in product(range(1,k+1),V):
        F.add_clause([(False,M(v,i)),(True,D(v))])
    for v in V:
        F.add_clause([(False,D(v))] + [(True,M(v,i)) for i in range(1,k+1)])

    # Every neighborhood must have a true D variable
    for v1,v2 in E:
        F.add_clause([(True,D(v1)), (True,D(v2))])

    return F
예제 #40
0
def CliqueColoring(n,k,c):
    r"""Clique-coloring CNF formula 

    The formula claims that a graph :math:`G` with :math:`n` vertices
    simultaneously contains a clique of size :math:`k` and a coloring
    of size :math:`c`.

    If :math:`k = c + 1` then the formula is clearly unsatisfiable,
    and it is the only known example of a formula hard for cutting
    planes proof system. [1]_

    Variables :math:`e_{u,v}` to encode the edges of the graph.
    
    Variables :math:`q_{i,v}` encode a function from :math:`[k]` to
    :math:`[n]` that represents a clique.
    
    Variables :math:`r_{v,\ell}` encode a function from :math:`[n]` to
    :math:`[c]` that represents a coloring.
     
    Parameters
    ----------
    n : number of vertices in the graph
    k : size of the clique
    c : size of the coloring

    Returns
    -------
    A CNF object

    References
    ----------
    .. [1] Pavel Pudlak.
           Lower bounds for resolution and cutting plane proofs and
           monotone computations.
           Journal of Symbolic Logic (1997)

    """

    def E(u,v):
        "Name of an edge variable"
        return 'e_{{{0},{1}}}'.format(min(u,v),max(u,v))
    
    def Q(i,v):
        "Name of an edge variable"
        return 'q_{{{0},{1}}}'.format(i,v)

    def R(v,ell):
        "Name of an coloring variable"
        return 'r_{{{0},{1}}}'.format(v,ell)
    
    formula=CNF()
    formula.mode_strict()
    formula.header="There is a graph of {0} vertices with a {1}-clique".format(n,k)+\
        " and a {0}-coloring\n\n".format(c)\
        + formula.header

    # Edge variables
    for u in range(1,n+1):
        for v in range(u+1,n+1):
            formula.add_variable(E(u,v))
    # Clique encoding variables
    for i in range(1,k+1):
        for v in range(1,n+1):
            formula.add_variable(Q(i,v))
    # Coloring encoding variables
    for v in range(1,n+1):
        for ell in range(1,c+1):
            formula.add_variable(R(v,ell))

    # some vertex is i'th member of clique
    formula.mode_strict()
    for k in range(1,k+1):
        formula.add_equal_to([Q(k,v) for v in range(1,n+1)], 1)

    # clique members are connected by edges
    for v in range(1,n+1):
        for i,j in combinations(range(1,k+1),2):
            formula.add_clause([(False, Q(i,v)), (False, Q(j,v))])
    for u,v in combinations(range(1,n+1),2):
        for i,j in permutations(range(1,k+1),2):
            formula.add_clause([(True, E(u,v)), (False, Q(i,u)), (False, Q(j,v))])

    # every vertex v has exactly one colour
    for v in range(1,n+1):
        formula.add_equal_to([R(v,ell) for ell in range(1,c+1)], 1)

    # neighbours have distinct colours
    for u,v in combinations(range(1,n+1),2):
        for ell in range(1,c+1):
            formula.add_clause([(False, E(u,v)), (False, R(u,ell)), (False, R(v,ell))])
    return formula
예제 #41
0
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
예제 #42
0
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
예제 #43
0
def StoneFormula(D,nstones):
    """Stone formulas

    The stone formulas have been introduced in [2]_ and generalized in
    [1]_. They are one of the classic examples that separate regular
    resolutions from general resolution [1]_.

    A \"Stones formula\" from a directed acyclic graph :math:`D`
    claims that each vertex of the graph is associated with one on
    :math:`s` stones (not necessarily in an injective way).
    In particular for each vertex :math:`v` in :math:`V(D)` and each
    stone :math:`j` we have a variable :math:`P_{v,j}` that claims
    that stone :math:`j` is associated to vertex :math:`v`.

    Each stone can be either red or blue, and not both.
    The propositional variable :math:`R_j` if true when the stone
    :math:`j` is red and false otherwise.

    The clauses of the formula encode the following constraints.
    If a stone is on a source vertex (i.e. a vertex with no incoming
    edges), then it must be red. If all stones on the predecessors of
    a vertex are red, then the stone of the vertex itself must be red.

    The formula furthermore enforces that the stones on the sinks
    (i.e. vertices with no outgoing edges) are blue.

    .. note:: The exact formula structure depends by the graph and on
              its topological order, which is determined by the
              ``enumerate_vertices(D)``.

    Parameters
    ----------
    D : a directed acyclic graph
        it should be a directed acyclic graph.
    nstones : int
       the number of stones.

    Raises
    ------
    ValueError
       if :math:`D` is not a directed acyclic graph
    
    ValueError
       if the number of stones is negative

    References
    ----------
    .. [1] M. Alekhnovich, J. Johannsen, T. Pitassi and A. Urquhart
    	   An Exponential Separation between Regular and General Resolution.
           Theory of Computing (2007)
    .. [2] R. Raz and P. McKenzie
           Separation of the monotone NC hierarchy.
           Combinatorica (1999)

    """
    if not is_dag(D):
        raise ValueError("Stone formulas are defined only for directed acyclic graphs.")
    
    if nstones<0:
        raise ValueError("There must be at least one stone.")

    cnf = CNF()
    cnf.mode_unchecked()

    if hasattr(D, 'name'):
        cnf.header = "Stone formula of: " + D.name + "\nwith " + str(nstones) + " stones\n" + cnf.header
    else:
        cnf.header = "Stone formula with " + str(nstones) + " stones\n" + cnf.header

    # Add variables in the appropriate order
    vertices=enumerate_vertices(D)
    position=dict((v,i) for (i,v) in enumerate(vertices))
    stones=range(1,nstones+1)

    # Caching variable names
    color_vn = {}
    stone_vn = {}
    
    # Stones->Vertices variables
    for v in vertices:
        for j in stones:
            stone_vn[(v,j)] = "P_{{{0},{1}}}".format(v,j) 
            cnf.add_variable(stone_vn[(v,j)],
                             description="Stone ${1}$ on vertex ${0}$".format(v,j))

    # Color variables
    for j in stones:
        color_vn[j] = "R_{{{0}}}".format(j)
        cnf.add_variable(color_vn[j],
                         description="Stone ${}$ is red".format(j))
    
    # Each vertex has some stone
    for v in vertices:
        cnf.add_clause([(True,stone_vn[(v,j)]) for j in stones])
        
    # If predecessors have red stones, the sink must have a red stone
    for v in vertices:
        for j in stones:
            pred=sorted(D.predecessors(v),key=lambda x:position[x])
            for stones_tuple in product([s for s in stones if s!=j],repeat=len(pred)):
                cnf.add_clause([(False, stone_vn[(p,s)]) for (p,s) in zip(pred,stones_tuple)] +
                               [(False, stone_vn[(v,j)])] +
                               [(False, color_vn[s]) for s in _uniqify_list(stones_tuple)] +
                               [(True,  color_vn[j])])
        
        if D.out_degree(v)==0: #the sink
            for j in stones:
                cnf.add_clause([ (False,stone_vn[(v,j)]), (False,color_vn[j])])

    return cnf
예제 #44
0
def DominatingSet(G,d, alternative = False):
    r"""Generates the clauses for a dominating set for G of size <= d

    The formula encodes the fact that the graph :math:`G` has
    a dominating set of size :math:`d`. This means that it is possible
    to pick at most :math:`d` vertices in :math:`V(G)` so that all remaining
    vertices have distance at most one from the selected ones.

    Parameters
    ----------
    G : networkx.Graph
        a simple undirected graph
    d : a positive int
        the size limit for the dominating set
    alternative : bool
        use an alternative construction that 
        is provably hard from resolution proofs.

    Returns
    -------
    CNF
       the CNF encoding for dominating of size :math:`\leq d` for graph :math:`G`

    """
    F=CNF()

    if not isinstance(d,int) or d<1:
        ValueError("Parameter \"d\" is expected to be a positive integer")
    
    # Describe the formula
    name="{}-dominating set".format(d)
    
    if hasattr(G,'name'):
        F.header=name+" of graph:\n"+G.name+".\n\n"+F.header
    else:
        F.header=name+".\n\n"+F.header

    # Fix the vertex order
    V=enumerate_vertices(G)
    

    def D(v):
        return "x_{{{0}}}".format(v)

    def M(v,i):
        return "g_{{{0},{1}}}".format(v,i)

    def N(v):
        return tuple(sorted([ v ] + [ u for u in G.neighbors(v) ]))
    
    # Create variables
    for v in V:
        F.add_variable(D(v))
    for i,v in product(range(1,d+1),V):
        F.add_variable(M(v,i))
    
    # No two (active) vertices map to the same index
    if alternative:
        for u,v in combinations(V,2):
            for i in range(1,d+1):
                F.add_clause( [ (False,D(u)),(False,D(v)), (False,M(u,i)), (False,M(v,i))    ])
    else:
        for i in range(1,d+1):
            for c in CNF.less_or_equal_constraint([M(v,i) for v in V],1):
                F.add_clause(c)
                
    # (Active) Vertices in the sequence are not repeated
    if alternative:
        for v in V:
            for i,j in combinations(range(1,d+1),2):
                F.add_clause([(False,D(v)),(False,M(v,i)),(False,M(v,j))])
    else:
        for i,j in combinations_with_replacement(range(1,d+1),2):
            i,j = min(i,j),max(i,j)
            for u,v in combinations(V,2):
                u,v = max(u,v),min(u,v)
                F.add_clause([(False,M(u,i)),(False,M(v,j))])

    # D(v) = M(v,1) or M(v,2) or ... or M(v,d)        
    if not alternative:
        for i,v in product(range(1,d+1),V):
            F.add_clause([(False,M(v,i)),(True,D(v))])
    for v in V:
        F.add_clause([(False,D(v))] + [(True,M(v,i)) for i in range(1,d+1)])
    
        
    # Every neighborhood must have a true D variable
    neighborhoods = sorted( set(N(v) for v in V) )
    for N in neighborhoods:
        F.add_clause([ (True,D(v)) for v in N])
        
    return F
예제 #45
0
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
예제 #46
0
def SubgraphFormula(graph,templates, symmetric=False):
    """Test whether a graph contains one of the templates.

    Given a graph :math:`G` and a sequence of template graphs
    :math:`H_1`, :math:`H_2`, ..., :math:`H_t`, the CNF formula claims
    that :math:`G` contains an isomorphic copy of at least one of the
    template graphs.

    E.g. when :math:`H_1` is the complete graph of :math:`k` vertices
    and it is the only template, the formula claims that :math:`G`
    contains a :math:`k`-clique.

    Parameters
    ----------
    graph : networkx.Graph
        a simple graph

    templates : list-like object
        a sequence of graphs.

    symmetric:
        all template graphs are symmetric wrt permutations of
        vertices. This allows some optimization in the search space of
        the assignments.

    induce: 
        force the subgraph to be induced (i.e. no additional edges are allowed)


    Returns
    -------
    a CNF object

    """

    F=CNF()

    
    # One of the templates is chosen to be the subgraph
    if len(templates)==0:
        return F
    elif len(templates)==1:
        selectors=[]
    elif len(templates)==2:
        selectors=['c']
    else:
        selectors=['c_{{{}}}'.format(i) for i in range(len(templates))]

    for s in selectors:
        F.add_variable(s)
        
    if len(selectors)>1:
        for cls in F.equal_to_constraint(selectors,1):
            F.add_clause( cls , strict=True )

    # comment the formula accordingly
    if len(selectors)>1:
        F.header=dedent("""\
                 CNF encoding of the claim that a graph contains one among
                 a family of {0} possible subgraphs.
                 """.format(len(templates))) + F.header
    else:
        F.header=dedent("""\
                 CNF encoding of the claim that a graph contains an induced
                 copy of a subgraph.
                 """.format(len(templates)))  + F.header

    # A subgraph is chosen
    N=graph.order()
    k=max([s.order() for s in templates])

    var_name = lambda i,j: "S_{{{0},{1}}}".format(i,j)

    for i,j in product(range(k),range(N)):
        F.add_variable( var_name(i,j) )

    if symmetric:
        gencls = F.unary_mapping(range(k),range(N),var_name=var_name,
                                 functional=True,injective=True,
                                 nondecreasing=True)
    else:
        gencls = F.unary_mapping(range(k),range(N),var_name=var_name,
                                 functional=True,injective=True)
        
    for cls in gencls:
        F.add_clause( cls, strict=True )
        
    # The selectors choose a template subgraph.  A mapping must map
    # edges to edges and non-edges to non-edges for the active
    # template.

    if len(templates)==1:

        activation_prefixes = [[]]

    elif len(templates)==2:

        activation_prefixes = [[(True,selectors[0])],[(False,selectors[0])]]

    else:
        activation_prefixes = [[(True,v)] for v in selectors]


    # maps must preserve the structure of the template graph
    gV = enumerate_vertices(graph)

    for i in range(len(templates)):

        k  = templates[i].order()
        tV = enumerate_vertices(templates[i])

        if symmetric:
            # Using non-decreasing map to represent a subset
            localmaps = product(combinations(range(k),2),
                                combinations(range(N),2))
        else:
            localmaps = product(combinations(range(k),2),
                                permutations(range(N),2))
       

        for (i1,i2),(j1,j2) in localmaps:

            # check if this mapping is compatible
            tedge=templates[i].has_edge(tV[i1],tV[i2])
            gedge=graph.has_edge(gV[j1],gV[j2])
            if tedge == gedge:
                continue

            # if it is not, add the corresponding
            F.add_clause(activation_prefixes[i] + \
                         [(False,var_name(i1,j1)),
                          (False,var_name(i2,j2)) ],strict=True)

    return F
예제 #47
0
def RandomKCNF(k, n, m, seed=None, planted_assignments=[]):
    """Build a random k-CNF

    Sample :math:`m` clauses over :math:`n` variables, each of width
    :math:`k`, uniformly at random. The sampling is done without
    repetition, meaning that whenever a randomly picked clause is
    already in the CNF, it is sampled again.

    Parameters
    ----------
    k : int
       width of each clause

    n : int
       number of variables to choose from. The resulting CNF object
       will contain n variables even if some are not mentioned in the clauses.

    m : int
       number of clauses to generate

    seed : hashable object
       seed of the random generator

    planted_assignments : iterable(dict), optional 
       a set of total/partial assigments such that all clauses in the formula 
       will be satisfied by all of them.

    Returns
    -------
    a CNF object

    Raises
    ------
    ValueError
        when some paramenter is negative, or when k>n.

    """
    if seed:
        random.seed(seed)

    if n < 0 or m < 0 or k < 0:
        raise ValueError("Parameters must be non-negatives.")

    if k > n:
        raise ValueError("Clauses cannot have more {} literals.".format(n))

    F = CNF()
    F.header = "Random {}-CNF over {} variables and {} clauses\n".format(
        k, n, m) + F.header

    indices = xrange(1, n + 1)
    for i in indices:
        F.add_variable('x_{0}'.format(i))
    try:
        for clause in sample_clauses(k, indices, m, planted_assignments):
            F.add_clause(list(clause), strict=True)
    except ValueError:
        raise ValueError(
            "There are fewer clauses available than the number requested")

    return F
예제 #48
0
def StoneFormula(D, nstones):
    """Stone formulas

    The stone formulas have been introduced in [2]_ and generalized in
    [1]_. They are one of the classic examples that separate regular
    resolutions from general resolution [1]_.

    A \"Stones formula\" from a directed acyclic graph :math:`D`
    claims that each vertex of the graph is associated with one on
    :math:`s` stones (not necessarily in an injective way).
    In particular for each vertex :math:`v` in :math:`V(D)` and each
    stone :math:`j` we have a variable :math:`P_{v,j}` that claims
    that stone :math:`j` is associated to vertex :math:`v`.

    Each stone can be either red or blue, and not both.
    The propositional variable :math:`R_j` if true when the stone
    :math:`j` is red and false otherwise.

    The clauses of the formula encode the following constraints.
    If a stone is on a source vertex (i.e. a vertex with no incoming
    edges), then it must be red. If all stones on the predecessors of
    a vertex are red, then the stone of the vertex itself must be red.

    The formula furthermore enforces that the stones on the sinks
    (i.e. vertices with no outgoing edges) are blue.

    .. note:: The exact formula structure depends by the graph and on
              its topological order, which is determined by the
              ``enumerate_vertices(D)``.

    Parameters
    ----------
    D : a directed acyclic graph
        it should be a directed acyclic graph.
    nstones : int
       the number of stones.

    Raises
    ------
    ValueError
       if :math:`D` is not a directed acyclic graph

    ValueError
       if the number of stones is negative

    References
    ----------
    .. [1] M. Alekhnovich, J. Johannsen, T. Pitassi and A. Urquhart
           An Exponential Separation between Regular and General Resolution.
           Theory of Computing (2007)
    .. [2] R. Raz and P. McKenzie
           Separation of the monotone NC hierarchy.
           Combinatorica (1999)

    """
    if not is_dag(D):
        raise ValueError(
            "Stone formulas are defined only for directed acyclic graphs.")

    if nstones < 0:
        raise ValueError("There must be at least one stone.")

    cnf = CNF()
    cnf.mode_unchecked()

    if hasattr(D, 'name'):
        cnf.header = "Stone formula of: " + D.name + \
            "\nwith " + str(nstones) + " stones\n" + cnf.header
    else:
        cnf.header = "Stone formula with " + \
            str(nstones) + " stones\n" + cnf.header

    # Add variables in the appropriate order
    vertices = enumerate_vertices(D)
    position = dict((v, i) for (i, v) in enumerate(vertices))
    stones = list(range(1, nstones+1))

    # Caching variable names
    color_vn = {}
    stone_vn = {}

    # Stones->Vertices variables
    for v in vertices:
        for j in stones:
            stone_vn[(v, j)] = "P_{{{0},{1}}}".format(v, j)
            cnf.add_variable(stone_vn[(v, j)],
                             description="Stone ${1}$ on vertex ${0}$".format(v, j))

    # Color variables
    for j in stones:
        color_vn[j] = "R_{{{0}}}".format(j)
        cnf.add_variable(color_vn[j],
                         description="Stone ${}$ is red".format(j))

    # Each vertex has some stone
    for v in vertices:
        cnf.add_clause([(True, stone_vn[(v, j)]) for j in stones])

    # If predecessors have red stones, the sink must have a red stone
    for v in vertices:
        for j in stones:
            pred = sorted(D.predecessors(v), key=lambda x: position[x])
            for stones_tuple in product([s for s in stones if s != j], repeat=len(pred)):
                cnf.add_clause([(False, stone_vn[(p, s)]) for (p, s) in zip(pred, stones_tuple)] +
                               [(False, stone_vn[(v, j)])] +
                               [(False, color_vn[s]) for s in _uniqify_list(stones_tuple)] +
                               [(True,  color_vn[j])])

        if D.out_degree(v) == 0:  # the sink
            for j in stones:
                cnf.add_clause(
                    [(False, stone_vn[(v, j)]), (False, color_vn[j])])

    return cnf
예제 #49
0
def GraphOrderingPrinciple(graph,total=False,smart=False,plant=False,knuth=0):
    """Generates the clauses for graph ordering principle

    Arguments:
    - `graph` : undirected graph
    - `total` : add totality axioms (i.e. "x < y" or "x > y")
    - `smart` : "x < y" and "x > y" are represented by a single variable (implies `total`)
    - `plant` : allow last element to be minimum (and could make the formula SAT)
    - `knuth` : Don Knuth variants 2 or 3 of the formula (anything else suppress it)
    """
    gop = CNF()

    # Describe the formula
    if total or smart:
        name = "Total graph ordering principle"
    else:
        name = "Ordering principle"

    if smart:
        name = name + "(compact representation)"

    if hasattr(graph, 'name'):
        gop.header = name+"\n on graph "+graph.name+".\n\n"+gop.header
    else:
        gop.header = name+".\n\n"+gop.header


    # Fix the vertex order
    V = enumerate_vertices(graph)

    # Add variables
    iterator = combinations if smart else permutations
    for v1,v2 in iterator(V,2):
        gop.add_variable(varname(v1,v2))

    #
    # Non minimality axioms
    #

    # Clause is generated in such a way that if totality is enforces,
    # every pair occurs with a specific orientation.
    # Allow minimum on last vertex if 'plant' options.

    for med in xrange(len(V) - (plant and 1)):
        clause = []
        for lo in xrange(med):
            if graph.has_edge(V[med], V[lo]):
                clause += [(True, varname(V[lo], V[med]))]
        for hi in xrange(med+1, len(V)):
            if not graph.has_edge(V[med], V[hi]):
                continue
            elif smart:
                clause += [(False, varname(V[med], V[hi]))]
            else:
                clause += [(True, varname(V[hi], V[med]))]
        gop.add_clause(clause, strict=True)

    #
    # Transitivity axiom
    #

    if len(V) >= 3:
        if smart:
            # Optimized version if smart representation of totality is used
            for (v1, v2, v3) in combinations(V, 3):

                gop.add_clause([(True,  varname(v1, v2)),
                                (True,  varname(v2, v3)),
                                (False, varname(v1, v3))],
                               strict=True)
                
                gop.add_clause([(False, varname(v1, v2)),
                                (False, varname(v2, v3)),
                                (True,  varname(v1, v3))],
                               strict=True)

        elif total:
            # With totality we still need just two axiom per triangle
            for (v1, v2, v3) in combinations(V, 3):
                
                gop.add_clause([(False, varname(v1, v2)),
                                (False, varname(v2, v3)),
                                (False, varname(v3, v1))],
                               strict=True)

                gop.add_clause([(False, varname(v1, v3)),
                                (False, varname(v3, v2)),
                                (False, varname(v2, v1))],
                               strict=True)

        else:
            for (v1, v2, v3) in permutations(V, 3):

                # knuth variants will reduce the number of
                # transitivity axioms
                if knuth == 2 and ((v2 < v1) or (v2 < v3)):
                    continue
                if knuth == 3 and ((v3 < v1) or (v3 < v2)):
                    continue

                gop.add_clause([(False, varname(v1, v2)),
                                (False, varname(v2, v3)),
                                (True,  varname(v1, v3))],
                               strict=True)

    if not smart:
        # Antisymmetry axioms (useless for 'smart' representation)
        for (v1, v2) in combinations(V, 2):
            gop.add_clause([(False, varname(v1, v2)),
                            (False, varname(v2, v1))],
                           strict=True)

        # Totality axioms (useless for 'smart' representation)
        if total:
            for (v1, v2) in combinations(V, 2):
                gop.add_clause([(True, varname(v1, v2)),
                                (True, varname(v2, v1))],
                               strict=True)

    return gop
예제 #50
0
파일: subgraph.py 프로젝트: lizzij/LCProj
def SubgraphFormula(graph, templates, symmetric=False):
    """Test whether a graph contains one of the templates.

    Given a graph :math:`G` and a sequence of template graphs
    :math:`H_1`, :math:`H_2`, ..., :math:`H_t`, the CNF formula claims
    that :math:`G` contains an isomorphic copy of at least one of the
    template graphs.

    E.g. when :math:`H_1` is the complete graph of :math:`k` vertices
    and it is the only template, the formula claims that :math:`G`
    contains a :math:`k`-clique.

    Parameters
    ----------
    graph : networkx.Graph
        a simple graph

    templates : list-like object
        a sequence of graphs.

    symmetric:
        all template graphs are symmetric wrt permutations of
        vertices. This allows some optimization in the search space of
        the assignments.

    induce: 
        force the subgraph to be induced (i.e. no additional edges are allowed)


    Returns
    -------
    a CNF object

    """

    F = CNF()

    # One of the templates is chosen to be the subgraph
    if len(templates) == 0:
        return F
    elif len(templates) == 1:
        selectors = []
    elif len(templates) == 2:
        selectors = ['c']
    else:
        selectors = ['c_{{{}}}'.format(i) for i in range(len(templates))]

    for s in selectors:
        F.add_variable(s)

    if len(selectors) > 1:
        for cls in F.equal_to_constraint(selectors, 1):
            F.add_clause(cls, strict=True)

    # comment the formula accordingly
    if len(selectors) > 1:
        F.header = dedent("""\
                 CNF encoding of the claim that a graph contains one among
                 a family of {0} possible subgraphs.
                 """.format(len(templates))) + F.header
    else:
        F.header = dedent("""\
                 CNF encoding of the claim that a graph contains an induced
                 copy of a subgraph.
                 """.format(len(templates))) + F.header

    # A subgraph is chosen
    N = graph.order()
    k = max([s.order() for s in templates])

    var_name = lambda i, j: "S_{{{0},{1}}}".format(i, j)

    if symmetric:
        mapping = F.unary_mapping(range(k),
                                  range(N),
                                  var_name=var_name,
                                  functional=True,
                                  injective=True,
                                  nondecreasing=True)
    else:
        mapping = F.unary_mapping(range(k),
                                  range(N),
                                  var_name=var_name,
                                  functional=True,
                                  injective=True,
                                  nondecreasing=False)

    for v in mapping.variables():
        F.add_variable(v)

    for cls in mapping.clauses():
        F.add_clause(cls, strict=True)

    # The selectors choose a template subgraph.  A mapping must map
    # edges to edges and non-edges to non-edges for the active
    # template.

    if len(templates) == 1:

        activation_prefixes = [[]]

    elif len(templates) == 2:

        activation_prefixes = [[(True, selectors[0])], [(False, selectors[0])]]

    else:
        activation_prefixes = [[(True, v)] for v in selectors]

    # maps must preserve the structure of the template graph
    gV = enumerate_vertices(graph)

    for i in range(len(templates)):

        k = templates[i].order()
        tV = enumerate_vertices(templates[i])

        if symmetric:
            # Using non-decreasing map to represent a subset
            localmaps = product(combinations(range(k), 2),
                                combinations(range(N), 2))
        else:
            localmaps = product(combinations(range(k), 2),
                                permutations(range(N), 2))

        for (i1, i2), (j1, j2) in localmaps:

            # check if this mapping is compatible
            tedge = templates[i].has_edge(tV[i1], tV[i2])
            gedge = graph.has_edge(gV[j1], gV[j2])
            if tedge == gedge:
                continue

            # if it is not, add the corresponding
            F.add_clause(activation_prefixes[i] + \
                         [(False,var_name(i1,j1)),
                          (False,var_name(i2,j2)) ],strict=True)

    return F
예제 #51
0
def GraphIsomorphism(G1, G2):
    """Graph Isomorphism formula

    The formula is the CNF encoding of the statement that two simple
    graphs G1 and G2 are isomorphic.

    Parameters
    ----------
    G1 : networkx.Graph
        an undirected graph object
    G2 : networkx.Graph
        an undirected graph object

    Returns
    -------
    A CNF formula which is satiafiable if and only if graphs G1 and G2
    are isomorphic.

    """
    F = CNF()
    F.header = "Graph Isomorphism problem between graphs " +\
               G1.name + " and " + G2.name + "\n" + F.header

    U=enumerate_vertices(G1)
    V=enumerate_vertices(G2)
    var = _graph_isomorphism_var

    for (u, v) in product(U,V):
        F.add_variable(var(u, v))

    # Defined on both side
    for u in U:
        F.add_clause([(True, var(u, v)) for v in V], strict=True)

    for v in V:
        F.add_clause([(True, var(u, v)) for u in U], strict=True)

    # Injective on both sides
    for u in U:
        for v1, v2 in combinations(V, 2):
            F.add_clause([(False, var(u, v1)),
                          (False, var(u, v2))], strict=True)
    for v in V:
        for u1, u2 in combinations(U, 2):
            F.add_clause([(False, var(u1, v)),
                          (False, var(u2, v))], strict=True)

    # Edge consistency
    for u1, u2 in combinations(U, 2):
        for v1, v2 in combinations(V, 2):
            if G1.has_edge(u1, u2) != G2.has_edge(v1, v2):
                F.add_clause([(False, var(u1, v1)),
                              (False, var(u2, v2))], strict=True)
                F.add_clause([(False, var(u1, v2)),
                              (False, var(u2, v1))], strict=True)

    return F
예제 #52
0
def GraphColoringFormula(G, colors, functional=True):
    """Generates the clauses for colorability formula

    The formula encodes the fact that the graph :math:`G` has a coloring
    with color set ``colors``. This means that it is possible to
    assign one among the elements in ``colors``to that each vertex of
    the graph such that no two adjacent vertices get the same color.

    Parameters
    ----------
    G : networkx.Graph
        a simple undirected graph
    colors : list or positive int
        a list of colors or a number of colors

    Returns
    -------
    CNF
       the CNF encoding of the coloring problem on graph ``G``

    """
    col = CNF()

    if isinstance(colors, int) and colors >= 0:
        colors = range(1, colors + 1)

    if not isinstance(list, collections.Iterable):
        ValueError("Parameter \"colors\" is expected to be a iterable")

    # Describe the formula
    name = "graph colorability"

    if hasattr(G, 'name'):
        col.header = name + " of graph:\n" + G.name + ".\n\n" + col.header
    else:
        col.header = name + ".\n\n" + col.header

    # Fix the vertex order
    V = enumerate_vertices(G)

    # Each vertex has a color
    for vertex in V:
        clause = []
        for color in colors:
            clause += [(True, 'x_{{{0},{1}}}'.format(vertex, color))]
        col.add_clause(clause)

        # unique color per vertex
        if functional:
            for (c1, c2) in combinations(colors, 2):
                col.add_clause([(False, 'x_{{{0},{1}}}'.format(vertex, c1)),
                                (False, 'x_{{{0},{1}}}'.format(vertex, c2))],
                               strict=True)

    # This is a legal coloring
    for (v1, v2) in enumerate_edges(G):
        for c in colors:
            col.add_clause([(False, 'x_{{{0},{1}}}'.format(v1, c)),
                            (False, 'x_{{{0},{1}}}'.format(v2, c))],
                           strict=True)

    return col
예제 #53
0
def GraphColoringFormula(G,colors,functional=True):
    """Generates the clauses for colorability formula

    The formula encodes the fact that the graph :math:`G` has a coloring
    with color set ``colors``. This means that it is possible to
    assign one among the elements in ``colors``to that each vertex of
    the graph such that no two adjacent vertices get the same color.

    Parameters
    ----------
    G : networkx.Graph
        a simple undirected graph
    colors : list or positive int
        a list of colors or a number of colors

    Returns
    -------
    CNF
       the CNF encoding of the coloring problem on graph ``G``

    """
    col=CNF()
    col.mode_strict()

    if isinstance(colors,int) and colors>=0:
        colors = range(1,colors+1)
    
    if not isinstance(list, collections.Iterable):
        ValueError("Parameter \"colors\" is expected to be a iterable")
    
    # Describe the formula
    name="graph colorability"
    
    if hasattr(G,'name'):
        col.header=name+" of graph:\n"+G.name+".\n\n"+col.header
    else:
        col.header=name+".\n\n"+col.header

    # Fix the vertex order
    V=enumerate_vertices(G)

    # Create the variables
    for vertex in V:
        for color in colors:
            col.add_variable('x_{{{0},{1}}}'.format(vertex,color))
        
    # Each vertex has a color
    for vertex in V:
        clause = []
        for color in colors:
            clause += [(True,'x_{{{0},{1}}}'.format(vertex,color))]
        col.add_clause(clause)
        
        # unique color per vertex
        if functional:
            for (c1,c2) in combinations(colors,2):
                col.add_clause([
                    (False,'x_{{{0},{1}}}'.format(vertex,c1)),
                    (False,'x_{{{0},{1}}}'.format(vertex,c2))])

    # This is a legal coloring
    for (v1,v2) in enumerate_edges(G):
        for c in colors:
            col.add_clause([
                (False,'x_{{{0},{1}}}'.format(v1,c)),
                (False,'x_{{{0},{1}}}'.format(v2,c))])
            
    return col
예제 #54
0
def GraphOrderingPrinciple(graph,
                           total=False,
                           smart=False,
                           plant=False,
                           knuth=0):
    """Generates the clauses for graph ordering principle

    Arguments:
    - `graph` : undirected graph
    - `total` : add totality axioms (i.e. "x < y" or "x > y")
    - `smart` : "x < y" and "x > y" are represented by a single variable (implies `total`)
    - `plant` : allow last element to be minimum (and could make the formula SAT)
    - `knuth` : Don Knuth variants 2 or 3 of the formula (anything else suppress it)
    """
    gop = CNF()

    # Describe the formula
    if total or smart:
        name = "Total graph ordering principle"
    else:
        name = "Ordering principle"

    if smart:
        name = name + "(compact representation)"

    if hasattr(graph, 'name'):
        gop.header = name + "\n on graph " + graph.name + ".\n\n" + gop.header
    else:
        gop.header = name + ".\n\n" + gop.header

    # Fix the vertex order
    V = enumerate_vertices(graph)

    # Add variables
    iterator = combinations if smart else permutations
    for v1, v2 in iterator(V, 2):
        gop.add_variable(varname(v1, v2))

    #
    # Non minimality axioms
    #

    # Clause is generated in such a way that if totality is enforces,
    # every pair occurs with a specific orientation.
    # Allow minimum on last vertex if 'plant' options.

    for med in xrange(len(V) - (plant and 1)):
        clause = []
        for lo in xrange(med):
            if graph.has_edge(V[med], V[lo]):
                clause += [(True, varname(V[lo], V[med]))]
        for hi in xrange(med + 1, len(V)):
            if not graph.has_edge(V[med], V[hi]):
                continue
            elif smart:
                clause += [(False, varname(V[med], V[hi]))]
            else:
                clause += [(True, varname(V[hi], V[med]))]
        gop.add_clause(clause, strict=True)

    #
    # Transitivity axiom
    #

    if len(V) >= 3:
        if smart:
            # Optimized version if smart representation of totality is used
            for (v1, v2, v3) in combinations(V, 3):

                gop.add_clause([(True, varname(v1, v2)),
                                (True, varname(v2, v3)),
                                (False, varname(v1, v3))],
                               strict=True)

                gop.add_clause([(False, varname(v1, v2)),
                                (False, varname(v2, v3)),
                                (True, varname(v1, v3))],
                               strict=True)

        elif total:
            # With totality we still need just two axiom per triangle
            for (v1, v2, v3) in combinations(V, 3):

                gop.add_clause([(False, varname(v1, v2)),
                                (False, varname(v2, v3)),
                                (False, varname(v3, v1))],
                               strict=True)

                gop.add_clause([(False, varname(v1, v3)),
                                (False, varname(v3, v2)),
                                (False, varname(v2, v1))],
                               strict=True)

        else:
            for (v1, v2, v3) in permutations(V, 3):

                # knuth variants will reduce the number of
                # transitivity axioms
                if knuth == 2 and ((v2 < v1) or (v2 < v3)):
                    continue
                if knuth == 3 and ((v3 < v1) or (v3 < v2)):
                    continue

                gop.add_clause([(False, varname(v1, v2)),
                                (False, varname(v2, v3)),
                                (True, varname(v1, v3))],
                               strict=True)

    if not smart:
        # Antisymmetry axioms (useless for 'smart' representation)
        for (v1, v2) in combinations(V, 2):
            gop.add_clause([(False, varname(v1, v2)),
                            (False, varname(v2, v1))],
                           strict=True)

        # Totality axioms (useless for 'smart' representation)
        if total:
            for (v1, v2) in combinations(V, 2):
                gop.add_clause([(True, varname(v1, v2)),
                                (True, varname(v2, v1))],
                               strict=True)

    return gop