{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Standard Quantum Tomography\n",
    "Quara supports several estimators (linear, least squares, maximum-likelihood) for standard quantum tomography of state, POVM, and gate. Here we briefly explain how to use them with examples on 1-qubit system. Quara supports much larger systems. However, performances (computation speed and constraint feasibility) of numerical optimization solver implemented are quite low, and we do not recommend to perform tomographic data-processing for systems larger than 1-qubit. Improvements on the performances are necessary tasks at the development of Quara in the near future."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "\n",
    "# quara\n",
    "from quara.objects.composite_system_typical import generate_composite_system\n",
    "from quara.objects.tester_typical import (\n",
    "    generate_tester_states,\n",
    "    generate_tester_povms,\n",
    ")\n",
    "from quara.objects.qoperation_typical import generate_qoperation\n",
    "from quara.protocol.qtomography.standard.standard_qst import StandardQst\n",
    "from quara.protocol.qtomography.standard.standard_povmt import StandardPovmt\n",
    "from quara.protocol.qtomography.standard.standard_qpt import StandardQpt\n",
    "\n",
    "from quara.protocol.qtomography.standard.linear_estimator import LinearEstimator\n",
    "from quara.protocol.qtomography.standard.loss_minimization_estimator import (\n",
    "    LossMinimizationEstimator,\n",
    ")\n",
    "from quara.loss_function.weighted_probability_based_squared_error import (\n",
    "    WeightedProbabilityBasedSquaredError,\n",
    "    WeightedProbabilityBasedSquaredErrorOption,\n",
    ")\n",
    "from quara.minimization_algorithm.projected_gradient_descent_backtracking import (\n",
    "    ProjectedGradientDescentBacktracking,\n",
    "    ProjectedGradientDescentBacktrackingOption,\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Quantum State Tomography (1-qubit)\n",
    "First, we consider quantum state tomography on 1-qubit system. We prepare a system."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "elemental_systems:\n",
      "[0] 0 (system_id=140214303147968)\n",
      "\n",
      "dim: 2\n",
      "basis:\n",
      "(array([[0.70710678+0.j, 0.        +0.j],\n",
      "       [0.        +0.j, 0.70710678+0.j]]), array([[0.        +0.j, 0.70710678+0.j],\n",
      "       [0.70710678+0.j, 0.        +0.j]]), array([[0.+0.j        , 0.-0.70710678j],\n",
      "       [0.+0.70710678j, 0.+0.j        ]]), array([[ 0.70710678+0.j,  0.        +0.j],\n",
      "       [ 0.        +0.j, -0.70710678+0.j]]))\n"
     ]
    }
   ],
   "source": [
    "mode = \"qubit\"\n",
    "num = 1\n",
    "c_sys = generate_composite_system(mode=mode, num=num)\n",
    "print(c_sys)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In order to perform data-pocessing of standard quantum tomography, we need some pre-knowledge information on the tomographic experiment. Suppose that we performed projective measurements along with x, y, and z axes. We call them a tester. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Tester  0 :\n",
      " Type:\n",
      "Povm\n",
      "\n",
      "Dim:\n",
      "2\n",
      "\n",
      "Number of outcomes:\n",
      "2\n",
      "\n",
      "Vecs:\n",
      "[[ 0.70710678  0.70710678  0.          0.        ]\n",
      " [ 0.70710678 -0.70710678  0.          0.        ]]\n",
      "Tester  1 :\n",
      " Type:\n",
      "Povm\n",
      "\n",
      "Dim:\n",
      "2\n",
      "\n",
      "Number of outcomes:\n",
      "2\n",
      "\n",
      "Vecs:\n",
      "[[ 0.70710678  0.          0.70710678  0.        ]\n",
      " [ 0.70710678  0.         -0.70710678  0.        ]]\n",
      "Tester  2 :\n",
      " Type:\n",
      "Povm\n",
      "\n",
      "Dim:\n",
      "2\n",
      "\n",
      "Number of outcomes:\n",
      "2\n",
      "\n",
      "Vecs:\n",
      "[[ 0.70710678  0.          0.          0.70710678]\n",
      " [ 0.70710678  0.          0.         -0.70710678]]\n"
     ]
    }
   ],
   "source": [
    "# Testers\n",
    "names = [\"x\", \"y\", \"z\"]\n",
    "testers = generate_tester_povms(c_sys=c_sys, names = names)\n",
    "for i, tester in enumerate(testers):\n",
    "    print(\"Tester \", i, \":\\n\", tester)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We generate a StandardQst object. We set seed for generating pseudo-data. The seed is unnecessary if you do not generate pseudo-data."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "seed = 7896\n",
    "qst = StandardQst(testers, on_para_eq_constraint=True, schedules=\"all\", seed_data=seed)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Suppose that the state of the system to be estimated is $|A\\rangle$. We call it the true state."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Type:\n",
      "State\n",
      "\n",
      "Dim:\n",
      "2\n",
      "\n",
      "Vec:\n",
      "[0.70710678 0.5        0.5        0.        ]\n"
     ]
    }
   ],
   "source": [
    "mode = \"state\"\n",
    "name = \"a\"\n",
    "true = generate_qoperation(mode=mode, name=name, c_sys=c_sys)\n",
    "print(true)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The true probability distribution is given as follows."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[0.85355339 0.14644661]\n",
      " [0.85355339 0.14644661]\n",
      " [0.5        0.5       ]]\n"
     ]
    }
   ],
   "source": [
    "prob_dists = qst.calc_prob_dists(true)\n",
    "print(prob_dists)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Suppose that we performed tomographic experiment and repeated each sub-experiment with tester element 1000 times."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(1000, array([0.866, 0.134]))\n",
      "(1000, array([0.849, 0.151]))\n",
      "(1000, array([0.503, 0.497]))\n"
     ]
    }
   ],
   "source": [
    "#qst.reset_seed()\n",
    "num_data = 1000\n",
    "empi_dists = qst.generate_empi_dists(state=true, num_sum=num_data)\n",
    "for f in empi_dists:\n",
    "    print(f)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Here empi_dists is a set of empirical distributions, which is a pair of the repetition number and relative frequencies.\n",
    "\n",
    "When we choose a linear estimator, the estimate is calculated as follows."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Type:\n",
      "State\n",
      "\n",
      "Dim:\n",
      "2\n",
      "\n",
      "Vec:\n",
      "[0.70710678 0.51760216 0.49356053 0.00424264]\n",
      "is estimate physical? :  False\n",
      "\n",
      "Eigenvalues are:  [1.005733131206568, -0.005733131206568459]\n"
     ]
    }
   ],
   "source": [
    "estimator = LinearEstimator()\n",
    "result = estimator.calc_estimate(qtomography=qst, empi_dists=empi_dists, is_computation_time_required=True)\n",
    "estimate = result.estimated_qoperation\n",
    "print(estimate)\n",
    "print(\"is estimate physical? : \", estimate.is_physical())\n",
    "print(\"\\nEigenvalues are: \", estimate.calc_eigenvalues())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "An eigenvalue of the estimated density matrix is negative, which violates the requirement of positive-semidefiniteness on density matrix. This kind of violation can occur when we choose a linear estimator. In order to avoid the problem, we need to perform a constraint optimization at the data-processing."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "When we choose a constraint least squares estimator, the estimate is calculated as folllows. Here we choose a projected gradient descent back-tracking as the numerical optimization algorithm."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Type:\n",
      "State\n",
      "\n",
      "Dim:\n",
      "2\n",
      "\n",
      "Vec:\n",
      "[0.70710678 0.51173451 0.48796542 0.00419455]\n",
      "\n",
      "is estimate physical? :  False\n",
      "\n",
      "Eigenvalues are:  [1.0000000252534356, -2.5253435731453777e-08]\n"
     ]
    }
   ],
   "source": [
    "estimator = LossMinimizationEstimator()\n",
    "loss = WeightedProbabilityBasedSquaredError()\n",
    "loss_option = WeightedProbabilityBasedSquaredErrorOption(\"identity\")\n",
    "algo = ProjectedGradientDescentBacktracking()\n",
    "algo_option = ProjectedGradientDescentBacktrackingOption(mode_stopping_criterion_gradient_descent=\"sum_absolute_difference_variable\", num_history_stopping_criterion_gradient_descent=1)\n",
    "\n",
    "result = estimator.calc_estimate(qtomography=qst, empi_dists=empi_dists, loss=loss, loss_option=loss_option, algo=algo, algo_option=algo_option, is_computation_time_required=True)\n",
    "estimate = result.estimated_qoperation\n",
    "print(estimate)\n",
    "print(\"\\nis estimate physical? : \", estimate.is_physical())\n",
    "print(\"\\nEigenvalues are: \", estimate.calc_eigenvalues())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "An eigenvalue of the estimated density matrix is negative as same as observed at the case of linear estimator, although the strength of the violation is reduced from O(10^{-4}) to O(10^{-8}). This is due to the low constraint feasibility of the numerical solver implemented at the current version of Quara, which must be improved in the near future."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## POVM tomography (1-qubit)\n",
    "Next, we consider POVM tomography on 1-qubit system."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Composite System\n",
    "mode = \"qubit\"\n",
    "num = 1\n",
    "c_sys = generate_composite_system(mode=mode, num=num)\n",
    "#print(c_sys)\n",
    "\n",
    "# Testers\n",
    "names = [\"x0\", \"y0\", \"z0\", \"z1\"]\n",
    "testers = generate_tester_states(c_sys=c_sys, names = names)\n",
    "povmt = StandardPovmt(testers, num_outcomes=2, on_para_eq_constraint=True, schedules=\"all\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Type:\n",
      "Povm\n",
      "\n",
      "Dim:\n",
      "2\n",
      "\n",
      "Number of outcomes:\n",
      "2\n",
      "\n",
      "Vecs:\n",
      "[[ 0.70710678  0.          0.          0.70710678]\n",
      " [ 0.70710678  0.          0.         -0.70710678]]\n"
     ]
    }
   ],
   "source": [
    "mode = \"povm\"\n",
    "name = \"z\"\n",
    "true = generate_qoperation(mode=mode, name=name, c_sys=c_sys)\n",
    "print(true)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[0.5 0.5]\n",
      " [0.5 0.5]\n",
      " [1.  0. ]\n",
      " [0.  1. ]]\n"
     ]
    }
   ],
   "source": [
    "prob_dists = povmt.calc_prob_dists(true)\n",
    "print(prob_dists)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(1000, array([0.481, 0.519]))\n",
      "(1000, array([0.472, 0.528]))\n",
      "(1000, array([1., 0.]))\n",
      "(1000, array([0., 1.]))\n"
     ]
    }
   ],
   "source": [
    "num_data = 1000\n",
    "empi_dists = povmt.generate_empi_dists(povm=true, num_sum=num_data)\n",
    "for f in empi_dists:\n",
    "    print(f)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Type:\n",
      "Povm\n",
      "\n",
      "Dim:\n",
      "2\n",
      "\n",
      "Number of outcomes:\n",
      "2\n",
      "\n",
      "Vecs:\n",
      "[[ 0.70710678 -0.02687006 -0.03959798  0.70710678]\n",
      " [ 0.70710678  0.02687006  0.03959798 -0.70710678]]\n",
      "\n",
      "is estimate physical? :  False\n",
      "\n",
      "Eigenvalues are: [[1.0011436919686807, -0.0011436919686808854], [1.0011436919686814, -0.001143691968680663]]\n"
     ]
    }
   ],
   "source": [
    "estimator = LinearEstimator()\n",
    "result = estimator.calc_estimate(qtomography=povmt, empi_dists=empi_dists, is_computation_time_required=True)\n",
    "estimate = result.estimated_qoperation\n",
    "print(estimate)\n",
    "print(\"\\nis estimate physical? : \", estimate.is_physical())\n",
    "print(\"\\nEigenvalues are:\", estimate.calc_eigenvalues())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Type:\n",
      "Povm\n",
      "\n",
      "Dim:\n",
      "2\n",
      "\n",
      "Number of outcomes:\n",
      "2\n",
      "\n",
      "Vecs:\n",
      "[[ 0.7071068  -0.02674827 -0.03941851  0.70550034]\n",
      " [ 0.70710677  0.02674827  0.03941851 -0.70550034]]\n",
      "\n",
      "is estimate physical? :  False\n",
      "\n",
      "Eigenvalues are: [[1.000000020995048, 2.2410805849970572e-13], [0.9999999999997758, -2.0995047856709262e-08]]\n"
     ]
    }
   ],
   "source": [
    "estimator = LossMinimizationEstimator()\n",
    "loss = WeightedProbabilityBasedSquaredError(4)\n",
    "loss_option = WeightedProbabilityBasedSquaredErrorOption(\"identity\")\n",
    "algo = ProjectedGradientDescentBacktracking()  \n",
    "algo_option = ProjectedGradientDescentBacktrackingOption()\n",
    "\n",
    "result = estimator.calc_estimate(qtomography=povmt, empi_dists=empi_dists, loss=loss, loss_option=loss_option, algo=algo, algo_option=algo_option, is_computation_time_required=True)\n",
    "estimate = result.estimated_qoperation\n",
    "print(estimate)\n",
    "print(\"\\nis estimate physical? : \", estimate.is_physical())\n",
    "print(\"\\nEigenvalues are:\", estimate.calc_eigenvalues())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Quantum Process Tomography (1-qubit)\n",
    "Finally, we consider quantum process tomography on 1-qubit system. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Composite System\n",
    "mode = \"qubit\"\n",
    "num = 1\n",
    "c_sys = generate_composite_system(mode=mode, num=num)\n",
    "#print(c_sys)\n",
    "\n",
    "# Testers\n",
    "names_states = [\"x0\", \"y0\", \"z0\", \"z1\"]\n",
    "testers_states = generate_tester_states(c_sys=c_sys, names = names_states)\n",
    "names_povms = [\"x\", \"y\", \"z\"]\n",
    "testers_povms = generate_tester_povms(c_sys=c_sys, names=names_povms)\n",
    "qpt = StandardQpt(states=testers_states, povms=testers_povms, on_para_eq_constraint=True, schedules=\"all\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Type:\n",
      "Gate\n",
      "\n",
      "Dim:\n",
      "2\n",
      "\n",
      "HS:\n",
      "[[ 1.  0.  0.  0.]\n",
      " [ 0.  0.  0.  1.]\n",
      " [ 0.  0. -1.  0.]\n",
      " [ 0.  1.  0.  0.]]\n"
     ]
    }
   ],
   "source": [
    "mode = \"gate\"\n",
    "name = \"hadamard\"\n",
    "true = generate_qoperation(mode=mode, name=name, c_sys=c_sys)\n",
    "print(true)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[0.5 0.5]\n",
      " [0.5 0.5]\n",
      " [1.  0. ]\n",
      " [0.5 0.5]\n",
      " [0.  1. ]\n",
      " [0.5 0.5]\n",
      " [1.  0. ]\n",
      " [0.5 0.5]\n",
      " [0.5 0.5]\n",
      " [0.  1. ]\n",
      " [0.5 0.5]\n",
      " [0.5 0.5]]\n"
     ]
    }
   ],
   "source": [
    "prob_dists = qpt.calc_prob_dists(true)\n",
    "print(prob_dists)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(1000, array([0.478, 0.522]))\n",
      "(1000, array([0.533, 0.467]))\n",
      "(1000, array([1., 0.]))\n",
      "(1000, array([0.52, 0.48]))\n",
      "(1000, array([0., 1.]))\n",
      "(1000, array([0.511, 0.489]))\n",
      "(1000, array([1., 0.]))\n",
      "(1000, array([0.493, 0.507]))\n",
      "(1000, array([0.487, 0.513]))\n",
      "(1000, array([0., 1.]))\n",
      "(1000, array([0.507, 0.493]))\n",
      "(1000, array([0.514, 0.486]))\n"
     ]
    }
   ],
   "source": [
    "num_data = 1000\n",
    "empi_dists = qpt.generate_empi_dists(gate=true, num_sum=num_data)\n",
    "for f in empi_dists:\n",
    "    print(f)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Type:\n",
      "Gate\n",
      "\n",
      "Dim:\n",
      "2\n",
      "\n",
      "HS:\n",
      "[[ 1.     0.     0.     0.   ]\n",
      " [ 0.    -0.044  0.04   1.   ]\n",
      " [ 0.     0.066 -1.    -0.014]\n",
      " [ 0.001  0.999  0.021 -0.027]]\n",
      "\n",
      "is estimate physical? :  False\n",
      "\n",
      "Eigenvalues are: [-0.03627627 -0.02101806  0.05672322  2.00057111]\n"
     ]
    }
   ],
   "source": [
    "estimator = LinearEstimator()\n",
    "result = estimator.calc_estimate(qtomography=qpt, empi_dists=empi_dists, is_computation_time_required=True)\n",
    "estimate = result.estimated_qoperation\n",
    "print(estimate)\n",
    "print(\"\\nis estimate physical? : \", estimate.is_physical())\n",
    "evals, evecs = np.linalg.eigh(estimate.to_choi_matrix())\n",
    "print(\"\\nEigenvalues are:\", evals)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Type:\n",
      "Gate\n",
      "\n",
      "Dim:\n",
      "2\n",
      "\n",
      "HS:\n",
      "[[ 1.00000000e+00  0.00000000e+00  0.00000000e+00  0.00000000e+00]\n",
      " [-1.02656507e-03 -1.09288050e-02  1.77136570e-02  9.84091419e-01]\n",
      " [-2.51796302e-03  5.18102148e-02 -9.76171254e-01 -1.85715203e-03]\n",
      " [ 9.98824726e-04  9.78244503e-01  3.60986493e-02 -1.15642106e-02]]\n",
      "\n",
      "is estimate physical? :  False\n",
      "\n",
      "Eigenvalues are: [-1.49033317e-08 -1.28148015e-08  2.97230730e-02  1.97027695e+00]\n"
     ]
    }
   ],
   "source": [
    "estimator = LossMinimizationEstimator()\n",
    "loss = WeightedProbabilityBasedSquaredError()\n",
    "loss_option = WeightedProbabilityBasedSquaredErrorOption(\"identity\")\n",
    "algo = ProjectedGradientDescentBacktracking()  \n",
    "algo_option = ProjectedGradientDescentBacktrackingOption()\n",
    "\n",
    "result = estimator.calc_estimate(qtomography=qpt, empi_dists=empi_dists, loss=loss, loss_option=loss_option, algo=algo, algo_option=algo_option, is_computation_time_required=True)\n",
    "estimate = result.estimated_qoperation\n",
    "print(estimate)\n",
    "print(\"\\nis estimate physical? : \", estimate.is_physical())\n",
    "evals, evecs = np.linalg.eigh(estimate.to_choi_matrix())\n",
    "print(\"\\nEigenvalues are:\", evals)"
   ]
  }
 ],
 "metadata": {
  "hide_input": false,
  "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.8.2"
  },
  "metadata": {
   "interpreter": {
    "hash": "46853efbcfaf24d51a2d058b3851333fe5208d7cf1dd9f790da7ce54269df75e"
   }
  },
  "toc": {
   "base_numbering": 1,
   "nav_menu": {},
   "number_sections": true,
   "sideBar": true,
   "skip_h1_title": false,
   "title_cell": "Table of Contents",
   "title_sidebar": "Contents",
   "toc_cell": false,
   "toc_position": {},
   "toc_section_display": true,
   "toc_window_display": false
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
