{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Things you can do with pyGSTi's \"essential objects\"\n", "Now that we've covered what [circuits, models, and data sets](01-Essential-Objects.ipy) are, let's see what we can do with them! This tutorial is intended to be an overview of the things that pyGSTi is able to do, with links to more detailed explanations and demonstrations as is appropriate. We begin with the simpler applications and proceed to the more complex ones. Here's a table of contents to give you a sense of what's here and so you can skip around if you'd like. Each of the sections here can more-or-less stand on its own.\n", "\n", "## Contents\n", "- [computing circuit outcome probabilities](#computing_circuit_probs)\n", "- [simulating observed data based on a model](#simulating_data)\n", "- [testing how well a model describes a set of data](#model_testing)\n", "- [running Randomized Benchmarking (RB)](#randomized_benchmarking)\n", "- [running Robust Phase Estimation (RPE)](#robust_phase_estimation)\n", "- [performing data set comparison tests](#data_comparisons)\n", "- [running Gate Set Tomography (GST)](#gate_set_tomography)\n", "- [idle tomography (IDT)](#idle_tomography)\n", "- [drift characterization](#drift_characterization)\n", "- [time-dependent GST](#timedep_tomography)\n", "- [multi-qubit tomography](#multiq_tomography)\n", "\n", "We'll begin by setting up a `Workspace` so we can display pretty interactive figures inline (see the [intro to Workspaces tutorial](reporting/Workspace.ipynb) for more details)." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "import pygsti\n", "import numpy as np\n", "ws = pygsti.report.Workspace()\n", "ws.init_notebook_mode(autodisplay=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Computing circuit outcome probabilities\n", "One of the simplest uses of pyGSTi is to construct a `Model` and use it to compute the outcome probabilities of one or more `Circuit` objects. This is generally accomplished using the `.probs` method of a `Model` object as shown below (this was also demonstrated in the [essential objects tutorial](01-EssentialObjects.ipynb)). The real work is in constructing the `Circuit` and `Model` objects, which is covered in more detail in the [circuits tutorial](objects/Circuit.ipynb) and in the [explicit-model](objects/ExplicitModel.ipynb) (best for 1-2 qubits) and [implicit-model](objects/ImplicitModel.ipynb) (best for 3+ qubits) tutorials. For more information on circuit simulation, see the [circuit simulation tutorial](algorithms/CircuitSimulation.ipynb). " ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "mdl = pygsti.construction.build_explicit_model((0,1),\n", " [(), ('Gx',0), ('Gy',0), ('Gx',1), ('Gy',1), ('Gcnot',0,1)],\n", " [\"I(0,1)\",\"X(pi/2,0)\", \"Y(pi/2,0)\", \"X(pi/2,1)\", \"Y(pi/2,1)\", \"CNOT(0,1)\"]) \n", "c = pygsti.objects.Circuit([('Gx',0),('Gcnot',0,1),('Gy',1)] , line_labels=[0,1])\n", "print(\"mdl will simulate probabilities using the '%s' forward-simulation method.\" % mdl.simtype)\n", "mdl.probs(c) # Compute the outcome probabilities of circuit `c`" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Back to contents\n", "" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Simulating observed data based on a model\n", "Only slightly more complex than computing circuit-outcome probabilities is generating simulated data (outcome *counts* rather than probabilities). This is performed by the `generate_fake_data` function, which just samples the circuit-outcome probability distribution. You supply a list of `Circuits`, a number of samples, and often times a seed to initialize the random sampler. This is an easy way to create a `DataSet` to test other pyGSTi functions or to use independently. \n", "\n", "The default behavior is to sample the multinomial distribution associated with the given outcome probabilities and number of samples. It's possible to turn off finite-sample error altogether and make the data-set counts *exactly equal* the probability values multiplied by the number of samples by setting `sampleError='none'`, as demonstrated below." ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "circuit_list = pygsti.construction.circuit_list([ (), \n", " (('Gx',0),),\n", " (('Gx',0),('Gy',1)),\n", " (('Gx',0),)*4,\n", " (('Gx',0),('Gcnot',0,1)) ], line_labels=(0,1))\n", "ds_fake = pygsti.construction.generate_fake_data(mdl, circuit_list, nSamples=100,\n", " sampleError='multinomial', seed=1234)\n", "print(\"Normal:\")\n", "print(ds_fake)\n", "\n", "ds_nosampleerr = pygsti.construction.generate_fake_data(mdl, circuit_list, nSamples=100,\n", " sampleError='none')\n", "print(\"Without any sample error:\")\n", "print(ds_nosampleerr)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Back to contents\n", "" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Testing how well a model describes a set of data\n", "The above section showed how the circuit-outcome probabilities computed by a `Model` object can be used to generate data. We can also compare these probabilities with the outcome counts in an existing `DataSet`, that is, ask the question: \"For each circuit, how well do the frequencies of the outcomes (in the data) align with the probabilities predicted by the model?\". There are several common statistics for this purpose; the two used most often in pyGSTi are the $\\chi^2$ and log-likelihood ($\\log\\mathcal{L}$) statistics. If you're not sure what these are, the Methods section of [this paper](https://www.nature.com/articles/ncomms14485) provides some details and here are a few practical considerations:\n", "- the larger $\\log\\mathcal{L}$ is, and the smaller $\\chi^2$ is, the better the model agrees with the data.\n", "- the value of $\\log\\mathcal{L}$ doesn't mean anything in isolation - only when compared to other $\\log\\mathcal{L}$ values.\n", "- one can compute the $\\log\\mathcal{L}$ of a \"maximal model\" that agrees with the data exactly. We call this value $\\log\\mathcal{L}_{max}$.\n", "- in the limit of many samples, $\\chi^2 \\approx 2(\\log\\mathcal{L}_{max}-\\log\\mathcal{L})$, and we denote the latter $2\\Delta\\log\\mathcal{L}$. \n", "- let $k$ be the the number of independent degrees of freedom in the data (e.g. each row of 4 (00, 01, 10, and 11) counts in the example below contributes $3$ degrees of freedom because the counts are constrained to add to 100, so $k=5*3=15$). If the model is \"valid\" (i.e. it *could* have generated the data) then $2\\Delta\\log\\mathcal{L}$ should have come from a $\\chi^2_k$ distribution, i.e. it has expectation value $k$ and standard deviation $\\sqrt{2k}$.\n", "\n", "Here's how we compute the $\\chi^2$ and $2\\Delta\\log\\mathcal{L}$ between some data and a model:" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "dataset_txt = \\\n", "\"\"\"## Columns = 00 count, 01 count, 10 count, 11 count\n", "{}@(0,1) 100 0 0 0\n", "Gx:0@(0,1) 55 5 40 0\n", "Gx:0Gy:1@(0,1) 20 27 23 30\n", "Gx:0^4@(0,1) 85 3 10 2\n", "Gx:0Gcnot:0:1@(0,1) 45 1 4 50\n", "\"\"\"\n", "with open(\"tutorial_files/Example_Short_Dataset.txt\",\"w\") as f:\n", " f.write(dataset_txt)\n", "ds = pygsti.io.load_dataset(\"tutorial_files/Example_Short_Dataset.txt\")\n", "\n", "def compare(prefix, model, dataset):\n", " chi2 = pygsti.tools.chi2(model, dataset)\n", " logl = pygsti.tools.logl(model, dataset)\n", " max_logl = pygsti.tools.logl_max(model, dataset)\n", " k = dataset.get_degrees_of_freedom()\n", " Nsigma = (2*(max_logl-logl) - k)/np.sqrt(2*k)\n", " print(prefix, \"\\n chi^2 = \",chi2,\"\\n 2DeltaLogL = \", 2*(max_logl-logl),\n", " \"\\n #std-deviations away from expected (%g) = \" % k,Nsigma,\"\\n\")\n", " \n", "print(\"\\nModel compared with:\")\n", "compare(\"1. Hand-chosen data (doesn't agree): \",mdl,ds)\n", "compare(\"2. Model-generated data (agrees): \",mdl,ds_fake)\n", "compare(\"3. Model-generated data w/no sample err (agrees *exactly*): \",mdl,ds_nosampleerr)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can also look at these values on a per-circuit basis:" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "logl_percircuit = pygsti.tools.logl_terms(mdl, ds)\n", "max_logl_percircuit = pygsti.tools.logl_max_terms(mdl, ds)\n", "print(\"2DeltaLogL per circuit = \", 2*(max_logl_percircuit - logl_percircuit))\n", "\n", "#ws.ColorBoxPlot('logl', pygsti.obj.LsGermsSerialStructure([0],)) TODO" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "It's possible to display model testing results within figure and HTML reports too. For more information on model testing, especially alongside GST, see the [tutorial on model testing](algorithms/ModelTesting.ipynb)(using protocol objects) and the [functions for model testing](algorithms/ModelTesting-functions.ipynb)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Back to contents\n", "" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Randomized Benchmarking (RB)\n", "PyGSTi is able to perform two types of Randomized Benchmarking (RB). First, there is the [standard Clifford-circuit-based RB](http://journals.aps.org/prl/abstract/10.1103/PhysRevLett.106.180504) protocol first defined by Magesan et al. Second, there is [\"Direct RB\"](https://arxiv.org/abs/1807.07975), which is particularly suited to multi-qubit benchmarking. More more details on using these protocols (e.g. how to generate a set of RB sequences) see the separate [Clifford RB tutorial](algorithms/CliffordRB.ipynb) and [Direct RB tutorial](algorithms/DirectRB.ipynb) notebooks. Below we demonstrate how easy it is to analyze RB data (the same using either protocol). For further details and options on analyzing RB data, see the [RB data analysis tutorial](algorithms/RBAnalysis.ipynb). If you're interested using pyGSTi noise models to simulate RB results, see the tutorials for [Clifford RB simulation using explicit-op models](algorithms/advanced/CliffordRB-Simulation-ExplicitModel.ipynb) and [Clifford RB simulation using implicit models](algorithms/advanced/CliffordRB-Simulation-ImplicitModel.ipynb)." ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [], "source": [ "#TODO REMOVE\n", "# from pygsti.extras import rb\n", "\n", "# rbresults_txt = \\\n", "# \"\"\"# Results from a DRB simulation\n", "# # Number of qubits\n", "# 5\n", "# # RB length // Success counts // Total counts // Circuit depth // Circuit two-qubit gate count\n", "# 0 37 50 50 25\n", "# 10 36 50 51 33\n", "# 20 38 50 74 56\n", "# 30 35 50 78 62\n", "# 50 28 50 102 87\n", "# 100 25 50 139 163\n", "# 200 20 50 241 288\n", "# 400 10 50 446 546\n", "# \"\"\"\n", "# with open(\"tutorial_files/Example_RBData.txt\",\"w\") as f:\n", "# f.write(rbresults_txt)\n", "\n", "# rbdata = rb.io.import_rb_summary_data('tutorial_files/Example_RBData.txt')\n", "# rbresults = rb.analysis.std_practice_analysis(rbdata)\n", "# ws.RandomizedBenchmarkingPlot(rbresults)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Back to contents\n", "" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Robust Phase Estimation (RPE)\n", "The Robust Phase Estimation (RPE) protocol is designed to efficiently estimate a few specific parameters of certain single-qubit models. Below we demonstrate how to run RPE with the single-qubit model containing $X(\\pi/2)$ and $Y(\\pi/2)$ gates. The list of requisite circuits is given by `make_rpe_angle_string_list_dict` and simulated noisy data is analyzed using `analyze_rpe_data`. For more information on running RPE see the [RPE tutorial](algorithms/RobustPhaseEstimation.ipynb)." ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [], "source": [ "from pygsti.extras import rpe\n", "from pygsti.modelpacks.legacy import std1Q_XY\n", "import numpy as np\n", "\n", "#Declare the particular RPE instance we are interested in (X and Y pi/2 rotations)\n", "# Note: Prep and measurement are for the |0> state.\n", "rpeconfig_inst = rpe.rpeconfig_GxPi2_GyPi2_00\n", "stringListsRPE = rpe.rpeconstruction.make_rpe_angle_string_list_dict(10,rpeconfig_inst)\n", "\n", "angleList = ['alpha','epsilon','theta']\n", "numStrsD = {'RPE' : [6*i for i in np.arange(1,12)] }\n", "\n", "#Create fake noisy model\n", "print(stringListsRPE['totalStrList'][0])\n", "mdl_real = std1Q_XY.target_model().randomize_with_unitary(.01,seed=0)\n", "ds_rpe = pygsti.construction.generate_fake_data(mdl_real,stringListsRPE['totalStrList'],\n", " 1000,sampleError='binomial',seed=1)\n", "\n", "#Run RPE protocol\n", "resultsRPE = rpe.analyze_rpe_data(ds_rpe,mdl_real,stringListsRPE,rpeconfig_inst)\n", "\n", "print('alpha_true - alpha_est_final =',resultsRPE['alphaErrorList'][-1])\n", "print('epsilon_true - epsilon_est_final =',resultsRPE['epsilonErrorList'][-1])\n", "print('theta_true - theta_est_final =',resultsRPE['thetaErrorList'][-1])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Back to contents\n", "" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Data set comparison tests\n", "The `DataComparator` object is designed to check multiple `DataSet` objects for consistency. This procedure essentially answers the question: \"Is it better to describe `DataSet`s $A$ and $B$ as having been generated by the *same* set of probabilities or *different* sets?\". This quick test is useful for detecting drift in experimental setups from one round of data-taking to the next, and doesn't require constructing any `Model` objects. Below, we generate three `DataSet` objects - two from the same underlying model and one from a different model - and show that we can detect this difference. For more information, see the [tutorial on data set comparison](algorithms/DataSetComparison.ipynb)." ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [], "source": [ "from pygsti.modelpacks import smq1Q_XYI\n", "\n", "#Generate data from two different models\n", "mdlA = smq1Q_XYI.target_model().randomize_with_unitary(.01,seed=0)\n", "mdlB = smq1Q_XYI.target_model().randomize_with_unitary(.01,seed=1)\n", "\n", "circuits = pygsti.construction.make_lsgst_experiment_list(\n", " smq1Q_XYI.target_model(),smq1Q_XYI.prep_fiducials(),\n", " smq1Q_XYI.meas_fiducials(),smq1Q_XYI.germs(),[1,2,4,8])\n", "\n", "#Generate the data for the two datasets, using the same model, and one with a different model\n", "dsA1 = pygsti.construction.generate_fake_data(mdlA,circuits,100,'binomial',seed=10)\n", "dsA2 = pygsti.construction.generate_fake_data(mdlA,circuits,100,'binomial',seed=20)\n", "dsB = pygsti.construction.generate_fake_data(mdlB,circuits,100,'binomial',seed=30)\n", "\n", "#Let's compare the two datasets.\n", "print(\"Compare two *consistent* DataSets (generated from the same underlying model)\")\n", "comparator_A1_A2 = pygsti.objects.DataComparator([dsA1,dsA2])\n", "comparator_A1_A2.implement(significance=0.05)\n", "\n", "print(\"\\nCompare two *inconsistent* DataSets (generated from different model)\")\n", "comparator_A1_B = pygsti.objects.DataComparator([dsA1,dsB])\n", "comparator_A1_B.implement(significance=0.05)\n", "\n", "#Plots of consistent (top) and inconsistent (bottom) cases\n", "ws.DatasetComparisonHistogramPlot(comparator_A1_A2, log=True, display='pvalue', scale=0.8)\n", "ws.DatasetComparisonHistogramPlot(comparator_A1_B, log=True, display='pvalue', scale=0.8)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Back to contents\n", "" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Gate Set Tomography (GST)\n", "Gate set tomography (GST) is a protocol designed to solve the inverse of \"[use this model to simulate observed data](#simulating_data)\"; its goal is to *infer a model based on actual observed data*. From a functional perspective, GST can be viewed as an inverse of the `generate_fake_data` function we've used a bunch above: it takes a `DataSet` and produces a `Model`.\n", "\n", "Because this inverse problem traverses some technical challenges, GST also requires a *structured* set of `Circuits` to work reliably and efficiently. Here enters the concepts of \"fiducial\" and \"germ\" circuits, as well as a list of \"maximum-repeated-germ-lengths\" or just \"max-lengths\". For details, see the [tutorial on the structure of GST circuits](objects/advanced/GSTCircuitConstruction.ipynb) and the [tutorial on fiducial and germ selection](algorithms/advanced/GST-FiducialAndGermSelection.ipynb). The important takeaway is that the GST circuits are described below by the 4 variables: `prep_fiducials`, `meas_fiducials`, `germs`, and `maxLengths`.\n", "\n", "Below, we generate a set of GST circuits and simulate them using a noisy (slightly depolarized) model of the some ideal operations to get a `DataSet`. " ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [], "source": [ "from pygsti.modelpacks import smq1Q_XYI\n", "\n", "# 1) get the target Model\n", "mdl_ideal = smq1Q_XYI.target_model()\n", "\n", "# 2) get the building blocks needed to specify which circuits are needed\n", "prep_fiducials, meas_fiducials = smq1Q_XYI.prep_fiducials(), smq1Q_XYI.meas_fiducials()\n", "germs = smq1Q_XYI.germs()\n", "maxLengths = [1,2,4] # roughly gives the length of the sequences used by GST\n", "\n", "# 3) generate \"fake\" data from a depolarized version of mdl_ideal\n", "mdl_true = mdl_ideal.depolarize(op_noise=0.01, spam_noise=0.001)\n", "listOfExperiments = pygsti.construction.make_lsgst_experiment_list(\n", " mdl_ideal, prep_fiducials, meas_fiducials, germs, maxLengths)\n", "ds = pygsti.construction.generate_fake_data(mdl_true, listOfExperiments, nSamples=1000,\n", " sampleError=\"binomial\", seed=1234)\n", "\n", "#Run GST\n", "results = pygsti.do_stdpractice_gst(ds, mdl_ideal, prep_fiducials, meas_fiducials, \n", " germs, maxLengths, modes=\"TP,Target\", verbosity=1)\n", "\n", "mdl_estimate = results.estimates['TP'].models['stdgaugeopt']\n", "print(\"2DeltaLogL(estimate, data): \", pygsti.tools.two_delta_logl(mdl_estimate, ds))\n", "print(\"2DeltaLogL(true, data): \", pygsti.tools.two_delta_logl(mdl_true, ds))\n", "print(\"2DeltaLogL(ideal, data): \", pygsti.tools.two_delta_logl(mdl_ideal, ds))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "[Recall](#model_testing) that a lower $2\\Delta\\log\\mathcal{L}$ means a better agreement between model and data. Note that the GST estimate fits the data best (even slightly better than the *true* model, because it agrees better with the finite sample noise), and the GST estimate is much better than the ideal model.\n", "\n", "GST is essentially an automated model-tester that keeps modifying and testing models until it finds one the agrees with the data as well any model of the specified type can. To learn more about how to run GST using functions that act on essential objects see the [function-based GST overview tutorial](algorithms/GST-Overview-functionbased.ipynb) and the [GST driver functions tutorial](algorithms/GST-Driverfunctions.ipynb). GST can also be run in a more object-oriented way, using `Protocol` objects as described in the [GST overview tutorial](algorithms/GST-Overview.ipynb).\n", "\n", "The output of GST is an entire `Model` (contrasted with the one or several numbers of RB and RPE), there are many ways to assess and understand the performance of a QIP based on GST results. The `ModelEstimateResults` object in pyGSTi is responsible for holding the GST and other model-based protocol results. The structure and use of a `ModelEstimateResults` object is explained in the [Results tutorial](objects/advanced/Results.ipynb). A common use for results objects is to generate \"reports\". PyGSTi has the ability to generate HTML reports (a directory of files) whose goal is to display relevant model vs. data metrics such as $2\\Delta\\log\\mathcal{L}$ as well as model vs. model metrics like process fidelity and diamond distance. To learn more about generating these \"model-explaining\" reports see the [report generation tutorial](reporting/ReportGeneration.ipynb).\n", "\n", "Here's an example of how to generate a report (it will auto-open in a new tab; if it doesn't display **try it in FireFox**):" ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "scrolled": true }, "outputs": [], "source": [ "pygsti.report.construct_standard_report(\n", " results, title=\"Example GST Report\", verbosity=1\n", ").write_html(\"tutorial_files/myFirstGSTReport\", auto_open=True, verbosity=1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Back to contents\n", "" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Idle tomography\n", "Idle tomography estimates the error rates of an $n$-qubit idle operation using relatively few sequences. To learn more about how to use it, see the [idle tomography tutorial](algorithms/IdleTomography.ipynb)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Back to contents\n", "" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Drift Characterization\n", "Time-series data can be analyzed for significant indications of drift (time variance in circuit outcome probabilities). See the [tutorial on drift characterization](algorithms/DriftCharacterization.ipynb) for more details." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Back to contents\n", "" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Time-dependent gate set tomography\n", "pyGSTi has recently added support for time-dependent models and data sets, allowing the GST to be performed in a time-dependent fashion. See the [time-dependent GST tutorial](algorithms/advanced/Time-dependent-GST.ipynb) for more details." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Back to contents\n", "" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Multi-qubit tomography\n", "pyGSTi contains all the ingredients necessary for multi-qubit tomography, and this functionality is currently in the beta-testing stages. Stay tuned!" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [], "source": [ "#Coming soon" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Back to contents" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# What's next?\n", "This concludes our overview of what can be done with pyGSTi. There are a few minor topics that haven't been touched on that we've collected within the next tutorial on [miscellaneous topics](03-Miscellaneous.ipynb), so you might want to take a quick look there to see if there's anything you're especially interested in." ] } ], "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.7.0" } }, "nbformat": 4, "nbformat_minor": 2 }