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 743 input node #752

Merged
merged 19 commits into from
Dec 5, 2019
Merged
Show file tree
Hide file tree
Changes from 18 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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## Features

- Added `InputParameter` node for quickly changing parameter values ([#752](https://github.com/pybamm-team/PyBaMM/pull/752))
- 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))
Expand Down
1 change: 1 addition & 0 deletions docs/source/expression_tree/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,6 @@ Expression Tree
concatenations
broadcasts
functions
input_parameter
interpolant
operations/index
5 changes: 5 additions & 0 deletions docs/source/expression_tree/input_parameter.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Input Parameter
===============

.. autoclass:: pybamm.InputParameter
:members:
1 change: 1 addition & 0 deletions pybamm/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ def version(formatted=False):
from .expression_tree.unary_operators import *
from .expression_tree.functions import *
from .expression_tree.interpolant import Interpolant
from .expression_tree.input_parameter import InputParameter
from .expression_tree.parameter import Parameter, FunctionParameter
from .expression_tree.broadcasts import Broadcast, PrimaryBroadcast, FullBroadcast
from .expression_tree.scalar import Scalar
Expand Down
10 changes: 5 additions & 5 deletions pybamm/expression_tree/binary_operators.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,21 +174,21 @@ def _binary_new_copy(self, left, right):
"Default behaviour for new_copy"
return self.__class__(left, right)

def evaluate(self, t=None, y=None, known_evals=None):
def evaluate(self, t=None, y=None, u=None, known_evals=None):
""" See :meth:`pybamm.Symbol.evaluate()`. """
if known_evals is not None:
id = self.id
try:
return known_evals[id], known_evals
except KeyError:
left, known_evals = self.left.evaluate(t, y, known_evals)
right, known_evals = self.right.evaluate(t, y, known_evals)
left, known_evals = self.left.evaluate(t, y, u, known_evals)
right, known_evals = self.right.evaluate(t, y, u, known_evals)
value = self._binary_evaluate(left, right)
known_evals[id] = value
return value, known_evals
else:
left = self.left.evaluate(t, y)
right = self.right.evaluate(t, y)
left = self.left.evaluate(t, y, u)
right = self.right.evaluate(t, y, u)
return self._binary_evaluate(left, right)

def evaluate_for_shape(self):
Expand Down
8 changes: 5 additions & 3 deletions pybamm/expression_tree/concatenations.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,20 +52,22 @@ def _concatenation_evaluate(self, children_eval):
else:
return self.concatenation_function(children_eval)

def evaluate(self, t=None, y=None, known_evals=None):
def evaluate(self, t=None, y=None, u=None, known_evals=None):
""" See :meth:`pybamm.Symbol.evaluate()`. """
children = self.cached_children
if known_evals is not None:
if self.id not in known_evals:
children_eval = [None] * len(children)
for idx, child in enumerate(children):
children_eval[idx], known_evals = child.evaluate(t, y, known_evals)
children_eval[idx], known_evals = child.evaluate(
t, y, u, known_evals
)
known_evals[self.id] = self._concatenation_evaluate(children_eval)
return known_evals[self.id], known_evals
else:
children_eval = [None] * len(children)
for idx, child in enumerate(children):
children_eval[idx] = child.evaluate(t, y)
children_eval[idx] = child.evaluate(t, y, u)
return self._concatenation_evaluate(children_eval)

def new_copy(self):
Expand Down
10 changes: 5 additions & 5 deletions pybamm/expression_tree/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ class Function(pybamm.Symbol):
----------
function : method
A function can have 0 or many inputs. If no inputs are given, self.evaluate()
simply returns func(). Otherwise, self.evaluate(t, y) returns
func(child0.evaluate(t, y), child1.evaluate(t, y), etc).
simply returns func(). Otherwise, self.evaluate(t, y, u) returns
func(child0.evaluate(t, y, u), child1.evaluate(t, y, u), etc).
children : :class:`pybamm.Symbol`
The children nodes to apply the function to
derivative : str, optional
Expand Down Expand Up @@ -152,19 +152,19 @@ def _function_jac(self, children_jacs):

return jacobian

def evaluate(self, t=None, y=None, known_evals=None):
def evaluate(self, t=None, y=None, u=None, known_evals=None):
""" See :meth:`pybamm.Symbol.evaluate()`. """
if known_evals is not None:
if self.id not in known_evals:
evaluated_children = [None] * len(self.children)
for i, child in enumerate(self.children):
evaluated_children[i], known_evals = child.evaluate(
t, y, known_evals
t, y, known_evals=known_evals
)
known_evals[self.id] = self._function_evaluate(evaluated_children)
return known_evals[self.id], known_evals
else:
evaluated_children = [child.evaluate(t, y) for child in self.children]
evaluated_children = [child.evaluate(t, y, u) for child in self.children]
return self._function_evaluate(evaluated_children)

def evaluate_for_shape(self):
Expand Down
53 changes: 53 additions & 0 deletions pybamm/expression_tree/input_parameter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#
# Parameter classes
#
import numpy as np
import pybamm


