Reference
FunctionOperators.setPlan
— FunctionManually set the plan of a FunctionOperator Arguments:
FO
composite FunctionOperator to be changedf
manually defined plan (function with two arguments, first is the output buffer, second is the input array)f_str
(Optional) string representation of the plan
FunctionOperators.FunOp
— TypeSupertype for FunctionOperator and FunctionOperatorComposite
FunctionOperators.FunctionOperator
— TypeFunctionOperator is an operator that maps from a multidimensional space to another multidimensional space. The mapping is defined by a function (forw
), and optionally the reverse mapping can also be defined (backw
). The input the mapping must be subtype of AbstractArray.
The following constructors are available:
- Positional constructor #1:
FunctionOperator{eltype}(forw, inDims, outDims)
- Positional constructor #2:
FunctionOperator{eltype}(forw, backw, inDims, outDims)
- Positional constructor #3:
FunctionOperator{eltype}(name, forw, inDims, outDims)
- Positional constructor #4:
FunctionOperator{eltype}(name, forw, backw, inDims, outDims)
- Keyword constructor:
FunctionOperator{eltype}(;kwargs...)
where eltype
is the type enforced on elements of input array.
Arguments
name::String
(Optional but strongly recommended) The operator is referenced later in error messages by this string. Warning! It is also used to check equality of (composite) FunctionOperators. Default value:OpX
where X is a number incremented in each constructor-call.forw::Function
Function defining the mapping. Must accept one or two arguments. In case of two arguments, the first argument is a preallocated buffer to write the result into (to speed up code by avoiding repeated allocations). In case of both one and two arguments, the return value must be the result of the mapping.backw::Function
(Optional) Same as backw, but defines the backward mappinginDims::Tuple{Vararg{Int}}
Size of input arrayoutDims::Tuple{Vararg{Int}}
Size of output array
FunctionOperators.@recycle
— MacroSpeed up iteratively executed code fragments with many matrix operations by transforming code in such a way that preserves arrays allocated for intermediate results, and re-use them for subsequent iterations.
First variant:
@recycle <code to be optimized>
Second variant:
@recycle(arrays = [<list of array variables>], funops = [<list of funop variables], numbers = [<list of number variables>], <code to be optimized>)
The first variant is the more convenient one that tries to guess the type of variables (the other variant requires its user to declare explicitly the list of variables which are type of Array, FunOp, and Number. As a tradeoff, this variant fails when the optimized code contains either a closure or non-const global variable.
The second variant is the more flexible (and also more verbose) one one that requires its user to declare explicitly the list of variables which are type of Array, FunOp, and Number. The other variant tries to guess the type of variables, thus it is more convenient, but as a tradeoff, that variant fails when the optimized code contains either a closure or non-const global variable. On the other hand, this (more verbose) variant is free from these limitations. Note: All of the "keyword arguments" are optional, and also their order is arbitrary.
An example to first variant: This function
function foo()
A = rand(100,100)
B = rand(100,100)
@recycle for i = 1:5
A += A / 2 + B
C = A * B + 5
end
end
is turned into the following:
function foo()
A = rand(100,100)
B = rand(100,100)
(C, 🔃₂) = fill(nothing, 2)
(is_first_run₁,) = fill(true, 1)
for i = 1:5
A .+= A ./ 2 .+ B
if is_first_run₁
is_first_run₁ = false
🔃₂ = A * B
C = 🔃₂ .+ 5
else
mul!(🔃₂, A, B)
C .= 🔃₂ .+ 5
end
end
end
Another example showing what second variant can do (and the first can't):
bar = @recycle(arrays=[A,B], (A, B) -> begin
A += A / 2 + B
B = A * B .+ 5
end)
function baz()
A = rand(100,100)
B = rand(100,100)
for i = 1:5
bar(A,B)
end
end
is turned into the following:
bar = begin
(🔃₁,) = fill(nothing, 1)
(is_first_run₁,) = fill(true, 1)
(A, B)->begin
A .+= A ./ 2 .+ B
begin
if is_first_run₁
is_first_run₁ = false
🔃₁ = A * B
else
mul!(🔃₁, A, B)
end
B .= 🔃₁ .+ 5
end
end
end
function baz()
A = rand(100,100)
B = rand(100,100)
for i = 1:5
bar(A,B)
end
end
FunctionOperators.@♻
— MacroRecycling macro: Reduce the number of allocations inside a for loop by preallocation of arrays for the outputs of marked operations. Markers: @♻
(\:recycle:
), 🔝
(\:top:
), 🔃
(\:arrows_clockwise:
), and @🔃
Macro @♻ should be placed right before a for loop, and then it executes the following substitutions:
- Expressions marked by
🔝
:
They are going to be calculated before the loop, the result is stored in a variable, and the expression will be replaced by that variable. It also can be useful when a constant expression is used in the loop, but the idea behind creating that substitution is to allow caching of composite FunctionMatrices. Eg:
@♻ for i=1:5
result = 🔝((FuncOp₁ + 2I) * FuncOp₂) * data
end
will be transformed to
🔝_1 = (FuncOp₁ + 2I) * FuncOp₂
for i = 1:5
result = 🔝_1 * data
end
so that way plan is calculated only once, and also buffers for intermediate results of the composite operator are allocated once.
- Expressions marked by
🔃
:
They are going to be calculated before the loop (to allocate an array to store the result), but the expression is also evaluated in each loop iteration. The difference after the substitution is that the result of the expression is always saved to the preallocated array. Eg:
@♻ for i=1:5
result = FuncOp₁ * 🔃(A + B)
end
will be transformed to
🔃_1 = A + B
for i = 1:5
result = FuncOp₁ * @.(🔃_1 = A + B)
end
This transformation first allocates an array named 🔃_1
, and then in every iteration it is recalculated, saved to 🔃_1
, and the this value is used for the rest of the operation (i.e.: FuncOp₁ * 🔃_1
. Note that @.
macro is inserted before the inline assignment. This is needed otherwise A + B
would allocate a new array before it is stored in 🔃_1
. Warning! It can break your code, e.g. @.(🔃_1 = A * B) ≠ (🔃_1 = A .* B)
{matrix multiplication vs. elementwise multiplication}! On the other hand, when the marked expression consists only a multiplication, then it is transformed into a call of mul!
. Eg:
@♻ for i=1:5
result = FuncOp₁ * 🔃(A * B)
end
will be transformed to
🔃_1 = A * B
for i = 1:5
result = FuncOp₁ * mul!(🔃_1, A, B)
end
- Lastly, assignments marked by
@🔃
:
They will be transformed into a call of mul!
. Of course, it works only if @🔃
is directly followed by an assignment that has a single multiplication on the right side. Eg:
@♻ for i=1:5
@🔃 result = FuncOp₁ * A
end
will be transformed to
result = FuncOp₁ * A
for i = 1:5
mul!(result, FuncOp₁, A)
end
Final note: 🔝
can be arbitrarily nested, and it can be embedded in expressions marked by 🔃
. 🔃
can also be nested, and it can be used in assigments marked by @🔃
(along with 🔝
, of course).
FunctionOperators.FunctionOperators_global_settings
— ConstantObject that holds global settings for FunctionOperators
library
Fields:
verbose::Bool
If set to true, then allocation information and calculated plan function will be displayed upon creation (i.e., when a composite operator is first used). Default:false
macro_verbose::Bool
If set to true, then recycling macros (@♻ and @recycle) will print the transformed code. Default:false
auto_reshape::Bool
If set to true, then input and output is reshaped according to the inDims and outDims values of the FunctionOperator before and after any multiplication. Default:false
Base.:==
— MethodEquality check based on name
HEADS UP! This works only when operators with the same name has also the same functionality!
It performs basic arithmetic transformations on the expressions, so it recognizes even some less obvious equalities. The rules it uses:
- Associativity: $op1 * op2 * op3 = (op1 * op2) * op3 = op1 * (op2 * op3)$, $op1 + op2 + op3 = (op1 + op2) + op3 = op1 + (op2 + op3)$, $op1 + (op2 - op3) == (op1 + op2) - op3$, $op1 - (op2 + op3) == (op1 - op2) - op3$
- Commutativity: $op1 - op2 - op3 = op1 - op3 - op2$, $op1 + op2 = op2 + op1$
- Distributivity: $(op1 + op2) * op3 = op1 * op3 + op2 * op3$ (Note that $op1 * (op2 + op3) ≠ op1 * op2 + op1 * op3$)
Base.eltype
— MethodDetermine type of elements of array accepted by this operator