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 Variablen i_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.@nloopsMacro
@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.

source
Base.Cartesian.@nrefMacro
@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])
source
Base.Cartesian.@nextractMacro
@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]
source
Base.Cartesian.@nexprsMacro
@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
source
Base.Cartesian.@ncallMacro
@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.

source
Base.Cartesian.@ncallkwMacro
@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
source
Base.Cartesian.@ntupleMacro
@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.

source
Base.Cartesian.@nallMacro
@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.

source
Base.Cartesian.@nanyMacro
@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.

source
Base.Cartesian.@nifMacro
@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
source