Example #1
0
    def test_electrolyte_conductivity(self):
        root = pybamm.root_dir()
        p = "pybamm/input/parameters/lithium-ion/electrolytes/lipf6_Landesfeind2019"
        k_path = os.path.join(root, p)
        files = [
            f for f in os.listdir(k_path)
            if ".py" in f and "_base" not in f and "conductivity" in f
        ]
        files.sort()
        funcs = [pybamm.load_function(os.path.join(k_path, f)) for f in files]
        T_ref = 298.15
        T = T_ref + 30.0
        c = 1000.0
        k = [np.around(f(c, T).value, 6) for f in funcs]
        self.assertEqual(k, [1.839786, 1.361015, 0.750259])
        T += 20
        k = [np.around(f(c, T).value, 6) for f in funcs]
        self.assertEqual(k, [2.292425, 1.664438, 0.880755])

        chemistry = pybamm.parameter_sets.Chen2020
        param = pybamm.ParameterValues(chemistry=chemistry)
        param["Electrolyte conductivity [S.m-1]"] = funcs[0]
        model = pybamm.lithium_ion.SPM()
        sim = pybamm.Simulation(model, parameter_values=param)
        sim.set_parameters()
        sim.build()
    def test_interpolant_against_function(self):
        parameter_values = pybamm.ParameterValues({"a": 0.6})
        parameter_values.update(
            {
                "function": "[function]lico2_ocp_Dualfoil1998",
                "interpolation": "[data]lico2_data_example",
            },
            path=os.path.join(
                pybamm.root_dir(),
                "input",
                "parameters",
                "lithium-ion",
                "cathodes",
                "lico2_Marquis2019",
            ),
        )

        a = pybamm.Parameter("a")
        func = pybamm.FunctionParameter("function", a)
        interp = pybamm.FunctionParameter("interpolation", a)

        processed_func = parameter_values.process_symbol(func)
        processed_interp = parameter_values.process_symbol(interp)
        np.testing.assert_array_almost_equal(processed_func.evaluate(),
                                             processed_interp.evaluate(),
                                             decimal=4)

        # process differentiated function parameter
        diff_func = func.diff(a)
        diff_interp = interp.diff(a)
        processed_diff_func = parameter_values.process_symbol(diff_func)
        processed_diff_interp = parameter_values.process_symbol(diff_interp)
        np.testing.assert_array_almost_equal(processed_diff_func.evaluate(),
                                             processed_diff_interp.evaluate(),
                                             decimal=2)
Example #3
0
    def __init__(self,
                 filename,
                 units="[]",
                 current_scale=pybamm.electrical_parameters.I_typ):
        self.parameters = {"Current [A]": current_scale}
        self.parameters_eval = {"Current [A]": current_scale}

        # Load data from csv
        if filename:
            pybamm_path = pybamm.root_dir()
            data = pd.read_csv(
                os.path.join(pybamm_path, "input", "drive_cycles", filename),
                comment="#",
                skip_blank_lines=True,
            ).to_dict("list")

            self.time = np.array(data["time [s]"])
            self.units = units
            self.current = np.array(data["current " + units])
            # If voltage data is present, load it into the class
            try:
                self.voltage = np.array(data["voltage [V]"])
            except KeyError:
                self.voltage = None
        else:
            raise pybamm.ModelError("No input file provided for current")
Example #4
0
    def test_electrolyte_diffusivity(self):
        root = pybamm.root_dir()
        p = "pybamm/input/parameters/lithium-ion/electrolytes/lipf6_Landesfeind2019"
        d_path = os.path.join(root, p)
        files = [
            f for f in os.listdir(d_path)
            if ".py" in f and "_base" not in f and "diffusivity" in f
        ]
        files.sort()
        funcs = [pybamm.load_function(os.path.join(d_path, f)) for f in files]
        T_ref = 298.15
        T = T_ref + 30.0
        c = 1000.0
        D = [np.around(f(c, T).value, 16) for f in funcs]
        self.assertEqual(D, [5.796505e-10, 5.417881e-10, 5.608856e-10])
        T += 20
        D = [np.around(f(c, T).value, 16) for f in funcs]
        self.assertEqual(D, [8.5992e-10, 7.752815e-10, 7.907549e-10])

        chemistry = pybamm.parameter_sets.Chen2020
        param = pybamm.ParameterValues(chemistry=chemistry)
        param["Electrolyte diffusivity [m2.s-1]"] = funcs[0]
        model = pybamm.lithium_ion.SPM()
        sim = pybamm.Simulation(model, parameter_values=param)
        sim.set_parameters()
        sim.build()
