├── data ├── raw-data │ ├── Summary_of_builds.xlsx │ ├── Summary_of_CAMP_Cells.xlsx │ └── README.md ├── README.md └── hdf5 │ └── README.md ├── environment.yml ├── README.md ├── 3_publish-to-mdf.ipynb ├── 1_add-derived-features.ipynb └── 0_creating-archive-formats.ipynb /data/raw-data/Summary_of_builds.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/materials-data-facility/publishing-battery-data/HEAD/data/raw-data/Summary_of_builds.xlsx -------------------------------------------------------------------------------- /data/raw-data/Summary_of_CAMP_Cells.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/materials-data-facility/publishing-battery-data/HEAD/data/raw-data/Summary_of_CAMP_Cells.xlsx -------------------------------------------------------------------------------- /data/README.md: -------------------------------------------------------------------------------- 1 | # CAMP 2023 Data 2 | 3 | The subdirectories of this folder contain the raw data in MACCOR format 4 | along with metadata in XSLX files (`raw-data`), 5 | and processed forms in HDF5 format that contain both data and metadata (`hdf5`). 6 | -------------------------------------------------------------------------------- /environment.yml: -------------------------------------------------------------------------------- 1 | name: camp 2 | dependencies: 3 | - python==3.11.* 4 | - pip 5 | - pip: 6 | - jupyterlab 7 | - mdf_connect_client 8 | - matplotlib 9 | - openpyxl 10 | - tqdm 11 | - battery-data-toolkit>=0.4 12 | -------------------------------------------------------------------------------- /data/raw-data/README.md: -------------------------------------------------------------------------------- 1 | # Raw Data 2 | 3 | This directory contains the data in their original form. 4 | 5 | We have the: 6 | 7 | - `CAMP_Data`: MACCOR-format test files 8 | - `Summary_of_CAMP_Cells.xlsx`: Description of the tests for each of the test files 9 | - `Summary_of_builds.xlsx`: Description of the batteries used in each batch of tests 10 | -------------------------------------------------------------------------------- /data/hdf5/README.md: -------------------------------------------------------------------------------- 1 | # Data in HDF5 Format 2 | 3 | These folders contain the data in HDF5 format. `refined` contains the 300 cells used in [Paulson et al.](https://www.sciencedirect.com/science/article/pii/S0378775322001495), 4 | and `other` contains any other cell for which we have documentation. 5 | 6 | An example of how to read a cell using Argonne's Battery Data Toolkit: 7 | 8 | ```python 9 | from batdata.data import BatteryDataset 10 | BatteryDataset.from_batdata_hdf('./data/refined/batch_B28B_cell_2.h5') 11 | ``` 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Building a Publication-Ready Dataset from CAMP 2 | 3 | [Paulson et al. JPC (2022)](ihttps://doi.org/10.1016/j.jpowsour.2022.231127) created machine learning models using data from different types of cells made by the [CAMP Facility](https://www.anl.gov/cse/cell-analysis-modeling-and-prototyping-camp-facility). 4 | We convert that data into publication-ready forms in this repository by storing the data in well-defined formats that package metadata about the cell's design and testing. 5 | 6 | See [our battery data toolkit](https://github.com/materials-data-facility/battery-data-toolkit) for a full description on the tools for how to use them. 7 | 8 | ## Accessing the Data 9 | 10 | The data are available via the Materials Data Facility: [landing page](https://doi.org/10.18126/s90e-dq90) 11 | 12 | You can download the entire data as a single ZIP file ([link](https://data.materialsdatafacility.org/mdf_open/camp_2023/1.1/all_hdf5s.zip)), 13 | or use [Globus Transfer](https://docs.globus.org/how-to/get-started/) to get different subsets. 14 | 15 | ## Installation 16 | 17 | Install the environment for this repository using Anaconda. 18 | 19 | ```bash 20 | conda env create --file environment.yml --force 21 | ``` 22 | -------------------------------------------------------------------------------- /3_publish-to-mdf.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "12baa199-5ef8-49a6-b8bd-4d698471fc7a", 6 | "metadata": {}, 7 | "source": [ 8 | "# Publish Dataset to the MDF\n", 9 | "Publishes our data to the Materials Data Facility" 10 | ] 11 | }, 12 | { 13 | "cell_type": "code", 14 | "execution_count": 1, 15 | "id": "27b966ea-8abc-4547-8b84-a526219ceb03", 16 | "metadata": {}, 17 | "outputs": [], 18 | "source": [ 19 | "from mdf_connect_client import MDFConnectClient\n", 20 | "from globus_sdk import LocalGlobusConnectPersonal\n", 21 | "from battdat import __version__ as bd_version\n", 22 | "from zipfile import ZipFile, ZIP_DEFLATED\n", 23 | "from pathlib import Path, PurePosixPath\n", 24 | "from shutil import rmtree\n", 25 | "import html" 26 | ] 27 | }, 28 | { 29 | "cell_type": "markdown", 30 | "id": "fcc4b7c5-79b9-47af-a5a1-dab6f4744137", 31 | "metadata": {}, 32 | "source": [ 33 | "Configuration" 34 | ] 35 | }, 36 | { 37 | "cell_type": "code", 38 | "execution_count": 2, 39 | "id": "4179307d-dd2e-4b6d-a4fe-9ec579a2775a", 40 | "metadata": {}, 41 | "outputs": [], 42 | "source": [ 43 | "local_ep = LocalGlobusConnectPersonal()" 44 | ] 45 | }, 46 | { 47 | "cell_type": "code", 48 | "execution_count": 3, 49 | "id": "3399a800-22ca-4f8a-8e58-fd90f5989bdb", 50 | "metadata": {}, 51 | "outputs": [], 52 | "source": [ 53 | "source_endpoint = local_ep.endpoint_id # Globus endpoint of my desktop\n", 54 | "data_path = Path.cwd() / 'data'" 55 | ] 56 | }, 57 | { 58 | "cell_type": "markdown", 59 | "id": "245a97ee-9501-42db-bdc2-2101079bf84c", 60 | "metadata": {}, 61 | "source": [ 62 | "## Clean the directories\n", 63 | "Remove temporary files placed by Jupyter" 64 | ] 65 | }, 66 | { 67 | "cell_type": "code", 68 | "execution_count": 4, 69 | "id": "7dcb218f-5eb1-441c-9c6f-53c1f8cc6c4e", 70 | "metadata": {}, 71 | "outputs": [], 72 | "source": [ 73 | "for path in data_path.glob('*data/**/.ipynb_checkpoints'):\n", 74 | " rmtree(path)\n", 75 | " print(f'Removed: {path.relative_to(Path().cwd())}')" 76 | ] 77 | }, 78 | { 79 | "cell_type": "markdown", 80 | "id": "5d8f0f59-1d36-4b56-ab4a-f9e8d94fa5e4", 81 | "metadata": {}, 82 | "source": [ 83 | "## Make a ZIP of all HDF5 files\n", 84 | "Something easy for people to download" 85 | ] 86 | }, 87 | { 88 | "cell_type": "code", 89 | "execution_count": 5, 90 | "id": "2fb4ecbc-3f42-4223-9638-c8a0e1348d9e", 91 | "metadata": {}, 92 | "outputs": [], 93 | "source": [ 94 | "zip_path = data_path / 'all_hdf5s.zip'\n", 95 | "zip_path.unlink(missing_ok=True)" 96 | ] 97 | }, 98 | { 99 | "cell_type": "code", 100 | "execution_count": 6, 101 | "id": "a870973b-fc53-4adc-8d59-16ac514048ee", 102 | "metadata": {}, 103 | "outputs": [], 104 | "source": [ 105 | "with ZipFile(data_path / 'all_hdf5s.zip', 'w', compresslevel=9, compression=ZIP_DEFLATED) as zf:\n", 106 | " for file in (data_path / 'hdf5' ).rglob(\"*.h5\"):\n", 107 | " zf.write(file, arcname=file.relative_to(data_path / 'hdf5'))" 108 | ] 109 | }, 110 | { 111 | "cell_type": "markdown", 112 | "id": "e7f50a48-dc9f-40ef-ad38-da64716d2016", 113 | "metadata": {}, 114 | "source": [ 115 | "## Create the client\n", 116 | "This will handle authentication with the Materials Data Facility" 117 | ] 118 | }, 119 | { 120 | "cell_type": "code", 121 | "execution_count": 7, 122 | "id": "597c39f0-08c1-4efc-a421-924a9c4cc119", 123 | "metadata": {}, 124 | "outputs": [ 125 | { 126 | "name": "stdout", 127 | "output_type": "stream", 128 | "text": [ 129 | "Starting login with Globus Auth, press ^C to cancel.\n" 130 | ] 131 | } 132 | ], 133 | "source": [ 134 | "client = MDFConnectClient()" 135 | ] 136 | }, 137 | { 138 | "cell_type": "markdown", 139 | "id": "1ec9d39a-cb93-4575-9054-61714186c9f9", 140 | "metadata": {}, 141 | "source": [ 142 | "## Add basic metadata\n", 143 | "Authors, titles, related publications" 144 | ] 145 | }, 146 | { 147 | "cell_type": "code", 148 | "execution_count": 8, 149 | "id": "46d25494-bf3f-4e56-9cc1-40974c54eac9", 150 | "metadata": {}, 151 | "outputs": [], 152 | "source": [ 153 | "n_refined = len(list((data_path / 'hdf5' / 'refined').glob(\"*.h5\")))\n", 154 | "n_other = len(list((data_path / 'hdf5' / 'other').glob(\"*.h5\")))\n", 155 | "assert n_refined == 300\n", 156 | "assert n_refined + n_other == 602" 157 | ] 158 | }, 159 | { 160 | "cell_type": "code", 161 | "execution_count": 9, 162 | "id": "f134aa7b-f6db-4732-8468-4bea0519ee80", 163 | "metadata": {}, 164 | "outputs": [], 165 | "source": [ 166 | "client.create_dc_block(\n", 167 | " 'Dataset of NMC battery Tests from CAMP, 2023 Release',\n", 168 | " authors = [\n", 169 | " 'Logan Ward',\n", 170 | " 'Joseph Kubal',\n", 171 | " 'Susan J. Babinec',\n", 172 | " 'Wenquan Lu',\n", 173 | " 'Allison Dunlop',\n", 174 | " 'Steve Trask',\n", 175 | " 'Bryant Polzin',\n", 176 | " 'Andrew Jansen',\n", 177 | " 'Noah H. Paulson'\n", 178 | " ],\n", 179 | " affiliations='Argonne National Laboratory',\n", 180 | " description='Collection of cycle life tests of Li-ion batteries from the '\n", 181 | " 'Cell Analysis, Modeling, and Prototyping (CAMP) Facility at Argonne National Laboratory. '\n", 182 | " f'The dataset contains all {n_refined} cells used by Paulson et al. to study the effect of feature '\n", 183 | " 'engineering on machine learning models to predict the life of batteries, which were selected because they '\n", 184 | " 'have a graphite anode, used low charging rates, and were tested for at least 100 cycles. '\n", 185 | " f'We also include {n_other} cells that '\n", 186 | " 'failed to meet acceptence criteria for that study. '\n", 187 | " f'Each cell is stored in the HDF5 format of Argonne\\'s Battery Data Toolkit v{bd_version}, '\n", 188 | " 'which includes battery metadata, the raw signal from the testing equipment, and '\n", 189 | " 'per-cycle summaries of battery performance.',\n", 190 | " related_dois=['10.1016/j.jpowsour.2022.231127']\n", 191 | ")" 192 | ] 193 | }, 194 | { 195 | "cell_type": "code", 196 | "execution_count": 10, 197 | "id": "80d32c82-9f40-4837-aa62-7533146488d2", 198 | "metadata": {}, 199 | "outputs": [], 200 | "source": [ 201 | "client.set_source_name('camp_2023')" 202 | ] 203 | }, 204 | { 205 | "cell_type": "code", 206 | "execution_count": 11, 207 | "id": "547277e2-64cd-4b92-a9cf-d7d51582acb6", 208 | "metadata": {}, 209 | "outputs": [ 210 | { 211 | "name": "stdout", 212 | "output_type": "stream", 213 | "text": [ 214 | "Collection of cycle life tests of Li-ion batteries from the Cell Analysis, Modeling, and Prototyping (CAMP) Facility at Argonne National Laboratory. The dataset contains all 300 cells used by Paulson et al. to study the effect of feature engineering on machine learning models to predict the life of batteries, which were selected because they have a graphite anode, used low charging rates, and were tested for at least 100 cycles. We also include 302 cells that failed to meet acceptence criteria for that study. Each cell is stored in the HDF5 format of Argonne's Battery Data Toolkit v0.4.3, which includes battery metadata, the raw signal from the testing equipment, and per-cycle summaries of battery performance.\n" 215 | ] 216 | } 217 | ], 218 | "source": [ 219 | "print(client.dc['descriptions'][0]['description'])" 220 | ] 221 | }, 222 | { 223 | "cell_type": "markdown", 224 | "id": "44740e38-9576-4077-98ce-d29c954d3584", 225 | "metadata": {}, 226 | "source": [ 227 | "## Add Data Source\n", 228 | "Tell MDF where to get the data from" 229 | ] 230 | }, 231 | { 232 | "cell_type": "markdown", 233 | "id": "3f6c1f65-e3cc-462e-b450-6dfdbc201f8d", 234 | "metadata": {}, 235 | "source": [ 236 | "Prepend the drive letter if on Windows" 237 | ] 238 | }, 239 | { 240 | "cell_type": "code", 241 | "execution_count": 12, 242 | "id": "a3c27fde-2cb0-4d3f-b54b-57107c072c53", 243 | "metadata": {}, 244 | "outputs": [], 245 | "source": [ 246 | "if len(data_path.drive) > 0:\n", 247 | " globus_path = data_path.relative_to(data_path.drive + '/').as_posix()\n", 248 | " globus_path = PurePosixPath('/') / data_path.drive.rstrip(':') / globus_path\n", 249 | "else:\n", 250 | " globus_path = data_path.absolute()" 251 | ] 252 | }, 253 | { 254 | "cell_type": "code", 255 | "execution_count": 13, 256 | "id": "89f7d688-0bb3-4720-affe-6bb10b2c3b14", 257 | "metadata": {}, 258 | "outputs": [], 259 | "source": [ 260 | "client.add_data_source(f'https://app.globus.org/file-manager?origin_id={source_endpoint}&origin_path={html.escape(str(globus_path))}')" 261 | ] 262 | }, 263 | { 264 | "cell_type": "markdown", 265 | "id": "9dae2556-6eb5-4c9e-88c3-436c755e78d4", 266 | "metadata": {}, 267 | "source": [ 268 | "# Submit the Dataset\n", 269 | "Send it to the Materials Data Facility to be Processed" 270 | ] 271 | }, 272 | { 273 | "cell_type": "code", 274 | "execution_count": 14, 275 | "id": "9ad917ee-8e4a-41bc-9fac-25c9394ecb7e", 276 | "metadata": {}, 277 | "outputs": [], 278 | "source": [ 279 | "client.set_organization(\"MDF Open\") # Mark that we want it to go to MDF" 280 | ] 281 | }, 282 | { 283 | "cell_type": "markdown", 284 | "id": "69de9d72-cead-4813-9e88-c439b2e0bd1b", 285 | "metadata": {}, 286 | "source": [ 287 | "Print out what I submitted" 288 | ] 289 | }, 290 | { 291 | "cell_type": "code", 292 | "execution_count": 15, 293 | "id": "03a7dfd8-9c2f-43be-94a7-6697da60910c", 294 | "metadata": {}, 295 | "outputs": [ 296 | { 297 | "data": { 298 | "text/plain": [ 299 | "{'dc': {'titles': [{'title': 'Dataset of NMC battery Tests from CAMP, 2023 Release'}],\n", 300 | " 'creators': [{'creatorName': 'Ward, Logan',\n", 301 | " 'familyName': 'Ward',\n", 302 | " 'givenName': 'Logan',\n", 303 | " 'affiliations': ['Argonne National Laboratory']},\n", 304 | " {'creatorName': 'Kubal, Joseph',\n", 305 | " 'familyName': 'Kubal',\n", 306 | " 'givenName': 'Joseph',\n", 307 | " 'affiliations': ['Argonne National Laboratory']},\n", 308 | " {'creatorName': 'Babinec, Susan J.',\n", 309 | " 'familyName': 'Babinec',\n", 310 | " 'givenName': 'Susan J.',\n", 311 | " 'affiliations': ['Argonne National Laboratory']},\n", 312 | " {'creatorName': 'Lu, Wenquan',\n", 313 | " 'familyName': 'Lu',\n", 314 | " 'givenName': 'Wenquan',\n", 315 | " 'affiliations': ['Argonne National Laboratory']},\n", 316 | " {'creatorName': 'Dunlop, Allison',\n", 317 | " 'familyName': 'Dunlop',\n", 318 | " 'givenName': 'Allison',\n", 319 | " 'affiliations': ['Argonne National Laboratory']},\n", 320 | " {'creatorName': 'Trask, Steve',\n", 321 | " 'familyName': 'Trask',\n", 322 | " 'givenName': 'Steve',\n", 323 | " 'affiliations': ['Argonne National Laboratory']},\n", 324 | " {'creatorName': 'Polzin, Bryant',\n", 325 | " 'familyName': 'Polzin',\n", 326 | " 'givenName': 'Bryant',\n", 327 | " 'affiliations': ['Argonne National Laboratory']},\n", 328 | " {'creatorName': 'Jansen, Andrew',\n", 329 | " 'familyName': 'Jansen',\n", 330 | " 'givenName': 'Andrew',\n", 331 | " 'affiliations': ['Argonne National Laboratory']},\n", 332 | " {'creatorName': 'Paulson, Noah H.',\n", 333 | " 'familyName': 'Paulson',\n", 334 | " 'givenName': 'Noah H.',\n", 335 | " 'affiliations': ['Argonne National Laboratory']}],\n", 336 | " 'publisher': 'Materials Data Facility',\n", 337 | " 'publicationYear': '2025',\n", 338 | " 'resourceType': {'resourceTypeGeneral': 'Dataset',\n", 339 | " 'resourceType': 'Dataset'},\n", 340 | " 'descriptions': [{'description': \"Collection of cycle life tests of Li-ion batteries from the Cell Analysis, Modeling, and Prototyping (CAMP) Facility at Argonne National Laboratory. The dataset contains all 300 cells used by Paulson et al. to study the effect of feature engineering on machine learning models to predict the life of batteries, which were selected because they have a graphite anode, used low charging rates, and were tested for at least 100 cycles. We also include 302 cells that failed to meet acceptence criteria for that study. Each cell is stored in the HDF5 format of Argonne's Battery Data Toolkit v0.4.3, which includes battery metadata, the raw signal from the testing equipment, and per-cycle summaries of battery performance.\",\n", 341 | " 'descriptionType': 'Other'}],\n", 342 | " 'relatedIdentifiers': [{'relatedIdentifier': '10.1016/j.jpowsour.2022.231127',\n", 343 | " 'relatedIdentifierType': 'DOI',\n", 344 | " 'relationType': 'IsPartOf'}]},\n", 345 | " 'data_sources': ['https://app.globus.org/file-manager?origin_id=0c3771de-f236-11ec-b3c1-15403b7b75ed&origin_path=/home/lward/Work/ROVI/publishing-camp-data/data'],\n", 346 | " 'test': False,\n", 347 | " 'update': False,\n", 348 | " 'mdf': {'source_name': 'camp_2023', 'organization': 'MDF Open'},\n", 349 | " 'update_metadata_only': False}" 350 | ] 351 | }, 352 | "execution_count": 15, 353 | "metadata": {}, 354 | "output_type": "execute_result" 355 | } 356 | ], 357 | "source": [ 358 | "client.get_submission()" 359 | ] 360 | }, 361 | { 362 | "cell_type": "code", 363 | "execution_count": 16, 364 | "id": "0df24fc9-4cc4-4d5b-a137-afbb761e23e5", 365 | "metadata": {}, 366 | "outputs": [ 367 | { 368 | "data": { 369 | "text/plain": [ 370 | "{'source_id': 'camp_2023', 'success': True, 'error': None, 'status_code': 202}" 371 | ] 372 | }, 373 | "execution_count": 16, 374 | "metadata": {}, 375 | "output_type": "execute_result" 376 | } 377 | ], 378 | "source": [ 379 | "result = client.submit_dataset(update=True)\n", 380 | "result" 381 | ] 382 | }, 383 | { 384 | "cell_type": "code", 385 | "execution_count": null, 386 | "id": "2e0b72d0-72fc-4f08-9ef7-36d209545ef1", 387 | "metadata": {}, 388 | "outputs": [], 389 | "source": [] 390 | } 391 | ], 392 | "metadata": { 393 | "kernelspec": { 394 | "display_name": "Python 3 (ipykernel)", 395 | "language": "python", 396 | "name": "python3" 397 | }, 398 | "language_info": { 399 | "codemirror_mode": { 400 | "name": "ipython", 401 | "version": 3 402 | }, 403 | "file_extension": ".py", 404 | "mimetype": "text/x-python", 405 | "name": "python", 406 | "nbconvert_exporter": "python", 407 | "pygments_lexer": "ipython3", 408 | "version": "3.11.11" 409 | } 410 | }, 411 | "nbformat": 4, 412 | "nbformat_minor": 5 413 | } 414 | -------------------------------------------------------------------------------- /1_add-derived-features.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "a1c07eac-dca9-454f-8dea-2e9695017fa9", 6 | "metadata": {}, 7 | "source": [ 8 | "# Add Cycle-Level Features\n", 9 | "We use a few different types of faetures that summarize cycles for the machine learning models." 10 | ] 11 | }, 12 | { 13 | "cell_type": "code", 14 | "execution_count": 1, 15 | "id": "06d4dc4b-cae5-4af4-b247-e3fef85cc3e4", 16 | "metadata": {}, 17 | "outputs": [], 18 | "source": [ 19 | "%matplotlib inline\n", 20 | "from matplotlib import pyplot as plt\n", 21 | "from battdat.postprocess.integral import CapacityPerCycle, StateOfCharge\n", 22 | "from battdat.postprocess.timing import CycleTimesSummarizer\n", 23 | "from battdat.data import BatteryDataset\n", 24 | "from concurrent.futures import ProcessPoolExecutor\n", 25 | "from functools import partial\n", 26 | "from pathlib import Path\n", 27 | "from tqdm import tqdm\n", 28 | "import pandas as pd" 29 | ] 30 | }, 31 | { 32 | "cell_type": "markdown", 33 | "id": "3f0070ab-db5c-4ac8-9a2c-5a3666f540a0", 34 | "metadata": {}, 35 | "source": [ 36 | "Configuration" 37 | ] 38 | }, 39 | { 40 | "cell_type": "code", 41 | "execution_count": 2, 42 | "id": "a503d6ae-daf0-4d23-a1e5-9470b8a4cadb", 43 | "metadata": {}, 44 | "outputs": [], 45 | "source": [ 46 | "hdf5_path = './data/hdf5/'" 47 | ] 48 | }, 49 | { 50 | "cell_type": "markdown", 51 | "id": "16fccd76-bb30-43e1-aaaf-bbf0fbf006d4", 52 | "metadata": {}, 53 | "source": [ 54 | "## Load in an example cell\n", 55 | "Start with one to show off how this works" 56 | ] 57 | }, 58 | { 59 | "cell_type": "code", 60 | "execution_count": 3, 61 | "id": "944ca2d1-7d94-4cdf-aca4-7cbca3a8f003", 62 | "metadata": {}, 63 | "outputs": [], 64 | "source": [ 65 | "dataset = BatteryDataset.from_hdf('./data/hdf5/refined/batch_B28B_cell_2.h5')" 66 | ] 67 | }, 68 | { 69 | "cell_type": "markdown", 70 | "id": "6fc8a205-762c-4e9e-aedb-4936c41be363", 71 | "metadata": {}, 72 | "source": [ 73 | "## Compute the Features\n", 74 | "Each is described using a different class" 75 | ] 76 | }, 77 | { 78 | "cell_type": "markdown", 79 | "id": "a54771a7-0303-48bf-b378-e9922977c3a2", 80 | "metadata": {}, 81 | "source": [ 82 | "### Capacity Per Cycle\n", 83 | "Compute the amount of charge pushed into the battery and the " 84 | ] 85 | }, 86 | { 87 | "cell_type": "code", 88 | "execution_count": 4, 89 | "id": "b7f5fc2b-c434-4eb5-ab7d-d9af5a3e795d", 90 | "metadata": {}, 91 | "outputs": [], 92 | "source": [ 93 | "CapacityPerCycle().add_summaries(dataset)" 94 | ] 95 | }, 96 | { 97 | "cell_type": "code", 98 | "execution_count": 5, 99 | "id": "c656a8c6-87b0-422e-8e0e-1feb9f3b3dcf", 100 | "metadata": {}, 101 | "outputs": [ 102 | { 103 | "data": { 104 | "text/plain": [ 105 | "Text(0, 0.5, 'Charge Capacity (A-hr)')" 106 | ] 107 | }, 108 | "execution_count": 5, 109 | "metadata": {}, 110 | "output_type": "execute_result" 111 | }, 112 | { 113 | "data": { 114 | "image/png": "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", 115 | "text/plain": [ 116 | "
" 117 | ] 118 | }, 119 | "metadata": {}, 120 | "output_type": "display_data" 121 | } 122 | ], 123 | "source": [ 124 | "fig, ax = plt.subplots(figsize=(3.5, 2.5))\n", 125 | "\n", 126 | "ax.plot(dataset.cycle_stats['capacity_charge'])\n", 127 | "\n", 128 | "ax.set_xlabel('Cycle')\n", 129 | "ax.set_ylabel('Charge Capacity (A-hr)')" 130 | ] 131 | }, 132 | { 133 | "cell_type": "code", 134 | "execution_count": 6, 135 | "id": "178c7d37-4811-4e6b-9249-dab587ac8707", 136 | "metadata": {}, 137 | "outputs": [ 138 | { 139 | "data": { 140 | "text/html": [ 141 | "
\n", 142 | "\n", 155 | "\n", 156 | " \n", 157 | " \n", 158 | " \n", 159 | " \n", 160 | " \n", 161 | " \n", 162 | " \n", 163 | " \n", 164 | " \n", 165 | " \n", 166 | " \n", 167 | " \n", 168 | " \n", 169 | " \n", 170 | " \n", 171 | " \n", 172 | " \n", 173 | " \n", 174 | " \n", 175 | " \n", 176 | " \n", 177 | " \n", 178 | " \n", 179 | " \n", 180 | " \n", 181 | " \n", 182 | " \n", 183 | " \n", 184 | " \n", 185 | " \n", 186 | " \n", 187 | " \n", 188 | " \n", 189 | " \n", 190 | " \n", 191 | " \n", 192 | " \n", 193 | " \n", 194 | " \n", 195 | " \n", 196 | " \n", 197 | " \n", 198 | " \n", 199 | " \n", 200 | " \n", 201 | " \n", 202 | " \n", 203 | " \n", 204 | " \n", 205 | " \n", 206 | " \n", 207 | " \n", 208 | "
cycle_numberenergy_chargecapacity_chargeenergy_dischargecapacity_discharge
004.336156e-071.250638e-070.0000000.000000
111.460777e+003.913738e-011.5456220.420994
221.562767e+004.219583e-011.5390380.419249
331.555165e+004.194255e-011.5171070.413389
441.534272e+004.134042e-011.5136090.412468
\n", 209 | "
" 210 | ], 211 | "text/plain": [ 212 | " cycle_number energy_charge capacity_charge energy_discharge \\\n", 213 | "0 0 4.336156e-07 1.250638e-07 0.000000 \n", 214 | "1 1 1.460777e+00 3.913738e-01 1.545622 \n", 215 | "2 2 1.562767e+00 4.219583e-01 1.539038 \n", 216 | "3 3 1.555165e+00 4.194255e-01 1.517107 \n", 217 | "4 4 1.534272e+00 4.134042e-01 1.513609 \n", 218 | "\n", 219 | " capacity_discharge \n", 220 | "0 0.000000 \n", 221 | "1 0.420994 \n", 222 | "2 0.419249 \n", 223 | "3 0.413389 \n", 224 | "4 0.412468 " 225 | ] 226 | }, 227 | "execution_count": 6, 228 | "metadata": {}, 229 | "output_type": "execute_result" 230 | } 231 | ], 232 | "source": [ 233 | "dataset.cycle_stats.head(5)" 234 | ] 235 | }, 236 | { 237 | "cell_type": "markdown", 238 | "id": "0c3706f5-741a-41db-aaac-f584d776d5ce", 239 | "metadata": {}, 240 | "source": [ 241 | "## Run for each cell\n", 242 | "Load in, create cycle summaries, save back" 243 | ] 244 | }, 245 | { 246 | "cell_type": "code", 247 | "execution_count": 7, 248 | "id": "2b806c37-58ec-4fe1-8298-da1e517865ec", 249 | "metadata": {}, 250 | "outputs": [], 251 | "source": [ 252 | "feature_computers = [CapacityPerCycle(), StateOfCharge(), CycleTimesSummarizer()]" 253 | ] 254 | }, 255 | { 256 | "cell_type": "markdown", 257 | "id": "c3828279-d32f-4200-868f-9f4a91b0383b", 258 | "metadata": {}, 259 | "source": [ 260 | "Make a function we can run in parallel" 261 | ] 262 | }, 263 | { 264 | "cell_type": "code", 265 | "execution_count": 8, 266 | "id": "065446c5-3fb2-4738-8714-c799629d16b8", 267 | "metadata": {}, 268 | "outputs": [], 269 | "source": [ 270 | "def add_features(file: Path, computers: list):\n", 271 | " \"\"\"Add features to a certain file\"\"\"\n", 272 | " from warnings import catch_warnings, filterwarnings\n", 273 | " dataset = BatteryDataset.from_hdf(str(file))\n", 274 | " with catch_warnings():\n", 275 | " filterwarnings('ignore')\n", 276 | " for feature in feature_computers:\n", 277 | " feature.compute_features(dataset)\n", 278 | " dataset.to_hdf(str(file), complevel=9)\n", 279 | " return file\n", 280 | "func = partial(add_features, computers=feature_computers)" 281 | ] 282 | }, 283 | { 284 | "cell_type": "code", 285 | "execution_count": 9, 286 | "id": "bff61e7a-178d-4e74-be7c-074396f1dc75", 287 | "metadata": {}, 288 | "outputs": [ 289 | { 290 | "name": "stderr", 291 | "output_type": "stream", 292 | "text": [ 293 | "602it [54:36, 5.44s/it]\n" 294 | ] 295 | } 296 | ], 297 | "source": [ 298 | "for path in tqdm(Path(hdf5_path).rglob('*.h5')):\n", 299 | " func(path)" 300 | ] 301 | }, 302 | { 303 | "cell_type": "markdown", 304 | "id": "869421e8-207e-487a-8d3a-4c70788a0adc", 305 | "metadata": {}, 306 | "source": [ 307 | "## Show the improved HDF5 file\n", 308 | "The features are now part of HDF5 file" 309 | ] 310 | }, 311 | { 312 | "cell_type": "code", 313 | "execution_count": 10, 314 | "id": "43c10023-e3ca-42f7-b34e-c740ee964909", 315 | "metadata": {}, 316 | "outputs": [], 317 | "source": [ 318 | "dataset = BatteryDataset.from_hdf('./data/hdf5/refined/batch_B28B_cell_2.h5')" 319 | ] 320 | }, 321 | { 322 | "cell_type": "code", 323 | "execution_count": 11, 324 | "id": "75535afa-cf0e-486e-8529-290c5d6b090c", 325 | "metadata": {}, 326 | "outputs": [ 327 | { 328 | "data": { 329 | "text/html": [ 330 | "
\n", 331 | "\n", 344 | "\n", 345 | " \n", 346 | " \n", 347 | " \n", 348 | " \n", 349 | " \n", 350 | " \n", 351 | " \n", 352 | " \n", 353 | " \n", 354 | " \n", 355 | " \n", 356 | " \n", 357 | " \n", 358 | " \n", 359 | " \n", 360 | " \n", 361 | " \n", 362 | " \n", 363 | " \n", 364 | " \n", 365 | " \n", 366 | " \n", 367 | " \n", 368 | " \n", 369 | " \n", 370 | " \n", 371 | " \n", 372 | " \n", 373 | " \n", 374 | " \n", 375 | " \n", 376 | " \n", 377 | " \n", 378 | " \n", 379 | " \n", 380 | " \n", 381 | " \n", 382 | " \n", 383 | " \n", 384 | " \n", 385 | " \n", 386 | " \n", 387 | " \n", 388 | " \n", 389 | " \n", 390 | " \n", 391 | " \n", 392 | " \n", 393 | " \n", 394 | " \n", 395 | " \n", 396 | " \n", 397 | " \n", 398 | " \n", 399 | " \n", 400 | " \n", 401 | " \n", 402 | " \n", 403 | " \n", 404 | " \n", 405 | " \n", 406 | " \n", 407 | " \n", 408 | " \n", 409 | "
cycle_numberenergy_chargecapacity_chargeenergy_dischargecapacity_dischargecycle_startcycle_duration
004.336156e-071.250638e-070.0000000.0000000.0000060.048000
111.460777e+003.913738e-011.5456220.42099460.04800156219.453125
221.562767e+004.219583e-011.5390380.419249156279.50000161755.468750
331.555165e+004.194255e-011.5171070.413389318034.9687580239.937500
441.534272e+004.134042e-011.5136090.412468398274.9062579615.718750
\n", 410 | "
" 411 | ], 412 | "text/plain": [ 413 | " cycle_number energy_charge capacity_charge energy_discharge \\\n", 414 | "0 0 4.336156e-07 1.250638e-07 0.000000 \n", 415 | "1 1 1.460777e+00 3.913738e-01 1.545622 \n", 416 | "2 2 1.562767e+00 4.219583e-01 1.539038 \n", 417 | "3 3 1.555165e+00 4.194255e-01 1.517107 \n", 418 | "4 4 1.534272e+00 4.134042e-01 1.513609 \n", 419 | "\n", 420 | " capacity_discharge cycle_start cycle_duration \n", 421 | "0 0.000000 0.00000 60.048000 \n", 422 | "1 0.420994 60.04800 156219.453125 \n", 423 | "2 0.419249 156279.50000 161755.468750 \n", 424 | "3 0.413389 318034.96875 80239.937500 \n", 425 | "4 0.412468 398274.90625 79615.718750 " 426 | ] 427 | }, 428 | "execution_count": 11, 429 | "metadata": {}, 430 | "output_type": "execute_result" 431 | } 432 | ], 433 | "source": [ 434 | "dataset.cycle_stats.head(5)" 435 | ] 436 | }, 437 | { 438 | "cell_type": "code", 439 | "execution_count": null, 440 | "id": "2548b245-d1d9-40d1-8569-fe6838ae001d", 441 | "metadata": {}, 442 | "outputs": [], 443 | "source": [] 444 | } 445 | ], 446 | "metadata": { 447 | "kernelspec": { 448 | "display_name": "Python 3 (ipykernel)", 449 | "language": "python", 450 | "name": "python3" 451 | }, 452 | "language_info": { 453 | "codemirror_mode": { 454 | "name": "ipython", 455 | "version": 3 456 | }, 457 | "file_extension": ".py", 458 | "mimetype": "text/x-python", 459 | "name": "python", 460 | "nbconvert_exporter": "python", 461 | "pygments_lexer": "ipython3", 462 | "version": "3.11.11" 463 | } 464 | }, 465 | "nbformat": 4, 466 | "nbformat_minor": 5 467 | } 468 | -------------------------------------------------------------------------------- /0_creating-archive-formats.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "bb062297-7821-4df2-8474-8fad271e7c30", 6 | "metadata": {}, 7 | "source": [ 8 | "# Creating Archive-Ready Metadata\n", 9 | "The raw data is split into a few different files:\n", 10 | "- [A mapping of tests to filenames](./raw-data/Summary_of_CAMP_Cells.xlsx)\n", 11 | "- [A mapping of tests to battery design](./raw-data/Summary_of_builds.xlsx)\n", 12 | "- The actual raw data from the machines in MACCOR format\n", 13 | "- [A list of the cells used by Paulson et al.](./raw-data/known-cells.csv)\n", 14 | "This notebook combines them together into a single HDF5 file for each cell." 15 | ] 16 | }, 17 | { 18 | "cell_type": "code", 19 | "execution_count": 1, 20 | "id": "771937d3-8e99-453c-a0b9-36fede9eb29f", 21 | "metadata": {}, 22 | "outputs": [], 23 | "source": [ 24 | "from battdat.io.maccor import MACCORReader\n", 25 | "from battdat.schemas.battery import ElectrodeDescription, ElectrolyteDescription, BatteryDescription\n", 26 | "from battdat.schemas import BatteryMetadata\n", 27 | "from battdat.data import BatteryDataset\n", 28 | "from concurrent.futures import ProcessPoolExecutor, as_completed\n", 29 | "from datetime import datetime\n", 30 | "from shutil import rmtree\n", 31 | "from tqdm.auto import tqdm\n", 32 | "from pathlib import Path\n", 33 | "import pandas as pd\n", 34 | "import numpy as np" 35 | ] 36 | }, 37 | { 38 | "cell_type": "markdown", 39 | "id": "9bc16bdc-e822-4922-9cce-2b76fd3ac5a5", 40 | "metadata": {}, 41 | "source": [ 42 | "Configuration" 43 | ] 44 | }, 45 | { 46 | "cell_type": "code", 47 | "execution_count": 2, 48 | "id": "39741c3c-c44c-4771-ac5c-04ad44b03909", 49 | "metadata": {}, 50 | "outputs": [], 51 | "source": [ 52 | "data_path = Path('./data/raw-data/CAMP_data/')\n", 53 | "h5_path = Path('./data/hdf5')" 54 | ] 55 | }, 56 | { 57 | "cell_type": "code", 58 | "execution_count": 3, 59 | "id": "168c0307-5d1b-47b5-bcab-f49ca6557c7e", 60 | "metadata": {}, 61 | "outputs": [], 62 | "source": [ 63 | "h5_path.mkdir(exist_ok=True)" 64 | ] 65 | }, 66 | { 67 | "cell_type": "markdown", 68 | "id": "d324787c-c387-4dcf-aef0-63c2e3058e7d", 69 | "metadata": {}, 70 | "source": [ 71 | "## Load in the Mapping Spreadsheets\n", 72 | "These spreadsheets allow us to understand the content of the in our MACCOR files" 73 | ] 74 | }, 75 | { 76 | "cell_type": "code", 77 | "execution_count": 4, 78 | "id": "ab1aa3b1-9c90-4546-8472-81c78f116c3f", 79 | "metadata": {}, 80 | "outputs": [ 81 | { 82 | "data": { 83 | "text/html": [ 84 | "
\n", 85 | "\n", 98 | "\n", 99 | " \n", 100 | " \n", 101 | " \n", 102 | " \n", 103 | " \n", 104 | " \n", 105 | " \n", 106 | " \n", 107 | " \n", 108 | " \n", 109 | " \n", 110 | " \n", 111 | " \n", 112 | " \n", 113 | " \n", 114 | " \n", 115 | " \n", 116 | " \n", 117 | " \n", 118 | " \n", 119 | " \n", 120 | " \n", 121 | " \n", 122 | " \n", 123 | " \n", 124 | " \n", 125 | " \n", 126 | " \n", 127 | " \n", 128 | " \n", 129 | " \n", 130 | " \n", 131 | " \n", 132 | " \n", 133 | " \n", 134 | " \n", 135 | " \n", 136 | " \n", 137 | " \n", 138 | " \n", 139 | " \n", 140 | " \n", 141 | " \n", 142 | " \n", 143 | " \n", 144 | " \n", 145 | " \n", 146 | " \n", 147 | " \n", 148 | " \n", 149 | " \n", 150 | " \n", 151 | " \n", 152 | " \n", 153 | " \n", 154 | " \n", 155 | " \n", 156 | " \n", 157 | " \n", 158 | " \n", 159 | " \n", 160 | " \n", 161 | " \n", 162 | " \n", 163 | " \n", 164 | " \n", 165 | " \n", 166 | " \n", 167 | " \n", 168 | " \n", 169 | " \n", 170 | " \n", 171 | " \n", 172 | "
Unnamed: 0File NameOwnerBatchCell NumberCell TestStart TimeInitial Cycle NumberLast CycleTest TimeMax Capacity (Ah)Max EnergyMax Current (A)Min VoltageMax VoltageDate of TestPathFile CommentsProcedureNumber of Cycles in file
00ARGONNE #20_SET-LN3024-104-1a.001SETLN3024_10411a03/31/2016 16:05:310.00.01.16670.0000000.000000.0000003.3057153.306783\\t03/31/2016\\t\\tC:\\Data\\MIMS\\Backup\\ARGONNE #20\\SET-LN3024-1...SET-LN3024-104 Targray NCM811 [LN2086-32-4] ...ABRHV-NCM523-Form-4p1.000NCM 523 Formation T...0.0
11ARGONNE #20_SET-LN3024-104-1aa.001SETLN3024_10411aa03/31/2016 16:07:530.03.04942.67880.0030380.011790.0002422.9999244.300908\\t03/31/2016\\t\\tC:\\Data\\MIMS\\Backup\\ARGONNE #20\\SET-LN3024-1...SET-LN3024-104 Targray NCM811 [LN2086-32-4] ...ABRHV-NCM523-Form-4p3.000NCM 523 Formation T...3.0
\n", 173 | "
" 174 | ], 175 | "text/plain": [ 176 | " Unnamed: 0 File Name Owner Batch \\\n", 177 | "0 0 ARGONNE #20_SET-LN3024-104-1a.001 SET LN3024_104 \n", 178 | "1 1 ARGONNE #20_SET-LN3024-104-1aa.001 SET LN3024_104 \n", 179 | "\n", 180 | " Cell Number Cell Test Start Time Initial Cycle Number \\\n", 181 | "0 1 1a 03/31/2016 16:05:31 0.0 \n", 182 | "1 1 1aa 03/31/2016 16:07:53 0.0 \n", 183 | "\n", 184 | " Last Cycle Test Time Max Capacity (Ah) Max Energy Max Current (A) \\\n", 185 | "0 0.0 1.1667 0.000000 0.00000 0.000000 \n", 186 | "1 3.0 4942.6788 0.003038 0.01179 0.000242 \n", 187 | "\n", 188 | " Min Voltage Max Voltage Date of Test \\\n", 189 | "0 3.305715 3.306783 \\t03/31/2016\\t \n", 190 | "1 2.999924 4.300908 \\t03/31/2016\\t \n", 191 | "\n", 192 | " Path \\\n", 193 | "0 \\tC:\\Data\\MIMS\\Backup\\ARGONNE #20\\SET-LN3024-1... \n", 194 | "1 \\tC:\\Data\\MIMS\\Backup\\ARGONNE #20\\SET-LN3024-1... \n", 195 | "\n", 196 | " File Comments \\\n", 197 | "0 SET-LN3024-104 Targray NCM811 [LN2086-32-4] ... \n", 198 | "1 SET-LN3024-104 Targray NCM811 [LN2086-32-4] ... \n", 199 | "\n", 200 | " Procedure Number of Cycles in file \n", 201 | "0 ABRHV-NCM523-Form-4p1.000NCM 523 Formation T... 0.0 \n", 202 | "1 ABRHV-NCM523-Form-4p3.000NCM 523 Formation T... 3.0 " 203 | ] 204 | }, 205 | "execution_count": 4, 206 | "metadata": {}, 207 | "output_type": "execute_result" 208 | } 209 | ], 210 | "source": [ 211 | "test_descriptions = pd.read_excel('data/raw-data/Summary_of_CAMP_Cells.xlsx')\n", 212 | "test_descriptions.head(2)" 213 | ] 214 | }, 215 | { 216 | "cell_type": "code", 217 | "execution_count": 5, 218 | "id": "3f08bc60-33c2-4715-b46c-e8bbe882b353", 219 | "metadata": {}, 220 | "outputs": [ 221 | { 222 | "data": { 223 | "text/html": [ 224 | "
\n", 225 | "\n", 238 | "\n", 239 | " \n", 240 | " \n", 241 | " \n", 242 | " \n", 243 | " \n", 244 | " \n", 245 | " \n", 246 | " \n", 247 | " \n", 248 | " \n", 249 | " \n", 250 | " \n", 251 | " \n", 252 | " \n", 253 | " \n", 254 | " \n", 255 | " \n", 256 | " \n", 257 | " \n", 258 | " \n", 259 | " \n", 260 | " \n", 261 | " \n", 262 | " \n", 263 | " \n", 264 | " \n", 265 | " \n", 266 | " \n", 267 | " \n", 268 | " \n", 269 | " \n", 270 | " \n", 271 | " \n", 272 | " \n", 273 | " \n", 274 | " \n", 275 | " \n", 276 | " \n", 277 | " \n", 278 | " \n", 279 | " \n", 280 | " \n", 281 | " \n", 282 | " \n", 283 | " \n", 284 | " \n", 285 | " \n", 286 | " \n", 287 | " \n", 288 | " \n", 289 | " \n", 290 | " \n", 291 | " \n", 292 | " \n", 293 | " \n", 294 | " \n", 295 | " \n", 296 | " \n", 297 | " \n", 298 | " \n", 299 | " \n", 300 | " \n", 301 | " \n", 302 | " \n", 303 | " \n", 304 | " \n", 305 | " \n", 306 | " \n", 307 | " \n", 308 | " \n", 309 | " \n", 310 | " \n", 311 | " \n", 312 | " \n", 313 | " \n", 314 | " \n", 315 | "
buildanodecathodecathode_full_namecathode_supplieranode_full_nameanode_supplierelectrolyteelectrolyte_additivetotal_cathode_area (cm2)...number_interfacestarget_capacity (Ah)anode_thickness (um)anode_loading (mg/cm2)anode_porositycathode_thickness (um)cathode_loading (mg/cm2)cathode_porositytemperature (C)Notes
0B1CHE5050HE5050CommercialGraphiteCommercialGen 2NONENaN...NaN0.37586.05.7535.068.014.542.030.0NaN
1B1ACHE5050HE5050CommercialGraphiteCommercialGen 2NONENaN...NaN0.37586.05.7535.068.014.542.030.0NaN
\n", 316 | "

