diff --git a/docs/source/expression_tree/concatenations.rst b/docs/source/expression_tree/concatenations.rst
new file mode 100644
index 00000000..3083003a
--- /dev/null
+++ b/docs/source/expression_tree/concatenations.rst
@@ -0,0 +1,13 @@
+Concatenations
+==============
+
+.. autoclass:: pybamm.Concatenation
+  :members:
+
+.. autoclass:: pybamm.NumpyConcatenation
+  :members:
+
+.. autoclass:: pybamm.DomainConcatenation
+  :members:
+
+.
diff --git a/docs/source/expression_tree/index.rst b/docs/source/expression_tree/index.rst
index fb267a58..8b63c475 100644
--- a/docs/source/expression_tree/index.rst
+++ b/docs/source/expression_tree/index.rst
@@ -12,3 +12,4 @@ Expression Tree
   scalar
   matrix
   vector
+  concatenations
diff --git a/pybamm/__init__.py b/pybamm/__init__.py
index 59856547..c1eacaec 100644
--- a/pybamm/__init__.py
+++ b/pybamm/__init__.py
@@ -58,7 +58,11 @@ from .expression_tree.binary_operators import (
     Multiplication,
     Division,
 )
-from .expression_tree.concatenations import Concatenation, NumpyConcatenation
+from .expression_tree.concatenations import (
+    Concatenation,
+    NumpyConcatenation,
+    DomainConcatenation,
+)
 from .expression_tree.array import Array
 from .expression_tree.matrix import Matrix
 from .expression_tree.parameter import Parameter
diff --git a/pybamm/discretisations/base_discretisation.py b/pybamm/discretisations/base_discretisation.py
index a19743e4..c27fddfa 100644
--- a/pybamm/discretisations/base_discretisation.py
+++ b/pybamm/discretisations/base_discretisation.py
@@ -188,14 +188,16 @@ class BaseDiscretisation(object):
             return pybamm.StateVector(y_slices[symbol.id])
 
         elif isinstance(symbol, pybamm.Concatenation):
-            # only know how to discretise a concatenation of scalars...
-            all_scalars = all(
-                isinstance(child, pybamm.Scalar) for child in symbol.children
-            )
-            if all_scalars:
-                return self.scalar_to_vector(symbol.children)
-            else:
-                raise NotImplementedError
+            new_children = [
+                self.process_symbol(child, y_slices, boundary_conditions)
+                for child in symbol.children
+            ]
+            new_symbol = pybamm.DomainConcatenation(new_children, self.mesh)
+
+            if new_symbol.is_constant():
+                return pybamm.Vector(new_symbol.evaluate())
+
+            return new_symbol
 
         else:
             # hack to copy the symbol but without a parent
@@ -238,49 +240,6 @@ class BaseDiscretisation(object):
         """
         raise NotImplementedError
 
-    def scalar_to_vector(self, scalars, domain=None):
-        """
-        Convert a :class:`Scalar` (or a list of :class:`Scalar`) to a uniform
-        Vector of size given by mesh. The size of the Vector is based on the
-        domains associated with the Scalar, and the size of the mesh.
-
-        Parameters
-        ----------
-        scalar: :class:`Scalar` or list of :class:`Scalar`
-            The scalar values to assign to the vector. A list can be given for
-            different values for different domains
-
-        domain : list, optional
-            If given, overrides the domains given in scalar.domain
-
-        """
-        # convert scalar to list if a single one
-        if isinstance(scalars, pybamm.Scalar):
-            scalars = [scalars]
-
-        # work out total size of vector
-        subvector_sizes = []
-        for s in scalars:
-            if domain is None:
-                this_domain = s.domain
-            else:
-                this_domain = domain
-            subvector_size = sum([self.mesh[dom].npts for dom in this_domain])
-            subvector_sizes.append(subvector_size)
-
-        # allocate vector
-        vector = np.empty(sum(subvector_sizes), dtype=float)
-
-        # assign slices of vector associated with concatenated scalars
-        start = 0
-        end = 0
-        for s, size in zip(scalars, subvector_sizes):
-            end += size
-            vector[start:end] = s.value
-            start = end
-
-        return pybamm.Vector(vector)
-
     def vector_of_ones(self, domain):
         """
         Returns a Vector of ones of the size given by the mesh.