Example #5
0
    def test_drive_cycle_data(self):
        model = pybamm.lithium_ion.SPM()
        param = model.default_parameter_values
        param["Current function [A]"] = "[current data]US06"

        drive_cycle = pd.read_csv(
            os.path.join(pybamm.root_dir(), "input", "drive_cycles",
                         "US06.csv"),
            comment="#",
            skip_blank_lines=True,
            header=None,
        )
        time_data = drive_cycle.values[:, 0]

        sim = pybamm.Simulation(model, parameter_values=param)

        # check solution is returned at the times in the data
        sim.solve()
        tau = model.timescale.evaluate()
        np.testing.assert_array_almost_equal(sim.solution.t, time_data / tau)

        # check warning raised if the largest gap in t_eval is bigger than the
        # smallest gap in the data
        sim.reset()
        with self.assertWarns(pybamm.SolverWarning):
            sim.solve(t_eval=np.linspace(0, 1, 100))

        # check warning raised if t_eval doesnt contain time_data , but has a finer
        # resolution (can still solve, but good for users to know they dont have
        # the solution returned at the data points)
        sim.reset()
        with self.assertWarns(pybamm.SolverWarning):
            sim.solve(t_eval=np.linspace(0, time_data[-1], 800))
Example #6
0
    def print(self, filename=None, output_format="text"):
        """Print all citations that were used for running simulations.

        Parameters
        ----------
        filename : str, optional
            Filename to which to print citations. If None, citations are printed to the
            terminal.
        """
        citations = ""
        citations_file = os.path.join(pybamm.root_dir(), "pybamm",
                                      "CITATIONS.txt")
        if output_format == "text":
            citations = pybtex.format_from_file(
                citations_file,
                "plain",
                citations=self._papers_to_cite,
                output_backend="plaintext",
            )
        elif output_format == "bibtex":
            for key in self._papers_to_cite:
                citations += self._all_citations[key] + "\n"
        else:
            raise pybamm.OptionError(
                "Output format {} not recognised."
                "It should be 'text' or 'bibtex'.".format(output_format))

        if filename is None:
            print(citations)
        else:
            with open(filename, "w") as f:
                f.write(citations)
Example #7
0
    def read_citations(self):
        "Read the citations text file"
        self._all_citations = {}

        citations_file = os.path.join(pybamm.root_dir(), "pybamm", "CITATIONS.txt")
        citation = ""
        start = True

        for line in open(citations_file):
            # if start is true, we need to find the key
            if start is True:
                # match everything between { and , in the first line to get the key
                brace_idx = line.find("{")
                comma_idx = line.find(",")
                key = line[brace_idx + 1 : comma_idx]
                # turn off start as we now have the right key
                start = False
            citation += line
            # blank line means next block, add citation to dictionary and
            # reset everything
            if line == "\n":
                self._all_citations[key] = citation
                citation = ""
                start = True

        # add the final citation
        self._all_citations[key] = citation
Example #8
0
 def update_from_chemistry(self, chemistry):
     """
     Load standard set of components from a 'chemistry' dictionary
     """
     base_chemistry = chemistry["chemistry"]
     # Create path to file
     path = os.path.join(pybamm.root_dir(), "input", "parameters", base_chemistry)
     # Load each component name
     for component_group in [
         "cell",
         "anode",
         "cathode",
         "separator",
         "electrolyte",
         "experiment",
     ]:
         # Make sure component is provided
         try:
             component = chemistry[component_group]
         except KeyError:
             raise KeyError(
                 "must provide '{}' parameters for {} chemistry".format(
                     component_group, base_chemistry
                 )
             )
         # Create path to component and load values
         component_path = os.path.join(path, component_group + "s", component)
         component_params = self.read_parameters_csv(
             os.path.join(component_path, "parameters.csv")
         )
         # Update parameters, making sure to check any conflicts
         self.update(component_params, check_conflict=True, path=component_path)
