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

Clean up the implementation of circuit mirroring



support all gate types and add a deuteron ansatz check

Signed-off-by: default avatarThien Nguyen <nguyentm@ornl.gov>
parent 718d7774
Loading
Loading
Loading
Loading
+163 −52
Original line number Diff line number Diff line
#include "mirror_circuit_rb.hpp"
#include "AllGateVisitor.hpp"
#include "clifford_gate_utils.hpp"
#include "qcor_ir.hpp"
#include "qcor_pimpl_impl.hpp"
@@ -7,6 +8,136 @@
#include <cassert>
#include <random>

namespace xacc {
namespace quantum {
// Helper to convert a gate
class GateConverterVisitor : public AllGateVisitor {
public:
  GateConverterVisitor() {
    m_gateRegistry = xacc::getService<xacc::IRProvider>("quantum");
    m_program = m_gateRegistry->createComposite("temp_composite");
  }

  // Keep these 2 gates:
  void visit(CNOT &cnot) override { m_program->addInstruction(cnot.clone()); }
  void visit(U &u) override { m_program->addInstruction(u.clone()); }

  // Rotation gates:
  void visit(Ry &ry) override {
    const double theta = InstructionParameterToDouble(ry.getParameter(0));
    m_program->addInstruction(m_gateRegistry->createInstruction(
        "U", {ry.bits()[0]}, {theta, 0.0, 0.0}));
  }

  void visit(Rx &rx) override {
    const double theta = InstructionParameterToDouble(rx.getParameter(0));
    m_program->addInstruction(m_gateRegistry->createInstruction(
        "U", {rx.bits()[0]}, {theta, -1.0 * M_PI / 2.0, M_PI / 2.0}));
  }

  void visit(Rz &rz) override {
    const double theta = InstructionParameterToDouble(rz.getParameter(0));
    m_program->addInstruction(m_gateRegistry->createInstruction(
        "U", {rz.bits()[0]}, {0.0, theta, 0.0}));
  }

  void visit(X &x) override {
    Rx rx(x.bits()[0], M_PI);
    visit(rx);
  }
  void visit(Y &y) override {
    Ry ry(y.bits()[0], M_PI);
    visit(ry);
  }
  void visit(Z &z) override {
    Rz rz(z.bits()[0], M_PI);
    visit(rz);
  }
  void visit(S &s) override {
    Rz rz(s.bits()[0], M_PI / 2.0);
    visit(rz);
  }
  void visit(Sdg &sdg) override {
    Rz rz(sdg.bits()[0], -M_PI / 2.0);
    visit(rz);
  }
  void visit(T &t) override {
    Rz rz(t.bits()[0], M_PI / 4.0);
    visit(rz);
  }
  void visit(Tdg &tdg) override {
    Rz rz(tdg.bits()[0], -M_PI / 4.0);
    visit(rz);
  }

  void visit(Hadamard &h) override {
    m_program->addInstruction(m_gateRegistry->createInstruction(
        "U", {h.bits()[0]}, {M_PI / 2.0, 0.0, M_PI}));
  }

  void visit(Measure &measure) override {
    xacc::error("The mirror circuit must not contain measure gates.");
  }

  void visit(Identity &i) override {}

  void visit(CY &cy) override {
    // controlled-Y = Sdg(target) - CX - S(target)
    CNOT c1(cy.bits());
    Sdg sdg(cy.bits()[1]);
    S s(cy.bits()[1]);

    visit(sdg);
    visit(c1);
    visit(s);
  }

  void visit(CZ &cz) override {
    // CZ = H(target) - CX - H(target)
    CNOT c1(cz.bits());
    Hadamard h1(cz.bits()[1]);
    Hadamard h2(cz.bits()[1]);

    visit(h1);
    visit(c1);
    visit(h2);
  }

  void visit(CRZ &crz) override {
    const auto theta = InstructionParameterToDouble(crz.getParameter(0));
    // Decompose
    Rz rz1(crz.bits()[1], theta / 2);
    CNOT c1(crz.bits());
    Rz rz2(crz.bits()[1], -theta / 2);
    CNOT c2(crz.bits());

    // Revisit:
    visit(rz1);
    visit(c1);
    visit(rz2);
    visit(c2);
  }

  void visit(CH &ch) override {
    // controlled-H = Ry(pi/4, target) - CX - Ry(-pi/4, target)
    CNOT c1(ch.bits());
    Ry ry1(ch.bits()[1], M_PI_4);
    Ry ry2(ch.bits()[1], -M_PI_4);

    visit(ry1);
    visit(c1);
    visit(ry2);
  }

