Files
zk-stats-lib/examples/stdev/stdev.ipynb
2024-04-10 22:16:47 +08:00

240 lines
19 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, generate_data_commitment"
]
},
{
"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')\n",
"data_commitment_path = os.path.join('shared/data_commitment.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 = [3]\n",
"selected_columns = ['col_name']\n",
"generate_data_commitment(data_path, scales, data_commitment_path)"
]
},
{
"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": [
"\n",
"\n",
" <------------- Numerical Fidelity Report (input_scale: 3, param_scale: 3, 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.022477627 | -0.044955254 | 0 | -0.044955254 | 0.022477627 | 0.044955254 | 0.044955254 | 0 | 0.0010104874 | -0.0015416706 | 0.0015416706 |\n",
"+--------------+--------------+-----------+--------------+----------------+------------------+---------------+---------------+--------------------+--------------------+------------------------+\n",
"\n",
"\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"==== Generate & Calibrate Setting ====\n",
"scale: [3]\n",
"setting: {\"run_args\":{\"tolerance\":{\"val\":0.0,\"scale\":1.0},\"input_scale\":3,\"param_scale\":3,\"scale_rebase_multiplier\":1,\"lookup_range\":[-20110,20332],\"logrows\":16,\"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\":9944,\"total_const_size\":2118,\"model_instance_shapes\":[[1],[1]],\"model_output_scales\":[0,3],\"model_input_scales\":[3],\"module_sizes\":{\"kzg\":[],\"poseidon\":[14432,[1]]},\"required_lookups\":[\"Abs\",{\"GreaterThan\":{\"a\":0.0}}],\"required_range_checks\":[[-4,4]],\"check_mode\":\"UNSAFE\",\"version\":\"9.1.0\",\"num_blinding_factors\":null,\"timestamp\":1709715644083}\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: 6.912375211715698 seconds\n",
"=======================================\n",
"==== Generating Witness ====\n",
"witness boolean: 1.0\n",
"witness result 1 : 14.625\n",
"==== Generating Proof ====\n",
"proof: {'instances': [['d57f47950cdabf2cb79306e0f33e75726a2c2960806e902b0fc88d3ff949a108', '0100000000000000000000000000000000000000000000000000000000000000', '7500000000000000000000000000000000000000000000000000000000000000']], 'proof': '0x26116db2aaeab71204d7291cdef443716025ea95d766eaa78856d2ae8ac10a7802d892acd07b3202ed7c2a25739aa59f232df3440f9d57d751928fe99e15d92f2c30b66a8d2ebedf6e97598a3ce064944df5b09553afa1807bdcf68a74057cee1cd4db71ad1cc53a4727bb22d22581619e8a93969befcf63b35d9ef9ce3ad0ba2bc4f175813a35482aa2713a844ee88587e8445ee50ce5889dec4061c6a373591a47078e7297df578c9923e9c88920be6aad7c39c8ce4bbacccc9c926d4a5397182edc51212b4e7d0081949a46b1251a4cd71812047fcf60efb12c5b3a4da87c12ae289f61839b82706e5c70bd53ae5eddb161264672df55c1c3e193371334301d7e400f430887cf49332ab1d21a7f667e04574367d6750082b4fb5861f5f23a042b7c175a400a22d551ccb69f0320131828bfef054bc11dcb351216db3dad760764647e819d2c247d4c2ced32b2db19934dc1cad8a309903fcbc76e0f36af120a735ce4eda83cb6c06431ffbcdc9dfb08b4acccb94613b757ca77d52e50fbe90e0d9c5093e81d09cb724e1e4f0dfd6bc5d26c170524bf343b7470f66726121c279254fa32dcee47cd970cdd97ced2851437c6edc1eea14814bf8aebab014cfa0d2f51715b8101ecd18fa1bb066d472265f5ce7e22a1d194a12ea3479527a41f0f8462f0b67fdab9de8726abd62f7f90104bcabf3dbfbcaa49c080f1a54d40d51ce39c8b895a8dbcae1deb9323f3475d8fc00cfc7f89015a07248f12e3653fab0ca8563a80815b0be00fe3e5b1692f2846ede8cb9af1785779e8e30696efedd52b2dab644a2125c2e19c379b0990c5ae6bad52661406b72e6116a664441918d219f94ae34bb051ba9a773be61e32659b64e1b8d08b956a127e02d04a6c6bfc2b24ceb37719cf3962f7b4bfd391f3f32229575ec73e1b6d547ac1c49fe5297b5d2e2d2177534ef75e128be91e8fd327c357927b995beba59714bd58370f3c36f31b33f94442932e5e68ee7e14e4347b0e373c83ced554c20d168bb3ceec66a8f20204a7140dc795ef9455ba3fbeb50fdccabe5dbe7bed19469a69af1d965602bd10923fc17f250f67ea7d803fbab9e04874f72097b6846c22a3c3f63e8991af6e29fc13f6d413264d51b687e311ca85a42d45c84aa11ae401a823f20ff9c6ec412ff72d37b657a433e775b98e4f0c65fe93c06af70de3ba12f6bfd8cc57af10131cc4460a9bb7ba15d18e3dfc9aab80e0e153b97ef3841cfa58f4b6b231be74cc0959060d38199e65ce2d00f584179ca34208f231698f011eac33cbc44dcc67062ca1a8d42d3cba1856c609ad5a17c2737c28694de10cdf3646a9d6cd13b3027529954a5008dbc3b1ecf26dd70da6a34a5faa7e122e86f4fefae032da0a9f7a6b21692efd9956c9ef78ee1b24c85fc7bd14da03c2fbfbb58cf1d16c41fb1d3fb21e938a1795320e658eb3ed328039ed388a80ae9f1ee6df9e2392a56227ec3e651c6d6012ecbaeb205a87cdafeb668a0a97b7ba8a4f32a3d1686f48308383b51b1db38b3bab05105d2e00ecbd48971b46165df4170f4ba0e1600c53bf6bcd289a1e642d61aa1da327d2a8897b9a3a5bb92c5d5b063080c38a47c107cd92397de707e9379f4328e56569e4701c181dac2ee3ade8d515c7028325816e555bf348be16e4de2d7bd8c84dc7d50769d276d1d941ea203845d796fb0bf2704fdcfd4d2c2239ca12008be268d1d998b1954189a13be621b0bc81efcdc9e0274b2dfb744f1af0376f7eaf1ebc346e0122131345a485bf721d209b6a84239359b10d2cf19e12e89b77d17ab78eabcbb9e8fc5b660d98934c7d6b8b8e52379a64cadafe46be2d3b5cc8c10ead0b17d836801f9691dd9354cb2cd3ed148c80f180f2f6e47e401284f600c8d84bfd9f82c96068a6e41d064d971b7e0216f8636b53471b90e4c21ddf11d4156d6eb80f86bbcf0279eeff278e2489de5c3875555ffa5df4882bb313cdd5cc0bd5b53c90ba29b2bc4bf797b0f947f87c975aa009fb01d63a9d356118e4caa9543f38866b8c3cdbd66f49080b3ae7d17cab2f0628aff8f3ce59f4cb0ffdee56c7cd3e1819a804181b7551fa4ea994193fdad6f2f5436b3fa6e357e30b704c6429ca4df17348ac733baab1a712c488d207b0319d3ab23ad19c9ae35d2ed258112c040c1601392ae6dcadeaa5e7fb713cbdc3309688cc8669dd11e5781a7b92f1ebadacd7f915e7fc8ef2f6e0453accf575cd451f79d12f4448bc8d0c14cb162d7fbb4237e3de41921d65c0cc250c2006116cb35f17fc168695d755ad20b76e1026dff71582d93b824df342275c1b4f9d4624636651dd1e75693b2c5a0a0f8750a04ea4fdcfc9801c85e7679e9be4a8c488571e3b483aea3ab09b07bd198c775d991ddd80ae7eacb2e202461b2e8b61ceea36cb91d1a3e6787a886b9a1d150b9dc599cd4f51e8e6796aef4604864f91de98406e01429fdd1c6737551b1f3d0f467a0c8e0fe378652013be42b48adcff941911fa8fdb1b0c13015896ce2ae2723daffc77466f1e94051a1614af19196decef1cb1af0f2670976109a3551387d8ec5f985ea1ad70172b96e8ca4278d7e4568656c43cc699702bb2b8a3a6123fc851f3f5d781227e53bce8e7a86ec2987f5478ac60cefd1262fd56457b422a8b05b1a6ed74a2851ae070935bd2f2611d1204cceb5972ce8f816aa106c808241cc1a670400d4deabff849ccf6109c8ddf50f6a83fd8b4293ce42f81ac0b150c0469579cf201fe236a5c39cb9765378fc06f3bbde693ece22f1d9de3283c24174236154606e159266c7e25b08d342470d116f9b518b5d9f6babcaf168c65d82a3293f753fc15f3451c13336e644927bee8b85e7038e4dea90118f38434ff7d1a0dc329cbcb2a916c114d5eeb27e3cb3d0a71617d978f1af7cdef34f59e27ba09a2d3ece0dace3805caf459b05cbcd135665e53bd562bbcc0d5d658721401b5147631363a38caf547062bcf7658c5da01636dc378797ff31cac91b077eef9761469d262c434f1cc5a840b48ee2ace00329220149ee8dffd997734b62187b9f62b39dfbf7c6c35137ccab7011067594ffb0a0190eb75af6120de1be12c9f7c850cf249b0ea4c287978bcb0499d889512e8d97d2a464faa66eba42fba34d9ba56263540d2f2866f3447dec9b6d21d5b6dea4b51573e76c7ae192f5a923871b77f0961d24418c02fb9a62f8d05e3c5c2d47a79332f105c079304853596d5d4264b1da1c825f4976eaf31fe5ce494090466a347164c2c10979c282bfbe5084195762627406b5b3a9198d92b900d226f86f52949d34c621205aaf69994589208db3f2789c0c5a5c8053fcb623f6932e054bae6a698d56b2a76265127e1608cc193451abef3698876f0fe362996d0fa3a9f2c15cc0bb610bd9e171e34e845fd0ef8211d1775a366cc480c48ec31a1ecc371dfd89a8c411425b5a734e9879b4ac81a0413869a5b277b2b5d35039d721c6d5959bd00aec28216441f96f764bfbdd28f980a306700a59125cdc54f8c182d7aeed1cb8d6759ff7f9706715be5c0de18b3b92ff9e921958aab018fc696a977a6118611a2e0df591bd3e82233b0ce5112156e224464174afd1c168464915f48168025b890fc6b9ee5e7871651effcdf9a172c2a402b1af8ab7ff776ed5e881670f6e8f1fb0f5fe8e5281f644a439380e8a5d206de1bbcb520e1e86dda5218ae14f384c7611826c65401f1d868548f00a3523b1558cbdadeaffdd07e41630c5ea55b4d3b74ce6e75863104392ff473ce72e91102479bae65020c3190ede08a6613602bcec0b9a55cf34a54f6baaf00a60ef41c18b2e32fa29067a1d78c7d703e93b51a60d5f03c83dce4d87b781d875f580f5f285a79185a1621a8748dcfab966861b0ffc5879141830d31fed1013d477c46c00aa9f96230e6262927a20a77e85e2a300f3ed0fd60fee8d5d229f301b62a90430000000000000000000000000000000000000000000000000000000000000000066e04d1e73bbe940296482e0e00aaba69505c56086512eea3e4edc21d3909290cd12b5ba61361019e4a9775d34418313615a818f79a1e24f9ec68209899f65600000000000000000000000000000000000000000000000000000000000000001701cff2aa8d9ab8a9324ab50e1e14fe981c7499fa28bc4c3147e944ac89c48f1898d6bf716a5eddd579241bdb941f615aa9b1146c781275ed01ccfc741a9faa2f42a89a6e36bbb540c0469c5c02b96c5795b441fd8c849e53ad71024c8a246100000000000000000000000000000000000000000000000000000000000000001a9df8ed4264c7fd68291482c9b475452a7cf0d58043fc1f0ceed0813bea781d0cd12b5ba61361019e4a9775d34418313615a818f79a1e24f9ec68209899f65600000000000000000000000000000000000000000000000000000000000000001c6a65e4dcea9d1436da308fda6fb3d88b118cf9ae18b66fb831e96e60d2a82100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001a774fc3d8d1d556eb516d830f46037ba0c5357f9d57afa190104034d59d17d22fec133b8a47a44c6efeb19c3396062207f55f0acb3f39f78b2534e44f20259410e1a2254de05ddda861484cc5b45e44a60e92fb6e7fad49451147488f598f951b041e12ec794cb51374e02d77687903a3ea7feb720e2e6b068d064ec02b8eba0dd291b7755154114be6bcee6644f813bc6f5df94615c2d0a2fbc89fd1f3c85e0000000000000000000000000000000000000000000000000000000000000000271660e3bf3200c1584eda09e0c72ef4dfa559a46304e0045729005271e4672711c6b6049f5cc21155d7c58c9948616c3e7f25ac7a1b9098a0a277a6dca48b95207a8cbc1de9ec62a65ae4c03c4a73772032e3535c78ed536e50b1c7e0a2eaf0271d044cc07d4b6882330379f2428e6a475afa67e104bfe9afc5d4dbc271551c2bcc2bef2e47fe49bd9067887b1d4edbce76b82ec7575cb39e61f6498bb9930e06b46ee440cef34f9949b75ba33d9d130770329e62f8cc3ba1d498514e9e1b051c02675ad4aca63d1229f11d1bf2dda28da50e7d7eec3669b45669568c8041c601bcd7e9cacf705016546d45f7b8a8cd86b3944469fa80d59fcdcf2232a601b200a8d101db3c1f909138b2d42995104610526180dee56f125c09e536c3c81cd20f7c2dbe1512b04dda2ec1aaaada975fcb2b815fced3343e5e09b1361bb6bd132a28cdfa12082a56935a8d5f6b9c31568ec3bc5b872818f28838eb67ac32764e0e833460d77291b62225d0d9a73786a19e20b0a7c6082ffd35e78079e40e87352c5d23f26871ca81b9d40165c5fafe6b2cbf9d62a26ebead852a0bd4b35f8cda29cd4610e4668485061951a204d156eb671166a5bbd96b87ce4425b3a7aa9b7a0b79e231f2304636505cb875fb1053d24beea1ab6a0dc9e2ed1420fffd2fbc4803a59d11eaaa11236146c56e3936f430ff63bfc0a989750e1efd9697ca92d3990b5069992b6a71c85cc91cb26adb7fe3cd12892e97d0d980b4f85862cdfd473d1d057c64189917cf8f4bee3aa702cdbdab4cf348b17dc965c1bad22733d8592623d431ae408784b718328f3b3da6f010f00991238595033578dcaee034fa97011a825a3663f8e5c8db6a7424dcb475ae4b85dde192a8d16ad6e8e9f2b5eefbbf0e75a6e572a3cc5e9540ff7db9961d22802b22c43ecefc1b530cb41ba1d67d3524ba9bfdf7fbd664dbb6045b7ab1c67a3e849b1decbad7361ff8c29c60daf2d406731158ad835e88577c4db1e8447d6ed4f45a7788e67b45e07e099a62f47a85192374b00e8ae4634cd19478ad4c2ea5c46161287f022650f356a440e540c9ac2f6b6af9692988a05b95bb5db5815ddf36d33b94814855595e2d6c751995d3c417f4bb4fe5bac4377324ff7bbf8d1673554991b5837fafefeafd8df21338d1281e9119324d56a342d103d18a67a94417a97b8dc109dc4cb56ee99a447d1ca4af1b1689e41802391734d5a1ecc91aee54b2ae86d8e9d3a20e561013bb0ad2a0842d1ddc1e54204a1c2df6ab3cf8c37df08123ff9a18b7e29717e0eec5a4761c5825fcd8f7b570fa53fe6b732c9a87973e0934f225ac2e10eec43c224cc41346ff2cf206e5ef1b70446bd046f34b05e7c1546fc2ad4f533eaa1441438ed9a27f4f0fe98df64eeeeae89f281ea4515c2a26a7819f6b9217425b794c618e315041e712569a21bf6c65e08480ad1a093a4614d82f7db05448b1afd8df8a702ba0b67f123bd16662cd8b32e29e5cf92cacd57f2bb6589a5985cc3eac15269ea4c7900d254cb8d6d1df478b6db683936edc52cbf791befb15fc6044b64295dc95729f3d191f28bbd09d35337ce863be4b04f80be5961df87c506b4eb2a050891b2e4ada2d93afa7443ab44a0d47ad761b58101a3d544c6623c12aa38705bf8256bb492905c1f340774e1478bbd84a426b7304f994eecc0b4a46c9f2d406b958a879dd8b0bab3bf169f637bcb0e31d2f50269f83602c97c066b644d09130a9f77be08ca9177aefb5a2490d66a657ae20793a442f5020964e044531544bfc63649960d1392a1d6643d9909457569e47dd1c559fd72d0d3f503dabc155e5e1710b99280ac0235a1507880a18b70b8dc280a6c2d7f507ad01dd351e4c427e3c6ee33bb546f90713a8776289ffd2f44f38a87ff5278184f5a74f2159681a66ecfbee6b2031a50ed844210260c07b061b8a0d1732f168d7d9e0f75829ac4f8f382865cae29c0819e7c7218837683a9cc3d3e5bc9e0385ca5bbc5568b0d2621da913c065d3126a', 'transcript_type': 'EVM'}\n",
"Time gen prf: 8.47111177444458 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.625]\n"
]
}
],
"source": [
"# Verifier verifies\n",
"res = verifier_verify(proof_path, settings_path, vk_path, selected_columns, data_commitment_path)\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
}