Mentions légales du service

Skip to content
Snippets Groups Projects
Commit ce69f1af authored by hhakim's avatar hhakim
Browse files

Mention the FAQ entry about F[I, J] in the pyfaust notebook part about slicing...

Mention the FAQ entry about F[I, J] in the pyfaust notebook part about slicing and indexing + other note about column-major order of matrices in FAµST.
parent 62a4cbcb
Branches
Tags 3.27.1
No related merge requests found
Pipeline #834079 skipped
This diff is collapsed.
%% Cell type:markdown id: tags:
# How to Manipulate a Faust
<a name="start"></a>
This notebook is intended to gently introduce the operations available to manipulate a Faust object.
It comes after the first notebook (available [here](https://faustgrp.gitlabpages.inria.fr/faust/last-doc/html/Faust_creation.ipynb) for download or [here](https://faustgrp.gitlabpages.inria.fr/faust/last-doc/html/Faust_creation.html) as a web page), so it's assumed you already know how to create a Faust object from one way or another.
Keep in mind that a full API doc is available [here](https://faustgrp.gitlabpages.inria.fr/faust/last-doc/html/namespacepyfaust.html) every time you need details. In particular the Faust class is documented [here](https://faustgrp.gitlabpages.inria.fr/faust/last-doc/html/classpyfaust_1_1Faust.html).
**NOTE**: the notebook is made to be executed sequentially, otherwise, skipping some cells, you would end up on an import error.
# Table of Contents:
[**1. Getting Basic Information about a Faust Object**](#1.-Getting-Basic-Information-about-a-Faust-Object)<br/>
[1.1 Obtaining Dimension and Scalar Type Information](#1.1-Obtaining-Dimension-and-Scalar-Type-Information)<br/>
[1.2 Obtaining Other Faust Specific Information](#1.2-Obtaining-Other-Faust-Specific-Information)<br/>
[1.3 Plotting a Faust](#1.3-Plotting-a-Faust)<br/>
[1.4 About Sparsity!](#1.4-About-Sparsity!)<br/>
[**2. Faust Algebra and other Operations**](#2.-Faust-Algebra-and-other-Operations)<br/>
[2.1 Transpose, conjugate, transconjugate](#2.1-Transpose,-conjugate,-transconjugate)<br/>
[2.2 Add, Subtract and Multiply](#2.2-Add,-Subtract-and-Multiply)<br/>
[2.3 Faust Multiplication by a Vector or a Matrix](#2.3-Faust-Multiplication-by-a-Vector-or-a-Matrix)<br/>
[2.4 Faust Norms](#2.4-Faust-Norms)<br/>
[2.5 Faust Normalizations](#2.5-Faust-Normalizations)<br/>
[2.6 Faust Concatenation](#2.6-Faust-Concatenation)<br/>
[2.7 Faust Indexing and Slicing](#2.7-Faust-Indexing-and-Slicing)<br/>
# 1. Getting Basic Information about a Faust Object
First of all, given any object, you might ask yourself if it's a Faust or not (typically when you receive an object in a function, python being built on dynamic types, you can't say for sure it's a Faust).
[Faust.isFaust()](https://faustgrp.gitlabpages.inria.fr/faust/last-doc/html/classpyfaust_1_1Faust.html#a965047e6c0161e59a1c3e08f2b60e806) is the function to verify an object is a Faust.
Its use is straighforward as you can see in the [documentation](https://faustgrp.gitlabpages.inria.fr/faust/last-doc/html/classpyfaust_1_1Faust.html#a965047e6c0161e59a1c3e08f2b60e806). Note by the way, that a more accessible alias is available at the package root ([pyfaust.isFaust](https://faustgrp.gitlabpages.inria.fr/faust/last-doc/html/namespacepyfaust.html#a8e9ce6bb49c094e7380fed832e23baba)).
### 1.1 Obtaining Dimension and Scalar Type Information
Firstly, let's list basic Faust informative methods/attributes you're probably used to for numpy arrays:
- [shape](https://faustgrp.gitlabpages.inria.fr/faust/last-doc/html/classpyfaust_1_1Faust.html#a64b167c138d18a23bb5b9794fcb03ef1),
- [size](https://faustgrp.gitlabpages.inria.fr/faust/last-doc/html/classpyfaust_1_1Faust.html#a6505fdc600b4868bbe77d8fc4a48b350),
- [dtype](https://faustgrp.gitlabpages.inria.fr/faust/last-doc/html/classpyfaust_1_1Faust.html#ad0615fb9508899a95af2c7de42377e91).
To keep it really clear, let's show some examples performed on a random Faust.
%% Cell type:code id: tags:
``` python
from pyfaust import rand
F = rand(5,10)
print(F)
```
%% Cell type:code id: tags:
``` python
F.shape
```
%% Cell type:code id: tags:
``` python
F.size
```
%% Cell type:code id: tags:
``` python
F.dtype
```
%% Cell type:markdown id: tags:
If the attributes printed out above seem not clear to you, you're probably not a numpy user. Anyway you'll find all descriptive informations in the FAµST API documentation (see the links [above](#start)).
As a complement, you can also refer to the numpy API documentation:
- [shape](https://docs.scipy.org/doc/numpy/reference/generated/numpy.ndarray.shape.html)
- [size](https://docs.scipy.org/doc/numpy/reference/generated/numpy.ndarray.size.html)
- [dtype](https://docs.scipy.org/doc/numpy/reference/generated/numpy.ndarray.dtype.html)
About shape, it's noteworthy that contrary to what numpy is capable of, you _cannot_ [reshape](https://docs.scipy.org/doc/numpy/reference/generated/numpy.ndarray.reshape.html) a Faust.
%% Cell type:markdown id: tags:
### 1.2 Obtaining Other Faust Specific Information
As you've seen in this notebook and the [first one](#start), when you print a Faust object, several pieces of information appear.
Most of them are also available independently with specific functions.
For instance, if you want information about factors, nothing is more simple than calling directly the next functions:
- [numfactors()](https://faustgrp.gitlabpages.inria.fr/faust/last-doc/html/classpyfaust_1_1Faust.html#ab6e7f82d0a34b9ec90c73f9c887a2b51) ; which gives you the number of factors (aka layers) a Faust object is composed of.
- [factors()](https://faustgrp.gitlabpages.inria.fr/faust/last-doc/html/classpyfaust_1_1Faust.html#a9cefcbcee255b2af0747b6d7693b26b4) ; which allows you to copy any of the Faust's factors given its index.
Going back to our F object, let's call these functions:
%% Cell type:code id: tags:
``` python
F.numfactors()
```
%% Cell type:markdown id: tags:
For example, try to copy the third factor:
%% Cell type:code id: tags:
``` python
f3 = F.factors(2)
f3
```
%% Cell type:markdown id: tags:
Note that, since FAµST 2.3, the function doesn't alterate the factor format. If the Faust object contains a sparse factor then you'll receive a sparse (CSR) matrix.
%% Cell type:markdown id: tags:
Since FAµST 2.3 again, it's possible to retrieve a sub-sequence of Faust factors.
Go straight to the example, extracting factors from F:
%% Cell type:code id: tags:
``` python
F.factors(range(2,4))
```
%% Cell type:markdown id: tags:
Hmm... something is different from the previous example. We indeed received a Faust as return type, great! You've just learned another way to create a Faust from another, additionally to what you've seen in the first [notebook](#start).
Without this function, you'd surely have written something similar to:
%% Cell type:code id: tags:
``` python
from pyfaust import Faust
F2 = Faust([F.factors(2), F.factors(3)])
F2
```
%% Cell type:markdown id: tags:
OK, that's not awful but I let you imagine how much complicated it is with more factors (even with a list comprehension, it's longer to write).
### 1.3 Plotting a Faust
It's quite useful to print a Faust as we've seen before, calling ``print(F)`` or ``F.display()`` or just ``F`` in an interactive terminal but this is wordy.
How about plotting a Faust in a more graphical fashion?
%% Cell type:code id: tags:
``` python
import matplotlib.pyplot as plt
%matplotlib inline
F.imshow()
plt.show()
```
%% Cell type:markdown id: tags:
What do we see above? On the bottom right is the dense matrix associated to F, obtained with ```F.toarray()```. On the top are the indexed factors of F.
Note that you can change the default [colormap](https://matplotlib.org/tutorials/colors/colormaps.html) in matplotlib parameters.
Let's look at a last example:
%% Cell type:code id: tags:
``` python
from pyfaust import Faust
from numpy import eye
Faust([eye(5,4),eye(4,10)]).imshow()
```
%% Cell type:markdown id: tags:
The dimension ratio of the factors is respected in the figure.
### 1.4 About Sparsity!
Three functions of the Faust class are here to evaluate the sparsity of a Faust object.
Let's call the first one:
%% Cell type:code id: tags:
``` python
F.nnz_sum()
```
%% Cell type:markdown id: tags:
I'm sure you guessed exactly what the function returns, if you doubt it, here is the doc: [Faust.nnz_sum()](https://faustgrp.gitlabpages.inria.fr/faust/last-doc/html/classpyfaust_1_1Faust.html#a216c598bb384d92b4e37843b67c42255). The smaller ```nnz_sum```, the sparser the Faust.
Next comes the function: [Faust.density()](https://faustgrp.gitlabpages.inria.fr/faust/last-doc/html/classpyfaust_1_1Faust.html#af73ecb7c5fca3c7673c665d8ba4058f6).
This function along with its reciprocal [Faust.rcg()](https://faustgrp.gitlabpages.inria.fr/faust/last-doc/html/classpyfaust_1_1Faust.html#a6a51a05c20041504a0b8f2a73dd8d05a) can give you a big hint on how much your Faust is potentially optimized both for storage and calculation. The sparser the Faust, the larger the Relative Complexity Gain (RCG)!
%% Cell type:code id: tags:
``` python
F.density()
```
%% Cell type:code id: tags:
``` python
F.rcg()
```
%% Cell type:markdown id: tags:
According to its RCG, this Faust doesn't seem to be of any help for optimization but look at the graphic the next script generates:
%% Cell type:code id: tags:
``` python
from pyfaust import Faust, rand
import numpy as np
import os
import matplotlib.pyplot as plt
nfactors = 3
startd = 0.01
endd = 1
dim_sz = 1000
sizes = []
rcs = []
ntests = 10
for i, d in zip(
list(range(0, ntests)),
np.linspace(startd, endd, ntests)):
F = rand(dim_sz, dim_sz, nfactors, density=d, fac_type='sparse')
filepath = 'test_faust_size'+str(i)+'.mat'
F.save(filepath)
stat = os.stat(filepath)
sizes.append(stat.st_size)
rcs.append(F.density())
os.remove(filepath)
plt.title('File Size vs Density for Pure-Sparse Fausts \n('+str(nfactors)+' '
'random factors '+str(dim_sz)+'x'+str(dim_sz)+')')
plt.xlabel('Density')
plt.ylabel('File Size (bytes)')
plt.rcParams['figure.figsize'] = [8.0, 6]
plt.tight_layout()
plt.plot(rcs, sizes)
plt.show()
```
%% Cell type:markdown id: tags:
Isn't it obvious now that the smaller the density the better?! Indeed, for two Faust objects of the same shape and the same number of factors, a smaller density (linearly) implies a smaller file size for storage.
This point applies also to the memory (RAM) space to work on a Faust.
We'll see later how the computation can benefit of a larger RCG (or smaller density). But let's point out right now that when it comes to matrix factorizations the sparsity is often a tradeoff with accuracy, as the following plot shows about the truncated [SVD](https://docs.scipy.org/doc/numpy/reference/generated/numpy.linalg.svd.html) of a matrix $M \in {\mathbb R}^{512 \times 512}$. Note beforehand that the SVD of M (truncated or not) can be seen as a Faust which approximates M.
```
Faust([U,S,Vt])
```
<img src="data:image/png;base64,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
/vaKFSsUHh6up59+Wjk5OcrJyVHHjh01bdo0bdiwwans+vXrFRERoU6dOtFoAEgAAeBedenSRVlZ
WUpJSdHJkyf1wQcfKCwsTA8++KCWLl2qZs2aacuWLY7ypaWl2rhxo0JCQhQREaGYmBh9+OGHjgSw
uLhYY8eOVWBgoLp3766f//znaty4cYW/6+PjI09PTzVs2FB+fn7y8/OT3W7Xk08+qTNnzujo0aOS
pKKiIr3zzjuaNm0ajQWABBAAaoIxRjabTceOHdO1a9fUsmVLNW7c2PE4f/6809RrYGCgmjRp4nju
7++vvLw8SVKPHj00ePBgde/eXePGjdPatWv11VdfVev9+Pv7a9SoUVq/fr0kaefOnbp586bGjRtH
YwGwJHcOAYC65vTp0woKClJpaan8/f2VmppaoUz5Czc8PDyc/s1mszmuJLbb7UpOTtahQ4e0b98+
vfbaa3rppZeUnp6uoKCgO35PTz31lGJiYrR8+XJt2LBB0dHRatiwIY0FgAQQAO7V/v37dfLkSc2a
NUvt27dXbm6u3N3dFRgYeNf7tNls6tevn/r166eXX35ZAQEBSkxMVGxsbIWynp6eKikpqbB95MiR
atSokVavXq3du3frwIEDNBYAEkAAqK6CggLl5uaqpKRE//rXv7Rnzx4tXLhQo0eP1uTJk+Xm5qbw
8HCNGTNGixcvVnBwsC5evKikpCSNGTNGYWFht/0b6enp+vDDDxUVFaU2bdooPT1d//73v9W1a9dK
ywcGBio9PV1ZWVlq3LixWrRoITc3N9ntdk2dOlVz5szRAw88oPDwcBoQgGXxG0AAlrVnzx75+/sr
MDBQw4cPV0pKilauXKnt27fLbrfLZrMpKSlJAwYM0LRp09S5c2dNmDBBWVlZ8vX1vaO/0bRpUx04
cEAjR45U586dNXfuXC1btkwjRoyotPyvfvUr2e12devWTa1bt9aFCxcc/zZ9+nQVFhZy8QcAy7OZ
ytY6AABU28cff6zIyEj985//vOMEFABIAAGgDiooKFB2draeeeYZ+fv769133+WgALA0poAB4B7F
x8crODhYV65c0ZIlSzggACyPEUAAAAAXwwggAAAACSAAAABIAAEAAEACCAAAABJAAAAAkAACAACA
BBAAAAAkgAAAACABBAAAAAkgAAAASAABAABAAggAAAASQAAAAJAAAgAAuK7/B1ySYZMrherMAAAA
AElFTkSuQmCC"/>
Here is the script to reproduce the last figure: [test_svd_rc_vs_err.py](https://faustgrp.gitlabpages.inria.fr/faust/last-doc/html/test_svd_rc_vs_err.py)
%% Cell type:markdown id: tags:
# 2. Faust Algebra and other Operations
%% Cell type:markdown id: tags:
In order to write some nice algorithms using Faust objects, you'll have to use the basic "stable" operations a Faust is capable of. Let's make a tour of them.
### 2.1 Transpose, conjugate, transconjugate
- [Faust.T](https://faustgrp.gitlabpages.inria.fr/faust/last-doc/html/classpyfaust_1_1Faust.html#a064a412cac321f8211098db0fae84e8e)
- [Faust.conj](https://faustgrp.gitlabpages.inria.fr/faust/last-doc/html/classpyfaust_1_1Faust.html#abd273a0e1684a7b238798ac82cd07a61)
- [Faust.H](https://faustgrp.gitlabpages.inria.fr/faust/last-doc/html/classpyfaust_1_1Faust.html#ac7b77cdde5f66f85b6458594aa01ad31)
You are probably familiar with [T](https://docs.scipy.org/doc/numpy/reference/generated/numpy.matrix.T.html) and [H](https://docs.scipy.org/doc/numpy/reference/generated/numpy.matrix.H.html) attributes/properties from numpy/scipy. Well, they are also used in the Faust class.
%% Cell type:code id: tags:
``` python
G = rand(10, 15, dim_sizes=[10,15],field='complex')
```
%% Cell type:code id: tags:
``` python
G.T
```
%% Cell type:code id: tags:
``` python
G.conj()
```
%% Cell type:code id: tags:
``` python
G.H
```
%% Cell type:markdown id: tags:
What really matters here is that the results of ``G.T``, ``G.conj()`` and ``G.H`` are all Faust objects. Behind the scene, there is just one memory zone allocated to the factors. Strictly speaking they are memory views shared between ``G``, ``G.T`` and ``G.H``. So don't hesitate to use!
%% Cell type:markdown id: tags:
### 2.2 Add, Subtract and Multiply
%% Cell type:code id: tags:
``` python
F = rand(G.shape[0], G.shape[0])
G = rand(G.shape[0], G.shape[0], field='complex')
F+G
```
%% Cell type:markdown id: tags:
Go ahead and verify it's accurate.
%% Cell type:code id: tags:
``` python
from numpy.linalg import norm
norm((F+G).toarray()-(F.toarray()+G.toarray()))
```
%% Cell type:markdown id: tags:
Some points are noticeable here:
- F is real (dtype float), but G is complex. The Faust API is able to return the proper type for the resulting Faust, that is a complex Faust.
- F+G is composed of 8 factors, however F and G are both 5 factors long. It's due to the way the addition is implemented (Faust concatenation is hiding behind).
Subtracting is not different:
%% Cell type:code id: tags:
``` python
F-G
```
%% Cell type:markdown id: tags:
You can also add/subtract scalars to Faust objects.
%% Cell type:code id: tags:
``` python
F+2
```
%% Cell type:markdown id: tags:
Note that here again ```(F+2).numfactors() != F.numfactors()```
But conversely you can also add/subtract a Faust to a scalar (pay attention to the order of operands):
%% Cell type:code id: tags:
``` python
2+F
```
%% Cell type:code id: tags:
``` python
2-F
```
%% Cell type:markdown id: tags:
The FAµST API supports equally the Faust to array addition and subtraction.
%% Cell type:code id: tags:
``` python
F.toarray()+F # or F+F.toarray()
```
%% Cell type:code id: tags:
``` python
F-F.toarray() # F.toarray()-F is supported too
```
%% Cell type:code id: tags:
``` python
from numpy import zeros, allclose
allclose((F.toarray()-F).toarray(), zeros(F.shape))
```
%% Cell type:markdown id: tags:
Now let's multiply these Fausts!
%% Cell type:code id: tags:
``` python
FG = F@G
```
%% Cell type:code id: tags:
``` python
norm(FG.toarray()-F.toarray()@G.toarray())/norm(F.toarray()@G.toarray())
```
%% Cell type:markdown id: tags:
The relative error proves it's working.
FG is a Faust too!
%% Cell type:code id: tags:
``` python
Faust.isFaust(FG)
```
%% Cell type:markdown id: tags:
Faust scalar multiplication is also available and here again the result is a Faust object!
%% Cell type:code id: tags:
``` python
F*2 # or 2*F
```
%% Cell type:markdown id: tags:
### 2.3 Faust Multiplication by a Vector or a Matrix
When you multiply a Faust by a vector or a matrix (the number of nrows must match the number of Faust columns), you'll get respectively a vector or a matrix as result.
%% Cell type:code id: tags:
``` python
from numpy.random import rand as nrand
vec = nrand(F.shape[1],1)
F@vec # or F.dot(vec)
```
%% Cell type:markdown id: tags:
Let's launch a timer to compare the execution times of Faust-vector multiplication and Faust's dense matrix-vector multiplication
Note that appliying a Faust on a ```numpy.ndarray``` can also be done with the function dot().
%% Cell type:code id: tags:
``` python
timeit F@vec
```
%% Cell type:code id: tags:
``` python
FD = F.toarray()
```
%% Cell type:code id: tags:
``` python
timeit FD@vec
```
%% Cell type:code id: tags:
``` python
from numpy import allclose
allclose(F@vec,FD@vec)
```
%% Cell type:code id: tags:
``` python
F.rcg()
```
%% Cell type:markdown id: tags:
When the RCG is lower than 1 the Faust-vector multiplication is slower. Making a random Faust with a large RCG (small density) shows better results.
%% Cell type:code id: tags:
``` python
G = rand(1024, 1024, num_factors=3, density=.001, fac_type='sparse')
GD = G.toarray()
vec2 = nrand(1024, 1)
```
%% Cell type:code id: tags:
``` python
timeit G@vec2
```
%% Cell type:code id: tags:
``` python
timeit GD.dot(vec2)
```
%% Cell type:code id: tags:
``` python
G.rcg()
```
%% Cell type:markdown id: tags:
It goes without saying that a big RCG gives a big speedup to the Faust-vector multiplication relatively to the corresponding (dense) matrix-vector multiplication.
I hope the example above has finished to convince you.
Just to convince you as well of the Faust-vector multiplication accuracy:
%% Cell type:code id: tags:
``` python
from numpy.linalg import norm
norm(G@vec2 - GD@vec2)
```
%% Cell type:markdown id: tags:
What applies to Faust-vector multiplication remains valid about Faust-matrix multiplication. Take a look:
%% Cell type:code id: tags:
``` python
M = nrand(1024,32)
M = np.asfortranarray(nrand(1024,32))
```
%% Cell type:markdown id: tags:
**NOTE**: [numpy.asfortranarray](https://numpy.org/doc/stable/reference/generated/numpy.asfortranarray.html) is used to convert the array from row-major order to column-major order. Indeed pyfaust works on matrix in the latter type of storage, so it's faster to multiply a matrix that is directly set up in this format. Otherwise the conversion is made on the fly in the multiplication (which is slower than doing it once and for all if you repeat the computation).
%% Cell type:code id: tags:
``` python
timeit G@M
```
%% Cell type:code id: tags:
``` python
timeit GD@M
```
%% Cell type:code id: tags:
``` python
norm(GD@M-G@M)/norm(GD@M)
```
%% Cell type:markdown id: tags:
Well, what do we see? A quicker Faust-matrix multiplication than the matrix-matrix corresponding multiplication, though a good accuracy of the Faust-matrix multiplication is also clearly confirmed.
These examples are somehow theoretical because we cherry-pick the Faust to ensure that the RCG is good to accelerate the muplication, but at least it shows the potential speedup using Faust objects.
%% Cell type:markdown id: tags:
### 2.4 Faust Norms
The Faust class provides a norm function which handles different types of norms.
This function is really close to ```numpy.linalg.norm``` function.
In the following example, three of the four norms available are computed.
%% Cell type:code id: tags:
``` python
F.norm(1)
```
%% Cell type:code id: tags:
``` python
F.norm(np.inf)
```
%% Cell type:code id: tags:
``` python
F.norm('fro')
```
%% Cell type:markdown id: tags:
Now, check the values are not far from the Faust's dense matrix.
%% Cell type:code id: tags:
``` python
from numpy.linalg import norm
norm(F.toarray(),1)
```
%% Cell type:code id: tags:
``` python
norm(F.toarray(), np.inf)
```
%% Cell type:code id: tags:
``` python
norm(F.toarray(), 'fro')
```
%% Cell type:markdown id: tags:
Perfect! But a last norm is available, this is the Faust's 2-norm. Let's see in the next small benchmark how the Faust 2-norm is being computed faster than the Faust's dense matrix 2-norm.
%% Cell type:code id: tags:
``` python
timeit -n 10 G.norm(2)
```
%% Cell type:code id: tags:
``` python
timeit -n 10 norm(GD,2) # sorry, it could be slow
```
%% Cell type:markdown id: tags:
The power-iteration algorithm implemented in the FAµST C++ core is faster on G and the relative error is not bad too. The norm computation is faster as it benefits from faster Faust-vector multiplication
%% Cell type:code id: tags:
``` python
err = abs((G.norm(2)-norm(GD,2))/norm(GD,2))
err
```
%% Cell type:markdown id: tags:
### 2.5 Faust Normalizations
%% Cell type:markdown id: tags:
The FAµST API proposes a group of normalizations. They correspond to the norms available and discussed above.
It's possible to normalize along columns or rows with any type of these norms.
%% Cell type:code id: tags:
``` python
F = rand(5,10)
NF = F.normalize()
```
%% Cell type:markdown id: tags:
The API doc is [here](https://faustgrp.gitlabpages.inria.fr/faust/last-doc/html/classpyfaust_1_1Faust.html#ae93af25bb48c768fd0d08e71631e9dac).
%% Cell type:markdown id: tags:
What's interesting here is the fact that ```Faust.normalize``` returns a Faust object. Combined with slicing (that you will see soon), ```normalize``` is useful to write algorithms such as Orthonormal Matching Pursuit (OMP), which require matrices with L2-normalized columns, in a way that makes them able to leverage the acceleration offered by the FAµST API.
The normalization coded in C++ is memory optimized (it never builds the dense matrix ```F.toarray()``` to compute the norms of the columns/rows). In the same goal the factors composing the Faust object NF are not duplicated in memory from same factors F, they're used as is with an additional factor giving the appropriate scaling.
%% Cell type:code id: tags:
``` python
F
```
%% Cell type:code id: tags:
``` python
NF
```
%% Cell type:code id: tags:
``` python
NF.factors(4)
```
%% Cell type:code id: tags:
``` python
cumerr = 0
for i in range(0,F.shape[1]):
cumerr += norm(NF.toarray()[:,i]-F.toarray()[:,i]/norm(F.toarray()[:,i]))
cumerr
```
%% Cell type:markdown id: tags:
And as you see it works!
%% Cell type:markdown id: tags:
### 2.6 Faust Concatenation
%% Cell type:markdown id: tags:
You're probably aware of numpy arrays concatenation otherwise look this example.
%% Cell type:code id: tags:
``` python
from numpy.random import rand as nrand
from numpy import eye, concatenate
M = nrand(5,5)
I = eye(5,5)
concatenate((M,I))
```
%% Cell type:code id: tags:
``` python
# it was vertical concatenation, now let's concatenate horizontally
concatenate((M,I), axis=1)
```
%% Cell type:markdown id: tags:
I'm sure you guessed that likewise you can concatenate Faust objects. That's right!
%% Cell type:code id: tags:
``` python
F.concatenate(F)
```
%% Cell type:code id: tags:
``` python
C = F.concatenate(F, axis=1)
```
%% Cell type:code id: tags:
``` python
C.toarray()-concatenate((F.toarray(), F.toarray()),axis=1)
```
%% Cell type:markdown id: tags:
The difference of the two concatenations is full of zeros, so of course it works!
As you noticed the Faust concatenation is stable, you give two Fausts and you get a Faust again.
Besides, it's possible to concatenate an arbitrary number of Faust objects.
%% Cell type:code id: tags:
``` python
F.concatenate(F, C, C, F, axis=1)
```
%% Cell type:markdown id: tags:
As an exercise, you can write the factors of the Faust ```F.concatenate(F)```, F being any Faust.
**Hint**: the block-diagonal matrices are around here.
%% Cell type:markdown id: tags:
### 2.7 Faust Indexing and Slicing
%% Cell type:markdown id: tags:
Sometimes you need to access the dense matrix corresponding to a Faust or an element of it (by the way, note that it's costly).
Let's access a Faust item:
%% Cell type:code id: tags:
``` python
F[2,3]
```
%% Cell type:markdown id: tags:
Why is it costly? Because it essentially converts the Faust to its dense form (modulo some optimizations) to just access one item.
%% Cell type:code id: tags:
``` python
timeit F[2,3]
```
%% Cell type:code id: tags:
``` python
timeit FD[2,3]
```
%% Cell type:markdown id: tags:
It's totally the same syntax as numpy but much slower so use it with care.
The more advanced slicing operation uses also the same syntax as numpy:
%% Cell type:code id: tags:
``` python
F[2:5, 3:10]
```
%% Cell type:markdown id: tags:
Here again, the result is another Faust. But this is not a full copy, it makes profit of memory views implemented behind in C++. Solely the first and last factors of the sliced Faust are new in memory, the others are just referenced from the initial Faust F. So use it with no worry for a Faust with a lot of factors!
%% Cell type:markdown id: tags:
The numpy's fancy indexing has also been implemented in the FAµST C++ core, let's try it:
%% Cell type:code id: tags:
``` python
FI = F[[1,3,2],:]
Faust.isFaust(FI)
```
%% Cell type:markdown id: tags:
Again, it's a Faust but is it really working? Verify!
%% Cell type:code id: tags:
``` python
(FI.toarray()[0] == F.toarray()[1]).all() and \
(FI.toarray()[1] == F.toarray()[3]).all() and \
(FI.toarray()[2] == F.toarray()[2]).all()
```
%% Cell type:markdown id: tags:
Yes it is!
As a complement about Faust indexing, you can take a look at this [FAQ entry](https://faustgrp.gitlabpages.inria.fr/faust/last-doc/html/FAQ.html#py_seven). It clarifies the use of indexing form F[I, J] (I and J being lists of indices). Actually, this indexing method is not implemented in pyfaust, the FAQ entry explains why and suggests another form of indexing that is easy to confuse with.
%% Cell type:markdown id: tags:
-----
%% Cell type:markdown id: tags:
This is the notebook's end, you have now a global view of what the Faust class is able and what kind of high-level algorithms it is ready for. You might be interested to read other notebooks, just go back to the [page](https://faustgrp.gitlabpages.inria.fr/faust/last-doc/html/index.html) where you got this one.
%% Cell type:markdown id: tags:
**Note**: this notebook was executed using the following pyfaust version:
%% Cell type:code id: tags:
``` python
import pyfaust
pyfaust.version()
```
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment