예제 #1
0
    def test_symbol_replacements(self):
        a = pybamm.Parameter("a")
        b = pybamm.Parameter("b")
        c = pybamm.Parameter("c")
        d = pybamm.Parameter("d")
        replacer = pybamm.SymbolReplacer({a: b, c: d})

        for symbol_in, symbol_out in [
            (a, b),  # just the symbol
            (a + a, b + b),  # binary operator
            (2 * pybamm.sin(a), 2 * pybamm.sin(b)),  # function
            (3 * b, 3 * b),  # no replacement
            (a + c, b + d),  # two replacements
        ]:
            replaced_symbol = replacer.process_symbol(symbol_in)
            self.assertEqual(replaced_symbol.id, symbol_out.id)

        var1 = pybamm.Variable("var 1", domain="dom 1")
        var2 = pybamm.Variable("var 2", domain="dom 2")
        var3 = pybamm.Variable("var 3", domain="dom 1")
        conc = pybamm.concatenation(var1, var2)

        replacer = pybamm.SymbolReplacer({var1: var3})
        replaced_symbol = replacer.process_symbol(conc)
        self.assertEqual(replaced_symbol.id,
                         pybamm.concatenation(var3, var2).id)
예제 #2
0
 def new_copy(self):
     """
     Creates an identical copy of the model, using the functionality of
     :class:`pybamm.SymbolReplacer` but without performing any replacements
     """
     replacer = pybamm.SymbolReplacer({})
     return replacer.process_model(self, inplace=False)
예제 #3
0
    def set_up_model_for_experiment_new(self, model):
        """
        Set up self.model to be able to run the experiment (new version).
        In this version, a new model is created for each step.

        This increases set-up time since several models to be processed, but
        reduces simulation time since the model formulation is efficient.
        """
        self.op_conds_to_model_and_param = {}
        self.op_conds_to_built_models = None
        for op_cond, op_inputs in zip(self.experiment.operating_conditions,
                                      self._experiment_inputs):
            # Create model for this operating condition if it has not already been seen
            # before
            if op_cond[:2] not in self.op_conds_to_model_and_param:
                if op_inputs["Current switch"] == 1:
                    # Current control
                    # Make a new copy of the model (we will update events later))
                    new_model = model.new_copy()
                else:
                    # Voltage or power control
                    # Create a new model where the current density is now a variable
                    # To do so, we replace all instances of the current density in the
                    # model with a current density variable, which is obtained from the
                    # FunctionControl submodel
                    # create the FunctionControl submodel and extract variables
                    external_circuit_variables = (
                        pybamm.external_circuit.FunctionControl(
                            model.param, None).get_fundamental_variables())

                    # Perform the replacement
                    symbol_replacement_map = {
                        model.variables[name]: variable
                        for name, variable in
                        external_circuit_variables.items()
                    }
                    replacer = pybamm.SymbolReplacer(symbol_replacement_map)
                    new_model = replacer.process_model(model, inplace=False)

                    # Update the algebraic equation and initial conditions for
                    # FunctionControl
                    # This creates an algebraic equation for the current to allow
                    # current, voltage, or power control, together with the appropriate
                    # guess for the initial condition.
                    # External circuit submodels are always equations on the current
                    # The external circuit function should fix either the current, or
                    # the voltage, or a combination (e.g. I*V for power control)
                    i_cell = new_model.variables["Total current density"]
                    new_model.initial_conditions[
                        i_cell] = new_model.param.current_with_time

                    # add current events to the model
                    # current events both negative and positive to catch specification
                    new_model.events.extend([
                        pybamm.Event(
                            "Current cut-off (positive) [A] [experiment]",
                            new_model.variables["Current [A]"] -
                            abs(pybamm.InputParameter("Current cut-off [A]")),
                        ),
                        pybamm.Event(
                            "Current cut-off (negative) [A] [experiment]",
                            new_model.variables["Current [A]"] +
                            abs(pybamm.InputParameter("Current cut-off [A]")),
                        ),
                    ])
                    if op_inputs["Voltage switch"] == 1:
                        new_model.algebraic[i_cell] = constant_voltage(
                            new_model.variables,
                            pybamm.Parameter("Voltage function [V]"),
                        )
                    elif op_inputs["Power switch"] == 1:
                        new_model.algebraic[i_cell] = constant_power(
                            new_model.variables,
                            pybamm.Parameter("Power function [W]"),
                        )

                # add voltage events to the model
                if op_inputs["Power switch"] == 1 or op_inputs[
                        "Current switch"] == 1:
                    new_model.events.append(
                        pybamm.Event(
                            "Voltage cut-off [V] [experiment]",
                            new_model.variables["Terminal voltage [V]"] -
                            op_inputs["Voltage cut-off [V]"] /
                            model.param.n_cells,
                        ))

                # Keep the min and max voltages as safeguards but add some tolerances
                # so that they are not triggered before the voltage limits in the
                # experiment
                for event in new_model.events:
                    if event.name == "Minimum voltage":
                        event._expression += 1
                    elif event.name == "Maximum voltage":
                        event._expression -= 1

                # Update parameter values
                new_parameter_values = self.parameter_values.copy()
                if op_inputs["Current switch"] == 1:
                    new_parameter_values.update({
                        "Current function [A]":
                        op_inputs["Current input [A]"]
                    })
                elif op_inputs["Voltage switch"] == 1:
                    new_parameter_values.update(
                        {
                            "Voltage function [V]":
                            op_inputs["Voltage input [V]"]
                        },
                        check_already_exists=False,
                    )
                elif op_inputs["Power switch"] == 1:
                    new_parameter_values.update(
                        {"Power function [W]": op_inputs["Power input [W]"]},
                        check_already_exists=False,
                    )

                self.op_conds_to_model_and_param[op_cond[:2]] = (
                    new_model,
                    new_parameter_values,
                )
        self.model = model
