Base.Cartesian

El módulo (no exportado) Cartesian proporciona macros que facilitan la escritura de algoritmos multidimensionales. Más a menudo, puedes escribir tales algoritmos con straightforward techniques; sin embargo, hay algunos casos en los que Base.Cartesian sigue siendo útil o necesario.

Principles of usage

Un ejemplo simple de uso es:

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

que genera el siguiente código:

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 general, Cartesian te permite escribir código genérico que contiene elementos repetitivos, como los bucles anidados en este ejemplo. Otras aplicaciones incluyen expresiones repetidas (por ejemplo, desenrollado de bucles) o crear llamadas a funciones con un número variable de argumentos sin usar el constructo "splat" (i...).

Basic syntax

La sintaxis (básica) de @nloops es la siguiente:

  • El primer argumento debe ser un entero (no una variable) que especifique el número de bucles.
  • El segundo argumento es el prefijo de símbolo utilizado para la variable del iterador. Aquí usamos i, y se generaron las variables i_1, i_2, i_3.
  • El tercer argumento especifica el rango para cada variable de iterador. Si usas una variable (símbolo) aquí, se toma como axes(A, dim). Más flexiblemente, puedes usar la sintaxis de expresión de función anónima descrita a continuación.
  • El último argumento es el cuerpo del bucle. Aquí, eso es lo que aparece entre begin...end.

Hay algunas características adicionales de @nloops descritas en el reference section.

@nref sigue un patrón similar, generando A[i_1,i_2,i_3] a partir de @nref 3 A i. La práctica general es leer de izquierda a derecha, por lo que @nloops es @nloops 3 i A expr (como en for i_2 = axes(A, 2), donde i_2 está a la izquierda y el rango está a la derecha) mientras que @nref es @nref 3 A i (como en A[i_1,i_2,i_3], donde el arreglo viene primero).

Si estás desarrollando código con Cartesian, puede que encuentres que depurar es más fácil cuando examinas el código generado, utilizando @macroexpand:

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

Supplying the number of expressions

El primer argumento de ambos macros es el número de expresiones, que debe ser un entero. Cuando estás escribiendo una función que pretendes que funcione en múltiples dimensiones, esto puede no ser algo que desees codificar de forma rígida. El enfoque recomendado es usar una @generated function. Aquí hay un ejemplo:

@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

Naturalmente, también puedes preparar expresiones o realizar cálculos antes del bloque quote.

Anonymous-function expressions as macro arguments

Quizás la característica más poderosa en Cartesian es la capacidad de proporcionar expresiones de funciones anónimas que se evalúan en el momento del análisis. Consideremos un ejemplo simple:

@nexprs 2 j->(i_j = 1)

@nexprs genera n expresiones que siguen un patrón. Este código generaría las siguientes declaraciones:

i_1 = 1
i_2 = 1

En cada declaración generada, un j "aislado" (la variable de la función anónima) se reemplaza por valores en el rango 1:2. Hablando en términos generales, Cartesian emplea una sintaxis similar a LaTeX. Esto te permite hacer matemáticas sobre el índice j. Aquí hay un ejemplo que calcula los pasos de un arreglo:

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

generar expresiones

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

Las expresiones de funciones anónimas tienen muchos usos en la práctica.

Macro reference

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

Genera N bucles anidados, utilizando itersym como el prefijo para las variables de iteración. rangeexpr puede ser una expresión de función anónima, o un símbolo simple var, en cuyo caso el rango es axes(var, d) para la dimensión d.

Opcionalmente, puedes proporcionar expresiones "pre" y "post". Estas se ejecutan primero y al final, respectivamente, en el cuerpo de cada bucle. Por ejemplo:

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

generaría:

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 solo deseas una expresión post, proporciona nothing para la expresión pre. Usando paréntesis y punto y coma, puedes proporcionar expresiones de múltiples declaraciones.

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

Generar expresiones como A[i_1, i_2, ...]. indexexpr puede ser un prefijo de símbolo de iteración, o una expresión de función anónima.

Ejemplos

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

Genera N variables esym_1, esym_2, ..., esym_N para extraer valores de isym. isym puede ser un Symbol o una expresión de función anónima.

@nextract 2 x y generaría

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

mientras que @nextract 3 x d->y[2d-1] produce

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

Generar N expresiones. expr debe ser una expresión de función anónima.

Ejemplos

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...

Genera una expresión de llamada a función. sym representa cualquier número de argumentos de función, el último de los cuales puede ser una expresión de función anónima y se expande en N argumentos.

Por ejemplo, @ncall 3 func a genera

func(a_1, a_2, a_3)

mientras que @ncall 2 func a b i->c[i] produce

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

Genera una expresión de llamada a función con argumentos de palabra clave kw.... Como en el caso de @ncall, sym representa cualquier número de argumentos de función, el último de los cuales puede ser una expresión de función anónima y se expande en N argumentos.

Ejemplos

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

Genera un N-tupla. @ntuple 2 i generaría (i_1, i_2), y @ntuple 2 k->k+1 generaría (2,3).

source
Base.Cartesian.@nallMacro
@nall N expr

Verifica si todas las expresiones generadas por la expresión de función anónima expr se evalúan como true.

@nall 3 d->(i_d > 1) generaría la expresión (i_1 > 1 && i_2 > 1 && i_3 > 1). Esto puede ser conveniente para la verificación de límites.

source
Base.Cartesian.@nanyMacro
@nany N expr

Verifica si alguna de las expresiones generadas por la expresión de función anónima expr evalúa a true.

@nany 3 d->(i_d > 1) generaría la expresión (i_1 > 1 || i_2 > 1 || i_3 > 1).

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

Genera una secuencia de declaraciones if ... elseif ... else ... end. Por ejemplo:

@nif 3 d->(i_d >= size(A,d)) d->(error("Dimensión ", d, " demasiado grande")) d->println("Todo OK")

generaría:

if i_1 > size(A, 1)
    error("Dimensión ", 1, " demasiado grande")
elseif i_2 > size(A, 2)
    error("Dimensión ", 2, " demasiado grande")
else
    println("Todo OK")
end
source