def test_XY(): for theta in np.linspace(-2 * np.pi, 2 * np.pi): p = Program(XY(theta, 0, 1)) u1 = program_unitary(p, n_qubits=2) u2 = program_unitary(basic_compile(p), n_qubits=2) assert equal_up_to_global_phase(u1, u2, atol=1e-12) p = Program(XY(theta, 1, 0)) u1 = program_unitary(p, n_qubits=2) u2 = program_unitary(basic_compile(p), n_qubits=2) assert equal_up_to_global_phase(u1, u2, atol=1e-12)
def _ISWAP(q0: QubitLike, q1: QubitLike) -> Program: """ An ISWAP as an XY(pi). Of course, assumes XY is available. """ p = Program() p += XY(np.pi, q0, q1) return p
def basic_compile(program: Program) -> Program: """ A rudimentary but predictable compiler. No rewiring or optimization is done by this compilation step. There may be some gates that are not yet supported. Gates defined in the input program are included without change in the output program. :param program: A program to be compiled to native quil with simple replacements. :return: A program with some of the input non-native quil gates replaced with basic native quil gate implementations. """ new_prog = Program() new_prog.num_shots = program.num_shots new_prog.inst(program.defined_gates) for inst in program: if isinstance(inst, Gate): if inst.name == "CCNOT": new_prog += _CCNOT(*inst.qubits) elif inst.name == "CNOT": new_prog += _CNOT(*inst.qubits) # NB: we haven't implemented CPHASE00/01/10 elif inst.name == "CPHASE": angle_param = inst.params[0] new_prog += _CPHASE(angle_param, *inst.qubits) elif inst.name == "CZ": new_prog += CZ(*inst.qubits) # remove dag modifiers elif inst.name == "H": new_prog += _H(inst.qubits[0]) elif inst.name == "I": new_prog += I(inst.qubits[0]) # remove dag modifiers elif inst.name == "ISWAP": new_prog += _ISWAP(*inst.qubits) # remove dag modifiers elif inst.name == "PHASE": angle_param = inst.params[0] new_prog += _PHASE(angle_param, inst.qubits[0]) elif inst.name == "RX": angle_param = inst.params[0] if is_magic_angle(inst.params[0]): # in case dagger new_prog += RX(angle_param, inst.qubits[0]) else: new_prog += _RX(angle_param, inst.qubits[0]) elif inst.name == "RY": angle_param = inst.params[0] new_prog += _RY(angle_param, inst.qubits[0]) elif inst.name == "RZ": # in case dagger angle_param = inst.params[0] new_prog += RZ(angle_param, inst.qubits[0]) elif inst.name == "S": new_prog += _S(inst.qubits[0]) # NB: we haven't implemented CSWAP or PSWAP elif inst.name == "SWAP": new_prog += _SWAP(*inst.qubits) elif inst.name == "T": new_prog += _T(inst.qubits[0]) elif inst.name == "X": new_prog += _X(inst.qubits[0]) elif inst.name == "XY": angle_param = inst.params[0] new_prog += XY(angle_param, *inst.qubits) elif inst.name == "Y": new_prog += _Y(inst.qubits[0]) elif inst.name == "Z": new_prog += _Z(inst.qubits[0]) elif inst.name in [gate.name for gate in new_prog.defined_gates]: new_prog += inst else: raise ValueError(f"Unknown gate instruction {inst}") else: new_prog += inst new_prog.native_quil_metadata = { # type: ignore[assignment] "final_rewiring": None, "gate_depth": None, "gate_volume": None, "multiqubit_gate_depth": None, "program_duration": None, "program_fidelity": None, "topological_swaps": 0, } return new_prog
def basic_compile(program: Program): """ A rudimentary but predictable compiler. No rewiring or optimization is done by this compilation step. There may be some gates that are not yet supported. Gates defined in the input program are included without change in the output program. :param program: a program to be compiled to native quil with simple replacements. :return: a program with some of the input non-native quil gates replaced with basic native quil gate implementations. """ new_prog = Program() new_prog.num_shots = program.num_shots new_prog.inst(program.defined_gates) daggered_defgates = [] for inst in program: if isinstance(inst, Gate): # TODO: this is only a stopgap while the noisy QVM does not support modifiers. # dagger this gate if odd number of daggers. Ignore controlled for now. needs_dagger = inst.modifiers.count('DAGGER') % 2 == 1 angle_param = None if len(inst.params) > 0: angle_param = inst.params[0] if needs_dagger: angle_param = -angle_param if 'CONTROLLED' in inst.modifiers: raise ValueError("Controlled gates are not currently supported.") if inst.name == 'CZ': new_prog += CZ(*inst.qubits) # remove dag modifiers elif inst.name == 'XY': new_prog += XY(angle_param, *inst.qubits) elif inst.name == 'I': new_prog += I(inst.qubits[0]) # remove dag modifiers elif inst.name == 'RZ': # in case dagger new_prog += RZ(angle_param, inst.qubits[0]) elif inst.name == 'RX': if is_magic_angle(inst.params[0]): # in case dagger new_prog += RX(angle_param, inst.qubits[0]) else: new_prog += _RX(angle_param, inst.qubits[0]) elif inst.name == 'RY': new_prog += _RY(angle_param, inst.qubits[0]) elif inst.name == 'CNOT': new_prog += _CNOT(*inst.qubits) elif inst.name == 'CCNOT': new_prog += _CCNOT(*inst.qubits) elif inst.name == 'SWAP': new_prog += _SWAP(*inst.qubits) elif inst.name == 'T': new_prog += _T(inst.qubits[0], needs_dagger) elif inst.name == "H": new_prog += _H(inst.qubits[0]) elif inst.name == "X": new_prog += _X(inst.qubits[0]) elif inst.name in [gate.name for gate in new_prog.defined_gates]: if needs_dagger and inst.name not in daggered_defgates: new_prog.defgate(inst.name + 'DAG', inst.matrix.T.conj()) daggered_defgates.append(inst.name) new_prog += inst else: raise ValueError(f"Unknown gate instruction {inst}") else: new_prog += inst new_prog.native_quil_metadata = { 'final_rewiring': None, 'gate_depth': None, 'gate_volume': None, 'multiqubit_gate_depth': None, 'program_duration': None, 'program_fidelity': None, 'topological_swaps': 0, } return new_prog