Exemple #1
0
    def measure(self):
        """
        Measures a circuit on the computational basis.

        Returns
        -------
        array : np.ndarray
            with real entries and the same shape as :code:`self.eval().array`.

        Examples
        --------
        >>> m = X.measure()
        >>> list(np.round(m.flatten()))
        [0.0, 1.0, 1.0, 0.0]
        >>> assert (Ket(0) >> X >> Bra(1)).measure() == m[0, 1]
        """
        def bitstring(i, length):
            return map(int, '{{:0{}b}}'.format(length).format(i))

        process = self.eval()
        states, effects = [], []
        states = [
            Ket(*bitstring(i, len(self.dom))).eval()
            for i in range(2**len(self.dom))
        ]
        effects = [
            Bra(*bitstring(j, len(self.cod))).eval()
            for j in range(2**len(self.cod))
        ]
        array = np.zeros(len(self.dom + self.cod) * (2, ))
        for state in states if self.dom else [Tensor.id(1)]:
            for effect in effects if self.cod else [Tensor.id(1)]:
                scalar = np.absolute((state >> process >> effect).array)**2
                array += scalar * (state.dagger() >> effect.dagger()).array
        return array
Exemple #2
0
 def __init__(self, *bitstring, _dagger=False):
     utensor = Tensor.id(
         Dim(1)).tensor(*(Tensor(Dim(1), Dim(2), [0, 1] if bit else [1, 0])
                          for bit in bitstring))
     name = "Bits({})".format(', '.join(map(str, bitstring)))
     dom, cod = (len(bitstring), 0) if _dagger else (0, len(bitstring))
     super().__init__(name, dom, cod, array=utensor.array, _dagger=_dagger)
     self.bitstring = bitstring
Exemple #3
0
 def array(self):
     """
     >>> Ket(0).eval()
     Tensor(dom=Dim(1), cod=Dim(2), array=[1, 0])
     >>> Ket(0, 1).eval()
     Tensor(dom=Dim(1), cod=Dim(2, 2), array=[0, 1, 0, 0])
     """
     tensor = Tensor(Dim(1), Dim(1), [1])
     for bit in self.bitstring:
         tensor = tensor @ Tensor(Dim(2), Dim(1), [0, 1] if bit else [1, 0])
     return tensor.array
Exemple #4
0
 def __init__(self, dom, cod, array=None, data=None):
     if array is None and data is None:
         raise ValueError("One of array or data must be given.")
     if data is None:
         data = Tensor(dom.classical @ dom.quantum @ dom.quantum,
                       cod.classical @ cod.quantum @ cod.quantum, array)
     self.array = data.array
     super().__init__("CQMap", dom, cod, data=data)
Exemple #5
0
 def ar(box):
     if isinstance(box, Swap):
         return CQMap.swap(F_ob(box.dom[:1]), F_ob(box.dom[1:]))
     if isinstance(box, Discard):
         return CQMap.discard(F_ob(box.dom))
     if isinstance(box, Measure):
         measure = CQMap.measure(
             F_ob(box.dom).quantum, destructive=box.destructive)
         measure = measure @ CQMap.discard(F_ob(box.dom).classical)\
             if box.override_bits else measure
         return measure
     if isinstance(box, (MixedState, Encode)):
         return ar(box.dagger()).dagger()
     if not box.is_mixed and box.classical:
         return CQMap(F_ob(box.dom), F_ob(box.cod), box.array)
     if not box.is_mixed:
         dom, cod = F_ob(box.dom).quantum, F_ob(box.cod).quantum
         return CQMap.pure(Tensor(dom, cod, box.array))
     raise TypeError(messages.type_err(QuantumGate, box))
