Beispiel #1
0
    def atmost(cls, lits, bound=1, top_id=None, vpool=None,
            encoding=EncType.seqcounter):
        """
            This method can be used for creating a CNF encoding of an AtMostK
            constraint, i.e. of :math:`\sum_{i=1}^{n}{x_i}\leq k`. The method
            shares the arguments and the return type with method
            :meth:`CardEnc.atleast`. Please, see it for details.
        """

        if encoding < 0 or encoding > 9:
            raise(NoSuchEncodingError(encoding))

        # checking if the bound is meaningless for any encoding
        if bound < 0:
            raise ValueError('Wrong bound: {0}'.format(bound))

        if encoding in (0, 4, 5) and 1 < bound < len(lits) - 1:
            raise(UnsupportedBound(encoding, bound))

        assert not top_id or not vpool, \
                'Use either a top id or a pool of variables but not both.'

        # we are going to return this formula
        ret = CNFPlus()

        # if the list of literals is empty, return empty formula
        if not lits:
            return ret

        # obtaining the top id from the variable pool
        if vpool:
            top_id = vpool.top

        # making sure we are dealing with a list of literals
        lits = list(lits)

        # choosing the maximum id among the current top and the list of literals
        top_id = max(map(lambda x: abs(x), lits + [top_id if top_id != None else 0]))

        # MiniCard's native representation is handled separately
        if encoding == 9:
            ret.atmosts, ret.nv = [(lits, bound)], top_id
            return ret

        res = pycard.encode_atmost(lits, bound, top_id, encoding,
                int(MainThread.check()))

        if res:
            ret.clauses, ret.nv = res

            # updating vpool if necessary
            if vpool:
                if vpool._occupied and vpool.top <= vpool._occupied[0][0] <= ret.nv:
                    cls._update_vids(ret, vpool)
                else:
                    # here, ret.nv id is assumed to be larger than the top id
                    vpool.top = ret.nv - 1
                    vpool._next()

        return ret
Beispiel #2
0
    def new(self, lits=[], ubound=1, top_id=None):
        """
            The actual constructor of :class:`ITotalizer`. Invoked from
            ``self.__init__()``. Creates an object of :class:`ITotalizer` given
            a list of literals in the sum, the largest potential bound to
            consider, as well as the top variable identifier used so far. See
            the description of :class:`ITotalizer` for details.
        """

        self.lits = list(lits)
        self.ubound = ubound
        self.top_id = max(map(lambda x: abs(x), self.lits + [top_id if top_id != None else 0]))

        if MainThread.check() == True:
            # saving default SIGINT handler
            def_sigint_handler = signal.signal(signal.SIGINT, signal.SIG_DFL)

            # creating the object
            self.tobj, clauses, self.rhs, self.top_id = pycard.itot_new(self.lits,
                    self.ubound, self.top_id, 1)

            # recovering default SIGINT handler
            def_sigint_handler = signal.signal(signal.SIGINT, def_sigint_handler)
        else:
            self.tobj, clauses, self.rhs, self.top_id = pycard.itot_new(self.lits,
                    self.ubound, self.top_id, 0)

        # saving the result
        self.cnf.clauses = clauses
        self.cnf.nv = self.top_id

        # for convenience, keeping the number of clauses
        self.nof_new = len(clauses)
