def __call__(self, task: _Simulate2qXEBTask) -> List[Dict[str, Any]]: """Helper function for simulating a given (circuit, cycle_depth).""" circuit_i = task.circuit_i cycle_depths = set(task.cycle_depths) circuit = task.circuit param_resolver = task.param_resolver circuit_max_cycle_depth = (len(circuit) - 1) // 2 if max(cycle_depths) > circuit_max_cycle_depth: raise ValueError( "`circuit` was not long enough to compute all `cycle_depths`.") records: List[Dict[str, Any]] = [] for moment_i, step_result in enumerate( self.simulator.simulate_moment_steps( circuit=circuit, param_resolver=param_resolver)): # Translate from moment_i to cycle_depth: # We know circuit_depth = cycle_depth * 2 + 1, and step_result is the result *after* # moment_i, so circuit_depth = moment_i + 1 and moment_i = cycle_depth * 2. if moment_i % 2 == 1: continue cycle_depth = moment_i // 2 if cycle_depth not in cycle_depths: continue psi = step_result.state_vector() pure_probs = value.state_vector_to_probabilities(psi) records += [{ 'circuit_i': circuit_i, 'cycle_depth': cycle_depth, 'pure_probs': pure_probs, }] return records
def _get_xeb_result( qubit_pair: GridQubitPair, circuits: List['cirq.Circuit'], measurement_results: Sequence[List[np.ndarray]], num_circuits: int, repetitions: int, cycles: List[int], ) -> CrossEntropyResult: # pytest-cov is unable to detect that this function is called by a # multiprocessing Pool # coverage: ignore simulator = sim.Simulator() # Simulate circuits to get bitstring probabilities all_and_observed_probabilities: Dict[int, List[Tuple[ np.ndarray, np.ndarray]]] = collections.defaultdict(list) empirical_probabilities: Dict[ int, List[np.ndarray]] = collections.defaultdict(list) for i, circuit in enumerate(circuits): step_results = simulator.simulate_moment_steps(circuit, qubit_order=qubit_pair) moment_index = 0 for depth, measurements in zip(cycles, measurement_results[i]): while moment_index < 2 * depth: step_result = next(step_results) moment_index += 1 # copy=False is safe because state_vector_to_probabilities will copy anyways amplitudes = step_result.state_vector(copy=False) probabilities = value.state_vector_to_probabilities(amplitudes) _, counts = np.unique(measurements, return_counts=True) empirical_probs = counts / len(measurements) empirical_probs = np.pad(empirical_probs, (0, 4 - len(empirical_probs)), mode='constant') all_and_observed_probabilities[depth].append( (probabilities, probabilities[measurements])) empirical_probabilities[depth].append(empirical_probs) # Compute XEB result data = [] purity_data = [] for depth in cycles: all_probabilities, observed_probabilities = zip( *all_and_observed_probabilities[depth]) empirical_probs = np.asarray(empirical_probabilities[depth]).flatten() fidelity, _ = least_squares_xeb_fidelity_from_probabilities( hilbert_space_dimension=4, observed_probabilities=observed_probabilities, all_probabilities=all_probabilities, observable_from_probability=None, normalize_probabilities=True, ) purity = purity_from_probabilities(4, empirical_probs) data.append(CrossEntropyPair(depth, fidelity)) purity_data.append(SpecklePurityPair(depth, purity)) return CrossEntropyResult( # type: ignore data=data, repetitions=repetitions, purity_data=purity_data)
def xeb_fidelity( circuit: Circuit, bitstrings: Sequence[int], qubit_order: QubitOrderOrList = QubitOrder.DEFAULT, amplitudes: Optional[Mapping[int, complex]] = None, estimator: Callable[[int, Sequence[float]], float] = linear_xeb_fidelity_from_probabilities, ) -> float: """Estimates XEB fidelity from one circuit using user-supplied estimator. Fidelity quantifies the similarity of two quantum states. Here, we estimate the fidelity between the theoretically predicted output state of circuit and the state produced in its experimental realization. Note that we don't know the latter state. Nevertheless, we can estimate the fidelity between the two states from the knowledge of the bitstrings observed in the experiment. In order to make the estimate more robust one should average the estimates over many random circuits. The API supports per-circuit fidelity estimation to enable users to examine the properties of estimate distribution over many circuits. See https://arxiv.org/abs/1608.00263 for more details. Args: circuit: Random quantum circuit which has been executed on quantum processor under test. bitstrings: Results of terminal all-qubit measurements performed after each circuit execution as integer array where each integer is formed from measured qubit values according to `qubit_order` from most to least significant qubit, i.e. in the order consistent with `cirq.final_state_vector`. qubit_order: Qubit order used to construct bitstrings enumerating qubits starting with the most significant qubit. amplitudes: Optional mapping from bitstring to output amplitude. If provided, simulation is skipped. Useful for large circuits when an offline simulation had already been performed. estimator: Fidelity estimator to use, see above. Defaults to the linear XEB fidelity estimator. Returns: Estimate of fidelity associated with an experimental realization of circuit which yielded measurements in bitstrings. Raises: ValueError: Circuit is inconsistent with qubit order or one of the bitstrings is inconsistent with the number of qubits. """ dim = np.prod(circuit.qid_shape(), dtype=np.int64) if isinstance(bitstrings, tuple): bitstrings = list(bitstrings) for bitstring in bitstrings: if not 0 <= bitstring < dim: raise ValueError( f'Bitstring {bitstring} could not have been observed ' f'on {len(circuit.qid_shape())} qubits.') if amplitudes is None: output_state = final_state_vector(circuit, qubit_order=qubit_order) output_probabilities = state_vector_to_probabilities(output_state) bitstring_probabilities = output_probabilities[bitstrings] else: bitstring_probabilities = np.abs( [amplitudes[bitstring] for bitstring in bitstrings])**2 return estimator(dim, bitstring_probabilities)
def cross_entropy_benchmarking( sampler: work.Sampler, qubits: Sequence[ops.Qid], *, benchmark_ops: Sequence[circuits.Moment] = None, num_circuits: int = 20, repetitions: int = 1000, cycles: Union[int, Iterable[int]] = range(2, 103, 10), scrambling_gates_per_cycle: List[List[ops.Gate]] = None, simulator: sim.Simulator = None, ) -> CrossEntropyResult: r"""Cross-entropy benchmarking (XEB) of multiple qubits. A total of M random circuits are generated, each of which comprises N layers where N = max('cycles') or 'cycles' if a single value is specified for the 'cycles' parameter. Every layer contains randomly generated single-qubit gates applied to each qubit, followed by a set of user-defined benchmarking operations (e.g. a set of two-qubit gates). Each circuit (circuit_m) from the M random circuits is further used to generate a set of circuits {circuit_mn}, where circuit_mn is built from the first n cycles of circuit_m. n spans all the values in 'cycles'. For each fixed value n, the experiment performs the following: 1) Experimentally collect a number of bit-strings for each circuit_mn via projective measurements in the z-basis. 2) Theoretically compute the expected bit-string probabilities $P^{th, mn}_|...00>$, $P^{th, mn}_|...01>$, $P^{th, mn}_|...10>$, $P^{th, mn}_|...11>$ ... at the end of circuit_mn for all m and for all possible bit-strings in the Hilbert space. 3) Compute an experimental XEB function for each circuit_mn: $f_{mn}^{meas} = \langle D * P^{th, mn}_q - 1 \rangle$ where D is the number of states in the Hilbert space, $P^{th, mn}_q$ is the theoretical probability of a bit-string q at the end of circuit_mn, and $\langle \rangle$ corresponds to the ensemble average over all measured bit-strings. Then, take the average of $f_{mn}^{meas}$ over all circuit_mn with fixed n to obtain: $f_{n}^{meas} = (\sum_m f_{mn}^{meas}) / M$ 4) Compute a theoretical XEB function for each circuit_mn: $f_{mn}^{th} = D \sum_q (P^{th, mn}_q) ** 2 - 1$ where the summation goes over all possible bit-strings q in the Hilbert space. Similarly, we then average $f_m^{th}$ over all circuit_mn with fixed n to obtain: $f_{n}^{th} = (\sum_m f_{mn}^{th}) / M$ 5) Calculate the XEB fidelity $\alpha_n$ at fixed n: $\alpha_n = f_{n} ^ {meas} / f_{n} ^ {th}$ Args: sampler: The quantum engine or simulator to run the circuits. qubits: The qubits included in the XEB experiment. benchmark_ops: A sequence of circuits.Moment containing gate operations between specific qubits which are to be benchmarked for fidelity. If more than one circuits.Moment is specified, the random circuits will rotate between the circuits.Moment's. As an example, if benchmark_ops = [Moment([ops.CZ(q0, q1), ops.CZ(q2, q3)]), Moment([ops.CZ(q1, q2)]) where q0, q1, q2 and q3 are instances of Qid (such as GridQubits), each random circuit will apply CZ gate between q0 and q1 plus CZ between q2 and q3 for the first cycle, CZ gate between q1 and q2 for the second cycle, CZ between q0 and q1 and CZ between q2 and q3 for the third cycle and so on. If None, the circuits will consist only of single-qubit gates. num_circuits: The total number of random circuits to be used. repetitions: The number of measurements for each circuit to estimate the bit-string probabilities. cycles: The different numbers of circuit layers in the XEB study. Could be a single or a collection of values. scrambling_gates_per_cycle: If None (by default), the single-qubit gates are chosen from X/2 ($\pi/2$ rotation around the X axis), Y/2 ($\pi/2$ rotation around the Y axis) and (X + Y)/2 ($\pi/2$ rotation around an axis $\pi/4$ away from the X on the equator of the Bloch sphere). Otherwise the single-qubit gates for each layer are chosen from a list of possible choices (each choice is a list of one or more single-qubit gates). simulator: A simulator that calculates the bit-string probabilities of the ideal circuit. By default, this is set to sim.Simulator(). Returns: A CrossEntropyResult object that stores and plots the result. """ simulator = sim.Simulator() if simulator is None else simulator num_qubits = len(qubits) if isinstance(cycles, int): cycle_range = [cycles] else: cycle_range = list(cycles) # These store the measured and simulated bit-string probabilities from # all trials in two dictionaries. The keys of the dictionaries are the # numbers of cycles. The values are 2D arrays with each row being the # probabilities obtained from a single trial. probs_meas = { n: np.zeros((num_circuits, 2**num_qubits)) for n in cycle_range } probs_th = { n: np.zeros((num_circuits, 2**num_qubits)) for n in cycle_range } for k in range(num_circuits): # Generates one random XEB circuit with max(num_cycle_range) cycles. # Then the first n cycles of the circuit are taken to generate # shorter circuits with n cycles (n taken from cycles). All of these # circuits are stored in circuits_k. circuits_k = _build_xeb_circuits(qubits, cycle_range, scrambling_gates_per_cycle, benchmark_ops) # Run each circuit with the sampler to obtain a collection of # bit-strings, from which the bit-string probabilities are estimated. probs_meas_k = _measure_prob_distribution(sampler, repetitions, qubits, circuits_k) # Simulate each circuit with the Cirq simulator to obtain the # state vector at the end of each circuit, from which the # theoretically expected bit-string probabilities are obtained. probs_th_k: List[np.ndarray] = [] for circ_k in circuits_k: res = simulator.simulate(circ_k, qubit_order=qubits) state_probs = value.state_vector_to_probabilities( np.asarray(res.final_state_vector)) probs_th_k.append(state_probs) for i, num_cycle in enumerate(cycle_range): probs_th[num_cycle][k, :] = probs_th_k[i] probs_meas[num_cycle][k, :] = probs_meas_k[i] fidelity_vals = _xeb_fidelities(probs_th, probs_meas) xeb_data = [ CrossEntropyPair(c, k) for (c, k) in zip(cycle_range, fidelity_vals) ] return CrossEntropyResult(data=xeb_data, repetitions=repetitions) # type: ignore