Le 2021-01-29, j'utilise Julia_1.5.3 sur VS_Code_1.52.1. Ordinateur : W10 Pro, i9-10900F.
Préalables, mes billets précédents sur Julia sont supposés connus et assimilés.
Opérateur dot
Documentation en anglais :
- Vectorized "dot" operators
- Dot Syntax for Vectorizing Functions
Pour chaque opération binaire comme ^, il y a une opération "point" correspondante .^ qui est automatiquement définie pour effectuer ^ élément par élément sur les tableaux. Par exemple [1,2,3] ^ 3 n'est pas défini, car il n'y a pas de signification mathématique standard pour "cuber" un tableau (non carré), mais [1,2,3] .^ 3 est défini comme le calcul du résultat élément par élément (ou "vectorisé") [1^3, 2^3, 3^3]. La combinaison d'opérateurs de points avec des littéraux numériques peut être ambiguë. Par exemple, il n'est pas clair si 1.+x signifie 1. + x ou 1 .+ x. Par conséquent, cette syntaxe n'est pas autorisée et des espaces doivent être utilisés autour de l'opérateur dans de tels cas.
Dans les langages de calcul technique, il est courant d'avoir des versions "vectorisées" de fonctions, qui appliquent simplement une fonction donnée f(x) à chaque élément d'un tableau A pour produire un nouveau tableau via f(A). Toute fonction de Julia f peut être appliquée élément par élément à n'importe quel tableau (ou autre collection) avec la syntaxe f.(A). Bien sûr, vous pouvez omettre le point si vous écrivez une méthode "vectorielle" spécialisée de f, par exemple via f(A::AbstractArray) = map(f, A), et c'est tout aussi efficace que f.(A). L'avantage de la syntaxe f.(A) est que les fonctions qui peuvent être vectorisées n'ont pas besoin d'être décidées à l'avance par le rédacteur de la bibliothèque.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| function main()
f(x,y) = 3x + 4y
X = [1.0, 2.0, 3.0]
Y = [4.0, 5.0, 6.0]
@time @show f.(X, Y)
@time @show f.(X, Y)
end
main()
#=
La première mesure tient compte du temps de compilation
f.(X, Y) = [19.0, 26.0, 33.0]
0.214062 seconds (412.58 k allocations: 21.876 MiB, 2.26% gc time)
f.(X, Y) = [19.0, 26.0, 33.0]
0.000113 seconds (28 allocations: 2.312 KiB)
=# |
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
| function main()
#=
L'opérateur dot peut combiner des tableaux et des scalaires, des
tableaux de la même taille (en effectuant l'opération élément
par élément), et même des tableaux de formes différentes
(par exemple en combinant des vecteurs ligne et colonne pour
produire une matrice).
=#
v_ligne = [1.0 2.0 3.0]
v_col = [5.0, 5.0, 5.0]
@time @show v_ligne .+ v_col
@time @show v_ligne .+ v_col
v = [1.0, 2.0]
m = [5.0 5.0 5.0 5.0; 6.0 6.0 6.0 6.0]
@time @show v .+ m
#=
Si vous calculez 2 .* A .^2 .+ sin.(A) pour un tableau A, il
effectue une seule boucle A, calculant 2a^2 + sin(a) pour
chaque élément de A.
=#
A = [3.0, 6.0, 9.0]
@time println("2 .* A .^2 .+ sin.(A) = $(2 .* A .^2 .+ sin.(A))")
# Les appels de points imbriqués comme f.(g.(x)) sont fusionnés
@time println("sqrt.(log10.(A)) = $(sqrt.(log10.(A)))")
#=
Il existe une macro dot (@.) qui permet de simplifier l'écriture
lorsque vous savez ce que vous faites en l'utilisant. Je crois
qu'il est préférable de commencer à utiliser dot sans elle.
=#
@time println("@. 2A^2 + sin(A) = $(@. 2A^2 + sin(A))")
end
main()
#=
La première mesure tient compte du temps de compilation
v_ligne .+ v_col = [6.0 7.0 8.0; 6.0 7.0 8.0; 6.0 7.0 8.0]
0.217286 seconds (413.79 k allocations: 21.913 MiB)
v_ligne .+ v_col = [6.0 7.0 8.0; 6.0 7.0 8.0; 6.0 7.0 8.0]
0.000463 seconds (35 allocations: 4.547 KiB)
v .+ m = [6.0 6.0 6.0 6.0; 8.0 8.0 8.0 8.0]
0.000347 seconds (33 allocations: 4.109 KiB)
2 .* A .^2 .+ sin.(A) = [18.14112000805987, 71.72058450180107, 162.41211848524176]
0.041482 seconds (3.07 k allocations: 172.266 KiB)
sqrt.(log10.(A)) = [0.6907396432228734, 0.8821288173411204, 0.9768533715145405]
0.000233 seconds (23 allocations: 2.000 KiB)
@. 2A^2 + sin(A) = [18.14112000805987, 71.72058450180107, 162.41211848524176]
0.000044 seconds (23 allocations: 2.000 KiB)
=# |
Composition et pipe
Documentation en anglais :
Function composition and piping
Les fonctions de Julia peuvent être combinées en les composant (∘) ou en les enchaînant (chaînage, pipe : |>).
Vous utilisez l'opérateur de composition de fonction (∘) pour composer les fonctions, c'est donc (f ∘ g)(args...)la même chose que f(g(args...)). Vous pouvez saisir l'opérateur de composition dans la REPL et les éditeurs correctement configurés à l'aide de \circ<tab>.
Le chaînage de fonctions (parfois appelé "piping" ou "using a pipe" pour envoyer des données à une fonction ultérieure) consiste à appliquer une fonction à la sortie de la fonction précédente avec |>.
function main()
@time @show (sqrt ∘ +)(3, 6)
@time @show (sqrt ∘ +)(3, 6)
@time @show map(first ∘ reverse ∘ uppercase, split("you can compose functions like this"))
@time @show split("you can compose functions like this") .|> uppercase .|> reverse .|> first
@time @show (sqrt ∘ sum)(1:10)
@time @show 1:10 |> sum |> sqrt
@time @show sum(sqrt.(1:10))
@time @show 1:10 .|> sqrt |> sum
end
main()
#=
La première mesure tient compte du temps de compilation
(sqrt ∘ (+))(3, 6) = 3.0
0.163357 seconds (409.03 k allocations: 21.682 MiB)
(sqrt ∘ (+))(3, 6) = 3.0
0.000256 seconds (16 allocations: 832 bytes)
map((first ∘ reverse) ∘ uppercase, split("you can compose functions like this")) =
['U', 'N', 'E', 'S', 'E', 'S']
0.084758 seconds (14.10 k allocations: 746.850 KiB)
((split("you can compose functions like this") .|> uppercase) .|> reverse) .|> first =
['U', 'N', 'E', 'S', 'E', 'S']
0.000074 seconds (45 allocations: 2.016 KiB)
(sqrt ∘ sum)(1:10) = 7.416198487095663
0.000071 seconds (16 allocations: 848 bytes)
(1:10 |> sum) |> sqrt = 7.416198487095663
0.000061 seconds (17 allocations: 1.156 KiB)
sum(sqrt.(1:10)) = 22.4682781862041
0.000061 seconds (17 allocations: 1008 bytes)
(1:10 .|> sqrt) |> sum = 22.4682781862041
0.000060 seconds (18 allocations: 1.312 KiB)
=#
function main()
xs = [1.0, 2.0, 3.0, 4.0, 5.0]
add(x) = x + 1.0
mul(x) = 2.0x
addmul(xs) = xs .|> add .|> mul
@time @show addmul(xs)
@time @show addmul(xs)
@time @show sqrt.(2.0π .* xs)
@time @show @. sqrt(2.0π * xs)
end
main()
#=
addmul(xs) = [4.0, 6.0, 8.0, 10.0, 12.0]
0.213491 seconds (412.59 k allocations: 21.877 MiB, 2.27% gc time)
addmul(xs) = [4.0, 6.0, 8.0, 10.0, 12.0]
0.000074 seconds (34 allocations: 3.203 KiB)
sqrt.((2.0π) .* xs) = [2.5066282746310002, 3.5449077018110318, 4.3416075273496055,
5.0132565492620005, 5.604991216397929]
0.000574 seconds (34 allocations: 3.109 KiB)
@__dot__(sqrt((2.0π) * xs)) = [2.5066282746310002, 3.5449077018110318,
4.3416075273496055, 5.0132565492620005, 5.604991216397929]
0.000082 seconds (34 allocations: 3.109 KiB)
=#
Licence Creative Commons Attribution 2.0 Belgique