Extra Homework 1 Solutions

Solutions: You can find the file with solutions for all questions here.

Newton's Method

Q1: Intersect

Implement intersect, which takes two functions f and g and their derivatives df and dg. It returns an intersection point x, at which f(x) is equal to g(x).

def intersect(f, df, g, dg):
    """Return where f with derivative df intersects g with derivative dg.

    >>> parabola, line = lambda x: x*x - 2, lambda x: x + 10
    >>> dp, dl = lambda x: 2*x, lambda x: 1
    >>> intersect(parabola, dp, line, dl)
    4.0
    """
return find_zero(lambda x: f(x)-g(x), lambda x: df(x)-dg(x))

Q2: Polynomials

Differentiation of polynomials can be performed automatically by applying the product rule and the fact that the derivative of a sum is the sum of the derivatives of the terms.

In the following example, polynomials are expressed as two-argument Python functions. The first argument is the input x. The second argument called derive is True or False. When derive is True, the derivative is returned. When derive is False, the function value is returned.

For example, the quadratic function below returns a quadratic polynomial. The linear term X and constant function K are defined using conditional expressions.

X = lambda x, derive: 1 if derive else x
K = lambda k: lambda x, derive: 0 if derive else k

def quadratic(a, b, c):
    """Return a quadratic polynomial a*x*x + b*x + c.

    >>> q_and_dq = quadratic(1, 6, 8) # x*x + 6*x + 8
    >>> q_and_dq(1.0, False)  # value at 1
    15.0
    >>> q_and_dq(1.0, True)   # derivative at 1
    8.0
    >>> q_and_dq(-1.0, False) # value at -1
    3.0
    >>> q_and_dq(-1.0, True)  # derivative at -1
    4.0
    """
    A, B, C = K(a), K(b), K(c)
    AXX = mul_fns(A, mul_fns(X, X))
    BX = mul_fns(B, X)
    return add_fns(AXX, add_fns(BX, C))

To complete this implementation and apply Newton's method to polynomials, fill in the bodies of add_fns, mul_fns, and poly_zero below.

def add_fns(f_and_df, g_and_dg):
    """Return the sum of two polynomials."""
return lambda x, derive: f_and_df(x, derive) + g_and_dg(x, derive)
def mul_fns(f_and_df, g_and_dg): """Return the product of two polynomials."""
f, df = lambda x: f_and_df(x, False), lambda x: f_and_df(x, True) g, dg = lambda x: g_and_dg(x, False), lambda x: g_and_dg(x, True) def product(x, derive): if derive: return f(x) * dg(x) + df(x) * g(x) else: return f(x) * g(x) return product
def poly_zero(f_and_df): """Return a zero of polynomial f_and_df, which returns: f(x) for f_and_df(x, False) df(x) for f_and_df(x, True) >>> q = quadratic(1, 6, 8) >>> round(poly_zero(q), 5) # Round to 5 decimal places -2.0 >>> round(poly_zero(quadratic(-1, -6, -9)), 5) -3.0 """
return find_zero(lambda x: f_and_df(x, False), lambda x: f_and_df(x, True))

Q3: Cycles

Newton's method is not guaranteed to find a zero. One way that it can fail is due to a cycle: after applying k newton updates from an initial guess x, the result is x again.

Implement cycle, which returns an x near the given guess for which applying k Newton updates for the provided function f and its derivative df results in x. The doctests use a function print_updates which prints out the result of applying k Newton updates.

Hint: Part of your solution will involve computing the result of applying k updates from a starting point.

Hint: The provided differentiate function will find the derivative of a function automatically. The result is approximate, but good enough for this problem.

def print_updates(f, df, x, k, digits=4):
    """Print the first k Newton guesses for a zero of f, starting at x.

    >>> print_updates(lambda x: x*x - 2, lambda x: 2*x, 1, 4)
    1, 1.5, 1.4167, 1.4142, 1.4142
    """
    update = newton_update(f, df)
    guesses = [x]
    for _ in range(k):
        guesses.append(update(guesses[-1]))
    print(*[round(guess, digits) for guess in guesses], sep=', ')

def cycle(f, df, k, guess):
    """Find a k-step cycle in Newton's method starting near guess.

    >>> f = lambda x: x*x*x - 8*x*x + 17*x - 3
    >>> df = lambda x: 3*x*x - 16*x + 17
    >>> f(find_zero(f, df, 1)) # Starting at a guess of 1 finds a zero
    0.0
    >>> print_cycle = lambda k, x: print_updates(f, df, cycle(f, df, k, x), k)
    >>> print_cycle(3, 4.2)  # A 3-step cycle starting near 4.2
    4.2123, 3.7175, 4.7112, 4.2123
    >>> print_cycle(3, 3.7)  # A 3-step cycle starting near 3.7 (the same cycle)
    3.7175, 4.7112, 4.2123, 3.7175
    >>> print_cycle(5, 4)    # A 5-step cycle starting near 4
    4.003, 3.0234, 3.7591, 5.0564, 4.4548, 4.003
    """
update = newton_update(f, df) def offset(x): original = x for _ in range(k): x = update(x) return x - original return find_zero(offset, differentiate(offset), guess)

Use Ok to test your code:

python3 ok -q cycle