Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions assets/lfp_18650_cell_BPX.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
{
"Header": {
"BPX": "0.1.0",
"Title": "Parameterisation example of an LFP|graphite 2 Ah cylindrical 18650 cell.",
"Description": "LFP|graphite 2 Ah cylindrical 18650 cell. Parameterisation by About:Energy Limited (aboutenergy.io), December 2022, based on cell cycling data, and electrode data gathered after cell teardown. Electrolyte properties from Nyman et al. 2008 (doi:10.1016/j.electacta.2008.04.023). Negative electrode entropic coefficient data are from O'Regan et al. 2022 (doi:10.1016/j.electacta.2022.140700). Positive electrode entropic coefficient data are from Gerver and Meyers 2011 (doi:10.1149/1.3591799). Other thermal properties are estimated.",
"Model": "DFN"
},
"Parameterisation": {
"Cell": {
"Ambient temperature [K]": 298.15,
"Initial temperature [K]": 298.15,
"Reference temperature [K]": 298.15,
"Lower voltage cut-off [V]": 2.0,
"Upper voltage cut-off [V]": 3.65,
"Nominal cell capacity [A.h]": 2,
"Specific heat capacity [J.K-1.kg-1]": 999,
"Thermal conductivity [W.m-1.K-1]": 1.89,
"Density [kg.m-3]": 1940,
"Electrode area [m2]": 0.08959998,
"Number of electrode pairs connected in parallel to make a cell": 1,
"External surface area [m2]": 0.00431,
"Volume [m3]": 1.7e-05
},
"Electrolyte": {
"Initial concentration [mol.m-3]": 1000,
"Cation transference number": 0.259,
"Conductivity [S.m-1]": "0.1297 * (x / 1000) ** 3 - 2.51 * (x / 1000) ** 1.5 + 3.329 * (x / 1000)",
"Diffusivity [m2.s-1]": "8.794e-11 * (x / 1000) ** 2 - 3.972e-10 * (x / 1000) + 4.862e-10",
"Conductivity activation energy [J.mol-1]": 17100,
"Diffusivity activation energy [J.mol-1]": 17100
},
"Negative electrode": {
"Particle radius [m]": 4.8e-06,
"Thickness [m]": 4.44e-05,
"Diffusivity [m2.s-1]": 9.6e-15,
"OCP [V]": "5.29210878e+01 * exp(-1.72699386e+02 * x) - 1.17963399e+03 + 1.20956356e+03 * tanh(6.72033948e+01 * (x + 2.44746396e-02)) + 4.52430314e-02 * tanh(-1.47542326e+01 * (x - 1.62746053e-01)) + 2.01855800e+01 * tanh(-2.46666302e+01 * (x - 1.12986136e+00)) + 2.01708039e-02 * tanh(-1.19900231e+01 * (x - 5.49773440e-01)) + 4.99616805e+01 * tanh(-6.11370883e+01 * (x + 4.69382558e-03))",
"Entropic change coefficient [V.K-1]": "(-0.1112 * x + 0.02914 + 0.3561 * exp(-((x - 0.08309) ** 2) / 0.004616)) / 1000",
"Conductivity [S.m-1]": 7.46,
"Surface area per unit volume [m-1]": 473004,
"Porosity": 0.20666,
"Transport efficiency": 0.09395,
"Reaction rate constant [mol.m-2.s-1]": 6.872e-06,
"Minimum stoichiometry": 0.0016261,
"Maximum stoichiometry": 0.82258,
"Maximum concentration [mol.m-3]": 31400,
"Diffusivity activation energy [J.mol-1]": 30000,
"Reaction rate constant activation energy [J.mol-1]": 55000
},
"Positive electrode": {
"Particle radius [m]": 5e-07,
"Thickness [m]": 6.43e-05,
"Diffusivity [m2.s-1]": 6.873e-17,
"OCP [V]": "3.41285712e+00 - 1.49721852e-02 * x + 3.54866018e+14 * exp(-3.95729493e+02 * x) - 1.45998465e+00 * exp(-1.10108622e+02 * (1 - x))",
"Entropic change coefficient [V.K-1]": {
"x": [0, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.65, 0.7, 0.75, 0.8, 0.85, 0.9, 0.95, 1],
"y": [0.0001, 4.7145e-05, 3.7666e-05, 2.0299e-05, 5.9833e-06, -4.6859e-06, -1.3966e-05, -2.3528e-05, -3.3593e-05, -4.3433e-05, -5.2311e-05, -6.0211e-05, -6.8006e-05, -7.6939e-05, -8.7641e-05, -9.913e-05, -0.00010855, -0.00011266, -0.00011238, -0.00010921, -0.00022539]
},
"Conductivity [S.m-1]": 0.80,
"Surface area per unit volume [m-1]": 4418460,
"Porosity": 0.20359,
"Transport efficiency": 0.09186,
"Reaction rate constant [mol.m-2.s-1]": 9.736e-07,
"Minimum stoichiometry": 0.0875,
"Maximum stoichiometry": 0.95038,
"Maximum concentration [mol.m-3]": 21200,
"Diffusivity activation energy [J.mol-1]": 80000,
"Reaction rate constant activation energy [J.mol-1]": 35000
},
"Separator": {
"Thickness [m]": 2e-05,
"Porosity": 0.47,
"Transport efficiency": 0.3222
}
}
}
86 changes: 86 additions & 0 deletions assets/nmc_pouch_cell_BPX.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
{
"Header": {
"BPX": "0.1.0",
"Title": "Parameterisation example of an NMC111|graphite 12.5 Ah pouch cell",
"Description": "NMC111|graphite 12.5 Ah pouch cell. Parameterisation by About:Energy Limited (aboutenergy.io), December 2022, based on cell cycling data, and electrode data gathered after cell teardown. Electrolyte properties from Nyman et al. 2008 (doi:10.1016/j.electacta.2008.04.023). Negative electrode entropic coefficient data are from O'Regan et al. 2022 (doi:10.1016/j.electacta.2022.140700). Positive electrode entropic coefficient data are from Viswanathan et al. 2010 (doi:10.1016/j.jpowsour.2009.11.103). Other thermal properties are estimated.",
"Model": "DFN"
},
"Parameterisation": {
"Cell": {
"Ambient temperature [K]": 298.15,
"Initial temperature [K]": 298.15,
"Reference temperature [K]": 298.15,
"Lower voltage cut-off [V]": 2.7,
"Upper voltage cut-off [V]": 4.2,
"Nominal cell capacity [A.h]": 12.5,
"Specific heat capacity [J.K-1.kg-1]": 913,
"Thermal conductivity [W.m-1.K-1]": 2.04,
"Density [kg.m-3]": 1847,
"Electrode area [m2]": 0.016808,
"Number of electrode pairs connected in parallel to make a cell": 34,
"External surface area [m2]": 0.0379,
"Volume [m3]": 0.000128
},
"Electrolyte": {
"Initial concentration [mol.m-3]": 1000,
"Cation transference number": 0.2594,
"Conductivity [S.m-1]": "0.1297 * (x / 1000) ** 3 - 2.51 * (x / 1000) ** 1.5 + 3.329 * (x / 1000)",
"Diffusivity [m2.s-1]": "8.794e-11 * (x / 1000) ** 2 - 3.972e-10 * (x / 1000) + 4.862e-10",
"Conductivity activation energy [J.mol-1]": 17100,
"Diffusivity activation energy [J.mol-1]": 17100
},
"Negative electrode": {
"Particle radius [m]": 4.12e-06,
"Thickness [m]": 5.62e-05,
"Diffusivity [m2.s-1]": 2.728e-14,
"OCP [V]": "9.47057878e-01 * exp(-1.59418743e+02 * x) - 3.50928033e+04 + 1.64230269e-01 * tanh(-4.55509094e+01 * (x - 3.24116012e-02 )) + 3.69968491e-02 * tanh(-1.96718868e+01 * (x - 1.68334476e-01)) + 1.91517003e+04 * tanh(3.19648312e+00 * (x - 1.85139824e+00)) + 5.42448511e+04 * tanh(-3.19009848e+00 * (x - 2.01660395e+00))",
"Entropic change coefficient [V.K-1]": "(-0.1112 * x + 0.02914 + 0.3561 * exp(-((x - 0.08309) ** 2) / 0.004616)) / 1000",
"Conductivity [S.m-1]": 0.222,
"Surface area per unit volume [m-1]": 499522,
"Porosity": 0.253991,
"Transport efficiency": 0.128,
"Reaction rate constant [mol.m-2.s-1]": 5.199e-06,
"Minimum stoichiometry": 0.005504,
"Maximum stoichiometry": 0.75668,
"Maximum concentration [mol.m-3]": 29730,
"Diffusivity activation energy [J.mol-1]": 30000,
"Reaction rate constant activation energy [J.mol-1]": 55000
},
"Positive electrode": {
"Particle radius [m]": 4.6e-06,
"Thickness [m]": 5.23e-05,
"Diffusivity [m2.s-1]": 3.2e-14,
"OCP [V]": "-3.04420906 * x + 10.04892207 - 0.65637536 * tanh(-4.02134095 * (x - 0.80063948)) + 4.24678547 * tanh(12.17805062 * (x - 7.57659337)) - 0.3757068 * tanh(59.33067782 * (x - 0.99784492))",
"Entropic change coefficient [V.K-1]": -1e-4,
"Conductivity [S.m-1]": 0.789,
"Surface area per unit volume [m-1]": 432072,
"Porosity": 0.277493,
"Transport efficiency": 0.1462,
"Reaction rate constant [mol.m-2.s-1]": 2.305e-05,
"Minimum stoichiometry": 0.42424,
"Maximum stoichiometry": 0.96210,
"Maximum concentration [mol.m-3]": 46200,
"Diffusivity activation energy [J.mol-1]": 15000,
"Reaction rate constant activation energy [J.mol-1]": 35000
},
"Separator": {
"Thickness [m]": 2e-05,
"Porosity": 0.47,
"Transport efficiency": 0.3222
}
},
"Validation": {
"C/20 discharge": {
"Time [s]": [0, 1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, 10000, 11000, 12000, 13000, 14000, 15000, 16000, 17000, 18000, 19000, 20000, 21000, 22000, 23000, 24000, 25000, 26000, 27000, 28000, 29000, 30000, 31000, 32000, 33000, 34000, 35000, 36000, 37000, 38000, 39000, 40000, 41000, 42000, 43000, 44000, 45000, 46000, 47000, 48000, 49000, 50000, 51000, 52000, 53000, 54000, 55000, 56000, 57000, 58000, 59000, 60000, 61000, 62000, 63000, 64000, 65000, 66000, 67000, 68000, 69000, 70000, 71000, 72000, 73000, 74000, 75000],
"Current [A]": [-0.625, -0.625, -0.625, -0.625, -0.625, -0.625, -0.625, -0.625, -0.625, -0.625, -0.625, -0.625, -0.625, -0.625, -0.625, -0.625, -0.625, -0.625, -0.625, -0.625, -0.625, -0.625, -0.625, -0.625, -0.625, -0.625, -0.625, -0.625, -0.625, -0.625, -0.625, -0.625, -0.625, -0.625, -0.625, -0.625, -0.625, -0.625, -0.625, -0.625, -0.625, -0.625, -0.625, -0.625, -0.625, -0.625, -0.625, -0.625, -0.625, -0.625, -0.625, -0.625, -0.625, -0.625, -0.625, -0.625, -0.625, -0.625, -0.625, -0.625, -0.625, -0.625, -0.625, -0.625, -0.625, -0.625, -0.625, -0.625, -0.625, -0.625, -0.625, -0.625, -0.625, -0.625, -0.625, -0.625],
"Voltage [V]": [4.19367569, 4.1677888, 4.14976386, 4.13250593, 4.11582327, 4.09952412, 4.08360848, 4.06788459, 4.05254422, 4.03720384, 4.02186346, 4.00690659, 3.99194973, 3.97699286, 3.96222774, 3.94727088, 3.93250576, 3.9173572, 3.90240027, 3.8874434, 3.87191127, 3.85637914, 3.8404635, 3.82493136, 3.80978274, 3.79482587, 3.78082778, 3.76740495, 3.75455738, 3.74286035, 3.73173857, 3.72176733, 3.71237135, 3.70393414, 3.69607219, 3.68859376, 3.68169059, 3.67517093, 3.66922653, 3.66309038, 3.65752958, 3.65235212, 3.64717474, 3.64257263, 3.637587, 3.63298489, 3.62819102, 3.62339716, 3.61879504, 3.61380942, 3.6088238, 3.60345467, 3.59770202, 3.59175763, 3.58542972, 3.57891014, 3.57200689, 3.56472021, 3.55685827, 3.54822931, 3.53864157, 3.52828682, 3.5173568, 3.5071938, 3.49894834, 3.49204517, 3.48590902, 3.47938936, 3.47152742, 3.4605974, 3.44218895, 3.39981122, 3.33749094, 3.25407757, 3.13308034, 2.89472934],
"Temperature [K]": [298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15]
},
"1C discharge": {
"Time [s]": [0, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000, 1100, 1200, 1300, 1400, 1500, 1600, 1700, 1800, 1900, 2000, 2100, 2200, 2300, 2400, 2500, 2600, 2700, 2800, 2900, 3000, 3100, 3200, 3300, 3400, 3500, 3600, 3700],
"Current [A]": [-12.5, -12.5, -12.5, -12.5, -12.5, -12.5, -12.5, -12.5, -12.5, -12.5, -12.5, -12.5, -12.5, -12.5, -12.5, -12.5, -12.5, -12.5, -12.5, -12.5, -12.5, -12.5, -12.5, -12.5, -12.5, -12.5, -12.5, -12.5, -12.5, -12.5, -12.5, -12.5, -12.5, -12.5, -12.5, -12.5, -12.5, -12.5],
"Voltage [V]": [4.1936757, 4.0487091, 4.0107418, 3.9762259, 3.9426688, 3.9094952, 3.8763218, 3.8433398, 3.8113168, 3.7802525, 3.7505305, 3.7221509, 3.695497, 3.6703771, 3.6467912, 3.6247394, 3.6042217, 3.5858132, 3.5685555, 3.5532149, 3.5382581, 3.5238765, 3.5102619, 3.4974143, 3.4849502, 3.4728697, 3.4609809, 3.4485169, 3.4352858, 3.4216712, 3.4070979, 3.3907987, 3.3720067, 3.3474621, 3.3121793, 3.2569541, 3.1589672, 2.9047014],
"Temperature [K]": [298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15, 298.15]
}
}
}
16 changes: 8 additions & 8 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,40 +72,40 @@ def input_parser():
input_types = list(set(types) - {"jsonld"})
output_types = types

parser.add_argument("-input-file", required=True, help="Input filename")
parser.add_argument("--input-file", required=True, help="Input filename")
parser.add_argument(
"-input-type",
"--input-type",
required=True,
help=f"Input type string (must be one of {input_types})",
)
parser.add_argument(
"-output-file", required=False, default="output.jsonld", help="Output filename"
"--output-file", required=False, default="output.jsonld", help="Output filename"
)
parser.add_argument(
"-output-type",
"--output-type",
required=False,
default="jsonld",
help=f"Output type string (must be one of {output_types})",
)
parser.add_argument(
"-cell-id",
"--cell-id",
required=False,
default="Cell ID",
help="Cell ID (eg BattMo) for JSON-LD output",
)
parser.add_argument(
"-cell-type",
"--cell-type",
required=False,
default="Pouch",
help="Cell Type (eg Pouch) for JSON-LD output",
)
parser.add_argument(
"-ontology-ref",
"--ontology-ref",
default="assets/battery-model-lithium-ion.ttl",
help="Ontology file path",
)
parser.add_argument(
"-template-ref", default="assets/bpx_template.json", help="Template file path"
"--template-ref", default="assets/bpx_template.json", help="Template file path"
)
return parser, input_types, output_types

Expand Down
132 changes: 132 additions & 0 deletions scripts/extract_validation_data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
"""Utility script to extract validation data from a BPX JSON file.

The BPX format allows an optional "Validation" section containing experimental
discharge curves (e.g. C/20 discharge, 1C discharge). This script extracts
those curves and saves each experiment as a CSV file.

Usage::

python scripts/extract_validation_data.py assets/nmc_pouch_cell_BPX.json

This will create one CSV file per experiment found in the Validation section,
named after the experiment (spaces replaced by underscores), e.g.:
nmc_pouch_cell_BPX_C_20_discharge.csv
nmc_pouch_cell_BPX_1C_discharge.csv

Each CSV contains the columns present in the experiment data, e.g.:
Time [s], Current [A], Voltage [V], Temperature [K]

Options
-------
--output-dir Directory in which to write output CSV files (default: same
directory as the input file).
--list List available experiments without writing any files.
"""

import argparse
import csv
import json
import os
import sys


def load_bpx(path):
"""Load a BPX JSON file and return the parsed dict."""
with open(path) as f:
return json.load(f)


def list_experiments(data):
"""Return the names of all experiments in the Validation section."""
validation = data.get("Validation", {})
return list(validation.keys())


def extract_experiment(data, experiment_name):
"""Return the rows (list of dicts) for a single experiment.

Parameters
----------
data : dict
Parsed BPX data.
experiment_name : str
Key inside the ``Validation`` section.

Returns
-------
list[dict]
List of row dicts with column names as keys.
"""
experiment = data["Validation"][experiment_name]
columns = list(experiment.keys())
n_rows = len(experiment[columns[0]])
rows = []
for i in range(n_rows):
row = {col: experiment[col][i] for col in columns}
rows.append(row)
return rows, columns


def safe_filename(name):
"""Convert an experiment name to a safe filename fragment."""
return name.replace("/", "_").replace(" ", "_").replace("\\", "_")


def write_csv(rows, columns, path):
"""Write rows to a CSV file."""
with open(path, "w", newline="") as f:
writer = csv.DictWriter(f, fieldnames=columns)
writer.writeheader()
writer.writerows(rows)


def main(argv=None):
parser = argparse.ArgumentParser(
description="Extract validation data from a BPX JSON file into CSV files."
)
parser.add_argument("bpx_file", help="Path to the BPX JSON file.")
parser.add_argument(
"--output-dir",
default=None,
help="Directory for output CSV files (default: same directory as input file).",
)
parser.add_argument(
"--list",
action="store_true",
help="List available experiments and exit without writing files.",
)
args = parser.parse_args(argv)

bpx_path = args.bpx_file
if not os.path.isfile(bpx_path):
print(f"Error: file not found: {bpx_path}", file=sys.stderr)
sys.exit(1)

data = load_bpx(bpx_path)

experiments = list_experiments(data)
if not experiments:
print("No Validation section found in the BPX file.", file=sys.stderr)
sys.exit(0)

if args.list:
print("Available experiments:")
for name in experiments:
print(f" {name}")
return

output_dir = args.output_dir or os.path.dirname(os.path.abspath(bpx_path))
os.makedirs(output_dir, exist_ok=True)

base = os.path.splitext(os.path.basename(bpx_path))[0]

for experiment_name in experiments:
rows, columns = extract_experiment(data, experiment_name)
out_name = f"{base}_{safe_filename(experiment_name)}.csv"
out_path = os.path.join(output_dir, out_name)
write_csv(rows, columns, out_path)
print(f"Wrote {len(rows)} rows to {out_path}")


if __name__ == "__main__":
main()
Loading