Example #9
0
    def update_from_chemistry(self, chemistry):
        """
        Load standard set of components from a 'chemistry' dictionary
        """
        base_chemistry = chemistry["chemistry"]
        # Create path to file
        path = os.path.join(
            pybamm.root_dir(), "pybamm", "input", "parameters", base_chemistry
        )
        # Load each component name

        component_groups = [
            "cell",
            "anode",
            "cathode",
            "separator",
            "electrolyte",
            "experiment",
        ]

        # add sei parameters if provided
        if "sei" in chemistry:
            component_groups += ["sei"]

        for component_group in component_groups:
            # Make sure component is provided
            try:
                component = chemistry[component_group]
            except KeyError:
                raise KeyError(
                    "must provide '{}' parameters for {} chemistry".format(
                        component_group, base_chemistry
                    )
                )
            # Create path to component and load values
            component_path = os.path.join(path, component_group + "s", component)
            component_params = self.read_parameters_csv(
                pybamm.get_parameters_filepath(
                    os.path.join(component_path, "parameters.csv")
                )
            )
            # Update parameters, making sure to check any conflicts
            self.update(
                component_params,
                check_conflict=True,
                check_already_exists=False,
                path=component_path,
            )

        # register (list of) citations
        if "citation" in chemistry:
            citations = chemistry["citation"]
            if not isinstance(citations, list):
                citations = [citations]
            for citation in citations:
                pybamm.citations.register(citation)
Example #10
0
    def test_get_parameters_filepath(self):
        tempfile_obj = tempfile.NamedTemporaryFile("w", dir=".")
        self.assertTrue(
            pybamm.get_parameters_filepath(tempfile_obj.name) ==
            tempfile_obj.name)
        tempfile_obj.close()

        package_dir = os.path.join(pybamm.root_dir(), "pybamm")
        tempfile_obj = tempfile.NamedTemporaryFile("w", dir=package_dir)
        path = os.path.join(package_dir, tempfile_obj.name)
        self.assertTrue(
            pybamm.get_parameters_filepath(tempfile_obj.name) == path)
Example #11
0
 def test_read_parameters_csv(self):
     data = pybamm.ParameterValues({}).read_parameters_csv(
         os.path.join(
             pybamm.root_dir(),
             "pybamm",
             "input",
             "parameters",
             "lithium-ion",
             "cathodes",
             "lico2_Marquis2019",
             "parameters.csv",
         ))
     self.assertEqual(data["Positive electrode porosity"], "0.3")
Example #12
0
def update_doc(generated_doc):
    """
    Opens parameter_sets.py, replaces the docstring and then writes it
    """
    with open(
        os.path.join(pybamm.root_dir(), "pybamm", "parameters", "parameter_sets.py"),
        "r+",
    ) as ps_fp:
        ps_output = ps_fp.read()
        ps_output = ps_output.replace(parameter_sets.__doc__, generated_doc)
        ps_fp.truncate(0)
        ps_fp.seek(0)
        ps_fp.write(ps_output)
Example #13
0
def generate_ps_doc(parameter_set_dict):
    """
    Generates docstring of parameter_sets.py from the given dictionary
    """
    output_list = [DOC_INTRO]
    citations_file = os.path.join(pybamm.root_dir(), "pybamm", "CITATIONS.txt")

    for ps_chemistry in sorted(parameter_set_dict.keys()):
        output_list.append("")
        ps_citations = parameter_set_dict[ps_chemistry]
        chem_name = ps_chemistry.capitalize().replace("_", "-") + " " + "parameter sets"
        output_list.append(chem_name)
        dashes = "-" * len(ps_chemistry) + "-" * 15
        output_list.append(dashes)

        for ps_name, ps_citation in sorted(ps_citations):
            citations = pybtex.format_from_file(
                citations_file,
                style="plain",
                output_backend="plaintext",
                citations=ps_citation,
                nocite=True,
            )
            # Remove citation labels "[3]"
            citations = re.split(r"(?:^|\n)\[\d+\]\s", citations)
            # Remove empty strings
            citations = filter(bool, citations)
            fmt_citations = []
            for citation in citations:
                # Break line at the first space before 80 characters
                citation_parts = re.findall(r"(.{1,79})(?:\s|$)", citation)
                # first_line = citation.split('\n')

                indent_citation_parts = []
                for idx, citation_part in enumerate(citation_parts):
                    if idx == 0:
                        citation_part = "- " + citation_part
                    else:
                        citation_part = "  " + citation_part
                    indent_citation_parts.append(" " * 7 + citation_part)

                # Join to create a single citation paragraph
                citation = "\n".join(indent_citation_parts)
                fmt_citations.append(citation)
            fmt_citations = "\n".join(fmt_citations)
            ps_doc = f"    * {ps_name:} :\n{fmt_citations}"
            output_list.append(ps_doc)

    output = "\n".join(output_list)
    output += "\n"
    return output
Example #14
0
    def test_load_function(self):
        # Test filename ends in '.py'
        with self.assertRaisesRegex(
                ValueError, "Expected filename.py, but got doesnotendindotpy"):
            pybamm.load_function("doesnotendindotpy")

        # Test exception if absolute file not found
        with self.assertRaisesRegex(
                ValueError, "is an absolute path, but the file is not found"):
            nonexistent_abs_file = os.path.join(os.getcwd(), "i_dont_exist.py")
            pybamm.load_function(nonexistent_abs_file)

        # Test exception if relative file not found
        with self.assertRaisesRegex(ValueError,
                                    "cannot be found in the PyBaMM directory"):
            pybamm.load_function("i_dont_exist.py")

        # Test exception if relative file found more than once
        with self.assertRaisesRegex(
                ValueError, "found multiple times in the PyBaMM directory"):
            pybamm.load_function("__init__.py")

        # Test exception if no matching function found in module
        with self.assertRaisesRegex(ValueError,
                                    "No function .+ found in module .+"):
            pybamm.load_function("process_symbol_bad_function.py")

        # Test function load with absolute path
        abs_test_path = os.path.join(
            pybamm.root_dir(),
            "tests",
            "unit",
            "test_parameters",
            "data",
            "process_symbol_test_function.py",
        )
        self.assertTrue(os.path.isfile(abs_test_path))
        func = pybamm.load_function(abs_test_path)
        self.assertEqual(func(2), 246)

        # Test function load with relative path
        func = pybamm.load_function("process_symbol_test_function.py")
        self.assertEqual(func(3), 369)
Example #15
0
    def test_interpolant_against_function(self):
        parameter_values = pybamm.ParameterValues({})
        parameter_values.update(
            {
                "function": "[function]lico2_ocp_Dualfoil1998",
                "interpolation": "[data]lico2_data_example",
            },
            path=os.path.join(
                pybamm.root_dir(),
                "pybamm",
                "input",
                "parameters",
                "lithium_ion",
                "positive_electrodes",
                "lico2_Marquis2019",
            ),
            check_already_exists=False,
        )

        a = pybamm.Scalar(0.6)
        func = pybamm.FunctionParameter("function", {"a": a})
        interp = pybamm.FunctionParameter("interpolation", {"a": a})

        processed_func = parameter_values.process_symbol(func)
        processed_interp = parameter_values.process_symbol(interp)
        np.testing.assert_array_almost_equal(
            processed_func.evaluate(),
            processed_interp.evaluate(),
            decimal=4,
        )

        # process differentiated function parameter
        diff_func = func.diff(a)
        diff_interp = interp.diff(a)
        processed_diff_func = parameter_values.process_symbol(diff_func)
        processed_diff_interp = parameter_values.process_symbol(diff_interp)
        np.testing.assert_array_almost_equal(
            processed_diff_func.evaluate(),
            processed_diff_interp.evaluate(),
            decimal=2,
        )
