diff --git a/Lab-1/.ipynb_checkpoints/ejemyr_lab1-checkpoint.ipynb b/Lab-1/.ipynb_checkpoints/ejemyr_lab1-checkpoint.ipynb
new file mode 100644
index 0000000..ccb802d
--- /dev/null
+++ b/Lab-1/.ipynb_checkpoints/ejemyr_lab1-checkpoint.ipynb
@@ -0,0 +1,486 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "view-in-github"
+ },
+ "source": [
+ ""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "6RgtXlfYO_i7"
+ },
+ "source": [
+ "# **Lab 1: Matrix algorithms**\n",
+ "**Christoffer Ejemyr**"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "9x_J5FVuPzbm"
+ },
+ "source": [
+ "# **Abstract**\n",
+ "\n",
+ "In this report we implement the most fundamental vector and matrix algorithms. They are compared to the solutions of the highly regarded package numpy. All algorithms passed down to the seventh decimal."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "OkT8J7uOWpT3"
+ },
+ "source": [
+ "# **About the code**"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 34
+ },
+ "colab_type": "code",
+ "id": "Pdll1Xc9WP0e",
+ "outputId": "ce1a945e-2dae-4530-cb4d-1236274284c0"
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "'KTH Royal Institute of Technology, Stockholm, Sweden.'"
+ ]
+ },
+ "execution_count": 1,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "\"\"\"This program is a template for lab reports in the course\"\"\"\n",
+ "\"\"\"DD2363 Methods in Scientific Computing, \"\"\"\n",
+ "\"\"\"KTH Royal Institute of Technology, Stockholm, Sweden.\"\"\"\n",
+ "\n",
+ "# Copyright (C) 2019 Christoffer Ejemyr (ejemyr@kth.se)\n",
+ "\n",
+ "# This file is part of the course DD2363 Methods in Scientific Computing\n",
+ "# KTH Royal Institute of Technology, Stockholm, Sweden\n",
+ "#\n",
+ "# This is free software: you can redistribute it and/or modify\n",
+ "# it under the terms of the GNU Lesser General Public License as published by\n",
+ "# the Free Software Foundation, either version 3 of the License, or\n",
+ "# (at your option) any later version."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "28xLGz8JX3Hh"
+ },
+ "source": [
+ "# **Set up environment**"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "Xw7VlErAX7NS"
+ },
+ "outputs": [],
+ "source": [
+ "# Load neccessary modules.\n",
+ "# from google.colab import files\n",
+ "\n",
+ "import time\n",
+ "import numpy as np\n",
+ "import unittest\n",
+ "\n",
+ "from matplotlib import pyplot as plt\n",
+ "from matplotlib import tri\n",
+ "from matplotlib import axes\n",
+ "from mpl_toolkits.mplot3d import Axes3D\n",
+ "\n",
+ "class Tests(unittest.TestCase):\n",
+ " @staticmethod\n",
+ " def check_accuracy(est, true, decimal = 7):\n",
+ " np.testing.assert_almost_equal(est, true, decimal=decimal)\n",
+ "\n",
+ " @staticmethod\n",
+ " def check_accuracy_multiple_random(num_of_tests, generating_func, decimal = 7):\n",
+ " for i in range(num_of_tests):\n",
+ " est, true = generating_func()\n",
+ " Tests.check_accuracy(est, true, decimal)\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "gnO3lhAigLev"
+ },
+ "source": [
+ "# **Introduction**\n",
+ "\n",
+ "In this lab we will define multiplication of the most fundamental parts of linear algebra; vectors and matrices. They will ble implemented acording to the algorithms described in the course literature."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "WeFO9QMeUOAu"
+ },
+ "source": [
+ "# **Methods**"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "uwVrMhGQ4viZ"
+ },
+ "source": [
+ "### Scalar product"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "jqrNhl6Np2Ze"
+ },
+ "outputs": [],
+ "source": [
+ "def scalar_product(x, y):\n",
+ " if type(x) != np.ndarray:\n",
+ " try:\n",
+ " x = np.array(x)\n",
+ " except:\n",
+ " raise Exception(\"Vector format error.\\n\" + \"x: \" + str(x) + \"\\ny: \" + str(y))\n",
+ " if type(y) != np.ndarray:\n",
+ " try:\n",
+ " y = np.array(y)\n",
+ " except:\n",
+ " raise Exception(\"Vector format error.\\n\" + \"x: \" + str(x) + \"\\ny: \" + str(y))\n",
+ "\n",
+ " if x.ndim != 1 or y.ndim != 1:\n",
+ " raise Exception(\"Vector dimentions error.\\n\" + \"x: \" + str(x) + \"\\ny: \" + str(y))\n",
+ " if x.size != y.size:\n",
+ " raise Exception(\"Vector formats don't agree.\\n\" + \"x: \" + str(x) + \"\\ny: \" + str(y))\n",
+ " \n",
+ " sum = 0\n",
+ " for i in range(len(x)):\n",
+ " sum += x[i] * y[i]\n",
+ "\n",
+ " return sum"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "CDxXDYtrrQ-a"
+ },
+ "outputs": [],
+ "source": [
+ "class Tests(Tests):\n",
+ " def test_scalar_product(self):\n",
+ " with self.assertRaises(Exception):\n",
+ " scalar_product([1,2], [1,2,3])\n",
+ " with self.assertRaises(Exception):\n",
+ " scalar_product([[0,0],[0,0]], [[0,0],[0,0]])\n",
+ " with self.assertRaises(Exception):\n",
+ " scalar_product([\"s\",2], [1,3])\n",
+ " with self.assertRaises(Exception):\n",
+ " scalar_product(\"Hej\", [1,2,3])\n",
+ "\n",
+ " min_length = 1\n",
+ " max_length = 100\n",
+ " def genetator():\n",
+ " n = np.random.randint(min_length, max_length)\n",
+ " a = np.random.rand(n)\n",
+ " b = np.random.rand(n)\n",
+ " return scalar_product(a, b), a.dot(b)\n",
+ "\n",
+ " Tests.check_accuracy_multiple_random(1000, genetator)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "FNWnhrnD4jmh"
+ },
+ "source": [
+ "### Matrix-vector product"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "j7rPWYYP4iUV"
+ },
+ "outputs": [],
+ "source": [
+ "def matrix_vector_product(M, x):\n",
+ " if type(M) != np.ndarray:\n",
+ " try:\n",
+ " M = np.array(M)\n",
+ " except:\n",
+ " raise Exception(\"Matrix format error.\\n\" + \"M: \" + str(M))\n",
+ " if type(x) != np.ndarray:\n",
+ " try:\n",
+ " x = np.array(x)\n",
+ " except:\n",
+ " raise Exception(\"Vector format error.\\n\" + \"x: \" + str(x))\n",
+ "\n",
+ " if x.ndim != 1 or M.ndim != 2:\n",
+ " raise Exception(\"Matrix or vector dimentions error.\\n\" + \"M: \" + str(M) + \"\\nx: \" + str(x))\n",
+ " if M.shape[1] != x.size:\n",
+ " raise Exception(\"Matrix and vector formats don't agree.\\n\" + \"M: \" + str(M) + \"\\nx: \" + str(x))\n",
+ "\n",
+ " b = np.zeros(M.shape[0])\n",
+ " for i in range(M.shape[0]):\n",
+ " for j in range(M.shape[1]):\n",
+ " b[i] += M[i, j] * x[j]\n",
+ "\n",
+ " return b"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "53zAuKI06Ona"
+ },
+ "outputs": [],
+ "source": [
+ "class Tests(Tests):\n",
+ " def test_matrix_vector_product(self):\n",
+ " with self.assertRaises(Exception):\n",
+ " scalar_product([[0,0],[0,0]], [1,2,3])\n",
+ " with self.assertRaises(Exception):\n",
+ " scalar_product([[0,0],[0,0]], [\"Hej\", \"Hå\"])\n",
+ "\n",
+ " min_length = 1\n",
+ " max_length = 100\n",
+ " def genetator():\n",
+ " n = np.random.randint(min_length, max_length)\n",
+ " m = np.random.randint(min_length, max_length)\n",
+ " x = np.random.rand(n)\n",
+ " M = np.random.rand(m, n)\n",
+ " return matrix_vector_product(M, x), M.dot(x)\n",
+ "\n",
+ " Tests.check_accuracy_multiple_random(1000, genetator)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "mEWh21BX8mzG"
+ },
+ "source": [
+ "### Matrix-Matrix product"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "2UAZ0QxV8mLb"
+ },
+ "outputs": [],
+ "source": [
+ "def matrix_matrix_product(A, B):\n",
+ " def format_test(M, name):\n",
+ " if type(M) != np.ndarray:\n",
+ " try:\n",
+ " M = np.array(M)\n",
+ " except:\n",
+ " raise Exception(\"Matrix format error.\\n\" + name + \": \" + str(M))\n",
+ "\n",
+ " if M.ndim != 2:\n",
+ " raise Exception(\"Matrix dimentions error.\\n\" + name + \": \" + str(M))\n",
+ " format_test(A, \"A\")\n",
+ " format_test(B, \"B\")\n",
+ " \n",
+ " if A.shape[1] != B.shape[0]:\n",
+ " raise Exception(\"Matrix formats don't agree.\\n\" + \"A: \" + str(A) + \"\\nB: \" + str(B))\n",
+ " \n",
+ " \n",
+ " C = np.zeros((A.shape[0], B.shape[1]))\n",
+ " for i in range(A.shape[0]):\n",
+ " for j in range(B.shape[1]):\n",
+ " for k in range(A.shape[1]):\n",
+ " C[i, j] += A[i, k] * B[k, j]\n",
+ "\n",
+ " return C"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 8,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "bI2zuBOP9lIx"
+ },
+ "outputs": [],
+ "source": [
+ "class Tests(Tests):\n",
+ " def test_matrix_matrix_product(self):\n",
+ " with self.assertRaises(Exception):\n",
+ " scalar_product([[0,0,0],[0,0,0]], [[0,0],[0,0]])\n",
+ " with self.assertRaises(Exception):\n",
+ " scalar_product([[0,0],[0,0]], [[\"Hej\"], [\"Hå\"]])\n",
+ "\n",
+ " min_length = 1\n",
+ " max_length = 100\n",
+ " def genetator():\n",
+ " i = np.random.randint(min_length, max_length)\n",
+ " j = np.random.randint(min_length, max_length)\n",
+ " k = np.random.randint(min_length, max_length)\n",
+ " A = np.random.rand(i, j)\n",
+ " B = np.random.rand(j, k)\n",
+ " return matrix_matrix_product(A, B), A.dot(B)\n",
+ "\n",
+ " Tests.check_accuracy_multiple_random(100, genetator)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "SsQLT38gVbn_"
+ },
+ "source": [
+ "# **Results**"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "To test the methods and to receive a result we test both the error handling and the accuracy of the methods. Sice we don't have any known solution to the expresions tested we compare with a solution known to be exact enough. It is difficult to know where the limit of an \"accurate\" result lie. In this lab i've chosen to check that all result lie within a 7 decimal margin.\n",
+ "\n",
+ "The test first checks that exceptions are raised when entering incompatible inputs, and then somwhere between 100 and 1000 random vectors or matricies are tested for accuracy according to the method described above."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 9,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 102
+ },
+ "colab_type": "code",
+ "id": "G1hVxfti4Ib-",
+ "outputId": "159525fa-e6f3-4acb-c401-f8b9d8ab9928"
+ },
+ "outputs": [
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "...\n",
+ "----------------------------------------------------------------------\n",
+ "Ran 3 tests in 20.209s\n",
+ "\n",
+ "OK\n"
+ ]
+ }
+ ],
+ "source": [
+ "suite = unittest.TestSuite()\n",
+ "suite.addTest(Tests('test_scalar_product'))\n",
+ "suite.addTest(Tests('test_matrix_vector_product'))\n",
+ "suite.addTest(Tests('test_matrix_matrix_product'))\n",
+ "\n",
+ "if __name__ == '__main__':\n",
+ " runner = unittest.TextTestRunner()\n",
+ " runner.run(suite)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "All the test passes, meaning that the potential floating-point-errors is within an acceptable level."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "_4GLBv0zWr7m"
+ },
+ "source": [
+ "# **Discussion**"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Rather non-supricing all methods passed the tests. Since all implemented algorithms have a unique and mathematically explicit way of calculationg the result there ain't much room for error."
+ ]
+ }
+ ],
+ "metadata": {
+ "colab": {
+ "collapsed_sections": [],
+ "include_colab_link": true,
+ "name": "ejemyr_lab1.ipynb",
+ "provenance": [],
+ "toc_visible": true
+ },
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.8.1"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 1
+}
diff --git a/Lab-1/ejemyr_lab1.ipynb b/Lab-1/ejemyr_lab1.ipynb
new file mode 100644
index 0000000..88bc363
--- /dev/null
+++ b/Lab-1/ejemyr_lab1.ipynb
@@ -0,0 +1,486 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "view-in-github"
+ },
+ "source": [
+ ""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "6RgtXlfYO_i7"
+ },
+ "source": [
+ "# **Lab 1: Matrix algorithms**\n",
+ "**Christoffer Ejemyr**"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "9x_J5FVuPzbm"
+ },
+ "source": [
+ "# **Abstract**\n",
+ "\n",
+ "In this report we implement the most fundamental vector and matrix algorithms. They are compared to the solutions of the highly regarded package numpy. All algorithms passed down to the seventh decimal."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "OkT8J7uOWpT3"
+ },
+ "source": [
+ "# **About the code**"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 34
+ },
+ "colab_type": "code",
+ "id": "Pdll1Xc9WP0e",
+ "outputId": "ce1a945e-2dae-4530-cb4d-1236274284c0"
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "'KTH Royal Institute of Technology, Stockholm, Sweden.'"
+ ]
+ },
+ "execution_count": 1,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "\"\"\"This program is a template for lab reports in the course\"\"\"\n",
+ "\"\"\"DD2363 Methods in Scientific Computing, \"\"\"\n",
+ "\"\"\"KTH Royal Institute of Technology, Stockholm, Sweden.\"\"\"\n",
+ "\n",
+ "# Copyright (C) 2019 Christoffer Ejemyr (ejemyr@kth.se)\n",
+ "\n",
+ "# This file is part of the course DD2363 Methods in Scientific Computing\n",
+ "# KTH Royal Institute of Technology, Stockholm, Sweden\n",
+ "#\n",
+ "# This is free software: you can redistribute it and/or modify\n",
+ "# it under the terms of the GNU Lesser General Public License as published by\n",
+ "# the Free Software Foundation, either version 3 of the License, or\n",
+ "# (at your option) any later version."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "28xLGz8JX3Hh"
+ },
+ "source": [
+ "# **Set up environment**"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "Xw7VlErAX7NS"
+ },
+ "outputs": [],
+ "source": [
+ "# Load neccessary modules.\n",
+ "# from google.colab import files\n",
+ "\n",
+ "import time\n",
+ "import numpy as np\n",
+ "import unittest\n",
+ "\n",
+ "from matplotlib import pyplot as plt\n",
+ "from matplotlib import tri\n",
+ "from matplotlib import axes\n",
+ "from mpl_toolkits.mplot3d import Axes3D\n",
+ "\n",
+ "class Tests(unittest.TestCase):\n",
+ " @staticmethod\n",
+ " def check_accuracy(est, true, decimal = 7):\n",
+ " np.testing.assert_almost_equal(est, true, decimal=decimal)\n",
+ "\n",
+ " @staticmethod\n",
+ " def check_accuracy_multiple_random(num_of_tests, generating_func, decimal = 7):\n",
+ " for i in range(num_of_tests):\n",
+ " est, true = generating_func()\n",
+ " Tests.check_accuracy(est, true, decimal)\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "gnO3lhAigLev"
+ },
+ "source": [
+ "# **Introduction**\n",
+ "\n",
+ "In this lab we will define multiplication of the most fundamental parts of linear algebra; vectors and matrices. They will ble implemented acording to the algorithms described in the course's literature."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "WeFO9QMeUOAu"
+ },
+ "source": [
+ "# **Methods**"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "uwVrMhGQ4viZ"
+ },
+ "source": [
+ "### Scalar product"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "jqrNhl6Np2Ze"
+ },
+ "outputs": [],
+ "source": [
+ "def scalar_product(x, y):\n",
+ " if type(x) != np.ndarray:\n",
+ " try:\n",
+ " x = np.array(x)\n",
+ " except:\n",
+ " raise Exception(\"Vector format error.\\n\" + \"x: \" + str(x) + \"\\ny: \" + str(y))\n",
+ " if type(y) != np.ndarray:\n",
+ " try:\n",
+ " y = np.array(y)\n",
+ " except:\n",
+ " raise Exception(\"Vector format error.\\n\" + \"x: \" + str(x) + \"\\ny: \" + str(y))\n",
+ "\n",
+ " if x.ndim != 1 or y.ndim != 1:\n",
+ " raise Exception(\"Vector dimentions error.\\n\" + \"x: \" + str(x) + \"\\ny: \" + str(y))\n",
+ " if x.size != y.size:\n",
+ " raise Exception(\"Vector formats don't agree.\\n\" + \"x: \" + str(x) + \"\\ny: \" + str(y))\n",
+ " \n",
+ " sum = 0\n",
+ " for i in range(len(x)):\n",
+ " sum += x[i] * y[i]\n",
+ "\n",
+ " return sum"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "CDxXDYtrrQ-a"
+ },
+ "outputs": [],
+ "source": [
+ "class Tests(Tests):\n",
+ " def test_scalar_product(self):\n",
+ " with self.assertRaises(Exception):\n",
+ " scalar_product([1,2], [1,2,3])\n",
+ " with self.assertRaises(Exception):\n",
+ " scalar_product([[0,0],[0,0]], [[0,0],[0,0]])\n",
+ " with self.assertRaises(Exception):\n",
+ " scalar_product([\"s\",2], [1,3])\n",
+ " with self.assertRaises(Exception):\n",
+ " scalar_product(\"Hej\", [1,2,3])\n",
+ "\n",
+ " min_length = 1\n",
+ " max_length = 100\n",
+ " def genetator():\n",
+ " n = np.random.randint(min_length, max_length)\n",
+ " a = np.random.rand(n)\n",
+ " b = np.random.rand(n)\n",
+ " return scalar_product(a, b), a.dot(b)\n",
+ "\n",
+ " Tests.check_accuracy_multiple_random(1000, genetator)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "FNWnhrnD4jmh"
+ },
+ "source": [
+ "### Matrix-vector product"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "j7rPWYYP4iUV"
+ },
+ "outputs": [],
+ "source": [
+ "def matrix_vector_product(M, x):\n",
+ " if type(M) != np.ndarray:\n",
+ " try:\n",
+ " M = np.array(M)\n",
+ " except:\n",
+ " raise Exception(\"Matrix format error.\\n\" + \"M: \" + str(M))\n",
+ " if type(x) != np.ndarray:\n",
+ " try:\n",
+ " x = np.array(x)\n",
+ " except:\n",
+ " raise Exception(\"Vector format error.\\n\" + \"x: \" + str(x))\n",
+ "\n",
+ " if x.ndim != 1 or M.ndim != 2:\n",
+ " raise Exception(\"Matrix or vector dimentions error.\\n\" + \"M: \" + str(M) + \"\\nx: \" + str(x))\n",
+ " if M.shape[1] != x.size:\n",
+ " raise Exception(\"Matrix and vector formats don't agree.\\n\" + \"M: \" + str(M) + \"\\nx: \" + str(x))\n",
+ "\n",
+ " b = np.zeros(M.shape[0])\n",
+ " for i in range(M.shape[0]):\n",
+ " for j in range(M.shape[1]):\n",
+ " b[i] += M[i, j] * x[j]\n",
+ "\n",
+ " return b"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "53zAuKI06Ona"
+ },
+ "outputs": [],
+ "source": [
+ "class Tests(Tests):\n",
+ " def test_matrix_vector_product(self):\n",
+ " with self.assertRaises(Exception):\n",
+ " scalar_product([[0,0],[0,0]], [1,2,3])\n",
+ " with self.assertRaises(Exception):\n",
+ " scalar_product([[0,0],[0,0]], [\"Hej\", \"Hå\"])\n",
+ "\n",
+ " min_length = 1\n",
+ " max_length = 100\n",
+ " def genetator():\n",
+ " n = np.random.randint(min_length, max_length)\n",
+ " m = np.random.randint(min_length, max_length)\n",
+ " x = np.random.rand(n)\n",
+ " M = np.random.rand(m, n)\n",
+ " return matrix_vector_product(M, x), M.dot(x)\n",
+ "\n",
+ " Tests.check_accuracy_multiple_random(1000, genetator)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "mEWh21BX8mzG"
+ },
+ "source": [
+ "### Matrix-Matrix product"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "2UAZ0QxV8mLb"
+ },
+ "outputs": [],
+ "source": [
+ "def matrix_matrix_product(A, B):\n",
+ " def format_test(M, name):\n",
+ " if type(M) != np.ndarray:\n",
+ " try:\n",
+ " M = np.array(M)\n",
+ " except:\n",
+ " raise Exception(\"Matrix format error.\\n\" + name + \": \" + str(M))\n",
+ "\n",
+ " if M.ndim != 2:\n",
+ " raise Exception(\"Matrix dimentions error.\\n\" + name + \": \" + str(M))\n",
+ " format_test(A, \"A\")\n",
+ " format_test(B, \"B\")\n",
+ " \n",
+ " if A.shape[1] != B.shape[0]:\n",
+ " raise Exception(\"Matrix formats don't agree.\\n\" + \"A: \" + str(A) + \"\\nB: \" + str(B))\n",
+ " \n",
+ " \n",
+ " C = np.zeros((A.shape[0], B.shape[1]))\n",
+ " for i in range(A.shape[0]):\n",
+ " for j in range(B.shape[1]):\n",
+ " for k in range(A.shape[1]):\n",
+ " C[i, j] += A[i, k] * B[k, j]\n",
+ "\n",
+ " return C"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 8,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "bI2zuBOP9lIx"
+ },
+ "outputs": [],
+ "source": [
+ "class Tests(Tests):\n",
+ " def test_matrix_matrix_product(self):\n",
+ " with self.assertRaises(Exception):\n",
+ " scalar_product([[0,0,0],[0,0,0]], [[0,0],[0,0]])\n",
+ " with self.assertRaises(Exception):\n",
+ " scalar_product([[0,0],[0,0]], [[\"Hej\"], [\"Hå\"]])\n",
+ "\n",
+ " min_length = 1\n",
+ " max_length = 100\n",
+ " def genetator():\n",
+ " i = np.random.randint(min_length, max_length)\n",
+ " j = np.random.randint(min_length, max_length)\n",
+ " k = np.random.randint(min_length, max_length)\n",
+ " A = np.random.rand(i, j)\n",
+ " B = np.random.rand(j, k)\n",
+ " return matrix_matrix_product(A, B), A.dot(B)\n",
+ "\n",
+ " Tests.check_accuracy_multiple_random(100, genetator)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "SsQLT38gVbn_"
+ },
+ "source": [
+ "# **Results**"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "To test the methods and to receive a result we test both the error handling and the accuracy of the methods. Sice we don't have any known solution to the expresions tested we compare with a solution known to be exact enough. It is difficult to know where the limit of an \"accurate\" result lie. In this lab i've chosen to check that all result lie within a 7 decimal margin.\n",
+ "\n",
+ "The test first checks that exceptions are raised when entering incompatible inputs, and then somwhere between 100 and 1000 random vectors or matricies are tested for accuracy according to the method described above."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 9,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 102
+ },
+ "colab_type": "code",
+ "id": "G1hVxfti4Ib-",
+ "outputId": "159525fa-e6f3-4acb-c401-f8b9d8ab9928"
+ },
+ "outputs": [
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "...\n",
+ "----------------------------------------------------------------------\n",
+ "Ran 3 tests in 20.209s\n",
+ "\n",
+ "OK\n"
+ ]
+ }
+ ],
+ "source": [
+ "suite = unittest.TestSuite()\n",
+ "suite.addTest(Tests('test_scalar_product'))\n",
+ "suite.addTest(Tests('test_matrix_vector_product'))\n",
+ "suite.addTest(Tests('test_matrix_matrix_product'))\n",
+ "\n",
+ "if __name__ == '__main__':\n",
+ " runner = unittest.TextTestRunner()\n",
+ " runner.run(suite)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "All the test passes, meaning that the potential floating-point-errors is within an acceptable level."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "_4GLBv0zWr7m"
+ },
+ "source": [
+ "# **Discussion**"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Rather non-supricing all methods passed the tests. Since all implemented algorithms have a unique and mathematically explicit way of calculationg the result there ain't much room for error."
+ ]
+ }
+ ],
+ "metadata": {
+ "colab": {
+ "collapsed_sections": [],
+ "include_colab_link": true,
+ "name": "ejemyr_lab1.ipynb",
+ "provenance": [],
+ "toc_visible": true
+ },
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.8.1"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 1
+}
diff --git a/Lab-2/.ipynb_checkpoints/ejemyr_lab2-checkpoint.ipynb b/Lab-2/.ipynb_checkpoints/ejemyr_lab2-checkpoint.ipynb
new file mode 100644
index 0000000..2aecf8e
--- /dev/null
+++ b/Lab-2/.ipynb_checkpoints/ejemyr_lab2-checkpoint.ipynb
@@ -0,0 +1,707 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "view-in-github"
+ },
+ "source": [
+ ""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "6RgtXlfYO_i7"
+ },
+ "source": [
+ "# **Lab 2: Matrix factorization**\n",
+ "**Christoffer Ejemyr**"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "9x_J5FVuPzbm"
+ },
+ "source": [
+ "# **Abstract**"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "yJipbXtnjrJZ"
+ },
+ "source": [
+ "This lab aims to implement efficient algorithms to both multiply and factorize matrices. The focus lies both in mathematical accuracy and computational cost.\n",
+ "\n",
+ "All methods were implemented in a satisfactory way and the accuracy was generally high."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "OkT8J7uOWpT3"
+ },
+ "source": [
+ "#**About the code**"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "HmB2noTr1Oyo"
+ },
+ "source": [
+ "A short statement on who is the author of the file, and if the code is distributed under a certain license. "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 34
+ },
+ "colab_type": "code",
+ "id": "Pdll1Xc9WP0e",
+ "outputId": "a8b10835-c325-4b91-88f8-4bcdc70c7177"
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "'KTH Royal Institute of Technology, Stockholm, Sweden.'"
+ ]
+ },
+ "execution_count": 2,
+ "metadata": {
+ "tags": []
+ },
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "\"\"\"This program is a template for lab reports in the course\"\"\"\n",
+ "\"\"\"DD2363 Methods in Scientific Computing, \"\"\"\n",
+ "\"\"\"KTH Royal Institute of Technology, Stockholm, Sweden.\"\"\"\n",
+ "\n",
+ "# Copyright (C) 2019 Christoffer Ejemyr (ejemyr@kth.se)\n",
+ "# In collaboration with Leo Enge (leoe@kth.se)\n",
+ "\n",
+ "# This file is part of the course DD2363 Methods in Scientific Computing\n",
+ "# KTH Royal Institute of Technology, Stockholm, Sweden\n",
+ "#\n",
+ "# This is free software: you can redistribute it and/or modify\n",
+ "# it under the terms of the GNU Lesser General Public License as published by\n",
+ "# the Free Software Foundation, either version 3 of the License, or\n",
+ "# (at your option) any later version."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "28xLGz8JX3Hh"
+ },
+ "source": [
+ "# **Set up environment**"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "D2PYNusD08Wa"
+ },
+ "source": [
+ "To have access to the neccessary modules you have to run this cell. If you need additional modules, this is where you add them. "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 0,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "Xw7VlErAX7NS"
+ },
+ "outputs": [],
+ "source": [
+ "# Load neccessary modules.\n",
+ "from google.colab import files\n",
+ "\n",
+ "import unittest\n",
+ "import numpy as np\n",
+ "import scipy.sparse as sparse"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "gnO3lhAigLev"
+ },
+ "source": [
+ "# **Introduction**"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "kJiVD0LvXN6e"
+ },
+ "source": [
+ "This lab aims to implement efficient algorithms to both multiply and factorize matrices. The focus lies both in mathematical accuracy and computational cost.\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "WeFO9QMeUOAu"
+ },
+ "source": [
+ "# **Methods**"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "P7YR1kG_RYLl"
+ },
+ "source": [
+ "## Sparce matrix class\n",
+ "This class saves and handles sparce matrices in CRS format. I've overvritten the numpy method *dot(self, other)* to manually define the matrix-vector product. The algorithm uses the liniarity of matrix-vector multiplication by adding the contribution from each non-zero element in the sparse matrix."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 0,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "gWsprvd7pCVf"
+ },
+ "outputs": [],
+ "source": [
+ "class SparseMatrix(sparse.csr_matrix):\n",
+ " def dot(self, other):\n",
+ " if not(type(other) == np.ndarray and other.ndim == 1):\n",
+ " raise Exception(\"Vector format not recognized.\")\n",
+ " \n",
+ " if other.size != self.shape[1]:\n",
+ " raise Exception(\"Vector is of wrong length.\")\n",
+ "\n",
+ " b = np.zeros(self.shape[0])\n",
+ " for i in range(self.shape[0]):\n",
+ " for j in range(self.indptr[i], self.indptr[i + 1]):\n",
+ " b[i] += self.data[j] * other[self.indices[j]]\n",
+ " return b\n",
+ "\n",
+ " def __str__(self):\n",
+ " return str(self.todense())"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "qJtBIpbVPIHZ"
+ },
+ "source": [
+ "## QR-factorization\n",
+ "The QR-factorization factorizes a matrix $A$ into a matrix $Q$ with normalized column vectors spanning $\\text{Range}(A)$ and an upper triangonal square matrix $R$ with values scaling the column vectors of $Q$ back to $A$.\n",
+ "\n",
+ "The algorithm used is the ordenary Gram-Schmidt method. Its simlisity makes it easely adaptable and is here implemented for all $m \\times n$ matrices where $m \\geq n$.\n",
+ "\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 0,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "daYOUpyXvOp9"
+ },
+ "outputs": [],
+ "source": [
+ "def QR_factorization(A):\n",
+ " if not(type(A) == np.ndarray and A.ndim == 2 and A.shape[1] <= A.shape[0]):\n",
+ " raise Exception(\"Matrix format not recognized.\")\n",
+ "\n",
+ " R = np.zeros((A.shape[1], A.shape[1]))\n",
+ " Q = np.zeros(A.shape)\n",
+ " v = np.zeros(A.shape[0])\n",
+ " v[:] = A[:, 0]\n",
+ "\n",
+ " for i in range(A.shape[1]):\n",
+ " R[i, i] = np.linalg.norm(v)\n",
+ " Q[:, i] = v / R[i, i]\n",
+ " for j in range(i + 1, A.shape[1]):\n",
+ " R[i, j] = Q[:, i].dot(A[:, j])\n",
+ "\n",
+ " if i + 1 != A.shape[1]:\n",
+ " v[:] = A[:, i + 1]\n",
+ " for j in range(i + 1):\n",
+ " v[:] -= R[j, i + 1] * Q[:, j]\n",
+ " \n",
+ " return Q, R"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "EVCT6FJn9Cx3"
+ },
+ "source": [
+ "## Matrix equation solvers\n",
+ "Below three functions for solving or optimizing matrix equations are implemented. All functions solve equations on the form\n",
+ "$$ A x = b. $$\n",
+ "The *backwards_substitution* function works for square, upper triangular matrices $A$. It then uses posibility to explicitly calculate each component of $x$ by backtracking from the equation of the last row in $Ax=b$.\n",
+ "\n",
+ "The *eq_sys_solver* function only takes square non-singular matrices. Using QR-factorization and the fact that for square matrices $Q^{-1} = Q^T$ you get\n",
+ "\n",
+ "\\begin{align}\n",
+ "Ax&=b \\\\\n",
+ "QRx&=b \\\\\n",
+ "Rx&=Q^Tb\n",
+ "\\end{align}\n",
+ "\n",
+ "Since $R$ is upper triangonal the system can be solved by backwards substitution.\n",
+ "\n",
+ "In a similar manner the *least_squares* uses the fact that the solution $x$ to $A^TAx = A^Tb$ will minimise $||Ax - b||$ and thus be the least square solution. Since $A^TA$ is a square matrix we can use the same method as in the *eq_sys_solver* function to solve this new matrix equation."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 0,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "Q_YfLjXmez8O"
+ },
+ "outputs": [],
+ "source": [
+ "def backwards_substitution(U, b):\n",
+ " if not(type(U) == np.ndarray and U.ndim == 2):\n",
+ " raise Exception(\"Matrix format not recognized.\")\n",
+ " if (U != np.triu(U)).any():\n",
+ " raise Exception(\"Matrix is not upper triangular.\")\n",
+ " if np.linalg.det(U) == 0:\n",
+ " raise Exception(\"Matrix is singular\")\n",
+ " if not(type(b) == np.ndarray and b.ndim == 1):\n",
+ " raise Exception(\"Vector format not recognized.\")\n",
+ " if len(b) != U.shape[1]:\n",
+ " raise Exception(\"Vector and matrix formats does not match.\")\n",
+ " \n",
+ " n = U.shape[0]\n",
+ " x = np.zeros(n)\n",
+ " x[-1] = b[-1] / U[-1, -1]\n",
+ "\n",
+ " for i in range(n - 2, -1, -1):\n",
+ " s = 0\n",
+ " for j in range(i + 1, n):\n",
+ " s += U[i, j] * x[j]\n",
+ " x[i] = (b[i] - s) / U[i, i]\n",
+ "\n",
+ " return x\n",
+ "\n",
+ "def eq_sys_solver(A, b):\n",
+ " if A.shape[0] != A.shape[1]:\n",
+ " raise Exception(\"Matrix not square.\")\n",
+ " if np.linalg.det(A) == 0:\n",
+ " raise Exception(\"Matrix is singular.\")\n",
+ " if not(type(A) == np.ndarray and A.ndim == 2):\n",
+ " raise Exception(\"Matrix format not recognized.\")\n",
+ " if not(type(b) == np.ndarray and b.ndim == 1):\n",
+ " raise Exception(\"Vector format not recognized.\")\n",
+ " if len(b) != A.shape[0]:\n",
+ " raise Exception(\"Vector and matrix formats does not match.\")\n",
+ " \n",
+ " Q, R = QR_factorization(A)\n",
+ " return backwards_substitution(R, Q.transpose().dot(b))\n",
+ "\n",
+ "def least_squares(A, b):\n",
+ " if not(type(A) == np.ndarray and A.ndim == 2):\n",
+ " raise Exception(\"Matrix format not recognized.\")\n",
+ " if not(type(b) == np.ndarray and b.ndim == 1):\n",
+ " raise Exception(\"Vector format not recognized.\")\n",
+ " if len(b) != A.shape[0]:\n",
+ " raise Exception(\"Vector and matrix formats does not match.\")\n",
+ " \n",
+ " Q, R = QR_factorization(A.transpose().dot(A))\n",
+ " return backwards_substitution(R, Q.transpose().dot(A.transpose().dot(b)))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "MF1Y_pnQD0u0"
+ },
+ "source": [
+ "## QR eigenvalue algorithm\n",
+ "This eigenvalue algorithm will, after many itterations, converge A and U to the Schur factorization matrices. Since only square matrices will have eigenvalues we limit $A_{in}$ to beeing a square matrix. The method returns the approximation of the eigenvalues and the corresponding eigenvectors."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 0,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "6eE3gbc5ClA5"
+ },
+ "outputs": [],
+ "source": [
+ "def eigen_vals_vecs(A_in, ittr :int):\n",
+ " A = A_in.copy()\n",
+ " if not(type(A) == np.ndarray and A.ndim == 2):\n",
+ " raise Exception(\"Matrix format not recognized.\")\n",
+ " if A.shape[0] != A.shape[1]:\n",
+ " raise Exception(\"Matrix not square.\")\n",
+ " U = np.eye(A.shape[0])\n",
+ " for i in range(ittr):\n",
+ " Q, R = QR_factorization(A)\n",
+ " A = R.dot(Q)\n",
+ " U = U.dot(Q)\n",
+ " return A.diagonal(), U"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "yc1nUGpYA2kG"
+ },
+ "source": [
+ "## Block matrix matrix multiplication\n",
+ "My implementation divides the matrices into blocks using the folowing algorithm.\n",
+ "\n",
+ "Given a dimention of length $N$ that is to be divided into $n$ blocks the fisrt block will be of size\n",
+ "$$d_1 = \\text{ceil}(N / n).$$\n",
+ "The next block will then be of size\n",
+ "$$d_2 = \\text{ceil}(\\frac{N - d_1}{n - 1}).$$\n",
+ "Continuing with the $i$:th block beeing of size\n",
+ "$$d_i = \\text{ceil}(\\frac{N - d_1 - d_2 - \\ldots - d_{i-1}}{n - (i - 1)}.)$$\n",
+ "\n",
+ "This method will garantuee that the blocks will be of sizes differing by maximally one element and that the larges blocks will be first follwed by the smaller blocks.\n",
+ "\n",
+ "I chose this method since it is very deterministic and not dependent on coinsidences between matrix sizes and bock numbers."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 0,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "J5pySVVVBQaP"
+ },
+ "outputs": [],
+ "source": [
+ "def blocked_matrix_matrix(A, B, m :int, n :int, p :int):\n",
+ " if not(type(A) == np.ndarray and A.ndim == 2 and type(B) == np.ndarray and B.ndim == 2):\n",
+ " raise Exception(\"Matrix format not recognized.\")\n",
+ " if A.shape[1] != B.shape[0]:\n",
+ " raise Exception(\"Matrix format do not argree.\")\n",
+ " if m > A.shape[0] or m < 1 or n > B.shape[1] or n < 1 or p > A.shape[1] or p < 1:\n",
+ " raise Exception(\"Invlid number of blocks.\")\n",
+ "\n",
+ " C = np.zeros((A.shape[0], B.shape[1]))\n",
+ "\n",
+ " idx_i, idx_j, idx_k = 0, 0, 0\n",
+ " step_i, step_j, step_k = 0, 0, 0\n",
+ "\n",
+ " for i in range(m):\n",
+ " idx_i += step_i\n",
+ " step_i = int(np.ceil((A.shape[0] - idx_i) / (m - i)))\n",
+ " idx_j = 0\n",
+ " step_j = 0\n",
+ " for j in range(n):\n",
+ " idx_j += step_j\n",
+ " step_j = int(np.ceil((B.shape[1] - idx_j) / (n - j)))\n",
+ " idx_k = 0\n",
+ " step_k = 0\n",
+ " for k in range(p):\n",
+ " idx_k += step_k\n",
+ " step_k = int(np.ceil((A.shape[1] - idx_k) / (p - k)))\n",
+ " C[idx_i : idx_i + step_i, idx_j : idx_j + step_j] += A[idx_i : idx_i + step_i, idx_k : idx_k + step_k].dot(B[idx_k : idx_k + step_k, idx_j : idx_j + step_j])\n",
+ " \n",
+ " return C"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "0cdBusjD2FO8"
+ },
+ "source": [
+ "# Tests\n",
+ "Testing the algorithms mainly consists of two parts: checking raises and other assertions and testing for accuracy and floating point precition.\n",
+ "\n",
+ "Generally the first is done for some common mistakes and checks that exceptions are raised. The second test is done by multiple times generating random input data and testing either against nown results, like norm equal to zero, or against other algorithms that are known to be accurate.\n",
+ "\n",
+ "Most of the accurasy testing methods are strait forward and easy to understand. One that is more interesting is the method to test the least squares solution. Since the norm will not always be zero (only in special cases) we must instead check that the norm of the error is the smallest one for all x. What I did was to repeatidly add a small vector $v$ to the solution $x$ and check that the norm of the error was never smaller than the least squares solution."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 16,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 102
+ },
+ "colab_type": "code",
+ "id": "6rt2JT47al64",
+ "outputId": "5eade20c-6f2c-4559-cff6-dccffb18daec"
+ },
+ "outputs": [
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "..........\n",
+ "----------------------------------------------------------------------\n",
+ "Ran 10 tests in 15.323s\n",
+ "\n",
+ "OK\n"
+ ]
+ }
+ ],
+ "source": [
+ "class TestSparseMatrix(unittest.TestCase):\n",
+ "\n",
+ " def test_exceptions(self):\n",
+ " A = SparseMatrix([[1,0,0],[0,1,0],[0,0,1]])\n",
+ " B = SparseMatrix([[0,0,0],[0,0,0],[0,0,1]])\n",
+ " v = np.array([1, 4])\n",
+ " u = [1, 3, 4]\n",
+ " with self.assertRaises(Exception):\n",
+ " A.dot(B)\n",
+ " with self.assertRaises(Exception):\n",
+ " A.dot(v1)\n",
+ " with self.assertRaises(Exception):\n",
+ " A.dot(v2)\n",
+ "\n",
+ " def test_accuracy(self):\n",
+ " max_n = 10\n",
+ " for i in range(num_of_tests):\n",
+ " n = np.random.randint(1, max_n)\n",
+ " M = np.random.rand(np.random.randint(1, max_n), n)\n",
+ " M_spase = SparseMatrix(M)\n",
+ " v = np.random.rand(n)\n",
+ " for i in range(7):\n",
+ " np.testing.assert_almost_equal(M.dot(v), M_spase.dot(v), decimal=i)\n",
+ "\n",
+ "\n",
+ "class TestQRFactorization(unittest.TestCase):\n",
+ " def test_exceptions(self):\n",
+ " with self.assertRaises(Exception):\n",
+ " QR_factorization(np.array([1]))\n",
+ " with self.assertRaises(Exception):\n",
+ " QR_factorization([[1, 2], [3, 4]])\n",
+ " \n",
+ " def test_accuracy(self):\n",
+ " max_n = 10\n",
+ " for i in range(num_of_tests):\n",
+ " n = np.random.randint(1, max_n)\n",
+ " M = np.random.rand(n, n)\n",
+ "\n",
+ " Q, R = QR_factorization(M)\n",
+ " M_reconstructed = Q.dot(R)\n",
+ "\n",
+ " for i in range(7):\n",
+ " np.testing.assert_almost_equal(\n",
+ " np.linalg.norm(Q.transpose().dot(Q)-np.eye(Q.shape[0]), 'fro'),\n",
+ " 0,\n",
+ " decimal=i)\n",
+ " np.testing.assert_almost_equal(\n",
+ " np.linalg.norm(Q.dot(R) - M, 'fro'),\n",
+ " 0,\n",
+ " decimal=i)\n",
+ " np.testing.assert_almost_equal(R, np.triu(R), decimal=i)\n",
+ "\n",
+ "\n",
+ "class TestBackwardsSub(unittest.TestCase):\n",
+ " def test_exceptions(self):\n",
+ " with self.assertRaises(Exception):\n",
+ " backwards_substitution(np.array([[1, 2, 3], [1, 2, 3], [0, 0, 1]]),\n",
+ " np.array([1, 2, 3]))\n",
+ " \n",
+ " def test_accuracy(self):\n",
+ " max_n = 10\n",
+ " for i in range(num_of_tests):\n",
+ " n = np.random.randint(1, max_n)\n",
+ " U = np.triu(np.random.rand(n, n))\n",
+ " x = np.random.rand(n)\n",
+ " b = U.dot(x)\n",
+ " for i in range(7):\n",
+ " np.testing.assert_almost_equal(x, backwards_substitution(U, b), decimal=i)\n",
+ "\n",
+ "\n",
+ "class TestEqSolver(unittest.TestCase):\n",
+ " def test_accuracy(self):\n",
+ " max_n = 10\n",
+ " for i in range(num_of_tests):\n",
+ " n = np.random.randint(1, max_n)\n",
+ " A = np.random.rand(n, n)\n",
+ " x_true = np.random.rand(n)\n",
+ " b = A.dot(x_true)\n",
+ " x = eq_sys_solver(A, b)\n",
+ "\n",
+ " for i in range(7):\n",
+ " np.testing.assert_almost_equal(\n",
+ " np.linalg.norm(x - x_true),\n",
+ " 0,\n",
+ " decimal=i)\n",
+ " np.testing.assert_almost_equal(\n",
+ " np.linalg.norm(A.dot(x) - b),\n",
+ " 0,\n",
+ " decimal=i)\n",
+ "\n",
+ "\n",
+ "class TestLeastSquare(unittest.TestCase):\n",
+ " def test_accuracy(self):\n",
+ " max_dim = 10\n",
+ " for i in range(num_of_tests):\n",
+ " A = np.zeros((1,1))\n",
+ " while np.linalg.det(A.transpose().dot(A)) == 0:\n",
+ " m = np.random.randint(1, max_dim)\n",
+ " n = np.random.randint(1, m + 1)\n",
+ " A = np.random.rand(m, n)\n",
+ " b = np.random.rand(m)\n",
+ " x = least_squares(A, b)\n",
+ "\n",
+ " for i in range(100):\n",
+ " diff_vec = 0.01 * (2 * np.random.rand(n) - 1)\n",
+ " self.assertTrue(np.linalg.norm(A.dot(x) - b) <= np.linalg.norm(A.dot(x + diff_vec) - b))\n",
+ "\n",
+ "\n",
+ "class TestEigenValues(unittest.TestCase):\n",
+ " def test_accuracy(self):\n",
+ " max_n = 10\n",
+ " for i in range(num_of_tests):\n",
+ " n = np.random.randint(1, max_n)\n",
+ " A = np.random.rand(n, n)\n",
+ " A = A.transpose().dot(A)\n",
+ " eigen_vals, eigen_vectors = eigen_vals_vecs(A, 100)\n",
+ "\n",
+ " for i in range(4):\n",
+ " for i, e in enumerate(eigen_vals):\n",
+ " np.testing.assert_almost_equal(np.linalg.det(A - e * np.eye(A.shape[0])), 0, decimal=i)\n",
+ " np.testing.assert_almost_equal(np.linalg.norm(A.dot(eigen_vectors[:, i]) - e * eigen_vectors[:, i]), 0, decimal=i)\n",
+ "\n",
+ "\n",
+ "class TestBlockedMatrixMult(unittest.TestCase):\n",
+ " def test_accuracy(self):\n",
+ " max_dim = 50\n",
+ " for i in range(num_of_tests):\n",
+ " M = np.random.randint(1, max_dim + 1)\n",
+ " N = np.random.randint(1, max_dim + 1)\n",
+ " P = np.random.randint(1, max_dim + 1)\n",
+ " A = np.random.rand(M, P)\n",
+ " B = np.random.rand(P, N)\n",
+ "\n",
+ " m = np.random.randint(1, M + 1)\n",
+ " n = np.random.randint(1, N + 1)\n",
+ " p = np.random.randint(1, P + 1)\n",
+ "\n",
+ " for i in range(7):\n",
+ " np.testing.assert_almost_equal(\n",
+ " blocked_matrix_matrix(A, B, m, n, p),\n",
+ " A.dot(B),\n",
+ " decimal=i\n",
+ " )\n",
+ "\n",
+ "\n",
+ "num_of_tests = 100\n",
+ "\n",
+ "if __name__ == '__main__':\n",
+ " unittest.main(argv=['first-arg-is-ignored'], exit=False)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "SsQLT38gVbn_"
+ },
+ "source": [
+ "# **Results**"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "poXBrl37O0G2"
+ },
+ "source": [
+ "All test passed with an accuracy of up to seven decimals, except the eigenvalue finder that only had an accuracy of someware around four decimal places. The accuracy of that method did increase with the number of itterations, but is very dependent on the matrix given."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "_4GLBv0zWr7m"
+ },
+ "source": [
+ "# **Discussion**"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "6bcsDSoRXHZe"
+ },
+ "source": [
+ "No suprices were encounterd and all methods implemented in a satisfactory way. I'm especially pleased with the block-size algorithm in the blocked matrix-matrix multiplication algorithm that I figured out on my own."
+ ]
+ }
+ ],
+ "metadata": {
+ "colab": {
+ "collapsed_sections": [],
+ "include_colab_link": true,
+ "name": "Copy of ejemyr_lab2.ipynb",
+ "provenance": []
+ },
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.8.1"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 1
+}
diff --git a/Lab-2/ejemyr_lab2.ipynb b/Lab-2/ejemyr_lab2.ipynb
new file mode 100644
index 0000000..2aecf8e
--- /dev/null
+++ b/Lab-2/ejemyr_lab2.ipynb
@@ -0,0 +1,707 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "view-in-github"
+ },
+ "source": [
+ ""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "6RgtXlfYO_i7"
+ },
+ "source": [
+ "# **Lab 2: Matrix factorization**\n",
+ "**Christoffer Ejemyr**"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "9x_J5FVuPzbm"
+ },
+ "source": [
+ "# **Abstract**"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "yJipbXtnjrJZ"
+ },
+ "source": [
+ "This lab aims to implement efficient algorithms to both multiply and factorize matrices. The focus lies both in mathematical accuracy and computational cost.\n",
+ "\n",
+ "All methods were implemented in a satisfactory way and the accuracy was generally high."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "OkT8J7uOWpT3"
+ },
+ "source": [
+ "#**About the code**"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "HmB2noTr1Oyo"
+ },
+ "source": [
+ "A short statement on who is the author of the file, and if the code is distributed under a certain license. "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 34
+ },
+ "colab_type": "code",
+ "id": "Pdll1Xc9WP0e",
+ "outputId": "a8b10835-c325-4b91-88f8-4bcdc70c7177"
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "'KTH Royal Institute of Technology, Stockholm, Sweden.'"
+ ]
+ },
+ "execution_count": 2,
+ "metadata": {
+ "tags": []
+ },
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "\"\"\"This program is a template for lab reports in the course\"\"\"\n",
+ "\"\"\"DD2363 Methods in Scientific Computing, \"\"\"\n",
+ "\"\"\"KTH Royal Institute of Technology, Stockholm, Sweden.\"\"\"\n",
+ "\n",
+ "# Copyright (C) 2019 Christoffer Ejemyr (ejemyr@kth.se)\n",
+ "# In collaboration with Leo Enge (leoe@kth.se)\n",
+ "\n",
+ "# This file is part of the course DD2363 Methods in Scientific Computing\n",
+ "# KTH Royal Institute of Technology, Stockholm, Sweden\n",
+ "#\n",
+ "# This is free software: you can redistribute it and/or modify\n",
+ "# it under the terms of the GNU Lesser General Public License as published by\n",
+ "# the Free Software Foundation, either version 3 of the License, or\n",
+ "# (at your option) any later version."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "28xLGz8JX3Hh"
+ },
+ "source": [
+ "# **Set up environment**"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "D2PYNusD08Wa"
+ },
+ "source": [
+ "To have access to the neccessary modules you have to run this cell. If you need additional modules, this is where you add them. "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 0,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "Xw7VlErAX7NS"
+ },
+ "outputs": [],
+ "source": [
+ "# Load neccessary modules.\n",
+ "from google.colab import files\n",
+ "\n",
+ "import unittest\n",
+ "import numpy as np\n",
+ "import scipy.sparse as sparse"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "gnO3lhAigLev"
+ },
+ "source": [
+ "# **Introduction**"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "kJiVD0LvXN6e"
+ },
+ "source": [
+ "This lab aims to implement efficient algorithms to both multiply and factorize matrices. The focus lies both in mathematical accuracy and computational cost.\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "WeFO9QMeUOAu"
+ },
+ "source": [
+ "# **Methods**"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "P7YR1kG_RYLl"
+ },
+ "source": [
+ "## Sparce matrix class\n",
+ "This class saves and handles sparce matrices in CRS format. I've overvritten the numpy method *dot(self, other)* to manually define the matrix-vector product. The algorithm uses the liniarity of matrix-vector multiplication by adding the contribution from each non-zero element in the sparse matrix."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 0,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "gWsprvd7pCVf"
+ },
+ "outputs": [],
+ "source": [
+ "class SparseMatrix(sparse.csr_matrix):\n",
+ " def dot(self, other):\n",
+ " if not(type(other) == np.ndarray and other.ndim == 1):\n",
+ " raise Exception(\"Vector format not recognized.\")\n",
+ " \n",
+ " if other.size != self.shape[1]:\n",
+ " raise Exception(\"Vector is of wrong length.\")\n",
+ "\n",
+ " b = np.zeros(self.shape[0])\n",
+ " for i in range(self.shape[0]):\n",
+ " for j in range(self.indptr[i], self.indptr[i + 1]):\n",
+ " b[i] += self.data[j] * other[self.indices[j]]\n",
+ " return b\n",
+ "\n",
+ " def __str__(self):\n",
+ " return str(self.todense())"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "qJtBIpbVPIHZ"
+ },
+ "source": [
+ "## QR-factorization\n",
+ "The QR-factorization factorizes a matrix $A$ into a matrix $Q$ with normalized column vectors spanning $\\text{Range}(A)$ and an upper triangonal square matrix $R$ with values scaling the column vectors of $Q$ back to $A$.\n",
+ "\n",
+ "The algorithm used is the ordenary Gram-Schmidt method. Its simlisity makes it easely adaptable and is here implemented for all $m \\times n$ matrices where $m \\geq n$.\n",
+ "\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 0,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "daYOUpyXvOp9"
+ },
+ "outputs": [],
+ "source": [
+ "def QR_factorization(A):\n",
+ " if not(type(A) == np.ndarray and A.ndim == 2 and A.shape[1] <= A.shape[0]):\n",
+ " raise Exception(\"Matrix format not recognized.\")\n",
+ "\n",
+ " R = np.zeros((A.shape[1], A.shape[1]))\n",
+ " Q = np.zeros(A.shape)\n",
+ " v = np.zeros(A.shape[0])\n",
+ " v[:] = A[:, 0]\n",
+ "\n",
+ " for i in range(A.shape[1]):\n",
+ " R[i, i] = np.linalg.norm(v)\n",
+ " Q[:, i] = v / R[i, i]\n",
+ " for j in range(i + 1, A.shape[1]):\n",
+ " R[i, j] = Q[:, i].dot(A[:, j])\n",
+ "\n",
+ " if i + 1 != A.shape[1]:\n",
+ " v[:] = A[:, i + 1]\n",
+ " for j in range(i + 1):\n",
+ " v[:] -= R[j, i + 1] * Q[:, j]\n",
+ " \n",
+ " return Q, R"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "EVCT6FJn9Cx3"
+ },
+ "source": [
+ "## Matrix equation solvers\n",
+ "Below three functions for solving or optimizing matrix equations are implemented. All functions solve equations on the form\n",
+ "$$ A x = b. $$\n",
+ "The *backwards_substitution* function works for square, upper triangular matrices $A$. It then uses posibility to explicitly calculate each component of $x$ by backtracking from the equation of the last row in $Ax=b$.\n",
+ "\n",
+ "The *eq_sys_solver* function only takes square non-singular matrices. Using QR-factorization and the fact that for square matrices $Q^{-1} = Q^T$ you get\n",
+ "\n",
+ "\\begin{align}\n",
+ "Ax&=b \\\\\n",
+ "QRx&=b \\\\\n",
+ "Rx&=Q^Tb\n",
+ "\\end{align}\n",
+ "\n",
+ "Since $R$ is upper triangonal the system can be solved by backwards substitution.\n",
+ "\n",
+ "In a similar manner the *least_squares* uses the fact that the solution $x$ to $A^TAx = A^Tb$ will minimise $||Ax - b||$ and thus be the least square solution. Since $A^TA$ is a square matrix we can use the same method as in the *eq_sys_solver* function to solve this new matrix equation."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 0,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "Q_YfLjXmez8O"
+ },
+ "outputs": [],
+ "source": [
+ "def backwards_substitution(U, b):\n",
+ " if not(type(U) == np.ndarray and U.ndim == 2):\n",
+ " raise Exception(\"Matrix format not recognized.\")\n",
+ " if (U != np.triu(U)).any():\n",
+ " raise Exception(\"Matrix is not upper triangular.\")\n",
+ " if np.linalg.det(U) == 0:\n",
+ " raise Exception(\"Matrix is singular\")\n",
+ " if not(type(b) == np.ndarray and b.ndim == 1):\n",
+ " raise Exception(\"Vector format not recognized.\")\n",
+ " if len(b) != U.shape[1]:\n",
+ " raise Exception(\"Vector and matrix formats does not match.\")\n",
+ " \n",
+ " n = U.shape[0]\n",
+ " x = np.zeros(n)\n",
+ " x[-1] = b[-1] / U[-1, -1]\n",
+ "\n",
+ " for i in range(n - 2, -1, -1):\n",
+ " s = 0\n",
+ " for j in range(i + 1, n):\n",
+ " s += U[i, j] * x[j]\n",
+ " x[i] = (b[i] - s) / U[i, i]\n",
+ "\n",
+ " return x\n",
+ "\n",
+ "def eq_sys_solver(A, b):\n",
+ " if A.shape[0] != A.shape[1]:\n",
+ " raise Exception(\"Matrix not square.\")\n",
+ " if np.linalg.det(A) == 0:\n",
+ " raise Exception(\"Matrix is singular.\")\n",
+ " if not(type(A) == np.ndarray and A.ndim == 2):\n",
+ " raise Exception(\"Matrix format not recognized.\")\n",
+ " if not(type(b) == np.ndarray and b.ndim == 1):\n",
+ " raise Exception(\"Vector format not recognized.\")\n",
+ " if len(b) != A.shape[0]:\n",
+ " raise Exception(\"Vector and matrix formats does not match.\")\n",
+ " \n",
+ " Q, R = QR_factorization(A)\n",
+ " return backwards_substitution(R, Q.transpose().dot(b))\n",
+ "\n",
+ "def least_squares(A, b):\n",
+ " if not(type(A) == np.ndarray and A.ndim == 2):\n",
+ " raise Exception(\"Matrix format not recognized.\")\n",
+ " if not(type(b) == np.ndarray and b.ndim == 1):\n",
+ " raise Exception(\"Vector format not recognized.\")\n",
+ " if len(b) != A.shape[0]:\n",
+ " raise Exception(\"Vector and matrix formats does not match.\")\n",
+ " \n",
+ " Q, R = QR_factorization(A.transpose().dot(A))\n",
+ " return backwards_substitution(R, Q.transpose().dot(A.transpose().dot(b)))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "MF1Y_pnQD0u0"
+ },
+ "source": [
+ "## QR eigenvalue algorithm\n",
+ "This eigenvalue algorithm will, after many itterations, converge A and U to the Schur factorization matrices. Since only square matrices will have eigenvalues we limit $A_{in}$ to beeing a square matrix. The method returns the approximation of the eigenvalues and the corresponding eigenvectors."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 0,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "6eE3gbc5ClA5"
+ },
+ "outputs": [],
+ "source": [
+ "def eigen_vals_vecs(A_in, ittr :int):\n",
+ " A = A_in.copy()\n",
+ " if not(type(A) == np.ndarray and A.ndim == 2):\n",
+ " raise Exception(\"Matrix format not recognized.\")\n",
+ " if A.shape[0] != A.shape[1]:\n",
+ " raise Exception(\"Matrix not square.\")\n",
+ " U = np.eye(A.shape[0])\n",
+ " for i in range(ittr):\n",
+ " Q, R = QR_factorization(A)\n",
+ " A = R.dot(Q)\n",
+ " U = U.dot(Q)\n",
+ " return A.diagonal(), U"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "yc1nUGpYA2kG"
+ },
+ "source": [
+ "## Block matrix matrix multiplication\n",
+ "My implementation divides the matrices into blocks using the folowing algorithm.\n",
+ "\n",
+ "Given a dimention of length $N$ that is to be divided into $n$ blocks the fisrt block will be of size\n",
+ "$$d_1 = \\text{ceil}(N / n).$$\n",
+ "The next block will then be of size\n",
+ "$$d_2 = \\text{ceil}(\\frac{N - d_1}{n - 1}).$$\n",
+ "Continuing with the $i$:th block beeing of size\n",
+ "$$d_i = \\text{ceil}(\\frac{N - d_1 - d_2 - \\ldots - d_{i-1}}{n - (i - 1)}.)$$\n",
+ "\n",
+ "This method will garantuee that the blocks will be of sizes differing by maximally one element and that the larges blocks will be first follwed by the smaller blocks.\n",
+ "\n",
+ "I chose this method since it is very deterministic and not dependent on coinsidences between matrix sizes and bock numbers."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 0,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "J5pySVVVBQaP"
+ },
+ "outputs": [],
+ "source": [
+ "def blocked_matrix_matrix(A, B, m :int, n :int, p :int):\n",
+ " if not(type(A) == np.ndarray and A.ndim == 2 and type(B) == np.ndarray and B.ndim == 2):\n",
+ " raise Exception(\"Matrix format not recognized.\")\n",
+ " if A.shape[1] != B.shape[0]:\n",
+ " raise Exception(\"Matrix format do not argree.\")\n",
+ " if m > A.shape[0] or m < 1 or n > B.shape[1] or n < 1 or p > A.shape[1] or p < 1:\n",
+ " raise Exception(\"Invlid number of blocks.\")\n",
+ "\n",
+ " C = np.zeros((A.shape[0], B.shape[1]))\n",
+ "\n",
+ " idx_i, idx_j, idx_k = 0, 0, 0\n",
+ " step_i, step_j, step_k = 0, 0, 0\n",
+ "\n",
+ " for i in range(m):\n",
+ " idx_i += step_i\n",
+ " step_i = int(np.ceil((A.shape[0] - idx_i) / (m - i)))\n",
+ " idx_j = 0\n",
+ " step_j = 0\n",
+ " for j in range(n):\n",
+ " idx_j += step_j\n",
+ " step_j = int(np.ceil((B.shape[1] - idx_j) / (n - j)))\n",
+ " idx_k = 0\n",
+ " step_k = 0\n",
+ " for k in range(p):\n",
+ " idx_k += step_k\n",
+ " step_k = int(np.ceil((A.shape[1] - idx_k) / (p - k)))\n",
+ " C[idx_i : idx_i + step_i, idx_j : idx_j + step_j] += A[idx_i : idx_i + step_i, idx_k : idx_k + step_k].dot(B[idx_k : idx_k + step_k, idx_j : idx_j + step_j])\n",
+ " \n",
+ " return C"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "0cdBusjD2FO8"
+ },
+ "source": [
+ "# Tests\n",
+ "Testing the algorithms mainly consists of two parts: checking raises and other assertions and testing for accuracy and floating point precition.\n",
+ "\n",
+ "Generally the first is done for some common mistakes and checks that exceptions are raised. The second test is done by multiple times generating random input data and testing either against nown results, like norm equal to zero, or against other algorithms that are known to be accurate.\n",
+ "\n",
+ "Most of the accurasy testing methods are strait forward and easy to understand. One that is more interesting is the method to test the least squares solution. Since the norm will not always be zero (only in special cases) we must instead check that the norm of the error is the smallest one for all x. What I did was to repeatidly add a small vector $v$ to the solution $x$ and check that the norm of the error was never smaller than the least squares solution."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 16,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 102
+ },
+ "colab_type": "code",
+ "id": "6rt2JT47al64",
+ "outputId": "5eade20c-6f2c-4559-cff6-dccffb18daec"
+ },
+ "outputs": [
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "..........\n",
+ "----------------------------------------------------------------------\n",
+ "Ran 10 tests in 15.323s\n",
+ "\n",
+ "OK\n"
+ ]
+ }
+ ],
+ "source": [
+ "class TestSparseMatrix(unittest.TestCase):\n",
+ "\n",
+ " def test_exceptions(self):\n",
+ " A = SparseMatrix([[1,0,0],[0,1,0],[0,0,1]])\n",
+ " B = SparseMatrix([[0,0,0],[0,0,0],[0,0,1]])\n",
+ " v = np.array([1, 4])\n",
+ " u = [1, 3, 4]\n",
+ " with self.assertRaises(Exception):\n",
+ " A.dot(B)\n",
+ " with self.assertRaises(Exception):\n",
+ " A.dot(v1)\n",
+ " with self.assertRaises(Exception):\n",
+ " A.dot(v2)\n",
+ "\n",
+ " def test_accuracy(self):\n",
+ " max_n = 10\n",
+ " for i in range(num_of_tests):\n",
+ " n = np.random.randint(1, max_n)\n",
+ " M = np.random.rand(np.random.randint(1, max_n), n)\n",
+ " M_spase = SparseMatrix(M)\n",
+ " v = np.random.rand(n)\n",
+ " for i in range(7):\n",
+ " np.testing.assert_almost_equal(M.dot(v), M_spase.dot(v), decimal=i)\n",
+ "\n",
+ "\n",
+ "class TestQRFactorization(unittest.TestCase):\n",
+ " def test_exceptions(self):\n",
+ " with self.assertRaises(Exception):\n",
+ " QR_factorization(np.array([1]))\n",
+ " with self.assertRaises(Exception):\n",
+ " QR_factorization([[1, 2], [3, 4]])\n",
+ " \n",
+ " def test_accuracy(self):\n",
+ " max_n = 10\n",
+ " for i in range(num_of_tests):\n",
+ " n = np.random.randint(1, max_n)\n",
+ " M = np.random.rand(n, n)\n",
+ "\n",
+ " Q, R = QR_factorization(M)\n",
+ " M_reconstructed = Q.dot(R)\n",
+ "\n",
+ " for i in range(7):\n",
+ " np.testing.assert_almost_equal(\n",
+ " np.linalg.norm(Q.transpose().dot(Q)-np.eye(Q.shape[0]), 'fro'),\n",
+ " 0,\n",
+ " decimal=i)\n",
+ " np.testing.assert_almost_equal(\n",
+ " np.linalg.norm(Q.dot(R) - M, 'fro'),\n",
+ " 0,\n",
+ " decimal=i)\n",
+ " np.testing.assert_almost_equal(R, np.triu(R), decimal=i)\n",
+ "\n",
+ "\n",
+ "class TestBackwardsSub(unittest.TestCase):\n",
+ " def test_exceptions(self):\n",
+ " with self.assertRaises(Exception):\n",
+ " backwards_substitution(np.array([[1, 2, 3], [1, 2, 3], [0, 0, 1]]),\n",
+ " np.array([1, 2, 3]))\n",
+ " \n",
+ " def test_accuracy(self):\n",
+ " max_n = 10\n",
+ " for i in range(num_of_tests):\n",
+ " n = np.random.randint(1, max_n)\n",
+ " U = np.triu(np.random.rand(n, n))\n",
+ " x = np.random.rand(n)\n",
+ " b = U.dot(x)\n",
+ " for i in range(7):\n",
+ " np.testing.assert_almost_equal(x, backwards_substitution(U, b), decimal=i)\n",
+ "\n",
+ "\n",
+ "class TestEqSolver(unittest.TestCase):\n",
+ " def test_accuracy(self):\n",
+ " max_n = 10\n",
+ " for i in range(num_of_tests):\n",
+ " n = np.random.randint(1, max_n)\n",
+ " A = np.random.rand(n, n)\n",
+ " x_true = np.random.rand(n)\n",
+ " b = A.dot(x_true)\n",
+ " x = eq_sys_solver(A, b)\n",
+ "\n",
+ " for i in range(7):\n",
+ " np.testing.assert_almost_equal(\n",
+ " np.linalg.norm(x - x_true),\n",
+ " 0,\n",
+ " decimal=i)\n",
+ " np.testing.assert_almost_equal(\n",
+ " np.linalg.norm(A.dot(x) - b),\n",
+ " 0,\n",
+ " decimal=i)\n",
+ "\n",
+ "\n",
+ "class TestLeastSquare(unittest.TestCase):\n",
+ " def test_accuracy(self):\n",
+ " max_dim = 10\n",
+ " for i in range(num_of_tests):\n",
+ " A = np.zeros((1,1))\n",
+ " while np.linalg.det(A.transpose().dot(A)) == 0:\n",
+ " m = np.random.randint(1, max_dim)\n",
+ " n = np.random.randint(1, m + 1)\n",
+ " A = np.random.rand(m, n)\n",
+ " b = np.random.rand(m)\n",
+ " x = least_squares(A, b)\n",
+ "\n",
+ " for i in range(100):\n",
+ " diff_vec = 0.01 * (2 * np.random.rand(n) - 1)\n",
+ " self.assertTrue(np.linalg.norm(A.dot(x) - b) <= np.linalg.norm(A.dot(x + diff_vec) - b))\n",
+ "\n",
+ "\n",
+ "class TestEigenValues(unittest.TestCase):\n",
+ " def test_accuracy(self):\n",
+ " max_n = 10\n",
+ " for i in range(num_of_tests):\n",
+ " n = np.random.randint(1, max_n)\n",
+ " A = np.random.rand(n, n)\n",
+ " A = A.transpose().dot(A)\n",
+ " eigen_vals, eigen_vectors = eigen_vals_vecs(A, 100)\n",
+ "\n",
+ " for i in range(4):\n",
+ " for i, e in enumerate(eigen_vals):\n",
+ " np.testing.assert_almost_equal(np.linalg.det(A - e * np.eye(A.shape[0])), 0, decimal=i)\n",
+ " np.testing.assert_almost_equal(np.linalg.norm(A.dot(eigen_vectors[:, i]) - e * eigen_vectors[:, i]), 0, decimal=i)\n",
+ "\n",
+ "\n",
+ "class TestBlockedMatrixMult(unittest.TestCase):\n",
+ " def test_accuracy(self):\n",
+ " max_dim = 50\n",
+ " for i in range(num_of_tests):\n",
+ " M = np.random.randint(1, max_dim + 1)\n",
+ " N = np.random.randint(1, max_dim + 1)\n",
+ " P = np.random.randint(1, max_dim + 1)\n",
+ " A = np.random.rand(M, P)\n",
+ " B = np.random.rand(P, N)\n",
+ "\n",
+ " m = np.random.randint(1, M + 1)\n",
+ " n = np.random.randint(1, N + 1)\n",
+ " p = np.random.randint(1, P + 1)\n",
+ "\n",
+ " for i in range(7):\n",
+ " np.testing.assert_almost_equal(\n",
+ " blocked_matrix_matrix(A, B, m, n, p),\n",
+ " A.dot(B),\n",
+ " decimal=i\n",
+ " )\n",
+ "\n",
+ "\n",
+ "num_of_tests = 100\n",
+ "\n",
+ "if __name__ == '__main__':\n",
+ " unittest.main(argv=['first-arg-is-ignored'], exit=False)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "SsQLT38gVbn_"
+ },
+ "source": [
+ "# **Results**"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "poXBrl37O0G2"
+ },
+ "source": [
+ "All test passed with an accuracy of up to seven decimals, except the eigenvalue finder that only had an accuracy of someware around four decimal places. The accuracy of that method did increase with the number of itterations, but is very dependent on the matrix given."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "_4GLBv0zWr7m"
+ },
+ "source": [
+ "# **Discussion**"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "6bcsDSoRXHZe"
+ },
+ "source": [
+ "No suprices were encounterd and all methods implemented in a satisfactory way. I'm especially pleased with the block-size algorithm in the blocked matrix-matrix multiplication algorithm that I figured out on my own."
+ ]
+ }
+ ],
+ "metadata": {
+ "colab": {
+ "collapsed_sections": [],
+ "include_colab_link": true,
+ "name": "Copy of ejemyr_lab2.ipynb",
+ "provenance": []
+ },
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.8.1"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 1
+}
diff --git a/Lab-3/.ipynb_checkpoints/ejemyr_lab3-checkpoint.ipynb b/Lab-3/.ipynb_checkpoints/ejemyr_lab3-checkpoint.ipynb
new file mode 100644
index 0000000..1c1465e
--- /dev/null
+++ b/Lab-3/.ipynb_checkpoints/ejemyr_lab3-checkpoint.ipynb
@@ -0,0 +1,624 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "view-in-github"
+ },
+ "source": [
+ ""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "6RgtXlfYO_i7"
+ },
+ "source": [
+ "# **Lab 3: Iterative methods**\n",
+ "**Christoffer Ejemyr**"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "9x_J5FVuPzbm"
+ },
+ "source": [
+ "# **Abstract**"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "OkT8J7uOWpT3"
+ },
+ "source": [
+ "**About the code**"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "HmB2noTr1Oyo"
+ },
+ "source": [
+ "A short statement on who is the author of the file, and if the code is distributed under a certain license. "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 11,
+ "metadata": {
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 34
+ },
+ "colab_type": "code",
+ "id": "Pdll1Xc9WP0e",
+ "outputId": "f74fa781-413b-41e7-a2ec-1bba2288ad4f"
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "'KTH Royal Institute of Technology, Stockholm, Sweden.'"
+ ]
+ },
+ "execution_count": 11,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "\"\"\"This program is a template for lab reports in the course\"\"\"\n",
+ "\"\"\"DD2363 Methods in Scientific Computing, \"\"\"\n",
+ "\"\"\"KTH Royal Institute of Technology, Stockholm, Sweden.\"\"\"\n",
+ "\n",
+ "# Copyright (C) 2019 Christoffer Ejemyr (ejemyr@kth.se)\n",
+ "\n",
+ "# This file is part of the course DD2363 Methods in Scientific Computing\n",
+ "# KTH Royal Institute of Technology, Stockholm, Sweden\n",
+ "#\n",
+ "# This is free software: you can redistribute it and/or modify\n",
+ "# it under the terms of the GNU Lesser General Public License as published by\n",
+ "# the Free Software Foundation, either version 3 of the License, or\n",
+ "# (at your option) any later version."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "28xLGz8JX3Hh"
+ },
+ "source": [
+ "# **Set up environment**"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "D2PYNusD08Wa"
+ },
+ "source": [
+ "To have access to the neccessary modules you have to run this cell. If you need additional modules, this is where you add them. "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 12,
+ "metadata": {
+ "colab": {},
+ "colab_type": "code",
+ "id": "Xw7VlErAX7NS"
+ },
+ "outputs": [],
+ "source": [
+ "# Load neccessary modules.\n",
+ "\n",
+ "import time\n",
+ "import numpy as np\n",
+ "import unittest\n",
+ "\n",
+ "from matplotlib import pyplot as plt\n",
+ "from matplotlib import tri\n",
+ "from matplotlib import axes\n",
+ "from mpl_toolkits.mplot3d import Axes3D"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "gnO3lhAigLev"
+ },
+ "source": [
+ "# **Introduction**"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "WeFO9QMeUOAu"
+ },
+ "source": [
+ "# Methods"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Spectral radius\n",
+ "\n",
+ "We define the spectral radius of a matrix $M$ as \n",
+ "\n",
+ "$$\\rho(M) = |\\text{max}(\\lambda_1, \\lambda_2, \\ldots, \\lambda_n)|$$\n",
+ "\n",
+ "where $\\lambda_1, \\lambda_2, \\ldots, \\lambda_n$ are the eigenvalues of $M$.\n",
+ "\n",
+ "### Richardson iteration\n",
+ "\n",
+ "Below I defined the left preconditioned Richardson iteration. Using $B = I$ (letting parameter `B=None`\n",
+ ") you get the non preconitioned Richardson iteration.\n",
+ "\n",
+ "In my implementation I have the method raising an Exception when $\\rho(I - \\alpha BA) \\geq1$. This is beceause we can not guarantee convergence. But having $\\rho(I - \\alpha BA) \\geq 1$ does not necessarily make it divergent.\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 30,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def spectral_radius(M):\n",
+ " if type(M) != np.ndarray or M.ndim != 2:\n",
+ " raise Exception(\"M matrix format not recogniced.\")\n",
+ " return abs(np.max(np.linalg.eig(M)[0]))\n",
+ "\n",
+ "def richardson_iteration(A, b, alpha, tol=10e-6, x0=None, B=None):\n",
+ " \"\"\"The left preconditioned Richardson iteration.\"\"\"\n",
+ " \n",
+ " if type(A) != np.ndarray or A.ndim != 2:\n",
+ " raise Exception(\"A matrix format not recogniced.\")\n",
+ " if B is None:\n",
+ " B = np.eye(A.shape[0])\n",
+ " if type(B) != np.ndarray or B.ndim != 2:\n",
+ " raise Exception(\"B matrix format not recogniced.\")\n",
+ " if A.shape[0] != A.shape[1]:\n",
+ " raise Exception(\"Matrix not square.\")\n",
+ " if (x0 is not None) and x0.size != A.shape[1]:\n",
+ " raise Exception(\"Shapes of x0 and A does not agree.\")\n",
+ " if A.shape[0] != B.shape[1]:\n",
+ " raise Exception(\"Shapes of A and B does not agree.\")\n",
+ " if B.shape[0] != b.size:\n",
+ " raise Exception(\"Shapes of B and b does not agree.\")\n",
+ " \n",
+ " x = None\n",
+ " if x0 is None:\n",
+ " x = np.zeros(A.shape[1])\n",
+ " else:\n",
+ " x = x0.copy()\n",
+ " \n",
+ " if spectral_radius(np.eye(B.shape[0]) - alpha * B.dot(A)) >= 1:\n",
+ " raise Exception(\"Not converging.\")\n",
+ " \n",
+ " \n",
+ " r = np.zeros(B.shape[0])\n",
+ " r[:] = b - A.dot(x)\n",
+ " i = 0\n",
+ " while np.linalg.norm(r) >= tol:\n",
+ " r[:] = b - A.dot(x)\n",
+ " x[:] = x[:] + alpha * B.dot(r)\n",
+ " i += 1\n",
+ "\n",
+ " return x, i"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Jacobi iteration\n",
+ "\n",
+ "As the lecture notes pointed out the Jacobi iteration is only the left preconditioned richardson itteration with $B = (\\alpha D)^{-1}$, where $D$ is the diagonal matrix with $\\text{diag}(D) = \\text{diag}(A)$."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 31,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def jacobi_iteration(A, b, alpha, tol=10e-6, x0=None):\n",
+ " B = (1. / alpha) * np.diag(1. / np.diag(A))\n",
+ " return richardson_iteration(A, b, alpha, tol=tol, x0=x0, B=B)\n",
+ "\n",
+ "def check_jacobi_convergence(A, alpha):\n",
+ " B = (1. / alpha) * np.diag(1. / np.diag(A))\n",
+ " return spectral_radius(np.eye(B.shape[0]) - alpha * B.dot(A)) < 1"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Gauss-Seidel iteration\n",
+ "\n",
+ "As the lecture notes pointed out the Gauss-Seidel iteration is only the left preconditioned richardson itteration with $B = (\\alpha L)^{-1}$, where $L$ is the lower triangonal matrix created by zeroing out the over-diagonal elements in $A$."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 32,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def gauss_seidel_iteration(A, b, alpha, tol=10e-6, x0=None):\n",
+ " try:\n",
+ " B = (1. / alpha) * np.linalg.inv(np.tril(A))\n",
+ " return richardson_iteration(A, b, alpha, tol=tol, x0=x0, B=B)\n",
+ " except:\n",
+ " return False\n",
+ "\n",
+ "def check_gauss_seidel_convergence(A, b, alpha):\n",
+ " try:\n",
+ " B = (1. / alpha) * np.linalg.inv(np.tril(A))\n",
+ " return spectral_radius(np.eye(B.shape[0]) - alpha * B.dot(A)) < 1\n",
+ " except:\n",
+ " return False"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Arnoldi iteration\n",
+ "\n",
+ "I used the algorithm in the lecturenotes with slight modifications. Having problems with the algorithm dividing by zero I (with slight inpiration from Wikipedia, heh.) added a test `H[j + 1, j] > 1e-12` to ensure that no `nan` values occur."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 33,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def arnoldi_iteration(A, b, k: int):\n",
+ " if type(A) != np.ndarray or A.ndim != 2:\n",
+ " raise Exception(\"A matrix format not recogniced.\")\n",
+ " if type(b) != np.ndarray or b.ndim != 1:\n",
+ " raise Exception(\"b vector format not recogniced.\")\n",
+ " if A.shape[0] != b.size:\n",
+ " raise Exception(\"Shapes of A and b does not agree.\")\n",
+ " \n",
+ " H = np.zeros((k + 1, k))\n",
+ " Q = np.zeros((A.shape[0], k + 1))\n",
+ " Q[:, 0] = b / np.linalg.norm(b)\n",
+ " \n",
+ " for j in range(k):\n",
+ " v = A.dot(Q[:, j])\n",
+ " for i in range(j + 1):\n",
+ " H[i, j] = np.dot(Q[:, i].conj(), v)\n",
+ " v = v - H[i, j] * Q[:, i]\n",
+ "\n",
+ " H[j + 1, j] = np.linalg.norm(v)\n",
+ " if H[j + 1, j] > 1e-12:\n",
+ " Q[:, j + 1] = v / H[j + 1, j]\n",
+ " else:\n",
+ " return Q, H\n",
+ " return Q, H"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Standard basis\n",
+ "Super simple vector generator. Replace element $i$ with $1$."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 34,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def standard_basis(n: int, i: int):\n",
+ " e_i = np.zeros(n)\n",
+ " e_i[i] = 1.\n",
+ " return e_i"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### GMRES algorithm\n",
+ "\n",
+ "Since we already written a least squares solver in a previous lab I use Numpy's `numpy.linalg.lstsq` method. To the algorithm in the lecture notes I've also added a maximum number of itterations."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 35,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def gmres(A, b, max_itr=None, tol=10e-6):\n",
+ " if type(A) != np.ndarray or A.ndim != 2:\n",
+ " raise Exception(\"A matrix format not recogniced.\")\n",
+ " if type(b) != np.ndarray or b.ndim != 1:\n",
+ " raise Exception(\"b vector format not recogniced.\")\n",
+ " if A.shape[0] != b.size:\n",
+ " raise Exception(\"Shapes of A and b does not agree.\")\n",
+ " \n",
+ " norm_b = np.linalg.norm(b)\n",
+ " \n",
+ " Q = np.zeros((b.size, 1))\n",
+ " Q[:, 0] = b[:]/norm_b\n",
+ " \n",
+ " y = None\n",
+ " r = tol * norm_b\n",
+ " \n",
+ " k = 0\n",
+ " while np.linalg.norm(r) >= tol * norm_b:\n",
+ " Q, H = arnoldi_iteration(A, b, k)\n",
+ " y = np.linalg.lstsq(H, norm_b * standard_basis(k+1, 0), rcond=None)[0]\n",
+ " r = H.dot(y)\n",
+ " r[:] = norm_b * standard_basis(k+1, 0) - r[:]\n",
+ " k += 1\n",
+ " if not(max_itr is None) and k >= max_itr:\n",
+ " break\n",
+ " \n",
+ " x = Q[:, 0:k-1].dot(y)\n",
+ " return x, k"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Testing iteration algorithms\n",
+ "\n",
+ "The testing of accuracy of the iteration solvers are very alike. Therefore I defined a `test_iteration_solver` method. It generates random matrix $A$ of size $\\text{max_size} \\times \\text{max_size}$ and a random vector $x$ of size $\\text{max_size}$ and then creates $b = Ax$. It then checks $||x_{est} - x|| \\approx 0$ and $||Ax - b|| \\approx 0$ down to `decimal` decimals. The process is repeated `num_of_tests` times."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 36,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "F.F\n",
+ "======================================================================\n",
+ "FAIL: test_gauss_seidel (__main__.TestIterationSolvers)\n",
+ "----------------------------------------------------------------------\n",
+ "Traceback (most recent call last):\n",
+ " File \"\", line 41, in test_gauss_seidel\n",
+ " test_iteration_solver(gauss_seidel_iteration,\n",
+ " File \"\", line 23, in test_iteration_solver\n",
+ " np.testing.assert_almost_equal(\n",
+ " File \"/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/numpy/testing/_private/utils.py\", line 593, in assert_almost_equal\n",
+ " raise AssertionError(_build_err_msg())\n",
+ "AssertionError: \n",
+ "Arrays are not almost equal to 0 decimals\n",
+ " ACTUAL: nan\n",
+ " DESIRED: 0\n",
+ "\n",
+ "======================================================================\n",
+ "FAIL: test_jacobi (__main__.TestIterationSolvers)\n",
+ "----------------------------------------------------------------------\n",
+ "Traceback (most recent call last):\n",
+ " File \"\", line 34, in test_jacobi\n",
+ " test_iteration_solver(jacobi_iteration,\n",
+ " File \"\", line 23, in test_iteration_solver\n",
+ " np.testing.assert_almost_equal(\n",
+ " File \"/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/numpy/testing/_private/utils.py\", line 593, in assert_almost_equal\n",
+ " raise AssertionError(_build_err_msg())\n",
+ "AssertionError: \n",
+ "Arrays are not almost equal to 0 decimals\n",
+ " ACTUAL: nan\n",
+ " DESIRED: 0\n",
+ "\n",
+ "----------------------------------------------------------------------\n",
+ "Ran 3 tests in 0.107s\n",
+ "\n",
+ "FAILED (failures=2)\n"
+ ]
+ }
+ ],
+ "source": [
+ "def test_iteration_solver(solver, alpha=None, decimal=6, num_of_tests=1000, max_size=100):\n",
+ " i = 0\n",
+ " while i < num_of_tests:\n",
+ " n = np.random.randint(1, max_size)\n",
+ " A = np.random.rand(n, n)\n",
+ " x_true = np.random.rand(n)\n",
+ " b = A.dot(x_true)\n",
+ " x = np.zeros(n)\n",
+ "\n",
+ " if solver == jacobi_iteration and (not check_jacobi_convergence(A, b, alpha)):\n",
+ " continue\n",
+ " elif solver == gauss_seidel_iteration and (not check_gauss_seidel_convergence(A, b, alpha)):\n",
+ " continue\n",
+ " \n",
+ " if alpha is None:\n",
+ " x[:] = solver(A, b, tol=10**(-decimal))[0]\n",
+ " else:\n",
+ " x[:] = solver(A, b, alpha, tol=10**(-decimal))[0]\n",
+ "\n",
+ " i += 1\n",
+ "\n",
+ " for j in range(decimal):\n",
+ " np.testing.assert_almost_equal(\n",
+ " np.linalg.norm(x - x_true),\n",
+ " 0,\n",
+ " decimal=j)\n",
+ " np.testing.assert_almost_equal(\n",
+ " np.linalg.norm(A.dot(x) - b),\n",
+ " 0,\n",
+ " decimal=j)\n",
+ "\n",
+ "class TestIterationSolvers(unittest.TestCase):\n",
+ " def test_jacobi(self):\n",
+ " test_iteration_solver(jacobi_iteration,\n",
+ " alpha=0.1,\n",
+ " decimal=6,\n",
+ " num_of_tests=10,\n",
+ " max_size=10)\n",
+ " \n",
+ " def test_gauss_seidel(self):\n",
+ " test_iteration_solver(gauss_seidel_iteration,\n",
+ " alpha=0.1,\n",
+ " decimal=6,\n",
+ " num_of_tests=10,\n",
+ " max_size=10)\n",
+ "\n",
+ " def test_gmres(self):\n",
+ " test_iteration_solver(gmres,\n",
+ " decimal=6,\n",
+ " num_of_tests=10,\n",
+ " max_size=10)\n",
+ "\n",
+ "if __name__ == '__main__':\n",
+ " unittest.main(argv=['first-arg-is-ignored'], exit=False)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "SsQLT38gVbn_"
+ },
+ "source": [
+ "# **Results**"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "#### Convergence rate of different iterators.\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 10,
+ "metadata": {},
+ "outputs": [
+ {
+ "ename": "KeyboardInterrupt",
+ "evalue": "",
+ "output_type": "error",
+ "traceback": [
+ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
+ "\u001b[0;31mKeyboardInterrupt\u001b[0m Traceback (most recent call last)",
+ "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 15\u001b[0m \u001b[0;32mwhile\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0mis_ok\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 16\u001b[0m \u001b[0mA\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrandom\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrand\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mn\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mn\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 17\u001b[0;31m \u001b[0;32mif\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0;32mnot\u001b[0m \u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0misclose\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdiag\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mA\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m0\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0many\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mand\u001b[0m \u001b[0mis_invertible\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtril\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mA\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 18\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mspectral_radius\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0meye\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mn\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m-\u001b[0m \u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mlinalg\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0minv\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtril\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mA\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdot\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mA\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m<\u001b[0m \u001b[0;36m1\u001b[0m \u001b[0;32mand\u001b[0m \u001b[0mspectral_radius\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0meye\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mn\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m-\u001b[0m \u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdiag\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m1.\u001b[0m \u001b[0;34m/\u001b[0m \u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdiag\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mA\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdot\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mA\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m<\u001b[0m \u001b[0;36m1\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 19\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mA\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
+ "\u001b[0;32m<__array_function__ internals>\u001b[0m in \u001b[0;36mtril\u001b[0;34m(*args, **kwargs)\u001b[0m\n",
+ "\u001b[0;32m/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/numpy/lib/twodim_base.py\u001b[0m in \u001b[0;36mtril\u001b[0;34m(m, k)\u001b[0m\n\u001b[1;32m 436\u001b[0m \u001b[0mmask\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mtri\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0mm\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mshape\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m-\u001b[0m\u001b[0;36m2\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mk\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mk\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mdtype\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mbool\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 437\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 438\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mwhere\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmask\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mm\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mzeros\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mm\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdtype\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 439\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 440\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n",
+ "\u001b[0;32m<__array_function__ internals>\u001b[0m in \u001b[0;36mwhere\u001b[0;34m(*args, **kwargs)\u001b[0m\n",
+ "\u001b[0;31mKeyboardInterrupt\u001b[0m: "
+ ]
+ }
+ ],
+ "source": [
+ "iterators = [jacobi_iteration, gauss_seidel_iteration]\n",
+ "alphas = np.linspace(0.001, 0.1, 10)\n",
+ "rates = [[], []]\n",
+ "\n",
+ "n = 10\n",
+ "num_of_tests = 10\n",
+ "\n",
+ "alpha = max(alphas)\n",
+ "\n",
+ "def is_invertible(a):\n",
+ " return a.shape[0] == a.shape[1] and np.linalg.matrix_rank(a) == a.shape[0]\n",
+ "\n",
+ "is_ok = False\n",
+ "A = np.zeros((n, n))\n",
+ "while not is_ok:\n",
+ " A = np.random.rand(n, n)\n",
+ " if (not np.isclose(np.diag(A), 0).any()) and is_invertible(np.tril(A)):\n",
+ " if spectral_radius(np.eye(n) - np.linalg.inv(np.tril(A)).dot(A)) < 1 and spectral_radius(np.eye(n) - np.diag(1. / np.diag(A)).dot(A)) < 1:\n",
+ " print(A)\n",
+ " is_ok = True\n",
+ " \n",
+ "b = A.dot(np.random.rand(n))\n",
+ "for i, iterator in enumerate(iterators):\n",
+ " for alpha in alphas:\n",
+ " t0 = time.time()\n",
+ " for j in range(num_of_tests):\n",
+ " iterator(A, b, alpha, tol=10e-6)\n",
+ " \n",
+ " rates[i].append(time.time() - t0)\n",
+ " \n",
+ " plt.plot(alphas, rates[i], label=iterator)\n",
+ " \n",
+ "plt.show()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": []
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "_4GLBv0zWr7m"
+ },
+ "source": [
+ "\n",
+ "# **Discussion**"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": []
+ }
+ ],
+ "metadata": {
+ "colab": {
+ "include_colab_link": true,
+ "name": "template-report-lab-X.ipynb",
+ "provenance": []
+ },
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.8.1"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 1
+}
diff --git a/Lab-3/ejemyr_lab3.ipynb b/Lab-3/ejemyr_lab3.ipynb
new file mode 100644
index 0000000..33a9f52
--- /dev/null
+++ b/Lab-3/ejemyr_lab3.ipynb
@@ -0,0 +1,769 @@
+{
+ "nbformat": 4,
+ "nbformat_minor": 0,
+ "metadata": {
+ "colab": {
+ "name": "ejemyr_lab_3.ipynb",
+ "provenance": [],
+ "collapsed_sections": [],
+ "toc_visible": true,
+ "include_colab_link": true
+ },
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.8.1"
+ }
+ },
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "view-in-github",
+ "colab_type": "text"
+ },
+ "source": [
+ ""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "6RgtXlfYO_i7"
+ },
+ "source": [
+ "# **Lab 3: Iterative methods**\n",
+ "**Christoffer Ejemyr**"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "9x_J5FVuPzbm"
+ },
+ "source": [
+ "# Abstract\n",
+ "\n",
+ "In this lab many different itterative methods were investigated. They generally succeded, but not allways. It is intresting how the initial values sometimes can matter to the degree that the method never converges for some initial values."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "OkT8J7uOWpT3"
+ },
+ "source": [
+ "# About the code"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "HmB2noTr1Oyo"
+ },
+ "source": [
+ "A short statement on who is the author of the file, and if the code is distributed under a certain license. "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "metadata": {
+ "colab_type": "code",
+ "id": "Pdll1Xc9WP0e",
+ "outputId": "855e77bf-545b-4a37-abbc-8cdc89ee8069",
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 34
+ }
+ },
+ "source": [
+ "\"\"\"This program is a template for lab reports in the course\"\"\"\n",
+ "\"\"\"DD2363 Methods in Scientific Computing, \"\"\"\n",
+ "\"\"\"KTH Royal Institute of Technology, Stockholm, Sweden.\"\"\"\n",
+ "\n",
+ "# Copyright (C) 2019 Christoffer Ejemyr (ejemyr@kth.se)\n",
+ "\n",
+ "# This file is part of the course DD2363 Methods in Scientific Computing\n",
+ "# KTH Royal Institute of Technology, Stockholm, Sweden\n",
+ "#\n",
+ "# This is free software: you can redistribute it and/or modify\n",
+ "# it under the terms of the GNU Lesser General Public License as published by\n",
+ "# the Free Software Foundation, either version 3 of the License, or\n",
+ "# (at your option) any later version."
+ ],
+ "execution_count": 1,
+ "outputs": [
+ {
+ "output_type": "execute_result",
+ "data": {
+ "text/plain": [
+ "'KTH Royal Institute of Technology, Stockholm, Sweden.'"
+ ]
+ },
+ "metadata": {
+ "tags": []
+ },
+ "execution_count": 1
+ }
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "28xLGz8JX3Hh"
+ },
+ "source": [
+ "# **Set up environment**"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "D2PYNusD08Wa"
+ },
+ "source": [
+ "To have access to the neccessary modules you have to run this cell. If you need additional modules, this is where you add them. "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "metadata": {
+ "colab_type": "code",
+ "id": "Xw7VlErAX7NS",
+ "colab": {}
+ },
+ "source": [
+ "# Load neccessary modules.\n",
+ "\n",
+ "import time\n",
+ "import numpy as np\n",
+ "import unittest\n",
+ "import math\n",
+ "import random\n",
+ "\n",
+ "from matplotlib import pyplot as plt\n",
+ "from matplotlib import tri\n",
+ "from matplotlib import axes\n",
+ "from mpl_toolkits.mplot3d import Axes3D"
+ ],
+ "execution_count": 0,
+ "outputs": []
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "gnO3lhAigLev"
+ },
+ "source": [
+ "# Introduction\n",
+ "\n",
+ "In this lab we will solve systems of linear equations, as well as finding zeros of functions. This will all be done using iterative methods."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "WeFO9QMeUOAu"
+ },
+ "source": [
+ "# Methods"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "EN1qvsnqRj4z"
+ },
+ "source": [
+ "### Standard basis\n",
+ "Super simple vector generator. Replace element $i$ with $1$."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "metadata": {
+ "colab_type": "code",
+ "id": "qN-JazumRj41",
+ "colab": {}
+ },
+ "source": [
+ "def standard_basis(n: int, i: int):\n",
+ " e_i = np.zeros(n)\n",
+ " e_i[i] = 1.\n",
+ " return e_i"
+ ],
+ "execution_count": 0,
+ "outputs": []
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "nHeust2SRj4S"
+ },
+ "source": [
+ "### Spectral radius\n",
+ "\n",
+ "We define the spectral radius of a matrix $M$ as \n",
+ "\n",
+ "$$\\rho(M) = \\text{max}\\lbrace|\\lambda_1|, |\\lambda_2|, \\ldots, |\\lambda_n|\\rbrace$$\n",
+ "\n",
+ "where $\\lambda_1, \\lambda_2, \\ldots, \\lambda_n$ are the eigenvalues of $M$.\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "metadata": {
+ "id": "Y6YTpQi8OZjR",
+ "colab_type": "code",
+ "colab": {}
+ },
+ "source": [
+ "def spectral_radius(M):\n",
+ " if type(M) != np.ndarray or M.ndim != 2:\n",
+ " raise Exception(\"M matrix format not recogniced.\")\n",
+ " return np.max(np.abs(np.linalg.eig(M)[0]))"
+ ],
+ "execution_count": 0,
+ "outputs": []
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "TlvMgwWqOWlV",
+ "colab_type": "text"
+ },
+ "source": [
+ "### Richardson iteration\n",
+ "\n",
+ "Below I defined the left preconditioned Richardson iteration. Using $B = I$ (letting parameter `B=None`\n",
+ ") you get the non preconitioned Richardson iteration.\n",
+ "\n",
+ "In my implementation I have the method raising an Exception when $\\rho(I - \\alpha BA) \\geq1$. This is beceause we can not guarantee convergence. But having $\\rho(I - \\alpha BA) \\geq 1$ does not necessarily make it divergent.\n",
+ "\n",
+ "I've also changed the stoping criteria to be $||b - Ax|| < \\text{TOL}$ instead of $||B(b - Ax)|| < \\text{TOL}$, since it generally is the residual $||b - Ax||$ you want to minimize."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "metadata": {
+ "colab_type": "code",
+ "id": "mvBbM7pURj4W",
+ "colab": {}
+ },
+ "source": [
+ "def richardson_iteration(A, b, alpha, tol=1e-6, x0=None, B=None):\n",
+ " \"\"\"The left preconditioned Richardson iteration.\"\"\"\n",
+ " \n",
+ " if type(A) != np.ndarray or A.ndim != 2:\n",
+ " raise Exception(\"A matrix format not recogniced.\")\n",
+ " if B is None:\n",
+ " B = np.eye(A.shape[0])\n",
+ " if type(B) != np.ndarray or B.ndim != 2:\n",
+ " raise Exception(\"B matrix format not recogniced.\")\n",
+ " if A.shape[0] != A.shape[1]:\n",
+ " raise Exception(\"Matrix not square.\")\n",
+ " if (x0 is not None) and x0.size != A.shape[1]:\n",
+ " raise Exception(\"Shapes of x0 and A does not agree.\")\n",
+ " if A.shape[0] != B.shape[1]:\n",
+ " raise Exception(\"Shapes of A and B does not agree.\")\n",
+ " if B.shape[0] != b.size:\n",
+ " raise Exception(\"Shapes of B and b does not agree.\")\n",
+ " \n",
+ " x = None\n",
+ " if x0 is None:\n",
+ " x = np.zeros(A.shape[1])\n",
+ " else:\n",
+ " x = x0.copy()\n",
+ " \n",
+ " if spectral_radius(np.eye(B.shape[0]) - alpha * B.dot(A)) >= 1:\n",
+ " return None\n",
+ " \n",
+ " r = np.zeros(B.shape[0])\n",
+ " r[:] = b - A.dot(x)\n",
+ " i = 0\n",
+ " while np.linalg.norm(r) > tol:\n",
+ " r[:] = b - A.dot(x)\n",
+ " x[:] = x[:] + alpha * B.dot(r)\n",
+ " i += 1\n",
+ "\n",
+ " return x, i"
+ ],
+ "execution_count": 0,
+ "outputs": []
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "CNMzSwaQRj4a"
+ },
+ "source": [
+ "### Jacobi iteration\n",
+ "\n",
+ "As the lecture notes pointed out the Jacobi iteration is only the left preconditioned richardson itteration with $B = (\\alpha D)^{-1}$, where $D$ is the diagonal matrix with $\\text{diag}(D) = \\text{diag}(A)$."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "metadata": {
+ "colab_type": "code",
+ "id": "R9ZTdJEARj4d",
+ "colab": {}
+ },
+ "source": [
+ "def check_jacobi_convergence(A):\n",
+ " if (np.diag(A) != 0).all():\n",
+ " B = np.diag(1. / np.diag(A))\n",
+ " return spectral_radius(np.eye(B.shape[0]) - B.dot(A)) < 1\n",
+ " else:\n",
+ " return False\n",
+ "\n",
+ "def jacobi_iteration(A, b, tol=1e-6, x0=None):\n",
+ " if check_jacobi_convergence(A):\n",
+ " B = np.diag(1. / np.diag(A))\n",
+ " return richardson_iteration(A, b, 1., tol=tol, x0=x0, B=B)\n",
+ " else:\n",
+ " return None"
+ ],
+ "execution_count": 0,
+ "outputs": []
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "FhX3ZwNDRj4g"
+ },
+ "source": [
+ "### Gauss-Seidel iteration\n",
+ "\n",
+ "As the lecture notes pointed out the Gauss-Seidel iteration is only the left preconditioned richardson itteration with $B = (\\alpha L)^{-1}$, where $L$ is the lower triangonal matrix created by zeroing out the over-diagonal elements in $A$."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "metadata": {
+ "colab_type": "code",
+ "id": "ULfGq2P8Rj4h",
+ "colab": {}
+ },
+ "source": [
+ "def check_gauss_seidel_convergence(A):\n",
+ " if (np.diag(A) != 0).all():\n",
+ " B = np.linalg.inv(np.tril(A))\n",
+ " return spectral_radius(np.eye(B.shape[0]) - B.dot(A)) < 1\n",
+ " else:\n",
+ " return False\n",
+ "\n",
+ "def gauss_seidel_iteration(A, b, tol=1e-6, x0=None):\n",
+ " if check_gauss_seidel_convergence(A):\n",
+ " B = np.linalg.inv(np.tril(A))\n",
+ " return richardson_iteration(A, b, 1., tol=tol, x0=x0, B=B)\n",
+ " else:\n",
+ " return None"
+ ],
+ "execution_count": 0,
+ "outputs": []
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "Mjl7lz1HuvKS",
+ "colab_type": "text"
+ },
+ "source": [
+ "### Newtons method"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "metadata": {
+ "id": "zY4BPEMku4XB",
+ "colab_type": "code",
+ "colab": {}
+ },
+ "source": [
+ "def get_derivative(f, x: np.array, dx_vec: np.array):\n",
+ " return (f(x + dx_vec) - f(x - dx_vec)) / (2 * np.linalg.norm(dx_vec))\n",
+ "\n",
+ "def jacobian(f, x0, dx: float):\n",
+ " n = x0.size\n",
+ " Df = np.zeros((n,n))\n",
+ " for i in range(0,n):\n",
+ " Df[:, i] = get_derivative(f, x0, dx * standard_basis(n, i))\n",
+ " return Df\n",
+ "\n",
+ "def newtons_method(f, x0, dx: float, tol=1e-6, max_itr=1e3):\n",
+ " x = x0\n",
+ " i = 0\n",
+ " while np.linalg.norm(f(x)) >= tol:\n",
+ " if i >= max_itr:\n",
+ " print(\"Max itr\")\n",
+ " return None\n",
+ " i += 1\n",
+ "\n",
+ " Df = jacobian(f, x0, dx)\n",
+ " if np.allclose(np.linalg.det(Df), 0, atol=1e-9):\n",
+ " print(\"Singular jacobian\")\n",
+ " return None\n",
+ " \n",
+ " x[:] = x - np.linalg.solve(Df, f(x))\n",
+ "\n",
+ " return x, i\n",
+ "\n",
+ "def scalar_newton(f, x0, dx: float, tol=1e-6, max_itr=1e4):\n",
+ " if type(x0) != np.ndarray:\n",
+ " x0 = np.array([x0])\n",
+ " \n",
+ " ans = newtons_method(f, x0, dx, tol=tol, max_itr=max_itr)\n",
+ " if ans is None:\n",
+ " return None, 0\n",
+ " else: \n",
+ " return ans[0][0], ans[1]\n"
+ ],
+ "execution_count": 0,
+ "outputs": []
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "fB2CO5yPRj4s"
+ },
+ "source": [
+ "### Arnoldi iteration\n",
+ "\n",
+ "I used the algorithm in the lecturenotes with slight modifications. Having problems with the algorithm dividing by zero I (with slight inpiration from Wikipedia, heh.) added a test `H[j + 1, j] > 1e-12` to ensure that no `nan` values occur."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "metadata": {
+ "colab_type": "code",
+ "id": "mDJtku63Rj4t",
+ "colab": {}
+ },
+ "source": [
+ "def arnoldi_iteration(A, b, k: int):\n",
+ " if type(A) != np.ndarray or A.ndim != 2:\n",
+ " raise Exception(\"A matrix format not recogniced.\")\n",
+ " if type(b) != np.ndarray or b.ndim != 1:\n",
+ " raise Exception(\"b vector format not recogniced.\")\n",
+ " if A.shape[0] != b.size:\n",
+ " raise Exception(\"Shapes of A and b does not agree.\")\n",
+ " \n",
+ " H = np.zeros((k + 1, k))\n",
+ " Q = np.zeros((A.shape[0], k + 1))\n",
+ " Q[:, 0] = b / np.linalg.norm(b)\n",
+ " \n",
+ " for j in range(k):\n",
+ " v = A.dot(Q[:, j])\n",
+ " for i in range(j + 1):\n",
+ " H[i, j] = np.dot(Q[:, i].conj(), v)\n",
+ " v = v - H[i, j] * Q[:, i]\n",
+ "\n",
+ " H[j + 1, j] = np.linalg.norm(v)\n",
+ " if H[j + 1, j] > 1e-12:\n",
+ " Q[:, j + 1] = v / H[j + 1, j]\n",
+ " else:\n",
+ " break\n",
+ " return Q, H"
+ ],
+ "execution_count": 0,
+ "outputs": []
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "kf274YzBRj48"
+ },
+ "source": [
+ "### GMRES algorithm\n",
+ "\n",
+ "Since we already written a least squares solver in a previous lab I use Numpy's `numpy.linalg.lstsq` method. To the algorithm in the lecture notes I've also added a maximum number of itterations."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "metadata": {
+ "colab_type": "code",
+ "id": "ARnPi-VMRj49",
+ "colab": {}
+ },
+ "source": [
+ "def gmres(A, b, max_itr=None, tol=1e-6):\n",
+ " if type(A) != np.ndarray or A.ndim != 2:\n",
+ " raise Exception(\"A matrix format not recogniced.\")\n",
+ " if type(b) != np.ndarray or b.ndim != 1:\n",
+ " raise Exception(\"b vector format not recogniced.\")\n",
+ " if A.shape[0] != b.size:\n",
+ " raise Exception(\"Shapes of A and b does not agree.\")\n",
+ " \n",
+ " norm_b = np.linalg.norm(b)\n",
+ " \n",
+ " Q = np.zeros((b.size, 1))\n",
+ " Q[:, 0] = b[:]/norm_b\n",
+ " \n",
+ " y = None\n",
+ " r = tol * norm_b\n",
+ " \n",
+ " k = 0\n",
+ " while np.linalg.norm(r) >= tol * norm_b:\n",
+ " Q, H = arnoldi_iteration(A, b, k)\n",
+ " y = np.linalg.lstsq(H, norm_b * standard_basis(k+1, 0), rcond=None)[0]\n",
+ " r = H.dot(y)\n",
+ " r[:] = norm_b * standard_basis(k+1, 0) - r[:]\n",
+ " k += 1\n",
+ " if not(max_itr is None) and k >= max_itr:\n",
+ " break\n",
+ " \n",
+ " x = Q[:, 0:k-1].dot(y)\n",
+ " return x, k"
+ ],
+ "execution_count": 0,
+ "outputs": []
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "SxtzgZLtRj5A"
+ },
+ "source": [
+ "# Testing"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "h__3J5w4L7CT",
+ "colab_type": "text"
+ },
+ "source": [
+ "## Iteration algorithms\n",
+ "\n",
+ "The testing of accuracy of the iteration solvers are very alike. Therefore I defined a `test_iteration_solver` method. It generates random matrix $A$ of size $\\text{max_size} \\times \\text{max_size}$ and a random vector $x$ of size $\\text{max_size}$ and then creates $b = Ax$. It then checks $||x_{est} - x|| \\approx 0$ and $||Ax - b|| \\approx 0$ down to `decimal` decimals. The process is repeated `num_of_tests` times."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "metadata": {
+ "colab_type": "code",
+ "id": "-B5mIAyKRj5E",
+ "colab": {}
+ },
+ "source": [
+ "def test_iteration_solver(solver, decimal=4, num_of_tests=1000, max_size=10, alpha=None):\n",
+ " i = 0\n",
+ " tol = 1e-6\n",
+ "\n",
+ " while i < num_of_tests:\n",
+ " n = np.random.randint(1, max_size)\n",
+ " A = 1000 * np.random.rand(n, n)\n",
+ " x_true = np.random.rand(n)\n",
+ " b = A.dot(x_true)\n",
+ " \n",
+ " if np.allclose(np.linalg.det(A), 0, 1e-9):\n",
+ " continue\n",
+ "\n",
+ " ans = None\n",
+ " if solver == richardson_iteration:\n",
+ " ans = solver(A, b, alpha, tol=tol)\n",
+ " else:\n",
+ " ans = solver(A, b, tol=tol)\n",
+ " if ans is None:\n",
+ " continue\n",
+ " i += 1\n",
+ "\n",
+ " x = ans[0]\n",
+ " \n",
+ " np.testing.assert_allclose(\n",
+ " np.linalg.norm(A.dot(x) - b),\n",
+ " 0,\n",
+ " atol=10 * tol)\n",
+ "\n",
+ "class TestIterationSolvers(unittest.TestCase):\n",
+ " def test_jacobi(self):\n",
+ " test_iteration_solver(jacobi_iteration)\n",
+ " \n",
+ " def test_gauss_seidel(self):\n",
+ " test_iteration_solver(gauss_seidel_iteration)\n",
+ "\n",
+ " def test_gmres(self):\n",
+ " test_iteration_solver(gmres)"
+ ],
+ "execution_count": 0,
+ "outputs": []
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "01tRYtQDI1gA",
+ "colab_type": "text"
+ },
+ "source": [
+ "## Newtons method\n",
+ "For the scalar Newtons method I generate polynomials with roots spaced along the $x$-axis. I use Newtons method to find these polynomials to find the zeros of the function and then check for accuracy. I checked that $|f(x)|\\approx 0$ and that $|x-r|\\approx 0$ for some root $r$. I calculated the tolerance of $|x-r|$ by using the derivative at the calculated root and using a linear approximation. Then the tolerance in the $x$-axis is given by the contition that\n",
+ "$$|x - r| < \\frac{\\text{TOL}}{f'(x)}$$\n",
+ "where $\\text{TOL}$ is the tolerance in the $y$-axis.\n",
+ "\n",
+ "I tested for a $1000$ random polynomials in a predefined (convenient) subspace of polynomials ($\\text{deg} < 10$, $r < \\text{10000}$, $0.1 < |r_n - r_m| < 100$)."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "metadata": {
+ "id": "qbDknwBh-6jy",
+ "colab_type": "code",
+ "colab": {}
+ },
+ "source": [
+ "def get_rand_polynomial(deg: int, root_max_abs: float, minimal_root_dist: float):\n",
+ " if 2 * root_max_abs < (deg - 1) * minimal_root_dist:\n",
+ " raise Exception(\"Intervall error\")\n",
+ "\n",
+ " roots = []\n",
+ " low = -root_max_abs\n",
+ " high = root_max_abs - (deg - 1) * minimal_root_dist\n",
+ " for i in range(deg):\n",
+ " roots.append(random.uniform(low, high))\n",
+ " low = roots[i] + minimal_root_dist\n",
+ " high += minimal_root_dist\n",
+ "\n",
+ " def f(x):\n",
+ " y = 1\n",
+ " for root in roots:\n",
+ " y *= (x - root)\n",
+ " return y\n",
+ "\n",
+ " return f, roots\n",
+ "\n",
+ "class TestNewtonScalar(unittest.TestCase):\n",
+ " def test_rand_polynomial(self):\n",
+ " max_deg = 2\n",
+ " max_dist = 100\n",
+ " max_root_max = 10000\n",
+ " tol = 1e-6\n",
+ "\n",
+ " for i in range(1000):\n",
+ " root_dist = random.uniform(0.1, max_dist)\n",
+ " deg = random.randint(1, max_deg)\n",
+ " root_max = random.uniform((deg - 1) * root_dist, max_root_max)\n",
+ " \n",
+ " f, roots = get_rand_polynomial(deg, root_max, root_dist)\n",
+ "\n",
+ " x0 = random.uniform(-root_max, root_max)\n",
+ " root = scalar_newton(f, x0, tol)[0]\n",
+ "\n",
+ " np.testing.assert_allclose(f(root), 0, atol=tol)\n",
+ " is_close = False\n",
+ " dfdx = get_derivative(f, np.array([root]), np.array([1e-6]))[0]\n",
+ " for r in roots:\n",
+ " diff = r - root\n",
+ " if np.allclose(diff, 0, atol=tol/abs(dfdx)):\n",
+ " is_close = True\n",
+ " break\n",
+ "\n",
+ " assert is_close\n"
+ ],
+ "execution_count": 0,
+ "outputs": []
+ },
+ {
+ "cell_type": "code",
+ "metadata": {
+ "id": "Hh7QQSSLL0eW",
+ "colab_type": "code",
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 102
+ },
+ "outputId": "f1926116-8a77-46b6-c9af-551edb714f32"
+ },
+ "source": [
+ "if __name__ == '__main__':\n",
+ " unittest.main(argv=['first-arg-is-ignored'], exit=False)"
+ ],
+ "execution_count": 17,
+ "outputs": [
+ {
+ "output_type": "stream",
+ "text": [
+ "....\n",
+ "----------------------------------------------------------------------\n",
+ "Ran 4 tests in 9.055s\n",
+ "\n",
+ "OK\n"
+ ],
+ "name": "stderr"
+ }
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "SsQLT38gVbn_"
+ },
+ "source": [
+ "# Results"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "8pvacOInMQdP",
+ "colab_type": "text"
+ },
+ "source": [
+ "Above we can se that the iterative solvers all solve systems of linear equations. The toleranse-levels are generally accomplished, but not allways (strange...).\n",
+ "\n",
+ "The method for scalar Newton succeed in all polynomials with zeros not to close together. It can not allways be guaraanteed to succeed since you can create \"deadlocks\", but that is more common in symetric equations as $cos(x)$."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "colab_type": "text",
+ "id": "_4GLBv0zWr7m"
+ },
+ "source": [
+ "\n",
+ "# **Discussion**"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "AbqFg1rSPC7V",
+ "colab_type": "text"
+ },
+ "source": [
+ "Having more time for the lab it would have been interesting to investigate both the number of iterations taken by the different methods, but allso the absolute time. The GMRES has a very different method to the Jacobi and Gauss-Seidel methods, so even thou the number of itterations are fewer the time complexity or absolute time might be very different."
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Lab-4/ejemyr_lab4.ipynb b/Lab-4/ejemyr_lab4.ipynb
new file mode 100644
index 0000000..745ddca
--- /dev/null
+++ b/Lab-4/ejemyr_lab4.ipynb
@@ -0,0 +1,698 @@
+{
+ "nbformat": 4,
+ "nbformat_minor": 0,
+ "metadata": {
+ "colab": {
+ "name": "ejemyr_lab4.ipynb",
+ "provenance": [],
+ "collapsed_sections": [],
+ "toc_visible": true,
+ "machine_shape": "hm",
+ "include_colab_link": true
+ },
+ "kernelspec": {
+ "name": "python3",
+ "display_name": "Python 3"
+ }
+ },
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "view-in-github",
+ "colab_type": "text"
+ },
+ "source": [
+ ""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "6RgtXlfYO_i7",
+ "colab_type": "text"
+ },
+ "source": [
+ "# **Lab 4: Function approximation**\n",
+ "**Christoffer Ejemyr**"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "9x_J5FVuPzbm",
+ "colab_type": "text"
+ },
+ "source": [
+ "# **Abstract**"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "4puuvkvu-nOL",
+ "colab_type": "text"
+ },
+ "source": [
+ "In this lab we estimate functions i one and two dimentions. Generally it was found that an arbitrarily good approximation can be made, at the expense of computational work and memory load."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "OkT8J7uOWpT3",
+ "colab_type": "text"
+ },
+ "source": [
+ "#**About the code**"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "HmB2noTr1Oyo",
+ "colab_type": "text"
+ },
+ "source": [
+ "A short statement on who is the author of the file, and if the code is distributed under a certain license. "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "metadata": {
+ "id": "Pdll1Xc9WP0e",
+ "colab_type": "code",
+ "outputId": "126a8872-c9fb-4b2b-b16b-c977f466d23e",
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 34
+ }
+ },
+ "source": [
+ "\"\"\"This program is a template for lab reports in the course\"\"\"\n",
+ "\"\"\"DD2363 Methods in Scientific Computing, \"\"\"\n",
+ "\"\"\"KTH Royal Institute of Technology, Stockholm, Sweden.\"\"\"\n",
+ "\n",
+ "# Copyright (C) 2019 Christoffer Ejemyr (ejemyr@kth.se)\n",
+ "# Written in colaboration with Leo Enge (leoe@kth.se)\n",
+ "\n",
+ "# This file is part of the course DD2363 Methods in Scientific Computing\n",
+ "# KTH Royal Institute of Technology, Stockholm, Sweden\n",
+ "#\n",
+ "# This is free software: you can redistribute it and/or modify\n",
+ "# it under the terms of the GNU Lesser General Public License as published by\n",
+ "# the Free Software Foundation, either version 3 of the License, or\n",
+ "# (at your option) any later version."
+ ],
+ "execution_count": 0,
+ "outputs": [
+ {
+ "output_type": "execute_result",
+ "data": {
+ "text/plain": [
+ "'KTH Royal Institute of Technology, Stockholm, Sweden.'"
+ ]
+ },
+ "metadata": {
+ "tags": []
+ },
+ "execution_count": 3
+ }
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "28xLGz8JX3Hh",
+ "colab_type": "text"
+ },
+ "source": [
+ "# **Set up environment**"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "D2PYNusD08Wa",
+ "colab_type": "text"
+ },
+ "source": [
+ "To have access to the neccessary modules you have to run this cell. If you need additional modules, this is where you add them. "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "metadata": {
+ "id": "Xw7VlErAX7NS",
+ "colab_type": "code",
+ "colab": {}
+ },
+ "source": [
+ "# Load neccessary modules.\n",
+ "from google.colab import files\n",
+ "\n",
+ "import time\n",
+ "import numpy as np\n",
+ "\n",
+ "from matplotlib import pyplot as plt\n",
+ "from matplotlib import tri\n",
+ "from matplotlib import axes\n",
+ "from mpl_toolkits.mplot3d import Axes3D\n",
+ "from scipy.integrate import quad, dblquad"
+ ],
+ "execution_count": 0,
+ "outputs": []
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "gnO3lhAigLev",
+ "colab_type": "text"
+ },
+ "source": [
+ "# **Introduction**"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "0DlTQl-x6dfJ",
+ "colab_type": "text"
+ },
+ "source": [
+ "In this lab we will investigate how to approximate functions in a 1d- and 2d-space. This will be done by finding the function $f_{\\pi} \\in P(I)$ such that $\\| f - f_{\\pi} \\|_{L^2}$ is minimized."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "WeFO9QMeUOAu",
+ "colab_type": "text"
+ },
+ "source": [
+ "# **Methods**"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "id": "1WQUJfPg_drt",
+ "colab_type": "text"
+ },
+ "source": [
+ "## Approximation in 1D\n",
+ "We use the algorithm in the lecture notes.\n",
+ "\n",
+ "Using a global basis consisting of hat functions $\\phi_i$ with peak at mesh-point $x_i$ we calculate the values of a matrix $A$ and a vector $b$ as\n",
+ "\n",
+ "$$A_{ij} = (\\phi_i, \\phi_j) \\\\ b_i = (f, \\phi_i)$$\n",
+ "\n",
+ "where $f$ is the function to approximate.\n",
+ "\n",
+ "Using the hat funcitons we have that \n",
+ "\n",
+ "\\begin{align}\n",
+ "(\\phi_i, \\phi_i) &= \\frac{x_i - x_{i-1}}{3} + \\frac{x_{i+1} - x_{i}}{3},\\\\\n",
+ "(\\phi_i, \\phi_{i+1}) &= \\frac{x_{i+1} - x_{i}}{3}, \\\\\n",
+ "(\\phi_i, \\phi_j) &= 0 \\quad \\text{else}.\n",
+ "\\end{align}\n",
+ "\n",
+ "The value of $b_i$ is calculated by integrating using the `scipy.integrate.quad` method. you only nead to integrate on the interval where $\\phi_i \\neq 0$ (the interval $(x_{i-1}, x_{i+1})$).\n",
+ "\n",
+ "By using clever indexing and itteration order, as well as thanks to the linearity of integrals, we can divide the integrals and continiusly add the contribution of each interval on all the different values in $A$ and $b$ as we traverse them.\n",
+ "\n",
+ "When $A$ and $b$ are filled with values we solve the linear system $A\\alpha = b$. $\\alpha$ will then contain the approximated values of each point on the grid. $\\alpha_i \\approx f(x_i)$.\n",
+ "\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "metadata": {
+ "id": "QBtX4Q4mKY_Y",
+ "colab_type": "code",
+ "colab": {}
+ },
+ "source": [
+ "def lagrange_poly_1D(x : float, i : int, xk0 : float, xk1 : float, hk : float):\n",
+ " if i == 0:\n",
+ " return (xk1 - x) / hk\n",
+ " elif i == 1:\n",
+ " return (x - xk0) / hk\n",
+ " else:\n",
+ " raise Exception(\"Index error.\")\n",
+ "\n",
+ "\n",
+ "def L2_projection_1D(f, mesh):\n",
+ " h = np.diff(mesh)\n",
+ " b = np.zeros(len(mesh))\n",
+ " A = np.zeros((len(mesh), len(mesh)))\n",
+ " for k in range(len(mesh) - 1):\n",
+ " for i in range(2):\n",
+ " b[k + i] += quad(lambda x : f(x) * lagrange_poly_1D(x, i, mesh[k], mesh[k + 1], h[k]), mesh[k], mesh[k + 1])[0]\n",
+ " for j in range(2):\n",
+ " if i == j:\n",
+ " A[k + i, k + j] += h[k] / 3.\n",
+ " else:\n",
+ " A[k + i, k + j] += h[k] / 6.\n",
+ "\n",
+ " return np.linalg.solve(A, b)"
+ ],
+ "execution_count": 0,
+ "outputs": []
+ },
+ {
+ "cell_type": "code",
+ "metadata": {
+ "id": "4DosyShHTsMy",
+ "colab_type": "code",
+ "outputId": "88620613-5d8c-49db-a70c-92506007aa6d",
+ "colab": {
+ "base_uri": "https://localhost:8080/",
+ "height": 282
+ }
+ },
+ "source": [
+ "f = lambda x : np.sin(x)**2\n",
+ "\n",
+ "x = np.linspace(-5, 5, 1000)\n",
+ "mesh = np.linspace(-5, 5, 20)\n",
+ "\n",
+ "alpha = L2_projection_1D(f, mesh)\n",
+ "\n",
+ "plt.plot(x, f(x))\n",
+ "plt.plot(mesh, alpha)"
+ ],
+ "execution_count": 0,
+ "outputs": [
+ {
+ "output_type": "execute_result",
+ "data": {
+ "text/plain": [
+ "[]"
+ ]
+ },
+ "metadata": {
+ "tags": []
+ },
+ "execution_count": 20
+ },
+ {
+ "output_type": "display_data",
+ "data": {
+ "image/png": "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\nywAAAABJRU5ErkJggg==\n",
+ "text/plain": [
+ "