Beispiel #3
0
    def atmost(cls, lits, bound=1, top_id=None, vpool=None,
            encoding=EncType.seqcounter):
        """
            This method can be used for creating a CNF encoding of an AtMostK
            constraint, i.e. of :math:`\sum_{i=1}^{n}{x_i}\leq k`. The method
            shares the arguments and the return type with method
            :meth:`CardEnc.atleast`. Please, see it for details.
        """

        if encoding < 0 or encoding > 9:
            raise(NoSuchEncodingError(encoding))

        assert not top_id or not vpool, \
                'Use either a top id or a pool of variables but not both.'

        # we are going to return this formula
        ret = CNFPlus()

        # if the list of literals is empty, return empty formula
        if not lits:
            return ret

        # obtaining the top id from the variable pool
        if vpool:
            top_id = vpool.top

        # choosing the maximum id among the current top and the list of literals
        top_id = max(map(lambda x: abs(x), lits + [top_id if top_id != None else 0]))

        # MiniCard's native representation is handled separately
        if encoding == 9:
            ret.atmosts, ret.nv = [(lits, bound)], top_id
            return ret

        if MainThread.check() == True:
            # saving default SIGINT handler
            def_sigint_handler = signal.signal(signal.SIGINT, signal.SIG_DFL)

            res = pycard.encode_atmost(lits, bound, top_id, encoding, 1)

            # recovering default SIGINT handler
            def_sigint_handler = signal.signal(signal.SIGINT, def_sigint_handler)
        else:
            res = pycard.encode_atmost(lits, bound, top_id, encoding, 0)

        if res:
            ret.clauses, ret.nv = res

        # updating vpool if necessary
        if vpool:
            if vpool._occupied and vpool.top <= vpool._occupied[0][0] <= ret.nv:
                cls._update_vids(ret, vpool)
            else:
                vpool.top = ret.nv - 1
                vpool._next()

        return ret
Beispiel #4
0
    def merge_with(self, another, ubound=None, top_id=None):
        """
            This method merges a tree of the current :class:`ITotalizer`
            object, with a tree of another object and (if needed) increases a
            potential upper bound that can be imposed on the complete list of
            literals in the sum of an existing :class:`ITotalizer` object to a
            new value.

            :param another: another totalizer to merge with.
            :param ubound: a new upper bound.
            :param top_id: a new top variable identifier.

            :type another: :class:`ITotalizer`
            :type ubound: int
            :type top_id: integer or None

            The top identifier ``top_id`` applied only if it is greater than
            the one used in ``self``.

            This method creates additional clauses encoding the existing
            totalizer tree merged with another totalizer tree into *one* sum
            and updating the upper bound. As a result, it appends the new
            clauses to the list of clauses of :class:`.CNF` ``self.cnf``. The
            number of newly created clauses is stored in variable
            ``self.nof_new``.

            Also, if the upper bound is updated, a list of bounds ``self.rhs``
            gets increased and its length becomes ``ubound+1``. Otherwise, it
            is updated with new values.

            The method can be used in the following way:

            .. code-block:: python

                >>> from pysat.card import ITotalizer
                >>> with ITotalizer(lits=[1, 2], ubound=1) as t1:
                ...     print(t1.cnf.clauses)
                [[-2, 3], [-1, 3], [-1, -2, 4]]
                ...     print(t1.rhs)
                [3, 4]
                ...
                ...     t2 = ITotalizer(lits=[5, 6], ubound=1)
                ...     print(t1.cnf.clauses)
                [[-6, 7], [-5, 7], [-5, -6, 8]]
                ...     print(t1.rhs)
                [7, 8]
                ...
                ...     t1.merge_with(t2)
                ...     print(t1.cnf.clauses)
                [[-2, 3], [-1, 3], [-1, -2, 4], [-6, 7], [-5, 7], [-5, -6, 8], [-7, 9], [-8, 10], [-3, 9], [-4, 10], [-3, -7, 10]]
                ...     print(t1.cnf.clauses[-t1.nof_new:])
                [[-6, 7], [-5, 7], [-5, -6, 8], [-7, 9], [-8, 10], [-3, 9], [-4, 10], [-3, -7, 10]]
                ...     print(t1.rhs)
                [9, 10]
                ...
                ...     t2.delete()
        """

        self.top_id = max(self.top_id, top_id if top_id != None else 0, another.top_id)
        self.ubound = max(self.ubound, ubound if ubound != None else 0, another.ubound)

        # extending the list of input literals
        self.lits.extend(another.lits)

        # updating the object and adding more variables and clauses
        self.tobj, clauses, self.rhs, self.top_id = pycard.itot_mrg(self.tobj,
                another.tobj, self.ubound, self.top_id, int(MainThread.check()))

        # saving the result
        self.cnf.clauses.extend(another.cnf.clauses)
        self.cnf.clauses.extend(clauses)
        self.cnf.nv = self.top_id

        # for convenience, keeping the number of new clauses
        self.nof_new = len(another.cnf.clauses) + len(clauses)

        # memory deallocation should not be done for the merged tree
        another._merged = True
