Guide: The BatteryProc Formula Engine¶
The mathematical heart of BatteryProc—a tool that battery scientists rely on to process, validate, and analyze cycling data from electrochemical experiments.
This guide provides a comprehensive, theory-rich overview of BatteryProc's formula engine. It is designed for researchers who understand battery physics and electrochemistry but may not be familiar with the codebase. Our goal is to create a bridge between rigorous electrochemical science and practical software implementation, enabling you to trust the calculations, understand the theory, and leverage the full power of the tool.
Here, you will find the mathematical foundations, core assumptions, and practical usage of the engine, ensuring you can: - Validate instrument measurements against theoretical expectations - Understand exactly how capacity, energy, and efficiency are computed - Debug data quality issues with confidence - Select the right CLI flags for your experimental workflows
1. Introduction¶
1.1. Purpose and Scope¶
The formulas engine is the centralized, instrument-agnostic core of BatteryProc's quantitative analysis. It operates on a standardized, or "canonical," set of inputs, ensuring that a metric like "Coulombic Efficiency" is calculated identically whether the source data comes from a Neware cycler, a Bio-Logic potentiostat, or a simple CSV file.
1.2. Design Principles¶
The engine is built on four key principles: - Vectorized Operations: All calculations use NumPy and Pandas vectorized operations for high performance on large datasets. - Internal SI Units: All internal computations are performed using base SI units (Amperes, Volts, Seconds) to maintain physical consistency. Conventional battery units (e.g., mAh, Wh) are used for inputs and outputs to align with user expectations. - Tolerance-Based Validation: The engine can compare metrics computed from the data against values reported in the source file, flagging discrepancies based on configurable absolute and relative tolerances. This is a powerful tool for validating instrument accuracy. - Graceful Degradation: The engine handles missing optional inputs (like active mass) with clear warnings and fallbacks, ensuring that calculations proceed as far as possible.
2. Conventions and Canonical Columns¶
To achieve instrument-agnostic processing, the formula engine operates on a standardized set of column names and conventions. The normalize_inputs() function is the primary entry point for preparing your data.
2.1. Required Columns¶
time_s: Timestamp in seconds (s). Must be monotonically increasing within each data segment.current_a: Current in Amperes (A).voltage_v: Voltage in Volts (V).
2.2. Optional Columns¶
capacity_mAh: Cumulative capacity from the file (mAh or Ah), used for validation.energy_mWh: Cumulative energy from the file (mWh or Wh), used for validation.mass_g: Active material mass in grams (g), for calculating specific capacity.weight_kg: Cell weight in kilograms (kg), for calculating specific energy/power.cycle_index: Cycle number for per-cycle aggregation.step_type: A string label for the experimental step (e.g., "charge", "discharge", "rest").
2.3. Sign Conventions¶
The engine enforces a strict sign convention for current:
- Charge: current_a > 0 (positive current)
- Discharge: current_a < 0 (negative current)
Instrument-specific conventions (e.g., discharge as positive) are automatically normalized during the parsing stage.
2.4. Unit Conversions¶
| Conversion | Formula |
|---|---|
| Ampere-hours to Coulombs | 1 Ah = 3600 C |
| Milliampere-hours to Coulombs | 1 mAh = 3.6 C |
| Watt-hours to Joules | 1 Wh = 3600 J |
| Specific Capacity | mAh/g |
| Specific Energy | Wh/kg (when mass_g in grams: Wh/kg = 1000 × Wh / mass_g) |
3. Core Equations¶
Battery scientists think in equations. Here, we provide the complete mathematical basis for each metric, including its continuous (integral) form, its discrete (numerical) implementation, and its physical interpretation.
3.0. Profiles (Voltage vs. Specific Capacity, First Cycle)¶
The metrics.voltage_profiles and metrics.first_cycle_profile helpers are now backed by the formulas engine.
- Respect cycle-detection configuration: both helpers reuse the active cycle detection policy (AUTO, step_type, current_sign) and propagate cycle metadata to outputs.
- Preserve legacy schema: outputs retain
Spec. Cap.(mAh/g)andVoltage(V)(andTime_min,Current(mA)for the first cycle) so downstream plots remain compatible. - Graceful fallbacks: when normalization fails or specific capacity cannot be computed from mass metadata, the helpers fall back to the legacy implementations and emit the same warnings.
- CLI alignment: the helpers reflect
configure_cycle_detection/CLI settings and update the cached detection summary used by the CLI’s status messages.
3.1. Electric Charge (Capacity)¶
Capacity is the time integral of current—literally the area under the I vs. t curve. It represents the total charge transferred to or from the battery.
Continuous Form¶
For specific capacity, the value is normalized by the active material mass:
Discrete Implementation¶
Numerical integration is performed using the trapezoidal rule, which approximates the area under the curve by summing the areas of trapezoids formed by adjacent data points. This method is well-suited for typical battery data sampling rates.
# Cumulative capacity using trapezoidal integration
# from scipy.integrate import cumulative_trapezoid
Q_Ah = cumulative_trapezoid(current_a, time_s, initial=0) / 3600
Q_mAh = Q_Ah * 1000
# Specific capacity (if mass available)
Q_spec = Q_mAh / mass_g
Per-Cycle Capacity¶
For per-cycle analysis, the data is first grouped by cycle_index and then split into charge (current_a > 0) and discharge (current_a < 0) segments before integration.
Assumptions¶
- Time is in seconds and is monotonically increasing.
- The sign of the current correctly indicates the charge/discharge direction.
- The trapezoidal rule provides sufficient accuracy for the given data sampling rate (typically 0.1–10 Hz).
3.2. Energy¶
Energy is the time integral of power. It represents the total work done by or on the battery.
Continuous Form¶
Specific energy is normalized by the cell's weight (or mass):
Discrete Implementation¶
First, instantaneous power is calculated. Then, energy is computed by numerically integrating the power over time.
# Instantaneous power
power_w = voltage_v * current_a
# Cumulative energy using trapezoidal integration
E_Wh = cumulative_trapezoid(power_w, time_s, initial=0) / 3600
# Specific energy
E_spec = E_Wh / weight_kg
Energy Efficiency¶
Per-cycle energy efficiency is a key performance indicator, calculated as:
Assumptions¶
- Voltage and current measurements are synchronous.
- The V × I product accurately captures the total electrical work, excluding thermal losses external to the measurement points.
3.3. Power¶
Power is the instantaneous rate of energy transfer.
Continuous Form¶
Specific power is normalized by the cell's weight (or mass):
Discrete Implementation¶
# Instantaneous power
P_w = voltage_v * current_a
# Average power over an interval
P_avg_w = (E_Wh[t2] - E_Wh[t1]) * 3600 / (time_s[t2] - time_s[t1])
# Specific power
P_spec_W_per_kg = P_w / weight_kg
3.4. Coulombic Efficiency (CE)¶
Coulombic Efficiency (also called Faradaic Efficiency) measures the ratio of charge extracted during discharge to the charge inserted during charge. An ideal battery would have a CE of 100%, but in reality, losses from side reactions and self-discharge reduce this value.
Definition¶
Discrete Implementation¶
The calculation is performed on a per-cycle basis, using the total charge and discharge capacities for each cycle.
# Group data by cycle_index
# For each cycle:
Q_charge_mAh = integrate_capacity(charge_segment)
Q_discharge_mAh = abs(integrate_capacity(discharge_segment))
eta_C = 100 * Q_discharge_mAh / Q_charge_mAh
Assumptions¶
- Cycle boundaries are correctly identified.
- Both charge and discharge segments use identical integration methods.
- Self-discharge during rest periods is implicitly included in the measured discharge capacity.
3.5. Differential Capacity (dQ/dV)¶
Differential Capacity Analysis (DCA or dQ/dV) is a powerful technique for diagnosing battery degradation mechanisms. By plotting dQ/dV against voltage, characteristic peaks appear that correspond to electrochemical phase transitions.
Galvanostatic Relation¶
Under constant current (galvanostatic) conditions, the relationship is derived from the chain rule:
Warning: This formula is only valid during constant-current (CC) segments. It is mathematically undefined during constant-voltage (CV) segments where \(dV/dt \to 0\).
Discrete Implementation¶
Calculating a clean dQ/dV trace from real-world data is challenging because the voltage derivative (dV/dt) amplifies measurement noise. BatteryProc uses a multi-step process:
# 1. Smooth voltage to reduce noise
# from scipy.signal import savgol_filter
V_smooth = savgol_filter(voltage_v, window_length=21, polyorder=3)
# 2. Compute voltage derivative (central difference)
dV_dt = np.gradient(V_smooth, time_s)
# 3. Mask regions where dV/dt is near zero to avoid division by zero
epsilon = 1e-5 # V/s threshold
is_valid_dV_dt = np.abs(dV_dt) > epsilon
# 4. Restrict to constant-current segments
is_CC = np.abs(current_a - np.median(current_segment)) < delta_I
valid_points = is_valid_dV_dt & is_CC
# 5. Calculate dQ/dV only for valid points
dQ_dV = np.full_like(voltage_v, np.nan)
dQ_dV[valid_points] = current_a[valid_points] / dV_dt[valid_points]
Assumptions¶
- The analysis is restricted to galvanostatic (constant current) portions of the cycling protocol.
- The voltage data is smoothed before differentiation to suppress noise amplification.
- Regions where the voltage is flat (plateaus, CV holds) are masked to prevent numerical instability.
3.6. Internal Resistance (DCIR)¶
Internal resistance is a measure of the opposition to current flow within the battery. It is a key indicator of battery health and power capability. It is typically estimated by applying a current step and measuring the resulting voltage response.
Step Response Method¶
Where \(\Delta I\) is the magnitude of the current step and \(\Delta V\) is the voltage response.
Discrete Implementation¶
The engine detects current steps and calculates two types of resistance:
- Ohmic Resistance (R_ohmic): The immediate voltage response (typically within 10-50 ms), representing the purely resistive components.
- DC Internal Resistance (R_DCIR): The voltage response at longer timescales (1-5 s), which includes both ohmic resistance and polarization effects.
# 1. Detect a current step at index i
delta_I = current_a[i+1] - current_a[i]
if abs(delta_I) > threshold:
# 2. Measure V_before (average over a pre-step window)
V_before = median(voltage_v[i-n1:i])
# 3. Measure V_after at short latency for R_ohmic
V_after_ohmic = median(voltage_v[i+1:i+1+n2])
R_ohmic = (V_after_ohmic - V_before) / delta_I
# 4. Measure V_after at long latency for R_DCIR
V_after_long = median(voltage_v[i+n_long:i+n_long+n2])
R_DCIR = (V_after_long - V_before) / delta_I
Assumptions¶
- The current step is sharp relative to the data sampling rate.
- The voltage response is measured at well-defined latencies.
- The resistance value may vary significantly with State of Charge (SOC), temperature, and battery age.
4. Numerical Implementation Details¶
- Integration: The trapezoidal rule is implemented via
scipy.integrate.cumulative_trapezoidor a NumPy equivalent. - Data Types: All calculations use
float64to minimize rounding errors. - NaN/Inf Handling:
normalize_inputsdrops rows where required fields are NaN or Inf. - Segmentation: Data is split by
cycle_indexand current sign for per-cycle metrics. Non-monotonic time steps also trigger segmentation.
5. Stability and Edge Cases¶
- dQ/dV Stability: As noted, dV/dt can approach zero. The engine masks these regions to prevent numerical blow-ups and restricts the calculation to constant-current segments only.
- Internal Resistance Edge Cases: If the sampling rate is too low to resolve the ohmic drop, the confidence score of the estimate will be low.
- Missing Inputs: If
mass_gorweight_kgare not provided, specific metrics cannot be computed and will be returned as NaN. Ifcycle_indexis missing, the engine will attempt to detect cycles automatically. See our Guide to Cycle Detection for more details. - Time Non-Monotonicity: Caused by file concatenation or instrument resets. The engine detects backward time jumps and splits the data into separate segments for processing.
See Also¶
- Guide: Cycle Detection in BatteryProc: Understand how
batteryprocidentifies cycles, a crucial prerequisite for per-cycle metrics. - Guide: Debugging Flags and Fine-Tuning: Learn how to use CLI flags to control and debug the formula engine's behavior.
6. Tolerance-Based Validation¶
A key feature of the formula engine is its ability to validate file-reported metrics against its own recomputed values. This helps detect instrument miscalibration or data corruption.
The comparison logic uses both absolute (atol) and relative (rtol) tolerances:
def compare_metrics(file_value, computed_value, atol, rtol):
diff = abs(file_value - computed_value)
# The denominator is clamped to avoid division by zero
rel_diff = diff / max(1e-9, abs(computed_value))
passed = (diff <= atol) or (rel_diff <= rtol)
return passed
You can control these tolerances via the CLI. See our Guide to Debugging Flags for practical examples.
7. API Reference¶
For a detailed, auto-generated API reference of all functions in the batteryproc.formulas module, including their signatures and parameters, please see the API Reference. This guide focuses on the theory and concepts.
8. Practical Examples¶
The best way to understand the formula engine is to see it in action. Below are brief snippets for common tasks.
Note: For fully runnable scripts with sample data and plotting, please see the Examples Directory.
Example 1: Computing Capacity and CE Per Cycle¶
This is the most common workflow. The per_cycle_capacity_summary function handles the integration and grouping automatically.
from batteryproc.formulas import normalize_inputs, per_cycle_capacity_summary
inputs = normalize_inputs(df)
# mass_g is optional and will add specific capacity columns to the output.
summary = per_cycle_capacity_summary(inputs, mass_g=0.523)
print(summary)
# cycle_index charge_mAh discharge_mAh coulombic_efficiency_percent ...
# 0 1 0.833 0.833 100.0
# 1 2 0.833 0.833 100.0
# 2 3 0.833 0.833 100.0
example_basic_capacity.py
Example 2: Energy and Efficiency Per Cycle¶
Similarly, the energy_efficiency_per_cycle function calculates energy-based metrics.
from batteryproc.formulas import normalize_inputs, energy_efficiency_per_cycle
inputs = normalize_inputs(df)
# weight_kg is optional and will add specific energy columns.
energy_summary = energy_efficiency_per_cycle(inputs, weight_kg=0.125)
print(energy_summary)
# cycle_index charge_energy_mWh discharge_energy_mWh energy_efficiency_percent ...
# 0 1 2.917 2.917 100.0
# 1 2 2.917 2.917 100.0
# 2 3 2.917 2.917 100.0
example_energy_efficiency.py
Example 3: dQ/dV Trace Generation¶
The dqdv_trace function computes the differential capacity, automatically handling the necessary filtering and smoothing.
from batteryproc.formulas import normalize_inputs, dqdv_trace
inputs = normalize_inputs(df_cccv)
# The function automatically finds constant-current segments.
dqdv_results = dqdv_trace(inputs, window_length=5, polyorder=2)
# Plot dqdv_results['dqdv_mAh_per_V'] vs. dqdv_results['voltage_v']
example_dqdv.py