def default_compilation_pass(self, optimisation_level: int = 1) -> BasePass: assert optimisation_level in range(3) passes = [DecomposeBoxes()] if optimisation_level == 1: passes.append(SynthesiseIBM()) elif optimisation_level == 2: passes.append(FullPeepholeOptimise()) passes.append(self._rebase_pass) if self._device_type == _DeviceType.QPU: passes.append( CXMappingPass( self._tket_device, NoiseAwarePlacement(self._tket_device), directed_cx=False, delay_measures=True, )) # If CX weren't supported by the device then we'd need to do another # rebase_pass here. But we checked above that it is. if optimisation_level == 1: passes.extend([RemoveRedundancies(), self._squash_pass]) if optimisation_level == 2: passes.extend([ CliffordSimp(False), SynthesiseIBM(), self._rebase_pass, self._squash_pass, ]) return SequencePass(passes)
def default_compilation_pass(self, optimisation_level: int = 1) -> BasePass: assert optimisation_level in range(3) passlist = [DecomposeBoxes()] if optimisation_level == 0: passlist.append(self._rebase_pass) elif optimisation_level == 1: passlist.append(SynthesiseIBM()) else: passlist.append(FullPeepholeOptimise()) if self._noise_model and self._device: passlist.append( CXMappingPass( self._device, NoiseAwarePlacement(self._device), directed_cx=True, delay_measures=False, )) if optimisation_level == 0: passlist.append(self._rebase_pass) elif optimisation_level == 1: passlist.append(SynthesiseIBM()) else: passlist.extend([CliffordSimp(False), SynthesiseIBM()]) return SequencePass(passlist)
def default_compilation_pass(self, optimisation_level: int = 1) -> BasePass: assert optimisation_level in range(3) passlist = [ DecomposeBoxes(), FlattenRegisters(), ] if optimisation_level == 1: passlist.append(SynthesiseIBM()) elif optimisation_level == 2: passlist.append(FullPeepholeOptimise()) passlist.append( CXMappingPass( self._device, NoiseAwarePlacement(self._device), directed_cx=False, delay_measures=True, )) if optimisation_level == 2: passlist.append(CliffordSimp(False)) if optimisation_level > 0: passlist.append(SynthesiseIBM()) passlist.append(RebaseQuil()) if optimisation_level > 0: passlist.append(EulerAngleReduction(OpType.Rx, OpType.Rz)) return SequencePass(passlist)
def test_implicit_perm() -> None: c = Circuit(2) c.CX(0, 1) c.CX(1, 0) c.Ry(0.1, 1) c1 = c.copy() CliffordSimp().apply(c1) b = MyBackend() b.compile_circuit(c) b.compile_circuit(c1) assert c.implicit_qubit_permutation() != c1.implicit_qubit_permutation() for bo in [BasisOrder.ilo, BasisOrder.dlo]: s = b.get_state(c, bo) s1 = b.get_state(c1, bo) assert np.allclose(s, s1)
def test_swaps_basisorder() -> None: # Check that implicit swaps can be corrected irrespective of BasisOrder b = ProjectQBackend() c = Circuit(4) c.X(0) c.CX(0, 1) c.CX(1, 0) CliffordSimp(True).apply(c) assert c.n_gates_of_type(OpType.CX) == 1 b.compile_circuit(c) s_ilo = b.get_state(c, basis=BasisOrder.ilo) s_dlo = b.get_state(c, basis=BasisOrder.dlo) correct_ilo = np.zeros((16, )) correct_ilo[4] = 1.0 assert np.allclose(s_ilo, correct_ilo) correct_dlo = np.zeros((16, )) correct_dlo[2] = 1.0 assert np.allclose(s_dlo, correct_dlo)
def test_swaps_basisorder() -> None: # Check that implicit swaps can be corrected irrespective of BasisOrder b = AerStateBackend() c = Circuit(4) c.X(0) c.CX(0, 1) c.CX(1, 0) c.CX(1, 3) c.CX(3, 1) c.X(2) cu = CompilationUnit(c) CliffordSimp(True).apply(cu) c1 = cu.circuit assert c1.n_gates_of_type(OpType.CX) == 2 b.compile_circuit(c) b.compile_circuit(c1) handles = b.process_circuits([c, c1]) s_ilo = b.get_state(c1, basis=BasisOrder.ilo) correct_ilo = b.get_state(c, basis=BasisOrder.ilo) assert np.allclose(s_ilo, correct_ilo) s_dlo = b.get_state(c1, basis=BasisOrder.dlo) correct_dlo = b.get_state(c, basis=BasisOrder.dlo) assert np.allclose(s_dlo, correct_dlo) qbs = c.qubits for result in b.get_results(handles): assert (result.get_state([qbs[1], qbs[2], qbs[3], qbs[0]]).real.tolist().index(1.0) == 6) assert (result.get_state([qbs[2], qbs[1], qbs[0], qbs[3]]).real.tolist().index(1.0) == 9) assert (result.get_state([qbs[2], qbs[3], qbs[0], qbs[1]]).real.tolist().index(1.0) == 12) bu = AerUnitaryBackend() u_ilo = bu.get_unitary(c1, basis=BasisOrder.ilo) correct_ilo = bu.get_unitary(c, basis=BasisOrder.ilo) assert np.allclose(u_ilo, correct_ilo) u_dlo = bu.get_unitary(c1, basis=BasisOrder.dlo) correct_dlo = bu.get_unitary(c, basis=BasisOrder.dlo) assert np.allclose(u_dlo, correct_dlo)
def default_compilation_pass(self, optimisation_level: int = 1) -> BasePass: assert optimisation_level in range(3) passlist = [DecomposeBoxes()] if optimisation_level == 0: passlist.append(self._rebase_pass) elif optimisation_level == 1: passlist.append(SynthesiseIBM()) elif optimisation_level == 2: passlist.append(FullPeepholeOptimise()) passlist.append( CXMappingPass( self._device, NoiseAwarePlacement(self._device), directed_cx=False, delay_measures=(not self._mid_measure), )) if optimisation_level == 1: passlist.append(SynthesiseIBM()) if optimisation_level == 2: passlist.extend([CliffordSimp(False), SynthesiseIBM()]) if not self._legacy_gateset: passlist.extend([self._rebase_pass, RemoveRedundancies()]) return SequencePass(passlist)
# Now that we have a circuit, `pytket` can take this and start operating on it directly. For example, we can apply some basic compilation passes to simplify it. from pytket.extensions.qiskit import qiskit_to_tk, tk_to_qiskit tk_circ = qiskit_to_tk(state_prep_circ) from pytket.passes import ( SequencePass, CliffordSimp, DecomposeBoxes, KAKDecomposition, SynthesiseIBM, ) DecomposeBoxes().apply(tk_circ) optimise = SequencePass([KAKDecomposition(), CliffordSimp(False), SynthesiseIBM()]) optimise.apply(tk_circ) # Display the optimised circuit: print(tk_to_qiskit(tk_circ)) # The Backends in `pytket` abstract away the differences between different devices and simulators as much as possible, allowing painless switching between them. The `pytket_pyquil` package provides two Backends: `ForestBackend` encapsulates both running on physical devices via Rigetti QCS and simulating those devices on the QVM, and `ForestStateBackend` acts as a wrapper to the pyQuil Wavefunction Simulator. # # Both of these still have a few restrictions on the circuits that can be run. Each only supports a subset of the gate types available in `pytket`, and a real device or associated simulation will have restricted qubit connectivity. The Backend objects will contain a default compilation pass that will statisfy these constraints as much as possible, with minimal or no optimisation. # # The `ForestStateBackend` will allow us to view the full statevector (wavefunction) expected from a perfect execution of the circuit. from pytket.extensions.pyquil import ForestStateBackend state_backend = ForestStateBackend()
tk_circ = qiskit_to_tk(q_circ) backend = FakeMelbourne() coupling_list = backend.configuration().coupling_map coupling_map = CouplingMap(coupling_list) characterisation = process_characterisation(backend) directed_arc = Device( characterisation.get("NodeErrors", {}), characterisation.get("EdgeErrors", {}), characterisation.get("Architecture", Architecture([])), ) comp_tk = tk_circ.copy() DecomposeBoxes().apply(comp_tk) FullPeepholeOptimise().apply(comp_tk) CXMappingPass(directed_arc, NoiseAwarePlacement(directed_arc), directed_cx=True, delay_measures=True).apply(comp_tk) DecomposeSwapsToCXs(directed_arc).apply(comp_tk) cost = lambda c: c.n_gates_of_type(OpType.CX) comp = RepeatWithMetricPass( SequencePass( [CommuteThroughMultis(), RemoveRedundancies(), CliffordSimp(False)]), cost) comp.apply(comp_tk) SynthesiseIBM().apply(comp_tk)
c_tket = pyzx_to_tk(c) _pass.apply(c_tket) RebasePyZX().apply(c_tket) c_opt = tk_to_pyzx(c_tket) opt_tcount = c_opt.tcount() opt_2qubitcount = c_opt.twoqubitcount() # Quick and dirty. In theory, should depend on score function if orig_tcount == opt_tcount and orig_2qubitcount == opt_2qubitcount: return False, (c, g) return True, (c_opt, c_opt.to_graph()) remove_redundancies = partial(tket_pass_base, _pass=RemoveRedundancies()) pauli_simp = partial(tket_pass_base, _pass=PauliSimp()) clifford_simp = partial(tket_pass_base, _pass=CliffordSimp()) # FIXME: Have to put into appropriate gate set first kak_decomposition = partial(tket_pass_base, _pass=KAKDecomposition()) ### ADVANCED ACTIONS def basic_optimization(c, g): orig_tcount = c.tcount() orig_2qubitcount = c.twoqubitcount() c_opt = zx.basic_optimization(c) opt_tcount = c_opt.tcount() opt_2qubitcount = c_opt.twoqubitcount() if orig_tcount == opt_tcount and orig_2qubitcount == opt_2qubitcount: return False, (c, g)
from pytket.extensions.qiskit import qiskit_to_tk, tk_to_qiskit tk_circ = qiskit_to_tk(state_prep_circ) from pytket.passes import ( SequencePass, CliffordSimp, DecomposeBoxes, KAKDecomposition, SynthesiseIBM, ) DecomposeBoxes().apply(tk_circ) optimise = SequencePass( [KAKDecomposition(), CliffordSimp(False), SynthesiseIBM()]) optimise.apply(tk_circ) # Display the optimised circuit: print(tk_to_qiskit(tk_circ)) # The Backends in `pytket` abstract away the differences between different devices and simulators as much as possible, allowing painless switching between them. The `pytket_pyquil` package provides two Backends: `ForestBackend` encapsulates both running on physical devices via Rigetti QCS and simulating those devices on the QVM, and `ForestStateBackend` acts as a wrapper to the pyQuil Wavefunction Simulator. # # Both of these still have a few restrictions on the circuits that can be run. Each only supports a subset of the gate types available in `pytket`, and a real device or associated simulation will have restricted qubit connectivity. The Backend objects will contain a default compilation pass that will statisfy these constraints as much as possible, with minimal or no optimisation. # # The `ForestStateBackend` will allow us to view the full statevector (wavefunction) expected from a perfect execution of the circuit. from pytket.extensions.pyquil import ForestStateBackend