Semaine 1 : Introduction à Julia¶
using Plots
using LaTeXStrings
Comprendre la sortie des cellules Jupyter¶
- La sortie d'une série de commandes Julia est la dernière expression :
# Jupyter will display the result of `a + b` after running the cell
a = 5
b = 4
c = a + b
9
- L'utilisation de
;en fin de commande supprime l'affichage
# Jupyter will not display anything
a = 5
b = 4
a + b;
- Cela s'applique aussi aux graphiques. Comparer les deux situations suivantes :
plot(cos)
plot(sin);
- On peut aussi utiliser
@showouprint:
a = 5
b = 4
@show a
println(b)
a + b
a = 5
4
9
Manipulation de chaînes de caractères¶
- Les chaînes de caractères sont définies en Julia entre guillemets doubles
". Contrairement à Python, les guillemets simples'ne sont utilisés que pour les caractères simples.
s = "Hello world!"
"Hello world!"
'o' ∈ s # ∈ is an alias for in
true
- Contrairement à de nombreux langages qui utilisent
+, Julia utilise*pour la concaténation de chaînes. L'idée est de souligner la non-commutativité de cette opération
s1 = "Hello " ; s2 = "world!"
s = s1 * s2
println(s)
Hello world!
print(s1^5)
Hello Hello Hello Hello Hello
- L'interpolation dans une chaîne s'obtient avec
$
col = "red" ; x = 20_000 ; discount = 5
s = "My $col car cost $(x*(1-discount/100)) €"
print(s)
My red car cost 19000.0 €
s = "Hello world!"
ordinal_suffix(i) = i == 1 ? "st" : i == 2 ? "nd" : "th"
for (i, c) ∈ enumerate(s)
println("The character at $i$(ordinal_suffix(i)) place is $c")
end
The character at 1st place is H The character at 2nd place is e The character at 3th place is l The character at 4th place is l The character at 5th place is o The character at 6th place is The character at 7th place is w The character at 8th place is o The character at 9th place is r The character at 10th place is l The character at 11th place is d The character at 12th place is !
Notez que les chaînes LaTeX peuvent être construites grâce au package LaTeXStrings, importé ci-dessus et utilisé plus loin dans ce notebook.
Utilisation et définition de fonctions¶
[Exercice 1]¶
Écrire une fonction qui retourne la somme des n premiers entiers au carré en utilisant une boucle for.
Indices :
- lire la documentation de
function(ouvrir un REPL et taper?suivi du mot recherché, icifunction),- un intervalle d'entiers de
màns'écrit simplementm:n(contrairement à Python, les bornes sont incluses dans l'intervalle).
function sum_of_squares(n)
### BEGIN SOLUTION
result = 0
for i in 1:n
result += i*i
end
return result
### END SOLUTION
end;
@assert sum_of_squares(1) == 1 "❌"
@assert sum_of_squares(2) == 5 "❌"
@assert sum_of_squares(5) == 55 "❌"
@assert sum_of_squares(10) == 385 "❌"
Remarques :
- La même fonction peut s'écrire sous forme compacte
short_sum_of_squares(n) = (1:n)'*(1:n)
@show short_sum_of_squares(2)
@show short_sum_of_squares(4);
short_sum_of_squares(2) = 5 short_sum_of_squares(4) = 30
- Lire la documentation de la fonction
sumpour comprendre pourquoi le code suivant fonctionne aussi.
other_sum_of_squares(n) = sum(x -> x^2, 1:n)
@show other_sum_of_squares(2)
@show other_sum_of_squares(4);
other_sum_of_squares(2) = 5
other_sum_of_squares(4) = 30
- La fonction peut être appliquée élément par élément à un tableau en utilisant l'opérateur de diffusion
.
@show short_sum_of_squares.([2, 4, 5])
# The above command is syntactic sugar for
@show broadcast(short_sum_of_squares, [2, 4, 5]);
short_sum_of_squares.([2, 4, 5]) = [5, 30, 55] broadcast(short_sum_of_squares, [2, 4, 5]) = [5, 30, 55]
- La diffusion fonctionne même pour des fonctions à plusieurs arguments
geometric_mean(a, b) = √(a*b)
@show geometric_mean.(2, [8, 18]);
geometric_mean.(2, [8, 18]) = [4.0, 6.0]
- Il est parfois commode de définir des fonctions anonymes avec la notation
->:
# Plot the sum of cubes for different n
scatter(n -> sum(x -> x^3, 1:n), 1:5, label="sum of cubes")
[Exercice 2]¶
La méthode babylonienne pour calculer la racine carrée de $a > 0$ est donnée par l'itération $$ \forall k\in\mathbb{N},\qquad x_{k+1}=\frac{1}{2}\left(x_k+\frac{a}{x_k}\right) $$
Écrire une fonction my_sqrt(a, x₀) qui retourne une approximation de la racine carrée de a par 10 itérations de cette méthode, en partant de x₀.
function my_sqrt(a, x₀)
### BEGIN SOLUTION
x = x₀
for i in 1:10
x = (x + a/x)/2
end
return x
### END SOLUTION
end;
@assert my_sqrt(4, 1) ≈ 2 "❌"
@assert my_sqrt(144, 1) ≈ 12 "❌"
Tracé de courbes et création d'animations¶
- La fonction
plotpeut être appelée de différentes façons. Une première approche consiste à lui passer une fonction :
f(x) = cos(x) * exp(-abs(x))
plot(f, xlims=(-5, 5))
- Une autre option, souvent plus flexible, est de lui passer des tableaux $x$ et $y$ :
f(x) = cos(x) * exp(-abs(x))
x = -5:.01:5
plot(x, f.(x))
- Utiliser les commandes suivies de
!pour modifier un graphique existant.
plot(cos, label = "f1")
plot!(sin, label = "f2")
xlims!(-5, 5)
title!("title")
xlabel!("x")
ylabel!("y")
- Les options peuvent être regroupées dans un appel à
plot!:
plot(cos, label = "f1")
plot!(sin, label = "f2")
plot!(xlims = (-5, 5), title = "title", xlabel = "x", ylabel = "y")
- Les animations peuvent être créées de la façon suivante :
anim = Animation()
for θ in LinRange(0, 2π, 50)
plot(x -> cos(x - θ), label=nothing)
title!("θ = $(round(θ, digits=3))")
frame(anim)
end
gif(anim, fps=10)
[ Info: Saved animation to /home/urbain/work/teaching/casc_private/notebooks/2026/source/week1/tmp.gif
- La bibliothèque
Plotsdéfinit la macro@animatepour simplifier la création d'animations :
anim = @animate for θ in LinRange(0, 2π, 50)
plot(x -> cos(x - θ), label=nothing)
title!("θ = $(round(θ, digits=3))")
end
gif(anim, fps=10)
[ Info: Saved animation to /home/urbain/work/teaching/casc_private/notebooks/2026/source/week1/tmp.gif
[Exercice 3]¶
Créer une animation illustrant le mouvement d'une balle de golf dont la position dans le plan $x,y$ est donnée par
$$
\begin{aligned}
x(t) &= u_0 t, \\
y(t) &= v_0 t - g \frac{t^2}{2},
\end{aligned}
$$
Utiliser les paramètres fournis ainsi que la fonction scatter,
et rendre l'animation aussi soignée que possible.
u₀ = 50
v₀ = 50
g = 9.81
### BEGIN SOLUTION
X(t) = u₀*t
Y(t) = v₀*t - g*t^2/2
tf = 2v₀/g
nframes = 100
anim = @animate for t in LinRange(0, tf, nframes)
scatter([X(t)], [Y(t)], label=nothing, markersize=10)
title!("t = $(round(t, digits=3))")
xlims!(0, X(tf))
ylims!(0, Y(tf/2) * 1.1)
end
gif(anim, fps=nframes/tf)
### END SOLUTION
[ Info: Saved animation to /home/urbain/work/teaching/casc_private/notebooks/2026/source/week1/tmp.gif
Formats à virgule flottante¶
Seul un nombre fini de réels peut être représenté exactement sur un ordinateur. Par conséquent, certains réels sont représentables exactement et d'autres non. La norme IEEE 754, à laquelle se conforment pratiquement tous les langages de programmation, spécifie un petit nombre de formats à virgule flottante. L'ensemble des réels représentables dans chaque format est de la forme $$ \begin{align*} F(p, E_{\min}, E_{\max}) = \Bigl\{ & (-1)^s 2^E (b_0. b_1 b_2 \dots b_{p-1})_2 \colon \\ & \qquad s \in \{0, 1\}, b_i \in \{0, 1\} \, \text{et} \, E_{\min} \leq E \leq E_{\max} \Bigr\}. \end{align*} $$ Ici $(b_0.b_1 b_2 \dots b_{p-1})_2$ est un nombre en représentation binaire, c'est-à-dire $$ (b_0.b_1 b_2 \dots b_{p-1})_2 = b_0 + \frac{1}{2} b_1 + \dotsc + \frac{1}{2^{p-1}} b_{p-1}. $$ Pour un élément $x \in F(p, E_{\min}, E_{\max})$, $(b_0. b_1 b_2 \dots b_{p-1})_2$ est appelée la mantisse, $s$ est le signe et $E$ est l'exposant. Trois paramètres apparaissent dans la définition de l'ensemble ci-dessus :
- $p$ est le nombre de bits significatifs (appelé aussi la précision),
- $E_{\min}$ est l'exposant minimum,
- $E_{\max}$ est l'exposant maximum.
À partir de la précision, l'epsilon machine associé au format est défini comme $$ \varepsilon = 2^{-(p-1)} $$
Les paramètres des formats les plus couramment utilisés spécifiés par la norme sont les suivants :
| Paramètre | Demi-précision (Float16) |
Simple précision (Float32) |
Double précision (Float64) |
|---|---|---|---|
| $p$ | 11 | 24 | 53 |
| $E_{\min}$ | -14 | -126 | -1022 |
| $E_{\max}$ | 15 | 127 | 1023 |
En Julia, les formats à virgule flottante demi, simple et double précision sont donnés par
Float16, Float32 et Float64, le format par défaut étant Float64.
a = √2
b = Float32(√2)
c = Float16(√2)
@show a
@show b
@show c
@show typeof(a)
@show typeof(b)
@show typeof(c);
a = 1.4142135623730951
b = 1.4142135f0 c = Float16(1.414) typeof(a) = Float64 typeof(b) = Float32 typeof(c) = Float16
L'epsilon machine doit être compris comme une mesure de l'espacement relatif entre un nombre à virgule flottante et le suivant, au sein d'un format. En effet, le nombre 1 est représentable exactement dans tous les formats, et le prochain nombre représentable est précisément $1 + \varepsilon$
a = Float16(1.0)
@show nextfloat(a) - a
@show eps(Float16);
nextfloat(a) - a = Float16(0.000977) eps(Float16) = Float16(0.000977)
De même, le prochain nombre représentable après $2$ est $2 + 2\varepsilon$ et le prochain nombre représentable après $4$ est $4 + 4\varepsilon$ :
ε = eps(Float64)
@show nextfloat(2.0) - 2.0
@show 2ε;
@show nextfloat(4.0) - 4.0
@show 4ε;
nextfloat(2.0) - 2.0 = 4.440892098500626e-16 2ε = 4.440892098500626e-16 nextfloat(4.0) - 4.0 = 8.881784197001252e-16 4ε = 8.881784197001252e-16
Comme le suggèrent leurs noms,
un nombre Float16 est stocké sur 16 bits,
un Float32 sur 32 bits,
et un nombre Float64 sur 64 bits.
Parmi ces bits, un est utilisé pour stocker le signe,
et les autres pour stocker la mantisse et l'exposant.
De plus, quelques combinaisons de bits sont réservées pour représenter les valeurs spéciales
Inf, -Inf et NaN, ce dernier étant l'acronyme de « not a number » (pas un nombre).
Un octet, abrégé par un B majuscule, est une unité composée de 8 bits.
C'est l'unité généralement utilisée pour décrire la quantité de mémoire disponible dans les produits commerciaux ;
par exemple, un ordinateur portable dispose typiquement de 8 Go ou 16 Go de mémoire vive.
Il est souvent utile d'avoir une idée approximative de l'espace occupé en mémoire par les objets mathématiques.
Par exemple, une matrice 1000 × 1000 de nombres Float64 nécessite $8 \times 10^6$ octets pour être stockée,
et une matrice 10000 × 10000 de nombres Float64 requiert près de 800 Mo de mémoire vive.
A = zeros(10_000, 10_000)
@show Base.summarysize(A);
Base.summarysize(A) = 800000048
Pour approfondir notre compréhension des formats à virgule flottante,
traçons sur un même graphique l'espacement entre
les nombres successifs Float16, Float32 et Float64
dans l'intervalle $[1, 10^4]$. On constate que la densité de
nombres à virgule flottante représentables autour de $x$ diminue
avec $x$.
n = 1000
x_16 = (^).(10, LinRange{Float16}(0, 4, n))
x_32 = (^).(10, LinRange{Float32}(0, 4, n))
x_64 = (^).(10, LinRange{Float64}(0, 4, n))
spacing16 = nextfloat.(x_16) - x_16
spacing32 = nextfloat.(x_32) - x_32
spacing64 = nextfloat.(x_64) - x_64
plot(x_16, spacing16, label="Float16", )
plot!(x_32, spacing32, label="Float32")
plot!(x_64, spacing64, label="Float64")
plot!(xscale=:log10, yscale=:log10,
xlabel="x", ylabel="Δx", legend=:bottomright)
[Exercice 4]¶
Pour approfondir votre compréhension,
- Lire la documentation de la fonction
nextfloat. - En utilisant cette fonction,
tracer l'espacement relatif, normalisé par l'epsilon machine du format
Float32, entre les nombresFloat32successifs dans l'intervalle $[10^{-2}, 10^2]$. Plus précisément, tracer la fonction $$ x \mapsto \frac{\text{nextfloat}(x) - x}{x \varepsilon} $$
Utiliser une échelle logarithmique pour l'axe $x$ uniquement.
Il peut être utile d'utiliser LinRange{Type}(a, b, n)
pour créer un vecteur de $n$ nombres de type Type équidistants entre $a$ et $b$.
### BEGIN SOLUTION
x = (^).(10, LinRange{Float32}(-2, 2, 10000))
spacing = (nextfloat.(x) - x) ./ (x*eps(Float32))
plot(x, spacing, label="Relative spacing")
plot!(xscale=:log2, xlabel="x", ylabel="Δx/x (in ε units)")
### END SOLUTION
Abordons maintenant une question naturelle découlant de cette discussion : que se passe-t-il si le résultat d'une opération mathématique n'est pas exactement représentable dans le format à virgule flottante ? La réponse est simple : le résultat est arrondi au nombre représentable le plus proche. Cela explique les résultats surprenants suivants :
@show 1 + eps()/3 # the result is 1
@show 1 + 2*eps()/3 # the result is 1 + ε
@show .1 + .2 == .3 # this is False
1 + eps() / 3 = 1.0 1 + (2 * eps()) / 3 = 1.0000000000000002 0.1 + 0.2 == 0.3 = false
false
Les erreurs causées par ce processus d'arrondi sont appelées erreurs d'arrondi. Elles sont particulièrement importantes pour le calcul des dérivées. En effet, supposons que l'on souhaite calculer, pour une fonction différentiable $f\colon \mathbb R \to \mathbb R$, une approximation de $f'(1)$. L'approche la plus naturelle est de calculer pour un petit $\delta > 0$ $$ d(\delta) = \frac{f(1+ \delta) - f(1)}{\delta}. $$ En arithmétique exacte, cela fournirait une approximation de $f'(1)$ avec une erreur en $\mathcal O(\delta)$, en supposant $f''(1) \neq 0$. En arithmétique d'ordinateur, cependant, une erreur supplémentaire apparaît en raison des arrondis, dont on peut montrer qu'elle est de l'ordre de $\mathcal O(\varepsilon/\delta)$. Le meilleur choix est de prendre $\delta \approx \sqrt{\varepsilon}$, de sorte que les erreurs soient approximativement du même ordre de grandeur.
f(x) = exp(x)
d(δ) = (f(1+δ) - f(1))/δ
δs = 10 .^(0:-.1:-17)
err = abs.(d.(δs) .- exp(1))
plot(δs, err, xscale=:log10, yscale=:log10, label=L"|d(\delta) - f'(1)|", size=(900,450))
plot!([eps()], seriestype=:vline, label = L"\varepsilon")
plot!([sqrt(eps())], seriestype=:vline, label = L"\sqrt{\varepsilon}")
Pour des $\delta$ très petits de l'ordre de l'epsilon machine $\varepsilon$, l'approximation obtenue est complètement fausse. Comprenez-vous les observations suivantes ?
δ = eps()/4; @show (exp(δ) - exp(0)) / δ
δ = eps()/2; @show (exp(δ) - exp(0)) / δ
δ = eps(); @show (exp(δ) - exp(0)) / δ;
(exp(δ) - exp(0)) / δ = 0.0 (exp(δ) - exp(0)) / δ = 2.0 (exp(δ) - exp(0)) / δ = 1.0
Remarque. Dans de nombreux contextes, il est utile d'utiliser des nombres à virgule flottante avec plus de précision que les types standard,
afin de réduire les erreurs d'arrondi et d'observer l'erreur intrinsèque,
liée au caractère inexact de nombreux algorithmes même en arithmétique exacte.
Le type BigFloat en Julia est un format à précision arbitraire.
Le nombre de bits significatifs correspondant peut être défini à l'aide de la fonction setprecision.
@show Float64(π)
setprecision(100, base=10) # We want 100 digits in base 10
BigFloat(π)
Float64(π) = 3.141592653589793
3.14159265358979323846264338327950288419716939937510582097494459230781640628620899862803482534211706807
Manipulation des tableaux¶
- Définition explicite simple
u = [1, 2, 3] # defines a column Vector
display(u)
@show typeof(u)
3-element Vector{Int64}:
1
2
3
typeof(u) = Vector{Int64}
Vector{Int64} (alias for Array{Int64, 1})
- Remarquer la différence avec
u = [1 2 3] # defines a Matrix of 1 line
display(u)
@show typeof(u)
1×3 Matrix{Int64}:
1 2 3
typeof(u) = Matrix{Int64}
Matrix{Int64} (alias for Array{Int64, 2})
- Définition explicite d'une matrice
A = [1 2 3
4 5 6
7 8 9]
3×3 Matrix{Int64}:
1 2 3
4 5 6
7 8 9
A = [1 2 3; 4 5 6; 7 8 9]
3×3 Matrix{Int64}:
1 2 3
4 5 6
7 8 9
- Extraction d'une colonne ou d'une ligne
Note : en
Julia, les indices commencent à1comme enFortran(et non à0comme enPythonouC)
A[1,:]
3-element Vector{Int64}:
1
2
3
A[:,1]
3-element Vector{Int64}:
1
4
7
:est un raccourci pourbegin:endou1:end
A[2,1:end]
3-element Vector{Int64}:
4
5
6
A[begin:end,2]
3-element Vector{Int64}:
2
5
8
- Construction d'un tableau par syntaxe de compréhension
A = [10i+j for i in 0:2, j in 0:3]
display(A)
print("\nA = $A\n\n")
@show A
@show A[1,1]
@show A[1,2]
@show length(A)
@show size(A)
@show size(A, 1)
@show size(A, 2)
@show eltype(A)
;
3×4 Matrix{Int64}:
0 1 2 3
10 11 12 13
20 21 22 23
A = [0 1 2 3; 10 11 12 13; 20 21 22 23] A =
[0 1 2 3; 10 11 12 13; 20 21 22 23] A[1, 1] = 0 A[1, 2] = 1 length(A) = 12 size(A) = (3, 4) size(A, 1) = 3 size(A, 2) = 4 eltype(A) = Int64
- Matrice adjointe
A = [1 2 3
4 5 6]
A'
3×2 adjoint(::Matrix{Int64}) with eltype Int64:
1 4
2 5
3 6
display(A'A)
display(A*A')
3×3 Matrix{Int64}:
17 22 27
22 29 36
27 36 45
2×2 Matrix{Int64}:
14 32
32 77
A = [2+3im 4+2im 1+3im
1+2im 2+3im 5+6im]
A'
3×2 adjoint(::Matrix{Complex{Int64}}) with eltype Complex{Int64}:
2-3im 1-2im
4-2im 2-3im
1-3im 5-6im
display(A'A)
display(A*A')
3×3 Matrix{Complex{Int64}}:
18+0im 22-9im 28-1im
22+9im 33+0im 38+7im
28+1im 38-7im 71+0im
2×2 Matrix{Complex{Int64}}:
43+0im 45+0im
45+0im 79+0im
- Concaténation de tableaux
A = [1 2 3
4 5 6]
u = [10, 20, 30]
v = [100, 200]
;
[A v]
2×4 Matrix{Int64}:
1 2 3 100
4 5 6 200
[A; u']
3×3 Matrix{Int64}:
1 2 3
4 5 6
10 20 30
[A A]
2×6 Matrix{Int64}:
1 2 3 1 2 3
4 5 6 4 5 6
[A; A]
4×3 Matrix{Int64}:
1 2 3
4 5 6
1 2 3
4 5 6
hcat(A, A, A)
2×9 Matrix{Int64}:
1 2 3 1 2 3 1 2 3
4 5 6 4 5 6 4 5 6
vcat(A, A, A)
6×3 Matrix{Int64}:
1 2 3
4 5 6
1 2 3
4 5 6
1 2 3
4 5 6
- Les composantes sont stockées par colonnes et peuvent être accédées par un indice simple même pour des tableaux multidimensionnels
eachindexetCartesianIndicespermettent d'itérer sur tous les indices d'un tableau (indice simple pour le premier, multi-indice pour le second)- Un
CartesianIndexpeut être converti enTuple ...est l'opérateur splat (voir la documentation)
A = [1 2 3; 4 5 6]
for k ∈ eachindex(A)
println("A[$k] = ", A[k])
end
println()
for k ∈ CartesianIndices(A)
println("A[$k] = A$(Tuple(k)...) = ", A[k])
end
println()
for i in 1:size(A, 1)
for j in 1:size(A, 2)
println("A[$i, $j] = ", A[i, j])
end
end
println()
for i in 1:size(A, 1), j in 1:size(A, 2)
println("A[$i, $j] = ", A[i, j])
end
A[1] = 1
A[2] = 4 A[3] = 2 A[4] = 5 A[5] = 3 A[6] = 6 A[CartesianIndex(1, 1)] = A11 = 1 A[CartesianIndex(2, 1)] = A21 = 4 A[CartesianIndex(1, 2)] = A12 = 2 A[CartesianIndex(2, 2)] = A22 = 5 A[CartesianIndex(1, 3)] = A13 = 3 A[CartesianIndex(2, 3)] = A23 = 6 A[1, 1] = 1
A[1, 2] = 2 A[1, 3] = 3 A[2, 1] = 4 A[2, 2] = 5 A[2, 3] = 6 A[1, 1] = 1 A[1, 2] = 2 A[1, 3] = 3 A[2, 1] = 4 A[2, 2] = 5 A[2, 3] = 6
- Une matrice (tableau d'ordre 2) n'est pas un vecteur de vecteurs
B = [[10i+j for i in 0:2] for j in 0:3]
display(B)
@show B isa Matrix
;
4-element Vector{Vector{Int64}}:
[0, 10, 20]
[1, 11, 21]
[2, 12, 22]
[3, 13, 23]
B isa Matrix = false
- Les tableaux sont mutables (si le type est cohérent), les tuples ne le sont pas !
v = [1, 2]
@show typeof(v)
@show eltype(v)
@show v
v[1] = 3
@show v
# v[1] = 1.5 # error since v is of type Vector{Int64}
# @show v
t = (1, 2)
@show t[1] ;
# t[1] = 3 # error since t is not mutable
typeof(v) = Vector{Int64}
eltype(v) = Int64
v = [1, 2]
v = [3, 2]
t[1] = 1
- Définition d'un tableau vide et construction a posteriori
Remarquer la différence entre
push!etappend!(il est rappelé que pour les deux,!exprime que par convention, et non par obligation, l'argument est modifié)
v = [] # void vector containing any type (not optimal!)
@show typeof(v)
push!(v, 1)
append!(v, [2.5, 3.6]) # pushes each item of the vector [2.5, 3.6]
@show v
push!(v, [4.1, 5.2]) # pushes a vector as new item
@show v ;
@show v[end]
@show v[end-1] ;
typeof(v) = Vector{Any}
v =
Any[1, 2.5, 3.6] v = Any[1, 2.5, 3.6, [4.1, 5.2]] v[end] = [4.1, 5.2] v[end - 1] = 3.6
- Pour de meilleures performances, il est préférable, si possible, de préciser le type des éléments du vecteur lors de la construction
v = Float64[]
@show typeof(v)
push!(v, 1)
append!(v, [2//3, π]) # pushes each item of the vector [2//3, π] and convert on-the-fly to Float64
@show v
# push!(v, [4.3, 5.7]) # throws an error since [4.3, 5.7] is not of type Float64
;
typeof(v) = Vector{Float64}
v = [1.0, 0.6666666666666666, 3.141592653589793]
- Référence d'un tableau
A = [10i+j for i in 1:2, j in 1:3]
@show A
B = A # B is a reference to A (both point to the same memory location)
@show B == A # same content
@show B === A # same memory location
B .= 99 # .= assigns all the coefficients of the array to 99
@show B
@show A # A is of course also affected
;
A = [11 12 13; 21 22 23] B == A = true B === A = true B = [99 99 99; 99 99 99] A = [99 99 99; 99 99 99]
- Copie d'un tableau
A = [10i+j for i in 1:2, j in 1:3]
@show A
B = copy(A) # B and A are two distinct arrays
@show B == A # same content
@show B === A # not the same memory location
B .= 99 # .= assigns all the coefficients of the array to 99
@show B
@show A # A unaffected
;
A = [11 12 13; 21 22 23] B == A = true B === A = false B = [99 99 99; 99 99 99] A = [11 12 13; 21 22 23]
- Tranche en tant que l-value ≡ référence
A = [10i+j for i in 1:6, j in 1:8]
display(A)
A[1:2:end, 2:2:end] .= 0 # components of odd line and even column are set to 0
display(A)
;
6×8 Matrix{Int64}:
11 12 13 14 15 16 17 18
21 22 23 24 25 26 27 28
31 32 33 34 35 36 37 38
41 42 43 44 45 46 47 48
51 52 53 54 55 56 57 58
61 62 63 64 65 66 67 68
6×8 Matrix{Int64}:
11 0 13 0 15 0 17 0
21 22 23 24 25 26 27 28
31 0 33 0 35 0 37 0
41 42 43 44 45 46 47 48
51 0 53 0 55 0 57 0
61 62 63 64 65 66 67 68
- Tranche en tant que r-value ≡ copie
A = [10i+j for i in 1:6, j in 1:8]
display(A)
B = A[1:2:end, 2:2:end] # B is a copy of the submatrix
display(B)
B .= 0
display(B)
display(A) # A remains unaffected
;
6×8 Matrix{Int64}:
11 12 13 14 15 16 17 18
21 22 23 24 25 26 27 28
31 32 33 34 35 36 37 38
41 42 43 44 45 46 47 48
51 52 53 54 55 56 57 58
61 62 63 64 65 66 67 68
3×4 Matrix{Int64}:
12 14 16 18
32 34 36 38
52 54 56 58
3×4 Matrix{Int64}:
0 0 0 0
0 0 0 0
0 0 0 0
6×8 Matrix{Int64}:
11 12 13 14 15 16 17 18
21 22 23 24 25 26 27 28
31 32 33 34 35 36 37 38
41 42 43 44 45 46 47 48
51 52 53 54 55 56 57 58
61 62 63 64 65 66 67 68
- Référence en tant que r-value via
view
A = [10i+j for i in 1:6, j in 1:8]
display(A)
B = view(A, 1:2:size(A,1), 2:2:size(A,2)) # B is a view of the submatrix (reference not a copy)
display(B)
B .= 0
display(B)
display(A) # A is modified
;
6×8 Matrix{Int64}:
11 12 13 14 15 16 17 18
21 22 23 24 25 26 27 28
31 32 33 34 35 36 37 38
41 42 43 44 45 46 47 48
51 52 53 54 55 56 57 58
61 62 63 64 65 66 67 68
3×4 view(::Matrix{Int64}, 1:2:5, 2:2:8) with eltype Int64:
12 14 16 18
32 34 36 38
52 54 56 58
3×4 view(::Matrix{Int64}, 1:2:5, 2:2:8) with eltype Int64:
0 0 0 0
0 0 0 0
0 0 0 0
6×8 Matrix{Int64}:
11 0 13 0 15 0 17 0
21 22 23 24 25 26 27 28
31 0 33 0 35 0 37 0
41 42 43 44 45 46 47 48
51 0 53 0 55 0 57 0
61 62 63 64 65 66 67 68
- Autres façons de définir des tranches par sélection d'indices
A = [10i+j for i in 1:6, j in 1:8]
A[isodd.(1:end), iseven.(1:end)] .= 0 ; display(A)
A = [10i+j for i in 1:6, j in 1:8]
A[findall(isodd,A)] .= 0 ; display(A) ;
6×8 Matrix{Int64}:
11 0 13 0 15 0 17 0
21 22 23 24 25 26 27 28
31 0 33 0 35 0 37 0
41 42 43 44 45 46 47 48
51 0 53 0 55 0 57 0
61 62 63 64 65 66 67 68
6×8 Matrix{Int64}:
0 12 0 14 0 16 0 18
0 22 0 24 0 26 0 28
0 32 0 34 0 36 0 38
0 42 0 44 0 46 0 48
0 52 0 54 0 56 0 58
0 62 0 64 0 66 0 68
- Expliquer les lignes suivantes
A = [10i+j for i in 1:6, j in 1:8]
B = view(A, collect(1:size(A,1)) .% 3 .== 0, collect(1:size(A,2)) .% 5 .!= 0)
B .= 0
A
6×8 Matrix{Int64}:
11 12 13 14 15 16 17 18
21 22 23 24 25 26 27 28
0 0 0 0 35 0 0 0
41 42 43 44 45 46 47 48
51 52 53 54 55 56 57 58
0 0 0 0 65 0 0 0
A = rand(5, 5) ; display(A)
A[findall(x -> x < 1/2, A)] .= 0
A
5×5 Matrix{Float64}:
0.629737 0.48251 0.697925 0.68341 0.902767
0.970592 0.425703 0.938402 0.512732 0.391903
0.572262 0.276557 0.880927 0.00621013 0.289465
0.651025 0.687269 0.978548 0.571009 0.562262
0.157937 0.27258 0.713588 0.376401 0.0146206
5×5 Matrix{Float64}:
0.629737 0.0 0.697925 0.68341 0.902767
0.970592 0.0 0.938402 0.512732 0.0
0.572262 0.0 0.880927 0.0 0.0
0.651025 0.687269 0.978548 0.571009 0.562262
0.0 0.0 0.713588 0.0 0.0
n, m = 7, 9
B = rand(n, m)
u = rand(m)
display(B*u)
subi = collect(1:n) .% 3 .!= 0 ; @show subi
subj = collect(1:m) .% 3 .== 0 ; @show subj
B[subi,subj]*u[subj]
7-element Vector{Float64}:
2.1784480846131173
2.2422262996043503
2.0366621622328287
2.0622759962259187
2.2764233260826416
1.8064111486377548
0.927613589982037
subi = Bool[1, 1, 0, 1, 1, 0, 1]
subj = Bool[0, 0, 1, 0, 0, 1, 0, 0, 1]
5-element Vector{Float64}:
0.6338022054600351
0.7072326593247753
0.8209544084710862
0.3885537023137302
0.2351861237828015
[Exercice 5]¶
Écrire une fonction qui calcule la comatrice d'une matrice explicitement à partir du déterminant de sous-matrices.
On rappelle que la composante $C_{ij}$ de la comatrice de $A$ est obtenue comme le déterminant de la sous-matrice obtenue à partir de $A$ en supprimant sa $i^\textrm{ème}$ ligne et sa $j^\textrm{ème}$ colonne, multiplié par $(-1)^{i+j}.$ Le déterminant d'une petite matrice dense est calculé par la fonction det disponible dans la bibliothèque LinearAlgebra.
using LinearAlgebra
function comatrix(A)
C = zero(A)
### BEGIN SOLUTION
for ij ∈ CartesianIndices(A)
i, j = Tuple(ij)
C[ij] = (-1)^(i+j)*det(A[1:end .!= i, 1:end .!= j])
end
### END SOLUTION
return C
end
comatrix (generic function with 1 method)
n = 10
A = rand(n, n)
C = comatrix(A)
display(C'A)
@assert C'A ≈ det(A)*LinearAlgebra.I
10×10 Matrix{Float64}:
-0.0118234 1.07504e-17 -7.68942e-18 … 4.51631e-18 -2.87112e-18
-1.22014e-18 -0.0118234 -1.72461e-18 -1.94408e-18 -1.4214e-18
-2.7005e-18 -8.47232e-19 -0.0118234 1.18278e-18 -1.7136e-18
5.02304e-20 -1.16077e-18 -1.22284e-18 6.63492e-19 1.19226e-18
-3.95394e-18 -6.12742e-18 -3.54564e-18 -3.30749e-18 -1.24493e-18
1.2887e-18 7.9194e-18 1.71291e-18 … 3.59662e-18 7.14169e-19
5.14955e-18 8.34019e-18 9.2066e-18 3.19522e-18 2.36739e-18
-6.65131e-18 -1.11913e-17 -7.39803e-18 -1.83992e-18 -4.25048e-18
-7.72255e-18 -9.44905e-18 -8.38531e-18 -0.0118234 -7.52197e-18
-2.88097e-17 -2.6799e-17 -3.49374e-17 -1.67336e-17 -0.0118234
Création de structures personnalisées¶
Julia n'est pas un langage orienté objet. Il supporte à la place la distribution multiple (multiple dispatch), dont la motivation est exposée éloquemment dans la documentation de Julia :
The choice of which method to execute when a function is applied is called dispatch. Julia allows the dispatch process to choose which of a function's methods to call based on the number of arguments given, and on the types of all of the function's arguments. This is different from traditional object-oriented languages, where dispatch occurs based only on the first argument, which often has a special argument syntax, and is sometimes implied rather than explicitly written as an argument. Using all of a function's arguments to choose which method should be invoked, rather than just the first, is known as multiple dispatch. Multiple dispatch is particularly useful for mathematical code, where it makes little sense to artificially deem the operations to "belong" to one argument more than any of the others: does the addition operation in x + y belong to x any more than it does to y? The implementation of a mathematical operator generally depends on the types of all of its arguments. Even beyond mathematical operations, however, multiple dispatch ends up being a powerful and convenient paradigm for structuring and organizing programs.
struct Dog
name::String
end
struct Cat
name::String
end
greet(a::Dog, b::Dog) = "$(a.name): Wouf wouf $(b.name), content de te revoir mon vieux"
greet(a::Dog, b::Cat) = "$(a.name): Wouf wouf wouf wouf !"
greet(a::Cat, b::Dog) = "$(a.name) s'éloigne discrètement de $(b.name)"
greet(a::Cat, b::Cat) = "$(a.name) commence à ronronner"
milou = Dog("Milou")
rantanplan = Dog("Rantanplan")
miaouss = Cat("Miaouss")
mrMoustache = Cat("MrMoustache")
@show greet(milou, rantanplan)
@show greet(milou, mrMoustache)
@show greet(mrMoustache, miaouss)
@show greet(mrMoustache, rantanplan);
greet(milou, rantanplan) = "Milou: Wouf wouf Rantanplan, content de te revoir mon vieux" greet(milou, mrMoustache) = "Milou: Wouf wouf wouf wouf !" greet(mrMoustache, miaouss) = "MrMoustache commence à ronronner" greet(mrMoustache, rantanplan) = "MrMoustache s'éloigne discrètement de Rantanplan"
Pour conclure cette séance,
illustrons la puissance de la distribution multiple avec deux autres exemples.
Premièrement, créons un type spécifique pour la matrice identité,
et redéfinissons les opérations + et * sur les matrices pour qu'elles fonctionnent avec ce nouveau type.
struct IdentityMatrix end
# This is required to redefine + and *
import Base: +,*
function +(A::Matrix, Id::IdentityMatrix)
result = copy(A)
for i in 1:size(A, 1)
result[i, i] += 1
end
return result
end
+(Id::IdentityMatrix, A::Matrix) = +(A, Id)
*(A::Matrix, Id::IdentityMatrix) = copy(A)
*(Id::IdentityMatrix, A::Matrix) = copy(A)
A = ones(3, 3)
Id = IdentityMatrix()
# We can now write operations such as
@show A
@show A + Id
@show Id + A
@show A * Id;
A = [1.0 1.0 1.0; 1.0 1.0 1.0; 1.0 1.0 1.0] A + Id =
[2.0 1.0 1.0; 1.0 2.0 1.0; 1.0 1.0 2.0] Id + A = [2.0 1.0 1.0; 1.0 2.0 1.0; 1.0 1.0 2.0] A * Id = [1.0 1.0 1.0; 1.0 1.0 1.0; 1.0 1.0 1.0]
Remarquer que nous n'avons pas eu besoin de modifier l'implémentation interne des matrices.
On notera également qu'avec notre implémentation de la matrice identité,
le produit A*I est pratiquement gratuit en temps de calcul.
Si la matrice identité avait été stockée comme une matrice pleine,
beaucoup de puissance de calcul aurait été gaspillée dans la multiplication matricielle.
n = 5000
A = rand(n, n)
I1 = IdentityMatrix()
I2 = [(i == j)*1.0 for i in 1:n, j in 1:n]
@time A*I1;
@time A*I2;
0.133289 seconds (3 allocations: 190.738 MiB, 34.73% gc time) 2.961209 seconds (114 allocations: 190.743 MiB, 5.11% gc time, 0.35% compilation time)
[Exercice 6]¶
L'objectif de cet exercice est de créer une structure pour représenter les nombres duaux.
Les nombres duaux sont une construction mathématique qui étend
les réels en introduisant un nouvel élément, souvent
noté $\varepsilon$, tel que $\varepsilon^2 = 0$.
Un nombre dual est composé d'une partie réelle et d'une partie duale,
où la partie duale se comporte comme une quantité infinitésimale.
Ils seront très utiles plus loin dans le cours,
lorsque nous étudierons la différentiation automatique, une technique
pour calculer efficacement des dérivées numériquement.
Compléter la définition de structure ci-dessous en ajoutant les opérations -, * et / aux nombres duaux.
import Base: +, -, *, /, show
struct Dual
real::Float64
dual::Float64
end
+(x::Dual, y::Dual) = Dual(x.real + y.real, x.dual + y.dual)
### BEGIN SOLUTION
*(x::Dual, y::Dual) = Dual(x.real*y.real, (x.dual*y.real + x.real*y.dual))
-(x::Dual, y::Dual) = Dual(x.real - y.real, x.dual - y.dual)
/(x::Dual, y::Dual) = Dual(x.real/y.real, (y.real*x.dual - x.real*y.dual)/y.real^2)
### END SOLUTION
show(io::IO, x::Dual) = print(io, x.real, x.dual ≥ 0 ? " + $(x.dual)" : " - $(-x.dual)", " ε")
# Example usage
a = Dual(3.0, 2.0) # 3 + 2ε
b = Dual(1.0, 4.0) # 1 + 4ε
@show a + b
@show a - b
@show a * b
@show a / b;
a + b = 4.0 + 6.0 ε a - b = 2.0 - 2.0 ε a * b = 3.0 + 14.0 ε a / b = 3.0 - 10.0 ε
@assert a + b == Dual(4, 6)
@assert a - b == Dual(2, -2)
@assert a * b == Dual(3, 14)
@assert a / b == Dual(3, -10)