Reference

FunctionOperators.setPlanFunction

Manually set the plan of a FunctionOperator Arguments:

  • FO composite FunctionOperator to be changed
  • f 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
source
FunctionOperators.FunctionOperatorType

FunctionOperator 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 mapping
  • inDims::Tuple{Vararg{Int}} Size of input array
  • outDims::Tuple{Vararg{Int}} Size of output array
source
FunctionOperators.@recycleMacro

Speed 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
source
FunctionOperators.@♻Macro

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

source
FunctionOperators.FunctionOperators_global_settingsConstant

Object 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
source
Base.:==Method

Equality 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$)
source
Base.eltypeMethod

Determine type of elements of array accepted by this operator

source