Exemple #1
0
    def __init__(
        self, t, y, t_event=None, y_event=None, termination="final time", copy_this=None
    ):
        self._t = t
        if isinstance(y, casadi.DM):
            y = y.full()
        self._y = y
        self._t_event = t_event
        self._y_event = y_event
        self._termination = termination
        if copy_this is None:
            # initialize empty inputs and model, to be populated later
            self._inputs = pybamm.FuzzyDict()
            self.model = pybamm.BaseModel()
            self.set_up_time = None
            self.solve_time = None
            self.integration_time = None
            self.has_symbolic_inputs = False
        else:
            self._inputs = copy.copy(copy_this.inputs)
            self.model = copy_this.model
            self.set_up_time = copy_this.set_up_time
            self.solve_time = copy_this.solve_time
            self.integration_time = copy_this.integration_time
            self.has_symbolic_inputs = copy_this.has_symbolic_inputs

        # initiaize empty variables and data
        self._variables = pybamm.FuzzyDict()
        self.data = pybamm.FuzzyDict()

        # initialize empty known evals
        self._known_evals = defaultdict(dict)
        for time in t:
            self._known_evals[time] = {}
Exemple #2
0
    def __init__(
        self,
        t,
        y,
        t_event=None,
        y_event=None,
        termination="final time",
        copy_this=None,
    ):
        self._t = t
        self._y = y
        self._t_event = t_event
        self._y_event = y_event
        self._termination = termination
        if copy_this is None:
            # initialize empty inputs and model, to be populated later
            self._inputs = pybamm.FuzzyDict()
            self._model = None
            self.set_up_time = None
            self.solve_time = None
        else:
            self._inputs = copy.copy(copy_this.inputs)
            self._model = copy_this.model
            self.set_up_time = copy_this.set_up_time
            self.solve_time = copy_this.solve_time

        # initiaize empty variables and data
        self._variables = pybamm.FuzzyDict()
        self.data = pybamm.FuzzyDict()

        # initialize empty known evals
        self._known_evals = defaultdict(dict)
        for time in t:
            self._known_evals[time] = {}
Exemple #3
0
    def __init__(self, values=None, chemistry=None):
        self._dict_items = pybamm.FuzzyDict()
        # Must provide either values or chemistry, not both (nor neither)
        if values is not None and chemistry is not None:
            raise ValueError(
                "Only one of values and chemistry can be provided. To change parameters"
                " slightly from a chemistry, first load parameters with the chemistry"
                " (param = pybamm.ParameterValues(chemistry=...)) and then update with"
                " param.update({dict of values}).")
        if values is None and chemistry is None:
            raise ValueError("values and chemistry cannot both be None")
        # First load chemistry
        if chemistry is not None:
            self.update_from_chemistry(chemistry)
        # Then update with values dictionary or file
        if values is not None:
            # If base_parameters is a filename, load from that filename
            if isinstance(values, str):
                file_path = self.find_parameter(values)
                path = os.path.split(file_path)[0]
                values = self.read_parameters_csv(file_path)
            else:
                path = ""
            # Don't check parameter already exists when first creating it
            self.update(values, check_already_exists=False, path=path)

        # Initialise empty _processed_symbols dict (for caching)
        self._processed_symbols = {}
        self.parameter_events = []
Exemple #4
0
    def __init__(self, name="Unnamed model"):
        self.name = name
        self.options = {}

        # Initialise empty model
        self._rhs = {}
        self._algebraic = {}
        self._initial_conditions = {}
        self._boundary_conditions = {}
        self._variables = pybamm.FuzzyDict()
        self._events = []
        self._concatenated_rhs = None
        self._concatenated_algebraic = None
        self._concatenated_initial_conditions = None
        self._mass_matrix = None
        self._mass_matrix_inv = None
        self._jacobian = None
        self._jacobian_algebraic = None
        self.external_variables = []

        # Default behaviour is to use the jacobian and simplify
        self.use_jacobian = True
        self.use_simplify = True
        self.convert_to_format = "casadi"

        # Default timescale is 1 second
        self.timescale = pybamm.Scalar(1)
Exemple #5
0
 def build_coupled_variables(self):
     # Note: pybamm will try to get the coupled variables for the submodels in the
     # order they are set by the user. If this fails for a particular submodel,
     # return to it later and try again. If setting coupled variables fails and
     # there are no more submodels to try, raise an error.
     submodels = list(self.submodels.keys())
     count = 0
     # For this part the FuzzyDict of variables is briefly converted back into a
     # normal dictionary for speed with KeyErrors
     self._variables = dict(self._variables)
     while len(submodels) > 0:
         count += 1
         for submodel_name, submodel in self.submodels.items():
             if submodel_name in submodels:
                 pybamm.logger.debug(
                     "Getting coupled variables for {} submodel ({})".
                     format(submodel_name, self.name))
                 try:
                     self.variables.update(
                         submodel.get_coupled_variables(self.variables))
                     submodels.remove(submodel_name)
                 except KeyError as key:
                     if len(submodels) == 1 or count == 100:
                         # no more submodels to try
                         raise pybamm.ModelError(
                             """Submodel "{}" requires the variable {}, but it cannot be found.
                             Check the selected submodels provide all of the required
                             variables.""".format(submodel_name, key))
                     else:
                         # try setting coupled variables on next loop through
                         pybamm.logger.debug(
                             "Can't find {}, trying other submodels first".
                             format(key))
     # Convert variables back into FuzzyDict
     self._variables = pybamm.FuzzyDict(self._variables)
Exemple #6
0
    def __init__(self, name="Unnamed model"):
        self.name = name
        self.options = {}

        # Initialise empty model
        self._rhs = {}
        self._algebraic = {}
        self._initial_conditions = {}
        self._boundary_conditions = {}
        self._variables = pybamm.FuzzyDict({})
        self._events = []
        self._concatenated_rhs = None
        self._concatenated_algebraic = None
        self._concatenated_initial_conditions = None
        self._mass_matrix = None
        self._mass_matrix_inv = None
        self._jacobian = None
        self._jacobian_algebraic = None
        self.external_variables = []
        self._parameters = None
        self._input_parameters = None
        self._variables_casadi = {}

        # Default behaviour is to use the jacobian
        self.use_jacobian = True
        self.convert_to_format = "casadi"

        # Model is not initially discretised
        self.is_discretised = False
        self.y_slices = None

        # Default timescale is 1 second
        self.timescale = pybamm.Scalar(1)
        self.length_scales = {}
Exemple #7
0
    def set_summary_variables(self, all_summary_variables):
        summary_variables = {var: [] for var in all_summary_variables[0]}
        for sum_vars in all_summary_variables:
            for name, value in sum_vars.items():
                summary_variables[name].append(value)

        summary_variables["Cycle number"] = range(1, len(all_summary_variables) + 1)
        self.all_summary_variables = all_summary_variables
        self._summary_variables = pybamm.FuzzyDict(
            {name: np.array(value) for name, value in summary_variables.items()}
        )
Exemple #8
0
    def options(self, extra_options):
        default_options = {"dimensionality": 1}
        extra_options = extra_options or {}

        options = pybamm.FuzzyDict(default_options)
        # any extra options overwrite the default options
        for name, opt in extra_options.items():
            if name in default_options:
                options[name] = opt
            else:
                raise pybamm.OptionError(
                    "Option '{}' not recognised. Best matches are {}".format(
                        name, options.get_best_matches(name)))

        if options["dimensionality"] not in [1, 2]:
            raise pybamm.OptionError(
                "Dimension of current collectors must be 1 or 2, not {}".
                format(options["dimensionality"]))
        self._options = options
    def options(self, extra_options):
        default_options = {
            "operating mode": "current",
            "dimensionality": 0,
            "surface form": False,
            "convection": False,
            "side reactions": [],
            "interfacial surface area": "constant",
            "current collector": "uniform",
            "particle": "Fickian diffusion",
            "particle shape": "spherical",
            "thermal": "isothermal",
            "cell geometry": None,
            "external submodels": [],
            "sei": None,
            "sei porosity change": False,
            "working electrode": None,
        }
        # Change the default for cell geometry based on which thermal option is provided
        extra_options = extra_options or {}
        thermal_option = extra_options.get(
            "thermal", None
        )  # return None if option not given
        if thermal_option is None or thermal_option in ["isothermal", "lumped"]:
            default_options["cell geometry"] = "arbitrary"
        else:
            default_options["cell geometry"] = "pouch"
        # The "cell geometry" option will still be overridden by extra_options if
        # provided

        # Change the default for SEI film resistance based on which sei option is
        # provided
        # extra_options = extra_options or {}
        sei_option = extra_options.get("sei", None)  # return None if option not given
        if sei_option is None:
            default_options["sei film resistance"] = None
        else:
            default_options["sei film resistance"] = "distributed"
        # The "sei film resistance" option will still be overridden by extra_options if
        # provided

        options = pybamm.FuzzyDict(default_options)
        # any extra options overwrite the default options
        for name, opt in extra_options.items():
            if name in default_options:
                options[name] = opt
            else:
                raise pybamm.OptionError(
                    "Option '{}' not recognised. Best matches are {}".format(
                        name, options.get_best_matches(name)
                    )
                )

        # Options that are incompatible with models
        if isinstance(self, pybamm.lithium_ion.BaseModel):
            if options["convection"] is not False:
                raise pybamm.OptionError(
                    "convection not implemented for lithium-ion models"
                )
            if (
                options["thermal"] in ["x-lumped", "x-full"]
                and options["cell geometry"] != "pouch"
            ):
                raise pybamm.OptionError(
                    options["thermal"] + " model must have pouch geometry."
                )
        if isinstance(self, pybamm.lead_acid.BaseModel):
            if options["thermal"] != "isothermal" and options["dimensionality"] != 0:
                raise pybamm.OptionError(
                    "Lead-acid models can only have thermal "
                    "effects if dimensionality is 0."
                )
            if options["sei"] is not None or options["sei film resistance"] is not None:
                raise pybamm.OptionError("Lead-acid models cannot have SEI formation")

        # Some standard checks to make sure options are compatible
        if not (
            options["operating mode"] in ["current", "voltage", "power"]
            or callable(options["operating mode"])
        ):
            raise pybamm.OptionError(
                "operating mode '{}' not recognised".format(options["operating mode"])
            )
        if (
            isinstance(self, (pybamm.lead_acid.LOQS, pybamm.lead_acid.Composite))
            and options["surface form"] is False
        ):
            if len(options["side reactions"]) > 0:
                raise pybamm.OptionError(
                    """must use surface formulation to solve {!s} with side reactions
                    """.format(
                        self
                    )
                )
        if options["surface form"] not in [False, "differential", "algebraic"]:
            raise pybamm.OptionError(
                "surface form '{}' not recognised".format(options["surface form"])
            )
        if options["convection"] not in [
            False,
            "uniform transverse",
            "full transverse",
        ]:
            raise pybamm.OptionError(
                "convection option '{}' not recognised".format(options["convection"])
            )
        if options["current collector"] not in [
            "uniform",
            "potential pair",
            "potential pair quite conductive",
        ]:
            raise pybamm.OptionError(
                "current collector model '{}' not recognised".format(
                    options["current collector"]
                )
            )
        if options["dimensionality"] not in [0, 1, 2]:
            raise pybamm.OptionError(
                "Dimension of current collectors must be 0, 1, or 2, not {}".format(
                    options["dimensionality"]
                )
            )
        if options["thermal"] not in ["isothermal", "lumped", "x-lumped", "x-full"]:
            raise pybamm.OptionError(
                "Unknown thermal model '{}'".format(options["thermal"])
            )
        if options["cell geometry"] not in ["arbitrary", "pouch"]:
            raise pybamm.OptionError(
                "Unknown geometry '{}'".format(options["cell geometry"])
            )
        if options["sei"] not in [
            None,
            "constant",
            "reaction limited",
            "solvent-diffusion limited",
            "electron-migration limited",
            "interstitial-diffusion limited",
            "ec reaction limited",
        ]:
            raise pybamm.OptionError("Unknown sei model '{}'".format(options["sei"]))
        if options["sei film resistance"] not in [None, "distributed", "average"]:
            raise pybamm.OptionError(
                "Unknown sei film resistance model '{}'".format(
                    options["sei film resistance"]
                )
            )
        if options["sei porosity change"] not in [True, False]:
            raise pybamm.OptionError(
                "Unknown sei porosity change '{}'".format(
                    options["sei porosity change"]
                )
            )

        if options["dimensionality"] == 0:
            if options["current collector"] not in ["uniform"]:
                raise pybamm.OptionError(
                    "current collector model must be uniform in 0D model"
                )
            if options["convection"] == "full transverse":
                raise pybamm.OptionError(
                    "cannot have transverse convection in 0D model"
                )
        if options["particle"] not in [
            "Fickian diffusion",
            "fast diffusion",
            "uniform profile",
            "quadratic profile",
            "quartic profile",
        ]:
            raise pybamm.OptionError(
                "particle model '{}' not recognised".format(options["particle"])
            )
        if options["particle"] == "fast diffusion":
            raise NotImplementedError(
                "The 'fast diffusion' option has been renamed. "
                "Use 'uniform profile' instead."
            )
        if options["particle shape"] not in ["spherical", "user"]:
            raise pybamm.OptionError(
                "particle shape '{}' not recognised".format(options["particle shape"])
            )

        if options["thermal"] == "x-lumped" and options["dimensionality"] == 1:
            warnings.warn(
                "1+1D Thermal models are only valid if both tabs are "
                "placed at the top of the cell."
            )

        self._options = options
