{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Circuit Tutorial\n", "This tutorial will show you how to create and use `Circuit` objects, which represent (suprise, suprise) quantum circuits. Noteable among their features is the ability to interface pyGSTi with other quantum circuit standards (e.g., conversion to [OpenQasm](https://arxiv.org/abs/1707.03429))\n", "\n", "First let's get the usual imports out of the way." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import pygsti\n", "from pygsti.objects import Circuit\n", "from pygsti.objects import Label as L" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Labels\n", "Let's begin by discussing gate and layer labels, which we'll use to build circuits.\n", "\n", "### Gate Labels\n", "Gate labels represent a single gate within a circuit, like a CNOT operation between two qubits. A gate label has two parts: a `str`-type name and a tuple of line labels. Gate names typically begin with 'G' because this is expected when we parse circuits from text files. The line labels assign the gate to those lines in the circuit. For example, `\"Gx\"` or `\"Gcnot\"` are common gate names, and the integers 0 to $n$ might be the available line labels. We can make a proper gate label by creating a instance of the `Label` class:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "myGateLabel = L('Gcnot',(0,1))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "But in nearly all scenarios it's also fine to use the Python tuple `('Gcnot',0,1)` as shorthand - this will get converted into the `Label` object above as needed within pyGSTi." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "myJustAsGoodGateLabel = ('Gcnot',0,1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As a **special case**, the tuple of line labels can be `None`. This is interpreted to mean that the gate acts on *all* the available lines. When just a string is used as a gate label it acts as though it's line labels are `None`. So these are also valid gate labels:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "mySpecialGateLabel = L('Gi')\n", "myJustAsGoodSpecialGateLabel = 'Gi'" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "When dealing with actual `Label` objects you can access the name and line labels of a gate label via the `.name` and `.sslbls` (short for \"state space labels\", which are the same as line labels as we'll see) members:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print(\"name = \", myGateLabel.name, \" sslbls = \", myGateLabel.sslbls)\n", "print(\"name = \", mySpecialGateLabel.name, \" sslbls = \", mySpecialGateLabel.sslbls)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Simple enough; now let's move on to layer labels:\n", "\n", "### Layer labels\n", "\n", "Layer labels represent an entire layer of a circuit. A layer label can either be a single gate label or a sequence of gate labels. In the former case, the layer is interpreted to have just a single gate in it. In the latter case, all of the gate labels comprising the layer label are interpreted as occurring simultaneously (in parallel) during the given circuit layer. Again, there's a proper way to make a layer label using a `Label` object, and a number of shorthand ways which are almost always equivalent: " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "layerLabel1 = myGateLabel # single-gate layer using Label object\n", "layerLabel2 = myJustAsGoodGateLabel # single-gate layer using tuple\n", "layerLabel3 = 'Gi' # single-gate layer using a string\n", "layerLabel4 = L( [L('Gx',0), L('Gcnot',(0,1))] ) # multi-gate layer as Label object, from Label objects\n", "layerLabel5 = L( [('Gx',0),('Gcnot',0,1)] ) # multi-gate layer as Label object, from tuple objects\n", "layerLabel6 = L( [('Gx',0),L('Gcnot',(0,1))] ) # multi-gate layer as Label object, from mixed objects\n", "layerLabel7 = [('Gx',0),('Gcnot',0,1)] # multi-gate layer as a list of tuples\n", "layerLable8 = L( [] ) # *empty* gate layer - useful to mean the identity on all qubits\n", "# etc, etc. -- anything reasonable works like it should" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Notice that the same `Label` object used for gate labels is used for layer labels. This is natural when gates and layers are thought of more broadly as \"operations\" (e.g. a layer of an $n$-qubit circuit is just a $n$-qubit gate). Thus, you can access the `.name` and `.sslbls` of a layer too (though the name is given the default value \"COMPOUND\"):" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print(\"name = \", layerLabel5.name, \" sslbls = \", layerLabel5.sslbls)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "A couple tricks:\n", "- when you're not sure whether a layer `Label` object has a multiple gates or is just a single simple gate label, you can iterate over the `.components` member of a `Label`. This iterates over the gate labels for a multi-gate layer label and just over the label itself for a simple gate label. For example:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print( list(L([('Gx',0),('Gcnot',0,1)]).components) )\n", "print( list(L('Gx',0).components) )" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- you can use `lbl.qubits` as an alias for `lbl.sslbls`, and `lbl.number_of_qubits` instead of `len(lbl.sslbls)`. These can improve code legibility when dealing a system of qubits (as opposed to qutrits, etc.). **Beware**: both of these quantities can be `None`, just like `lbl.sslbls`." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "lbl = L('Gcnot',(0,1))\n", "print(\"The label %s applies to %d qubits: %s\" % (str(lbl), lbl.number_of_qubits, str(lbl.qubits)))\n", "lbl = L('Gi')\n", "print(\"The label %s applies to %s qubits: %s\" % (str(lbl), lbl.number_of_qubits, str(lbl.qubits)))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Circuits\n", "\n", "The `Circuit` object encapsulates a quantum circuit as a sequence of *layer labels*, each of which contains zero or more non-identity *gate lables*. A `Circuit` has some number of labeled *lines* which should have a one-to-one correspondence with the factors $\\mathcal{H}_i$ when the quantum-state space is written as a tensor product: $\\mathcal{H}_1 \\otimes \\mathcal{H}_2 \\cdots \\otimes \\mathcal{H}_n$. Line labels can be integers or strings (in the above examples we used the integers 0 and 1). \n", "\n", "### Construction\n", "We initialize a `Circuit` with a sequence of *layer labels*, and either:\n", "- a sequence of line labels, as `line_labels`, or\n", "- the number of lines for the circuit, as `num_lines`, in which case the line labels are taken to be integers starting at 0." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "c = Circuit( [('Gx',0),('Gcnot',0,1),(),'Gall',('Gy',3)], line_labels=[0,1,2,3])\n", "c2 = Circuit( [('Gx',0),('Gcnot',0,1),(),'Gall',('Gy',3)], num_lines=4) # equivalent to above\n", "c3 = Circuit( [('Gx',0),('Gcnot',0,1),(),'Gall',('Gy',3)] ) # NOT equivalent, because line 2 is never used (see below)\n", "\n", "print(c) # You can print circuits to get a\n", "print(c2) # text-art version.\n", "print(c3)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In the **case of 1 or 2 qubits**, it can be more convenient to dispense with the line labels entirely and just equate gates with circuit layers and represent them with simple Python strings. If we initialize a `Circuit` without specifying the line labels (either by `line_labels` or by `num_lines`) *and* the layer labels don't contain any non-`None` line labels, then a `Circuit` is created which has a single special **'\\*'-line** which indicates that this circuit doesn't contain any explicit lines: " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "c4 = Circuit( ('Gx','Gy','Gi') )\n", "print(c4)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Using circuits with '\\*'-lines is fine (and is the default type of circuit for the \"standard\" 1- and 2-qubit modules located at `pygsti.construction.std*`); one just needs to be careful not to combine these circuits with other non-'\\*'-line circuits.\n", "\n", "**1Q Note:** Particularly within the 1-qubit context the **ordering direction** is important. The elements of a `Circuit` are read from **left-to-right**, meaning the first (left-most) layer is performed first. This is very natural for experiments since one can read the operation sequence as a script, executing each gate as one reads from left to right. It's also natural for 2+ qubit circuits which similar to standard quantum circuit diagrams. However, for 1-qubit circuits, since we insist on \"normal\" matrix multiplication conventions, the fact that the ordering of matrix products is *reversed* from that of operation sequences may be confusing. For example, the circuit `('Ga','Gb','Gc')`, in which Ga is performed first, corresponds to the matrix product $G_c G_b G_a$. The probability of this operation sequence for a SPAM label associated with the (column) vectors ($\\rho_0$,$E_0$) is given by $E_0^T G_c G_b G_a \\rho_0$, which can be interpreted as \"prepare state 0 first, then apply gate A, then B, then C, and finally measure effect 0\". While this nuance is typically hidden from the user (the `Model` functions which compute products and probabilities from `Circuit` objects perform the order reversal internally), it becomes very important if you plan to perform matrix products by hand. \n", "\n", "### Implied SPAM\n", "A `Circuit` may optionally begin with an explicit state-preparation and end with an explicit measurement, but these may be omitted when used with `Model` objects which have only a single state-preparation and/or POVM. Usually state preparation and measurement operations are represented by `\"rho\"`- and `\"M\"`-prefixed `str`-type labels that therefore act on all circuit lines. For example:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "c5 = Circuit( ['rho010',('Gz',1),[('Gswap',0,1),('Gy',2)],'Mx'] , line_labels=[0,1,2])\n", "print(c5)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Basic member access\n", "The basic member variables and functions of a `Circuit` that you may be interested in are demonstrated below: " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print(\"depth = \", c.depth) # circuit depth, i.e., the number of layers\n", "print(\"tup = \", c.tup) # circuit as a tuple of layer-labels (elements are *always* Label objects)\n", "print(\"str = \", c.str) # circuit as a single-line string\n", "print(\"lines = \",c.line_labels) # tuple of line labels\n", "print(\"#lines = \",c.num_lines) #number of line labels\n", "print(\"#multi-qubit gates = \", c.num_multiq_gates)\n", "c_copy = c.copy() #copies the circuit" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Indexing and Slicing\n", "When a `Circuit` is indexed as if it were a tuple of layer-labels. The index must be an integer and a `Label` object is returned. Once can also access a particular gate label by providing a second index which is either a single line label, a tuple of line labels, or, *if the line labels are integers*, a slice of line labels:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "c = Circuit( [('Gx',0),[('Gcnot',0,1),('Gz',3)],(),'Gall',('Gy',3)], line_labels=[0,1,2,3])\n", "print(c)\n", "print('c[1] = ',c[1]) # layer\n", "print('c[0,0] = ',c[0,0]) # gate at layer=0, line=0 (Gx)\n", "print('c[0,2] = ',c[0,2]) # gate at layer=0, line=2 (nothing)\n", "print('c[1,0] = ',c[1,0]) # gate at layer=0, line=0 (NOTE: nothing because CNOT doesn't *only* occupy line 0)\n", "print('c[1,(0,1)] = ',c[1,(0,1)]) # gate at layer=0, lines=0&1 (Gcnot)\n", "print('c[1,(0,1,3)] = ', c[1,(0,1,3)]) # layer-label restricted to lines 0,1,&3\n", "print('c[1,0:3] = ', c[1,0:3]) # layer-label restricted to lines 0,1,&2 (line-label slices OK b/c ints)\n", "print('c[3,0] = ',c[3,0])\n", "print('c[3,:] = ',c[3,:]) # DEBUG!\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If the first index is a tuple or slice of layer indices, a `Circuit` is returned which contains only the indexed layers. This indexing may be combined with the line-label indexing described above. Here are some examples:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print(c)\n", "print('c[1:3] = ');print(c[1:3]) # Circuit formed from layers 1 & 2 of original circuit\n", "print('c[2:3] = ');print(c[1:2]) # Layer 1 but as a circuit (not the same as c[1], which is a Label)\n", "print('c[0:2,(0,1)] = ');print(c[0:2,(0,1)]) # upper left \"box\" of circuit\n", "print('c[(0,3,4),(0,3)] = ');print(c[(0,3,4),(0,3)]) #Note: gates only partially in the selected \"box\" are omitted" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Editing Circuits\n", "**Circuits are by default created as read-only objects**. This is because making them read-only allows them to be hashed (e.g. used as the keys of a dictionary) and there are many tasks that don't require them being editable. That said, it's easy to get an editable `Circuit`: just create one or make a copy of one with `editable=True`:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ecircuit1 = Circuit( [('Gx',0),('Gcnot',0,1),(),'Gall',('Gy',3)], num_lines=4, editable=True)\n", "ecircuit2 = c.copy(editable=True)\n", "print(ecircuit1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "When a circuit is editable, you can perform additional operations that alter the circuit in place (see below). When you're done, call `.done_editing()` to change the `Circuit` into read-only mode. Once in read-only mode, a `Circuit` cannot be changed back into editable-mode, you must make an editable *copy* of the circuit. \n", "\n", "As you may have guessed, you're allowed to *assign* the layers or labels of an editable circuit by indexing:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ecircuit1[0,(2,3)] = ('Gcnot',2,3)\n", "print(ecircuit1)\n", "\n", "ecircuit1[2,1] = 'Gz' # interpreted as ('Gz',1) \n", "print(ecircuit1)\n", "\n", "ecircuit1[2:4] = [[('Gx',1),('Gcnot',3,2)],('Gy',1)] #assigns to layers 2 & 3\n", "print(ecircuit1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "There are also methods for inserting and removing lines and layers:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ecircuit1.append_circuit( Circuit([('Gx',0),'Gi'], num_lines=4) )\n", "print(ecircuit1)\n", "\n", "ecircuit1.insert_circuit( Circuit([('Gx',0),('Gx',1),('Gx',2),('Gx',3)], num_lines=4), 1)\n", "print(ecircuit1)\n", "\n", "ecircuit1.insert_layer( L( (L('Gz',0),L('Gz',3)) ), 0) #expects something like a *label*\n", "print(ecircuit1)\n", "\n", "ecircuit1.delete_layers([2,3])\n", "print(ecircuit1)\n", "\n", "ecircuit1.insert_idling_lines(2, ['N1','N2'])\n", "print(ecircuit1)\n", "\n", "ecircuit1.delete_lines(['N1','N2'])\n", "print(ecircuit1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Finally, there are more complex methods which do fancy things to `Circuit`s:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ecircuit1.compress_depth_inplace()\n", "print(ecircuit1)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ecircuit1.change_gate_library({('Gx',0) : [('Gx2',0)]}, allow_unchanged_gates=True)\n", "print(ecircuit1)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "ecircuit1.done_editing()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Circuits as tuples\n", "In many ways `Circuit` objects behave as a tuple of layer labels. We've already shown how indexing and slicing mimic this behavior. You can also add circuits together and multiply them by integers:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "c2 = Circuit([('Gx',0),('Gx',1),('Gx',2),('Gx',3)], num_lines=4)\n", "print(c)\n", "print(c+c2)\n", "print(c*2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "There are also methods to \"parallelize\" and \"serialize\" circuits, which are available to read-only circuits too because they return new `Circuit` objects and don't modify anything in place:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "c2 = Circuit([[('Gx',0),('Gx',1)],('Gx',2),('Gx',3)], num_lines=4)\n", "print(c2)\n", "print(c2.parallelize())\n", "print(c2.serialize())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### String representations\n", "`Circuit` objects carry along with them a string representation, accessible via the `.str` member. This is intended to hold a compact human-readable expression for the circuit that can be parsed, using pyGSTi's standard circuit format and conventions, to reconstruct the circuit. This isn't quite true because the line-labels are not currently contained in the string representation, but this will likely change in future releases.\n", "\n", "Here's how you can construct a `Circuit` with or from a string representation which thereafter illustrates how you can print different representation of a `Circuit`. Note that two `Circuits` may be equal even if their string representations are different." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#Construction of a Circuit\n", "c1 = Circuit( ('Gx','Gx') ) # from a tuple\n", "c2 = Circuit( ('Gx','Gx'), stringrep=\"Gx^2\" ) # from tuple and string representations (must match!)\n", "c3 = Circuit( \"Gx^2\" ) # from just a string representation\n", "\n", "#All of these are equivalent (even though their string representations aren't -- only tuples are compared)\n", "assert(c1 == c2 == c3)\n", "\n", "#Printing displays the Circuit representation\n", "print(\"Printing as string (multi-line string rep)\")\n", "print(\"c1 = %s\" % c1)\n", "print(\"c2 = %s\" % c2)\n", "print(\"c3 = %s\" % c3, end='\\n\\n')\n", "\n", "#Printing displays the Circuit representation\n", "print(\"Printing .str (single-line string rep)\")\n", "print(\"c1 = %s\" % c1.str)\n", "print(\"c2 = %s\" % c2.str)\n", "print(\"c3 = %s\" % c3.str, end='\\n\\n')\n", "\n", "#Casting to tuple displays the tuple representation\n", "print(\"Printing tuple(.) (tuple rep)\")\n", "print(\"c1 =\", tuple(c1))\n", "print(\"c2 =\", tuple(c2))\n", "print(\"c3 =\", tuple(c3), end='\\n\\n')\n", "\n", "#Operations\n", "assert(c1 == ('Gx','Gx')) #can compare with tuples\n", "c4 = c1+c2 #addition (note this concatenates string reps)\n", "c5 = c1*3 #integer-multplication (note this exponentiates in string rep)\n", "print(\"c1 + c2 = \",c4.str, \", tuple = \", tuple(c4))\n", "print(\"c1*3 = \",c5.str, \", tuple = \", tuple(c5), end='\\n\\n')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### File I/O\n", "Circuits can be saved to and read from their single-line string format, which uses square brackets to enclose each layer of the circuit. See the lines of [MyCircuits.txt](../tutorial_files/MyCircuits.txt), which we read in below, for examples. Note that a `Circuit`'s line labels are not included in their single-line-string format, and so to reliably import circuits the line labels should be supplied separately to the `load_circuit_list` function: " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "circuitList = pygsti.io.load_circuit_list(\"../tutorial_files/MyCircuits.txt\", line_labels=[0,1,2,3,4])\n", "for c in circuitList:\n", " print(c)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Converting circuits external formats\n", "\n", "`Circuit` objects can be easily converted to [OpenQasm](https://arxiv.org/abs/1707.03429) or [Quil](https://arxiv.org/pdf/1608.03355.pdf) strings, using the `convert_to_openqasm()` and `convert_to_quil()` methods. This conversion is automatic for circuits that containing only gates with name that are in-built into `pyGSTi` (the docstring of `pygsti.tools.internalgates.standard_gatename_unitaries()`). This is with some exceptions in the case of Quil: currently not all of the in-built gate names can be converted to quil gate names automatically, but this will be fixed in the future. \n", "\n", "For other gate names (or even more crucially, if you have re-purposed any of the gate names that `pyGSTi` knows for a different unitary), the desired gate name conversation must be specified as an optional argument for both `convert_to_openqasm()` and `convert_to_quil()`. \n", "\n", "Circuits with line labels that are *integers* or of the form 'Q*integer*' are auto-converted to the corresponding integer. If either of these labelling conventions is used but the mapping should be different, or if the qubit labelling in the circuit is not of one of these two forms, the mapping should be handed to these conversion methods." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "label_lst = [ [L('Gh','Q0'),L('Gh','Q1')], L('Gcphase',('Q0','Q1')), [L('Gh','Q0'),L('Gh','Q1')]]\n", "c = Circuit(label_lst, line_labels=['Q0','Q1'])\n", "\n", "print(c)\n", "openqasm = c.convert_to_openqasm()\n", "print(openqasm)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "label_lst = [L('Gxpi2','Q0'),L('Gcnot',('Q0','Q1')),L('Gypi2','Q1')]\n", "c2 = Circuit(label_lst, line_labels=['Q0','Q1'])\n", "quil = c2.convert_to_quil()\n", "print(quil)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Simulating circuits\n", "`Model` objects in pyGSTi are able to *simulate*, or \"generate the outcome probabilities for\", circuits. To demonstrate, let's create a circuit and a model (see the tutorials on [\"explicit\" models](ExplicitModel.ipynb) and [\"implicit\" models](ImplicitModel.ipynb) for more information on model creation):" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "clifford_circuit = Circuit([ [L('Gh','Q0'),L('Gh','Q1')],\n", " L('Gcphase',('Q0','Q1')),\n", " [L('Gh','Q0'),L('Gh','Q1')]],\n", " line_labels=['Q0','Q1'])\n", "model = pygsti.construction.create_localnoise_model(num_qubits=2, gate_names=('Gh','Gcphase'),\n", " qubit_labels=['Q0','Q1'])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Then circuit outcome probabilities can be computed using either the `model.probabilities(circuit)` or `circuit.simulate(model)`, whichever is more convenient: " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "out1 = model.probabilities(clifford_circuit)\n", "out2 = clifford_circuit.simulate(model)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The output is simply a dictionary of outcome probabilities:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "out1" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The keys of the outcome dictionary `out` are things like `('00',)` instead of just `'00'` because of possible *intermediate* outcomes. See the [Instruments tutorial](advanced/Instruments.ipynb) if you're interested in learning more about intermediate outcomes.\n", "\n", "Computation of outcome probabilities may be done in a variety of ways, and `Model` objects are associated with a *forward simulator* that supplies the core computational routines for generating outcome probabilities. In the example above the simulation was performed by multiplying together process matrices. For more information on the types of forward simulators in pyGSTi and how to use them, see the [forward simulators tutorial](../algorithms/advanced/ForwardSimulationTypes.ipynb)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Conclusion\n", "This concludes our detailed look into the `Circuit` object. If you're intersted in using circuits for specific applications, you might want to check out the [tutorial on circuit lists](advanced/CircuitLists.ipynb) or the [tutorial on constructing GST circuits](advanced/GSTCircuitConstruction.ipynb)" ] } ], "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.8.6" } }, "nbformat": 4, "nbformat_minor": 1 }