"""Authored scalar gradient, vector Jacobian and vector-Jacobian chain rule with exact finite differences."""
from fractions import Fraction as F
import json
def g(x):
    u,v=x;return [u*u+v,u*v]
def scalar(x):
    a,b=g(x);return a+2*b
def demo():
    x=[F(2),F(3)];h=F(1,1000);jacobian=[[2*x[0],F(1)],[x[1],x[0]]];numerical=[[F(0)]*2 for _ in range(2)]
    scalar_numerical=[]
    for k in range(2):
        xp=x.copy();xm=x.copy();xp[k]+=h;xm[k]-=h
        gp,gm=g(xp),g(xm)
        for i in range(2):numerical[i][k]=(gp[i]-gm[i])/(2*h)
        scalar_numerical.append((scalar(xp)-scalar(xm))/(2*h))
    assert numerical==jacobian==[[4,1],[3,2]]
    upstream=[F(1),F(2)];gradient=[sum((jacobian[i][k]*upstream[i] for i in range(2)),F(0)) for k in range(2)]
    assert gradient==scalar_numerical==[10,5]
    direction=[F(1),F(-1)];directional=sum((a*b for a,b in zip(gradient,direction)),F(0))
    xp=[v+h*d for v,d in zip(x,direction)];xm=[v-h*d for v,d in zip(x,direction)]
    assert (scalar(xp)-scalar(xm))/(2*h)==directional==5
    return {'input':[str(v) for v in x],'vector_output':[str(v) for v in g(x)],'jacobian_rows_output_columns_input':[[str(v) for v in r] for r in jacobian],'finite_difference_jacobian':[[str(v) for v in r] for r in numerical],'upstream_scalar_gradient_wrt_outputs':[str(v) for v in upstream],'scalar_function':'L=g1+2*g2','scalar_value':str(scalar(x)),'input_gradient_Jtranspose_v':[str(v) for v in gradient],'finite_difference_scalar_gradient':[str(v) for v in scalar_numerical],'finite_difference_h':str(h),'direction':[str(v) for v in direction],'directional_derivative':str(directional),'central_difference_exact_for_these_polynomials_not_general':True,'optimizer_training_or_pytorch_run':False}
if __name__=='__main__':print(json.dumps(demo(),ensure_ascii=False,indent=2))