diff --git a/pybamm/expression_tree/array.py b/pybamm/expression_tree/array.py
index b4950393..162d1095 100644
--- a/pybamm/expression_tree/array.py
+++ b/pybamm/expression_tree/array.py
@@ -10,19 +10,23 @@ class Array(pybamm.Symbol):
     """node in the expression tree that holds an tensor type variable
     (e.g. :class:`numpy.array`)
 
-    Arguements:
-    ``entries``
+    Parameters
+    ----------
+
+    entries : numpy.array
         the array associated with the node
-    ``name``
-        the name of the node. Optional, defaults to ``str(entries)`` if not provided
+    name : str, optional
+        the name of the node
+    domain : iterable of str, optional
+        list of domains the parameter is valid over, defaults to empty list
 
     *Extends:* :class:`Symbol`
     """
 
-    def __init__(self, entries, name=None):
+    def __init__(self, entries, name=None, domain=[]):
         if name is None:
             name = str(entries)
-        super().__init__(name)
+        super().__init__(name, domain=domain)
         self._entries = entries
 
     @property
diff --git a/pybamm/expression_tree/concatenations.py b/pybamm/expression_tree/concatenations.py
index 30aecaa6..1d54cc27 100644
--- a/pybamm/expression_tree/concatenations.py
+++ b/pybamm/expression_tree/concatenations.py
@@ -6,6 +6,7 @@ from __future__ import print_function, unicode_literals
 import pybamm
 
 import numpy as np
+import numbers
 
 
 class Concatenation(pybamm.Symbol):
@@ -23,7 +24,6 @@ class Concatenation(pybamm.Symbol):
     def __init__(self, *children, name=None):
         if name is None:
             name = "concatenation"
-
         domain = self.get_children_domains(children)
         super().__init__(name, children, domain=domain)
 
@@ -73,6 +73,116 @@ class NumpyConcatenation(Concatenation):
 
         super().__init__(*children, name="numpy concatenation")
 
-    def evaluate(self, t, y):
+    def evaluate(self, t=None, y=None):
         """ See :meth:`pybamm.Symbol.evaluate()`. """
         return np.concatenate([child.evaluate(t, y) for child in self.children])
+
+
+class DomainConcatenation(Concatenation):
+    """A node in the expression tree representing a concatenation of symbols.
+
+    It is assumed that each child has a domain, and the final concatenated vector will
+    respect the sizes and ordering of domains established in pybamm.KNOWN_DOMAINS
+
+    **Extends**: :class:`pybamm.Concatenation`
+
+    Parameters
+    ----------
+
+    children : iterable of :class:`pybamm.Symbol`
+        The symbols to concatenate
+
+    mesh : :class:`pybamm.BaseMesh` (or subclass)
+        The underlying mesh for discretisation, used to obtain the number of mesh points
+        in each domain.
+
+    """
+
+    def __init__(self, children, mesh):
+        # Convert any constant symbols in children to a Vector of the right size for
+        # concatenation
+
+        children = list(children)
+
+        for i, child in enumerate(children):
+            if child.is_constant():
+                children[i] = self.process_node_for_concatenate(child, mesh)
+
+        # Allow the base class to sort the domains into the correct order
+        super().__init__(*children, name="numpy concatenation")
+
+        # deal with "whole cell" special case.
+        # need to split the "whole cell" domain up when we calculate slices, then
+        # recombine again afterwards (see below)
+        if self.domain == ["whole cell"]:
+            self.domain = ["negative electrode", "separator", "positive electrode"]
+
+        # create dict of domain => slice of final vector
+        self._slices = self.create_slices(self, mesh)
+
+        # store size of final vector
+        self._size = self._slices[self.domain[-1]].stop
+
+        # deal with "whole cell" special case
+        if self.domain == ["negative electrode", "separator", "positive electrode"]:
+            self.domain = ["whole cell"]
+
+        # create disc of domain => slice for each child
+        self._children_slices = []
+        for child in self.children:
+            self._children_slices.append(self.create_slices(child, mesh))
+
+    def create_slices(self, node, mesh):
+        slices = {}
+        start = 0
+        end = 0
+        for dom in node.domain:
+            end += mesh[dom].npts
+            slices[dom] = slice(start, end)
+            start = end
+        return slices
+
+    def process_node_for_concatenate(self, node, mesh):
+        """
+        the node is assumed to be constant in time. this function replaces it with a
+        single Vector node with the correct length vector (according to its domain)
+
+        Parameters
+        ----------
+        node: derived from :class:`Symbol`
+            the sub-expression to process (node.is_constant() is true)
+
+        """
+
+        # node must be constant
+        value = node.evaluate()
+
+        # correct size of vector should be number of points in the domains
+        subvector_size = sum([mesh[dom].npts for dom in node.domain])
+
+        # check if its a scalar, if so convert to vector
+        if isinstance(value, numbers.Number):
+            value = np.full(subvector_size, value)
+
+        # check it is the right size
+        if value.size != subvector_size:
+            raise ValueError(
+                "Error: expression evaluated to a vector of incorrect length"
+            )
+
+        # convert to a Vector node
+        return pybamm.Vector(value, domain=node.domain)
+
+    def evaluate(self, t=None, y=None):
+        """ See :meth:`pybamm.Symbol.evaluate()`. """
+
+        # preallocate vector
+        vector = np.empty(self._size)
+
+        # loop through domains of children writing subvectors to final vector
+        for child, slices in zip(self.children, self._children_slices):
+            child_vector = child.evaluate(t, y)
+            for dom in child.domain:
+                vector[self._slices[dom]] = child_vector[slices[dom]]
+
+        return vector
diff --git a/pybamm/expression_tree/symbol.py b/pybamm/expression_tree/symbol.py
index fa0a3178..5e855da2 100644
--- a/pybamm/expression_tree/symbol.py
+++ b/pybamm/expression_tree/symbol.py
@@ -284,3 +284,21 @@ class Symbol(anytree.NodeMixin):
                 self, type(self)
             )
         )
