예제 #1
0
 def get_lower(self, gamble, event=True, algorithm='linprog'):
     """Calculate lower expectation, using Algorithm 4 of Walley,
     Pelessoni, and Vicig (2004) [#walley2004]_. The algorithm
     deals properly with zero probabilities.
     """
     # set fastest algorithm
     if algorithm is None:
         algorithm = 'linprog'
     # check algorithm
     if algorithm != 'linprog':
         raise ValueError("invalid algorithm '{0}'".format(algorithm))
     # check avoiding sure loss (just in case)
     if not self.is_avoiding_sure_loss():
         raise ValueError(
             "lower prevision incurs sure loss:\n{0}".format(self))
     # get the matrix
     matrix = self.get_matrix(gamble, event)
     #print(matrix) # DEBUG
     linprog = cdd.LinProg(matrix)
     linprog.solve()
     #print(linprog) # DEBUG
     if linprog.status != cdd.LPStatusType.OPTIMAL:
         raise RuntimeError(
             "BUG: unexpected status (%i)\n"
             "gamble:\n%s\n"
             "conditioning event:\n%s\n"
             "lower prevision:\n%s\n"
             "matrix:\n%s\n"
             "linear program:\n%s\n" %
             (linprog.status, gamble, event, self, matrix, linprog))
     return linprog.obj_value
예제 #2
0
def test_another(number_type, assert_value_equal, assert_vector_equal):
    mat = cdd.Matrix([[1, -1, -1, -1], [-1, 1, 1, 1], [0, 1, 0, 0],
                      [0, 0, 1, 0], [0, 0, 0, 1]],
                     number_type=number_type)
    mat.obj_type = cdd.LPObjType.MIN
    mat.obj_func = (0, 1, 2, 3)
    lp = cdd.LinProg(mat)
    lp.solve()
    assert_value_equal(lp.obj_value, 1)
    mat.obj_func = (0, -1, -2, -3)
    lp = cdd.LinProg(mat)
    lp.solve()
    assert_value_equal(lp.obj_value, -3)
    mat.obj_func = (0, '1.12', '1.2', '1.3')
    lp = cdd.LinProg(mat)
    lp.solve()
    assert_value_equal(lp.obj_value, Fraction(28, 25))
    assert_vector_equal(lp.primal_solution, (1, 0, 0))
예제 #3
0
def test_lp2(number_type, assert_value_equal, assert_vector_equal):
    mat = cdd.Matrix([['4/3', -2, -1], ['2/3', 0, -1], [0, 1, 0], [0, 0, 1]],
                     number_type=number_type)
    mat.obj_type = cdd.LPObjType.MAX
    mat.obj_func = (0, 3, 4)
    lp = cdd.LinProg(mat)
    lp.solve()
    assert lp.status == cdd.LPStatusType.OPTIMAL
    assert_value_equal(lp.obj_value, Fraction(11, 3))
    assert_vector_equal(lp.primal_solution, (Fraction(1, 3), Fraction(2, 3)))
    assert_vector_equal(lp.dual_solution, (Fraction(3, 2), Fraction(5, 2)))
예제 #4
0
def cdd_solve_lp(c, G, h, A=None, b=None):
    """
    Solve a linear program defined by:

        minimize
            c.T * x

        subject to
            G * x <= h
            A * x == b

    using the LP solver from `cdd <https://github.com/mcmtroffaes/pycddlib>`_.

    Parameters
    ----------
    c : array, shape=(n,)
        Linear-cost vector.
    G : array, shape=(m, n)
        Linear inequality constraint matrix.
    h : array, shape=(m,)
        Linear inequality constraint vector.
    A : array, shape=(meq, n), optional
        Linear equality constraint matrix.
    b : array, shape=(meq,), optional
        Linear equality constraint vector.
    solver : string, optional
        Solver to use, default is GLPK if available

    Returns
    -------
    x : array, shape=(n,)
        Optimal (primal) solution of the LP, if one exists.

    Raises
    ------
    ValueError
        If the LP is not feasible.
    """
    if A is not None:
        v = hstack([h, b, -b])
        U = vstack([G, A, -A])
    else:  # no equality constraint
        v = h
        U = G
    v = v.reshape((v.shape[0], 1))
    mat = cdd.Matrix(hstack([v, -U]), number_type='float')
    mat.obj_type = cdd.LPObjType.MIN
    mat.obj_func = [0.] + list(c)
    lp = cdd.LinProg(mat)
    lp.solve()
    if lp.status != cdd.LPStatusType.OPTIMAL:
        raise ValueError("LP optimum not found: %s" % lp.status)
    return array(lp.primal_solution)