Example #16
0
    def test_load_function(self):
        # Test replace function and deprecation warning for lithium-ion
        with self.assertWarns(Warning):
            warn_path = os.path.join(
                "pybamm",
                "input",
                "parameters",
                "lithium-ion",
                "negative_electrodes",
                "graphite_Chen2020",
                "graphite_LGM50_electrolyte_exchange_current_density_Chen2020.py",
            )
            pybamm.load_function(warn_path)

        # Test replace function and deprecation warning for lead-acid
        with self.assertWarns(Warning):
            warn_path = os.path.join(
                "pybamm",
                "input",
                "parameters",
                "lead-acid",
                "negative_electrodes",
                "lead_Sulzer2019",
                "lead_exchange_current_density_Sulzer2019.py",
            )
            pybamm.load_function(warn_path)

        # Test function load with absolute path
        abs_test_path = os.path.join(
            pybamm.root_dir(),
            "pybamm",
            "input",
            "parameters",
            "lithium_ion",
            "negative_electrodes",
            "graphite_Chen2020",
            "graphite_LGM50_electrolyte_exchange_current_density_Chen2020.py",
        )
        func = pybamm.load_function(abs_test_path)
        self.assertEqual(
            func,
            pybamm.input.parameters.lithium_ion.negative_electrodes.
            graphite_Chen2020.
            graphite_LGM50_electrolyte_exchange_current_density_Chen2020.
            graphite_LGM50_electrolyte_exchange_current_density_Chen2020,  # noqa
        )

        # Test function load with relative path
        rel_test_path = os.path.join(
            "pybamm",
            "input",
            "parameters",
            "lithium_ion",
            "negative_electrodes",
            "graphite_Chen2020",
            "graphite_LGM50_electrolyte_exchange_current_density_Chen2020.py",
        )
        func = pybamm.load_function(rel_test_path)
        self.assertEqual(
            func,
            pybamm.input.parameters.lithium_ion.negative_electrodes.
            graphite_Chen2020.
            graphite_LGM50_electrolyte_exchange_current_density_Chen2020.
            graphite_LGM50_electrolyte_exchange_current_density_Chen2020,  # noqa
        )
Example #17
0
    def update(self,
               values,
               check_conflict=False,
               check_already_exists=True,
               path=""):
        """
        Update parameter dictionary, while also performing some basic checks.

        Parameters
        ----------
        values : dict
            Dictionary of parameter values to update parameter dictionary with
        check_conflict : bool, optional
            Whether to check that a parameter in `values` has not already been defined
            in the parameter class when updating it, and if so that its value does not
            change. This is set to True during initialisation, when parameters are
            combined from different sources, and is False by default otherwise
        check_already_exists : bool, optional
            Whether to check that a parameter in `values` already exists when trying to
            update it. This is to avoid cases where an intended change in the parameters
            is ignored due a typo in the parameter name, and is True by default but can
            be manually overridden.
        path : string, optional
            Path from which to load functions
        """
        # check parameter values
        self.check_parameter_values(values)
        # update
        for name, value in values.items():
            # check for conflicts
            if (check_conflict is True and name in self.keys() and
                    not (self[name] == float(value) or self[name] == value)):
                raise ValueError(
                    "parameter '{}' already defined with value '{}'".format(
                        name, self[name]))
            # check parameter already exists (for updating parameters)
            if check_already_exists is True:
                try:
                    self._dict_items[name]
                except KeyError as err:
                    raise KeyError(
                        "Cannot update parameter '{}' as it does not ".format(
                            name) +
                        "have a default value. ({}). If you are ".format(
                            err.args[0]) +
                        "sure you want to update this parameter, use " +
                        "param.update({{name: value}}, check_already_exists=False)"
                    )
            # if no conflicts, update, loading functions and data if they are specified
            # Functions are flagged with the string "[function]"
            if isinstance(value, str):
                if value.startswith("[function]"):
                    loaded_value = pybamm.load_function(
                        os.path.join(path, value[10:]))
                    self._dict_items[name] = loaded_value
                    values[name] = loaded_value
                # Data is flagged with the string "[data]" or "[current data]"
                elif value.startswith("[current data]") or value.startswith(
                        "[data]"):
                    if value.startswith("[current data]"):
                        data_path = os.path.join(pybamm.root_dir(), "pybamm",
                                                 "input", "drive_cycles")
                        filename = os.path.join(data_path, value[14:] + ".csv")
                        function_name = value[14:]
                    else:
                        filename = os.path.join(path, value[6:] + ".csv")
                        function_name = value[6:]
                    filename = pybamm.get_parameters_filepath(filename)
                    data = pd.read_csv(filename,
                                       comment="#",
                                       skip_blank_lines=True,
                                       header=None).to_numpy()
                    # Save name and data
                    self._dict_items[name] = (function_name, data)
                    values[name] = (function_name, data)
                elif value == "[input]":
                    self._dict_items[name] = pybamm.InputParameter(name)
                # Anything else should be a converted to a float
                else:
                    self._dict_items[name] = float(value)
                    values[name] = float(value)
            else:
                self._dict_items[name] = value
        # reset processed symbols
        self._processed_symbols = {}
