diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index 6ab3295b..4ebd8e12 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -1,8 +1,5 @@ -# From https://github.com/mitmproxy/pdoc/blob/main/.github/workflows/docs.yml -# with minor adaptations - -name: Deploy docs - +# From https://github.com/mhausenblas/mkdocs-deploy-gh-pages +name: Publish mkDocs via GitHub Pages # build the documentation whenever there are new commits on main on: push: @@ -12,38 +9,18 @@ on: # tags: # - '*' -# security: restrict permissions for CI jobs. -permissions: - contents: read - jobs: - # Build the documentation and upload the static HTML files as an artifact. build: + name: Deploy MkDocs runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 - with: - python-version: '3.10' - - - run: pip install -e .[build] - - run: pdoc -o docs-generated/ -t docs/templates --math --docformat=numpy docs/bib/bib.py docs/dev_guide.py ./dapper + - name: Checkout main + uses: actions/checkout@v2 - - uses: actions/upload-pages-artifact@v1 - with: - path: docs-generated/ - - # Deploy the artifact to GitHub pages. - # This is a separate job so that only actions/deploy-pages has the necessary permissions. - deploy: - needs: build - runs-on: ubuntu-latest - permissions: - pages: write - id-token: write - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - steps: - - id: deployment - uses: actions/deploy-pages@v1 + - name: Deploy docs + uses: mhausenblas/mkdocs-deploy-gh-pages@master + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + CONFIG_FILE: mkdocs.yml + REQUIREMENTS: requirements.txt + # CUSTOM_DOMAIN: optionaldomain.com diff --git a/.gitignore b/.gitignore index 18d3ded3..01439c6a 100644 --- a/.gitignore +++ b/.gitignore @@ -154,9 +154,6 @@ cython_debug/ # Don't commit results dpr_data/ -# Don't include local builds of docs -doc-build - # Generated when running QG model dapper/mods/QG/f90/prms_*\.txt diff --git a/.markdownlint.yaml b/.markdownlint.yaml new file mode 100644 index 00000000..285b5c66 --- /dev/null +++ b/.markdownlint.yaml @@ -0,0 +1,9 @@ +# markdownlint (js library) config +MD052: false +first-line-h1: false +no-multiple-blanks: false +line-length: false +# MD029: +# style: "ordered" +# MD007: false +# "MD007": { "indent": 4 } diff --git a/README.md b/README.md index b108eee6..a7b3e66b 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ ! --> - + DAPPER is a set of templates for **benchmarking** the performance of **data assimilation** (DA) methods. The numerical experiments provide support and guidance for new developments in DA. @@ -27,7 +27,7 @@ and then estimate that truth given the models and noisy observations. ## Getting started [Install](#installation), then -read, run and try to understand `examples/basic_{1,2,3}.py`. +read, run and try to understand `docs/examples/basic_{1,2,3}.py`. Some of the examples also exist as Jupyter notebooks, and can be run in the cloud *without installation* (but requiring Google login): [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](http://colab.research.google.com/github/nansencenter/DAPPER). This [screencast](https://www.youtube.com/watch?v=YtalK0Zkzvg&t=6475s) @@ -49,7 +49,7 @@ through a variety of typical [test cases](#test-cases-models) and statistics. It (b) facilitates comparative studies, thus promoting the (a) reliability and (b) relevance of the results. -For example, the figure below is generated by `examples/basic_3.py`, +For example, the figure below is generated by `docs/examples/basic_3.py`, reproduces figure 5.7 of [these lecture notes](http://cerea.enpc.fr/HomePages/bocquet/teaching/assim-mb-en.pdf). DAPPER is (c) open source, written in Python, and @@ -59,10 +59,10 @@ this promotes the (d) dissemination of the underlying science, and makes it easy to adapt and extend. -![Comparative benchmarks with Lorenz-96 plotted as a function of the ensemble size (N)](./docs/imgs/ex3.svg) +![Comparative benchmarks with Lorenz-96 plotted as a function of the ensemble size (N)](https://github.com/nansencenter/DAPPER/raw/master/docs/images/ex3.svg) DAPPER demonstrates how to parallelise ensemble forecasts (e.g., the QG model), -local analyses (e.g., the LETKF), and independent experiments (e.g., `examples/basic_3.py`). +local analyses (e.g., the LETKF), and independent experiments (e.g., `docs/examples/basic_3.py`). It includes a battery of diagnostics and statistics, which all get averaged over subdomains (e.g., "ocean" and "land") and then in time. Confidence intervals are computed, including correction for auto-correlations, @@ -72,7 +72,7 @@ which may be paused for further interactive inspection. In summary, DAPPER is well suited for teaching and fundamental DA research. Also see its [drawbacks](#similar-projects). -![EnKF - Lorenz-96](./docs/imgs/ex1.jpg) +![EnKF - Lorenz-96](https://github.com/nansencenter/DAPPER/raw/master/docs/images/ex1.jpg) -![NORCE](./docs/imgs/norce-logo.png) -![NERSC](./docs/imgs/nansen-logo.png) - - - +![NORCE](https://github.com/nansencenter/DAPPER/raw/master/docs/images/logos/norce-logo.png) +![NERSC](https://github.com/nansencenter/DAPPER/raw/master/docs/images/logos/nansen-logo.png) + + + ## Publications diff --git a/dapper/README.md b/dapper/README.md index ed35297b..e00f5adc 100644 --- a/dapper/README.md +++ b/dapper/README.md @@ -1,20 +1,3 @@ -## API reference - -Click the links in the navigation menu to view -the docs for the various modules. - -## Installation - -See [README/Installation](https://github.com/nansencenter/DAPPER#installation) - -## Usage - -See [README/Getting-started](https://github.com/nansencenter/DAPPER#getting-started) - -##### Examples - -See [examples/README](https://github.com/nansencenter/DAPPER/tree/master/examples) - ##### Adding your own model/method If you wish to illustrate and run benchmarks with @@ -22,22 +5,13 @@ your own **model** or **method**, then - If it is a complex one, you may be better off using DAPPER merely as *inspiration* (but you can still - [cite it](https://github.com/nansencenter/DAPPER#getting-started)) + [cite it](../#getting-started)) rather than trying to squeeze everything into its templates. - If it is relatively simple, however, you may well want to use DAPPER. In that case, read this: - - `dapper.mods` - - `dapper.da_methods` + - [`mods`](mods) + - [`da_methods`](da_methods) Since the generality of DAPPER is -[limited](https://github.com/nansencenter/DAPPER#similar-projects) +[limited](../#similar-projects) it is quite likely you will also need to make changes to the DAPPER code itself. - -## Developer guide - -If you are making a pull request, please read the [developer guide](dev_guide). - -## Bibliography - -Click the various citations/references (e.g. `bib.anderson2010non`) -to access the [bibliography](bib). diff --git a/dapper/__init__.py b/dapper/__init__.py index 11a65160..80401d8c 100644 --- a/dapper/__init__.py +++ b/dapper/__init__.py @@ -1,7 +1,7 @@ """Root package of **DAPPER** (Data Assimilation with Python: a Package for Experimental Research) -.. include:: ./README.md +--8<-- "dapper/README.md" """ __version__ = "1.7.0" diff --git a/dapper/da_methods/README.md b/dapper/da_methods/README.md index 85b619a8..20bb9755 100644 --- a/dapper/da_methods/README.md +++ b/dapper/da_methods/README.md @@ -1,6 +1,4 @@ -Also see the section on -[DA Methods](https://github.com/nansencenter/DAPPER#DA-Methods) -in the main README +Also see [this section on DA Methods](../../#da-methods) for an overview of the methods included with DAPPER. ## Defining your own method @@ -8,7 +6,7 @@ for an overview of the methods included with DAPPER. Follow the example of one of the methods within one of the sub-directories/packages. The simplest example is perhaps -`dapper.da_methods.ensemble.EnKF`. +[`da_methods.ensemble.EnKF`][]. ## General advice for programming/debugging scientific experiments diff --git a/dapper/da_methods/__init__.py b/dapper/da_methods/__init__.py index 3fb46c23..b6b3bd60 100644 --- a/dapper/da_methods/__init__.py +++ b/dapper/da_methods/__init__.py @@ -1,6 +1,6 @@ """Contains the data assimilation methods included with DAPPER. -.. include:: ./README.md +--8<-- "dapper/da_methods/README.md" """ from pathlib import Path @@ -16,7 +16,7 @@ def da_method(*default_dataclasses): The decorated classes are defined like a `dataclass`, but are decorated by `@da_method()` instead of `@dataclass`. - .. note:: + !!! note The classes must define a method called `assimilate`. This method gets slightly enhanced by this wrapper which provides: @@ -25,7 +25,8 @@ def da_method(*default_dataclasses): - Duration timing - Progressbar naming magic. - Example: + Examples + -------- >>> @da_method() ... class Sleeper(): ... "Do nothing." @@ -43,7 +44,6 @@ def da_method(*default_dataclasses): which enables defining default parameters which can be inherited, similar to subclassing. - Example: >>> class ens_defaults: ... infl : float = 1.0 ... rot : bool = False @@ -56,13 +56,13 @@ def da_method(*default_dataclasses): ... def assimilate(self, HMM, xx, yy): ... ... - .. note:: + !!! note Apart from what's listed in the above `Note`, there is nothing special to the resulting `xp`. That is, just like any Python object, it can serve as a data container, and you can write any number of attributes to it (at creation-time, or later). For example, you can set attributes that are not used by the `assimilate` method, but are instead used to customize other aspects of the - experiments (see `dapper.xp_launch.run_experiment`). + experiments (see [`xp_launch.run_experiment`][]). """ import dataclasses import functools diff --git a/dapper/da_methods/baseline.py b/dapper/da_methods/baseline.py index 64cb0b96..87903539 100644 --- a/dapper/da_methods/baseline.py +++ b/dapper/da_methods/baseline.py @@ -1,6 +1,6 @@ """Unsophisticated" but robust (widely applicable) DA methods. -Many are based on `bib.raanes2016thesis`. +Many are based on [raanes2016thesis][]. """ from typing import Callable, Optional @@ -146,10 +146,15 @@ def fit_sigmoid(Sb, L, kb): The "normalized" sigmoid, `S1`, is symmetric around 0, and `S1(-∞)=0` and `S1(∞)=1`. - The sigmoid `S(k) = S1(a*(k-kb) + b)` is fitted (see docs/snippets/sigmoid.jpg) with + The sigmoid `S(k) = S1(a*(k-kb) + b)` is fitted with - `a` corresponding to a given corr. length `L`. - `b` to match values of `S(kb)` and `Sb` + +
+ ![](../../images/snippets/sigmoid.jpg){ width="300" } +
Illustration
+
""" def sigmoid(k): diff --git a/dapper/da_methods/ensemble.py b/dapper/da_methods/ensemble.py index aa9b40b9..96507316 100644 --- a/dapper/da_methods/ensemble.py +++ b/dapper/da_methods/ensemble.py @@ -28,7 +28,7 @@ class ens_method: class EnKF: """The ensemble Kalman filter. - Refs: `bib.evensen2009ensemble`. + Refs: [evensen2009ensemble][]. """ upd_a: str @@ -67,8 +67,8 @@ def EnKF_analysis(E, Eo, hnoise, y, upd_a, stats=None, ko=None): This implementation includes several flavours and forms, specified by `upd_a`. - Main references: `bib.sakov2008deterministic`, - `bib.sakov2008implications`, `bib.hoteit2015mitigating` + Main references: [sakov2008deterministic][], + [sakov2008implications][], [hoteit2015mitigating][] """ R = hnoise.C # Obs noise cov N, Nx = E.shape # Dimensionality @@ -113,7 +113,7 @@ def EnKF_analysis(E, Eo, hnoise, y, upd_a, stats=None, ko=None): d = pad0(s**2, N) + N1 Pw = (V * d ** (-1.0)) @ V.T T = (V * d ** (-0.5)) @ V.T * sqrt(N1) - # docs/snippets/trHK.jpg + # docs/images/snippets/trHK.jpg trHK = np.sum((s**2 + N1) ** (-1.0) * s**2) elif "sS" in upd_a: # Same as 'svd', but with slightly different notation @@ -123,7 +123,7 @@ def EnKF_analysis(E, Eo, hnoise, y, upd_a, stats=None, ko=None): d = pad0(s**2, N) + 1 Pw = (V * d ** (-1.0)) @ V.T / N1 # = G/(N1) T = (V * d ** (-0.5)) @ V.T - # docs/snippets/trHK.jpg + # docs/images/snippets/trHK.jpg trHK = np.sum((s**2 + 1) ** (-1.0) * s**2) else: # 'eig' in upd_a: # Implementation using eig. val. decomp. @@ -261,7 +261,7 @@ def post_process(E, infl, rot): def add_noise(E, dt, noise, method): """Treatment of additive noise for ensembles. - Refs: `bib.raanes2014ext` + Refs: [raanes2014ext][] """ if noise.C == 0: return E @@ -354,7 +354,7 @@ def sqrt_core(): class EnKS: """The ensemble Kalman smoother. - Refs: `bib.evensen2009ensemble` + Refs: [evensen2009ensemble][] The only difference to the EnKF is the management of the lag and the reshapings. @@ -414,7 +414,7 @@ def assimilate(self, HMM, xx, yy): class EnRTS: """EnRTS (Rauch-Tung-Striebel) smoother. - Refs: `bib.raanes2016thesis` + Refs: [raanes2016thesis][] """ upd_a: str @@ -484,7 +484,7 @@ def serial_inds(upd_a, y, cvR, A): class SL_EAKF: """Serial, covariance-localized EAKF. - Refs: `bib.karspeck2007experimental`. + Refs: [karspeck2007experimental][]. In contrast with LETKF, this iterates over the observations rather than over the state (batches). @@ -614,10 +614,10 @@ def local_analysis(ii): class LETKF: """Same as EnKF (Sqrt), but with localization. - Refs: `bib.hunt2007efficient`. + Refs: [hunt2007efficient][]. - NB: Multiproc. yields slow-down for `dapper.mods.Lorenz96`, - even with `batch_size=(1,)`. But for `dapper.mods.QG` + NB: Multiproc. yields slow-down for [`mods.Lorenz96`][], + even with `batch_size=(1,)`. But for [`mods.QG`][] (`batch_size=(2,2)` or less) it is quicker. NB: If `len(ii)` is small, analysis may be slowed-down with '-N' infl. @@ -778,7 +778,7 @@ def hyperprior_coeffs(s, N, xN=1, g=0): - Reason 1: mode correction. These parameters bridge the Jeffreys (`xN=1`) and Dirac (`xN=Inf`) hyperpriors - for the prior covariance, B, as discussed in `bib.bocquet2015expanding`. + for the prior covariance, B, as discussed in [bocquet2015expanding][]. Indeed, mode correction becomes necessary when $$ R \rightarrow \infty $$ because then there should be no ensemble update (and also no inflation!). More specifically, the mode of `l1`'s should be adjusted towards 1 @@ -790,7 +790,7 @@ def hyperprior_coeffs(s, N, xN=1, g=0): - Reason 2: Boosting the inflation prior's certainty from N to xN*N. The aim is to take advantage of the fact that the ensemble may not have quite as much sampling error as a fully stochastic sample, - as illustrated in section 2.1 of `bib.raanes2019adaptive`. + as illustrated in section 2.1 of [raanes2019adaptive][]. - Its damping effect is similar to work done by J. Anderson. @@ -809,7 +809,7 @@ def hyperprior_coeffs(s, N, xN=1, g=0): eN = (N + 1) / N cL = (N + g) / N1 - # Mode correction (almost) as in eqn 36 of `bib.bocquet2015expanding` + # Mode correction (almost) as in eqn 36 of [bocquet2015expanding][] prior_mode = eN / cL # Mode of l1 (before correction) diagonal = pad0(s**2, N) + N1 # diag of Y@R.inv@Y + N1*I # (Hessian of J) @@ -834,7 +834,7 @@ def zeta_a(eN, cL, w): Returns `zeta_a = (N-1)/pre-inflation^2`. Using this inside an iterative minimization as in the - `dapper.da_methods.variational.iEnKS` effectively blends + [`da_methods.variational.iEnKS`][] effectively blends the distinction between the primal and dual EnKF-N. """ N = len(w) @@ -847,12 +847,12 @@ def zeta_a(eN, cL, w): class EnKF_N: """Finite-size EnKF (EnKF-N). - Refs: `bib.bocquet2011ensemble`, `bib.bocquet2015expanding` + Refs: [bocquet2011ensemble][], [bocquet2015expanding][] This implementation is pedagogical, prioritizing the "dual" form. In consequence, the efficiency of the "primal" form suffers a bit. The primal form is included for completeness and to demonstrate equivalence. - In `dapper.da_methods.variational.iEnKS`, however, + In [`da_methods.variational.iEnKS`][], however, the primal form is preferred because it already does optimization for w (as treatment for nonlinear models). @@ -985,7 +985,7 @@ def nvrs(w): # l1 = 1.0 # Explicitly inflate prior - # => formulae look different from `bib.bocquet2015expanding`. + # => formulae look different from [bocquet2015expanding][]. A *= l1 Y *= l1 @@ -1000,7 +1000,7 @@ def nvrs(w): else: # Also include angular-radial co-dependence. # Note: denominator not squared coz - # unlike `bib.bocquet2015expanding` we have inflated Y. + # unlike [bocquet2015expanding][] we have inflated Y. Hw = ( Y @ R.inv @ Y.T / N1 + eye(N) diff --git a/dapper/da_methods/other.py b/dapper/da_methods/other.py index 95d44512..f13a997c 100644 --- a/dapper/da_methods/other.py +++ b/dapper/da_methods/other.py @@ -15,7 +15,7 @@ class RHF: """Rank histogram filter. - Refs: `bib.anderson2010non`. + Refs: [anderson2010non][]. Quick & dirty implementation without attention to (de)tails. """ @@ -86,9 +86,9 @@ def assimilate(self, HMM, xx, yy): class LNETF: """The Nonlinear-Ensemble-Transform-Filter (localized). - Refs: `bib.wiljes2016second`, `bib.todter2015second`. + Refs: [wiljes2016second][], [todter2015second][]. - It is (supposedly) a deterministic upgrade of the NLEAF of `bib.lei2011moment`. + It is (supposedly) a deterministic upgrade of the NLEAF of [lei2011moment][]. """ N: int diff --git a/dapper/da_methods/particle.py b/dapper/da_methods/particle.py index b3f1fbbd..7e929329 100644 --- a/dapper/da_methods/particle.py +++ b/dapper/da_methods/particle.py @@ -23,7 +23,7 @@ class particle_method: class PartFilt: r"""Particle filter ≡ Sequential importance (re)sampling SIS (SIR). - Refs: `bib.wikle2007bayesian`, `bib.van2009particle`, `bib.chen2003bayesian` + Refs: [wikle2007bayesian][], [van2009particle][], [chen2003bayesian][] This is the bootstrap version: the proposal density is just @@ -37,7 +37,7 @@ class PartFilt: - qroot: "Inflate" (anneal) the proposal noise kernels by this root to increase diversity. The weights are updated to maintain un-biased-ness. - See `bib.chen2003bayesian`, section VI-M.2 + See [chen2003bayesian][], section VI-M.2 """ N: int @@ -92,11 +92,10 @@ def assimilate(self, HMM, xx, yy): class OptPF: """'Optimal proposal' particle filter, also known as 'Implicit particle filter'. - Ref: `bib.bocquet2010beyond`. + Ref: [bocquet2010beyond][]. - .. note:: Regularization (`Qs`) is here added BEFORE Bayes' rule. - If `Qs==0`: OptPF should be equal to - the bootstrap filter `PartFilt`. + !!! note Regularization (`Qs`) is here added BEFORE Bayes' rule. + If `Qs==0`: OptPF should be equal to the bootstrap filter `PartFilt`. """ N: int @@ -521,7 +520,7 @@ def mask_unique_of_sorted(idx): def auto_bandw(N, M): """Optimal bandwidth (not bandwidth^2), as per Scott's rule-of-thumb. - Refs: `bib.doucet2001sequential` section 12.2.2, [Wik17]_ section "Rule_of_thumb" + Refs: [doucet2001sequential][] section 12.2.2, [Wik17]_ section "Rule_of_thumb" """ return N ** (-1 / (M + 4)) @@ -535,7 +534,7 @@ def regularize(C12, E, idx, no_uniq_jitter): (so-named because Dirac-deltas are approximated Gaussian kernels), which controls the strength of the jitter. This causes a bias. But, as N-->∞, the reg. bandwidth-->0, i.e. bias-->0. - Ref: `bib.doucet2001sequential`, section 12.2.2. + Ref: [doucet2001sequential][], section 12.2.2. """ # Select E = E[idx] @@ -555,7 +554,7 @@ def regularize(C12, E, idx, no_uniq_jitter): def resample(w, kind="Systematic", N=None, wroot=1.0): """Multinomial resampling. - Refs: `bib.doucet2009tutorial`, `bib.van2009particle`, `bib.liu2001theoretical`. + Refs: [doucet2009tutorial][], [van2009particle][], [liu2001theoretical][]. - kind: 'Systematic', 'Residual' or 'Stochastic'. 'Stochastic' corresponds to `rng.choice` or `rng.multinomial`. @@ -564,7 +563,7 @@ def resample(w, kind="Systematic", N=None, wroot=1.0): Among the three, 'Systematic' is fastest, introduces the least noise, and brings continuity benefits for localized particle filters, and is therefore generally prefered. - Example: see docs/snippets/ex_resample.py. + Example: see `docs/images/snippets/ex_resample.py`. - N can be different from len(w) (e.g. in case some particles have been elimintated). @@ -572,7 +571,7 @@ def resample(w, kind="Systematic", N=None, wroot=1.0): - wroot: Adjust weights before resampling by this root to promote particle diversity and mitigate thinning. The outcomes of the resampling are then weighted to maintain un-biased-ness. - Ref: `bib.liu2001theoretical`, section 3.1 + Ref: [liu2001theoretical][], section 3.1 Note: (a) resampling methods are beneficial because they discard low-weight ("doomed") particles and reduce the variance of the weights. diff --git a/dapper/da_methods/variational.py b/dapper/da_methods/variational.py index 981f1123..9e344d17 100644 --- a/dapper/da_methods/variational.py +++ b/dapper/da_methods/variational.py @@ -28,10 +28,10 @@ class var_method: class iEnKS: """Iterative EnKS. - Special cases: EnRML, ES-MDA, iEnKF, EnKF `bib.raanes2019revising`. + Special cases: EnRML, ES-MDA, iEnKF, EnKF [raanes2019revising][]. - As in `bib.bocquet2014iterative`, optimization uses Gauss-Newton. - See `bib.bocquet2012combining` for Levenberg-Marquardt. + As in [bocquet2014iterative][], optimization uses Gauss-Newton. + See [bocquet2012combining][] for Levenberg-Marquardt. If MDA=True, then there's not really any optimization, but rather Gaussian annealing. @@ -41,13 +41,13 @@ class iEnKS: - "Sqrt" : as in ETKF , using a deterministic matrix square root transform. - "PertObs": as in EnRML , using stochastic, perturbed-observations. - - "Order1" : as in DEnKF of `bib.sakov2008deterministic`. + - "Order1" : as in DEnKF of [sakov2008deterministic][]. Lag: Length of the DA window (DAW, multiples of dko, i.e. cycles). - - Lag=1 (default) => iterative "filter" iEnKF `bib.sakov2012iterative`. - - Lag=0 => maximum-likelihood filter `bib.zupanski2005maximum`. + - Lag=1 (default) => iterative "filter" iEnKF [sakov2012iterative][]. + - Lag=0 => maximum-likelihood filter [zupanski2005maximum][]. Shift : How far (in cycles) to slide the DAW. Fixed at 1 for code simplicity. @@ -60,7 +60,7 @@ class iEnKS: Recommended: 1e-5. MDA : Use iterations of the "multiple data assimlation" type. - Ref `bib.emerick2012history` + Ref [emerick2012history][] bundle: Use finite-diff. linearization instead of of least-squares regression. Makes the iEnKS very much alike the iterative, extended KF (IEKS). @@ -70,8 +70,8 @@ class iEnKS: Total number of model simulations (of duration dto): N * (nIter*Lag + 1). (due to boundary cases: only asymptotically valid) - Refs: `bib.bocquet2012combining`, `bib.bocquet2013joint`, - `bib.bocquet2014iterative`. + Refs: [bocquet2012combining][], [bocquet2013joint][], + [bocquet2014iterative][]. """ upd_a: str @@ -264,7 +264,7 @@ def Cowp(expo): # (i) avoid re-running the model and # (ii) reproduce EnKF in case nIter==1. final_increment = (dw + T - T_old) @ Xf - # See docs/snippets/iEnKS_Ea.jpg. + # See docs/images/snippets/iEnKS_Ea.jpg. stats.assess(k, ko, "a", E=E + final_increment) stats.iters[ko] = iteration + 1 if xN: diff --git a/dapper/mods/DoublePendulum/__init__.py b/dapper/mods/DoublePendulum/__init__.py index 7d124dcf..723f4bfa 100644 --- a/dapper/mods/DoublePendulum/__init__.py +++ b/dapper/mods/DoublePendulum/__init__.py @@ -27,6 +27,7 @@ @modelling.ens_compatible def dxdt(x): + """Evolution equation (coupled ODEs) specifying the dynamics.""" th1, w1, th2, w2 = x dydx = np.zeros_like(x) diff --git a/dapper/mods/KS/bocquet2019.py b/dapper/mods/KS/bocquet2019.py index 8a285ca5..936ee162 100644 --- a/dapper/mods/KS/bocquet2019.py +++ b/dapper/mods/KS/bocquet2019.py @@ -1,4 +1,4 @@ -"""Settings as in `bib.bocquet2019consistency`.""" +"""Settings as in [bocquet2019consistency][].""" import numpy as np diff --git a/dapper/mods/LA/__init__.py b/dapper/mods/LA/__init__.py index 5d115d9f..0432cf3e 100644 --- a/dapper/mods/LA/__init__.py +++ b/dapper/mods/LA/__init__.py @@ -76,7 +76,8 @@ def sinusoidal_sample(Nx, k, N): but serves to avoid the initial transitory regime if the model is dissipative(, and more ?). - Example: + Examples + -------- >>> E = sinusoidal_sample(100, 4, 5) >>> plt.plot(E.T) # doctest: +SKIP """ diff --git a/dapper/mods/LA/evensen2009.py b/dapper/mods/LA/evensen2009.py index 5dd4a854..26de4e63 100644 --- a/dapper/mods/LA/evensen2009.py +++ b/dapper/mods/LA/evensen2009.py @@ -1,12 +1,12 @@ -"""A mix of `bib.evensen2009ensemble` and `bib.sakov2008implications`. +"""A mix of [evensen2009ensemble][] and [sakov2008implications][]. -.. note:: +!!! note Since there is no noise, and the system is stable, the rmse's from this HMM go to zero as `T` goes to infinity. Thus, benchmarks largely depend on the initial error, and so these absolute rmse values are not so useful for quantatative evaluation of DA methods. - For that purpose, see `dapper.mods.LA.raanes2015` instead. + For that purpose, see [`mods.LA.raanes2015`][] instead. """ import numpy as np diff --git a/dapper/mods/LA/raanes2015.py b/dapper/mods/LA/raanes2015.py index b897cd03..b50835b5 100644 --- a/dapper/mods/LA/raanes2015.py +++ b/dapper/mods/LA/raanes2015.py @@ -1,4 +1,4 @@ -"""Reproduce results from Fig. 2 of `bib.raanes2014ext`.""" +"""Reproduce results from Fig. 2 of [raanes2014ext][].""" import numpy as np diff --git a/dapper/mods/Lorenz05/__init__.py b/dapper/mods/Lorenz05/__init__.py index f6758240..a85e947a 100644 --- a/dapper/mods/Lorenz05/__init__.py +++ b/dapper/mods/Lorenz05/__init__.py @@ -1,8 +1,8 @@ """A multi-scale, smooth version of the classic Lorenz-96. -This is an implementation of "Model III" of `bib.lorenz2005designing`. +This is an implementation of "Model III" of [lorenz2005designing][]. -Similar to `dapper.mods.LorenzUV` this model is designed +Similar to [`mods.LorenzUV`][] this model is designed to contain two different scales. However, in "Model III" the two scales are not kept separate, but superimposed, and the large scale variables are (adjustably) spatially smooth. @@ -111,7 +111,7 @@ def step(self, E, t, dt): def summation_kernel(width): - """Prepare computation of the modified sum in `bib.lorenz2005designing`. + """Prepare computation of the modified sum in [lorenz2005designing][]. Note: This gets repeatedly called, but actually the input is only ever `width = K` or `2*J`, so we should really cache or pre-compute. @@ -131,7 +131,7 @@ def boxcar(x, n, method="direct"): For symmetry, if `n` is pair, the actual number of elements used is `n+1`, and the outer elements weighted by `0.5` to compensate for the `+1`. - This is the modified sum of `bib.lorenz2005designing`, used e.g. in eqn. 9. + This is the modified sum of [lorenz2005designing][], used e.g. in eqn. 9. For intuition: this type of summation (and the associated Sigma prime notation) may also be found for the "Trapezoidal rule" and in the inverse DFT used in spectral methods on a periodic domain. @@ -151,7 +151,8 @@ def boxcar(x, n, method="direct"): method is a little slower again. If `K` or `J` is increased, then the "fft" method becomes the fastest. - Examples: + Examples + -------- >>> x = np.array([0, 1, 2], dtype=float) >>> np.allclose(boxcar(x, 1), x) True @@ -216,7 +217,7 @@ def shift(x, k): def prodsum_self(x, k): - """Compute `prodsum(x, x, k)` efficiently: eqn 10 of `bib.lorenz2005designing`.""" + """Compute `prodsum(x, x, k)` efficiently: eqn 10 of [lorenz2005designing][].""" W = boxcar(x, k) WW = shift(W, -2 * k) * shift(W, -k) WX = shift(W, -k) * shift(x, k) diff --git a/dapper/mods/Lorenz63/anderson2010rhf.py b/dapper/mods/Lorenz63/anderson2010rhf.py index dc05a8af..036cab44 100644 --- a/dapper/mods/Lorenz63/anderson2010rhf.py +++ b/dapper/mods/Lorenz63/anderson2010rhf.py @@ -1,4 +1,4 @@ -"""Settings from `bib.anderson2010non`.""" +"""Settings from [anderson2010non][].""" import numpy as np diff --git a/dapper/mods/Lorenz63/bocquet2012.py b/dapper/mods/Lorenz63/bocquet2012.py index 23c5f06e..a2641ee2 100644 --- a/dapper/mods/Lorenz63/bocquet2012.py +++ b/dapper/mods/Lorenz63/bocquet2012.py @@ -1,4 +1,4 @@ -"""Reproduce results from Fig 11 of `bib.bocquet2012combining`.""" +"""Reproduce results from Fig 11 of [bocquet2012combining][].""" import numpy as np diff --git a/dapper/mods/Lorenz63/mandel2016.py b/dapper/mods/Lorenz63/mandel2016.py index d9d74ddf..ce08a444 100644 --- a/dapper/mods/Lorenz63/mandel2016.py +++ b/dapper/mods/Lorenz63/mandel2016.py @@ -1,4 +1,4 @@ -"""Settings from `bib.mandel2016hybrid`.""" +"""Settings from [mandel2016hybrid][].""" import dapper.mods as modelling from dapper.mods.Lorenz63.sakov2012 import HMM as _HMM diff --git a/dapper/mods/Lorenz63/sakov2012.py b/dapper/mods/Lorenz63/sakov2012.py index 7dc1c699..11d8488d 100644 --- a/dapper/mods/Lorenz63/sakov2012.py +++ b/dapper/mods/Lorenz63/sakov2012.py @@ -1,4 +1,4 @@ -"""Reproduce results from Table 1 `bib.sakov2012iterative`.""" +"""Reproduce results from Table 1 [sakov2012iterative][].""" import numpy as np diff --git a/dapper/mods/Lorenz63/wiljes2017.py b/dapper/mods/Lorenz63/wiljes2017.py index d6def422..a1471de9 100644 --- a/dapper/mods/Lorenz63/wiljes2017.py +++ b/dapper/mods/Lorenz63/wiljes2017.py @@ -1,4 +1,4 @@ -"""Settings from `bib.wiljes2016second`.""" +"""Settings from [wiljes2016second][].""" import numpy as np diff --git a/dapper/mods/Lorenz84/__init__.py b/dapper/mods/Lorenz84/__init__.py index a069780f..4f27bf39 100644 --- a/dapper/mods/Lorenz84/__init__.py +++ b/dapper/mods/Lorenz84/__init__.py @@ -1,6 +1,6 @@ """A chaotic system of size 3, like Lorenz-63, but with +complex geometry. -Refs: `bib.lorenz1984irregularity`, `bib.lorenz2005look` +Refs: [lorenz1984irregularity][], [lorenz2005look][] """ import numpy as np diff --git a/dapper/mods/Lorenz84/harder.py b/dapper/mods/Lorenz84/harder.py index db174cc2..3d59ef3d 100644 --- a/dapper/mods/Lorenz84/harder.py +++ b/dapper/mods/Lorenz84/harder.py @@ -1,4 +1,4 @@ -"""Harder settings than in `dapper.mods.Lorenz84.pajonk2012`. +"""Harder settings than in [`mods.Lorenz84.pajonk2012`][]. This was adjudged by noting that with their settings, the average val. of `trHK` is 0.013. diff --git a/dapper/mods/Lorenz84/pajonk2012.py b/dapper/mods/Lorenz84/pajonk2012.py index 35121dca..7b03b826 100644 --- a/dapper/mods/Lorenz84/pajonk2012.py +++ b/dapper/mods/Lorenz84/pajonk2012.py @@ -1,4 +1,4 @@ -"""Settings from `bib.pajonk2012deterministic`. +"""Settings from [pajonk2012deterministic][]. There is nothing to reproduce from the paper as there are no statistically converged numbers. diff --git a/dapper/mods/Lorenz96/__init__.py b/dapper/mods/Lorenz96/__init__.py index d0af3373..d80ef9d8 100644 --- a/dapper/mods/Lorenz96/__init__.py +++ b/dapper/mods/Lorenz96/__init__.py @@ -1,6 +1,6 @@ """A 1D emulator of chaotic atmospheric behaviour. -`bib.lorenz1996predictability` +[lorenz1996predictability][] For a short introduction, see diff --git a/dapper/mods/Lorenz96/anderson2009.py b/dapper/mods/Lorenz96/anderson2009.py index e5bef2e7..8d8ea7e0 100644 --- a/dapper/mods/Lorenz96/anderson2009.py +++ b/dapper/mods/Lorenz96/anderson2009.py @@ -1,4 +1,4 @@ -"""A land-ocean setup from `bib.anderson2009spatially`.""" +"""A land-ocean setup from [anderson2009spatially][].""" import numpy as np diff --git a/dapper/mods/Lorenz96/bocquet2010.py b/dapper/mods/Lorenz96/bocquet2010.py index 70b5160d..d942e92b 100644 --- a/dapper/mods/Lorenz96/bocquet2010.py +++ b/dapper/mods/Lorenz96/bocquet2010.py @@ -1,4 +1,4 @@ -"""From Fig. 1 of `bib.bocquet2010beyond`.""" +"""From Fig. 1 of [bocquet2010beyond][].""" import numpy as np diff --git a/dapper/mods/Lorenz96/bocquet2010_m40.py b/dapper/mods/Lorenz96/bocquet2010_m40.py index 9727bb00..9909be9b 100644 --- a/dapper/mods/Lorenz96/bocquet2010_m40.py +++ b/dapper/mods/Lorenz96/bocquet2010_m40.py @@ -1,4 +1,4 @@ -"""From `bib.bocquet2010beyond` (again), but `ndim=40` (i.e. Fig. 5 of paper).""" +"""From [bocquet2010beyond][] (again), but `ndim=40` (i.e. Fig. 5 of paper).""" import numpy as np diff --git a/dapper/mods/Lorenz96/bocquet2015loc.py b/dapper/mods/Lorenz96/bocquet2015loc.py index fed3abd6..866d773d 100644 --- a/dapper/mods/Lorenz96/bocquet2015loc.py +++ b/dapper/mods/Lorenz96/bocquet2015loc.py @@ -1,4 +1,4 @@ -"""Settings as in `bib.bocquet2016localization`.""" +"""Settings as in [bocquet2016localization][].""" import numpy as np diff --git a/dapper/mods/Lorenz96/frei2013bridging.py b/dapper/mods/Lorenz96/frei2013bridging.py index ddc5d1a4..f4c374f6 100644 --- a/dapper/mods/Lorenz96/frei2013bridging.py +++ b/dapper/mods/Lorenz96/frei2013bridging.py @@ -1,8 +1,8 @@ -"""Settings as in `bib.frei2013bridging`. +"""Settings as in [frei2013bridging][]. They also cite its use in the following: -`bib.bengtsson2003toward`, `bib.lei2011moment`, `bib.frei2013mixture`. +[bengtsson2003toward][], [lei2011moment][], [frei2013mixture][]. """ import numpy as np diff --git a/dapper/mods/Lorenz96/hoteit2015.py b/dapper/mods/Lorenz96/hoteit2015.py index 4fa0ac54..152b5084 100644 --- a/dapper/mods/Lorenz96/hoteit2015.py +++ b/dapper/mods/Lorenz96/hoteit2015.py @@ -1,4 +1,4 @@ -"""Reproduce results from `bib.hoteit2015mitigating`.""" +"""Reproduce results from [hoteit2015mitigating][].""" from dapper.mods.Lorenz96.sakov2008 import HMM as _HMM diff --git a/dapper/mods/Lorenz96/miyoshi2011.py b/dapper/mods/Lorenz96/miyoshi2011.py index 2c6763b6..ce41a7c6 100644 --- a/dapper/mods/Lorenz96/miyoshi2011.py +++ b/dapper/mods/Lorenz96/miyoshi2011.py @@ -1,6 +1,6 @@ """A land-ocean setup. -Refs: `bib.miyoshi2011gaussian`, which was inspired by `bib.lorenz1998optimal`. +Refs: [miyoshi2011gaussian][], which was inspired by [lorenz1998optimal][]. """ import numpy as np diff --git a/dapper/mods/Lorenz96/pinheiro2019.py b/dapper/mods/Lorenz96/pinheiro2019.py index 55074c4b..c8c924d0 100644 --- a/dapper/mods/Lorenz96/pinheiro2019.py +++ b/dapper/mods/Lorenz96/pinheiro2019.py @@ -1,4 +1,4 @@ -"""Settings from `bib.pinheiro2019efficient`.""" +"""Settings from [pinheiro2019efficient][].""" import dapper.mods as modelling import dapper.mods.Lorenz96 as model diff --git a/dapper/mods/Lorenz96/raanes2016.py b/dapper/mods/Lorenz96/raanes2016.py index 427bcb06..95b185db 100644 --- a/dapper/mods/Lorenz96/raanes2016.py +++ b/dapper/mods/Lorenz96/raanes2016.py @@ -1,4 +1,4 @@ -"""Reproduce `bib.raanes2015rts`.""" +"""Reproduce [raanes2015rts][].""" import dapper.mods as modelling from dapper.mods.Lorenz96.sakov2008 import HMM as _HMM diff --git a/dapper/mods/Lorenz96/sakov2008.py b/dapper/mods/Lorenz96/sakov2008.py index f28f6373..58f3c795 100644 --- a/dapper/mods/Lorenz96/sakov2008.py +++ b/dapper/mods/Lorenz96/sakov2008.py @@ -1,9 +1,9 @@ -"""Settings as in `bib.sakov2008deterministic`. +"""Settings as in [sakov2008deterministic][]. This HMM is used (with small variations) in many DA papers, for example -`bib.bocquet2011ensemble`, `bib.sakov2012iterative`, -`bib.bocquet2015expanding`, `bib.bocquet2013joint`. +[bocquet2011ensemble][], [sakov2012iterative][], +[bocquet2015expanding][], [bocquet2013joint][]. """ import numpy as np diff --git a/dapper/mods/Lorenz96/todter2015.py b/dapper/mods/Lorenz96/todter2015.py index e3bc35f5..522069fa 100644 --- a/dapper/mods/Lorenz96/todter2015.py +++ b/dapper/mods/Lorenz96/todter2015.py @@ -1,4 +1,4 @@ -"""Concerns figure 4 of `bib.todter2015second`.""" +"""Concerns figure 4 of [todter2015second][].""" import numpy as np diff --git a/dapper/mods/Lorenz96/todter2015_G.py b/dapper/mods/Lorenz96/todter2015_G.py index 6186be65..e35bae8b 100644 --- a/dapper/mods/Lorenz96/todter2015_G.py +++ b/dapper/mods/Lorenz96/todter2015_G.py @@ -1,4 +1,4 @@ -"""From `dapper.mods.Lorenz96.todter2015` again, but with Gaussian likelihood.""" +"""From [`mods.Lorenz96.todter2015`][] again, but with Gaussian likelihood.""" import dapper.mods as modelling from dapper.mods.Lorenz96.todter2015 import HMM as _HMM diff --git a/dapper/mods/Lorenz96s/__init__.py b/dapper/mods/Lorenz96s/__init__.py index 89d81db3..5351b45c 100644 --- a/dapper/mods/Lorenz96s/__init__.py +++ b/dapper/mods/Lorenz96s/__init__.py @@ -1,6 +1,6 @@ """A perfect-random version of Lorenz-96. -Used by `bib.grudzien2020numerical` to study the precision of +Used by [grudzien2020numerical][] to study the precision of stochastic integration schemes. Both the model and truth are to be integrated by the same *random* model (with almost @@ -11,7 +11,7 @@ The truth twin should be generated by the order 2.0 Taylor scheme below, for the accuracy with respect to convergence in the strong sense. -See `bib.grudzien2020numerical` for a full discussion of benchmarks on this +See [grudzien2020numerical][] for a full discussion of benchmarks on this model and statistically robust configurations. This study uses no multiplicative inflation / localization or other @@ -19,7 +19,7 @@ observation EnKF as a simple estimator to study the asymptotic filtering statistics under different model scenarios. -The purpose of the study in `bib.grudzien2020numerical` was to explore the relationships +The purpose of the study in [grudzien2020numerical][] was to explore the relationships between: - numerical discretization error in truth twins; @@ -38,7 +38,7 @@ thus corresponds to a wider variance of the relizations of the diffeomorphsims that generate the model / truth twin between observation times. -It is demonstrated by `bib.grudzien2020numerical` that the model error due to +It is demonstrated by [grudzien2020numerical][] that the model error due to discretization of the SDE equations of motion is most detrimental to the filtering cycle when model uncertainty is low and observation precision is high. In other configurations, such as those with high model uncertainty, the differences between @@ -50,7 +50,7 @@ respect to convergence in the strong sense for generating the observation sequence. The model simulation step sizes are varied in the settings below to demonstrate the differences between the commonly uses Euler-Maruyama and the more statistically -robust Runge-Kutta method for SDE integration. See README in `dapper.mods.Lorenz96s`. +robust Runge-Kutta method for SDE integration. See README in [`mods.Lorenz96s`][]. """ import numpy as np @@ -93,7 +93,7 @@ def l96s_tay2_step(x, t, dt, s): model twin will be generated by on of the wrappers below. The order 2.0 Taylor-Stratonovich discretization scheme implemented here is the basic formulation which makes a Fourier truncation at p=1 for the Brownian bridge process. See - `bib.grudzien2020numerical` for full details of the scheme and other versions.""" + [grudzien2020numerical][] for full details of the scheme and other versions.""" # Infer system dimension sys_dim = len(x) diff --git a/dapper/mods/Lorenz96s/grudzien2020.py b/dapper/mods/Lorenz96s/grudzien2020.py index 50da5464..d6dcd2bc 100644 --- a/dapper/mods/Lorenz96s/grudzien2020.py +++ b/dapper/mods/Lorenz96s/grudzien2020.py @@ -1,4 +1,4 @@ -"""Settings as in `bib.grudzien2020numerical`.""" +"""Settings as in [grudzien2020numerical][].""" import dapper.mods as modelling from dapper.mods.Lorenz96 import Tplot, x0 diff --git a/dapper/mods/LorenzUV/__init__.py b/dapper/mods/LorenzUV/__init__.py index c2aba4f6..30f54245 100644 --- a/dapper/mods/LorenzUV/__init__.py +++ b/dapper/mods/LorenzUV/__init__.py @@ -1,6 +1,6 @@ """The 2-scale/layer/speed coupled version of Lorenz-96. -See `bib.wilks2005effects` +See [wilks2005effects][] - U: large amp, low frequency vars: convective events - V: small amp, high frequency vars: large-scale synoptic events @@ -45,7 +45,7 @@ def newfun(x, *args, reverse=False, **kwargs): class model_instance: """Use OOP to facilitate having multiple parameter settings simultaneously. - Default parameters from `bib.wilks2005effects`. + Default parameters from [wilks2005effects][]. """ def __init__(self, nU=8, J=32, F=20, h=1, b=10, c=10): diff --git a/dapper/mods/LorenzUV/illust_LorenzUV.py b/dapper/mods/LorenzUV/illust_LorenzUV.py index 8080ec1f..c859163c 100644 --- a/dapper/mods/LorenzUV/illust_LorenzUV.py +++ b/dapper/mods/LorenzUV/illust_LorenzUV.py @@ -140,7 +140,10 @@ def tV(zz): if False: fig.savefig( - "docs/imgs/logo_wtxt.png", bbox_inches="tight", pad_inches=0, dpi=200 + "docs/images/logos/logo_wtxt.png", + bbox_inches="tight", + pad_inches=0, + dpi=200, ) plt.show() diff --git a/dapper/mods/LorenzUV/lorenz96.py b/dapper/mods/LorenzUV/lorenz96.py index aa119ea2..6b75a315 100644 --- a/dapper/mods/LorenzUV/lorenz96.py +++ b/dapper/mods/LorenzUV/lorenz96.py @@ -1,4 +1,4 @@ -"""As in `bib.lorenz1996predictability`.""" +"""As in [lorenz1996predictability][].""" from pathlib import Path diff --git a/dapper/mods/LorenzUV/wilks05.py b/dapper/mods/LorenzUV/wilks05.py index 7ef7478a..43a671c3 100644 --- a/dapper/mods/LorenzUV/wilks05.py +++ b/dapper/mods/LorenzUV/wilks05.py @@ -1,4 +1,4 @@ -"""Uses `nU`, `J`, `F` as in `dapper.mods.LorenzUV` ie. from `bib.wilks2005effects`. +"""Uses `nU`, `J`, `F` as in [`mods.LorenzUV`][] ie. from [wilks2005effects][]. Obs settings taken from different places (=> quasi-linear regime). """ diff --git a/dapper/mods/LotkaVolterra/__init__.py b/dapper/mods/LotkaVolterra/__init__.py index fe83f1d5..42f61a82 100644 --- a/dapper/mods/LotkaVolterra/__init__.py +++ b/dapper/mods/LotkaVolterra/__init__.py @@ -2,7 +2,7 @@ Refs: [Wiki](https://en.wikipedia.org/wiki/Competitive_Lotka-Volterra_equations), -`bib.vano2006chaos`. +[vano2006chaos][]. """ import numpy as np diff --git a/dapper/mods/QG/__init__.py b/dapper/mods/QG/__init__.py index ee6dc5e7..50c73c09 100644 --- a/dapper/mods/QG/__init__.py +++ b/dapper/mods/QG/__init__.py @@ -1,4 +1,4 @@ -"""Quasi-geostraphic 2D flow. Described in detail by `bib.sakov2008deterministic`. +"""Quasi-geostraphic 2D flow. Described in detail by [sakov2008deterministic][]. Adapted from Pavel Sakov's enkf-matlab package. diff --git a/dapper/mods/QG/counillon2009.py b/dapper/mods/QG/counillon2009.py index 1d6f6436..aac81145 100644 --- a/dapper/mods/QG/counillon2009.py +++ b/dapper/mods/QG/counillon2009.py @@ -1,4 +1,4 @@ -"""Reproduce experiments from `bib.counillon2009application`.""" +"""Reproduce experiments from [counillon2009application][].""" import dapper.mods as modelling from dapper.mods.QG import model_config diff --git a/dapper/mods/QG/sakov2008.py b/dapper/mods/QG/sakov2008.py index 604575f1..fc45f5e0 100644 --- a/dapper/mods/QG/sakov2008.py +++ b/dapper/mods/QG/sakov2008.py @@ -1,4 +1,4 @@ -"""Reproduce results from `bib.sakov2008deterministic`.""" +"""Reproduce results from [sakov2008deterministic][].""" import numpy as np diff --git a/dapper/mods/README.md b/dapper/mods/README.md index 63b800ac..18cc4422 100644 --- a/dapper/mods/README.md +++ b/dapper/mods/README.md @@ -1,40 +1,37 @@ See the README section on -[test cases (models)](https://github.com/nansencenter/DAPPER#Test-cases-models) +[test cases (models)](../../index.md#test-cases-models) for a table overview of the included models. ## Defining your own model Below is a sugested structuring followed by most models already within DAPPER. However, you are free to organize your model as you see fit, -as long as it culminates in the definition of one or more `dapper.mods.HiddenMarkovModel`. +as long as it culminates in the definition of one or more [`mods.HiddenMarkovModel`][]. For the sake of modularity, -try not to import stuff from DAPPER outside of `dapper.mods` and `liveplotting`. +try not to import stuff from DAPPER outside of [`mods`](.) and [`tools.liveplotting`][]. - Make a directory: `my_model`. It does not have to reside within the `dapper/mods` folder, but make sure to look into some of the other dirs thereunder as examples, for example `dapper/mods/DoublePendulum`. - - Make a file: `my_model/__init__.py` to hold the core workings of the model. Further details are given [below](#details-on-my_model__init__py), but the main work lies in defining a `step(x, t, dt)` function (you can name it however you like, but `step` is the convention), to implement the dynamical model/system mapping the state `x` from one time `t` to another `t + dt`. - - Make a file: `my_model/demo.py` to run `step` and visually showcase a simulation of the model without any DA, and verify it's working. - - Make a file: `my_model/my_settings_1.py` that defines - (or "configures", since there is usually little programming logic and flow taking place) - a complete `dapper.mods.HiddenMarkovModel` ready for a synthetic experiment - (also called "twin experiment" or OSSE). -- Once you've made some experiments you believe are noteworthy you should add a - "suggested settings/tunings" section in comments at the bottom of - `my_model/my_settings_1.py`, listing some of the relevant DA method - configurations that you tested, along with the RMSE (or other stats) that - you obtained for those methods. You will find plenty of examples already in - DAPPER, used for cross-referenced with literature to verify the workings of DAPPER - (and the reproducibility of publications). + (or "configures", since there is usually little programming logic and flow taking place) + a complete [`mods.HiddenMarkovModel`][] ready for a synthetic experiment + (also called "twin experiment" or OSSE). +- Once you've made some experiments you believe are noteworthy you should add a + "suggested settings/tunings" section in comments at the bottom of + `my_model/my_settings_1.py`, listing some of the relevant DA method + configurations that you tested, along with the RMSE (or other stats) that + you obtained for those methods. You will find plenty of examples already in + DAPPER, used for cross-referenced with literature to verify the workings of DAPPER + (and the reproducibility of publications). ### Details on `my_model/__init__.py` @@ -44,19 +41,19 @@ try not to import stuff from DAPPER outside of `dapper.mods` and `liveplotting`. number of dimensions (as the input). See - - `dapper.mods.Lorenz63`: use of `ens_compatible`. - - `dapper.mods.Lorenz96`: use of relatively clever slice notation. - - `dapper.mods.LorenzUV`: use of cleverer slice notation: `...` (ellipsis). + - [`mods.Lorenz63`][]: use of `ens_compatible`. + - [`mods.Lorenz96`][]: use of relatively clever slice notation. + - [`mods.LorenzUV`][]: use of cleverer slice notation: `...` (ellipsis). Consider pre-defining the slices like so: + ```python + iiX = (..., slice(None, Nx)) + iiP = (..., slice(Nx, None)) + ``` + to abbreviate the indexing elsewhere. - iiX = (..., slice(None, Nx)) - iiP = (..., slice(Nx, None)) - - to abbreviate the indexing elsewhere. - - - `dapper.mods.QG`: use of parallelized for loop (map). + - [`mods.QG`][]: use of parallelized for loop (map). - .. note:: + !!! note To begin with, test whether the model works on 1 realization, before running it with several (simultaneously). Also, start with a small integration time step, @@ -64,16 +61,16 @@ try not to import stuff from DAPPER outside of `dapper.mods` and `liveplotting`. Note that the time step might need to be shorter in assimilation, because it may cause instabilities. - .. note:: + !!! note Most models are defined using simple procedural style. - However, `dapper.mods.LorenzUV` and `dapper.mods.QG` use OOP, + However, [`mods.LorenzUV`][] and [`mods.QG`][] use OOP, which is perhaps more robust when different control-variable settings are to be investigated. The choice is yours. In parameter estimation problems, the parameters are treated as input variables to the "forward model". This does not *necessarily* require OOP. - See `examples/param_estim.py`. + See `docs/examples/param_estim.py`. - Optional: define a suggested/example initial state, `x0`. This facilitates the specification of initial conditions for different synthetic diff --git a/dapper/mods/VL20/__init__.py b/dapper/mods/VL20/__init__.py index 138093ab..cdbe2544 100644 --- a/dapper/mods/VL20/__init__.py +++ b/dapper/mods/VL20/__init__.py @@ -1,7 +1,7 @@ """ Single-scale Lorenz-96 with an added thermodynamic component. -Refs: `bib.vissio2020mechanics` +Refs: [vissio2020mechanics][] """ import numpy as np diff --git a/dapper/mods/VL20/demo.py b/dapper/mods/VL20/demo.py index 9885d30c..55ec3d28 100644 --- a/dapper/mods/VL20/demo.py +++ b/dapper/mods/VL20/demo.py @@ -1,6 +1,6 @@ """Demonstrate the Vissio-Lucarini-20 model. -Reproduce Hovmoller diagram Fig 4. in `bib.vissio2020mechanics`. +Reproduce Hovmoller diagram Fig 4. in [vissio2020mechanics][]. """ import numpy as np diff --git a/dapper/mods/__init__.py b/dapper/mods/__init__.py index 723f7a5d..de651d86 100644 --- a/dapper/mods/__init__.py +++ b/dapper/mods/__init__.py @@ -1,6 +1,6 @@ """Contains models included with DAPPER. -.. include:: ./README.md +--8<-- "dapper/mods/README.md" """ import copy as cp @@ -30,55 +30,66 @@ class HiddenMarkovModel(struct_tools.NicePrint): Should contain the details necessary to run synthetic DA experiments, also known as "twin experiment", or OSSE (observing system simulation experiment). The synthetic truth and observations may then be obtained by running - `HiddenMarkovModel.simulate`. - - .. note:: - Each model included with DAPPER comes with several examples - of model settings from the literature. - See, for example, `dapper.mods.Lorenz63.sakov2012`. - - .. warning:: - These example configurations do not necessarily hold a high programming standard, - as they may have been whipped up at short notice to replicate some experiments, - and are not intended for re-use. - Nevertheless, sometimes they are re-used by another configuration script, - leading to a major gotcha/pitfall: changes made to the imported `HMM` (or - the model's module itself) also impact the original object (since they - are mutable and thereby referenced). This *usually* isn't an issue, since - one rarely imports two/more separate configurations. However, the test suite - imports all configurations, which might then unintentionally interact. - To avoid this, you should use the `copy` method of the `HMM` - before making any changes to it. - - - Parameters - ---------- - Dyn: `Operator` or dict - Operator for the dynamics. - Obs: `Operator` or dict - Operator for the observations - Can also be time-dependent, ref `TimeDependentOperator`. - tseq: `dapper.tools.chronos.Chronology` - Time sequence of the HMM process. - X0: `dapper.tools.randvars.RV` - Random distribution of initial condition - liveplotters: `list`, optional - A list of tuples. See example use in function `LPs` of `dapper.mods.Lorenz63`. - - The first element of the tuple determines if the liveplotter - is shown by default. If `False`, the liveplotter is only shown when - included among the `liveplots` argument of `assimilate` - - The second element in the tuple gives the corresponding liveplotter - function/class. - sectors: `dict`, optional - Labelled indices referring to parts of the state vector. - When defined, field-mean statistics are computed for each sector. - Example use can be found in `examples/param_estim.py` - and `dapper/mods/Lorenz96/miyoshi2011.py` - name: str, optional - Label for the `HMM`. + [`.simulate`][mods.HiddenMarkovModel.simulate]. + + !!! note + Each model included with DAPPER comes with several examples + of model settings from the literature. + See, for example, [`mods.Lorenz63.sakov2012`][]. + + !!! warning + These example configs do not necessarily hold a high programming standard, + as they may have been whipped up at short notice to replicate some experiments, + and are not intended for re-use. + Nevertheless, sometimes they are re-used by another configuration script, + leading to a major gotcha/pitfall: changes made to the imported `HMM` (or + the model's module itself) also impact the original object (since they + are mutable and thereby referenced). This *usually* isn't an issue, since + one rarely imports two/more separate configurations. However, the test suite + imports all configurations, which might then unintentionally interact. + To avoid this, you should use the `copy` method of the `HMM` + before making any changes to it. """ - def __init__(self, *args, **kwargs): + def __init__( + self, + Dyn, + Obs, + tseq, + X0, + liveplotters=None, + sectors=None, + name=None, + **kwargs, + ): + """Initialize. + + Parameters + ---------- + Dyn : Operator or dict + Operator for the dynamics. + Obs : Operator or TimeDependentOperator or dict + Operator for the observations + Can also be time-dependent, ref `TimeDependentOperator`. + tseq : tools.chronos.Chronology + Time sequence of the HMM process. + X0 : tools.randvars.RV + Random distribution of initial condition + liveplotters : list, optional + A list of tuples. See example use in function `LPs` of [`mods.Lorenz63`][]. + - The first element of the tuple determines if the liveplotter + is shown by default. If `False`, the liveplotter is only shown when + included among the `liveplots` argument of `assimilate` + - The second element in the tuple gives the corresponding liveplotter + function/class. + sectors : dict, optional + Labelled indices referring to parts of the state vector. + When defined, field-mean statistics are computed for each sector. + Example use can be found in `docs/examples/param_estim.py` + and `dapper/mods/Lorenz96/miyoshi2011.py` + name : str, optional + Label for the `HMM`. + """ # Expected args/kwargs, along with type and default. attrs = dict( Dyn=(Operator, None), @@ -90,11 +101,6 @@ def __init__(self, *args, **kwargs): name=(str, self._default_name()), ) - # Transfer args to kwargs - for arg, kw in zip(args, attrs): - assert kw not in kwargs, "Could not sort out arguments." - kwargs[kw] = arg - # Un-abbreviate abbrevs = {"LP": "liveplotters", "loc": "localizer"} for key in list(kwargs): @@ -106,9 +112,10 @@ def __init__(self, *args, **kwargs): assert full not in kwargs, "Could not sort out arguments." kwargs[full] = kwargs.pop(key) - # Convert dict to object + # Collect args, kwargs. for key, (type_, default) in attrs.items(): - val = kwargs.get(key, default) + val = locals()[key] or kwargs.get(key, default) + # Convert dict to object if not isinstance(val, type_) and val is not None: val = type_(**val) kwargs[key] = val @@ -177,7 +184,8 @@ class TimeDependentOperator: The time instance should be specified by `ko`, i.e. the index of an observation time. - Examples: `examples/time-dep-obs-operator.py` and `dapper/mods/QG/sakov2008.py`. + Examples: `docs/examples/time-dep-obs-operator.py` + and `dapper/mods/QG/sakov2008.py`. """ def __init__(self, **kwargs): @@ -216,14 +224,16 @@ class Operator(struct_tools.NicePrint): Parameters ---------- - M: int + M : int Length of output vectors. - model: function + model : function The actual operator. - noise: RV, optional + noise : RV, optional The associated additive noise. The noise can also be a scalar or an array, producing `GaussRV(C=noise)`. + Note + ---- Any remaining keyword arguments are written to the object as attributes. """ diff --git a/dapper/mods/integration.py b/dapper/mods/integration.py index 46d4da87..f4843834 100644 --- a/dapper/mods/integration.py +++ b/dapper/mods/integration.py @@ -19,7 +19,7 @@ def rk4(f, x, t, dt, stages=4, s=0): For SDEs with additive noise (`s>0`), the order of convergence (both weak and strong) is 1 for `stages` equal to 1 or 4. These correspond to the classic Euler-Maruyama scheme and the Runge-Kutta - scheme for S-ODEs respectively, see `bib.grudzien2020numerical` + scheme for S-ODEs respectively, see [grudzien2020numerical][] for a DA-specific discussion on integration schemes and their discretization errors. Parameters @@ -98,7 +98,7 @@ def with_recursion(func, prog=False): Return a version of `func` whose 2nd argument (`k`) specifies the number of times to times apply func on its output. - .. warning:: Only the first argument to `func` will change, + !!! warning Only the first argument to `func` will change, so, for example, if `func` is `step(x, t, dt)`, it will get fed the same `t` and `dt` at each iteration. @@ -115,6 +115,7 @@ def with_recursion(func, prog=False): fun_k : function A function that returns the sequence generated by recursively running `func`, i.e. the trajectory of system's evolution. + Examples -------- >>> def dxdt(x): @@ -154,10 +155,13 @@ def integrate_TLM(TLM, dt, method='approx'): - the Jacobian of the step func. - the integral of (with *M* as the TLM): - $$ \frac{d U}{d t} = M U, \quad U_0 = I .$$ - .. note:: the tangent linear model (TLM) - is assumed constant (for each `method` below). + $$ + \frac{d U}{d t} = M U, \quad U_0 = I . + $$ + + !!! tip "The tangent linear model (TLM)" + is assumed constant (for each `method` below). Parameters ---------- @@ -166,7 +170,9 @@ def integrate_TLM(TLM, dt, method='approx'): - `'approx'` : derived from the forward-euler scheme. - `'rk4'` : higher-precision approx. - `'analytic'`: exact. - .. warning:: 'analytic' typically requries higher inflation in the ExtKF. + + !!! warning + "'analytic' typically requries higher inflation in the ExtKF." See Also -------- diff --git a/dapper/mods/utils.py b/dapper/mods/utils.py index f5beb1d5..1aeb223c 100644 --- a/dapper/mods/utils.py +++ b/dapper/mods/utils.py @@ -42,12 +42,12 @@ def ens_compatible(func): This is helpful to make functions compatible with both 1d and 2d ndarrays. - .. note:: This is not `the_way™` -- other tricks (ref `dapper.mods`) - are sometimes more practical. + !!! tip "This is not `the_way™`" + Other tricks (ref [`mods`][]) are sometimes more practical. Examples -------- - `dapper.mods.Lorenz63.dxdt`, `dapper.mods.DoublePendulum.dxdt` + [`mods.Lorenz63.dxdt`][], [`mods.DoublePendulum.dxdt`][] See Also -------- @@ -70,15 +70,16 @@ def linear_model_setup(ModelMatrix, dt0): r"""Make the Dyn/Obs field of a HMM representing a linear model. Let *M* be the model matrix. Then - .. math:: - x(t+dt) = M^{dt/dt0} x(t), + $$ + x(t+dt) = M^{dt/dt0} x(t), + $$ i.e. - .. math:: - - \frac{dx}{dt} = \frac{\log(M)}{dt0} x(t). + $$ + \frac{dx}{dt} = \frac{\log(M)}{dt0} x(t). + $$ In typical use, `dt0==dt` (where `dt` is defined by the chronology). Anyways, `dt` must be an integer multiple of `dt0`. @@ -115,14 +116,14 @@ def direct_obs_matrix(Nx, obs_inds): Parameters ---------- - Nx: int + Nx : int Length of state vector - obs_inds: ndarray + obs_inds : ndarray Indices of elements of the state vector that are (directly) observed. Returns ------- - H: ndarray + H : ndarray The observation matrix for direct partial observations. """ Ny = len(obs_inds) @@ -142,14 +143,14 @@ def partial_Id_Obs(Nx, obs_inds): Parameters ---------- - Nx: int + Nx : int Length of state vector - obs_inds: ndarray + obs_inds : ndarray The observed indices. Returns ------- - Obs: dict + Obs : dict Observation operator including size of the observation space, observation operator/model and tangent linear observation operator """ @@ -180,12 +181,12 @@ def Id_Obs(Nx): Parameters ---------- - Nx: int + Nx : int Length of state vector Returns ------- - Obs: dict + Obs : dict Observation operator including size of the observation space, observation operator/model and tangent linear observation operator """ @@ -197,18 +198,18 @@ def linspace_int(Nx, Ny, periodic=True): Parameters ---------- - Nx: int + Nx : int Range of integers - Ny: int + Ny : int Number of integers - periodic: bool, optional + periodic : bool, optional Whether the vector is periodic. Determines if `Nx == 0`. Default: True Returns ------- - integers: ndarray + integers : ndarray The list of integers. Examples diff --git a/dapper/stats.py b/dapper/stats.py index 2e81e623..cf9cdebc 100644 --- a/dapper/stats.py +++ b/dapper/stats.py @@ -1,15 +1,15 @@ """Statistics for the assessment of DA methods. -`Stats` is a data container for ([mostly] time series of) statistics. +[`Stats`][stats.Stats] is a data container for ([mostly] time series of) statistics. It comes with a battery of methods to compute the default statistics. -`Avrgs` is a data container *for the same statistics*, +[`Avrgs`][stats.Avrgs] is a data container *for the same statistics*, but after they have been averaged in time (after the assimilation has finished). -Instances of these objects are created by `dapper.da_methods.da_method` +Instances of these objects are created by [`da_methods.da_method`][] (i.e. "`xp`") objects and written to their `.stats` and `.avrgs` attributes. -.. include:: ../docs/stats_etc.md +--8<-- "dapper/stats_etc.md" """ import warnings @@ -127,7 +127,8 @@ def new_series(self, name, shape, length="FAUSt", field_mean=False, **kws): """Create (and register) a statistics time series, initialized with `nan`s. If `length` is an integer, a `DataSeries` (a trivial subclass of - `numpy.ndarray`) is made. By default, though, a `series.FAUSt` is created. + `numpy.ndarray`) is made. By default, though, a [tools.series.FAUSt][] + is created. NB: The `sliding_diagnostics` liveplotting relies on detecting `nan`'s to avoid plotting stats that are not being used. @@ -173,7 +174,8 @@ def data_series(self): ] def assess(self, k, ko=None, faus=None, E=None, w=None, mu=None, Cov=None): - """Common interface for both `Stats.assess_ens` and `Stats.assess_ext`. + """Common interface for both [`Stats`.assess_ens][stats.Stats.assess_ens] + and [`Stats`.assess_ext][stats.Stats.assess_ext]. The `_ens` assessment function gets called if `E is not None`, and `_ext` if `mu is not None`. @@ -438,11 +440,12 @@ def replay(self, figlist="default", speed=np.inf, t1=0, t2=None, **kwargs): - t1, t2: time window to plot. - 'figlist' and 'speed': See LivePlot's doc. - .. note:: `store_u` (whether to store non-obs-time stats) must - have been `True` to have smooth graphs as in the actual LivePlot. + !!! note + `store_u` (whether to store non-obs-time stats) must + have been `True` to have smooth graphs as in the actual LivePlot. - .. note:: Ensembles are generally not stored in the stats - and so cannot be replayed. + !!! note + Ensembles are generally not stored in the stats and so cannot be replayed. """ # Time settings tseq = self.HMM.tseq @@ -499,12 +502,13 @@ class Avrgs(series.StatPrint, struct_tools.DotDict): Embellishments: - - `dapper.tools.StatPrint` - - `Avrgs.tabulate` + - [`tools.series.StatPrint`][] + - [`Avrgs.tabulate`][stats.Avrgs.tabulate] - `getattr` that supports abbreviations. """ def tabulate(self, statkeys=(), decimals=None): + """Tabulate using [`tabulate_avrgs`][stats.tabulate_avrgs]""" columns = tabulate_avrgs([self], statkeys, decimals=decimals) return tabulate(columns, headers="keys").replace("␣", " ") @@ -551,7 +555,8 @@ def align_col(col, pad="␣", missingval="", just=">"): Treats `int`s and fixed-point `float`/`str` especially, aligning on the point. - Example: + Examples + -------- >>> xx = [1, 1., 1.234, 12.34, 123.4, "1.2e-3", None, np.nan, "inf", (1, 2)] >>> print(*align_col(xx), sep="\n") ␣␣1␣␣␣␣ @@ -623,14 +628,14 @@ def unpack_uqs(uq_list, decimals=None): and may get formatted somehow (e.g. cast to strings) in the output. If `uq` is `None`, then `None` is inserted in each list. - Else, `uq` must be an instance of `dapper.tools.rounding.UncertainQtty`. + Else, `uq` must be an instance of [`tools.rounding.UncertainQtty`][]. Parameters ---------- - uq_list: list + uq_list : list List of `uq`s. - decimals: int + decimals : int Desired number of decimals. Used for (only) the columns "val" and "prec". Default: `None`. In this case, the formatting is left to the `uq`s. @@ -703,23 +708,23 @@ def center(E, axis=0, rescale=False): Parameters ---------- - E: ndarray + E : ndarray Ensemble which going to be inflated - axis: int, optional + axis : int, optional The axis to be centered. Default: 0 - rescale: bool, optional + rescale : bool, optional If True, inflate to compensate for reduction in the expected variance. The inflation factor is \(\sqrt{\frac{N}{N - 1}}\) where N is the ensemble size. Default: False Returns ------- - X: ndarray + X : ndarray Ensemble anomaly - x: ndarray + x : ndarray Mean of the ensemble """ x = np.mean(E, axis=axis, keepdims=True) @@ -735,7 +740,7 @@ def center(E, axis=0, rescale=False): def mean0(E, axis=0, rescale=True): - """Like `center`, but only return the anomalies (not the mean). + """Like [`center`][stats.center], but only return the anomalies (not the mean). Uses `rescale=True` by default, which is beneficial when used to center observation perturbations. @@ -751,7 +756,7 @@ def inflate_ens(E, factor): E : ndarray Ensemble which going to be inflated - factor: `float` + factor : `float` Inflation factor Returns @@ -773,10 +778,10 @@ def weight_degeneracy(w, prec=1e-10): Parameters ---------- - w: ndarray + w : ndarray Importance weights. Must sum to 1. - prec: float, optional + prec : float, optional Tolerance of the distance between w and one. Default:1e-10 Returns @@ -792,21 +797,21 @@ def unbias_var(w=None, N_eff=None, avoid_pathological=False): Parameters ---------- - w: ndarray, optional + w : ndarray, optional Importance weights. Must sum to 1. Only one of `w` and `N_eff` can be `None`. Default: `None` - N_eff: float, optional + N_eff : float, optional The "effective" size of the weighted ensemble. If not provided, it is computed from the weights. The unbiasing factor is $$ N_{eff} / (N_{eff} - 1) $$. - avoid_pathological: bool, optional + avoid_pathological : bool, optional Avoid weight collapse. Default: `False` Returns ------- - ub: float + ub : float factor used to unbiasing variance Reference diff --git a/docs/stats_etc.md b/dapper/stats_etc.md similarity index 58% rename from docs/stats_etc.md rename to dapper/stats_etc.md index fd870ca9..8c22a5dd 100644 --- a/docs/stats_etc.md +++ b/dapper/stats_etc.md @@ -2,15 +2,17 @@ List them using ->>> list(vars(xp.stats)) -... list(vars(xp.avrgs)) +```python +list(vars(xp.stats)) +list(vars(xp.avrgs)) +``` ### The `FAUSt` key/attribute The time series of statistics (the attributes of `.stats`) may have attributes `.f`, `.a`, `.s`, `.u`, referring to whether the statistic is for a "forecast", "analysis", or "smoothing" estimate (as is decided when the calls to -`Stats.assess` is made), or a "universal" (forecast, but at intermediate +[Stats.assess][stats.Stats.assess] is made), or a "universal" (forecast, but at intermediate [non-obs.-time]) estimate. The same applies for the time-averages of `.avrgs`. @@ -20,7 +22,14 @@ The same applies for the time-averages of `.avrgs`. The statistics are also averaged in space. This is done according to the methods listed in `dpr.rc.field_summaries`. -.. note:: +!!! note + + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla et euismod + nulla. Curabitur feugiat, tortor non consequat finibus, justo purus auctor + massa, nec semper lorem quam in massa. + +!!! note + Although sometimes pretty close, `rmv` (a.k.a. `spread.rms`) is not (supposed to be) an un-biased estimator of `rmse` (a.k.a. `err.rms`). This is because of the square roots involved in the field summary. Instead, `spread.ms` (i.e. @@ -30,10 +39,12 @@ This is done according to the methods listed in `dpr.rc.field_summaries`. If the `HiddenMarkovModel` has the attribute `.sectors` with value, e.g., ->>> HMM.sectors = { -... "ocean": inds_of_state_of_the_ocean, -... "atmos": inds_of_state_of_the_atmosphere, -... } +```python +HMM.sectors = { + "ocean": inds_of_state_of_the_ocean, + "atmos": inds_of_state_of_the_atmosphere, +} +``` then `.stats.rms` and `.avrgs.rms` will also have attributes named after the keys of `HMM.sectors`, e.g. `stats.err.rms.ocean`. @@ -41,28 +52,38 @@ This also goes for any other (than `rms`) type of field summary method. ## Declaring new, custom statistics -Only the time series created with `Stats.new_series` will be in the format -operated on by `Stats.average_in_time`. For example, create `ndarray` of +Only the time series created with [Stats.new_series][stats.Stats.new_series] will be in the format +operated on by [Stats.average_in_time][stats.Stats.average_in_time]. For example, create `ndarray` of length `Ko+1` to hold the time series of estimated inflation values: ->>> self.stats.new_series('infl', 1, Ko+1) +```python +self.stats.new_series('infl', 1, Ko+1) +``` Alternatively you can overwrite a default statistic; for example: ->>> error_time_series_a = xx - ensemble_time_series_a.mean(axis=1) -... self.stats.err.rms.a = np.sqrt(np.mean(error_time_series_a**2, axis=-1)) +```python +error_time_series_a = xx - ensemble_time_series_a.mean(axis=1) +self.stats.err.rms.a = np.sqrt(np.mean(error_time_series_a**2, axis=-1)) +``` Of course, you could just do this ->>> self.stats.my_custom_stat = value +```python +self.stats.my_custom_stat = value +``` -However, `dapper.xp_launch.run_experiment` (without `free=False`) will delete +However, [`xp_launch.run_experiment`][] (without `free=False`) will delete the `Stats` object from `xp` after the assimilation, in order to save memory. Therefore, in order to have `my_custom_stat` be available among `xp.avrgs`, it must be "registered": ->>> self.stats.stat_register.append("my_custom_stat") +```python +self.stats.stat_register.append("my_custom_stat") +``` Alternatively, you can do both at once ->>> self.stat("my_custom_stat", value) +```python +self.stat("my_custom_stat", value) +``` diff --git a/dapper/tools/chronos.py b/dapper/tools/chronos.py index fd169805..e61f3e28 100644 --- a/dapper/tools/chronos.py +++ b/dapper/tools/chronos.py @@ -24,9 +24,9 @@ class Chronology: kko: 2 4 6 [----dko------] - .. warning:: By convention, there is no obs at 0. - This is hardcorded in DAPPER, - whose cycling starts by the forecast. + !!! warning + By convention, there is no obs at 0. + This is hardcorded in DAPPER, whose cycling starts by the forecast. Identities (subject to precision): diff --git a/dapper/tools/colors.py b/dapper/tools/colors.py index b55c1be2..939f704a 100644 --- a/dapper/tools/colors.py +++ b/dapper/tools/colors.py @@ -39,7 +39,8 @@ def color_text(text, *color_codes): def coloring(*color_codes): """Color printing using 'with'. - Example: + Examples + -------- >>> with coloring(colorama.Fore.GREEN): ... print("--- This is in color ---") # doctest: +SKIP """ diff --git a/dapper/tools/linalg.py b/dapper/tools/linalg.py index c307847c..612951dc 100644 --- a/dapper/tools/linalg.py +++ b/dapper/tools/linalg.py @@ -39,11 +39,11 @@ def tsvd(A, threshold=0.99999, avoid_pathological=True): Parameters ---------- - avoid_pathological: bool + avoid_pathological : bool Avoid truncating (e.g.) the identity matrix. NB: only applies for float threshold. - threshold: float or int + threshold : float or int - if `float`, `< 1.0` then "rank" = lowest number such that the "energy" retained >= threshold diff --git a/dapper/tools/liveplotting.py b/dapper/tools/liveplotting.py index 55dc8456..c147c20b 100644 --- a/dapper/tools/liveplotting.py +++ b/dapper/tools/liveplotting.py @@ -1,14 +1,14 @@ """On-line (live) plots of the DA process for various models and methods. Liveplotters are given by a list of tuples as property or arguments in -`dapper.mods.HiddenMarkovModel`. +[`mods.HiddenMarkovModel`][]. - The first element of the tuple determines whether the liveplotter is shown if the names of liveplotters are not given by `liveplots` argument in `assimilate`. - The second element in the tuple gives the corresponding liveplotter -function/class. See example of function `LPs` in `dapper.mods.Lorenz63`. +function/class. See example of function `LPs` in [`mods.Lorenz63`][]. The liveplotters can be fine-tuned by each DA experiments via argument of `liveplots` when calling `assimilate`. diff --git a/dapper/tools/localization.py b/dapper/tools/localization.py index 0cc16c46..f03f0076 100644 --- a/dapper/tools/localization.py +++ b/dapper/tools/localization.py @@ -20,13 +20,13 @@ def pairwise_distances(A, B=None, domain=None): Parameters ---------- - A: array of shape `(nPoints, nDims)`. + A : array of shape `(nPoints, nDims)`. A collection of points. - B: + B : Same as `A`, but `nPoints` can differ. - domain: tuple + domain : tuple Assume the domain is a **periodic** hyper-rectangle whose edges along dimension `i` span from 0 to `domain[i]`. NB: Behaviour not defined if `any(A.max(0) > domain)`, and likewise for `B`. @@ -221,8 +221,8 @@ def rectangular_partitioning(shape, steps, do_ind=True): Parameters ---------- - shape: (len(grid[dim]) for dim in range(ndim)) - steps: (step_len[dim] for dim in range(ndim)) + shape : (len(grid[dim]) for dim in range(ndim)) + steps : (step_len[dim] for dim in range(ndim)) Returns ------- diff --git a/dapper/tools/matrices.py b/dapper/tools/matrices.py index d2bc6a26..a3627c50 100644 --- a/dapper/tools/matrices.py +++ b/dapper/tools/matrices.py @@ -137,8 +137,8 @@ def funm_psd(a, fun, check_finite=False): Adapted from `sla.funm` doc. - Example - ------- + Examples + -------- >>> def sqrtm_psd(A): ... return funm_psd(A, sqrt) """ @@ -151,8 +151,8 @@ def funm_psd(a, fun, check_finite=False): def chol_reduce(Right): """Return rnk-by-ndim R such that `R.T@R - R.T@R ≈ 0`. - Example - ------- + Examples + -------- >>> from dapper.stats import mean0 >>> X = mean0(np.random.randn(20, 5), axis=1) >>> C = X.T @ X diff --git a/dapper/tools/multiproc.py b/dapper/tools/multiproc.py index 98caaf31..2bf1d9b4 100644 --- a/dapper/tools/multiproc.py +++ b/dapper/tools/multiproc.py @@ -48,18 +48,18 @@ def Pool(NPROC=None): so you likely want to re-use a pool rather than repeatedly creating one. Consider using `functools.partial` to fix kwargs. - .. note:: + !!! note In contrast to *reading*, in-place writing does not work with multiprocessing. This changes with "shared" arrays, but that has not been tested here. By contrast, multi*threading* shares the process memory, but was significantly slower in the tested (pertinent) cases. - .. warning:: + !!! warning `multiprocessing` does not mix with `matplotlib`, so ensure `func` does not reference `xp.stats.LP_instance`. In fact, `func` should not reference `xp` at all, because it takes time to serialize. - See example use in `dapper.mods.QG` and `dapper.da_methods.ensemble.LETKF`. + See example use in [`mods.QG`][] and [`da_methods.ensemble.LETKF`][]. """ if NPROC == False: # noqa: E712 # Yield plain old map diff --git a/dapper/tools/randvars.py b/dapper/tools/randvars.py index f0e5f134..8530f89f 100644 --- a/dapper/tools/randvars.py +++ b/dapper/tools/randvars.py @@ -149,8 +149,8 @@ def __init__(self, mu=0, C=0, M=None): def sample(self, N): """Sample N realizations. Returns N-by-M (ndim) sample matrix. - Example - ------- + Examples + -------- >>> plt.scatter(*(UniRV(C=randcov(2)).sample(10**4).T)) # doctest: +SKIP """ if self.C == 0: diff --git a/dapper/tools/remote/uplink.py b/dapper/tools/remote/uplink.py index 93f8ff75..cd5b935b 100644 --- a/dapper/tools/remote/uplink.py +++ b/dapper/tools/remote/uplink.py @@ -370,7 +370,8 @@ def get_ip(instance): def sub_run(*args, check=True, capture_output=True, text=True, **kwargs): r"""Do `subprocess.run`, with responsive defaults. - Examples: + Examples + -------- >>> gitfiles = sub_run(["git", "ls-tree", "-r", "--name-only", "HEAD"]) # or: >>> # gitfiles = sub_run("git ls-tree -r --name-only HEAD", shell=True) """ diff --git a/dapper/tools/rounding.py b/dapper/tools/rounding.py index f1ae2bf7..ab3558c9 100644 --- a/dapper/tools/rounding.py +++ b/dapper/tools/rounding.py @@ -17,7 +17,8 @@ class UncertainQtty: However, this class in itself does not define the `prec` attribute by anything else than what it does: impact the rounding & printing of `val`. - Examples: + Examples + -------- >>> for c in [.01, .1, .2, .9, 1]: ... print(UncertainQtty(1.2345, c)) 1.23 ±0.01 @@ -74,7 +75,9 @@ def round(self=1.0): # noqa return v, c def __str__(self): - """Returns 'val ±prec', using `UncertainQtty.round` and some finesse.""" + """Returns 'val ±prec', using [tools.rounding.UncertainQtty.round][] + + and some finesse.""" v, c = self.round() if np.isnan(c): @@ -122,12 +125,12 @@ def np_vectorize(f): Parameters ---------- - f: callable + f : callable Your function. Returns ------- - vectorized: callable + vectorized : callable Your function, now element-wise applicable to an iterable. """ vectorized = np.vectorize(f) @@ -148,6 +151,8 @@ def _round2prec(num, prec): This function is left here just for reference. Use `round2` instead. + Examples + -------- The issue is that: >>> _round2prec(0.7,.1) 0.7000000000000001 @@ -190,7 +195,7 @@ def round2(x, prec=1.0): ---------- x : array_like Value to be rounded. - prec: float + prec : float Precision, before prettify, which is given by $$ \text{prec} = 10^{\text{floor}(-\log_{10}|\text{prec}|)} $$ @@ -257,12 +262,12 @@ def is_whole(x, **kwargs): Parameters ---------- - x: float or ndarray + x : float or ndarray Values to be checked Returns ------- - l: bool + l : bool True if rounded x is close to x, otherwise False """ return np.isclose(x, round(x), **kwargs) diff --git a/dapper/tools/series.py b/dapper/tools/series.py index 82a186bb..42085290 100644 --- a/dapper/tools/series.py +++ b/dapper/tools/series.py @@ -65,7 +65,7 @@ def mean_ratio(xx): def estimate_corr_length(xx): r"""Estimate the correlation length of a time series. - For explanation, see `dapper.mods.LA.homogeneous_1D_cov`. + For explanation, see [`mods.LA.homogeneous_1D_cov`][]. Also note that, for exponential corr function, as assumed here, $$\text{corr}(L) = \exp(-1) \approx 0.368$$ @@ -210,8 +210,9 @@ class FAUSt(DataSeries, StatPrint): self[k,'u'] self[k,whatever,'u'] - .. note:: If a data series only pertains to analysis times, - then you should use a plain np.array instead. + !!! note + If a data series only pertains to analysis times, then you should use a plain + np.array instead. """ def __init__(self, K, Ko, item_shape, store_u, store_s, **kwargs): diff --git a/dapper/tools/viz.py b/dapper/tools/viz.py index c14918c2..07cc3094 100644 --- a/dapper/tools/viz.py +++ b/dapper/tools/viz.py @@ -27,9 +27,9 @@ def setup_wrapping(M, periodicity=None): Parameters ---------- - M: int + M : int Length of the periodic domain - periodicity: bool, optional + periodicity : bool, optional The mode of the wrapping. "+1": the first element is appended after the last. "+/-05": adding the midpoint of the first and last elements. @@ -37,9 +37,9 @@ def setup_wrapping(M, periodicity=None): Returns ------- - ii: ndarray + ii : ndarray indices of periodic domain - wrap: func + wrap : func transform non-periodic data into periodic data """ if periodicity in (None, True): @@ -77,24 +77,24 @@ def amplitude_animation( Parameters ---------- - EE: ndarray + EE : ndarray Ensemble arry of the shape (K, N, Nx). K is the length of time, N is the ensemble size, and Nx is the length of state vector. - dt: float + dt : float Time interval of each frame. - interval: float, optional + interval : float, optional Delay between frames in milliseconds. Defaults to 200. - periodicity: bool, optional + periodicity : bool, optional The mode of the wrapping. "+1": the first element is appended after the last. "+/-05": adding the midpoint of the first and last elements. Default: "+1" - blit: bool, optional + blit : bool, optional Controls whether blitting is used to optimize drawing. Default: True - fignum: int, optional + fignum : int, optional Figure index. Default: None - repeat: bool, optional + repeat : bool, optional If True, repeat the animation. Default: False """ fig, ax = place.freshfig(fignum or "Amplitude animation") @@ -133,15 +133,15 @@ def xtrema(xx, axis=None): Parameters ---------- - xx: ndarray - axis: int, optional + xx : ndarray + axis : int, optional Specific axis for min and max. Defaults: None Returns ------- - a: float + a : float min value - b: float + b : float max value """ a = np.nanmin(xx, axis) @@ -154,21 +154,21 @@ def stretch(a, b, factor=1, int_=False): Parameters ---------- - a: float + a : float Lower bound of domain. - b: float + b : float Upper bound of domain. - factor: float, optional + factor : float, optional Streching factor. Defaults: 1 - int_: bool, optional + int_ : bool, optional If True, the domain bounds are integer. Defaults: False Returns ------- - a: float + a : float Lower bound of domain. - b: float + b : float Upper bound of domain. """ c = (a + b) / 2 @@ -185,12 +185,12 @@ def set_ilim(ax, i, Min=None, Max=None): Parameters ---------- - ax: matplotlib.axes - i: int + ax : matplotlib.axes + i : int 1: x-axis; 2: y-axis; 3: z-axis - Min: float, optional + Min : float, optional Lower bound limit. Defaults: None - Max: float, optional + Max : float, optional Upper bound limit. Defaults: None """ if i == 0: @@ -210,20 +210,20 @@ def estimate_good_plot_length(xx, tseq=None, mult=100): Parameters ---------- - xx: ndarray + xx : ndarray Plotted array - tseq: `dapper.tools.chronos.Chronology`, optional + tseq : [`tools.chronos.Chronology`][], optional object with property dko. Defaults: None - mult: int, optional + mult : int, optional Number of waves for plotting. Defaults: 100 Returns ------- - K: int + K : int length for plotting - Example - ------- + Examples + -------- >>> K_lag = estimate_good_plot_length(stats.xx, tseq, mult=80) # doctest: +SKIP """ if xx.ndim == 2: @@ -319,9 +319,9 @@ def plot_hovmoller(xx, tseq=None): Parameters ---------- - xx: ndarray + xx : ndarray Plotted array - tseq: `dapper.tools.chronos.Chronology`, optional + tseq : [`tools.chronos.Chronology`][], optional object with property dko. Defaults: None """ fig, ax = place.freshfig("Hovmoller", figsize=(4, 3.5)) @@ -352,15 +352,15 @@ def integer_hist(E, N, centrd=False, weights=None, **kwargs): Parameters ---------- - E: ndarray + E : ndarray Ensemble array. - N: int + N : int Number of histogram bins. - centrd: bool, optional + centrd : bool, optional If True, each bin is centered in the midpoint. Default: False - weights: float, optional + weights : float, optional Weights for histogram. Default: None - kwargs: dict + kwargs : dict keyword arguments for matplotlib.hist """ ax = plt.gca() @@ -374,10 +374,10 @@ def not_available_text(ax, txt=None, fs=20): Parameters ---------- - ax: matplotlib.axes - txt: str, optional + ax : matplotlib.axes + txt : str, optional Printed text. Defaults: '[Not available]' - fs: float, optional + fs : float, optional Font size. Defaults: 20. """ if txt is None: @@ -401,16 +401,17 @@ def plot_err_components(stats): Parameters ---------- - stats: `dapper.stats.Stats` - - .. note:: - it was chosen to `plot(ii, mean_in_time(abs(err_i)))`, - and thus the corresponding spread measure is MAD. - If one chose instead: `plot(ii, std_spread_in_time(err_i))`, - then the corresponding measure of spread would have been `spread`. - This choice was made in part because (wrt. subplot 2) - the singular values (`svals`) correspond to rotated MADs, - and because `rms(umisf)` seems too convoluted for interpretation. + stats : stats.Stats + + Note + ---- + It was chosen to `plot(ii, mean_in_time(abs(err_i)))`, + and thus the corresponding spread measure is MAD. + If one chose instead: `plot(ii, std_spread_in_time(err_i))`, + then the corresponding measure of spread would have been `spread`. + This choice was made in part because (wrt. subplot 2) + the singular values (`svals`) correspond to rotated MADs, + and because `rms(umisf)` seems too convoluted for interpretation. """ fig, (ax0, ax1, ax2) = place.freshfig("Error components", figsize=(6, 6), nrows=3) @@ -466,7 +467,7 @@ def plot_rank_histogram(stats): Parameters ---------- - stats: `dapper.stats.Stats` + stats: stats.Stats """ tseq = stats.HMM.tseq @@ -512,12 +513,12 @@ def axis_scale_by_array(ax, arr, axis="y", nbins=3): Parameters ---------- - ax: matplotlib.axes - arr: ndarray + ax : matplotlib.axes + arr : ndarray Array for plotting - axis: str, optional + axis : str, optional Scaled axis, which can be 'x', 'y' or 'z'. Defaults: 'y' - nbins: int, optional + nbins : int, optional Number of major ticks. Defaults: 3 """ yy = array([y for y in arr if y is not None], dtype=float) # rm None diff --git a/dapper/xp_launch.py b/dapper/xp_launch.py index 58cc5701..9bc7c3ea 100644 --- a/dapper/xp_launch.py +++ b/dapper/xp_launch.py @@ -1,6 +1,6 @@ """Tools (notably `xpList`) for setup and running of experiments (known as `xp`s). -See `dapper.da_methods.da_method` for the strict definition of `xp`s. +See [`da_methods.da_method`][] for the strict definition of `xp`s. """ import copy @@ -34,16 +34,17 @@ def seed_and_simulate(HMM, xp): """Default experiment setup (sets seed and simulates truth and obs). - Used by `xpList.launch` via `run_experiment`. + Used by [xp_launch.xpList.launch][] + via [xp_launch.run_experiment][]. Parameters ---------- - HMM: HiddenMarkovModel + HMM : HiddenMarkovModel Container defining the system. - xp: object - Type: a `dapper.da_methods.da_method`-decorated class. + xp : object + Type: a [`da_methods.da_method`][]-decorated class. - .. warning:: `xp.seed` should be set (and `int`). + !!! warning "`xp.seed` should be set (and `int`)." Without `xp.seed` the seed does not get set, and different `xp`s will use different seeds @@ -73,9 +74,9 @@ def run_experiment( fail_gently=False, **stat_kwargs, ): - """Used by `xpList.launch` to run each single (DA) experiment ("xp"). + """Used by [xp_launch.xpList.launch][] to run each single (DA) experiment ("xp"). - This involves steps similar to `examples/basic_1.py`, i.e.: + This involves steps similar to `docs/examples/basic_1.py`, i.e.: - `setup` : Initialize experiment. - `xp.assimilate` : run DA, pass on exception if fail_gently @@ -85,25 +86,25 @@ def run_experiment( Parameters ---------- - xp: object - Type: a `dapper.da_methods.da_method`-decorated class. - label: str + xp : object + Type: a [`da_methods.da_method`][]-decorated class. + label : str Name attached to progressbar during assimilation. - savedir: str + savedir : str Path of folder wherein to store the experiment data. - HMM: HiddenMarkovModel + HMM : HiddenMarkovModel Container defining the system. - free: bool + free : bool Whether (or not) to `del xp.stats` after the experiment is done, so as to free up memory and/or not save this data (just keeping `xp.avrgs`). - statkeys: list + statkeys : list A list of names (possibly in the form of abbreviations) of the statistical averages that should be printed immediately afther this xp. - fail_gently: bool + fail_gently : bool Whether (or not) to propagate exceptions. - setup: function + setup : function This function must take two arguments: `HMM` and `xp`, and return the `HMM` to be used by the DA methods (typically the same as the input `HMM`, but could be modified), and the (typically synthetic) truth and obs time series. @@ -111,7 +112,7 @@ def run_experiment( This gives you the ability to customize almost any aspect of the individual experiments within a batch launch of experiments (i.e. not just the parameters of the DA. method). Typically you will grab one or more parameter values stored - in the `xp` (see `dapper.da_methods.da_method`) and act on them, usually by + in the `xp` (see [`da_methods.da_method`][]) and act on them, usually by assigning them to some object that impacts the experiment. Thus, by generating a new `xp` for each such parameter value you can investigate the impact/sensitivity of the results to this parameter. Examples include: @@ -217,17 +218,17 @@ class xpList(list): Parameters ---------- - args: entries + args : entries Nothing, or a list of `xp`s. - unique: bool + unique : bool Duplicates won't get appended. Makes `append` (and `__iadd__`) relatively slow. Use `extend` or `__add__` or `combinator` to bypass this validation. Also see -------- - - Examples: `examples/basic_2`, `examples/basic_3` - - `dapper.xp_process.xpSpace`, which is used for experient result **presentation**, + - Examples: `docs/examples/basic_2`, `docs/examples/basic_3` + - [`xp_process.xpSpace`][], which is used for experient result **presentation**, as opposed to this class (`xpList`), which handles **launching** experiments. """ @@ -294,11 +295,11 @@ def prep_table(self, nomerge=()): since there might exist a subset of attributes that) uniquely identify each `xp` in the list (the `redundant` and `common` can be "squeezed" out). Thus, a table of the `xp`s does not need to list all of the attributes. - This function also does the heavy lifting for `xpSpace.squeeze`. + This function also does the heavy lifting for [xp_process.xpSpace.squeeze][]. Parameters ---------- - nomerge: list + nomerge : list Attributes that should always be seen as distinct. """ @@ -464,7 +465,7 @@ def launch(self, HMM, save_as="noname", mp=False, fail_gently=None, **kwargs): """Essentially: `for xp in self: run_experiment(xp, ..., **kwargs)`. See `run_experiment` for documentation on the `kwargs` and `fail_gently`. - See `dapper.tools.datafiles.create_run_dir` for documentation `save_as`. + See [`tools.datafiles.create_run_dir`][] for documentation `save_as`. Depending on `mp`, `run_experiment` is delegated as follows: @@ -481,7 +482,7 @@ def launch(self, HMM, save_as="noname", mp=False, fail_gently=None, **kwargs): - If this dict field is empty, then all python files in `sys.path[0]` are uploaded. - See `examples/basic_2.py` and `examples/basic_3.py` for example use. + See `docs/examples/basic_2.py` and `docs/examples/basic_3.py` for example use. """ # Parse mp option if not mp: @@ -627,14 +628,14 @@ def combinator(param_dict, **glob_dict): """Mass creation of `xp`'s by combining the value lists in the `param_dict`. Returns a function (`for_params`) that creates all possible combinations - of parameters (from their value list) for a given `dapper.da_methods.da_method`. + of parameters (from their value list) for a given [`da_methods.da_method`][]. This is a good deal more efficient than relying on `xpList`'s `unique`. Parameters - not found among the args of the given DA method are ignored by `for_params`. - specified as keywords to the `for_params` fix the value preventing using the corresponding (if any) value list in the `param_dict`. - .. warning:: + !!! warning Beware! If, eg., `infl` or `rot` are in `param_dict`, aimed at the `EnKF`, but you forget that they are also attributes some method where you don't actually want to use them (eg. `SVGDF`), diff --git a/dapper/xp_process.py b/dapper/xp_process.py index 32c9f230..f090e10d 100644 --- a/dapper/xp_process.py +++ b/dapper/xp_process.py @@ -24,7 +24,10 @@ class SparseSpace(dict): Like a normal `dict`, it can hold any type of objects. But, since the keys must conform, they effectively follow a coordinate system, - so that the `dict` becomes a vector **space**. Example: + so that the `dict` becomes a vector **space**. + + Examples + -------- >>> dct = xpSpace(["x", "y", "z"]) >>> dct[(1, 2, 3)] = "pointA" @@ -90,7 +93,7 @@ def __init__(self, dims): Parameters ---------- - dims: list or tuple + dims : list or tuple The attributes defining the coordinate system. """ # Define coordinate system @@ -109,7 +112,7 @@ def update(self, items): """Update dict, using the custom `__setitem__` to ensure key conformity. NB: the `kwargs` syntax is not supported because it only works for keys that - consist of (a single) string, which is not very interesting for SparseSpace. + consist of (a single) string, which is not very interesting for `SparseSpace`. """ # See https://stackoverflow.com/a/2588648 # and https://stackoverflow.com/a/2390997 @@ -151,16 +154,17 @@ def __getitem__(self, key): return super().__getitem__(key) def __call__(self, **kwargs): - """Shortcut (syntactic sugar) for `SparseSpace.subspace`.""" + """Shortcut (syntactic sugar) for [xp_process.SparseSpace.subspace][].""" return self.subspace(**kwargs) def subspace(self, **kwargs): """Get an affine subspace. NB: If you're calling this repeatedly (for all values of the same `kwargs`) - then you should consider using `SparseSpace.nest` instead. + then you should consider using [xp_process.SparseSpace.nest][] instead. - Example: + Examples + -------- >>> xp_dict.subspace(da_method="EnKF", infl=1, seed=3) # doctest: +SKIP """ # Slow version @@ -177,11 +181,13 @@ def subspace(self, **kwargs): def coords_matching(self, **kwargs): """Get all `coord`s matching kwargs. - Used by `SparseSpace.label_xSection` and `SparseSpace.subspace`. Unlike the - latter, this function returns a *list* of *keys* of the *original subspace*. + Used by [xp_process.SparseSpace.label_xSection][] and + [xp_process.SparseSpace.subspace][]. + Unlike the latter, this function returns a *list* of *keys* + of the *original subspace*. - Note that the `missingval` shenanigans of `xpList.inds` are here unnecessary - since each coordinate is complete. + Note that the `missingval` shenanigans of [xp_launch.xpList.inds][] + are here unnecessary since each coordinate is complete. """ def match(coord): @@ -479,8 +485,8 @@ def table_tree(self, statkey, dims, *, costfun=None): by the mean/tune operations. The `dims['outer']` and `dims['inner'] become the keys for the output hierarchy. - .. note:: - cannot support multiple `statkey`s because it's not (obviously) meaningful + !!! note + Cannot support multiple `statkey`s because it's not (obviously) meaningful when optimizing over `dims['optim']`. """ @@ -585,10 +591,10 @@ def print( Parameters ---------- - statkey: str + statkey : str The statistic to extract from the `xp.avrgs` for each `xp`. Examples: `"rmse.a"` (i.e. `"err.rms.a"`), `"rmse.ocean.a"`, `"duration"`. - dims: dict + dims : dict Allots (maps) the dims of `xpSpace` to different roles in the tables. - The "role" `outer` should list the dims/attributes @@ -608,7 +614,7 @@ def print( Equivalently, use `mean=("seed",)`. It is acceptible to leave this empty: `mean=()` or `mean=None`. - subcols: bool + subcols : bool If `True`, then subcolumns are added to indicate - `1σ`: the confidence interval. If `mean=None` is used, this simply reports @@ -619,16 +625,16 @@ def print( as defined by `costfun`. - `☠`: the number of failures (non-finite values) at that point. - `✓`: the number of successes that go into the value - decimals: int + decimals : int Number of decimals to print. If `None`, this is determined for each statistic by its uncertainty. - costfun: str or function + costfun : str or function Use `'increasing'` (default) or `'decreasing'` to indicate that the optimum is defined as the lowest or highest value of the `statkey` found. - squeeze_labels: bool + squeeze_labels : bool Don't include redundant attributes in the line labels. Caution: `get_style` will not be able to access the eliminated attrs. - colorize: bool + colorize : bool Add color to tables for readability. """ # Title @@ -766,16 +772,16 @@ def plot( Parameters ---------- - get_style: function + get_style : function A function that takes an object, and returns a dict of line styles, usually as a function of the object's attributes. - title1: anything + title1 : anything Figure title (in addition to the the defaults). - title2: anything + title2 : anything Figure title (in addition to the defaults). Goes on a new line. - unique_labels: bool + unique_labels : bool Only show a given line label once, even if it appears in several panels. - squeeze_labels: + squeeze_labels : Don't include redundant attributes in the labels. """ diff --git a/docs/bib/bib.py b/docs/bib/bib.py deleted file mode 100644 index ff9d2df4..00000000 --- a/docs/bib/bib.py +++ /dev/null @@ -1,375 +0,0 @@ -"""Bibliography/references.""" - -anderson2010non = None -""" -**Anderson**, **J L** 2010 A non-Gaussian ensemble filter update -for data assimilation. *Monthly Weather Review*, 138(11): -4186–4198. -""" - -anderson2009spatially = None -""" -**Anderson**, **J L** 2009 Spatially and temporally varying -adaptive covariance inflation for ensemble filters. *Tellus A*, -61(1): 72–83. -""" - -bengtsson2003toward = None -""" -**Bengtsson**, **T**, **Snyder**, **C**, and **Nychka**, **D** -2003 Toward a nonlinear ensemble filter for high-dimensional -systems. *Journal of Geophysical Research: Atmospheres*, 108(D24). -""" - -bocquet2016localization = None -""" -**Bocquet**, **M** 2016 Localization and the iterative ensemble -Kalman smoother. *Quarterly Journal of the Royal Meteorological -Society*, 142(695): 1075–1089. -""" - -bocquet2011ensemble = None -""" -**Bocquet**, **M** 2011 Ensemble Kalman filtering without the -intrinsic need for inflation. *Nonlinear Processes in Geophysics*, -18(5): 735–750. -""" - -bocquet2019consistency = None -""" -**Bocquet**, **M** and **Farchi**, **A** 2019 On the consistency -of the local ensemble square root kalman filter perturbation -update. *Tellus A: Dynamic Meteorology and Oceanography*, 71(1): -1613142. -""" - -bocquet2010beyond = None -""" -**Bocquet**, **M**, **Pires**, **C A**, and **Wu**, **L** 2010 -Beyond Gaussian statistical modeling in geophysical data -assimilation. *Monthly Weather Review*, 138(8): 2997–3023. -""" - -bocquet2015expanding = None -""" -**Bocquet**, **M**, **Raanes**, **P N**, and **Hannart**, **A** -2015 Expanding the validity of the ensemble Kalman filter without -the intrinsic need for inflation. *Nonlinear Processes in -Geophysics*, 22(6): 645–662. -""" - -bocquet2014iterative = None -""" -**Bocquet**, **M** and **Sakov**, **P** 2014 An iterative ensemble -Kalman smoother. *Quarterly Journal of the Royal Meteorological -Society*, 140(682): 1521–1535. -""" - -bocquet2013joint = None -""" -**Bocquet**, **M** and **Sakov**, **P** 2013 Joint state and -parameter estimation with an iterative ensemble Kalman smoother. -*Nonlinear Processes in Geophysics*, 20(5): 803–818. -""" - -bocquet2012combining = None -""" -**Bocquet**, **M** and **Sakov**, **P** 2012 Combining -inflation-free and iterative ensemble Kalman filters for strongly -nonlinear systems. *Nonlinear Processes in Geophysics*, 19(3): -383–399. -""" - -chen2003bayesian = None -""" -**Chen**, **Z** 2003 Bayesian filtering: From Kalman filters to -particle filters, and beyond. *Statistics*, 182(1): 1–69. -""" - -counillon2009application = None -""" -**Counillon**, **F**, **Sakov**, **P**, and **Bertino**, **L** -2009 Application of a hybrid EnKF-OI to ocean forecasting. -""" - -doucet2001sequential = None -""" -**Doucet**, **A**, **De Freitas**, **N**, and **Gordon**, **N** -2001 *Sequential Monte Carlo methods in practice*. Springer. -""" - -doucet2009tutorial = None -""" -**Doucet**, **A** and **Johansen**, **A M** 2009 A tutorial on -particle filtering and smoothing: Fifteen years later. *Handbook -of Nonlinear Filtering*, 656–704. -""" - -emerick2012history = None -""" -**Emerick**, **A A** and **Reynolds**, **A C** 2012 History -matching time-lapse seismic data using the ensemble Kalman filter -with multiple data assimilations. *Computational Geosciences*, -16(3): 639–659. -""" - -evensen2009ensemble = None -""" -**Evensen**, **G** 2009 The ensemble Kalman filter for combined -state and parameter estimation. *Control Systems, IEEE*, 29(3): -83–104. -""" - -frei2013mixture = None -""" -**Frei**, **M** and **Künsch**, **H R** 2013b Mixture ensemble -kalman filters. *Computational Statistics & Data Analysis*, 58: -127–138. -""" - -frei2013bridging = None -""" -**Frei**, **M** and **Künsch**, **H R** 2013a Bridging the -ensemble Kalman and particle filters. *Biometrika*, ast020. -""" - -grudzien2020numerical = None -""" -**Grudzien**, **C**, **Bocquet**, **M**, and **Carrassi**, **A** -2020 On the numerical integration of the lorenz-96 model, with -scalar additive noise, for benchmark twin experiments. -*Geoscientific Model Development*, 13(4): 1903–1924. -""" - -harty2021eigenvector = None -""" -**Harty**, **T**, **Morzfeld**, **M**, and **Snyder**, **C** 2021 -Eigenvector-spatial localisation. *Tellus A: Dynamic Meteorology -and Oceanography*, 73(1): 1–18. -""" - -hoteit2015mitigating = None -""" -**Hoteit**, **I**, **Pham**, **D-T**, **Gharamti**, **M E**, and -**Luo**, **X** 2015 Mitigating observation perturbation sampling -errors in the stochastic EnKF. *Monthly Weather Review*, 143(7): -2918–2936. -""" - -hunt2007efficient = None -""" -**Hunt**, **B R**, **Kostelich**, **E J**, and **Szunyogh**, **I** -2007 Efficient data assimilation for spatiotemporal chaos: A local -ensemble transform Kalman filter. *Physica D: Nonlinear -Phenomena*, 230(1): 112–126. -""" - -karspeck2007experimental = None -""" -**Karspeck**, **A R** and **Anderson**, **J L** 2007 Experimental -implementation of an ensemble adjustment filter for an -intermediate ENSO model. *Journal of Climate*, 20(18): 4638–4658. -""" - -van2009particle = None -""" -**Leeuwen**, **P J** **van** 2009 Particle filtering in -geophysical systems. *Monthly Weather Review*, 137(12): 4089–4114. -""" - -lei2011moment = None -""" -**Lei**, **J** and **Bickel**, **P** 2011 A moment matching -ensemble filter for nonlinear non-Gaussian data assimilation. -*Monthly Weather Review*, 139(12): 3964–3973. -""" - -liu2001theoretical = None -""" -**Liu**, **J S**, **Chen**, **R**, and **Logvinenko**, **T** 2001 -A theoretical framework for sequential importance sampling with -resampling. In: *Sequential Monte Carlo Methods in Practice*. -Springer. pp. 225–246. -""" - -lorenz2005look = None -""" -**Lorenz**, **E N** 2005 A look at some details of the growth of -initial uncertainties. *Tellus A: Dynamic Meteorology and -Oceanography*, 57(1): 1–11. -""" - -lorenz1996predictability = None -""" -**Lorenz**, **E N** 1996 Predictability: A problem partly solved. -In: *Proc. ECMWF Seminar on Predictability*. Reading, UK. pp. -1–18. -""" - -lorenz1984irregularity = None -""" -**Lorenz**, **E N** 1984 Irregularity: A fundamental property of -the atmosphere. *Tellus A*, 36(2): 98–110. -""" - -lorenz1998optimal = None -""" -**Lorenz**, **E N** and **Emanuel**, **K A** 1998 Optimal sites -for supplementary weather observations: Simulation with a small -model. *Journal of the Atmospheric Sciences*, 55(3): 399–414. -""" - -lorenz2005designing = None -""" -**Lorenz**, **Edward N** 2005 Designing chaotic models. *Journal -of the Atmospheric Sciences*, 62(5): 1574–1587. -""" - -mandel2016hybrid = None -""" -**Mandel**, **J**, **Bergou**, **E**, **Gürol**, **S**, -**Gratton**, **S**, and **Kasanický**, **I** 2016 Hybrid -Levenberg-Marquardt and weak-constraint ensemble Kalman smoother -method. *Nonlinear Processes in Geophysics*, 23(2): 59–73. -""" - -miyoshi2011gaussian = None -""" -**Miyoshi**, **T** 2011 The Gaussian approach to adaptive -covariance inflation and its implementation with the local -ensemble transform Kalman filter. *Monthly Weather Review*, -139(5): 1519–1535. -""" - -pajonk2012deterministic = None -""" -**Pajonk**, **O**, **Rosić**, **B V**, **Litvinenko**, **A**, and -**Matthies**, **H G** 2012 A deterministic filter for non-Gaussian -Bayesian estimation—applications to dynamical system estimation -with noisy measurements. *Physica D: Nonlinear Phenomena*, 241(7): -775–788. -""" - -pinheiro2019efficient = None -""" -**Pinheiro**, **F R**, **Leeuwen**, **P J** **van**, and -**Geppert**, **G** 2019 Efficient nonlinear data assimilation -using synchronization in a particle filter. *Quarterly Journal of -the Royal Meteorological Society*, 145(723): 2510–2523. -""" - -raanes2016thesis = None -""" -**Raanes**, **Patrick N** 2016 Improvements to ensemble methods -for data assimilation in the geosciences (PhD thesis). University -of Oxford. -""" - -raanes2015rts = None -""" -**Raanes**, **Patrick Nima** 2016 On the ensemble -Rauch-Tung-Striebel smoother and its equivalence to the ensemble -Kalman smoother. *Quarterly Journal of the Royal Meteorological -Society*, 142(696): 1259–1264. -""" - -raanes2019adaptive = None -""" -**Raanes**, **Patrick N**, **Bocquet**, **M**, and **Carrassi**, -**A** 2019 Adaptive covariance inflation in the ensemble Kalman -filter by Gaussian scale mixtures. *Quarterly Journal of the Royal -Meteorological Society*, 145(718): 53–75. DOI: -https://doi.org/10.1002/qj.3386 -""" - -raanes2014ext = None -""" -**Raanes**, **P N**, **Carrassi**, **A**, and **Bertino**, **L** -2015 Extending the square root method to account for model noise -in the ensemble Kalman filter. *Monthly Weather Review*, 143(10): -3857–3873. -""" - -raanes2019revising = None -""" -**Raanes**, **Patrick Nima**, **Stordal**, **A S**, and -**Evensen**, **G** 2019 Revising the stochastic iterative ensemble -smoother. *Nonlinear Processes in Geophysics*, 26(3): 325–338. -""" - -rainwater2013mixed = None -""" -**Rainwater**, **S** and **Hunt**, **B** 2013 Mixed-resolution -ensemble data assimilation. *Monthly weather review*, 141(9): -3007–3021. -""" - -sakov2008implications = None -""" -**Sakov**, **P** and **Oke**, **P R** 2008b Implications of the -form of the ensemble transformation in the ensemble square root -filters. *Monthly Weather Review*, 136(3): 1042–1053. -""" - -sakov2008deterministic = None -""" -**Sakov**, **P** and **Oke**, **P R** 2008a A deterministic -formulation of the ensemble Kalman filter: An alternative to -ensemble square root filters. *Tellus A*, 60(2): 361–371. -""" - -sakov2012iterative = None -""" -**Sakov**, **P**, **Oliver**, **D S**, and **Bertino**, **L** 2012 -An iterative EnKF for strongly nonlinear systems. *Monthly Weather -Review*, 140(6): 1988–2004. -""" - -todter2015second = None -""" -**Tödter**, **J** and **Ahrens**, **B** 2015 A second-order exact -ensemble square root filter for nonlinear data assimilation. -*Monthly Weather Review*, 143(4): 1347–1367. -""" - -vano2006chaos = None -""" -**Vano**, **J A**, **Wildenberg**, **J C**, **Anderson**, **M B**, -**Noel**, **J K**, and **Sprott**, **J C** 2006 Chaos in -low-dimensional Lotka–Volterra models of competition. -*Nonlinearity*, 19(10): 2391. -""" - -vissio2020mechanics = None -""" -**Vissio**, **G** and **Lucarini**, **V** 2020 Mechanics and -thermodynamics of a new minimal model of the atmosphere. *The -European Physical Journal Plus*, 135(10): 1–21. -""" - -wikle2007bayesian = None -""" -**Wikle**, **C K** and **Berliner**, **L M** 2007 A Bayesian -tutorial for data assimilation. *Physica D: Nonlinear Phenomena*, -230(1-2): 1–16. -""" - -wiljes2016second = None -""" -**Wiljes**, **J** **de**, **Acevedo**, **W**, and **Reich**, **S** -2016 Second-order accurate ensemble transform particle filters. -*arXiv preprint arXiv:1608.08179*. -""" - -wilks2005effects = None -""" -**Wilks**, **D S** 2005 Effects of stochastic parametrizations in -the Lorenz’96 system. *Quarterly Journal of the Royal -Meteorological Society*, 131(606): 389–407. -""" - -zupanski2005maximum = None -""" -**Zupanski**, **M** 2005 Maximum likelihood ensemble filter: -Theoretical aspects. *Monthly Weather Review*, 133(6): 1710–1726. -""" diff --git a/docs/bib/bib2md.py b/docs/bib/bib2md.py new file mode 100644 index 00000000..c6e3534f --- /dev/null +++ b/docs/bib/bib2md.py @@ -0,0 +1,35 @@ +"""Render bibtex file as markdown with keys as headings that can be cross-referenced.""" + +from pathlib import Path + +from pybtex.database import parse_string +from pybtex.richtext import Tag, Text +from pybtex.style.formatting.unsrt import Style as UnsrtStyle +from pybtex.style.formatting.unsrt import field, sentence + +HERE = Path(__file__).parent + + +class MyStyle(UnsrtStyle): + # default_sorting_style = "author_year_title" + def format_title(self, e, which_field, as_sentence=True): + formatted_title = field( + which_field, + apply_func=lambda text: Tag("tt", Text('"', text.capitalize(), '"')), + ) + if as_sentence: + return sentence[formatted_title] + else: + return formatted_title + + +bib = parse_string((HERE / "refs.bib").read_text(), "bibtex") + +formatted = MyStyle().format_entries(bib.entries.values()) + +md = "" +for entry in formatted: + md += f"### `{entry.key}`\n\n" + md += entry.text.render_as("markdown") + "\n\n" + +(HERE.parent / "references.md").write_text(md) diff --git a/docs/bib/data-science-journal.csl b/docs/bib/data-science-journal.csl deleted file mode 100644 index e7f179e9..00000000 --- a/docs/bib/data-science-journal.csl +++ /dev/null @@ -1,228 +0,0 @@ - - diff --git a/docs/bib/make_bib.py b/docs/bib/make_bib.py deleted file mode 100755 index 42960c91..00000000 --- a/docs/bib/make_bib.py +++ /dev/null @@ -1,93 +0,0 @@ -#!/usr/bin/env python - -"""Make bib.py from refs.bib. - -Usage: - -- Put the references you refer to in dapper's docstrings in refs.bib. - Consider using `copy_refs.py` for this purpose. -- Run this to update the bib.py file. -- Run pdoc. - -Note: requires pandoc, and presumably latex too. -""" - -import os -from pathlib import Path -from subprocess import run -from textwrap import dedent - -os.chdir(Path(__file__).parent) - - -# Create .md doc with \notice{*} -open("bib.md", "w").write(r""" ---- -bibliography: refs.bib -nocite: '@*' -... - -# Bibliography -""") - - -# Convert to .rst (to process the references) -# Requires recent version of pandoc -# NB: Unfortunately, converting directly to .md -# outputs in a verbose "list-ish" format. -# Note: for CSL styles see https://editor.citationstyles.org/about/ -run( - [ - "pandoc", - "--citeproc", - "--csl=data-science-journal.csl", - "-s", - "bib.md", - "-o", - "bib.rst", - ], - check=True, -) - - -# Parse rst -rst = open("bib.rst").readlines() -linenumbers = [] -REF_START = " :name: ref-" -# Get ref names and line numbers -for lineno, ln in enumerate(rst): - if REF_START in ln: - name = ln[len(REF_START) :].strip() - linenumbers.append((name, lineno)) -# Get ref blocks -ref_dict = {} -for i, (name, lineno1) in enumerate(linenumbers): - try: - lineno2 = linenumbers[i + 1][1] - 1 - except IndexError: - lineno2 = len(rst) - block = rst[lineno1 + 2 : lineno2] - block = dedent("".join(block)).strip() - ref_dict[name] = block - - -# Clean up -Path("bib.rst").unlink() -Path("bib.md").unlink() - -# Sort -# ref_dict = dict(sorted(ref_dict.items())) - -# Write bib.py -with open("bib.py", "w") as bibfile: - - def _print(*a, **b): - print(*a, **b, file=bibfile) - - _print('"""Bibliography/references."""') - for key, block in ref_dict.items(): - _print("") - _print(key, "=", "None") - _print('"""') - _print(block) - _print('"""') diff --git a/docs/bib/refs.bib b/docs/bib/refs.bib index 3fb8cc2a..856f809a 100644 --- a/docs/bib/refs.bib +++ b/docs/bib/refs.bib @@ -412,10 +412,15 @@ @article{rainwater2013mixed } @article{counillon2009application, - title={Application of a hybrid EnKF-OI to ocean forecasting}, - author={Counillon, Fran{\c{c}}ois and Sakov, Pavel and Bertino, Laurent}, - year={2009}, - publisher={Copernicus Publications on behalf of the European Geosciences Union} + title={Application of a hybrid `EnKF-OI` to ocean forecasting}, + author={Counillon, Fran{\c{c}}ois and Sakov, Pavel and Bertino, Laurent}, + journal={Ocean Science}, + file={~/P/Refs/articles/counillon2009application.pdf}, + volume={5}, + number={4}, + pages={389--401}, + year={2009}, + publisher={Copernicus GmbH} } @article{pajonk2012deterministic, diff --git a/docs/dev_guide.md b/docs/dev_guide.md index 718d28b2..d2f9f5a1 100644 --- a/docs/dev_guide.md +++ b/docs/dev_guide.md @@ -1,3 +1,8 @@ +--- +hide: +- navigation +--- + # Developer guide ## Conventions @@ -80,29 +85,7 @@ For detailed linting messages, run ruff check --output-format=concise ``` -.. warning:: Obsolete - Kept for reference. - You may also want to display linting issues in your editor as you code. -Below is a suggested configuration of VS-Code with the pylance plug-in -or Vim (with the coc.nvim plug-in with the pyright extension) - -```jsonc -// Put this in settings.json (VS-Code) or ~/.vim/coc-settings.json (For Vim) -{ - "python.analysis.typeCheckingMode": "off", - "python.analysis.useLibraryCodeForTypes": true, - "python.analysis.extraPaths": ["scripts"], - "python.formatting.provider": "autopep8", - "python.formatting.autopep8Path": "~/.local/bin/autopep8", - "python.linting.enabled": true, - "python.linting.flake8Enabled": true, - "python.linting.flake8Args": ["lint"], - "python.linting.flake8Path": "${env:CONDA_PREFIX}/bin/flakeheaven", - // Without VENV (requires `pip3 install --user flakeheaven flake8-docstrings flake8-bugbear ...`) - // "python.linting.flake8Path": "[USE PATH PRINTED BY PIP ABOVE]/Python/3.8/bin/flakeheaven", -} -``` ## Adding to the examples @@ -111,15 +94,15 @@ as showcasing some feature, new examples should make sure to reproduce some published literature results. After making the example, consider converting the script to the Jupyter notebook format (or vice versa) so that the example can be run on Colab without users needing to install anything (see -`examples/README.md`). This should be done using the `jupytext` plug-in (with +`docs/examples/README.md`). This should be done using the `jupytext` plug-in (with the `lightscript` format), so that the paired files can be kept in synch. ## Documentation -The documentation is built with `pdoc`, e.g. +The documentation is built with `mkdocs`, e.g. ```sh -pdoc -t docs/templates --math --docformat=numpy docs/bib/bib.py docs/dev_guide.py ./dapper +mkdocs build # use `serve` instead for live preview with hot-reloading ``` ##### Hosting @@ -134,8 +117,9 @@ creates an artefact that is deployed to Github Pages. In order to add new references, insert their bibtex into `docs/bib/refs.bib`, -then run `docs/bib/make_bib.py`, -which will format and add entries to `docs/bib/bib.py`. +then run `docs/bib/bib2md.py` +which will format and add entries to `docs/references.md` +that can be cited with regular cross-reference syntax, e.g. `[bocquet2010beyond][]`. ## Profiling diff --git a/docs/dev_guide.py b/docs/dev_guide.py deleted file mode 100644 index 5b637555..00000000 --- a/docs/dev_guide.py +++ /dev/null @@ -1,3 +0,0 @@ -""" -.. include:: ./dev_guide.md -""" diff --git a/examples/README.md b/docs/examples/README.md similarity index 93% rename from examples/README.md rename to docs/examples/README.md index f4aac7a9..1cdbca2b 100644 --- a/examples/README.md +++ b/docs/examples/README.md @@ -3,7 +3,7 @@ Here are some example scripts using DAPPER. They all consist of one (or more) synthetic experiments. -Run them using `python examples/the_script.py`, +Run them using `python docs/examples/the_script.py`, or with the `%run` command inside `ipython`. Some of the scripts have also been converted to Jupyter notebooks (`.ipynb`). @@ -14,7 +14,7 @@ and that it requires a Google login): [![Open In Collab](https://colab.research. ## Description When adapting the scripts to your needs, -you should begin with `examples/basic_1.py` +you should begin with `basic_1.py` before incorporating the aspects of `basic_2` and `basic_3`. - `basic_1.py`: A single experiment, with Liveplotting. diff --git a/docs/examples/basic_1.ipynb b/docs/examples/basic_1.ipynb new file mode 100644 index 00000000..c635f289 --- /dev/null +++ b/docs/examples/basic_1.ipynb @@ -0,0 +1,7446 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "3c5106d3", + "metadata": {}, + "source": [ + "## A single, interactive, synthetic (\"twin\") experiment\n", + "If run as a script in a terminal (i.e. not as a jupyter notebook)\n", + "then the liveplotting is *interactive* (can be paused, skipped, etc)." + ] + }, + { + "cell_type": "markdown", + "id": "fe5f6628", + "metadata": {}, + "source": [ + "### Imports\n", + "NB: On Gooble Colab,\n", + "*replace* `%matplotlib notebook` (right below) by\\\n", + "`!python -m pip install git+https://github.com/nansencenter/DAPPER.git`" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "89f5e089", + "metadata": {}, + "outputs": [], + "source": [ + "%matplotlib notebook" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "fc681615", + "metadata": {}, + "outputs": [], + "source": [ + "import dapper as dpr\n", + "import dapper.da_methods as da" + ] + }, + { + "cell_type": "markdown", + "id": "7afbdeb6", + "metadata": {}, + "source": [ + "Generate the same random numbers each time this script is run:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "0e74274d", + "metadata": {}, + "outputs": [], + "source": [ + "dpr.set_seed(3000);" + ] + }, + { + "cell_type": "markdown", + "id": "1957d07f", + "metadata": {}, + "source": [ + "### Load experiment setup: the hidden Markov model (HMM)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "8b272589", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "HiddenMarkovModel({\n", + " 'Dyn': \n", + " Operator({\n", + " 'M': 3,\n", + " 'model':\n", + " rk4 integration of \n", + " .step at 0x119028720>,\n", + " 'noise':\n", + " GaussRV({\n", + " 'mu': array([0., 0., 0.]),\n", + " 'M': 3,\n", + " 'C': 0\n", + " }),\n", + " 'linear': \n", + " }),\n", + " 'Obs': \n", + " CONSTANT operator sepcified by .Op1:\n", + " Operator({\n", + " 'M': 3,\n", + " 'model':\n", + " Direct obs. at [0 1 2]\n", + " .model at 0x119028c20>,\n", + " 'noise':\n", + " GaussRV({\n", + " 'C': \n", + " M: 3\n", + " kind: 'diag'\n", + " trunc: 1.0\n", + " rk: 3\n", + " full:\n", + " [[2. 0. 0.]\n", + " [0. 2. 0.]\n", + " [0. 0. 2.]]\n", + " diag:\n", + " [2. 2. 2.],\n", + " 'mu': array([0., 0., 0.]),\n", + " 'M': 3\n", + " }),\n", + " 'linear':\n", + " Constant matrix\n", + " [[1. 0. 0.]\n", + " [0. 1. 0.]\n", + " [0. 0. 1.]]\n", + " .linear at 0x119028e00>\n", + " }),\n", + " 'tseq': \n", + " \n", + " - K: 25025\n", + " - Ko: 1000\n", + " - T: 250.25\n", + " - BurnIn: 16.0\n", + " - dto: 0.25\n", + " - dt: 0.01,\n", + " 'X0': \n", + " GaussRV({\n", + " 'C': \n", + " M: 3\n", + " kind: 'diag'\n", + " trunc: 1.0\n", + " rk: 3\n", + " full:\n", + " [[2. 0. 0.]\n", + " [0. 2. 0.]\n", + " [0. 0. 2.]]\n", + " diag:\n", + " [2. 2. 2.],\n", + " 'mu': array([ 1.509, -1.531, 25.46 ]),\n", + " 'M': 3\n", + " }),\n", + " 'liveplotters': \n", + " [(1, ),\n", + " (1, .init at\n", + " 0x11903c720>), (1, .init\n", + " at 0x11903c5e0>)],\n", + " 'sectors': {},\n", + " 'name': 'Lorenz63/sakov2012.py'\n", + " })" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from dapper.mods.Lorenz63.sakov2012 import HMM\n", + "HMM # ⇒ printout (in notebooks)" + ] + }, + { + "cell_type": "markdown", + "id": "2ce9b72c", + "metadata": {}, + "source": [ + "### Simulate synthetic truth (xx) and noisy obs (yy)\n", + "A variable named `` conventionally refers to a *time series* of ``." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "c54bcca2", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Truth & Obs: 100%|█████████████| 3000/3000 [00:00<00:00, 42197.63it/s]\n" + ] + } + ], + "source": [ + "HMM.tseq.T = 30 # shorten experiment\n", + "xx, yy = HMM.simulate()" + ] + }, + { + "cell_type": "markdown", + "id": "3c40799e", + "metadata": {}, + "source": [ + "### Specify a DA method\n", + "Here \"xp\" is short for \"experiment\" configuration." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "b39ebe02", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "EnKF(upd_a='Sqrt', N=10, infl=1.02, rot=True, fnoise_treatm='Stoch')" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# xp = da.OptInterp()\n", + "# xp = da.Var3D()\n", + "# xp = da.ExtKF(infl=90)\n", + "xp = da.EnKF(\"Sqrt\", N=10, infl=1.02, rot=True)\n", + "# xp = da.PartFilt(N=100, reg=2.4, NER=0.3)\n", + "xp # ⇒ printout (in notebooks)" + ] + }, + { + "cell_type": "markdown", + "id": "08f20322", + "metadata": {}, + "source": [ + "### Assimilate yy\n", + "Note that the assimilation \"knows\" the HMM,\n", + "but `xx` is only used for performance *assessment*." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "0a0ea6ff", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "EnKF: 100%|████████████████████| 3000/3000 [00:00<00:00, 14291.97it/s]\n" + ] + } + ], + "source": [ + "xp.assimilate(HMM, xx, yy)" + ] + }, + { + "cell_type": "markdown", + "id": "18ff2a15", + "metadata": {}, + "source": [ + "### Average the time series\n", + "Computes a set of different statistics." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "a7f6fb40", + "metadata": {}, + "outputs": [], + "source": [ + "# print(xp.stats) # ⇒ long printout\n", + "xp.stats.average_in_time()" + ] + }, + { + "cell_type": "markdown", + "id": "842ad66b", + "metadata": {}, + "source": [ + "Print some of these time-averages" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "cc4e9955", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "rmse.a 1σ rmv.a 1σ \n", + "------------ -----------\n", + " 0.78 ±0.06 0.57 ±0.07\n" + ] + } + ], + "source": [ + "# print(xp.avrgs) # ⇒ long printout\n", + "print(xp.avrgs.tabulate([\"rmse.a\", \"rmv.a\"]))" + ] + }, + { + "cell_type": "markdown", + "id": "f130c09b", + "metadata": {}, + "source": [ + "### Replay liveplotters" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "a7ebf5e6", + "metadata": { + "scrolled": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Initializing liveplots...\n" + ] + }, + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "/* global mpl */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function () {\n", + " if (typeof WebSocket !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof MozWebSocket !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert(\n", + " 'Your browser does not have WebSocket support. ' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.'\n", + " );\n", + " }\n", + "};\n", + "\n", + "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = this.ws.binaryType !== undefined;\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById('mpl-warnings');\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent =\n", + " 'This browser does not support binary websocket messages. ' +\n", + " 'Performance may be slow.';\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = document.createElement('div');\n", + " this.root.setAttribute('style', 'display: inline-block');\n", + " this._root_extra_style(this.root);\n", + "\n", + " parent_element.appendChild(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message('supports_binary', { value: fig.supports_binary });\n", + " fig.send_message('send_image_mode', {});\n", + " if (fig.ratio !== 1) {\n", + " fig.send_message('set_device_pixel_ratio', {\n", + " device_pixel_ratio: fig.ratio,\n", + " });\n", + " }\n", + " fig.send_message('refresh', {});\n", + " };\n", + "\n", + " this.imageObj.onload = function () {\n", + " if (fig.image_mode === 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function () {\n", + " fig.ws.close();\n", + " };\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "};\n", + "\n", + "mpl.figure.prototype._init_header = function () {\n", + " var titlebar = document.createElement('div');\n", + " titlebar.classList =\n", + " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", + " var titletext = document.createElement('div');\n", + " titletext.classList = 'ui-dialog-title';\n", + " titletext.setAttribute(\n", + " 'style',\n", + " 'width: 100%; text-align: center; padding: 3px;'\n", + " );\n", + " titlebar.appendChild(titletext);\n", + " this.root.appendChild(titlebar);\n", + " this.header = titletext;\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._init_canvas = function () {\n", + " var fig = this;\n", + "\n", + " var canvas_div = (this.canvas_div = document.createElement('div'));\n", + " canvas_div.setAttribute('tabindex', '0');\n", + " canvas_div.setAttribute(\n", + " 'style',\n", + " 'border: 1px solid #ddd;' +\n", + " 'box-sizing: content-box;' +\n", + " 'clear: both;' +\n", + " 'min-height: 1px;' +\n", + " 'min-width: 1px;' +\n", + " 'outline: 0;' +\n", + " 'overflow: hidden;' +\n", + " 'position: relative;' +\n", + " 'resize: both;' +\n", + " 'z-index: 2;'\n", + " );\n", + "\n", + " function on_keyboard_event_closure(name) {\n", + " return function (event) {\n", + " return fig.key_event(event, name);\n", + " };\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'keydown',\n", + " on_keyboard_event_closure('key_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'keyup',\n", + " on_keyboard_event_closure('key_release')\n", + " );\n", + "\n", + " this._canvas_extra_style(canvas_div);\n", + " this.root.appendChild(canvas_div);\n", + "\n", + " var canvas = (this.canvas = document.createElement('canvas'));\n", + " canvas.classList.add('mpl-canvas');\n", + " canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box;' +\n", + " 'pointer-events: none;' +\n", + " 'position: relative;' +\n", + " 'z-index: 0;'\n", + " );\n", + "\n", + " this.context = canvas.getContext('2d');\n", + "\n", + " var backingStore =\n", + " this.context.backingStorePixelRatio ||\n", + " this.context.webkitBackingStorePixelRatio ||\n", + " this.context.mozBackingStorePixelRatio ||\n", + " this.context.msBackingStorePixelRatio ||\n", + " this.context.oBackingStorePixelRatio ||\n", + " this.context.backingStorePixelRatio ||\n", + " 1;\n", + "\n", + " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", + " 'canvas'\n", + " ));\n", + " rubberband_canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box;' +\n", + " 'left: 0;' +\n", + " 'pointer-events: none;' +\n", + " 'position: absolute;' +\n", + " 'top: 0;' +\n", + " 'z-index: 1;'\n", + " );\n", + "\n", + " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n", + " if (this.ResizeObserver === undefined) {\n", + " if (window.ResizeObserver !== undefined) {\n", + " this.ResizeObserver = window.ResizeObserver;\n", + " } else {\n", + " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n", + " this.ResizeObserver = obs.ResizeObserver;\n", + " }\n", + " }\n", + "\n", + " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n", + " var nentries = entries.length;\n", + " for (var i = 0; i < nentries; i++) {\n", + " var entry = entries[i];\n", + " var width, height;\n", + " if (entry.contentBoxSize) {\n", + " if (entry.contentBoxSize instanceof Array) {\n", + " // Chrome 84 implements new version of spec.\n", + " width = entry.contentBoxSize[0].inlineSize;\n", + " height = entry.contentBoxSize[0].blockSize;\n", + " } else {\n", + " // Firefox implements old version of spec.\n", + " width = entry.contentBoxSize.inlineSize;\n", + " height = entry.contentBoxSize.blockSize;\n", + " }\n", + " } else {\n", + " // Chrome <84 implements even older version of spec.\n", + " width = entry.contentRect.width;\n", + " height = entry.contentRect.height;\n", + " }\n", + "\n", + " // Keep the size of the canvas and rubber band canvas in sync with\n", + " // the canvas container.\n", + " if (entry.devicePixelContentBoxSize) {\n", + " // Chrome 84 implements new version of spec.\n", + " canvas.setAttribute(\n", + " 'width',\n", + " entry.devicePixelContentBoxSize[0].inlineSize\n", + " );\n", + " canvas.setAttribute(\n", + " 'height',\n", + " entry.devicePixelContentBoxSize[0].blockSize\n", + " );\n", + " } else {\n", + " canvas.setAttribute('width', width * fig.ratio);\n", + " canvas.setAttribute('height', height * fig.ratio);\n", + " }\n", + " /* This rescales the canvas back to display pixels, so that it\n", + " * appears correct on HiDPI screens. */\n", + " canvas.style.width = width + 'px';\n", + " canvas.style.height = height + 'px';\n", + "\n", + " rubberband_canvas.setAttribute('width', width);\n", + " rubberband_canvas.setAttribute('height', height);\n", + "\n", + " // And update the size in Python. We ignore the initial 0/0 size\n", + " // that occurs as the element is placed into the DOM, which should\n", + " // otherwise not happen due to the minimum size styling.\n", + " if (fig.ws.readyState == 1 && width != 0 && height != 0) {\n", + " fig.request_resize(width, height);\n", + " }\n", + " }\n", + " });\n", + " this.resizeObserverInstance.observe(canvas_div);\n", + "\n", + " function on_mouse_event_closure(name) {\n", + " /* User Agent sniffing is bad, but WebKit is busted:\n", + " * https://bugs.webkit.org/show_bug.cgi?id=144526\n", + " * https://bugs.webkit.org/show_bug.cgi?id=181818\n", + " * The worst that happens here is that they get an extra browser\n", + " * selection when dragging, if this check fails to catch them.\n", + " */\n", + " var UA = navigator.userAgent;\n", + " var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n", + " if(isWebKit) {\n", + " return function (event) {\n", + " /* This prevents the web browser from automatically changing to\n", + " * the text insertion cursor when the button is pressed. We\n", + " * want to control all of the cursor setting manually through\n", + " * the 'cursor' event from matplotlib */\n", + " event.preventDefault()\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " } else {\n", + " return function (event) {\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " }\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'mousedown',\n", + " on_mouse_event_closure('button_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'mouseup',\n", + " on_mouse_event_closure('button_release')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'dblclick',\n", + " on_mouse_event_closure('dblclick')\n", + " );\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " canvas_div.addEventListener(\n", + " 'mousemove',\n", + " on_mouse_event_closure('motion_notify')\n", + " );\n", + "\n", + " canvas_div.addEventListener(\n", + " 'mouseenter',\n", + " on_mouse_event_closure('figure_enter')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'mouseleave',\n", + " on_mouse_event_closure('figure_leave')\n", + " );\n", + "\n", + " canvas_div.addEventListener('wheel', function (event) {\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " on_mouse_event_closure('scroll')(event);\n", + " });\n", + "\n", + " canvas_div.appendChild(canvas);\n", + " canvas_div.appendChild(rubberband_canvas);\n", + "\n", + " this.rubberband_context = rubberband_canvas.getContext('2d');\n", + " this.rubberband_context.strokeStyle = '#000000';\n", + "\n", + " this._resize_canvas = function (width, height, forward) {\n", + " if (forward) {\n", + " canvas_div.style.width = width + 'px';\n", + " canvas_div.style.height = height + 'px';\n", + " }\n", + " };\n", + "\n", + " // Disable right mouse context menu.\n", + " canvas_div.addEventListener('contextmenu', function (_e) {\n", + " event.preventDefault();\n", + " return false;\n", + " });\n", + "\n", + " function set_focus() {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'mpl-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " continue;\n", + " }\n", + "\n", + " var button = (fig.buttons[name] = document.createElement('button'));\n", + " button.classList = 'mpl-widget';\n", + " button.setAttribute('role', 'button');\n", + " button.setAttribute('aria-disabled', 'false');\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + "\n", + " var icon_img = document.createElement('img');\n", + " icon_img.src = '_images/' + image + '.png';\n", + " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", + " icon_img.alt = tooltip;\n", + " button.appendChild(icon_img);\n", + "\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " var fmt_picker = document.createElement('select');\n", + " fmt_picker.classList = 'mpl-widget';\n", + " toolbar.appendChild(fmt_picker);\n", + " this.format_dropdown = fmt_picker;\n", + "\n", + " for (var ind in mpl.extensions) {\n", + " var fmt = mpl.extensions[ind];\n", + " var option = document.createElement('option');\n", + " option.selected = fmt === mpl.default_extension;\n", + " option.innerHTML = fmt;\n", + " fmt_picker.appendChild(option);\n", + " }\n", + "\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "};\n", + "\n", + "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", + " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", + " // which will in turn request a refresh of the image.\n", + " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", + "};\n", + "\n", + "mpl.figure.prototype.send_message = function (type, properties) {\n", + " properties['type'] = type;\n", + " properties['figure_id'] = this.id;\n", + " this.ws.send(JSON.stringify(properties));\n", + "};\n", + "\n", + "mpl.figure.prototype.send_draw_message = function () {\n", + " if (!this.waiting) {\n", + " this.waiting = true;\n", + " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " var format_dropdown = fig.format_dropdown;\n", + " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", + " fig.ondownload(fig, format);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", + " var size = msg['size'];\n", + " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", + " fig._resize_canvas(size[0], size[1], msg['forward']);\n", + " fig.send_message('refresh', {});\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", + " var x0 = msg['x0'] / fig.ratio;\n", + " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n", + " var x1 = msg['x1'] / fig.ratio;\n", + " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n", + " x0 = Math.floor(x0) + 0.5;\n", + " y0 = Math.floor(y0) + 0.5;\n", + " x1 = Math.floor(x1) + 0.5;\n", + " y1 = Math.floor(y1) + 0.5;\n", + " var min_x = Math.min(x0, x1);\n", + " var min_y = Math.min(y0, y1);\n", + " var width = Math.abs(x1 - x0);\n", + " var height = Math.abs(y1 - y0);\n", + "\n", + " fig.rubberband_context.clearRect(\n", + " 0,\n", + " 0,\n", + " fig.canvas.width / fig.ratio,\n", + " fig.canvas.height / fig.ratio\n", + " );\n", + "\n", + " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", + " // Updates the figure title.\n", + " fig.header.textContent = msg['label'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", + " fig.canvas_div.style.cursor = msg['cursor'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_message = function (fig, msg) {\n", + " fig.message.textContent = msg['message'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", + " // Request the server to send over a new figure.\n", + " fig.send_draw_message();\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", + " fig.image_mode = msg['mode'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", + " for (var key in msg) {\n", + " if (!(key in fig.buttons)) {\n", + " continue;\n", + " }\n", + " fig.buttons[key].disabled = !msg[key];\n", + " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", + " if (msg['mode'] === 'PAN') {\n", + " fig.buttons['Pan'].classList.add('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " } else if (msg['mode'] === 'ZOOM') {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.add('active');\n", + " } else {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Called whenever the canvas gets updated.\n", + " this.send_message('ack', {});\n", + "};\n", + "\n", + "// A function to construct a web socket function for onmessage handling.\n", + "// Called in the figure constructor.\n", + "mpl.figure.prototype._make_on_message_function = function (fig) {\n", + " return function socket_on_message(evt) {\n", + " if (evt.data instanceof Blob) {\n", + " var img = evt.data;\n", + " if (img.type !== 'image/png') {\n", + " /* FIXME: We get \"Resource interpreted as Image but\n", + " * transferred with MIME type text/plain:\" errors on\n", + " * Chrome. But how to set the MIME type? It doesn't seem\n", + " * to be part of the websocket stream */\n", + " img.type = 'image/png';\n", + " }\n", + "\n", + " /* Free the memory for the previous frames */\n", + " if (fig.imageObj.src) {\n", + " (window.URL || window.webkitURL).revokeObjectURL(\n", + " fig.imageObj.src\n", + " );\n", + " }\n", + "\n", + " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", + " img\n", + " );\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " } else if (\n", + " typeof evt.data === 'string' &&\n", + " evt.data.slice(0, 21) === 'data:image/png;base64'\n", + " ) {\n", + " fig.imageObj.src = evt.data;\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " }\n", + "\n", + " var msg = JSON.parse(evt.data);\n", + " var msg_type = msg['type'];\n", + "\n", + " // Call the \"handle_{type}\" callback, which takes\n", + " // the figure and JSON message as its only arguments.\n", + " try {\n", + " var callback = fig['handle_' + msg_type];\n", + " } catch (e) {\n", + " console.log(\n", + " \"No handler for the '\" + msg_type + \"' message type: \",\n", + " msg\n", + " );\n", + " return;\n", + " }\n", + "\n", + " if (callback) {\n", + " try {\n", + " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", + " callback(fig, msg);\n", + " } catch (e) {\n", + " console.log(\n", + " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", + " e,\n", + " e.stack,\n", + " msg\n", + " );\n", + " }\n", + " }\n", + " };\n", + "};\n", + "\n", + "function getModifiers(event) {\n", + " var mods = [];\n", + " if (event.ctrlKey) {\n", + " mods.push('ctrl');\n", + " }\n", + " if (event.altKey) {\n", + " mods.push('alt');\n", + " }\n", + " if (event.shiftKey) {\n", + " mods.push('shift');\n", + " }\n", + " if (event.metaKey) {\n", + " mods.push('meta');\n", + " }\n", + " return mods;\n", + "}\n", + "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * https://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys(original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object') {\n", + " obj[key] = original[key];\n", + " }\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", + "mpl.figure.prototype.mouse_event = function (event, name) {\n", + " if (name === 'button_press') {\n", + " this.canvas.focus();\n", + " this.canvas_div.focus();\n", + " }\n", + "\n", + " // from https://stackoverflow.com/q/1114465\n", + " var boundingRect = this.canvas.getBoundingClientRect();\n", + " var x = (event.clientX - boundingRect.left) * this.ratio;\n", + " var y = (event.clientY - boundingRect.top) * this.ratio;\n", + "\n", + " this.send_message(name, {\n", + " x: x,\n", + " y: y,\n", + " button: event.button,\n", + " step: event.step,\n", + " modifiers: getModifiers(event),\n", + " guiEvent: simpleKeys(event),\n", + " });\n", + "\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", + " // Handle any extra behaviour associated with a key event\n", + "};\n", + "\n", + "mpl.figure.prototype.key_event = function (event, name) {\n", + " // Prevent repeat events\n", + " if (name === 'key_press') {\n", + " if (event.key === this._key) {\n", + " return;\n", + " } else {\n", + " this._key = event.key;\n", + " }\n", + " }\n", + " if (name === 'key_release') {\n", + " this._key = null;\n", + " }\n", + "\n", + " var value = '';\n", + " if (event.ctrlKey && event.key !== 'Control') {\n", + " value += 'ctrl+';\n", + " }\n", + " else if (event.altKey && event.key !== 'Alt') {\n", + " value += 'alt+';\n", + " }\n", + " else if (event.shiftKey && event.key !== 'Shift') {\n", + " value += 'shift+';\n", + " }\n", + "\n", + " value += 'k' + event.key;\n", + "\n", + " this._key_event_extra(event, name);\n", + "\n", + " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", + " if (name === 'download') {\n", + " this.handle_save(this, null);\n", + " } else {\n", + " this.send_message('toolbar_button', { name: name });\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", + " this.message.textContent = tooltip;\n", + "};\n", + "\n", + "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n", + "// prettier-ignore\n", + "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n", + "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n", + "\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n", + "\n", + "mpl.default_extension = \"png\";/* global mpl */\n", + "\n", + "var comm_websocket_adapter = function (comm) {\n", + " // Create a \"websocket\"-like object which calls the given IPython comm\n", + " // object with the appropriate methods. Currently this is a non binary\n", + " // socket, so there is still some room for performance tuning.\n", + " var ws = {};\n", + "\n", + " ws.binaryType = comm.kernel.ws.binaryType;\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " function updateReadyState(_event) {\n", + " if (comm.kernel.ws) {\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " } else {\n", + " ws.readyState = 3; // Closed state.\n", + " }\n", + " }\n", + " comm.kernel.ws.addEventListener('open', updateReadyState);\n", + " comm.kernel.ws.addEventListener('close', updateReadyState);\n", + " comm.kernel.ws.addEventListener('error', updateReadyState);\n", + "\n", + " ws.close = function () {\n", + " comm.close();\n", + " };\n", + " ws.send = function (m) {\n", + " //console.log('sending', m);\n", + " comm.send(m);\n", + " };\n", + " // Register the callback with on_msg.\n", + " comm.on_msg(function (msg) {\n", + " //console.log('receiving', msg['content']['data'], msg);\n", + " var data = msg['content']['data'];\n", + " if (data['blob'] !== undefined) {\n", + " data = {\n", + " data: new Blob(msg['buffers'], { type: data['blob'] }),\n", + " };\n", + " }\n", + " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", + " ws.onmessage(data);\n", + " });\n", + " return ws;\n", + "};\n", + "\n", + "mpl.mpl_figure_comm = function (comm, msg) {\n", + " // This is the function which gets called when the mpl process\n", + " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", + "\n", + " var id = msg.content.data.id;\n", + " // Get hold of the div created by the display call when the Comm\n", + " // socket was opened in Python.\n", + " var element = document.getElementById(id);\n", + " var ws_proxy = comm_websocket_adapter(comm);\n", + "\n", + " function ondownload(figure, _format) {\n", + " window.open(figure.canvas.toDataURL());\n", + " }\n", + "\n", + " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", + "\n", + " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", + " // web socket which is closed, not our websocket->open comm proxy.\n", + " ws_proxy.onopen();\n", + "\n", + " fig.parent_element = element;\n", + " fig.cell_info = mpl.find_output_cell(\"
\");\n", + " if (!fig.cell_info) {\n", + " console.error('Failed to find cell for figure', id, fig);\n", + " return;\n", + " }\n", + " fig.cell_info[0].output_area.element.on(\n", + " 'cleared',\n", + " { fig: fig },\n", + " fig._remove_fig_handler\n", + " );\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_close = function (fig, msg) {\n", + " var width = fig.canvas.width / fig.ratio;\n", + " fig.cell_info[0].output_area.element.off(\n", + " 'cleared',\n", + " fig._remove_fig_handler\n", + " );\n", + " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n", + "\n", + " // Update the output cell to use the data from the current canvas.\n", + " fig.push_to_output();\n", + " var dataURL = fig.canvas.toDataURL();\n", + " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", + " // the notebook keyboard shortcuts fail.\n", + " IPython.keyboard_manager.enable();\n", + " fig.parent_element.innerHTML =\n", + " '';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", + " // Turn the data on the canvas into data in the output cell.\n", + " var width = this.canvas.width / this.ratio;\n", + " var dataURL = this.canvas.toDataURL();\n", + " this.cell_info[1]['text/html'] =\n", + " '';\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Tell IPython that the notebook contents must change.\n", + " IPython.notebook.set_dirty(true);\n", + " this.send_message('ack', {});\n", + " var fig = this;\n", + " // Wait a second, then push the new image to the DOM so\n", + " // that it is saved nicely (might be nice to debounce this).\n", + " setTimeout(function () {\n", + " fig.push_to_output();\n", + " }, 1000);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'btn-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " var button;\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " continue;\n", + " }\n", + "\n", + " button = fig.buttons[name] = document.createElement('button');\n", + " button.classList = 'btn btn-default';\n", + " button.href = '#';\n", + " button.title = name;\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message pull-right';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = document.createElement('div');\n", + " buttongrp.classList = 'btn-group inline pull-right';\n", + " button = document.createElement('button');\n", + " button.classList = 'btn btn-mini btn-primary';\n", + " button.href = '#';\n", + " button.title = 'Stop Interaction';\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', function (_evt) {\n", + " fig.handle_close(fig, {});\n", + " });\n", + " button.addEventListener(\n", + " 'mouseover',\n", + " on_mouseover_closure('Stop Interaction')\n", + " );\n", + " buttongrp.appendChild(button);\n", + " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", + " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", + "};\n", + "\n", + "mpl.figure.prototype._remove_fig_handler = function (event) {\n", + " var fig = event.data.fig;\n", + " if (event.target !== this) {\n", + " // Ignore bubbled events from children.\n", + " return;\n", + " }\n", + " fig.close_ws(fig, {});\n", + "};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (el) {\n", + " // this is important to make the div 'focusable\n", + " el.setAttribute('tabindex', 0);\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " } else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which === 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " fig.ondownload(fig, null);\n", + "};\n", + "\n", + "mpl.find_output_cell = function (html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i = 0; i < ncells; i++) {\n", + " var cell = cells[i];\n", + " if (cell.cell_type === 'code') {\n", + " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", + " var data = cell.output_area.outputs[j];\n", + " if (data.data) {\n", + " // IPython >= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] === html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "};\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel !== null) {\n", + " IPython.notebook.kernel.comm_manager.register_target(\n", + " 'matplotlib',\n", + " mpl.mpl_figure_comm\n", + " );\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "/* global mpl */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function () {\n", + " if (typeof WebSocket !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof MozWebSocket !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert(\n", + " 'Your browser does not have WebSocket support. ' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.'\n", + " );\n", + " }\n", + "};\n", + "\n", + "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = this.ws.binaryType !== undefined;\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById('mpl-warnings');\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent =\n", + " 'This browser does not support binary websocket messages. ' +\n", + " 'Performance may be slow.';\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = document.createElement('div');\n", + " this.root.setAttribute('style', 'display: inline-block');\n", + " this._root_extra_style(this.root);\n", + "\n", + " parent_element.appendChild(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message('supports_binary', { value: fig.supports_binary });\n", + " fig.send_message('send_image_mode', {});\n", + " if (fig.ratio !== 1) {\n", + " fig.send_message('set_device_pixel_ratio', {\n", + " device_pixel_ratio: fig.ratio,\n", + " });\n", + " }\n", + " fig.send_message('refresh', {});\n", + " };\n", + "\n", + " this.imageObj.onload = function () {\n", + " if (fig.image_mode === 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function () {\n", + " fig.ws.close();\n", + " };\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "};\n", + "\n", + "mpl.figure.prototype._init_header = function () {\n", + " var titlebar = document.createElement('div');\n", + " titlebar.classList =\n", + " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", + " var titletext = document.createElement('div');\n", + " titletext.classList = 'ui-dialog-title';\n", + " titletext.setAttribute(\n", + " 'style',\n", + " 'width: 100%; text-align: center; padding: 3px;'\n", + " );\n", + " titlebar.appendChild(titletext);\n", + " this.root.appendChild(titlebar);\n", + " this.header = titletext;\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._init_canvas = function () {\n", + " var fig = this;\n", + "\n", + " var canvas_div = (this.canvas_div = document.createElement('div'));\n", + " canvas_div.setAttribute('tabindex', '0');\n", + " canvas_div.setAttribute(\n", + " 'style',\n", + " 'border: 1px solid #ddd;' +\n", + " 'box-sizing: content-box;' +\n", + " 'clear: both;' +\n", + " 'min-height: 1px;' +\n", + " 'min-width: 1px;' +\n", + " 'outline: 0;' +\n", + " 'overflow: hidden;' +\n", + " 'position: relative;' +\n", + " 'resize: both;' +\n", + " 'z-index: 2;'\n", + " );\n", + "\n", + " function on_keyboard_event_closure(name) {\n", + " return function (event) {\n", + " return fig.key_event(event, name);\n", + " };\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'keydown',\n", + " on_keyboard_event_closure('key_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'keyup',\n", + " on_keyboard_event_closure('key_release')\n", + " );\n", + "\n", + " this._canvas_extra_style(canvas_div);\n", + " this.root.appendChild(canvas_div);\n", + "\n", + " var canvas = (this.canvas = document.createElement('canvas'));\n", + " canvas.classList.add('mpl-canvas');\n", + " canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box;' +\n", + " 'pointer-events: none;' +\n", + " 'position: relative;' +\n", + " 'z-index: 0;'\n", + " );\n", + "\n", + " this.context = canvas.getContext('2d');\n", + "\n", + " var backingStore =\n", + " this.context.backingStorePixelRatio ||\n", + " this.context.webkitBackingStorePixelRatio ||\n", + " this.context.mozBackingStorePixelRatio ||\n", + " this.context.msBackingStorePixelRatio ||\n", + " this.context.oBackingStorePixelRatio ||\n", + " this.context.backingStorePixelRatio ||\n", + " 1;\n", + "\n", + " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", + " 'canvas'\n", + " ));\n", + " rubberband_canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box;' +\n", + " 'left: 0;' +\n", + " 'pointer-events: none;' +\n", + " 'position: absolute;' +\n", + " 'top: 0;' +\n", + " 'z-index: 1;'\n", + " );\n", + "\n", + " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n", + " if (this.ResizeObserver === undefined) {\n", + " if (window.ResizeObserver !== undefined) {\n", + " this.ResizeObserver = window.ResizeObserver;\n", + " } else {\n", + " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n", + " this.ResizeObserver = obs.ResizeObserver;\n", + " }\n", + " }\n", + "\n", + " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n", + " var nentries = entries.length;\n", + " for (var i = 0; i < nentries; i++) {\n", + " var entry = entries[i];\n", + " var width, height;\n", + " if (entry.contentBoxSize) {\n", + " if (entry.contentBoxSize instanceof Array) {\n", + " // Chrome 84 implements new version of spec.\n", + " width = entry.contentBoxSize[0].inlineSize;\n", + " height = entry.contentBoxSize[0].blockSize;\n", + " } else {\n", + " // Firefox implements old version of spec.\n", + " width = entry.contentBoxSize.inlineSize;\n", + " height = entry.contentBoxSize.blockSize;\n", + " }\n", + " } else {\n", + " // Chrome <84 implements even older version of spec.\n", + " width = entry.contentRect.width;\n", + " height = entry.contentRect.height;\n", + " }\n", + "\n", + " // Keep the size of the canvas and rubber band canvas in sync with\n", + " // the canvas container.\n", + " if (entry.devicePixelContentBoxSize) {\n", + " // Chrome 84 implements new version of spec.\n", + " canvas.setAttribute(\n", + " 'width',\n", + " entry.devicePixelContentBoxSize[0].inlineSize\n", + " );\n", + " canvas.setAttribute(\n", + " 'height',\n", + " entry.devicePixelContentBoxSize[0].blockSize\n", + " );\n", + " } else {\n", + " canvas.setAttribute('width', width * fig.ratio);\n", + " canvas.setAttribute('height', height * fig.ratio);\n", + " }\n", + " /* This rescales the canvas back to display pixels, so that it\n", + " * appears correct on HiDPI screens. */\n", + " canvas.style.width = width + 'px';\n", + " canvas.style.height = height + 'px';\n", + "\n", + " rubberband_canvas.setAttribute('width', width);\n", + " rubberband_canvas.setAttribute('height', height);\n", + "\n", + " // And update the size in Python. We ignore the initial 0/0 size\n", + " // that occurs as the element is placed into the DOM, which should\n", + " // otherwise not happen due to the minimum size styling.\n", + " if (fig.ws.readyState == 1 && width != 0 && height != 0) {\n", + " fig.request_resize(width, height);\n", + " }\n", + " }\n", + " });\n", + " this.resizeObserverInstance.observe(canvas_div);\n", + "\n", + " function on_mouse_event_closure(name) {\n", + " /* User Agent sniffing is bad, but WebKit is busted:\n", + " * https://bugs.webkit.org/show_bug.cgi?id=144526\n", + " * https://bugs.webkit.org/show_bug.cgi?id=181818\n", + " * The worst that happens here is that they get an extra browser\n", + " * selection when dragging, if this check fails to catch them.\n", + " */\n", + " var UA = navigator.userAgent;\n", + " var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n", + " if(isWebKit) {\n", + " return function (event) {\n", + " /* This prevents the web browser from automatically changing to\n", + " * the text insertion cursor when the button is pressed. We\n", + " * want to control all of the cursor setting manually through\n", + " * the 'cursor' event from matplotlib */\n", + " event.preventDefault()\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " } else {\n", + " return function (event) {\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " }\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'mousedown',\n", + " on_mouse_event_closure('button_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'mouseup',\n", + " on_mouse_event_closure('button_release')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'dblclick',\n", + " on_mouse_event_closure('dblclick')\n", + " );\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " canvas_div.addEventListener(\n", + " 'mousemove',\n", + " on_mouse_event_closure('motion_notify')\n", + " );\n", + "\n", + " canvas_div.addEventListener(\n", + " 'mouseenter',\n", + " on_mouse_event_closure('figure_enter')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'mouseleave',\n", + " on_mouse_event_closure('figure_leave')\n", + " );\n", + "\n", + " canvas_div.addEventListener('wheel', function (event) {\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " on_mouse_event_closure('scroll')(event);\n", + " });\n", + "\n", + " canvas_div.appendChild(canvas);\n", + " canvas_div.appendChild(rubberband_canvas);\n", + "\n", + " this.rubberband_context = rubberband_canvas.getContext('2d');\n", + " this.rubberband_context.strokeStyle = '#000000';\n", + "\n", + " this._resize_canvas = function (width, height, forward) {\n", + " if (forward) {\n", + " canvas_div.style.width = width + 'px';\n", + " canvas_div.style.height = height + 'px';\n", + " }\n", + " };\n", + "\n", + " // Disable right mouse context menu.\n", + " canvas_div.addEventListener('contextmenu', function (_e) {\n", + " event.preventDefault();\n", + " return false;\n", + " });\n", + "\n", + " function set_focus() {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'mpl-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " continue;\n", + " }\n", + "\n", + " var button = (fig.buttons[name] = document.createElement('button'));\n", + " button.classList = 'mpl-widget';\n", + " button.setAttribute('role', 'button');\n", + " button.setAttribute('aria-disabled', 'false');\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + "\n", + " var icon_img = document.createElement('img');\n", + " icon_img.src = '_images/' + image + '.png';\n", + " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", + " icon_img.alt = tooltip;\n", + " button.appendChild(icon_img);\n", + "\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " var fmt_picker = document.createElement('select');\n", + " fmt_picker.classList = 'mpl-widget';\n", + " toolbar.appendChild(fmt_picker);\n", + " this.format_dropdown = fmt_picker;\n", + "\n", + " for (var ind in mpl.extensions) {\n", + " var fmt = mpl.extensions[ind];\n", + " var option = document.createElement('option');\n", + " option.selected = fmt === mpl.default_extension;\n", + " option.innerHTML = fmt;\n", + " fmt_picker.appendChild(option);\n", + " }\n", + "\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "};\n", + "\n", + "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", + " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", + " // which will in turn request a refresh of the image.\n", + " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", + "};\n", + "\n", + "mpl.figure.prototype.send_message = function (type, properties) {\n", + " properties['type'] = type;\n", + " properties['figure_id'] = this.id;\n", + " this.ws.send(JSON.stringify(properties));\n", + "};\n", + "\n", + "mpl.figure.prototype.send_draw_message = function () {\n", + " if (!this.waiting) {\n", + " this.waiting = true;\n", + " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " var format_dropdown = fig.format_dropdown;\n", + " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", + " fig.ondownload(fig, format);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", + " var size = msg['size'];\n", + " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", + " fig._resize_canvas(size[0], size[1], msg['forward']);\n", + " fig.send_message('refresh', {});\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", + " var x0 = msg['x0'] / fig.ratio;\n", + " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n", + " var x1 = msg['x1'] / fig.ratio;\n", + " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n", + " x0 = Math.floor(x0) + 0.5;\n", + " y0 = Math.floor(y0) + 0.5;\n", + " x1 = Math.floor(x1) + 0.5;\n", + " y1 = Math.floor(y1) + 0.5;\n", + " var min_x = Math.min(x0, x1);\n", + " var min_y = Math.min(y0, y1);\n", + " var width = Math.abs(x1 - x0);\n", + " var height = Math.abs(y1 - y0);\n", + "\n", + " fig.rubberband_context.clearRect(\n", + " 0,\n", + " 0,\n", + " fig.canvas.width / fig.ratio,\n", + " fig.canvas.height / fig.ratio\n", + " );\n", + "\n", + " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", + " // Updates the figure title.\n", + " fig.header.textContent = msg['label'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", + " fig.canvas_div.style.cursor = msg['cursor'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_message = function (fig, msg) {\n", + " fig.message.textContent = msg['message'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", + " // Request the server to send over a new figure.\n", + " fig.send_draw_message();\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", + " fig.image_mode = msg['mode'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", + " for (var key in msg) {\n", + " if (!(key in fig.buttons)) {\n", + " continue;\n", + " }\n", + " fig.buttons[key].disabled = !msg[key];\n", + " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", + " if (msg['mode'] === 'PAN') {\n", + " fig.buttons['Pan'].classList.add('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " } else if (msg['mode'] === 'ZOOM') {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.add('active');\n", + " } else {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Called whenever the canvas gets updated.\n", + " this.send_message('ack', {});\n", + "};\n", + "\n", + "// A function to construct a web socket function for onmessage handling.\n", + "// Called in the figure constructor.\n", + "mpl.figure.prototype._make_on_message_function = function (fig) {\n", + " return function socket_on_message(evt) {\n", + " if (evt.data instanceof Blob) {\n", + " var img = evt.data;\n", + " if (img.type !== 'image/png') {\n", + " /* FIXME: We get \"Resource interpreted as Image but\n", + " * transferred with MIME type text/plain:\" errors on\n", + " * Chrome. But how to set the MIME type? It doesn't seem\n", + " * to be part of the websocket stream */\n", + " img.type = 'image/png';\n", + " }\n", + "\n", + " /* Free the memory for the previous frames */\n", + " if (fig.imageObj.src) {\n", + " (window.URL || window.webkitURL).revokeObjectURL(\n", + " fig.imageObj.src\n", + " );\n", + " }\n", + "\n", + " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", + " img\n", + " );\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " } else if (\n", + " typeof evt.data === 'string' &&\n", + " evt.data.slice(0, 21) === 'data:image/png;base64'\n", + " ) {\n", + " fig.imageObj.src = evt.data;\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " }\n", + "\n", + " var msg = JSON.parse(evt.data);\n", + " var msg_type = msg['type'];\n", + "\n", + " // Call the \"handle_{type}\" callback, which takes\n", + " // the figure and JSON message as its only arguments.\n", + " try {\n", + " var callback = fig['handle_' + msg_type];\n", + " } catch (e) {\n", + " console.log(\n", + " \"No handler for the '\" + msg_type + \"' message type: \",\n", + " msg\n", + " );\n", + " return;\n", + " }\n", + "\n", + " if (callback) {\n", + " try {\n", + " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", + " callback(fig, msg);\n", + " } catch (e) {\n", + " console.log(\n", + " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", + " e,\n", + " e.stack,\n", + " msg\n", + " );\n", + " }\n", + " }\n", + " };\n", + "};\n", + "\n", + "function getModifiers(event) {\n", + " var mods = [];\n", + " if (event.ctrlKey) {\n", + " mods.push('ctrl');\n", + " }\n", + " if (event.altKey) {\n", + " mods.push('alt');\n", + " }\n", + " if (event.shiftKey) {\n", + " mods.push('shift');\n", + " }\n", + " if (event.metaKey) {\n", + " mods.push('meta');\n", + " }\n", + " return mods;\n", + "}\n", + "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * https://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys(original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object') {\n", + " obj[key] = original[key];\n", + " }\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", + "mpl.figure.prototype.mouse_event = function (event, name) {\n", + " if (name === 'button_press') {\n", + " this.canvas.focus();\n", + " this.canvas_div.focus();\n", + " }\n", + "\n", + " // from https://stackoverflow.com/q/1114465\n", + " var boundingRect = this.canvas.getBoundingClientRect();\n", + " var x = (event.clientX - boundingRect.left) * this.ratio;\n", + " var y = (event.clientY - boundingRect.top) * this.ratio;\n", + "\n", + " this.send_message(name, {\n", + " x: x,\n", + " y: y,\n", + " button: event.button,\n", + " step: event.step,\n", + " modifiers: getModifiers(event),\n", + " guiEvent: simpleKeys(event),\n", + " });\n", + "\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", + " // Handle any extra behaviour associated with a key event\n", + "};\n", + "\n", + "mpl.figure.prototype.key_event = function (event, name) {\n", + " // Prevent repeat events\n", + " if (name === 'key_press') {\n", + " if (event.key === this._key) {\n", + " return;\n", + " } else {\n", + " this._key = event.key;\n", + " }\n", + " }\n", + " if (name === 'key_release') {\n", + " this._key = null;\n", + " }\n", + "\n", + " var value = '';\n", + " if (event.ctrlKey && event.key !== 'Control') {\n", + " value += 'ctrl+';\n", + " }\n", + " else if (event.altKey && event.key !== 'Alt') {\n", + " value += 'alt+';\n", + " }\n", + " else if (event.shiftKey && event.key !== 'Shift') {\n", + " value += 'shift+';\n", + " }\n", + "\n", + " value += 'k' + event.key;\n", + "\n", + " this._key_event_extra(event, name);\n", + "\n", + " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", + " if (name === 'download') {\n", + " this.handle_save(this, null);\n", + " } else {\n", + " this.send_message('toolbar_button', { name: name });\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", + " this.message.textContent = tooltip;\n", + "};\n", + "\n", + "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n", + "// prettier-ignore\n", + "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n", + "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n", + "\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n", + "\n", + "mpl.default_extension = \"png\";/* global mpl */\n", + "\n", + "var comm_websocket_adapter = function (comm) {\n", + " // Create a \"websocket\"-like object which calls the given IPython comm\n", + " // object with the appropriate methods. Currently this is a non binary\n", + " // socket, so there is still some room for performance tuning.\n", + " var ws = {};\n", + "\n", + " ws.binaryType = comm.kernel.ws.binaryType;\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " function updateReadyState(_event) {\n", + " if (comm.kernel.ws) {\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " } else {\n", + " ws.readyState = 3; // Closed state.\n", + " }\n", + " }\n", + " comm.kernel.ws.addEventListener('open', updateReadyState);\n", + " comm.kernel.ws.addEventListener('close', updateReadyState);\n", + " comm.kernel.ws.addEventListener('error', updateReadyState);\n", + "\n", + " ws.close = function () {\n", + " comm.close();\n", + " };\n", + " ws.send = function (m) {\n", + " //console.log('sending', m);\n", + " comm.send(m);\n", + " };\n", + " // Register the callback with on_msg.\n", + " comm.on_msg(function (msg) {\n", + " //console.log('receiving', msg['content']['data'], msg);\n", + " var data = msg['content']['data'];\n", + " if (data['blob'] !== undefined) {\n", + " data = {\n", + " data: new Blob(msg['buffers'], { type: data['blob'] }),\n", + " };\n", + " }\n", + " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", + " ws.onmessage(data);\n", + " });\n", + " return ws;\n", + "};\n", + "\n", + "mpl.mpl_figure_comm = function (comm, msg) {\n", + " // This is the function which gets called when the mpl process\n", + " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", + "\n", + " var id = msg.content.data.id;\n", + " // Get hold of the div created by the display call when the Comm\n", + " // socket was opened in Python.\n", + " var element = document.getElementById(id);\n", + " var ws_proxy = comm_websocket_adapter(comm);\n", + "\n", + " function ondownload(figure, _format) {\n", + " window.open(figure.canvas.toDataURL());\n", + " }\n", + "\n", + " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", + "\n", + " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", + " // web socket which is closed, not our websocket->open comm proxy.\n", + " ws_proxy.onopen();\n", + "\n", + " fig.parent_element = element;\n", + " fig.cell_info = mpl.find_output_cell(\"
\");\n", + " if (!fig.cell_info) {\n", + " console.error('Failed to find cell for figure', id, fig);\n", + " return;\n", + " }\n", + " fig.cell_info[0].output_area.element.on(\n", + " 'cleared',\n", + " { fig: fig },\n", + " fig._remove_fig_handler\n", + " );\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_close = function (fig, msg) {\n", + " var width = fig.canvas.width / fig.ratio;\n", + " fig.cell_info[0].output_area.element.off(\n", + " 'cleared',\n", + " fig._remove_fig_handler\n", + " );\n", + " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n", + "\n", + " // Update the output cell to use the data from the current canvas.\n", + " fig.push_to_output();\n", + " var dataURL = fig.canvas.toDataURL();\n", + " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", + " // the notebook keyboard shortcuts fail.\n", + " IPython.keyboard_manager.enable();\n", + " fig.parent_element.innerHTML =\n", + " '';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", + " // Turn the data on the canvas into data in the output cell.\n", + " var width = this.canvas.width / this.ratio;\n", + " var dataURL = this.canvas.toDataURL();\n", + " this.cell_info[1]['text/html'] =\n", + " '';\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Tell IPython that the notebook contents must change.\n", + " IPython.notebook.set_dirty(true);\n", + " this.send_message('ack', {});\n", + " var fig = this;\n", + " // Wait a second, then push the new image to the DOM so\n", + " // that it is saved nicely (might be nice to debounce this).\n", + " setTimeout(function () {\n", + " fig.push_to_output();\n", + " }, 1000);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'btn-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " var button;\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " continue;\n", + " }\n", + "\n", + " button = fig.buttons[name] = document.createElement('button');\n", + " button.classList = 'btn btn-default';\n", + " button.href = '#';\n", + " button.title = name;\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message pull-right';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = document.createElement('div');\n", + " buttongrp.classList = 'btn-group inline pull-right';\n", + " button = document.createElement('button');\n", + " button.classList = 'btn btn-mini btn-primary';\n", + " button.href = '#';\n", + " button.title = 'Stop Interaction';\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', function (_evt) {\n", + " fig.handle_close(fig, {});\n", + " });\n", + " button.addEventListener(\n", + " 'mouseover',\n", + " on_mouseover_closure('Stop Interaction')\n", + " );\n", + " buttongrp.appendChild(button);\n", + " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", + " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", + "};\n", + "\n", + "mpl.figure.prototype._remove_fig_handler = function (event) {\n", + " var fig = event.data.fig;\n", + " if (event.target !== this) {\n", + " // Ignore bubbled events from children.\n", + " return;\n", + " }\n", + " fig.close_ws(fig, {});\n", + "};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (el) {\n", + " // this is important to make the div 'focusable\n", + " el.setAttribute('tabindex', 0);\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " } else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which === 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " fig.ondownload(fig, null);\n", + "};\n", + "\n", + "mpl.find_output_cell = function (html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i = 0; i < ncells; i++) {\n", + " var cell = cells[i];\n", + " if (cell.cell_type === 'code') {\n", + " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", + " var data = cell.output_area.outputs[j];\n", + " if (data.data) {\n", + " // IPython >= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] === html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "};\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel !== null) {\n", + " IPython.notebook.kernel.comm_manager.register_target(\n", + " 'matplotlib',\n", + " mpl.mpl_figure_comm\n", + " );\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "/* global mpl */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function () {\n", + " if (typeof WebSocket !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof MozWebSocket !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert(\n", + " 'Your browser does not have WebSocket support. ' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.'\n", + " );\n", + " }\n", + "};\n", + "\n", + "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = this.ws.binaryType !== undefined;\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById('mpl-warnings');\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent =\n", + " 'This browser does not support binary websocket messages. ' +\n", + " 'Performance may be slow.';\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = document.createElement('div');\n", + " this.root.setAttribute('style', 'display: inline-block');\n", + " this._root_extra_style(this.root);\n", + "\n", + " parent_element.appendChild(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message('supports_binary', { value: fig.supports_binary });\n", + " fig.send_message('send_image_mode', {});\n", + " if (fig.ratio !== 1) {\n", + " fig.send_message('set_device_pixel_ratio', {\n", + " device_pixel_ratio: fig.ratio,\n", + " });\n", + " }\n", + " fig.send_message('refresh', {});\n", + " };\n", + "\n", + " this.imageObj.onload = function () {\n", + " if (fig.image_mode === 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function () {\n", + " fig.ws.close();\n", + " };\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "};\n", + "\n", + "mpl.figure.prototype._init_header = function () {\n", + " var titlebar = document.createElement('div');\n", + " titlebar.classList =\n", + " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", + " var titletext = document.createElement('div');\n", + " titletext.classList = 'ui-dialog-title';\n", + " titletext.setAttribute(\n", + " 'style',\n", + " 'width: 100%; text-align: center; padding: 3px;'\n", + " );\n", + " titlebar.appendChild(titletext);\n", + " this.root.appendChild(titlebar);\n", + " this.header = titletext;\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._init_canvas = function () {\n", + " var fig = this;\n", + "\n", + " var canvas_div = (this.canvas_div = document.createElement('div'));\n", + " canvas_div.setAttribute('tabindex', '0');\n", + " canvas_div.setAttribute(\n", + " 'style',\n", + " 'border: 1px solid #ddd;' +\n", + " 'box-sizing: content-box;' +\n", + " 'clear: both;' +\n", + " 'min-height: 1px;' +\n", + " 'min-width: 1px;' +\n", + " 'outline: 0;' +\n", + " 'overflow: hidden;' +\n", + " 'position: relative;' +\n", + " 'resize: both;' +\n", + " 'z-index: 2;'\n", + " );\n", + "\n", + " function on_keyboard_event_closure(name) {\n", + " return function (event) {\n", + " return fig.key_event(event, name);\n", + " };\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'keydown',\n", + " on_keyboard_event_closure('key_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'keyup',\n", + " on_keyboard_event_closure('key_release')\n", + " );\n", + "\n", + " this._canvas_extra_style(canvas_div);\n", + " this.root.appendChild(canvas_div);\n", + "\n", + " var canvas = (this.canvas = document.createElement('canvas'));\n", + " canvas.classList.add('mpl-canvas');\n", + " canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box;' +\n", + " 'pointer-events: none;' +\n", + " 'position: relative;' +\n", + " 'z-index: 0;'\n", + " );\n", + "\n", + " this.context = canvas.getContext('2d');\n", + "\n", + " var backingStore =\n", + " this.context.backingStorePixelRatio ||\n", + " this.context.webkitBackingStorePixelRatio ||\n", + " this.context.mozBackingStorePixelRatio ||\n", + " this.context.msBackingStorePixelRatio ||\n", + " this.context.oBackingStorePixelRatio ||\n", + " this.context.backingStorePixelRatio ||\n", + " 1;\n", + "\n", + " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", + " 'canvas'\n", + " ));\n", + " rubberband_canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box;' +\n", + " 'left: 0;' +\n", + " 'pointer-events: none;' +\n", + " 'position: absolute;' +\n", + " 'top: 0;' +\n", + " 'z-index: 1;'\n", + " );\n", + "\n", + " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n", + " if (this.ResizeObserver === undefined) {\n", + " if (window.ResizeObserver !== undefined) {\n", + " this.ResizeObserver = window.ResizeObserver;\n", + " } else {\n", + " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n", + " this.ResizeObserver = obs.ResizeObserver;\n", + " }\n", + " }\n", + "\n", + " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n", + " var nentries = entries.length;\n", + " for (var i = 0; i < nentries; i++) {\n", + " var entry = entries[i];\n", + " var width, height;\n", + " if (entry.contentBoxSize) {\n", + " if (entry.contentBoxSize instanceof Array) {\n", + " // Chrome 84 implements new version of spec.\n", + " width = entry.contentBoxSize[0].inlineSize;\n", + " height = entry.contentBoxSize[0].blockSize;\n", + " } else {\n", + " // Firefox implements old version of spec.\n", + " width = entry.contentBoxSize.inlineSize;\n", + " height = entry.contentBoxSize.blockSize;\n", + " }\n", + " } else {\n", + " // Chrome <84 implements even older version of spec.\n", + " width = entry.contentRect.width;\n", + " height = entry.contentRect.height;\n", + " }\n", + "\n", + " // Keep the size of the canvas and rubber band canvas in sync with\n", + " // the canvas container.\n", + " if (entry.devicePixelContentBoxSize) {\n", + " // Chrome 84 implements new version of spec.\n", + " canvas.setAttribute(\n", + " 'width',\n", + " entry.devicePixelContentBoxSize[0].inlineSize\n", + " );\n", + " canvas.setAttribute(\n", + " 'height',\n", + " entry.devicePixelContentBoxSize[0].blockSize\n", + " );\n", + " } else {\n", + " canvas.setAttribute('width', width * fig.ratio);\n", + " canvas.setAttribute('height', height * fig.ratio);\n", + " }\n", + " /* This rescales the canvas back to display pixels, so that it\n", + " * appears correct on HiDPI screens. */\n", + " canvas.style.width = width + 'px';\n", + " canvas.style.height = height + 'px';\n", + "\n", + " rubberband_canvas.setAttribute('width', width);\n", + " rubberband_canvas.setAttribute('height', height);\n", + "\n", + " // And update the size in Python. We ignore the initial 0/0 size\n", + " // that occurs as the element is placed into the DOM, which should\n", + " // otherwise not happen due to the minimum size styling.\n", + " if (fig.ws.readyState == 1 && width != 0 && height != 0) {\n", + " fig.request_resize(width, height);\n", + " }\n", + " }\n", + " });\n", + " this.resizeObserverInstance.observe(canvas_div);\n", + "\n", + " function on_mouse_event_closure(name) {\n", + " /* User Agent sniffing is bad, but WebKit is busted:\n", + " * https://bugs.webkit.org/show_bug.cgi?id=144526\n", + " * https://bugs.webkit.org/show_bug.cgi?id=181818\n", + " * The worst that happens here is that they get an extra browser\n", + " * selection when dragging, if this check fails to catch them.\n", + " */\n", + " var UA = navigator.userAgent;\n", + " var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n", + " if(isWebKit) {\n", + " return function (event) {\n", + " /* This prevents the web browser from automatically changing to\n", + " * the text insertion cursor when the button is pressed. We\n", + " * want to control all of the cursor setting manually through\n", + " * the 'cursor' event from matplotlib */\n", + " event.preventDefault()\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " } else {\n", + " return function (event) {\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " }\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'mousedown',\n", + " on_mouse_event_closure('button_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'mouseup',\n", + " on_mouse_event_closure('button_release')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'dblclick',\n", + " on_mouse_event_closure('dblclick')\n", + " );\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " canvas_div.addEventListener(\n", + " 'mousemove',\n", + " on_mouse_event_closure('motion_notify')\n", + " );\n", + "\n", + " canvas_div.addEventListener(\n", + " 'mouseenter',\n", + " on_mouse_event_closure('figure_enter')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'mouseleave',\n", + " on_mouse_event_closure('figure_leave')\n", + " );\n", + "\n", + " canvas_div.addEventListener('wheel', function (event) {\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " on_mouse_event_closure('scroll')(event);\n", + " });\n", + "\n", + " canvas_div.appendChild(canvas);\n", + " canvas_div.appendChild(rubberband_canvas);\n", + "\n", + " this.rubberband_context = rubberband_canvas.getContext('2d');\n", + " this.rubberband_context.strokeStyle = '#000000';\n", + "\n", + " this._resize_canvas = function (width, height, forward) {\n", + " if (forward) {\n", + " canvas_div.style.width = width + 'px';\n", + " canvas_div.style.height = height + 'px';\n", + " }\n", + " };\n", + "\n", + " // Disable right mouse context menu.\n", + " canvas_div.addEventListener('contextmenu', function (_e) {\n", + " event.preventDefault();\n", + " return false;\n", + " });\n", + "\n", + " function set_focus() {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'mpl-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " continue;\n", + " }\n", + "\n", + " var button = (fig.buttons[name] = document.createElement('button'));\n", + " button.classList = 'mpl-widget';\n", + " button.setAttribute('role', 'button');\n", + " button.setAttribute('aria-disabled', 'false');\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + "\n", + " var icon_img = document.createElement('img');\n", + " icon_img.src = '_images/' + image + '.png';\n", + " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", + " icon_img.alt = tooltip;\n", + " button.appendChild(icon_img);\n", + "\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " var fmt_picker = document.createElement('select');\n", + " fmt_picker.classList = 'mpl-widget';\n", + " toolbar.appendChild(fmt_picker);\n", + " this.format_dropdown = fmt_picker;\n", + "\n", + " for (var ind in mpl.extensions) {\n", + " var fmt = mpl.extensions[ind];\n", + " var option = document.createElement('option');\n", + " option.selected = fmt === mpl.default_extension;\n", + " option.innerHTML = fmt;\n", + " fmt_picker.appendChild(option);\n", + " }\n", + "\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "};\n", + "\n", + "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", + " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", + " // which will in turn request a refresh of the image.\n", + " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", + "};\n", + "\n", + "mpl.figure.prototype.send_message = function (type, properties) {\n", + " properties['type'] = type;\n", + " properties['figure_id'] = this.id;\n", + " this.ws.send(JSON.stringify(properties));\n", + "};\n", + "\n", + "mpl.figure.prototype.send_draw_message = function () {\n", + " if (!this.waiting) {\n", + " this.waiting = true;\n", + " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " var format_dropdown = fig.format_dropdown;\n", + " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", + " fig.ondownload(fig, format);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", + " var size = msg['size'];\n", + " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", + " fig._resize_canvas(size[0], size[1], msg['forward']);\n", + " fig.send_message('refresh', {});\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", + " var x0 = msg['x0'] / fig.ratio;\n", + " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n", + " var x1 = msg['x1'] / fig.ratio;\n", + " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n", + " x0 = Math.floor(x0) + 0.5;\n", + " y0 = Math.floor(y0) + 0.5;\n", + " x1 = Math.floor(x1) + 0.5;\n", + " y1 = Math.floor(y1) + 0.5;\n", + " var min_x = Math.min(x0, x1);\n", + " var min_y = Math.min(y0, y1);\n", + " var width = Math.abs(x1 - x0);\n", + " var height = Math.abs(y1 - y0);\n", + "\n", + " fig.rubberband_context.clearRect(\n", + " 0,\n", + " 0,\n", + " fig.canvas.width / fig.ratio,\n", + " fig.canvas.height / fig.ratio\n", + " );\n", + "\n", + " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", + " // Updates the figure title.\n", + " fig.header.textContent = msg['label'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", + " fig.canvas_div.style.cursor = msg['cursor'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_message = function (fig, msg) {\n", + " fig.message.textContent = msg['message'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", + " // Request the server to send over a new figure.\n", + " fig.send_draw_message();\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", + " fig.image_mode = msg['mode'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", + " for (var key in msg) {\n", + " if (!(key in fig.buttons)) {\n", + " continue;\n", + " }\n", + " fig.buttons[key].disabled = !msg[key];\n", + " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", + " if (msg['mode'] === 'PAN') {\n", + " fig.buttons['Pan'].classList.add('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " } else if (msg['mode'] === 'ZOOM') {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.add('active');\n", + " } else {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Called whenever the canvas gets updated.\n", + " this.send_message('ack', {});\n", + "};\n", + "\n", + "// A function to construct a web socket function for onmessage handling.\n", + "// Called in the figure constructor.\n", + "mpl.figure.prototype._make_on_message_function = function (fig) {\n", + " return function socket_on_message(evt) {\n", + " if (evt.data instanceof Blob) {\n", + " var img = evt.data;\n", + " if (img.type !== 'image/png') {\n", + " /* FIXME: We get \"Resource interpreted as Image but\n", + " * transferred with MIME type text/plain:\" errors on\n", + " * Chrome. But how to set the MIME type? It doesn't seem\n", + " * to be part of the websocket stream */\n", + " img.type = 'image/png';\n", + " }\n", + "\n", + " /* Free the memory for the previous frames */\n", + " if (fig.imageObj.src) {\n", + " (window.URL || window.webkitURL).revokeObjectURL(\n", + " fig.imageObj.src\n", + " );\n", + " }\n", + "\n", + " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", + " img\n", + " );\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " } else if (\n", + " typeof evt.data === 'string' &&\n", + " evt.data.slice(0, 21) === 'data:image/png;base64'\n", + " ) {\n", + " fig.imageObj.src = evt.data;\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " }\n", + "\n", + " var msg = JSON.parse(evt.data);\n", + " var msg_type = msg['type'];\n", + "\n", + " // Call the \"handle_{type}\" callback, which takes\n", + " // the figure and JSON message as its only arguments.\n", + " try {\n", + " var callback = fig['handle_' + msg_type];\n", + " } catch (e) {\n", + " console.log(\n", + " \"No handler for the '\" + msg_type + \"' message type: \",\n", + " msg\n", + " );\n", + " return;\n", + " }\n", + "\n", + " if (callback) {\n", + " try {\n", + " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", + " callback(fig, msg);\n", + " } catch (e) {\n", + " console.log(\n", + " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", + " e,\n", + " e.stack,\n", + " msg\n", + " );\n", + " }\n", + " }\n", + " };\n", + "};\n", + "\n", + "function getModifiers(event) {\n", + " var mods = [];\n", + " if (event.ctrlKey) {\n", + " mods.push('ctrl');\n", + " }\n", + " if (event.altKey) {\n", + " mods.push('alt');\n", + " }\n", + " if (event.shiftKey) {\n", + " mods.push('shift');\n", + " }\n", + " if (event.metaKey) {\n", + " mods.push('meta');\n", + " }\n", + " return mods;\n", + "}\n", + "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * https://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys(original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object') {\n", + " obj[key] = original[key];\n", + " }\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", + "mpl.figure.prototype.mouse_event = function (event, name) {\n", + " if (name === 'button_press') {\n", + " this.canvas.focus();\n", + " this.canvas_div.focus();\n", + " }\n", + "\n", + " // from https://stackoverflow.com/q/1114465\n", + " var boundingRect = this.canvas.getBoundingClientRect();\n", + " var x = (event.clientX - boundingRect.left) * this.ratio;\n", + " var y = (event.clientY - boundingRect.top) * this.ratio;\n", + "\n", + " this.send_message(name, {\n", + " x: x,\n", + " y: y,\n", + " button: event.button,\n", + " step: event.step,\n", + " modifiers: getModifiers(event),\n", + " guiEvent: simpleKeys(event),\n", + " });\n", + "\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", + " // Handle any extra behaviour associated with a key event\n", + "};\n", + "\n", + "mpl.figure.prototype.key_event = function (event, name) {\n", + " // Prevent repeat events\n", + " if (name === 'key_press') {\n", + " if (event.key === this._key) {\n", + " return;\n", + " } else {\n", + " this._key = event.key;\n", + " }\n", + " }\n", + " if (name === 'key_release') {\n", + " this._key = null;\n", + " }\n", + "\n", + " var value = '';\n", + " if (event.ctrlKey && event.key !== 'Control') {\n", + " value += 'ctrl+';\n", + " }\n", + " else if (event.altKey && event.key !== 'Alt') {\n", + " value += 'alt+';\n", + " }\n", + " else if (event.shiftKey && event.key !== 'Shift') {\n", + " value += 'shift+';\n", + " }\n", + "\n", + " value += 'k' + event.key;\n", + "\n", + " this._key_event_extra(event, name);\n", + "\n", + " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", + " if (name === 'download') {\n", + " this.handle_save(this, null);\n", + " } else {\n", + " this.send_message('toolbar_button', { name: name });\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", + " this.message.textContent = tooltip;\n", + "};\n", + "\n", + "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n", + "// prettier-ignore\n", + "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n", + "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n", + "\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n", + "\n", + "mpl.default_extension = \"png\";/* global mpl */\n", + "\n", + "var comm_websocket_adapter = function (comm) {\n", + " // Create a \"websocket\"-like object which calls the given IPython comm\n", + " // object with the appropriate methods. Currently this is a non binary\n", + " // socket, so there is still some room for performance tuning.\n", + " var ws = {};\n", + "\n", + " ws.binaryType = comm.kernel.ws.binaryType;\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " function updateReadyState(_event) {\n", + " if (comm.kernel.ws) {\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " } else {\n", + " ws.readyState = 3; // Closed state.\n", + " }\n", + " }\n", + " comm.kernel.ws.addEventListener('open', updateReadyState);\n", + " comm.kernel.ws.addEventListener('close', updateReadyState);\n", + " comm.kernel.ws.addEventListener('error', updateReadyState);\n", + "\n", + " ws.close = function () {\n", + " comm.close();\n", + " };\n", + " ws.send = function (m) {\n", + " //console.log('sending', m);\n", + " comm.send(m);\n", + " };\n", + " // Register the callback with on_msg.\n", + " comm.on_msg(function (msg) {\n", + " //console.log('receiving', msg['content']['data'], msg);\n", + " var data = msg['content']['data'];\n", + " if (data['blob'] !== undefined) {\n", + " data = {\n", + " data: new Blob(msg['buffers'], { type: data['blob'] }),\n", + " };\n", + " }\n", + " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", + " ws.onmessage(data);\n", + " });\n", + " return ws;\n", + "};\n", + "\n", + "mpl.mpl_figure_comm = function (comm, msg) {\n", + " // This is the function which gets called when the mpl process\n", + " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", + "\n", + " var id = msg.content.data.id;\n", + " // Get hold of the div created by the display call when the Comm\n", + " // socket was opened in Python.\n", + " var element = document.getElementById(id);\n", + " var ws_proxy = comm_websocket_adapter(comm);\n", + "\n", + " function ondownload(figure, _format) {\n", + " window.open(figure.canvas.toDataURL());\n", + " }\n", + "\n", + " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", + "\n", + " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", + " // web socket which is closed, not our websocket->open comm proxy.\n", + " ws_proxy.onopen();\n", + "\n", + " fig.parent_element = element;\n", + " fig.cell_info = mpl.find_output_cell(\"
\");\n", + " if (!fig.cell_info) {\n", + " console.error('Failed to find cell for figure', id, fig);\n", + " return;\n", + " }\n", + " fig.cell_info[0].output_area.element.on(\n", + " 'cleared',\n", + " { fig: fig },\n", + " fig._remove_fig_handler\n", + " );\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_close = function (fig, msg) {\n", + " var width = fig.canvas.width / fig.ratio;\n", + " fig.cell_info[0].output_area.element.off(\n", + " 'cleared',\n", + " fig._remove_fig_handler\n", + " );\n", + " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n", + "\n", + " // Update the output cell to use the data from the current canvas.\n", + " fig.push_to_output();\n", + " var dataURL = fig.canvas.toDataURL();\n", + " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", + " // the notebook keyboard shortcuts fail.\n", + " IPython.keyboard_manager.enable();\n", + " fig.parent_element.innerHTML =\n", + " '';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", + " // Turn the data on the canvas into data in the output cell.\n", + " var width = this.canvas.width / this.ratio;\n", + " var dataURL = this.canvas.toDataURL();\n", + " this.cell_info[1]['text/html'] =\n", + " '';\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Tell IPython that the notebook contents must change.\n", + " IPython.notebook.set_dirty(true);\n", + " this.send_message('ack', {});\n", + " var fig = this;\n", + " // Wait a second, then push the new image to the DOM so\n", + " // that it is saved nicely (might be nice to debounce this).\n", + " setTimeout(function () {\n", + " fig.push_to_output();\n", + " }, 1000);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'btn-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " var button;\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " continue;\n", + " }\n", + "\n", + " button = fig.buttons[name] = document.createElement('button');\n", + " button.classList = 'btn btn-default';\n", + " button.href = '#';\n", + " button.title = name;\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message pull-right';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = document.createElement('div');\n", + " buttongrp.classList = 'btn-group inline pull-right';\n", + " button = document.createElement('button');\n", + " button.classList = 'btn btn-mini btn-primary';\n", + " button.href = '#';\n", + " button.title = 'Stop Interaction';\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', function (_evt) {\n", + " fig.handle_close(fig, {});\n", + " });\n", + " button.addEventListener(\n", + " 'mouseover',\n", + " on_mouseover_closure('Stop Interaction')\n", + " );\n", + " buttongrp.appendChild(button);\n", + " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", + " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", + "};\n", + "\n", + "mpl.figure.prototype._remove_fig_handler = function (event) {\n", + " var fig = event.data.fig;\n", + " if (event.target !== this) {\n", + " // Ignore bubbled events from children.\n", + " return;\n", + " }\n", + " fig.close_ws(fig, {});\n", + "};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (el) {\n", + " // this is important to make the div 'focusable\n", + " el.setAttribute('tabindex', 0);\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " } else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which === 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " fig.ondownload(fig, null);\n", + "};\n", + "\n", + "mpl.find_output_cell = function (html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i = 0; i < ncells; i++) {\n", + " var cell = cells[i];\n", + " if (cell.cell_type === 'code') {\n", + " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", + " var data = cell.output_area.outputs[j];\n", + " if (data.data) {\n", + " // IPython >= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] === html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "};\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel !== null) {\n", + " IPython.notebook.kernel.comm_manager.register_target(\n", + " 'matplotlib',\n", + " mpl.mpl_figure_comm\n", + " );\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "/* global mpl */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function () {\n", + " if (typeof WebSocket !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof MozWebSocket !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert(\n", + " 'Your browser does not have WebSocket support. ' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.'\n", + " );\n", + " }\n", + "};\n", + "\n", + "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = this.ws.binaryType !== undefined;\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById('mpl-warnings');\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent =\n", + " 'This browser does not support binary websocket messages. ' +\n", + " 'Performance may be slow.';\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = document.createElement('div');\n", + " this.root.setAttribute('style', 'display: inline-block');\n", + " this._root_extra_style(this.root);\n", + "\n", + " parent_element.appendChild(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message('supports_binary', { value: fig.supports_binary });\n", + " fig.send_message('send_image_mode', {});\n", + " if (fig.ratio !== 1) {\n", + " fig.send_message('set_device_pixel_ratio', {\n", + " device_pixel_ratio: fig.ratio,\n", + " });\n", + " }\n", + " fig.send_message('refresh', {});\n", + " };\n", + "\n", + " this.imageObj.onload = function () {\n", + " if (fig.image_mode === 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function () {\n", + " fig.ws.close();\n", + " };\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "};\n", + "\n", + "mpl.figure.prototype._init_header = function () {\n", + " var titlebar = document.createElement('div');\n", + " titlebar.classList =\n", + " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", + " var titletext = document.createElement('div');\n", + " titletext.classList = 'ui-dialog-title';\n", + " titletext.setAttribute(\n", + " 'style',\n", + " 'width: 100%; text-align: center; padding: 3px;'\n", + " );\n", + " titlebar.appendChild(titletext);\n", + " this.root.appendChild(titlebar);\n", + " this.header = titletext;\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._init_canvas = function () {\n", + " var fig = this;\n", + "\n", + " var canvas_div = (this.canvas_div = document.createElement('div'));\n", + " canvas_div.setAttribute('tabindex', '0');\n", + " canvas_div.setAttribute(\n", + " 'style',\n", + " 'border: 1px solid #ddd;' +\n", + " 'box-sizing: content-box;' +\n", + " 'clear: both;' +\n", + " 'min-height: 1px;' +\n", + " 'min-width: 1px;' +\n", + " 'outline: 0;' +\n", + " 'overflow: hidden;' +\n", + " 'position: relative;' +\n", + " 'resize: both;' +\n", + " 'z-index: 2;'\n", + " );\n", + "\n", + " function on_keyboard_event_closure(name) {\n", + " return function (event) {\n", + " return fig.key_event(event, name);\n", + " };\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'keydown',\n", + " on_keyboard_event_closure('key_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'keyup',\n", + " on_keyboard_event_closure('key_release')\n", + " );\n", + "\n", + " this._canvas_extra_style(canvas_div);\n", + " this.root.appendChild(canvas_div);\n", + "\n", + " var canvas = (this.canvas = document.createElement('canvas'));\n", + " canvas.classList.add('mpl-canvas');\n", + " canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box;' +\n", + " 'pointer-events: none;' +\n", + " 'position: relative;' +\n", + " 'z-index: 0;'\n", + " );\n", + "\n", + " this.context = canvas.getContext('2d');\n", + "\n", + " var backingStore =\n", + " this.context.backingStorePixelRatio ||\n", + " this.context.webkitBackingStorePixelRatio ||\n", + " this.context.mozBackingStorePixelRatio ||\n", + " this.context.msBackingStorePixelRatio ||\n", + " this.context.oBackingStorePixelRatio ||\n", + " this.context.backingStorePixelRatio ||\n", + " 1;\n", + "\n", + " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", + " 'canvas'\n", + " ));\n", + " rubberband_canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box;' +\n", + " 'left: 0;' +\n", + " 'pointer-events: none;' +\n", + " 'position: absolute;' +\n", + " 'top: 0;' +\n", + " 'z-index: 1;'\n", + " );\n", + "\n", + " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n", + " if (this.ResizeObserver === undefined) {\n", + " if (window.ResizeObserver !== undefined) {\n", + " this.ResizeObserver = window.ResizeObserver;\n", + " } else {\n", + " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n", + " this.ResizeObserver = obs.ResizeObserver;\n", + " }\n", + " }\n", + "\n", + " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n", + " var nentries = entries.length;\n", + " for (var i = 0; i < nentries; i++) {\n", + " var entry = entries[i];\n", + " var width, height;\n", + " if (entry.contentBoxSize) {\n", + " if (entry.contentBoxSize instanceof Array) {\n", + " // Chrome 84 implements new version of spec.\n", + " width = entry.contentBoxSize[0].inlineSize;\n", + " height = entry.contentBoxSize[0].blockSize;\n", + " } else {\n", + " // Firefox implements old version of spec.\n", + " width = entry.contentBoxSize.inlineSize;\n", + " height = entry.contentBoxSize.blockSize;\n", + " }\n", + " } else {\n", + " // Chrome <84 implements even older version of spec.\n", + " width = entry.contentRect.width;\n", + " height = entry.contentRect.height;\n", + " }\n", + "\n", + " // Keep the size of the canvas and rubber band canvas in sync with\n", + " // the canvas container.\n", + " if (entry.devicePixelContentBoxSize) {\n", + " // Chrome 84 implements new version of spec.\n", + " canvas.setAttribute(\n", + " 'width',\n", + " entry.devicePixelContentBoxSize[0].inlineSize\n", + " );\n", + " canvas.setAttribute(\n", + " 'height',\n", + " entry.devicePixelContentBoxSize[0].blockSize\n", + " );\n", + " } else {\n", + " canvas.setAttribute('width', width * fig.ratio);\n", + " canvas.setAttribute('height', height * fig.ratio);\n", + " }\n", + " /* This rescales the canvas back to display pixels, so that it\n", + " * appears correct on HiDPI screens. */\n", + " canvas.style.width = width + 'px';\n", + " canvas.style.height = height + 'px';\n", + "\n", + " rubberband_canvas.setAttribute('width', width);\n", + " rubberband_canvas.setAttribute('height', height);\n", + "\n", + " // And update the size in Python. We ignore the initial 0/0 size\n", + " // that occurs as the element is placed into the DOM, which should\n", + " // otherwise not happen due to the minimum size styling.\n", + " if (fig.ws.readyState == 1 && width != 0 && height != 0) {\n", + " fig.request_resize(width, height);\n", + " }\n", + " }\n", + " });\n", + " this.resizeObserverInstance.observe(canvas_div);\n", + "\n", + " function on_mouse_event_closure(name) {\n", + " /* User Agent sniffing is bad, but WebKit is busted:\n", + " * https://bugs.webkit.org/show_bug.cgi?id=144526\n", + " * https://bugs.webkit.org/show_bug.cgi?id=181818\n", + " * The worst that happens here is that they get an extra browser\n", + " * selection when dragging, if this check fails to catch them.\n", + " */\n", + " var UA = navigator.userAgent;\n", + " var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n", + " if(isWebKit) {\n", + " return function (event) {\n", + " /* This prevents the web browser from automatically changing to\n", + " * the text insertion cursor when the button is pressed. We\n", + " * want to control all of the cursor setting manually through\n", + " * the 'cursor' event from matplotlib */\n", + " event.preventDefault()\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " } else {\n", + " return function (event) {\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " }\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'mousedown',\n", + " on_mouse_event_closure('button_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'mouseup',\n", + " on_mouse_event_closure('button_release')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'dblclick',\n", + " on_mouse_event_closure('dblclick')\n", + " );\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " canvas_div.addEventListener(\n", + " 'mousemove',\n", + " on_mouse_event_closure('motion_notify')\n", + " );\n", + "\n", + " canvas_div.addEventListener(\n", + " 'mouseenter',\n", + " on_mouse_event_closure('figure_enter')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'mouseleave',\n", + " on_mouse_event_closure('figure_leave')\n", + " );\n", + "\n", + " canvas_div.addEventListener('wheel', function (event) {\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " on_mouse_event_closure('scroll')(event);\n", + " });\n", + "\n", + " canvas_div.appendChild(canvas);\n", + " canvas_div.appendChild(rubberband_canvas);\n", + "\n", + " this.rubberband_context = rubberband_canvas.getContext('2d');\n", + " this.rubberband_context.strokeStyle = '#000000';\n", + "\n", + " this._resize_canvas = function (width, height, forward) {\n", + " if (forward) {\n", + " canvas_div.style.width = width + 'px';\n", + " canvas_div.style.height = height + 'px';\n", + " }\n", + " };\n", + "\n", + " // Disable right mouse context menu.\n", + " canvas_div.addEventListener('contextmenu', function (_e) {\n", + " event.preventDefault();\n", + " return false;\n", + " });\n", + "\n", + " function set_focus() {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'mpl-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " continue;\n", + " }\n", + "\n", + " var button = (fig.buttons[name] = document.createElement('button'));\n", + " button.classList = 'mpl-widget';\n", + " button.setAttribute('role', 'button');\n", + " button.setAttribute('aria-disabled', 'false');\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + "\n", + " var icon_img = document.createElement('img');\n", + " icon_img.src = '_images/' + image + '.png';\n", + " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", + " icon_img.alt = tooltip;\n", + " button.appendChild(icon_img);\n", + "\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " var fmt_picker = document.createElement('select');\n", + " fmt_picker.classList = 'mpl-widget';\n", + " toolbar.appendChild(fmt_picker);\n", + " this.format_dropdown = fmt_picker;\n", + "\n", + " for (var ind in mpl.extensions) {\n", + " var fmt = mpl.extensions[ind];\n", + " var option = document.createElement('option');\n", + " option.selected = fmt === mpl.default_extension;\n", + " option.innerHTML = fmt;\n", + " fmt_picker.appendChild(option);\n", + " }\n", + "\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "};\n", + "\n", + "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", + " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", + " // which will in turn request a refresh of the image.\n", + " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", + "};\n", + "\n", + "mpl.figure.prototype.send_message = function (type, properties) {\n", + " properties['type'] = type;\n", + " properties['figure_id'] = this.id;\n", + " this.ws.send(JSON.stringify(properties));\n", + "};\n", + "\n", + "mpl.figure.prototype.send_draw_message = function () {\n", + " if (!this.waiting) {\n", + " this.waiting = true;\n", + " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " var format_dropdown = fig.format_dropdown;\n", + " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", + " fig.ondownload(fig, format);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", + " var size = msg['size'];\n", + " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", + " fig._resize_canvas(size[0], size[1], msg['forward']);\n", + " fig.send_message('refresh', {});\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", + " var x0 = msg['x0'] / fig.ratio;\n", + " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n", + " var x1 = msg['x1'] / fig.ratio;\n", + " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n", + " x0 = Math.floor(x0) + 0.5;\n", + " y0 = Math.floor(y0) + 0.5;\n", + " x1 = Math.floor(x1) + 0.5;\n", + " y1 = Math.floor(y1) + 0.5;\n", + " var min_x = Math.min(x0, x1);\n", + " var min_y = Math.min(y0, y1);\n", + " var width = Math.abs(x1 - x0);\n", + " var height = Math.abs(y1 - y0);\n", + "\n", + " fig.rubberband_context.clearRect(\n", + " 0,\n", + " 0,\n", + " fig.canvas.width / fig.ratio,\n", + " fig.canvas.height / fig.ratio\n", + " );\n", + "\n", + " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", + " // Updates the figure title.\n", + " fig.header.textContent = msg['label'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", + " fig.canvas_div.style.cursor = msg['cursor'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_message = function (fig, msg) {\n", + " fig.message.textContent = msg['message'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", + " // Request the server to send over a new figure.\n", + " fig.send_draw_message();\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", + " fig.image_mode = msg['mode'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", + " for (var key in msg) {\n", + " if (!(key in fig.buttons)) {\n", + " continue;\n", + " }\n", + " fig.buttons[key].disabled = !msg[key];\n", + " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", + " if (msg['mode'] === 'PAN') {\n", + " fig.buttons['Pan'].classList.add('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " } else if (msg['mode'] === 'ZOOM') {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.add('active');\n", + " } else {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Called whenever the canvas gets updated.\n", + " this.send_message('ack', {});\n", + "};\n", + "\n", + "// A function to construct a web socket function for onmessage handling.\n", + "// Called in the figure constructor.\n", + "mpl.figure.prototype._make_on_message_function = function (fig) {\n", + " return function socket_on_message(evt) {\n", + " if (evt.data instanceof Blob) {\n", + " var img = evt.data;\n", + " if (img.type !== 'image/png') {\n", + " /* FIXME: We get \"Resource interpreted as Image but\n", + " * transferred with MIME type text/plain:\" errors on\n", + " * Chrome. But how to set the MIME type? It doesn't seem\n", + " * to be part of the websocket stream */\n", + " img.type = 'image/png';\n", + " }\n", + "\n", + " /* Free the memory for the previous frames */\n", + " if (fig.imageObj.src) {\n", + " (window.URL || window.webkitURL).revokeObjectURL(\n", + " fig.imageObj.src\n", + " );\n", + " }\n", + "\n", + " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", + " img\n", + " );\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " } else if (\n", + " typeof evt.data === 'string' &&\n", + " evt.data.slice(0, 21) === 'data:image/png;base64'\n", + " ) {\n", + " fig.imageObj.src = evt.data;\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " }\n", + "\n", + " var msg = JSON.parse(evt.data);\n", + " var msg_type = msg['type'];\n", + "\n", + " // Call the \"handle_{type}\" callback, which takes\n", + " // the figure and JSON message as its only arguments.\n", + " try {\n", + " var callback = fig['handle_' + msg_type];\n", + " } catch (e) {\n", + " console.log(\n", + " \"No handler for the '\" + msg_type + \"' message type: \",\n", + " msg\n", + " );\n", + " return;\n", + " }\n", + "\n", + " if (callback) {\n", + " try {\n", + " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", + " callback(fig, msg);\n", + " } catch (e) {\n", + " console.log(\n", + " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", + " e,\n", + " e.stack,\n", + " msg\n", + " );\n", + " }\n", + " }\n", + " };\n", + "};\n", + "\n", + "function getModifiers(event) {\n", + " var mods = [];\n", + " if (event.ctrlKey) {\n", + " mods.push('ctrl');\n", + " }\n", + " if (event.altKey) {\n", + " mods.push('alt');\n", + " }\n", + " if (event.shiftKey) {\n", + " mods.push('shift');\n", + " }\n", + " if (event.metaKey) {\n", + " mods.push('meta');\n", + " }\n", + " return mods;\n", + "}\n", + "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * https://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys(original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object') {\n", + " obj[key] = original[key];\n", + " }\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", + "mpl.figure.prototype.mouse_event = function (event, name) {\n", + " if (name === 'button_press') {\n", + " this.canvas.focus();\n", + " this.canvas_div.focus();\n", + " }\n", + "\n", + " // from https://stackoverflow.com/q/1114465\n", + " var boundingRect = this.canvas.getBoundingClientRect();\n", + " var x = (event.clientX - boundingRect.left) * this.ratio;\n", + " var y = (event.clientY - boundingRect.top) * this.ratio;\n", + "\n", + " this.send_message(name, {\n", + " x: x,\n", + " y: y,\n", + " button: event.button,\n", + " step: event.step,\n", + " modifiers: getModifiers(event),\n", + " guiEvent: simpleKeys(event),\n", + " });\n", + "\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", + " // Handle any extra behaviour associated with a key event\n", + "};\n", + "\n", + "mpl.figure.prototype.key_event = function (event, name) {\n", + " // Prevent repeat events\n", + " if (name === 'key_press') {\n", + " if (event.key === this._key) {\n", + " return;\n", + " } else {\n", + " this._key = event.key;\n", + " }\n", + " }\n", + " if (name === 'key_release') {\n", + " this._key = null;\n", + " }\n", + "\n", + " var value = '';\n", + " if (event.ctrlKey && event.key !== 'Control') {\n", + " value += 'ctrl+';\n", + " }\n", + " else if (event.altKey && event.key !== 'Alt') {\n", + " value += 'alt+';\n", + " }\n", + " else if (event.shiftKey && event.key !== 'Shift') {\n", + " value += 'shift+';\n", + " }\n", + "\n", + " value += 'k' + event.key;\n", + "\n", + " this._key_event_extra(event, name);\n", + "\n", + " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", + " if (name === 'download') {\n", + " this.handle_save(this, null);\n", + " } else {\n", + " this.send_message('toolbar_button', { name: name });\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", + " this.message.textContent = tooltip;\n", + "};\n", + "\n", + "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n", + "// prettier-ignore\n", + "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n", + "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n", + "\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n", + "\n", + "mpl.default_extension = \"png\";/* global mpl */\n", + "\n", + "var comm_websocket_adapter = function (comm) {\n", + " // Create a \"websocket\"-like object which calls the given IPython comm\n", + " // object with the appropriate methods. Currently this is a non binary\n", + " // socket, so there is still some room for performance tuning.\n", + " var ws = {};\n", + "\n", + " ws.binaryType = comm.kernel.ws.binaryType;\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " function updateReadyState(_event) {\n", + " if (comm.kernel.ws) {\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " } else {\n", + " ws.readyState = 3; // Closed state.\n", + " }\n", + " }\n", + " comm.kernel.ws.addEventListener('open', updateReadyState);\n", + " comm.kernel.ws.addEventListener('close', updateReadyState);\n", + " comm.kernel.ws.addEventListener('error', updateReadyState);\n", + "\n", + " ws.close = function () {\n", + " comm.close();\n", + " };\n", + " ws.send = function (m) {\n", + " //console.log('sending', m);\n", + " comm.send(m);\n", + " };\n", + " // Register the callback with on_msg.\n", + " comm.on_msg(function (msg) {\n", + " //console.log('receiving', msg['content']['data'], msg);\n", + " var data = msg['content']['data'];\n", + " if (data['blob'] !== undefined) {\n", + " data = {\n", + " data: new Blob(msg['buffers'], { type: data['blob'] }),\n", + " };\n", + " }\n", + " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", + " ws.onmessage(data);\n", + " });\n", + " return ws;\n", + "};\n", + "\n", + "mpl.mpl_figure_comm = function (comm, msg) {\n", + " // This is the function which gets called when the mpl process\n", + " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", + "\n", + " var id = msg.content.data.id;\n", + " // Get hold of the div created by the display call when the Comm\n", + " // socket was opened in Python.\n", + " var element = document.getElementById(id);\n", + " var ws_proxy = comm_websocket_adapter(comm);\n", + "\n", + " function ondownload(figure, _format) {\n", + " window.open(figure.canvas.toDataURL());\n", + " }\n", + "\n", + " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", + "\n", + " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", + " // web socket which is closed, not our websocket->open comm proxy.\n", + " ws_proxy.onopen();\n", + "\n", + " fig.parent_element = element;\n", + " fig.cell_info = mpl.find_output_cell(\"
\");\n", + " if (!fig.cell_info) {\n", + " console.error('Failed to find cell for figure', id, fig);\n", + " return;\n", + " }\n", + " fig.cell_info[0].output_area.element.on(\n", + " 'cleared',\n", + " { fig: fig },\n", + " fig._remove_fig_handler\n", + " );\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_close = function (fig, msg) {\n", + " var width = fig.canvas.width / fig.ratio;\n", + " fig.cell_info[0].output_area.element.off(\n", + " 'cleared',\n", + " fig._remove_fig_handler\n", + " );\n", + " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n", + "\n", + " // Update the output cell to use the data from the current canvas.\n", + " fig.push_to_output();\n", + " var dataURL = fig.canvas.toDataURL();\n", + " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", + " // the notebook keyboard shortcuts fail.\n", + " IPython.keyboard_manager.enable();\n", + " fig.parent_element.innerHTML =\n", + " '';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", + " // Turn the data on the canvas into data in the output cell.\n", + " var width = this.canvas.width / this.ratio;\n", + " var dataURL = this.canvas.toDataURL();\n", + " this.cell_info[1]['text/html'] =\n", + " '';\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Tell IPython that the notebook contents must change.\n", + " IPython.notebook.set_dirty(true);\n", + " this.send_message('ack', {});\n", + " var fig = this;\n", + " // Wait a second, then push the new image to the DOM so\n", + " // that it is saved nicely (might be nice to debounce this).\n", + " setTimeout(function () {\n", + " fig.push_to_output();\n", + " }, 1000);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'btn-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " var button;\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " continue;\n", + " }\n", + "\n", + " button = fig.buttons[name] = document.createElement('button');\n", + " button.classList = 'btn btn-default';\n", + " button.href = '#';\n", + " button.title = name;\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message pull-right';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = document.createElement('div');\n", + " buttongrp.classList = 'btn-group inline pull-right';\n", + " button = document.createElement('button');\n", + " button.classList = 'btn btn-mini btn-primary';\n", + " button.href = '#';\n", + " button.title = 'Stop Interaction';\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', function (_evt) {\n", + " fig.handle_close(fig, {});\n", + " });\n", + " button.addEventListener(\n", + " 'mouseover',\n", + " on_mouseover_closure('Stop Interaction')\n", + " );\n", + " buttongrp.appendChild(button);\n", + " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", + " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", + "};\n", + "\n", + "mpl.figure.prototype._remove_fig_handler = function (event) {\n", + " var fig = event.data.fig;\n", + " if (event.target !== this) {\n", + " // Ignore bubbled events from children.\n", + " return;\n", + " }\n", + " fig.close_ws(fig, {});\n", + "};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (el) {\n", + " // this is important to make the div 'focusable\n", + " el.setAttribute('tabindex', 0);\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " } else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which === 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " fig.ondownload(fig, null);\n", + "};\n", + "\n", + "mpl.find_output_cell = function (html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i = 0; i < ncells; i++) {\n", + " var cell = cells[i];\n", + " if (cell.cell_type === 'code') {\n", + " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", + " var data = cell.output_area.outputs[j];\n", + " if (data.data) {\n", + " // IPython >= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] === html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "};\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel !== null) {\n", + " IPython.notebook.kernel.comm_manager.register_target(\n", + " 'matplotlib',\n", + " mpl.mpl_figure_comm\n", + " );\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "EnKF (replay): 100%|███████████| 3000/3000 [00:00<00:00, 15191.34it/s]\n" + ] + } + ], + "source": [ + "xp.stats.replay(\n", + " # speed=.6 # `speed` does not work in notebooks\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "6335abba", + "metadata": {}, + "source": [ + "### Further diagnostic plots" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "da5fd628", + "metadata": {}, + "outputs": [], + "source": [ + "import dapper.tools.viz as viz" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "450a7fc1", + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "/* global mpl */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function () {\n", + " if (typeof WebSocket !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof MozWebSocket !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert(\n", + " 'Your browser does not have WebSocket support. ' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.'\n", + " );\n", + " }\n", + "};\n", + "\n", + "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = this.ws.binaryType !== undefined;\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById('mpl-warnings');\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent =\n", + " 'This browser does not support binary websocket messages. ' +\n", + " 'Performance may be slow.';\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = document.createElement('div');\n", + " this.root.setAttribute('style', 'display: inline-block');\n", + " this._root_extra_style(this.root);\n", + "\n", + " parent_element.appendChild(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message('supports_binary', { value: fig.supports_binary });\n", + " fig.send_message('send_image_mode', {});\n", + " if (fig.ratio !== 1) {\n", + " fig.send_message('set_device_pixel_ratio', {\n", + " device_pixel_ratio: fig.ratio,\n", + " });\n", + " }\n", + " fig.send_message('refresh', {});\n", + " };\n", + "\n", + " this.imageObj.onload = function () {\n", + " if (fig.image_mode === 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function () {\n", + " fig.ws.close();\n", + " };\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "};\n", + "\n", + "mpl.figure.prototype._init_header = function () {\n", + " var titlebar = document.createElement('div');\n", + " titlebar.classList =\n", + " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", + " var titletext = document.createElement('div');\n", + " titletext.classList = 'ui-dialog-title';\n", + " titletext.setAttribute(\n", + " 'style',\n", + " 'width: 100%; text-align: center; padding: 3px;'\n", + " );\n", + " titlebar.appendChild(titletext);\n", + " this.root.appendChild(titlebar);\n", + " this.header = titletext;\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._init_canvas = function () {\n", + " var fig = this;\n", + "\n", + " var canvas_div = (this.canvas_div = document.createElement('div'));\n", + " canvas_div.setAttribute('tabindex', '0');\n", + " canvas_div.setAttribute(\n", + " 'style',\n", + " 'border: 1px solid #ddd;' +\n", + " 'box-sizing: content-box;' +\n", + " 'clear: both;' +\n", + " 'min-height: 1px;' +\n", + " 'min-width: 1px;' +\n", + " 'outline: 0;' +\n", + " 'overflow: hidden;' +\n", + " 'position: relative;' +\n", + " 'resize: both;' +\n", + " 'z-index: 2;'\n", + " );\n", + "\n", + " function on_keyboard_event_closure(name) {\n", + " return function (event) {\n", + " return fig.key_event(event, name);\n", + " };\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'keydown',\n", + " on_keyboard_event_closure('key_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'keyup',\n", + " on_keyboard_event_closure('key_release')\n", + " );\n", + "\n", + " this._canvas_extra_style(canvas_div);\n", + " this.root.appendChild(canvas_div);\n", + "\n", + " var canvas = (this.canvas = document.createElement('canvas'));\n", + " canvas.classList.add('mpl-canvas');\n", + " canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box;' +\n", + " 'pointer-events: none;' +\n", + " 'position: relative;' +\n", + " 'z-index: 0;'\n", + " );\n", + "\n", + " this.context = canvas.getContext('2d');\n", + "\n", + " var backingStore =\n", + " this.context.backingStorePixelRatio ||\n", + " this.context.webkitBackingStorePixelRatio ||\n", + " this.context.mozBackingStorePixelRatio ||\n", + " this.context.msBackingStorePixelRatio ||\n", + " this.context.oBackingStorePixelRatio ||\n", + " this.context.backingStorePixelRatio ||\n", + " 1;\n", + "\n", + " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", + " 'canvas'\n", + " ));\n", + " rubberband_canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box;' +\n", + " 'left: 0;' +\n", + " 'pointer-events: none;' +\n", + " 'position: absolute;' +\n", + " 'top: 0;' +\n", + " 'z-index: 1;'\n", + " );\n", + "\n", + " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n", + " if (this.ResizeObserver === undefined) {\n", + " if (window.ResizeObserver !== undefined) {\n", + " this.ResizeObserver = window.ResizeObserver;\n", + " } else {\n", + " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n", + " this.ResizeObserver = obs.ResizeObserver;\n", + " }\n", + " }\n", + "\n", + " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n", + " var nentries = entries.length;\n", + " for (var i = 0; i < nentries; i++) {\n", + " var entry = entries[i];\n", + " var width, height;\n", + " if (entry.contentBoxSize) {\n", + " if (entry.contentBoxSize instanceof Array) {\n", + " // Chrome 84 implements new version of spec.\n", + " width = entry.contentBoxSize[0].inlineSize;\n", + " height = entry.contentBoxSize[0].blockSize;\n", + " } else {\n", + " // Firefox implements old version of spec.\n", + " width = entry.contentBoxSize.inlineSize;\n", + " height = entry.contentBoxSize.blockSize;\n", + " }\n", + " } else {\n", + " // Chrome <84 implements even older version of spec.\n", + " width = entry.contentRect.width;\n", + " height = entry.contentRect.height;\n", + " }\n", + "\n", + " // Keep the size of the canvas and rubber band canvas in sync with\n", + " // the canvas container.\n", + " if (entry.devicePixelContentBoxSize) {\n", + " // Chrome 84 implements new version of spec.\n", + " canvas.setAttribute(\n", + " 'width',\n", + " entry.devicePixelContentBoxSize[0].inlineSize\n", + " );\n", + " canvas.setAttribute(\n", + " 'height',\n", + " entry.devicePixelContentBoxSize[0].blockSize\n", + " );\n", + " } else {\n", + " canvas.setAttribute('width', width * fig.ratio);\n", + " canvas.setAttribute('height', height * fig.ratio);\n", + " }\n", + " /* This rescales the canvas back to display pixels, so that it\n", + " * appears correct on HiDPI screens. */\n", + " canvas.style.width = width + 'px';\n", + " canvas.style.height = height + 'px';\n", + "\n", + " rubberband_canvas.setAttribute('width', width);\n", + " rubberband_canvas.setAttribute('height', height);\n", + "\n", + " // And update the size in Python. We ignore the initial 0/0 size\n", + " // that occurs as the element is placed into the DOM, which should\n", + " // otherwise not happen due to the minimum size styling.\n", + " if (fig.ws.readyState == 1 && width != 0 && height != 0) {\n", + " fig.request_resize(width, height);\n", + " }\n", + " }\n", + " });\n", + " this.resizeObserverInstance.observe(canvas_div);\n", + "\n", + " function on_mouse_event_closure(name) {\n", + " /* User Agent sniffing is bad, but WebKit is busted:\n", + " * https://bugs.webkit.org/show_bug.cgi?id=144526\n", + " * https://bugs.webkit.org/show_bug.cgi?id=181818\n", + " * The worst that happens here is that they get an extra browser\n", + " * selection when dragging, if this check fails to catch them.\n", + " */\n", + " var UA = navigator.userAgent;\n", + " var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n", + " if(isWebKit) {\n", + " return function (event) {\n", + " /* This prevents the web browser from automatically changing to\n", + " * the text insertion cursor when the button is pressed. We\n", + " * want to control all of the cursor setting manually through\n", + " * the 'cursor' event from matplotlib */\n", + " event.preventDefault()\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " } else {\n", + " return function (event) {\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " }\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'mousedown',\n", + " on_mouse_event_closure('button_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'mouseup',\n", + " on_mouse_event_closure('button_release')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'dblclick',\n", + " on_mouse_event_closure('dblclick')\n", + " );\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " canvas_div.addEventListener(\n", + " 'mousemove',\n", + " on_mouse_event_closure('motion_notify')\n", + " );\n", + "\n", + " canvas_div.addEventListener(\n", + " 'mouseenter',\n", + " on_mouse_event_closure('figure_enter')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'mouseleave',\n", + " on_mouse_event_closure('figure_leave')\n", + " );\n", + "\n", + " canvas_div.addEventListener('wheel', function (event) {\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " on_mouse_event_closure('scroll')(event);\n", + " });\n", + "\n", + " canvas_div.appendChild(canvas);\n", + " canvas_div.appendChild(rubberband_canvas);\n", + "\n", + " this.rubberband_context = rubberband_canvas.getContext('2d');\n", + " this.rubberband_context.strokeStyle = '#000000';\n", + "\n", + " this._resize_canvas = function (width, height, forward) {\n", + " if (forward) {\n", + " canvas_div.style.width = width + 'px';\n", + " canvas_div.style.height = height + 'px';\n", + " }\n", + " };\n", + "\n", + " // Disable right mouse context menu.\n", + " canvas_div.addEventListener('contextmenu', function (_e) {\n", + " event.preventDefault();\n", + " return false;\n", + " });\n", + "\n", + " function set_focus() {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'mpl-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " continue;\n", + " }\n", + "\n", + " var button = (fig.buttons[name] = document.createElement('button'));\n", + " button.classList = 'mpl-widget';\n", + " button.setAttribute('role', 'button');\n", + " button.setAttribute('aria-disabled', 'false');\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + "\n", + " var icon_img = document.createElement('img');\n", + " icon_img.src = '_images/' + image + '.png';\n", + " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", + " icon_img.alt = tooltip;\n", + " button.appendChild(icon_img);\n", + "\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " var fmt_picker = document.createElement('select');\n", + " fmt_picker.classList = 'mpl-widget';\n", + " toolbar.appendChild(fmt_picker);\n", + " this.format_dropdown = fmt_picker;\n", + "\n", + " for (var ind in mpl.extensions) {\n", + " var fmt = mpl.extensions[ind];\n", + " var option = document.createElement('option');\n", + " option.selected = fmt === mpl.default_extension;\n", + " option.innerHTML = fmt;\n", + " fmt_picker.appendChild(option);\n", + " }\n", + "\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "};\n", + "\n", + "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", + " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", + " // which will in turn request a refresh of the image.\n", + " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", + "};\n", + "\n", + "mpl.figure.prototype.send_message = function (type, properties) {\n", + " properties['type'] = type;\n", + " properties['figure_id'] = this.id;\n", + " this.ws.send(JSON.stringify(properties));\n", + "};\n", + "\n", + "mpl.figure.prototype.send_draw_message = function () {\n", + " if (!this.waiting) {\n", + " this.waiting = true;\n", + " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " var format_dropdown = fig.format_dropdown;\n", + " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", + " fig.ondownload(fig, format);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", + " var size = msg['size'];\n", + " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", + " fig._resize_canvas(size[0], size[1], msg['forward']);\n", + " fig.send_message('refresh', {});\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", + " var x0 = msg['x0'] / fig.ratio;\n", + " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n", + " var x1 = msg['x1'] / fig.ratio;\n", + " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n", + " x0 = Math.floor(x0) + 0.5;\n", + " y0 = Math.floor(y0) + 0.5;\n", + " x1 = Math.floor(x1) + 0.5;\n", + " y1 = Math.floor(y1) + 0.5;\n", + " var min_x = Math.min(x0, x1);\n", + " var min_y = Math.min(y0, y1);\n", + " var width = Math.abs(x1 - x0);\n", + " var height = Math.abs(y1 - y0);\n", + "\n", + " fig.rubberband_context.clearRect(\n", + " 0,\n", + " 0,\n", + " fig.canvas.width / fig.ratio,\n", + " fig.canvas.height / fig.ratio\n", + " );\n", + "\n", + " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", + " // Updates the figure title.\n", + " fig.header.textContent = msg['label'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", + " fig.canvas_div.style.cursor = msg['cursor'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_message = function (fig, msg) {\n", + " fig.message.textContent = msg['message'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", + " // Request the server to send over a new figure.\n", + " fig.send_draw_message();\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", + " fig.image_mode = msg['mode'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", + " for (var key in msg) {\n", + " if (!(key in fig.buttons)) {\n", + " continue;\n", + " }\n", + " fig.buttons[key].disabled = !msg[key];\n", + " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", + " if (msg['mode'] === 'PAN') {\n", + " fig.buttons['Pan'].classList.add('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " } else if (msg['mode'] === 'ZOOM') {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.add('active');\n", + " } else {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Called whenever the canvas gets updated.\n", + " this.send_message('ack', {});\n", + "};\n", + "\n", + "// A function to construct a web socket function for onmessage handling.\n", + "// Called in the figure constructor.\n", + "mpl.figure.prototype._make_on_message_function = function (fig) {\n", + " return function socket_on_message(evt) {\n", + " if (evt.data instanceof Blob) {\n", + " var img = evt.data;\n", + " if (img.type !== 'image/png') {\n", + " /* FIXME: We get \"Resource interpreted as Image but\n", + " * transferred with MIME type text/plain:\" errors on\n", + " * Chrome. But how to set the MIME type? It doesn't seem\n", + " * to be part of the websocket stream */\n", + " img.type = 'image/png';\n", + " }\n", + "\n", + " /* Free the memory for the previous frames */\n", + " if (fig.imageObj.src) {\n", + " (window.URL || window.webkitURL).revokeObjectURL(\n", + " fig.imageObj.src\n", + " );\n", + " }\n", + "\n", + " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", + " img\n", + " );\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " } else if (\n", + " typeof evt.data === 'string' &&\n", + " evt.data.slice(0, 21) === 'data:image/png;base64'\n", + " ) {\n", + " fig.imageObj.src = evt.data;\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " }\n", + "\n", + " var msg = JSON.parse(evt.data);\n", + " var msg_type = msg['type'];\n", + "\n", + " // Call the \"handle_{type}\" callback, which takes\n", + " // the figure and JSON message as its only arguments.\n", + " try {\n", + " var callback = fig['handle_' + msg_type];\n", + " } catch (e) {\n", + " console.log(\n", + " \"No handler for the '\" + msg_type + \"' message type: \",\n", + " msg\n", + " );\n", + " return;\n", + " }\n", + "\n", + " if (callback) {\n", + " try {\n", + " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", + " callback(fig, msg);\n", + " } catch (e) {\n", + " console.log(\n", + " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", + " e,\n", + " e.stack,\n", + " msg\n", + " );\n", + " }\n", + " }\n", + " };\n", + "};\n", + "\n", + "function getModifiers(event) {\n", + " var mods = [];\n", + " if (event.ctrlKey) {\n", + " mods.push('ctrl');\n", + " }\n", + " if (event.altKey) {\n", + " mods.push('alt');\n", + " }\n", + " if (event.shiftKey) {\n", + " mods.push('shift');\n", + " }\n", + " if (event.metaKey) {\n", + " mods.push('meta');\n", + " }\n", + " return mods;\n", + "}\n", + "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * https://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys(original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object') {\n", + " obj[key] = original[key];\n", + " }\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", + "mpl.figure.prototype.mouse_event = function (event, name) {\n", + " if (name === 'button_press') {\n", + " this.canvas.focus();\n", + " this.canvas_div.focus();\n", + " }\n", + "\n", + " // from https://stackoverflow.com/q/1114465\n", + " var boundingRect = this.canvas.getBoundingClientRect();\n", + " var x = (event.clientX - boundingRect.left) * this.ratio;\n", + " var y = (event.clientY - boundingRect.top) * this.ratio;\n", + "\n", + " this.send_message(name, {\n", + " x: x,\n", + " y: y,\n", + " button: event.button,\n", + " step: event.step,\n", + " modifiers: getModifiers(event),\n", + " guiEvent: simpleKeys(event),\n", + " });\n", + "\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", + " // Handle any extra behaviour associated with a key event\n", + "};\n", + "\n", + "mpl.figure.prototype.key_event = function (event, name) {\n", + " // Prevent repeat events\n", + " if (name === 'key_press') {\n", + " if (event.key === this._key) {\n", + " return;\n", + " } else {\n", + " this._key = event.key;\n", + " }\n", + " }\n", + " if (name === 'key_release') {\n", + " this._key = null;\n", + " }\n", + "\n", + " var value = '';\n", + " if (event.ctrlKey && event.key !== 'Control') {\n", + " value += 'ctrl+';\n", + " }\n", + " else if (event.altKey && event.key !== 'Alt') {\n", + " value += 'alt+';\n", + " }\n", + " else if (event.shiftKey && event.key !== 'Shift') {\n", + " value += 'shift+';\n", + " }\n", + "\n", + " value += 'k' + event.key;\n", + "\n", + " this._key_event_extra(event, name);\n", + "\n", + " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", + " if (name === 'download') {\n", + " this.handle_save(this, null);\n", + " } else {\n", + " this.send_message('toolbar_button', { name: name });\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", + " this.message.textContent = tooltip;\n", + "};\n", + "\n", + "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n", + "// prettier-ignore\n", + "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n", + "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n", + "\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n", + "\n", + "mpl.default_extension = \"png\";/* global mpl */\n", + "\n", + "var comm_websocket_adapter = function (comm) {\n", + " // Create a \"websocket\"-like object which calls the given IPython comm\n", + " // object with the appropriate methods. Currently this is a non binary\n", + " // socket, so there is still some room for performance tuning.\n", + " var ws = {};\n", + "\n", + " ws.binaryType = comm.kernel.ws.binaryType;\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " function updateReadyState(_event) {\n", + " if (comm.kernel.ws) {\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " } else {\n", + " ws.readyState = 3; // Closed state.\n", + " }\n", + " }\n", + " comm.kernel.ws.addEventListener('open', updateReadyState);\n", + " comm.kernel.ws.addEventListener('close', updateReadyState);\n", + " comm.kernel.ws.addEventListener('error', updateReadyState);\n", + "\n", + " ws.close = function () {\n", + " comm.close();\n", + " };\n", + " ws.send = function (m) {\n", + " //console.log('sending', m);\n", + " comm.send(m);\n", + " };\n", + " // Register the callback with on_msg.\n", + " comm.on_msg(function (msg) {\n", + " //console.log('receiving', msg['content']['data'], msg);\n", + " var data = msg['content']['data'];\n", + " if (data['blob'] !== undefined) {\n", + " data = {\n", + " data: new Blob(msg['buffers'], { type: data['blob'] }),\n", + " };\n", + " }\n", + " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", + " ws.onmessage(data);\n", + " });\n", + " return ws;\n", + "};\n", + "\n", + "mpl.mpl_figure_comm = function (comm, msg) {\n", + " // This is the function which gets called when the mpl process\n", + " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", + "\n", + " var id = msg.content.data.id;\n", + " // Get hold of the div created by the display call when the Comm\n", + " // socket was opened in Python.\n", + " var element = document.getElementById(id);\n", + " var ws_proxy = comm_websocket_adapter(comm);\n", + "\n", + " function ondownload(figure, _format) {\n", + " window.open(figure.canvas.toDataURL());\n", + " }\n", + "\n", + " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", + "\n", + " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", + " // web socket which is closed, not our websocket->open comm proxy.\n", + " ws_proxy.onopen();\n", + "\n", + " fig.parent_element = element;\n", + " fig.cell_info = mpl.find_output_cell(\"
\");\n", + " if (!fig.cell_info) {\n", + " console.error('Failed to find cell for figure', id, fig);\n", + " return;\n", + " }\n", + " fig.cell_info[0].output_area.element.on(\n", + " 'cleared',\n", + " { fig: fig },\n", + " fig._remove_fig_handler\n", + " );\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_close = function (fig, msg) {\n", + " var width = fig.canvas.width / fig.ratio;\n", + " fig.cell_info[0].output_area.element.off(\n", + " 'cleared',\n", + " fig._remove_fig_handler\n", + " );\n", + " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n", + "\n", + " // Update the output cell to use the data from the current canvas.\n", + " fig.push_to_output();\n", + " var dataURL = fig.canvas.toDataURL();\n", + " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", + " // the notebook keyboard shortcuts fail.\n", + " IPython.keyboard_manager.enable();\n", + " fig.parent_element.innerHTML =\n", + " '';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", + " // Turn the data on the canvas into data in the output cell.\n", + " var width = this.canvas.width / this.ratio;\n", + " var dataURL = this.canvas.toDataURL();\n", + " this.cell_info[1]['text/html'] =\n", + " '';\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Tell IPython that the notebook contents must change.\n", + " IPython.notebook.set_dirty(true);\n", + " this.send_message('ack', {});\n", + " var fig = this;\n", + " // Wait a second, then push the new image to the DOM so\n", + " // that it is saved nicely (might be nice to debounce this).\n", + " setTimeout(function () {\n", + " fig.push_to_output();\n", + " }, 1000);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'btn-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " var button;\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " continue;\n", + " }\n", + "\n", + " button = fig.buttons[name] = document.createElement('button');\n", + " button.classList = 'btn btn-default';\n", + " button.href = '#';\n", + " button.title = name;\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message pull-right';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = document.createElement('div');\n", + " buttongrp.classList = 'btn-group inline pull-right';\n", + " button = document.createElement('button');\n", + " button.classList = 'btn btn-mini btn-primary';\n", + " button.href = '#';\n", + " button.title = 'Stop Interaction';\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', function (_evt) {\n", + " fig.handle_close(fig, {});\n", + " });\n", + " button.addEventListener(\n", + " 'mouseover',\n", + " on_mouseover_closure('Stop Interaction')\n", + " );\n", + " buttongrp.appendChild(button);\n", + " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", + " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", + "};\n", + "\n", + "mpl.figure.prototype._remove_fig_handler = function (event) {\n", + " var fig = event.data.fig;\n", + " if (event.target !== this) {\n", + " // Ignore bubbled events from children.\n", + " return;\n", + " }\n", + " fig.close_ws(fig, {});\n", + "};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (el) {\n", + " // this is important to make the div 'focusable\n", + " el.setAttribute('tabindex', 0);\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " } else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which === 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " fig.ondownload(fig, null);\n", + "};\n", + "\n", + "mpl.find_output_cell = function (html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i = 0; i < ncells; i++) {\n", + " var cell = cells[i];\n", + " if (cell.cell_type === 'code') {\n", + " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", + " var data = cell.output_area.outputs[j];\n", + " if (data.data) {\n", + " // IPython >= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] === html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "};\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel !== null) {\n", + " IPython.notebook.kernel.comm_manager.register_target(\n", + " 'matplotlib',\n", + " mpl.mpl_figure_comm\n", + " );\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "/* global mpl */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function () {\n", + " if (typeof WebSocket !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof MozWebSocket !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert(\n", + " 'Your browser does not have WebSocket support. ' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.'\n", + " );\n", + " }\n", + "};\n", + "\n", + "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = this.ws.binaryType !== undefined;\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById('mpl-warnings');\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent =\n", + " 'This browser does not support binary websocket messages. ' +\n", + " 'Performance may be slow.';\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = document.createElement('div');\n", + " this.root.setAttribute('style', 'display: inline-block');\n", + " this._root_extra_style(this.root);\n", + "\n", + " parent_element.appendChild(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message('supports_binary', { value: fig.supports_binary });\n", + " fig.send_message('send_image_mode', {});\n", + " if (fig.ratio !== 1) {\n", + " fig.send_message('set_device_pixel_ratio', {\n", + " device_pixel_ratio: fig.ratio,\n", + " });\n", + " }\n", + " fig.send_message('refresh', {});\n", + " };\n", + "\n", + " this.imageObj.onload = function () {\n", + " if (fig.image_mode === 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function () {\n", + " fig.ws.close();\n", + " };\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "};\n", + "\n", + "mpl.figure.prototype._init_header = function () {\n", + " var titlebar = document.createElement('div');\n", + " titlebar.classList =\n", + " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", + " var titletext = document.createElement('div');\n", + " titletext.classList = 'ui-dialog-title';\n", + " titletext.setAttribute(\n", + " 'style',\n", + " 'width: 100%; text-align: center; padding: 3px;'\n", + " );\n", + " titlebar.appendChild(titletext);\n", + " this.root.appendChild(titlebar);\n", + " this.header = titletext;\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._init_canvas = function () {\n", + " var fig = this;\n", + "\n", + " var canvas_div = (this.canvas_div = document.createElement('div'));\n", + " canvas_div.setAttribute('tabindex', '0');\n", + " canvas_div.setAttribute(\n", + " 'style',\n", + " 'border: 1px solid #ddd;' +\n", + " 'box-sizing: content-box;' +\n", + " 'clear: both;' +\n", + " 'min-height: 1px;' +\n", + " 'min-width: 1px;' +\n", + " 'outline: 0;' +\n", + " 'overflow: hidden;' +\n", + " 'position: relative;' +\n", + " 'resize: both;' +\n", + " 'z-index: 2;'\n", + " );\n", + "\n", + " function on_keyboard_event_closure(name) {\n", + " return function (event) {\n", + " return fig.key_event(event, name);\n", + " };\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'keydown',\n", + " on_keyboard_event_closure('key_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'keyup',\n", + " on_keyboard_event_closure('key_release')\n", + " );\n", + "\n", + " this._canvas_extra_style(canvas_div);\n", + " this.root.appendChild(canvas_div);\n", + "\n", + " var canvas = (this.canvas = document.createElement('canvas'));\n", + " canvas.classList.add('mpl-canvas');\n", + " canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box;' +\n", + " 'pointer-events: none;' +\n", + " 'position: relative;' +\n", + " 'z-index: 0;'\n", + " );\n", + "\n", + " this.context = canvas.getContext('2d');\n", + "\n", + " var backingStore =\n", + " this.context.backingStorePixelRatio ||\n", + " this.context.webkitBackingStorePixelRatio ||\n", + " this.context.mozBackingStorePixelRatio ||\n", + " this.context.msBackingStorePixelRatio ||\n", + " this.context.oBackingStorePixelRatio ||\n", + " this.context.backingStorePixelRatio ||\n", + " 1;\n", + "\n", + " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", + " 'canvas'\n", + " ));\n", + " rubberband_canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box;' +\n", + " 'left: 0;' +\n", + " 'pointer-events: none;' +\n", + " 'position: absolute;' +\n", + " 'top: 0;' +\n", + " 'z-index: 1;'\n", + " );\n", + "\n", + " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n", + " if (this.ResizeObserver === undefined) {\n", + " if (window.ResizeObserver !== undefined) {\n", + " this.ResizeObserver = window.ResizeObserver;\n", + " } else {\n", + " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n", + " this.ResizeObserver = obs.ResizeObserver;\n", + " }\n", + " }\n", + "\n", + " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n", + " var nentries = entries.length;\n", + " for (var i = 0; i < nentries; i++) {\n", + " var entry = entries[i];\n", + " var width, height;\n", + " if (entry.contentBoxSize) {\n", + " if (entry.contentBoxSize instanceof Array) {\n", + " // Chrome 84 implements new version of spec.\n", + " width = entry.contentBoxSize[0].inlineSize;\n", + " height = entry.contentBoxSize[0].blockSize;\n", + " } else {\n", + " // Firefox implements old version of spec.\n", + " width = entry.contentBoxSize.inlineSize;\n", + " height = entry.contentBoxSize.blockSize;\n", + " }\n", + " } else {\n", + " // Chrome <84 implements even older version of spec.\n", + " width = entry.contentRect.width;\n", + " height = entry.contentRect.height;\n", + " }\n", + "\n", + " // Keep the size of the canvas and rubber band canvas in sync with\n", + " // the canvas container.\n", + " if (entry.devicePixelContentBoxSize) {\n", + " // Chrome 84 implements new version of spec.\n", + " canvas.setAttribute(\n", + " 'width',\n", + " entry.devicePixelContentBoxSize[0].inlineSize\n", + " );\n", + " canvas.setAttribute(\n", + " 'height',\n", + " entry.devicePixelContentBoxSize[0].blockSize\n", + " );\n", + " } else {\n", + " canvas.setAttribute('width', width * fig.ratio);\n", + " canvas.setAttribute('height', height * fig.ratio);\n", + " }\n", + " /* This rescales the canvas back to display pixels, so that it\n", + " * appears correct on HiDPI screens. */\n", + " canvas.style.width = width + 'px';\n", + " canvas.style.height = height + 'px';\n", + "\n", + " rubberband_canvas.setAttribute('width', width);\n", + " rubberband_canvas.setAttribute('height', height);\n", + "\n", + " // And update the size in Python. We ignore the initial 0/0 size\n", + " // that occurs as the element is placed into the DOM, which should\n", + " // otherwise not happen due to the minimum size styling.\n", + " if (fig.ws.readyState == 1 && width != 0 && height != 0) {\n", + " fig.request_resize(width, height);\n", + " }\n", + " }\n", + " });\n", + " this.resizeObserverInstance.observe(canvas_div);\n", + "\n", + " function on_mouse_event_closure(name) {\n", + " /* User Agent sniffing is bad, but WebKit is busted:\n", + " * https://bugs.webkit.org/show_bug.cgi?id=144526\n", + " * https://bugs.webkit.org/show_bug.cgi?id=181818\n", + " * The worst that happens here is that they get an extra browser\n", + " * selection when dragging, if this check fails to catch them.\n", + " */\n", + " var UA = navigator.userAgent;\n", + " var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n", + " if(isWebKit) {\n", + " return function (event) {\n", + " /* This prevents the web browser from automatically changing to\n", + " * the text insertion cursor when the button is pressed. We\n", + " * want to control all of the cursor setting manually through\n", + " * the 'cursor' event from matplotlib */\n", + " event.preventDefault()\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " } else {\n", + " return function (event) {\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " }\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'mousedown',\n", + " on_mouse_event_closure('button_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'mouseup',\n", + " on_mouse_event_closure('button_release')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'dblclick',\n", + " on_mouse_event_closure('dblclick')\n", + " );\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " canvas_div.addEventListener(\n", + " 'mousemove',\n", + " on_mouse_event_closure('motion_notify')\n", + " );\n", + "\n", + " canvas_div.addEventListener(\n", + " 'mouseenter',\n", + " on_mouse_event_closure('figure_enter')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'mouseleave',\n", + " on_mouse_event_closure('figure_leave')\n", + " );\n", + "\n", + " canvas_div.addEventListener('wheel', function (event) {\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " on_mouse_event_closure('scroll')(event);\n", + " });\n", + "\n", + " canvas_div.appendChild(canvas);\n", + " canvas_div.appendChild(rubberband_canvas);\n", + "\n", + " this.rubberband_context = rubberband_canvas.getContext('2d');\n", + " this.rubberband_context.strokeStyle = '#000000';\n", + "\n", + " this._resize_canvas = function (width, height, forward) {\n", + " if (forward) {\n", + " canvas_div.style.width = width + 'px';\n", + " canvas_div.style.height = height + 'px';\n", + " }\n", + " };\n", + "\n", + " // Disable right mouse context menu.\n", + " canvas_div.addEventListener('contextmenu', function (_e) {\n", + " event.preventDefault();\n", + " return false;\n", + " });\n", + "\n", + " function set_focus() {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'mpl-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " continue;\n", + " }\n", + "\n", + " var button = (fig.buttons[name] = document.createElement('button'));\n", + " button.classList = 'mpl-widget';\n", + " button.setAttribute('role', 'button');\n", + " button.setAttribute('aria-disabled', 'false');\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + "\n", + " var icon_img = document.createElement('img');\n", + " icon_img.src = '_images/' + image + '.png';\n", + " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", + " icon_img.alt = tooltip;\n", + " button.appendChild(icon_img);\n", + "\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " var fmt_picker = document.createElement('select');\n", + " fmt_picker.classList = 'mpl-widget';\n", + " toolbar.appendChild(fmt_picker);\n", + " this.format_dropdown = fmt_picker;\n", + "\n", + " for (var ind in mpl.extensions) {\n", + " var fmt = mpl.extensions[ind];\n", + " var option = document.createElement('option');\n", + " option.selected = fmt === mpl.default_extension;\n", + " option.innerHTML = fmt;\n", + " fmt_picker.appendChild(option);\n", + " }\n", + "\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "};\n", + "\n", + "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", + " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", + " // which will in turn request a refresh of the image.\n", + " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", + "};\n", + "\n", + "mpl.figure.prototype.send_message = function (type, properties) {\n", + " properties['type'] = type;\n", + " properties['figure_id'] = this.id;\n", + " this.ws.send(JSON.stringify(properties));\n", + "};\n", + "\n", + "mpl.figure.prototype.send_draw_message = function () {\n", + " if (!this.waiting) {\n", + " this.waiting = true;\n", + " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " var format_dropdown = fig.format_dropdown;\n", + " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", + " fig.ondownload(fig, format);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", + " var size = msg['size'];\n", + " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", + " fig._resize_canvas(size[0], size[1], msg['forward']);\n", + " fig.send_message('refresh', {});\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", + " var x0 = msg['x0'] / fig.ratio;\n", + " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n", + " var x1 = msg['x1'] / fig.ratio;\n", + " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n", + " x0 = Math.floor(x0) + 0.5;\n", + " y0 = Math.floor(y0) + 0.5;\n", + " x1 = Math.floor(x1) + 0.5;\n", + " y1 = Math.floor(y1) + 0.5;\n", + " var min_x = Math.min(x0, x1);\n", + " var min_y = Math.min(y0, y1);\n", + " var width = Math.abs(x1 - x0);\n", + " var height = Math.abs(y1 - y0);\n", + "\n", + " fig.rubberband_context.clearRect(\n", + " 0,\n", + " 0,\n", + " fig.canvas.width / fig.ratio,\n", + " fig.canvas.height / fig.ratio\n", + " );\n", + "\n", + " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", + " // Updates the figure title.\n", + " fig.header.textContent = msg['label'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", + " fig.canvas_div.style.cursor = msg['cursor'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_message = function (fig, msg) {\n", + " fig.message.textContent = msg['message'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", + " // Request the server to send over a new figure.\n", + " fig.send_draw_message();\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", + " fig.image_mode = msg['mode'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", + " for (var key in msg) {\n", + " if (!(key in fig.buttons)) {\n", + " continue;\n", + " }\n", + " fig.buttons[key].disabled = !msg[key];\n", + " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", + " if (msg['mode'] === 'PAN') {\n", + " fig.buttons['Pan'].classList.add('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " } else if (msg['mode'] === 'ZOOM') {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.add('active');\n", + " } else {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Called whenever the canvas gets updated.\n", + " this.send_message('ack', {});\n", + "};\n", + "\n", + "// A function to construct a web socket function for onmessage handling.\n", + "// Called in the figure constructor.\n", + "mpl.figure.prototype._make_on_message_function = function (fig) {\n", + " return function socket_on_message(evt) {\n", + " if (evt.data instanceof Blob) {\n", + " var img = evt.data;\n", + " if (img.type !== 'image/png') {\n", + " /* FIXME: We get \"Resource interpreted as Image but\n", + " * transferred with MIME type text/plain:\" errors on\n", + " * Chrome. But how to set the MIME type? It doesn't seem\n", + " * to be part of the websocket stream */\n", + " img.type = 'image/png';\n", + " }\n", + "\n", + " /* Free the memory for the previous frames */\n", + " if (fig.imageObj.src) {\n", + " (window.URL || window.webkitURL).revokeObjectURL(\n", + " fig.imageObj.src\n", + " );\n", + " }\n", + "\n", + " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", + " img\n", + " );\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " } else if (\n", + " typeof evt.data === 'string' &&\n", + " evt.data.slice(0, 21) === 'data:image/png;base64'\n", + " ) {\n", + " fig.imageObj.src = evt.data;\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " }\n", + "\n", + " var msg = JSON.parse(evt.data);\n", + " var msg_type = msg['type'];\n", + "\n", + " // Call the \"handle_{type}\" callback, which takes\n", + " // the figure and JSON message as its only arguments.\n", + " try {\n", + " var callback = fig['handle_' + msg_type];\n", + " } catch (e) {\n", + " console.log(\n", + " \"No handler for the '\" + msg_type + \"' message type: \",\n", + " msg\n", + " );\n", + " return;\n", + " }\n", + "\n", + " if (callback) {\n", + " try {\n", + " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", + " callback(fig, msg);\n", + " } catch (e) {\n", + " console.log(\n", + " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", + " e,\n", + " e.stack,\n", + " msg\n", + " );\n", + " }\n", + " }\n", + " };\n", + "};\n", + "\n", + "function getModifiers(event) {\n", + " var mods = [];\n", + " if (event.ctrlKey) {\n", + " mods.push('ctrl');\n", + " }\n", + " if (event.altKey) {\n", + " mods.push('alt');\n", + " }\n", + " if (event.shiftKey) {\n", + " mods.push('shift');\n", + " }\n", + " if (event.metaKey) {\n", + " mods.push('meta');\n", + " }\n", + " return mods;\n", + "}\n", + "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * https://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys(original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object') {\n", + " obj[key] = original[key];\n", + " }\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", + "mpl.figure.prototype.mouse_event = function (event, name) {\n", + " if (name === 'button_press') {\n", + " this.canvas.focus();\n", + " this.canvas_div.focus();\n", + " }\n", + "\n", + " // from https://stackoverflow.com/q/1114465\n", + " var boundingRect = this.canvas.getBoundingClientRect();\n", + " var x = (event.clientX - boundingRect.left) * this.ratio;\n", + " var y = (event.clientY - boundingRect.top) * this.ratio;\n", + "\n", + " this.send_message(name, {\n", + " x: x,\n", + " y: y,\n", + " button: event.button,\n", + " step: event.step,\n", + " modifiers: getModifiers(event),\n", + " guiEvent: simpleKeys(event),\n", + " });\n", + "\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", + " // Handle any extra behaviour associated with a key event\n", + "};\n", + "\n", + "mpl.figure.prototype.key_event = function (event, name) {\n", + " // Prevent repeat events\n", + " if (name === 'key_press') {\n", + " if (event.key === this._key) {\n", + " return;\n", + " } else {\n", + " this._key = event.key;\n", + " }\n", + " }\n", + " if (name === 'key_release') {\n", + " this._key = null;\n", + " }\n", + "\n", + " var value = '';\n", + " if (event.ctrlKey && event.key !== 'Control') {\n", + " value += 'ctrl+';\n", + " }\n", + " else if (event.altKey && event.key !== 'Alt') {\n", + " value += 'alt+';\n", + " }\n", + " else if (event.shiftKey && event.key !== 'Shift') {\n", + " value += 'shift+';\n", + " }\n", + "\n", + " value += 'k' + event.key;\n", + "\n", + " this._key_event_extra(event, name);\n", + "\n", + " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", + " if (name === 'download') {\n", + " this.handle_save(this, null);\n", + " } else {\n", + " this.send_message('toolbar_button', { name: name });\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", + " this.message.textContent = tooltip;\n", + "};\n", + "\n", + "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n", + "// prettier-ignore\n", + "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n", + "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n", + "\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n", + "\n", + "mpl.default_extension = \"png\";/* global mpl */\n", + "\n", + "var comm_websocket_adapter = function (comm) {\n", + " // Create a \"websocket\"-like object which calls the given IPython comm\n", + " // object with the appropriate methods. Currently this is a non binary\n", + " // socket, so there is still some room for performance tuning.\n", + " var ws = {};\n", + "\n", + " ws.binaryType = comm.kernel.ws.binaryType;\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " function updateReadyState(_event) {\n", + " if (comm.kernel.ws) {\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " } else {\n", + " ws.readyState = 3; // Closed state.\n", + " }\n", + " }\n", + " comm.kernel.ws.addEventListener('open', updateReadyState);\n", + " comm.kernel.ws.addEventListener('close', updateReadyState);\n", + " comm.kernel.ws.addEventListener('error', updateReadyState);\n", + "\n", + " ws.close = function () {\n", + " comm.close();\n", + " };\n", + " ws.send = function (m) {\n", + " //console.log('sending', m);\n", + " comm.send(m);\n", + " };\n", + " // Register the callback with on_msg.\n", + " comm.on_msg(function (msg) {\n", + " //console.log('receiving', msg['content']['data'], msg);\n", + " var data = msg['content']['data'];\n", + " if (data['blob'] !== undefined) {\n", + " data = {\n", + " data: new Blob(msg['buffers'], { type: data['blob'] }),\n", + " };\n", + " }\n", + " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", + " ws.onmessage(data);\n", + " });\n", + " return ws;\n", + "};\n", + "\n", + "mpl.mpl_figure_comm = function (comm, msg) {\n", + " // This is the function which gets called when the mpl process\n", + " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", + "\n", + " var id = msg.content.data.id;\n", + " // Get hold of the div created by the display call when the Comm\n", + " // socket was opened in Python.\n", + " var element = document.getElementById(id);\n", + " var ws_proxy = comm_websocket_adapter(comm);\n", + "\n", + " function ondownload(figure, _format) {\n", + " window.open(figure.canvas.toDataURL());\n", + " }\n", + "\n", + " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", + "\n", + " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", + " // web socket which is closed, not our websocket->open comm proxy.\n", + " ws_proxy.onopen();\n", + "\n", + " fig.parent_element = element;\n", + " fig.cell_info = mpl.find_output_cell(\"
\");\n", + " if (!fig.cell_info) {\n", + " console.error('Failed to find cell for figure', id, fig);\n", + " return;\n", + " }\n", + " fig.cell_info[0].output_area.element.on(\n", + " 'cleared',\n", + " { fig: fig },\n", + " fig._remove_fig_handler\n", + " );\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_close = function (fig, msg) {\n", + " var width = fig.canvas.width / fig.ratio;\n", + " fig.cell_info[0].output_area.element.off(\n", + " 'cleared',\n", + " fig._remove_fig_handler\n", + " );\n", + " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n", + "\n", + " // Update the output cell to use the data from the current canvas.\n", + " fig.push_to_output();\n", + " var dataURL = fig.canvas.toDataURL();\n", + " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", + " // the notebook keyboard shortcuts fail.\n", + " IPython.keyboard_manager.enable();\n", + " fig.parent_element.innerHTML =\n", + " '';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", + " // Turn the data on the canvas into data in the output cell.\n", + " var width = this.canvas.width / this.ratio;\n", + " var dataURL = this.canvas.toDataURL();\n", + " this.cell_info[1]['text/html'] =\n", + " '';\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Tell IPython that the notebook contents must change.\n", + " IPython.notebook.set_dirty(true);\n", + " this.send_message('ack', {});\n", + " var fig = this;\n", + " // Wait a second, then push the new image to the DOM so\n", + " // that it is saved nicely (might be nice to debounce this).\n", + " setTimeout(function () {\n", + " fig.push_to_output();\n", + " }, 1000);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'btn-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " var button;\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " continue;\n", + " }\n", + "\n", + " button = fig.buttons[name] = document.createElement('button');\n", + " button.classList = 'btn btn-default';\n", + " button.href = '#';\n", + " button.title = name;\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message pull-right';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = document.createElement('div');\n", + " buttongrp.classList = 'btn-group inline pull-right';\n", + " button = document.createElement('button');\n", + " button.classList = 'btn btn-mini btn-primary';\n", + " button.href = '#';\n", + " button.title = 'Stop Interaction';\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', function (_evt) {\n", + " fig.handle_close(fig, {});\n", + " });\n", + " button.addEventListener(\n", + " 'mouseover',\n", + " on_mouseover_closure('Stop Interaction')\n", + " );\n", + " buttongrp.appendChild(button);\n", + " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", + " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", + "};\n", + "\n", + "mpl.figure.prototype._remove_fig_handler = function (event) {\n", + " var fig = event.data.fig;\n", + " if (event.target !== this) {\n", + " // Ignore bubbled events from children.\n", + " return;\n", + " }\n", + " fig.close_ws(fig, {});\n", + "};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (el) {\n", + " // this is important to make the div 'focusable\n", + " el.setAttribute('tabindex', 0);\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " } else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which === 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " fig.ondownload(fig, null);\n", + "};\n", + "\n", + "mpl.find_output_cell = function (html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i = 0; i < ncells; i++) {\n", + " var cell = cells[i];\n", + " if (cell.cell_type === 'code') {\n", + " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", + " var data = cell.output_area.outputs[j];\n", + " if (data.data) {\n", + " // IPython >= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] === html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "};\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel !== null) {\n", + " IPython.notebook.kernel.comm_manager.register_target(\n", + " 'matplotlib',\n", + " mpl.mpl_figure_comm\n", + " );\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "/* global mpl */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function () {\n", + " if (typeof WebSocket !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof MozWebSocket !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert(\n", + " 'Your browser does not have WebSocket support. ' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.'\n", + " );\n", + " }\n", + "};\n", + "\n", + "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = this.ws.binaryType !== undefined;\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById('mpl-warnings');\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent =\n", + " 'This browser does not support binary websocket messages. ' +\n", + " 'Performance may be slow.';\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = document.createElement('div');\n", + " this.root.setAttribute('style', 'display: inline-block');\n", + " this._root_extra_style(this.root);\n", + "\n", + " parent_element.appendChild(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message('supports_binary', { value: fig.supports_binary });\n", + " fig.send_message('send_image_mode', {});\n", + " if (fig.ratio !== 1) {\n", + " fig.send_message('set_device_pixel_ratio', {\n", + " device_pixel_ratio: fig.ratio,\n", + " });\n", + " }\n", + " fig.send_message('refresh', {});\n", + " };\n", + "\n", + " this.imageObj.onload = function () {\n", + " if (fig.image_mode === 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function () {\n", + " fig.ws.close();\n", + " };\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "};\n", + "\n", + "mpl.figure.prototype._init_header = function () {\n", + " var titlebar = document.createElement('div');\n", + " titlebar.classList =\n", + " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", + " var titletext = document.createElement('div');\n", + " titletext.classList = 'ui-dialog-title';\n", + " titletext.setAttribute(\n", + " 'style',\n", + " 'width: 100%; text-align: center; padding: 3px;'\n", + " );\n", + " titlebar.appendChild(titletext);\n", + " this.root.appendChild(titlebar);\n", + " this.header = titletext;\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._init_canvas = function () {\n", + " var fig = this;\n", + "\n", + " var canvas_div = (this.canvas_div = document.createElement('div'));\n", + " canvas_div.setAttribute('tabindex', '0');\n", + " canvas_div.setAttribute(\n", + " 'style',\n", + " 'border: 1px solid #ddd;' +\n", + " 'box-sizing: content-box;' +\n", + " 'clear: both;' +\n", + " 'min-height: 1px;' +\n", + " 'min-width: 1px;' +\n", + " 'outline: 0;' +\n", + " 'overflow: hidden;' +\n", + " 'position: relative;' +\n", + " 'resize: both;' +\n", + " 'z-index: 2;'\n", + " );\n", + "\n", + " function on_keyboard_event_closure(name) {\n", + " return function (event) {\n", + " return fig.key_event(event, name);\n", + " };\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'keydown',\n", + " on_keyboard_event_closure('key_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'keyup',\n", + " on_keyboard_event_closure('key_release')\n", + " );\n", + "\n", + " this._canvas_extra_style(canvas_div);\n", + " this.root.appendChild(canvas_div);\n", + "\n", + " var canvas = (this.canvas = document.createElement('canvas'));\n", + " canvas.classList.add('mpl-canvas');\n", + " canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box;' +\n", + " 'pointer-events: none;' +\n", + " 'position: relative;' +\n", + " 'z-index: 0;'\n", + " );\n", + "\n", + " this.context = canvas.getContext('2d');\n", + "\n", + " var backingStore =\n", + " this.context.backingStorePixelRatio ||\n", + " this.context.webkitBackingStorePixelRatio ||\n", + " this.context.mozBackingStorePixelRatio ||\n", + " this.context.msBackingStorePixelRatio ||\n", + " this.context.oBackingStorePixelRatio ||\n", + " this.context.backingStorePixelRatio ||\n", + " 1;\n", + "\n", + " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", + " 'canvas'\n", + " ));\n", + " rubberband_canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box;' +\n", + " 'left: 0;' +\n", + " 'pointer-events: none;' +\n", + " 'position: absolute;' +\n", + " 'top: 0;' +\n", + " 'z-index: 1;'\n", + " );\n", + "\n", + " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n", + " if (this.ResizeObserver === undefined) {\n", + " if (window.ResizeObserver !== undefined) {\n", + " this.ResizeObserver = window.ResizeObserver;\n", + " } else {\n", + " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n", + " this.ResizeObserver = obs.ResizeObserver;\n", + " }\n", + " }\n", + "\n", + " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n", + " var nentries = entries.length;\n", + " for (var i = 0; i < nentries; i++) {\n", + " var entry = entries[i];\n", + " var width, height;\n", + " if (entry.contentBoxSize) {\n", + " if (entry.contentBoxSize instanceof Array) {\n", + " // Chrome 84 implements new version of spec.\n", + " width = entry.contentBoxSize[0].inlineSize;\n", + " height = entry.contentBoxSize[0].blockSize;\n", + " } else {\n", + " // Firefox implements old version of spec.\n", + " width = entry.contentBoxSize.inlineSize;\n", + " height = entry.contentBoxSize.blockSize;\n", + " }\n", + " } else {\n", + " // Chrome <84 implements even older version of spec.\n", + " width = entry.contentRect.width;\n", + " height = entry.contentRect.height;\n", + " }\n", + "\n", + " // Keep the size of the canvas and rubber band canvas in sync with\n", + " // the canvas container.\n", + " if (entry.devicePixelContentBoxSize) {\n", + " // Chrome 84 implements new version of spec.\n", + " canvas.setAttribute(\n", + " 'width',\n", + " entry.devicePixelContentBoxSize[0].inlineSize\n", + " );\n", + " canvas.setAttribute(\n", + " 'height',\n", + " entry.devicePixelContentBoxSize[0].blockSize\n", + " );\n", + " } else {\n", + " canvas.setAttribute('width', width * fig.ratio);\n", + " canvas.setAttribute('height', height * fig.ratio);\n", + " }\n", + " /* This rescales the canvas back to display pixels, so that it\n", + " * appears correct on HiDPI screens. */\n", + " canvas.style.width = width + 'px';\n", + " canvas.style.height = height + 'px';\n", + "\n", + " rubberband_canvas.setAttribute('width', width);\n", + " rubberband_canvas.setAttribute('height', height);\n", + "\n", + " // And update the size in Python. We ignore the initial 0/0 size\n", + " // that occurs as the element is placed into the DOM, which should\n", + " // otherwise not happen due to the minimum size styling.\n", + " if (fig.ws.readyState == 1 && width != 0 && height != 0) {\n", + " fig.request_resize(width, height);\n", + " }\n", + " }\n", + " });\n", + " this.resizeObserverInstance.observe(canvas_div);\n", + "\n", + " function on_mouse_event_closure(name) {\n", + " /* User Agent sniffing is bad, but WebKit is busted:\n", + " * https://bugs.webkit.org/show_bug.cgi?id=144526\n", + " * https://bugs.webkit.org/show_bug.cgi?id=181818\n", + " * The worst that happens here is that they get an extra browser\n", + " * selection when dragging, if this check fails to catch them.\n", + " */\n", + " var UA = navigator.userAgent;\n", + " var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n", + " if(isWebKit) {\n", + " return function (event) {\n", + " /* This prevents the web browser from automatically changing to\n", + " * the text insertion cursor when the button is pressed. We\n", + " * want to control all of the cursor setting manually through\n", + " * the 'cursor' event from matplotlib */\n", + " event.preventDefault()\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " } else {\n", + " return function (event) {\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " }\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'mousedown',\n", + " on_mouse_event_closure('button_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'mouseup',\n", + " on_mouse_event_closure('button_release')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'dblclick',\n", + " on_mouse_event_closure('dblclick')\n", + " );\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " canvas_div.addEventListener(\n", + " 'mousemove',\n", + " on_mouse_event_closure('motion_notify')\n", + " );\n", + "\n", + " canvas_div.addEventListener(\n", + " 'mouseenter',\n", + " on_mouse_event_closure('figure_enter')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'mouseleave',\n", + " on_mouse_event_closure('figure_leave')\n", + " );\n", + "\n", + " canvas_div.addEventListener('wheel', function (event) {\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " on_mouse_event_closure('scroll')(event);\n", + " });\n", + "\n", + " canvas_div.appendChild(canvas);\n", + " canvas_div.appendChild(rubberband_canvas);\n", + "\n", + " this.rubberband_context = rubberband_canvas.getContext('2d');\n", + " this.rubberband_context.strokeStyle = '#000000';\n", + "\n", + " this._resize_canvas = function (width, height, forward) {\n", + " if (forward) {\n", + " canvas_div.style.width = width + 'px';\n", + " canvas_div.style.height = height + 'px';\n", + " }\n", + " };\n", + "\n", + " // Disable right mouse context menu.\n", + " canvas_div.addEventListener('contextmenu', function (_e) {\n", + " event.preventDefault();\n", + " return false;\n", + " });\n", + "\n", + " function set_focus() {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'mpl-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " continue;\n", + " }\n", + "\n", + " var button = (fig.buttons[name] = document.createElement('button'));\n", + " button.classList = 'mpl-widget';\n", + " button.setAttribute('role', 'button');\n", + " button.setAttribute('aria-disabled', 'false');\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + "\n", + " var icon_img = document.createElement('img');\n", + " icon_img.src = '_images/' + image + '.png';\n", + " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", + " icon_img.alt = tooltip;\n", + " button.appendChild(icon_img);\n", + "\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " var fmt_picker = document.createElement('select');\n", + " fmt_picker.classList = 'mpl-widget';\n", + " toolbar.appendChild(fmt_picker);\n", + " this.format_dropdown = fmt_picker;\n", + "\n", + " for (var ind in mpl.extensions) {\n", + " var fmt = mpl.extensions[ind];\n", + " var option = document.createElement('option');\n", + " option.selected = fmt === mpl.default_extension;\n", + " option.innerHTML = fmt;\n", + " fmt_picker.appendChild(option);\n", + " }\n", + "\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "};\n", + "\n", + "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", + " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", + " // which will in turn request a refresh of the image.\n", + " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", + "};\n", + "\n", + "mpl.figure.prototype.send_message = function (type, properties) {\n", + " properties['type'] = type;\n", + " properties['figure_id'] = this.id;\n", + " this.ws.send(JSON.stringify(properties));\n", + "};\n", + "\n", + "mpl.figure.prototype.send_draw_message = function () {\n", + " if (!this.waiting) {\n", + " this.waiting = true;\n", + " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " var format_dropdown = fig.format_dropdown;\n", + " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", + " fig.ondownload(fig, format);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", + " var size = msg['size'];\n", + " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", + " fig._resize_canvas(size[0], size[1], msg['forward']);\n", + " fig.send_message('refresh', {});\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", + " var x0 = msg['x0'] / fig.ratio;\n", + " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n", + " var x1 = msg['x1'] / fig.ratio;\n", + " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n", + " x0 = Math.floor(x0) + 0.5;\n", + " y0 = Math.floor(y0) + 0.5;\n", + " x1 = Math.floor(x1) + 0.5;\n", + " y1 = Math.floor(y1) + 0.5;\n", + " var min_x = Math.min(x0, x1);\n", + " var min_y = Math.min(y0, y1);\n", + " var width = Math.abs(x1 - x0);\n", + " var height = Math.abs(y1 - y0);\n", + "\n", + " fig.rubberband_context.clearRect(\n", + " 0,\n", + " 0,\n", + " fig.canvas.width / fig.ratio,\n", + " fig.canvas.height / fig.ratio\n", + " );\n", + "\n", + " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", + " // Updates the figure title.\n", + " fig.header.textContent = msg['label'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", + " fig.canvas_div.style.cursor = msg['cursor'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_message = function (fig, msg) {\n", + " fig.message.textContent = msg['message'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", + " // Request the server to send over a new figure.\n", + " fig.send_draw_message();\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", + " fig.image_mode = msg['mode'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", + " for (var key in msg) {\n", + " if (!(key in fig.buttons)) {\n", + " continue;\n", + " }\n", + " fig.buttons[key].disabled = !msg[key];\n", + " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", + " if (msg['mode'] === 'PAN') {\n", + " fig.buttons['Pan'].classList.add('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " } else if (msg['mode'] === 'ZOOM') {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.add('active');\n", + " } else {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Called whenever the canvas gets updated.\n", + " this.send_message('ack', {});\n", + "};\n", + "\n", + "// A function to construct a web socket function for onmessage handling.\n", + "// Called in the figure constructor.\n", + "mpl.figure.prototype._make_on_message_function = function (fig) {\n", + " return function socket_on_message(evt) {\n", + " if (evt.data instanceof Blob) {\n", + " var img = evt.data;\n", + " if (img.type !== 'image/png') {\n", + " /* FIXME: We get \"Resource interpreted as Image but\n", + " * transferred with MIME type text/plain:\" errors on\n", + " * Chrome. But how to set the MIME type? It doesn't seem\n", + " * to be part of the websocket stream */\n", + " img.type = 'image/png';\n", + " }\n", + "\n", + " /* Free the memory for the previous frames */\n", + " if (fig.imageObj.src) {\n", + " (window.URL || window.webkitURL).revokeObjectURL(\n", + " fig.imageObj.src\n", + " );\n", + " }\n", + "\n", + " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", + " img\n", + " );\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " } else if (\n", + " typeof evt.data === 'string' &&\n", + " evt.data.slice(0, 21) === 'data:image/png;base64'\n", + " ) {\n", + " fig.imageObj.src = evt.data;\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " }\n", + "\n", + " var msg = JSON.parse(evt.data);\n", + " var msg_type = msg['type'];\n", + "\n", + " // Call the \"handle_{type}\" callback, which takes\n", + " // the figure and JSON message as its only arguments.\n", + " try {\n", + " var callback = fig['handle_' + msg_type];\n", + " } catch (e) {\n", + " console.log(\n", + " \"No handler for the '\" + msg_type + \"' message type: \",\n", + " msg\n", + " );\n", + " return;\n", + " }\n", + "\n", + " if (callback) {\n", + " try {\n", + " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", + " callback(fig, msg);\n", + " } catch (e) {\n", + " console.log(\n", + " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", + " e,\n", + " e.stack,\n", + " msg\n", + " );\n", + " }\n", + " }\n", + " };\n", + "};\n", + "\n", + "function getModifiers(event) {\n", + " var mods = [];\n", + " if (event.ctrlKey) {\n", + " mods.push('ctrl');\n", + " }\n", + " if (event.altKey) {\n", + " mods.push('alt');\n", + " }\n", + " if (event.shiftKey) {\n", + " mods.push('shift');\n", + " }\n", + " if (event.metaKey) {\n", + " mods.push('meta');\n", + " }\n", + " return mods;\n", + "}\n", + "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * https://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys(original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object') {\n", + " obj[key] = original[key];\n", + " }\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", + "mpl.figure.prototype.mouse_event = function (event, name) {\n", + " if (name === 'button_press') {\n", + " this.canvas.focus();\n", + " this.canvas_div.focus();\n", + " }\n", + "\n", + " // from https://stackoverflow.com/q/1114465\n", + " var boundingRect = this.canvas.getBoundingClientRect();\n", + " var x = (event.clientX - boundingRect.left) * this.ratio;\n", + " var y = (event.clientY - boundingRect.top) * this.ratio;\n", + "\n", + " this.send_message(name, {\n", + " x: x,\n", + " y: y,\n", + " button: event.button,\n", + " step: event.step,\n", + " modifiers: getModifiers(event),\n", + " guiEvent: simpleKeys(event),\n", + " });\n", + "\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", + " // Handle any extra behaviour associated with a key event\n", + "};\n", + "\n", + "mpl.figure.prototype.key_event = function (event, name) {\n", + " // Prevent repeat events\n", + " if (name === 'key_press') {\n", + " if (event.key === this._key) {\n", + " return;\n", + " } else {\n", + " this._key = event.key;\n", + " }\n", + " }\n", + " if (name === 'key_release') {\n", + " this._key = null;\n", + " }\n", + "\n", + " var value = '';\n", + " if (event.ctrlKey && event.key !== 'Control') {\n", + " value += 'ctrl+';\n", + " }\n", + " else if (event.altKey && event.key !== 'Alt') {\n", + " value += 'alt+';\n", + " }\n", + " else if (event.shiftKey && event.key !== 'Shift') {\n", + " value += 'shift+';\n", + " }\n", + "\n", + " value += 'k' + event.key;\n", + "\n", + " this._key_event_extra(event, name);\n", + "\n", + " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", + " if (name === 'download') {\n", + " this.handle_save(this, null);\n", + " } else {\n", + " this.send_message('toolbar_button', { name: name });\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", + " this.message.textContent = tooltip;\n", + "};\n", + "\n", + "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n", + "// prettier-ignore\n", + "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n", + "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n", + "\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n", + "\n", + "mpl.default_extension = \"png\";/* global mpl */\n", + "\n", + "var comm_websocket_adapter = function (comm) {\n", + " // Create a \"websocket\"-like object which calls the given IPython comm\n", + " // object with the appropriate methods. Currently this is a non binary\n", + " // socket, so there is still some room for performance tuning.\n", + " var ws = {};\n", + "\n", + " ws.binaryType = comm.kernel.ws.binaryType;\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " function updateReadyState(_event) {\n", + " if (comm.kernel.ws) {\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " } else {\n", + " ws.readyState = 3; // Closed state.\n", + " }\n", + " }\n", + " comm.kernel.ws.addEventListener('open', updateReadyState);\n", + " comm.kernel.ws.addEventListener('close', updateReadyState);\n", + " comm.kernel.ws.addEventListener('error', updateReadyState);\n", + "\n", + " ws.close = function () {\n", + " comm.close();\n", + " };\n", + " ws.send = function (m) {\n", + " //console.log('sending', m);\n", + " comm.send(m);\n", + " };\n", + " // Register the callback with on_msg.\n", + " comm.on_msg(function (msg) {\n", + " //console.log('receiving', msg['content']['data'], msg);\n", + " var data = msg['content']['data'];\n", + " if (data['blob'] !== undefined) {\n", + " data = {\n", + " data: new Blob(msg['buffers'], { type: data['blob'] }),\n", + " };\n", + " }\n", + " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", + " ws.onmessage(data);\n", + " });\n", + " return ws;\n", + "};\n", + "\n", + "mpl.mpl_figure_comm = function (comm, msg) {\n", + " // This is the function which gets called when the mpl process\n", + " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", + "\n", + " var id = msg.content.data.id;\n", + " // Get hold of the div created by the display call when the Comm\n", + " // socket was opened in Python.\n", + " var element = document.getElementById(id);\n", + " var ws_proxy = comm_websocket_adapter(comm);\n", + "\n", + " function ondownload(figure, _format) {\n", + " window.open(figure.canvas.toDataURL());\n", + " }\n", + "\n", + " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", + "\n", + " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", + " // web socket which is closed, not our websocket->open comm proxy.\n", + " ws_proxy.onopen();\n", + "\n", + " fig.parent_element = element;\n", + " fig.cell_info = mpl.find_output_cell(\"
\");\n", + " if (!fig.cell_info) {\n", + " console.error('Failed to find cell for figure', id, fig);\n", + " return;\n", + " }\n", + " fig.cell_info[0].output_area.element.on(\n", + " 'cleared',\n", + " { fig: fig },\n", + " fig._remove_fig_handler\n", + " );\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_close = function (fig, msg) {\n", + " var width = fig.canvas.width / fig.ratio;\n", + " fig.cell_info[0].output_area.element.off(\n", + " 'cleared',\n", + " fig._remove_fig_handler\n", + " );\n", + " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n", + "\n", + " // Update the output cell to use the data from the current canvas.\n", + " fig.push_to_output();\n", + " var dataURL = fig.canvas.toDataURL();\n", + " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", + " // the notebook keyboard shortcuts fail.\n", + " IPython.keyboard_manager.enable();\n", + " fig.parent_element.innerHTML =\n", + " '';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", + " // Turn the data on the canvas into data in the output cell.\n", + " var width = this.canvas.width / this.ratio;\n", + " var dataURL = this.canvas.toDataURL();\n", + " this.cell_info[1]['text/html'] =\n", + " '';\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Tell IPython that the notebook contents must change.\n", + " IPython.notebook.set_dirty(true);\n", + " this.send_message('ack', {});\n", + " var fig = this;\n", + " // Wait a second, then push the new image to the DOM so\n", + " // that it is saved nicely (might be nice to debounce this).\n", + " setTimeout(function () {\n", + " fig.push_to_output();\n", + " }, 1000);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'btn-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " var button;\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " continue;\n", + " }\n", + "\n", + " button = fig.buttons[name] = document.createElement('button');\n", + " button.classList = 'btn btn-default';\n", + " button.href = '#';\n", + " button.title = name;\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message pull-right';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = document.createElement('div');\n", + " buttongrp.classList = 'btn-group inline pull-right';\n", + " button = document.createElement('button');\n", + " button.classList = 'btn btn-mini btn-primary';\n", + " button.href = '#';\n", + " button.title = 'Stop Interaction';\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', function (_evt) {\n", + " fig.handle_close(fig, {});\n", + " });\n", + " button.addEventListener(\n", + " 'mouseover',\n", + " on_mouseover_closure('Stop Interaction')\n", + " );\n", + " buttongrp.appendChild(button);\n", + " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", + " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", + "};\n", + "\n", + "mpl.figure.prototype._remove_fig_handler = function (event) {\n", + " var fig = event.data.fig;\n", + " if (event.target !== this) {\n", + " // Ignore bubbled events from children.\n", + " return;\n", + " }\n", + " fig.close_ws(fig, {});\n", + "};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (el) {\n", + " // this is important to make the div 'focusable\n", + " el.setAttribute('tabindex', 0);\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " } else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which === 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " fig.ondownload(fig, null);\n", + "};\n", + "\n", + "mpl.find_output_cell = function (html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i = 0; i < ncells; i++) {\n", + " var cell = cells[i];\n", + " if (cell.cell_type === 'code') {\n", + " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", + " var data = cell.output_area.outputs[j];\n", + " if (data.data) {\n", + " // IPython >= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] === html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "};\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel !== null) {\n", + " IPython.notebook.kernel.comm_manager.register_target(\n", + " 'matplotlib',\n", + " mpl.mpl_figure_comm\n", + " );\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "viz.plot_rank_histogram(xp.stats)\n", + "viz.plot_err_components(xp.stats)\n", + "viz.plot_hovmoller(xx)" + ] + }, + { + "cell_type": "markdown", + "id": "304273d1", + "metadata": {}, + "source": [ + "### Excercise (methods)\n", + "- Try out each of the above DA methods (currently commented out).\n", + "- Next, remove the call to `replay`, and set `liveplots=False` above.\n", + "- Now, use the iterative EnKS (`iEnKS`), and try to find a parameter combination\n", + " for it so that you achieve a lower `rmse.a` than with the `PartFilt`.\n", + "\n", + "*Hint*: In general, there is no free lunch. Similarly, not all methods work\n", + "for all problems; additionally, methods often have parameters that require\n", + "tuning. Luckily, in DAPPER, you should be able to find suitably tuned\n", + "configuration settings for various DA methods *in the files that define the\n", + "HMM*. If you do not find a suggested configuration for a given method, you\n", + "will have to tune it yourself. The example script `basic_2` shows how DAPPER\n", + "facilitates the tuning process, and `basic_3` takes this further." + ] + }, + { + "cell_type": "markdown", + "id": "de2e51e4", + "metadata": {}, + "source": [ + "### Excercise (models)\n", + "Run an experiment for each of these models\n", + "\n", + "- LotkaVolterra\n", + "- Lorenz96\n", + "- LA\n", + "- QG" + ] + }, + { + "cell_type": "markdown", + "id": "bee8dfc6", + "metadata": {}, + "source": [ + "### Excercise (diagnostics)\n", + "- Create a new code cell, and copy-paste the above `print(...tabulate)`\n", + " command into it. Then, replace `rmse` by `err.rms`. This should yield\n", + " the same printout, as is merely an abbreviation of the latter.\n", + "- Next, figure out how to print the time average *forecast (i.e. prior)* error\n", + " (and `rmv`) instead. Explain (in broad terms) why the values are larger than\n", + " for the *analysis* values.\n", + "- Finally, instead of the `rms` spatial/field averages,\n", + " print the regular mean (`.m`) averages. Explain why `err.m` is nearly zero,\n", + " in contrast to `err.rms`." + ] + }, + { + "cell_type": "markdown", + "id": "ad85889d", + "metadata": {}, + "source": [ + "### Excercise (memory)\n", + "Why are the replay plots not as smooth as the liveplot?\n", + "\n", + "*Hint*: provide the keyword `store_u=True` to `assimilate()` to avoid this." + ] + } + ], + "metadata": { + "jupytext": { + "cell_metadata_filter": "-all", + "formats": "py:light,ipynb", + "main_language": "python", + "notebook_metadata_filter": "-all" + }, + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.4" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/basic_1.py b/docs/examples/basic_1.py similarity index 55% rename from examples/basic_1.py rename to docs/examples/basic_1.py index 41df9f09..f4577901 100644 --- a/examples/basic_1.py +++ b/docs/examples/basic_1.py @@ -1,77 +1,78 @@ -# ## Illustrate usage of DAPPER to (interactively) run a synthetic ("twin") experiment. +# ## A single, interactive, synthetic ("twin") experiment +# If run as a script in a terminal (i.e. not as a jupyter notebook) +# then the liveplotting is *interactive* (can be paused, skipped, etc). -# #### Imports +# ### Imports # NB: On Gooble Colab, # *replace* `%matplotlib notebook` (right below) by\ # `!python -m pip install git+https://github.com/nansencenter/DAPPER.git` # %matplotlib notebook -from mpl_tools import is_notebook_or_qt as nb import dapper as dpr import dapper.da_methods as da -# #### Load experiment setup: the hidden Markov model (HMM) -from dapper.mods.Lorenz63.sakov2012 import HMM +# Generate the same random numbers each time this script is run: + +dpr.set_seed(3000); -# #### Generate the same random numbers each time this script is run +# ### Load experiment setup: the hidden Markov model (HMM) -dpr.set_seed(3000) +from dapper.mods.Lorenz63.sakov2012 import HMM +HMM # ⇒ printout (in notebooks) -# #### Simulate synthetic truth (xx) and noisy obs (yy) +# ### Simulate synthetic truth (xx) and noisy obs (yy) +# A variable named `` conventionally refers to a *time series* of ``. HMM.tseq.T = 30 # shorten experiment xx, yy = HMM.simulate() -# #### Specify a DA method configuration ("xp" is short for "experiment") +# ### Specify a DA method +# Here "xp" is short for "experiment" configuration. # xp = da.OptInterp() # xp = da.Var3D() # xp = da.ExtKF(infl=90) xp = da.EnKF("Sqrt", N=10, infl=1.02, rot=True) # xp = da.PartFilt(N=100, reg=2.4, NER=0.3) +xp # ⇒ printout (in notebooks) -# #### Assimilate yy, knowing the HMM; xx is used to assess the performance +# ### Assimilate yy +# Note that the assimilation "knows" the HMM, +# but `xx` is only used for performance *assessment*. -xp.assimilate(HMM, xx, yy, liveplots=not nb) +xp.assimilate(HMM, xx, yy) -# #### Average the time series of various statistics +# ### Average the time series +# Computes a set of different statistics. # print(xp.stats) # ⇒ long printout xp.stats.average_in_time() -# #### Print some of these time-averages +# Print some of these time-averages # print(xp.avrgs) # ⇒ long printout print(xp.avrgs.tabulate(["rmse.a", "rmv.a"])) -# #### Replay liveplotters +# ### Replay liveplotters xp.stats.replay( # speed=.6 # `speed` does not work in notebooks ) -# #### Further diagnostic plots - -if nb: - import dapper.tools.viz as viz +# ### Further diagnostic plots - viz.plot_rank_histogram(xp.stats) - viz.plot_err_components(xp.stats) - viz.plot_hovmoller(xx) +import dapper.tools.viz as viz -# #### Explore objects +viz.plot_rank_histogram(xp.stats) +viz.plot_err_components(xp.stats) +viz.plot_hovmoller(xx) -if nb: - print(xp) - -if nb: - print(HMM) - -# #### Excercise: Try out each of the above DA methods (currently commented out). -# Next, remove the call to `replay`, and set `liveplots=False` above. -# Now, use the iterative EnKS (`iEnKS`), and try to find a parameter combination -# for it so that you achieve a lower `rmse.a` than with the `PartFilt`. +# ### Excercise (methods) +# - Try out each of the above DA methods (currently commented out). +# - Next, remove the call to `replay`, and set `liveplots=False` above. +# - Now, use the iterative EnKS (`iEnKS`), and try to find a parameter combination +# for it so that you achieve a lower `rmse.a` than with the `PartFilt`. # # *Hint*: In general, there is no free lunch. Similarly, not all methods work # for all problems; additionally, methods often have parameters that require @@ -81,13 +82,15 @@ # will have to tune it yourself. The example script `basic_2` shows how DAPPER # facilitates the tuning process, and `basic_3` takes this further. -# #### Excercise: Run an experiment for each of these models +# ### Excercise (models) +# Run an experiment for each of these models +# # - LotkaVolterra # - Lorenz96 # - LA # - QG -# #### Excercise: Printing other diagnostics. +# ### Excercise (diagnostics) # - Create a new code cell, and copy-paste the above `print(...tabulate)` # command into it. Then, replace `rmse` by `err.rms`. This should yield # the same printout, as is merely an abbreviation of the latter. @@ -98,5 +101,7 @@ # print the regular mean (`.m`) averages. Explain why `err.m` is nearly zero, # in contrast to `err.rms`. -# #### Excercise: Why are the replay plots not as smooth as the liveplot? +# ### Excercise (memory) +# Why are the replay plots not as smooth as the liveplot? +# # *Hint*: provide the keyword `store_u=True` to `assimilate()` to avoid this. diff --git a/examples/basic_2.ipynb b/docs/examples/basic_2.ipynb similarity index 56% rename from examples/basic_2.ipynb rename to docs/examples/basic_2.ipynb index ddbac2f1..712c16d5 100644 --- a/examples/basic_2.ipynb +++ b/docs/examples/basic_2.ipynb @@ -5,7 +5,7 @@ "id": "cf1f0bba", "metadata": {}, "source": [ - "## Illustrate usage of DAPPER to benchmark multiple DA methods." + "## Multiple synthetic benchmark experiments" ] }, { @@ -13,7 +13,7 @@ "id": "e60a67d5", "metadata": {}, "source": [ - "#### Imports\n", + "### Imports\n", "NB: If you're on Gooble Colab,\n", "then replace `%matplotlib notebook` below by\\\n", "`!python -m pip install git+https://github.com/nansencenter/DAPPER.git`\\\n", @@ -22,7 +22,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "id": "b1c0d97d", "metadata": {}, "outputs": [], @@ -39,24 +39,23 @@ "lines_to_next_cell": 2 }, "source": [ - "#### DA method configurations" + "### DA method configurations" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "id": "9f819077", "metadata": {}, "outputs": [], "source": [ - "# fmt: off\n", "from dapper.mods.Lorenz63.sakov2012 import HMM # Expected rmse.a:\n", "\n", "xps = dpr.xpList()\n", "xps += da.Climatology() # 7.6\n", "xps += da.OptInterp() # 1.25\n", - "xps += da.Persistence() # 10.7\n", - "xps += da.PreProg(lambda k, xx, yy: xx[k]) # 0\n", + "#xps += da.Persistence() # 10.7\n", + "#xps += da.PreProg(lambda k, xx, yy: xx[k]) # 0\n", "xps += da.Var3D(xB=0.1) # 1.03\n", "xps += da.ExtKF(infl=90) # 0.87\n", "xps += da.EnKF('Sqrt' , N=3 , infl=1.30) # 0.82\n", @@ -67,8 +66,7 @@ "xps += da.PartFilt( N=100 , reg=2.4 , NER=0.3) # 0.38\n", "xps += da.PartFilt( N=800 , reg=0.9 , NER=0.2) # 0.28\n", "# xps += da.PartFilt( N=4000, reg=0.7 , NER=0.05) # 0.27\n", - "# xps += da.PFxN(xN=1000, N=30 , Qs=2 , NER=0.2) # 0.56\n", - "# # fmt: on" + "# xps += da.PFxN(xN=1000, N=30 , Qs=2 , NER=0.2) # 0.56" ] }, { @@ -81,12 +79,11 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "id": "e665575f", "metadata": {}, "outputs": [], "source": [ - "# fmt: off\n", "# from dapper.mods.Lorenz96.sakov2008 import HMM # Expected rmse.a:\n", "# xps = dpr.xpList()\n", "# xps += da.Climatology() # 3.6\n", @@ -101,8 +98,7 @@ "# xps += da.iEnKS('Sqrt' , N=40, infl=1.01, rot=True) # 0.17\n", "# # With localisation:\n", "# xps += da.LETKF( N=7 , infl=1.04, rot=True, loc_rad=4) # 0.22\n", - "# xps += da.SL_EAKF( N=7 , infl=1.07, rot=True, loc_rad=6) # 0.23\n", - "# fmt: on" + "# xps += da.SL_EAKF( N=7 , infl=1.07, rot=True, loc_rad=6) # 0.23" ] }, { @@ -115,7 +111,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "id": "7929298c", "metadata": {}, "outputs": [], @@ -130,7 +126,7 @@ "id": "d8deb761", "metadata": {}, "source": [ - "#### Launch" + "### Launch" ] }, { @@ -144,7 +140,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "id": "8b903ed3", "metadata": {}, "outputs": [], @@ -163,7 +159,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, "id": "07722c35", "metadata": {}, "outputs": [], @@ -181,10 +177,46 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 7, "id": "b24e7557", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Experiment gets stored at /Users/para/data/dpr_data/noname/run_2024-10-31__17-12-44\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Truth & Obs: 100%|█████████████| 5000/5000 [00:00<00:00, 41765.45it/s]\n", + "Climatology: 100%|█████████████| 5000/5000 [00:00<00:00, 58936.74it/s]\n", + "Truth & Obs: 100%|█████████████| 5000/5000 [00:00<00:00, 42079.80it/s]\n", + "OptInterp: 100%|███████████████| 5000/5000 [00:00<00:00, 25958.39it/s]\n", + "Truth & Obs: 100%|█████████████| 5000/5000 [00:00<00:00, 42096.02it/s]\n", + "Var3D: 100%|███████████████████| 5000/5000 [00:00<00:00, 26433.50it/s]\n", + "Truth & Obs: 100%|█████████████| 5000/5000 [00:00<00:00, 38184.81it/s]\n", + "ExtKF infl:90: 100%|███████████| 5000/5000 [00:00<00:00, 19740.78it/s]\n", + "Truth & Obs: 100%|█████████████| 5000/5000 [00:00<00:00, 41851.22it/s]\n", + "EnKF infl:1.3 upd_a:Sqrt N:3 rot:False: 100%|█| 5000/5000 [00:00<00:00\n", + "Truth & Obs: 100%|█████████████| 5000/5000 [00:00<00:00, 40812.37it/s]\n", + "EnKF infl:1.02 upd_a:Sqrt N:10 rot:True: 100%|█| 5000/5000 [00:00<00:0\n", + "Truth & Obs: 100%|█████████████| 5000/5000 [00:00<00:00, 42888.91it/s]\n", + "EnKF infl:0.95 upd_a:PertObs N:500 rot:False: 100%|█| 5000/5000 [00:00\n", + "Truth & Obs: 100%|█████████████| 5000/5000 [00:00<00:00, 40611.08it/s]\n", + "EnKF_N infl:1 N:10 rot:True xN:1: 100%|█| 5000/5000 [00:00<00:00, 1325\n", + "Truth & Obs: 100%|█████████████| 5000/5000 [00:00<00:00, 40498.23it/s]\n", + "iEnKS infl:1.02 upd_a:Sqrt N:10 rot:True: 100%|█| 201/201 [00:03<00:00\n", + "Truth & Obs: 100%|█████████████| 5000/5000 [00:00<00:00, 42317.38it/s]\n", + "PartFilt N:100 reg:2.4 NER:0.3: 100%|█| 5000/5000 [00:00<00:00, 13981.\n", + "Truth & Obs: 100%|█████████████| 5000/5000 [00:00<00:00, 42299.37it/s]\n", + "PartFilt N:800 reg:0.9 NER:0.2: 100%|█| 5000/5000 [00:00<00:00, 8458.1\n" + ] + } + ], "source": [ "save_as = xps.launch(HMM, liveplots=False)" ] @@ -194,17 +226,37 @@ "id": "7e44ffe8", "metadata": {}, "source": [ - "#### Print results" + "### Print results" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 8, "id": "8bd94fa0", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " da_method infl upd_a N rot xN reg NER | rmse.a 1σ \n", + "-- ----------- ----- ------- --- ----- -- --- --- - ------------\n", + " 0 Climatology | 7.4 ±0.3 \n", + " 1 OptInterp | 1.23 ±0.04\n", + " 2 Var3D | 1.07 ±0.06\n", + " 3 ExtKF 90 | 0.88 ±0.09\n", + " 4 EnKF 1.3 Sqrt 3 False | 0.8 ±0.1 \n", + " 5 EnKF 1.02 Sqrt 10 True | 0.59 ±0.06\n", + " 6 EnKF 0.95 PertObs 500 False | 1.1 ±0.3 \n", + " 7 EnKF_N 1 10 True 1 | 0.71 ±0.07\n", + " 8 iEnKS 1.02 Sqrt 10 True | 0.23 ±0.02\n", + " 9 PartFilt 100 2.4 0.3 | 0.32 ±0.03\n", + "10 PartFilt 800 0.9 0.2 | 0.20 ±0.03\n" + ] + } + ], "source": [ - "print(xps.tabulate_avrgs())" + "print(xps.tabulate_avrgs(statkeys=[\"rmse.a\"]))" ] } ], @@ -230,7 +282,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.16" + "version": "3.12.4" } }, "nbformat": 4, diff --git a/examples/basic_2.py b/docs/examples/basic_2.py similarity index 88% rename from examples/basic_2.py rename to docs/examples/basic_2.py index 65a4e2d3..48bc80d3 100644 --- a/examples/basic_2.py +++ b/docs/examples/basic_2.py @@ -1,6 +1,6 @@ -# ## Illustrate usage of DAPPER to benchmark multiple DA methods. +# ## Multiple synthetic benchmark experiments -# #### Imports +# ### Imports # NB: If you're on Gooble Colab, # then replace `%matplotlib notebook` below by\ # `!python -m pip install git+https://github.com/nansencenter/DAPPER.git`\ @@ -10,18 +10,17 @@ import dapper as dpr # noqa: I001 import dapper.da_methods as da -# #### DA method configurations +# ### DA method configurations # + -# fmt: off from dapper.mods.Lorenz63.sakov2012 import HMM # Expected rmse.a: xps = dpr.xpList() xps += da.Climatology() # 7.6 xps += da.OptInterp() # 1.25 -xps += da.Persistence() # 10.7 -xps += da.PreProg(lambda k, xx, yy: xx[k]) # 0 +#xps += da.Persistence() # 10.7 +#xps += da.PreProg(lambda k, xx, yy: xx[k]) # 0 xps += da.Var3D(xB=0.1) # 1.03 xps += da.ExtKF(infl=90) # 0.87 xps += da.EnKF('Sqrt' , N=3 , infl=1.30) # 0.82 @@ -33,13 +32,11 @@ xps += da.PartFilt( N=800 , reg=0.9 , NER=0.2) # 0.28 # xps += da.PartFilt( N=4000, reg=0.7 , NER=0.05) # 0.27 # xps += da.PFxN(xN=1000, N=30 , Qs=2 , NER=0.2) # 0.56 -# # fmt: on # - # #### With Lorenz-96 instead # + -# fmt: off # from dapper.mods.Lorenz96.sakov2008 import HMM # Expected rmse.a: # xps = dpr.xpList() # xps += da.Climatology() # 3.6 @@ -55,7 +52,6 @@ # # With localisation: # xps += da.LETKF( N=7 , infl=1.04, rot=True, loc_rad=4) # 0.22 # xps += da.SL_EAKF( N=7 , infl=1.07, rot=True, loc_rad=6) # 0.23 -# fmt: on # - # #### Other models (suitable xp's listed in HMM files): @@ -66,7 +62,7 @@ # from dapper.mods.LotkaVolterra.settings101 import HMM # - -# #### Launch +# ### Launch # Write some more non-arg parameters to the `xps`. In this case we set the seed, # so that repeat experiments produce exactly the same result. @@ -82,6 +78,6 @@ save_as = xps.launch(HMM, liveplots=False) -# #### Print results +# ### Print results -print(xps.tabulate_avrgs()) +print(xps.tabulate_avrgs(statkeys=["rmse.a"])) diff --git a/examples/basic_3a.py b/docs/examples/basic_3a.py similarity index 100% rename from examples/basic_3a.py rename to docs/examples/basic_3a.py diff --git a/examples/basic_3b.py b/docs/examples/basic_3b.py similarity index 100% rename from examples/basic_3b.py rename to docs/examples/basic_3b.py diff --git a/examples/param_estim.py b/docs/examples/param_estim.py similarity index 97% rename from examples/param_estim.py rename to docs/examples/param_estim.py index 85184e73..cfc3845a 100644 --- a/examples/param_estim.py +++ b/docs/examples/param_estim.py @@ -6,10 +6,10 @@ # (concatenating) the state vector with the parameter (vector). # # This particular experiment is a reproduction of section 3.3 of -# `bib.bocquet2013joint`. Briefly: the unknown parameter is the forcing used in +# [bocquet2013joint][]. Briefly: the unknown parameter is the forcing used in # Lorenz-96; only the state is observed; both parameter and state get estimated. # -# This example builds mostly on `examples/basic_2.py`. For brevity, it does not +# This example builds mostly on `docs/examples/basic_2.py`. For brevity, it does not # contain some of the facilities of `examples/basic_3.py` to run and analyse a # larger numbers of experiments. # diff --git a/examples/stoch_model1.py b/docs/examples/stoch_model1.py similarity index 96% rename from examples/stoch_model1.py rename to docs/examples/stoch_model1.py index 5ff1c7ce..95156a78 100644 --- a/examples/stoch_model1.py +++ b/docs/examples/stoch_model1.py @@ -1,4 +1,4 @@ -# ## Reproduce basics of `bib.grudzien2020numerical`, and do live plotting. +# ## Reproduce basics of [grudzien2020numerical][], and do live plotting. # This is a simple demonstration of the effect of using low-precision numerics # for a perfect-random model configuration in which the system is not dominated # by the noise. In this case, we see a failure of the ensemble generated diff --git a/examples/stoch_models.py b/docs/examples/stoch_models.py similarity index 100% rename from examples/stoch_models.py rename to docs/examples/stoch_models.py diff --git a/examples/time-dep-obs-operator.py b/docs/examples/time-dep-obs-operator.py similarity index 100% rename from examples/time-dep-obs-operator.py rename to docs/examples/time-dep-obs-operator.py diff --git a/docs/gen_ref_pages.py b/docs/gen_ref_pages.py new file mode 100644 index 00000000..509c3975 --- /dev/null +++ b/docs/gen_ref_pages.py @@ -0,0 +1,41 @@ +"""Generate the code reference pages.""" +# Based on https://mkdocstrings.github.io/recipes/ + +from pathlib import Path + +import mkdocs_gen_files + +nav = mkdocs_gen_files.Nav() + +root = Path(__file__).parent.parent +src = root / "dapper" + +for path in sorted(src.rglob("*.py")): + module_path = path.relative_to(src).with_suffix("") + doc_path = path.relative_to(src).with_suffix(".md") + full_doc_path = Path("reference", doc_path) + + parts = tuple(module_path.parts) + + if parts[-1] == "__init__": + parts = parts[:-1] or src.parts[-1:] + if not parts: + # we're in root pkg + parts = src.parts[-1:] + doc_path = doc_path.with_name("index.md") + full_doc_path = full_doc_path.with_name("index.md") + elif parts[-1] == "__main__": + continue + + # PS: rm mkdocs_gen_files to get to inspect actual .md files + # NB: will (over)write in docs/ folder. + with mkdocs_gen_files.open(full_doc_path, "w") as fd: + identifier = ".".join(parts) + print("::: " + identifier, file=fd) + + mkdocs_gen_files.set_edit_path(full_doc_path, path.relative_to(root)) + +# > So basically, you can use the literate-nav plugin just for its ability to +# > infer only sub-directories, without ever writing any actual "literate navs". +# with mkdocs_gen_files.open("reference/SUMMARY.md", "w") as nav_file: +# nav_file.writelines(nav.build_literate_nav()) diff --git a/docs/imgs/ex1.jpg b/docs/images/ex1.jpg similarity index 100% rename from docs/imgs/ex1.jpg rename to docs/images/ex1.jpg diff --git a/docs/imgs/ex3.svg b/docs/images/ex3.svg similarity index 100% rename from docs/imgs/ex3.svg rename to docs/images/ex3.svg diff --git a/docs/imgs/CW3E-Logo-Horizontal-FullColor.png b/docs/images/logos/CW3E-Logo-Horizontal-FullColor.png similarity index 100% rename from docs/imgs/CW3E-Logo-Horizontal-FullColor.png rename to docs/images/logos/CW3E-Logo-Horizontal-FullColor.png diff --git a/docs/imgs/UoR-logo.png b/docs/images/logos/UoR-logo.png similarity index 100% rename from docs/imgs/UoR-logo.png rename to docs/images/logos/UoR-logo.png diff --git a/docs/imgs/logo.png b/docs/images/logos/logo.png similarity index 100% rename from docs/imgs/logo.png rename to docs/images/logos/logo.png diff --git a/docs/imgs/logo_wtxt.png b/docs/images/logos/logo_wtxt.png similarity index 100% rename from docs/imgs/logo_wtxt.png rename to docs/images/logos/logo_wtxt.png diff --git a/docs/imgs/nansen-logo.png b/docs/images/logos/nansen-logo.png similarity index 100% rename from docs/imgs/nansen-logo.png rename to docs/images/logos/nansen-logo.png diff --git a/docs/imgs/nceologo1000.png b/docs/images/logos/nceologo1000.png similarity index 100% rename from docs/imgs/nceologo1000.png rename to docs/images/logos/nceologo1000.png diff --git a/docs/imgs/norce-logo.png b/docs/images/logos/norce-logo.png similarity index 100% rename from docs/imgs/norce-logo.png rename to docs/images/logos/norce-logo.png diff --git a/docs/snippets/ex_resample.py b/docs/images/snippets/ex_resample.py similarity index 100% rename from docs/snippets/ex_resample.py rename to docs/images/snippets/ex_resample.py diff --git a/docs/snippets/iEnKS_Ea.jpg b/docs/images/snippets/iEnKS_Ea.jpg similarity index 100% rename from docs/snippets/iEnKS_Ea.jpg rename to docs/images/snippets/iEnKS_Ea.jpg diff --git a/docs/snippets/serial_sqrt.jpg b/docs/images/snippets/serial_sqrt.jpg similarity index 100% rename from docs/snippets/serial_sqrt.jpg rename to docs/images/snippets/serial_sqrt.jpg diff --git a/docs/snippets/sigmoid.jpg b/docs/images/snippets/sigmoid.jpg similarity index 100% rename from docs/snippets/sigmoid.jpg rename to docs/images/snippets/sigmoid.jpg diff --git a/docs/snippets/trHK.jpg b/docs/images/snippets/trHK.jpg similarity index 100% rename from docs/snippets/trHK.jpg rename to docs/images/snippets/trHK.jpg diff --git a/docs/snippets/unbiased_skew_kurt.jpg b/docs/images/snippets/unbiased_skew_kurt.jpg similarity index 100% rename from docs/snippets/unbiased_skew_kurt.jpg rename to docs/images/snippets/unbiased_skew_kurt.jpg diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 00000000..0dad5287 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,6 @@ +--- +hide: +- navigation +--- + +--8<-- "README.md" diff --git a/docs/javascripts/extra.js b/docs/javascripts/extra.js new file mode 100644 index 00000000..584b3c70 --- /dev/null +++ b/docs/javascripts/extra.js @@ -0,0 +1,9 @@ +// https://squidfunk.github.io/mkdocs-material/customization/#additional-javascript +console.log("This message appears on every page") + +// Tag converted notebooks (to facilitate custom styling) +document.addEventListener("DOMContentLoaded", function() { + if (document.querySelector('.jp-Notebook')) { + document.body.classList.add('document-is-notebook'); + } +}); diff --git a/docs/javascripts/mathjax.js b/docs/javascripts/mathjax.js new file mode 100644 index 00000000..0be88e04 --- /dev/null +++ b/docs/javascripts/mathjax.js @@ -0,0 +1,19 @@ +window.MathJax = { + tex: { + inlineMath: [["\\(", "\\)"]], + displayMath: [["\\[", "\\]"]], + processEscapes: true, + processEnvironments: true + }, + options: { + ignoreHtmlClass: ".*|", + processHtmlClass: "arithmatex" + } +}; + +document$.subscribe(() => { + MathJax.startup.output.clearCache() + MathJax.typesetClear() + MathJax.texReset() + MathJax.typesetPromise() +}) diff --git a/docs/overrides/main.html b/docs/overrides/main.html new file mode 100644 index 00000000..67930cce --- /dev/null +++ b/docs/overrides/main.html @@ -0,0 +1,20 @@ + +{% extends "base.html" %} + +{% block extrahead %} + + +{% endblock %} + +{% block content %} + {{ super() }} + + {# + Hello World! + {{ page.url }} + {{ "relative/link" | url }} + {{ "/absolute/link" | url }} + {{ "https://www.google.com/" | url }} + #} + +{% endblock %} diff --git a/docs/references.md b/docs/references.md new file mode 100644 index 00000000..caccdcdf --- /dev/null +++ b/docs/references.md @@ -0,0 +1,315 @@ +### `raanes2016thesis` + +Patrick N\. Raanes\. +*Improvements to Ensemble Methods for Data Assimilation in the Geosciences*\. +PhD thesis, University of Oxford, January 2016\. +\\url https://ora\.ox\.ac\.uk/objects/uuid:9f9961f0\-6906\-4147\-a8a9\-ca9f2d0e4a12\. + +### `bocquet2010beyond` + +Marc Bocquet, Carlos A\. Pires, and Lin Wu\. +`"Beyond Gaussian statistical modeling in geophysical data assimilation"`\. +*Monthly Weather Review*, 138\(8\):2997–3023, 2010\. + +### `bocquet2015expanding` + +Marc Bocquet, Patrick N\. Raanes, and Alexis Hannart\. +`"Expanding the validity of the ensemble Kalman filter without the intrinsic need for inflation"`\. +*Nonlinear Processes in Geophysics*, 22\(6\):645–662, 2015\. + +### `sakov2008implications` + +Pavel Sakov and Peter R\. Oke\. +`"Implications of the form of the ensemble transformation in the ensemble square root filters"`\. +*Monthly Weather Review*, 136\(3\):1042–1053, 2008\. + +### `wiljes2016second` + +Jana de Wiljes, Walter Acevedo, and Sebastian Reich\. +`"Second\-order accurate ensemble transform particle filters"`\. +*arXiv preprint arXiv:1608\.08179*, 2016\. + +### `bocquet2011ensemble` + +Marc Bocquet\. +`"Ensemble Kalman filtering without the intrinsic need for inflation"`\. +*Nonlinear Processes in Geophysics*, 18\(5\):735–750, 2011\. + +### `hoteit2015mitigating` + +I\. Hoteit, D\.\-T\. Pham, M\. E\. Gharamti, and X\. Luo\. +`"Mitigating observation perturbation sampling errors in the stochastic EnKF"`\. +*Monthly Weather Review*, 143\(7\):2918–2936, 2015\. + +### `bocquet2013joint` + +Marc Bocquet and Pavel Sakov\. +`"Joint state and parameter estimation with an iterative ensemble Kalman smoother"`\. +*Nonlinear Processes in Geophysics*, 20\(5\):803–818, 2013\. + +### `hunt2007efficient` + +Brian R\. Hunt, Eric J\. Kostelich, and Istvan Szunyogh\. +`"Efficient data assimilation for spatiotemporal chaos: a local ensemble transform Kalman filter"`\. +*Physica D: Nonlinear Phenomena*, 230\(1\):112–126, 2007\. + +### `karspeck2007experimental` + +Alicia R\. Karspeck and Jeffrey L\. Anderson\. +`"Experimental implementation of an ensemble adjustment filter for an intermediate ENSO model"`\. +*Journal of Climate*, 20\(18\):4638–4658, 2007\. + +### `todter2015second` + +Julian Tödter and Bodo Ahrens\. +`"A second\-order exact ensemble square root filter for nonlinear data assimilation"`\. +*Monthly Weather Review*, 143\(4\):1347–1367, 2015\. + +### `chen2003bayesian` + +Zhe Chen\. +`"Bayesian filtering: from Kalman filters to particle filters, and beyond"`\. +*Statistics*, 182\(1\):1–69, 2003\. + +### `bocquet2012combining` + +Marc Bocquet and Pavel Sakov\. +`"Combining inflation\-free and iterative ensemble Kalman filters for strongly nonlinear systems"`\. +*Nonlinear Processes in Geophysics*, 19\(3\):383–399, 2012\. + +### `doucet2001sequential` + +Arnaud Doucet, Nando De Freitas, and Neil Gordon\. +*Sequential Monte Carlo Methods in Practice*\. +Springer, 2001\. + +### `liu2001theoretical` + +Jun S\. Liu, Rong Chen, and Tanya Logvinenko\. +`"A theoretical framework for sequential importance sampling with resampling"`\. +In *Sequential Monte Carlo methods in practice*, pages 225–246\. +Springer, 2001\. + +### `bocquet2014iterative` + +Marc Bocquet and Pavel Sakov\. +`"An iterative ensemble Kalman smoother"`\. +*Quarterly Journal of the Royal Meteorological Society*, 140\(682\):1521–1535, 2014\. + +### `sakov2008deterministic` + +Pavel Sakov and Peter R\. Oke\. +`"A deterministic formulation of the ensemble Kalman filter: an alternative to ensemble square root filters"`\. +*Tellus A*, 60\(2\):361–371, 2008\. + +### `zupanski2005maximum` + +Milija Zupanski\. +`"Maximum likelihood ensemble filter: theoretical aspects"`\. +*Monthly Weather Review*, 133\(6\):1710–1726, 2005\. + +### `sakov2012iterative` + +Pavel Sakov, Dean S\. Oliver, and Laurent Bertino\. +`"An iterative EnKF for strongly nonlinear systems\."`\. +*Monthly Weather Review*, 140\(6\):1988–2004, 2012\. + +### `evensen2009ensemble` + +G\. Evensen\. +`"The ensemble Kalman filter for combined state and parameter estimation"`\. +*Control Systems, IEEE*, 29\(3\):83–104, 2009\. + +### `raanes2015rts` + +Patrick Nima Raanes\. +`"On the ensemble Rauch\-Tung\-Striebel smoother and its equivalence to the ensemble Kalman smoother"`\. +*Quarterly Journal of the Royal Meteorological Society*, 142\(696\):1259–1264, 2016\. + +### `anderson2010non` + +Jeffrey L\. Anderson\. +`"A non\-Gaussian ensemble filter update for data assimilation\."`\. +*Monthly Weather Review*, 138\(11\):4186–4198, 2010\. + +### `raanes2019revising` + +Patrick Nima Raanes, Andreas Størksen Stordal, and Geir Evensen\. +`"Revising the stochastic iterative ensemble smoother"`\. +*Nonlinear Processes in Geophysics*, 26\(3\):325–338, 2019\. + +### `van2009particle` + +Peter Jan van Leeuwen\. +`"Particle filtering in geophysical systems"`\. +*Monthly Weather Review*, 137\(12\):4089–4114, 2009\. + +### `raanes2019adaptive` + +Patrick N\. Raanes, Marc Bocquet, and Alberto Carrassi\. +`"Adaptive covariance inflation in the ensemble Kalman filter by Gaussian scale mixtures"`\. +*Quarterly Journal of the Royal Meteorological Society*, 145\(718\):53–75, 2019\. +[doi:10\.1002/qj\.3386](https://doi.org/10.1002/qj.3386)\. + +### `lei2011moment` + +Jing Lei and Peter Bickel\. +`"A moment matching ensemble filter for nonlinear non\-Gaussian data assimilation"`\. +*Monthly Weather Review*, 139\(12\):3964–3973, 2011\. + +### `wikle2007bayesian` + +C\. K\. Wikle and L\. M\. Berliner\. +`"A Bayesian tutorial for data assimilation"`\. +*Physica D: Nonlinear Phenomena*, 230\(1\-2\):1–16, 2007\. + +### `doucet2009tutorial` + +Arnaud Doucet and Adam M\. Johansen\. +`"A tutorial on particle filtering and smoothing: fifteen years later"`\. +*Handbook of Nonlinear Filtering*, pages 656–704, 2009\. + +### `bocquet2016localization` + +Marc Bocquet\. +`"Localization and the iterative ensemble Kalman smoother"`\. +*Quarterly Journal of the Royal Meteorological Society*, 142\(695\):1075–1089, 2016\. + +### `raanes2014ext` + +Patrick N\. Raanes, Alberto Carrassi, and Laurent Bertino\. +`"Extending the square root method to account for model noise in the ensemble Kalman filter"`\. +*Monthly Weather Review*, 143\(10\):3857–3873, 2015\. + +### `lorenz1998optimal` + +Edward N\. Lorenz and Kerry A\. Emanuel\. +`"Optimal sites for supplementary weather observations: simulation with a small model"`\. +*Journal of the Atmospheric Sciences*, 55\(3\):399–414, 1998\. + +### `frei2013mixture` + +Marco Frei and Hans R Künsch\. +`"Mixture ensemble kalman filters"`\. +*Computational Statistics & Data Analysis*, 58:127–138, 2013\. + +### `frei2013bridging` + +Marco Frei and Hans R Künsch\. +`"Bridging the ensemble Kalman and particle filters"`\. +*Biometrika*, pages ast020, 2013\. + +### `miyoshi2011gaussian` + +Takemasa Miyoshi\. +`"The Gaussian approach to adaptive covariance inflation and its implementation with the local ensemble transform Kalman filter"`\. +*Monthly Weather Review*, 139\(5\):1519–1535, 2011\. + +### `mandel2016hybrid` + +J\. Mandel, E\. Bergou, S\. Gürol, S\. Gratton, and I\. Kasanický\. +`"Hybrid Levenberg\-Marquardt and weak\-constraint ensemble Kalman smoother method"`\. +*Nonlinear Processes in Geophysics*, 23\(2\):59–73, 2016\. + +### `anderson2009spatially` + +Jeffrey L\. Anderson\. +`"Spatially and temporally varying adaptive covariance inflation for ensemble filters"`\. +*Tellus A*, 61\(1\):72–83, 2009\. + +### `bengtsson2003toward` + +Thomas Bengtsson, Chris Snyder, and Doug Nychka\. +`"Toward a nonlinear ensemble filter for high\-dimensional systems"`\. +*Journal of Geophysical Research: Atmospheres*, 2003\. + +### `lorenz1996predictability` + +Edward N\. Lorenz\. +`"Predictability: a problem partly solved"`\. +In *Proc\. ECMWF Seminar on Predictability*, volume 1, 1–18\. Reading, UK, 1996\. + +### `wilks2005effects` + +Daniel S\. Wilks\. +`"Effects of stochastic parametrizations in the Lorenz'96 system"`\. +*Quarterly Journal of the Royal Meteorological Society*, 131\(606\):389–407, 2005\. + +### `harty2021eigenvector` + +Travis Harty, Matthias Morzfeld, and Chris Snyder\. +`"Eigenvector\-spatial localisation"`\. +*Tellus A: Dynamic Meteorology and Oceanography*, 73\(1\):1–18, 2021\. + +### `rainwater2013mixed` + +Sabrina Rainwater and Brian Hunt\. +`"Mixed\-resolution ensemble data assimilation"`\. +*Monthly weather review*, 141\(9\):3007–3021, 2013\. + +### `counillon2009application` + +François Counillon, Pavel Sakov, and Laurent Bertino\. +`"Application of a hybrid \`enkf\-oi\` to ocean forecasting"`\. +*Ocean Science*, 5\(4\):389–401, 2009\. + +### `pajonk2012deterministic` + +Oliver Pajonk, Bojana V\. Rosić, Alexander Litvinenko, and Hermann G\. Matthies\. +`"A deterministic filter for non\-Gaussian Bayesian estimation—applications to dynamical system estimation with noisy measurements"`\. +*Physica D: Nonlinear Phenomena*, 241\(7\):775–788, 2012\. + +### `lorenz2005look` + +Edward N\. Lorenz\. +`"A look at some details of the growth of initial uncertainties"`\. +*Tellus A: Dynamic Meteorology and Oceanography*, 57\(1\):1–11, 2005\. + +### `bocquet2019consistency` + +Marc Bocquet and Alban Farchi\. +`"On the consistency of the local ensemble square root kalman filter perturbation update"`\. +*Tellus A: Dynamic Meteorology and Oceanography*, 71\(1\):1613142, 2019\. + +### `lorenz1984irregularity` + +Edward N\. Lorenz\. +`"Irregularity: a fundamental property of the atmosphere"`\. +*Tellus A*, 36\(2\):98–110, 1984\. + +### `vano2006chaos` + +J\. A\. Vano, J\. C\. Wildenberg, M\. B\. Anderson, J\. K\. Noel, and J\. C\. Sprott\. +`"Chaos in low\-dimensional Lotka–Volterra models of competition"`\. +*Nonlinearity*, 19\(10\):2391, 2006\. + +### `emerick2012history` + +Alexandre A\. Emerick and Albert C\. Reynolds\. +`"History matching time\-lapse seismic data using the ensemble Kalman filter with multiple data assimilations"`\. +*Computational Geosciences*, 16\(3\):639–659, 2012\. + +### `pinheiro2019efficient` + +Flavia R\. Pinheiro, Peter J\. van Leeuwen, and Gernot Geppert\. +`"Efficient nonlinear data assimilation using synchronization in a particle filter"`\. +*Quarterly Journal of the Royal Meteorological Society*, 145\(723\):2510–2523, 2019\. + +### `vissio2020mechanics` + +Gabriele Vissio and Valerio Lucarini\. +`"Mechanics and thermodynamics of a new minimal model of the atmosphere"`\. +*The European Physical Journal Plus*, 135\(10\):1–21, 2020\. + +### `grudzien2020numerical` + +Colin Grudzien, Marc Bocquet, and Alberto Carrassi\. +`"On the numerical integration of the lorenz\-96 model, with scalar additive noise, for benchmark twin experiments"`\. +*Geoscientific Model Development*, 13\(4\):1903–1924, 2020\. + +### `lorenz2005designing` + +Edward\. N\. Lorenz\. +`"Designing chaotic models"`\. +*Journal of the Atmospheric Sciences*, 62\(5\):1574–1587, 2005\. + diff --git a/docs/snippets/README.md b/docs/snippets/README.md deleted file mode 100644 index 72ee4691..00000000 --- a/docs/snippets/README.md +++ /dev/null @@ -1,3 +0,0 @@ -This folder does not provide *the* DAPPER documentation, -but merely some snippets referred to at various places in the code. -See the [Getting started in the README](https://github.com/nansencenter/DAPPER#getting-started) for more documentation. diff --git a/docs/stylesheets/code_select.css b/docs/stylesheets/code_select.css new file mode 100644 index 00000000..20885379 --- /dev/null +++ b/docs/stylesheets/code_select.css @@ -0,0 +1,3 @@ +.language-pycon .gp, .language-pycon .go { /* Generic.Prompt, Generic.Output */ + user-select: none; +} diff --git a/docs/stylesheets/jupyter.css b/docs/stylesheets/jupyter.css new file mode 100644 index 00000000..880a9921 --- /dev/null +++ b/docs/stylesheets/jupyter.css @@ -0,0 +1,8 @@ +/* No need for these margins in the rendered jupyter notebooks */ +.document-is-notebook h1 { margin-bottom: 0; } +.jp-Notebook h2:first-of-type { margin-top: 0; } + +/* stderr is used by tqdm progressbar so should not be red */ +.jupyter-wrapper .jp-RenderedText[data-mime-type="application/vnd.jupyter.stderr"] { + background-color: #cdd6d6ff !important; +} diff --git a/docs/templates/custom.css b/docs/templates/custom.css deleted file mode 100644 index 712a3a18..00000000 --- a/docs/templates/custom.css +++ /dev/null @@ -1,4 +0,0 @@ -nav.pdoc { - background-color: white; - box-shadow: 0 0 20px rgb(50 50 50 / 20%); -} diff --git a/docs/templates/frame.html.jinja2 b/docs/templates/frame.html.jinja2 deleted file mode 100644 index 48fa0d93..00000000 --- a/docs/templates/frame.html.jinja2 +++ /dev/null @@ -1,5 +0,0 @@ -{% extends "default/frame.html.jinja2" %} -{% block head %} - - -{% endblock %} diff --git a/docs/templates/index.html.jinja2 b/docs/templates/index.html.jinja2 deleted file mode 100644 index 7f670a9b..00000000 --- a/docs/templates/index.html.jinja2 +++ /dev/null @@ -1,2 +0,0 @@ -{% set root_module_name = 'dapper' %} -{% extends "default/index.html.jinja2" %} diff --git a/docs/templates/module.html.jinja2 b/docs/templates/module.html.jinja2 deleted file mode 100644 index faa4e616..00000000 --- a/docs/templates/module.html.jinja2 +++ /dev/null @@ -1,19 +0,0 @@ -{% set logo = 'https://raw.githubusercontent.com/nansencenter/DAPPER/master/docs/imgs/logo_wtxt.png' %} -{% set logo_link = '/' %} -{% set favicon = 'https://raw.githubusercontent.com/nansencenter/DAPPER/master/docs/imgs/logo.png' %} - -{% extends "default/module.html.jinja2" %} - -{% block title %}DAPPER docs: {{ module.modulename }}{% endblock %} - -{# Rm "Module Index" button on root package #} -{% block module_list_link %} - {% set parentmodule = ".".join(module.modulename.split(".")[:-1]) %} - {% if parentmodule and parentmodule in all_modules %} - - {% include "resources/box-arrow-in-left.svg" %} -   - {{- parentmodule -}} - - {% endif %} -{% endblock %} diff --git a/examples/basic_1.ipynb b/examples/basic_1.ipynb deleted file mode 100644 index b7326914..00000000 --- a/examples/basic_1.ipynb +++ /dev/null @@ -1,328 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "3c5106d3", - "metadata": {}, - "source": [ - "## Illustrate usage of DAPPER to (interactively) run a synthetic (\"twin\") experiment." - ] - }, - { - "cell_type": "markdown", - "id": "fe5f6628", - "metadata": {}, - "source": [ - "#### Imports\n", - "NB: On Gooble Colab,\n", - "*replace* `%matplotlib notebook` (right below) by\\\n", - "`!python -m pip install git+https://github.com/nansencenter/DAPPER.git`" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "89f5e089", - "metadata": {}, - "outputs": [], - "source": [ - "%matplotlib notebook\n", - "from mpl_tools import is_notebook_or_qt as nb" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "fc681615", - "metadata": {}, - "outputs": [], - "source": [ - "import dapper as dpr\n", - "import dapper.da_methods as da" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "8b272589", - "metadata": {}, - "outputs": [], - "source": [ - "# #### Load experiment setup: the hidden Markov model (HMM)\n", - "from dapper.mods.Lorenz63.sakov2012 import HMM" - ] - }, - { - "cell_type": "markdown", - "id": "7afbdeb6", - "metadata": {}, - "source": [ - "#### Generate the same random numbers each time this script is run" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "0e74274d", - "metadata": {}, - "outputs": [], - "source": [ - "dpr.set_seed(3000)" - ] - }, - { - "cell_type": "markdown", - "id": "2ce9b72c", - "metadata": {}, - "source": [ - "#### Simulate synthetic truth (xx) and noisy obs (yy)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c54bcca2", - "metadata": {}, - "outputs": [], - "source": [ - "HMM.tseq.T = 30 # shorten experiment\n", - "xx, yy = HMM.simulate()" - ] - }, - { - "cell_type": "markdown", - "id": "3c40799e", - "metadata": {}, - "source": [ - "#### Specify a DA method configuration (\"xp\" is short for \"experiment\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b39ebe02", - "metadata": {}, - "outputs": [], - "source": [ - "# xp = da.OptInterp()\n", - "# xp = da.Var3D()\n", - "# xp = da.ExtKF(infl=90)\n", - "xp = da.EnKF(\"Sqrt\", N=10, infl=1.02, rot=True)\n", - "# xp = da.PartFilt(N=100, reg=2.4, NER=0.3)" - ] - }, - { - "cell_type": "markdown", - "id": "08f20322", - "metadata": {}, - "source": [ - "#### Assimilate yy, knowing the HMM; xx is used to assess the performance" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "0a0ea6ff", - "metadata": {}, - "outputs": [], - "source": [ - "xp.assimilate(HMM, xx, yy, liveplots=not nb)" - ] - }, - { - "cell_type": "markdown", - "id": "18ff2a15", - "metadata": {}, - "source": [ - "#### Average the time series of various statistics" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a7f6fb40", - "metadata": {}, - "outputs": [], - "source": [ - "# print(xp.stats) # ⇒ long printout\n", - "xp.stats.average_in_time()" - ] - }, - { - "cell_type": "markdown", - "id": "842ad66b", - "metadata": {}, - "source": [ - "#### Print some of these time-averages" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "cc4e9955", - "metadata": {}, - "outputs": [], - "source": [ - "# print(xp.avrgs) # ⇒ long printout\n", - "print(xp.avrgs.tabulate([\"rmse.a\", \"rmv.a\"]))" - ] - }, - { - "cell_type": "markdown", - "id": "f130c09b", - "metadata": {}, - "source": [ - "#### Replay liveplotters" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a7ebf5e6", - "metadata": {}, - "outputs": [], - "source": [ - "xp.stats.replay(\n", - " # speed=.6 # `speed` does not work in notebooks\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "6335abba", - "metadata": {}, - "source": [ - "#### Further diagnostic plots" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "450a7fc1", - "metadata": {}, - "outputs": [], - "source": [ - "if nb:\n", - " import dapper.tools.viz as viz\n", - "\n", - " viz.plot_rank_histogram(xp.stats)\n", - " viz.plot_err_components(xp.stats)\n", - " viz.plot_hovmoller(xx)" - ] - }, - { - "cell_type": "markdown", - "id": "51a3c22f", - "metadata": {}, - "source": [ - "#### Explore objects" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "70fa4918", - "metadata": {}, - "outputs": [], - "source": [ - "if nb:\n", - " print(xp)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3274e3b1", - "metadata": {}, - "outputs": [], - "source": [ - "if nb:\n", - " print(HMM)" - ] - }, - { - "cell_type": "markdown", - "id": "304273d1", - "metadata": {}, - "source": [ - "#### Excercise: Try out each of the above DA methods (currently commented out).\n", - "Next, remove the call to `replay`, and set `liveplots=False` above.\n", - "Now, use the iterative EnKS (`iEnKS`), and try to find a parameter combination\n", - "for it so that you achieve a lower `rmse.a` than with the `PartFilt`.\n", - "\n", - "*Hint*: In general, there is no free lunch. Similarly, not all methods work\n", - "for all problems; additionally, methods often have parameters that require\n", - "tuning. Luckily, in DAPPER, you should be able to find suitably tuned\n", - "configuration settings for various DA methods *in the files that define the\n", - "HMM*. If you do not find a suggested configuration for a given method, you\n", - "will have to tune it yourself. The example script `basic_2` shows how DAPPER\n", - "facilitates the tuning process, and `basic_3` takes this further." - ] - }, - { - "cell_type": "markdown", - "id": "de2e51e4", - "metadata": {}, - "source": [ - "#### Excercise: Run an experiment for each of these models\n", - "- LotkaVolterra\n", - "- Lorenz96\n", - "- LA\n", - "- QG" - ] - }, - { - "cell_type": "markdown", - "id": "bee8dfc6", - "metadata": {}, - "source": [ - "#### Excercise: Printing other diagnostics.\n", - "- Create a new code cell, and copy-paste the above `print(...tabulate)`\n", - " command into it. Then, replace `rmse` by `err.rms`. This should yield\n", - " the same printout, as is merely an abbreviation of the latter.\n", - "- Next, figure out how to print the time average *forecast (i.e. prior)* error\n", - " (and `rmv`) instead. Explain (in broad terms) why the values are larger than\n", - " for the *analysis* values.\n", - "- Finally, instead of the `rms` spatial/field averages,\n", - " print the regular mean (`.m`) averages. Explain why `err.m` is nearly zero,\n", - " in contrast to `err.rms`." - ] - }, - { - "cell_type": "markdown", - "id": "ad85889d", - "metadata": {}, - "source": [ - "#### Excercise: Why are the replay plots not as smooth as the liveplot?\n", - "*Hint*: provide the keyword `store_u=True` to `assimilate()` to avoid this." - ] - } - ], - "metadata": { - "jupytext": { - "cell_metadata_filter": "-all", - "formats": "py:light,ipynb", - "main_language": "python", - "notebook_metadata_filter": "-all" - }, - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.16" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 00000000..4a691abb --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,191 @@ +# ╔═══════╗ +# ║ Notes ║ +# ╚═══════╝ +# When doing significant work on the docs you probably want to +# - Set `validation: unrecognized_links: warn` (see rationale below) +# - Disable `mkdocs-jupyter` which significantly slows build reloads. + +site_name: DAPPER +site_author: Patrick N. Raanes +site_url: https://nansencenter.github.io/DAPPER +repo_url: https://github.com/nansencenter/DAPPER +# edit_uri: edit/master/docs/ + +docs_dir: docs +watch: [mkdocs.yml, README.md, dapper] +nav: + - Home: index.md + - Reference: reference/ + - Examples: examples/ + - dev_guide.md + - Bibliography: references.md + +validation: + # Disabled coz it complains about relative links that work just fine + # (and enable interlinking READMEs even on GitHub). + # Set to "warn" to help find non-working links. + unrecognized_links: ignore + +theme: + name: material + icon: + repo: fontawesome/brands/github + edit: material/pencil + view: material/eye + features: + - content.code.copy # copy button + # - content.code.select # [insiders only] + - content.code.annotate # clickable toggler for code comments + - navigation.indexes # enable subdir/index.md + - toc.follow # ? + # - toc.integrate # render ToC as part of left sidebar [not compat with navigation.indexes] + - navigation.tabs # top-level sections rendered in header tabs (for wide screens) instead of left sidebar + # - navigation.tabs.sticky # don't hide tabs when scrolled + # - navigation.expand # expand sections + # - navigation.sections # group (rather than collapse) top-level sections + - navigation.path # breadcrumbs + # - navigation.prune # for large projects + - navigation.tracking # update URL to current section + - navigation.footer # "Next/Prev" + - navigation.instant # SSA + - navigation.instant.progress # Progbar on top + - navigation.top # "back to top when scrolling up" + - header.autohide + - search.highlight # highlight matches on page + - search.share # deep link to search + - search.suggest # search suggestions + + custom_dir: docs/overrides + +plugins: + - glightbox # zoom functionality + # - roamlinks + # - blog + - search + - autorefs # enable anchor/heading links without specifying its page + # - meta # [insiders only] enable front-matter defaults as in Jekyll + # TODO: enable + # - mkdocs-jupyter: + # include: ["*.ipynb"] # Default: ["*.py", "*.ipynb"] + # ignore: + # - ".ipynb_checkpoints/*" + # execute: false + # - bibtex: + # # NB: does not work with mkdocstrings (https://github.com/shyamd/mkdocs-bibtex/issues/246) + # # PS: requires pandoc installed on system and markdown_extensions: footnotes + # bib_file: docs/bib/refs.bib + # bib_by_default: true + # csl_file: docs/bib/data-science-journal.csl + + # Autodoc from docstrings: + - gen-files: # Genrate .md reflecting .py hierarchy: + scripts: + - docs/gen_ref_pages.py + - literate-nav: # Auto-generate nav + nav_file: SUMMARY.md + - section-index + - mkdocstrings: # Parse docstrings + handlers: + python: + paths: [., dapper] + import: + - https://docs.python.org/3/objects.inv + - https://mkdocstrings.github.io/objects.inv + options: + # Overview at https://mkdocstrings.github.io/python/usage/?h=show_source#globallocal-options + # See sidebar for details. + docstring_style: numpy + # TODO: activate `show_source` when these [insiders!?] are done + # - https://github.com/mkdocstrings/mkdocstrings/issues/525 + # - https://github.com/mkdocstrings/mkdocstrings/issues/249 + show_source: false + show_symbol_type_heading: true + show_symbol_type_toc: true + show_root_heading: false + summary: false # pretty cool actually + show_labels: false + +# Mostly from +# https://squidfunk.github.io/mkdocs-material/setup/extensions/#recommended-configuration +markdown_extensions: + # Python Markdown official extensions + - abbr + - admonition + - def_list + - footnotes + - toc: + title: On this page + permalink: ⚓︎ + toc_depth: 3 + # Adds the ability to align images, add captions to images (rendering them as figures), and mark large images for lazy-loading: + - attr_list + - md_in_html + + # Extensions part of Pymdown + - pymdownx.arithmatex: + generic: true + - pymdownx.betterem: + smart_enable: all + - pymdownx.caret + - pymdownx.details + - pymdownx.emoji: + emoji_index: !!python/name:material.extensions.emoji.twemoji + emoji_generator: !!python/name:material.extensions.emoji.to_svg + - pymdownx.highlight: + anchor_linenums: true + line_spans: __span + pygments_lang_class: true + use_pygments: true + - pymdownx.inlinehilite + - pymdownx.snippets: + base_path: + - !relative # dir of current Markdown file + # NB: does not work with the virtual files generated by mkdocs-gen-files, + # ref https://github.com/oprypin/mkdocs-gen-files/issues/25 + - !relative $docs_dir # dir of docs + - !relative $config_dir # dir of mkdocs.yml + - pymdownx.superfences + - pymdownx.keys + - pymdownx.mark + - pymdownx.smartsymbols + - pymdownx.tabbed: + alternate_style: true + - pymdownx.tasklist: + custom_checkbox: true + - pymdownx.tilde + +# hooks: +# - list_logos.py + +extra_javascript: + - javascripts/extra.js + # For MathJax + - javascripts/mathjax.js + - https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js + +extra_css: + - stylesheets/code_select.css + - stylesheets/jupyter.css + + +extra: + generator: false # "Made with Material for MkDocs" + footer: + logos: + row1: + - name: norce.svg + height: 50px + brightness: 50% + - name: nfr.svg + height: 80px + brightness: 50% + row2: + - name: akerbp.svg + height: 40px + brightness: 50% + - name: equinor.svg + height: 60px + brightness: 50% + - name: wintershall-dea.svg + height: 80px + brightness: 70% diff --git a/pyproject.toml b/pyproject.toml index d09981a1..651af286 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -80,7 +80,7 @@ exclude = [ "dist", "site-packages", "venv", - ".*", "README.*", "examples/*.ipynb", "autoscaler.py", + ".*", "README.*", "docs/examples/*", "autoscaler.py", ] # Same as Black. diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..961629b2 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,610 @@ +# This file was autogenerated by uv via the following command: +# uv pip compile --extra dev setup.py -o requirements.txt +anyio==4.6.2.post1 + # via + # httpx + # jupyter-server +appnope==0.1.4 + # via ipykernel +argon2-cffi==23.1.0 + # via jupyter-server +argon2-cffi-bindings==21.2.0 + # via argon2-cffi +arrow==1.3.0 + # via isoduration +asttokens==2.4.1 + # via stack-data +async-lru==2.0.4 + # via jupyterlab +attrs==24.2.0 + # via + # jsonschema + # referencing +babel==2.16.0 + # via + # jupyterlab-server + # mkdocs-material +beautifulsoup4==4.12.3 + # via nbconvert +bleach==6.2.0 + # via nbconvert +cachetools==5.5.0 + # via tox +certifi==2024.8.30 + # via + # httpcore + # httpx + # requests +cffi==1.17.1 + # via argon2-cffi-bindings +cfgv==3.4.0 + # via pre-commit +chardet==5.2.0 + # via tox +charset-normalizer==3.4.0 + # via requests +click==8.1.7 + # via + # mkdocs + # mkdocstrings +colorama==0.4.6 + # via + # dapper (setup.py) + # griffe + # mkdocs-material + # tox +comm==0.2.2 + # via + # ipykernel + # ipywidgets +contourpy==1.3.0 + # via matplotlib +coverage==7.6.4 + # via + # dapper (setup.py) + # pytest-cov +cycler==0.12.1 + # via matplotlib +debugpy==1.8.7 + # via ipykernel +decorator==5.1.1 + # via + # ipdb + # ipython +defusedxml==0.7.1 + # via nbconvert +dill==0.3.8 + # via + # dapper (setup.py) + # multiprocess + # pathos +distlib==0.3.9 + # via virtualenv +docutils==0.21.2 + # via readme-renderer +execnet==2.1.1 + # via pytest-xdist +executing==2.1.0 + # via stack-data +fastjsonschema==2.20.0 + # via nbformat +filelock==3.16.1 + # via + # tox + # virtualenv +fonttools==4.54.1 + # via matplotlib +fqdn==1.5.1 + # via jsonschema +ghp-import==2.1.0 + # via mkdocs +griffe==1.5.1 + # via mkdocstrings-python +h11==0.14.0 + # via httpcore +httpcore==1.0.6 + # via httpx +httpx==0.27.2 + # via jupyterlab +identify==2.6.1 + # via pre-commit +idna==3.10 + # via + # anyio + # httpx + # jsonschema + # requests +importlib-metadata==8.5.0 + # via twine +iniconfig==2.0.0 + # via pytest +ipdb==0.13.13 + # via dapper (setup.py) +ipykernel==6.29.5 + # via + # jupyter + # jupyter-console + # jupyterlab + # mkdocs-jupyter +ipython==8.29.0 + # via + # dapper (setup.py) + # ipdb + # ipykernel + # ipywidgets + # jupyter-console +ipywidgets==8.1.5 + # via jupyter +isoduration==20.11.0 + # via jsonschema +jaraco-classes==3.4.0 + # via keyring +jaraco-context==6.0.1 + # via keyring +jaraco-functools==4.1.0 + # via keyring +jedi==0.19.1 + # via ipython +jinja2==3.1.4 + # via + # jupyter-server + # jupyterlab + # jupyterlab-server + # mkdocs + # mkdocs-material + # mkdocstrings + # nbconvert +json5==0.9.25 + # via jupyterlab-server +jsonpointer==3.0.0 + # via jsonschema +jsonschema==4.23.0 + # via + # jupyter-events + # jupyterlab-server + # nbformat +jsonschema-specifications==2024.10.1 + # via jsonschema +jupyter==1.1.1 + # via dapper (setup.py) +jupyter-client==8.6.3 + # via + # ipykernel + # jupyter-console + # jupyter-server + # nbclient +jupyter-console==6.6.3 + # via jupyter +jupyter-core==5.7.2 + # via + # ipykernel + # jupyter-client + # jupyter-console + # jupyter-server + # jupyterlab + # nbclient + # nbconvert + # nbformat +jupyter-events==0.10.0 + # via jupyter-server +jupyter-lsp==2.2.5 + # via jupyterlab +jupyter-server==2.14.2 + # via + # jupyter-lsp + # jupyterlab + # jupyterlab-server + # notebook + # notebook-shim +jupyter-server-terminals==0.5.3 + # via jupyter-server +jupyterlab==4.2.5 + # via + # jupyter + # notebook +jupyterlab-pygments==0.3.0 + # via nbconvert +jupyterlab-server==2.27.3 + # via + # jupyterlab + # notebook +jupyterlab-widgets==3.0.13 + # via ipywidgets +jupytext==1.16.4 + # via + # dapper (setup.py) + # mkdocs-jupyter +keyring==25.5.0 + # via twine +kiwisolver==1.4.7 + # via matplotlib +latexcodec==3.0.0 + # via pybtex +line-profiler==4.1.3 + # via dapper (setup.py) +markdown==3.7 + # via + # mkdocs + # mkdocs-autorefs + # mkdocs-material + # mkdocstrings + # pymdown-extensions +markdown-it-py==3.0.0 + # via + # jupytext + # mdit-py-plugins + # rich +markupsafe==3.0.2 + # via + # jinja2 + # mkdocs + # mkdocs-autorefs + # mkdocstrings + # nbconvert +matplotlib==3.8.4 + # via + # dapper (setup.py) + # mpl-tools + # patlib +matplotlib-inline==0.1.7 + # via + # ipykernel + # ipython +mdit-py-plugins==0.4.2 + # via jupytext +mdurl==0.1.2 + # via markdown-it-py +mergedeep==1.3.4 + # via + # mkdocs + # mkdocs-get-deps +mistune==3.0.2 + # via nbconvert +mkdocs==1.6.1 + # via + # mkdocs-autorefs + # mkdocs-gen-files + # mkdocs-jupyter + # mkdocs-literate-nav + # mkdocs-material + # mkdocs-section-index + # mkdocstrings +mkdocs-autorefs==1.2.0 + # via + # mkdocstrings + # mkdocstrings-python +mkdocs-gen-files==0.5.0 + # via dapper (setup.py) +mkdocs-get-deps==0.2.0 + # via mkdocs +mkdocs-glightbox==0.4.0 + # via dapper (setup.py) +mkdocs-jupyter==0.25.1 + # via dapper (setup.py) +mkdocs-literate-nav==0.6.1 + # via dapper (setup.py) +mkdocs-material==9.5.42 + # via + # dapper (setup.py) + # mkdocs-jupyter +mkdocs-material-extensions==1.3.1 + # via mkdocs-material +mkdocs-section-index==0.3.9 + # via dapper (setup.py) +mkdocstrings==0.26.2 + # via + # dapper (setup.py) + # mkdocstrings-python +mkdocstrings-python==1.12.2 + # via dapper (setup.py) +more-itertools==10.5.0 + # via + # jaraco-classes + # jaraco-functools +mpl-tools==0.2.50 + # via dapper (setup.py) +multiprocess==0.70.16 + # via pathos +nbclient==0.10.0 + # via nbconvert +nbconvert==7.16.4 + # via + # jupyter + # jupyter-server + # mkdocs-jupyter +nbformat==5.10.4 + # via + # jupyter-server + # jupytext + # nbclient + # nbconvert +nest-asyncio==1.6.0 + # via ipykernel +nh3==0.2.18 + # via readme-renderer +nodeenv==1.9.1 + # via pre-commit +notebook==7.2.2 + # via jupyter +notebook-shim==0.2.4 + # via + # jupyterlab + # notebook +numpy==1.26.4 + # via + # dapper (setup.py) + # contourpy + # matplotlib + # patlib + # scipy +overrides==7.7.0 + # via jupyter-server +packaging==24.1 + # via + # ipykernel + # jupyter-server + # jupyterlab + # jupyterlab-server + # jupytext + # matplotlib + # mkdocs + # nbconvert + # pyproject-api + # pytest + # pytest-sugar + # tox +paginate==0.5.7 + # via mkdocs-material +pandocfilters==1.5.1 + # via nbconvert +parso==0.8.4 + # via jedi +pathos==0.3.2 + # via dapper (setup.py) +pathspec==0.12.1 + # via mkdocs +patlib==0.3.5 + # via dapper (setup.py) +pexpect==4.9.0 + # via ipython +pillow==11.0.0 + # via matplotlib +pkginfo==1.10.0 + # via twine +platformdirs==4.3.6 + # via + # jupyter-core + # mkdocs-get-deps + # mkdocstrings + # tox + # virtualenv +pluggy==1.5.0 + # via + # pytest + # tox +pox==0.3.5 + # via pathos +ppft==1.7.6.9 + # via pathos +pprintpp==0.4.0 + # via pytest-clarity +pre-commit==4.0.1 + # via dapper (setup.py) +prometheus-client==0.21.0 + # via jupyter-server +prompt-toolkit==3.0.48 + # via + # ipython + # jupyter-console +psutil==6.1.0 + # via ipykernel +ptyprocess==0.7.0 + # via + # pexpect + # terminado +pure-eval==0.2.3 + # via stack-data +py-cpuinfo==9.0.0 + # via pytest-benchmark +pybtex==0.24.0 + # via dapper (setup.py) +pycparser==2.22 + # via cffi +pygments==2.18.0 + # via + # ipython + # jupyter-console + # mkdocs-jupyter + # mkdocs-material + # nbconvert + # readme-renderer + # rich +pymdown-extensions==10.12 + # via + # mkdocs-material + # mkdocstrings +pyparsing==3.2.0 + # via matplotlib +pyproject-api==1.8.0 + # via tox +pytest==8.3.3 + # via + # dapper (setup.py) + # pytest-benchmark + # pytest-clarity + # pytest-cov + # pytest-sugar + # pytest-timeout + # pytest-xdist +pytest-benchmark==5.1.0 + # via dapper (setup.py) +pytest-clarity==1.0.1 + # via dapper (setup.py) +pytest-cov==6.0.0 + # via dapper (setup.py) +pytest-sugar==1.0.0 + # via dapper (setup.py) +pytest-timeout==2.3.1 + # via dapper (setup.py) +pytest-xdist==3.6.1 + # via dapper (setup.py) +python-dateutil==2.9.0.post0 + # via + # arrow + # ghp-import + # jupyter-client + # matplotlib +python-json-logger==2.0.7 + # via jupyter-events +pyyaml==6.0.2 + # via + # dapper (setup.py) + # jupyter-events + # jupytext + # mkdocs + # mkdocs-get-deps + # pre-commit + # pybtex + # pymdown-extensions + # pyyaml-env-tag +pyyaml-env-tag==0.1 + # via mkdocs +pyzmq==26.2.0 + # via + # ipykernel + # jupyter-client + # jupyter-console + # jupyter-server +readme-renderer==44.0 + # via twine +referencing==0.35.1 + # via + # jsonschema + # jsonschema-specifications + # jupyter-events +regex==2024.9.11 + # via mkdocs-material +requests==2.32.3 + # via + # jupyterlab-server + # mkdocs-material + # requests-toolbelt + # twine +requests-toolbelt==1.0.0 + # via twine +rfc3339-validator==0.1.4 + # via + # jsonschema + # jupyter-events +rfc3986==2.0.0 + # via twine +rfc3986-validator==0.1.1 + # via + # jsonschema + # jupyter-events +rich==13.9.3 + # via + # pytest-clarity + # twine +rpds-py==0.20.0 + # via + # jsonschema + # referencing +ruff==0.7.1 + # via dapper (setup.py) +scipy==1.14.1 + # via + # dapper (setup.py) + # mpl-tools + # patlib +send2trash==1.8.3 + # via jupyter-server +setuptools==75.3.0 + # via jupyterlab +six==1.16.0 + # via + # asttokens + # pybtex + # python-dateutil + # rfc3339-validator +sniffio==1.3.1 + # via + # anyio + # httpx +soupsieve==2.6 + # via beautifulsoup4 +stack-data==0.6.3 + # via ipython +struct-tools==0.2.5 + # via dapper (setup.py) +tabulate==0.8.10 + # via dapper (setup.py) +termcolor==2.5.0 + # via pytest-sugar +terminado==0.18.1 + # via + # jupyter-server + # jupyter-server-terminals +threadpoolctl==3.5.0 + # via dapper (setup.py) +tinycss2==1.4.0 + # via nbconvert +tornado==6.4.1 + # via + # dapper (setup.py) + # ipykernel + # jupyter-client + # jupyter-server + # jupyterlab + # notebook + # terminado +tox==4.23.2 + # via dapper (setup.py) +tqdm==4.66.6 + # via dapper (setup.py) +traitlets==5.14.3 + # via + # comm + # ipykernel + # ipython + # ipywidgets + # jupyter-client + # jupyter-console + # jupyter-core + # jupyter-events + # jupyter-server + # jupyterlab + # matplotlib-inline + # nbclient + # nbconvert + # nbformat +twine==5.1.1 + # via dapper (setup.py) +types-python-dateutil==2.9.0.20241003 + # via arrow +uri-template==1.3.0 + # via jsonschema +urllib3==2.2.3 + # via + # requests + # twine +virtualenv==20.27.1 + # via + # pre-commit + # tox +watchdog==5.0.3 + # via mkdocs +wcwidth==0.2.13 + # via prompt-toolkit +webcolors==24.8.0 + # via jsonschema +webencodings==0.5.1 + # via + # bleach + # tinycss2 +websocket-client==1.8.0 + # via jupyter-server +widgetsnbextension==4.0.13 + # via ipywidgets +zipp==3.20.2 + # via importlib-metadata diff --git a/setup.py b/setup.py index b3981d3a..038b3363 100755 --- a/setup.py +++ b/setup.py @@ -22,6 +22,7 @@ "scipy>=1.10", "numpy~=1.20", "jupyter", + "notebook<7", "ipdb", "ipython>=5.1", "tornado~=6.3", # 6.2 breaks Jupyter plots (tested on local Mac, Linux) @@ -53,10 +54,23 @@ "pytest-timeout", ], "lint": ["ruff"], + "doc": [ + "mkdocs-material", + "mkdocstrings", + "mkdocstrings-python", + "mkdocs-gen-files", + "mkdocs-literate-nav", + "mkdocs-section-index", + "mkdocs-glightbox", + "mkdocs-jupyter", + "pybtex", + ], # 'flake8-docstrings', 'flake8-bugbear', 'flake8-comprehensions'], - "build": ["twine", "pdoc", "jupytext"], + "build": ["twine", "jupytext"], } -EXTRAS["dev"] = EXTRAS["debug"] + EXTRAS["test"] + EXTRAS["lint"] + EXTRAS["build"] +EXTRAS["dev"] = ( + EXTRAS["debug"] + EXTRAS["test"] + EXTRAS["lint"] + EXTRAS["build"] + EXTRAS["doc"] +) def find_version(*file_paths): @@ -100,7 +114,7 @@ def read(*parts): install_requires=INSTALL_REQUIRES, extras_require=EXTRAS, packages=find_packages(), - py_modules=["examples.basic_1", "examples.basic_2", "examples.basic_3"], + # py_modules=["examples.basic_1", "examples.basic_2", "examples.basic_3"], package_data={ "": ["*.txt", "*.md", "*.png", "*.yaml"], "dapper.mods.QG.f90": ["*.txt", "*.md", "*.png", "Makefile", "*.f90"], diff --git a/tests/test_data.py b/tests/test_data.py index 95ac52da..de3b9551 100644 --- a/tests/test_data.py +++ b/tests/test_data.py @@ -1,4 +1,4 @@ -"""Test data loading and `dapper.xp_process.xpSpace.print`. +"""Test data loading and [`xp_process.xpSpace.print`][]. If script is run with one of these command-line arguments, then there is no test generation, but rather: @@ -65,7 +65,7 @@ def cap_stdout(fun, *args, **kwargs): def gen_test_set(xp_dict, *args, **kwargs): - """Capture printout of `dapper.xp_process.xpSpace.print`, gen tests.""" + """Capture printout of [`xp_process.xpSpace.print`][], gen tests.""" # Get stdout of xpSpace.print() output = cap_stdout(xp_dict.print, *args, **kwargs, colorize=False) diff --git a/tests/test_round2.py b/tests/test_round2.py index 6870db0f..5ec6ca2f 100644 --- a/tests/test_round2.py +++ b/tests/test_round2.py @@ -9,7 +9,8 @@ class ca(float): """Make `==` approximate. - Example: + Examples + -------- >>> ca(1 + 1e-6) == 1 True