2 rows × 21 columns

\n", 317 | "
" 318 | ], 319 | "text/plain": [ 320 | " build anode cathode cathode_full_name cathode_supplier anode_full_name \\\n", 321 | "0 B1 C HE5050 HE5050 Commercial Graphite \n", 322 | "1 B1A C HE5050 HE5050 Commercial Graphite \n", 323 | "\n", 324 | " anode_supplier electrolyte electrolyte_additive total_cathode_area (cm2) \\\n", 325 | "0 Commercial Gen 2 NONE NaN \n", 326 | "1 Commercial Gen 2 NONE NaN \n", 327 | "\n", 328 | " ... number_interfaces target_capacity (Ah) anode_thickness (um) \\\n", 329 | "0 ... NaN 0.375 86.0 \n", 330 | "1 ... NaN 0.375 86.0 \n", 331 | "\n", 332 | " anode_loading (mg/cm2) anode_porosity cathode_thickness (um) \\\n", 333 | "0 5.75 35.0 68.0 \n", 334 | "1 5.75 35.0 68.0 \n", 335 | "\n", 336 | " cathode_loading (mg/cm2) cathode_porosity temperature (C) Notes \n", 337 | "0 14.5 42.0 30.0 NaN \n", 338 | "1 14.5 42.0 30.0 NaN \n", 339 | "\n", 340 | "[2 rows x 21 columns]" 341 | ] 342 | }, 343 | "execution_count": 5, 344 | "metadata": {}, 345 | "output_type": "execute_result" 346 | } 347 | ], 348 | "source": [ 349 | "cell_descriptions = pd.read_excel('data/raw-data/Summary_of_builds.xlsx')\n", 350 | "cell_descriptions.head(2)" 351 | ] 352 | }, 353 | { 354 | "cell_type": "markdown", 355 | "id": "9ba0fa7d-264c-4124-a3dc-ed06cbe02c08", 356 | "metadata": {}, 357 | "source": [ 358 | "## Get the Cells from the Paper\n", 359 | "Get the cells that are listed in a table from Noah Paulson, describing the source fo the data in Table S1 of [our paper](https://www.sciencedirect.com/science/article/pii/S0378775322001495#appsec1)." 360 | ] 361 | }, 362 | { 363 | "cell_type": "code", 364 | "execution_count": 6, 365 | "id": "441d814a-66ed-4885-bd8d-6a0b1f91ae91", 366 | "metadata": {}, 367 | "outputs": [ 368 | { 369 | "name": "stdout", 370 | "output_type": "stream", 371 | "text": [ 372 | "Loaded 300 known cells\n" 373 | ] 374 | } 375 | ], 376 | "source": [ 377 | "known_cells = pd.read_csv('data/raw-data/known-cells.csv')\n", 378 | "print(f'Loaded {len(known_cells)} known cells')" 379 | ] 380 | }, 381 | { 382 | "cell_type": "markdown", 383 | "id": "f6f2f0b3-03eb-4711-b634-6dc2d14705f6", 384 | "metadata": {}, 385 | "source": [ 386 | "Go from filename to batch name and cell number" 387 | ] 388 | }, 389 | { 390 | "cell_type": "code", 391 | "execution_count": 7, 392 | "id": "7bba0429-b987-451f-ae6f-33f01eb16bcd", 393 | "metadata": {}, 394 | "outputs": [], 395 | "source": [ 396 | "known_cells['cell_number'] = known_cells['cellname'].str.split(\"_\").apply(lambda x: x[2])\n", 397 | "known_cells['batch'] = known_cells['cellname'].str.split(\"_\").apply(lambda x: x[1])" 398 | ] 399 | }, 400 | { 401 | "cell_type": "markdown", 402 | "id": "bb0f7ec4-8be7-49e8-842a-83504794042f", 403 | "metadata": {}, 404 | "source": [ 405 | "## Filter down to best-documented cells\n", 406 | "Get only the test descriptions where we have the \"Batch\" described in the cell descriptions" 407 | ] 408 | }, 409 | { 410 | "cell_type": "code", 411 | "execution_count": 8, 412 | "id": "64e14d71-9ddf-4bf1-bb58-bd40c8051b6c", 413 | "metadata": {}, 414 | "outputs": [], 415 | "source": [ 416 | "is_documented = test_descriptions['Batch'].apply(lambda x: x in set(cell_descriptions['build']))" 417 | ] 418 | }, 419 | { 420 | "cell_type": "code", 421 | "execution_count": 9, 422 | "id": "da34a048-fd2b-4a35-b866-46cd3470069e", 423 | "metadata": {}, 424 | "outputs": [ 425 | { 426 | "name": "stdout", 427 | "output_type": "stream", 428 | "text": [ 429 | "Found descriptions for 3544/8618 tests\n" 430 | ] 431 | } 432 | ], 433 | "source": [ 434 | "print(f'Found descriptions for {is_documented.sum()}/{len(is_documented)} tests')" 435 | ] 436 | }, 437 | { 438 | "cell_type": "code", 439 | "execution_count": 10, 440 | "id": "b1ca600b-3449-4f3a-9822-f3c8dfc70612", 441 | "metadata": {}, 442 | "outputs": [], 443 | "source": [ 444 | "test_descriptions = test_descriptions[is_documented]" 445 | ] 446 | }, 447 | { 448 | "cell_type": "code", 449 | "execution_count": 11, 450 | "id": "edbb3383-a6b0-4cbc-9ad1-4b4646d9267d", 451 | "metadata": {}, 452 | "outputs": [ 453 | { 454 | "name": "stdout", 455 | "output_type": "stream", 456 | "text": [ 457 | "There is a total of 645 unique cells\n" 458 | ] 459 | } 460 | ], 461 | "source": [ 462 | "print(f'There is a total of {len(test_descriptions[[\"Batch\", \"Cell Number\"]].value_counts())} unique cells')" 463 | ] 464 | }, 465 | { 466 | "cell_type": "markdown", 467 | "id": "85e4e3e0-2008-435f-abab-b2164ca773f6", 468 | "metadata": {}, 469 | "source": [ 470 | "## Remove Formation Tests\n", 471 | "Get tests that do not have \"formation\" in the procedure" 472 | ] 473 | }, 474 | { 475 | "cell_type": "code", 476 | "execution_count": 12, 477 | "id": "5be1ba5c-331a-492a-9197-a1207297e7cc", 478 | "metadata": {}, 479 | "outputs": [], 480 | "source": [ 481 | "is_formation = test_descriptions['Procedure'].str.lower().str.contains('formation')" 482 | ] 483 | }, 484 | { 485 | "cell_type": "code", 486 | "execution_count": 13, 487 | "id": "a13dc911-c26f-4317-8995-fe1656fae274", 488 | "metadata": {}, 489 | "outputs": [ 490 | { 491 | "name": "stdout", 492 | "output_type": "stream", 493 | "text": [ 494 | "Found 667/3544 formation experiments\n" 495 | ] 496 | } 497 | ], 498 | "source": [ 499 | "print(f'Found {is_formation.sum()}/{len(is_formation)} formation experiments')" 500 | ] 501 | }, 502 | { 503 | "cell_type": "code", 504 | "execution_count": 14, 505 | "id": "d3ce5407-abbe-443d-942b-2e0a8b46c249", 506 | "metadata": {}, 507 | "outputs": [ 508 | { 509 | "name": "stdout", 510 | "output_type": "stream", 511 | "text": [ 512 | "There is a total of 602 unique cells\n" 513 | ] 514 | } 515 | ], 516 | "source": [ 517 | "test_descriptions = test_descriptions[~is_formation]\n", 518 | "print(f'There is a total of {len(test_descriptions[[\"Batch\", \"Cell Number\"]].value_counts())} unique cells')" 519 | ] 520 | }, 521 | { 522 | "cell_type": "markdown", 523 | "id": "d2d3428a-2d4c-4676-a9f5-be26b8f4e00d", 524 | "metadata": {}, 525 | "source": [ 526 | "## Create a Function to Document Cell\n", 527 | "Build a batdata-compliant metadata for a test given the information from the \"test descriptons\" and \"cell descriptions\" spreadsheets.\n", 528 | "This new format will contain the same information, but mapped to community-agreed-upon names for concepts" 529 | ] 530 | }, 531 | { 532 | "cell_type": "markdown", 533 | "id": "c438140b-c927-4802-ba23-d2c41332f695", 534 | "metadata": {}, 535 | "source": [ 536 | "First get an example record" 537 | ] 538 | }, 539 | { 540 | "cell_type": "code", 541 | "execution_count": 15, 542 | "id": "512f01bb-31ab-4ddd-9519-cdaed481a317", 543 | "metadata": {}, 544 | "outputs": [ 545 | { 546 | "data": { 547 | "text/plain": [ 548 | "Unnamed: 0 205\n", 549 | "File Name ARGONNE_10_CFF-B12A-P1b.008\n", 550 | "Owner CFF\n", 551 | "Batch B12A\n", 552 | "Cell Number 1\n", 553 | "Cell Test P1b\n", 554 | "Start Time 10:51:04\n", 555 | "Initial Cycle Number 0.0\n", 556 | "Last Cycle 12.0\n", 557 | "Test Time 50.202\n", 558 | "Max Capacity (Ah) 0.0\n", 559 | "Max Energy 0.0\n", 560 | "Max Current (A) 0.000076\n", 561 | "Min Voltage 0.0\n", 562 | "Max Voltage 10.0\n", 563 | "Date of Test \\t09/20/2012\\t \n", 564 | "Path \\tB:\\Data\\MIMS\\Backup\\ARGONNE_10\\CFF-B12A-P1b....\n", 565 | "File Comments \n", 566 | "Procedure CFF-Rate-4p5V.000Rate Capability Test at 0.2C...\n", 567 | "Number of Cycles in file 12.0\n", 568 | "Name: 205, dtype: object" 569 | ] 570 | }, 571 | "execution_count": 15, 572 | "metadata": {}, 573 | "output_type": "execute_result" 574 | } 575 | ], 576 | "source": [ 577 | "record = test_descriptions.iloc[0]\n", 578 | "record" 579 | ] 580 | }, 581 | { 582 | "cell_type": "markdown", 583 | "id": "324f9f1b-fa13-4c0b-97b6-812dd9e5437e", 584 | "metadata": {}, 585 | "source": [ 586 | "Look up the cell metadata" 587 | ] 588 | }, 589 | { 590 | "cell_type": "code", 591 | "execution_count": 16, 592 | "id": "437a344a-1914-4357-8b9b-660c1ef64e91", 593 | "metadata": {}, 594 | "outputs": [ 595 | { 596 | "data": { 597 | "text/plain": [ 598 | "build B12A\n", 599 | "anode C\n", 600 | "cathode HE5050\n", 601 | "cathode_full_name HE5050\n", 602 | "cathode_supplier Commercial\n", 603 | "anode_full_name Graphite\n", 604 | "anode_supplier Commercial\n", 605 | "electrolyte Gen 2\n", 606 | "electrolyte_additive NONE\n", 607 | "total_cathode_area (cm2) 169.2\n", 608 | "number_layers 13.0\n", 609 | "number_interfaces 12.0\n", 610 | "target_capacity (Ah) 0.3\n", 611 | "anode_thickness (um) 86.0\n", 612 | "anode_loading (mg/cm2) 5.75\n", 613 | "anode_porosity 35.0\n", 614 | "cathode_thickness (um) 63.0\n", 615 | "cathode_loading (mg/cm2) 14.3\n", 616 | "cathode_porosity 38.0\n", 617 | "temperature (C) 30.0\n", 618 | "Notes NaN\n", 619 | "Name: 21, dtype: object" 620 | ] 621 | }, 622 | "execution_count": 16, 623 | "metadata": {}, 624 | "output_type": "execute_result" 625 | } 626 | ], 627 | "source": [ 628 | "cell_metadata = cell_descriptions.query(f'build == \"{record[\"Batch\"]}\"').iloc[0]\n", 629 | "cell_metadata" 630 | ] 631 | }, 632 | { 633 | "cell_type": "markdown", 634 | "id": "b675e4a7-f964-4f97-abdc-1f03bbeea718", 635 | "metadata": {}, 636 | "source": [ 637 | "We just need to rearrange this data into the structure provided by `battdat`." 638 | ] 639 | }, 640 | { 641 | "cell_type": "code", 642 | "execution_count": 17, 643 | "id": "89b6bc5d-0d8f-41ae-b43e-5a548b02aafd", 644 | "metadata": {}, 645 | "outputs": [ 646 | { 647 | "name": "stdout", 648 | "output_type": "stream", 649 | "text": [ 650 | "{\n", 651 | " \"name\": \"HE5050\",\n", 652 | " \"supplier\": \"Commercial\",\n", 653 | " \"product\": null,\n", 654 | " \"thickness\": 63.0,\n", 655 | " \"area\": 169.2,\n", 656 | " \"loading\": 14.3,\n", 657 | " \"porosity\": 38.0\n", 658 | "}\n" 659 | ] 660 | } 661 | ], 662 | "source": [ 663 | "cathode_metadata = ElectrodeDescription(\n", 664 | " name=cell_metadata['cathode'],\n", 665 | " supplier=cell_metadata['cathode_supplier'],\n", 666 | " thickness=cell_metadata['cathode_thickness (um)'],\n", 667 | " area=cell_metadata['total_cathode_area (cm2)'],\n", 668 | " loading=cell_metadata['cathode_loading (mg/cm2)'],\n", 669 | " porosity=cell_metadata['cathode_porosity']\n", 670 | ")\n", 671 | "print(cathode_metadata.model_dump_json(indent=2))" 672 | ] 673 | }, 674 | { 675 | "cell_type": "markdown", 676 | "id": "794f04ba-e0bf-4975-bd76-e59ab2093242", 677 | "metadata": {}, 678 | "source": [ 679 | "We put all of this into a single function for convenience" 680 | ] 681 | }, 682 | { 683 | "cell_type": "code", 684 | "execution_count": 18, 685 | "id": "c3791f19-c058-46c2-b66f-7aa2d491daae", 686 | "metadata": {}, 687 | "outputs": [ 688 | { 689 | "data": { 690 | "text/plain": [ 691 | "{'layer_count': 13,\n", 692 | " 'anode': {'name': 'C',\n", 693 | " 'supplier': 'Commercial',\n", 694 | " 'product': 'Graphite',\n", 695 | " 'thickness': 86.0,\n", 696 | " 'loading': 5.75,\n", 697 | " 'porosity': 35.0},\n", 698 | " 'cathode': {'name': 'HE5050',\n", 699 | " 'supplier': 'Commercial',\n", 700 | " 'thickness': 63.0,\n", 701 | " 'area': 169.2,\n", 702 | " 'loading': 14.3,\n", 703 | " 'porosity': 38.0},\n", 704 | " 'electrolyte': {'name': 'Gen 2', 'additives': []},\n", 705 | " 'nominal_capacity': 0.3}" 706 | ] 707 | }, 708 | "execution_count": 18, 709 | "metadata": {}, 710 | "output_type": "execute_result" 711 | } 712 | ], 713 | "source": [ 714 | "def describe_cell(test_record: dict) -> BatteryMetadata:\n", 715 | " \"\"\"Create a single metadata record\n", 716 | " \n", 717 | " Args:\n", 718 | " test_record: Record for a certain test\n", 719 | " Returns:\n", 720 | " Formatted metadata for the battery\n", 721 | " \"\"\"\n", 722 | " \n", 723 | " # Match cell description\n", 724 | " matches = cell_descriptions.query(f'build == \"{test_record[\"Batch\"]}\"')\n", 725 | " if len(matches) > 1: \n", 726 | " print(f'WARNING: Found {len(matches)} descriptions for build=\"{test_record[\"Batch\"]}\". Picking the first')\n", 727 | " cell_metadata = matches.iloc[0].to_dict()\n", 728 | " \n", 729 | " # Replace NaNs with None so that we know they are missing\n", 730 | " cell_metadata = dict((k, None if isinstance(v, float) and np.isnan(v) else v) for k, v in cell_metadata.items())\n", 731 | "\n", 732 | " # Describe the electrodes\n", 733 | " cathode_metadata = None\n", 734 | " anode_metadata = None\n", 735 | " if cell_metadata['cathode'] != \"unknown\":\n", 736 | " cathode_metadata = ElectrodeDescription(\n", 737 | " name=cell_metadata['cathode'],\n", 738 | " supplier=cell_metadata['cathode_supplier'],\n", 739 | " thickness=cell_metadata['cathode_thickness (um)'],\n", 740 | " area=cell_metadata['total_cathode_area (cm2)'],\n", 741 | " loading=cell_metadata['cathode_loading (mg/cm2)'],\n", 742 | " porosity=cell_metadata['cathode_porosity']\n", 743 | " )\n", 744 | " if cell_metadata['anode'] != \"unknown\":\n", 745 | " anode_metadata = ElectrodeDescription(\n", 746 | " name=cell_metadata['anode'],\n", 747 | " supplier=cell_metadata['anode_supplier'],\n", 748 | " product=cell_metadata['anode_full_name'],\n", 749 | " thickness=cell_metadata['anode_thickness (um)'],\n", 750 | " loading=cell_metadata['anode_loading (mg/cm2)'],\n", 751 | " porosity=cell_metadata['anode_porosity']\n", 752 | " )\n", 753 | "\n", 754 | " # Get the electrolyte information\n", 755 | " additives = cell_metadata['electrolyte_additive']\n", 756 | " additives = [] if additives == 'NONE' else [{'name': x.strip()} for x in additives.split(\",\")]\n", 757 | " electrolyte = ElectrolyteDescription(\n", 758 | " name=cell_metadata['electrolyte'],\n", 759 | " additives=additives\n", 760 | " )\n", 761 | " \n", 762 | " # Combine to form a cell description\n", 763 | " battery = BatteryDescription(\n", 764 | " anode=anode_metadata,\n", 765 | " cathode=cathode_metadata,\n", 766 | " electrolyte=electrolyte,\n", 767 | " )\n", 768 | " if cell_metadata['target_capacity (Ah)'] != \"unknown\":\n", 769 | " battery.nominal_capacity = cell_metadata['target_capacity (Ah)']\n", 770 | " if cell_metadata['number_layers'] != \"unknown\":\n", 771 | " value = cell_metadata['number_layers']\n", 772 | " battery.layer_count = int(value) if value is not None else value\n", 773 | " return battery\n", 774 | "describe_cell(record).dict(exclude_unset=True)" 775 | ] 776 | }, 777 | { 778 | "cell_type": "markdown", 779 | "id": "a00fc597-4187-443e-9629-6cbabb54519b", 780 | "metadata": {}, 781 | "source": [ 782 | "## Load in an Example Test\n", 783 | "Tests are stored in MACCOR format. Let's load one in to see how the data looks.\n" 784 | ] 785 | }, 786 | { 787 | "cell_type": "code", 788 | "execution_count": 19, 789 | "id": "f6a4c1c3-ee46-435f-929f-dcaadd941759", 790 | "metadata": {}, 791 | "outputs": [], 792 | "source": [ 793 | "reader = MACCORReader(ignore_time=True) # The datetime column is a problem in a few files" 794 | ] 795 | }, 796 | { 797 | "cell_type": "code", 798 | "execution_count": 20, 799 | "id": "146a2fb6-81ba-4db9-8958-1d3fcb9eabfc", 800 | "metadata": {}, 801 | "outputs": [ 802 | { 803 | "data": { 804 | "text/html": [ 805 | "
\n", 806 | "\n", 819 | "\n", 820 | " \n", 821 | " \n", 822 | " \n", 823 | " \n", 824 | " \n", 825 | " \n", 826 | " \n", 827 | " \n", 828 | " \n", 829 | " \n", 830 | " \n", 831 | " \n", 832 | " \n", 833 | " \n", 834 | " \n", 835 | " \n", 836 | " \n", 837 | " \n", 838 | " \n", 839 | " \n", 840 | " \n", 841 | " \n", 842 | " \n", 843 | " \n", 844 | " \n", 845 | " \n", 846 | " \n", 847 | " \n", 848 | " \n", 849 | " \n", 850 | " \n", 851 | " \n", 852 | " \n", 853 | " \n", 854 | " \n", 855 | " \n", 856 | " \n", 857 | " \n", 858 | " \n", 859 | " \n", 860 | "
cycle_numberfile_numbertest_timestatecurrentvoltagestep_indexmethodsubstep_index
0000.000ChargingState.unknown0.00.0735480ControlMethod.constant_current0
1000.162ChargingState.unknown0.00.0000000ControlMethod.constant_current0
\n", 861 | "
" 862 | ], 863 | "text/plain": [ 864 | " cycle_number file_number test_time state current \\\n", 865 | "0 0 0 0.000 ChargingState.unknown 0.0 \n", 866 | "1 0 0 0.162 ChargingState.unknown 0.0 \n", 867 | "\n", 868 | " voltage step_index method substep_index \n", 869 | "0 0.073548 0 ControlMethod.constant_current 0 \n", 870 | "1 0.000000 0 ControlMethod.constant_current 0 " 871 | ] 872 | }, 873 | "execution_count": 20, 874 | "metadata": {}, 875 | "output_type": "execute_result" 876 | } 877 | ], 878 | "source": [ 879 | "data = reader.read_file(data_path / record['File Name'])\n", 880 | "data.head(2)" 881 | ] 882 | }, 883 | { 884 | "cell_type": "markdown", 885 | "id": "9d2966ef-ce9b-420f-86d0-320de8295ac2", 886 | "metadata": {}, 887 | "source": [ 888 | "## Process all known cells\n", 889 | "Loop through everything and save it into HDF5 format" 890 | ] 891 | }, 892 | { 893 | "cell_type": "code", 894 | "execution_count": 21, 895 | "id": "55d5cd2c-2d4a-41e2-a138-6da9bcc3bdf0", 896 | "metadata": {}, 897 | "outputs": [], 898 | "source": [ 899 | "for path in h5_path.iterdir():\n", 900 | " if path.is_dir():\n", 901 | " rmtree(path)" 902 | ] 903 | }, 904 | { 905 | "cell_type": "markdown", 906 | "id": "28af2ecc-209b-4ba6-b776-e1b4adb09a0b", 907 | "metadata": {}, 908 | "source": [ 909 | "Make a function we can run in parallel" 910 | ] 911 | }, 912 | { 913 | "cell_type": "code", 914 | "execution_count": 22, 915 | "id": "694c67c7-9576-4408-8128-684689e71605", 916 | "metadata": {}, 917 | "outputs": [], 918 | "source": [ 919 | "def process_cell(extractor: MACCORReader, files: list[str], metadata: BatteryMetadata, path: str):\n", 920 | " \"\"\"Convert then save to HDF5 format\"\"\"\n", 921 | " \n", 922 | " data = extractor.read_dataset(files, metadata=metadata)\n", 923 | " for col in data.raw_data.columns:\n", 924 | " if data.raw_data[col].dtype == np.float64:\n", 925 | " data.raw_data[col] = data.raw_data[col].astype(np.float32)\n", 926 | " data.validate()\n", 927 | " data.to_hdf(path, complevel=9)" 928 | ] 929 | }, 930 | { 931 | "cell_type": "markdown", 932 | "id": "befdcbd2-f624-48ad-b33f-c24ee588356a", 933 | "metadata": {}, 934 | "source": [ 935 | "Parse the test date" 936 | ] 937 | }, 938 | { 939 | "cell_type": "code", 940 | "execution_count": 23, 941 | "id": "37a44ad7-e810-40ba-bed1-6aaf3f56872a", 942 | "metadata": {}, 943 | "outputs": [], 944 | "source": [ 945 | "test_descriptions['start_date'] = test_descriptions['Date of Test'].apply(lambda x: datetime.strptime(x.strip(), '%m/%d/%Y'))" 946 | ] 947 | }, 948 | { 949 | "cell_type": "markdown", 950 | "id": "f45c5d2e-d925-4dd4-bff5-ff117880cad7", 951 | "metadata": {}, 952 | "source": [ 953 | "Run for all cells" 954 | ] 955 | }, 956 | { 957 | "cell_type": "code", 958 | "execution_count": 24, 959 | "id": "4fdfdbc8-6be0-49e6-b774-f588c422840b", 960 | "metadata": {}, 961 | "outputs": [ 962 | { 963 | "data": { 964 | "application/vnd.jupyter.widget-view+json": { 965 | "model_id": "5e7a79ae04d44b8585dcddc99eeea92f", 966 | "version_major": 2, 967 | "version_minor": 0 968 | }, 969 | "text/plain": [ 970 | " 0%| | 0/602 [00:00