예제 #1
0
 def test_sqrt(self):
     a = pybamm.InputParameter("a")
     fun = pybamm.sqrt(a)
     self.assertIsInstance(fun, pybamm.Sqrt)
     self.assertEqual(fun.evaluate(inputs={"a": 3}), np.sqrt(3))
     h = 0.0000001
     self.assertAlmostEqual(
         fun.diff(a).evaluate(inputs={"a": 3}),
         (pybamm.sqrt(pybamm.Scalar(3 + h)).evaluate() -
          fun.evaluate(inputs={"a": 3})) / h,
         places=5,
     )
예제 #2
0
    def test_model_solver_failure(self):
        # Create model
        model = pybamm.BaseModel()
        var = pybamm.Variable("var")
        model.rhs = {var: -pybamm.sqrt(var)}
        model.initial_conditions = {var: 1}
        # add events so that safe mode is used (won't be triggered)
        model.events = [pybamm.Event("10", var - 10)]
        # No need to set parameters; can use base discretisation (no spatial operators)

        # create discretisation
        disc = pybamm.Discretisation()
        model_disc = disc.process_model(model, inplace=False)

        solver = pybamm.CasadiSolver(
            extra_options_call={"regularity_check": False})
        # Solve with failure at t=2
        t_eval = np.linspace(0, 20, 100)
        with self.assertRaises(pybamm.SolverError):
            solver.solve(model_disc, t_eval)
        # Solve with failure at t=0
        model.initial_conditions = {var: 0}
        model_disc = disc.process_model(model, inplace=False)
        t_eval = np.linspace(0, 20, 100)
        with self.assertRaises(pybamm.SolverError):
            solver.solve(model_disc, t_eval)
def electrolyte_conductivity_Landesfeind2019_base(c_e, T, coeffs):
    """
    Conductivity of LiPF6 in solvent_X as a function of ion concentration and
    Temperature. The data comes from [1].
    References
    ----------
    .. [1] Landesfeind, J. and Gasteiger, H.A., 2019. Temperature and Concentration
    Dependence of the Ionic Transport Properties of Lithium-Ion Battery Electrolytes.
    Journal of The Electrochemical Society, 166(14), pp.A3079-A3097.
    ----------
    c_e: :class: `numpy.Array`
        Dimensional electrolyte concentration
    T: :class: `numpy.Array`
        Dimensional temperature
    coeffs: :class: `numpy.Array`
        Fitting parameter coefficients
    Returns
    -------
    :`numpy.Array`
        Electrolyte diffusivity
    """
    c = c_e / 1000  # mol.m-3 -> mol.l
    p1, p2, p3, p4, p5, p6 = coeffs
    A = p1 * (1 + (T - p2))
    B = 1 + p3 * sqrt(c) + p4 * (1 + p5 * exp(1000 / T)) * c
    C = 1 + c**4 * (p6 * exp(1000 / T))
    sigma_e = A * c * B / C  # mS.cm-1

    return sigma_e / 10
예제 #4
0
    def test_model_ode_integrate_failure(self):
        # Turn off warnings to ignore sqrt error
        warnings.simplefilter("ignore")

        model = pybamm.BaseModel()
        var = pybamm.Variable("var")
        model.rhs = {var: -pybamm.sqrt(var)}
        model.initial_conditions = {var: 1}
        disc = pybamm.Discretisation()
        disc.process_model(model)

        t_eval = np.linspace(0, 3, 100)
        solver = pybamm.ScikitsOdeSolver()
        # Expect solver to fail when y goes negative
        with self.assertRaises(pybamm.SolverError):
            solver.solve(model, t_eval)

        # Turn warnings back on
        warnings.simplefilter("default")
