diff --git a/CHANGELOG.md b/CHANGELOG.md index d2938a9781..9118dee54b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ - Added `skip_ok` option to `step` to allow for steps to be skipped if they are infeasible at initial conditions. ([#4839](https://github.com/pybamm-team/PyBaMM/pull/4839)) - Deprecated `CrateTermination` and renamed it to `CRateTermination`. ([#4834](https://github.com/pybamm-team/PyBaMM/pull/4834)) +- Made y, z optional arguments for FunctionParameters that depend on space ([#4883](https://github.com/pybamm-team/PyBaMM/pull/4883)) ## Bug fixes diff --git a/examples/scripts/DFN_ambient_temperature.py b/examples/scripts/DFN_ambient_temperature.py index 4d724aaa0e..fa640fd2ae 100644 --- a/examples/scripts/DFN_ambient_temperature.py +++ b/examples/scripts/DFN_ambient_temperature.py @@ -18,7 +18,7 @@ # load parameter values and process model and geometry -def ambient_temperature(y, z, t): +def ambient_temperature(t, y=None, z=None): return 300 + t * 100 / 3600 diff --git a/examples/scripts/pouch_cell_cooling.py b/examples/scripts/pouch_cell_cooling.py index be35f52420..8434478fd0 100644 --- a/examples/scripts/pouch_cell_cooling.py +++ b/examples/scripts/pouch_cell_cooling.py @@ -20,7 +20,7 @@ parameter_values = model.default_parameter_values -def T_amb(y, z, t): +def T_amb(t, y=None, z=None): return 300 + 20 * pybamm.sin(np.pi * y / L_y) * pybamm.sin(np.pi * z / L_z) diff --git a/src/pybamm/models/submodels/thermal/base_thermal.py b/src/pybamm/models/submodels/thermal/base_thermal.py index 1040227de0..883f2aa8bf 100644 --- a/src/pybamm/models/submodels/thermal/base_thermal.py +++ b/src/pybamm/models/submodels/thermal/base_thermal.py @@ -45,7 +45,7 @@ def _get_standard_fundamental_variables(self, T_dict): # (y, z) only and time y = pybamm.standard_spatial_vars.y z = pybamm.standard_spatial_vars.z - T_amb = self.param.T_amb(y, z, pybamm.t) + T_amb = self.param.T_amb(pybamm.t, y, z) T_amb_av = self._yz_average(T_amb) variables = { diff --git a/src/pybamm/models/submodels/thermal/isothermal.py b/src/pybamm/models/submodels/thermal/isothermal.py index 4b729f1294..1e39dc00da 100644 --- a/src/pybamm/models/submodels/thermal/isothermal.py +++ b/src/pybamm/models/submodels/thermal/isothermal.py @@ -29,7 +29,7 @@ def get_fundamental_variables(self): # Broadcast t to be the same size as y and z (to catch cases where the ambient # temperature is a function of time only) t_broadcast = pybamm.PrimaryBroadcast(pybamm.t, "current collector") - T_x_av = self.param.T_amb(y, z, t_broadcast) + T_x_av = self.param.T_amb(t_broadcast, y, z) T_vol_av = self._yz_average(T_x_av) T_dict = { diff --git a/src/pybamm/parameters/parameter_values.py b/src/pybamm/parameters/parameter_values.py index ba8c6c324e..9312d96962 100644 --- a/src/pybamm/parameters/parameter_values.py +++ b/src/pybamm/parameters/parameter_values.py @@ -4,6 +4,7 @@ from pprint import pformat from warnings import warn from collections import defaultdict +import inspect class ParameterValues: @@ -730,7 +731,13 @@ def _process_symbol(self, symbol): function = pybamm.Scalar(function_name, name=symbol.name) elif callable(function_name): # otherwise evaluate the function to create a new PyBaMM object - function = function_name(*new_children) + sig = inspect.signature(function_name) + # If function accepts fewer parameters than provided, only pass what it can accept + if len(sig.parameters) < len(new_children): + # Only pass the number of parameters the function can accept + function = function_name(*new_children[: len(sig.parameters)]) + else: + function = function_name(*new_children) elif isinstance( function_name, (pybamm.Interpolant, pybamm.InputParameter) ) or ( diff --git a/src/pybamm/parameters/thermal_parameters.py b/src/pybamm/parameters/thermal_parameters.py index cf714d9c06..b287238a99 100644 --- a/src/pybamm/parameters/thermal_parameters.py +++ b/src/pybamm/parameters/thermal_parameters.py @@ -3,6 +3,7 @@ # import pybamm from .base_parameters import BaseParameters +import warnings class ThermalParameters(BaseParameters): @@ -40,22 +41,25 @@ def _set_parameters(self): # Initial temperature self.T_init = pybamm.Parameter("Initial temperature [K]") - def T_amb(self, y, z, t): + def T_amb(self, t, y=None, z=None): """Ambient temperature [K]""" - return pybamm.FunctionParameter( - "Ambient temperature [K]", - { - "Distance across electrode width [m]": y, - "Distance across electrode height [m]": z, - "Time [s]": t, - }, + warnings.warn( + "order of arguments for T_amb has changed. Please use T_amb(t, y, z)", + UserWarning, + stacklevel=2, ) + inputs = {"Time [s]": t} + if y is not None: + inputs["Distance across electrode width [m]"] = y + if z is not None: + inputs["Distance across electrode height [m]"] = z + return pybamm.FunctionParameter("Ambient temperature [K]", inputs) def T_amb_av(self, t): """YZ-averaged ambient temperature [K]""" y = pybamm.standard_spatial_vars.y z = pybamm.standard_spatial_vars.z - return pybamm.yz_average(self.T_amb(y, z, t)) + return pybamm.yz_average(self.T_amb(t, y, z)) def h_edge(self, y, z): """Cell edge heat transfer coefficient [W.m-2.K-1]""" diff --git a/tests/integration/test_models/test_full_battery_models/test_lithium_ion/base_lithium_ion_tests.py b/tests/integration/test_models/test_full_battery_models/test_lithium_ion/base_lithium_ion_tests.py index 8621d8a861..fa40683631 100644 --- a/tests/integration/test_models/test_full_battery_models/test_lithium_ion/base_lithium_ion_tests.py +++ b/tests/integration/test_models/test_full_battery_models/test_lithium_ion/base_lithium_ion_tests.py @@ -376,7 +376,7 @@ def test_basic_processing_temperature_interpolant(self): times = np.arange(0, 4000, 10) tmax = max(times) - def temp_drive_cycle(y, z, t): + def temp_drive_cycle(t, y=None, z=None): return pybamm.Interpolant( times, 298.15 + 20 * (times / tmax), diff --git a/tests/unit/test_experiments/test_experiment.py b/tests/unit/test_experiments/test_experiment.py index 3ddf89412f..0092137a21 100644 --- a/tests/unit/test_experiments/test_experiment.py +++ b/tests/unit/test_experiments/test_experiment.py @@ -355,3 +355,60 @@ def test_pchip_extrapolation(self): result = np.array(f(test_points)).flatten() np.testing.assert_allclose(result, expected, rtol=1e-7, atol=1e-6) + + def test_ambient_temperature_interpolant_in_lumped_model(self): + options = {"thermal": "lumped"} + model = pybamm.lithium_ion.DFN(options) + + geometry = model.default_geometry + + times = np.arange(0, 1810, 10) + tmax = times[-1] + + def ambient_temperature(t): + return pybamm.Interpolant(times, 298.15 + 20 * (times / tmax), pybamm.t) + + param = model.default_parameter_values + param.update( + {"Ambient temperature [K]": ambient_temperature}, check_already_exists=False + ) + + param.process_model(model) + param.process_geometry(geometry) + + var_pts = {"x_n": 30, "x_s": 30, "x_p": 30, "r_n": 10, "r_p": 10} + mesh = pybamm.Mesh(geometry, model.default_submesh_types, var_pts) + disc = pybamm.Discretisation(mesh, model.default_spatial_methods) + disc.process_model(model) + + t_eval = np.linspace(0, 1800, 100) + solver = pybamm.CasadiSolver(mode="fast", atol=1e-6, rtol=1e-3) + solution = solver.solve(model, t_eval) + + amb_temp = solution["Ambient temperature [K]"] + + t_sol = solution.t + computed = amb_temp(t_sol) + + expected = 298.15 + 20 * (t_sol / tmax) + + np.testing.assert_allclose(computed, expected, rtol=1e-2) + + def test_ambient_function_required_spatial_params(self): + options = {"particle": "quadratic profile", "thermal": "lumped"} + model = pybamm.lithium_ion.SPMe(options) + + def ambient_temperature(t, y=None, z=None): + return 300 + t * 100 / 3600 + 2 * y * z + + param = pybamm.ParameterValues("Chen2020") + param.update( + {"Ambient temperature [K]": ambient_temperature}, check_already_exists=False + ) + + with pytest.raises( + ValueError, match=r"Dimensions .* do not exist\. Expected .*" + ): + sim = pybamm.Simulation(model, parameter_values=param) + solution = sim.solve([0, 3600]) + solution["Ambient temperature [K]"](5, 2, 4)