예제 #5
0
def solve_with_cdd_for_II(A, verbose=False):
    """This method finds II's minmax strategy for zero-sum game A"""
    m = A.shape[0]  # number of rows
    n = A.shape[1]  # number of columns

    A = np.column_stack([[0] * m, -A, [1] * m])

    I = np.eye(n)
    nn = np.column_stack([[0] * n, I, [0] * n])

    # non-negativity constraints
    n1 = [-1] * n
    n1.insert(0, 1)
    n1.append(0)  # n1 = 1,-1,-1,...,-1,0]
    n2 = [1] * n
    n2.insert(0, -1)
    n2.append(0)  # n1 = 1,-1,-1,...,-1,0]

    d = np.vstack([A, nn, n1, n2])

    mat = cdd.Matrix(d.tolist(), number_type='fraction')
    mat.obj_type = cdd.LPObjType.MIN

    d = [0] * (n + 1)
    d.append(1)  # [0,0,...0,1]
    mat.obj_func = d

    lp = cdd.LinProg(mat)
    lp.solve()
    lp.status == cdd.LPStatusType.OPTIMAL

    # lp.primal_solution uses fractions, and has value as last entry, so that
    # is dropped
    p = [float(val) for val in lp.primal_solution[:-1]]
    u = float(lp.obj_value)

    if verbose:
        print("------ Solved with cdd -------------")
        print("Optimal strategy:", p)
        print("Optimal payoff:", -u)
        print("------------------------------------")

    return p, -u
