text: # Copyright 2020 National Technology & Engineering Solutions of Sandia, LLC (NTESS).
  # Under the terms of Contract DE-NA0003525 with NTESS, the U.S. Government retains
  # certain rights in this software.
  import projectq
  from projectq.setups import restrictedgateset, trapped_ion_decomposer
  from projectq.ops import (
      Rx,
      Ry,
      Rz,
      Rxx,
      Ryy,
      XGate,
      YGate,
      SqrtXGate,
      FlushGate,
      Measure,
      Allocate,
      Deallocate,
      Barrier,
      BarrierGate,
  )
  from projectq.cengines import BasicEngine
  from projectq.meta import get_control_count
  from projectq.types import WeakQubitRef
  
  from jaqalpaq.core import CircuitBuilder
  from jaqalpaq.core.circuitbuilder import UnscheduledBlockBuilder
  from jaqalpaq import JaqalError
  
  import numpy as np
  
  one_qubit_gates = {
      Rx: lambda g, q: ("R", q, 0, normalize_angle(g.angle)),
      Ry: lambda g, q: ("R", q, np.pi / 2, normalize_angle(g.angle)),
      Rz: lambda g, q: ("Rz", q, normalize_angle(g.angle)),
      XGate: lambda g, q: ("Px", q),
      YGate: lambda g, q: ("Py", q),
      SqrtXGate: lambda g, q: ("Sx", q),
  }
  two_qubit_gates = {
      Rxx: lambda g, q1, q2: ("MS", q1, q2, 0, normalize_angle(g.angle)),
      Ryy: lambda g, q1, q2: ("MS", q1, q2, np.pi / 2, normalize_angle(g.angle)),
  }
  
  
  def get_engine_list():
      """
      Creates a list of engines that compile to QSCOUT's native gate set.
  
      :returns: The engines.
      :rtype: list(projectq.cengines.BasicEngine)
      """
      return restrictedgateset.get_engine_list(
          one_qubit_gates=tuple(one_qubit_gates.keys()),
          two_qubit_gates=tuple(two_qubit_gates.keys()),
          other_gates=(BarrierGate,),
          compiler_chooser=trapped_ion_decomposer.chooser_Ry_reducer,
      )
  
  
  def normalize_angle(p):
      if p > np.pi * 2.0:
          p -= 2.0 * np.pi
      if p < np.pi * -2.0:
          p += 2.0 * np.pi
      return p
  
  
  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)
  