Example #1
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)]
        cnf.add_equal_to(edge_vars, 1)

    return cnf
Example #2
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

    mapping = binary_mapping(range(1, pigeons + 1),
                             range(1, holes + 1),
                             injective=True)
    bphp.mode_unchecked()
    mapping.load_variables_to_formula(bphp)
    mapping.load_clauses_to_formula(bphp)
    bphp.mode_default()

    return bphp
Example #3
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_unsafe(c)

    return bphp
Example #4
0
def GraphAutomorphism(G):
    """Graph Automorphism formula

    The formula is the CNF encoding of the statement that a graph G
    has a nontrivial automorphism, i.e. an automorphism different from
    the idential one.

    Parameter
    ---------
    G : a simple graph

    Returns
    -------
    A CNF formula which is satiafiable if and only if graph G has a
    nontrivial automorphism.
    """
    tmp = CNF()
    header = "Graph automorphism formula for graph " + G.name + "\n" + tmp.header
    F = GraphIsomorphism(G, G)
    F.header = header

    var = _graph_isomorphism_var

    F.add_clause([(False, var(u, u)) for u in enumerate_vertices(G)],
                 strict=True)

    return F
Example #5
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
Example #6
0
 def test_empty(self):
     opb = """\
     * #variable= 0 #constraint= 0
     *
     """
     F = CNF()
     self.assertCnfEqualsOPB(F, opb)
Example #7
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]]
        cnf.add_equal_to(edge_vars, 1)

    return cnf
Example #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")])
Example #9
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.mode_strict()
    F.header = "Even coloring formula on graph " + G.name + "\n" + F.header

    def var_name(u, v):
        if u <= v:
            return 'x_{{{0},{1}}}'.format(u, v)
        else:
            return 'x_{{{0},{1}}}'.format(v, u)

    for (u, v) in enumerate_edges(G):
        F.add_variable(var_name(u, v))

    # Defined on both side
    for v in enumerate_vertices(G):

        if G.degree(v) % 2 == 1:
            raise ValueError(
                "Markstrom formulas requires all vertices to have even degree."
            )

        edge_vars = [var_name(u, v) for u in neighbors(G, v)]

        # F.add_exactly_half_floor would work the same
        F.add_exactly_half_ceil(edge_vars)

    return F
Example #10
0
 def test_most(self):
     ass = [
         {'x_1': True, 'x_2': False},
         {'x_1': False, 'x_2': False},
         {'x_1': True, 'x_2': True},
     ]
     F = RandomKCNF(2, 2, 1, planted_assignments=ass)
     G = CNF([[(True, 'x_1'), (False, 'x_2')]])
     self.assertCnfEqual(F, G)
Example #11
0
 def test_weighted_leq(self):
     opb = """\
     * #variable= 5 #constraint= 1
     *
     -1 x1 -2 x2 -3 x3 +1 x4 +2 x5 >= -2;
     """
     F = CNF()
     F.add_linear(1, "a", 2, "b", 3, "c", -1, "d", -2, "e", "<=", 2)
     self.assertCnfEqualsOPB(F, opb)
Example #12
0
 def test_one_eq(self):
     opb = """\
     * #variable= 5 #constraint= 1
     *
     +1 x1 +1 x2 +1 x3 +1 x4 +1 x5 = 2;
     """
     F = CNF()
     F.add_equal_to(["a", "b", "c", "d", "e"], 2)
     self.assertCnfEqualsOPB(F, opb)
Example #13
0
 def test_one_lt(self):
     opb = """\
     * #variable= 5 #constraint= 1
     *
     -1 x1 -1 x2 -1 x3 -1 x4 -1 x5 >= -1;
     """
     F = CNF()
     F.add_strictly_less_than(["a", "b", "c", "d", "e"], 2)
     self.assertCnfEqualsOPB(F, opb)
Example #14
0
 def test_one_leq(self):
     opb = """\
     * #variable= 5 #constraint= 1
     *
     -1 x1 -1 x2 -1 x3 -1 x4 -1 x5 >= -2;
     """
     F = CNF()
     F.add_less_or_equal(["a", "b", "c", "d", "e"], 2)
     self.assertCnfEqualsOPB(F, opb)
Example #15
0
 def test_one_gt(self):
     opb = """\
     * #variable= 5 #constraint= 1
     *
     +1 x1 +1 x2 +1 x3 +1 x4 +1 x5 >= 3;
     """
     F = CNF()
     F.add_strictly_greater_than(["a", "b", "c", "d", "e"], 2)
     self.assertCnfEqualsOPB(F, opb)
Example #16
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)
Example #17
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
Example #18
0
 def test_weighted_gt(self):
     opb = """\
     * #variable= 5 #constraint= 1
     *
     +1 x1 +2 x2 +3 x3 -1 x4 -2 x5 >= 3;
     """
     F = CNF()
     F.add_linear(1, "a", 2, "b", 3, "c", -1, "d", -2, "e", ">", 2)
     self.assertCnfEqualsOPB(F, opb)
Example #19
0
 def test_one(self):
     ass = [{'x_1': True, 'x_2': False}]
     F = RandomKCNF(2, 2, 3, planted_assignments=ass)
     G = CNF([
         [(True, 'x_1'), (False, 'x_2')],
         [(False, 'x_1'), (False, 'x_2')],
         [(True, 'x_1'), (True, 'x_2')],
     ])
     self.assertCnfEqual(F, G)
Example #20
0
    def build_cnf(args):
        """Build a CNF formula with an empty clause 

        Parameters
        ----------
        args : ignored 
             command line options
        """
        return CNF([[]])
Example #21
0
    def build_cnf(args):
        """Build an empty CNF formula 

        Parameters
        ----------
        args : ignored 
             command line options
        """
        return CNF()
Example #22
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")])
Example #23
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")])
Example #24
0
    def build_cnf(args):
        """Build a conjunction

        Arguments:
        - `args`: command line options
        """
        clauses = [ [(True,"x_{}".format(i))] for i in range(args.P) ] + \
                  [ [(False,"y_{}".format(i))] for i in range(args.N) ]
        andcnf = CNF(clauses)
        andcnf.header = "Singleton clauses: {} positive and {} negative\n\n""".format(args.P,args.N) +\
                        andcnf.header
        return andcnf
Example #25
0
    def build_cnf(args):
        """Build an disjunction

        Arguments:
        - `args`: command line options
        """
        clause = [ (True,"x_{}".format(i)) for i in range(args.P) ] + \
                 [ (False,"y_{}".format(i)) for i in range(args.N) ]
        orcnf = CNF([clause])
        orcnf.header = "Clause with {} positive and {} negative literals\n\n".format(args.P,args.N) + \
                       orcnf.header
        return orcnf
Example #26
0
 def test_parity(self):
     opb = """\
     * #variable= 3 #constraint= 4
     *
     +1 x1 +1 x2 +1 x3 >= 1;
     +1 x1 -1 x2 -1 x3 >= -1;
     -1 x1 +1 x2 -1 x3 >= -1;
     -1 x1 -1 x2 +1 x3 >= -1;
     """
     F = CNF()
     F.add_parity(["a", "b", "c"], 1)
     self.assertCnfEqualsOPB(F, opb)
Example #27
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 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
Example #28
0
    def getFormula(self):
        self.f = CNF()
        self.f._header += headerInfo
        self.addAllVariables()

        result = fe.And()
        for i in range(self.numRows):
            result.append(self.addRowConstraint(i))

        for j in range(self.numCols):
            result.append(self.addColConstraint(j))

        fe.toCNFgen(self.f, result)
        return self.f
Example #29
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
Example #30
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