Exemple #10
0
    def print_parameters(self, parameters, output_file=None):
        """
        Return dictionary of evaluated parameters, and optionally print these evaluated
        parameters to an output file.
        For dimensionless parameters that depend on the C-rate, the value is given as a
        function of the C-rate (either x * Crate or x / Crate depending on the
        dependence)

        Parameters
        ----------
        parameters : class or dict containing :class:`pybamm.Parameter` objects
            Class or dictionary containing all the parameters to be evaluated
        output_file : string, optional
            The file to print parameters to. If None, the parameters are not printed,
            and this function simply acts as a test that all the parameters can be
            evaluated, and returns the dictionary of evaluated parameters.

        Returns
        -------
        evaluated_parameters : defaultdict
            The evaluated parameters, for further processing if needed

        Notes
        -----
        A C-rate of 1 C is the current required to fully discharge the battery in 1
        hour, 2 C is current to discharge the battery in 0.5 hours, etc
        """
        # Set list of attributes to ignore, for when we are evaluating parameters from
        # a class of parameters
        ignore = [
            "__name__",
            "__doc__",
            "__package__",
            "__loader__",
            "__spec__",
            "__file__",
            "__cached__",
            "__builtins__",
            "absolute_import",
            "division",
            "print_function",
            "unicode_literals",
            "pybamm",
            "_options",
            "constants",
            "np",
            "geo",
            "elec",
            "therm",
        ]

        # If 'parameters' is a class, extract the dict
        if not isinstance(parameters, dict):
            parameters = {
                k: v
                for k, v in parameters.__dict__.items() if k not in ignore
            }

        evaluated_parameters = defaultdict(list)
        # Calculate parameters for each C-rate
        for Crate in [1, 10]:
            # Update Crate
            capacity = self.get("Nominal cell capacity [A.h]")
            if capacity is not None:
                self.update(
                    {"Current function [A]": Crate * capacity},
                    check_already_exists=False,
                )

            # Turn to regular dictionary for faster KeyErrors
            self._dict_items = dict(self._dict_items)

            for name, symbol in parameters.items():
                if not callable(symbol):
                    try:
                        proc_symbol = self.process_symbol(symbol)
                    except KeyError:
                        # skip parameters that don't have a value in that parameter set
                        proc_symbol = None
                    if not (callable(proc_symbol) or proc_symbol is None
                            or proc_symbol.has_symbol_of_classes(
                                (pybamm.Concatenation, pybamm.Broadcast))):
                        evaluated_parameters[name].append(
                            proc_symbol.evaluate(t=0))

            # Turn back to FuzzyDict
            self._dict_items = pybamm.FuzzyDict(self._dict_items)

        # Calculate C-dependence of the parameters based on the difference between the
        # value at 1C and the value at C / 10
        for name, values in evaluated_parameters.items():
            if values[1] == 0 or abs(values[0] / values[1] - 1) < 1e-10:
                C_dependence = ""
            elif abs(values[0] / values[1] - 10) < 1e-10:
                C_dependence = " * Crate"
            elif abs(values[0] / values[1] - 0.1) < 1e-10:
                C_dependence = " / Crate"
            evaluated_parameters[name] = (values[0], C_dependence)
        # Print the evaluated_parameters dict to output_file
        if output_file:
            self.print_evaluated_parameters(evaluated_parameters, output_file)

        return evaluated_parameters
