DataFit has two coupled pieces:
- Objectives (
iws.objectives.*) — what experiments to compare model output against. - Cost (
iws.costs.*) — how the per-point disagreements are aggregated into a single number.
Available cost functions
| Schema | Formula | When to use |
|---|---|---|
iws.costs.SSE() | Default; works with every optimiser | |
iws.costs.MSE() | Scale-aware mean of squared residuals | |
iws.costs.RMSE() | Interpretable units; scalar-only (won’t work with residual-array optimisers) | |
iws.costs.MAE() | Robust to outliers | |
iws.costs.Wasserstein() | Match distributions (sorted samples) rather than point-wise time series. Set position_variable and weight_variable for weighted point-cloud mode |
iws.costs.GaussianLogLikelihood — it accepts per-variable noise standard deviations or can estimate them alongside the fitting parameters. It produces a Gaussian negative log-likelihood suitable for Bayesian and MAP estimation.
Wiring a cost into a fit
cost is omitted, the optimizer’s default cost function is used (typically a least-squares form).
Wasserstein weighted point-cloud mode
By defaultiws.costs.Wasserstein() compares the model and data samples for each objective variable with uniform weights (sorted point-wise comparison). Set both position_variable and weight_variable to switch to weighted point-cloud mode: one variable supplies the positions, the other supplies the (sign-stripped, renormalised) weights, and a single Wasserstein-1 distance is computed per objective.
Use this when you want to match a density by position rather than sample-by-sample values — for example, lining up dQ/dV peaks in voltage rather than penalising every dQ/dV residual.
position_variable and weight_variable must be set together — providing only one raises a validation error. Weights are taken as absolute values and renormalised internally, so sign conventions on dQ/dV don’t matter. Residual-array output is not available in this mode.Available objectives
| Schema | Use for |
|---|---|
iws.objectives.CurrentDriven(data_input=..., options={...}) | Time-series voltage vs. current loads (drive cycles, custom loads) |
iws.objectives.Pulse(data_input=..., options={...}) | Pulse experiments — GITT, HPPC, ICI — with optional feature-extraction variants |
iws.objectives.OCPHalfCell(electrode=..., data_input=...) | Half-cell OCP curves |
iws.objectives.MSMRHalfCell(...) | Fit MSMR parameters to half-cell data |
iws.objectives.MSMRFullCell(...) | Fit MSMR parameters to full-cell data. Supports Differential voltage [V/Ah] and Differential capacity [Ah/V] as objective variables |
iws.objectives.ElectrodeBalancing(...) | Stoichiometry windows from full-cell discharge |
iws.objectives.EIS(...) | Electrochemical impedance spectra |
iws.objectives.Resistance(...) | DC resistance extracted from pulse data |
iws.objectives.CalendarAgeing(...) / iws.objectives.CycleAgeing(...) | Ageing curves |
dict[str, objective] to DataFit.objectives.
Tuning the auto-built solver
Simulation-backed objectives (CurrentDriven, Pulse, CalendarAgeing, CycleAgeing, MSMRFullCell, …) build an IonworksSolver for you when no explicit solver is provided. Pass solver_kwargs inside simulation_kwargs to override individual pieces of that default without restating the rest:
- Nested
optionsare merged over the default IDAKLU options. For example,{"options": {"compile": True}}flips on model compilation but keeps every other tuned option. - Other top-level keys (
atol,rtol,on_extrapolation, …) override the corresponding default solver kwargs.
solver_kwargs is ignored (with a warning) when an explicit solver is supplied — configure those on the solver instance directly. It is also ignored when the model’s default solver isn’t IDAKLU-based.
Objective Functions (theory)
Residual vs. canonical form, MLE interpretation.
Data Fitting overview
Putting objectives, parameters, and optimisers together.