Base.Cartesian
Das (nicht exportierte) Cartesian-Modul bietet Makros, die das Schreiben von multidimensionalen Algorithmen erleichtern. Am häufigsten können Sie solche Algorithmen mit straightforward techniques schreiben; es gibt jedoch einige Fälle, in denen Base.Cartesian
immer noch nützlich oder notwendig ist.
Principles of usage
Ein einfaches Beispiel für die Verwendung ist:
@nloops 3 i A begin
s += @nref 3 A i
end
welches den folgenden Code generiert:
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
Im Allgemeinen ermöglicht es Cartesian, generischen Code zu schreiben, der sich wiederholende Elemente enthält, wie die geschachtelten Schleifen in diesem Beispiel. Weitere Anwendungen umfassen wiederholte Ausdrücke (z. B. Schleifenentfaltung) oder das Erstellen von Funktionsaufrufen mit variablen Argumentanzahlen, ohne die "Splat"-Konstruktion (i...
) zu verwenden.
Basic syntax
Die (grundlegende) Syntax von @nloops
ist wie folgt:
- Das erste Argument muss eine Ganzzahl (keine Variable) sein, die die Anzahl der Schleifen angibt.
- Das zweite Argument ist das Symbol-Präfix, das für die Iterator-Variable verwendet wird. Hier haben wir
i
verwendet, und die Variableni_1, i_2, i_3
wurden generiert. - Das dritte Argument gibt den Bereich für jede Iterator-Variable an. Wenn Sie hier eine Variable (Symbol) verwenden, wird sie als
axes(A, dim)
interpretiert. Flexibler können Sie die unten beschriebene Syntax für anonyme Funktionsausdrücke verwenden. - Das letzte Argument ist der Körper der Schleife. Hier ist das, was zwischen
begin...end
erscheint.
Es gibt einige zusätzliche Funktionen von @nloops
, die in der reference section beschrieben sind.
@nref
folgt einem ähnlichen Muster und erzeugt A[i_1,i_2,i_3]
aus @nref 3 A i
. Die allgemeine Praxis besteht darin, von links nach rechts zu lesen, weshalb @nloops
@nloops 3 i A expr
ist (wie in for i_2 = axes(A, 2)
, wobei i_2
links steht und der Bereich rechts steht), während @nref
@nref 3 A i
ist (wie in A[i_1,i_2,i_3]
, wobei das Array zuerst kommt).
Wenn Sie Code mit Cartesian entwickeln, stellen Sie möglicherweise fest, dass das Debuggen einfacher ist, wenn Sie den generierten Code mit @macroexpand
untersuchen:
julia> @macroexpand @nref 2 A i
:(A[i_1, i_2])
Supplying the number of expressions
Das erste Argument beider dieser Makros ist die Anzahl der Ausdrücke, die eine ganze Zahl sein muss. Wenn Sie eine Funktion schreiben, die in mehreren Dimensionen funktionieren soll, möchten Sie dies möglicherweise nicht fest codieren. Der empfohlene Ansatz ist die Verwendung einer @generated function
. Hier ist ein Beispiel:
@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
Natürlich können Sie auch Ausdrücke vorbereiten oder Berechnungen vor dem quote
-Block durchführen.
Anonymous-function expressions as macro arguments
Vielleicht ist das einzige, aber mächtigste Merkmal von Cartesian
die Möglichkeit, anonyme Funktionsausdrücke bereitzustellen, die zur Parsing-Zeit ausgewertet werden. Lassen Sie uns ein einfaches Beispiel betrachten:
@nexprs 2 j->(i_j = 1)
@nexprs
generiert n
Ausdrücke, die einem Muster folgen. Dieser Code würde die folgenden Aussagen erzeugen:
i_1 = 1
i_2 = 1
In jeder generierten Aussage wird ein "isoliertes" j
(die Variable der anonymen Funktion) durch Werte im Bereich 1:2
ersetzt. Allgemein verwendet Cartesian eine LaTeX-ähnliche Syntax. Dies ermöglicht es Ihnen, mathematische Operationen auf dem Index j
durchzuführen. Hier ist ein Beispiel zur Berechnung der Schritte eines Arrays:
s_1 = 1
@nexprs 3 j->(s_{j+1} = s_j * size(A, j))
würde Ausdrücke generieren
s_1 = 1
s_2 = s_1 * size(A, 1)
s_3 = s_2 * size(A, 2)
s_4 = s_3 * size(A, 3)
Anonyme Funktionsausdrücke haben in der Praxis viele Anwendungen.
Macro reference
Base.Cartesian.@nloops
— Macro@nloops N itersym rangeexpr bodyexpr
@nloops N itersym rangeexpr preexpr bodyexpr
@nloops N itersym rangeexpr preexpr postexpr bodyexpr
Generiere N
geschachtelte Schleifen, wobei itersym
als Präfix für die Iterationsvariablen verwendet wird. rangeexpr
kann ein Ausdruck einer anonymen Funktion oder ein einfaches Symbol var
sein, in welchem Fall der Bereich axes(var, d)
für die Dimension d
ist.
Optional kannst du "pre" und "post" Ausdrücke angeben. Diese werden jeweils zuerst und zuletzt im Körper jeder Schleife ausgeführt. Zum Beispiel:
@nloops 2 i A d -> j_d = min(i_d, 5) begin
s += @nref 2 A j
end
würde generieren:
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
Wenn du nur einen Post-Ausdruck möchtest, gib nothing
für den Pre-Ausdruck an. Mit Klammern und Semikolons kannst du mehrteilige Ausdrücke angeben.
Base.Cartesian.@nref
— Macro@nref N A indexexpr
Generiere Ausdrücke wie A[i_1, i_2, ...]
. indexexpr
kann entweder ein Iterationssymbol-Präfix oder ein anonymer Funktionsausdruck sein.
Beispiele
julia> @macroexpand Base.Cartesian.@nref 3 A i
:(A[i_1, i_2, i_3])
Base.Cartesian.@nextract
— Macro@nextract N esym isym
Generiere N
Variablen esym_1
, esym_2
, ..., esym_N
, um Werte aus isym
zu extrahieren. isym
kann entweder ein Symbol
oder ein anonyme Funktionsausdruck sein.
@nextract 2 x y
würde generieren
x_1 = y[1]
x_2 = y[2]
während @nextract 3 x d->y[2d-1]
ergibt
x_1 = y[1]
x_2 = y[3]
x_3 = y[5]
Base.Cartesian.@nexprs
— Macro@nexprs N expr
Generiere N
Ausdrücke. expr
sollte ein Ausdruck einer anonymen Funktion sein.
Beispiele
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
Base.Cartesian.@ncall
— Macro@ncall N f sym...
Generiere einen Funktionsaufruf-Ausdruck. sym
steht für eine beliebige Anzahl von Funktionsargumenten, wobei das letzte eine anonyme Funktionsausdruck sein kann und in N
Argumente erweitert wird.
Zum Beispiel erzeugt @ncall 3 func a
func(a_1, a_2, a_3)
während @ncall 2 func a b i->c[i]
func(a, b, c[1], c[2])
liefert.
Base.Cartesian.@ncallkw
— Macro@ncallkw N f kw sym...
Generiere einen Funktionsaufruf-Ausdruck mit Schlüsselwortargumenten kw...
. Wie im Fall von @ncall
steht sym
für eine beliebige Anzahl von Funktionsargumenten, von denen das letzte ein Ausdruck für eine anonyme Funktion sein kann und in N
Argumente erweitert wird.
Beispiele
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
Base.Cartesian.@ntuple
— Macro@ntuple N expr
Generiert ein N
-Tupel. @ntuple 2 i
würde (i_1, i_2)
generieren, und @ntuple 2 k->k+1
würde (2,3)
generieren.
Base.Cartesian.@nall
— Macro@nall N expr
Überprüfen Sie, ob alle von dem anonymen Funktionsausdruck expr
generierten Ausdrücke zu true
ausgewertet werden.
@nall 3 d->(i_d > 1)
würde den Ausdruck (i_1 > 1 && i_2 > 1 && i_3 > 1)
generieren. Dies kann für die Überprüfung von Grenzen nützlich sein.
Base.Cartesian.@nany
— Macro@nany N expr
Überprüfen Sie, ob eine der durch den anonymen Funktionsausdruck expr
generierten Ausdrücke true
ergibt.
@nany 3 d->(i_d > 1)
würde den Ausdruck (i_1 > 1 || i_2 > 1 || i_3 > 1)
generieren.
Base.Cartesian.@nif
— Macro@nif N bedingungsexpr expr
@nif N bedingungsexpr expr sonstexpr
Generiert eine Sequenz von if ... elseif ... else ... end
Anweisungen. Zum Beispiel:
@nif 3 d->(i_d >= size(A,d)) d->(error("Dimension ", d, " zu groß")) d->println("Alles OK")
würde generieren:
if i_1 > size(A, 1)
error("Dimension ", 1, " zu groß")
elseif i_2 > size(A, 2)
error("Dimension ", 2, " zu groß")
else
println("Alles OK")
end