Commit a5b32f10 authored by Nguyen, Thien Minh's avatar Nguyen, Thien Minh
Browse files

Fixed control action in compute block



During kernel control: check if we are in a compute block. If so, temporarily disable compute segment flag to create C-U circuit but need to mark all of the instructions as inside compute segment afterward.

No need special treatment for single qubit instruction anymore.

Signed-off-by: default avatarThien Nguyen <nguyentm@ornl.gov>
parent 76bcbe25
Loading
Loading
Loading
Loading
+74 −2
Original line number Diff line number Diff line
@@ -5,7 +5,7 @@ import unittest
from qcor import *

class TestKernelJIT(unittest.TestCase):
  def test_compute_action(self):
  def test_simple(self):
    @qjit
    def x_gate_standalone(q: qubit):
      X(q)
@@ -13,6 +13,7 @@ class TestKernelJIT(unittest.TestCase):
    @qjit
    def test_compute_action1(q : qreg, x : float):
      with compute:
        # control of x gate
        X.ctrl(q[0], q[1])
        for i in range(3):
            H(q[i+1])
@@ -22,6 +23,7 @@ class TestKernelJIT(unittest.TestCase):
    @qjit
    def test_compute_action2(q : qreg, x : float):
      with compute:
        # control of x gate wrapped in a kernel
        x_gate_standalone.ctrl(q[0], q[1])
        for i in range(3):
            H(q[i+1])
@@ -30,9 +32,14 @@ class TestKernelJIT(unittest.TestCase):

    @qjit
    def test_compute_action3(q : qreg, x : float):
      # Control of a kernel compute-action
      # Control of a kernel compute-action (version 1: ctrl of simple gate)
      test_compute_action1.ctrl(q[0], q[1:q.size()], x)

    @qjit
    def test_compute_action4(q : qreg, x : float):
      # Control of a kernel compute-action (version 2: ctrl of a kernel)
      test_compute_action2.ctrl(q[0], q[1:q.size()], x)
    
    q = qalloc(5)
    comp0 = test_compute_action1.extract_composite(q, 1.2345)    
    print(comp0)
@@ -50,8 +57,73 @@ class TestKernelJIT(unittest.TestCase):

    qq = qalloc(6)
    comp2 = test_compute_action3.extract_composite(qq, 1.2345)    
    print(comp2)
    # First and last instructions are CNOT's
    self.assertEqual(comp2.getInstruction(0).name(), "CNOT")  
    self.assertEqual(comp2.getInstruction(comp0.nInstructions() - 1).name(), "CNOT") 
    middle_inst_idx = (int) (comp2.nInstructions() / 2)
    # only Rz ==> CRz
    self.assertEqual(comp2.getInstruction(middle_inst_idx).name(), 'CRZ')
    self.assertEqual(comp2.nInstructions(), comp0.nInstructions()) 

    comp3 = test_compute_action4.extract_composite(qq, 1.2345)    
    print(comp3)
    # First and last instructions are CNOT's
    self.assertEqual(comp3.getInstruction(0).name(), "CNOT")  
    self.assertEqual(comp3.getInstruction(comp0.nInstructions() - 1).name(), "CNOT") 
    middle_inst_idx = (int) (comp3.nInstructions() / 2)
    # only Rz ==> CRz
    self.assertEqual(comp3.getInstruction(middle_inst_idx).name(), 'CRZ')
    self.assertEqual(comp3.nInstructions(), comp0.nInstructions()) 

  def test_multi_control(self):
    @qjit
    def x_gate_standalone_m(q: qubit):
      X(q)

    @qjit
    def test_compute_action1_m(q : qreg, x : float):
      with compute:
        # CCX
        X.ctrl(q[0:2], q[2])
      with action:
        Rz(q[3], x)

    @qjit
    def test_compute_action2_m(q : qreg, x : float):
      with compute:
        # control of x gate wrapped in a kernel
        x_gate_standalone_m.ctrl(q[0:2], q[2])
      with action:
        Rz(q[3], x)

    @qjit
    def test_compute_action3_m(q : qreg, x : float):
      # Control of a kernel compute-action (version 1: ctrl of simple gate)
      test_compute_action1_m.ctrl(q[0], q[1:q.size()], x)

    @qjit
    def test_compute_action4_m(q : qreg, x : float):
      # Control of a kernel compute-action (version 2: ctrl of a kernel)
      test_compute_action2_m.ctrl(q[0], q[1:q.size()], x)
    
    q = qalloc(5)
    comp0 = test_compute_action1_m.extract_composite(q, 1.2345)    
    print(comp0)
    # 2 CCX and 1 Rz
    self.assertEqual(comp0.nInstructions(), 31)
    comp1 = test_compute_action2_m.extract_composite(q, 1.2345)    
    self.assertEqual(comp1.nInstructions(), 31)

    comp2 = test_compute_action3_m.extract_composite(q, 1.2345)    
    self.assertEqual(comp2.nInstructions(), 31)
    comp3 = test_compute_action4_m.extract_composite(q, 1.2345)    
    self.assertEqual(comp3.nInstructions(), 31)
    # Check outer loop control
    self.assertEqual(comp0.getInstruction(15).name(), 'Rz')
    self.assertEqual(comp1.getInstruction(15).name(), 'Rz')
    self.assertEqual(comp2.getInstruction(15).name(), 'CRZ')
    self.assertEqual(comp3.getInstruction(15).name(), 'CRZ')

