From a362f928307fbd57832623339bbe2b1e733427b4 Mon Sep 17 00:00:00 2001 From: "Eric G. Kratz" Date: Fri, 27 Dec 2024 10:35:44 -0500 Subject: [PATCH] Update BPX version and make PyBaMM compatible with Pydantic V2 (#4701) * Force version second attempt * Fix typo * Fixing some tests * Rename some parameters * Fix typo * Remove strings with typos * Fix another bug * Revert file * Fix attribute parsing * Force version * Fix pin * Update changelog --- CHANGELOG.md | 1 + pyproject.toml | 2 +- src/pybamm/parameters/bpx.py | 15 ++++---- tests/unit/test_parameters/test_bpx.py | 52 ++++++++------------------ 4 files changed, 25 insertions(+), 45 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 09e1d11fe2..c15aba8bbe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ ## Breaking changes +- Updated BPX to v0.5.0 and made changes for the switch to Pydantic V2 ([#4701](https://github.com/pybamm-team/PyBaMM/pull/4701)) - Summary variables now calculated only when called, accessed via a class in the same manner as other variables rather than a dictionary. ([#4621](https://github.com/pybamm-team/PyBaMM/pull/4621)) - The conda distribution (`pybamm`) now installs all optional dependencies available on conda-forge. Use the new `pybamm-base` conda package to install PyBaMM with only the required dependencies. ([conda-forge/pybamm-feedstock#70](https://github.com/conda-forge/pybamm-feedstock/pull/70)) diff --git a/pyproject.toml b/pyproject.toml index 9d5d788129..61db5fe44c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -91,7 +91,7 @@ cite = [ ] # Battery Parameter eXchange format bpx = [ - "bpx==0.4.0", + "bpx>=0.5.0,<0.6.0", ] # Low-overhead progress bars tqdm = [ diff --git a/src/pybamm/parameters/bpx.py b/src/pybamm/parameters/bpx.py index 92801c96ca..101cafba19 100644 --- a/src/pybamm/parameters/bpx.py +++ b/src/pybamm/parameters/bpx.py @@ -398,8 +398,7 @@ def _conductivity(c_e, T, Ea, sigma_ref, constant=False): # Add user-defined parameters, if any user_defined = bpx.parameterisation.user_defined if user_defined: - for name in user_defined.__dict__.keys(): - value = getattr(user_defined, name) + for name, value in user_defined: value = process_float_function_table(value, name) if callable(value): pybamm_dict[name] = partial(_callable_func, fun=value) @@ -447,7 +446,7 @@ def _bpx_to_domain_param_dict(instance: BPX, pybamm_dict: dict, domain: Domain) Turns a BPX instance in to a dictionary of parameters for PyBaMM for a given domain """ # Loop over fields in BPX instance and add to pybamm dictionary - for name, field in instance.__fields__.items(): + for name, field in instance.model_fields.items(): value = getattr(instance, name) # Handle blended electrodes, where the field is now an instance of # ElectrodeBlended or ElectrodeBlendedSPM @@ -460,16 +459,16 @@ def _bpx_to_domain_param_dict(instance: BPX, pybamm_dict: dict, domain: Domain) for i, phase_name in enumerate(particle_instance.keys()): phase_instance = particle_instance[phase_name] # Loop over fields in phase instance and add to pybamm dictionary - for name, field in phase_instance.__fields__.items(): - value = getattr(phase_instance, name) + for name_to_add, field_to_add in phase_instance.model_fields.items(): + value = getattr(phase_instance, name_to_add) pybamm_name = PHASE_NAMES[i] + _get_pybamm_name( - field.field_info.alias, domain + field_to_add.alias, domain ) - value = process_float_function_table(value, name) + value = process_float_function_table(value, name_to_add) pybamm_dict[pybamm_name] = value # Handle other fields, which correspond directly to parameters else: - pybamm_name = _get_pybamm_name(field.field_info.alias, domain) + pybamm_name = _get_pybamm_name(field.alias, domain) value = process_float_function_table(value, name) pybamm_dict[pybamm_name] = value return pybamm_dict diff --git a/tests/unit/test_parameters/test_bpx.py b/tests/unit/test_parameters/test_bpx.py index 0f92653c60..5bed44161e 100644 --- a/tests/unit/test_parameters/test_bpx.py +++ b/tests/unit/test_parameters/test_bpx.py @@ -4,11 +4,12 @@ import copy import numpy as np import pytest +from typing import Any class TestBPX: def setup_method(self): - self.base = { + self.base: dict[str, Any] = { "Header": { "BPX": 1.0, "Title": "Parametrisation example", @@ -116,7 +117,7 @@ def test_bpx(self): }, }, }, - copy.copy(self.base), + copy.deepcopy(self.base), ] model = pybamm.lithium_ion.DFN() @@ -132,9 +133,6 @@ def test_bpx(self): with tempfile.NamedTemporaryFile( suffix=filename, delete=False, mode="w" ) as tmp: - # write to a temporary file so we can - # get the source later on using inspect.getsource - # (as long as the file still exists) json.dump(obj, tmp) tmp.flush() @@ -153,13 +151,13 @@ def test_no_already_exists_in_BPX(self): with tempfile.NamedTemporaryFile( suffix="test.json", delete=False, mode="w" ) as test_file: - json.dump(copy.copy(self.base), test_file) + json.dump(copy.deepcopy(self.base), test_file) test_file.flush() params = pybamm.ParameterValues.create_from_bpx(test_file.name) assert "check_already_exists" not in params.keys() def test_constant_functions(self): - bpx_obj = copy.copy(self.base) + bpx_obj = copy.deepcopy(self.base) bpx_obj["Parameterisation"]["Electrolyte"].update( { "Conductivity [S.m-1]": 1, @@ -183,9 +181,6 @@ def test_constant_functions(self): with tempfile.NamedTemporaryFile( suffix=filename, delete=False, mode="w" ) as tmp: - # write to a tempory file so we can - # get the source later on using inspect.getsource - # (as long as the file still exists) json.dump(bpx_obj, tmp) tmp.flush() @@ -210,7 +205,7 @@ def check_constant_output(func): check_constant_output(De) def test_table_data(self): - bpx_obj = copy.copy(self.base) + bpx_obj = copy.deepcopy(self.base) data = {"x": [0, 1], "y": [0, 1]} bpx_obj["Parameterisation"]["Electrolyte"].update( { @@ -237,9 +232,6 @@ def test_table_data(self): with tempfile.NamedTemporaryFile( suffix=filename, delete=False, mode="w" ) as tmp: - # write to a temporary file so we can - # get the source later on using inspect.getsource - # (as long as the file still exists) json.dump(bpx_obj, tmp) tmp.flush() @@ -263,20 +255,17 @@ def test_table_data(self): assert isinstance(dUdT, pybamm.Interpolant) def test_bpx_soc_error(self): - bpx_obj = copy.copy(self.base) + bpx_obj = copy.deepcopy(self.base) with pytest.raises(ValueError, match="Target SOC"): pybamm.ParameterValues.create_from_bpx_obj(bpx_obj, target_soc=10) def test_bpx_arrhenius(self): - bpx_obj = copy.copy(self.base) + bpx_obj = copy.deepcopy(self.base) filename = "tmp.json" with tempfile.NamedTemporaryFile( suffix=filename, delete=False, mode="w" ) as tmp: - # write to a tempory file so we can - # get the source later on using inspect.getsource - # (as long as the file still exists) json.dump(bpx_obj, tmp) tmp.flush() @@ -327,7 +316,7 @@ def arrhenius_assertion(pv, param_key, Ea_key): arrhenius_assertion(pv, param_key, Ea_key) def test_bpx_blended(self): - bpx_obj = copy.copy(self.base) + bpx_obj = copy.deepcopy(self.base) bpx_obj["Parameterisation"]["Positive electrode"] = { "Thickness [m]": 5.23e-05, "Conductivity [S.m-1]": 0.789, @@ -367,9 +356,6 @@ def test_bpx_blended(self): with tempfile.NamedTemporaryFile( suffix=filename, delete=False, mode="w" ) as tmp: - # write to a tempory file so we can - # get the source later on using inspect.getsource - # (as long as the file still exists) json.dump(bpx_obj, tmp) tmp.flush() @@ -393,7 +379,7 @@ def test_bpx_blended(self): sim.solve(calc_esoh=False) def test_bpx_blended_error(self): - bpx_obj = copy.copy(self.base) + bpx_obj = copy.deepcopy(self.base) bpx_obj["Parameterisation"]["Positive electrode"] = { "Thickness [m]": 5.23e-05, "Conductivity [S.m-1]": 0.789, @@ -446,9 +432,6 @@ def test_bpx_blended_error(self): with tempfile.NamedTemporaryFile( suffix=filename, delete=False, mode="w" ) as tmp: - # write to a tempory file so we can - # get the source later on using inspect.getsource - # (as long as the file still exists) json.dump(bpx_obj, tmp) tmp.flush() @@ -456,7 +439,7 @@ def test_bpx_blended_error(self): pybamm.ParameterValues.create_from_bpx(tmp.name) def test_bpx_user_defined(self): - bpx_obj = copy.copy(self.base) + bpx_obj = copy.deepcopy(self.base) data = {"x": [0, 1], "y": [0, 1]} bpx_obj["Parameterisation"]["User-defined"] = { "User-defined scalar parameter": 1.0, @@ -468,9 +451,6 @@ def test_bpx_user_defined(self): with tempfile.NamedTemporaryFile( suffix=filename, delete=False, mode="w" ) as tmp: - # write to a tempory file so we can - # get the source later on using inspect.getsource - # (as long as the file still exists) json.dump(bpx_obj, tmp) tmp.flush() @@ -488,14 +468,14 @@ def test_bpx_user_defined(self): ) def test_bpx_activation_energy_default(self): - bpx_obj = copy.copy(self.base) - bpx_obj["Parameterisation"]["Negative electrode"][ + bpx_obj = copy.deepcopy(self.base) + del bpx_obj["Parameterisation"]["Negative electrode"][ "Diffusivity activation energy [J.mol-1]" - ] = None + ] with tempfile.NamedTemporaryFile( suffix="test.json", delete=False, mode="w" ) as test_file: - json.dump(copy.copy(bpx_obj), test_file) + json.dump(copy.deepcopy(bpx_obj), test_file) test_file.flush() param = pybamm.ParameterValues.create_from_bpx(test_file.name) assert param[ @@ -503,6 +483,6 @@ def test_bpx_activation_energy_default(self): ] == pytest.approx(0.0, rel=1e-12) def test_bpx_from_obj(self): - bpx_obj = copy.copy(self.base) + bpx_obj = copy.deepcopy(self.base) param = pybamm.ParameterValues.create_from_bpx_obj(bpx_obj) assert isinstance(param, pybamm.ParameterValues)