def _apply_unitary_( self, args: protocols.ApplyUnitaryArgs) -> Optional[np.ndarray]: if self._name == 'X': return protocols.apply_unitary(common_gates.X, args) elif self._name == 'Y': return protocols.apply_unitary(common_gates.Y, args) else: return protocols.apply_unitary(common_gates.Z, args)
def _apply_unitary_(self, args: protocols.ApplyUnitaryArgs) -> np.ndarray: n = len(self.controls) control_axes = args.axes[:n] sub_axes = args.axes[n:] active = linalg.slice_for_qubits_equal_to(control_axes, -1) view_axes = _positions_after_removals_at(initial_positions=sub_axes, removals=control_axes) target_view = args.target_tensor[active] buffer_view = args.available_buffer[active] result = protocols.apply_unitary(self.sub_operation, protocols.ApplyUnitaryArgs( target_view, buffer_view, view_axes), default=NotImplemented) if result is NotImplemented: return NotImplemented if result is target_view: return args.target_tensor # HACK: assume they didn't somehow escape the slice view and edit the # rest of target_tensor. args.target_tensor[active] = result return args.target_tensor
def _apply_unitary_(self, args: 'protocols.ApplyUnitaryArgs') -> np.ndarray: return protocols.apply_unitary( controlled_gate.ControlledGate(swap_gates.SWAP), protocols.ApplyUnitaryArgs(args.target_tensor, args.available_buffer, args.axes), default=NotImplemented)
def _apply_unitary_(self, args: 'protocols.ApplyUnitaryArgs') -> np.ndarray: n = len(self.controls) sub_n = len(args.axes) - n sub_axes = args.axes[n:] for control_vals in itertools.product(*self.control_values): active = (..., *(slice(v, v + 1) for v in control_vals), *(slice(None), ) * sub_n) target_view = args.target_tensor[active] buffer_view = args.available_buffer[active] result = protocols.apply_unitary( self.sub_operation, protocols.ApplyUnitaryArgs(target_view, buffer_view, sub_axes), default=NotImplemented, ) if result is NotImplemented: return NotImplemented if result is not target_view: # HACK: assume they didn't somehow escape the slice view and # edit the rest of target_tensor. target_view[...] = result return args.target_tensor
def _expectation_from_wavefunction_no_validation( self, state: np.ndarray, qubit_map: Mapping[raw_types.Qid, int]) -> float: """Evaluate the expectation of this PauliString given a wavefunction. This method does not provide input validation. See `PauliString.expectation_from_wavefunction` for function description. Args: state: An array representing a valid wavefunction. qubit_map: A map from all qubits used in this PauliString to the indices of the qubits that `state` is defined over. Returns: The expectation value of the input state. """ if len(state.shape) == 1: num_qubits = state.shape[0].bit_length() - 1 state = np.reshape(state, (2, ) * num_qubits) ket = np.copy(state) for qubit, pauli in self.items(): buffer = np.empty(ket.shape, dtype=state.dtype) args = protocols.ApplyUnitaryArgs(target_tensor=ket, available_buffer=buffer, axes=(qubit_map[qubit], )) ket = protocols.apply_unitary(pauli, args) return self.coefficient * (np.tensordot( state.conj(), ket, axes=len(ket.shape)).item())
def _expectation_from_density_matrix_no_validation( self, state: np.ndarray, qubit_map: Mapping[raw_types.Qid, int]) -> float: """Evaluate the expectation of this PauliString given a density matrix. This method does not provide input validation. See `PauliString.expectation_from_density_matrix` for function description. Args: state: An array representing a valid density matrix. qubit_map: A map from all qubits used in this PauliString to the indices of the qubits that `state` is defined over. Returns: The expectation value of the input state. """ result = np.copy(state) if len(state.shape) == 2: num_qubits = state.shape[0].bit_length() - 1 result = np.reshape(result, (2, ) * num_qubits * 2) for qubit, pauli in self.items(): buffer = np.empty(result.shape, dtype=state.dtype) args = protocols.ApplyUnitaryArgs(target_tensor=result, available_buffer=buffer, axes=(qubit_map[qubit], )) result = protocols.apply_unitary(pauli, args) while any(result.shape): result = np.trace(result, axis1=0, axis2=len(result.shape) // 2) return result * self.coefficient
def assert_has_consistent_apply_unitary(val: Any, *, qubit_count: Optional[int] = None, atol: float = 1e-8) -> None: """Tests whether a value's _apply_unitary_ is correct. Contrasts the effects of the value's `_apply_unitary_` with the matrix returned by the value's `_unitary_` method. Args: val: The value under test. Should have a `__pow__` method. qubit_count: Usually inferred. The number of qubits the value acts on. This argument isn't needed if the gate has a unitary matrix or implements `cirq.SingleQubitGate`/`cirq.TwoQubitGate`/ `cirq.ThreeQubitGate`. atol: Absolute error tolerance. """ expected = protocols.unitary(val, default=None) qubit_counts = [ qubit_count, # Only fall back to using the unitary size if num_qubits or qid_shape # protocols are not defined. protocols.num_qubits(val, default=expected.shape[0].bit_length() - 1 if expected is not None else None), _infer_qubit_count(val) ] qubit_counts = [e for e in qubit_counts if e is not None] if not qubit_counts: raise NotImplementedError( 'Failed to infer qubit count of <{!r}>. Specify it.'.format(val)) assert len(set(qubit_counts)) == 1, ( 'Inconsistent qubit counts from different methods: {}'.format( qubit_counts)) n = cast(int, qubit_counts[0]) qid_shape = protocols.qid_shape(val, default=(2, ) * n) eye = linalg.eye_tensor((2, ) + qid_shape, dtype=np.complex128) actual = protocols.apply_unitary( unitary_value=val, args=protocols.ApplyUnitaryArgs(target_tensor=eye, available_buffer=np.ones_like(eye) * float('nan'), axes=list(range(1, n + 1))), default=None) # If you don't have a unitary, you shouldn't be able to apply a unitary. if expected is None: assert actual is None else: expected = np.kron(np.eye(2), expected) # If you applied a unitary, it should match the one you say you have. if actual is not None: np.testing.assert_allclose(actual.reshape((np.prod( (2, ) + qid_shape, dtype=int), ) * 2), expected, atol=atol)
def _apply_unitary_(self, args: 'protocols.ApplyUnitaryArgs') -> np.ndarray: qubits = cirq.LineQid.for_gate(self) op = self.sub_gate.on(*qubits[self.num_controls():]) c_op = cop.ControlledOperation(qubits[:self.num_controls()], op, self.control_values) return protocols.apply_unitary(c_op, args, default=NotImplemented)
def _apply_unitary_(self, args: protocols.ApplyUnitaryArgs) -> np.ndarray: control = args.axes[0] rest = args.axes[1:] active = linalg.slice_for_qubits_equal_to([control], 1) sub_axes = [r - int(r > control) for r in rest] target_view = args.target_tensor[active] buffer_view = args.available_buffer[active] result = protocols.apply_unitary(self.sub_gate, protocols.ApplyUnitaryArgs( target_view, buffer_view, sub_axes), default=NotImplemented) if result is NotImplemented: return NotImplemented if result is target_view: return args.target_tensor if result is buffer_view: inactive = linalg.slice_for_qubits_equal_to([control], 0) args.available_buffer[inactive] = args.target_tensor[inactive] return args.available_buffer # HACK: assume they didn't somehow escape the slice view and edit the # rest of target_tensor. args.target_tensor[active] = result return args.target_tensor
def _apply_unitary_(self, args: protocols.ApplyUnitaryArgs) -> np.ndarray: qubits = cirq.LineQubit.range(self.num_controls() + self.sub_gate.num_qubits()) op = self.sub_gate.on(*qubits[self.num_controls():]) c_op = cop.ControlledOperation(qubits[:self.num_controls()], op) return protocols.apply_unitary(c_op, args, default=NotImplemented)
def _base_iterator( self, circuit: circuits.Circuit, qubit_order: ops.QubitOrderOrList, initial_state: Union[int, np.ndarray], perform_measurements: bool = True, ) -> Iterator[simulator.StepResult]: 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)} state = wave_function.to_valid_state_vector(initial_state, num_qubits, self._dtype) def on_stuck(bad_op: ops.Operation): return TypeError( "Can't simulate unknown operations that don't specify a " "_unitary_ method, a _decompose_ method, or " "(_has_unitary_ + _apply_unitary_) methods" ": {!r}".format(bad_op)) def keep(potential_op: ops.Operation) -> bool: return (protocols.has_unitary(potential_op) or ops.MeasurementGate.is_measurement(potential_op)) state = np.reshape(state, (2, ) * num_qubits) buffer = np.empty((2, ) * num_qubits, dtype=self._dtype) for moment in circuit: measurements = collections.defaultdict( list) # type: Dict[str, List[bool]] unitary_ops_and_measurements = protocols.decompose( moment.operations, keep=keep, on_stuck_raise=on_stuck) for op in unitary_ops_and_measurements: indices = [qubit_map[qubit] for qubit in op.qubits] if ops.MeasurementGate.is_measurement(op): gate = cast(ops.MeasurementGate, cast(ops.GateOperation, op).gate) if perform_measurements: invert_mask = gate.invert_mask or num_qubits * ( False, ) # Measure updates inline. bits, _ = wave_function.measure_state_vector( state, indices, state) corrected = [ bit ^ mask for bit, mask in zip(bits, invert_mask) ] measurements[cast(str, gate.key)].extend(corrected) else: result = protocols.apply_unitary( op, args=protocols.ApplyUnitaryArgs( state, buffer, indices)) if result is buffer: buffer = state state = result yield SimulatorStep(state, measurements, qubit_map, self._dtype)
def _simulate_unitary(self, op: ops.Operation, data: _StateAndBuffer, indices: List[int]) -> None: """Simulate an op that has a unitary.""" result = protocols.apply_unitary(op, args=protocols.ApplyUnitaryArgs( data.state, data.buffer, indices)) if result is data.buffer: data.buffer = data.state data.state = result
def _apply_unitary_(self, args: protocols.ApplyUnitaryArgs) -> np.ndarray: if protocols.is_parameterized(self): return NotImplemented p = 1j**(2 * self._exponent * self._global_shift) if p != 1: args.target_tensor *= p return protocols.apply_unitary(controlled_gate.ControlledGate( controlled_gate.ControlledGate(pauli_gates.X**self.exponent)), protocols.ApplyUnitaryArgs( args.target_tensor, args.available_buffer, args.axes), default=NotImplemented)
def value_derived_from_wavefunction( self, state: np.ndarray, qubit_map: Dict[raw_types.Qid, int]) -> float: num_qubits = state.shape[0].bit_length() - 1 ket = np.reshape(np.copy(state), (2, ) * num_qubits) for qubit, pauli in self._pauli_string.items(): buffer = np.empty(ket.shape, dtype=state.dtype) args = protocols.ApplyUnitaryArgs(target_tensor=ket, available_buffer=buffer, axes=(qubit_map[qubit], )) ket = protocols.apply_unitary(pauli, args) ket = np.reshape(ket, state.shape) return np.dot(state.conj(), ket) * self._pauli_string.coefficient
def value_derived_from_density_matrix( self, state: np.ndarray, qubit_map: Dict[raw_types.Qid, int]) -> float: num_qubits = state.shape[0].bit_length() - 1 result = np.reshape(np.copy(state), (2, ) * num_qubits * 2) for qubit, pauli in self._pauli_string.items(): buffer = np.empty(result.shape, dtype=state.dtype) args = protocols.ApplyUnitaryArgs(target_tensor=result, available_buffer=buffer, axes=(qubit_map[qubit], )) result = protocols.apply_unitary(pauli, args) result = np.reshape(result, state.shape) return np.trace(result) * self._pauli_string.coefficient
def _strat_act_on_state_vector_from_apply_unitary( unitary_value: Any, args: 'cirq.ActOnStateVectorArgs', ) -> bool: new_target_tensor = protocols.apply_unitary( unitary_value, protocols.ApplyUnitaryArgs( target_tensor=args.target_tensor, available_buffer=args.available_buffer, axes=args.axes, ), allow_decompose=False, default=NotImplemented) if new_target_tensor is NotImplemented: return NotImplemented args.swap_target_tensor_for(new_target_tensor) return True
def assert_has_consistent_apply_unitary(val: Any, *, atol: float = 1e-8) -> None: """Tests whether a value's _apply_unitary_ is correct. Contrasts the effects of the value's `_apply_unitary_` with the matrix returned by the value's `_unitary_` method. Args: val: The value under test. Should have a `__pow__` method. atol: Absolute error tolerance. """ # pylint: disable=unused-variable __tracebackhide__ = True # pylint: enable=unused-variable _assert_apply_unitary_works_when_axes_transposed(val, atol=atol) expected = protocols.unitary(val, default=None) qid_shape = protocols.qid_shape(val) eye = qis.eye_tensor((2, ) + qid_shape, dtype=np.complex128) actual = protocols.apply_unitary( unitary_value=val, args=protocols.ApplyUnitaryArgs( target_tensor=eye, available_buffer=np.ones_like(eye) * float('nan'), axes=list(range(1, len(qid_shape) + 1)), ), default=None, ) # If you don't have a unitary, you shouldn't be able to apply a unitary. if expected is None: assert actual is None else: expected = np.kron(np.eye(2), expected) # If you applied a unitary, it should match the one you say you have. if actual is not None: np.testing.assert_allclose(actual.reshape((np.prod( (2, ) + qid_shape, dtype=int), ) * 2), expected, atol=atol)
def assert_has_consistent_apply_unitary(val: Any, *, qubit_count: Optional[int] = None, atol: float = 1e-8) -> None: """Tests whether a value's _apply_unitary_ is correct. Contrasts the effects of the value's `_apply_unitary_` with the matrix returned by the value's `_unitary_` method. Args: val: The value under test. Should have a `__pow__` method. qubit_count: Usually inferred. The number of qubits the value acts on. This argument isn't needed if the gate has a unitary matrix or implements `cirq.SingleQubitGate`/`cirq.TwoQubitGate`/ `cirq.ThreeQubitGate`. """ expected = protocols.unitary(val, default=None) if qubit_count is not None: n = qubit_count elif expected is not None: n = expected.shape[0].bit_length() - 1 else: n = _infer_qubit_count(val) eye = np.eye(2 << n, dtype=np.complex128).reshape((2, ) * (2 * n + 2)) actual = protocols.apply_unitary( unitary_value=val, args=protocols.ApplyUnitaryArgs(target_tensor=eye, available_buffer=np.ones_like(eye) * float('nan'), axes=list(range(1, n + 1))), default=None) # If you don't have a unitary, you shouldn't be able to apply a unitary. if expected is None: assert actual is None else: expected = np.kron(np.eye(2), expected) # If you applied a unitary, it should match the one you say you have. if actual is not None: np.testing.assert_allclose(actual.reshape(2 << n, 2 << n), expected, atol=atol)
def matrix(self) -> np.ndarray: """Reconstructs matrix of self using unitaries of underlying operations. Raises: TypeError: if any of the gates in self does not provide a unitary. """ num_qubits = len(self.qubits) num_dim = 2**num_qubits qubit_to_axis = {q: i for i, q in enumerate(self.qubits)} result = np.zeros((2, ) * (2 * num_qubits), dtype=np.complex128) for op, coefficient in self.items(): identity = np.eye(num_dim, dtype=np.complex128).reshape(result.shape) workspace = np.empty_like(identity) axes = tuple(qubit_to_axis[q] for q in op.qubits) u = protocols.apply_unitary( op, protocols.ApplyUnitaryArgs(identity, workspace, axes)) result += coefficient * u return result.reshape((num_dim, num_dim))
def _apply_unitary_( self, args: protocols.ApplyUnitaryArgs ) -> Union[np.ndarray, None, NotImplementedType]: """Replicates the logic the simulators use to apply the equivalent sequence of GateOperations """ state = args.target_tensor buffer = args.available_buffer for axis in args.axes: result = protocols.apply_unitary(self.gate, protocols.ApplyUnitaryArgs( state, buffer, (axis, )), default=NotImplemented) if result is buffer: buffer = state state = result return state
def apply_unitary(self, action: Any, axes: Sequence[int]) -> bool: """Apply unitary to state. Args: action: The value with a unitary to apply. axes: The axes on which to apply the unitary. Returns: True if the operation succeeded. """ new_target_tensor = protocols.apply_unitary( action, protocols.ApplyUnitaryArgs(target_tensor=self._state_vector, available_buffer=self._buffer, axes=axes), allow_decompose=False, default=NotImplemented, ) if new_target_tensor is NotImplemented: return False self._swap_target_tensor_for(new_target_tensor) return True
def _apply_unitary_( self, args: 'protocols.ApplyUnitaryArgs' ) -> Union[np.ndarray, None, NotImplementedType]: return protocols.apply_unitary(self.sub_operation, args, default=None)
def _assert_apply_unitary_works_when_axes_transposed(val: Any, *, atol: float = 1e-8 ) -> None: """Tests whether a value's _apply_unitary_ handles out-of-order axes. A common mistake to make when implementing `_apply_unitary_` is to assume that the incoming axes will be contiguous, or ascending, or that they can be flattened, or that other axes have a length of two, etc, etc ,etc. This method checks that `_apply_unitary_` does the same thing to out-of-order axes that it does to contiguous in-order axes. Args: val: The operation, gate, or other unitary object to test. atol: Absolute error tolerance. """ # Only test custom apply unitary methods. if not hasattr(val, '_apply_unitary_') or not protocols.has_unitary(val): return # Pick sizes and shapes. shape = protocols.qid_shape(val) n = len(shape) padded_shape = shape + (1, 2, 2, 3) padded_n = len(padded_shape) size = np.product(padded_shape).item() # Shuffle the axes. permutation = list(range(padded_n)) random.shuffle(permutation) transposed_shape = [0] * padded_n for i in range(padded_n): transposed_shape[permutation[i]] = padded_shape[i] # Prepare input states. in_order_input = lin_alg_utils.random_superposition(size).reshape( padded_shape) out_of_order_input = np.empty(shape=transposed_shape, dtype=np.complex128) out_of_order_input.transpose(permutation)[...] = in_order_input # Apply to in-order and out-of-order axes. in_order_output = protocols.apply_unitary( val, protocols.ApplyUnitaryArgs( target_tensor=in_order_input, available_buffer=np.empty_like(in_order_input), axes=range(n), ), ) out_of_order_output = protocols.apply_unitary( val, protocols.ApplyUnitaryArgs( target_tensor=out_of_order_input, available_buffer=np.empty_like(out_of_order_input), axes=permutation[:n], ), ) # Put the out of order output back into order, to enable comparison. reordered_output = out_of_order_output.transpose(permutation) # The results should be identical. if not np.allclose(in_order_output, reordered_output, atol=atol): raise AssertionError( f'The _apply_unitary_ method of {repr(val)} acted differently on ' f'out-of-order axes than on in-order axes.\n' f'\n' f'The failing axis order: {repr(permutation[:n])}')
def _apply_unitary_(self, args): return protocols.apply_unitary(self.to_circuit(), args)
def _apply_unitary_( self, args: protocols.ApplyUnitaryArgs ) -> Union[np.ndarray, None, NotImplementedType]: return protocols.apply_unitary(self.gate, args, default=NotImplemented)
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)} state = wave_function.to_valid_state_vector(initial_state, num_qubits, self._dtype) if len(circuit) == 0: yield SparseSimulatorStep(state, {}, qubit_map, self._dtype) def on_stuck(bad_op: ops.Operation): return TypeError( "Can't simulate unknown operations that don't specify a " "_unitary_ method, a _decompose_ method, or " "(_has_unitary_ + _apply_unitary_) methods" ": {!r}".format(bad_op)) def keep(potential_op: ops.Operation) -> bool: return (protocols.has_unitary(potential_op) or protocols.is_measurement(potential_op)) state = np.reshape(state, (2, ) * num_qubits) buffer = np.empty((2, ) * num_qubits, dtype=self._dtype) for moment in circuit: measurements = collections.defaultdict( list) # type: Dict[str, List[bool]] non_display_ops = (op for op in moment if not isinstance(op, ( ops.SamplesDisplay, ops.WaveFunctionDisplay, ops.DensityMatrixDisplay))) unitary_ops_and_measurements = protocols.decompose( non_display_ops, keep=keep, on_stuck_raise=on_stuck) for op in unitary_ops_and_measurements: indices = [qubit_map[qubit] for qubit in op.qubits] # TODO: Support measurements outside of computational basis. # TODO: Support mixtures. meas = ops.op_gate_of_type(op, ops.MeasurementGate) if meas: if perform_measurements: invert_mask = meas.invert_mask or num_qubits * ( False, ) # Measure updates inline. bits, _ = wave_function.measure_state_vector( state, indices, state) corrected = [ bit ^ mask for bit, mask in zip(bits, invert_mask) ] key = protocols.measurement_key(meas) measurements[key].extend(corrected) else: result = protocols.apply_unitary( op, args=protocols.ApplyUnitaryArgs( state, buffer, indices)) if result is buffer: buffer = state state = result yield SparseSimulatorStep(state_vector=state, measurements=measurements, qubit_map=qubit_map, dtype=self._dtype)