def __call__(self, *values): """ Call method implemented using PythonFunctors. >>> assert SWAP(1, 2) == (2, 1) >>> assert (COPY @ COPY >> Id(1) @ SWAP @ Id(1))(1, 2) == (1, 2, 1, 2) """ ob = Quiver(lambda t: PRO(len(t))) ar = Quiver(lambda f: Function(len(f.dom), len(f.cod), f.function)) return PythonFunctor(ob, ar)(self)(*values)
def flatten(self): """ Takes a diagram of diagrams and returns a diagram. >>> x, y = Ty('x'), Ty('y') >>> f0, f1 = Box('f0', x, y), Box('f1', y, x) >>> g = Box('g', x @ y, y) >>> d = (Id(y) @ f0 @ Id(x) >> f0.dagger() @ Id(y) @ f0 >>\\ ... g @ f1 >> f1 @ Id(x)).normal_form() >>> assert d.foliation().flatten().normal_form() == d >>> assert d.foliation().dagger().flatten()\\ ... == d.foliation().flatten().dagger() """ return Functor(Quiver(lambda x: x), Quiver(lambda f: f))(self)
def eval(self): """ Evaluates the circuit as a discopy Tensor. Returns ------- tensor : :class:`discopy.tensor.Tensor` with complex amplitudes as entries. Examples -------- >>> state, isometry = Ket(1, 1), Id(1) @ Bra(0) >>> assert state.eval() >> isometry.eval()\\ ... == (state >> isometry).eval() """ return TensorFunctor({Ty(1): 2}, Quiver(lambda g: g.array))(self)
def normalize(self, _dagger=False): """ Multiplies all the scalars in the diagram. Moves the kets to the top of the diagram, adding swaps if necessary. Fuses them into preparation layers. Moves the bras to the bottom of the diagram, Fuses them into meaurement layers. >>> circuit = sqrt(2) @ Ket(1, 0) >> CX >> Id(1) @ Ket(0) @ Id(1) >>> for step in circuit.normalize(): ... print(', '.join(map(str, step.boxes))) Ket(1, 0), CX, Ket(0), 1.414 Ket(1), Ket(0), CX, Ket(0), 1.414 Ket(1), Ket(0), CX, Ket(0), 1.414 Ket(0), Ket(1), Ket(0), CX, SWAP, 1.414 Ket(0), Ket(1), Ket(0), 1.414, CX, SWAP Ket(0, 1), Ket(0), 1.414, CX, SWAP Ket(0, 1, 0), 1.414, CX, SWAP """ def remove_scalars(diagram): for i, box in enumerate(diagram.boxes): if box.dom == box.cod == PRO(): return diagram[:i] >> diagram[i + 1:], box.array[0] return diagram, None def find_ket(diagram): boxes, offsets = diagram.boxes, diagram.offsets for i in range(len(diagram) - 1): if isinstance(boxes[i], Ket) and isinstance(boxes[i + 1], Ket)\ and offsets[i + 1] == offsets[i] + len(boxes[i].cod): return i return None def fuse_kets(diagram, i): boxes, offsets = diagram.boxes, diagram.offsets ket = Ket(*(boxes[i].bitstring + boxes[i + 1].bitstring)) return Circuit(len(diagram.dom), len(diagram.cod), boxes[:i] + [ket] + boxes[i + 2:], offsets[:i + 1] + offsets[i + 2:]) def unfuse(ket): if not isinstance(ket, Ket): return ket result = Id(0) for bit in ket.bitstring: result = result @ Ket(bit) return result diagram = self # step 0: multiply scalars to the right of the diagram if not _dagger: scalar = 1 while True: diagram, number = remove_scalars(diagram) if number is None: break scalar = scalar * number yield diagram @ Gate('{0:.3f}'.format(scalar), 0, [scalar]) diagram = diagram @ Gate('{0:.3f}'.format(scalar), 0, [scalar]) # step 1: unfuse all kets before = diagram diagram = CircuitFunctor(ob=Quiver(len), ar=Quiver(unfuse))(diagram) if diagram != before: yield diagram # step 2: move kets to the bottom of the diagram by foliating ket_count = sum( [1 if isinstance(box, Ket) else 0 for box in diagram.boxes]) gen = diagram.foliate() for _ in range(ket_count): diagram = next(gen) yield diagram # step 4: fuse kets while True: fusable = find_ket(diagram) if fusable is None: break diagram = fuse_kets(diagram, fusable) yield diagram # step 5: repeat for bras using dagger if not _dagger: for _diagram in diagram.dagger().normalize(_dagger=True): yield _diagram.dagger()
def to_tk(self): def remove_ket1(box): if not isinstance(box, Ket): return box x_gates = Circuit.id(0) for bit in box.bitstring: x_gates = x_gates @ (X if bit else Circuit.id(1)) return Ket(*(len(box.bitstring) * (0, ))) >> x_gates def swap(tk_circ, i, j): old = Qubit('q', i) tmp = Qubit('tmp', 0) new = Qubit('q', j) tk_circ.rename_units({old: tmp}) tk_circ.rename_units({new: old}) tk_circ.rename_units({tmp: new}) def prepare_qubit(tk_circ, left, box, right): if len(right) > 0: renaming = dict() for i in range(len(left), tk_circ.n_qubits): old = Qubit('q', i) new = Qubit('q', i + len(box.cod)) renaming.update({old: new}) tk_circ.rename_units(renaming) tk_circ.add_blank_wires(len(box.cod)) def add_gate(tk_circ, box, off): qubits = [off + j for j in range(len(box.dom))] if isinstance(box, (Rx, Rz)): tk_circ.__getattribute__(box.name[:2])(2 * box.phase, *qubits) elif isinstance(box, CRz): tk_circ.__getattribute__(box.name[:3])(2 * box.phase, *qubits) else: tk_circ.__getattribute__(box.name)(*qubits) def measure_qubit(tk_circ, left, box, right): if len(right) > 0: renaming = dict() for i, _ in enumerate(box.dom): old = Qubit('q', len(left) + i) tmp = Qubit('tmp', i) renaming.update({old: tmp}) for i, _ in enumerate(right): old = Qubit('q', len(left @ box.dom) + i) new = Qubit('q', len(left) + i) renaming.update({old: new}) tk_circ.rename_units(renaming) renaming = dict() for j, _ in enumerate(box.dom): tmp = Qubit('tmp', j) new = Qubit('q', len(left @ right) + j) renaming.update({tmp: new}) tk_circ.rename_units(renaming) return { len(left @ right) + j: box.bitstring[j] for j, _ in enumerate(box.dom) } circuit = CircuitFunctor(ob=Quiver(len), ar=Quiver(remove_ket1))(self) if circuit.dom != PRO(0): circuit = Ket(*(len(circuit.dom) * (0, ))) >> circuit tk_circ = TketCircuit() for left, box, right in circuit.layers: if isinstance(box, Ket): prepare_qubit(tk_circ, left, box, right) elif isinstance(box, Bra): tk_circ.post_selection.update( measure_qubit(tk_circ, left, box, right)) elif box == SWAP: swap(tk_circ, len(left), len(left) + 1) elif box.dom == box.cod == PRO(0): tk_circ.scalar *= box.array[0] else: add_gate(tk_circ, box, len(left)) return tk_circ