Example #18
0
import pybamm
import numpy as np
import os
import pickle
import scipy.interpolate as interp

# change working directory to the root of pybamm
os.chdir(pybamm.root_dir())

"-----------------------------------------------------------------------------"
"Pick C_rate and load comsol data"

# C_rate
# NOTE: the results in pybamm stop when a voltage cutoff is reached, so
# for higher C-rate the pybamm solution may stop before the comsol solution
C_rates = {"01": 0.1, "05": 0.5, "1": 1, "2": 2, "3": 3}
C_rate = "1"  # choose the key from the above dictionary of available results

# load the comsol results
comsol_results_path = pybamm.get_parameters_filepath(
    "input/comsol_results/comsol_{}C.pickle".format(C_rate))
comsol_variables = pickle.load(open(comsol_results_path, "rb"))

"-----------------------------------------------------------------------------"
"Create and solve pybamm model"

# load model and geometry
pybamm.set_logging_level("INFO")
pybamm_model = pybamm.lithium_ion.DFN()
geometry = pybamm_model.default_geometry
Example #19
0
    def test_load_params(self):
        data_D_e = {
            "EC_DMC_1_1": [1.94664e-10, 1.94233e-10],
            "EC_EMC_3_7": [2.01038e-10, 1.78391e-10],
            "EMC_FEC_19_1": [2.16871e-10, 1.8992e-10],
        }
        data_sigma_e = {
            "EC_DMC_1_1": [0.870352, 0.839076],
            "EC_EMC_3_7": [0.695252, 0.668677],
            "EMC_FEC_19_1": [0.454054, 0.632419],
        }
        data_TDF = {
            "EC_DMC_1_1": [1.84644, 4.16915],
            "EC_EMC_3_7": [1.82671, 3.9218],
            "EMC_FEC_19_1": [0.92532, 3.22481],
        }
        data_tplus = {
            "EC_DMC_1_1": [0.17651, 0.241924],
            "EC_EMC_3_7": [0.0118815, 0.151879],
            "EMC_FEC_19_1": [-0.0653014, 0.0416203],
        }

        T = [273.15 + 10.0, 273.15 + 30.0]
        c = [1000.0, 2000.0]

        for solvent in ["EC_DMC_1_1", "EC_EMC_3_7", "EMC_FEC_19_1"]:
            root = pybamm.root_dir()
            p = ("pybamm/input/parameters/lithium-ion/electrolytes/lipf6_" +
                 solvent + "_Landesfeind2019/")
            k_path = os.path.join(root, p)

            sigma_e = pybamm.load_function(
                os.path.join(
                    k_path,
                    "electrolyte_conductivity_" + solvent +
                    "_Landesfeind2019.py",
                ))
            D_e = pybamm.load_function(
                os.path.join(
                    k_path, "electrolyte_diffusivity_" + solvent +
                    "_Landesfeind2019.py"))
            TDF = pybamm.load_function(
                os.path.join(
                    k_path,
                    "electrolyte_TDF_" + solvent + "_Landesfeind2019.py"))
            tplus = pybamm.load_function(
                os.path.join(
                    k_path,
                    "electrolyte_transference_number_" + solvent +
                    "_Landesfeind2019.py",
                ))

            for i, _ in enumerate(T):
                self.assertAlmostEqual(sigma_e(c[i], T[i]).value,
                                       data_sigma_e[solvent][i],
                                       places=5)
                self.assertAlmostEqual(D_e(c[i], T[i]).value,
                                       data_D_e[solvent][i],
                                       places=5)
                self.assertAlmostEqual(TDF(c[i], T[i]),
                                       data_TDF[solvent][i],
                                       places=5)
                self.assertAlmostEqual(tplus(c[i], T[i]),
                                       data_tplus[solvent][i],
                                       places=5)
