예제 #1
0
    def compute_amplitudes_sweep(
        self,
        program: circuits.Circuit,
        bitstrings: Sequence[int],
        params: study.Sweepable,
        qubit_order: ops.QubitOrderOrList = ops.QubitOrder.DEFAULT,
    ) -> Sequence[Sequence[complex]]:

        if not isinstance(program, qsimc.QSimCircuit):
            program = qsimc.QSimCircuit(program, device=program.device)

        n_qubits = len(program.all_qubits())
        # qsim numbers qubits in reverse order from cirq
        bitstrings = [
            format(bitstring, 'b').zfill(n_qubits)[::-1]
            for bitstring in bitstrings
        ]

        options = {'i': '\n'.join(bitstrings)}
        options.update(self.qsimh_options)
        param_resolvers = study.to_resolvers(params)

        trials_results = []
        for prs in param_resolvers:

            solved_circuit = protocols.resolve_parameters(program, prs)

            options['c'] = solved_circuit.translate_cirq_to_qsim(qubit_order)

            options.update(self.qsimh_options)
            amplitudes = qsim.qsimh_simulate(options)
            trials_results.append(amplitudes)

        return trials_results
예제 #2
0
    def compute_amplitudes_sweep(
        self,
        program: circuits.Circuit,
        bitstrings: Sequence[int],
        params: study.Sweepable,
        qubit_order: ops.QubitOrderOrList = ops.QubitOrder.DEFAULT,
    ) -> Sequence[Sequence[complex]]:
        """Computes the desired amplitudes using qsim.

        The initial state is assumed to be the all zeros state.

        Args:
            program: The circuit to simulate.
            bitstrings: The bitstrings whose amplitudes are desired, input as an
              string array where each string is formed from measured qubit values
              according to `qubit_order` from most to least significant qubit,
              i.e. in big-endian ordering.
            param_resolver: Parameters to run with the program.
            qubit_order: Determines the canonical ordering of the qubits. This is
              often used in specifying the initial state, i.e. the ordering of the
              computational basis states.

        Returns:
            List of amplitudes.
        """
        if not isinstance(program, qsimc.QSimCircuit):
            program = qsimc.QSimCircuit(program, device=program.device)

        # qsim numbers qubits in reverse order from cirq
        cirq_order = ops.QubitOrder.as_qubit_order(qubit_order).order_for(
            program.all_qubits())
        num_qubits = len(cirq_order)
        bitstrings = [
            format(bitstring, "b").zfill(num_qubits)[::-1]
            for bitstring in bitstrings
        ]

        options = {"i": "\n".join(bitstrings)}
        options.update(self.qsim_options)

        param_resolvers = study.to_resolvers(params)

        trials_results = []
        if _needs_trajectories(program):
            translator_fn_name = "translate_cirq_to_qtrajectory"
            simulator_fn = qsim.qtrajectory_simulate
        else:
            translator_fn_name = "translate_cirq_to_qsim"
            simulator_fn = qsim.qsim_simulate

        for prs in param_resolvers:
            solved_circuit = protocols.resolve_parameters(program, prs)
            translator_fn = getattr(solved_circuit, translator_fn_name)
            options["c"] = translator_fn(cirq_order)
            options["s"] = self.get_seed()
            amplitudes = simulator_fn(options)
            trials_results.append(amplitudes)

        return trials_results
예제 #3
0
    def compute_amplitudes_sweep(
        self,
        program: circuits.Circuit,
        bitstrings: Sequence[int],
        params: study.Sweepable,
        qubit_order: ops.QubitOrderOrList = ops.QubitOrder.DEFAULT,
    ) -> Sequence[Sequence[complex]]:
        """Computes the desired amplitudes using qsim.

      The initial state is assumed to be the all zeros state.

      Args:
          program: The circuit to simulate.
          bitstrings: The bitstrings whose amplitudes are desired, input as an
            string array where each string is formed from measured qubit values
            according to `qubit_order` from most to least significant qubit,
            i.e. in big-endian ordering.
          param_resolver: Parameters to run with the program.
          qubit_order: Determines the canonical ordering of the qubits. This is
            often used in specifying the initial state, i.e. the ordering of the
            computational basis states.

      Returns:
          List of amplitudes.
      """
        if not isinstance(program, qsimc.QSimCircuit):
            program = qsimc.QSimCircuit(program, device=program.device)

        n_qubits = len(program.all_qubits())
        # qsim numbers qubits in reverse order from cirq
        bitstrings = [
            format(bitstring, 'b').zfill(n_qubits)[::-1]
            for bitstring in bitstrings
        ]

        options = {'i': '\n'.join(bitstrings)}
        options.update(self.qsim_options)

        param_resolvers = study.to_resolvers(params)

        trials_results = []
        for prs in param_resolvers:

            solved_circuit = protocols.resolve_parameters(program, prs)

            options['c'] = solved_circuit.translate_cirq_to_qsim(qubit_order)

            amplitudes = qsim.qsim_simulate(options)
            trials_results.append(amplitudes)

        return trials_results