class InputParameter(pybamm.Symbol):
"""A node in the expression tree representing an input parameter

This node's value can be set at the point of solving, allowing parameter estimation
and control

Parameters
----------
name : str
name of the node

"""

def __init__(self, name):
super().__init__(name)

def new_copy(self):
""" See :meth:`pybamm.Symbol.new_copy()`. """
return InputParameter(self.name)

def evaluate_for_shape(self):
"""
Returns the scalar 'NaN' to represent the shape of a parameter.
See :meth:`pybamm.Symbol.evaluate_for_shape()`
"""
return np.nan

def _jac(self, variable):
""" See :meth:`pybamm.Symbol._jac()`. """
return pybamm.Scalar(0)

def evaluate(self, t=None, y=None, u=None, known_evals=None):
# u should be a dictionary
# convert 'None' to empty dictionary for more informative error
if u is None:
u = {}
if not isinstance(u, dict):
# if the special input "shape test" is passed, just return 1
if u == "shape test":
return 1
raise TypeError("inputs u should be a dictionary")
try:
return u[self.name]
# raise more informative error if can't find name in dict
except KeyError:
raise KeyError("Input parameter '{}' not found".format(self.name))
41 changes: 24 additions & 17 deletions pybamm/expression_tree/operations/convert_to_casadi.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,36 +11,43 @@ class CasadiConverter(object):
def __init__(self, casadi_symbols=None):
self._casadi_symbols = casadi_symbols or {}

def convert(self, symbol, t=None, y=None):
def convert(self, symbol, t=None, y=None, u=None):
"""
This function recurses down the tree, applying any simplifications defined in
classes derived from pybamm.Symbol. E.g. any expression multiplied by a
pybamm.Scalar(0) will be simplified to a pybamm.Scalar(0).
If a symbol has already been simplified, the stored value is returned.
This function recurses down the tree, converting the PyBaMM expression tree to
a CasADi expression tree

Parameters
----------
symbol : :class:`pybamm.Symbol`
The symbol to convert
t : :class:`casadi.MX`
A casadi symbol representing time
y : :class:`casadi.MX`
A casadi symbol representing state vectors
u : dict
A dictionary of casadi symbols representing inputs

Returns
-------
CasADi symbol
The convert symbol
:class:`casadi.MX`
The converted symbol
"""

try:
return self._casadi_symbols[symbol.id]
except KeyError:
casadi_symbol = self._convert(symbol, t, y)
# Change u to empty dictionary if it's None
u = u or {}
casadi_symbol = self._convert(symbol, t, y, u)
self._casadi_symbols[symbol.id] = casadi_symbol

return casadi_symbol

def _convert(self, symbol, t, y):
def _convert(self, symbol, t=None, y=None, u=None):
""" See :meth:`CasadiConverter.convert()`. """
if isinstance(symbol, (pybamm.Scalar, pybamm.Array, pybamm.Time)):
return casadi.MX(symbol.evaluate(t, y))
if isinstance(
symbol, (pybamm.Scalar, pybamm.Array, pybamm.Time, pybamm.InputParameter)
):
return casadi.MX(symbol.evaluate(t, y, u))

elif isinstance(symbol, pybamm.StateVector):
if y is None:
Expand All @@ -50,23 +57,23 @@ def _convert(self, symbol, t, y):
elif isinstance(symbol, pybamm.BinaryOperator):
left, right = symbol.children
# process children
converted_left = self.convert(left, t, y)
converted_right = self.convert(right, t, y)
converted_left = self.convert(left, t, y, u)
converted_right = self.convert(right, t, y, u)
if isinstance(symbol, pybamm.Outer):
return casadi.kron(converted_left, converted_right)
else:
# _binary_evaluate defined in derived classes for specific rules
return symbol._binary_evaluate(converted_left, converted_right)

elif isinstance(symbol, pybamm.UnaryOperator):
converted_child = self.convert(symbol.child, t, y)
converted_child = self.convert(symbol.child, t, y, u)
if isinstance(symbol, pybamm.AbsoluteValue):
return casadi.fabs(converted_child)
return symbol._unary_evaluate(converted_child)

elif isinstance(symbol, pybamm.Function):
converted_children = [
self.convert(child, t, y) for child in symbol.children
self.convert(child, t, y, u) for child in symbol.children
]
# Special functions
if symbol.function == np.min:
Expand Down Expand Up @@ -97,7 +104,7 @@ def _convert(self, symbol, t, y):
return symbol._function_evaluate(converted_children)
elif isinstance(symbol, pybamm.Concatenation):
converted_children = [
self.convert(child, t, y) for child in symbol.children
self.convert(child, t, y, u) for child in symbol.children
]
if isinstance(symbol, (pybamm.NumpyConcatenation, pybamm.SparseStack)):
return casadi.vertcat(*converted_children)
Expand Down
5 changes: 4 additions & 1 deletion pybamm/expression_tree/operations/evaluate.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,9 @@ def find_symbols(symbol, constant_symbols, variable_symbols):
elif isinstance(symbol, pybamm.Time):
symbol_str = "t"

