Cirq Integration#

This notebook shows a simple example of how to use pyGSTi with Cirq. It has three sections:

  1. Sets up pyGSTi.

  2. Shows how pyGSTi circuits can be converted to Cirq circuits.

  3. Shows how Cirq circuits can be converted into pyGSTi circuits.

  4. Shows how the Cirq circuits can be run and the results loaded back into pyGSTi for analysis.

import cirq
import pygsti
from pygsti.modelpacks import smq1Q_XYI
from pygsti.circuits import Circuit
import numpy as np
import tqdm

1. Generate the GST circuits#

Make target gate set \(\{R_{X}(\pi/2), R_{Y}(\pi/2),I\}\)#

target_model = smq1Q_XYI.target_model()

Preparation and measurement fiducials, germs#

preps = smq1Q_XYI.prep_fiducials()
effects = smq1Q_XYI.meas_fiducials()
germs = smq1Q_XYI.germs()

Construct pyGSTi circuits#

max_lengths = list(np.logspace(0, 10, 11, base=2, dtype=int))
print(max_lengths)
pygsti_circuits = pygsti.circuits.gstcircuits.create_lsgst_circuits(target_model, preps, effects, germs, max_lengths)
len(pygsti_circuits)

2. Convert to runable cirq.Circuit’s#

Setup#

Now, we need to map the qubit names from pyGSTi (0, 1, etc.) into cirq qubits. There’s nothing special about cirq.GridQubit(8, 3); it’s just an example.

q0 = cirq.GridQubit(8, 3)
qubit_label_dict = {0: q0}

Testing examples#

Do an example conversion.

pygsti_circuit = pygsti_circuits[111]
print('pyGSTi:')
print(pygsti_circuit)
print('Cirq:')
print(pygsti_circuit.convert_to_cirq(qubit_label_dict))

Do another example conversion.

pygsti_circuit = pygsti_circuits[90]
print('pyGSTi:')
print(pygsti_circuit)
print('Cirq:')
print(pygsti_circuit.convert_to_cirq(qubit_label_dict))

Now, lets try the same thing but specifing a wait duration for the idle operation.

wait_duration = cirq.Duration(nanos=100)
pygsti_circuit = pygsti_circuits[111]
print('pyGSTi:')
print(pygsti_circuit)
print('Cirq:')
print(pygsti_circuit.convert_to_cirq(qubit_label_dict, wait_duration))
pygsti_circuit = pygsti_circuits[90]
print('pyGSTi:')
print(pygsti_circuit)
print('Cirq:')
print(pygsti_circuit.convert_to_cirq(qubit_label_dict, wait_duration))

The real thing#

Now, convert all the circuits.

cirq_circuits = [c.convert_to_cirq(qubit_label_dict, wait_duration) for c in tqdm.tqdm(pygsti_circuits)]
cirq_circuits

Note that we’re missing the measurments and the first circuit is empty (it’s should just be an idle). Otherwise, the results look good, and those things should be easy to fix.

3. Convert Cirq circuits to pyGSTi circuits#

We also have support for converting a cirq circuit to a pyGSTi circuit, which is demonstrated below. Begin by constructing a cirq circuit directly.

#create to cirq qubit objects
qubit_00 = cirq.GridQubit(0,0)
qubit_01 = cirq.GridQubit(0,1)
#define a series of Moment objects, which fill the same role as circuit layers in pyGSTi.
moment1 = cirq.Moment([cirq.XPowGate(exponent=.5).on(qubit_00), cirq.I(qubit_01)])
moment2 = cirq.Moment([cirq.I(qubit_00), cirq.I(qubit_01)])
#This weird looking gate is the so-called N gate.
moment3 = cirq.Moment([cirq.PhasedXZGate(axis_phase_exponent=0.14758361765043326, 
                                         x_exponent=0.4195693767448338, 
                                         z_exponent=-0.2951672353008665).on(qubit_00),
                    cirq.I(qubit_01)])
moment4 = cirq.Moment([cirq.H(qubit_00), (cirq.T**-1).on(qubit_01)])
moment5 = cirq.Moment([cirq.CNOT.on(qubit_00, qubit_01)])
cirq_circuit_example = cirq.Circuit([moment1, moment2, moment3, moment4, moment5])
print(cirq_circuit_example)

To convert this into a pyGSTi circuit we can use the from_cirq class method of the Circuit class.

converted_cirq_circuit_default = Circuit.from_cirq(cirq_circuit_example)
print(converted_cirq_circuit_default)