예제 #4
0
    def simulate_expectation_values_sweep(
        self,
        program: 'cirq.Circuit',
        observables: Union['cirq.PauliSumLike', List['cirq.PauliSumLike']],
        params: 'study.Sweepable',
        qubit_order: ops.QubitOrderOrList = ops.QubitOrder.DEFAULT,
        initial_state: Any = None,
        permit_terminal_measurements: bool = False,
    ) -> List[List[float]]:
        """Simulates the supplied circuit and calculates exact expectation
    values for the given observables on its final state.

    This method has no perfect analogy in hardware. Instead compare with
    Sampler.sample_expectation_values, which calculates estimated
    expectation values by sampling multiple times.

    Args:
        program: The circuit to simulate.
        observables: An observable or list of observables.
        param_resolver: Parameters to run with the program.
        qubit_order: Determines the canonical ordering of the qubits. This
            is often used in specifying the initial state, i.e. the
            ordering of the computational basis states.
        initial_state: The initial state for the simulation. The form of
            this state depends on the simulation implementation. See
            documentation of the implementing class for details.
        permit_terminal_measurements: If the provided circuit ends with
            measurement(s), this method will generate an error unless this
            is set to True. This is meant to prevent measurements from
            ruining expectation value calculations.

    Returns:
        A list of expectation values, with the value at index `n`
        corresponding to `observables[n]` from the input.

    Raises:
        ValueError if 'program' has terminal measurement(s) and
        'permit_terminal_measurements' is False. (Note: We cannot test this
        until Cirq's `are_any_measurements_terminal` is released.)
    """
        # TODO: replace with commented check when Cirq v0.10 is released.
        if not permit_terminal_measurements:
            raise ValueError(
                'Automatic terminal measurement checking is not supported in qsim. '
                'Please check that your circuit has no terminal measurements, then '
                'set permit_terminal_measurements=True to bypass this error.')
        # if not permit_terminal_measurements and program.are_any_measurements_terminal():
        #   raise ValueError(
        #     'Provided circuit has terminal measurements, which may '
        #     'skew expectation values. If this is intentional, set '
        #     'permit_terminal_measurements=True.'
        #   )
        if not isinstance(observables, List):
            observables = [observables]
        psumlist = [ops.PauliSum.wrap(pslike) for pslike in observables]

        ordered_qubits = ops.QubitOrder.as_qubit_order(qubit_order).order_for(
            program.all_qubits())
        ordered_qubits = list(reversed(ordered_qubits))
        num_qubits = len(ordered_qubits)
        qubit_map = {
            qubit: index
            for index, qubit in enumerate(ordered_qubits)
        }

        opsums_and_qubit_counts = []
        for psum in psumlist:
            opsum = []
            opsum_qubits = set()
            for pstr in psum:
                opstring = qsim.OpString()
                opstring.weight = pstr.coefficient
                for q, pauli in pstr.items():
                    op = pauli.on(q)
                    opsum_qubits.add(q)
                    qsimc.add_op_to_opstring(op, qubit_map, opstring)
                opsum.append(opstring)
            opsums_and_qubit_counts.append((opsum, len(opsum_qubits)))

        if initial_state is None:
            initial_state = 0
        if not isinstance(initial_state, (int, np.ndarray)):
            raise TypeError('initial_state must be an int or state vector.')
        if not isinstance(program, qsimc.QSimCircuit):
            program = qsimc.QSimCircuit(program, device=program.device)

        options = {}
        options.update(self.qsim_options)

        param_resolvers = study.to_resolvers(params)
        if isinstance(initial_state, np.ndarray):
            if initial_state.dtype != np.complex64:
                raise TypeError(
                    f'initial_state vector must have dtype np.complex64.')
            input_vector = initial_state.view(np.float32)
            if len(input_vector) != 2**num_qubits * 2:
                raise ValueError(
                    f'initial_state vector size must match number of qubits.'
                    f'Expected: {2**num_qubits * 2} Received: {len(input_vector)}'
                )

        results = []
        if _needs_trajectories(program):
            translator_fn_name = 'translate_cirq_to_qtrajectory'
            ev_simulator_fn = qsim.qtrajectory_simulate_expectation_values
        else:
            translator_fn_name = 'translate_cirq_to_qsim'
            ev_simulator_fn = qsim.qsim_simulate_expectation_values

        for prs in param_resolvers:
            solved_circuit = protocols.resolve_parameters(program, prs)
            translator_fn = getattr(solved_circuit, translator_fn_name)
            options['c'] = translator_fn(qubit_order)
            options['s'] = self.get_seed()

            if isinstance(initial_state, int):
                evs = ev_simulator_fn(options, opsums_and_qubit_counts,
                                      initial_state)
            elif isinstance(initial_state, np.ndarray):
                evs = ev_simulator_fn(options, opsums_and_qubit_counts,
                                      input_vector)
            results.append(evs)

        return results
