Base.Cartesian

Le module (non exporté) Cartesian fournit des macros qui facilitent l'écriture d'algorithmes multidimensionnels. Le plus souvent, vous pouvez écrire de tels algorithmes avec straightforward techniques; cependant, il existe quelques cas où Base.Cartesian est encore utile ou nécessaire.

Principles of usage

Un exemple simple d'utilisation est :

@nloops 3 i A begin
    s += @nref 3 A i
end

qui génère le code suivant :

for i_3 = axes(A, 3)
    for i_2 = axes(A, 2)
        for i_1 = axes(A, 1)
            s += A[i_1, i_2, i_3]
        end
    end
end

En général, Cartesian vous permet d'écrire du code générique contenant des éléments répétitifs, comme les boucles imbriquées dans cet exemple. D'autres applications incluent des expressions répétées (par exemple, le déroulement de boucle) ou la création d'appels de fonction avec un nombre variable d'arguments sans utiliser la construction "splat" (i...).

Basic syntax

La syntaxe (de base) de @nloops est la suivante :

  • Le premier argument doit être un entier (pas une variable) spécifiant le nombre de boucles.
  • Le deuxième argument est le préfixe de symbole utilisé pour la variable d'itération. Ici, nous avons utilisé i, et les variables i_1, i_2, i_3 ont été générées.
  • Le troisième argument spécifie la plage pour chaque variable d'itérateur. Si vous utilisez une variable (symbole) ici, elle est considérée comme axes(A, dim). Plus flexiblement, vous pouvez utiliser la syntaxe d'expression de fonction anonyme décrite ci-dessous.
  • Le dernier argument est le corps de la boucle. Ici, c'est ce qui apparaît entre begin...end.

Il existe des fonctionnalités supplémentaires de @nloops décrites dans le reference section.

@nref suit un modèle similaire, générant A[i_1,i_2,i_3] à partir de @nref 3 A i. La pratique générale est de lire de gauche à droite, c'est pourquoi @nloops est @nloops 3 i A expr (comme dans for i_2 = axes(A, 2), où i_2 est à gauche et la plage est à droite) tandis que @nref est @nref 3 A i (comme dans A[i_1,i_2,i_3], où le tableau vient en premier).

Si vous développez du code avec Cartesian, vous constaterez peut-être que le débogage est plus facile lorsque vous examinez le code généré, en utilisant @macroexpand :

julia> @macroexpand @nref 2 A i
:(A[i_1, i_2])

Supplying the number of expressions

Le premier argument de ces deux macros est le nombre d'expressions, qui doit être un entier. Lorsque vous écrivez une fonction que vous souhaitez faire fonctionner dans plusieurs dimensions, ce n'est peut-être pas quelque chose que vous voulez coder en dur. L'approche recommandée est d'utiliser une @generated function. Voici un exemple :

@generated function mysum(A::Array{T,N}) where {T,N}
    quote
        s = zero(T)
        @nloops $N i A begin
            s += @nref $N A i
        end
        s
    end
end

Naturellement, vous pouvez également préparer des expressions ou effectuer des calculs avant le bloc quote.

Anonymous-function expressions as macro arguments

Peut-être la fonctionnalité la plus puissante de Cartesian est la capacité de fournir des expressions de fonction anonyme qui sont évaluées au moment de l'analyse. Considérons un exemple simple :

@nexprs 2 j->(i_j = 1)

@nexprs génère n expressions qui suivent un modèle. Ce code générerait les déclarations suivantes :

i_1 = 1
i_2 = 1

Dans chaque déclaration générée, un j "isolé" (la variable de la fonction anonyme) est remplacé par des valeurs dans la plage 1:2. En général, Cartesian utilise une syntaxe similaire à LaTeX. Cela vous permet de faire des calculs sur l'index j. Voici un exemple de calcul des pas d'un tableau :

s_1 = 1
@nexprs 3 j->(s_{j+1} = s_j * size(A, j))

générer des expressions

s_1 = 1
s_2 = s_1 * size(A, 1)
s_3 = s_2 * size(A, 2)
s_4 = s_3 * size(A, 3)

Les expressions de fonction anonyme ont de nombreuses utilisations en pratique.

Macro reference

Base.Cartesian.@nloopsMacro
@nloops N itersym rangeexpr bodyexpr
@nloops N itersym rangeexpr preexpr bodyexpr
@nloops N itersym rangeexpr preexpr postexpr bodyexpr

Générer N boucles imbriquées, en utilisant itersym comme préfixe pour les variables d'itération. rangeexpr peut être une expression de fonction anonyme, ou un symbole simple var dans ce cas la plage est axes(var, d) pour la dimension d.

En option, vous pouvez fournir des expressions "pré" et "post". Celles-ci sont exécutées en premier et en dernier, respectivement, dans le corps de chaque boucle. Par exemple :

@nloops 2 i A d -> j_d = min(i_d, 5) begin
    s += @nref 2 A j
end

générerait :

for i_2 = axes(A, 2)
    j_2 = min(i_2, 5)
    for i_1 = axes(A, 1)
        j_1 = min(i_1, 5)
        s += A[j_1, j_2]
    end
end

Si vous souhaitez juste une expression post, fournissez nothing pour l'expression pré. En utilisant des parenthèses et des points-virgules, vous pouvez fournir des expressions à plusieurs instructions.

source
Base.Cartesian.@nrefMacro
@nref N A indexexpr

Générer des expressions comme A[i_1, i_2, ...]. indexexpr peut être soit un préfixe de symbole d'itération, soit une expression de fonction anonyme.

Exemples

julia> @macroexpand Base.Cartesian.@nref 3 A i
:(A[i_1, i_2, i_3])
source
Base.Cartesian.@nextractMacro
@nextract N esym isym

Générer N variables esym_1, esym_2, ..., esym_N pour extraire des valeurs de isym. isym peut être soit un Symbol, soit une expression de fonction anonyme.

@nextract 2 x y générerait

x_1 = y[1]
x_2 = y[2]

tandis que @nextract 3 x d->y[2d-1] donnerait

x_1 = y[1]
x_2 = y[3]
x_3 = y[5]
source
Base.Cartesian.@nexprsMacro
@nexprs N expr

Générer N expressions. expr doit être une expression de fonction anonyme.

Exemples

julia> @macroexpand Base.Cartesian.@nexprs 4 i -> y[i] = A[i+j]
quote
    y[1] = A[1 + j]
    y[2] = A[2 + j]
    y[3] = A[3 + j]
    y[4] = A[4 + j]
end
source
Base.Cartesian.@ncallMacro
@ncall N f sym...

Générez une expression d'appel de fonction. sym représente un nombre quelconque d'arguments de fonction, dont le dernier peut être une expression de fonction anonyme et est développé en N arguments.

Par exemple, @ncall 3 func a génère

func(a_1, a_2, a_3)

tandis que @ncall 2 func a b i->c[i] produit

func(a, b, c[1], c[2])
source
Base.Cartesian.@ncallkwMacro
@ncallkw N f kw sym...

Générez une expression d'appel de fonction avec des arguments de mot-clé kw.... Comme dans le cas de @ncall, sym représente un nombre quelconque d'arguments de fonction, dont le dernier peut être une expression de fonction anonyme et est développé en N arguments.

Exemples

julia> using Base.Cartesian

julia> f(x...; a, b = 1, c = 2, d = 3) = +(x..., a, b, c, d);

julia> x_1, x_2 = (-1, -2); b = 0; kw = (c = 0, d = 0);

julia> @ncallkw 2 f (; a = 0, b, kw...) x
-3
source
Base.Cartesian.@ntupleMacro
@ntuple N expr

Génère un N-uplet. @ntuple 2 i générerait (i_1, i_2), et @ntuple 2 k->k+1 générerait (2,3).

source
Base.Cartesian.@nallMacro
@nall N expr

Vérifiez si toutes les expressions générées par l'expression de fonction anonyme expr évaluent à true.

@nall 3 d->(i_d > 1) générerait l'expression (i_1 > 1 && i_2 > 1 && i_3 > 1). Cela peut être pratique pour vérifier les limites.

source
Base.Cartesian.@nanyMacro
@nany N expr

Vérifiez si l'une des expressions générées par l'expression de fonction anonyme expr évalue à true.

@nany 3 d->(i_d > 1) générerait l'expression (i_1 > 1 || i_2 > 1 || i_3 > 1).

source
Base.Cartesian.@nifMacro
@nif N conditionexpr expr
@nif N conditionexpr expr elseexpr

Génère une séquence d'instructions if ... elseif ... else ... end. Par exemple :

@nif 3 d->(i_d >= size(A,d)) d->(error("Dimension ", d, " trop grande")) d->println("Tout va bien")

générerait :

if i_1 > size(A, 1)
    error("Dimension ", 1, " trop grande")
elseif i_2 > size(A, 2)
    error("Dimension ", 2, " trop grande")
else
    println("Tout va bien")
end
source