Your first degradation simulation¶
run_ageing_sim runs an ageing campaign the way a lab would: a reference
performance test (RPT) on the fresh cell, then N ageing cycles, another RPT,
and so on until a stop criterion fires. SoH and resistance come from the
RPTs. The degradation physics (SEI growth, lithium inventory loss, plating)
evolves through every cycle.
This notebook checks the cell's operating window, builds the two protocols a campaign needs, previews them, and runs 200 cycles.
from breathe_simulate import api_interface as api
from breathe_simulate.ageing import AgeingCycler, RptCycler
cell_name = "Molicel P45B"
CAP_AH = 4.5 # or: api.get_eqm_kpis(cell_name).capacity
ageing_builder = AgeingCycler(selected_unit="C", cell_capacity=CAP_AH)
rpt_builder = RptCycler(selected_unit="C", cell_capacity=CAP_AH)
Currents in this series are C-rates (selected_unit="C", converted against
nominal capacity). Use selected_unit="A" for amperes. Charge is positive,
discharge negative.
1. What does this cell accept?¶
Degradation models are calibrated inside an operating window, and campaigns
outside it are rejected. get_operating_window reports that window plus the
limits run_sim enforces, for any cell.
api.get_operating_window(cell_name)
OperatingWindow — molicel_p45b
performance (run_sim)
voltage 2.49998 to 4.2 V
capacity 4.5 Ah
degradation (run_ageing_sim)
temperature 10 to 45 degC
C-rate chg <= 3.4C, dch <= 3C
SoC 0 to 100 %
voltage 2.5 to 4.2 V
The same limits as a table:
api.get_operating_window(cell_name).to_frame()
| simulation | quantity | min | max | unit | |
|---|---|---|---|---|---|
| 0 | run_sim | voltage | 2.499981 | 4.200003 | V |
| 1 | run_sim | capacity (C-rate reference) | 4.500000 | 4.500000 | Ah |
| 2 | run_ageing_sim | temperature | 10.000000 | 45.000000 | degC |
| 3 | run_ageing_sim | voltage | 2.500000 | 4.200000 | V |
| 4 | run_ageing_sim | SoC | 0.000000 | 1.000000 | - |
| 5 | run_ageing_sim | max charge rate | NaN | 3.400000 | C |
| 6 | run_ageing_sim | max discharge rate | NaN | 3.000000 | C |
2. What can I configure?¶
describe() lists every parameter with units, defaults and bounds.
ageing_builder.describe()
| unit | default | bounds | description | ||
|---|---|---|---|---|---|
| protocol | parameter | ||||
| cyclic | I_chg | A|C | required | [0, 10000] | CC charge current (positive) of the ageing cycle. |
| I_dch | A|C | required | [-10000, 0] | CC discharge current (negative: discharge) of ... | |
| I_cut | A|C | optional | [1e-06, 30] | Optional charge CV cut-off current (positive).... | |
| I_cut_dch | A|C | optional | - | Optional discharge CV cut-off current (negativ... | |
| V_max | V | required | [1.8, 4.6] | Upper voltage limit. | |
| V_min | V | required | [1.8, 4.6] | Lower voltage limit. | |
| soc_min | - | optional | - | Optional lower SoC bound: legs become amp-hour... | |
| soc_max | - | optional | - | Optional upper SoC bound. | |
| t_rest_s | s | 1800.0 | [0, 86400] | Rest after each leg. | |
| t_max_cv_s | s | 10800.0 | [0, 86400] | Cap on the CV phase. | |
| anode_potential_threshold_mV | mV | optional | - | Optional anode-potential derating threshold fo... | |
| temperature_threshold_degC | degC | optional | - | Optional thermal-derating threshold. | |
| calendar | storage_soc | - | required | [0, 1] | SoC the cell is stored at (set by a partial di... |
| storage_days | days | required | [0, 3650] | Storage duration of ONE calendar block (one bl... | |
| I_chg | A|C | required | [0, 10000] | CC charge current (positive). | |
| I_dch | A|C | required | [-10000, 0] | CC discharge current (negative) used to reach ... | |
| I_cut | A|C | required | [1e-06, 30] | CV cut-off current. | |
| V_max | V | required | [1.8, 4.6] | Upper voltage limit. | |
| V_min | V | required | [1.8, 4.6] | Lower voltage limit. | |
| t_rest_s | s | 3600.0 | [0, 86400] | Rest between legs. | |
| t_max_cv_s | s | 10800.0 | [0, 86400] | Cap on the CV phase. | |
| custom / from_csv | experiment_text | list | required (custom) | - | run_sim CUSTOM steps: command strings, step di... |
| path / time_col / value_col | - | required (from_csv) | - | CSV duty cycle: time column [s] and a current ... | |
| ambient_col | degC | optional | - | Optional ambient-setpoint column: splits the p... | |
| V_guard_min / V_guard_max | V | optional | - | Optional voltage guards for drive-cycle legs: ... | |
| soc_guard_min / soc_guard_max | - | optional | - | Optional SoC guards (0..1, model-tracked SoC) ... | |
| pre / post | list | optional | - | Steps run before/after the profile each cycle ... | |
| anode_potential_threshold_mV | mV | optional | - | Optional anode-potential derating for charge s... | |
| temperature_threshold_degC | degC | optional | - | Optional thermal-derating threshold. |
rpt_builder.describe()
| unit | default | bounds | feeds | description | |
|---|---|---|---|---|---|
| parameter | |||||
| I_chg | A|C | required | [0, 10000] | all charge legs | CC charge current (positive) used to reconditi... |
| I_cut | A|C | required | [1e-06, 30] | all charge legs | CV cut-off current (positive): charge legs hol... |
| V_max | V | required | [1.8, 4.6] | all legs | Upper voltage limit of the RPT. |
| V_min | V | required | [1.8, 4.6] | all legs | Lower voltage limit of the RPT. |
| capacity_checks | list | 1 check (reference) | current [0.0, 10000.0] A | SoH + SoC anchor | Capacity measurement(s): [{'current': positive... |
| t_equilibration_s | s | t_rest_s | - | SoH | Rest before each capacity measurement, to sett... |
| I_capacity_dch | A|C | legacy | [0, 10000] | SoH + SoC anchor | Legacy single-rate form of capacity_checks (be... |
| I_soc_ref_dch | A|C | legacy | [0, 10000] | extra check | Legacy second measurement at a slow rate. Cost... |
| pulses | list | 1 pulse @ SoC 0.5 | soc [0, 1) | current [-10000.0, 10000.0] A | d... | DCIR | Multi-point DCIR map: [{soc, current (signed),... |
| t_rest_s | s | 1800.0 | [0, 86400] | all legs | Rest between legs (relaxation before capacity/... |
| t_rest_before_pulse_s | s | t_rest_s | - | DCIR | Rest before each pulse; its end defines the re... |
| t_rest_after_pulse_s | s | t_rest_s | - | DCIR | Rest after each pulse (merges with the next pu... |
| I_conditioning_dch | A|C | I_soc_ref_dch | - | protocol anchor | Discharge current of the pre-RPT conditioning ... |
| t_conditioning_rest_s | s | t_rest_s | - | protocol anchor | Rest after the pre-RPT conditioning discharge,... |
| I_dva_dch | A|C | off | - | DVA/ICA | Enables the dedicated differential-analysis cy... |
| I_dva_chg | A|C | I_dva_dch | - | DVA/ICA | Charge current of the dedicated DVA cycle (the... |
| I_dva_cut | A|C | I_cut | - | DVA/ICA | CV cut-off of the dedicated DVA cycle's charge... |
| t_max_cv_s | s | 10800.0 | [0, 86400] | all charge legs | Cap on the CV phase duration. |
| reset_temperature | bool | True | - | thermal | Run the RPT at the campaign's initial (referen... |
template() returns an editable starting point. The ageing builder takes
the protocol name.
ageing_builder.template("cyclic")
{'I_chg': None,
'I_dch': None,
'I_cut': None,
'I_cut_dch': None,
'V_max': None,
'V_min': None,
'soc_min': None,
'soc_max': None,
't_rest_s': 1800.0,
't_max_cv_s': 10800.0}
3. Build the two protocols¶
A campaign needs an ageing cycler (one ageing increment, repeated) and an RPT cycler (the check-up). Here the ageing cycle is a 1C CC-CV cycle over the full voltage window. For the RPT you declare capacity checks and DCIR pulses, one of each marked as the reference. The reference capacity check defines SoH and anchors the SoC set points. Each check costs one cycle.
ageing = ageing_builder.cyclic(
I_chg=1.0, # 1C charge
I_dch=-1.0, # 1C discharge
I_cut=0.05, # CV cut-off current. Omit it for pure CC charging
V_max=4.2,
V_min=2.5,
)
rpt = rpt_builder.build(
I_chg=1.0,
I_cut=0.05,
V_max=4.2,
V_min=2.5,
# one capacity check at the use-case rate, so SoH reflects usable
# capacity. Marked reference: it defines SoH and anchors every SoC set point
capacity_checks=[{"current": 1.0, "reference": True}],
t_equilibration_s=1800, # settle at chamber temperature before measuring
pulses=[{"soc": 0.5, "current": -2.0, "duration_s": 30, "reference": True}],
)
4. Preview before you run¶
plot_preview draws the planned profile from coulomb counting, without
simulating.
ageing_builder.plot_preview(ageing)
The RPT previews the same way, and preview() prints the steps. Every RPT
starts from the same defined-empty state, so "full" means the same thing at
every check-up.
rpt_builder.plot_preview(rpt)
print(rpt_builder.preview(rpt))
Conditioning: discharge 4.5 A to 2.5 V, rest 30 min [defined-empty anchor before every RPT] Part 1 (capacity): CC charge 4.5 A to 4.2 V, CV hold to 0.225 A [anchors 'full' identically every RPT] Rest 30 min (equilibration), then capacity discharge 4.5 A to 2.5 V [SoH + SoC anchor] Rest 30 min Part 2 (DCIR): CC-CV charge to 4.2 V, rest 30 min Discharge to SoC 50 % (coulomb-counted on discharge against part 1's reference capacity), rest 30 min Pulse -9 A (discharge) for 30 s, rest 30 min [DCIR reference] Final discharge 4.5 A to 2.5 V (defined empty state)
5. Run the campaign¶
Two hundred cycles with an RPT every 50, stopping early if SoH reaches 80 %.
result = api.run_ageing_sim(
cell_name,
ageing,
rpt_cycler=rpt,
rpt_every_n_cycles=50,
max_cycles=200,
stop_on_soh_pct=80, # end-of-life criterion (RPT-based SoH)
initialTemperature_degC=25.0,
ambientTemperature_degC=25.0,
heatTransferCoefficient=35.0,
)
result.stop_condition
Running ageing campaign on 'Molicel P45B' (up to 200 cycles)... Campaign finished: max_cycles at cycle 200 (wall clock 00:01:25)
{'triggered': 'max_cycles', 'at_cycle': 200, 'at_time_s': 2337868.67742816}
6. Read the result¶
The result object prints its own contents, grouped by what each part holds
result
AgeingSimulationResults campaign stopped: max_cycles at cycle 200, final SoH 90.81 % measurements .rpt (5 RPTs) | .dcir (5 pulses) | .dva (0 curves) | .performance (0 tests) trajectories .cycles (per cycle) | .timeseries (off — pass return_timeseries=True) bookkeeping .stop_condition | .rpt_protocol | .ageing_protocol plots .plot_soh() .plot_capacity() .plot_dcir() .plot_degradation_modes() .plot_dva() .plot_ica() .plot_anode_potential() .plot_charge_time() .plot_timeseries() .performance.plot_dynamic_response()
result.rpt has one row per check-up: capacity, SoH against the baseline,
DCIR, and the degradation modes behind them.
result.rpt
| cycle_number | capacity_Ah | soh_pct | soc_ref_capacity_Ah | dcir_ohm | lli [%] | lli_sei [%] | lli_plating [%] | lam_ne [%] | lam_pe [%] | sei_thickness_m | li_plating [mol.m-3] | cracking | abs_ah_throughput_Ah | wh_in | wh_out | elapsed_time_s | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| rpt_number | |||||||||||||||||
| 0 | 0 | 4.437555 | 100.000000 | 4.437555 | 0.014612 | 0.133605 | 0.060553 | 0.073052 | 0.036307 | 0.0 | 2.584802e-09 | 18.224363 | 0.0 | 19.965181 | 33.718064 | 39.217767 | 2.775106e+04 |
| 1 | 50 | 4.320712 | 97.366944 | 4.320712 | 0.015664 | 2.593301 | 0.680164 | 1.913136 | 0.840972 | 0.0 | 1.494697e-08 | 477.272399 | 0.0 | 483.645303 | 915.864547 | 872.143817 | 6.181208e+05 |
| 2 | 100 | 4.218402 | 95.061390 | 4.218402 | 0.016188 | 4.779496 | 1.094984 | 3.684512 | 1.644144 | 0.0 | 2.149312e-08 | 919.179602 | 0.0 | 935.954579 | 1778.349866 | 1685.141344 | 1.199534e+06 |
| 3 | 150 | 4.121931 | 92.887422 | 4.121931 | 0.016583 | 6.842696 | 1.440147 | 5.402549 | 2.434405 | 0.0 | 2.653195e-08 | 1347.780306 | 0.0 | 1377.760278 | 2622.441901 | 2479.790865 | 1.772644e+06 |
| 4 | 200 | 4.029657 | 90.808031 | 4.029657 | 0.016931 | 8.807269 | 1.740628 | 7.066641 | 3.216560 | 0.0 | 3.075209e-08 | 1762.923243 | 0.0 | 1809.587883 | 3448.937529 | 3257.016359 | 2.337869e+06 |
plot_soh() plots the RPT measurements.
result.plot_soh()