.. DO NOT EDIT. .. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY. .. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE: .. "gallery/0200_cs/bsbl_1.py" .. LINE NUMBERS ARE GIVEN BELOW. .. only:: html .. note:: :class: sphx-glr-download-link-note :ref:`Go to the end ` to download the full example code .. rst-class:: sphx-glr-example-title .. _sphx_glr_gallery_0200_cs_bsbl_1.py: .. _gallery:cs:bsbl:1: Block Sparse Bayesian Learning ================================= .. contents:: :depth: 2 :local: In this example, we demonstrate the BSBL (Block Sparse Bayesian Learning) algorithm :cite:`zhang2012recovery,zhang2013extension` for reconstruction of block sparse signals with intra block correlations from their compressive measurements. In particular, we show - Creation of block sparse signals with intra-block correlation - Compressive sampling of the signal with Gaussian and sparse binary sensing matrices. - Reconstruction using BSBL EM algorithm (Expectation Maximization). - Reconstruction using BSBL BO algorithm (Bound Optimization). - Reconstruction in the presence of high measurement noise Our implementation of BSBL is fully JIT compilable. To achieve this, we limit ourselves of equal sized blocks where the block size is user defined. This is not a problem in practice. As shown in :cite:`zhang2012compressed`, the reconstruction from compressive measurements of real life signals is not affected much by the block size. The basic compressive sensing model is given by .. math:: \by = \Phi \bx + \be where :math:`\by` is a known measurement vector, :math:`\Phi` is a known sensing matrix and :math:`\bx` is a sparse signal to be recovered from the measurements. We introduce the block/group structure on :math:`\bx` as .. math:: \bx = \begin{pmatrix} \bx_1 & \bx_2 & \dots & \bx_g \end{pmatrix} where each :math:`\bx_i` is a block of :math:`b` values. The signal :math:`\bx` consists of :math:`g` such blocks/groups. We only consider the case of equal sized blocks in our implementation. Under the block sparsity model, only a few :math:`k \ll g` blocks are nonzero (active) in the signal :math:`\bx` however, the locations of these blocks are unknown. We can rewrite the sensing equation as: .. math:: \by = \sum_{i=1}^g \Phi_i \bx_i + \be by splitting the sensing matrix into blocks of columns appropriately. Under the sparse Bayesian framework, each block is assumed to satisfy a parametrized multivariate Gaussian distribution: .. math:: \PP(\bx_i ; \gamma_i, \bB_i) = \NNN(\bzero, \gamma_i \bB_i), \Forall i=1,\dots,g. The covariance matrix :math:`\bB_i` captures the intra block correlations. We further assume that the blocks are mutually uncorrelated. The prior of :math:`\bx` can then be written as .. math:: \PP(\bx; \{ \gamma_i, \bB_i\}_i ) = \NNN(\bzero, \Sigma_0) where .. math:: \Sigma_0 = \text{diag}\{\gamma_1 \bB_1, \dots, \gamma_g \bB_g \}. We also model the correlation among the values within each active block as an AR-1 process. Under this assumption the matrices :math:`\bB_i` take the form of a Toeplitz matrix .. math:: \bB = \begin{bmatrix} 1 & r & \dots & r^{b-1}\\ r & 1 & \dots & r^{b-2}\\ \vdots & & \ddots & \vdots\\ r^{b-1} & r^{b-2} & \dots & 1 \end{bmatrix} where :math:`r` is the AR-1 model coefficient. This constraint significantly reduces the model parameters to be learned. Measurement noise is modeled as independent zero mean Gaussian noise :math:`\PP(\be; \lambda) \sim \NNN(\bzero, \lambda \bI)`. BSBL doesn't require you to provide the value of noise variance as input. It is able to estimate :math:`\lambda` within a algorithm. The estimate of :math:`\bx` under Bayesian learning framework is given by the posterior mean of :math:`\bx` given the measurements :math:`\by`. Please also refer to the `BSBL website `_ by the authors of the original algorithm for further information. Related Examples - :ref:`gallery:cs:sparse_binary_sensor` .. GENERATED FROM PYTHON SOURCE LINES 137-139 Setup ------------------------------ .. GENERATED FROM PYTHON SOURCE LINES 139-144 .. code-block:: default # Configure JAX for 64-bit computing from jax.config import config config.update("jax_enable_x64", True) .. GENERATED FROM PYTHON SOURCE LINES 145-146 Let's import necessary libraries .. GENERATED FROM PYTHON SOURCE LINES 146-162 .. code-block:: default from matplotlib import pyplot as plt # jax imports import jax.numpy as jnp from jax import random, jit # cr-suite imports import cr.nimble as crn import cr.nimble.dsp as crdsp import cr.sparse as crs import cr.sparse.dict as crdict import cr.sparse.data as crdata import cr.sparse.plots as crplot import cr.sparse.block.bsbl as bsbl .. GENERATED FROM PYTHON SOURCE LINES 163-165 Problem Configuration ------------------------------ .. GENERATED FROM PYTHON SOURCE LINES 165-177 .. code-block:: default # ambient dimension n = 300 # block length b = 4 # number of blocks nb = n // b # Block sparsity: number of nonzero blocks k = 6 # Number of measurements m = 100 .. GENERATED FROM PYTHON SOURCE LINES 178-180 Block Sparse Signal ------------------------------ .. GENERATED FROM PYTHON SOURCE LINES 180-188 .. code-block:: default # Block sparse signal with intra block correlation x, blocks, indices = crdata.sparse_normal_blocks( crn.KEYS[2], n, k, b, cor=0.9, normalize_blocks=True) ax = crplot.one_plot() ax.stem(x); .. image-sg:: /gallery/0200_cs/images/sphx_glr_bsbl_1_001.png :alt: bsbl 1 :srcset: /gallery/0200_cs/images/sphx_glr_bsbl_1_001.png :class: sphx-glr-single-img .. rst-class:: sphx-glr-script-out .. code-block:: none .. GENERATED FROM PYTHON SOURCE LINES 189-191 Gaussian Sensing ------------------------------ .. GENERATED FROM PYTHON SOURCE LINES 193-194 Sensing matrix .. GENERATED FROM PYTHON SOURCE LINES 194-198 .. code-block:: default Phi = crdict.gaussian_mtx(crn.KEYS[0], m, n, normalize_atoms=True) ax = crplot.one_plot() ax.imshow(Phi); .. image-sg:: /gallery/0200_cs/images/sphx_glr_bsbl_1_002.png :alt: bsbl 1 :srcset: /gallery/0200_cs/images/sphx_glr_bsbl_1_002.png :class: sphx-glr-single-img .. rst-class:: sphx-glr-script-out .. code-block:: none .. GENERATED FROM PYTHON SOURCE LINES 199-200 Measurements .. GENERATED FROM PYTHON SOURCE LINES 200-204 .. code-block:: default y = Phi @ x ax = crplot.one_plot() crplot.plot_signal(ax, y) .. image-sg:: /gallery/0200_cs/images/sphx_glr_bsbl_1_003.png :alt: bsbl 1 :srcset: /gallery/0200_cs/images/sphx_glr_bsbl_1_003.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 205-210 Reconstruction using BSBL EM ''''''''''''''''''''''''''''''''''' We need to provide the sensing matrix, measurements and the block size as parameters to the reconstruction algorithm .. GENERATED FROM PYTHON SOURCE LINES 210-214 .. code-block:: default options = bsbl.bsbl_em_options(y, learn_lambda=0) sol = bsbl.bsbl_em_jit(Phi, y, b, options) print(sol) .. rst-class:: sphx-glr-script-out .. code-block:: none iterations=24 block size=4 blocks=75, nonzero=6 r_norm=2.28e-10 x_norm=2.45e+00 lambda=1.00e-12 dmu=1.33e-10 .. GENERATED FROM PYTHON SOURCE LINES 215-216 Reconstructed signal .. GENERATED FROM PYTHON SOURCE LINES 216-220 .. code-block:: default x_hat = sol.x print(f'BSBL-EM: PRD: {crn.prd(x, x_hat):.1f} %, SNR: {crn.signal_noise_ratio(x, x_hat):.2f} dB.' ) .. rst-class:: sphx-glr-script-out .. code-block:: none BSBL-EM: PRD: 0.0 %, SNR: 198.62 dB. .. GENERATED FROM PYTHON SOURCE LINES 221-222 Plot the original and reconstructed signal .. GENERATED FROM PYTHON SOURCE LINES 222-227 .. code-block:: default ax = crplot.h_plots(2) ax[0].stem(x) ax[1].stem(x_hat) .. image-sg:: /gallery/0200_cs/images/sphx_glr_bsbl_1_004.png :alt: bsbl 1 :srcset: /gallery/0200_cs/images/sphx_glr_bsbl_1_004.png :class: sphx-glr-single-img .. rst-class:: sphx-glr-script-out .. code-block:: none .. GENERATED FROM PYTHON SOURCE LINES 228-233 Reconstruction using BSBL BO ''''''''''''''''''''''''''''''''''' We need to provide the sensing matrix, measurements and the block size as parameters to the reconstruction algorithm .. GENERATED FROM PYTHON SOURCE LINES 233-237 .. code-block:: default options = bsbl.bsbl_bo_options(y, learn_lambda=0) sol = bsbl.bsbl_bo_jit(Phi, y, b, options) print(sol) .. rst-class:: sphx-glr-script-out .. code-block:: none iterations=6 block size=4 blocks=75, nonzero=6 r_norm=2.06e-10 x_norm=2.45e+00 lambda=1.00e-12 dmu=1.14e-10 .. GENERATED FROM PYTHON SOURCE LINES 238-239 Reconstructed signal .. GENERATED FROM PYTHON SOURCE LINES 239-243 .. code-block:: default x_hat = sol.x print(f'BSBL-BO: PRD: {crn.prd(x, x_hat):.1f} %, SNR: {crn.signal_noise_ratio(x, x_hat):.2f} dB.' ) .. rst-class:: sphx-glr-script-out .. code-block:: none BSBL-BO: PRD: 0.0 %, SNR: 199.47 dB. .. GENERATED FROM PYTHON SOURCE LINES 244-245 Plot the original and reconstructed signal .. GENERATED FROM PYTHON SOURCE LINES 245-250 .. code-block:: default ax = crplot.h_plots(2) ax[0].stem(x) ax[1].stem(x_hat) .. image-sg:: /gallery/0200_cs/images/sphx_glr_bsbl_1_005.png :alt: bsbl 1 :srcset: /gallery/0200_cs/images/sphx_glr_bsbl_1_005.png :class: sphx-glr-single-img .. rst-class:: sphx-glr-script-out .. code-block:: none .. GENERATED FROM PYTHON SOURCE LINES 251-262 Observations: * We specified ``learn_lambda=0`` since we knew that this is a noiseless problem. * Note the nonzero blocks count. They have been identified correctly. * Recovery is perfect for both algorithms. In other words, both the nonzero coefficient values and locations have been correctly estimated and identified respectively. * BSBL-BO is faster compared to BSBL-EM. See how it finished in far less number of iterations. This is on expected lines as BSBL-BO accelerates the convergence using bound optimization a.k.a. majorization-minimization. .. GENERATED FROM PYTHON SOURCE LINES 264-266 Sparse Binary Sensing ------------------------------ .. GENERATED FROM PYTHON SOURCE LINES 268-269 We shall have just 12 ones in each column of the sensing matrix .. GENERATED FROM PYTHON SOURCE LINES 269-270 .. code-block:: default d = 12 .. GENERATED FROM PYTHON SOURCE LINES 271-272 Build the sensing matrix .. GENERATED FROM PYTHON SOURCE LINES 272-276 .. code-block:: default Phi = crdict.sparse_binary_mtx(crn.KEYS[0], m, n, d, normalize_atoms=True, dense=True) ax = crplot.one_plot() ax.spy(Phi); .. image-sg:: /gallery/0200_cs/images/sphx_glr_bsbl_1_006.png :alt: bsbl 1 :srcset: /gallery/0200_cs/images/sphx_glr_bsbl_1_006.png :class: sphx-glr-single-img .. rst-class:: sphx-glr-script-out .. code-block:: none .. GENERATED FROM PYTHON SOURCE LINES 277-278 Measurements .. GENERATED FROM PYTHON SOURCE LINES 278-282 .. code-block:: default y = Phi @ x ax = crplot.one_plot() crplot.plot_signal(ax, y) .. image-sg:: /gallery/0200_cs/images/sphx_glr_bsbl_1_007.png :alt: bsbl 1 :srcset: /gallery/0200_cs/images/sphx_glr_bsbl_1_007.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 283-285 Reconstruction using BSBL EM ''''''''''''''''''''''''''''''''''' .. GENERATED FROM PYTHON SOURCE LINES 285-289 .. code-block:: default options = bsbl.bsbl_em_options(y, learn_lambda=0) sol = bsbl.bsbl_em_jit(Phi, y, b, options) print(sol) .. rst-class:: sphx-glr-script-out .. code-block:: none iterations=24 block size=4 blocks=75, nonzero=6 r_norm=2.12e-10 x_norm=2.45e+00 lambda=1.00e-12 dmu=1.15e-10 .. GENERATED FROM PYTHON SOURCE LINES 290-291 Reconstructed signal .. GENERATED FROM PYTHON SOURCE LINES 291-295 .. code-block:: default x_hat = sol.x print(f'BSBL-EM: PRD: {crn.prd(x, x_hat):.1f} %, SNR: {crn.signal_noise_ratio(x, x_hat):.2f} dB.' ) .. rst-class:: sphx-glr-script-out .. code-block:: none BSBL-EM: PRD: 0.0 %, SNR: 199.70 dB. .. GENERATED FROM PYTHON SOURCE LINES 296-297 Plot the original and reconstructed signal .. GENERATED FROM PYTHON SOURCE LINES 297-302 .. code-block:: default ax = crplot.h_plots(2) ax[0].stem(x) ax[1].stem(x_hat) .. image-sg:: /gallery/0200_cs/images/sphx_glr_bsbl_1_008.png :alt: bsbl 1 :srcset: /gallery/0200_cs/images/sphx_glr_bsbl_1_008.png :class: sphx-glr-single-img .. rst-class:: sphx-glr-script-out .. code-block:: none .. GENERATED FROM PYTHON SOURCE LINES 303-305 Reconstruction using BSBL BO ''''''''''''''''''''''''''''''''''' .. GENERATED FROM PYTHON SOURCE LINES 305-309 .. code-block:: default options = bsbl.bsbl_bo_options(y, learn_lambda=0) sol = bsbl.bsbl_bo_jit(Phi, y, b, options) print(sol) .. rst-class:: sphx-glr-script-out .. code-block:: none iterations=6 block size=4 blocks=75, nonzero=6 r_norm=1.89e-10 x_norm=2.45e+00 lambda=1.00e-12 dmu=9.35e-11 .. GENERATED FROM PYTHON SOURCE LINES 310-311 Reconstructed signal .. GENERATED FROM PYTHON SOURCE LINES 311-315 .. code-block:: default x_hat = sol.x print(f'BSBL-BO: PRD: {crn.prd(x, x_hat):.1f} %, SNR: {crn.signal_noise_ratio(x, x_hat):.2f} dB.' ) .. rst-class:: sphx-glr-script-out .. code-block:: none BSBL-BO: PRD: 0.0 %, SNR: 200.75 dB. .. GENERATED FROM PYTHON SOURCE LINES 316-317 Plot the original and reconstructed signal .. GENERATED FROM PYTHON SOURCE LINES 317-321 .. code-block:: default ax = crplot.h_plots(2) ax[0].stem(x) ax[1].stem(x_hat) .. image-sg:: /gallery/0200_cs/images/sphx_glr_bsbl_1_009.png :alt: bsbl 1 :srcset: /gallery/0200_cs/images/sphx_glr_bsbl_1_009.png :class: sphx-glr-single-img .. rst-class:: sphx-glr-script-out .. code-block:: none .. GENERATED FROM PYTHON SOURCE LINES 322-328 Observations: * Recovery is perfect for both algorithms. * BSBL-BO is much faster. * Both algorithms are converging in same number of iterations as Gaussian sensing matrices. .. GENERATED FROM PYTHON SOURCE LINES 330-334 Noisy Measurements ------------------------------ We now consider an example where compressive measurements are corrupted with significant amount of noise. .. GENERATED FROM PYTHON SOURCE LINES 334-351 .. code-block:: default # rows (measurements) m = 80 # columns (signal space) n = 162 # block length b = 6 # number of nonzero blocks k = 5 # number of signals s = 1 # number of blocks nb = n // b # Signal to Noise Ratio in DB snr = 15 .. GENERATED FROM PYTHON SOURCE LINES 352-353 generate block sparse signal with high intra block correlation .. GENERATED FROM PYTHON SOURCE LINES 353-359 .. code-block:: default x, blocks, indices = crdata.sparse_normal_blocks( crn.KEYS[1], n, k, b, s, cor=0.95, normalize_blocks=True) ax = crplot.one_plot() ax.stem(x) .. image-sg:: /gallery/0200_cs/images/sphx_glr_bsbl_1_010.png :alt: bsbl 1 :srcset: /gallery/0200_cs/images/sphx_glr_bsbl_1_010.png :class: sphx-glr-single-img .. rst-class:: sphx-glr-script-out .. code-block:: none .. GENERATED FROM PYTHON SOURCE LINES 360-361 Sensing matrix .. GENERATED FROM PYTHON SOURCE LINES 361-363 .. code-block:: default Phi = crdict.gaussian_mtx(crn.KEYS[2], m, n) .. GENERATED FROM PYTHON SOURCE LINES 364-365 Noiseless measurements .. GENERATED FROM PYTHON SOURCE LINES 365-367 .. code-block:: default y0 = Phi @ x .. GENERATED FROM PYTHON SOURCE LINES 368-369 Noise at an SNR of 15 dB .. GENERATED FROM PYTHON SOURCE LINES 369-371 .. code-block:: default noise = crdsp.awgn_at_snr_std(crn.KEYS[3], y0, snr) .. GENERATED FROM PYTHON SOURCE LINES 372-373 Addition of noise to measurements .. GENERATED FROM PYTHON SOURCE LINES 373-376 .. code-block:: default y = y0 + noise print(f'measurement SNR: {crn.signal_noise_ratio(y0, y):.2f} dB') .. rst-class:: sphx-glr-script-out .. code-block:: none measurement SNR: 15.96 dB .. GENERATED FROM PYTHON SOURCE LINES 377-378 Plot the noiseless and noisy measurements .. GENERATED FROM PYTHON SOURCE LINES 378-383 .. code-block:: default ax = crplot.h_plots(2) ax[0].plot(y0) ax[1].plot(y) .. image-sg:: /gallery/0200_cs/images/sphx_glr_bsbl_1_011.png :alt: bsbl 1 :srcset: /gallery/0200_cs/images/sphx_glr_bsbl_1_011.png :class: sphx-glr-single-img .. rst-class:: sphx-glr-script-out .. code-block:: none [] .. GENERATED FROM PYTHON SOURCE LINES 384-392 Reconstruction Benchmark '''''''''''''''''''''''''''''''''' An oracle reconstruction is possible if one knows the nonzero indices of x. One can then compute a least square solution over these indices. The reconstruction SNR of this solution gives us a good benchmark against which we can evaluate the quality of reconstruction by any other algorithm. .. GENERATED FROM PYTHON SOURCE LINES 392-398 .. code-block:: default # Find the least square solution x_ls_coeffs = jnp.linalg.pinv(Phi[:, indices]) @ y x_ls = crdsp.build_signal_from_indices_and_values(n, indices, x_ls_coeffs) print(f'Benchmark rec: PRD: {crn.prd(x, x_ls):.1f} %, SNR: {crn.signal_noise_ratio(x, x_ls):.2f} dB') .. rst-class:: sphx-glr-script-out .. code-block:: none Benchmark rec: PRD: 13.3 %, SNR: 17.55 dB .. GENERATED FROM PYTHON SOURCE LINES 399-400 Plot the oracle reconstruction .. GENERATED FROM PYTHON SOURCE LINES 400-405 .. code-block:: default ax = crplot.h_plots(2) ax[0].stem(x) ax[1].stem(x_ls) .. image-sg:: /gallery/0200_cs/images/sphx_glr_bsbl_1_012.png :alt: bsbl 1 :srcset: /gallery/0200_cs/images/sphx_glr_bsbl_1_012.png :class: sphx-glr-single-img .. rst-class:: sphx-glr-script-out .. code-block:: none .. GENERATED FROM PYTHON SOURCE LINES 406-408 Reconstruction using BSBL EM ''''''''''''''''''''''''''''''''''' .. GENERATED FROM PYTHON SOURCE LINES 408-412 .. code-block:: default options = bsbl.bsbl_em_options(y) sol = bsbl.bsbl_em_jit(Phi, y, b, options) print(sol) .. rst-class:: sphx-glr-script-out .. code-block:: none iterations=62 block size=6 blocks=27, nonzero=5 r_norm=3.05e-01 x_norm=2.23e+00 lambda=1.68e-03 dmu=7.40e-09 .. GENERATED FROM PYTHON SOURCE LINES 413-414 Reconstructed signal .. GENERATED FROM PYTHON SOURCE LINES 414-418 .. code-block:: default x_hat = sol.x print(f'BSBL-EM: PRD: {crn.prd(x, x_hat):.1f} %, SNR: {crn.signal_noise_ratio(x, x_hat):.2f} dB.' ) .. rst-class:: sphx-glr-script-out .. code-block:: none BSBL-EM: PRD: 11.0 %, SNR: 19.16 dB. .. GENERATED FROM PYTHON SOURCE LINES 419-420 Plot the original and reconstructed signal .. GENERATED FROM PYTHON SOURCE LINES 420-425 .. code-block:: default ax = crplot.h_plots(2) ax[0].stem(x) ax[1].stem(x_hat) .. image-sg:: /gallery/0200_cs/images/sphx_glr_bsbl_1_013.png :alt: bsbl 1 :srcset: /gallery/0200_cs/images/sphx_glr_bsbl_1_013.png :class: sphx-glr-single-img .. rst-class:: sphx-glr-script-out .. code-block:: none .. GENERATED FROM PYTHON SOURCE LINES 426-428 Reconstruction using BSBL BO ''''''''''''''''''''''''''''''''''' .. GENERATED FROM PYTHON SOURCE LINES 428-432 .. code-block:: default options = bsbl.bsbl_bo_options(y) sol = bsbl.bsbl_bo_jit(Phi, y, b, options) print(sol) .. rst-class:: sphx-glr-script-out .. code-block:: none iterations=56 block size=6 blocks=27, nonzero=5 r_norm=3.00e-01 x_norm=2.21e+00 lambda=1.56e-03 dmu=9.77e-09 .. GENERATED FROM PYTHON SOURCE LINES 433-434 Reconstructed signal .. GENERATED FROM PYTHON SOURCE LINES 434-438 .. code-block:: default x_hat = sol.x print(f'BSBL-BO: PRD: {crn.prd(x, x_hat):.1f} %, SNR: {crn.signal_noise_ratio(x, x_hat):.2f} dB.' ) .. rst-class:: sphx-glr-script-out .. code-block:: none BSBL-BO: PRD: 10.6 %, SNR: 19.45 dB. .. GENERATED FROM PYTHON SOURCE LINES 439-440 Plot the original and reconstructed signal .. GENERATED FROM PYTHON SOURCE LINES 440-445 .. code-block:: default ax = crplot.h_plots(2) ax[0].stem(x) ax[1].stem(x_hat) .. image-sg:: /gallery/0200_cs/images/sphx_glr_bsbl_1_014.png :alt: bsbl 1 :srcset: /gallery/0200_cs/images/sphx_glr_bsbl_1_014.png :class: sphx-glr-single-img .. rst-class:: sphx-glr-script-out .. code-block:: none .. GENERATED FROM PYTHON SOURCE LINES 446-458 Observations: * Benchmark SNR is slightly better than measurement SNR. This is due to the fact that we are using our knowledge of support for x. * Both BSBL-EM and BSBL-BO are able to detect the correct support of x. * SNR for both BSBL-EM and BSBL-BO is better than the benchmark SNR. This is due to the fact that BSBL is exploiting the intra-block correlation modeled as an AR-1 process. * The ordinary least squares solution is not attempting to exploit the intra block correlation structure at all. * In this example BSBL-BO is somewhat faster than BSBL-EM but not by much. .. rst-class:: sphx-glr-timing **Total running time of the script:** (0 minutes 16.414 seconds) .. _sphx_glr_download_gallery_0200_cs_bsbl_1.py: .. only:: html .. container:: sphx-glr-footer sphx-glr-footer-example .. container:: sphx-glr-download sphx-glr-download-python :download:`Download Python source code: bsbl_1.py ` .. container:: sphx-glr-download sphx-glr-download-jupyter :download:`Download Jupyter notebook: bsbl_1.ipynb ` .. only:: html .. rst-class:: sphx-glr-signature `Gallery generated by Sphinx-Gallery `_