+
+    def is_constant(self):
+        """returns true if evaluating the expression is not dependent on `t` or `y`
+
+        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)
+
+        # do the search, return true if no relevent nodes are found
+        return all([
+            not isinstance(n, search_types) for n in self.pre_order()
+        ])
diff --git a/pybamm/expression_tree/vector.py b/pybamm/expression_tree/vector.py
index 99132fba..b2abb123 100644
--- a/pybamm/expression_tree/vector.py
+++ b/pybamm/expression_tree/vector.py
@@ -18,31 +18,36 @@ class Vector(pybamm.Array):
         the array associated with the node
     name : str, optional
         the name of the node
+    domain : iterable of str, optional
+        list of domains the parameter is valid over, defaults to empty list
 
     """
 
-    def __init__(self, entries, name=None):
-        super().__init__(entries, name=name)
+    def __init__(self, entries, name=None, domain=[]):
+        super().__init__(entries, name=name, domain=domain)
 
 
 class StateVector(pybamm.Symbol):
     """
     node in the expression tree that holds a slice to read from an external vector type
 
-    Arguments:
+    Parameters
+    ----------
 
-    ``y_slice``
+    y_slice: slice
         the slice of an external y to read
-    ``name``
+    name: str, optional
         the name of the node
+    domain : iterable of str, optional
+        list of domains the parameter is valid over, defaults to empty list
 
     *Extends:* :class:`Array`
     """
 
-    def __init__(self, y_slice, name=None):
+    def __init__(self, y_slice, name=None, domain=[]):
         if name is None:
             name = str(y_slice)
-        super().__init__(name=name)
+        super().__init__(name=name, domain=domain)
         self._y_slice = y_slice
 
     @property

allow concatenation of variables and scalars
From @tinosulzer  in #77:

It would useful to allow concatenation of variables and scalars, to deal with a case like the following:
Interfacial current G is some function in the positive and negative electrodes, and zero in the separator. In the model, this would probably be coded as

Gn = cn ** 2
Gs = Scalar(0)
Gp = cp ** 3
G = Concatenation(Gn, Gs, Gp)
Then G should get discretised to a concatenation of an expression tree with a StateVector, representing cn, for the negative electrode, a Vector of zeros with the right shape, and an expression tree StateVector, representing cp, for the positive electrode.
diff --git a/tests/test_discretisations/test_base_discretisations.py b/tests/test_discretisations/test_base_discretisations.py
index 7ff51bea..2a13c144 100644
--- a/tests/test_discretisations/test_base_discretisations.py
+++ b/tests/test_discretisations/test_base_discretisations.py
@@ -285,42 +285,6 @@ class TestDiscretise(unittest.TestCase):
         )
         np.testing.assert_array_equal(S0 * T0, model.variables["ST"].evaluate(None, y0))
 
