Solutions to assignment tasks

Solutions to assignment tasksΒΆ

import numpy as np
import matplotlib.pyplot as plt
from scipy import linalg, interpolate, special

Assignment task 01:

def sum_of_series(n):
    s = np.zeros(n)
    for i in range(1, n + 1):
        s[i-1] = 2.0 * np.sqrt(i) * np.sin(i * np.pi / 10.0)
    return np.sum(s)

print(sum_of_series(100))

n = 100
x = np.linspace(1, 100, 100)
s = np.zeros(n)
for i in range(1, n + 1):
    s[i-1] = sum_of_series(i)

fig, ax = plt.subplots()
ax.plot(x, s)
plt.show()
-56.05201335500862
../_images/solutions_3_1.png

Assignment task 02:

def maclaurin_series(n):
    """
    Calculates the Maclaurin series of sin(x) using n terms.
    Args:
        n (int): Number of terms of the Maclaurin series
    Returns:
        numpy.ploy1d: Maclaurin polynomial
    """
    coeff = np.zeros(2 * n + 2)
    for i in range(0, 2 * n + 1, 2):
        coeff[i + 1] = (-1.0)**(i / 2) / np.math.factorial(i + 1)
    return np.poly1d(coeff[::-1])

def relative_error(x, n):
    """
    Calculates the relative error of the Maclaurin series.
    Args:
        x (float): Coordinate x
        n (int): Number of terms of the Maclaurin series
    Returns:
        float: Relative error
    """
    return np.abs(maclaurin_series(n)(x) - np.sin(x)) / np.abs(np.sin(x))

print(format(relative_error(np.pi / 2.0, 2), ".16f"))

n = 0
while True:
    if(relative_error(np.pi / 2.0, n) < 200.0 * np.finfo(np.float64).eps):
        break
    n = n + 1
print(n)
0.0045248555348174
8

Assignment task 03:

def gauss_jordan_elimination(A):
    """
    Calculates the inverse matrix using Gauss-Jordan elimination algorithm.
    Args:
        A (array_like): A n-by-n invertible matrix
    Returns:
        numpy.ndarray: Inverse matrix
    """
    n, n = A.shape
    U = np.zeros((n, 2 * n))
    U[:, :n] = A
    U[:, n:] = np.eye(n)
    for i in range(0, n):  
        max_row = np.argmax(np.abs(U[i:, i]))
        if max_row != 0:
            row_i = np.copy(U[i, :])
            U[i, :] = U[i + max_row, :]
            U[i + max_row, :] = row_i     
        for j in range(i + 1, n):
            U[j, :] = U[j, :] - (U[j, i] / U[i, i]) * U[i, :]  
        U[i, :] /= U[i, i]
    for j in reversed(range(0, n - 1)):
        for i in reversed(range(j + 1, n)):
            U[j, :] = U[j, :] - U[j, i] * U[i, :] 
    return U[:, n:]

A = np.array([[3, 0, 2], [2, 0, -2], [0, 1, 1]])
print(gauss_jordan_elimination(A))

try:
    np.testing.assert_array_almost_equal(gauss_jordan_elimination(A), linalg.inv(A), decimal=7)
except AssertionError as E:
    print(E)
else:
    print("The implementation is correct.")
[[ 0.2  0.2  0. ]
 [-0.2  0.3  1. ]
 [ 0.2 -0.3 -0. ]]
The implementation is correct.

Assignment task 04:

def conjugate_gradient_method(A, b, error_tolerance):
    """
    Solves system of linear equations using conjugate gradient method.
    Args:
        A (array_like): A n-by-n real, symmetric, and positive-definite matrix
        b (array_like): RHS vector of size n
        error_tolerance (float): Error tolerance
    Returns:
        list: List of all iterations
    """
    n, n = A.shape
    x = np.zeros(n)
    r = b - A @ x
    p = r
    list_of_iterations = [x]
    while True:
        alpha = (r.T @ r) / (p.T @ A @ p) 
        x = x + alpha * p
        list_of_iterations.append(x)
        r_new = r - alpha * A @ p
        if linalg.norm(r_new) < error_tolerance:
            break
        beta = (r_new.T @ r_new) / (r.T @ r)
        p = r_new + beta * p 
        r = r_new
    return list_of_iterations

