{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Battery runtime planner\n",
    "\n",
    "EECE 5554 Robotics Sensing and Navigation, Lecture 06. Put `battery_runtime.py` in the same folder as this notebook, then run the cells in order."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Each dark box below is a code cell. Edit it, then press **Run** or **Shift+Enter**. Cells share variables from top to bottom, like Jupyter."
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "from battery_runtime import simulate, ocv_cell, PROFILES\n",
    "\n",
    "r = simulate()          # NMC 7S3P, patrol mission, 25 C\n",
    "print(f\"runtime {r['runtime_s'] / 60:.0f} min, stopped by: {r['reason']}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1. Open-circuit voltage curves\n",
    "NMC slopes all the way down; LiFePO4 is flat in the middle and drops at the end."
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "\n",
    "soc = range(0, 101)\n",
    "for chem in ('nmc', 'lfp'):\n",
    "    plt.plot(soc, [ocv_cell(chem, s) for s in soc], label=chem)\n",
    "plt.xlabel('state of charge (%)'); plt.ylabel('cell voltage at rest (V)'); plt.legend()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2. Voltage sag, by hand\n",
    "With constant power P and resistance R: V = (OCV + √(OCV² − 4PR)) / 2."
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "import math\n",
    "\n",
    "ocv, R, P = 7 * 3.6, 0.097, 320.0     # pack volts, pack ohms, burst watts\n",
    "V = (ocv + math.sqrt(ocv ** 2 - 4 * P * R)) / 2\n",
    "print(f'loaded {V:.2f} V, current {P / V:.1f} A, sag {ocv - V:.2f} V')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3. Cold weather"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "for temp in ('25', '0', '-10'):\n",
    "    r = simulate(temp=temp)\n",
    "    print(f\"{temp:>4} C: {r['runtime_s'] / 60:4.0f} min, stopped by {r['reason']}, {r['soc_end']:.0f}% left\")"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "for temp in ('25', '-10'):\n",
    "    r = simulate(temp=temp)\n",
    "    plt.plot([row[0] / 60 for row in r['rows']], [row[1] for row in r['rows']], lw=0.6, label=temp + ' C')\n",
    "plt.axhline(7 * 3.0, color='r', ls='--', label='cutoff')\n",
    "plt.xlabel('minutes'); plt.ylabel('pack voltage under load (V)'); plt.legend()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4. Your turn\n",
    "The patrol mission is 90 minutes. At −10 °C, how many cells in parallel does the pack need? Try changing the loop, the chemistry, or the cell resistance."
   ]
  },
  {
   "cell_type": "code",
   "metadata": {},
   "execution_count": null,
   "outputs": [],
   "source": [
    "for p in range(3, 7):\n",
    "    r = simulate(p=p, temp='-10')\n",
    "    print(f\"{p}P: {r['runtime_s'] / 60:4.0f} min ({r['reason']})\")"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "name": "python"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}