diff --git a/.all-contributorsrc b/.all-contributorsrc index 304a73ce..2e8f851d 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -50,7 +50,9 @@ "contributions": [ "ideas", "mentoring", - "review" + "review", + "code", + "test" ] }, { @@ -79,6 +81,16 @@ "contributions": [ "financial" ] + }, + { + "login": "agriyakhetarpal", + "name": "Agriya Khetarpal", + "avatar_url": "https://avatars.githubusercontent.com/u/74401230?v=4", + "profile": "https://github.com/agriyakhetarpal", + "contributions": [ + "code", + "infra" + ] } ], "contributorsPerLine": 7, diff --git a/.github/workflows/release-action.yaml b/.github/workflows/release_action.yaml similarity index 100% rename from .github/workflows/release-action.yaml rename to .github/workflows/release_action.yaml diff --git a/.github/workflows/scheduled_tests.yaml b/.github/workflows/scheduled_tests.yaml index 83a0d986..0ed549f0 100644 --- a/.github/workflows/scheduled_tests.yaml +++ b/.github/workflows/scheduled_tests.yaml @@ -10,38 +10,97 @@ on: schedule: - cron: '0 9 * * *' +# Check noxfile.py for associated environment variables +env: + PYBOP_SCHEDULED: 1 + jobs: + # Dynamically create a matrix of OS, Python, and PyBaMM versions + create_pybamm_matrix: + name: Dynamically create GitHub Actions matrix + runs-on: ubuntu-latest + steps: + - name: Check out PyBOP repository + uses: actions/checkout@v4 + with: + sparse-checkout-cone-mode: false + sparse-checkout: | + scripts/ci/build_matrix.sh + + - name: Run script to create matrix + id: set-matrix + run: | + echo "matrix=$(bash scripts/ci/build_matrix.sh)" >> "$GITHUB_OUTPUT" + outputs: + pybop_matrix: ${{ steps.set-matrix.outputs.matrix }} + + # filter the matrix to only include the macOS-latest entries + filter_pybamm_matrix: + name: Filter the matrix for macOS-latest entries + needs: [create_pybamm_matrix] + runs-on: ubuntu-latest + outputs: + filtered_pybop_matrix: ${{ steps.set-matrix.outputs.matrix }} + steps: + - name: Filter pybop matrix + id: set-matrix + run: | + import json + import os + + # Get the matrix + matrix_json = '${{ needs.create_pybamm_matrix.outputs.pybop_matrix }}' + matrix = json.loads(matrix_json) + + # Filter the matrix for macOS-latest entries only + filtered_entries = [entry for entry in matrix['include'] if entry['os'] == 'macos-latest'] + filtered_matrix = {'include': filtered_entries} + + # Set the output variable for other jobs to use + output_file = os.environ['GITHUB_OUTPUT'] + with open(output_file, "a", encoding="utf-8") as output_stream: + output_stream.write(f"matrix={json.dumps(filtered_matrix)}\n") + shell: python + build: + needs: [create_pybamm_matrix, filter_pybamm_matrix] + name: Build (${{ matrix.os }}, Python ${{ matrix.python_version }}, PyBaMM ${{ matrix.pybamm_version }}) runs-on: ${{ matrix.os }} strategy: fail-fast: false - matrix: - os: [ubuntu-latest, windows-latest, macos-latest] - python-version: ["3.8", "3.9", "3.10", "3.11"] + matrix: ${{fromJson(needs.create_pybamm_matrix.outputs.pybop_matrix)}} + env: + PYBAMM_VERSION: ${{ matrix.pybamm_version }} steps: - uses: actions/checkout@v4 - - name: Set up Python ${{ matrix.python-version }} + - name: Set up Python ${{ matrix.python_version }} uses: actions/setup-python@v4 with: - python-version: ${{ matrix.python-version }} + python-version: ${{ matrix.python_version }} + - name: Install dependencies run: | python -m pip install --upgrade pip nox + - name: Unit tests with nox - run: | - python -m nox -s unit - python -m nox -s notebooks + run: python -m nox -s unit - #M-series Mac Mini + - name: Run notebooks with nox + run: python -m nox -s notebooks + + # M-series Mac Mini build-apple-mseries: + needs: [filter_pybamm_matrix] + name: Build (MacOS M-series, Python ${{ matrix.python_version }}, PyBaMM ${{ matrix.pybamm_version }}) runs-on: [self-hosted, macOS, ARM64] + if: github.repository == 'pybop-team/PyBOP' env: GITHUB_PATH: ${PYENV_ROOT/bin:$PATH} + PYBAMM_VERSION: ${{ matrix.pybamm_version }} strategy: fail-fast: false - matrix: - python-version: ["3.10"] + matrix: ${{fromJson(needs.filter_pybamm_matrix.outputs.filtered_pybop_matrix)}} steps: - uses: actions/checkout@v4 @@ -49,15 +108,15 @@ jobs: shell: bash run: | eval "$(pyenv init -)" - pyenv install ${{ matrix.python-version }} -s - pyenv virtualenv ${{ matrix.python-version }} pybop-${{ matrix.python-version }} + pyenv install ${{ matrix.python_version }} -s + pyenv virtualenv ${{ matrix.python_version }} pybop-${{ matrix.python_version }}-${{ matrix.pybamm_version }} - - name: Install dependencies & run unit tests + - name: Install dependencies & run unit + notebook tests shell: bash run: | eval "$(pyenv init -)" - pyenv activate pybop-${{ matrix.python-version }} - python -m pip install --upgrade pip wheel setuptools nox + pyenv activate pybop-${{ matrix.python_version }}-${{ matrix.pybamm_version }} + python -m pip install --upgrade pip nox python -m nox -s unit python -m nox -s notebooks @@ -66,5 +125,5 @@ jobs: shell: bash run: | eval "$(pyenv init -)" - pyenv activate pybop-${{ matrix.python-version }} + pyenv activate pybop-${{ matrix.python_version }}-${{ matrix.pybamm_version }} pyenv uninstall -f $( python --version ) diff --git a/.github/workflows/test_on_push.yaml b/.github/workflows/test_on_push.yaml index 688614e4..cfc373b8 100644 --- a/.github/workflows/test_on_push.yaml +++ b/.github/workflows/test_on_push.yaml @@ -29,26 +29,96 @@ jobs: build: + needs: style runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: [ubuntu-latest, windows-latest, macos-latest] - python-version: ["3.8", "3.9", "3.10", "3.11"] + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] + exclude: # We run the coverage tests on Ubuntu with Python 3.11 + - os: ubuntu-latest + python-version: "3.11" + # Include MacOS M-series Runners + include: + - os: macos-14 + python-version: "3.10" + - os: macos-14 + python-version: "3.11" + - os: macos-14 + python-version: "3.12" steps: - - uses: actions/checkout@v4 + - name: Check out PyBOP repository + uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} + - name: Install dependencies run: | python -m pip install --upgrade pip nox + + - name: Check out PyBaMM v24.1 + uses: actions/checkout@v4 + with: + repository: pybamm-team/PyBaMM + ref: v24.1 + path: pybamm + + - name: Compile PyBaMM from source with IDAKLU solver (ubuntu-latest) + if: matrix.os == 'ubuntu-latest' + run: | + cd pybamm + sudo apt-get update && sudo apt-get install -y libopenblas-dev + python -m nox -s pybamm-requires + python -m pip install .[all] + cd .. + + - name: Compile PyBaMM from source with IDAKLU solver (macos-latest) + if: matrix.os == 'macos-latest' + run: | + brew reinstall gcc + brew install libomp + cd pybamm + python -m nox -s pybamm-requires + python -m pip install .[all] + cd .. + + - name: Compile PyBaMM from source with IDAKLU solver (macos-14) + if: matrix.os == 'macos-14' + run: | + brew reinstall gcc + brew install libomp + cd pybamm + python -m nox -s pybamm-requires + python -m pip install .[all] + cd .. + + # TODO: remove later, temporary addition to test Windows build + - name: Compile PyBaMM from source with IDAKLU solver (windows-latest) + if: matrix.os == 'windows-latest' + env: + PYBAMM_USE_VCPKG: "ON" + VCPKG_ROOT_DIR: C:\vcpkg + VCPKG_DEFAULT_TRIPLET: x64-windows-static-md + VCPKG_FEATURE_FLAGS: manifests,registries + CMAKE_GENERATOR: "Visual Studio 17 2022" + CMAKE_GENERATOR_PLATFORM: x64 + CMAKE_BUILD_PARALLEL_LEVEL: 4 + run: | + cd pybamm + git clone --depth 1 --branch v2.11.1 https://github.com/pybind/pybind11.git + python -m pip install .[all] + cd .. + - name: Unit and notebook tests with nox run: | - nox -s unit - nox -s notebooks + pip install -e .[all,dev] + python -m pytest --unit + python -m pytest --nbmake --examples examples/ # Runs only on Ubuntu with Python 3.11 check_coverage: @@ -60,6 +130,7 @@ jobs: steps: - name: Check out PyBOP repository uses: actions/checkout@v4 + - name: Set up Python 3.11 id: setup-python uses: actions/setup-python@v4 @@ -71,8 +142,28 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip nox + + - name: Check out PyBaMM v24.1 + uses: actions/checkout@v4 + with: + repository: pybamm-team/PyBaMM + ref: v24.1 + path: pybamm + + - name: Compile PyBaMM from source with IDAKLU solver (ubuntu-latest) + run: | + cd pybamm + sudo apt-get update + sudo apt-get install -y libopenblas-dev + python -m nox -s pybamm-requires + python -m pip install .[all] + rm -rf pybind11/ + cd .. + - name: Run coverage tests for Ubuntu with Python 3.11 and generate report - run: nox -s coverage + run: | + pip install -e .[all,dev] + python -m pytest --unit --examples --cov --cov-report=xml - name: Upload coverage report uses: codecov/codecov-action@v3 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 69b81ea9..b61b4c9a 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.1.14" + rev: "v0.2.2" hooks: - id: ruff args: [--fix, --show-fixes] diff --git a/CHANGELOG.md b/CHANGELOG.md index 130a627f..46210212 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,15 +2,25 @@ ## Features +- [#206](https://github.com/pybop-team/PyBOP/pull/206) - Adds Python 3.12 support with corresponding github actions changes. +- [#18](https://github.com/pybop-team/PyBOP/pull/18) - Adds geometric parameter fitting capability, via `model.rebuild()` with `model.rebuild_parameters`. +- [#203](https://github.com/pybop-team/PyBOP/pull/203) - Adds support for modern Python packaging via a `pyproject.toml` file and configures the `pytest` test runner and `ruff` linter to use their configurations stored as declarative metadata. +- [#123](https://github.com/pybop-team/PyBOP/issues/123) - Configures scheduled tests to run against the last three PyPI releases of PyBaMM via dynamic GitHub Actions matrix generation. +- [#187](https://github.com/pybop-team/PyBOP/issues/187) - Adds M1 Github runner to `test_on_push` workflow, updt. self-hosted supported python versions in scheduled tests. - [#118](https://github.com/pybop-team/PyBOP/issues/118) - Adds example jupyter notebooks. - [#151](https://github.com/pybop-team/PyBOP/issues/151) - Adds a standalone version of the Problem class. +- [#12](https://github.com/pybop-team/PyBOP/issues/12) - Adds initial implementation of an Observer class and an unscented Kalman filter. +- [#190](https://github.com/pybop-team/PyBOP/issues/190) - Adds a second example design cost, namely the VolumetricEnergyDensity. ## Bug Fixes +- [#123](https://github.com/pybop-team/PyBOP/issues/123) - Reinstates check for availability of parameter sets via PyBaMM upon retrieval by `pybop.ParameterSet.pybamm()`. +- [#196](https://github.com/pybop-team/PyBOP/issues/196) - Fixes failing observer cost tests. - [#63](https://github.com/pybop-team/PyBOP/issues/63) - Removes NLOpt Optimiser from future releases. This is to support deployment to the Apple M-Series platform. - [#164](https://github.com/pybop-team/PyBOP/issues/164) - Fixes convergence issues with gradient-based optimisers, changes default `model.check_params()` to allow infeasible solutions during optimisation iterations. Adds a feasibility check on the optimal parameters. # [v23.12](https://github.com/pybop-team/PyBOP/tree/v23.12) - 2023-12-19 + ## Features - [#141](https://github.com/pybop-team/PyBOP/pull/141) - Adds documentation with Sphinx and PyData Sphinx Theme. Updates docstrings across package, relocates `costs` and `dataset` to top-level of package. Adds noxfile session and deployment workflow for docs. @@ -28,6 +38,8 @@ ## Bug Fixes +- [#182](https://github.com/pybop-team/PyBOP/pull/182) - Allow square-brackets indexing of Dataset + # [v23.11](https://github.com/pybop-team/PyBOP/releases/tag/v23.11) - Initial release - Adds Pints, NLOpt, and SciPy optimisers diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9fecf0c0..6d6f8aaa 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,17 +4,19 @@ If you'd like to contribute to PyBOP, please have a look at the guidelines below ## Developer-Installation -To install PyBOP for development purposes, which includes the testing and plotting dependencies, use the `[all]` flag as demonstrated below: +To install PyBOP for development purposes, which includes the plotting dependencies, use the `[all]` and the `[dev]` flags as demonstrated below: For `zsh`: ```sh -pip install -e '.[all]' +pip install -e '.[all,dev]' ``` + For `bash`: ```sh -pip install -e .[all] +pip install -e .[all,dev] ``` + ## Pre-commit checks Before you commit any code, please perform the following checks using [Nox](https://nox.thea.codes/en/stable/index.html): @@ -40,7 +42,7 @@ If you would like to skip the failing checks and push the code for further discu ## Workflow -We use [GIT](https://en.wikipedia.org/wiki/Git) and [GitHub](https://en.wikipedia.org/wiki/GitHub) to coordinate our work. When making any kind of update, we try to follow the procedure below. +We use [Git](https://en.wikipedia.org/wiki/Git) and [GitHub](https://en.wikipedia.org/wiki/GitHub) to coordinate our work. When making any kind of update, we try to follow the procedure below. ### A. Before you begin @@ -105,8 +107,8 @@ On the other hand... We _do_ want to compare several tools, to generate document 1. Core PyBOP: A minimal set, including things like NumPy, SciPy, etc. All infrastructure should run against this set of dependencies, as well as any numerical methods we implement ourselves. 2. Extras: Other inference packages and their dependencies. Methods we don't want to implement ourselves, but do want to provide an interface to can have their dependencies added here. -3. Documentation generating code: Everything you need to generate and work on the docs. -4. Development code: Everything you need to do PyBOP development (so all of the above packages, plus ruff and other testing tools). +3. Documentation generating code: Everything you need to generate and work on the docs. This is managed by the `[docs]` set of extras. +4. Development code: Everything you need to do PyBOP development (so all of the above packages, plus ruff and other testing tools). This is managed by the `[dev]` set of extras. Only 'core pybop' is installed by default. The others have to be specified explicitly when running the installation command. @@ -283,14 +285,14 @@ as above, and then use some of the profiling tools. In order of increasing detai ## Infrastructure -### Setuptools +### Installation via `pip` -Installation of PyBOP _and dependencies_ is handled via [setuptools](http://setuptools.readthedocs.io/) +Installation of PyBOP and its dependencies is handled via [`pip`](https://pip.pypa.io/) through the [setuptools](http://setuptools.readthedocs.io/) build-backend. Configuration files: ``` -setup.py +pyproject.toml ``` Note that this file must be kept in sync with the version number in [pybop/**init**.py](https://github.com/pybop-team/PyBOP/blob/develop/pybop/__init__.py). diff --git a/README.md b/README.md index c86b05da..b3c9cd32 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,7 @@ pip install -e "path/to/pybop" To check whether PyBOP has been installed correctly, run one of the examples in the following section. For a development installation, please refer to the [contributing guide](https://github.com/pybop-team/PyBOP/blob/develop/CONTRIBUTING.md#Installation). ### Prerequisites -To use and/or contribute to PyBOP, first install Python (3.8-3.11). On a Debian-based distribution, this looks like: +To use and/or contribute to PyBOP, first install Python (3.8 — 3.12). On a Debian-based distribution, this looks like: ```bash sudo apt update @@ -111,13 +111,13 @@ These general cases encompass a wide variety of optimisation problems that requi PyBOP comes with a number of [example notebooks and scripts](https://github.com/pybop-team/PyBOP/blob/develop/examples) which can be found in the examples folder. -The [spm_descent.py](https://github.com/pybop-team/PyBOP/blob/develop/examples/scripts/spm_descent.py) script illustrates a straightforward example that starts by generating artificial data from a single particle model (SPM). The unknown parameter values are identified by implementing a sum-of-square error cost function using the terminal voltage as the observed signal and a gradient descent optimiser. To run this example: +The [spm_pso.py](https://github.com/pybop-team/PyBOP/blob/develop/examples/scripts/spm_pso.py) script illustrates a straightforward example that starts by generating artificial data from a single particle model (SPM). The unknown parameter values are identified by employing a sum-of-squared errors cost function using the terminal voltage as the observed signal and a particle swarm optimisation algorithm. To run this example: ```bash -python examples/scripts/spm_descent.py +python examples/scripts/spm_pso.py ``` -In addition, [spm_CMAES.ipynb](https://github.com/pybop-team/PyBOP/blob/develop/examples/notebooks/spm_CMAES.ipynb) provides a second example in notebook form. This example estimates the SPM parameters based on an RMSE cost function and a BOBYQA optimiser. +Alternatively, [spm_CMAES.ipynb](https://github.com/pybop-team/PyBOP/blob/develop/examples/notebooks/spm_CMAES.ipynb) provides an example in notebook form. This example estimates SPM parameters based on a sum-of-squared errors cost function and a CMA-ES optimiser. ## Code of Conduct @@ -145,10 +145,13 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d Brady Planden
Brady Planden

🚇 ⚠️ 💻 💡 👀 NicolaCourtier
NicolaCourtier

💻 👀 💡 ⚠️ David Howey
David Howey

🤔 🧑‍🏫 - Martin Robinson
Martin Robinson

🤔 🧑‍🏫 👀 + Martin Robinson
Martin Robinson

🤔 🧑‍🏫 👀 💻 ⚠️ Ferran Brosa Planella
Ferran Brosa Planella

👀 - UKRI
UKRI

💵 Faraday Institution
Faraday Institution

💵 + UK Research and Innovation
UK Research and Innovation

💵 + + + Agriya Khetarpal
Agriya Khetarpal

