{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "4f1c07be",
   "metadata": {},
   "source": [
    "# DCT Laboratory — Volume II, Chapter 2\n",
    "## The General Enterprise Optimization Problem Revisited\n",
    "**Seed `26202`** · Companion to the chapter and AXIOM Module **AXIOM-02 (Vol. II)**\n",
    "\n",
    "The GEOP as a computational template: a **two-period dynamic allocation** whose\n",
    "interior first-order condition is infeasible (the optimum front-loads at the\n",
    "budget corner — Volume I Ch. 15's lesson, dynamic edition), the **Objective\n",
    "Equivalence Theorem** verified by state augmentation, and a classical **LP\n",
    "solved by vertex enumeration** — Classical Problems are GEOP Special Cases\n",
    "(Prop.). Mirrored in `DCT_V2_Ch02_Lab.xlsx`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "98ade2e8",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-13T23:39:08.820110Z",
     "iopub.status.busy": "2026-07-13T23:39:08.819921Z",
     "iopub.status.idle": "2026-07-13T23:39:09.295156Z",
     "shell.execute_reply": "2026-07-13T23:39:09.293953Z"
    }
   },
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "plt.rcParams['figure.dpi']=110\n",
    "\n",
    "import numpy as np\n",
    "SEED = 26202\n",
    "K0, RHO, BUD = 10.0, 0.9, 6.0\n",
    "def J(u0):\n",
    "    K1 = RHO*K0 + u0\n",
    "    K2 = RHO*K1 + (BUD-u0)\n",
    "    return 2*np.sqrt(K1) + 6*np.sqrt(K2)\n",
    "def J_augmented(u0):\n",
    "    \"\"\"terminal-only reformulation: augment state with accumulated reward.\"\"\"\n",
    "    K1 = RHO*K0 + u0; acc = 2*np.sqrt(K1)\n",
    "    K2 = RHO*K1 + (BUD-u0)\n",
    "    return acc + 6*np.sqrt(K2)          # terminal objective on (K2, acc)\n",
    "GRID = np.arange(0, 6.5, 0.5)\n",
    "\n",
    "# --- LP special case: max 3x+2y s.t. x+y<=6, 2x+y<=8, x,y>=0 ---\n",
    "VERTS = [(0,0),(0,6),(2,4),(4,0)]\n",
    "def lp_val(v): return 3*v[0]+2*v[1]\n",
    "\n",
    "def reference_values():\n",
    "    js = np.array([J(u) for u in GRID])\n",
    "    u_star = float(GRID[int(np.argmax(js))])\n",
    "    vstar = max(VERTS, key=lp_val)\n",
    "    return {\n",
    "        \"u0_star\": u_star,\n",
    "        \"J_star\": round(float(js.max()),4),\n",
    "        \"J_even\": round(float(J(3.0)),4),\n",
    "        \"J_backload\": round(float(J(0.0)),4),\n",
    "        \"equivalence_maxdiff\": round(float(max(abs(J(u)-J_augmented(u)) for u in GRID)),4),\n",
    "        \"lp_opt_value\": lp_val(vstar),\n",
    "        \"lp_x\": vstar[0], \"lp_y\": vstar[1],\n",
    "        \"n_vertices\": len(VERTS),\n",
    "    }\n",
    "if __name__ == \"__main__\":\n",
    "    [print(f\"{k:20s} {v}\") for k,v in reference_values().items()]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "52cfae5d",
   "metadata": {},
   "source": [
    "## Panel 1 — The canonical GEOP, two periods\n",
    "$K_1 = 0.9K_0 + u_0$, $K_2 = 0.9K_1 + u_1$, budget $u_0 + u_1 = 6$; objective\n",
    "$J = 2\\sqrt{K_1} + 6\\sqrt{K_2}$ (running + terminal). The interior FOC demands\n",
    "$K_2 = 0.09K_1$ — unreachable within the budget — so the optimum sits at the\n",
    "corner $u_0^* = 6$: **front-load everything**. Early capital earns twice: it\n",
    "scores in the running term AND compounds (at 0.9) into the terminal one."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "232698b7",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-13T23:39:09.297167Z",
     "iopub.status.busy": "2026-07-13T23:39:09.296876Z",
     "iopub.status.idle": "2026-07-13T23:39:09.517554Z",
     "shell.execute_reply": "2026-07-13T23:39:09.516498Z"
    }
   },
   "outputs": [],
   "source": [
    "js = np.array([J(u) for u in GRID])\n",
    "fig, ax = plt.subplots(figsize=(8.0,4.2))\n",
    "ax.plot(GRID, js, \"o-\", c=\"#C8A24B\", lw=2.2, ms=5)\n",
    "ax.scatter([GRID[np.argmax(js)]],[js.max()], c=\"#0B3D2E\", s=110, zorder=5, label=f\"u0* = {GRID[np.argmax(js)]:.1f}, J* = {js.max():.4f}\")\n",
    "ax.set(xlabel=\"u0 (period-0 investment; u1 = 6 − u0)\", ylabel=\"J\", title=\"Front-loading wins at the corner — seed 26202\")\n",
    "ax.legend(frameon=False); ax.grid(alpha=.25); plt.tight_layout(); plt.show()\n",
    "print(f\"J(front-load 6) = {J(6.0):.4f}   J(even 3) = {J(3.0):.4f}   J(back-load 0) = {J(0.0):.4f}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c92bdc6d",
   "metadata": {},
   "source": [
    "## Panel 2 — The Objective Equivalence Theorem, executed\n",
    "Running objectives can be absorbed into terminal ones by augmenting the state\n",
    "with an accumulator. Both formulations computed across the whole grid: maximum\n",
    "difference **0.0000**. The theorem is why solvers may standardize on terminal\n",
    "form without loss — a formulation freedom Volume II uses constantly."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ca08323a",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-13T23:39:09.520008Z",
     "iopub.status.busy": "2026-07-13T23:39:09.519286Z",
     "iopub.status.idle": "2026-07-13T23:39:09.525467Z",
     "shell.execute_reply": "2026-07-13T23:39:09.524456Z"
    }
   },
   "outputs": [],
   "source": [
    "diffs = [abs(J(u)-J_augmented(u)) for u in GRID]\n",
    "for u, d in zip(GRID, diffs):\n",
    "    if u in (0.0, 3.0, 6.0): print(f\"u0={u:3.1f}   J_running_form={J(u):9.4f}   J_terminal_form={J_augmented(u):9.4f}   |diff|={d:.6f}\")\n",
    "print(f\"\\nmax |difference| over the grid: {max(diffs):.6f}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "de2f07a6",
   "metadata": {},
   "source": [
    "## Panel 3 — A classic, as a GEOP special case\n",
    "The LP $\\max 3x + 2y$ s.t. $x+y \\le 6$, $2x+y \\le 8$, $x,y \\ge 0$ — Chapter\n",
    "1's objective A, now with the grid removed. Linear objective on a polyhedron:\n",
    "the optimum sits at a **vertex**, and enumerating the four feasible vertices\n",
    "finds it: $(2,4)$ at value 14 — the same point the grid crowned, now with the\n",
    "geometry explaining why. Classical Problems are GEOP Special Cases (Prop.):\n",
    "LP is the GEOP with linear everything and no dynamics."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e38a49af",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-13T23:39:09.527233Z",
     "iopub.status.busy": "2026-07-13T23:39:09.527060Z",
     "iopub.status.idle": "2026-07-13T23:39:09.765997Z",
     "shell.execute_reply": "2026-07-13T23:39:09.764855Z"
    }
   },
   "outputs": [],
   "source": [
    "fig, ax = plt.subplots(figsize=(6.4,5.0))\n",
    "xs = np.linspace(0,6,50)\n",
    "ax.fill([0,0,2,4],[0,6,4,0], color=\"#9CC3B2\", alpha=.45, label=\"feasible polyhedron\")\n",
    "ax.plot(xs, 6-xs, c=\"#1B6B52\", lw=1.5); ax.plot(xs, 8-2*xs, c=\"#1B6B52\", lw=1.5)\n",
    "for v in VERTS:\n",
    "    ax.scatter(*v, c=\"#0B3D2E\", s=90, zorder=5)\n",
    "    ax.annotate(f\"{v} → {lp_val(v)}\", v, textcoords=\"offset points\", xytext=(8,6), fontsize=10)\n",
    "ax.set(xlim=(-0.3,6.3), ylim=(-0.3,6.6), xlabel=\"x\", ylabel=\"y\", title=\"Vertex enumeration: the optimum is at (2,4)\")\n",
    "ax.legend(frameon=False, fontsize=9); ax.grid(alpha=.2); plt.tight_layout(); plt.show()\n",
    "vstar = max(VERTS, key=lp_val)\n",
    "print(f\"optimal vertex {vstar}, value {lp_val(vstar)}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2720a022",
   "metadata": {},
   "source": [
    "## Validation — agrees with `DCT_V2_Ch02_Lab.xlsx`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b294b72d",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-13T23:39:09.767983Z",
     "iopub.status.busy": "2026-07-13T23:39:09.767791Z",
     "iopub.status.idle": "2026-07-13T23:39:09.773583Z",
     "shell.execute_reply": "2026-07-13T23:39:09.772671Z"
    }
   },
   "outputs": [],
   "source": [
    "ref = reference_values()\n",
    "expected = {\"u0_star\":6.0,\"J_star\":29.7914,\"J_even\":29.2172,\"J_backload\":28.53,\n",
    " \"equivalence_maxdiff\":0.0,\"lp_opt_value\":14,\"lp_x\":2,\"lp_y\":4,\"n_vertices\":4}\n",
    "for k,v in expected.items():\n",
    "    assert abs(ref[k]-v)<5e-4, f\"MISMATCH {k}\"\n",
    "    print(f\"PASS  {k:20s} {ref[k]}\")\n",
    "print(\"\\nAll checkpoints agree — seed 26202.\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "636daaae",
   "metadata": {},
   "source": [
    "**Next**: Exercises 2.5–2.9 (Part C) vary persistence and horizon; AXIOM-02's canonical-form console rewrites your GEOP into terminal form live. Chapter 3 makes convexity the reason all of this is solvable. Solutions: IM Vol. II, Ch. 2."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "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.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
