Exemple #1
0
    def __init__(self, cnf, B, function='xor'):
        """Build a new CNF obtained by substituting a XOR to the
        variables of the original CNF.

        Parameters
        ----------
        cnf : CNF
            the original cnf formula
        B : networkx.Graph
            a bipartite graph. The right side must have the number of
            vertices equal to the number of original variables
        
        function: string 
            Select which faction is used for the compression. It must
            be one among 'xor' or 'maj'.

        """
        if function not in ['xor','maj']:
            raise ValueError("Function specification for variable compression must be either 'xor' or 'maj'.")

        Left,Right = bipartite_sets(B)

        if len(Right) != len(list(cnf.variables())):
            raise ValueError("Right side of the graph must match the variable numbers of the CNF.")

        self._pattern = B
        self._function = function
        for n,v in zip(cnf.variables(),Right):
            self._name_vertex_dict[n]=v
            
        super(VariableCompression,self).__init__(cnf, new_variables = ["Y_{{{0}}}".format(i) for i in Left])

        self._header="Variable {}-compression from {} to {} variables\n\n".format(function,len(Right),len(Left)) \
            +self._header
    def test_bshift(self):

        G = self.parse(["--bshift", "10","9"])
        self.assertEqual(G.order(),19)
        left, right = bipartite_sets(G)
        self.assertEqual(len(left),10)
        self.assertEqual(len(right),9)
        for v in left:
            self.assertEqual(G.degree(v),0)
        G = self.parse(["--bshift", "10","10","1","2","4","8"])
        self.assertEqual(G.order(),20)
        left, right = bipartite_sets(G)
        self.assertEqual(len(left),10)
        self.assertEqual(len(right),10)
        for v in left:
            self.assertEqual(G.degree(v),4)
Exemple #3
0
 def test_bm(self):
     G = self.parse(["--bm", "10", "9", "15"])
     self.assertEqual(G.order(), 19)
     self.assertEqual(G.size(), 15)
     left, right = bipartite_sets(G)
     self.assertEqual(len(left), 10)
     self.assertEqual(len(right), 9)
    def test_readGraph_kthlist_bipartite(self):

        G = readGraph(sio(kthlist_bipartite), graph_type="bipartite", file_format="kthlist")
        self.assertEqual(G.order(), 5)
        L, R = bipartite_sets(G)
        self.assertEqual(len(L), 2)
        self.assertEqual(len(R), 3)
Exemple #5
0
    def test_bshift(self):

        G = self.parse(["--bshift", "10", "9"])
        self.assertEqual(G.order(), 19)
        left, right = bipartite_sets(G)
        self.assertEqual(len(left), 10)
        self.assertEqual(len(right), 9)
        for v in left:
            self.assertEqual(G.degree(v), 0)
        G = self.parse(["--bshift", "10", "10", "1", "2", "4", "8"])
        self.assertEqual(G.order(), 20)
        left, right = bipartite_sets(G)
        self.assertEqual(len(left), 10)
        self.assertEqual(len(right), 10)
        for v in left:
            self.assertEqual(G.degree(v), 4)
 def test_bm(self):
     G = self.parse(["--bm", "10", "9", "15"])
     self.assertEqual(G.order(),19)
     self.assertEqual(G.size(),15)
     left, right = bipartite_sets(G)
     self.assertEqual(len(left),10)
     self.assertEqual(len(right),9)
Exemple #7
0
    def __init__(self, cnf, B, function='xor'):
        """Build a new CNF obtained by substituting a XOR to the
        variables of the original CNF.

        Parameters
        ----------
        cnf : CNF
            the original cnf formula
        B : networkx.Graph
            a bipartite graph. The right side must have the number of
            vertices equal to the number of original variables
        
        function: string 
            Select which faction is used for the compression. It must
            be one among 'xor' or 'maj'.

        """
        if function not in ['xor','maj']:
            raise ValueError("Function specification for variable compression must be either 'xor' or 'maj'.")

        Left,Right = bipartite_sets(B)

        if len(Right) != len(list(cnf.variables())):
            raise ValueError("Right side of the graph must match the variable numbers of the CNF.")

        self._pattern = B
        self._function = function
        for n,v in zip(cnf.variables(),Right):
            self._name_vertex_dict[n]=v
            
        super(VariableCompression,self).__init__(cnf, new_variables = ["Y_{{{0}}}".format(i) for i in Left])

        self._header="Variable {}-compression from {} to {} variables\n\n".format(function,len(Right),len(Left)) \
            +self._header
Exemple #8
0
    def test_readGraph_kthlist_bipartite(self):

        G = readGraph(sio(kthlist_bipartite),
                      graph_type='bipartite', file_format='kthlist')
        self.assertEqual(G.order(), 5)
        L, R = bipartite_sets(G)
        self.assertEqual(len(L), 2)
        self.assertEqual(len(R), 3)
Exemple #9
0
 def test_bd(self):
     G = self.parse(["--bd", "10", "9", "3"])
     self.assertEqual(G.order(), 19)
     self.assertEqual(G.size(), 30)
     left, right = bipartite_sets(G)
     self.assertEqual(len(left), 10)
     self.assertEqual(len(right), 9)
     for v in left:
         self.assertEqual(G.degree(v), 3)
 def test_bd(self):
     G = self.parse(["--bd", "10", "9", "3"])
     self.assertEqual(G.order(),19)
     self.assertEqual(G.size(),30)
     left, right = bipartite_sets(G)
     self.assertEqual(len(left),10)
     self.assertEqual(len(right),9)
     for v in left:
         self.assertEqual(G.degree(v),3)
 def test_bregular(self):
     G = self.parse(["--bregular", "10", "8", "4"])
     self.assertEqual(G.order(),18)
     self.assertEqual(G.size(),40)
     left, right = bipartite_sets(G)
     self.assertEqual(len(left),10)
     self.assertEqual(len(right),8)
     for v in left:
         self.assertEqual(G.degree(v),4)
     for v in right:
         self.assertEqual(G.degree(v),5)
Exemple #12
0
 def test_bregular(self):
     G = self.parse(["--bregular", "10", "8", "4"])
     self.assertEqual(G.order(), 18)
     self.assertEqual(G.size(), 40)
     left, right = bipartite_sets(G)
     self.assertEqual(len(left), 10)
     self.assertEqual(len(right), 8)
     for v in left:
         self.assertEqual(G.degree(v), 4)
     for v in right:
         self.assertEqual(G.degree(v), 5)
def SubsetCardinalityFormula(B, equalities=False):
    r"""SubsetCardinalityFormula

    Consider a bipartite graph :math:`B`. The CNF claims that at least half
    of the edges incident to each of the vertices on left side of :math:`B`
    must be zero, while at least half of the edges incident to each
    vertex on the left side must be one.

    Variants of these formula on specific families of bipartite graphs
    have been studied in [1]_, [2]_ and [3]_, and turned out to be
    difficult for resolution based SAT-solvers.

    Each variable of the formula is denoted as :math:`x_{i,j}` where
    :math:`\{i,j\}` is an edge of the bipartite graph. The clauses of
    the CNF encode the following constraints on the edge variables.

    For every left vertex i with neighborhood :math:`\Gamma(i)`

    .. math::

         \sum_{j \in \Gamma(i)} x_{i,j} \geq \frac{|\Gamma(i)|}{2}

    For every right vertex j with neighborhood :math:`\Gamma(j)`

    .. math::

         \sum_{i \in \Gamma(j)} x_{i,j} \leq \frac{|\Gamma(j)|}{2}.

    If the ``equalities`` flag is true, the constraints are instead
    represented by equations.

    .. math::

         \sum_{j \in \Gamma(i)} x_{i,j} = \left\lceil \frac{|\Gamma(i)|}{2} \right\rceil

    .. math::

         \sum_{i \in \Gamma(j)} x_{i,j} = \left\lfloor \frac{|\Gamma(j)|}{2} \right\rfloor .

    Parameters
    ----------
    B : networkx.Graph
        the graph vertices must have the 'bipartite' attribute
        set. Left vertices must have it set to 0 and the right ones to 1.
        A KeyException is raised otherwise.

    equalities : boolean
        use equations instead of inequalities to express the
        cardinality constraints.  (default: False) 

    Returns
    -------
    A CNF object

    References
    ----------
    .. [1] Mladen Miksa and Jakob Nordstrom
           Long proofs of (seemingly) simple formulas
           Theory and Applications of Satisfiability Testing--SAT 2014 (2014)
    .. [2] Ivor Spence
           sgen1: A generator of small but difficult satisfiability benchmarks
           Journal of Experimental Algorithmics (2010)
    .. [3] Allen Van Gelder and Ivor Spence
           Zero-One Designs Produce Small Hard SAT Instances
           Theory and Applications of Satisfiability Testing--SAT 2010(2010)

    """
    Left, Right = bipartite_sets(B)

    ssc = CNF()
    ssc.header = "Subset cardinality formula for graph {0}\n".format(B.name)
    ssc.mode_strict()

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

    for u in Left:
        for v in neighbors(B, u):
            ssc.add_variable(var_name(u, v))

    for u in Left:
        edge_vars = [var_name(u, v) for v in neighbors(B, u)]

        if equalities:
            ssc.add_exactly_half_ceil(edge_vars)
        else:
            ssc.add_loose_majority(edge_vars)

    for v in Right:
        edge_vars = [var_name(u, v) for u in neighbors(B, v)]

        if equalities:
            ssc.add_exactly_half_floor(edge_vars)
        else:
            ssc.add_loose_minority(edge_vars)

    return ssc
 def test_bp(self):
     G = self.parse(["--bp", "10", "9", "0.5"])
     self.assertEqual(G.order(),19)
     left, right = bipartite_sets(G)
     self.assertEqual(len(left),10)
     self.assertEqual(len(right),9)
