The ability prepare, propagate, and measure quantum states in simulations is a core capability of pyGSTi. We call the calculation of circuit outcome probabilities forward simulation, and this type of calculation lies at the heart of Gate Set Tomography and other characterization protocols which need to compare a model's predictions with actual data. As you're probably already encountered through other tutorials, Model
objects have a .probs(circuit)
method that performs forward simulation (computes the outcome probabilities of circuit
).
What is not so apparent is that there can be several different types of computational "engines" under the hood of a Model
that do the heavy lifting within a call to probs
. Understanding the different types of forward simulation engines in pyGSTi is the topic of this tutorial.
First, let's lay down a bit of background. A Model
contains a .evotype
attribute that describes what type of state object is being stored and propagated by the model - the evolution type. Allowed values are:
"densitymx"
: a mixed state is propagated as a vectorized density matrix (of length $4^n$ for $n$ qubits) in a Hermitian basis (so its elements are real). If an operation is represented as a dense (real) matrix, its shape is $4^n \times 4^n$. This is the default evolution type in pyGSTi."statevec"
: a pure state is propagated as a complex state vector (of length $2^n$ for $n$ qubits). Dense operations representations are $2^n \times 2^n$ unitary matrices in this case. Because of the lower dimensionality, state-vector propagation is faster than density-matrix propagation, and is therefore preferable when all preparations and POVM effects are pure states and projections, and all operations are unitary."stabilizer"
: a stabilizer state is propagated by keeping track of its stabilizer (and anti-stabilizer) group generators. This only requires memory that scales with $n$ for $n$ qubits, and so this state representation offers significantly more efficient simulation than the two aforementioned evolution types. The caveat is preparations must prepare a stabilizer state, POVMs must measure in the computational $z$-basis, and all operations must be Clifford elements, mapping Pauli group elements to Pauli group elements."svterm"
and "cterm"
: advanced topic to be added at a later time.Every operator (state preparation, POVM, gate/layer operation) within a Model
also has an evolution type, and it must match the parent Model
's .evotype
. (You usually don't need to worry about this; Model
objects are created with a given evotype
and their contained elements are created to match that type.)
Related to their evotype
, Model
objects also have a simtype
attribute. This attribute determines which forward simulation engine is used when the model is asked to compute circuit probabilities. There are again several different options, but each simtype
value is only compatible with certain evolution types (evotype
value):
"map"
: propagate a "densitymx"
, "statevec"
, "stabilizer"
state by repeatedly acting with circuit layer operators, treated as maps from an input to an output state. When there is a dense layer-operation matrix for each circuit layer, this engine repeatedly performs matrix-vector products (one per layer, between the operation matrix and column-vector state) and finally contracts the result with each row-vector POVM effect to get outcome probabilities. This is the most straightforward and intuitive of the forward simulation engines, so much so that you may be thinking "what else would you do?". Keep reading :)"matrix"
: propagate a "densitymx"
or "statevec"
state by composing a dense operation matrix for the entire circuit and then applying that "circuit map" to the input state. Each circuit layer must be representable as a dense matrix to use this forward simulation type, as it multiplies together the matrices of each layer (in reverse order!) to get a single "circuit matrix* and then contracts this matrix between the (column vector) state preparation and each of the (row vector) POVM effects (and norm-squaring the result when the evolution type is "statevec"
) to get outcome probabilities. At first glance this seems like a very inefficient way to compute probabilities since it performs matrix-matrix instead of matrix-vector multiplications, and it is true that this method should not be used with many-qubit models. However, for lower dimensional Hilbert spaces (1-2 qubits in practice) the ability to cache and reuse intermediate results can make this method faster than the "map"
method when the outcome probabilities of many circuits are needed at once (as is the case in Gate Set Tomography, for instance). Indeed, it was specifically for 1- and 2-qubit GST that this method was implemented. Apart from this case, "map"
should be used (thought using "matrix"
instead on 1 or 2 qubit systems often achieves similar run times because of other technical implementation factors)."termorder"
: advanced topic to be added at a later timeThus, but setting a model's evotype
and simtype
you can specify how circuit-probability-computation is implemented. But how do you set these values? The evotype
of a Model
is almost always set for good when the object is created. The simtype
can be changed using the set_simtype
method of a Model
, but it's usually set to an appropriate value at object-creation time and doesn't need to be altered. Here's how several of pyGSTi's model-construction routines allow you to set (or set for you) the evotype
and simtype
(note that this is often done indirectly, as setting the evotype
is largely done "under the hood", and the default behavior is usually what is desired):
When creating an empty ExplicitOpModel
, you can specify evotype
and sim_type
arguments.
pygsti.construction.build_explicit_model
currently always creates a model with the "densitymx"
evolution type. (It accepts a parameterization
argument, which may, in the future, be used to construct models with other evolution types.)
pygsti.construction.build_standard_localnoise_model
, LocalNoiseModel.build_standard
, and just LocalNoiseModel
have a parameterization
argument which is the easiest way to create models with various evolution types. Here is an incomplete list of valid parameterization
values and the corresponding evolution type of the created Model
:
"static"
, "full"
, "TP"
, "CPTP"
, "H+S"
$\rightarrow$ "densitymx"
"static unitary"
$\rightarrow$ "statevec"
"clifford"
$\rightarrow$ "stabilizer"
"H+S"
$\rightarrow$ "densitymx"
"H+S terms"
$\rightarrow$ "svterm"
"H+S clifford terms"
$\rightarrow$ "cterm"
There are also a separate "evotype"
argument, which should usually be left as "auto"
unless you want to override the default behavior of the parameterization
argument. The "sim_type"
argument can be used to override the default choice of forward simulation type/engine, which is usually unnecessary. The "matrix"
simulator is used only for $\le 2$ qubits, for instance.
pygsti.construction.build_standard_cloudnoise_model
, CloudNoiseModel.build_standard
, and just CloudNoiseModel
similarly have a parameterization
argument which determines the evolution type. Here are some allowed examples:"H+S"
$\rightarrow$ "densitymx"
"H+S terms"
$\rightarrow$ "svterm"
"H+S clifford terms"
$\rightarrow$ "cterm"
Similar to the local noisie model case, the "sim_type"
argument can be used to override the default choice of forward simulation type/engine, which is usually unnecessary.
Below, we'll demonstrate the use of different evolution and forward-simulation types using a local-noise model on 5 and then 10 qubits.
First, let's generate a random circuit using the Randomized Benchmarking (RB) sub-package pygsti.extras.rb
(just so we don't need to write down a circuit by hand). See the Randomized Benchmarking tutorial for more information about running RB.
import pygsti, time
import numpy as np
from pygsti.extras import rb
nQubits = 5
ps = pygsti.obj.ProcessorSpec(nQubits=nQubits, gate_names=['Gx','Gy','Gcnot'],
availability={'Gcnot': [(i,i+1) for i in range(nQubits-1)]})
c = rb.sample.random_circuit(ps, length=20)
print(c)
Qubit 0 ---|C1|-|Gy|-|C1|-|C1|-|C1|-|Gx|-|Gy|-|Gy|-|C1|-|C1|-|Gy|-|Gx|-|Gy|-|Gy|-|Gy|-|Gx|-|Gx|-|C1|-|Gx|-|Gx|--- Qubit 1 ---|T0|-|Gx|-|T0|-|T0|-|T0|-|C2|-|Gx|-|Gy|-|T0|-|T0|-|C2|-|C2|-|Gx|-|Gy|-|Gx|-|Gy|-|Gy|-|T0|-|C2|-|C2|--- Qubit 2 ---|Gy|-|C3|-|Gy|-|C3|-|Gy|-|T1|-|Gx|-|Gx|-|Gx|-|Gy|-|T1|-|T1|-|Gy|-|Gy|-|Gy|-|Gy|-|Gx|-|C3|-|T1|-|T1|--- Qubit 3 ---|Gy|-|T2|-|Gy|-|T2|-|Gx|-|Gx|-|C4|-|C4|-|C4|-|Gy|-|C4|-|Gy|-|C4|-|C4|-|C4|-|C4|-|C4|-|T2|-|C4|-|Gy|--- Qubit 4 ---|Gx|-|Gx|-|Gy|-|Gx|-|Gx|-|Gy|-|T3|-|T3|-|T3|-|Gy|-|T3|-|Gx|-|T3|-|T3|-|T3|-|T3|-|T3|-|Gx|-|T3|-|Gx|---
Propagate a density matrix ("densitymx"
evolution type) using the matrix-matrix multiplying forward simulator ("matrix"
):
mdl = pygsti.construction.build_localnoise_model(
nQubits, ['Gx','Gy','Gcnot'], sim_type="matrix")
print("Gx gate is a ",type(mdl.operation_blks['gates']['Gx']))
t0 = time.time()
out = mdl.probs(c)
print("%d probabilities computed in %.3fs" % (len(out), time.time()-t0))
Gx gate is a <class 'pygsti.objects.operation.StaticDenseOp'> 32 probabilities computed in 27.458s
We can also propagate a density matrix using the matrix-vector multiplying forward simulator ("map"
). This forward simulator is much faster for even several (5) qubits. This is why pyGSTi automatically selects the "map"
simulator when the number of qubits is $\ge 2$.
mdl = pygsti.construction.build_localnoise_model(
nQubits, ['Gx','Gy','Gcnot'], sim_type="map")
t0 = time.time()
out2 = mdl.probs(c)
print("%d probabilities computed in %.3fs" % (len(out2), time.time()-t0))
assert(all([np.isclose(out[k],out2[k]) for k in out])) # check that the probabilites are the same
32 probabilities computed in 0.022s
If we don't need to consider mixed states, we can represent the quantum state using a state vector ("statevec"
, selected by the "static unitary"
parameterization type) and use either the matrix-matrix or matrix-vector product simulation types (the latter is again considerably faster even for just 5 qubits):
mdl = pygsti.construction.build_localnoise_model(
nQubits, ['Gx','Gy','Gcnot'], parameterization="static unitary", sim_type="matrix")
t0 = time.time()
out3 = mdl.probs(c)
print("Mat-mat: %d probabilities in %.3fs" % (len(out3), time.time()-t0))
assert(all([np.isclose(out[k],out3[k]) for k in out])) # check that the probabilites are the same
mdl = pygsti.construction.build_localnoise_model(
nQubits, ['Gx','Gy','Gcnot'], parameterization="static unitary", sim_type="map")
t0 = time.time()
out4 = mdl.probs(c)
print("Mat-vec: %d probabilities in %.3fs" % (len(out4), time.time()-t0))
assert(all([np.isclose(out[k],out4[k]) for k in out])) # check that the probabilites are the same
Mat-mat: 32 probabilities in 0.258s Mat-vec: 32 probabilities in 0.018s
Finally, if all the gates are Clifford operations (as they are in this case), we can use the "clifford"
parameterization to propagate a "stabilizer"
state. Only the "map"
simulation type is compatible with the "stabilizer"
evolution type (selected automatically).
mdl = pygsti.construction.build_localnoise_model(
nQubits, ['Gx','Gy','Gcnot'], parameterization="clifford")
print("Gx gate is a ",type(mdl.operation_blks['gates']['Gx']))
t0 = time.time()
out5 = mdl.probs(c)
print("%d probabilities in %.3fs" % (len(out5), time.time()-t0))
assert(all([np.isclose(out[k],out5[k]) for k in out])) # check that the probabilites are the same
Gx gate is a <class 'pygsti.objects.operation.CliffordOp'> 32 probabilities in 0.020s
Let's create a function to compare the above methods for a given number of qubits. We'll automatically exclude the "densitymx"
-"matrix"
case when the number of qubits is greater than 5 as we know this is getting slow at this point. At 10 qubits, the stabilizer and state-vector simulations are of comparable runtime (though this is largely due to the fact that all the outcomes are always computed - see below).
import pygsti, time
def compare_calc_methods(nQubits):
print("---- Comparing times for %d qubits (%d outcomes) ----" % (nQubits,2**nQubits))
t0=time.time()
ps = pygsti.obj.ProcessorSpec(nQubits=nQubits, gate_names=['Gx','Gy','Gcnot'],
availability={'Gcnot': [(i,i+1) for i in range(nQubits-1)]})
print("Create processor spec: %.3fs" % (time.time()-t0))
from pygsti.extras import rb
c = rb.sample.random_circuit(ps, 20)
print("Random Circuit:")
print(c)
if nQubits <= 5:
mdl = pygsti.construction.build_localnoise_model(
nQubits, ['Gx','Gy','Gcnot'], sim_type="matrix")
t0 = time.time()
mdl.probs(c)
print("densitymx, matrix: %.3fs" % (time.time()-t0))
mdl = pygsti.construction.build_localnoise_model(
nQubits, ['Gx','Gy','Gcnot'], parameterization="static unitary", sim_type="matrix")
t0 = time.time()
mdl.probs(c)
print("statevec, matrix: %.3fs" % (time.time()-t0))
if nQubits <= 12:
mdl = pygsti.construction.build_localnoise_model(
nQubits, ['Gx','Gy','Gcnot'], sim_type="map")
t0 = time.time()
mdl.probs(c)
print("densitymx, map: %.3fs" % (time.time()-t0))
mdl = pygsti.construction.build_localnoise_model(
nQubits, ['Gx','Gy','Gcnot'], parameterization="static unitary", sim_type="map")
t0 = time.time()
mdl.probs(c)
print("statevec, map: %.3fs" % (time.time()-t0))
mdl = pygsti.construction.build_localnoise_model(
nQubits, ['Gx','Gy','Gcnot'], parameterization="clifford")
t0 = time.time()
out5 = mdl.probs(c)
print("stabilizer, map: %.3fs" % (time.time()-t0))
compare_calc_methods(10)
---- Comparing times for 10 qubits (1024 outcomes) ---- Create processor spec: 3.758s Random Circuit: Qubit 0 ---|C1|-|C1|-|C1|-|Gx|-|C1|-|Gx|-|C1|-|C1|-|Gx|-|C1|-|C1|-|C1|-|C1|-|C1|-|Gy|-|Gx|-|Gx|-|Gx|-|Gx|-|Gy|--- Qubit 1 ---|T0|-|T0|-|T0|-|Gx|-|T0|-|Gy|-|T0|-|T0|-|C2|-|T0|-|T0|-|T0|-|T0|-|T0|-|Gx|-|C2|-|C2|-|C2|-|C2|-|Gy|--- Qubit 2 ---|Gx|-|Gy|-|Gy|-|C3|-|C3|-|Gx|-|Gy|-|Gy|-|T1|-|C3|-|Gy|-|Gy|-|Gx|-|C3|-|Gy|-|T1|-|T1|-|T1|-|T1|-|C3|--- Qubit 3 ---|C4|-|Gy|-|C4|-|T2|-|T2|-|Gy|-|Gx|-|C4|-|C4|-|T2|-|Gy|-|Gy|-|C4|-|T2|-|C4|-|C4|-|Gx|-|Gy|-|C4|-|T2|--- Qubit 4 ---|T3|-|C5|-|T3|-|Gx|-|Gx|-|C5|-|Gx|-|T3|-|T3|-|Gx|-|Gx|-|C5|-|T3|-|Gy|-|T3|-|T3|-|Gx|-|C5|-|T3|-|C5|--- Qubit 5 ---|Gx|-|T4|-|Gy|-|C6|-|Gx|-|T4|-|C6|-|C6|-|C6|-|Gy|-|Gx|-|T4|-|C6|-|C6|-|C6|-|Gx|-|C6|-|T4|-|Gy|-|T4|--- Qubit 6 ---|Gx|-|C7|-|Gx|-|T5|-|C7|-|C7|-|T5|-|T5|-|T5|-|Gx|-|Gx|-|Gy|-|T5|-|T5|-|T5|-|Gy|-|T5|-|Gx|-|Gy|-|Gy|--- Qubit 7 ---|Gx|-|T6|-|C8|-|Gy|-|T6|-|T6|-|Gy|-|Gx|-|Gx|-|Gx|-|Gy|-|C8|-|C8|-|Gx|-|Gy|-|C8|-|C8|-|C8|-|Gx|-|Gy|--- Qubit 8 ---|Gx|-|Gy|-|T7|-|Gy|-|Gy|-|C9|-|Gy|-|C9|-|Gx|-|Gy|-|Gx|-|T7|-|T7|-|C9|-|Gx|-|T7|-|T7|-|T7|-|Gx|-|C9|--- Qubit 9 ---|Gy|-|Gx|-|Gy|-|Gy|-|Gx|-|T8|-|Gx|-|T8|-|Gy|-|Gy|-|Gx|-|Gx|-|Gx|-|T8|-|Gx|-|Gx|-|Gy|-|Gx|-|Gy|-|T8|--- densitymx, map: 2.991s statevec, map: 0.088s stabilizer, map: 0.087s
Going beyond 10 qubits, run times will start to get long even for the stabilizer-simulation case. This is because pyGSTi currently always computes all the outcomes of a circuit, the number of which scales exponentially with the system size (as $2^n$ for $n$ qubits). Future versions will remedy this technical issue, allowing you to compute just the outcome probabilites you want. Once this update is released, the stabilizer state simulation will clearly be faster than either the density-matrix or state-vector approaches; for now, we can see that it get's marginally faster as the number of qubits rises (note: this cell takes several minutes to run!):
compare_calc_methods(12)
---- Comparing times for 12 qubits (4096 outcomes) ---- Create processor spec: 5.571s Random Circuit: Qubit 0 ---|Gy|-|Gx |-|Gy|-|Gx |-|Gy|-|Gx |-|Gx |-|C1 |-|Gy |-|C1|-|C1 |-|C1 |-|Gy |-|Gx |-|Gy|-|Gx |-|Gx |-|Gy |-|Gx |-|C1 |--- Qubit 1 ---|Gy|-|C2 |-|Gx|-|C2 |-|Gy|-|Gx |-|Gx |-|T0 |-|Gy |-|T0|-|T0 |-|T0 |-|C2 |-|Gy |-|C2|-|C2 |-|C2 |-|Gx |-|Gy |-|T0 |--- Qubit 2 ---|Gx|-|T1 |-|Gy|-|T1 |-|Gy|-|C3 |-|Gy |-|Gy |-|Gy |-|Gy|-|Gx |-|Gy |-|T1 |-|Gx |-|T1|-|T1 |-|T1 |-|C3 |-|C3 |-|C3 |--- Qubit 3 ---|Gx|-|Gx |-|C4|-|C4 |-|C4|-|T2 |-|Gy |-|Gy |-|Gy |-|Gy|-|Gx |-|Gy |-|Gy |-|C4 |-|Gy|-|C4 |-|Gx |-|T2 |-|T2 |-|T2 |--- Qubit 4 ---|Gy|-|Gx |-|T3|-|T3 |-|T3|-|Gy |-|Gy |-|Gx |-|Gx |-|C5|-|Gx |-|Gy |-|Gy |-|T3 |-|C5|-|T3 |-|C5 |-|Gy |-|Gy |-|Gx |--- Qubit 5 ---|Gx|-|C6 |-|C6|-|Gx |-|C6|-|Gx |-|Gx |-|Gy |-|Gy |-|T4|-|C6 |-|Gy |-|Gx |-|C6 |-|T4|-|Gx |-|T4 |-|Gx |-|C6 |-|Gx |--- Qubit 6 ---|C7|-|T5 |-|T5|-|Gy |-|T5|-|Gy |-|C7 |-|Gy |-|Gx |-|Gx|-|T5 |-|Gx |-|Gy |-|T5 |-|C7|-|Gx |-|Gy |-|Gy |-|T5 |-|Gy |--- Qubit 7 ---|T6|-|Gy |-|C8|-|Gx |-|C8|-|C8 |-|T6 |-|Gy |-|Gy |-|C8|-|Gy |-|Gx |-|Gy |-|Gy |-|T6|-|C8 |-|C8 |-|C8 |-|C8 |-|Gx |--- Qubit 8 ---|Gy|-|C9 |-|T7|-|C9 |-|T7|-|T7 |-|Gx |-|Gy |-|C9 |-|T7|-|C9 |-|Gx |-|C9 |-|Gy |-|C9|-|T7 |-|T7 |-|T7 |-|T7 |-|C9 |--- Qubit 9 ---|Gx|-|T8 |-|Gy|-|T8 |-|Gx|-|C10|-|C10|-|C10|-|T8 |-|Gy|-|T8 |-|C10|-|T8 |-|C10|-|T8|-|C10|-|C10|-|Gx |-|C10|-|T8 |--- Qubit 10 ---|Gy|-|C11|-|Gy|-|C11|-|Gx|-|T9 |-|T9 |-|T9 |-|C11|-|Gy|-|C11|-|T9 |-|C11|-|T9 |-|Gy|-|T9 |-|T9 |-|C11|-|T9 |-|C11|--- Qubit 11 ---|Gx|-|T10|-|Gy|-|T10|-|Gy|-|Gy |-|Gy |-|Gx |-|T10|-|Gy|-|T10|-|Gy |-|T10|-|Gy |-|Gx|-|Gy |-|Gy |-|T10|-|Gx |-|T10|--- densitymx, map: 52.909s statevec, map: 0.288s stabilizer, map: 0.375s
compare_calc_methods(16)
---- Comparing times for 16 qubits (65536 outcomes) ---- Create processor spec: 14.371s Random Circuit: Qubit 0 ---|Gy |-|Gx |-|C1|-|Gx |-|Gy |-|Gx |-|Gy |-|C1 |-|Gx |-|C1 |-|C1 |-|C1 |-|C1 |-|C1 |-|Gy |-|Gy |-|Gx |-|Gx |-|Gx |-|Gx |--- Qubit 1 ---|C2 |-|Gy |-|T0|-|Gy |-|Gy |-|C2 |-|Gx |-|T0 |-|Gx |-|T0 |-|T0 |-|T0 |-|T0 |-|T0 |-|Gx |-|Gx |-|Gx |-|Gy |-|C2 |-|C2 |--- Qubit 2 ---|T1 |-|Gy |-|Gy|-|Gy |-|C3 |-|T1 |-|Gy |-|C3 |-|Gy |-|Gy |-|Gx |-|Gx |-|Gx |-|Gx |-|Gx |-|Gy |-|Gx |-|Gy |-|T1 |-|T1 |--- Qubit 3 ---|Gx |-|C4 |-|Gx|-|Gy |-|T2 |-|Gy |-|C4 |-|T2 |-|C4 |-|C4 |-|Gy |-|Gy |-|Gx |-|C4 |-|Gy |-|C4 |-|Gx |-|Gy |-|Gx |-|Gx |--- Qubit 4 ---|C5 |-|T3 |-|C5|-|Gx |-|Gy |-|Gx |-|T3 |-|Gx |-|T3 |-|T3 |-|C5 |-|Gy |-|Gy |-|T3 |-|Gx |-|T3 |-|Gx |-|Gy |-|C5 |-|Gy |--- Qubit 5 ---|T4 |-|C6 |-|T4|-|Gx |-|Gx |-|C6 |-|Gy |-|Gy |-|C6 |-|Gy |-|T4 |-|Gx |-|Gy |-|Gx |-|Gx |-|C6 |-|Gx |-|C6 |-|T4 |-|C6 |--- Qubit 6 ---|Gy |-|T5 |-|Gx|-|Gy |-|C7 |-|T5 |-|Gx |-|Gy |-|T5 |-|C7 |-|C7 |-|Gy |-|Gy |-|Gx |-|Gx |-|T5 |-|C7 |-|T5 |-|Gx |-|T5 |--- Qubit 7 ---|Gy |-|Gy |-|C8|-|C8 |-|T6 |-|Gx |-|Gx |-|C8 |-|C8 |-|T6 |-|T6 |-|Gx |-|C8 |-|Gy |-|Gy |-|C8 |-|T6 |-|Gy |-|Gy |-|C8 |--- Qubit 8 ---|Gx |-|Gx |-|T7|-|T7 |-|C9 |-|Gx |-|Gx |-|T7 |-|T7 |-|Gy |-|C9 |-|Gx |-|T7 |-|Gx |-|C9 |-|T7 |-|Gx |-|Gy |-|C9 |-|T7 |--- Qubit 9 ---|C10|-|C10|-|Gy|-|Gx |-|T8 |-|Gy |-|Gy |-|Gx |-|C10|-|C10|-|T8 |-|C10|-|C10|-|Gy |-|T8 |-|Gx |-|Gy |-|Gx |-|T8 |-|Gy |--- Qubit 10 ---|T9 |-|T9 |-|Gy|-|Gy |-|C11|-|C11|-|Gy |-|Gy |-|T9 |-|T9 |-|Gy |-|T9 |-|T9 |-|C11|-|C11|-|Gx |-|Gx |-|Gy |-|Gx |-|Gy |--- Qubit 11 ---|Gx |-|Gx |-|Gx|-|Gy |-|T10|-|T10|-|Gy |-|Gx |-|C12|-|Gx |-|Gy |-|Gx |-|Gy |-|T10|-|T10|-|Gy |-|Gx |-|Gx |-|Gx |-|C12|--- Qubit 12 ---|Gx |-|C13|-|Gx|-|Gy |-|C13|-|Gx |-|Gx |-|C13|-|T11|-|Gx |-|C13|-|Gx |-|C13|-|Gx |-|Gy |-|C13|-|Gx |-|C13|-|C13|-|T11|--- Qubit 13 ---|Gx |-|T12|-|Gy|-|Gx |-|T12|-|Gy |-|Gy |-|T12|-|C14|-|C14|-|T12|-|C14|-|T12|-|Gy |-|Gy |-|T12|-|C14|-|T12|-|T12|-|Gx |--- Qubit 14 ---|Gy |-|Gy |-|Gx|-|C15|-|Gy |-|Gx |-|C15|-|C15|-|T13|-|T13|-|Gx |-|T13|-|C15|-|Gx |-|Gy |-|Gy |-|T13|-|Gy |-|C15|-|C15|--- Qubit 15 ---|Gx |-|Gx |-|Gy|-|T14|-|Gy |-|Gy |-|T14|-|T14|-|Gx |-|Gy |-|Gy |-|Gy |-|T14|-|Gy |-|Gy |-|Gy |-|Gx |-|Gx |-|T14|-|T14|--- statevec, map: 5.114s stabilizer, map: 4.937s