def random_rotations_between_grid_interaction_layers_circuit( qubits: Iterable['cirq.GridQubit'], depth: int, *, # forces keyword arguments two_qubit_op_factory: Callable[ ['cirq.GridQubit', 'cirq.GridQubit', 'np.random.RandomState'], 'cirq.OP_TREE' ] = lambda a, b, _: ops.CZPowGate()(a, b), pattern: Sequence[GridInteractionLayer] = GRID_STAGGERED_PATTERN, single_qubit_gates: Sequence['cirq.Gate'] = ( ops.X ** 0.5, ops.Y ** 0.5, ops.PhasedXPowGate(phase_exponent=0.25, exponent=0.5), ), add_final_single_qubit_layer: bool = True, seed: 'cirq.RANDOM_STATE_OR_SEED_LIKE' = None, ) -> 'cirq.Circuit': """Generate a random quantum circuit of a particular form. This construction is based on the circuits used in the paper https://www.nature.com/articles/s41586-019-1666-5. The generated circuit consists of a number of "cycles", this number being specified by `depth`. Each cycle is actually composed of two sub-layers: a layer of single-qubit gates followed by a layer of two-qubit gates, controlled by their respective arguments, see below. The pairs of qubits in a given entangling layer is controlled by the `pattern` argument, see below. Args: qubits: The qubits to use. depth: The number of cycles. two_qubit_op_factory: A callable that returns a two-qubit operation. These operations will be generated with calls of the form `two_qubit_op_factory(q0, q1, prng)`, where `prng` is the pseudorandom number generator. pattern: A sequence of GridInteractionLayers, each of which determine which pairs of qubits are entangled. The layers in a pattern are iterated through sequentially, repeating until `depth` is reached. single_qubit_gates: Single-qubit gates are selected randomly from this sequence. No qubit is acted upon by the same single-qubit gate in consecutive cycles. If only one choice of single-qubit gate is given, then this constraint is not enforced. add_final_single_qubit_layer: Whether to include a final layer of single-qubit gates after the last cycle. seed: A seed or random state to use for the pseudorandom number generator. """ prng = value.parse_random_state(seed) qubits = list(qubits) coupled_qubit_pairs = _coupled_qubit_pairs(qubits) circuit = circuits.Circuit() previous_single_qubit_layer = ops.Moment() single_qubit_layer_factory = _single_qubit_gates_arg_to_factory( single_qubit_gates=single_qubit_gates, qubits=qubits, prng=prng ) for i in range(depth): single_qubit_layer = single_qubit_layer_factory.new_layer(previous_single_qubit_layer) circuit += single_qubit_layer two_qubit_layer = _two_qubit_layer( coupled_qubit_pairs, two_qubit_op_factory, pattern[i % len(pattern)], prng ) circuit += two_qubit_layer previous_single_qubit_layer = single_qubit_layer if add_final_single_qubit_layer: circuit += single_qubit_layer_factory.new_layer(previous_single_qubit_layer) return circuit
def new_layer(self, previous_single_qubit_layer: 'cirq.Moment') -> 'cirq.Moment': return ops.Moment(v.on(q) for q, v in self.fixed_single_qubit_layer.items())
def random_circuit( qubits: Union[Sequence[ops.Qid], int], n_moments: int, op_density: float, gate_domain: Optional[Dict[ops.Gate, int]] = None, random_state: 'cirq.RANDOM_STATE_OR_SEED_LIKE' = None) -> Circuit: """Generates a random circuit. Args: qubits: If a sequence of qubits, then these are the qubits that the circuit should act on. Because the qubits on which an operation acts are chosen randomly, not all given qubits may be acted upon. If an int, then this number of qubits will be automatically generated, and the qubits will be `cirq.NamedQubits` with names given by the integers in `range(qubits)`. n_moments: The number of moments in the generated circuit. op_density: The probability that a gate is selected to operate on randomly selected qubits. Note that this is not the expected number of qubits that are acted on, since there are cases where the number of qubits that a gate acts on does not evenly divide the total number of qubits. gate_domain: The set of gates to choose from, specified as a dictionary where each key is a gate and the value of the key is the number of qubits the gate acts on. If not provided, the default gate domain is {X, Y, Z, H, S, T, CNOT, CZ, SWAP, ISWAP, CZPowGate()}. Only gates which act on a number of qubits less than len(qubits) (or qubits if provided as an int) are selected from the gate domain. random_state: Random state or random state seed. Raises: ValueError: * op_density is not in (0, 1]. * gate_domain is empty. * qubits is an int less than 1 or an empty sequence. Returns: The randomly generated Circuit. """ if not 0 < op_density <= 1: raise ValueError(f'op_density must be in (0, 1] but was {op_density}.') if gate_domain is None: gate_domain = DEFAULT_GATE_DOMAIN if not gate_domain: raise ValueError('gate_domain must be non-empty.') if isinstance(qubits, int): qubits = tuple(ops.NamedQubit(str(i)) for i in range(qubits)) n_qubits = len(qubits) if n_qubits < 1: raise ValueError('At least one qubit must be specified.') gate_domain = {k: v for k, v in gate_domain.items() if v <= n_qubits} if not gate_domain: raise ValueError(f'After removing gates that act on less than ' f'{n_qubits} qubits, gate_domain had no gates.') max_arity = max(gate_domain.values()) prng = value.parse_random_state(random_state) moments: List[ops.Moment] = [] gate_arity_pairs = sorted(gate_domain.items(), key=repr) num_gates = len(gate_domain) for _ in range(n_moments): operations = [] free_qubits = set(qubits) while len(free_qubits) >= max_arity: gate, arity = gate_arity_pairs[prng.randint(num_gates)] op_qubits = prng.choice(sorted(free_qubits), size=arity, replace=False) free_qubits.difference_update(op_qubits) if prng.rand() <= op_density: operations.append(gate(*op_qubits)) moments.append(ops.Moment(operations)) return Circuit(moments)
def random_rotations_between_two_qubit_circuit( q0: 'cirq.Qid', q1: 'cirq.Qid', depth: int, two_qubit_op_factory: Callable[ ['cirq.Qid', 'cirq.Qid', 'np.random.RandomState'], 'cirq.OP_TREE' ] = lambda a, b, _: ops.CZPowGate()(a, b), single_qubit_gates: Sequence['cirq.Gate'] = ( ops.X ** 0.5, ops.Y ** 0.5, ops.PhasedXPowGate(phase_exponent=0.25, exponent=0.5), ), add_final_single_qubit_layer: bool = True, seed: 'cirq.RANDOM_STATE_OR_SEED_LIKE' = None, ) -> 'cirq.Circuit': """Generate a random two-qubit quantum circuit. This construction uses a similar structure to those in the paper https://www.nature.com/articles/s41586-019-1666-5. The generated circuit consists of a number of "cycles", this number being specified by `depth`. Each cycle is actually composed of two sub-layers: a layer of single-qubit gates followed by a layer of two-qubit gates, controlled by their respective arguments, see below. Args: q0: The first qubit q1: The second qubit depth: The number of cycles. two_qubit_op_factory: A callable that returns a two-qubit operation. These operations will be generated with calls of the form `two_qubit_op_factory(q0, q1, prng)`, where `prng` is the pseudorandom number generator. single_qubit_gates: Single-qubit gates are selected randomly from this sequence. No qubit is acted upon by the same single-qubit gate in consecutive cycles. If only one choice of single-qubit gate is given, then this constraint is not enforced. add_final_single_qubit_layer: Whether to include a final layer of single-qubit gates after the last cycle (subject to the same non-consecutivity constraint). seed: A seed or random state to use for the pseudorandom number generator. """ prng = value.parse_random_state(seed) circuit = circuits.Circuit() previous_single_qubit_layer = ops.Moment() single_qubit_layer_factory = _single_qubit_gates_arg_to_factory( single_qubit_gates=single_qubit_gates, qubits=(q0, q1), prng=prng ) for _ in range(depth): single_qubit_layer = single_qubit_layer_factory.new_layer(previous_single_qubit_layer) circuit += single_qubit_layer circuit += two_qubit_op_factory(q0, q1, prng) previous_single_qubit_layer = single_qubit_layer if add_final_single_qubit_layer: circuit += single_qubit_layer_factory.new_layer(previous_single_qubit_layer) return circuit
def _apply_amplitude_damp_noise(duration, t1, moments, system_qubits): moments.append( ops.Moment( ops.amplitude_damp(1 - np.exp(-duration / t1)).on_each(system_qubits)))
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()) 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) if 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) full_results = sim.sample_state_vector(final_state, range(len(ordered_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: options['c'] = program.translate_cirq_to_qsim( ops.QubitOrder.DEFAULT) 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
def random_circuit(qubits: Union[Sequence[ops.Qid], int], n_moments: int, op_density: float, gate_domain: Optional[Dict[ops.Gate, int]] = None, random_state: value.RANDOM_STATE_LIKE = None) -> Circuit: """Generates a random circuit. Args: qubits: If a sequence of qubits, then these are the qubits that the circuit should act on. Because the qubits on which an operation acts are chosen randomly, not all given qubits may be acted upon. If an int, then this number of qubits will be automatically generated. n_moments: the number of moments in the generated circuit. op_density: the expected proportion of qubits that are acted on in any moment. gate_domain: The set of gates to choose from, with a specified arity. random_state: Random state or random state seed. Raises: ValueError: * op_density is not in (0, 1). * gate_domain is empty. * qubits is an int less than 1 or an empty sequence. Returns: The randomly generated Circuit. """ if not 0 < op_density < 1: raise ValueError('op_density must be in (0, 1).') if gate_domain is None: gate_domain = DEFAULT_GATE_DOMAIN if not gate_domain: raise ValueError('gate_domain must be non-empty') max_arity = max(gate_domain.values()) if isinstance(qubits, int): qubits = tuple(ops.NamedQubit(str(i)) for i in range(qubits)) n_qubits = len(qubits) if n_qubits < 1: raise ValueError('At least one qubit must be specified.') prng = value.parse_random_state(random_state) moments: List[ops.Moment] = [] gate_arity_pairs = sorted(gate_domain.items(), key=repr) num_gates = len(gate_domain) for _ in range(n_moments): operations = [] free_qubits = set(qubits) while len(free_qubits) >= max_arity: gate, arity = gate_arity_pairs[prng.randint(num_gates)] op_qubits = prng.choice(sorted(free_qubits), size=arity, replace=False) free_qubits.difference_update(op_qubits) if prng.rand() <= op_density: operations.append(gate(*op_qubits)) moments.append(ops.Moment(operations)) return Circuit(moments)
def _noisy_operation_impl_moment( self, operation: 'cirq.Operation') -> 'cirq.OP_TREE': return self.noisy_moment(ops.Moment([operation]), operation.qubits)
def map_moment(self, moment: ops.Moment) -> ops.Moment: return ops.Moment(self.map_operation(op) for op in moment.operations)
def noisy_moment(self, moment: 'cirq.Moment', system_qubits: Sequence['cirq.Qid']): return [ moment, ops.Moment([self.qubit_noise_gate(q) for q in system_qubits]) ]
def merge_operations( circuit: CIRCUIT_TYPE, merge_func: Callable[[ops.Operation, ops.Operation], Optional[ops.Operation]], ) -> CIRCUIT_TYPE: """Merges operations in a circuit by calling `merge_func` iteratively on operations. Two operations op1 and op2 are merge-able if - There is no other operations between op1 and op2 in the circuit - is_subset(op1.qubits, op2.qubits) or is_subset(op2.qubits, op1.qubits) The `merge_func` is a callable which, given two merge-able operations op1 and op2, decides whether they should be merged into a single operation or not. If not, it should return None, else it should return the single merged operations `op`. The method iterates on the input circuit moment-by-moment from left to right and attempts to repeatedly merge each operation in the latest moment with all the corresponding merge-able operations to it's left. If op1 and op2 are merged, both op1 and op2 are deleted from the circuit and the resulting `merged_op` is inserted at the index corresponding to the larger of op1/op2. If both op1 and op2 act on the same number of qubits, `merged_op` is inserted in the smaller moment index to minimize circuit depth. The number of calls to `merge_func` is O(N), where N = Total no. of operations, because: - Every time the `merge_func` returns a new operation, the number of operations in the circuit reduce by 1 and hence this can happen at most O(N) times - Every time the `merge_func` returns None, the current operation is inserted into the frontier and we go on to process the next operation, which can also happen at-most O(N) times. Args: circuit: Input circuit to apply the transformations on. The input circuit is not mutated. merge_func: Callable to determine whether two merge-able operations in the circuit should be merged. If the operations can be merged, the callable should return the merged operation, else None. Returns: Copy of input circuit with merged operations. Raises: ValueError if the merged operation acts on new qubits outside the set of qubits corresponding to the original operations to be merged. """ def apply_merge_func(op1: ops.Operation, op2: ops.Operation) -> Optional[ops.Operation]: new_op = merge_func(op1, op2) qubit_set = frozenset(op1.qubits + op2.qubits) if new_op is not None and not qubit_set.issuperset(new_op.qubits): raise ValueError( f"Merged operation {new_op} must act on a subset of qubits of " f"original operations {op1} and {op2}") return new_op ret_circuit = circuits.Circuit() for current_moment in circuit: new_moment = ops.Moment() for op in sorted(current_moment.operations, key=lambda op: op.qubits): op_qs = set(op.qubits) idx = ret_circuit.prev_moment_operating_on(tuple(op_qs)) if idx is not None and op_qs.issubset( ret_circuit[idx][op_qs].operations[0].qubits): # Case-1: Try to merge op with the larger operation on the left. left_op = ret_circuit[idx][op_qs].operations[0] new_op = apply_merge_func(left_op, op) if new_op is not None: ret_circuit.batch_replace([(idx, left_op, new_op)]) else: new_moment = new_moment.with_operation(op) continue while idx is not None and len(op_qs) > 0: # Case-2: left_ops will merge right into `op` whenever possible. for left_op in ret_circuit[idx][op_qs].operations: is_merged = False if op_qs.issuperset(left_op.qubits): # Try to merge left_op into op new_op = apply_merge_func(left_op, op) if new_op is not None: ret_circuit.batch_remove([(idx, left_op)]) op, is_merged = new_op, True if not is_merged: op_qs -= frozenset(left_op.qubits) idx = ret_circuit.prev_moment_operating_on(tuple(op_qs)) new_moment = new_moment.with_operation(op) ret_circuit += new_moment return _to_target_circuit_type(ret_circuit, circuit)