예제 #5
0
    def simulate_sweep(
        self,
        program: circuits.Circuit,
        params: study.Sweepable,
        qubit_order: ops.QubitOrderOrList = ops.QubitOrder.DEFAULT,
        initial_state: Optional[Union[int, np.ndarray]] = None,
    ) -> List['SimulationTrialResult']:
        """Simulates the supplied Circuit.

      This method returns a result which allows access to the entire
      wave function. In contrast to simulate, this allows for sweeping
      over different parameter values.

      Args:
          program: The circuit to simulate.
          params: Parameters to run with the program.
          qubit_order: Determines the canonical ordering of the qubits. This is
            often used in specifying the initial state, i.e. the ordering of the
            computational basis states.
          initial_state: The initial state for the simulation. This can either
            be an integer representing a pure state (e.g. 11010) or a numpy
            array containing the full state vector. If none is provided, this
            is assumed to be the all-zeros state.

      Returns:
          List of SimulationTrialResults for this run, one for each
          possible parameter resolver.

      Raises:
          TypeError: if an invalid initial_state is provided.
      """
        if initial_state is None:
            initial_state = 0
        if not isinstance(initial_state, (int, np.ndarray)):
            raise TypeError('initial_state must be an int or state vector.')
        if not isinstance(program, qsimc.QSimCircuit):
            program = qsimc.QSimCircuit(program, device=program.device)

        options = {}
        options.update(self.qsim_options)

        param_resolvers = study.to_resolvers(params)
        qubits = program.all_qubits()
        num_qubits = len(qubits)
        if isinstance(initial_state, np.ndarray):
            if initial_state.dtype != np.complex64:
                raise TypeError(
                    f'initial_state vector must have dtype np.complex64.')
            input_vector = initial_state.view(np.float32)
            if len(input_vector) != 2**num_qubits * 2:
                raise ValueError(
                    f'initial_state vector size must match number of qubits.'
                    f'Expected: {2**num_qubits * 2} Received: {len(input_vector)}'
                )

        trials_results = []
        if _needs_trajectories(program):
            translator_fn_name = 'translate_cirq_to_qtrajectory'
            fullstate_simulator_fn = qsim.qtrajectory_simulate_fullstate
        else:
            translator_fn_name = 'translate_cirq_to_qsim'
            fullstate_simulator_fn = qsim.qsim_simulate_fullstate

        for prs in param_resolvers:
            solved_circuit = protocols.resolve_parameters(program, prs)
            translator_fn = getattr(solved_circuit, translator_fn_name)
            options['c'] = translator_fn(qubit_order)
            options['s'] = self.get_seed()
            ordered_qubits = ops.QubitOrder.as_qubit_order(
                qubit_order).order_for(qubits)
            # qsim numbers qubits in reverse order from cirq
            ordered_qubits = list(reversed(ordered_qubits))

            qubit_map = {
                qubit: index
                for index, qubit in enumerate(ordered_qubits)
            }

            if isinstance(initial_state, int):
                qsim_state = fullstate_simulator_fn(options, initial_state)
            elif isinstance(initial_state, np.ndarray):
                qsim_state = fullstate_simulator_fn(options, input_vector)
            assert qsim_state.dtype == np.float32
            assert qsim_state.ndim == 1
            final_state = QSimSimulatorState(qsim_state, qubit_map)
            # create result for this parameter
            # TODO: We need to support measurements.
            result = QSimSimulatorTrialResult(
                params=prs, measurements={}, final_simulator_state=final_state)
            trials_results.append(result)

        return trials_results
예제 #6
0
    def _sample_measure_results(
        self,
        program: circuits.Circuit,
        repetitions: int = 1,
    ) -> Dict[str, np.ndarray]:
        """Samples from measurement gates in the circuit.

    Note that this will execute the circuit 'repetitions' times.

    Args:
        program: The circuit to sample from.
        repetitions: The number of samples to take.

    Returns:
        A dictionary from measurement gate key to measurement
        results. Measurement results are stored in a 2-dimensional
        numpy array, the first dimension corresponding to the repetition
        and the second to the actual boolean measurement results (ordered
        by the qubits being measured.)

    Raises:
        ValueError: If there are multiple MeasurementGates with the same key,
            or if repetitions is negative.
    """
        if not isinstance(program, qsimc.QSimCircuit):
            program = qsimc.QSimCircuit(program, device=program.device)

        # Compute indices of measured qubits
        ordered_qubits = ops.QubitOrder.DEFAULT.order_for(program.all_qubits())
        num_qubits = len(ordered_qubits)

        qubit_map = {
            qubit: index
            for index, qubit in enumerate(ordered_qubits)
        }

        # Computes
        # - the list of qubits to be measured
        # - the start (inclusive) and end (exclusive) indices of each measurement
        # - a mapping from measurement key to measurement gate
        measurement_ops = [
            op for _, op, _ in program.findall_operations_with_gate_type(
                ops.MeasurementGate)
        ]
        measured_qubits = []  # type: List[ops.Qid]
        bounds = {}  # type: Dict[str, Tuple]
        meas_ops = {}  # type: Dict[str, cirq.GateOperation]
        current_index = 0
        for op in measurement_ops:
            gate = op.gate
            key = protocols.measurement_key(gate)
            meas_ops[key] = op
            if key in bounds:
                raise ValueError(
                    "Duplicate MeasurementGate with key {}".format(key))
            bounds[key] = (current_index, current_index + len(op.qubits))
            measured_qubits.extend(op.qubits)
            current_index += len(op.qubits)

        # Set qsim options
        options = {}
        options.update(self.qsim_options)

        results = {}
        for key, bound in bounds.items():
            results[key] = np.ndarray(shape=(repetitions, bound[1] - bound[0]),
                                      dtype=int)

        noisy = _needs_trajectories(program)
        if noisy:
            translator_fn_name = 'translate_cirq_to_qtrajectory'
            sampler_fn = qsim.qtrajectory_sample
        else:
            translator_fn_name = 'translate_cirq_to_qsim'
            sampler_fn = qsim.qsim_sample

        if not noisy and program.are_all_measurements_terminal(
        ) and repetitions > 1:
            print('Provided circuit has no intermediate measurements. ' +
                  'Sampling repeatedly from final state vector.')
            # Measurements must be replaced with identity gates to sample properly.
            # Simply removing them may omit qubits from the circuit.
            for i in range(len(program.moments)):
                program.moments[i] = ops.Moment(
                    op if not isinstance(op.gate, ops.MeasurementGate) else
                    [ops.IdentityGate(1).on(q) for q in op.qubits]
                    for op in program.moments[i])
            options['c'] = program.translate_cirq_to_qsim(
                ops.QubitOrder.DEFAULT)
            options['s'] = self.get_seed()
            final_state = qsim.qsim_simulate_fullstate(options, 0)
            full_results = sim.sample_state_vector(final_state.view(
                np.complex64),
                                                   range(num_qubits),
                                                   repetitions=repetitions,
                                                   seed=self._prng)

            for i in range(repetitions):
                for key, op in meas_ops.items():
                    meas_indices = [qubit_map[qubit] for qubit in op.qubits]
                    for j, q in enumerate(meas_indices):
                        results[key][i][j] = full_results[i][q]
        else:
            translator_fn = getattr(program, translator_fn_name)
            options['c'] = translator_fn(ops.QubitOrder.DEFAULT)
            for i in range(repetitions):
                options['s'] = self.get_seed()
                measurements = sampler_fn(options)
                for key, bound in bounds.items():
                    for j in range(bound[1] - bound[0]):
                        results[key][i][j] = int(measurements[bound[0] + j])

        return results
