Skip to content

Commit

Permalink
#871 improve 2d
Browse files Browse the repository at this point in the history
  • Loading branch information
valentinsulzer committed Mar 13, 2020
1 parent 6bac2d7 commit 0b65f65
Show file tree
Hide file tree
Showing 5 changed files with 72 additions and 62 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## Features

- 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))
- 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))
- Added a search feature to `FuzzyDict` ([#875](https://github.com/pybamm-team/PyBaMM/pull/875))
Expand Down
8 changes: 4 additions & 4 deletions examples/scripts/compare_lithium_ion_3D.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -56,6 +56,6 @@
solutions[i] = solution

# plot
output_variables = ["Terminal voltage [V]"]
output_variables = ["Terminal voltage [V]", "Positive current collector potential"]
plot = pybamm.QuickPlot(solutions, output_variables)
plot.dynamic_plot()
26 changes: 10 additions & 16 deletions pybamm/processed_variable.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
"""
Expand Down Expand Up @@ -67,10 +65,8 @@ def __init__(self, base_variable, solution, known_evals=None):
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)
Expand Down Expand Up @@ -210,10 +206,8 @@ def initialise_2D(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)
Expand Down Expand Up @@ -330,25 +324,25 @@ def __call__(self, t=None, x=None, r=None, y=None, z=None, warn=True):
if self.dimensions == 0:
out = self._interpolation_function(t)
elif self.dimensions == 1:
out = self.call_2D(t, x, r, z)
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_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):
Expand Down
91 changes: 49 additions & 42 deletions pybamm/quick_plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,24 +144,24 @@ def __init__(
variables = models[0].variables
# empty spatial scales, will raise error later if can't find a particular one
self.spatial_scales = {}
if "x [m]" and "x" in variables:
if "x [m]" in variables and "x" in variables:
x_scale = (variables["x [m]"] / variables["x"]).evaluate()[
-1
] * spatial_factor
self.spatial_scales.update({dom: x_scale for dom in variables["x"].domain})
if "y [m]" and "y" in variables:
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]" and "z" in variables:
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]" and "r_n" in variables:
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] * spatial_factor
if "r_p [m]" and "r_p" in variables:
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] * spatial_factor
Expand Down Expand Up @@ -230,6 +230,7 @@ def set_output_variables(self, output_variables, solutions):
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 = {}
Expand Down Expand Up @@ -279,6 +280,7 @@ def set_output_variables(self, output_variables, solutions):
key[0], domain, key[idx], variable.domain
)
)
self.spatial_variable_dict[key] = {}

# Set the x variable (i.e. "x" or "r" for any one-dimensional variables)
if first_variable.dimensions == 1:
Expand Down Expand Up @@ -323,6 +325,10 @@ def set_output_variables(self, output_variables, solutions):
self.second_dimensional_spatial_variable[key] = (
second_spatial_var_value * second_spatial_scale
)
if first_spatial_var_name == "r" and second_spatial_var_name == "x":
self.is_x_r[key] = True
else:
self.is_x_r[key] = False

# Store variables and subplot position
self.variables[key] = variables
Expand Down Expand Up @@ -374,26 +380,27 @@ def reset_axis(self):
if variable_lists[0][0].dimensions == 0:
x_min = self.min_t
x_max = self.max_t
spatial_vars = {}
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]
# Read dictionary of spatial variables
spatial_vars = self.spatial_variable_dict[key]
elif variable_lists[0][0].dimensions == 2:
# First spatial variable
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]

# Read dictionary of spatial variables
spatial_vars = self.spatial_variable_dict[key]
# 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:
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[key] = [x_min, x_max, y_min, y_max]

# Get min and max variable values
spatial_vars = self.spatial_variable_dict[key]
var_min = np.min(
[
ax_min(var(self.ts[i], **spatial_vars, warn=False))
Expand Down Expand Up @@ -513,31 +520,31 @@ def plot(self, t):
variable_handles.append(self.plots[key][0][j])
solution_handles.append(self.plots[key][i][0])
elif variable_lists[0][0].dimensions == 2:
# 2D plot: plot as a function of x and y at time t
# Read dictionary of spatial variables
# note "first" and "second" variables are not in the 'right' order so
# that, in the particle case, x is on x-axis and r is on y-axis
# TODO: make an if statement for this
spatial_vars = self.spatial_variable_dict[key]
x_name = list(spatial_vars.keys())[1][0]
y_name = list(spatial_vars.keys())[0][0]
# 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
)
# there can only be one entry in the variable list
variable = variable_lists[0][0]
vmin, vmax = self.var_limits[key]
ax.contourf(
self.second_dimensional_spatial_variable[key],
self.first_dimensional_spatial_variable[key],
variable(t, **spatial_vars, warn=False),
levels=100,
vmin=vmin,
vmax=vmax,
)
ax.contourf(x, y, var, levels=100, vmin=vmin, vmax=vmax)
self.fig.colorbar(
cm.ScalarMappable(colors.Normalize(vmin=vmin, vmax=vmax)), ax=ax
)
Expand Down Expand Up @@ -604,11 +611,10 @@ def dynamic_plot(self, testing=False, step=None):
if not testing: # pragma: no cover
plt.show()

def slider_update(self, val):
def slider_update(self, t):
"""
Update the plot in self.plot() with values at new time
"""
t = self.slider.val
t_dimensionless = t / self.time_scale
for k, (key, plot) in enumerate(self.plots.items()):
if self.variables[key][0][0].dimensions == 0:
Expand All @@ -631,13 +637,14 @@ def slider_update(self, val):
# there can only be one entry in the variable list
variable = self.variables[key][0][0]
vmin, vmax = self.var_limits[key]
ax.contourf(
self.second_dimensional_spatial_variable[key],
self.first_dimensional_spatial_variable[key],
variable(t_dimensionless, **spatial_vars, warn=False),
levels=100,
vmin=vmin,
vmax=vmax,
)
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)

self.fig.canvas.draw_idle()
7 changes: 7 additions & 0 deletions tests/unit/test_quick_plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,13 @@ def test_simple_ode_model(self):
[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(
Expand Down

0 comments on commit 0b65f65

Please sign in to comment.