示例#1
0
def _symmetrization(
        program: Program,
        meas_qubits: List[int],
        symm_type: int = 3) -> Tuple[List[Program], List[Tuple[bool]]]:
    """
    For the input program generate new programs which flip the measured qubits with an X gate in
    certain combinations in order to symmetrize readout.

    An expanded list of programs is returned along with a list of bools which indicates which
    qubits are flipped in each program.

    The symmetrization types are specified by an int; the types available are:

    * -1 -- exhaustive symmetrization uses every possible combination of flips
    *  0 -- trivial that is no symmetrization
    *  1 -- symmetrization using an OA with strength 1
    *  2 -- symmetrization using an OA with strength 2
    *  3 -- symmetrization using an OA with strength 3

    In the context of readout symmetrization the strength of the orthogonal array enforces the
    symmetry of the marginal confusion matrices.

    By default a strength 3 OA is used; this ensures expectations of the form <b_k * b_j * b_i>
    for bits any bits i,j,k will have symmetric readout errors. Here expectation of a random
    variable x as is denote <x> = sum_i Pr(i) x_i. It turns out that a strength 3 OA is also a
    strength 2 and strength 1 OA it also ensures <b_j * b_i> and <b_i> have symmetric readout
    errors for any bits b_j and b_i.

    :param programs: a program which will be symmetrized.
    :param meas_qubits: the groups of measurement qubits. Only these qubits will be symmetrized
        over, even if the program acts on other qubits.
    :param sym_type: an int determining the type of symmetrization performed.
    :return: a list of symmetrized programs, the corresponding array of bools indicating which
        qubits were flipped.
    """
    if symm_type < -1 or symm_type > 3:
        raise ValueError(
            "symm_type must be one of the following ints [-1, 0, 1, 2, 3].")
    elif symm_type == -1:
        # exhaustive = all possible binary strings
        flip_matrix = np.asarray(
            list(itertools.product([0, 1], repeat=len(meas_qubits))))
    elif symm_type >= 0:
        flip_matrix = _construct_orthogonal_array(len(meas_qubits), symm_type)

    # The next part is not rigorous in the sense that we simply truncate to the desired
    # number of qubits. The problem is that orthogonal arrays of a certain strength for an
    # arbitrary number of qubits are not known to exist.
    flip_matrix = flip_matrix[:, :len(meas_qubits)]

    symm_programs = []
    flip_arrays = []
    for flip_array in flip_matrix:
        total_prog_symm = program.copy()
        prog_symm = _flip_array_to_prog(flip_array, meas_qubits)
        total_prog_symm += prog_symm
        symm_programs.append(total_prog_symm)
        flip_arrays.append(flip_array)

    return symm_programs, flip_arrays
示例#2
0
def _get_flipped_protoquil_program(program: Program) -> Program:
    """For symmetrization, generate a program where X gates are added before measurement.

    Forest 1.3 is really picky about where the measure instructions happen. It has to be
    at the end!
    """
    program = program.copy()
    to_measure = []
    while len(program) > 0:
        inst = program.instructions[-1]
        if isinstance(inst, Measurement):
            program.pop()
            to_measure.append((inst.qubit, inst.classical_reg))
        else:
            break

    program += Pragma('PRESERVE_BLOCK')
    for qu, addr in to_measure[::-1]:
        program += RX(pi, qu)
    program += Pragma('END_PRESERVE_BLOCK')

    for qu, addr in to_measure[::-1]:
        program += Measurement(qubit=qu, classical_reg=addr)

    return program
示例#3
0
    def run_and_measure(self, program: Program, trials: int):
        """
        Run the provided state preparation program and measure all qubits.

        .. note::

            In contrast to :py:class:`QVMConnection.run_and_measure`, this method simulates
            noise correctly for noisy QVMs. However, this method is slower for ``trials > 1``.
            For faster noise-free simulation, consider
            :py:class:`WavefunctionSimulator.run_and_measure`.

        :param program: The state preparation program to run and then measure.
        :param trials: The number of times to run the program.
        :return: A numpy array of shape (trials, n_device_qubits) that contains 0s and 1s
        """
        program = program.copy()
        for instr in program.instructions:
            if not isinstance(instr, Gate) and not isinstance(instr, Reset):
                raise ValueError(
                    "run_and_measure programs must consist only of quantum gates."
                )
        ro = program.declare('ro', 'BIT',
                             max(self.device.qubit_topology().nodes) + 1)
        for q in sorted(self.device.qubit_topology().nodes):
            program.inst(MEASURE(q, ro[q]))
        program.wrap_in_numshots_loop(trials)
        executable = self.compile(program)
        return self.run(executable=executable)
