Repository: pybamm-team/PyBaMM (python)

Issue:
Processing Interpolants bug
When there are multiple interpolants in the model, they all have the same id so they get processed to the same object

Pull request:
#961 fix interpolant ids
# Description

Change interpolant id so that different interpolants have different ids

Fixes #961 

## Type of change

Please add a line in the relevant section of [CHANGELOG.md](https://github.com/pybamm-team/PyBaMM/blob/master/CHANGELOG.md) to document the change (include PR #) - note reverse order of PR #s. If necessary, also add to the list of breaking changes.

- [ ] New feature (non-breaking change which adds functionality)
- [ ] Optimization (back-end change that speeds up the code)
- [x] Bug fix (non-breaking change which fixes an issue)


# Key checklist:

- [x] No style issues: `$ flake8`
- [x] All tests pass: `$ python run-tests.py --unit`
- [x] The documentation builds: `$ cd docs` and then `$ make clean; make html`

You can run all three at once, using `$ python run-tests.py --quick`.

## Further checks:

- [x] Code is commented, particularly in hard-to-understand areas
- [x] Tests added that prove fix is effective or that feature works


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f940e63a8..c477799b2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -15,6 +15,7 @@
 
 ## Bug fixes
 
+-   Fixed `Interpolant` ids to allow processing ([#962](https://github.com/pybamm-team/PyBaMM/pull/962)
 -   Changed simulation attributes to assign copies rather than the objects themselves ([#952](https://github.com/pybamm-team/PyBaMM/pull/952)
 -   Added default values to base model so that it works with the `Simulation` class ([#952](https://github.com/pybamm-team/PyBaMM/pull/952)
 -   Fixed solver to recompute initial conditions when inputs are changed ([#951](https://github.com/pybamm-team/PyBaMM/pull/951)
diff --git a/pybamm/expression_tree/interpolant.py b/pybamm/expression_tree/interpolant.py
index 19a6e5448..de49da96f 100644
--- a/pybamm/expression_tree/interpolant.py
+++ b/pybamm/expression_tree/interpolant.py
@@ -31,7 +31,13 @@ class Interpolant(pybamm.Function):
     """
 
     def __init__(
-        self, data, child, name=None, interpolator="cubic spline", extrapolate=True
+        self,
+        data,
+        child,
+        name=None,
+        interpolator="cubic spline",
+        extrapolate=True,
+        entries_string=None,
     ):
         if data.ndim != 2 or data.shape[1] != 2:
             raise ValueError(
@@ -56,16 +62,38 @@ class Interpolant(pybamm.Function):
             name = "interpolating function ({})".format(name)
         else:
             name = "interpolating function"
+        self.data = data
+        self.entries_string = entries_string
         super().__init__(
             interpolating_function, child, name=name, derivative="derivative"
         )
         # Store information as attributes
-        self.data = data
         self.x = data[:, 0]
         self.y = data[:, 1]
         self.interpolator = interpolator
         self.extrapolate = extrapolate
 
+    @property
+    def entries_string(self):
+        return self._entries_string
+
+    @entries_string.setter
+    def entries_string(self, value):
+        # We must include the entries in the hash, since different arrays can be
+        # indistinguishable by class, name and domain alone
+        # Slightly different syntax for sparse and non-sparse matrices
+        if value is not None:
+            self._entries_string = value
+        else:
+            entries = self.data
+            self._entries_string = entries.tostring()
+
+    def set_id(self):
+        """ See :meth:`pybamm.Symbol.set_id()`. """
+        self._id = hash(
+            (self.__class__, self.name, self.entries_string) + tuple(self.domain)
+        )
+
     def _function_new_copy(self, children):
         """ See :meth:`Function._function_new_copy()` """
         return pybamm.Interpolant(
@@ -73,5 +101,6 @@ class Interpolant(pybamm.Function):
             *children,
             name=self.name,
             interpolator=self.interpolator,
-            extrapolate=self.extrapolate
+            extrapolate=self.extrapolate,
+            entries_string=self.entries_string
         )


Test patch:
diff --git a/tests/unit/test_parameters/test_parameter_values.py b/tests/unit/test_parameters/test_parameter_values.py
index 39156f62a..351f7b23e 100644
--- a/tests/unit/test_parameters/test_parameter_values.py
+++ b/tests/unit/test_parameters/test_parameter_values.py
@@ -331,11 +331,11 @@ class TestParameterValues(unittest.TestCase):
         x = np.linspace(0, 10)[:, np.newaxis]
         data = np.hstack([x, 2 * x])
         parameter_values = pybamm.ParameterValues(
-            {"a": 3.01, "Diffusivity": ("times two", data)}
+            {"a": 3.01, "Times two": ("times two", data)}
         )
 
         a = pybamm.Parameter("a")
-        func = pybamm.FunctionParameter("Diffusivity", {"a": a})
+        func = pybamm.FunctionParameter("Times two", {"a": a})
 
         processed_func = parameter_values.process_symbol(func)
         self.assertIsInstance(processed_func, pybamm.Interpolant)
@@ -346,6 +346,16 @@ class TestParameterValues(unittest.TestCase):
         processed_diff_func = parameter_values.process_symbol(diff_func)
         self.assertEqual(processed_diff_func.evaluate(), 2)
 
+        # interpolant defined up front
+        interp2 = pybamm.Interpolant(data, a)
+        processed_interp2 = parameter_values.process_symbol(interp2)
+        self.assertEqual(processed_interp2.evaluate(), 6.02)
+
+        data3 = np.hstack([x, 3 * x])
+        interp3 = pybamm.Interpolant(data3, a)
+        processed_interp3 = parameter_values.process_symbol(interp3)
+        self.assertEqual(processed_interp3.evaluate(), 9.03)
+
     def test_interpolant_against_function(self):
         parameter_values = pybamm.ParameterValues({})
         parameter_values.update(