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 692 external temperature matrix #728

Merged
merged 36 commits into from
Nov 20, 2019
Merged
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
eb0685e
#692 added an get_external_variables method to submodels
Scottmar93 Nov 6, 2019
faf0a59
#692 changed to pass in external option
Scottmar93 Nov 8, 2019
065d6e0
#692 build seems to work
Scottmar93 Nov 9, 2019
9fe3a3d
#692 adding features to base solver to allow external variables
Scottmar93 Nov 9, 2019
97f9a67
#692 structure for external variables in place but need to debug
Scottmar93 Nov 9, 2019
99aa4ea
#692 fixed yslice issue
Scottmar93 Nov 9, 2019
a283442
#692 simulation runs
Scottmar93 Nov 9, 2019
3e8ec1e
#692 simulation runs and can save a reasonable looking solution howev…
Scottmar93 Nov 9, 2019
08c9a4a
#692 trying to locate strange quick plot error
Scottmar93 Nov 9, 2019
ed6a083
#692 prevented repeated timesteps being saved to solution
Scottmar93 Nov 11, 2019
121f479
#692 working on ode models
Scottmar93 Nov 11, 2019
8979e19
#692 starting to put in dae support
Scottmar93 Nov 11, 2019
f9370ad
#692 external works for IDAKLU
Scottmar93 Nov 11, 2019
d64511a
#692 moved integration tests
Scottmar93 Nov 11, 2019
6b084be
#692 begining to add cc tests
Scottmar93 Nov 12, 2019
1b9d527
#692 external works for current collector models
Scottmar93 Nov 12, 2019
9deb90c
#692 merged from master
Scottmar93 Nov 12, 2019
607df1f
#692 added unit test for external variables in discretization
Scottmar93 Nov 12, 2019
270d207
#692 can calculate jacobian using casadi on external variables
Scottmar93 Nov 12, 2019
832940a
#692 added tests for discretization
Scottmar93 Nov 12, 2019
d36d5cc
692 added tests for simulation
Scottmar93 Nov 13, 2019
e57de2c
692 fixed flake8 and added solver tests
Scottmar93 Nov 13, 2019
97d99db
Merge remote-tracking branch 'origin/master' into issue-692-external-…
Scottmar93 Nov 13, 2019
dfd2926
692 updated changelog
Scottmar93 Nov 13, 2019
751500b
692 updated documentation in simulation slightly
Scottmar93 Nov 13, 2019
3932c23
692 fixed external temperature tests
Scottmar93 Nov 13, 2019
b4e8ace
692 skip tests that require idaklu
Scottmar93 Nov 13, 2019
6576303
Merge remote-tracking branch 'origin/master' into issue-692-external-…
Scottmar93 Nov 13, 2019
18e90bb
692 adressed tinos comments
Scottmar93 Nov 14, 2019
6395fa7
Merge remote-tracking branch 'origin/master' into issue-692-external-…
Scottmar93 Nov 15, 2019
4528403
692 addressed roberts comments
Scottmar93 Nov 19, 2019
faca42f
692 updated example
Scottmar93 Nov 19, 2019
f3c976f
Merge remote-tracking branch 'origin/master' into issue-692-external-…
Scottmar93 Nov 20, 2019
41694ca
692 passes external variables test
Scottmar93 Nov 20, 2019
8678839
692 fixed some tests
Scottmar93 Nov 20, 2019
dbdd10c
692 added line to restart tests
Scottmar93 Nov 20, 2019
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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@

## 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))
- 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))
- 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 @@ -147,7 +156,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
Scottmar93 marked this conversation as resolved.
Show resolved Hide resolved
# 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:
Scottmar93 marked this conversation as resolved.
Show resolved Hide resolved
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 @@ -228,10 +258,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 @@ -819,7 +872,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 @@ -99,6 +99,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 @@ -371,7 +372,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 @@ -462,19 +473,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`.
Scottmar93 marked this conversation as resolved.
Show resolved Hide resolved


**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