Skip to content
Snippets Groups Projects
Commit a4701221 authored by Olivier Cots's avatar Olivier Cots
Browse files

clear outputs for julia

parent e85b73c1
No related branches found
No related tags found
No related merge requests found
%% Cell type:markdown id: tags:
# Introduction au langage julia #
## Types, id et références ##
`Julia` est un langage typé. La fonction `typeof` permet d'obtenir le type d'un objet ou d'une variable. Un seconde fonction importante pour comprendre comment sont stockés les objets est `objectid`. Cette
fonction donne l'identifiant de l'objet (en gros sa place en mémoire).
%% Cell type:code id: tags:
``` julia
a = 3
println(typeof(a))
println(objectid(a))
```
%% Output
Int64
17186855983609068021
%% Cell type:markdown id: tags:
## Int, float, complexes, rationnels et booléens ##
### Les entiers et réels flottants
- Les types entiers signés : Int8, Int16, Int32, Int64, Int128;
- Les types entiers non signés : UInt8, UInt16, UInt32, UInt64, UInt128;
- Les réels flottants : Float16, Float32, Float64.
Par défaut, le type d'une variable est définie par son affectation, mais on peut préciser son type.
%% Cell type:code id: tags:
``` julia
x = 12
println("x =",x)
println("type de x : ",typeof(x))
y = UInt64(12)
println("y = ",y)
println("type de y : ",typeof(y))
z = 1.0
println("type de z : ",typeof(z))
```
%% Output
x =12
type de x : Int64
y = 12
type de y : UInt64
type de z : Float64
%% Cell type:markdown id: tags:
### Les complexes
%% Cell type:code id: tags:
``` julia
a = 1+2im
println("a = ",a)
println("type de a : ",typeof(a))
```
%% Output
a = 1 + 2im
type de a : Complex{Int64}
%% Cell type:markdown id: tags:
### Les rationnels
%% Cell type:code id: tags:
``` julia
q = 2//3
println("q = ",q)
println("type de q",typeof(q))
q = q + 1//5
println("q = ",q)
```
%% Output
q = 2//3
type de qRational{Int64}
q = 13//15
%% Cell type:markdown id: tags:
### Booléens
%% Cell type:code id: tags:
``` julia
a = true
println("a = ",a)
println("type de a : ",typeof(a))
```
%% Output
a = true
type de a : Bool
%% Cell type:markdown id: tags:
## Itérateur
1:5 est un **itérateur**
%% Cell type:code id: tags:
``` julia
a = 1:2:7
println("a = ",a)
println("type de a : ", typeof(a))
b = collect(a) # renvoie le vecteur des valeurs
println("b = ",b)
println("type de b : ", typeof(b))
```
%% Output
a = 1:2:7
type de a : StepRange{Int64,Int64}
b = [1, 3, 5, 7]
type de b : Array{Int64,1}
%% Cell type:markdown id: tags:
## type array
%% Cell type:code id: tags:
``` julia
a = [1 2 3]
b = [1,2,3]
c = [1.0 2 3 ; 1 2 3]
println("a = $a, b = $b, c = $c")
b_type = typeof(b)
a_type = typeof(a)
c_type = typeof(c)
println("type de a = $a_type, type de b = $b_type, type de c = $c_type")
```
%% Output
a = [1 2 3], b = [1, 2, 3], c = [1.0 2.0 3.0; 1.0 2.0 3.0]
type de a = Array{Int64,2}, type de b = Array{Int64,1}, type de c = Array{Float64,2}
%% Cell type:markdown id: tags:
- a est une matrice d'entier à 1 ligne, 3 colonnes
- b est un vecteur d'entier
- c est une matrice de flottants 2 lignes, 3 colonnes
Que se passe-t-il si on calcul : `a*b, b*a, c*b, c*a`
%% Cell type:code id: tags:
``` julia
a*b
```
%% Output
1-element Array{Int64,1}:
14
%% Cell type:code id: tags:
``` julia
b*a
```
%% Output
3×3 Array{Int64,2}:
1 2 3
2 4 6
3 6 9
%% Cell type:code id: tags:
``` julia
c*a
```
%% Output
DimensionMismatch("matrix A has dimensions (2,3), matrix B has dimensions (1,3)")
Stacktrace:
[1] _generic_matmatmul!(::Array{Float64,2}, ::Char, ::Char, ::Array{Float64,2}, ::Array{Int64,2}, ::LinearAlgebra.MulAddMul{true,true,Bool,Bool}) at /Users/julia/buildbot/worker/package_macos64/build/usr/share/julia/stdlib/v1.4/LinearAlgebra/src/matmul.jl:736
[2] generic_matmatmul!(::Array{Float64,2}, ::Char, ::Char, ::Array{Float64,2}, ::Array{Int64,2}, ::LinearAlgebra.MulAddMul{true,true,Bool,Bool}) at /Users/julia/buildbot/worker/package_macos64/build/usr/share/julia/stdlib/v1.4/LinearAlgebra/src/matmul.jl:724
[3] mul! at /Users/julia/buildbot/worker/package_macos64/build/usr/share/julia/stdlib/v1.4/LinearAlgebra/src/matmul.jl:235 [inlined]
[4] mul! at /Users/julia/buildbot/worker/package_macos64/build/usr/share/julia/stdlib/v1.4/LinearAlgebra/src/matmul.jl:208 [inlined]
[5] *(::Array{Float64,2}, ::Array{Int64,2}) at /Users/julia/buildbot/worker/package_macos64/build/usr/share/julia/stdlib/v1.4/LinearAlgebra/src/matmul.jl:153
[6] top-level scope at In[10]:1
%% Cell type:code id: tags:
``` julia
c*b
```
%% Output
2-element Array{Float64,1}:
14.0
14.0
%% Cell type:markdown id: tags:
b est donc un vecteur colonne
%% Cell type:markdown id: tags:
## Opérations terme à terme
On a bien sur les opétations classique : + * et /, l'étoile
\* est la multiplication matricielle pais on a aussi les opérations terme à terme
%% Cell type:code id: tags:
``` julia
d = 2*ones(2,3)
c .* d
```
%% Output
2×3 Array{Float64,2}:
2.0 4.0 6.0
2.0 4.0 6.0
%% Cell type:markdown id: tags:
**Attention au .** Étant donné que le point . permet à la fois de définir un float et d'effectuer
les opérations élément par élément, il faut mettre des espaces entre les points afin de pouvoir distinguer
la signification de chaque point.
%% Cell type:code id: tags:
``` julia
5. .+ [1,2]
```
%% Output
2-element Array{Float64,1}:
6.0
7.0
%% Cell type:code id: tags:
``` julia
5.+[1,2]
```
%% Output
syntax: invalid syntax "5.+"; add space(s) to clarify
%% Cell type:code id: tags:
``` julia
E = 1:4
println("E = ",E)
println("type de E : ",typeof(E))
```
%% Output
E = 1:4
type de E : UnitRange{Int64}
%% Cell type:markdown id: tags:
On peut extraire des sous matrice facilement, mais attention
%% Cell type:code id: tags:
``` julia
E = [1 2 3 4 ; 5 6 7 8 ; 9 10 11 12]
E[[1,3],1:2]
```
%% Output
2×2 Array{Int64,2}:
1 2
9 10
%% Cell type:code id: tags:
``` julia
E[[1,3],[1:2]]
```
%% Output
ArgumentError: invalid index: UnitRange{Int64}[1:2] of type Array{UnitRange{Int64},1}
Stacktrace:
[1] to_index(::Array{UnitRange{Int64},1}) at ./indices.jl:294
[2] to_index(::Array{Int64,2}, ::Array{UnitRange{Int64},1}) at ./indices.jl:274
[3] to_indices at ./indices.jl:325 [inlined] (repeats 2 times)
[4] to_indices at ./indices.jl:321 [inlined]
[5] getindex(::Array{Int64,2}, ::Array{Int64,1}, ::Array{UnitRange{Int64},1}) at ./abstractarray.jl:980
[6] top-level scope at In[17]:1
%% Cell type:markdown id: tags:
## Les tuples
Ce sont des sortes de tableaux à 1 dimension dont les objets peuvent avoir des types différents. Ce sont des objet non modifiables
%% Cell type:code id: tags:
``` julia
t = 12, 13, "quatorze"
```
%% Output
(12, 13, "quatorze")
%% Cell type:code id: tags:
``` julia
t[1]
```
%% Output
12
%% Cell type:code id: tags:
``` julia
t[3]
```
%% Output
"quatorze"
%% Cell type:code id: tags:
``` julia
typeof(t)
```
%% Output
Tuple{Int64,Int64,String}
%% Cell type:code id: tags:
``` julia
t[2]=1 # erreur car non modifiable
```
%% Output
MethodError: no method matching setindex!(::Tuple{Int64,Int64,String}, ::Int64, ::Int64)
Stacktrace:
[1] top-level scope at In[22]:1
%% Cell type:markdown id: tags:
### Autres types de base
* les chaînes de caractères;
* les dictionnaires;
* les ensembles.
On peut aussi créer des types : structures, ...
%% Cell type:markdown id: tags:
## Fonctions
**Les paramètres de type scalaire ou tuple sont en entrée**
%% Cell type:code id: tags:
``` julia
function fct2(a,b)
a_id = objectid(a)
b_id = objectid(b)
println("objectid(a) = $a_id, objectid(b) = $b_id")
b = a
a_id = objectid(a)
b_id = objectid(b)
println("objectid(a) = $a_id, objectid(b) = $b_id")
return b
end
a = 0
b = 1
a_id = objectid(a)
b_id = objectid(b)
println("Avant fct2")
println("objectid(a) = $a_id, objectid(b) = $b_id")
println("a, b = $a, $b")
println("Dans fct2")
c = fct2(a,b)
println("Après fct2")
a_id = objectid(a)
b_id = objectid(b)
println("objectid(a) = $a_id, objectid(b) = $b_id")
println("a, b, c = $a,$b,$c")
```
%% Output
Avant fct2
objectid(a) = 9949443806018717880, objectid(b) = 11967854120867199718
a, b = 0, 1
Dans fct2
objectid(a) = 9949443806018717880, objectid(b) = 11967854120867199718
objectid(a) = 9949443806018717880, objectid(b) = 9949443806018717880
Après fct2
objectid(a) = 9949443806018717880, objectid(b) = 11967854120867199718
a, b, c = 0,1,0
%% Cell type:markdown id: tags:
## Type Array
**Les paramètres sont passés par référence**
%% Cell type:code id: tags:
``` julia
function fct3(a,b)
a_id = objectid(a)
b_id = objectid(b)
println("objectid(a) = $a_id, objectid(b) = $b_id")
b[1] = a[1]
b[2] = a[2]
a_id = objectid(a)
b_id = objectid(b)
println("objectid(a) = $a_id, objectid(b) = $b_id")
println("a, b = $a, $b")
return b
end
a = [0,0]
b = [1,1]
a_id = objectid(a)
b_id = objectid(b)
println("Avant fct3")
println("objectid(a) = $a_id, objectid(b) = $b_id")
println("a, b = $a, $b")
println("Dans fct3")
c = fct3(a,b)
println("Après fct3")
a_id = objectid(a)
b_id = objectid(b)
c_id = objectid(c)
println("objectid(a) = $a_id, objectid(b) = $b_id, objectid(c) = $c_id")
println("a, b, c = $a, $b, $c")
c[1]=10
println("b = $b, c = $c")
```
%% Output
Avant fct3
objectid(a) = 15031927814252516001, objectid(b) = 13102773869306322552
a, b = [0, 0], [1, 1]
Dans fct3
objectid(a) = 15031927814252516001, objectid(b) = 13102773869306322552
objectid(a) = 15031927814252516001, objectid(b) = 13102773869306322552
a, b = [0, 0], [0, 0]
Après fct3
objectid(a) = 15031927814252516001, objectid(b) = 13102773869306322552, objectid(c) = 13102773869306322552
a, b, c = [0, 0], [0, 0], [0, 0]
b = [10, 0], c = [10, 0]
%% Cell type:markdown id: tags:
**`b`et `c`sont les mêmes objects!**
**`a` et `b` sont les mêmes objets**
%% Cell type:code id: tags:
``` julia
function fct3_1(a,b)
a_id = objectid(a)
b_id = objectid(b)
println("objectid(a) = $a_id, objectid(b) = $b_id")
b[1] = a[1]
b[2] = a[2]
a_id = objectid(a)
b_id = objectid(b)
println("objectid(a) = $a_id, objectid(b) = $b_id")
println("a, b = $a, $b")
return b[:] # on renvoie un nouvel objet
end
a = [0,0]
b = [1,1]
a_id = objectid(a)
b_id = objectid(b)
println("Avant fct3_1")
println("objectid(a) = $a_id, objectid(b) = $b_id")
println("a, b = $a, $b")
println("Dans fct3_1")
c = fct3_1(a,b)
println("Après fct3_1")
a_id = objectid(a)
b_id = objectid(b)
c_id = objectid(c)
println("objectid(a) = $a_id, objectid(b) = $b_id, objectid(c) = $c_id")
println("a, b, c = $a, $b, $c")
c[1]=10
println("b = $b, c = $c")
```
%% Output
Avant fct3_1
objectid(a) = 6846767276606612758, objectid(b) = 16616932192875253576
a, b = [0, 0], [1, 1]
Dans fct3_1
objectid(a) = 6846767276606612758, objectid(b) = 16616932192875253576
objectid(a) = 6846767276606612758, objectid(b) = 16616932192875253576
a, b = [0, 0], [0, 0]
Après fct3_1
objectid(a) = 6846767276606612758, objectid(b) = 16616932192875253576, objectid(c) = 208917430185405003
a, b, c = [0, 0], [0, 0], [0, 0]
b = [0, 0], c = [10, 0]
%% Cell type:markdown id: tags:
**`b`et `c`sont différents**
%% Cell type:markdown id: tags:
- le contenu du vecteur b a été modifié;
- le pointeur sur le vecteur b est le même.
%% Cell type:markdown id: tags:
**`fct`et `fct!` donnent les mêmes résultats**. Par convention on aura un ! à la fin du nom de la fonction si au moins un des paramètres en entrée est modifié (en général le premier).
%% Cell type:markdown id: tags:
## Arguments
- arguments optionnels;
- vectorisation : `sin.([0,\pi/2,\pi])`;
- `Maps`et `Filters`(programmation fonctionnelle);
- récursivité.
%% Cell type:code id: tags:
``` julia
function showtypetree(T, level=0)
println("\t" ^ level, T)
for t in subtypes(T)
if t != Any
showtypetree(t, level+1)
end
end
end
showtypetree(Number)
```
%% Output
Number
Complex
Real
AbstractFloat
BigFloat
Float16
Float32
Float64
AbstractIrrational
Irrational
Integer
Bool
Signed
BigInt
Int128
Int16
Int32
Int64
Int8
Unsigned
UInt128
UInt16
UInt32
UInt64
UInt8
Rational
%% Cell type:code id: tags:
``` julia
# vecteur
A = Array{Real}(undef,3)
B = Vector{Real}(undef,4)
A_type = typeof(A)
B_type = typeof(B)
println("type de A = $A_type, type de B = $B_type")
println(A)
A_type == B_type # renvoie true
#
# Matrices
A = Array{Real}(undef,2,4)
B = Matrix{Real}(undef,3,4)
A_type = typeof(A)
B_type = typeof(B)
println("type de A = $A_type, type de B = $B_type")
A_type == B_type # renvoie true
```
%% Output
type de A = Array{Real,1}, type de B = Array{Real,1}
Real[#undef, #undef, #undef]
type de A = Array{Real,2}, type de B = Array{Real,2}
true
%% Cell type:code id: tags:
``` julia
Int<:Real
```
%% Output
true
%% Cell type:code id: tags:
``` julia
Int<:AbstractFloat
```
%% Output
false
%% Cell type:markdown id: tags:
## Types dans les fonctions
### Paramètres en entrée
Si les paramètres ne sont pas du bon type alors le programme plante
%% Cell type:code id: tags:
``` julia
function fct5(a::Int, b::Int)
return a+b
end
f1 = fct5(2.,2)
```
%% Output
MethodError: no method matching fct5(::Float64, ::Int64)
Closest candidates are:
fct5(!Matched::Int64, ::Int64) at In[30]:2
Stacktrace:
[1] top-level scope at In[30]:4
%% Cell type:markdown id: tags:
### Paramètre en sortie
%% Cell type:code id: tags:
``` julia
function fct6!(a::Real ,x::Vector)
#function fct(a::Real ,x::Vector, y::Vector)
a = "a" # a is a new variable
x = [2,2] # x is new variable
return a, x
end
function fct7!(a::Real ,x::Vector)::Tuple{Int,Vector}
a = "a" # a is a new variable
x = [2,2] # x is new variable
return a, x
end
println("Main Program, test of fct6!")
println("--------------------------")
a = 1
x = [0, 0]
f_a, f_x = fct6!(a, x)
println("a, x = $a, $x")
println("f_a, f_x = $f_a, $f_x")
println("Main Program, test of fct7!")
println("--------------------------")
a = 1
x = [0, 0]
f_a, f_x = fct7!(a, x)
println("a, x, = $a, $x")
```
%% Output
Main Program, test of fct6!
--------------------------
a, x = 1, [0, 0]
f_a, f_x = a, [2, 2]
Main Program, test of fct7!
--------------------------
MethodError: Cannot `convert` an object of type String to an object of type Int64
Closest candidates are:
convert(::Type{T}, !Matched::T) where T<:Number at number.jl:6
convert(::Type{T}, !Matched::Number) where T<:Number at number.jl:7
convert(::Type{T}, !Matched::Ptr) where T<:Integer at pointer.jl:23
...
Stacktrace:
[1] convert(::Type{Tuple{Int64,Array{T,1} where T}}, ::Tuple{String,Array{Int64,1}}) at ./essentials.jl:310
[2] fct7!(::Int64, ::Array{Int64,1}) at ./In[31]:11
[3] top-level scope at In[31]:26
%% Cell type:markdown id: tags:
## Dispatch multiple
%% Cell type:code id: tags:
``` julia
function fct5(a::Real, b::Int)
return a-b
end
f1 = fct5(2,2)
f2 = fct5(2.0,2)
println("f1 = $f1, f2 = $f2")
```
%% Output
f1 = 4, f2 = 0.0
%% Cell type:code id: tags:
``` julia
methods(fct5)
```
%% Output
# 2 methods for generic function "fct5":
[1] fct5(a::Int64, b::Int64) in Main at In[30]:2
[2] fct5(a::Real, b::Int64) in Main at In[32]:2
%% Cell type:markdown id: tags:
# Portée des variables
De nombreux langages de programmation font la différence entre les variables globales (communes à tout le programme) et les variables locales, qui correspondent aux variables introduites dans le code d'une fonction
Ici, la situation est un peu plus complexe, car la philosophie générale est d'aller vers une localisation plus forte des variables de manière à éviter des conflits de noms pouvant induire des comportements non voulus.
Ce renforcement du cloisonnement se fait de deux manières :
* Une variable déclarée dans le programme principal (ou en ligne de commande REPL) n'est pas immédiatement accessible dans les blocs for. . . end, while. . . end,try. . . end du programme principal, non plus que dans les fonctions appelées par le programme. Leur appel doit être précédé du mot global.
* Une variable déclarée dans une fonction est visible dans toute fonction interne à cette fonction, ainsi que dans les blocs for. . . end, while. . . end,try. . . end de cette fonction.
* Si dans un bloc de code la déclaration d'une variable est précédée du mot clef local, c'est une nouvelle variable locale qui est créée. Elle sera détruite à la fin du bloc; si une variable précédente existait, c'est à nouveau elle qui a la main.
%% Cell type:code id: tags:
``` julia
function niveau_un()
function niveau_deux()
x=3;
println("x=",x)
end
function niveau_deux_deux()
local x=5;
println("x=",x)
end
x=1;
println("x=",x)
for i=1:1
x=4;
end
println("x=",x)
niveau_deux();
println("x=",x)
niveau_deux_deux();
println("x=",x)
end
```
%% Output
niveau_un (generic function with 1 method)
%% Cell type:code id: tags:
``` julia
niveau_un()
```
%% Output
x=1
x=4
x=3
x=3
x=5
x=3
%% Cell type:code id: tags:
``` julia
function niveau_deux()
x=3;
println("x = ",x)
end
function niveau_deux_deux()
local x=5;
println("x = ",x)
end
x = 1;
println("x = ",x)
for i = 1:1
x = 4;
end
println("après le for x = ",x)
niveau_deux();
println("x = ",x)
niveau_deux_deux()
println("x = ",x)
```
%% Output
x = 1
après le for x = 4
x = 3
x = 4
x = 5
x = 4
%% Cell type:markdown id: tags:
**mais sous julia REPL on obtient pour `x`après le for 1 !**
%% Cell type:markdown id: tags:
# Performance
%% Cell type:code id: tags:
``` julia
x = rand(1000);
function sum_global()
s = 0.0
for i in x
s += i
end
return s
end;
p1 = @time sum_global()
println("Permormance1 = $p1")
p2 = @time sum_global()
println("Permormance2 = $p2")
```
%% Output
0.012944 seconds (3.65 k allocations: 80.641 KiB)
Permormance1 = 503.0964853516044
0.000240 seconds (3.49 k allocations: 70.156 KiB)
Permormance2 = 503.0964853516044
%% Cell type:markdown id: tags:
On the first call (@time sum_global()) the function gets compiled. (If you've not yet used @time in this session, it will also compile functions needed for timing.) You should not take the results of this run seriously. For the second run, note that in addition to reporting the time, it also indicated that a significant amount of memory was allocated. We are here just computing a sum over all elements in a vector of 64-bit floats so there should be no need to allocate memory (at least not on the heap which is what @time reports).
%% Cell type:code id: tags:
``` julia
x = rand(1000);
function sum_arg(x)
s = 0.0
for i in x
s += i
end
return s
end;
p1 = @time sum_arg(x)
println("Permormance1 = $p1")
p2 = @time sum_arg(x)
println("Permormance2 = $p2")
```
%% Output
0.009822 seconds (5.25 k allocations: 241.867 KiB)
Permormance1 = 503.8521806777714
0.000004 seconds (1 allocation: 16 bytes)
Permormance2 = 503.8521806777714
%% Cell type:markdown id: tags:
Conclusion : attention aux variables globales!
%% Cell type:code id: tags:
``` julia
function sumofsins1(n::Integer)
r = 0
for i in 1:n
r += sin(3.4)
end
return r
end
function sumofsins2(n::Integer)
r = 0.0
for i in 1:n
r += sin(3.4)
end
return r
end
```
%% Output
sumofsins2 (generic function with 1 method)
%% Cell type:code id: tags:
``` julia
sumofsins1(100_000)
sumofsins2(100_000)
@time [sumofsins1(100_000) for i in 1:100];
@time [sumofsins2(100_000) for i in 1:100];
```
%% Output
0.085448 seconds (125.55 k allocations: 6.676 MiB)
0.079326 seconds (118.57 k allocations: 6.369 MiB)
%% Cell type:code id: tags:
``` julia
@time [sumofsins1(100_000) for i in 1:100];
@time [sumofsins2(100_000) for i in 1:100];
```
%% Output
0.116044 seconds (125.54 k allocations: 6.647 MiB, 15.46% gc time)
0.098289 seconds (118.57 k allocations: 6.369 MiB)
%% Cell type:markdown id: tags:
## Autres points
### Modules
### Interface programme en C, en fortran, ...
### Calcul parallèle
### ...
%% Cell type:markdown id: tags:
## Remarques
### Caractères UTF-8
%% Cell type:code id: tags:
``` julia
β
α = 10. # on tape \alp puis TAB, on obtient \alpha et on retape TAB et on a le caractère grec
```
%% Output
UndefVarError: β not defined
Stacktrace:
[1] top-level scope at In[42]:1
%% Cell type:code id: tags:
``` julia
ch = "élément"
ch[1]
```
%% Output
'é': Unicode U+00E9 (category Ll: Letter, lowercase)
%% Cell type:code id: tags:
``` julia
ch[2]
```
%% Output
StringIndexError("élément", 2)
Stacktrace:
[1] string_index_err(::String, ::Int64) at ./strings/string.jl:12
[2] getindex_continued(::String, ::Int64, ::UInt32) at ./strings/string.jl:220
[3] getindex(::String, ::Int64) at ./strings/string.jl:213
[4] top-level scope at In[44]:1
%% Cell type:code id: tags:
``` julia
ch[3]
```
%% Output
'l': ASCII/Unicode U+006C (category Ll: Letter, lowercase)
%% Cell type:markdown id: tags:
### Inconvénients
* la portée des variables;
* la possibilité dans une fonction de créer une variable (locale) de même nom qu'un paramètre formel;
* On compile à la volé lors de chaque lancement d'une session. Possibilité de générer du code compiler?
......
This diff is collapsed.
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment