
##########################################
# f(v) = sum_i(v_i in v)

def sum_of_squares(v):
    """computes the sum of squared elements in v"""
    return sum(v_i ** 2 for v_i in v)


##########################################
# f'(v) = [2v1, 2v2, ....]

def sum_of_squares_gradient(v):
    return [2 * v_i for v_i in v]

##############################################
# step(v (start), direction (-grad), step_size)
#
# Return:  [v_1 + step_size*dir_1, v_2 + step_size*dir_2, ...]

def step(v, direction, step_size):
    """move step_size in the direction from v"""

    return [v_i + step_size * direction_i
            for v_i, direction_i in zip(v, direction)]


################################################
# Gradient descent for f():

from scratch.linear_algebra import distance

v = [2,6,9,8]
tolerance = 0.0000001

while True:
    print("v = ", v)
    gradient = sum_of_squares_gradient(v)    # compute the gradient at v

    next_v = step(v, gradient, -0.01)        # take a negative gradient step

    if distance(next_v, v) < tolerance:      # stop if we're converging
        break

    v = next_v                               # continue if we're not
