1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
|
{
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"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.10.12"
}
},
"nbformat": 4,
"nbformat_minor": 4,
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"import numpy as np\n",
"import numpy.linalg as lin\n",
"\n",
"np.set_printoptions(precision=3, linewidth=150, suppress=True)\n",
"np.random.seed(123)"
]
},
{
"cell_type": "markdown",
"metadata": {
"lang": "en"
},
"source": [
"## Numerical simulation\n",
"\n",
"If the price of bananas is important, the sector with the greatest system resolution needs\n",
"matrices is numerical simulation.\n",
"This includes <a href=\"https://www.nas.nasa.gov/SC18/demos/demo10.html\">this</a>\n",
"\n",
"<center><img alt=\"simulation du X-57 par la Nasa\" src=\"images/nasa-x57-pression.jpg\"/></center>\n",
"\n",
"and everything that is digitally simulated, namely everything related to transport, energy, construction, everything\n",
"what we manufacture, which is expensive and which must have a very specific physical behavior. But that's not all,\n",
"understanding our environment (weather, global warming, chemistry, etc.) also means solving\n",
"matrix systems as well as other areas. However, there are numerical simulation methods\n",
"that do not go through matrix systems.\n",
"\n",
"To make the image above, we transform physical equations like [Navier-Stokes](https://fr.wikipedia.org/wiki/%C3%89quations_de_Navier-Stokes) into a matrix system where the unknowns are defined at each point\n",
"of a mesh to be defined. In our case the unknown is the pressure and the mesh is the interior of a\n",
"imaginary box that includes the plane and the air that circulates around it.\n",
"\n",
"If the box is a cube with 1000 points in each direction, we have 1 billion points in the box (minus\n",
"what is inside the plane but let's stay on 1 billion). Then the matrix has 1 quadrillion elements (one billion squared).\n",
"\n",
"$$\n",
"\\begin{bmatrix}\n",
"a_{11} \\; a_{12} \\ldots a_{1,10^9} \\\\\n",
"a_{21} \\; a_{22} \\ldots a_{2,10^9} \\\\\n",
" \\vdots \\\\\n",
"a_{10^9,1} a_{n2} \\ldots a_{10^9,10^9} \\\\\n",
"\\end{bmatrix}\n",
"\\;\n",
"\\begin{bmatrix}\n",
"p_{1} \\\\\n",
"p_{2} \\\\\n",
"\\vdots \\\\\n",
"p_{10^9} \\\\\n",
"\\end{bmatrix}\n",
"=\n",
"\\begin{bmatrix}\n",
"f_{1} \\\\\n",
"f_{2} \\\\\n",
"\\vdots \\\\\n",
"f_{10^9} \\\\\n",
"\\end{bmatrix}\n",
"$$\n",
"\n",
"Inverting a matrix is done in $O(n³)$ operations or $O(10^{27})$ in our case.\n",
"\n",
"If you have [the most powerful computer in the world](https://www.top500.org/) in 2024\n",
"you can do 1 Eflops or $10^{18}$ operations per second. Also you will need $10^{9}$ seconds or a little over 30 years. It's too much.\n",
"\n",
"**Inverting a matrix or solving by a direct method is not the right solution to solve a large matrix system.**\n",
"\n",
"#### Exercise 12.1\n",
"For such a simulation it is also necessary to calculate the speed of the air in each of the 1 billion points of our grid. A speed is 3 variables that must be added to the pressure. How long does it take to reverse\n",
"the matrix which also integrates the speed of the air?"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"# ceci est une calculatrice, vous pouvez donc écrire la réponse ici\n"
]
},
{
"cell_type": "markdown",
"metadata": {
"lang": "en"
},
"source": [
"It is a good idea to estimate the time of a large calculation before launching it.\n",
"\n",
"Besides, with such long times, he\n",
"should not stay with orders of magnitude but specify with the exact formula. So with Choleski it's $n^3/3$\n",
"operations therefore it runs 3 times faster, but it is still too much."
]
},
{
"cell_type": "markdown",
"metadata": {
"lang": "en"
},
"source": [
"## Iterative Methods\n",
"\n",
"Iterative methods are methods that approach the desired solution step by step. They allow\n",
"to find an approximation of ${\\bf x}$, the solution of $A\\, {\\bf x} = b$.\n",
"\n",
"In general we\n",
"stops the calculation when we estimate that we are at a chosen distance from the solution (which we call the error) rather\n",
"than waiting to be at the exact solution.\n",
"Anyway the exact solution on a computer which is limited in number of decimal places can\n",
"be unattainable. So **we will never seek to have an error smaller than our maximum precision**.\n",
"\n",
"The fundamental idea of the iterative algorithm is to have a formula like $\\; {\\bf x}^{t+1} = B \\, {\\bf x}^t + {\\bf c}\\;$ or in Python:\n",
"\n",
"```\n",
"x = np.random(size = c.size)\n",
"while np.square(x - old_x) > threshold:\n",
" old_x = x\n",
" x = B @ x + c\n",
"```\n",
"\n",
"The magic is if **x** converges. In this case we have reached a fixed point i.e. that ${\\bf x}^{t+1} = {\\bf x}^t$\n",
"and so\n",
"\n",
"$${\\bf x}^t = B \\, {\\bf x}^t + {\\bf c} \\quad \\textrm{c.a.d.} \\quad (Id -B) \\, {\\bf x}^t = {\\bf c}$$\n",
"\n",
"We find our $A \\; {\\bf x} = {\\bf b}$ that being in practice we do not cut A into Id and B because\n",
"ca does not converge (except particular cases). Let's see how the Jacobi method does it."
]
},
{
"cell_type": "markdown",
"metadata": {
"lang": "en"
},
"source": [
"## Jacobi method\n",
"\n",
"The Jacobi method cuts the matrix A into M and N with\n",
"\n",
"*M the diagonal matrix that includes the elements of the diagonal of A* N = M - A (so 0 on the diagonal and the opposite of the elements of A elsewhere)\n",
"\n",
"So the system to solve is $\\; (M - N) \\, {\\bf x} = {\\bf b}$.\n",
"\n",
"The iterative formula is therefore for iteration k+1 expressed as a function of iteration k:\n",
"\n",
"$$\n",
"M \\; {\\bf x}^{k+1} = N\\; {\\bf x}^k + {\\bf b}\n",
"$$\n",
"\n",
"and since M is a diagonal matrix, we have:\n",
"\n",
"$$\n",
"\\begin{array}{l}\n",
"a_{11} x_1^{k+1} = \\qquad -a_{12} \\, x_2^k - a_{13} \\, x_3^k \\ldots - a_{1n} \\, x_n^k + b_1 \\\\\n",
"a_{22} x_2^{k+1} = -a_{21} \\, x_1^k \\qquad - a_{23} \\, x_3^k \\ldots - a_{2n} \\, x_n^k + b_2 \\\\\n",
"a_{33} x_3^{k+1} = -a_{31} \\, x_1^k - a_{32} \\, x_3^k \\qquad \\ldots - a_{3n} \\, x_n^k + b_3 \\\\\n",
" \\vdots \\\\\n",
"a_{nn} x_n^{k+1} = -a_{n1} \\, x_1^k - a_{n2} \\, x_3^k \\ldots - a_{n-1,n-1} \\, x_{n-1}^k \\qquad + b_n \\\\\n",
"\\end{array}\n",
"$$\n",
"\n",
"We see in watermark $A \\; {\\bf x} = {\\bf b}$.\n",
"\n",
"To calculate $x_1^{k+1}$ it is necessary to divide the right term of the first line by $a_{11}$ it is thus necessary that $a_{11} \\ne 0$.\n",
"\n",
"To calculate the following terms $x_i^{k+1}$ it is also necessary that $a_{ii}$ be non-zero so **it\n",
"A must not have zero on its diagonal**.\n",
"\n",
"This can be found in the following writing, which takes up the initial formula:\n",
"$ {\\bf x}^{k+1} = M^{-1} \\;(N\\; {\\bf x}^k + {\\bf b})$\n",
"\n",
"> We see that in order to be effective, M must be easily invertible, otherwise we might as well invert A directly.\n",
"Here it is indeed the case since M is diagonal."
]
},
{
"cell_type": "markdown",
"metadata": {
"lang": "en"
},
"source": [
"#### Let's program Jacobi"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"A:\n",
" [[2 2 6 1]\n",
" [3 9 6 1]\n",
" [0 1 9 0]\n",
" [0 9 3 4]] \n",
"b:\n",
" [11 19 10 16] \n",
"\n",
"M:\n",
" [[2 0 0 0]\n",
" [0 9 0 0]\n",
" [0 0 9 0]\n",
" [0 0 0 4]]\n",
"N:\n",
" [[ 0 -2 -6 -1]\n",
" [-3 0 -6 -1]\n",
" [ 0 -1 0 0]\n",
" [ 0 -9 -3 0]]\n",
"\n",
"x_0 = [0.398 0.738 0.182 0.175]\n",
"x_1 = [4.127 1.837 1.029 2.203]\n",
"x_2 = [-0.526 -0.195 0.907 -0.906]\n",
"x_3 = [3.427 1.782 1.133 3.759]\n",
"x_4 = [-1.56 -0.204 0.913 -0.86 ]\n",
"x_5 = [3.395 2.118 1.134 3.775]\n",
"x_6 = [-1.907 -0.196 0.876 -1.616]\n",
"x_7 = [3.877 2.342 1.133 3.784]\n",
"x_8 = [-2.133 -0.357 0.851 -2.12 ]\n",
"x_9 = [4.364 2.49 1.151 4.165]\n",
"x_10 = [-2.525 -0.574 0.834 -2.467]\n",
"x_11 = [4.804 2.671 1.175 4.665]\n",
"x_12 = [-3.027 -0.792 0.814 -2.89 ]\n",
"x_13 = [5.293 2.898 1.199 5.17 ]\n",
"x_14 = [-3.581 -1.027 0.789 -3.421]\n",
"x_15 = [5.87 3.159 1.225 5.719]\n",
"x_16 = [-4.194 -1.298 0.76 -4.026]\n",
"x_17 = [6.531 3.45 1.255 6.35 ]\n",
"x_18 = [-4.891 -1.608 0.728 -4.704]\n",
"x_19 = [7.277 3.779 1.29 7.073]\n"
]
}
],
"source": [
"A = np.random.randint(10, size=(4,4))\n",
"b = A.sum(axis=1) # ainsi la solution est [1,1,1,1]\n",
"print('A:\\n', A, \"\\nb:\\n\", b, \"\\n\")\n",
"\n",
"M = np.diag(A) # attention, c'est un vecteur\n",
"N = np.diag(M) - A # np.diag d'une matrice donne un vecteur, np.diag d'un vecteur donne une matrice\n",
"print(f\"M:\\n {np.diag(M)}\\nN:\\n {N}\\n\")\n",
"\n",
"x = np.random.random(4)\n",
"for i in range(20):\n",
" print(f\"x_{i} = {x}\")\n",
" x = (N @ x + b) / M"
]
},
{
"cell_type": "markdown",
"metadata": {
"lang": "en"
},
"source": [
"It doesn't really converge... (if you rerun and see `NaN` it means there is a zero on the diagonal)\n",
"\n",
"2nd try:"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"A:\n",
" [[24 2 4 8]\n",
" [ 0 24 9 3]\n",
" [ 4 6 16 5]\n",
" [ 6 2 1 32]] \n",
"b:\n",
" [38 36 31 41] \n",
"\n",
"M:\n",
" [[24 0 0 0]\n",
" [ 0 24 0 0]\n",
" [ 0 0 16 0]\n",
" [ 0 0 0 32]]\n",
"N:\n",
" [[ 0 -2 -4 -8]\n",
" [ 0 0 -9 -3]\n",
" [-4 -6 0 -5]\n",
" [-6 -2 -1 0]]\n",
"\n",
"x_0 = [0.766 0.777 0.028 0.174]\n",
"x_1 = [1.456 1.468 1.4 1.088]\n",
"x_2 = [0.865 0.839 0.683 0.873]\n",
"x_3 = [1.109 1.135 1.134 1.045]\n",
"x_4 = [0.951 0.944 0.908 0.967]\n",
"x_5 = [1.031 1.039 1.043 1.015]\n",
"x_6 = [0.984 0.982 0.973 0.99 ]\n",
"x_7 = [1.009 1.011 1.014 1.005]\n",
"x_8 = [0.995 0.994 0.992 0.997]\n",
"x_9 = [1.003 1.003 1.004 1.002]\n",
"x_10 = [0.998 0.998 0.998 0.999]\n",
"x_11 = [1.001 1.001 1.001 1. ]\n",
"x_12 = [1. 0.999 0.999 1. ]\n",
"x_13 = [1. 1. 1. 1.]\n",
"x_14 = [1. 1. 1. 1.]\n",
"x_15 = [1. 1. 1. 1.]\n",
"x_16 = [1. 1. 1. 1.]\n",
"x_17 = [1. 1. 1. 1.]\n",
"x_18 = [1. 1. 1. 1.]\n",
"x_19 = [1. 1. 1. 1.]\n"
]
}
],
"source": [
"A = np.random.randint(10, size=(4,4))\n",
"A = A + np.diag(A.sum(axis=0))\n",
"b = A.sum(axis=1) # ainsi la solution est [1,1,1,1]\n",
"print('A:\\n', A, \"\\nb:\\n\", b, \"\\n\")\n",
"\n",
"\n",
"M = np.diag(A) # attention, c'est un vecteur\n",
"N = np.diag(M) - A # np.diag d'une matrice donne un vecteur, np.diag d'un vecteur donne une matrice\n",
"print(f\"M:\\n {np.diag(M)}\\nN:\\n {N}\\n\")\n",
"\n",
"x = np.random.random(4)\n",
"for i in range(20):\n",
" print(f\"x_{i} = {x}\")\n",
" x = (N @ x + b) / M"
]
},
{
"cell_type": "markdown",
"metadata": {
"lang": "en"
},
"source": [
"Magic!"
]
},
{
"cell_type": "markdown",
"metadata": {
"lang": "en"
},
"source": [
"### Why does the 2nd case work?\n",
"\n",
"For an iterative method of the ${\\bf x} = B\\; {\\bf x} + {\\bf c}$ type to converge, it is necessary to choose\n",
"\n",
"* $\\rho(B) < 1\\quad$ with $\\rho$ the spectral radius (which is the largest eigenvalue in absolute value)\n",
"* $||B|| < 1\\quad$ with a matrix norm is subordinate to a vector norm.\n",
" \n",
"\n",
"#### Case of the Jacobi method\n",
"\n",
"For the Jacobi matrix B, these conditions are met if A is at **dominant diagonal**, namely that each\n",
"diagonal element is greater in modulus than the sum of all other modulus elements in its row or column ($|a_{i,i}| \\ge \\sum_{j \\ne i} |a_{i,j}|$).\n",
"\n",
"Jacobi also converges if A is symmetric, real and positive definite\n",
"(i.e. $\\forall {\\bf x}, \\; {\\bf x}^T \\, A \\, {\\bf x} > 0$)."
]
},
{
"cell_type": "markdown",
"metadata": {
"lang": "en"
},
"source": [
"### Calculation time\n",
"\n",
"If we converge in 10 iterations then\n",
"10 matrix multiplications, 10 vector additions and 10 vector divisions are used, i.e. 180 operations\n",
"to be compared to the $4^3 / 3 = 22$ operations of a direct method. Damn !\n",
"\n",
"Some reasons why we lose are\n",
"\n",
"* Matrix A is too small, iterative methods are interesting for large matrices\n",
"* Jacobi's method is not the best (but it is very easily parallelizable)\n",
"* The spectral radius of the matrix is too large. The greater the spectral radius\n",
" smaller and the faster we converge."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
]
}
|