def prepare_into_buffer(k: int): linalg.targeted_left_multiply( left_matrix=kraus_tensors[k], right_target=self._state_vector, target_axes=axes, out=self._buffer, )
def _compute_samples_display_value(display: ops.SamplesDisplay, state: np.ndarray, qubit_order: ops.QubitOrder, qubit_map: Dict[ops.Qid, int]): n = len(qubit_map) state = np.reshape(state, (2,) * n * 2) basis_change = ops.flatten_op_tree(display.measurement_basis_change()) for op in basis_change: # TODO: Use apply_channel similar to apply_unitary. indices = [qubit_map[qubit] for qubit in op.qubits] gate = cast(ops.GateOperation, op).gate unitary = protocols.unitary(gate) krauss_tensor = np.reshape(unitary, (2,) * gate.num_qubits() * 2) state = linalg.targeted_left_multiply(krauss_tensor, state, indices) # TODO add a test that fails if the below is not performed state = linalg.targeted_left_multiply( np.conjugate(krauss_tensor), state, [x + n for x in indices]) state = state.reshape((2**n, 2**n)) indices = [qubit_map[qubit] for qubit in display.qubits] samples = density_matrix_utils.sample_density_matrix( state, indices, display.num_samples) return display.value_derived_from_samples(samples)
def prepare_into_buffer(k: int): linalg.targeted_left_multiply( left_matrix=kraus_tensors[k], right_target=args.target_tensor, target_axes=args.axes, out=args.available_buffer, )
def apply_mixture(self, action: Any, axes: Sequence[int], prng) -> Optional[int]: """Apply mixture to state. Args: action: The value with a mixture to apply. axes: The axes on which to apply the mixture. prng: The pseudo random number generator to use. Returns: The mixture index if the operation succeeded, otherwise None. """ mixture = protocols.mixture(action, default=None) if mixture is None: return None probabilities, unitaries = zip(*mixture) index = prng.choice(range(len(unitaries)), p=probabilities) shape = protocols.qid_shape(action) * 2 unitary = unitaries[index].astype( self._state_vector.dtype).reshape(shape) linalg.targeted_left_multiply(unitary, self._state_vector, axes, out=self._buffer) self._swap_target_tensor_for(self._buffer) return index
def _apply_unitary_from_matrix_strat( val: np.ndarray, args: 'ApplyMixtureArgs', is_density_matrix: bool) -> Optional[np.ndarray]: """Used to enact mixture tuples that are given as (probability, np.ndarray) If `val` does not support `apply_unitary` returns None. """ qid_shape = tuple(args.target_tensor.shape[i] for i in args.left_axes) matrix_tensor = np.reshape(val.astype(args.target_tensor.dtype), qid_shape * 2) linalg.targeted_left_multiply(matrix_tensor, args.target_tensor, args.left_axes, out=args.auxiliary_buffer0) if not is_density_matrix: return args.auxiliary_buffer0 # No need to transpose as we are acting on the tensor # representation of matrix, so transpose is done for us. linalg.targeted_left_multiply( np.conjugate(matrix_tensor), args.auxiliary_buffer0, cast(Tuple[int], args.right_axes), out=args.target_tensor, ) return args.target_tensor
def _strat_apply_unitary_from_unitary( unitary_value: Any, args: ApplyUnitaryArgs) -> Optional[np.ndarray]: # Check for magic method. method = getattr(unitary_value, '_unitary_', None) if method is None: return NotImplemented # Attempt to get the unitary matrix. matrix = method() if matrix is NotImplemented or matrix is None: return matrix val_qid_shape = qid_shape_protocol.qid_shape(unitary_value, default=(2, ) * len(args.axes)) sub_args = args._for_operation_with_qid_shape(range(len(val_qid_shape)), val_qid_shape) matrix = matrix.astype(sub_args.target_tensor.dtype) if len(val_qid_shape) == 1 and val_qid_shape[0] <= 2: # Special case for single-qubit, 2x2 or 1x1 operations. # np.einsum is faster for larger cases. subspaces = [(..., level) for level in range(val_qid_shape[0])] sub_result = linalg.apply_matrix_to_slices( sub_args.target_tensor, matrix, subspaces, out=sub_args.available_buffer) else: # General case via np.einsum. sub_result = linalg.targeted_left_multiply( matrix.reshape(val_qid_shape * 2), sub_args.target_tensor, sub_args.axes, out=sub_args.available_buffer) return _incorporate_result_into_target(args, sub_args, sub_result)
def _strat_apply_unitary_from_unitary( unitary_value: Any, args: ApplyUnitaryArgs) -> Optional[np.ndarray]: # Check for magic method. method = getattr(unitary_value, '_unitary_', None) if method is None: return NotImplemented # Attempt to get the unitary matrix. matrix = method() if matrix is NotImplemented or matrix is None: return matrix # Special case for single-qubit operations. if matrix.shape == (2, 2): zero = args.subspace_index(0) one = args.subspace_index(1) return linalg.apply_matrix_to_slices(args.target_tensor, matrix, [zero, one], out=args.available_buffer) # General case via np.einsum. return linalg.targeted_left_multiply(matrix.astype( args.target_tensor.dtype).reshape((2, ) * (2 * len(args.axes))), args.target_tensor, args.axes, out=args.available_buffer)
def _strat_act_on_state_vector_from_mixture( action: Any, args: 'cirq.ActOnStateVectorArgs') -> bool: mixture = protocols.mixture(action, default=None) if mixture is None: return NotImplemented probabilities, unitaries = zip(*mixture) index = args.prng.choice(range(len(unitaries)), p=probabilities) shape = protocols.qid_shape(action) * 2 unitary = unitaries[index].astype(args.target_tensor.dtype).reshape(shape) linalg.targeted_left_multiply(unitary, args.target_tensor, args.axes, out=args.available_buffer) args.swap_target_tensor_for(args.available_buffer) return True
def _apply_krauss_multi_qubit(krauss: Union[Tuple[Any], Sequence[Any]], args: 'ApplyChannelArgs') -> np.ndarray: """Use numpy's einsum to apply a multi-qubit channel.""" for krauss_op in krauss: np.copyto(dst=args.target_tensor, src=args.auxiliary_buffer0) krauss_tensor = np.reshape(krauss_op.astype(args.target_tensor.dtype), (2, ) * len(args.left_axes) * 2) linalg.targeted_left_multiply(krauss_tensor, args.target_tensor, args.left_axes, out=args.auxiliary_buffer1) # No need to transpose as we are acting on the tensor # representation of matrix, so transpose is done for us. linalg.targeted_left_multiply(np.conjugate(krauss_tensor), args.auxiliary_buffer1, args.right_axes, out=args.target_tensor) args.out_buffer += args.target_tensor return args.out_buffer
def _strat_act_on_state_vector_from_mixture( action: Any, args: 'cirq.ActOnStateVectorArgs', qubits: Sequence['cirq.Qid']) -> bool: mixture = protocols.mixture(action, default=None) if mixture is None: return NotImplemented probabilities, unitaries = zip(*mixture) index = args.prng.choice(range(len(unitaries)), p=probabilities) shape = protocols.qid_shape(action) * 2 unitary = unitaries[index].astype(args.target_tensor.dtype).reshape(shape) linalg.targeted_left_multiply(unitary, args.target_tensor, args.get_axes(qubits), out=args.available_buffer) args.swap_target_tensor_for(args.available_buffer) if protocols.is_measurement(action): key = protocols.measurement_key_name(action) args._classical_data.record_channel_measurement(key, index) return True
def _simulate_mixture(self, op: ops.Operation, data: _StateAndBuffer, indices: List[int]) -> None: """Simulate an op that is a mixtures of unitaries.""" probs, unitaries = zip(*protocols.mixture(op)) # We work around numpy barfing on choosing from a list of # numpy arrays (which is not `one-dimensional`) by selecting # the index of the unitary. index = np.random.choice(range(len(unitaries)), p=probs) shape = (2,) * (2 * len(indices)) unitary = unitaries[index].astype(self._dtype).reshape(shape) result = linalg.targeted_left_multiply(unitary, data.state, indices, out=data.buffer) data.buffer = data.state data.state = result
def apply_unitary( unitary_value: Any, args: ApplyUnitaryArgs, default: TDefault = RaiseTypeErrorIfNotProvided ) -> Union[np.ndarray, TDefault]: """High performance left-multiplication of a unitary effect onto a tensor. If `unitary_value` defines an `_apply_unitary_` method, that method will be used to apply `unitary_value`'s unitary effect to the target tensor. Otherwise, if `unitary_value` defines a `_unitary_` method, its unitary matrix will be retrieved and applied using a generic method. Otherwise the application fails, and either an exception is raised or the specified default value is returned. Args: unitary_value: The value with a unitary effect to apply to the target. args: A mutable `cirq.ApplyUnitaryArgs` object describing the target tensor, available workspace, and axes to operate on. The attributes of this object will be mutated as part of computing the result. default: What should be returned if `unitary_value` doesn't have a unitary effect. If not specified, a TypeError is raised instead of returning a default value. Returns: If the receiving object is not able to apply its unitary effect, the specified default value is returned (or a TypeError is raised). If this occurs, then `target_tensor` should not have been mutated. If the receiving object was able to work inline, directly mutating target_tensor it will return target_tensor. The caller is responsible for checking if the result is target_tensor. If the receiving object wrote its output over available_buffer, the result will be available_buffer. The caller is responsible for checking if the result is available_buffer (and e.g. swapping the buffer for the target tensor before the next call). The receiving object may also write its output over a new buffer that it created, in which case that new array is returned. Raises: TypeError: `unitary_value` doesn't have a unitary effect and `default` wasn't specified. """ # Check if the specialized method is present. func = getattr(unitary_value, '_apply_unitary_', None) if func is not None: result = func(args) if result is not NotImplemented and result is not None: return result # Fallback to using the object's _unitary_ matrix. matrix = unitary(unitary_value, None) if matrix is not None: # Special case for single-qubit operations. if matrix.shape == (2, 2): zero = args.subspace_index(0) one = args.subspace_index(1) return linalg.apply_matrix_to_slices(args.target_tensor, matrix, [zero, one], out=args.available_buffer) # Fallback to np.einsum for the general case. return linalg.targeted_left_multiply(matrix.astype( args.target_tensor.dtype).reshape((2, ) * (2 * len(args.axes))), args.target_tensor, args.axes, out=args.available_buffer) # Don't know how to apply. Fallback to specified default behavior. if default is not RaiseTypeErrorIfNotProvided: return default raise TypeError( "object of type '{}' has no _apply_unitary_ or _unitary_ methods " "(or they returned None or NotImplemented).".format( type(unitary_value)))
def apply_unitary_to_tensor( val: Any, target_tensor: np.ndarray, available_buffer: np.ndarray, axes: Sequence[int], default: TDefault = RaiseTypeErrorIfNotProvided ) -> Union[np.ndarray, TDefault]: """High performance left-multiplication of a unitary effect onto a tensor. If `val` defines an _apply_unitary_to_tensor_ method, that method will be used to apply `val`'s unitary effect to the target tensor. Otherwise, if `val` defines a _unitary_ method, its unitary matrix will be retrieved and applied using a generic method. Otherwise the application fails, and either an exception is raised or the specified default value is returned. The target may represent a wavefunction, a unitary matrix, or some other tensor. Implementations will work in all of these cases as long as they correctly focus on only operating on the given axes. See also: `cirq.slice_for_qubits_equal_to(axes, int)`, which does the correct thing in all these cases. Args: val: The value with a unitary effect to apply to the target tensor. target_tensor: The input tensor that needs to be left-multiplied by the unitary effect of `val`. Note that this value may be mutated inline into the output. The tensor will have the shape (2, 2, 2, ..., 2). target_tensor may correspond to a multi-qubit superposition (with each axis being a qubit), a multi-qubit unitary transformation (with some axes being qubit inputs and others being qubit outputs), or some other concept. available_buffer: Pre-allocated workspace with the same shape and dtype as the target tensor. Note that the output may be written into this buffer. axes: Which axes the unitary effect is being applied to (e.g. the qubits that the gate is operating on). For example, a CNOT being applied to qubits #4 and #2 of a circuit would result in axes=(4, 2). default: What should be returned if `val` doesn't have a unitary effect. If not specified, a TypeError is raised instead of returning a default value. Returns: If the receiving object is not able to apply its unitary effect, the specified default value is returned (or a TypeError is raised). If the receiving object was able to work inline, directly mutating target_tensor it will return target_tensor. The caller is responsible for checking if the result is target_tensor. If the receiving object wrote its output over available_buffer, the result will be available_buffer. The caller is responsible for checking if the result is available_buffer (and e.g. swapping the buffer for the target tensor before the next call). The receiving object may also write its output over a new buffer that it created, in which case that new array is returned. Raises: TypeError: `val` doesn't have a unitary effect and `default` wasn't specified. """ # Check if the specialized method is present. getter = getattr(val, '_apply_unitary_to_tensor_', None) if getter is not None: result = getter(target_tensor, available_buffer, axes) if result is not NotImplemented: return result # Fallback to using the object's _unitary_ matrix. matrix = unitary(val, None) if matrix is not None: # Special case for single-qubit operations. if matrix.shape == (2, 2): zero = linalg.slice_for_qubits_equal_to(axes, 0) one = linalg.slice_for_qubits_equal_to(axes, 1) return linalg.apply_matrix_to_slices(target_tensor, matrix, [zero, one], out=available_buffer) # Fallback to np.einsum for the general case. return linalg.targeted_left_multiply(matrix.astype( target_tensor.dtype).reshape((2, ) * (2 * len(axes))), target_tensor, axes, out=available_buffer) # Don't know how to apply. Fallback to specified default behavior. if default is not RaiseTypeErrorIfNotProvided: return default raise TypeError("object of type '{}' " "has no _apply_unitary_to_tensor_ " "or _unitary_ methods " "(or they returned NotImplemented).".format(type(val)))
def _circuit_as_layers(circuit: circuits.Circuit, grouping: _QubitGrouping) -> List[_TransformsThenCzs]: """Transforms a circuit into a series of GroupMatrix+CZ layers. Args: circuit: The circuit to transform. grouping: How the circuit's qubits are combined into groups. Returns: A list of layers. Each layer has a matrix to apply to each group of qubits, and a list of CZs to apply to pairs of qubits crossing between groups. """ frontier = {q: 0 for q in circuit.all_qubits()} layers = [] while True: # Pull within-group operations into per-group matrices. any_group_matrices = False group_matrices = [] for g in grouping.groups: # Scan for reachable operations contained within the group qubits. start_frontier = {q: frontier[q] for q in g} end_frontier = circuit.reachable_frontier_from(start_frontier) mergeable_ops = circuit.findall_operations_between( start_frontier, end_frontier) # Advance frontier. for q, v in end_frontier.items(): frontier[q] = v # Fold reachable operations into a single group matrix. group_matrix = np.eye(1 << len(g)).reshape((2, 2) * len(g)) if mergeable_ops: any_group_matrices = True for _, op in mergeable_ops: group_matrix = linalg.targeted_left_multiply( left_matrix=protocols.unitary(op).reshape( (2, 2) * len(op.qubits)), right_target=group_matrix, target_axes=[grouping.loc(q)[1] for q in op.qubits]) group_matrices.append( np.transpose(group_matrix.reshape(1 << len(g), 1 << len(g)))) # Scan for reachable CZ operations between groups. end_frontier = circuit.reachable_frontier_from( frontier, is_blocker=lambda op: grouping.all_in_same_group(*op.qubits)) cz_ops = circuit.findall_operations_between(frontier, end_frontier) # Advance frontier. frontier = end_frontier # List out qubit index pairs for each CZ. cz_indices = [] for _, cz in cz_ops: a, b = cz.qubits assert cz == ops.CZ(a, b) cz_indices.append((grouping.ind(a), grouping.ind(b))) # Combine group and CZ operations into a simulation layer. if not any_group_matrices and not cz_indices: break layer = _TransformsThenCzs(group_matrices=group_matrices, cz_indices=cz_indices) layers.append(layer) # We should have processed the whole circuit. assert frontier == {q: len(circuit) for q in circuit.all_qubits()} return layers
def _base_iterator(self, circuit: circuits.Circuit, qubit_order: ops.QubitOrderOrList, initial_state: Union[int, np.ndarray], perform_measurements: bool = True) -> Iterator: qubits = ops.QubitOrder.as_qubit_order(qubit_order).order_for( circuit.all_qubits()) num_qubits = len(qubits) qubit_map = {q: i for i, q in enumerate(qubits)} matrix = density_matrix_utils.to_valid_density_matrix( initial_state, num_qubits, self._dtype) if len(circuit) == 0: yield DensityMatrixStepResult(matrix, {}, qubit_map, self._dtype) matrix = np.reshape(matrix, (2, ) * num_qubits * 2) def on_stuck(bad_op: ops.Operation): return TypeError( "Can't simulate operations that don't implement " "SupportsUnitary, SupportsApplyUnitary, SupportsMixture, " "SupportsChannel or is a measurement: {!r}".format(bad_op)) def keep(potential_op: ops.Operation) -> bool: return (protocols.has_channel(potential_op) or ops.MeasurementGate.is_measurement(potential_op) or isinstance(potential_op, (ops.SamplesDisplay, ops.WaveFunctionDisplay))) matrix = np.reshape(matrix, (2, ) * num_qubits * 2) for moment in circuit: measurements = collections.defaultdict( list) # type: Dict[str, List[bool]] channel_ops_and_measurements = protocols.decompose( moment.operations, keep=keep, on_stuck_raise=on_stuck) for op in channel_ops_and_measurements: indices = [qubit_map[qubit] for qubit in op.qubits] if isinstance(op, (ops.SamplesDisplay, ops.WaveFunctionDisplay)): continue elif ops.MeasurementGate.is_measurement(op): meas = cast(ops.MeasurementGate, cast(ops.GateOperation, op).gate) if perform_measurements: invert_mask = meas.invert_mask or num_qubits * ( False, ) # Measure updates inline. bits, _ = density_matrix_utils.measure_density_matrix( matrix, indices, matrix) corrected = [ bit ^ mask for bit, mask in zip(bits, invert_mask) ] key = protocols.measurement_key(meas) measurements[key].extend(corrected) else: # TODO: Use apply_channel similar to apply_unitary. gate = cast(ops.GateOperation, op).gate channel = protocols.channel(gate) sum_buffer = np.zeros((2, ) * 2 * num_qubits, dtype=self._dtype) for krauss in channel: krauss_tensor = np.reshape(krauss.astype( self._dtype), (2, ) * gate.num_qubits() * 2) buffer = linalg.targeted_left_multiply( krauss_tensor, matrix, indices) # No need to transpose as we are acting on the tensor # representation of matrix, so transpose is done for us. buffer = linalg.targeted_left_multiply( np.conjugate(krauss_tensor), buffer, [num_qubits + x for x in indices]) sum_buffer += buffer np.copyto(dst=matrix, src=sum_buffer) yield DensityMatrixStepResult(density_matrix=matrix, measurements=measurements, qubit_map=qubit_map, dtype=self._dtype)