def apply(box, *inputs, offset=None): for node in inputs: if not isinstance(node, Node): raise TypeError(messages.type_err(Node, node)) if len(inputs) != len(box.dom): raise AxiomError("Expected {} inputs, got {} instead.".format( len(box.dom), len(inputs))) depth = len(box_nodes) box_node = Node("box", box=box, depth=depth, offset=offset) box_nodes.append(box_node) graph.add_node(box_node) for i, obj in enumerate(box.dom): if inputs[i].obj != obj: raise AxiomError( "Expected {} as input, got {} instead.".format( obj, inputs[i].obj)) dom_node = Node("dom", obj=obj, i=i, depth=depth) graph.add_edge(inputs[i], dom_node) graph.add_edge(dom_node, box_node) outputs = [] for i, obj in enumerate(box.cod): cod_node = Node("cod", obj=obj, i=i, depth=depth) graph.add_edge(box_node, cod_node) outputs.append(cod_node) return untuplify(*outputs)
def decorator(func): from discopy import messages from discopy.cat import AxiomError from discopy.cartesian import tuplify, untuplify graph, box_nodes = nx.DiGraph(), [] def apply(box, *inputs, offset=None): for node in inputs: if not isinstance(node, Node): raise TypeError(messages.type_err(Node, node)) if len(inputs) != len(box.dom): raise AxiomError("Expected {} inputs, got {} instead.".format( len(box.dom), len(inputs))) depth = len(box_nodes) box_node = Node("box", box=box, depth=depth, offset=offset) box_nodes.append(box_node) graph.add_node(box_node) for i, obj in enumerate(box.dom): if inputs[i].obj != obj: raise AxiomError( "Expected {} as input, got {} instead.".format( obj, inputs[i].obj)) dom_node = Node("dom", obj=obj, i=i, depth=depth) graph.add_edge(inputs[i], dom_node) graph.add_edge(dom_node, box_node) outputs = [] for i, obj in enumerate(box.cod): cod_node = Node("cod", obj=obj, i=i, depth=depth) graph.add_edge(box_node, cod_node) outputs.append(cod_node) return untuplify(*outputs) for box in boxes: box._apply = apply inputs = [] for i, obj in enumerate(dom): node = Node("input", obj=obj, i=i) inputs.append(node) graph.add_node(node) outputs = tuplify(func(*inputs)) for i, obj in enumerate(cod): if outputs[i].obj != obj: raise AxiomError( "Expected {} as output, got {} instead.".format( obj, outputs[i].obj)) node = Node("output", obj=obj, i=i) graph.add_edge(outputs[i], node) for box in boxes: del box._apply result = nx2diagram(graph, ob_factory=type(dom), id_factory=id_factory) if result.cod != cod: raise AxiomError( "Expected diagram.cod == {}, got {} instead.".format( cod, result.cod)) # pragma: no cover return result
def __init__(self, left, right): if not isinstance(left, Ty): raise TypeError(messages.type_err(Ty, left)) if not isinstance(right, Ty): raise TypeError(messages.type_err(Ty, right)) if len(left) != 1 or len(right) != 1: raise ValueError(messages.cap_vs_caps(left, right)) if left != right.r and left.r != right: raise AxiomError(messages.are_not_adjoints(left, right)) if left.r == right: raise AxiomError(messages.wrong_adjunction(left, right, cup=False)) self.left, self.right, self.draw_as_wire = left, right, True super().__init__("Cap({}, {})".format(left, right), Ty(), left @ right)
def __init__(self, name, dim=2, z=0): super().__init__(name) if z != 0: raise AxiomError("circuit.Ob are self-dual.") if not isinstance(dim, int) or dim < 2: raise ValueError("Dimension should be an int greater than 1.") self._dim = dim
def __add__(self, other): if other == 0: return self if not isinstance(other, Tensor): raise TypeError(messages.type_err(Tensor, other)) if (self.dom, self.cod) != (other.dom, other.cod): raise AxiomError(messages.cannot_add(self, other)) return Tensor(self.dom, self.cod, self.array + other.array)
def cups(left, right): if not isinstance(left, Dim): raise TypeError(messages.type_err(Dim, left)) if not isinstance(right, Dim): raise TypeError(messages.type_err(Dim, right)) if left.r != right: raise AxiomError(messages.are_not_adjoints(left, right)) return Tensor(left @ right, Dim(1), Id(left).array)
def then(self, other): if not isinstance(other, Tensor): raise TypeError(messages.type_err(Tensor, other)) if self.cod != other.dom: raise AxiomError(messages.does_not_compose(self, other)) array = np.tensordot(self.array, other.array, len(self.cod))\ if self.array.shape and other.array.shape\ else self.array * other.array return Tensor(self.dom, other.cod, array)
def __init__(self, x, y): if not isinstance(x, Ty): raise TypeError(messages.type_err(Ty, x)) if not isinstance(y, Ty): raise TypeError(messages.type_err(Ty, y)) if x != y.r and x.r != y: raise AxiomError(messages.are_not_adjoints(x, y)) if len(x) != 1 or len(y) != 1: raise ValueError(messages.cap_vs_caps(x, y)) if x.r == y: raise NotImplementedError(messages.pivotal_not_implemented()) super().__init__('CAP', Ty(), x @ y)
def then(self, *others): if len(others) != 1 or any(isinstance(other, Sum) for other in others): return monoidal.Diagram.then(self, *others) other, = others if not isinstance(other, Tensor): raise TypeError(messages.type_err(Tensor, other)) if self.cod != other.dom: raise AxiomError(messages.does_not_compose(self, other)) array = Tensor.np.tensordot(self.array, other.array, len(self.cod))\ if self.array.shape and other.array.shape\ else self.array * other.array return Tensor(self.dom, other.cod, array)
def __init__(self, left, right): if not isinstance(left, Ty): raise TypeError(messages.type_err(Ty, left)) if not isinstance(right, Ty): raise TypeError(messages.type_err(Ty, right)) if len(left) != 1 or len(right) != 1: raise ValueError(messages.cup_vs_cups(left, right)) if left.r != right and left != right.r: raise AxiomError(messages.are_not_adjoints(left, right)) self.left, self.right = left, right super().__init__("Cup({}, {})".format(left, right), left @ right, Ty()) self.draw_as_wires = True
def __init__(self, left, right): if not isinstance(left, Ty): raise TypeError(messages.type_err(Ty, left)) if not isinstance(right, Ty): raise TypeError(messages.type_err(Ty, right)) if len(left) != 1 or len(right) != 1: raise ValueError(messages.cap_vs_caps(left, right)) if left != right.r and left.r != right: raise AxiomError(messages.are_not_adjoints(left, right)) monoidal.BinaryBoxConstructor.__init__(self, left, right) Box.__init__( self, "Cap({}, {})".format(left, right), Ty(), left @ right) self.draw_as_wires = True
def cups(left, right, ar_factory=Diagram, cup_factory=Cup, reverse=False): """ Constructs a diagram of nested cups. """ for typ in left, right: if not isinstance(typ, Ty): raise TypeError(messages.type_err(Ty, typ)) if left.r != right and right.r != left: raise AxiomError(messages.are_not_adjoints(left, right)) result = ar_factory.id(left @ right) for i in range(len(left)): j = len(left) - i - 1 cup = cup_factory(left[j:j + 1], right[i:i + 1]) layer = ar_factory.id(left[:j]) @ cup @ ar_factory.id(right[i + 1:]) result = result << layer if reverse else result >> layer return result
def then(self, other): """ Implements the sequential composition of Python functions. >>> copy = Function(1, 2, lambda *x: x + x) >>> swap = Function(2, 2, lambda x, y: (y, x)) >>> assert (copy >> swap)(1) == copy(1) >>> assert (swap >> swap)(1, 2) == (1, 2) """ if not isinstance(other, Function): raise TypeError(messages.type_err(Function, other)) if len(self.cod) != len(other.dom): raise AxiomError(messages.does_not_compose(self, other)) return Function(self.dom, other.cod, lambda *vals: other(*tuplify(self(*vals))))
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 ba(left, right): """ Backward application. """ if right.left != left: raise AxiomError(messages.are_not_adjoints(left, right)) return BA(right)
def fa(left, right): """ Forward application. """ if left.right != right: raise AxiomError(messages.are_not_adjoints(left, right)) return FA(left)
def __add__(self, other): if other == 0: return self if (self.dom, self.cod) != (other.dom, other.cod): raise AxiomError(messages.cannot_add(self, other)) return CQMap(self.dom, self.cod, self.array + other.array)
def eval(self, *others, backend=None, mixed=False, **params): """ Evaluate a circuit on a backend, or simulate it with numpy. Parameters ---------- others : 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 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.]) """ 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 ] post_processes = Id(self.cod) for left, box, right in self.layers: if isinstance(box, ClassicalGate) and not box.is_linear: if left.count(bit) or right.count(bit): raise AxiomError("You can't tensor non-linear gates.") post_processes = (post_processes or Id(box.dom)) >> box elif post_processes and not isinstance(box, ClassicalGate): raise AxiomError("You can't do anything quantum " "after non-linear gates.") circuit = self[:-len(post_processes) or len(self)] functor = cqmap.Functor() if mixed or self.is_mixed\ else tensor.Functor(lambda x: 2, lambda f: f.array) result = functor(circuit) for process in post_processes.boxes: result = process(result) return result 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() for process in circuit.post_processing.boxes: result = process(result) results.append(result) return results if len(results) > 1 else results[0]