"""Authored finite probability laws only; no task data or dispatch."""
from fractions import Fraction as F
from math import log, sqrt

def evaluate(law, utilities):
    """law[(x,z,w)] exact probability; utilities[a][(x,w)] in [0,1]."""
    if not law or not utilities or any(type(v)is not F or v<0 for v in law.values()) or sum(law.values())!=1:
        raise ValueError('finite probability law')
    px={};pxz={};pxw={}
    for (x,z,w),mass in law.items():
        px[x]=px.get(x,F(0))+mass;pxz[x,z]=pxz.get((x,z),F(0))+mass;pxw[x,w]=pxw.get((x,w),F(0))+mass
    domain=set(pxw)
    if any(set(u)!=domain or any(type(v)is not F or not 0<=v<=1 for v in u.values())for u in utilities.values()):
        raise ValueError('fixed bounded utilities')
    v0=F(0);v1=F(0);tv=F(0);information=0.
    for x,massx in px.items():
        if massx==0:continue
        v0+=max(sum(mass*u[x,w] for (xx,w),mass in pxw.items()if xx==x)for u in utilities.values())
    for (x,z),massxz in pxz.items():
        if massxz==0:continue
        states=[w for xx,w in domain if xx==x]
        prior={w:pxw[x,w]/px[x]for w in states}
        posterior={w:law.get((x,z,w),F(0))/massxz for w in states}
        v1+=massxz*max(sum(posterior[w]*u[x,w]for w in states)for u in utilities.values())
        tv+=massxz*sum(abs(posterior[w]-prior[w])for w in states)/2
        information+=float(massxz)*sum(float(posterior[w])*log(float(posterior[w]/prior[w]))for w in states if posterior[w])
    return {'v0':v0,'v1':v1,'gain':v1-v0,'expected_tv':tv,'conditional_information_nats':information,'pinsker_ceiling':sqrt(max(0.,information)/2)}