示例#4
0
    def _run(self,
             circuit: Program,
             num_shots: int = 8192) -> List[np.ndarray]:
        """Run a Program a number of times and record the measurement results.
        
        Args:
            circuit: A Program to be run and measured.
            num_shots: An integer representing the number of times the quantum circuit is to be evaluated.
            
        Returns: A list of numpy arrays containing the readout of qubits from single measurements.
        
        """
        prog = circuit.copy()

        #check whether we are given any bit-flip probabilities
        if self._noisy_readout_probabilities:

            # Add readout error to each qubit according to the dictionary
            for qubit in circuit.get_qubits(True):

                # it might happen that there are qubits without any probabilities given which would raise a KeyError
                try:
                    p0, p1 = self._noisy_readout_probabilities[qubit]
                    prog.define_noisy_readout(qubit, p00=p0, p11=p1)
                except:

                    # if we don't find the key in the dictionary, we don't add readout error the the qubit, but rather pass
                    pass

        # execute the circuit num_shots times
        prog.wrap_in_numshots_loop(num_shots)
        result = self._device.run(self._device.compile(prog))

        return result
示例#5
0
    def _evaluate_single_term(self, ansatz_circuit: Program,
                              meas_term: PauliTerm) -> np.ndarray:
        """Compute the expectation value of a single PauliTerm , given a quantum circuit to evaluate on.
        
        Args:
            ansatz_circuit: A Program representing the quantum state to evaluate the PauliTerm on.
            
            meas_term: a PauliTerm object to be evaluated.
        
        """
        # First, create the quantum circuit needed for evaluation.

        # concatenate operator (converted to a Program) and ansatz circuit
        expectation_circuit = ansatz_circuit.copy()

        expectation_circuit += meas_term.program

        ro = expectation_circuit.declare('ro', 'BIT',
                                         len(meas_term.get_qubits()))

        # add necessary post-rotations and measurement
        for i, qubit in enumerate(sorted(list(meas_term.get_qubits()))):

            if meas_term.pauli_string([qubit]) == 'X':
                expectation_circuit += H(qubit)
            elif meas_term.pauli_string([qubit]) == 'Y':
                expectation_circuit += H(qubit)
                expectation_circuit += S(qubit)

            expectation_circuit += Program().measure(qubit, ro[i])

        result = self._run(expectation_circuit,
                           num_shots=self._num_shots_evaluation)

        return result
示例#6
0
def test_copy():
    prog1 = Program(H(0), CNOT(0, 1))
    prog2 = prog1.copy().measure_all()
    assert prog1.out() == "\n".join(["H 0", "CNOT 0 1", ""])
    assert prog2.out() == "\n".join([
        "H 0", "CNOT 0 1", "DECLARE ro BIT[2]", "MEASURE 0 ro[0]",
        "MEASURE 1 ro[1]", ""
    ])
示例#7
0
    def run_and_measure(self, program: Program,
                        trials: int) -> Dict[int, np.ndarray]:
        """
        Run the provided state preparation program and measure all qubits.

        The returned data is a dictionary keyed by qubit index because qubits for a given
        QuantumComputer may be non-contiguous and non-zero-indexed. To turn this dictionary
        into a 2d numpy array of bitstrings, consider::

            bitstrings = qc.run_and_measure(...)
            bitstring_array = np.vstack([bitstrings[q] for q in qc.qubits()]).T
            bitstring_array.shape  # (trials, len(qc.qubits()))

        .. note::

            If the target :py:class:`QuantumComputer` is a noiseless :py:class:`QVM` then
            only the qubits explicitly used in the program will be measured. Otherwise all
            qubits will be measured. In some circumstances this can exhaust the memory
            available to the simulator, and this may be manifested by the QVM failing to
            respond or timeout.

        .. note::

            In contrast to :py:class:`QVMConnection.run_and_measure`, this method simulates
            noise correctly for noisy QVMs. However, this method is slower for ``trials > 1``.
            For faster noise-free simulation, consider
            :py:class:`WavefunctionSimulator.run_and_measure`.

        :param program: The state preparation program to run and then measure.
        :param trials: The number of times to run the program.
        :return: A dictionary keyed by qubit index where the corresponding value is a 1D array of
            measured bits.
        """
        program = program.copy()
        validate_supported_quil(program)
        ro = program.declare("ro", "BIT", len(self.qubits()))
        measure_used = isinstance(self.qam,
                                  QVM) and self.qam.noise_model is None
        qubits_to_measure = set(
            map(qubit_index, program.get_qubits()) if measure_used else self.
            qubits())
        for i, q in enumerate(qubits_to_measure):
            program.inst(MEASURE(q, ro[i]))
        program.wrap_in_numshots_loop(trials)
        executable = self.compile(program)
        bitstring_array = self.run(executable=executable)
        bitstring_dict = {}
        for i, q in enumerate(qubits_to_measure):
            bitstring_dict[q] = bitstring_array[:, i]
        for q in set(self.qubits()) - set(qubits_to_measure):
            bitstring_dict[q] = np.zeros(trials)
        return bitstring_dict
