def single_qubit_state_tomography(sampler: work.Sampler, qubit: devices.GridQubit, circuit: circuits.Circuit, repetitions: int = 1000) -> TomographyResult: """Single-qubit state tomography. The density matrix of the output state of a circuit is measured by first doing projective measurements in the z-basis, which determine the diagonal elements of the matrix. A X/2 or Y/2 rotation is then added before the z-basis measurement, which determines the imaginary and real parts of the off-diagonal matrix elements, respectively. See Vandersypen and Chuang, Rev. Mod. Phys. 76, 1037 for details. Args: sampler: The quantum engine or simulator to run the circuits. qubit: The qubit under test. circuit: The circuit to execute on the qubit before tomography. repetitions: The number of measurements for each basis rotation. Returns: A TomographyResult object that stores and plots the density matrix. """ circuit_z = circuit + circuits.Circuit.from_ops(ops.measure(qubit, key='z')) results = sampler.run(circuit_z, repetitions=repetitions) rho_11 = np.mean(results.measurements['z']) rho_00 = 1.0 - rho_11 circuit_x = circuits.Circuit.from_ops(circuit, ops.X(qubit)**0.5, ops.measure(qubit, key='z')) results = sampler.run(circuit_x, repetitions=repetitions) rho_01_im = np.mean(results.measurements['z']) - 0.5 circuit_y = circuits.Circuit.from_ops(circuit, ops.Y(qubit)**-0.5, ops.measure(qubit, key='z')) results = sampler.run(circuit_y, repetitions=repetitions) rho_01_re = 0.5 - np.mean(results.measurements['z']) rho_01 = rho_01_re + 1j * rho_01_im rho_10 = np.conj(rho_01) rho = np.array([[rho_00, rho_01], [rho_10, rho_11]]) return TomographyResult(rho)
def two_qubit_randomized_benchmarking( sampler: work.Sampler, first_qubit: devices.GridQubit, second_qubit: devices.GridQubit, *, num_clifford_range: Sequence[int] = range(5, 50, 5), num_circuits: int = 20, repetitions: int = 1000) -> RandomizedBenchMarkResult: """Clifford-based randomized benchmarking (RB) of two qubits. A total of num_circuits random circuits are generated, each of which contains a fixed number of two-qubit Clifford gates plus one additional Clifford that inverts the whole sequence and a measurement in the z-basis. Each circuit is repeated a number of times and the average |00> state population is determined from the measurement outcomes of all of the circuits. The above process is done for different circuit lengths specified by the integers in num_clifford_range. For example, an integer 10 means the random circuits will contain 10 Clifford gates each plus one inverting Clifford. The user may use the result to extract an average gate fidelity, by analyzing the change in the average |00> state population at different circuit lengths. For actual experiments, one should choose num_clifford_range such that a clear exponential decay is observed in the results. The two-qubit Cliffords here are decomposed into CZ gates plus single-qubit x and y rotations. See Barends et al., Nature 508, 500 for details. Args: sampler: The quantum engine or simulator to run the circuits. first_qubit: The first qubit under test. second_qubit: The second qubit under test. num_clifford_range: The different numbers of Cliffords in the RB study. num_circuits: The number of random circuits generated for each number of Cliffords. repetitions: The number of repetitions of each circuit. Returns: A RandomizedBenchMarkResult object that stores and plots the result. """ cliffords = _single_qubit_cliffords() cfd_matrices = _two_qubit_clifford_matrices(first_qubit, second_qubit, cliffords) gnd_probs = [] for num_cfds in num_clifford_range: gnd_probs_l = [] for _ in range(num_circuits): circuit = _random_two_q_clifford(first_qubit, second_qubit, num_cfds, cfd_matrices, cliffords) circuit.append(ops.measure(first_qubit, second_qubit, key='z')) results = sampler.run(circuit, repetitions=repetitions) gnds = [(not r[0] and not r[1]) for r in results.measurements['z']] gnd_probs_l.append(np.mean(gnds)) gnd_probs.append(float(np.mean(gnd_probs_l))) return RandomizedBenchMarkResult(num_clifford_range, gnd_probs)
def single_qubit_randomized_benchmarking( sampler: work.Sampler, qubit: devices.GridQubit, use_xy_basis: bool = True, *, num_clifford_range: Sequence[int] = range(10, 100, 10), num_circuits: int = 20, repetitions: int = 1000) -> RandomizedBenchMarkResult: """Clifford-based randomized benchmarking (RB) of a single qubit. A total of num_circuits random circuits are generated, each of which contains a fixed number of single-qubit Clifford gates plus one additional Clifford that inverts the whole sequence and a measurement in the z-basis. Each circuit is repeated a number of times and the average |0> state population is determined from the measurement outcomes of all of the circuits. The above process is done for different circuit lengths specified by the integers in num_clifford_range. For example, an integer 10 means the random circuits will contain 10 Clifford gates each plus one inverting Clifford. The user may use the result to extract an average gate fidelity, by analyzing the change in the average |0> state population at different circuit lengths. For actual experiments, one should choose num_clifford_range such that a clear exponential decay is observed in the results. See Barends et al., Nature 508, 500 for details. Args: sampler: The quantum engine or simulator to run the circuits. qubit: The qubit under test. use_xy_basis: Determines if the Clifford gates are built with x and y rotations (True) or x and z rotations (False). num_clifford_range: The different numbers of Cliffords in the RB study. num_circuits: The number of random circuits generated for each number of Cliffords. repetitions: The number of repetitions of each circuit. Returns: A RandomizedBenchMarkResult object that stores and plots the result. """ cliffords = _single_qubit_cliffords() c1 = cliffords.c1_in_xy if use_xy_basis else cliffords.c1_in_xz cfd_mats = np.array([_gate_seq_to_mats(gates) for gates in c1]) gnd_probs = [] for num_cfds in num_clifford_range: excited_probs_l = [] for _ in range(num_circuits): circuit = _random_single_q_clifford(qubit, num_cfds, c1, cfd_mats) circuit.append(ops.measure(qubit, key='z')) results = sampler.run(circuit, repetitions=repetitions) excited_probs_l.append(np.mean(results.measurements['z'])) gnd_probs.append(1.0 - np.mean(excited_probs_l)) return RandomizedBenchMarkResult(num_clifford_range, gnd_probs)
def t1_decay(sampler: work.Sampler, *, qubit: devices.GridQubit, num_points: int, max_delay: 'cirq.DURATION_LIKE', min_delay: 'cirq.DURATION_LIKE' = None, repetitions: int = 1000) -> 'cirq.experiments.T1DecayResult': """Runs a t1 decay experiment. Initializes a qubit into the |1⟩ state, waits for a variable amount of time, and measures the qubit. Plots how often the |1⟩ state is observed for each amount of waiting. Args: sampler: The quantum engine or simulator to run the circuits. qubit: The qubit under test. num_points: The number of evenly spaced delays to test. max_delay: The largest delay to test. min_delay: The smallest delay to test. Defaults to no delay. repetitions: The number of repetitions of the circuit for each delay. Returns: A T1DecayResult object that stores and can plot the data. """ min_delay_dur = value.Duration(min_delay) max_delay_dur = value.Duration(max_delay) if repetitions <= 0: raise ValueError('repetitions <= 0') if max_delay_dur < min_delay_dur: raise ValueError('max_delay < min_delay') if min_delay_dur < 0: raise ValueError('min_delay < 0') var = sympy.Symbol('delay_ns') sweep = study.Linspace(var, start=min_delay_dur.total_nanos(), stop=max_delay_dur.total_nanos(), length=num_points) circuit = circuits.Circuit( ops.X(qubit), ops.WaitGate(value.Duration(nanos=var)).on(qubit), ops.measure(qubit, key='output'), ) results = sampler.sample(circuit, params=sweep, repetitions=repetitions) # Cross tabulate into a delay_ns, false_count, true_count table. tab = pd.crosstab(results.delay_ns, results.output) tab.rename_axis(None, axis="columns", inplace=True) tab = tab.rename(columns={0: 'false_count', 1: 'true_count'}).reset_index() for col_index, name in [(1, 'false_count'), (2, 'true_count')]: if name not in tab: tab.insert(col_index, name, [0] * tab.shape[0]) return T1DecayResult(tab)
def _measure_prob_distribution( sampler: work.Sampler, repetitions: int, qubits: Sequence[ops.Qid], circuit_list: List[circuits.Circuit]) -> List[np.ndarray]: all_probs = [] # type: List[np.ndarray] num_states = 2**len(qubits) for circuit in circuit_list: trial_circuit = circuit.copy() trial_circuit.append(ops.measure(*qubits, key='z')) res = sampler.run(trial_circuit, repetitions=repetitions) res_hist = dict(res.histogram(key='z')) probs = np.zeros(num_states, dtype=float) for k, v in res_hist.items(): probs[k] = float(v) / float(repetitions) all_probs.append(probs) return all_probs
def rabi_oscillations(sampler: work.Sampler, qubit: devices.GridQubit, max_angle: float = 2 * np.pi, *, repetitions: int = 1000, num_points: int = 200) -> RabiResult: """Runs a Rabi oscillation experiment. Rotates a qubit around the x-axis of the Bloch sphere by a sequence of Rabi angles evenly spaced between 0 and max_angle. For each rotation, repeat the circuit a number of times and measure the average probability of the qubit being in the |1> state. Args: sampler: The quantum engine or simulator to run the circuits. qubit: The qubit under test. max_angle: The final Rabi angle in radians. repetitions: The number of repetitions of the circuit for each Rabi angle. num_points: The number of Rabi angles. Returns: A RabiResult object that stores and plots the result. """ theta = sympy.Symbol('theta') circuit = circuits.Circuit(ops.X(qubit)**theta) circuit.append(ops.measure(qubit, key='z')) sweep = study.Linspace(key='theta', start=0.0, stop=max_angle / np.pi, length=num_points) results = sampler.run_sweep(circuit, params=sweep, repetitions=repetitions) angles = np.linspace(0.0, max_angle, num_points) excited_state_probs = np.zeros(num_points) for i in range(num_points): excited_state_probs[i] = np.mean(results[i].measurements['z']) return RabiResult(angles, excited_state_probs)
def t2_decay( sampler: work.Sampler, *, qubit: devices.GridQubit, experiment_type: 'ExperimentType' = ExperimentType.RAMSEY, num_points: int, max_delay: 'cirq.DURATION_LIKE', min_delay: 'cirq.DURATION_LIKE' = None, repetitions: int = 1000, delay_sweep: Optional[study.Sweep] = None, ) -> 'cirq.experiments.T2DecayResult': """Runs a t2 transverse relaxation experiment. Initializes a qubit into a superposition state, evolves the system using rules determined by the experiment type and by the delay parameters, then rotates back for measurement. This will measure the phase decoherence decay. This experiment has three types of T2 metrics, each which measure a different slice of the noise spectrum. For the Ramsey experiment type (often denoted T2*), the state will be prepared with a square root Y gate (`cirq.Y ** 0.5`) and then waits for a variable amount of time. After this time, it will do basic state tomography to measure the expectation of the Pauli-X and Pauli-Y operators by performing either a `cirq.Y ** -0.5` or `cirq.X ** -0.5`. The square of these two measurements is summed to determine the length of the Bloch vector. This experiment measures the phase decoherence of the system under free evolution. For the Hahn echo experiment (often denoted T2 or spin echo), the state will also be prepared with a square root Y gate (`cirq.Y ** 0.5`). However, during the mid-point of the delay time being measured, a pi-pulse (`cirq.X`) gate will be applied to cancel out inhomogeneous dephasing. The same method of measuring the final state as Ramsey experiment is applied after the second half of the delay period. CPMG, or the Carr-Purcell-Meiboom-Gill sequence, is currently not implemented. Args: sampler: The quantum engine or simulator to run the circuits. qubit: The qubit under test. experiment_type: The type of T2 test to run. num_points: The number of evenly spaced delays to test. max_delay: The largest delay to test. min_delay: The smallest delay to test. Defaults to no delay. repetitions: The number of repetitions of the circuit for each delay and for each tomography result. delay_sweep: Optional range of time delays to sweep across. This should be a SingleSweep using the 'delay_ns' with values in integer number of nanoseconds. If specified, this will override the max_delay and min_delay parameters. If not specified, the experiment will sweep from min_delay to max_delay with linear steps. Returns: A T2DecayResult object that stores and can plot the data. """ min_delay_dur = value.Duration(min_delay) max_delay_dur = value.Duration(max_delay) # Input validation if repetitions <= 0: raise ValueError('repetitions <= 0') if max_delay_dur < min_delay_dur: raise ValueError('max_delay < min_delay') if min_delay_dur < 0: raise ValueError('min_delay < 0') # Initialize values used in sweeps delay_var = sympy.Symbol('delay_ns') inv_x_var = sympy.Symbol('inv_x') inv_y_var = sympy.Symbol('inv_y') if not delay_sweep: delay_sweep = study.Linspace(delay_var, start=min_delay_dur.total_nanos(), stop=max_delay_dur.total_nanos(), length=num_points) if delay_sweep.keys != ['delay_ns']: raise ValueError('delay_sweep must be a SingleSweep ' 'with delay_ns parameter') if experiment_type == ExperimentType.RAMSEY: # Ramsey T2* experiment # Use sqrt(Y) to flip to the equator. # Evolve the state for a given amount of delay time # Then measure the state in both X and Y bases. circuit = circuits.Circuit( ops.Y(qubit)**0.5, ops.WaitGate(value.Duration(nanos=delay_var))(qubit), ops.X(qubit)**inv_x_var, ops.Y(qubit)**inv_y_var, ops.measure(qubit, key='output'), ) tomography_sweep = study.Zip( study.Points('inv_x', [0.0, -0.5]), study.Points('inv_y', [-0.5, 0.0]), ) sweep = study.Product(delay_sweep, tomography_sweep) elif experiment_type == ExperimentType.HAHN_ECHO: # Hahn / Spin Echo T2 experiment # Use sqrt(Y) to flip to the equator. # Evolve the state for half the given amount of delay time # Flip the state using an X gate # Evolve the state for half the given amount of delay time # Then measure the state in both X and Y bases. circuit = circuits.Circuit( ops.Y(qubit)**0.5, ops.WaitGate(value.Duration(nanos=0.5 * delay_var))(qubit), ops.X(qubit), ops.WaitGate(value.Duration(nanos=0.5 * delay_var))(qubit), ops.X(qubit)**inv_x_var, ops.Y(qubit)**inv_y_var, ops.measure(qubit, key='output'), ) tomography_sweep = study.Zip( study.Points('inv_x', [0.0, 0.5]), study.Points('inv_y', [-0.5, 0.0]), ) sweep = study.Product(delay_sweep, tomography_sweep) else: raise ValueError(f'Experiment type {experiment_type} not supported') # Tabulate measurements into a histogram results = sampler.sample(circuit, params=sweep, repetitions=repetitions) y_basis_measurements = results[abs(results.inv_y) > 0] x_basis_measurements = results[abs(results.inv_x) > 0] x_basis_tabulation = pd.crosstab( x_basis_measurements.delay_ns, x_basis_measurements.output).reset_index() y_basis_tabulation = pd.crosstab( y_basis_measurements.delay_ns, y_basis_measurements.output).reset_index() # If all measurements are 1 or 0, fill in the missing column with all zeros. for tab in [x_basis_tabulation, y_basis_tabulation]: for col_index, name in [(1, 0), (2, 1)]: if name not in tab: tab.insert(col_index, name, [0] * tab.shape[0]) # Return the results in a container object return T2DecayResult(x_basis_tabulation, y_basis_tabulation)
def t2_decay( sampler: work.Sampler, *, qubit: devices.GridQubit, experiment_type: 'ExperimentType' = ExperimentType.RAMSEY, num_points: int, max_delay: 'cirq.DURATION_LIKE', min_delay: 'cirq.DURATION_LIKE' = None, repetitions: int = 1000, delay_sweep: Optional[study.Sweep] = None, num_pulses: List[int] = None ) -> Union['cirq.experiments.T2DecayResult', List['cirq.experiments.T2DecayResult']]: """Runs a t2 transverse relaxation experiment. Initializes a qubit into a superposition state, evolves the system using rules determined by the experiment type and by the delay parameters, then rotates back for measurement. This will measure the phase decoherence decay. This experiment has three types of T2 metrics, each which measure a different slice of the noise spectrum. For the Ramsey experiment type (often denoted T2*), the state will be prepared with a square root Y gate (`cirq.Y ** 0.5`) and then waits for a variable amount of time. After this time, it will do basic state tomography to measure the expectation of the Pauli-X and Pauli-Y operators by performing either a `cirq.Y ** -0.5` or `cirq.X ** 0.5`. The square of these two measurements is summed to determine the length of the Bloch vector. This experiment measures the phase decoherence of the system under free evolution. For the Hahn echo experiment (often denoted T2 or spin echo), the state will also be prepared with a square root Y gate (`cirq.Y ** 0.5`). However, during the mid-point of the delay time being measured, a pi-pulse (`cirq.X`) gate will be applied to cancel out inhomogeneous dephasing. The same method of measuring the final state as Ramsey experiment is applied after the second half of the delay period. See the animation on the wiki page https://en.wikipedia.org/wiki/Spin_echo for a visual illustration of this experiment. CPMG, or the Carr-Purcell-Meiboom-Gill sequence, involves using a sqrt(Y) followed by a sequence of pi pulses (X gates) in a specific timing pattern: π/2, t, π, 2t, π, ... 2t, π, t The first pulse, a sqrt(Y) gate, will put the qubit's state on the Bloch equator. After a delay, successive X gates will refocus dehomogenous phase effects by causing them to precess in opposite directions and averaging their effects across the entire pulse train. This pulse pattern has two variables that can be adjusted. The first, denoted as 't' in the above sequence, is delay, which can be specified with `delay_min` and `delay_max` or by using a `delay_sweep`, similar to the other experiments. The second variable is the number of pi pulses (X gates). This can be specified as a list of integers using the `num_pulses` parameter. If multiple different pulses are specified, the data will be presented in a data frame with two indices (delay_ns and num_pulses). See the following reference for more information about CPMG pulse trains: Meiboom, S., and D. Gill, “Modified spin-echo method for measuring nuclear relaxation times”, Rev. Sci. Inst., 29, 688–691 (1958). https://doi.org/10.1063/1.1716296 Note that interpreting T2 data is fairly tricky and subtle, as it can include other effects that need to be accounted for. For instance, amplitude damping (T1) will present as T2 noise and needs to be appropriately compensated for to find a true measure of T2. Due to this subtlety and lack of standard way to interpret the data, the fitting of the data to an exponential curve and the extrapolation of an actual T2 time value is left as an exercise to the reader. Args: sampler: The quantum engine or simulator to run the circuits. qubit: The qubit under test. experiment_type: The type of T2 test to run. num_points: The number of evenly spaced delays to test. max_delay: The largest delay to test. min_delay: The smallest delay to test. Defaults to no delay. repetitions: The number of repetitions of the circuit for each delay and for each tomography result. delay_sweep: Optional range of time delays to sweep across. This should be a SingleSweep using the 'delay_ns' with values in integer number of nanoseconds. If specified, this will override the max_delay and min_delay parameters. If not specified, the experiment will sweep from min_delay to max_delay with linear steps. num_pulses: For CPMG, a list of the number of pulses to use. If multiple pulses are specified, each will be swept on. Returns: A T2DecayResult object that stores and can plot the data. """ min_delay_dur = value.Duration(min_delay) max_delay_dur = value.Duration(max_delay) # Input validation if repetitions <= 0: raise ValueError('repetitions <= 0') if max_delay_dur < min_delay_dur: raise ValueError('max_delay < min_delay') if min_delay_dur < 0: raise ValueError('min_delay < 0') if num_pulses and experiment_type != ExperimentType.CPMG: raise ValueError('num_pulses is only valid for CPMG experiments.') # Initialize values used in sweeps delay_var = sympy.Symbol('delay_ns') inv_x_var = sympy.Symbol('inv_x') inv_y_var = sympy.Symbol('inv_y') max_pulses = max(num_pulses) if num_pulses else 0 if not delay_sweep: delay_sweep = study.Linspace(delay_var, start=min_delay_dur.total_nanos(), stop=max_delay_dur.total_nanos(), length=num_points) if delay_sweep.keys != ['delay_ns']: raise ValueError('delay_sweep must be a SingleSweep ' 'with delay_ns parameter') if experiment_type == ExperimentType.RAMSEY: # Ramsey T2* experiment # Use sqrt(Y) to flip to the equator. # Evolve the state for a given amount of delay time # Then measure the state in both X and Y bases. circuit = circuits.Circuit( ops.Y(qubit)**0.5, ops.WaitGate(value.Duration(nanos=delay_var))(qubit), ) else: if experiment_type == ExperimentType.HAHN_ECHO: # Hahn / Spin Echo T2 experiment # Use sqrt(Y) to flip to the equator. # Evolve the state for the given amount of delay time # Flip the state using an X gate # Evolve the state for the given amount of delay time # Then measure the state in both X and Y bases. num_pulses = [0] # This is equivalent to a CPMG experiment with zero pulses # and will follow the same code path. # Carr-Purcell-Meiboom-Gill sequence. # Performs the following sequence # π/2 - wait(t) - π - wait(2t) - ... - π - wait(t) # There will be N π pulses (X gates) # where N sweeps over the values of num_pulses # if not num_pulses: raise ValueError('At least one value must be given ' 'for num_pulses in a CPMG experiment') circuit = _cpmg_circuit(qubit, delay_var, max_pulses) # Add simple state tomography circuit.append(ops.X(qubit)**inv_x_var) circuit.append(ops.Y(qubit)**inv_y_var) circuit.append(ops.measure(qubit, key='output')) tomography_sweep = study.Zip( study.Points('inv_x', [0.0, 0.5]), study.Points('inv_y', [-0.5, 0.0]), ) if num_pulses and max_pulses > 0: pulse_sweep = _cpmg_sweep(num_pulses) sweep = study.Product(delay_sweep, pulse_sweep, tomography_sweep) else: sweep = study.Product(delay_sweep, tomography_sweep) # Tabulate measurements into a histogram results = sampler.sample(circuit, params=sweep, repetitions=repetitions) y_basis_measurements = results[abs(results.inv_y) > 0].copy() x_basis_measurements = results[abs(results.inv_x) > 0].copy() if num_pulses and len(num_pulses) > 1: cols = tuple(f'pulse_{t}' for t in range(max_pulses)) x_basis_measurements[ 'num_pulses'] = x_basis_measurements.loc[:, cols].sum(axis=1) y_basis_measurements[ 'num_pulses'] = y_basis_measurements.loc[:, cols].sum(axis=1) x_basis_tabulation = _create_tabulation(x_basis_measurements) y_basis_tabulation = _create_tabulation(y_basis_measurements) # Return the results in a container object return T2DecayResult(x_basis_tabulation, y_basis_tabulation)