Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Linear Operators

Open in Colab
Populating the interactive namespace from numpy and matplotlib

In linear algebra, a linear transformation, linear operator, or linear map, is a map of vector spaces T:V→WT:V \to W where

T(αv1+βv2)=αTv1+βTv2T(\alpha v_1 + \beta v_2) = \alpha T v_1 + \beta T v_2

If you choose bases for the vector spaces VV and WW, you can represent TT using a (dense) matrix. However, there are many situations where we may want to represent TT in some other format which will allow us to do faster matrix-vector and matrix-matrix multiplications.

The case of a sparse matrix is handled by special matrix formats, but there are also situations in which dense matrices can also be applied quickly.

Low Rank Matrices

The easiest situation to describe is a low-rank matrix. We saw an example of this when we first saw object-oriented programming in the LinearMap class. SciPy provides a very similar class which can be used to construct arbitrary linear operators, which is called LinearOperator.

The LinearOperator class can be found in scipy.sparse.linalg. The aslinearoperator function lets us to treat dense and sparse arrays as LinearOperators.

As an example, let’s construct a LinearOperator that acts as the matrix of all ones. This matrix is rank-1 and can be written as 11T11^T, where 1 is a vector of the appropriate dimension.

<10x20 _ProductLinearOperator with dtype=float64>
array([3.05575978, 3.05575978, 3.05575978, 3.05575978, 3.05575978, 3.05575978, 3.05575978, 3.05575978, 3.05575978, 3.05575978])
3.055759776070151
array([[1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.], [1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.], [1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.], [1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.], [1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.], [1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.], [1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.], [1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.], [1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.], [1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.]])

Let’s do a timing comparison between the LinearOperator and a dense matrix.

time for linear operator: 0.00027561187744140625 sec.
time for dense: 0.0016298294067382812 sec.
LinearOperator speedup = 5.913494809688581 x
2.1907600843918615e-12

Linear Operators from Functions

We can also specify linear operators through functions.

Another way to characterize the action of a matrix containing all 1s is that it sums up all the entries in a vector of length n, and repeats the sum in every entry in a vector of length m. For simplicity, we’ll assume that m = n.

8.881784197001252e-16
array([[2.1794517 , 0.80902747], [2.1794517 , 0.80902747], [2.1794517 , 0.80902747], [2.1794517 , 0.80902747], [2.1794517 , 0.80902747]])
array([2.1794517 , 0.80902747])

The problem is that this function doesn’t play nicely with other linear operators. In order to do that, we wrap the function in the LinearOperator class. For a LinearOperator A, We define the functions

  • matvec (computes A @ x)

  • rmatvec (computes A.T @ x)

  • matmat (computes A @ B)

  • rmatmat (computes A.T @ B) as well as the shape of the operator. Note that you don’t need to define all the functions (matvec is most important), but you may get errors in certain situations (e.g. taking transpose) if you don’t.

---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-25-e8ef22334d5e> in <module>
      1 x = np.random.rand(m)
----> 2 Afun @ x

ValueError: matmul: Input operand 0 does not have enough dimensions (has 0, gufunc core with signature (n?,k),(k,m?)->(n?,m?) requires 1)
array([5.05001422, 5.05001422, 5.05001422, 5.05001422, 5.05001422, 5.05001422, 5.05001422, 5.05001422, 5.05001422, 5.05001422])

Composition

Because of linearity, sums and products of Linear operators can also have nice properties. This is encoded in the LinearOperator class:

array([8.00550961, 8.00550961, 8.00550961, 8.00550961, 8.00550961, 8.00550961, 8.00550961, 8.00550961, 8.00550961, 8.00550961])
array([60.71496565, 60.71496565, 60.71496565, 60.71496565, 60.71496565, 60.71496565, 60.71496565, 60.71496565, 60.71496565, 60.71496565])
(scipy.sparse.linalg.interface._SumLinearOperator, scipy.sparse.linalg.interface._ProductLinearOperator)

Exercises

  1. Define a LinearOperator that “centers” a vector: A: x -> x - mean(x). i.e. we subtract the mean of the vector from every entry of the vector.

0.0
5.087681048627601e-16
  1. Define a LinearOperator that returns the differences in a vector. i.e. A is a n-1 x n operator, where (A @ x)[i] is x[i+1] - x[i]. You may want to look at np.diff.

Notebook Cell
1.2212453270876722e-15
Notebook Cell
0.0
  1. In both the above exercises, you could define these linear operators using either functions or a combination of dense/sparse matrices. If you completed an exercise above using a function, write a second version which uses dense/sparse matrices. If you completed an exercise above using dense/sparse matrices, write a second version that uses functions.