from __future__ import division
from collections import Set, Mapping
from murasyp.vectors import Vector
from murasyp.gambles import Gamble
[docs]class UMFunc(Vector):
"""(Unit) mass functions map states or events to abstract mass values
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.Hashable`
:class:`~collections.Container`, then the mass is spread uniformly over
its components.
>>> assert UMFunc('abc') == UMFunc({'a': '1/3', 'c': '1/3', 'b': '1/3'})
* Its total mass, i.e., the sum of its values, is one and its domain
coincides with its support.
>>> assert (
... UMFunc({'a': -.6, 'b': 1.2, 'c': 1.4, 'd': 0}) ==
... UMFunc({'a': '-3/10', 'c': '7/10', 'b': '3/5'})
... )
>>> UMFunc({'a': -1, 'b': 1})
Traceback (most recent call last):
...
ValueError: no UMFunc can be constructed from a Mapping {'a': -1, 'b': 1} with ...
* Restriction becomes conditioning, i.e., it includes renormalization and
may be impossible if the conditioning event has zero net mass assigned.
>>> assert (
... UMFunc({'a': -.6, 'b': 1.2, 'c': 1.4}) | {'a', 'b', 'd'} ==
... UMFunc({'a': -1, 'b': 2})
... )
>>> UMFunc({'a': -1, 'b': 1, 'd': 1}) | {'a', 'c', 'd'}
Traceback (most recent call last):
...
ValueError: ...
* Mass functions can be used to to express weighted sums of
:class:`~murasyp.gambles.Gamble` values using standard product notation
(think 'expectation' if the mass function is a
:class:`~murasyp.massfuncs.PMFunc`).
>>> m = UMFunc({'a': 1.6, 'b': -.6})
>>> f = Gamble({'a': 13, 'b': -3})
>>> m * f
Fraction(113, 5)
Take note, however, that the domain of the gamble acts as a conditioning
event (so one can calculate conditional expectations without explicitly
calculating conditional mass functions).
>>> m = UMFunc({'a': '1/3', 'b': '1/6', 'c': '1/2'})
>>> f = Gamble({'a': 1, 'b': -1, 'c': 0})
>>> m * f
Fraction(1, 6)
>>> m * (f | f.support())
Fraction(1, 3)
>>> (m | f.support()) * f
Fraction(1, 3)
* Arithmetic with unit mass functions results in a vector, which may be
converted to a unit mass function in case it satisfies the conditions.
>>> m = UMFunc({'a': 1.7, 'b': -.7})
>>> n = UMFunc({'a': .5, 'b': .5})
>>> assert m + n == Vector({'a': '11/5', 'b': '-1/5'})
>>> assert UMFunc(.5 * m + n / 2) == UMFunc({'a': '11/10', 'b': '-1/10'})
"""
def __init__(self, data={}):
"""Create a unit mass function"""
if isinstance(data, Mapping): # Hashable Mapping to Rational
umfunc = Vector(data).sum_normalized()
if umfunc == None:
raise ValueError("no UMFunc can be constructed from a Mapping "
+ str(data) + " with a total mass of zero")
Vector.__init__(self, umfunc | umfunc.support())
else: # uniform over Hashable Container
Vector.__init__(self, {component: 1 / self._make_rational(len(data))
for component in data})
def __or__(self, other):
"""Mass function conditional on the given event"""
return type(self)(Vector(self) | other)
def __mul__(self, other):
"""'Expectation' of a gamble"""
if isinstance(other, Gamble):
pspace = self.domain() & other.domain()
return sum((self | pspace)[x] * other[x] for x in pspace)
else:
return Vector(self) * other
__add__ = lambda self, other: Vector(self) + other
__radd__ = __add__
__truediv__ = lambda self, other: Vector(self) / other
__rmul__ = __mul__
[docs]class PMFunc(UMFunc):
"""Probability mass functions map states to probability mass
This class derives from :class:`~murasyp.massfuncs.UMFunc`,
so its methods apply here as well.
What has changed:
* Its values have to be nonnegative.
>>> PMFunc({'a': -1, 'b': 2})
Traceback (most recent call last):
...
ValueError: no PMFunc can be constructed from a Mapping {'a': -1, 'b': 2} with ...
"""
def __init__(self, data):
"""Create a probability mass function"""
UMFunc.__init__(self, data)
if not self.is_nonnegative():
raise ValueError("no PMFunc can be constructed from a Mapping "
+ str(data) + " with negative values")