A lightweight, matrix-free linear operator framework for inverse problems, signal processing, seismic imaging, and PDE/FWI kernels.
MiniOps allows you to write expressions such as:
y = A * x
x̂ = A' * y
L = R * F * A
z = L * xwhere A, F, R, … are linear operators, not matrices.
The goals:
- Minimal but powerful abstraction
- Matrix-free forward/adjoint operators
- Shape-agnostic (1D, 2D, ND arrays)
- Real/complex type-agnostic
- Clean algebra: composition, adjoint, scalar multiplication
- Essential operators:
- 1D convolution
- columnwise convolution
- sampling
- scaling & diagonal ops
- FFT / inverse FFT
- Diagnostics: adjoint test, linearity test, norm estimate (max eigenvalue of A'A)
MiniOps supports two design families of linear operators:
- Size-agnostic operators (generic, work on any array)
- Size-fixed operators (optimized, assume a known grid)
Both approaches have advantages and trade-offs.
Size-Agnostic Operators: A size-agnostic operator does not know the shape of the input a priori. It simply takes whatever array you give it and applies the transform. Example: unitary FFT defined as closures:
F = fft_op() # works for any shape
y = F * x
x2 = F' * ySize-Fixed (Planned) Operators: A size-fixed operator stores shape information at construction time. This enables precomputation and better performance.
F = fft_op(x0) # remembers size(x0)
y = F * xWhen to Use Which Approach
Use size-agnostic operators for:
- Classroom demonstrations.
- Quick experiments.
- Code that must accept arbitrary input.
Use size-fixed operators for:
- Production solvers.
- Iterative schemes (ISTA, FISTA, CG, LSQR).
- Full waveform inversion, RTM, Radon transforms and any operator used inside a loop called many times
Inside the package folder:
using Pkg
Pkg.activate(".")
Pkg.instantiate()Then:
using MiniOpsFor development:
using Pkg
Pkg.develop(path="/path/to/MiniOps")An Op represents a matrix-free linear operator:
struct Op{F,FT}
f::F # forward x -> A*x
ft::FT # adjoint y -> A'*y
m::Int # number of elements in codomain (optional)
n::Int # number of elements in domain (optional)
name::Symbol
endf/ftare closures (forward/adjoint).mandndefault to-1(shape unknown).- Operators compose:
L = R * F * A- Adjoint works automatically:
At = A'- Operators work on arrays of any shape.
| Expression | Meaning |
|---|---|
A * x |
forward |
A(x) |
same as above |
A' * y |
adjoint |
C = A * B |
composition |
size(A) |
returns (m,n) |
α * A |
scalar multiplication |
Assign meaningful (m,n) based on a sample input:
A_shaped = with_shape(A, x0)Sets:
n = length(vec(x0))m = length(vec(A*x0))
Example:
F = fft_op()
X0 = randn(ComplexF64,128,128)
F = with_shape(F, X0)
@show size(F)1D convolution operator.
A = conv1d_op(h)
y = A * xColumnwise convolution (applied to each trace).
A = conv1d_cols_op(h)
Y = A * X
Z = A' * YGeneralized sampling (masking): forward extracts samples, adjoint scatters back.
Scalar multiplication operator.
Diagonal operator: y = w .* x
Matrix-free FFT/bFFT operator.
F = fft_op()
Y = F * X
X2 = F' * YP = pad_op( (4,4,4), (2,2,2), (2,2,2))
A = P*randn(4,4,4)
# A has been padded with 2 samples at beggining and end
# of each dimValidates inner-product identity:
Checks linearity.
Power-method estimate of the spectral norm sn=‖A‖₂. Maximum eig of A'A is sn²
Tests whether A ≈ A'.
Iterative Soft-Thresholding Algorithm ||A x - y||_2^2 + mu ||x||_1
Conjugate Gradients to minmize ||A x-b||_2^2 + mu ||x||_2^2
function my_op()
f = x -> forward_code(x)
ft = y -> adjoint_code(y)
Op(f, ft; m=-1, n=-1, name=:my_op)
endRun:
adjoint_test(A, randn(size), randn(size))Check 00_demo.ipynb, 01_demo.ipynb, 02_demo.ipynb