Commit 9bafef37 authored by Mccaskey, Alex's avatar Mccaskey, Alex
Browse files

updates to IR v3 for qcor support


Signed-off-by: Mccaskey, Alex's avatarAlex McCaskey <mccaskeyaj@ornl.gov>
parent 68e2a3bf
......@@ -27,7 +27,7 @@ Gate::Gate(std::string name, std::vector<std::size_t> qubts,
std::vector<InstructionParameter> params)
: gateName(name), qbits(qubts), parameters(params) {}
Gate::Gate(const Gate &inst)
: gateName(inst.gateName), qbits(inst.qbits), parameters(inst.parameters),
: gateName(inst.gateName), qbits(inst.qbits), parameters(inst.parameters), arguments(inst.arguments),
enabled(inst.enabled) {}
const std::string Gate::name() const { return gateName; }
......
......@@ -54,12 +54,11 @@ TEST(GateTester, checkIR3) {
EXPECT_THROW(circuit->updateRuntimeArguments(2.2), std::runtime_error);
circuit->updateRuntimeArguments(2.2, 3.3, 4.4);
circuit->applyRuntimeArguments();
std::cout << circuit->toString() <<"\n";
circuit->updateRuntimeArguments(3.2, 4.3, 5.3);
circuit->applyRuntimeArguments();
std::cout << circuit->toString() <<"\n";
}
......
......@@ -30,7 +30,7 @@ void IRToGraphVisitor::addSingleQubitGate(Gate &inst) {
std::make_pair("bits", inst.bits())};
graph->addVertex(newNode);
graph->addEdge(lastNode.get<int>("id"), newNode.get<int>("id"), 1);
graph->addEdge(lastNode.get<std::size_t>("id"), newNode.get<std::size_t>("id"), 1);
qubitToLastNode[bit] = newNode;
}
......@@ -39,8 +39,8 @@ void IRToGraphVisitor::addTwoQubitGate(Gate &inst) {
auto srcbit = inst.bits()[0];
auto tgtbit = inst.bits()[1];
auto lastsrcnodeid = qubitToLastNode[srcbit].get<int>("id");
auto lasttgtnodeid = qubitToLastNode[tgtbit].get<int>("id");
auto lastsrcnodeid = qubitToLastNode[srcbit].get<std::size_t>("id");
auto lasttgtnodeid = qubitToLastNode[tgtbit].get<std::size_t>("id");
id++;
CircuitNode newNode{std::make_pair("name", inst.name()),
......@@ -60,7 +60,7 @@ IRToGraphVisitor::IRToGraphVisitor(const int nQubits) {
std::vector<std::size_t> allQbitIds(nQubits);
std::iota(std::begin(allQbitIds), std::end(allQbitIds), 0);
CircuitNode initNode{std::make_pair("name", std::string("InitialState")),
std::make_pair("id", 0),
std::make_pair("id", id),
std::make_pair("bits", allQbitIds)};
for (int i = 0; i < nQubits; i++) {
qubitToLastNode[i] = initNode;
......@@ -75,7 +75,7 @@ std::shared_ptr<Graph> IRToGraphVisitor::getGraph() {
graph->addVertex(finalNode);
for (auto &kv : qubitToLastNode) {
graph->addEdge(kv.second.get<int>("id"), finalNode.get<int>("id"), 1.0);
graph->addEdge(kv.second.get<std::size_t>("id"), finalNode.get<std::size_t>("id"), 1.0);
}
return graph;
......
......@@ -20,7 +20,7 @@
namespace xacc {
namespace quantum {
using CircuitNode = HeterogeneousMap;//std::map<std::string, InstructionParameter>;
using CircuitNode = HeterogeneousMap;
class IRToGraphVisitor : public AllGateVisitor {
......@@ -29,7 +29,7 @@ protected:
std::map<int, CircuitNode> qubitToLastNode;
int id = 0;
std::size_t id = 0;
void addSingleQubitGate(Gate &inst);
void addTwoQubitGate(Gate &inst);
......
......@@ -38,9 +38,7 @@ PauliOperator::PauliOperator(double c) {
terms.emplace(std::make_pair("I", c));
}
PauliOperator::PauliOperator(std::string fromStr) {
fromString(fromStr);
}
PauliOperator::PauliOperator(std::string fromStr) { fromString(fromStr); }
PauliOperator::PauliOperator(std::complex<double> c, std::string var) {
terms.emplace(std::piecewise_construct, std::forward_as_tuple("I"),
......@@ -90,10 +88,17 @@ PauliOperator::PauliOperator(std::map<int, std::string> operators,
std::forward_as_tuple(coeff, var, operators));
}
std::complex<double> PauliOperator::coefficient() {
if (terms.size() > 1) {
xacc::error("Cannot call PauliOperator::coefficient on operator with more "
"than 1 term.");
}
return terms.begin()->second.coeff();
}
std::vector<std::shared_ptr<CompositeInstruction>>
PauliOperator::observe(std::shared_ptr<CompositeInstruction> function) {
// Create a new GateQIR to hold the spin based terms
// Create a new GateQIR to hold the spin based terms
auto gateRegistry = xacc::getService<IRProvider>("quantum");
std::vector<std::shared_ptr<CompositeInstruction>> observed;
int counter = 0;
......@@ -104,8 +109,8 @@ PauliOperator::observe(std::shared_ptr<CompositeInstruction> function) {
Term spinInst = inst.second;
auto gateFunction = gateRegistry->createComposite(
inst.first, function->getVariables());
auto gateFunction =
gateRegistry->createComposite(inst.first, function->getVariables());
gateFunction->setCoefficient(spinInst.coeff());
......@@ -113,6 +118,10 @@ PauliOperator::observe(std::shared_ptr<CompositeInstruction> function) {
gateFunction->addInstruction(function->clone());
}
for (auto arg : function->getArguments()) {
gateFunction->addArgument(arg, 0);
}
// Loop over all terms in the Spin Instruction
// and create instructions to run on the Gate QPU.
std::vector<std::shared_ptr<xacc::Instruction>> measurements;
......@@ -130,8 +139,8 @@ PauliOperator::observe(std::shared_ptr<CompositeInstruction> function) {
int t = qbit;
std::size_t tt = t;
auto gateName = terms[i].second;
auto meas =
gateRegistry->createInstruction("Measure", std::vector<std::size_t>{tt});
auto meas = gateRegistry->createInstruction("Measure",
std::vector<std::size_t>{tt});
xacc::InstructionParameter classicalIdx(qbit);
meas->setParameter(0, classicalIdx);
measurements.push_back(meas);
......@@ -141,7 +150,8 @@ PauliOperator::observe(std::shared_ptr<CompositeInstruction> function) {
gateRegistry->createInstruction("H", std::vector<std::size_t>{tt});
gateFunction->addInstruction(hadamard);
} else if (gateName == "Y") {
auto rx = gateRegistry->createInstruction("Rx", std::vector<std::size_t>{tt});
auto rx =
gateRegistry->createInstruction("Rx", std::vector<std::size_t>{tt});
InstructionParameter p(pi / 2.0);
rx->setParameter(0, p);
gateFunction->addInstruction(rx);
......@@ -278,8 +288,8 @@ std::vector<std::complex<double>> PauliOperator::toDenseMatrix(const int n) {
}
}
std::vector<std::complex<double>> retv(dim*dim);
Eigen::MatrixXcd::Map(&retv.data()[0], A.rows(),A.cols()) = A;
std::vector<std::complex<double>> retv(dim * dim);
Eigen::MatrixXcd::Map(&retv.data()[0], A.rows(), A.cols()) = A;
return retv;
}
......@@ -360,7 +370,6 @@ void PauliOperator::fromString(const std::string str) {
clear();
operator+=(listener.getOperator());
}
bool PauliOperator::contains(PauliOperator &op) {
......@@ -434,7 +443,8 @@ bool PauliOperator::operator==(const PauliOperator &v) noexcept {
bool found = false;
for (auto &vkv : v.terms) {
if (kv.second.operator==(vkv.second) | (kv.second.id() == "I" && vkv.second.id() == "I")) {
if (kv.second.operator==(vkv.second) |
(kv.second.id() == "I" && vkv.second.id() == "I")) {
found = true;
break;
}
......@@ -638,10 +648,9 @@ std::shared_ptr<IR> PauliOperator::toXACCIR() {
auto tmp = gateRegistry->createComposite("tmp");
auto kernels = observe(tmp);
auto newIr = gateRegistry->createIR();
for (auto& k : kernels) newIr->addComposite(k);
for (auto &k : kernels)
newIr->addComposite(k);
return newIr;
}
int PauliOperator::nQubits() {
......
......@@ -12,7 +12,6 @@
*******************************************************************************/
#ifndef QUANTUM_UTILS_PAULIOPERATOR_HPP_
#define QUANTUM_UTILS_PAULIOPERATOR_HPP_
#include <ios>
#include <unordered_map>
#include <complex>
#include <map>
......@@ -223,7 +222,8 @@ public:
};
class PauliOperator
: public xacc::Observable, public xacc::Cloneable<Observable>,
: public xacc::Observable,
public xacc::Cloneable<Observable>,
public tao::operators::commutative_ring<PauliOperator>,
public tao::operators::equality_comparable<PauliOperator>,
public tao::operators::commutative_multipliable<PauliOperator, double>,
......@@ -234,7 +234,7 @@ protected:
public:
std::shared_ptr<Observable> clone() override {
return std::make_shared<PauliOperator>();
return std::make_shared<PauliOperator>();
}
std::unordered_map<std::string, Term>::iterator begin() {
......@@ -259,6 +259,39 @@ public:
std::vector<std::shared_ptr<CompositeInstruction>>
observe(std::shared_ptr<CompositeInstruction> function) override;
std::vector<std::shared_ptr<Observable>> getSubTerms() override {
std::vector<std::shared_ptr<Observable>> ret;
for (auto &term : getTerms()) {
ret.emplace_back(
new PauliOperator(term.second.ops(), term.second.coeff()));
}
return ret;
}
virtual std::vector<std::shared_ptr<Observable>> getNonIdentitySubTerms() {
std::vector<std::shared_ptr<Observable>> ret;
for (auto &term : getTerms()) {
if (term.first != "I") {
ret.emplace_back(
new PauliOperator(term.second.ops(), term.second.coeff()));
}
}
return ret;
}
virtual std::shared_ptr<Observable> getIdentitySubTerm() {
for (auto &term : getTerms()) {
if (term.first == "I") {
return std::make_shared<PauliOperator>(term.second.ops(),
term.second.coeff());
}
}
return nullptr;
}
std::complex<double> coefficient() override;
const std::vector<std::pair<std::string, std::complex<double>>>
computeActionOnKet(const std::string &bitString);
const std::vector<std::pair<std::string, std::complex<double>>>
......@@ -302,7 +335,7 @@ public:
eval(const std::map<std::string, std::complex<double>> varToValMap);
bool isClose(PauliOperator &other);
int nQubits();
const int nBits() override {return nQubits();}
const int nBits() override { return nQubits(); }
PauliOperator &operator+=(const PauliOperator &v) noexcept;
PauliOperator &operator-=(const PauliOperator &v) noexcept;
......@@ -311,18 +344,15 @@ public:
PauliOperator &operator*=(const double v) noexcept;
PauliOperator &operator*=(const std::complex<double> v) noexcept;
const std::string name() const override {
return "pauli";
}
const std::string description() const override {
return "";
}
void fromOptions(const HeterogeneousMap& options) override {
return;
}
const std::string name() const override { return "pauli"; }
const std::string description() const override { return ""; }
void fromOptions(const HeterogeneousMap &options) override { return; }
};
} // namespace quantum
template const quantum::PauliOperator &
HeterogeneousMap::get<quantum::PauliOperator>(const std::string key) const;
} // namespace xacc
#endif
......@@ -13,9 +13,11 @@
#include "exp.hpp"
#include "FermionOperator.hpp"
#include "IRProvider.hpp"
#include "Instruction.hpp"
#include "ObservableTransform.hpp"
#include "PauliOperator.hpp"
#include "Utils.hpp"
#include "xacc.hpp"
#include "xacc_service.hpp"
#include <memory>
......@@ -140,11 +142,11 @@ bool Exp::expand(const HeterogeneousMap &parameters) {
int name_counter = 1;
std::string name = "exp_tmp";
while (xacc::hasCompiled(name)) {
name += std::to_string(name_counter);
name += std::to_string(name_counter);
}
xasm_src = "__qpu__ void "+name+"(qbit q, double " + paramLetter + ") {\n" +
xasm_src + "}";
xasm_src = "__qpu__ void " + name + "(qbit q, double " + paramLetter +
") {\n" + xasm_src + "}";
auto xasm = xacc::getCompiler("xasm");
auto tmp = xasm->compile(xasm_src)->getComposites()[0];
......@@ -153,7 +155,145 @@ bool Exp::expand(const HeterogeneousMap &parameters) {
addInstruction(inst);
return true;
} // namespace instructions
}
void Exp::applyRuntimeArguments() {
// we expect first argument to be the variable name
// we expect the second argument to be the observable
std::string variable_name = arguments[0]->name;
auto x_val =
arguments[0]->runtimeValue.get<double>(INTERNAL_ARGUMENT_VALUE_KEY);
auto observable = arguments[1]->runtimeValue.getPointerLike<Observable>(
INTERNAL_ARGUMENT_VALUE_KEY);
// Have to make sure this wasn't already expanded
if (nInstructions() == 0) {
std::unordered_map<std::string, xacc::quantum::Term> terms;
if (dynamic_cast<FermionOperator *>(observable)) {
terms = std::dynamic_pointer_cast<PauliOperator>(
xacc::getService<ObservableTransform>("jw")->transform(
xacc::as_shared_ptr(observable)))
->getTerms();
} else {
terms = dynamic_cast<PauliOperator *>(observable)->getTerms();
}
double pi = xacc::constants::pi;
auto gateRegistry = xacc::getService<IRProvider>("quantum");
std::string xasm_src = "";
for (auto inst : terms) {
Term spinInst = inst.second;
// Get the individual pauli terms
auto termsMap = std::get<2>(spinInst);
std::vector<std::pair<int, std::string>> terms;
for (auto &kv : termsMap) {
if (kv.second != "I" && !kv.second.empty()) {
terms.push_back({kv.first, kv.second});
}
}
// The largest qubit index is on the last term
int largestQbitIdx = terms[terms.size() - 1].first;
std::vector<std::size_t> qidxs;
std::stringstream basis_front, basis_back;
for (auto &term : terms) {
auto qid = term.first;
auto pop = term.second;
qidxs.push_back(qid);
if (pop == "X") {
basis_front << "H(q[" << qid << "]);\n";
basis_back << "H(q[" << qid << "]);\n";
} else if (pop == "Y") {
basis_front << "Rx(q[" << qid << "], " << 1.57079362679 << ");\n";
basis_back << "Rx(q[" << qid << "], " << -1.57079362679 << ");\n";
}
}
// std::cout << "QIDS: " << qidxs << "\n";
Eigen::MatrixXi cnot_pairs(2, qidxs.size() - 1);
for (int i = 0; i < qidxs.size() - 1; i++) {
cnot_pairs(0, i) = qidxs[i];
}
for (int i = 0; i < qidxs.size() - 1; i++) {
cnot_pairs(1, i) = qidxs[i + 1];
}
// std::cout << "HOWDY: \n" << cnot_pairs << "\n";
std::stringstream cnot_front, cnot_back;
for (int i = 0; i < qidxs.size() - 1; i++) {
Eigen::VectorXi pairs = cnot_pairs.col(i);
auto c = pairs(0);
auto t = pairs(1);
cnot_front << "CNOT(q[" << c << "], q[" << t << "]);\n";
}
for (int i = qidxs.size() - 2; i >= 0; i--) {
Eigen::VectorXi pairs = cnot_pairs.col(i);
auto c = pairs(0);
auto t = pairs(1);
cnot_back << "CNOT(q[" << c << "], q[" << t << "]);\n";
}
xasm_src = xasm_src + "\n" + basis_front.str() + cnot_front.str();
xasm_src = xasm_src + "Rz(q[" + std::to_string(qidxs[qidxs.size() - 1]) +
"], " + std::to_string(std::real(spinInst.coeff())) + " * " +
variable_name + ");\n";
xasm_src = xasm_src + cnot_back.str() + basis_back.str();
}
int name_counter = 1;
std::string name = "exp_tmp";
while (xacc::hasCompiled(name)) {
name += std::to_string(name_counter);
}
xasm_src = "__qpu__ void " + name + "(qbit q, double " +
arguments[0]->name + ") {\n" + xasm_src + "}";
auto xasm = xacc::getCompiler("xasm");
auto tmp = xasm->compile(xasm_src)->getComposites()[0];
for (auto inst : tmp->getInstructions())
addInstruction(inst);
// store the Rz expressions
for (auto &i : instructions) {
if (i->name() == "Rz") {
rz_expressions.push_back(i->getParameter(0).toString());
}
}
parsingUtil = xacc::getService<ExpressionParsingUtil>("exprtk");
}
int counter = 0;
for (auto &i : instructions) {
if (i->name() == "Rz") {
double x_val_ref = 0.0;
parsingUtil->evaluate(rz_expressions[counter], {variable_name}, {x_val},
x_val_ref);
i->setParameter(0, x_val_ref);
counter++;
}
}
}
} // namespace circuits
} // namespace xacc
\ No newline at end of file
......@@ -14,12 +14,17 @@
#define XACC_GENERATORS_EXP_HPP_
#include "Circuit.hpp"
#include <expression_parsing_util.hpp>
namespace xacc {
namespace circuits {
class Exp : public xacc::quantum::Circuit {
protected:
std::vector<std::string> rz_expressions;
std::shared_ptr<ExpressionParsingUtil> parsingUtil;
public:
Exp() : Circuit("exp_i_theta") {}
void applyRuntimeArguments() override;
bool expand(const xacc::HeterogeneousMap &runtimeOptions) override;
const std::vector<std::string> requiredKeys() override;
DEFINE_CLONE(Exp);
......
......@@ -138,9 +138,9 @@ void CircuitOptimizer::apply(std::shared_ptr<CompositeInstruction> gateFunction,
for (int i = 1; i < graphView->order() - 2; i++) {
auto node = graphView->getVertexProperties(i);
if (node.getString("name") == "CNOT" &&
gateFunction->getInstruction(node.get<int>("id") - 1)
gateFunction->getInstruction(node.get<std::size_t>("id") - 1)
->isEnabled()) {
auto nAsVec = graphView->getNeighborList(node.get<int>("id"));
auto nAsVec = graphView->getNeighborList(node.get<std::size_t>("id"));
// std::vector<int> nAsVec(neighbors.begin(), neighbors.end());
// Note: There is an edge-case if the CNOT is last gate on a pair of qubit wires,
// i.e. both of its neighbors will be the final state node.
......@@ -148,7 +148,7 @@ void CircuitOptimizer::apply(std::shared_ptr<CompositeInstruction> gateFunction,
if (nAsVec[0] == nAsVec[1] && nAsVec[0] != graphView->order() - 1) {
// Check that the neighbor gate is indeed a CNOT gate, i.e. not a different 2-qubit gate.
if (gateFunction->getInstruction(nAsVec[0] - 1)->name() == "CNOT") {
gateFunction->getInstruction(node.get<int>("id") - 1)->disable();
gateFunction->getInstruction(node.get<std::size_t>("id") - 1)->disable();
gateFunction->getInstruction(nAsVec[0] - 1)->disable();
modified = true;
break;
......@@ -170,13 +170,13 @@ void CircuitOptimizer::apply(std::shared_ptr<CompositeInstruction> gateFunction,
for (int i = 1; i < graphView->order() - 2; ++i) {
auto node = graphView->getVertexProperties(i);
auto nAsVec = graphView->getNeighborList(node.get<int>("id"));
auto nAsVec = graphView->getNeighborList(node.get<std::size_t>("id"));
// std::vector<int> nAsVec(adj.begin(), adj.end());
if (nAsVec.size() == 1) {
auto nextNode = graphView->getVertexProperties(nAsVec[0]);
if (node.getString("name") == "H" &&
nextNode.getString("name") == "H") {
gateFunction->getInstruction(node.get<int>("id") - 1)->disable();
gateFunction->getInstruction(node.get<std::size_t>("id") - 1)->disable();
gateFunction->getInstruction(nAsVec[0] - 1)->disable();
modified = true;
break;
......@@ -199,7 +199,7 @@ void CircuitOptimizer::apply(std::shared_ptr<CompositeInstruction> gateFunction,
// neighbor
for (int i = 1; i < graphView->order() - 2; i++) {
auto node = graphView->getVertexProperties(i);
auto nAsVec = graphView->getNeighborList(node.get<int>("id"));
auto nAsVec = graphView->getNeighborList(node.get<std::size_t>("id"));
// if it has more than 1 neighbor, don't consider
if (nAsVec.size() == 1) {
auto nextNode = graphView->getVertexProperties(nAsVec[0]);
......@@ -207,21 +207,21 @@ void CircuitOptimizer::apply(std::shared_ptr<CompositeInstruction> gateFunction,
isRotation(nextNode.getString("name")) &&
node.getString("name") == nextNode.getString("name")) {
auto val1 =
ipToDouble(gateFunction->getInstruction(node.get<int>("id") - 1)
ipToDouble(gateFunction->getInstruction(node.get<std::size_t>("id") - 1)
->getParameter(0));
auto val2 = ipToDouble(
gateFunction->getInstruction(nextNode.get<int>("id") - 1)
gateFunction->getInstruction(nextNode.get<std::size_t>("id") - 1)
->getParameter(0));
if (std::fabs(val1 + val2) < 1e-12) {
gateFunction->getInstruction(node.get<int>("id") - 1)->disable();
gateFunction->getInstruction(nextNode.get<int>("id") - 1)
gateFunction->getInstruction(node.get<std::size_t>("id") - 1)->disable();
gateFunction->getInstruction(nextNode.get<std::size_t>("id") - 1)
->disable();
} else {
InstructionParameter tmp(val1 + val2);
gateFunction->getInstruction(node.get<int>("id") - 1)
gateFunction->getInstruction(node.get<std::size_t>("id") - 1)
->setParameter(0, tmp);
gateFunction->getInstruction(nextNode.get<int>("id") - 1)
gateFunction->getInstruction(nextNode.get<std::size_t>("id") - 1)
->disable();
}
modified = true;
......@@ -302,21 +302,21 @@ bool CircuitOptimizer::tryReduceHadamardGates(std::shared_ptr<CompositeInstructi
// Algorithm: we iterate the circuit until we hit an Hadamard gate,
// then check if the sequence is one of those in Figure 4 of https://arxiv.org/pdf/1710.07345.pdf
auto graphView = io_program->toGraph();
std::vector<int> hadamardNodeIds;
std::vector<std::size_t> hadamardNodeIds;
// We collect all Hadamard nodes ahead of time for the iteration and matching
for (int i = 1; i < graphView->order() - 1; ++i) {
const auto node = graphView->getVertexProperties(i);
if (node.getString("name") == "H") {
hadamardNodeIds.emplace_back(node.get<int>("id"));
hadamardNodeIds.emplace_back(node.get<std::size_t>("id"));
}
}
std::vector<std::vector<int>> matchedReductionPatterns;
std::vector<std::vector<std::size_t>> matchedReductionPatterns;
// Set of Hadamard node Ids that have already been matched against a pattern,
// hence no need to match any more to prevent double matching.
// The below matching is constructed to prioritize patterns that result in more
// gate count reduction, hence we want to keep track of which H gates have already been matched.
std::unordered_set<int> matchedHadamardNodeIds;
std::unordered_set<std::size_t> matchedHadamardNodeIds;
for (const auto& hadamardNode: hadamardNodeIds) {
if (container::contains(matchedHadamardNodeIds, hadamardNode)) {
......@@ -339,14 +339,14 @@ bool CircuitOptimizer::tryReduceHadamardGates(std::shared_ptr<CompositeInstructi
// |
// |