예제 #4
0
    def set_up_model_for_experiment_old(self, model):
        """
        Set up self.model to be able to run the experiment (old version).
        In this version, a single model is created which can then be called with
        different inputs for current-control, voltage-control, or power-control.

        This reduces set-up time since only one model needs to be processed, but
        increases simulation time since the model formulation is inefficient
        """
        # Create a new model where the current density is now a variable
        # To do so, we replace all instances of the current density in the
        # model with a current density variable, which is obtained from the
        # FunctionControl submodel
        # create the FunctionControl submodel and extract variables
        external_circuit_variables = pybamm.external_circuit.FunctionControl(
            model.param, None).get_fundamental_variables()

        # Perform the replacement
        symbol_replacement_map = {
            model.variables[name]: variable
            for name, variable in external_circuit_variables.items()
        }
        replacer = pybamm.SymbolReplacer(symbol_replacement_map)
        new_model = replacer.process_model(model, inplace=False)

        # Update the algebraic equation and initial conditions for FunctionControl
        # This creates an algebraic equation for the current to allow current, voltage,
        # or power control, together with the appropriate guess for the
        # initial condition.
        # External circuit submodels are always equations on the current
        # The external circuit function should fix either the current, or the voltage,
        # or a combination (e.g. I*V for power control)
        i_cell = new_model.variables["Total current density"]
        new_model.initial_conditions[
            i_cell] = new_model.param.current_with_time
        new_model.algebraic[
            i_cell] = constant_current_constant_voltage_constant_power(
                new_model.variables)

        # Remove upper and lower voltage cut-offs that are *not* part of the experiment
        new_model.events = [
            event for event in model.events
            if event.name not in ["Minimum voltage", "Maximum voltage"]
        ]
        # add current and voltage events to the model
        # current events both negative and positive to catch specification
        new_model.events.extend([
            pybamm.Event(
                "Current cut-off (positive) [A] [experiment]",
                new_model.variables["Current [A]"] -
                abs(pybamm.InputParameter("Current cut-off [A]")),
            ),
            pybamm.Event(
                "Current cut-off (negative) [A] [experiment]",
                new_model.variables["Current [A]"] +
                abs(pybamm.InputParameter("Current cut-off [A]")),
            ),
            pybamm.Event(
                "Voltage cut-off [V] [experiment]",
                new_model.variables["Terminal voltage [V]"] -
                pybamm.InputParameter("Voltage cut-off [V]") /
                model.param.n_cells,
            ),
        ])

        self.model = new_model

        self.op_conds_to_model_and_param = {
            op_cond[:2]: (new_model, self.parameter_values)
            for op_cond in set(self.experiment.operating_conditions)
        }
        self.op_conds_to_built_models = None
