Skip to content

Commit

Permalink
Merge branch 'master' into issue-744-particledistribution
Browse files Browse the repository at this point in the history
  • Loading branch information
valentinsulzer committed Dec 7, 2019
2 parents 802f616 + b5f51da commit 7b7f4e9
Show file tree
Hide file tree
Showing 32 changed files with 757 additions and 247 deletions.
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:
130 changes: 96 additions & 34 deletions docs/tutorials/add-parameter-values.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,56 +21,36 @@ For an example of how the parameter values work, see the
Adding a set of parameters values
---------------------------------

There are two ways to add parameter sets:
Parameter sets are split by material into ``anodes``, ``separators``, ``cathodes``, ``electrolytes``, ``cells`` (for cell geometries and thermal properties) and ``experiments`` (for initial conditions and charge/discharge rates).
To add a new parameter set in one of these subcategories, first create a new folder in the appropriate chemistry folder: for example, to add a new anode chemistry for lithium-ion, add a subfolder ``input/parameters/lithium-ion/anodes/new_anode_chemistry_AuthorYear``.
This subfolder should then contain:

1. **Complete parameter file**: Parameter sets should be added as csv files in the appropriate chemistry folder in ``input/parameters/`` (add a new folder if no parameters exist for that chemistry yet).
The expected structure of the csv file is
- a csv file ``parameters.csv`` with all the relevant scalar parameters. The expected structure of the csv file is:

+-------------------------+----------------------+-----------------------+-------------+
| Name [Units] | Value | Reference | Notes |
+=========================+======================+=======================+=============+
| Example [m] | 13 | bloggs2019 | an example |
| Example [m] | 13 | AuthorYear | an example |
+-------------------------+----------------------+-----------------------+-------------+

Empty lines, and lines starting with ``#``, will be ignored.

2. **Parameters for a single material**: It's possible to add parameters for a single material (e.g. anode) and then re-use existing parameters for the other materials. To do this, add a new subfolder with a ``README.md`` for references and csv file of parameters (e.g. ``input/parameters/lithium-ion/anodes/new_anode_chemistry_Bloggs2019/``). Then this file can be referenced using the ``chemistry`` option in ``ParameterValues``, e.g.
- a ``README.md`` file with information on where these parameters came from
- python files for any functions, which should be referenced from the ``parameters.csv`` file (see ``Adding a Function`` below)
- csv files for any data to be interpolated, which should be referenced from the ``parameters.csv`` file (see ``Adding data for interpolation`` below)

.. code-block:: python
param = pybamm.ParameterValues(
chemistry={
"chemistry": "lithium-ion",
"cell": "kokam_Marquis2019",
"anode": "new_anode_chemistry_Bloggs2019",
"separator": "separator_Marquis2019",
"cathode": "lico2_Marquis2019",
"electrolyte": "lipf6_Marquis2019",
"experiment": "1C_discharge_from_full_Marquis2019",
}
)
or, equivalently in this case (since the only difference from the standard parameters from Marquis et al. is the set of anode parameters),

.. code-block:: python
param = pybamm.ParameterValues(
chemistry={
**pybamm.parameter_sets.Marquis2019,
"anode": "new_anode_chemistry_Bloggs2019",
}
)
The easiest way to start is to copy an existing file (e.g. ````input/parameters/lithium-ion/anodes/graphite_mcmb2528_Marquis2019``) and replace all entries in all files as appropriate

Adding a function
-----------------

Functions should be added as Python functions under a file with the same name in the appropriate chemistry folder in ``input/parameters/``.
These Python functions should be documented with references explaining where they were obtained.
For example, we would put the following Python function in a file ``input/parameters/lead-acid/diffusivity_Bloggs2019.py``
For example, we would put the following Python function in a file ``input/parameters/lithium_ion/anodes/new_anode_chemistry_AuthorYear/diffusivity_AuthorYear.py``