예제 #5
0
    def test_solver_only_works_with_jax(self):
        model = pybamm.BaseModel()
        var = pybamm.Variable("var")
        model.rhs = {var: -pybamm.sqrt(var)}
        model.initial_conditions = {var: 1}
        # No need to set parameters; can use base discretisation (no spatial operators)

        # create discretisation
        disc = pybamm.Discretisation()
        disc.process_model(model)

        t_eval = np.linspace(0, 3, 100)

        # solver needs a model converted to jax
        for convert_to_format in ["casadi", "python", "something_else"]:
            model.convert_to_format = convert_to_format

            solver = pybamm.JaxSolver()
            with self.assertRaisesRegex(RuntimeError, "must be converted to JAX"):
                solver.solve(model, t_eval)
예제 #6
0
    def test_model_solver_failure(self):
        # Turn off warnings to ignore sqrt error
        warnings.simplefilter("ignore")
        model = pybamm.BaseModel()
        model.convert_to_format = "python"
        var = pybamm.Variable("var")
        model.rhs = {var: -pybamm.sqrt(var)}
        model.initial_conditions = {var: 1}
        # No need to set parameters; can use base discretisation (no spatial operators)

        # create discretisation
        disc = pybamm.Discretisation()
        disc.process_model(model)

        t_eval = np.linspace(0, 3, 100)
        solver = pybamm.ScipySolver(rtol=1e-8, atol=1e-8, method="RK45")
        # Expect solver to fail when y goes negative
        with self.assertRaises(pybamm.SolverError):
            solver.solve(model, t_eval)

        # Turn warnings back on
        warnings.simplefilter("default")
