diff --git a/.all-contributorsrc b/.all-contributorsrc index b2da537f7..7f9a01b02 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -62,7 +62,8 @@ "profile": "https://www.brosaplanella.xyz", "contributions": [ "review", - "code" + "code", + "example" ] }, { @@ -97,7 +98,7 @@ { "login": "IntelLiGent", "name": "Horizon Europe IntelLiGent Consortium", - "avatar_url": "assets/logo-farger.pdf", + "avatar_url": "assets/logo-farger.svg", "profile": "https://heuintelligent.eu/", "contributions": [ "financial" @@ -111,6 +112,24 @@ "contributions": [ "code" ] + }, + { + "login": "MarkBlyth", + "name": "MarkBlyth", + "avatar_url": "https://avatars.githubusercontent.com/u/20501619?v=4", + "profile": "https://github.com/MarkBlyth", + "contributions": [ + "code" + ] + }, + { + "login": "f-g-r-i-m-m", + "name": "f-g-r-i-m-m", + "avatar_url": "https://avatars.githubusercontent.com/u/137511310?v=4", + "profile": "https://github.com/f-g-r-i-m-m", + "contributions": [ + "example" + ] } ], "contributorsPerLine": 7, diff --git a/.github/workflows/lychee_links.yaml b/.github/workflows/lychee_links.yaml new file mode 100644 index 000000000..9d5037dad --- /dev/null +++ b/.github/workflows/lychee_links.yaml @@ -0,0 +1,61 @@ +# Lychee Link Checking + +name: Links +on: + workflow_dispatch: + pull_request: + push: + branches: + - main + schedule: + - cron: '0 6 * * 0' # Run weekly on Sundays at 06:00 UTC + +jobs: + Lychee: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Restore lychee cache + uses: actions/cache@v4 + with: + path: .lycheecache + key: cache-lychee-${{ github.sha }} + restore-keys: cache-lychee- + + - name: Set up Lychee + uses: lycheeverse/lychee-action@v1.10.0 + with: + args: >- + --cache + --no-progress + --max-cache-age 2d + --timeout 10 + --max-retries 5 + --skip-missing + --exclude-loopback + --accept 200,429 + --exclude "https://tiles.stadiamaps.com/*|https://b.tile.openstreetmap.org/*" + --exclude "https://cartodb-basemaps-c.global.ssl.fastly.net/*" + --exclude "https://events.mapbox.com/*|https://events.mapbox.cn/*|https://api.mapbox.cn/*" + --exclude "https://github.com/mikolalysenko/glsl-read-float/*" + --exclude "https://fonts.openmaptiles.org/*" + --exclude "https://a.tile.openstreetmap.org/*" + --exclude "https://openstreetmap.org/*|https://www.openstreetmap.org/*" + --exclude "https://cdn.plot.ly/*" + --exclude "http://www.w3.org/*|https://www.w3.org/*" + --exclude "https://doi.org/*" + --exclude "https://raw.githubusercontent.com/paramm-team/pybamm-param/develop/pbparam/input/data/" + --exclude-path ./CHANGELOG.md + --exclude-path asv.conf.json + --exclude-path docs/conf.py + './**/*.rst' + './**/*.md' + './**/*.py' + './**/*.ipynb' + './**/*.json' + './**/*.toml' + fail: true + jobSummary: true + format: markdown diff --git a/.github/workflows/test_on_push.yaml b/.github/workflows/test_on_pull_request.yaml similarity index 84% rename from .github/workflows/test_on_push.yaml rename to .github/workflows/test_on_pull_request.yaml index e2b6d19d9..2f74bbe32 100644 --- a/.github/workflows/test_on_push.yaml +++ b/.github/workflows/test_on_pull_request.yaml @@ -105,6 +105,37 @@ jobs: run: | nox -s examples + # Quick benchmarks on macos-14 + benchmarks: + needs: style + runs-on: macos-14 + strategy: + fail-fast: false + name: Benchmarks + + steps: + - name: Check out PyBOP repository + uses: actions/checkout@v4 + with: + fetch-depth: 2 + + - name: Set up Python 3.12 + id: setup-python + uses: actions/setup-python@v4 + with: + python-version: 3.12 + + - name: Install dependencies + shell: bash + run: | + python -m pip install --upgrade pip asv[virtualenv] + + - name: Run quick benchmarks + shell: bash + run: | + asv machine --machine "GitHubRunner" + asv run --machine "GitHubRunner" --quick --show-stderr + # Runs only on macos-14 with Python 3.12 check_coverage: needs: style diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 47ef467c5..f063b3d4d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,7 +4,7 @@ ci: repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: "v0.5.0" + rev: "v0.6.4" hooks: - id: ruff args: [--fix, --show-fixes] @@ -32,3 +32,9 @@ repos: - id: rst-backticks - id: rst-directive-colons - id: rst-inline-touching-normal + + - repo: https://github.com/kynan/nbstripout + rev: 0.7.1 + hooks: + - id: nbstripout + args: ['--keep-output', '--drop-empty-cells'] diff --git a/CHANGELOG.md b/CHANGELOG.md index 616a26aeb..7668f65f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,14 +6,52 @@ ## Breaking Changes +# [v24.9.0](https://github.com/pybop-team/PyBOP/tree/v24.9.0) - 2024-09-10 + +## Features + +- [#462](https://github.com/pybop-team/PyBOP/pull/462) - Enables multidimensional learning rate for `pybop.AdamW` with updated (more robust) integration testing. Fixes bug in `Minkowski` and `SumofPower` cost functions for gradient-based optimisers. +- [#411](https://github.com/pybop-team/PyBOP/pull/411) - Updates notebooks with README in `examples/` directory, removes kaleido dependency and moves to nbviewer rendering, displays notebook figures with `notebook_connected` plotly renderer +- [#6](https://github.com/pybop-team/PyBOP/issues/6) - Adds Monte Carlo functionality, with methods based on Pints' algorithms. A base class is added `BaseSampler`, in addition to `PintsBaseSampler`. +- [#353](https://github.com/pybop-team/PyBOP/issues/353) - Allow user-defined check_params functions to enforce nonlinear constraints, and enable SciPy constrained optimisation methods +- [#222](https://github.com/pybop-team/PyBOP/issues/222) - Adds an example for performing and electrode balancing. +- [#441](https://github.com/pybop-team/PyBOP/issues/441) - Adds an example for estimating constants within a `pybamm.FunctionalParameter`. +- [#405](https://github.com/pybop-team/PyBOP/pull/405) - Adds frequency-domain based EIS prediction methods via `model.simulateEIS` and updates to `problem.evaluate` with examples and tests. +- [#460](https://github.com/pybop-team/PyBOP/pull/460) - Notebook example files added for ECM and folder structure updated. +- [#450](https://github.com/pybop-team/PyBOP/pull/450) - Adds support for IDAKLU with output variables, and corresponding examples, tests. +- [#364](https://github.com/pybop-team/PyBOP/pull/364) - Adds the MultiFittingProblem class and the multi_fitting example script. +- [#444](https://github.com/pybop-team/PyBOP/issues/444) - Merge `BaseModel` `build()` and `rebuild()` functionality. +- [#435](https://github.com/pybop-team/PyBOP/pull/435) - Adds SLF001 linting for private members. +- [#418](https://github.com/pybop-team/PyBOP/issues/418) - Wraps the `get_parameter_info` method from PyBaMM to get a dictionary of parameter names and types. +- [#413](https://github.com/pybop-team/PyBOP/pull/413) - Adds `DesignCost` functionality to `WeightedCost` class with additional tests. +- [#357](https://github.com/pybop-team/PyBOP/pull/357) - Adds `Transformation()` class with `LogTransformation()`, `IdentityTransformation()`, and `ScaledTransformation()`, `ComposedTransformation()` implementations with corresponding examples and tests. +- [#427](https://github.com/pybop-team/PyBOP/issues/427) - Adds the nbstripout pre-commit hook to remove unnecessary metadata from notebooks. +- [#327](https://github.com/pybop-team/PyBOP/issues/327) - Adds the `WeightedCost` subclass, defines when to evaluate a problem and adds the `spm_weighted_cost` example script. +- [#393](https://github.com/pybop-team/PyBOP/pull/383) - Adds Minkowski and SumofPower cost classes, with an example and corresponding tests. +- [#403](https://github.com/pybop-team/PyBOP/pull/403/) - Adds lychee link checking action. + +## Bug Fixes + +- [#473](https://github.com/pybop-team/PyBOP/pull/473) - Bugfixes for transformation class, adds optional `apply_transform` arg to `BaseCost.__call__()`, adds `log_update()` method to `BaseOptimiser` +- [#464](https://github.com/pybop-team/PyBOP/issues/464) - Fix order of design `parameter_set` updates and refactor `update_capacity`. +- [#468](https://github.com/pybop-team/PyBOP/issue/468) - Renames `quick_plot.py` to `standard_plots.py`. +- [#454](https://github.com/pybop-team/PyBOP/issue/454) - Fixes benchmarking suite. +- [#421](https://github.com/pybop-team/PyBOP/issues/421) - Adds a default value for the initial SOC for design problems. + +## Breaking Changes + +- [#483](https://github.com/pybop-team/PyBOP/pull/483) - Replaces `pybop.MAP` with `pybop.LogPosterior` with an updated call args and bugfixes. +- [#436](https://github.com/pybop-team/PyBOP/pull/436) - **API Change:** The functionality from `BaseCost.evaluate/S1` & `BaseCost._evaluate/S1` is represented in `BaseCost.__call__` & `BaseCost.compute`. `BaseCost.compute` directly acts on the predictions, while `BaseCost.__call__` calls `BaseProblem.evaluate/S1` before `BaseCost.compute`. `compute` has optional args for gradient cost calculations. +- [#424](https://github.com/pybop-team/PyBOP/issues/424) - Replaces the `init_soc` input to `FittingProblem` with the option to pass an initial OCV value, updates `BaseModel` and fixes `multi_model_identification.ipynb` and `spm_electrode_design.ipynb`. + # [v24.6.1](https://github.com/pybop-team/PyBOP/tree/v24.6.1) - 2024-07-31 ## Features + - [#313](https://github.com/pybop-team/PyBOP/pull/313/) - Fixes for PyBaMM v24.5, drops support for PyBaMM v23.9, v24.1 ## Bug Fixes - ## Breaking Changes # [v24.6](https://github.com/pybop-team/PyBOP/tree/v24.6) - 2024-07-08 diff --git a/CITATION.cff b/CITATION.cff index 6583447f3..0b6552e6e 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -11,5 +11,5 @@ authors: family-names: Courtier - given-names: David family-names: Howey -version: "24.6.1" # Update this when you release a new version +version: "24.9.0" # Update this when you release a new version repository-code: 'https://www.github.com/pybop-team/pybop' diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 727c5c510..274ba954e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -21,7 +21,7 @@ pip install -e .[all,dev] Before you commit any code, please perform the following checks using [Nox](https://nox.thea.codes/en/stable/index.html): -- [All tests pass](#testing): `$ nox -s unit` +- [All tests pass](#testing): `$ nox -s quick` ### Installing and using pre-commit @@ -62,9 +62,9 @@ You now have everything you need to start making changes! ### B. Writing your code -6. PyBOP is developed in [Python](https://en.wikipedia.org/wiki/Python_(programming_language)), and makes heavy use of [NumPy](https://en.wikipedia.org/wiki/NumPy) (see also [NumPy for MatLab users](https://numpy.org/doc/stable/user/numpy-for-matlab-users.html) and [Python for R users](http://blog.hackerearth.com/how-can-r-users-learn-python-for-data-science)). +6. PyBOP is developed in [Python](https://en.wikipedia.org/wiki/Python_(programming_language)), and makes heavy use of [NumPy](https://en.wikipedia.org/wiki/NumPy) (see also [NumPy for MatLab users](https://numpy.org/doc/stable/user/numpy-for-matlab-users.html) and [Python for R users](https://rebeccabarter.com/blog/2023-09-11-from_r_to_python)). 7. Make sure to follow our [coding style guidelines](#coding-style-guidelines). -8. Commit your changes to your branch with [useful, descriptive commit messages](https://chris.beams.io/posts/git-commit/): Remember these are publicly visible and should still make sense a few months ahead in time. While developing, you can keep using the GitHub issue you're working on as a place for discussion. [Refer to your commits](https://stackoverflow.com/questions/8910271/how-can-i-reference-a-commit-in-an-issue-comment-on-github) when discussing specific lines of code. +8. Commit your changes to your branch with [useful, descriptive commit messages](https://chris.beams.io/posts/git-commit/): Remember these are publicly visible and should still make sense a few months ahead in time. While developing, you can keep using the GitHub issue you're working on as a place for discussion. Refer to your commits when discussing specific lines of code. This is achieved by referencing the SHA-hash in the comment. An example of this looks like: `the commit 3e5c1e6 solved the issue...` 9. If you want to add a dependency on another library, or re-use code you found somewhere else, have a look at [these guidelines](#dependencies-and-reusing-code). ### C. Merging your changes with PyBOP @@ -83,16 +83,20 @@ PyBOP follows the [PEP8 recommendations](https://www.python.org/dev/peps/pep-000 ### Ruff -We use [ruff](https://github.com/charliermarsh/ruff) to check our PEP8 adherence. To try this on your system, navigate to the PyBOP directory in a console and type +We use [ruff](https://github.com/charliermarsh/ruff) to lint and ensure adherence to Python PEP standards. To manually trigger `ruff`, navigate to the PyBOP directory in a console and type ```bash python -m pip install pre-commit pre-commit run ruff ``` -ruff is configured inside the file `pre-commit-config.yaml`, allowing us to ignore some errors. If you think this should be added or removed, please submit an [issue](https://guides.github.com/features/issues/). +ruff is configured inside the file `pyproject.toml`, allowing us to ignore some errors. If you think a rule should be added or removed, please submit an [issue](https://guides.github.com/features/issues/). -When you commit your changes they will be checked against ruff automatically (see [Pre-commit checks](#pre-commit-checks)). +When you commit your changes they will be checked against ruff automatically (see [Pre-commit checks](#pre-commit-checks)). If you are having issues getting your commit to pass the linting, it +is possible to skip linting for single lines (this should only be done as a **last resort**) by adding a line comment of `#noqa: $ruff_rule` where the `$ruff_rule` is replaced with the rule in question. +It is also possible to skip linting altogether by committing your changes by using the +`--no-verify` command-line flag. +These rules can be found in the ruff configuration in `pyproject.toml` or in the failed pre-commit output. Please note the lint skipping in the pull request for reviewers. ### Naming @@ -105,7 +109,7 @@ Class names are CamelCase, and start with an upper case letter, for example `MyO While it's a bad idea for developers to "reinvent the wheel", it's important for users to get a _reasonably sized download and an easy install_. In addition, external libraries can sometimes cease to be supported, and when they contain bugs it might take a while before fixes become available as automatic downloads to PyBOP users. For these reasons, all dependencies in PyBOP should be thought about carefully and discussed on GitHub. -Direct inclusion of code from other packages is possible, as long as their license permits it and is compatible with ours, but again should be considered carefully and discussed in the group. Snippets from blogs and [stackoverflow](https://stackoverflow.com/) can often be included but must include attribution to the original by commenting with a link in the source code. +Direct inclusion of code from other packages is possible, as long as their license permits it and is compatible with ours, but again should be considered carefully and discussed in the group. Snippets from blogs and stackoverflow can often be included but must include attribution to the original by commenting with a link in the source code. ### Separating dependencies diff --git a/README.md b/README.md index 99f7a031e..5814f7202 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ [![Contributors](https://img.shields.io/github/contributors/pybop-team/PyBOP)](https://github.com/pybop-team/PyBOP/graphs/contributors) [![Last Commit](https://img.shields.io/github/last-commit/pybop-team/PyBOP/develop?color=purple)](https://github.com/pybop-team/PyBOP/commits/develop) [![Python Versions from PEP 621 TOML](https://img.shields.io/python/required-version-toml?tomlFilePath=https%3A%2F%2Fraw.githubusercontent.com%2Fpybop-team%2FPyBOP%2Fdevelop%2Fpyproject.toml&label=Python)](https://pypi.org/project/pybop/) - [![Forks](https://img.shields.io/github/forks/pybop-team/PyBOP?style=flat)](https://github.com/pybop-team/PyBOPe/network/members) + [![Forks](https://img.shields.io/github/forks/pybop-team/PyBOP?style=flat)](https://github.com/pybop-team/PyBOP/network/members) [![Stars](https://img.shields.io/github/stars/pybop-team/PyBOP?style=flat&color=gold)](https://github.com/pybop-team/PyBOP/stargazers) [![Codecov](https://codecov.io/gh/pybop-team/PyBOP/branch/develop/graph/badge.svg)](https://codecov.io/gh/pybop-team/PyBOP) [![Open Issues](https://img.shields.io/github/issues/pybop-team/PyBOP)](https://github.com/pybop-team/PyBOP/issues/) @@ -74,7 +74,7 @@ Additional script-based examples can be found in the [examples directory](https: - [Unscented Kalman filter parameter identification of a SPM](https://github.com/pybop-team/PyBOP/blob/develop/examples/scripts/spm_UKF.py) - [Import and export parameters using Faraday's BPX format](https://github.com/pybop-team/PyBOP/blob/develop/examples/scripts/BPX_spm.py) - [Maximum a posteriori parameter identification of a SPM](https://github.com/pybop-team/PyBOP/blob/develop/examples/scripts/BPX_spm.py) -- [Gradient based parameter identification of a SPM](https://github.com/pybop-team/PyBOP/blob/develop/examples/scripts/spm_adam.py) +- [Gradient based parameter identification of a SPM](https://github.com/pybop-team/PyBOP/blob/develop/examples/scripts/spm_AdamW.py) ### Supported Methods The table below lists the currently supported [models](https://github.com/pybop-team/PyBOP/tree/develop/pybop/models), [optimisers](https://github.com/pybop-team/PyBOP/tree/develop/pybop/optimisers), and [cost functions](https://github.com/pybop-team/PyBOP/tree/develop/pybop/costs) in PyBOP. @@ -120,14 +120,16 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
0?[0]:[]);if(o.enter().append(\"g\").classed(f.containerClassName,!0).style(\"cursor\",\"pointer\"),o.exit().each((function(){n.select(this).selectAll(\"g.\"+f.headerGroupClassName).each(a)})).remove(),0!==r.length){var l=o.selectAll(\"g.\"+f.headerGroupClassName).data(r,p);l.enter().append(\"g\").classed(f.headerGroupClassName,!0);for(var u=s.ensureSingle(o,\"g\",f.dropdownButtonGroupClassName,(function(t){t.style(\"pointer-events\",\"all\")})),c=0;c 90&&i.log(\"Long binary search...\"),h-1},e.sorterAsc=function(t,e){return t-e},e.sorterDes=function(t,e){return e-t},e.distinctVals=function(t){var r,n=t.slice();for(n.sort(e.sorterAsc),r=n.length-1;r>-1&&n[r]===o;r--);for(var i,a=n[r]-n[0]||1,s=a/(r||1)/1e4,l=[],u=0;u<=r;u++){var c=n[u],f=c-i;void 0===i?(l.push(c),i=c):f>s&&(a=Math.min(a,f),l.push(c),i=c)}return{vals:l,minDiff:a}},e.roundUp=function(t,e,r){for(var n,i=0,a=e.length-1,o=0,s=r?0:1,l=r?1:0,u=r?Math.ceil:Math.floor;i0&&(n=1),r&&n)return t.sort(e)}return n?t:t.reverse()},e.findIndexOfMin=function(t,e){e=e||a;for(var r,n=1/0,i=0;il?r.y-l:0;return Math.sqrt(u*u+f*f)}for(var p=h(u);p;){if((u+=p+r)>f)return;p=h(u)}for(p=h(f);p;){if(u>(f-=p+r))return;p=h(f)}return{min:u,max:f,len:f-u,total:c,isClosed:0===u&&f===c&&Math.abs(n.x-i.x)<.1&&Math.abs(n.y-i.y)<.1}},e.findPointOnPath=function(t,e,r,n){for(var i,a,o,s=(n=n||{}).pathLength||t.getTotalLength(),l=n.tolerance||.001,u=n.iterationLimit||30,c=t.getPointAtLength(0)[r]>t.getPointAtLength(s)[r]?-1:1,f=0,h=0,p=s;f0?p=i:h=i,f++}return a}},33040:function(t,e,r){\"use strict\";var n=r(38248),i=r(49760),a=r(72160),o=r(8932),s=r(22548).defaultLine,l=r(38116).isArrayOrTypedArray,u=a(s);function c(t,e){var r=t;return r[3]*=e,r}function f(t){if(n(t))return u;var e=a(t);return e.length?e:u}function h(t){return n(t)?t:1}t.exports={formatColor:function(t,e,r){var n=t.color;n&&n._inputArray&&(n=n._inputArray);var i,s,p,d,v,g=l(n),y=l(e),m=o.extractOpts(t),x=[];if(i=void 0!==m.colorscale?o.makeColorScaleFuncFromTrace(t):f,s=g?function(t,e){return void 0===t[e]?u:a(i(t[e]))}:f,p=y?function(t,e){return void 0===t[e]?1:h(t[e])}:h,g||y)for(var b=0;b
/i;e.BR_TAG_ALL=/
/gi;var _=/(^|[\\s\"'])style\\s*=\\s*(\"([^\"]*);?\"|'([^']*);?')/i,w=/(^|[\\s\"'])href\\s*=\\s*(\"([^\"]*)\"|'([^']*)')/i,T=/(^|[\\s\"'])target\\s*=\\s*(\"([^\"\\s]*)\"|'([^'\\s]*)')/i,k=/(^|[\\s\"'])popup\\s*=\\s*(\"([\\w=,]*)\"|'([\\w=,]*)')/i;function A(t,e){if(!t)return null;var r=t.match(e),n=r&&(r[3]||r[4]);return n&&L(n)}var M=/(^|;)\\s*color:/;e.plainText=function(t,e){for(var r=void 0!==(e=e||{}).len&&-1!==e.len?e.len:1/0,n=void 0!==e.allowedTags?e.allowedTags:[\"br\"],i=t.split(m),a=[],o=\"\",s=0,l=0;l
\"+l;e.text=u}(t,o,r,u):\"log\"===c?function(t,e,r,n,a){var o=t.dtick,l=e.x,u=t.tickformat,c=\"string\"==typeof o&&o.charAt(0);if(\"never\"===a&&(a=\"\"),n&&\"L\"!==c&&(o=\"L3\",c=\"L\"),u||\"L\"===c)e.text=bt(Math.pow(10,l),t,a,n);else if(i(o)||\"D\"===c&&s.mod(l+.01,1)<.1){var f=Math.round(l),h=Math.abs(f),p=t.exponentformat;\"power\"===p||mt(p)&&xt(f)?(e.text=0===f?1:1===f?\"10\":\"10\"+(f>1?\"\":P)+h+\"\",e.fontSize*=1.25):(\"e\"===p||\"E\"===p)&&h>2?e.text=\"1\"+p+(f>0?\"+\":P)+h:(e.text=bt(Math.pow(10,l),t,\"\",\"fakehover\"),\"D1\"===o&&\"y\"===t._id.charAt(0)&&(e.dy-=e.fontSize/6))}else{if(\"D\"!==c)throw\"unrecognized dtick \"+String(o);e.text=String(Math.round(Math.pow(10,s.mod(l,1)))),e.fontSize*=.75}if(\"D1\"===t.dtick){var d=String(e.text).charAt(0);\"0\"!==d&&\"1\"!==d||(\"y\"===t._id.charAt(0)?e.dx-=e.fontSize/4:(e.dy+=e.fontSize/2,e.dx+=(t.range[1]>t.range[0]?1:-1)*e.fontSize*(l<0?.5:.25)))}}(t,o,0,u,g):\"category\"===c?function(t,e){var r=t._categories[Math.round(e.x)];void 0===r&&(r=\"\"),e.text=String(r)}(t,o):\"multicategory\"===c?function(t,e,r){var n=Math.round(e.x),i=t._categories[n]||[],a=void 0===i[1]?\"\":String(i[1]),o=void 0===i[0]?\"\":String(i[0]);r?e.text=o+\" - \"+a:(e.text=a,e.text2=o)}(t,o,r):Dt(t)?function(t,e,r,n,i){if(\"radians\"!==t.thetaunit||r)e.text=bt(e.x,t,i,n);else{var a=e.x/180;if(0===a)e.text=\"0\";else{var o=function(t){function e(t,e){return Math.abs(t-e)<=1e-6}var r=function(t){for(var r=1;!e(Math.round(t*r)/r,t);)r*=10;return r}(t),n=t*r,i=Math.abs(function t(r,n){return e(n,0)?r:t(n,r%n)}(n,r));return[Math.round(n/i),Math.round(r/i)]}(a);if(o[1]>=100)e.text=bt(s.deg2rad(e.x),t,i,n);else{var l=e.x<0;1===o[1]?1===o[0]?e.text=\"π\":e.text=o[0]+\"π\":e.text=[\"\",o[0],\"\",\"⁄\",\"\",o[1],\"\",\"π\"].join(\"\"),l&&(e.text=P+e.text)}}}}(t,o,r,u,g):function(t,e,r,n,i){\"never\"===i?i=\"\":\"all\"===t.showexponent&&Math.abs(e.x/t.dtick)<1e-6&&(i=\"hide\"),e.text=bt(e.x,t,i,n)}(t,o,0,u,g),n||(t.tickprefix&&!v(t.showtickprefix)&&(o.text=t.tickprefix+o.text),t.ticksuffix&&!v(t.showticksuffix)&&(o.text+=t.ticksuffix)),t.labelalias&&t.labelalias.hasOwnProperty(o.text)){var y=t.labelalias[o.text];\"string\"==typeof y&&(o.text=y)}return(\"boundaries\"===t.tickson||t.showdividers)&&(o.xbnd=[h(o.x-.5),h(o.x+t.dtick-.5)]),o},H.hoverLabelText=function(t,e,r){r&&(t=s.extendFlat({},t,{hoverformat:r}));var n=s.isArrayOrTypedArray(e)?e[0]:e,i=s.isArrayOrTypedArray(e)?e[1]:void 0;if(void 0!==i&&i!==n)return H.hoverLabelText(t,n,r)+\" - \"+H.hoverLabelText(t,i,r);var a=\"log\"===t.type&&n<=0,o=H.tickText(t,t.c2l(a?-n:n),\"hover\").text;return a?0===n?\"0\":P+o:o};var yt=[\"f\",\"p\",\"n\",\"μ\",\"m\",\"\",\"k\",\"M\",\"G\",\"T\"];function mt(t){return\"SI\"===t||\"B\"===t}function xt(t){return t>14||t<-15}function bt(t,e,r,n){var a=t<0,o=e._tickround,l=r||e.exponentformat||\"B\",u=e._tickexponent,c=H.getTickFormat(e),f=e.separatethousands;if(n){var h={exponentformat:l,minexponent:e.minexponent,dtick:\"none\"===e.showexponent?e.dtick:i(t)&&Math.abs(t)||1,range:\"none\"===e.showexponent?e.range.map(e.r2d):[0,t||1]};vt(h),o=(Number(h._tickround)||0)+4,u=h._tickexponent,e.hoverformat&&(c=e.hoverformat)}if(c)return e._numFormat(c)(t).replace(/-/g,P);var p,d=Math.pow(10,-o)/2;if(\"none\"===l&&(u=0),(t=Math.abs(t))
\")):x=h.textLabel;var L={x:h.traceCoordinate[0],y:h.traceCoordinate[1],z:h.traceCoordinate[2],data:_._input,fullData:_,curveNumber:_.index,pointNumber:T};d.appendArrayPointValue(L,_,T),t._module.eventData&&(L=_._module.eventData(L,h,_,{},T));var C={points:[L]};if(e.fullSceneLayout.hovermode){var P=[];d.loneHover({trace:_,x:(.5+.5*m[0]/m[3])*s,y:(.5-.5*m[1]/m[3])*l,xLabel:k.xLabel,yLabel:k.yLabel,zLabel:k.zLabel,text:x,name:c.name,color:d.castHoverOption(_,T,\"bgcolor\")||c.color,borderColor:d.castHoverOption(_,T,\"bordercolor\"),fontFamily:d.castHoverOption(_,T,\"font.family\"),fontSize:d.castHoverOption(_,T,\"font.size\"),fontColor:d.castHoverOption(_,T,\"font.color\"),nameLength:d.castHoverOption(_,T,\"namelength\"),textAlign:d.castHoverOption(_,T,\"align\"),hovertemplate:f.castOption(_,T,\"hovertemplate\"),hovertemplateLabels:f.extendFlat({},L,k),eventData:[L]},{container:n,gd:r,inOut_bbox:P}),L.bbox=P[0]}h.distance<5&&(h.buttons||w)?r.emit(\"plotly_click\",C):r.emit(\"plotly_hover\",C),this.oldEventData=C}else d.loneUnhover(n),this.oldEventData&&r.emit(\"plotly_unhover\",this.oldEventData),this.oldEventData=void 0;e.drawAnnotations(e)},k.recoverContext=function(){var t=this;t.glplot.dispose();var e=function(){t.glplot.gl.isContextLost()?requestAnimationFrame(e):t.initializeGLPlot()?t.plot.apply(t,t.plotArgs):f.error(\"Catastrophic and unrecoverable WebGL error. Context lost.\")};requestAnimationFrame(e)};var M=[\"xaxis\",\"yaxis\",\"zaxis\"];function S(t,e,r){for(var n=t.fullSceneLayout,i=0;i<3;i++){var a=M[i],o=a.charAt(0),s=n[a],l=e[o],u=e[o+\"calendar\"],c=e[\"_\"+o+\"length\"];if(f.isArrayOrTypedArray(l))for(var h,p=0;p<(c||l.length);p++)if(f.isArrayOrTypedArray(l[p]))for(var d=0;d
\");b.text(T).attr(\"data-unformatted\",T).call(f.convertToTspans,t),_=c.bBox(b.node())}b.attr(\"transform\",a(-3,8-_.height)),x.insert(\"rect\",\".static-attribution\").attr({x:-_.width-6,y:-_.height-3,width:_.width+6,height:_.height+3,fill:\"rgba(255, 255, 255, 0.75)\"});var k=1;_.width+6>w&&(k=w/(_.width+6));var A=[n.l+n.w*h.x[1],n.t+n.h*(1-h.y[0])];x.attr(\"transform\",a(A[0],A[1])+o(k))}},e.updateFx=function(t){for(var e=t._fullLayout,r=e._subplots[p],n=0;n