diff --git a/examples/sim.py b/examples/sim.py index 6bb3e9b11..d88baada2 100644 --- a/examples/sim.py +++ b/examples/sim.py @@ -14,7 +14,7 @@ def run_example(): # Step 1: Create a model object - batt = Battery() + batt = Battery(process_noise_dist='none') # Step 2: Define future loading function def future_loading(t, x=None): @@ -64,6 +64,7 @@ def future_loading(t, x=None): # Note that even though the step size is 2, the odd points in the save frequency are met perfectly, dt is adjusted automatically to capture the save points simulated_results.outputs.plot() + simulated_results.event_states.plot() if isinstance(batt, BatteryElectroChem): # Plotting max current with time diff --git a/src/prog_models/models/battery_electrochem.py b/src/prog_models/models/battery_electrochem.py index 5e567275f..493834d6e 100644 --- a/src/prog_models/models/battery_electrochem.py +++ b/src/prog_models/models/battery_electrochem.py @@ -151,6 +151,12 @@ def update_v0(params: dict) -> dict: 'v0': Vep - Ven - params['x0']['Vo'] - params['x0']['Vsn'] - params['x0']['Vsp'] } +def update_QEOD(params: dict) -> dict: + # Update the charge after which the battery is considered discharged + return { + 'QEOD': params['qnMax']*params['SOC_Thresh'] + } + def update_qSBmax(params : dict) -> dict: # max charge at surface, bulk (pos and neg) return { @@ -164,10 +170,14 @@ class BatteryElectroChemEOD(PrognosticsModel): Vectorized prognostics :term:`model` for a battery, represented by an electrochemical equations as described in the following paper: `M. Daigle and C. Kulkarni, "Electrochemistry-based Battery Modeling for Prognostics," Annual Conference of the Prognostics and Health Management Society 2013, pp. 249-261, New Orleans, LA, October 2013. https://papers.phmsociety.org/index.php/phmconf/article/view/2252`. This model predicts the end of discharge event. + This model has 2 primary events: discharge and lowvoltage, and a third EOD event that combines the two (kept for backwards compatability). Discharge event occurs when the battery is fully discharged (i.e., there is no charge remaining), while lowvoltage occurs when the voltage falls below an acceptable threshold. + The default model parameters included are for Li-ion batteries, specifically 18650-type cells. Experimental discharge curves for these cells can be downloaded from the `Prognostics Center of Excellence Data Repository https://ti.arc.nasa.gov/tech/dash/groups/pcoe/prognostic-data-repository/`. - :term:`Events`: (1) - EOD: End of Discharge + :term:`Events`: (3) + EOD: Original End of discharge event - combination of discharge and lowvoltage events + discharge: Battery is fully discharged + lowvoltage: Voltage falls below minimum threshold :term:`Inputs/Loading`: (1) i: Current draw on the battery @@ -246,14 +256,23 @@ class BatteryElectroChemEOD(PrognosticsModel): Redlich-Kister parameter (- electrode) VEOD : float End of Discharge Voltage Threshold + SOC_Thresh : float + Percentage of initial qnB + qnS (0-1) that defines the minimum charge, after which the battery is considered discharged. Used to calculate apparent soc. + VDropoff : float + Voltage (above VEOD) at which voltage starts playing a role in EOD event state x0 : dict[str, float] Initial :term:`state` + v0 : Initial voltage See Also -------- BatteryElectroChemEOL, BatteryElectroChem, BatteryElectroChemEODEOL + + Note + -------- + lowvoltage can occur either when the battery is discharge (i.e., discharge :term:`event state` is approaching 0), or when there is a high current draw. If the discharge :term:`event state` is still high and the lowvoltage :term:`event` is occuring, decreasing the current draw will fix the lowvoltage issue. Maximum sustainable current without triggering lowvoltage decreases as the battery discharges. """ - events = ['EOD'] + events = ['discharge', 'lowvoltage', 'EOD'] inputs = ['i'] states = ['tb', 'Vo', 'Vsn', 'Vsp', 'qnB', 'qnS', 'qpB', 'qpS'] outputs = ['t', 'v'] @@ -300,9 +319,9 @@ class BatteryElectroChemEOD(PrognosticsModel): 'process_noise': 1e-3, - # End of discharge voltage threshold 'VEOD': 3.0, - 'VDropoff': 0.1 # Voltage above EOD after which voltage will be considered in SOC calculation + 'SOC_Thresh': 0.05, + 'VDropoff': 0.1 } state_limits = { @@ -314,7 +333,14 @@ class BatteryElectroChemEOD(PrognosticsModel): } param_callbacks = { # Callbacks for derived parameters + 'An': [update_v0], + 'Ap': [update_v0], + 'x0': [update_v0], + 'qnMax': [update_QEOD], + 'qSMax': [update_v0], 'qMobile': [update_qmax], + 'U0n': [update_v0], + 'U0p': [update_v0], 'VolSFraction': [update_vols, update_qpSBmin, update_qpSBmax, update_qSBmax], 'Vol': [update_vols], 'qMax': [update_qpmin, update_qpmax, update_qpSBmin, update_qpSBmax, update_qnmin, update_qnmax, update_qpSBmin, update_qpSBmax, update_qSBmax], @@ -493,10 +519,13 @@ def event_state(self, x : dict) -> dict: Vep = params['U0p'] + R*x['tb']/F*np.log(one_minus_xpS/xpS) + sum(VepParts) v = Vep - Ven - x['Vo'] - x['Vsn'] - x['Vsp'] - charge_EOD = (x['qnS'] + x['qnB'])/self.parameters['qnMax'] - voltage_EOD = (v - self.parameters['VEOD'])/self.parameters['VDropoff'] + charge_EOD = (x['qnS'] + x['qnB']-params['QEOD'])/(self.parameters['qnMax']-params['QEOD']) + voltage_EOD = (v - self.parameters['VEOD'])/(self.parameters['v0'] - self.parameters['VEOD']) + voltage_EOD_old = (v - self.parameters['VEOD'])/self.parameters['VDropoff'] return { - 'EOD': min(charge_EOD, voltage_EOD) + 'discharge': max(min(charge_EOD, 1),0), + 'lowvoltage': max(min(voltage_EOD, 1),0), + 'EOD': max(min(min(charge_EOD, voltage_EOD_old), 1), 0) } def output(self, x : dict): @@ -557,7 +586,9 @@ def threshold_met(self, x : dict) -> dict: # Return true if voltage is less than the voltage threshold return { - 'EOD': z['v'] < self.parameters['VEOD'] + 'EOD': z['v'] < self.parameters['VEOD'], + 'discharge': (x['qnB'] + x['qnS']) < self.parameters['QEOD'], + 'lowvoltage': z['v'] < self.parameters['VEOD'] }