Example #20
0
    def test_load_function(self):
        # Test filename ends in '.py'
        with self.assertRaisesRegex(
                ValueError, "Expected filename.py, but got doesnotendindotpy"):
            pybamm.load_function("doesnotendindotpy")

        # Test replace function and deprecation warning for lithium-ion
        with self.assertWarns(Warning):
            warn_path = os.path.join(
                "pybamm",
                "input",
                "parameters",
                "lithium-ion",
                "negative_electrodes",
                "graphite_Chen2020",
                "graphite_LGM50_electrolyte_exchange_current_density_Chen2020.py",
            )
            pybamm.load_function(warn_path)

        # Test replace function and deprecation warning for lead-acid
        with self.assertWarns(Warning):
            warn_path = os.path.join(
                "pybamm",
                "input",
                "parameters",
                "lead-acid",
                "negative_electrodes",
                "lead_Sulzer2019",
                "lead_exchange_current_density_Sulzer2019.py",
            )
            pybamm.load_function(warn_path)

        # Test exception if absolute file not found
        with self.assertRaisesRegex(
                ValueError, "is an absolute path, but the file is not found"):
            nonexistent_abs_file = os.path.join(os.getcwd(), "i_dont_exist.py")
            pybamm.load_function(nonexistent_abs_file)

        # Test exception if relative file not found
        with self.assertRaisesRegex(ValueError,
                                    "cannot be found in the PyBaMM directory"):
            pybamm.load_function("i_dont_exist.py")

        # Test exception if relative file found more than once
        with self.assertRaisesRegex(
                ValueError, "found multiple times in the PyBaMM directory"):
            pybamm.load_function("__init__.py")

        # Test exception if no matching function found in module
        with self.assertRaisesRegex(ValueError,
                                    "No function .+ found in module .+"):
            pybamm.load_function("process_symbol_bad_function.py")

        # Test function load with absolute path
        abs_test_path = os.path.join(
            pybamm.root_dir(),
            "tests",
            "unit",
            "test_parameters",
            "data",
            "process_symbol_test_function.py",
        )
        self.assertTrue(os.path.isfile(abs_test_path))
        func = pybamm.load_function(abs_test_path)
        self.assertEqual(func(2), 246)

        # Test function load with relative path
        func = pybamm.load_function("process_symbol_test_function.py")
        self.assertEqual(func(3), 369)