elif isinstance(symbol, pybamm.InputParameter):
symbol_str = "u['{}']".format(symbol.name)

else:
raise NotImplementedError(
"Not implemented for a symbol of type '{}'".format(type(symbol))
Expand Down Expand Up @@ -270,7 +273,7 @@ def __init__(self, symbol):
self._result_var, "return" + self._result_var, "eval"
)

def evaluate(self, t=None, y=None, known_evals=None):
def evaluate(self, t=None, y=None, u=None, known_evals=None):
"""
Acts as a drop-in replacement for :func:`pybamm.Symbol.evaluate`
"""
Expand Down
29 changes: 17 additions & 12 deletions pybamm/expression_tree/symbol.py
Original file line number Diff line number Diff line change
Expand Up @@ -459,7 +459,7 @@ def _base_evaluate(self, t=None, y=None):
)
)

def evaluate(self, t=None, y=None, known_evals=None):
def evaluate(self, t=None, y=None, u=None, known_evals=None):
"""Evaluate expression tree (wrapper to allow using dict of known values).
If the dict 'known_evals' is provided, the dict is searched for self.id; if
self.id is in the keys, return that value; otherwise, evaluate using
Expand Down Expand Up @@ -498,44 +498,49 @@ def evaluate_for_shape(self):

def is_constant(self):
"""returns true if evaluating the expression is not dependent on `t` or `y`
or `u`

See Also
--------
evaluate : evaluate the expression

"""
# if any of the nodes are instances of any of these types, then the whole
# expression depends on either t or y
search_types = (pybamm.Variable, pybamm.StateVector, pybamm.IndependentVariable)
# expression depends on either t or y or u
search_types = (
pybamm.Variable,
pybamm.StateVector,
pybamm.Time,
pybamm.InputParameter,
)

# do the search, return true if no relevent nodes are found
return not any((isinstance(n, search_types)) for n in self.pre_order())

def evaluate_ignoring_errors(self):
"""
Evaluates the expression. If a node exists in the tree that cannot be evaluated
as a scalar or vectr (e.g. Parameter, Variable, StateVector), then None is
returned. Otherwise the result of the evaluation is given
as a scalar or vector (e.g. Parameter, Variable, StateVector, InputParameter),
then None is returned. Otherwise the result of the evaluation is given

See Also
--------
evaluate : evaluate the expression

"""
try:
result = self.evaluate(t=0)
result = self.evaluate(t=0, u="shape test")
except NotImplementedError:
# return false if NotImplementedError is raised
# return None if NotImplementedError is raised
# (there is a e.g. Parameter, Variable, ... in the tree)
return None
except TypeError as error:
# return false if specific TypeError is raised
# return None if specific TypeError is raised
# (there is a e.g. StateVector in the tree)
if error.args[0] == "StateVector cannot evaluate input 'y=None'":
return None
else:
raise error

return result

def evaluates_to_number(self):
Expand Down Expand Up @@ -579,12 +584,12 @@ def simplify(self, simplified_symbols=None):
""" Simplify the expression tree. See :class:`pybamm.Simplification`. """
return pybamm.Simplification(simplified_symbols).simplify(self)

def to_casadi(self, t=None, y=None, casadi_symbols=None):
def to_casadi(self, t=None, y=None, u=None, casadi_symbols=None):
"""
Convert the expression tree to a CasADi expression tree.
See :class:`pybamm.CasadiConverter`.
"""
return pybamm.CasadiConverter(casadi_symbols).convert(self, t, y)
return pybamm.CasadiConverter(casadi_symbols).convert(self, t, y, u)

def new_copy(self):
"""
Expand Down Expand Up @@ -614,7 +619,7 @@ def shape(self):
# Try with some large y, to avoid having to use pre_order (slow)
try:
y = np.linspace(0.1, 0.9, int(1e4))
evaluated_self = self.evaluate(0, y)
evaluated_self = self.evaluate(0, y, u="shape test")
# If that fails, fall back to calculating how big y should really be
except ValueError:
state_vectors_in_node = [
Expand Down
6 changes: 3 additions & 3 deletions pybamm/expression_tree/unary_operators.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,15 +63,15 @@ def _unary_evaluate(self, child):
"""Perform unary operation on a child. """
raise NotImplementedError

def evaluate(self, t=None, y=None, known_evals=None):
def evaluate(self, t=None, y=None, u=None, known_evals=None):
""" See :meth:`pybamm.Symbol.evaluate()`. """
if known_evals is not None:
if self.id not in known_evals:
child, known_evals = self.child.evaluate(t, y, known_evals)
child, known_evals = self.child.evaluate(t, y, u, known_evals)
known_evals[self.id] = self._unary_evaluate(child)
return known_evals[self.id], known_evals
else:
child = self.child.evaluate(t, y)
child = self.child.evaluate(t, y, u)
return self._unary_evaluate(child)

def evaluate_for_shape(self):
Expand Down
1 change: 0 additions & 1 deletion pybamm/logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,3 @@ def set_logging_level(level):
# Create a custom logger
logger = logging.getLogger(__name__)
set_logging_level("WARNING")

Loading