💻 🚇 diff --git a/conftest.py b/conftest.py index c632f3e5..5fc769a9 100644 --- a/conftest.py +++ b/conftest.py @@ -5,6 +5,9 @@ plotly.io.renderers.default = None matplotlib.use("Template") +# Ignore the pybamm/ folder and any subdirectories from test discovery +collect_ignore = ["pybamm/"] + def pytest_addoption(parser): parser.addoption( diff --git a/docs/conf.py b/docs/conf.py index 2b8b9278..ae94c5ad 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -8,7 +8,7 @@ root_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) sys.path.insert(0, root_path) -from pybop.version import __version__ # noqa: E402 +from pybop._version import __version__ # noqa: E402 # -- Project information ----------------------------------------------------- project = "PyBOP" diff --git a/examples/notebooks/spm_electrode_design.ipynb b/examples/notebooks/spm_electrode_design.ipynb new file mode 100644 index 00000000..9b378388 --- /dev/null +++ b/examples/notebooks/spm_electrode_design.ipynb @@ -0,0 +1,14703 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "expmkveO04pw" + }, + "source": [ + "## A Electrode Design Optimisation Example\n", + "\n", + "NOTE: This is a brittle example, the classes and methods below will be integrated into PyBOP in a future release.\n", + "\n", + "A design optimisation example loosely based on work by L.D. Couto available at https://doi.org/10.1016/j.energy.2022.125966.\n", + "\n", + "The target is to maximise the gravimetric energy density over a range of possible design parameter values, including for example:\n", + "\n", + "cross-sectional area = height x width (only need change one), electrode widths, particle radii, volume fractions and separator width." + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "X87NUGPW04py", + "outputId": "0d785b07-7cff-4aeb-e60a-4ff5a669afbf" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Note: you may need to restart the kernel to use updated packages.\n", + "Note: you may need to restart the kernel to use updated packages.\n" + ] + } + ], + "source": [ + "%pip install --upgrade pip ipywidgets pybamm -q\n", + "%pip install pybop -q" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "jAvD5fk104p0" + }, + "source": [ + "Next, we import the added packages plus any additional dependencies," + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": { + "id": "SQdt4brD04p1" + }, + "outputs": [], + "source": [ + "import pybop" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "X8-tubYY04p_" + }, + "source": [ + "## Optimise the Parameters" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "PQqhvSZN04p_" + }, + "source": [ + "First, we define the model to be used for the parameter optimisation," + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": { + "id": "zuvGHWID04p_" + }, + "outputs": [], + "source": [ + "parameter_set = pybop.ParameterSet.pybamm(\"Chen2020\")\n", + "model = pybop.lithium_ion.SPMe(parameter_set=parameter_set)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "ffS3CF_704qA" + }, + "source": [ + "Next, we define the model parameters for optimisation. Furthermore, PyBOP provides functionality to define a prior for the parameters. The initial parameters values used in the optimisation will be randomly drawn from the prior distribution." + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": { + "id": "WPCybXIJ04qA" + }, + "outputs": [], + "source": [ + "parameters = [\n", + " pybop.Parameter(\n", + " \"Positive electrode thickness [m]\",\n", + " prior=pybop.Gaussian(7.56e-05, 0.05e-05),\n", + " bounds=[65e-06, 10e-05],\n", + " ),\n", + " pybop.Parameter(\n", + " \"Positive particle radius [m]\",\n", + " prior=pybop.Gaussian(5.22e-06, 0.05e-06),\n", + " bounds=[2e-06, 9e-06],\n", + " ),\n", + "]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next, we construct the experiment for design optimisation and the initial state-of-charge," + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": {}, + "outputs": [], + "source": [ + "experiment = pybop.Experiment(\n", + " [\"Discharge at 1C until 2.5 V (5 seconds period)\"],\n", + ")\n", + "init_soc = 1 # start from full charge" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "n4OHa-aF04qA" + }, + "source": [ + "We can now define the output signal, the problem (which combines the model with the dataset) and construct a cost function which in this example is the `GravimetricEnergyDensity()` used to maximise the gravimetric energy density of the cell." + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": { + "id": "etMzRtx404qA" + }, + "outputs": [], + "source": [ + "signal = [\"Voltage [V]\", \"Current [A]\"]\n", + "problem = pybop.DesignProblem(\n", + " model, parameters, experiment, signal=signal, init_soc=init_soc\n", + ")\n", + "cost = pybop.GravimetricEnergyDensity(problem)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "eQiGurUV04qB" + }, + "source": [ + "Let's construct PyBOP's optimisation class. This class provides the methods needed to fit the forward model. For this example, we use particle swarm optimisation (PSO). Due to the computational requirements of the design optimisation methods, we limit the number of iterations to 5 for this example." + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": { + "id": "N3FtAhrT04qB" + }, + "outputs": [], + "source": [ + "optim = pybop.Optimisation(cost, optimiser=pybop.PSO, verbose=True)\n", + "optim.set_max_iterations(5)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "caprp-bV04qB" + }, + "source": [ + "Finally, we run the optimisation and return the values obtained," + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "metadata": { + "id": "-9OVt0EQ04qB" + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Halt: Maximum number of iterations (5) reached.\n", + "Estimated parameters: [7.38910820e-05 2.07135001e-06]\n", + "Initial gravimetric energy density: 386.13 Wh.kg-1\n", + "Optimised gravimetric energy density: 397.79 Wh.kg-1\n" + ] + } + ], + "source": [ + "x, final_cost = optim.run()\n", + "print(\"Estimated parameters:\", x)\n", + "print(f\"Initial gravimetric energy density: {-cost(cost.x0):.2f} Wh.kg-1\")\n", + "print(f\"Optimised gravimetric energy density: {-final_cost:.2f} Wh.kg-1\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "KxKURtH704qC" + }, + "source": [ + "## Plotting and Visualisation\n", + "\n", + "PyBOP provides various plotting utilities to visualise the results of the optimisation." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "-cWCOiqR04qC" + }, + "source": [ + "## Comparing System Response\n", + "\n", + "We can quickly plot the system's response using the estimated parameters compared to the initial parameters:\n" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "metadata": { + "id": "ZVfozY0A04qC" + }, + "outputs": [ + { + "data": { + "application/vnd.plotly.v1+json": { + "config": { + "plotlyServerURL": "https://plot.ly" + }, + "data": [ + { + "fill": "toself", + "fillcolor": "rgba(255,229,204,0.8)", + "hoverinfo": "skip", + "line": { + "color": "rgba(255,255,255,0)" + }, + "showlegend": false, + "type": "scatter", + "x": [ + 0, + 5, + 10, + 15, + 20, + 25, + 30, + 35, + 40, + 45, + 50, + 55, + 60, + 65, + 70, + 75, + 80, + 85, + 90, + 95, + 100, + 105, + 110, + 115, + 120, + 125, + 130, + 135, + 140, + 145, + 150, + 155, + 160, + 165, + 170, + 175, + 180, + 185, + 190, + 195, + 200, + 205, + 210, + 215, + 220, + 225, + 230, + 235, + 240, + 245, + 250, + 255, + 260, + 265, + 270, + 275, + 280, + 285, + 290, + 295, + 300, + 305, + 310, + 315, + 320, + 325, + 330, + 335, + 340, + 345, + 350, + 355, + 360, + 365, + 370, + 375, + 380, + 385, + 390, + 395, + 400, + 405, + 410, + 415, + 420, + 425, + 430, + 435, + 440, + 445, + 450, + 455, + 460, + 465, + 470, + 475, + 480, + 485, + 490, + 495, + 500, + 505, + 510, + 515, + 520, + 525, + 530, + 535, + 540, + 545, + 550, + 555, + 560, + 565, + 570, + 575, + 580, + 585, + 590, + 595, + 600, + 605, + 610, + 615, + 620, + 625, + 630, + 635, + 640, + 645, + 650, + 655, + 660, + 665, + 670, + 675, + 680, + 685, + 690, + 695, + 700, + 705, + 710, + 715, + 720, + 725, + 730, + 735, + 740, + 745, + 750, + 755, + 760, + 765, + 770, + 775, + 780, + 785, + 790, + 795, + 800, + 805, + 810, + 815, + 820, + 825, + 830, + 835, + 840, + 845, + 850, + 855, + 860, + 865, + 870, + 875, + 880, + 885, + 890, + 895, + 900, + 905, + 910, + 915, + 920, + 925, + 930, + 935, + 940, + 945, + 950, + 955, + 960, + 965, + 970, + 975, + 980, + 985, + 990, + 995, + 1000, + 1005, + 1010, + 1015, + 1020, + 1025, + 1030, + 1035, + 1040, + 1045, + 1050, + 1055, + 1060, + 1065, + 1070, + 1075, + 1080, + 1085, + 1090, + 1095, + 1100, + 1105, + 1110, + 1115, + 1120, + 1125, + 1130, + 1135, + 1140, + 1145, + 1150, + 1155, + 1160, + 1165, + 1170, + 1175, + 1180, + 1185, + 1190, + 1195, + 1200, + 1205, + 1210, + 1215, + 1220, + 1225, + 1230, + 1235, + 1240, + 1245, + 1250, + 1255, + 1260, + 1265, + 1270, + 1275, + 1280, + 1285, + 1290, + 1295, + 1300, + 1305, + 1310, + 1315, + 1320, + 1325, + 1330, + 1335, + 1340, + 1345, + 1350, + 1355, + 1360, + 1365, + 1370, + 1375, + 1380, + 1385, + 1390, + 1395, + 1400, + 1405, + 1410, + 1415, + 1420, + 1425, + 1430, + 1435, + 1440, + 1445, + 1450, + 1455, + 1460, + 1465, + 1470, + 1475, + 1480, + 1485, + 1490, + 1495, + 1500, + 1505, + 1510, + 1515, + 1520, + 1525, + 1530, + 1535, + 1540, + 1545, + 1550, + 1555, + 1560, + 1565, + 1570, + 1575, + 1580, + 1585, + 1590, + 1595, + 1600, + 1605, + 1610, + 1615, + 1620, + 1625, + 1630, + 1635, + 1640, + 1645, + 1650, + 1655, + 1660, + 1665, + 1670, + 1675, + 1680, + 1685, + 1690, + 1695, + 1700, + 1705, + 1710, + 1715, + 1720, + 1725, + 1730, + 1735, + 1740, + 1745, + 1750, + 1755, + 1760, + 1765, + 1770, + 1775, + 1780, + 1785, + 1790, + 1795, + 1800, + 1805, + 1810, + 1815, + 1820, + 1825, + 1830, + 1835, + 1840, + 1845, + 1850, + 1855, + 1860, + 1865, + 1870, + 1875, + 1880, + 1885, + 1890, + 1895, + 1900, + 1905, + 1910, + 1915, + 1920, + 1925, + 1930, + 1935, + 1940, + 1945, + 1950, + 1955, + 1960, + 1965, + 1970, + 1975, + 1980, + 1985, + 1990, + 1995, + 2000, + 2005, + 2010, + 2015, + 2020, + 2025, + 2030, + 2035, + 2040, + 2045, + 2050, + 2055, + 2060, + 2065, + 2070, + 2075, + 2080, + 2085, + 2090, + 2095, + 2100, + 2105, + 2110, + 2115, + 2120, + 2125, + 2130, + 2135, + 2140, + 2145, + 2150, + 2155, + 2160, + 2165, + 2170, + 2175, + 2180, + 2185, + 2190, + 2195, + 2200, + 2205, + 2210, + 2215, + 2220, + 2225, + 2230, + 2235, + 2240, + 2245, + 2250, + 2255, + 2260, + 2265, + 2270, + 2275, + 2280, + 2285, + 2290, + 2295, + 2300, + 2305, + 2310, + 2315, + 2320, + 2325, + 2330, + 2335, + 2340, + 2345, + 2350, + 2355, + 2360, + 2365, + 2370, + 2375, + 2380, + 2385, + 2390, + 2395, + 2400, + 2405, + 2410, + 2415, + 2420, + 2425, + 2430, + 2435, + 2440, + 2445, + 2450, + 2455, + 2460, + 2465, + 2470, + 2475, + 2480, + 2485, + 2490, + 2495, + 2500, + 2505, + 2510, + 2515, + 2520, + 2525, + 2530, + 2535, + 2540, + 2545, + 2550, + 2555, + 2560, + 2565, + 2570, + 2575, + 2580, + 2585, + 2590, + 2595, + 2600, + 2605, + 2610, + 2615, + 2620, + 2625, + 2630, + 2635, + 2640, + 2645, + 2650, + 2655, + 2660, + 2665, + 2670, + 2675, + 2680, + 2685, + 2690, + 2695, + 2700, + 2705, + 2710, + 2715, + 2720, + 2725, + 2730, + 2735, + 2740, + 2745, + 2750, + 2755, + 2760, + 2765, + 2770, + 2775, + 2780, + 2785, + 2790, + 2795, + 2800, + 2805, + 2810, + 2815, + 2820, + 2825, + 2830, + 2835, + 2840, + 2845, + 2850, + 2855, + 2860, + 2865, + 2870, + 2875, + 2880, + 2885, + 2890, + 2895, + 2900, + 2905, + 2910, + 2915, + 2920, + 2925, + 2930, + 2935, + 2940, + 2945, + 2950, + 2955, + 2960, + 2965, + 2970, + 2975, + 2980, + 2985, + 2990, + 2995, + 3000, + 3005, + 3010, + 3015, + 3020, + 3025, + 3030, + 3035, + 3040, + 3045, + 3050, + 3055, + 3060, + 3065, + 3070, + 3075, + 3080, + 3085, + 3090, + 3095, + 3100, + 3105, + 3110, + 3115, + 3120, + 3125, + 3130, + 3135, + 3140, + 3145, + 3150, + 3155, + 3160, + 3165, + 3170, + 3175, + 3180, + 3185, + 3190, + 3195, + 3200, + 3205, + 3210, + 3215, + 3220, + 3225, + 3230, + 3235, + 3240, + 3245, + 3250, + 3255, + 3260, + 3265, + 3270, + 3275, + 3280, + 3285, + 3290, + 3295, + 3300, + 3305, + 3310, + 3315, + 3320, + 3325, + 3330, + 3335, + 3340, + 3345, + 3350, + 3355, + 3360, + 3365, + 3370, + 3375, + 3380, + 3385, + 3390, + 3395, + 3400, + 3405, + 3410, + 3415, + 3420, + 3425, + 3430, + 3435, + 3440, + 3445, + 3450, + 3455, + 3460, + 3465, + 3470, + 3475, + 3480, + 3485, + 3490, + 3495, + 3500, + 3505, + 3510, + 3510.522507468857, + 3510.522507468857, + 3510, + 3505, + 3500, + 3495, + 3490, + 3485, + 3480, + 3475, + 3470, + 3465, + 3460, + 3455, + 3450, + 3445, + 3440, + 3435, + 3430, + 3425, + 3420, + 3415, + 3410, + 3405, + 3400, + 3395, + 3390, + 3385, + 3380, + 3375, + 3370, + 3365, + 3360, + 3355, + 3350, + 3345, + 3340, + 3335, + 3330, + 3325, + 3320, + 3315, + 3310, + 3305, + 3300, + 3295, + 3290, + 3285, + 3280, + 3275, + 3270, + 3265, + 3260, + 3255, + 3250, + 3245, + 3240, + 3235, + 3230, + 3225, + 3220, + 3215, + 3210, + 3205, + 3200, + 3195, + 3190, + 3185, + 3180, + 3175, + 3170, + 3165, + 3160, + 3155, + 3150, + 3145, + 3140, + 3135, + 3130, + 3125, + 3120, + 3115, + 3110, + 3105, + 3100, + 3095, + 3090, + 3085, + 3080, + 3075, + 3070, + 3065, + 3060, + 3055, + 3050, + 3045, + 3040, + 3035, + 3030, + 3025, + 3020, + 3015, + 3010, + 3005, + 3000, + 2995, + 2990, + 2985, + 2980, + 2975, + 2970, + 2965, + 2960, + 2955, + 2950, + 2945, + 2940, + 2935, + 2930, + 2925, + 2920, + 2915, + 2910, + 2905, + 2900, + 2895, + 2890, + 2885, + 2880, + 2875, + 2870, + 2865, + 2860, + 2855, + 2850, + 2845, + 2840, + 2835, + 2830, + 2825, + 2820, + 2815, + 2810, + 2805, + 2800, + 2795, + 2790, + 2785, + 2780, + 2775, + 2770, + 2765, + 2760, + 2755, + 2750, + 2745, + 2740, + 2735, + 2730, + 2725, + 2720, + 2715, + 2710, + 2705, + 2700, + 2695, + 2690, + 2685, + 2680, + 2675, + 2670, + 2665, + 2660, + 2655, + 2650, + 2645, + 2640, + 2635, + 2630, + 2625, + 2620, + 2615, + 2610, + 2605, + 2600, + 2595, + 2590, + 2585, + 2580, + 2575, + 2570, + 2565, + 2560, + 2555, + 2550, + 2545, + 2540, + 2535, + 2530, + 2525, + 2520, + 2515, + 2510, + 2505, + 2500, + 2495, + 2490, + 2485, + 2480, + 2475, + 2470, + 2465, + 2460, + 2455, + 2450, + 2445, + 2440, + 2435, + 2430, + 2425, + 2420, + 2415, + 2410, + 2405, + 2400, + 2395, + 2390, + 2385, + 2380, + 2375, + 2370, + 2365, + 2360, + 2355, + 2350, + 2345, + 2340, + 2335, + 2330, + 2325, + 2320, + 2315, + 2310, + 2305, + 2300, + 2295, + 2290, + 2285, + 2280, + 2275, + 2270, + 2265, + 2260, + 2255, + 2250, + 2245, + 2240, + 2235, + 2230, + 2225, + 2220, + 2215, + 2210, + 2205, + 2200, + 2195, + 2190, + 2185, + 2180, + 2175, + 2170, + 2165, + 2160, + 2155, + 2150, + 2145, + 2140, + 2135, + 2130, + 2125, + 2120, + 2115, + 2110, + 2105, + 2100, + 2095, + 2090, + 2085, + 2080, + 2075, + 2070, + 2065, + 2060, + 2055, + 2050, + 2045, + 2040, + 2035, + 2030, + 2025, + 2020, + 2015, + 2010, + 2005, + 2000, + 1995, + 1990, + 1985, + 1980, + 1975, + 1970, + 1965, + 1960, + 1955, + 1950, + 1945, + 1940, + 1935, + 1930, + 1925, + 1920, + 1915, + 1910, + 1905, + 1900, + 1895, + 1890, + 1885, + 1880, + 1875, + 1870, + 1865, + 1860, + 1855, + 1850, + 1845, + 1840, + 1835, + 1830, + 1825, + 1820, + 1815, + 1810, + 1805, + 1800, + 1795, + 1790, + 1785, + 1780, + 1775, + 1770, + 1765, + 1760, + 1755, + 1750, + 1745, + 1740, + 1735, + 1730, + 1725, + 1720, + 1715, + 1710, + 1705, + 1700, + 1695, + 1690, + 1685, + 1680, + 1675, + 1670, + 1665, + 1660, + 1655, + 1650, + 1645, + 1640, + 1635, + 1630, + 1625, + 1620, + 1615, + 1610, + 1605, + 1600, + 1595, + 1590, + 1585, + 1580, + 1575, + 1570, + 1565, + 1560, + 1555, + 1550, + 1545, + 1540, + 1535, + 1530, + 1525, + 1520, + 1515, + 1510, + 1505, + 1500, + 1495, + 1490, + 1485, + 1480, + 1475, + 1470, + 1465, + 1460, + 1455, + 1450, + 1445, + 1440, + 1435, + 1430, + 1425, + 1420, + 1415, + 1410, + 1405, + 1400, + 1395, + 1390, + 1385, + 1380, + 1375, + 1370, + 1365, + 1360, + 1355, + 1350, + 1345, + 1340, + 1335, + 1330, + 1325, + 1320, + 1315, + 1310, + 1305, + 1300, + 1295, + 1290, + 1285, + 1280, + 1275, + 1270, + 1265, + 1260, + 1255, + 1250, + 1245, + 1240, + 1235, + 1230, + 1225, + 1220, + 1215, + 1210, + 1205, + 1200, + 1195, + 1190, + 1185, + 1180, + 1175, + 1170, + 1165, + 1160, + 1155, + 1150, + 1145, + 1140, + 1135, + 1130, + 1125, + 1120, + 1115, + 1110, + 1105, + 1100, + 1095, + 1090, + 1085, + 1080, + 1075, + 1070, + 1065, + 1060, + 1055, + 1050, + 1045, + 1040, + 1035, + 1030, + 1025, + 1020, + 1015, + 1010, + 1005, + 1000, + 995, + 990, + 985, + 980, + 975, + 970, + 965, + 960, + 955, + 950, + 945, + 940, + 935, + 930, + 925, + 920, + 915, + 910, + 905, + 900, + 895, + 890, + 885, + 880, + 875, + 870, + 865, + 860, + 855, + 850, + 845, + 840, + 835, + 830, + 825, + 820, + 815, + 810, + 805, + 800, + 795, + 790, + 785, + 780, + 775, + 770, + 765, + 760, + 755, + 750, + 745, + 740, + 735, + 730, + 725, + 720, + 715, + 710, + 705, + 700, + 695, + 690, + 685, + 680, + 675, + 670, + 665, + 660, + 655, + 650, + 645, + 640, + 635, + 630, + 625, + 620, + 615, + 610, + 605, + 600, + 595, + 590, + 585, + 580, + 575, + 570, + 565, + 560, + 555, + 550, + 545, + 540, + 535, + 530, + 525, + 520, + 515, + 510, + 505, + 500, + 495, + 490, + 485, + 480, + 475, + 470, + 465, + 460, + 455, + 450, + 445, + 440, + 435, + 430, + 425, + 420, + 415, + 410, + 405, + 400, + 395, + 390, + 385, + 380, + 375, + 370, + 365, + 360, + 355, + 350, + 345, + 340, + 335, + 330, + 325, + 320, + 315, + 310, + 305, + 300, + 295, + 290, + 285, + 280, + 275, + 270, + 265, + 260, + 255, + 250, + 245, + 240, + 235, + 230, + 225, + 220, + 215, + 210, + 205, + 200, + 195, + 190, + 185, + 180, + 175, + 170, + 165, + 160, + 155, + 150, + 145, + 140, + 135, + 130, + 125, + 120, + 115, + 110, + 105, + 100, + 95, + 90, + 85, + 80, + 75, + 70, + 65, + 60, + 55, + 50, + 45, + 40, + 35, + 30, + 25, + 20, + 15, + 10, + 5, + 0 + ], + "y": [ + 4.0781535600043215, + 4.062128492127174, + 4.052857122903849, + 4.045468006198349, + 4.039164655238445, + 4.033627171418397, + 4.028679583717739, + 4.024206701988653, + 4.020126671141043, + 4.016378976602813, + 4.0129172388855325, + 4.009704860323629, + 4.006712148243633, + 4.003914931880226, + 4.001293401720541, + 3.99883105267252, + 3.996513935514436, + 3.994329756382196, + 3.992268199397369, + 3.990320376053805, + 3.9884786151773146, + 3.9867362494432466, + 3.985087257169886, + 3.983526273017628, + 3.982048410059068, + 3.9806493509083007, + 3.9793252039606934, + 3.978072411222857, + 3.976887703615617, + 3.9757680144916834, + 3.974710494221364, + 3.9737124647122415, + 3.9727714087989336, + 3.9718849370518097, + 3.971050756685545, + 3.970266658014845, + 3.969530495605136, + 3.968840185890992, + 3.9681936922444754, + 3.9675890165982617, + 3.9670241906840658, + 3.966497268901439, + 3.9660063241849626, + 3.9655494426727618, + 3.965124722491505, + 3.9647302700948246, + 3.964364200491103, + 3.96402463621366, + 3.963709707556047, + 3.9634175509708824, + 3.963146309998812, + 3.9628941362300134, + 3.9626591937709392, + 3.962439660679193, + 3.962233730227712, + 3.962039611945965, + 3.9618555334371224, + 3.961679745579746, + 3.961510524327599, + 3.961346175226278, + 3.961185033115423, + 3.961025465675149, + 3.9608658760771354, + 3.960704706406239, + 3.960540438943686, + 3.9603716016500656, + 3.9601967674705296, + 3.9600145563644937, + 3.9598236409958183, + 3.959622743439794, + 3.95941063977861, + 3.9591861609960928, + 3.9589481906857658, + 3.958695677190458, + 3.9584276269293177, + 3.958143105106755, + 3.957841219978721, + 3.9575211653766007, + 3.9571821882635025, + 3.956823597460946, + 3.95644474094881, + 3.9560450489014496, + 3.9556240092170705, + 3.9551811668294734, + 3.954716112093187, + 3.954228495561752, + 3.953718036274191, + 3.9531845023818577, + 3.952627716265804, + 3.952047553288323, + 3.951443940452529, + 3.95081681939006, + 3.9501662187187385, + 3.9494922074961463, + 3.9487948939344584, + 3.9480744285682583, + 3.947331002452851, + 3.946564842068588, + 3.9457761724734417, + 3.944965309992684, + 3.9441325800465257, + 3.9432783377421696, + 3.942402965889137, + 3.941506873027282, + 3.9405904873417623, + 3.939654253971517, + 3.93869865012846, + 3.937724165047615, + 3.9367313037486937, + 3.9357205852191233, + 3.9346925406436792, + 3.933647718924989, + 3.932586674408453, + 3.9315099636900217, + 3.9304181529211393, + 3.929311812830394, + 3.9281915173138953, + 3.9270578420856808, + 3.9259113661614737, + 3.924752671067227, + 3.923582336475441, + 3.922400932133523, + 3.921209026068122, + 3.920007184672868, + 3.9187959711483114, + 3.917575932353624, + 3.916347613554031, + 3.915111551967116, + 3.9138682760725696, + 3.912618304973568, + 3.91136214780803, + 3.9101003032077903, + 3.908833258803867, + 3.9075614907756258, + 3.9062854634420208, + 3.9050056288924857, + 3.903722426655612, + 3.9024362838142888, + 3.9011476169194825, + 3.8998568227693142, + 3.8985642880087874, + 3.8972703855527144, + 3.895975474433693, + 3.8946798996721896, + 3.893383992166422, + 3.8920880685998727, + 3.8907924313642135, + 3.889497368495429, + 3.888203153621002, + 3.88691004591595, + 3.885618290065571, + 3.8843281162328154, + 3.883039740028083, + 3.881753362479445, + 3.880469170001101, + 3.879187334358151, + 3.877908012625472, + 3.8766313471388174, + 3.875357465436043, + 3.874086480186543, + 3.8728184902884193, + 3.871553577419355, + 3.870291808871612, + 3.869033236830797, + 3.867777898016867, + 3.866525813502974, + 3.8652769885098297, + 3.864031412173953, + 3.862789057288426, + 3.8615498800148944, + 3.860313819565679, + 3.8590807978551904, + 3.857850719119923, + 3.856623469506763, + 3.8553989166295626, + 3.8541769090945737, + 3.852957275995444, + 3.851739826379542, + 3.850524348687568, + 3.8493106101695633, + 3.848098356281136, + 3.846887310064644, + 3.845677171521611, + 3.844467616983481, + 3.843258298489774, + 3.8420488431839814, + 3.840838852739704, + 3.839627902831415, + 3.838415542666537, + 3.8372012945979743, + 3.8359846538389095, + 3.834765088304472, + 3.833542038607813, + 3.832314918241333, + 3.831083113976733, + 3.829845986520855, + 3.828602871467264, + 3.827353080586043, + 3.8260959034970865, + 3.8248306097736746, + 3.8235564515767657, + 3.822272666807052, + 3.8209784823430417, + 3.8196731188882183, + 3.818355795729425, + 3.817025736386133, + 3.815682174975635, + 3.8143243633068846, + 3.8129515787012234, + 3.811563132520345, + 3.8101583793612583, + 3.808736726853778, + 3.807297645969768, + 3.8058406817238137, + 3.80436546411492, + 3.802871719126947, + 3.8013592795750033, + 3.799828095556136, + 3.798278244237408, + 3.796709938695079, + 3.795123535506046, + 3.7935195407902023, + 3.791898614409963, + 3.7902615720536934, + 3.788609384963118, + 3.7869431771111666, + 3.7852642196964066, + 3.7835739228902656, + 3.781873824853354, + 3.7801655781220935, + 3.778450933554908, + 3.77673172211256, + 3.775009834826224, + 3.7732872013754, + 3.7715657677511754, + 3.7698474735163607, + 3.7681342291899154, + 3.766427894278298, + 3.764730256450638, + 3.763043012383892, + 3.76136775030427, + 3.759705935267657, + 3.7580588967027095, + 3.756427818706251, + 3.754813733065729, + 3.753217515064813, + 3.7516398818772, + 3.750081393439029, + 3.748542455571776, + 3.747023325098422, + 3.7455241166687054, + 3.744044810994625, + 3.742585264194985, + 3.741145217955012, + 3.7397243102231448, + 3.7383220861894384, + 3.7369380093171713, + 3.735571472229128, + 3.734221807281244, + 3.732888296687621, + 3.731570182090632, + 3.730266673498163, + 3.728976957535277, + 3.727700204980097, + 3.7264355775731017, + 3.7251822341053327, + 3.7239393358039874, + 3.7227060510445447, + 3.7214815594261057, + 3.720265055252273, + 3.7190557504633626, + 3.7178528770675903, + 3.7166556891193983, + 3.7154634642923026, + 3.7142755050922776, + 3.713091139755458, + 3.7119097228712814, + 3.710730635772411, + 3.709553286711547, + 3.708377110886087, + 3.707201570312106, + 3.7060261535853862, + 3.704850375550881, + 3.703673776900308, + 3.7024959237151753, + 3.701316406970347, + 3.7001348420108537, + 3.6989508680127496, + 3.6977641474369536, + 3.696574365483474, + 3.695381229551866, + 3.694184468712492, + 3.692983833192168, + 3.6917790938766006, + 3.6905700418313105, + 3.689356487841866, + 3.6881382619737697, + 3.686915213151619, + 3.6856872087568857, + 3.684454134243129, + 3.6832158927672474, + 3.6819724048350486, + 3.680723607959318, + 3.679469456328282, + 3.6782099204824057, + 3.676944986997304, + 3.675674658170475, + 3.6743989517097777, + 3.673117900421307, + 3.671831551894593, + 3.6705399681830535, + 3.6692432254777265, + 3.6679414137723967, + 3.666634636518434, + 3.665323010267701, + 3.664006664302177, + 3.662685740248972, + 3.661360391679693, + 3.660030783693226, + 3.658697092481317, + 3.657359504876294, + 3.656018217880785, + 3.654673438179195, + 3.6533253816311055, + 3.651974272746874, + 3.650620344145859, + 3.649263835998055, + 3.6479049954498697, + 3.64654407603522, + 3.6451813370730024, + 3.6438170430523886, + 3.642451463007464, + 3.6410848698827625, + 3.63971753989153, + 3.638349751868498, + 3.636981786619237, + 3.6356139262679137, + 3.6342464536056944, + 3.632879651441849, + 3.631513801959559, + 3.6301491860787576, + 3.628786082827955, + 3.627424768727187, + 3.626065517184118, + 3.624708597905212, + 3.623354276323971, + 3.622002813047884, + 3.620654463325893, + 3.6193094765378895, + 3.617968095707732, + 3.6166305570410184, + 3.6152970894888607, + 3.613967914338624, + 3.612643244832525, + 3.611323285814783, + 3.610008233407862, + 3.6086982747181993, + 3.607393587571689, + 3.606094340279004, + 3.6048006914304, + 3.603512789720329, + 3.602230773801203, + 3.6009547721656663, + 3.5996849030565214, + 3.5984212744049477, + 3.597163983794369, + 3.5959131184501274, + 3.5946687552535765, + 3.5934309607795667, + 3.5921997913561636, + 3.590975293145315, + 3.5897575022431543, + 3.5885464447987476, + 3.58734213714977, + 3.5861445859738885, + 3.584953788454368, + 3.5837697324585904, + 3.582592396728057, + 3.581421751078609, + 3.5802577566094453, + 3.579100365919649, + 3.577949523331005, + 3.576805165115871, + 3.575667219728831, + 3.5745356080411113, + 3.573410243576644, + 3.572291032748701, + 3.571177875096211, + 3.5700706635187833, + 3.568969284509659, + 3.567873618385751, + 3.5667835395141276, + 3.5656989165341613, + 3.564619612574924, + 3.5635454854671544, + 3.5624763879494097, + 3.5614121678680313, + 3.5603526683703404, + 3.5592977280912264, + 3.558247181332565, + 3.5572008582352743, + 3.556158584944156, + 3.555120183765203, + 3.5540854733155114, + 3.553054268665782, + 3.552026381475508, + 3.551001620120972, + 3.549979789816222, + 3.548960692727279, + 3.5479441280797843, + 3.5469298922604047, + 3.5459177789123357, + 3.54490757902527, + 3.5438990810201907, + 3.542892070829531, + 3.541886331973032, + 3.5408816456299164, + 3.539877790707815, + 3.5388745439091256, + 3.5378716797952148, + 3.536868970849262, + 3.5358661875382458, + 3.5348630983748404, + 3.5338594699798067, + 3.5328550671457184, + 3.5318496529026273, + 3.530842988586536, + 3.529834833911404, + 3.528824947045443, + 3.5278130846926485, + 3.52679900218025, + 3.525782453553013, + 3.5247631916752344, + 3.523740968341321, + 3.5227155343957404, + 3.52168663986339, + 3.520654034091064, + 3.519617465901109, + 3.5185766837579777, + 3.5175314359486443, + 3.516481470777728, + 3.5154265367780937, + 3.5143663829378275, + 3.513300758944305, + 3.5122294154461127, + 3.511152104333493, + 3.5100685790379904, + 3.508978594851838, + 3.5078819092676476, + 3.5067782823387015, + 3.5056674770604, + 3.504549259772839, + 3.503423400584881, + 3.502289673819573, + 3.5011478584808287, + 3.4999977387411385, + 3.4988391044498233, + 3.4976717516612643, + 3.496495483182384, + 3.495310109138316, + 3.494115447555253, + 3.492911324958987, + 3.491697576987634, + 3.4904740490167594, + 3.48924059679486, + 3.4879970870869235, + 3.486743398323682, + 3.4854794212537072, + 3.4842050595955354, + 3.482920230686549, + 3.4816248661252818, + 3.480318912403507, + 3.4790023315243683, + 3.4776751016025056, + 3.4763372174421123, + 3.4749886910886008, + 3.4736295523495304, + 3.472259849280337, + 3.4708796486302957, + 3.4694890362443003, + 3.4680881174159053, + 3.4666770171871777, + 3.4652558805911324, + 3.463824872832561, + 3.462384179403261, + 3.460934006128083, + 3.459474579138267, + 3.4580061447691928, + 3.4565289693797165, + 3.4550433390910085, + 3.4535495594431485, + 3.4520479549682195, + 3.450538868679267, + 3.449022661475053, + 3.447499711461173, + 3.445970413188571, + 3.4444351768113988, + 3.4428944271664896, + 3.4413486027776026, + 3.4397981547881065, + 3.438243545826284, + 3.4366852488082644, + 3.435123745683842, + 3.4335595261311216, + 3.431993086206317, + 3.4304249269554377, + 3.4288555529948748, + 3.4272854710682346, + 3.4257151885868966, + 3.4241452121619247, + 3.4225760461350117, + 3.4210081911160173, + 3.419442142534674, + 3.4178783892137656, + 3.4163174119708324, + 3.414759682255156, + 3.4132056608263377, + 3.411655796480413, + 3.4101105248288035, + 3.408570267135024, + 3.4070354292132636, + 3.405506400392595, + 3.403983552549601, + 3.4024672392117923, + 3.400957794733415, + 3.399455533544512, + 3.3979607494735324, + 3.39647371514301, + 3.3949946814372924, + 3.3935238770405647, + 3.3920615080428966, + 3.390607757611462, + 3.389162785723548, + 3.3877267289574102, + 3.3862997003367714, + 3.384881789224161, + 3.3834730612579964, + 3.382073558328104, + 3.3806832985838384, + 3.379302276469116, + 3.3779304627780213, + 3.3765678047249033, + 3.375214226022518, + 3.3738696269617083, + 3.372533884486157, + 3.3712068522556384, + 3.369888360691123, + 3.3685782169952594, + 3.3672762051416316, + 3.365982085826301, + 3.3646955963751912, + 3.363416450600904, + 3.3621443386026506, + 3.360878926503067, + 3.359619856115685, + 3.3583667445370065, + 3.3571191836572125, + 3.3558767395835076, + 3.3546389519703874, + 3.353405333251112, + 3.352175367764781, + 3.3509485107736836, + 3.3497241873656023, + 3.3485017912361044, + 3.34728068334599, + 3.346060190449433, + 3.344839603488674, + 3.3436181758516086, + 3.3423951214890755, + 3.3411696128893005, + 3.339940778907851, + 3.3387077024521448, + 3.3374694180208495, + 3.3362249090997276, + 3.334973105417095, + 3.333712880063687, + 3.332443046484157, + 3.3311623553495604, + 3.329869491323346, + 3.328563069736361, + 3.32724163319048, + 3.325903648114467, + 3.3245475013007426, + 3.3231714964572268, + 3.321773850814463, + 3.320352691835222, + 3.3189060540813884, + 3.317431876301088, + 3.3159279988083807, + 3.314392161237326, + 3.312822000763001, + 3.3112150508927645, + 3.3095687409427863, + 3.307880396326462, + 3.306147239793123, + 3.304366393766847, + 3.3025348839458797, + 3.300649644332875, + 3.2987075238735573, + 3.2967052948870874, + 3.294639663473017, + 3.292507282077737, + 3.290304764395832, + 3.288028702768201, + 3.2856756882176765, + 3.2832423332335283, + 3.2807252973771406, + 3.278121315731635, + 3.2754272301574874, + 3.2726400232438095, + 3.269756854760963, + 3.2667751003251047, + 3.2636923918801815, + 3.2605066594903542, + 3.25721617381772, + 3.2538195885414565, + 3.250315981858906, + 3.246704896102827, + 3.242986374418139, + 3.239160993372672, + 3.2352298903369876, + 3.2311947844644098, + 3.227057990140412, + 3.222822421854606, + 3.218491589581512, + 3.214069583938895, + 3.209561050621223, + 3.204971153876343, + 3.2003055290966023, + 3.195570224919974, + 3.1907716355687565, + 3.185916424477614, + 3.1810114405627146, + 3.176063628744403, + 3.1710799365422058, + 3.1660672187022643, + 3.1610321418847236, + 3.155981091428758, + 3.1509200821259715, + 3.145854674774099, + 3.140789900061202, + 3.1357301910580246, + 3.130679325287935, + 3.1256403770153307, + 3.120615680061021, + 3.1156068011316647, + 3.110614523353324, + 3.105638839437114, + 3.1006789536860233, + 3.0957332918802307, + 3.090799517956329, + 3.085874556322205, + 3.0809546186207895, + 3.076035233767983, + 3.0711112801359244, + 3.06617701882649, + 3.061226127073741, + 3.056251730921822, + 3.051246436440277, + 3.0462023588562612, + 3.041111149098527, + 3.035964017357659, + 3.0307517533678836, + 3.025464743206662, + 3.0200929824875216, + 3.0146260858892946, + 3.0090532930207265, + 3.0033634706642856, + 2.9975451114773475, + 2.9915863292539195, + 2.985474850866622, + 2.9791980050182136, + 2.9727427079349993, + 2.966095446132939, + 2.9592422563810845, + 2.952168702977978, + 2.9448598524448917, + 2.937300245726268, + 2.9294738679728756, + 2.92136411596758, + 2.9129537632373315, + 2.9042249228786146, + 2.895159008107192, + 2.8857366905264774, + 2.8759378560928313, + 2.8657415587400674, + 2.8551259716095316, + 2.8440683358167096, + 2.8325449066694137, + 2.820530897237168, + 2.808000419155303, + 2.794926420531019, + 2.781280620801645, + 2.7670334423774694, + 2.752153938882235, + 2.7366097197836883, + 2.7203668711836975, + 2.7033898725116132, + 2.68564150883573, + 2.6670827784741347, + 2.64767279554727, + 2.627368687068769, + 2.60612548411602, + 2.5838960065557774, + 2.547832201997476, + 2.570061679557719, + 2.591304882510468, + 2.6116089909889686, + 2.6310189739158334, + 2.6495777042774287, + 2.667326067953312, + 2.684303066625396, + 2.700545915225387, + 2.7160901343239336, + 2.730969637819168, + 2.7452168162433437, + 2.758862615972718, + 2.771936614597002, + 2.7844670926788666, + 2.7964811021111124, + 2.8080045312584083, + 2.8190621670512304, + 2.829677754181766, + 2.83987405153453, + 2.849672885968176, + 2.8590952035488906, + 2.8681611183203133, + 2.87688995867903, + 2.885300311409279, + 2.8934100634145743, + 2.9012364411679665, + 2.9087960478865904, + 2.9161048984196767, + 2.9231784518227832, + 2.930031641574638, + 2.936678903376698, + 2.9431342004599124, + 2.9494110463083207, + 2.9555225246956183, + 2.961481306919046, + 2.9672996661059843, + 2.9729894884624253, + 2.9785622813309933, + 2.9840291779292203, + 2.9894009386483607, + 2.9946879488095823, + 2.999900212799358, + 3.005047344540226, + 3.01013855429796, + 3.015182631881976, + 3.0201879263635205, + 3.0251623225154396, + 3.0301132142681886, + 3.035047475577623, + 3.0399714292096816, + 3.044890814062488, + 3.049810751763904, + 3.0547357133980277, + 3.0596694873219294, + 3.064615149127722, + 3.0695750348788127, + 3.074550718795023, + 3.0795429965733634, + 3.08455187550272, + 3.0895765724570294, + 3.0946155207296338, + 3.0996663864997234, + 3.104726095502901, + 3.109790870215798, + 3.1148562775676703, + 3.1199172868704568, + 3.1249683373264223, + 3.130003414143963, + 3.1350161319839045, + 3.1399998241861016, + 3.1449476360044133, + 3.149852619919313, + 3.1547078310104553, + 3.1595064203616725, + 3.164241724538301, + 3.1689073493180415, + 3.173497246062922, + 3.1780057793805936, + 3.1824277850232106, + 3.186758617296305, + 3.190994185582111, + 3.1951309799061085, + 3.1991660857786863, + 3.203097188814371, + 3.2069225698598376, + 3.2106410915445256, + 3.2142521773006045, + 3.217755783983155, + 3.2211523692594186, + 3.224442854932053, + 3.22762858732188, + 3.2307112957668034, + 3.233693050202662, + 3.2365762186855083, + 3.239363425599186, + 3.242057511173334, + 3.2446614928188393, + 3.247178528675227, + 3.2496118836593753, + 3.2519648982099, + 3.2542409598375306, + 3.2564434775194355, + 3.258575858914716, + 3.260641490328786, + 3.262643719315256, + 3.2645858397745737, + 3.2664710793875784, + 3.2683025892085458, + 3.2700834352348216, + 3.27181659176816, + 3.273504936384485, + 3.275151246334463, + 3.2767581962046997, + 3.2783283566790247, + 3.2798641942500795, + 3.281368071742787, + 3.282842249523087, + 3.284288887276922, + 3.285710046256162, + 3.2871076918989255, + 3.2884836967424413, + 3.2898398435561655, + 3.2911778286321787, + 3.29249926517806, + 3.2938056867650447, + 3.295098550791259, + 3.2963792419258557, + 3.2976490755053858, + 3.2989093008587935, + 3.3001611045414263, + 3.301405613462548, + 3.3026438978938435, + 3.3038769743495497, + 3.305105808330999, + 3.306331316930774, + 3.3075543712933073, + 3.3087757989303728, + 3.309996385891132, + 3.3112168787876888, + 3.312437986677803, + 3.313660382807301, + 3.3148847062153823, + 3.31611156320648, + 3.3173415286928107, + 3.318575147412086, + 3.3198129350252064, + 3.3210553790989112, + 3.322302939978705, + 3.3235560515573837, + 3.324815121944766, + 3.3260805340443493, + 3.327352646042603, + 3.32863179181689, + 3.3299182812679997, + 3.3312124005833303, + 3.332514412436958, + 3.333824556132822, + 3.335143047697337, + 3.3364700799278557, + 3.337805822403407, + 3.339150421464218, + 3.340504000166602, + 3.34186665821972, + 3.343238471910815, + 3.344619494025537, + 3.3460097537698026, + 3.347409256699695, + 3.34881798466586, + 3.35023589577847, + 3.351662924399109, + 3.353098981165247, + 3.354543953053161, + 3.3559977034845954, + 3.3574600724822634, + 3.358930876878991, + 3.3604099105847087, + 3.361896944915231, + 3.3633917289862105, + 3.364893990175114, + 3.366403434653491, + 3.3679197479912997, + 3.3694425958342937, + 3.3709716246549624, + 3.3725064625767227, + 3.3740467202705022, + 3.3755919919221116, + 3.3771418562680364, + 3.3786958776968548, + 3.380253607412531, + 3.3818145846554644, + 3.3833783379763727, + 3.384944386557716, + 3.3865122415767104, + 3.3880814076036234, + 3.3896513840285953, + 3.3912216665099333, + 3.3927917484365735, + 3.3943611223971364, + 3.395929281648016, + 3.3974957215728203, + 3.399059941125541, + 3.400621444249963, + 3.402179741267983, + 3.403734350229805, + 3.4052847982193013, + 3.4068306226081884, + 3.4083713722530975, + 3.4099066086302696, + 3.411435906902872, + 3.412958856916752, + 3.414475064120966, + 3.415984150409918, + 3.417485754884847, + 3.418979534532707, + 3.4204651648214153, + 3.4219423402108915, + 3.4234107745799656, + 3.4248702015697816, + 3.4263203748449595, + 3.42776106827426, + 3.429192076032831, + 3.4306132126288764, + 3.432024312857604, + 3.433425231685999, + 3.4348158440719945, + 3.4361960447220357, + 3.437565747791229, + 3.4389248865302995, + 3.440273412883811, + 3.4416112970442043, + 3.442938526966067, + 3.4442551078452057, + 3.4455610615669805, + 3.4468564261282477, + 3.448141255037234, + 3.449415616695406, + 3.450679593765381, + 3.451933282528622, + 3.4531767922365586, + 3.454410244458458, + 3.4556337724293327, + 3.456847520400686, + 3.458051642996952, + 3.459246304580015, + 3.460431678624083, + 3.461607947102963, + 3.462775299891522, + 3.4639339341828372, + 3.4650840539225274, + 3.4662258692612715, + 3.4673595960265793, + 3.468485455214538, + 3.4696036725020987, + 3.4707144777804, + 3.4718181047093464, + 3.4729147902935367, + 3.474004774479689, + 3.4750882997751913, + 3.4761656108878114, + 3.4772369543860036, + 3.4783025783795263, + 3.4793627322197924, + 3.480417666219427, + 3.481467631390343, + 3.4825128791996764, + 3.4835536613428073, + 3.484590229532763, + 3.485622835305089, + 3.486651729837439, + 3.4876771637830197, + 3.488699387116933, + 3.4897186489947116, + 3.490735197621949, + 3.4917492801343473, + 3.492761142487142, + 3.493771029353103, + 3.4947791840282347, + 3.495785848344326, + 3.496791262587417, + 3.4977956654215054, + 3.498799293816539, + 3.4998023829799445, + 3.5008051662909607, + 3.5018078752369135, + 3.5028107393508243, + 3.5038139861495137, + 3.504817841071615, + 3.5058225274147308, + 3.5068282662712296, + 3.5078352764618894, + 3.508843774466969, + 3.5098539743540345, + 3.5108660877021034, + 3.511880323521483, + 3.512896888168978, + 3.513915985257921, + 3.514937815562671, + 3.5159625769172065, + 3.5169904641074807, + 3.51802166875721, + 3.519056379206902, + 3.520094780385855, + 3.521137053676973, + 3.522183376774264, + 3.523233923532925, + 3.524288863812039, + 3.52534836330973, + 3.5264125833911084, + 3.527481680908853, + 3.528555808016623, + 3.52963511197586, + 3.5307197349558264, + 3.53180981382745, + 3.5329054799513577, + 3.534006858960482, + 3.53511407053791, + 3.5362272281903997, + 3.537346439018343, + 3.53847180348281, + 3.5396034151705296, + 3.54074136055757, + 3.5418857187727033, + 3.543036561361348, + 3.544193952051144, + 3.5453579465203076, + 3.546528592169756, + 3.547705927900289, + 3.548889983896067, + 3.5500807814155872, + 3.5512783325914685, + 3.5524826402404464, + 3.553693697684853, + 3.5549114885870137, + 3.5561359867978624, + 3.5573671562212654, + 3.5586049506952753, + 3.559849313891826, + 3.561100179236068, + 3.5623574698466465, + 3.56362109849822, + 3.564890967607365, + 3.566166969242902, + 3.5674489851620277, + 3.5687368868720983, + 3.570030535720703, + 3.571329783013388, + 3.572634470159898, + 3.5739444288495608, + 3.575259481256482, + 3.576579440274224, + 3.5779041097803224, + 3.5792332849305595, + 3.580566752482717, + 3.5819042911494305, + 3.583245671979588, + 3.5845906587675915, + 3.5859390084895826, + 3.5872904717656695, + 3.5886447933469108, + 3.590001712625817, + 3.591360964168886, + 3.592722278269654, + 3.5940853815204563, + 3.595449997401258, + 3.5968158468835476, + 3.598182649047393, + 3.599550121709612, + 3.6009179820609356, + 3.6022859473101967, + 3.603653735333229, + 3.605021065324461, + 3.6063876584491625, + 3.6077532384940874, + 3.609117532514701, + 3.610480271476919, + 3.611841190891568, + 3.613200031439754, + 3.6145565395875576, + 3.6159104681885728, + 3.617261577072804, + 3.6186096336208937, + 3.619954413322484, + 3.621295700317993, + 3.622633287923015, + 3.6239669791349254, + 3.625296587121391, + 3.626621935690671, + 3.627942859743875, + 3.629259205709399, + 3.630570831960133, + 3.6318776092140954, + 3.6331794209194257, + 3.634476163624752, + 3.635767747336292, + 3.6370540958630055, + 3.638335147151477, + 3.6396108536121736, + 3.640881182439003, + 3.642146115924104, + 3.6434056517699807, + 3.6446598034010167, + 3.6459086002767473, + 3.647152088208946, + 3.648390329684828, + 3.649623404198584, + 3.6508514085933177, + 3.652074457415469, + 3.653292683283565, + 3.6545062372730097, + 3.6557152893182994, + 3.6569200286338663, + 3.6581206641541906, + 3.659317424993565, + 3.660510560925174, + 3.661700342878652, + 3.662887063454448, + 3.664071037452553, + 3.665252602412046, + 3.666432119156874, + 3.667609972342007, + 3.66878657099258, + 3.6699623490270854, + 3.6711377657538047, + 3.6723133063277857, + 3.673489482153246, + 3.67466683121411, + 3.67584591831298, + 3.677027335197157, + 3.678211700533976, + 3.6793996597340017, + 3.680591884561097, + 3.6817890725092894, + 3.6829919459050617, + 3.684201250693971, + 3.685417754867804, + 3.6866422464862434, + 3.687875531245686, + 3.6891184295470314, + 3.6903717730148, + 3.691636400421795, + 3.692913152976976, + 3.6942028689398616, + 3.695506377532331, + 3.69682449212932, + 3.6981580027229426, + 3.6995076676708263, + 3.70087420475887, + 3.702258281631137, + 3.703660505664843, + 3.705081413396711, + 3.706521459636684, + 3.7079810064363232, + 3.709460312110404, + 3.7109595205401207, + 3.712478651013475, + 3.714017588880728, + 3.7155760773188984, + 3.717153710506511, + 3.718749928507428, + 3.7203640141479504, + 3.721995092144408, + 3.723642130709356, + 3.725303945745969, + 3.726979207825591, + 3.728666451892338, + 3.730364089719997, + 3.732070424631614, + 3.733783668958059, + 3.735501963192874, + 3.737223396817099, + 3.738946030267923, + 3.7406679175542585, + 3.742387128996606, + 3.744101773563792, + 3.745810020295053, + 3.747510118331964, + 3.7492004151381058, + 3.7508793725528657, + 3.7525455804048167, + 3.754197767495392, + 3.7558348098516623, + 3.7574557362319014, + 3.7590597309477447, + 3.7606461341367776, + 3.762214439679107, + 3.7637642909978344, + 3.765295475016702, + 3.7668079145686457, + 3.768301659556619, + 3.769776877165512, + 3.7712338414114663, + 3.772672922295477, + 3.774094574802957, + 3.775499327962044, + 3.776887774142922, + 3.7782605587485834, + 3.7796183704173343, + 3.780961931827832, + 3.7822919911711232, + 3.783609314329917, + 3.78491467778474, + 3.786208862248751, + 3.787492647018464, + 3.788766805215374, + 3.7900320989387857, + 3.791289276027742, + 3.792539066908963, + 3.7937821819625537, + 3.795019309418432, + 3.796251113683032, + 3.797478234049512, + 3.7987012837461704, + 3.799920849280608, + 3.8011374900396735, + 3.802351738108236, + 3.803564098273114, + 3.804775048181402, + 3.80598503862568, + 3.8071944939314726, + 3.80840381242518, + 3.8096133669633097, + 3.810823505506343, + 3.812034551722835, + 3.813246805611262, + 3.8144605441292665, + 3.8156760218212407, + 3.8168934714371425, + 3.818113104536272, + 3.8193351120712618, + 3.8205596649484623, + 3.8217869145616223, + 3.823016993296889, + 3.8242500150073777, + 3.825486075456593, + 3.826725252730124, + 3.8279676076156512, + 3.829213183951528, + 3.830462008944673, + 3.8317140934585656, + 3.832969432272496, + 3.8342280043133106, + 3.835489772861054, + 3.836754685730118, + 3.838022675628242, + 3.8392936608777415, + 3.840567542580516, + 3.841844208067171, + 3.8431235297998496, + 3.844405365442799, + 3.845689557921143, + 3.8469759354697817, + 3.848264311674514, + 3.8495544855072703, + 3.8508462413576487, + 3.8521393490627007, + 3.853433563937127, + 3.854728626805912, + 3.8560242640415714, + 3.8573201876081207, + 3.858616095113888, + 3.859911669875392, + 3.861206580994413, + 3.862500483450486, + 3.8637930182110134, + 3.8650838123611817, + 3.866372479255987, + 3.867658622097311, + 3.868941824334184, + 3.870221658883719, + 3.871497686217325, + 3.8727694542455655, + 3.874036498649489, + 3.875298343249728, + 3.8765545004152666, + 3.877804471514268, + 3.8790477474088143, + 3.88028380899573, + 3.881512127795323, + 3.88273216659001, + 3.883943380114567, + 3.885145221509821, + 3.8863371275752217, + 3.8875185319171393, + 3.8886888665089256, + 3.889847561603172, + 3.890994037527379, + 3.892127712755594, + 3.8932480082720926, + 3.894354348362838, + 3.89544615913172, + 3.896522869850152, + 3.8975839143666873, + 3.898628736085378, + 3.899656780660822, + 3.900667499190392, + 3.9016603604893136, + 3.9026348455701583, + 3.903590449413216, + 3.904526682783461, + 3.9054430684689807, + 3.906339161330836, + 3.907214533183869, + 3.908068775488224, + 3.908901505434383, + 3.909712367915141, + 3.9105010375102864, + 3.9112671978945497, + 3.9120106240099575, + 3.912731089376157, + 3.913428402937845, + 3.914102414160437, + 3.914753014831758, + 3.9153801358942273, + 3.9159837487300218, + 3.916563911707503, + 3.917120697823556, + 3.9176542317158902, + 3.918164691003451, + 3.9186523075348862, + 3.919117362271172, + 3.919560204658769, + 3.919981244343148, + 3.920380936390509, + 3.9207597929026448, + 3.9211183837052017, + 3.921457360818299, + 3.92177741542042, + 3.922079300548454, + 3.922363822371016, + 3.9226318726321567, + 3.922884386127465, + 3.923122356437791, + 3.923346835220309, + 3.923558938881493, + 3.923759836437517, + 3.923950751806192, + 3.924132962912229, + 3.924307797091765, + 3.924476634385384, + 3.924640901847938, + 3.924802071518834, + 3.924961661116848, + 3.9251212285571215, + 3.925282370667978, + 3.925446719769298, + 3.925615941021445, + 3.925791728878821, + 3.925975807387664, + 3.9261699256694103, + 3.9263758561208912, + 3.926595389212638, + 3.926830331671712, + 3.9270825054405103, + 3.927353746412581, + 3.9276459029977455, + 3.9279608316553585, + 3.9283003959328022, + 3.9286664655365233, + 3.929060917933204, + 3.929485638114461, + 3.9299425196266617, + 3.9304334643431376, + 3.930960386125764, + 3.93152521203996, + 3.932129887686174, + 3.9327763813326904, + 3.933466691046835, + 3.934202853456543, + 3.934986952127243, + 3.935821132493508, + 3.936707604240632, + 3.93764866015394, + 3.9386466896630625, + 3.939704209933382, + 3.9408238990573152, + 3.942008606664556, + 3.943261399402392, + 3.944585546349999, + 3.9459846055007666, + 3.9474624684593262, + 3.9490234526115846, + 3.9506724448849457, + 3.952414810619014, + 3.954256571495504, + 3.9562043948390673, + 3.9582659518238943, + 3.960450130956135, + 3.962767248114219, + 3.965229597162239, + 3.967851127321925, + 3.970648343685331, + 3.973641055765328, + 3.976853434327231, + 3.980315172044512, + 3.984062866582742, + 3.988142897430352, + 3.992615779159438, + 3.997563366860096, + 4.003100850680144, + 4.009404201640048, + 4.016793318345548, + 4.026064687568873, + 4.04208975544602 + ] + }, + { + "mode": "markers", + "name": "Initial", + "type": "scatter", + "x": [ + 0, + 5, + 10, + 15, + 20, + 25, + 30, + 35, + 40, + 45, + 50, + 55, + 60, + 65, + 70, + 75, + 80, + 85, + 90, + 95, + 100, + 105, + 110, + 115, + 120, + 125, + 130, + 135, + 140, + 145, + 150, + 155, + 160, + 165, + 170, + 175, + 180, + 185, + 190, + 195, + 200, + 205, + 210, + 215, + 220, + 225, + 230, + 235, + 240, + 245, + 250, + 255, + 260, + 265, + 270, + 275, + 280, + 285, + 290, + 295, + 300, + 305, + 310, + 315, + 320, + 325, + 330, + 335, + 340, + 345, + 350, + 355, + 360, + 365, + 370, + 375, + 380, + 385, + 390, + 395, + 400, + 405, + 410, + 415, + 420, + 425, + 430, + 435, + 440, + 445, + 450, + 455, + 460, + 465, + 470, + 475, + 480, + 485, + 490, + 495, + 500, + 505, + 510, + 515, + 520, + 525, + 530, + 535, + 540, + 545, + 550, + 555, + 560, + 565, + 570, + 575, + 580, + 585, + 590, + 595, + 600, + 605, + 610, + 615, + 620, + 625, + 630, + 635, + 640, + 645, + 650, + 655, + 660, + 665, + 670, + 675, + 680, + 685, + 690, + 695, + 700, + 705, + 710, + 715, + 720, + 725, + 730, + 735, + 740, + 745, + 750, + 755, + 760, + 765, + 770, + 775, + 780, + 785, + 790, + 795, + 800, + 805, + 810, + 815, + 820, + 825, + 830, + 835, + 840, + 845, + 850, + 855, + 860, + 865, + 870, + 875, + 880, + 885, + 890, + 895, + 900, + 905, + 910, + 915, + 920, + 925, + 930, + 935, + 940, + 945, + 950, + 955, + 960, + 965, + 970, + 975, + 980, + 985, + 990, + 995, + 1000, + 1005, + 1010, + 1015, + 1020, + 1025, + 1030, + 1035, + 1040, + 1045, + 1050, + 1055, + 1060, + 1065, + 1070, + 1075, + 1080, + 1085, + 1090, + 1095, + 1100, + 1105, + 1110, + 1115, + 1120, + 1125, + 1130, + 1135, + 1140, + 1145, + 1150, + 1155, + 1160, + 1165, + 1170, + 1175, + 1180, + 1185, + 1190, + 1195, + 1200, + 1205, + 1210, + 1215, + 1220, + 1225, + 1230, + 1235, + 1240, + 1245, + 1250, + 1255, + 1260, + 1265, + 1270, + 1275, + 1280, + 1285, + 1290, + 1295, + 1300, + 1305, + 1310, + 1315, + 1320, + 1325, + 1330, + 1335, + 1340, + 1345, + 1350, + 1355, + 1360, + 1365, + 1370, + 1375, + 1380, + 1385, + 1390, + 1395, + 1400, + 1405, + 1410, + 1415, + 1420, + 1425, + 1430, + 1435, + 1440, + 1445, + 1450, + 1455, + 1460, + 1465, + 1470, + 1475, + 1480, + 1485, + 1490, + 1495, + 1500, + 1505, + 1510, + 1515, + 1520, + 1525, + 1530, + 1535, + 1540, + 1545, + 1550, + 1555, + 1560, + 1565, + 1570, + 1575, + 1580, + 1585, + 1590, + 1595, + 1600, + 1605, + 1610, + 1615, + 1620, + 1625, + 1630, + 1635, + 1640, + 1645, + 1650, + 1655, + 1660, + 1665, + 1670, + 1675, + 1680, + 1685, + 1690, + 1695, + 1700, + 1705, + 1710, + 1715, + 1720, + 1725, + 1730, + 1735, + 1740, + 1745, + 1750, + 1755, + 1760, + 1765, + 1770, + 1775, + 1780, + 1785, + 1790, + 1795, + 1800, + 1805, + 1810, + 1815, + 1820, + 1825, + 1830, + 1835, + 1840, + 1845, + 1850, + 1855, + 1860, + 1865, + 1870, + 1875, + 1880, + 1885, + 1890, + 1895, + 1900, + 1905, + 1910, + 1915, + 1920, + 1925, + 1930, + 1935, + 1940, + 1945, + 1950, + 1955, + 1960, + 1965, + 1970, + 1975, + 1980, + 1985, + 1990, + 1995, + 2000, + 2005, + 2010, + 2015, + 2020, + 2025, + 2030, + 2035, + 2040, + 2045, + 2050, + 2055, + 2060, + 2065, + 2070, + 2075, + 2080, + 2085, + 2090, + 2095, + 2100, + 2105, + 2110, + 2115, + 2120, + 2125, + 2130, + 2135, + 2140, + 2145, + 2150, + 2155, + 2160, + 2165, + 2170, + 2175, + 2180, + 2185, + 2190, + 2195, + 2200, + 2205, + 2210, + 2215, + 2220, + 2225, + 2230, + 2235, + 2240, + 2245, + 2250, + 2255, + 2260, + 2265, + 2270, + 2275, + 2280, + 2285, + 2290, + 2295, + 2300, + 2305, + 2310, + 2315, + 2320, + 2325, + 2330, + 2335, + 2340, + 2345, + 2350, + 2355, + 2360, + 2365, + 2370, + 2375, + 2380, + 2385, + 2390, + 2395, + 2400, + 2405, + 2410, + 2415, + 2420, + 2425, + 2430, + 2435, + 2440, + 2445, + 2450, + 2455, + 2460, + 2465, + 2470, + 2475, + 2480, + 2485, + 2490, + 2495, + 2500, + 2505, + 2510, + 2515, + 2520, + 2525, + 2530, + 2535, + 2540, + 2545, + 2550, + 2555, + 2560, + 2565, + 2570, + 2575, + 2580, + 2585, + 2590, + 2595, + 2600, + 2605, + 2610, + 2615, + 2620, + 2625, + 2630, + 2635, + 2640, + 2645, + 2650, + 2655, + 2660, + 2665, + 2670, + 2675, + 2680, + 2685, + 2690, + 2695, + 2700, + 2705, + 2710, + 2715, + 2720, + 2725, + 2730, + 2735, + 2740, + 2745, + 2750, + 2755, + 2760, + 2765, + 2770, + 2775, + 2780, + 2785, + 2790, + 2795, + 2800, + 2805, + 2810, + 2815, + 2820, + 2825, + 2830, + 2835, + 2840, + 2845, + 2850, + 2855, + 2860, + 2865, + 2870, + 2875, + 2880, + 2885, + 2890, + 2895, + 2900, + 2905, + 2910, + 2915, + 2920, + 2925, + 2930, + 2935, + 2940, + 2945, + 2950, + 2955, + 2960, + 2965, + 2970, + 2975, + 2980, + 2985, + 2990, + 2995, + 3000, + 3005, + 3010, + 3015, + 3020, + 3025, + 3030, + 3035, + 3040, + 3045, + 3050, + 3055, + 3060, + 3065, + 3070, + 3075, + 3080, + 3085, + 3090, + 3095, + 3100, + 3105, + 3110, + 3115, + 3120, + 3125, + 3130, + 3135, + 3140, + 3145, + 3150, + 3155, + 3160, + 3165, + 3170, + 3175, + 3180, + 3185, + 3190, + 3195, + 3200, + 3205, + 3210, + 3215, + 3220, + 3225, + 3230, + 3235, + 3240, + 3245, + 3250, + 3255, + 3260, + 3265, + 3270, + 3275, + 3280, + 3285, + 3290, + 3295, + 3300, + 3305, + 3310, + 3315, + 3320, + 3325, + 3330, + 3335, + 3340, + 3345, + 3350, + 3355, + 3360, + 3365, + 3370, + 3375, + 3380, + 3385, + 3390, + 3395, + 3400, + 3405, + 3410, + 3415, + 3420, + 3425, + 3430, + 3435, + 3440, + 3445, + 3450, + 3455, + 3460, + 3465, + 3470, + 3475, + 3480, + 3485, + 3490, + 3495, + 3500, + 3505, + 3510, + 3510.522507468857 + ], + "y": [ + 4.051091197755109, + 4.026475108438385, + 4.008477230182524, + 3.994743960024916, + 3.9838543280120633, + 3.9749913244469983, + 3.9676532382673617, + 3.9615083809432377, + 3.9563232224458926, + 3.951925843627623, + 3.948184747176896, + 3.944995727609847, + 3.942273785922641, + 3.939948635878476, + 3.937961382079423, + 3.936261890108397, + 3.934807038436865, + 3.933559012260305, + 3.9324851327226193, + 3.931556777388467, + 3.9307488615214665, + 3.930039338828893, + 3.9294086705095816, + 3.9288396300260153, + 3.928316953040113, + 3.927827287088335, + 3.927358907257412, + 3.926901554605048, + 3.926446275028168, + 3.925985271887887, + 3.925511816401267, + 3.925020127680449, + 3.924505306614829, + 3.923963235167639, + 3.92339049602856, + 3.922784301368551, + 3.92214242468456, + 3.921463149377828, + 3.920745212041869, + 3.919987756314304, + 3.9191902863636656, + 3.9183526240114017, + 3.91747487089897, + 3.9165573733152192, + 3.915600692297605, + 3.914605573809794, + 3.913572923313997, + 3.9125037831662377, + 3.911399310270373, + 3.9102607556047166, + 3.9090894459581422, + 3.907886767570613, + 3.9066541544107505, + 3.9053930749232113, + 3.9041050217496687, + 3.902791498765284, + 3.9014540120117593, + 3.900094062228471, + 3.898713139278162, + 3.897312716992423, + 3.895894245743106, + 3.894459148584555, + 3.8930088156677343, + 3.891544603279242, + 3.890067830266237, + 3.8885797757484504, + 3.8870816803782287, + 3.88557474089635, + 3.884060108999762, + 3.8825388980826334, + 3.881012166605889, + 3.8794809390568865, + 3.877946178699285, + 3.876408816563815, + 3.874869745029061, + 3.873329814907405, + 3.871789812202043, + 3.8702504832112647, + 3.8687125655143606, + 3.8671767461867, + 3.865643662651416, + 3.8641138779356594, + 3.862587984105185, + 3.861066515208374, + 3.859549972965006, + 3.858038757021709, + 3.856533326009225, + 3.855034079108866, + 3.8535413880321694, + 3.8520555988992906, + 3.85057703381059, + 3.849105979973825, + 3.8476426021317303, + 3.8461872023175623, + 3.844739998262977, + 3.84330118798268, + 3.84187095100722, + 3.8404494495742183, + 3.839036803109904, + 3.837633047858334, + 3.83623833876022, + 3.8348527571203497, + 3.833476369163409, + 3.8321092268344943, + 3.830751368565041, + 3.829402806176272, + 3.8280635288487503, + 3.826733548590174, + 3.825412849161585, + 3.824101403107537, + 3.822799172278868, + 3.821506108327485, + 3.820222167080443, + 3.8189472934050097, + 3.817681406105694, + 3.8164244260533033, + 3.8151762667582303, + 3.813936834750651, + 3.8127060299372175, + 3.8114837495043346, + 3.81027054905092, + 3.8090652828279663, + 3.807868089955092, + 3.806678856713983, + 3.805497534088226, + 3.804323997614932, + 3.803158151099869, + 3.801999650606534, + 3.8008484387023658, + 3.7997044962828057, + 3.798567705214392, + 3.797437943275455, + 3.796315084344113, + 3.7951989985711463, + 3.794088996867595, + 3.7929853416497026, + 3.791887908309829, + 3.7907965552699583, + 3.78971113776844, + 3.788631507942738, + 3.7875573787766825, + 3.786488252006753, + 3.785424306903648, + 3.784365385273782, + 3.783311325912911, + 3.782261964601563, + 3.7812171340859497, + 3.780176392493644, + 3.779139509198147, + 3.778106504048643, + 3.777077198194828, + 3.77605140931841, + 3.775028951519508, + 3.774009635186179, + 3.7729928860143462, + 3.7719787021371074, + 3.770966949011369, + 3.769957421066427, + 3.7689499079884023, + 3.7679441944751497, + 3.7669400162932023, + 3.765936810120639, + 3.764934619510436, + 3.7639332065540065, + 3.762932326696258, + 3.761931728350295, + 3.760931152486282, + 3.759930195333132, + 3.758928452229471, + 3.757925811117531, + 3.7569219784313623, + 3.7559166506091968, + 3.7549095135169104, + 3.7539002418439975, + 3.752888281796543, + 3.7518733326775338, + 3.750855114448382, + 3.7498332501356177, + 3.7488073481150743, + 3.747777001374497, + 3.746741786765226, + 3.745700977649681, + 3.7446543206509846, + 3.743601344306701, + 3.742541552814826, + 3.741474430497341, + 3.740399441150352, + 3.7393159633105344, + 3.738223260991708, + 3.737120883455387, + 3.7360082066211655, + 3.734884583843864, + 3.7337493459344744, + 3.732601801386736, + 3.7314411097545257, + 3.730266510446479, + 3.729077336385026, + 3.7278728136012673, + 3.7266521512996142, + 3.725414544153301, + 3.7241591751054663, + 3.722885033362144, + 3.7215913832968206, + 3.720277424876117, + 3.7189423329676337, + 3.7175852938632454, + 3.716205512017186, + 3.7148021941032567, + 3.713374434277096, + 3.711921675709079, + 3.710443276687608, + 3.708938658096601, + 3.7074073141188975, + 3.705848823306803, + 3.704262785403982, + 3.7026489130296993, + 3.701007194809328, + 3.699337657639283, + 3.69764046691591, + 3.69591593538358, + 3.69416453064445, + 3.6923867580814673, + 3.690583438783865, + 3.688755576600643, + 3.686904295725777, + 3.68503088266543, + 3.6831367801549986, + 3.681223578288515, + 3.679292844367936, + 3.677346528980446, + 3.675386581846091, + 3.673415036909972, + 3.6714339936213336, + 3.6694455951366183, + 3.667452005719403, + 3.665455387859761, + 3.6634578796457022, + 3.661461572908639, + 3.659468493270011, + 3.6574808301781303, + 3.655500141026384, + 3.653528020671549, + 3.651566090477876, + 3.64961591970504, + 3.64767874777366, + 3.645755738233628, + 3.643847926633869, + 3.641956175838086, + 3.6400812044743103, + 3.6382235792075512, + 3.636383729905451, + 3.63456201156556, + 3.632758638178245, + 3.630973719334009, + 3.6292072706146543, + 3.62745922416492, + 3.62572943293978, + 3.624017597023729, + 3.6223235370089006, + 3.620646953545162, + 3.618987512818841, + 3.6173448546654017, + 3.615718599959673, + 3.614108357261962, + 3.6125136404784928, + 3.6109340876597806, + 3.609369325838584, + 3.6078189662453033, + 3.606282630270269, + 3.604759952019108, + 3.603250580329514, + 3.6017541301979135, + 3.6002702598964644, + 3.598798717886237, + 3.5973392233627743, + 3.595891514048498, + 3.59445534601216, + 3.5930304932765877, + 3.5916167256893132, + 3.59021375777888, + 3.588821495027339, + 3.5874397758059207, + 3.5860684525048763, + 3.584707390518293, + 3.5833564672043288, + 3.5820155708364867, + 3.5806845995590013, + 3.579363460357595, + 3.5780520680548618, + 3.5767503443379676, + 3.5754582168247135, + 3.5741756181729234, + 3.5729024852367215, + 3.5716387582723956, + 3.5703843786717067, + 3.569139197397881, + 3.567903239965505, + 3.5666764526754458, + 3.5654587820698236, + 3.564250174448393, + 3.5630505754340014, + 3.5618599295855256, + 3.5606781800562737, + 3.5595052682959345, + 3.558341133793797, + 3.5571857138610103, + 3.556038943449582, + 3.5549007550057468, + 3.5537710783553624, + 3.552649840619015, + 3.551536966154549, + 3.5504323653972203, + 3.549335964261777, + 3.5482476804307517, + 3.5471674270219764, + 3.5460951144407016, + 3.5450306504415687, + 3.5439739402037516, + 3.542924886417629, + 3.54188338938139, + 3.540849347106075, + 3.539822655427651, + 3.5388032081248264, + 3.537790897041368, + 3.53678561221188, + 3.535787241989854, + 3.534795673177249, + 3.533810807152796, + 3.532832522743972, + 3.5318606962228953, + 3.5308952096451365, + 3.529935944152046, + 3.5289827800934086, + 3.5280355971470025, + 3.5270942744345337, + 3.5261586906337223, + 3.5252287240861824, + 3.5243042529008446, + 3.5233851550527304, + 3.522471308476922, + 3.5215625911575374, + 3.5206588812116895, + 3.5197600569683063, + 3.518866008856703, + 3.5179766183813097, + 3.5170917550793694, + 3.5162112990482166, + 3.515335130909995, + 3.514463131859937, + 3.513595183709622, + 3.512731168925286, + 3.5118709706613815, + 3.511014472789345, + 3.5101615599217624, + 3.5093121174320796, + 3.5084660314698612, + 3.507623188971874, + 3.506783477668947, + 3.5059468180895785, + 3.505113070808307, + 3.5042821130253867, + 3.503453839714827, + 3.502628155560913, + 3.5018049461217235, + 3.5009841054208373, + 3.500165530277254, + 3.499349115100144, + 3.4985347554550756, + 3.4977223480154103, + 3.496911790045252, + 3.4961029793646645, + 3.4952958143124095, + 3.494490183046301, + 3.493685963193977, + 3.4928830832865994, + 3.492081443342534, + 3.491280943677112, + 3.4904814848525296, + 3.4896829676259076, + 3.488885292895574, + 3.488088361645684, + 3.487292074889229, + 3.486496333609527, + 3.4857010387002414, + 3.484906090904042, + 3.484111390749972, + 3.483316838489538, + 3.4825223340317217, + 3.481727776876827, + 3.4809330660494093, + 3.4801381000301843, + 3.4793427766871985, + 3.4785469932061286, + 3.4777506460199876, + 3.4769535985941307, + 3.4761557414408126, + 3.4753569991425586, + 3.474557264427684, + 3.4737564289118295, + 3.4729543830256997, + 3.47215101594281, + 3.47134621550744, + 3.4705398681629087, + 3.4697318588802983, + 3.4689220710878637, + 3.4681103866012, + 3.4672966855544183, + 3.46648084633252, + 3.465662745505131, + 3.464842257761884, + 3.4640192558496175, + 3.463193610511731, + 3.4623651904298556, + 3.4615338621682414, + 3.4606994901210815, + 3.4598619364631267, + 3.459021017770725, + 3.458176615144849, + 3.4573285985856432, + 3.4564768209831165, + 3.4556211328223383, + 3.4547613821617738, + 3.453897414617772, + 3.4530290733556592, + 3.4521561990879936, + 3.451278630080486, + 3.4503962021661847, + 3.4495087487685057, + 3.448616100933651, + 3.447718087373124, + 3.446814534516931, + 3.445905266578167, + 3.4449901056296217, + 3.4440688716931342, + 3.443141382842379, + 3.4422074553197946, + 3.4412669036684034, + 3.440319540879151, + 3.439365132738992, + 3.4384035302951923, + 3.437434544014805, + 3.436457982493587, + 3.4354736537702832, + 3.434481365582195, + 3.433480925640874, + 3.432472141928711, + 3.4314548230167956, + 3.430428778404429, + 3.429393818880761, + 3.4283497569087378, + 3.4272964070315592, + 3.426233586301763, + 3.425161114732811, + 3.424078815773117, + 3.4229865168021365, + 3.421884049648113, + 3.4207712511268893, + 3.4196479636009283, + 3.418514035557666, + 3.4173693168017603, + 3.4162136537020302, + 3.4150469354587796, + 3.413869041184869, + 3.4126798592517784, + 3.411479287952255, + 3.410267236169707, + 3.409043624051654, + 3.4078083836842747, + 3.406561459764918, + 3.405302810269166, + 3.4040324071088595, + 3.402750236777333, + 3.4014563009777765, + 3.400150617230701, + 3.398833219456096, + 3.3975041585259453, + 3.396163502782571, + 3.39481133780715, + 3.393447766242977, + 3.392072909628145, + 3.390686911461197, + 3.3892899365425797, + 3.3878821635541785, + 3.386463793818072, + 3.3850350485011735, + 3.383596168626729, + 3.3821474150101456, + 3.380689068116281, + 3.3792214278358483, + 3.377744812537824, + 3.3762595581598513, + 3.3747660228482967, + 3.373264580287283, + 3.3717556212071345, + 3.370239552654385, + 3.3687167971794914, + 3.3671877919443203, + 3.36565298775243, + 3.3641128480057008, + 3.362567847591306, + 3.361018471703969, + 3.359465214608614, + 3.3579085783493814, + 3.3563490714111524, + 3.354787207340347, + 3.35322350333206, + 3.351658478790755, + 3.3500926538722116, + 3.3485265480142403, + 3.3469606741490447, + 3.345395535521542, + 3.3438316543043887, + 3.3422695324177267, + 3.3407096638191893, + 3.3391525331271783, + 3.3375986142843015, + 3.336048369266992, + 3.3345022468468613, + 3.332960681408968, + 3.331424091831286, + 3.3298928804292927, + 3.3283674319687915, + 3.3268481127495177, + 3.325335269761191, + 3.3238292299133545, + 3.3223302993391575, + 3.32083876277302, + 3.319354883001166, + 3.317878900383496, + 3.3164110324446137, + 3.314951473531292, + 3.3135003945330204, + 3.3120579426619097, + 3.310624241287626, + 3.3091993898227643, + 3.307783463653505, + 3.306376514110241, + 3.3049785684724733, + 3.3035896300020595, + 3.302209677998705, + 3.300838667871357, + 3.29947653121914, + 3.298123175915253, + 3.2967784861872893, + 3.2954423226873084, + 3.2941145225450295, + 3.2927948993974967, + 3.291483243388616, + 3.290179300844195, + 3.288882831898938, + 3.2875935578187336, + 3.2863111739368027, + 3.285035351410079, + 3.2837657369016027, + 3.2825019521880026, + 3.2812435936859425, + 3.2799902318915923, + 3.278741410727174, + 3.2774966467888813, + 3.276255428490454, + 3.275017215096959, + 3.2737814356433996, + 3.272547487733064, + 3.2713147362106936, + 3.2700825117058967, + 3.2688501090425017, + 3.2676167855101235, + 3.2663817589945596, + 3.265144205964376, + 3.2639032593117134, + 3.262658006046384, + 3.261407484843172, + 3.2601506834438103, + 3.2588865359163943, + 3.257613919776889, + 3.2563316529794983, + 3.255038490784942, + 3.2537331225187915, + 3.252414168234902, + 3.2510801753030725, + 3.2497296149441044, + 3.248360878740259, + 3.246972275154833, + 3.2455620261003344, + 3.2441282636018673, + 3.242669026609765, + 3.241182256769195, + 3.239665797926234, + 3.2381173938485084, + 3.2365346846230834, + 3.234915206080872, + 3.2332563891572264, + 3.2315555600522883, + 3.2298099413292873, + 3.228016654100678, + 3.2261727214628637, + 3.2242750739602313, + 3.2223205542316817, + 3.2203059267871152, + 3.2182278883766293, + 3.2160830802221567, + 3.2138681010291426, + 3.2115795261840336, + 3.2092139268908633, + 3.2067678927734495, + 3.2042380573190137, + 3.2016211261933103, + 3.198913908397262, + 3.196113350163349, + 3.1932165714062855, + 3.190220904447609, + 3.187123934628521, + 3.183923542311989, + 3.1806179456561554, + 3.177205743420512, + 3.1736859569486833, + 3.170058070362576, + 3.166322064855059, + 3.1624784608556338, + 3.1585283380342344, + 3.1544733594039984, + 3.150315785936714, + 3.146058482389281, + 3.141704913632553, + 3.1372591307225486, + 3.132725746181483, + 3.128109898226134, + 3.1234172039855896, + 3.1186537020771947, + 3.11382578524601, + 3.108940124102552, + 3.104003583300486, + 3.0990231317637873, + 3.0940057487874557, + 3.088958327984548, + 3.0838875811272164, + 3.078799943925715, + 3.0737014857074643, + 3.068597824802668, + 3.063494051222941, + 3.0583946579467725, + 3.0533034818154463, + 3.0482236547113013, + 3.0431575653533103, + 3.0381068317181903, + 3.033072283792399, + 3.0280539560924873, + 3.0230510891662874, + 3.018062139110873, + 3.01308479401631, + 3.0081159961670028, + 3.0031519688011254, + 2.99818824623844, + 2.993219706231952, + 2.988240603471948, + 2.9832446032652142, + 2.978224814520922, + 2.9731738212912053, + 2.968083712233682, + 2.96294610748013, + 2.9577521825067516, + 2.9524926887042353, + 2.9471579704381172, + 2.9417379784707047, + 2.9362222796848605, + 2.9306000631068825, + 2.9248601422714695, + 2.918990954006901, + 2.9129805500974904, + 2.90681659862296, + 2.900486366186482, + 2.8939767029821564, + 2.887274026805922, + 2.880364302902069, + 2.8732330210943173, + 2.865865170306786, + 2.8582452105663556, + 2.8503570425630795, + 2.842183974829324, + 2.833708688581958, + 2.8249132002552466, + 2.8157788217355195, + 2.806286118291993, + 2.7964148641817967, + 2.7861439958911416, + 2.775451562958369, + 2.7643146763089894, + 2.752709454016846, + 2.7406109643896723, + 2.7279931662612316, + 2.714828846355646, + 2.7010895535721993, + 2.6867455300209717, + 2.671765638619922, + 2.656117287043286, + 2.639766347787585, + 2.6226770740957828, + 2.6048120114503917, + 2.586131904312632, + 2.5665955977449313, + 2.5461599335074667, + 2.524779640163713, + 2.5024072166621973, + 2.500010012082775 + ] + }, + { + "line": { + "width": 4 + }, + "mode": "lines", + "name": "Optimised", + "type": "scatter", + "x": [ + 0, + 5, + 10, + 15, + 20, + 25, + 30, + 35, + 40, + 45, + 50, + 55, + 60, + 65, + 70, + 75, + 80, + 85, + 90, + 95, + 100, + 105, + 110, + 115, + 120, + 125, + 130, + 135, + 140, + 145, + 150, + 155, + 160, + 165, + 170, + 175, + 180, + 185, + 190, + 195, + 200, + 205, + 210, + 215, + 220, + 225, + 230, + 235, + 240, + 245, + 250, + 255, + 260, + 265, + 270, + 275, + 280, + 285, + 290, + 295, + 300, + 305, + 310, + 315, + 320, + 325, + 330, + 335, + 340, + 345, + 350, + 355, + 360, + 365, + 370, + 375, + 380, + 385, + 390, + 395, + 400, + 405, + 410, + 415, + 420, + 425, + 430, + 435, + 440, + 445, + 450, + 455, + 460, + 465, + 470, + 475, + 480, + 485, + 490, + 495, + 500, + 505, + 510, + 515, + 520, + 525, + 530, + 535, + 540, + 545, + 550, + 555, + 560, + 565, + 570, + 575, + 580, + 585, + 590, + 595, + 600, + 605, + 610, + 615, + 620, + 625, + 630, + 635, + 640, + 645, + 650, + 655, + 660, + 665, + 670, + 675, + 680, + 685, + 690, + 695, + 700, + 705, + 710, + 715, + 720, + 725, + 730, + 735, + 740, + 745, + 750, + 755, + 760, + 765, + 770, + 775, + 780, + 785, + 790, + 795, + 800, + 805, + 810, + 815, + 820, + 825, + 830, + 835, + 840, + 845, + 850, + 855, + 860, + 865, + 870, + 875, + 880, + 885, + 890, + 895, + 900, + 905, + 910, + 915, + 920, + 925, + 930, + 935, + 940, + 945, + 950, + 955, + 960, + 965, + 970, + 975, + 980, + 985, + 990, + 995, + 1000, + 1005, + 1010, + 1015, + 1020, + 1025, + 1030, + 1035, + 1040, + 1045, + 1050, + 1055, + 1060, + 1065, + 1070, + 1075, + 1080, + 1085, + 1090, + 1095, + 1100, + 1105, + 1110, + 1115, + 1120, + 1125, + 1130, + 1135, + 1140, + 1145, + 1150, + 1155, + 1160, + 1165, + 1170, + 1175, + 1180, + 1185, + 1190, + 1195, + 1200, + 1205, + 1210, + 1215, + 1220, + 1225, + 1230, + 1235, + 1240, + 1245, + 1250, + 1255, + 1260, + 1265, + 1270, + 1275, + 1280, + 1285, + 1290, + 1295, + 1300, + 1305, + 1310, + 1315, + 1320, + 1325, + 1330, + 1335, + 1340, + 1345, + 1350, + 1355, + 1360, + 1365, + 1370, + 1375, + 1380, + 1385, + 1390, + 1395, + 1400, + 1405, + 1410, + 1415, + 1420, + 1425, + 1430, + 1435, + 1440, + 1445, + 1450, + 1455, + 1460, + 1465, + 1470, + 1475, + 1480, + 1485, + 1490, + 1495, + 1500, + 1505, + 1510, + 1515, + 1520, + 1525, + 1530, + 1535, + 1540, + 1545, + 1550, + 1555, + 1560, + 1565, + 1570, + 1575, + 1580, + 1585, + 1590, + 1595, + 1600, + 1605, + 1610, + 1615, + 1620, + 1625, + 1630, + 1635, + 1640, + 1645, + 1650, + 1655, + 1660, + 1665, + 1670, + 1675, + 1680, + 1685, + 1690, + 1695, + 1700, + 1705, + 1710, + 1715, + 1720, + 1725, + 1730, + 1735, + 1740, + 1745, + 1750, + 1755, + 1760, + 1765, + 1770, + 1775, + 1780, + 1785, + 1790, + 1795, + 1800, + 1805, + 1810, + 1815, + 1820, + 1825, + 1830, + 1835, + 1840, + 1845, + 1850, + 1855, + 1860, + 1865, + 1870, + 1875, + 1880, + 1885, + 1890, + 1895, + 1900, + 1905, + 1910, + 1915, + 1920, + 1925, + 1930, + 1935, + 1940, + 1945, + 1950, + 1955, + 1960, + 1965, + 1970, + 1975, + 1980, + 1985, + 1990, + 1995, + 2000, + 2005, + 2010, + 2015, + 2020, + 2025, + 2030, + 2035, + 2040, + 2045, + 2050, + 2055, + 2060, + 2065, + 2070, + 2075, + 2080, + 2085, + 2090, + 2095, + 2100, + 2105, + 2110, + 2115, + 2120, + 2125, + 2130, + 2135, + 2140, + 2145, + 2150, + 2155, + 2160, + 2165, + 2170, + 2175, + 2180, + 2185, + 2190, + 2195, + 2200, + 2205, + 2210, + 2215, + 2220, + 2225, + 2230, + 2235, + 2240, + 2245, + 2250, + 2255, + 2260, + 2265, + 2270, + 2275, + 2280, + 2285, + 2290, + 2295, + 2300, + 2305, + 2310, + 2315, + 2320, + 2325, + 2330, + 2335, + 2340, + 2345, + 2350, + 2355, + 2360, + 2365, + 2370, + 2375, + 2380, + 2385, + 2390, + 2395, + 2400, + 2405, + 2410, + 2415, + 2420, + 2425, + 2430, + 2435, + 2440, + 2445, + 2450, + 2455, + 2460, + 2465, + 2470, + 2475, + 2480, + 2485, + 2490, + 2495, + 2500, + 2505, + 2510, + 2515, + 2520, + 2525, + 2530, + 2535, + 2540, + 2545, + 2550, + 2555, + 2560, + 2565, + 2570, + 2575, + 2580, + 2585, + 2590, + 2595, + 2600, + 2605, + 2610, + 2615, + 2620, + 2625, + 2630, + 2635, + 2640, + 2645, + 2650, + 2655, + 2660, + 2665, + 2670, + 2675, + 2680, + 2685, + 2690, + 2695, + 2700, + 2705, + 2710, + 2715, + 2720, + 2725, + 2730, + 2735, + 2740, + 2745, + 2750, + 2755, + 2760, + 2765, + 2770, + 2775, + 2780, + 2785, + 2790, + 2795, + 2800, + 2805, + 2810, + 2815, + 2820, + 2825, + 2830, + 2835, + 2840, + 2845, + 2850, + 2855, + 2860, + 2865, + 2870, + 2875, + 2880, + 2885, + 2890, + 2895, + 2900, + 2905, + 2910, + 2915, + 2920, + 2925, + 2930, + 2935, + 2940, + 2945, + 2950, + 2955, + 2960, + 2965, + 2970, + 2975, + 2980, + 2985, + 2990, + 2995, + 3000, + 3005, + 3010, + 3015, + 3020, + 3025, + 3030, + 3035, + 3040, + 3045, + 3050, + 3055, + 3060, + 3065, + 3070, + 3075, + 3080, + 3085, + 3090, + 3095, + 3100, + 3105, + 3110, + 3115, + 3120, + 3125, + 3130, + 3135, + 3140, + 3145, + 3150, + 3155, + 3160, + 3165, + 3170, + 3175, + 3180, + 3185, + 3190, + 3195, + 3200, + 3205, + 3210, + 3215, + 3220, + 3225, + 3230, + 3235, + 3240, + 3245, + 3250, + 3255, + 3260, + 3265, + 3270, + 3275, + 3280, + 3285, + 3290, + 3295, + 3300, + 3305, + 3310, + 3315, + 3320, + 3325, + 3330, + 3335, + 3340, + 3345, + 3350, + 3355, + 3360, + 3365, + 3370, + 3375, + 3380, + 3385, + 3390, + 3395, + 3400, + 3405, + 3410, + 3415, + 3420, + 3425, + 3430, + 3435, + 3440, + 3445, + 3450, + 3455, + 3460, + 3465, + 3470, + 3475, + 3480, + 3485, + 3490, + 3495, + 3500, + 3505, + 3510, + 3510.522507468857 + ], + "y": [ + 4.060121657725171, + 4.0440965898480234, + 4.034825220624699, + 4.027436103919198, + 4.021132752959295, + 4.015595269139246, + 4.010647681438589, + 4.006174799709503, + 4.002094768861892, + 3.998347074323662, + 3.9948853366063815, + 3.9916729580444783, + 3.9886802459644817, + 3.985883029601075, + 3.9832614994413897, + 3.9807991503933695, + 3.9784820332352857, + 3.976297854103046, + 3.974236297118218, + 3.9722884737746544, + 3.970446712898164, + 3.968704347164096, + 3.9670553548907352, + 3.9654943707384778, + 3.964016507779917, + 3.96261744862915, + 3.961293301681543, + 3.9600405089437065, + 3.958855801336466, + 3.9577361122125327, + 3.956678591942213, + 3.955680562433091, + 3.954739506519783, + 3.9538530347726586, + 3.9530188544063942, + 3.952234755735694, + 3.9514985933259856, + 3.950808283611841, + 3.9501617899653247, + 3.949557114319111, + 3.9489922884049142, + 3.948465366622288, + 3.947974421905812, + 3.947517540393611, + 3.9470928202123545, + 3.946698367815674, + 3.946332298211953, + 3.945992733934509, + 3.945677805276896, + 3.945385648691732, + 3.945114407719662, + 3.9448622339508623, + 3.9446272914917886, + 3.944407758400042, + 3.944201827948562, + 3.9440077096668142, + 3.943823631157972, + 3.943647843300595, + 3.943478622048449, + 3.943314272947128, + 3.943153130836272, + 3.9429935633959983, + 3.9428339737979847, + 3.942672804127088, + 3.9425085366645343, + 3.942339699370915, + 3.942164865191379, + 3.941982654085342, + 3.941791738716668, + 3.941590841160643, + 3.941378737499459, + 3.941154258716942, + 3.940916288406615, + 3.9406637749113074, + 3.940395724650167, + 3.940111202827605, + 3.9398093176995705, + 3.93948926309745, + 3.939150285984352, + 3.9387916951817954, + 3.938412838669659, + 3.938013146622299, + 3.93759210693792, + 3.937149264550323, + 3.936684209814037, + 3.9361965932826015, + 3.935686133995041, + 3.9351526001027066, + 3.9345958139866535, + 3.934015651009173, + 3.933412038173378, + 3.9327849171109097, + 3.932134316439588, + 3.931460305216995, + 3.930762991655308, + 3.930042526289108, + 3.9292991001737, + 3.928532939789437, + 3.927744270194291, + 3.9269334077135336, + 3.926100677767375, + 3.925246435463019, + 3.9243710636099864, + 3.9234749707481313, + 3.922558585062612, + 3.9216223516923665, + 3.92066674784931, + 3.919692262768464, + 3.918699401469542, + 3.9176886829399726, + 3.9166606383645286, + 3.915615816645838, + 3.9145547721293026, + 3.913478061410871, + 3.9123862506419886, + 3.9112799105512432, + 3.9101596150347446, + 3.90902593980653, + 3.907879463882322, + 3.906720768788076, + 3.90555043419629, + 3.904369029854372, + 3.9031771237889714, + 3.9019752823937175, + 3.9007640688691607, + 3.8995440300744737, + 3.898315711274881, + 3.8970796496879654, + 3.895836373793419, + 3.8945864026944177, + 3.8933302455288783, + 3.89206840092864, + 3.890801356524716, + 3.889529588496475, + 3.88825356116287, + 3.886973726613334, + 3.8856905243764617, + 3.884404381535138, + 3.883115714640332, + 3.881824920490164, + 3.8805323857296368, + 3.879238483273564, + 3.8779435721545426, + 3.876647997393039, + 3.8753520898872713, + 3.874056166320722, + 3.872760529085063, + 3.871465466216278, + 3.8701712513418514, + 3.8688781436367994, + 3.867586387786421, + 3.8662962139536647, + 3.865007837748932, + 3.863721460200294, + 3.86243726772195, + 3.861155432079, + 3.8598761103463217, + 3.858599444859667, + 3.857325563156892, + 3.856054577907393, + 3.8547865880092687, + 3.853521675140205, + 3.852259906592461, + 3.8510013345516465, + 3.849745995737716, + 3.848493911223823, + 3.847245086230678, + 3.845999509894802, + 3.8447571550092743, + 3.843517977735744, + 3.842281917286528, + 3.84104889557604, + 3.839818816840773, + 3.838591567227613, + 3.837367014350412, + 3.836145006815423, + 3.834925373716293, + 3.8337079241003913, + 3.832492446408417, + 3.8312787078904127, + 3.8300664540019858, + 3.8288554077854937, + 3.82764526924246, + 3.8264357147043304, + 3.825226396210623, + 3.824016940904831, + 3.8228069504605537, + 3.821596000552264, + 3.8203836403873863, + 3.819169392318824, + 3.817952751559759, + 3.816733186025321, + 3.8155101363286623, + 3.8142830159621823, + 3.8130512116975823, + 3.811814084241704, + 3.8105709691881136, + 3.809321178306893, + 3.808064001217936, + 3.806798707494524, + 3.805524549297614, + 3.8042407645279015, + 3.80294658006389, + 3.801641216609067, + 3.800323893450274, + 3.7989938341069824, + 3.797650272696485, + 3.796292461027734, + 3.7949196764220727, + 3.7935312302411943, + 3.792126477082108, + 3.790704824574627, + 3.789265743690617, + 3.787808779444662, + 3.7863335618357694, + 3.784839816847797, + 3.7833273772958527, + 3.781796193276985, + 3.7802463419582577, + 3.778678036415928, + 3.7770916332268953, + 3.775487638511052, + 3.773866712130813, + 3.7722296697745423, + 3.7705774826839673, + 3.768911274832016, + 3.767232317417256, + 3.765542020611115, + 3.763841922574203, + 3.762133675842943, + 3.7604190312757577, + 3.758699819833409, + 3.756977932547074, + 3.7552552990962496, + 3.7535338654720247, + 3.75181557123721, + 3.7501023269107647, + 3.748395991999147, + 3.746698354171488, + 3.7450111101047416, + 3.743335848025119, + 3.7416740329885063, + 3.740026994423559, + 3.738395916427101, + 3.7367818307865783, + 3.7351856127856617, + 3.733607979598049, + 3.7320494911598785, + 3.7305105532926257, + 3.7289914228192713, + 3.727492214389555, + 3.726012908715474, + 3.7245533619158344, + 3.7231133156758616, + 3.721692407943994, + 3.720290183910288, + 3.7189061070380207, + 3.717539569949978, + 3.716189905002093, + 3.7148563944084705, + 3.7135382798114818, + 3.712234771219012, + 3.7109450552561265, + 3.7096683027009463, + 3.70840367529395, + 3.707150331826182, + 3.7059074335248368, + 3.704674148765394, + 3.7034496571469546, + 3.702233152973122, + 3.701023848184212, + 3.69982097478844, + 3.698623786840248, + 3.697431562013152, + 3.696243602813127, + 3.695059237476307, + 3.693877820592131, + 3.692698733493261, + 3.691521384432397, + 3.690345208606936, + 3.6891696680329553, + 3.687994251306236, + 3.6868184732717304, + 3.6856418746211577, + 3.6844640214360247, + 3.683284504691197, + 3.682102939731703, + 3.680918965733599, + 3.679732245157803, + 3.678542463204324, + 3.677349327272715, + 3.676152566433341, + 3.674951930913017, + 3.67374719159745, + 3.67253813955216, + 3.671324585562715, + 3.670106359694619, + 3.668883310872468, + 3.667655306477734, + 3.6664222319639785, + 3.6651839904880967, + 3.663940502555898, + 3.6626917056801673, + 3.6614375540491313, + 3.660178018203255, + 3.6589130847181535, + 3.657642755891324, + 3.656367049430627, + 3.655085998142156, + 3.6537996496154426, + 3.652508065903903, + 3.651211323198576, + 3.649909511493246, + 3.648602734239283, + 3.6472911079885497, + 3.645974762023026, + 3.6446538379698215, + 3.6433284894005418, + 3.641998881414076, + 3.640665190202166, + 3.639327602597143, + 3.6379863156016343, + 3.636641535900045, + 3.635293479351955, + 3.6339423704677234, + 3.632588441866708, + 3.631231933718905, + 3.629873093170719, + 3.6285121737560697, + 3.627149434793852, + 3.625785140773238, + 3.624419560728313, + 3.623052967603612, + 3.621685637612379, + 3.6203178495893473, + 3.6189498843400862, + 3.6175820239887626, + 3.616214551326544, + 3.6148477491626982, + 3.613481899680409, + 3.612117283799607, + 3.610754180548805, + 3.6093928664480366, + 3.608033614904967, + 3.6066766956260614, + 3.60532237404482, + 3.603970910768733, + 3.602622561046742, + 3.601277574258739, + 3.599936193428581, + 3.598598654761868, + 3.59726518720971, + 3.595936012059473, + 3.5946113425533746, + 3.593291383535633, + 3.5919763311287114, + 3.5906663724390486, + 3.5893616852925385, + 3.5880624379998536, + 3.586768789151249, + 3.5854808874411783, + 3.5841988715220525, + 3.5829228698865156, + 3.581653000777371, + 3.580389372125797, + 3.5791320815152186, + 3.5778812161709768, + 3.576636852974426, + 3.575399058500416, + 3.574167889077013, + 3.5729433908661643, + 3.5717255999640036, + 3.570514542519597, + 3.569310234870619, + 3.568112683694738, + 3.5669218861752174, + 3.56573783017944, + 3.5645604944489064, + 3.563389848799458, + 3.5622258543302947, + 3.5610684636404986, + 3.559917621051854, + 3.558773262836721, + 3.55763531744968, + 3.5565037057619606, + 3.5553783412974935, + 3.5542591304695503, + 3.5531459728170605, + 3.5520387612396327, + 3.5509373822305084, + 3.5498417161066005, + 3.548751637234977, + 3.54766701425501, + 3.5465877102957735, + 3.545513583188004, + 3.544444485670259, + 3.5433802655888806, + 3.5423207660911897, + 3.5412658258120757, + 3.5402152790534145, + 3.5391689559561237, + 3.5381266826650055, + 3.5370882814860525, + 3.5360535710363608, + 3.5350223663866314, + 3.533994479196357, + 3.5329697178418216, + 3.5319478875370716, + 3.530928790448129, + 3.5299122258006337, + 3.528897989981254, + 3.527885876633185, + 3.5268756767461196, + 3.52586717874104, + 3.52486016855038, + 3.5238544296938814, + 3.5228497433507657, + 3.5218458884286643, + 3.520842641629975, + 3.519839777516064, + 3.5188370685701114, + 3.517834285259095, + 3.5168311960956897, + 3.515827567700656, + 3.514823164866568, + 3.5138177506234767, + 3.512811086307386, + 3.5118029316322534, + 3.5107930447662925, + 3.509781182413498, + 3.5087670999010996, + 3.5077505512738623, + 3.506731289396084, + 3.5057090660621704, + 3.5046836321165897, + 3.5036547375842395, + 3.5026221318119135, + 3.501585563621958, + 3.500544781478827, + 3.4994995336694936, + 3.4984495684985775, + 3.497394634498943, + 3.496334480658677, + 3.495268856665154, + 3.494197513166962, + 3.493120202054342, + 3.49203667675884, + 3.4909466925726873, + 3.489850006988497, + 3.488746380059551, + 3.4876355747812493, + 3.4865173574936885, + 3.48539149830573, + 3.484257771540422, + 3.483115956201678, + 3.481965836461988, + 3.4808072021706726, + 3.4796398493821137, + 3.4784635809032336, + 3.4772782068591654, + 3.4760835452761025, + 3.4748794226798365, + 3.4736656747084833, + 3.4724421467376088, + 3.4712086945157092, + 3.469965184807773, + 3.4687114960445316, + 3.4674475189745566, + 3.4661731573163848, + 3.4648883284073984, + 3.463592963846131, + 3.4622870101243564, + 3.4609704292452177, + 3.459643199323355, + 3.4583053151629617, + 3.45695678880945, + 3.45559765007038, + 3.4542279470011863, + 3.452847746351145, + 3.4514571339651496, + 3.450056215136754, + 3.448645114908027, + 3.447223978311982, + 3.4457929705534105, + 3.44435227712411, + 3.442902103848932, + 3.441442676859116, + 3.439974242490042, + 3.438497067100566, + 3.437011436811858, + 3.435517657163998, + 3.434016052689069, + 3.4325069664001164, + 3.4309907591959026, + 3.4294678091820225, + 3.4279385109094203, + 3.426403274532248, + 3.424862524887339, + 3.423316700498452, + 3.421766252508956, + 3.4202116435471335, + 3.418653346529114, + 3.4170918434046915, + 3.415527623851971, + 3.4139611839271664, + 3.412393024676287, + 3.410823650715724, + 3.409253568789084, + 3.407683286307746, + 3.406113309882774, + 3.404544143855861, + 3.402976288836866, + 3.4014102402555233, + 3.399846486934615, + 3.3982855096916817, + 3.3967277799760054, + 3.395173758547187, + 3.393623894201262, + 3.392078622549653, + 3.3905383648558733, + 3.389003526934113, + 3.3874744981134444, + 3.3859516502704503, + 3.3844353369326416, + 3.3829258924542644, + 3.381423631265361, + 3.379928847194382, + 3.3784418128638594, + 3.376962779158142, + 3.375491974761414, + 3.374029605763746, + 3.3725758553323115, + 3.3711308834443976, + 3.3696948266782596, + 3.3682677980576208, + 3.3668498869450105, + 3.3654411589788458, + 3.364041656048953, + 3.362651396304688, + 3.3612703741899654, + 3.3598985604988707, + 3.3585359024457526, + 3.357182323743368, + 3.3558377246825577, + 3.3545019822070063, + 3.353174949976488, + 3.351856458411973, + 3.3505463147161088, + 3.349244302862481, + 3.3479501835471503, + 3.3466636940960406, + 3.3453845483217535, + 3.3441124363235, + 3.342847024223917, + 3.3415879538365343, + 3.340334842257856, + 3.339087281378062, + 3.337844837304357, + 3.336607049691237, + 3.335373430971962, + 3.3341434654856306, + 3.332916608494533, + 3.3316922850864517, + 3.3304698889569537, + 3.3292487810668394, + 3.3280282881702825, + 3.3268077012095234, + 3.325586273572458, + 3.324363219209925, + 3.32313771061015, + 3.3219088766287004, + 3.320675800172994, + 3.319437515741699, + 3.318193006820577, + 3.316941203137944, + 3.3156809777845364, + 3.3144111442050064, + 3.3131304530704098, + 3.3118375890441953, + 3.3105311674572104, + 3.3092097309113293, + 3.307871745835316, + 3.306515599021592, + 3.305139594178076, + 3.303741948535313, + 3.302320789556072, + 3.3008741518022378, + 3.2993999740219375, + 3.29789609652923, + 3.2963602589581753, + 3.2947900984838503, + 3.293183148613614, + 3.2915368386636357, + 3.2898484940473107, + 3.288115337513972, + 3.2863344914876964, + 3.284502981666729, + 3.2826177420537244, + 3.2806756215944066, + 3.2786733926079368, + 3.2766077611938664, + 3.274475379798586, + 3.2722728621166812, + 3.2699968004890505, + 3.267643785938526, + 3.2652104309543777, + 3.26269339509799, + 3.2600894134524845, + 3.2573953278783367, + 3.254608120964659, + 3.251724952481813, + 3.248743198045954, + 3.245660489601031, + 3.2424747572112036, + 3.2391842715385692, + 3.235787686262306, + 3.232284079579755, + 3.228672993823676, + 3.224954472138988, + 3.2211290910935215, + 3.217197988057837, + 3.213162882185259, + 3.2090260878612615, + 3.2047905195754556, + 3.200459687302361, + 3.196037681659744, + 3.1915291483420725, + 3.186939251597192, + 3.1822736268174516, + 3.177538322640823, + 3.172739733289606, + 3.1678845221984635, + 3.162979538283564, + 3.158031726465252, + 3.153048034263055, + 3.1480353164231136, + 3.143000239605573, + 3.1379491891496074, + 3.132888179846821, + 3.127822772494949, + 3.122757997782051, + 3.117698288778874, + 3.1126474230087844, + 3.10760847473618, + 3.1025837777818706, + 3.097574898852514, + 3.0925826210741736, + 3.0876069371579633, + 3.0826470514068727, + 3.07770138960108, + 3.0727676156771784, + 3.0678426540430546, + 3.062922716341639, + 3.058003331488832, + 3.0530793778567737, + 3.048145116547339, + 3.0431942247945902, + 3.038219828642671, + 3.0332145341611265, + 3.0281704565771106, + 3.023079246819377, + 3.017932115078509, + 3.012719851088733, + 3.0074328409275113, + 3.002061080208371, + 2.996594183610144, + 2.991021390741576, + 2.985331568385135, + 2.979513209198197, + 2.973554426974769, + 2.9674429485874714, + 2.961166102739063, + 2.9547108056558486, + 2.9480635438537885, + 2.941210354101934, + 2.9341368006988273, + 2.926827950165741, + 2.919268343447117, + 2.911441965693725, + 2.9033322136884294, + 2.894921860958181, + 2.886193020599464, + 2.877127105828041, + 2.8677047882473268, + 2.8579059538136806, + 2.847709656460917, + 2.837094069330381, + 2.826036433537559, + 2.814513004390263, + 2.8024989949580172, + 2.7899685168761525, + 2.7768945182518685, + 2.7632487185224943, + 2.7490015400983188, + 2.7341220366030843, + 2.7185778175045376, + 2.702334968904547, + 2.6853579702324626, + 2.6676096065565793, + 2.649050876194984, + 2.6296408932681192, + 2.6093367847896185, + 2.5880935818368696, + 2.565864104276627 + ] + } + ], + "layout": { + "autosize": false, + "height": 576, + "legend": { + "font": { + "size": 12 + }, + "x": 1, + "xanchor": "right", + "y": 1, + "yanchor": "top" + }, + "margin": { + "b": 10, + "l": 10, + "pad": 4, + "r": 10, + "t": 75 + }, + "showlegend": true, + "template": { + "data": { + "bar": [ + { + "error_x": { + "color": "#2a3f5f" + }, + "error_y": { + "color": "#2a3f5f" + }, + "marker": { + "line": { + "color": "#E5ECF6", + "width": 0.5 + }, + "pattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + } + }, + "type": "bar" + } + ], + "barpolar": [ + { + "marker": { + "line": { + "color": "#E5ECF6", + "width": 0.5 + }, + "pattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + } + }, + "type": "barpolar" + } + ], + "carpet": [ + { + "aaxis": { + "endlinecolor": "#2a3f5f", + "gridcolor": "white", + "linecolor": "white", + "minorgridcolor": "white", + "startlinecolor": "#2a3f5f" + }, + "baxis": { + "endlinecolor": "#2a3f5f", + "gridcolor": "white", + "linecolor": "white", + "minorgridcolor": "white", + "startlinecolor": "#2a3f5f" + }, + "type": "carpet" + } + ], + "choropleth": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "type": "choropleth" + } + ], + "contour": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "contour" + } + ], + "contourcarpet": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "type": "contourcarpet" + } + ], + "heatmap": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "heatmap" + } + ], + "heatmapgl": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "heatmapgl" + } + ], + "histogram": [ + { + "marker": { + "pattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + } + }, + "type": "histogram" + } + ], + "histogram2d": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "histogram2d" + } + ], + "histogram2dcontour": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "histogram2dcontour" + } + ], + "mesh3d": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "type": "mesh3d" + } + ], + "parcoords": [ + { + "line": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "parcoords" + } + ], + "pie": [ + { + "automargin": true, + "type": "pie" + } + ], + "scatter": [ + { + "fillpattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + }, + "type": "scatter" + } + ], + "scatter3d": [ + { + "line": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatter3d" + } + ], + "scattercarpet": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattercarpet" + } + ], + "scattergeo": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattergeo" + } + ], + "scattergl": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattergl" + } + ], + "scattermapbox": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattermapbox" + } + ], + "scatterpolar": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatterpolar" + } + ], + "scatterpolargl": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatterpolargl" + } + ], + "scatterternary": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatterternary" + } + ], + "surface": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "surface" + } + ], + "table": [ + { + "cells": { + "fill": { + "color": "#EBF0F8" + }, + "line": { + "color": "white" + } + }, + "header": { + "fill": { + "color": "#C8D4E3" + }, + "line": { + "color": "white" + } + }, + "type": "table" + } + ] + }, + "layout": { + "annotationdefaults": { + "arrowcolor": "#2a3f5f", + "arrowhead": 0, + "arrowwidth": 1 + }, + "autotypenumbers": "strict", + "coloraxis": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "colorscale": { + "diverging": [ + [ + 0, + "#8e0152" + ], + [ + 0.1, + "#c51b7d" + ], + [ + 0.2, + "#de77ae" + ], + [ + 0.3, + "#f1b6da" + ], + [ + 0.4, + "#fde0ef" + ], + [ + 0.5, + "#f7f7f7" + ], + [ + 0.6, + "#e6f5d0" + ], + [ + 0.7, + "#b8e186" + ], + [ + 0.8, + "#7fbc41" + ], + [ + 0.9, + "#4d9221" + ], + [ + 1, + "#276419" + ] + ], + "sequential": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "sequentialminus": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ] + }, + "colorway": [ + "#636efa", + "#EF553B", + "#00cc96", + "#ab63fa", + "#FFA15A", + "#19d3f3", + "#FF6692", + "#B6E880", + "#FF97FF", + "#FECB52" + ], + "font": { + "color": "#2a3f5f" + }, + "geo": { + "bgcolor": "white", + "lakecolor": "white", + "landcolor": "#E5ECF6", + "showlakes": true, + "showland": true, + "subunitcolor": "white" + }, + "hoverlabel": { + "align": "left" + }, + "hovermode": "closest", + "mapbox": { + "style": "light" + }, + "paper_bgcolor": "white", + "plot_bgcolor": "#E5ECF6", + "polar": { + "angularaxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + }, + "bgcolor": "#E5ECF6", + "radialaxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + } + }, + "scene": { + "xaxis": { + "backgroundcolor": "#E5ECF6", + "gridcolor": "white", + "gridwidth": 2, + "linecolor": "white", + "showbackground": true, + "ticks": "", + "zerolinecolor": "white" + }, + "yaxis": { + "backgroundcolor": "#E5ECF6", + "gridcolor": "white", + "gridwidth": 2, + "linecolor": "white", + "showbackground": true, + "ticks": "", + "zerolinecolor": "white" + }, + "zaxis": { + "backgroundcolor": "#E5ECF6", + "gridcolor": "white", + "gridwidth": 2, + "linecolor": "white", + "showbackground": true, + "ticks": "", + "zerolinecolor": "white" + } + }, + "shapedefaults": { + "line": { + "color": "#2a3f5f" + } + }, + "ternary": { + "aaxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + }, + "baxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + }, + "bgcolor": "#E5ECF6", + "caxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + } + }, + "title": { + "x": 0.05 + }, + "xaxis": { + "automargin": true, + "gridcolor": "white", + "linecolor": "white", + "ticks": "", + "title": { + "standoff": 15 + }, + "zerolinecolor": "white", + "zerolinewidth": 2 + }, + "yaxis": { + "automargin": true, + "gridcolor": "white", + "linecolor": "white", + "ticks": "", + "title": { + "standoff": 15 + }, + "zerolinecolor": "white", + "zerolinewidth": 2 + } + } + }, + "title": { + "text": "Optimised Comparison", + "x": 0.5 + }, + "width": 1024, + "xaxis": { + "tickfont": { + "size": 12 + }, + "title": { + "font": { + "size": 12 + }, + "text": "Time [s]" + } + }, + "yaxis": { + "tickfont": { + "size": 12 + }, + "title": { + "font": { + "size": 12 + }, + "text": "Voltage [V]" + } + } + } + } + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.plotly.v1+json": { + "config": { + "plotlyServerURL": "https://plot.ly" + }, + "data": [ + { + "fill": "toself", + "fillcolor": "rgba(255,229,204,0.8)", + "hoverinfo": "skip", + "line": { + "color": "rgba(255,255,255,0)" + }, + "showlegend": false, + "type": "scatter", + "x": [ + 0, + 5, + 10, + 15, + 20, + 25, + 30, + 35, + 40, + 45, + 50, + 55, + 60, + 65, + 70, + 75, + 80, + 85, + 90, + 95, + 100, + 105, + 110, + 115, + 120, + 125, + 130, + 135, + 140, + 145, + 150, + 155, + 160, + 165, + 170, + 175, + 180, + 185, + 190, + 195, + 200, + 205, + 210, + 215, + 220, + 225, + 230, + 235, + 240, + 245, + 250, + 255, + 260, + 265, + 270, + 275, + 280, + 285, + 290, + 295, + 300, + 305, + 310, + 315, + 320, + 325, + 330, + 335, + 340, + 345, + 350, + 355, + 360, + 365, + 370, + 375, + 380, + 385, + 390, + 395, + 400, + 405, + 410, + 415, + 420, + 425, + 430, + 435, + 440, + 445, + 450, + 455, + 460, + 465, + 470, + 475, + 480, + 485, + 490, + 495, + 500, + 505, + 510, + 515, + 520, + 525, + 530, + 535, + 540, + 545, + 550, + 555, + 560, + 565, + 570, + 575, + 580, + 585, + 590, + 595, + 600, + 605, + 610, + 615, + 620, + 625, + 630, + 635, + 640, + 645, + 650, + 655, + 660, + 665, + 670, + 675, + 680, + 685, + 690, + 695, + 700, + 705, + 710, + 715, + 720, + 725, + 730, + 735, + 740, + 745, + 750, + 755, + 760, + 765, + 770, + 775, + 780, + 785, + 790, + 795, + 800, + 805, + 810, + 815, + 820, + 825, + 830, + 835, + 840, + 845, + 850, + 855, + 860, + 865, + 870, + 875, + 880, + 885, + 890, + 895, + 900, + 905, + 910, + 915, + 920, + 925, + 930, + 935, + 940, + 945, + 950, + 955, + 960, + 965, + 970, + 975, + 980, + 985, + 990, + 995, + 1000, + 1005, + 1010, + 1015, + 1020, + 1025, + 1030, + 1035, + 1040, + 1045, + 1050, + 1055, + 1060, + 1065, + 1070, + 1075, + 1080, + 1085, + 1090, + 1095, + 1100, + 1105, + 1110, + 1115, + 1120, + 1125, + 1130, + 1135, + 1140, + 1145, + 1150, + 1155, + 1160, + 1165, + 1170, + 1175, + 1180, + 1185, + 1190, + 1195, + 1200, + 1205, + 1210, + 1215, + 1220, + 1225, + 1230, + 1235, + 1240, + 1245, + 1250, + 1255, + 1260, + 1265, + 1270, + 1275, + 1280, + 1285, + 1290, + 1295, + 1300, + 1305, + 1310, + 1315, + 1320, + 1325, + 1330, + 1335, + 1340, + 1345, + 1350, + 1355, + 1360, + 1365, + 1370, + 1375, + 1380, + 1385, + 1390, + 1395, + 1400, + 1405, + 1410, + 1415, + 1420, + 1425, + 1430, + 1435, + 1440, + 1445, + 1450, + 1455, + 1460, + 1465, + 1470, + 1475, + 1480, + 1485, + 1490, + 1495, + 1500, + 1505, + 1510, + 1515, + 1520, + 1525, + 1530, + 1535, + 1540, + 1545, + 1550, + 1555, + 1560, + 1565, + 1570, + 1575, + 1580, + 1585, + 1590, + 1595, + 1600, + 1605, + 1610, + 1615, + 1620, + 1625, + 1630, + 1635, + 1640, + 1645, + 1650, + 1655, + 1660, + 1665, + 1670, + 1675, + 1680, + 1685, + 1690, + 1695, + 1700, + 1705, + 1710, + 1715, + 1720, + 1725, + 1730, + 1735, + 1740, + 1745, + 1750, + 1755, + 1760, + 1765, + 1770, + 1775, + 1780, + 1785, + 1790, + 1795, + 1800, + 1805, + 1810, + 1815, + 1820, + 1825, + 1830, + 1835, + 1840, + 1845, + 1850, + 1855, + 1860, + 1865, + 1870, + 1875, + 1880, + 1885, + 1890, + 1895, + 1900, + 1905, + 1910, + 1915, + 1920, + 1925, + 1930, + 1935, + 1940, + 1945, + 1950, + 1955, + 1960, + 1965, + 1970, + 1975, + 1980, + 1985, + 1990, + 1995, + 2000, + 2005, + 2010, + 2015, + 2020, + 2025, + 2030, + 2035, + 2040, + 2045, + 2050, + 2055, + 2060, + 2065, + 2070, + 2075, + 2080, + 2085, + 2090, + 2095, + 2100, + 2105, + 2110, + 2115, + 2120, + 2125, + 2130, + 2135, + 2140, + 2145, + 2150, + 2155, + 2160, + 2165, + 2170, + 2175, + 2180, + 2185, + 2190, + 2195, + 2200, + 2205, + 2210, + 2215, + 2220, + 2225, + 2230, + 2235, + 2240, + 2245, + 2250, + 2255, + 2260, + 2265, + 2270, + 2275, + 2280, + 2285, + 2290, + 2295, + 2300, + 2305, + 2310, + 2315, + 2320, + 2325, + 2330, + 2335, + 2340, + 2345, + 2350, + 2355, + 2360, + 2365, + 2370, + 2375, + 2380, + 2385, + 2390, + 2395, + 2400, + 2405, + 2410, + 2415, + 2420, + 2425, + 2430, + 2435, + 2440, + 2445, + 2450, + 2455, + 2460, + 2465, + 2470, + 2475, + 2480, + 2485, + 2490, + 2495, + 2500, + 2505, + 2510, + 2515, + 2520, + 2525, + 2530, + 2535, + 2540, + 2545, + 2550, + 2555, + 2560, + 2565, + 2570, + 2575, + 2580, + 2585, + 2590, + 2595, + 2600, + 2605, + 2610, + 2615, + 2620, + 2625, + 2630, + 2635, + 2640, + 2645, + 2650, + 2655, + 2660, + 2665, + 2670, + 2675, + 2680, + 2685, + 2690, + 2695, + 2700, + 2705, + 2710, + 2715, + 2720, + 2725, + 2730, + 2735, + 2740, + 2745, + 2750, + 2755, + 2760, + 2765, + 2770, + 2775, + 2780, + 2785, + 2790, + 2795, + 2800, + 2805, + 2810, + 2815, + 2820, + 2825, + 2830, + 2835, + 2840, + 2845, + 2850, + 2855, + 2860, + 2865, + 2870, + 2875, + 2880, + 2885, + 2890, + 2895, + 2900, + 2905, + 2910, + 2915, + 2920, + 2925, + 2930, + 2935, + 2940, + 2945, + 2950, + 2955, + 2960, + 2965, + 2970, + 2975, + 2980, + 2985, + 2990, + 2995, + 3000, + 3005, + 3010, + 3015, + 3020, + 3025, + 3030, + 3035, + 3040, + 3045, + 3050, + 3055, + 3060, + 3065, + 3070, + 3075, + 3080, + 3085, + 3090, + 3095, + 3100, + 3105, + 3110, + 3115, + 3120, + 3125, + 3130, + 3135, + 3140, + 3145, + 3150, + 3155, + 3160, + 3165, + 3170, + 3175, + 3180, + 3185, + 3190, + 3195, + 3200, + 3205, + 3210, + 3215, + 3220, + 3225, + 3230, + 3235, + 3240, + 3245, + 3250, + 3255, + 3260, + 3265, + 3270, + 3275, + 3280, + 3285, + 3290, + 3295, + 3300, + 3305, + 3310, + 3315, + 3320, + 3325, + 3330, + 3335, + 3340, + 3345, + 3350, + 3355, + 3360, + 3365, + 3370, + 3375, + 3380, + 3385, + 3390, + 3395, + 3400, + 3405, + 3410, + 3415, + 3420, + 3425, + 3430, + 3435, + 3440, + 3445, + 3450, + 3455, + 3460, + 3465, + 3470, + 3475, + 3480, + 3485, + 3490, + 3495, + 3500, + 3505, + 3510, + 3510.522507468857, + 3510.522507468857, + 3510, + 3505, + 3500, + 3495, + 3490, + 3485, + 3480, + 3475, + 3470, + 3465, + 3460, + 3455, + 3450, + 3445, + 3440, + 3435, + 3430, + 3425, + 3420, + 3415, + 3410, + 3405, + 3400, + 3395, + 3390, + 3385, + 3380, + 3375, + 3370, + 3365, + 3360, + 3355, + 3350, + 3345, + 3340, + 3335, + 3330, + 3325, + 3320, + 3315, + 3310, + 3305, + 3300, + 3295, + 3290, + 3285, + 3280, + 3275, + 3270, + 3265, + 3260, + 3255, + 3250, + 3245, + 3240, + 3235, + 3230, + 3225, + 3220, + 3215, + 3210, + 3205, + 3200, + 3195, + 3190, + 3185, + 3180, + 3175, + 3170, + 3165, + 3160, + 3155, + 3150, + 3145, + 3140, + 3135, + 3130, + 3125, + 3120, + 3115, + 3110, + 3105, + 3100, + 3095, + 3090, + 3085, + 3080, + 3075, + 3070, + 3065, + 3060, + 3055, + 3050, + 3045, + 3040, + 3035, + 3030, + 3025, + 3020, + 3015, + 3010, + 3005, + 3000, + 2995, + 2990, + 2985, + 2980, + 2975, + 2970, + 2965, + 2960, + 2955, + 2950, + 2945, + 2940, + 2935, + 2930, + 2925, + 2920, + 2915, + 2910, + 2905, + 2900, + 2895, + 2890, + 2885, + 2880, + 2875, + 2870, + 2865, + 2860, + 2855, + 2850, + 2845, + 2840, + 2835, + 2830, + 2825, + 2820, + 2815, + 2810, + 2805, + 2800, + 2795, + 2790, + 2785, + 2780, + 2775, + 2770, + 2765, + 2760, + 2755, + 2750, + 2745, + 2740, + 2735, + 2730, + 2725, + 2720, + 2715, + 2710, + 2705, + 2700, + 2695, + 2690, + 2685, + 2680, + 2675, + 2670, + 2665, + 2660, + 2655, + 2650, + 2645, + 2640, + 2635, + 2630, + 2625, + 2620, + 2615, + 2610, + 2605, + 2600, + 2595, + 2590, + 2585, + 2580, + 2575, + 2570, + 2565, + 2560, + 2555, + 2550, + 2545, + 2540, + 2535, + 2530, + 2525, + 2520, + 2515, + 2510, + 2505, + 2500, + 2495, + 2490, + 2485, + 2480, + 2475, + 2470, + 2465, + 2460, + 2455, + 2450, + 2445, + 2440, + 2435, + 2430, + 2425, + 2420, + 2415, + 2410, + 2405, + 2400, + 2395, + 2390, + 2385, + 2380, + 2375, + 2370, + 2365, + 2360, + 2355, + 2350, + 2345, + 2340, + 2335, + 2330, + 2325, + 2320, + 2315, + 2310, + 2305, + 2300, + 2295, + 2290, + 2285, + 2280, + 2275, + 2270, + 2265, + 2260, + 2255, + 2250, + 2245, + 2240, + 2235, + 2230, + 2225, + 2220, + 2215, + 2210, + 2205, + 2200, + 2195, + 2190, + 2185, + 2180, + 2175, + 2170, + 2165, + 2160, + 2155, + 2150, + 2145, + 2140, + 2135, + 2130, + 2125, + 2120, + 2115, + 2110, + 2105, + 2100, + 2095, + 2090, + 2085, + 2080, + 2075, + 2070, + 2065, + 2060, + 2055, + 2050, + 2045, + 2040, + 2035, + 2030, + 2025, + 2020, + 2015, + 2010, + 2005, + 2000, + 1995, + 1990, + 1985, + 1980, + 1975, + 1970, + 1965, + 1960, + 1955, + 1950, + 1945, + 1940, + 1935, + 1930, + 1925, + 1920, + 1915, + 1910, + 1905, + 1900, + 1895, + 1890, + 1885, + 1880, + 1875, + 1870, + 1865, + 1860, + 1855, + 1850, + 1845, + 1840, + 1835, + 1830, + 1825, + 1820, + 1815, + 1810, + 1805, + 1800, + 1795, + 1790, + 1785, + 1780, + 1775, + 1770, + 1765, + 1760, + 1755, + 1750, + 1745, + 1740, + 1735, + 1730, + 1725, + 1720, + 1715, + 1710, + 1705, + 1700, + 1695, + 1690, + 1685, + 1680, + 1675, + 1670, + 1665, + 1660, + 1655, + 1650, + 1645, + 1640, + 1635, + 1630, + 1625, + 1620, + 1615, + 1610, + 1605, + 1600, + 1595, + 1590, + 1585, + 1580, + 1575, + 1570, + 1565, + 1560, + 1555, + 1550, + 1545, + 1540, + 1535, + 1530, + 1525, + 1520, + 1515, + 1510, + 1505, + 1500, + 1495, + 1490, + 1485, + 1480, + 1475, + 1470, + 1465, + 1460, + 1455, + 1450, + 1445, + 1440, + 1435, + 1430, + 1425, + 1420, + 1415, + 1410, + 1405, + 1400, + 1395, + 1390, + 1385, + 1380, + 1375, + 1370, + 1365, + 1360, + 1355, + 1350, + 1345, + 1340, + 1335, + 1330, + 1325, + 1320, + 1315, + 1310, + 1305, + 1300, + 1295, + 1290, + 1285, + 1280, + 1275, + 1270, + 1265, + 1260, + 1255, + 1250, + 1245, + 1240, + 1235, + 1230, + 1225, + 1220, + 1215, + 1210, + 1205, + 1200, + 1195, + 1190, + 1185, + 1180, + 1175, + 1170, + 1165, + 1160, + 1155, + 1150, + 1145, + 1140, + 1135, + 1130, + 1125, + 1120, + 1115, + 1110, + 1105, + 1100, + 1095, + 1090, + 1085, + 1080, + 1075, + 1070, + 1065, + 1060, + 1055, + 1050, + 1045, + 1040, + 1035, + 1030, + 1025, + 1020, + 1015, + 1010, + 1005, + 1000, + 995, + 990, + 985, + 980, + 975, + 970, + 965, + 960, + 955, + 950, + 945, + 940, + 935, + 930, + 925, + 920, + 915, + 910, + 905, + 900, + 895, + 890, + 885, + 880, + 875, + 870, + 865, + 860, + 855, + 850, + 845, + 840, + 835, + 830, + 825, + 820, + 815, + 810, + 805, + 800, + 795, + 790, + 785, + 780, + 775, + 770, + 765, + 760, + 755, + 750, + 745, + 740, + 735, + 730, + 725, + 720, + 715, + 710, + 705, + 700, + 695, + 690, + 685, + 680, + 675, + 670, + 665, + 660, + 655, + 650, + 645, + 640, + 635, + 630, + 625, + 620, + 615, + 610, + 605, + 600, + 595, + 590, + 585, + 580, + 575, + 570, + 565, + 560, + 555, + 550, + 545, + 540, + 535, + 530, + 525, + 520, + 515, + 510, + 505, + 500, + 495, + 490, + 485, + 480, + 475, + 470, + 465, + 460, + 455, + 450, + 445, + 440, + 435, + 430, + 425, + 420, + 415, + 410, + 405, + 400, + 395, + 390, + 385, + 380, + 375, + 370, + 365, + 360, + 355, + 350, + 345, + 340, + 335, + 330, + 325, + 320, + 315, + 310, + 305, + 300, + 295, + 290, + 285, + 280, + 275, + 270, + 265, + 260, + 255, + 250, + 245, + 240, + 235, + 230, + 225, + 220, + 215, + 210, + 205, + 200, + 195, + 190, + 185, + 180, + 175, + 170, + 165, + 160, + 155, + 150, + 145, + 140, + 135, + 130, + 125, + 120, + 115, + 110, + 105, + 100, + 95, + 90, + 85, + 80, + 75, + 70, + 65, + 60, + 55, + 50, + 45, + 40, + 35, + 30, + 25, + 20, + 15, + 10, + 5, + 0 + ], + "y": [ + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081 + ] + }, + { + "mode": "markers", + "name": "Initial", + "type": "scatter", + "x": [ + 0, + 5, + 10, + 15, + 20, + 25, + 30, + 35, + 40, + 45, + 50, + 55, + 60, + 65, + 70, + 75, + 80, + 85, + 90, + 95, + 100, + 105, + 110, + 115, + 120, + 125, + 130, + 135, + 140, + 145, + 150, + 155, + 160, + 165, + 170, + 175, + 180, + 185, + 190, + 195, + 200, + 205, + 210, + 215, + 220, + 225, + 230, + 235, + 240, + 245, + 250, + 255, + 260, + 265, + 270, + 275, + 280, + 285, + 290, + 295, + 300, + 305, + 310, + 315, + 320, + 325, + 330, + 335, + 340, + 345, + 350, + 355, + 360, + 365, + 370, + 375, + 380, + 385, + 390, + 395, + 400, + 405, + 410, + 415, + 420, + 425, + 430, + 435, + 440, + 445, + 450, + 455, + 460, + 465, + 470, + 475, + 480, + 485, + 490, + 495, + 500, + 505, + 510, + 515, + 520, + 525, + 530, + 535, + 540, + 545, + 550, + 555, + 560, + 565, + 570, + 575, + 580, + 585, + 590, + 595, + 600, + 605, + 610, + 615, + 620, + 625, + 630, + 635, + 640, + 645, + 650, + 655, + 660, + 665, + 670, + 675, + 680, + 685, + 690, + 695, + 700, + 705, + 710, + 715, + 720, + 725, + 730, + 735, + 740, + 745, + 750, + 755, + 760, + 765, + 770, + 775, + 780, + 785, + 790, + 795, + 800, + 805, + 810, + 815, + 820, + 825, + 830, + 835, + 840, + 845, + 850, + 855, + 860, + 865, + 870, + 875, + 880, + 885, + 890, + 895, + 900, + 905, + 910, + 915, + 920, + 925, + 930, + 935, + 940, + 945, + 950, + 955, + 960, + 965, + 970, + 975, + 980, + 985, + 990, + 995, + 1000, + 1005, + 1010, + 1015, + 1020, + 1025, + 1030, + 1035, + 1040, + 1045, + 1050, + 1055, + 1060, + 1065, + 1070, + 1075, + 1080, + 1085, + 1090, + 1095, + 1100, + 1105, + 1110, + 1115, + 1120, + 1125, + 1130, + 1135, + 1140, + 1145, + 1150, + 1155, + 1160, + 1165, + 1170, + 1175, + 1180, + 1185, + 1190, + 1195, + 1200, + 1205, + 1210, + 1215, + 1220, + 1225, + 1230, + 1235, + 1240, + 1245, + 1250, + 1255, + 1260, + 1265, + 1270, + 1275, + 1280, + 1285, + 1290, + 1295, + 1300, + 1305, + 1310, + 1315, + 1320, + 1325, + 1330, + 1335, + 1340, + 1345, + 1350, + 1355, + 1360, + 1365, + 1370, + 1375, + 1380, + 1385, + 1390, + 1395, + 1400, + 1405, + 1410, + 1415, + 1420, + 1425, + 1430, + 1435, + 1440, + 1445, + 1450, + 1455, + 1460, + 1465, + 1470, + 1475, + 1480, + 1485, + 1490, + 1495, + 1500, + 1505, + 1510, + 1515, + 1520, + 1525, + 1530, + 1535, + 1540, + 1545, + 1550, + 1555, + 1560, + 1565, + 1570, + 1575, + 1580, + 1585, + 1590, + 1595, + 1600, + 1605, + 1610, + 1615, + 1620, + 1625, + 1630, + 1635, + 1640, + 1645, + 1650, + 1655, + 1660, + 1665, + 1670, + 1675, + 1680, + 1685, + 1690, + 1695, + 1700, + 1705, + 1710, + 1715, + 1720, + 1725, + 1730, + 1735, + 1740, + 1745, + 1750, + 1755, + 1760, + 1765, + 1770, + 1775, + 1780, + 1785, + 1790, + 1795, + 1800, + 1805, + 1810, + 1815, + 1820, + 1825, + 1830, + 1835, + 1840, + 1845, + 1850, + 1855, + 1860, + 1865, + 1870, + 1875, + 1880, + 1885, + 1890, + 1895, + 1900, + 1905, + 1910, + 1915, + 1920, + 1925, + 1930, + 1935, + 1940, + 1945, + 1950, + 1955, + 1960, + 1965, + 1970, + 1975, + 1980, + 1985, + 1990, + 1995, + 2000, + 2005, + 2010, + 2015, + 2020, + 2025, + 2030, + 2035, + 2040, + 2045, + 2050, + 2055, + 2060, + 2065, + 2070, + 2075, + 2080, + 2085, + 2090, + 2095, + 2100, + 2105, + 2110, + 2115, + 2120, + 2125, + 2130, + 2135, + 2140, + 2145, + 2150, + 2155, + 2160, + 2165, + 2170, + 2175, + 2180, + 2185, + 2190, + 2195, + 2200, + 2205, + 2210, + 2215, + 2220, + 2225, + 2230, + 2235, + 2240, + 2245, + 2250, + 2255, + 2260, + 2265, + 2270, + 2275, + 2280, + 2285, + 2290, + 2295, + 2300, + 2305, + 2310, + 2315, + 2320, + 2325, + 2330, + 2335, + 2340, + 2345, + 2350, + 2355, + 2360, + 2365, + 2370, + 2375, + 2380, + 2385, + 2390, + 2395, + 2400, + 2405, + 2410, + 2415, + 2420, + 2425, + 2430, + 2435, + 2440, + 2445, + 2450, + 2455, + 2460, + 2465, + 2470, + 2475, + 2480, + 2485, + 2490, + 2495, + 2500, + 2505, + 2510, + 2515, + 2520, + 2525, + 2530, + 2535, + 2540, + 2545, + 2550, + 2555, + 2560, + 2565, + 2570, + 2575, + 2580, + 2585, + 2590, + 2595, + 2600, + 2605, + 2610, + 2615, + 2620, + 2625, + 2630, + 2635, + 2640, + 2645, + 2650, + 2655, + 2660, + 2665, + 2670, + 2675, + 2680, + 2685, + 2690, + 2695, + 2700, + 2705, + 2710, + 2715, + 2720, + 2725, + 2730, + 2735, + 2740, + 2745, + 2750, + 2755, + 2760, + 2765, + 2770, + 2775, + 2780, + 2785, + 2790, + 2795, + 2800, + 2805, + 2810, + 2815, + 2820, + 2825, + 2830, + 2835, + 2840, + 2845, + 2850, + 2855, + 2860, + 2865, + 2870, + 2875, + 2880, + 2885, + 2890, + 2895, + 2900, + 2905, + 2910, + 2915, + 2920, + 2925, + 2930, + 2935, + 2940, + 2945, + 2950, + 2955, + 2960, + 2965, + 2970, + 2975, + 2980, + 2985, + 2990, + 2995, + 3000, + 3005, + 3010, + 3015, + 3020, + 3025, + 3030, + 3035, + 3040, + 3045, + 3050, + 3055, + 3060, + 3065, + 3070, + 3075, + 3080, + 3085, + 3090, + 3095, + 3100, + 3105, + 3110, + 3115, + 3120, + 3125, + 3130, + 3135, + 3140, + 3145, + 3150, + 3155, + 3160, + 3165, + 3170, + 3175, + 3180, + 3185, + 3190, + 3195, + 3200, + 3205, + 3210, + 3215, + 3220, + 3225, + 3230, + 3235, + 3240, + 3245, + 3250, + 3255, + 3260, + 3265, + 3270, + 3275, + 3280, + 3285, + 3290, + 3295, + 3300, + 3305, + 3310, + 3315, + 3320, + 3325, + 3330, + 3335, + 3340, + 3345, + 3350, + 3355, + 3360, + 3365, + 3370, + 3375, + 3380, + 3385, + 3390, + 3395, + 3400, + 3405, + 3410, + 3415, + 3420, + 3425, + 3430, + 3435, + 3440, + 3445, + 3450, + 3455, + 3460, + 3465, + 3470, + 3475, + 3480, + 3485, + 3490, + 3495, + 3500, + 3505, + 3510, + 3510.522507468857 + ], + "y": [ + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965, + 5.114173249321965 + ] + }, + { + "line": { + "width": 4 + }, + "mode": "lines", + "name": "Optimised", + "type": "scatter", + "x": [ + 0, + 5, + 10, + 15, + 20, + 25, + 30, + 35, + 40, + 45, + 50, + 55, + 60, + 65, + 70, + 75, + 80, + 85, + 90, + 95, + 100, + 105, + 110, + 115, + 120, + 125, + 130, + 135, + 140, + 145, + 150, + 155, + 160, + 165, + 170, + 175, + 180, + 185, + 190, + 195, + 200, + 205, + 210, + 215, + 220, + 225, + 230, + 235, + 240, + 245, + 250, + 255, + 260, + 265, + 270, + 275, + 280, + 285, + 290, + 295, + 300, + 305, + 310, + 315, + 320, + 325, + 330, + 335, + 340, + 345, + 350, + 355, + 360, + 365, + 370, + 375, + 380, + 385, + 390, + 395, + 400, + 405, + 410, + 415, + 420, + 425, + 430, + 435, + 440, + 445, + 450, + 455, + 460, + 465, + 470, + 475, + 480, + 485, + 490, + 495, + 500, + 505, + 510, + 515, + 520, + 525, + 530, + 535, + 540, + 545, + 550, + 555, + 560, + 565, + 570, + 575, + 580, + 585, + 590, + 595, + 600, + 605, + 610, + 615, + 620, + 625, + 630, + 635, + 640, + 645, + 650, + 655, + 660, + 665, + 670, + 675, + 680, + 685, + 690, + 695, + 700, + 705, + 710, + 715, + 720, + 725, + 730, + 735, + 740, + 745, + 750, + 755, + 760, + 765, + 770, + 775, + 780, + 785, + 790, + 795, + 800, + 805, + 810, + 815, + 820, + 825, + 830, + 835, + 840, + 845, + 850, + 855, + 860, + 865, + 870, + 875, + 880, + 885, + 890, + 895, + 900, + 905, + 910, + 915, + 920, + 925, + 930, + 935, + 940, + 945, + 950, + 955, + 960, + 965, + 970, + 975, + 980, + 985, + 990, + 995, + 1000, + 1005, + 1010, + 1015, + 1020, + 1025, + 1030, + 1035, + 1040, + 1045, + 1050, + 1055, + 1060, + 1065, + 1070, + 1075, + 1080, + 1085, + 1090, + 1095, + 1100, + 1105, + 1110, + 1115, + 1120, + 1125, + 1130, + 1135, + 1140, + 1145, + 1150, + 1155, + 1160, + 1165, + 1170, + 1175, + 1180, + 1185, + 1190, + 1195, + 1200, + 1205, + 1210, + 1215, + 1220, + 1225, + 1230, + 1235, + 1240, + 1245, + 1250, + 1255, + 1260, + 1265, + 1270, + 1275, + 1280, + 1285, + 1290, + 1295, + 1300, + 1305, + 1310, + 1315, + 1320, + 1325, + 1330, + 1335, + 1340, + 1345, + 1350, + 1355, + 1360, + 1365, + 1370, + 1375, + 1380, + 1385, + 1390, + 1395, + 1400, + 1405, + 1410, + 1415, + 1420, + 1425, + 1430, + 1435, + 1440, + 1445, + 1450, + 1455, + 1460, + 1465, + 1470, + 1475, + 1480, + 1485, + 1490, + 1495, + 1500, + 1505, + 1510, + 1515, + 1520, + 1525, + 1530, + 1535, + 1540, + 1545, + 1550, + 1555, + 1560, + 1565, + 1570, + 1575, + 1580, + 1585, + 1590, + 1595, + 1600, + 1605, + 1610, + 1615, + 1620, + 1625, + 1630, + 1635, + 1640, + 1645, + 1650, + 1655, + 1660, + 1665, + 1670, + 1675, + 1680, + 1685, + 1690, + 1695, + 1700, + 1705, + 1710, + 1715, + 1720, + 1725, + 1730, + 1735, + 1740, + 1745, + 1750, + 1755, + 1760, + 1765, + 1770, + 1775, + 1780, + 1785, + 1790, + 1795, + 1800, + 1805, + 1810, + 1815, + 1820, + 1825, + 1830, + 1835, + 1840, + 1845, + 1850, + 1855, + 1860, + 1865, + 1870, + 1875, + 1880, + 1885, + 1890, + 1895, + 1900, + 1905, + 1910, + 1915, + 1920, + 1925, + 1930, + 1935, + 1940, + 1945, + 1950, + 1955, + 1960, + 1965, + 1970, + 1975, + 1980, + 1985, + 1990, + 1995, + 2000, + 2005, + 2010, + 2015, + 2020, + 2025, + 2030, + 2035, + 2040, + 2045, + 2050, + 2055, + 2060, + 2065, + 2070, + 2075, + 2080, + 2085, + 2090, + 2095, + 2100, + 2105, + 2110, + 2115, + 2120, + 2125, + 2130, + 2135, + 2140, + 2145, + 2150, + 2155, + 2160, + 2165, + 2170, + 2175, + 2180, + 2185, + 2190, + 2195, + 2200, + 2205, + 2210, + 2215, + 2220, + 2225, + 2230, + 2235, + 2240, + 2245, + 2250, + 2255, + 2260, + 2265, + 2270, + 2275, + 2280, + 2285, + 2290, + 2295, + 2300, + 2305, + 2310, + 2315, + 2320, + 2325, + 2330, + 2335, + 2340, + 2345, + 2350, + 2355, + 2360, + 2365, + 2370, + 2375, + 2380, + 2385, + 2390, + 2395, + 2400, + 2405, + 2410, + 2415, + 2420, + 2425, + 2430, + 2435, + 2440, + 2445, + 2450, + 2455, + 2460, + 2465, + 2470, + 2475, + 2480, + 2485, + 2490, + 2495, + 2500, + 2505, + 2510, + 2515, + 2520, + 2525, + 2530, + 2535, + 2540, + 2545, + 2550, + 2555, + 2560, + 2565, + 2570, + 2575, + 2580, + 2585, + 2590, + 2595, + 2600, + 2605, + 2610, + 2615, + 2620, + 2625, + 2630, + 2635, + 2640, + 2645, + 2650, + 2655, + 2660, + 2665, + 2670, + 2675, + 2680, + 2685, + 2690, + 2695, + 2700, + 2705, + 2710, + 2715, + 2720, + 2725, + 2730, + 2735, + 2740, + 2745, + 2750, + 2755, + 2760, + 2765, + 2770, + 2775, + 2780, + 2785, + 2790, + 2795, + 2800, + 2805, + 2810, + 2815, + 2820, + 2825, + 2830, + 2835, + 2840, + 2845, + 2850, + 2855, + 2860, + 2865, + 2870, + 2875, + 2880, + 2885, + 2890, + 2895, + 2900, + 2905, + 2910, + 2915, + 2920, + 2925, + 2930, + 2935, + 2940, + 2945, + 2950, + 2955, + 2960, + 2965, + 2970, + 2975, + 2980, + 2985, + 2990, + 2995, + 3000, + 3005, + 3010, + 3015, + 3020, + 3025, + 3030, + 3035, + 3040, + 3045, + 3050, + 3055, + 3060, + 3065, + 3070, + 3075, + 3080, + 3085, + 3090, + 3095, + 3100, + 3105, + 3110, + 3115, + 3120, + 3125, + 3130, + 3135, + 3140, + 3145, + 3150, + 3155, + 3160, + 3165, + 3170, + 3175, + 3180, + 3185, + 3190, + 3195, + 3200, + 3205, + 3210, + 3215, + 3220, + 3225, + 3230, + 3235, + 3240, + 3245, + 3250, + 3255, + 3260, + 3265, + 3270, + 3275, + 3280, + 3285, + 3290, + 3295, + 3300, + 3305, + 3310, + 3315, + 3320, + 3325, + 3330, + 3335, + 3340, + 3345, + 3350, + 3355, + 3360, + 3365, + 3370, + 3375, + 3380, + 3385, + 3390, + 3395, + 3400, + 3405, + 3410, + 3415, + 3420, + 3425, + 3430, + 3435, + 3440, + 3445, + 3450, + 3455, + 3460, + 3465, + 3470, + 3475, + 3480, + 3485, + 3490, + 3495, + 3500, + 3505, + 3510, + 3510.522507468857 + ], + "y": [ + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081, + 5.106005404082081 + ] + } + ], + "layout": { + "autosize": false, + "height": 576, + "legend": { + "font": { + "size": 12 + }, + "x": 1, + "xanchor": "right", + "y": 1, + "yanchor": "top" + }, + "margin": { + "b": 10, + "l": 10, + "pad": 4, + "r": 10, + "t": 75 + }, + "showlegend": true, + "template": { + "data": { + "bar": [ + { + "error_x": { + "color": "#2a3f5f" + }, + "error_y": { + "color": "#2a3f5f" + }, + "marker": { + "line": { + "color": "#E5ECF6", + "width": 0.5 + }, + "pattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + } + }, + "type": "bar" + } + ], + "barpolar": [ + { + "marker": { + "line": { + "color": "#E5ECF6", + "width": 0.5 + }, + "pattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + } + }, + "type": "barpolar" + } + ], + "carpet": [ + { + "aaxis": { + "endlinecolor": "#2a3f5f", + "gridcolor": "white", + "linecolor": "white", + "minorgridcolor": "white", + "startlinecolor": "#2a3f5f" + }, + "baxis": { + "endlinecolor": "#2a3f5f", + "gridcolor": "white", + "linecolor": "white", + "minorgridcolor": "white", + "startlinecolor": "#2a3f5f" + }, + "type": "carpet" + } + ], + "choropleth": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "type": "choropleth" + } + ], + "contour": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "contour" + } + ], + "contourcarpet": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "type": "contourcarpet" + } + ], + "heatmap": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "heatmap" + } + ], + "heatmapgl": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "heatmapgl" + } + ], + "histogram": [ + { + "marker": { + "pattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + } + }, + "type": "histogram" + } + ], + "histogram2d": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "histogram2d" + } + ], + "histogram2dcontour": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "histogram2dcontour" + } + ], + "mesh3d": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "type": "mesh3d" + } + ], + "parcoords": [ + { + "line": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "parcoords" + } + ], + "pie": [ + { + "automargin": true, + "type": "pie" + } + ], + "scatter": [ + { + "fillpattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + }, + "type": "scatter" + } + ], + "scatter3d": [ + { + "line": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatter3d" + } + ], + "scattercarpet": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattercarpet" + } + ], + "scattergeo": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattergeo" + } + ], + "scattergl": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattergl" + } + ], + "scattermapbox": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattermapbox" + } + ], + "scatterpolar": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatterpolar" + } + ], + "scatterpolargl": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatterpolargl" + } + ], + "scatterternary": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatterternary" + } + ], + "surface": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "surface" + } + ], + "table": [ + { + "cells": { + "fill": { + "color": "#EBF0F8" + }, + "line": { + "color": "white" + } + }, + "header": { + "fill": { + "color": "#C8D4E3" + }, + "line": { + "color": "white" + } + }, + "type": "table" + } + ] + }, + "layout": { + "annotationdefaults": { + "arrowcolor": "#2a3f5f", + "arrowhead": 0, + "arrowwidth": 1 + }, + "autotypenumbers": "strict", + "coloraxis": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "colorscale": { + "diverging": [ + [ + 0, + "#8e0152" + ], + [ + 0.1, + "#c51b7d" + ], + [ + 0.2, + "#de77ae" + ], + [ + 0.3, + "#f1b6da" + ], + [ + 0.4, + "#fde0ef" + ], + [ + 0.5, + "#f7f7f7" + ], + [ + 0.6, + "#e6f5d0" + ], + [ + 0.7, + "#b8e186" + ], + [ + 0.8, + "#7fbc41" + ], + [ + 0.9, + "#4d9221" + ], + [ + 1, + "#276419" + ] + ], + "sequential": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "sequentialminus": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ] + }, + "colorway": [ + "#636efa", + "#EF553B", + "#00cc96", + "#ab63fa", + "#FFA15A", + "#19d3f3", + "#FF6692", + "#B6E880", + "#FF97FF", + "#FECB52" + ], + "font": { + "color": "#2a3f5f" + }, + "geo": { + "bgcolor": "white", + "lakecolor": "white", + "landcolor": "#E5ECF6", + "showlakes": true, + "showland": true, + "subunitcolor": "white" + }, + "hoverlabel": { + "align": "left" + }, + "hovermode": "closest", + "mapbox": { + "style": "light" + }, + "paper_bgcolor": "white", + "plot_bgcolor": "#E5ECF6", + "polar": { + "angularaxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + }, + "bgcolor": "#E5ECF6", + "radialaxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + } + }, + "scene": { + "xaxis": { + "backgroundcolor": "#E5ECF6", + "gridcolor": "white", + "gridwidth": 2, + "linecolor": "white", + "showbackground": true, + "ticks": "", + "zerolinecolor": "white" + }, + "yaxis": { + "backgroundcolor": "#E5ECF6", + "gridcolor": "white", + "gridwidth": 2, + "linecolor": "white", + "showbackground": true, + "ticks": "", + "zerolinecolor": "white" + }, + "zaxis": { + "backgroundcolor": "#E5ECF6", + "gridcolor": "white", + "gridwidth": 2, + "linecolor": "white", + "showbackground": true, + "ticks": "", + "zerolinecolor": "white" + } + }, + "shapedefaults": { + "line": { + "color": "#2a3f5f" + } + }, + "ternary": { + "aaxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + }, + "baxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + }, + "bgcolor": "#E5ECF6", + "caxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + } + }, + "title": { + "x": 0.05 + }, + "xaxis": { + "automargin": true, + "gridcolor": "white", + "linecolor": "white", + "ticks": "", + "title": { + "standoff": 15 + }, + "zerolinecolor": "white", + "zerolinewidth": 2 + }, + "yaxis": { + "automargin": true, + "gridcolor": "white", + "linecolor": "white", + "ticks": "", + "title": { + "standoff": 15 + }, + "zerolinecolor": "white", + "zerolinewidth": 2 + } + } + }, + "title": { + "text": "Optimised Comparison", + "x": 0.5 + }, + "width": 1024, + "xaxis": { + "tickfont": { + "size": 12 + }, + "title": { + "font": { + "size": 12 + }, + "text": "Time [s]" + } + }, + "yaxis": { + "tickfont": { + "size": 12 + }, + "title": { + "font": { + "size": 12 + }, + "text": "Current [A]" + } + } + } + } + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "if cost.update_capacity:\n", + " cost.problem._model.approximate_capacity(x)\n", + "pybop.quick_plot(x, cost, title=\"Optimised Comparison\");" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "ntIvAJmA04qD" + }, + "source": [ + "## Cost Landscape\n", + "\n", + "Finally, we can visualise the cost landscape and the path taken by the optimiser:\n" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 467 + }, + "id": "tJUJ80Ve04qD", + "outputId": "855fbaa2-1e09-4935-eb1a-8caf7f99eb75" + }, + "outputs": [ + { + "data": { + "application/vnd.plotly.v1+json": { + "config": { + "plotlyServerURL": "https://plot.ly" + }, + "data": [ + { + "type": "contour", + "x": [ + 0.000065, + 0.0000825, + 0.0001 + ], + "y": [ + 2e-6, + 5.5e-6, + 9e-6 + ], + "z": [ + [ + -411.2298715307869, + -384.5514548667088, + -360.1243749344209 + ], + [ + -389.45853879647154, + -375.6347882136447, + -353.0562652976013 + ], + [ + -297.51446838856344, + -361.0290869712437, + -342.44425194479135 + ] + ] + }, + { + "marker": { + "color": "red", + "line": { + "color": "midnightblue", + "width": 1 + }, + "showscale": false, + "size": 12, + "symbol": "x" + }, + "mode": "markers", + "showlegend": false, + "type": "scatter", + "x": [ + 0.00007503043032178322 + ], + "y": [ + 5.213989386373176e-6 + ] + }, + { + "marker": { + "color": [ + 0, + 0.034482758620689655, + 0.06896551724137931, + 0.10344827586206896, + 0.13793103448275862, + 0.1724137931034483, + 0.20689655172413793, + 0.2413793103448276, + 0.27586206896551724, + 0.3103448275862069, + 0.3448275862068966, + 0.3793103448275862, + 0.41379310344827586, + 0.4482758620689655, + 0.4827586206896552, + 0.5172413793103449, + 0.5517241379310345, + 0.5862068965517241, + 0.6206896551724138, + 0.6551724137931034, + 0.6896551724137931, + 0.7241379310344828, + 0.7586206896551724, + 0.7931034482758621, + 0.8275862068965517, + 0.8620689655172413, + 0.896551724137931, + 0.9310344827586208, + 0.9655172413793104 + ], + "colorscale": [ + [ + 0, + "rgb(255,255,229)" + ], + [ + 0.125, + "rgb(255,247,188)" + ], + [ + 0.25, + "rgb(254,227,145)" + ], + [ + 0.375, + "rgb(254,196,79)" + ], + [ + 0.5, + "rgb(254,153,41)" + ], + [ + 0.625, + "rgb(236,112,20)" + ], + [ + 0.75, + "rgb(204,76,2)" + ], + [ + 0.875, + "rgb(153,52,4)" + ], + [ + 1, + "rgb(102,37,6)" + ] + ], + "showscale": false + }, + "mode": "markers", + "showlegend": false, + "type": "scatter", + "x": [ + 0.00007503043032178322, + 0.00009802702542268024, + 0.00009961571599618376, + 0.00009696983570692418, + 0.00008395762998574042, + 0.00008911496694118421, + 0.00007515805406653971, + 0.00009204596401393966, + 0.00008057020125259683, + 0.00008386969615225464, + 0.00007254052989342064, + 0.00007935751972288362, + 0.0000750001946075022, + 0.00008577128815953699, + 0.00006982413489087496, + 0.00006640947572511218, + 0.00006612369542972039, + 0.00007061848272252423, + 0.00007472708479047697, + 0.0000738910819935762, + 0.00006523593718322669, + 0.00008483575733667275, + 0.00007033181106801437, + 0.00006562638003355986, + 0.00007040332572662624, + 0.00007229780199113263, + 0.00008264638468653952, + 0.00007440284793077438, + 0.00006533356141242215 + ], + "y": [ + 5.213989386373176e-6, + 6.987440787316741e-6, + 2.64453786570932e-6, + 3.389077859556841e-6, + 3.817668779124727e-6, + 5.687676535722202e-6, + 5.269200008333387e-6, + 5.489880994504372e-6, + 3.4059598243459385e-6, + 5.8057531438234875e-6, + 5.433979620135304e-6, + 5.189789142120092e-6, + 5.197278021663849e-6, + 3.6326734178928816e-6, + 5.035835393184889e-6, + 8.106091461340278e-6, + 6.696212143249081e-6, + 4.942164056645866e-6, + 5.279709597217932e-6, + 2.0713500143226936e-6, + 5.9660067637513625e-6, + 4.586549463916286e-6, + 6.3368993449299045e-6, + 5.485105039355238e-6, + 5.140156754229586e-6, + 5.3613255870193305e-6, + 2.844730658911555e-6, + 3.36938348462524e-6, + 5.694287622969207e-6 + ] + } + ], + "layout": { + "height": 600, + "template": { + "data": { + "bar": [ + { + "error_x": { + "color": "#2a3f5f" + }, + "error_y": { + "color": "#2a3f5f" + }, + "marker": { + "line": { + "color": "#E5ECF6", + "width": 0.5 + }, + "pattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + } + }, + "type": "bar" + } + ], + "barpolar": [ + { + "marker": { + "line": { + "color": "#E5ECF6", + "width": 0.5 + }, + "pattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + } + }, + "type": "barpolar" + } + ], + "carpet": [ + { + "aaxis": { + "endlinecolor": "#2a3f5f", + "gridcolor": "white", + "linecolor": "white", + "minorgridcolor": "white", + "startlinecolor": "#2a3f5f" + }, + "baxis": { + "endlinecolor": "#2a3f5f", + "gridcolor": "white", + "linecolor": "white", + "minorgridcolor": "white", + "startlinecolor": "#2a3f5f" + }, + "type": "carpet" + } + ], + "choropleth": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "type": "choropleth" + } + ], + "contour": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "contour" + } + ], + "contourcarpet": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "type": "contourcarpet" + } + ], + "heatmap": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "heatmap" + } + ], + "heatmapgl": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "heatmapgl" + } + ], + "histogram": [ + { + "marker": { + "pattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + } + }, + "type": "histogram" + } + ], + "histogram2d": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "histogram2d" + } + ], + "histogram2dcontour": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "histogram2dcontour" + } + ], + "mesh3d": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "type": "mesh3d" + } + ], + "parcoords": [ + { + "line": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "parcoords" + } + ], + "pie": [ + { + "automargin": true, + "type": "pie" + } + ], + "scatter": [ + { + "fillpattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + }, + "type": "scatter" + } + ], + "scatter3d": [ + { + "line": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatter3d" + } + ], + "scattercarpet": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattercarpet" + } + ], + "scattergeo": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattergeo" + } + ], + "scattergl": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattergl" + } + ], + "scattermapbox": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattermapbox" + } + ], + "scatterpolar": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatterpolar" + } + ], + "scatterpolargl": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatterpolargl" + } + ], + "scatterternary": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatterternary" + } + ], + "surface": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "surface" + } + ], + "table": [ + { + "cells": { + "fill": { + "color": "#EBF0F8" + }, + "line": { + "color": "white" + } + }, + "header": { + "fill": { + "color": "#C8D4E3" + }, + "line": { + "color": "white" + } + }, + "type": "table" + } + ] + }, + "layout": { + "annotationdefaults": { + "arrowcolor": "#2a3f5f", + "arrowhead": 0, + "arrowwidth": 1 + }, + "autotypenumbers": "strict", + "coloraxis": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "colorscale": { + "diverging": [ + [ + 0, + "#8e0152" + ], + [ + 0.1, + "#c51b7d" + ], + [ + 0.2, + "#de77ae" + ], + [ + 0.3, + "#f1b6da" + ], + [ + 0.4, + "#fde0ef" + ], + [ + 0.5, + "#f7f7f7" + ], + [ + 0.6, + "#e6f5d0" + ], + [ + 0.7, + "#b8e186" + ], + [ + 0.8, + "#7fbc41" + ], + [ + 0.9, + "#4d9221" + ], + [ + 1, + "#276419" + ] + ], + "sequential": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "sequentialminus": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ] + }, + "colorway": [ + "#636efa", + "#EF553B", + "#00cc96", + "#ab63fa", + "#FFA15A", + "#19d3f3", + "#FF6692", + "#B6E880", + "#FF97FF", + "#FECB52" + ], + "font": { + "color": "#2a3f5f" + }, + "geo": { + "bgcolor": "white", + "lakecolor": "white", + "landcolor": "#E5ECF6", + "showlakes": true, + "showland": true, + "subunitcolor": "white" + }, + "hoverlabel": { + "align": "left" + }, + "hovermode": "closest", + "mapbox": { + "style": "light" + }, + "paper_bgcolor": "white", + "plot_bgcolor": "#E5ECF6", + "polar": { + "angularaxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + }, + "bgcolor": "#E5ECF6", + "radialaxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + } + }, + "scene": { + "xaxis": { + "backgroundcolor": "#E5ECF6", + "gridcolor": "white", + "gridwidth": 2, + "linecolor": "white", + "showbackground": true, + "ticks": "", + "zerolinecolor": "white" + }, + "yaxis": { + "backgroundcolor": "#E5ECF6", + "gridcolor": "white", + "gridwidth": 2, + "linecolor": "white", + "showbackground": true, + "ticks": "", + "zerolinecolor": "white" + }, + "zaxis": { + "backgroundcolor": "#E5ECF6", + "gridcolor": "white", + "gridwidth": 2, + "linecolor": "white", + "showbackground": true, + "ticks": "", + "zerolinecolor": "white" + } + }, + "shapedefaults": { + "line": { + "color": "#2a3f5f" + } + }, + "ternary": { + "aaxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + }, + "baxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + }, + "bgcolor": "#E5ECF6", + "caxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + } + }, + "title": { + "x": 0.05 + }, + "xaxis": { + "automargin": true, + "gridcolor": "white", + "linecolor": "white", + "ticks": "", + "title": { + "standoff": 15 + }, + "zerolinecolor": "white", + "zerolinewidth": 2 + }, + "yaxis": { + "automargin": true, + "gridcolor": "white", + "linecolor": "white", + "ticks": "", + "title": { + "standoff": 15 + }, + "zerolinecolor": "white", + "zerolinewidth": 2 + } + } + }, + "title": { + "text": "Cost Landscape", + "x": 0.5, + "y": 0.9 + }, + "width": 600, + "xaxis": { + "range": [ + 0.000065, + 0.0001 + ], + "title": { + "text": "Positive electrode thickness [m]" + } + }, + "yaxis": { + "range": [ + 2e-6, + 9e-6 + ], + "title": { + "text": "Positive particle radius [m]" + } + } + } + } + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "if len(x) == 2:\n", + " pybop.plot_cost2d(cost, optim=optim, steps=3)" + ] + } + ], + "metadata": { + "colab": { + "provenance": [] + }, + "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.11.4" + }, + "widgets": { + "application/vnd.jupyter.widget-state+json": { + "06f2374f91c8455bb63252092512f2ed": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "2.0.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "2.0.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border_bottom": null, + "border_left": null, + "border_right": null, + "border_top": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "423bffea3a1c42b49a9ad71218e5811b": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "2.0.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "2.0.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border_bottom": null, + "border_left": null, + "border_right": null, + "border_top": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "56ff19291e464d63b23e63b8e2ac9ea3": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "2.0.0", + "model_name": "SliderStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "2.0.0", + "_model_name": "SliderStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "StyleView", + "description_width": "", + "handle_color": null + } + }, + "646a8670cb204a31bb56bc2380898093": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "2.0.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "2.0.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border_bottom": null, + "border_left": null, + "border_right": null, + "border_top": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "7d46516469314b88be3500e2afcafcf6": { + "model_module": "@jupyter-widgets/output", + "model_module_version": "1.0.0", + "model_name": "OutputModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/output", + "_model_module_version": "1.0.0", + "_model_name": "OutputModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/output", + "_view_module_version": "1.0.0", + "_view_name": "OutputView", + "layout": "IPY_MODEL_646a8670cb204a31bb56bc2380898093", + "msg_id": "", + "outputs": [], + "tabbable": null, + "tooltip": null + } + }, + "8d003c14da5f4fa68284b28c15cee6e6": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "2.0.0", + "model_name": "VBoxModel", + "state": { + "_dom_classes": [ + "widget-interact" + ], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "2.0.0", + "_model_name": "VBoxModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "2.0.0", + "_view_name": "VBoxView", + "box_style": "", + "children": [ + "IPY_MODEL_aef2fa7adcc14ad0854b73d5910ae3b4", + "IPY_MODEL_7d46516469314b88be3500e2afcafcf6" + ], + "layout": "IPY_MODEL_423bffea3a1c42b49a9ad71218e5811b", + "tabbable": null, + "tooltip": null + } + }, + "aef2fa7adcc14ad0854b73d5910ae3b4": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "2.0.0", + "model_name": "FloatSliderModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "2.0.0", + "_model_name": "FloatSliderModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "2.0.0", + "_view_name": "FloatSliderView", + "behavior": "drag-tap", + "continuous_update": true, + "description": "t", + "description_allow_html": false, + "disabled": false, + "layout": "IPY_MODEL_06f2374f91c8455bb63252092512f2ed", + "max": 1.1333333333333333, + "min": 0, + "orientation": "horizontal", + "readout": true, + "readout_format": ".2f", + "step": 0.011333333333333332, + "style": "IPY_MODEL_56ff19291e464d63b23e63b8e2ac9ea3", + "tabbable": null, + "tooltip": null, + "value": 0 + } + } + } + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} diff --git a/examples/scripts/exp_UKF.py b/examples/scripts/exp_UKF.py new file mode 100644 index 00000000..e4cf24f3 --- /dev/null +++ b/examples/scripts/exp_UKF.py @@ -0,0 +1,116 @@ +import pybop +import pybamm +import numpy as np +from examples.standalone.model import ExponentialDecay + +# Parameter set and model definition +parameter_set = pybamm.ParameterValues({"k": "[input]", "y0": "[input]"}) +model = ExponentialDecay(parameter_set=parameter_set, n_states=1) +x0 = np.array([0.1, 1.0]) + +# Fitting parameters +parameters = [ + pybop.Parameter( + "k", + prior=pybop.Gaussian(0.1, 0.05), + bounds=[0, 1], + ), + pybop.Parameter( + "y0", + prior=pybop.Gaussian(1, 0.05), + bounds=[0, 3], + ), +] + +# Verification: save fixed inputs for testing +inputs = dict() +for i, param in enumerate(parameters): + inputs[param.name] = x0[i] + +# Make a prediction with measurement noise +sigma = 1e-2 +t_eval = np.linspace(0, 20, 10) +values = model.predict(t_eval=t_eval, inputs=inputs) +values = values["2y"].data +corrupt_values = values + np.random.normal(0, sigma, len(t_eval)) + +# Verification step: compute the analytical solution for 2y +expected_values = 2 * inputs["y0"] * np.exp(-inputs["k"] * t_eval) + +# Verification step: make another prediction using the Observer class +model.build(parameters=parameters) +simulator = pybop.Observer(parameters, model, signal=["2y"], x0=x0) +simulator._time_data = t_eval +measurements = simulator.evaluate(x0) +measurements = measurements[:, 0] + +# Verification step: Compare by plotting +go = pybop.PlotlyManager().go +line1 = go.Scatter(x=t_eval, y=corrupt_values, name="Corrupt values", mode="markers") +line2 = go.Scatter( + x=t_eval, y=expected_values, name="Expected trajectory", mode="lines" +) +line3 = go.Scatter(x=t_eval, y=measurements, name="Observed values", mode="markers") +fig = go.Figure(data=[line1, line2, line3]) + +# Form dataset +dataset = pybop.Dataset( + { + "Time [s]": t_eval, + "Current function [A]": 0 * t_eval, # placeholder + "2y": corrupt_values, + } +) + +# Build the model to get the number of states +model.build(dataset=dataset.data, parameters=parameters) + +# Define the UKF observer +signal = ["2y"] +n_states = model.n_states +n_signals = len(signal) +covariance = np.diag([sigma**2] * n_states) +process_noise = np.diag([1e-6] * n_states) +measurement_noise = np.diag([sigma**2] * n_signals) +observer = pybop.UnscentedKalmanFilterObserver( + parameters, + model, + covariance, + process_noise, + measurement_noise, + dataset, + signal=signal, + x0=x0, +) + +# Verification step: Find the maximum likelihood estimate given the true parameters +estimation = observer.evaluate(x0) +estimation = estimation[:, 0] + +# Verification step: Add the estimate to the plot +line4 = go.Scatter(x=t_eval, y=estimation, name="Estimated trajectory", mode="lines") +fig.add_trace(line4) +fig.show() + +# Generate problem, cost function, and optimisation class +cost = pybop.ObserverCost(observer) +optim = pybop.Optimisation(cost, optimiser=pybop.CMAES, verbose=True) + +# Run optimisation +x, final_cost = optim.run() +print("Estimated parameters:", x) + +# Plot the timeseries output (requires model that returns Voltage) +pybop.quick_plot(x, cost, title="Optimised Comparison") + +# Plot convergence +pybop.plot_convergence(optim) + +# Plot the parameter traces +pybop.plot_parameters(optim) + +# Plot the cost landscape +pybop.plot_cost2d(cost, steps=15) + +# Plot the cost landscape with optimisation path +pybop.plot_cost2d(cost, optim=optim, steps=15) diff --git a/examples/scripts/spm_CMAES.py b/examples/scripts/spm_CMAES.py index e24eb7a1..f5728019 100644 --- a/examples/scripts/spm_CMAES.py +++ b/examples/scripts/spm_CMAES.py @@ -8,14 +8,16 @@ # Fitting parameters parameters = [ pybop.Parameter( - "Negative electrode active material volume fraction", - prior=pybop.Gaussian(0.6, 0.05), - bounds=[0.5, 0.8], + "Negative particle radius [m]", + prior=pybop.Gaussian(6e-06, 0.1e-6), + bounds=[1e-6, 9e-6], + true_value=parameter_set["Negative particle radius [m]"], ), pybop.Parameter( - "Positive electrode active material volume fraction", - prior=pybop.Gaussian(0.48, 0.05), - bounds=[0.4, 0.7], + "Positive particle radius [m]", + prior=pybop.Gaussian(4.5e-06, 0.1e-6), + bounds=[1e-6, 9e-6], + true_value=parameter_set["Positive particle radius [m]"], ), ] @@ -42,6 +44,13 @@ # Run the optimisation x, final_cost = optim.run() +print( + "True parameters:", + [ + parameters[0].true_value, + parameters[1].true_value, + ], +) print("Estimated parameters:", x) # Plot the timeseries output @@ -57,5 +66,4 @@ pybop.plot_cost2d(cost, steps=15) # Plot the cost landscape with optimisation path and updated bounds -bounds = np.array([[0.55, 0.75], [0.48, 0.68]]) -pybop.plot_cost2d(cost, optim=optim, bounds=bounds, steps=15) +pybop.plot_cost2d(cost, optim=optim, steps=15) diff --git a/examples/scripts/spm_UKF.py b/examples/scripts/spm_UKF.py new file mode 100644 index 00000000..7128293c --- /dev/null +++ b/examples/scripts/spm_UKF.py @@ -0,0 +1,82 @@ +import pybop +import numpy as np + +# Parameter set and model definition +parameter_set = pybop.ParameterSet.pybamm("Chen2020") +model = pybop.lithium_ion.SPM(parameter_set=parameter_set) + +# Fitting parameters +parameters = [ + pybop.Parameter( + "Negative electrode active material volume fraction", + prior=pybop.Gaussian(0.6, 0.05), + bounds=[0.5, 0.8], + ), + pybop.Parameter( + "Positive electrode active material volume fraction", + prior=pybop.Gaussian(0.48, 0.05), + bounds=[0.4, 0.7], + ), +] + +# Make a prediction with measurement noise +sigma = 0.001 +t_eval = np.arange(0, 300, 2) +values = model.predict(t_eval=t_eval) +corrupt_values = values["Voltage [V]"].data + np.random.normal(0, sigma, len(t_eval)) + +# Form dataset +dataset = pybop.Dataset( + { + "Time [s]": t_eval, + "Current function [A]": values["Current [A]"].data, + "Voltage [V]": corrupt_values, + } +) + +# Build the model to get the number of states +model.build(dataset=dataset.data, parameters=parameters) + +# Define the UKF observer, setting the particle boundaries as uncertain states +signal = ["Voltage [V]"] +n_states = model.n_states +n_signals = len(signal) +covariance = np.diag([0] * 19 + [sigma**2] + [0] * 19 + [sigma**2]) +process_noise = np.diag([0] * 19 + [1e-6] + [0] * 19 + [1e-6]) +measurement_noise = np.diag([sigma**2]) +observer = pybop.UnscentedKalmanFilterObserver( + parameters, + model, + covariance, + process_noise, + measurement_noise, + dataset, + signal=signal, +) + +# Generate problem, cost function, and optimisation class +cost = pybop.ObserverCost(observer) +optim = pybop.Optimisation(cost, optimiser=pybop.PSO, verbose=True) + +# Parameter identification using the current observer implementation is very slow +# so let's restrict the number of iterations and reduce the number of plots +optim.set_max_iterations(5) + +# Run optimisation +x, final_cost = optim.run() +print("Estimated parameters:", x) + +# Plot the timeseries output (requires model that returns Voltage) +pybop.quick_plot(x, cost, title="Optimised Comparison") + +# # Plot convergence +# pybop.plot_convergence(optim) + +# # Plot the parameter traces +# pybop.plot_parameters(optim) + +# # Plot the cost landscape +# pybop.plot_cost2d(cost, steps=5) + +# # Plot the cost landscape with optimisation path +# pybop.plot_cost2d(cost, optim=optim, steps=5) diff --git a/examples/scripts/spme_max_energy.py b/examples/scripts/spme_max_energy.py index 96c0eeaf..ebd5bebf 100644 --- a/examples/scripts/spme_max_energy.py +++ b/examples/scripts/spme_max_energy.py @@ -1,9 +1,5 @@ import pybop -import numpy as np -import warnings -## NOTE: This is a brittle example, the classes and methods below will be -## integrated into pybop in a future release. # A design optimisation example loosely based on work by L.D. Couto # available at https://doi.org/10.1016/j.energy.2022.125966. @@ -14,6 +10,12 @@ # electrode widths, particle radii, volume fractions and # separator width. +# NOTE: This script can be easily adjusted to consider the volumetric +# (instead of gravimetric) energy density by changing the line which +# defines the cost and changing the output to: +# print(f"Initial volumetric energy density: {-cost(cost.x0):.2f} Wh.m-3") +# print(f"Optimised volumetric energy density: {-final_cost:.2f} Wh.m-3") + # Define parameter set and model parameter_set = pybop.ParameterSet.pybamm("Chen2020") model = pybop.lithium_ion.SPMe(parameter_set=parameter_set) @@ -31,90 +33,6 @@ bounds=[2e-06, 9e-06], ), ] -# Define stoichiometries -sto = model._electrode_soh.get_min_max_stoichiometries(parameter_set) -mean_sto_neg = np.mean(sto[0:2]) -mean_sto_pos = np.mean(sto[2:4]) - - -# Define functions -def nominal_capacity( - x, model -): # > 50% of the simulation time is spent in this function (~0.7 sec per iteration vs ~1.1 for the forward simulation) - """ - Update the nominal capacity based on the theoretical energy density and an - average voltage. - """ - inputs = { - key: x[i] for i, key in enumerate([param.name for param in model.parameters]) - } - model._parameter_set.update(inputs) - - theoretical_energy = model._electrode_soh.calculate_theoretical_energy( # All of the computational time is in this line (~0.7) - model._parameter_set - ) - average_voltage = model._parameter_set["Positive electrode OCP [V]"]( - mean_sto_pos - ) - model._parameter_set["Negative electrode OCP [V]"](mean_sto_neg) - - theoretical_capacity = theoretical_energy / average_voltage - model._parameter_set.update({"Nominal cell capacity [A.h]": theoretical_capacity}) - - -def cell_mass(parameter_set): # This is very low compute time - """ - Compute the total cell mass [kg] for the current parameter set. - """ - - # Approximations due to SPM(e) parameter set limitations - electrolyte_density = parameter_set["Separator density [kg.m-3]"] - - # Electrode mass densities [kg.m-3] - positive_mass_density = ( - parameter_set["Positive electrode active material volume fraction"] - * parameter_set["Positive electrode density [kg.m-3]"] - ) - +(parameter_set["Positive electrode porosity"] * electrolyte_density) - negative_mass_density = ( - parameter_set["Negative electrode active material volume fraction"] - * parameter_set["Negative electrode density [kg.m-3]"] - ) - +(parameter_set["Negative electrode porosity"] * electrolyte_density) - - # Area densities [kg.m-2] - positive_area_density = ( - parameter_set["Positive electrode thickness [m]"] * positive_mass_density - ) - negative_area_density = ( - parameter_set["Negative electrode thickness [m]"] * negative_mass_density - ) - separator_area_density = ( - parameter_set["Separator thickness [m]"] - * parameter_set["Separator porosity"] - * parameter_set["Separator density [kg.m-3]"] - ) - positive_current_collector_area_density = ( - parameter_set["Positive current collector thickness [m]"] - * parameter_set["Positive current collector density [kg.m-3]"] - ) - negative_current_collector_area_density = ( - parameter_set["Negative current collector thickness [m]"] - * parameter_set["Negative current collector density [kg.m-3]"] - ) - - # Cross-sectional area [m2] - cross_sectional_area = ( - parameter_set["Electrode height [m]"] * parameter_set["Electrode width [m]"] - ) - - return cross_sectional_area * ( - positive_area_density - + separator_area_density - + negative_area_density - + positive_current_collector_area_density - + negative_current_collector_area_density - ) - # Define test protocol experiment = pybop.Experiment( @@ -128,58 +46,12 @@ def cell_mass(parameter_set): # This is very low compute time model, parameters, experiment, signal=signal, init_soc=init_soc ) -# Update the C-rate and the example dataset -nominal_capacity(problem.x0, model) -sol = problem.evaluate(problem.x0) -problem._time_data = sol[:, -1] -problem._target = sol[:, 0:-1] -dt = sol[1, -1] - sol[0, -1] - - -# Define cost function as a subclass -class GravimetricEnergyDensity(pybop.BaseCost): - """ - Defines the (negative*) gravimetric energy density corresponding to a - normalised 1C discharge from upper to lower voltage limits. - *The energy density is maximised by minimising the negative energy density. - """ - - def __init__(self, problem): - super().__init__(problem) - - def _evaluate(self, x, grad=None): - """ - Compute the cost - """ - with warnings.catch_warnings(record=True) as w: - # Update the C-rate and run the simulation - nominal_capacity(x, self.problem._model) - sol = self.problem.evaluate(x) - - if any(w) and issubclass(w[-1].category, UserWarning): - # Catch infeasible parameter combinations e.g. E_Li > Q_p - print(f"ignoring this sample due to: {w[-1].message}") - return np.inf - - else: - voltage = sol[:, 0] - current = sol[:, 1] - gravimetric_energy_density = -np.trapz( - voltage * current, dx=dt - ) / ( # trapz over-estimates compares to pybamm (~0.5%) - 3600 * cell_mass(self.problem._model._parameter_set) - ) - # Return the negative energy density, as the optimiser minimises - # this function, to carry out maximisation of the energy density - return gravimetric_energy_density - - -# Generate cost function and optimisation class -cost = GravimetricEnergyDensity(problem) +# Generate cost function and optimisation class: +cost = pybop.GravimetricEnergyDensity(problem) optim = pybop.Optimisation( cost, optimiser=pybop.PSO, verbose=True, allow_infeasible_solutions=False ) -optim.set_max_iterations(5) +optim.set_max_iterations(15) # Run optimisation x, final_cost = optim.run() @@ -188,7 +60,8 @@ def _evaluate(self, x, grad=None): print(f"Optimised gravimetric energy density: {-final_cost:.2f} Wh.kg-1") # Plot the timeseries output -nominal_capacity(x, cost.problem._model) +if cost.update_capacity: + cost.problem._model.approximate_capacity(x) pybop.quick_plot(x, cost, title="Optimised Comparison") # Plot the cost landscape with optimisation path diff --git a/examples/standalone/model.py b/examples/standalone/model.py new file mode 100644 index 00000000..2295d080 --- /dev/null +++ b/examples/standalone/model.py @@ -0,0 +1,59 @@ +import pybamm + +from pybop.models.base_model import BaseModel + + +class ExponentialDecay(BaseModel): + """ + Exponential decay model with two parameters y0 and k + + dy/dt = -ky + y(0) = y0 + + """ + + def __init__( + self, + name: str = "Constant Model", + parameter_set: pybamm.ParameterValues = None, + n_states: int = 1, + ): + super().__init__() + self.n_states = n_states + if n_states < 1: + raise ValueError("The number of states (n_states) must be greater than 0") + self.pybamm_model = pybamm.BaseModel() + ys = [pybamm.Variable(f"y_{i}") for i in range(n_states)] + k = pybamm.Parameter("k") + y0 = pybamm.Parameter("y0") + self.pybamm_model.rhs = {y: -k * y for y in ys} + self.pybamm_model.initial_conditions = {y: y0 for y in ys} + self.pybamm_model.variables = {"y_0": ys[0], "2y": 2 * ys[0]} + + default_parameter_values = pybamm.ParameterValues( + { + "k": 0.1, + "y0": 1, + } + ) + + self._unprocessed_model = self.pybamm_model + self.name = name + + self.default_parameter_values = ( + default_parameter_values if parameter_set is None else parameter_set + ) + self._parameter_set = self.default_parameter_values + self._unprocessed_parameter_set = self._parameter_set + + self.geometry = self.pybamm_model.default_geometry + self.submesh_types = self.pybamm_model.default_submesh_types + self.var_pts = self.pybamm_model.default_var_pts + self.spatial_methods = self.pybamm_model.default_spatial_methods + self.solver = pybamm.CasadiSolver(mode="fast") + self._model_with_set_params = None + self._built_model = None + self._built_initial_soc = None + self._mesh = None + self._disc = None + self.rebuild_parameters = {} diff --git a/noxfile.py b/noxfile.py index ddb7bd19..d27e81b1 100644 --- a/noxfile.py +++ b/noxfile.py @@ -1,20 +1,29 @@ +import os import nox + # nox options nox.options.reuse_existing_virtualenvs = True +nox.options.venv_backend = "virtualenv" + +# Environment variables to control CI behaviour for nox sessions +PYBOP_SCHEDULED = int(os.environ.get("PYBOP_SCHEDULED", 0)) +PYBAMM_VERSION = os.environ.get("PYBAMM_VERSION", None) @nox.session def unit(session): - session.run_always("pip", "install", "-e", ".[all]") - session.install("pytest", "pytest-mock") + session.install("-e", ".[all,dev]", silent=False) + if PYBOP_SCHEDULED: + session.run("pip", "install", f"pybamm=={PYBAMM_VERSION}", silent=False) session.run("pytest", "--unit") @nox.session def coverage(session): - session.run_always("pip", "install", "-e", ".[all]") - session.install("pytest", "pytest-cov", "pytest-mock") + session.install("-e", ".[all,dev]", silent=False) + if PYBOP_SCHEDULED: + session.run("pip", "install", f"pybamm=={PYBAMM_VERSION}", silent=False) session.run( "pytest", "--unit", @@ -27,9 +36,10 @@ def coverage(session): @nox.session def notebooks(session): """Run the examples tests for Jupyter notebooks.""" - session.run_always("pip", "install", "-e", ".[all]") - session.install("pytest", "nbmake") - session.run("pytest", "--nbmake", "--examples", "examples/", external=True) + session.install("-e", ".[all,dev]", silent=False) + if PYBOP_SCHEDULED: + session.run("pip", "install", f"pybamm=={PYBAMM_VERSION}", silent=False) + session.run("pytest", "--nbmake", "--examples", "examples/") @nox.session @@ -39,7 +49,7 @@ def docs(session): Credit: PyBaMM Team """ envbindir = session.bin - session.install("-e", ".[all,docs]") + session.install("-e", ".[all,docs]", silent=False) session.chdir("docs") # Local development if session.interactive: diff --git a/pybop/__init__.py b/pybop/__init__.py index 6093d9d9..b623add9 100644 --- a/pybop/__init__.py +++ b/pybop/__init__.py @@ -5,14 +5,14 @@ # This file is adapted from Pints # (see https://github.com/pints-team/pints) # - +from __future__ import annotations import sys from os import path # # Version info # -from pybop.version import __version__ +from pybop._version import __version__ # # Constants @@ -26,7 +26,17 @@ # # Cost function class # -from ._costs import BaseCost, RootMeanSquaredError, SumSquaredError +from .costs.base_cost import BaseCost +from .costs.fitting_costs import ( + RootMeanSquaredError, + SumSquaredError, + ObserverCost, +) +from .costs.design_costs import ( + DesignCost, + GravimetricEnergyDensity, + VolumetricEnergyDensity, +) # # Dataset class @@ -39,6 +49,8 @@ from .models.base_model import BaseModel from .models import lithium_ion from .models import empirical +from .models.base_model import TimeSeriesState +from .models.base_model import Inputs # # Experiment class @@ -77,6 +89,12 @@ # from ._problem import FittingProblem, DesignProblem +# +# Observer classes +# +from .observers.unscented_kalman import UnscentedKalmanFilterObserver +from .observers.observer import Observer + # # Plotting class # diff --git a/pybop/_dataset.py b/pybop/_dataset.py index 1263ace3..ae3ad125 100644 --- a/pybop/_dataset.py +++ b/pybop/_dataset.py @@ -35,6 +35,9 @@ def __init__(self, data_dictionary): self.data = data_dictionary self.names = self.data.keys() + def __getitem__(self, key): + return self.data[key] + def __repr__(self): """ Return a string representation of the Dataset instance. diff --git a/pybop/_problem.py b/pybop/_problem.py index 67d518e1..bd829029 100644 --- a/pybop/_problem.py +++ b/pybop/_problem.py @@ -13,6 +13,8 @@ class BaseProblem: The model to be used for the problem (default: None). check_model : bool, optional Flag to indicate if the model should be checked (default: True). + signal: List[str] + The signal to observe. init_soc : float, optional Initial state of charge (default: None). x0 : np.ndarray, optional @@ -59,7 +61,7 @@ def __init__( # Add the initial values to the parameter definitions for i, param in enumerate(self.parameters): - param.update(value=self.x0[i]) + param.update(initial_value=self.x0[i]) def evaluate(self, x): """ @@ -79,7 +81,8 @@ def evaluate(self, x): def evaluateS1(self, x): """ - Evaluate the model with the given parameters and return the signal and its derivatives. + Evaluate the model with the given parameters and return the signal and + its derivatives. Parameters ---------- @@ -115,6 +118,10 @@ def target(self): """ return self._target + @property + def model(self): + return self._model + class FittingProblem(BaseProblem): """ @@ -128,8 +135,8 @@ class FittingProblem(BaseProblem): The model to fit. parameters : list List of parameters for the problem. - dataset : list - List of data objects to fit the model to. + dataset : Dataset + Dataset object containing the data to fit the model to. signal : str, optional The signal to fit (default: "Voltage [V]"). """ @@ -146,11 +153,12 @@ def __init__( ): super().__init__(parameters, model, check_model, signal, init_soc, x0) self._dataset = dataset.data + self.x = self.x0 # Check that the dataset contains time and current for name in ["Time [s]", "Current function [A]"] + self.signal: if name not in self._dataset: - raise ValueError(f"expected {name} in list of dataset") + raise ValueError(f"Expected {name} in list of dataset") self._time_data = self._dataset["Time [s]"] self.n_time_data = len(self._time_data) @@ -197,6 +205,12 @@ def evaluate(self, x): y : np.ndarray The model output y(t) simulated with inputs x. """ + if (x != self.x).any() and self._model.matched_parameters: + for i, param in enumerate(self.parameters): + param.update(value=x[i]) + + self._model.rebuild(parameters=self.parameters) + self.x = x y = np.asarray(self._model.simulate(inputs=x, t_eval=self._time_data)) @@ -217,6 +231,10 @@ def evaluateS1(self, x): A tuple containing the simulation result y(t) and the sensitivities dy/dx(t) evaluated with given inputs x. """ + if self._model.matched_parameters: + raise RuntimeError( + "Gradient not available when using geometric parameters." + ) y, dy = self._model.simulateS1( inputs=x, diff --git a/pybop/_version.py b/pybop/_version.py new file mode 100644 index 00000000..44f0ff34 --- /dev/null +++ b/pybop/_version.py @@ -0,0 +1,3 @@ +import importlib.metadata + +__version__ = importlib.metadata.version("pybop") diff --git a/pybop/costs/__init__.py b/pybop/costs/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pybop/costs/base_cost.py b/pybop/costs/base_cost.py new file mode 100644 index 00000000..171e09b3 --- /dev/null +++ b/pybop/costs/base_cost.py @@ -0,0 +1,142 @@ +class BaseCost: + """ + Base class for defining cost functions. + + This class is intended to be subclassed to create specific cost functions + for evaluating model predictions against a set of data. The cost function + quantifies the goodness-of-fit between the model predictions and the + observed data, with a lower cost value indicating a better fit. + + Parameters + ---------- + problem : object + A problem instance containing the data and functions necessary for + evaluating the cost function. + _target : array-like + An array containing the target data to fit. + x0 : array-like + The initial guess for the model parameters. + bounds : tuple + The bounds for the model parameters. + n_parameters : int + The number of parameters in the model. + n_outputs : int + The number of outputs in the model. + """ + + def __init__(self, problem): + self.problem = problem + if problem is not None: + self._target = problem._target + self.x0 = problem.x0 + self.bounds = problem.bounds + self.n_parameters = problem.n_parameters + self.n_outputs = problem.n_outputs + + def __call__(self, x, grad=None): + """ + Call the evaluate function for a given set of parameters. + + Parameters + ---------- + x : array-like + The parameters for which to evaluate the cost. + grad : array-like, optional + An array to store the gradient of the cost function with respect + to the parameters. + + Returns + ------- + float + The calculated cost function value. + + Raises + ------ + ValueError + If an error occurs during the calculation of the cost. + """ + try: + return self._evaluate(x, grad) + + except NotImplementedError as e: + raise e + + except Exception as e: + raise ValueError(f"Error in cost calculation: {e}") + + def _evaluate(self, x, grad=None): + """ + Calculate the cost function value for a given set of parameters. + + This method must be implemented by subclasses. + + Parameters + ---------- + x : array-like + The parameters for which to evaluate the cost. + grad : array-like, optional + An array to store the gradient of the cost function with respect + to the parameters. + + Returns + ------- + float + The calculated cost function value. + + Raises + ------ + NotImplementedError + If the method has not been implemented by the subclass. + """ + raise NotImplementedError + + def evaluateS1(self, x): + """ + Call _evaluateS1 for a given set of parameters. + + Parameters + ---------- + x : array-like + The parameters for which to compute the cost and gradient. + + Returns + ------- + tuple + A tuple containing the cost and the gradient. The cost is a float, + and the gradient is an array-like of the same length as `x`. + + Raises + ------ + ValueError + If an error occurs during the calculation of the cost or gradient. + """ + try: + return self._evaluateS1(x) + + except NotImplementedError as e: + raise e + + except Exception as e: + raise ValueError(f"Error in cost calculation: {e}") + + def _evaluateS1(self, x): + """ + Compute the cost and its gradient with respect to the parameters. + + Parameters + ---------- + x : array-like + The parameters for which to compute the cost and gradient. + + Returns + ------- + tuple + A tuple containing the cost and the gradient. The cost is a float, + and the gradient is an array-like of the same length as `x`. + + Raises + ------ + NotImplementedError + If the method has not been implemented by the subclass. + """ + raise NotImplementedError diff --git a/pybop/costs/design_costs.py b/pybop/costs/design_costs.py new file mode 100644 index 00000000..8b02f1f2 --- /dev/null +++ b/pybop/costs/design_costs.py @@ -0,0 +1,181 @@ +import numpy as np +import warnings + +from pybop.costs.base_cost import BaseCost + + +class DesignCost(BaseCost): + """ + Overwrites and extends `BaseCost` class for design-related cost functions. + + Inherits all parameters and attributes from ``BaseCost``. + + Additional Attributes + --------------------- + problem : object + The associated problem containing model and evaluation methods. + parameter_set : object) + The set of parameters from the problem's model. + dt : float + The time step size used in the simulation. + """ + + def __init__(self, problem, update_capacity=False): + """ + Initialises the gravimetric energy density calculator with a problem. + + Parameters + ---------- + problem : object + The problem instance containing the model and data. + """ + super().__init__(problem) + self.problem = problem + if update_capacity is True: + nominal_capacity_warning = ( + "The nominal capacity is approximated for each iteration." + ) + else: + nominal_capacity_warning = ( + "The nominal capacity is fixed at the initial model value." + ) + warnings.warn(nominal_capacity_warning, UserWarning) + self.update_capacity = update_capacity + self.parameter_set = problem.model.parameter_set + self.update_simulation_data(problem.x0) + + def update_simulation_data(self, initial_conditions): + """ + Updates the simulation data based on the initial conditions. + + Parameters + ---------- + initial_conditions : array + The initial conditions for the simulation. + """ + if self.update_capacity: + self.problem.model.approximate_capacity(self.problem.x0) + solution = self.problem.evaluate(initial_conditions) + self.problem._time_data = solution[:, -1] + self.problem._target = solution[:, 0:-1] + self.dt = solution[1, -1] - solution[0, -1] + + def _evaluate(self, x, grad=None): + """ + Computes the value of the cost function. + + This method must be implemented by subclasses. + + Parameters + ---------- + x : array + The parameter set for which to compute the cost. + grad : array, optional + Gradient information, not used in this method. + + Raises + ------ + NotImplementedError + If the method has not been implemented by the subclass. + """ + raise NotImplementedError + + +class GravimetricEnergyDensity(DesignCost): + """ + Represents the gravimetric energy density of a battery cell, calculated based + on a normalised discharge from upper to lower voltage limits. The goal is to + maximise the energy density, which is achieved by minimizing the negative energy + density reported by this class. + + Inherits all parameters and attributes from ``DesignCost``. + """ + + def __init__(self, problem, update_capacity=False): + super().__init__(problem, update_capacity) + + def _evaluate(self, x, grad=None): + """ + Computes the cost function for the energy density. + + Parameters + ---------- + x : array + The parameter set for which to compute the cost. + grad : array, optional + Gradient information, not used in this method. + + Returns + ------- + float + The negative gravimetric energy density or infinity in case of infeasible parameters. + """ + try: + with warnings.catch_warnings(): + # Convert UserWarning to an exception + warnings.filterwarnings("error", category=UserWarning) + + if self.update_capacity: + self.problem.model.approximate_capacity(x) + solution = self.problem.evaluate(x) + + voltage, current = solution[:, 0], solution[:, 1] + negative_energy_density = -np.trapz(voltage * current, dx=self.dt) / ( + 3600 * self.problem.model.cell_mass(self.parameter_set) + ) + + return negative_energy_density + + except UserWarning as e: + print(f"Ignoring this sample due to: {e}") + return np.inf + + +class VolumetricEnergyDensity(DesignCost): + """ + Represents the volumetric energy density of a battery cell, calculated based + on a normalised discharge from upper to lower voltage limits. The goal is to + maximise the energy density, which is achieved by minimizing the negative energy + density reported by this class. + + Inherits all parameters and attributes from ``DesignCost``. + """ + + def __init__(self, problem, update_capacity=False): + super().__init__(problem, update_capacity) + + def _evaluate(self, x, grad=None): + """ + Computes the cost function for the energy density. + + Parameters + ---------- + x : array + The parameter set for which to compute the cost. + grad : array, optional + Gradient information, not used in this method. + + Returns + ------- + float + The negative volumetric energy density or infinity in case of infeasible parameters. + """ + try: + with warnings.catch_warnings(): + # Convert UserWarning to an exception + warnings.filterwarnings("error", category=UserWarning) + + if self.update_capacity: + self.problem.model.approximate_capacity(x) + solution = self.problem.evaluate(x) + + voltage, current = solution[:, 0], solution[:, 1] + negative_energy_density = -np.trapz(voltage * current, dx=self.dt) / ( + 3600 * self.problem.model.cell_volume(self.parameter_set) + ) + + return negative_energy_density + + except UserWarning as e: + print(f"Ignoring this sample due to: {e}") + return np.inf diff --git a/pybop/_costs.py b/pybop/costs/fitting_costs.py similarity index 62% rename from pybop/_costs.py rename to pybop/costs/fitting_costs.py index 65bf2662..2da337f1 100644 --- a/pybop/_costs.py +++ b/pybop/costs/fitting_costs.py @@ -1,139 +1,7 @@ import numpy as np - -class BaseCost: - """ - Base class for defining cost functions. - - This class is intended to be subclassed to create specific cost functions - for evaluating model predictions against a set of data. The cost function - quantifies the goodness-of-fit between the model predictions and the - observed data, with a lower cost value indicating a better fit. - - Parameters - ---------- - problem : object - A problem instance containing the data and functions necessary for - evaluating the cost function. - _target : array-like - An array containing the target data to fit. - x0 : array-like - The initial guess for the model parameters. - bounds : tuple - The bounds for the model parameters. - n_parameters : int - The number of parameters in the model. - """ - - def __init__(self, problem): - self.problem = problem - if problem is not None: - self._target = problem._target - self.x0 = problem.x0 - self.bounds = problem.bounds - self.n_parameters = problem.n_parameters - - def __call__(self, x, grad=None): - """ - Call the evaluate function for a given set of parameters. - - Parameters - ---------- - x : array-like - The parameters for which to evaluate the cost. - grad : array-like, optional - An array to store the gradient of the cost function with respect - to the parameters. - - Returns - ------- - float - The calculated cost function value. - - Raises - ------ - ValueError - If an error occurs during the calculation of the cost. - """ - try: - return self._evaluate(x, grad) - - except Exception as e: - raise ValueError(f"Error in cost calculation: {e}") - - def _evaluate(self, x, grad=None): - """ - Calculate the cost function value for a given set of parameters. - - This method must be implemented by subclasses. - - Parameters - ---------- - x : array-like - The parameters for which to evaluate the cost. - grad : array-like, optional - An array to store the gradient of the cost function with respect - to the parameters. - - Returns - ------- - float - The calculated cost function value. - - Raises - ------ - NotImplementedError - If the method has not been implemented by the subclass. - """ - raise NotImplementedError - - def evaluateS1(self, x): - """ - Call _evaluateS1 for a given set of parameters. - - Parameters - ---------- - x : array-like - The parameters for which to compute the cost and gradient. - - Returns - ------- - tuple - A tuple containing the cost and the gradient. The cost is a float, - and the gradient is an array-like of the same length as `x`. - - Raises - ------ - ValueError - If an error occurs during the calculation of the cost or gradient. - """ - try: - return self._evaluateS1(x) - - except Exception as e: - raise ValueError(f"Error in cost calculation: {e}") - - def _evaluateS1(self, x): - """ - Compute the cost and its gradient with respect to the parameters. - - Parameters - ---------- - x : array-like - The parameters for which to compute the cost and gradient. - - Returns - ------- - tuple - A tuple containing the cost and the gradient. The cost is a float, - and the gradient is an array-like of the same length as `x`. - - Raises - ------ - NotImplementedError - If the method has not been implemented by the subclass. - """ - raise NotImplementedError +from pybop.costs.base_cost import BaseCost +from pybop.observers.observer import Observer class RootMeanSquaredError(BaseCost): @@ -169,7 +37,6 @@ def _evaluate(self, x, grad=None): The root mean square error. """ - prediction = self.problem.evaluate(x) if len(prediction) < len(self._target): @@ -252,13 +119,13 @@ def _evaluateS1(self, x): y, dy = self.problem.evaluateS1(x) if len(y) < len(self._target): e = np.float64(np.inf) - de = self._de * np.ones(self.problem.n_parameters) + de = self._de * np.ones(self.n_parameters) else: dy = dy.reshape( ( self.problem.n_time_data, - self.problem.n_outputs, - self.problem.n_parameters, + self.n_outputs, + self.n_parameters, ) ) r = y - self._target @@ -281,3 +148,64 @@ def set_fail_gradient(self, de): """ de = float(de) self._de = de + + +class ObserverCost(BaseCost): + """ + Observer cost function. + + Computes the cost function for an observer model, which is log likelihood + of the data points given the model parameters. + + Inherits all parameters and attributes from ``BaseCost``. + + """ + + def __init__(self, observer: Observer): + super().__init__(problem=observer) + self._observer = observer + + def _evaluate(self, x, grad=None): + """ + Calculate the observer cost for a given set of parameters. + + Parameters + ---------- + x : array-like + The parameters for which to evaluate the cost. + grad : array-like, optional + An array to store the gradient of the cost function with respect + to the parameters. + + Returns + ------- + float + The observer cost (negative of the log likelihood). + """ + inputs = {key: x[i] for i, key in enumerate(self._observer._model.fit_keys)} + log_likelihood = self._observer.log_likelihood( + self._target, self._observer.time_data(), inputs + ) + return -log_likelihood + + def evaluateS1(self, x): + """ + Compute the cost and its gradient with respect to the parameters. + + Parameters + ---------- + x : array-like + The parameters for which to compute the cost and gradient. + + Returns + ------- + tuple + A tuple containing the cost and the gradient. The cost is a float, + and the gradient is an array-like of the same length as `x`. + + Raises + ------ + ValueError + If an error occurs during the calculation of the cost or gradient. + """ + raise NotImplementedError diff --git a/pybop/models/base_model.py b/pybop/models/base_model.py index 86967333..978226f8 100644 --- a/pybop/models/base_model.py +++ b/pybop/models/base_model.py @@ -1,5 +1,35 @@ +from __future__ import annotations +from dataclasses import dataclass +from typing import Any, Dict, Optional import pybamm import numpy as np +import casadi + +Inputs = Dict[str, float] + + +@dataclass +class TimeSeriesState(object): + """ + The current state of a time series model that is a pybamm model + """ + + sol: pybamm.Solution + inputs: Inputs + t: float = 0.0 + + def as_ndarray(self) -> np.ndarray: + ncol = self.sol.y.shape[1] + if ncol > 1: + y = self.sol.y[:, -1] + else: + y = self.sol.y + if isinstance(y, casadi.DM): + y = y.full() + return y + + def __len__(self): + return self.sol.y.shape[0] class BaseModel: @@ -27,6 +57,9 @@ def __init__(self, name="Base Model"): self.parameters = None self.dataset = None self.signal = None + self.matched_parameters = {} + self.non_matched_parameters = {} + self.fit_keys = [] self.param_check_counter = 0 self.allow_infeasible_solutions = True @@ -58,7 +91,10 @@ def build( self.dataset = dataset self.parameters = parameters if self.parameters is not None: + self.set_parameter_classification(self.parameters) self.fit_keys = [param.name for param in self.parameters] + else: + self.fit_keys = [] if init_soc is not None: self.set_init_soc(init_soc) @@ -71,15 +107,18 @@ def build( self._built_model = self.pybamm_model else: self.set_params() + self._mesh = pybamm.Mesh(self.geometry, self.submesh_types, self.var_pts) self._disc = pybamm.Discretisation(self.mesh, self.spatial_methods) self._built_model = self._disc.process_model( self._model_with_set_params, inplace=False, check_model=check_model ) - # Clear solver + # Clear solver and setup model self._solver._model_set_up = {} + self.n_states = self._built_model.len_rhs_and_alg # len_rhs + len_alg + def set_init_soc(self, init_soc): """ Set the initial state of charge for the battery model. @@ -105,24 +144,24 @@ def set_init_soc(self, init_soc): # Save solved initial SOC in case we need to rebuild the model self._built_initial_soc = init_soc - def set_params(self): + def set_params(self, rebuild=False): """ Assign the parameters to the model. This method processes the model with the given parameters, sets up the geometry, and updates the model instance. """ - if self.model_with_set_params: + if self.model_with_set_params and not rebuild: return # Mark any simulation inputs in the parameter set - if self.parameters is not None: + if self.non_matched_parameters: for i in self.fit_keys: self._parameter_set[i] = "[input]" - if self.dataset is not None and self.parameters is not None: + if self.dataset is not None and self.non_matched_parameters: if "Current function [A]" not in self.fit_keys: - self.parameter_set["Current function [A]"] = pybamm.Interpolant( + self._parameter_set["Current function [A]"] = pybamm.Interpolant( self.dataset["Time [s]"], self.dataset["Current function [A]"], pybamm.t, @@ -133,10 +172,147 @@ def set_params(self): self._model_with_set_params = self._parameter_set.process_model( self._unprocessed_model, inplace=False ) - self._parameter_set.process_geometry(self.geometry) + if self.geometry is not None: + self._parameter_set.process_geometry(self.geometry) self.pybamm_model = self._model_with_set_params - def simulate(self, inputs, t_eval): + def rebuild( + self, + dataset=None, + parameters=None, + parameter_set=None, + check_model=True, + init_soc=None, + ): + """ + Rebuild the PyBaMM model for a given parameter set. + + This method requires the self.build() method to be called first, and + then rebuilds the model for a given parameter set. Specifically, + this method applies the given parameters, sets up the mesh and discretization if needed, and prepares the model + for simulations. + + Parameters + ---------- + dataset : pybamm.Dataset, optional + The dataset to be used in the model construction. + parameters : dict, optional + A dictionary containing parameter values to apply to the model. + parameter_set : pybop.parameter_set, optional + A PyBOP parameter set object or a dictionary containing the parameter values + check_model : bool, optional + If True, the model will be checked for correctness after construction. + init_soc : float, optional + The initial state of charge to be used in simulations. + """ + self.dataset = dataset + self.parameters = parameters + if parameters is not None: + self.set_parameter_classification(parameters) + + if init_soc is not None: + self.set_init_soc(init_soc) + + if self._built_model is None: + raise ValueError("Model must be built before calling rebuild") + + self.set_params(rebuild=True) + self._mesh = pybamm.Mesh(self.geometry, self.submesh_types, self.var_pts) + self._disc = pybamm.Discretisation(self.mesh, self.spatial_methods) + self._built_model = self._disc.process_model( + self._model_with_set_params, inplace=False, check_model=check_model + ) + + # Clear solver and setup model + self._solver._model_set_up = {} + + def set_parameter_classification(self, parameters): + """ + Set the parameter classification for the model. + + Parameters + ---------- + parameters : Pybop.ParameterSet + + Returns + ------- + None + The method updates attributes on self. + + """ + processed_parameters = {param.name: param.value for param in parameters} + matched_parameters = { + param: processed_parameters[param] + for param in processed_parameters + if param in self.rebuild_parameters + } + non_matched_parameters = { + param: processed_parameters[param] + for param in processed_parameters + if param not in self.rebuild_parameters + } + + self.matched_parameters.update(matched_parameters) + self.non_matched_parameters.update(non_matched_parameters) + + if self.matched_parameters: + self._parameter_set.update(self.matched_parameters) + self._unprocessed_parameter_set = self._parameter_set + self.geometry = self.pybamm_model.default_geometry + + if self.non_matched_parameters: + self.fit_keys = list(self.non_matched_parameters.keys()) + + def reinit( + self, inputs: Inputs, t: float = 0.0, x: Optional[np.ndarray] = None + ) -> TimeSeriesState: + """ + Initialises the solver with the given inputs and returns the initial state of the problem + """ + if self._built_model is None: + raise ValueError("Model must be built before calling reinit") + + if not isinstance(inputs, dict): + inputs = {key: inputs[i] for i, key in enumerate(self.fit_keys)} + + self._solver.set_up(self._built_model, inputs=inputs) + + if x is None: + x = self._built_model.y0 + + sol = pybamm.Solution([np.array([t])], [x], self._built_model, inputs) + + return TimeSeriesState(sol=sol, inputs=inputs, t=t) + + def get_state(self, inputs: Inputs, t: float, x: np.ndarray) -> TimeSeriesState: + """ + Returns the given state for the problem (inputs are assumed constant since last reinit) + """ + if self._built_model is None: + raise ValueError("Model must be built before calling get_state") + + sol = pybamm.Solution([np.array([t])], [x], self._built_model, inputs) + + return TimeSeriesState(sol=sol, inputs=inputs, t=t) + + def step(self, state: TimeSeriesState, time: np.ndarray) -> TimeSeriesState: + """ + Step forward in time from the given state until the given time. + + Parameters + ---------- + state : TimeSeriesState + The current state of the model + time : np.ndarray + The time to predict the system to (in whatever time units the model is in) + """ + dt = time - state.t + new_sol = self._solver.step( + state.sol, self.built_model, dt, npts=2, inputs=state.inputs, save=False + ) + return TimeSeriesState(sol=new_sol, inputs=state.inputs, t=time) + + def simulate(self, inputs, t_eval) -> np.ndarray[np.float64]: """ Execute the forward model simulation and return the result. @@ -158,26 +334,30 @@ def simulate(self, inputs, t_eval): ValueError If the model has not been built before simulation. """ - if self._built_model is None: raise ValueError("Model must be built before calling simulate") else: - if not isinstance(inputs, dict): - inputs = {key: inputs[i] for i, key in enumerate(self.fit_keys)} + if not self.fit_keys and self.matched_parameters: + sol = self.solver.solve(self.built_model, t_eval=t_eval) - if self.check_params( - inputs=inputs, - allow_infeasible_solutions=self.allow_infeasible_solutions, - ): - sol = self.solver.solve(self.built_model, inputs=inputs, t_eval=t_eval) + else: + if not isinstance(inputs, dict): + inputs = {key: inputs[i] for i, key in enumerate(self.fit_keys)} + + if self.check_params( + inputs=inputs, + allow_infeasible_solutions=self.allow_infeasible_solutions, + ): + sol = self.solver.solve( + self.built_model, inputs=inputs, t_eval=t_eval + ) + else: + return [np.inf] predictions = [sol[signal].data for signal in self.signal] # breakpoint() return np.vstack(predictions).T - else: - return [np.inf] - def simulateS1(self, inputs, t_eval): """ Perform the forward model simulation with sensitivities. @@ -212,7 +392,7 @@ def simulateS1(self, inputs, t_eval): inputs=inputs, allow_infeasible_solutions=self.allow_infeasible_solutions, ): - sol = self.solver.solve( + sol = self._solver.solve( self.built_model, inputs=inputs, t_eval=t_eval, @@ -331,7 +511,15 @@ def check_params( """ if inputs is not None: if not isinstance(inputs, dict): - inputs = {key: inputs[i] for i, key in enumerate(self.fit_keys)} + if isinstance(inputs, list): + for entry in inputs: + if not isinstance(entry, (int, float)): + raise ValueError( + "Expecting inputs in the form of a dictionary, numeric list" + + f" or None, but received a list with type: {type(inputs)}" + ) + else: + inputs = {key: inputs[i] for i, key in enumerate(self.fit_keys)} return self._check_params( inputs=inputs, allow_infeasible_solutions=allow_infeasible_solutions @@ -408,16 +596,18 @@ def geometry(self): return self._geometry @geometry.setter - def geometry(self, geometry): - self._geometry = geometry.copy() + def geometry(self, geometry: Optional[pybamm.Geometry]): + self._geometry = geometry.copy() if geometry is not None else None @property def submesh_types(self): return self._submesh_types @submesh_types.setter - def submesh_types(self, submesh_types): - self._submesh_types = submesh_types.copy() + def submesh_types(self, submesh_types: Optional[Dict[str, Any]]): + self._submesh_types = ( + submesh_types.copy() if submesh_types is not None else None + ) @property def mesh(self): @@ -428,16 +618,18 @@ def var_pts(self): return self._var_pts @var_pts.setter - def var_pts(self, var_pts): - self._var_pts = var_pts.copy() + def var_pts(self, var_pts: Optional[Dict[str, int]]): + self._var_pts = var_pts.copy() if var_pts is not None else None @property def spatial_methods(self): return self._spatial_methods @spatial_methods.setter - def spatial_methods(self, spatial_methods): - self._spatial_methods = spatial_methods.copy() + def spatial_methods(self, spatial_methods: Optional[Dict[str, Any]]): + self._spatial_methods = ( + spatial_methods.copy() if spatial_methods is not None else None + ) @property def solver(self): @@ -445,4 +637,4 @@ def solver(self): @solver.setter def solver(self, solver): - self._solver = solver.copy() + self._solver = solver.copy() if solver is not None else None diff --git a/pybop/models/empirical/ecm.py b/pybop/models/empirical/ecm.py index eee59295..6c0427c8 100644 --- a/pybop/models/empirical/ecm.py +++ b/pybop/models/empirical/ecm.py @@ -1,8 +1,8 @@ import pybamm -from ..base_model import BaseModel +from .ecm_base import ECircuitModel -class Thevenin(BaseModel): +class Thevenin(ECircuitModel): """ The Thevenin class represents an equivalent circuit model based on the Thevenin model in PyBaMM. @@ -75,6 +75,7 @@ def __init__( self._built_initial_soc = None self._mesh = None self._disc = None + self.rebuild_parameters = {} def _check_params(self, inputs=None, allow_infeasible_solutions=True): """ diff --git a/pybop/models/empirical/ecm_base.py b/pybop/models/empirical/ecm_base.py new file mode 100644 index 00000000..b66fb47b --- /dev/null +++ b/pybop/models/empirical/ecm_base.py @@ -0,0 +1,29 @@ +from ..base_model import BaseModel + + +class ECircuitModel(BaseModel): + """ + Overwrites and extends `BaseModel` class for circuit-based PyBaMM models. + """ + + def __init__(self): + super().__init__() + + def _check_params(self, inputs=None, allow_infeasible_solutions=True): + """ + Check the compatibility of the model parameters. + + Parameters + ---------- + inputs : dict + The input parameters for the simulation. + allow_infeasible_solutions : bool, optional + If True, infeasible parameter values will be allowed in the optimisation (default: True). + + Returns + ------- + bool + A boolean which signifies whether the parameters are compatible. + + """ + return True diff --git a/pybop/models/lithium_ion/echem.py b/pybop/models/lithium_ion/echem.py index e8e1cbda..0869258b 100644 --- a/pybop/models/lithium_ion/echem.py +++ b/pybop/models/lithium_ion/echem.py @@ -1,9 +1,8 @@ import pybamm -import warnings -from ..base_model import BaseModel +from .echem_base import EChemBaseModel -class SPM(BaseModel): +class SPM(EChemBaseModel): """ Wraps the Single Particle Model (SPM) for simulating lithium-ion batteries, as implemented in PyBaMM. @@ -71,59 +70,10 @@ def __init__( self._disc = None self._electrode_soh = pybamm.lithium_ion.electrode_soh + self.rebuild_parameters = self.set_rebuild_parameters() - def _check_params( - self, inputs=None, parameter_set=None, allow_infeasible_solutions=True - ): - """ - Check compatibility of the model parameters. - - Parameters - ---------- - inputs : dict - The input parameters for the simulation. - allow_infeasible_solutions : bool, optional - If True, infeasible parameter values will be allowed in the optimisation (default: True). - - Returns - ------- - bool - A boolean which signifies whether the parameters are compatible. - """ - parameter_set = parameter_set or self._parameter_set - - electrode_params = [ - ( - "Negative electrode active material volume fraction", - "Negative electrode porosity", - ), - ( - "Positive electrode active material volume fraction", - "Positive electrode porosity", - ), - ] - - related_parameters = { - key: inputs.get(key) if inputs and key in inputs else parameter_set[key] - for pair in electrode_params - for key in pair - } - - for material_vol_fraction, porosity in electrode_params: - if ( - related_parameters[material_vol_fraction] + related_parameters[porosity] - > 1 - ): - if self.param_check_counter <= len(electrode_params): - infeasibility_warning = "Non-physical point encountered - [{material_vol_fraction} + {porosity}] > 1.0!" - warnings.warn(infeasibility_warning, UserWarning) - self.param_check_counter += 1 - return allow_infeasible_solutions - - return True - - -class SPMe(BaseModel): + +class SPMe(EChemBaseModel): """ Represents the Single Particle Model with Electrolyte (SPMe) for lithium-ion batteries. @@ -192,53 +142,4 @@ def __init__( self._disc = None self._electrode_soh = pybamm.lithium_ion.electrode_soh - - def _check_params( - self, inputs=None, parameter_set=None, allow_infeasible_solutions=True - ): - """ - Check compatibility of the model parameters. - - Parameters - ---------- - inputs : dict - The input parameters for the simulation. - allow_infeasible_solutions : bool, optional - If True, infeasible parameter values will be allowed in the optimisation (default: True). - - Returns - ------- - bool - A boolean which signifies whether the parameters are compatible. - """ - parameter_set = parameter_set or self._parameter_set - - electrode_params = [ - ( - "Negative electrode active material volume fraction", - "Negative electrode porosity", - ), - ( - "Positive electrode active material volume fraction", - "Positive electrode porosity", - ), - ] - - related_parameters = { - key: inputs.get(key) if inputs and key in inputs else parameter_set[key] - for pair in electrode_params - for key in pair - } - - for material_vol_fraction, porosity in electrode_params: - if ( - related_parameters[material_vol_fraction] + related_parameters[porosity] - > 1 - ): - if self.param_check_counter <= len(electrode_params): - infeasibility_warning = "Non-physical point encountered - [{material_vol_fraction} + {porosity}] > 1.0!" - warnings.warn(infeasibility_warning, UserWarning) - self.param_check_counter += 1 - return allow_infeasible_solutions - - return True + self.rebuild_parameters = self.set_rebuild_parameters() diff --git a/pybop/models/lithium_ion/echem_base.py b/pybop/models/lithium_ion/echem_base.py new file mode 100644 index 00000000..7e5c869f --- /dev/null +++ b/pybop/models/lithium_ion/echem_base.py @@ -0,0 +1,259 @@ +import warnings +from ..base_model import BaseModel + + +class EChemBaseModel(BaseModel): + """ + Overwrites and extends `BaseModel` class for electrochemical PyBaMM models. + """ + + def __init__(self): + super().__init__() + + def _check_params( + self, inputs=None, parameter_set=None, allow_infeasible_solutions=True + ): + """ + Check compatibility of the model parameters. + + Parameters + ---------- + inputs : dict + The input parameters for the simulation. + allow_infeasible_solutions : bool, optional + If True, infeasible parameter values will be allowed in the optimisation (default: True). + + Returns + ------- + bool + A boolean which signifies whether the parameters are compatible. + """ + parameter_set = parameter_set or self._parameter_set + + electrode_params = [ + ( + "Negative electrode active material volume fraction", + "Negative electrode porosity", + ), + ( + "Positive electrode active material volume fraction", + "Positive electrode porosity", + ), + ] + + related_parameters = { + key: inputs.get(key) if inputs and key in inputs else parameter_set[key] + for pair in electrode_params + for key in pair + } + + for material_vol_fraction, porosity in electrode_params: + if ( + related_parameters[material_vol_fraction] + related_parameters[porosity] + > 1 + ): + if self.param_check_counter <= len(electrode_params): + infeasibility_warning = "Non-physical point encountered - [{material_vol_fraction} + {porosity}] > 1.0!" + warnings.warn(infeasibility_warning, UserWarning) + self.param_check_counter += 1 + return allow_infeasible_solutions + + return True + + def cell_volume(self, parameter_set=None): + """ + Calculate the total cell volume in m3. + + This method uses the provided parameter set to calculate the total thickness + of the cell including electrodes, separator, and current collectors. It then + calculates the volume by multiplying by the cross-sectional area. + + Parameters + ---------- + parameter_set : dict, optional + A dictionary containing the parameter values necessary for the volume + calculation. + + Returns + ------- + float + The total volume of the cell in m3. + """ + parameter_set = parameter_set or self._parameter_set + + # Calculate cell thickness + cell_thickness = ( + parameter_set["Positive electrode thickness [m]"] + + parameter_set["Negative electrode thickness [m]"] + + parameter_set["Separator thickness [m]"] + + parameter_set["Positive current collector thickness [m]"] + + parameter_set["Negative current collector thickness [m]"] + ) + + # Calculate cross-sectional area + cross_sectional_area = ( + parameter_set["Electrode height [m]"] * parameter_set["Electrode width [m]"] + ) + + # Calculate and return total cell volume + return cross_sectional_area * cell_thickness + + def cell_mass(self, parameter_set=None): + """ + Calculate the total cell mass in kilograms. + + This method uses the provided parameter set to calculate the mass of different + components of the cell, such as electrodes, separator, and current collectors, + based on their densities, porosities, and thicknesses. It then calculates the + total mass by summing the mass of each component. + + Parameters + ---------- + parameter_set : dict, optional + A dictionary containing the parameter values necessary for the mass + calculations. + + Returns + ------- + float + The total mass of the cell in kilograms. + """ + parameter_set = parameter_set or self._parameter_set + + def mass_density( + active_material_vol_frac, density, porosity, electrolyte_density + ): + return (active_material_vol_frac * density) + ( + porosity * electrolyte_density + ) + + def area_density(thickness, mass_density): + return thickness * mass_density + + # Approximations due to SPM(e) parameter set limitations + electrolyte_density = parameter_set["Separator density [kg.m-3]"] + + # Calculate mass densities + positive_mass_density = mass_density( + parameter_set["Positive electrode active material volume fraction"], + parameter_set["Positive electrode density [kg.m-3]"], + parameter_set["Positive electrode porosity"], + electrolyte_density, + ) + negative_mass_density = mass_density( + parameter_set["Negative electrode active material volume fraction"], + parameter_set["Negative electrode density [kg.m-3]"], + parameter_set["Negative electrode porosity"], + electrolyte_density, + ) + + # Calculate area densities + positive_area_density = area_density( + parameter_set["Positive electrode thickness [m]"], positive_mass_density + ) + negative_area_density = area_density( + parameter_set["Negative electrode thickness [m]"], negative_mass_density + ) + separator_area_density = area_density( + parameter_set["Separator thickness [m]"], + parameter_set["Separator porosity"] * electrolyte_density, + ) + positive_cc_area_density = area_density( + parameter_set["Positive current collector thickness [m]"], + parameter_set["Positive current collector density [kg.m-3]"], + ) + negative_cc_area_density = area_density( + parameter_set["Negative current collector thickness [m]"], + parameter_set["Negative current collector density [kg.m-3]"], + ) + + # Calculate cross-sectional area + cross_sectional_area = ( + parameter_set["Electrode height [m]"] * parameter_set["Electrode width [m]"] + ) + + # Calculate and return total cell mass + total_area_density = ( + positive_area_density + + negative_area_density + + separator_area_density + + positive_cc_area_density + + negative_cc_area_density + ) + return cross_sectional_area * total_area_density + + def approximate_capacity(self, x): + """ + Calculate and update an estimate for the nominal cell capacity based on the theoretical + energy density and an average voltage. + + The nominal capacity is computed by dividing the theoretical energy (in watt-hours) by + the average open circuit potential (voltage) of the cell. + + Parameters + ---------- + x : array-like + An array of values representing the model inputs. + + Returns + ------- + None + The nominal cell capacity is updated directly in the model's parameter set. + """ + # Extract stoichiometries and compute mean values + ( + min_sto_neg, + max_sto_neg, + min_sto_pos, + max_sto_pos, + ) = self._electrode_soh.get_min_max_stoichiometries(self._parameter_set) + mean_sto_neg = (min_sto_neg + max_sto_neg) / 2 + mean_sto_pos = (min_sto_pos + max_sto_pos) / 2 + + inputs = { + key: x[i] for i, key in enumerate([param.name for param in self.parameters]) + } + self._parameter_set.update(inputs) + + # Calculate theoretical energy density + theoretical_energy = self._electrode_soh.calculate_theoretical_energy( + self._parameter_set + ) + + # Calculate average voltage + positive_electrode_ocp = self._parameter_set["Positive electrode OCP [V]"] + negative_electrode_ocp = self._parameter_set["Negative electrode OCP [V]"] + average_voltage = positive_electrode_ocp(mean_sto_pos) - negative_electrode_ocp( + mean_sto_neg + ) + + # Calculate and update nominal capacity + theoretical_capacity = theoretical_energy / average_voltage + self._parameter_set.update( + {"Nominal cell capacity [A.h]": theoretical_capacity} + ) + + def set_rebuild_parameters(self): + """ + Sets the parameters that can be changed when rebuilding the model. + + Returns + ------- + dict + A dictionary of parameters that can be changed when rebuilding the model. + + """ + rebuild_parameters = dict.fromkeys( + [ + "Negative particle radius [m]", + "Negative electrode porosity", + "Negative electrode thickness [m]", + "Positive particle radius [m]", + "Positive electrode porosity", + "Positive electrode thickness [m]", + "Separator porosity", + "Separator thickness [m]", + ] + ) + + return rebuild_parameters diff --git a/pybop/observers/__init__.py b/pybop/observers/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pybop/observers/observer.py b/pybop/observers/observer.py new file mode 100644 index 00000000..0a93fe8a --- /dev/null +++ b/pybop/observers/observer.py @@ -0,0 +1,172 @@ +from typing import List, Optional +import numpy as np +from pybop._problem import BaseProblem +from pybop.models.base_model import BaseModel, Inputs, TimeSeriesState +from pybop.parameters.parameter import Parameter + + +class Observer(BaseProblem): + """ + An observer of a time series state. Observers: + 1. keep track of the distribution of a current time series model state + 2. predict forward in time the distribution of the state + 3. update the distribution of the state with new observations + + Parameters + ---------- + parameters : list + List of parameters for the problem. + model : BaseModel + The model to observe. + check_model : bool, optional + Flag to indicate if the model should be checked (default: True). + signal: List[str] + The signal to observe. + init_soc : float, optional + Initial state of charge (default: None). + x0 : np.ndarray, optional + Initial parameter values (default: None). + """ + + # define a subtype for covariance matrices for use by derived classes + Covariance = np.ndarray + + def __init__( + self, + parameters: List[Parameter], + model: BaseModel, + check_model=True, + signal=["Voltage [V]"], + init_soc=None, + x0=None, + ) -> None: + super().__init__(parameters, model, check_model, signal, init_soc, x0) + if model._built_model is None: + raise ValueError("Only built models can be used in Observers") + if model.signal is None: + model.signal = self.signal + + inputs = dict() + for param in self.parameters: + inputs[param.name] = param.value + + self._state = model.reinit(inputs) + self._model = model + self._signal = self.signal + + def reset(self, inputs: Inputs) -> None: + self._state = self._model.reinit(inputs) + + def observe(self, time: float, value: Optional[np.ndarray] = None) -> float: + """ + Predict the time series model until t = `time` and optionally observe the measurement `value`. + + Returns the log likelihood of the model given the value and inputs. If no value is given, the log likelihood is 0. + + The base observer does not perform any value observation and always returns 0. + + Parameters + ---------- + time : float + The time of the new observation. + value : np.ndarray (optional) + The new observation. + """ + if time < self._state.t: + raise ValueError("Time must be increasing.") + if time != self._state.t: + self._state = self._model.step(self._state, time) + return 0.0 + + def log_likelihood( + self, values: np.ndarray, times: np.ndarray, inputs: Inputs + ) -> float: + """ + Returns the log likelihood of the model given the values and inputs. + + Parameters + ---------- + values : np.ndarray + The values of the model. + times : np.ndarray + The times at which to observe the model. + inputs : Inputs + The inputs to the model. + """ + if len(values) != len(times): + raise ValueError("values and times must have the same length.") + log_likelihood = 0.0 + self.reset(inputs) + for t, v in zip(times, values): + try: + log_likelihood += self.observe(t, v) + except Exception: + return np.float64(-np.inf) + return log_likelihood + + def get_current_state(self) -> TimeSeriesState: + """ + Returns the current state of the model. + """ + return self._state + + def get_current_measure(self) -> np.ndarray: + """ + Returns the current measurement. + """ + return self.get_measure(self._state) + + def get_current_covariance(self) -> Covariance: + """ + Returns the current covariance of the model. + """ + n = len(self._state) + return np.zeros((n, n)) + + def get_measure(self, x: TimeSeriesState) -> np.ndarray: + measures = [x.sol[s].data[-1] for s in self._signal] + return np.array([[m] for m in measures]) + + def get_current_time(self) -> float: + """ + Returns the current time. + """ + return self._state.t + + def evaluate(self, x): + """ + Evaluate the model with the given parameters and return the signal. + + Parameters + ---------- + x : np.ndarray + Parameter values to evaluate the model at. + + Returns + ------- + y : np.ndarray + The model output y(t) simulated with inputs x. + """ + inputs = dict() + if isinstance(x[0], Parameter): + for param in x: + inputs[param.name] = param.value + else: # x is an array of parameter values + for i, param in enumerate(self.parameters): + inputs[param.name] = x[i] + self.reset(inputs) + + output = [] + if hasattr(self, "_dataset"): + ym = self._target + for i, t in enumerate(self._time_data): + self.observe(t, ym[i]) + ys = self.get_current_measure() + output.append(ys) + else: + for t in self._time_data: + self.observe(t) + ys = self.get_current_measure() + output.append(ys) + + return np.vstack(output) diff --git a/pybop/observers/unscented_kalman.py b/pybop/observers/unscented_kalman.py new file mode 100644 index 00000000..ae13acc8 --- /dev/null +++ b/pybop/observers/unscented_kalman.py @@ -0,0 +1,467 @@ +from dataclasses import dataclass +import numpy as np +import scipy.linalg as linalg +from typing import List, Tuple, Union + +from pybop.models.base_model import BaseModel, Inputs +from pybop.observers.observer import Observer +from pybop.parameters.parameter import Parameter + + +class UnscentedKalmanFilterObserver(Observer): + """ + An observer using the unscented Kalman filter. This is a wrapper class for PyBOP, see class SquareRootUKF for more details on the method. + + Parameters + ---------- + parameters: List[Parameters] + The inputs to the model. + model : BaseModel + The model to observe. + sigma0 : np.ndarray | float + The covariance matrix of the initial state. If a float is provided, the covariance matrix is set to sigma0 * np.eye(n), where n is the number of states. + To remove a state from the filter, set the corresponding row and col to zero in both sigma0 and process. + process : np.ndarray | float + The covariance matrix of the process noise. If a float is provided, the covariance matrix is set to process * np.eye(n), where n is the number of states. + To remove a state from the filter, set the corresponding row and col to zero in both sigma0 and process. + measure : np.ndarray | float + The covariance matrix of the measurement noise. If a float is provided, the covariance matrix is set to measure * np.eye(m), where m is the number of measurements. + dataset : Dataset + Dataset object containing the data to fit the model to. + check_model : bool, optional + Flag to indicate if the model should be checked (default: True). + signal: str + The signal to observe. + init_soc : float, optional + Initial state of charge (default: None). + x0 : np.ndarray, optional + Initial parameter values (default: None). + """ + + Covariance = np.ndarray + + def __init__( + self, + parameters: List[Parameter], + model: BaseModel, + sigma0: Union[Covariance, float], + process: Union[Covariance, float], + measure: Union[Covariance, float], + dataset=None, + check_model=True, + signal=["Voltage [V]"], + init_soc=None, + x0=None, + ) -> None: + super().__init__(parameters, model, check_model, signal, init_soc, x0) + if dataset is not None: + self._dataset = dataset.data + + # Check that the dataset contains time and current + for name in ["Time [s]", "Current function [A]"] + self.signal: + if name not in self._dataset: + raise ValueError(f"expected {name} in list of dataset") + + self._time_data = self._dataset["Time [s]"] + self.n_time_data = len(self._time_data) + if np.any(self._time_data < 0): + raise ValueError("Times can not be negative.") + if np.any(self._time_data[:-1] >= self._time_data[1:]): + raise ValueError("Times must be increasing.") + + for signal in self.signal: + if len(self._dataset[signal]) != self.n_time_data: + raise ValueError( + f"Time data and {signal} data must be the same length." + ) + target = [self._dataset[signal] for signal in self.signal] + self._target = np.vstack(target).T + + # Add useful parameters to model + if model is not None: + self._model.signal = self.signal + self._model.n_outputs = self.n_outputs + if dataset is not None: + self._model.n_time_data = self.n_time_data + + # Observer initiation + self._process = process + + x0 = self.get_current_state().as_ndarray() + m0 = self.get_current_measure() + + m = len(m0) + n = len(x0) + if isinstance(sigma0, float): + sigma0 = sigma0 * np.eye(n) + if isinstance(process, float): + process = process * np.eye(n) + if isinstance(measure, float): + measure = measure * np.eye(m) + + if sigma0.shape != (n, n): + raise ValueError(f"sigma0 must be a square matrix of size n = {n}") + if process.shape != (n, n): + raise ValueError(f"process must be a square matrix of size n = {n}") + if measure.shape != (m, m): + raise ValueError(f"measure must be a square matrix of size m = {m}") + + self._sigma0 = sigma0 + + def measure_f(x: np.ndarray) -> np.ndarray: + x = x.reshape(-1, 1) + sol = self._model.get_state(inputs=self._state.inputs, t=self._state.t, x=x) + return self.get_measure(sol).reshape(-1) + + self._ukf = SquareRootUKF( + x0=x0, + P0=sigma0, + Rp=process, + Rm=measure, + f=None, + h=measure_f, + ) + + def reset(self, inputs: Inputs) -> None: + super().reset(inputs) + self._ukf.reset(self.get_current_state().as_ndarray(), self._sigma0) + + def observe(self, time: float, value: np.ndarray) -> float: + if value is None: + raise ValueError("Measurement must be provided.") + elif isinstance(value, np.floating): + value = np.array([value]) + + dt = time - self.get_current_time() + if dt < 0: + raise ValueError("Time must be increasing.") + + if dt == 0: + + def f(x: np.ndarray) -> np.ndarray: + return x + + else: + + def f(x: np.ndarray) -> np.ndarray: + x = x.reshape(-1, 1) + sol = self._model.get_state( + inputs=self._state.inputs, t=self._state.t, x=x + ) + return self._model.step(sol, time).as_ndarray().reshape(-1) + + self._ukf.f = f + self._ukf.Rp = dt * self._process + log_likelihood = self._ukf.step(value) + self._state = self._model.get_state( + inputs=self._state.inputs, t=time, x=self._ukf.x + ) + return log_likelihood + + def get_current_covariance(self) -> Covariance: + # Get the covariance from the square-root covariance + return self._ukf.S @ self._ukf.S.T + + +@dataclass +class SigmaPoint(object): + """ + A sigma point is a point in the state space that is used to estimate the mean and covariance of a random variable. + """ + + x: np.ndarray + w_m: float + w_c: float + + +class SquareRootUKF(object): + """ + van der Menve, R., & Wan, E. A. (2001). THE SQUARE-ROOT UNSCENTED KALMAN FILTER FOR STATE AND PARAMETER-ESTIMATION. + https://doi.org/10.1109/ICASSP.2001.940586 + + We implement a square root unscented Kalman filter (UKF) with additive process and measurement noise. + + The square root UKF is a variant of the UKF that is more numerically stable and has better performance. + + Parameters + ---------- + x0 : np.ndarray + The initial state vector + P0 : np.ndarray + The initial covariance matrix + Rp : np.ndarray + The covariance matrix of the process noise + Rm : np.ndarray + The covariance matrix of the measurement noise + f : callable + The state transition function + h : callable + The measurement function + """ + + def __init__( + self, + x0: np.ndarray, + P0: np.ndarray, + Rp: np.ndarray, + Rm: np.ndarray, + f: callable, + h: callable, + ) -> None: + # Find states that are zero in both sigma0 and process + zero_rows = np.logical_and(np.all(P0 == 0, axis=0), np.all(Rp == 0, axis=0)) + zero_cols = np.logical_and(np.all(P0 == 0, axis=1), np.all(Rp == 0, axis=1)) + zeros = np.logical_and(zero_rows, zero_cols) + ones = np.logical_not(zeros) + states = np.array(range(len(x0)))[ones] + bool_mask = np.ix_(ones, ones) + + S_filtered = linalg.cholesky(P0[ones, :][:, ones]) + sqrtRp_filtered = linalg.cholesky(Rp[ones, :][:, ones]) + + n = len(x0) + S = np.zeros((n, n)) + sqrtRp = np.zeros((n, n)) + S[bool_mask] = S_filtered + sqrtRp[bool_mask] = sqrtRp_filtered + + self.x = x0 + self.S = S + self.sqrtRp = sqrtRp + self.sqrtRm = linalg.cholesky(Rm) + self.alpha = 1e-3 + self.beta = 2 + self.f = f + self.h = h + self.states = states + self.bool_mask = bool_mask + + def reset(self, x: np.ndarray, S: np.ndarray) -> None: + self.x = x + S_filtered = S[self.states, :][:, self.states] + S_filtered = linalg.cholesky(S_filtered) + S_full = S.copy() + S_full[self.bool_mask] = S_filtered + self.S = S_full + + @staticmethod + def gen_sigma_points( + x: np.ndarray, S: np.ndarray, alpha: float, beta: float, states: np.ndarray + ) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: + """ + Generates 2L+1 sigma points for the unscented transform, where L is the number of states. + + Parameters + ---------- + x : np.ndarray + The state vector + S : np.ndarray + The square root of the covariance matrix + alpha : float + The spread of the sigma points. Typically 1e-4 < alpha < 1 + beta : float + The prior knowledge of the distribution. Typically 2 for a Gaussian distribution + states: np.ndarray + array of indices of states to use for the sigma points + + Returns + ------- + List[np.ndarray] + The sigma points + List[float] + The weights of the sigma points + List[float] + The weights of the covariance of the sigma points + """ + # Set the scaling parameters: sigma and eta + kappa = 0.0 + L = len(states) + sigma = alpha**2 * (L + kappa) - L + eta = np.sqrt(L + sigma) + + # Define the sigma points + points = np.hstack( + [x] + + [x + eta * S[:, i].reshape(-1, 1) for i in states] + + [x - eta * S[:, i].reshape(-1, 1) for i in states] + ) + + # Define the weights of the sigma points + w_m0 = sigma / (L + sigma) + w_m = np.array([w_m0] + [1 / (2 * (L + sigma))] * (2 * L)) + + # Define the weights of the covariance of the sigma points + w_c0 = w_m0 + (1 - alpha**2 + beta) + w_c = np.array([w_c0] + [1 / (2 * (L + sigma))] * (2 * L)) + + return (points, w_m, w_c) + + @staticmethod + def unscented_transform( + sigma_points: np.ndarray, + w_m: np.ndarray, + w_c: np.ndarray, + sqrtR: np.ndarray, + states: Union[np.ndarray, None] = None, + ) -> Tuple[np.ndarray, np.ndarray]: + """ + Performs the unscented transform + + Parameters + ---------- + sigma_points : List[SigmaPoint] + The sigma points + sqrtR : np.ndarray + The square root of the covariance matrix + states: np.ndarray + array of indices of states to use for the transform + + Returns + ------- + Tuple[np.ndarray, np.ndarray] + The mean and square-root covariance of the sigma points + """ + # Update the predicted mean of the sigma points + x = np.sum(w_m * sigma_points, axis=1).reshape(-1, 1) + + # Update the predicted square-root covariance + if states is None: + sigma_points_diff = sigma_points - x + A = np.hstack([np.sqrt(w_c[1:]) * (sigma_points_diff[:, 1:]), sqrtR]) + (_, S) = linalg.qr(A.T, mode="economic") + S = SquareRootUKF.cholupdate(S, sigma_points_diff[:, 0:1], w_c[0]) + else: + # First overwrite states without noise to remove numerial error + clean = np.full(len(x), True) + clean[states] = False + x[clean] = sigma_points[clean, 0].reshape(-1, 1) + + sigma_points_diff = sigma_points[states, :] - x[states] + A = np.hstack( + [ + np.sqrt(w_c[1:]) * (sigma_points_diff[:, 1:]), + sqrtR[states, :][:, states], + ] + ) + (_, S_filtered) = linalg.qr(A.T, mode="economic") + S_filtered = SquareRootUKF.cholupdate( + S_filtered, sigma_points_diff[:, 0:1], w_c[0] + ) + ones = np.logical_not(clean) + S = np.zeros_like(sqrtR) + S[np.ix_(ones, ones)] = S_filtered + + return x, S + + @staticmethod + def filtered_cholupdate( + R: np.ndarray, x: np.ndarray, w: float, states: np.ndarray + ) -> np.ndarray: + R_full = R.copy() + R_filtered = R[states, :][:, states] + x_filtered = x[states] + R_filtered = SquareRootUKF.cholupdate(R_filtered, x_filtered, w) + ones = np.full(len(x), False) + ones[states] = True + R_full[np.ix_(ones, ones)] = R_filtered + return R_full + + @staticmethod + def cholupdate(R: np.ndarray, x: np.ndarray, w: float) -> np.ndarray: + """ + Updates the Cholesky decomposition of a matrix (see https://github.com/modusdatascience/choldate/blob/master/choldate/_choldate.pyx) + + Note: will be in scipy soon so replace with this: https://github.com/scipy/scipy/pull/16499 + + TODO: need to replace with something more low-level + + Parameters + ---------- + R : np.ndarray + The Cholesky decomposition of the matrix + x : np.ndarray + The vector to add to the matrix + w : float + The weight of the vector + + Returns + ------- + np.ndarray + The updated Cholesky decomposition + """ + sign = np.sign(w) + x = np.sqrt(abs(w)) * x.flatten() + p = x.shape[0] + for k in range(p): + Rkk = abs(R[k, k]) + xk = abs(x[k]) + r = SquareRootUKF.hypot(Rkk, xk, sign) + c = r / R[k, k] + s = x[k] / R[k, k] + R[k, k] = r + if k < p - 1: + R[k, k + 1 :] = (R[k, k + 1 :] + sign * s * x[k + 1 :]) / c + x[k + 1 :] = c * x[k + 1 :] - s * R[k, k + 1 :] + return R + + def hypot(R: float, x: float, sign: float) -> float: + if R < x: + return R * np.sqrt(1 + sign * R**2 / x**2) + elif x < R: + return np.sqrt(R**2 + sign * x**2) + else: + return 0.0 + + def step(self, y: np.ndarray) -> float: + """ + Steps the filter forward one step using a measurement. Returns the log likelihood of the measurement. + + Parameters + ---------- + y : np.ndarray + The measurement vector + + Returns + ------- + float + The log likelihood of the measurement + """ + # Sigma point calculation + sigma_points, w_m, w_c = self.gen_sigma_points( + self.x, self.S, self.alpha, self.beta, self.states + ) + + # Update sigma points in time + sigma_points = np.apply_along_axis(self.f, 0, sigma_points) + + # Compute the mean and square-root covariance + x_minus, S_minus = self.unscented_transform( + sigma_points, w_m, w_c, self.sqrtRp, self.states + ) + + # Compute the output corresponding to the updated sigma points + sigma_points_y = np.apply_along_axis(self.h, 0, sigma_points) + + # Compute the mean and square-root covariance + y_minus, S_y = self.unscented_transform(sigma_points_y, w_m, w_c, self.sqrtRm) + + # Compute the gain from the covariance + P = np.einsum( + "k,jk,lk -> jl ", w_c, sigma_points - x_minus, sigma_points_y - y_minus + ) + gain = linalg.lstsq(linalg.lstsq(P.T, S_y.transpose())[0].T, S_y)[0] + + # Update the states and square-root covariance based on the gain + residual = y - y_minus + self.x = x_minus + gain @ residual + U = gain @ S_y + self.S = self.filtered_cholupdate(S_minus, U, -1, self.states) + + # Compute the log-likelihood of the covariance + S = self.S[self.states, :][:, self.states] + log_det = 2 * np.sum(np.log(np.diag(S))) + n = len(y) + log_likelihood = -0.5 * ( + n * log_det + residual.T @ linalg.cho_solve((S_y, True), residual) + ) + return np.sum(log_likelihood) diff --git a/pybop/parameters/parameter.py b/pybop/parameters/parameter.py index 15dffecb..6136b0ac 100644 --- a/pybop/parameters/parameter.py +++ b/pybop/parameters/parameter.py @@ -28,12 +28,15 @@ class Parameter: the margin is set outside the interval (0, 1). """ - def __init__(self, name, initial_value=None, prior=None, bounds=None): + def __init__( + self, name, initial_value=None, true_value=None, prior=None, bounds=None + ): """ Construct the parameter class with a name, initial value, prior, and bounds. """ self.name = name self.prior = prior + self.true_value = true_value self.initial_value = initial_value self.value = initial_value self.bounds = bounds @@ -69,7 +72,7 @@ def rvs(self, n_samples): return samples - def update(self, value): + def update(self, value=None, initial_value=None): """ Update the parameter's current value. @@ -78,7 +81,12 @@ def update(self, value): value : float The new value to be assigned to the parameter. """ - self.value = value + if value is not None: + self.value = value + elif initial_value is not None: + self.value = initial_value + else: + raise ValueError("No value provided to update parameter") def __repr__(self): """ diff --git a/pybop/parameters/parameter_set.py b/pybop/parameters/parameter_set.py index 946d05ba..8a99b8b2 100644 --- a/pybop/parameters/parameter_set.py +++ b/pybop/parameters/parameter_set.py @@ -152,4 +152,10 @@ def pybamm(cls, name): pybamm.ParameterValues A PyBaMM parameter set corresponding to the provided name. """ + + msg = f"Parameter set '{name}' is not a valid PyBaMM parameter set. Available parameter sets are: {list(pybamm.parameter_sets)}" + + if name not in list(pybamm.parameter_sets): + raise ValueError(msg) + return pybamm.ParameterValues(name).copy() diff --git a/pybop/plotting/plot_cost2d.py b/pybop/plotting/plot_cost2d.py index ee43d9d0..1565ed24 100644 --- a/pybop/plotting/plot_cost2d.py +++ b/pybop/plotting/plot_cost2d.py @@ -46,7 +46,7 @@ def plot_cost2d(cost, bounds=None, optim=None, steps=10): # Populate cost matrix for i, xi in enumerate(x): for j, yj in enumerate(y): - costs[j, i] = cost([xi, yj]) + costs[j, i] = cost(np.array([xi, yj])) # Create figure fig = create_figure(x, y, costs, bounds, cost.problem.parameters, optim) diff --git a/pybop/plotting/quick_plot.py b/pybop/plotting/quick_plot.py index 56593efe..56487a8c 100644 --- a/pybop/plotting/quick_plot.py +++ b/pybop/plotting/quick_plot.py @@ -154,8 +154,13 @@ def create_traces(self): ) if self.y2 is not None: + if isinstance(self.cost.problem, pybop.DesignProblem): + name = "Initial" + else: + name = "Target" + target_trace = self.go.Scatter( - x=self.x, y=self.y2, mode="markers", name="Target" + x=self.x, y=self.y2, mode="markers", name=name ) fill_trace = self.go.Scatter( x=self.x + self.x[::-1], @@ -214,6 +219,10 @@ def quick_plot(params, cost, title="Scatter Plot", width=1024, height=576): time_data = cost.problem.time_data() model_output = cost.problem.evaluate(params) target_output = cost.problem.target() + if isinstance(cost.problem, pybop.DesignProblem): + trace_name = "Optimised" + else: + trace_name = "Model" # Ensure outputs have the same length len_diff = len(target_output) - len(model_output) @@ -235,7 +244,7 @@ def quick_plot(params, cost, title="Scatter Plot", width=1024, height=576): xaxis_title="Time [s]", yaxis_title=cost.problem.signal[i], title=title, - trace_name="Model", + trace_name=trace_name, width=width, height=height, )() diff --git a/pybop/version.py b/pybop/version.py deleted file mode 100644 index f3d58cf8..00000000 --- a/pybop/version.py +++ /dev/null @@ -1 +0,0 @@ -__version__ = "23.12" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..43928f48 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,69 @@ +[build-system] +requires = ["setuptools>=64"] +build-backend = "setuptools.build_meta" + +[project] +name = "pybop" +version = "23.12" +authors = [ + {name = "The PyBOP Team"}, +] +maintainers = [ + {name = "The PyBOP Team"}, +] +description = "Python Battery Optimisation and Parameterisation" +readme = "README.md" +license = { file = "LICENSE" } +# https://pypi.org/classifiers/ +classifiers = [] +requires-python = ">=3.8, <3.13" +dependencies = [ + "pybamm>=23.5", + "numpy>=1.16", + "scipy>=1.3", + "pandas>=1.0", + "pints>=0.5", +] + +[project.optional-dependencies] +plot = ["plotly>=5.0"] +docs = [ + "pydata-sphinx-theme", + "sphinx>=6", + "sphinx-autobuild", + "sphinx-autoapi", + "sphinx_copybutton", + "sphinx_favicon", + "sphinx_design", + "myst-parser", +] +dev = [ + "nox", + "nbmake", + "pre-commit", + "pytest>=6", + "pytest-cov", + "pytest-mock", + "pytest-xdist", + "ruff", + ] +all = ["pybop[plot]"] + +[tool.setuptools.packages.find] +include = ["pybop", "pybop.*"] + +[project.urls] +Homepage = "https://github.com/pybop-team/PyBOP" + +[tool.pytest.ini_options] +addopts = "--showlocals -v" + +[tool.ruff] +extend-include = ["*.ipynb"] +extend-exclude = ["__init__.py"] + +[tool.ruff.lint] +ignore = ["E501","E741"] + +[tool.ruff.lint.per-file-ignores] +"**.ipynb" = ["E402", "E703"] diff --git a/pytest.ini b/pytest.ini deleted file mode 100644 index ab0a003d..00000000 --- a/pytest.ini +++ /dev/null @@ -1,3 +0,0 @@ -# pytest.ini -[pytest] -addopts = --showlocals -v diff --git a/ruff.toml b/ruff.toml deleted file mode 100644 index 29a4a244..00000000 --- a/ruff.toml +++ /dev/null @@ -1,8 +0,0 @@ -extend-include = ["*.ipynb"] -extend-exclude = ["__init__.py"] - -[lint] -ignore = ["E501","E741"] - -[lint.per-file-ignores] -"**.ipynb" = ["E402", "E703"] diff --git a/scripts/ci/build_matrix.sh b/scripts/ci/build_matrix.sh new file mode 100755 index 00000000..5e580303 --- /dev/null +++ b/scripts/ci/build_matrix.sh @@ -0,0 +1,47 @@ +#!/bin/bash + +# This helper script generates a matrix for further use in the +# scheduled/nightly builds for PyBOP, i.e., in scheduled_tests.yaml +# It generates a matrix of all combinations of the following variables: +# - python_version: 3.X +# - os: ubuntu-latest, windows-latest, macos-latest +# - pybamm_version: the last X versions of PyBaMM from PyPI, excluding release candidates + +# To update the matrix, the variables below can be modified as needed. + +python_version=("3.8" "3.9" "3.10" "3.11" "3.12") +os=("ubuntu-latest" "windows-latest" "macos-latest") +# This command fetches the last three PyBaMM versions excluding release candidates from PyPI +pybamm_version=($(curl -s https://pypi.org/pypi/pybamm/json | jq -r '.releases | keys[]' | grep -v rc | tail -n 3 | paste -sd " " -)) + +# open dict +json='{ + "include": [ +' + +# loop through each combination of variables to generate matrix components +for py_ver in "${python_version[@]}"; do + for os_type in "${os[@]}"; do + for pybamm_ver in "${pybamm_version[@]}"; do + json+='{ + "os": "'$os_type'", + "python_version": "'$py_ver'", + "pybamm_version": "'$pybamm_ver'" + },' + done + done +done + +# fix structure, removing trailing comma +json=${json%,} + +# close dict +json+=' + ] +}' + +# Filter out incompatible combinations +json=$(echo "$json" | jq -c 'del(.include[] | select(.pybamm_version == "23.5" and .python_version == "3.12"))') +json=$(echo "$json" | jq -c 'del(.include[] | select(.pybamm_version == "23.9" and .python_version == "3.12"))') + +echo "$json" | jq -c . diff --git a/setup.py b/setup.py deleted file mode 100644 index 5a369780..00000000 --- a/setup.py +++ /dev/null @@ -1,51 +0,0 @@ -from distutils.core import setup -import os -from setuptools import find_packages - -# User-friendly description from README.md -current_directory = os.path.dirname(os.path.abspath(__file__)) -try: - with open(os.path.join(current_directory, "README.md"), encoding="utf-8") as f: - long_description = f.read() -except Exception: - long_description = "" - -# Defines __version__ -root = os.path.abspath(os.path.dirname(__file__)) -with open(os.path.join(root, "pybop", "version.py")) as f: - exec(f.read()) - -setup( - name="pybop", - packages=find_packages("."), - version=__version__, # noqa F821 - license="BSD-3-Clause", - description="Python Battery Optimisation and Parameterisation", - long_description=long_description, - long_description_content_type="text/markdown", - url="https://github.com/pybop-team/PyBOP", - install_requires=[ - "pybamm>=23.5", - "numpy>=1.16", - "scipy>=1.3", - "pandas>=1.0", - "pints>=0.5", - ], - extras_require={ - "plot": ["plotly>=5.0"], - "all": ["pybop[plot]"], - "docs": [ - "sphinx>=6", - "pydata-sphinx-theme", - "sphinx-autobuild", - "sphinx-autoapi", - "sphinx_copybutton", - "sphinx_favicon", - "sphinx_design", - "myst-parser", - ], - }, - # https://pypi.org/classifiers/ - classifiers=[], - python_requires=">=3.8,<=3.12", -) diff --git a/tests/unit/test_cost.py b/tests/unit/test_cost.py index 3a37bc50..aa127ffd 100644 --- a/tests/unit/test_cost.py +++ b/tests/unit/test_cost.py @@ -1,3 +1,4 @@ +from __future__ import annotations import pytest import pybop import numpy as np @@ -8,24 +9,42 @@ class TestCosts: Class for tests cost functions """ - @pytest.mark.parametrize("cut_off", [2.5, 3.777]) - @pytest.mark.unit - def test_costs(self, cut_off): - # Construct model - model = pybop.lithium_ion.SPM() + @pytest.fixture + def model(self): + return pybop.lithium_ion.SPM() - parameters = [ + @pytest.fixture + def parameters(self): + return [ pybop.Parameter( "Negative electrode active material volume fraction", - prior=pybop.Gaussian(0.5, 0.02), + prior=pybop.Gaussian(0.5, 0.01), bounds=[0.375, 0.625], - ) + ), ] - # Form dataset - x0 = np.array([0.52]) - solution = self.getdata(model, x0) - dataset = pybop.Dataset( + @pytest.fixture + def experiment(self): + return pybop.Experiment( + [ + ("Discharge at 1C for 10 minutes (20 second period)"), + ] + ) + + @pytest.fixture + def x0(self): + return np.array([0.52]) + + @pytest.fixture + def dataset(self, model, experiment, x0): + model.parameter_set = model.pybamm_model.default_parameter_values + model.parameter_set.update( + { + "Negative electrode active material volume fraction": x0[0], + } + ) + solution = model.predict(experiment=experiment) + return pybop.Dataset( { "Time [s]": solution["Time [s]"].data, "Current function [A]": solution["Current [A]"].data, @@ -33,70 +52,153 @@ def test_costs(self, cut_off): } ) - # Construct Problem - signal = ["Voltage [V]"] + @pytest.fixture + def signal(self): + return "Voltage [V]" + + @pytest.fixture(params=[2.5, 3.777]) + def problem(self, model, parameters, dataset, signal, x0, request): + cut_off = request.param model.parameter_set.update({"Lower voltage cut-off [V]": cut_off}) - problem = pybop.FittingProblem(model, parameters, dataset, signal=signal, x0=x0) + problem = pybop.FittingProblem( + model, parameters, dataset, signal=signal, x0=x0, init_soc=1.0 + ) + return problem + + @pytest.fixture( + params=[pybop.RootMeanSquaredError, pybop.SumSquaredError, pybop.ObserverCost] + ) + def cost(self, problem, request): + cls = request.param + if cls == pybop.RootMeanSquaredError or cls == pybop.SumSquaredError: + return cls(problem) + elif cls == pybop.ObserverCost: + inputs = {p.name: problem.x0[i] for i, p in enumerate(problem.parameters)} + state = problem._model.reinit(inputs) + n = len(state) + sigma_diag = [0.0] * n + sigma_diag[0] = 1e-4 + sigma_diag[1] = 1e-4 + process_diag = [0.0] * n + process_diag[0] = 1e-4 + process_diag[1] = 1e-4 + sigma0 = np.diag(sigma_diag) + process = np.diag(process_diag) + dataset = type("dataset", (object,), {"data": problem._dataset})() + return cls( + pybop.UnscentedKalmanFilterObserver( + problem.parameters, + problem._model, + sigma0=sigma0, + process=process, + measure=1e-4, + dataset=dataset, + signal=problem.signal, + ), + ) - # Base Cost + @pytest.mark.unit + def test_base(self, problem): base_cost = pybop.BaseCost(problem) assert base_cost.problem == problem with pytest.raises(NotImplementedError): - base_cost._evaluate([0.5]) - base_cost._evaluateS1([0.5]) + base_cost([0.5]) + with pytest.raises(NotImplementedError): + base_cost.evaluateS1([0.5]) - # Root Mean Squared Error - rmse_cost = pybop.RootMeanSquaredError(problem) - rmse_cost([0.5]) + @pytest.mark.unit + def test_design_base(self, problem): + design_cost = pybop.DesignCost(problem) + with pytest.raises(NotImplementedError): + design_cost([0.5]) - # Sum Squared Error - sums_cost = pybop.SumSquaredError(problem) - sums_cost([0.5]) + @pytest.mark.unit + def test_costs(self, cost): + higher_cost = cost([0.55]) + lower_cost = cost([0.52]) + assert higher_cost > lower_cost or ( + higher_cost == lower_cost and higher_cost == np.inf + ) # Test type of returned value - assert type(rmse_cost([0.5])) == np.float64 - assert rmse_cost([0.5]) >= 0 + assert type(cost([0.5])) == np.float64 - assert type(sums_cost([0.5])) == np.float64 - assert sums_cost([0.5]) >= 0 - e, de = sums_cost.evaluateS1([0.5]) - assert type(e) == np.float64 - assert type(de) == np.ndarray + if isinstance(cost, pybop.ObserverCost): + with pytest.raises(NotImplementedError): + cost.evaluateS1([0.5]) - # Test option setting - sums_cost.set_fail_gradient(1) + # Test UserWarnings + if isinstance(cost, (pybop.SumSquaredError, pybop.RootMeanSquaredError)): + assert cost([0.5]) >= 0 + with pytest.warns(UserWarning) as record: + cost([1.1]) - # Test infeasible locations - rmse_cost.problem._model.allow_infeasible_solutions = False - assert rmse_cost([1.1]) == np.inf + if isinstance(cost, pybop.SumSquaredError): + e, de = cost.evaluateS1([0.5]) - # Test UserWarnings - with pytest.warns(UserWarning) as record: - rmse_cost([1.1]) - sums_cost.evaluateS1([1.1]) + assert type(e) == np.float64 + assert type(de) == np.ndarray + + # Test option setting + cost.set_fail_gradient(1) + + # Test exception for non-numeric inputs + with pytest.raises(ValueError): + cost.evaluateS1(["StringInputShouldNotWork"]) - assert len(record) == 2 - for i in range(len(record)): - assert "Non-physical point encountered" in str(record[i].message) + with pytest.warns(UserWarning) as record: + cost.evaluateS1([1.1]) + + for i in range(len(record)): + assert "Non-physical point encountered" in str(record[i].message) + + if isinstance(cost, pybop.RootMeanSquaredError): + # Test infeasible locations + cost.problem._model.allow_infeasible_solutions = False + assert cost([1.1]) == np.inf # Test exception for non-numeric inputs with pytest.raises(ValueError): - rmse_cost(["StringInputShouldNotWork"]) - with pytest.raises(ValueError): - sums_cost(["StringInputShouldNotWork"]) - with pytest.raises(ValueError): - sums_cost.evaluateS1(["StringInputShouldNotWork"]) + cost(["StringInputShouldNotWork"]) # Test treatment of simulations that terminated early # by variation of the cut-off voltage. - def getdata(self, model, x0): - model.parameter_set = model.pybamm_model.default_parameter_values - model.parameter_set.update( - { - "Negative electrode active material volume fraction": x0[0], - } + @pytest.mark.parametrize( + "cost_class", + [pybop.GravimetricEnergyDensity, pybop.VolumetricEnergyDensity], + ) + @pytest.mark.unit + def test_energy_density_costs( + self, + cost_class, + model, + parameters, + experiment, + signal, + ): + # Construct Problem + problem = pybop.DesignProblem( + model, parameters, experiment, signal=signal, init_soc=0.5 ) - sim = model.predict(t_eval=np.linspace(0, 10, 100)) - return sim + # Construct Cost + cost = cost_class(problem) + + # Test type of returned value + assert type(cost([0.5])) == np.float64 + assert cost([0.4]) <= 0 # Should be a viable design + assert cost([0.8]) == np.inf # Should exceed active material + porosity < 1 + assert cost([1.4]) == np.inf # Definitely not viable + + # Test infeasible locations + cost.problem._model.allow_infeasible_solutions = False + assert cost([1.1]) == np.inf + + # Test exception for non-numeric inputs + with pytest.raises(ValueError): + cost(["StringInputShouldNotWork"]) + + # Compute after updating nominal capacity + cost = cost_class(problem, update_capacity=True) + cost([0.4]) diff --git a/tests/unit/test_dataset.py b/tests/unit/test_dataset.py index b5cfa96d..934e0f4a 100644 --- a/tests/unit/test_dataset.py +++ b/tests/unit/test_dataset.py @@ -28,6 +28,7 @@ def test_dataset(self): # Test data structure assert dataset.data == data_dictionary + assert np.all(dataset["Time [s]"] == solution["Time [s]"].data) # Test exception for non-dictionary inputs with pytest.raises(ValueError): diff --git a/tests/unit/test_models.py b/tests/unit/test_models.py index fc3e71d1..dfce88e4 100644 --- a/tests/unit/test_models.py +++ b/tests/unit/test_models.py @@ -1,6 +1,9 @@ import pybop import pytest import numpy as np +import pybamm + +from examples.standalone.model import ExponentialDecay class TestModels: @@ -73,3 +76,141 @@ def test_build(self): # Test that the model can be built again model.build() assert model.built_model is not None + + @pytest.mark.unit + def test_rebuild(self): + model = pybop.lithium_ion.SPM() + model.build() + initial_built_model = model._built_model + assert model._built_model is not None + + # Test that the model can be built again + model.rebuild() + rebuilt_model = model._built_model + assert rebuilt_model is not None + + # Filter out special and private attributes + attributes_to_compare = [ + "algebraic", + "bcs", + "boundary_conditions", + "mass_matrix", + "parameters", + "submodels", + "summary_variables", + "rhs", + "variables", + "y_slices", + ] + + # Loop through the filtered attributes and compare them + for attribute in attributes_to_compare: + assert getattr(rebuilt_model, attribute) == getattr( + initial_built_model, attribute + ) + + @pytest.mark.unit + def test_rebuild_geometric_parameters(self): + parameter_set = pybop.ParameterSet.pybamm("Chen2020") + parameters = [ + pybop.Parameter( + "Positive particle radius [m]", + prior=pybop.Gaussian(4.8e-06, 0.05e-06), + bounds=[4e-06, 6e-06], + initial_value=4.8e-06, + ), + pybop.Parameter( + "Negative electrode thickness [m]", + prior=pybop.Gaussian(40e-06, 1e-06), + bounds=[30e-06, 50e-06], + initial_value=48e-06, + ), + ] + + model = pybop.lithium_ion.SPM(parameter_set=parameter_set) + model.build(parameters=parameters) + initial_built_model = model.copy() + assert initial_built_model._built_model is not None + + # Run prediction + t_eval = np.linspace(0, 100, 100) + out_init = initial_built_model.predict(t_eval=t_eval) + + # Test that the model can be rebuilt with different geometric parameters + parameters[0].update(5e-06) + parameters[1].update(45e-06) + model.rebuild(parameters=parameters) + rebuilt_model = model + assert rebuilt_model._built_model is not None + + # Test model geometry + assert ( + rebuilt_model._mesh["negative electrode"].nodes[1] + != initial_built_model._mesh["negative electrode"].nodes[1] + ) + assert ( + rebuilt_model.geometry["negative electrode"]["x_n"]["max"] + != initial_built_model.geometry["negative electrode"]["x_n"]["max"] + ) + + assert ( + rebuilt_model.geometry["positive particle"]["r_p"]["max"] + != initial_built_model.geometry["positive particle"]["r_p"]["max"] + ) + + assert ( + rebuilt_model._mesh["positive particle"].nodes[1] + != initial_built_model._mesh["positive particle"].nodes[1] + ) + + # Compare model results + out_rebuild = rebuilt_model.predict(t_eval=t_eval) + with pytest.raises(AssertionError): + np.testing.assert_allclose( + out_init["Terminal voltage [V]"].data, + out_rebuild["Terminal voltage [V]"].data, + atol=1e-5, + ) + + @pytest.mark.unit + def test_reinit(self): + k = 0.1 + y0 = 1 + model = ExponentialDecay(pybamm.ParameterValues({"k": k, "y0": y0})) + model.build() + state = model.reinit(inputs={}) + np.testing.assert_array_almost_equal(state.as_ndarray(), np.array([[y0]])) + + state = model.reinit(inputs=[]) + np.testing.assert_array_almost_equal(state.as_ndarray(), np.array([[y0]])) + + model = ExponentialDecay(pybamm.ParameterValues({"k": k, "y0": y0})) + with pytest.raises(ValueError): + model.reinit(inputs={}) + + @pytest.mark.unit + def test_simulate(self): + k = 0.1 + y0 = 1 + model = ExponentialDecay(pybamm.ParameterValues({"k": k, "y0": y0})) + model.build() + model.signal = ["y_0"] + inputs = {} + t_eval = np.linspace(0, 10, 100) + expected = y0 * np.exp(-k * t_eval).reshape(-1, 1) + solved = model.simulate(inputs, t_eval) + np.testing.assert_array_almost_equal(solved, expected, decimal=5) + + @pytest.mark.unit + def test_basemodel(self): + base = pybop.BaseModel() + x = np.array([1, 2, 3]) + + with pytest.raises(NotImplementedError): + base.cell_mass() + + with pytest.raises(NotImplementedError): + base.cell_volume() + + with pytest.raises(NotImplementedError): + base.approximate_capacity(x) diff --git a/tests/unit/test_observer_unscented_kalman.py b/tests/unit/test_observer_unscented_kalman.py new file mode 100644 index 00000000..e89db15b --- /dev/null +++ b/tests/unit/test_observer_unscented_kalman.py @@ -0,0 +1,159 @@ +import pybop +import numpy as np +import pybamm +import pytest +from pybop.observers.unscented_kalman import SquareRootUKF +from examples.standalone.model import ExponentialDecay + + +class TestUKF: + """ + A class to test the unscented kalman filter. + """ + + measure_noise = 1e-4 + + @pytest.fixture(params=[1, 2, 3]) + def model(self, request): + model = ExponentialDecay( + parameter_set=pybamm.ParameterValues({"k": "[input]", "y0": "[input]"}), + n_states=request.param, + ) + model.build() + return model + + @pytest.fixture + def parameters(self): + return [ + pybop.Parameter( + "k", + prior=pybop.Gaussian(0.1, 0.05), + bounds=[0, 1], + ), + pybop.Parameter( + "y0", + prior=pybop.Gaussian(1, 0.05), + bounds=[0, 3], + ), + ] + + @pytest.fixture + def x0(self): + return np.array([0.1, 1.0]) + + @pytest.fixture + def dataset(self, model: pybop.BaseModel, parameters, x0): + observer = pybop.Observer(parameters, model, signal=["2y"], x0=x0) + measurements = [] + t_eval = np.linspace(0, 20, 10) + for t in t_eval: + observer.observe(t) + m = observer.get_current_measure() + np.random.normal( + 0, np.sqrt(self.measure_noise) + ) + measurements.append(m) + measurements = np.hstack(measurements) + return {"Time [s]": t_eval, "y": measurements} + + @pytest.fixture + def observer(self, model: pybop.BaseModel, parameters, x0): + n = model.n_states + sigma0 = np.diag([self.measure_noise] * n) + process = np.diag([1e-6] * n) + # for 3rd model, set sigma0 and process to zero for the 1st and 2nd state + if n == 3: + sigma0[0, 0] = 0 + sigma0[1, 1] = 0 + process[0, 0] = 0 + process[1, 1] = 0 + measure = np.diag([1e-4]) + observer = pybop.UnscentedKalmanFilterObserver( + parameters, model, sigma0, process, measure, signal=["2y"], x0=x0 + ) + return observer + + @pytest.mark.unit + def test_cholupdate(self): + # Create a random positive definite matrix, V + np.random.seed(1) + X = np.random.normal(size=(100, 10)) + V = np.dot(X.transpose(), X) + + # Calculate the upper Cholesky factor, R + R = np.linalg.cholesky(V).transpose() + + # Create a random update vector, u + u = np.random.normal(size=R.shape[0]) + + # Calculate the updated positive definite matrix, V1, and its Cholesky factor, R1 + V1 = V + np.outer(u, u) + R1 = np.linalg.cholesky(V1).transpose() + + # The following is equivalent to the above + R1_ = R.copy() + SquareRootUKF.cholupdate(R1_, u.copy(), 1.0) + np.testing.assert_array_almost_equal(R1, R1_) + + @pytest.mark.unit + def test_unscented_kalman_filter(self, dataset, observer): + t_eval = dataset["Time [s]"] + measurements = dataset["y"] + inputs = observer._state.inputs + n = observer._model.n_states + expected = inputs["y0"] * np.exp(-inputs["k"] * t_eval) + + for i, t in enumerate(t_eval): + y = np.array([[expected[i]]] * n) + ym = measurements[:, i] + observer.observe(t, ym) + np.testing.assert_array_almost_equal( + observer.get_current_state().as_ndarray(), + y, + decimal=4, + ) + np.testing.assert_array_almost_equal( + observer.get_current_measure(), + np.array([2 * y[0]]), + decimal=4, + ) + + @pytest.mark.unit + def test_observe_no_measurement(self, observer): + with pytest.raises(ValueError): + observer.observe(0, None) + + @pytest.mark.unit + def test_observe_decreasing_time(self, observer): + observer.observe(0, np.array([2])) + observer.observe(0.1, np.array([2])) + with pytest.raises(ValueError): + observer.observe(0, np.array([2])) + + @pytest.mark.unit + def test_wrong_input_shapes(self, model, parameters): + signal = "2y" + n = model.n_states + + sigma0 = np.diag([1e-4] * (n + 1)) + process = np.diag([1e-4] * n) + measure = np.diag([1e-4]) + with pytest.raises(ValueError): + pybop.UnscentedKalmanFilterObserver( + parameters, model, sigma0, process, measure, signal=signal + ) + + sigma0 = np.diag([1e-4] * n) + process = np.diag([1e-4] * (n + 1)) + measure = np.diag([1e-4]) + with pytest.raises(ValueError): + pybop.UnscentedKalmanFilterObserver( + parameters, model, sigma0, process, measure, signal=signal + ) + + sigma0 = np.diag([1e-4] * n) + process = np.diag([1e-4] * n) + measure = np.diag([1e-4] * 2) + with pytest.raises(ValueError): + pybop.UnscentedKalmanFilterObserver( + parameters, model, sigma0, process, measure, signal=signal + ) diff --git a/tests/unit/test_observers.py b/tests/unit/test_observers.py new file mode 100644 index 00000000..020f8f91 --- /dev/null +++ b/tests/unit/test_observers.py @@ -0,0 +1,58 @@ +import pybop +import numpy as np +import pybamm +import pytest +from examples.standalone.model import ExponentialDecay + + +class TestObserver: + """ + A class to test the observer class. + """ + + @pytest.fixture(params=[1, 2]) + def model(self, request): + model = ExponentialDecay( + parameter_set=pybamm.ParameterValues({"k": "[input]", "y0": "[input]"}), + n_states=request.param, + ) + model.build() + return model + + @pytest.fixture + def parameters(self): + return [ + pybop.Parameter( + "k", + prior=pybop.Gaussian(0.1, 0.05), + bounds=[0, 1], + ), + pybop.Parameter( + "y0", + prior=pybop.Gaussian(1, 0.05), + bounds=[0, 3], + ), + ] + + @pytest.fixture + def x0(self): + return np.array([0.1, 1.0]) + + @pytest.mark.unit + def test_observer(self, model, parameters, x0): + n = model.n_states + observer = pybop.Observer(parameters, model, signal=["2y"], x0=x0) + t_eval = np.linspace(0, 1, 100) + expected = x0[1] * np.exp(-x0[0] * t_eval) + for y, t in zip(expected, t_eval): + observer.observe(t) + np.testing.assert_array_almost_equal( + observer.get_current_state().as_ndarray(), + np.array([[y]] * n), + decimal=4, + ) + np.testing.assert_array_almost_equal( + observer.get_current_measure(), + np.array([[2 * y]]), + decimal=4, + ) diff --git a/tests/unit/test_parameter_sets.py b/tests/unit/test_parameter_sets.py index fc9356d2..39d29d41 100644 --- a/tests/unit/test_parameter_sets.py +++ b/tests/unit/test_parameter_sets.py @@ -10,7 +10,7 @@ class TestParameterSets: @pytest.mark.unit def test_parameter_set(self): - # Tests parameter set creation + # Tests parameter set creation and validation with pytest.raises(ValueError): pybop.ParameterSet.pybamm("sChen2010s") diff --git a/tests/unit/test_parameters.py b/tests/unit/test_parameters.py new file mode 100644 index 00000000..52f0c69f --- /dev/null +++ b/tests/unit/test_parameters.py @@ -0,0 +1,53 @@ +import pybop +import pytest + + +class TestParameters: + """ + A class to test the parameter classes. + """ + + @pytest.fixture + def parameter(self): + return pybop.Parameter( + "Negative electrode active material volume fraction", + prior=pybop.Gaussian(0.6, 0.02), + bounds=[0.375, 0.7], + initial_value=0.6, + ) + + @pytest.mark.unit + def test_parameter_construction(self, parameter): + assert parameter.name == "Negative electrode active material volume fraction" + assert parameter.prior.mean == 0.6 + assert parameter.prior.sigma == 0.02 + assert parameter.bounds == [0.375, 0.7] + assert parameter.initial_value == 0.6 + + @pytest.mark.unit + def test_parameter_repr(self, parameter): + assert ( + repr(parameter) + == "Parameter: Negative electrode active material volume fraction \n Prior: Gaussian, mean: 0.6, sigma: 0.02 \n Bounds: [0.375, 0.7] \n Value: 0.6" + ) + + @pytest.mark.unit + def test_parameter_rvs(self, parameter): + samples = parameter.rvs(n_samples=500) + assert (samples >= 0.375).all() and (samples <= 0.7).all() + + @pytest.mark.unit + def test_parameter_update(self, parameter): + # Test value update + parameter.update(value=0.534) + assert parameter.value == 0.534 + + # Test initial value update + parameter.update(initial_value=0.654) + assert parameter.value == 0.654 + + @pytest.mark.unit + def test_parameter_margin(self, parameter): + assert parameter.margin == 1e-4 + parameter.set_margin(margin=1e-3) + assert parameter.margin == 1e-3 diff --git a/tests/unit/test_problem.py b/tests/unit/test_problem.py index b92b89a3..0cdfd8bc 100644 --- a/tests/unit/test_problem.py +++ b/tests/unit/test_problem.py @@ -16,14 +16,14 @@ def model(self): def parameters(self): return [ pybop.Parameter( - "Negative electrode active material volume fraction", - prior=pybop.Gaussian(0.5, 0.02), - bounds=[0.375, 0.625], + "Negative particle radius [m]", + prior=pybop.Gaussian(2e-05, 0.1e-5), + bounds=[1e-6, 5e-5], ), pybop.Parameter( - "Positive electrode active material volume fraction", - prior=pybop.Gaussian(0.65, 0.02), - bounds=[0.525, 0.75], + "Positive particle radius [m]", + prior=pybop.Gaussian(0.5e-05, 0.1e-5), + bounds=[1e-6, 5e-5], ), ] @@ -44,11 +44,11 @@ def experiment(self): @pytest.fixture def dataset(self, model, experiment): model.parameter_set = model.pybamm_model.default_parameter_values - x0 = np.array([0.52, 0.63]) + x0 = np.array([2e-5, 0.5e-5]) model.parameter_set.update( { - "Negative electrode active material volume fraction": x0[0], - "Positive electrode active material volume fraction": x0[1], + "Negative particle radius [m]": x0[0], + "Positive particle radius [m]": x0[1], } ) solution = model.predict(experiment=experiment) @@ -76,12 +76,12 @@ def test_base_problem(self, parameters, model): assert problem._model == model with pytest.raises(NotImplementedError): - problem.evaluate([0.5, 0.5]) + problem.evaluate([1e-5, 1e-5]) with pytest.raises(NotImplementedError): - problem.evaluateS1([0.5, 0.5]) + problem.evaluateS1([1e-5, 1e-5]) with pytest.raises(ValueError): - pybop._problem.BaseProblem(parameters, model=model, signal=[0.5, 0.5]) + pybop._problem.BaseProblem(parameters, model=model, signal=[1e-5, 1e-5]) @pytest.mark.unit def test_fitting_problem(self, parameters, dataset, model, signal): @@ -98,7 +98,7 @@ def test_fitting_problem(self, parameters, dataset, model, signal): assert problem._model._built_model is not None # Test model.simulate - model.simulate(inputs=[0.5, 0.5], t_eval=np.linspace(0, 10, 100)) + model.simulate(inputs=[1e-5, 1e-5], t_eval=np.linspace(0, 10, 100)) # Test problem construction errors for bad_dataset in [ @@ -147,5 +147,27 @@ def test_design_problem(self, parameters, experiment, model): ) # building postponed with input experiment # Test model.predict - model.predict(inputs=[0.5, 0.5], experiment=experiment) - model.predict(inputs=[1.1, 0.5], experiment=experiment) + model.predict(inputs=[1e-5, 1e-5], experiment=experiment) + model.predict(inputs=[3e-5, 3e-5], experiment=experiment) + + @pytest.mark.unit + def test_problem_construct_with_model_predict( + self, parameters, model, dataset, signal + ): + # Construct model and predict + out = model.predict(inputs=[1e-5, 1e-5], t_eval=np.linspace(0, 10, 100)) + + problem = pybop.FittingProblem( + model, parameters, dataset=dataset, signal=signal + ) + + # Test problem evaluate + problem_output = problem.evaluate([2e-5, 2e-5]) + + assert problem._model._built_model is not None + with pytest.raises(AssertionError): + np.testing.assert_allclose( + out["Terminal voltage [V]"].data, + problem_output, + atol=1e-5, + ) diff --git a/tests/unit/test_standalone.py b/tests/unit/test_standalone.py index 333233e3..4ba611a9 100644 --- a/tests/unit/test_standalone.py +++ b/tests/unit/test_standalone.py @@ -14,7 +14,7 @@ class TestStandalone: def test_standalone(self): # Build an Optimisation problem with a StandaloneCost cost = StandaloneCost() - opt = pybop.Optimisation(cost=cost, optimiser=pybop.NLoptOptimize) + opt = pybop.Optimisation(cost=cost, optimiser=pybop.SciPyDifferentialEvolution) x, final_cost = opt.run() assert len(opt.x0) == opt.n_parameters