if __name__ == '__main__':
  unittest.main()
 No newline at end of file
+133 −69
Original line number Diff line number Diff line
@@ -32,8 +32,7 @@ enum class QrtType { NISQ, FTQC };
// with an appropriate implementation of constructors and destructors.
// Users can then call for adjoint/ctrl methods like this
// foo::adjoint(q); foo::ctrl(1, q);
template <typename Derived, typename... Args>
class QuantumKernel {
template <typename Derived, typename... Args> class QuantumKernel {
protected:
  // Tuple holder for variadic kernel arguments
  std::tuple<Args...> args_tuple;
@@ -66,8 +65,7 @@ class QuantumKernel {
  QuantumKernel(std::shared_ptr<qcor::CompositeInstruction> _parent_kernel,
                Args... args)
      : args_tuple(std::forward_as_tuple(args...)),
        parent_kernel(_parent_kernel),
        is_callable(false) {
        parent_kernel(_parent_kernel), is_callable(false) {
    runtime_env = (__qrt_env == "ftqc") ? QrtType::FTQC : QrtType::NISQ;
  }

@@ -158,10 +156,25 @@ class QuantumKernel {
    Derived derived(args...);
    derived.disable_destructor = true;

    // Is is in a **compute** segment?
    // i.e. doing control within the compute block itself.
    // need to by-pass the compute marking in order for the control gate to
    // work.
    const bool cached_is_compute_section =
        ::quantum::qrt_impl->isComputeSection();
    if (cached_is_compute_section) {
      ::quantum::qrt_impl->__end_mark_segment_as_compute();
    }

    // run the operator()(args...) call to get the the functor
    // as a CompositeInstruction (derived.parent_kernel)
    // No compute markings on these instructions
    derived(args...);

    if (cached_is_compute_section) {
      ::quantum::qrt_impl->__begin_mark_segment_as_compute();
    }

    // Use the controlled gate module of XACC to transform
    auto tempKernel = qcor::__internal__::create_composite("temp_control");
    tempKernel->addInstruction(derived.parent_kernel);
@@ -172,10 +185,27 @@ class QuantumKernel {
        std::make_pair("control-idx", ctrlIdx),
    });

    // Mark all the *Controlled* instructions as compute segment
    // if it was in the compute_section.
    // i.e. we have bypassed the marker previously to make C-U to work,
    // now we mark all the generated instructions.
    // e.g.
    // compute {
    //  kernel::ctrl(....)
    //}
    // We disable compute flag to expand kernel then generate its control
    // circuit **then** mark compute for all instructions so that
    // later if we control the top-level kernel (containing this compute/action)
    // no controlling is needed for these instructions.
    if (cached_is_compute_section) {
      for (int instId = 0; instId < ctrlKernel->nInstructions(); ++instId) {
        ctrlKernel->getInstruction(instId)->attachMetadata(
            {{"__qcor__compute__segment__", true}});
      }
    }
    // std::cout << "HELLO\n" << ctrlKernel->toString() << "\n";
    for (int instId = 0; instId < ctrlKernel->nInstructions(); ++instId) {
      parent_kernel->addInstruction(
          ctrlKernel->getInstruction(instId)->clone());
      parent_kernel->addInstruction(ctrlKernel->getInstruction(instId));
    }
    // Need to reset and point current program to the parent
    quantum::set_current_program(parent_kernel);
@@ -216,10 +246,24 @@ class QuantumKernel {
    Derived derived(args...);
    derived.disable_destructor = true;

    // Is is in a **compute** segment?
    // i.e. doing control within the compute block itself.
    // need to by-pass the compute marking in order for the control gate to
    // work.
    const bool cached_is_compute_section =
        ::quantum::qrt_impl->isComputeSection();
    if (cached_is_compute_section) {
      ::quantum::qrt_impl->__end_mark_segment_as_compute();
    }

    // run the operator()(args...) call to get the the functor
    // as a CompositeInstruction (derived.parent_kernel)
    derived(args...);

    if (cached_is_compute_section) {
      ::quantum::qrt_impl->__begin_mark_segment_as_compute();
    }

    // Use the controlled gate module of XACC to transform
    auto tempKernel = qcor::__internal__::create_composite("temp_control");
    tempKernel->addInstruction(derived.parent_kernel);
@@ -229,9 +273,19 @@ class QuantumKernel {
                        {"control-idx", ctrl_bits},
                        {"control-buffer", buffer_name}});

    // Mark all the *Controlled* instructions as compute segment
    // if it was in the compute_section.
    // i.e. we have bypassed the marker previously to make C-U to work,
    // now we mark all the generated instructions.
    if (cached_is_compute_section) {
      for (int instId = 0; instId < ctrlKernel->nInstructions(); ++instId) {
        ctrlKernel->getInstruction(instId)->attachMetadata(
            {{"__qcor__compute__segment__", true}});
      }
    }

    for (int instId = 0; instId < ctrlKernel->nInstructions(); ++instId) {
      parent_kernel->addInstruction(
          ctrlKernel->getInstruction(instId)->clone());
      parent_kernel->addInstruction(ctrlKernel->getInstruction(instId));
    }
    // Need to reset and point current program to the parent
    quantum::set_current_program(parent_kernel);
@@ -246,10 +300,25 @@ class QuantumKernel {
    Derived derived(args...);
    derived.disable_destructor = true;

    // Is is in a **compute** segment?
    // i.e. doing control within the compute block itself.
    // need to by-pass the compute marking in order for the control gate to
    // work.
    const bool cached_is_compute_section =
        ::quantum::qrt_impl->isComputeSection();
    if (cached_is_compute_section) {
      ::quantum::qrt_impl->__end_mark_segment_as_compute();
    }

    // run the operator()(args...) call to get the the functor
    // as a CompositeInstruction (derived.parent_kernel)
    // No compute markings on these instructions
    derived(args...);

    if (cached_is_compute_section) {
      ::quantum::qrt_impl->__begin_mark_segment_as_compute();
    }

    // Use the controlled gate module of XACC to transform
    auto tempKernel = qcor::__internal__::create_composite("temp_control");
    tempKernel->addInstruction(derived.parent_kernel);
@@ -259,9 +328,19 @@ class QuantumKernel {
                        {"control-idx", ctrl_bit},
                        {"control-buffer", ctrl_qbit.first}});

    // Mark all the *Controlled* instructions as compute segment
    // if it was in the compute_section.
    // i.e. we have bypassed the marker previously to make C-U to work,
    // now we mark all the generated instructions.
    if (cached_is_compute_section) {
      for (int instId = 0; instId < ctrlKernel->nInstructions(); ++instId) {
        ctrlKernel->getInstruction(instId)->attachMetadata(
            {{"__qcor__compute__segment__", true}});
      }
    }

    for (int instId = 0; instId < ctrlKernel->nInstructions(); ++instId) {
      parent_kernel->addInstruction(
          ctrlKernel->getInstruction(instId)->clone());
      parent_kernel->addInstruction(ctrlKernel->getInstruction(instId));
    }
    // Need to reset and point current program to the parent
    quantum::set_current_program(parent_kernel);
@@ -364,15 +443,7 @@ using OneQubitKernel = QuantumKernel<Derived, qubit>;
      if (runtime_env == QrtType::FTQC) {                                      \
        quantum::set_current_buffer(q.results());                              \
      }                                                                        \
      bool cached_is_compute_section =                                    \
          ::quantum::qrt_impl->isComputeSection();                        \
      if (cached_is_compute_section) {                                    \
        ::quantum::qrt_impl->__end_mark_segment_as_compute();             \
      }                                                                   \
      ::quantum::QRTNAME(q);                                                   \
      if (cached_is_compute_section) {                                    \
        ::quantum::qrt_impl->__begin_mark_segment_as_compute();           \
      }                                                                   \
      return;                                                                  \
    }                                                                          \
    virtual ~CLASSNAME() {}                                                    \
@@ -400,8 +471,7 @@ ONE_QUBIT_KERNEL_CTRL_ENABLER(Sdg, sdg)
// trailing variadic argument for the lambda class constructor. Once
// instantiated lambda invocation looks just like kernel invocation.

template <typename... CaptureArgs>
class _qpu_lambda {
template <typename... CaptureArgs> class _qpu_lambda {
private:
  // Private inner class for getting the type
  // of a capture variable as a string at runtime
@@ -411,8 +481,7 @@ class _qpu_lambda {
    std::vector<std::string> var_names;
    int counter = 0;

    template <class T>
    std::string type_name() {
    template <class T> std::string type_name() {
      typedef typename std::remove_reference<T>::type TR;
      std::unique_ptr<char, void (*)(void *)> own(
          abi::__cxa_demangle(typeid(TR).name(), nullptr, nullptr, nullptr),
@@ -425,8 +494,7 @@ class _qpu_lambda {
    TupleToTypeArgString(std::string &t) : tmp(t) {}
    TupleToTypeArgString(std::string &t, std::vector<std::string> &_var_names)
        : tmp(t), var_names(_var_names) {}
    template <typename T>
    void operator()(T &t) {
    template <typename T> void operator()(T &t) {
      tmp += type_name<decltype(t)>() + " " +
             (var_names.empty() ? "arg_" + std::to_string(counter)
                                : var_names[counter]) +
@@ -452,8 +520,7 @@ class _qpu_lambda {
  // specifying them since we're using C++17
  _qpu_lambda(std::string &&ff, std::string &&_capture_var_names,
              CaptureArgs &... _capture_vars)
      : src_str(ff),
        capture_var_names(_capture_var_names),
      : src_str(ff), capture_var_names(_capture_var_names),
        capture_vars(std::forward_as_tuple(_capture_vars...)) {
    // Get the original args list
    auto first = src_str.find_first_of("(");
@@ -490,7 +557,8 @@ class _qpu_lambda {
    // preamble if necessary
    auto jit_src = ss.str();
    first = jit_src.find_first_of("{");
    if (!capture_var_names.empty()) jit_src.insert(first + 1, capture_preamble);
    if (!capture_var_names.empty())
      jit_src.insert(first + 1, capture_preamble);

    // std::cout << "JITSRC:\n" << jit_src << "\n";
    // JIT Compile, storing the function pointers
@@ -518,8 +586,7 @@ class _qpu_lambda {
        final_args_tuple);
  }

  template <typename... FunctionArgs>
  void operator()(FunctionArgs... args) {
  template <typename... FunctionArgs> void operator()(FunctionArgs... args) {
    // Map the function args to a tuple
    auto kernel_args_tuple = std::make_tuple(args...);

@@ -558,8 +625,7 @@ template <typename... Args>
using callable_function_ptr =
    void (*)(std::shared_ptr<xacc::CompositeInstruction>, Args...);

template <typename... Args>
class KernelSignature {
template <typename... Args> class KernelSignature {
private:
  callable_function_ptr<Args...> *readOnly = 0;
  callable_function_ptr<Args...> &function_pointer;
@@ -567,13 +633,11 @@ class KernelSignature {
      lambda_func;

public:

  // Here we set function_pointer to null and instead
  // only use lambda_func. If we set lambda_func, function_pointer
  // will never be used, so we should be good.
  template <typename... CaptureArgs>
  KernelSignature(
      _qpu_lambda<CaptureArgs...> &lambda)
  KernelSignature(_qpu_lambda<CaptureArgs...> &lambda)
      : function_pointer(*readOnly),
        lambda_func([&](std::shared_ptr<xacc::CompositeInstruction> pp,
                        Args... a) { lambda(pp, a...); }) {}