-    def test_scalar_to_vector(self):
-        a = pybamm.Scalar(5)
-        mesh = MeshForTesting()
-        disc = pybamm.BaseDiscretisation(mesh)
-        a_vec = disc.scalar_to_vector(a, ["whole cell"])
-        expected_vector = 5 * np.ones_like(mesh["whole cell"].nodes)
-        np.testing.assert_allclose(a_vec.evaluate(), expected_vector)
-
-        a = pybamm.Scalar(5, domain=["whole cell"])
-        a_vec = disc.scalar_to_vector(a)
-        np.testing.assert_allclose(a_vec.evaluate(), expected_vector)
-
-        a_vec = disc.scalar_to_vector(a, ["negative electrode", "whole cell"])
-        expected_vector = np.concatenate(
-            [
-                5 * np.ones_like(mesh["negative electrode"].nodes),
-                5 * np.ones_like(mesh["whole cell"].nodes),
-            ]
-        )
-        np.testing.assert_allclose(a_vec.evaluate(), expected_vector)
-
-        a = pybamm.Scalar(5, domain=["negative electrode", "whole cell"])
-        a_vec = disc.scalar_to_vector(a)
-        np.testing.assert_allclose(a_vec.evaluate(), expected_vector)
-
-        a = pybamm.Scalar(5, domain=["whole cell"])
-        b = pybamm.Scalar(4, domain=["negative electrode"])
-        a_vec = disc.scalar_to_vector([a, b])
-        expected_vector = np.concatenate(
-            [
-                5 * np.ones_like(mesh["whole cell"].nodes),
-                4 * np.ones_like(mesh["negative electrode"].nodes),
-            ]
-        )
-        np.testing.assert_allclose(a_vec.evaluate(), expected_vector)
-
     def test_vector_of_ones(self):
         mesh = MeshForTesting()
         disc = pybamm.BaseDiscretisation(mesh)
@@ -355,11 +319,6 @@ class TestDiscretise(unittest.TestCase):
         )
         np.testing.assert_allclose(eqn_disc.evaluate(), expected_vector)
 
-        # should only be able to concatentate scalars
-        eqn = pybamm.Concatenation(a, var)
-        with self.assertRaises(NotImplementedError):
-            eqn_disc = disc.process_symbol(eqn, y_slices, {})
-
 
 if __name__ == "__main__":
     print("Add -v for more debug output")
diff --git a/tests/test_expression_tree/test_concatenations.py b/tests/test_expression_tree/test_concatenations.py
index a815b4dd..79fcab95 100644
--- a/tests/test_expression_tree/test_concatenations.py
+++ b/tests/test_expression_tree/test_concatenations.py
@@ -6,6 +6,15 @@ import numpy as np
 import unittest
 
 
+class MeshForTesting(pybamm.BaseMesh):
+    def __init__(self):
+        super().__init__(None)
+        self["whole cell"] = self.submeshclass(np.linspace(0, 1, 100))
+        self["negative electrode"] = self.submeshclass(self["whole cell"].nodes[:30])
+        self["separator"] = self.submeshclass(self["whole cell"].nodes[30:40])
+        self["positive electrode"] = self.submeshclass(self["whole cell"].nodes[40:])
+
+
 class TestConcatenations(unittest.TestCase):
     def test_base_concatenation(self):
         a = pybamm.Symbol("a")
@@ -80,6 +89,51 @@ class TestConcatenations(unittest.TestCase):
             conc.evaluate(None, y), np.concatenate([y, np.array([16]), np.array([3])])
         )
 
