From 79baf2bf29f1fb1ededfcc12cf0bef71701f1063 Mon Sep 17 00:00:00 2001 From: Alex McCaskey <mccaskeyaj@ornl.gov> Date: Fri, 3 Feb 2017 19:15:47 +0000 Subject: [PATCH] Adding ability for Scaffold compiler and QasmToGraph util to handle conditional statements based on measured qubits --- .../gate/compilers/scaffold/ScaffCCAPI.hpp | 2 +- .../compilers/scaffold/ScaffoldCompiler.cpp | 52 +++++ .../compilers/scaffold/ScaffoldCompiler.hpp | 7 + .../scaffold/tests/ScaffoldCompilerTester.cpp | 23 +-- quantum/gate/utils/QasmToGraph.hpp | 195 ++++++++++++------ 5 files changed, 199 insertions(+), 80 deletions(-) diff --git a/quantum/gate/compilers/scaffold/ScaffCCAPI.hpp b/quantum/gate/compilers/scaffold/ScaffCCAPI.hpp index 3f46a3b7f..0cbecadec 100644 --- a/quantum/gate/compilers/scaffold/ScaffCCAPI.hpp +++ b/quantum/gate/compilers/scaffold/ScaffCCAPI.hpp @@ -71,7 +71,7 @@ public: tempSrcFile.close(); // Execute the scaffold compiler - std::system("scaffcc -fp .tmpSrcFile.scaffold"); + std::system("scaffcc -fp .tmpSrcFile.scaffold &> /dev/null"); // Remove the temporary source file, we don't need it anymore std::remove(".tmpSrcFile.scaffold"); diff --git a/quantum/gate/compilers/scaffold/ScaffoldCompiler.cpp b/quantum/gate/compilers/scaffold/ScaffoldCompiler.cpp index 15b938174..1c96502be 100644 --- a/quantum/gate/compilers/scaffold/ScaffoldCompiler.cpp +++ b/quantum/gate/compilers/scaffold/ScaffoldCompiler.cpp @@ -33,6 +33,7 @@ #include <regex> #include "ScaffCCAPI.hpp" #include "QasmToGraph.hpp" +#include <boost/algorithm/string.hpp> namespace xacc { @@ -48,6 +49,38 @@ void ScaffoldCompiler::modifySource() { kernelSource.erase(kernelSource.find("__qpu__"), 7); kernelSource = std::string("module ") + kernelSource; + std::string qubitAllocationLine = " qbit qreg[3];\n"; + + // conditional on measurements + // FIXME FOR NOW WE ONLY ACCEPT format + // 'if (creg[0] == 1) GATEOP' + int counter = 1; + std::vector<std::string> ifLines; + std::regex ifstmts("if\\s?\\(\\w+\\[\\w+\\]\\s?=.*\\s?\\)\\s?"); + for (auto i = std::sregex_iterator(kernelSource.begin(), kernelSource.end(), + ifstmts); i != std::sregex_iterator(); ++i) { + std::cout << "HELLO WORLD: " << (*i).str() << "\n"; + std::vector<std::string> splitVec; + std::string ifLine = (*i).str(); + boost::trim(ifLine); + boost::split(splitVec, ifLine, boost::is_any_of(" ")); + conditionalCodeSegments.push_back("module foo" + std::to_string(counter) + "() {\n" + + qubitAllocationLine + " " + splitVec[splitVec.size()-1] + ";\n}\nint main() {\n" + " foo" + std::to_string(counter) + "();\n}"); + counter++; + + ifLines.push_back(ifLine + ";\n"); + } + + for (auto s : conditionalCodeSegments) { + std::cout << s << "\n"; + } + + for (auto s : ifLines) { + auto idx = kernelSource.find(s); + kernelSource.erase(idx, s.size()); + } + // Get the kernel name std::regex functionName("((\\w+)\\s*\\()\\s*"); auto begin = std::sregex_iterator(kernelSource.begin(), kernelSource.end(), @@ -80,6 +113,25 @@ std::shared_ptr<IR> ScaffoldCompiler::compile() { // Get the Qasm as a Graph... auto circuitGraph = QasmToGraph::getCircuitGraph(qasm); + // HERE we have main circuit graph, before conditional + // if branches... So then for each conditional code statement, + // get its circuit graph and add it to the main graph after + // the addition of a COND conditional node that will enable the + // conditional nodes if the measured cbit is a 1. + + // So a COND node needs to know the gate id of the measurement gate + // and the nodes to mark enabled if the measurement is a 1, + if (!conditionalCodeSegments.empty()) { + std::vector<qci::common::Graph<CircuitNode>> condGraphs; + for (auto cond : conditionalCodeSegments) { + auto condQasm = scaffcc.getFlatQASMFromSource(cond); + auto g = QasmToGraph::getCircuitGraph(condQasm); + condGraphs.push_back(g); + } + + QasmToGraph::linkConditionalQasm(circuitGraph, condGraphs); + } + // Create a GraphIR instance from that graph auto graphIR = std::make_shared<ScaffoldGraphIR>(circuitGraph); diff --git a/quantum/gate/compilers/scaffold/ScaffoldCompiler.hpp b/quantum/gate/compilers/scaffold/ScaffoldCompiler.hpp index be662f368..8c641ed51 100644 --- a/quantum/gate/compilers/scaffold/ScaffoldCompiler.hpp +++ b/quantum/gate/compilers/scaffold/ScaffoldCompiler.hpp @@ -80,6 +80,13 @@ public: */ virtual ~ScaffoldCompiler() {} +protected: + + /** + * Reference to potential conditional code + */ + std::vector<std::string> conditionalCodeSegments; + }; } diff --git a/quantum/gate/compilers/scaffold/tests/ScaffoldCompilerTester.cpp b/quantum/gate/compilers/scaffold/tests/ScaffoldCompilerTester.cpp index 157973113..2cc2022be 100644 --- a/quantum/gate/compilers/scaffold/tests/ScaffoldCompilerTester.cpp +++ b/quantum/gate/compilers/scaffold/tests/ScaffoldCompilerTester.cpp @@ -69,7 +69,7 @@ BOOST_AUTO_TEST_CASE(checkSimpleCompile) { } -BOOST_AUTO_TEST_CASE(checkAnotherSimpleCompile) { +BOOST_AUTO_TEST_CASE(checkCodeWithMeasurementIf) { using GraphType = Graph<CircuitNode>; auto compiler = @@ -79,18 +79,15 @@ BOOST_AUTO_TEST_CASE(checkAnotherSimpleCompile) { const std::string src("__qpu__ teleport () {\n" " qbit qreg[3];\n" + " cbit creg[2];\n" " H(qreg[1]);\n" " CNOT(qreg[1],qreg[2]);\n" " CNOT(qreg[0],qreg[1]);\n" " H(qreg[0]);\n" - " MeasZ(qreg[0]);\n" - " MeasZ(qreg[1]);\n" - " // Cz\n" - " H(qreg[2]);\n" - " CNOT(qreg[2], qreg[1]);\n" - " H(qreg[2]);\n" - " // CX = CNOT\n" - " CNOT(qreg[2], qreg[0]);\n" + " creg[0] = MeasZ(qreg[0]);\n" + " creg[1] = MeasZ(qreg[1]);\n" + " if(creg[0] == 1) Z(qreg[2]);\n" + " if (creg[1] == 1) X(qreg[2]);\n" "}\n"); auto ir = compiler->compile(src); @@ -98,10 +95,8 @@ BOOST_AUTO_TEST_CASE(checkAnotherSimpleCompile) { auto graphir = std::dynamic_pointer_cast<xacc::GraphIR<GraphType>>(ir); BOOST_VERIFY(graphir); - // I drew this out on paper, we should have 12 - // nodes and 17 edges... - BOOST_VERIFY(graphir->order() == 12); - BOOST_VERIFY(graphir->size() == 17); - graphir->persist(std::cout); + BOOST_VERIFY(graphir->order() == 16); + BOOST_VERIFY(graphir->size() == 23); + } diff --git a/quantum/gate/utils/QasmToGraph.hpp b/quantum/gate/utils/QasmToGraph.hpp index d6bbb05e2..f8193e974 100644 --- a/quantum/gate/utils/QasmToGraph.hpp +++ b/quantum/gate/utils/QasmToGraph.hpp @@ -33,39 +33,11 @@ #include "Graph.hpp" #include <regex> -#include <boost/unordered_map.hpp> -#include <boost/assign/list_of.hpp> #include <boost/algorithm/string.hpp> namespace xacc { namespace quantum { -using boost::assign::map_list_of; - -/** - * Enumeration of gates we support - */ -enum SupportedGates { - H, CNot, C_Z, C_X, Measure, X, Y, Z, - T, S, ZZ, SS, Swap, Toffoli, InitialState, - FinalState -}; - -/** - * Create a string to SupportedGates mapping - */ -const boost::unordered_map<std::string, SupportedGates> strToGate = - map_list_of("h", H)("cnot", CNot)("c_z", C_Z)("c_x", C_X)("measure", - Measure)("x", X)("y", Y)("z", Z)("t", T)("s", S)("zz", ZZ)("ss", - SS)("swap", Swap)("toffoli", Toffoli); - -/** - * Create a SupportedGates to string mapping - */ -const boost::unordered_map<SupportedGates, std::string> gateToStr = - map_list_of(H, "h")(CNot, "cnot")(C_Z, "c_z")(C_X, "c_x")(Measure, - "measure")(X, "x")(Y, "y")(Z, "z")(T, "t")(S, "s")(ZZ, "zz")(SS, - "ss")(Swap, "swap")(Toffoli, "toffoli")(FinalState, "FinalState")(InitialState, "InitialState"); /** * CircuitNode subclasses QCIVertex to provide the following * parameters in the given order: @@ -74,7 +46,7 @@ const boost::unordered_map<SupportedGates, std::string> gateToStr = * Qubit Ids that the gate acts on */ class CircuitNode: public qci::common::QCIVertex<std::string, int, int, - std::vector<int>> { + std::vector<int>, bool> { public: CircuitNode() : QCIVertex() { @@ -82,6 +54,11 @@ public: propertyNames[1] = "Circuit Layer"; propertyNames[2] = "Gate Vertex Id"; propertyNames[3] = "Gate Acting Qubits"; + propertyNames[4] = "Enabled"; + + // by default all circuit nodes + // are enabled and + std::get<4>(properties) = true; } }; @@ -133,12 +110,10 @@ public: // Set the number of qubits nQubits = qubitVarNameToId.size(); - std::cout << "Number of Qubits is " << nQubits << std::endl; - // First create a starting node for the initial // wave function - it should have nQubits outgoing // edges - graph.addVertex(gateToStr.at(SupportedGates::InitialState), 0, 0, allQbitIds); + graph.addVertex("InitialState", 0, 0, allQbitIds, true); std::vector<CircuitNode> gateOperations; for (auto line : qasmLines) { @@ -157,7 +132,6 @@ public: auto g = boost::to_lower_copy(gateCommand[0]); boost::trim(g); if (g == "measz") g = "measure"; -// auto s = strToGate.at(g); std::get<0>(node.properties) = g; // If not a 2 qubit gate, and if the acting @@ -198,10 +172,18 @@ public: } // Add a final layer for the graph sink - graph.addVertex(gateToStr.at(SupportedGates::FinalState), layer+1, gateId, allQbitIds); + CircuitNode finalNode; + std::get<0>(finalNode.properties) = "FinalState"; + std::get<1>(finalNode.properties) = layer+1; + std::get<2>(finalNode.properties) = gateId; + std::get<3>(finalNode.properties) = allQbitIds; + std::get<4>(finalNode.properties) = true; + + graph.addVertex(finalNode); + gateOperations.push_back(finalNode); // Set how many layers are in this circuit - int maxLayer = layer; + int maxLayer = layer+1; // Print info... for (auto cn : gateOperations) { @@ -217,14 +199,123 @@ public: std::cout << "\n\n"; } - // Add an edge between the initial state node - // and the layer 1 gates + generateEdgesFromLayer(1, graph, gateOperations, 0); + + return graph; + } + + /** + * Create connecting conditional nodes that link the main + * circuit graph to subsequent conditional graphs. The conditional + * nodes can be used by Accelerators to figure out if the condition + * code should be executed or not. + * s + * @param mainGraph + * @param conditionalGraphs + */ + static void linkConditionalQasm(qci::common::Graph<CircuitNode>& mainGraph, + std::vector<qci::common::Graph<CircuitNode>> conditionalGraphs) { + + // At this point we have a main circuit graph, + // and one or more smaller conditional graphs (each with + // initial and final state nodes. + + // We need to loop through the conditional graphs and + // pull out the gate vertices (skip initial final state nodes) + // and add them to the main graph after some conditional nodes + + // NOTE We assume that the ith conditionalGraph corresponds to the ith + // measurement gate... + std::vector<int> measurementIds; + std::vector<std::vector<int>>measurementQubits; + for (int i = 0; i < mainGraph.order(); i++) { + if (mainGraph.getVertexProperty<0>(i) == "measure") { + measurementIds.push_back(mainGraph.getVertexProperty<2>(i)); + measurementQubits.push_back(mainGraph.getVertexProperty<3>(i)); + } + } + + assert (measurementIds.size() == conditionalGraphs.size()); + + int counter = 0; + int finalIdMainGraph = mainGraph.getVertexProperty<2>(mainGraph.order() - 1); + int finalLayerMainGraph = mainGraph.getVertexProperty<1>(mainGraph.order() - 1); + int layer = finalLayerMainGraph + 1; + int id = finalIdMainGraph + 1; + + // By default all conditional graph nodes should be disabled + // Conditional nodes should have acting qubits set to the + // measured qubit, and gate vertex id set to the measurement + // gate vertex id. + for (auto g : conditionalGraphs) { + CircuitNode node; + std::get<0>(node.properties) = "conditional"; + std::get<2>(node.properties) = measurementIds[counter]; + std::get<3>(node.properties) = measurementQubits[counter]; + mainGraph.addVertex(node); + + // Connect the main graph to the cond node + mainGraph.addEdge(finalIdMainGraph, id); + + id++; + + std::vector<CircuitNode> newGates; + int initialStateId, initialStateLayer; + for (int i = 0; i < g.order(); i++) { + CircuitNode newVert; + newVert.properties = g.getVertexProperties(i); + std::get<0>(newVert.properties) = g.getVertexProperty<0>(i); + std::get<1>(newVert.properties) = layer+g.getVertexProperty<1>(i); + std::get<2>(newVert.properties) = id; + std::get<3>(newVert.properties) = g.getVertexProperty<3>(i); + std::get<4>(newVert.properties) = false; + mainGraph.addVertex(newVert); + newGates.push_back(newVert); + + // Connect conditional node with first node here + if(i == 0) mainGraph.addEdge(id - 1, id); + if(i == 0) initialStateId = id; + if (i == 0) initialStateLayer = std::get<1>(newVert.properties); + + id++; + } + + generateEdgesFromLayer(initialStateLayer+1, mainGraph, newGates, initialStateId); + + // Set the new layer for the next conditional node + layer = mainGraph.getVertexProperty<1>(id - 1) + 1; + counter++; + } + + + + + } + +private: + + /** + * Generate all edges for the circuit graph starting at + * the given layer. + * + * @param layer + * @param graph + * @param gateOperations + * @param initialStateId + */ + static void generateEdgesFromLayer(const int layer, + qci::common::Graph<CircuitNode>& graph, + std::vector<CircuitNode>& gateOperations, int initialStateId) { + + int nQubits = std::get<3>(graph.getVertexProperties(0)).size(); + int maxLayer = std::get<1>(gateOperations[gateOperations.size() - 1].properties); + std::map<int,int> qubitToCurrentGateId; for (int i = 0 ; i < nQubits; i++) { - qubitToCurrentGateId[i] = 0; + qubitToCurrentGateId[i] = initialStateId; } - int currentLayer = 0; + int currentLayer = layer; while (currentLayer <= maxLayer) { std::vector<CircuitNode> currentLayerGates; std::copy_if(gateOperations.begin(), gateOperations.end(), @@ -242,33 +333,7 @@ public: currentLayer++; } - - // Add a graph sink - ie a final node representing - // the final system state - int counter = 0; - - // Walk the list downward, skip first node since is - // the graph sink - for (int i = gateOperations.size() - 1; i >= 0; i--) { - auto gate = gateOperations[i]; - int currentGateId = std::get<2>(gate.properties); - int nQubitsActing = std::get<3>(gate.properties).size(); - int gateDegree = graph.degree(currentGateId); - for (int j = gateDegree; j < 2 * nQubitsActing; j++) { - graph.addEdge(currentGateId, gateId); - counter++; - } - - // Break early if we can... - if (counter == nQubits) - break; - } - - return graph; } - -private: - /** * This method determines if a new layer should be added to the circuit. * -- GitLab