{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "b6d1e3ec",
      "metadata": {},
      "source": [
        "---\n",
        "title: \"ParityQC の「Parity Twine Optimizer」を使用して、シェリントン・カークパトリックモデルを解く\"\n",
        "description: \"Parity Twine Optimizer を用いたシェリントン・カークパトリックモデルの解法チュートリアル\"\n",
        "---\n",
        "\n",
        "{/* cspell:ignore parityqc QOBLIB marketsplit independentset */}\n",
        "\n",
        "<span id=\"solve-the-sherrington-kirkpatrick-model-with-the-parityqc-parity-twine-optimizer\" />\n",
        "\n",
        "# ParityQC の「Parity Twine Optimizer」を使用して、シェリントン・カークパトリックモデルを解く\n",
        "\n"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "a6f69b77",
      "metadata": {},
      "source": [
        "推定実行*時間：Nighthawk r2 プロセッサで10秒。 （注：これはあくまで概算です。 （実行時間は状況によって異なる場合があります。）*\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "21156b6f",
      "metadata": {},
      "source": [
        "<span id=\"learning-outcomes\" />\n",
        "\n",
        "## 学習成果\n",
        "\n",
        "* Parity Twine Optimizer を使用して、シェリントン・カークパトリック・モデルを解きます。\n",
        "* Parity Twine Optimizer で利用可能なオプションと、どのような結果が出力されるかを確認します。\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "d185259f257c1618",
      "metadata": {},
      "source": [
        "<span id=\"background\" />\n",
        "\n",
        "## 背景\n",
        "\n",
        "このチュートリアルでは、 ParityQC の「Parity Twine Optimizer」を使用して、シェリントン・カークパトリックモデルを解く方法について解説します。\n",
        "\n",
        "このコードは、問題を完全にローカルな範囲で定式化し、Parity Twine Optimizer と連携可能な形式にするためのものです。\n",
        "\n",
        "<span id=\"the-sherrington-kirkpatrick-model\" />\n",
        "\n",
        "### シェリントン・カークパトリック・モデル\n",
        "\n",
        "シェリントン・カークパトリック（SK）モデルは、統計力学、とりわけスピングラスの研究における基礎的なモデルである。 相互作用が通常は\n",
        "最近接隣接スピンに限定される標準的なイジングモデルとは異なり、SKモデルは無限範囲モデルであり、システム内のすべてのスピンが他のすべてのスピンと相互作用することを意味する。 これにより、多くの局所極小点を特徴とする、極めて複雑で「起伏に富んだ」エネルギーランドスケープが生じ、\n",
        "これがガラス状挙動の顕著な特徴となっている。\n",
        "\n",
        "SKモデルの最大の特徴は、フラストレーションにある。 このモデルでは、スピン間の相互作用の強さ $J_ij$ は、正の値と負の値の間でランダムに分布している。 これにより、（\n",
        "例えば、三角配置の場合など）、すべての相互作用を同時に最小化するようにスピンを配置できない状況が生じる。 SKモデルでは、すべてのスピンが他のすべてのスピンと相互作用するため、このフラストレーションが\n",
        "全体として増幅され、相反する制約の網が形成される。\n",
        "\n",
        "<span id=\"mathematical-formulation\" />\n",
        "\n",
        "### 数学的定式化\n",
        "\n",
        "系の状態は、 $N$ 個のアイジングスピン $s_i \\in \\{+1, -1 \\} $ の集合によって定義される。特定の配置のエネルギーは、ハミルトニアンによって与えられる：\n",
        "\n",
        "$H = - \\sum_{1 \\leq i \\leq j \\leq N} J_{ij} s_i s_j$\n",
        "\n",
        "ここで、 $J_{ij}$ は、スピン $i$ とスピン $j$ 間の結合強度である。\n",
        "\n",
        "SKモデルにおいて、結合 $J_{ij}$ は、独立かつ同一分布を持つ確率変数である。 エネルギーが、\n",
        "$N \\rightarrow \\infty$ として、依然として広範囲（ $N$ に比例）であることを保証するためには、結合の分散は粒子数に比例して変化しなければならない：\n",
        "\n",
        "$J_{ij} \\sim \\mathcal{N} \\left( 0, \\frac{J^2}{N} \\right) $\n",
        "\n",
        "ある $H$ における基底状態とは、エネルギーを最小化するスピンの特定の配置（ $s_1, s_2, ...s_n$ ）のことである。 基底状態を見つけることは、NP困難な最適化問題である。\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "55b94021",
      "metadata": {},
      "source": [
        "<span id=\"requirements\" />\n",
        "\n",
        "## 要件\n",
        "\n",
        "このチュートリアルを始める前に、以下のものがインストールされていることを確認してください：\n",
        "\n",
        "* Qiskit Functions Catalog IBM クライアント (`pip install qiskit-ibm-catalog`)\n",
        "* Qiskit アドオン「Optimization Mapper」(`pip install qiskit_addon_opt_mapper`)\n",
        "* NumPy (`pip install numpy`)\n",
        "\n",
        "また、「 ParityQC 」のTwine Optimizer関数にアクセスするには、権限が必要です。 アクセスを申請するには、 [このフォームにご記入ください](https://parityqc.com/products/parity-twine-optimizer/free-trial)。\n",
        "\n"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "7db2e559",
      "metadata": {},
      "source": [
        "<span id=\"setup\" />\n",
        "\n",
        "## セットアップ\n",
        "\n",
        "（このコードは、 [アカウントが](/docs/guides/functions-get-started#install-qiskit-functions-catalog-client)すでにローカル環境に保存されていることを前提としています。）\n",
        "\n",
        "まず、このチュートリアルに必要なパッケージをすべてインポートします。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "bc380c46",
      "metadata": {},
      "outputs": [],
      "source": [
        "import numpy as np\n",
        "\n",
        "from qiskit_ibm_catalog import QiskitFunctionsCatalog"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "6d808e11c0992c07",
      "metadata": {},
      "source": [
        "Qiskit Functions カタログから「Parity Twine Optimizer」を読み込みます：\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "c4a7dc1cbb3d35ff",
      "metadata": {},
      "outputs": [],
      "source": [
        "catalog = QiskitFunctionsCatalog(channel=\"ibm_quantum_platform\")\n",
        "function = catalog.load(\"parityqc/parity-twine-optimizer\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "3071bc21ac4f3484",
      "metadata": {},
      "source": [
        "<span id=\"step-1-define-the-problem-as-an-objective-function\" />\n",
        "\n",
        "### ステップ 1：問題を目的関数として定式化する\n",
        "\n",
        "[マーケット・スプリット問題の場合のように](/docs/tutorials/parity-twine-optimizer-ms)ライブラリからSK問題を読み込むのではなく、直接問題を定式化します。\n",
        "\n",
        "この関 `generate_sk_problem` 数は、SK問題を必要な辞書形式で直接定式化します。 必要な入力は `n`、モデルにおけるスピンの数だけです。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "d4c3b37d482e4f56",
      "metadata": {},
      "outputs": [],
      "source": [
        "def generate_sk_problem(\n",
        "    n: int,\n",
        "    coupling_mean: float = 0.0,\n",
        "    coupling_std: float = 1.0,\n",
        "    local_fields_mean: float = 0.0,\n",
        "    local_fields_std: float = 0.0,\n",
        "    edge_density: float = 1.0,\n",
        "    ensure_extensivity: bool = False,\n",
        "    seed: int | None = None,\n",
        ") -> dict:\n",
        "    \"\"\"Generate the Sherrington-Kirkpatrick (SK) model with varying\n",
        "     edge density.\n",
        "\n",
        "    Samples couplings and local fields via :func:`generate_couplings_sk_model`\n",
        "    and assembles the corresponding Ising Hamiltonian\n",
        "\n",
        "        H = -∑_{i<j} J_ij z_i z_j - ∑_i h_i z_i,\n",
        "\n",
        "    where z_i ∈ {-1, +1}.\n",
        "\n",
        "    Args:\n",
        "        n: Number of spins (>= 2).\n",
        "        coupling_mean: Mean coupling before optional SK scaling.\n",
        "        coupling_std: Coupling std before optional SK scaling.\n",
        "        local_fields_mean: Mean longitudinal field.\n",
        "        local_fields_std: Std of the longitudinal fields.\n",
        "        edge_density: Fraction of non-zero couplings, in ``[2/n, 1]``.\n",
        "        ensure_extensivity: Whether to apply the SK 1/n scaling.\n",
        "        seed: random number generator seed.\n",
        "\n",
        "    Returns:\n",
        "        A ``ProblemRepresentation`` encoding the SK Hamiltonian.\n",
        "\n",
        "    Raises:\n",
        "        ValueError: If ``n < 2``, ``coupling_std < 0``, ``local_fields_std < 0``,\n",
        "            or ``edge_density`` is outside ``[2/n, 1]``.\n",
        "    \"\"\"\n",
        "    couplings, local_fields = _generate_couplings_sk_model(\n",
        "        n=n,\n",
        "        coupling_mean=coupling_mean,\n",
        "        coupling_std=coupling_std,\n",
        "        local_fields_mean=local_fields_mean,\n",
        "        local_fields_std=local_fields_std,\n",
        "        edge_density=edge_density,\n",
        "        ensure_extensivity=ensure_extensivity,\n",
        "        seed=seed,\n",
        "    )\n",
        "\n",
        "    # Handle quadratic terms: coupling[i, j] * zj[i] * zj[j]\n",
        "    # Only iterate over the upper triangle (i < j)\n",
        "    sk_problem = {\n",
        "        str((i, j)): float(couplings[i, j])\n",
        "        for i in range(n)\n",
        "        for j in range(i + 1, n)\n",
        "        if couplings[i, j] != 0\n",
        "    }\n",
        "\n",
        "    # Handle linear terms: local_fields[i] * zj[i]\n",
        "    sk_problem.update(\n",
        "        {\n",
        "            str((i,)): float(local_fields[i])\n",
        "            for i in range(n)\n",
        "            if local_fields[i] != 0\n",
        "        }\n",
        "    )\n",
        "\n",
        "    return sk_problem"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "c0caec311cb77bf6",
      "metadata": {},
      "source": [
        "SKモデルにおけるランダム結合項を計算 `_generate_couplings_sk_model` するには、関数 を使用してください。 カップリングをより細かく制御するには、オプション引数を使用できます。これらの引数については、関数のdocstringで説明されています。\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "cb32954d63628202",
      "metadata": {},
      "outputs": [],
      "source": [
        "def _generate_couplings_sk_model(\n",
        "    n: int,\n",
        "    coupling_mean: float = 0.0,\n",
        "    coupling_std: float = 1.0,\n",
        "    local_fields_mean: float = 0.0,\n",
        "    local_fields_std: float = 0.0,\n",
        "    edge_density: float = 1.0,\n",
        "    ensure_extensivity: bool = False,\n",
        "    seed: int | None = None,\n",
        ") -> tuple[np.ndarray, np.ndarray]:\n",
        "    \"\"\"Generate random couplings and local fields for an Ising / SK model.\n",
        "\n",
        "    Couplings are Gaussian. With ``ensure_extensivity=True`` they follow the\n",
        "    Sherrington-Kirkpatrick scaling ``J_ij ~ N(coupling_mean/n, coupling_std^2/n)``\n",
        "    (extensive energy, O(n)); otherwise ``J_ij ~ N(coupling_mean, coupling_std^2)``\n",
        "    (energy O(n^2)). Fields are ``h_i ~ N(local_fields_mean, local_fields_std^2)``.\n",
        "\n",
        "    ``edge_density`` sets the fraction of the ``n*(n-1)/2`` possible couplings that\n",
        "    are non-zero (1 = fully dense). The kept edges always include a random spanning\n",
        "    tree, so the interaction graph is guaranteed connected. This requires at least\n",
        "    ``n-1`` edges, so ``edge_density`` must be at least ``2/n``.\n",
        "\n",
        "    Args:\n",
        "        n: Number of spins (>= 2).\n",
        "        coupling_mean: Mean coupling before optional SK scaling.\n",
        "        coupling_std: Coupling std before optional SK scaling.\n",
        "        local_fields_mean: Mean longitudinal field.\n",
        "        local_fields_std: Std of the longitudinal fields.\n",
        "        edge_density: Fraction of non-zero couplings, in ``[2/n, 1]``.\n",
        "        ensure_extensivity: Whether to apply the SK 1/n scaling.\n",
        "        seed: random number generator seed.\n",
        "\n",
        "    Returns:\n",
        "        Tuple ``(couplings, fields)``: a symmetric ``(n, n)`` matrix with zero\n",
        "        diagonal, and an ``(n,)`` field vector.\n",
        "\n",
        "    Raises:\n",
        "        ValueError: If ``n < 2``, ``coupling_std < 0``, ``local_fields_std < 0``,\n",
        "            or ``edge_density`` is outside ``[2/n, 1]``.\n",
        "    \"\"\"\n",
        "    if n < 2:\n",
        "        raise ValueError(f\"n must be >= 2, got {n}\")\n",
        "    if coupling_std < 0 or local_fields_std < 0:\n",
        "        raise ValueError(\n",
        "            \"coupling_std and local_fields_std must be non-negative\"\n",
        "        )\n",
        "\n",
        "    # A connected graph on n nodes needs at least n-1 of the n*(n-1)/2 possible\n",
        "    # edges, so edge_density has a hard lower bound of 2/n.\n",
        "    min_edge_density = 2.0 / n\n",
        "    if not min_edge_density <= edge_density <= 1.0:\n",
        "        raise ValueError(\n",
        "            f\"edge_density must be in [{min_edge_density:.4g}, 1] for n={n} \"\n",
        "            f\"(at least n-1 edges are needed to keep the graph connected), \"\n",
        "            f\"got {edge_density}\"\n",
        "        )\n",
        "\n",
        "    rng = np.random.default_rng(seed)\n",
        "\n",
        "    j_loc, j_scale = (\n",
        "        (coupling_mean / n, coupling_std / np.sqrt(n))\n",
        "        if ensure_extensivity\n",
        "        else (coupling_mean, coupling_std)\n",
        "    )\n",
        "\n",
        "    upper_idx = np.triu_indices(n, k=1)\n",
        "    n_edges = len(upper_idx[0])\n",
        "\n",
        "    # Select which edges are present.\n",
        "    if edge_density < 1.0:\n",
        "        n_keep = int(round(edge_density * n_edges))\n",
        "        # Map each (i, j) node pair to its position in the flat upper-triangle list.\n",
        "        pair_to_flat = {\n",
        "            (int(i), int(j)): idx\n",
        "            for idx, (i, j) in enumerate(\n",
        "                zip(upper_idx[0], upper_idx[1], strict=False)\n",
        "            )\n",
        "        }\n",
        "\n",
        "        # Random spanning tree: node perm[k] links to a random earlier node.\n",
        "        perm = rng.permutation(n)\n",
        "        keep = np.zeros(n_edges, dtype=bool)\n",
        "        for k in range(1, n):\n",
        "            child, parent = perm[k], perm[rng.integers(0, k)]\n",
        "            i, j = min(child, parent), max(child, parent)\n",
        "            keep[pair_to_flat[(int(i), int(j))]] = True\n",
        "\n",
        "        # Fill the remaining budget with random non-tree edges.\n",
        "        remaining = n_keep - (n - 1)\n",
        "        if remaining > 0:\n",
        "            keep[\n",
        "                rng.choice(\n",
        "                    np.flatnonzero(~keep), size=remaining, replace=False\n",
        "                )\n",
        "            ] = True\n",
        "    else:\n",
        "        keep = np.ones(n_edges, dtype=bool)\n",
        "\n",
        "    n_present = int(keep.sum())\n",
        "    if j_scale == 0.0:\n",
        "        vals = np.full(n_present, j_loc)\n",
        "    else:\n",
        "        vals = rng.normal(loc=j_loc, scale=j_scale, size=n_present)\n",
        "\n",
        "    couplings = np.zeros((n, n))\n",
        "    couplings[upper_idx[0][keep], upper_idx[1][keep]] = vals\n",
        "    couplings += couplings.T  # symmetrize; diagonal stays zero\n",
        "\n",
        "    fields = (\n",
        "        np.full(n, local_fields_mean)\n",
        "        if local_fields_std == 0.0\n",
        "        else rng.normal(loc=local_fields_mean, scale=local_fields_std, size=n)\n",
        "    )\n",
        "\n",
        "    return couplings, fields"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "cbc412ce550b1e6c",
      "metadata": {},
      "source": [
        "<span id=\"step-2-solve-the-problem-using-the-parity-twine-optimizer\" />\n",
        "\n",
        "### ステップ 2：パリティ・トワイン・オプティマイザーを使用して問題を解く\n",
        "\n",
        "上記の機能を使えば、SK問題を設定し、Twine Optimizerと選択した IBM Quantum® バックエンドを用いて解を見つけることができます。\n",
        "\n",
        "この関数を実行するには、適切なバックエンドを選択してください。たとえば、ibm\\_phoenix などです。\n",
        "\n",
        "必要に応じて、送信をさらに細かく制御するためのオプションを使用してください：\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "9dcece43ce7ad415",
      "metadata": {},
      "outputs": [],
      "source": [
        "options = {\n",
        "    \"shots\": 100000,\n",
        "    \"postprocessing_level\": 1,\n",
        "    \"transpile_only\": False,\n",
        "    \"job_tags\": [\"sk\"],\n",
        "}"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "f5f18f4df42cb260",
      "metadata": {},
      "source": [
        "ここで、 は回路の実行回数を指定する整数 `shots` であり、 は結果に後処理を適用するかどうかを決定 `postprocessing_level` し、\n",
        "`transpile_only` は問題が回路へのトランスパイルのみ（解かれることはない）かを選択し、 は IBM Quantum Platform 上でジョブを識別するためのラベル `job_tags` です。\n",
        "\n",
        "SKモデルのサイズは、相互作用するスピンの数である $N$ によって定義される。 $N$ を選択すると、上記のコードによって、. に関する問題が生成されます `n_spins`。\n",
        "\n",
        "オプティマイザーを実行します：\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "d36e4ab4ca0a4a08",
      "metadata": {},
      "outputs": [],
      "source": [
        "n_spins = 50\n",
        "sk_problem = generate_sk_problem(n_spins)\n",
        "\n",
        "function_job = function.run(\n",
        "    problem=sk_problem,\n",
        "    variable_type=\"spin\",\n",
        "    backend_name=\"ibm_phoenix\",\n",
        "    options=options,\n",
        ")\n",
        "print(f\"Job ID: {function_job.job_id}\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "8d4d7fd9e9fa2448",
      "metadata": {},
      "source": [
        "ジョブのステータスを確認する：\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "c42621d5cdc6b25",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Monitor the job status\n",
        "function_job.status()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "a9ec094ef5bcdac2",
      "metadata": {},
      "source": [
        "結果を取得する：\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "c10e7dd2b49e7e36",
      "metadata": {},
      "outputs": [],
      "source": [
        "result = function_job.result()\n",
        "\n",
        "result"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "d993123e6787e5af",
      "metadata": {},
      "source": [
        "結果は次のような形式になります：\n",
        "\n",
        "```\n",
        "{\n",
        "    'solution': {'0': 1, '1': 1, '10': 1, '11': 1, ... },\n",
        "    'objective_value':  -240.5425312543882,\n",
        "    'solution_bitstring': '00001101110100100111001110101001101111111011001110',\n",
        "    'metadata': {\n",
        "        'circuit_metrics': {\n",
        "            'depth': 523,\n",
        "            'gate_count': 10118,\n",
        "            'two_qubit_gate_depth': 196,\n",
        "            'two_qubit_gate_count': 2499,\n",
        "            'num_qubits': 50,\n",
        "            'operations': {'sx': 3353, 'rz': 3320, 'cz': 2499, 'delay': 894, 'measure': 50, 'x': 2},\n",
        "        },\n",
        "        'solver_info': {\n",
        "            'variable_mapping': {'0': 0, '1': 1, '10': 2, '11': 3, ... },\n",
        "            'bitstring_distributions': {\n",
        "                'before_postprocessing': {'011101110010110111001110011000': 1, ... },\n",
        "                'after_postprocessing': {'011011110000110101001111011000': 1, ... }\n",
        "            },\n",
        "            'best_parameters': {\n",
        "                'beta': [-0.4602084830507902],\n",
        "                'gamma': [1.8500357096574955]\n",
        "            }\n",
        "        },\n",
        "        'resource_usage': {\n",
        "            'RUNNING: MAPPING': {'CPU_TIME': 290.272},\n",
        "            'RUNNING: OPTIMIZING_FOR_HARDWARE': {'CPU_TIME': 0.494},\n",
        "            'RUNNING: WAITING_FOR_QPU': {'CPU_TIME': 8.775},\n",
        "            'RUNNING: EXECUTING_QPU': {'QPU_TIME': 31.0},\n",
        "            'RUNNING: POST_PROCESSING': {'CPU_TIME': 162.96},\n",
        "        },\n",
        "    }\n",
        "}\n",
        "```\n",
        "\n",
        "ここで、辞書 `solution` は問題で定義された量子ビットに対応しており、SKモデルハミルトニアンに対するそれらの最適化されたスピン値を示している。\n",
        "最適解におけるこの特定のスピンの配列は、与えられたランダムな相互作用の強さを基に、系の総エネルギーを最小化する状態を表している。\n",
        "SKモデルにおいて、これは無秩序な磁性系の最低エネルギー状態とみなすことができる。\n",
        "\n",
        "`metadata` トランスパイレーションに関する情報（2量子ビットゲートの数／深さ、使用されたゲート、アクティブな量子ビット）や、さまざまな実行時間に関する情報を提供します。\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "2cb5785c",
      "metadata": {},
      "source": [
        "<span id=\"next-steps\" />\n",
        "\n",
        "## 次のステップ\n",
        "\n",
        "<Admonition type=\"tip\" title=\"推奨事項\">\n",
        "  * この[フォーム](https://parityqc.com/products/parity-twine-optimizer/free-trial)に必要事項を記入して、当該機能へのアクセス権を申請してください。\n",
        "  * このQiskit関数の [APIリファレンス](/docs/api/functions/parity-twine-optimizer)をご覧ください。\n",
        "  * [ガイド](/docs/guides/parity-twine-optimizer)をお読みください。\n",
        "  * 「Market Split」問題にParity Twine Optimizerを適用する方法について、 [チュートリアル](/docs/tutorials/parity-twine-optimizer-ms)を試してみてください。\n",
        "  * [Drier ら（2025）による](https://arxiv.org/abs/2501.14020) 「接続性を考慮した量子アルゴリズムの合成」 ArXiv のプレプリントを参照のこと。\n",
        "</Admonition>\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "id": "a1b8767d",
      "source": "© IBM Corp., 2017-2026"
    }
  ],
  "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"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 5
}