예제 #7
0
    def test_model_solver_events(self):
        # Create model
        model = pybamm.BaseModel()
        whole_cell = ["negative electrode", "separator", "positive electrode"]
        var1 = pybamm.Variable("var1", domain=whole_cell)
        var2 = pybamm.Variable("var2", domain=whole_cell)
        model.rhs = {var1: 0.1 * var1}
        model.algebraic = {var2: 2 * var1 - var2}
        model.initial_conditions = {var1: 1, var2: 2}
        model.events = [
            pybamm.Event("var1 = 1.5", pybamm.min(var1 - 1.5)),
            pybamm.Event("var2 = 2.5", pybamm.min(var2 - 2.5)),
        ]
        disc = get_discretisation_for_testing()
        disc.process_model(model)

        # Solve using "safe" mode
        solver = pybamm.CasadiSolver(mode="safe", rtol=1e-8, atol=1e-8)
        t_eval = np.linspace(0, 5, 100)
        solution = solver.solve(model, t_eval)
        np.testing.assert_array_less(solution.y.full()[0, :-1], 1.5)
        np.testing.assert_array_less(solution.y.full()[-1, :-1], 2.5)
        np.testing.assert_equal(solution.t_event[0], solution.t[-1])
        np.testing.assert_array_equal(solution.y_event[:, 0],
                                      solution.y.full()[:, -1])
        np.testing.assert_array_almost_equal(solution.y.full()[0],
                                             np.exp(0.1 * solution.t),
                                             decimal=5)
        np.testing.assert_array_almost_equal(solution.y.full()[-1],
                                             2 * np.exp(0.1 * solution.t),
                                             decimal=5)

        # Solve using "safe" mode with debug off
        pybamm.settings.debug_mode = False
        solver = pybamm.CasadiSolver(mode="safe",
                                     rtol=1e-8,
                                     atol=1e-8,
                                     dt_max=1)
        t_eval = np.linspace(0, 5, 100)
        solution = solver.solve(model, t_eval)
        np.testing.assert_array_less(solution.y.full()[0], 1.5)
        np.testing.assert_array_less(solution.y.full()[-1], 2.5 + 1e-10)
        # test the last entry is exactly 2.5
        np.testing.assert_array_almost_equal(solution.y[-1, -1],
                                             2.5,
                                             decimal=2)
        np.testing.assert_array_almost_equal(solution.y.full()[0],
                                             np.exp(0.1 * solution.t),
                                             decimal=5)
        np.testing.assert_array_almost_equal(solution.y.full()[-1],
                                             2 * np.exp(0.1 * solution.t),
                                             decimal=5)
        pybamm.settings.debug_mode = True

        # Try dt_max=0 to enforce using all timesteps
        solver = pybamm.CasadiSolver(dt_max=0, rtol=1e-8, atol=1e-8)
        t_eval = np.linspace(0, 5, 100)
        solution = solver.solve(model, t_eval)
        np.testing.assert_array_less(solution.y.full()[0], 1.5)
        np.testing.assert_array_less(solution.y.full()[-1], 2.5 + 1e-10)
        np.testing.assert_array_almost_equal(solution.y.full()[0],
                                             np.exp(0.1 * solution.t),
                                             decimal=5)
        np.testing.assert_array_almost_equal(solution.y.full()[-1],
                                             2 * np.exp(0.1 * solution.t),
                                             decimal=5)

        # Solve using "fast with events" mode
        model = pybamm.BaseModel()
        var1 = pybamm.Variable("var1")
        var2 = pybamm.Variable("var2")
        model.rhs = {var1: 0.1 * var1}
        model.algebraic = {var2: 2 * var1 - var2}
        model.initial_conditions = {var1: 1, var2: 2}
        model.events = [
            pybamm.Event("var1 = 1.5", var1 - 1.5),
            pybamm.Event("var2 = 2.5", var2 - 2.5),
            pybamm.Event("var1 = 1.5 switch", var1 - 2,
                         pybamm.EventType.SWITCH),
            pybamm.Event("var2 = 2.5 switch", var2 - 3,
                         pybamm.EventType.SWITCH),
        ]

        solver = pybamm.CasadiSolver(mode="fast with events",
                                     rtol=1e-8,
                                     atol=1e-8)
        t_eval = np.linspace(0, 5, 100)
        solution = solver.solve(model, t_eval)
        np.testing.assert_array_less(solution.y.full()[0, :-1], 1.5)
        np.testing.assert_array_less(solution.y.full()[-1, :-1], 2.5)
        np.testing.assert_equal(solution.t_event[0], solution.t[-1])
        np.testing.assert_array_almost_equal(solution.y_event[:, 0].flatten(),
                                             [1.25, 2.5],
                                             decimal=5)
        np.testing.assert_array_almost_equal(solution.y.full()[0],
                                             np.exp(0.1 * solution.t),
                                             decimal=5)
        np.testing.assert_array_almost_equal(solution.y.full()[-1],
                                             2 * np.exp(0.1 * solution.t),
                                             decimal=5)

        # Test when an event returns nan
        model = pybamm.BaseModel()
        var = pybamm.Variable("var")
        model.rhs = {var: 0.1 * var}
        model.initial_conditions = {var: 1}
        model.events = [
            pybamm.Event("event", var - 1.02),
            pybamm.Event("sqrt event", pybamm.sqrt(1.0199 - var)),
        ]
        disc = pybamm.Discretisation()
        disc.process_model(model)
        solver = pybamm.CasadiSolver(rtol=1e-8, atol=1e-8)
        solution = solver.solve(model, t_eval)
        np.testing.assert_array_less(solution.y.full()[0], 1.02 + 1e-10)
        np.testing.assert_array_almost_equal(solution.y[0, -1],
                                             1.02,
                                             decimal=2)