Exemple #11
0
 def variables(self, variables):
     self._variables = pybamm.FuzzyDict(variables)
Exemple #12
0
 def test_fuzzy_dict(self):
     d = pybamm.FuzzyDict({"test": 1, "test2": 2})
     self.assertEqual(d["test"], 1)
     with self.assertRaisesRegex(KeyError,
                                 "'test3' not found. Best matches are "):
         d["test3"]
    def __init__(self, extra_options):
        self.possible_options = {
            "surface form": ["false", "differential", "algebraic"],
            "convection": ["none", "uniform transverse", "full transverse"],
            "current collector": [
                "uniform",
                "potential pair",
                "potential pair quite conductive",
            ],
            "dimensionality": [0, 1, 2],
            "interfacial surface area": ["constant", "varying"],
            "thermal": ["isothermal", "lumped", "x-lumped", "x-full"],
            "cell geometry": ["arbitrary", "pouch"],
            "SEI": [
                "none",
                "constant",
                "reaction limited",
                "solvent-diffusion limited",
                "electron-migration limited",
                "interstitial-diffusion limited",
                "ec reaction limited",
            ],
            "SEI film resistance": ["none", "distributed", "average"],
            "SEI porosity change": ["true", "false"],
            "lithium plating": ["none", "reversible", "irreversible"],
            "loss of active material":
            ["none", "negative", "positive", "both"],
            "operating mode": ["current", "voltage", "power"],
            "particle cracking": [
                "none",
                "no cracking",
                "negative",
                "positive",
                "both",
            ],
            "lithium plating porosity change": ["true", "false"],
            "particle": [
                "Fickian diffusion",
                "fast diffusion",
                "uniform profile",
                "quadratic profile",
                "quartic profile",
            ],
            "particle shape": ["spherical", "user", "no particles"],
            "electrolyte conductivity": [
                "default",
                "full",
                "leading order",
                "composite",
                "integrated",
            ],
            "total interfacial current density as a state": ["true", "false"],
        }

        default_options = {
            "operating mode": "current",
            "dimensionality": 0,
            "surface form": "false",
            "convection": "none",
            "side reactions": [],
            "interfacial surface area": "constant",
            "current collector": "uniform",
            "particle": "Fickian diffusion",
            "particle shape": "spherical",
            "electrolyte conductivity": "default",
            "thermal": "isothermal",
            "cell geometry": "none",
            "external submodels": [],
            "SEI": "none",
            "lithium plating": "none",
            "SEI porosity change": "false",
            "lithium plating porosity change": "false",
            "loss of active material": "none",
            "working electrode": "none",
            "particle cracking": "none",
            "total interfacial current density as a state": "false",
        }

        # Change the default for cell geometry based on which thermal option is provided
        extra_options = extra_options or {}
        thermal_option = extra_options.get("thermal", "none")
        # return "none" if option not given
        if thermal_option in ["none", "isothermal", "lumped"]:
            default_options["cell geometry"] = "arbitrary"
        else:
            default_options["cell geometry"] = "pouch"
        # The "cell geometry" option will still be overridden by extra_options if
        # provided

        # Change the default for SEI film resistance based on which SEI option is
        # provided
        # extra_options = extra_options or {}
        sei_option = extra_options.get("SEI", "none")
        # return "none" if option not given
        if sei_option == "none":
            default_options["SEI film resistance"] = "none"
        else:
            default_options["SEI film resistance"] = "distributed"
        # The "SEI film resistance" option will still be overridden by extra_options if
        # provided

        options = pybamm.FuzzyDict(default_options)
        # any extra options overwrite the default options
        for name, opt in extra_options.items():
            if name in default_options:
                options[name] = opt
            else:
                raise pybamm.OptionError(
                    "Option '{}' not recognised. Best matches are {}".format(
                        name, options.get_best_matches(name)))

        # If "SEI film resistance" is "distributed" then "total interfacial current
        # density as a state" must be "true"
        if options["SEI film resistance"] == "distributed":
            options["total interfacial current density as a state"] = "true"
            # Check that extra_options did not try to provide a clashing option
            if (extra_options.get(
                    "total interfacial current density as a state") == "false"
                ):
                raise pybamm.OptionError(
                    "If 'sei film resistance' is 'distributed' then 'total interfacial "
                    "current density as a state' must be 'true'")

        # Some standard checks to make sure options are compatible
        if options["SEI porosity change"] in [True, False]:
            raise pybamm.OptionError(
                "SEI porosity change must now be given in string format "
                "('true' or 'false')")

        if options["dimensionality"] == 0:
            if options["current collector"] not in ["uniform"]:
                raise pybamm.OptionError(
                    "current collector model must be uniform in 0D model")
            if options["convection"] == "full transverse":
                raise pybamm.OptionError(
                    "cannot have transverse convection in 0D model")

        if options["particle"] == "fast diffusion":
            raise NotImplementedError(
                "The 'fast diffusion' option has been renamed. "
                "Use 'uniform profile' instead.")

        if options["thermal"] == "x-lumped" and options["dimensionality"] == 1:
            warnings.warn(
                "1+1D Thermal models are only valid if both tabs are "
                "placed at the top of the cell.")

        for option, value in options.items():
            if (option == "side reactions" or option == "external submodels"
                    or option == "working electrode"):
                pass
            elif value not in self.possible_options[option]:
                if not (option == "operating mode" and callable(value)):
                    raise pybamm.OptionError(
                        f"\n'{value}' is not recognized in option '{option}'. "
                        f"Possible values are {self.possible_options[option]}")

        super().__init__(options.items())
