def tensor(self, *others): if len(others) != 1: return monoidal.Diagram.tensor(self, *others) other, = others f = rigid.Box('f', Ty('c00', 'q00', 'q00'), Ty('c10', 'q10', 'q10')) g = rigid.Box('g', Ty('c01', 'q01', 'q01'), Ty('c11', 'q11', 'q11')) above = Diagram.id(f.dom[:1] @ g.dom[:1] @ f.dom[1:2])\ @ Diagram.swap(g.dom[1:2], f.dom[2:]) @ Diagram.id(g.dom[2:])\ >> Diagram.id(f.dom[:1]) @ Diagram.swap(g.dom[:1], f.dom[1:])\ @ Diagram.id(g.dom[1:]) below =\ Diagram.id(f.cod[:1]) @ Diagram.swap(f.cod[1:], g.cod[:1])\ @ Diagram.id(g.cod[1:])\ >> Diagram.id(f.cod[:1] @ g.cod[:1] @ f.cod[1:2])\ @ Diagram.swap(f.cod[2:], g.cod[1:2]) @ Diagram.id(g.cod[2:]) diagram2tensor = tensor.Functor(ob={ Ty("{}{}{}".format(a, b, c)): z.__getattribute__(y).__getattribute__(x) for a, x in zip(['c', 'q'], ['classical', 'quantum']) for b, y in zip([0, 1], ['dom', 'cod']) for c, z in zip([0, 1], [self, other]) }, ar={ f: self.utensor.array, g: other.utensor.array }) return CQMap(self.dom @ other.dom, self.cod @ other.cod, utensor=diagram2tensor(above >> f @ g >> below))
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]