Skip to content

Commit 22e9d5a

Browse files
merge develop, back to long form for lam
2 parents d91dac9 + ea1f675 commit 22e9d5a

37 files changed

+208
-1146
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
## Features
44

5-
- Added variables "Loss of lithium due to LAM in negative/positive electrode [mol]", and "Total lithium lost to LAM [mol]/[A.h]". This is also included in the calculation of "total lithium in system" to make sure that lithium is truly conserved. ([#2529](https://github.com/pybamm-team/PyBaMM/pull/2529))
5+
- Added variables "Loss of lithium due to loss of active material in negative/positive electrode [mol]". These should be included in the calculation of "total lithium in system" to make sure that lithium is truly conserved. ([#2529](https://github.com/pybamm-team/PyBaMM/pull/2529))
66
- `initial_soc` can now be a string "x V", in which case the simulation is initialized to start from that voltage ([#2508](https://github.com/pybamm-team/PyBaMM/pull/2508))
77
- The `ElectrodeSOH` solver can now calculate electrode balance based on a target "cell capacity" (requires cell capacity "Q" as input), as well as the default "cyclable cell capacity" (requires cyclable lithium capacity "Q_Li" as input). Use the keyword argument `known_value` to control which is used. ([#2508](https://github.com/pybamm-team/PyBaMM/pull/2508))
88

@@ -14,6 +14,7 @@
1414
## Breaking changes
1515

1616
- Renamed "Negative/Positive electrode SOC" to "Negative/Positive electrode stoichiometry" to avoid confusion with cell SOC ([#2529](https://github.com/pybamm-team/PyBaMM/pull/2529))
17+
- Removed external variables and submodels. InputParameter should now be used in all cases ([#2502](https://github.com/pybamm-team/PyBaMM/pull/2502))
1718
- Trying to use a solver to solve multiple models results in a RuntimeError exception ([#2481](https://github.com/pybamm-team/PyBaMM/pull/2481))
1819
- Inputs for the `ElectrodeSOH` solver are now (i) "Q_Li", the total cyclable capacity of lithium in the electrodes (previously "n_Li", the total number of moles, n_Li = 3600/F \* Q_Li) (ii) "Q_n", the capacity of the negative electrode (previously "C_n"), and "Q_p", the capacity of the positive electrode (previously "C_p") ([#2508](https://github.com/pybamm-team/PyBaMM/pull/2508))
1920

docs/source/expression_tree/variable.rst

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,3 @@ Variable
77
.. autoclass:: pybamm.VariableDot
88
:members:
99

10-
.. autoclass:: pybamm.ExternalVariable
11-
:members:
12-

pybamm/discretisations/discretisation.py

Lines changed: 27 additions & 136 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,6 @@ def __init__(self, mesh=None, spatial_methods=None):
6363
self._bcs = {}
6464
self.y_slices = {}
6565
self._discretised_symbols = {}
66-
self.external_variables = {}
6766

6867
@property
6968
def mesh(self):
@@ -178,11 +177,6 @@ def process_model(
178177
pybamm.logger.verbose("Set variable slices for {}".format(model.name))
179178
self.set_variable_slices(variables)
180179

181-
# now add extrapolated external variables to the boundary conditions
182-
# if required by the spatial method
183-
self._preprocess_external_variables(model)
184-
self.set_external_variables(model)
185-
186180
# set boundary conditions (only need key ids for boundary_conditions)
187181
pybamm.logger.verbose(
188182
"Discretise boundary conditions for {}".format(model.name)
@@ -242,11 +236,6 @@ def process_model(
242236
processed_events.append(processed_event)
243237
model_disc.events = processed_events
244238

245-
# Set external variables
246-
model_disc.external_variables = [
247-
self.process_symbol(var) for var in model.external_variables
248-
]
249-
250239
# Create mass matrix
251240
pybamm.logger.verbose("Create mass matrix for {}".format(model.name))
252241
model_disc.mass_matrix, model_disc.mass_matrix_inv = self.create_mass_matrix(
@@ -349,70 +338,6 @@ def _get_variable_size(self, variable):
349338
size += spatial_method.mesh[dom].npts_for_broadcast_to_nodes * repeats
350339
return size
351340

352-
def _preprocess_external_variables(self, model):
353-
"""
354-
A method to preprocess external variables so that they are
355-
compatible with the spatial method. For example, in finite
356-
volume, the user will supply a vector of values valid on the
357-
cell centres. However, for model processing, we also require
358-
the boundary edge fluxes. Therefore, we extrapolate and add
359-
the boundary fluxes to the boundary conditions, which are
360-
employed in generating the grad and div matrices.
361-
The processing is delegated to spatial methods as
362-
the preprocessing required for finite volume and finite
363-
element will be different.
364-
"""
365-
366-
for var in model.external_variables:
367-
if var.domain != []:
368-
new_bcs = self.spatial_methods[
369-
var.domain[0]
370-
].preprocess_external_variables(var)
371-
372-
model.boundary_conditions.update(new_bcs)
373-
374-
def set_external_variables(self, model):
375-
"""
376-
Add external variables to the list of variables to account for, being careful
377-
about concatenations
378-
"""
379-
for var in model.external_variables:
380-
# Find the name in the model variables
381-
# Look up dictionary key based on value
382-
try:
383-
idx = list(model.variables.values()).index(var)
384-
except ValueError:
385-
raise ValueError(
386-
"""
387-
Variable {} must be in the model.variables dictionary to be set
388-
as an external variable
389-
""".format(
390-
var
391-
)
392-
)
393-
name = list(model.variables.keys())[idx]
394-
if isinstance(var, pybamm.Variable):
395-
# No need to keep track of the parent
396-
self.external_variables[(name, None)] = var
397-
elif isinstance(var, pybamm.ConcatenationVariable):
398-
start = 0
399-
end = 0
400-
for child in var.children:
401-
dom = child.domain[0]
402-
if (
403-
self.spatial_methods[dom]._get_auxiliary_domain_repeats(
404-
child.domains
405-
)
406-
> 1
407-
):
408-
raise NotImplementedError(
409-
"Cannot create 2D external variable with concatenations"
410-
)
411-
end += self._get_variable_size(child)
412-
# Keep a record of the parent
413-
self.external_variables[(name, (var, start, end))] = child
414-
start = end
415-
416341
def set_internal_boundary_conditions(self, model):
417342
"""
418343
A method to set the internal boundary conditions for the submodel.
@@ -526,16 +451,15 @@ def process_boundary_conditions(self, model):
526451

527452
# check if the boundary condition at the origin for sphere domains is other
528453
# than no flux
529-
if key not in model.external_variables:
530-
for subdomain in key.domain:
531-
if self.mesh[subdomain].coord_sys == "spherical polar":
532-
if bcs["left"][0].value != 0 or bcs["left"][1] != "Neumann":
533-
raise pybamm.ModelError(
534-
"Boundary condition at r = 0 must be a homogeneous "
535-
"Neumann condition for {} coordinates".format(
536-
self.mesh[subdomain].coord_sys
537-
)
454+
for subdomain in key.domain:
455+
if self.mesh[subdomain].coord_sys == "spherical polar":
456+
if bcs["left"][0].value != 0 or bcs["left"][1] != "Neumann":
457+
raise pybamm.ModelError(
458+
"Boundary condition at r = 0 must be a homogeneous "
459+
"Neumann condition for {} coordinates".format(
460+
self.mesh[subdomain].coord_sys
538461
)
462+
)
539463

540464
# Handle any boundary conditions applied on the tabs
541465
if any("tab" in side for side in list(bcs.keys())):
@@ -956,52 +880,26 @@ def _process_symbol(self, symbol):
956880
)
957881

958882
elif isinstance(symbol, pybamm.Variable):
959-
# Check if variable is a standard variable or an external variable
960-
if any(symbol == var for var in self.external_variables.values()):
961-
# Look up dictionary key based on value
962-
idx = list(self.external_variables.values()).index(symbol)
963-
name, parent_and_slice = list(self.external_variables.keys())[idx]
964-
if parent_and_slice is None:
965-
# Variable didn't come from a concatenation so we can just create a
966-
# normal external variable using the symbol's name
967-
return pybamm.ExternalVariable(
968-
symbol.name,
969-
size=self._get_variable_size(symbol),
970-
domains=symbol.domains,
971-
)
972-
else:
973-
# We have to use a special name since the concatenation doesn't have
974-
# a very informative name. Needs improving
975-
parent, start, end = parent_and_slice
976-
ext = pybamm.ExternalVariable(
977-
name,
978-
size=self._get_variable_size(parent),
979-
domains=parent.domains,
980-
)
981-
out = pybamm.Index(ext, slice(start, end))
982-
out.copy_domains(symbol)
983-
return out
984-
else:
985-
# add a try except block for a more informative error if a variable
986-
# can't be found. This should usually be caught earlier by
987-
# model.check_well_posedness, but won't be if debug_mode is False
988-
try:
989-
y_slices = self.y_slices[symbol]
990-
except KeyError:
991-
raise pybamm.ModelError(
992-
"""
993-
No key set for variable '{}'. Make sure it is included in either
994-
model.rhs, model.algebraic, or model.external_variables in an
995-
unmodified form (e.g. not Broadcasted)
996-
""".format(
997-
symbol.name
998-
)
883+
# add a try except block for a more informative error if a variable
884+
# can't be found. This should usually be caught earlier by
885+
# model.check_well_posedness, but won't be if debug_mode is False
886+
try:
887+
y_slices = self.y_slices[symbol]
888+
except KeyError:
889+
raise pybamm.ModelError(
890+
"""
891+
No key set for variable '{}'. Make sure it is included in either
892+
model.rhs or model.algebraic in an unmodified form
893+
(e.g. not Broadcasted)
894+
""".format(
895+
symbol.name
999896
)
1000-
# Add symbol's reference and multiply by the symbol's scale
1001-
# so that the state vector is of order 1
1002-
return symbol.reference + symbol.scale * pybamm.StateVector(
1003-
*y_slices, domains=symbol.domains
1004897
)
898+
# Add symbol's reference and multiply by the symbol's scale
899+
# so that the state vector is of order 1
900+
return symbol.reference + symbol.scale * pybamm.StateVector(
901+
*y_slices, domains=symbol.domains
902+
)
1005903

1006904
elif isinstance(symbol, pybamm.SpatialVariable):
1007905
return spatial_method.spatial_variable(symbol)
@@ -1083,14 +981,7 @@ def _concatenate_in_order(self, var_eqn_dict, check_complete=False, sparse=False
1083981
if check_complete:
1084982
# Check keys from the given var_eqn_dict against self.y_slices
1085983
unpacked_variables_set = set(unpacked_variables)
1086-
external_vars = set(self.external_variables.values())
1087-
for var in self.external_variables.values():
1088-
child_vars = set(var.children)
1089-
external_vars = external_vars.union(child_vars)
1090-
y_slices_with_external_removed = set(self.y_slices.keys()).difference(
1091-
external_vars
1092-
)
1093-
if unpacked_variables_set != y_slices_with_external_removed:
984+
if unpacked_variables_set != set(self.y_slices.keys()):
1094985
given_variable_names = [v.name for v in var_eqn_dict.keys()]
1095986
raise pybamm.ModelError(
1096987
"Initial conditions are insufficient. Only "

pybamm/expression_tree/exceptions.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -56,12 +56,6 @@ class ModelWarning(UserWarning):
5656
pass
5757

5858

59-
class InputError(Exception):
60-
"""An external variable has been input incorrectly into PyBaMM."""
61-
62-
pass
63-
64-
6559
class DiscretisationError(Exception):
6660
"""A model could not be discretised."""
6761

pybamm/expression_tree/operations/convert_to_casadi.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,6 @@ def _convert(self, symbol, t, y, y_dot, inputs):
5555
pybamm.Array,
5656
pybamm.Time,
5757
pybamm.InputParameter,
58-
pybamm.ExternalVariable,
5958
),
6059
):
6160
return casadi.MX(symbol.evaluate(t, y, y_dot, inputs))

pybamm/expression_tree/variable.py

Lines changed: 0 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
#
22
# Variable class
33
#
4-
import numbers
54

65
import numpy as np
76
import sympy
@@ -223,107 +222,3 @@ def diff(self, variable):
223222
raise pybamm.ModelError("cannot take second time derivative of a Variable")
224223
else:
225224
return pybamm.Scalar(0)
226-
227-
228-
class ExternalVariable(Variable):
229-
"""
230-
A node in the expression tree representing an external variable variable.
231-
232-
This node will be discretised by :class:`.Discretisation` and converted
233-
to a :class:`.Vector` node.
234-
235-
Parameters
236-
----------
237-
238-
name : str
239-
name of the node
240-
domain : iterable of str
241-
list of domains that this variable is valid over
242-
auxiliary_domains : dict
243-
dictionary of auxiliary domains ({'secondary': ..., 'tertiary': ...,
244-
'quaternary': ...}). For example, for the single particle model, the particle
245-
concentration would be a Variable with domain 'negative particle' and secondary
246-
auxiliary domain 'current collector'. For the DFN, the particle concentration
247-
would be a Variable with domain 'negative particle', secondary domain
248-
'negative electrode' and tertiary domain 'current collector'
249-
domains : dict
250-
A dictionary equivalent to {'primary': domain, auxiliary_domains}. Either
251-
'domain' and 'auxiliary_domains', or just 'domains', should be provided
252-
(not both). In future, the 'domain' and 'auxiliary_domains' arguments may be
253-
deprecated.
254-
scale : float or :class:`pybamm.Symbol`, optional
255-
The scale of the variable, used for scaling the model when solving. The state
256-
vector representing this variable will be multiplied by this scale.
257-
Default is 1.
258-
reference : float or :class:`pybamm.Symbol`, optional
259-
The reference value of the variable, used for scaling the model when solving.
260-
This value will be added to the state vector representing this variable.
261-
Default is 0.
262-
263-
*Extends:* :class:`pybamm.Variable`
264-
"""
265-
266-
def __init__(
267-
self,
268-
name,
269-
size,
270-
domain=None,
271-
auxiliary_domains=None,
272-
domains=None,
273-
scale=1,
274-
reference=0,
275-
):
276-
self._size = size
277-
super().__init__(
278-
name, domain, auxiliary_domains, domains, scale=scale, reference=reference
279-
)
280-
281-
@property
282-
def size(self):
283-
return self._size
284-
285-
def create_copy(self):
286-
"""See :meth:`pybamm.Symbol.new_copy()`."""
287-
return ExternalVariable(self.name, self.size, domains=self.domains)
288-
289-
def _evaluate_for_shape(self):
290-
"""See :meth:`pybamm.Symbol.evaluate_for_shape_using_domain()`"""
291-
return np.nan * np.ones((self.size, 1))
292-
293-
def _base_evaluate(self, t=None, y=None, y_dot=None, inputs=None):
294-
# inputs should be a dictionary
295-
# convert 'None' to empty dictionary for more informative error
296-
if inputs is None:
297-
inputs = {}
298-
if not isinstance(inputs, dict):
299-
# if the special input "shape test" is passed, just return 1
300-
if inputs == "shape test":
301-
return self.evaluate_for_shape()
302-
raise TypeError("inputs should be a dictionary")
303-
try:
304-
out = inputs[self.name]
305-
if isinstance(out, numbers.Number) or out.shape[0] == 1:
306-
return out * np.ones((self.size, 1))
307-
elif out.shape[0] != self.size:
308-
raise ValueError(
309-
"External variable input has size {} but should be {}".format(
310-
out.shape[0], self.size
311-
)
312-
)
313-
else:
314-
if isinstance(out, np.ndarray) and out.ndim == 1:
315-
out = out[:, np.newaxis]
316-
return out
317-
# raise more informative error if can't find name in dict
318-
except KeyError:
319-
raise KeyError("External variable '{}' not found".format(self.name))
320-
321-
def diff(self, variable):
322-
if variable == self:
323-
return pybamm.Scalar(1)
324-
elif variable == pybamm.t:
325-
raise pybamm.ModelError(
326-
"cannot take time derivative of an external variable"
327-
)
328-
else:
329-
return pybamm.Scalar(0)

0 commit comments

Comments
 (0)