{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "b7aa80ce-9d6e-401a-8870-a8a3e990b04a",
   "metadata": {},
   "source": [
    "## Imports"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 89,
   "id": "5cc5b64f-cf09-43f8-a7a5-359301e9821c",
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "import scipy.optimize\n",
    "# from impedance.models.circuits import CustomCircuit\n",
    "# from impedance.visualization import plot_nyquist # Kept if you want to switch plotting methods\n",
    "\n",
    "import os\n",
    "from os import listdir\n",
    "from os.path import isfile, join\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "28ede9cb-3a55-4e31-9852-b8792e90c2d9",
   "metadata": {},
   "source": [
    "## Data Loading functions"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 90,
   "id": "f38b45f4-cf5b-49d0-bc65-67489bec0a98",
   "metadata": {},
   "outputs": [],
   "source": [
    "# --- Data Loading ---\n",
    "def EIS_z60_import(filename):\n",
    "    \"\"\" Import z60 file as pandas dataframe.\"\"\"\n",
    "    df = pd.read_csv(\n",
    "        filename,\n",
    "        skiprows=11,\n",
    "        sep=r'\\s+',\n",
    "        header=None,\n",
    "        names=[\"Freq\", \"Ampl\", \"Bias\", \"Time\", \"Z'\", \"Z''\", \"GD\", \"Err\", \"Range\"],\n",
    "        index_col=\"Freq\")\n",
    "\n",
    "    df = df.drop(columns=['Ampl', 'Bias', 'Time', 'GD', 'Err', 'Range']) # Drop columns\n",
    "    mask = (df[\"Z'\"] > 0) & (df[\"Z''\"] < 0) # Only keep first quadrant in Nyquist plot\n",
    "\n",
    "    df['Z'] = np.sqrt( (df[\"Z'\"].to_numpy())**2 + \\\n",
    "                       (df[\"Z'\"].to_numpy())**2 )\n",
    "\n",
    "    df['theta'] = np.arctan2(-df[\"Z'\"].to_numpy(), df[\"Z''\"].to_numpy()) * 180 / np.pi\n",
    "    \n",
    "    df[~mask] = None\n",
    "    return df"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 91,
   "id": "7faff843-86d5-43a4-b68a-0e8aaa159065",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>Z'</th>\n",
       "      <th>Z''</th>\n",
       "      <th>Z</th>\n",
       "      <th>theta</th>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Freq</th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>100000.000000</th>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>89051.300000</th>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>79301.400000</th>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>70618.900000</th>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>62887.000000</th>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>...</th>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>0.014616</th>\n",
       "      <td>676330.0</td>\n",
       "      <td>-564709.0</td>\n",
       "      <td>9.564751e+05</td>\n",
       "      <td>-129.860556</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>0.013016</th>\n",
       "      <td>607638.0</td>\n",
       "      <td>-414233.0</td>\n",
       "      <td>8.593299e+05</td>\n",
       "      <td>-124.282652</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>0.011591</th>\n",
       "      <td>814114.0</td>\n",
       "      <td>-698537.0</td>\n",
       "      <td>1.151331e+06</td>\n",
       "      <td>-130.630696</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>0.010322</th>\n",
       "      <td>848202.0</td>\n",
       "      <td>-689490.0</td>\n",
       "      <td>1.199539e+06</td>\n",
       "      <td>-129.107116</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>0.010000</th>\n",
       "      <td>720312.0</td>\n",
       "      <td>-1031530.0</td>\n",
       "      <td>1.018675e+06</td>\n",
       "      <td>-145.073605</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "<p>140 rows × 4 columns</p>\n",
       "</div>"
      ],
      "text/plain": [
       "                     Z'        Z''             Z       theta\n",
       "Freq                                                        \n",
       "100000.000000       NaN        NaN           NaN         NaN\n",
       "89051.300000        NaN        NaN           NaN         NaN\n",
       "79301.400000        NaN        NaN           NaN         NaN\n",
       "70618.900000        NaN        NaN           NaN         NaN\n",
       "62887.000000        NaN        NaN           NaN         NaN\n",
       "...                 ...        ...           ...         ...\n",
       "0.014616       676330.0  -564709.0  9.564751e+05 -129.860556\n",
       "0.013016       607638.0  -414233.0  8.593299e+05 -124.282652\n",
       "0.011591       814114.0  -698537.0  1.151331e+06 -130.630696\n",
       "0.010322       848202.0  -689490.0  1.199539e+06 -129.107116\n",
       "0.010000       720312.0 -1031530.0  1.018675e+06 -145.073605\n",
       "\n",
       "[140 rows x 4 columns]"
      ]
     },
     "execution_count": 91,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "EIS_z60_import(\"EIS_10mV_Timing task2025_07_11_17_19_C001.z60\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ddd32fc6-fd73-48e0-b705-cbf037e941c0",
   "metadata": {},
   "source": [
    "## Loading all files from timed repeated measurements"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 92,
   "id": "3bbd1375-9e48-483b-b3a0-cbc097396470",
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "from os import listdir\n",
    "from os.path import isfile, join\n",
    "\n",
    "\n",
    "EIS_10 = [file \n",
    "          for file in os.listdir('.') \n",
    "          if file.startswith('EIS_10mV') and file.endswith('.z60')]\n",
    "EIS_10_df_list = [] # 1. Create an empty list to hold the DataFrames\n",
    "for filename in EIS_10:\n",
    "    base_name = filename.removesuffix('.z60')\n",
    "    repeat_number = \"\".join(char for char in base_name.rsplit('_', 1)[-1] if char.isdigit())\n",
    "    repeat_number = int(repeat_number) \n",
    "    time = (repeat_number - 1)*2\n",
    "    #print(f\"{base_name}  {time}\")\n",
    "    df = EIS_z60_import(filename)\n",
    "    df[\"Time\"] = time\n",
    "    df.set_index('Time', append=True, inplace=True)    \n",
    "    df_swapped = df.swaplevel()\n",
    "    df_sorted = df_swapped.sort_index()\n",
    "    EIS_10_df_list.append(df_sorted) # 2. Add the processed DataFrame to the list\n",
    "\n",
    "EIS_20 = [file \n",
    "          for file in os.listdir('.') \n",
    "          if file.startswith('EIS_20mV') and file.endswith('.z60')]\n",
    "EIS_20_df_list = [] # 1. Create an empty list to hold the DataFrames\n",
    "for filename in EIS_20:\n",
    "    base_name = filename.removesuffix('.z60')\n",
    "    repeat_number = \"\".join(char for char in base_name.rsplit('_', 1)[-1] if char.isdigit())\n",
    "    repeat_number = int(repeat_number) \n",
    "    time = (repeat_number - 1)*2\n",
    "    #print(f\"{base_name}  {time}\")\n",
    "    df = EIS_z60_import(filename)\n",
    "    df[\"Time\"] = time\n",
    "    df.set_index('Time', append=True, inplace=True)    \n",
    "    df_swapped = df.swaplevel()\n",
    "    df_sorted = df_swapped.sort_index()\n",
    "    EIS_20_df_list.append(df_sorted) # 2. Add the processed DataFrame to the list\n",
    "\n",
    "if EIS_10_df_list:\n",
    "    EIS_10_df = pd.concat(EIS_10_df_list)\n",
    "else:\n",
    "    EIS_10_df = pd.DataFrame() # Ensure EIS_10_df is an empty DataFrame if no files were found\n",
    "    raise ValueError(\"No EIS 10mV files found\")\n",
    "\n",
    "if EIS_20_df_list:\n",
    "    EIS_20_df = pd.concat(EIS_20_df_list)\n",
    "else:\n",
    "    EIS_20_df = pd.DataFrame() # Ensure EIS_10_df is an empty DataFrame if no files were found\n",
    "    raise ValueError(\"No EIS 20mV files found\")\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 93,
   "id": "61ab95b2-4dd7-4a05-a990-d54ab001b285",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th>Z'</th>\n",
       "      <th>Z''</th>\n",
       "      <th>Z</th>\n",
       "      <th>theta</th>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Time</th>\n",
       "      <th>Freq</th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th rowspan=\"5\" valign=\"top\">0</th>\n",
       "      <th>0.010000</th>\n",
       "      <td>720312.0</td>\n",
       "      <td>-1031530.00</td>\n",
       "      <td>1.018675e+06</td>\n",
       "      <td>-145.073605</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>0.010322</th>\n",
       "      <td>848202.0</td>\n",
       "      <td>-689490.00</td>\n",
       "      <td>1.199539e+06</td>\n",
       "      <td>-129.107116</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>0.011591</th>\n",
       "      <td>814114.0</td>\n",
       "      <td>-698537.00</td>\n",
       "      <td>1.151331e+06</td>\n",
       "      <td>-130.630696</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>0.013016</th>\n",
       "      <td>607638.0</td>\n",
       "      <td>-414233.00</td>\n",
       "      <td>8.593299e+05</td>\n",
       "      <td>-124.282652</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>0.014616</th>\n",
       "      <td>676330.0</td>\n",
       "      <td>-564709.00</td>\n",
       "      <td>9.564751e+05</td>\n",
       "      <td>-129.860556</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>...</th>\n",
       "      <th>...</th>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th rowspan=\"5\" valign=\"top\">26</th>\n",
       "      <th>62887.000000</th>\n",
       "      <td>170147.0</td>\n",
       "      <td>-5666.63</td>\n",
       "      <td>2.406242e+05</td>\n",
       "      <td>-91.907492</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>70618.900000</th>\n",
       "      <td>125131.0</td>\n",
       "      <td>-26409.20</td>\n",
       "      <td>1.769620e+05</td>\n",
       "      <td>-101.917519</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>79301.400000</th>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>89051.300000</th>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>100000.000000</th>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "<p>1960 rows × 4 columns</p>\n",
       "</div>"
      ],
      "text/plain": [
       "                          Z'         Z''             Z       theta\n",
       "Time Freq                                                         \n",
       "0    0.010000       720312.0 -1031530.00  1.018675e+06 -145.073605\n",
       "     0.010322       848202.0  -689490.00  1.199539e+06 -129.107116\n",
       "     0.011591       814114.0  -698537.00  1.151331e+06 -130.630696\n",
       "     0.013016       607638.0  -414233.00  8.593299e+05 -124.282652\n",
       "     0.014616       676330.0  -564709.00  9.564751e+05 -129.860556\n",
       "...                      ...         ...           ...         ...\n",
       "26   62887.000000   170147.0    -5666.63  2.406242e+05  -91.907492\n",
       "     70618.900000   125131.0   -26409.20  1.769620e+05 -101.917519\n",
       "     79301.400000        NaN         NaN           NaN         NaN\n",
       "     89051.300000        NaN         NaN           NaN         NaN\n",
       "     100000.000000       NaN         NaN           NaN         NaN\n",
       "\n",
       "[1960 rows x 4 columns]"
      ]
     },
     "execution_count": 93,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "EIS_10_df"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 94,
   "id": "77b7bb99-499c-4811-8cc4-07fe2710dad8",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th>Z'</th>\n",
       "      <th>Z''</th>\n",
       "      <th>Z</th>\n",
       "      <th>theta</th>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>Time</th>\n",
       "      <th>Freq</th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "      <th></th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th rowspan=\"5\" valign=\"top\">0</th>\n",
       "      <th>0.010000</th>\n",
       "      <td>798861.0</td>\n",
       "      <td>-436871.0</td>\n",
       "      <td>1.129760e+06</td>\n",
       "      <td>-118.672809</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>0.010322</th>\n",
       "      <td>870890.0</td>\n",
       "      <td>-353106.0</td>\n",
       "      <td>1.231624e+06</td>\n",
       "      <td>-112.070300</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>0.011591</th>\n",
       "      <td>700737.0</td>\n",
       "      <td>-637478.0</td>\n",
       "      <td>9.909918e+05</td>\n",
       "      <td>-132.293576</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>0.013016</th>\n",
       "      <td>635843.0</td>\n",
       "      <td>-702820.0</td>\n",
       "      <td>8.992178e+05</td>\n",
       "      <td>-137.864277</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>0.014616</th>\n",
       "      <td>679839.0</td>\n",
       "      <td>-543164.0</td>\n",
       "      <td>9.614375e+05</td>\n",
       "      <td>-128.623447</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>...</th>\n",
       "      <th>...</th>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th rowspan=\"5\" valign=\"top\">26</th>\n",
       "      <th>62887.000000</th>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>70618.900000</th>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>79301.400000</th>\n",
       "      <td>319716.0</td>\n",
       "      <td>-63722.3</td>\n",
       "      <td>4.521467e+05</td>\n",
       "      <td>-101.271863</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>89051.300000</th>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>100000.000000</th>\n",
       "      <td>96997.8</td>\n",
       "      <td>-139224.0</td>\n",
       "      <td>1.371756e+05</td>\n",
       "      <td>-145.134952</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "<p>1960 rows × 4 columns</p>\n",
       "</div>"
      ],
      "text/plain": [
       "                          Z'       Z''             Z       theta\n",
       "Time Freq                                                       \n",
       "0    0.010000       798861.0 -436871.0  1.129760e+06 -118.672809\n",
       "     0.010322       870890.0 -353106.0  1.231624e+06 -112.070300\n",
       "     0.011591       700737.0 -637478.0  9.909918e+05 -132.293576\n",
       "     0.013016       635843.0 -702820.0  8.992178e+05 -137.864277\n",
       "     0.014616       679839.0 -543164.0  9.614375e+05 -128.623447\n",
       "...                      ...       ...           ...         ...\n",
       "26   62887.000000        NaN       NaN           NaN         NaN\n",
       "     70618.900000        NaN       NaN           NaN         NaN\n",
       "     79301.400000   319716.0  -63722.3  4.521467e+05 -101.271863\n",
       "     89051.300000        NaN       NaN           NaN         NaN\n",
       "     100000.000000   96997.8 -139224.0  1.371756e+05 -145.134952\n",
       "\n",
       "[1960 rows x 4 columns]"
      ]
     },
     "execution_count": 94,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "EIS_20_df\n",
    "# In order to get all the values for the zeroth hour, use the following:\n",
    "# EIS_20_df.loc[0]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "97f431d5-bfc1-4600-a83a-bc6d7ccae13a",
   "metadata": {},
   "source": [
    "## Fit data to Randles equivalent circuit"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 95,
   "id": "2559f6f1-cf17-4650-be09-7c7b6801fc47",
   "metadata": {
    "jupyter": {
     "source_hidden": true
    }
   },
   "outputs": [],
   "source": [
    "import functools\n",
    "import numpy as np\n",
    "import scipy.optimize\n",
    "import pandas as pd # Added for the example\n",
    "\n",
    "def get_resistance_values(\n",
    "    frequencies,\n",
    "    z_real,\n",
    "    z_imag,\n",
    "    initial_guesses=None,\n",
    "    bounds=None,\n",
    "    sigma_real=None, # Changed to optional\n",
    "    sigma_imag=None, # Changed to optional\n",
    "    max_evaluations=5000000\n",
    "):\n",
    "    \"\"\"\n",
    "    Fits EIS data to a Randles equivalent circuit and returns resistance values.\n",
    "\n",
    "    This function can perform either a weighted (if sigma is provided) or\n",
    "    unweighted (if sigma is None) least squares fit.\n",
    "\n",
    "    Args:\n",
    "        frequencies (array-like): Array of frequency values.\n",
    "        z_real (array-like): Real part of the impedance (Z').\n",
    "        z_imag (array-like): Imaginary part of the impedance (Z'').\n",
    "        initial_guesses (list or tuple): Initial guesses for [Rs, Rp, Q, alpha].\n",
    "        bounds (tuple): A 2-tuple of lists, e.g., ([low_bounds], [high_bounds]).\n",
    "        sigma_real (array-like, optional): Error for the real impedance. Defaults to None.\n",
    "        sigma_imag (array-like, optional): Error for the imaginary impedance. Defaults to None.\n",
    "        max_evaluations (int, optional): Max function evaluations. Defaults to 5,000,000.\n",
    "\n",
    "    Returns:\n",
    "        dict: A dictionary of optimized parameters and errors. Returns None if fitting fails.\n",
    "    \"\"\"\n",
    "\n",
    "    if initial_guesses is None:\n",
    "        # Initial guesses for the parameters (R_s, R_ct, Q, alpha)\n",
    "        initial_guesses = [10.0, 7e6,  1e-6, 0.87]\n",
    "\n",
    "    if bounds is None:\n",
    "        # Helps to keep parameters within physically realistic ranges. \n",
    "        bounds=([0,    0,    1e-9, 0], \n",
    "                [1000, 1e15, 1e-2, 1])\n",
    "        \n",
    "    # --- Helper functions for circuit elements and combinations ---\n",
    "    def R(f, R_val): return np.full_like(f, R_val, dtype=np.complex128)\n",
    "    def CPE(f, Q, alpha): return 1.0 / (Q * (1j * 2 * np.pi * f) ** alpha)\n",
    "    def series(*args): return functools.reduce(np.add, args)\n",
    "    def parallel(*args): return 1.0 / functools.reduce(np.add, [1.0/z for z in args])\n",
    "\n",
    "    # --- Equivalent circuit model ---\n",
    "    def randles_circuit(f, Rs, Rp, Q, alpha):\n",
    "        return series(R(f, Rs), parallel(R(f, Rp), CPE(f, Q, alpha)))\n",
    "\n",
    "    def model_func_for_fitter(f, Rs, Rp, Q, alpha):\n",
    "        Z_fit = randles_circuit(f, Rs, Rp, Q, alpha)\n",
    "        return np.concatenate([Z_fit.real, Z_fit.imag])\n",
    "\n",
    "    # --- Prepare data for fitting ---\n",
    "    valid_indices = ~np.isnan(z_real) & ~np.isnan(z_imag)\n",
    "    freq_data = np.array(frequencies)[valid_indices]\n",
    "    y_data = np.concatenate([\n",
    "        np.array(z_real)[valid_indices],\n",
    "        np.array(z_imag)[valid_indices]\n",
    "    ])\n",
    "\n",
    "    try:    \n",
    "        if sigma_real is not None and sigma_imag is not None:\n",
    "            print(\"Sigma values provided. Performing weighted least squares fit.\")\n",
    "            sigma = np.concatenate([\n",
    "                np.array(sigma_real)[valid_indices],\n",
    "                np.array(sigma_imag)[valid_indices]\n",
    "            ])\n",
    "            absolute_sigma = True # Treat sigma as true standard deviations\n",
    "\n",
    "            popt, pcov = scipy.optimize.curve_fit(\n",
    "                f=model_func_for_fitter,\n",
    "                xdata=freq_data,\n",
    "                ydata=y_data,\n",
    "                p0=initial_guesses,\n",
    "                bounds=bounds,\n",
    "                maxfev=max_evaluations,\n",
    "            )\n",
    "        else:\n",
    "            popt, pcov = scipy.optimize.curve_fit(\n",
    "                f=model_func_for_fitter,\n",
    "                xdata=freq_data,\n",
    "                ydata=y_data,\n",
    "                p0=initial_guesses,\n",
    "                bounds=bounds,\n",
    "                maxfev=max_evaluations,\n",
    "            )\n",
    "        #print(\"Curve fitting finished successfully! ✅\")\n",
    "\n",
    "        # --- Extract and return results ---\n",
    "        perr = np.sqrt(np.diag(pcov))\n",
    "        results = {\n",
    "            'Rs': popt[0], 'Rs_err': perr[0],\n",
    "            'Rp': popt[1], 'Rp_err': perr[1],\n",
    "            'Q': popt[2], 'Q_err': perr[2],\n",
    "            'alpha': popt[3], 'alpha_err': perr[3],\n",
    "        }\n",
    "        return results\n",
    "    \n",
    "    except (RuntimeError, ValueError) as e:\n",
    "        print(f\"Curve fitting failed: {e} ❌\")\n",
    "        print(\"Try adjusting initial guesses, bounds, or the model itself.\")\n",
    "        return None\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 96,
   "id": "91f3af6e-93f2-487e-9b84-9c054fbeabaa",
   "metadata": {},
   "outputs": [],
   "source": [
    "EIS_10_results = {}\n",
    "for time in list(set([time for (time, freq) in EIS_10_df.index])):\n",
    "   EIS_10_results[time] = get_resistance_values(\n",
    "        frequencies=EIS_10_df.loc[time].index,\n",
    "        z_real=EIS_10_df.loc[time][\"Z'\"],\n",
    "        z_imag=EIS_10_df.loc[time][\"Z''\"],\n",
    "    )\n",
    "\n",
    "EIS_20_results = {}\n",
    "for time in list(set([time for (time, freq) in EIS_20_df.index])):\n",
    "   EIS_20_results[time] = get_resistance_values(\n",
    "        frequencies=EIS_20_df.loc[time].index,\n",
    "        z_real=EIS_20_df.loc[time][\"Z'\"],\n",
    "        z_imag=EIS_20_df.loc[time][\"Z''\"],\n",
    "    )\n",
    "    \n",
    "\n",
    "EIS_10_results_df = pd.DataFrame.from_dict(EIS_10_results, orient='index')\n",
    "EIS_20_results_df = pd.DataFrame.from_dict(EIS_20_results, orient='index')\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 97,
   "id": "c544ad4f-0231-44fa-865e-fbb7be597ea2",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<matplotlib.legend.Legend at 0x296ea6b13a0>"
      ]
     },
     "execution_count": 97,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "[encoded data, 46288 characters]",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "fig, ax = plt.subplots()\n",
    "\n",
    "EIS_10_results_df[\"Rp\"].plot(ax=ax, label=\"10 mV\")\n",
    "EIS_20_results_df[\"Rp\"].plot(ax=ax, label=\"20 mV\")\n",
    "ax.legend()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 104,
   "id": "88d6cbff-d2cf-4838-bbd4-0fe75eeab386",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 146444 characters]",
      "text/plain": [
       "<Figure size 960x480 with 4 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "\n",
    "# 1. Set a professional style\n",
    "plt.style.use('seaborn-v0_8-whitegrid')\n",
    "\n",
    "# 2. Create a 2x2 grid of subplots\n",
    "# `sharex=True` links the x-axes for easier comparison.\n",
    "fig, axes = plt.subplots(nrows=2, ncols=2, figsize=(8, 4), dpi=120, sharex=True)\n",
    "\n",
    "# Flatten the 2x2 array of axes to easily loop through them\n",
    "axes = axes.flatten()\n",
    "\n",
    "# 3. Define parameters and labels for each subplot\n",
    "params = ['Rs', 'Rp', 'Q', 'alpha']\n",
    "titles = [\n",
    "    'Solution Resistance ($R_s$)',\n",
    "    'Polarization Resistance ($R_p$)',\n",
    "    'Constant Phase Element (Q)',\n",
    "    'Alpha Coefficient (α)'\n",
    "]\n",
    "ylabels = [\n",
    "    '$R_s$ / $\\\\Omega$',\n",
    "    '$R_p$ / $\\\\Omega$',\n",
    "    'Q / $s^{\\\\alpha} \\\\Omega^{-1}$',\n",
    "    'Alpha (α)'\n",
    "]\n",
    "\n",
    "# 4. Loop through each parameter and create a subplot\n",
    "for i, ax in enumerate(axes):\n",
    "    param = params[i]\n",
    "\n",
    "    # Plot data from both DataFrames on the current axis\n",
    "    ax.plot(EIS_10_results_df.index, EIS_10_results_df[param],\n",
    "            label=\"10 mV\", marker='o', linestyle='-', linewidth=2, color='#007ACC')\n",
    "\n",
    "    ax.plot(EIS_20_results_df.index, EIS_20_results_df[param],\n",
    "            label=\"20 mV\", marker='s', linestyle='--', linewidth=2, color='#D62728')\n",
    "\n",
    "    # Customize titles and labels for each subplot\n",
    "    ax.set_title(titles[i], fontsize=14, fontweight='bold')\n",
    "    ax.set_ylabel(ylabels[i], fontsize=12)\n",
    "\n",
    "    # Customize grid and spines\n",
    "    ax.grid(True, which='both', linestyle='--', linewidth=0.5)\n",
    "    ax.spines['top'].set_visible(False)\n",
    "    ax.spines['right'].set_visible(False)\n",
    "    ax.tick_params(axis='both', which='major', labelsize=10)\n",
    "\n",
    "    # Apply scientific notation where needed (e.g., for Rp and Q)\n",
    "    if param in ['Rp', 'Q']:\n",
    "        ax.ticklabel_format(style='sci', axis='y', scilimits=(0,0), useMathText=True)\n",
    "        ax.yaxis.get_offset_text().set_fontsize(10)\n",
    "\n",
    "# 5. Global customizations\n",
    "# Add a shared x-axis label to the bottom plots\n",
    "axes[2].set_xlabel('Time (hr)', fontsize=12)\n",
    "axes[3].set_xlabel('Time (hr)', fontsize=12)\n",
    "\n",
    "# Create a single, shared legend for the entire figure\n",
    "handles, labels = axes[0].get_legend_handles_labels()\n",
    "fig.legend(handles, labels, loc='upper right', fontsize=11, bbox_to_anchor=(0.98, 0.98))\n",
    "\n",
    "# Add a main title for the whole figure\n",
    "fig.suptitle('EIS Parameter Evolution vs. Cycle Number', fontsize=18, fontweight='bold')\n",
    "\n",
    "# 6. Adjust layout to prevent titles/labels from overlapping\n",
    "fig.tight_layout(rect=[0, 0, 1, 0.96]) # Adjust rect to make space for suptitle\n",
    "\n",
    "# 7. Show the plot\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "74ccb4af-035b-4292-8c93-a3e72246a378",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