Beispiel #5
0
    def extend(self, lits=[], ubound=None, top_id=None):
        """
            Extends the list of literals in the sum and (if needed) increases a
            potential upper bound that can be imposed on the complete list of
            literals in the sum of an existing :class:`ITotalizer` object to a
            new value.

            :param lits: additional literals to be included in the sum.
            :param ubound: a new upper bound.
            :param top_id: a new top variable identifier.

            :type lits: iterable(int)
            :type ubound: int
            :type top_id: integer or None

            The top identifier ``top_id`` applied only if it is greater than
            the one used in ``self``.

            This method creates additional clauses encoding the existing
            totalizer tree augmented with new literals in the sum and updating
            the upper bound. As a result, it appends the new clauses to the
            list of clauses of :class:`.CNF` ``self.cnf``. The number of newly
            created clauses is stored in variable ``self.nof_new``.

            Also, if the upper bound is updated, a list of bounds ``self.rhs``
            gets increased and its length becomes ``ubound+1``. Otherwise, it
            is updated with new values.

            The method can be used in the following way:

            .. code-block:: python

                >>> from pysat.card import ITotalizer
                >>> t = ITotalizer(lits=[1, 2], ubound=1)
                >>> print(t.cnf.clauses)
                [[-2, 3], [-1, 3], [-1, -2, 4]]
                >>> print(t.rhs)
                [3, 4]
                >>>
                >>> t.extend(lits=[5], ubound=2)
                >>> print(t.cnf.clauses)
                [[-2, 3], [-1, 3], [-1, -2, 4], [-5, 6], [-3, 6], [-4, 7], [-3, -5, 7], [-4, -5, 8]]
                >>> print(t.cnf.clauses[-t.nof_new:])
                [[-5, 6], [-3, 6], [-4, 7], [-3, -5, 7], [-4, -5, 8]]
                >>> print(t.rhs)
                [6, 7, 8]
                >>> t.delete()
        """

        # preparing a new list of distinct input literals
        lits = sorted(set(lits).difference(set(self.lits)))

        if not lits:
            # nothing to merge with -> just increase the bound
            if ubound:
                self.increase(ubound=ubound, top_id=top_id)

            return

        self.top_id = max(map(lambda x: abs(x), self.lits + [self.top_id, top_id if top_id != None else 0]))
        self.ubound = max(self.ubound, ubound if ubound != None else 0)

        # updating the object and adding more variables and clauses
        self.tobj, clauses, self.rhs, self.top_id = pycard.itot_ext(self.tobj,
                lits, self.ubound, self.top_id, int(MainThread.check()))

        # saving the result
        self.cnf.clauses.extend(clauses)
        self.cnf.nv = self.top_id
        self.lits.extend(lits)

        # for convenience, keeping the number of new clauses
        self.nof_new = len(clauses)
Beispiel #6
0
    def increase(self, ubound=1, top_id=None):
        """
            Increases a potential upper bound that can be imposed on the
            literals in the sum of an existing :class:`ITotalizer` object to a
            new value.

            :param ubound: a new upper bound.
            :param top_id: a new top variable identifier.

            :type ubound: int
            :type top_id: integer or None

            The top identifier ``top_id`` applied only if it is greater than
            the one used in ``self``.

            This method creates additional clauses encoding the existing
            totalizer tree up to the new upper bound given and appends them to
            the list of clauses of :class:`.CNF` ``self.cnf``. The number of
            newly created clauses is stored in variable ``self.nof_new``.

            Also, a list of bounds ``self.rhs`` gets increased and its length
            becomes ``ubound+1``.

            The method can be used in the following way:

            .. code-block:: python

                >>> from pysat.card import ITotalizer
                >>> t = ITotalizer(lits=[1, 2, 3], ubound=1)
                >>> print(t.cnf.clauses)
                [[-2, 4], [-1, 4], [-1, -2, 5], [-4, 6], [-5, 7], [-3, 6], [-3, -4, 7]]
                >>> print(t.rhs)
                [6, 7]
                >>>
                >>> t.increase(ubound=2)
                >>> print(t.cnf.clauses)
                [[-2, 4], [-1, 4], [-1, -2, 5], [-4, 6], [-5, 7], [-3, 6], [-3, -4, 7], [-3, -5, 8]]
                >>> print(t.cnf.clauses[-t.nof_new:])
                [[-3, -5, 8]]
                >>> print(t.rhs)
                [6, 7, 8]
                >>> t.delete()
        """

        self.top_id = max(self.top_id, top_id if top_id != None else 0)

        # do nothing if the bound is set incorrectly
        if ubound <= self.ubound or self.ubound >= len(self.lits):
            self.nof_new = 0
            return
        else:
            self.ubound = ubound

        # updating the object and adding more variables and clauses
        clauses, self.rhs, self.top_id = pycard.itot_inc(self.tobj,
                self.ubound, self.top_id, int(MainThread.check()))

        # saving the result
        self.cnf.clauses.extend(clauses)
        self.cnf.nv = self.top_id

        # keeping the number of newly added clauses
        self.nof_new = len(clauses)
