def cups(left, right): """ >>> bell_state = Circuit.cups(PRO(1), PRO(1)) >>> print(bell_state) CX >> H @ Id(1) >> Id(1) @ sqrt(2) @ Id(1) >> Bra(0, 0) >>> assert np.allclose(bell_state.eval().array, [[1, 0], [0, 1]]) >>> double_bell_state = Circuit.cups(PRO(2), PRO(2)) >>> print('\\n>> '.join(str(layer) for layer in double_bell_state)) Id(1) @ CX @ Id(1) >> Id(1) @ H @ Id(2) >> Id(2) @ sqrt(2) @ Id(2) >> Id(1) @ Bra(0, 0) @ Id(1) >> CX >> H @ Id(1) >> Id(1) @ sqrt(2) @ Id(1) >> Bra(0, 0) """ if not isinstance(left, PRO): raise TypeError(messages.type_err(PRO, left)) if not isinstance(right, PRO): raise TypeError(messages.type_err(PRO, right)) result = Id(left @ right) cup = CX >> H @ sqrt(2) @ Id(1) >> Bra(0, 0) for i in range(1, len(left) + 1): result = result >> Id(len(left) - i) @ cup @ Id(len(left) - i) return result
def __init__(self, name, dom, cod, **params): if not isinstance(dom, PRO): raise TypeError(messages.type_err(PRO, dom)) if not isinstance(cod, PRO): raise TypeError(messages.type_err(PRO, cod)) rigid.Box.__init__(self, name, dom, cod, **params) Diagram.__init__(self, dom, cod, [self], [0])
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 __init__(self, name, cod, dom=Ty(), data=None, _dagger=False): if not isinstance(name, str): raise TypeError(messages.type_err(str, name)) if not isinstance(dom, Ty): raise TypeError(messages.type_err(Ty, dom)) if not isinstance(cod, Ty): raise TypeError(messages.type_err(Ty, cod)) super().__init__(name, dom, cod, data, _dagger)
def __init__(self, left, right): if not isinstance(left, Over): raise TypeError(messages.type_err(Over, left)) if not isinstance(right, Over): raise TypeError(messages.type_err(Over, right)) name = "FC({}, {})".format(left, right) dom, cod = left @ right, left.left << right.right super().__init__(name, dom, cod)
def __init__(self, name, cod, dom=None, data=None, _dagger=False, _z=0): if not isinstance(name, str): raise TypeError(messages.type_err(str, name)) if not isinstance(cod, Ty): raise TypeError(messages.type_err(Ty, cod)) dom = dom or cod[0:0] if not isinstance(dom, Ty): raise TypeError(messages.type_err(Ty, dom)) super().__init__(name, dom, cod, data=data, _dagger=_dagger, _z=_z)
def __init__(self, left, right): if not isinstance(left, Over): raise TypeError(messages.type_err(Under, left)) if not isinstance(right, Under): raise TypeError(messages.type_err(Under, right)) if left.left != right.left: raise TypeError(messages.does_not_compose(left, right)) name = "BX({}, {})".format(left, right) dom, cod = left @ right, right.right << left.right super().__init__(name, dom, cod)
def __init__(self, left, right): if not isinstance(left, Over): raise TypeError(messages.type_err(Over, left)) if not isinstance(right, Under): raise TypeError(messages.type_err(Over, right)) if left.right != right.right: raise TypeError(messages.does_not_compose(left, right)) name = "FX({}, {})".format(left, right) dom, cod = left @ right, right.left >> left.left Box.__init__(self, name, dom, cod) BinaryBoxConstructor.__init__(self, left, right)
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)) self.left, self.right = left, right super().__init__("Cap({}, {})".format(left, right), Ty(), left @ right) self.draw_as_wires = True
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 __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)) if left == right.r: raise AxiomError(messages.wrong_adjunction(left, right, cup=True)) self.left, self.right, self.draw_as_wire = left, right, True super().__init__("Cup({}, {})".format(left, right), left @ right, Ty())
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 tensor(self, *others): """ Returns the tensor of types, i.e. the concatenation of their lists of objects. This is called with the binary operator `@`. >>> Ty('x') @ Ty('y', 'z') Ty('x', 'y', 'z') Parameters ---------- other : monoidal.Ty Returns ------- t : monoidal.Ty such that :code:`t.objects == self.objects + other.objects`. Note ---- We can take the sum of a list of type, specifying the unit `Ty()`. >>> types = Ty('x'), Ty('y'), Ty('z') >>> Ty().tensor(*types) Ty('x', 'y', 'z') We can take the exponent of a type by any natural number. >>> Ty('x') ** 3 Ty('x', 'x', 'x') """ for other in others: if not isinstance(other, Ty): raise TypeError(messages.type_err(Ty, other)) return Ty(*sum([t.objects for t in [self] + list(others)], []))
def __call__(self, diagram): if isinstance(diagram, monoidal.Ty): def adjoint(obj): if not hasattr(obj, "z") or not obj.z: return self.ob[type(diagram)(obj)] result = self.ob[type(diagram)(type(obj)(obj.name, z=0))] if obj.z < 0: for _ in range(-obj.z): result = result.l elif obj.z > 0: for _ in range(obj.z): result = result.r return result return self.ob_factory().tensor(*map(adjoint, diagram.objects)) if isinstance(diagram, Cup): return self.ar_factory.cups(self(diagram.dom[:1]), self(diagram.dom[1:])) if isinstance(diagram, Cap): return self.ar_factory.caps(self(diagram.cod[:1]), self(diagram.cod[1:])) if isinstance(diagram, monoidal.Diagram): return super().__call__(diagram) raise TypeError(messages.type_err(Diagram, diagram))
def __init__(self, *dims): for dim in dims: if not isinstance(dim, int): raise TypeError(messages.type_err(int, dim)) if dim < 1: raise ValueError super().__init__(*[Ob(dim) for dim in dims if dim > 1])
def __init__(self, name, dom, cod, is_mixed=True, _dagger=False): if dom and not isinstance(dom, BitsAndQubits): raise TypeError(messages.type_err(BitsAndQubits, dom)) if cod and not isinstance(cod, BitsAndQubits): raise TypeError(messages.type_err(BitsAndQubits, cod)) rigid.Box.__init__(self, name, dom, cod, _dagger=_dagger) Circuit.__init__(self, dom, cod, [self], [0]) if not is_mixed: if all(x.name == "bit" for x in dom @ cod): self.classical = True elif all(x.name == "qubit" for x in dom @ cod): self.classical = False else: raise ValueError( "dom and cod should be bits only or qubits only.") self._mixed = is_mixed
def tensor(self, *others): """ Returns the horizontal composition of 'self' with a diagram 'other'. This method is called using the binary operator `@`: >>> x, y, z, w = Ty('x'), Ty('y'), Ty('z'), Ty('w') >>> f0, f1 = Box('f0', x, y), Box('f1', z, w) >>> assert f0 @ f1 == f0.tensor(f1) == f0 @ Id(z) >> Id(y) @ f1 Parameters ---------- other : :class:`Diagram` Returns ------- diagram : :class:`Diagram` the tensor of 'self' and 'other'. """ if not others: return self if len(others) > 1: return self.tensor(others[0]).tensor(*others[1:]) other = others[0] if not isinstance(other, Diagram): raise TypeError(messages.type_err(Diagram, other)) dom, cod = self.dom @ other.dom, self.cod @ other.cod boxes = self.boxes + other.boxes offsets = self.offsets + [n + len(self.cod) for n in other.offsets] layers = cat.Id(dom) for left, box, right in self.layers: layers = layers >> Layer(left, box, right @ other.dom) for left, box, right in other.layers: layers = layers >> Layer(self.cod @ left, box, right) return Diagram(dom, cod, boxes, offsets, layers=layers)
def __call__(self, diagram): """ >>> x, y, z = Ty('x'), Ty('y'), Ty('z') >>> f, g = Box('f', x, y), Box('g', y, z) >>> F = Functor({x: y, y: z}, {f: g}) >>> assert F(f.transpose_l()) == F(f).transpose_l() >>> assert F(f.transpose_r()) == F(f).transpose_r() """ if isinstance(diagram, Ty): return sum([self(b) for b in diagram.objects], self.ob_factory()) if isinstance(diagram, Ob) and not diagram.z: return self.ob[Ty(diagram.name)] if isinstance(diagram, Ob): result = self(Ob(diagram.name, z=0)) if diagram.z < 0: for _ in range(-diagram.z): result = result.l elif diagram.z > 0: for _ in range(diagram.z): result = result.r return result if isinstance(diagram, Cup): return self.ar_factory.cups( self(diagram.dom[0]), self(diagram.dom[1])) if isinstance(diagram, Cap): return self.ar_factory.caps( self(diagram.cod[0]), self(diagram.cod[1])) if isinstance(diagram, Diagram): return super().__call__(diagram) raise TypeError(messages.type_err(Diagram, diagram))
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 __pow__(self, n_times): if not isinstance(n_times, int): raise TypeError(messages.type_err(int, n_times)) result = type(self)() for _ in range(n_times): result = result @ self return result
def to_pyzx(self): """ Returns a :class:`pyzx.Graph`. >>> bialgebra = Z(1, 2, .25) @ Z(1, 2, .75)\\ ... >> Id(1) @ SWAP @ Id(1) >> X(2, 1, .5) @ X(2, 1, .5) >>> graph = bialgebra.to_pyzx() >>> assert len(graph.vertices()) == 8 >>> assert (graph.inputs, graph.outputs) == ([0, 1], [6, 7]) >>> from pyzx import VertexType >>> assert graph.type(2) == graph.type(3) == VertexType.Z >>> assert graph.phase(2) == 2 * .25 and graph.phase(3) == 2 * .75 >>> assert graph.type(4) == graph.type(5) == VertexType.X >>> assert graph.phase(4) == graph.phase(5) == 2 * .5 >>> assert graph.graph == { ... 0: {2: 1}, ... 1: {3: 1}, ... 2: {0: 1, 4: 1, 5: 1}, ... 3: {1: 1, 4: 1, 5: 1}, ... 4: {2: 1, 3: 1, 6: 1}, ... 5: {2: 1, 3: 1, 7: 1}, ... 6: {4: 1}, ... 7: {5: 1}} """ from pyzx import Graph, VertexType, EdgeType graph, scan = Graph(), [] for i, _ in enumerate(self.dom): node, hadamard = graph.add_vertex(VertexType.BOUNDARY), False scan.append((node, hadamard)) graph.inputs.append(node) graph.set_position(node, i, 0) for row, (box, offset) in enumerate(zip(self.boxes, self.offsets)): if isinstance(box, Spider): node = graph.add_vertex( VertexType.Z if isinstance(box, Z) else VertexType.X, phase=box.phase * 2 if box.phase else None) graph.set_position(node, offset, row + 1) for i, _ in enumerate(box.dom): input, hadamard = scan[offset + i] etype = EdgeType.HADAMARD if hadamard else EdgeType.SIMPLE graph.add_edge((input, node), etype) scan = scan[:offset] + len(box.cod) * [(node, False)]\ + scan[offset + len(box.dom):] elif isinstance(box, Swap): scan = scan[:offset] + [scan[offset + 1], scan[offset]]\ + scan[offset + 2:] elif box == H: node, hadamard = scan[offset] scan[offset] = (node, not hadamard) else: raise TypeError(messages.type_err(Box, box)) for i, _ in enumerate(self.cod): node = graph.add_vertex(VertexType.BOUNDARY) input, hadamard = scan[i] etype = EdgeType.HADAMARD if hadamard else EdgeType.SIMPLE graph.add_edge((input, node), etype) graph.set_position(node, i, len(self) + 1) graph.outputs.append(node) return graph
def tensor(self, other): if not isinstance(other, Tensor): raise TypeError(messages.type_err(Tensor, other)) dom, cod = self.dom + other.dom, self.cod + other.cod array = np.tensordot(self.array, other.array, 0)\ if self.array.shape and other.array.shape\ else self.array * other.array return Tensor(dom, cod, array)
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 __init__(self, name, dom, cod, is_mixed=True, data=None, _dagger=False): if dom and not isinstance(dom, Ty): raise TypeError(messages.type_err(Ty, dom)) if cod and not isinstance(cod, Ty): raise TypeError(messages.type_err(Ty, cod)) rigid.Box.__init__(self, name, dom, cod, data=data, _dagger=_dagger) Circuit.__init__(self, dom, cod, [self], [0]) if not is_mixed: if all(isinstance(x, Digit) for x in dom @ cod): self.classical = True elif all(isinstance(x, Qudit) for x in dom @ cod): self.classical = False else: raise ValueError( "dom and cod should be Digits only or Qudits only.") self._mixed = is_mixed
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, dom, cod, boxes, _scan=True): if not isinstance(dom, Ob): raise TypeError(messages.type_err(Ob, dom)) if not isinstance(cod, Ob): raise TypeError(messages.type_err(Ob, cod)) if _scan: scan = dom for depth, box in enumerate(boxes): if not isinstance(box, Arrow): raise TypeError(messages.type_err(Arrow, box)) if box.dom != scan: raise AxiomError(messages.does_not_compose( boxes[depth - 1] if depth else Id(dom), box)) scan = box.cod if scan != cod: raise AxiomError(messages.does_not_compose( boxes[-1] if boxes else Id(dom), Id(cod))) self._dom, self._cod, self._boxes = dom, cod, boxes
def __init__(self, *dims): dims = map(lambda x: x if isinstance(x, monoidal.Ob) else Ob(x), dims) dims = list(filter(lambda x: x.name != 1, dims)) # Dim(1) == Dim() for dim in dims: if not isinstance(dim.name, int): raise TypeError(messages.type_err(int, dim.name)) if dim.name < 1: raise ValueError super().__init__(*dims)
def caps(left, right): """ Constructs nested cups witnessing adjointness of x and y >>> a, b = Ty('a'), Ty('b') >>> assert Diagram.caps(a, a.l) == Cap(a, a.l) >>> assert Diagram.caps(a @ b, (a @ b).l) == (Cap(a, a.l) ... >> Id(a) @ Cap(b, b.l) @ Id(a.l)) """ if not isinstance(left, Ty): raise TypeError(messages.type_err(Ty, left)) if not isinstance(right, Ty): raise TypeError(messages.type_err(Ty, right)) caps = Id(left @ right) for i in range(len(left)): j = len(left) - i - 1 cap = Cap(left[j:j + 1], right[i:i + 1]) caps = caps << Id(left[:j]) @ cap @ Id(right[i + 1:]) return caps
def cups(left, right): """ Constructs nested cups witnessing adjointness of x and y >>> a, b = Ty('a'), Ty('b') >>> assert Diagram.cups(a, a.r) == Cup(a, a.r) >>> assert Diagram.cups(a @ b, (a @ b).r) ==\\ ... Id(a) @ Cup(b, b.r) @ Id(a.r) >> Cup(a, a.r) """ if not isinstance(left, Ty): raise TypeError(messages.type_err(Ty, left)) if not isinstance(right, Ty): raise TypeError(messages.type_err(Ty, right)) cups = Id(left @ right) for i in range(len(left)): j = len(left) - i - 1 cup = Cup(left[j:j + 1], right[i:i + 1]) cups = cups >> Id(left[:j]) @ cup @ Id(right[i + 1:]) return cups
def draw(diagram, **params): """ Draws a pregroup diagram, i.e. of shape :code:`word @ ... @ word >> cups`. Parameters ---------- width : float, optional Width of the word triangles, default is :code:`2.0`. space : float, optional Space between word triangles, default is :code:`0.5`. textpad : pair of floats, optional Padding between text and wires, default is :code:`(0.1, 0.2)`. draw_type_labels : bool, optional Whether to draw type labels, default is :code:`True`. aspect : string, optional Aspect ratio, one of :code:`['equal', 'auto']`. margins : tuple, optional Margins, default is :code:`(0.05, 0.05)`. fontsize : int, optional Font size for the words, default is :code:`12`. fontsize_types : int, optional Font size for the types, default is :code:`12`. figsize : tuple, optional Figure size. path : str, optional Where to save the image, if :code:`None` we call :code:`plt.show()`. pretty_types : bool, optional Whether to draw type labels with superscript, default is :code:`False`. triangles : bool, optional Whether to draw words as triangular states, default is :code:`False`. Raises ------ ValueError Whenever the input is not a pregroup diagram. """ from discopy.rigid import Swap if not isinstance(diagram, Diagram): raise TypeError(messages.type_err(Diagram, diagram)) words, is_pregroup = Id(Ty()), True for _, box, right in diagram.layers: if isinstance(box, Word): if right: # word boxes should be tensored left to right. is_pregroup = False break words = words @ box else: break cups = diagram[len(words):].foliation().boxes\ if len(words) < len(diagram) else [] is_pregroup = is_pregroup and words and all( isinstance(box, Cup) or isinstance(box, Swap) for s in cups for box in s.boxes) if not is_pregroup: raise ValueError(messages.expected_pregroup()) drawing.pregroup_draw(words, cups, **params)