Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix conflict
Browse files Browse the repository at this point in the history
vsoftco committed Nov 28, 2023
2 parents edbb2af + 8bf5aa4 commit fc01a9a
Showing 11 changed files with 571 additions and 12 deletions.
8 changes: 8 additions & 0 deletions examples/ionq_test.qasm
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
OPENQASM 2.0;
include "qelib1.inc";

qreg q[2];
qreg a[2];

tdg a[0];
crz(2) a[1], q[0];
19 changes: 12 additions & 7 deletions include/grid_synth/gmp_functions.hpp
Original file line number Diff line number Diff line change
@@ -210,14 +210,15 @@ inline mpf_class reduce_angle(const mpf_class& phi) {
inline mpf_class sin(const mpf_class& theta) {
long int initial_prec = theta.get_prec();
long int tol_exp = std::log10(2) * initial_prec;
mpf_class eps(("1e-" + std::to_string(tol_exp)));
mpf_class phi = reduce_angle(theta);
mpz_class i(1);
mpf_class lasts(0);
mpf_class s = phi;
mpf_class fact(1);
mpf_class num(phi);
mpf_class sign(1);
while (gmp_abs(s - lasts) > mpf_class("1e-" + std::to_string(tol_exp))) {
while (gmp_abs(s - lasts) > eps) {
lasts = s;
i += mpf_class("2");
fact *= i * (i - mpf_class("1"));
@@ -231,14 +232,15 @@ inline mpf_class sin(const mpf_class& theta) {
inline mpf_class cos(const mpf_class& theta) {
// long int initial_prec = theta.get_prec();
long int tol_exp = std::log10(2) * theta.get_prec();
mpf_class eps(("1e-" + std::to_string(tol_exp)));
mpf_class phi = reduce_angle(theta);
mpz_class i(0);
mpf_class lasts(0);
mpf_class s(1);
mpf_class fact(1);
mpf_class num(1);
mpf_class sign(1);
while (gmp_abs(s - lasts) > mpf_class("1e-" + std::to_string(tol_exp))) {
while (gmp_abs(s - lasts) > eps) {
lasts = s;
i += mpf_class("2");
fact *= i * (i - mpf_class("1"));
@@ -250,16 +252,19 @@ inline mpf_class cos(const mpf_class& theta) {
}

inline mpf_class exp(const mpf_class& x) {
// TODO: fix stability issue when x negative
if (x < 0)
return 1 / gmpf::exp(-x);
long int tol_exp = std::log10(2) * x.get_prec();
mpz_class i = 0;
mpf_class eps(("1e-" + std::to_string(tol_exp)));
mpz_class i = 1;
mpf_class s = 1;
mpf_class term = 1;
while (gmp_abs(term) > mpf_class("1e-" + std::to_string(tol_exp))) {
mpf_class term = x;
// Use Taylor's remainder theorem bound on error
while (gmp_abs(term*s) > eps) {
s += term;
i += 1;
term *= x;
term /= i;
s += term;
}
return s;
}
2 changes: 1 addition & 1 deletion include/output/cirq.hpp
Original file line number Diff line number Diff line change
@@ -375,7 +375,7 @@ class CirqOutputter final : public ast::Visitor {
}
};

/** \brief Writes an AST in Cirq format to a stdout */
/** \brief Writes an AST in Cirq format to stdout */
void output_cirq(ast::Program& prog) {
CirqOutputter outputter(std::cout);
outputter.run(prog);
243 changes: 243 additions & 0 deletions include/output/ionq.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
/*
* This file is part of staq.
*
* Copyright (c) 2019 - 2023 softwareQ Inc. All rights reserved.
*
* MIT License
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

/**
* \file output/ionq.hpp
* \brief IonQ outputter
*/

#ifndef OUTPUT_IONQ_HPP_
#define OUTPUT_IONQ_HPP_

#include <fstream>
#include <iomanip>
#include <typeinfo>

#include "qasmtools/ast/ast.hpp"

namespace staq {
namespace output {

namespace ast = qasmtools::ast;

/** \brief Equivalent IonQ standard gates for qasm standard gates */
std::unordered_map<std::string, std::string> qasmstd_to_ionq{
{"sdg", "si"}, {"tdg", "ti"}, {"u1", "rz"}};

/**
* \class staq::output::IonQOutputter
* \brief Visitor for converting a QASM AST to IonQ
*/
class IonQOutputter final : public ast::Visitor {
public:
IonQOutputter(std::ostream& os) : Visitor(), os_(os) {}
~IonQOutputter() = default;

void run(ast::Program& prog) {
prefix_ = "";
first_gate = true;

prog.accept(*this);
}

// Variables
void visit(ast::VarAccess& ap) {}

// Expressions
void visit(ast::BExpr& expr) {}

void visit(ast::UExpr& expr) {}

void visit(ast::PiExpr&) {}

void visit(ast::IntExpr& expr) {}

void visit(ast::RealExpr& expr) {}

void visit(ast::VarExpr& expr) {}

// Statements
void visit(ast::MeasureStmt& stmt) {}

void visit(ast::ResetStmt& stmt) {}

void visit(ast::IfStmt& stmt) {}

// Gates
void visit(ast::UGate& gate) {}

void visit(ast::CNOTGate& gate) {}

void visit(ast::BarrierGate&) {}

void visit(ast::DeclaredGate& gate) {
// JSON output: avoid outputting comma before first gate
if (first_gate) {
first_gate = false;
} else {
os_ << ",\n";
}

os_ << prefix_ << "{\n";
prefix_ += " ";

std::string name = gate.name();
if (auto it = qasmstd_to_ionq.find(gate.name());
it != qasmstd_to_ionq.end())
name = it->second;

int cur_qarg = 0;
bool ctrl = false;
// Handle control gates
// Control is the first qarg, if applicable
if (name[0] == 'c') {
cur_qarg = 1;
ctrl = true;
name = name.substr(1);
}

// IonQ handles gates with multiple control args,
// but the QE standard gates have at most one control arg.
if (ctrl) {
os_ << prefix_ << "\"control\": " << gate.qarg(0).offset().value()
<< ",\n";
}

if (gate.num_qargs() - cur_qarg == 1) {
// Single target gate
os_ << prefix_
<< "\"target\": " << gate.qarg(cur_qarg).offset().value()
<< ",\n";
} else {
// Multi target gate
os_ << prefix_ << "\"targets\": [";
for (int i = cur_qarg; i < gate.num_qargs(); ++i) {
os_ << gate.qarg(i).offset().value();
if (i + 1 != gate.num_qargs())
os_ << ",";
}

os_ << "],\n";
}

if (gate.num_cargs()) {
// TODO: assert that there is exactly one carg
// TODO: assert that this is a rotation gate
// TODO: Handle multiples of pi nicely
os_ << prefix_ << "\"angle\": " << std::fixed
<< gate.carg(0).constant_eval().value() / qasmtools::utils::pi << ",\n";
}

os_ << prefix_ << "\"gate\": \"" << name << "\"\n"; // no comma

prefix_.resize(prefix_.size() - 4);
os_ << prefix_
<< "}"; // Do not output newline for proper JSON formatting
}

// Declarations
void visit(ast::GateDecl& decl) {}

void visit(ast::OracleDecl& decl) {}

void visit(ast::RegisterDecl& decl) {
if (decl.is_quantum()) {
os_ << prefix_ << "\"qubits\": " << decl.size() << ",\n";
} else {
// should never occur
}
}

void visit(ast::AncillaDecl& decl) {}

// Program
void visit(ast::Program& prog) {
os_ << prefix_ << "{\n";
prefix_ += " ";

os_ << prefix_ << "\"format\": \"ionq.circuit.v0\",\n";
os_ << prefix_ << "\"gateset\": \"qis\",\n";

// Print the qubit count line (global register decl)
prog.foreach_stmt([this](auto& stmt) {
if (typeid(stmt) == typeid(ast::RegisterDecl))
stmt.accept(*this);
});

os_ << prefix_ << "\"circuit\": [\n";
prefix_ += " ";

// Program body
prog.foreach_stmt([this](auto& stmt) {
// Skip the gate declarations from qelib1.inc
// and the global register decl
if ((typeid(stmt) != typeid(ast::GateDecl)) &&
(typeid(stmt) != typeid(ast::RegisterDecl)))
stmt.accept(*this);
});

// Close circuit
os_ << "\n";
prefix_.resize(prefix_.size() - 4);
os_ << prefix_ << "]\n";

// Close input
prefix_.resize(prefix_.size() - 4);
os_ << prefix_ << "}\n";
}

private:
std::ostream& os_;

std::string prefix_ = "";
bool first_gate = true;
};

/** \brief Writes an AST in IonQ format to stdout */
void output_ionq(ast::Program& prog) {
IonQOutputter outputter(std::cout);
outputter.run(prog);
}

/** \brief Writes an AST in IonQ format to a given output stream */
void write_ionq(ast::Program& prog, std::string fname) {
std::ofstream ofs;
ofs.open(fname);

if (!ofs.good()) {
std::cerr << "Error: failed to open output file " << fname << "\n";
} else {
IonQOutputter outputter(ofs);
outputter.run(prog);
}

ofs.close();
}

} /* namespace output */
} /* namespace staq */

#endif /* OUTPUT_IONQ_HPP_ */
2 changes: 1 addition & 1 deletion include/output/projectq.hpp
Original file line number Diff line number Diff line change
@@ -431,7 +431,7 @@ class ProjectQOutputter final : public ast::Visitor {
}
};

/** \brief Writes an AST in ProjectQ format to a stdout */
/** \brief Writes an AST in ProjectQ format to stdout */
void output_projectq(ast::Program& prog) {
ProjectQOutputter outputter(std::cout);
outputter.run(prog);
2 changes: 1 addition & 1 deletion include/output/qsharp.hpp
Original file line number Diff line number Diff line change
@@ -372,7 +372,7 @@ class QSharpOutputter final : public ast::Visitor {
bool ambiguous_ = false;
};

/** \brief Writes an AST in Q# format to a stdout */
/** \brief Writes an AST in Q# format to stdout */
void output_qsharp(ast::Program& prog) {
QSharpOutputter outputter(std::cout);
outputter.run(prog);
2 changes: 1 addition & 1 deletion include/output/quil.hpp
Original file line number Diff line number Diff line change
@@ -340,7 +340,7 @@ class QuilOutputter final : public ast::Visitor {
std::unordered_map<ast::symbol, std::pair<int, int>> globals_{};
};

/** \brief Writes an AST in Quil format to a stdout */
/** \brief Writes an AST in Quil format to stdout */
void output_quil(ast::Program& prog) {
QuilOutputter outputter(std::cout);
outputter.run(prog);
171 changes: 171 additions & 0 deletions include/transformations/replace_ugate.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
/*
* This file is part of staq.
*
* Copyright (c) 2019 - 2023 softwareQ Inc. All rights reserved.
*
* MIT License
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

/**
* \file transformations/replace_ugate.hpp
* \brief Replacing common U gates with QE standard gates
*/

#ifndef TRANSFORMATIONS_REPLACE_UGATE_HPP_
#define TRANSFORMATIONS_REPLACE_UGATE_HPP_

#include <list>
#include <unordered_map>
#include <variant>

#include "qasmtools/ast/replacer.hpp"

namespace staq {
namespace transformations {

namespace ast = qasmtools::ast;

/**
* \brief Replacing UGates
*
* Visits an AST and replaces common U gates with QE standard
* gates if possible. Assumes qelib1.inc is included.
*/
void replace_ugates(ast::ASTNode& node);

static constexpr double pi = qasmtools::utils::pi;
static constexpr double EPS = 1e-9;

struct UArgs {
double theta;
double phi;
double lambda;
};

// clang-format off
static const std::vector<std::pair<UArgs,std::string>> standard_gates{
{{pi, 0, pi}, "x"},
{{pi, pi/2, pi/2}, "y"},
{{0, 0, pi}, "z"},
{{pi/2, 0, pi}, "h"},
{{0, 0, pi/2}, "s"},
{{0, 0, -pi/2}, "sdg"},
{{0, 0, pi/4}, "t"},
{{0, 0, -pi/4}, "tdg"}
};
// clang-format on

/* Implementation */
class ReplaceUGateImpl final : public ast::Replacer {
public:
ReplaceUGateImpl() = default;
~ReplaceUGateImpl() = default;

void run(ast::ASTNode& node) { node.accept(*this); }

// Replace ast::CNOT with ast::DeclaredGate cx
std::optional<std::list<ast::ptr<ast::Gate>>>
replace(ast::CNOTGate& gate) override {
std::cerr << "CNOT\n";
std::vector<ast::ptr<ast::Expr>> c_args;
std::vector<ast::VarAccess> q_args{gate.ctrl(), gate.tgt()};

std::list<ast::ptr<ast::Gate>> ret;
ret.emplace_back(std::make_unique<ast::DeclaredGate>(ast::DeclaredGate(
gate.pos(), "cx", std::move(c_args), std::move(q_args))));
return std::move(ret);
}

std::optional<std::list<ast::ptr<ast::Gate>>>
replace(ast::UGate& gate) override {
double theta, phi, lambda;
try {
theta = gate.theta().constant_eval().value();
phi = gate.phi().constant_eval().value();
lambda = gate.lambda().constant_eval().value();
} catch (...) {
std::cerr << "found VarExpr in UGate args"
<< "\n";
// this should never happen; if this is reached then the Replacer
// is recurring too far down the AST
throw;
}

std::string name = "";
std::vector<ast::ptr<ast::Expr>> c_args;
std::vector<ast::VarAccess> q_args{gate.arg()};

// Simple cases: x y z h s sdg t tdg
for (auto& [g_args, g_name] : standard_gates) {
if (std::abs(theta - g_args.theta) < EPS &&
std::abs(phi - g_args.phi) < EPS &&
std::abs(lambda - g_args.lambda) < EPS) {

name = g_name;
break;
}
}

// Remaining cases: rz ry rx
if (name == "") {
if (std::abs(theta) < EPS && std::abs(phi) < EPS) {
name = "rz";
// TODO: Directly copy the exprs from the U gate
// to avoid precision loss
c_args.emplace_back(
std::make_unique<ast::RealExpr>(gate.pos(), lambda));
} else if (std::abs(phi) < EPS && std::abs(lambda) < EPS) {
name = "ry";
c_args.emplace_back(
std::make_unique<ast::RealExpr>(gate.pos(), theta));
} else if (std::abs(phi + pi / 2) < EPS &&
std::abs(lambda - pi / 2) < EPS) {
name = "rx";
c_args.emplace_back(
std::make_unique<ast::RealExpr>(gate.pos(), theta));
}
}

// Throw error if U gate is not a QE standard gate
if (name == "") {
throw std::logic_error{""};
return std::nullopt;
}

std::list<ast::ptr<ast::Gate>> ret;
ret.emplace_back(std::make_unique<ast::DeclaredGate>(ast::DeclaredGate(
gate.pos(), name, std::move(c_args), std::move(q_args))));
return std::move(ret);
}

// Avoid visiting children of GateDecl
void visit(ast::GateDecl& decl) override {}
};

void replace_ugates(ast::ASTNode& node) {
ReplaceUGateImpl alg;
alg.run(node);
}

} /* namespace transformations */
} /* namespace staq */

#endif /* TRANSFORMATIONS_REPLACE_UGATE_HPP_ */
9 changes: 8 additions & 1 deletion src/tools/grid_synth.cpp
Original file line number Diff line number Diff line change
@@ -89,8 +89,15 @@ int main(int argc, char** argv) {
random_numbers.seed(rd());

for (const auto& angle : thetas) {
real_t gmp_angle;
try {
gmp_angle = real_t(angle);
} catch (std::invalid_argument& e) {
std::cerr << "Invalid angle provided: " << angle << std::endl;
return EXIT_FAILURE;
}
str_t op_str =
synthesizer.get_op_str(real_t(angle) * gmpf::gmp_pi());
synthesizer.get_op_str(gmp_angle * gmpf::gmp_pi());
for (char c : op_str) {
std::cout << c << ' ';
}
84 changes: 84 additions & 0 deletions src/tools/ionq.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*
* This file is part of staq.
*
* Copyright (c) 2019 - 2023 softwareQ Inc. All rights reserved.
*
* MIT License
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

#include <CLI/CLI.hpp>

#include "mapping/device.hpp"
#include "mapping/layout/basic.hpp"
#include "output/ionq.hpp"
#include "qasmtools/parser/parser.hpp"
#include "transformations/desugar.hpp"
#include "transformations/expression_simplifier.hpp"
#include "transformations/inline.hpp"
#include "transformations/replace_ugate.hpp"

static const std::set<std::string_view> ionq_overrides{
"x", "y", "z", "h", "s", "sdg", "t", "tdg", "rx",
"ry", "rz", "cz", "cy", "swap", "cx", "u1", "ch", "crz"};

int main(int argc, char** argv) {
using namespace staq;
using qasmtools::parser::parse_stdin;

std::string filename = "";

CLI::App app{"QASM to IonQ transpiler"};

app.add_option("-o,--output", filename, "Output to a file");

CLI11_PARSE(app, argc, argv);
auto program = parse_stdin();

if (program) {
transformations::desugar(*program);

// Flatten qregs into one global qreg.
// IonQ Simulator has 29 qubits;
// the other IonQ devices have less than 29 qubits.
// For now let's set a cap of 11 qubits; change this later.
auto device = mapping::fully_connected(11);
auto layout = mapping::compute_basic_layout(device, *program);
mapping::apply_layout(layout, device, *program);

// Inline declared gates
transformations::Inliner::config params{false, ionq_overrides};
transformations::inline_ast(*program, params);

// Evaluate expressions
// TODO: Handle multiples of pi nicely
transformations::expr_simplify(*program, true);

// Replace U gates
transformations::replace_ugates(*program);

if (filename.empty())
output::output_ionq(*program);
else
output::write_ionq(*program, filename);
} else {
std::cerr << "Parsing failed\n";
}
}
41 changes: 41 additions & 0 deletions unit_tests/tests/grid_synth/gmp_functions.cpp
Original file line number Diff line number Diff line change
@@ -109,3 +109,44 @@ TEST(GmpFunctions, round) {

EXPECT_TRUE(gmp_round(x) == mpz_class("4935810934580939"));
}

TEST(GmpFunctions, exp) {
long int prec = 256;
mpf_set_default_prec(prec);
// The -2 is a little hacky, but it is necessary to have the tests pass
// (we are testing absolute error instead of relative error, and the
// largest values are roughly in the range 10^2).
long int tol_exp = std::log10(2) * prec - 2;
mpf_class eps(("1e-" + std::to_string(tol_exp)));
// Expect values are calculated using Wolfram Alpha to 100 digits.
// log(2) * 256 is approx 77, so 100 digits is accurate enough.

std::vector<std::pair<std::string, std::string>> cases{
{"0", "1"},
{"1", "2."
"7182818284590452353602874713526624977572470936999595749"
"66967627724076630353547594571382178525166427"},
{"-1", "0."
"367879441171442321595523770161460867445811131031767834507836801"
"6974614957448998033571472743459196437"},
{"-0.1234567",
"0."
"883859911549690424603734186208757339780798792486720427068041849393"
"9612541057720515407769091940206197"},
{"5.623478", "276."
"8505970916278258711936698732987836757702032228446903804"
"870918696416770256055219817409072316698596"},
/* This case will fail because the absolute error is too large
* (although, the relative error is still bounded by epsilon).
* {"100", "26881171418161354484126255515800135873611118."
* "77374192241519160861528028703490956491415887109721984571"},
*/
{"-100", "0."
"0000000000000000000000000000000000000000000372007597602083596"
"2959695803863118337358892292376781967120613876663290475895815"
"718157118778642281497"}};

for (auto& [x, expect] : cases) {
EXPECT_TRUE(gmp_abs(gmpf::exp(mpf_class(x)) - mpf_class(expect)) < eps);
}
}

0 comments on commit fc01a9a

Please sign in to comment.