  std::shared_ptr<CompositeInstruction> getProgram() { return m_program; }

private:
  std::shared_ptr<CompositeInstruction> m_program;
  std::shared_ptr<xacc::IRProvider> m_gateRegistry;
};
} // namespace quantum
} // namespace xacc

namespace {
std::vector<std::shared_ptr<xacc::Instruction>>
getLayer(std::shared_ptr<xacc::CompositeInstruction> circuit, int layerId) {
@@ -23,27 +154,6 @@ getLayer(std::shared_ptr<xacc::CompositeInstruction> circuit, int layerId) {
  assert(!result.empty());
  return result;
}

std::shared_ptr<xacc::Instruction>
rotationToU3Gate(std::shared_ptr<xacc::Instruction> gate) {
  assert(gate->bits().size() == 1);
  const double theta = InstructionParameterToDouble(gate->getParameter(0));
  auto gateProvider = xacc::getService<xacc::IRProvider>("quantum");
  if (gate->name() == "Rx") {
    return gateProvider->createInstruction(
        "U", {gate->bits()[0]}, {theta, -1.0 * M_PI / 2.0, M_PI / 2.0});
  }
  if (gate->name() == "Ry") {
    return gateProvider->createInstruction("U", {gate->bits()[0]},
                                           {theta, 0.0, 0.0});
  }
  if (gate->name() == "Rz") {
    return gateProvider->createInstruction("U", {gate->bits()[0]},
                                           {0.0, theta, 0.0});
  }
  assert(false);
  return nullptr;
}
} // namespace

namespace qcor {
@@ -52,7 +162,18 @@ createMirrorCircuit(std::shared_ptr<CompositeInstruction> in_circuit) {
  std::vector<std::shared_ptr<xacc::Instruction>> mirrorCircuit;
  auto gateProvider = xacc::getService<xacc::IRProvider>("quantum");

  const int n = in_circuit->nPhysicalBits();
  // Gate conversion:
  xacc::quantum::GateConverterVisitor visitor;
  xacc::InstructionIterator it(in_circuit->as_xacc());
  while (it.hasNext()) {
    auto nextInst = it.next();
    if (nextInst->isEnabled() && !nextInst->isComposite()) {
      nextInst->accept(&visitor);
    }
  }

  auto program = visitor.getProgram();
  const int n = program->nPhysicalBits();
  // Tracking the Pauli layer as it is commuted through
  std::vector<qcor::utils::PauliLabel> net_paulis(n,
                                                  qcor::utils::PauliLabel::I);
@@ -85,7 +206,7 @@ createMirrorCircuit(std::shared_ptr<CompositeInstruction> in_circuit) {
        return result;
      };

  // in_circuit->as_xacc()->toGraph()->write(std::cout);
  // program->as_xacc()->toGraph()->write(std::cout);
  const auto decomposeU3Angle = [](xacc::InstPtr u3_gate) {
    const double theta = InstructionParameterToDouble(u3_gate->getParameter(0));
    const double phi = InstructionParameterToDouble(u3_gate->getParameter(1));
@@ -103,36 +224,29 @@ createMirrorCircuit(std::shared_ptr<CompositeInstruction> in_circuit) {
    return gateProvider->createInstruction(
        "U", {qubit}, {theta2 - M_PI, theta3 - 3.0 * M_PI, theta1});
  };
  static const std::vector<std::string> SELF_ADJOINT_CLIFFORD_GATES{
      "H", "X", "Y", "Z", "CNOT", "Swap"};
  const auto d = in_circuit->depth();

  const auto d = program->depth();
  for (int layer = d - 1; layer >= 0; --layer) {
    auto current_layers = getLayer(in_circuit->as_xacc(), layer);
    auto current_layers = getLayer(program, layer);
    for (const auto &gate : current_layers) {
      if (gate->name() == "U" || gate->name() == "Rx" || gate->name() == "Ry" ||
          gate->name() == "Rz") {
        auto u3Gate = gate->name() == "U" ? gate : rotationToU3Gate(gate);
        const auto u3_angles = decomposeU3Angle(u3Gate);
      if (gate->bits().size() == 1) {
        assert(gate->name() == "U");
        const auto u3_angles = decomposeU3Angle(gate);
        const auto [theta1_inv, theta2_inv, theta3_inv] =
            qcor::utils::invU3Gate(u3_angles);
        const size_t qubit = gate->bits()[0];
        in_circuit->addInstruction(gateProvider->createInstruction(
        program->addInstruction(gateProvider->createInstruction(
            "U", {qubit},
            {theta2_inv - M_PI, theta1_inv - 3.0 * M_PI, theta3_inv}));
      } else if (xacc::container::contains(SELF_ADJOINT_CLIFFORD_GATES,
                                           gate->name())) {
        // Handle Clifford gates:
        in_circuit->addInstruction(gate->clone());
      } else {
        xacc::error(
            "Gate " + gate->name() +
            " is not currently supported. Thien, please implement it!!!");
        assert(gate->name() == "CNOT");
        program->addInstruction(gate->clone());
      }
    }
  }
  const int newDepth = in_circuit->depth();
  const int newDepth = program->depth();
  for (int layer = 0; layer < newDepth; ++layer) {
    auto current_layers = getLayer(in_circuit->as_xacc(), layer);
    auto current_layers = getLayer(program, layer);
    // New random Pauli layer
    const std::vector<qcor::utils::PauliLabel> new_paulis = [](int nQubits) {
      static std::random_device rd;
@@ -174,9 +288,8 @@ createMirrorCircuit(std::shared_ptr<CompositeInstruction> in_circuit) {

    const auto current_net_paulis_as_layer = pauliListToLayer(net_paulis);
    for (const auto &gate : current_layers) {
      if (gate->name() == "U" || gate->name() == "Rx" || gate->name() == "Ry" ||
          gate->name() == "Rz") {
        auto u3Gate = gate->name() == "U" ? gate : rotationToU3Gate(gate);
      if (gate->bits().size() == 1) {
        assert(gate->name() == "U");
        const auto new_paulis_as_layer = pauliListToLayer(new_paulis);
        const auto new_net_paulis_reps =
            qcor::utils::computeCircuitSymplecticRepresentations(
@@ -195,7 +308,7 @@ createMirrorCircuit(std::shared_ptr<CompositeInstruction> in_circuit) {
        }

        const size_t qubit = gate->bits()[0];
        const auto [theta1, theta2, theta3] = decomposeU3Angle(u3Gate);
        const auto [theta1, theta2, theta3] = decomposeU3Angle(gate);
        // Compute the pseudo_inverse gate:
        const auto [theta1_new, theta2_new, theta3_new] =
            qcor::utils::computeRotationInPauliFrame(
@@ -203,8 +316,7 @@ createMirrorCircuit(std::shared_ptr<CompositeInstruction> in_circuit) {
                net_paulis[qubit]);
        mirrorCircuit.emplace_back(
            createU3GateFromAngle(qubit, theta1_new, theta2_new, theta3_new));
      } else if (xacc::container::contains(SELF_ADJOINT_CLIFFORD_GATES,
                                           gate->name())) {
      } else {
        mirrorCircuit.emplace_back(gate->clone());
        // we need to account for how the net pauli changes when it gets passed
        // through the clifford layers
@@ -236,8 +348,7 @@ createMirrorCircuit(std::shared_ptr<CompositeInstruction> in_circuit) {
    target_bitString.emplace_back(telp_p[i] == 2);
  }

  auto mirror_comp =
      gateProvider->createComposite(in_circuit->name() + "_MIRROR");
  auto mirror_comp = gateProvider->createComposite(program->name() + "_MIRROR");
  mirror_comp->addInstructions(mirrorCircuit);
  return std::make_pair(std::make_shared<CompositeInstruction>(mirror_comp),
                        target_bitString);
+42 −8
Original line number Diff line number Diff line
@@ -29,14 +29,14 @@ TEST(MirrorCircuitTester, checkU3Inverse) {
        std::make_shared<qcor::CompositeInstruction>(circuit));
    EXPECT_EQ(expected_result.size(), 1);
    EXPECT_EQ(mirror_cir->nInstructions(), 2);
    std::cout << "Expected: " << expected_result[0] << "\n";
    std::cout << "HOWDY: \n" << mirror_cir->toString() << "\n";
    // std::cout << "Expected: " << expected_result[0] << "\n";
    // std::cout << "HOWDY: \n" << mirror_cir->toString() << "\n";
    auto mirror_circuit = provider->createComposite("test_mirror");
    mirror_circuit->addInstructions(mirror_cir->getInstructions());
    mirror_circuit->addInstruction(provider->createInstruction("Measure", {0}));
    auto mc_buffer = xacc::qalloc(1);
    accelerator->execute(mc_buffer, mirror_circuit);
    mc_buffer->print();
    // mc_buffer->print();
    EXPECT_EQ(mc_buffer->getMeasurementCounts().size(), 1);
    EXPECT_EQ(
        mc_buffer->getMeasurementCounts()[std::to_string(expected_result[0])],
@@ -70,15 +70,15 @@ TEST(MirrorCircuitTester, checkMultipleU3) {
        std::make_shared<qcor::CompositeInstruction>(circuit));
    EXPECT_EQ(expected_result.size(), 2);
    EXPECT_EQ(mirror_cir->nInstructions(), 4);
    std::cout << "Expected: " << expected_result[0] << expected_result[1] << "\n";
    std::cout << "HOWDY: \n" << mirror_cir->toString() << "\n";
    // std::cout << "Expected: " << expected_result[0] << expected_result[1] << "\n";
    // std::cout << "HOWDY: \n" << mirror_cir->toString() << "\n";
    auto mirror_circuit = provider->createComposite("test_mirror");
    mirror_circuit->addInstructions(mirror_cir->getInstructions());
    mirror_circuit->addInstruction(provider->createInstruction("Measure", {0}));
    mirror_circuit->addInstruction(provider->createInstruction("Measure", {1}));
    auto mc_buffer = xacc::qalloc(2);
    accelerator->execute(mc_buffer, mirror_circuit);
    mc_buffer->print();
    // mc_buffer->print();
    const std::string expectedBitString =
        std::to_string(expected_result[0]) + std::to_string(expected_result[1]);
    EXPECT_EQ(mc_buffer->getMeasurementCounts().size(), 1);
@@ -116,8 +116,39 @@ TEST(MirrorCircuitTester, checkCliffordGates) {
        std::make_shared<qcor::CompositeInstruction>(circuit));
    const std::string expectedBitString =
        std::to_string(expected_result[0]) + std::to_string(expected_result[1]);
    // std::cout << "HOWDY: \n" << mirror_cir->toString() << "\n";
    // std::cout << "Expected bitstring: " << expectedBitString << "\n";
    auto mirror_circuit = provider->createComposite("test_mirror");
    mirror_circuit->addInstructions(mirror_cir->getInstructions());
    mirror_circuit->addInstruction(provider->createInstruction("Measure", {0}));
    mirror_circuit->addInstruction(provider->createInstruction("Measure", {1}));
    auto mc_buffer = xacc::qalloc(2);
    accelerator->execute(mc_buffer, mirror_circuit);
    //mc_buffer->print();
    EXPECT_EQ(mc_buffer->getMeasurementCounts().size(), 1);
    EXPECT_EQ(mc_buffer->getMeasurementCounts()[expectedBitString], 1024);
    allBitStrings.emplace(expectedBitString);
  }
  // Cover both cases (randomized Pauli worked)
  EXPECT_EQ(allBitStrings.size(), 4);
}

TEST(MirrorCircuitTester, checkDeuteron) {
  auto provider = xacc::getIRProvider("quantum");
  constexpr int NUM_TESTS = 1000;
  auto accelerator = xacc::getAccelerator("qpp", {{"shots", 1024}});
  std::set<std::string> allBitStrings;
  for (int i = 0; i < NUM_TESTS; ++i) {
    auto circuit =
        provider->createComposite(std::string("test") + std::to_string(i));
    circuit->addInstruction(provider->createInstruction("X", {0}));
    circuit->addInstruction(provider->createInstruction(
        "Ry", {1}, std::vector<xacc::InstructionParameter>{random_angle()}));
    circuit->addInstruction(provider->createInstruction("CNOT", {1, 0}));
    auto [mirror_cir, expected_result] = qcor::createMirrorCircuit(
        std::make_shared<qcor::CompositeInstruction>(circuit));
    std::cout << "Expected: " << expected_result[0] << expected_result[1] << "\n";
    std::cout << "HOWDY: \n" << mirror_cir->toString() << "\n";
    std::cout << "Expected bitstring: " << expectedBitString << "\n";
    auto mirror_circuit = provider->createComposite("test_mirror");
    mirror_circuit->addInstructions(mirror_cir->getInstructions());
    mirror_circuit->addInstruction(provider->createInstruction("Measure", {0}));
@@ -125,11 +156,14 @@ TEST(MirrorCircuitTester, checkCliffordGates) {
    auto mc_buffer = xacc::qalloc(2);
    accelerator->execute(mc_buffer, mirror_circuit);
    mc_buffer->print();
    const std::string expectedBitString =
        std::to_string(expected_result[0]) + std::to_string(expected_result[1]);
    EXPECT_EQ(mc_buffer->getMeasurementCounts().size(), 1);
    EXPECT_EQ(mc_buffer->getMeasurementCounts()[expectedBitString], 1024);
    allBitStrings.emplace(expectedBitString);
  }
  // Cover both cases (randomized Pauli worked)
  // We should have seen all 4 possible cases with that number of randomized
  // Pauli runs
  EXPECT_EQ(allBitStrings.size(), 4);
}