Repository: tinosulzer/PyBaMM

Issue:
Symbolic differentiation
We need a function (e.g. `pybamm.diff`) that calculates the derivative of an expression tree with respect to a variable. For example, some lead-acid models require the user to provide a function for the open-circuit potential `U(c)` while others require its derivative `dU/dc` (and also for the exchange-current density). 

We could either ask the user to provide the appropriate derivatives manually at the same time as the functions themselves, or calculate the derivative using the expression tree and appropriate applications of the chain rule and power rule. I think the latter is safer

Discussion:

Patch:
diff --git a/docs/source/expression_tree/parameter.rst b/docs/source/expression_tree/parameter.rst
index bb6cdf6a..d49ca150 100644
--- a/docs/source/expression_tree/parameter.rst
+++ b/docs/source/expression_tree/parameter.rst
@@ -4,3 +4,5 @@ Parameter
 .. autoclass:: pybamm.Parameter
   :members:
 
+.. autoclass:: pybamm.FunctionParameter
+  :members:
diff --git a/pybamm/__init__.py b/pybamm/__init__.py
index 7dd7592b..6f87435a 100644
--- a/pybamm/__init__.py
+++ b/pybamm/__init__.py
@@ -67,7 +67,6 @@ from .expression_tree.concatenations import (
 )
 from .expression_tree.array import Array
 from .expression_tree.matrix import Matrix