A = np.array([[4, 1], [1, 3]])
b = np.array([1, 2])

solution = conjugate_gradient_method(A, b, 1.0e-15)
for s in solution:
    print(s)
    
n = 100
x_1 = np.linspace(-1.0, 1.0, n)
x_2 = np.linspace(-1.0, 1.0, n)
X_1, X_2 = np.meshgrid(x_1, x_2)
f = np.zeros((n, n))
for i in range(n):
    for j in range(n):
        x = np.array([X_1[i, j], X_2[i, j]])
        f[i, j] = 0.5 * x.T @ A @ x - x.T @ b
        
fig, ax = plt.subplots()
ax.contour(x_1, x_2, f, colors="black", zorder=1)
for s in solution:
    ax.scatter(s[0], s[1], color="red", zorder=2)
plt.show()
[0. 0.]
[0.25 0.5 ]
[0.09090909 0.63636364]
../_images/solutions_9_1.png

Assignment task 05:

def function(x):
    y = np.zeros(x.size)
    y[x <= 5.0] = -1.0
    y[x > 5.0] = 1.0
    return y

a = 0
b = 10

x_p = (a + b) / 2.0 + (b - a) / 2.0 * special.roots_chebyt(10)[0]
y_p = function(x_p)

f = interpolate.lagrange(x_p, y_p)
print(f)

x = np.linspace(a, b, 1000)

fig, ax = plt.subplots(1, 2, figsize=(10, 3))
ax[0].scatter(x_p, y_p, color="red")
ax[0].plot(x, f(x), color="red")
ax[0].plot(x, function(x), color="black")
ax[1].plot(x, np.abs(f(x) - function(x)), color="blue")
fig.tight_layout()
plt.show()
           9            8           7        6         5         4
2.654e-05 x - 0.001194 x + 0.02221 x - 0.22 x + 1.247 x - 4.052 x
          3         2
 + 7.172 x - 6.148 x + 1.958 x - 1.099
../_images/solutions_11_1.png

Assignment task 06:

def cubic_spline(x_p, y_p):
    """
    Calculates cubic spline interpolation of a given set of data points.
    Args:
        x_p (array_like): X-coordinates of a set of data points
        y_p (array_like): Y-coordinates of a set of data points
    Returns:
        List[numpy.poly1d]: List of natural cubic splines
    """
    n = x_p.size
    cubic_splines = []
    p = np.zeros(n - 3)
    q = np.zeros(n - 2)
    r = np.zeros(n - 3)
    b = np.zeros(n - 2)
    for i in range(n - 3):
        p[i] = (x_p[i + 2] - x_p[i + 1]) / 6.0
        r[i] = (x_p[i + 2] - x_p[i + 1]) / 6.0
    for i in range(n - 2):
        q[i] = (x_p[i + 2] - x_p[i]) / 3.0
        b[i] = (y_p[i + 2] - y_p[i + 1]) / (x_p[i + 2] - x_p[i + 1]) - (y_p[i + 1] - y_p[i]) / (x_p[i + 1] - x_p[i])
    d2y = np.linalg.solve(np.diag(p, 1) + np.diag(q, 0) + np.diag(r, -1), b)
    d2y = np.insert(d2y, 0, 0.0)
    d2y = np.insert(d2y, n - 1, 0.0)
    for i in range(n - 1):
        A = np.poly1d([-1.0, x_p[i + 1]]) / (x_p[i + 1] - x_p[i])
        B = 1.0 - A
        C = (A**3 - A) / 6.0 * (x_p[i + 1] - x_p[i])**2
        D = (B**3 - B) / 6.0 * (x_p[i + 1] - x_p[i])**2
        cubic_splines.append(A * y_p[i] + B * y_p[i + 1] + C * d2y[i] + D * d2y[i + 1])
    return cubic_splines

x_p = np.array([0.0, 1.0, 2.0, 3.0])
y_p = np.array([0.0, 2.0, -1.0, 1.0])

f = cubic_spline(x_p, y_p)

fig, ax = plt.subplots()
ax.scatter(x_p, y_p, color="black")
for i in range(len(f)):
    x = np.linspace(x_p[i], x_p[i + 1], 100)
    ax.plot(x, f[i](x), color="red")