예제 #8
0
    def __init__(self, n=100, max_x=10, param=None):
        # Set fixed parameters here
        if param is None:
            param = pybamm.ParameterValues({
                "Far-field concentration of A [mol cm-3]":
                1e-6,
                "Diffusion Constant [cm2 s-1]":
                7.2e-6,
                "Faraday Constant [C mol-1]":
                96485.3328959,
                "Gas constant [J K-1 mol-1]":
                8.314459848,
                "Electrode Area [cm2]":
                0.07,
                "Temperature [K]":
                297.0,
                "Voltage frequency [rad s-1]":
                9.0152,
                "Voltage start [V]":
                0.5,
                "Voltage reverse [V]":
                -0.1,
                "Voltage amplitude [V]":
                0.08,
                "Scan Rate [V s-1]":
                0.08941,
            })

        # Create dimensional fixed parameters
        c_inf = pybamm.Parameter("Far-field concentration of A [mol cm-3]")
        D = pybamm.Parameter("Diffusion Constant [cm2 s-1]")
        F = pybamm.Parameter("Faraday Constant [C mol-1]")
        R = pybamm.Parameter("Gas constant [J K-1 mol-1]")
        S = pybamm.Parameter("Electrode Area [cm2]")
        T = pybamm.Parameter("Temperature [K]")

        E_start_d = pybamm.Parameter("Voltage start [V]")
        E_reverse_d = pybamm.Parameter("Voltage reverse [V]")
        deltaE_d = pybamm.Parameter("Voltage amplitude [V]")
        v = pybamm.Parameter("Scan Rate [V s-1]")

        # Create dimensional input parameters
        E0 = pybamm.InputParameter("Reversible Potential [non-dim]")
        k0 = pybamm.InputParameter("Reaction Rate [non-dim]")
        alpha = pybamm.InputParameter("Symmetry factor [non-dim]")
        Cdl = pybamm.InputParameter("Capacitance [non-dim]")
        Ru = pybamm.InputParameter("Uncompensated Resistance [non-dim]")
        omega_d = pybamm.InputParameter("Voltage frequency [rad s-1]")

        E0_d = pybamm.InputParameter("Reversible Potential [V]")
        k0_d = pybamm.InputParameter("Reaction Rate [s-1]")
        alpha = pybamm.InputParameter("Symmetry factor [non-dim]")
        Cdl_d = pybamm.InputParameter("Capacitance [F]")
        Ru_d = pybamm.InputParameter("Uncompensated Resistance [Ohm]")

        # Create scaling factors for non-dimensionalisation
        E_0 = R * T / F
        T_0 = E_0 / v
        L_0 = pybamm.sqrt(D * T_0)
        I_0 = D * F * S * c_inf / L_0

        # Non-dimensionalise parameters
        E0 = E0_d / E_0
        k0 = k0_d * L_0 / D
        Cdl = Cdl_d * S * E_0 / (I_0 * T_0)
        Ru = Ru_d * I_0 / E_0
        omega = 2 * np.pi * omega_d * T_0

        E_start = E_start_d / E_0
        E_reverse = E_reverse_d / E_0
        t_reverse = E_start - E_reverse
        deltaE = deltaE_d / E_0

        # Input voltage protocol
        Edc_forward = -pybamm.t
        Edc_backwards = pybamm.t - 2 * t_reverse
        Eapp = E_start + \
            (pybamm.t <= t_reverse) * Edc_forward + \
            (pybamm.t > t_reverse) * Edc_backwards + \
            deltaE * pybamm.sin(omega * pybamm.t)

        # create PyBaMM model object
        model = pybamm.BaseModel()

        # Create state variables for model
        theta = pybamm.Variable("ratio_A", domain="solution")
        i = pybamm.Variable("Current")

        # Effective potential
        Eeff = Eapp - i * Ru

        # Faradaic current
        i_f = pybamm.BoundaryGradient(theta, "left")

        # ODE equations
        model.rhs = {
            theta: pybamm.div(pybamm.grad(theta)),
            i: 1 / (Cdl * Ru) * (-i_f + Cdl * Eapp.diff(pybamm.t) - i),
        }

        # algebraic equations (none)
        model.algebraic = {}

        # Butler-volmer boundary condition at electrode
        theta_at_electrode = pybamm.BoundaryValue(theta, "left")
        butler_volmer = k0 * (theta_at_electrode * pybamm.exp(-alpha *
                                                              (Eeff - E0)) -
                              (1 - theta_at_electrode) * pybamm.exp(
                                  (1 - alpha) * (Eeff - E0)))

        # Boundary and initial conditions
        model.boundary_conditions = {
            theta: {
                "right": (pybamm.Scalar(1), "Dirichlet"),
                "left": (butler_volmer, "Neumann"),
            }
        }

        model.initial_conditions = {
            theta: pybamm.Scalar(1),
            i: Cdl * (-1.0 + deltaE * omega),
        }

        # set spatial variables and solution domain geometry
        x = pybamm.SpatialVariable('x', domain="solution")
        default_geometry = pybamm.Geometry({
            "solution": {
                x: {
                    "min": pybamm.Scalar(0),
                    "max": pybamm.Scalar(max_x)
                }
            }
        })

        default_var_pts = {x: n}

        # Using Finite Volume discretisation on an expanding 1D grid for solution
        default_submesh_types = {
            "solution":
            pybamm.MeshGenerator(pybamm.Exponential1DSubMesh, {'side': 'left'})
        }
        default_spatial_methods = {"solution": pybamm.FiniteVolume()}

        # model variables
        model.variables = {
            "Current [non-dim]": i,
        }

        #--------------------------------

        # Set model parameters
        param.process_model(model)
        geometry = default_geometry
        param.process_geometry(geometry)

        # Create mesh and discretise model
        mesh = pybamm.Mesh(geometry, default_submesh_types, default_var_pts)
        disc = pybamm.Discretisation(mesh, default_spatial_methods)
        disc.process_model(model)

        # Create solver
        solver = pybamm.CasadiSolver(
            mode="fast",
            rtol=1e-9,
            atol=1e-9,
            extra_options_setup={'print_stats': False})
        #model.convert_to_format = 'jax'
        #solver = pybamm.JaxSolver(method='BDF')
        #model.convert_to_format = 'python'
        #solver = pybamm.ScipySolver(method='BDF')

        # Store discretised model and solver
        self._model = model
        self._solver = solver
        self._fast_solver = None
        self._omega_d = param["Voltage frequency [rad s-1]"]

        self._I_0 = param.process_symbol(I_0).evaluate()
        self._T_0 = param.process_symbol(T_0).evaluate()
        self._E_0 = param.process_symbol(E_0).evaluate()
        self._L_0 = param.process_symbol(L_0).evaluate()
        self._S = param.process_symbol(S).evaluate()
        self._D = param.process_symbol(D).evaluate()
        self._default_var_points = default_var_pts