-from .expression_tree.parameter import Parameter
 from .expression_tree.unary_operators import (
     UnaryOperator,
     Negate,
@@ -82,8 +81,8 @@ from .expression_tree.unary_operators import (
     div,
     surf,
 )
+from .expression_tree.parameter import Parameter, FunctionParameter
 from .expression_tree.broadcasts import Broadcast, NumpyBroadcast
-from .expression_tree.function_parameter import FunctionParameter
 from .expression_tree.scalar import Scalar
 from .expression_tree.variable import Variable
 from .expression_tree.independent_variable import (
diff --git a/pybamm/expression_tree/function_parameter.py b/pybamm/expression_tree/function_parameter.py
deleted file mode 100644
index 7cc3a1cd..00000000
--- a/pybamm/expression_tree/function_parameter.py
+++ /dev/null
@@ -1,27 +0,0 @@
-#
-# Parameter class
-#
-from __future__ import absolute_import, division
-from __future__ import print_function, unicode_literals
-import pybamm
-
-
-class FunctionParameter(pybamm.UnaryOperator):
-    """A node in the expression tree representing a function parameter
-
-    This node will be replaced by a :class:`.Function` node
-
-    Parameters
-    ----------
-
-    name : str
-        name of the node
-    child : :class:`Symbol`
-        child node
-    domain : iterable of str, optional
-        list of domains the parameter is valid over, defaults to empty list
-
-    """
-
-    def __init__(self, name, child):
-        super().__init__(name, child)
diff --git a/pybamm/expression_tree/parameter.py b/pybamm/expression_tree/parameter.py
index 1195356c..f305972c 100644
--- a/pybamm/expression_tree/parameter.py
+++ b/pybamm/expression_tree/parameter.py
@@ -1,5 +1,5 @@
 #
-# Parameter class
+# Parameter classes
 #
 from __future__ import absolute_import, division
 from __future__ import print_function, unicode_literals
@@ -23,3 +23,34 @@ class Parameter(pybamm.Symbol):
 
     def __init__(self, name, domain=[]):
         super().__init__(name, domain=domain)
+
+
+class FunctionParameter(pybamm.UnaryOperator):
+    """A node in the expression tree representing a function parameter
+
+    This node will be replaced by a :class:`pybamm.Function` node
+
+    Parameters
+    ----------
+
+    name : str
+        name of the node
+    child : :class:`Symbol`
+        child node
+    diff_variable : :class:`pybamm.Symbol`, optional
+        if diff_variable is specified, the FunctionParameter node will be replaced by a
+        :class:`pybamm.Function` and then differentiated with respect to diff_variable.
+        Default is None.
+
+    """
+
+    def __init__(self, name, child, diff_variable=None):
+        super().__init__(name, child)
+        # assign diff variable
+        self.diff_variable = diff_variable
+
+    def diff(self, variable):
+        """ See :meth:`pybamm.Symbol.diff()`. """
+        # return a new FunctionParameter, that knows it will need to be differentiated
+        # when the parameters are set
+        return FunctionParameter(self.name, self.orphans[0], diff_variable=variable)
diff --git a/pybamm/expression_tree/scalar.py b/pybamm/expression_tree/scalar.py
index 70cfa0a8..c03ea53f 100644
--- a/pybamm/expression_tree/scalar.py
+++ b/pybamm/expression_tree/scalar.py
@@ -54,7 +54,7 @@ class Scalar(pybamm.Symbol):
 
     @value.setter
     def value(self, value):
-        self._value = value
+        self._value = float(value)
 
     def evaluate(self, t=None, y=None):
         """ See :meth:`pybamm.Symbol.evaluate()`. """
diff --git a/pybamm/parameters/parameter_values.py b/pybamm/parameters/parameter_values.py
index e7e48560..cfe589b6 100644
--- a/pybamm/parameters/parameter_values.py
+++ b/pybamm/parameters/parameter_values.py
@@ -152,7 +152,13 @@ class ParameterValues(dict):
         elif isinstance(symbol, pybamm.FunctionParameter):
             new_child = self.process_symbol(symbol.children[0])
             function_name = self.get_parameter_value(symbol)
-            return pybamm.Function(pybamm.load_function(function_name), new_child)
+            function = pybamm.Function(pybamm.load_function(function_name), new_child)
+            if symbol.diff_variable is None:
+                return function
+            else:
+                # return differentiated function
+                new_diff_variable = self.process_symbol(symbol.children[0])
+                return function.diff(new_diff_variable)
 
         elif isinstance(symbol, pybamm.BinaryOperator):
             left, right = symbol.children


Test patch:
diff --git a/tests/test_discretisations/test_discretisation.py b/tests/test_discretisations/test_discretisation.py
index dfd82b0d..f2c4200f 100644
--- a/tests/test_discretisations/test_discretisation.py
+++ b/tests/test_discretisations/test_discretisation.py
@@ -149,10 +149,10 @@ class TestDiscretise(unittest.TestCase):
     def test_process_complex_expression(self):
         var1 = pybamm.Variable("var1")
         var2 = pybamm.Variable("var2")
-        scal1 = pybamm.Scalar("scal1")
-        scal2 = pybamm.Scalar("scal2")
-        scal3 = pybamm.Scalar("scal3")
-        scal4 = pybamm.Scalar("scal4")
+        scal1 = pybamm.Scalar(1)
+        scal2 = pybamm.Scalar(2)
+        scal3 = pybamm.Scalar(3)
+        scal4 = pybamm.Scalar(4)
         expression = (scal1 * (scal3 + var2)) / ((var1 - scal4) + scal2)
 
         # create discretisation
diff --git a/tests/test_expression_tree/test_parameter.py b/tests/test_expression_tree/test_parameter.py
index aa51851a..5db9dd7e 100644
--- a/tests/test_expression_tree/test_parameter.py
+++ b/tests/test_expression_tree/test_parameter.py
@@ -17,6 +17,21 @@ class TestParameter(unittest.TestCase):
         self.assertEqual(c.domain[0], "test")
 
 
+class TestFunctionParameter(unittest.TestCase):
+    def test_function_parameter_init(self):
+        var = pybamm.Variable("var")
+        func = pybamm.FunctionParameter("func", var)
+        self.assertEqual(func.name, "func")
+        self.assertEqual(func.children[0].id, var.id)
+        self.assertEqual(func.domain, [])
+        self.assertEqual(func.diff_variable, None)
+
+    def test_function_parameter_diff(self):
+        var = pybamm.Variable("var")
+        func = pybamm.FunctionParameter("a", var).diff(var)
+        self.assertEqual(func.diff_variable, var)
+
+
 if __name__ == "__main__":
     print("Add -v for more debug output")
     import sys
diff --git a/tests/test_parameters/test_parameter_values.py b/tests/test_parameters/test_parameter_values.py
index 3bc421b2..62328b10 100644
--- a/tests/test_parameters/test_parameter_values.py
+++ b/tests/test_parameters/test_parameter_values.py
@@ -145,13 +145,18 @@ class TestParameterValues(unittest.TestCase):
         self.assertIsInstance(processed_func, pybamm.Function)
         self.assertEqual(processed_func.evaluate(), 369)
 
+        # process differentiated function parameter
+        diff_func = func.diff(a)
+        processed_diff_func = parameter_values.process_symbol(diff_func)
+        self.assertEqual(processed_diff_func.evaluate(), 123)
+
     def test_process_complex_expression(self):
         var1 = pybamm.Variable("var1")
         var2 = pybamm.Variable("var2")
         par1 = pybamm.Parameter("par1")
         par2 = pybamm.Parameter("par2")
-        scal1 = pybamm.Scalar("scal1")
-        scal2 = pybamm.Scalar("scal2")
+        scal1 = pybamm.Scalar(3)
+        scal2 = pybamm.Scalar(4)
         expression = (scal1 * (par1 + var2)) / ((var1 - par2) + scal2)
 
         param = pybamm.ParameterValues(base_parameters={"par1": 1, "par2": 2})