Files
zk-stats-lib/examples/stdev/stdev.ipynb
2024-03-06 15:48:04 +07:00

242 lines
20 KiB
Plaintext

{
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"import ezkl\n",
"import torch\n",
"from torch import nn\n",
"import json\n",
"import os\n",
"import time\n",
"import scipy\n",
"import numpy as np\n",
"import matplotlib.pyplot as plt\n",
"import statistics\n",
"import math"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"from zkstats.core import create_dummy, verifier_define_calculation, prover_gen_settings, setup, prover_gen_proof, verifier_verify, get_data_commitment_maps"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"# init path\n",
"os.makedirs(os.path.dirname('shared/'), exist_ok=True)\n",
"os.makedirs(os.path.dirname('prover/'), exist_ok=True)\n",
"verifier_model_path = os.path.join('shared/verifier.onnx')\n",
"prover_model_path = os.path.join('prover/prover.onnx')\n",
"verifier_compiled_model_path = os.path.join('shared/verifier.compiled')\n",
"prover_compiled_model_path = os.path.join('prover/prover.compiled')\n",
"pk_path = os.path.join('shared/test.pk')\n",
"vk_path = os.path.join('shared/test.vk')\n",
"proof_path = os.path.join('shared/test.pf')\n",
"settings_path = os.path.join('shared/settings.json')\n",
"srs_path = os.path.join('shared/kzg.srs')\n",
"witness_path = os.path.join('prover/witness.json')\n",
"# this is private to prover since it contains actual data\n",
"sel_data_path = os.path.join('prover/sel_data.json')\n",
"# this is just dummy random value\n",
"sel_dummy_data_path = os.path.join('shared/sel_dummy_data.json')"
]
},
{
"attachments": {},
"cell_type": "markdown",
"metadata": {},
"source": [
"======================= ZK-STATS FLOW ======================="
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [],
"source": [
"data_path = os.path.join('data.json')\n",
"dummy_data_path = os.path.join('shared/dummy_data.json')\n",
"create_dummy(data_path, dummy_data_path)"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [],
"source": [
"scales = [6]\n",
"selected_columns = ['col_name']\n",
"commitment_maps = get_data_commitment_maps(data_path, scales)"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"/Users/jernkun/Desktop/zk-stats-lib/zkstats/computation.py:172: 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",
" is_precise_aggregated = torch.tensor(1.0)\n",
"/Users/jernkun/Library/Caches/pypoetry/virtualenvs/zkstats-OJpceffF-py3.11/lib/python3.11/site-packages/torch/onnx/symbolic_opset9.py:2174: FutureWarning: 'torch.onnx.symbolic_opset9._cast_Bool' is deprecated in version 2.0 and will be removed in the future. Please Avoid using this function and create a Cast node instead.\n",
" return fn(g, to_cast_func(g, input, False), to_cast_func(g, other, False))\n",
"/Users/jernkun/Library/Caches/pypoetry/virtualenvs/zkstats-OJpceffF-py3.11/lib/python3.11/site-packages/torch/onnx/utils.py:1703: UserWarning: The exported ONNX model failed ONNX shape inference. The model will not be executable by the ONNX Runtime. If this is unintended and you believe there is a bug, please report an issue at https://github.com/pytorch/pytorch/issues. Error reported by strict ONNX shape inference: [ShapeInferenceError] (op_type:Where, node name: /Where): Y has inconsistent type tensor(float) (Triggered internally at /Users/runner/work/pytorch/pytorch/pytorch/torch/csrc/jit/serialization/export.cpp:1490.)\n",
" _C._check_onnx_proto(proto)\n"
]
}
],
"source": [
"# Verifier/ data consumer side: send desired calculation\n",
"from zkstats.computation import computation_to_model, State\n",
"\n",
"\n",
"def computation(s: State, data: list[torch.Tensor]) -> torch.Tensor:\n",
" x = data[0]\n",
" return s.stdev(x)\n",
"\n",
"error = 0.01\n",
"_, verifier_model = computation_to_model(computation, error)\n",
"\n",
"verifier_define_calculation(dummy_data_path, selected_columns, sel_dummy_data_path, verifier_model, verifier_model_path)"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"Using 8 columns for non-linearity table.\n",
"Using 8 columns for non-linearity table.\n",
"Using 8 columns for non-linearity table.\n",
"\n",
"\n",
" <------------- Numerical Fidelity Report (input_scale: 6, param_scale: 6, scale_input_multiplier: 1) ------------->\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.0009598732 | 0.0019197464 | 0.0019197464 | 0 | 0.0009598732 | 0.0019197464 | 0.0019197464 | 0 | 0.0000018427131 | 0.00006583472 | 0.00006583472 |\n",
"+--------------+--------------+--------------+-----------+----------------+------------------+---------------+---------------+--------------------+--------------------+------------------------+\n",
"\n",
"\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"==== Generate & Calibrate Setting ====\n",
"scale: [6]\n",
"setting: {\"run_args\":{\"tolerance\":{\"val\":0.0,\"scale\":1.0},\"input_scale\":6,\"param_scale\":6,\"scale_rebase_multiplier\":1,\"lookup_range\":[-162602,162656],\"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\":14432,\"total_assignments\":9950,\"total_const_size\":2120,\"model_instance_shapes\":[[1],[1]],\"model_output_scales\":[0,6],\"model_input_scales\":[6],\"module_sizes\":{\"kzg\":[],\"poseidon\":[14432,[1]]},\"required_lookups\":[\"Abs\",{\"GreaterThan\":{\"a\":0.0}}],\"required_range_checks\":[[-32,32]],\"check_mode\":\"UNSAFE\",\"version\":\"9.1.0\",\"num_blinding_factors\":null,\"timestamp\":1709714355149}\n"
]
}
],
"source": [
"# Prover/ data owner side\n",
"_, prover_model = computation_to_model(computation, error)\n",
"\n",
"prover_gen_settings(data_path, selected_columns, sel_data_path, prover_model,prover_model_path, scales, \"resources\", settings_path)"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"==== setting up ezkl ====\n",
"Time setup: 59.26989006996155 seconds\n",
"=======================================\n",
"==== Generating Witness ====\n",
"witness boolean: 1.0\n",
"witness result 1 : 14.578125\n",
"==== Generating Proof ====\n",
"proof: {'instances': [['c371c58876874ace4444c2f2683e0d7bb7ce264f49872aa6deab40635ed90f23', '0100000000000000000000000000000000000000000000000000000000000000', 'a503000000000000000000000000000000000000000000000000000000000000']], 'proof': '0x2ea53767627a825d488d58596fc992c33ac4780a4d585c0ae3a2664fe513ce900c79099b797ba238d29133da21f2ff81a4eb0312d8084b2f2bbd725c54f5e0c32bcf23f22b94c1764f8bfc2c66b97768bb62e3e69f0e11ced087b0eb74370a9f20e0ff4f1bdb3c4f58a80940b7b2df8024be0100008a578925e93bcad8fd274a1009140f48bb12422b923c1f33e6e8ede158c081a1a21b4f42ce1caa64749de71c42fac312d374a9ee18c477933db3cba1dfc1dd95fdf8369adbc3b00bd82c0e261eee129ba354368a9bed9a1e7e290052e6a230f5c4cb94c136f76fdca2e357045f74e768829996e853d7a7e947dcba3d38d74b9b735025bde53143cfa5a0da29ef4bae4e2a3885f3a37bbf7a7dbcc39fea652a70092999d921d15b880be3341e1f0ffc2c39667b79956d5fbe0f560d2315f58adcd6d872dc23553a236cfdf10098df0c5366a8e27d180e589eac25a3d1265fa16294bd7d8899aafda60cb7910c7c100322b35ff4f00115f418dcb716c36e0f205828f3b39ea83bf6264612a51c7f39bb07de1460fa21d5cbde62cabe68bc346727e3be736d79b7e37a19715211c8cec05a5a4381de2be303f83a67a94cd4c5f69aa413febabf0f35319ef1732757b54ce2e93afdaa825e599285efee173ab03612e8a06b0febd45836d078ed16b1b7a4599f72faf2aee13ddb7e401b4443e35b6ca8de192247eb42eedfee450b11539523ae877e13a6ba7cd38e07d720f995371dbe9c7815120b242f8419fb23f7c0b89c50dd33abe1000627d50743bce108ee05c8d331c74ed67d9d9ed2421c7d1eefdc098191560237e346081ca82a5b3c8ed2c03cb095e69a41a115a4751d9ee08de6fda97f313740e6c2f7dbd97fec773270a423b46d0f0e1bc0f785d6036cd843faa1dcfba1af2963625cea0ff80fa31ccd944ab851eae6632dba7fcc2cc993d9c3d578c294ab9ec7f3661a1c7324df89a0e0771f6799f6c058e9872815e4c6df4b1f901f7230188f9a9d128274d7f21a5635319d9551db501411358d0de7665252ce5b3dd59182b5153a20e12180903ca5dfda504e92c287d0b4dd0a062a40852126acd8f81da3610150c01519e0c9a045f695cd6ddcbe683342a8df0df45d855befa336e9d5df3dd7281f6568128fd426ac6c0eea033d1383478cbe1d8f841b4f4f91a03f0e98c613e7bda6e05a3b92111c9bf473662f9324da4f5f2029ab20e66b04f18419578b47f6a25d6ed138ba1b37900454ee7d8bcb20cb85281d090b3c6b8e18fe9eaf71a8003e29a56dd3f8cc86a71a82f7a5af07993a57070cfc95f3c9b233053d8381982ae9200cdb6e7d108d11f885d62fe03d3f813723fcb531459a1970d207d0e6b05884fccf8b633a7c109bc5f349478e856f7acc2c4716bf24b5b78705dc160b8ddd378d35796611b485fb07231b1fa2ab187df61464755176c206571a1231377be5b8c5db9ba5003c88a4ffc92be628a097de8c030c8de60eb45ffcd5874c3ccdfc3dd84eed5b07569749e49158b2da657fb7a10040bcad71328961a3bee788397d99255ffe369e54bd0e587b38f16f1a18d1e31fa30dbcbf5ae05d95b6c576f2dea328802056cdddb82972810bd09fe22c655c05e1e7d8b16da06f5b8c87b672593650de90da924cbcaeb935c45bf4dcd5a9bb0e504a14fdc66853e1a6f5363688ecdad9853a59896678db5cec495bb3c6768b2d62c6f3648b6cefab038b5b1a9a216b476216ea45d225a5b7803b9370d5ed2223fe9fffad65661a9eae60b69484e6df453d7788b527922ff68b3bb1d0199b66183336ea841f590fd957710aaefc3f54e9fe090b9e56affcb54418bb214e83962169555d408389b44dc540898df1cc911ece449d7c90a45c59c23656486316db2f4f754a356785c1ccd80b10e7df60bfcdf7c7e69d4ca65606fe3b3ba26f324317977076ad484a2fa23b0d15c8caf7645723e764e54165705845ef94595ab47525f6feef178a4efc7052b2f9f27eaa94cd3142269b9b42fd8c898f58ee33482b04e428c83bc16e5f68b63d64a2596f3658f37ccd4a68aba899b42856b41545052405b76be953a8364332194987ccd4ef7a6b2db6e7ae9f32dc6357978852f5410bb1d50f585bdc6e4e11f5b9847bd8fd062f7b0d31ac0f186266482365d8deb4184150e0d4aa07cca97b6038cd2413843e96539e7edb124a9f8fc7e831be4c66080d684c78a0463ba2e45282d7f7a8ce53a70414e1291fbffdd0907172d4dcb41f0dac891370295813a4611f4050997ed4b5852560e1515b8dbd3499da3370b508add8c0a5c9446a1894f52449a17a1885aa4754e608afa6e5ddae8fd0510be425f85f941d85ba0113394f22adbcac53f3d83df752f4cb7f85de4f29f21c10451372f27b71c8d2da241d2e371a589f6056c56c6480aef32d4da7195ed242bdc720f1bf6376a3957b8502a3e297ea4108c0f29ed3dcd754d70405d0524cb984c70036bb6c5df7b9cc7c8278cec218358bc61328e55869f83532dd7f8302f1ab82027d5ff0dcb3565bdc3539eb90f53564e9678f2c2fc862c986da1d3da040b6f421b1f74673f52944a0ed3c58670dd07ccd5c4f4330f050a02a716b8f99195bda2815fdb9996a0b5dcc5b309bb5002dbf72ca9ca88a8f0e3d5d3160019e484d960ad66ce257e41c1a192c06a8eb2d82dfa71437d159f9cbe82bd07d9912331a221be315dd461b9537eae9c595518a57b4f63174b82bc91b3dabc2c5437dd107241dfb07304695fa7d1d578f4606ce5491a6f61da6a6188737fbc4fb045531b5fd0789f87fe2d80844125c14134b782e9cf91ba6477dce95300e484099b080ad561a09be1c7005aae2adae91430f6165573e94c2036fc17fad0e85e5e5e74b4e450dec77b3cbcf6d3a0d0db53b00f1211605204a8eca5960d9de9e449a4e831a521ffcdbccbfaf97cc0de5a00b6fd6dbed9d6b241fb0ec485c4ce717bc86c2fcf303597fe6ae5d97fa16fc13f11b0a3997dce7b7b9b1ab7f0e04103395631275ea0cb6669f9b73d50d742def1eead1739d6e3f0f28ddc98e203c9057266c188f7a1a703907964c0bed6b6bf7e1a4af06817f902742a224ad077aa923778befd4e31c64ad7993766864eb0dc29f0ed02961550c2c4c77ad53c818f72a84ea33fc6f24d5ad14c833e2962e3f65811bc1abd026b9c103e766112a6c776071852408e91b7391be1e664a42ad74898dd824db764bf383c2e83f4402692eba767ee739cc000a68b91e06b009aaa335460b68de0673a083726ec86435d72cb3ea417311181e664720c45a4baa48244bdbda200219147919be3c875b83768886d790399225116a06ea1247e459674e762f5bde6a1d431f36c974efbd7720955ca2037f589d2078f049741935f641bafb963b94f9bb40bc473ee55512e059271d95225bca9922f0a100b7ed137bc35ec76738ead38a9476cf8ac110afb1269a646a0f1866ab0b4491b324e32c6a2f46b4a847489c66a088ffbf3d570b658516f07d990fc89e2f956a9d98b69cd508fe7edf0fd45e5297da956a2f25eac91eb50182ffe9ea032ec7b03ba208f093ab715fb16343bf90550a058a5956c0e40b4829bb1049f49f1fa996060959b6a60b730cb7063864ac6f7545ba26ac54bdd690cc973c75e4d61dd68b6078e63c56d934a97548dfac1dc8b53a3fbedaecbf401bbe511457773e0a72191cbcce727b63cbbcbc0e4482cbb3b9f67753c002910d3253f1ec6606b9294c80dd713203af988c9493d4780dfa155f14a8aac8ee74e75772fe014df13207715639d343ca72bf66ddc09297a4d1c5f6005e55f8de7f0dd578187700997311c8b9fb87e36ed56c887feb1319f6f7c2e8be185f1961eb6170192e71cfe87816d004550ad3a7c6bf7db51206c7ff5aa0c331baf52bf0e8224980f854b4affc11ca4ddb6b91e9b29fba590c10e90f5cc156472d225e14fe9ac044619bfa17bd00000000000000000000000000000000000000000000000000000000000000000ede1b97bd8401ec5e0bd422c0f35c5b931a1a3e235d7a2d275b87d8180e552b1bde2224b4f329249df82189badceff2dcc75c564eff3f96b48e951833f7ee9200000000000000000000000000000000000000000000000000000000000000001b39e5dc42a7488f5caff6ea559a14ecc464bd924f48c573a8849ebbf5b626371fb55de518c97b3d644cd6c19bc7b44ca49e5ca60e20584bad91b6835da5dbb50013bbb310e87295a081844b1de7930f279d4eb2d80a6d1ede3675f3b39e453b000000000000000000000000000000000000000000000000000000000000000018d745c02fd59a8ec80cfef4a49e0ea0062c48756f769db56966d5996b634d321bde2224b4f329249df82189badceff2dcc75c564eff3f96b48e951833f7ee9200000000000000000000000000000000000000000000000000000000000000002e904b2bb6e522eece6f8a4361142c0c0b9a3d45de21436049ee636aab47963a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002d446a20412def0f9e141f2bc6348c1d40182caf902a02c3975bd9fda612e9741c21a5b37bddbef3f0623621d11f1eaf3dbfe42bc4acb8a36eb85b4e6ecde2f02aa50dc9de0f8c020d7cbfea16a8fe2e11bc4fc45516ae34039c2489e1ee4cb801ec3ec130a1889fbd32b71ec38d6048df766a57e7b9bd764de6e3eeee5e4a7f2069110d9f357d9c30eee817d82f3e06eba5343ef4cadd36cf93244e7687184300000000000000000000000000000000000000000000000000000000000000000f0b55e4494082a3a84e065c0d8ac492b5e96802bd15ec096a20e11af113f0771be2021cee472ef6d47e6409e1bf0890861fb30f6f85646e5ac529eafdb589ac28ca14be3694f5dcaf1ffd441169ff796e2810520a5173120b91a78a16e272b7153b4c73459748c5734128c7359ad7c8db2470d16ebe6f9d7ccd0f30b7e378ef04538deafd038828e24f594aa04bb2aa668554c8598661783d46604eda11b7022391a47524cc458dff8149eab758a52060fc421aa2250610617ce7f5199ee94a0dec0a7afbbd3a8a36473e45284b980fd3ec204313ba2a3a465cf047ff069556121813d6db65153b9b6f2377c27e71579b08581143598fd3fb87c107863f09f11737361d1719401a90fa37b35d4cc12249963e559b219889acde074354382d7b1cedc2940b1049bc13112001182fca5fcf4931aa10f8c3d067bf07235a98d8e01015f0ee66e35f14764d0f10bec418335b2f7b38a6ae425e37b81ccad6aa7ac31225449f7c2bac4ff1be70b45044ca464629765d765892ebaffb838039dc4712240785134e3c2efea5d92e1fe158cef4ce9df50a2e39c1d46b593e0e228a9f44253eeb93a0e237c53db52acce526957eb73c09d8d3148b22005ffd319e70f35e0534177d68eede220c2ebbbf52d28224d0d516fc245052d06fef046ac8f1b2f20d4428dfbfaf1a2504af5af1c31106b95c5273e02b2fd0375fd513c97af5b0381854e65bd18e117528bf4e8a940b56238847a9b74b2288950f05b763fb39e178096f4c474d4bacc78b80028caea1ace78f391a78bbe0179dcb3c0f644c397e6d109da4139bfd146987961eff03eded9cac0bdf6892c03b56403c3f0410f3bfd618602b7f9f98739f3e192148088e34138fb82ac6284afa876f0b102f366f74541d8455a95ae59f1c51fd19586fc6719d2e6cafe9f95127b15a9c22deb76bf182064f7676f8486b0b54402b2258aa4c2aba53b1c04c8ccc7eb4cba20295a36de3061fdb550103919b5755d11aec737bd80a35eb56226cff69fcabfa164927250c1f3ac322d8e13d36bc3b24ef5c9033fde303f48384b8ed409e5a1a0d88d1757f0b32106f2a7fb311598d7080775a1a934baac55ec6d03743d3d0cc455d812e9101fe8a3f9bc8b8cb06159bba73943f099aae4a493fdd7b374d941d3d50c26cc22b5ed27a3beaf24fd21de4ee00f95d7e3a112cd36d00f0adca1712b1890247290e4cdb215cdd7a1103b2e75541dc83a9d71fdc98b6ce31fcbba9159d2ac6de0d276a06dfec9c9005225c0c2e7167064e142a8724d6aae3d6250a180e7d46c92d0c3488191ca584ca4e0c748b20c878c7d21af5c759aa9fe9d609e99260c0af8b004b0bc6a47daebee5d6c0aff5daeed0651ca9081ef334e47337ab250803e902177d6fdf6c1bbe233acbaf5e9d5fd807a8b826ceebe039ff313fa0b403376bfe02716c7eb1ba0a1c51e92662df8e3b5d690bd2709e3e74e1fa2038e12e776b5b1d025c072c67f424986f6be9ee7d09aa696236c66a6f247bcf843875182d270d1c48d3c0cae6b603af64e8b21ce5d93170460a5e3ae786c050f4e0d718c26b5011d8a70b7423e5bce5335782369c550961bb2a3b4c1f88612e1f1ad0d33330a629ce5b1cd24d7f09475712611e7d47fb14cbff7fde238313abf0a0c3625e390e0efd5a0f02222331da74d85dd193942dc1d0d4404a0a20b6065643c5c6d603791e4017dcab38766e486f7a5a92a1f39a841de258c0c7f128a2ca1a767f40e4e92e3baafd2c9598b4429b0cc5c1d8bb92ea29ec2981363eb8709441a4f9cd08830a161df79dd9ec204f0fbff974cc9925b6db8a5579834593a4a29fdaadce7bc90c6b0c81a35a09222cb9a0feb7024bbb086d7fef1b01e0d660d628afcccfdb701598116d6e508303ce150e33448b9f9b06b781997737236b7cce85bd01a2c3ca10ad58df56d418cea3857c2ba47acc320e4e11d0791d2f4007840f87c6a9443025fb6bb4895892a62646c8d4063c7e7715157b2b530178344468b09639cb5279', 'transcript_type': 'EVM'}\n",
"Time gen prf: 75.34422492980957 seconds\n"
]
}
],
"source": [
"# Here verifier & prover can concurrently call setup since all params are public to get pk.\n",
"# Here write as verifier function to emphasize that verifier must calculate its own vk to be sure\n",
"setup(verifier_model_path, verifier_compiled_model_path, settings_path,vk_path, pk_path )\n",
"\n",
"print(\"=======================================\")\n",
"# Prover generates proof\n",
"prover_gen_proof(prover_model_path, sel_data_path, witness_path, prover_compiled_model_path, settings_path, proof_path, pk_path)"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Verifier gets result: [14.578125]\n"
]
}
],
"source": [
"# Verifier verifies\n",
"res = verifier_verify(proof_path, settings_path, vk_path, selected_columns, commitment_maps)\n",
"print(\"Verifier gets result:\", res)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"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.11.4"
},
"orig_nbformat": 4
},
"nbformat": 4,
"nbformat_minor": 2
}