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
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
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
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)
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))
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)
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]
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)
def cups(left, right): return CQMap.classical(Tensor.cups(left.classical, right.classical))\ @ CQMap.pure(Tensor.cups(left.quantum, right.quantum))
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)
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)
def utensor(self): """ Underlying tensor. """ return Tensor(self._udom, self._ucod, self.array)
def id(dom=CQ()): utensor = Tensor.id(dom.classical @ dom.quantum @ dom.quantum) return CQMap(dom, dom, utensor=utensor)
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)
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)
def id(dom): data = Tensor.id(dom.classical @ dom.quantum @ dom.quantum) return CQMap(dom, dom, data.array)
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))
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]