{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "viz-0",
   "metadata": {
    "cell_marker": "\"\"\""
   },
   "source": [
    "# Drawing a recovered structure\n",
    "\n",
    "[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/Abel-ai-lab/andrey/blob/main/examples/viz.ipynb)\n",
    "\n",
    "`andrey.viz.draw` renders a recovered graph to a self-contained SVG. The grammar is simple:\n",
    "marks are drawn by shape (arrowhead, hollow circle, tail), so\n",
    "color stays a redundant channel; resolved edges are a calm graphite backbone and color is reserved\n",
    "for what remains uncertain."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7fb27b941602401d91542211134fc71a",
   "metadata": {},
   "source": [
    "## Start\n",
    "\n",
    "Choose a CPU runtime and run this cell first. In Colab it installs `andrey-core==0.9.0` from PyPI;\n",
    "local kernels use the installed environment."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "acae54e37e7d407bbb7b55eff062a284",
   "metadata": {
    "tags": [
     "colab-setup"
    ]
   },
   "outputs": [],
   "source": [
    "import os\n",
    "import sys\n",
    "\n",
    "IN_COLAB = \"google.colab\" in sys.modules\n",
    "\n",
    "if IN_COLAB:\n",
    "    os.environ[\"ANDREY_DEVICE\"] = \"cpu\"\n",
    "    !pip install --quiet \"andrey-core[viz]==0.9.0\" pandas"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "viz-1",
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import pandas as pd\n",
    "\n",
    "import andrey\n",
    "import andrey.viz"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "viz-2",
   "metadata": {},
   "source": [
    "Recover a CPDAG from linear-Gaussian data over a four-node structure: a fork `x0 -> x1`,\n",
    "`x0 -> x2`, closing into a collider at `x3`. The columns come in an order drawn from the seed, so\n",
    "their order says nothing about the causal order."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "viz-3",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "kind: cpdag\n",
      "edges: [('x1', 'x3', 'directed'), ('x1', 'x0', 'undirected'), ('x2', 'x3', 'directed'), ('x2', 'x0', 'undirected')]\n"
     ]
    }
   ],
   "source": [
    "EDGES = [(\"x0\", \"x1\"), (\"x0\", \"x2\"), (\"x1\", \"x3\"), (\"x2\", \"x3\")]  # cause first\n",
    "\n",
    "\n",
    "def sample(seed: int, n: int = 2000, shuffle: bool = True) -> pd.DataFrame:\n",
    "    \"\"\"Draw n rows of x0..x3, the columns in an order drawn from the seed.\"\"\"\n",
    "    rng = np.random.default_rng(seed)\n",
    "    x0 = rng.standard_normal(n)\n",
    "    x1 = 0.9 * x0 + rng.standard_normal(n)\n",
    "    x2 = 0.8 * x0 + rng.standard_normal(n)\n",
    "    x3 = 0.9 * x1 + 0.9 * x2 + rng.standard_normal(n)\n",
    "    frame = pd.DataFrame({\"x0\": x0, \"x1\": x1, \"x2\": x2, \"x3\": x3})\n",
    "    return frame[rng.permutation(frame.columns)] if shuffle else frame\n",
    "\n",
    "\n",
    "data = sample(seed=0)\n",
    "out = andrey.ges(data)\n",
    "print(\"kind:\", out.structure.kind)\n",
    "print(\"edges:\", out.structure.oriented_edges())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "viz-4",
   "metadata": {},
   "source": [
    "Draw it in both themes at once. `draw()` returns an `SVG`, a `str` subclass carrying a\n",
    "`_repr_html_`, so composing a figure is plain string assembly: the `Panels` helper below\n",
    "stitches the light and dark renders into one captioned row, and every piece it stitches is markup\n",
    "`draw()` already produced. Both palettes are first-class, so a figure is never shipped in one\n",
    "theme only. The DataFrame's column names label the nodes: the collider at `x3` is oriented (both\n",
    "edges the graphite backbone), and whatever the CPDAG leaves unoriented is the undirected amber."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "viz-5",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div class=\"andrey-figure-pair\"><figure><svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 360 240\" width=\"100%\" font-family=\"Inter, system-ui, sans-serif\"><rect width=\"360\" height=\"240\" fill=\"#fafbfc\"/><line x1=\"143.00\" y1=\"84.52\" x2=\"207.00\" y2=\"84.52\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"217.00,84.52 207.00,79.52 207.00,89.52\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"140.60\" y1=\"92.04\" x2=\"219.40\" y2=\"147.96\" stroke=\"#e0982b\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><line x1=\"211.24\" y1=\"97.83\" x2=\"140.60\" y2=\"147.96\" stroke=\"#3a4658\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"219.40,92.04 208.35,93.75 214.14,101.90\" fill=\"#3a4658\" stroke-linejoin=\"round\"/><line x1=\"143.00\" y1=\"155.48\" x2=\"217.00\" y2=\"155.48\" stroke=\"#e0982b\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><circle cx=\"130.00\" cy=\"84.52\" r=\"13.00\" fill=\"#ffffff\" stroke=\"#182233\" stroke-width=\"1.8\"/><text x=\"130.00\" y=\"84.52\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#182233\" dominant-baseline=\"central\">x1</text><circle cx=\"230.00\" cy=\"84.52\" r=\"13.00\" fill=\"#ffffff\" stroke=\"#182233\" stroke-width=\"1.8\"/><text x=\"230.00\" y=\"84.52\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#182233\" dominant-baseline=\"central\">x3</text><circle cx=\"130.00\" cy=\"155.48\" r=\"13.00\" fill=\"#ffffff\" stroke=\"#182233\" stroke-width=\"1.8\"/><text x=\"130.00\" y=\"155.48\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#182233\" dominant-baseline=\"central\">x2</text><circle cx=\"230.00\" cy=\"155.48\" r=\"13.00\" fill=\"#ffffff\" stroke=\"#182233\" stroke-width=\"1.8\"/><text x=\"230.00\" y=\"155.48\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#182233\" dominant-baseline=\"central\">x0</text></svg><figcaption>Light</figcaption></figure><figure><svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 360 240\" width=\"100%\" font-family=\"Inter, system-ui, sans-serif\"><rect width=\"360\" height=\"240\" fill=\"#0b1220\"/><line x1=\"143.00\" y1=\"84.52\" x2=\"207.00\" y2=\"84.52\" stroke=\"#98a4bd\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"217.00,84.52 207.00,79.52 207.00,89.52\" fill=\"#98a4bd\" stroke-linejoin=\"round\"/><line x1=\"140.60\" y1=\"92.04\" x2=\"219.40\" y2=\"147.96\" stroke=\"#f0b357\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><line x1=\"211.24\" y1=\"97.83\" x2=\"140.60\" y2=\"147.96\" stroke=\"#98a4bd\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><polygon points=\"219.40,92.04 208.35,93.75 214.14,101.90\" fill=\"#98a4bd\" stroke-linejoin=\"round\"/><line x1=\"143.00\" y1=\"155.48\" x2=\"217.00\" y2=\"155.48\" stroke=\"#f0b357\" stroke-width=\"2.0\" stroke-linecap=\"round\"/><circle cx=\"130.00\" cy=\"84.52\" r=\"13.00\" fill=\"#121a2b\" stroke=\"#e8ecf5\" stroke-width=\"1.8\"/><text x=\"130.00\" y=\"84.52\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#e8ecf5\" dominant-baseline=\"central\">x1</text><circle cx=\"230.00\" cy=\"84.52\" r=\"13.00\" fill=\"#121a2b\" stroke=\"#e8ecf5\" stroke-width=\"1.8\"/><text x=\"230.00\" y=\"84.52\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#e8ecf5\" dominant-baseline=\"central\">x3</text><circle cx=\"130.00\" cy=\"155.48\" r=\"13.00\" fill=\"#121a2b\" stroke=\"#e8ecf5\" stroke-width=\"1.8\"/><text x=\"130.00\" y=\"155.48\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#e8ecf5\" dominant-baseline=\"central\">x2</text><circle cx=\"230.00\" cy=\"155.48\" r=\"13.00\" fill=\"#121a2b\" stroke=\"#e8ecf5\" stroke-width=\"1.8\"/><text x=\"230.00\" y=\"155.48\" text-anchor=\"middle\" font-size=\"11\" font-weight=\"600\" fill=\"#e8ecf5\" dominant-baseline=\"central\">x0</text></svg><figcaption>Dark</figcaption></figure></div>"
      ],
      "text/plain": [
       "<__main__.Panels at 0x155550c30440>"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "class Panels:\n",
    "    \"\"\"A labeled row of renders; ``_repr_html_`` lets the notebooks embed it.\"\"\"\n",
    "\n",
    "    def __init__(self, **renders: str) -> None:\n",
    "        self.renders = renders\n",
    "\n",
    "    def _repr_html_(self) -> str:\n",
    "        cells = \"\".join(\n",
    "            f\"<figure>{svg}<figcaption>{label}</figcaption></figure>\"\n",
    "            for label, svg in self.renders.items()\n",
    "        )\n",
    "        return f'<div class=\"andrey-figure-pair\">{cells}</div>'\n",
    "\n",
    "\n",
    "Panels(Light=andrey.viz.draw(out.structure), Dark=andrey.viz.draw(out.structure, dark=True))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "viz-6",
   "metadata": {},
   "source": [
    "Each render is also just a string. `background=False` drops the paper fill for a transparent SVG\n",
    "that composites onto an already-themed page, and the markup writes straight to a file."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "viz-7",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "markup: <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 360 240 ...\n",
      "bytes: 1679\n"
     ]
    }
   ],
   "source": [
    "svg = andrey.viz.draw(out.structure, background=False)\n",
    "print(\"markup:\", svg[:60], \"...\")\n",
    "print(\"bytes:\", len(svg))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "viz-8",
   "metadata": {},
   "source": []
  }
 ],
 "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.12"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