plt.show()
../_images/solutions_13_0.png

Assignment task 07:

def newton_raphson(f, x_0, error_tolerance=1.0e-15, max_iterations=500):
    """
    Finds the solution of a sytem of equations using Newton-Raphson method.
    Args:
        f (list): List of functions whose derivatives are continuous and nonzero in the neighborhood of a root
        x_0 (numpy.ndarray): Vector of initial values
        error_tolerance (float): Error tolerance
        max_iterations (int): Maximum number of iterations
    Returns:
        numpy.ndarray: The vector of solution of a given sytem of equations
    Raises:
        RuntimeError: Raises an exception when no solution is found within specified error tolerance and maximum number of iterations
    """
    def df(f, x, j, h=1.0e-7):
        return (f(x + h * np.identity(x.size)[j]) - f(x)) / h 
    n = x_0.size
    J = np.zeros((n, n))
    b = np.zeros(n)
    k = 0
    while k < max_iterations:
        for i in range(n):
            b[i] = -f[i](x_0)
            for j in range(n):
                J[i, j] = df(f[i], x_0, j)
        delta = linalg.solve(J, b)
        x = x_0 + delta
        if np.max(np.abs([f[i](x) for i in range(n)])) < error_tolerance:
            print("root found within tolerance", error_tolerance, "using", n, "iterations")
            return x
        x_0 = x
        k = k + 1
    raise RuntimeError("no root found within tolerance", error_tolerance, "using", max_iterations, "iterations")
    
def f(x):
    return x[0]**2 + x[1]**2 - 2.0 * x[2]

def g(x):
    return x[0]**2 + x[2]**2 - 0.5

def h(x):
    return x[0]**2 + x[1]**2 + x[2]**2 - 1.0

system = [f, g, h]
x_0 = np.array([1.0, 1.0, 0.0])
root = newton_raphson(system, x_0, 1.0e-7, 10)
print(root)
root found within tolerance 1e-07 using 3 iterations
[0.57308562 0.70710678 0.41421356]

Assignment task 08:

import numpy as np
import matplotlib.pyplot as plt
from scipy import linalg

def steepest_descent_nd(f, x_0, step, error_tolerance=1.0e-15, max_iterations=1e5):
    """
    Finds the minimum of function using the method of steepest descent.
    Args:
        f (function): A strictly unimodal and differentiable function in a neighborhood of a point x_0
        x_0 (float): Initial guess
        step (float): Step size multiplier
        error_tolerance (float): Error tolerance
        n_max (int): Maximum number of iterations
    Returns:
        float: A coordinate of minimum
    Raises:
        RuntimeError: Raises an exception when the minimum is not found
    """
    def grad(f, x, h=1.0e-8):
        return np.array([(f(x + h * np.identity(x.size)[j]) - f(x)) / h for j in range(x.size)])
    k = 0
    while k < max_iterations:
        x_1 = x_0 - step * grad(f, x_0)
        k = k + 1
        if linalg.norm(x_1 - x_0) < error_tolerance:
            #print("minimum found within tolerance", error_tolerance, "using", k, "iterations")
            return x_1
        x_0 = x_1
    raise RuntimeError("minimum not found within tolerance", error_tolerance, "using", max_iterations, "iterations")
    
def f(x): 
    return np.sin(x[1]) * np.exp((1.0 - np.cos(x[0]))**2) + np.cos(x[0]) * np.exp((1.0 - np.sin(x[1]))**2) + (x[0] - x[1])**2

x_min = steepest_descent_nd(f, np.array([-2, -2]), 1.0e-3, 1.0e-7)
print(x_min, f(x_min))

x = np.meshgrid(np.linspace(-3.0 * np.pi / 2.0, -np.pi / 2.0, 100), np.linspace(-np.pi, 0.0, 100))
fig, ax = plt.subplots()
ax.contour(x[0], x[1], f(x), colors="black", linestyles="-")
ax.scatter(x_min[0], x_min[1], color="red", marker="+", s=400)
plt.show()
[-3.13024659 -1.5821422 ] -106.76453674925818
../_images/solutions_17_1.png

Assignment task 09:

def midpoint_rule(f, a, b, N=100):
    """
    Calculates definite integral of 1D function using rectangular rule.
    Args:
        f (function): A function defined on interval [a, b]
        a (float): Left-hand side point of the interval
        b (float): Right-hand side point of the interval
        N (int): Number of subdivisions of the interval [a, b]
    Returns:
        float: Definite integral
    """
    x, h = np.linspace(a, b, N, retstep=True)
    I = 0.0
    for i in range(N - 1):
        I += h * f((x[i] + x[i+1]) / 2.0)
    return I

def f(x):
    return x**4 * (1.0 - x)**4 / (1.0 + x**2)

a = 0.0
b = 1.0
N = 10

I = midpoint_rule(f, a, b, N)
error = np.abs((22.0 / 7.0 - np.pi) - I)
print(I, error)
0.0012644494821294993 3.978522017844717e-08

Assignment task 10:

import numpy as np
import matplotlib.pyplot as plt

def forward_euler(f, x_0, x_n, y_0, h):
    x = np.arange(x_0, x_n + h, h)
    n = np.size(x)
    y = np.zeros(n)
    y[0] = y_0
    for i in range(n-1):
        y[i+1] = y[i] + h * f(x[i], y[i])
    return x, y

def f(t, N):
    return (1.0 + np.cos(t)) * N

t_start = 0.0
t_end = 5.0
N_0 = 1.0
h = 0.2

t, N = forward_euler(f, t_start, t_end, N_0, h)
print(N[-1])

t_exact = np.linspace(t_start, t_end, 1000)
N_exact = N_0 * np.exp(t_exact + np.sin(t_exact))

fig, ax = plt.subplots()
ax.plot(t_exact, N_exact, color="black")
ax.scatter(t, N, color="red")
plt.show()
38.25827715526892
../_images/solutions_21_1.png

Assignment task 11:

import numpy as np
from scipy import optimize
import matplotlib.pyplot as plt

def shooting_method(f, g, a, b, n, alpha, beta):
    """
    Solves 2nd order explicit ODE in the form y(x)'' = f(x, y(x), y(x)') in [a, b] with y(a) = alpha, y(b) = beta
    using shooting method.
    Args:
        f (function): function 
        g (function): function
        a (float): The left-most point of the interval
        b (float): The right-most point of the interval
        n (int): number of points dividing interval [a, b]
        alpha (float): initial value at x = a
        beta (float): initial value at x = b
    Returns:
        numpy.ndarray: Vector of grid-points
        numpy.ndarray: Vector of solution
    """
    def semi_implicit_euler_system(f, g, a, b, n, y_0, z_0):
        x, h = np.linspace(a, b, n, retstep=True)
        y = np.zeros(n)
        z = np.zeros(n)
        y[0] = y_0
        z[0] = z_0
        for i in range(n - 1):
            z[i + 1] = z[i] + h * f(x[i], y[i], z[i])
            y[i + 1] = optimize.fsolve(lambda q: q - y[i] - h * g(x[i + 1], q, z[i + 1]), y[i])
        return x, y
    def shooting_function(gamma):
        x, y = semi_implicit_euler_system(f, g, a, b, n, alpha, gamma)
        return y[-1] - beta
    root = optimize.fsolve(shooting_function, 0.0)
    x, y = semi_implicit_euler_system(f, g, a, b, n, alpha, root)
    return x, y

def f(x, y, z):
    return 5.0 / x * z - 8.0 / x**2 * y

def g(x, y, z):
    return z

a = 1.0
b = 2.0
n = 10
alpha = 0.0
beta = 24.0

x_numerical, y_numerical = shooting_method(f, g, a, b, n, alpha, beta)
print(y_numerical)

x_analytical = np.linspace(a, b, 100) 
y_analytical = 2.0 * (x_analytical**4 - x_analytical**2)

fig, ax = plt.subplots()
ax.scatter(x_numerical, y_numerical, color="red")
ax.plot(x_analytical, y_analytical, color="black")
plt.show()
[ 0.          0.80073862  1.93778745  3.46355849  5.43264755  7.9019118
 10.93052913 14.58004453 18.91440711 24.        ]
../_images/solutions_23_1.png