Example #21
0
def load_function(filename):
    """
    Load a python function from a file "function_name.py" called "function_name".
    The filename might either be an absolute path, in which case that specific file will
    be used, or the file will be searched for relative to PyBaMM root.

    Arguments
    ---------
    filename : str
        The name of the file containing the function of the same name.

    Returns
    -------
    function
        The python function loaded from the file.
    """

    if not filename.endswith(".py"):
        raise ValueError("Expected filename.py, but got {}".format(filename))

    # If it's an absolute path, find that exact file
    if os.path.isabs(filename):
        if not os.path.isfile(filename):
            raise ValueError(
                "{} is an absolute path, but the file is not found".format(filename)
            )

        valid_filename = filename

    # Else, search in the whole PyBaMM directory for matches
    else:
        search_path = pybamm.root_dir()

        head, tail = os.path.split(filename)

        matching_files = []

        for root, _, files in os.walk(search_path):
            for file in files:
                if file == tail:
                    full_path = os.path.join(root, file)
                    if full_path.endswith(filename):
                        matching_files.append(full_path)

        if len(matching_files) == 0:
            raise ValueError(
                "{} cannot be found in the PyBaMM directory".format(filename)
            )
        elif len(matching_files) > 1:
            raise ValueError(
                "{} found multiple times in the PyBaMM directory".format(filename)
            )

        valid_filename = matching_files[0]

    # Now: we have some /path/to/valid/filename.py
    # Add "/path/to/vaid" to the python path, and load the module "filename".
    # Then, check "filename" module contains "filename" function.  If it does, return
    # that function object, or raise an exception

    valid_path, valid_leaf = os.path.split(valid_filename)
    sys.path.append(valid_path)

    # Load the module, which must be the leaf of filename, minus the .py extension
    valid_module = valid_leaf.replace(".py", "")
    module_object = importlib.import_module(valid_module)

    # Check that a function of the same name exists in the loaded module
    if valid_module not in dir(module_object):
        raise ValueError(
            "No function {} found in module {}".format(valid_module, valid_module)
        )

    # Remove valid_path from sys_path to avoid clashes down the line
    sys.path.remove(valid_path)

    return getattr(module_object, valid_module)
Example #22
0
    def test_cycle_summary_variables(self):
        # Test cycle_summary_variables works for different combinations of data and
        # function OCPs
        experiment = pybamm.Experiment([
            (
                "Discharge at 1C until 3.3V",
                "Charge at C/3 until 4.0V",
                "Hold at 4.0V until C/10",
            ),
        ] * 5, )
        model = pybamm.lithium_ion.SPM()

        # Chen 2020 plating: pos = function, neg = data
        param = pybamm.ParameterValues(
            chemistry=pybamm.parameter_sets.Chen2020_plating)
        sim = pybamm.Simulation(model,
                                experiment=experiment,
                                parameter_values=param)
        sim.solve(solver=pybamm.CasadiSolver("fast with events"),
                  save_at_cycles=2)

        # Chen 2020: pos = function, neg = function
        param = pybamm.ParameterValues(
            chemistry=pybamm.parameter_sets.Chen2020)
        sim = pybamm.Simulation(model,
                                experiment=experiment,
                                parameter_values=param)
        sim.solve(solver=pybamm.CasadiSolver("fast with events"),
                  save_at_cycles=2)

        # Chen 2020 with data: pos = data, neg = data
        # Load negative electrode OCP data
        filename = os.path.join(
            pybamm.root_dir(),
            "pybamm",
            "input",
            "parameters",
            "lithium_ion",
            "negative_electrodes",
            "graphite_Chen2020",
            "graphite_LGM50_ocp_Chen2020.csv",
        )
        function_name = "graphite_Chen2020"
        filename = pybamm.get_parameters_filepath(filename)
        data = pd.read_csv(filename,
                           comment="#",
                           skip_blank_lines=True,
                           header=None).to_numpy()
        param["Negative electrode OCP [V]"] = (function_name, data)

        # Load positive electrode OCP data
        filename = os.path.join(
            pybamm.root_dir(),
            "pybamm",
            "input",
            "parameters",
            "lithium_ion",
            "positive_electrodes",
            "nmc_Chen2020",
            "nmc_LGM50_ocp_Chen2020.csv",
        )
        function_name = "nmc_LGM50_ocp_Chen2020.csv"
        filename = pybamm.get_parameters_filepath(filename)
        data = pd.read_csv(filename,
                           comment="#",
                           skip_blank_lines=True,
                           header=None).to_numpy()
        param["Positive electrode OCP [V]"] = (function_name, data)

        sim = pybamm.Simulation(model,
                                experiment=experiment,
                                parameter_values=param)
        sim.solve(solver=pybamm.CasadiSolver("safe"), save_at_cycles=2)