Exemple #15
0
def SparseStoneFormula(D,B):
    """Sparse Stone formulas

    This is a variant of the :py:func:`StoneFormula`. See that for
    a description of the formula. This variant is such that each
    vertex has only a small selection of which stone can go to that
    vertex. In particular which stones are allowed on each vertex is
    specified by a bipartite graph :math:`B` on which the left
    vertices represent the vertices of DAG :math:`D` and the right
    vertices are the stones. 

    If a vertex of :math:`D` correspond to the left vertex :math:`v`
    in :math:`B`, then its neighbors describe which stones are allowed
    for it.

    The vertices in :math:`D` do not need to have the same name as the
    one on the left side of :math:`B`. It is only important that the
    number of vertices in :math:`D` is the same as the vertices in the
    left side of :math:`B`.

    In that case the element at position :math:`i` in the ordered
    sequence ``enumerate_vertices(D)`` corresponds to the element of
    rank :math:`i` in the sequence of left side vertices of
    :math:`B` according to the output of ``Left, Right =
    bipartite_sets(B)``.

    Standard :py:func:`StoneFormula` is essentially equivalent to
    a sparse stone formula where :math:`B` is the complete graph.

    Parameters
    ----------
    D : a directed acyclic graph
        it should be a directed acyclic graph.
    B : bipartite graph

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

    ValueError
       when size differs between :math:`D` and the left side of
       :math:`B`

    See Also
    --------
    StoneFormula

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

    if not has_bipartition(B):
        raise ValueError("Vertices to stones mapping must be specified with a bipartite graph")
    
    Left, Right = bipartite_sets(B)
    nstones = len(Right)

    if len(Left) != D.order():
        raise ValueError("Formula requires the bipartite left side to match #vertices of the DAG.")
     
    cnf = CNF()
    
    if hasattr(D, 'name'):
        cnf.header = "Sparse Stone formula of: " + D.name + "\nwith " + str(nstones) + " stones\n" + cnf.header
    else:
        cnf.header = "Sparse Stone formula with " + str(nstones) + " stones\n" + cnf.header

    # add variables in the appropriate order
    vertices=enumerate_vertices(D)
    stones=range(1,nstones+1)
    
    mapping = cnf.unary_mapping(vertices,stones,sparsity_pattern=B)

    stone_formula_helper(cnf,D,mapping)
    return cnf
Exemple #16
0
 def test_bp(self):
     G = self.parse(["--bp", "10", "9", "0.5"])
     self.assertEqual(G.order(), 19)
     left, right = bipartite_sets(G)
     self.assertEqual(len(left), 10)
     self.assertEqual(len(right), 9)
Exemple #17
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, Right = bipartite_sets(graph)

    mapping = unary_mapping(Left,Right,
                            sparsity_pattern=graph,
                            var_name=var_name,
                            injective = True,
                            functional = functional,
                            surjective = onto)
    
    gphp.mode_unchecked()
    mapping.load_variables_to_formula(gphp)
    mapping.load_clauses_to_formula(gphp)
    gphp.mode_default()

    return gphp
Exemple #18
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, Right = bipartite_sets(graph)

    mapping = unary_mapping(Left,
                            Right,
                            sparsity_pattern=graph,
                            var_name=var_name,
                            injective=True,
                            functional=functional,
                            surjective=onto)

    gphp.mode_unchecked()
    mapping.load_variables_to_formula(gphp)
    mapping.load_clauses_to_formula(gphp)
    gphp.mode_default()

    return gphp
Exemple #19
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
Exemple #20
0
def SparseStoneFormula(D, B):
    """Sparse Stone formulas

    This is a variant of the :py:func:`StoneFormula`. See that for
    a description of the formula. This variant is such that each
    vertex has only a small selection of which stone can go to that
    vertex. In particular which stones are allowed on each vertex is
    specified by a bipartite graph :math:`B` on which the left
    vertices represent the vertices of DAG :math:`D` and the right
    vertices are the stones. 

    If a vertex of :math:`D` correspond to the left vertex :math:`v`
    in :math:`B`, then its neighbors describe which stones are allowed
    for it.

    The vertices in :math:`D` do not need to have the same name as the
    one on the left side of :math:`B`. It is only important that the
    number of vertices in :math:`D` is the same as the vertices in the
    left side of :math:`B`.

    In that case the element at position :math:`i` in the ordered
    sequence ``enumerate_vertices(D)`` corresponds to the element of
    rank :math:`i` in the sequence of left side vertices of
    :math:`B` according to the output of ``Left, Right =
    bipartite_sets(B)``.

    Standard :py:func:`StoneFormula` is essentially equivalent to
    a sparse stone formula where :math:`B` is the complete graph.

    Parameters
    ----------
    D : a directed acyclic graph
        it should be a directed acyclic graph.
    B : bipartite graph

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

    ValueError
       when size differs between :math:`D` and the left side of
       :math:`B`

    See Also
    --------
    StoneFormula

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

    if not has_bipartition(B):
        raise ValueError(
            "Vertices to stones mapping must be specified with a bipartite graph"
        )

    Left, Right = bipartite_sets(B)
    nstones = len(Right)

    if len(Left) != D.order():
        raise ValueError(
            "Formula requires the bipartite left side to match #vertices of the DAG."
        )

    cnf = CNF()

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

    # add variables in the appropriate order
    vertices = enumerate_vertices(D)
    stones = list(range(1, nstones + 1))

    mapping = cnf.unary_mapping(vertices, stones, sparsity_pattern=B)

    stone_formula_helper(cnf, D, mapping)
    return cnf
