<p>Note that we know the shape of the resulting <code>LazyLinearOp</code> without the need to evaluate it.</p>
<p>For a tour of all supported operations on <code>LazyLinearOp</code> objects please take a look at : <a href="TODO">link</a>.</p>
<p>For a tour of all supported operations on <code>LazyLinearOp</code> objects please take a look at : <a href="LazyLinearOp reference">https://faustgrp.gitlabpages.inria.fr/faust/last-doc/html/classpyfaust_1_1lazylinop_1_1LazyLinearOp.html</a>.</p>
"Note that we know the shape of the resulting ``LazyLinearOp`` without the need to evaluate it. \n",
"\n",
"For a tour of all supported operations on ``LazyLinearOp`` objects please take a look at : [link](TODO)."
"For a tour of all supported operations on ``LazyLinearOp`` objects please take a look at : [LazyLinearOp reference](https://faustgrp.gitlabpages.inria.fr/faust/last-doc/html/classpyfaust_1_1lazylinop_1_1LazyLinearOp.html)."
]
},
{
...
...
%% Cell type:markdown id: tags:
# Using LazyLinearOp-s
The ``LazyLinearOp`` class is a kind of linear operator (indeed, it extends the scipy [LinearOperator](https://docs.scipy.org/doc/scipy/reference/generated/scipy.sparse.linalg.LinearOperator.html) class) for which the operations you make are delayed until you choose to evaluate them.
This class is intended to lower the computational cost in situations where many operations are made on linear operators but eventually not all of them will be used / evaluated.
Theoretically, it might also be useful to distribute the calculations around many computation units.
In this notebook we shall see how to create a ``LazyLinearOp`` instance, make operations on it and finally how to evaluate it to get the real result of these operations. We assume the reader is familiar with at least numpy arrays and their operations.
%% Cell type:markdown id: tags:
### 1. Creating a LazyLinearOp
In order to create this kind of object, you simply need to use the ``asLazyLinearOp`` function. This function receives an object that represents a linear operator, for instance a ``Faust`` (but it can also be a numpy array or a scipy matrix). The function instantiates a ``LazyLinearOp`` that encapsulates the ``Faust`` you gave.
%% Cell type:code id: tags:
``` python
frompyfaust.lazylinopimportasLazyLinearOp
importpyfaustaspf
# create a random Faust
F=pf.rand(4096,4096,density=.001)
# create a LazyLinearOp upon it
lF=asLazyLinearOp(F)
print(lF)
```
%% Cell type:markdown id: tags:
As said earlier, it is also possible to create a ``LazyLinearOp`` upon numpy arrays or scipy matrices.
%% Cell type:code id: tags:
``` python
fromscipy.sparseimportrandom
fromnumpy.randomimportrand
S=random(4096,4096,.2,format='csr')# scipy matrix
lS=asLazyLinearOp(S)
M=rand(4096,4096)*1j# numpy complex array
lM=asLazyLinearOp(M)
```
%% Cell type:markdown id: tags:
Then we can start to do some operations on these new objects. For example, let's multiply ``lF`` by a scalar:
%% Cell type:code id: tags:
``` python
lF=2*lF
print(lF)
```
%% Cell type:markdown id: tags:
As you can see it stays a ``LazyLinearOp`` after the scalar multiplication. That's the principle of the lazy evaluation we talked about at the beginning of this notebook. No operation is really computed, only the track of the operations asked is kept in a new LazyLinearOp object.
Let's continue asking other operations. For example, a matrix multiplication and then a concatenation.
%% Cell type:code id: tags:
``` python
importpyfaust.lazylinopaslp
lFs=lF@lF
print("lF shape before concatenation:",lFs.shape)
lFc=lp.vstack((lFs,lFs))
print("lF shape after concatenation:",lFc.shape)
```
%% Cell type:markdown id: tags:
Note that we know the shape of the resulting LazyLinearOp without the need to evaluate it.
Let's try other operations with ``lM`` and ``lS``, all ``LazyLinearOp`` are compatible with each other.
%% Cell type:code id: tags:
``` python
lMSF=(2*lM.conj().T+lS)@lFc[:4096,:]
# then get back the imaginary part of the LazyLinearOp
lMSF_imag=lMSF.imag
```
%% Cell type:markdown id: tags:
Note that we know the shape of the resulting ``LazyLinearOp`` without the need to evaluate it.
For a tour of all supported operations on ``LazyLinearOp`` objects please take a look at : [link](TODO).
For a tour of all supported operations on ``LazyLinearOp`` objects please take a look at : [LazyLinearOp reference](https://faustgrp.gitlabpages.inria.fr/faust/last-doc/html/classpyfaust_1_1lazylinop_1_1LazyLinearOp.html).
%% Cell type:markdown id: tags:
### 2. Evaluating a LazyLinearOp
Now that we've seen how to create and operate a ``LazyLinearOp`` let's see how to get the evaluation done, that is obtaining the real results of our operations.
There are several ways to get these results. The most important way is to multiply the ``LazyLinearOp`` by a dense matrix (a numpy array) or a vector.
%% Cell type:code id: tags:
``` python
importnumpyasnp
lMSF_imag@np.random.rand(4096)
```
%% Cell type:markdown id: tags:
The other ways to evaluate is to call ``LazyLinearOp.eval`` or ``LazyLinearOp.toarray`` on our object. The former function returns an object whose type depends on the operations made so far on the starting object. The latter function returns always a numpy array which is the result of the same operations.
%% Cell type:markdown id: tags:
### 3. Checking the computation times
As a last step in this notebook, we shall verify how much computation time it takes to use a ``LazyLinearOperator`` compared to a numpy array. Of course it depends on the underlying objects used behind (in the operations encoded in the ``LazyLinearOperator``). Here we make the measurement on ``lFs`` which was initialized before upon a Faust object.
Great! As excepted ``lFs`` is faster than its numpy array counterpart ``FD``.
%% Cell type:markdown id: tags:
This notebook comes to its end. We've seen quickly how to create and evaluate a ``LazyLinearOp`` objects based on a Faust object, numpy array or scipy matrices. For more information about ``LazyLinearOp`` objects you can take a look to the API documentation [here](TODO).
**NOTE**: this notebook was executed using the pyfaust version: