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]
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 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 __init__(self, dim=Dim(1)): super().__init__(Dim(1), dim)
def __init__(self, dim=Dim(1)): super().__init__(dim, Dim(1))
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)
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)
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]
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, 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)