In [1]:
2+2
Out[1]:
Ou bien aussi
In [2]:
using LinearAlgebra
a=Matrix(I,3,3)
Out[2]:
In [3]:
?zeros
Out[3]:
In [4]:
function f(x)
return sin(x+1)
end
Out[4]:
Ou l'opérateur d'affectation =
In [5]:
f2(x) = sin(x+1)
Out[5]:
ou encore l'operateur ->
In [6]:
f3 = x-> sin(x+1)
Out[6]:
On peut annoter une fonction selon le type des arguments qu'elle prend
In [7]:
"""
puiss(x::Number, n::Int)
calcule la puissance d'un nombre
"""
function puiss(x::Number, n::Int)
s = 1.
for i=1:abs(n)
s *= ( n > 0 ? x : 1. /x )
end
return s
end
Out[7]:
In [8]:
puiss(2., 2)
Out[8]:
In [9]:
puiss(2., -2)
Out[9]:
Par contre si on essaye avec un second argument flottant
In [10]:
puiss(2, 4.5)
In [11]:
puiss(x::Float64, n::Float64) = exp(n*log(abs(x)))
Out[11]:
In [12]:
puiss(2.,4.5)
Out[12]:
On peut alors lister les fonctions puiss
grâce à la fonction methods
In [13]:
methods(puiss)
Out[13]:
In [14]:
using Test
@test 1==2
In [15]:
@test 1==1
Out[15]:
In [16]:
@test puiss(2,3) == 8
Out[16]:
In [17]:
@test puiss(√2, 2) ≈ 2 atol= 1e-12
Out[17]:
$\newcommand{\R}{\mathbb{R}}$ On va tout d'abord étudier différentes manières de calculer un laplacien en différences finies sur le carré $\Omega = [0,1]^2$. </br> En réalité, on ne va travailler que sur l'intérieur $\stackrel{o}{\Omega}$. On considère une fonction $u : \Omega \rightarrow \R$, que l'on souhaite approcher par ses valeurs $$ U_{i,j} = u(ih,jh) \mbox{ pour } i,j=0,\cdots,n+1 $$ avec $h=\frac{1}{n+1}$. On considérera - {\em abusivement} - $U$ comme une matrice ou un vecteur - noté $u$ - de $\R^{(n+2)^2}$ ou de $\R^{n\times n}$ en considérant que $u$ est nulle sur les bords $$ u(x,y) = 0 \qquad \forall (x,y) \in \overline{\Omega} $$ ce qui se traduit par $$ U_{0,i} = U_{n+1,i} = U_{i,0} = U_{i,n+1} = 0 \mbox{ pour } i=0,\cdots, n+1 $$ Pour discrétiser le laplacien sur une grille régulière on va utiliser l'approximation suivante, basée sur un développement limité \begin{eqnarray*}\label{Chal} \Delta u(x,y) & \approx & \frac{u(x-h_x, y) + u(x+ h_x, y) - 2 u(x,y)}{h^2} \\ & + & \frac{u(x, y-h_y) + u(x, y + h_y) - 2 u(x,y)}{h^2}, \end{eqnarray*}
Graphiquement, cela correspondera à appliquer le pochoir(«stencil») suivant
à l'intérieur d'une matrice avec les coefficients $d = -4/h^2$ , $w_x = w_y = 1/h^2$
En première approche, on peut calculer $\Delta u$ comme une matrice dense d'ordre $n^2$. L'ordre
choisi pour déplier une matrice $n \times n$ en un vecteur de $\R^{n^2}$ est celui du Fortran, c'est à dire.</br>
colonne par colonne. En Julia, on peut utiliser la fonction vec
</br>
Si on considère la matrice tridiagonale
$$
K_n = \begin{pmatrix}
2 & -1 & {\bf 0} \\
-1 & \ddots & -1 \\
{\bf 0} & -1 & 2 \\
\end{pmatrix},
$$
alors le laplacien 2D peut se construire sous de la façon suivante $\Delta u = -\frac{1}{h^2} K2D_n$ avec
$$ K2D_n = K_n \otimes I_n + I_{n} \otimes K_n. $$K1D
qui prend un entier n
en argument et construit la matrice $K_n$ (indice : on pourra utiliser diagm
ou Tridiagonal
@test norm(diag(K1D(3)) - 2*ones(3)) ≈ 0 atol=1e-12
@test norm(diag(K1D(3),-1)+ones(2)) ≈ 0 atol =1e-12
@test norm(diag(K1D(4),1)+ones(3)) ≈ 0 atol=1e-12
v1D(n)
et val1D(n)
qui calculent respectivement
$$
V^n_{k,l} = \sin \left( \frac{kl\pi}{n+1} \right) \forall (k,l) \in \{1,\cdots,n\}^2 \mbox{ et } \lambda^n_k = 2 - 2 \cos \left( \frac{k\pi}{n+1} \right)
$$
des vecteurs propres de $K_n$ et ses valeurs propres</br>
v1D(n)
renverra une matrice et val1D(n)
un vecteur.</br>
Attention, sin
et sin.
sont differents si on les appliquent à une matrice@test norm(val1D(3)[1]*v1D(3)[:,1]-K1D(3)*v1D(3)[:,1]) ≈ 0 atol=1e-10
n=3; V=v1D(n);
aI=V*V'
@test norm(aI-aI[1,1]*Matrix(I,3,3)) ≈ 0 atol=1e-12
K2D
qui construit $K2D_n$; penser à utiliser kron
v₁₁=vec(v1D(3)[1,:]*v1D(3)[2,:]')
@test norm(K2D(3)*v₁₁-sum(val1D(3)[1:2])*v₁₁) ≈ 0 atol=1e-12
In [18]:
function K1D(n::Int)
return diagm(0 => 2*ones(n), 1 => -ones(n-1), -1 => -ones(n-1))
end
function val1D(n::Int)
return 2 .- 2*cos.((1:n) * π / (n + 1))
end
function v1D(n::Int)
return sin.((1:n)*(1:n)'*π/(n+1))
end
Out[18]:
In [19]:
@test norm(val1D(3)[1]*v1D(3)[:,1]-K1D(3)*v1D(3)[:,1]) ≈ 0 atol=1e-10
n=3; V=v1D(n);
aI=V*V'
@test norm(aI-aI[1,1]*Matrix(I,3,3)) ≈ 0 atol=1e-12
Out[19]:
In [20]:
function K2D(n::Int)
return (kron(K1D(n), Matrix(I,n,n)) + kron(Matrix(I,n,n), K1D(n)))
end
v₁₁=vec(v1D(3)[1,:]*v1D(3)[2,:]')
@test norm(K2D(3)*v₁₁-sum(val1D(3)[1:2])*v₁₁) ≈ 0 atol=1e-12
Out[20]:
Au lieu de considérer $\Delta u$ comme une matrice dense, on peut :
x & \mapsto & K2D(n)x.
\end{eqnarray*}
avec $x = {\mathrm vec}(X)$ que l'on peut calculer grâce à x = vec(X[2:end-1,2:end-1])
,
en admettant que $X$ soit de dimension $n+2$ et contienne des zéros sur le bord.
Cela revient en définitive à calculer une matrice $Y$ de même taille que $X$ telle que
$$
Y_{i,j} = 4 X_{i,j} - X_{i-1,j} - X_{i+1,j} -X_{i,j-1} - X_{i,j+1} \mbox{ pour } i,j=2,\cdots,n+1
$$Pour résoudre un tel système, on fera typiquement appel à une méthode itérative (CG, GMRES, etc..), car le propre de ces méthodes est de ne pas construire explicitement la matrice de l'opérateur mais juste de l'évaluer contre des vecteurs choisis au cours des itérations. Par rapport, aux constructions précédentes, on va considérer que l'objet d'entrée de notre fonction est une matrice carrée d'ordre $n+2$ et qu'elle renverra une matrice de même dimension, dont les indices internes contiennent le laplacien discret 2D de la matrice d'entrée.
K2Dₗₒₒₚ(X)
qui prend en entrée une matrice $X$ d'ordre $n+2$ et
renvoie une matrice du même ordre dont les indices internes ($\{2,\cdots,n+1\}$) contiennent
$K2D(x)$ K2Dₘₐₙₒ
sans boucle (optionel) K2D
N = 5
u=LinRange(0, 1, N)
Xᵢₙ=.√u*u'.^3
Xᵢₙ[1,:] .= 0;Xᵢₙ[end,:] .= 0; Xᵢₙ[:,end] .= 0; Xᵢₙ[:, 1] .= 0;
@test norm(vec(K2Dₛₚₐᵣₛₑ(Xᵢₙ)[2:end-1,2:end-1]) - K2D(N-2)* vec(Xᵢₙ[2:end-1,2:end-1])) ≈ 0 atol=1e-12
In [21]:
function K2Dₘₐₙₒ(X)
H = zeros(size(X))
H[2:end-1, 2:end-1] = 4. * X[2:end-1, 2:end-1] - X[1:end-2, 2:end-1] - X[3:end, 2:end-1] - X[2:end-1, 1:end-2] - X[2:end-1, 3:end]
return H
end
N = 6
u=LinRange(0, 1, N)
Xᵢₙ=.√u*u'.^3
Xᵢₙ[1,:] .= 0;Xᵢₙ[end,:] .= 0; Xᵢₙ[:,end] .= 0; Xᵢₙ[:, 1] .= 0;
@test norm(vec(K2Dₘₐₙₒ(Xᵢₙ)[2:end-1,2:end-1]) - K2D(N-2)* vec(Xᵢₙ[2:end-1,2:end-1])) ≈ 0 atol=1e-12
Out[21]:
In [22]:
function K2Dₗₒₒₚ(X)
m,n = size(X)
H = zeros(m,n)
for j = 2:n-1
for i= 2:m-1
H[i,j] = 4. * X[i,j] - X[i-1,j] - X[i+1, j] - X[i, j+1] - X[i, j-1]
end
end
return H
end
@test norm(vec(K2Dₗₒₒₚ(Xᵢₙ)[2:end-1, 2:end-1])-K2D(N-2)*vec(Xᵢₙ[2:end-1,2:end-1])) ≈ 0 atol = 1e-12
Out[22]:
In [23]:
using SparseArrays
In [24]:
?sparse
Out[24]:
K1Dₖᵣₑᵤₛₑ
- un hommage à ceux qui n'ont pas le revolver - qui implemente K1D sous un forme creux IJV@test norm(Matrix(K1Dₖᵣₑᵤₛₑ(3))-K1D(3)) ≈ 0 atol=1e-12
repeat
de Julia, implémenter
une méthode K2Dₖᵣₑᵤₛₑ
qui construit $K2D_n$ avec le format IJV (indice : @time
In [25]:
function K1Dₖᵣₑᵤₛₑ(n::Int)
V = [2*ones(n); -ones(n-1); -ones(n-1)]
iI = [1:n; 2:n; 1:n-1]
iJ = [1:n; 1:n-1; 2:n]
return sparse(iI, iJ, V)
end
@test norm(Matrix(K1Dₖᵣₑᵤₛₑ(3))-K1D(3)) ≈ 0 atol=1e-12
Out[25]:
In [26]:
function K2Dₖᵣₑᵤₛₑ(n::Int)
B = K1Dₖᵣₑᵤₛₑ(n)
iB,jB,vB = findnz(B)
# blocs centraux
V = repeat([vB; 2*ones(n)],n)
iI = repeat([iB; 1:n], n) + repeat((0:n-1)*n, inner=length(iB)+n)
iJ = repeat([jB; 1:n], n) + repeat((0:n-1)*n, inner=length(jB)+n)
# blocs sous diagonaux
lV = repeat(-ones(n), n-1)
lI = repeat(1:n, n-1) + repeat((1:n-1) * n, inner=n)
lJ = repeat(1:n, n-1) + repeat((0:n-2) * n, inner=n)
# blocs sur diagonaux
uI = repeat(1:n, n-1) + repeat((0:n-2) * n, inner=n)
uJ = repeat(1:n, n-1) + repeat((1:n-1) * n, inner=n)
iI = [iI; uI; lI ]
iJ = [iJ; uJ; lJ]
V = [V ; lV ; lV]
return sparse(iI, iJ, V)
end
@test norm(Matrix(K2Dₖᵣₑᵤₛₑ(3))-K2D(3)) ≈ 0 atol=1e-12
Out[26]:
In [39]:
N = [10, 20, 50, 100]
res_creation = zeros(length(N), 4)
res_execution = zeros(length(N), 4)
for (i, n) in enumerate(N)
K1 , res_creation[i,1] = @timed K2D(n)
K4 , res_creation[i,4] = @timed K2Dₖᵣₑᵤₛₑ(n)
X = rand(n,n)
x = vec(X)
val, res_execution[i, 1] = @timed K1*x
val, res_execution[i, 2] = @timed K2Dₘₐₙₒ(X)
val, res_execution[i, 3] = @timed K2Dₗₒₒₚ(X)
val, res_execution[i, 4] = @timed K4*x
end
In [40]:
res_execution
Out[40]:
In [41]:
using PyPlot
plot(N, res_creation[:,1], "r-", N, res_creation[:,4],"g-");
In [47]:
#plot(N, res_execution[:,1], "r-", N, res_execution[:,2], "b-", N, res_execution[:,3], "b+", N, res_execution[:,4],"g-");
plot(N[2:end],res_execution[2:end,2], "b-", N[2:end], res_execution[2:end,3], "p-", N[2:end], res_execution[2:end,4],"g-");
Pour des valeurs de N plus grand, la première méthode K2D
dépassant rapidement la mémoire on peut se contenter de comparer
uniquement les deux autres méthodes : pour évaluer le compromis temps de création vs temps d'execution, on pourra
tracer par exemple 100 * tps_execution + tps_creation
pour K2Dₖᵣₑᵤₛₑ
contre 100 * tps_execution
pour K2Dₗₒₒₚ
In [48]:
N = [100, 500, 1000]
res_creation = zeros(length(N), 2)
res_execution = zeros(length(N), 2)
for (i, n) in enumerate(N)
K , res_creation[i,2] = @timed K2Dₖᵣₑᵤₛₑ(n)
X = rand(n,n)
x = vec(X)
val, res_execution[i, 1] = @timed K2Dₗₒₒₚ(X)
val, res_execution[i, 2] = @timed K*x
end
In [49]:
plot(N, 100 * res_execution[:,1], "b-", N, 100*res_execution[:,2]+res_creation[:,2], "g-");
Maintenant que le laplacien 2D est implémenté on peut résoudre numériquement l'équation de la chaleur avec un schéma
d'Euler explicite en écrivant
$$
U_{n+1} = U_n - \frac{\delta_t}{h^2} K2D_n U_n
$$
on prendra $h = 1 / N$ et $\delta_t = h^2/4$
Pour vérifier que le processus d'itération s'arretera bien on pourra comparer l'erreur
$$
e = \sqrt{\sum_{i,j} \left(U_{n+1}(i,j) - U_{n}(i,j)\right)^2}
$$
avec une précision donnée $\varepsilon = 10^{-4}$
Uₙ
avec des zéros partout, sauf sur le bord (on mettra des 1.)
In [32]:
function meshGrid(x,y)
xx = repeat(x, outer=length(y))
yy = repeat(y, inner=length(x))
XX = reshape(xx, length(x), length(y))
YY = reshape(yy, length(x), length(y))
return XX,YY
end
Out[32]:
In [33]:
using Printf
N = 5
Uₙ = zeros(N+2,N+2)
h = 1 / N
Uₙ[1, :] .= 1.;
Uₙ[:, 1] .= 1.;
Uₙ[end, :] .= 1.;
Uₙ[:, end] .= 1.;
Uₙ₊₁ = copy(Uₙ)
t = 0;
tEnd = 10.
prec = 1e-4
err = 2 * prec
itMax = 1000
it = 0
δₜ = h^2/4.
x=LinRange(0,1, N+2)
y=LinRange(0,1, N+2)
XX, YY = meshGrid(x,y)
fig = figure()
surf(XX,YY,Uₙ)
ax = Axes3D(fig)
ax[:set_zlim3d](bottom=0., top=1.)
while t < tEnd && it < itMax && err > prec
Uₙ₊₁ -= K2Dₗₒₒₚ(Uₙ) * δₜ / h.^2
err = √sum((Uₙ₊₁-Uₙ).^2)
it += 1
Uₙ = Uₙ₊₁
ax[:clear]()
surf(XX, YY, Uₙ);
ax[:set_zlim3d](bottom=0., top=1.);
savefig(@sprintf("hehe_%04d",it));
end
println("it = ", it, " err = ", √sum((Uₙ-ones(N+2,N+2)).^2))
In [50]:
using Base64
function showgif(filename)
open(filename) do f
base64_video = base64encode(f)
display("text/html", """<img src="data:image/gif;base64,$base64_video">""")
end
end
run(`magick -delay 20 -loop 0 hehe_\*.png heat.gif`)
showgif("heat.gif")