From dbdaaa134ea8bd4aaea8ed465ef5bc155e257886 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Defferrard?= Date: Thu, 28 Mar 2019 22:18:20 +0100 Subject: [PATCH 1/9] doc: Reference guide => API reference --- doc/reference/index.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/reference/index.rst b/doc/reference/index.rst index 4e58903d..71422b6b 100644 --- a/doc/reference/index.rst +++ b/doc/reference/index.rst @@ -1,6 +1,6 @@ -=============== -Reference guide -=============== +============= +API reference +============= .. automodule:: pygsp From 2798f992c2ed20d13079479eeab9c6df19c02dfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Defferrard?= Date: Thu, 28 Mar 2019 22:43:56 +0100 Subject: [PATCH 2/9] doc: use sphinx-gallery for short examples --- .gitignore | 3 ++- doc/conf.py | 23 ++++++++++++++++++----- doc/index.rst | 1 + examples/README.txt | 3 +++ setup.py | 2 ++ 5 files changed, 26 insertions(+), 6 deletions(-) create mode 100644 examples/README.txt diff --git a/.gitignore b/.gitignore index c6c6ba6d..f56c8f74 100644 --- a/.gitignore +++ b/.gitignore @@ -25,7 +25,8 @@ output/*.html output/*/index.html # Sphinx documentation -doc/_build +/doc/_build +/doc/examples/ # Vim swap files .*.swp diff --git a/doc/conf.py b/doc/conf.py index 00bd4bfd..fc95a1cc 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -2,11 +2,13 @@ import pygsp -extensions = ['sphinx.ext.viewcode', - 'sphinx.ext.autosummary', - 'sphinx.ext.mathjax', - 'sphinx.ext.inheritance_diagram', - 'sphinxcontrib.bibtex'] +extensions = [ + 'sphinx.ext.viewcode', + 'sphinx.ext.autosummary', + 'sphinx.ext.mathjax', + 'sphinx.ext.inheritance_diagram', + 'sphinxcontrib.bibtex', +] extensions.append('sphinx.ext.autodoc') autodoc_default_flags = ['members', 'undoc-members'] @@ -39,6 +41,17 @@ from pygsp import graphs, filters, utils, plotting """ +extensions.append('sphinx_gallery.gen_gallery') +sphinx_gallery_conf = { + 'examples_dirs': '../examples', + 'gallery_dirs': 'examples', + 'filename_pattern': '/', + 'reference_url': { + 'pygsp': None, + }, + 'show_memory': True, +} + exclude_patterns = ['_build'] source_suffix = '.rst' master_doc = 'index' diff --git a/doc/index.rst b/doc/index.rst index 4fab6d08..1ebdcf59 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -5,6 +5,7 @@ Home tutorials/index + examples/index reference/index contributing history diff --git a/examples/README.txt b/examples/README.txt new file mode 100644 index 00000000..b90c0e1c --- /dev/null +++ b/examples/README.txt @@ -0,0 +1,3 @@ +======== +Examples +======== diff --git a/setup.py b/setup.py index 9cef6fc4..2ed970f9 100644 --- a/setup.py +++ b/setup.py @@ -61,6 +61,8 @@ 'sphinx', 'numpydoc', 'sphinxcontrib-bibtex', + 'sphinx-gallery', + 'memory_profiler', 'sphinx-rtd-theme', # Build and upload packages. 'wheel', From 5044bd3cf7e485eb6cc102667e4e55262a9e54c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Defferrard?= Date: Thu, 28 Mar 2019 23:12:34 +0100 Subject: [PATCH 3/9] doc: eigenvalue concentration example --- examples/eigenvalue_concentration.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 examples/eigenvalue_concentration.py diff --git a/examples/eigenvalue_concentration.py b/examples/eigenvalue_concentration.py new file mode 100644 index 00000000..d9fcdd86 --- /dev/null +++ b/examples/eigenvalue_concentration.py @@ -0,0 +1,26 @@ +r""" +Concentration of the eigenvalues +================================ + +The eigenvalues of the graph Laplacian concentrates to the same value as the +graph becomes full. +""" + +from matplotlib import pyplot as plt +import pygsp as pg + +n_neighbors = [1, 2, 5, 8] +fig, axes = plt.subplots(4, len(n_neighbors), figsize=(15, 10)) + +for k, ax in zip(n_neighbors, axes.T): + graph = pg.graphs.Ring(17, k=k) + graph.compute_fourier_basis() + graph.plot(graph.U[:, 1], ax=ax[0]) + ax[0].axis('equal') + ax[1].spy(graph.W) + ax[2].plot(graph.e, '.') + ax[2].set_title('k={}'.format(k)) + graph.set_coordinates('line1D') + graph.plot(graph.U[:, :4], ax=ax[3], title='') + +fig.tight_layout() From 9d82ed5607d7e25b093782653b51ffdcf601cade Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Defferrard?= Date: Fri, 29 Mar 2019 01:01:34 +0100 Subject: [PATCH 4/9] doc gallery: need this config for intersphinx links to work see https://github.com/sphinx-gallery/sphinx-gallery/issues/467 --- .gitignore | 3 ++- doc/conf.py | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index f56c8f74..9c15768f 100644 --- a/.gitignore +++ b/.gitignore @@ -25,8 +25,9 @@ output/*.html output/*/index.html # Sphinx documentation -/doc/_build +/doc/_build/ /doc/examples/ +/doc/backrefs/ # Vim swap files .*.swp diff --git a/doc/conf.py b/doc/conf.py index fc95a1cc..c1986fb0 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -46,9 +46,9 @@ 'examples_dirs': '../examples', 'gallery_dirs': 'examples', 'filename_pattern': '/', - 'reference_url': { - 'pygsp': None, - }, + 'reference_url': {'pygsp': None}, + 'backreferences_dir': 'backrefs', + 'doc_module': 'pygsp', 'show_memory': True, } From 2c4dcc1edfd6b546ad6e972f9b86d391a2d1489d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Defferrard?= Date: Fri, 29 Mar 2019 01:13:06 +0100 Subject: [PATCH 5/9] doc: more examples from my talks --- examples/filtering.py | 70 ++++++++++++++++++++++++++++++++ examples/fourier_basis.py | 46 +++++++++++++++++++++ examples/fourier_transform.py | 50 +++++++++++++++++++++++ examples/heat_diffusion.py | 49 +++++++++++++++++++++++ examples/kernel_localization.py | 44 ++++++++++++++++++++ examples/random_walk.py | 71 +++++++++++++++++++++++++++++++++ examples/wave_propagation.py | 49 +++++++++++++++++++++++ 7 files changed, 379 insertions(+) create mode 100644 examples/filtering.py create mode 100644 examples/fourier_basis.py create mode 100644 examples/fourier_transform.py create mode 100644 examples/heat_diffusion.py create mode 100644 examples/kernel_localization.py create mode 100644 examples/random_walk.py create mode 100644 examples/wave_propagation.py diff --git a/examples/filtering.py b/examples/filtering.py new file mode 100644 index 00000000..d53e388f --- /dev/null +++ b/examples/filtering.py @@ -0,0 +1,70 @@ +r""" +Filtering a graph signal +======================== + +A graph signal is filtered by transforming it to the spectral domain (via the +Fourier transform), performing a point-wise multiplication (motivated by the +convolution theorem), and transforming it back to the vertex domain (via the +inverse graph Fourier transform). + +.. note:: + + In practice, filtering is implemented in the vertex domain to avoid the + computationally expensive graph Fourier transform. To do so, filters are + implemented as polynomials of the eigenvalues / Laplacian. Hence, filtering + a signal reduces to its multiplications with sparse matrices (the graph + Laplacian). + +""" + +import numpy as np +from matplotlib import pyplot as plt +import pygsp as pg + +#plt.rc('font', family='Latin Modern Roman') +plt.rc('text', usetex=True) +plt.rc('text.latex', preamble=r'\usepackage{lmodern}') + +G = pg.graphs.Sensor(seed=42) +G.compute_fourier_basis() + +#g = pg.filters.Rectangular(G, band_max=0.2) +g = pg.filters.Expwin(G, band_max=0.5) + +fig, axes = plt.subplots(1, 3, figsize=(12, 4)) +fig.subplots_adjust(hspace=0.5) + +x = np.random.RandomState(1).normal(size=G.N) +#x = np.random.RandomState(42).uniform(-1, 1, size=G.N) +x = 3 * x / np.linalg.norm(x) +y = g.filter(x) +x_hat = G.gft(x).squeeze() +y_hat = G.gft(y).squeeze() + +limits = [x.min(), x.max()] + +G.plot(x, limits=limits, ax=axes[0], title='input signal $x$ in the vertex domain') +axes[0].text(0, -0.1, '$x^T L x = {:.2f}$'.format(G.dirichlet_energy(x))) +axes[0].set_axis_off() + +g.plot(ax=axes[1], alpha=1) +line_filt = axes[1].lines[-2] +line_in, = axes[1].plot(G.e, np.abs(x_hat), '.-') +line_out, = axes[1].plot(G.e, np.abs(y_hat), '.-') +#axes[1].set_xticks(range(0, 16, 4)) +axes[1].set_xlabel(r'graph frequency $\lambda$') +axes[1].set_ylabel(r'frequency content $\hat{x}(\lambda)$') +axes[1].set_title(r'signals in the spectral domain') +axes[1].legend(['input signal $\hat{x}$']) +labels = [ + r'input signal $\hat{x}$', + 'kernel $g$', + r'filtered signal $\hat{y}$', +] +axes[1].legend([line_in, line_filt, line_out], labels, loc='upper right') + +G.plot(y, limits=limits, ax=axes[2], title='filtered signal $y$ in the vertex domain') +axes[2].text(0, -0.1, '$y^T L y = {:.2f}$'.format(G.dirichlet_energy(y))) +axes[2].set_axis_off() + +fig.tight_layout() diff --git a/examples/fourier_basis.py b/examples/fourier_basis.py new file mode 100644 index 00000000..e99126ad --- /dev/null +++ b/examples/fourier_basis.py @@ -0,0 +1,46 @@ +r""" +Fourier basis of graphs +======================= + +The eigenvectors of the graph Laplacian form the Fourier basis. +The eigenvalues are a measure of variation of their corresponding eigenvector. +The lower the eigenvalue, the smoother the eigenvector. They are hence a +measure of "frequency". + +In classical signal processing, Fourier modes are completely delocalized, like +on the grid graph. For general graphs however, Fourier modes might be +localized. See :attr:`pygsp.graphs.Graph.coherence`. +""" + +import numpy as np +from matplotlib import pyplot as plt +import pygsp as pg + +#plt.rc('font', family='Latin Modern Roman') +plt.rc('text', usetex=True) +plt.rc('text.latex', preamble=r'\usepackage{lmodern}') + +n_eigenvectors = 7 + +fig, axes = plt.subplots(2, 7, figsize=(15, 4)) + +def plot_eigenvectors(G, axes): + G.compute_fourier_basis(n_eigenvectors) + limits = [f(G.U) for f in (np.min, np.max)] + for i, ax in enumerate(axes): + G.plot(G.U[:, i], limits=limits, colorbar=False, vertex_size=50, ax=ax) + energy = abs(G.dirichlet_energy(G.U[:, i])) + ax.set_title(r'$u_{0}^\top L u_{0} = {1:.2f}$'.format(i+1, energy)) + ax.set_axis_off() + +G = pg.graphs.Grid2d(10, 10) +plot_eigenvectors(G, axes[0]) +fig.subplots_adjust(hspace=0.5, right=0.8) +cax = fig.add_axes([0.82, 0.60, 0.01, 0.26]) +fig.colorbar(axes[0, -1].collections[1], cax=cax, ticks=[-0.2, 0, 0.2]) + +G = pg.graphs.Sensor(seed=42) +plot_eigenvectors(G, axes[1]) +fig.subplots_adjust(hspace=0.5, right=0.8) +cax = fig.add_axes([0.82, 0.16, 0.01, 0.26]) +fig.colorbar(axes[1, -1].collections[1], cax=cax, ticks=[-0.4, 0, 0.4]) diff --git a/examples/fourier_transform.py b/examples/fourier_transform.py new file mode 100644 index 00000000..8e99969c --- /dev/null +++ b/examples/fourier_transform.py @@ -0,0 +1,50 @@ +r""" +Fourier transform +================= + +The graph Fourier transform (:meth:`pygsp.graphs.Graph.gft`) transforms a +signal from the vertex domain to the spectral domain. The smoother the signal +(see :meth:`pygsp.graphs.Graph.dirichlet_energy`), the lower in the frequencies +its energy is concentrated. +""" + +import numpy as np +from matplotlib import pyplot as plt +import pygsp as pg + +#plt.rc('font', family='Latin Modern Roman') +plt.rc('text', usetex=True) +plt.rc('text.latex', preamble=r'\usepackage{lmodern}') + +G = pg.graphs.Sensor(seed=42) +G.compute_fourier_basis() + +scales = [10, 3, 0] +limit = 0.32 + +fig, axes = plt.subplots(2, len(scales), figsize=(12, 4)) +fig.subplots_adjust(hspace=0.5) + +x0 = np.random.RandomState(1).normal(size=G.N) +for i, scale in enumerate(scales): + g = pg.filters.Heat(G, scale) + x = g.filter(x0).squeeze() + x /= np.linalg.norm(x) + x_hat = G.gft(x).squeeze() + + assert np.all((-limit < x) & (x < limit)) + G.plot(x, limits=[-limit, limit], ax=axes[0, i]) + axes[0, i].set_axis_off() + axes[0, i].set_title('$x^T L x = {:.2f}$'.format(G.dirichlet_energy(x))) + + axes[1, i].plot(G.e, np.abs(x_hat), '.-') + axes[1, i].set_xticks(range(0, 16, 4)) + axes[1, i].set_xlabel(r'graph frequency $\lambda$') + axes[1, i].set_ylim(-0.05, 0.95) + +axes[1, 0].set_ylabel(r'frequency content $\hat{x}(\lambda)$') + +# axes[0, 0].set_title(r'$x$: signal in the vertex domain') +# axes[1, 0].set_title(r'$\hat{x}$: signal in the spectral domain') + +fig.tight_layout() diff --git a/examples/heat_diffusion.py b/examples/heat_diffusion.py new file mode 100644 index 00000000..8ea42608 --- /dev/null +++ b/examples/heat_diffusion.py @@ -0,0 +1,49 @@ +r""" +Heat diffusion on graphs +======================== + +Solve the heat equation by filtering the initial conditions with the heat +kernel. +""" + +from os import path + +import numpy as np +from matplotlib import pyplot as plt +import pygsp as pg + +#plt.rc('font', family='Latin Modern Roman') +plt.rc('text', usetex=True) +plt.rc('text.latex', preamble=r'\usepackage{lmodern}') + +n_side = 13 +G = pg.graphs.Grid2d(n_side) +G.compute_fourier_basis() + +sources = [ + (n_side//4 * n_side) + (n_side//4), + (n_side*3//4 * n_side) + (n_side*3//4), +] +x = np.zeros(G.n_vertices) +x[sources] = 5 + +times = [0, 5, 10, 20] + +fig, axes = plt.subplots(2, len(times), figsize=(12, 5)) +for i, t in enumerate(times): + g = pg.filters.Heat(G, scale=t) + title = fr'$\hat{{f}}({t}) = g_{{1,{t}}} \odot \hat{{f}}(0)$' + g.plot(alpha=1, ax=axes[0, i], title=title) + axes[0, i].set_xlabel(r'$\lambda$') +# axes[0, i].set_ylabel(r'$g(\lambda)$') + if i > 0: + axes[0, i].set_ylabel('') + y = g.filter(x) + line, = axes[0, i].plot(G.e, G.gft(y)) + labels = [fr'$\hat{{f}}({t})$', fr'$g_{{1,{t}}}$'] + axes[0, i].legend([line, axes[0, i].lines[-3]], labels, loc='lower right') + G.plot(y, edges=False, highlight=sources, ax=axes[1, i], title=fr'$f({t})$') + axes[1, i].set_aspect('equal', 'box') + axes[1, i].set_axis_off() + +fig.tight_layout() diff --git a/examples/kernel_localization.py b/examples/kernel_localization.py new file mode 100644 index 00000000..5bbd2225 --- /dev/null +++ b/examples/kernel_localization.py @@ -0,0 +1,44 @@ +r""" +Kernel localization +=================== + +In classical signal processing, a filter can be translated in the vertex +domain. We cannot do that on graphs. Instead, we can +:meth:`~pygsp.filters.Filter.localize` a filter kernel. Note how on classic +structures (like the ring), the localized kernel is the same everywhere, while +it changes when localized on irregular graphs. +""" + +import numpy as np +from matplotlib import pyplot as plt +import pygsp as pg + +#plt.rc('font', family='Latin Modern Roman') +plt.rc('text', usetex=True) +plt.rc('text.latex', preamble=r'\usepackage{lmodern}') + +fig, axes = plt.subplots(2, 4, figsize=(10, 4)) + +graphs = [ + pg.graphs.Ring(40), + pg.graphs.Sensor(64, seed=42), +] + +locations = [0, 10, 20] + +for graph, axs in zip(graphs, axes): + graph.compute_fourier_basis() + g = pg.filters.Heat(graph) + g.plot(ax=axs[0], title='heat kernel') + axs[0].set_xlabel(r'eigenvalues $\lambda$') + axs[0].set_ylabel(r'$g(\lambda) = \exp \left( \frac{{-{}\lambda}}{{\lambda_{{max}}}} \right)$'.format(g.scale[0])) + maximum = 0 + for loc in locations: + x = g.localize(loc) + maximum = np.maximum(maximum, x.max()) + for loc, ax in zip(locations, axs[1:]): + graph.plot(g.localize(loc), limits=[0, maximum], highlight=loc, ax=ax, + title=r'$g(L) \delta_{{{}}}$'.format(loc)) + ax.set_axis_off() + +fig.tight_layout() diff --git a/examples/random_walk.py b/examples/random_walk.py new file mode 100644 index 00000000..b3031b4c --- /dev/null +++ b/examples/random_walk.py @@ -0,0 +1,71 @@ +r""" +Random walks +============ + +Probability of a random walker to be on any given vertex after a given number +of steps starting from a given distribution. +""" + +# sphinx_gallery_thumbnail_number = 2 + +import numpy as np +from scipy import sparse +from matplotlib import pyplot as plt +import pygsp as pg + +#plt.rc('font', family='Latin Modern Roman') +plt.rc('text', usetex=True) +plt.rc('text.latex', preamble=r'\usepackage{lmodern}') + +N = 7 +steps = [0, 1, 2, 3] + +graph = pg.graphs.Grid2d(N) +delta = np.zeros(graph.N) +delta[N//2*N + N//2] = 1 + +probability = sparse.diags(graph.dw**(-1)) @ graph.W + +fig, axes = plt.subplots(1, len(steps), figsize=(12, 3)) +for step, ax in zip(steps, axes): + state = delta @ probability**step + graph.plot(state, ax=ax, title=r'$\delta P^{}$'.format(step)) + ax.set_axis_off() + +fig.tight_layout() + +############################################################################### +# Stationary distribution. + +graphs = [ + pg.graphs.Ring(10), + pg.graphs.Grid2d(5), + pg.graphs.Comet(8, 4), + pg.graphs.BarabasiAlbert(20, seed=42), +] + +fig, axes = plt.subplots(1, len(graphs), figsize=(12, 3)) + +for graph, ax in zip(graphs, axes): + + if not hasattr(graph, 'coords'): + graph.set_coordinates(seed=10) + + P = sparse.diags(graph.dw**(-1)) @ graph.W + +# e, u = np.linalg.eig(P.T.toarray()) +# np.testing.assert_allclose(np.linalg.inv(u.T) @ np.diag(e) @ u.T, +# P.toarray(), atol=1e-10) +# np.testing.assert_allclose(np.abs(e[0]), 1) +# stationary = np.abs(u.T[0]) + + e, u = sparse.linalg.eigs(P.T, k=1, which='LR') + np.testing.assert_allclose(e, 1) + stationary = np.abs(u).squeeze() + assert np.all(stationary < 0.71) + + colorbar = False if type(graph) is pg.graphs.Ring else True + graph.plot(stationary, colorbar=colorbar, ax=ax, title='$xP = x$') + ax.set_axis_off() + +fig.tight_layout() diff --git a/examples/wave_propagation.py b/examples/wave_propagation.py new file mode 100644 index 00000000..2abf71d9 --- /dev/null +++ b/examples/wave_propagation.py @@ -0,0 +1,49 @@ +r""" +Wave propagation on graphs +========================== + +Solve the wave equation by filtering the initial conditions with the wave +kernel. +""" + +from os import path + +import numpy as np +from matplotlib import pyplot as plt +import pygsp as pg + +#plt.rc('font', family='Latin Modern Roman') +plt.rc('text', usetex=True) +plt.rc('text.latex', preamble=r'\usepackage{lmodern}') + +n_side = 13 +G = pg.graphs.Grid2d(n_side) +G.compute_fourier_basis() + +sources = [ + (n_side//4 * n_side) + (n_side//4), + (n_side*3//4 * n_side) + (n_side*3//4), +] +x = np.zeros(G.n_vertices) +x[sources] = 5 + +times = [0, 5, 10, 20] + +fig, axes = plt.subplots(2, len(times), figsize=(12, 5)) +for i, t in enumerate(times): + g = pg.filters.Wave(G, time=t, speed=1) + title = fr'$\hat{{f}}({t}) = g_{{1,{t}}} \odot \hat{{f}}(0)$' + g.plot(alpha=1, ax=axes[0, i], title=title) + axes[0, i].set_xlabel(r'$\lambda$') +# axes[0, i].set_ylabel(r'$g(\lambda)$') + if i > 0: + axes[0, i].set_ylabel('') + y = g.filter(x) + line, = axes[0, i].plot(G.e, G.gft(y)) + labels = [fr'$\hat{{f}}({t})$', fr'$g_{{1,{t}}}$'] + axes[0, i].legend([line, axes[0, i].lines[-3]], labels, loc='lower right') + G.plot(y, edges=False, highlight=sources, ax=axes[1, i], title=fr'$f({t})$') + axes[1, i].set_aspect('equal', 'box') + axes[1, i].set_axis_off() + +fig.tight_layout() From 1973c4099e4f6781b652d4d7178201b3438a7cf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Defferrard?= Date: Fri, 29 Mar 2019 01:25:21 +0100 Subject: [PATCH 6/9] history: examples gallery --- doc/history.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/history.rst b/doc/history.rst index 914ca6fd..11b5286c 100644 --- a/doc/history.rst +++ b/doc/history.rst @@ -27,6 +27,8 @@ History * Import and export graphs and their signals to NetworkX and graph-tool. * Save and load graphs and theirs signals to / from GraphML, GML, and GEXF. * Documentation: path graph linked to DCT, ring graph linked to DFT. +* We now have a gallery of examples! That is convenient for users to get a + taste of what the library can do, and to start working from a code snippet. * Merged all the extra requirements in a single dev requirement. Experimental filter API (to be tested and validated): @@ -93,7 +95,7 @@ The following packages were made optional dependencies: workflow, it's not necessary for users who only want to process data without plotting graphs, signals and filters. * pyflann, as it is only used for approximate kNN. The problem was that the - source distribution would not build for Windows. On conda-forge, (py)flann + source distribution would not build for Windows. On conda-forge, (py)flann is not built for Windows either. Moreover, matplotlib is now the default drawing backend. It's well integrated From 18db015f12a20bdeb1beef4669a695f6e080c207 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Defferrard?= Date: Fri, 29 Mar 2019 01:29:15 +0100 Subject: [PATCH 7/9] examples: while math looks better (especially for papers and slides), latex is not available everywhere --- examples/filtering.py | 4 ---- examples/fourier_basis.py | 4 ---- examples/fourier_transform.py | 4 ---- examples/heat_diffusion.py | 4 ---- examples/kernel_localization.py | 4 ---- examples/random_walk.py | 4 ---- examples/wave_propagation.py | 4 ---- 7 files changed, 28 deletions(-) diff --git a/examples/filtering.py b/examples/filtering.py index d53e388f..62256685 100644 --- a/examples/filtering.py +++ b/examples/filtering.py @@ -21,10 +21,6 @@ from matplotlib import pyplot as plt import pygsp as pg -#plt.rc('font', family='Latin Modern Roman') -plt.rc('text', usetex=True) -plt.rc('text.latex', preamble=r'\usepackage{lmodern}') - G = pg.graphs.Sensor(seed=42) G.compute_fourier_basis() diff --git a/examples/fourier_basis.py b/examples/fourier_basis.py index e99126ad..1064c58f 100644 --- a/examples/fourier_basis.py +++ b/examples/fourier_basis.py @@ -16,10 +16,6 @@ from matplotlib import pyplot as plt import pygsp as pg -#plt.rc('font', family='Latin Modern Roman') -plt.rc('text', usetex=True) -plt.rc('text.latex', preamble=r'\usepackage{lmodern}') - n_eigenvectors = 7 fig, axes = plt.subplots(2, 7, figsize=(15, 4)) diff --git a/examples/fourier_transform.py b/examples/fourier_transform.py index 8e99969c..2589ef12 100644 --- a/examples/fourier_transform.py +++ b/examples/fourier_transform.py @@ -12,10 +12,6 @@ from matplotlib import pyplot as plt import pygsp as pg -#plt.rc('font', family='Latin Modern Roman') -plt.rc('text', usetex=True) -plt.rc('text.latex', preamble=r'\usepackage{lmodern}') - G = pg.graphs.Sensor(seed=42) G.compute_fourier_basis() diff --git a/examples/heat_diffusion.py b/examples/heat_diffusion.py index 8ea42608..e0e0ac8d 100644 --- a/examples/heat_diffusion.py +++ b/examples/heat_diffusion.py @@ -12,10 +12,6 @@ from matplotlib import pyplot as plt import pygsp as pg -#plt.rc('font', family='Latin Modern Roman') -plt.rc('text', usetex=True) -plt.rc('text.latex', preamble=r'\usepackage{lmodern}') - n_side = 13 G = pg.graphs.Grid2d(n_side) G.compute_fourier_basis() diff --git a/examples/kernel_localization.py b/examples/kernel_localization.py index 5bbd2225..d9484fa9 100644 --- a/examples/kernel_localization.py +++ b/examples/kernel_localization.py @@ -13,10 +13,6 @@ from matplotlib import pyplot as plt import pygsp as pg -#plt.rc('font', family='Latin Modern Roman') -plt.rc('text', usetex=True) -plt.rc('text.latex', preamble=r'\usepackage{lmodern}') - fig, axes = plt.subplots(2, 4, figsize=(10, 4)) graphs = [ diff --git a/examples/random_walk.py b/examples/random_walk.py index b3031b4c..47e67dd3 100644 --- a/examples/random_walk.py +++ b/examples/random_walk.py @@ -13,10 +13,6 @@ from matplotlib import pyplot as plt import pygsp as pg -#plt.rc('font', family='Latin Modern Roman') -plt.rc('text', usetex=True) -plt.rc('text.latex', preamble=r'\usepackage{lmodern}') - N = 7 steps = [0, 1, 2, 3] diff --git a/examples/wave_propagation.py b/examples/wave_propagation.py index 2abf71d9..fc6f8766 100644 --- a/examples/wave_propagation.py +++ b/examples/wave_propagation.py @@ -12,10 +12,6 @@ from matplotlib import pyplot as plt import pygsp as pg -#plt.rc('font', family='Latin Modern Roman') -plt.rc('text', usetex=True) -plt.rc('text.latex', preamble=r'\usepackage{lmodern}') - n_side = 13 G = pg.graphs.Grid2d(n_side) G.compute_fourier_basis() From 9eb2b1d6fbed377ca1688063fcf6aee20287f727 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Defferrard?= Date: Fri, 29 Mar 2019 02:26:55 +0100 Subject: [PATCH 8/9] replace f-strings (>=3.6) and @ infix operator (>=3.5) --- examples/heat_diffusion.py | 6 +++--- examples/random_walk.py | 6 +++--- examples/wave_propagation.py | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/examples/heat_diffusion.py b/examples/heat_diffusion.py index e0e0ac8d..81701bbe 100644 --- a/examples/heat_diffusion.py +++ b/examples/heat_diffusion.py @@ -28,7 +28,7 @@ fig, axes = plt.subplots(2, len(times), figsize=(12, 5)) for i, t in enumerate(times): g = pg.filters.Heat(G, scale=t) - title = fr'$\hat{{f}}({t}) = g_{{1,{t}}} \odot \hat{{f}}(0)$' + title = r'$\hat{{f}}({0}) = g_{{1,{0}}} \odot \hat{{f}}(0)$'.format(t) g.plot(alpha=1, ax=axes[0, i], title=title) axes[0, i].set_xlabel(r'$\lambda$') # axes[0, i].set_ylabel(r'$g(\lambda)$') @@ -36,9 +36,9 @@ axes[0, i].set_ylabel('') y = g.filter(x) line, = axes[0, i].plot(G.e, G.gft(y)) - labels = [fr'$\hat{{f}}({t})$', fr'$g_{{1,{t}}}$'] + labels = [r'$\hat{{f}}({})$'.format(t), r'$g_{{1,{}}}$'.format(t)] axes[0, i].legend([line, axes[0, i].lines[-3]], labels, loc='lower right') - G.plot(y, edges=False, highlight=sources, ax=axes[1, i], title=fr'$f({t})$') + G.plot(y, edges=False, highlight=sources, ax=axes[1, i], title=r'$f({})$'.format(t)) axes[1, i].set_aspect('equal', 'box') axes[1, i].set_axis_off() diff --git a/examples/random_walk.py b/examples/random_walk.py index 47e67dd3..9fc69456 100644 --- a/examples/random_walk.py +++ b/examples/random_walk.py @@ -20,11 +20,11 @@ delta = np.zeros(graph.N) delta[N//2*N + N//2] = 1 -probability = sparse.diags(graph.dw**(-1)) @ graph.W +probability = sparse.diags(graph.dw**(-1)).dot(graph.W) fig, axes = plt.subplots(1, len(steps), figsize=(12, 3)) for step, ax in zip(steps, axes): - state = delta @ probability**step + state = (probability**step).__rmatmul__(delta) ## = delta @ probability**step graph.plot(state, ax=ax, title=r'$\delta P^{}$'.format(step)) ax.set_axis_off() @@ -47,7 +47,7 @@ if not hasattr(graph, 'coords'): graph.set_coordinates(seed=10) - P = sparse.diags(graph.dw**(-1)) @ graph.W + P = sparse.diags(graph.dw**(-1)).dot(graph.W) # e, u = np.linalg.eig(P.T.toarray()) # np.testing.assert_allclose(np.linalg.inv(u.T) @ np.diag(e) @ u.T, diff --git a/examples/wave_propagation.py b/examples/wave_propagation.py index fc6f8766..ec69b73c 100644 --- a/examples/wave_propagation.py +++ b/examples/wave_propagation.py @@ -28,7 +28,7 @@ fig, axes = plt.subplots(2, len(times), figsize=(12, 5)) for i, t in enumerate(times): g = pg.filters.Wave(G, time=t, speed=1) - title = fr'$\hat{{f}}({t}) = g_{{1,{t}}} \odot \hat{{f}}(0)$' + title = r'$\hat{{f}}({0}) = g_{{1,{0}}} \odot \hat{{f}}(0)$'.format(t) g.plot(alpha=1, ax=axes[0, i], title=title) axes[0, i].set_xlabel(r'$\lambda$') # axes[0, i].set_ylabel(r'$g(\lambda)$') @@ -36,9 +36,9 @@ axes[0, i].set_ylabel('') y = g.filter(x) line, = axes[0, i].plot(G.e, G.gft(y)) - labels = [fr'$\hat{{f}}({t})$', fr'$g_{{1,{t}}}$'] + labels = [r'$\hat{{f}}({})$'.format(t), r'$g_{{1,{}}}$'.format(t)] axes[0, i].legend([line, axes[0, i].lines[-3]], labels, loc='lower right') - G.plot(y, edges=False, highlight=sources, ax=axes[1, i], title=fr'$f({t})$') + G.plot(y, edges=False, highlight=sources, ax=axes[1, i], title=r'$f({})$'.format(t)) axes[1, i].set_aspect('equal', 'box') axes[1, i].set_axis_off() From 370726635fd1bcfedc0b71e7bde34e069529806d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Defferrard?= Date: Fri, 29 Mar 2019 03:11:19 +0100 Subject: [PATCH 9/9] =?UTF-8?q?ring=20example:=20check=20that=20DFT=20is?= =?UTF-8?q?=20an=20eigenbasis=20(by=20Nathana=C3=ABl)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/eigenvalue_concentration.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/examples/eigenvalue_concentration.py b/examples/eigenvalue_concentration.py index d9fcdd86..e47c78cf 100644 --- a/examples/eigenvalue_concentration.py +++ b/examples/eigenvalue_concentration.py @@ -6,6 +6,7 @@ graph becomes full. """ +import numpy as np from matplotlib import pyplot as plt import pygsp as pg @@ -13,6 +14,7 @@ fig, axes = plt.subplots(4, len(n_neighbors), figsize=(15, 10)) for k, ax in zip(n_neighbors, axes.T): + graph = pg.graphs.Ring(17, k=k) graph.compute_fourier_basis() graph.plot(graph.U[:, 1], ax=ax[0]) @@ -23,4 +25,14 @@ graph.set_coordinates('line1D') graph.plot(graph.U[:, :4], ax=ax[3], title='') + # Check that the DFT matrix is an eigenbasis of the Laplacian. + U = np.fft.fft(np.identity(graph.n_vertices)) + LambdaM = (graph.L.todense().dot(U)) / (U + 1e-15) + # Eigenvalues should be real. + assert np.all(np.abs(np.imag(LambdaM)) < 1e-10) + LambdaM = np.real(LambdaM) + # Check that the eigenvectors are really eigenvectors of the laplacian. + Lambda = np.mean(LambdaM, axis=0) + assert np.all(np.abs(LambdaM - Lambda) < 1e-10) + fig.tight_layout()