def add_bit(self, unit, offset=None): """ Add a bit, update post_processing. """ if offset is not None: self.post_processing @= Id(bit) self.post_processing >>= Id(bit ** offset)\ @ Id.swap(self.post_processing.cod[offset:-1], bit) super().add_bit(unit)
def random_tiling(n_qubits, depth=3, gateset=None, seed=None): """ Returns a random Euler decomposition if n_qubits == 1, otherwise returns a random tiling with the given depth and gateset. >>> c = random_tiling(1, seed=420) >>> print(c) Rx(0.0263) >> Rz(0.781) >> Rx(0.273) >>> print(random_tiling(2, 2, gateset=[CX, H, T], seed=420)) CX >> T @ Id(1) >> Id(1) @ T >>> print(random_tiling(3, 2, gateset=[CX, H, T], seed=420)) CX @ Id(1) >> Id(2) @ T >> H @ Id(2) >> Id(1) @ H @ Id(1) >> Id(2) @ H >>> print(random_tiling(2, 1, gateset=[Rz, Rx], seed=420)) Rz(0.673) @ Id(1) >> Id(1) @ Rx(0.273) """ gateset = gateset or [H, Rx, CX] if seed is not None: random.seed(seed) if n_qubits == 1: phases = [random.random() for _ in range(3)] return Rx(phases[0]) >> Rz(phases[1]) >> Rx(phases[2]) result = Id(n_qubits) for _ in range(depth): line, n_affected = Id(0), 0 while n_affected < n_qubits: gate = random.choice( gateset if n_qubits - n_affected > 1 else [g for g in gateset if g is Rx or g is Rz or len(g.dom) == 1]) if gate is Rx or gate is Rz: gate = gate(random.random()) line = line @ gate n_affected += len(gate.dom) result = result >> line return result
def make_units_adjacent(tk_gate): offset = tk_gate.qubits[0].index[0] swaps = Id(qubit**n_qubits @ bit**n_bits) for i, tk_qubit in enumerate(tk_gate.qubits[1:]): source, target = tk_qubit.index[0], offset + i + 1 if source < target: left, right = swaps.cod[:source], swaps.cod[target:] swap = Id.swap(swaps.cod[source:source + 1], swaps.cod[source + 1:target]) if source <= offset: offset -= 1 elif source > target: left, right = swaps.cod[:target], swaps.cod[source + 1:] swap = Id.swap(swaps.cod[target:target + 1], swaps.cod[target + 1:source + 1]) else: # pragma: no cover continue # units are adjacent already swaps = swaps >> Id(left) @ swap @ Id(right) return offset, swaps
def __init__(self, n_qubits=0, n_bits=0, post_selection=None, scalar=None, post_processing=None): self.post_selection = post_selection or {} self.scalar = scalar or 1 self.post_processing = post_processing\ or Id(bit ** (n_bits - len(self.post_selection))) super().__init__(n_qubits, n_bits)
def grad(self, var, **params): if var not in self.free_symbols: return Sum([], self.dom, self.cod) if params.get('mixed', True): return super().grad(var, **params) gradient = self.phase.diff(var) gradient = complex(gradient) if not gradient.free_symbols else gradient _i_half_pi = .5j * self.modules.pi op1 = Z @ X @ scalar(_i_half_pi * gradient) op2 = Id(qubit) @ X @ scalar(-_i_half_pi * gradient) return self >> (op1 + op2)
def __init__(self, n_qubits, params): def layer(thetas): hadamards = Id(0).tensor(*(n_qubits * [H])) rotations = Id(n_qubits).then( *(Id(i) @ CRz(thetas[i]) @ Id(n_qubits - 2 - i) for i in range(n_qubits - 1))) return hadamards >> rotations if n_qubits == 1: circuit = Rx(params[0]) >> Rz(params[1]) >> Rx(params[2]) elif len(np.shape(params)) != 2 or np.shape(params)[1] != n_qubits - 1: raise ValueError( "Expected params of shape (depth, {})".format(n_qubits - 1)) else: depth = np.shape(params)[0] circuit = Id(n_qubits).then(*(layer(params[i]) for i in range(depth))) super().__init__(circuit.dom, circuit.cod, circuit.boxes, circuit.offsets)
def from_tk(tk_circuit): """ Translates from tket to discopy. """ if not isinstance(tk_circuit, tk.Circuit): raise TypeError(messages.type_err(tk.Circuit, tk_circuit)) if not isinstance(tk_circuit, Circuit): tk_circuit = Circuit.upgrade(tk_circuit) n_bits = tk_circuit.n_bits - len(tk_circuit.post_selection) n_qubits = tk_circuit.n_qubits def box_from_tk(tk_gate): name = tk_gate.op.type.name if name == 'Rx': return Rx(tk_gate.op.params[0] / 2) if name == 'Rz': return Rz(tk_gate.op.params[0] / 2) if name == 'CRz': return CRz(tk_gate.op.params[0] / 2) for gate in GATES: if name == gate.name: return gate raise NotImplementedError def make_units_adjacent(tk_gate): offset = tk_gate.qubits[0].index[0] swaps = Id(qubit**n_qubits @ bit**n_bits) for i, tk_qubit in enumerate(tk_gate.qubits[1:]): source, target = tk_qubit.index[0], offset + i + 1 if source < target: left, right = swaps.cod[:source], swaps.cod[target:] swap = Id.swap(swaps.cod[source:source + 1], swaps.cod[source + 1:target]) if source <= offset: offset -= 1 elif source > target: left, right = swaps.cod[:target], swaps.cod[source + 1:] swap = Id.swap(swaps.cod[target:target + 1], swaps.cod[target + 1:source + 1]) else: # pragma: no cover continue # units are adjacent already swaps = swaps >> Id(left) @ swap @ Id(right) return offset, swaps circuit = Id(0).tensor(*(n_qubits * [Ket(0)] + n_bits * [Bits(0)])) bras = {} for tk_gate in tk_circuit.get_commands(): if tk_gate.op.type.name == "Measure": offset = tk_gate.qubits[0].index[0] bit_index = tk_gate.bits[0].index[0] if bit_index in tk_circuit.post_selection: bras[offset] = tk_circuit.post_selection[bit_index] continue # post selection happens at the end box = Measure(destructive=False, override_bits=True) swaps = Id(circuit.cod[:offset + 1]) swaps = swaps @ Id.swap( circuit.cod[offset + 1:n_qubits + bit_index], circuit.cod[n_qubits:][bit_index: bit_index + 1])\ @ Id(circuit.cod[n_qubits + bit_index + 1:]) else: box = box_from_tk(tk_gate) offset, swaps = make_units_adjacent(tk_gate) left, right = swaps.cod[:offset], swaps.cod[offset + len(box.dom):] circuit = circuit >> swaps >> Id(left) @ box @ Id(right) >> swaps[::-1] circuit = circuit >> Id(0).tensor(*(Bra(bras[i]) if i in bras else Discard( ) if x.name == 'qubit' else Id(bit) for i, x in enumerate(circuit.cod))) if tk_circuit.scalar != 1: circuit = circuit @ MixedScalar(tk_circuit.scalar) return circuit >> tk_circuit.post_processing
def remove_ket1(box): if not isinstance(box, Ket): return box x_gates = Id(0).tensor(*(X if x else Id(1) for x in box.bitstring)) return Ket(*(len(box.bitstring) * (0, ))) >> x_gates
def to_tk(circuit): """ Takes a :class:`discopy.quantum.Circuit`, returns a :class:`Circuit`. """ # bits and qubits are lists of register indices, at layer i we want # len(bits) == circuit[:i].cod.count(bit) and same for qubits tk_circ, bits, qubits = Circuit(), [], [] circuit = circuit.init_and_discard() def remove_ket1(box): if not isinstance(box, Ket): return box x_gates = Id(0).tensor(*(X if x else Id(1) for x in box.bitstring)) return Ket(*(len(box.bitstring) * (0, ))) >> x_gates def prepare_qubits(qubits, box, offset): renaming = dict() start = tk_circ.n_qubits if not qubits else 0\ if not offset else qubits[offset - 1] + 1 for i in range(start, 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)) return qubits[:offset] + list(range(start, start + len(box.cod)))\ + [i + len(box.cod) for i in qubits[offset:]] def prepare_bits(bits, box, offset): renaming = dict() start = tk_circ.n_bits if not bits else 0\ if not offset else bits[offset - 1] + 1 for i in range(start, tk_circ.n_bits): old = Bit(i) new = Bit(i + len(box.cod)) renaming.update({old: new}) tk_circ.rename_units(renaming) for i in range(start, start + len(box.cod)): tk_circ.add_bit(Bit(i), offset=offset + i - start) return bits[:offset] + list(range(start, start + len(box.cod)))\ + [i + len(box.cod) for i in bits[offset:]] def measure_qubits(qubits, bits, box, bit_offset, qubit_offset): if isinstance(box, Measure) and box.override_bits: for j, _ in enumerate(box.dom[:len(box.dom) // 2]): i_bit = bits[bit_offset + j] i_qubit = qubits[qubit_offset + j] tk_circ.Measure(i_qubit, i_bit) return bits, qubits for j, _ in enumerate(box.dom): i_bit, i_qubit = len(tk_circ.bits), qubits[qubit_offset + j] offset = len(bits) if isinstance(box, Measure) else None tk_circ.add_bit(Bit(i_bit), offset=offset) tk_circ.Measure(i_qubit, i_bit) if isinstance(box, Bra): tk_circ.post_select({i_bit: box.bitstring[j]}) if isinstance(box, Measure): bits = bits[:bit_offset + j] + [i_bit] + bits[bit_offset + j:] if isinstance(box, Bra)\ or isinstance(box, Measure) and box.destructive: qubits = qubits[:qubit_offset]\ + qubits[qubit_offset + len(box.dom):] return bits, qubits def swap(i, j, unit_factory=Qubit): old, tmp, new =\ unit_factory(i), unit_factory('tmp', 0), unit_factory(j) tk_circ.rename_units({old: tmp}) tk_circ.rename_units({new: old}) tk_circ.rename_units({tmp: new}) def add_gate(qubits, box, offset): i_qubits = [qubits[offset + j] for j in range(len(box.dom))] if isinstance(box, (Rx, Rz)): tk_circ.__getattribute__(box.name[:2])(2 * box.phase, *i_qubits) elif isinstance(box, CRz): tk_circ.__getattribute__(box.name[:3])(2 * box.phase, *i_qubits) elif hasattr(tk_circ, box.name): tk_circ.__getattribute__(box.name)(*i_qubits) else: raise NotImplementedError circuit = Functor(ob=lambda x: x, ar=remove_ket1)(circuit) for left, box, _ in circuit.layers: if isinstance(box, Ket): qubits = prepare_qubits(qubits, box, left.count(qubit)) elif isinstance(box, Bits) and not box.is_dagger: if 1 in box.bitstring: raise NotImplementedError bits = prepare_bits(bits, box, left.count(bit)) elif isinstance(box, (Measure, Bra)): bits, qubits = measure_qubits(qubits, bits, box, left.count(bit), left.count(qubit)) elif isinstance(box, Discard): bits = bits[:left.count(bit)]\ + bits[left.count(bit) + box.dom.count(bit):] qubits = qubits[:left.count(qubit)]\ + qubits[left.count(qubit) + box.dom.count(qubit):] elif isinstance(box, Swap): if box == Swap(qubit, qubit): off = left.count(qubit) swap(qubits[off], qubits[off + 1]) elif box == Swap(bit, bit): off = left.count(bit) if tk_circ.post_processing: right = Id(tk_circ.post_processing.cod[off + 2:]) tk_circ.post_process(Id(bit**off) @ Swap(bit, bit) @ right) else: swap(bits[off], bits[off + 1], unit_factory=Bit) else: # pragma: no cover continue # bits and qubits live in different registers. elif isinstance(box, Scalar): tk_circ.scale( box.array[0] if box.is_mixed else abs(box.array[0])**2) elif isinstance(box, ClassicalGate)\ or isinstance(box, Bits) and box.is_dagger: off = left.count(bit) right = Id(tk_circ.post_processing.cod[off + len(box.dom):]) tk_circ.post_process(Id(bit**off) @ box @ right) elif isinstance(box, QuantumGate): add_gate(qubits, box, left.count(qubit)) else: # pragma: no cover raise NotImplementedError return tk_circ
def layer(thetas): hadamards = Id(0).tensor(*(n_qubits * [H])) rotations = Id(n_qubits).then( *(Id(i) @ CRz(thetas[i]) @ Id(n_qubits - 2 - i) for i in range(n_qubits - 1))) return hadamards >> rotations