예제 #7
0
    def simulate_sweep(
        self,
        program: circuits.Circuit,
        params: study.Sweepable,
        qubit_order: ops.QubitOrderOrList = ops.QubitOrder.DEFAULT,
        initial_state: Any = None,
    ) -> List['SimulationTrialResult']:
        """Simulates the supplied Circuit.

      This method returns a result which allows access to the entire
      wave function. In contrast to simulate, this allows for sweeping
      over different parameter values.

      Args:
          program: The circuit to simulate.
          params: Parameters to run with the program.
          qubit_order: Determines the canonical ordering of the qubits. This is
            often used in specifying the initial state, i.e. the ordering of the
            computational basis states.
          initial_state: The initial state for the simulation. The form of this
            state depends on the simulation implementation.  See documentation
            of the implementing class for details.

      Returns:
          List of SimulationTrialResults for this run, one for each
          possible parameter resolver.
      """
        if not isinstance(program, qsimc.QSimCircuit):
            program = qsimc.QSimCircuit(program, device=program.device)

        options = {}
        options.update(self.qsim_options)

        param_resolvers = study.to_resolvers(params)

        trials_results = []
        for prs in param_resolvers:
            solved_circuit = protocols.resolve_parameters(program, prs)

            options['c'] = solved_circuit.translate_cirq_to_qsim(qubit_order)
            options['s'] = self.get_seed()
            ordered_qubits = ops.QubitOrder.as_qubit_order(
                qubit_order).order_for(solved_circuit.all_qubits())
            # qsim numbers qubits in reverse order from cirq
            ordered_qubits = list(reversed(ordered_qubits))

            qubit_map = {
                qubit: index
                for index, qubit in enumerate(ordered_qubits)
            }

            qsim_state = qsim.qsim_simulate_fullstate(options)
            assert qsim_state.dtype == np.float32
            assert qsim_state.ndim == 1
            final_state = QSimSimulatorState(qsim_state, qubit_map)
            # create result for this parameter
            # TODO: We need to support measurements.
            result = QSimSimulatorTrialResult(
                params=prs, measurements={}, final_simulator_state=final_state)
            trials_results.append(result)

        return trials_results