예제 #6
0
    def get_outer_approx(self, algorithm=None):
        """Generate an outer approximation.

        :parameter algorithm: a :class:`~string` denoting the algorithm used:
            ``None``, ``'linvac'``, ``'irm'``, ``'imrm'``, or ``'lpbelfunc'``
        :rtype: :class:`~improb.lowprev.lowprob.LowProb`

        This method replaces the lower probability :math:`\underline{P}` by
        a lower probability :math:`\underline{R}` determined by the
        ``algorithm`` argument:

        ``None``
            returns the original lower probability.

            >>> pspace = PSpace('abc')
            >>> lprob = LowProb(pspace,
            ...             lprob={'ab': .5, 'ac': .5, 'bc': .5},
            ...             number_type='fraction')
            >>> lprob.extend()
            >>> print(lprob)
                  : 0
            a     : 0
              b   : 0
                c : 0
            a b   : 1/2
            a   c : 1/2
              b c : 1/2
            a b c : 1
            >>> lprob == lprob.get_outer_approx()
            True

        ``'linvac'``
            replaces the imprecise part :math:`\underline{Q}` by the vacuous
            lower probability :math:`\underline{R}=\min` to generate a simple
            outer approximation.

        ``'irm'``
            replaces :math:`\underline{P}` by a completely monotone lower
            probability :math:`\underline{R}` that is obtained by using the
            IRM algorithm of Hall & Lawry [#hall2004]_. The Moebius transform
            of a lower probability that is not completely monotone contains
            negative belief assignments. Consider such a lower probability and
            an event with such a negative belief assignment. The approximation
            consists of removing this negative assignment and compensating for
            this by correspondingly reducing the positive masses for events
            below it; for details, see the paper.

            The following example illustrates the procedure:

            >>> pspace = PSpace('abc')
            >>> lprob = LowProb(pspace,
            ...             lprob={'ab': .5, 'ac': .5, 'bc': .5},
            ...             number_type='fraction')
            >>> lprob.extend()
            >>> print(lprob)
                  : 0
            a     : 0
              b   : 0
                c : 0
            a b   : 1/2
            a   c : 1/2
              b c : 1/2
            a b c : 1
            >>> lprob.is_completely_monotone()
            False
            >>> print(lprob.mobius)
                  : 0
            a     : 0
              b   : 0
                c : 0
            a b   : 1/2
            a   c : 1/2
              b c : 1/2
            a b c : -1/2
            >>> belfunc = lprob.get_outer_approx('irm')
            >>> print(belfunc.mobius)
                  : 0
            a     : 0
              b   : 0
                c : 0
            a b   : 1/3
            a   c : 1/3
              b c : 1/3
            a b c : 0
            >>> print(belfunc)
                  : 0
            a     : 0
              b   : 0
                c : 0
            a b   : 1/3
            a   c : 1/3
              b c : 1/3
            a b c : 1
            >>> belfunc.is_completely_monotone()
            True

            The next is Example 2 from Hall & Lawry's 2004 paper [#hall2004]_:

            >>> pspace = PSpace('ABCD')
            >>> lprob = LowProb(pspace, lprob={'': 0, 'ABCD': 1,
            ...                                'A': .0895, 'B': .2743,
            ...                                'C': .2668, 'D': .1063,
            ...                                'AB': .3947, 'AC': .4506,
            ...                                'AD': .2959, 'BC': .5837,
            ...                                'BD': .4835, 'CD': .4079,
            ...                                'ABC': .7248, 'ABD': .6224,
            ...                                'ACD': .6072, 'BCD': .7502})
            >>> lprob.is_avoiding_sure_loss()
            True
            >>> lprob.is_coherent()
            False
            >>> lprob.is_completely_monotone()
            False
            >>> belfunc = lprob.get_outer_approx('irm')
            >>> belfunc.is_completely_monotone()
            True
            >>> print(lprob)
                    : 0.0
            A       : 0.0895
              B     : 0.2743
                C   : 0.2668
                  D : 0.1063
            A B     : 0.3947
            A   C   : 0.4506
            A     D : 0.2959
              B C   : 0.5837
              B   D : 0.4835
                C D : 0.4079
            A B C   : 0.7248
            A B   D : 0.6224
            A   C D : 0.6072
              B C D : 0.7502
            A B C D : 1.0
            >>> print(belfunc)
                    : 0.0
            A       : 0.0895
              B     : 0.2743
                C   : 0.2668
                  D : 0.1063
            A B     : 0.375789766751
            A   C   : 0.405080300695
            A     D : 0.259553087227
              B C   : 0.560442004097
              B   D : 0.43812301076
                C D : 0.399034985143
            A B C   : 0.710712071543
            A B   D : 0.603365864737
            A   C D : 0.601068373065
              B C D : 0.7502
            A B C D : 1.0
            >>> print(lprob.mobius)
                    : 0.0
            A       : 0.0895
              B     : 0.2743
                C   : 0.2668
                  D : 0.1063
            A B     : 0.0309
            A   C   : 0.0943
            A     D : 0.1001
              B C   : 0.0426
              B   D : 0.1029
                C D : 0.0348
            A B C   : -0.0736
            A B   D : -0.0816
            A   C D : -0.0846
              B C D : -0.0775
            A B C D : 0.1748
            >>> print(belfunc.mobius)
                    : 0.0
            A       : 0.0895
              B     : 0.2743
                C   : 0.2668
                  D : 0.1063
            A B     : 0.0119897667507
            A   C   : 0.0487803006948
            A     D : 0.0637530872268
              B C   : 0.019342004097
              B   D : 0.0575230107598
                C D : 0.0259349851432
            A B C   : 3.33066907388e-16
            A B   D : -1.11022302463e-16
            A   C D : -1.11022302463e-16
              B C D : 0.0
            A B C D : 0.0357768453276
            >>> sum(lprev for (lprev, uprev)
            ...           in (lprob - belfunc).itervalues())/(2 ** len(pspace))
            0.013595658498933991

            .. note::

                This algorithm is *not* invariant under permutation of the
                possibility space.

            .. warning::

                The lower probability must be defined for all events. If
                needed, call :meth:`~improb.lowprev.lowpoly.LowPoly.extend`
                first.

        ``'imrm'``
            replaces :math:`\underline{P}` by a completely monotone lower
            probability :math:`\underline{R}` that is obtained by using an
            algorithm by Quaeghebeur that is as of yet unpublished.

            We apply it to Example 2 from Hall & Lawry's 2004 paper
            [#hall2004]_:

            >>> pspace = PSpace('ABCD')
            >>> lprob = LowProb(pspace, lprob={
            ...     '': 0, 'ABCD': 1,
            ...     'A': .0895, 'B': .2743,
            ...     'C': .2668, 'D': .1063,
            ...     'AB': .3947, 'AC': .4506,
            ...     'AD': .2959, 'BC': .5837,
            ...     'BD': .4835, 'CD': .4079,
            ...     'ABC': .7248, 'ABD': .6224,
            ...     'ACD': .6072, 'BCD': .7502})
            >>> belfunc = lprob.get_outer_approx('imrm')
            >>> belfunc.is_completely_monotone()
            True
            >>> print(lprob)
                    : 0.0
            A       : 0.0895
              B     : 0.2743
                C   : 0.2668
                  D : 0.1063
            A B     : 0.3947
            A   C   : 0.4506
            A     D : 0.2959
              B C   : 0.5837
              B   D : 0.4835
                C D : 0.4079
            A B C   : 0.7248
            A B   D : 0.6224
            A   C D : 0.6072
              B C D : 0.7502
            A B C D : 1.0
            >>> print(belfunc)
                    : 0.0
            A       : 0.0895
              B     : 0.2743
                C   : 0.2668
                  D : 0.1063
            A B     : 0.381007057096
            A   C   : 0.411644226231
            A     D : 0.26007767078
              B C   : 0.562748716673
              B   D : 0.4404197271
                C D : 0.394394926787
            A B C   : 0.7248
            A B   D : 0.6224
            A   C D : 0.6072
              B C D : 0.7502
            A B C D : 1.0
            >>> print(lprob.mobius)
                    : 0.0
            A       : 0.0895
              B     : 0.2743
                C   : 0.2668
                  D : 0.1063
            A B     : 0.0309
            A   C   : 0.0943
            A     D : 0.1001
              B C   : 0.0426
              B   D : 0.1029
                C D : 0.0348
            A B C   : -0.0736
            A B   D : -0.0816
            A   C D : -0.0846
              B C D : -0.0775
            A B C D : 0.1748
            >>> print(belfunc.mobius)
                    : 0.0
            A       : 0.0895
              B     : 0.2743
                C   : 0.2668
                  D : 0.1063
            A B     : 0.0172070570962
            A   C   : 0.0553442262305
            A     D : 0.0642776707797
              B C   : 0.0216487166733
              B   D : 0.0598197271
                C D : 0.0212949267869
            A B C   : 2.22044604925e-16
            A B   D : 0.0109955450242
            A   C D : 0.00368317620293
              B C D : 3.66294398528e-05
            A B C D : 0.00879232466651
            >>> sum(lprev for (lprev, uprev)
            ...           in (lprob - belfunc).itervalues())/(2 ** len(pspace))
            0.010375479708342836

            .. note::

                This algorithm *is* invariant under permutation of the
                possibility space.

            .. warning::

                The lower probability must be defined for all events. If
                needed, call :meth:`~improb.lowprev.lowpoly.LowPoly.extend`
                first.

        ``'lpbelfunc'``
            replaces :math:`\underline{P}` by a completely monotone lower
            probability :math:`\underline{R}_\mu` that is obtained via the zeta
            transform of the basic belief assignment :math:`\mu`, a solution of
            the following optimization (linear programming) problem:

            .. math::

                \min\{
                \sum_{A\subseteq\Omega}(\underline{P}(A)-\underline{R}_\mu(A)):
                \mu(A)\geq0, \sum_{B\subseteq\Omega}\mu(B)=1,
                \underline{R}_\mu(A)\leq\underline{P}(A), A\subseteq\Omega
                \},

            which, because constants in the objective function do not influence
            the solution and because
            :math:`\underline{R}_\mu(A)=\sum_{B\subseteq A}\mu(B)`,
            is equivalent to:

            .. math::

                \max\{
                \sum_{B\subseteq\Omega}2^{|\Omega|-|B|}\mu(B):
                \mu(A)\geq0, \sum_{B\subseteq\Omega}\mu(B)=1,
                \sum_{B\subseteq A}\mu(B)
                 \leq\underline{P}(A), A\subseteq\Omega
                \},

            the version that is implemented.

            We apply this to Example 2 from Hall & Lawry's 2004 paper
            [#hall2004]_, which we also used for ``'irm'``:

            >>> pspace = PSpace('ABCD')
            >>> lprob = LowProb(pspace, lprob={'': 0, 'ABCD': 1,
            ...                                'A': .0895, 'B': .2743,
            ...                                'C': .2668, 'D': .1063,
            ...                                'AB': .3947, 'AC': .4506,
            ...                                'AD': .2959, 'BC': .5837,
            ...                                'BD': .4835, 'CD': .4079,
            ...                                'ABC': .7248, 'ABD': .6224,
            ...                                'ACD': .6072, 'BCD': .7502})
            >>> belfunc = lprob.get_outer_approx('lpbelfunc')
            >>> belfunc.is_completely_monotone()
            True
            >>> print(lprob)
                    : 0.0
            A       : 0.0895
              B     : 0.2743
                C   : 0.2668
                  D : 0.1063
            A B     : 0.3947
            A   C   : 0.4506
            A     D : 0.2959
              B C   : 0.5837
              B   D : 0.4835
                C D : 0.4079
            A B C   : 0.7248
            A B   D : 0.6224
            A   C D : 0.6072
              B C D : 0.7502
            A B C D : 1.0
            >>> print(belfunc)
                    : 0.0
            A       : 0.0895
              B     : 0.2743
                C   : 0.2668
                  D : 0.1063
            A B     : 0.3638
            A   C   : 0.4079
            A     D : 0.28835
              B C   : 0.5837
              B   D : 0.44035
                C D : 0.37355
            A B C   : 0.7248
            A B   D : 0.6224
            A   C D : 0.6072
              B C D : 0.7502
            A B C D : 1.0
            >>> print(lprob.mobius)
                    : 0.0
            A       : 0.0895
              B     : 0.2743
                C   : 0.2668
                  D : 0.1063
            A B     : 0.0309
            A   C   : 0.0943
            A     D : 0.1001
              B C   : 0.0426
              B   D : 0.1029
                C D : 0.0348
            A B C   : -0.0736
            A B   D : -0.0816
            A   C D : -0.0846
              B C D : -0.0775
            A B C D : 0.1748
            >>> print(belfunc.mobius)
                    : 0.0
            A       : 0.0895
              B     : 0.2743
                C   : 0.2668
                  D : 0.1063
            A B     : 0.0
            A   C   : 0.0516
            A     D : 0.09255
              B C   : 0.0426
              B   D : 0.05975
                C D : 0.00045
            A B C   : 0.0
            A B   D : 1.11022302463e-16
            A   C D : 0.0
              B C D : 0.0
            A B C D : 0.01615
            >>> sum(lprev for (lprev, uprev)
            ...           in (lprob - belfunc).itervalues())/(2 ** len(pspace)
            ...     ) # doctest: +ELLIPSIS
            0.00991562...

            .. note::

                This algorithm is *not* invariant under permutation of the
                possibility space or changes in the LP-solver:
                there may be a nontrivial convex set of optimal solutions.

            .. warning::

                The lower probability must be defined for all events. If
                needed, call :meth:`~improb.lowprev.lowpoly.LowPoly.extend`
                first.

        """
        if algorithm is None:
            return self
        elif algorithm == 'linvac':
            prob, coeff = self.get_precise_part()
            return prob.get_linvac(1 - coeff)
        elif algorithm == 'irm':
            # Initialize the algorithm
            pspace = self.pspace
            bba = SetFunction(pspace, number_type=self.number_type)
            bba[False] = 0

            def mass_below(event):
                subevents = pspace.subsets(event, full=False, empty=False)
                return sum(bba[subevent] for subevent in subevents)

            def basin_for_negmass(event):
                mass = 0
                index = len(event)
                while bba[event] + mass < 0:
                    index -= 1
                    subevents = pspace.subsets(event, size=index)
                    mass += sum(bba[subevent] for subevent in subevents)
                return (index, mass)

            lprob = self.set_function
            # The algoritm itself:
            # we climb the algebra of events, calculating the belief assignment
            # for each and compensate negative ones by proportionally reducing
            # the assignments in the smallest basin of subevents needed
            for cardinality in range(1, len(pspace) + 1):
                for event in pspace.subsets(size=cardinality):
                    bba[event] = lprob[event] - mass_below(event)
                    if bba[event] < 0:
                        index, mass = basin_for_negmass(event)
                        subevents = chain.from_iterable(
                            pspace.subsets(event, size=k)
                            for k in range(index, cardinality))
                        for subevent in subevents:
                            bba[subevent] = (bba[subevent] *
                                             (1 + (bba[event] / mass)))
                        bba[event] = 0
            return LowProb(pspace,
                           lprob=dict((event, bba.get_zeta(event))
                                      for event in bba.iterkeys()))
        elif algorithm == 'imrm':
            # Initialize the algorithm
            pspace = self.pspace
            number_type = self.number_type
            bba = SetFunction(pspace, number_type=number_type)
            bba[False] = 0

            def mass_below(event, cardinality=None):
                subevents = pspace.subsets(event,
                                           full=False,
                                           empty=False,
                                           size=cardinality)
                return sum(bba[subevent] for subevent in subevents)

            def basin_for_negmass(event):
                mass = 0
                index = len(event)
                while bba[event] + mass < 0:
                    index -= 1
                    subevents = pspace.subsets(event, size=index)
                    mass += sum(bba[subevent] for subevent in subevents)
                return (index, mass)

            lprob = self.set_function
            # The algorithm itself:
            cardinality = 1
            while cardinality <= len(pspace):
                temp_bba = SetFunction(pspace, number_type=number_type)
                for event in pspace.subsets(size=cardinality):
                    bba[event] = lprob[event] - mass_below(event)
                offenders = dict((event, basin_for_negmass(event))
                                 for event in pspace.subsets(size=cardinality)
                                 if bba[event] < 0)
                if len(offenders) == 0:
                    cardinality += 1
                else:
                    minindex = min(pair[0] for pair in offenders.itervalues())
                    for event in offenders:
                        if offenders[event][0] == minindex:
                            mass = mass_below(event, cardinality=minindex)
                            scalef = (offenders[event][1] + bba[event]) / mass
                            for subevent in pspace.subsets(event,
                                                           size=minindex):
                                if subevent not in temp_bba:
                                    temp_bba[subevent] = 0
                                temp_bba[subevent] = max(
                                    temp_bba[subevent], scalef * bba[subevent])
                    for event, value in temp_bba.iteritems():
                        bba[event] = value
                    cardinality = minindex + 1
            return LowProb(pspace,
                           lprob=dict((event, bba.get_zeta(event))
                                      for event in bba.iterkeys()))
        elif algorithm == 'lpbelfunc':
            # Initialize the algorithm
            lprob = self.set_function
            pspace = lprob.pspace
            number_type = lprob.number_type
            n = 2**len(pspace)
            # Set up the linear program
            mat = cdd.Matrix(list(
                chain(
                    [[-1] + n * [1], [1] + n * [-1]],
                    [[0] + [int(event == other) for other in pspace.subsets()]
                     for event in pspace.subsets()],
                    [[lprob[event]] +
                     [-int(other <= event) for other in pspace.subsets()]
                     for event in pspace.subsets()])),
                             number_type=number_type)
            mat.obj_type = cdd.LPObjType.MAX
            mat.obj_func = (0, ) + tuple(2**(len(pspace) - len(event))
                                         for event in pspace.subsets())
            lp = cdd.LinProg(mat)
            # Solve the linear program and check the solution
            lp.solve()
            if lp.status == cdd.LPStatusType.OPTIMAL:
                bba = SetFunction(pspace,
                                  data=dict(
                                      izip(list(pspace.subsets()),
                                           list(lp.primal_solution))),
                                  number_type=number_type)
                return LowProb(pspace,
                               lprob=dict((event, bba.get_zeta(event))
                                          for event in bba.iterkeys()))
            else:
                raise RuntimeError('No optimal solution found.')
        else:
            raise NotImplementedError
