Libraries and helper functions

import pandas as pd
import altair as alt
from typing import List
Vector = List[float]
Vector
typing.List[float]
def dot(vector1: Vector, vector2: Vector) -> float:
    assert len(vector1) == len(vector2)

    return sum(v1 * v2 for v1, v2 in zip(vector1, vector2))


assert dot([1, 2, 3], [4, 5, 6]) == 32
def sum_of_squares(v: Vector) -> Vector:
    return dot(v, v)

assert sum_of_squares([1, 2, 3]) == 14

Difference quotient

from typing import Callable

def difference_quotient(
    f: Callable[[float], float],
    x: float,
    h: float
) -> float :
    return (f(x + h) - f(x)) / h

A simple estimate

def square(x: float) -> float:
    return x * x
def derivative_x2(x: float) -> float:
    return 2 * x
xs = range(-10, 11)
actuals = [derivative_x2(x) for x in xs]
actuals
[-20,
 -18,
 -16,
 -14,
 -12,
 -10,
 -8,
 -6,
 -4,
 -2,
 0,
 2,
 4,
 6,
 8,
 10,
 12,
 14,
 16,
 18,
 20]
estimates = [difference_quotient(square, x, h=0.001) for x in xs]
estimates
[-19.998999999984335,
 -17.998999999988996,
 -15.999000000007868,
 -13.999000000005424,
 -11.99900000000298,
 -9.999000000004088,
 -7.998999999999867,
 -5.998999999999199,
 -3.9989999999994197,
 -1.998999999999973,
 0.001,
 2.0009999999996975,
 4.000999999999699,
 6.000999999999479,
 8.0010000000037,
 10.001000000002591,
 12.001000000005035,
 14.00100000000748,
 16.000999999988608,
 18.000999999983947,
 20.000999999993496]
df = pd.DataFrame({'actuals': actuals, 'estimates': estimates}).reset_index()
df = df.melt(id_vars='index')
df.sample(10)
index variable value
5 5 actuals -10.000
13 13 actuals 6.000
34 13 estimates 6.001
2 2 actuals -16.000
19 19 actuals 18.000
0 0 actuals -20.000
7 7 actuals -6.000
3 3 actuals -14.000
24 3 estimates -13.999
11 11 actuals 2.000
alt.Chart(df).mark_circle(opacity=0.75).encode(
    alt.X('index:Q'),
    alt.Y('value:Q'),
    alt.Size('variable:N'),
    alt.Color('variable:N')
).properties(title='Actual derivatives and estimated quotients')

Calculating an i-th difference quotient

def partial_difference_quotient(
    f: Callable[[Vector], float],
    v: Vector,
    i: int,
    h: float
) -> float:
    """Return i-th parital difference quotient of `f` at a`v`"""
    w = [
        v_j + (h if j == i else 0)
        for j, v_j in enumerate(v)
    ]

    return (f(w) - f(v)) / h
def estimate_gradient(
    f: Callable[[Vector], float],
    v: Vector,
    h: float = 0.0001
):
    return [
        partial_difference_quotient(f, v, i, h)
        for i in range(len(v))
    ]