# Path: scratch/linear_algebra.py
# def add(v: Vector, w: Vector) -> Vector:
# def subtract(v: Vector, w: Vector) -> Vector:
# def vector_sum(vectors: List[Vector]) -> Vector:
# def scalar_multiply(c: float, v: Vector) -> Vector:
# def vector_mean(vectors: List[Vector]) -> Vector:
# def dot(v: Vector, w: Vector) -> float:
# def sum_of_squares(v: Vector) -> float:
# def magnitude(v: Vector) -> float:
# def squared_distance(v: Vector, w: Vector) -> float:
# def distance(v: Vector, w: Vector) -> float:
# def distance(v: Vector, w: Vector) -> float:  # type: ignore
# def shape(A: Matrix) -> Tuple[int, int]:
# def get_row(A: Matrix, i: int) -> Vector:
# def get_column(A: Matrix, j: int) -> Vector:
# def make_matrix(num_rows: int,
#                 num_cols: int,
#                 entry_fn: Callable[[int, int], float]) -> Matrix:
# def identity_matrix(n: int) -> Matrix:
# A = [[1, 2, 3],  # A has 2 rows and 3 columns
#      [4, 5, 6]]
# B = [[1, 2],     # B has 3 rows and 2 columns
#      [3, 4],
#      [5, 6]]
# 
# Path: scratch/linear_algebra.py
# def distance(v: Vector, w: Vector) -> float:
#     """Computes the distance between v and w"""
#     return math.sqrt(squared_distance(v, w))
# 
# def add(v: Vector, w: Vector) -> Vector:
#     """Adds corresponding elements"""
#     assert len(v) == len(w), "vectors must be the same length"
# 
#     return [v_i + w_i for v_i, w_i in zip(v, w)]
# 
# def scalar_multiply(c: float, v: Vector) -> Vector:
#     """Multiplies every element by c"""
#     return [c * v_i for v_i in v]


from scratch.linear_algebra import Vector, dot
from typing import Callable
from scratch.linear_algebra import distance, add, scalar_multiply
from typing import TypeVar, List, Iterator
    from scratch.linear_algebra import vector_mean
import random
    import matplotlib.pyplot as plt
    plt.title("Actual Derivatives vs. Estimates")
    plt.plot(xs, actuals, 'rx', label='Actual')       # red  x
    plt.plot(xs, estimates, 'b+', label='Estimate')   # blue +
    plt.legend(loc=9)
    # plt.show()
    
    
    plt.close()
    
    def partial_difference_quotient(f: Callable[[Vector], float],
                                    v: Vector,
                                    i: int,
                                    h: float) -> float:
        """Returns the i-th partial difference quotient of f at v"""
        w = [v_j + (h if j == i else 0)    # add h to just the ith element of v
             for j, v_j in enumerate(v)]
    
        return (f(w) - f(v)) / h
    
    
    # "Using the Gradient" example
    
    # pick a random starting point
    v = [random.uniform(-10, 10) for i in range(3)]
    
    for epoch in range(1000):
        grad = sum_of_squares_gradient(v)    # compute the gradient at v
        v = gradient_step(v, grad, -0.01)    # take a negative gradient step
        print(epoch, v)
    

# Path: scratch/linear_algebra.py
# def add(v: Vector, w: Vector) -> Vector:
# def subtract(v: Vector, w: Vector) -> Vector:
# def vector_sum(vectors: List[Vector]) -> Vector:
# def scalar_multiply(c: float, v: Vector) -> Vector:
# def vector_mean(vectors: List[Vector]) -> Vector:
# def dot(v: Vector, w: Vector) -> float:
# def sum_of_squares(v: Vector) -> float:
# def magnitude(v: Vector) -> float:
# def squared_distance(v: Vector, w: Vector) -> float:
# def distance(v: Vector, w: Vector) -> float:
# def distance(v: Vector, w: Vector) -> float:  # type: ignore
# def shape(A: Matrix) -> Tuple[int, int]:
# def get_row(A: Matrix, i: int) -> Vector:
# def get_column(A: Matrix, j: int) -> Vector:
# def make_matrix(num_rows: int,
#                 num_cols: int,
#                 entry_fn: Callable[[int, int], float]) -> Matrix:
# def identity_matrix(n: int) -> Matrix:
# A = [[1, 2, 3],  # A has 2 rows and 3 columns
#      [4, 5, 6]]
# B = [[1, 2],     # B has 3 rows and 2 columns
#      [3, 4],
#      [5, 6]]
# 
# Path: scratch/linear_algebra.py
# def distance(v: Vector, w: Vector) -> float:
#     """Computes the distance between v and w"""
#     return math.sqrt(squared_distance(v, w))
# 
# def add(v: Vector, w: Vector) -> Vector:
#     """Adds corresponding elements"""
#     assert len(v) == len(w), "vectors must be the same length"
# 
#     return [v_i + w_i for v_i, w_i in zip(v, w)]
# 
# def scalar_multiply(c: float, v: Vector) -> Vector:
#     """Multiplies every element by c"""
#     return [c * v_i for v_i in v]


# Path: scratch/gradient_descent.py
from scratch.linear_algebra import Vector, dot
from typing import Callable
from scratch.linear_algebra import distance, add, scalar_multiply
from typing import TypeVar, List, Iterator
    from scratch.linear_algebra import vector_mean
import random
    import matplotlib.pyplot as plt

    plt.title("Actual Derivatives vs. Estimates")
    plt.plot(xs, actuals, 'rx', label='Actual')       # red  x
    plt.plot(xs, estimates, 'b+', label='Estimate')   # blue +
    plt.legend(loc=9)
    # plt.show()
    
    
    plt.close()
    
    def partial_difference_quotient(f: Callable[[Vector], float],
                                    v: Vector,
                                    i: int,
                                    h: float) -> float:
        """Returns the i-th partial difference quotient of f at v"""
        w = [v_j + (h if j == i else 0)    # add h to just the ith element of v
             for j, v_j in enumerate(v)]
    
        return (f(w) - f(v)) / h
    
    
    # "Using the Gradient" example
    
    # pick a random starting point
    v = [random.uniform(-10, 10) for i in range(3)]
    
    for epoch in range(1000):
        grad = sum_of_squares_gradient(v)    # compute the gradient at v
        v = gradient_step(v, grad, -0.01)    # take a negative gradient step
        print(epoch, v)
    

    assert distance(v, [0, 0, 0]) < 0.001    # v should be close to 0