Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue 992 plot arrays #1008

Merged
merged 6 commits into from
May 26, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .flake8
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ exclude=
share,
pyvenv.cfg,
third-party,
sundials-5.0.0,
KLU_module_deps,
ignore=
# False positive for white space before ':' on list slice
# black should format these correctly
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## Features

- Added `BackwardIndefiniteIntegral` symbol ([#1014](https://github.com/pybamm-team/PyBaMM/pull/1014))
- Added `plot` and `plot2D` to enable easy plotting of `pybamm.Array` objects ([#1008](https://github.com/pybamm-team/PyBaMM/pull/1008))
- Added SEI film resistance as an option ([#994](https://github.com/pybamm-team/PyBaMM/pull/994))
- Added tab, edge, and surface cooling ([#965](https://github.com/pybamm-team/PyBaMM/pull/965))
- Added functionality to solver to automatically discretise a 0D model ([#947](https://github.com/pybamm-team/PyBaMM/pull/947))
Expand Down
2 changes: 1 addition & 1 deletion docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ API documentation
source/solvers/index
source/experiments/index
source/simulation
source/quick_plot
source/plotting/index
source/util
source/citations
source/parameters_cli
Expand Down
9 changes: 9 additions & 0 deletions docs/source/expression_tree/array.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Array
=====

.. autoclass:: pybamm.Array
:members:

.. autofunction:: pybamm.linspace

.. autofunction:: pybamm.meshgrid
1 change: 1 addition & 0 deletions docs/source/expression_tree/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Expression Tree
variable
independent_variable
scalar
array
matrix
vector
state_vector
Expand Down
4 changes: 4 additions & 0 deletions docs/source/plotting/dynamic_plot.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Dynamic Plot
============

.. autofunction:: pybamm.dynamic_plot
9 changes: 9 additions & 0 deletions docs/source/plotting/index.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Plotting
========

.. toctree::

quick_plot
dynamic_plot
plot
plot_2D
4 changes: 4 additions & 0 deletions docs/source/plotting/plot.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Plot
====

.. autofunction:: pybamm.plot
4 changes: 4 additions & 0 deletions docs/source/plotting/plot_2D.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Plot 2D
=======

.. autofunction:: pybamm.plot2D
File renamed without changes.
12 changes: 9 additions & 3 deletions pybamm/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ def version(formatted=False):
from .expression_tree.symbol import *
from .expression_tree.binary_operators import *
from .expression_tree.concatenations import *
from .expression_tree.array import Array
from .expression_tree.array import Array, linspace, meshgrid
from .expression_tree.matrix import Matrix
from .expression_tree.unary_operators import *
from .expression_tree.functions import *
Expand Down Expand Up @@ -221,10 +221,16 @@ def version(formatted=False):
from . import experiments

#
# other
# Plotting
#
from .quick_plot import QuickPlot, dynamic_plot, ax_min, ax_max
from .plotting.quick_plot import QuickPlot
from .plotting.plot import plot
from .plotting.plot2D import plot2D
from .plotting.dynamic_plot import dynamic_plot

#
# Simulation
#
from .simulation import Simulation, load_sim, is_notebook

#
Expand Down
21 changes: 21 additions & 0 deletions pybamm/expression_tree/array.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,3 +101,24 @@ def new_copy(self):
def _base_evaluate(self, t=None, y=None, y_dot=None, inputs=None):
""" See :meth:`pybamm.Symbol._base_evaluate()`. """
return self._entries


def linspace(start, stop, num=50, **kwargs):
"""
Creates a linearly spaced array by calling `numpy.linspace` with keyword
arguments 'kwargs'. For a list of 'kwargs' see the
`numpy linspace documentation <https://tinyurl.com/yc4ne47x>`_
"""
return pybamm.Array(np.linspace(start, stop, num, **kwargs))


def meshgrid(x, y, **kwargs):
"""
Return coordinate matrices as from coordinate vectors by calling
`numpy.meshgrid` with keyword arguments 'kwargs'. For a list of 'kwargs'
see the `numpy meshgrid documentation <https://tinyurl.com/y8azewrj>`_
"""
[X, Y] = np.meshgrid(x.entries, y.entries)
X = pybamm.Array(X)
Y = pybamm.Array(Y)
return X, Y
Empty file added pybamm/plotting/__init__.py
Empty file.
22 changes: 22 additions & 0 deletions pybamm/plotting/dynamic_plot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#
# Method for creating a dynamic plot
#
import pybamm


def dynamic_plot(*args, **kwargs):
"""
Creates a :class:`pybamm.QuickPlot` object (with arguments 'args' and keyword
arguments 'kwargs') and then calls :meth:`pybamm.QuickPlot.dynamic_plot`.
The key-word argument 'testing' is passed to the 'dynamic_plot' method, not the
`QuickPlot` class.

Returns
-------
plot : :class:`pybamm.QuickPlot`
The 'QuickPlot' object that was created
"""
kwargs_for_class = {k: v for k, v in kwargs.items() if k != "testing"}
plot = pybamm.QuickPlot(*args, **kwargs_for_class)
plot.dynamic_plot(kwargs.get("testing", False))
return plot
44 changes: 44 additions & 0 deletions pybamm/plotting/plot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#
# Method for creating a 1D plot of pybamm arrays
#
import pybamm
from .quick_plot import ax_min, ax_max


def plot(x, y, xlabel=None, ylabel=None, title=None, testing=False, **kwargs):
"""
Generate a simple 1D plot. Calls `matplotlib.pyplot.plot` with keyword
arguments 'kwargs'. For a list of 'kwargs' see the
`matplotlib plot documentation <https://tinyurl.com/ycblw9bx>`_

Parameters
----------
x : :class:`pybamm.Array`
The array to plot on the x axis
y : :class:`pybamm.Array`
The array to plot on the y axis
xlabel : str, optional
The label for the x axis
ylabel : str, optional
The label for the y axis
testing : bool, optional
Whether to actually make the plot (turned off for unit tests)

"""
import matplotlib.pyplot as plt

if not isinstance(x, pybamm.Array):
raise TypeError("x must be 'pybamm.Array'")
if not isinstance(y, pybamm.Array):
raise TypeError("y must be 'pybamm.Array'")

plt.plot(x.entries, y.entries, **kwargs)
plt.ylim([ax_min(y.entries), ax_max(y.entries)])
plt.xlabel(xlabel)
plt.ylabel(ylabel)
plt.title(title)

if not testing: # pragma: no cover
plt.show()

return
66 changes: 66 additions & 0 deletions pybamm/plotting/plot2D.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#
# Method for creating a filled contour plot of pybamm arrays
#
import pybamm
from .quick_plot import ax_min, ax_max


def plot2D(x, y, z, xlabel=None, ylabel=None, title=None, testing=False, **kwargs):
"""
Generate a simple 2D plot. Calls `matplotlib.pyplot.contourf` with keyword
arguments 'kwargs'. For a list of 'kwargs' see the
`matplotlib contourf documentation <https://tinyurl.com/y8mnadtn>`_

Parameters
----------
x : :class:`pybamm.Array`
The array to plot on the x axis. Can be of shape (M, N) or (N, 1)
y : :class:`pybamm.Array`
The array to plot on the y axis. Can be of shape (M, N) or (M, 1)
z : :class:`pybamm.Array`
The array to plot on the z axis. Is of shape (M, N)
xlabel : str, optional
The label for the x axis
ylabel : str, optional
The label for the y axis
title : str, optional
The title for the plot
testing : bool, optional
Whether to actually make the plot (turned off for unit tests)

"""
import matplotlib.pyplot as plt

if not isinstance(x, pybamm.Array):
raise TypeError("x must be 'pybamm.Array'")
if not isinstance(y, pybamm.Array):
raise TypeError("y must be 'pybamm.Array'")
if not isinstance(z, pybamm.Array):
raise TypeError("z must be 'pybamm.Array'")

# Get correct entries of x and y depending on shape
if x.shape == y.shape == z.shape:
x_entries = x.entries
y_entries = y.entries
else:
x_entries = x.entries[:, 0]
y_entries = y.entries[:, 0]

plt.contourf(
x_entries,
y_entries,
z.entries,
vmin=ax_min(z.entries),
vmax=ax_max(z.entries),
cmap="coolwarm",
**kwargs
)
plt.xlabel(xlabel)
plt.ylabel(ylabel)
plt.title(title)
plt.colorbar()

if not testing: # pragma: no cover
plt.show()

return
18 changes: 0 additions & 18 deletions pybamm/quick_plot.py → pybamm/plotting/quick_plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,24 +44,6 @@ def split_long_string(title, max_words=4):
return first_line + "\n" + second_line


def dynamic_plot(*args, **kwargs):
"""
Creates a :class:`pybamm.QuickPlot` object (with arguments 'args' and keyword
arguments 'kwargs') and then calls :meth:`pybamm.QuickPlot.dynamic_plot`.
The key-word argument 'testing' is passed to the 'dynamic_plot' method, not the
`QuickPlot' class.

Returns
-------
plot : :class:`pybamm.QuickPlot`
The 'QuickPlot' object that was created
"""
kwargs_for_class = {k: v for k, v in kwargs.items() if k != "testing"}
plot = pybamm.QuickPlot(*args, **kwargs_for_class)
plot.dynamic_plot(kwargs.get("testing", False))
return plot


class QuickPlot(object):
"""
Generates a quick plot of a subset of key outputs of the model so that the model
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ def test_compare_outputs_thermal(self):
solutions = []
t_eval = np.linspace(0, 3600, 100)
for model in models:
solution = pybamm.CasadiSolver(dt_max=0.01).solve(model, t_eval)
solution = pybamm.CasadiSolver().solve(model, t_eval)
solutions.append(solution)

# compare outputs
Expand Down
38 changes: 38 additions & 0 deletions tests/unit/test_expression_tree/test_array.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#
# Tests for the Array class
#
import pybamm
import numpy as np

import unittest


class TestArray(unittest.TestCase):
def test_name(self):
arr = pybamm.Array(np.array([1, 2, 3]))
self.assertEqual(arr.name, "Array of shape (3, 1)")

def test_linspace(self):
x = np.linspace(0, 1, 100)[:, np.newaxis]
y = pybamm.linspace(0, 1, 100)
np.testing.assert_array_equal(x, y.entries)

def test_meshgrid(self):
a = np.linspace(0, 5)
b = np.linspace(0, 3)
A, B = np.meshgrid(a, b)
c = pybamm.linspace(0, 5)
d = pybamm.linspace(0, 3)
C, D = pybamm.meshgrid(c, d)
np.testing.assert_array_equal(A, C.entries)
np.testing.assert_array_equal(B, D.entries)


if __name__ == "__main__":
print("Add -v for more debug output")
import sys

if "-v" in sys.argv:
debug = True
pybamm.settings.debug_mode = True
unittest.main()
6 changes: 0 additions & 6 deletions tests/unit/test_expression_tree/test_matrix.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,6 @@ def test_matrix_operations(self):
)


class TestArray(unittest.TestCase):
def test_name(self):
arr = pybamm.Array(np.array([1, 2, 3]))
self.assertEqual(arr.name, "Array of shape (3, 1)")


if __name__ == "__main__":
print("Add -v for more debug output")
import sys
Expand Down
47 changes: 47 additions & 0 deletions tests/unit/test_plot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import pybamm
import unittest
import numpy as np


class TestPlot(unittest.TestCase):
def test_plot(self):
x = pybamm.Array(np.array([0, 3, 10]))
y = pybamm.Array(np.array([6, 16, 78]))
pybamm.plot(x, y, xlabel="x", ylabel="y", title="title", testing=True)

def test_plot_fail(self):
x = pybamm.Array(np.array([0]))
with self.assertRaisesRegex(TypeError, "x must be 'pybamm.Array'"):
pybamm.plot("bad", x)
with self.assertRaisesRegex(TypeError, "y must be 'pybamm.Array'"):
pybamm.plot(x, "bad")

def test_plot2D(self):
x = pybamm.Array(np.array([0, 3, 10]))
y = pybamm.Array(np.array([6, 16, 78]))
X, Y = pybamm.meshgrid(x, y)

# plot with array directly
pybamm.plot2D(x, y, Y, xlabel="x", ylabel="y", title="title", testing=True)

# plot with meshgrid
pybamm.plot2D(X, Y, Y, xlabel="x", ylabel="y", title="title", testing=True)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could plot2D automatically do meshgrid(x,y) if they aren't given in meshgrid form?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

turns out contourf handles this provided they are the correct shape. have added some lines to account for this


def test_plot2D_fail(self):
x = pybamm.Array(np.array([0]))
with self.assertRaisesRegex(TypeError, "x must be 'pybamm.Array'"):
pybamm.plot2D("bad", x, x)
with self.assertRaisesRegex(TypeError, "y must be 'pybamm.Array'"):
pybamm.plot2D(x, "bad", x)
with self.assertRaisesRegex(TypeError, "z must be 'pybamm.Array'"):
pybamm.plot2D(x, x, "bad")


if __name__ == "__main__":
print("Add -v for more debug output")
import sys

if "-v" in sys.argv:
debug = True
pybamm.settings.debug_mode = True
unittest.main()