title | date | categories | math | comments | |
---|---|---|---|---|---|
CALCOLO 2024 |
2024-05-05 16:00:00 -0700 |
|
true |
true |
{: .prompt-tip }
for those who are english speakers, I don't know if my teacher wants this documentation in english or italian, so I'll write it in italian you can translate it with google translate or something like that.
Questo è un riassunto di tutti i codici di calcolo numerico che ho scritto durante il corso di calcolo numerico all'università di Bari.
{: .prompt-info }
Potete trovare tutti i codici qui!
Il metodo di bisezione è un metodo di ricerca di zeri di una funzione continua in un intervallo.
Il metodo prevede di dividere l'intervallo in due parti uguali e di selezionare il sottointervallo in cui la funzione soddisfi il teorema degli zero.
Questo processo viene ripetuto fino a quando la lunghezza dell'intervallo diventa sufficientemente piccola.
Formalmente il metodo può essere descritto come segue:
- Scegliere un intervallo iniziale
$$[a, b]$$ tale che $$ f(a) * f(b) < 0 \space$$ a.k.a teorema degli zeri o di Bolzano - Calcolare il punto medio $$ c = (a + b) / 2 $$
- Calcolare $$ f(c) $$
- Se $$ f(c) = 0 $$ , allora $$ c $$ è la radice
- Altrimenti, se $$ f(c) * f(a) < 0 $$ , allora la radice si trova nell'intervallo $$ [a, c] $$
- Altrimenti, la radice si trova nell'intervallo $$ [c, b] $$
- Ripetere i passaggi 2-6 fino a quando la lunghezza dell'intervallo diventa più piccola di una certa tolleranza
function m = bisection(f, low, high, tol , cap)
disp('Bisection Method');
y1 = feval(f, low);
y2 = feval(f, high);
i = 0;
if y1 * y2 > 0
disp('Bolzano theorem not verified ...');
m = 'Error';
return
end
disp('Iter low high x0');
while (abs(high - low) >= tol)
i = i + 1;
m = (high + low)/2;
y3 = feval(f, m);
if y3 == 0
fprintf('Root at x = %f \n\n', m);
return
end
fprintf('%2i \t %f \t %f \t %f \n', i-1, low, high, m);
if y1 * y3 > 0
low = m;
y1 = y3;
else
high = m;
end
if i > cap
break
end
end
fprintf('\n x = %f produces f(x) = %f \n %i iterations\n', m, y3, i-1);
fprintf(' Approximation with tolerance = %f \n', tol);
Il codice scritto calcola la radice di una funzione $$ f $$ nell'intervallo $$ [low, high] $$ con una tolleranza $$ tol $$ e un limite di iterazioni $$ cap $$
{: .prompt-info}
Esempio di utilizzo del codice:
f = @(x)(x+1)^2-4;
bisection(f,-3,4,0.00000001 , 100);
{: .prompt-tip}
Dando come output:
Bisection Method
Iter low high x0
0 -3.000000 4.000000 0.500000
1 -3.000000 0.500000 -1.250000
2 -3.000000 -1.250000 -2.125000
3 -3.000000 -2.125000 -2.562500
4 -3.000000 -2.562500 -2.781250
5 -3.000000 -2.781250 -2.890625
6 -3.000000 -2.890625 -2.945312
7 -3.000000 -2.945312 -2.972656
8 -3.000000 -2.972656 -2.986328
9 -3.000000 -2.986328 -2.993164
10 -3.000000 -2.993164 -2.996582
11 -3.000000 -2.996582 -2.998291
12 -3.000000 -2.998291 -2.999146
13 -3.000000 -2.999146 -2.999573
14 -3.000000 -2.999573 -2.999786
15 -3.000000 -2.999786 -2.999893
16 -3.000000 -2.999893 -2.999947
17 -3.000000 -2.999947 -2.999973
18 -3.000000 -2.999973 -2.999987
19 -3.000000 -2.999987 -2.999993
20 -3.000000 -2.999993 -2.999997
21 -3.000000 -2.999997 -2.999998
22 -3.000000 -2.999998 -2.999999
23 -3.000000 -2.999999 -3.000000
24 -3.000000 -3.000000 -3.000000
25 -3.000000 -3.000000 -3.000000
26 -3.000000 -3.000000 -3.000000
27 -3.000000 -3.000000 -3.000000
28 -3.000000 -3.000000 -3.000000
29 -3.000000 -3.000000 -3.000000
x = -3.000000 produces f(x) = -0.000000
29 iterations
Approximation with tolerance = 0.000000
{: .prompt-tip}
Plot della funzione $$ f(x):=x*cos(x)$$
Il metodo della Regula Falsi è un metodo di ricerca di zeri di una funzione continua in un intervallo.
Esso è una variante del metodo delle bisezioni, in cui il punto medio viene calcolato come l'intersezione della retta che passa per $$ (a, f(a)) $$ e $$ (b, f(b)) $$ con l'asse x.
Il metodo della Regula Falsi può essere descritto come segue:
- Scegliere un intervallo iniziale
$$[a, b]$$ tale che $$ f(a) * f(b) < 0 $$ teorema di Bolzano - Calcolare il punto medio $$ c = a - f(a) * \dfrac{(b - a)}{(f(b) - f(a))} $$
- Calcolare $$ f(c) $$
- Se $$ f(c) = 0 $$ , allora $$ c $$ è la radice
- Altrimenti, se $$ f(c) * f(a) < 0 $$ , allora la radice si trova nell'intervallo $$ [a, c] $$
- Altrimenti, la radice si trova nell'intervallo $$ [c, b] $$
- Ripetere i passaggi 2-6 fino a quando la lunghezza dell'intervallo diventa più piccola di una certa tolleranza
function m = falsePosition(f, low , high , tol , cap)
disp('falsePosition');
y1 = feval(f, low);
y2 = feval(f, high);
i = 0;
if y1 * y2 > 0
disp('Bolzano theorem not verified ...');
m = 'Error';
return
end
disp('Iter low high x0');
while (abs(high - low) >= tol)
i = i + 1;
m = low - y1 * (high - low) / (y2 - y1);
y3 = feval(f,m);
if y3 == 0
fprintf('Root at x = %f \n\n', m);
return
end
fprintf('%2i \t %f \t %f \t %f \n', i-1, low, high, m);
if y1 * y3 > 0
low = m;
y1 = y3;
else
high = m;
end
if i > cap
break
end
end
fprintf('\n x = %f produces f(x) = %f \n %i iterations\n', m, y3, i-1);
fprintf(' Approximation with tolerance = %f \n', tol);
Il codice scritto segue lo stesso principio del metodo delle bisezioni, ma invece di calcolare il punto medio, calcola il punto in cui la retta che passa per $$ (a, f(a)) $$ e $$ (b, f(b)) $$ interseca l'asse x.
{: .prompt-info}
Esempio di utilizzo del codice:
f = @(x)(x+1)^2-10;
falsePosition(f,-4,10,0.00000001, 100);
{: .prompt-tip}
Dando come output:
falsePosition
Iter low high x0
0 -4.000000 10.000000 -3.875000
1 -3.875000 10.000000 -3.661538
2 -3.661538 10.000000 -3.311808
3 -3.311808 10.000000 -2.775961
4 -2.775961 10.000000 -2.033774
5 -2.033774 10.000000 -1.137616
6 -1.137616 10.000000 -0.218751
7 -0.218751 10.000000 0.578248
8 0.578248 10.000000 1.175242
9 1.175242 10.000000 1.575107
10 1.575107 10.000000 1.823269
11 1.823269 10.000000 1.970061
12 1.970061 10.000000 2.054437
13 2.054437 10.000000 2.102138
14 2.102138 10.000000 2.128853
15 2.128853 10.000000 2.143736
16 2.143736 10.000000 2.152003
17 2.152003 10.000000 2.156587
18 2.156587 10.000000 2.159127
19 2.159127 10.000000 2.160534
20 2.160534 10.000000 2.161312
21 2.161312 10.000000 2.161743
22 2.161743 10.000000 2.161982
23 2.161982 10.000000 2.162114
24 2.162114 10.000000 2.162187
25 2.162187 10.000000 2.162228
26 2.162228 10.000000 2.162250
27 2.162250 10.000000 2.162262
28 2.162262 10.000000 2.162269
29 2.162269 10.000000 2.162273
30 2.162273 10.000000 2.162275
31 2.162275 10.000000 2.162276
32 2.162276 10.000000 2.162277
33 2.162277 10.000000 2.162277
34 2.162277 10.000000 2.162277
35 2.162277 10.000000 2.162278
36 2.162278 10.000000 2.162278
37 2.162278 10.000000 2.162278
38 2.162278 10.000000 2.162278
39 2.162278 10.000000 2.162278
40 2.162278 10.000000 2.162278
x = 2.162278 produces f(x) = -0.000000
40 iterations
Approximation with tolerance = 0.000000
{: .prompt-tip}
Plot della funzione $$ f(x):=x^2 - 4$$
Il metodo di Newton è un metodo iterativo per trovare gli zeri di una funzione.
Il metodo di Newton è basato sull'idea di approssimare la funzione con una retta tangente e trovare il punto in cui la retta tangente interseca l'asse x.
Il metodo di Newton può essere descritto come segue:
- Scegliere un punto iniziale $$ x_0 $$
- Calcolare la derivata della funzione $$ f'(x) $$
- Calcolare il punto in cui la retta tangente interseca l'asse x: $$ x_1 = x_0 - \dfrac{f(x_0)}{f'(x_0)} $$
- Ripetere i passaggi 2-3 fino a quando la differenza tra $$ x_n $$ e $$ x_{n-1} $$ è inferiore a una certa tolleranza stando attenti a non dividere per un valore troppo piccolo e a non superare un certo numero di iterazioni
function m = newton(f, g, x0, tol, cap)
i = 1;
m = x0;
dm = - f(m) / g(m);
disp('Iter x0');
while abs(dm) > tol
if i > cap
disp("iteration limit reached")
m = "Error";
return
end
if abs(g(m)) < 10^-10
disp("the value of g(x) is too small")
m = "Error";
return
end
m = m + dm;
fprintf("%i \t %f \n", i, m)
dm = - f(m) / g(m);
i = i + 1;
end
fprintf('\n x = %f produces f(x) = %f \n %i iterations\n', m, feval(f,m), i-1);
fprintf(' Approximation with tolerance = %f \n', tol);
Il codice scritto calcola la radice di una funzione $$ f $$ con la derivata $$ g $$ e il punto iniziale $$ x_0 $$ con una tolleranza $$ tol $$ e un limite di iterazioni $$ cap $$.
{: .prompt-info}
Esempio di utilizzo del codice:
f = @(x)(x+1)^2-9;
g = @(x)2*x + 2;
disp(newton(f, g , 3 ,10^-6 , 100));
{: .prompt-tip}
Dando come output:
Iter x0
1 2.125000
2 2.002500
3 2.000001
4 2.000000
x = 2.000000 produces f(x) = 0.000000
4 iterations
Approximation with tolerance = 0.000001
2.0000
{: .prompt-tip}
Plot della funzione $$ f(x):= x*sin(x) $$
Il metodo delle secanti è un metodo iterativo per trovare gli zeri di una funzione.
Il metodo delle secanti è simile al metodo di Newton, ma invece di calcolare la derivata della funzione, approssima la derivata con una retta che passa per due punti.
Il metodo delle secanti può essere descritto come segue:
- Scegliere due punti iniziali $$ x_0 $$ e $$ x_1 $$
- Calcolare il punto in cui la retta che passa per $$ (x_0, f(x_0)) $$ e $$ (x_1, f(x_1)) $$ interseca l'asse x: $$ x_2 = x_1 - f(x_1) * \dfrac{(x_1 - x_0)}{(f(x_1) - f(x_0))} $$
- Ripetere i passaggi 2-3 fino a quando la differenza tra $$ x_n $$ e $$ x_{n-1} $$ è inferiore a una certa tolleranza stando attenti a non dividere per un valore troppo piccolo e a non superare un certo numero di iterazioni
function m = secant(f, x0, x1 , tol, cap)
i = 1;
dm = - f(x1)* (x1 - x0) / (f(x1) - f(x0));
disp('Iter x0 x1');
while abs(dm) > tol
if i > cap
disp("iteration limit reached")
m = "Error";
return
end
m = x1;
x0 = x1;
x1 = x1 + dm;
fprintf("%i \t %f \t %f \n", i, x0 , x1)
dm = - f(x1)* (x1 - x0) / (f(x1) - f(x0));
i = i + 1;
end
fprintf('\n x = %f produces f(x) = %f \n %i iterations\n', m, feval(f,m), i-1);
fprintf(' Approximation with tolerance = %f \n', tol);
Il codice calcola la radice di una funzione $$ f $$ con il punto iniziale $$ x_0 $$ e $$ x_1 $$ con una tolleranza $$ tol $$ e un limite di iterazioni $$ cap $$.
{: .prompt-info}
Esempio di utilizzo del codice:
f = @(x)(x+1)^2-9;
g = @(x)2*x + 2;
disp(secant(f,-3 , 3 ,10^-6 , 100));
{: .prompt-tip}
Dando come output:
example
Iter x0 x1
1 3.000000 -0.500000
2 -0.500000 1.444444
3 1.444444 2.471698
4 2.471698 1.955705
5 1.955705 1.996749
6 1.996749 2.000024
7 2.000024 2.000000
x = 2.000024 produces f(x) = 0.000145
7 iterations
Approximation with tolerance = 0.000001
2.0000
{: .prompt-tip}
Plot della funzione $$ f(x):= -x^3 + 3x^2 - 4 $$
Il metodo delle corde è un metodo iterativo per trovare gli zeri di una funzione.
Il metodo delle corde è simile al metodo delle secanti, ma invece di calcolare il punto in cui la retta che passa per $$ (x_0, f(x_0)) $$ e $$ (x_1, f(x_1)) $$ interseca l'asse x, calcola il punto in cui la retta che passa per $$ (a, f(a)) $$ e $$ (b, f(b)) $$ interseca l'asse x.
Il metodo delle corde può essere descritto come segue:
- Scegliere due punti iniziali $$ a $$ e $$ b $$
- Calcolare il punto in cui la retta che passa per $$ (a, f(a)) $$ e $$ (b, f(b)) $$ interseca l'asse x: $$ x_2 = b - f(b) * \dfrac{(b - a)}{(f(b) - f(a))} $$
- Ripetere i passaggi 2-3 fino a quando la differenza tra $$ x_n $$ e $$ x_{n-1} $$ è inferiore a una certa tolleranza stando attenti a non dividere per un valore troppo piccolo e a non superare un certo numero di iterazioni
function m = rope(f, g, x0, tol, cap)
i = 1;
m = x0;
ca = g(m);
dm = - f(m) / ca;
disp('Iter x0');
while abs(dm) > tol
if i > cap
disp("iteration limit reached")
m = "Error";
return
end
m = m + dm;
fprintf("%i \t %f \n", i, m)
dm = - f(m) / ca;
i = i + 1;
end
fprintf('\n x = %f produces f(x) = %f \n %i iterations\n', m, feval(f,m), i-1);
fprintf(' Approximation with tolerance = %f \n', tol);
Il codice scritto calcola la radice di una funzione $$ f $$ con la derivata $$ g $$ e il punto iniziale $$ x_0 $$ con una tolleranza $$ tol $$ e un limite di iterazioni $$ cap $$.
{: .prompt-info}
Esempio di utilizzo del codice:
f = @(x)(x+1)^2-9;
g = @(x)2*x + 2;
disp(rope(f, g , 3 ,10^-6 , 100));
{: .prompt-tip}
Dando come output:
Iter x0
1 2.125000
2 2.029297
3 2.007217
4 2.001798
5 2.000449
6 2.000112
7 2.000028
8 2.000007
9 2.000002
10 2.000000
x = 2.000000 produces f(x) = 0.000003
10 iterations
Approximation with tolerance = 0.000001
2.0000
{: .prompt-tip}
Plot della funzione $$ f(x):= x^3 - 2x^2 - 4 $$
$$ A \cdot x = b $$ Che belle le matrici, vero?
La fattorizzazione LU è un metodo per fattorizzare una matrice $$ A $$ in due matrici una triangolare inferiore e un' altra triangolare inferiore $$L$$ower $$U$$pper.
La fattorizzazione LU può essere descritta come segue:
- Scegliere una matrice $$ A $$
- Verificare che la matrice sia quadrata
- calcoliamo la colonne di $$ L $$ e le righe di $$ U $$ con la seguente formula: $$ L(i, j) = \dfrac{U(i, j)}{U(j, j)} $$
- calcoliamo la matrice $$ U $$ con la seguente formula: $$ U(i, j:n) = U(i, j:n) - L(i, j) * U(j, j:n) $$
- passaggi 3-4 fino a quando la matrice è fattorizzata
- restituire le matrici $$ L $$ e $$ U $$
function [L, U] = LUfactorization(A)
[n, m] = size(A);
if n ~= m
error('Matrix must be square');
end
L = eye(n);
U = A;
for k = 1:n-1
for i = k+1:n
L(i, k) = U(i, k) / U(k, k);
U(i, k:n) = U(i, k:n) - L(i, k) * U(k, k:n);
end
end
end
{: .prompt-info}
Esempio di utilizzo del codice:
A = [1 , 2, 3; 4, 5 ,6 ;7, 8, 11];
[L,U] = LUfactorization(A);
disp(L)
disp(U)
disp(L*U)
{: .prompt-tip}
Dando come output:
L = |1 0 0|
|4 1 0|
|7 2 1|
U = |1 2 3|
|0 -3 -6|
|0 0 2|
LU = |1 2 3|
|4 5 6|
|7 8 11|
La fattorizzazione LU con pivoting parziale è un metodo per fattorizzare una matrice in due matrici triangolari inferiori e superiori con pivoting parziale.
Il pivoting parziale è un metodo per scambiare le righe di una matrice in modo che il pivot sia il più grande possibile.
Il pivot deve essere il più grande possibile per evitare errori di arrotondamento.
La fattorizzazione LU con pivoting parziale può essere descritta come segue:
- Scegliere una matrice $$ A $$
- Verificare che la matrice sia quadrata
- Scegliere il pivot più grande nella colonna corrente
- Scambiare la riga corrente con la riga del pivot
- Calcoliamo la colonne di $$ L $$ e le righe di $$ U $$ con la seguente formula: $$ L(i, j) = \dfrac{U(i, j)}{U(j, j)} $$
- Calcoliamo la matrice $$ U $$ con la seguente formula: $$ U(i, j:n) = U(i, j:n) - L(i, j) * U(j, j:n) $$
- Passaggi 3-6 fino a quando la matrice è fattorizzata
- Restituire le matrici $$ L $$ e $$ U $$
function [L, U, P] = LUfactorization_partial_pivoting(A)
[m, n] = size(A);
L = eye(m);
U = A;
P = eye(m);
for k = 1:min(m, n)
[~, pivot] = max(abs(U(k:m, k)));
pivot = pivot + k - 1;
if pivot ~= k
U([k, pivot], :) = U([pivot, k], :);
P([k, pivot], :) = P([pivot, k], :);
if k > 1
L([k, pivot], 1:k-1) = L([pivot, k], 1:k-1);
end
end
for i = k+1:m
L(i, k) = U(i, k) / U(k, k);
U(i, k:n) = U(i, k:n) - L(i, k) * U(k, k:n);
end
end
if m > n
U = U(1:n, :);
L = L(:, 1:n);
end
end
{: .prompt-info}
Esempio di utilizzo del codice:
A = [4, 3, 2;
3, 2, 1;
2, 1, 3;
1, 4, 5];
[L, U, P] = LUfactorization_partial_pivoting(A);
disp('Matrix A:');
disp(A);
disp('Permutation matrix P:');
disp(P);
disp('Lower triangular matrix L:');
disp(L);
disp('Upper triangular matrix U:');
disp(U);
disp('Verification: P * A should be equal to L * U');
disp(P * A);
disp(L * U);
{: .prompt-tip}
Dando come output:
Matrix A:
|4 3 2|
|3 2 1|
|2 1 3|
|1 4 5|
Row permutation matrix P:
|0 0 0 1|
|1 0 0 0|
|0 0 1 0|
|0 1 0 0|
Column permutation matrix Q:
|0 1 0|
|0 0 1|
|1 0 0|
Lower triangular matrix L:
|1.0000 0 0|
|0.4000 1.0000 0|
|0.6000 0.3889 1.0000|
|0.2000 0.7778 -0.0571|
Upper triangular matrix U:
|5.0000 1.0000 4.0000|
| 0 3.6000 1.4000|
| 0 0 -1.9444|
Verification: P * A * Q should be equal to L * U
|5 1 4|
|2 4 3|
|3 2 1|
|1 3 2|
|5 1 4|
|2 4 3|
|3 2 1|
|1 3 2|
{: .prompt-info}
Un altro esempio di utilizzo del codice:
A = [1 , 2 , 3 , 4 ;
5 , 6 , 7 , 8 ;
9 , 10, 11, 12;]
[L, U, P] = LUfactorization_partial_pivoting(A);
disp('Matrix A:');
disp(A);
disp('Permutation matrix P:');
disp(P);
disp('Lower triangular matrix L:');
disp(L);
disp('Upper triangular matrix U:');
disp(U);
disp('Verification: P * A should be equal to L * U');
disp(P * A);
disp(L * U);
{: .prompt-tip}
Dando come output:
A =
|1 2 3 4|
|5 6 7 8|
|9 10 11 12|
Matrix A:
|1 2 3 4|
|5 6 7 8|
|9 10 11 12|
Permutation matrix P:
|0 0 1|
|1 0 0|
|0 1 0|
Lower triangular matrix L:
|1.0000 0 0|
|0.1111 1.0000 0|
|0.5556 0.5000 1.0000|
Upper triangular matrix U:
|9.0000 10.0000 11.0000 12.0000|
| 0 0.8889 1.7778 2.6667|
| 0 0 -0.0000 -0.0000|
Verification: P * A should be equal to L * U
|9 10 11 12|
|1 2 3 4|
|5 6 7 8|
|9 10 11 12|
|1 2 3 4|
|5 6 7 8|
La fattorizzazione LU con pivoting totale è un metodo per fattorizzare una matrice in due matrici triangolari inferiori e superiori con pivoting totale.
Il pivoting totale è un metodo per scambiare le righe e le colonne di una matrice in modo che il pivot sia il più grande possibile.
Il pivot deve essere il più grande possibile per evitare errori di arrotondamento.
La fattorizzazione LU con pivoting totale può essere descritta come segue:
- Scegliere una matrice $$ A $$
- Verificare che la matrice sia quadrata
- Scegliere il pivot più grande nella matrice
- Scambiare la riga corrente con la riga del pivot
- Scambiare la colonna corrente con la colonna del pivot
- Calcoliamo la colonne di $$ L $$ e le righe di $$ U $$ con la seguente formula: $$ L(i, j) = \dfrac{U(i, j)}{U(j, j)} $$
- Calcoliamo la matrice $$ U $$ con la seguente formula: $$ U(i, j:n) = U(i, j:n) - L(i, j) * U(j, j:n) $$
- Passaggi 3-7 fino a quando la matrice è fattorizzata
- Restituire le matrici $$ L $$ e $$ U $$
function [L, U, P, Q] = LUfactorization_total_pivoting(A)
[m, n] = size(A);
L = eye(m);
U = A;
P = eye(m);
Q = eye(n);
for k = 1:min(m, n)
[pivot_row, pivot_col] = find(abs(U(k:m, k:n)) == max(max(abs(U(k:m, k:n)))), 1);
pivot_row = pivot_row + k - 1;
pivot_col = pivot_col + k - 1;
if pivot_row ~= k
U([k, pivot_row], :) = U([pivot_row, k], :);
P([k, pivot_row], :) = P([pivot_row, k], :);
if k > 1
L([k, pivot_row], 1:k-1) = L([pivot_row, k], 1:k-1);
end
end
if pivot_col ~= k
U(:, [k, pivot_col]) = U(:, [pivot_col, k]);
Q(:, [k, pivot_col]) = Q(:, [pivot_col, k]);
end
for i = k+1:m
L(i, k) = U(i, k) / U(k, k);
U(i, k:n) = U(i, k:n) - L(i, k) * U(k, k:n);
end
end
if m > n
U = U(1:n, :);
L = L(:, 1:n);
end
end
{: .prompt-info}
Esempio di utilizzo del codice:
A = [4, 3, 2;
3, 2, 1;
2, 1, 3;
1, 4, 5];
[L, U, P, Q] = LUfactorization_total_pivoting(A);
disp('Matrix A:');
disp(A);
disp('Row permutation matrix P:');
disp(P);
disp('Column permutation matrix Q:');
disp(Q);
disp('Lower triangular matrix L:');
disp(L);
disp('Upper triangular matrix U:');
disp(U);
disp('Verification: P * A * Q should be equal to L * U');
disp(P * A * Q);
disp(L * U);
{: .prompt-tip}
Dando come output:
Matrix A:
|4 3 2|
|3 2 1|
|2 1 3|
|1 4 5|
Row permutation matrix P:
|0 0 0 1|
|1 0 0 0|
|0 0 1 0|
|0 1 0 0|
Column permutation matrix Q:
|0 1 0|
|0 0 1|
|1 0 0|
Lower triangular matrix L:
|1.0000 0 0|
|0.4000 1.0000 0|
|0.6000 0.3889 1.0000|
|0.2000 0.7778 -0.0571|
Upper triangular matrix U:
|5.0000 1.0000 4.0000|
| 0 3.6000 1.4000|
| 0 0 -1.9444|
Verification: P * A * Q should be equal to L * U
|5 1 4|
|2 4 3|
|3 2 1|
|1 3 2|
|5 1 4|
|2 4 3|
|3 2 1|
|1 3 2|
{: .prompt-info}
Un altro esempio di utilizzo del codice:
A = [1 , 2 , 3 , 4 ;
5 , 6 , 7 , 8 ;
9 , 10, 11, 13;]
[L, U, P, Q] = LUfactorization_total_pivoting(A);
disp('Matrix A:');
disp(A);
disp('Row permutation matrix P:');
disp(P);
disp('Column permutation matrix Q:');
disp(Q);
disp('Lower triangular matrix L:');
disp(L);
disp('Upper triangular matrix U:');
disp(U);
disp('Verification: P * A * Q should be equal to L * U');
disp(P * A * Q);
disp(L * U);
{: .prompt-tip}
Dando come output:
A =
|1 2 3 4|
|5 6 7 8|
|9 10 11 13|
Matrix A:
|1 2 3 4|
|5 6 7 8|
|9 10 11 13|
Row permutation matrix P:
|0 0 1|
|1 0 0|
|0 1 0|
Column permutation matrix Q:
|0 1 0 0|
|0 0 0 1|
|0 0 1 0|
|1 0 0 0|
Lower triangular matrix L:
|1.0000 0 0|
|0.3077 1.0000 0|
|0.6154 0.3043 1.0000|
Upper triangular matrix U:
|13.0000 9.0000 11.0000 10.0000|
| 0 -1.7692 -0.3846 -1.0769|
| 0 0 0.3478 0.1739|
Verification: P * A * Q should be equal to L * U
|13 9 11 10|
| 4 1 3 2|
| 8 5 7 6|
|13 9 11 10|
| 4 1 3 2|
| 8 5 7 6|
La sostituzione in avanti è un metodo per risolvere un sistema lineare con una matrice triangolare inferiore.
La sostituzione in avanti può essere descritta come segue:
- Scegliere una matrice $$ L $$ e un vettore $$ b $$
- Calcolare il vettore $$ x $$ con la seguente formula: $$ x(1) = b(1) / L(1, 1) $$ $$ x(i) = (b(i) - \sum_{j=1}^{i-1} L(i, j) * x(j)) / L(i, i) $$
- Ripetere i passaggi 3-5 fino a quando il vettore $$ x $$ è calcolato
function x = forwardSubstitution(L, b)
n = length(b);
x = zeros(n, 1);
for i = 1:n
if L(i, i) == 0
error('Matrix is singular!');
end
x(i) = (b(i) - L(i, 1:i-1) * x(1:i-1)) / L(i, i);
end
end
{: .prompt-info}
Esempio di utilizzo del codice:
L = [1, 0, 0;
2, 3, 0;
4, 5, 6];
b = [3; 6; 24];
x = forwardSubstitution(L, b);
disp('Solution vector x:');
disp(x);
disp('Verification: L * x should be equal to b');
disp(L * x);
disp(b);
{: .prompt-tip}
Dando come output:
Solution vector
x = [3]
[0]
[2]
Verification: L * x should be equal to b
b = [ 3]
[ 6]
[24]
L*x = [ 3]
[ 6]
[24]
La sostituzione all'indietro è un metodo per risolvere un sistema lineare con una matrice triangolare superiore.
La sostituzione all'indietro può essere descritta come segue:
- Scegliere una matrice $$ U $$ e un vettore $$ b $$
- Calcolare il vettore $$ x $$ con la seguente formula:
- Ripetere i passaggi 3-5 fino a quando il vettore $$ x $$ è calcolato
function x = backwardSubstitution(U, b)
n = length(b);
x = zeros(n, 1);
for i = n:-1:1
if U(i, i) == 0
error('Matrix is singular!');
end
x(i) = (b(i) - U(i, i+1:n) * x(i+1:n)) / U(i, i);
end
end
{: .prompt-info}
Esempio di utilizzo del codice:
U = [2, -1, 0;
0, 3, 1;
0, 0, 4];
b = [1; 8; 4];
x = backwardSubstitution(U, b);
disp('Solution vector x:');
disp(x);
disp('Verification: U * x should be equal to b');
disp(U * x);
disp(b);
{: .prompt-tip}
Dando come output:
Solution vector x:
x = [1.6667]
[2.3333]
[1.0000]
Verification: U * x should be equal to b
b = [1]
[8]
[4]
U*x = [1]
[8]
[4]
Interpoliamo gente!
Il metodo di interpolazione di Lagrange è un metodo per interpolare una funzione con un polinomio di grado n date n+1 coppie di punti.
Il metodo di interpolazione di Lagrange può essere descritto come segue:
- Scegliere n+1 coppie di punti $$ (x_0, y_0), (x_1, y_1), ..., (x_n, y_n) $$
- Calcolare il polinomio di interpolazione con la seguente formula: $$ P(x) = \sum_{i=0}^{n} y_i \prod_{j=0, j \neq i}^{n} \dfrac{x - x_j}{x_i - x_j} $$
function P = lagrangeInterpolation(x, y)
n = length(x);
P = zeros(1, n);
for i = 1:n
Li = 1;
for j = 1:n
if j ~= i
Li = conv(Li, [1, -x(j)]) / (x(i) - x(j));
end
end
P = P + y(i) * Li;
end
end
{: .prompt-info}
Esempio di utilizzo del codice:
x = [0, 1, -1];
y = [0, 1, 1];
P = lagrangeInterpolation(x, y);
disp('Coefficients of the interpolating polynomial:');
disp(P);
xi = linspace(-2, 2, 100);
yi = polyval(P, xi);
figure;
plot(x, y, 'o', xi, yi, '-');
title('Lagrange Interpolation');
xlabel('x');
ylabel('P(x)');
legend('Data Points', 'Interpolating Polynomial');
grid on;
{: .prompt-tip}
Dando come output:
Il metodo di interpolazione di Lagrange con i nodi di Chebyshev è un metodo per interpolare una funzione con un polinomio di grado n date n+1 coppie di punti.
Il metodo di interpolazione di Lagrange con i nodi di Chebyshev può essere descritto come segue:
-
Scegliere n+1 coppie di punti $$ (x_0, y_0), (x_1, y_1), ..., (x_n, y_n) $$
-
Calcolare i nodi di Chebyshev con la seguente formula:
$$ x_i = \dfrac{1}{2} (b - a) \cos \left( \dfrac{2i - 1}{2n} \pi \right) + \dfrac{a + b}{2} $$
$$ x_i = 1/2 * (b-a) * (x_i + 1) + a $$
-
Calcolare il polinomio di interpolazione con la seguente formula: $$ P(x) = \sum_{i=0}^{n} y_i \prod_{j=0, j \neq i}^{n} \dfrac{x - x_j}{x_i - x_j} $$
function P = lagrangeChebyshevInterpolation(f, n, a, b)
x = cos((2*(1:n) - 1) * pi / (2*n));
x = 0.5 * (b - a) * (x + 1) + a;
y = f(x);
P = zeros(1, n);
for k = 1:n
Lk = 1;
for j = 1:n
if j ~= k
Lk = conv(Lk, [1, -x(j)]) / (x(k) - x(j));
end
end
P = P + y(k) * Lk;
end
end
{: .prompt-info}
Esempio di utilizzo del codice:
f = @(x)(1./(1+25*x.^2));
n = 9;
a = -5;
b = 5;
P = lagrangeChebyshevInterpolation(f, n, a, b);
disp('Coefficients of the interpolating polynomial:');
disp(P);
xi = linspace(a, b, 100);
yi = polyval(P, xi);
y_exact = f(xi);
figure;
plot(xi, y_exact, 'b-', xi, yi, 'r--', x, f(x), 'ko');
title('Lagrange Interpolation with Chebyshev Nodes');
xlabel('x');
ylabel('f(x)');
legend('Exact Function', 'Interpolating Polynomial', 'Chebyshev Nodes');
grid on;
{: .prompt-tip}
Dando come output:
Per $$ n = 9 $$
Per $$ n = 13 $$
Il metodo delle potenze è un metodo per calcolare l'autovalore dominante e l'autovettore associato di una matrice.
Il metodo delle potenze può essere descritto come segue:
- Scegliere una matrice $$ A $$ e un vettore $$ x_0 $$
- Calcolare il vettore $$ y = A * x_0 $$
- Calcolare il vettore $$ x_1 = \dfrac{y}{\lVert y \rVert _2} $$
- Ripetere i passaggi 2-3 fino a quando la differenza tra $$ x_n $$ e $$ x_{n-1} $$ è inferiore a una certa tolleranza
function [lambda, v] = method_of_powers(A, tol, max_iter)
n = size(A, 1);
v = ones(n, 1);
v = v / norm(v);
lambda = 0;
for k = 1:max_iter
v_new = A * v;
lambda_new = max(abs(v_new));
v_new = v_new / norm(v_new);
if norm(v_new - v) < tol
break;
end
v = v_new;
lambda = lambda_new;
end
v = v_new;
end
{: .prompt-info}
Esempio di utilizzo del codice:
A = [4, 7; 2, 3];
tol = 1e-6;
max_iter = 1000;
[lambda, v] = method_of_powers(A, tol, max_iter);
disp('dominant eigenvalue:');
disp(lambda);
disp('dominant eigenvector:');
disp(v);
{: .prompt-tip}
Dando come output:
dominant eigenvalue:
6.5894
dominant eigenvector:
0.9058
0.4238
/\_/\
( o.o ) Cosa ci sarai mai qui ?
> ^ <
Questo algoritmo è stato un problema di una gara di logica-matematica, il problema era il seguente:
Dato l'algoritmo di cifratura $$ C $$ trovare la chiave $$ k $$ .
L'algoritmo di cifratura $$ C $$ è il seguente:
key = bytearray(b"???????????????")
message = bytearray(b"yellow submarine")
def get_bit(b, n):
byte = b[n // 8]
return (byte >> (7 - (n % 8))) & 1
def set_bit(b, n, v):
byte = b[n // 8]
byte &= ~(v << (7 - (n % 8)))
byte |= v << (7 - (n % 8))
b[n // 8] = byte
return b
def bfri(i):
a = hex(i)[2:]
if (len(a) % 2 == 1):
a = "0" + a
return bytes.fromhex(a)
def xor(x1, x2):
assert len(x1) == len(x2)
return b"".join([bfri(x1[i] ^ x2[i]) for i in range(len(x1))])
def keysch(k):
out = bytearray(b"\x00"*16)
arr = [0 for i in range(128)]
for i in range(128):
for bit_loc in schedule[i]:
arr[i] ^= get_bit(k, bit_loc)
for i in range(128):
out = set_bit(out, i, arr[i])
return out
def enc(p, k):
dat = bytearray(p)
return xor(p, keysch(k))
def dec(c, k):
dat = bytearray(c)
return xor(c, keysch(k))
c = enc(message, key)
print("m:", message)
print("c:", c)
-
la funzione $$ keysch $$ è la funzione che genera un array di 128 bit a partire da una chiave di 16 byte.
-
la funzione $$ enc $$ è la funzione che cifra un messaggio con una chiave.
-
la funzione $$ dec $$ è la funzione che decifra un messaggio con una chiave.
-
la funzione $$ xor $$ è la funzione che fa lo xor tra due byte.
-
la funzione $$ get_bit $$ è la funzione che prende un bit da un byte.
-
la funzione $$ set_bit $$ è la funzione che setta un bit in un byte.
-
la funzione $$ bfri $$ è la funzione che trasforma un intero in un byte.
-
la chiave $$ k $$ è una stringa di 16 byte.
-
il messaggio $$ m $$ è una stringa di 16 byte.
Ho portato questo problema come bonus, perchè racchiude molti aspetti dell'algebra lineare e di come possa essere utilizzata in contesti reali.
Il codice fornito è veramente lungo, ma è facile da capire una volta semplificato, qui riporto in breve cosa succede in questo algoritmo :
- il codice cifra un messaggio $$ m $$ con una sequenza pseudo-casuale di 128 bit generata da una chiave $$ k
$$, chiamiamo questa sequenza $$ keyschedule $$ - la cifratura avviene con uno $$ xor $$ tra il messaggio e la keyschedule $$ c = m \oplus keyschedule $$
- la decifratura avviene con uno $$ xor $$ tra il messaggio cifrato e la keyschedule $$ m = c \oplus keyschedule $$
Dobbiamo trovare la chiave $$ k $$ dato il messaggio cifrato $$ c $$ e il messaggio $$ m $$.
Come ho detto prima, il problema fa parte di una competizione che si basava su problemi di crittografia è facile capire che se puoi ricavare la chiave da un algoritmo di cifratura, vuol dire che l'algoritmo è debole.
Dove sta la debolezza di questo algoritmo ?
in qualche modo la chiave $$ k $$ è collegata alla $$ keyschedule
Partiamo da due osservazioni :
- possiamo ricavarci la
$$keyschedule$$ dato il messaggio cifrato e il messaggio $$ keyschedule = m \oplus c $$ - adesso riflettiamo su cosa è la
$$keyschedule$$ per trovare un collegamento con la chiave $$ k $$:
def keysch(k):
out = bytearray(b"\x00"*16)
arr = [0 for i in range(128)]
for i in range(128):
for bit_loc in schedule[i]:
arr[i] ^= get_bit(k, bit_loc)
for i in range(128):
out = set_bit(out, i, arr[i])
return out
- $$ schedule $$ è una tabella 128 x 128 dove ogni cella rappresenta una posizione da 0 a 127 .
- la $$ keyschedule $$ è ricavata secondo la seguente regola :
- per ogni riga della $$ schedule $$ prendiamo i bit della chiave $$ k $$ referenziati dalla riga e facciamo lo xor tra di loro.
- il risultato di questo xor sarà il bit i della $$ keyschedule $$.
formalmente :
{: .prompt-tip }
ad esempio ho un byte 10010100 e schedule è [1, 3, 5, 7] allora il bit 0 della $$ keyschedule $$ sarà $$ bit[1] \oplus bit[3] \oplus bit[5] \oplus bit[7] $$
{: .prompt-info }
come facciamo a risalire alla chiave dalla $$ keyschedule $$ ?
La soluzione è molto semplice :
immaginiamo se accendessimo l'iesimo bit della chiave, questo influenzerà tutti i bit che sono referenziati nella $$ schedule $$.
Adesso ordiniamo i bit per colonne cosa otteniamo ?
Un sistema da 128 equazioni lineari con 128 incognite, le incognite sono i bit della chiave $$ k $$ e le equazioni ci dicono quante volte stiamo prendendo il bit i della chiave $$ k $$ nella $$ keyschedule $$.
{: .prompt-tip }
Risolvere questo sistema è molto semplice, pensiamo a cosa vuol dire fare lo xor tra due bit : $$ 0 \oplus 0 = 0
$$, $$ 1 \oplus 1 = 0$$, $$ 0 \oplus 1 = 1$$, $$ 1 \oplus 0 = 1 $$, quindi possiamo dire che fare lo xor tra due bit è come fare una somma in$Z/2Z$ .
in maniera formale costruiamo il sistema lineare :
dove $$ bit_{ij} $$ è il bit i della chiave k referenziato dalla j-esima riga della $$ schedule $$ $$ n $$ $$ mod 2 $$ volte.
la schedule non è altro che il vettore generato usando la matrice $$ system $$ sulla chiave $$ k $$ che è la nostra incognita
$$ \begin{equation} \begin{bmatrix} bit_{1,1} & bit_{2,1} & \cdots & bit_{128,1} \ bit_{1,2} & bit_{2,2} & \cdots & bit_{128,2} \ \vdots & \vdots & \ddots & \vdots \ bit_{1,128} & bit_{2,128} & \cdots & bit_{128,128} \end{bmatrix} \begin{bmatrix} x_1 \ x_2 \ \vdots \ x_{128} \end{bmatrix}
\begin{bmatrix} b_1 \ b_2 \ \vdots \ b_{128} \end{bmatrix} \end{equation} $$
Non ci resta che risolvere il sistema lineare per trovare la chiave $$ k $$
però c'è un problema, la matrice $$ system $$ è singolare quindi non possiamo risolvere il sistema lineare direttamente, ma ci troviamo in
Questo per far capire che lavorare su un campo finito è diverso da lavorare con $$ \mathbb{R} $$ se prendiamo un vettore e lo allunghiamo rapprentiamo una retta in $$ Z/2Z $$ invece giochiamo con dei punti. Usare diversi campi sulle matrici è un argomento molto interessante e che può essere sfruttato in molti contesti.
from sage.all_cmdline import *
from scheduu import schedule
from pwn import xor
def set_bit(b, n, v):
byte = b[n // 8]
byte &= ~(v << (7 - (n % 8)))
byte |= v << (7 - (n % 8))
b[n // 8] = byte
return b
system = [[0 for i in range(128)] for i in range(128)]
pt = b'yellow submarine'
ct = b'\x83h\xdbWi\'\xc4\xf5\x02,_Z\x95p\xab\xc3'
keystream = xor(pt,ct)
for i in range(0,128):
for k in schedule[i]:
system[i][k]+=1
system[i][k]%=2
b = []
for i in keystream:
for j in range(8):
b.append((i>>(7-j))&1)
A = matrix(GF(2),system)
b = vector(GF(2),b)
sol= A.solve_right(b)
key = bytearray(b"\x00" * 16)
ker = A.right_kernel()
for i in range(128):
set_bit(key, i, int(sol[i]))
print(key)
_sol=sol+ker.basis()[0]
key_ = bytearray(b"\x00" * 16)
for i in range(128):
set_bit(key_, i, int(_sol[i]))
print(key_)
Dando come output:
{: .prompt-tip }
sono_la_chiave!!
Gli autovalori possono essere sfruttati per calcolare la potenza di una matrice attraverso la diagonalizzazione. Questo metodo è particolarmente utile quando si ha a che fare con matrici che possono essere diagonalizzate, ossia quelle matrici che hanno una base di autovettori. Di seguito viene spiegato il processo in dettaglio.
Supponiamo di avere una matrice quadrata $$ A
Dove:
- $$ D $$ è una matrice diagonale i cui elementi sulla diagonale principale sono gli autovalori di $$ A $$.
- $$ P $$ è una matrice i cui colonne sono gli autovettori di $$ A $$.
Se vogliamo calcolare $$ A^n $$ (dove $$ n $$ è un intero positivo), possiamo sfruttare la diagonalizzazione come segue:
- Diagonalizza $$ A $$: trova $$ P $$ e $$ D $$ tali che $$ A = PDP^{-1} $$.
- Eleva $$ A $$ alla potenza $$ n $$:
- Utilizza la proprietà delle matrici diagonalizzate per semplificare il calcolo:
- Poiché $$ D $$ è diagonale, elevare $$ D $$ alla potenza $$ n $$ è semplice: basta elevare ciascun autovalore sulla diagonale alla potenza $$ n $$:
Quindi, possiamo scrivere:
In questo modo, calcolare $$ A^n $$ diventa molto più semplice, poiché si riduce al calcolo delle potenze degli autovalori e alla moltiplicazione di matrici, anziché dover effettuare moltiplicazioni ripetute della matrice originale.
from sage.all_cmdline import *
from Crypto.Util.number import long_to_bytes,bytes_to_long
A = matrix(QQ, 2, [3, 1, 0, 2])
print(A)
print(A.eigenvectors_right())
P = matrix(QQ, 2, [1, 1, 0, -1])
D = matrix(QQ, 2, [3, 0, 0, 2])
print(D)
print(D ** 50)
D = D**50
print(P * D * P ** -1)
print(A ** 50)
assert (P * D * P ** -1 == A ** 50)
L'esponenziazione veloce di una matrice, anche conosciuta come "exponentiation by squaring", è un algoritmo efficiente per calcolare la potenza di una matrice in tempo logaritmico rispetto all'esponente. Questa tecnica è particolarmente utile quando si devono calcolare potenze elevate di una matrice, come ad esempio in applicazioni di algebra lineare, grafi, sistemi dinamici e crittografia.
L'idea principale dell'esponenziazione veloce è ridurre il numero di moltiplicazioni matriciali sfruttando le proprietà delle potenze. L'algoritmo funziona in modo ricorsivo o iterativo dividendo l'esponente per 2.
Dato una matrice
- Se
$$n = 0$$ , allora$$A^0 = I$$ (la matrice identità). - Se
$$n = 1$$ , allora$$A^1 = A$$ . - Se
$$n$$ è pari, allora$$A^n = (A^{n/2})^2$$ . - Se
$$n$$ è dispari, allora$$A^n = A \cdot A^{n-1}$$ .
Un modo iterativo per eseguire l'esponenziazione veloce di una matrice è il seguente:
- Inizializzare il risultato
$$R$$ come la matrice identità$$I$$ . - Utilizzare una variabile
$$B$$ inizializzata a$$A$$ e una variabile$$n$$ per l'esponente. - Ripetere i seguenti passaggi finché
$$n > 0$$ :
- Se
$$n$$ è dispari, aggiornare$$R$$ come$$R = R \cdot B$$ . - Aggiornare
$$B$$ come$$B = B \cdot B$$ . - Dividere
$$n$$ per 2 (trascurando il resto).
Ecco uno pseudocodice dell'algoritmo iterativo:
function B = fast_exp(A,k)
B = eye(size(A));
while k > 0
if mod(k, 2) == 1
B = B * A;
end
A = A * A;
k = floor(k / 2);
end
{: .prompt-info}
Ecco un esempio di come utilizzare l'esponenziazione veloce per calcolare la potenza di una matrice:
A = eye(3);
A(1,2) = 1;
A = A*2;
disp(fast_exp(A,3));
{: .prompt-tip}
Dando come output:
8 24 0
0 8 0
0 0 8
La complessità temporale dell'esponenziazione veloce di una matrice è