Source code for pynfg.utilities.utilities
# -*- coding: utf-8 -*-
"""
Several useful functions for use in various places in PyNFG
Part of: PyNFG - a Python package for modeling and solving Network Form Games
Created on Tue May 7 15:39:38 2013
Copyright (C) 2013 James Bono (jwbono@gmail.com)
GNU Affero General Public License
"""
from __future__ import division
import numpy as np
import copy
import pynfg
[docs]def mceu(Game, dn, N, tol=30, delta=1, verbose=False):
"""Compute the move-conditioned expected utilities for all parent values
:arg Game: the SemiNFG of interest
:type Game: SemiNFG or iterSemiNFG
:arg dn: the name of the decision node where MCEUs are estimated
:type dn: str
:arg N: the max number of iterations for the estimation
:type N: int
:arg tol: the minimum number of samples per parent value
:type tol: int
"""
if type(Game) == pynfg.classes.seminfg.SemiNFG:
return _mceu_static(Game, dn, N, tol, verbose)
else:
return _mceu_iterated(Game, dn, N, tol, delta, verbose)
def _mceu_iterated(Game, dn, N, tol=30, delta=1, verbose=False):
G = copy.deepcopy(Game)
player = G.node_dict[dn].player
CPT_shape = G.node_dict[dn].CPT.shape
childnames = [node.name for node in G.children(dn)]
space = G.node_dict[dn].space
Utable = np.zeros(CPT_shape)
visits = np.zeros(CPT_shape)
n = 0
ufoo = G.npv_reward
uargs = [player, G.node_dict[dn].time, delta]
while np.min(visits)<tol and n<N: # Do we want both of these to hold
n += 1 # or just one? I think both.
G.sample()
idx = G.node_dict[dn].get_CPTindex()
visits[idx[:-1]] += 1
Utable[idx] += ufoo(*uargs)
for a in xrange(CPT_shape[-1]):
if a != idx[-1]:
G.node_dict[dn].set_value(space[a])
G.sample(start=childnames)
idy = idx[:-1]+(a,)
Utable[idy] += ufoo(*uargs)
if verbose:
print('number of unvisited messages:', \
(visits.size-np.count_nonzero(visits))/CPT_shape[-1])
print('least number of visits:', np.min(visits[np.nonzero(visits)]))
idx = (visits==0)
visits[idx] = 1
return Utable/visits
def _mceu_static(Game, dn, N, tol, verbose=False):
G = copy.deepcopy(Game)
player = G.node_dict[dn].player
CPT_shape = G.node_dict[dn].CPT.shape
childnames = [node.name for node in G.children(dn)]
space = G.node_dict[dn].space
Utable = np.zeros(CPT_shape)
visits = np.zeros(CPT_shape)
n = 0
ufoo = G.utility
uargs = player
while np.min(visits)<tol and n<N: # Do we want both of these to hold
n += 1 # or just one? I think both.
G.sample()
idx = G.node_dict[dn].get_CPTindex()
visits[idx[:-1]] += 1
Utable[idx] += ufoo(uargs)
if len(childnames) > 0 :
for a in xrange(CPT_shape[-1]):
if a != idx[-1]:
G.node_dict[dn].set_value(space[a])
G.sample(start=childnames)
idy = idx[:-1]+(a,)
Utable[idy] += ufoo(uargs)
else: # no children samples whole net!
for a in xrange(CPT_shape[-1]):
if a != idx[-1]:
G.node_dict[dn].set_value(space[a])
idy = idx[:-1]+(a,)
Utable[idy] += ufoo(uargs)
if verbose:
print('number of unvisited messages:', \
(visits.size-np.count_nonzero(visits))/CPT_shape[-1])
print('least number of visits:', np.min(visits[np.nonzero(visits)]))
idx = (visits==0)
visits[idx] = 1
return Utable/visits
[docs]def convert_2_pureCPT(anarray):
"""Convert an arbitrary matrix to a pure CPT w/ weight on maximum elements
:arg anarray: The numpy array to be converted to
:type anarray: np.array
:returns: a normalized conditional probability distribution over actions
given messages with all elements zero or one.
"""
idx = np.where(anarray == np.max(anarray, axis=-1)[...,np.newaxis])
newarray = np.zeros(anarray.shape)
newarray[idx] = 1
newCPT = newarray/np.sum(newarray, axis=-1)[...,np.newaxis]
return newCPT
[docs]def mh_decision(pnew, pold, qnew=1, qold=1):
"""Decide to accept the new draw or keep the old one
:arg pnew: the unnormalized likelihood of the new draw
:type pnew: float
:arg pold: the unnormalized likelihood of the old draw
:type pnew: float
:arg qnew: the probability of transitioning from the old draw to the new
draw.
:type qnew: float
:arg qold: the probability of transitioning from the new draw to the old
draw.
:type qold: float
:returns: either True or False to determine whether the new draw is
accepted.
"""
if pold<=0 or qnew<=0:
a = 1
else:
a = min([(pnew*qold)/(pold*qnew), 1])
u = np.random.rand()
if a > u:
verdict = True
else:
verdict = False
return verdict