From 244b22ea645c1cc2f0724aac82634e04b5d630a3 Mon Sep 17 00:00:00 2001 From: Alec Bills Date: Wed, 15 Mar 2023 13:53:28 -0400 Subject: [PATCH 1/6] batt-carnot --- .../lithium_ion/electrode_soh.py | 47 +++++++++++++++++++ .../test_lithium_ion/test_electrode_soh.py | 17 +++++++ 2 files changed, 64 insertions(+) diff --git a/pybamm/models/full_battery_models/lithium_ion/electrode_soh.py b/pybamm/models/full_battery_models/lithium_ion/electrode_soh.py index 1f21baa39b..b9ff099101 100644 --- a/pybamm/models/full_battery_models/lithium_ion/electrode_soh.py +++ b/pybamm/models/full_battery_models/lithium_ion/electrode_soh.py @@ -557,3 +557,50 @@ def get_min_max_stoichiometries( """ esoh_solver = ElectrodeSOHSolver(parameter_values, param, known_value) return esoh_solver.get_min_max_stoichiometries() + +def calculate_theoretical_energy(parameter_values, initial_soc=1.0, final_soc=0.0, points=100): + """ + Calculate maximum energy possible from a cell given OCV, initial soc, and final soc + given voltage limits, open-circuit potentials, etc defined by parameter_values + + Parameters + ---------- + parameter_values : :class:`pybamm.ParameterValues` + The parameter values class that will be used for the simulation. + initial_soc : float + The soc at begining of discharge, default 1.0 + final_soc : float + The soc at end of discharge, default 1.0 + points : int + The number of points at which to calculate voltage. + + Returns + ------- + E + The total energy of the cell in Wh + """ + #Get initial and final stoichiometric values + n_i, p_i = get_initial_stoichiometries(initial_soc, parameter_values) + n_f, p_f = get_initial_stoichiometries(final_soc, parameter_values) + n_vals = np.linspace(n_i, n_f, num=points) + p_vals = np.linspace(p_i, p_f, num=points) + #Calculate OCV at each stoichiometry + Vs = np.empty(n_vals.shape) + for i in range(n_vals.size): + V_pos = parameter_values["Positive electrode OCP [V]"](p_vals[i]).value + V_neg = parameter_values["Negative electrode OCP [V]"](n_vals[i]).value + Vs[i] = V_pos - V_neg + Q_max_pos = parameter_values["Maximum concentration in positive electrode [mol.m-3]"] + #Get electrode properties to convert stoich range to moles Li + W = parameter_values["Electrode width [m]"] + H = parameter_values["Electrode height [m]"] + T_pos = parameter_values["Positive electrode thickness [m]"] + eps_s_pos = parameter_values["Positive electrode active material volume fraction"] + vol_pos = W*H*T_pos*eps_s_pos + #Calculate dQ + Q_p = vol_pos*Q_max_pos*(p_f - p_i) + dQ = Q_p/(points - 1) + #Integrate and convert to W-h + E = np.trapz(Vs, dx=dQ)*26.8 + return E + \ No newline at end of file diff --git a/tests/unit/test_models/test_full_battery_models/test_lithium_ion/test_electrode_soh.py b/tests/unit/test_models/test_full_battery_models/test_lithium_ion/test_electrode_soh.py index 5b5fe61859..9d901d4b93 100644 --- a/tests/unit/test_models/test_full_battery_models/test_lithium_ion/test_electrode_soh.py +++ b/tests/unit/test_models/test_full_battery_models/test_lithium_ion/test_electrode_soh.py @@ -142,6 +142,23 @@ def test_known_solution(self): self.assertAlmostEqual(sol["Uw(x_100)"].data[0], V_max, places=5) self.assertAlmostEqual(sol["Uw(x_0)"].data[0], V_min, places=5) +class TestCalculateTheoreticalEnergy(unittest.TestCase): + def test_efficiency(self): + model = pybamm.lithium_ion.DFN( + options = {"calculate discharge energy" : True} + ) + parameter_values = pybamm.ParameterValues("Chen2020") + sim = pybamm.Simulation(model, parameter_values=parameter_values) + sol = sim.solve([0,3600], initial_soc = 1.0) + discharge_energy = sol["Discharge energy [W.h]"].entries[-1] + theoretical_energy = pybamm.lithium_ion.electrode_soh.calculate_theoretical_energy(parameter_values) + # Real energy should be less than discharge energy, and both should be greater than 1 + self.assertLess(discharge_energy, theoretical_energy) + self.assertLess(0, discharge_energy) + self.assertLess(0, theoretical_energy) + + + class TestGetInitialSOC(unittest.TestCase): def test_initial_soc(self): From 3ef943dbcf7d600c29f3de7fc24b1a232f99470c Mon Sep 17 00:00:00 2001 From: Alec Bills Date: Wed, 15 Mar 2023 13:57:41 -0400 Subject: [PATCH 2/6] make fail --- .../test_lithium_ion/test_electrode_soh.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/unit/test_models/test_full_battery_models/test_lithium_ion/test_electrode_soh.py b/tests/unit/test_models/test_full_battery_models/test_lithium_ion/test_electrode_soh.py index 9d901d4b93..d9a6ebd5a8 100644 --- a/tests/unit/test_models/test_full_battery_models/test_lithium_ion/test_electrode_soh.py +++ b/tests/unit/test_models/test_full_battery_models/test_lithium_ion/test_electrode_soh.py @@ -152,10 +152,11 @@ def test_efficiency(self): sol = sim.solve([0,3600], initial_soc = 1.0) discharge_energy = sol["Discharge energy [W.h]"].entries[-1] theoretical_energy = pybamm.lithium_ion.electrode_soh.calculate_theoretical_energy(parameter_values) - # Real energy should be less than discharge energy, and both should be greater than 1 + # Real energy should be less than discharge energy, and both should be greater than 0 self.assertLess(discharge_energy, theoretical_energy) self.assertLess(0, discharge_energy) self.assertLess(0, theoretical_energy) + self.assertEqual(0, 1) From 3842558036439d4c5abb9030bf9682acada209d2 Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Wed, 15 Mar 2023 14:08:17 -0400 Subject: [PATCH 3/6] Method for calculating theoretical efficiency --- .../lithium_ion/electrode_soh.py | 28 +++++++++++-------- .../test_lithium_ion/test_electrode_soh.py | 20 +++++++------ 2 files changed, 27 insertions(+), 21 deletions(-) diff --git a/pybamm/models/full_battery_models/lithium_ion/electrode_soh.py b/pybamm/models/full_battery_models/lithium_ion/electrode_soh.py index b9ff099101..b32022e44a 100644 --- a/pybamm/models/full_battery_models/lithium_ion/electrode_soh.py +++ b/pybamm/models/full_battery_models/lithium_ion/electrode_soh.py @@ -558,7 +558,10 @@ def get_min_max_stoichiometries( esoh_solver = ElectrodeSOHSolver(parameter_values, param, known_value) return esoh_solver.get_min_max_stoichiometries() -def calculate_theoretical_energy(parameter_values, initial_soc=1.0, final_soc=0.0, points=100): + +def calculate_theoretical_energy( + parameter_values, initial_soc=1.0, final_soc=0.0, points=100 +): """ Calculate maximum energy possible from a cell given OCV, initial soc, and final soc given voltage limits, open-circuit potentials, etc defined by parameter_values @@ -579,28 +582,29 @@ def calculate_theoretical_energy(parameter_values, initial_soc=1.0, final_soc=0. E The total energy of the cell in Wh """ - #Get initial and final stoichiometric values + # Get initial and final stoichiometric values. n_i, p_i = get_initial_stoichiometries(initial_soc, parameter_values) n_f, p_f = get_initial_stoichiometries(final_soc, parameter_values) n_vals = np.linspace(n_i, n_f, num=points) p_vals = np.linspace(p_i, p_f, num=points) - #Calculate OCV at each stoichiometry + # Calculate OCV at each stoichiometry Vs = np.empty(n_vals.shape) for i in range(n_vals.size): V_pos = parameter_values["Positive electrode OCP [V]"](p_vals[i]).value V_neg = parameter_values["Negative electrode OCP [V]"](n_vals[i]).value Vs[i] = V_pos - V_neg - Q_max_pos = parameter_values["Maximum concentration in positive electrode [mol.m-3]"] - #Get electrode properties to convert stoich range to moles Li + Q_max_pos = parameter_values[ + "Maximum concentration in positive electrode [mol.m-3]" + ] + # Get electrode properties to convert stoich range to moles Li W = parameter_values["Electrode width [m]"] H = parameter_values["Electrode height [m]"] T_pos = parameter_values["Positive electrode thickness [m]"] eps_s_pos = parameter_values["Positive electrode active material volume fraction"] - vol_pos = W*H*T_pos*eps_s_pos - #Calculate dQ - Q_p = vol_pos*Q_max_pos*(p_f - p_i) - dQ = Q_p/(points - 1) - #Integrate and convert to W-h - E = np.trapz(Vs, dx=dQ)*26.8 + vol_pos = W * H * T_pos * eps_s_pos + # Calculate dQ + Q_p = vol_pos * Q_max_pos * (p_f - p_i) + dQ = Q_p / (points - 1) + # Integrate and convert to W-h + E = np.trapz(Vs, dx=dQ) * 26.8 return E - \ No newline at end of file diff --git a/tests/unit/test_models/test_full_battery_models/test_lithium_ion/test_electrode_soh.py b/tests/unit/test_models/test_full_battery_models/test_lithium_ion/test_electrode_soh.py index d9a6ebd5a8..1f76fc6c33 100644 --- a/tests/unit/test_models/test_full_battery_models/test_lithium_ion/test_electrode_soh.py +++ b/tests/unit/test_models/test_full_battery_models/test_lithium_ion/test_electrode_soh.py @@ -142,23 +142,25 @@ def test_known_solution(self): self.assertAlmostEqual(sol["Uw(x_100)"].data[0], V_max, places=5) self.assertAlmostEqual(sol["Uw(x_0)"].data[0], V_min, places=5) + class TestCalculateTheoreticalEnergy(unittest.TestCase): def test_efficiency(self): - model = pybamm.lithium_ion.DFN( - options = {"calculate discharge energy" : True} - ) + model = pybamm.lithium_ion.DFN(options={"calculate discharge energy": "true"}) parameter_values = pybamm.ParameterValues("Chen2020") sim = pybamm.Simulation(model, parameter_values=parameter_values) - sol = sim.solve([0,3600], initial_soc = 1.0) + sol = sim.solve([0, 3600], initial_soc=1.0) discharge_energy = sol["Discharge energy [W.h]"].entries[-1] - theoretical_energy = pybamm.lithium_ion.electrode_soh.calculate_theoretical_energy(parameter_values) - # Real energy should be less than discharge energy, and both should be greater than 0 + theoretical_energy = ( + pybamm.lithium_ion.electrode_soh.calculate_theoretical_energy( + parameter_values + ) + ) + # Real energy should be less than discharge energy, + # and both should be greater than 0 + print(discharge_energy / theoretical_energy) self.assertLess(discharge_energy, theoretical_energy) self.assertLess(0, discharge_energy) self.assertLess(0, theoretical_energy) - self.assertEqual(0, 1) - - class TestGetInitialSOC(unittest.TestCase): From a749f82575466a0ff445b97b41deb62e7b2b11e1 Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Wed, 15 Mar 2023 14:10:33 -0400 Subject: [PATCH 4/6] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index dafc054618..872c9aac96 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ - Added an option for using a banded jacobian and sundials banded solvers for the IDAKLU solve ([#2677](https://github.com/pybamm-team/PyBaMM/pull/2677)) - The "particle size" option can now be a tuple to allow different behaviour in each electrode ([#2672](https://github.com/pybamm-team/PyBaMM/pull/2672)). - Added temperature control to experiment class. ([#2518](https://github.com/pybamm-team/PyBaMM/pull/2518)) +- Added method to calculate maximum theoretical energy. ([#2777](https://github.com/pybamm-team/PyBaMM/pull/2777)) ## Bug fixes From 617549cbd9ed32e1142a731043c5b4bbd864c450 Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Wed, 15 Mar 2023 14:16:34 -0400 Subject: [PATCH 5/6] whoops, forgot the ol debug print statement --- .../test_lithium_ion/test_electrode_soh.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/unit/test_models/test_full_battery_models/test_lithium_ion/test_electrode_soh.py b/tests/unit/test_models/test_full_battery_models/test_lithium_ion/test_electrode_soh.py index 1f76fc6c33..6e1f9fdfb8 100644 --- a/tests/unit/test_models/test_full_battery_models/test_lithium_ion/test_electrode_soh.py +++ b/tests/unit/test_models/test_full_battery_models/test_lithium_ion/test_electrode_soh.py @@ -157,7 +157,6 @@ def test_efficiency(self): ) # Real energy should be less than discharge energy, # and both should be greater than 0 - print(discharge_energy / theoretical_energy) self.assertLess(discharge_energy, theoretical_energy) self.assertLess(0, discharge_energy) self.assertLess(0, theoretical_energy) From 0fa49e2f849d755012988066059c6fa37dac1629 Mon Sep 17 00:00:00 2001 From: Alexander Bills Date: Wed, 15 Mar 2023 16:45:09 -0400 Subject: [PATCH 6/6] edits --- .../lithium_ion/electrode_soh.py | 21 +++++++------------ 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/pybamm/models/full_battery_models/lithium_ion/electrode_soh.py b/pybamm/models/full_battery_models/lithium_ion/electrode_soh.py index b32022e44a..0706ae6a29 100644 --- a/pybamm/models/full_battery_models/lithium_ion/electrode_soh.py +++ b/pybamm/models/full_battery_models/lithium_ion/electrode_soh.py @@ -588,23 +588,16 @@ def calculate_theoretical_energy( n_vals = np.linspace(n_i, n_f, num=points) p_vals = np.linspace(p_i, p_f, num=points) # Calculate OCV at each stoichiometry + param = pybamm.LithiumIonParameters() + T = param.T_amb(0) Vs = np.empty(n_vals.shape) for i in range(n_vals.size): - V_pos = parameter_values["Positive electrode OCP [V]"](p_vals[i]).value - V_neg = parameter_values["Negative electrode OCP [V]"](n_vals[i]).value - Vs[i] = V_pos - V_neg - Q_max_pos = parameter_values[ - "Maximum concentration in positive electrode [mol.m-3]" - ] - # Get electrode properties to convert stoich range to moles Li - W = parameter_values["Electrode width [m]"] - H = parameter_values["Electrode height [m]"] - T_pos = parameter_values["Positive electrode thickness [m]"] - eps_s_pos = parameter_values["Positive electrode active material volume fraction"] - vol_pos = W * H * T_pos * eps_s_pos + Vs[i] = parameter_values.evaluate( + param.p.prim.U(p_vals[i], T) + ) - parameter_values.evaluate(param.n.prim.U(n_vals[i], T)) # Calculate dQ - Q_p = vol_pos * Q_max_pos * (p_f - p_i) + Q_p = parameter_values.evaluate(param.p.prim.Q_init) * (p_f - p_i) dQ = Q_p / (points - 1) # Integrate and convert to W-h - E = np.trapz(Vs, dx=dQ) * 26.8 + E = np.trapz(Vs, dx=dQ) return E