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

FTQC to support qalloc as well



Refactor the internal apply_gate to get qubit arguments and keep an intermal map of qubit ID to a global ID.

The logic of anc. qubit allocation is identical to NISQ ==> will combine them in the next commit.

Signed-off-by: default avatarThien Nguyen <nguyentm@ornl.gov>
parent 365977df
Loading
Loading
Loading
Loading
+44 −0
Original line number Diff line number Diff line
#include <qalloc>
// Compile with: qcor -qpu qpp -qrt ftqc qalloc_ftqc.cpp

__qpu__ void test(qreg q, std::vector<int> &result, int shots) {
  for (int i = 0; i < shots; ++i) {
    // Allocate inside a big loop to make sure
    // ancilla qubits are reused appropriately.
    auto anc_reg = qalloc(1);
    H(q[0]);
    CX(q[0], anc_reg[0]);
    int value = 0;
    if (Measure(q[0])) {
      value = value + 1;
      X(q[0]);
    }
    if (Measure(anc_reg[0])) {
      value = value + 2;
      X(anc_reg[0]);
    }
    result.emplace_back(value);
  }
}

int main() {
  auto q = qalloc(1);
  std::vector<int> results;
  test(q, results, 1024);

  int count00 = 0;
  int count11 = 0;
  for (const auto &result : results) {
    if (result == 0) {
      count00++;
    }
    if (result == 3) {
      count11++;
    }
  }
  std::cout << "Count 00 = " << count00 << "; Count 11 = " << count11 << "\n";
  // Reasonable balance Bell distribution
  qcor_expect(count00 + count11 == 1024);
  qcor_expect(count00 > 400);
  qcor_expect(count11 > 400);
}
+137 −22
Original line number Diff line number Diff line
@@ -12,12 +12,84 @@
#include "xacc_service.hpp"
using namespace cppmicroservices;

namespace {
class FtqcQubitAllocator : public AllocEventListener, public QubitAllocator {
public:
  static inline const std::string ANC_BUFFER_NAME = "ftqc_temp_buffer";
  virtual void onAllocate(qubit *in_qubit) override {
    // std::cout << "Allocate: " << (void *)in_qubit << "\n";
  }

  // On deallocate: don't try to deref the qubit since it may have been gone.
  virtual void onDealloc(qubit *in_qubit) override {
    // std::cout << "Deallocate: " << (void *)in_qubit << "\n";
    // If this qubit was allocated from this pool:
    if (xacc::container::contains(m_allocatedQubits, in_qubit)) {
      const auto qIndex = std::find(m_allocatedQubits.begin(),
                                    m_allocatedQubits.end(), in_qubit) -
                          m_allocatedQubits.begin();
      // Strategy: create a storage copy of the returned qubit:
      // i.e. with the same index w.r.t. this global anc. buffer
      // but store it in the pool vector -> will stay alive
      // until giving out at the next allocate()
      qubit archive_qubit(ANC_BUFFER_NAME, qIndex, m_buffer.get());
      m_allocatedQubits[qIndex] = &archive_qubit;
      m_qubitPool.emplace_back(archive_qubit);
    }
  }

  virtual qubit allocate() override {
    if (!m_qubitPool.empty()) {
      auto recycled_qubit = m_qubitPool.back();
      m_qubitPool.pop_back();
      return recycled_qubit;
    }
    if (!m_buffer) {
      // This must be the first call.
      assert(m_allocatedQubits.empty());
      m_buffer = xacc::qalloc(1);
      m_buffer->setName(ANC_BUFFER_NAME);
    }

    // Need to allocate new qubit:
    // Each new qubit will have an incrementing index.
    const auto newIdx = m_allocatedQubits.size();
    qubit new_qubit(ANC_BUFFER_NAME, newIdx, m_buffer.get());
    // Just track that we allocated this qubit
    m_allocatedQubits.emplace_back(&new_qubit);
    m_buffer->setSize(m_allocatedQubits.size());
    return new_qubit;
  }

  static FtqcQubitAllocator *getInstance() {
    if (!g_instance) {
      g_instance = new FtqcQubitAllocator();
    }
    return g_instance;
  }
  static FtqcQubitAllocator *g_instance;