def SubsetCardinalityFormula(B, equalities = False):
    r"""SubsetCardinalityFormula

    Consider a bipartite graph :math:`B`. The CNF claims that at least half
    of the edges incident to each of the vertices on left side of :math:`B`
    must be zero, while at least half of the edges incident to each
    vertex on the left side must be one.

    Variants of these formula on specific families of bipartite graphs
    have been studied in [1]_, [2]_ and [3]_, and turned out to be
    difficult for resolution based SAT-solvers.

    Each variable of the formula is denoted as :math:`x_{i,j}` where
    :math:`\{i,j\}` is an edge of the bipartite graph. The clauses of
    the CNF encode the following constraints on the edge variables.

    For every left vertex i with neighborhood :math:`\Gamma(i)`

    .. math::
         
         \sum_{j \in \Gamma(i)} x_{i,j} \geq \frac{|\Gamma(i)|}{2}

    For every right vertex j with neighborhood :math:`\Gamma(j)`

    .. math::
         
         \sum_{i \in \Gamma(j)} x_{i,j} \leq \frac{|\Gamma(j)|}{2}.

    If the ``equalities`` flag is true, the constraints are instead
    represented by equations.
    
    .. math::
         
         \sum_{j \in \Gamma(i)} x_{i,j} = \left\lceil \frac{|\Gamma(i)|}{2} \right\rceil

    .. math::
         
         \sum_{i \in \Gamma(j)} x_{i,j} = \left\lfloor \frac{|\Gamma(j)|}{2} \right\rfloor .

    Parameters
    ----------
    B : networkx.Graph
        the graph vertices must have the 'bipartite' attribute
        set. Left vertices must have it set to 0 and the right ones to 1.
        A KeyException is raised otherwise.

    equalities : boolean
        use equations instead of inequalities to express the
        cardinality constraints.  (default: False) 

    Returns
    -------
    A CNF object

    References
    ----------
    .. [1] Mladen Miksa and Jakob Nordstrom
           Long proofs of (seemingly) simple formulas
           Theory and Applications of Satisfiability Testing--SAT 2014 (2014)
    .. [2] Ivor Spence
           sgen1: A generator of small but difficult satisfiability benchmarks
           Journal of Experimental Algorithmics (2010)
    .. [3] Allen Van Gelder and Ivor Spence
           Zero-One Designs Produce Small Hard SAT Instances
           Theory and Applications of Satisfiability Testing--SAT 2010(2010)

    """
    Left, Right = bipartite_sets(B)
            
    ssc=CNF()
    ssc.header="Subset cardinality formula for graph {0}\n".format(B.name)

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

    for u in Left:
        for e in B.edges(u):
            ssc.add_variable(var_name(*e))
        
    for u in Left:
        edge_vars = [ var_name(*e) for e in B.edges(u) ]

        if equalities:
            for cls in exactly_half_ceil(edge_vars):
                ssc.add_clause(cls,strict=True)
        else:
            for cls in loose_majority_constraint(edge_vars):
                ssc.add_clause(cls,strict=True)

    for v in Right:
        edge_vars = [ var_name(*e) for e in B.edges(v) ]

        if equalities:
            for cls in exactly_half_floor(edge_vars):
                ssc.add_clause(cls,strict=True)
        else:
            for cls in loose_minority_constraint(edge_vars):
                ssc.add_clause(cls,strict=True)
    
    return ssc