Exemple #14
0
    def __init__(
        self,
        all_ts,
        all_ys,
        all_models,
        all_inputs,
        t_event=None,
        y_event=None,
        termination="final time",
    ):
        if not isinstance(all_ts, list):
            all_ts = [all_ts]
        if not isinstance(all_ys, list):
            all_ys = [all_ys]
        if not isinstance(all_models, list):
            all_models = [all_models]
        self._all_ts = all_ts
        self._all_ys = all_ys
        self._all_models = all_models

        self._t_event = t_event
        self._y_event = y_event
        self._termination = termination

        # Set up inputs
        if not isinstance(all_inputs, list):
            for key, value in all_inputs.items():
                if isinstance(value, numbers.Number):
                    all_inputs[key] = np.array([value])
            all_inputs = [all_inputs]
        self.all_inputs = all_inputs
        self.has_symbolic_inputs = any(
            isinstance(v, casadi.MX) for v in all_inputs[0].values())

        # Copy the timescale_eval and lengthscale_evals if they exist
        if hasattr(all_models[0], "timescale_eval"):
            self.timescale_eval = all_models[0].timescale_eval
        else:
            self.timescale_eval = all_models[0].timescale.evaluate()

        if hasattr(all_models[0], "length_scales_eval"):
            self.length_scales_eval = all_models[0].length_scales_eval
        else:
            self.length_scales_eval = {
                domain: scale.evaluate()
                for domain, scale in all_models[0].length_scales.items()
            }

        self.set_up_time = None
        self.solve_time = None
        self.integration_time = None

        # initiaize empty variables and data
        self._variables = pybamm.FuzzyDict()
        self.data = pybamm.FuzzyDict()

        # Add self as sub-solution for compatibility with ProcessedVariable
        self._sub_solutions = [self]

        # Solution now uses CasADi
        pybamm.citations.register("Andersson2019")
