Source code for pynfg.classes.chancenode

# -*- coding: utf-8 -*-
"""
Implements the ChanceNode class

Part of: PyNFG - a Python package for modeling and solving Network Form Games

Created on Mon Feb 18 10:35:19 2013

Copyright (C) 2013 James Bono (jwbono@gmail.com)

GNU Affero General Public License

"""

from __future__ import division
import inspect
import numpy as np
import scipy as sp
import scipy.stats.distributions as randvars
from node import *

[docs]class ChanceNode(Node): """Implements a chance node of the semi-NFG formalism created by D. Wolpert The :class:`classes.ChanceNode` can be initialized with either a conditional probability distribution (CPT) or a distribution object from :py:mod:`scipy.stats.distributions` (discrete and continuous types are both supported). :arg name: the name of the ChanceNode, usually descriptive, e.g. C5 for the fifth chance node, or C21 for the second chance node in the first time step, etc. :type name: str :arg CPTip: The input parameters for a chance node based on a CPT. It is a tuple with the following three elements: * CPTip[0]: np.array giving CPT - in order given by parent spaces * CPTip[1]: list of parents - in order of dims of CPT * CPTip[2]: list of space - in order of last dim of CPT :type CPTip: tuple :arg distip: The input parameters for a chance node based on a distribution from the :py:mod:`scipy.stats.distributions` module. It is a tuple with the following three elements:: * distip[0]: :py:mod:`scipy.stats.distributions` distribution object * distip[1]: list of distribution params - order given by distribution args. * distip[2]: (None if distribution object is continuous) list of space if distribution object is discrete. :type distip: tuple :arg description: a description of the chance node, usually including a summary description of the distribution, space, parents and children. :type description: str :arg time: the timestep to which the node belongs. This is generally only used when the node is in a :class:`seminfg.iterSemiNFG`. :type time: int :arg basename: This is only used when the node is in a :class:`seminfg.iterSemiNFG`. It references a theoretical node in the base or kernel. :type basename: str .. note:: For a :class:`classes.ChanceNode` based on a CPT, the parents must be discrete valued nodes. The dimensions of the CPT must correspond to the order of the parents. The order of the CPT in each dimension must correspond to the order of the parent space for that dimension. Formally, a chance node has the following properties: * belongs to the *nature* player * has a space of possible values * has a conditional probability distribution from the values of its parents - given by :py:meth:`classes.DecisionNode.prob()` or :py:meth:`classes.ChanceNode.prob()`. Example:: import pynfg import scipy.stats.distributions as randvars D1 = DecisionNode('D1', '1', [-1, 0, 1], parents=[], description='This is a child node of C1') dist1 = randvars.norm params1 = [D1, 2] distip1 = (dist1, params1) C1 = ChanceNode('C1', distip=distip1, description='CN norm rv with scale=2 and loc=D1') or:: import pynfg import scipy.stats.distributions as randvars dist1 = randvars.randint params1 = [5, 10] distip1 = (dist1, params1) C1 = ChanceNode('C1', distip=distip1, description='root CN randint from 5 to 10') dist2 = randvars.hypergeom params2 = [C1, 3, 3] space2 = [0, 1, 2, 3] distip2 = (dist2, params2, space2) C2 = ChanceNode('C2', distip=distip1, description='CN hypergeom M=C1, n=3, N=3') Upon initialization, the following private method is called: :py:meth:`classes.ChanceNode._set_parent_dict()` Some useful methods are: * :py:meth:`classes.ChanceNode.draw_value()` * :py:meth:`classes.ChanceNode.prob()` * :py:meth:`classes.ChanceNode.logprob()` """ def __init__(self, name, CPTip=None, distip=None, \ description='no description', time=None, basename=None, \ verbose=False): if verbose: try: print 'Name: '+ name + '\nDescription: '+ description except TypeError: print('name and description should be strings') self.name = name self.description = description self.player = 'nature' self.time = time self.basename = basename if distip is None: self.CPT = CPTip[0] self.parents = self._set_parent_dict(CPTip[1]) self._check_disc_parents() if isinstance(CPTip[2], list): self.space = CPTip[2] else: raise TypeError('The space, CPTip[2], must be a list') self.continuous = False else: self.CPT = None self.distribution = distip[0] self.params = distip[1] parlist = filter(lambda x: isinstance(x,Node), self.params) self.parents = self._set_parent_dict(parlist) self.continuous = (randvars.rv_continuous in \ inspect.getmro(type(self.distribution))) if self.continuous is False: self.space = distip[2] else: self.space = [] # self.draw_value() def __str__(self): return self.name
[docs] def draw_value(self, parentinput=None, setvalue=True): """Draw a value from the :class:`classes.ChanceNode` object :arg parentinput: Optional. Specify values of the parents at which to draw values from the conditional probability distribution. Keys are parent names. Values are parent values. To specify values for only a subset of the parents, only enter those parents in the dictionary. If no parent values are specified, then the current values of the parents are used. :type parentinput: dict :arg setvalue: (Optional) determines if the random draw replaces :py:attr:`classes.ChanceNode.value`. True by default. :type setvalue: bool :returns: an element of :py:attr:`classes.ChanceNode.space` if the ChanceNode object is discrete. For continuous ChanceNode objects, it returns a value at which the pdf is nonzero. .. note:: If parent values are specified in parentinput, those values must be legitimate values of the parent. For discrete parents, the values must correspond to an item in the parent's space attribute. """ if parentinput is None: parentinput = {} if self.CPT is None: arglist = [] for val in self.params: if isinstance(val,Node): if val.name in parentinput.keys(): arglist.append(parentinput[val.name]) else: arglist.append(val.get_value()) else: arglist.append(val) argtuple = tuple(arglist) r = self.distribution.rvs(*argtuple) if setvalue: self.set_value(r) return self.get_value() else: return r else: indo = self.get_CPTindex(parentinput, valueinput=False) cdf = np.cumsum(self.CPT[indo]) cutoff = np.random.rand() idx = np.nonzero( cdf >= cutoff )[0][0] if setvalue: self.set_valueindex(idx) return self.get_value() else: return self.space[idx]
[docs] def prob(self, parentinput=None, valueinput=None): """Compute the conditional probability of the current or specified value :arg parentinput: Optional. Specify values of the parents at which to compute the conditional probability. Keys are parent names. Values are parent values. To specify values for only a subset of the parents, only enter those parents in the dictionary. If no parent values are specified, then the current values of the parents are used. :type parentinput: dict :arg valueinput: Optional. A legitimate value of the chance node object. If no valueinput is specified, then the current value of the node is used. :returns: the conditional probability of valueinput or the current value conditioned on parentinput or the current values of the parents. .. note:: If parent values are specified in parentinput, those values must be legitimate values of the parent. For discrete parents, the values must correspond to an item in the parent's space attribute. """ if parentinput is None: parentinput = {} if self.CPT is None: if not parentinput: arglist = map(lambda x: x.get_value() \ if isinstance(x,Node) else x, self.params) else: arglist = map(lambda x: parentinput[x.name] \ if isinstance(x,Node) else x, self.params) args = tuple(arglist) if valueinput is None: valueinput = self.get_value() if self.continuous: r = self.distribution.pdf(valueinput, *args) else: r = self.distribution.pmf(valueinput, *args) else: indo = self.get_CPTindex(parentinput, valueinput) r = self.CPT[indo] return r
[docs] def logprob(self, parentinput=None, valueinput=None): """Compute the conditional logprob of the current or specified value :arg parentinput: Optional. Specify values of the parents at which to compute the conditional logprob. Keys are parent names. Values are parent values. To specify values for only a subset of the parents, only enter those parents in the dictionary. If no parent values are specified, then the current values of the parents are used. :type parentinput: dict :arg valueinput: Optional. A legitimate value of the chance node object. If no valueinput is specified, then the current value of the node is used. :returns: the log conditional probability of valueinput or the current value conditioned on parentinput or the current values of the parents. .. note:: If parent values are specified in parentinput, those values must be legitimate values of the parent. For discrete parents, the values must correspond to an item in the parent's space attribute. This is equivalent to ``np.log(ChanceNode.prob())`` """ if parentinput is None: parentinput = {} r = self.prob(parentinput, valueinput) return np.log(r)

Mailing List

Join the Google Group: