def test_customgate() -> None: a = Symbol("a") def_circ = Circuit(2) def_circ.CZ(0, 1) def_circ.Rx(a, 1) gate_def = CustomGateDef.define("MyCRx", def_circ, [a]) circ = Circuit(3) circ.Rx(0.1, 0) circ.Rx(0.4, 2) circ.add_custom_gate(gate_def, [0.2], [0, 1]) qc1 = tk_to_qiskit(circ) newcirc = qiskit_to_tk(qc1) print(repr(newcirc)) qc2 = tk_to_qiskit(newcirc) correct_circ = Circuit(3).Rx(0.1, 0).Rx(0.4, 2).CZ(0, 1).Rx(0.2, 1) correct_qc = tk_to_qiskit(correct_circ) backend = Aer.get_backend("statevector_simulator") states = [] for qc in (qc1, qc2, correct_qc): job = execute([qc], backend) states.append(job.result().get_statevector(qc)) assert compare_statevectors(states[0], states[1]) assert compare_statevectors(states[1], states[2])
# ## Custom gates # We can define custom parametrized gates in `pytket` by first setting up a circuit containing symbolic parameters and then converting this to a parametrized operation type: from pytket.circuit import CustomGateDef a = Symbol("a") b = Symbol("b") setup = Circuit(3) setup.CX(0, 1) setup.Rz(a + 0.5, 2) setup.CRz(b, 0, 2) my_gate = CustomGateDef.define("g", setup, [a, b]) c = Circuit(4) c.add_custom_gate(my_gate, [0.2, 1.3], [0, 3, 1]) print(c.get_commands()) # Custom gates can also receive symbolic parameters: x = Symbol("x") c.add_custom_gate(my_gate, [x, 1.0], [0, 1, 2]) print(c.get_commands()) # ## Decomposing boxes and custom gates # Having defined a circuit containing custom gates, we may now want to decompose it into elementary gates. The `DecomposeBoxes()` transform allows us to do this: Transform.DecomposeBoxes().apply(c) print(c.get_commands())
class CircuitBuilder: def __init__( self, qregs: List[QuantumRegister], cregs: Optional[List[ClassicalRegister]] = None, name: Optional[str] = None, phase: Optional[float] = 0.0, ): self.qregs = qregs self.cregs = [] if cregs is None else cregs self.tkc = Circuit(name=name) self.tkc.add_phase(phase) self.qregmap = {} for reg in qregs: tk_reg = self.tkc.add_q_register(reg.name, len(reg)) self.qregmap.update({reg: tk_reg}) self.cregmap = {} for reg in self.cregs: tk_reg = self.tkc.add_c_register(reg.name, len(reg)) self.cregmap.update({reg: tk_reg}) def circuit(self) -> Circuit: return self.tkc def add_qiskit_data(self, data: "QuantumCircuitData") -> None: for i, qargs, cargs in data: condition_kwargs = {} if i.condition is not None: cond_reg = self.cregmap[i.condition[0]] condition_kwargs = { "condition_bits": [cond_reg[k] for k in range(len(cond_reg))], "condition_value": i.condition[1], } if type(i) == ControlledGate: if type(i.base_gate) == qiskit_gates.RYGate: optype = OpType.CnRy else: # Maybe handle multicontrolled gates in a more general way, # but for now just do CnRy raise NotImplementedError( "qiskit ControlledGate with " + "base gate {} not implemented".format(i.base_gate)) else: optype = _known_qiskit_gate[type(i)] qubits = [ self.qregmap[qbit.register][qbit.index] for qbit in qargs ] bits = [self.cregmap[bit.register][bit.index] for bit in cargs] if optype == OpType.Unitary2qBox: u = i.to_matrix() ubox = Unitary2qBox(u) self.tkc.add_unitary2qbox(ubox, qubits[0], qubits[1], **condition_kwargs) elif optype == OpType.Barrier: self.tkc.add_barrier(qubits) elif optype in (OpType.CircBox, OpType.Custom): qregs = [QuantumRegister(i.num_qubits, "q") ] if i.num_qubits > 0 else [] cregs = ([ClassicalRegister(i.num_clbits, "c")] if i.num_clbits > 0 else []) builder = CircuitBuilder(qregs, cregs) builder.add_qiskit_data(i.definition) subc = builder.circuit() if optype == OpType.CircBox: cbox = CircBox(subc) self.tkc.add_circbox(cbox, qubits + bits, **condition_kwargs) else: # warning, this will catch all `Gate` instances # that were not picked up as a subclass in _known_qiskit_gate params = [param_to_tk(p) for p in i.params] gate_def = CustomGateDef.define(i.name, subc, list(subc.free_symbols())) self.tkc.add_custom_gate(gate_def, params, qubits + bits) else: params = [param_to_tk(p) for p in i.params] self.tkc.add_gate(optype, params, qubits + bits, **condition_kwargs)