Unverified Commit 5fb42d71 authored by Mccaskey, Alex's avatar Mccaskey, Alex Committed by GitHub
Browse files

Merge pull request #476 from tnguyen-ornl/tnguyen/getNativeCode

First pass of implementing Accelerator::getNativeCode
parents f820332d c61dbb6d
Pipeline #158333 failed with stage
in 11 minutes and 51 seconds
import xacc
xacc.qasm('''
.compiler xasm
.circuit iterative_qpe
.qbit q
H(q[0]);
X(q[1]);
// Prepare the state:
CPhase(q[0], q[1], -5*pi/8);
CPhase(q[0], q[1], -5*pi/8);
CPhase(q[0], q[1], -5*pi/8);
CPhase(q[0], q[1], -5*pi/8);
CPhase(q[0], q[1], -5*pi/8);
CPhase(q[0], q[1], -5*pi/8);
CPhase(q[0], q[1], -5*pi/8);
CPhase(q[0], q[1], -5*pi/8);
H(q[0]);
// Measure and reset
Measure(q[0], c[0]);
Reset(q[0]);
H(q[0]);
CPhase(q[0], q[1], -5*pi/8);
CPhase(q[0], q[1], -5*pi/8);
CPhase(q[0], q[1], -5*pi/8);
CPhase(q[0], q[1], -5*pi/8);
// Conditional rotation
if(c[0]) {
Rz(q[0], -pi/2);
}
H(q[0]);
Measure(q[0], c[1]);
Reset(q[0]);
H(q[0]);
CPhase(q[0], q[1], -5*pi/8);
CPhase(q[0], q[1], -5*pi/8);
if(c[0]) {
Rz(q[0], -pi/4);
}
if(c[1]) {
Rz(q[0], -pi/2);
}
H(q[0]);
Measure(q[0], c[2]);
Reset(q[0]);
H(q[0]);
CPhase(q[0], q[1], -5*pi/8);
if(c[0]) {
Rz(q[0], -pi/8);
}
if(c[1]) {
Rz(q[0], -pi/4);
}
if(c[2]) {
Rz(q[0], -pi/2);
}
H(q[0]);
Measure(q[0], c[3]);
''')
f = xacc.getCompiled('iterative_qpe')
qpu = xacc.getAccelerator('ibm:ibmq_manhattan')
# Note: this QASM represents the circuit in the *native* gateset of the backend:
# e.g. {sx, rz, cx}
print('HOWDY QASM:\n', qpu.getNativeCode(f, {'format': 'qasm'}))
# we can also see the native circuit as a QObj json
print('HOWDY QObj:\n', qpu.getNativeCode(f, {'format': 'QObj'}))
# Make sure the native QASM is valid by recompiling with Qiskit.
from qiskit import QuantumCircuit
qiskit_qc = QuantumCircuit.from_qasm_str(qpu.getNativeCode(f, {'format': 'qasm'}))
qiskit_qc.draw()
...@@ -112,6 +112,24 @@ void bind_accelerator(py::module &m) { ...@@ -112,6 +112,24 @@ void bind_accelerator(py::module &m) {
xacc::Accelerator::updateConfiguration, xacc::Accelerator::updateConfiguration,
"") "")
.def("getConnectivity", &xacc::Accelerator::getConnectivity, "") .def("getConnectivity", &xacc::Accelerator::getConnectivity, "")
.def(
"getNativeCode",
[](xacc::Accelerator &qpu, std::shared_ptr<CompositeInstruction> f) {
return qpu.getNativeCode(f);
},
"")
.def(
"getNativeCode",
[](xacc::Accelerator &qpu, std::shared_ptr<CompositeInstruction> f,
PyHeterogeneousMap &options) {
HeterogeneousMap m;
for (auto &item : options) {
PyHeterogeneousMap2HeterogeneousMap vis(m, item.first);
mpark::visit(vis, item.second);
}
return qpu.getNativeCode(f, m);
},
"")
.def("configurationKeys", &xacc::Accelerator::configurationKeys, "") .def("configurationKeys", &xacc::Accelerator::configurationKeys, "")
.def("contributeInstructions", &xacc::Accelerator::contributeInstructions, .def("contributeInstructions", &xacc::Accelerator::contributeInstructions,
""); "");
......
...@@ -1237,6 +1237,125 @@ void IBMAccelerator::put(const std::string &_url, const std::string &postStr, ...@@ -1237,6 +1237,125 @@ void IBMAccelerator::put(const std::string &_url, const std::string &postStr,
return; return;
} }
std::string
IBMAccelerator::getNativeCode(std::shared_ptr<CompositeInstruction> program,
const HeterogeneousMap &config) {
std::string format = "QObj"; // QObj/QASM
if (config.stringExists("format")) {
format = config.getString("format");
}
// Handle different ways to specify the format:
if (format == "QObj" || format == "QOBJ" || format == "JSON") {
chosenBackend = availableBackends[backend];
auto connectivity = getConnectivity();
// Get the correct QObject Generator
auto qobjGen = xacc::getService<QObjGenerator>(mode);
// Generate the QObject JSON
auto jsonStr = qobjGen->getQObjJsonStr(
{program}, shots, chosenBackend, getBackendPropsResponse, connectivity,
json::parse(defaults_response));
return jsonStr;
} else if (format == "qasm" || format == "Qasm" || format == "QASM" ||
format == "OpenQASM" || format == "OPENQASM") {
chosenBackend = availableBackends[backend];
const auto basis_gates =
chosenBackend["basis_gates"].get<std::vector<std::string>>();
// If the gate set has "u3" -> old gateset.
const auto gateSet = (xacc::container::contains(basis_gates, "u3"))
? QObjectExperimentVisitor::GateSet::U_CX
: QObjectExperimentVisitor::GateSet::RZ_SX_CX;
std::stringstream ss;
InstructionIterator it(program);
int memSlots = 0;
std::map<std::string, std::string> cbit_reg_map;
while (it.hasNext()) {
auto nextInst = it.next();
if (nextInst->isEnabled() && !nextInst->isComposite()) {
auto visitor = std::make_shared<QObjectExperimentVisitor>(
program->name(), program->nLogicalBits(), gateSet);
visitor->maxMemorySlots = memSlots;
nextInst->accept(visitor);
if (nextInst->name() == "Measure") {
++memSlots;
}
auto experiment = visitor->getExperiment();
for (auto &inst : experiment.get_instructions()) {
// std::cout << "HOWDY: " << inst.toString() << "\n";
ss << inst.toString() << "\n";
}
} else if (nextInst->name() == "ifstmt") {
auto ifStmt =
std::dynamic_pointer_cast<xacc::CompositeInstruction>(nextInst);
// !! NOTE!! OpenQASM2 doesn't allow multi-statement if block
// so need to wrap each statement individually.
if (ifStmt) {
for (auto &i : ifStmt->getInstructions()) {
auto visitor = std::make_shared<QObjectExperimentVisitor>(
program->name(), program->nLogicalBits(), gateSet);
i->accept(visitor);
auto experiment = visitor->getExperiment();
// Note: OpenQASM2 cannot express conditional on a single cbit
// e.g., if (c[k] == 1) (c is a multi-bit register)
// Ref:
// https://quantumcomputing.stackexchange.com/questions/17901/if-statement-in-openqasm2-0-on-ibm-quantum-experience-error/17902#17902
// Also see: https://github.com/Qiskit/qiskit-terra/pull/6018
// qc.qasm() breaks when circuit qc contains gates with classical conditioning on a single cbit
// Please note that this is a limitation of **OpenQASM** only,
// i.e., qiskit's QuantumCircuit and QObj can both handle classical conditioning on a single cbit.
// Our native code gen strategy here is to
// convert multi-qreg indexing into multiple single-cbit registers:
// i.e., c[3] -> c3 (single-bit register)
// This only happens when classical conditioning on a single cbit is needed.
for (auto &inst : experiment.get_instructions()) {
// std::cout << "HOWDY: " << inst.toString() << "\n";
const std::string orig_reg_name =
"c[" + std::to_string(nextInst->bits()[0]) + "]";
if (cbit_reg_map.find(orig_reg_name) != cbit_reg_map.end()) {
const std::string single_reg_name = cbit_reg_map[orig_reg_name];
ss << "if (" + single_reg_name + " == 1) ";
} else {
const std::string single_reg_name =
"cReg" + std::to_string(cbit_reg_map.size());
cbit_reg_map[orig_reg_name] = single_reg_name;
ss << "if (" + single_reg_name + " == 1) ";
}
ss << inst.toString() << "\n";
}
}
}
}
}
const auto replaceAll = [](const std::string &t, const std::string &s,
std::string &str) {
std::string::size_type n = 0;
while ((n = str.find(s, n)) != std::string::npos) {
str.replace(n, s.size(), t);
n += t.size();
}
};
std::string preAmple = R"(OPENQASM 2.0;
include "qelib1.inc";
qreg q[)" + std::to_string(program->nLogicalBits()) +
"];\ncreg c[" + std::to_string(memSlots) +
"];\n";
auto qasm_body = ss.str();
for (const auto &[org_name, new_name] : cbit_reg_map) {
const std::string creg_decl = "creg " + new_name + "[1];\n";
preAmple += creg_decl;
// Replace the target of the measurement as well.
replaceAll(new_name, org_name, qasm_body);
}
return preAmple + qasm_body;
}
xacc::error("Unknown native code format '" + format + "'");
return "";
}
std::string std::string
IBMAccelerator::get(const std::string &_url, const std::string &path, IBMAccelerator::get(const std::string &_url, const std::string &path,
std::map<std::string, std::string> headers, std::map<std::string, std::string> headers,
......
...@@ -131,6 +131,8 @@ public: ...@@ -131,6 +131,8 @@ public:
std::vector<std::pair<int, int>> getConnectivity() override; std::vector<std::pair<int, int>> getConnectivity() override;
std::string getNativeCode(std::shared_ptr<CompositeInstruction> program,
const HeterogeneousMap &config) override;
// Return the name of an IRTransformation of type Placement that is // Return the name of an IRTransformation of type Placement that is
// preferred for this Accelerator // preferred for this Accelerator
const std::string defaultPlacementTransformation() override { const std::string defaultPlacementTransformation() override {
......
...@@ -399,6 +399,33 @@ public: ...@@ -399,6 +399,33 @@ public:
std::optional<int64_t> get_condition_reg_id() const { return conditional; } std::optional<int64_t> get_condition_reg_id() const { return conditional; }
void set_condition_reg_id(int64_t value) { this->conditional = value; } void set_condition_reg_id(int64_t value) { this->conditional = value; }
std::string toString() const {
std::stringstream ss;
ss << name;
if (!params.empty()) {
ss << "(";
for (int i = 0; i < params.size(); ++i) {
ss << params[i];
if (i != params.size() - 1) {
ss << ", ";
}
}
ss << ")";
}
for (int i = 0; i < qubits.size(); ++i) {
ss << " q[" << qubits[i] << "]";
if (i != qubits.size() - 1) {
ss << ", ";
}
}
if (!get_memory().empty()) {
ss << " -> c[" << get_memory()[0] << "]";
}
ss << ";";
return ss.str();
}
}; };
class Experiment { class Experiment {
......
...@@ -106,6 +106,14 @@ public: ...@@ -106,6 +106,14 @@ public:
return std::vector<std::pair<int, int>>{}; return std::vector<std::pair<int, int>>{};
} }
// Get circuit representation that the backend would be submitting to the
// physical QPU
virtual std::string
getNativeCode(std::shared_ptr<CompositeInstruction> program,
const HeterogeneousMap &config = {}) {
return program->toString();
}
virtual const std::vector<std::complex<double>> virtual const std::vector<std::complex<double>>
getAcceleratorState(std::shared_ptr<CompositeInstruction> program) { getAcceleratorState(std::shared_ptr<CompositeInstruction> program) {
return std::vector<std::complex<double>>{}; return std::vector<std::complex<double>>{};
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment