4. Full-cell validation

In this example, we load the previous results from OCP fitting, electrode balancing, and half-cell GITT fitting to create a parameter set for the full cell. We then validate this parameter set against the real parameters.

from pathlib import Path

import ionworkspipeline as iwp
import pybamm

from plots import constant_current, drive_cycle
from true_parameters.parameters import full_cell

We begin by making a list of “known parameters”. These are values that are already known from other sources such as spec sheets, direct measurement, and/or other experiments. For this example, we load the known parameters from the true parameters used to generate the synthetic data.

# Known values
KNOWN_PARAMETERS = [
    # Negative electrode
    "Negative electrode thickness [m]",
    "Negative electrode conductivity [S.m-1]",
    "Negative electrode porosity",
    "Negative electrode active material volume fraction",
    "Negative particle radius [m]",
    "Negative electrode OCP entropic change [V.K-1]",
    # Separator
    "Separator thickness [m]",
    "Separator porosity",
    # Positive electrode
    "Positive electrode thickness [m]",
    "Positive electrode conductivity [S.m-1]",
    "Positive electrode porosity",
    "Positive electrode active material volume fraction",
    "Positive particle radius [m]",
    "Positive electrode OCP entropic change [V.K-1]",
    # Cell
    "Electrode height [m]",
    "Electrode width [m]",
    # these are user-specified at the cell level and may differ from the actual
    # voltage and capacity as measured in the experiment
    "Lower voltage cut-off [V]",
    "Upper voltage cut-off [V]",
    "Open-circuit voltage at 0% SOC [V]",
    "Open-circuit voltage at 100% SOC [V]",
    "Nominal cell capacity [A.h]",
    "Current function [A]",
]

Next we load in the known parameters

true_parameter_values = full_cell()
known_parameter_values = {k: true_parameter_values[k] for k in KNOWN_PARAMETERS}
known_values = iwp.direct_entries.DirectEntry(
    known_parameter_values, "Measured or assumed values"
)

and load in the previously fitted open circuit, electrode balancing and GITT parameters.

ocp_n = iwp.calculations.ocp_data_interpolant_from_csv(
    "negative",
    "fitted_parameters/negative_electrode_ocp.csv",
)
ocp_p = iwp.calculations.ocp_data_interpolant_from_csv(
    "positive",
    "fitted_parameters/positive_electrode_ocp.csv",
)
electrode_balance_params = iwp.direct_entries.from_json(
    Path("fitted_parameters") / "full_cell_balance.json",
)
def j0_n(c_e, c_s_surf, c_s_max, T):
    j0_ref = pybamm.Parameter(
        "Negative electrode reference exchange-current density [A.m-2]"
    )
    alpha = 0.5
    c_e_ref = 1000

    return (
        j0_ref
        * (c_e / c_e_ref) ** (1 - alpha)
        * (c_s_surf / c_s_max) ** alpha
        * (1 - c_s_surf / c_s_max) ** (1 - alpha)
    )


def j0_p(c_e, c_s_surf, c_s_max, T):
    j0_ref = pybamm.Parameter(
        "Positive electrode reference exchange-current density [A.m-2]"
    )
    alpha = 0.5
    c_e_ref = 1000

    return (
        j0_ref
        * (c_e / c_e_ref) ** (1 - alpha)
        * (c_s_surf / c_s_max) ** alpha
        * (1 - c_s_surf / c_s_max) ** (1 - alpha)
    )


exchange_current_params = iwp.direct_entries.DirectEntry(
    {
        "Negative electrode exchange-current density [A.m-2]": j0_n,
        "Positive electrode exchange-current density [A.m-2]": j0_p,
    },
    source="Known functional forms for exchange-current densities",
)
negative_gitt_params = iwp.direct_entries.from_json(
    Path("fitted_parameters") / "negative_electrode_gitt.json",
)
positive_gitt_params = iwp.direct_entries.from_json(
    Path("fitted_parameters") / "positive_electrode_gitt.json",
)

Next, we construct a pipeline that uses the known parameters and the fitted parameters to create a full cell parameter set.

pipeline = iwp.Pipeline(
    {
        "known values": known_values,
        "negative OCP": ocp_n,
        "positive OCP": ocp_p,
        "Electrode balance parameters": electrode_balance_params,
        "Exchange-current densities": exchange_current_params,
        "Negative GITT parameters": negative_gitt_params,
        "Positive GITT parameters": positive_gitt_params,
        "Defaults": iwp.direct_entries.standard_defaults(),
        "Bruggeman": iwp.direct_entries.bruggeman(),
        "Temperatures": iwp.direct_entries.temperatures(298.15),
        "Electrolyte": iwp.direct_entries.nyman_electrolyte(1000),
        "Maximum concentration negative": iwp.calculations.ElectrodeCapacity(
            "negative",
            unknown="maximum concentration",
            method="capacity",
        ),
        "Maximum concentration positive": iwp.calculations.ElectrodeCapacity(
            "positive",
            unknown="maximum concentration",
            method="capacity",
        ),
        "Initial concentration negative": iwp.calculations.InitialSOCHalfCell(
            "negative", "maximum"
        ),
        "Initial concentration positive": iwp.calculations.InitialSOCHalfCell(
            "positive", "maximum"
        ),
    }
)

We run the pipeline

fitted_parameter_values = pipeline.run()

and export a python script for the full cell parameter values

# Export a .py file with the PyBaMM parameters
# Note: if we specify data_path=None, the variable DATA_PATH will be set to the
# directory containing the exported script
iwp.util.export_python_script(
    fitted_parameter_values,
    "fitted_parameters/full_cell.py",
    data_path=None,
)

Finally, we validate this parameter set against the known parameters.

model = pybamm.lithium_ion.SPMe()
true_parameter_values = pybamm.ParameterValues(true_parameter_values)


def plot_constant_current(model, Crate=1):
    constant_current(
        model,
        true_parameter_values,
        fitted_parameter_values,
        Crate=Crate,
        initial_soc=1,
    )


def plot_drive_cycle(model, peak_current=None):
    peak_current = peak_current or true_parameter_values["Nominal cell capacity [A.h]"]
    drive_cycle(
        model,
        true_parameter_values,
        fitted_parameter_values,
        peak_current=peak_current,
        initial_soc=1,
    )
plot_constant_current(model, Crate=1)
../../../_images/1a02fe42aeee1268d17bd16ccf02f66241031c696dd9e65dae2cf39cd5a56dca.png
plot_drive_cycle(model)
../../../_images/9896b46ff0bc4a6d613c3f9c2380d2c289b3a2307027b47054372c4699263e4e.png