def __init__( self, outfile=None, one_qubit_gate_map=None, two_qubit_gate_map=None, native_gates=None, ): BasicEngine.__init__(self) if native_gates is None: from qscout.v1.std import NATIVE_GATES native_gates = NATIVE_GATES self._circuit = CircuitBuilder(native_gates=native_gates) self._q = self._circuit.register("q", 0) self._block = UnscheduledBlockBuilder() self._circuit.expression.append(self._block.expression) self._block.gate("prepare_all") self.measure_accumulator = set() self.reset_accumulator = set() self.outfile = outfile if one_qubit_gate_map is None: self.one_qubit_gates = one_qubit_gates else: self.one_qubit_gates = one_qubit_gate_map if two_qubit_gate_map is None: self.two_qubit_gates = two_qubit_gates else: self.two_qubit_gates = two_qubit_gate_map
def test_2_ion_compilation(self): p = Program() ro = p.declare("ro", "BIT", 6) p += X(0) p += MEASURE(0, ro[0]) p += MEASURE(1, ro[1]) p += RESET(0) p += RESET(1) p += X(1) p += MEASURE(1, ro[3]) p += MEASURE(0, ro[2]) p += RESET() p += X(0) p += MEASURE(0, ro[4]) p += MEASURE(1, ro[5]) qc = get_ion_qc(2) circ = qc.compile(p) jcirc = CircuitBuilder() reg = jcirc.register("qreg", 2) block = jcirc.block() block.gate("prepare_all") block.gate("Px", reg[0]) block.gate("measure_all") block.gate("prepare_all") block.gate("Px", reg[1]) block.gate("measure_all") block.gate("prepare_all") block.gate("Px", reg[0]) block.gate("measure_all") self.assertEqual(generate_jaqal_program(jcirc.build()), generate_jaqal_program(circ))
def test_transpile_2q_circuit(self): qr = QuantumRegister(2) cr = ClassicalRegister(4) circ = QuantumCircuit(qr, cr) circ.x(qr[0]) circ.measure(qr[0], cr[0]) circ.measure(qr[1], cr[1]) circ.barrier() circ.reset(qr[0]) circ.reset(qr[1]) circ.barrier() circ.y(qr[0]) dag = circuit_to_dag(circ) jcirc = CircuitBuilder() reg1 = jcirc.register("baseregister", 2) reg2 = jcirc.map(qr.name, reg1, slice(0, 2, 1)) block = jcirc.block() block.gate("prepare_all") block.gate("Px", reg2[0]) block.gate("measure_all") block = jcirc.block() block.gate("prepare_all") block = jcirc.block() block.gate("Py", reg2[0]) block.gate("measure_all") self.assertEqual( generate_jaqal_program(jcirc.build()), generate_jaqal_program(jaqal_circuit_from_dag_circuit(dag)), )
def test_native_gates(self): gates = quil_gates() p = Program() ro = p.declare("ro", "BIT", 1) p += gates["SX"](0) p += MEASURE(0, ro[0]) qc = get_ion_qc(1) circ = qc.compile(p) jcirc = CircuitBuilder() reg = jcirc.register("qreg", 1) block = jcirc.block() block.gate("prepare_all") block.gate("Sx", reg[0]) block.gate("measure_all") self.assertEqual(generate_jaqal_program(jcirc.build()), generate_jaqal_program(circ))
def test_transpile_1q_circuit(self): c = Circuit(1, 1) c.Rz(1, 0) c.add_barrier([0]) c.Measure(0, 0) qsc = jaqal_circuit_from_tket_circuit(c) jcirc = CircuitBuilder() reg = jcirc.register("baseregister", 1) reg2 = jcirc.map("q", reg, slice(0, 1, 1)) block = jcirc.block() block.gate("prepare_all") block.gate("Rz", reg2[0], pi) block = jcirc.block() block.gate("measure_all") self.assertEqual(generate_jaqal_program(jcirc.build()), generate_jaqal_program(qsc))
def test_transpile_grid_circuit(self): c = Circuit() qb = pytket._tket.circuit.Qubit("grid", 0, 0) c.add_qubit(qb) c.Rz(1, qb) qsc = jaqal_circuit_from_tket_circuit(c) jcirc = CircuitBuilder() reg = jcirc.register("baseregister", 1) reg2 = jcirc.map("grid0_0", reg, slice(0, 1, 1)) block = jcirc.block() block.gate("prepare_all") block.gate("Rz", reg2[0], pi) block.gate("measure_all") self.assertEqual(generate_jaqal_program(jcirc.build()), generate_jaqal_program(qsc))
def test_transpile_circuit(self): backend = JaqalBackend() engine_list = get_engine_list() eng = MainEngine(backend, engine_list, verbose=True) q1 = eng.allocate_qubit() q2 = eng.allocate_qubit() SqrtX | q1 SqrtX | q2 Barrier | (q1, q2) Rxx(1.0) | (q1, q2) All(Measure) | [q1, q2] eng.flush() circ = backend.circuit jcirc = CircuitBuilder() reg = jcirc.register("q", 2) block = jcirc.block() block.gate("prepare_all") block.gate("Sx", reg[0]) block.gate("Sx", reg[1]) block = jcirc.block() block.gate("MS", reg[0], reg[1], 0, 1.0) block.gate("measure_all") self.assertEqual(generate_jaqal_program(jcirc.build()), generate_jaqal_program(circ))
class JaqalBackend(BasicEngine): """ A ProjectQ backend that converts the input circuit to Jaqal, building a :class:`jaqalpup.core.Circuit` object which can be retrieved with the :attr:`circuit` property. If an output file is supplied, it also writes the Jaqal code to it when the engine is flushed. :param str outfile: Optionally specify a path to output Jaqal code to. If omitted, the Jaqal program is only available via the :attr:`circuit` property. :param one_qubit_gate_map: A dictionary mapping ProjectQ gate classes to functions taking as arguments an instance of that class and a :class:`jaqalpup.core.NamedQubit` and returning a tuple of arguments for :meth:`jaqalpup.core.Circuit.build_gate`. Defaults to a mapping from :class:`projectq.ops.Rx`, :class:`projectq.ops.Ry`, :class:`projectq.ops.Rz`, :class:`projectq.ops.X`, :class:`projectq.ops.Y`, and :class:`projectq.ops.SqrtX` to their QSCOUT counterparts. :type one_qubit_gate_map: dict or None :param two_qubit_gate_map: A dictionary mapping ProjectQ gate classes to functions taking as arguments an instance of that class and two :class:`jaqalpup.core.NamedQubit` ojects and returning a tuple of arguments for :meth:`jaqalpup.core.Circuit.build_gate`. Defaults to a mapping from :class:`projectq.ops.Rxx` and :class:`projectq.ops.Ryy` to QSCOUT's Mølmer-Sørenson gate. :type two_qubit_gate_map: dict or None :param native_gates: The native gate set to target. If None, target the QSCOUT native gates. :type native_gates: dict or None """ def __init__( self, outfile=None, one_qubit_gate_map=None, two_qubit_gate_map=None, native_gates=None, ): BasicEngine.__init__(self) if native_gates is None: from qscout.v1.std import NATIVE_GATES native_gates = NATIVE_GATES self._circuit = CircuitBuilder(native_gates=native_gates) self._q = self._circuit.register("q", 0) self._block = UnscheduledBlockBuilder() self._circuit.expression.append(self._block.expression) self._block.gate("prepare_all") self.measure_accumulator = set() self.reset_accumulator = set() self.outfile = outfile if one_qubit_gate_map is None: self.one_qubit_gates = one_qubit_gates else: self.one_qubit_gates = one_qubit_gate_map if two_qubit_gate_map is None: self.two_qubit_gates = two_qubit_gates else: self.two_qubit_gates = two_qubit_gate_map @property def circuit(self): """ Allows access to the Jaqal circuit generated by the transpilation process. """ return self._circuit.build() def is_available(self, cmd): """ Returns whether Jaqal supports the specified command for transpilation. Any gate in the QSCOUT native gate set, as well as measure, allocate, deallocate, and barrier statements, are supported. Controlled gates are not supported. The :func:`jaqalpaq.transpilers.projectq.get_engine_list` function provides the ProjectQ engines that will compile a circuit to this gate basis. :param projectq.ops.Command cmd: The command for which to check availability. """ if get_control_count(cmd) > 0: return False if cmd.gate in self.one_qubit_gates.keys() + self.two_qubit_gates.keys( ): return True if cmd in (Measure, Allocate, Deallocate, Barrier): return True return False def receive(self, command_list): """ Converts each instruction in the input into its Jaqal equivalent and saves it. This should rarely be called directly by users; usually it will be called by ``projectq.cengines.MainEngine``. :param command_list: The ProjectQ program to convert. :type command_list: list(projectq.ops.Command) """ for cmd in command_list: if cmd.gate == FlushGate(): if self.outfile is not None: from jaqalpaq.generator import generate_jaqal_program with open(self.outfile, "w+") as f: f.write(generate_jaqal_program(self.circuit)) else: self._store(cmd) def _mapped_qubit_id(self, qubit): """ Converts a qubit from a logical to a mapped qubit if there is a mapper. """ mapper = self.main_engine.mapper if mapper is not None: if qubit.id not in mapper.current_mapping: raise RuntimeError("Unknown qubit id. " "Please make sure you have called " "eng.flush().") return mapper.current_mapping[qubit.id] else: return qubit.id def _store(self, cmd): gate = cmd.gate if len(self.measure_accumulator) == len(self._q) and len(self._q) > 0: self.measure_accumulator = set() self._block.gate("prepare_all") if gate == Allocate: qid = self._mapped_qubit_id(cmd.qubits[0][0]) self._circuit.stretch_register(qid + 1) elif gate == Deallocate: pass # The user might stop caring about the qubit, but we need to keep it around. elif gate == Measure: qid = self._mapped_qubit_id(cmd.qubits[0][0]) if qid in self.measure_accumulator: raise JaqalError("Can't measure qubit %d twice!" % qid) else: self.measure_accumulator.add(qid) if len(self.measure_accumulator) == len(self._q): self._block.gate("measure_all") elif gate == Barrier: self._block = UnscheduledBlockBuilder() self._circuit.expression.append(self._block.expression) elif type(gate) in one_qubit_gates: qid = self._mapped_qubit_id(cmd.qubits[0][0]) if qid in self.measure_accumulator: raise JaqalError( "Can't do gates in the middle of measurement!") else: self._block.gate( *self.one_qubit_gates[type(gate)](gate, self._q[qid])) elif type(gate) in two_qubit_gates: qids = [self._mapped_qubit_id(qb[0]) for qb in cmd.qubits] for qid in qids: if qid in self.measure_accumulator: raise JaqalError( "Can't do gates in the middle of measurement!") self._block.gate(*self.two_qubit_gates[type(gate)]( gate, *[self._q[qid] for qid in qids])) else: raise JaqalError("Unknown instruction! %s" % gate)
def jaqal_circuit_from_cirq_circuit(ccirc, names=None, native_gates=None): """Converts a Cirq Circuit object to a :class:`jaqalpaq.core.Circuit`. The circuit will be structured as a sequence of parallel blocks, one for each Cirq Moment in the input. Measurement are supported, but only if applied to every qubit in the circuit in the same moment. If so, they will be mapped to a measure_all gate. If the measure_all gate is not the last gate in the circuit, a prepare_all gate will be inserted after it. Additionally, a prepare_all gate will be inserted before the first moment. If the circuit does not end with a measurement, then a measure_all gate will be appended. Circuits built on a line register will map each qubit to the qubit of the same index on the hardware. This may leave some qubits unused. Otherwise, the qubits will be mapped onto the hardware in the order given by ccirc.all_qubits(). :param cirq.Circuit ccirc: The Circuit to convert. :param names: A mapping from Cirq gate classes to the corresponding native Jaqal gate names. If omitted, maps ``cirq.XXPowGate``, ``cirq.XPowGate``, ``cirq.YPowGate``, ``cirq.ZPowGate``, and ``cirq.PhasedXPowGate`` to their QSCOUT counterparts. The ``cirq.ConvertToIonGates`` function will transpile a circuit into this basis. :type names: dict or None :param native_gates: The native gate set to target. If None, target the QSCOUT native gates. :type native_gates: dict or None :returns: The same quantum circuit, converted to JaqalPaq. :rtype: Circuit :raises JaqalError: If the circuit includes a gate not included in `names`. """ if native_gates is None: from qscout.v1.std import NATIVE_GATES native_gates = NATIVE_GATES builder = CircuitBuilder(native_gates=native_gates) if names is None: names = CIRQ_NAMES try: n = 1 + max([qb.x for qb in ccirc.all_qubits()]) line = True except: cqubits = ccirc.all_qubits() n = len(cqubits) qubitmap = {cqubits[i]: i for i in range(n)} line = False allqubits = builder.register("allqubits", n) need_prep = True for moment in ccirc: if len(moment) == 0: continue if need_prep: builder.gate("prepare_all") need_prep = False if ( len(moment) == n and all([op.gate for op in moment]) and all([isinstance(op.gate, MeasurementGate) for op in moment]) ): builder.gate("measure_all") need_prep = True continue if len(moment) > 1: block = builder.block(parallel=True) # Note: If you tell Cirq you want MS gates in parallel, we'll generate a Jaqal # file with exactly that, never mind that QSCOUT can't execute it. else: block = builder for op in moment: if op.gate: if type(op.gate) in names: if line: targets = [allqubits[qb.x] for qb in op.qubits] else: targets = [allqubits[qubitmap[qb]] for qb in op.qubits] block.gate(*names[type(op.gate)](op.gate, *targets)) else: raise JaqalError("Convert circuit to ion gates before compiling.") else: raise JaqalError("Cannot compile operation %s." % op) if not need_prep: # If we just measured, or the circuit is empty, don't add a final measurement. builder.gate("measure_all") return builder.build()
def native_quil_to_executable(self, nq_program: Program) -> Optional[Circuit]: """ Compiles a Quil program to a :class:`qscout.core.Circuit`. Because Quil does not support any form of schedule control, the entire circuit will be put in a single unscheduled block. If the :mod:`qscout.scheduler` is run on the circuit, as many as possible of those gates will be parallelized, while maintaining the order of gates that act on the same qubits. Otherwise, the circuit will be treated as a fully sequential circuit. Measurement and reset commands are supported, but only if applied to every qubit in the circuit in immediate succession. If so, they will be mapped to a prepare_all or measure_all gate. If the circuit does not end with a measurement, then a measure_all gate will be appended to it. :param pyquil.quil.Program nq_program: The program to compile. :returns: The same quantum program, converted to JaqalPaq. :rtype: qscout.core.Circuit :raises JaqalError: If the program includes a non-gate instruction other than resets or measurements. :raises JaqalError: If the user tries to measure or reset only some of the qubits, rather than all of them. :raises JaqalError: If the program includes a gate not included in `names`. """ n = max(nq_program.get_qubits()) + 1 if n > len(self._device.qubits()): raise JaqalError( "Program uses more qubits (%d) than device supports (%d)." % (n, len(self._device.qubits())) ) qsc = CircuitBuilder(native_gates=self.native_gates) block = UnscheduledBlockBuilder() qsc.expression.append(block.expression) # Quil doesn't support barriers, so either the user # won't run the the scheduler and everything will happen # sequentially, or the user will and everything can be # rescheduled as needed. qreg = qsc.register("qreg", n) block.gate("prepare_all") reset_accumulator = set() measure_accumulator = set() in_preamble = True for instr in nq_program: if reset_accumulator: if isinstance(instr, ResetQubit): reset_accumulator.add(instr.qubit.index) if nq_program.get_qubits() <= reset_accumulator: block.gate("prepare_all") reset_accumulator = set() in_preamble = False continue else: raise JaqalError( "Cannot reset only qubits %s and not whole register." % reset_accumulator ) # reset_accumulator = set() if measure_accumulator: if isinstance(instr, Measurement): measure_accumulator.add(instr.qubit.index) if nq_program.get_qubits() <= measure_accumulator: block.gate("measure_all") measure_accumulator = set() in_preamble = False continue else: raise JaqalError( "Cannot measure only qubits %s and not whole register." % measure_accumulator ) # measure_accumulator = set() if isinstance(instr, Gate): if instr.name in self.names: block.gate( self.names[instr.name], *[qreg[qubit.index] for qubit in instr.qubits], *[float(p) for p in instr.params] ) in_preamble = False else: raise JaqalError("Gate %s not in native gate set." % instr.name) elif isinstance(instr, Reset): if not in_preamble: block.gate("prepare_all") in_preamble = False elif isinstance(instr, ResetQubit): if not in_preamble: reset_accumulator = {instr.qubit.index} if nq_program.get_qubits() <= reset_accumulator: block.gate("prepare_all") reset_accumulator = set() elif isinstance(instr, Measurement): measure_accumulator = {instr.qubit.index} # We ignore the classical register. if nq_program.get_qubits() <= measure_accumulator: block.gate("measure_all") measure_accumulator = set() in_preamble = False elif isinstance(instr, Declare): pass # Ignore allocations of classical memory. else: raise JaqalError("Instruction %s not supported." % instr.out()) block.gate("measure_all", no_duplicate=True) return qsc.build()
def test_transpile_line_circuit(self): c = cirq.Circuit() qubits = [cirq.LineQubit(0), cirq.LineQubit(1)] c.append(cirq.H.on(qubits[0])) c.append(cirq.CNOT(*qubits)) ic = cirq.ConvertToIonGates().convert_circuit(c) ic.append( cirq.measure_each(*qubits), strategy=cirq.circuits.InsertStrategy.NEW_THEN_INLINE, ) jc = jaqal_circuit_from_cirq_circuit(ic) jcirc = CircuitBuilder() reg = jcirc.register("allqubits", 2) jcirc.gate("prepare_all") jcirc.gate("R", reg[0], pi, pi) jcirc.gate("MS", reg[0], reg[1], 0, pi / 2) block = jcirc.block(True) block.gate("R", reg[0], -1.5707963267948972, pi / 2) # Last few digits are off if we just use -pi/2 block.gate("R", reg[1], pi, pi / 2) jcirc.gate("Rz", reg[0], -pi / 2) jcirc.gate("measure_all") self.assertEqual( generate_jaqal_program(jcirc.build()), generate_jaqal_program(jc) )
def jaqal_circuit_from_qiskit_circuit(circuit, names=None, native_gates=None): """ Converts a Qiskit circuit to a :class:`jaqalpaq.core.Circuit`. The circuit will be structured into a sequence of unscheduled blocks. All instructions between one barrier statement and the next will be put into an unscheduled block together. If the :mod:`qscout.scheduler` is run on the circuit, as many as possible of those gates will be parallelized within each block, while maintaining the order of the blocks. Otherwise, the circuit will be treated as a fully sequential circuit. Measurement and reset commands are supported, but only if applied to every qubit in the circuit in immediate succession. If so, they will be mapped to a prepare_all or measure_all gate. If the circuit does not end with a measurement, then a measure_all gate will be appended to it. Circuits containing multiple quantum registers will be converted to circuits with a single quantum register, containing all the qubits from each register. The parts of that larger register that correspond to each of the original registers will be mapped with the appropriate names. :param qiskit.circuit.QuantumCircuit circuit: The circuit to convert. :param names: A mapping from names of Qiskit gates to the corresponding native Jaqal gate names. If omitted, maps i, r (:class:`jaqalpaq.transpilers.qiskit.RGate`), sx (:class:`jaqalpaq.qiskit.SXGate`), sy (:class:`jaqalpaq.qiskit.SYGate`), x, y, rz, and ms2 (:class:`jaqalpaq.qiskit.MSGate`) to their QSCOUT counterparts. :type names: dict or None :param native_gates: The native gate set to target. If None, target the QSCOUT native gates. :type native_gates: dict or None :returns: The same quantum circuit, converted to JaqalPaq. :rtype: jaqalpaq.core.Circuit :raises JaqalError: If any instruction acts on a qubit from a register other than the circuit's qregs. :raises JaqalError: If the circuit includes a snapshot instruction. :raises JaqalError: If the user tries to measure or reset only some of the qubits, rather than all of them. :raises JaqalError: If the circuit includes a gate not included in `names`. """ if native_gates is None: from qscout.v1.std import NATIVE_GATES native_gates = NATIVE_GATES n = sum([qreg.size for qreg in circuit.qregs]) qsc = CircuitBuilder(native_gates=native_gates) if names is None: names = QISKIT_NAMES baseregister = qsc.register("baseregister", n) offset = 0 registers = {} for qreg in circuit.qregs: registers[qreg.name] = qsc.map(qreg.name, baseregister, slice(offset, offset + qreg.size)) offset += qreg.size # We're going to divide the circuit up into blocks. Each block will contain every gate # between one barrier statement and the next. If the circuit is output with no further # processing, then the gates in each block will be run in sequence. However, if the # circuit is passed to the scheduler, it'll try to parallelize as many of the gates # within each block as possible, while keeping the blocks themselves sequential. block = UnscheduledBlockBuilder() qsc.expression.append(block.expression) block.gate("prepare_all") measure_accumulator = set() reset_accumulator = set() in_preamble = True for instr in circuit.data: if reset_accumulator: if instr[0].name == "reset": target = instr[1][0] if target.register.name in registers: reset_accumulator.add( registers[target.register.name].resolve_qubit( target.index)[1]) else: raise JaqalError("Register %s invalid!" % target.register.name) if len(reset_accumulator) == n: block.gate("prepare_all") reset_accumulator = {} continue else: raise JaqalError( "Cannot reset only qubits %s and not whole register." % reset_accumulator) # reset_accumulator = set() if measure_accumulator: if instr[0].name == "measure": target = instr[1][0] if target.register.name in registers: measure_accumulator.add( registers[target.register.name].resolve_qubit( target.index)[1]) else: raise JaqalError("Register %s invalid!" % target.register.name) if len(measure_accumulator) == n: block.gate("measure_all") measure_accumulator = {} continue else: raise JaqalError( "Cannot measure only qubits %s and not whole register." % reset_accumulator) # measure_accumulator = set() if instr[0].name == "measure": in_preamble = False target = instr[1][0] if target.register.name in registers: measure_accumulator = { registers[target.register.name].resolve_qubit( target.index)[1] } if len(measure_accumulator) == n: block.gate("measure_all") measure_accumulator = {} continue else: raise JaqalError("Register %s invalid!" % target.register.name) elif instr[0].name == "reset": if not in_preamble: target = instr[1][0] if target.register.name in registers: reset_accumulator = { registers[target.register.name].resolve_qubit( target.index)[1] } if len(reset_accumulator) == n: block.gate("prepare_all") reset_accumulator = {} else: raise JaqalError("Register %s invalid!" % target.register.name) elif instr[0].name == "barrier": block = UnscheduledBlockBuilder() qsc.expression.append(block.expression) # Use barriers to inform the scheduler, as explained above. elif instr[0].name == "snapshot": raise JaqalError( "Physical hardware does not support snapshot instructions.") elif instr[0].name in names: in_preamble = False targets = instr[1] for target in targets: if target.register.name not in registers: raise JaqalError("Gate register %s invalid!" % target.register.name) block.gate( names[instr[0].name], *[ registers[target.register.name][target.index] for target in targets ], *[float(param) for param in instr[0].params]) else: raise JaqalError( "Instruction %s not available on trapped ion hardware; try unrolling first." % instr[0].name) block.gate("measure_all", no_duplicate=True) return qsc.build()