{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "e2e8b6ee",
   "metadata": {},
   "source": [
    "**WARNING: this was LLM-generated and has not been fully reviewed yet.**"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b1",
   "metadata": {},
   "source": [
    "# Competitive Binding and Concentration Sweeps\n",
    "\n",
    "This notebook demonstrates how to use equiconc to study competitive\n",
    "binding and visualize how equilibrium shifts with changing conditions."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "b2",
   "metadata": {},
   "outputs": [],
   "source": [
    "import equiconc\n",
    "import numpy as np\n",
    "import matplotlib.pyplot as plt"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b3",
   "metadata": {},
   "source": [
    "## Example 1: competitive dimers\n",
    "\n",
    "Strand **A** can bind either **B** (forming **AB**) or **C** (forming **AC**).\n",
    "We fix $[\\text{A}]_0 = [\\text{C}]_0 = 1\\;\\mu\\text{M}$ and sweep\n",
    "$[\\text{B}]_0$ to see how the competitor displaces **C** from **A**.\n",
    "\n",
    "- $\\Delta G^\\circ_{\\text{AB}} = -12$ kcal/mol (stronger binder)\n",
    "- $\\Delta G^\\circ_{\\text{AC}} = -10$ kcal/mol (weaker binder)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b4",
   "metadata": {},
   "outputs": [],
   "source": "conc_A = 1e-6   # 1 uM\nconc_C = 1e-6   # 1 uM\ndg_AB = -12.0\ndg_AC = -10.0\n\nconc_B_range = np.logspace(-8, -4, 80)  # 10 nM to 100 uM\n\nresults = {\"B0\": [], \"free_A\": [], \"AB\": [], \"AC\": [], \"free_B\": [], \"free_C\": []}\n\nfor conc_B in conc_B_range:\n    eq = (\n        equiconc.System()\n        .monomer(\"A\", conc_A)\n        .monomer(\"B\", conc_B)\n        .monomer(\"C\", conc_C)\n        .complex(\"AB\", [(\"A\", 1), (\"B\", 1)], dg_st=dg_AB)\n        .complex(\"AC\", [(\"A\", 1), (\"C\", 1)], dg_st=dg_AC)\n        .equilibrium()\n    )\n    results[\"B0\"].append(conc_B)\n    results[\"free_A\"].append(eq[\"A\"])\n    results[\"AB\"].append(eq[\"AB\"])\n    results[\"AC\"].append(eq[\"AC\"])\n    results[\"free_B\"].append(eq[\"B\"])\n    results[\"free_C\"].append(eq[\"C\"])"
  },
  {
   "cell_type": "markdown",
   "id": "b5",
   "metadata": {},
   "source": [
    "As $[\\text{B}]_0$ increases, **AB** formation rises and **AC** is displaced."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "id": "b6",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 42176 characters]",
      "text/plain": [
       "<Figure size 600x400 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "B0_uM = np.array(results[\"B0\"]) * 1e6\n",
    "\n",
    "fig, ax = plt.subplots(figsize=(6, 4))\n",
    "ax.plot(B0_uM, np.array(results[\"AB\"]) * 1e6, label=\"[AB]\", linewidth=2)\n",
    "ax.plot(B0_uM, np.array(results[\"AC\"]) * 1e6, label=\"[AC]\", linewidth=2)\n",
    "ax.plot(B0_uM, np.array(results[\"free_A\"]) * 1e6, label=\"free [A]\",\n",
    "        linewidth=2, linestyle=\"--\")\n",
    "ax.set_xscale(\"log\")\n",
    "ax.set_xlabel(\"Total [B] (uM)\")\n",
    "ax.set_ylabel(\"Concentration (uM)\")\n",
    "ax.set_title(\"Competitive binding: titrating B into A + C\")\n",
    "ax.legend()\n",
    "ax.set_ylim(bottom=0)\n",
    "fig.tight_layout()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b7",
   "metadata": {},
   "source": [
    "\n",
    "Next, we fix all concentrations at 1 $\\mu$M and sweep temperature\n",
    "to see how the equilibrium shifts. We'll set $\\Delta G^\\circ$ to -15.0 at 37°C, and use a $\\Delta S$ of -0.3."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b8",
   "metadata": {},
   "outputs": [],
   "source": "temperatures_C = np.linspace(10, 90, 80)\n\nconc_all = 1e-6\ndg37 = -15.0\nds = -0.3\n\nconc_bound = np.array([\n        equiconc.System(temperature_C=T)\n        .monomer(\"A\", conc_all)\n        .monomer(\"B\", conc_all)\n        .complex(\"AB\", [(\"A\", 1), (\"B\", 1)], dg_st=(dg37,37), ds_st=ds)\n        .equilibrium()[\"AB\"] for T in temperatures_C])\n\nfraction_bound = conc_bound/conc_all"
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "id": "b9",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 30204 characters]",
      "text/plain": [
       "<Figure size 600x400 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "fig, ax = plt.subplots(figsize=(6, 4))\n",
    "ax.plot(temperatures_C, fraction_bound, linewidth=2, color=\"teal\")\n",
    "ax.set_xlabel(\"Temperature (C)\")\n",
    "ax.set_ylabel(\"Fraction of A bound as AB\")\n",
    "ax.set_title(\"Melting curve: A + B <=> AB\")\n",
    "ax.set_ylim(-0.02, 1.02)\n",
    "ax.axhline(0.5, color=\"gray\", linestyle=\":\", linewidth=1)\n",
    "fig.tight_layout()\n",
    "plt.show()"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "equiconc",
   "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.14.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}