"""Original finite asymmetric independence-proposal MH; exact, no empirical mixing claim."""
from fractions import Fraction as F
import json
def probabilities(a):
 if type(a) is not list or not 2<=len(a)<=4 or any(type(x) not in [int,F] or x<=0 or x>1 for x in a) or sum(a)!=1:raise ValueError('2..4 strictly positive exact normalized masses')
 return list(map(F,a))
def transition(target,proposal,correct=True):
 p=probabilities(target);q=probabilities(proposal)
 if len(p)!=len(q):raise ValueError('same finite states')
 a=[[min(F(1),p[j]*q[i]/(p[i]*q[j]) if correct else p[j]/p[i]) for j in range(len(p))] for i in range(len(p))]
 out=[]
 for i in range(len(p)):
  row=[q[j]*a[i][j] if j!=i else F(0) for j in range(len(p))];row[i]=1-sum(row);assert all(x>=0 for x in row) and sum(row)==1;out.append(row)
 return a,out
def demo():
 p=[F(1,2),F(1,3),F(1,6)];q=[F(1,3),F(1,2),F(1,6)];a,t=transition(p,q);balance=[]
 for i in range(3):
  for j in range(3):
   l=p[i]*t[i][j];r=p[j]*t[j][i];assert l==r;balance.append({'i':i,'j':j,'forward_flux':str(l),'reverse_flux':str(r)})
 final=[sum(p[i]*t[i][j] for i in range(3)) for j in range(3)];assert final==p
 _,wrong=transition(p,q,False);badfinal=[sum(p[i]*wrong[i][j] for i in range(3)) for j in range(3)];assert badfinal!=p
 state=0;trace=[]
 for proposed,coin in zip([1,2,0,1,0,2],[F(1,4),F(3,4),F(1,2),F(1,2),F(1,3),F(1,5)]):
  old=state;accept=coin<a[state][proposed]
  if accept:state=proposed
  trace.append({'old':old,'proposed':proposed,'authored_uniform_threshold':str(coin),'acceptance':str(a[old][proposed]),'accepted':accept,'new':state})
 rejected=[]
 for bad in [[F(1,2),F(1,4)],[1,0],[-1,2],[True,F(0)],[]]:
  try:probabilities(bad)
  except ValueError:rejected.append([str(x) for x in bad])
  else:raise AssertionError('expected rejection')
 encode=lambda x:[[str(z) for z in row] for row in x]
 return {'target_pi':[str(x) for x in p],'independent_proposal_each_row_q':[str(x) for x in q],'acceptance_matrix':encode(a),'transition_P':encode(t),'nine_detailed_balance_checks':balance,'pi_times_P':[str(x) for x in final],'wrong_without_reverse_proposal_ratio':{'transition_P':encode(wrong),'pi_times_P':[str(x) for x in badfinal],'stationary_target_failed':True},'hand_authored_six_step_trace_not_random_sample':trace,'invalid_probabilities_rejected':rejected,'scope':'finite exact balance, not a convergence rate, independent samples or real model execution'}
if __name__=='__main__':print(json.dumps(demo(),ensure_ascii=True,indent=2))