Exemple #15
0
    def options(self, extra_options):
        default_options = {
            "operating mode": "current",
            "dimensionality": 0,
            "surface form": False,
            "convection": False,
            "side reactions": [],
            "interfacial surface area": "constant",
            "current collector": "uniform",
            "particle": "Fickian diffusion",
            "thermal": "isothermal",
            "thermal current collector": False,
            "external submodels": [],
        }
        options = pybamm.FuzzyDict(default_options)
        # any extra options overwrite the default options
        if extra_options is not None:
            for name, opt in extra_options.items():
                if name in default_options:
                    options[name] = opt
                else:
                    raise pybamm.OptionError(
                        "Option '{}' not recognised. Best matches are {}".
                        format(name, options.get_best_matches(name)))

        # Some standard checks to make sure options are compatible
        if not (options["operating mode"] in ["current", "voltage", "power"]
                or callable(options["operating mode"])):
            raise pybamm.OptionError(
                "operating mode '{}' not recognised".format(
                    options["operating mode"]))
        if (isinstance(self,
                       (pybamm.lead_acid.LOQS, pybamm.lead_acid.Composite))
                and options["surface form"] is False):
            if len(options["side reactions"]) > 0:
                raise pybamm.OptionError("""
                    must use surface formulation to solve {!s} with side reactions
                    """.format(self))
        if options["surface form"] not in [False, "differential", "algebraic"]:
            raise pybamm.OptionError("surface form '{}' not recognised".format(
                options["surface form"]))
        if options["current collector"] not in [
                "uniform",
                "potential pair",
                "potential pair quite conductive",
        ]:
            raise pybamm.OptionError(
                "current collector model '{}' not recognised".format(
                    options["current collector"]))
        if options["dimensionality"] not in [0, 1, 2]:
            raise pybamm.OptionError(
                "Dimension of current collectors must be 0, 1, or 2, not {}".
                format(options["dimensionality"]))
        if options["thermal"] not in [
                "isothermal",
                "x-full",
                "x-lumped",
                "xyz-lumped",
                "lumped",
        ]:
            raise pybamm.OptionError("Unknown thermal model '{}'".format(
                options["thermal"]))
        if options["particle"] not in ["Fickian diffusion", "fast diffusion"]:
            raise pybamm.OptionError(
                "particle model '{}' not recognised".format(
                    options["particle"]))

        # Options that are incompatible with models
        if isinstance(self, pybamm.lithium_ion.BaseModel):
            if options["convection"] is True:
                raise pybamm.OptionError(
                    "convection not implemented for lithium-ion models")
        if isinstance(self, pybamm.lead_acid.BaseModel):
            if options["thermal"] != "isothermal" and options[
                    "dimensionality"] != 0:
                raise pybamm.OptionError(
                    "Lead-acid models can only have thermal "
                    "effects if dimensionality is 0.")

            if options["thermal current collector"] is True:
                raise pybamm.OptionError(
                    "Thermal current collector effects are not implemented "
                    "for lead-acid models.")

        self._options = options
