diff --git a/ex5.py b/ex5.py
new file mode 100644
index 0000000000000000000000000000000000000000..7eca272f35ab9aaa743cb4e83e23f9bcde17a220
--- /dev/null
+++ b/ex5.py
@@ -0,0 +1,137 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+import numpy as np
+import matplotlib.pyplot as plt
+from scipy import sparse
+import scipy.sparse.linalg
+
+
+def GradientDescent(x0, E, DE, beta, sigma, maxIter, stopEpsilon, inverseSPMatrix=None):
+
+    def ArmijoRule(x, initialTau, beta, sigma, energy, descentDir, gradientAtX, E):
+
+        tangentSlope = np.dot(descentDir.ravel(), gradientAtX.ravel())
+
+        def ArmijoCondition(tau):
+            secantSlope = (E(x + tau * descentDir) - energy)/tau
+            cond = (secantSlope / tangentSlope) >= sigma
+            return cond
+
+        tauMin = 0.000001
+        tauMax = 100000
+        tau = max(min(initialTau, tauMax), tauMin)
+
+        condition = ArmijoCondition(tau)
+        if condition:
+            while (condition and (tau < tauMax)):
+                tau = tau / beta
+                condition = ArmijoCondition(tau)
+
+            tau = beta * tau
+        else:
+            while ((not condition) and (tau > tauMin)):
+                tau = beta * tau
+                condition = ArmijoCondition(tau)
+
+        return tau
+
+    x = x0
+    energyNew = E(x)
+    tau = 1
+
+    print('Initial energy {:.6f}'.format(energyNew))
+    for i in range(maxIter):
+        energy = energyNew
+        gradient = DE(x)
+        descentDir = - gradient
+        if (inverseSPMatrix is not None):
+            descentDir = inverseSPMatrix * descentDir
+
+        tau = ArmijoRule(x, tau, beta, sigma, energy, descentDir, gradient, E)
+        x = x + tau * descentDir
+        energyNew = E(x)
+
+        print('{} steps, tau: {:.6f} energy {:.6f}'.format(i+1, tau, energyNew))
+
+        if ((energy - energyNew) < stopEpsilon):
+            break
+
+    return x
+
+
+def Ja(x, f, h, lambda_):
+    energy = np.sum(np.square(x - f)) + lambda_ / h**2 * np.sum(np.square(x[1:]-x[0:-1]))
+    return energy
+
+
+def DJa(x, f, h, lambda_):
+    grad = 2 * (x - f)
+    grad[0:-1] = grad[0:-1] - 2 * lambda_ / h**2 * (x[1:]-x[0:-1])
+    grad[1:] = grad[1:] - 2 * lambda_ / h**2 * (x[0:-1] - x[1:])
+    return grad
+
+
+def Jb(x, f, h, lambda_, epsilon):
+    energy = np.sum(np.square(x - f)) + lambda_ / h * np.sum(np.sqrt(np.square(x[1:] - x[0:-1]) + epsilon**2))
+    return energy
+
+
+def DJb(x, f, h, lambda_, epsilon):
+    grad = 2 * (x - f)
+    grad[0:-1] = grad[0:-1] - lambda_ / h * np.divide(x[1:] - x[0:-1], np.sqrt(np.square(x[1:] - x[0:-1]) + epsilon**2))
+    grad[1:] = grad[1:] - lambda_ / h * np.divide(x[0:-1] - x[1:], np.sqrt(np.square(x[0:-1] - x[1:]) + epsilon**2))
+    return grad
+
+
+def main():
+    # Create a noisy input signal
+    N = 200
+    coords = np.linspace(0, 2*np.pi, N)
+    f0 = np.sin(coords)
+    np.random.seed(42)
+    f = f0 + 0.2 * (np.random.rand(coords.size) - 0.5)
+    x0 = f
+
+    # Set parameters;
+    lambda_ = 0.005
+    sigma = 0.5
+    beta = 0.5
+    h = 1/(N - 1)
+    epsilon = .001
+
+    # First functional
+    def Ea(x): return Ja(x, f, h, lambda_)
+    def DEa(x): return DJa(x, f, h, lambda_)
+    xa = GradientDescent(x0, Ea, DEa, beta, sigma, 1000, 0.001)
+
+    # Second functional
+    def Eb(x): return Jb(x, f, h, lambda_, epsilon)
+    def DEb(x): return DJb(x, f, h, lambda_, epsilon)
+    xb = GradientDescent(x0, Eb, DEb, beta, sigma, 1000, 0.001)
+
+    # First functional using the system of linear equations
+    L = 1/h * sparse.csr_matrix(sparse.diags([-1, 1], [0, 1], shape=(N, N)))
+    L[N-1, N-1] = 0
+    identityMatrix = sparse.eye(N)
+    A = identityMatrix + lambda_*L.transpose()*L
+    # For such a low number of unknowns, computing the inverse is fine.
+    invA = scipy.sparse.linalg.inv(sparse.csc_matrix(A))
+    xaLE = invA*x0
+
+    # First functional with gradient flow
+    # Use a small sigma here to get the optimal tau in this case.
+    xaG = GradientDescent(x0, Ea, DEa, beta, 0.1, 1000, 0.001, invA)
+
+    # figure
+    plt.plot(coords, xa, label='xa')
+    plt.plot(coords, xaLE, label='xaLE')
+    plt.plot(coords, xaG, label='xaG')
+    plt.plot(coords, xb, label='xb')
+    plt.plot(coords, f, label='f')
+    plt.plot(coords, f0, label='f0')
+    plt.legend()
+    plt.show()
+
+
+if __name__ == '__main__':
+    main()