diff --git a/examples/ionq_test.qasm b/examples/ionq_test.qasm new file mode 100644 index 00000000..be5d2f9b --- /dev/null +++ b/examples/ionq_test.qasm @@ -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]; diff --git a/include/grid_synth/gmp_functions.hpp b/include/grid_synth/gmp_functions.hpp index fb66e98d..7b8d5fbe 100644 --- a/include/grid_synth/gmp_functions.hpp +++ b/include/grid_synth/gmp_functions.hpp @@ -210,6 +210,7 @@ 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); @@ -217,7 +218,7 @@ inline mpf_class sin(const mpf_class& theta) { 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,6 +232,7 @@ 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); @@ -238,7 +240,7 @@ inline mpf_class cos(const mpf_class& theta) { 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; } diff --git a/include/output/cirq.hpp b/include/output/cirq.hpp index 196d4bdd..f5fb63a6 100644 --- a/include/output/cirq.hpp +++ b/include/output/cirq.hpp @@ -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); diff --git a/include/output/ionq.hpp b/include/output/ionq.hpp new file mode 100644 index 00000000..ca09c889 --- /dev/null +++ b/include/output/ionq.hpp @@ -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 +#include +#include + +#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 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_ */ diff --git a/include/output/projectq.hpp b/include/output/projectq.hpp index 5cef919b..fbe8582d 100644 --- a/include/output/projectq.hpp +++ b/include/output/projectq.hpp @@ -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); diff --git a/include/output/qsharp.hpp b/include/output/qsharp.hpp index 93a31836..87d586ef 100644 --- a/include/output/qsharp.hpp +++ b/include/output/qsharp.hpp @@ -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); diff --git a/include/output/quil.hpp b/include/output/quil.hpp index c77fa017..f0af467c 100644 --- a/include/output/quil.hpp +++ b/include/output/quil.hpp @@ -340,7 +340,7 @@ class QuilOutputter final : public ast::Visitor { std::unordered_map> 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); diff --git a/include/transformations/replace_ugate.hpp b/include/transformations/replace_ugate.hpp new file mode 100644 index 00000000..8b3b77b3 --- /dev/null +++ b/include/transformations/replace_ugate.hpp @@ -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 +#include +#include + +#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> 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>> + replace(ast::CNOTGate& gate) override { + std::cerr << "CNOT\n"; + std::vector> c_args; + std::vector q_args{gate.ctrl(), gate.tgt()}; + + std::list> ret; + ret.emplace_back(std::make_unique(ast::DeclaredGate( + gate.pos(), "cx", std::move(c_args), std::move(q_args)))); + return std::move(ret); + } + + std::optional>> + 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> c_args; + std::vector 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(gate.pos(), lambda)); + } else if (std::abs(phi) < EPS && std::abs(lambda) < EPS) { + name = "ry"; + c_args.emplace_back( + std::make_unique(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(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> ret; + ret.emplace_back(std::make_unique(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_ */ diff --git a/src/tools/grid_synth.cpp b/src/tools/grid_synth.cpp index 8424aa99..c6c9903e 100644 --- a/src/tools/grid_synth.cpp +++ b/src/tools/grid_synth.cpp @@ -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 << ' '; } diff --git a/src/tools/ionq.cpp b/src/tools/ionq.cpp new file mode 100644 index 00000000..6d7d364a --- /dev/null +++ b/src/tools/ionq.cpp @@ -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 + +#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 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"; + } +} diff --git a/unit_tests/tests/grid_synth/gmp_functions.cpp b/unit_tests/tests/grid_synth/gmp_functions.cpp index 8901a4ac..4d0004ae 100644 --- a/unit_tests/tests/grid_synth/gmp_functions.cpp +++ b/unit_tests/tests/grid_synth/gmp_functions.cpp @@ -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> 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); + } +}