Exemple #16
0
def get_cycle_summary_variables(cycle_solution, esoh_sim):
    Q = cycle_solution["Discharge capacity [A.h]"].data
    min_Q = np.min(Q)
    max_Q = np.max(Q)

    cycle_summary_variables = pybamm.FuzzyDict(
        {
            "Minimum measured discharge capacity [A.h]": min_Q,
            "Maximum measured discharge capacity [A.h]": max_Q,
            "Measured capacity [A.h]": max_Q - min_Q,
        }
    )

    degradation_variables = [
        "Negative electrode capacity [A.h]",
        "Positive electrode capacity [A.h]",
        # LAM, LLI
        "Loss of active material in negative electrode [%]",
        "Loss of active material in positive electrode [%]",
        "Loss of lithium inventory [%]",
        "Loss of lithium inventory, including electrolyte [%]",
        # Total lithium
        "Total lithium [mol]",
        "Total lithium in electrolyte [mol]",
        "Total lithium in positive electrode [mol]",
        "Total lithium in negative electrode [mol]",
        "Total lithium in particles [mol]",
        # Lithium lost
        "Total lithium lost [mol]",
        "Total lithium lost from particles [mol]",
        "Total lithium lost from electrolyte [mol]",
        "Loss of lithium to negative electrode SEI [mol]",
        "Loss of lithium to positive electrode SEI [mol]",
        "Loss of lithium to negative electrode lithium plating [mol]",
        "Loss of lithium to positive electrode lithium plating [mol]",
        "Loss of capacity to negative electrode SEI [A.h]",
        "Loss of capacity to positive electrode SEI [A.h]",
        "Loss of capacity to negative electrode lithium plating [A.h]",
        "Loss of capacity to positive electrode lithium plating [A.h]",
        "Total lithium lost to side reactions [mol]",
        "Total capacity lost to side reactions [A.h]",
        # Resistance
        "Local ECM resistance [Ohm]",
    ]
    first_state = cycle_solution.first_state
    last_state = cycle_solution.last_state
    for var in degradation_variables:
        data_first = first_state[var].data
        data_last = last_state[var].data
        cycle_summary_variables[var] = data_last[0]
        var_lowercase = var[0].lower() + var[1:]
        cycle_summary_variables["Change in " + var_lowercase] = (
            data_last[0] - data_first[0]
        )

    if esoh_sim is not None:
        V_min = esoh_sim.parameter_values["Lower voltage cut-off [V]"]
        V_max = esoh_sim.parameter_values["Upper voltage cut-off [V]"]
        C_n = last_state["Negative electrode capacity [A.h]"].data[0]
        C_p = last_state["Positive electrode capacity [A.h]"].data[0]
        n_Li = last_state["Total lithium in particles [mol]"].data[0]
        if esoh_sim.solution is not None:
            # initialize with previous solution if it is available
            esoh_sim.built_model.set_initial_conditions_from(esoh_sim.solution)
            solver = None
        else:
            x_100_init = np.max(cycle_solution["Negative electrode SOC"].data)
            # make sure x_0 > 0
            C_init = np.minimum(0.95 * (C_n * x_100_init), max_Q - min_Q)

            # Solve the esoh model and add outputs to the summary variables
            # use CasadiAlgebraicSolver if there are interpolants
            if isinstance(
                esoh_sim.parameter_values["Negative electrode OCP [V]"], tuple
            ) or isinstance(
                esoh_sim.parameter_values["Positive electrode OCP [V]"], tuple
            ):
                solver = pybamm.CasadiAlgebraicSolver()
                # Choose x_100_init so as not to violate the interpolation limits
                if isinstance(
                    esoh_sim.parameter_values["Positive electrode OCP [V]"], tuple
                ):
                    y_100_min = np.min(
                        esoh_sim.parameter_values["Positive electrode OCP [V]"][1][:, 0]
                    )
                    x_100_max = (
                        n_Li * pybamm.constants.F.value / 3600 - y_100_min * C_p
                    ) / C_n
                    x_100_init = np.minimum(x_100_init, 0.99 * x_100_max)
            else:
                solver = None
            # Update initial conditions using the cycle solution
            esoh_sim.build()
            esoh_sim.built_model.set_initial_conditions_from(
                {"x_100": x_100_init, "C": C_init}
            )
        esoh_sol = esoh_sim.solve(
            [0],
            inputs={
                "V_min": V_min,
                "V_max": V_max,
                "C_n": C_n,
                "C_p": C_p,
                "n_Li": n_Li,
            },
            solver=solver,
        )
        for var in esoh_sim.built_model.variables:
            cycle_summary_variables[var] = esoh_sol[var].data[0]

        cycle_summary_variables["Capacity [A.h]"] = cycle_summary_variables["C"]

    return cycle_summary_variables