{
  "nbformat": 4,
  "nbformat_minor": 5,
  "metadata": {
    "kernelspec": {
      "display_name": "Python 3",
      "language": "python",
      "name": "python3"
    },
    "language_info": {
      "name": "python",
      "version": "3.12.14"
    },
    "research_runtime": {
      "engine": "pyodide",
      "packages": [],
      "assets": [
        "experiment.py",
        "figure_generation.py"
      ],
      "generated_assets": [
        "results.json",
        "figure.svg"
      ],
      "display_assets": [
        "figure.svg"
      ],
      "filesystem_root": ".",
      "network_required": false,
      "validation_engine": "CPython standard-library execution"
    }
  },
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "# Is a safety constraint the same as a large negative reward?\n\nExact enumeration of a one-step constrained decision and fixed-penalty alternatives; not an implementation of CPO or a real-world safety controller.\n\nThis notebook runs the complete saved experiment, checks its assertions, and regenerates the SVG from the computed results. Python 3.10+ and standard-library modules only. Place experiment.py and figure_generation.py beside the notebook. The exact loaded assets are recorded in notebook metadata.\n"
      ],
      "id": "cell-1"
    },
    {
      "cell_type": "code",
      "metadata": {},
      "execution_count": 1,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Constrained: p=0.20, reward=5.20, cost=0.60\n",
            "lambda=0.5: optimal p range [1.00, 1.00]\n",
            "lambda=1.5: optimal p range [1.00, 1.00]\n",
            "lambda=2.0: optimal p range [0.00, 1.00]\n",
            "lambda=3.0: optimal p range [0.00, 0.00]\n",
            "At constrained optimum, cost-3 episodes occur with probability 0.20\n",
            "Budget sweep: each extra cost unit buys 2 reward units until budget 3.\n",
            "At budget 0.6, the expected-cost-optimal policy still exceeds that budget in 20% of episodes.\n"
          ]
        }
      ],
      "source": [
        "import runpy\n_ = runpy.run_path('experiment.py', run_name='__main__')\n"
      ],
      "id": "cell-2"
    },
    {
      "cell_type": "code",
      "metadata": {},
      "execution_count": 2,
      "outputs": [
        {
          "output_type": "stream",
          "name": "stdout",
          "text": [
            "Regenerated figure.svg from results.json.\n"
          ]
        }
      ],
      "source": [
        "runpy.run_path('figure_generation.py', run_name='__main__')\n",
        "print('Regenerated figure.svg from results.json.')\n",
        "\n",
        "import base64\n",
        "print('__NOTEBOOK_IMAGE__svg+xml:' + base64.b64encode(open('figure.svg','rb').read()).decode())\n"
      ],
      "id": "cell-3"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Inspect the saved result\n\nThe article's static figure is available before running code. The regenerated figure.svg uses results.json, and every plotted quantity comes from the experiment.\n\n![Computed figure](figure.svg)\n\nChange an input in experiment.py, rerun both code cells, then compare the saved result and figure.\n"
      ],
      "id": "cell-4"
    }
  ]
}