예제 #8
0
    def simulate_moment_expectation_values(
        self,
        program: cirq.Circuit,
        indexed_observables: Union[Dict[int, Union[cirq.PauliSumLike,
                                                   List[cirq.PauliSumLike]]],
                                   cirq.PauliSumLike,
                                   List[cirq.PauliSumLike], ],
        param_resolver: cirq.ParamResolver,
        qubit_order: cirq.QubitOrderOrList = cirq.QubitOrder.DEFAULT,
        initial_state: Any = None,
    ) -> List[List[float]]:
        """Calculates expectation values at each moment of a circuit.

        Args:
            program: The circuit to simulate.
            indexed_observables: A map of moment indices to an observable
                or list of observables to calculate after that moment. As a
                convenience, users can instead pass in a single observable
                or observable list to calculate after ALL moments.
            param_resolver: Parameters to run with the program.
            qubit_order: Determines the canonical ordering of the qubits. This
                is often used in specifying the initial state, i.e. the
                ordering of the computational basis states.
            initial_state: The initial state for the simulation. The form of
                this state depends on the simulation implementation. See
                documentation of the implementing class for details.
            permit_terminal_measurements: If the provided circuit ends with
                measurement(s), this method will generate an error unless this
                is set to True. This is meant to prevent measurements from
                ruining expectation value calculations.

        Returns:
            A list of expectation values for each moment m in the circuit,
            where value `n` corresponds to `indexed_observables[m][n]`.

        Raises:
            ValueError if 'program' has terminal measurement(s) and
            'permit_terminal_measurements' is False. (Note: We cannot test this
            until Cirq's `are_any_measurements_terminal` is released.)
        """
        if not isinstance(indexed_observables, Dict):
            if not isinstance(indexed_observables, List):
                indexed_observables = [(i, [indexed_observables])
                                       for i, _ in enumerate(program)]
            else:
                indexed_observables = [(i, indexed_observables)
                                       for i, _ in enumerate(program)]
        else:
            indexed_observables = [(i, obs) if isinstance(obs, List) else
                                   (i, [obs])
                                   for i, obs in indexed_observables.items()]
        indexed_observables.sort(key=lambda x: x[0])
        psum_pairs = [(i, [cirq.PauliSum.wrap(pslike) for pslike in obs_list])
                      for i, obs_list in indexed_observables]

        all_qubits = program.all_qubits()
        cirq_order = cirq.QubitOrder.as_qubit_order(qubit_order).order_for(
            all_qubits)
        qsim_order = list(reversed(cirq_order))
        num_qubits = len(qsim_order)
        qubit_map = {qubit: index for index, qubit in enumerate(qsim_order)}

        opsums_and_qcount_map = {}
        for i, psumlist in psum_pairs:
            opsums_and_qcount_map[i] = []
            for psum in psumlist:
                opsum = []
                opsum_qubits = set()
                for pstr in psum:
                    opstring = qsim.OpString()
                    opstring.weight = pstr.coefficient
                    for q, pauli in pstr.items():
                        op = pauli.on(q)
                        opsum_qubits.add(q)
                        qsimc.add_op_to_opstring(op, qubit_map, opstring)
                    opsum.append(opstring)
                opsums_and_qcount_map[i].append((opsum, len(opsum_qubits)))

        if initial_state is None:
            initial_state = 0
        if not isinstance(initial_state, (int, np.ndarray)):
            raise TypeError("initial_state must be an int or state vector.")

        # Add noise to the circuit if a noise model was provided.
        program = qsimc.QSimCircuit(
            self.noise.noisy_moments(program, sorted(all_qubits))
            if self.noise is not cirq.NO_NOISE else program, )

        options = {}
        options.update(self.qsim_options)

        param_resolver = cirq.to_resolvers(param_resolver)
        if isinstance(initial_state, np.ndarray):
            if initial_state.dtype != np.complex64:
                raise TypeError(
                    f"initial_state vector must have dtype np.complex64.")
            input_vector = initial_state.view(np.float32)
            if len(input_vector) != 2**num_qubits * 2:
                raise ValueError(
                    f"initial_state vector size must match number of qubits."
                    f"Expected: {2**num_qubits * 2} Received: {len(input_vector)}"
                )

        is_noisy = _needs_trajectories(program)
        if is_noisy:
            translator_fn_name = "translate_cirq_to_qtrajectory"
            ev_simulator_fn = (self._sim_module.
                               qtrajectory_simulate_moment_expectation_values)
        else:
            translator_fn_name = "translate_cirq_to_qsim"
            ev_simulator_fn = self._sim_module.qsim_simulate_moment_expectation_values

        solved_circuit = cirq.resolve_parameters(program, param_resolver)
        options["c"], opsum_reindex = self._translate_circuit(
            solved_circuit,
            translator_fn_name,
            cirq_order,
        )
        opsums_and_qubit_counts = []
        for m, opsum_qc in opsums_and_qcount_map.items():
            pair = (opsum_reindex[m], opsum_qc)
            opsums_and_qubit_counts.append(pair)
        options["s"] = self.get_seed()

        if isinstance(initial_state, int):
            return ev_simulator_fn(options, opsums_and_qubit_counts,
                                   initial_state)
        elif isinstance(initial_state, np.ndarray):
            return ev_simulator_fn(options, opsums_and_qubit_counts,
                                   input_vector)