Above you can see the result of converting the circuit using the default conversion settings. The classmethod has multiple options for customizing the returned pyGSTi circuit.

  1. By default the method constructs a mapping between cirq qubit objects and pygsti qubit labels based on the type of cirq qubit provided. E.g. a GridQubit gets mapped to Q{row}_{col} where row and col are the corresponding attribute values for the GridQubit. Something similar is done for NamedQubit and LineQubit objects. This can be overridden by passing in a dictionary for the qubit_conversion kwarg.

converted_cirq_circuit_custom_qubit_map = Circuit.from_cirq(cirq_circuit_example, qubit_conversion={qubit_00: 'Qalice', qubit_01: 'Qbob'})
print(converted_cirq_circuit_custom_qubit_map)
  1. By default cirq included idle gates explicitly on all qubits in a layer without a specified operation applied. In pygsti we typically treat these as implied, and so the default behavior is to strip these extra idles. This can be turned off by setting remove_implied_idles to False.

converted_cirq_circuit_implied_idles = Circuit.from_cirq(cirq_circuit_example, remove_implied_idles=True)
print(converted_cirq_circuit_implied_idles)
  1. Layers consisting entirely of idle gates are by default converted to the default pyGSTi global idle convention or Label(()), or to a user specified replacement. This is controlled by the global_idle_replacement_label kwarg. The default value is the string ‘auto’, which will utilize the aforementioned default convention. Users can instead pass in either a string, which is converted to a corresponding Label object, or a circuit Label object directly. Finally, by passing in None the global idle replacement is not performed, and the full verbatim translation of that cirq layer is produced.

#auto is the default value, explicitly including here for comparison to alternative options.
converted_cirq_circuit_global_idle = Circuit.from_cirq(cirq_circuit_example, global_idle_replacement_label='auto')
print(converted_cirq_circuit_global_idle)
converted_cirq_circuit_global_idle_1 = Circuit.from_cirq(cirq_circuit_example, global_idle_replacement_label='Gbanana')
print(converted_cirq_circuit_global_idle_1)
from pygsti.baseobjs import Label
converted_cirq_circuit_global_idle_2 = Circuit.from_cirq(cirq_circuit_example, global_idle_replacement_label=Label('Gbanana', ('Q0_0','Q0_1')))
print(converted_cirq_circuit_global_idle_2)
converted_cirq_circuit_global_idle_3 = Circuit.from_cirq(cirq_circuit_example, global_idle_replacement_label= None)
print(converted_cirq_circuit_global_idle_3)
  1. There is built-in support for converting most Cirq gates into their corresponding built-in pyGSTi gate names (see cirq_gatenames_standard_conversions in pygsti.tools.internalgates for more on this). There is also a fallback behavior where if not found in the default map, the converter will search among the built-in gate unitaries for one that matches (up to a global phase). If this doesn’t work for a particular gate of user interest, of you simply want to override the default mapping as needed, this can be done by passing in a custom dictionary for the cirq_gate_conversion kwarg.

custom_gate_map = pygsti.tools.internalgates.cirq_gatenames_standard_conversions()
custom_gate_map[cirq.H] = 'Gdefinitelynoth'
converted_cirq_circuit_custom_gate_map = Circuit.from_cirq(cirq_circuit_example, cirq_gate_conversion=custom_gate_map)
print(converted_cirq_circuit_custom_gate_map)

4. Run the circuits#

Add measurements to the circuits.

for circuit in cirq_circuits:
    circuit.append(cirq.measure(q0, key='result'))

Simulate the circuits (or run them on a real quantum computer!)

simulator = cirq.Simulator()
results = [simulator.run(circuit, repetitions=1000) for circuit in tqdm.tqdm(cirq_circuits)]

Load everything the results into a pyGSTi dataset.

dataset = pygsti.data.dataset.DataSet()
for pygsti_circuit, trial_result in zip(pygsti_circuits, results):
    dataset.add_cirq_trial_result(pygsti_circuit, trial_result, key='result')

Perform GST.

gst_results = pygsti.run_stdpractice_gst(dataset, target_model, preps, effects, germs, max_lengths, modes=["full TP","Target"], verbosity=1)

See what if finds.

mdl_estimate = gst_results.estimates['full TP'].models['stdgaugeopt']
print("2DeltaLogL(estimate, data): ", pygsti.tools.two_delta_logl(mdl_estimate, dataset))
print("2DeltaLogL(ideal, data): ", pygsti.tools.two_delta_logl(target_model, dataset))