numeric-linalg
Educational material on the SciPy implementation of numerical linear algebra algorithms
File Name | Size | Mode |
linear-solvers.ipynb | 7388B | -rw-r--r-- |
1 { 2 "cells": [ 3 { 4 "cell_type": "markdown", 5 "id": "e1791697-e733-4721-9e10-fcdcfbda9064", 6 "metadata": {}, 7 "source": [ 8 "# Solving Linear systems in SciPy\n", 9 "\n", 10 "A linear system is a system of equations of the form\n", 11 "$$ \\left\\{ \\begin{aligned} a_{1 1} x_1 + a_{1 2} x_2 + \\cdots + a_{1 n} x_n &= b_1 \\\\ a_{2 1} x_1 + a_{2 2} x_2 + \\cdots + a_{2 n} x_n &= b_2 \\\\ & \\vdots \\\\ a_{n 1} x_1 + a_{n 2} x_2 + \\cdots + a_{n n} x_n &= b_n\\end{aligned} \\right.$$\n", 12 "on the variables $x_1, \\ldots, x_n$.\n", 13 "\n", 14 "Solving this system is equivalent to solving the equation $A x = b$ on $x$ where $A = (a_{ij})_{ij}$ is a $n\\times n$ matrix and $x = (x_1, \\ldots, x_n) \\; \\& \\; b = (b_1, \\ldots, b_n)$ are vectors, which can always be done provided $A$ is invertible." 15 ] 16 }, 17 { 18 "cell_type": "markdown", 19 "id": "26bc6a30-7853-4c78-adfb-320f0a65dd10", 20 "metadata": {}, 21 "source": [ 22 "In SciPy, we can solve linear systems using the `la.solve` function." 23 ] 24 }, 25 { 26 "cell_type": "code", 27 "execution_count": 1, 28 "id": "b1ced47f-6783-48ed-a4e4-4f1f4e2b8835", 29 "metadata": {}, 30 "outputs": [ 31 { 32 "data": { 33 "text/plain": [ 34 "array([[-0.12672176],\n", 35 " [ 0.1046832 ],\n", 36 " [ 1.19008264]])" 37 ] 38 }, 39 "execution_count": 1, 40 "metadata": {}, 41 "output_type": "execute_result" 42 } 43 ], 44 "source": [ 45 "import numpy as np\n", 46 "import scipy.linalg as la\n", 47 "\n", 48 "A, b = np.array([[6,15,1],[8,7,12],[2,7,8]]), np.array([[2], [14], [10]])\n", 49 "la.solve(A, b)" 50 ] 51 }, 52 { 53 "cell_type": "markdown", 54 "id": "bfdacdbf-150f-4b4d-8072-ee18758d3b60", 55 "metadata": {}, 56 "source": [ 57 "## But how does `la.solve` work???\n", 58 "\n", 59 "Internally, the `la.solve` function uses the the [LAPACK library](https://netlib.org/lapack), a Fortran package for numerical linear algebra. The LAPACK generic linear solver algorithm goes something like the following:\n", 60 "\n", 61 "1. Decompose $A$ as $A = PL U$, where $P$ is permutation matrix, $L$ is a lower triangular matrix with $1$ in the diagonal and $U$ is an upper triangular matrix.\n", 62 "3. Solve $P b' = b$ for $b'$, i.e. compute $b' = P^{-1} b$. Since $P$ is a permutation matrix, this operation is $O(n)$.\n", 63 "4. Solve $L b'' = b'$ for $b''$, i.e. compute $b'' = L^{-1} P^{-1} b$. Since $L$ is known to be lower triangular, this operation is $O(n^2)$.\n", 64 "3. Solve $U x = b''$ for $x$, i.e. compute $x = U^{-1} L^{-1} P^{-1} b = A^{-1} b$. Since $U$ is known to be upper triangular, this operation is $O(n^2)$.\n", 65 "\n", 66 "This is implemented in the `GETRS` family of subroutines." 67 ] 68 }, 69 { 70 "cell_type": "markdown", 71 "id": "5b7ad45f-ea81-4d46-9097-735cc159cf1a", 72 "metadata": {}, 73 "source": [ 74 "As for the decomposition of $A$ in the first step, LAPACK uses a method called [_partial pivoting_](https://en.wikipedia.org/wiki/LU_decomposition#LU_factorization_with_partial_pivoting). A simple simple recurssive algorithm using such method might look something like the following:\n", 75 "\n", 76 "1. If $A = a_{11}$ is $1 \\times 1$ then take $P = L = 1$ and $U = a_{11}$.\n", 77 "2. If $A$ is $n \\times n$ for $n > 1$, choose $i_0$ that maximizes $|a_{i_0, 1}|$ and consider the $n \\times n$ permutation matrix $S_{i_0}$ that swaps the first and $i_0$-th basis vectors. Searching for $i_0$ is an $O(n)$ operation.\n", 78 "3. Write\n", 79 " $$S_{i_0} A = \\left( \\begin{array}{c|c} a_{i_0} & A_{12}' \\\\ \\hline A_{21}' & A_{22}' \\end{array} \\right), $$\n", 80 " where $A_{22}'$ is $(n - 1) \\times (n - 1)$ and $a_{i_0} \\ne 0$ — given $A$ is invertible. Since $S_{i_0}$ acts on $A$ by swaping the first and $i_0$-th rows, computing $S_{i_0} A$ is an $O(n)$ operation.\n", 81 "4. We want to solve the equation\n", 82 " $$S_{i_0} A = \\left( \\begin{array}{c|c} 1 & 0 \\\\ \\hline 0 & P_{22} \\end{array} \\right) \\left( \\begin{array}{c|c} 1 & 0 \\\\ \\hline L_{21} & L_{22} \\end{array} \\right) \\cdot \\left( \\begin{array}{c|c} u_{11} & U_{12} \\\\ \\hline 0 & U_{22} \\end{array} \\right),$$\n", 83 " where $P_{22}$ is a permutation matrix, $L_{22}$ is lower triangular with $1$ in the diagonal entries and $U_{22}$ is upper triangular. In other words, we want to solve the equations\n", 84 " $$\n", 85 " \\begin{aligned}\n", 86 " a_{i_0} &= u_{11} & A_{12}' &= U_{12} \\\\\n", 87 " A_{21}' &= u_{11} P_{22} L_{21} & A_{22}' &= P_{22} L_{21} U_{12} + P_{22} L_{22} U_{22}.\n", 88 " \\end{aligned}\n", 89 " $$\n", 90 " We must take $u_{11} = a_{i_0}$, $U_{12} = A_{12}'$ and $L_{21} = a_{i_0}^{-1} P_{22}^{-1} A_{12}'$, so it remains to solve the bottom-right equation.\n", 91 "5. Write $(A_{22}' - a_{i_0}^{-1} A_{21}' A_{12}') = P_{22} L_{22} U_{22}$, where $P_{22}$ is a permutation matrix, $L_{22}$ is lower triangular with $1$ in the diagonals and $U_{22}$ is upper triangular. Computing $A_{21}' A_{12}'$ (and thus $A_{22}' - a_{i_0}^{-1} A_{21}' A_{12}'$) is, of course, a $O(n^3)$ operation. Now since $P_{22}$ is a permutation matrix, computing $L_{21} = a_{i_0}^{-1} P_{22}^{-1} A_{12}$ is an $O(n^2)$ operation.\n", 92 "7. Take\n", 93 " $$\n", 94 " \\begin{aligned}\n", 95 " L &= \\begin{pmatrix} 1 & 0 \\\\ L_{21} & L_{22} \\end{pmatrix} &\n", 96 " U &= \\begin{pmatrix} u_{11} & U_{12} \\\\ 0 & U_{22} \\end{pmatrix}\n", 97 " \\end{aligned}\n", 98 " $$\n", 99 " for $L_{21}, L_{22}, u_{11}, U_{12}, U_{22}$ as above, so that\n", 100 " $$\n", 101 " S_{i_0} A = \\begin{pmatrix} 1 & 0 \\\\ 0 & P_{22} \\end{pmatrix} L U.\n", 102 " $$\n", 103 "8. Hence by taking\n", 104 " $$\n", 105 " P = S_{i_0} \\begin{pmatrix} 1 & 0 \\\\ 0 & P_{22} \\end{pmatrix}\n", 106 " $$\n", 107 " we get $A = P L U$ as desired!\n", 108 "\n", 109 "In total, this algorithm takes $n$ recursive steps to solve $A = P L U$. Since each step involves $O(n^3)$ operations, the total complexity of our facotization algorithm is $O(n^4)$." 110 ] 111 }, 112 { 113 "cell_type": "markdown", 114 "id": "7c6678f2-9ae6-4daa-922c-828771c1a796", 115 "metadata": {}, 116 "source": [ 117 "The actual LAPACK algorithm for factorizing $A$ is a slight variation of this concept, where we instead take the decomposition\n", 118 "$$\n", 119 "A =\n", 120 "\\left(\n", 121 "\\begin{array}{c|c}\n", 122 " A_{11} & A_{12} \\\\ \\hline\n", 123 " A_{21} & A_{22}\n", 124 "\\end{array}\n", 125 "\\right)\n", 126 "$$\n", 127 "with $A_{11}$ is a $\\left\\lfloor \\frac{\\min \\{m, n\\}}{2} \\right\\rfloor \\times \\left\\lfloor \\frac{\\min \\{m, n\\}}{2} \\right\\rfloor$ matrix. This is implemented in the `GETRF` family of subroutines." 128 ] 129 }, 130 { 131 "cell_type": "code", 132 "execution_count": null, 133 "id": "229ed6e3-493e-49c3-835a-0b8582aa6586", 134 "metadata": {}, 135 "outputs": [], 136 "source": [] 137 } 138 ], 139 "metadata": { 140 "kernelspec": { 141 "display_name": "Python 3 (ipykernel)", 142 "language": "python", 143 "name": "python3" 144 }, 145 "language_info": { 146 "codemirror_mode": { 147 "name": "ipython", 148 "version": 3 149 }, 150 "file_extension": ".py", 151 "mimetype": "text/x-python", 152 "name": "python", 153 "nbconvert_exporter": "python", 154 "pygments_lexer": "ipython3", 155 "version": "3.12.7" 156 } 157 }, 158 "nbformat": 4, 159 "nbformat_minor": 5 160 }