예제 #9
0
    def simulate_expectation_values_sweep(
        self,
        program: cirq.Circuit,
        observables: Union[cirq.PauliSumLike, List[cirq.PauliSumLike]],
        params: cirq.Sweepable,
        qubit_order: cirq.QubitOrderOrList = cirq.QubitOrder.DEFAULT,
        initial_state: Any = None,
        permit_terminal_measurements: bool = False,
    ) -> List[List[float]]:
        """Simulates the supplied circuit and calculates exact expectation
        values for the given observables on its final state.

        This method has no perfect analogy in hardware. Instead compare with
        Sampler.sample_expectation_values, which calculates estimated
        expectation values by sampling multiple times.

        Args:
            program: The circuit to simulate.
            observables: An observable or list of observables.
            params: Parameters to run with the program.
            qubit_order: Determines the canonical ordering of the qubits. This
                is often used in specifying the initial state, i.e. the
                ordering of the computational basis states.
            initial_state: The initial state for the simulation. The form of
                this state depends on the simulation implementation. See
                documentation of the implementing class for details.
            permit_terminal_measurements: If the provided circuit ends with
                measurement(s), this method will generate an error unless this
                is set to True. This is meant to prevent measurements from
                ruining expectation value calculations.

        Returns:
            A list of expectation values, with the value at index `n`
            corresponding to `observables[n]` from the input.

        Raises:
            ValueError if 'program' has terminal measurement(s) and
            'permit_terminal_measurements' is False. (Note: We cannot test this
            until Cirq's `are_any_measurements_terminal` is released.)
        """
        if not permit_terminal_measurements and program.are_any_measurements_terminal(
        ):
            raise ValueError(
                "Provided circuit has terminal measurements, which may "
                "skew expectation values. If this is intentional, set "
                "permit_terminal_measurements=True.")
        if not isinstance(observables, List):
            observables = [observables]
        psumlist = [cirq.PauliSum.wrap(pslike) for pslike in observables]

        all_qubits = program.all_qubits()
        cirq_order = cirq.QubitOrder.as_qubit_order(qubit_order).order_for(
            all_qubits)
        qsim_order = list(reversed(cirq_order))
        num_qubits = len(qsim_order)
        qubit_map = {qubit: index for index, qubit in enumerate(qsim_order)}

        opsums_and_qubit_counts = []
        for psum in psumlist:
            opsum = []
            opsum_qubits = set()
            for pstr in psum:
                opstring = qsim.OpString()
                opstring.weight = pstr.coefficient
                for q, pauli in pstr.items():
                    op = pauli.on(q)
                    opsum_qubits.add(q)
                    qsimc.add_op_to_opstring(op, qubit_map, opstring)
                opsum.append(opstring)
            opsums_and_qubit_counts.append((opsum, len(opsum_qubits)))

        if initial_state is None:
            initial_state = 0
        if not isinstance(initial_state, (int, np.ndarray)):
            raise TypeError("initial_state must be an int or state vector.")

        # Add noise to the circuit if a noise model was provided.
        program = qsimc.QSimCircuit(
            self.noise.noisy_moments(program, sorted(all_qubits))
            if self.noise is not cirq.NO_NOISE else program, )

        options = {}
        options.update(self.qsim_options)

        param_resolvers = cirq.to_resolvers(params)
        if isinstance(initial_state, np.ndarray):
            if initial_state.dtype != np.complex64:
                raise TypeError(
                    f"initial_state vector must have dtype np.complex64.")
            input_vector = initial_state.view(np.float32)
            if len(input_vector) != 2**num_qubits * 2:
                raise ValueError(
                    f"initial_state vector size must match number of qubits."
                    f"Expected: {2**num_qubits * 2} Received: {len(input_vector)}"
                )

        results = []
        if _needs_trajectories(program):
            translator_fn_name = "translate_cirq_to_qtrajectory"
            ev_simulator_fn = self._sim_module.qtrajectory_simulate_expectation_values
        else:
            translator_fn_name = "translate_cirq_to_qsim"
            ev_simulator_fn = self._sim_module.qsim_simulate_expectation_values

        for prs in param_resolvers:
            solved_circuit = cirq.resolve_parameters(program, prs)
            options["c"], _ = self._translate_circuit(
                solved_circuit,
                translator_fn_name,
                cirq_order,
            )
            options["s"] = self.get_seed()

            if isinstance(initial_state, int):
                evs = ev_simulator_fn(options, opsums_and_qubit_counts,
                                      initial_state)
            elif isinstance(initial_state, np.ndarray):
                evs = ev_simulator_fn(options, opsums_and_qubit_counts,
                                      input_vector)
            results.append(evs)

        return results