Exemple #6
0
def tensor_from_counts(counts, post_selection=None, scalar=1, normalize=True):
    """
    Parameters
    ----------
    counts : dict
        From bitstrings to counts.
    post_selection : dict, optional
        From qubit indices to bits.
    scalar : complex, optional
        Scale the output using the Born rule.
    normalize : bool, optional
        Whether to normalize the counts.

    Returns
    -------
    tensor : discopy.tensor.Tensor
        Of dimension :code:`n_qubits * (2, )` for :code:`n_qubits` the number
        of post-selected qubits.
    """
    if normalize:
        counts = probs_from_counts(counts)
    n_qubits = len(list(counts.keys()).pop())
    if post_selection:
        post_selected = dict()
        for bitstring, count in counts.items():
            if all(bitstring[qubit] == bit
                   for qubit, bit in post_selection.items()):
                post_selected.update({
                    tuple(bit for qubit, bit in enumerate(bitstring) if qubit not in post_selection):
                    count
                })
        n_qubits -= len(post_selection.keys())
        counts = post_selected
    array = np.zeros(n_qubits * (2, ))
    for bitstring, count in counts.items():
        array += count * Ket(*bitstring).array
    array = abs(scalar)**2 * array
    return Tensor(Dim(1), Dim(*(n_qubits * (2, ))), array)
Exemple #7
0
 def _ar(self, box):
     """ Overrides the input mapping on arrows. """
     if isinstance(box, Discard):
         return CQMap.discard(self(box.dom))
     if isinstance(box, Measure):
         measure = CQMap.measure(self(box.dom).quantum,
                                 destructive=box.destructive)
         measure = measure @ CQMap.discard(self(box.dom).classical)\
             if box.override_bits else measure
         return measure
     if isinstance(box, (MixedState, Encode)):
         return self(box.dagger()).dagger()
     if isinstance(box, Scalar):
         scalar = box.array[0] if box.is_mixed else abs(box.array[0])**2
         return CQMap(CQ(), CQ(), scalar)
     if not box.is_mixed and box.classical:
         return CQMap(self(box.dom), self(box.cod), box.array)
     if not box.is_mixed:
         dom, cod = self(box.dom).quantum, self(box.cod).quantum
         return CQMap.pure(Tensor(dom, cod, box.array))
     if hasattr(box, "array"):
         return CQMap(self(box.dom), self(box.cod), box.array)
     return self.__ar[box]
Exemple #8
0
 def __call__(self, box):
     if isinstance(box, Sum) or not isinstance(box, Box):
         return super().__call__(box)
     if isinstance(box, Swap):
         return CQMap.swap(self(box.dom[:1]), self(box.dom[1:]))
     if isinstance(box, Discard):
         return CQMap.discard(self(box.dom))
     if isinstance(box, Measure):
         measure = CQMap.measure(self(box.dom).quantum,
                                 destructive=box.destructive)
         measure = measure @ CQMap.discard(self(box.dom).classical)\
             if box.override_bits else measure
         return measure
     if isinstance(box, (MixedState, Encode)):
         return self(box.dagger()).dagger()
     if isinstance(box, Scalar):
         return CQMap(CQ(), CQ(), abs(box.array[0])**2)
     if not box.is_mixed and box.classical:
         return CQMap(self(box.dom), self(box.cod), box.array)
     if not box.is_mixed:
         dom, cod = self(box.dom).quantum, self(box.cod).quantum
         return CQMap.pure(Tensor(dom, cod, box.array))
     return CQMap(self(box.dom), self(box.cod), box.array)
Exemple #9
0
 def cups(left, right):
     return CQMap.classical(Tensor.cups(left.classical, right.classical))\
         @ CQMap.pure(Tensor.cups(left.quantum, right.quantum))
Exemple #10
0
 def swap(left, right):
     utensor = Tensor.swap(left.classical, right.classical)\
         @ Tensor.swap(left.quantum, right.quantum)\
         @ Tensor.swap(left.quantum, right.quantum)
     return CQMap(left @ right, right @ left, utensor=utensor)
Exemple #11
0
 def discard(dom):
     """ Discard a quantum dimension or take the marginal distribution. """
     array = Tensor.np.tensordot(Tensor.np.ones(dom.classical),
                                 Tensor.id(dom.quantum).array, 0)
     return CQMap(dom, CQ(), array)
Exemple #12
0
 def utensor(self):
     """ Underlying tensor. """
     return Tensor(self._udom, self._ucod, self.array)
Exemple #13
0
 def id(dom=CQ()):
     utensor = Tensor.id(dom.classical @ dom.quantum @ dom.quantum)
     return CQMap(dom, dom, utensor=utensor)
Exemple #14
0
 def swap(left, right):
     data = Tensor.swap(left.classical, right.classical)\
         @ Tensor.swap(left.quantum, right.quantum)\
         @ Tensor.swap(left.quantum, right.quantum)
     return CQMap(left @ right, right @ left, data.array)
Exemple #15
0
    def eval(self, backend=None, mixed=False, **params):
        """
        Parameters
        ----------
        backend : pytket.Backend, optional
            Backend on which to run the circuit, if none then we apply
            :class:`TensorFunctor` or :class:`CQMapFunctor` instead.
        mixed : bool, optional
            Whether to apply :class:`TensorFunctor` or :class:`CQMapFunctor`.
        params : kwargs, optional
            Get passed to Circuit.get_counts.

        Returns
        -------
        tensor : :class:`discopy.tensor.Tensor`
            If :code:`backend is not None` or :code:`mixed=False`.
        cqmap : :class:`CQMap`
            Otherwise.

        Examples
        --------
        We can evaluate a pure circuit (i.e. with :code:`not circuit.is_mixed`)
        as a unitary :class:`discopy.tensor.Tensor` or as a :class:`CQMap`:

        >>> H.eval().round(2)
        Tensor(dom=Dim(2), cod=Dim(2), array=[0.71, 0.71, 0.71, -0.71])
        >>> H.eval(mixed=True).round(1)  # doctest: +ELLIPSIS
        CQMap(dom=Q(Dim(2)), cod=Q(Dim(2)), array=[0.5, ..., 0.5])

        We can evaluate a mixed circuit as a :class:`CQMap`:

        >>> Measure().eval()
        CQMap(dom=Q(Dim(2)), cod=C(Dim(2)), array=[1, 0, 0, 0, 0, 0, 0, 1])
        >>> circuit = Bits(1, 0) @ Ket(0) >> Discard(bit ** 2 @ qubit)
        >>> assert circuit.eval() == CQMap(dom=CQ(), cod=CQ(), array=[1.0])

        We can execute any circuit on a `pytket.Backend`:

        >>> circuit = Ket(0, 0) >> sqrt(2) @ H @ X >> CX >> Measure() @ Bra(0)
        >>> from unittest.mock import Mock
        >>> backend = Mock()
        >>> backend.get_counts.return_value = {(0, 1): 512, (1, 0): 512}
        >>> assert circuit.eval(backend, n_shots=2**10).round()\\
        ...     == Tensor(dom=Dim(1), cod=Dim(2), array=[0., 1.])
        """
        if backend is None and (mixed or self.is_mixed):
            ob = {Ty('bit'): C(Dim(2)), Ty('qubit'): Q(Dim(2))}
            F_ob = CQMapFunctor(ob, {})
            def ar(box):
                if isinstance(box, Swap):
                    return CQMap.swap(F_ob(box.dom[:1]), F_ob(box.dom[1:]))
                if isinstance(box, Discard):
                    return CQMap.discard(F_ob(box.dom))
                if isinstance(box, Measure):
                    measure = CQMap.measure(
                        F_ob(box.dom).quantum, destructive=box.destructive)
                    measure = measure @ CQMap.discard(F_ob(box.dom).classical)\
                        if box.override_bits else measure
                    return measure
                if isinstance(box, (MixedState, Encode)):
                    return ar(box.dagger()).dagger()
                if not box.is_mixed and box.classical:
                    return CQMap(F_ob(box.dom), F_ob(box.cod), box.array)
                if not box.is_mixed:
                    dom, cod = F_ob(box.dom).quantum, F_ob(box.cod).quantum
                    return CQMap.pure(Tensor(dom, cod, box.array))
                raise TypeError(messages.type_err(QuantumGate, box))
            return CQMapFunctor(ob, ar)(self)
        if backend is None:
            return TensorFunctor(lambda x: 2, lambda f: f.array)(self)
        counts = self.get_counts(backend, **params)
        n_bits = len(list(counts.keys()).pop())
        array = np.zeros(n_bits * (2, ) or (1, ))
        for bitstring, count in counts.items():
            array += count * Ket(*bitstring).array
        return Tensor(Dim(1), Dim(*(n_bits * (2, ))), array)
