<jupyter_start><jupyter_text>Circuit SimulationThis tutorial demonstrates how to compute (simulate) the outcome probabilities of circuits in pyGSTi.  There are currently two basic ways to to this - but constructing and simulating a `Circuit` object, or by constructing and propagating a state. Method 1: `Circuit` simulationThis is the primary way circuit simulation is done in pyGSTi.  `Model` objects are statistical models that predict the outcome probabilities of events, and (at least for all current model types) "events" are circuits, described by `Circuit` objects.  Thus, the three steps to simulating a circuit using this approach are:1. create a `Model`2. create a `Circuit`3. call `model.probs(circuit)`Building models and circuits (steps 1 and 2) are largely covered in other tutorials (see the [essential objects tutorial](../01-EssentialObjects.ipynb), [circuits tutorial](../objects/Circuit.ipynb), and [explicit-op model](../objects/ExplicitModel.ipynb) and [implicit-op model](../objects/ImplicitModel.ipynb) tutorials).  This section focuses on step 3 and `Model` options which impact the way in which a model computes probabilities.  This approach to circuit simulation is most convenient when you have a large number of circuits which are known (and fixed) beforehand.Let's begin with a simple example, essentially the same as the one in the [using-essential-objects tutorial](../02-Using-Essential-Objects.ipynb):<jupyter_code>import pygsti
mdl = pygsti.construction.build_explicit_model((0,1),
            [(),      ('Gxpi2',0),    ('Gypi2',0),    ('Gxpi2',1),    ('Gypi2',1),    ('Gcnot',0,1)],
            ["I(0,1)","X(pi/2,0)", "Y(pi/2,0)", "X(pi/2,1)", "Y(pi/2,1)", "CNOT(0,1)"]) 
c = pygsti.objects.Circuit([('Gxpi2',0),('Gcnot',0,1),('Gypi2',1)] , line_labels=[0,1])
print(c)
mdl.probs(c) # Compute the outcome probabilities of circuit `c`<jupyter_output><empty_output><jupyter_text>This example builds an `ExplicitOpModel` (best for 1-2 qubits) on 2 qubits with $X(\pi/2)$ and $Y(\pi/2)$ rotation gates on each qubit and a CNOT gate between them.  This model is able to simulate any circuit *layer* (a.k.a. "time-step" or "clock-cycle") that contains any *one* of these gates (this is what it means to be an explicit-op model: the operation for every simulate-able circuit layer must be explicitly supplied to the `Model`).  For example, this model cannot simulate a circuit layer where two `Gxpi2` gates occur in parallel:<jupyter_code>c2 = pygsti.objects.Circuit([ [('Gxpi2',0), ('Gxpi2',1)],('Gcnot',0,1) ] , line_labels=[0,1])
print(c2)
try:
    mdl.probs(c2)
except KeyError as e:
    print("KEY ERROR (can't simulate this layer): " + str(e))<jupyter_output><empty_output><jupyter_text>As is detailed in the [implicit-op model tutorial](../objects/ImplicitModel.ipynb), an "implicit-operation" model *is* able to implicitly create layer operations from constituent gates, and thus perform the simulation of `c2`:<jupyter_code>implicit_mdl = pygsti.construction.build_localnoise_model(2, ('Gxpi2', 'Gypi2', 'Gcnot'))
