├── LICENSE ├── README.md ├── digitalTwin_Li_ION.ipynb └── discharge.csv /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 JAVIER MARIN 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![image](https://github.com/user-attachments/assets/121e0566-a380-4686-b0ff-41cd68424d43) 2 | 3 | # Digital-Twin-in-python 4 | In this repo we will show how to build a simple but useful Digital Twin using python. Our asset will be a Li-ion battery. This Digital Twin will allow us to model and predict batteries behavior and can be included in any virtual asset management process. 5 | https://medium.com/towards-data-science/how-to-build-a-digital-twin-b31058fd5d3e 6 | -------------------------------------------------------------------------------- /digitalTwin_Li_ION.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "nbformat": 4, 3 | "nbformat_minor": 0, 4 | "metadata": { 5 | "kernelspec": { 6 | "display_name": "Python 3", 7 | "language": "python", 8 | "name": "python3" 9 | }, 10 | "language_info": { 11 | "codemirror_mode": { 12 | "name": "ipython", 13 | "version": 3 14 | }, 15 | "file_extension": ".py", 16 | "mimetype": "text/x-python", 17 | "name": "python", 18 | "nbconvert_exporter": "python", 19 | "pygments_lexer": "ipython3", 20 | "version": "3.7.1" 21 | }, 22 | "colab": { 23 | "name": "digitalTwin_Li_ION_.ipynb", 24 | "provenance": [], 25 | "collapsed_sections": [] 26 | } 27 | }, 28 | "cells": [ 29 | { 30 | "cell_type": "markdown", 31 | "metadata": { 32 | "id": "AEil20eqS2So" 33 | }, 34 | "source": [ 35 | "# Hybrid digital twin of a Li-ion battery\n" 36 | ] 37 | }, 38 | { 39 | "cell_type": "code", 40 | "metadata": { 41 | "id": "ZYaB7HSTS2Ss" 42 | }, 43 | "source": [ 44 | "import pandas as pd\n", 45 | "import numpy as np\n", 46 | "from sklearn.model_selection import train_test_split\n", 47 | "import keras\n", 48 | "import tensorflow as tf\n", 49 | "from keras.models import Sequential\n", 50 | "from keras.layers import LSTM, Dense\n", 51 | "from keras.optimizers import SGD\n", 52 | "from tensorflow.keras.layers import Dropout\n", 53 | "from matplotlib import pyplot as plt\n", 54 | "import plotly.express as px\n", 55 | "from plotly.subplots import make_subplots\n", 56 | "import plotly.graph_objects as go\n", 57 | "import plotly.figure_factory as ff" 58 | ], 59 | "execution_count": null, 60 | "outputs": [] 61 | }, 62 | { 63 | "cell_type": "markdown", 64 | "metadata": { 65 | "id": "NIhtsIQHS2St" 66 | }, 67 | "source": [ 68 | "### 1. Load experimental data" 69 | ] 70 | }, 71 | { 72 | "cell_type": "code", 73 | "metadata": { 74 | "id": "UrmwSwuMS_iY" 75 | }, 76 | "source": [ 77 | "df = pd.read_csv('discharge.csv')" 78 | ], 79 | "execution_count": null, 80 | "outputs": [] 81 | }, 82 | { 83 | "cell_type": "code", 84 | "metadata": { 85 | "id": "rWmpbfS7W9zV" 86 | }, 87 | "source": [ 88 | "df = df[df['Battery'] == 'B0005']\n", 89 | "df = df[df['Temperature_measured'] > 36] #choose battery B0005\n", 90 | "#df['Time'] =pd.to_datetime(df['Time'], unit='s')\n", 91 | "dfb = df.groupby(['id_cycle']).max()\n", 92 | "dfb['Cumulated_T'] = dfb['Time'].cumsum()" 93 | ], 94 | "execution_count": null, 95 | "outputs": [] 96 | }, 97 | { 98 | "cell_type": "code", 99 | "metadata": { 100 | "colab": { 101 | "base_uri": "https://localhost:8080/", 102 | "height": 1000 103 | }, 104 | "id": "CcBEYPoQC9SE", 105 | "outputId": "7e16313e-8ccf-4422-a787-a8dba8e29a22" 106 | }, 107 | "source": [ 108 | "import plotly.express as px\n", 109 | "fig = px.scatter_matrix(dfb.drop(columns=['Time','type', 'ambient_temperature', \n", 110 | " 'time', 'Battery']), \n", 111 | " )\n", 112 | "fig.update_traces(marker=dict(size=2,\n", 113 | " color='crimson',\n", 114 | " symbol='square')),\n", 115 | "fig.update_traces(diagonal_visible=False)\n", 116 | "fig.update_layout(\n", 117 | " title='Battery dataset',\n", 118 | " width=900,\n", 119 | " height=1200,\n", 120 | ")\n", 121 | "fig.update_layout({'plot_bgcolor': '#f2f8fd',\n", 122 | " 'paper_bgcolor': 'white',}, \n", 123 | " template='plotly_white',\n", 124 | " font=dict(size=7)\n", 125 | " )\n", 126 | "\n", 127 | "fig.show()" 128 | ], 129 | "execution_count": null, 130 | "outputs": [ 131 | { 132 | "output_type": "display_data", 133 | "data": { 134 | "text/html": [ 135 | "\n", 136 | "\n", 137 | "\n", 138 | "
\n", 139 | " \n", 140 | " \n", 141 | " \n", 142 | "
\n", 143 | " \n", 181 | "
\n", 182 | "\n", 183 | "" 184 | ] 185 | }, 186 | "metadata": { 187 | "tags": [] 188 | } 189 | } 190 | ] 191 | }, 192 | { 193 | "cell_type": "code", 194 | "metadata": { 195 | "colab": { 196 | "base_uri": "https://localhost:8080/", 197 | "height": 542 198 | }, 199 | "id": "bmLhWPg6Dnzk", 200 | "outputId": "1a350f6d-dd02-4b61-fbb8-404ba9510ecc" 201 | }, 202 | "source": [ 203 | "fig = go.Figure()\n", 204 | "\n", 205 | "fig.add_trace(go.Scatter(x=dfb['Cumulated_T']/3600, \n", 206 | " y=dfb['Capacity'],\n", 207 | " mode='lines',\n", 208 | " name='Capacity',\n", 209 | " marker_size=3, \n", 210 | " line=dict(color='crimson', width=3) \n", 211 | " ))\n", 212 | "fig.update_layout(\n", 213 | " title=\"Battery discharge capacity\",\n", 214 | " xaxis_title=\"Working time [hours]\",\n", 215 | " yaxis_title=f\"Battery capacity in Ahr\"\n", 216 | " )\n", 217 | "fig.update_layout({'plot_bgcolor': '#f2f8fd',\n", 218 | " 'paper_bgcolor': 'white',}, \n", 219 | " template='plotly_white')" 220 | ], 221 | "execution_count": null, 222 | "outputs": [ 223 | { 224 | "output_type": "display_data", 225 | "data": { 226 | "text/html": [ 227 | "\n", 228 | "\n", 229 | "\n", 230 | "
\n", 231 | " \n", 232 | " \n", 233 | " \n", 234 | "
\n", 235 | " \n", 273 | "
\n", 274 | "\n", 275 | "" 276 | ] 277 | }, 278 | "metadata": { 279 | "tags": [] 280 | } 281 | } 282 | ] 283 | }, 284 | { 285 | "cell_type": "markdown", 286 | "metadata": { 287 | "id": "z4khX4N5wys7" 288 | }, 289 | "source": [ 290 | "### 2. Define a physical model" 291 | ] 292 | }, 293 | { 294 | "cell_type": "markdown", 295 | "metadata": { 296 | "id": "NhiMrtrkcpiR" 297 | }, 298 | "source": [ 299 | "Physical model according [1]. The basic equation is:
\n", 300 | "
$L = 1 − (1 − L' )e^{-f_d}$

\n", 301 | "\n", 302 | "Where $L$ is the battery lifetime and $L'$ the initial battery lifetime. $f_d$ is a Linearized degradation rate per unit time and per cycle. It can be described as:
\n", 303 | "
$f_d = f_d(t, δ, σ, T_c)$

\n", 304 | "\n", 305 | "where $t$ is charging time, δ is the cycle depth of discharge, σ is the cycle average state of charge, and $T_c$ is cell temperature. The equation for baatery capacity could be written as follows:
\n", 306 | "
$C = C_0e^{f_d}$

\n", 307 | "\n", 308 | "We have empirically found that $f_d$ aproximates to:\n", 309 | "
$f_d = \\frac{kT_Ci}{t}$

\n", 310 | "\n", 311 | "where $k= $ 0.13, $i$ the cycle number and $t$ the charge time for every cycle.\n", 312 | "\n", 313 | "- [1] *Xu, Bolun & Oudalov, Alexandre & Ulbig, Andreas & Andersson, Göran & Kirschen, D.s. (2016). Modeling of Lithium-Ion Battery Degradation for Cell Life Assessment. IEEE Transactions on Smart Grid. 99. 1-1. 10.1109/TSG.2016.2578950.* " 314 | ] 315 | }, 316 | { 317 | "cell_type": "code", 318 | "metadata": { 319 | "id": "rTU_ImZ1rPLf" 320 | }, 321 | "source": [ 322 | "from math import e\n", 323 | "L = (dfb['Capacity']-dfb['Capacity'].iloc[0:1].values[0])/-dfb['Capacity'].iloc[0:1].values[0]\n", 324 | "K = 0.13\n", 325 | "L_1 = 1-e**(-K*dfb.index*dfb['Temperature_measured']/(dfb['Time']))\n", 326 | "dfb['C. Capacity'] = -(L_1*dfb['Capacity'].iloc[0:1].values[0]) + dfb['Capacity'].iloc[0:1].values[0]" 327 | ], 328 | "execution_count": null, 329 | "outputs": [] 330 | }, 331 | { 332 | "cell_type": "code", 333 | "metadata": { 334 | "colab": { 335 | "base_uri": "https://localhost:8080/", 336 | "height": 542 337 | }, 338 | "id": "l8bmHjJQCBEe", 339 | "outputId": "0030b53e-9dde-449a-9f29-ba6606820c2e" 340 | }, 341 | "source": [ 342 | "fig = go.Figure()\n", 343 | "\n", 344 | "fig.add_trace(go.Scatter(x=dfb.index, \n", 345 | " y=dfb['C. Capacity'],\n", 346 | " mode='lines',\n", 347 | " name='Physical model',\n", 348 | " line=dict(color='navy', \n", 349 | " width=2.5,\n", 350 | " )))\n", 351 | "\n", 352 | "fig.add_trace(go.Scatter(x=dfb.index, \n", 353 | " y=dfb['Capacity'],\n", 354 | " mode='markers',\n", 355 | " marker=dict(\n", 356 | " size=4,\n", 357 | " color='grey',\n", 358 | " symbol='cross'\n", 359 | " ),\n", 360 | " name='NASA dataset',\n", 361 | " line_color='navy'))\n", 362 | "fig.update_layout(\n", 363 | " title=\"Physical model comparison \",\n", 364 | " xaxis_title=\"Cycles\",\n", 365 | " yaxis_title=\"𝐶, Capacity [Ahr]\")\n", 366 | "\n", 367 | "fig.update_layout(legend=dict(\n", 368 | " yanchor=\"top\",\n", 369 | " y=0.9,\n", 370 | " xanchor=\"left\",\n", 371 | " x=0.8\n", 372 | "))\n", 373 | "\n", 374 | "fig.update_layout({'plot_bgcolor': '#f2f8fd',\n", 375 | " 'paper_bgcolor': 'white',}, \n", 376 | " template='plotly_white')" 377 | ], 378 | "execution_count": null, 379 | "outputs": [ 380 | { 381 | "output_type": "display_data", 382 | "data": { 383 | "text/html": [ 384 | "\n", 385 | "\n", 386 | "\n", 387 | "
\n", 388 | " \n", 389 | " \n", 390 | " \n", 391 | "
\n", 392 | " \n", 430 | "
\n", 431 | "\n", 432 | "" 433 | ] 434 | }, 435 | "metadata": { 436 | "tags": [] 437 | } 438 | } 439 | ] 440 | }, 441 | { 442 | "cell_type": "markdown", 443 | "metadata": { 444 | "id": "GaZ8opJqS2Sx" 445 | }, 446 | "source": [ 447 | "### 3. Compare experimental data with physical model" 448 | ] 449 | }, 450 | { 451 | "cell_type": "code", 452 | "metadata": { 453 | "colab": { 454 | "base_uri": "https://localhost:8080/" 455 | }, 456 | "id": "0jRN6RyZxMvG", 457 | "outputId": "22590dfb-0d36-453b-a065-9117fe84aa81" 458 | }, 459 | "source": [ 460 | "# Mean Absolute Error\n", 461 | "M = pd.DataFrame()\n", 462 | "S = pd.DataFrame()\n", 463 | "def MAE(M,S): \n", 464 | " return np.sum(S-M)/len(S)\n", 465 | "\n", 466 | "print(f'Mean Absolute Error =', round(MAE(dfb['Capacity'], dfb['C. Capacity']), 3))" 467 | ], 468 | "execution_count": null, 469 | "outputs": [ 470 | { 471 | "output_type": "stream", 472 | "text": [ 473 | "Mean Absolute Error = 0.004\n" 474 | ], 475 | "name": "stdout" 476 | } 477 | ] 478 | }, 479 | { 480 | "cell_type": "markdown", 481 | "metadata": { 482 | "id": "MQj67klQS2S0" 483 | }, 484 | "source": [ 485 | "### 4. Hybrid digital twin " 486 | ] 487 | }, 488 | { 489 | "cell_type": "code", 490 | "metadata": { 491 | "id": "P4tOwrSYS2S0" 492 | }, 493 | "source": [ 494 | "#Define inputs and outputs\n", 495 | "X_in = dfb['C. Capacity'] # input: the simulation time series\n", 496 | "X_out = dfb['Capacity'] - dfb['C. Capacity'] # output: difference between measurement and simulation\n", 497 | "\n", 498 | "X_in_train, X_in_test, X_out_train, X_out_test = train_test_split(X_in, X_out, test_size=0.33)" 499 | ], 500 | "execution_count": null, 501 | "outputs": [] 502 | }, 503 | { 504 | "cell_type": "code", 505 | "metadata": { 506 | "colab": { 507 | "base_uri": "https://localhost:8080/" 508 | }, 509 | "id": "r4RzAol2J8or", 510 | "outputId": "d096e003-658c-4c95-c759-3e315ade8d63" 511 | }, 512 | "source": [ 513 | "X_in_train.shape" 514 | ], 515 | "execution_count": null, 516 | "outputs": [ 517 | { 518 | "output_type": "execute_result", 519 | "data": { 520 | "text/plain": [ 521 | "(112,)" 522 | ] 523 | }, 524 | "metadata": { 525 | "tags": [] 526 | }, 527 | "execution_count": 10 528 | } 529 | ] 530 | }, 531 | { 532 | "cell_type": "code", 533 | "metadata": { 534 | "id": "dnfxtbRqF1V6" 535 | }, 536 | "source": [ 537 | "#The Dense function in Keras constructs a fully connected neural network layer, automatically initializing the weights as biases.\n", 538 | "#First hidden layer\n", 539 | "model = Sequential()\n", 540 | "model.add(Dense(64, activation='relu'))\n", 541 | "model.add(Dense(64, activation='relu'))\n", 542 | "model.add(Dense(1))" 543 | ], 544 | "execution_count": null, 545 | "outputs": [] 546 | }, 547 | { 548 | "cell_type": "code", 549 | "metadata": { 550 | "colab": { 551 | "base_uri": "https://localhost:8080/" 552 | }, 553 | "id": "xpWJebynF6uZ", 554 | "outputId": "bcc5ab35-7be1-491c-a89f-db29f12638e5" 555 | }, 556 | "source": [ 557 | "epochs = 100\n", 558 | "loss = \"mse\"\n", 559 | "model.compile(optimizer='adam',\n", 560 | " loss=loss,\n", 561 | " metrics=['mae'], #Mean Absolute Error\n", 562 | " )\n", 563 | "history = model.fit(X_in_train, X_out_train, \n", 564 | " shuffle=True, \n", 565 | " epochs=epochs,\n", 566 | " batch_size=20,\n", 567 | " validation_data=(X_in_test, X_out_test), \n", 568 | " verbose=1)" 569 | ], 570 | "execution_count": null, 571 | "outputs": [ 572 | { 573 | "output_type": "stream", 574 | "text": [ 575 | "Epoch 1/100\n", 576 | "6/6 [==============================] - 22s 167ms/step - loss: 0.0073 - mae: 0.0724 - val_loss: 0.0029 - val_mae: 0.0441\n", 577 | "Epoch 2/100\n", 578 | "6/6 [==============================] - 0s 7ms/step - loss: 0.0020 - mae: 0.0367 - val_loss: 0.0030 - val_mae: 0.0478\n", 579 | "Epoch 3/100\n", 580 | "6/6 [==============================] - 0s 7ms/step - loss: 0.0019 - mae: 0.0379 - val_loss: 0.0021 - val_mae: 0.0354\n", 581 | "Epoch 4/100\n", 582 | "6/6 [==============================] - 0s 6ms/step - loss: 0.0016 - mae: 0.0319 - val_loss: 0.0011 - val_mae: 0.0289\n", 583 | "Epoch 5/100\n", 584 | "6/6 [==============================] - 0s 7ms/step - loss: 0.0011 - mae: 0.0288 - val_loss: 8.8750e-04 - val_mae: 0.0258\n", 585 | "Epoch 6/100\n", 586 | "6/6 [==============================] - 0s 7ms/step - loss: 8.6720e-04 - mae: 0.0241 - val_loss: 9.3450e-04 - val_mae: 0.0255\n", 587 | "Epoch 7/100\n", 588 | "6/6 [==============================] - 0s 6ms/step - loss: 7.7068e-04 - mae: 0.0238 - val_loss: 9.3880e-04 - val_mae: 0.0270\n", 589 | "Epoch 8/100\n", 590 | "6/6 [==============================] - 0s 10ms/step - loss: 7.6818e-04 - mae: 0.0235 - val_loss: 9.1910e-04 - val_mae: 0.0254\n", 591 | "Epoch 9/100\n", 592 | "6/6 [==============================] - 0s 6ms/step - loss: 7.8089e-04 - mae: 0.0221 - val_loss: 9.1008e-04 - val_mae: 0.0266\n", 593 | "Epoch 10/100\n", 594 | "6/6 [==============================] - 0s 6ms/step - loss: 7.3556e-04 - mae: 0.0235 - val_loss: 8.7510e-04 - val_mae: 0.0254\n", 595 | "Epoch 11/100\n", 596 | "6/6 [==============================] - 0s 6ms/step - loss: 7.5666e-04 - mae: 0.0227 - val_loss: 8.7445e-04 - val_mae: 0.0258\n", 597 | "Epoch 12/100\n", 598 | "6/6 [==============================] - 0s 7ms/step - loss: 7.2720e-04 - mae: 0.0231 - val_loss: 8.7741e-04 - val_mae: 0.0260\n", 599 | "Epoch 13/100\n", 600 | "6/6 [==============================] - 0s 6ms/step - loss: 8.0716e-04 - mae: 0.0245 - val_loss: 8.7129e-04 - val_mae: 0.0253\n", 601 | "Epoch 14/100\n", 602 | "6/6 [==============================] - 0s 7ms/step - loss: 8.9908e-04 - mae: 0.0232 - val_loss: 8.6374e-04 - val_mae: 0.0255\n", 603 | "Epoch 15/100\n", 604 | "6/6 [==============================] - 0s 10ms/step - loss: 8.0920e-04 - mae: 0.0242 - val_loss: 8.6269e-04 - val_mae: 0.0253\n", 605 | "Epoch 16/100\n", 606 | "6/6 [==============================] - 0s 6ms/step - loss: 7.7771e-04 - mae: 0.0231 - val_loss: 8.6120e-04 - val_mae: 0.0257\n", 607 | "Epoch 17/100\n", 608 | "6/6 [==============================] - 0s 7ms/step - loss: 7.4492e-04 - mae: 0.0228 - val_loss: 8.5888e-04 - val_mae: 0.0257\n", 609 | "Epoch 18/100\n", 610 | "6/6 [==============================] - 0s 7ms/step - loss: 7.1863e-04 - mae: 0.0222 - val_loss: 8.5531e-04 - val_mae: 0.0254\n", 611 | "Epoch 19/100\n", 612 | "6/6 [==============================] - 0s 6ms/step - loss: 7.0377e-04 - mae: 0.0223 - val_loss: 8.5408e-04 - val_mae: 0.0253\n", 613 | "Epoch 20/100\n", 614 | "6/6 [==============================] - 0s 8ms/step - loss: 7.0186e-04 - mae: 0.0217 - val_loss: 8.7235e-04 - val_mae: 0.0264\n", 615 | "Epoch 21/100\n", 616 | "6/6 [==============================] - 0s 10ms/step - loss: 7.9355e-04 - mae: 0.0241 - val_loss: 8.7012e-04 - val_mae: 0.0250\n", 617 | "Epoch 22/100\n", 618 | "6/6 [==============================] - 0s 7ms/step - loss: 6.9302e-04 - mae: 0.0206 - val_loss: 9.7644e-04 - val_mae: 0.0278\n", 619 | "Epoch 23/100\n", 620 | "6/6 [==============================] - 0s 10ms/step - loss: 8.7732e-04 - mae: 0.0255 - val_loss: 9.7150e-04 - val_mae: 0.0252\n", 621 | "Epoch 24/100\n", 622 | "6/6 [==============================] - 0s 7ms/step - loss: 7.9624e-04 - mae: 0.0228 - val_loss: 9.6106e-04 - val_mae: 0.0277\n", 623 | "Epoch 25/100\n", 624 | "6/6 [==============================] - 0s 10ms/step - loss: 8.3275e-04 - mae: 0.0243 - val_loss: 9.0669e-04 - val_mae: 0.0250\n", 625 | "Epoch 26/100\n", 626 | "6/6 [==============================] - 0s 7ms/step - loss: 7.3055e-04 - mae: 0.0209 - val_loss: 9.4628e-04 - val_mae: 0.0275\n", 627 | "Epoch 27/100\n", 628 | "6/6 [==============================] - 0s 6ms/step - loss: 8.2731e-04 - mae: 0.0250 - val_loss: 9.1348e-04 - val_mae: 0.0249\n", 629 | "Epoch 28/100\n", 630 | "6/6 [==============================] - 0s 10ms/step - loss: 8.7785e-04 - mae: 0.0229 - val_loss: 8.5502e-04 - val_mae: 0.0262\n", 631 | "Epoch 29/100\n", 632 | "6/6 [==============================] - 0s 7ms/step - loss: 7.4989e-04 - mae: 0.0229 - val_loss: 8.3938e-04 - val_mae: 0.0255\n", 633 | "Epoch 30/100\n", 634 | "6/6 [==============================] - 0s 12ms/step - loss: 7.9248e-04 - mae: 0.0237 - val_loss: 8.5845e-04 - val_mae: 0.0249\n", 635 | "Epoch 31/100\n", 636 | "6/6 [==============================] - 0s 8ms/step - loss: 7.7327e-04 - mae: 0.0213 - val_loss: 8.9804e-04 - val_mae: 0.0270\n", 637 | "Epoch 32/100\n", 638 | "6/6 [==============================] - 0s 7ms/step - loss: 7.2960e-04 - mae: 0.0235 - val_loss: 0.0010 - val_mae: 0.0254\n", 639 | "Epoch 33/100\n", 640 | "6/6 [==============================] - 0s 7ms/step - loss: 8.1861e-04 - mae: 0.0218 - val_loss: 0.0011 - val_mae: 0.0288\n", 641 | "Epoch 34/100\n", 642 | "6/6 [==============================] - 0s 7ms/step - loss: 8.8335e-04 - mae: 0.0247 - val_loss: 0.0011 - val_mae: 0.0258\n", 643 | "Epoch 35/100\n", 644 | "6/6 [==============================] - 0s 7ms/step - loss: 9.8814e-04 - mae: 0.0242 - val_loss: 9.0522e-04 - val_mae: 0.0271\n", 645 | "Epoch 36/100\n", 646 | "6/6 [==============================] - 0s 6ms/step - loss: 7.0212e-04 - mae: 0.0230 - val_loss: 9.1783e-04 - val_mae: 0.0250\n", 647 | "Epoch 37/100\n", 648 | "6/6 [==============================] - 0s 10ms/step - loss: 8.6581e-04 - mae: 0.0241 - val_loss: 8.5024e-04 - val_mae: 0.0250\n", 649 | "Epoch 38/100\n", 650 | "6/6 [==============================] - 0s 6ms/step - loss: 7.5783e-04 - mae: 0.0221 - val_loss: 9.0157e-04 - val_mae: 0.0271\n", 651 | "Epoch 39/100\n", 652 | "6/6 [==============================] - 0s 6ms/step - loss: 0.0010 - mae: 0.0265 - val_loss: 0.0014 - val_mae: 0.0287\n", 653 | "Epoch 40/100\n", 654 | "6/6 [==============================] - 0s 6ms/step - loss: 9.4764e-04 - mae: 0.0240 - val_loss: 0.0013 - val_mae: 0.0303\n", 655 | "Epoch 41/100\n", 656 | "6/6 [==============================] - 0s 6ms/step - loss: 0.0011 - mae: 0.0281 - val_loss: 8.8451e-04 - val_mae: 0.0249\n", 657 | "Epoch 42/100\n", 658 | "6/6 [==============================] - 0s 6ms/step - loss: 7.4749e-04 - mae: 0.0230 - val_loss: 8.4974e-04 - val_mae: 0.0251\n", 659 | "Epoch 43/100\n", 660 | "6/6 [==============================] - 0s 6ms/step - loss: 7.4331e-04 - mae: 0.0215 - val_loss: 8.5526e-04 - val_mae: 0.0264\n", 661 | "Epoch 44/100\n", 662 | "6/6 [==============================] - 0s 6ms/step - loss: 6.6416e-04 - mae: 0.0221 - val_loss: 8.4034e-04 - val_mae: 0.0260\n", 663 | "Epoch 45/100\n", 664 | "6/6 [==============================] - 0s 7ms/step - loss: 7.7864e-04 - mae: 0.0242 - val_loss: 9.1566e-04 - val_mae: 0.0250\n", 665 | "Epoch 46/100\n", 666 | "6/6 [==============================] - 0s 6ms/step - loss: 6.8302e-04 - mae: 0.0207 - val_loss: 0.0011 - val_mae: 0.0290\n", 667 | "Epoch 47/100\n", 668 | "6/6 [==============================] - 0s 7ms/step - loss: 8.8531e-04 - mae: 0.0255 - val_loss: 0.0012 - val_mae: 0.0261\n", 669 | "Epoch 48/100\n", 670 | "6/6 [==============================] - 0s 6ms/step - loss: 0.0010 - mae: 0.0242 - val_loss: 8.5470e-04 - val_mae: 0.0264\n", 671 | "Epoch 49/100\n", 672 | "6/6 [==============================] - 0s 6ms/step - loss: 8.5538e-04 - mae: 0.0235 - val_loss: 9.0461e-04 - val_mae: 0.0271\n", 673 | "Epoch 50/100\n", 674 | "6/6 [==============================] - 0s 6ms/step - loss: 8.3663e-04 - mae: 0.0245 - val_loss: 0.0010 - val_mae: 0.0254\n", 675 | "Epoch 51/100\n", 676 | "6/6 [==============================] - 0s 6ms/step - loss: 7.0976e-04 - mae: 0.0218 - val_loss: 9.6546e-04 - val_mae: 0.0277\n", 677 | "Epoch 52/100\n", 678 | "6/6 [==============================] - 0s 9ms/step - loss: 9.2072e-04 - mae: 0.0252 - val_loss: 8.7586e-04 - val_mae: 0.0267\n", 679 | "Epoch 53/100\n", 680 | "6/6 [==============================] - 0s 6ms/step - loss: 7.7637e-04 - mae: 0.0235 - val_loss: 8.4499e-04 - val_mae: 0.0252\n", 681 | "Epoch 54/100\n", 682 | "6/6 [==============================] - 0s 6ms/step - loss: 6.8052e-04 - mae: 0.0226 - val_loss: 8.4043e-04 - val_mae: 0.0253\n", 683 | "Epoch 55/100\n", 684 | "6/6 [==============================] - 0s 7ms/step - loss: 7.3551e-04 - mae: 0.0226 - val_loss: 8.3528e-04 - val_mae: 0.0256\n", 685 | "Epoch 56/100\n", 686 | "6/6 [==============================] - 0s 6ms/step - loss: 6.7654e-04 - mae: 0.0219 - val_loss: 8.8698e-04 - val_mae: 0.0269\n", 687 | "Epoch 57/100\n", 688 | "6/6 [==============================] - 0s 6ms/step - loss: 7.3546e-04 - mae: 0.0232 - val_loss: 9.2346e-04 - val_mae: 0.0250\n", 689 | "Epoch 58/100\n", 690 | "6/6 [==============================] - 0s 6ms/step - loss: 8.0102e-04 - mae: 0.0220 - val_loss: 0.0013 - val_mae: 0.0306\n", 691 | "Epoch 59/100\n", 692 | "6/6 [==============================] - 0s 9ms/step - loss: 0.0011 - mae: 0.0280 - val_loss: 8.3792e-04 - val_mae: 0.0254\n", 693 | "Epoch 60/100\n", 694 | "6/6 [==============================] - 0s 6ms/step - loss: 9.1964e-04 - mae: 0.0252 - val_loss: 9.7688e-04 - val_mae: 0.0251\n", 695 | "Epoch 61/100\n", 696 | "6/6 [==============================] - 0s 7ms/step - loss: 8.3015e-04 - mae: 0.0230 - val_loss: 8.5337e-04 - val_mae: 0.0263\n", 697 | "Epoch 62/100\n", 698 | "6/6 [==============================] - 0s 8ms/step - loss: 8.5996e-04 - mae: 0.0243 - val_loss: 8.3637e-04 - val_mae: 0.0257\n", 699 | "Epoch 63/100\n", 700 | "6/6 [==============================] - 0s 7ms/step - loss: 7.5642e-04 - mae: 0.0237 - val_loss: 9.3817e-04 - val_mae: 0.0250\n", 701 | "Epoch 64/100\n", 702 | "6/6 [==============================] - 0s 8ms/step - loss: 8.8207e-04 - mae: 0.0244 - val_loss: 8.6595e-04 - val_mae: 0.0266\n", 703 | "Epoch 65/100\n", 704 | "6/6 [==============================] - 0s 8ms/step - loss: 7.5487e-04 - mae: 0.0231 - val_loss: 8.5121e-04 - val_mae: 0.0263\n", 705 | "Epoch 66/100\n", 706 | "6/6 [==============================] - 0s 7ms/step - loss: 6.9196e-04 - mae: 0.0222 - val_loss: 0.0011 - val_mae: 0.0253\n", 707 | "Epoch 67/100\n", 708 | "6/6 [==============================] - 0s 8ms/step - loss: 8.5080e-04 - mae: 0.0220 - val_loss: 0.0013 - val_mae: 0.0304\n", 709 | "Epoch 68/100\n", 710 | "6/6 [==============================] - 0s 7ms/step - loss: 9.3323e-04 - mae: 0.0249 - val_loss: 9.2514e-04 - val_mae: 0.0250\n", 711 | "Epoch 69/100\n", 712 | "6/6 [==============================] - 0s 7ms/step - loss: 6.4373e-04 - mae: 0.0214 - val_loss: 9.2058e-04 - val_mae: 0.0273\n", 713 | "Epoch 70/100\n", 714 | "6/6 [==============================] - 0s 6ms/step - loss: 7.4666e-04 - mae: 0.0227 - val_loss: 8.3819e-04 - val_mae: 0.0256\n", 715 | "Epoch 71/100\n", 716 | "6/6 [==============================] - 0s 7ms/step - loss: 7.0207e-04 - mae: 0.0218 - val_loss: 9.1534e-04 - val_mae: 0.0272\n", 717 | "Epoch 72/100\n", 718 | "6/6 [==============================] - 0s 8ms/step - loss: 8.1527e-04 - mae: 0.0244 - val_loss: 0.0010 - val_mae: 0.0252\n", 719 | "Epoch 73/100\n", 720 | "6/6 [==============================] - 0s 8ms/step - loss: 9.5500e-04 - mae: 0.0238 - val_loss: 8.4162e-04 - val_mae: 0.0260\n", 721 | "Epoch 74/100\n", 722 | "6/6 [==============================] - 0s 7ms/step - loss: 7.5374e-04 - mae: 0.0227 - val_loss: 8.4190e-04 - val_mae: 0.0255\n", 723 | "Epoch 75/100\n", 724 | "6/6 [==============================] - 0s 10ms/step - loss: 6.5247e-04 - mae: 0.0211 - val_loss: 8.4693e-04 - val_mae: 0.0254\n", 725 | "Epoch 76/100\n", 726 | "6/6 [==============================] - 0s 10ms/step - loss: 7.3209e-04 - mae: 0.0220 - val_loss: 9.1979e-04 - val_mae: 0.0272\n", 727 | "Epoch 77/100\n", 728 | "6/6 [==============================] - 0s 8ms/step - loss: 8.1132e-04 - mae: 0.0243 - val_loss: 9.6925e-04 - val_mae: 0.0251\n", 729 | "Epoch 78/100\n", 730 | "6/6 [==============================] - 0s 7ms/step - loss: 7.9817e-04 - mae: 0.0236 - val_loss: 9.6579e-04 - val_mae: 0.0277\n", 731 | "Epoch 79/100\n", 732 | "6/6 [==============================] - 0s 6ms/step - loss: 8.7666e-04 - mae: 0.0242 - val_loss: 9.4684e-04 - val_mae: 0.0275\n", 733 | "Epoch 80/100\n", 734 | "6/6 [==============================] - 0s 6ms/step - loss: 7.8953e-04 - mae: 0.0235 - val_loss: 9.6187e-04 - val_mae: 0.0251\n", 735 | "Epoch 81/100\n", 736 | "6/6 [==============================] - 0s 9ms/step - loss: 7.9530e-04 - mae: 0.0222 - val_loss: 9.0846e-04 - val_mae: 0.0271\n", 737 | "Epoch 82/100\n", 738 | "6/6 [==============================] - 0s 7ms/step - loss: 8.5763e-04 - mae: 0.0248 - val_loss: 8.3873e-04 - val_mae: 0.0257\n", 739 | "Epoch 83/100\n", 740 | "6/6 [==============================] - 0s 8ms/step - loss: 6.6067e-04 - mae: 0.0221 - val_loss: 0.0010 - val_mae: 0.0281\n", 741 | "Epoch 84/100\n", 742 | "6/6 [==============================] - 0s 7ms/step - loss: 8.8731e-04 - mae: 0.0254 - val_loss: 8.9134e-04 - val_mae: 0.0251\n", 743 | "Epoch 85/100\n", 744 | "6/6 [==============================] - 0s 7ms/step - loss: 8.1048e-04 - mae: 0.0233 - val_loss: 8.5842e-04 - val_mae: 0.0253\n", 745 | "Epoch 86/100\n", 746 | "6/6 [==============================] - 0s 7ms/step - loss: 6.8501e-04 - mae: 0.0225 - val_loss: 8.4013e-04 - val_mae: 0.0256\n", 747 | "Epoch 87/100\n", 748 | "6/6 [==============================] - 0s 6ms/step - loss: 7.3615e-04 - mae: 0.0218 - val_loss: 0.0012 - val_mae: 0.0295\n", 749 | "Epoch 88/100\n", 750 | "6/6 [==============================] - 0s 8ms/step - loss: 0.0010 - mae: 0.0273 - val_loss: 8.5857e-04 - val_mae: 0.0253\n", 751 | "Epoch 89/100\n", 752 | "6/6 [==============================] - 0s 6ms/step - loss: 8.3378e-04 - mae: 0.0241 - val_loss: 0.0010 - val_mae: 0.0251\n", 753 | "Epoch 90/100\n", 754 | "6/6 [==============================] - 0s 7ms/step - loss: 0.0010 - mae: 0.0247 - val_loss: 8.7960e-04 - val_mae: 0.0268\n", 755 | "Epoch 91/100\n", 756 | "6/6 [==============================] - 0s 7ms/step - loss: 7.5908e-04 - mae: 0.0228 - val_loss: 8.4580e-04 - val_mae: 0.0255\n", 757 | "Epoch 92/100\n", 758 | "6/6 [==============================] - 0s 13ms/step - loss: 6.4425e-04 - mae: 0.0220 - val_loss: 8.4094e-04 - val_mae: 0.0259\n", 759 | "Epoch 93/100\n", 760 | "6/6 [==============================] - 0s 7ms/step - loss: 7.8918e-04 - mae: 0.0237 - val_loss: 0.0010 - val_mae: 0.0251\n", 761 | "Epoch 94/100\n", 762 | "6/6 [==============================] - 0s 6ms/step - loss: 8.5163e-04 - mae: 0.0235 - val_loss: 8.7668e-04 - val_mae: 0.0267\n", 763 | "Epoch 95/100\n", 764 | "6/6 [==============================] - 0s 6ms/step - loss: 7.2157e-04 - mae: 0.0218 - val_loss: 8.6276e-04 - val_mae: 0.0265\n", 765 | "Epoch 96/100\n", 766 | "6/6 [==============================] - 0s 7ms/step - loss: 7.7666e-04 - mae: 0.0237 - val_loss: 9.2775e-04 - val_mae: 0.0251\n", 767 | "Epoch 97/100\n", 768 | "6/6 [==============================] - 0s 6ms/step - loss: 7.9270e-04 - mae: 0.0228 - val_loss: 8.6700e-04 - val_mae: 0.0266\n", 769 | "Epoch 98/100\n", 770 | "6/6 [==============================] - 0s 6ms/step - loss: 7.2839e-04 - mae: 0.0229 - val_loss: 8.3969e-04 - val_mae: 0.0257\n", 771 | "Epoch 99/100\n", 772 | "6/6 [==============================] - 0s 8ms/step - loss: 7.2456e-04 - mae: 0.0230 - val_loss: 8.8624e-04 - val_mae: 0.0252\n", 773 | "Epoch 100/100\n", 774 | "6/6 [==============================] - 0s 8ms/step - loss: 7.3666e-04 - mae: 0.0223 - val_loss: 9.1588e-04 - val_mae: 0.0272\n" 775 | ], 776 | "name": "stdout" 777 | } 778 | ] 779 | }, 780 | { 781 | "cell_type": "code", 782 | "metadata": { 783 | "colab": { 784 | "base_uri": "https://localhost:8080/", 785 | "height": 542 786 | }, 787 | "id": "m40rR1lA8COE", 788 | "outputId": "865a83fe-c2fd-4cc0-fa6e-553f23e1fca6" 789 | }, 790 | "source": [ 791 | "fig = go.Figure()\n", 792 | "\n", 793 | "fig.add_trace(go.Scatter(x=np.arange(0, epochs, 1),\n", 794 | " y=history.history['mae'],\n", 795 | " mode='lines',\n", 796 | " name=f'Training MAE',\n", 797 | " marker_size=3, \n", 798 | " line_color='orange'))\n", 799 | "fig.add_trace(go.Scatter(x=np.arange(0, epochs, 1),\n", 800 | " y=history.history['val_mae'],\n", 801 | " mode='lines',\n", 802 | " name=f'Validation MAE',\n", 803 | " line_color='grey'))\n", 804 | "\n", 805 | "fig.update_layout(\n", 806 | " title=\"Network training\",\n", 807 | " xaxis_title=\"Epochs\",\n", 808 | " yaxis_title=f\"Mean Absolute Error\")\n", 809 | "fig.update_layout({'plot_bgcolor': '#f2f8fd' , \n", 810 | " 'paper_bgcolor': 'white',}, \n", 811 | " template='plotly_white')" 812 | ], 813 | "execution_count": null, 814 | "outputs": [ 815 | { 816 | "output_type": "display_data", 817 | "data": { 818 | "text/html": [ 819 | "\n", 820 | "\n", 821 | "\n", 822 | "
\n", 823 | " \n", 824 | " \n", 825 | " \n", 826 | "
\n", 827 | " \n", 865 | "
\n", 866 | "\n", 867 | "" 868 | ] 869 | }, 870 | "metadata": { 871 | "tags": [] 872 | } 873 | } 874 | ] 875 | }, 876 | { 877 | "cell_type": "markdown", 878 | "metadata": { 879 | "id": "gXIxRV1HS2S3" 880 | }, 881 | "source": [ 882 | "### 4. Compile the hybrid digital twin" 883 | ] 884 | }, 885 | { 886 | "cell_type": "code", 887 | "metadata": { 888 | "colab": { 889 | "base_uri": "https://localhost:8080/", 890 | "height": 542 891 | }, 892 | "id": "0PjNqTBjAEEi", 893 | "outputId": "ff5267eb-f824-4909-a738-18f3c83b375a" 894 | }, 895 | "source": [ 896 | "fig = go.Figure()\n", 897 | "fig.add_trace(go.Scatter(x=X_in_train, \n", 898 | " y=X_out_train,\n", 899 | " mode='markers',\n", 900 | " name=f'Modelled Capacity',\n", 901 | " marker=dict(\n", 902 | " size=4,\n", 903 | " color='grey',\n", 904 | " symbol='cross'\n", 905 | " ), \n", 906 | " line_color='crimson'))\n", 907 | "fig.add_trace(go.Scatter(x = X_in_train, \n", 908 | " y=model.predict(X_in_train).reshape(-1),\n", 909 | " mode='lines',\n", 910 | " name=f'Trained Capacity',\n", 911 | " line=dict(color='navy', width=3)))\n", 912 | "fig.update_layout(\n", 913 | " title=\"Network training\",\n", 914 | " xaxis_title=\"Modelled capacity\",\n", 915 | " yaxis_title=\"Δ (Mod. Capacity - Measured Cap.)\")\n", 916 | "\n", 917 | "fig.update_layout(legend=dict(\n", 918 | " yanchor=\"top\",\n", 919 | " y=0.95,\n", 920 | " xanchor=\"left\",\n", 921 | " x=0.45\n", 922 | "))\n", 923 | "fig.update_layout({'plot_bgcolor': '#f2f8fd' , #or azure\n", 924 | "'paper_bgcolor': 'white',}, template='plotly_white')" 925 | ], 926 | "execution_count": null, 927 | "outputs": [ 928 | { 929 | "output_type": "display_data", 930 | "data": { 931 | "text/html": [ 932 | "\n", 933 | "\n", 934 | "\n", 935 | "
\n", 936 | " \n", 937 | " \n", 938 | " \n", 939 | "
\n", 940 | " \n", 978 | "
\n", 979 | "\n", 980 | "" 981 | ] 982 | }, 983 | "metadata": { 984 | "tags": [] 985 | } 986 | } 987 | ] 988 | }, 989 | { 990 | "cell_type": "code", 991 | "metadata": { 992 | "colab": { 993 | "base_uri": "https://localhost:8080/", 994 | "height": 542 995 | }, 996 | "id": "5U6sfANYBjxd", 997 | "outputId": "52c95c9b-d4b2-4103-fe3b-85fcce2f607c" 998 | }, 999 | "source": [ 1000 | "X_twin = X_in + model.predict(X_in).reshape(-1)\n", 1001 | "\n", 1002 | "fig = go.Figure()\n", 1003 | "\n", 1004 | "fig.add_trace(go.Scatter(x=dfb.index, \n", 1005 | " y=X_twin,\n", 1006 | " mode='lines',\n", 1007 | " name=f'Hybrid digial twin',\n", 1008 | " line=dict(color='firebrick', width=3)))\n", 1009 | "fig.add_trace(go.Scatter(x=dfb.index, \n", 1010 | " y=dfb['C. Capacity'],\n", 1011 | " mode='lines',\n", 1012 | " name=f'Modelled capacity',\n", 1013 | " line=dict(color='navy', \n", 1014 | " width=3,\n", 1015 | " dash='dash')))\n", 1016 | "\n", 1017 | "fig.add_trace(go.Scatter(x=dfb.index, \n", 1018 | " y=dfb['Capacity'],\n", 1019 | " mode='markers',\n", 1020 | " marker=dict(\n", 1021 | " size=4,\n", 1022 | " color='grey',\n", 1023 | " symbol='cross'\n", 1024 | " ),\n", 1025 | " name=f'Observed capacity',\n", 1026 | " line_color='navy'))\n", 1027 | "fig.update_layout(\n", 1028 | " title=\"Comparison of hybrid twin with other models\",\n", 1029 | " xaxis_title=\"Cycles\",\n", 1030 | " yaxis_title=\"Capacity in Ahr\")\n", 1031 | "fig.update_layout(legend=dict(\n", 1032 | " yanchor=\"top\",\n", 1033 | " y=0.95,\n", 1034 | " xanchor=\"left\",\n", 1035 | " x=0.77\n", 1036 | "))\n", 1037 | "fig.update_layout({'plot_bgcolor': '#f2f8fd',\n", 1038 | " 'paper_bgcolor': 'white',}, \n", 1039 | " template='plotly_white')" 1040 | ], 1041 | "execution_count": null, 1042 | "outputs": [ 1043 | { 1044 | "output_type": "display_data", 1045 | "data": { 1046 | "text/html": [ 1047 | "\n", 1048 | "\n", 1049 | "\n", 1050 | "
\n", 1051 | " \n", 1052 | " \n", 1053 | " \n", 1054 | "
\n", 1055 | " \n", 1093 | "
\n", 1094 | "\n", 1095 | "" 1096 | ] 1097 | }, 1098 | "metadata": { 1099 | "tags": [] 1100 | } 1101 | } 1102 | ] 1103 | }, 1104 | { 1105 | "cell_type": "markdown", 1106 | "metadata": { 1107 | "id": "lt2L-FqWS2S3" 1108 | }, 1109 | "source": [ 1110 | "## 5. Prediction whit hybrid twin model\n" 1111 | ] 1112 | }, 1113 | { 1114 | "cell_type": "code", 1115 | "metadata": { 1116 | "id": "pf-mRrfdJ5ek" 1117 | }, 1118 | "source": [ 1119 | "cycles = np.arange(168,500,1)\n", 1120 | "temperature = dfb['Temperature_measured'].iloc[167]\n", 1121 | "time = dfb['Time'].iloc[167]\n", 1122 | "K = 0.13\n", 1123 | "L_e = 1-e**(-K*cycles*temperature/time)\n", 1124 | "X_in_e = -(L_e*dfb['Capacity'].iloc[0:1].values[0]) + dfb['Capacity'].iloc[0:1].values[0]\n", 1125 | "C_twin_e = X_in_e + model.predict(X_in_e).reshape(-1)" 1126 | ], 1127 | "execution_count": null, 1128 | "outputs": [] 1129 | }, 1130 | { 1131 | "cell_type": "code", 1132 | "metadata": { 1133 | "colab": { 1134 | "base_uri": "https://localhost:8080/", 1135 | "height": 542 1136 | }, 1137 | "id": "4WJpp8TzMvPq", 1138 | "outputId": "0840db8c-cb67-4a77-cb1e-dedfb990b2d8" 1139 | }, 1140 | "source": [ 1141 | "X_twin = X_in + model.predict(X_in).reshape(-1)\n", 1142 | "\n", 1143 | "fig = go.Figure()\n", 1144 | "\n", 1145 | "fig.add_trace(go.Scatter(x=cycles, \n", 1146 | " y=X_in_e,\n", 1147 | " mode='lines',\n", 1148 | " name=f'C modelled (predicted)',\n", 1149 | " line=dict(color='navy', \n", 1150 | " width=3,\n", 1151 | " dash='dash')))\n", 1152 | "fig.add_trace(go.Scatter(x=cycles, \n", 1153 | " y=C_twin_e,\n", 1154 | " mode='lines',\n", 1155 | " name=f'C Digital twin (predicted)',\n", 1156 | " line=dict(color='crimson', \n", 1157 | " width=3,\n", 1158 | " dash='dash'\n", 1159 | " )))\n", 1160 | "\n", 1161 | "fig.add_trace(go.Scatter(x=dfb.index, \n", 1162 | " y=X_twin,\n", 1163 | " mode='lines',\n", 1164 | " name=f'C Digital twin',\n", 1165 | " line=dict(color='crimson',\n", 1166 | " width=2)))\n", 1167 | "fig.add_trace(go.Scatter(x=dfb.index, \n", 1168 | " y=dfb['C. Capacity'],\n", 1169 | " mode='lines',\n", 1170 | " name=f'C modelled',\n", 1171 | " line=dict(color='navy', \n", 1172 | " width=2)))\n", 1173 | "\n", 1174 | "fig.update_layout(\n", 1175 | " title=\"Battery capacity prediction\",\n", 1176 | " xaxis_title=\"Cycles\",\n", 1177 | " yaxis_title=\"Battery capacity [Ahr]\")\n", 1178 | "fig.update_layout(legend=dict(\n", 1179 | " yanchor=\"top\",\n", 1180 | " y=0.95,\n", 1181 | " xanchor=\"left\",\n", 1182 | " x=0.72\n", 1183 | "))\n", 1184 | "fig.update_layout({'plot_bgcolor': '#f2f8fd',\n", 1185 | " 'paper_bgcolor': 'white',}, \n", 1186 | " template='plotly_white')" 1187 | ], 1188 | "execution_count": null, 1189 | "outputs": [ 1190 | { 1191 | "output_type": "display_data", 1192 | "data": { 1193 | "text/html": [ 1194 | "\n", 1195 | "\n", 1196 | "\n", 1197 | "
\n", 1198 | " \n", 1199 | " \n", 1200 | " \n", 1201 | "
\n", 1202 | " \n", 1240 | "
\n", 1241 | "\n", 1242 | "" 1243 | ] 1244 | }, 1245 | "metadata": { 1246 | "tags": [] 1247 | } 1248 | } 1249 | ] 1250 | } 1251 | ] 1252 | } --------------------------------------------------------------------------------