Exemple #16
0
 def id(dom):
     data = Tensor.id(dom.classical @ dom.quantum @ dom.quantum)
     return CQMap(dom, dom, data.array)
Exemple #17
0
 def apply(state):
     dom, cod = Dim(*(len(self.dom) * [2])), Dim(*(len(self.cod) * [2]))
     if (state.dom, state.cod) != (Dim(1), dom):
         raise AxiomError("Non-linear gates can only be applied "
                          "to states, not processes.")
     return Tensor(Dim(1), cod, self.data(state.array))
Exemple #18
0
    def eval(self, *others, backend=None, mixed=False, **params):
        """
        Evaluate a circuit on a backend, or simulate it with numpy.

        Parameters
        ----------
        others : :class:`discopy.quantum.circuit.Circuit`
            Other circuits to process in batch.
        backend : pytket.Backend, optional
            Backend on which to run the circuit, if none then we apply
            :class:`discopy.tensor.Functor` or :class:`CQMapFunctor` instead.
        mixed : bool, optional
            Whether to apply :class:`discopy.tensor.Functor`
            or :class:`CQMapFunctor`.
        params : kwargs, optional
            Get passed to Circuit.get_counts.

        Returns
        -------
        tensor : :class:`discopy.tensor.Tensor`
            If :code:`backend is not None` or :code:`mixed=False`.
        cqmap : :class:`CQMap`
            Otherwise.

        Examples
        --------
        We can evaluate a pure circuit (i.e. with :code:`not circuit.is_mixed`)
        as a unitary :class:`discopy.tensor.Tensor` or as a :class:`CQMap`:

        >>> from discopy.quantum import *

        >>> H.eval().round(2)
        Tensor(dom=Dim(2), cod=Dim(2), array=[0.71, 0.71, 0.71, -0.71])
        >>> H.eval(mixed=True).round(1)  # doctest: +ELLIPSIS
        CQMap(dom=Q(Dim(2)), cod=Q(Dim(2)), array=[0.5, ..., 0.5])

        We can evaluate a mixed circuit as a :class:`CQMap`:

        >>> assert Measure().eval()\\
        ...     == CQMap(dom=Q(Dim(2)), cod=C(Dim(2)),
        ...              array=[1, 0, 0, 0, 0, 0, 0, 1])
        >>> circuit = Bits(1, 0) @ Ket(0) >> Discard(bit ** 2 @ qubit)
        >>> assert circuit.eval() == CQMap(dom=CQ(), cod=CQ(), array=[1])

        We can execute any circuit on a `pytket.Backend`:

        >>> circuit = Ket(0, 0) >> sqrt(2) @ H @ X >> CX >> Measure() @ Bra(0)
        >>> from discopy.quantum.tk import mockBackend
        >>> backend = mockBackend({(0, 1): 512, (1, 0): 512})
        >>> assert circuit.eval(backend, n_shots=2**10).round()\\
        ...     == Tensor(dom=Dim(1), cod=Dim(2), array=[0., 1.])
        """
        from discopy import cqmap
        from discopy.quantum.gates import Bits, scalar, ClassicalGate
        if len(others) == 1 and not isinstance(others[0], Circuit):
            # This allows the syntax :code:`circuit.eval(backend)`
            return self.eval(backend=others[0], mixed=mixed, **params)
        if backend is None:
            if others:
                return [circuit.eval(mixed=mixed, **params)
                        for circuit in (self, ) + others]
            functor = cqmap.Functor() if mixed or self.is_mixed\
                else tensor.Functor(lambda x: x[0].dim, lambda f: f.array)
            return functor(self)
        circuits = [circuit.to_tk() for circuit in (self, ) + others]
        results, counts = [], circuits[0].get_counts(
            *circuits[1:], backend=backend, **params)
        for i, circuit in enumerate(circuits):
            n_bits = len(circuit.post_processing.dom)
            result = Tensor.zeros(Dim(1), Dim(*(n_bits * (2, ))))
            for bitstring, count in counts[i].items():
                result += (scalar(count) @ Bits(*bitstring)).eval()
            if circuit.post_processing:
                result = result >> circuit.post_processing.eval()
            results.append(result)
        return results if len(results) > 1 else results[0]