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 measure(self, mixed=False): """ Measures a circuit on the computational basis using :code:`numpy`. Parameters ---------- mixed : bool, optional Whether to apply :class:`tensor.Functor` or :class:`CQMapFunctor`. Returns ------- array : numpy.ndarray """ from discopy.quantum.gates import Bra, Ket if mixed or self.is_mixed: return self.init_and_discard().eval(mixed=True).array.real state = (Ket(*(len(self.dom) * [0])) >> self).eval() effects = [ Bra(*index2bitstring(j, len(self.cod))).eval() for j in range(2**len(self.cod)) ] array = np.zeros(len(self.cod) * (2, ) or (1, )) for effect in effects: array += effect.array * np.absolute((state >> effect).array)**2 return array
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 measure(self, mixed=False): """ Measures a circuit on the computational basis. Parameters ---------- mixed : bool, optional Whether to apply :class:`TensorFunctor` or :class:`CQMapFunctor`. Returns ------- array : numpy.ndarray """ circuit = self.init_and_discard() if mixed or circuit.is_mixed: return circuit.eval(mixed=True).array.real state = circuit.eval() effects = [Bra(*index2bitstring(j, len(circuit.cod))).eval() for j in range(2 ** len(circuit.cod))] array = np.zeros(len(circuit.cod) * (2, ) or (1, )) for effect in effects: array += effect.array * np.absolute((state >> effect).array) ** 2 return 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)