コード例 #1
0
 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)
コード例 #2
0
ファイル: gates.py プロジェクト: Doomsk/discopy
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
コード例 #3
0
 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
コード例 #4
0
 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)
コード例 #5
0
 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)
コード例 #6
0
ファイル: gates.py プロジェクト: Doomsk/discopy
    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)
コード例 #7
0
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
コード例 #8
0
 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
コード例 #9
0
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
コード例 #10
0
ファイル: gates.py プロジェクト: Doomsk/discopy
 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