Operator Algebra Logic#

At the heart of Pyxu’s microservice architecture is a powerful operator algebra logic allowing to create versatile and complex operators/functionals from fundamental building blocks. In this section, we’ll guide you through how to create and manipulate operators to construct intricate functionals and mappings. By the end, you’ll be an expert in leveraging the power of Pyxu’s operator algebra!

Arithmetic Operations on Operators#

In Pyxu, you can use a variety of arithmetic operations to combine basic operators into more complex ones. Here are some simple but powerful commands you can use:

op1 + op2       # Addition of two operators
op1 * op2       # Composition of two operators
op ** 3         # Exponentiation of an operator
op.argscale(c)  # Dilation by a scalar 'c'
op.argshift(x)  # Shifting by a vector 'x'
4 * op          # Scaling by a scalar
op.T            # Transposing

How Does it Work? 🛠️#

Every time you perform an arithmetic operation, Pyxu automatically infers the output type based on the properties of the operators involved in the operation. This type inference is super convenient because it saves you from manual calculations!

For example, Pyxu takes care of updating as needed methods like apply()🔗, jacobian()🔗, grad()🔗, prox()🔗, and adjoint()🔗 according to arithmetic rules. This means you can plug these composite operators directly into proximal gradient algorithms without havng to implement manually gradients or proximal steps yourself.

Behind the Scenes: Arithmetic Rules#

Arithmetic inference is done via a set of arithmetic rules located in the pyxu.abc.arithmetic🔗 module:

  • Rule🔗: Base class for all arithmetic rules.

  • ScaleRule🔗: Handles scaling of operators by scalars.

  • ArgScaleRule🔗: Manages the dilation of operator parameters.

  • ArgShiftRule🔗: Takes care of shifting operator parameters.

  • AddRule🔗: Manages the addition of two operators.

  • ChainRule🔗: Deals with the composition of two operators.

  • PowerRule🔗: Handles operator exponentiation..

  • TransposeRule🔗: Takes care of transposing a linear operator.

Rules are described in detail in the docstrings of the classes above. As an example, consider the composition of a DiffFunc🔗 f with a DiffMap🔗 L. Then their composition h = f * L is also a DiffFunc, with gradient given by:

h.grad(x) = L.jacobian(x).adjoint(f.grad(L(x)))

Building Block-Operators#

You can even define block-operators using coo_block🔗. Alternatively, higher-level functions like block🔗, block_diag🔗, stack🔗, vstack🔗, and hstack🔗 can also be used.

For example, the code below:

coo_block(
    ([A(500,1000), B(1,1000), C(500,500), D(1,3)],  # data
     [
      [0, 1, 0, 2],  # i
      [0, 0, 2, 1],  # j
     ]),
    grid_shape=(3, 3),
)

results in a block sparse composite operator of the form:

coarse_idx

0

1

2

0

A(500, 1000)

C(500, 500)

1

B(1, 1000)

2

D(1, 3)

Similarly, a functional \(h(x) = \sum_{i=1}^{3} f_i(K_ix)\), can be constructed as follows:

f = hstack([f_1, f_2, f_3]) * vstack([K_1, K_2, K_3])

Again, Pyxu takes care of all the heavy lifting by automatically inferring the output type and arithmetic methods of the block-operator from its building blocks.