  std::shared_ptr<xacc::AcceleratorBuffer> get_buffer() { return m_buffer; }

private:
  std::vector<qubit> m_qubitPool;
  // Track the list of qubit pointers for those
  // that was allocated by this Allocator.
  std::vector<qubit *> m_allocatedQubits;
  std::shared_ptr<xacc::AcceleratorBuffer> m_buffer;
};

FtqcQubitAllocator *FtqcQubitAllocator::g_instance = nullptr;
} // namespace

namespace qcor {
class FTQC : public quantum::QuantumRuntime {
 public:
  virtual void initialize(const std::string kernel_name) override {
    provider = xacc::getIRProvider("quantum");
    qpu = xacc::internal_compiler::qpu;
    qubitIdToGlobalIdx.clear();
    setGlobalQubitManager(FtqcQubitAllocator::getInstance());
  }

  void __begin_mark_segment_as_compute() override { mark_as_compute = true; }
@@ -26,74 +98,74 @@ class FTQC : public quantum::QuantumRuntime {
  const std::string name() const override { return "ftqc"; }
  const std::string description() const override { return ""; }

  virtual void h(const qubit &qidx) override { applyGate("H", {qidx.second}); }
  virtual void x(const qubit &qidx) override { applyGate("X", {qidx.second}); }
  virtual void y(const qubit &qidx) override { applyGate("Y", {qidx.second}); }
  virtual void z(const qubit &qidx) override { applyGate("Z", {qidx.second}); }
  virtual void t(const qubit &qidx) override { applyGate("T", {qidx.second}); }
  virtual void h(const qubit &qidx) override { applyGate("H", {qidx}); }
  virtual void x(const qubit &qidx) override { applyGate("X", {qidx}); }
  virtual void y(const qubit &qidx) override { applyGate("Y", {qidx}); }
  virtual void z(const qubit &qidx) override { applyGate("Z", {qidx}); }
  virtual void t(const qubit &qidx) override { applyGate("T", {qidx}); }
  virtual void tdg(const qubit &qidx) override {
    applyGate("Tdg", {qidx.second});
    applyGate("Tdg", {qidx});
  }
  virtual void s(const qubit &qidx) override { applyGate("S", {qidx.second}); }
  virtual void s(const qubit &qidx) override { applyGate("S", {qidx}); }
  virtual void sdg(const qubit &qidx) override {
    applyGate("Sdg", {qidx.second});
    applyGate("Sdg", {qidx});
  }

  // Common single-qubit, parameterized instructions
  virtual void rx(const qubit &qidx, const double theta) override {
    applyGate("Rx", {qidx.second}, {theta});
    applyGate("Rx", {qidx}, {theta});
  }
  virtual void ry(const qubit &qidx, const double theta) override {
    applyGate("Ry", {qidx.second}, {theta});
    applyGate("Ry", {qidx}, {theta});
  }
  virtual void rz(const qubit &qidx, const double theta) override {
    applyGate("Rz", {qidx.second}, {theta});
    applyGate("Rz", {qidx}, {theta});
  }
  // U1(theta) gate
  virtual void u1(const qubit &qidx, const double theta) override {
    applyGate("U1", {qidx.second}, {theta});
    applyGate("U1", {qidx}, {theta});
  }
  virtual void u3(const qubit &qidx, const double theta, const double phi,
                  const double lambda) override {
    applyGate("U", {qidx.second}, {theta, phi, lambda});
    applyGate("U", {qidx}, {theta, phi, lambda});
  }

  virtual void reset(const qubit &qidx) override {
    applyGate("Reset", {qidx.second});
    applyGate("Reset", {qidx});
  }

  // Measure-Z
  virtual bool mz(const qubit &qidx) override {
    applyGate("Measure", {qidx.second});
    applyGate("Measure", {qidx});
    // Return the measure result stored in the q reg.
    return (*qReg)[qidx.second];
  }

  // Common two-qubit gates.
  virtual void cnot(const qubit &src_idx, const qubit &tgt_idx) override {
    applyGate("CNOT", {src_idx.second, tgt_idx.second});
    applyGate("CNOT", {src_idx, tgt_idx});
  }
  virtual void cy(const qubit &src_idx, const qubit &tgt_idx) override {
    applyGate("CY", {src_idx.second, tgt_idx.second});
    applyGate("CY", {src_idx, tgt_idx});
  }
  virtual void cz(const qubit &src_idx, const qubit &tgt_idx) override {
    applyGate("CZ", {src_idx.second, tgt_idx.second});
    applyGate("CZ", {src_idx, tgt_idx});
  }
  virtual void ch(const qubit &src_idx, const qubit &tgt_idx) override {
    applyGate("CH", {src_idx.second, tgt_idx.second});
    applyGate("CH", {src_idx, tgt_idx});
  }
  virtual void swap(const qubit &src_idx, const qubit &tgt_idx) override {
    applyGate("Swap", {src_idx.second, tgt_idx.second});
    applyGate("Swap", {src_idx, tgt_idx});
  }

  // Common parameterized 2 qubit gates.
  virtual void cphase(const qubit &src_idx, const qubit &tgt_idx,
                      const double theta) override {
    applyGate("CPhase", {src_idx.second, tgt_idx.second}, {theta});
    applyGate("CPhase", {src_idx, tgt_idx}, {theta});
  }
  virtual void crz(const qubit &src_idx, const qubit &tgt_idx,
                   const double theta) override {
    applyGate("CRZ", {src_idx.second, tgt_idx.second}, {theta});
    applyGate("CRZ", {src_idx, tgt_idx}, {theta});
  }

  // exponential of i * theta * H, where H is an Observable pointer
@@ -137,6 +209,15 @@ class FTQC : public quantum::QuantumRuntime {

  void set_current_buffer(xacc::AcceleratorBuffer *buffer) override {
    qReg = xacc::as_shared_ptr(buffer);
    qubitIdToGlobalIdx.clear();
    // The base qreg will always have exact address in the global register.
    for (size_t i = 0; i < qReg->size(); ++i) {
      qubitIdToGlobalIdx[std::make_pair(qReg->name(), i)] = i;
    }
  }

  QubitAllocator *get_anc_qubit_allocator() {
    return FtqcQubitAllocator::getInstance();
  }

 private:
@@ -155,6 +236,39 @@ class FTQC : public quantum::QuantumRuntime {
    qpu->apply(qReg, gateInst);
  }

  void applyGate(const std::string &gateName,
                 std::initializer_list<size_t> bits,
                 const std::vector<double> &params = {}) {
    applyGate(gateName, std::vector<size_t>(bits), params);
  }

  void applyGate(const std::string &gateName, const std::vector<qubit> &qbits,
                 const std::vector<double> &params = {}) {
    std::vector<xacc::InstructionParameter> instParams;
    for (const auto &val : params) {
      instParams.emplace_back(val);
    }
    std::vector<size_t> bits;
    for (const auto &qb : qbits) {
      // Never seen this qubit
      const auto qubitId = std::make_pair(qb.first, qb.second);
      if (qubitIdToGlobalIdx.find(qubitId) == qubitIdToGlobalIdx.end()) {
        qubitIdToGlobalIdx[qubitId] = qubitIdToGlobalIdx.size();
        std::stringstream logss;
        logss << "Map " << qb.first << "[" << qb.second << "] to global ID "
              << qubitIdToGlobalIdx[qubitId];
        xacc::info(logss.str());
        qReg->setSize(qubitIdToGlobalIdx.size());
      }
      bits.emplace_back(qubitIdToGlobalIdx[qubitId]);
    }
    auto gateInst = provider->createInstruction(gateName, bits, instParams);
    if (mark_as_compute) {
      gateInst->attachMetadata({{"__qcor__compute__segment__", true}});
    }
    qpu->apply(qReg, gateInst);
  }

 private:
  bool mark_as_compute = false;
  std::shared_ptr<xacc::IRProvider> provider;
@@ -162,6 +276,7 @@ class FTQC : public quantum::QuantumRuntime {
  // TODO: eventually, we may want to support an arbitrary number of qubit
  // registers when the FTQC backend can support it.
  std::shared_ptr<xacc::AcceleratorBuffer> qReg;
  std::map<std::pair<std::string, size_t>, size_t> qubitIdToGlobalIdx;
};
}  // namespace qcor

+1 −1
Original line number Diff line number Diff line
@@ -81,7 +81,7 @@ public:
  virtual void set_current_buffer(xacc::AcceleratorBuffer *buffer) = 0;
  // Ancilla qubit allocator:
  // i.e. handle in kernel allocation.
  virtual QubitAllocator *get_anc_qubit_allocator() { return nullptr; }
  virtual QubitAllocator *get_anc_qubit_allocator() = 0;
};
// This represents the public API for the xacc-enabled
// qcor quantum runtime library. The goal here is to provide