"""Owned same-snapshot Q-learning/SARSA target comparison; next action explicitly authored."""
from fractions import Fraction as F
import copy,json
def update(kind,Q,s,a,reward,nxt,next_action,terminal,alpha=F(1,5),gamma=F(1,2)):
    if kind not in ('qlearning','sarsa'):raise ValueError('known update rule required')
    if not 0<alpha<=1 or not 0<=gamma<=1:raise ValueError('rate/discount contract')
    old=Q[s][a];selected=None;row=None
    if terminal:future=F(0)
    else:
        row=Q[nxt].copy()
        if kind=='qlearning':selected=max(row,key=row.get);future=row[selected]
        else:
            if next_action not in row:raise ValueError('SARSA requires declared next behavior action')
            selected=next_action;future=row[selected]
    target=reward+(F(0) if terminal else gamma*future);td=target-old;new=old+alpha*td;after=copy.deepcopy(Q);after[s][a]=new
    assert all(after[u][v]==value for u,actions in Q.items() for v,value in actions.items() if (u,v)!=(s,a))
    return {'rule':kind,'state':s,'action':a,'reward':reward,'next_state':nxt,'declared_behavior_next_action':next_action,'terminal':terminal,'source_next_Q_row':row,'bootstrap_action_used_by_rule':selected,'bootstrap_Q_value':future,'old':old,'target':target,'TD_error':td,'alpha':alpha,'gamma':gamma,'new':new,'Q_after':after,'all_nonupdated_cells_unchanged':True}
def demo():
    Q={'Start':{'wait':F(3),'send':F(1)},'Fork':{'steady':F(2),'risky':F(8)}};before=copy.deepcopy(Q);rows=[]
    for choice in ['steady','risky']:
        for kind in ['qlearning','sarsa']:rows.append(update(kind,Q,'Start','wait',F(-2),'Fork',choice,False))
    q1,s1,q2,s2=rows
    assert q1['target']==q2['target']==s2['target']==2 and q1['new']==q2['new']==s2['new']==F(14,5)
    assert s1['target']==-1 and s1['new']==F(11,5) and s1['bootstrap_action_used_by_rule']=='steady'
    terminal=[update(kind,Q,'Start','wait',F(-2),None,None,True) for kind in ['qlearning','sarsa']]
    assert all(r['target']==-2 and r['new']==2 and r['source_next_Q_row']is None for r in terminal)
    try:update('sarsa',Q,'Start','wait',F(-2),'Fork',None,False)
    except ValueError:missing=True
    else:raise AssertionError('missing next action accepted by SARSA')
    assert Q==before
    return {'authored_initial_Q_unchanged':Q,'behavior_action_contract':'two manually declared alternatives, each passed into the update; no random behavior policy or real environment sampled','same_snapshot_four_continuing_updates':rows,'two_terminal_no_next_row_updates':terminal,'missing_SARSA_next_action_rejected':missing,'multi_episode_training_convergence_or_Gymnasium_executed':False}
if __name__=='__main__':print(json.dumps(demo(),default=str,indent=2))