.. code-block:: python
def diffusivity_Bloggs2019(c_e):
def diffusivity_AuthorYear(c_e):
"""
Dimensional Fickian diffusivity in the electrolyte [m2.s-1], from [1]_, as a
function of the electrolyte concentration c_e [mol.m-3].
Expand All @@ -89,10 +69,92 @@ called (must be in the same folder), with the tag ``[function]``, for example:
+---------------------+--------------------------------------+--------------+-------------+
| Name [Units] | Value | Reference | Notes |
+=====================+======================================+==============+=============+
| Example [m2.s-1] | [function]diffusivity_Bloggs2019 | bloggs2019 | a function |
| Example [m2.s-1] | [function]diffusivity_AuthorYear | AuthorYear | a function |
+---------------------+--------------------------------------+--------------+-------------+

Adding data for interpolation
-----------------------------

Data should be added as as csv file in the appropriate chemistry folder in ``input/parameters/``.
For example, we would put the following data in a file ``input/parameters/lithium_ion/anodes/new_anode_chemistry_AuthorYear/diffusivity_AuthorYear.csv``

+--------------------------+--------------------------+
| # concentration [mol/m3] | Diffusivity [m2/s] |
+==========================+==========================+
| 0.000000000000000000e+00 | 4.714135898019971016e+00 |
| 2.040816326530612082e-02 | 4.708899441575220557e+00 |
| 4.081632653061224164e-02 | 4.702448345762175741e+00 |
| 6.122448979591836593e-02 | 4.694558534379876136e+00 |
| 8.163265306122448328e-02 | 4.684994372928071193e+00 |
| 1.020408163265306006e-01 | 4.673523893805322516e+00 |
| 1.224489795918367319e-01 | 4.659941254449398329e+00 |
| 1.428571428571428492e-01 | 4.644096031712390271e+00 |
+--------------------------+--------------------------+

Empty lines, and lines starting with ``#``, will be ignored.

Then, this data should be added to the parameter file from which it will be
called (must be in the same folder), with the tag ``[data]``, for example:

+---------------------+----------------------------------+--------------+-------------+
| Name [Units] | Value | Reference | Notes |
+=====================+==================================+==============+=============+
| Example [m2.s-1] | [data]diffusivity_AuthorYear | AuthorYear | some data |
+---------------------+----------------------------------+--------------+-------------+

Using new parameters
--------------------

If you have added a whole new set of parameters, then you can create a new parameter set in ``pybamm/parameters/parameter_sets.py``, by just adding a new dictionary to that file, for example

.. code-block:: python
AuthorYear = {
"chemistry": "lithium-ion",
"cell": "new_cell_AuthorYear",
"anode": "new_anode_AuthorYear",
"separator": "new_separator_AuthorYear",
"cathode": "new_cathode_AuthorYear",
"electrolyte": "new_electrolyte_AuthorYear",
"experiment": "new_experiment_AuthorYear",
}
Then, to use these new parameters, use:

.. code-block:: python
param = pybamm.ParameterValues(chemistry=pybamm.parameter_sets.AuthorYear)
Note that you can re-use existing parameter subsets instead of creating new ones (for example, you could just replace "experiment": "new_experiment_AuthorYear" with "experiment": "1C_discharge_from_full_Marquis2019" in the above dictionary).

It's also possible to add parameters for a single material (e.g. anode) and then re-use existing parameters for the other materials, without adding a parameter set to ``pybamm/parameters/parameter_sets.py``.

.. code-block:: python
param = pybamm.ParameterValues(
chemistry={
"chemistry": "lithium-ion",
"cell": "kokam_Marquis2019",
"anode": "new_anode_chemistry_AuthorYear",
"separator": "separator_Marquis2019",
"cathode": "lico2_Marquis2019",
"electrolyte": "lipf6_Marquis2019",
"experiment": "1C_discharge_from_full_Marquis2019",
}
)
or, equivalently in this case (since the only difference from the standard parameters from Marquis et al. is the set of anode parameters),

.. code-block:: python
param = pybamm.ParameterValues(
chemistry={
**pybamm.parameter_sets.Marquis2019,
"anode": "new_anode_chemistry_AuthorYear",
}
)
See the `"Getting Started" tutorial <https://github.com/pybamm-team/PyBaMM/blob/master/examples/notebooks/Getting%20Started/Tutorial%202%20-%20Setting%20Parameter%20Values.ipynb>`_ for examples of setting parameters in action.

Unit tests for the new class
----------------------------
Expand All @@ -105,13 +167,13 @@ Test on the models

In theory, any existing model can now be solved using the new parameters instead of their default parameters, with no extra work from here.
To test this, add something like the following test to one of the model test files
(e.g. `DFN <https://github.com/pybamm-team/PyBaMM/blob/master/tests/test_models/test_lithium_ion/test_lithium_ion_dfn.py>`_):
(e.g. `DFN <https://github.com/pybamm-team/PyBaMM/blob/master/tests/integration/test_models/test_full_battery_models/test_lithium_ion/test_dfn.py>`_):

.. code-block:: python
def test_my_new_parameters(self):
model = pybamm.lithium_ion.DFN()
parameter_values = pybamm.ParameterValues("path/to/parameter/file.csv")
parameter_values = pybamm.ParameterValues(chemistry=pybamm.parameter_sets.AuthorYear)
modeltest = tests.StandardModelTest(model, parameter_values=parameter_values)
modeltest.test_all()
Expand Down
2 changes: 1 addition & 1 deletion docs/tutorials/add-solver.rst
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ Test on the models

In theory, any existing model can now be solved using `MyFastDaeSolver` instead of their default solvers, with no extra work from here.
To test this, add something like the following test to one of the model test files
(e.g. `DFN <https://github.com/pybamm-team/PyBaMM/blob/master/tests/unit/test_models/test_lithium_ion/test_lithium_ion_dfn.py>`_):
(e.g. `DFN <https://github.com/pybamm-team/PyBaMM/blob/master/tests/integration/test_models/test_full_battery_models/test_lithium_ion/test_dfn.py>`_):

.. code-block:: python
Expand Down
2 changes: 1 addition & 1 deletion docs/tutorials/add-spatial-method.rst
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ Test on the models

In theory, any existing model can now be discretised using ``MyFastMethod`` instead of their default spatial methods, with no extra work from here.
To test this, add something like the following test to one of the model test files
(e.g. `DFN <https://github.com/pybamm-team/PyBaMM/blob/master/tests/unit/test_models/test_lithium_ion/test_lithium_ion_dfn.py>`_):
(e.g. `DFN <https://github.com/pybamm-team/PyBaMM/blob/master/tests/integration/test_models/test_full_battery_models/test_lithium_ion/test_dfn.py>`_):

.. code-block:: python
Expand Down
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,
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))
Loading

0 comments on commit 7b7f4e9

Please sign in to comment.