예제 #5
0
    def test_process_model(self):
        model = pybamm.BaseModel()
        a = pybamm.Parameter("a")
        b = pybamm.Parameter("b")
        c = pybamm.Parameter("c")
        d = pybamm.Parameter("d")
        var1 = pybamm.Variable("var1", domain="test")
        var2 = pybamm.Variable("var2", domain="test")
        model.rhs = {var1: a * pybamm.grad(var1)}
        model.algebraic = {var2: c * var2}
        model.initial_conditions = {var1: b, var2: d}
        model.boundary_conditions = {
            var1: {
                "left": (c, "Dirichlet"),
                "right": (d, "Neumann")
            }
        }
        model.variables = {
            "var1": var1,
            "var2": var2,
            "grad_var1": pybamm.grad(var1),
            "d_var1": d * var1,
        }
        model.timescale = b
        model.length_scales = {"test": c}

        replacer = pybamm.SymbolReplacer({
            pybamm.Parameter("a"):
            pybamm.Scalar(4),
            pybamm.Parameter("b"):
            pybamm.Scalar(2),
            pybamm.Parameter("c"):
            pybamm.Scalar(3),
            pybamm.Parameter("d"):
            pybamm.Scalar(42),
        })
        replacer.process_model(model)
        # rhs
        var1 = model.variables["var1"]
        self.assertIsInstance(model.rhs[var1], pybamm.Multiplication)
        self.assertIsInstance(model.rhs[var1].children[0], pybamm.Scalar)
        self.assertIsInstance(model.rhs[var1].children[1], pybamm.Gradient)
        self.assertEqual(model.rhs[var1].children[0].value, 4)
        # algebraic
        var2 = model.variables["var2"]
        self.assertIsInstance(model.algebraic[var2], pybamm.Multiplication)
        self.assertIsInstance(model.algebraic[var2].children[0], pybamm.Scalar)
        self.assertIsInstance(model.algebraic[var2].children[1],
                              pybamm.Variable)
        self.assertEqual(model.algebraic[var2].children[0].value, 3)
        # initial conditions
        self.assertIsInstance(model.initial_conditions[var1], pybamm.Scalar)
        self.assertEqual(model.initial_conditions[var1].value, 2)
        # boundary conditions
        bc_key = list(model.boundary_conditions.keys())[0]
        self.assertIsInstance(bc_key, pybamm.Variable)
        bc_value = list(model.boundary_conditions.values())[0]
        self.assertIsInstance(bc_value["left"][0], pybamm.Scalar)
        self.assertEqual(bc_value["left"][0].value, 3)
        self.assertIsInstance(bc_value["right"][0], pybamm.Scalar)
        self.assertEqual(bc_value["right"][0].value, 42)
        # variables
        self.assertEqual(model.variables["var1"].id, var1.id)
        self.assertIsInstance(model.variables["grad_var1"], pybamm.Gradient)
        self.assertTrue(
            isinstance(model.variables["grad_var1"].children[0],
                       pybamm.Variable))
        self.assertEqual(model.variables["d_var1"].id,
                         (pybamm.Scalar(42) * var1).id)
        self.assertIsInstance(model.variables["d_var1"].children[0],
                              pybamm.Scalar)
        self.assertTrue(
            isinstance(model.variables["d_var1"].children[1], pybamm.Variable))
        # timescale and length scales
        self.assertEqual(model.timescale.evaluate(), 2)
        self.assertEqual(model.length_scales["test"].evaluate(), 3)