+    def test_numpy_domain_concatenation(self):
+        mesh = MeshForTesting()
+        a_dom = ["negative electrode"]
+        b_dom = ["positive electrode"]
+        a = pybamm.Scalar(2, domain=a_dom)
+        b = pybamm.Vector(np.ones_like(mesh[b_dom[0]].nodes), domain=b_dom)
+
+        # concatenate them the "wrong" way round to check they get reordered correctly
+        conc = pybamm.DomainConcatenation([b, a], mesh)
+        np.testing.assert_array_equal(
+            conc.evaluate(),
+            np.concatenate([
+                np.full(mesh[a_dom[0]].npts, 2),
+                np.full(mesh[b_dom[0]].npts, 1)
+            ])
+        )
+
+        # vector child of wrong size will throw
+        b = pybamm.Vector(np.full(mesh[b_dom[0]].npts - 5, 1), domain=b_dom)
+        with self.assertRaises(ValueError):
+            conc = pybamm.DomainConcatenation([b, a], mesh)
+
+        # check the reordering in case a child vector has to be split up
+        a_dom = ["separator"]
+        b_dom = ["negative electrode", "positive electrode"]
+        a = pybamm.Scalar(2, domain=a_dom)
+        b = pybamm.Vector(
+            np.concatenate([np.full(mesh[b_dom[0]].npts, 1),
+                            np.full(mesh[b_dom[1]].npts, 3)]),
+            domain=b_dom
+        )
+
+        conc = pybamm.DomainConcatenation([a, b], mesh)
+        np.testing.assert_array_equal(
+            conc.evaluate(),
+            np.concatenate([
+                np.full(mesh[b_dom[0]].npts, 1),
+                np.full(mesh[a_dom[0]].npts, 2),
+                np.full(mesh[b_dom[1]].npts, 3),
+            ])
+        )
+
+        # check special case: final domain is still ["whole cell"]
+        self.assertEqual(conc.domain, ["whole cell"])
+
 
 if __name__ == "__main__":
     print("Add -v for more debug output")
diff --git a/tests/test_expression_tree/test_symbol.py b/tests/test_expression_tree/test_symbol.py
index 381abed3..79ecd094 100644
--- a/tests/test_expression_tree/test_symbol.py
+++ b/tests/test_expression_tree/test_symbol.py
@@ -6,6 +6,7 @@ from __future__ import print_function, unicode_literals
 import pybamm
 
 import unittest
+import numpy as np
 
 
 class TestSymbol(unittest.TestCase):
@@ -113,6 +114,25 @@ class TestSymbol(unittest.TestCase):
         with self.assertRaises(NotImplementedError):
             a.evaluate()
 
