"""Fixed encoder and ten full-batch decoder updates on two toy vectors."""
import math,json
X=[[1.0,0.0],[0.0,1.0]]
def encode(x):return max(0.0,x[0]-x[1])
def sigmoid(v):return 1/(1+math.exp(-v))
def loss_and_gradient(a,c):
    loss=0.0;ga=[0.0,0.0];gc=[0.0,0.0];outputs=[]
    for x in X:
        z=encode(x);p=[sigmoid(a[j]*z+c[j]) for j in range(2)];outputs.append(p)
        for j in range(2):
            loss-=(x[j]*math.log(p[j])+(1-x[j])*math.log1p(-p[j]))/4
            ga[j]+=(p[j]-x[j])*z/4;gc[j]+=(p[j]-x[j])/4
    return loss,ga,gc,outputs
def run():
    a=[0.0,0.0];c=[0.0,0.0];rows=[]
    for step in range(11):
        loss,ga,gc,outputs=loss_and_gradient(a,c)
        rows.append({"step":step,"loss":loss,"a":a.copy(),"c":c.copy(),"outputs":outputs})
        if step<10:a=[v-g for v,g in zip(a,ga)];c=[v-g for v,g in zip(c,gc)]
    return rows
if __name__=="__main__":
    rows=run()
    assert math.isclose(rows[0]["loss"],math.log(2),abs_tol=1e-14)
    assert rows[1]["a"]==[.125,-.125] and rows[1]["c"]==[0.,0.]
    assert math.isclose(rows[-1]["loss"],.48455057043874583,abs_tol=1e-13)
    assert all(b["loss"]<a["loss"] for a,b in zip(rows,rows[1:]))
    # Central differences independently check four analytical gradients at a nonzero state.
    a=[.3,-.2];c=[-.1,.2];_,ga,gc,_=loss_and_gradient(a,c);eps=1e-6
    for group,expected in [("a",ga),("c",gc)]:
        for j in range(2):
            ap,am,cp,cm=a.copy(),a.copy(),c.copy(),c.copy()
            (ap if group=="a" else cp)[j]+=eps
            (am if group=="a" else cm)[j]-=eps
            numeric=(loss_and_gradient(ap,cp)[0]-loss_and_gradient(am,cm)[0])/(2*eps)
            assert math.isclose(numeric,expected[j],rel_tol=1e-6,abs_tol=1e-9)
    print(json.dumps({"training_vectors":X,"encoder_fixed":True,"decoder_updates":10,"learning_rate":1.0,"gradient_checks":4,"history":rows,"generalization_test":False},indent=2))
