Esempio n. 1
0
 def _ob(self, typ):
     """ Overrides the input mapping on objects for Digit and Qudit. """
     obj, = typ
     if isinstance(obj, Digit):
         return C(Dim(obj.dim))
     if isinstance(obj, Qudit):
         return Q(Dim(obj.dim))
     return self.__ob[typ]
Esempio n. 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
Esempio n. 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
Esempio n. 4
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)
Esempio n. 5
0
 def __init__(self, dim=Dim(1)):
     super().__init__(Dim(1), dim)
Esempio n. 6
0
 def __init__(self, dim=Dim(1)):
     super().__init__(dim, Dim(1))
Esempio n. 7
0
 def __init__(self, classical=Dim(1), quantum=Dim(1)):
     self.classical, self.quantum = classical, quantum
     types = [Ob("C({})".format(dim)) for dim in classical]\
         + [Ob("Q({})".format(dim)) for dim in quantum]
     super().__init__(*types)
Esempio n. 8
0
 def __init__(self, ob=None, ar=None):
     ob, ar = ob or {bit: C(Dim(2)), qubit: Q(Dim(2))}, ar or {}
     super().__init__(ob, ar, ob_factory=CQ, ar_factory=CQMap)
Esempio n. 9
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]
Esempio n. 10
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))
Esempio n. 11
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)