def from_mixture(mixture: 'protocols.SupportsMixture', key: Union[str, 'cirq.MeasurementKey', None] = None): """Creates a copy of a mixture with the given measurement key.""" return MixedUnitaryChannel(mixture=list(protocols.mixture(mixture)), key=key)
def _mixture_(self) -> Union[np.ndarray, NotImplementedType]: 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.mixture(c_op, default=NotImplemented)
def _mixture_(self) -> Sequence[Tuple[float, Any]]: return protocols.mixture(self.sub_operation, NotImplemented)
def _mixture_(self) -> Optional[List[Tuple[float, np.ndarray]]]: sub_mixture = protocols.mixture(self.sub_operation, None) if sub_mixture is None: return None return [(p, self._extend_matrix(m)) for p, m in sub_mixture]
def apply_op(self, op: 'cirq.Operation', prng: np.random.RandomState): """Applies a unitary operation, mutating the object to represent the new state. op: The operation that mutates the object. Note that currently, only 1- and 2- qubit operations are currently supported. """ old_inds = tuple([self.i_str(self.qubit_map[qubit]) for qubit in op.qubits]) new_inds = tuple(['new_' + old_ind for old_ind in old_inds]) if protocols.has_unitary(op): U = protocols.unitary(op) else: mixtures = protocols.mixture(op) mixture_idx = int(prng.choice(len(mixtures), p=[mixture[0] for mixture in mixtures])) U = mixtures[mixture_idx][1] U = qtn.Tensor( U.reshape([qubit.dimension for qubit in op.qubits] * 2), inds=(new_inds + old_inds) ) # TODO(tonybruguier): Explore using the Quimb's tensor network natively. if len(op.qubits) == 1: n = self.grouping[op.qubits[0]] self.M[n] = (U @ self.M[n]).reindex({new_inds[0]: old_inds[0]}) elif len(op.qubits) == 2: n, p = [self.grouping[qubit] for qubit in op.qubits] if n == p: self.M[n] = (U @ self.M[n]).reindex( {new_inds[0]: old_inds[0], new_inds[1]: old_inds[1]} ) else: # This is the index on which we do the contraction. We need to add it iff it's # the first time that we do the joining for that specific pair. mu_ind = self.mu_str(n, p) if mu_ind not in self.M[n].inds: self.M[n].new_ind(mu_ind) if mu_ind not in self.M[p].inds: self.M[p].new_ind(mu_ind) T = U @ self.M[n] @ self.M[p] left_inds = tuple(set(T.inds) & set(self.M[n].inds)) + (new_inds[0],) X, Y = T.split( left_inds, method=self.simulation_options.method, max_bond=self.simulation_options.max_bond, cutoff=self.simulation_options.cutoff, cutoff_mode=self.simulation_options.cutoff_mode, get='tensors', absorb='both', bond_ind=mu_ind, ) # Equations (13), (14), and (15): # TODO(tonybruguier): When Quimb 2.0.0 is released, the split() # function should have a 'renorm' that, when set to None, will # allow to compute e_n exactly as: # np.sum(abs((X @ Y).data) ** 2).real / np.sum(abs(T) ** 2).real # # The renormalization would then have to be done manually. # # However, for now, e_n are just the estimated value. e_n = self.simulation_options.cutoff self.estimated_gate_error_list.append(e_n) self.M[n] = X.reindex({new_inds[0]: old_inds[0]}) self.M[p] = Y.reindex({new_inds[1]: old_inds[1]}) else: # NOTE(tonybruguier): There could be a way to handle higher orders. I think this could # involve HOSVDs: # https://en.wikipedia.org/wiki/Higher-order_singular_value_decomposition # # TODO(tonybruguier): Evaluate whether it's even useful to implement and learn more # about HOSVDs. raise ValueError('Can only handle 1 and 2 qubit operations') return True
def _mixture_(self) -> Sequence[Tuple[float, Any]]: return protocols.mixture(self.gate, NotImplemented)