예제 #10
0
    def simulate_sweep(
        self,
        program: cirq.Circuit,
        params: cirq.Sweepable,
        qubit_order: cirq.QubitOrderOrList = cirq.QubitOrder.DEFAULT,
        initial_state: Optional[Union[int, np.ndarray]] = None,
    ) -> List["SimulationTrialResult"]:
        """Simulates the supplied Circuit.

        This method returns a result which allows access to the entire
        wave function. In contrast to simulate, this allows for sweeping
        over different parameter values.

        Avoid using this method with `use_gpu=True` in the simulator options;
        when used with GPU this method must copy state from device to host memory
        multiple times, which can be very slow. This issue is not present in
        `simulate_expectation_values_sweep`.

        Args:
            program: The circuit to simulate.
            params: Parameters to run with the program.
            qubit_order: Determines the canonical ordering of the qubits. This is
              often used in specifying the initial state, i.e. the ordering of the
              computational basis states.
            initial_state: The initial state for the simulation. This can either
              be an integer representing a pure state (e.g. 11010) or a numpy
              array containing the full state vector. If none is provided, this
              is assumed to be the all-zeros state.

        Returns:
            List of SimulationTrialResults for this run, one for each
            possible parameter resolver.

        Raises:
            TypeError: if an invalid initial_state is provided.
        """
        if initial_state is None:
            initial_state = 0
        if not isinstance(initial_state, (int, np.ndarray)):
            raise TypeError("initial_state must be an int or state vector.")

        # Add noise to the circuit if a noise model was provided.
        all_qubits = program.all_qubits()
        program = qsimc.QSimCircuit(
            self.noise.noisy_moments(program, sorted(all_qubits))
            if self.noise is not cirq.NO_NOISE else program, )

        options = {}
        options.update(self.qsim_options)

        param_resolvers = cirq.to_resolvers(params)
        # qsim numbers qubits in reverse order from cirq
        cirq_order = cirq.QubitOrder.as_qubit_order(qubit_order).order_for(
            all_qubits)
        qsim_order = list(reversed(cirq_order))
        num_qubits = len(qsim_order)
        if isinstance(initial_state, np.ndarray):
            if initial_state.dtype != np.complex64:
                raise TypeError(
                    f"initial_state vector must have dtype np.complex64.")
            input_vector = initial_state.view(np.float32)
            if len(input_vector) != 2**num_qubits * 2:
                raise ValueError(
                    f"initial_state vector size must match number of qubits."
                    f"Expected: {2**num_qubits * 2} Received: {len(input_vector)}"
                )

        trials_results = []
        if _needs_trajectories(program):
            translator_fn_name = "translate_cirq_to_qtrajectory"
            fullstate_simulator_fn = self._sim_module.qtrajectory_simulate_fullstate
        else:
            translator_fn_name = "translate_cirq_to_qsim"
            fullstate_simulator_fn = self._sim_module.qsim_simulate_fullstate

        for prs in param_resolvers:
            solved_circuit = cirq.resolve_parameters(program, prs)

            options["c"], _ = self._translate_circuit(
                solved_circuit,
                translator_fn_name,
                cirq_order,
            )
            options["s"] = self.get_seed()
            qubit_map = {
                qubit: index
                for index, qubit in enumerate(qsim_order)
            }

            if isinstance(initial_state, int):
                qsim_state = fullstate_simulator_fn(options, initial_state)
            elif isinstance(initial_state, np.ndarray):
                qsim_state = fullstate_simulator_fn(options, input_vector)
            assert qsim_state.dtype == np.float32
            assert qsim_state.ndim == 1
            final_state = QSimSimulatorState(qsim_state, qubit_map)
            # create result for this parameter
            # TODO: We need to support measurements.
            result = QSimSimulatorTrialResult(
                params=prs, measurements={}, final_simulator_state=final_state)
            trials_results.append(result)

        return trials_results
예제 #11
0
    def _sample_measure_results(
        self,
        program: cirq.Circuit,
        repetitions: int = 1,
    ) -> Dict[str, np.ndarray]:
        """Samples from measurement gates in the circuit.

        Note that this will execute the circuit 'repetitions' times.

        Args:
            program: The circuit to sample from.
            repetitions: The number of samples to take.

        Returns:
            A dictionary from measurement gate key to measurement
            results. Measurement results are stored in a 2-dimensional
            numpy array, the first dimension corresponding to the repetition
            and the second to the actual boolean measurement results (ordered
            by the qubits being measured.)

        Raises:
            ValueError: If there are multiple MeasurementGates with the same key,
                or if repetitions is negative.
        """

        # Add noise to the circuit if a noise model was provided.
        all_qubits = program.all_qubits()
        program = qsimc.QSimCircuit(
            self.noise.noisy_moments(program, sorted(all_qubits))
            if self.noise is not cirq.NO_NOISE else program, )

        # Compute indices of measured qubits
        ordered_qubits = cirq.QubitOrder.DEFAULT.order_for(all_qubits)
        num_qubits = len(ordered_qubits)

        qubit_map = {
            qubit: index
            for index, qubit in enumerate(ordered_qubits)
        }

        # Compute:
        # - number of qubits for each measurement key.
        # - measurement ops for each measurement key.
        # - measurement info for each measurement.
        # - total number of measured bits.
        measurement_ops = [
            op for _, op, _ in program.findall_operations_with_gate_type(
                cirq.MeasurementGate)
        ]
        num_qubits_by_key: Dict[str, int] = {}
        meas_ops: Dict[str, List[cirq.GateOperation]] = {}
        meas_infos: List[MeasInfo] = []
        num_bits = 0
        for op in measurement_ops:
            gate = op.gate
            key = cirq.measurement_key_name(gate)
            meas_ops.setdefault(key, [])
            i = len(meas_ops[key])
            meas_ops[key].append(op)
            n = len(op.qubits)
            if key in num_qubits_by_key:
                if n != num_qubits_by_key[key]:
                    raise ValueError(
                        f"repeated key {key!r} with different numbers of qubits: "
                        f"{num_qubits_by_key[key]} != {n}")
            else:
                num_qubits_by_key[key] = n
            meas_infos.append(
                MeasInfo(
                    key=key,
                    idx=i,
                    invert_mask=gate.full_invert_mask(),
                    start=num_bits,
                    end=num_bits + n,
                ))
            num_bits += n

        # Set qsim options
        options = {**self.qsim_options}

        results = {
            key: np.ndarray(shape=(repetitions, len(meas_ops[key]), n),
                            dtype=int)
            for key, n in num_qubits_by_key.items()
        }

        noisy = _needs_trajectories(program)
        if not noisy and program.are_all_measurements_terminal(
        ) and repetitions > 1:
            # Measurements must be replaced with identity gates to sample properly.
            # Simply removing them may omit qubits from the circuit.
            for i in range(len(program.moments)):
                program.moments[i] = cirq.Moment(
                    op if not isinstance(op.gate, cirq.MeasurementGate) else
                    [cirq.IdentityGate(1).on(q) for q in op.qubits]
                    for op in program.moments[i])
            translator_fn_name = "translate_cirq_to_qsim"
            options["c"], _ = self._translate_circuit(
                program,
                translator_fn_name,
                cirq.QubitOrder.DEFAULT,
            )
            options["s"] = self.get_seed()
            raw_results = self._sim_module.qsim_sample_final(
                options, repetitions)
            full_results = np.array([[
                bool(result & (1 << q)) for q in reversed(range(num_qubits))
            ] for result in raw_results])

            for key, oplist in meas_ops.items():
                for i, op in enumerate(oplist):
                    meas_indices = [qubit_map[qubit] for qubit in op.qubits]
                    invert_mask = op.gate.full_invert_mask()
                    # Apply invert mask to re-ordered results
                    results[
                        key][:,
                             i, :] = full_results[:,
                                                  meas_indices] ^ invert_mask

        else:
            if noisy:
                translator_fn_name = "translate_cirq_to_qtrajectory"
                sampler_fn = self._sim_module.qtrajectory_sample
            else:
                translator_fn_name = "translate_cirq_to_qsim"
                sampler_fn = self._sim_module.qsim_sample

            options["c"], _ = self._translate_circuit(
                program,
                translator_fn_name,
                cirq.QubitOrder.DEFAULT,
            )
            measurements = np.empty(shape=(repetitions, num_bits), dtype=int)
            for i in range(repetitions):
                options["s"] = self.get_seed()
                measurements[i] = sampler_fn(options)

            for m in meas_infos:
                results[m.key][:, m.idx, :] = (measurements[:, m.start:m.end]
                                               ^ m.invert_mask)

        return results
