Пример #1
0
 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)
Пример #2
0
 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)
Пример #3
0
 def _mixture_(self) -> Sequence[Tuple[float, Any]]:
     return protocols.mixture(self.sub_operation, NotImplemented)
Пример #4
0
 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]
Пример #5
0
    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
Пример #6
0
 def _mixture_(self) -> Sequence[Tuple[float, Any]]:
     return protocols.mixture(self.gate, NotImplemented)