def test_raw(): circuit = cirq.Circuit(cirq.H.on(cirq.LineQubit(0))) observable = Observable(PauliString("X")) executor = Executor(mitiq_cirq.compute_density_matrix) raw_value = raw.execute(circuit, executor, observable) assert isinstance(raw_value, complex) assert executor.executed_circuits == [circuit] compute_density_matrix_noiseless = functools.partial( mitiq_cirq.compute_density_matrix, noise_level=(0.0, )) executor_noiseless = Executor(compute_density_matrix_noiseless) true_value = raw.execute(circuit, executor_noiseless, observable) assert np.isclose(true_value, 1.0) assert executor_noiseless.executed_circuits == [circuit]
def execute( circuit: QPROGRAM, executor: Union[Executor, Callable[[QPROGRAM], QuantumResult]], observable: Optional[Observable] = None, ) -> complex: """Evaluates the expectation value associated to the input circuit without using error mitigation. The only purpose of this function is to provide the same interface for non-error-mitigated values as the rest of the techniques in Mitiq. This is useful when comparing error-mitigated results to non-error-mitigated results. Args: circuit: The circuit to run. executor: Executes a circuit and returns a `QuantumResult`. observable: Observable to compute the expectation value of. If None, the `executor` must return an expectation value. Otherwise, the `QuantumResult` returned by `executor` is used to compute the expectation of the observable. """ if not isinstance(executor, Executor): executor = Executor(executor) return executor.evaluate(circuit, observable)[0]
def test_execute_with_ddd_with_num_trials(executor): """Tests the option num_trials of execute_with_ddd.""" executor = Executor(executor) mitigated_1 = execute_with_ddd( circuit_cirq_a, executor, rule=xx, num_trials=1, ) assert executor.calls_to_executor == 1 assert len(executor.executed_circuits) == 1 mitigated_2 = execute_with_ddd( circuit_cirq_a, executor, rule=xx, num_trials=2, ) # Note executor contains the history of both experiments if executor.can_batch: assert executor.calls_to_executor == 2 else: assert executor.calls_to_executor == 3 assert len(executor.executed_circuits) == 3 # For deterministic DDD sequences num_trials is irrelevant assert np.isclose(mitigated_1, mitigated_2)
def test_execute_with_ddd_with_full_output(): """Tests the option full_output of execute_with_ddd.""" executor = Executor(noiseless_serial_executor) ddd_value, ddd_data = execute_with_ddd( circuit_cirq_a, executor, rule=xx, num_trials=2, full_output=True, ) assert len(executor.executed_circuits) == 2 assert len(ddd_data["circuits_with_ddd"]) == 2 assert len(ddd_data["ddd_trials"]) == 2 assert ddd_data["ddd_value"] == ddd_value # For a deterministic rule assert ddd_data["ddd_trials"][0] == ddd_data["ddd_trials"][1]
def execute_with_pec( circuit: QPROGRAM, executor: Union[Executor, Callable[[QPROGRAM], QuantumResult]], observable: Optional[Observable] = None, *, representations: Sequence[OperationRepresentation], precision: float = 0.03, num_samples: Optional[int] = None, force_run_all: bool = True, random_state: Optional[Union[int, np.random.RandomState]] = None, full_output: bool = False, ) -> Union[float, Tuple[float, Dict[str, Any]]]: r"""Estimates the error-mitigated expectation value associated to the input circuit, via the application of probabilistic error cancellation (PEC). [Temme2017]_ [Endo2018]_. This function implements PEC by: 1. Sampling different implementable circuits from the quasi-probability representation of the input circuit; 2. Evaluating the noisy expectation values associated to the sampled circuits (through the "executor" function provided by the user); 3. Estimating the ideal expectation value from a suitable linear combination of the noisy ones. Args: circuit: The input circuit to execute with error-mitigation. executor: A Mitiq executor that executes a circuit and returns the unmitigated ``QuantumResult`` (e.g. an expectation value). observable: Observable to compute the expectation value of. If None, the `executor` must return an expectation value. Otherwise, the `QuantumResult` returned by `executor` is used to compute the expectation of the observable. representations: Representations (basis expansions) of each operation in the input circuit. precision: The desired estimation precision (assuming the observable is bounded by 1). The number of samples is deduced according to the formula (one_norm / precision) ** 2, where 'one_norm' is related to the negativity of the quasi-probability representation [Temme2017]_. If 'num_samples' is explicitly set by the user, 'precision' is ignored and has no effect. num_samples: The number of noisy circuits to be sampled for PEC. If not given, this is deduced from the argument 'precision'. force_run_all: If True, all sampled circuits are executed regardless of uniqueness, else a minimal unique set is executed. random_state: Seed for sampling circuits. full_output: If False only the average PEC value is returned. If True a dictionary containing all PEC data is returned too. Returns: The tuple ``(pec_value, pec_data)`` where ``pec_value`` is the expectation value estimated with PEC and ``pec_data`` is a dictionary which contains all the raw data involved in the PEC process (including the PEC estimation error). The error is estimated as ``pec_std / sqrt(num_samples)``, where ``pec_std`` is the standard deviation of the PEC samples, i.e., the square root of the mean squared deviation of the sampled values from ``pec_value``. If ``full_output`` is ``True``, only ``pec_value`` is returned. .. [Endo2018] : Suguru Endo, Simon C. Benjamin, Ying Li, "Practical Quantum Error Mitigation for Near-Future Applications" *Phys. Rev. **X 8**, 031027 (2018), (https://arxiv.org/abs/1712.09271). .. [Takagi2020] : Ryuji Takagi, "Optimal resource cost for error mitigation," (https://arxiv.org/abs/2006.12509). """ if isinstance(random_state, int): random_state = np.random.RandomState(random_state) if not (0 < precision <= 1): raise ValueError( "The value of 'precision' should be within the interval (0, 1]," f" but precision is {precision}.") converted_circuit, input_type = convert_to_mitiq(circuit) # Get the 1-norm of the circuit quasi-probability representation _, _, norm = sample_circuit( converted_circuit, representations, num_samples=1, ) # Deduce the number of samples (if not given by the user) if not isinstance(num_samples, int): num_samples = int((norm / precision)**2) # Issue warning for very large sample size if num_samples > 10**5: warnings.warn(_LARGE_SAMPLE_WARN, LargeSampleWarning) # Sample all the circuits sampled_circuits, signs, _ = sample_circuit( converted_circuit, representations, random_state=random_state, num_samples=num_samples, ) # Convert back to the original type sampled_circuits = [ convert_from_mitiq(cast(CirqCircuit, c), input_type) for c in sampled_circuits ] # Execute all sampled circuits if not isinstance(executor, Executor): executor = Executor(executor) results = executor.evaluate(sampled_circuits, observable, force_run_all) # Evaluate unbiased estimators [Temme2017] [Endo2018] [Takagi2020] unbiased_estimators = [ norm * s * val # type: ignore[operator] for s, val in zip(signs, results) ] pec_value = np.average(unbiased_estimators) if not full_output: return pec_value # Build dictionary with additional results and data pec_data: Dict[str, Any] = { "num_samples": num_samples, "precision": precision, "pec_value": pec_value, "pec_error": np.std(unbiased_estimators) / np.sqrt(num_samples), "unbiased_estimators": unbiased_estimators, "measured_expectation_values": results, "sampled_circuits": sampled_circuits, } return pec_value, pec_data
def execute_with_cdr( circuit: QPROGRAM, executor: Union[Executor, Callable[[QPROGRAM], QuantumResult]], observable: Optional[Observable] = None, *, simulator: Union[Executor, Callable[[QPROGRAM], QuantumResult]], num_training_circuits: int = 10, fraction_non_clifford: float = 0.1, fit_function: Callable[..., float] = linear_fit_function, num_fit_parameters: Optional[int] = None, scale_factors: Sequence[float] = (1, ), scale_noise: Callable[[QPROGRAM, float], QPROGRAM] = fold_gates_at_random, **kwargs: Any, ) -> float: """Function for the calculation of an observable from some circuit of interest to be mitigated with CDR (or vnCDR) based on Ref. :cite:`Czarnik_2021_Quantum` and Ref. :cite:`Lowe_2021_PRR`. The circuit of interest must be compiled in the native basis of the IBM quantum computers, that is {Rz, sqrt(X), CNOT}, or such that all the non-Clifford gates are contained in the Rz rotations. The observable/s to be calculated should be input as an array or a list of arrays representing the diagonal of the observables to be measured. Note these observables MUST be diagonal in z-basis measurements corresponding to the circuit of interest. Returns mitigated observables list of raw observables (at noise scale factors). This function returns the mitigated observable/s. Args: circuit: Quantum program to execute with error mitigation. executor: Executes a circuit and returns a `QuantumResult`. observable: Observable to compute the expectation value of. If None, the `executor` must return an expectation value. Otherwise the `QuantumResult` returned by `executor` is used to compute the expectation of the observable. simulator: Executes a circuit without noise and returns a `QuantumResult`. For CDR to be efficient, the simulator must be able to efficiently simulate near-Clifford circuits. num_training_circuits: Number of training circuits to be used in the mitigation. fraction_non_clifford: The fraction of non-Clifford gates to be substituted in the training circuits. fit_function: The function to map noisy to exact data. Takes array of noisy and data and parameters returning a float. See ``cdr.linear_fit_function`` for an example. num_fit_parameters: The number of parameters the fit_function takes. scale_noise: scale_noise: Function for scaling the noise of a quantum circuit. scale_factors: Factors by which to scale the noise. - When 1.0 is the only scale factor, the method is known as CDR. - Note: When scale factors larger than 1.0 are provided, the method is known as "variable-noise CDR." kwargs: Available keyword arguments are: - method_select (string): Specifies the method used to select the non-Clifford gates to replace when constructing the near-Clifford training circuits. Can be 'uniform' or 'gaussian'. - method_replace (string): Specifies the method used to replace the selected non-Clifford gates with a Clifford when constructing the near-Clifford training circuits. Can be 'uniform', 'gaussian', or 'closest'. - sigma_select (float): Width of the Gaussian distribution used for ``method_select='gaussian'``. - sigma_replace (float): Width of the Gaussian distribution used for ``method_replace='gaussian'``. - random_state (int): Seed for sampling. """ # Handle keyword arguments for generating training circuits. method_select = kwargs.get("method_select", "uniform") method_replace = kwargs.get("method_replace", "closest") random_state = kwargs.get("random_state", None) kwargs_for_training_set_generation = { "sigma_select": kwargs.get("sigma_select"), "sigma_replace": kwargs.get("sigma_replace"), } if num_fit_parameters is None: if fit_function is linear_fit_function: num_fit_parameters = 1 + len(scale_factors) elif fit_function is linear_fit_function_no_intercept: num_fit_parameters = len(scale_factors) else: raise ValueError( "Must provide `num_fit_parameters` for custom fit function.") # cast executor and simulator inputs to Executor type if not isinstance(executor, Executor): executor = Executor(executor) if not isinstance(simulator, Executor): simulator = Executor(simulator) # Check if circuit is already Clifford if is_clifford(circuit): return simulator.evaluate(circuit, observable)[0].real # Generate training circuits. training_circuits = generate_training_circuits( circuit, num_training_circuits, fraction_non_clifford, method_select, method_replace, random_state, kwargs=kwargs_for_training_set_generation, ) # [Optionally] Scale noise in circuits. all_circuits = [ [scale_noise(c, s) for s in scale_factors] for c in [circuit] + training_circuits # type: ignore ] to_run = [circuit for circuits in all_circuits for circuit in circuits] all_circuits_shape = (len(all_circuits), len(all_circuits[0])) results = executor.evaluate(to_run, observable) noisy_results = np.array(results).reshape(all_circuits_shape) results = simulator.evaluate(training_circuits, observable) ideal_results = np.array(results) # Do the regression. fitted_params, _ = curve_fit( lambda x, *params: fit_function(x, params), noisy_results[1:, :].T, ideal_results, p0=np.zeros(num_fit_parameters), ) return fit_function(noisy_results[0, :], fitted_params)
def execute_with_ddd( circuit: QPROGRAM, executor: Union[Executor, Callable[[QPROGRAM], QuantumResult]], observable: Optional[Observable] = None, *, rule: Callable[[int], QPROGRAM], rule_args: Dict[str, Any] = {}, num_trials: int = 1, full_output: bool = False, ) -> Union[float, Tuple[float, Dict[str, Any]]]: r"""Estimates the error-mitigated expectation value associated to the input circuit, via the application of digital dynamical decoupling (DDD). Args: circuit: The input circuit to execute with DDD. executor: A Mitiq executor that executes a circuit and returns the unmitigated ``QuantumResult`` (e.g. an expectation value). observable: Observable to compute the expectation value of. If None, the ``executor`` must return an expectation value. Otherwise, the ``QuantumResult`` returned by ``executor`` is used to compute the expectation of the observable. rule: A function that takes as main argument a slack length (i.e. the number of idle moments) of a slack window (i.e. a single-qubit idle window in a circuit) and returns the DDD sequence of gates to be applied in that window. Mitiq provides standard built-in rules that can be directly imported from ``mitiq.ddd.rules``. rule_args: An optional dictionary of keyword arguments for ``rule``. num_trials: The number of independent experiments to average over. A number larger than 1 can be useful to average over multiple applications of a rule returning non-deterministic DDD sequences. full_output: If ``False`` only the mitigated expectation value is returned. If ``True`` a dictionary containing all DDD data is returned too. Returns: The tuple ``(ddd_value, ddd_data)`` where ``ddd_value`` is the expectation value estimated with DDD and ``ddd_data`` is a dictionary containing all the raw data involved in the DDD process (e.g. the circuit filled with DDD sequences). If ``full_output`` is false, only ``ddd_value`` is returned. """ # Initialize executor if not isinstance(executor, Executor): executor = Executor(executor) rule_partial: Callable[[int], QPROGRAM] rule_partial = partial(rule, **rule_args) # Insert DDD sequences in (a copy of) the input circuit circuits_with_ddd = [ insert_ddd_sequences(circuit, rule_partial) for _ in range(num_trials) ] results = executor.evaluate( circuits_with_ddd, observable, force_run_all=True, ) assert len(results) == num_trials ddd_value = np.real_if_close(np.sum(results)) / num_trials if not full_output: return ddd_value ddd_data = { "ddd_value": ddd_value, "ddd_trials": results, "circuits_with_ddd": circuits_with_ddd, } return ddd_value, ddd_data
def mitigate_executor( executor: Callable[[QPROGRAM], QuantumResult], observable: Optional[Observable] = None, *, rule: Callable[[int], QPROGRAM], rule_args: Dict[str, Any] = {}, num_trials: int = 1, full_output: bool = False, ) -> Callable[[QPROGRAM], Union[float, Tuple[float, Dict[str, Any]]]]: """Returns a modified version of the input 'executor' which is error-mitigated with digital dynamical decoupling (DDD). Args: executor: A function that executes a circuit and returns the unmitigated `QuantumResult` (e.g. an expectation value). observable: Observable to compute the expectation value of. If None, the `executor` must return an expectation value. Otherwise, the `QuantumResult` returned by `executor` is used to compute the expectation of the observable. rule: A function that takes as main argument a slack length (i.e. the number of idle moments) of a slack window (i.e. a single-qubit idle window in a circuit) and returns the DDD sequence of gates to be applied in that window. Mitiq provides standard built-in rules that can be directly imported from `mitiq.ddd.rules`. rule_args: An optional dictionary of keyword arguments for `rule`. num_trials: The number of independent experiments to average over. A number larger than 1 can be useful to average over multiple applications of a rule returning non-deterministic DDD sequences. full_output: If False only the mitigated expectation value is returned. If True a dictionary containing all DDD data is returned too. Returns: The error-mitigated version of the input executor. """ executor_obj = Executor(executor) if not executor_obj.can_batch: @wraps(executor) def new_executor( circuit: QPROGRAM, ) -> Union[float, Tuple[float, Dict[str, Any]]]: return execute_with_ddd( circuit, executor, observable, rule=rule, rule_args=rule_args, num_trials=num_trials, full_output=full_output, ) else: @wraps(executor) def new_executor( circuits: List[QPROGRAM], ) -> List[Union[float, Tuple[float, Dict[str, Any]]]]: return [ execute_with_ddd( circuit, executor, observable, rule=rule, rule_args=rule_args, num_trials=num_trials, full_output=full_output, ) for circuit in circuits ] return new_executor