예제 #12
0
    def _sample_measure_results(
        self,
        program: circuits.Circuit,
        repetitions: int = 1,
    ) -> Dict[str, np.ndarray]:
        """Samples from measurement gates in the circuit.

    Note that this will execute the circuit 'repetitions' times.

    Args:
        program: The circuit to sample from.
        repetitions: The number of samples to take.

    Returns:
        A dictionary from measurement gate key to measurement
        results. Measurement results are stored in a 2-dimensional
        numpy array, the first dimension corresponding to the repetition
        and the second to the actual boolean measurement results (ordered
        by the qubits being measured.)

    Raises:
        ValueError: If there are multiple MeasurementGates with the same key,
            or if repetitions is negative.
    """
        if not isinstance(program, qsimc.QSimCircuit):
            program = qsimc.QSimCircuit(program, device=program.device)

        if program.are_all_measurements_terminal() and repetitions > 1:
            print('Provided circuit has no intermediate measurements. ' +
                  'It may be faster to sample from the final state vector. ' +
                  'Continuing with one-by-one sampling.')

        # Compute indices of measured qubits
        ordered_qubits = ops.QubitOrder.DEFAULT.order_for(program.all_qubits())
        ordered_qubits = list(reversed(ordered_qubits))

        qubit_map = {
            qubit: index
            for index, qubit in enumerate(ordered_qubits)
        }

        # Computes
        # - the list of qubits to be measured
        # - the start (inclusive) and end (exclusive) indices of each measurement
        # - a mapping from measurement key to measurement gate
        measurement_ops = [
            op for _, op, _ in program.findall_operations_with_gate_type(
                ops.MeasurementGate)
        ]
        measured_qubits = []  # type: List[ops.Qid]
        bounds = {}  # type: Dict[str, Tuple]
        meas_ops = {}  # type: Dict[str, cirq.MeasurementGate]
        current_index = 0
        for op in measurement_ops:
            gate = op.gate
            key = protocols.measurement_key(gate)
            meas_ops[key] = gate
            if key in bounds:
                raise ValueError(
                    "Duplicate MeasurementGate with key {}".format(key))
            bounds[key] = (current_index, current_index + len(op.qubits))
            measured_qubits.extend(op.qubits)
            current_index += len(op.qubits)

        indices = [qubit_map[qubit] for qubit in measured_qubits]

        # Set qsim options
        options = {}
        options.update(self.qsim_options)
        options['c'] = program.translate_cirq_to_qsim(ops.QubitOrder.DEFAULT)

        results = {}
        for key, bound in bounds.items():
            results[key] = np.ndarray(shape=(repetitions, bound[1] - bound[0]),
                                      dtype=int)

        for i in range(repetitions):
            options['s'] = self.get_seed()
            measurements = qsim.qsim_sample(options)
            for key, bound in bounds.items():
                for j in range(bound[1] - bound[0]):
                    results[key][i][j] = int(measurements[bound[0] + j])

        return results