diff --git a/.gitignore b/.gitignore index 3c3bb708d..2bafcca63 100644 --- a/.gitignore +++ b/.gitignore @@ -314,3 +314,6 @@ $RECYCLE.BIN/ # Airspeed Velocity *.asv/ results/ + +# Pycharm +*.idea/ diff --git a/CHANGELOG.md b/CHANGELOG.md index b2cb56fe9..616a26aeb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ ## Features +## Bug Fixes + +## Breaking Changes + +# [v24.6.1](https://github.com/pybop-team/PyBOP/tree/v24.6.1) - 2024-07-31 + +## Features +- [#313](https://github.com/pybop-team/PyBOP/pull/313/) - Fixes for PyBaMM v24.5, drops support for PyBaMM v23.9, v24.1 ## Bug Fixes diff --git a/CITATION.cff b/CITATION.cff index b5c838165..6583447f3 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -11,5 +11,5 @@ authors: family-names: Courtier - given-names: David family-names: Howey -version: "24.6" # Update this when you release a new version +version: "24.6.1" # Update this when you release a new version repository-code: 'https://www.github.com/pybop-team/pybop' diff --git a/docs/_static/switcher.json b/docs/_static/switcher.json index 2847bc265..8985f8ee1 100644 --- a/docs/_static/switcher.json +++ b/docs/_static/switcher.json @@ -6,7 +6,7 @@ { "name": "v24.6 (stable)", "version": "v24.6", - "url": "https://pybop-docs.readthedocs.io/en/v24.6/", + "url": "https://pybop-docs.readthedocs.io/en/v24.6.1/", "preferred": true }, { diff --git a/examples/scripts/gitt.py b/examples/scripts/gitt.py index 6d3b4a94b..4b8c1561b 100644 --- a/examples/scripts/gitt.py +++ b/examples/scripts/gitt.py @@ -36,10 +36,10 @@ model = pybop.lithium_ion.WeppnerHuggins(parameter_set=parameter_set) parameters = pybop.Parameter( - "Positive electrode diffusivity [m2.s-1]", + "Positive particle diffusivity [m2.s-1]", prior=pybop.Gaussian(5e-14, 1e-13), bounds=[1e-16, 1e-11], - true_value=parameter_set["Positive electrode diffusivity [m2.s-1]"], + true_value=parameter_set["Positive particle diffusivity [m2.s-1]"], ) problem = pybop.FittingProblem( diff --git a/examples/scripts/parameters/example_BPX.json b/examples/scripts/parameters/example_BPX.json index 43bbcf905..1e1efaadb 100644 --- a/examples/scripts/parameters/example_BPX.json +++ b/examples/scripts/parameters/example_BPX.json @@ -1,7 +1,7 @@ { "Header": { - "BPX": 0.1, - "Title": "Parameterisation example of an LFP|graphite 2 Ah cylindrical 18650 cell.", + "BPX": 0.4, + "Title": "Parameterisation example of an LFP|graphite 2 Ah cylindrical 18650 cell. File downloaded on 19/3/24 from https://github.com/FaradayInstitution/BPX/blob/main/examples/lfp_18650_cell_BPX.json", "Description": "LFP|graphite 2 Ah cylindrical 18650 cell. Parameterisation by About:Energy Limited (aboutenergy.io), December 2022, based on cell cycling data, and electrode data gathered after cell teardown. Electrolyte properties from Nyman et al. 2008 (doi:10.1016/j.electacta.2008.04.023). Negative electrode entropic coefficient data are from O'Regan et al. 2022 (doi:10.1016/j.electacta.2022.140700). Positive electrode entropic coefficient data are from Gerver and Meyers 2011 (doi:10.1149/1.3591799). Other thermal properties are estimated.", "Model": "DFN" }, @@ -70,9 +70,6 @@ "Thickness [m]": 2e-05, "Porosity": 0.47, "Transport efficiency": 0.3222 - }, - "User-defined": { - "Source:": "An example BPX json file downloaded on 19/3/24 from https://github.com/FaradayInstitution/BPX/blob/main/examples/lfp_18650_cell_BPX.json" } } } diff --git a/examples/scripts/spm_UKF.py b/examples/scripts/spm_UKF.py index e528c715e..c69883b23 100644 --- a/examples/scripts/spm_UKF.py +++ b/examples/scripts/spm_UKF.py @@ -22,7 +22,7 @@ # Make a prediction with measurement noise sigma = 0.001 -t_eval = np.arange(0, 300, 2) +t_eval = np.arange(0, 900, 0.5) values = model.predict(t_eval=t_eval) corrupt_values = values["Voltage [V]"].data + np.random.normal(0, sigma, len(t_eval)) @@ -42,8 +42,8 @@ signal = ["Voltage [V]"] n_states = model.n_states n_signals = len(signal) -covariance = np.diag([0] * 19 + [sigma**2] + [0] * 19 + [sigma**2]) -process_noise = np.diag([0] * 19 + [1e-6] + [0] * 19 + [1e-6]) +covariance = np.diag([0] * 20 + [sigma**2] + [0] * 20 + [sigma**2]) +process_noise = np.diag([0] * 20 + [1e-6] + [0] * 20 + [1e-6]) measurement_noise = np.diag([sigma**2]) observer = pybop.UnscentedKalmanFilterObserver( parameters, diff --git a/pybop/_experiment.py b/pybop/_experiment.py index 1c4953849..a651dffc2 100644 --- a/pybop/_experiment.py +++ b/pybop/_experiment.py @@ -49,6 +49,4 @@ def __init__( period, temperature, termination, - drive_cycles, - cccv_handling, ) diff --git a/pybop/costs/fitting_costs.py b/pybop/costs/fitting_costs.py index 6315c4b6f..e474fd110 100644 --- a/pybop/costs/fitting_costs.py +++ b/pybop/costs/fitting_costs.py @@ -161,7 +161,7 @@ def _evaluate(self, inputs: Inputs, grad=None): e = np.asarray( [ - np.sum(((prediction[signal] - self._target[signal]) ** 2)) + np.sum((prediction[signal] - self._target[signal]) ** 2) for signal in self.signal ] ) diff --git a/pybop/models/base_model.py b/pybop/models/base_model.py index e9ba3d6c4..eba7ed88e 100644 --- a/pybop/models/base_model.py +++ b/pybop/models/base_model.py @@ -11,7 +11,7 @@ @dataclass -class TimeSeriesState(object): +class TimeSeriesState: """ The current state of a time series model that is a pybamm model. """ @@ -119,9 +119,13 @@ def build( self.set_params() self._mesh = pybamm.Mesh(self.geometry, self.submesh_types, self.var_pts) - self._disc = pybamm.Discretisation(self.mesh, self.spatial_methods) + self._disc = pybamm.Discretisation( + mesh=self.mesh, + spatial_methods=self.spatial_methods, + check_model=check_model, + ) self._built_model = self._disc.process_model( - self._model_with_set_params, inplace=False, check_model=check_model + self._model_with_set_params, inplace=False ) # Clear solver and setup model @@ -230,9 +234,13 @@ def rebuild( self.set_params(rebuild=True) self._mesh = pybamm.Mesh(self.geometry, self.submesh_types, self.var_pts) - self._disc = pybamm.Discretisation(self.mesh, self.spatial_methods) + self._disc = pybamm.Discretisation( + mesh=self.mesh, + spatial_methods=self.spatial_methods, + check_model=check_model, + ) self._built_model = self._disc.process_model( - self._model_with_set_params, inplace=False, check_model=check_model + self._model_with_set_params, inplace=False ) # Clear solver and setup model diff --git a/pybop/models/lithium_ion/weppner_huggins.py b/pybop/models/lithium_ion/weppner_huggins.py index 5d8d626a4..b8707cca9 100644 --- a/pybop/models/lithium_ion/weppner_huggins.py +++ b/pybop/models/lithium_ion/weppner_huggins.py @@ -36,7 +36,7 @@ def __init__(self, name="Weppner & Huggins model", **model_kwargs): # Model kwargs (build, options) are not implemented, keeping here for consistent interface if model_kwargs is not dict(build=True): unused_kwargs_warning = "The input model_kwargs are not currently used by the Weppner & Huggins model." - warnings.warn(unused_kwargs_warning, UserWarning) + warnings.warn(unused_kwargs_warning, UserWarning, stacklevel=2) super().__init__({}, name) @@ -65,7 +65,7 @@ def __init__(self, name="Weppner & Huggins model", **model_kwargs): # Parameters ###################### - d_s = Parameter("Positive electrode diffusivity [m2.s-1]") + d_s = Parameter("Positive particle diffusivity [m2.s-1]") c_s_max = Parameter("Maximum concentration in positive electrode [mol.m-3]") diff --git a/pybop/observers/unscented_kalman.py b/pybop/observers/unscented_kalman.py index afbc2a010..c7eb18b0b 100644 --- a/pybop/observers/unscented_kalman.py +++ b/pybop/observers/unscented_kalman.py @@ -152,7 +152,7 @@ def get_current_covariance(self) -> Covariance: @dataclass -class SigmaPoint(object): +class SigmaPoint: """ A sigma point is a point in the state space that is used to estimate the mean and covariance of a random variable. """ @@ -162,7 +162,7 @@ class SigmaPoint(object): w_c: float -class SquareRootUKF(object): +class SquareRootUKF: """ van der Menve, R., & Wan, E. A. (2001). THE SQUARE-ROOT UNSCENTED KALMAN FILTER FOR STATE AND PARAMETER-ESTIMATION. https://doi.org/10.1109/ICASSP.2001.940586 diff --git a/pybop/parameters/parameter_set.py b/pybop/parameters/parameter_set.py index 43f3e999b..821faf682 100644 --- a/pybop/parameters/parameter_set.py +++ b/pybop/parameters/parameter_set.py @@ -67,7 +67,7 @@ def import_parameters(self, json_path=None): # Read JSON file if not self.params and self.json_path: - with open(self.json_path, "r") as file: + with open(self.json_path) as file: self.params = json.load(file) else: raise ValueError( diff --git a/pyproject.toml b/pyproject.toml index 99067e24e..a4334c1e8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,7 +26,7 @@ classifiers = [ ] requires-python = ">=3.9, <3.13" dependencies = [ - "pybamm>=23.9", + "pybamm>=24.5", "numpy>=1.16, <2.0", "scipy>=1.3", "pints>=0.5", @@ -73,7 +73,7 @@ Homepage = "https://github.com/pybop-team/PyBOP" Documentation = "https://pybop-docs.readthedocs.io" Repository = "https://github.com/pybop-team/PyBOP" Releases = "https://github.com/pybop-team/PyBOP/releases" -Changelog = "https://github.com/pybop-team/PyBOP/CHANGELOG.md" +Changelog = "https://github.com/pybop-team/PyBOP/blob/develop/CHANGELOG.md" [tool.pytest.ini_options] addopts = "--showlocals -v -n auto" @@ -81,6 +81,7 @@ addopts = "--showlocals -v -n auto" [tool.ruff] extend-include = ["*.ipynb"] extend-exclude = ["__init__.py"] +fix = true [tool.ruff.lint] extend-select = ["I"] diff --git a/scripts/ci/build_matrix.sh b/scripts/ci/build_matrix.sh index 9dfe32249..491bb59df 100755 --- a/scripts/ci/build_matrix.sh +++ b/scripts/ci/build_matrix.sh @@ -11,8 +11,11 @@ python_version=("3.9" "3.10" "3.11" "3.12") os=("ubuntu-latest" "windows-latest" "macos-13" "macos-14") -# This command fetches the last two PyBaMM versions excluding release candidates from PyPI -pybamm_version=($(curl -s https://pypi.org/pypi/pybamm/json | jq -r '.releases | keys[]' | grep -v rc | tail -n 2 | paste -sd " " -)) +# This command fetches the last PyBaMM version excluding release candidates from PyPI +pybamm_version=($(curl -s https://pypi.org/pypi/pybamm/json | jq -r '.releases | keys[]' | grep -v rc | tail -n 1 | paste -sd " " -)) + +# This command fetches the last PyBaMM versions including release candidates from PyPI +#pybamm_version=($(curl -s https://pypi.org/pypi/pybamm/json | jq -r '.releases | keys[]' | tail -n 1 | paste -sd " " -)) # open dict json='{ @@ -40,7 +43,7 @@ json+=' ] }' -# Filter out incompatible combinations -json=$(echo "$json" | jq -c 'del(.include[] | select(.pybamm_version == "23.9" and .python_version == "3.12"))') +# Example for filtering out incompatible combinations +#json=$(echo "$json" | jq -c 'del(.include[] | select(.pybamm_version == "23.9" and .python_version == "3.12"))') echo "$json" | jq -c . diff --git a/tests/integration/test_model_experiment_changes.py b/tests/integration/test_model_experiment_changes.py index 64d27132a..7bcc33ddb 100644 --- a/tests/integration/test_model_experiment_changes.py +++ b/tests/integration/test_model_experiment_changes.py @@ -22,7 +22,7 @@ class TestModelAndExperimentChanges: ), pybop.Parameters( pybop.Parameter( # non-geometric parameter - "Positive electrode diffusivity [m2.s-1]", + "Positive particle diffusivity [m2.s-1]", prior=pybop.Gaussian(3.43e-15, 1e-15), bounds=[1e-15, 5e-15], true_value=4e-15, diff --git a/tests/unit/test_cost.py b/tests/unit/test_cost.py index 80e44bea5..0fc7c546d 100644 --- a/tests/unit/test_cost.py +++ b/tests/unit/test_cost.py @@ -179,7 +179,8 @@ def test_costs(self, cost): cost([1.1]) # Test option setting - cost.set_fail_gradient(1) + cost.set_fail_gradient(10) + assert cost._de == 10 if isinstance(cost, pybop.SumSquaredError): e, de = cost.evaluateS1([0.5]) @@ -263,4 +264,4 @@ def test_design_costs( # Compute after updating nominal capacity cost = cost_class(problem, update_capacity=True) - cost([0.4]) + assert np.isfinite(cost([0.4])) diff --git a/tests/unit/test_experiment.py b/tests/unit/test_experiment.py index 6d18ef509..71355ff84 100644 --- a/tests/unit/test_experiment.py +++ b/tests/unit/test_experiment.py @@ -18,9 +18,9 @@ def test_experiment(self): pybop_experiment = pybop.Experiment(protocol) pybamm_experiment = pybamm.Experiment(protocol) - assert [ - step.to_dict() for step in pybop_experiment.operating_conditions_steps - ] == [step.to_dict() for step in pybamm_experiment.operating_conditions_steps] + assert [step.to_dict() for step in pybop_experiment.steps] == [ + step.to_dict() for step in pybamm_experiment.steps + ] assert pybop_experiment.cycle_lengths == pybamm_experiment.cycle_lengths diff --git a/tests/unit/test_models.py b/tests/unit/test_models.py index b50a14bfc..b12b3639e 100644 --- a/tests/unit/test_models.py +++ b/tests/unit/test_models.py @@ -358,11 +358,13 @@ def test_non_converged_solution(self): "Voltage [V]": np.zeros(100), } ) - problem = pybop.FittingProblem(model, parameters=parameters, dataset=dataset) - res = problem.evaluate([-0.2, -0.2]) - _, res_grad = problem.evaluateS1([-0.2, -0.2]) + + # Simulate the DFN with active material values of 0. + # The solution elements will not change as the solver will not converge. + output = problem.evaluate([0, 0]) + output_S1, _ = problem.evaluateS1([0, 0]) for key in problem.signal: - assert np.isinf(res.get(key, [])).any() - assert np.isinf(res_grad).any() + assert np.allclose(output.get(key, [])[0], output.get(key, [])) + assert np.allclose(output_S1.get(key, [])[0], output_S1.get(key, [])) diff --git a/tests/unit/test_observer_unscented_kalman.py b/tests/unit/test_observer_unscented_kalman.py index ce60abbc0..a6217cbb8 100644 --- a/tests/unit/test_observer_unscented_kalman.py +++ b/tests/unit/test_observer_unscented_kalman.py @@ -93,6 +93,15 @@ def test_cholupdate(self): SquareRootUKF.cholupdate(R1_, u.copy(), 1.0) np.testing.assert_array_almost_equal(R1, R1_) + # Test hypot + f = 10.0 + j = 20.0 + out_1 = f * np.sqrt(1 + 1.0 * f**2 / j**2) + np.testing.assert_allclose(SquareRootUKF.hypot(f, j, 1.0), out_1) + + j = 10.0 + np.testing.assert_allclose(SquareRootUKF.hypot(f, j, 1.0), float(0)) + @pytest.mark.unit def test_unscented_kalman_filter(self, dataset, observer): t_eval = dataset["Time [s]"] @@ -116,6 +125,11 @@ def test_unscented_kalman_filter(self, dataset, observer): decimal=4, ) + # Test get covariance + cov = observer.get_current_measure() + assert cov.shape == (1, 1) + assert float(0) <= cov[0] + @pytest.mark.unit def test_observe_no_measurement(self, observer): with pytest.raises(ValueError):