MZQMohd Zamin Quadri
All work

Battery state of charge

The Gauge Drifts Before the Battery Does

Estimating battery charge from voltage.

Role
Author
Classification
Engineering prototype
Inspect repository
The generator's own voltage formula at two cycle counts, evaluated rather than drawn, with the charge a fresh-cell lookup gives away marked between the two curves.The synthetic generator's open-circuit-voltage curve, evaluated rather than sketched, drawn across the sampled range of state of charge. A second curve is added for the last cycle, and at this scale the two are nearly indistinguishable - ageing lowers the whole curve by the same amount at every state of charge - so the distance between them at half charge is marked and labelled as 0.020 volts. Because the honest plot cannot show what that costs, a magnified window opens beneath it over 0.44 to 0.56 of charge, labelled with its magnification. In that window a gauge calibrated on the fresh curve is traced: at a true 50 per cent the aged cell sits lower, and reading that voltage against the fresh lookup returns 47.6 per cent instead, giving away 2.4 points of charge. The temporal split follows - the last fifty cycles held out, by cycle number, nothing shuffled, because two samples a second apart inside one discharge are nearly the same row. The sequence closes on the seven things the repository says a benchmark would have to record before any score meant anything, none of which it publishes.
  1. Problem

    As a cell ages, a fresh calibration reads low.

  2. How it was solved

    I split data by cycle, so the test really is the future.

    1. Signals
    2. Five models
    3. Split by cycle
  3. Result

    0.0001 V

    lost per cycle in the generator

  4. Built with

    • scikit-learn
    • XGBoost
    • LightGBM
    • PyTorch
    See the evidence on GitHub
The synthetic generator's open-circuit-voltage curve, evaluated rather than sketched, drawn across the sampled range of state of charge. A second curve is added for the last cycle, and at this scale the two are nearly indistinguishable - ageing lowers the whole curve by the same amount at every state of charge - so the distance between them at half charge is marked and labelled as 0.020 volts. Because the honest plot cannot show what that costs, a magnified window opens beneath it over 0.44 to 0.56 of charge, labelled with its magnification. In that window a gauge calibrated on the fresh curve is traced: at a true 50 per cent the aged cell sits lower, and reading that voltage against the fresh lookup returns 47.6 per cent instead, giving away 2.4 points of charge. The temporal split follows - the last fifty cycles held out, by cycle number, nothing shuffled, because two samples a second apart inside one discharge are nearly the same row. The sequence closes on the seven things the repository says a benchmark would have to record before any score meant anything, none of which it publishes.

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

earlier cyclesthe curve moves as it agesCells or demo setAuthorised cell data ifpresent, a deterministic…Cycle-aware featuresRolling, voltage, thermal andenergy terms.Last 50 cycles held backBy cycle number, nevershuffled.Charge regressionSVR, random forest and XGBoostthrough one metric function.Analyses beside itClustering, genetic-fuzzyrules and state-of-health…Scores and curvesHeld-out error beside thedegradation the same cells…
The split is drawn as a gate because shuffling it would leak the ageing trajectory the project exists to study. The return edge is the finding: the degradation fits explain why the features drift out from under the estimator.

This diagram in words

  1. Cells or demo set gate — Authorised cell data if present, a deterministic synthetic set otherwise.
  2. Cycle-aware features process — Rolling, voltage, thermal and energy terms.
    pandas
  3. Last 50 cycles held back gate — By cycle number, never shuffled.
  4. Charge regression process — SVR, random forest and XGBoost through one metric function.
    scikit-learn · XGBoost
  5. Analyses beside it process — Clustering, genetic-fuzzy rules and state-of-health fits.
    DEAP · scikit-fuzzy
  6. Scores and curves artifact — Held-out error beside the degradation the same cells show.

01 / 7Voltage over state of chargeThe generator's own open-circuit-voltage curve, evaluated rather than drawn.

Curve
3.4 + 0.5s + 0.2s² + 0.05 ln s − 0.02 ln(1−s) − 0.0001·cycle
Source
The generator in src/data_loader.py, noise omitted
Models it feeds
SVR (RBF), Random Forest, XGBoost, LightGBM, LSTM
The synthetic generator's open-circuit-voltage curve, evaluated rather than sketched, drawn across the sampled range of state of charge. A second curve is added for the last cycle, and at this scale the two are nearly indistinguishable - ageing lowers the whole curve by the same amount at every state of charge - so the distance between them at half charge is marked and labelled as 0.020 volts. Because the honest plot cannot show what that costs, a magnified window opens beneath it over 0.44 to 0.56 of charge, labelled with its magnification. In that window a gauge calibrated on the fresh curve is traced: at a true 50 per cent the aged cell sits lower, and reading that voltage against the fresh lookup returns 47.6 per cent instead, giving away 2.4 points of charge. The temporal split follows - the last fifty cycles held out, by cycle number, nothing shuffled, because two samples a second apart inside one discharge are nearly the same row. The sequence closes on the seven things the repository says a benchmark would have to record before any score meant anything, none of which it publishes.
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.

  1. Infer

    Nothing measures charge directly.

  2. Calibrate

    A fresh cell gives the curve the lookup is built from: this voltage means this much charge.

  3. Age

    Cycling lowers the whole curve.

  4. Misread

    Read the aged cell's voltage against the fresh curve and the answer comes back low.

  5. Hold back

    So the split has to be by cycle, with the newest and most degraded cycles held out.

  6. 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.

  1. 1Load authorized source data, or generate the deterministic demonstration set
  2. 2Engineer cycle-aware features from voltage, current, temperature and time
  3. 3Hold back the last 50 cycles by cycle number, without shuffling
  4. 4Fit and score the regression models through one metric function
  5. 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

Ageing, in the generator's own formula2.4 points of charge

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.

What is publishedno battery result

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.

What is compared5 regression models

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.

Next evidence milestone: Earn a number. The repository already lists what that takes - cell identifiers, source-data version, preprocessing parameters, temporal split, random seed, dependency versions, evaluation artefacts - and the honest next step is to obtain authorized cell data, split it by cell rather than by cycle, and record all seven so that the first score published is one that can be checked.