def make_random(cls, pspace=None, division=None, zero=True, number_type='float'): """Generate a random coherent lower probability.""" # for now this is just a pretty dumb method pspace = PSpace.make(pspace) lpr = cls(pspace=pspace, number_type=number_type) for event in pspace.subsets(): if len(event) == 0: continue if len(event) == len(pspace): continue gamble = event.indicator(number_type=number_type) if division is None: # a number between 0 and len(event) / len(pspace) lprob = random.random() * len(event) / len(pspace) else: # a number between 0 and len(event) / len(pspace) # but discretized lprob = Fraction( random.randint( 0 if zero else 1, (division * len(event)) // len(pspace)), division) # make a copy of lpr tmplpr = cls(pspace=pspace, mapping=lpr, number_type=number_type) # set new assignment, and give up if it incurs sure loss tmplpr.set_lower(gamble, lprob) if not tmplpr.is_avoiding_sure_loss(): break else: lpr.set_lower(gamble, lprob) # done! return coherent version of it return lpr.get_coherent()
def __init__(self, pspace, data=None): self._data = OrderedDict() self._pspace = PSpace.make(pspace) # extract data if isinstance(data, collections.Mapping): for key, value in data.iteritems(): self[key] = value elif data is not None: raise TypeError('data must be a mapping')
def make_random(cls, pspace=None, division=None, zero=True, number_type='float'): """Generate a random probability mass function. :param pspace: The possibility space. :type pspace: |pspacetype| :param division: If specified, probabilities will be divisible by this factor. :type division: :class:`int` :param zero: Whether to allow for zero probability. :type zero: :class:`bool` :param number_type: The number type. :type number_type: :class:`str` >>> import random >>> random.seed(25) >>> print(Prob.make_random("abcd", division=10)) a : 0.4 b : 0.0 c : 0.1 d : 0.5 >>> random.seed(25) >>> print(Prob.make_random("abcd", division=10, zero=False)) a : 0.3 b : 0.1 c : 0.2 d : 0.4 """ pspace = PSpace.make(pspace) lpr = cls(pspace=pspace, number_type=number_type) # get a uniform sample from the simplex probs = [-math.log(random.random()) for omega in pspace] sum_probs = sum(probs) probs = [prob / sum_probs for prob in probs] if division is not None: # discretize the probabilities probs = [ int(prob * division + 0.5) + (0 if zero else 1) for prob in probs ] # now, again ensure they sum to division while sum(probs) < division: probs[random.randrange(len(probs))] += 1 while sum(probs) > division: while True: idx = random.randrange(len(probs)) if probs[idx] > (1 if zero else 2): probs[idx] -= 1 break # convert to fraction probs = [fractions.Fraction(prob, division) for prob in probs] # return the probability return cls(pspace=pspace, number_type=number_type, prob=probs)
def make_random(cls, pspace=None, division=None, zero=True, number_type='float'): """Generate a random probability mass function. :param pspace: The possibility space. :type pspace: |pspacetype| :param division: If specified, probabilities will be divisible by this factor. :type division: :class:`int` :param zero: Whether to allow for zero probability. :type zero: :class:`bool` :param number_type: The number type. :type number_type: :class:`str` >>> import random >>> random.seed(25) >>> print(Prob.make_random("abcd", division=10)) a : 0.4 b : 0.0 c : 0.1 d : 0.5 >>> random.seed(25) >>> print(Prob.make_random("abcd", division=10, zero=False)) a : 0.3 b : 0.1 c : 0.2 d : 0.4 """ pspace = PSpace.make(pspace) lpr = cls(pspace=pspace, number_type=number_type) # get a uniform sample from the simplex probs = [-math.log(random.random()) for omega in pspace] sum_probs = sum(probs) probs = [prob / sum_probs for prob in probs] if division is not None: # discretize the probabilities probs = [int(prob * division + 0.5) + (0 if zero else 1) for prob in probs] # now, again ensure they sum to division while sum(probs) < division: probs[random.randrange(len(probs))] += 1 while sum(probs) > division: while True: idx = random.randrange(len(probs)) if probs[idx] > (1 if zero else 2): probs[idx] -= 1 break # convert to fraction probs = [fractions.Fraction(prob, division) for prob in probs] # return the probability return cls(pspace=pspace, number_type=number_type, prob=probs)
def make_random(cls, pspace=None, division=None, zero=True, number_type='float'): """Generate a random coherent lower probability. :param pspace: The possibility space. :type pspace: |pspacetype| :param division: If specified, generated numbers will be divisible by this factor. :type division: :class:`int` :param zero: Whether to allow for zero probability. :type zero: :class:`bool` :param number_type: The number type. :type number_type: :class:`str` .. todo:: Current implementation is highly inefficient. Refactor to use :meth:`improb.lowprev.lowpoly.LowPoly.make_random` instead. """ # for now this is just a pretty dumb method pspace = PSpace.make(pspace) lpr = cls(pspace=pspace, number_type=number_type) for event in pspace.subsets(): if len(event) == 0: continue if len(event) == len(pspace): continue gamble = event.indicator(number_type=number_type) if division is None: # a number between 0 and len(event) / len(pspace) lprob = random.random() * len(event) / len(pspace) else: # a number between 0 and len(event) / len(pspace) # but discretized lprob = Fraction( random.randint(0 if zero else 1, (division * len(event)) // len(pspace)), division) # make a copy of lpr tmplpr = cls(pspace=pspace, mapping=lpr, number_type=number_type) # set new assignment, and give up if it incurs sure loss tmplpr.set_lower(gamble, lprob) if not tmplpr.is_avoiding_sure_loss(): break else: lpr.set_lower(gamble, lprob) # done! return coherent version of it return lpr.get_coherent()
def make_random(cls, pspace=None, division=None, zero=True, number_type='float'): """Generate a random coherent lower probability. :param pspace: The possibility space. :type pspace: |pspacetype| :param division: If specified, generated numbers will be divisible by this factor. :type division: :class:`int` :param zero: Whether to allow for zero probability. :type zero: :class:`bool` :param number_type: The number type. :type number_type: :class:`str` .. todo:: Current implementation is highly inefficient. Refactor to use :meth:`improb.lowprev.lowpoly.LowPoly.make_random` instead. """ # for now this is just a pretty dumb method pspace = PSpace.make(pspace) lpr = cls(pspace=pspace, number_type=number_type) for event in pspace.subsets(): if len(event) == 0: continue if len(event) == len(pspace): continue gamble = event.indicator(number_type=number_type) if division is None: # a number between 0 and len(event) / len(pspace) lprob = random.random() * len(event) / len(pspace) else: # a number between 0 and len(event) / len(pspace) # but discretized lprob = Fraction( random.randint( 0 if zero else 1, (division * len(event)) // len(pspace)), division) # make a copy of lpr tmplpr = cls(pspace=pspace, mapping=lpr, number_type=number_type) # set new assignment, and give up if it incurs sure loss tmplpr.set_lower(gamble, lprob) if not tmplpr.is_avoiding_sure_loss(): break else: lpr.set_lower(gamble, lprob) # done! return coherent version of it return lpr.get_coherent()
def timeit(n=10, m=10): k = 10000 // n # number of trials, for timing gambles = [ dict((i, random.randint(1, m)) for i in xrange(n)) for j in xrange(k)] pspace = PSpace(n) s = SetFunction( pspace=pspace, number_type='fraction') # hack so we do not need to fill the set function with data # (this solves issues when testing for large n) s._data = defaultdict(lambda: random.randint(-len(pspace), len(pspace))) t = time.clock() for gamble in gambles: s.get_choquet(gamble) return time.clock() - t
def make_random(cls, pspace=None, division=None, zero=True, number_type='float'): """Generate a random probability mass function. >>> import random >>> random.seed(25) >>> print(Prob.make_random("abcd", division=10)) a : 0.4 b : 0.0 c : 0.1 d : 0.5 >>> random.seed(25) >>> print(Prob.make_random("abcd", division=10, zero=False)) a : 0.3 b : 0.1 c : 0.2 d : 0.4 """ pspace = PSpace.make(pspace) lpr = cls(pspace=pspace, number_type=number_type) # get a uniform sample from the simplex probs = [-math.log(random.random()) for omega in pspace] sum_probs = sum(probs) probs = [prob / sum_probs for prob in probs] if division is not None: # discretize the probabilities probs = [int(prob * division + 0.5) + (0 if zero else 1) for prob in probs] # now, again ensure they sum to division while sum(probs) < division: probs[random.randrange(len(probs))] += 1 while sum(probs) > division: while True: idx = random.randrange(len(probs)) if probs[idx] > (1 if zero else 2): probs[idx] -= 1 break # convert to fraction probs = [fractions.Fraction(prob, division) for prob in probs] # return the probability return cls(pspace=pspace, number_type=number_type, prob=probs)
def __init__(self, pspace, data=None, number_type=None): """Construct a set function on the power set of the given possibility space. :param pspace: The possibility space. :type pspace: |pspacetype| :param data: A mapping that defines the value on each event (missing values default to zero). :type data: :class:`dict` """ if number_type is None: if data is not None: number_type = cdd.get_number_type_from_sequences( data.itervalues()) else: number_type = 'float' cdd.NumberTypeable.__init__(self, number_type) self._pspace = PSpace.make(pspace) self._data = {} if data is not None: for event, value in data.iteritems(): self[event] = value
def make_extreme_n_monotone(cls, pspace, monotonicity=None): """Yield extreme lower probabilities with given monotonicity. .. warning:: Currently this doesn't work very well except for the cases below. >>> lprs = list(LowProb.make_extreme_n_monotone('abc', monotonicity=2)) >>> len(lprs) 8 >>> all(lpr.is_coherent() for lpr in lprs) True >>> all(lpr.is_n_monotone(2) for lpr in lprs) True >>> all(lpr.is_n_monotone(3) for lpr in lprs) False >>> lprs = list(LowProb.make_extreme_n_monotone('abc', monotonicity=3)) >>> len(lprs) 7 >>> all(lpr.is_coherent() for lpr in lprs) True >>> all(lpr.is_n_monotone(2) for lpr in lprs) True >>> all(lpr.is_n_monotone(3) for lpr in lprs) True >>> lprs = list(LowProb.make_extreme_n_monotone('abcd', monotonicity=2)) >>> len(lprs) 41 >>> all(lpr.is_coherent() for lpr in lprs) True >>> all(lpr.is_n_monotone(2) for lpr in lprs) True >>> all(lpr.is_n_monotone(3) for lpr in lprs) False >>> all(lpr.is_n_monotone(4) for lpr in lprs) False >>> lprs = list(LowProb.make_extreme_n_monotone('abcd', monotonicity=3)) >>> len(lprs) 16 >>> all(lpr.is_coherent() for lpr in lprs) True >>> all(lpr.is_n_monotone(2) for lpr in lprs) True >>> all(lpr.is_n_monotone(3) for lpr in lprs) True >>> all(lpr.is_n_monotone(4) for lpr in lprs) False >>> lprs = list(LowProb.make_extreme_n_monotone('abcd', monotonicity=4)) >>> len(lprs) 15 >>> all(lpr.is_coherent() for lpr in lprs) True >>> all(lpr.is_n_monotone(2) for lpr in lprs) True >>> all(lpr.is_n_monotone(3) for lpr in lprs) True >>> all(lpr.is_n_monotone(4) for lpr in lprs) True >>> # cddlib hangs on larger possibility spaces >>> #lprs = list(LowProb.make_extreme_n_monotone('abcde', monotonicity=2)) """ pspace = PSpace.make(pspace) # constraint for empty set and full set matrix = cdd.Matrix( [[0] + [1 if event.is_false() else 0 for event in pspace.subsets()], [-1] + [1 if event.is_true() else 0 for event in pspace.subsets()]], linear=True, number_type='fraction') # constraints for monotonicity constraints = [ dict(constraint) for constraint in cls.get_constraints_n_monotone(pspace, xrange(1, monotonicity + 1))] matrix.extend([[0] + [constraint.get(event, 0) for event in pspace.subsets()] for constraint in constraints]) matrix.rep_type = cdd.RepType.INEQUALITY # debug: simplify matrix #print(pspace, monotonicity) # debug #print("original:", len(matrix)) #matrix.canonicalize() #print("new :", len(matrix)) #print(matrix) # debug # calculate extreme points poly = cdd.Polyhedron(matrix) # convert these points back to lower probabilities #print(poly.get_generators()) # debug for vert in poly.get_generators(): yield cls( pspace=pspace, lprob=dict((event, vert[1 + index]) for index, event in enumerate(pspace.subsets())), number_type='fraction')
def get_constraints_n_monotone(cls, pspace, monotonicity=None): """Yields constraints for lower probabilities with given monotonicity. :param pspace: The possibility space. :type pspace: |pspacetype| :param monotonicity: Requested level of monotonicity (see notes below for details). :type monotonicity: :class:`int` or :class:`collections.Iterable` of :class:`int` As described in :meth:`~improb.setfunction.SetFunction.get_constraints_bba_n_monotone`, the n-monotonicity constraints on basic belief assignment are: .. math:: \sum_{B\colon C\subseteq B\subseteq A}m(B)\ge 0 for all :math:`C\subseteq A\subseteq\Omega`, with :math:`1\le|C|\le n`. By the Mobius transform, this is equivalent to: .. math:: \sum_{B\colon C\subseteq B\subseteq A} \sum_{D\colon D\subseteq B}(-1)^{|B\setminus D|} \underline{P}(D)\ge 0 Once noted that .. math:: (C\subseteq B\subseteq A\quad \& \quad D\subseteq B) \iff (C\cup D\subseteq B\subseteq A\quad \& \quad D\subseteq A), we can conveniently rewrite the sum as: .. math:: \sum_{D\colon D\subseteq A} \sum_{B\colon C\cup D\subseteq B\subseteq A}(-1)^{|B\setminus D|} \underline{P}(D)\ge 0 This implementation iterates over all :math:`C\subseteq A\subseteq\Omega`, with :math:`|C|=n`, and yields each constraint as an iterable of (event, coefficient) pairs, where zero coefficients are omitted. .. note:: As just mentioned, this method returns the constraints corresponding to the latter equation for :math:`|C|` equal to *monotonicity*. To get all the constraints for n-monotonicity, call this method with *monotonicity=xrange(1, n + 1)*. The rationale for this approach is that, in case you already know that (n-1)-monotonicity is satisfied, then you only need the constraints for *monotonicity=n* to check for n-monotonicity. .. note:: The trivial constraints that the empty set must have lower probability zero, and that the possibility space must have lower probability one, are not included: so for *monotonicity=0* this method returns an empty iterator. >>> pspace = PSpace("abc") >>> for mono in xrange(1, len(pspace) + 1): ... print("{0} monotonicity:".format(mono)) ... print(" ".join("{0:<{1}}".format("".join(i for i in event), len(pspace)) ... for event in pspace.subsets())) ... constraints = [ ... dict(constraint) for constraint in ... LowProb.get_constraints_n_monotone(pspace, mono)] ... constraints = [ ... [constraint.get(event, 0) for event in pspace.subsets()] ... for constraint in constraints] ... for constraint in sorted(constraints): ... print(" ".join("{0:<{1}}".format(value, len(pspace)) ... for value in constraint)) 1 monotonicity: a b c ab ac bc abc -1 0 0 1 0 0 0 0 -1 0 1 0 0 0 0 0 -1 1 0 0 0 0 0 0 0 -1 0 0 0 1 0 0 0 -1 0 0 1 0 0 0 0 0 -1 0 0 0 1 0 0 0 -1 0 1 0 0 0 0 0 0 -1 0 0 1 0 0 0 0 -1 0 1 0 0 0 0 0 0 -1 0 0 1 0 0 0 0 0 -1 0 1 0 0 0 0 0 0 -1 1 2 monotonicity: a b c ab ac bc abc 0 0 0 1 0 -1 -1 1 0 0 1 0 -1 0 -1 1 0 1 0 0 -1 -1 0 1 1 -1 -1 0 1 0 0 0 1 -1 0 -1 0 1 0 0 1 0 -1 -1 0 0 1 0 3 monotonicity: a b c ab ac bc abc -1 1 1 1 -1 -1 -1 1 """ pspace = PSpace.make(pspace) # check type if monotonicity is None: raise ValueError("specify monotonicity") elif isinstance(monotonicity, collections.Iterable): # special case: return it for all values in the iterable for mono in monotonicity: for constraint in cls.get_constraints_n_monotone(pspace, mono): yield constraint return elif not isinstance(monotonicity, (int, long)): raise TypeError("monotonicity must be integer") # check value if monotonicity < 0: raise ValueError("specify a non-negative monotonicity") if monotonicity == 0: # don't return constraints in this case return # yield all constraints for event_a in pspace.subsets(size=xrange(monotonicity, len(pspace) + 1)): for subevent_c in pspace.subsets(event_a, size=monotonicity): yield ( (subevent_d, sum((-1) ** len(event_b - subevent_d) for event_b in pspace.subsets( event_a, contains=(subevent_d | subevent_c)))) for subevent_d in pspace.subsets(event_a))
def __init__(self, pspace=None, mapping=None, lprev=None, uprev=None, prev=None, lprob=None, uprob=None, prob=None, bba=None, credalset=None, number_type=None): """Construct a polyhedral lower prevision on *pspace*. :param pspace: The possibility space. :type pspace: |pspacetype| :param mapping: Mapping from (gamble, event) to (lower prevision, upper prevision). :type mapping: :class:`collections.Mapping` :param lprev: Mapping from gamble to lower prevision. :type lprev: :class:`collections.Mapping` :param uprev: Mapping from gamble to upper prevision. :type uprev: :class:`collections.Mapping` :param prev: Mapping from gamble to precise prevision. :type prev: :class:`collections.Mapping` :param lprob: Mapping from event to lower probability. :type lprob: :class:`collections.Mapping` or :class:`collections.Sequence` :param uprob: Mapping from event to upper probability. :type uprob: :class:`collections.Mapping` or :class:`collections.Sequence` :param prob: Mapping from event to precise probability. :type prob: :class:`collections.Mapping` or :class:`collections.Sequence` :param bba: Mapping from event to basic belief assignment (useful for constructing belief functions). :type bba: :class:`collections.Mapping` :param credalset: Sequence of probability mass functions. :type credalset: :class:`collections.Sequence` :param number_type: The number type. If not specified, it is determined using :func:`~cdd.get_number_type_from_sequences` on all values. :type number_type: :class:`str` Generally, you can pass a :class:`dict` as a keyword argument in order to initialize the lower and upper previsions and/or probabilities: >>> print(LowPoly(pspace=3, mapping={ ... ((3, 1, 2), True): (1.5, None), ... ((1, 0, -1), (1, 2)): (0.25, 0.3)})) # doctest: +NORMALIZE_WHITESPACE 0 1 2 3.0 1.0 2.0 | 0 1 2 : [1.5 , ] 1.0 0.0 -1.0 | 1 2 : [0.25, 0.3 ] >>> print(LowPoly(pspace=3, ... lprev={(1, 3, 2): 1.5, (2, 0, -1): 1}, ... uprev={(2, 0, -1): 1.9}, ... prev={(9, 8, 20): 15}, ... lprob={(1, 2): 0.2, (1,): 0.1}, ... uprob={(1, 2): 0.3, (0,): 0.9}, ... prob={(2,): '0.3'})) # doctest: +NORMALIZE_WHITESPACE 0 1 2 0.0 0.0 1.0 | 0 1 2 : [0.3 , 0.3 ] 0.0 1.0 0.0 | 0 1 2 : [0.1 , ] 0.0 1.0 1.0 | 0 1 2 : [0.2 , 0.3 ] 1.0 0.0 0.0 | 0 1 2 : [ , 0.9 ] 1.0 3.0 2.0 | 0 1 2 : [1.5 , ] 2.0 0.0 -1.0 | 0 1 2 : [1.0 , 1.9 ] 9.0 8.0 20.0 | 0 1 2 : [15.0, 15.0] A credal set can be specified simply as a list: >>> print(LowPoly(pspace=3, ... credalset=[['0.1', '0.45', '0.45'], ... ['0.4', '0.3', '0.3'], ... ['0.3', '0.2', '0.5']])) 0 1 2 -10 10 0 | 0 1 2 : [-1, ] -1 -2 0 | 0 1 2 : [-1, ] 1 1 1 | 0 1 2 : [1 , 1 ] 50/23 40/23 0 | 0 1 2 : [1 , ] As a special case, for lower/upper/precise probabilities, if you need to set values on singletons, you can use a list instead of a dictionary: >>> print(LowPoly(pspace='abc', lprob=['0.1', '0.2', '0.3'])) # doctest: +NORMALIZE_WHITESPACE a b c 0 0 1 | a b c : [3/10, ] 0 1 0 | a b c : [1/5 , ] 1 0 0 | a b c : [1/10, ] If the first argument is a :class:`LowPoly` instance, then it is copied. For example: >>> from improb.lowprev.lowprob import LowProb >>> lpr = LowPoly(pspace='abc', lprob=['0.1', '0.1', '0.1']) >>> print(lpr) a b c 0 0 1 | a b c : [1/10, ] 0 1 0 | a b c : [1/10, ] 1 0 0 | a b c : [1/10, ] >>> lprob = LowProb(lpr) >>> print(lprob) a : 1/10 b : 1/10 c : 1/10 """ def iter_items(obj): """Return an iterator over all items of the mapping or the sequence. """ if isinstance(obj, collections.Mapping): return obj.iteritems() elif isinstance(obj, collections.Sequence): if len(obj) < len(self.pspace): raise ValueError('sequence too short') return (((omega,), value) for omega, value in itertools.izip(self.pspace, obj)) else: raise TypeError( 'expected collections.Mapping or collections.Sequence') def get_number_type(xprevs, xprobs): """Determine number type from arguments.""" # special case: nothing specified, defaults to float if (all(xprev is None for xprev in xprevs) and all(xprob is None for xprob in xprobs)): return 'float' # inspect all values for xprev in xprevs: if xprev is None: continue for key, value in xprev.iteritems(): # inspect gamble if isinstance(key, Gamble): if key.number_type == 'float': return 'float' elif isinstance(key, collections.Sequence): if cdd.get_number_type_from_sequences(key) == 'float': return 'float' elif isinstance(key, collections.Mapping): if cdd.get_number_type_from_sequences(key.itervalues()) == 'float': return 'float' # inspect value(s) if isinstance(value, collections.Sequence): if cdd.get_number_type_from_sequences(value) == 'float': return 'float' else: if cdd.get_number_type_from_value(value) == 'float': return 'float' for xprob in xprobs: if xprob is None: continue for key, value in iter_items(xprob): if cdd.get_number_type_from_value(value) == 'float': return 'float' # everything is fraction return 'fraction' # if first argument is a LowPoly, then override all other arguments if isinstance(pspace, LowPoly): mapping = dict(pspace.iteritems()) number_type = pspace.number_type pspace = pspace.pspace # initialize everything self._pspace = PSpace.make(pspace) if number_type is None: number_type = get_number_type( [mapping, lprev, uprev, prev, bba], [lprob, uprob, prob] + (credalset if credalset else [])) cdd.NumberTypeable.__init__(self, number_type) self._mapping = {} if mapping: for key, value in mapping.iteritems(): self[key] = value if lprev: for gamble, value in lprev.iteritems(): self.set_lower(gamble, value) if uprev: for gamble, value in uprev.iteritems(): self.set_upper(gamble, value) if prev: for gamble, value in prev.iteritems(): self.set_precise(gamble, value) if lprob: for event, value in iter_items(lprob): event = self.pspace.make_event(event) self.set_lower(event, value) if uprob: for event, value in iter_items(uprob): event = self.pspace.make_event(event) self.set_upper(event, value) if prob: for event, value in iter_items(prob): event = self.pspace.make_event(event) self.set_precise(event, value) if bba: setfunc = SetFunction( pspace=self.pspace, data=bba, number_type=self.number_type) for event in self.pspace.subsets(): self.set_lower(event, setfunc.get_zeta(event)) if credalset: # set up polyhedral representation mat = cdd.Matrix([(['1'] + credalprob) for credalprob in credalset]) mat.rep_type = cdd.RepType.GENERATOR poly = cdd.Polyhedron(mat) dualmat = poly.get_inequalities() #print(mat) #print(dualmat) for rownum, row in enumerate(dualmat): if rownum in dualmat.lin_set: self.set_precise(row[1:], -row[0]) else: self.set_lower(row[1:], -row[0])
def make_extreme_n_monotone(cls, pspace, monotonicity=None): """Yield extreme lower probabilities with given monotonicity. .. warning:: Currently this doesn't work very well except for the cases below. >>> lprs = list(LowProb.make_extreme_n_monotone('abc', monotonicity=2)) >>> len(lprs) 8 >>> all(lpr.is_coherent() for lpr in lprs) True >>> all(lpr.is_n_monotone(2) for lpr in lprs) True >>> all(lpr.is_n_monotone(3) for lpr in lprs) False >>> lprs = list(LowProb.make_extreme_n_monotone('abc', monotonicity=3)) >>> len(lprs) 7 >>> all(lpr.is_coherent() for lpr in lprs) True >>> all(lpr.is_n_monotone(2) for lpr in lprs) True >>> all(lpr.is_n_monotone(3) for lpr in lprs) True >>> lprs = list(LowProb.make_extreme_n_monotone('abcd', monotonicity=2)) >>> len(lprs) 41 >>> all(lpr.is_coherent() for lpr in lprs) True >>> all(lpr.is_n_monotone(2) for lpr in lprs) True >>> all(lpr.is_n_monotone(3) for lpr in lprs) False >>> all(lpr.is_n_monotone(4) for lpr in lprs) False >>> lprs = list(LowProb.make_extreme_n_monotone('abcd', monotonicity=3)) >>> len(lprs) 16 >>> all(lpr.is_coherent() for lpr in lprs) True >>> all(lpr.is_n_monotone(2) for lpr in lprs) True >>> all(lpr.is_n_monotone(3) for lpr in lprs) True >>> all(lpr.is_n_monotone(4) for lpr in lprs) False >>> lprs = list(LowProb.make_extreme_n_monotone('abcd', monotonicity=4)) >>> len(lprs) 15 >>> all(lpr.is_coherent() for lpr in lprs) True >>> all(lpr.is_n_monotone(2) for lpr in lprs) True >>> all(lpr.is_n_monotone(3) for lpr in lprs) True >>> all(lpr.is_n_monotone(4) for lpr in lprs) True >>> # cddlib hangs on larger possibility spaces >>> #lprs = list(LowProb.make_extreme_n_monotone('abcde', monotonicity=2)) """ pspace = PSpace.make(pspace) # constraint for empty set and full set matrix = cdd.Matrix([ [0] + [1 if event.is_false() else 0 for event in pspace.subsets()], [-1] + [1 if event.is_true() else 0 for event in pspace.subsets()] ], linear=True, number_type='fraction') # constraints for monotonicity constraints = [ dict(constraint) for constraint in cls.get_constraints_n_monotone( pspace, xrange(1, monotonicity + 1)) ] matrix.extend( [[0] + [constraint.get(event, 0) for event in pspace.subsets()] for constraint in constraints]) matrix.rep_type = cdd.RepType.INEQUALITY # debug: simplify matrix #print(pspace, monotonicity) # debug #print("original:", len(matrix)) #matrix.canonicalize() #print("new :", len(matrix)) #print(matrix) # debug # calculate extreme points poly = cdd.Polyhedron(matrix) # convert these points back to lower probabilities #print(poly.get_generators()) # debug for vert in poly.get_generators(): yield cls(pspace=pspace, lprob=dict( (event, vert[1 + index]) for index, event in enumerate(pspace.subsets())), number_type='fraction')
def __init__(self, pspace=None, mapping=None, lprev=None, uprev=None, prev=None, lprob=None, uprob=None, prob=None, bba=None, credalset=None, number_type=None): """Construct a polyhedral lower prevision on *pspace*. :param pspace: The possibility space. :type pspace: |pspacetype| :param mapping: Mapping from (gamble, event) to (lower prevision, upper prevision). :type mapping: :class:`collections.Mapping` :param lprev: Mapping from gamble to lower prevision. :type lprev: :class:`collections.Mapping` :param uprev: Mapping from gamble to upper prevision. :type uprev: :class:`collections.Mapping` :param prev: Mapping from gamble to precise prevision. :type prev: :class:`collections.Mapping` :param lprob: Mapping from event to lower probability. :type lprob: :class:`collections.Mapping` or :class:`collections.Sequence` :param uprob: Mapping from event to upper probability. :type uprob: :class:`collections.Mapping` or :class:`collections.Sequence` :param prob: Mapping from event to precise probability. :type prob: :class:`collections.Mapping` or :class:`collections.Sequence` :param bba: Mapping from event to basic belief assignment (useful for constructing belief functions). :type bba: :class:`collections.Mapping` :param credalset: Sequence of probability mass functions. :type credalset: :class:`collections.Sequence` :param number_type: The number type. If not specified, it is determined using :func:`~cdd.get_number_type_from_sequences` on all values. :type number_type: :class:`str` Generally, you can pass a :class:`dict` as a keyword argument in order to initialize the lower and upper previsions and/or probabilities: >>> print(LowPoly(pspace=3, mapping={ ... ((3, 1, 2), True): (1.5, None), ... ((1, 0, -1), (1, 2)): (0.25, 0.3)})) # doctest: +NORMALIZE_WHITESPACE 0 1 2 3.0 1.0 2.0 | 0 1 2 : [1.5 , ] 1.0 0.0 -1.0 | 1 2 : [0.25, 0.3 ] >>> print(LowPoly(pspace=3, ... lprev={(1, 3, 2): 1.5, (2, 0, -1): 1}, ... uprev={(2, 0, -1): 1.9}, ... prev={(9, 8, 20): 15}, ... lprob={(1, 2): 0.2, (1,): 0.1}, ... uprob={(1, 2): 0.3, (0,): 0.9}, ... prob={(2,): '0.3'})) # doctest: +NORMALIZE_WHITESPACE 0 1 2 0.0 0.0 1.0 | 0 1 2 : [0.3 , 0.3 ] 0.0 1.0 0.0 | 0 1 2 : [0.1 , ] 0.0 1.0 1.0 | 0 1 2 : [0.2 , 0.3 ] 1.0 0.0 0.0 | 0 1 2 : [ , 0.9 ] 1.0 3.0 2.0 | 0 1 2 : [1.5 , ] 2.0 0.0 -1.0 | 0 1 2 : [1.0 , 1.9 ] 9.0 8.0 20.0 | 0 1 2 : [15.0, 15.0] A credal set can be specified simply as a list: >>> print(LowPoly(pspace=3, ... credalset=[['0.1', '0.45', '0.45'], ... ['0.4', '0.3', '0.3'], ... ['0.3', '0.2', '0.5']])) 0 1 2 -10 10 0 | 0 1 2 : [-1, ] -1 -2 0 | 0 1 2 : [-1, ] 1 1 1 | 0 1 2 : [1 , 1 ] 50/23 40/23 0 | 0 1 2 : [1 , ] As a special case, for lower/upper/precise probabilities, if you need to set values on singletons, you can use a list instead of a dictionary: >>> print(LowPoly(pspace='abc', lprob=['0.1', '0.2', '0.3'])) # doctest: +NORMALIZE_WHITESPACE a b c 0 0 1 | a b c : [3/10, ] 0 1 0 | a b c : [1/5 , ] 1 0 0 | a b c : [1/10, ] If the first argument is a :class:`LowPoly` instance, then it is copied. For example: >>> from improb.lowprev.lowprob import LowProb >>> lpr = LowPoly(pspace='abc', lprob=['0.1', '0.1', '0.1']) >>> print(lpr) a b c 0 0 1 | a b c : [1/10, ] 0 1 0 | a b c : [1/10, ] 1 0 0 | a b c : [1/10, ] >>> lprob = LowProb(lpr) >>> print(lprob) a : 1/10 b : 1/10 c : 1/10 """ def iter_items(obj): """Return an iterator over all items of the mapping or the sequence. """ if isinstance(obj, collections.Mapping): return obj.iteritems() elif isinstance(obj, collections.Sequence): if len(obj) < len(self.pspace): raise ValueError('sequence too short') return (((omega, ), value) for omega, value in itertools.izip(self.pspace, obj)) else: raise TypeError( 'expected collections.Mapping or collections.Sequence') def get_number_type(xprevs, xprobs): """Determine number type from arguments.""" # special case: nothing specified, defaults to float if (all(xprev is None for xprev in xprevs) and all(xprob is None for xprob in xprobs)): return 'float' # inspect all values for xprev in xprevs: if xprev is None: continue for key, value in xprev.iteritems(): # inspect gamble if isinstance(key, Gamble): if key.number_type == 'float': return 'float' elif isinstance(key, collections.Sequence): if cdd.get_number_type_from_sequences(key) == 'float': return 'float' elif isinstance(key, collections.Mapping): if cdd.get_number_type_from_sequences( key.itervalues()) == 'float': return 'float' # inspect value(s) if isinstance(value, collections.Sequence): if cdd.get_number_type_from_sequences( value) == 'float': return 'float' else: if cdd.get_number_type_from_value(value) == 'float': return 'float' for xprob in xprobs: if xprob is None: continue for key, value in iter_items(xprob): if cdd.get_number_type_from_value(value) == 'float': return 'float' # everything is fraction return 'fraction' # if first argument is a LowPoly, then override all other arguments if isinstance(pspace, LowPoly): mapping = dict(pspace.iteritems()) number_type = pspace.number_type pspace = pspace.pspace # initialize everything self._pspace = PSpace.make(pspace) if number_type is None: number_type = get_number_type([mapping, lprev, uprev, prev, bba], [lprob, uprob, prob] + (credalset if credalset else [])) cdd.NumberTypeable.__init__(self, number_type) self._mapping = {} if mapping: for key, value in mapping.iteritems(): self[key] = value if lprev: for gamble, value in lprev.iteritems(): self.set_lower(gamble, value) if uprev: for gamble, value in uprev.iteritems(): self.set_upper(gamble, value) if prev: for gamble, value in prev.iteritems(): self.set_precise(gamble, value) if lprob: for event, value in iter_items(lprob): event = self.pspace.make_event(event) self.set_lower(event, value) if uprob: for event, value in iter_items(uprob): event = self.pspace.make_event(event) self.set_upper(event, value) if prob: for event, value in iter_items(prob): event = self.pspace.make_event(event) self.set_precise(event, value) if bba: setfunc = SetFunction(pspace=self.pspace, data=bba, number_type=self.number_type) for event in self.pspace.subsets(): self.set_lower(event, setfunc.get_zeta(event)) if credalset: # set up polyhedral representation mat = cdd.Matrix([(['1'] + credalprob) for credalprob in credalset]) mat.rep_type = cdd.RepType.GENERATOR poly = cdd.Polyhedron(mat) dualmat = poly.get_inequalities() #print(mat) #print(dualmat) for rownum, row in enumerate(dualmat): if rownum in dualmat.lin_set: self.set_precise(row[1:], -row[0]) else: self.set_lower(row[1:], -row[0])
def get_constraints_n_monotone(cls, pspace, monotonicity=None): """Yields constraints for lower probabilities with given monotonicity. :param pspace: The possibility space. :type pspace: |pspacetype| :param monotonicity: Requested level of monotonicity (see notes below for details). :type monotonicity: :class:`int` or :class:`collections.Iterable` of :class:`int` As described in :meth:`~improb.setfunction.SetFunction.get_constraints_bba_n_monotone`, the n-monotonicity constraints on basic belief assignment are: .. math:: \sum_{B\colon C\subseteq B\subseteq A}m(B)\ge 0 for all :math:`C\subseteq A\subseteq\Omega`, with :math:`1\le|C|\le n`. By the Mobius transform, this is equivalent to: .. math:: \sum_{B\colon C\subseteq B\subseteq A} \sum_{D\colon D\subseteq B}(-1)^{|B\setminus D|} \underline{P}(D)\ge 0 Once noted that .. math:: (C\subseteq B\subseteq A\quad \& \quad D\subseteq B) \iff (C\cup D\subseteq B\subseteq A\quad \& \quad D\subseteq A), we can conveniently rewrite the sum as: .. math:: \sum_{D\colon D\subseteq A} \sum_{B\colon C\cup D\subseteq B\subseteq A}(-1)^{|B\setminus D|} \underline{P}(D)\ge 0 This implementation iterates over all :math:`C\subseteq A\subseteq\Omega`, with :math:`|C|=n`, and yields each constraint as an iterable of (event, coefficient) pairs, where zero coefficients are omitted. .. note:: As just mentioned, this method returns the constraints corresponding to the latter equation for :math:`|C|` equal to *monotonicity*. To get all the constraints for n-monotonicity, call this method with *monotonicity=xrange(1, n + 1)*. The rationale for this approach is that, in case you already know that (n-1)-monotonicity is satisfied, then you only need the constraints for *monotonicity=n* to check for n-monotonicity. .. note:: The trivial constraints that the empty set must have lower probability zero, and that the possibility space must have lower probability one, are not included: so for *monotonicity=0* this method returns an empty iterator. >>> pspace = PSpace("abc") >>> for mono in xrange(1, len(pspace) + 1): ... print("{0} monotonicity:".format(mono)) ... print(" ".join("{0:<{1}}".format("".join(i for i in event), len(pspace)) ... for event in pspace.subsets())) ... constraints = [ ... dict(constraint) for constraint in ... LowProb.get_constraints_n_monotone(pspace, mono)] ... constraints = [ ... [constraint.get(event, 0) for event in pspace.subsets()] ... for constraint in constraints] ... for constraint in sorted(constraints): ... print(" ".join("{0:<{1}}".format(value, len(pspace)) ... for value in constraint)) 1 monotonicity: a b c ab ac bc abc -1 0 0 1 0 0 0 0 -1 0 1 0 0 0 0 0 -1 1 0 0 0 0 0 0 0 -1 0 0 0 1 0 0 0 -1 0 0 1 0 0 0 0 0 -1 0 0 0 1 0 0 0 -1 0 1 0 0 0 0 0 0 -1 0 0 1 0 0 0 0 -1 0 1 0 0 0 0 0 0 -1 0 0 1 0 0 0 0 0 -1 0 1 0 0 0 0 0 0 -1 1 2 monotonicity: a b c ab ac bc abc 0 0 0 1 0 -1 -1 1 0 0 1 0 -1 0 -1 1 0 1 0 0 -1 -1 0 1 1 -1 -1 0 1 0 0 0 1 -1 0 -1 0 1 0 0 1 0 -1 -1 0 0 1 0 3 monotonicity: a b c ab ac bc abc -1 1 1 1 -1 -1 -1 1 """ pspace = PSpace.make(pspace) # check type if monotonicity is None: raise ValueError("specify monotonicity") elif isinstance(monotonicity, collections.Iterable): # special case: return it for all values in the iterable for mono in monotonicity: for constraint in cls.get_constraints_n_monotone(pspace, mono): yield constraint return elif not isinstance(monotonicity, (int, long)): raise TypeError("monotonicity must be integer") # check value if monotonicity < 0: raise ValueError("specify a non-negative monotonicity") if monotonicity == 0: # don't return constraints in this case return # yield all constraints for event_a in pspace.subsets(size=xrange(monotonicity, len(pspace) + 1)): for subevent_c in pspace.subsets(event_a, size=monotonicity): yield ((subevent_d, sum((-1)**len(event_b - subevent_d) for event_b in pspace.subsets( event_a, contains=(subevent_d | subevent_c)))) for subevent_d in pspace.subsets(event_a))
def get_constraints_bba_n_monotone(cls, pspace, monotonicity=None): """Yields constraints for basic belief assignments with given monotonicity. :param pspace: The possibility space. :type pspace: |pspacetype| :param monotonicity: Requested level of monotonicity (see notes below for details). :type monotonicity: :class:`int` or :class:`collections.Iterable` of :class:`int` This follows the algorithm described in Proposition 2 (for 1-monotonicity) and Proposition 4 (for n-monotonicity) of *Chateauneuf and Jaffray, 1989. Some characterizations of lower probabilities and other monotone capacities through the use of Mobius inversion. Mathematical Social Sciences 17(3), pages 263-283*: A set function :math:`s` defined on the power set of :math:`\Omega` is :math:`n`-monotone if and only if its Mobius transform :math:`m` satisfies: .. math:: m(\emptyset)=0, \qquad\sum_{A\subseteq\Omega} m(A)=1, and .. math:: \sum_{B\colon C\subseteq B\subseteq A} m(B)\ge 0 for all :math:`C\subseteq A\subseteq\Omega`, with :math:`1\le|C|\le n`. This implementation iterates over all :math:`C\subseteq A\subseteq\Omega`, with :math:`|C|=n`, and yields each constraint as an iterable of the events :math:`\{B\colon C\subseteq B\subseteq A\}`. For example, you can then check the constraint by summing over this iterable. .. note:: As just mentioned, this method returns the constraints corresponding to the latter equation for :math:`|C|` equal to *monotonicity*. To get all the constraints for n-monotonicity, call this method with *monotonicity=xrange(1, n + 1)*. The rationale for this approach is that, in case you already know that (n-1)-monotonicity is satisfied, then you only need the constraints for *monotonicity=n* to check for n-monotonicity. .. note:: The trivial constraints that the empty set must have mass zero, and that the masses must sum to one, are not included: so for *monotonicity=0* this method returns an empty iterator. >>> pspace = "abc" >>> for mono in xrange(1, len(pspace) + 1): ... print("{0} monotonicity:".format(mono)) ... print(" ".join("{0:<{1}}".format("".join(i for i in event), len(pspace)) ... for event in PSpace(pspace).subsets())) ... constraints = SetFunction.get_constraints_bba_n_monotone(pspace, mono) ... constraints = [set(constraint) for constraint in constraints] ... constraints = [[1 if event in constraint else 0 ... for event in PSpace(pspace).subsets()] ... for constraint in constraints] ... for constraint in sorted(constraints): ... print(" ".join("{0:<{1}}" ... .format(value, len(pspace)) ... for value in constraint)) 1 monotonicity: a b c ab ac bc abc 0 0 0 1 0 0 0 0 0 0 0 1 0 0 1 0 0 0 0 1 0 1 0 0 0 0 0 1 0 1 1 1 0 0 1 0 0 0 0 0 0 0 1 0 0 0 1 0 0 0 1 0 1 0 0 0 0 0 1 0 1 0 1 1 0 1 0 0 0 0 0 0 0 1 0 0 0 1 0 0 0 1 0 0 1 0 0 0 0 1 0 0 1 1 0 1 2 monotonicity: a b c ab ac bc abc 0 0 0 0 0 0 1 0 0 0 0 0 0 0 1 1 0 0 0 0 0 1 0 0 0 0 0 0 0 1 0 1 0 0 0 0 1 0 0 0 0 0 0 0 1 0 0 1 3 monotonicity: a b c ab ac bc abc 0 0 0 0 0 0 0 1 """ pspace = PSpace.make(pspace) # check type if monotonicity is None: raise ValueError("specify monotonicity") elif isinstance(monotonicity, collections.Iterable): # special case: return it for all values in the iterable for mono in monotonicity: for constraint in cls.get_constraints_bba_n_monotone( pspace, mono): yield constraint return elif not isinstance(monotonicity, (int, long)): raise TypeError("monotonicity must be integer") # check value if monotonicity < 0: raise ValueError("specify a non-negative monotonicity") if monotonicity == 0: # don't return constraints in this case return # yield all constraints for event in pspace.subsets(size=xrange(monotonicity, len(pspace) + 1)): for subevent in pspace.subsets(event, size=monotonicity): yield pspace.subsets(event, contains=subevent)
def __init__(self, pspace, number_type=None): if number_type is None: number_type = 'float' cdd.NumberTypeable.__init__(self, number_type) self._pspace = PSpace.make(pspace)
def get_constraints_bba_n_monotone(cls, pspace, monotonicity=None): """Yields constraints for basic belief assignments with given monotonicity. :param pspace: The possibility space. :type pspace: |pspacetype| :param monotonicity: Requested level of monotonicity (see notes below for details). :type monotonicity: :class:`int` or :class:`collections.Iterable` of :class:`int` This follows the algorithm described in Proposition 2 (for 1-monotonicity) and Proposition 4 (for n-monotonicity) of *Chateauneuf and Jaffray, 1989. Some characterizations of lower probabilities and other monotone capacities through the use of Mobius inversion. Mathematical Social Sciences 17(3), pages 263-283*: A set function :math:`s` defined on the power set of :math:`\Omega` is :math:`n`-monotone if and only if its Mobius transform :math:`m` satisfies: .. math:: m(\emptyset)=0, \qquad\sum_{A\subseteq\Omega} m(A)=1, and .. math:: \sum_{B\colon C\subseteq B\subseteq A} m(B)\ge 0 for all :math:`C\subseteq A\subseteq\Omega`, with :math:`1\le|C|\le n`. This implementation iterates over all :math:`C\subseteq A\subseteq\Omega`, with :math:`|C|=n`, and yields each constraint as an iterable of the events :math:`\{B\colon C\subseteq B\subseteq A\}`. For example, you can then check the constraint by summing over this iterable. .. note:: As just mentioned, this method returns the constraints corresponding to the latter equation for :math:`|C|` equal to *monotonicity*. To get all the constraints for n-monotonicity, call this method with *monotonicity=xrange(1, n + 1)*. The rationale for this approach is that, in case you already know that (n-1)-monotonicity is satisfied, then you only need the constraints for *monotonicity=n* to check for n-monotonicity. .. note:: The trivial constraints that the empty set must have mass zero, and that the masses must sum to one, are not included: so for *monotonicity=0* this method returns an empty iterator. >>> pspace = "abc" >>> for mono in xrange(1, len(pspace) + 1): ... print("{0} monotonicity:".format(mono)) ... print(" ".join("{0:<{1}}".format("".join(i for i in event), len(pspace)) ... for event in PSpace(pspace).subsets())) ... constraints = SetFunction.get_constraints_bba_n_monotone(pspace, mono) ... constraints = [set(constraint) for constraint in constraints] ... constraints = [[1 if event in constraint else 0 ... for event in PSpace(pspace).subsets()] ... for constraint in constraints] ... for constraint in sorted(constraints): ... print(" ".join("{0:<{1}}" ... .format(value, len(pspace)) ... for value in constraint)) 1 monotonicity: a b c ab ac bc abc 0 0 0 1 0 0 0 0 0 0 0 1 0 0 1 0 0 0 0 1 0 1 0 0 0 0 0 1 0 1 1 1 0 0 1 0 0 0 0 0 0 0 1 0 0 0 1 0 0 0 1 0 1 0 0 0 0 0 1 0 1 0 1 1 0 1 0 0 0 0 0 0 0 1 0 0 0 1 0 0 0 1 0 0 1 0 0 0 0 1 0 0 1 1 0 1 2 monotonicity: a b c ab ac bc abc 0 0 0 0 0 0 1 0 0 0 0 0 0 0 1 1 0 0 0 0 0 1 0 0 0 0 0 0 0 1 0 1 0 0 0 0 1 0 0 0 0 0 0 0 1 0 0 1 3 monotonicity: a b c ab ac bc abc 0 0 0 0 0 0 0 1 """ pspace = PSpace.make(pspace) # check type if monotonicity is None: raise ValueError("specify monotonicity") elif isinstance(monotonicity, collections.Iterable): # special case: return it for all values in the iterable for mono in monotonicity: for constraint in cls.get_constraints_bba_n_monotone(pspace, mono): yield constraint return elif not isinstance(monotonicity, (int, long)): raise TypeError("monotonicity must be integer") # check value if monotonicity < 0: raise ValueError("specify a non-negative monotonicity") if monotonicity == 0: # don't return constraints in this case return # yield all constraints for event in pspace.subsets(size=xrange(monotonicity, len(pspace) + 1)): for subevent in pspace.subsets(event, size=monotonicity): yield pspace.subsets(event, contains=subevent)