예제 #9
0
    def test_model_solver_events(self):
        # Create model
        model = pybamm.BaseModel()
        whole_cell = ["negative electrode", "separator", "positive electrode"]
        var1 = pybamm.Variable("var1", domain=whole_cell)
        var2 = pybamm.Variable("var2", domain=whole_cell)
        model.rhs = {var1: 0.1 * var1}
        model.algebraic = {var2: 2 * var1 - var2}
        model.initial_conditions = {var1: 1, var2: 2}
        model.events = [
            pybamm.Event("var1 = 1.5", pybamm.min(var1 - 1.5)),
            pybamm.Event("var2 = 2.5", pybamm.min(var2 - 2.5)),
        ]
        disc = get_discretisation_for_testing()
        disc.process_model(model)

        # Solve using "safe" mode
        solver = pybamm.CasadiSolver(mode="safe", rtol=1e-8, atol=1e-8)
        t_eval = np.linspace(0, 5, 100)
        solution = solver.solve(model, t_eval)
        np.testing.assert_array_less(solution.y[0], 1.5)
        np.testing.assert_array_less(solution.y[-1], 2.5)
        np.testing.assert_array_almost_equal(solution.y[0],
                                             np.exp(0.1 * solution.t),
                                             decimal=5)
        np.testing.assert_array_almost_equal(solution.y[-1],
                                             2 * np.exp(0.1 * solution.t),
                                             decimal=5)

        # Solve using "safe" mode with debug off
        pybamm.settings.debug_mode = False
        solver = pybamm.CasadiSolver(mode="safe",
                                     rtol=1e-8,
                                     atol=1e-8,
                                     dt_max=1)
        t_eval = np.linspace(0, 5, 100)
        solution = solver.solve(model, t_eval)
        np.testing.assert_array_less(solution.y[0], 1.5)
        np.testing.assert_array_less(solution.y[-1], 2.5)
        np.testing.assert_array_almost_equal(solution.y[0],
                                             np.exp(0.1 * solution.t),
                                             decimal=5)
        np.testing.assert_array_almost_equal(solution.y[-1],
                                             2 * np.exp(0.1 * solution.t),
                                             decimal=5)
        pybamm.settings.debug_mode = True

        # Solve using "old safe" mode
        solver = pybamm.CasadiSolver(mode="old safe", rtol=1e-8, atol=1e-8)
        t_eval = np.linspace(0, 5, 100)
        solution = solver.solve(model, t_eval)
        np.testing.assert_array_less(solution.y[0], 1.5)
        np.testing.assert_array_less(solution.y[-1], 2.5)
        np.testing.assert_array_almost_equal(solution.y[0],
                                             np.exp(0.1 * solution.t),
                                             decimal=5)
        np.testing.assert_array_almost_equal(solution.y[-1],
                                             2 * np.exp(0.1 * solution.t),
                                             decimal=5)

        # Test when an event returns nan
        model = pybamm.BaseModel()
        var = pybamm.Variable("var")
        model.rhs = {var: 0.1 * var}
        model.initial_conditions = {var: 1}
        model.events = [
            pybamm.Event("event", var - 1.02),
            pybamm.Event("sqrt event", pybamm.sqrt(1.0199 - var)),
        ]
        disc = pybamm.Discretisation()
        disc.process_model(model)
        solver = pybamm.CasadiSolver(rtol=1e-8, atol=1e-8)
        solution = solver.solve(model, t_eval)
        np.testing.assert_array_less(solution.y[0], 1.02)
