diff --git a/.flake8 b/.flake8 index 5915461f29..bad0d2ef96 100644 --- a/.flake8 +++ b/.flake8 @@ -12,6 +12,7 @@ exclude= share, pyvenv.cfg, third-party, + sundials-5.0.0, ignore= # False positive for white space before ':' on list slice # black should format these correctly diff --git a/CHANGELOG.md b/CHANGELOG.md index ba2315d060..c5fd34e340 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,11 +2,20 @@ ## Features +<<<<<<< HEAD +- Added functionality to broadcast to edges ([#891](https://github.com/pybamm-team/PyBaMM/pull/891)) +- Reformatted and cleaned up `QuickPlot` ([#886](https://github.com/pybamm-team/PyBaMM/pull/886)) +- Added thermal effects to lead-acid models ([#885](https://github.com/pybamm-team/PyBaMM/pull/885)) +- Add new symbols `VariableDot`, representing the derivative of a variable wrt time, + and `StateVectorDot`, representing the derivative of a state vector wrt time + ([#858](https://github.com/pybamm-team/PyBaMM/issues/858)) +======= - Add new symbols `VariableDot`, representing the derivative of a variable wrt time, and `StateVectorDot`, representing the derivative of a state vector wrt time ([#858](https://github.com/pybamm-team/PyBaMM/issues/858)) - Added functionality to broadcast to edges ([#891](https://github.com/pybamm-team/PyBaMM/pull/891)) +>>>>>>> 1b0e0c59154736ed48c92f45e042a9f006f33162 - Added additional notebooks showing how to create and compare models ([#877](https://github.com/pybamm-team/PyBaMM/pull/877)) - Added `Minimum`, `Maximum` and `Sign` operators ([#876](https://github.com/pybamm-team/PyBaMM/pull/876)) diff --git a/docs/index.rst b/docs/index.rst index ebbecaca68..e7910fdc97 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -31,9 +31,10 @@ Contents source/spatial_methods/index source/solvers/index source/experiments/index + source/simulation + source/quick_plot source/processed_variable source/util - source/simulation source/citations source/parameters_cli diff --git a/docs/source/quick_plot.rst b/docs/source/quick_plot.rst new file mode 100644 index 0000000000..b26903ba14 --- /dev/null +++ b/docs/source/quick_plot.rst @@ -0,0 +1,5 @@ +Quick Plot +========== + +.. autoclass:: pybamm.QuickPlot + :members: diff --git a/examples/notebooks/change-input-current.ipynb b/examples/notebooks/change-input-current.ipynb index 4b6516f1d3..7fb47d3ade 100644 --- a/examples/notebooks/change-input-current.ipynb +++ b/examples/notebooks/change-input-current.ipynb @@ -66,12 +66,12 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "528740d94922483b86f14724fcfa32b8", + "model_id": "8247adc1a3fd42dba31101bc6b501ed9", "version_major": 2, "version_minor": 0 }, "text/plain": [ - "interactive(children=(FloatSlider(value=0.0, description='t', max=0.0006659775771737802, step=0.005), Output()…" + "interactive(children=(FloatSlider(value=0.0, description='t', max=15.050167224080266, step=0.15050167224080266…" ] }, "metadata": {}, @@ -98,9 +98,7 @@ "\n", "# plot\n", "quick_plot = pybamm.QuickPlot(solution)\n", - "\n", - "import ipywidgets as widgets\n", - "widgets.interact(quick_plot.plot, t=widgets.FloatSlider(min=0,max=solution.t[-1],step=0.005,value=0));" + "quick_plot.dynamic_plot();" ] }, { @@ -118,12 +116,12 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "2811c58f82064ae6b2e558d33c5551fd", + "model_id": "511a6922f3f34350a543367723f09572", "version_major": 2, "version_minor": 0 }, "text/plain": [ - "interactive(children=(FloatSlider(value=0.0, description='t', max=0.022125255063884477, step=0.005), Output())…" + "interactive(children=(FloatSlider(value=0.0, description='t', max=500.0, step=5.0), Output()), _dom_classes=('…" ] }, "metadata": {}, @@ -136,9 +134,7 @@ "\n", "# plot\n", "quick_plot = pybamm.QuickPlot(solution)\n", - "\n", - "import ipywidgets as widgets\n", - "widgets.interact(quick_plot.plot, t=widgets.FloatSlider(min=0,max=solution.t[-1],step=0.005,value=0));" + "quick_plot.dynamic_plot();" ] }, { @@ -158,12 +154,12 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "8bad57c00aec41bd85c17bec2981a8da", + "model_id": "def542dad39b4ddebcd9a555cf684580", "version_major": 2, "version_minor": 0 }, "text/plain": [ - "interactive(children=(FloatSlider(value=0.0, description='t', max=0.026550306076661374, step=0.001), Output())…" + "interactive(children=(FloatSlider(value=0.0, description='t', max=600.0, step=6.0), Output()), _dom_classes=('…" ] }, "metadata": {}, @@ -195,9 +191,7 @@ "\n", "# plot\n", "quick_plot = pybamm.QuickPlot(solution)\n", - "\n", - "import ipywidgets as widgets\n", - "widgets.interact(quick_plot.plot, t=widgets.FloatSlider(min=0,max=solution.t[-1],step=0.001,value=0));" + "quick_plot.dynamic_plot();" ] }, { @@ -279,12 +273,12 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "47c47acd733b46428148e9dc7cdf373e", + "model_id": "7f4df64df94d410fb9aff92003538b14", "version_major": 2, "version_minor": 0 }, "text/plain": [ - "interactive(children=(FloatSlider(value=0.0, description='t', max=0.0013275153038330688, step=6.63757651916534…" + "interactive(children=(FloatSlider(value=0.0, description='t', max=30.0, step=0.3), Output()), _dom_classes=('w…" ] }, "metadata": {}, @@ -309,9 +303,7 @@ "# plot current and voltage\n", "output_variables = [\"Current [A]\", \"Terminal voltage [V]\"]\n", "quick_plot = pybamm.QuickPlot(solution, output_variables, label)\n", - "\n", - "import ipywidgets as widgets\n", - "widgets.interact(quick_plot.plot, t=widgets.FloatSlider(min=0,max=solution.t[-1],step=solution.t[-1]/20,value=0));" + "quick_plot.dynamic_plot();" ] }, { diff --git a/examples/notebooks/models/DFN.ipynb b/examples/notebooks/models/DFN.ipynb index a907a6bbda..97358cdb0c 100644 --- a/examples/notebooks/models/DFN.ipynb +++ b/examples/notebooks/models/DFN.ipynb @@ -184,7 +184,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "To get a quick overview of the model outputs we can use the QuickPlot class, which plots a common set of useful outputs. The method Quickplot.plot(t) is simply a function which either can be used statically to create a plot for a particular time, or interactively with a slider widget." + "To get a quick overview of the model outputs we can use the QuickPlot class, which plots a common set of useful outputs. The method `Quickplot.dynamic_plot` makes a slider widget." ] }, { @@ -195,12 +195,12 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "db794fc42b6743c4a2baad036cc9b5bc", + "model_id": "29f22a3ab3bd4ce8825acdd6fb655f36", "version_major": 2, "version_minor": 0 }, "text/plain": [ - "interactive(children=(FloatSlider(value=0.0, description='t', max=0.15930183645996823, step=0.05), Output()), …" + "interactive(children=(FloatSlider(value=0.0, description='t', max=3599.9999999999995, step=35.99999999999999),…" ] }, "metadata": {}, @@ -209,9 +209,7 @@ ], "source": [ "quick_plot = pybamm.QuickPlot(solution)\n", - "\n", - "import ipywidgets as widgets\n", - "widgets.interact(quick_plot.plot, t=widgets.FloatSlider(min=0,max=solution.t[-1],step=0.05,value=0));" + "quick_plot.dynamic_plot();" ] }, { @@ -273,7 +271,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.9" + "version": "3.7.3" } }, "nbformat": 4, diff --git a/examples/notebooks/models/SPM.ipynb b/examples/notebooks/models/SPM.ipynb index dc21cad1d7..e2ae07e3c8 100644 --- a/examples/notebooks/models/SPM.ipynb +++ b/examples/notebooks/models/SPM.ipynb @@ -185,7 +185,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, "metadata": {}, "outputs": [], "source": [ @@ -201,7 +201,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 7, "metadata": {}, "outputs": [], "source": [ @@ -220,11 +220,31 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 8, "metadata": { "scrolled": true }, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "negative electrode is of type Generator for Uniform1DSubMesh\n", + "separator is of type Generator for Uniform1DSubMesh\n", + "positive electrode is of type Generator for Uniform1DSubMesh\n", + "negative particle is of type Generator for Uniform1DSubMesh\n", + "positive particle is of type Generator for Uniform1DSubMesh\n", + "current collector is of type Generator for SubMesh0D\n", + "x_n has 20 mesh points\n", + "x_s has 20 mesh points\n", + "x_p has 20 mesh points\n", + "r_n has 10 mesh points\n", + "r_p has 10 mesh points\n", + "y has 10 mesh points\n", + "z has 10 mesh points\n" + ] + } + ], "source": [ "for k, t in model.default_submesh_types.items():\n", " print(k,'is of type',t.__repr__())\n", @@ -241,7 +261,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 9, "metadata": {}, "outputs": [], "source": [ @@ -257,9 +277,20 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 10, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "macroscale is discretised using FiniteVolume method\n", + "negative particle is discretised using FiniteVolume method\n", + "positive particle is discretised using FiniteVolume method\n", + "current collector is discretised using ZeroDimensionalMethod method\n" + ] + } + ], "source": [ "for k, method in model.default_spatial_methods.items():\n", " print(k,'is discretised using',method.__class__.__name__,'method')" @@ -274,7 +305,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 11, "metadata": {}, "outputs": [], "source": [ @@ -291,7 +322,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 12, "metadata": {}, "outputs": [], "source": [ @@ -309,9 +340,18 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 13, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Solving using ScipySolver solver...\n", + "Finished.\n" + ] + } + ], "source": [ "# Solve the model at the given time points (in seconds)\n", "solver = model.default_solver\n", @@ -331,9 +371,327 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 14, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "SPM model variables:\n", + "\t- Time\n", + "\t- Time [s]\n", + "\t- Time [min]\n", + "\t- Time [h]\n", + "\t- x\n", + "\t- x [m]\n", + "\t- x_n\n", + "\t- x_n [m]\n", + "\t- x_s\n", + "\t- x_s [m]\n", + "\t- x_p\n", + "\t- x_p [m]\n", + "\t- r_n\n", + "\t- r_n [m]\n", + "\t- r_p\n", + "\t- r_p [m]\n", + "\t- Total current density\n", + "\t- Total current density [A.m-2]\n", + "\t- Current [A]\n", + "\t- C-rate\n", + "\t- Discharge capacity [A.h]\n", + "\t- Porosity\n", + "\t- Negative electrode porosity\n", + "\t- Separator porosity\n", + "\t- Positive electrode porosity\n", + "\t- X-averaged negative electrode porosity\n", + "\t- X-averaged separator porosity\n", + "\t- X-averaged positive electrode porosity\n", + "\t- Active material volume fraction\n", + "\t- Negative electrode active material volume fraction\n", + "\t- Separator active material volume fraction\n", + "\t- Positive electrode active material volume fraction\n", + "\t- X-averaged negative electrode active material volume fraction\n", + "\t- X-averaged separator active material volume fraction\n", + "\t- X-averaged positive electrode active material volume fraction\n", + "\t- Leading-order porosity\n", + "\t- Leading-order negative electrode porosity\n", + "\t- Leading-order separator porosity\n", + "\t- Leading-order positive electrode porosity\n", + "\t- Leading-order x-averaged negative electrode porosity\n", + "\t- Leading-order x-averaged separator porosity\n", + "\t- Leading-order x-averaged positive electrode porosity\n", + "\t- Leading-order active material volume fraction\n", + "\t- Leading-order negative electrode active material volume fraction\n", + "\t- Leading-order separator active material volume fraction\n", + "\t- Leading-order positive electrode active material volume fraction\n", + "\t- Leading-order x-averaged negative electrode active material volume fraction\n", + "\t- Leading-order x-averaged separator active material volume fraction\n", + "\t- Leading-order x-averaged positive electrode active material volume fraction\n", + "\t- Porosity change\n", + "\t- Negative electrode porosity change\n", + "\t- Separator porosity change\n", + "\t- Positive electrode porosity change\n", + "\t- X-averaged porosity change\n", + "\t- X-averaged negative electrode porosity change\n", + "\t- X-averaged separator porosity change\n", + "\t- X-averaged positive electrode porosity change\n", + "\t- Leading-order x-averaged negative electrode porosity change\n", + "\t- Leading-order x-averaged separator porosity change\n", + "\t- Leading-order x-averaged positive electrode porosity change\n", + "\t- Volume-averaged velocity\n", + "\t- Volume-averaged velocity [m.s-1]\n", + "\t- Electrolyte pressure\n", + "\t- Negative particle concentration\n", + "\t- Negative particle concentration [mol.m-3]\n", + "\t- X-averaged negative particle concentration\n", + "\t- X-averaged negative particle concentration [mol.m-3]\n", + "\t- Negative particle surface concentration\n", + "\t- Negative particle surface concentration [mol.m-3]\n", + "\t- X-averaged negative particle surface concentration\n", + "\t- X-averaged negative particle surface concentration [mol.m-3]\n", + "\t- Negative electrode active volume fraction\n", + "\t- Negative electrode volume-averaged concentration\n", + "\t- Negative electrode volume-averaged concentration [mol.m-3]\n", + "\t- Negative electrode average extent of lithiation\n", + "\t- Positive particle concentration\n", + "\t- Positive particle concentration [mol.m-3]\n", + "\t- X-averaged positive particle concentration\n", + "\t- X-averaged positive particle concentration [mol.m-3]\n", + "\t- Positive particle surface concentration\n", + "\t- Positive particle surface concentration [mol.m-3]\n", + "\t- X-averaged positive particle surface concentration\n", + "\t- X-averaged positive particle surface concentration [mol.m-3]\n", + "\t- Positive electrode active volume fraction\n", + "\t- Positive electrode volume-averaged concentration\n", + "\t- Positive electrode volume-averaged concentration [mol.m-3]\n", + "\t- Positive electrode average extent of lithiation\n", + "\t- Electrolyte concentration\n", + "\t- Electrolyte concentration [mol.m-3]\n", + "\t- Electrolyte concentration [Molar]\n", + "\t- X-averaged electrolyte concentration\n", + "\t- X-averaged electrolyte concentration [mol.m-3]\n", + "\t- X-averaged electrolyte concentration [Molar]\n", + "\t- Negative electrolyte concentration\n", + "\t- Negative electrolyte concentration [mol.m-3]\n", + "\t- Negative electrolyte concentration [Molar]\n", + "\t- Separator electrolyte concentration\n", + "\t- Separator electrolyte concentration [mol.m-3]\n", + "\t- Separator electrolyte concentration [Molar]\n", + "\t- Positive electrolyte concentration\n", + "\t- Positive electrolyte concentration [mol.m-3]\n", + "\t- Positive electrolyte concentration [Molar]\n", + "\t- X-averaged negative electrolyte concentration\n", + "\t- X-averaged negative electrolyte concentration [mol.m-3]\n", + "\t- X-averaged separator electrolyte concentration\n", + "\t- X-averaged separator electrolyte concentration [mol.m-3]\n", + "\t- X-averaged positive electrolyte concentration\n", + "\t- X-averaged positive electrolyte concentration [mol.m-3]\n", + "\t- Electrolyte flux\n", + "\t- Electrolyte flux [mol.m-2.s-1]\n", + "\t- Negative current collector temperature\n", + "\t- Negative current collector temperature [K]\n", + "\t- X-averaged negative electrode temperature\n", + "\t- X-averaged negative electrode temperature [K]\n", + "\t- Negative electrode temperature\n", + "\t- Negative electrode temperature [K]\n", + "\t- X-averaged separator temperature\n", + "\t- X-averaged separator temperature [K]\n", + "\t- Separator temperature\n", + "\t- Separator temperature [K]\n", + "\t- X-averaged positive electrode temperature\n", + "\t- X-averaged positive electrode temperature [K]\n", + "\t- Positive electrode temperature\n", + "\t- Positive electrode temperature [K]\n", + "\t- Positive current collector temperature\n", + "\t- Positive current collector temperature [K]\n", + "\t- Cell temperature\n", + "\t- Cell temperature [K]\n", + "\t- X-averaged cell temperature\n", + "\t- X-averaged cell temperature [K]\n", + "\t- Volume-averaged cell temperature\n", + "\t- Volume-averaged cell temperature [K]\n", + "\t- Heat flux\n", + "\t- Heat flux [W.m-2]\n", + "\t- Ambient temperature [K]\n", + "\t- Ambient temperature\n", + "\t- Electrolyte tortuosity\n", + "\t- Negative electrolyte tortuosity\n", + "\t- Positive electrolyte tortuosity\n", + "\t- X-averaged negative electrolyte tortuosity\n", + "\t- X-averaged positive electrolyte tortuosity\n", + "\t- Separator tortuosity\n", + "\t- X-averaged separator tortuosity\n", + "\t- Electrode tortuosity\n", + "\t- Negative electrode tortuosity\n", + "\t- Positive electrode tortuosity\n", + "\t- X-averaged negative electrode tortuosity\n", + "\t- X-averaged positive electrode tortuosity\n", + "\t- Negative particle flux\n", + "\t- X-averaged negative particle flux\n", + "\t- Positive particle flux\n", + "\t- X-averaged positive particle flux\n", + "\t- Ohmic heating\n", + "\t- Ohmic heating [W.m-3]\n", + "\t- Irreversible electrochemical heating\n", + "\t- Irreversible electrochemical heating [W.m-3]\n", + "\t- Reversible heating\n", + "\t- Reversible heating [W.m-3]\n", + "\t- Total heating\n", + "\t- Total heating [W.m-3]\n", + "\t- X-averaged total heating\n", + "\t- X-averaged total heating [W.m-3]\n", + "\t- Volume-averaged total heating\n", + "\t- Volume-averaged total heating [W.m-3]\n", + "\t- Negative current collector potential\n", + "\t- Negative current collector potential [V]\n", + "\t- Current collector current density\n", + "\t- Current collector current density [A.m-2]\n", + "\t- Leading-order current collector current density\n", + "\t- Negative electrode interfacial current density\n", + "\t- X-averaged negative electrode interfacial current density\n", + "\t- Negative electrode interfacial current density [A.m-2]\n", + "\t- X-averaged negative electrode interfacial current density [A.m-2]\n", + "\t- Negative electrode interfacial current density per volume [A.m-3]\n", + "\t- X-averaged negative electrode interfacial current density per volume [A.m-3]\n", + "\t- X-averaged negative electrode total interfacial current density\n", + "\t- X-averaged negative electrode total interfacial current density [A.m-2]\n", + "\t- X-averaged negative electrode total interfacial current density per volume [A.m-3]\n", + "\t- Negative electrode exchange current density\n", + "\t- X-averaged negative electrode exchange current density\n", + "\t- Negative electrode exchange current density [A.m-2]\n", + "\t- X-averaged negative electrode exchange current density [A.m-2]\n", + "\t- Negative electrode exchange current density per volume [A.m-3]\n", + "\t- X-averaged negative electrode exchange current density per volume [A.m-3]\n", + "\t- Negative electrode reaction overpotential\n", + "\t- X-averaged negative electrode reaction overpotential\n", + "\t- Negative electrode reaction overpotential [V]\n", + "\t- X-averaged negative electrode reaction overpotential [V]\n", + "\t- Negative electrode surface potential difference\n", + "\t- X-averaged negative electrode surface potential difference\n", + "\t- Negative electrode surface potential difference [V]\n", + "\t- X-averaged negative electrode surface potential difference [V]\n", + "\t- Negative electrode open circuit potential\n", + "\t- Negative electrode open circuit potential [V]\n", + "\t- X-averaged negative electrode open circuit potential\n", + "\t- X-averaged negative electrode open circuit potential [V]\n", + "\t- Negative electrode entropic change\n", + "\t- X-averaged negative electrode entropic change\n", + "\t- Positive electrode interfacial current density\n", + "\t- X-averaged positive electrode interfacial current density\n", + "\t- Positive electrode interfacial current density [A.m-2]\n", + "\t- X-averaged positive electrode interfacial current density [A.m-2]\n", + "\t- Positive electrode interfacial current density per volume [A.m-3]\n", + "\t- X-averaged positive electrode interfacial current density per volume [A.m-3]\n", + "\t- X-averaged positive electrode total interfacial current density\n", + "\t- X-averaged positive electrode total interfacial current density [A.m-2]\n", + "\t- X-averaged positive electrode total interfacial current density per volume [A.m-3]\n", + "\t- Positive electrode exchange current density\n", + "\t- X-averaged positive electrode exchange current density\n", + "\t- Positive electrode exchange current density [A.m-2]\n", + "\t- X-averaged positive electrode exchange current density [A.m-2]\n", + "\t- Positive electrode exchange current density per volume [A.m-3]\n", + "\t- X-averaged positive electrode exchange current density per volume [A.m-3]\n", + "\t- Positive electrode reaction overpotential\n", + "\t- X-averaged positive electrode reaction overpotential\n", + "\t- Positive electrode reaction overpotential [V]\n", + "\t- X-averaged positive electrode reaction overpotential [V]\n", + "\t- Positive electrode surface potential difference\n", + "\t- X-averaged positive electrode surface potential difference\n", + "\t- Positive electrode surface potential difference [V]\n", + "\t- X-averaged positive electrode surface potential difference [V]\n", + "\t- Positive electrode open circuit potential\n", + "\t- Positive electrode open circuit potential [V]\n", + "\t- X-averaged positive electrode open circuit potential\n", + "\t- X-averaged positive electrode open circuit potential [V]\n", + "\t- Positive electrode entropic change\n", + "\t- X-averaged positive electrode entropic change\n", + "\t- Interfacial current density\n", + "\t- Interfacial current density [A.m-2]\n", + "\t- Interfacial current density per volume [A.m-3]\n", + "\t- Exchange current density\n", + "\t- Exchange current density [A.m-2]\n", + "\t- Exchange current density per volume [A.m-3]\n", + "\t- Negative electrode potential\n", + "\t- Negative electrode potential [V]\n", + "\t- X-averaged negative electrode potential\n", + "\t- X-averaged negative electrode potential [V]\n", + "\t- Negative electrode ohmic losses\n", + "\t- Negative electrode ohmic losses [V]\n", + "\t- X-averaged negative electrode ohmic losses\n", + "\t- X-averaged negative electrode ohmic losses [V]\n", + "\t- Gradient of negative electrode potential\n", + "\t- Negative electrode current density\n", + "\t- Negative electrode current density [A.m-2]\n", + "\t- Negative electrolyte potential\n", + "\t- Negative electrolyte potential [V]\n", + "\t- Separator electrolyte potential\n", + "\t- Separator electrolyte potential [V]\n", + "\t- Positive electrolyte potential\n", + "\t- Positive electrolyte potential [V]\n", + "\t- Electrolyte potential\n", + "\t- Electrolyte potential [V]\n", + "\t- X-averaged electrolyte potential\n", + "\t- X-averaged electrolyte potential [V]\n", + "\t- X-averaged negative electrolyte potential\n", + "\t- X-averaged negative electrolyte potential [V]\n", + "\t- X-averaged separator electrolyte potential\n", + "\t- X-averaged separator electrolyte potential [V]\n", + "\t- X-averaged positive electrolyte potential\n", + "\t- X-averaged positive electrolyte potential [V]\n", + "\t- X-averaged electrolyte overpotential\n", + "\t- X-averaged electrolyte overpotential [V]\n", + "\t- Gradient of negative electrolyte potential\n", + "\t- Gradient of separator electrolyte potential\n", + "\t- Gradient of positive electrolyte potential\n", + "\t- Gradient of electrolyte potential\n", + "\t- Electrolyte current density\n", + "\t- Electrolyte current density [A.m-2]\n", + "\t- Negative electrolyte current density\n", + "\t- Negative electrolyte current density [A.m-2]\n", + "\t- Positive electrolyte current density\n", + "\t- Positive electrolyte current density [A.m-2]\n", + "\t- X-averaged concentration overpotential\n", + "\t- X-averaged electrolyte ohmic losses\n", + "\t- X-averaged concentration overpotential [V]\n", + "\t- X-averaged electrolyte ohmic losses [V]\n", + "\t- Positive electrode potential\n", + "\t- Positive electrode potential [V]\n", + "\t- X-averaged positive electrode potential\n", + "\t- X-averaged positive electrode potential [V]\n", + "\t- Positive electrode ohmic losses\n", + "\t- Positive electrode ohmic losses [V]\n", + "\t- X-averaged positive electrode ohmic losses\n", + "\t- X-averaged positive electrode ohmic losses [V]\n", + "\t- Gradient of positive electrode potential\n", + "\t- Positive electrode current density\n", + "\t- Positive electrode current density [A.m-2]\n", + "\t- Electrode current density\n", + "\t- Positive current collector potential\n", + "\t- Positive current collector potential [V]\n", + "\t- Local voltage\n", + "\t- Local voltage [V]\n", + "\t- Terminal voltage\n", + "\t- Terminal voltage [V]\n", + "\t- X-averaged open circuit voltage\n", + "\t- Measured open circuit voltage\n", + "\t- X-averaged open circuit voltage [V]\n", + "\t- Measured open circuit voltage [V]\n", + "\t- X-averaged reaction overpotential\n", + "\t- X-averaged reaction overpotential [V]\n", + "\t- X-averaged solid phase ohmic losses\n", + "\t- X-averaged solid phase ohmic losses [V]\n", + "\t- X-averaged battery open circuit voltage [V]\n", + "\t- Measured battery open circuit voltage [V]\n", + "\t- X-averaged battery reaction overpotential [V]\n", + "\t- X-averaged battery solid phase ohmic losses [V]\n", + "\t- X-averaged battery electrolyte ohmic losses [V]\n", + "\t- X-averaged battery concentration overpotential [V]\n", + "\t- Battery voltage [V]\n", + "\t- Terminal power [W]\n" + ] + } + ], "source": [ "print('SPM model variables:')\n", "for v in model.variables.keys():\n", @@ -349,7 +707,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 15, "metadata": {}, "outputs": [], "source": [ @@ -367,9 +725,22 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 16, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], "source": [ "t = np.linspace(0,1,250)\n", "f, (ax1, ax2, ax3) = plt.subplots(1, 3, figsize=(13,4))\n", @@ -399,9 +770,24 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 17, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "57ac59ccd1af44dab95f2bca72d12771", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "interactive(children=(FloatSlider(value=0.0, description='t', max=0.17, step=0.01), Output()), _dom_classes=('…" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "c_s_n = solution['Negative particle concentration']\n", "c_s_p = solution['Positive particle concentration']\n", @@ -428,17 +814,32 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "The QuickPlot class can be used to plot the common set of useful outputs which should give you a good initial overview of the model. The method Quickplot.plot(t) is simply a function like plot_concentrations(t) above. We can therefore either use it statically for a particular value of $t$ or employ the slider widget. " + "The QuickPlot class can be used to plot the common set of useful outputs which should give you a good initial overview of the model. The method `Quickplot.dynamic_plot` employs the slider widget. " ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 18, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "22b9213dc69b48b1b9c4f51718aac0f3", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "interactive(children=(FloatSlider(value=0.0, description='t', max=3599.9999999999995, step=35.99999999999999),…" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "quick_plot = pybamm.QuickPlot(solution)\n", - "widgets.interact(quick_plot.plot, t=widgets.FloatSlider(min=0,max=quick_plot.max_t,step=0.05,value=0));" + "quick_plot.dynamic_plot();" ] }, { @@ -487,7 +888,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.9" + "version": "3.7.3" } }, "nbformat": 4, diff --git a/examples/notebooks/models/SPMe.ipynb b/examples/notebooks/models/SPMe.ipynb index f933a1176c..449155039c 100644 --- a/examples/notebooks/models/SPMe.ipynb +++ b/examples/notebooks/models/SPMe.ipynb @@ -184,7 +184,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "To get a quick overview of the model outputs we can use the QuickPlot class, which plots a common set of useful outputs. The method Quickplot.plot(t) is simply a function which either can be used statically to create a plot for a particular time, or interactively with a slider widget." + "To get a quick overview of the model outputs we can use the QuickPlot class, which plots a common set of useful outputs. The method `Quickplot.dynamic_plot` makes a slider widget." ] }, { @@ -195,12 +195,12 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "c6b2952a4777482b8bfd1a56b10f3d7a", + "model_id": "2e47cb08b18f4d94ae85e6a334ca22ef", "version_major": 2, "version_minor": 0 }, "text/plain": [ - "interactive(children=(FloatSlider(value=0.0, description='t', max=0.15930183645996823, step=0.05), Output()), …" + "interactive(children=(FloatSlider(value=0.0, description='t', max=3599.9999999999995, step=35.99999999999999),…" ] }, "metadata": {}, @@ -209,9 +209,7 @@ ], "source": [ "quick_plot = pybamm.QuickPlot(solution)\n", - "\n", - "import ipywidgets as widgets\n", - "widgets.interact(quick_plot.plot, t=widgets.FloatSlider(min=0,max=solution.t[-1],step=0.05,value=0));" + "quick_plot.dynamic_plot();" ] }, { @@ -264,7 +262,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.9" + "version": "3.7.3" } }, "nbformat": 4, diff --git a/examples/notebooks/models/compare-lithium-ion.ipynb b/examples/notebooks/models/compare-lithium-ion.ipynb index d90e556bc0..039d41f713 100644 --- a/examples/notebooks/models/compare-lithium-ion.ipynb +++ b/examples/notebooks/models/compare-lithium-ion.ipynb @@ -102,7 +102,7 @@ { "data": { "text/plain": [ - "" + "" ] }, "execution_count": 4, @@ -247,9 +247,9 @@ "name": "stdout", "output_type": "stream", "text": [ - "Solved the Doyle-Fuller-Newman model in 2.854 seconds\n", - "Solved the Single Particle Model in 0.328 seconds\n", - "Solved the Single Particle Model with electrolyte in 0.432 seconds\n" + "Solved the Doyle-Fuller-Newman model in 2.143 seconds\n", + "Solved the Single Particle Model in 0.379 seconds\n", + "Solved the Single Particle Model with electrolyte in 0.416 seconds\n" ] } ], @@ -287,7 +287,7 @@ "outputs": [ { "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAZQAAAELCAYAAAD+9XA2AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADh0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uMy4xLjEsIGh0dHA6Ly9tYXRwbG90bGliLm9yZy8QZhcZAAAgAElEQVR4nOzdd3zV9fX48df73uwdsklIwpaETcJGtoCKoi2KW7R11NlqtYqtVr6OurD+ahXqbOtCcTCUYdkoQhCQGUYIkBAyCNnz3nt+f3xuFhncQG4GvJ+Px+dx7/3MczHm5L2ViKBpmqZp58vU1gFomqZpFwadUDRN07QWoROKpmma1iJ0QtE0TdNahE4omqZpWotwaesAnC04OFhiY2PbOgxN07QOY9u2bTkiEtLc6y74hBIbG0tSUlJbh6FpmtZhKKWOnst1uspL0zRNaxE6oWiapmktQicUTdM0rUXohKJpmqa1CJ1QNE3TtBahE4qmaZrWInRCaYzNClZLW0ehaZrWYeiE0pDcFPjgCtjwSltHomma1mG0akJRSnVRSq1RSu1VSu1RSj1k3/+yUmq/UuoXpdRXSqmARq5PVUrtUkrtUEo5b7Rifhoc+xHWvwwntjvtMZqmaReS1i6hWIBHRCQOGA7cp5SKA1YBfUWkP3AAeKKJe4wXkYEikuC0KLteCsPuAZsFvroHKoqd9ihN07QLRasmFBHJEJGf7e8LgX1ApIisFJGqBovNQFRrxtWQsrFPYQnsBtn7jeqvwpNtHZKmaVq71mZtKEqpWGAQ8NMZh+4AvmvkMgFWKqW2KaXuauLedymlkpRSSdnZ2c2ObeW+Iwx7+1Xu9p8BgbFGtdc/R8COT0AvmaxpmtagNkkoSikfYBHwsIgU1No/B6Na7KNGLh0tIoOBaRjVZZc2dJKILBCRBBFJCAlp9oSZFKpkbEGL2Fy5lqzrlkK38VCaC1/fA/+ZAacON/uemqZpF7pWTyhKKVeMZPKRiHxZa//twJXATSINFwNEJN3+mgV8BQx1RoxX95qEGwGY3HKYt30b3PIVzHgbPAMhZS28ORSW/h4KMpzxeE3TtA6ptXt5KeBdYJ+IvFZr/1TgMeAqESlp5FpvpZRv1XvgMmC3M+J0MbkwOWo6AN8d/RqLTWDgDXB/Egy8GcQGSe/BGwNh+ZOQn+6MMDRN0zqU1i6hjAJuASbYu/7uUEpdDvwD8AVW2fe9DaCU6qyU+tZ+bRiwUSm1E9gCLBOR5c4K9IGhN4EoLB47+NcPu4yd3sEw402490focxVYymDzm/D3AfD17yBrv7PC0TRNa/dUI7VLF4yEhAQ51wW2Zn51J/sLtmDNH8o3179OzzDfuidk7ISNr8Per41SC0CvaTD8Hug6FpQ6z+g1TdNan1Jq27kMzdAj5Zvw4vgnUbhg9t/C7M8+IqeovO4JEQNg5vvwwDZIuBNcPODAd/Dvq412lp/mQ1l+2wSvaZrWynRCaUL3gO7c3f8eAE57f8is95aSVVhW/8RO3eDK1+Dh3TB+Dvh2hpwD8N1j8GofowE/c28rR69pmta6dJXXWVhsFn6z4h62Zf2ErSII39wH+NdNk+gX5d/4RdZK2L8Mtr4DqRtq9kclwqCbIf5a8PA755g0TdOc6VyrvHRCcUBxZTG3fHs7B/P2Y6v0pyJtNnePGM39E3rg4Wpu+uKsfUZi2fkZVBQa+1w8IX6GkVxiRum2Fk3T2hWdUBrREgkFIK8sj/tXP8DO7B2IzZXyzOl0cR3HczP6M6J70NlvUFEMexfD9v/C0Y01+wO7wsCbYMAsCOhy3nFqmqadL51QGtFSCQWgzFLG3M1zWXx4MQCW4m6Un7yaUTHx/HFKb/pHNThJcn25KbDjY2MrqDWGJXok9Ps1xM0AbweSlKZpmhPohNKIlkwoVZamLOXFLX8jvzwPxERF7kjKT41nYs+u3HVpN4Z27YRypBrLZoWUNUapJfk7Y1wLgMkFuk+EfjOh9zRw92nR+DVN05qiE0ojnJFQAPLL8/l/2/8fC5MXIghic6MidySVuWPoF9GZ347pxrS+4biYHexIV1YAyd/Crs/h8BoQq7Hf1ctIKnEzoMckcPNq8e+iaZpWm04ojXBWQqmy99Re3tj+BpvSNxk7bG5U5CVQkTuSzt5duHFYNNcldCHE193xmxZlG4Mld30BxzfX7HfxhJ6ToM/V0GuK7immaZpT6ITSCGcnlCo7s3fy9s632Zhub3AXhaXoEipyR6PKujOlbwQ3DYtmRLcgx6rDqpw+Cnu+gn1LIL3W9zC7QbdxxhQwvS/XbS6aprUYnVAa0VoJpUpybjIf7fuIZSnLqLBVAGArD6PidCKV+YPp1imEG4ZGc83gSIJ9mlFqAWNp4n1LYd9iOPoDxvIwgDIZY1x6TYGeUyAsXndF1jTtnOmE0ojWTihVTpWeYuGBhXy2/zNOlZ0ydooLlQX9qDydiCrvxrjeofxqcBQT+oTi7nKW8SxnKsoyBk/uW2IMnrRW1Bzzi4Jel0GvqcZyxq6eLffFNE274OmE0oi2SihVKq2VrDm+hkUHF/HDiR+q99vKQ6jIS8BSMBB/t2Cm9+/Mr4ZEMSDKv3lVYgDlhcY6LQeWw8FVUJRZc8zFA2JGGouEdRsHYX3BpGfc0TStcTqhNKKtE0ptaYVpfHnwS74+9DXZpfaliUVhKe5OZf5gLIXx9AgJ4ppBkUzv35nooHPo0WWzQcYOOLACDq4wli+uzSsYuo01Ekz38eAfdf5fTNO0C4pOKI1oTwmlisVmYX3aepYcXsK6tHVU2iqNAzY3KgvjqcwfhLW4BwOiApk+oDNX9I8gwv8cq62KsiBlnVGCSVlTdyAlQFAPI7nEjjIGVvqGndd30zSt42vxhKKUuuMcY/laRHLP8doW1x4TSm355fmsSF3B0pSlbM+qKU2IxY/Kgr5YCvpjLY0mMTaI6QM6M61vRPO6INcmAqcOGeNcUtYabS/lBXXP6dTNqCKLHgkxI4ypYXQDv6ZdVJyRUGznEIcAiSLy8zlc6xTtPaHUdrzgOEtTlrIkZQnHC49X75dKfyoL+1JZ0B/KujCiewhX9OvM5Liwc08uAFYLnPjZKMEc3QTHt0Blcd1zfMKNBBMz0uhJFhYPZtdzf6amae2esxLKpcBWB+/lAhQCCY0lFKVUF+DfGMv5CrBARP6ulHoZmA5UAIeB2SKS18D1U4G/A2bgHRF58WxBdaSEUkVE2J2zm5VHV7IidQUZxRk1xyr9jZJLYT9sZdEkxAQxJT6cKfHhdOl0nqPorRY4uROO/gjHfjS6JpeeUdh08TAWFotMgKghEDkEAmJ0KUbTLiDOSCg/AneIyD4HAzABm4DbRORAI+dEABEi8rNSyhfYBswAooDVImJRSv0NQEQeP+NaM3AAmAykYSS6G0SkyZWrOmJCqU1E2JWzixWpK1h5dCUni0/WHLN4Yym6BEthHJbinsRHBDM1PpwpfcPpGerT/N5iZ7LZjIXCjv1gJJn0bZB7uP553iFGYolMgM4DIby/bovRtA6sQzbKK6W+Af4hIqtq7bsG+LWI3HTGuSOAZ0Rkiv3zEwAi8kJTz+joCaU2m9iqk8vqY6tJL6ppYBebC9binliK+mAp7EPXwHAuiw9nat9w+kf6YzK1UAmiJBfSfzZG7advg7Sk+qUYAJ8wI7FE9K95DYjVXZY1rQNwRgnlAeAzEck63+AauX8ssB7oKyIFtfYvsT/3v2ec/2tgqoj8xv75FmCYiNzfwL3vAu4CiI6OHnL06FFnfIU2JSIcyjvE2uNrWXN8DbtydtU6qLCWRWIp7oW1qBeBLj2YdEkEEy4JZXTPYLzcXFoyEDh9BNK2GQnm5C9wclf9xn4Adz9jHExVkgnvCyGXgMt5tANpmtbinJFQrIANWAt8DHwpIvnnE2Ste/sA64DnROTLWvvnAAnAtXJGYM1JKLVdSCWUpmSXZLM2bS1rj69l84nN1dO+AIjVA0txT6xFvVDlvRkZ052Jl4QyoU8YkQFOGEVvs0FeKmT8YiSYqtfaAy6rmFwguLeRXML7GQknvB94B7d8XJqmOcQZCSUKmAVcDwwByoEVGMlliYiUnmOgrsBSYIWIvFZr/+3A3cBEESlp4LqLvsrLUaWWUpJOJrHpxCY2pm/kaEHdEpq1PBRrcTesJd3p6tOfKZd0Z0KfUAZGBbRc1VhDCjPtCWanUYrJ3A2nDlM9J1ltPuH1k0xQDzA1c4oaTdOazaltKEqp7sANGMklHigGFmMklxUiYnEwSAV8COSKyMO19k8FXgPGikh2I9e6YDTKTwTSMRrlbxSRPU0982JMKGdKK0zjhxM/sDF9I5szfqLUUjdfW8vCsZZ0w9PaixGdhzKxd1cu7RlMqJ+H84OrKIasfUaCqUoymXugoqj+uS4eEBpnJJqwfvbXePDwd36cmnYRabVGeaVUPEbJ5TqgB5AHfCEidztw7WhgA7ALozoN4EngDcAdsM+iyGYRuUcp1Rmje/Dl9usvB17H6Db8nog8d7Zn6oRSV6W1kt2ndrMlYws/ZWxhR9YOKqVW9ZgobOVGggl3u4Qx0YlMvaQ3CbGBeLi2Uumgqsrs5C44udtIMid3Q/6xhs8PiKlbkgnvq7sya9p5aPVeXkopf+A54F4AEWmXdRE6oTStwlrBL9m/sOXkFtYf38z+3F1YqVvgtFUGQFkMMd7xjI5O4Nr4BHqHBZx/t+TmKj1tlF5O7oZMe4kmaz9Yy+uf6+5nlF4ih0DMKIgeDl6dWjdeTeugWiWhKKW8gKswSihTADeMEscnIjK/uQ9vDTqhNE+ZpYyd2TvZenIbG44lcTB/N5VnNJeJzRVzZTRdvPqQENGfK3olMiSyG6a26BJstcCpg0aSOflLTWmmuIHOiaFxRnLpPsGY1t/dp/Xj1bQOwGkJRSnlBlyBkUSuALwwBiR+gtG9N72Jy9ucTijnxyY2DucdZsPxraw+spUD+bsolfq9tZTNm04u3egTFMf42MGMiR5EuHd465diqhRmGiWY45uNEf9pSXVLMmY3iB4BPSYZK14G92ibODWtHXJGL69pGEnkasAP2I+RRD4RkUPnEWur0gml5Z0qPcW3BzezLvVnkk/vJc+aAuZ6HfNwU37E+PQmIaIfiZ370juwN1G+UZhUG5RkLOXGgMwj6+DQ90aCqd27LDQe4mdA3NUQ0rv149O0dsRZc3kdAz7FSCI7zy/EtqETivNZrTY2pB7kuwNb2ZG1mxMlBxG3NJRL/STjqjyI9etG/9A4enfqRa/AXvQM7Imfm1/rBl2SC4dXw8GVkLwcymsNsQrpA/1+BX1/DZ26tm5cmtYOOCOhjBSRHxo82IHohNL6LFYbu9PzWXlgHz+m7eRQwT4s5hOY3DMwuTYwgh6I8I6gd2Bvegb2pJc90cT4xmBujXEnlgpjOv+938D+pVBWa17SqEToNxPirwGfUOfHomntgDMSiptIrf6kjgXR7GucTSeUtmezCYeyi9iamssPR46xLWMvpypSMXmcxOyegck9E2WqP5TJ3exOj4Ae9ArsVWcL8AhwXrDWSmO9mN1fwL6lNdP5K5OxhHK/mXDJleDRyiUqTWtFzpp6ZYSIbHEwADPG9PN6PRTtrDLyS0lKPU1Sai5bUnNIzj2Cye0kJo8MI8l4nMTkWm8FAwBCvULrJZlY/1hcTS28TktFCRz4DnZ9AQdXQdXKmmZ36DXFSC49LwPXVhgAqmmtyFltKPdjNMY7wgSspIn1UNqCTigdQ0FZJT8fPc3Px/LYfuw0O4/nUVBRiNn9pJFc3DMwe2Tg4pGJqPqFYFeTKz0CehAfHE//4P70C+5HV/+uLVdlVpIL+xYbySV1I9UN+u5+0Ocq6PdroyuynhpGuwDoFRsboRNKx2SzCSk5xWw/dpodx/PYfiyP/ScLsIkN5ZqL2Z5k3L0ycfPKokLVH3fi7epNfFA8/UP6kxiWyMDQgXi5nuciZAD56bDnS9j1uTEvWRWfMIi/1ii5RA7WI/W1DssZCeVc+06mikgDQ5fbhk4oF46SCgu70vLZftwoxWw/lkdWof1HzVSO2f0EJs/j+AWcwOR+nLLqmXwMLiYX+gf3JzE8kZGdRzIgZMD5l2CyDxjtLbs+h9yUmv1+UUabS7ex0HWsXnBM61A65AJbrUEnlAuXiJCRX8b2Y3nsOG5Ul+1Oz6fcYhSulUsBZo/juPkcxcsvlQrzcaTW2JMA9wDGRI5hbJexjOw8El833/MJBk78bFSJ7V5Uf6r+kD7QZShEJRgrW4b01tVjWrulE0ojdEK5uFRabezPKGT78dPsOJbH9uN5HMmx99QylWL2OoKL92Hc/ZIRl5zq61xMLoyOHM3lXS9nbNTY86sas9mMKWBS1hoDKY/+AJVnjMlx8zUWGgvtY2wh9lc935jWDuiE0gidULTTxRXsSMuzl2Ty2HHsNAVllZjcsjH77MPFdx9mz6MoZfy/4GbyYFyX8VzV/QpGdh6Jq/k8e49ZyuHEdmN0fnqSsbplYzMn+4RDcE9jQGVgV/trrPHe04ndpTWtFp1QGqETinYmm004cqqY7fYeZTuO57E/Kx2T7y+4+u3E7FXzy95N+TAsdAK39b+WoRGDW25ussJMoxSTtc++7YXs5JpxLw3xDDSSi3+U0UbjHwl+kfbPkeAbrqvRtBahE0ojdELRHFFYVknS0dP8lJLLhiP7OVSyEbPvDsweNW0hbhJCv4Dx3Bh3DRN7xGNu6dUtbTbIOwq5hyH3CJxOtb/a359ZbXYmZQbfiJpE49fZSDI+4carb7jRE83dV/dA05rUqglFKdUJyBORc+la3Kp0QtHORVG5haTUXL5N3sbGkyvJN22pO21MeTTdPS/lym6Xc9kl3YkOaoHuyE0RgaIsI7EUpBldlwvSIT/N/pre8JT9DXH1qpVowuonnKr3HgE68VyknJ5QlFITgb9grC/vAQwVkZ+VUm8C60Xks+Y+vDXohKK1hKzCEj7euZoVR78lrWILmIzuyiImrEW98bMOY2zUWMb2jGRk9yACvd1aP0hLORScqEkwhSeMqrWik1Bo34oyz17SqWJ2N+Yv8w4Gr2DwDjHeN/TqFaxnDLiAOHtN+RuA/wJfAKuBt7CPiFdKPQ5cJiITHbhPF+DfQBjGIMgFIvJ3pdRM4BmgD0aiajADKKVSgULAClgc+cI6oWgtrbiimEX7V7LowDekFG+najVrsbliKe6BtagP3X0SGdu9O4O6BNI/yp8If4+2WxumNhEoLzQSS2FG3YRTlFk38ZQ3PJFno9x8ayWaEKPHmncweAXZt6r3nYxXXfXWbjk7oewDlonIo/Y5uyqpSShXAO+KSLgD94kAIuzX+WIs1DUDI7nYgPnAo2dJKAkiktPQ8YbohKI5U05pDt+mfMei5CWkFO6rc8xaGoWluDvWkm4EmHoxIDKM/lH+9I/yp1eYL5EBnu0jyTSmohiKc+xbNpTYX6s+V2+njNequc4cZXarlWw61Uo4QfZE1OmMZNQJXNyd8121Os41obg4eF5X4NtGjpUA/o7cREQygAz7+0J7oooUkVVA+/6fS9MaEOwZzK3xt3Br/C1kFmeyLm0da46t46eMzeCZhtkzDVhHuSg2l0Wy6ZcYrFuisJVF4kkYPUL96BnmS89QH3qF+dIj1IfIAE9MLd3gfy7cvI0tMObs54pAWX7dZFNyqv5WnGPMi1ZyyujRVphhbA7H5NtAyeeMraqU5B+lE1ArczShpAP9Maq7zjQYSGlgf5OUUrHAIOCnZlwmwEplDBiYLyILGrn3XcBdANHR0c0NTdPOSZh3GNf1vo7rel9HqaWUpJNJJGUmkXQyiT2n9mCtTjAGsbpzoKwz+9PDsKWEYCsPw1YRihv+RHfyJibIq+Y1yIuYTl5EBXrh5tIGK16ejVLGOBnPAMeXU64sPSPZ1E4+OfbXXHsSsu+vKDS2vKOOBGX0eguMtW8xxniekN7G5up5Hl9Ya4ijVV5/Bh4BfgMsxSiVDMFYX/4r4EURec3hhyrlA6wDnhORL2vtX0vTVV6RIpKulAoFVgEPiMj6pp6lq7y09qCksoQdWTvYlbOLvaf2sufUHjJLMhs8V6we2MpDsVV2MraKQKSyE7aKTiirPyG+nkT4e9I5wIMIf08i/D3oHFDzGuzj3vJdmtuDqlJQvRJPAyWhwkyjc4JYG7mZMgaNhsZBeD/7lDhDjLE+mtPbUEzAv4DZQBlGL69S++sHInJnMwJ1xUhKK85MQmdLKGec+wxQJCKvNHWeTihae5VTmsP+3P2k5KWQkm9sh/MOU1DReGO4iBmpDMBWGYhU+mGz+COV/tgsAdWfXcSbUF8Pwv2NLczPgwj7a7hfzT4P1wt8EKS10uhWnXcUTh+1j+s5DFn74dShhpNNUE9jzrWuY6H7+It2lc5WGYeilIoHJgLBQC6wWkR+acb1CvgQyBWRhxs4vpZGEopSyhsw2dtevDFKKM+KyPKmnqkTitaRiAinyk5xJP8IaYVppBelk1aURnqh8ZpTevb+KGJzQSz+2Cqrko0/YvFHLD7YLH6IxRex+BLo6WUkGf/6CSfc33jv7+l6YbZtWsqNpJK515gWJz0JTuwA6xkTpYf3g+4ToOcUiB5+0cxE0CFGyiulRgMbgF1U9bWEJwF34P8BIUAesENEpiilOgPviMjlSqluGNVrYLT9fCwiz53tmTqhaBeSUkspJ4pOkFGcQWZxJpkl9q245rWwstChe4nVA5s9uVRtxmc/xOKDWPxwU/6E+QQS7udZJ9GE+3kQZk9Eob4eF0YVm6XCmA7n2I9weDWkbgJLac1xn3CIu9pYTC0q8YLu8uzsKq+hTRy2AQXAYZFGKyzbjE4o2sWmuLKYzJJMThafJLM4k5MlJ8kpySG7NJuc0hxySo33FpvFofuJzYxYfY0kY/VGLN7Gq9Ubm8Ubk82HYM8gOvsGEx0QQkxgJ7rYOxBEBXoS5tdBE05lmZFcDn0P+5bU7QgQGgdDbof+11+Qk3Y6O6HYgLOdWAS8DTzRnqZk0QlF0+oTEfLL86uTS+1Ek1OSQ05ZDtkl2WSX5lBcWdS8e9tcqhOOWLzB5oO32Z8A9wCCvQKJ8AmmS0Aw3TqFcUlION2CQnAzt8HMAs0hYlSN7V4Ev3xmdIsGcPOBhDtgxH3GdDUXCGcnlCkYgw7/BywGsjGqp67GaFN5HOgLPIrR4+uZ5gbiLDqhaNr5KbWUklOaw+my05wuO01uWS65ZbnG5/LT5JScIqvE2FdYkUflOSzYqmyeuJv88HH1I9A9kBDvTkT5BtPZL5hAj0AC3ANqXt0D8XXzPf/VNs+VpQKSv4Wkd+GIvZOp2R2G3AZj/wTeQW0TVwtydkJZCBwQkacaOPYcECci1yil/g+4SUS6NjcQZ9EJRdNaV6mltDr5nCo7RVbxKY7l5ZBemENm0Slyy05TUJFPiTWfSilCTMXVa9E4SqHwd/evm2jsr6FeoUR4R9DZpzOdfTrj5+bnpG8KpP8MG18zqsQAPPxh3BOQ+Bs433V02pCzE0ohcI2IfN/AscnAVyLiY59A8lsRaTfDU3VC0bT27XRxOXszM9mfncHhnCyO5WdzojCHU6W5lNkKUeZilLmk5tWlGGUuPfuN7XxcfYjwiSDaN5qu/l3p6t+Vbv7d6OrfFW9X75b5Epl7YcWTkLLG+BzSB65dYKzK2QE5O6GkAZ+JyCMNHHsNuE5Eouzzen0gIiHNDcRZdELRtI4rv6SSI6eKScku4mBWEQcziziYVcix3EIwldYkGhcj2Xh6lNLJrxR3z3ysplzyK7MoszaefKJ9o4kPiicuKI64oDjig+PPPcmIQPJ3RmI5fQRMrjDxLzDifjC1w9kNmuDshPIH4BWM2YaXULcN5VrgERGZp5R6CegvIlObG4iz6ISiaReeskorh7KKOJRVxIHMQvacKGB3ej6niivOOFPw9aogLtpKZEgxXt6nKJETHC1M5Uj+ESrPmNDSrMzEB8WTGJ7I0PChDAwdiJdrM9e6qSiBlU8ZbSxgDJKc+YExB1kH0RrrodwAPIHR+F5lN/CCiHxiP6czUCYiuc0NxFl0QtG0i4OIcLKgjF1p+exOz2f3iQJ2peeTXVi3k4C7i4mBXQJIjPWja2QRVpfjJJ/ex55Te9ifux9rrdEPLiYXhoUPY3yX8YzrMo4w7zDHAzqwAr65z+gRFhgLN34OIb1a6Ns6V6sNbFRKeWCsZ5IpImXNfWBr0wlF0y5u6XmlbD2Sy5bUXLYeyeVgVt1u0L7uLozqEczY3iEM6+ZNetletp7cypaTW9iXuw9brVEQieGJTO82nckxk/Fx8zn7w/PT4dMbIGOnMRPyzYug86CW/ootrkOMlG8LOqFomlZbbnEFW1Nz+fHwKdYfzCYlu7jO8UHRAVzeN4KpfcPx9ipjfdp6Vh9bzQ8nfqDcPjWLh9mDK7pdwU19bqJnYM+mH1hRAgtvhUOrwN0Pblvc7pNKa1R5RQI3AL0wJoWsQ0Rube7DW4NOKJqmNeV4bgnrD2azNjmb9QeyKbfUlEj6Rfpz7eBIrhkUidmlnO+Pfs/iw4tJyqz5nTIsYhh397+bxPDExh9iqYAvfwN7vzFmNJ79HYT2cebXOi/ObpQfgDEHVw4QA+wHAoFwjAWzjorIyOY+vDXohKJpmqOKyy2sTc7mu90ZrN6fRUmF0Z7i5mJiSnw41yd0YWT3II4WpvLxvo/55vA3lNrn+xoRMYIHBz9I3+C+Dd/cUgELb4EDy8E/Gn67GnzaTYfYOpydUFYBWcBtQAU1y/9OAD4AZovI/5r78NagE4qmaeeirNLK9/sy+WzrcTYeyqHqV2VskBd3junGzCFRVEgxH+37iH/v+TdF9ilqrup+FX9M+CMBHg3M8VVZCh9cAenbIGoo3L4MXNrftDPOTig5wM3ASsACjBaRH+zHfgvcKyKDm/vw1qATiqZp5yvtdAlfbEvj86Q00vOMEkknbzfuGBXL7aO6YpEi3t/zPv/d+18qbBV08ujEY4mPcXnXy+tP/1+YCf8abywANuJ+mHLWSdNbnbMTSi5wrYisVUplAg+KyGf2Y5OAb0SkhYactoviO1sAACAASURBVCydUDRNaykWq43le06yYH0Kv6TlAxDk7cbvxvfgpmHRZJam8eyPz7Ll5BYApsZO5a8j/1p/LMuxn+D9acYiXzd9AT0nt/ZXadK5JhRHh2/uA7rZ3/8EPKSU6qKUCgN+D6Q298GapmkdjYvZxJX9O/PNfaP4+DfDGBwdwKniCuYu3cuk19ZxKN2Ddy57h2dHPouXixfLU5dz07c3cbTgaN0bRQ+DCfapERc/aCxtfAFwNKG8C0Tb388BYjGSyAlgHPBYC8elaZrWbimlGNkjmEX3juTd2xLoHeZL2ulS7vwwibv+s43E4Cl8csUndPXvyqG8Q8xaOotN6Zvq3mTUQ8Y69oUn4Ptn2uR7tDSHEoqIvFc1Jb2I7ALigBnYuxGLyDJH7mMv1axRSu1VSu1RSj1k3z/T/tmmlGq0mKWUmqqUSlZKHVJK/cmRZ2qapjmLUoqJfcJY9uBo/nJlHD7uLqzam8mUeev5+bAbH1/+MZNjJlNUWcT9q+9neWqtFctNZrjqH8acX0nvGTMXd3AOJRSl1HVKqcCqzyKSJyJLRGQhUKyUus7B51kw5v2KA4YD9yml4jCmcLkWWN9EDGbgTWAaRkK7wX6tpmlam3Ixm7hjdFf+98hYpvUNp7jCyqOf7+RPXxzgz4kvcEvcLVhsFh5f/zirjq6quTAsDobfa7xfMQc6+EBzR6u8PgEaGw7a3X78rEQkQ0R+tr8vxGibiRSRfSKSfJbLhwKHRCRFRCqATzEmp9Q0TWsXwvw8+OdNg3ll5gC83cws/SWDK//fJqZH3c3d/e/GJjYeW/9Y3eqvSx81pmU59kPNuiodlKMJpakFoQOBwuY+WCkVCwzCaOR3RCRwvNbnNPu+hu59l1IqSSmVlJ2d3dzQNE3TzplSil8PiWLZg2PoH+VPel4pv37rR/p4/Jrb4m7DYrPw6LpHOXT6kHFB1aJcAGtfAFu7WUG92RpNKEqpK5RS/1RK/dO+67Gqz7W294B/Adua81CllA+wCHhYRArOOfpGiMgCEUkQkYSQkPY5ElXTtAtbbLA3C+8ewfQBnSmusPLbf28jsOwaLou5rLpNJb/c3rtr8K3gFwlZeyHZoSbpdqmpEko0MMa+AQys9blqGwD8ANzj6AOVUq4YyeQjEfmyGbGmA11qfY6y79M0TWuXPFzNvDFrIA9P6olNYO6y/URU3E5cUBzpRek888MziAi4uMOoh42L1r3UYdtSGk0oIvKWiPQTkX4Y1VJXVX2utQ0RkZtE5KAjD1PGkNF3gX0i8lozY90K9FRKdVVKuQGzgMXNvIemaVqrUkrx8KRevDpzACYF/1h9jDjzffi4+vD9se/5/MDnxomDbwWfMDj5C6RuaNugz5Gj3YZHiMjeFnjeKOAWYIJSaod9u1wpdY19meERwDKl1AowFuxSSn1rj8EC3A+swGjMXygie1ogJk3TNKf71ZAo3rhhECYF768rZEwno3fXq0mvklGUAa4ekHCHcfKWBW0Y6blrdOoVpdQdzbmRiLzXIhG1MD31iqZp7ckX29J49POdAIwYsZTdeRsZEzmGNye+iSrKhHnxIDZ46BcI6HKWuznHuU694tLEsXeacR8B2mVC0TRNa09+PSSK7MJy/rZ8Pzu2j8e/5w42pG9gfdp6xnYZC3EzYPcXsO0DmPjntg63WZqq8vJsxubVyD00TdO0M9wzthu/GhxFaZk3ctqYGPKVpFeotFVCwmzjpF8+63BdiJtqlC9vztaaQWuapnVkSimeu6YvfSL8yEwbgidhpBak8sWBLyB6JPh3gfzjcHTT2W/Wjjg6sBGllK9S6iGl1OdKqf/ZXx+0jynRNE3TmsHD1cw/bhyEl5s7uWlGKeWdXe9QIRbob5/N6pdP2zDC5nN0Lq9Y4BfgFYzR6QX211eBX5RSMU6KT9M07YLVPcSHP027BEthHKoigqySLL4+9DX0n2WcsOcbY5XHDsLREsprQDnGzMIjReQa+xryvYAy+3FN0zStmW4eFkNCTBAlWeMBeHfXu1QGdYWIAVBRCCnr2jhCxzmaUCYCc0TkSO2d9s9PA5NaOjBN07SLgcmkeP7aftiK+mIrD+FE8QnWHFsDl0w3Tti/tG0DbIbmTA7Z2FwANpqePFLTNE1rQq8wX65PjKXi9AgAPtn/CVxyhXEw+TuwWdswOsc5mlDWAX9VSnWuvVMpFYFRQlnbwnFpmqZdVH4/uSeuJYmI1Z2kzCQOuJohsCuU5MDxLW0dnkMcTSi/BwKAFKXUWqXUZ0qpNcAR+/7fOytATdO0i0Gorwe3DOtNZf5gABYe+LymlNJBqr0cncvrEMYCW3/CmOE3BGM9+ceA3iJy2GkRapqmXSTuHN0VCoYDsCzlOyp7TDQOHF7ThlE5rqmpV+oQkTLgdSfGommadlEL9fPg2n6JfJMdThEnWW+qZKKrF2TtgcJM8A1r6xCb5Og4lJVKqdlKqQBnB6RpmnYxu3N0bHW111cp30LMSOPAkfbffdjRNpRy4C3gpFJqiVLqRj1CXtM0reX1CPWlr984RBQb0tZTEGP0/OoI1V6OtqFMB8KAezGqyT4AMpVSXyilZiqlPJwXoqZp2sXl1mH9sJZ0xYaF9d72v91T1rT7lRwdnstLRPJF5H0RmQZEUNPz6yMg00nxaZqmXXSm9Y3ApawfAIuz9oJ3CBRmwKn23f/J4YRSm4icArYB24FcQFd/aZqmtRAPVzPjoyYAsDXrR8q7DDUOHN/chlGdXbMSilKqv1LqOaXUQWALcDXwL6C/g9d3UUqtUUrtVUrtUUo9ZN/fSSm1Sil10P4a2Mj11lpLB+v15DVNu2D9emA/rKWRWKSMn4IijZ3HLoCEopT6q1JqH0aJ5EbgSyBRRHqJyJ+bsba7BXhEROKA4cB9Sqk4jPEt/xORnsD/7J8bUioiA+3bVQ4+U9M0rcMZ0T0I13Kj2mtJeaGxs52PmHe0hPIbYAUwSkS6isjjIvJzcx8mIhlV14lIIbAPYxr8q4EP7ad9CMxo7r01TdMuJK5mEyMiRgGwKe8guHhATjKU5LZxZI1zNKFEicjDItJi5S37GiuDgJ+AMBHJsB86idGjrCEeSqkkpdRmpVSjSUcpdZf9vKTs7OyWClnTNK1Vzew/DLF4UWjN4nhne8tCOy6lONptuEX7qtnHsCwCHhaRggae1djzYkQkAaPa7XWlVPdG4l0gIgkikhASEtKSoWuaprWakd1CsZUav+bW+dv/zm7HDfPn1MvrfCilXDGSyUci8qV9d6Z95uKqGYyzGrpWRNLtrykYMxwPcnrAmqZpbcTTzUyM10AAVlWUGDtPbG/DiJrWqglFKaWAd4F9IlJ7lcfFwG3297cB3zRwbaBSyt3+PhgYBex1bsSapmlta1LsGAB2VaRhBcjY2W4HOLZ2CWUUcAswoVb338uBF4HJ9u7Ik+yfUUolKKXesV/bB0hSSu0E1gAviohOKJqmXdCu7tsXW0UnKikl2S8ESk9D/vG2DqtBDs823BJEZCONr+44sYHzkzB6mCEiPwD9nBedpmla+9M9xAc3Szcsbrn8FBBFXEE2nNgBAdFtHVo9jSYUpdSE5txIRFaffziapmlabUopuvv1JdmSxGazK7PBqPaKa39D8ZoqoXyP0dvKkfXiBTC3SESapmlaHaOiEkhO/YBdYh/gmLGjbQNqRFMJpU+rRaFpmqY1alrvgbx72INCczEnzWbCT+wwGuaVI3/vt55GE4qIJLdmIJqmaVrDeoX6oSpiwXM/230CmZafY8w+7Ne5rUOro7mTQyqlVGelVLczN2cFqGmadrEzmRRRnkal0WbvIGNn1r42jKhhjk4O6aKUmgfkAceBgw1smqZpmpMMDhsAwC9m+6/t7P1tGE3DHC2hPAlcDzyM0Uj/B+B3wCYgFfiVM4LTNE3TDOO7GiPmU01lxgDHjlpCwZg76xng3/bPG0VkvohcijG542QnxKZpmqbZDY+NwVYZgMVk5airS4cuoURjTJdiBcoxlv6t8iFwXUsHpmmaptXwcXfBw2YMZtzr5gbZye1uChZHE8pJwN/+PhVjCpUqMc24j6ZpmnaOIr16ALDL0w/KC6DgRBtHVJejiWA9NUnkPeAppdR7Sqm3gFeBpc4ITtM0TavRLyQegF/cvYwd2e2rHcXRhPIU8Ln9/SvA08AAYCxGgrm/5UPTNE3TahsbYzTMH3SxYQPIal/tKA5NDikiaUCa/b0AL9g3TdM0rZWM7NoN20Zfyl0LSXdxoUs7a5jXbR+apmkdhI+7C+4SCcAhN1fITWnjiOpydGCjWSl1v1JqtVLqgFLq2JmbswPVNE3TINjN6Ol12NUVTh1u42jqcnQ9lFeBB4CVwBKgwmkRaZqmaY3q5t+NjHw46OYO2SehvAjcfdo6LACUONCPWSl1EpgnIn9zfkgtKyEhQZKSkho9XlBQQFZWFpWVla0YlaZp2rkpLC+jsDIXF4FQqwV8w8Hs1qLPSE9PrwgJCcmotcumlDppsVj+Onjw4BWNXedoCcUF2HZeEQJKqS4Yo+3DMNZQWSAif1dKdQI+A2IxxrlcJyKnG7j+NoweZwD/JyIfnk88BQUFZGZmEhkZiaenJ6qdTQWtaZp2ppKKCo4UHEQBl1RUoAKjwTOwRZ9htVotffv2zan6bLPZVGlpqX9qauo/fv755/sbSyqONsq/B8xsgTgtwCMiEgcMB+5TSsUBfwL+JyI9gf/ZP9dhTzpPA8OAocDTSqnz+lfMysoiMjISLy8vnUw0TesQPFxdETEjQAUKLOVOf6bJZBJvb+/S2NjYChcXl6cbO8/REsph4Aml1HfAKoxZh+sQkffOdhMRyQAy7O8LlVL7gEjgamCc/bQPgbXA42dcPgVYJSK5AEqpVcBU4BMHv0M9lZWVeHp6nuvlmqZprc6kFCZcEayUmxTurZBQqnh6epaJSHhjxx1NKG/ZX6MxfrGfSTBKMQ5TSsUCgzAmlwyzJxswpnkJa+CSSIyp86uk2fc1dO+7gLsAoqOjzxZHM6LWNE1rey4mNyqljHKlwNp6CcVkMglN1Gw5WuXleZbNqzlBKaV8gEXAwyJSUPuYfeDkec14JiILRCRBRBJCQkLO51aapmntjrvZHcBIKK1YQjkbhxKKiJSfbXP0gUopV4xk8pGIfGnfnamUirAfjwCyGrg0HehS63OUfZ+madpFxdPFSCgVSoHNAjZrG0dkaDSh2Jf2da31vsnNkYcpo37pXYyp8F+rdWgxcJv9/W3ANw1cvgK4TCkVaG+Mv8y+76L2zDPPoJRCKYXJZCIwMJDExETmzJnDyZMnnfZcpRT/+Mc/WuResbGx1d+h9vbf//632fd59NFHqz/ffvvtJCQktEiMjfn+++9RShEaGkpJSUmdY6+//jouLo7WKmstIS8v75x+dm6++WaGDx/upKhanperB2AvoQBY28fQwKZ+2g9h9MTaYn/fWDWUsh8zO/C8UcAtwC6l1A77vieBF4GFSqk7gaPY11dRSiUA94jIb0QkVyk1F9hqv+7Zqgb6i52/vz/Lly8HID8/n59//pm33nqLBQsWsHz5coYMGdLGEZ7djTfeyAMPPFBnX48ePdoomubLzs5m/vz5/P73v2/rULSLgKerK6CwIVgAF2sFuLZ9B6OmEso0YF+t9+dNRDZiJKCGTGzg/CTgN7U+v0czG/8vBi4uLnX+upoyZQr33nsvl156KbNmzWL//v2YzY7k+7YTERHRbv9CLC0tPWtvwHHjxvHKK6/wu9/9Dnd391aKTLtYmU0mEBdQlVQohYulfZRQGq3yEpEVIlJY632TW+uFrDkiICCAl156iUOHDrFq1arq/Tk5Odx2220EBQXh5eXFuHHjqD2TwGOPPUa3bt04cwaFDz74ADc3N7Kzsxt95jfffENCQgIeHh6Eh4fz2GOPtcgMBM888wzBwcH19p9LtduxY8eYNWsWnTp1wsvLiylTppCcnFx9PDU1FaUUH330EbfeeisBAQFMnz79rPf905/+RFZWFu+//36T55WWlvLoo48SFRWFu7s7AwcOZMWKmv995syZQ1xcXPXngoICXFxcGDp0aPW+zMxMlFKsWbMGqKmuWbx4MX369MHLy4vp06eTl5dHcnIy48aNw9vbm8TERPbs2VMnnpdffpmEhAT8/PwICwvj6quv5vDhuvNDjR49mlmzZvGf//yH7t274+fnx+WXX86JE00v7vTOO++glGLHjh1ceumleHl5MXjwYHbs2EFRURG33XYbfn5+dO/enYULF9a7/o033qBHjx64u7vTs2dP3njjjXrnLFy4kJ49e+Lp6cm4ceM4cOBAg7HMnz+fuLg43N3diY2N5dVXX20y9o7AZLRIGO0o7aTK65xmG1ZKmc7cWjow7fyNGzcOFxcXNm/eXL1vxowZrFixgldeeYXPPvsMm83G+PHjOXToEAB33HEHR44cYd26dXXu9f777zN9+nQa6zW3cOFCrr32WoYOHcrixYt5+umnWbBgAU888YRDsYoIFoulerNaW76RMTc3l9GjR5OcnMzbb7/NwoULKS4uZtKkSZSWltY599FHH8XX15fPP/+cJ5988qz3jomJ4eabb+Zvf/sbFoulwXNEhGuuuYb//Oc/PPXUUyxZsoRBgwZx5ZVXsmvXLgDGjBnD/v37yckxBilv2rQJV1dXtm/fTnFxMQDr16/H1dWVYcOGVd/7yJEjPPvsszz//PPMnz+fDRs2cPfdd3PjjTdy00038fnnn1NWVsasWbPqxJSWlsaDDz7I4sWLWbBgAeXl5YwaNYrCwsI6523atIn58+czb9483n77bZKSkrjnnnvO+u8CcOutt3LzzTfzxRdfUFlZycyZM5k9ezbR0dEsWrSIIUOGcMstt5CRUTPTx1tvvcXDDz/MNddcw5IlS7j22mt5+OGHeeWVV6rP2bJlCzfccAODBw/mq6++Ytq0aVx//fX1nv/CCy/wwAMP8Ktf/Yply5Zx11138cQTT/D22287FH975aqM6VbaU0JxqMXQ3s33WeAaoHMj17XvOhUHxf5pWZs+P/XFK1rsXh4eHgQHB5OZmQnA8uXL2bRpE2vXrmXs2LEATJgwgdjYWF5++WXmz5/PJZdcwqhRo3j//fcZN24cACkpKWzYsIHFixc3+BwR4Y9//CO33nor//znP6v3u7u7c9999/HEE08QFBTUZKyvvfYar71W008jMjKStLS08/n69cybN4/i4mJ27NhBp06dABg1ahSxsbG899573HfffdXnDh8+nDfffLNZ93/iiSf497//zUcffcRtt91W7/jKlStZsWIFGzduZNQoYwHUyy67jOTkZJ5//nk++eQTRo4ciclkYuPGjcyYMYMNGzZw1VVXsWbNGn788UcmTZrEhg0bGDJkCF5eNb31c3Nz+emnn4iNjQVg+/btzJs3j48++ogbb7wRAIvFwtVXX83Bgwfp2bMnAH//+9+r72G1Wpk8eTIhISEsWbKk+jqAoqIili1bhr+/sRL4iRMneOyxx6ioqMDNrel5pB5//HFuuummOjGMHz+euXPnApCQkMCiRYtYunQpv/3tb7FYLPz1r3/lzjvv5OWXX67+dzp9+jTPPfccDz74IG5ubrz44ovEx8fz6aefopRi6tSplJWV8cwzz1Q/Oy8vj7lz5/L0008zZ84cACZNmkRRURFz587l7rvv7rBj0dzMbpRb2ldCcbRk8T5wJ0ZvrEeB3zWwae1Q7aqrLVu2EBoaWp1MALy9vbnyyivZuHFj9b4777yTRYsWUVRUBBjVXWFhYUydOrXBZxw4cIBjx45x3XXX1SllTJgwgbKyMnbv3g3QZAnk5ptvZuvWrdXbt99+22L/BlW+//57Jk+ejJ+fX3Ucvr6+DBkyhDMnEL3iirqJ3Waz1Ym/oUlVe/XqxcyZM3nhhRew2WwNPj8qKophw4bVudekSZOqn+/n58eAAQPYsGEDYJRGLr30UsaMGVNn35gxY+rcu3v37tXJBGo6NEyYMKHevvT0mt72P/zwA5MmTSIoKAgXFxe8vb0pKSmpV3U0bNiw6mQCEBcXh4ictdoLYOLEmubRhuIKDAwkKCioOq5jx46RmZnJzJl1Z3u6/vrrycvLq66227JlC1dddVWdhHDttdfWuWbTpk2UlpYyc+bMOv/mEydO5MSJEw7F3155uFSVUGg3CcXRPo2XAfeJSPP64nVALVlCaGtlZWWcOnWKsDBj4oGMjAxCQ0PrnRcWFkZubk2Hueuuu46HHnqIhQsXMnv2bD788ENuvfXWRrvAVlXPXH755Q0eP378OKmpqXTt2rV6X0xMDKmpqXVicHYX35ycHDZv3sxnn31W71jtX3pV8dT2l7/8heeee67689y5c3nqqac405NPPsnAgQP54osvGnx+Wloarq6u9Y7VbsivSh5lZWVs3bqVf/7zn1RWVrJ48WLy8/PZtWtX9V/3VQICAup8rio11N5fta+srAwwqsmmTJnCyJEjWbBgAREREbi5uTFlypTqc852/zPPa0hDMTR0v6p7VVV9nfnfoOpz1c9qZmZmvZ/nMz9X/Wz27t27wdiOHz9OZGSDE260e16uHlBWNRal0hiLYmrbiiJHE0o6kO/MQLSWt2bNGiwWCyNGjACMnlRZWfXHjGZmZlZXAYFRapk1axYffPABMTExHDt2jNmzZzf6nKprFyxYwKBBg+od79q1K76+vmzdurV6X3N6Qnl4eFBRUfcvsNOn601GfVadOnXiqquu4s9//nO9Y76+vnU+n1kN8rvf/Y4ZM2ZUf27sl1D//v2ZPn06zz//fL1qr06dOlW3G5yp9vPGjBnDm2++yerVq/H29qZfv35UVlYyZ84c1qxZg4hUV5mdj++++47y8nK+/vrr6l5sFRUV5OXVm6qvVUVERADU+1mtqrqt+nkLCwurd86Zn6vO/e677xrs2HHJJZe0TNBtwNPVDVBYwd51uLLDJJQngDlKqaRac25p7VheXh6PP/44PXr0YNKkSYBRbfH0009XV6MAlJSUsGzZMq655po61995550MHz6cZ555huHDhzf5P17v3r2JjIwkNTWV3/72t42ed64lkKioKAoLC0lPT6/+Rb5y5cpm32fixIksXLiQ+Pj4Zk8K2rlzZzp37uzQuU899RRDhw5lyZIl9Z7/xhtv4OfnR69evRq9fsyYMVgsFv72t78xevRolFIMHDgQs9nMvHnziI+Pr/MHwLkqLS3FbDbXKXl++umnDVbXtaaYmBjCwsL4/PPPmTx5cvX+hQsXEhgYSHx8PACJiYksXryYuXPnVifkL7/8ss69Ro4ciYeHBxkZGY1W2XZUZpOq7jpcqZR9LIpHm8bkUEIRkW+UUmOAI0qpAzQ82/ClLR2c5hiLxVLdk6uwsJBt27bx1ltvUVJSwvLly6vHoFRVb1x//fW8+OKLBAUF8corr1BaWsof//jHOvccNmwY8fHxbNy4kfnz5zf5fJPJxKuvvsott9xCQUEB06ZNw83NjZSUFL7++mu++OKLOg3IzTV16lQ8PT254447eOSRRzhy5Mg59dD5wx/+wH//+18mTJjAAw88QGRkJJmZmaxbt47Ro0dzww03nHOMtSUmJjJ58mRWrVpVZ/zPtGnTmDhxIpMnT+bxxx8nLi6ueiCq1Wrl//7v/wCj2qZ3796sX7++ulHabDYzcuRIVqxYwb333tsicU6cOJHHHnuM2bNnM3v2bHbt2sW8efPw8/NrkfufK7PZzNNPP819991HYGAgEydOZM2aNfzrX//ipZdeqq42e/zxxxk5ciQ33HADt99+O7/88gsffPBBnXsFBQXx5z//mfvvv58jR44wevRobDYbycnJrF+/vsHSYkdiUi7YMBKKZytOEtkYR3t5PQ/8AdiFMWq+fbQAaYAxOn7EiBEopfDz86NHjx7cfPPNPPDAA4SH151p+uuvv+aRRx7h4YcfpqysjKFDh7J69eoGR6XPmDGDlJSUel1NG3L99dfj5+fH888/z3vvvYfZbKZbt25ceeWVZ+0FdDbBwcEsWrSIRx99lBkzZjBkyBA+/vjjOuM1HL3P5s2bmTNnDr///e/Jy8sjIiKC0aNH079///OK8UxPPfVUnfE/YFRrffPNN8ydO5dXX32V48ePExQUxMCBA3nwwQfrnDtmzBiSk5OrS5JV+1asWMHo0aNbJMaBAwfy7rvv8uyzz7Jo0SIGDRrEokWL6lTttZV7772XiooK3njjDebNm0d0dDTz5s3joYceqj5n+PDhfPzxx8yZM4evv/6aoUOH8umnn9YbIPvkk08SFRXF66+/zksvvYSXlxe9evVqsT8g2pKLcqVCSo1fyNa2X3XW0SWATwOvi8hfnR9Sy2pqCeB9+/bRp0+fVo6o4xg6dCi9e/fmP//5T1uHomlaA46cPkmJ9RSdrFYiXP2gU2yL3Hf37t0lffv23dfQsZ07dwYPGDCgwQc52oZSDvxwjrFpHUxSUhKrV69m69atzR6LoWla63E3u1Jihcqqnl5tzNGE8g/gDozVGrULXGJiIgEBAbzwwgskJia2dTiapjXC3cUdKqoGN3achOIBjFJK7QLWUL9RXkSk0XWGtY7FkWpQTdPanqd9cGOlwkgoItCGI/8dTSh32l+DgZkNHBdAJxRN07RW5OFSNY09WLFhFiuotluDx9FuwxHODkTTNE1rHpNJgZhBWahUCrO1Ekxtl1DOOpeXUspDKbVYKaXHmWiaprUzJnu5oD20o5w1oYhIGXApjlePNUop9Z5SKksptbvWvgFKqR+VUruUUkuUUg2OqlJKpdrP2aGUargfsKZp2kXGbDLmhquE9p9Q7JYBV7bA8z4Azpz/4B3gTyLSD/gK+OOZF9UyXkQGiohzZxHUNE3rIFyrEko76DrsaKnja2CeUioU+BbI5Iw15kVk9dluA6neYgAAIABJREFUIiLrlVKxZ+zuBay3v18FrADqz96naZqm1eNWeyxKG5dQHE0oVfN932jfziSc+wJbe4CrMZLWTKBLI+cJsFIpJcB8+f/tnXtc1FX+/58HGG4O6oAXUL+iWEhqloCXEq+Id7ykbZj7VRR/tuWWq6lrfTW03MxL2aabreaq5YWw0i62VpqWbSi6m+ZdM+9X1NVMkQTevz9mmBhmBgYYGdTzfDzmwXzO53zO53U+58O8P+fyeb9FFjgrUCk1EhgJUL9+/TLK0mg0msqPr3fhpcOe9Yrl6pDXfSV8SudUyZbhwFNKqX8DQTj3ExYnItFAD2BUcYsERGSBiMSKSKyzkLV3EkuWLCEmJoagoCBMJhMtWrRg7Nix1v0FcdI//fTTW3L+5ORkt8Qy2bRpE0op68dkMhEXF8eGDRvcoNLMzJkz2bRpk116aePTO4tzX1oK2kYpZRPkrIBp06ahlLIJnlUexo0bV+qybvX9oykfft7mIa9cPD/k5ZJBEZEDJX3KKkBE9otIVxGJAVYCh53kO2X5ex7zXEursp7zTmL69OmMGDGCbt268eGHH/LOO+/Qt29fm3C9YWFhZGRkuM2p4K1m+fLlZGRksGzZMvz9/enevTs7duxwS9nODEpGRoZdhMCKxGg0kpaWZpeelpaG0Wj0gCLN7YL5XRTIVQq5TSblUUr5KKWGKaX+ZllG3MiS3l8pdW9ZBVjmZVBKeQGTADu/5EqpKkqpoILvmCNI7i6a725k3rx5PPHEE7z88sskJCSQmJjIlClTOHTokDWPn58fbdq0sYuSV1lp3rw5bdq0oVevXqxZswaj0cjChQvLVWZ2dnax+9u0aWMXIbAiSUxM5P3337cJjbxr1y727dtH797uWA+juVMxeHsDXgiQm58L4rl4Ni4ZFKVUBLAPeAN4AOgFFASYTgCed7GclUAG0FgpdVIplQIMssRY2Q+cxhy/HqVUHaVUQWDx2sC3SqmdQCawVkTWuXLOO53Lly/buagH2wiAjoYsGjRowLhx45gzZw716tXDZDKRlJRkF63vhx9+sAYpatq0KZ999hmxsbEkJycXq+v48eMkJSURHBxMYGAg3bp148CB0ndkjUYjkZGR1nDBZ86cYfjw4URERBAQEEBkZCSTJk2yiehYUN/ly5czZMgQqlevTmJiIg0aNODixYtMnTrVOsxU0FtxNOS1evVqWrVqRUBAACEhIfTs2ZNjx4451Xrp0iVGjhxJ7dq18ff35+GHH2br1q0u1bNPnz5cvXqVjRs3WtPS0tKIi4tzGB3yyJEj9OvXj6pVqxIUFERiYiI//vijTZ7Lly/z+OOPYzQaCQsLswlhXBh3tZXGMyhlebkRcy+FvFyPaXF1Uv4N4CLwEGY/XoXnOTYB010pREScBSD4q4O8p4Gelu8/YTZkmiJER0czd+5c6tevT+/evQkJCXH52PT0dJo3b86CBQs4efIkY8eO5fnnn+fNN98EzNEcu3XrRmhoKCtXruTGjRuMGTOG//73vzRr1sxpuZcuXSIuLo6QkBDeeustAgMDeeWVV+jSpQsHDx4sVbTEvLw8Tpw4YT3fhQsXCA4O5rXXXsNkMnHw4EGmTJlCVlaWXSCwcePG8cgjj7Bq1Sq8vb0xmUx06tSJgQMHMmLECACnMVXeffddhgwZQlJSEpMnT0ZE+Oqrr8jKyiI8PNwuf05ODl26dOHy5cvMmjWLWrVqMX/+fLp06cKhQ4ccGv3CGI1GevfuzcqVK60RNtPS0hg/frydocjJySE+Ph6DwcDChQvx8fEhNTWVDh06sGvXLms0x2HDhrFp0ybmzJlDaGgos2fP5vDhwzYRGt3ZVhrP4aW8yecmuWCZRylfDKKy4qpB6QgkicgFpVTR1VxngTvHNcuUaiXnuaXnv1Kq7H/729/o168fycnJKKW47777GDBgAOPGjSsx8p7BYGDNmjXWH5i9e/eSlpZmNSiLFy/m4sWLbN++3fqU3KhRI1q3bl1suXPmzOHatWvs2LHD+uPWtm1bGjRowD/+8Q9GjRpV7PF5eXnk5uZy6dIl/vKXv3DmzBlriOL777+f2bNnW/O2bduWKlWqMHz4cObOnWsTzKtNmzZ27vd9fHyoV6+eXRCmwuTn5zNx4kT69+/PypUrrel9+vRxesyyZcvYvXs3e/bs4d57zSPAXbp0oXHjxrz66qvWyIvFkZSUREpKCvPnz2fHjh0cP36cgQMH8sorr9jkW7x4McePH+fgwYNEREQA5gibERER/P3vf+e5555jz549rFmzhrS0NB577DEAOnXqRP369W3ui/K2laZy4I0P+RS8i+K5Hoqrcyg3AYOTfWHAz+6RoyktzZs3Z9++fXz88cc89dRTiAgvvfQSsbGx/PLLL8Ue26lTJ5un1SZNmnD+/Hlu3jRP7G3bto2YmBibIZdWrVqVONewfv16EhISqFq1Krm5ueTm5hIUFERMTAzOgp0V5sEHH8RgMFC7dm0WLVrEjBkzrPMIIsLrr79OkyZNCAgIwGAwMHjwYHJycjh+/LhNOb169SrxXI44cOAAp0+fZtiwYS4fs379emJiYmjYsKG1zgAdOnRwqc4APXv2JC8vj88//5y0tDTi4+MdriTLzMwkOjraakwA6tWrR9u2ba0rxbZt2wZA3759rXmMRqNNjPYC3eVpK03loOBt+VwPv4viag9lPTBRKfUFcMOSJkopH2AUcOfMZ5Syh1AZ8PPzIzExkcTERAAWLVrEiBEjWLRokU3I1KIUnaT39fVFRMjJycFgMHD27FkcLbsuaSn2hQsX2LJlC++9957dvvj4+BLrk5aWRqNGjTCZTISHh9sYvddff53x48fz5z//mQ4dOmAymdi2bRujRo3ixo0bNuWUdZL94sWLgHl1nKsU1NlgsH/uatSokUtl+Pn50a9fP1asWMHmzZutMeaLcubMGYd1q127tnWO5+zZswQFBeHv72+Tp1atWg51l7WtNJUDg5cPN/It76J4sIfiqkEZjzli4yHMxkOAiUBToDpw+wdnvoNISUlhwoQJ7N+/v1zlhIaGOpyczcrKKva44OBg+vTpw+TJ9g4PgoKCSjxv06ZNnc7RrFq1ioEDB9pMMO/du9dh3sILE0pDwTzUmTNnXD4mODiY2NhY5s+fb7fPz8/P5XKSkpLo3bs3BoPBOsxXlLCwMPbs2WOXfu7cOeuwVWhoKFevXuXGjRs2RuX8+fN2usvTVprKgcHbALkF76JUcoMiIkeVUg8AE4B44BTQGLOblJkicu7WSdQUx/nz5+2eOrOysrhy5Uq5l8G2bNmSFStWcOrUKeuwV2ZmJufOFd/c8fHxpKen07RpU7dP6mZnZ9v9QC9fvtzl4319fe16MkVp3LgxdevWZenSpdZeX0nEx8fzxRdfUL9+fbv2KA0JCQkMGDCAqKgoqlVzPJ/XunVr3nnnHY4cOULDhg0BOHXqFN999x1TpkwBsEba/Oijj6xzKL/88gtffvmlzRzKrWwrTcVR8HKjp92vODUoljfR/yMivwCISBbFO27UeID777+fvn370rVrV2rVqsWxY8eYPXs2gYGBDB06tFxlDxs2jGnTptG7d29SU1PJzs4mNTWVmjVr4uXlfPpt7NixLFu2jM6dO/P0009Tt25dzp07x9dff01cXByDBpW9Q5uQkMAbb7xB69atadSoEcuXL7dbBVUcUVFRrF27lu7du2M0GmncuLHdk7iXlxczZ85k8ODBDB48mEGDBqGU4quvvmLQoEEOvQIMGTKEt956i44dOzJu3DgiIiK4ePEimZmZhIaGMmbMGJf0+fj4kJ6eXmye5ORkZsyYQY8ePXjxxRfx9vZm6tSp1KhRgyeeeAIw9/L69OnDk08+yc8//0xYWBizZs0iMDDQpqxb2VaaisPfErkx18NDXsVNym+kfC5VNBXACy+8wNGjR3nmmWfo2rUrkydPpmnTpmRmZlqfXstKYGAg69atIyAggMcee4wpU6Ywc+ZMqlevXuwKsho1arBlyxaioqIYM2YMXbt2ZcKECVy5coXmzZuXS9MLL7zAoEGDmDRpEoMGDcLX15c33njD5eNnzZpFlSpV6NWrFy1btuTf//63w3yPP/44H3zwAfv372fgwIEMGTKE/fv3O50/8vf3Z+PGjSQkJJCamkrXrl0ZPXo0hw4dolUr9zp18PPzY/369URFRZGSksLQoUOpX78+mzZtsg55gdklT9euXfnTn/5ESkoK8fHxJCUl2ZR1K9tKU3H4WeYZ81Dke7CHopzFD1dK5QNtRCSzYiW5l9jYWHG2WmXfvn3cd999Fazo9ubIkSNERkayYMGCUq2C0mg0t5Y9WftB5XFPbh5+oeV7GNi9e/f1Zs2a7XO0b+fOnTUeeOCBBo72eS5WpOa2YPr06dSpU4fw8HCOHz/O9OnTqVmzJgMGDPC0NI1GUwiFN0IeeZJvdr+iXPas5TZKMig9lVJRrhQkIu+4QY+mkqGUYurUqZw+fRo/Pz/atWvH7NmzS3xpUqPRVCxKeZv9eRW4X/Gp+LflSzIoL7hYjgDaoNyBTJw4kYkTJ3pahkajKQFv5UO+4FH3KyUZlE6Afl1Wo9FoKjneypubYumheGilV0kGJVtErlWIEo1Go9GUGR8vH8i39FA85HG44mdtNBqNRuN2DF6WpcPKc5EbtUHRaDSaOwBDQShgD77c6HTIS0S0sdFoNJrbBF9v8895Lp5zv6KNhkaj0dwB+Nn0UPKKz3yLqFCDopT6h1LqvFJqd6G0B5RSGUqpXUqpT5RSDl9wUEp1V0odUEr9qJTS61gLsWTJEmJiYggKCsJkMtGiRQvGjh1r3e8oBLA7SU5OdujfqrRs2rTJGppXKYXJZCIuLo4NGza4QaWZmTNnWsP+FsZRCODimDJlisNYJaWloG2UUtZYJoWZNm0aSikaNGhQ7nOBOYplacu6VfdPQRjqAtLT01myZIldvo4dOzJw4EC3nruAW1W3zMxMq6NOd1Lcfeprcb+SjyKv0JCXs3v+VlDRPZQlQPciaW8DE0XkfmA1DhxQWqJE/g3ogdm/2CCllPYzhvlN9hEjRtCtWzc+/PBD3nnnHfr27cvHH39szRMWFkZGRgZxcXEeVOo6y5cvJyMjg2XLluHv70/37t3ZsWOHW8p29s+VkZHBo48+6pZzlAWj0UhaWppdelpaGkaj0QOKbj2rV6/mmWeesW47Myi3I5mZmUydOrVCz+mlFFhmKnLlLjAoIvINcKlIciTwjeX7l4Ajnx6tgB9F5CcR+RVIA/o6yHfXMW/ePJ544glefvllEhISSExMZMqUKRw6dMiax8/PjzZt2tgF1KqsNG/enDZt2tCrVy/WrFmD0Whk4cKF5SozOzu72P1t2rQpt7v/8pCYmMj7779PXt5vQxW7du1i37591miVdxotWrSgfv36npbhUUSkxHAKpUFhjtCeJ3fBkJcT9vCbcXgU+B8HeeoCJwptn7SkOUQpNVIptV0ptb2kYFC3O5cvXyY0NNQuvXBwKUfd+oLhhjlz5lCvXj1MJhNJSUlcvnzZppwffviBhx9+GH9/f5o2bcpnn31GbGwsycnJxeo6fvw4SUlJBAcHExgYSLdu3RwG6yoJo9FIZGQkR48eBcxBr4YPH05ERAQBAQFERkYyadIkfv31V7v6Ll++nCFDhlC9enUSExNp0KABFy9eZOrUqdZhpoInN0dDCatXr6ZVq1YEBAQQEhJCz549rRERHXHp0iVGjhxJ7dq18ff35+GHH2br1q0u1bNPnz5cvXqVjRs3WtPS0tKIi4uzCcFcwJEjR+jXrx9Vq1YlKCiIxMREOzf+ly9f5vHHH8doNBIWFmYTlKww7mirDh06MHLkSOv2559/jlLKZuj1gw8+wNfXl+vXrwO2Q17Jycl88MEHfP3119a2KTpktGLFCu655x6qVq1Kjx49OHnyZIm6ylq3t99+m6ZNm+Ln50d4eDgzZ860y/PNN9/QqVMnjEYj1apVo2PHjnz//fcsWbKEp59+GsBal44dOwK/DZV+++23tGzZEn9/f1atWgW41qaFefPNNzEajTahvpXyIfNfmVQJe5Cd3/+n2Hs+Pz+fV155hXvuuQc/Pz8iIyNZunRpidemOCqDQRkOPKWU+jcQBPxaQv4SEZEFIhIrIrElhau93YmOjmbu3LksXbrUGrrWVdLT09mwYQMLFixgxowZfPrppzz//PPW/devX6dbt25kZ2ezcuVKJk2axJgxY+xitxfl0qVLxMXFceDAAd566y3S09O5du0aXbp0KbGnUJS8vDxOnDhhNZoXLlwgODiY1157jXXr1jF+/HgWL15s/QcuzLhx4wgKCmLVqlU8//zzrF69mmrVqpGSkkJGRgYZGRlER0c7PO+7777LI488QqNGjUhPT2fx4sVERkY6jVaZk5NDly5dWL9+PbNmzWLNmjXUrFmTLl26cPbs2RLraTQa6d27NytXrrSmpaWlOYxHkpOTQ3x8PPv27WPhwoUsWbKEI0eO0KFDBy5d+m0AYNiwYfzzn/9kzpw5LFiwgC+++MJuWM1dbdWuXTs2b95s3f7mm2/w9/e3S4uOjraLyQIwefJkOnXqRIsWLaxtM2LECOv+rVu3Mm/ePF599VUWLFjAf/7zHxsD5oiy1m3WrFk8+eST9OvXj08//ZQnn3ySyZMn2zxwbNq0ifj4eAwGA0uXLuW9996jXbt2nDp1il69evHss88CWOvy5ptvWo+9fv06Q4cOZcSIEaxbt45WrVq53KaFefzxx8nLy+P999+3pnkrb9asWMMD99/HA/c3K/aef/rpp5k2bRojR45k7dq19O/fn+HDh5dvPklEKvQDNAB2O9kXCWQ6SH8I+LzQ9nPAc66cLyYmRpyxd+9eu7RmS5p59FNadu7cKQ0bNhRAlFLSpEkTmTx5sly5csWa58iRIwLIJ598Yk0LDw+XiIgIuXnzpjVt9OjRUrt2bev2vHnzxGAwyMmTJ61pW7duFUCGDh1qTRs6dKgUvs6TJk2S4OBguXjxojXt0qVLUrVqVZk3b57TumzcuFEA2bFjh9y8eVPOnTsnzzzzjJ32wty8eVOWL18ufn5+kpOTY1Pffv362eUPCQmR1NRUu3RA5s6dKyIieXl5UqdOHenfv79TrampqRISEmLdfvvtt8VgMMjBgwdttEVERMi4ceOcllO4bT788EMxmUySk5MjW7duFR8fH8nKypJnn31WwsPDrcfMnz9fvL295fDhw9a0EydOiMFgkJdffllERHbv3i2ApKWlWfNcvXpVTCaTTVmutJWj+6co69atE0DOnz8vIiLt2rWTUaNGibe3t1y9elVERFq0aGFzLcLDw+XZZ5+1bg8YMEA6dOhgV3aHDh2katWqcunSJWvanDlzBJDr16871VSWul25ckWqVKkiU6ZMsSlr8uTJUrt2bcnNzRURkTZt2khMTIzk5+c7PPfcuXPF/PNqS2pqqgCyZs0am3RX2lTE9j4VERk8eLC0b9/eur3z2AEJCAyQl/8yUSTnmog4vucPHTokSilZsmSJTfr//u//SmxsrOzateuaiGx39NmxY8dRcfJ76/EeilKqluWvFzAJeMtBtm3AvUqphkopXyAJ+NhBvruO5s2bs2/fPj7++GOeeuopRISXXnqJ2NhYm66wIzp16oSPz2+vIjVp0oTz589z86Z5Dfu2bduIiYmxGXJp1apViXMN69evJyEhgapVq5Kbm0tubi5BQUHExMTgLDZNYR588EEMBgO1a9dm0aJFzJgxwzqPICK8/vrrNGnShICAAAwGA4MHDyYnJ8eu59SrV68Sz+WIAwcOcPr06VLFe1m/fj0xMTE0bNjQWmcwDwW5UmeAnj17kpeXx+eff05aWhrx8fEOV5JlZmYSHR1NRESENa1evXq0bdvWulJs27ZtAPTt+9tUo9FoJCEhwU53edqqgIcffhhvb2++/fZbcnJyyMzMZMSIEYSEhJCRkcHPP//Mzp07adeunctlFqZly5aYTCbrdpMm5jU5p06dcnpMWeqWkZHBtWvXePTRR63H5Obm0rlzZ86dO8fJkye5du0aW7duZejQoTZDy66ilKJHjx42aa60qSNSUlLYvHkzP/30EwCff7yWvLw8+j7Ss9iXGzds2ICXlxf9+/e3qWd8fDw7duywmcsrDRUaD0UptRLoCNRQSp0EUgGjUmqUJcuHwGJL3jrA2yLSU0RylVJ/xBzD3hv4h4jsuRUadw3ddSuKvaX4+fmRmJhojX++aNEiRowYwaJFixg9erTT44pO0vv6+iIi5OTkYDAYOHv2rMMIhSUNI164cIEtW7bw3nvv2e2Lj48vsT5paWk0atQIk8lEeHi4jdF7/fXXGT9+PH/+85/p0KEDJpOJbdu2MWrUKLvJzbJOshcMHYaFhbl8TEGdDQaD3b5GjRq5VIafnx/9+vVjxYoVbN68mWnTpjnMd+bMGYd1q127tnWO5+zZswQFBeHv72+Tp2i8+/K2VQFBQUE8+OCDbN68mRo1ahAQEEDz5s2tQ2G5ubmISJlXGjq6V4FiJ7TLUrcLFy4A5hDKjjhx4gTe3t6ISKnuj8KYTCar/gJcaVNHdOzYkYiICJYsWcKLL77I+yvS6dS9E0HB1Ys1KBcuXCAvL49q1ao53J+VlVV6S0kFGxQRcRag+q8O8p4Gehba/gz47BZJu6NISUlhwoQJ7N+/v1zlhIaGOpzALGmhQ3BwMH369GHy5Ml2+4rGb3dE06ZNadasmcN9q1atYuDAgTYTzHv37nWYtyxPjwAhISGA+Z/cVYKDg4mNjWX+/Pl2+/z8/FwuJykpid69e2MwGOjfv7/DPGFhYezZY/88de7cOWsI4NDQUK5evcqNGzdsjMr58+ftdJenrQpTYDxCQkJo27YtXl5etGvXjjVr1nDz5k2aNGliE6L4VlOWuhXo+/TTTx3+wDdu3BgvLy+8vLxKdX8UxtF96UqbOitr+PDhLFiwgN///vdkZmxh+Yq3qZGXV6xBCQ4OxsfHh3/96194edkPVHl5eTkO5VsCOmLjbc758+ftnjqzsrK4cuVKuZfBtmzZkhUrVnDq1CnrsFdmZibnzp0r9rj4+HjS09Np2rQpAQEB5dJQlOzsbLsf6OXLl7t8vK+vb4nLNBs3bkzdunVZunSptddXEvHx8XzxxRfUr1/frj1KQ0JCAgMGDCAqKsrp02Pr1q155513OHLkCA0bNgTMQz/fffeddWVUy5YtAfjoo4947LHHAPjll1/48ssvbYKjubOt2rdvz9y5c/H19bUOtbVv357nnnuOa9eulTjc5UrblIay1O2hhx4iICCA06dPFztkWtAGf/zjHx0aiMI9qKK9xOLKK65NnZGcnMwLL7xASkoKdevW5bHu3fDOzrK+Le/ounbu3Jm8vDyuXLliNwwKsHv3brs0V9AG5Tbn/vvvp2/fvnTt2pVatWpx7NgxZs+eTWBgIEOHDi1X2cOGDWPatGn07t2b1NRUsrOzSU1NpWbNmg6fagoYO3Ysy5Yto3Pnzjz99NPUrVuXc+fO8fXXXxMXF+dw5ZKrJCQk8MYbb9C6dWsaNWrE8uXLi11aWZSoqCjWrl1L9+7dMRqNNG7c2O5p1cvLi5kzZzJ48GAGDx7MoEGDUErx1VdfMWjQIIdeAYYMGcJbb71Fx44dGTduHBEREVy8eJHMzExCQ0MZM2aMS/p8fHxIT08vNk9ycjIzZsygR48evPjii3h7ezN16lRq1KjBE088AZh7eX369OHJJ5/k559/JiwsjFmzZtmtsHJnW8XFxZGXl8d3333Hq6++CsADDzyAwWBg27Zt/OlPfyr2+KioKD766CPWrFlDvXr1qFOnDnXq1HH5/EUpS92qV6/OlClTGD16NMeOHaN9+/bk5+dz8OBBNm7cyOrVqwF45ZVX6NKlCz169GDkyJFUqVKFjIwMYmNj6d27N1FR5kC3f/3rX+ncuTNVq1alcePGTrW60qbOqFOnDt27d2ft2rU899xzeBv8IBtrD8XRPd+4cWP+8Ic/kJSUxIQJE4iNjeXGjRvs2bOHgwcPlthWTnE2W3+nfEq7yut2Y968eZKQkCBhYWHi5+cn4eHhMmjQINm3b581j7NVXoVX2IiILF68WADrqhwRkR07dshDDz0kvr6+EhkZKatXr5Z7771XRo8ebc1TdJWXiMipU6ckOTlZatWqJb6+vhIeHi6DBw+W3bt3O61LwSqvXbt2Oc1z9epVSU5OFpPJJCaTSVJSUuSTTz6xOa64VUnbt2+X1q1bS2BgoACyceNGEbFfPSMi8sEHH0h0dLT4+flJcHCw9OzZU44ePSoi9qu8REQuX74szzzzjNSrV08MBoPUrVtX+vfvL99++63T+riygqroKi8RkcOHD0vfvn3FaDRKlSpVpFevXjYrzETMK5oee+wxCQwMlFq1asnUqVMdllVSW7misYCoqCgJDAyUX3/91ZrWvXt3AeTYsWM2eYveg1lZWdKvXz8xmUwCWFcmdejQQQYMGGBzrCv3Snnq9u6770p0dLT4+/tL9erVpVWrVvLqq6/a5Nm0aZO0a9dOAgICpFq1atKxY0f5/vvvRUQkPz9fxo8fL2FhYaKUsq5ec3TfFOBKmzq6T0VEFi5cKIA5/7WLIqf+I3LxJxFxfs/n5+fLnDlzpEmTJuLr6ys1atSQ9u3by9KlS8u8ykuZNd65xMbGirMVHfv27eO+++6rYEW3N0eOHCEyMpIFCxaUahWURqO5dfzud7/jzJkz5vd+bvwMlw6DrxFq3Fum8nbv3n29WbNm+xzt27lzZ40HHniggaN9eshLUyzTp0+nTp06hIeHc/z4caZPn07NmjUZMMCRhxyNRlOR7Nq1i+3bt/Phhx/+9tKqJdCWJzwOa4OiKRalFFOnTuX06dP4+fnRrl13ybqWAAAH70lEQVQ7Zs+ebTOxq9FoPENiYiIXLlzgqaee+s0js9WgVHyQLW1QNMUyceJEJk7U0QI0mspIgY87G7zMDiLJzwURKOPy+bLg8TflNRqNRuNGvLxBeQECkl+xp67Qs1VC7vRFCRqN5i7kFg175efnK8CplbqrDYrBYCi191uNRqOp9HgZzB8391Cys7P9lVJO3Wff1XMotWrVsr4FHhAQUGZXHRqNRlOpqBnp1uLy8/NVdna2/9GjR31zc3OdhqK8qw1KwUql06dPWz3sajQazd3O2bNnffLy8gq7us5XSp3Nzc2dGh0d/bmz4+5qgwJmo6KXwGo0Gs1vNGnSZJeI2PsYKoG7eg5Fo9FoNO5DGxSNRqPRuAVtUDQajUbjFrRB0Wg0Go1b0AZFo9FoNG5BGxSNRqPRuIU7Ph6KUioLOFbGw2sAF9wopyLQmisGrbli0JorhqKaw0WkZmkLueMNSnlQSm0vy1psT6I1Vwxac8WgNVcM7tKsh7w0Go1G4xa0QdFoNBqNW9AGpXgWeFpAGdCaKwatuWLQmisGt2jWcygajUajcQu6h6LRaDQat6ANikaj0WjcgjYogFKqu1LqgFLqR6XURAf7/ZRS71n2b1VKNah4lXaaStLcXin1H6VUrlJqoCc0FsUFzWOVUnuVUj8opTYopcI9obOIppI0/0EptUsptUMp9a1SqokndBbRVKzmQvkGKKVEKeXxJa4uXOdkpVSW5TrvUEqN8ITOIppKvM5Kqd9Z7uk9SqkVFa3RgZ6SrvOcQtf4oFLqcqlOICJ39QfwBg4DEYAvsBNoUiTPU8Bblu9JwHu3geYGQHPgHWDgbXKdOwGBlu9P3ibXuWqh732AdZVdsyVfEPANsAWIreyagWRgnid1lkHzvcD3gMmyXauyay6S/2ngH6U5h+6hQCvgRxH5SUR+BdKAvkXy9AWWWr6/D8Qrz8YLLlGziBwVkR8A9waVLjuuaN4oItctm1uAehWssSiuaP650GYVwNOrXFy5nwFeAmYANypSnBNc1VyZcEXz/wP+JiL/BRCR8xWssSilvc6DgJWlOYE2KFAXOFFo+6QlzWEeEckFrgAhFaLOMa5ormyUVnMK8M9bqqhkXNKslBqllDoMzASeqSBtzihRs1IqGvgfEVlbkcKKwdV7Y4BlOPR9pdT/VIw0p7iiORKIVEr9Sym1RSnVvcLUOcbl/0HLcHND4KvSnEAbFE2lQyn1eyAWmOVpLa4gIn8TkUbAn4FJntZTHEopL+A14FlPayklnwANRKQ58CW/jRhUZnwwD3t1xPy0v1ApVd2jilwnCXhfRPJKc5A2KHAKKPy0U8+S5jCPUsoHqAZcrBB1jnFFc2XDJc1KqS7A/wF9RCSngrQ5o7TXOQ3od0sVlUxJmoOAZsAmpdRRoA3wsYcn5ku8ziJysdD98DYQU0HanOHKvXES+FhEborIEeAgZgPjKUpzPydRyuEuQE/KY36K+Alz965goqppkTyjsJ2UT6/smgvlXULlmJR35Tq3wDxpeK+n9ZZC872FvicC2yu75iL5N+H5SXlXrnNYoe/9gS23gebuwFLL9xqYh5tCKrNmS74o4CiWF99LdQ5PNkpl+QA9MT89HAb+z5L2IuanZAB/YBXwI5AJRNwGmltifkK6hrk3tec20LweOAfssHw+vg00/xXYY9G7sbgf78qiuUhejxsUF6/zdMt13mm5zlG3gWaFeXhxL7ALSKrsmi3bU4BXylK+dr2i0Wg0Greg51A0Go1G4xa0QdFoNBqNW9AGRaPRaDRuQRsUjUaj0bgFbVA0Go1G4xa0QdHctVg87Zb06WjxdCtKKaMHtS4ppOn1IunbXTj+aKHje99atZq7FR9PC9BoPMhDhb4HYPZbNA0o7ONqL+b3Hx4CruNZ9gPDgDNlOLY/Zg/UH7pTkEZTGG1QNHctIrKl4Huh3sfhwumFyKoYVcVyzYm2EhGR75VS/3W3II2mMHrIS6MpgaJDXkqpBpbtJKXUYqXUz0qpkxanliilJiilTlsCQs2wOGQsXF4zpdRapdRVy2eVUiq0nBoTLJ54r1kCfTUtT3kaTVnQBkWjKTszMA8/DQA2A0uVUq9ijjsxHHgdmAD8ruAApdQ9wL8wu/P5PebAUU2BT8oRY6c+Zs/Mf8Hs1bYW8J6HY/Zo7kL0kJdGU3a+EpHnAZRSW4GBmKM2RonZ7fc6pVRfzPMXaZZjUoGzQA8xBzlCKfUD5vmRntjO37hKMNBWRA5ZyvMCVgONLeVqNBWC7qFoNGVnQ8EXMUduzAK+FtsYEj9iG8SoC+Yf+3yllI8lHMIRzN5dy+pC/miBMbGw1/LX0xEvNXcZ2qBoNGXncpHtX52k+RfaroE5ENfNIp8IbGNVlFcHRc6r0dxy9JCXRlOxXMLcQ3nbwb4LFaxFo3Er2qBoNBXLBsyT8P8WHTtCc4ehDYpGU7FMwRykba1S6h+YeyV1gQRgiYhs8pw0jaZ86DkUjaYCEZGDmOO4XwcWAP8EpgI5mCfwNZrbFh2xUaO5DVBKLQGaYTZG+SKSX8rjvTG7XvkRSBSRT92tUaPRPRSN5vYhBvOKsNfKcOxhdA9Ic4vRPRSN5jZAKdUA85JjgHMicqKUx98P+Fk2D4nIFfep02jMaIOi0Wg0Gregh7w0Go1G4xa0QdFoNBqNW9AGRaPRaDRuQRsUjUaj0bgFbVA0Go1G4xb+P88kc7utGhr0AAAAAElFTkSuQmCC\n", + "image/png": "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/CTgN7U+v0czG/8vBi4uLnX+upoyZQr33nsvl156KbNmzWL//v2YzY7k+7YTERHRbv9CLC0tPWtvwHHjxvHKK6/wu9/9Dnd391aKTLtYmU0mEBdQlVQohYulfZRQGq3yEpEVIlJY632TW+uFrDkiICCAl156iUOHDrFq1arq/Tk5Odx2220EBQXh5eXFuHHjqD2TwGOPPUa3bt04cwaFDz74ADc3N7Kzsxt95jfffENCQgIeHh6Eh4fz2GOPtcgMBM888wzBwcH19p9LtduxY8eYNWsWnTp1wsvLiylTppCcnFx9PDU1FaUUH330EbfeeisBAQFMnz79rPf905/+RFZWFu+//36T55WWlvLoo48SFRWFu7s7AwcOZMWKmv995syZQ1xcXPXngoICXFxcGDp0aPW+zMxMlFKsWbMGqKmuWbx4MX369MHLy4vp06eTl5dHcnIy48aNw9vbm8TERPbs2VMnnpdffpmEhAT8/PwICwvj6quv5vDhuvNDjR49mlmzZvGf//yH7t274+fnx+WXX86JE00v7vTOO++glGLHjh1ceumleHl5MXjwYHbs2EFRURG33XYbfn5+dO/enYULF9a7/o033qBHjx64u7vTs2dP3njjjXrnLFy4kJ49e+Lp6cm4ceM4cOBAg7HMnz+fuLg43N3diY2N5dVXX20y9o7AZLRIGO0o7aTK65xmG1ZKmc7cWjow7fyNGzcOFxcXNm/eXL1vxowZrFixgldeeYXPPvsMm83G+PHjOXToEAB33HEHR44cYd26dXXu9f777zN9+nQa6zW3cOFCrr32WoYOHcrixYt5+umnWbBgAU888YRDsYoIFoulerNaW76RMTc3l9GjR5OcnMzbb7/NwoULKS4uZtKkSZSWltY599FHH8XX15fPP/+cJ5988qz3jomJ4eabb+Zvf/sbFoulwXNEhGuuuYb//Oc/PPXUUyxZsoRBgwZx5ZVXsmvXLgDGjBnD/v37yckxBilv2rQJV1dXtm/fTnFxMQDr16/H1dWVYcOGVd/7yJEjPPvsszz//PPMnz+fDRs2cPfdd3PjjTdy00038fnnn1NWVsasWbPqxJSWlsaDDz7I4sWLWbBgAeXl5YwaNYrCwsI6523atIn58+czb9483n77bZKSkrjnnnvO+u8CcOutt3LzzTfzxRdfUFlZycyZM5k9ezbR0dEs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\n", "text/plain": [ "
" ] @@ -341,12 +341,12 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "851b92d9553f4065939beb79d7b764f1", + "model_id": "19d3d199c5124fbf9d3b0db122a4d5d1", "version_major": 2, "version_minor": 0 }, "text/plain": [ - "interactive(children=(FloatSlider(value=0.0, description='t', max=0.6755852842809364, step=0.05), Output()), _…" + "interactive(children=(FloatSlider(value=0.0, description='t', max=2432.1070234113713, step=24.321070234113712)…" ] }, "metadata": {}, @@ -355,8 +355,7 @@ ], "source": [ "quick_plot = pybamm.QuickPlot(list_of_solutions)\n", - "import ipywidgets as widgets\n", - "widgets.interact(quick_plot.plot, t=widgets.FloatSlider(min=0,max=quick_plot.max_t,step=0.05,value=0));" + "quick_plot.dynamic_plot();" ] }, { @@ -375,18 +374,18 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 15, "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "7f2b49e7e21646a2addf36e17ad27f6c", + "model_id": "f219eff18b464c9696e64cbbf19c008f", "version_major": 2, "version_minor": 0 }, "text/plain": [ - "interactive(children=(FloatSlider(value=0.0, description='t', max=0.11839464882943145, step=0.05), Output()), …" + "interactive(children=(FloatSlider(value=0.0, description='t', max=426.2207357859532, step=4.262207357859532), …" ] }, "metadata": {}, @@ -402,9 +401,7 @@ "\n", "# Plot\n", "list_of_solutions = list(solutions.values())\n", - "\n", - "quick_plot = pybamm.QuickPlot(list_of_solutions)\n", - "widgets.interact(quick_plot.plot, t=widgets.FloatSlider(min=0,max=quick_plot.max_t,step=0.05,value=0));" + "quick_plot.dynamic_plot();" ] }, { @@ -438,7 +435,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.9" + "version": "3.7.3" } }, "nbformat": 4, diff --git a/examples/notebooks/models/lead-acid.ipynb b/examples/notebooks/models/lead-acid.ipynb index 1922a8af59..28eaa4d996 100644 --- a/examples/notebooks/models/lead-acid.ipynb +++ b/examples/notebooks/models/lead-acid.ipynb @@ -265,9 +265,9 @@ "name": "stdout", "output_type": "stream", "text": [ - "Solved the LOQS model in 0.146 seconds\n", - "Solved the Composite model in 1.127 seconds\n", - "Solved the Full model in 1.102 seconds\n" + "Solved the LOQS model in 0.137 seconds\n", + "Solved the Composite model in 1.160 seconds\n", + "Solved the Full model in 1.117 seconds\n" ] } ], @@ -343,12 +343,12 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "d3bad7c275604722af2babfb0a6cb2ee", + "model_id": "b1feaebac09b4bddace9494faa7d04b1", "version_major": 2, "version_minor": 0 }, "text/plain": [ - "interactive(children=(FloatSlider(value=0.0, description='t', max=17.000000000000004, step=0.05), Output()), _…" + "interactive(children=(FloatSlider(value=0.0, description='t', max=61200.00000000001, step=612.0000000000001), …" ] }, "metadata": {}, @@ -358,8 +358,7 @@ "source": [ "solution_values = [solutions[model] for model in models]\n", "quick_plot = pybamm.QuickPlot(solution_values)\n", - "import ipywidgets as widgets\n", - "widgets.interact(quick_plot.plot, t=widgets.FloatSlider(min=0,max=quick_plot.max_t,step=0.05,value=0));" + "quick_plot.dynamic_plot();" ] }, { @@ -379,12 +378,12 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "762755a1960d42f4a9d14b60b6e3d68e", + "model_id": "0955c956c0be444f805555ee5bca92e7", "version_major": 2, "version_minor": 0 }, "text/plain": [ - "interactive(children=(FloatSlider(value=0.0, description='t', max=0.9898989898989901, step=0.05), Output()), _…" + "interactive(children=(FloatSlider(value=0.0, description='t', max=3563.636363636364, step=35.63636363636364), …" ] }, "metadata": {}, @@ -399,7 +398,7 @@ "# Plot\n", "solution_values = [solutions[model] for model in models]\n", "quick_plot = pybamm.QuickPlot(solution_values)\n", - "widgets.interact(quick_plot.plot, t=widgets.FloatSlider(min=0,max=quick_plot.max_t,step=0.05,value=0));" + "quick_plot.dynamic_plot();" ] }, { diff --git a/examples/notebooks/models/spm1.png b/examples/notebooks/models/spm1.png index 7fb2ae3614..8579d6db1e 100644 Binary files a/examples/notebooks/models/spm1.png and b/examples/notebooks/models/spm1.png differ diff --git a/examples/notebooks/models/spm2.png b/examples/notebooks/models/spm2.png index dfd9e79074..155322eced 100644 Binary files a/examples/notebooks/models/spm2.png and b/examples/notebooks/models/spm2.png differ diff --git a/examples/notebooks/solution-data-and-processed-variables.ipynb b/examples/notebooks/solution-data-and-processed-variables.ipynb index 45a687237d..bf328e862d 100644 --- a/examples/notebooks/solution-data-and-processed-variables.ipynb +++ b/examples/notebooks/solution-data-and-processed-variables.ipynb @@ -22,12 +22,12 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "038a9f3ebf4c4549aa098f7bab633c92", + "model_id": "ac10fba1880a4043b34f9f0a5093dc36", "version_major": 2, "version_minor": 0 }, "text/plain": [ - "interactive(children=(FloatSlider(value=0.0, description='t', max=0.9750000000000001, step=0.05), Output()), _…" + "interactive(children=(FloatSlider(value=0.0, description='t', max=3510.0, step=35.1), Output()), _dom_classes=…" ] }, "metadata": {}, @@ -66,9 +66,7 @@ "solution = solver.solve(model, t_eval)\n", "\n", "quick_plot = pybamm.QuickPlot(solution)\n", - "\n", - "import ipywidgets as widgets\n", - "widgets.interact(quick_plot.plot, t=widgets.FloatSlider(min=0,max=quick_plot.max_t,step=0.05,value=0));" + "quick_plot.dynamic_plot();" ] }, { @@ -86,7 +84,7 @@ { "data": { "text/plain": [ - "dict_keys(['Negative particle surface concentration', 'Electrolyte concentration', 'Positive particle surface concentration', 'Current [A]', 'Negative electrode potential [V]', 'Electrolyte potential [V]', 'Positive electrode potential [V]', 'Terminal voltage [V]'])" + "dict_keys(['Negative particle surface concentration [mol.m-3]', 'Electrolyte concentration [mol.m-3]', 'Positive particle surface concentration [mol.m-3]', 'Current [A]', 'Negative electrode potential [V]', 'Electrolyte potential [V]', 'Positive electrode potential [V]', 'Terminal voltage [V]'])" ] }, "execution_count": 2, @@ -115,7 +113,7 @@ } ], "source": [ - "solution.data['Negative particle surface concentration'].shape" + "solution.data['Negative particle surface concentration [mol.m-3]'].shape" ] }, { @@ -154,7 +152,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "['Active material volume fraction', 'Battery voltage [V]', 'C-rate', 'Cell temperature', 'Cell temperature [K]', 'Current [A]', 'Current collector current density', 'Current collector current density [A.m-2]', 'Discharge capacity [A.h]', 'Electrode current density', 'Electrode tortuosity', 'Electrolyte concentration', 'Electrolyte concentration [Molar]', 'Electrolyte concentration [mol.m-3]', 'Electrolyte current density', 'Electrolyte current density [A.m-2]', 'Electrolyte flux', 'Electrolyte flux [mol.m-2.s-1]', 'Electrolyte potential', 'Electrolyte potential [V]', 'Electrolyte pressure', 'Electrolyte tortuosity', 'Exchange current density', 'Exchange current density [A.m-2]', 'Exchange current density per volume [A.m-3]', 'Gradient of electrolyte potential', 'Gradient of negative electrode potential', 'Gradient of negative electrolyte potential', 'Gradient of positive electrode potential', 'Gradient of positive electrolyte potential', 'Gradient of separator electrolyte potential', 'Heat flux', 'Heat flux [W.m-2]', 'Interfacial current density', 'Interfacial current density [A.m-2]', 'Interfacial current density per volume [A.m-3]', 'Irreversible electrochemical heating', 'Irreversible electrochemical heating [W.m-3]', 'Leading-order active material volume fraction', 'Leading-order current collector current density', 'Leading-order electrode tortuosity', 'Leading-order electrolyte tortuosity', 'Leading-order negative electrode active material volume fraction', 'Leading-order negative electrode porosity', 'Leading-order negative electrode tortuosity', 'Leading-order negative electrolyte tortuosity', 'Leading-order porosity', 'Leading-order positive electrode active material volume fraction', 'Leading-order positive electrode porosity', 'Leading-order positive electrode tortuosity', 'Leading-order positive electrolyte tortuosity', 'Leading-order separator active material volume fraction', 'Leading-order separator porosity', 'Leading-order separator tortuosity', 'Leading-order x-averaged negative electrode active material volume fraction', 'Leading-order x-averaged negative electrode porosity', 'Leading-order x-averaged negative electrode porosity change', 'Leading-order x-averaged negative electrode tortuosity', 'Leading-order x-averaged negative electrolyte tortuosity', 'Leading-order x-averaged positive electrode active material volume fraction', 'Leading-order x-averaged positive electrode porosity', 'Leading-order x-averaged positive electrode porosity change', 'Leading-order x-averaged positive electrode tortuosity', 'Leading-order x-averaged positive electrolyte tortuosity', 'Leading-order x-averaged separator active material volume fraction', 'Leading-order x-averaged separator porosity', 'Leading-order x-averaged separator porosity change', 'Leading-order x-averaged separator tortuosity', 'Local voltage', 'Local voltage [V]', 'Measured battery open circuit voltage [V]', 'Measured open circuit voltage', 'Measured open circuit voltage [V]', 'Negative current collector potential', 'Negative current collector potential [V]', 'Negative current collector temperature', 'Negative current collector temperature [K]', 'Negative electrode active material volume fraction', 'Negative electrode active volume fraction', 'Negative electrode average extent of lithiation', 'Negative electrode current density', 'Negative electrode current density [A.m-2]', 'Negative electrode entropic change', 'Negative electrode exchange current density', 'Negative electrode exchange current density [A.m-2]', 'Negative electrode exchange current density per volume [A.m-3]', 'Negative electrode interfacial current density', 'Negative electrode interfacial current density [A.m-2]', 'Negative electrode interfacial current density per volume [A.m-3]', 'Negative electrode ohmic losses', 'Negative electrode ohmic losses [V]', 'Negative electrode open circuit potential', 'Negative electrode open circuit potential [V]', 'Negative electrode porosity', 'Negative electrode porosity change', 'Negative electrode potential', 'Negative electrode potential [V]', 'Negative electrode reaction overpotential', 'Negative electrode reaction overpotential [V]', 'Negative electrode surface potential difference', 'Negative electrode surface potential difference [V]', 'Negative electrode temperature', 'Negative electrode temperature [K]', 'Negative electrode tortuosity', 'Negative electrode volume-averaged concentration', 'Negative electrode volume-averaged concentration [mol.m-3]', 'Negative electrolyte concentration', 'Negative electrolyte concentration [Molar]', 'Negative electrolyte concentration [mol.m-3]', 'Negative electrolyte current density', 'Negative electrolyte current density [A.m-2]', 'Negative electrolyte potential', 'Negative electrolyte potential [V]', 'Negative electrolyte tortuosity', 'Negative particle concentration', 'Negative particle concentration [mol.m-3]', 'Negative particle flux', 'Negative particle surface concentration', 'Negative particle surface concentration [mol.m-3]', 'Ohmic heating', 'Ohmic heating [W.m-3]', 'Porosity', 'Porosity change', 'Positive current collector potential', 'Positive current collector potential [V]', 'Positive current collector temperature', 'Positive current collector temperature [K]', 'Positive electrode active material volume fraction', 'Positive electrode active volume fraction', 'Positive electrode average extent of lithiation', 'Positive electrode current density', 'Positive electrode current density [A.m-2]', 'Positive electrode entropic change', 'Positive electrode exchange current density', 'Positive electrode exchange current density [A.m-2]', 'Positive electrode exchange current density per volume [A.m-3]', 'Positive electrode interfacial current density', 'Positive electrode interfacial current density [A.m-2]', 'Positive electrode interfacial current density per volume [A.m-3]', 'Positive electrode ohmic losses', 'Positive electrode ohmic losses [V]', 'Positive electrode open circuit potential', 'Positive electrode open circuit potential [V]', 'Positive electrode porosity', 'Positive electrode porosity change', 'Positive electrode potential', 'Positive electrode potential [V]', 'Positive electrode reaction overpotential', 'Positive electrode reaction overpotential [V]', 'Positive electrode surface potential difference', 'Positive electrode surface potential difference [V]', 'Positive electrode temperature', 'Positive electrode temperature [K]', 'Positive electrode tortuosity', 'Positive electrode volume-averaged concentration', 'Positive electrode volume-averaged concentration [mol.m-3]', 'Positive electrolyte concentration', 'Positive electrolyte concentration [Molar]', 'Positive electrolyte concentration [mol.m-3]', 'Positive electrolyte current density', 'Positive electrolyte current density [A.m-2]', 'Positive electrolyte potential', 'Positive electrolyte potential [V]', 'Positive electrolyte tortuosity', 'Positive particle concentration', 'Positive particle concentration [mol.m-3]', 'Positive particle flux', 'Positive particle surface concentration', 'Positive particle surface concentration [mol.m-3]', 'Reversible heating', 'Reversible heating [W.m-3]', 'Separator active material volume fraction', 'Separator electrolyte concentration', 'Separator electrolyte concentration [Molar]', 'Separator electrolyte concentration [mol.m-3]', 'Separator electrolyte potential', 'Separator electrolyte potential [V]', 'Separator porosity', 'Separator porosity change', 'Separator temperature', 'Separator temperature [K]', 'Separator tortuosity', 'Terminal power [W]', 'Terminal voltage', 'Terminal voltage [V]', 'Time', 'Time [h]', 'Time [min]', 'Time [s]', 'Total current density', 'Total current density [A.m-2]', 'Total heating', 'Total heating [W.m-3]', 'Volume-averaged cell temperature', 'Volume-averaged cell temperature [K]', 'Volume-averaged total heating', 'Volume-averaged total heating [W.m-3]', 'Volume-averaged velocity', 'Volume-averaged velocity [m.s-1]', 'X-averaged battery concentration overpotential [V]', 'X-averaged battery electrolyte ohmic losses [V]', 'X-averaged battery open circuit voltage [V]', 'X-averaged battery reaction overpotential [V]', 'X-averaged battery solid phase ohmic losses [V]', 'X-averaged cell temperature', 'X-averaged cell temperature [K]', 'X-averaged concentration overpotential', 'X-averaged concentration overpotential [V]', 'X-averaged electrolyte concentration', 'X-averaged electrolyte concentration [Molar]', 'X-averaged electrolyte concentration [mol.m-3]', 'X-averaged electrolyte ohmic losses', 'X-averaged electrolyte ohmic losses [V]', 'X-averaged electrolyte overpotential', 'X-averaged electrolyte overpotential [V]', 'X-averaged electrolyte potential', 'X-averaged electrolyte potential [V]', 'X-averaged negative electrode active material volume fraction', 'X-averaged negative electrode entropic change', 'X-averaged negative electrode exchange current density', 'X-averaged negative electrode exchange current density [A.m-2]', 'X-averaged negative electrode exchange current density per volume [A.m-3]', 'X-averaged negative electrode interfacial current density', 'X-averaged negative electrode interfacial current density [A.m-2]', 'X-averaged negative electrode interfacial current density per volume [A.m-3]', 'X-averaged negative electrode ohmic losses', 'X-averaged negative electrode ohmic losses [V]', 'X-averaged negative electrode open circuit potential', 'X-averaged negative electrode open circuit potential [V]', 'X-averaged negative electrode porosity', 'X-averaged negative electrode porosity change', 'X-averaged negative electrode potential', 'X-averaged negative electrode potential [V]', 'X-averaged negative electrode reaction overpotential', 'X-averaged negative electrode reaction overpotential [V]', 'X-averaged negative electrode surface potential difference', 'X-averaged negative electrode surface potential difference [V]', 'X-averaged negative electrode temperature', 'X-averaged negative electrode temperature [K]', 'X-averaged negative electrode tortuosity', 'X-averaged negative electrode total interfacial current density', 'X-averaged negative electrode total interfacial current density [A.m-2]', 'X-averaged negative electrode total interfacial current density per volume [A.m-3]', 'X-averaged negative electrolyte concentration', 'X-averaged negative electrolyte concentration [mol.m-3]', 'X-averaged negative electrolyte potential', 'X-averaged negative electrolyte potential [V]', 'X-averaged negative electrolyte tortuosity', 'X-averaged negative particle concentration', 'X-averaged negative particle concentration [mol.m-3]', 'X-averaged negative particle flux', 'X-averaged negative particle surface concentration', 'X-averaged negative particle surface concentration [mol.m-3]', 'X-averaged open circuit voltage', 'X-averaged open circuit voltage [V]', 'X-averaged porosity change', 'X-averaged positive electrode active material volume fraction', 'X-averaged positive electrode entropic change', 'X-averaged positive electrode exchange current density', 'X-averaged positive electrode exchange current density [A.m-2]', 'X-averaged positive electrode exchange current density per volume [A.m-3]', 'X-averaged positive electrode interfacial current density', 'X-averaged positive electrode interfacial current density [A.m-2]', 'X-averaged positive electrode interfacial current density per volume [A.m-3]', 'X-averaged positive electrode ohmic losses', 'X-averaged positive electrode ohmic losses [V]', 'X-averaged positive electrode open circuit potential', 'X-averaged positive electrode open circuit potential [V]', 'X-averaged positive electrode porosity', 'X-averaged positive electrode porosity change', 'X-averaged positive electrode potential', 'X-averaged positive electrode potential [V]', 'X-averaged positive electrode reaction overpotential', 'X-averaged positive electrode reaction overpotential [V]', 'X-averaged positive electrode surface potential difference', 'X-averaged positive electrode surface potential difference [V]', 'X-averaged positive electrode temperature', 'X-averaged positive electrode temperature [K]', 'X-averaged positive electrode tortuosity', 'X-averaged positive electrode total interfacial current density', 'X-averaged positive electrode total interfacial current density [A.m-2]', 'X-averaged positive electrode total interfacial current density per volume [A.m-3]', 'X-averaged positive electrolyte concentration', 'X-averaged positive electrolyte concentration [mol.m-3]', 'X-averaged positive electrolyte potential', 'X-averaged positive electrolyte potential [V]', 'X-averaged positive electrolyte tortuosity', 'X-averaged positive particle concentration', 'X-averaged positive particle concentration [mol.m-3]', 'X-averaged positive particle flux', 'X-averaged positive particle surface concentration', 'X-averaged positive particle surface concentration [mol.m-3]', 'X-averaged reaction overpotential', 'X-averaged reaction overpotential [V]', 'X-averaged separator active material volume fraction', 'X-averaged separator electrolyte concentration', 'X-averaged separator electrolyte concentration [mol.m-3]', 'X-averaged separator electrolyte potential', 'X-averaged separator electrolyte potential [V]', 'X-averaged separator porosity', 'X-averaged separator porosity change', 'X-averaged separator temperature', 'X-averaged separator temperature [K]', 'X-averaged separator tortuosity', 'X-averaged solid phase ohmic losses', 'X-averaged solid phase ohmic losses [V]', 'X-averaged total heating', 'X-averaged total heating [W.m-3]', 'r_n', 'r_n [m]', 'r_p', 'r_p [m]', 'x', 'x [m]', 'x_n', 'x_n [m]', 'x_p', 'x_p [m]', 'x_s', 'x_s [m]']\n" + "['Active material volume fraction', 'Ambient temperature', 'Ambient temperature [K]', 'Battery voltage [V]', 'C-rate', 'Cell temperature', 'Cell temperature [K]', 'Current [A]', 'Current collector current density', 'Current collector current density [A.m-2]', 'Discharge capacity [A.h]', 'Electrode current density', 'Electrode tortuosity', 'Electrolyte concentration', 'Electrolyte concentration [Molar]', 'Electrolyte concentration [mol.m-3]', 'Electrolyte current density', 'Electrolyte current density [A.m-2]', 'Electrolyte flux', 'Electrolyte flux [mol.m-2.s-1]', 'Electrolyte potential', 'Electrolyte potential [V]', 'Electrolyte pressure', 'Electrolyte tortuosity', 'Exchange current density', 'Exchange current density [A.m-2]', 'Exchange current density per volume [A.m-3]', 'Gradient of electrolyte potential', 'Gradient of negative electrode potential', 'Gradient of negative electrolyte potential', 'Gradient of positive electrode potential', 'Gradient of positive electrolyte potential', 'Gradient of separator electrolyte potential', 'Heat flux', 'Heat flux [W.m-2]', 'Interfacial current density', 'Interfacial current density [A.m-2]', 'Interfacial current density per volume [A.m-3]', 'Irreversible electrochemical heating', 'Irreversible electrochemical heating [W.m-3]', 'Leading-order active material volume fraction', 'Leading-order current collector current density', 'Leading-order electrode tortuosity', 'Leading-order electrolyte tortuosity', 'Leading-order negative electrode active material volume fraction', 'Leading-order negative electrode porosity', 'Leading-order negative electrode tortuosity', 'Leading-order negative electrolyte tortuosity', 'Leading-order porosity', 'Leading-order positive electrode active material volume fraction', 'Leading-order positive electrode porosity', 'Leading-order positive electrode tortuosity', 'Leading-order positive electrolyte tortuosity', 'Leading-order separator active material volume fraction', 'Leading-order separator porosity', 'Leading-order separator tortuosity', 'Leading-order x-averaged negative electrode active material volume fraction', 'Leading-order x-averaged negative electrode porosity', 'Leading-order x-averaged negative electrode porosity change', 'Leading-order x-averaged negative electrode tortuosity', 'Leading-order x-averaged negative electrolyte tortuosity', 'Leading-order x-averaged positive electrode active material volume fraction', 'Leading-order x-averaged positive electrode porosity', 'Leading-order x-averaged positive electrode porosity change', 'Leading-order x-averaged positive electrode tortuosity', 'Leading-order x-averaged positive electrolyte tortuosity', 'Leading-order x-averaged separator active material volume fraction', 'Leading-order x-averaged separator porosity', 'Leading-order x-averaged separator porosity change', 'Leading-order x-averaged separator tortuosity', 'Local voltage', 'Local voltage [V]', 'Measured battery open circuit voltage [V]', 'Measured open circuit voltage', 'Measured open circuit voltage [V]', 'Negative current collector potential', 'Negative current collector potential [V]', 'Negative current collector temperature', 'Negative current collector temperature [K]', 'Negative electrode active material volume fraction', 'Negative electrode active volume fraction', 'Negative electrode average extent of lithiation', 'Negative electrode current density', 'Negative electrode current density [A.m-2]', 'Negative electrode entropic change', 'Negative electrode exchange current density', 'Negative electrode exchange current density [A.m-2]', 'Negative electrode exchange current density per volume [A.m-3]', 'Negative electrode interfacial current density', 'Negative electrode interfacial current density [A.m-2]', 'Negative electrode interfacial current density per volume [A.m-3]', 'Negative electrode ohmic losses', 'Negative electrode ohmic losses [V]', 'Negative electrode open circuit potential', 'Negative electrode open circuit potential [V]', 'Negative electrode porosity', 'Negative electrode porosity change', 'Negative electrode potential', 'Negative electrode potential [V]', 'Negative electrode reaction overpotential', 'Negative electrode reaction overpotential [V]', 'Negative electrode surface potential difference', 'Negative electrode surface potential difference [V]', 'Negative electrode temperature', 'Negative electrode temperature [K]', 'Negative electrode tortuosity', 'Negative electrode volume-averaged concentration', 'Negative electrode volume-averaged concentration [mol.m-3]', 'Negative electrolyte concentration', 'Negative electrolyte concentration [Molar]', 'Negative electrolyte concentration [mol.m-3]', 'Negative electrolyte current density', 'Negative electrolyte current density [A.m-2]', 'Negative electrolyte potential', 'Negative electrolyte potential [V]', 'Negative electrolyte tortuosity', 'Negative particle concentration', 'Negative particle concentration [mol.m-3]', 'Negative particle flux', 'Negative particle surface concentration', 'Negative particle surface concentration [mol.m-3]', 'Ohmic heating', 'Ohmic heating [W.m-3]', 'Porosity', 'Porosity change', 'Positive current collector potential', 'Positive current collector potential [V]', 'Positive current collector temperature', 'Positive current collector temperature [K]', 'Positive electrode active material volume fraction', 'Positive electrode active volume fraction', 'Positive electrode average extent of lithiation', 'Positive electrode current density', 'Positive electrode current density [A.m-2]', 'Positive electrode entropic change', 'Positive electrode exchange current density', 'Positive electrode exchange current density [A.m-2]', 'Positive electrode exchange current density per volume [A.m-3]', 'Positive electrode interfacial current density', 'Positive electrode interfacial current density [A.m-2]', 'Positive electrode interfacial current density per volume [A.m-3]', 'Positive electrode ohmic losses', 'Positive electrode ohmic losses [V]', 'Positive electrode open circuit potential', 'Positive electrode open circuit potential [V]', 'Positive electrode porosity', 'Positive electrode porosity change', 'Positive electrode potential', 'Positive electrode potential [V]', 'Positive electrode reaction overpotential', 'Positive electrode reaction overpotential [V]', 'Positive electrode surface potential difference', 'Positive electrode surface potential difference [V]', 'Positive electrode temperature', 'Positive electrode temperature [K]', 'Positive electrode tortuosity', 'Positive electrode volume-averaged concentration', 'Positive electrode volume-averaged concentration [mol.m-3]', 'Positive electrolyte concentration', 'Positive electrolyte concentration [Molar]', 'Positive electrolyte concentration [mol.m-3]', 'Positive electrolyte current density', 'Positive electrolyte current density [A.m-2]', 'Positive electrolyte potential', 'Positive electrolyte potential [V]', 'Positive electrolyte tortuosity', 'Positive particle concentration', 'Positive particle concentration [mol.m-3]', 'Positive particle flux', 'Positive particle surface concentration', 'Positive particle surface concentration [mol.m-3]', 'Reversible heating', 'Reversible heating [W.m-3]', 'Separator active material volume fraction', 'Separator electrolyte concentration', 'Separator electrolyte concentration [Molar]', 'Separator electrolyte concentration [mol.m-3]', 'Separator electrolyte potential', 'Separator electrolyte potential [V]', 'Separator porosity', 'Separator porosity change', 'Separator temperature', 'Separator temperature [K]', 'Separator tortuosity', 'Terminal power [W]', 'Terminal voltage', 'Terminal voltage [V]', 'Time', 'Time [h]', 'Time [min]', 'Time [s]', 'Total current density', 'Total current density [A.m-2]', 'Total heating', 'Total heating [W.m-3]', 'Volume-averaged cell temperature', 'Volume-averaged cell temperature [K]', 'Volume-averaged total heating', 'Volume-averaged total heating [W.m-3]', 'Volume-averaged velocity', 'Volume-averaged velocity [m.s-1]', 'X-averaged battery concentration overpotential [V]', 'X-averaged battery electrolyte ohmic losses [V]', 'X-averaged battery open circuit voltage [V]', 'X-averaged battery reaction overpotential [V]', 'X-averaged battery solid phase ohmic losses [V]', 'X-averaged cell temperature', 'X-averaged cell temperature [K]', 'X-averaged concentration overpotential', 'X-averaged concentration overpotential [V]', 'X-averaged electrolyte concentration', 'X-averaged electrolyte concentration [Molar]', 'X-averaged electrolyte concentration [mol.m-3]', 'X-averaged electrolyte ohmic losses', 'X-averaged electrolyte ohmic losses [V]', 'X-averaged electrolyte overpotential', 'X-averaged electrolyte overpotential [V]', 'X-averaged electrolyte potential', 'X-averaged electrolyte potential [V]', 'X-averaged negative electrode active material volume fraction', 'X-averaged negative electrode entropic change', 'X-averaged negative electrode exchange current density', 'X-averaged negative electrode exchange current density [A.m-2]', 'X-averaged negative electrode exchange current density per volume [A.m-3]', 'X-averaged negative electrode interfacial current density', 'X-averaged negative electrode interfacial current density [A.m-2]', 'X-averaged negative electrode interfacial current density per volume [A.m-3]', 'X-averaged negative electrode ohmic losses', 'X-averaged negative electrode ohmic losses [V]', 'X-averaged negative electrode open circuit potential', 'X-averaged negative electrode open circuit potential [V]', 'X-averaged negative electrode porosity', 'X-averaged negative electrode porosity change', 'X-averaged negative electrode potential', 'X-averaged negative electrode potential [V]', 'X-averaged negative electrode reaction overpotential', 'X-averaged negative electrode reaction overpotential [V]', 'X-averaged negative electrode surface potential difference', 'X-averaged negative electrode surface potential difference [V]', 'X-averaged negative electrode temperature', 'X-averaged negative electrode temperature [K]', 'X-averaged negative electrode tortuosity', 'X-averaged negative electrode total interfacial current density', 'X-averaged negative electrode total interfacial current density [A.m-2]', 'X-averaged negative electrode total interfacial current density per volume [A.m-3]', 'X-averaged negative electrolyte concentration', 'X-averaged negative electrolyte concentration [mol.m-3]', 'X-averaged negative electrolyte potential', 'X-averaged negative electrolyte potential [V]', 'X-averaged negative electrolyte tortuosity', 'X-averaged negative particle concentration', 'X-averaged negative particle concentration [mol.m-3]', 'X-averaged negative particle flux', 'X-averaged negative particle surface concentration', 'X-averaged negative particle surface concentration [mol.m-3]', 'X-averaged open circuit voltage', 'X-averaged open circuit voltage [V]', 'X-averaged porosity change', 'X-averaged positive electrode active material volume fraction', 'X-averaged positive electrode entropic change', 'X-averaged positive electrode exchange current density', 'X-averaged positive electrode exchange current density [A.m-2]', 'X-averaged positive electrode exchange current density per volume [A.m-3]', 'X-averaged positive electrode interfacial current density', 'X-averaged positive electrode interfacial current density [A.m-2]', 'X-averaged positive electrode interfacial current density per volume [A.m-3]', 'X-averaged positive electrode ohmic losses', 'X-averaged positive electrode ohmic losses [V]', 'X-averaged positive electrode open circuit potential', 'X-averaged positive electrode open circuit potential [V]', 'X-averaged positive electrode porosity', 'X-averaged positive electrode porosity change', 'X-averaged positive electrode potential', 'X-averaged positive electrode potential [V]', 'X-averaged positive electrode reaction overpotential', 'X-averaged positive electrode reaction overpotential [V]', 'X-averaged positive electrode surface potential difference', 'X-averaged positive electrode surface potential difference [V]', 'X-averaged positive electrode temperature', 'X-averaged positive electrode temperature [K]', 'X-averaged positive electrode tortuosity', 'X-averaged positive electrode total interfacial current density', 'X-averaged positive electrode total interfacial current density [A.m-2]', 'X-averaged positive electrode total interfacial current density per volume [A.m-3]', 'X-averaged positive electrolyte concentration', 'X-averaged positive electrolyte concentration [mol.m-3]', 'X-averaged positive electrolyte potential', 'X-averaged positive electrolyte potential [V]', 'X-averaged positive electrolyte tortuosity', 'X-averaged positive particle concentration', 'X-averaged positive particle concentration [mol.m-3]', 'X-averaged positive particle flux', 'X-averaged positive particle surface concentration', 'X-averaged positive particle surface concentration [mol.m-3]', 'X-averaged reaction overpotential', 'X-averaged reaction overpotential [V]', 'X-averaged separator active material volume fraction', 'X-averaged separator electrolyte concentration', 'X-averaged separator electrolyte concentration [mol.m-3]', 'X-averaged separator electrolyte potential', 'X-averaged separator electrolyte potential [V]', 'X-averaged separator porosity', 'X-averaged separator porosity change', 'X-averaged separator temperature', 'X-averaged separator temperature [K]', 'X-averaged separator tortuosity', 'X-averaged solid phase ohmic losses', 'X-averaged solid phase ohmic losses [V]', 'X-averaged total heating', 'X-averaged total heating [W.m-3]', 'r_n', 'r_n [m]', 'r_p', 'r_p [m]', 'x', 'x [m]', 'x_n', 'x_n [m]', 'x_p', 'x_p [m]', 'x_s', 'x_s [m]']\n" ] } ], @@ -172,7 +170,7 @@ { "data": { "text/plain": [ - "" + "" ] }, "execution_count": 6, @@ -199,7 +197,7 @@ { "data": { "text/plain": [ - "dict_keys(['Negative particle surface concentration', 'Electrolyte concentration', 'Positive particle surface concentration', 'Current [A]', 'Negative electrode potential [V]', 'Electrolyte potential [V]', 'Positive electrode potential [V]', 'Terminal voltage [V]', 'Time [h]'])" + "dict_keys(['Negative particle surface concentration [mol.m-3]', 'Electrolyte concentration [mol.m-3]', 'Positive particle surface concentration [mol.m-3]', 'Current [A]', 'Negative electrode potential [V]', 'Electrolyte potential [V]', 'Positive electrode potential [V]', 'Terminal voltage [V]', 'Time [h]'])" ] }, "execution_count": 7, @@ -461,7 +459,7 @@ { "data": { "text/plain": [ - "" + "" ] }, "execution_count": 15, @@ -491,13 +489,6 @@ ")\n", "plt.legend()" ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { diff --git a/examples/notebooks/using-model-options_thermal-example.ipynb b/examples/notebooks/using-model-options_thermal-example.ipynb index 9f9ff48dda..eb70b9ffe1 100644 --- a/examples/notebooks/using-model-options_thermal-example.ipynb +++ b/examples/notebooks/using-model-options_thermal-example.ipynb @@ -140,12 +140,12 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "321d32d08b3f417caa1062450f00732c", + "model_id": "c3d04dd597c24caf83c370bd01fe6131", "version_major": 2, "version_minor": 0 }, "text/plain": [ - "interactive(children=(FloatSlider(value=0.0, description='t', max=1.0, step=0.05), Output()), _dom_classes=('w…" + "interactive(children=(FloatSlider(value=0.0, description='t', max=1.0, step=0.01), Output()), _dom_classes=('w…" ] }, "metadata": {}, @@ -160,9 +160,7 @@ " \"Cell temperature [K]\",\n", "]\n", "quick_plot = pybamm.QuickPlot(solution, output_variables)\n", - "\n", - "import ipywidgets as widgets\n", - "widgets.interact(quick_plot.plot, t=widgets.FloatSlider(min=0,max=1,step=0.05,value=0));" + "quick_plot.dynamic_plot();" ] }, { @@ -204,9 +202,9 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.8" + "version": "3.7.3" } }, "nbformat": 4, "nbformat_minor": 2 -} \ No newline at end of file +} diff --git a/examples/scripts/DFN.py b/examples/scripts/DFN.py index 9155d9eaa7..fb9c4563b9 100644 --- a/examples/scripts/DFN.py +++ b/examples/scripts/DFN.py @@ -5,7 +5,7 @@ import pybamm import numpy as np -pybamm.set_logging_level("DEBUG") +pybamm.set_logging_level("INFO") # load model @@ -36,5 +36,19 @@ solution = solver.solve(model, t_eval) # plot -plot = pybamm.QuickPlot(solution) +plot = pybamm.QuickPlot( + solution, + [ + "Negative particle concentration [mol.m-3]", + "Electrolyte concentration [mol.m-3]", + "Positive particle concentration [mol.m-3]", + "Current [A]", + "Negative electrode potential [V]", + "Electrolyte potential [V]", + "Positive electrode potential [V]", + "Terminal voltage [V]", + ], + time_unit="seconds", + spatial_unit="um", +) plot.dynamic_plot() diff --git a/examples/scripts/SPMe.py b/examples/scripts/SPMe.py index 364c7c0508..75921b13d2 100644 --- a/examples/scripts/SPMe.py +++ b/examples/scripts/SPMe.py @@ -5,7 +5,7 @@ import pybamm import numpy as np -pybamm.set_logging_level("DEBUG") +pybamm.set_logging_level("INFO") # load model model = pybamm.lithium_ion.SPMe() @@ -31,5 +31,19 @@ solution = model.default_solver.solve(model, t_eval) # plot -plot = pybamm.QuickPlot(solution) +plot = pybamm.QuickPlot( + solution, + [ + "Negative particle concentration [mol.m-3]", + "Electrolyte concentration [mol.m-3]", + "Positive particle concentration [mol.m-3]", + "Current [A]", + "Negative electrode potential [V]", + "Electrolyte potential [V]", + "Positive electrode potential [V]", + "Terminal voltage [V]", + ], + time_unit="seconds", + spatial_unit="um", +) plot.dynamic_plot() diff --git a/examples/scripts/compare_lead_acid.py b/examples/scripts/compare_lead_acid.py index fe9ea46768..4c2b1bad67 100644 --- a/examples/scripts/compare_lead_acid.py +++ b/examples/scripts/compare_lead_acid.py @@ -55,5 +55,5 @@ "Electrolyte potential [V]", "Terminal voltage [V]", ] -plot = pybamm.QuickPlot(solutions, output_variables) +plot = pybamm.QuickPlot(solutions, output_variables, linestyles=[":", "--", "-"]) plot.dynamic_plot() diff --git a/examples/scripts/compare_lithium_ion.py b/examples/scripts/compare_lithium_ion.py index 12aa958592..342509f95d 100644 --- a/examples/scripts/compare_lithium_ion.py +++ b/examples/scripts/compare_lithium_ion.py @@ -51,5 +51,5 @@ solutions[i] = model.default_solver.solve(model, t_eval) # plot -plot = pybamm.QuickPlot(solutions) +plot = pybamm.QuickPlot(solutions, linestyles=[":", "--", "-"]) plot.dynamic_plot() diff --git a/examples/scripts/compare_lithium_ion_3D.py b/examples/scripts/compare_lithium_ion_3D.py index a28a5ebce3..b09d0d6a38 100644 --- a/examples/scripts/compare_lithium_ion_3D.py +++ b/examples/scripts/compare_lithium_ion_3D.py @@ -20,9 +20,9 @@ pybamm.lithium_ion.SPM( {"current collector": "potential pair", "dimensionality": 2}, name="2+1D SPM" ), - pybamm.lithium_ion.SPMe( - {"current collector": "potential pair", "dimensionality": 2}, name="2+1D SPMe" - ), + # pybamm.lithium_ion.SPMe( + # {"current collector": "potential pair", "dimensionality": 2}, name="2+1D SPMe" + # ), ] # load parameter values and process models @@ -56,7 +56,6 @@ solutions[i] = solution # plot -# TO DO: plotting 3D variables -output_variables = ["Terminal voltage [V]"] +output_variables = ["Terminal voltage [V]", "Negative current collector potential [V]"] plot = pybamm.QuickPlot(solutions, output_variables) plot.dynamic_plot() diff --git a/examples/scripts/drive_cycle.py b/examples/scripts/drive_cycle.py index 1e9c708dad..f04d24a19a 100644 --- a/examples/scripts/drive_cycle.py +++ b/examples/scripts/drive_cycle.py @@ -3,15 +3,29 @@ # import pybamm +pybamm.set_logging_level("INFO") + # load model and update parameters so the input current is the US06 drive cycle -model = pybamm.lithium_ion.DFN() +model = pybamm.lithium_ion.SPMe({"thermal": "x-lumped"}) param = model.default_parameter_values param["Current function [A]"] = "[current data]US06" # create and run simulation using the CasadiSolver in "fast" mode, remembering to # pass in the updated parameters sim = pybamm.Simulation( - model, parameter_values=param, solver=pybamm.CasadiSolver(mode="fast") + model, parameter_values=param, solver=pybamm.CasadiSolver(mode="fast"), ) sim.solve() -sim.plot() +sim.plot( + [ + "Negative particle surface concentration [mol.m-3]", + "Electrolyte concentration [mol.m-3]", + "Positive particle surface concentration [mol.m-3]", + "Current [A]", + "Negative electrode potential [V]", + "Electrolyte potential [V]", + "Positive electrode potential [V]", + "Terminal voltage [V]", + "X-averaged cell temperature", + ] +) diff --git a/pybamm/__init__.py b/pybamm/__init__.py index 6fd55219f2..bcbd01d94b 100644 --- a/pybamm/__init__.py +++ b/pybamm/__init__.py @@ -219,7 +219,7 @@ def version(formatted=False): from .processed_variable import ProcessedVariable from .quick_plot import QuickPlot, ax_min, ax_max -from .simulation import Simulation, load_sim +from .simulation import Simulation, load_sim, is_notebook # # Remove any imported modules, so we don't expose them as part of pybamm diff --git a/pybamm/meshes/meshes.py b/pybamm/meshes/meshes.py index f6da5633c7..7cfa06ad2e 100644 --- a/pybamm/meshes/meshes.py +++ b/pybamm/meshes/meshes.py @@ -171,6 +171,11 @@ def combine_submeshes(self, *submeshnames): ) coord_sys = self[submeshnames[0]][i].coord_sys submeshes[i] = pybamm.SubMesh1D(combined_submesh_edges, coord_sys) + # add in internal boundaries + submeshes[i].internal_boundaries = [ + self[submeshname][i].edges[0] for submeshname in submeshnames[1:] + ] + return submeshes def add_ghost_meshes(self): diff --git a/pybamm/meshes/one_dimensional_submeshes.py b/pybamm/meshes/one_dimensional_submeshes.py index 69ea5ace75..48aace8740 100644 --- a/pybamm/meshes/one_dimensional_submeshes.py +++ b/pybamm/meshes/one_dimensional_submeshes.py @@ -33,6 +33,7 @@ def __init__(self, edges, coord_sys, tabs=None): self.d_nodes = np.diff(self.nodes) self.npts = self.nodes.size self.coord_sys = coord_sys + self.internal_boundaries = [] # Add tab locations in terms of "left" and "right" if tabs: diff --git a/pybamm/models/full_battery_models/base_battery_model.py b/pybamm/models/full_battery_models/base_battery_model.py index 80eda34696..6e3bcaa4f7 100644 --- a/pybamm/models/full_battery_models/base_battery_model.py +++ b/pybamm/models/full_battery_models/base_battery_model.py @@ -151,14 +151,18 @@ def options(self, extra_options): "thermal current collector": False, "external submodels": [], } - options = default_options + options = pybamm.FuzzyDict(default_options) # any extra options overwrite the default options if extra_options is not None: for name, opt in extra_options.items(): if name in default_options: options[name] = opt else: - raise pybamm.OptionError("option {} not recognised".format(name)) + raise pybamm.OptionError( + "Option '{}' not recognised. Best matches are {}".format( + name, options.get_best_matches(name) + ) + ) # Some standard checks to make sure options are compatible if not ( diff --git a/pybamm/parameters/parameter_values.py b/pybamm/parameters/parameter_values.py index 7baccaae94..81a6c6861b 100644 --- a/pybamm/parameters/parameter_values.py +++ b/pybamm/parameters/parameter_values.py @@ -122,7 +122,9 @@ def update_from_chemistry(self, chemistry): """ base_chemistry = chemistry["chemistry"] # Create path to file - path = os.path.join("input", "parameters", base_chemistry) + path = os.path.join( + pybamm.root_dir(), "pybamm", "input", "parameters", base_chemistry + ) # Load each component name for component_group in [ "cell", @@ -237,7 +239,9 @@ def update(self, values, check_conflict=False, check_already_exists=True, path=" # Data is flagged with the string "[data]" or "[current data]" elif value.startswith("[current data]") or value.startswith("[data]"): if value.startswith("[current data]"): - data_path = os.path.join("input", "drive_cycles") + data_path = os.path.join( + pybamm.root_dir(), "pybamm", "input", "drive_cycles" + ) filename = os.path.join(data_path, value[14:] + ".csv") function_name = value[14:] else: diff --git a/pybamm/processed_variable.py b/pybamm/processed_variable.py index 19a3df714a..4bfb5c267e 100644 --- a/pybamm/processed_variable.py +++ b/pybamm/processed_variable.py @@ -21,8 +21,6 @@ class ProcessedVariable(object): When evaluated, returns an array of size (m,n) solution : :class:`pybamm.Solution` The solution object to be used to create the processed variables - interp_kind : str - The method to use for interpolation known_evals : dict Dictionary of known evaluations, to be used to speed up finding the solution """ @@ -59,34 +57,47 @@ def __init__(self, base_variable, solution, known_evals=None): ): if len(solution.t) == 1: # space only (steady solution) - self.initialise_2Dspace_scikit_fem() + self.initialise_2D_fixed_t_scikit_fem() else: - self.initialise_3D_scikit_fem() + self.initialise_2D_scikit_fem() # check variable shape else: if len(solution.t) == 1: raise pybamm.SolverError( - """ - Solution time vector must have length > 1. Check whether simulation - terminated too early. - """ + "Solution time vector must have length > 1. Check whether " + "simulation terminated too early." ) elif ( isinstance(self.base_eval, numbers.Number) or len(self.base_eval.shape) == 0 or self.base_eval.shape[0] == 1 ): - self.initialise_1D() + self.initialise_0D() else: n = self.mesh[0].npts base_shape = self.base_eval.shape[0] + # Try some shapes that could make the variable a 1D variable if base_shape in [n, n + 1]: - self.initialise_2D() + self.initialise_1D() else: - self.initialise_3D() - - def initialise_1D(self): + # Try some shapes that could make the variable a 2D variable + first_dim_nodes = self.mesh[0].nodes + first_dim_edges = self.mesh[0].edges + second_dim_pts = self.base_variable.secondary_mesh[0].nodes + if self.base_eval.size // len(second_dim_pts) in [ + len(first_dim_nodes), + len(first_dim_edges), + ]: + self.initialise_2D() + else: + # Raise error for 3D variable + raise NotImplementedError( + "Shape not recognized for {} ".format(base_variable) + + "(note processing of 3D variables is not yet implemented)" + ) + + def initialise_0D(self): # initialise empty array of the correct size entries = np.empty(len(self.t_sol)) # Evaluate the base_variable index-by-index @@ -107,9 +118,9 @@ def initialise_1D(self): ) self.entries = entries - self.dimensions = 1 + self.dimensions = 0 - def initialise_2D(self): + def initialise_1D(self): len_space = self.base_eval.shape[0] entries = np.empty((len_space, len(self.t_sol))) @@ -147,7 +158,7 @@ def initialise_2D(self): # assign attributes for reference (either x_sol or r_sol) self.entries = entries - self.dimensions = 2 + self.dimensions = 1 if self.domain[0] in ["negative particle", "positive particle"]: self.first_dimension = "r" self.r_sol = space @@ -165,7 +176,8 @@ def initialise_2D(self): self.first_dimension = "x" self.x_sol = space - self.first_dim_pts = space + self.first_dim_pts = edges + self.internal_boundaries = self.mesh[0].internal_boundaries # set up interpolation # note that the order of 't' and 'space' is the reverse of what you'd expect @@ -174,9 +186,9 @@ def initialise_2D(self): self.t_sol, space, entries_for_interp, kind="linear", fill_value=np.nan ) - def initialise_3D(self): + def initialise_2D(self): """ - Initialise a 3D object that depends on x and r, or x and z. + Initialise a 2D object that depends on x and r, or x and z. """ first_dim_nodes = self.mesh[0].nodes first_dim_edges = self.mesh[0].edges @@ -209,10 +221,8 @@ def initialise_3D(self): self.z_sol = second_dim_pts else: raise pybamm.DomainError( - """ Cannot process 3D object with domain '{}' - and auxiliary_domains '{}'""".format( - self.domain, self.auxiliary_domains - ) + "Cannot process 3D object with domain '{}' " + "and auxiliary_domains '{}'".format(self.domain, self.auxiliary_domains) ) first_dim_size = len(first_dim_pts) @@ -243,7 +253,7 @@ def initialise_3D(self): # assign attributes for reference self.entries = entries - self.dimensions = 3 + self.dimensions = 2 self.first_dim_pts = first_dim_pts self.second_dim_pts = second_dim_pts @@ -255,7 +265,7 @@ def initialise_3D(self): fill_value=np.nan, ) - def initialise_2Dspace_scikit_fem(self): + def initialise_2D_fixed_t_scikit_fem(self): y_sol = self.mesh[0].edges["y"] len_y = len(y_sol) z_sol = self.mesh[0].edges["z"] @@ -275,13 +285,15 @@ def initialise_2Dspace_scikit_fem(self): self.z_sol = z_sol self.first_dimension = "y" self.second_dimension = "z" + self.first_dim_pts = y_sol + self.second_dim_pts = z_sol # set up interpolation self._interpolation_function = interp.interp2d( y_sol, z_sol, entries, kind="linear", fill_value=np.nan ) - def initialise_3D_scikit_fem(self): + def initialise_2D_scikit_fem(self): y_sol = self.mesh[0].edges["y"] len_y = len(y_sol) z_sol = self.mesh[0].edges["z"] @@ -307,11 +319,13 @@ def initialise_3D_scikit_fem(self): # assign attributes for reference self.entries = entries - self.dimensions = 3 + self.dimensions = 2 self.y_sol = y_sol self.z_sol = z_sol self.first_dimension = "y" self.second_dimension = "z" + self.first_dim_pts = y_sol + self.second_dim_pts = z_sol # set up interpolation self._interpolation_function = interp.RegularGridInterpolator( @@ -322,28 +336,28 @@ def __call__(self, t=None, x=None, r=None, y=None, z=None, warn=True): """ Evaluate the variable at arbitrary t (and x, r, y and/or z), using interpolation """ - if self.dimensions == 1: + if self.dimensions == 0: out = self._interpolation_function(t) + elif self.dimensions == 1: + out = self.call_1D(t, x, r, z) elif self.dimensions == 2: if t is None: out = self._interpolation_function(y, z) else: - out = self.call_2D(t, x, r, z) - elif self.dimensions == 3: - out = self.call_3D(t, x, r, y, z) + out = self.call_2D(t, x, r, y, z) if warn is True and np.isnan(out).any(): pybamm.logger.warning( "Calling variable outside interpolation range (returns 'nan')" ) return out - def call_2D(self, t, x, r, z): - "Evaluate a 2D variable" + def call_1D(self, t, x, r, z): + "Evaluate a 1D variable" spatial_var = eval_dimension_name(self.first_dimension, x, r, None, z) return self._interpolation_function(t, spatial_var) - def call_3D(self, t, x, r, y, z): - "Evaluate a 3D variable" + def call_2D(self, t, x, r, y, z): + "Evaluate a 2D variable" first_dim = eval_dimension_name(self.first_dimension, x, r, y, z) second_dim = eval_dimension_name(self.second_dimension, x, r, y, z) if isinstance(first_dim, np.ndarray): diff --git a/pybamm/quick_plot.py b/pybamm/quick_plot.py index fe6a80c3b1..667318ad95 100644 --- a/pybamm/quick_plot.py +++ b/pybamm/quick_plot.py @@ -7,6 +7,14 @@ from collections import defaultdict +class LoopList(list): + "A list which loops over itself when accessing an index so that it never runs out." + + def __getitem__(self, i): + # implement looping by calling "(i) modulo (length of list)" + return super().__getitem__(i % len(self)) + + def ax_min(data): "Calculate appropriate minimum axis value for plotting" data_min = np.nanmin(data) @@ -40,19 +48,13 @@ def split_long_string(title, max_words=4): class QuickPlot(object): """ Generates a quick plot of a subset of key outputs of the model so that the model - outputs can be easily assessed. The axis limits can be set using: - self.axis["Variable name"] = [x_min, x_max, y_min, y_max] - They can be reset to the default values by using self.reset_axis. + outputs can be easily assessed. Parameters ---------- - models: (iter of) :class:`pybamm.BaseModel` - The model(s) to plot the outputs of. - meshes: (iter of) :class:`pybamm.Mesh` - The mesh(es) on which the model(s) were solved. - solutions: (iter of) :class:`pybamm.Solver` - The numerical solution(s) for the model(s) which contained the solution to the - model(s). + solutions: (iter of) :class:`pybamm.Solution` or :class:`pybamm.Simulation` + The numerical solution(s) for the model(s), or the simulation object(s) + containing the solution(s). output_variables : list of str, optional List of variables to plot labels : list of str, optional @@ -62,6 +64,21 @@ class QuickPlot(object): ["r", "b", "k", "g", "m", "c"] linestyles : list of str, optional The linestyles to loop over when plotting. Defaults to ["-", ":", "--", "-."] + figsize : tuple of floats, optional + The size of the figure to make + time_unit : str, optional + Format for the time output ("hours", "minutes" or "seconds") + spatial_unit : str, optional + Format for the spatial axes ("m", "mm" or "um") + variable_limits : str or dict of str, optional + How to set the axis limits (for 0D or 1D variables) or colorbar limits (for 2D + variables). Options are: + + - "fixed" (default): keep all axes fixes so that all data is visible + - "tight": make axes tight to plot at each time + - dictionary: fine-grain control for each variable, can be either "fixed" or \ + "tight" or a specific tuple (lower, upper). + """ def __init__( @@ -71,59 +88,120 @@ def __init__( labels=None, colors=None, linestyles=None, + figsize=None, + time_unit=None, + spatial_unit="um", + variable_limits="fixed", ): - if isinstance(solutions, pybamm.Solution): + if isinstance(solutions, (pybamm.Solution, pybamm.Simulation)): solutions = [solutions] elif not isinstance(solutions, list): - raise TypeError("'solutions' must be 'pybamm.Solution' or list") + raise TypeError( + "solutions must be 'pybamm.Solution' or 'pybamm.Simulation' or list" + ) + + # Extract solution from any simulations + for idx, sol in enumerate(solutions): + if isinstance(sol, pybamm.Simulation): + # 'sol' is actually a 'Simulation' object here so it has a 'Solution' + # attribute + solutions[idx] = sol.solution models = [solution.model for solution in solutions] # Set labels - self.labels = labels or [model.name for model in models] + if labels is None: + self.labels = [model.name for model in models] + else: + if len(labels) != len(models): + raise ValueError( + "labels '{}' have different length to models '{}'".format( + labels, [model.name for model in models] + ) + ) + self.labels = labels - # Set colors and linestyles - self.colors = colors - self.linestyles = linestyles + # Set colors, linestyles, figsize, axis limits + # call LoopList to make sure list index never runs out + self.colors = LoopList(colors or ["r", "b", "k", "g", "m", "c"]) + self.linestyles = LoopList(linestyles or ["-", ":", "--", "-."]) + self.figsize = figsize or (15, 8) - # Time scale in hours - self.time_scale = models[0].timescale_eval / 3600 # Spatial scales (default to 1 if information not in model) + if spatial_unit == "m": + spatial_factor = 1 + self.spatial_unit = "m" + elif spatial_unit == "mm": + spatial_factor = 1e3 + self.spatial_unit = "mm" + elif spatial_unit == "um": # micrometers + spatial_factor = 1e6 + self.spatial_unit = "$\mu m$" + else: + raise ValueError("spatial unit '{}' not recognized".format(spatial_unit)) + variables = models[0].variables - self.spatial_scales = {"x": 1, "y": 1, "z": 1, "r_n": 1, "r_p": 1} - if "x [m]" and "x" in variables: - self.spatial_scales["x"] = (variables["x [m]"] / variables["x"]).evaluate()[ - -1 - ] - if "y [m]" and "y" in variables: - self.spatial_scales["y"] = (variables["y [m]"] / variables["y"]).evaluate()[ + # empty spatial scales, will raise error later if can't find a particular one + self.spatial_scales = {} + if "x [m]" in variables and "x" in variables: + x_scale = (variables["x [m]"] / variables["x"]).evaluate()[ -1 - ] - if "z [m]" and "z" in variables: - self.spatial_scales["z"] = (variables["z [m]"] / variables["z"]).evaluate()[ - -1 - ] - if "r_n [m]" and "r_n" in variables: - self.spatial_scales["r_n"] = ( + ] * spatial_factor + self.spatial_scales.update({dom: x_scale for dom in variables["x"].domain}) + if "y [m]" in variables and "y" in variables: + self.spatial_scales["current collector y"] = ( + variables["y [m]"] / variables["y"] + ).evaluate()[-1] * spatial_factor + if "z [m]" in variables and "z" in variables: + self.spatial_scales["current collector z"] = ( + variables["z [m]"] / variables["z"] + ).evaluate()[-1] * spatial_factor + if "r_n [m]" in variables and "r_n" in variables: + self.spatial_scales["negative particle"] = ( variables["r_n [m]"] / variables["r_n"] - ).evaluate()[-1] - if "r_p [m]" and "r_p" in variables: - self.spatial_scales["r_p"] = ( + ).evaluate()[-1] * spatial_factor + if "r_p [m]" in variables and "r_p" in variables: + self.spatial_scales["positive particle"] = ( variables["r_p [m]"] / variables["r_p"] - ).evaluate()[-1] + ).evaluate()[-1] * spatial_factor # Time parameters + model_timescale_in_seconds = models[0].timescale_eval self.ts = [solution.t for solution in solutions] - self.min_t = np.min([t[0] for t in self.ts]) * self.time_scale - self.max_t = np.max([t[-1] for t in self.ts]) * self.time_scale + min_t = np.min([t[0] for t in self.ts]) * model_timescale_in_seconds + max_t = np.max([t[-1] for t in self.ts]) * model_timescale_in_seconds + + # Set timescale + if time_unit is None: + # defaults depend on how long the simulation is + if max_t >= 3600: + time_scaling_factor = 3600 # time in hours + self.time_unit = "h" + else: + time_scaling_factor = 1 # time in seconds + self.time_unit = "s" + elif time_unit == "seconds": + time_scaling_factor = 1 + self.time_unit = "s" + elif time_unit == "minutes": + time_scaling_factor = 60 + self.time_unit = "min" + elif time_unit == "hours": + time_scaling_factor = 3600 + self.time_unit = "h" + else: + raise ValueError("time unit '{}' not recognized".format(time_unit)) + self.time_scale = model_timescale_in_seconds / time_scaling_factor + self.min_t = min_t / time_scaling_factor + self.max_t = max_t / time_scaling_factor # Default output variables for lead-acid and lithium-ion if output_variables is None: if isinstance(models[0], pybamm.lithium_ion.BaseModel): output_variables = [ - "Negative particle surface concentration", - "Electrolyte concentration", - "Positive particle surface concentration", + "Negative particle surface concentration [mol.m-3]", + "Electrolyte concentration [mol.m-3]", + "Positive particle surface concentration [mol.m-3]", "Current [A]", "Negative electrode potential [V]", "Electrolyte potential [V]", @@ -139,75 +217,182 @@ def __init__( "Electrolyte potential [V]", "Terminal voltage [V]", ] - # else plot all variables in first model + + # Prepare dictionary of variables + # output_variables is a list of strings or lists, e.g. + # ["var 1", ["variable 2", "var 3"]] + output_variable_tuples = [] + self.variable_limits = {} + for variable_list in output_variables: + # Make sure we always have a list of lists of variables, e.g. + # [["var 1"], ["variable 2", "var 3"]] + if isinstance(variable_list, str): + variable_list = [variable_list] + + # Store the key as a tuple + variable_tuple = tuple(variable_list) + output_variable_tuples.append(variable_tuple) + + # axis limits + if variable_limits in ["fixed", "tight"]: + self.variable_limits[variable_tuple] = variable_limits else: - output_variables = models[0].variables + # If there is only one variable, extract it + if len(variable_tuple) == 1: + variable = variable_tuple[0] + else: + variable = variable_tuple + try: + self.variable_limits[variable_tuple] = variable_limits[variable] + except KeyError: + # if variable_tuple is not provided, default to "fixed" + self.variable_limits[variable_tuple] = "fixed" + except TypeError: + raise TypeError( + "variable_limits must be 'fixed', 'tight', or a dict" + ) - self.set_output_variables(output_variables, solutions) + self.set_output_variables(output_variable_tuples, solutions) self.reset_axis() def set_output_variables(self, output_variables, solutions): # Set up output variables self.variables = {} - self.spatial_variable = {} + self.spatial_variable_dict = {} + self.first_dimensional_spatial_variable = {} + self.second_dimensional_spatial_variable = {} + self.first_spatial_scale = {} + self.second_spatial_scale = {} + self.is_x_r = {} # Calculate subplot positions based on number of variables supplied self.subplot_positions = {} self.n_rows = int(len(output_variables) // np.sqrt(len(output_variables))) self.n_cols = int(np.ceil(len(output_variables) / self.n_rows)) - # Process output variables into a form that can be plotted - processed_variables = {} - for solution in solutions: - processed_variables[solution] = {} - for variable_list in output_variables: - # Make sure we always have a list of lists of variables - if isinstance(variable_list, str): - variable_list = [variable_list] - # Add all variables to the list of variables that should be processed - processed_variables[solution].update( - {var: solution[var] for var in variable_list} - ) - - # Prepare dictionary of variables - for k, variable_list in enumerate(output_variables): - # Make sure we always have a list of lists of variables - if isinstance(variable_list, str): - variable_list = [variable_list] - + for k, variable_tuple in enumerate(output_variables): # Prepare list of variables - key = tuple(variable_list) - self.variables[key] = [None] * len(solutions) + variables = [None] * len(solutions) # process each variable in variable_list for each model for i, solution in enumerate(solutions): - # self.variables is a dictionary of lists of lists - self.variables[key][i] = [ - processed_variables[solution][var] for var in variable_list - ] + # variables lists of lists, so variables[i] is a list + variables[i] = [] + for var in variable_tuple: + sol = solution[var] + # Check variable isn't all-nan + if np.all(np.isnan(sol.entries)): + raise ValueError("All-NaN variable '{}' provided".format(var)) + # If ok, add to the list of solutions + else: + variables[i].append(sol) # Make sure variables have the same dimensions and domain - first_variable = self.variables[key][0][0] + # just use the first solution to check this + first_solution = variables[0] + first_variable = first_solution[0] domain = first_variable.domain - for variable in self.variables[key][0]: + # check all other solutions against the first solution + for idx, variable in enumerate(first_solution): if variable.domain != domain: - raise ValueError("mismatching variable domains") - - # Set the x variable for any two-dimensional variables - if first_variable.dimensions == 2: - spatial_variable_key = first_variable.first_dimension - spatial_variable_value = first_variable.first_dim_pts - self.spatial_variable[key] = ( - spatial_variable_key, - spatial_variable_value, + raise ValueError( + "Mismatching variable domains. " + "'{}' has domain '{}', but '{}' has domain '{}'".format( + variable_tuple[0], + domain, + variable_tuple[idx], + variable.domain, + ) + ) + self.spatial_variable_dict[variable_tuple] = {} + + # Set the x variable (i.e. "x" or "r" for any one-dimensional variables) + if first_variable.dimensions == 1: + ( + spatial_var_name, + spatial_var_value, + spatial_scale, + ) = self.get_spatial_var(variable_tuple, first_variable, "first") + self.spatial_variable_dict[variable_tuple] = { + spatial_var_name: spatial_var_value + } + self.first_dimensional_spatial_variable[variable_tuple] = ( + spatial_var_value * spatial_scale ) + self.first_spatial_scale[variable_tuple] = spatial_scale + + elif first_variable.dimensions == 2: + # Don't allow 2D variables if there are multiple solutions + if len(variables) > 1: + raise NotImplementedError( + "Cannot plot 2D variables when comparing multiple solutions, " + "but '{}' is 2D".format(variable_tuple[0]) + ) + # But do allow if just a single solution + else: + # Add both spatial variables to the variable_tuples + ( + first_spatial_var_name, + first_spatial_var_value, + first_spatial_scale, + ) = self.get_spatial_var(variable_tuple, first_variable, "first") + ( + second_spatial_var_name, + second_spatial_var_value, + second_spatial_scale, + ) = self.get_spatial_var(variable_tuple, first_variable, "second") + self.spatial_variable_dict[variable_tuple] = { + first_spatial_var_name: first_spatial_var_value, + second_spatial_var_name: second_spatial_var_value, + } + self.first_dimensional_spatial_variable[variable_tuple] = ( + first_spatial_var_value * first_spatial_scale + ) + self.second_dimensional_spatial_variable[variable_tuple] = ( + second_spatial_var_value * second_spatial_scale + ) + if first_spatial_var_name == "r" and second_spatial_var_name == "x": + self.is_x_r[variable_tuple] = True + else: + self.is_x_r[variable_tuple] = False + + # Store variables and subplot position + self.variables[variable_tuple] = variables + self.subplot_positions[variable_tuple] = (self.n_rows, self.n_cols, k + 1) + + def get_spatial_var(self, key, variable, dimension): + "Return the appropriate spatial variable(s)" + + # Extract name and dimensionless value + # Special case for current collector, which is 2D but in a weird way (both + # first and second variables are in the same domain, not auxiliary domain) + if dimension == "first": + spatial_var_name = variable.first_dimension + spatial_var_value = variable.first_dim_pts + domain = variable.domain[0] + elif dimension == "second": + spatial_var_name = variable.second_dimension + spatial_var_value = variable.second_dim_pts + if variable.domain[0] == "current collector": + domain = "current collector" + else: + domain = variable.auxiliary_domains["secondary"][0] + + if domain == "current collector": + domain += " {}".format(spatial_var_name) + + # Get scale + try: + spatial_scale = self.spatial_scales[domain] + except KeyError: + raise KeyError( + ( + "Can't find spatial scale for '{}', make sure both '{} [m]' " + + "and '{}' are defined in the model variables" + ).format(domain, *[spatial_var_name] * 2) + ) - # Don't allow 3D variables - elif any(var.dimensions == 3 for var in self.variables[key][0]): - raise NotImplementedError("cannot plot 3D variables") - - # Define subplot position - self.subplot_positions[key] = (self.n_rows, self.n_cols, k + 1) + return spatial_var_name, spatial_var_value, spatial_scale def reset_axis(self): """ @@ -215,61 +400,62 @@ def reset_axis(self): These are calculated to fit around the minimum and maximum values of all the variables in each subplot """ - self.axis = {} + self.axis_limits = {} for key, variable_lists in self.variables.items(): - if variable_lists[0][0].dimensions == 1: - spatial_var_name, spatial_var_value = "x", None + if variable_lists[0][0].dimensions == 0: x_min = self.min_t x_max = self.max_t + elif variable_lists[0][0].dimensions == 1: + x_min = self.first_dimensional_spatial_variable[key][0] + x_max = self.first_dimensional_spatial_variable[key][-1] elif variable_lists[0][0].dimensions == 2: - spatial_var_name, spatial_var_value = self.spatial_variable[key] - if spatial_var_name == "r": - if "negative" in key[0].lower(): - spatial_var_scaled = ( - spatial_var_value * self.spatial_scales["r_n"] - ) - elif "positive" in key[0].lower(): - spatial_var_scaled = ( - spatial_var_value * self.spatial_scales["r_p"] - ) + # different order based on whether the domains are x-r, x-z or y-z + if self.is_x_r[key] is True: + x_min = self.second_dimensional_spatial_variable[key][0] + x_max = self.second_dimensional_spatial_variable[key][-1] + y_min = self.first_dimensional_spatial_variable[key][0] + y_max = self.first_dimensional_spatial_variable[key][-1] else: - spatial_var_scaled = ( - spatial_var_value * self.spatial_scales[spatial_var_name] - ) - x_min = spatial_var_scaled[0] - x_max = spatial_var_scaled[-1] - - # Get min and max y values - y_min = np.min( - [ - ax_min( - var( - self.ts[i], - **{spatial_var_name: spatial_var_value}, - warn=False - ) - ) - for i, variable_list in enumerate(variable_lists) - for var in variable_list - ] - ) - y_max = np.max( - [ - ax_max( - var( - self.ts[i], - **{spatial_var_name: spatial_var_value}, - warn=False - ) - ) - for i, variable_list in enumerate(variable_lists) - for var in variable_list - ] - ) - if y_min == y_max: - y_min -= 1 - y_max += 1 - self.axis[key] = [x_min, x_max, y_min, y_max] + x_min = self.first_dimensional_spatial_variable[key][0] + x_max = self.first_dimensional_spatial_variable[key][-1] + y_min = self.second_dimensional_spatial_variable[key][0] + y_max = self.second_dimensional_spatial_variable[key][-1] + + # Create axis for contour plot + self.axis_limits[key] = [x_min, x_max, y_min, y_max] + + # Get min and max variable values + if self.variable_limits[key] == "fixed": + # fixed variable limits: calculate "globlal" min and max + spatial_vars = self.spatial_variable_dict[key] + var_min = np.min( + [ + ax_min(var(self.ts[i], **spatial_vars, warn=False)) + for i, variable_list in enumerate(variable_lists) + for var in variable_list + ] + ) + var_max = np.max( + [ + ax_max(var(self.ts[i], **spatial_vars, warn=False)) + for i, variable_list in enumerate(variable_lists) + for var in variable_list + ] + ) + if var_min == var_max: + var_min -= 1 + var_max += 1 + elif self.variable_limits[key] == "tight": + # tight variable limits: axes will adjust each time + var_min, var_max = None, None + else: + # user-specified axis limits + var_min, var_max = self.variable_limits[key] + + if variable_lists[0][0].dimensions in [0, 1]: + self.axis_limits[key] = [x_min, x_max, var_min, var_max] + else: + self.variable_limits[key] = (var_min, var_max) def plot(self, t): """Produces a quick plot with the internal states at time t. @@ -281,125 +467,263 @@ def plot(self, t): """ import matplotlib.pyplot as plt + import matplotlib.gridspec as gridspec + from matplotlib import cm, colors t /= self.time_scale - self.fig, self.ax = plt.subplots(self.n_rows, self.n_cols, figsize=(15, 8)) - plt.tight_layout() - plt.subplots_adjust(left=-0.1) + self.fig = plt.figure(figsize=self.figsize) + + self.gridspec = gridspec.GridSpec(self.n_rows, self.n_cols) self.plots = {} self.time_lines = {} + self.colorbars = {} + self.axes = [] + + # initialize empty handles, to be created only if the appropriate plots are made + solution_handles = [] - colors = self.colors or ["r", "b", "k", "g", "m", "c"] - linestyles = self.linestyles or ["-", ":", "--", "-."] - fontsize = 42 // self.n_cols + if self.n_cols == 1: + fontsize = 30 + else: + fontsize = 42 // self.n_cols for k, (key, variable_lists) in enumerate(self.variables.items()): - if len(self.variables) == 1: - ax = self.ax - else: - ax = self.ax.flat[k] - ax.set_xlim(self.axis[key][:2]) - ax.set_ylim(self.axis[key][2:]) + ax = self.fig.add_subplot(self.gridspec[k]) + self.axes.append(ax) + x_min, x_max, y_min, y_max = self.axis_limits[key] + ax.set_xlim(x_min, x_max) + if y_min is not None and y_max is not None: + ax.set_ylim(y_min, y_max) ax.xaxis.set_major_locator(plt.MaxNLocator(3)) self.plots[key] = defaultdict(dict) + variable_handles = [] # Set labels for the first subplot only (avoid repetition) - if variable_lists[0][0].dimensions == 2: - # 2D plot: plot as a function of x at time t - spatial_var_name, spatial_var_value = self.spatial_variable[key] - ax.set_xlabel(spatial_var_name + " [m]", fontsize=fontsize) + if variable_lists[0][0].dimensions == 0: + # 0D plot: plot as a function of time, indicating time t with a line + ax.set_xlabel("Time [{}]".format(self.time_unit), fontsize=fontsize) for i, variable_list in enumerate(variable_lists): for j, variable in enumerate(variable_list): - if spatial_var_name == "r": - if "negative" in key[0].lower(): - spatial_scale = self.spatial_scales["r_n"] - elif "positive" in key[0].lower(): - spatial_scale = self.spatial_scales["r_p"] + if len(variable_list) == 1: + # single variable -> use linestyle to differentiate model + linestyle = self.linestyles[i] else: - spatial_scale = self.spatial_scales[spatial_var_name] - (self.plots[key][i][j],) = ax.plot( - spatial_var_value * spatial_scale, - variable( - t, **{spatial_var_name: spatial_var_value}, warn=False - ), - lw=2, - color=colors[i], - linestyle=linestyles[j], - ) - else: - # 1D plot: plot as a function of time, indicating time t with a line - ax.set_xlabel("Time [h]", fontsize=fontsize) - for i, variable_list in enumerate(variable_lists): - for j, variable in enumerate(variable_list): + # multiple variables -> use linestyle to differentiate + # variables (color differentiates models) + linestyle = self.linestyles[j] full_t = self.ts[i] (self.plots[key][i][j],) = ax.plot( full_t * self.time_scale, variable(full_t, warn=False), lw=2, - color=colors[i], - linestyle=linestyles[j], + color=self.colors[i], + linestyle=linestyle, ) - y_min, y_max = self.axis[key][2:] + variable_handles.append(self.plots[key][0][j]) + solution_handles.append(self.plots[key][i][0]) + y_min, y_max = ax.get_ylim() + ax.set_ylim(y_min, y_max) (self.time_lines[key],) = ax.plot( [t * self.time_scale, t * self.time_scale], [y_min, y_max], "k--" ) + elif variable_lists[0][0].dimensions == 1: + # 1D plot: plot as a function of x at time t + # Read dictionary of spatial variables + spatial_vars = self.spatial_variable_dict[key] + spatial_var_name = list(spatial_vars.keys())[0] + ax.set_xlabel( + "{} [{}]".format(spatial_var_name, self.spatial_unit), + fontsize=fontsize, + ) + for i, variable_list in enumerate(variable_lists): + for j, variable in enumerate(variable_list): + if len(variable_list) == 1: + # single variable -> use linestyle to differentiate model + linestyle = self.linestyles[i] + else: + # multiple variables -> use linestyle to differentiate + # variables (color differentiates models) + linestyle = self.linestyles[j] + (self.plots[key][i][j],) = ax.plot( + self.first_dimensional_spatial_variable[key], + variable(t, **spatial_vars, warn=False), + lw=2, + color=self.colors[i], + linestyle=linestyle, + zorder=10, + ) + variable_handles.append(self.plots[key][0][j]) + solution_handles.append(self.plots[key][i][0]) + # add dashed lines for boundaries between subdomains + y_min, y_max = ax.get_ylim() + ax.set_ylim(y_min, y_max) + for bnd in variable_lists[0][0].internal_boundaries: + bnd_dim = bnd * self.first_spatial_scale[key] + ax.plot( + [bnd_dim, bnd_dim], [y_min, y_max], color="0.5", lw=1, zorder=0 + ) + elif variable_lists[0][0].dimensions == 2: + # Read dictionary of spatial variables + spatial_vars = self.spatial_variable_dict[key] + # there can only be one entry in the variable list + variable = variable_lists[0][0] + # different order based on whether the domains are x-r, x-z or y-z + if self.is_x_r[key] is True: + x_name = list(spatial_vars.keys())[1][0] + y_name = list(spatial_vars.keys())[0][0] + x = self.second_dimensional_spatial_variable[key] + y = self.first_dimensional_spatial_variable[key] + var = variable(t, **spatial_vars, warn=False) + else: + x_name = list(spatial_vars.keys())[0][0] + y_name = list(spatial_vars.keys())[1][0] + x = self.first_dimensional_spatial_variable[key] + y = self.second_dimensional_spatial_variable[key] + var = variable(t, **spatial_vars, warn=False).T + ax.set_xlabel( + "{} [{}]".format(x_name, self.spatial_unit), fontsize=fontsize + ) + ax.set_ylabel( + "{} [{}]".format(y_name, self.spatial_unit), fontsize=fontsize + ) + vmin, vmax = self.variable_limits[key] + ax.contourf( + x, y, var, levels=100, vmin=vmin, vmax=vmax, cmap="coolwarm" + ) + if vmin is None and vmax is None: + vmin = ax_min(var) + vmax = ax_max(var) + self.colorbars[key] = self.fig.colorbar( + cm.ScalarMappable( + colors.Normalize(vmin=vmin, vmax=vmax), cmap="coolwarm" + ), + ax=ax, + ) # Set either y label or legend entries if len(key) == 1: title = split_long_string(key[0]) ax.set_title(title, fontsize=fontsize) else: ax.legend( + variable_handles, [split_long_string(s, 6) for s in key], bbox_to_anchor=(0.5, 1), fontsize=8, loc="lower center", ) - if k == len(self.variables) - 1: - ax.legend(self.labels, loc="upper right", bbox_to_anchor=(1, -0.2)) - def dynamic_plot(self, testing=False): - """ - Generate a dynamic plot with a slider to control the time. We recommend using - ipywidgets instead of this function if you are using jupyter notebooks + # Set global legend + if len(solution_handles) > 0: + self.fig.legend(solution_handles, self.labels, loc="lower right") + + def dynamic_plot(self, testing=False, step=None): """ + Generate a dynamic plot with a slider to control the time. - import matplotlib.pyplot as plt - from matplotlib.widgets import Slider + Parameters + ---------- + step : float + For notebook mode, size of steps to allow in the slider. Defaults to 1/100th + of the total time. + testing : bool + Whether to actually make the plot (turned off for unit tests) + + """ + if pybamm.is_notebook(): # pragma: no cover + import ipywidgets as widgets + + step = step or self.max_t / 100 + widgets.interact( + self.plot, + t=widgets.FloatSlider(min=0, max=self.max_t, step=step, value=0), + continuous_update=False, + ) + else: + import matplotlib.pyplot as plt + from matplotlib.widgets import Slider - # create an initial plot at time 0 - self.plot(0) + # create an initial plot at time 0 + self.plot(0) - axcolor = "lightgoldenrodyellow" - axfreq = plt.axes([0.315, 0.02, 0.37, 0.03], facecolor=axcolor) - self.sfreq = Slider(axfreq, "Time [h]", 0, self.max_t, valinit=0) - self.sfreq.on_changed(self.update) + axcolor = "lightgoldenrodyellow" + ax_slider = plt.axes([0.315, 0.02, 0.37, 0.03], facecolor=axcolor) + self.slider = Slider( + ax_slider, "Time [{}]".format(self.time_unit), 0, self.max_t, valinit=0 + ) + self.slider.on_changed(self.slider_update) - # ignore the warning about tight layout - warnings.simplefilter("ignore") - self.fig.tight_layout() - warnings.simplefilter("always") + # ignore the warning about tight layout + warnings.simplefilter("ignore") + bottom = 0.05 + 0.03 * max((len(self.labels) - 2), 0) + self.gridspec.tight_layout(self.fig, rect=[0, bottom, 1, 1]) + warnings.simplefilter("always") - if not testing: # pragma: no cover - plt.show() + if not testing: # pragma: no cover + plt.show() - def update(self, val): + def slider_update(self, t): """ Update the plot in self.plot() with values at new time """ - t = self.sfreq.val + from matplotlib import cm, colors + t_dimensionless = t / self.time_scale - for key, plot in self.plots.items(): - if self.variables[key][0][0].dimensions == 2: - spatial_var_name, spatial_var_value = self.spatial_variable[key] + for k, (key, plot) in enumerate(self.plots.items()): + ax = self.axes[k] + if self.variables[key][0][0].dimensions == 0: + self.time_lines[key].set_xdata([t]) + elif self.variables[key][0][0].dimensions == 1: + var_min = np.inf + var_max = -np.inf for i, variable_lists in enumerate(self.variables[key]): for j, variable in enumerate(variable_lists): - plot[i][j].set_ydata( - variable( - t_dimensionless, - **{spatial_var_name: spatial_var_value}, - warn=False - ) + var = variable( + t_dimensionless, + **self.spatial_variable_dict[key], + warn=False ) - else: - self.time_lines[key].set_xdata([t]) + plot[i][j].set_ydata(var) + var_min = min(var_min, np.nanmin(var)) + var_max = max(var_max, np.nanmax(var)) + # update boundaries between subdomains + y_min, y_max = self.axis_limits[key][2:] + if y_min is None and y_max is None: + y_min, y_max = ax_min(var_min), ax_max(var_max) + ax.set_ylim(y_min, y_max) + for bnd in self.variables[key][0][0].internal_boundaries: + bnd_dim = bnd * self.first_spatial_scale[key] + ax.plot( + [bnd_dim, bnd_dim], + [y_min, y_max], + color="0.5", + lw=1, + zorder=0, + ) + elif self.variables[key][0][0].dimensions == 2: + # 2D plot: plot as a function of x and y at time t + # Read dictionary of spatial variables + spatial_vars = self.spatial_variable_dict[key] + # there can only be one entry in the variable list + variable = self.variables[key][0][0] + vmin, vmax = self.variable_limits[key] + if self.is_x_r[key] is True: + x = self.second_dimensional_spatial_variable[key] + y = self.first_dimensional_spatial_variable[key] + var = variable(t_dimensionless, **spatial_vars, warn=False) + else: + x = self.first_dimensional_spatial_variable[key] + y = self.second_dimensional_spatial_variable[key] + var = variable(t_dimensionless, **spatial_vars, warn=False).T + ax.contourf( + x, y, var, levels=100, vmin=vmin, vmax=vmax, cmap="coolwarm" + ) + if (vmin, vmax) == (None, None): + vmin = ax_min(var) + vmax = ax_max(var) + cb = self.colorbars[key] + cb.update_bruteforce( + cm.ScalarMappable( + colors.Normalize(vmin=vmin, vmax=vmax), cmap="coolwarm" + ) + ) self.fig.canvas.draw_idle() diff --git a/pybamm/simulation.py b/pybamm/simulation.py index 8f6b3dd40c..1ae9621a4c 100644 --- a/pybamm/simulation.py +++ b/pybamm/simulation.py @@ -9,12 +9,15 @@ import sys -def isnotebook(): +def is_notebook(): try: shell = get_ipython().__class__.__name__ - if shell == "ZMQInteractiveShell": - return True # Jupyter notebook or qtconsole - elif shell == "TerminalInteractiveShell": + if shell == "ZMQInteractiveShell": # pragma: no cover + # Jupyter notebook or qtconsole + cfg = get_ipython().config + nb = len(cfg["InteractiveShell"].keys()) == 0 + return nb + elif shell == "TerminalInteractiveShell": # pragma: no cover return False # Terminal running IPython else: return False # Other type (?) @@ -101,7 +104,7 @@ def __init__( self.reset(update_model=False) # ignore runtime warnings in notebooks - if isnotebook(): + if is_notebook(): # pragma: no cover import warnings warnings.filterwarnings("ignore") @@ -530,15 +533,7 @@ def plot(self, quick_plot_vars=None, testing=False): plot = pybamm.QuickPlot(self._solution, output_variables=quick_plot_vars) - if isnotebook(): - import ipywidgets as widgets - - widgets.interact( - plot.plot, - t=widgets.FloatSlider(min=0, max=plot.max_t, step=0.05, value=0), - ) - else: - plot.dynamic_plot(testing=testing) + plot.dynamic_plot(testing=testing) @property def model(self): diff --git a/tests/integration/test_models/standard_output_comparison.py b/tests/integration/test_models/standard_output_comparison.py index b36ac1a7bc..e96a060669 100644 --- a/tests/integration/test_models/standard_output_comparison.py +++ b/tests/integration/test_models/standard_output_comparison.py @@ -68,9 +68,9 @@ def compare(self, var, tol=1e-2): var0 = model_variables[0] spatial_pts = {} - if var0.dimensions >= 2: + if var0.dimensions >= 1: spatial_pts[var0.first_dimension] = var0.first_dim_pts - if var0.dimensions >= 3: + if var0.dimensions >= 2: spatial_pts[var0.second_dimension] = var0.second_dim_pts # Calculate tolerance based on the value of var0 diff --git a/tests/integration/test_quick_plot.py b/tests/integration/test_quick_plot.py deleted file mode 100644 index 9be762f7b0..0000000000 --- a/tests/integration/test_quick_plot.py +++ /dev/null @@ -1,89 +0,0 @@ -import pybamm -import unittest -import numpy as np - - -class TestQuickPlot(unittest.TestCase): - """ - Tests that QuickPlot is created correctly - """ - - def test_plot_lithium_ion(self): - spm = pybamm.lithium_ion.SPM() - spme = pybamm.lithium_ion.SPMe() - geometry = spm.default_geometry - param = spm.default_parameter_values - param.process_model(spm) - param.process_model(spme) - param.process_geometry(geometry) - mesh = pybamm.Mesh(geometry, spme.default_submesh_types, spme.default_var_pts) - disc_spm = pybamm.Discretisation(mesh, spm.default_spatial_methods) - disc_spme = pybamm.Discretisation(mesh, spme.default_spatial_methods) - disc_spm.process_model(spm) - disc_spme.process_model(spme) - t_eval = np.linspace(0, 3600, 100) - solution_spm = spm.default_solver.solve(spm, t_eval) - solution_spme = spme.default_solver.solve(spme, t_eval) - quick_plot = pybamm.QuickPlot([solution_spm, solution_spme]) - quick_plot.plot(0) - - # update the axis - new_axis = [0, 0.5, 0, 1] - quick_plot.axis.update({("Electrolyte concentration",): new_axis}) - self.assertEqual(quick_plot.axis[("Electrolyte concentration",)], new_axis) - - # and now reset them - quick_plot.reset_axis() - self.assertNotEqual(quick_plot.axis[("Electrolyte concentration",)], new_axis) - - # check dynamic plot loads - quick_plot.dynamic_plot(testing=True) - - quick_plot.update(0.01) - - # Test with different output variables - output_vars = [ - "Negative particle surface concentration", - "Electrolyte concentration", - "Positive particle surface concentration", - ] - quick_plot = pybamm.QuickPlot(solution_spm, output_vars) - self.assertEqual(len(quick_plot.axis), 3) - quick_plot.plot(0) - - # update the axis - new_axis = [0, 0.5, 0, 1] - quick_plot.axis.update({("Electrolyte concentration",): new_axis}) - self.assertEqual(quick_plot.axis[("Electrolyte concentration",)], new_axis) - - # and now reset them - quick_plot.reset_axis() - self.assertNotEqual(quick_plot.axis[("Electrolyte concentration",)], new_axis) - - # check dynamic plot loads - quick_plot.dynamic_plot(testing=True) - - quick_plot.update(0.01) - - def test_plot_lead_acid(self): - loqs = pybamm.lead_acid.LOQS() - geometry = loqs.default_geometry - param = loqs.default_parameter_values - param.process_model(loqs) - param.process_geometry(geometry) - mesh = pybamm.Mesh(geometry, loqs.default_submesh_types, loqs.default_var_pts) - disc_loqs = pybamm.Discretisation(mesh, loqs.default_spatial_methods) - disc_loqs.process_model(loqs) - t_eval = np.linspace(0, 3600, 100) - solution_loqs = loqs.default_solver.solve(loqs, t_eval) - - pybamm.QuickPlot(solution_loqs) - - -if __name__ == "__main__": - print("Add -v for more debug output") - import sys - - if "-v" in sys.argv: - debug = True - unittest.main() diff --git a/tests/unit/test_meshes/test_meshes.py b/tests/unit/test_meshes/test_meshes.py index 508a783723..0680010233 100644 --- a/tests/unit/test_meshes/test_meshes.py +++ b/tests/unit/test_meshes/test_meshes.py @@ -189,9 +189,11 @@ def test_combine_submeshes(self): ), 0, ) + np.testing.assert_almost_equal(submesh[0].internal_boundaries, [0.1 / 0.6]) with self.assertRaises(pybamm.DomainError): mesh.combine_submeshes("negative electrode", "positive electrode") + # test errors geometry = { "negative electrode": { "primary": { @@ -206,7 +208,6 @@ def test_combine_submeshes(self): } param.process_geometry(geometry) - # create mesh mesh = pybamm.Mesh(geometry, submesh_types, var_pts) with self.assertRaisesRegex(pybamm.DomainError, "trying"): diff --git a/tests/unit/test_models/test_full_battery_models/test_base_battery_model.py b/tests/unit/test_models/test_full_battery_models/test_base_battery_model.py index 8f0ed975bd..22983330a9 100644 --- a/tests/unit/test_models/test_full_battery_models/test_base_battery_model.py +++ b/tests/unit/test_models/test_full_battery_models/test_base_battery_model.py @@ -108,7 +108,7 @@ def test_default_spatial_methods(self): ) def test_bad_options(self): - with self.assertRaisesRegex(pybamm.OptionError, "option"): + with self.assertRaisesRegex(pybamm.OptionError, "Option"): pybamm.BaseBatteryModel({"bad option": "bad option"}) with self.assertRaisesRegex(pybamm.OptionError, "current collector model"): pybamm.BaseBatteryModel({"current collector": "bad current collector"}) diff --git a/tests/unit/test_processed_variable.py b/tests/unit/test_processed_variable.py index 79a31c9c9f..b488df8a3b 100644 --- a/tests/unit/test_processed_variable.py +++ b/tests/unit/test_processed_variable.py @@ -9,7 +9,7 @@ class TestProcessedVariable(unittest.TestCase): - def test_processed_variable_1D(self): + def test_processed_variable_0D(self): # without space t = pybamm.t y = pybamm.StateVector(slice(0, 1)) @@ -20,7 +20,7 @@ def test_processed_variable_1D(self): processed_var = pybamm.ProcessedVariable(var, pybamm.Solution(t_sol, y_sol)) np.testing.assert_array_equal(processed_var.entries, t_sol * y_sol[0]) - def test_processed_variable_2D(self): + def test_processed_variable_1D(self): t = pybamm.t var = pybamm.Variable("var", domain=["negative electrode", "separator"]) x = pybamm.SpatialVariable("x", domain=["negative electrode", "separator"]) @@ -60,7 +60,7 @@ def test_processed_variable_2D(self): x_s_edge.entries[:, 0], processed_x_s_edge.entries[:, 0] ) - def test_processed_variable_2D_unknown_domain(self): + def test_processed_variable_1D_unknown_domain(self): x = pybamm.SpatialVariable("x", domain="SEI layer", coord_sys="cartesian") geometry = pybamm.Geometry() geometry.add_domain( @@ -86,7 +86,7 @@ def test_processed_variable_2D_unknown_domain(self): c.mesh = mesh["SEI layer"] pybamm.ProcessedVariable(c, solution) - def test_processed_variable_3D_x_r(self): + def test_processed_variable_2D_x_r(self): var = pybamm.Variable( "var", domain=["negative particle"], @@ -111,7 +111,7 @@ def test_processed_variable_3D_x_r(self): np.reshape(y_sol, [len(r_sol), len(x_sol), len(t_sol)]), ) - def test_processed_variable_3D_x_z(self): + def test_processed_variable_2D_x_z(self): var = pybamm.Variable( "var", domain=["negative electrode", "separator"], @@ -151,7 +151,7 @@ def test_processed_variable_3D_x_z(self): x_s_edge.entries.flatten(), processed_x_s_edge.entries[:, :, 0].T.flatten() ) - def test_processed_variable_3D_scikit(self): + def test_processed_variable_2D_scikit(self): var = pybamm.Variable("var", domain=["current collector"]) disc = tests.get_2p1d_discretisation_for_testing() @@ -159,7 +159,6 @@ def test_processed_variable_3D_scikit(self): y = disc.mesh["current collector"][0].edges["y"] z = disc.mesh["current collector"][0].edges["z"] var_sol = disc.process_symbol(var) - var_sol.mesh = disc.mesh["current collector"] t_sol = np.linspace(0, 1) u_sol = np.ones(var_sol.shape[0])[:, np.newaxis] * np.linspace(0, 5) @@ -168,7 +167,7 @@ def test_processed_variable_3D_scikit(self): processed_var.entries, np.reshape(u_sol, [len(y), len(z), len(t_sol)]) ) - def test_processed_variable_2Dspace_scikit(self): + def test_processed_variable_2D_fixed_t_scikit(self): var = pybamm.Variable("var", domain=["current collector"]) disc = tests.get_2p1d_discretisation_for_testing() @@ -176,7 +175,6 @@ def test_processed_variable_2Dspace_scikit(self): y = disc.mesh["current collector"][0].edges["y"] z = disc.mesh["current collector"][0].edges["z"] var_sol = disc.process_symbol(var) - var_sol.mesh = disc.mesh["current collector"] t_sol = np.array([0]) u_sol = np.ones(var_sol.shape[0])[:, np.newaxis] @@ -185,7 +183,7 @@ def test_processed_variable_2Dspace_scikit(self): processed_var.entries, np.reshape(u_sol, [len(y), len(z)]) ) - def test_processed_var_1D_interpolation(self): + def test_processed_var_0D_interpolation(self): # without spatial dependence t = pybamm.t y = pybamm.StateVector(slice(0, 1)) @@ -212,7 +210,7 @@ def test_processed_var_1D_interpolation(self): np.testing.assert_array_equal(processed_eqn(2), np.nan) pybamm.set_logging_level("WARNING") - def test_processed_var_2D_interpolation(self): + def test_processed_var_1D_interpolation(self): t = pybamm.t var = pybamm.Variable("var", domain=["negative electrode", "separator"]) x = pybamm.SpatialVariable("x", domain=["negative electrode", "separator"]) @@ -263,7 +261,7 @@ def test_processed_var_2D_interpolation(self): processed_r_n(0, r=np.linspace(0, 1))[:, 0], np.linspace(0, 1) ) - def test_processed_var_3D_interpolation(self): + def test_processed_var_2D_interpolation(self): var = pybamm.Variable( "var", domain=["negative particle"], @@ -326,7 +324,7 @@ def test_processed_var_3D_interpolation(self): processed_var(t_sol, x_sol, r_sol).shape, (10, 35, 50) ) - def test_processed_var_3D_secondary_broadcast(self): + def test_processed_var_2D_secondary_broadcast(self): var = pybamm.Variable("var", domain=["negative particle"]) broad_var = pybamm.SecondaryBroadcast(var, "negative electrode") x = pybamm.SpatialVariable("x", domain=["negative electrode"]) @@ -379,7 +377,7 @@ def test_processed_var_3D_secondary_broadcast(self): processed_var(t_sol, x_sol, r_sol).shape, (10, 35, 50) ) - def test_processed_var_3D_scikit_interpolation(self): + def test_processed_var_2D_scikit_interpolation(self): var = pybamm.Variable("var", domain=["current collector"]) disc = tests.get_2p1d_discretisation_for_testing() @@ -387,7 +385,6 @@ def test_processed_var_3D_scikit_interpolation(self): y_sol = disc.mesh["current collector"][0].edges["y"] z_sol = disc.mesh["current collector"][0].edges["z"] var_sol = disc.process_symbol(var) - var_sol.mesh = disc.mesh["current collector"] t_sol = np.linspace(0, 1) u_sol = np.ones(var_sol.shape[0])[:, np.newaxis] * np.linspace(0, 5) @@ -417,7 +414,7 @@ def test_processed_var_3D_scikit_interpolation(self): # 3 scalars np.testing.assert_array_equal(processed_var(0.2, y=0.2, z=0.2).shape, ()) - def test_processed_var_2Dspace_scikit_interpolation(self): + def test_processed_var_2D_fixed_t_scikit_interpolation(self): var = pybamm.Variable("var", domain=["current collector"]) disc = tests.get_2p1d_discretisation_for_testing() @@ -425,7 +422,6 @@ def test_processed_var_2Dspace_scikit_interpolation(self): y_sol = disc.mesh["current collector"][0].edges["y"] z_sol = disc.mesh["current collector"][0].edges["z"] var_sol = disc.process_symbol(var) - var_sol.mesh = disc.mesh["current collector"] t_sol = np.array([0]) u_sol = np.ones(var_sol.shape[0])[:, np.newaxis] @@ -532,6 +528,22 @@ def test_solution_too_short(self): ): pybamm.ProcessedVariable(var, pybamm.Solution(t_sol, y_sol)) + def test_3D_raises_error(self): + var = pybamm.Variable( + "var", + domain=["negative electrode"], + auxiliary_domains={"secondary": ["current collector"]}, + ) + + disc = tests.get_2p1d_discretisation_for_testing() + disc.set_variable_slices([var]) + var_sol = disc.process_symbol(var) + t_sol = np.array([0, 1, 2]) + u_sol = np.ones(var_sol.shape[0] * 3)[:, np.newaxis] + + with self.assertRaisesRegex(NotImplementedError, "Shape not recognized"): + pybamm.ProcessedVariable(var_sol, pybamm.Solution(t_sol, u_sol)) + if __name__ == "__main__": print("Add -v for more debug output") diff --git a/tests/unit/test_quick_plot.py b/tests/unit/test_quick_plot.py index 2abe645568..a9b0779f76 100644 --- a/tests/unit/test_quick_plot.py +++ b/tests/unit/test_quick_plot.py @@ -37,6 +37,12 @@ def test_simple_ode_model(self): "c broadcasted positive electrode": pybamm.PrimaryBroadcast( c, "positive particle" ), + "x [m]": pybamm.standard_spatial_vars.x, + "x": pybamm.standard_spatial_vars.x, + "r_n [m]": pybamm.standard_spatial_vars.r_n, + "r_n": pybamm.standard_spatial_vars.r_n, + "r_p [m]": pybamm.standard_spatial_vars.r_p, + "r_p": pybamm.standard_spatial_vars.r_p, } # ODEs only (don't use jacobian) @@ -53,26 +59,35 @@ def test_simple_ode_model(self): solver = model.default_solver t_eval = np.linspace(0, 2, 100) solution = solver.solve(model, t_eval) - quick_plot = pybamm.QuickPlot(solution) + quick_plot = pybamm.QuickPlot( + solution, + [ + "a", + "b broadcasted", + "c broadcasted", + "b broadcasted negative electrode", + "c broadcasted positive electrode", + ], + ) quick_plot.plot(0) # update the axis new_axis = [0, 0.5, 0, 1] - quick_plot.axis.update({("a",): new_axis}) - self.assertEqual(quick_plot.axis[("a",)], new_axis) + quick_plot.axis_limits.update({("a",): new_axis}) + self.assertEqual(quick_plot.axis_limits[("a",)], new_axis) # and now reset them quick_plot.reset_axis() - self.assertNotEqual(quick_plot.axis[("a",)], new_axis) + self.assertNotEqual(quick_plot.axis_limits[("a",)], new_axis) # check dynamic plot loads quick_plot.dynamic_plot(testing=True) - quick_plot.update(0.01) + quick_plot.slider_update(0.01) # Test with different output variables quick_plot = pybamm.QuickPlot(solution, ["b broadcasted"]) - self.assertEqual(len(quick_plot.axis), 1) + self.assertEqual(len(quick_plot.axis_limits), 1) quick_plot.plot(0) quick_plot = pybamm.QuickPlot( @@ -85,39 +100,166 @@ def test_simple_ode_model(self): "c broadcasted positive electrode", ], ) - self.assertEqual(len(quick_plot.axis), 5) + self.assertEqual(len(quick_plot.axis_limits), 5) quick_plot.plot(0) # update the axis new_axis = [0, 0.5, 0, 1] var_key = ("c broadcasted",) - quick_plot.axis.update({var_key: new_axis}) - self.assertEqual(quick_plot.axis[var_key], new_axis) + quick_plot.axis_limits.update({var_key: new_axis}) + self.assertEqual(quick_plot.axis_limits[var_key], new_axis) # and now reset them quick_plot.reset_axis() - self.assertNotEqual(quick_plot.axis[var_key], new_axis) + self.assertNotEqual(quick_plot.axis_limits[var_key], new_axis) # check dynamic plot loads quick_plot.dynamic_plot(testing=True) - quick_plot.update(0.01) + quick_plot.slider_update(0.01) # Test longer name model.variables["Variable with a very long name"] = model.variables["a"] - quick_plot = pybamm.QuickPlot(solution) + quick_plot = pybamm.QuickPlot(solution, ["Variable with a very long name"]) quick_plot.plot(0) - # Test errors - with self.assertRaisesRegex(ValueError, "mismatching variable domains"): - pybamm.QuickPlot(solution, [["a", "b broadcasted"]]) - model.variables["3D variable"] = disc.process_symbol( + # Test different inputs + quick_plot = pybamm.QuickPlot( + [solution, solution], + ["a"], + colors=["r", "g", "b"], + linestyles=["-", "--"], + figsize=(1, 2), + labels=["sol 1", "sol 2"], + ) + self.assertEqual(quick_plot.colors, ["r", "g", "b"]) + self.assertEqual(quick_plot.linestyles, ["-", "--"]) + self.assertEqual(quick_plot.figsize, (1, 2)) + self.assertEqual(quick_plot.labels, ["sol 1", "sol 2"]) + + # Test different time units + quick_plot = pybamm.QuickPlot(solution, ["a"]) + self.assertEqual(quick_plot.time_scale, 1) + quick_plot = pybamm.QuickPlot(solution, ["a"], time_unit="seconds") + self.assertEqual(quick_plot.time_scale, 1) + quick_plot = pybamm.QuickPlot(solution, ["a"], time_unit="minutes") + self.assertEqual(quick_plot.time_scale, 1 / 60) + quick_plot = pybamm.QuickPlot(solution, ["a"], time_unit="hours") + self.assertEqual(quick_plot.time_scale, 1 / 3600) + with self.assertRaisesRegex(ValueError, "time unit"): + pybamm.QuickPlot(solution, ["a"], time_unit="bad unit") + # long solution defaults to hours instead of seconds + solution_long = solver.solve(model, np.linspace(0, 1e5)) + quick_plot = pybamm.QuickPlot(solution_long, ["a"]) + self.assertEqual(quick_plot.time_scale, 1 / 3600) + + # Test different spatial units + quick_plot = pybamm.QuickPlot(solution, ["a"]) + self.assertEqual(quick_plot.spatial_unit, "$\mu m$") + quick_plot = pybamm.QuickPlot(solution, ["a"], spatial_unit="m") + self.assertEqual(quick_plot.spatial_unit, "m") + quick_plot = pybamm.QuickPlot(solution, ["a"], spatial_unit="mm") + self.assertEqual(quick_plot.spatial_unit, "mm") + quick_plot = pybamm.QuickPlot(solution, ["a"], spatial_unit="um") + self.assertEqual(quick_plot.spatial_unit, "$\mu m$") + with self.assertRaisesRegex(ValueError, "spatial unit"): + pybamm.QuickPlot(solution, ["a"], spatial_unit="bad unit") + + # Test 2D variables + model.variables["2D variable"] = disc.process_symbol( pybamm.FullBroadcast( 1, "negative particle", {"secondary": "negative electrode"} ) ) - with self.assertRaisesRegex(NotImplementedError, "cannot plot 3D variables"): - pybamm.QuickPlot(solution, ["3D variable"]) + quick_plot = pybamm.QuickPlot(solution, ["2D variable"]) + quick_plot.plot(0) + quick_plot.dynamic_plot(testing=True) + quick_plot.slider_update(0.01) + + with self.assertRaisesRegex(NotImplementedError, "Cannot plot 2D variables"): + pybamm.QuickPlot([solution, solution], ["2D variable"]) + + # Test different variable limits + quick_plot = pybamm.QuickPlot( + solution, ["a", ["c broadcasted", "c broadcasted"]], variable_limits="tight" + ) + self.assertEqual(quick_plot.axis_limits[("a",)][2:], [None, None]) + self.assertEqual( + quick_plot.axis_limits[("c broadcasted", "c broadcasted")][2:], [None, None] + ) + quick_plot.plot(0) + quick_plot.slider_update(1) + + quick_plot = pybamm.QuickPlot( + solution, ["2D variable"], variable_limits="tight" + ) + self.assertEqual(quick_plot.variable_limits[("2D variable",)], (None, None)) + quick_plot.plot(0) + quick_plot.slider_update(1) + + quick_plot = pybamm.QuickPlot( + solution, + ["a", ["c broadcasted", "c broadcasted"]], + variable_limits={"a": [1, 2], ("c broadcasted", "c broadcasted"): [3, 4]}, + ) + self.assertEqual(quick_plot.axis_limits[("a",)][2:], [1, 2]) + self.assertEqual( + quick_plot.axis_limits[("c broadcasted", "c broadcasted")][2:], [3, 4] + ) + quick_plot.plot(0) + quick_plot.slider_update(1) + + quick_plot = pybamm.QuickPlot( + solution, ["a", "b broadcasted"], variable_limits={"a": "tight"} + ) + self.assertEqual(quick_plot.axis_limits[("a",)][2:], [None, None]) + self.assertNotEqual( + quick_plot.axis_limits[("b broadcasted",)][2:], [None, None] + ) + quick_plot.plot(0) + quick_plot.slider_update(1) + + with self.assertRaisesRegex( + TypeError, "variable_limits must be 'fixed', 'tight', or a dict" + ): + pybamm.QuickPlot( + solution, ["a", "b broadcasted"], variable_limits="bad variable limits" + ) + + # Test errors + with self.assertRaisesRegex(ValueError, "Mismatching variable domains"): + pybamm.QuickPlot(solution, [["a", "b broadcasted"]]) + with self.assertRaisesRegex(ValueError, "labels"): + pybamm.QuickPlot( + [solution, solution], ["a"], labels=["sol 1", "sol 2", "sol 3"] + ) + + # Remove 'x [m]' from the variables and make sure a key error is raise + del solution.model.variables["x [m]"] + with self.assertRaisesRegex( + KeyError, "Can't find spatial scale for 'negative electrode'", + ): + pybamm.QuickPlot(solution, ["b broadcasted"]) + + # No variable can be NaN + model.variables["NaN variable"] = disc.process_symbol(pybamm.Scalar(np.nan)) + with self.assertRaisesRegex( + ValueError, "All-NaN variable 'NaN variable' provided" + ): + pybamm.QuickPlot(solution, ["NaN variable"]) + + def test_spm_simulation(self): + # SPM + model = pybamm.lithium_ion.SPM() + sim = pybamm.Simulation(model) + + t_eval = np.linspace(0, 10, 2) + sim.solve(t_eval) + + # mixed simulation and solution input + # solution should be extracted from the simulation + quick_plot = pybamm.QuickPlot([sim, sim.solution]) + quick_plot.plot(0) def test_loqs_spm_base(self): t_eval = np.linspace(0, 10, 2) @@ -142,11 +284,53 @@ def test_loqs_spm_base(self): output_variables = [ "X-averaged negative particle concentration [mol.m-3]", "X-averaged positive particle concentration [mol.m-3]", + "Negative particle concentration [mol.m-3]", + "Positive particle concentration [mol.m-3]", ] pybamm.QuickPlot(solution, output_variables) + def test_plot_2plus1D_spm(self): + spm = pybamm.lithium_ion.SPM( + {"current collector": "potential pair", "dimensionality": 2} + ) + geometry = spm.default_geometry + param = spm.default_parameter_values + param.process_model(spm) + param.process_geometry(geometry) + var = pybamm.standard_spatial_vars + var_pts = { + var.x_n: 5, + var.x_s: 5, + var.x_p: 5, + var.r_n: 5, + var.r_p: 5, + var.y: 5, + var.z: 5, + } + mesh = pybamm.Mesh(geometry, spm.default_submesh_types, var_pts) + disc_spm = pybamm.Discretisation(mesh, spm.default_spatial_methods) + disc_spm.process_model(spm) + t_eval = np.linspace(0, 3600, 100) + solution_spm = spm.default_solver.solve(spm, t_eval) + + quick_plot = pybamm.QuickPlot( + solution_spm, + [ + "Negative current collector potential [V]", + "Positive current collector potential [V]", + "Terminal voltage [V]", + ], + ) + quick_plot.dynamic_plot(testing=True) + quick_plot.slider_update(1) + + with self.assertRaisesRegex(NotImplementedError, "Shape not recognized for"): + pybamm.QuickPlot( + solution_spm, ["Negative particle concentration [mol.m-3]"], + ) + def test_failure(self): - with self.assertRaisesRegex(TypeError, "'solutions' must be"): + with self.assertRaisesRegex(TypeError, "solutions must be"): pybamm.QuickPlot(1)