Skip to content

Commit

Permalink
Merge pull request #728 from pybamm-team/issue-692-external-temperatu…
Browse files Browse the repository at this point in the history
…re-matrix

Issue 692 external temperature matrix
  • Loading branch information
Scottmar93 authored Nov 20, 2019
2 parents 68539d3 + dbdd10c commit 20a0c23
Show file tree
Hide file tree
Showing 23 changed files with 951 additions and 251 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@

## Features

- Generalized importing of external variables ([#728](https://github.com/pybamm-team/PyBaMM/pull/728))
- Separated active and inactive material volume fractions ([#726](https://github.com/pybamm-team/PyBaMM/pull/726))
- Added submodels for tortuosity ([#726](https://github.com/pybamm-team/PyBaMM/pull/726))
- Simplified the interface for setting current functions ([#723](https://github.com/pybamm-team/PyBaMM/pull/723))
- Added Heaviside operator ([#723](https://github.com/pybamm-team/PyBaMM/pull/723))
- New extrapolation methods ([#707](https://github.com/pybamm-team/PyBaMM/pull/707))
- Added some "Getting Started" documentation ([#703](https://github.com/pybamm-team/PyBaMM/pull/703))
- Allow abs tolerance to be set by variable for IDA KLU solver ([#700](https://github.com/pybamm-team/PyBaMM/pull/700))
- Added Simulation class ([#693](https://github.com/pybamm-team/PyBaMM/pull/693)) with load/save functionality ([#732](https://github.com/pybamm-team/PyBaMM/pull/732))
- Added interface to CasADi solver ([#687](https://github.com/pybamm-team/PyBaMM/pull/687), [#691](https://github.com/pybamm-team/PyBaMM/pull/691), [#714](https://github.com/pybamm-team/PyBaMM/pull/714)). This makes the SUNDIALS DAE solvers (Scikits and KLU) truly optional (though IDA KLU is recommended for solving the DFN).
- Added option to use CasADi's Algorithmic Differentiation framework to calculate Jacobians ([#687](https://github.com/pybamm-team/PyBaMM/pull/687))
Expand Down
1 change: 1 addition & 0 deletions pybamm/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ def version(formatted=False):
ModelWarning,
UndefinedOperationError,
GeometryError,
InputError,
)

# Operations
Expand Down
66 changes: 63 additions & 3 deletions pybamm/discretisations/discretisation.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ def __init__(self, mesh=None, spatial_methods=None):
self.bcs = {}
self.y_slices = {}
self._discretised_symbols = {}
self.external_variables = []

@property
def mesh(self):
Expand Down Expand Up @@ -119,12 +120,20 @@ def process_model(self, model, inplace=True):

# Prepare discretisation
# set variables (we require the full variable not just id)
variables = list(model.rhs.keys()) + list(model.algebraic.keys())
variables = (
list(model.rhs.keys())
+ list(model.algebraic.keys())
+ model.external_variables
)

# Set the y split for variables
pybamm.logger.info("Set variable slices for {}".format(model.name))
self.set_variable_slices(variables)

# now add extrapolated external variables to the boundary conditions
# if required by the spatial method
self._preprocess_external_variables(model)

# set boundary conditions (only need key ids for boundary_conditions)
pybamm.logger.info("Discretise boundary conditions for {}".format(model.name))
self.bcs = self.process_boundary_conditions(model)
Expand All @@ -142,7 +151,28 @@ def process_model(self, model, inplace=True):

model_disc.bcs = self.bcs

# Process initial condtions
self.external_variables = model.external_variables
# find where external variables begin in state vector
# we always append external variables to the end, so
# it is sufficient to only know the starting location
start_vals = []
for var in self.external_variables:
if isinstance(var, pybamm.Concatenation):
for child in var.children:
start_vals += [self.y_slices[child.id][0].start]
elif isinstance(var, pybamm.Variable):
start_vals += [self.y_slices[var.id][0].start]

# attach properties of the state vector so that it
# can be divided correctly during the solving stage
model_disc.external_variables = model.external_variables
model_disc.y_length = self.y_length
model_disc.y_slices = self.y_slices
if start_vals:
model_disc.external_start = min(start_vals)
else:
model_disc.external_start = self.y_length

pybamm.logger.info("Discretise initial conditions for {}".format(model.name))
ics, concat_ics = self.process_initial_conditions(model)
model_disc.initial_conditions = ics
Expand Down Expand Up @@ -223,10 +253,33 @@ def set_variable_slices(self, variables):
start = end

self.y_slices = y_slices
self.y_length = end

# reset discretised_symbols
self._discretised_symbols = {}

def _preprocess_external_variables(self, model):
"""
A method to preprocess external variables so that they are
compatible with the spatial method. For example, in finite
volume, the user will supply a vector of values valid on the
cell centres. However, for model processing, we also require
the boundary edge fluxes. Therefore, we extrapolate and add
the boundary fluxes to the boundary conditions, which are
employed in generating the grad and div matrices.
The processing is delegated to spatial methods as
the preprocessing required for finite volume and finite
element will be different.
"""

for var in model.external_variables:
if var.domain != []:
new_bcs = self.spatial_methods[
var.domain[0]
].preprocess_external_variables(var)

model.boundary_conditions.update(new_bcs)

def set_internal_boundary_conditions(self, model):
"""
A method to set the internal boundary conditions for the submodel.
Expand Down Expand Up @@ -814,7 +867,14 @@ def _concatenate_in_order(self, var_eqn_dict, check_complete=False, sparse=False
if check_complete:
# Check keys from the given var_eqn_dict against self.y_slices
ids = {v.id for v in unpacked_variables}
if ids != self.y_slices.keys():
external_id = {v.id for v in self.external_variables}
for var in self.external_variables:
child_ids = {child.id for child in var.children}
external_id = external_id.union(child_ids)
y_slices_with_external_removed = set(self.y_slices.keys()).difference(
external_id
)
if ids != y_slices_with_external_removed:
given_variable_names = [v.name for v in var_eqn_dict.keys()]
raise pybamm.ModelError(
"Initial conditions are insufficient. Only "
Expand Down
8 changes: 8 additions & 0 deletions pybamm/expression_tree/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,11 @@ class UndefinedOperationError(Exception):
"""

pass


class InputError(Exception):
"""
An external variable has been input incorrectly into PyBaMM
"""

pass
27 changes: 23 additions & 4 deletions pybamm/models/base_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ def __init__(self, name="Unnamed model"):
self._mass_matrix = None
self._jacobian = None
self._jacobian_algebraic = None
self.external_variables = []

# Default behaviour is to use the jacobian and simplify
self.use_jacobian = True
Expand Down Expand Up @@ -400,7 +401,17 @@ def check_well_determined(self, post_discretisation):
# If any variables in the equations don't appear in the keys then the model is
# underdetermined
vars_in_keys = vars_in_rhs_keys.union(vars_in_algebraic_keys)
extra_variables = vars_in_eqns.difference(vars_in_keys)
extra_variables_in_equations = vars_in_eqns.difference(vars_in_keys)

# get ids of external variables
external_ids = {var.id for var in self.external_variables}
for var in self.external_variables:
if isinstance(var, pybamm.Concatenation):
child_ids = {child.id for child in var.children}
external_ids = external_ids.union(child_ids)

extra_variables = extra_variables_in_equations.difference(external_ids)

if extra_variables:
raise pybamm.ModelError("model is underdetermined (too many variables)")

Expand Down Expand Up @@ -491,19 +502,27 @@ def check_variables(self):
{x.id: x for x in eqn.pre_order() if isinstance(x, pybamm.Variable)}
)
var_ids_in_keys = set()
for var in {**self.rhs, **self.algebraic}.keys():

model_and_external_variables = (
list(self.rhs.keys())
+ list(self.algebraic.keys())
+ self.external_variables
)

for var in model_and_external_variables:
if isinstance(var, pybamm.Variable):
var_ids_in_keys.add(var.id)
# Key can be a concatenation
elif isinstance(var, pybamm.Concatenation):
var_ids_in_keys.update([child.id for child in var.children])

for var_id, var in all_vars.items():
if var_id not in var_ids_in_keys:
raise pybamm.ModelError(
"""
No key set for variable '{}'. Make sure it is included in either
model.rhs or model.algebraic in an unmodified form (e.g. not
Broadcasted)
model.rhs, model.algebraic, or model.external_variables in an
unmodified form (e.g. not Broadcasted)
""".format(
var
)
Expand Down
113 changes: 78 additions & 35 deletions pybamm/models/full_battery_models/base_battery_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@ class BaseBatteryModel(pybamm.BaseModel):
only takes effect if "dimensionality" is 0. If "dimensionality"
is 1 or 2 current collector effects are always included. Must be 'False'
for lead-acid models.
* "external submodels" : list
A list of the submodels that you would like to supply an external
variable for instead of solving in PyBaMM. The entries of the lists
are strings that correspond to the submodel names in the keys
of `self.submodels`.
**Extends:** :class:`pybamm.BaseModel`
Expand All @@ -68,6 +73,7 @@ def __init__(self, options=None, name="Unnamed battery model"):
self.set_standard_output_variables()
self.submodels = {}
self._built = False
self._built_fundamental_and_external = False

@property
def default_parameter_values(self):
Expand Down Expand Up @@ -150,6 +156,7 @@ def options(self, extra_options):
"particle": "Fickian diffusion",
"thermal": "isothermal",
"thermal current collector": False,
"external submodels": []
}
options = default_options
# any extra options overwrite the default options
Expand Down Expand Up @@ -389,17 +396,7 @@ def set_standard_output_variables(self):
{"y": var.y, "y [m]": var.y * L_y, "z": var.z, "z [m]": var.z * L_z}
)

def build_model(self):

# Check if already built
if self._built:
raise pybamm.ModelError(
"""Model already built. If you are adding a new submodel, try using
`model.update` instead."""
)

pybamm.logger.info("Building {}".format(self.name))

def build_fundamental_and_external(self):
# Get the fundamental variables
for submodel_name, submodel in self.submodels.items():
pybamm.logger.debug(
Expand All @@ -409,7 +406,25 @@ def build_model(self):
)
self.variables.update(submodel.get_fundamental_variables())

# Get coupled variables
# set the submodels that are external
for sub in self.options["external submodels"]:
self.submodels[sub].external = True

# Set any external variables
self.external_variables = []
for submodel_name, submodel in self.submodels.items():
pybamm.logger.debug(
"Getting external variables for {} submodel ({})".format(
submodel_name, self.name
)
)
external_variables = submodel.get_external_variables()

self.external_variables += external_variables

self._built_fundamental_and_external = True

def build_coupled_variables(self):
# Note: pybamm will try to get the coupled variables for the submodels in the
# order they are set by the user. If this fails for a particular submodel,
# return to it later and try again. If setting coupled variables fails and
Expand Down Expand Up @@ -444,35 +459,56 @@ def build_model(self):
# try setting coupled variables on next loop through
pass

def build_model_equations(self):
# Set model equations
for submodel_name, submodel in self.submodels.items():
pybamm.logger.debug(
"Setting rhs for {} submodel ({})".format(submodel_name, self.name)
)
submodel.set_rhs(self.variables)
pybamm.logger.debug(
"Setting algebraic for {} submodel ({})".format(
submodel_name, self.name
if submodel.external is False:
pybamm.logger.debug(
"Setting rhs for {} submodel ({})".format(submodel_name, self.name)
)
)
submodel.set_algebraic(self.variables)
pybamm.logger.debug(
"Setting boundary conditions for {} submodel ({})".format(
submodel_name, self.name

submodel.set_rhs(self.variables)
pybamm.logger.debug(
"Setting algebraic for {} submodel ({})".format(
submodel_name, self.name
)
)
)
submodel.set_boundary_conditions(self.variables)
pybamm.logger.debug(
"Setting initial conditions for {} submodel ({})".format(
submodel_name, self.name
submodel.set_algebraic(self.variables)
pybamm.logger.debug(
"Setting boundary conditions for {} submodel ({})".format(
submodel_name, self.name
)
)
submodel.set_boundary_conditions(self.variables)
pybamm.logger.debug(
"Setting initial conditions for {} submodel ({})".format(
submodel_name, self.name
)
)
submodel.set_initial_conditions(self.variables)
submodel.set_events(self.variables)
pybamm.logger.debug(
"Updating {} submodel ({})".format(submodel_name, self.name)
)
self.update(submodel)

def build_model(self):

# Check if already built
if self._built:
raise pybamm.ModelError(
"""Model already built. If you are adding a new submodel, try using
`model.update` instead."""
)
submodel.set_initial_conditions(self.variables)
submodel.set_events(self.variables)
pybamm.logger.debug(
"Updating {} submodel ({})".format(submodel_name, self.name)
)
self.update(submodel)

pybamm.logger.info("Building {}".format(self.name))

if self._built_fundamental_and_external is False:
self.build_fundamental_and_external()

self.build_coupled_variables()

self.build_model_equations()

pybamm.logger.debug("Setting voltage variables")
self.set_voltage_variables()
Expand Down Expand Up @@ -690,6 +726,11 @@ def set_voltage_variables(self):
V = pybamm.BoundaryValue(phi_s_cp, "positive tab")
V_dim = pybamm.BoundaryValue(phi_s_cp_dim, "positive tab")

phi_s_cn = self.variables["Negative current collector potential"]
phi_s_cn_dim = self.variables["Negative current collector potential [V]"]
V_local = phi_s_cp - phi_s_cn
V_local_dim = phi_s_cp_dim - phi_s_cn_dim

# TODO: add current collector losses to the voltage in 3D

self.variables.update(
Expand All @@ -702,6 +743,8 @@ def set_voltage_variables(self):
"X-averaged reaction overpotential [V]": eta_r_av_dim,
"X-averaged solid phase ohmic losses": delta_phi_s_av,
"X-averaged solid phase ohmic losses [V]": delta_phi_s_av_dim,
"Local voltage": V_local,
"Local voltage [V]": V_local_dim,
"Terminal voltage": V,
"Terminal voltage [V]": V_dim,
}
Expand Down
Loading

0 comments on commit 20a0c23

Please sign in to comment.