+    def test_symbol_is_constant(self):
+        a = pybamm.Variable("a")
+        self.assertFalse(a.is_constant())
+
+        a = pybamm.Parameter("a")
+        self.assertTrue(a.is_constant())
+
+        a = pybamm.Scalar(1) * pybamm.Variable("a")
+        self.assertFalse(a.is_constant())
+
+        a = pybamm.Scalar(1) * pybamm.Parameter("a")
+        self.assertTrue(a.is_constant())
+
+        a = pybamm.Scalar(1) * pybamm.StateVector(slice(10))
+        self.assertFalse(a.is_constant())
+
+        a = pybamm.Scalar(1) * pybamm.Vector(np.zeros(10))
+        self.assertTrue(a.is_constant())
+
     def test_symbol_repr(self):
         """
         test that __repr___ returns the string

pip install -e .[dev,docs]
parse_log_pytest
requirements.txt
pytest
3.6
requirements.txt requirements-dev.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
alabaster==0.7.13
anyio==3.6.2
anytree==2.8.0
argon2-cffi==21.3.0
argon2-cffi-bindings==21.2.0
async-generator==1.10
attrs==22.2.0
Babel==2.11.0
backcall==0.2.0
bleach==4.1.0
certifi==2021.5.30
cffi==1.15.1
charset-normalizer==2.0.12
comm==0.1.4
contextvars==2.4
cycler==0.11.0
dataclasses==0.8
decorator==5.1.1
defusedxml==0.7.1
docutils==0.18.1
entrypoints==0.4
flake8==3.9.2
guzzle-sphinx-theme==0.7.11
idna==3.10
imagesize==1.4.1
immutables==0.19
importlib-metadata==4.8.3
iniconfig==1.1.1
ipykernel==5.5.6
ipython==7.16.3
ipython-genutils==0.2.0
ipywidgets==7.8.5
jedi==0.17.2
Jinja2==3.0.3
json5==0.9.16
jsonschema==3.2.0
jupyter==1.1.1
jupyter-client==7.1.2
jupyter-console==6.4.3
jupyter-core==4.9.2
jupyter-server==1.13.1
jupyterlab==3.2.9
jupyterlab-pygments==0.1.2
jupyterlab-server==2.10.3
jupyterlab_widgets==1.1.11
kiwisolver==1.3.1
MarkupSafe==2.0.1
matplotlib==3.3.4
mccabe==0.6.1
mistune==0.8.4
nbclassic==0.3.5
nbclient==0.5.9
nbconvert==6.0.7
nbformat==5.1.3
nest-asyncio==1.6.0
notebook==6.4.10
numpy==1.19.5
packaging==21.3
pandas==1.1.5
pandocfilters==1.5.1
parso==0.7.1
pexpect==4.9.0
pickleshare==0.7.5
Pillow==8.4.0
pluggy==1.0.0
prometheus-client==0.17.1
prompt-toolkit==3.0.36
ptyprocess==0.7.0
py==1.11.0
-e git+https://github.com/tinosulzer/PyBaMM.git@d3b251dd9f1d2bfa26f82d32d7b3de45f07a2e25#egg=pybamm
pycodestyle==2.7.0
pycparser==2.21
pyflakes==2.3.1
Pygments==2.14.0
pyparsing==3.1.4
pyrsistent==0.18.0
pytest==7.0.1
python-dateutil==2.9.0.post0
pytz==2025.2
pyzmq==25.1.2
requests==2.27.1
scipy==1.5.4
Send2Trash==1.8.3
six==1.17.0
sniffio==1.2.0
snowballstemmer==2.2.0
Sphinx==5.3.0
sphinxcontrib-applehelp==1.0.2
sphinxcontrib-devhelp==1.0.2
sphinxcontrib-htmlhelp==2.0.0
sphinxcontrib-jsmath==1.0.1
sphinxcontrib-qthelp==1.0.3
sphinxcontrib-serializinghtml==1.1.5
terminado==0.12.1
testpath==0.6.0
tomli==1.2.3
tornado==6.1
traitlets==4.3.3
typing_extensions==4.1.1
urllib3==1.26.20
wcwidth==0.2.13
webencodings==0.5.1
websocket-client==1.3.1
widgetsnbextension==3.6.10
zipp==3.6.0

name: PyBaMM
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2021.5.30=py36h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.3=he6710b0_2
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - pip=21.2.2=py36h06a4308_0
  - python=3.6.13=h12debd9_1
  - readline=8.2=h5eee18b_0
  - setuptools=58.0.4=py36h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.37.1=pyhd3eb1b0_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - alabaster==0.7.13
      - anyio==3.6.2
      - anytree==2.8.0
      - argon2-cffi==21.3.0
      - argon2-cffi-bindings==21.2.0
      - async-generator==1.10
      - attrs==22.2.0
      - babel==2.11.0
      - backcall==0.2.0
      - bleach==4.1.0
      - cffi==1.15.1
      - charset-normalizer==2.0.12
      - comm==0.1.4
      - contextvars==2.4
      - cycler==0.11.0
      - dataclasses==0.8
      - decorator==5.1.1
      - defusedxml==0.7.1
      - docutils==0.18.1
      - entrypoints==0.4
      - flake8==3.9.2
      - guzzle-sphinx-theme==0.7.11
      - idna==3.10
      - imagesize==1.4.1
      - immutables==0.19
      - importlib-metadata==4.8.3
      - iniconfig==1.1.1
      - ipykernel==5.5.6
      - ipython==7.16.3
      - ipython-genutils==0.2.0
      - ipywidgets==7.8.5
      - jedi==0.17.2
      - jinja2==3.0.3
      - json5==0.9.16
      - jsonschema==3.2.0
      - jupyter==1.1.1
      - jupyter-client==7.1.2
      - jupyter-console==6.4.3
      - jupyter-core==4.9.2
      - jupyter-server==1.13.1
      - jupyterlab==3.2.9
      - jupyterlab-pygments==0.1.2
      - jupyterlab-server==2.10.3
      - jupyterlab-widgets==1.1.11
      - kiwisolver==1.3.1
      - markupsafe==2.0.1
      - matplotlib==3.3.4
      - mccabe==0.6.1
      - mistune==0.8.4
      - nbclassic==0.3.5
      - nbclient==0.5.9
      - nbconvert==6.0.7
      - nbformat==5.1.3
      - nest-asyncio==1.6.0
      - notebook==6.4.10
      - numpy==1.19.5
      - packaging==21.3
      - pandas==1.1.5
      - pandocfilters==1.5.1
      - parso==0.7.1
      - pexpect==4.9.0
      - pickleshare==0.7.5
      - pillow==8.4.0
      - pluggy==1.0.0
      - prometheus-client==0.17.1
      - prompt-toolkit==3.0.36
      - ptyprocess==0.7.0
      - py==1.11.0
      - pycodestyle==2.7.0
      - pycparser==2.21
      - pyflakes==2.3.1
      - pygments==2.14.0
      - pyparsing==3.1.4
      - pyrsistent==0.18.0
      - pytest==7.0.1
      - python-dateutil==2.9.0.post0
      - pytz==2025.2
      - pyzmq==25.1.2
      - requests==2.27.1
      - scipy==1.5.4
      - send2trash==1.8.3
      - six==1.17.0
      - sniffio==1.2.0
      - snowballstemmer==2.2.0
      - sphinx==5.3.0
      - sphinxcontrib-applehelp==1.0.2
      - sphinxcontrib-devhelp==1.0.2
      - sphinxcontrib-htmlhelp==2.0.0
      - sphinxcontrib-jsmath==1.0.1
      - sphinxcontrib-qthelp==1.0.3
      - sphinxcontrib-serializinghtml==1.1.5
      - terminado==0.12.1
      - testpath==0.6.0
      - tomli==1.2.3
      - tornado==6.1
      - traitlets==4.3.3
      - typing-extensions==4.1.1
      - urllib3==1.26.20
      - wcwidth==0.2.13
      - webencodings==0.5.1
      - websocket-client==1.3.1
      - widgetsnbextension==3.6.10
      - zipp==3.6.0
prefix: /opt/conda/envs/PyBaMM

tests/test_expression_tree/test_concatenations.py::TestConcatenations::test_numpy_domain_concatenation tests/test_expression_tree/test_symbol.py::TestSymbol::test_symbol_is_constant
tests/test_discretisations/test_base_discretisations.py::TestDiscretise::test_concatenation tests/test_discretisations/test_base_discretisations.py::TestDiscretise::test_concatenation_of_scalars tests/test_discretisations/test_base_discretisations.py::TestDiscretise::test_core_NotImplementedErrors tests/test_discretisations/test_base_discretisations.py::TestDiscretise::test_discretise_slicing tests/test_discretisations/test_base_discretisations.py::TestDiscretise::test_discretise_spatial_operator tests/test_discretisations/test_base_discretisations.py::TestDiscretise::test_process_complex_expression tests/test_discretisations/test_base_discretisations.py::TestDiscretise::test_process_dict tests/test_discretisations/test_base_discretisations.py::TestDiscretise::test_process_initial_conditions tests/test_discretisations/test_base_discretisations.py::TestDiscretise::test_process_model tests/test_discretisations/test_base_discretisations.py::TestDiscretise::test_process_symbol_base tests/test_discretisations/test_base_discretisations.py::TestDiscretise::test_vector_of_ones tests/test_expression_tree/test_concatenations.py::TestConcatenations::test_base_concatenation tests/test_expression_tree/test_concatenations.py::TestConcatenations::test_concatenation_domains tests/test_expression_tree/test_concatenations.py::TestConcatenations::test_numpy_concatenation_vector_scalar tests/test_expression_tree/test_concatenations.py::TestConcatenations::test_numpy_concatenation_vectors tests/test_expression_tree/test_symbol.py::TestSymbol::test_multiple_symbols tests/test_expression_tree/test_symbol.py::TestSymbol::test_symbol_domains tests/test_expression_tree/test_symbol.py::TestSymbol::test_symbol_evaluation tests/test_expression_tree/test_symbol.py::TestSymbol::test_symbol_init tests/test_expression_tree/test_symbol.py::TestSymbol::test_symbol_methods tests/test_expression_tree/test_symbol.py::TestSymbol::test_symbol_repr tests/test_expression_tree/test_symbol.py::TestSymbol::test_symbol_visualise
pybamm/expression_tree/array.py pybamm/__init__.py pybamm/expression_tree/vector.py pybamm/expression_tree/concatenations.py docs/source/expression_tree/concatenations.rst docs/source/expression_tree/index.rst pybamm/expression_tree/symbol.py pybamm/discretisations/base_discretisation.py
pybamm/expression_tree/array.py pybamm/__init__.py pybamm/expression_tree/vector.py pybamm/expression_tree/concatenations.py docs/source/expression_tree/concatenations.rst docs/source/expression_tree/index.rst pybamm/expression_tree/symbol.py pybamm/discretisations/base_discretisation.py