Battery state of charge
The Gauge Drifts Before the Battery Does
Estimating battery charge from voltage.
- Role
- Author
- Classification
- Engineering prototype
Problem
As a cell ages, a fresh calibration reads low.
How it was solved
I split data by cycle, so the test really is the future.
- Signals
- Five models
- Split by cycle
Result
0.0001 V
lost per cycle in the generator
Built with
- scikit-learn
- XGBoost
- LightGBM
- PyTorch
Research prototype / Time series
A lithium-ion cell has no fuel gauge, so charge has to be inferred from voltage, current and temperature. What happens to that inference when the cell ages and the voltage curve it was calibrated against quietly moves?
Ageing, in the generator's own formula2.4 points of charge
Held back by cycle number, and explained by ageing
This diagram in words
- Cells or demo set gate — Authorised cell data if present, a deterministic synthetic set otherwise.
- Cycle-aware features process — Rolling, voltage, thermal and energy terms.pandas
- Last 50 cycles held back gate — By cycle number, never shuffled.
- Charge regression process — SVR, random forest and XGBoost through one metric function.scikit-learn · XGBoost
- Analyses beside it process — Clustering, genetic-fuzzy rules and state-of-health fits.DEAP · scikit-fuzzy
- Scores and curves artifact — Held-out error beside the degradation the same cells show.
Read the full background
Problem
Why this work exists
Charge must be inferred from voltage, and the voltage a cell shows at a given charge falls as it ages. A gauge calibrated once against a fresh cell therefore grows wrong in a specific direction, and the usual way of measuring such a model - shuffle the rows, hold some out - hides the error completely, because two samples a second apart inside one discharge are very nearly the same row.
Contribution
What I can claim
Built the codebase that makes the comparison possible rather than a single model: NASA-compatible loading with a deterministic synthetic generator for development, four families of engineered feature, five regression models behind one metric function, and a split by cycle number so that the held-out cycles are genuinely unseen.
Method
Why a charge gauge goes wrong in one direction
A lithium-ion cell has no fuel gauge.
The curve is the generator’s own voltage formula, evaluated rather than drawn. It is a synthetic cell: the repository ships no battery data, no trained model and no score.
Infer
Nothing measures charge directly.
Calibrate
A fresh cell gives the curve the lookup is built from: this voltage means this much charge.
Age
Cycling lowers the whole curve.
Misread
Read the aged cell's voltage against the fresh curve and the answer comes back low.
Hold back
So the split has to be by cycle, with the newest and most degraded cycles held out.
What this does not show
The curve is a synthetic cell, and the sinking term is one the repository's own demonstration generator applies. It is a statement about the code, not a measurement of a battery: there is no dataset here, no trained model and no score, and none of it may be used to operate a battery-management system.
How it was built
System
Workflow and decisions
Six modules behind one pipeline: loading and the seeded generator, feature engineering, the regression models and their metric function, clustering, the genetic-fuzzy estimator, and the degradation analyses. Capacity in the generated set falls from 2.0 to 1.6 Ah across its 200 cycles, which is the drift the models are asked to survive.
- 1Load authorized source data, or generate the deterministic demonstration set
- 2Engineer cycle-aware features from voltage, current, temperature and time
- 3Hold back the last 50 cycles by cycle number, without shuffling
- 4Fit and score the regression models through one metric function
- 5Run the clustering, fuzzy and state-of-health analyses beside them
- Python
- scikit-learn
- XGBoost
- LightGBM
- PyTorch
- DEAP
- scikit-fuzzy
- pandas
Evidence
What is actually versioned
The demonstration generator lowers open-circuit voltage by 0.0001 V per cycle, a term that does not depend on charge, so by cycle 200 the whole curve sits 0.020 V lower. Read that voltage against the fresh-cell curve and it comes back as 47.6% when the charge is really 50%. That is a property of the synthetic cell the repository ships, not a measurement of a real one.
The repository ships no battery dataset, no trained weights and no tracked evaluation outputs, and it states that synthetic-data results demonstrate code execution only rather than estimation performance. It also lists what a benchmark would have to record first: cell identifiers, source-data version, preprocessing parameters, temporal split, random seed, dependency versions, evaluation artefacts. This page claims nothing past that.
SVR (RBF), Random Forest, XGBoost, LightGBM, LSTM, scored through one function reporting RMSE, MAE, R squared, over 4 families of engineered feature. Beside them sit K-Means and Gaussian-mixture clustering for operating regimes; a genetic-optimised fuzzy estimator; capacity fade, state of health, resistance and remaining useful life.
How the work is checked
Quality controls
How the work is checked
- The split is by cycle number and nothing is shuffled: training is cycles 1 to 150 and the test set is 151 to 200, which are the most degraded cycles in the set rather than a random sample of all of them
- Every model is scored by the same function, so the comparison between them is not a comparison of two different definitions of error
- The demonstration generator is seeded, so a pipeline change shows up as a changed number rather than as noise
- The README states what the repository does not have, and names the seven things a benchmark would need, instead of presenting a score table it cannot support
Limitations
Where the evidence stops
- No dataset, no trained weights and no tracked evaluation outputs. There is no score here because there is nothing versioned to substantiate one
- The synthetic cell is one generated cell of 100,000 rows with linear capacity fade, which is far simpler than a real one: it says what the code does, not how well estimation works
- The split holds back the last cycles of a single cell. A real benchmark would hold back whole cells, and verify that no cell or cycle leaks between the two sides
- A research prototype. It must not be used to operate a battery-management system or to make any safety decision, and the repository says so above everything else
What this changed in my practice
What this changed in my practice
The interesting failure was not a model's. It was that the quantity being predicted and the signal it is predicted from drift apart as the cell ages, so the test split is the part of the experiment that decides whether the number means anything. Shuffling the rows would have produced a much better score and a worthless one.