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
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
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
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
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
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
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
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)
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
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
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
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