{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "db4d5d46",
   "metadata": {},
   "source": [
    "# DCT Laboratory — Volume II, Chapter 10\n",
    "## Neuro-Fuzzy Robust Enterprise Optimization\n",
    "**Seed `26210`** · Companion to the chapter and AXIOM Module **AXIOM-10 (Vol. II)**\n",
    "\n",
    "Chapter 9 priced *known* randomness; this chapter handles **ambiguity** —\n",
    "uncertainty about the model itself. Three acts: triangular memberships forming\n",
    "a **partition of unity**, a three-rule **Sugeno system** whose smooth blend is\n",
    "5.3× more robust at the regime boundary than a crisp switch, and the **ANFIS\n",
    "least-squares step** — expert rules calibrated against data, RMSE falling from\n",
    "11.78 to 0.0003. Mirrored in `DCT_V2_Ch10_Lab.xlsx`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7de7e37e",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-14T00:28:16.059189Z",
     "iopub.status.busy": "2026-07-14T00:28:16.058860Z",
     "iopub.status.idle": "2026-07-14T00:28:16.618521Z",
     "shell.execute_reply": "2026-07-14T00:28:16.617085Z"
    }
   },
   "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 = 26210\n",
    "# --- memberships: Low tri(0,0,5), Med tri(0,5,10), High tri(5,10,10) on [0,10] ---\n",
    "def mu_L(x): return max(0.0, (5-x)/5) if x <= 5 else 0.0\n",
    "def mu_M(x): return max(0.0, x/5) if x <= 5 else max(0.0, (10-x)/5)\n",
    "def mu_H(x): return max(0.0, (x-5)/5) if x >= 5 else 0.0\n",
    "# --- hand-written (expert) Sugeno consequents ---\n",
    "EXPERT = {\"L\": (1.0, 0.2), \"M\": (3.0, 0.6), \"H\": (2.0, 1.0)}\n",
    "def sugeno(x, params):\n",
    "    ws = np.array([mu_L(x), mu_M(x), mu_H(x)])\n",
    "    ys = np.array([a + b*x for a, b in (params[\"L\"], params[\"M\"], params[\"H\"])])\n",
    "    return float(ws @ ys / ws.sum())\n",
    "# --- crisp switch policy (threshold at x = 5 between M and H rules) ---\n",
    "def crisp(x, params):\n",
    "    a, b = params[\"M\"] if x < 5 else params[\"H\"]\n",
    "    return a + b*x\n",
    "# --- the ANFIS least-squares step against an enterprise cost curve ---\n",
    "def target(x): return 20 - 3*x + 0.35*x*x\n",
    "XS = np.arange(0, 11, 1.0)\n",
    "def fit_lse():\n",
    "    rows = []\n",
    "    for x in XS:\n",
    "        ws = np.array([mu_L(x), mu_M(x), mu_H(x)]); ws = ws/ws.sum()\n",
    "        rows.append([ws[0], ws[0]*x, ws[1], ws[1]*x, ws[2], ws[2]*x])\n",
    "    A = np.array(rows); y = target(XS)\n",
    "    theta, *_ = np.linalg.lstsq(A, y, rcond=None)\n",
    "    theta = np.round(theta, 4)          # the rounded params ARE the deliverable model\n",
    "    return {\"L\": (theta[0], theta[1]), \"M\": (theta[2], theta[3]), \"H\": (theta[4], theta[5])}\n",
    "def rmse(params):\n",
    "    return float(np.sqrt(np.mean([(sugeno(x, params)-target(x))**2 for x in XS])))\n",
    "\n",
    "def reference_values():\n",
    "    fit = fit_lse()\n",
    "    r0, r1 = rmse(EXPERT), rmse(fit)\n",
    "    return {\n",
    "        \"mu_L_3\": round(mu_L(3),4), \"mu_M_3\": round(mu_M(3),4), \"mu_H_3\": round(mu_H(3),4),\n",
    "        \"partition_sum_3\": round(mu_L(3)+mu_M(3)+mu_H(3),4),\n",
    "        \"yhat_3\": round(sugeno(3, EXPERT),4), \"yhat_7\": round(sugeno(7, EXPERT),4),\n",
    "        \"crisp_jump\": round(abs(crisp(5.1, EXPERT)-crisp(4.9, EXPERT)),4),\n",
    "        \"fuzzy_delta\": round(abs(sugeno(5.1, EXPERT)-sugeno(4.9, EXPERT)),4),\n",
    "        \"robustness_ratio\": round(abs(crisp(5.1, EXPERT)-crisp(4.9, EXPERT))/abs(sugeno(5.1, EXPERT)-sugeno(4.9, EXPERT)),4),\n",
    "        \"rmse_expert\": round(r0,4), \"rmse_fitted\": round(r1,4),\n",
    "        \"yfit_3\": round(sugeno(3, fit_lse()),4), \"yfit_7\": round(sugeno(7, fit_lse()),4),\n",
    "    }\n",
    "if __name__ == \"__main__\":\n",
    "    fit = fit_lse()\n",
    "    print(\"fitted consequents:\", {k:(round(a,4),round(b,4)) for k,(a,b) in fit.items()})\n",
    "    [print(f\"{k:20s} {v}\") for k,v in reference_values().items()]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7b40a323",
   "metadata": {},
   "source": [
    "## Panel 1 — Membership functions: graded truth\n",
    "The Enterprise Linguistic Variable (Def.) \"demand\" over $[0, 10]$: Low, Medium,\n",
    "High as triangular Membership Functions (Def.). At $x = 3$: $\\mu_L = 0.4$,\n",
    "$\\mu_M = 0.6$, $\\mu_H = 0$ — demand is *somewhat low and rather medium*, both\n",
    "at once, and the memberships **sum to one** everywhere (a partition of unity:\n",
    "fuzzification loses nothing, it redistributes). This is the Fuzzy Enterprise\n",
    "Representation Theorem's raw material: vague managerial language, made exact\n",
    "enough to compute with."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "043f3207",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-14T00:28:16.620962Z",
     "iopub.status.busy": "2026-07-14T00:28:16.620563Z",
     "iopub.status.idle": "2026-07-14T00:28:16.842327Z",
     "shell.execute_reply": "2026-07-14T00:28:16.840964Z"
    }
   },
   "outputs": [],
   "source": [
    "xs = np.linspace(0, 10, 400)\n",
    "fig, ax = plt.subplots(figsize=(7.8,3.8))\n",
    "for f, lab, c in ((mu_L,\"Low\",\"#8A8F8B\"),(mu_M,\"Medium\",\"#C8A24B\"),(mu_H,\"High\",\"#0B3D2E\")):\n",
    "    ax.plot(xs, [f(x) for x in xs], lw=2.2, label=lab, c=c)\n",
    "ax.axvline(3, c=\"#B0532F\", ls=\":\", lw=1.4)\n",
    "ax.set(xlabel=\"demand x\", ylabel=\"membership μ\", title=\"Low / Medium / High — a partition of unity (seed 26210)\")\n",
    "ax.legend(frameon=False); ax.grid(alpha=.25); plt.tight_layout(); plt.show()\n",
    "print(f\"at x=3: mu_L={mu_L(3):.4f}  mu_M={mu_M(3):.4f}  mu_H={mu_H(3):.4f}  sum={mu_L(3)+mu_M(3)+mu_H(3):.4f}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4959fc0d",
   "metadata": {},
   "source": [
    "## Panel 2 — Sugeno inference: rules that blend\n",
    "The Enterprise Fuzzy Rule Base (Def.), hand-written by an expert: IF demand is\n",
    "Low THEN $y = 1 + 0.2x$; Medium: $y = 3 + 0.6x$; High: $y = 2 + x$. The Sugeno\n",
    "output is the membership-weighted blend — $\\hat{y}(3) = 3.52$,\n",
    "$\\hat{y}(7) = 7.92$. The payoff is at the regime boundary: a **crisp** switch\n",
    "at $x = 5$ jumps by 1.16 as the input crosses; the fuzzy blend moves 0.22 —\n",
    "**5.3× more robust** to the boundary-straddling inputs where misclassification\n",
    "lives (Neuro-Fuzzy Robustness Theorem in miniature)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d39580f1",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-14T00:28:16.844506Z",
     "iopub.status.busy": "2026-07-14T00:28:16.844288Z",
     "iopub.status.idle": "2026-07-14T00:28:17.028439Z",
     "shell.execute_reply": "2026-07-14T00:28:17.027595Z"
    }
   },
   "outputs": [],
   "source": [
    "xs = np.linspace(0, 10, 400)\n",
    "fig, ax = plt.subplots(figsize=(7.8,4.0))\n",
    "ax.plot(xs, [crisp(x, EXPERT) for x in xs], c=\"#8A8F8B\", lw=2, label=\"crisp switch at x = 5\")\n",
    "ax.plot(xs, [sugeno(x, EXPERT) for x in xs], c=\"#C8A24B\", lw=2.4, label=\"fuzzy Sugeno blend\")\n",
    "ax.axvspan(4.9, 5.1, color=\"#B0532F\", alpha=.15, label=\"boundary ±0.1\")\n",
    "ax.set(xlabel=\"demand x\", ylabel=\"decision y\", title=\"The blend has no cliff — seed 26210\")\n",
    "ax.legend(frameon=False, fontsize=9); ax.grid(alpha=.25); plt.tight_layout(); plt.show()\n",
    "print(f\"yhat(3) = {sugeno(3,EXPERT):.4f}   yhat(7) = {sugeno(7,EXPERT):.4f}\")\n",
    "print(f\"crisp jump across x=5: {abs(crisp(5.1,EXPERT)-crisp(4.9,EXPERT)):.4f}   fuzzy change: {abs(sugeno(5.1,EXPERT)-sugeno(4.9,EXPERT)):.4f}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c4a77a9f",
   "metadata": {},
   "source": [
    "## Panel 3 — The ANFIS step: rules, calibrated\n",
    "The expert's rules have the right *grammar* and the wrong *numbers*: against\n",
    "the true enterprise cost curve $f(x) = 20 - 3x + 0.35x^2$ they score RMSE\n",
    "**11.78**. ANFIS's least-squares pass (Adaptive Neuro-Fuzzy Inference System,\n",
    "Def.) keeps the memberships and refits the consequents — a linear problem in\n",
    "the normalized-weight design matrix. Result: RMSE **0.0003** on the same 11\n",
    "points; the fitted surface passes through the data (ANFIS Approximation\n",
    "Theorem, exhibited). The enterprise reading: linguistic structure from\n",
    "experts, coefficients from data — Learning Enterprise Decision Rules (§10.9)\n",
    "as a two-layer division of labor."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0d57e10d",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-14T00:28:17.030741Z",
     "iopub.status.busy": "2026-07-14T00:28:17.030523Z",
     "iopub.status.idle": "2026-07-14T00:28:17.256109Z",
     "shell.execute_reply": "2026-07-14T00:28:17.254783Z"
    }
   },
   "outputs": [],
   "source": [
    "fit = fit_lse()\n",
    "print(\"fitted consequents (a, b) per rule:\", {k:(round(a,4),round(b,4)) for k,(a,b) in fit.items()})\n",
    "xs = np.linspace(0, 10, 400)\n",
    "fig, ax = plt.subplots(figsize=(7.8,4.0))\n",
    "ax.plot(xs, [target(x) for x in xs], c=\"#17231F\", lw=2, ls=\"--\", label=\"true cost curve\")\n",
    "ax.plot(xs, [sugeno(x, EXPERT) for x in xs], c=\"#8A8F8B\", lw=2, label=f\"expert rules (RMSE {rmse(EXPERT):.2f})\")\n",
    "ax.plot(xs, [sugeno(x, fit) for x in xs], c=\"#C8A24B\", lw=2.4, label=f\"ANFIS-calibrated (RMSE {rmse(fit):.4f})\")\n",
    "ax.scatter(XS, target(XS), c=\"#0B3D2E\", s=28, zorder=5, label=\"data\")\n",
    "ax.set(xlabel=\"x\", ylabel=\"cost\", title=\"Same rule grammar, learned coefficients (seed 26210)\")\n",
    "ax.legend(frameon=False, fontsize=9); ax.grid(alpha=.25); plt.tight_layout(); plt.show()\n",
    "print(f\"yfit(3) = {sugeno(3,fit):.4f}   yfit(7) = {sugeno(7,fit):.4f}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0b8c2937",
   "metadata": {},
   "source": [
    "## Validation — agrees with `DCT_V2_Ch10_Lab.xlsx`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "73592ea8",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-07-14T00:28:17.258880Z",
     "iopub.status.busy": "2026-07-14T00:28:17.258089Z",
     "iopub.status.idle": "2026-07-14T00:28:17.268367Z",
     "shell.execute_reply": "2026-07-14T00:28:17.266832Z"
    }
   },
   "outputs": [],
   "source": [
    "ref = reference_values()\n",
    "expected = {\"mu_L_3\":0.4,\"mu_M_3\":0.6,\"mu_H_3\":0.0,\"partition_sum_3\":1.0,\n",
    " \"yhat_3\":3.52,\"yhat_7\":7.92,\"crisp_jump\":1.16,\"fuzzy_delta\":0.22,\n",
    " \"robustness_ratio\":5.2727,\"rmse_expert\":11.777,\"rmse_fitted\":0.0003,\n",
    " \"yfit_3\":14.1499,\"yfit_7\":16.1497}\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 26210.\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "719e5fe8",
   "metadata": {},
   "source": [
    "**Next**: Exercises 10.5–10.9 (Part C) move the membership breakpoints and re-fit; AXIOM-10's rule workbench lets you write rules in words and watch the surface. Chapter 11 makes ambiguity adversarial: distributionally robust optimization. Solutions: IM Vol. II, Ch. 10."
   ]
  }
 ],
 "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
}