示例#8
0
def test_copy():
    prog1 = Program(
        H(0),
        CNOT(0, 1),
    )
    prog2 = prog1.copy().measure_all()
    assert prog1.out() == '\n'.join(['H 0', 'CNOT 0 1', ''])
    assert prog2.out() == '\n'.join([
        'H 0',
        'CNOT 0 1',
        'DECLARE ro BIT[2]',
        'MEASURE 0 ro[0]',
        'MEASURE 1 ro[1]',
        '',
    ])
示例#9
0
def ansatz(params: Dict[int,float],
           prog_in: Program = program_initialization
           )-> Program:
    """
    Create a maximally expressive 2-qubit quantum circuit with minimal amount of parameters (15 rotations)
    
    Args:
        params: A dictionary of 15 real parameters for the 15 rotation gates in the ansatz circuit
        prog_in: A Program to start creating the circuit with.
        
    Returns: A Program representing the ansatz circuit.
    """
    prog_out = prog_in.copy()
    
    prog_out += RZ(params[0],  1)
    
    prog_out += CNOT(0,1)
    
    prog_out += RZ(params[1],  0)
    prog_out += RX(params[2],  0)
    prog_out += RZ(params[3],  0)
    
    prog_out += RZ(params[4],  1)
    prog_out += RX(params[5],  1)
    prog_out += RZ(params[6],  1)
    
    prog_out += CNOT(0,1)
    
    prog_out += RX(params[7],  0)
    prog_out += RZ(params[8],  1)
    
    prog_out += CNOT(0,1)
    
    prog_out += RZ(params[9],  0)
    prog_out += RX(params[10], 0)
    prog_out += RZ(params[11], 0)
    
    prog_out += RZ(params[12], 1)
    prog_out += RX(params[13], 1)
    prog_out += RZ(params[14], 1)


    return prog_out
示例#10
0
    def __init__(self,
                 prepare_ansatz: Program,
                 make_memory_map: Callable[[Iterable], dict],
                 hamiltonian: PauliSum,
                 qvm: Union[QuantumComputer, str],
                 scalar_cost_function: bool = True,
                 nshots: int = 1,
                 base_numshots: int = 100,
                 qubit_mapping: Dict[QubitPlaceholder, Union[Qubit, int]] = None,
                 enable_logging: bool = False,
                 hamiltonian_is_diagonal: bool =False):

        self.scalar = scalar_cost_function
        self.nshots = nshots
        self.make_memory_map = make_memory_map

        if isinstance(qvm, str):
            qvm = get_qc(qvm)
        self.qvm = qvm

        if qubit_mapping is not None:
            prepare_ansatz = address_qubits(prepare_ansatz, qubit_mapping)
            ham = address_qubits_hamiltonian(hamiltonian, qubit_mapping)
        else:
            ham = hamiltonian

        if not hamiltonian_is_diagonal:
            self.hams = commuting_decomposition(ham)
        else:
            self.hams = [ham]

        self.exes = []
        for ham in self.hams:
            # need a different program for each of the self commuting hams
            p = prepare_ansatz.copy()
            append_measure_register(p,
                                    qubits=ham.get_qubits(),
                                    trials=base_numshots,
                                    ham=ham)
            self.exes.append(qvm.compile(p))

        if enable_logging:
            self.log = []
示例#11
0
    def run_and_measure(self, program: Program,
                        trials: int) -> Dict[int, np.ndarray]:
        """
        Run the provided state preparation program and measure all qubits.

        This will measure all the qubits on this QuantumComputer, not just qubits
        that are used in the program.

        The returned data is a dictionary keyed by qubit index because qubits for a given
        QuantumComputer may be non-contiguous and non-zero-indexed. To turn this dictionary
        into a 2d numpy array of bitstrings, consider::

            bitstrings = qc.run_and_measure(...)
            bitstring_array = np.vstack(bitstrings[q] for q in sorted(qc.qubits())).T
            bitstring_array.shape  # (trials, len(qc.qubits()))

        .. note::

            In contrast to :py:class:`QVMConnection.run_and_measure`, this method simulates
            noise correctly for noisy QVMs. However, this method is slower for ``trials > 1``.
            For faster noise-free simulation, consider
            :py:class:`WavefunctionSimulator.run_and_measure`.

        :param program: The state preparation program to run and then measure.
        :param trials: The number of times to run the program.
        :return: A dictionary keyed by qubit index where the corresponding value is a 1D array of
            measured bits.
        """
        program = program.copy()
        program = _validate_run_and_measure_program(program)
        ro = program.declare('ro', 'BIT', len(self.qubits()))
        for i, q in enumerate(self.qubits()):
            program.inst(MEASURE(q, ro[i]))
        program.wrap_in_numshots_loop(trials)
        executable = self.compile(program)
        bitstring_array = self.run(executable=executable)
        bitstring_dict = {}
        for i, q in enumerate(self.qubits()):
            bitstring_dict[q] = bitstring_array[:, i]
        return bitstring_dict