예제 #7
0
 def _get_relevant_items(self, event=True, items=None):
     """Helper function for get_relevant_items."""
     # start with all items
     if items is None:
         items = set(self.iteritems())
     if not items:
         # special case: no items!
         return []
     compl_event = self.pspace.make_event(event).complement()
     if compl_event.is_false():
         # special case: unconditional, no need to check further
         return items
     # construct set of all conditioning events
     # (we need a variable tau_i for each of these)
     evs = set(ev for (ga, ev), (lprev, uprev) in items)
     num_evs = len(evs)
     # construct lists of lower and upper assessments
     # (we need a variable lambda_i for each of these)
     low_items = [((ga, ev), (lprev, uprev))
                  for (ga, ev), (lprev, uprev) in items
                  if lprev is not None]
     upp_items = [((ga, ev), (lprev, uprev))
                  for (ga, ev), (lprev, uprev) in items
                  if uprev is not None]
     num_items = len(low_items + upp_items)
     # construct the linear program
     matrix = cdd.Matrix(
         # tau_i >= 0
         [([0] + [(1 if i == j else 0) for i in xrange(num_evs)] +
           [0 for i in xrange(num_items)]) for j in xrange(num_evs)] +
         # tau_i <= 1
         [([1] + [(-1 if i == j else 0) for i in xrange(num_evs)] +
           [0 for i in xrange(num_items)]) for j in xrange(num_evs)] +
         # lambda_i >= 0
         [([0] + [0
                  for i in xrange(num_evs)] + [(1 if i == j else 0)
                                               for i in xrange(num_items)])
          for j in xrange(num_items)] +
         # sum_{i,j,k}
         #  - tau_k ev_k[omega]
         #  - lambda_i (ga_i[omega] - lprev_i)
         #  - lambda_j (uprev_j - ga_j[omega]) >= 0
         [([0] + [-1 if (omega in ev) else 0 for ev in evs] + [
             (lprev - ga[omega]) if omega in ev else 0
             for (ga, ev), (lprev, uprev) in low_items
         ] + [(ga[omega] - uprev) if omega in ev else 0
              for (ga, ev), (lprev, uprev) in upp_items])
          for omega in compl_event],
         number_type=self.number_type)
     matrix.rep_type = cdd.RepType.INEQUALITY
     matrix.obj_type = cdd.LPObjType.MAX
     # sum over all tau_i
     matrix.obj_func = [0] + [1] * num_evs + [0] * num_items
     #print(matrix) # DEBUG
     linprog = cdd.LinProg(matrix)
     linprog.solve()
     #print(linprog.primal_solution) # DEBUG
     if linprog.status != cdd.LPStatusType.OPTIMAL:
         raise RuntimeError("BUG: unexpected status (%i)\n"
                            "conditioning event:\n%s\n"
                            "lower prevision:\n%s" %
                            (linprog.status, event, self))
     # calculate set of events for which tau is 1
     new_evs = set()
     for tau, ev in itertools.izip(linprog.primal_solution[:num_evs], evs):
         if self.number_cmp(tau, 1) == 0:
             new_evs.add(ev)
         elif self.number_cmp(tau) != 0:
             raise RuntimeError(
                 "unexpected solution for tau: {0}".format(tau))
     # derive new set of items
     new_items = set(((ga, ev), (lprev, uprev))
                     for (ga, ev), (lprev, uprev) in items if ev in new_evs)
     if items == new_items:
         # if all tau were 1, we are done
         return items
     else:
         # otherwise, reiterate the algorithm with the reduced set
         # of items
         return self._get_relevant_items(event=event, items=new_items)
