"""Owned scalar random-walk Kalman trace, F=H=1, exact rational arithmetic."""
from fractions import Fraction as F
import json
def step(x,P,Q,R,z):
    if P<0 or Q<0 or R<=0:raise ValueError('P,Q nonnegative; R positive in this subset')
    prior_x=x;prior_P=P+Q
    if z is None:return {'z':None,'prior_x':prior_x,'prior_P':prior_P,'innovation':None,'S':None,'K':None,'posterior_x':prior_x,'posterior_P':prior_P,'measurement_used':False}
    innovation=z-prior_x;S=prior_P+R;K=prior_P/S;post_x=prior_x+K*innovation
    post_P=(1-K)**2*prior_P+K*K*R
    assert post_P==(1-K)*prior_P and 0<=K<=1 and post_P<=prior_P
    return {'z':z,'prior_x':prior_x,'prior_P':prior_P,'innovation':innovation,'S':S,'K':K,'posterior_x':post_x,'posterior_P':post_P,'measurement_used':True}
def demo():
    x,P=F(10),F(4);rows=[]
    for z in [F(12),None,F(11)]:
        row=step(x,P,F(1),F(4),z);rows.append(row);x,P=row['posterior_x'],row['posterior_P']
    assert rows[0]['K']==F(5,9) and rows[0]['posterior_x']==F(100,9) and rows[0]['posterior_P']==F(20,9)
    assert rows[1]['prior_P']==rows[1]['posterior_P']==F(29,9) and rows[1]['K']is None
    assert rows[2]['K']==F(19,37) and x==F(409,37) and P==F(76,37)
    lower_R=step(F(10),F(4),F(1),F(1),F(12));higher_R=step(F(10),F(4),F(1),F(9),F(12))
    assert lower_R['K']==F(5,6)>higher_R['K']==F(5,14)
    rejected=[]
    for name,P0,Q0,R0 in [('negative_P',F(-1),F(1),F(4)),('negative_Q',F(4),F(-1),F(4)),('zero_R_outside_own_subset',F(4),F(1),F(0))]:
        try:step(F(10),P0,Q0,R0,F(12))
        except ValueError:rejected.append(name)
        else:raise AssertionError(name)
    return {'model_assumptions':{'F':1,'H':1,'control':0,'Q_per_step':1,'R':4,'initial_mean':10,'initial_variance':4,'zero_mean_independent_process_and_measurement_noise_assumed':True,'actual_noise_parameters_estimated':False},'predict_then_update_trace':rows,'same_first_measurement_lower_R':lower_R,'same_first_measurement_higher_R':higher_R,'rejected_by_own_variance_contract':rejected,'FilterPy_or_real_signal_filtering_executed':False}
if __name__=='__main__':print(json.dumps(demo(),default=str,indent=2))