print(c2)
implicit_mdl.probs(c2)<jupyter_output><empty_output><jupyter_text>Method 2: state propagationIn this method of circuit simulation, a state object (a `SPAMVec` in pyGSTi) is propagated circuit-layer by circuit-layer.  This method is convenient when a there are few (or just one!) circuit that involves substantial classical logic or needs to be probed at various points in time.  It is slower to simulate circuits in this way, as it requires calls more calls between pyGSTi's Python and C routines than method 1 does.The two cells below show how to perform the same two circuits above using the state-propagation method.<jupyter_code>#Simulating circuit `c` above using `mdl`: [('Gxpi2',0),('Gcnot',0,1),('Gypi2',1)]
rho = mdl['rho0']
rho = mdl[('Gxpi2',0)].acton(rho)
rho = mdl[('Gcnot',0,1)].acton(rho)
rho = mdl[('Gypi2',1)].acton(rho)
probs = mdl['Mdefault'].acton(rho)
print(probs)<jupyter_output><empty_output><jupyter_text>Note that, especially for implicit models, the interface is a bit clunky.  Simulation by state propagation is a work in progress in pyGSTi, and users should expect that this interface may change (improve!) in the future.<jupyter_code>#Simulating circuit `c2` above using `implicit_mdl`: [ [('Gxpi2',0), ('Gxpi2',1)], ('Gcnot',0,1) ]
from pygsti.objects import Label as L
liz = implicit_mdl._layer_lizard()
rho = liz.get_prep( L('rho0') )
rho = liz.get_operation( L((('Gxpi2',0),('Gxpi2',1))) ).acton(rho)
rho = liz.get_operation( L('Gcnot',(0,1)) ).acton(rho)
probs = liz.povm_blks['layers']['Mdefault'].acton(rho)
print(probs)<jupyter_output><empty_output><jupyter_text>Method 3: hybrid(an addition planned in future releases of pyGSTi)  Forward-simulation typesPyGSTi refers to the process of computing circuit-outcome probabilities as *forward simulation*, and there are several methods of forward simulation currently available.  The default method for 1- and 2-qubit models multiplies together dense process matrices, and is named `"matrix"` (because operations are *matrices*). The default method for 3+ qubit models performs sparse matrix-vector products, and is named `"map"` (because operations are abstract *maps*).  A `Model` is constructed for a single type of forward simulation, and it stores this within its `.simtype` member.  For more information on using different types of forward simulation see the [forward simulation types tutorial](algorithms/advanced/ForwardSimulationTypes.ipynb).Here are some examples showing which method is being used and how to switch between them.  Usually you don't need to worry about the forward-simulation type, but in the future pyGSTi may have more options for specialized purposes.<jupyter_code>c3 = pygsti.objects.Circuit([('Gxpi2',0),('Gcnot',0,1)] , line_labels=[0,1])

explicit_mdl = pygsti.construction.build_explicit_model((0,1),
            [(),      ('Gxpi2',0),    ('Gypi2',0),    ('Gxpi2',1),    ('Gypi2',1),    ('Gcnot',0,1)],
            ["I(0,1)","X(pi/2,0)", "Y(pi/2,0)", "X(pi/2,1)", "Y(pi/2,1)", "CNOT(0,1)"]) 

print("2Q explicit_mdl will simulate probabilities using the '%s' forward-simulation method." % explicit_mdl.simtype)
explicit_mdl.probs(c3)
implicit_mdl = pygsti.construction.build_localnoise_model(3, ('Gxpi2', 'Gypi2', 'Gcnot'))
print("3Q implicit_mdl will simulate probabilities using the '%s' forward-simulation method." % implicit_mdl.simtype)
implicit_mdl.probs(c)
implicit_mdl.set_simtype('matrix')
print("3Q implicit_mdl will simulate probabilities using the '%s' forward-simulation method." % implicit_mdl.simtype)
implicit_mdl.probs(c)<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.1254701369038664, 'fraction_numerical': 0.020610801865503233, 'mean_word_length': 5.654654654654655, 'pattern_counts': {'":': 0, '<': 25, '<?xml version=': 0, '>': 25, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '6', 'dir': 'jupyter-structured-clean-dedup', 'id': '14300', 'license': 'Apache-2.0', 'n_tokens_mistral': 2223, 'n_tokens_neox': 2105, 'n_words': 717, 'path': 'jupyter_notebooks/Tutorials/algorithms/CircuitSimulation.ipynb', 'repo_name': 'lnmaurer/pyGSTi', 'size': '6647'}