{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Requirement already satisfied: torch in /Users/mhchia/Library/Caches/pypoetry/virtualenvs/zkstats-brXmXluj-py3.12/lib/python3.12/site-packages (2.2.2)\n",
      "Requirement already satisfied: zkstats==0.1.5 in /Users/mhchia/Library/Caches/pypoetry/virtualenvs/zkstats-brXmXluj-py3.12/lib/python3.12/site-packages (0.1.5)\n",
      "Requirement already satisfied: click<9.0.0,>=8.1.7 in /Users/mhchia/Library/Caches/pypoetry/virtualenvs/zkstats-brXmXluj-py3.12/lib/python3.12/site-packages (from zkstats==0.1.5) (8.1.7)\n",
      "Requirement already satisfied: ezkl==9.1.0 in /Users/mhchia/Library/Caches/pypoetry/virtualenvs/zkstats-brXmXluj-py3.12/lib/python3.12/site-packages (from zkstats==0.1.5) (9.1.0)\n",
      "Requirement already satisfied: matplotlib<4.0.0,>=3.8.2 in /Users/mhchia/Library/Caches/pypoetry/virtualenvs/zkstats-brXmXluj-py3.12/lib/python3.12/site-packages (from zkstats==0.1.5) (3.8.3)\n",
      "Requirement already satisfied: numpy<2.0.0,>=1.26.2 in /Users/mhchia/Library/Caches/pypoetry/virtualenvs/zkstats-brXmXluj-py3.12/lib/python3.12/site-packages (from zkstats==0.1.5) (1.26.4)\n",
      "Requirement already satisfied: onnx<2.0.0,>=1.15.0 in /Users/mhchia/Library/Caches/pypoetry/virtualenvs/zkstats-brXmXluj-py3.12/lib/python3.12/site-packages (from zkstats==0.1.5) (1.15.0)\n",
      "Requirement already satisfied: requests<3.0.0,>=2.31.0 in /Users/mhchia/Library/Caches/pypoetry/virtualenvs/zkstats-brXmXluj-py3.12/lib/python3.12/site-packages (from zkstats==0.1.5) (2.31.0)\n",
      "Requirement already satisfied: scipy<2.0.0,>=1.11.4 in /Users/mhchia/Library/Caches/pypoetry/virtualenvs/zkstats-brXmXluj-py3.12/lib/python3.12/site-packages (from zkstats==0.1.5) (1.12.0)\n",
      "Requirement already satisfied: statistics<2.0.0,>=1.0.3 in /Users/mhchia/Library/Caches/pypoetry/virtualenvs/zkstats-brXmXluj-py3.12/lib/python3.12/site-packages (from zkstats==0.1.5) (1.0.3.5)\n",
      "Requirement already satisfied: filelock in /Users/mhchia/Library/Caches/pypoetry/virtualenvs/zkstats-brXmXluj-py3.12/lib/python3.12/site-packages (from torch) (3.13.1)\n",
      "Requirement already satisfied: typing-extensions>=4.8.0 in /Users/mhchia/Library/Caches/pypoetry/virtualenvs/zkstats-brXmXluj-py3.12/lib/python3.12/site-packages (from torch) (4.10.0)\n",
      "Requirement already satisfied: sympy in /Users/mhchia/Library/Caches/pypoetry/virtualenvs/zkstats-brXmXluj-py3.12/lib/python3.12/site-packages (from torch) (1.12)\n",
      "Requirement already satisfied: networkx in /Users/mhchia/Library/Caches/pypoetry/virtualenvs/zkstats-brXmXluj-py3.12/lib/python3.12/site-packages (from torch) (3.2.1)\n",
      "Requirement already satisfied: jinja2 in /Users/mhchia/Library/Caches/pypoetry/virtualenvs/zkstats-brXmXluj-py3.12/lib/python3.12/site-packages (from torch) (3.1.3)\n",
      "Requirement already satisfied: fsspec in /Users/mhchia/Library/Caches/pypoetry/virtualenvs/zkstats-brXmXluj-py3.12/lib/python3.12/site-packages (from torch) (2024.2.0)\n",
      "Requirement already satisfied: contourpy>=1.0.1 in /Users/mhchia/Library/Caches/pypoetry/virtualenvs/zkstats-brXmXluj-py3.12/lib/python3.12/site-packages (from matplotlib<4.0.0,>=3.8.2->zkstats==0.1.5) (1.2.0)\n",
      "Requirement already satisfied: cycler>=0.10 in /Users/mhchia/Library/Caches/pypoetry/virtualenvs/zkstats-brXmXluj-py3.12/lib/python3.12/site-packages (from matplotlib<4.0.0,>=3.8.2->zkstats==0.1.5) (0.12.1)\n",
      "Requirement already satisfied: fonttools>=4.22.0 in /Users/mhchia/Library/Caches/pypoetry/virtualenvs/zkstats-brXmXluj-py3.12/lib/python3.12/site-packages (from matplotlib<4.0.0,>=3.8.2->zkstats==0.1.5) (4.49.0)\n",
      "Requirement already satisfied: kiwisolver>=1.3.1 in /Users/mhchia/Library/Caches/pypoetry/virtualenvs/zkstats-brXmXluj-py3.12/lib/python3.12/site-packages (from matplotlib<4.0.0,>=3.8.2->zkstats==0.1.5) (1.4.5)\n",
      "Requirement already satisfied: packaging>=20.0 in /Users/mhchia/Library/Caches/pypoetry/virtualenvs/zkstats-brXmXluj-py3.12/lib/python3.12/site-packages (from matplotlib<4.0.0,>=3.8.2->zkstats==0.1.5) (23.2)\n",
      "Requirement already satisfied: pillow>=8 in /Users/mhchia/Library/Caches/pypoetry/virtualenvs/zkstats-brXmXluj-py3.12/lib/python3.12/site-packages (from matplotlib<4.0.0,>=3.8.2->zkstats==0.1.5) (10.2.0)\n",
      "Requirement already satisfied: pyparsing>=2.3.1 in /Users/mhchia/Library/Caches/pypoetry/virtualenvs/zkstats-brXmXluj-py3.12/lib/python3.12/site-packages (from matplotlib<4.0.0,>=3.8.2->zkstats==0.1.5) (3.1.1)\n",
      "Requirement already satisfied: python-dateutil>=2.7 in /Users/mhchia/Library/Caches/pypoetry/virtualenvs/zkstats-brXmXluj-py3.12/lib/python3.12/site-packages (from matplotlib<4.0.0,>=3.8.2->zkstats==0.1.5) (2.9.0.post0)\n",
      "Requirement already satisfied: protobuf>=3.20.2 in /Users/mhchia/Library/Caches/pypoetry/virtualenvs/zkstats-brXmXluj-py3.12/lib/python3.12/site-packages (from onnx<2.0.0,>=1.15.0->zkstats==0.1.5) (4.25.3)\n",
      "Requirement already satisfied: charset-normalizer<4,>=2 in /Users/mhchia/Library/Caches/pypoetry/virtualenvs/zkstats-brXmXluj-py3.12/lib/python3.12/site-packages (from requests<3.0.0,>=2.31.0->zkstats==0.1.5) (3.3.2)\n",
      "Requirement already satisfied: idna<4,>=2.5 in /Users/mhchia/Library/Caches/pypoetry/virtualenvs/zkstats-brXmXluj-py3.12/lib/python3.12/site-packages (from requests<3.0.0,>=2.31.0->zkstats==0.1.5) (3.6)\n",
      "Requirement already satisfied: urllib3<3,>=1.21.1 in /Users/mhchia/Library/Caches/pypoetry/virtualenvs/zkstats-brXmXluj-py3.12/lib/python3.12/site-packages (from requests<3.0.0,>=2.31.0->zkstats==0.1.5) (2.2.1)\n",
      "Requirement already satisfied: certifi>=2017.4.17 in /Users/mhchia/Library/Caches/pypoetry/virtualenvs/zkstats-brXmXluj-py3.12/lib/python3.12/site-packages (from requests<3.0.0,>=2.31.0->zkstats==0.1.5) (2024.2.2)\n",
      "Requirement already satisfied: docutils>=0.3 in /Users/mhchia/Library/Caches/pypoetry/virtualenvs/zkstats-brXmXluj-py3.12/lib/python3.12/site-packages (from statistics<2.0.0,>=1.0.3->zkstats==0.1.5) (0.20.1)\n",
      "Requirement already satisfied: MarkupSafe>=2.0 in /Users/mhchia/Library/Caches/pypoetry/virtualenvs/zkstats-brXmXluj-py3.12/lib/python3.12/site-packages (from jinja2->torch) (2.1.5)\n",
      "Requirement already satisfied: mpmath>=0.19 in /Users/mhchia/Library/Caches/pypoetry/virtualenvs/zkstats-brXmXluj-py3.12/lib/python3.12/site-packages (from sympy->torch) (1.3.0)\n",
      "Requirement already satisfied: six>=1.5 in /Users/mhchia/Library/Caches/pypoetry/virtualenvs/zkstats-brXmXluj-py3.12/lib/python3.12/site-packages (from python-dateutil>=2.7->matplotlib<4.0.0,>=3.8.2->zkstats==0.1.5) (1.16.0)\n",
      "\n",
      "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m A new release of pip is available: \u001b[0m\u001b[31;49m23.3.1\u001b[0m\u001b[39;49m -> \u001b[0m\u001b[32;49m24.0\u001b[0m\n",
      "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m To update, run: \u001b[0m\u001b[32;49mpip install --upgrade pip\u001b[0m\n",
      "Note: you may need to restart the kernel to use updated packages.\n"
     ]
    }
   ],
   "source": [
    "%pip install torch zkstats==0.1.5"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "import json\n",
    "import os\n",
    "from zkstats.core import (\n",
    "    prover_gen_settings,\n",
    "    prover_gen_proof,\n",
    "    setup,\n",
    "    verifier_verify,\n",
    ")\n",
    "from zkstats.computation import computation_to_model\n",
    "\n",
    "cwd = os.getcwd()\n",
    "\n",
    "# FIXME: fill this in with the path to your data\n",
    "data_path = f\"{cwd}/data.json\"\n",
    "\n",
    "# Paths to the output files\n",
    "output_dir = f\"{cwd}/out\"\n",
    "os.makedirs(output_dir, exist_ok=True)\n",
    "model_onnx_path = f\"{output_dir}/model.onnx\"\n",
    "compiled_model_path = f\"{output_dir}/model.compiled\"\n",
    "\n",
    "pk_path = f\"{output_dir}/model.pk\"\n",
    "vk_path = f\"{output_dir}/model.vk\"\n",
    "proof_path = f\"{output_dir}/model.pf\"\n",
    "settings_path = f\"{output_dir}/settings.json\"\n",
    "witness_path = f\"{output_dir}/witness.json\"\n",
    "comb_data_path = f\"{output_dir}/comb_data.json\"\n",
    "data_commitment_path = f\"{output_dir}/data_commitment.json\""
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Columns selected by the user"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [],
   "source": [
    "# FIXME: this should be provided by users\n",
    "selected_columns = [\"x\", \"y\"]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## User-defined Computation\n",
    "\n",
    "A computation should be of type `TComputation`. For example, the following code snippet defines a computation that computes the sum of the private data.\n",
    "\n",
    "```python\n",
    "def computation(state: State, x: list[torch.Tensor]):\n",
    "    out_0 = state.median(x[0])\n",
    "    out_1 = state.median(x[1])\n",
    "    return state.mean(torch.tensor([out_0, out_1]).reshape(1,-1,1))\n",
    "```\n",
    "\n",
    "FIXME: The following code snippet is entirely from the user. You MUST check\n",
    "1. the code only performs zkstats-related operations.\n",
    "2. the computation must not leak any information about the private data."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [],
   "source": [
    "# This is just a dummy computation. Replace it with user's computation\n",
    "import torch\n",
    "from zkstats.computation import State\n",
    "\n",
    "def computation(state: State, x: list[torch.Tensor]):\n",
    "    out_0 = state.median(x[0])\n",
    "    out_1 = state.median(x[1])\n",
    "    return state.mean(torch.tensor([out_0, out_1]).reshape(1,-1,1))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Generate settings and setup with user's computation."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/var/folders/t3/5psrvr1x0w1_6n9kx2n7d9700000gn/T/ipykernel_22219/2234676788.py:8: TracerWarning: torch.tensor results are registered as constants in the trace. You can safely ignore this warning if you use this function to create tensors out of constant variables that would be the same every time you call this function. In any other case, this might cause the trace to be incorrect.\n",
      "  return state.mean(torch.tensor([out_0, out_1]).reshape(1,-1,1))\n",
      "/var/folders/t3/5psrvr1x0w1_6n9kx2n7d9700000gn/T/ipykernel_22219/2234676788.py:8: TracerWarning: Converting a tensor to a Python float might cause the trace to be incorrect. We can't record the data flow of Python values, so this value will be treated as a constant in the future. This means that the trace might not generalize to other inputs!\n",
      "  return state.mean(torch.tensor([out_0, out_1]).reshape(1,-1,1))\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "==== Generate & Calibrate Setting ====\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\n",
      "\n",
      " <------------- Numerical Fidelity Report (input_scale: 11, param_scale: 11, scale_input_multiplier: 10) ------------->\n",
      "\n",
      "+----------------+----------------+-----------+----------------+----------------+------------------+---------------+---------------+--------------------+--------------------+------------------------+\n",
      "| mean_error     | median_error   | max_error | min_error      | mean_abs_error | median_abs_error | max_abs_error | min_abs_error | mean_squared_error | mean_percent_error | mean_abs_percent_error |\n",
      "+----------------+----------------+-----------+----------------+----------------+------------------+---------------+---------------+--------------------+--------------------+------------------------+\n",
      "| -0.00004863739 | -0.00009727478 | 0         | -0.00009727478 | 0.00004863739  | 0.00009727478    | 0.00009727478 | 0             | 0.0000000047311914 | -0.000001957239    | 0.000001957239         |\n",
      "+----------------+----------------+-----------+----------------+----------------+------------------+---------------+---------------+--------------------+--------------------+------------------------+\n",
      "\n",
      "\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "scale:  default\n",
      "setting:  {\"run_args\":{\"tolerance\":{\"val\":0.0,\"scale\":1.0},\"input_scale\":11,\"param_scale\":11,\"scale_rebase_multiplier\":10,\"lookup_range\":[-219952,192922],\"logrows\":19,\"num_inner_cols\":2,\"variables\":[[\"batch_size\",1]],\"input_visibility\":{\"Hashed\":{\"hash_is_public\":true,\"outlets\":[]}},\"output_visibility\":\"Public\",\"param_visibility\":\"Private\",\"div_rebasing\":false,\"rebase_frac_zero_constants\":false,\"check_mode\":\"UNSAFE\"},\"num_rows\":2624,\"total_assignments\":735,\"total_const_size\":152,\"model_instance_shapes\":[[1],[1]],\"model_output_scales\":[0,11],\"model_input_scales\":[11,11],\"module_sizes\":{\"kzg\":[],\"poseidon\":[2624,[2]]},\"required_lookups\":[{\"Floor\":{\"scale\":2.0}},\"ReLU\",{\"GreaterThan\":{\"a\":0.0}}],\"required_range_checks\":[],\"check_mode\":\"UNSAFE\",\"version\":\"9.1.0\",\"num_blinding_factors\":null,\"timestamp\":1712764579432}\n"
     ]
    }
   ],
   "source": [
    "_, model = computation_to_model(computation)\n",
    "prover_gen_settings(\n",
    "    data_path,\n",
    "    selected_columns,\n",
    "    comb_data_path,\n",
    "    model,\n",
    "    model_onnx_path,\n",
    "    \"default\",\n",
    "    \"resources\",\n",
    "    settings_path,\n",
    ")\n",
    "\n",
    "# Determine which srs to use with the logrows in the settings.json\n",
    "with open(settings_path, \"r\") as f:\n",
    "    settings = json.load(f)\n",
    "logrows = int(settings[\"run_args\"][\"logrows\"])\n",
    "srs_path = f'~/.ezkl/srs/kzg{logrows}.srs'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "==== setting up ezkl ====\n",
      "Time setup: 89.43912076950073 seconds\n"
     ]
    }
   ],
   "source": [
    "setup(model_onnx_path, compiled_model_path, settings_path, vk_path, pk_path)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Generate proof with your data and user's computation."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "==== Generating Witness ====\n",
      "witness boolean:  1.0\n",
      "witness result 1 : 24.85009765625\n",
      "==== Generating Proof ====\n",
      "proof:  {'instances': [['4e29ced39e0837ccc54303617bee3278416698caa1099040db7586054c726819', '376d1c5357b7dca3dd77e4cd0cf36c71138900d41c115776af27ccb92d2f742e', '0100000000000000000000000000000000000000000000000000000000000000', 'cdc6000000000000000000000000000000000000000000000000000000000000']], 'proof': '[encoded data, 9922 characters]', 'transcript_type': 'EVM'}\n",
      "Time gen prf: 105.25368404388428 seconds\n"
     ]
    }
   ],
   "source": [
    "prover_gen_proof(\n",
    "    model_onnx_path,\n",
    "    comb_data_path,\n",
    "    witness_path,\n",
    "    compiled_model_path,\n",
    "    settings_path,\n",
    "    proof_path,\n",
    "    pk_path,\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Verify the proof to ensure it is correct"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "ename": "FileNotFoundError",
     "evalue": "[Errno 2] No such file or directory: '/Users/mhchia/projects/work/pse/demo-next/public/assets/out/data_commitment.json'",
     "output_type": "error",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31mFileNotFoundError\u001b[0m                         Traceback (most recent call last)",
      "Cell \u001b[0;32mIn[17], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43mverifier_verify\u001b[49m\u001b[43m(\u001b[49m\u001b[43mproof_path\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43msettings_path\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mvk_path\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mselected_columns\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mdata_commitment_path\u001b[49m\u001b[43m)\u001b[49m\n",
      "File \u001b[0;32m~/Library/Caches/pypoetry/virtualenvs/zkstats-brXmXluj-py3.12/lib/python3.12/site-packages/zkstats/core.py:240\u001b[0m, in \u001b[0;36mverifier_verify\u001b[0;34m(proof_path, settings_path, vk_path, selected_columns, data_commitment_path)\u001b[0m\n\u001b[1;32m    237\u001b[0m \u001b[38;5;28;01massert\u001b[39;00m \u001b[38;5;28mlen\u001b[39m(proof_instance) \u001b[38;5;241m==\u001b[39m len_inputs \u001b[38;5;241m+\u001b[39m len_outputs, \u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mlengths mismatch: \u001b[39m\u001b[38;5;132;01m{\u001b[39;00m\u001b[38;5;28mlen\u001b[39m(proof_instance)\u001b[38;5;132;01m=}\u001b[39;00m\u001b[38;5;124m, \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mlen_inputs\u001b[38;5;132;01m=}\u001b[39;00m\u001b[38;5;124m, \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mlen_outputs\u001b[38;5;132;01m=}\u001b[39;00m\u001b[38;5;124m\"\u001b[39m\n\u001b[1;32m    239\u001b[0m \u001b[38;5;66;03m# 2.1 Check input commitments\u001b[39;00m\n\u001b[0;32m--> 240\u001b[0m \u001b[38;5;28;01mwith\u001b[39;00m \u001b[38;5;28;43mopen\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43mdata_commitment_path\u001b[49m\u001b[43m)\u001b[49m \u001b[38;5;28;01mas\u001b[39;00m f:\n\u001b[1;32m    241\u001b[0m   data_commitment \u001b[38;5;241m=\u001b[39m json\u001b[38;5;241m.\u001b[39mload(f)\n\u001b[1;32m    242\u001b[0m \u001b[38;5;66;03m# All inputs are hashed so are commitments\u001b[39;00m\n",
      "\u001b[0;31mFileNotFoundError\u001b[0m: [Errno 2] No such file or directory: '/Users/mhchia/projects/work/pse/demo-next/public/assets/out/data_commitment.json'"
     ]
    }
   ],
   "source": [
    "verifier_verify(proof_path, settings_path, vk_path, selected_columns, data_commitment_path)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Print the file paths. You should share the following files back to the user for them to verify the proof. You **SHOULD NOT** share more files otherwise data might be leaked."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Model onnx:\t\t /Users/mhchia/projects/work/pse/demo-next/public/assets/out/model.onnx\n",
      "Settings:\t\t /Users/mhchia/projects/work/pse/demo-next/public/assets/out/settings.json\n",
      "Proof:\t\t\t /Users/mhchia/projects/work/pse/demo-next/public/assets/out/model.pf\n",
      "Verification key:\t /Users/mhchia/projects/work/pse/demo-next/public/assets/out/model.vk\n",
      "Srs path:\t\t ~/.ezkl/srs/kzg19.srs\n"
     ]
    }
   ],
   "source": [
    "print(\"Model onnx:\\t\\t\", model_onnx_path)\n",
    "print(\"Settings:\\t\\t\", settings_path)\n",
    "print(\"Proof:\\t\\t\\t\", proof_path)\n",
    "print(\"Verification key:\\t\", vk_path)\n",
    "print(\"Srs path:\\t\\t\", srs_path)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": ".venv",
   "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.1"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
