def test_process_function_parameter(self): parameter_values = pybamm.ParameterValues({ "a": 3, "func": pybamm.load_function("process_symbol_test_function.py"), "const": pybamm.load_function("process_symbol_test_constant_function.py"), }) a = pybamm.Parameter("a") # process function func = pybamm.FunctionParameter("func", a) processed_func = parameter_values.process_symbol(func) self.assertIsInstance(processed_func, pybamm.Function) self.assertEqual(processed_func.evaluate(), 369) # process constant function const = pybamm.FunctionParameter("const", a) processed_const = parameter_values.process_symbol(const) self.assertIsInstance(processed_const, pybamm.Function) self.assertEqual(processed_const.evaluate(), 254) # process differentiated function parameter diff_func = func.diff(a) processed_diff_func = parameter_values.process_symbol(diff_func) self.assertEqual(processed_diff_func.evaluate(), 123)
def test_process_function_parameter(self): parameter_values = pybamm.ParameterValues({ "a": 3, "func": pybamm.load_function("process_symbol_test_function.py"), "const": 254, }) a = pybamm.InputParameter("a") # process function func = pybamm.FunctionParameter("func", a) processed_func = parameter_values.process_symbol(func) self.assertEqual(processed_func.evaluate(u={"a": 3}), 369) # process constant function const = pybamm.FunctionParameter("const", a) processed_const = parameter_values.process_symbol(const) self.assertIsInstance(processed_const, pybamm.Scalar) self.assertEqual(processed_const.evaluate(), 254) # process differentiated function parameter diff_func = func.diff(a) processed_diff_func = parameter_values.process_symbol(diff_func) self.assertEqual(processed_diff_func.evaluate(u={"a": 3}), 123) # function itself as input (different to the variable being an input) parameter_values = pybamm.ParameterValues({"func": "[input]"}) a = pybamm.Scalar(3) func = pybamm.FunctionParameter("func", a) processed_func = parameter_values.process_symbol(func) self.assertEqual(processed_func.evaluate(u={"func": 13}), 13)
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_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()
def update(self, values, check_conflict=False, path=""): # check parameter values values = self.check_and_update_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] ) ) # if no conflicts, update, loading functions and data if they are specified else: # Functions are flagged with the string "[function]" if isinstance(value, str): if value.startswith("[function]"): self[name] = pybamm.load_function( os.path.join(path, value[10:] + ".py") ) # Inbuilt functions are flagged with the string "[inbuilt]" elif value.startswith("[inbuilt class]"): # Extra set of brackets at the end makes an instance of the # class self[name] = getattr(pybamm, value[15:])() # Data is flagged with the string "[data]" elif value.startswith("[data]"): data = np.loadtxt(os.path.join(path, value[6:] + ".csv")) # Save name and data self[name] = (value[6:], data) # Anything else should be a converted to a float else: self[name] = float(value) else: self[name] = value # reset processed symbols self._processed_symbols = {}
def test_process_function_parameter(self): parameter_values = pybamm.ParameterValues( { "a": 3, "func": pybamm.load_function( os.path.join( "tests", "unit", "test_parameters", "data", "process_symbol_test_function.py", ) ), "const": 254, "float_func": lambda x: 42, "mult": pybamm.InputParameter("b") * 5, "bad type": np.array([1, 2, 3]), } ) a = pybamm.InputParameter("a") # process function func = pybamm.FunctionParameter("func", {"a": a}) processed_func = parameter_values.process_symbol(func) self.assertEqual(processed_func.evaluate(inputs={"a": 3}), 369) # process constant function const = pybamm.FunctionParameter("const", {"a": a}) processed_const = parameter_values.process_symbol(const) self.assertIsInstance(processed_const, pybamm.Scalar) self.assertEqual(processed_const.evaluate(), 254) # process case where parameter provided is a pybamm symbol # (e.g. a multiplication) mult = pybamm.FunctionParameter("mult", {"a": a}) processed_mult = parameter_values.process_symbol(mult) self.assertEqual(processed_mult.evaluate(inputs={"a": 14, "b": 63}), 63 * 5) # process differentiated function parameter diff_func = func.diff(a) processed_diff_func = parameter_values.process_symbol(diff_func) self.assertEqual(processed_diff_func.evaluate(inputs={"a": 3}), 123) # make sure diff works, despite simplifications, when the child is constant a_const = pybamm.Scalar(3) func_const = pybamm.FunctionParameter("func", {"a": a_const}) diff_func_const = func_const.diff(a_const) processed_diff_func_const = parameter_values.process_symbol(diff_func_const) self.assertEqual(processed_diff_func_const.evaluate(), 123) # function parameter that returns a python float func = pybamm.FunctionParameter("float_func", {"a": a}) processed_func = parameter_values.process_symbol(func) self.assertEqual(processed_func.evaluate(), 42) # weird type raises error func = pybamm.FunctionParameter("bad type", {"a": a}) with self.assertRaisesRegex(TypeError, "Parameter provided for"): parameter_values.process_symbol(func) # function itself as input (different to the variable being an input) parameter_values = pybamm.ParameterValues( {"func": "[input]", "vector func": pybamm.InputParameter("vec", "test")} ) a = pybamm.Scalar(3) func = pybamm.FunctionParameter("func", {"a": a}) processed_func = parameter_values.process_symbol(func) self.assertEqual(processed_func.evaluate(inputs={"func": 13}), 13) func = pybamm.FunctionParameter("vector func", {"a": a}) processed_func = parameter_values.process_symbol(func) self.assertEqual(processed_func.evaluate(inputs={"vec": 13}), 13) # make sure function keeps the domain of the original function def my_func(x): return 2 * x x = pybamm.standard_spatial_vars.x_n func = pybamm.FunctionParameter("func", {"x": x}) parameter_values = pybamm.ParameterValues({"func": my_func}) func1 = parameter_values.process_symbol(func) parameter_values = pybamm.ParameterValues({"func": pybamm.InputParameter("a")}) func2 = parameter_values.process_symbol(func) parameter_values = pybamm.ParameterValues( {"func": pybamm.InputParameter("a", "negative electrode")} ) func3 = parameter_values.process_symbol(func) self.assertEqual(func1.domains, func2.domains) self.assertEqual(func1.domains, func3.domains)
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( os.getcwd(), "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)
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 = {}
def test_process_function_parameter(self): parameter_values = pybamm.ParameterValues({ "a": 3, "func": pybamm.load_function("process_symbol_test_function.py"), "const": 254, "float_func": lambda x: 42, "mult": pybamm.InputParameter("b") * 5, "bad type": np.array([1, 2, 3]), }) a = pybamm.InputParameter("a") # process function func = pybamm.FunctionParameter("func", {"a": a}) processed_func = parameter_values.process_symbol(func) self.assertEqual(processed_func.evaluate(inputs={"a": 3}), 369) # process constant function const = pybamm.FunctionParameter("const", {"a": a}) processed_const = parameter_values.process_symbol(const) self.assertIsInstance(processed_const, pybamm.Scalar) self.assertEqual(processed_const.evaluate(), 254) # process case where parameter provided is a pybamm symbol # (e.g. a multiplication) mult = pybamm.FunctionParameter("mult", {"a": a}) processed_mult = parameter_values.process_symbol(mult) self.assertEqual(processed_mult.evaluate(inputs={ "a": 14, "b": 63 }), 63 * 5) # process differentiated function parameter diff_func = func.diff(a) processed_diff_func = parameter_values.process_symbol(diff_func) self.assertEqual(processed_diff_func.evaluate(inputs={"a": 3}), 123) # function parameter that returns a python float func = pybamm.FunctionParameter("float_func", {"a": a}) processed_func = parameter_values.process_symbol(func) self.assertEqual(processed_func.evaluate(), 42) # weird type raises error func = pybamm.FunctionParameter("bad type", {"a": a}) with self.assertRaisesRegex(TypeError, "Parameter provided for"): parameter_values.process_symbol(func) # function itself as input (different to the variable being an input) parameter_values = pybamm.ParameterValues({ "func": "[input]", "vector func": pybamm.InputParameter("vec", "test") }) a = pybamm.Scalar(3) func = pybamm.FunctionParameter("func", {"a": a}) processed_func = parameter_values.process_symbol(func) self.assertEqual(processed_func.evaluate(inputs={"func": 13}), 13) func = pybamm.FunctionParameter("vector func", {"a": a}) processed_func = parameter_values.process_symbol(func) self.assertEqual(processed_func.evaluate(inputs={"vec": 13}), 13)
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)
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)
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 )