Beispiel #7
0
    def atleast(cls, lits, bound=1, top_id=None, vpool=None,
            encoding=EncType.seqcounter):
        """
            This method can be used for creating a CNF encoding of an AtLeastK
            constraint, i.e. of :math:`\sum_{i=1}^{n}{x_i}\geq k`. The method
            takes 1 mandatory argument ``lits`` and 3 default arguments can be
            specified: ``bound``, ``top_id``, ``vpool``, and ``encoding``.

            :param lits: a list of literals in the sum.
            :param bound: the value of bound :math:`k`.
            :param top_id: top variable identifier used so far.
            :param vpool: variable pool for counting the number of variables.
            :param encoding: identifier of the encoding to use.

            :type lits: iterable(int)
            :type bound: int
            :type top_id: integer or None
            :type vpool: :class:`.IDPool`
            :type encoding: integer

            Parameter ``top_id`` serves to increase integer identifiers of
            auxiliary variables introduced during the encoding process. This
            is helpful when augmenting an existing CNF formula with the new
            cardinality encoding to make sure there is no collision between
            identifiers of the variables. If specified, the identifiers of the
            first auxiliary variable will be ``top_id+1``.

            Instead of ``top_id``, one may want to use a pool of variable
            identifiers ``vpool``, which is automatically updated during the
            method call. In many circumstances, this is more convenient than
            using ``top_id``. Also note that parameters ``top_id`` and
            ``vpool`` **cannot** be specified *simultaneusly*.

            The default value of ``encoding`` is :attr:`Enctype.seqcounter`.

            The method *translates* the AtLeast constraint into an AtMost
            constraint by *negating* the literals of ``lits``, creating a new
            bound :math:`n-k` and invoking :meth:`CardEnc.atmost` with the
            modified list of literals and the new bound.

            :raises CardEnc.NoSuchEncodingError: if encoding does not exist.

            :rtype: a :class:`.CNFPlus` object where the new \
            clauses (or the new native atmost constraint) are stored.
        """

        if encoding < 0 or encoding > 9:
            raise(NoSuchEncodingError(encoding))

        assert not top_id or not vpool, \
                'Use either a top id or a pool of variables but not both.'

        # we are going to return this formula
        ret = CNFPlus()

        # if the list of literals is empty, return empty formula
        if not lits:
            return ret

        # obtaining the top id from the variable pool
        if vpool:
            top_id = vpool.top

        # choosing the maximum id among the current top and the list of literals
        top_id = max(map(lambda x: abs(x), lits + [top_id if top_id != None else 0]))

        # Minicard's native representation is handled separately
        if encoding == 9:
            ret.atmosts, ret.nv = [([-l for l in lits], len(lits) - bound)], top_id
            return ret

        res = pycard.encode_atleast(lits, bound, top_id, encoding,
                int(MainThread.check()))

        if res:
            ret.clauses, ret.nv = res

        # updating vpool if necessary
        if vpool:
            if vpool._occupied and vpool.top <= vpool._occupied[0][0] <= ret.nv:
                cls._update_vids(ret, vpool)
            else:
                vpool.top = ret.nv - 1
                vpool._next()

        return ret