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 1219 smooth approximations #1223

Merged
merged 16 commits into from
Nov 4, 2020
Merged
Show file tree
Hide file tree
Changes from 14 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
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,17 @@

## Features

- Added a notebook on how to speed up the solver and handle instabilities ([#1223](https://github.com/pybamm-team/PyBaMM/pull/1223))
- Improve string printing of `BinaryOperator`, `Function`, and `Concatenation` objects ([#1223](https://github.com/pybamm-team/PyBaMM/pull/1223))
- Added `Solution.integration_time`, which is the time taken just by the integration subroutine, without extra setups ([#1223](https://github.com/pybamm-team/PyBaMM/pull/1223))
- Added parameter set for an A123 LFP cell ([#1209](https://github.com/pybamm-team/PyBaMM/pull/1209))
- Added variables related to equivalent circuit models ([#1204](https://github.com/pybamm-team/PyBaMM/pull/1204))
- Added an example script to check conservation of lithium ([#1186](https://github.com/pybamm-team/PyBaMM/pull/1186))
- Added `erf` and `erfc` functions ([#1184](https://github.com/pybamm-team/PyBaMM/pull/1184))

## Optimizations

- Add (optional) smooth approximations for the `minimum`, `maximum`, `Heaviside`, and `AbsoluteValue` operators ([#1223](https://github.com/pybamm-team/PyBaMM/pull/1223))
Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very minor thing: should minimum and maximum be capitalised or is it because the smooth approximation is for the method but not for the class?

- Avoid unnecessary repeated computations in the solvers ([#1222](https://github.com/pybamm-team/PyBaMM/pull/1222))
- Rewrite `Symbol.is_constant` to be more efficient ([#1222](https://github.com/pybamm-team/PyBaMM/pull/1222))
- Cache shape and size calculations ([#1222](https://github.com/pybamm-team/PyBaMM/pull/1222))
Expand All @@ -18,7 +22,7 @@

- Fix bug that was slowing down creation of the EC reaction SEI submodel ([#1227](https://github.com/pybamm-team/PyBaMM/pull/1227))
- Add missing separator thermal parameters for the Ecker parameter set ([#1226](https://github.com/pybamm-team/PyBaMM/pull/1226))
- Raise error if saving to matlab with variable names that matlab can't read, and give option of providing alternative variable names ([#1206](https://github.com/pybamm-team/PyBaMM/pull/1206))
- Raise error if saving to MATLAB with variable names that MATLAB can't read, and give option of providing alternative variable names ([#1206](https://github.com/pybamm-team/PyBaMM/pull/1206))
- Raise error if the boundary condition at the origin in a spherical domain is other than no-flux ([#1175](https://github.com/pybamm-team/PyBaMM/pull/1175))
- Fix boundary conditions at r = 0 for Creating Models notebooks ([#1173](https://github.com/pybamm-team/PyBaMM/pull/1173))
- Make sure simulation solves when evaluated timescale is a function of an input parameter ([#1218](https://github.com/pybamm-team/PyBaMM/pull/1218))
Expand Down
6 changes: 6 additions & 0 deletions docs/source/expression_tree/binary_operator.rst
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,10 @@ Binary Operators

.. autofunction:: pybamm.maximum

.. autofunction:: pybamm.softminus

.. autofunction:: pybamm.softplus

.. autofunction:: pybamm.sigmoid

.. autofunction:: pybamm.source
2 changes: 2 additions & 0 deletions docs/source/expression_tree/unary_operator.rst
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ Unary Operators

.. autofunction:: pybamm.boundary_value

.. autofunction:: pybamm.smooth_absolute_value

.. autofunction:: pybamm.sign

.. autofunction:: pybamm.upwind
Expand Down
15 changes: 14 additions & 1 deletion examples/notebooks/change-input-current.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,20 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.6.9"
"version": "3.8.5"
},
"toc": {
"base_numbering": 1,
"nav_menu": {},
"number_sections": true,
"sideBar": true,
"skip_h1_title": false,
"title_cell": "Table of Contents",
"title_sidebar": "Contents",
"toc_cell": false,
"toc_position": {},
"toc_section_display": true,
"toc_window_display": true
}
},
"nbformat": 4,
Expand Down
1,078 changes: 1,078 additions & 0 deletions examples/notebooks/speed-up-solver.ipynb

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions examples/scripts/DFN.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@


# load model
model = pybamm.lithium_ion.DFN()
model = pybamm.lithium_ion.SPM()
Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be DFN


# create geometry
geometry = model.default_geometry
Expand All @@ -30,7 +30,7 @@

# solve model
t_eval = np.linspace(0, 3600, 100)
solver = pybamm.CasadiSolver(atol=1e-6, rtol=1e-3)
solver = pybamm.CasadiSolver(mode="fast", atol=1e-6, rtol=1e-3)
solution = solver.solve(model, t_eval)

# plot
Expand Down
70 changes: 63 additions & 7 deletions pybamm/expression_tree/binary_operators.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,23 @@ def format(self, left, right):

def __str__(self):
""" See :meth:`pybamm.Symbol.__str__()`. """
return "{!s} {} {!s}".format(self.left, self.name, self.right)
# Possibly add brackets for clarity
if isinstance(self.left, pybamm.BinaryOperator) and not (
(self.left.name == self.name)
or (self.left.name == "*" and self.name == "/")
or (self.left.name == "+" and self.name == "-")
or self.name == "+"
):
left_str = "({!s})".format(self.left)
else:
left_str = "{!s}".format(self.left)
if isinstance(self.right, pybamm.BinaryOperator) and not (
(self.name == "*" and self.right.name in ["*", "/"]) or self.name == "+"
):
right_str = "({!s})".format(self.right)
else:
right_str = "{!s}".format(self.right)
return "{} {} {}".format(left_str, self.name, right_str)

def get_children_domains(self, ldomain, rdomain):
"Combine domains from children in appropriate way"
Expand Down Expand Up @@ -832,18 +848,58 @@ def _binary_evaluate(self, left, right):

def minimum(left, right):
"""
Returns the smaller of two objects. Not to be confused with :meth:`pybamm.min`,
which returns min function of child.
Returns the smaller of two objects, possibly with a smoothing approximation.
Not to be confused with :meth:`pybamm.min`, which returns min function of child.
"""
return pybamm.simplify_if_constant(Minimum(left, right), keep_domains=True)
k = pybamm.settings.min_smoothing
# Return exact approximation if that is the setting or the outcome is a constant
# (i.e. no need for smoothing)
if k == "exact" or (pybamm.is_constant(left) and pybamm.is_constant(right)):
out = Minimum(left, right)
else:
out = pybamm.softminus(left, right, k)
return pybamm.simplify_if_constant(out, keep_domains=True)


def maximum(left, right):
"""
Returns the larger of two objects. Not to be confused with :meth:`pybamm.max`,
which returns max function of child.
Returns the larger of two objects, possibly with a smoothing approximation.
Not to be confused with :meth:`pybamm.max`, which returns max function of child.
"""
k = pybamm.settings.max_smoothing
# Return exact approximation if that is the setting or the outcome is a constant
# (i.e. no need for smoothing)
if k == "exact" or (pybamm.is_constant(left) and pybamm.is_constant(right)):
out = Maximum(left, right)
else:
out = pybamm.softplus(left, right, k)
return pybamm.simplify_if_constant(out, keep_domains=True)


def softminus(left, right, k):
"""
Softplus approximation to the minimum function. k is the smoothing parameter,
set by `pybamm.settings.min_smoothing`. The recommended value is k=10.
"""
return pybamm.log(pybamm.exp(-k * left) + pybamm.exp(-k * right)) / -k


def softplus(left, right, k):
"""
Softplus approximation to the maximum function. k is the smoothing parameter,
set by `pybamm.settings.max_smoothing`. The recommended value is k=10.
"""
return pybamm.log(pybamm.exp(k * left) + pybamm.exp(k * right)) / k


def sigmoid(left, right, k):
"""
Sigmoidal approximation to the heaviside function. k is the smoothing parameter,
set by `pybamm.settings.heaviside_smoothing`. The recommended value is k=10.
Note that the concept of deciding which side to pick when left=right does not apply
for this smooth approximation. When left=right, the value is (left+right)/2.
"""
return pybamm.simplify_if_constant(Maximum(left, right), keep_domains=True)
return (1 + pybamm.tanh(k * (right - left))) / 2


def source(left, right, boundary=False):
Expand Down
14 changes: 11 additions & 3 deletions pybamm/expression_tree/concatenations.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,14 @@ def __init__(self, *children, name=None, check_domain=True, concat_fun=None):
name, children, domain=domain, auxiliary_domains=auxiliary_domains
)

def __str__(self):
""" See :meth:`pybamm.Symbol.__str__()`. """
out = self.name + "("
for child in self.children:
out += "{!s}, ".format(child)
out = out[:-2] + ")"
return out

def get_children_domains(self, children):
# combine domains from children
domain = []
Expand Down Expand Up @@ -134,7 +142,7 @@ def __init__(self, *children):
children[i] = child * pybamm.Vector([1])
super().__init__(
*children,
name="numpy concatenation",
name="numpy_concatenation",
check_domain=False,
concat_fun=np.concatenate
)
Expand Down Expand Up @@ -193,7 +201,7 @@ def __init__(self, children, full_mesh, copy_this=None):
children = list(children)

# Allow the base class to sort the domains into the correct order
super().__init__(*children, name="domain concatenation")
super().__init__(*children, name="domain_concatenation")

# ensure domain is sorted according to mesh keys
domain_dict = {d: full_mesh.domain_order.index(d) for d in self.domain}
Expand Down Expand Up @@ -353,5 +361,5 @@ class SparseStack(Concatenation):
def __init__(self, *children):
children = list(children)
super().__init__(
*children, name="sparse stack", check_domain=False, concat_fun=vstack
*children, name="sparse_stack", check_domain=False, concat_fun=vstack
)
8 changes: 8 additions & 0 deletions pybamm/expression_tree/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,14 @@ def __init__(
name, children=children, domain=domain, auxiliary_domains=auxiliary_domains
)

def __str__(self):
""" See :meth:`pybamm.Symbol.__str__()`. """
out = "{}(".format(self.name[10:-1])
for child in self.children:
out += "{!s}, ".format(child)
out = out[:-2] + ")"
return out

def get_children_domains(self, children_list):
"""Obtains the unique domain of the children. If the
children have different domains then raise an error"""
Expand Down
69 changes: 49 additions & 20 deletions pybamm/expression_tree/symbol.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ def evaluate_for_shape_using_domain(domain, auxiliary_domains=None, typ="vector"
return create_object_of_size(_domain_size * _auxiliary_domain_sizes, typ)


def is_constant(symbol):
return isinstance(symbol, numbers.Number) or symbol.is_constant()


class Symbol(anytree.NodeMixin):
"""Base node class for the expression tree

Expand Down Expand Up @@ -441,38 +445,63 @@ def __rpow__(self, other):
return pybamm.simplify_if_constant(pybamm.Power(other, self), keep_domains=True)

def __lt__(self, other):
"""return a :class:`NotEqualHeaviside` object"""
return pybamm.simplify_if_constant(
pybamm.NotEqualHeaviside(self, other), keep_domains=True
)
"""return a :class:`NotEqualHeaviside` object, or a smooth approximation"""
k = pybamm.settings.heaviside_smoothing
# Return exact approximation if that is the setting or the outcome is a constant
# (i.e. no need for smoothing)
if k == "exact" or (is_constant(self) and is_constant(other)):
out = pybamm.NotEqualHeaviside(self, other)
else:
out = pybamm.sigmoid(self, other, k)
return pybamm.simplify_if_constant(out, keep_domains=True)

def __le__(self, other):
"""return a :class:`EqualHeaviside` object"""
return pybamm.simplify_if_constant(
pybamm.EqualHeaviside(self, other), keep_domains=True
)
"""return a :class:`EqualHeaviside` object, or a smooth approximation"""
k = pybamm.settings.heaviside_smoothing
# Return exact approximation if that is the setting or the outcome is a constant
# (i.e. no need for smoothing)
if k == "exact" or (is_constant(self) and is_constant(other)):
out = pybamm.EqualHeaviside(self, other)
else:
out = pybamm.sigmoid(self, other, k)
return pybamm.simplify_if_constant(out, keep_domains=True)

def __gt__(self, other):
"""return a :class:`NotEqualHeaviside` object"""
return pybamm.simplify_if_constant(
pybamm.NotEqualHeaviside(other, self), keep_domains=True
)
"""return a :class:`NotEqualHeaviside` object, or a smooth approximation"""
k = pybamm.settings.heaviside_smoothing
# Return exact approximation if that is the setting or the outcome is a constant
# (i.e. no need for smoothing)
if k == "exact" or (is_constant(self) and is_constant(other)):
out = pybamm.NotEqualHeaviside(other, self)
else:
out = pybamm.sigmoid(other, self, k)
return pybamm.simplify_if_constant(out, keep_domains=True)

def __ge__(self, other):
"""return a :class:`EqualHeaviside` object"""
return pybamm.simplify_if_constant(
pybamm.EqualHeaviside(other, self), keep_domains=True
)
"""return a :class:`EqualHeaviside` object, or a smooth approximation"""
k = pybamm.settings.heaviside_smoothing
# Return exact approximation if that is the setting or the outcome is a constant
# (i.e. no need for smoothing)
if k == "exact" or (is_constant(self) and is_constant(other)):
out = pybamm.EqualHeaviside(other, self)
else:
out = pybamm.sigmoid(other, self, k)
return pybamm.simplify_if_constant(out, keep_domains=True)

def __neg__(self):
"""return a :class:`Negate` object"""
return pybamm.simplify_if_constant(pybamm.Negate(self), keep_domains=True)

def __abs__(self):
"""return an :class:`AbsoluteValue` object"""
return pybamm.simplify_if_constant(
pybamm.AbsoluteValue(self), keep_domains=True
)
"""return an :class:`AbsoluteValue` object, or a smooth approximation"""
k = pybamm.settings.abs_smoothing
# Return exact approximation if that is the setting or the outcome is a constant
# (i.e. no need for smoothing)
if k == "exact" or is_constant(self):
out = pybamm.AbsoluteValue(self)
else:
out = pybamm.smooth_absolute_value(self, k)
return pybamm.simplify_if_constant(out, keep_domains=True)

def __mod__(self, other):
"""return an :class:`Modulo` object"""
Expand Down
14 changes: 14 additions & 0 deletions pybamm/expression_tree/unary_operators.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#
# Unary operator classes and methods
#
import numbers
import numpy as np
import pybamm
from scipy.sparse import issparse, csr_matrix
Expand All @@ -24,6 +25,8 @@ class UnaryOperator(pybamm.Symbol):
"""

def __init__(self, name, child, domain=None, auxiliary_domains=None):
if isinstance(child, numbers.Number):
child = pybamm.Scalar(child)
if domain is None:
domain = child.domain
if auxiliary_domains is None:
Expand Down Expand Up @@ -1347,3 +1350,14 @@ def boundary_value(symbol, side):
def sign(symbol):
" Returns a :class:`Sign` object. "
return Sign(symbol)


def smooth_absolute_value(symbol, k):
"""
Smooth approximation to the absolute value function. k is the smoothing parameter,
set by `pybamm.settings.abs_smoothing`. The recommended value is k=10.
"""
x = symbol
exp = pybamm.exp
kx = k * symbol
return x * (exp(kx) - exp(-kx)) / (exp(kx) + exp(-kx))
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ def get_fundamental_variables(self):
i_cell = pybamm.Variable("Total current density")

# Update derived variables
I = i_cell * abs(param.I_typ)
I = i_cell * pybamm.AbsoluteValue(param.I_typ)
i_cell_dim = I / (param.n_electrodes_parallel * param.A_cc)

variables = {
Expand Down
Loading