diff --git a/include/staq/output/ionq.hpp b/include/staq/output/ionq.hpp index e9f2aac..4560c59 100644 --- a/include/staq/output/ionq.hpp +++ b/include/staq/output/ionq.hpp @@ -44,8 +44,10 @@ 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"}}; +std::unordered_map qasmstd_to_ionq{{"sdg", "si"}, + {"tdg", "ti"}}; +// Note: u1 and rz gates are equivalent up to a global phase. This substitution +// has been left out for now; unsure if global phase matters in IonQ. /** * \class staq::output::IonQOutputter diff --git a/include/staq/transformations/group_qregs.hpp b/include/staq/transformations/group_qregs.hpp new file mode 100644 index 0000000..a96c373 --- /dev/null +++ b/include/staq/transformations/group_qregs.hpp @@ -0,0 +1,151 @@ +/* + * 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/group_qregs.hpp + * \brief Group individual qregs into one global register. + */ + +/** + * The classes in this file transformations::LayoutTransformer and + * transformations::BasicLayout are slightly modified versions of + * mapping::LayoutTransformer and mapping::BasicLayout, as defined in + * mapping/layout/basic.hpp. + */ + +#ifndef TRANSFORMATIONS_GROUP_QREGS_HPP_ +#define TRANSFORMATIONS_GROUP_QREGS_HPP_ + +#include "qasmtools/ast/replacer.hpp" +#include "qasmtools/ast/traversal.hpp" +#include "transformations/substitution.hpp" + +namespace staq { +namespace transformations { + +namespace ast = qasmtools::ast; +namespace parser = qasmtools::parser; + +using layout = std::unordered_map; + +/** + * \class staq::transformations::LayoutTransformer + * \brief Applies a hardware layout to a circuit + * + * Accepts a layout -- that is, a mapping from variable + * accesses to addresses of physical qubits -- and rewrites + * the AST so that all variable accesses refer to the + * relevant address of a global register representing + * the physical qubits + */ +class LayoutTransformer final : public ast::Replacer { + public: + /** + * \class staq::mapping::LayoutTransformer::config + * \brief Holds configuration options + */ + struct config { + std::string register_name = "q"; + }; + + LayoutTransformer() = default; + LayoutTransformer(const config& params) : Replacer(), config_(params) {} + ~LayoutTransformer() = default; + + /** \brief Main transformation method */ + void run(ast::Program& prog, const layout& l) { + // Visit entire program, removing register declarations, then + // add the physical register & apply substitutions + prog.accept(*this); + + // Physical register declaration + prog.body().emplace_front( + std::make_unique(ast::RegisterDecl( + prog.pos(), config_.register_name, true, l.size()))); + + // Substitution + std::unordered_map subst; + for (auto const& [access, idx] : l) { + subst.insert({access, ast::VarAccess(parser::Position(), + config_.register_name, idx)}); + } + transformations::subst_ap_ap(subst, prog); + } + + std::optional>> + replace(ast::RegisterDecl& decl) override { + if (decl.is_quantum()) + return std::list>(); + else + return std::nullopt; + } + + private: + config config_; +}; + +/** + * \class staq::transformations::BasicLayout + * \brief A simple layout generation algorithm + * + * Allocates physical qubits on a first-come, first-serve basis + */ +class BasicLayout final : public ast::Traverse { + public: + BasicLayout() : Traverse() {} + ~BasicLayout() = default; + + /** \brief Main generation method */ + layout generate(ast::Program& prog) { + current_ = layout(); + prog.accept(*this); + return current_; + } + + void visit(ast::RegisterDecl& decl) override { + if (decl.is_quantum()) { + for (auto i = 0; i < decl.size(); i++) { + current_[ast::VarAccess(parser::Position(), decl.id(), i)] = + static_cast(current_.size()); + } + } + } + + private: + layout current_; +}; + +inline void group_qregs(ast::Program& prog) { + BasicLayout gen; + layout l = gen.generate(prog); + LayoutTransformer alg; + alg.run(prog, l); +} + +} // namespace transformations +} /* namespace staq */ + +#endif /* TRANSFORMATIONS_GROUP_QREGS_HPP_ */ diff --git a/include/staq/transformations/qasm_synth.hpp b/include/staq/transformations/qasm_synth.hpp index 524865d..690ada5 100644 --- a/include/staq/transformations/qasm_synth.hpp +++ b/include/staq/transformations/qasm_synth.hpp @@ -81,7 +81,7 @@ class QASMSynthImpl final : public ast::Replacer { std::optional expr_val = theta_arg.constant_eval_gmp(); if (!expr_val) { std::cerr << gate.pos() << ": VarExpr found in classical arg0, " - << "please inline the code.\n"; + << "please inline the code first.\n"; throw qasmtools::parser::ParseError(); } real_t angle = expr_val.value(); diff --git a/include/staq/transformations/replace_ugate.hpp b/include/staq/transformations/replace_ugate.hpp index 2751982..96d426c 100644 --- a/include/staq/transformations/replace_ugate.hpp +++ b/include/staq/transformations/replace_ugate.hpp @@ -25,12 +25,12 @@ */ /** - * \file transformations/replace_ugate.hpp + * \file transformations/replace_ugates.hpp * \brief Replacing common U gates with QE standard gates */ -#ifndef TRANSFORMATIONS_REPLACE_UGATE_HPP_ -#define TRANSFORMATIONS_REPLACE_UGATE_HPP_ +#ifndef TRANSFORMATIONS_REPLACE_UGATES_HPP_ +#define TRANSFORMATIONS_REPLACE_UGATES_HPP_ #include #include @@ -44,7 +44,7 @@ namespace transformations { namespace ast = qasmtools::ast; /** - * \brief Replacing UGates + * \brief Replace UGates * * Visits an AST and replaces common U gates with QE standard * gates if possible. Assumes qelib1.inc is included. @@ -60,6 +60,9 @@ struct UArgs { double lambda; }; +/** + * List of replacements to make, e.g. replace U(pi,0,pi) with x. + */ // clang-format off static const std::vector> standard_gates{ {{pi, 0, pi}, "x"}, @@ -74,10 +77,10 @@ static const std::vector> standard_gates{ // clang-format on /* Implementation */ -class ReplaceUGateImpl final : public ast::Replacer { +class ReplaceUGatesImpl final : public ast::Replacer { public: - ReplaceUGateImpl() = default; - ~ReplaceUGateImpl() = default; + ReplaceUGatesImpl() = default; + ~ReplaceUGatesImpl() = default; void run(ast::ASTNode& node) { node.accept(*this); } @@ -101,11 +104,10 @@ class ReplaceUGateImpl final : public ast::Replacer { 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" + } catch (const std::bad_optional_access& e) { + std::cerr << "error: VarExpr found in UGate args, please inline " + "the code first." << "\n"; - // this should never happen; if this is reached then the Replacer - // is recurring too far down the AST throw; } @@ -127,20 +129,19 @@ class ReplaceUGateImpl final : public ast::Replacer { // 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)); + name = "rz"; // U(0,0,lambda) = rz(lambda) + // assumes rz == u1; ignores the global phase + c_args.emplace_back(std::unique_ptr( + ast::object::clone(gate.lambda()))); } else if (std::abs(phi) < EPS && std::abs(lambda) < EPS) { - name = "ry"; - c_args.emplace_back( - std::make_unique(gate.pos(), theta)); + name = "ry"; // U(theta,0,0) = ry(theta) + c_args.emplace_back(std::unique_ptr( + ast::object::clone(gate.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)); + name = "rx"; // U(theta,-pi/2,pi/2) = rx(theta) + c_args.emplace_back(std::unique_ptr( + ast::object::clone(gate.theta()))); } } @@ -161,11 +162,11 @@ class ReplaceUGateImpl final : public ast::Replacer { }; void replace_ugates(ast::ASTNode& node) { - ReplaceUGateImpl alg; + ReplaceUGatesImpl alg; alg.run(node); } } /* namespace transformations */ } /* namespace staq */ -#endif /* TRANSFORMATIONS_REPLACE_UGATE_HPP_ */ +#endif /* TRANSFORMATIONS_REPLACE_UGATES_HPP_ */ diff --git a/include/staq/transformations/replace_ugates.hpp b/include/staq/transformations/replace_ugates.hpp new file mode 100644 index 0000000..96d426c --- /dev/null +++ b/include/staq/transformations/replace_ugates.hpp @@ -0,0 +1,172 @@ +/* + * This file is part of staq. + * + * Copyright (c) 2019 - 2024 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_ugates.hpp + * \brief Replacing common U gates with QE standard gates + */ + +#ifndef TRANSFORMATIONS_REPLACE_UGATES_HPP_ +#define TRANSFORMATIONS_REPLACE_UGATES_HPP_ + +#include +#include +#include + +#include "qasmtools/ast/replacer.hpp" + +namespace staq { +namespace transformations { + +namespace ast = qasmtools::ast; + +/** + * \brief Replace 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; +}; + +/** + * List of replacements to make, e.g. replace U(pi,0,pi) with x. + */ +// 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 ReplaceUGatesImpl final : public ast::Replacer { + public: + ReplaceUGatesImpl() = default; + ~ReplaceUGatesImpl() = 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 (const std::bad_optional_access& e) { + std::cerr << "error: VarExpr found in UGate args, please inline " + "the code first." + << "\n"; + 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"; // U(0,0,lambda) = rz(lambda) + // assumes rz == u1; ignores the global phase + c_args.emplace_back(std::unique_ptr( + ast::object::clone(gate.lambda()))); + } else if (std::abs(phi) < EPS && std::abs(lambda) < EPS) { + name = "ry"; // U(theta,0,0) = ry(theta) + c_args.emplace_back(std::unique_ptr( + ast::object::clone(gate.theta()))); + } else if (std::abs(phi + pi / 2) < EPS && + std::abs(lambda - pi / 2) < EPS) { + name = "rx"; // U(theta,-pi/2,pi/2) = rx(theta) + c_args.emplace_back(std::unique_ptr( + ast::object::clone(gate.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) { + ReplaceUGatesImpl alg; + alg.run(node); +} + +} /* namespace transformations */ +} /* namespace staq */ + +#endif /* TRANSFORMATIONS_REPLACE_UGATES_HPP_ */ diff --git a/src/tools/group_qregs.cpp b/src/tools/group_qregs.cpp new file mode 100644 index 0000000..a841d2f --- /dev/null +++ b/src/tools/group_qregs.cpp @@ -0,0 +1,41 @@ +/* + * 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 "qasmtools/parser/parser.hpp" +#include "transformations/group_qregs.hpp" + +int main() { + using namespace staq; + using qasmtools::parser::parse_stdin; + + auto program = parse_stdin(); + if (program) { + transformations::group_qregs(*program); + std::cout << *program; + } else { + std::cerr << "Parsing failed\n"; + } +} diff --git a/src/tools/ionq.cpp b/src/tools/ionq.cpp index d21c28c..bd2cb0f 100644 --- a/src/tools/ionq.cpp +++ b/src/tools/ionq.cpp @@ -35,10 +35,11 @@ #include "staq/transformations/expression_simplifier.hpp" #include "staq/transformations/inline.hpp" #include "staq/transformations/replace_ugate.hpp" +#include "staq/transformations/group_qregs.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"}; + "ry", "rz", "cz", "cy", "swap", "cx", "ch", "crz"}; int main(int argc, char** argv) { using namespace staq; @@ -57,12 +58,7 @@ int main(int argc, char** argv) { 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); + transformations::group_qregs(*program); // Inline declared gates transformations::Inliner::config params{false, ionq_overrides};