예제 #10
0
    def __init__(self, param=None):
        # Set fixed parameters here
        if param is None:
            param = pybamm.ParameterValues({
                "Far-field concentration of S(soln) [mol cm-3]":
                1e-6,
                "Diffusion Constant [cm2 s-1]":
                7.2e-6,
                "Faraday Constant [C mol-1]":
                96485.3328959,
                "Gas constant [J K-1 mol-1]":
                8.314459848,
                "Electrode Area [cm2]":
                0.07,
                "Temperature [K]":
                273.0,
                "Voltage frequency [rad s-1]":
                9.0152,
                "Voltage start [V]":
                0.4,
                "Voltage reverse [V]":
                -0.4,
                "Voltage amplitude [V]":
                0.0,  # 0.05,
                "Scan Rate [V s-1]":
                0.05,
                "Electrode Coverage [mol cm2]":
                6.5e-12,
            })

        # Create dimensional fixed parameters
        D = pybamm.Parameter("Diffusion Constant [cm2 s-1]")
        F = pybamm.Parameter("Faraday Constant [C mol-1]")
        R = pybamm.Parameter("Gas constant [J K-1 mol-1]")
        a = pybamm.Parameter("Electrode Area [cm2]")
        T = pybamm.Parameter("Temperature [K]")
        omega_d = pybamm.Parameter("Voltage frequency [rad s-1]")
        E_start_d = pybamm.Parameter("Voltage start [V]")
        E_reverse_d = pybamm.Parameter("Voltage reverse [V]")
        deltaE_d = pybamm.Parameter("Voltage amplitude [V]")
        v = pybamm.Parameter("Scan Rate [V s-1]")
        Gamma = pybamm.Parameter("Electrode Coverage [mol cm2]")

        # Create dimensional input parameters
        E0_d = pybamm.InputParameter("Reversible Potential [V]")
        k0_d = pybamm.InputParameter("Redox Rate [s-1]")
        kcat_d = pybamm.InputParameter("Catalytic Rate [cm3 mol-l s-1]")
        alpha = pybamm.InputParameter("Symmetry factor [non-dim]")
        Cdl_d = pybamm.InputParameter("Capacitance [F]")
        Ru_d = pybamm.InputParameter("Uncompensated Resistance [Ohm]")

        # Create scaling factors for non-dimensionalisation
        E_0 = R * T / F
        T_0 = E_0 / v
        I_0 = F * a * Gamma / T
        L_0 = pybamm.sqrt(D * T_0)

        # Non-dimensionalise parameters
        E0 = E0_d / E_0
        k0 = k0_d * T_0
        kcat = kcat_d * Gamma * L_0 / D
        Cdl = Cdl_d * a * E_0 / (I_0 * T_0)
        Ru = Ru_d * I_0 / E_0

        omega = 2 * np.pi * omega_d * T_0
        E_start = E_start_d / E_0
        E_reverse = E_reverse_d / E_0
        t_reverse = E_start - E_reverse
        deltaE = deltaE_d / E_0

        # Input voltage protocol
        Edc_forward = -pybamm.t
        Edc_backwards = pybamm.t - 2 * t_reverse
        Eapp = E_start + \
            (pybamm.t <= t_reverse) * Edc_forward + \
            (pybamm.t > t_reverse) * Edc_backwards + \
            deltaE * pybamm.sin(omega * pybamm.t)

        # create PyBaMM model object
        model = pybamm.BaseModel()

        # Create state variables for model
        theta = pybamm.Variable("O(surf) [non-dim]")
        c = pybamm.Variable("S(soln) [non-dim]", domain="solution")
        i = pybamm.Variable("Current [non-dim]")

        # Effective potential
        Eeff = Eapp - i * Ru

        # Faridaic current (Butler Volmer)
        i_f = k0 * ((1 - theta) * pybamm.exp(
            (1 - alpha) * (Eeff - E0)) - theta * pybamm.exp(-alpha *
                                                            (Eeff - E0)))

        c_at_electrode = pybamm.BoundaryValue(c, "left")

        # Catalytic current
        i_cat = kcat * c_at_electrode * (1 - theta)

        # ODE equations
        model.rhs = {
            theta: i_f + i_cat,
            i: 1 / (Cdl * Ru) * (i_f + Cdl * Eapp.diff(pybamm.t) - i - i_cat),
            c: pybamm.div(pybamm.grad(c)),
        }

        # algebraic equations (none)
        model.algebraic = {}

        # Boundary and initial conditions
        model.boundary_conditions = {
            c: {
                "right": (pybamm.Scalar(1), "Dirichlet"),
                "left": (i_cat, "Neumann"),
            }
        }

        model.initial_conditions = {
            theta: pybamm.Scalar(1),
            i: Cdl * Eapp.diff(pybamm.t),
            c: pybamm.Scalar(1),
        }

        # set spatial variables and solution domain geometry
        x = pybamm.SpatialVariable('x', domain="solution")
        model.geometry = pybamm.Geometry({
            "solution": {
                x: {
                    "min": pybamm.Scalar(0),
                    "max": pybamm.Scalar(20)
                }
            }
        })
        model.var_pts = {x: 100}

        # Using Finite Volume discretisation on an expanding 1D grid
        model.submesh_types = {
            "solution":
            pybamm.MeshGenerator(pybamm.Exponential1DSubMesh, {'side': 'left'})
        }
        model.spatial_methods = {"solution": pybamm.FiniteVolume()}

        # model variables
        model.variables = {
            "Current [non-dim]": i,
            "O(surf) [non-dim]": theta,
            "S(soln) at electrode [non-dim]": c_at_electrode,
            "Applied Voltage [non-dim]": Eapp,
        }
        # --------------------------------

        # Set model parameters
        param.process_model(model)
        geometry = model.geometry
        param.process_geometry(geometry)

        # Create mesh and discretise model
        mesh = pybamm.Mesh(geometry, model.submesh_types, model.var_pts)
        disc = pybamm.Discretisation(mesh, model.spatial_methods)
        disc.process_model(model)

        # Create solver
        model.convert_to_format = 'python'
        solver = pybamm.ScipySolver(method='BDF', rtol=1e-6, atol=1e-6)
        #solver = pybamm.CasadiSolver(mode='fast', rtol=1e-8, atol=1e-10)

        # Store discretised model and solver
        self._model = model
        self._param = param
        self._solver = solver
        self._omega_d = param.process_symbol(omega_d).evaluate()
        self._I_0 = param.process_symbol(I_0).evaluate()
        self._T_0 = param.process_symbol(T_0).evaluate()