예제 #8
0
def cdd_solve_lp(
    c: np.ndarray,
    G: np.ndarray,
    h: np.ndarray,
    A: Optional[np.ndarray] = None,
    b: Optional[np.ndarray] = None,
) -> np.ndarray:
    """
    Solve a linear program defined by:

    .. math::

        \\begin{split}\\begin{array}{ll}
            \\mbox{minimize} &
                c^T x \\\\
            \\mbox{subject to}
                & G x \\leq h \\\\
                & A x = b
        \\end{array}\\end{split}

    using the LP solver from `cdd <https://github.com/mcmtroffaes/pycddlib>`_.

    Parameters
    ----------
    c :
        Linear cost vector.
    G :
        Linear inequality constraint matrix.
    h :
        Linear inequality constraint vector.
    A :
        Linear equality constraint matrix.
    b :
        Linear equality constraint vector.
    solver :
        Solver to use, default is GLPK if available

    Returns
    -------
    :
        Optimal (primal) solution of the linear program, if it exists.

    Raises
    ------
    ValueError
        If the linear program is not feasible.
    """
    if A is not None and b is not None:
        v = np.hstack([h, b, -b])
        U = np.vstack([G, A, -A])
    else:  # no equality constraint
        v = h
        U = G
    v = v.reshape((v.shape[0], 1))
    mat = cdd.Matrix(np.hstack([v, -U]), number_type="float")
    mat.obj_type = cdd.LPObjType.MIN
    mat.obj_func = [0.0] + list(c)
    lp = cdd.LinProg(mat)
    lp.solve()
    if lp.status != cdd.LPStatusType.OPTIMAL:
        raise ValueError(f"Linear program not feasible: {lp.status}")
    return np.array(lp.primal_solution)