Source code for mitiq.zne.scaling.identity_insertion

# Copyright (C) Unitary Fund
#
# This source code is licensed under the GPL license (v3) found in the
# LICENSE file in the root directory of this source tree.
"""Functions for scaling supported circuits by inserting layers of identity
gates."""

import random
from typing import Tuple

import numpy as np
from cirq import Circuit, Moment, ops

from mitiq.interface import accept_qprogram_and_validate
from mitiq.utils import _append_measurements, _pop_measurements


[docs] class UnscalableCircuitError(Exception): pass
# Helper functions for identity scaling def _check_scalable(input_circuit: Circuit) -> None: """Raises an error if the input circuit cannot be scaled by inserting identity layers. Args: circuit: Checks whether this circuit can be scaled. Raises: UnscalableCircuitError: * If the circuit has non-terminal measurements. """ if not input_circuit.are_all_measurements_terminal(): raise UnscalableCircuitError( "Circuit contains intermediate measurements and cannot be folded." ) def _calculate_id_layers( input_circuit_depth: int, scale_factor: float ) -> Tuple[int, int]: """Returns a tuple of integers that describes the number of identity layers to be inserted after each layer of the input circuit. Args: input_circuit_depth : Number of moments in the input_circuit scale_factor : Intended noise scaling factor Returns: (num_uniform_layers, num_partial_layers) : A tuple of the number of uniform identity layers to be inserted after each moment in the input_circuit and a number of partial layers to be inserted after some random moments to be able to achieve the intended scale factor. """ if scale_factor < 1: raise ValueError( f"Requires scale_factor >= 1 but scale_factor = {scale_factor}." ) num_uniform_layers = int(scale_factor - 1) int_scale_factor = num_uniform_layers + 1 if np.isclose(int_scale_factor, scale_factor): return (num_uniform_layers, 0) else: num_partial_layers = int( input_circuit_depth * (scale_factor - 1 - num_uniform_layers) ) return (num_uniform_layers, num_partial_layers)
[docs] @accept_qprogram_and_validate def insert_id_layers(input_circuit: Circuit, scale_factor: float) -> Circuit: """Returns a scaled version of the input circuit by inserting layers of identities. Args: input_circuit : Cirq Circuit to be scaled scale_factor : Noise scaling factor as a float Returns: scaled_circuit : Scaled quantum circuit via identity layer insertions """ _check_scalable(input_circuit) measurements = _pop_measurements(input_circuit) input_circuit_depth = len(input_circuit) num_uniform_layers, num_partial_layers = _calculate_id_layers( input_circuit_depth, scale_factor ) random_moment_indices = random.sample( range(input_circuit_depth), num_partial_layers ) circuit_qubits = input_circuit.all_qubits() id_layer = Moment(ops.I.on_each(*circuit_qubits)) scaled_circuit = Circuit() for i, moment in enumerate(input_circuit): scaled_circuit.append(moment) scaled_circuit.append([id_layer] * (num_uniform_layers)) if i in random_moment_indices: scaled_circuit.append(id_layer) _append_measurements(scaled_circuit, measurements) return scaled_circuit