Source code for murasyp.gambles

from __future__ import division
from collections import Set, Mapping
from murasyp.vectors import Vector, Polytope

[docs]class Gamble(Vector): """Gambles map states to utility payoffs This class derives from :class:`~murasyp.vectors.Vector`, so its methods apply here as well. What has changed: * There is a new constructor. If `data` is not a :class:`~collections.Mapping`, but is a :class:`~collections.Iterable` :class:`~collections.Hashable` :class:`~collections.Container`, then its so-called indicator function is generated. >>> assert Gamble('abc') == Gamble({'a': 1, 'c': 1, 'b': 1}) >>> assert Gamble({'abc'}) == Gamble({'abc': 1}) * A gamble's domain can be cylindrically extended to the cartesian product of its domain and a specified :class:`~collections.Set`. >>> assert ( ... Gamble({'a': 0, 'b': -1}) ^ {'c', 'd'} == ... Gamble({('b', 'c'): -1, ('a', 'd'): 0, ... ('a', 'c'): 0, ('b', 'd'): -1}) ... ) """ def __init__(self, data={}): """Create a gamble""" if isinstance(data, Mapping): # Hashable Mapping to Rational Vector.__init__(self, data) else: # indicator over Hashable Container Vector.__init__(self, {component: 1 for component in data}) def __xor__(self, other): """Cylindrical extension""" if isinstance(other, Set): return type(self)({(x, y): self[x] for x in self for y in other}) else: raise TypeError("the argument must be a Set")
[docs] def bounds(self): """The minimum and maximum values of the gamble :returns: the minimum and maximum values of the gamble :rtype: a pair (:class:`tuple`) of :class:`~fractions.Fraction` >>> Gamble({'a': 1, 'b': 3, 'c': 4}).bounds() (Fraction(1, 1), Fraction(4, 1)) >>> Gamble({}).bounds() (0, 0) """ values = self.range() if values != frozenset(): return (min(values), max(values)) else: return (0, 0)
[docs] def scaled_shifted(self): """Shifted and scaled version of the gamble :returns: a scaled and shifted version :math:`(f-\min f)/(\max f-\min f)` of the gamble :math:`f` :rtype: :class:`~murasyp.gambles.Gamble` >>> assert ( ... Gamble({'a': 1, 'b': 3, 'c': 4}).scaled_shifted() == ... Gamble({'a': 0, 'c': 1, 'b': '2/3'}) ... ) .. note:: ``None`` is returned in case the gamble is constant: >>> assert Gamble({'a': 2, 'b': 2}).scaled_shifted() == None """ minval, maxval = self.bounds() return None if maxval == minval else (self - minval) / (maxval - minval)
[docs] def norm(self): """The max-norm of the gamble :returns: the max-norm :math:`\|f\|_\infty=\max_{x\in\mathcal{X}}|f(x)|` of the gamble :math:`f` :rtype: :class:`~fractions.Fraction` >>> Gamble({'a': 1, 'b': 3, 'c': 4}).norm() Fraction(4, 1) """ minval, maxval = self.bounds() return max(-minval, maxval)
[docs] def normalized(self): """Max-norm normalized version of the gamble :returns: a normalized version :math:`f/\|f\|_\infty` of the gamble :math:`f` :rtype: :class:`~murasyp.gambles.Gamble` >>> assert ( ... Gamble({'a': 1, 'b': 3, 'c': 4}).normalized() == ... Gamble({'a': '1/4', 'c': 1, 'b': '3/4'}) ... ) .. note:: ``None`` is returned in case the the gamble's norm is zero. >>> Gamble({'a': 0}).normalized() == None True """ norm = self.norm() return None if norm == 0 else self / norm
[docs]class Ray(Gamble): """Rays directions in gamble space This class derives from :class:`~murasyp.gambles.Gamble`, so its methods apply here as well. What has changed: * Its domain coincides with its support and it is max-normalized. >>> assert Ray({'a': 5, 'b': -1, 'c': 0}) == Ray({'a': 1, 'b': '-1/5'}) >>> assert Ray({'a': 0}) == Ray({}) * Ray-arithmetic results in gambles (which can be converted to rays). >>> r = Ray({'a': 1,'b': -2}) >>> assert .3 * r * r + r / 2 == Gamble({'a': '13/40', 'b': '-1/5'}) """ def __init__(self, data={}): """Create a ray""" gamble = Gamble(data).normalized() if gamble == None: Gamble.__init__(self, {}) else: Gamble.__init__(self, gamble | gamble.support()) __add__ = lambda self, other: Gamble(self) + other __radd__ = __add__ __mul__ = lambda self, other: Gamble(self) * other __rmul__ = __mul__ __truediv__ = lambda self, other: Gamble(self) / other
[docs]class Cone(Polytope): """A frozenset of rays :type `data`: a non-:class:`~collections.Mapping` :class:`~collections.Iterable` :class:`~collections.Container` of arguments accepted by the :class:`~murasyp.gambles.Ray` constructor. >>> assert ( ... Cone([{'a': 2, 'b': 3}, {'b': 1, 'c': 4}]) == ... Cone({Ray({'a': '2/3', 'b': 1}), Ray({'c': 1, 'b': '1/4'})}) ... ) >>> assert ( ... Cone('abc') == ... Cone({Ray({'a': 1}), Ray({'b': 1}), Ray({'c': 1})}) ... ) >>> assert ( ... Cone({'ab', 'bc'}) == ... Cone({Ray({'c': 1, 'b': 1}), Ray({'a': 1, 'b': 1})}) ... ) This class derives from :class:`~murasyp.vectors.Polytope`, so its methods apply here as well. .. todo:: test all set methods and fix, or elegantly deal with, broken ones """ def __new__(cls, data=[]): """Create a frozenset of rays""" if isinstance(data, Mapping): raise TypeError(str(cls) + " does not accept a mapping," + " but you passed it " + str(data)) else: return frozenset.__new__(cls, (Ray(element) for element in data)) def __init__(self, data=[]): # only here for Sphinx to pick up the argument """Initialize the cone""" pass