def expectation_from_density_matrix( self, state: np.ndarray, qubit_map: Mapping[raw_types.Qid, int], *, atol: float = 1e-7, check_preconditions: bool = True, ) -> float: """Evaluate the expectation of this PauliSum given a density matrix. See `PauliString.expectation_from_density_matrix`. Args: state: An array representing a valid density matrix. qubit_map: A map from all qubits used in this PauliSum to the indices of the qubits that `state` is defined over. atol: Absolute numerical tolerance. check_preconditions: Whether to check that `state` represents a valid density matrix. Returns: The expectation value of the input state. Raises: NotImplementedError: If any of the coefficients are imaginary, so that this is not Hermitian. TypeError: If the input state is not a complex type. ValueError: If the input vector is not the correct size or shape. """ if any(abs(p.coefficient.imag) > 0.0001 for p in self): raise NotImplementedError( "Cannot compute expectation value of a non-Hermitian " "PauliString <{}>. Coefficient must be real.".format(self)) # FIXME: Avoid enforce specific complex type. This is necessary to # prevent an `apply_unitary` bug (Issue #2041). if state.dtype.kind != 'c': raise TypeError( "Input state dtype must be np.complex64 or np.complex128") size = state.size num_qubits = int(np.sqrt(size)).bit_length() - 1 _validate_qubit_mapping(qubit_map, self.qubits, num_qubits) dim = int(np.sqrt(size)) if state.shape != (dim, dim) and state.shape != (2, 2) * num_qubits: raise ValueError("Input array does not represent a density matrix " "with shape `(2 ** n, 2 ** n)` or `(2, ..., 2)`.") if check_preconditions: # Do not enforce reshaping if the state all axes are dimension 2. _ = qis.to_valid_density_matrix( density_matrix_rep=state.reshape(dim, dim), num_qubits=num_qubits, dtype=state.dtype, atol=atol, ) return sum( p._expectation_from_density_matrix_no_validation(state, qubit_map) for p in self)
def create( cls, *, initial_state: Union[np.ndarray, 'cirq.STATE_VECTOR_LIKE'] = 0, qid_shape: Optional[Tuple[int, ...]] = None, dtype: Optional['DTypeLike'] = None, buffer: Optional[List[np.ndarray]] = None, ): """Creates a buffered density matrix with the requested state. Args: initial_state: The initial state for the simulation in the computational basis. qid_shape: The shape of the density matrix, if the initial state is provided as an int. dtype: The desired dtype of the density matrix. buffer: Optional, must be length 3 and same shape as the density matrix. If not provided, a buffer will be created automatically. Raises: ValueError: If initial state is provided as integer, but qid_shape is not provided. """ if not isinstance(initial_state, np.ndarray): if qid_shape is None: raise ValueError( 'qid_shape must be provided if initial_state is not ndarray' ) density_matrix = qis.to_valid_density_matrix(initial_state, len(qid_shape), qid_shape=qid_shape, dtype=dtype).reshape( qid_shape * 2) else: if qid_shape is not None: if dtype and initial_state.dtype != dtype: initial_state = initial_state.astype(dtype) density_matrix = qis.to_valid_density_matrix( initial_state, len(qid_shape), qid_shape=qid_shape, dtype=dtype).reshape(qid_shape * 2) else: density_matrix = initial_state # coverage: ignore if np.may_share_memory(density_matrix, initial_state): density_matrix = density_matrix.copy() density_matrix = density_matrix.astype(dtype, copy=False) return cls(density_matrix, buffer)
def set_density_matrix(self, density_matrix_repr: Union[int, np.ndarray]): """Set the density matrix to a new density matrix. Args: density_matrix_repr: If this is an int, the density matrix is set to the computational basis state corresponding to this state. Otherwise if this is a np.ndarray it is the full state, either a pure state or the full density matrix. If it is the pure state it must be the correct size, be normalized (an L2 norm of 1), and be safely castable to an appropriate dtype for the simulator. If it is a mixed state it must be correctly sized and positive semidefinite with trace one. """ density_matrix = qis.to_valid_density_matrix( density_matrix_repr, len(self._qubit_map), qid_shape=self._qid_shape, dtype=self._dtype ) sim_state_matrix = self._simulator_state().density_matrix density_matrix = np.reshape(density_matrix, sim_state_matrix.shape) np.copyto(dst=sim_state_matrix, src=density_matrix)
def _create_partial_act_on_args( self, initial_state: Union[np.ndarray, 'cirq.STATE_VECTOR_LIKE', 'cirq.ActOnDensityMatrixArgs'], qubits: Sequence['cirq.Qid'], logs: Dict[str, Any], ) -> 'cirq.ActOnDensityMatrixArgs': """Creates the ActOnDensityMatrixArgs for a circuit. Args: initial_state: The initial state for the simulation in the computational basis. qubits: 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. logs: The log of measurement results that is added into. Returns: ActOnDensityMatrixArgs for the circuit. """ if isinstance(initial_state, act_on_density_matrix_args.ActOnDensityMatrixArgs): return initial_state qid_shape = protocols.qid_shape(qubits) initial_matrix = qis.to_valid_density_matrix(initial_state, len(qid_shape), qid_shape=qid_shape, dtype=self._dtype) if np.may_share_memory(initial_matrix, initial_state): initial_matrix = initial_matrix.copy() tensor = initial_matrix.reshape(qid_shape * 2) return act_on_density_matrix_args.ActOnDensityMatrixArgs( target_tensor=tensor, available_buffer=[np.empty_like(tensor) for _ in range(3)], qubits=qubits, qid_shape=qid_shape, prng=self._prng, log_of_measurement_results=logs, )
def _base_iterator(self, circuit: circuits.Circuit, qubit_order: ops.QubitOrderOrList, initial_state: Union[int, np.ndarray], all_measurements_are_terminal=False) -> Iterator: qubits = ops.QubitOrder.as_qubit_order(qubit_order).order_for( circuit.all_qubits()) qid_shape = protocols.qid_shape(qubits) qubit_map = {q: i for i, q in enumerate(qubits)} initial_matrix = qis.to_valid_density_matrix(initial_state, len(qid_shape), qid_shape=qid_shape, dtype=self._dtype) measured = collections.defaultdict( bool) # type: Dict[Tuple[cirq.Qid, ...], bool] if len(circuit) == 0: yield DensityMatrixStepResult(initial_matrix, {}, qubit_map, self._dtype) return state = _StateAndBuffers(len(qid_shape), initial_matrix.reshape(qid_shape * 2)) def on_stuck(bad_op: ops.Operation): return TypeError( "Can't simulate operations that don't implement " "SupportsUnitary, SupportsConsistentApplyUnitary, " "SupportsMixture, SupportsChannel or is a measurement: {!r}". format(bad_op)) def keep(potential_op: ops.Operation) -> bool: return (protocols.has_channel(potential_op) or isinstance(potential_op.gate, ops.MeasurementGate)) noisy_moments = self.noise.noisy_moments(circuit, sorted(circuit.all_qubits())) for moment in noisy_moments: measurements = collections.defaultdict( list) # type: Dict[str, List[int]] channel_ops_and_measurements = protocols.decompose( moment, keep=keep, on_stuck_raise=on_stuck) for op in channel_ops_and_measurements: indices = [qubit_map[qubit] for qubit in op.qubits] # TODO: support more general measurements. if all_measurements_are_terminal and measured[op.qubits]: continue if isinstance(op.gate, ops.MeasurementGate): measured[op.qubits] = True meas = op.gate if all_measurements_are_terminal: continue if self._ignore_measurement_results: for i, q in enumerate(op.qubits): self._apply_op_channel( ops.phase_damp(1).on(q), state, [indices[i]]) else: invert_mask = meas.full_invert_mask() # Measure updates inline. bits, _ = (density_matrix_utils.measure_density_matrix( state.tensor, indices, qid_shape=qid_shape, out=state.tensor, seed=self._prng)) corrected = [ bit ^ (bit < 2 and 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. self._apply_op_channel(op, state, indices) yield DensityMatrixStepResult(density_matrix=state.tensor, measurements=measurements, qubit_map=qubit_map, dtype=self._dtype)
def expectation_from_density_matrix( self, state: np.ndarray, qubit_map: Mapping[raw_types.Qid, int], *, atol: float = 1e-7, check_preconditions: bool = True) -> float: r"""Evaluate the expectation of this PauliString given a density matrix. Compute the expectation value of this PauliString with respect to an array representing a density matrix. By convention expectation values are defined for Hermitian operators, and so this method will fail if this PauliString is non-Hermitian. `state` must be an array representation of a density matrix and have shape `(2 ** n, 2 ** n)` or `(2, 2, ..., 2)` (2*n entries), where `state` is expressed over n qubits. `qubit_map` must assign an integer index to each qubit in this PauliString that determines which bit position of a computational basis state that qubit corresponds to. For example if `state` represents $|0\rangle |+\rangle$ and `q0, q1 = cirq.LineQubit.range(2)` then: cirq.X(q0).expectation(state, qubit_map={q0: 0, q1: 1}) = 0 cirq.X(q0).expectation(state, qubit_map={q0: 1, q1: 0}) = 1 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. atol: Absolute numerical tolerance. check_preconditions: Whether to check that `state` represents a valid density matrix. Returns: The expectation value of the input state. Raises: NotImplementedError if this PauliString is non-Hermitian. """ if abs(self.coefficient.imag) > 0.0001: raise NotImplementedError( 'Cannot compute expectation value of a non-Hermitian ' f'PauliString <{self}>. Coefficient must be real.') # FIXME: Avoid enforcing specific complex type. This is necessary to # prevent an `apply_unitary` bug (Issue #2041). if state.dtype.kind != 'c': raise TypeError("Input state dtype must be np.complex64 or " "np.complex128") size = state.size num_qubits = int(np.sqrt(size)).bit_length() - 1 dim = 1 << num_qubits if state.shape != (dim, dim) and state.shape != (2, 2) * num_qubits: raise ValueError("Input array does not represent a density matrix " "with shape `(2 ** n, 2 ** n)` or `(2, ..., 2)`.") _validate_qubit_mapping(qubit_map, self.qubits, num_qubits) if check_preconditions: # Do not enforce reshaping if the state all axes are dimension 2. _ = qis.to_valid_density_matrix(density_matrix_rep=state.reshape( dim, dim), num_qubits=num_qubits, dtype=state.dtype, atol=atol) return self._expectation_from_density_matrix_no_validation( state, qubit_map)
def __init__( self, target_tensor: Optional[np.ndarray] = None, available_buffer: Optional[List[np.ndarray]] = None, qid_shape: Optional[Tuple[int, ...]] = None, prng: Optional[np.random.RandomState] = None, log_of_measurement_results: Optional[Dict[str, List[int]]] = None, qubits: Optional[Sequence['cirq.Qid']] = None, ignore_measurement_results: bool = False, initial_state: Union[np.ndarray, 'cirq.STATE_VECTOR_LIKE'] = 0, dtype: Type[np.number] = np.complex64, classical_data: Optional['cirq.ClassicalDataStore'] = None, ): """Inits ActOnDensityMatrixArgs. Args: target_tensor: The state vector to act on, stored as a numpy array with one dimension for each qubit in the system. Operations are expected to perform inplace edits of this object. available_buffer: A workspace with the same shape and dtype as `target_tensor`. Used by operations that cannot be applied to `target_tensor` inline, in order to avoid unnecessary allocations. qubits: 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. qid_shape: The shape of the target tensor. prng: The pseudo random number generator to use for probabilistic effects. log_of_measurement_results: A mutable object that measurements are being recorded into. ignore_measurement_results: If True, then the simulation will treat measurement as dephasing instead of collapsing process. This is only applicable to simulators that can model dephasing. initial_state: The initial state for the simulation in the computational basis. dtype: The `numpy.dtype` of the inferred state vector. One of `numpy.complex64` or `numpy.complex128`. Only used when `target_tenson` is None. classical_data: The shared classical data container for this simulation. Raises: ValueError: The dimension of `target_tensor` is not divisible by 2 and `qid_shape` is not provided. """ if ignore_measurement_results: super().__init__( prng=prng, qubits=qubits, log_of_measurement_results=log_of_measurement_results, ignore_measurement_results=ignore_measurement_results, classical_data=classical_data, ) else: super().__init__( prng=prng, qubits=qubits, log_of_measurement_results=log_of_measurement_results, classical_data=classical_data, ) if target_tensor is None: qubits_qid_shape = protocols.qid_shape(self.qubits) initial_matrix = qis.to_valid_density_matrix( initial_state, len(qubits_qid_shape), qid_shape=qubits_qid_shape, dtype=dtype) if np.may_share_memory(initial_matrix, initial_state): initial_matrix = initial_matrix.copy() target_tensor = initial_matrix.reshape(qubits_qid_shape * 2) self.target_tensor = target_tensor if available_buffer is None: available_buffer = [np.empty_like(target_tensor) for _ in range(3)] self.available_buffer = available_buffer if qid_shape is None: target_shape = target_tensor.shape if len(target_shape) % 2 != 0: raise ValueError( 'The dimension of target_tensor is not divisible by 2.' ' Require explicit qid_shape.') qid_shape = target_shape[:len(target_shape) // 2] self.qid_shape = qid_shape
def _base_iterator( self, circuit: circuits.Circuit, qubit_order: ops.QubitOrderOrList, initial_state: Union[np.ndarray, 'cirq.STATE_VECTOR_LIKE'], all_measurements_are_terminal=False, is_raw_state=False, ) -> Iterator['DensityMatrixStepResult']: qubits = ops.QubitOrder.as_qubit_order(qubit_order).order_for( circuit.all_qubits()) qid_shape = protocols.qid_shape(qubits) qubit_map = {q: i for i, q in enumerate(qubits)} initial_matrix = (qis.to_valid_density_matrix(initial_state, len(qid_shape), qid_shape=qid_shape, dtype=self._dtype) if not is_raw_state else initial_state) if np.may_share_memory(initial_matrix, initial_state): initial_matrix = initial_matrix.copy() if len(circuit) == 0: yield DensityMatrixStepResult(initial_matrix, {}, qubit_map, self._dtype) return tensor = initial_matrix.reshape(qid_shape * 2) sim_state = act_on_density_matrix_args.ActOnDensityMatrixArgs( target_tensor=tensor, available_buffer=[np.empty_like(tensor) for _ in range(3)], axes=[], qid_shape=qid_shape, prng=self._prng, log_of_measurement_results={}, ) noisy_moments = self.noise.noisy_moments(circuit, sorted(circuit.all_qubits())) measured = collections.defaultdict( bool) # type: Dict[Tuple[cirq.Qid, ...], bool] for moment in noisy_moments: for op in flatten_to_ops(moment): # TODO: support more general measurements. # Github issue: https://github.com/quantumlib/Cirq/issues/3566 if all_measurements_are_terminal and measured[op.qubits]: continue if protocols.is_measurement(op): measured[op.qubits] = True if all_measurements_are_terminal: continue if self._ignore_measurement_results: op = ops.phase_damp(1).on(*op.qubits) sim_state.axes = tuple(qubit_map[qubit] for qubit in op.qubits) protocols.act_on(op, sim_state) yield DensityMatrixStepResult( density_matrix=sim_state.target_tensor, measurements=dict(sim_state.log_of_measurement_results), qubit_map=qubit_map, dtype=self._dtype, ) sim_state.log_of_measurement_results.clear()
def to_valid_density_matrix(*args, **kwargs): return qis.to_valid_density_matrix(*args, **kwargs)