def test_expw_matrix(half_turns): if (version[0] == 0 and version[1] <= 3): nptest.assert_array_almost_equal(xmon_gates.Exp11Gate(half_turns=half_turns).matrix, google.Exp11Gate(half_turns=half_turns).matrix()) else: nptest.assert_array_almost_equal(xmon_gates.Exp11Gate(half_turns=half_turns).matrix, unitary(ops.CZPowGate(exponent=half_turns)))
def test_expw_matrix(half_turns, axis_half_turns): m1 = xmon_gates.ExpWGate(half_turns=half_turns, axis_half_turns=axis_half_turns).matrix if (version[0] == 0 and version[1] <= 3): m2 = google.ExpWGate(half_turns=half_turns, axis_half_turns=axis_half_turns).matrix() else: m2 = unitary(ops.PhasedXPowGate(exponent=half_turns, phase_exponent=axis_half_turns)) nptest.assert_array_almost_equal(m1, m2)
def pauli_expansion( val: Any, *, default: Union[value.LinearDict[str], TDefault] = RaiseTypeErrorIfNotProvided, atol: float = 1e-9) -> Union[value.LinearDict[str], TDefault]: """Returns coefficients of the expansion of val in the Pauli basis. Args: val: The value whose Pauli expansion is to returned. default: Determines what happens when `val` does not have methods that allow Pauli expansion to be obtained (see below). If set, the value is returned in that case. Otherwise, TypeError is raised. atol: Ignore coefficients whose absolute value is smaller than this. Returns: If `val` has a _pauli_expansion_ method, then its result is returned. Otherwise, if `val` has a small unitary then that unitary is expanded in the Pauli basis and coefficients are returned. Otherwise, if default is set to None or other value then default is returned. Otherwise, TypeError is raised. Raises: TypeError if `val` has none of the methods necessary to obtain its Pauli expansion and no default value has been provided. """ method = getattr(val, '_pauli_expansion_', None) expansion = NotImplemented if method is None else method() if expansion is not NotImplemented: return expansion.clean(atol=atol) # Don't attempt to derive the pauli expansion if this is a qudit gate if not all(d == 2 for d in qid_shape_protocol.qid_shape(val, default=())): if default is RaiseTypeErrorIfNotProvided: raise TypeError( 'No Pauli expansion for object {} of type {}'.format( val, type(val))) return default matrix = unitary(val, default=None) if matrix is None: if default is RaiseTypeErrorIfNotProvided: raise TypeError( 'No Pauli expansion for object {} of type {}'.format( val, type(val))) return default num_qubits = matrix.shape[0].bit_length() - 1 basis = operator_spaces.kron_bases(operator_spaces.PAULI_BASIS, repeat=num_qubits) expansion = operator_spaces.expand_matrix_in_orthogonal_basis( matrix, basis) return expansion.clean(atol=atol)
def _strat_distance_from_unitary(val: Any) -> Optional[float]: """Attempts to compute a value's trace_distance_bound from its unitary.""" u = unitary(val, default=None) if u is None: return NotImplemented if u.shape == (2, 2): squared = 1 - (0.5 * abs(u[0][0] + u[1][1]))**2 if squared <= 0: return 0.0 return squared**0.5 return trace_distance_from_angle_list(np.angle(np.linalg.eigvals(u)))
def pauli_expansion(val: Any, *, default: Optional[Dict[ str, complex]] = RaiseTypeErrorIfNotProvided, tolerance: float = 1e-9) -> Optional[Dict[str, complex]]: """Returns coefficients of the expansion of val in the Pauli basis. Args: val: The value whose Pauli expansion is to returned. default: Determines what happens when `val` does not have methods that allow Pauli expansion to be obtained (see below). If set, the value is returned in that case. Otherwise, TypeError is raised. tolerance: Ignore coefficients whose absolute value is smaller than this. Returns: If `val` has a _pauli_expansion_ method, then its result is returned. Otherwise, if `val` has a small unitary then that unitary is expanded in the Pauli basis and coefficients are returned. Otherwise, if default is set to None or other value then default is returned. Otherwise, TypeError is raised. Raises: TypeError if `val` has none of the methods necessary to obtain its Pauli expansion and no default value has been provided. """ method = getattr(val, '_pauli_expansion_', None) expansion = NotImplemented if method is None else method() if expansion is not NotImplemented: return _filter_coefficients(expansion, tolerance) matrix = unitary(val, default=None) if matrix is None: if default is RaiseTypeErrorIfNotProvided: raise TypeError( 'No Pauli expansion for object {} of type {}'.format( val, type(val))) return default num_qubits = matrix.shape[0].bit_length() - 1 basis = operator_spaces.kron_bases(operator_spaces.PAULI_BASIS, repeat=num_qubits) expansion = operator_spaces.expand_matrix_in_orthogonal_basis( matrix, basis) return _filter_coefficients(expansion, tolerance)
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)))