From 0561d51ebddf34fac5ba42cc372077c30eb1438b Mon Sep 17 00:00:00 2001 From: Anton Borissov Date: Wed, 19 Nov 2025 11:13:08 -0500 Subject: [PATCH 1/3] hubbard resource estimator; tools_v1 gateset; tools_v1 -> qasmtools --- experimental/apps/build_observable.cpp | 212 ++++ experimental/include/circuit_dagger.hpp | 221 ++++ experimental/include/hubbard/builders.hpp | 22 + experimental/include/hubbard/layout.hpp | 40 + experimental/include/hubbard/model_params.hpp | 27 + experimental/include/hubbard/operators.hpp | 101 ++ experimental/include/hubbard/program_io.hpp | 41 + experimental/include/node_conversion.hpp | 158 +++ experimental/include/resource_estimator.hpp | 355 ++++++ experimental/include/square_hubbard_circ.hpp | 35 + .../include/square_hubbard_config.hpp | 144 +++ experimental/include/to_qasm_converter.hpp | 425 +++++++ .../tools_v1/algorithm/Interaction.hpp | 165 +++ .../include/tools_v1/algorithm/LCU.hpp | 366 ++++++ .../tools_v1/algorithm/Multiplication.hpp | 42 + .../include/tools_v1/algorithm/Observable.hpp | 216 ++++ .../include/tools_v1/algorithm/QFT.hpp | 83 ++ .../include/tools_v1/algorithm/QSVT.hpp | 136 +++ .../include/tools_v1/algorithm/Utils.hpp | 41 + experimental/include/tools_v1/ast/ast.hpp | 42 + experimental/include/tools_v1/ast/base.hpp | 111 ++ .../include/tools_v1/ast/cloneable.hpp | 71 ++ .../include/tools_v1/ast/control_gate.hpp | 212 ++++ experimental/include/tools_v1/ast/decl.hpp | 402 +++++++ experimental/include/tools_v1/ast/expr.hpp | 550 +++++++++ .../include/tools_v1/ast/gate_builder.hpp | 68 ++ .../tools_v1/ast/gate_builder_simple.hpp | 269 +++++ experimental/include/tools_v1/ast/program.hpp | 148 +++ .../include/tools_v1/ast/replacer.hpp | 402 +++++++ .../include/tools_v1/ast/semantic.hpp | 504 ++++++++ experimental/include/tools_v1/ast/stmt.hpp | 939 +++++++++++++++ .../include/tools_v1/ast/traversal.hpp | 118 ++ experimental/include/tools_v1/ast/var.hpp | 196 +++ experimental/include/tools_v1/ast/visitor.hpp | 117 ++ experimental/include/tools_v1/main.tex | 776 ++++++++++++ .../include/tools_v1/parser/lexer.hpp | 383 ++++++ .../include/tools_v1/parser/parser.hpp | 1062 +++++++++++++++++ .../include/tools_v1/parser/position.hpp | 121 ++ .../include/tools_v1/parser/preprocessor.hpp | 275 +++++ .../include/tools_v1/parser/token.hpp | 392 ++++++ .../tools_v1/tools/ancilla_management.hpp | 100 ++ .../include/tools_v1/tools/ast_printer.hpp | 219 ++++ .../include/tools_v1/tools/staq_builder.hpp | 286 +++++ experimental/include/tools_v1/utils/angle.hpp | 271 +++++ .../include/tools_v1/utils/templates.hpp | 50 + experimental/src/FastInversion.cpp | 30 + experimental/src/GS.cpp | 320 +++++ experimental/src/Interaction.cpp | 252 ++++ experimental/src/LCU.cpp | 219 ++++ experimental/src/Observable.cpp | 294 +++++ experimental/src/QFT.cpp | 225 ++++ experimental/src/QSVT.cpp | 334 ++++++ experimental/src/TrotterHam1.cpp | 801 +++++++++++++ experimental/src/gate_builder.cpp | 181 +++ experimental/src/hubbard/builders.cpp | 68 ++ experimental/src/hubbard/layout.cpp | 52 + experimental/src/hubbard/model_params.cpp | 18 + experimental/src/hubbard/operators.cpp | 411 +++++++ experimental/src/hubbard/program_io.cpp | 82 ++ experimental/src/main.cpp | 100 ++ experimental/src/test_qasmify.cpp | 13 + src/tools/CMakeLists.txt | 33 +- src/tools/resource_estimator.cpp | 201 +++- 63 files changed, 14531 insertions(+), 17 deletions(-) create mode 100644 experimental/apps/build_observable.cpp create mode 100644 experimental/include/circuit_dagger.hpp create mode 100644 experimental/include/hubbard/builders.hpp create mode 100644 experimental/include/hubbard/layout.hpp create mode 100644 experimental/include/hubbard/model_params.hpp create mode 100644 experimental/include/hubbard/operators.hpp create mode 100644 experimental/include/hubbard/program_io.hpp create mode 100644 experimental/include/node_conversion.hpp create mode 100644 experimental/include/resource_estimator.hpp create mode 100644 experimental/include/square_hubbard_circ.hpp create mode 100644 experimental/include/square_hubbard_config.hpp create mode 100644 experimental/include/to_qasm_converter.hpp create mode 100644 experimental/include/tools_v1/algorithm/Interaction.hpp create mode 100644 experimental/include/tools_v1/algorithm/LCU.hpp create mode 100644 experimental/include/tools_v1/algorithm/Multiplication.hpp create mode 100644 experimental/include/tools_v1/algorithm/Observable.hpp create mode 100644 experimental/include/tools_v1/algorithm/QFT.hpp create mode 100644 experimental/include/tools_v1/algorithm/QSVT.hpp create mode 100644 experimental/include/tools_v1/algorithm/Utils.hpp create mode 100644 experimental/include/tools_v1/ast/ast.hpp create mode 100644 experimental/include/tools_v1/ast/base.hpp create mode 100644 experimental/include/tools_v1/ast/cloneable.hpp create mode 100644 experimental/include/tools_v1/ast/control_gate.hpp create mode 100644 experimental/include/tools_v1/ast/decl.hpp create mode 100644 experimental/include/tools_v1/ast/expr.hpp create mode 100644 experimental/include/tools_v1/ast/gate_builder.hpp create mode 100644 experimental/include/tools_v1/ast/gate_builder_simple.hpp create mode 100644 experimental/include/tools_v1/ast/program.hpp create mode 100644 experimental/include/tools_v1/ast/replacer.hpp create mode 100644 experimental/include/tools_v1/ast/semantic.hpp create mode 100644 experimental/include/tools_v1/ast/stmt.hpp create mode 100644 experimental/include/tools_v1/ast/traversal.hpp create mode 100644 experimental/include/tools_v1/ast/var.hpp create mode 100644 experimental/include/tools_v1/ast/visitor.hpp create mode 100644 experimental/include/tools_v1/main.tex create mode 100644 experimental/include/tools_v1/parser/lexer.hpp create mode 100644 experimental/include/tools_v1/parser/parser.hpp create mode 100644 experimental/include/tools_v1/parser/position.hpp create mode 100644 experimental/include/tools_v1/parser/preprocessor.hpp create mode 100644 experimental/include/tools_v1/parser/token.hpp create mode 100644 experimental/include/tools_v1/tools/ancilla_management.hpp create mode 100644 experimental/include/tools_v1/tools/ast_printer.hpp create mode 100644 experimental/include/tools_v1/tools/staq_builder.hpp create mode 100644 experimental/include/tools_v1/utils/angle.hpp create mode 100644 experimental/include/tools_v1/utils/templates.hpp create mode 100644 experimental/src/FastInversion.cpp create mode 100644 experimental/src/GS.cpp create mode 100644 experimental/src/Interaction.cpp create mode 100644 experimental/src/LCU.cpp create mode 100644 experimental/src/Observable.cpp create mode 100644 experimental/src/QFT.cpp create mode 100644 experimental/src/QSVT.cpp create mode 100644 experimental/src/TrotterHam1.cpp create mode 100644 experimental/src/gate_builder.cpp create mode 100644 experimental/src/hubbard/builders.cpp create mode 100644 experimental/src/hubbard/layout.cpp create mode 100644 experimental/src/hubbard/model_params.cpp create mode 100644 experimental/src/hubbard/operators.cpp create mode 100644 experimental/src/hubbard/program_io.cpp create mode 100644 experimental/src/main.cpp create mode 100644 experimental/src/test_qasmify.cpp diff --git a/experimental/apps/build_observable.cpp b/experimental/apps/build_observable.cpp new file mode 100644 index 00000000..52aeb789 --- /dev/null +++ b/experimental/apps/build_observable.cpp @@ -0,0 +1,212 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using qbit = tools_v1::tools::qbit; + +namespace { + +struct CliOptions { + unsigned ell = 7; + double t = 1.0; + double U = 4.0; + double E0 = 3.0; + double z_real = 3.0; + double z_imag = 4.0; + std::string output_dir = "."; + std::string observable_name = "observable.qasm"; + std::string qasm_name = "qasimfy.qasm"; + bool use_real_space = true; + std::string layout_preset = "square"; + bool show_help = false; + std::optional precision; +}; + +void print_usage(std::string_view prog) { + std::cout << "Usage: " << prog + << " [--ell N] [--t value] [--U value] [--E0 value]\n" + << " [--z-real value] [--z-imag value]\n" + << " [--output-dir path] [--observable-name name]\n" + << " [--qasm-name name]\n" + << " [--mode real|momentum] [--layout square]\n" + << " [--prec value]\n" + << " [--help]\n"; +} + +unsigned deduce_ell_from_L(unsigned L) { + if (L == 0 || (L & (L - 1)) != 0) { + throw std::invalid_argument("L must be a positive power of two"); + } + unsigned ell = 0; + while ((1u << ell) < L) { + ++ell; + } + return ell; +} + +CliOptions parse_cli(int argc, char **argv) { + CliOptions opts; + auto expect_value = [&](int &index, const char *name) -> std::string { + if (index + 1 >= argc) { + throw std::invalid_argument(std::string("Missing value for ") + name); + } + return std::string(argv[++index]); + }; + + for (int i = 1; i < argc; ++i) { + std::string arg = argv[i]; + if (arg == "--help") { + opts.show_help = true; + } else if (arg == "--ell") { + opts.ell = + static_cast(std::stoul(expect_value(i, arg.c_str()))); + } else if (arg == "--L") { + unsigned L = + static_cast(std::stoul(expect_value(i, arg.c_str()))); + opts.ell = deduce_ell_from_L(L); + } else if (arg == "--t") { + opts.t = std::stod(expect_value(i, arg.c_str())); + } else if (arg == "--U") { + opts.U = std::stod(expect_value(i, arg.c_str())); + } else if (arg == "--E0") { + opts.E0 = std::stod(expect_value(i, arg.c_str())); + } else if (arg == "--z-real") { + opts.z_real = std::stod(expect_value(i, arg.c_str())); + } else if (arg == "--z-imag") { + opts.z_imag = std::stod(expect_value(i, arg.c_str())); + } else if (arg == "--output-dir") { + opts.output_dir = expect_value(i, arg.c_str()); + } else if (arg == "--observable-output" || arg == "--observable-name") { + opts.observable_name = expect_value(i, arg.c_str()); + } else if (arg == "--qasm-output" || arg == "--qasm-name") { + opts.qasm_name = expect_value(i, arg.c_str()); + } else if (arg == "--mode") { + auto mode = expect_value(i, arg.c_str()); + if (mode == "real") { + opts.use_real_space = true; + } else if (mode == "momentum") { + opts.use_real_space = false; + } else { + throw std::invalid_argument("Unknown mode: " + mode); + } + } else if (arg == "--layout") { + opts.layout_preset = expect_value(i, arg.c_str()); + } else if (arg == "--prec") { + opts.precision = std::stod(expect_value(i, arg.c_str())); + } else { + throw std::invalid_argument("Unknown argument: " + arg); + } + } + return opts; +} + +} // namespace + +int main(int argc, char **argv) { + CliOptions cli_opts; + try { + cli_opts = parse_cli(argc, argv); + } catch (const std::exception &ex) { + std::cerr << "Error: " << ex.what() << std::endl; + print_usage(argv[0]); + return 1; + } + if (cli_opts.show_help) { + print_usage(argv[0]); + return 0; + } + + if (cli_opts.layout_preset != "square") { + std::cerr << "Unsupported layout preset: " << cli_opts.layout_preset + << std::endl; + print_usage(argv[0]); + return 1; + } + + hubbard::ModelParams params(cli_opts.ell, cli_opts.t, cli_opts.U, cli_opts.E0, + {cli_opts.z_real, cli_opts.z_imag}); + hubbard::Layout layout(params); + const unsigned ell = params.ell; + const double t = params.t; + const int num_fermions = layout.num_data_qubits(); + square_hubbard_config &hubbard_config = layout.config(); + const std::complex z = params.z; + const double E0 = params.E0; + + // Boiler plate + using namespace tools_v1::ast; + tools_v1::parser::Position pos; + std::list> body; + auto qreg = RegisterDecl::create(pos, "q", true, num_fermions); + auto prog = Program::create(pos, true, std::move(body), 0, 0); + + auto data = layout.data_register("q"); + + // Create centralized ancilla memory + tools_v1::tools::ANC_MEM anc_mem; + hubbard::BuildContext build_ctx{pos, data, anc_mem}; + + // clang-format off + tools_v1::tools::circuit xyz; + if (cli_opts.use_real_space) { + auto ziEA_inv_real_for_combo = hubbard::build_ziEA_inverse_real(hubbard_config, t, data, anc_mem, E0, z); + auto iUB_real = hubbard::build_iUB_real(hubbard_config, data, anc_mem); + auto I_plus_real = hubbard::build_I_ziEA_inv_iUB(data, std::move(ziEA_inv_real_for_combo), std::move(iUB_real), anc_mem); + auto AinvB_inv_real = hubbard::build_AinvB_inverse(std::move(I_plus_real), anc_mem); + auto ziEA_inv_real = hubbard::build_ziEA_inverse_real(hubbard_config, t, data, anc_mem, E0, z); + xyz = hubbard::build_observable(2, 3, std::move(AinvB_inv_real), std::move(ziEA_inv_real), build_ctx); + } else { + auto ziEA_inv_for_combo = hubbard::build_ziEA_inverse(hubbard_config, t, data, anc_mem, num_fermions, ell, E0, z); + auto iUB = hubbard::build_iUB(hubbard_config, data, anc_mem, num_fermions); + auto I_plus = hubbard::build_I_ziEA_inv_iUB(data, std::move(ziEA_inv_for_combo), std::move(iUB), anc_mem); + auto AinvB_inv = hubbard::build_AinvB_inverse(std::move(I_plus), anc_mem); + auto ziEA_inv = hubbard::build_ziEA_inverse(hubbard_config, t, data, anc_mem, num_fermions, ell, E0, z); + xyz = hubbard::build_observable(2, 3, std::move(AinvB_inv), std::move(ziEA_inv), build_ctx); + } + // clang-format on + + hubbard::materialize_registers(*prog, anc_mem, "q", num_fermions); + + hubbard::push_circuit(prog, xyz); + + std::filesystem::path out_dir = cli_opts.output_dir; + auto observable_path = (out_dir / cli_opts.observable_name).string(); + auto qasm_path = (out_dir / cli_opts.qasm_name).string(); + + std::cout << "Saving program to " << observable_path << std::endl; + hubbard::save_program(*prog, observable_path); + + std::cout << "QASMifying" << std::endl; + auto qasm_artifacts = hubbard::qasmify_program(*prog); + + std::cout << "Saving program to " << qasm_path << std::endl; + hubbard::save_qasm(qasm_path, qasm_artifacts.code); + + if (qasm_artifacts.program) { + auto resources = + hubbard::estimate_resources(*qasm_artifacts.program, cli_opts.precision); + for (const auto &[str, val] : resources) { + std::cout << str << " :: " << val << std::endl; + } + } + + return 0; +} diff --git a/experimental/include/circuit_dagger.hpp b/experimental/include/circuit_dagger.hpp new file mode 100644 index 00000000..362f9c3c --- /dev/null +++ b/experimental/include/circuit_dagger.hpp @@ -0,0 +1,221 @@ +#ifndef CIRCUIT_DAGGER_HPP_ +#define CIRCUIT_DAGGER_HPP_ + +#include +#include +#include +#include +#include +#include +#include + +namespace tools_v1 { +namespace ast { + +std::list> gate_dagger(Gate &); +std::list> circuit_dagger(const tools::circuit &); + +class CircuitDagger : public Visitor { + ptr new_prog; + ptr neg(Expr &expr) { + return UExpr::create(parser::Position(), UnaryOp::Neg, object::clone(expr)); + }; + +public: + CircuitDagger() { + parser::Position pos; + bool std_include = false; + std::list> body; + new_prog = Program::create(pos, std_include, std::move(body), 0, 0); + } + std::list>& body(){ + return new_prog->body(); + } + void visit(VarAccess &) override { + throw "VarAccess is not supported when performing CircuitDagger operation."; + } + void visit(BExpr &) override { + throw "BExpr is not supported when performing CircuitDagger operation."; + } + void visit(UExpr &) override { + throw "UExpr is not supported when performing CircuitDagger operation."; + } + void visit(PiExpr &) override { + throw "PiExpr is not supported when performing CircuitDagger operation."; + } + void visit(IntExpr &) override { + throw "IntExpr is not supported when performing CircuitDagger operation."; + } + void visit(RealExpr &) override { + throw "RealExpr is not supported when performing CircuitDagger operation."; + } + void visit(VarExpr &) override { + throw "VarExpr is not supported when performing CircuitDagger operation."; + } + void visit(MeasureStmt &) override { + throw "MeasureStmt is not supported when performing CircuitDagger " + "operation."; + } + void visit(ResetStmt &) override { + throw "ResetStmt is not supported when performing CircuitDagger operation."; + } + void visit(IfStmt &) override { + throw "IfStmt is not supported when performing CircuitDagger operation."; + } + void visit(UGate &ug) override { + parser::Position pos; + auto new_ug = + UGate::create(pos, neg(ug.lambda()), neg(ug.phi()), neg(ug.theta()), + std::move(*object::clone(ug.arg()))); + new_prog->body().push_front(std::move(new_ug)); + } + void visit(CNOTGate &g) override { + new_prog->body().push_front(object::clone(g)); + } + void visit(BarrierGate &) override { + throw "BarrierGate not supported when computing Circuit Dagger."; + } + void visit(DeclaredGate &dg) override { + parser::Position pos; + ptr ng; + std::vector qargs; + std::vector hermitian = {"id", "cx", "x", "y", "z", "h"}; + for (auto &x : dg.qargs()) + qargs.emplace_back(std::move(*object::clone(x))); + if (std::find(hermitian.begin(), hermitian.end(), dg.name()) != + hermitian.end()) { + new_prog->body().push_front(object::clone(dg)); + } else if (dg.name() == "ry" || dg.name() == "rz" || dg.name() == "rx") { + auto angle = neg(dg.carg(0)); + std::vector> v; + v.emplace_back(std::move(angle)); + ng = DeclaredGate::create(pos, dg.name(), std::move(v), std::move(qargs)); + new_prog->body().push_front(object::clone(*ng)); + } else if (dg.name() == "s") { + ng = DeclaredGate::create(pos, "sdg", {}, std::move(qargs)); + new_prog->body().push_front(object::clone(*ng)); + } else if (dg.name() == "sdg") { + ng = DeclaredGate::create(pos, "s", {}, std::move(qargs)); + new_prog->body().push_front(object::clone(*ng)); + } else if (dg.name() == "t") { + ng = DeclaredGate::create(pos, "tdg", {}, std::move(qargs)); + new_prog->body().push_front(object::clone(*ng)); + } else if (dg.name() == "tdg") { + ng = DeclaredGate::create(pos, "t", {}, std::move(qargs)); + new_prog->body().push_front(object::clone(*ng)); + } else { + throw "DeclaredGate (" + dg.name() + + ") not supported when computing Circuit Dagger."; + } + } + void visit(PauliString &g) override { + new_prog->body().push_front(object::clone(g)); + } + void visit(PhaseGate &g) override { + parser::Position pos; + std::vector qargs; + for (auto &x : g.qargs()) + qargs.emplace_back(*object::clone(x)); + auto ng = PhaseGate::create(pos, neg(g.angle()), std::move(qargs)); + new_prog->body().push_front(std::move(ng)); + } + void visit(ExpPauli &g) override { + parser::Position pos; + std::vector qargs; + std::vector paulis; + for (auto &x : g.qargs()) + qargs.emplace_back(*object::clone(x)); + for (auto &x : g.paulis()) + paulis.push_back(x); + + auto ng = ExpPauli::create(pos, neg(g.angle()), std::move(qargs), + std::move(paulis)); + new_prog->body().push_front(std::move(ng)); + } + void visit(ControlGate &cg) override { + parser::Position pos; + + auto ntg = gate_dagger(cg.target_gate()); + for (auto &x : ntg) { + ptr g = stmt_to_gate(*x); + VarAccess ctrl = *object::clone(cg.ctrl()); + auto ng = ControlGate::create(pos, std::move(ctrl), std::move(g)); + new_prog->body().push_front(std::move(ng)); + } + } + void visit(MultiControlGate &mcg) override { + parser::Position pos; + std::vector ctrl1, ctrl2; + for (auto &x : mcg.ctrl1()) { + ctrl1.emplace_back(*object::clone(x)); + } + for (auto &x : mcg.ctrl2()) { + ctrl2.emplace_back(*object::clone(x)); + } + auto ntg = gate_dagger(mcg.target_gate()); + for (auto &x : ntg) { + ptr g = stmt_to_gate(*x); + auto ng = MultiControlGate::create(pos, std::move(ctrl1), + std::move(ctrl2), std::move(g)); + new_prog->body().push_front(std::move(ng)); + } + } + // + void visit(GateDecl &) override { + throw "GateDecl is not supported when performing CircuitDagger operation."; + } + void visit(OracleDecl &) override { + throw "OracleDecl is not supported when performing CircuitDagger " + "operation."; + } + void visit(RegisterDecl &) override { + throw "RegisterDecl is not supported when performing CircuitDagger " + "operation."; + } + void visit(AncillaDecl &) override { + throw "AncillaDecl is not supported when performing CircuitDagger " + "operation."; + } + void visit(Program &prog) override { + for (auto it = prog.begin(); it != prog.end(); ++it) { + (*it)->accept(*this); + } + } +}; + +inline std::list> gate_dagger(Gate &g) { + // temporary prog + parser::Position pos; + bool std_include = false; + std::list> body; + body.emplace_back(object::clone(g)); + ptr tmp_prog = + Program::create(pos, std_include, std::move(body), 0, 0); + // prepare CircuitDagger object + CircuitDagger cd; + tmp_prog->accept(cd); + // return the body of the program (to be appended into ) + return std::move(cd.body()); +} + +inline std::list> circuit_dagger(const tools::circuit &c) { + // temporary prog + parser::Position pos; + bool std_include = false; + std::list> body; + // body.emplace_back(object::clone(g)); + for (auto &x : c.body_list()) + body.push_back(std::move(x)); + ptr tmp_prog = + Program::create(pos, std_include, std::move(body), 0, 0); + // prepare CircuitDagger object + CircuitDagger cd; + tmp_prog->accept(cd); + // return the body of the program (to be appended into ) + return std::move(cd.body()); +} + +} // namespace ast +} // namespace tools_v1 + +#endif diff --git a/experimental/include/hubbard/builders.hpp b/experimental/include/hubbard/builders.hpp new file mode 100644 index 00000000..488dcca8 --- /dev/null +++ b/experimental/include/hubbard/builders.hpp @@ -0,0 +1,22 @@ +#ifndef HUBBARD_BUILDERS_HPP_ +#define HUBBARD_BUILDERS_HPP_ + +#include +#include +#include +#include + +namespace hubbard { + +struct BuildContext { + tools_v1::parser::Position &pos; + std::span data; + tools_v1::tools::ANC_MEM &anc_mem; +}; + +tools_v1::tools::circuit build_creation(int idx, BuildContext &ctx); +tools_v1::tools::circuit build_annihilation(int idx, BuildContext &ctx); + +} // namespace hubbard + +#endif // HUBBARD_BUILDERS_HPP_ diff --git a/experimental/include/hubbard/layout.hpp b/experimental/include/hubbard/layout.hpp new file mode 100644 index 00000000..eaa91fb5 --- /dev/null +++ b/experimental/include/hubbard/layout.hpp @@ -0,0 +1,40 @@ +#ifndef HUBBARD_LAYOUT_HPP_ +#define HUBBARD_LAYOUT_HPP_ + +#include +#include +#include +#include +#include +#include + +#include + +namespace hubbard { + +class Layout { +public: + using qbit = tools_v1::tools::qbit; + + explicit Layout(ModelParams params); + + const ModelParams ¶ms() const; + const square_hubbard_config &config() const; + square_hubbard_config &config(); + + int num_data_qubits() const; + std::vector data_register(const std::string &name) const; + + std::pair n_to_nx_ny(int n) const; + int nx_ny_to_n(int nx, int ny) const; + +private: + ModelParams params_; + square_hubbard_config config_; + std::vector> index_to_coord_; + std::unordered_map, int> coord_to_index_; +}; + +} // namespace hubbard + +#endif // HUBBARD_LAYOUT_HPP_ diff --git a/experimental/include/hubbard/model_params.hpp b/experimental/include/hubbard/model_params.hpp new file mode 100644 index 00000000..f2c41d34 --- /dev/null +++ b/experimental/include/hubbard/model_params.hpp @@ -0,0 +1,27 @@ +#ifndef HUBBARD_MODEL_PARAMS_HPP_ +#define HUBBARD_MODEL_PARAMS_HPP_ + +#include + +namespace hubbard { + +struct ModelParams { + unsigned ell = 0; + unsigned L = 0; + double t = 0.0; + double U = 0.0; + double E0 = 0.0; + std::complex z{0.0, 0.0}; + + ModelParams() = default; + ModelParams(unsigned ell_value, double t_value, double U_value, + double E0_value, std::complex z_value); + + static ModelParams real_space_defaults(); + + int num_fermions() const; +}; + +} // namespace hubbard + +#endif // HUBBARD_MODEL_PARAMS_HPP_ diff --git a/experimental/include/hubbard/operators.hpp b/experimental/include/hubbard/operators.hpp new file mode 100644 index 00000000..c8da726a --- /dev/null +++ b/experimental/include/hubbard/operators.hpp @@ -0,0 +1,101 @@ +#ifndef HUBBARD_OPERATORS_HPP_ +#define HUBBARD_OPERATORS_HPP_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace hubbard { + +struct BuildContext; + +tools_v1::tools::circuit build_lcu_A(square_hubbard_config &config, + double t, + std::span data, + tools_v1::tools::ANC_MEM &anc_mem, + int num_fermions, unsigned ell); + +tools_v1::tools::circuit build_lcu_A_real(square_hubbard_config &config, + double t, + std::span data, + tools_v1::tools::ANC_MEM &anc_mem); + +tools_v1::tools::circuit build_B(square_hubbard_config &config, + std::span data, + tools_v1::tools::ANC_MEM &anc_mem, + int num_fermions); + +tools_v1::tools::circuit build_B_real(square_hubbard_config &config, + std::span data, + tools_v1::tools::ANC_MEM &anc_mem); + +tools_v1::tools::circuit build_ziEA(square_hubbard_config &config, + double t, + std::span data, + tools_v1::tools::ANC_MEM &anc_mem, + int num_fermions, unsigned ell, + double E0, std::complex z); + +tools_v1::tools::circuit build_ziEA_real(square_hubbard_config &config, + double t, + std::span data, + tools_v1::tools::ANC_MEM &anc_mem, + double E0, std::complex z); + +tools_v1::tools::circuit build_ziEA_inverse(square_hubbard_config &config, + double t, + std::span data, + tools_v1::tools::ANC_MEM &anc_mem, + int num_fermions, unsigned ell, + double E0, std::complex z); + +tools_v1::tools::circuit build_ziEA_inverse_real( + square_hubbard_config &config, double t, + std::span data, + tools_v1::tools::ANC_MEM &anc_mem, double E0, + std::complex z); + +tools_v1::tools::circuit build_iUB(square_hubbard_config &config, + std::span data, + tools_v1::tools::ANC_MEM &anc_mem, + int num_fermions); + +tools_v1::tools::circuit build_iUB_real(square_hubbard_config &config, + std::span data, + tools_v1::tools::ANC_MEM &anc_mem); + +tools_v1::tools::circuit build_I_ziEA_inv_iUB( + std::span data, + tools_v1::tools::circuit ziEA_inv, + tools_v1::tools::circuit iUB, + tools_v1::tools::ANC_MEM &anc_mem); + +tools_v1::tools::circuit build_AinvB_inverse( + tools_v1::tools::circuit input, + tools_v1::tools::ANC_MEM &anc_mem); + +tools_v1::tools::circuit build_observable( + int creation_index, int annihilation_index, + tools_v1::tools::circuit AinvB_inv, + tools_v1::tools::circuit ziEA_inv, + BuildContext &ctx); + +tools_v1::tools::circuit build_lcu_two_unitaries( + const std::complex &c0, const std::complex &c1, + tools_v1::tools::circuit id_circuit, + tools_v1::tools::circuit target_circuit, + tools_v1::tools::ANC_MEM &anc_mem, + const std::string &ancilla_label); + +tools_v1::tools::circuit combine_circuits( + tools_v1::tools::circuit lhs, tools_v1::tools::circuit rhs); + +} // namespace hubbard + +#endif // HUBBARD_OPERATORS_HPP_ diff --git a/experimental/include/hubbard/program_io.hpp b/experimental/include/hubbard/program_io.hpp new file mode 100644 index 00000000..87471a47 --- /dev/null +++ b/experimental/include/hubbard/program_io.hpp @@ -0,0 +1,41 @@ +#ifndef HUBBARD_PROGRAM_IO_HPP_ +#define HUBBARD_PROGRAM_IO_HPP_ + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace hubbard { + +struct QasmArtifacts { + std::string code; + tools_v1::ast::ptr program; +}; + +void push_gate_builder(tools_v1::ast::ptr &prog, + tools_v1::ast::GateBuilder &gb); +void push_circuit(tools_v1::ast::ptr &prog, + tools_v1::tools::circuit &circuit); + +void materialize_registers(tools_v1::ast::Program &prog, + const tools_v1::tools::ANC_MEM &anc_mem, + const std::string &data_reg_name, + int num_data_qubits); + +void save_program(const tools_v1::ast::Program &prog, + const std::string &path); +void save_qasm(const std::string &path, std::string_view contents); + +QasmArtifacts qasmify_program(tools_v1::ast::Program &prog); +std::map estimate_resources( + const tools_v1::ast::Program &prog, + std::optional rotation_precision = std::nullopt); + +} // namespace hubbard + +#endif // HUBBARD_PROGRAM_IO_HPP_ diff --git a/experimental/include/node_conversion.hpp b/experimental/include/node_conversion.hpp new file mode 100644 index 00000000..3701081a --- /dev/null +++ b/experimental/include/node_conversion.hpp @@ -0,0 +1,158 @@ +#ifndef NODE_CONVERSION_HPP_ +#define NODE_CONVERSION_HPP_ + +#include +#include +#include +#include + +// Convert Stmt to Gate +struct GateConverter : public tools_v1::ast::Visitor { + tools_v1::ast::ptr converted_gate; + void visit(tools_v1::ast::DeclaredGate &gate) override { + converted_gate = tools_v1::ast::object::clone(gate); + } + void visit(tools_v1::ast::PauliString &gate) override { + converted_gate = tools_v1::ast::object::clone(gate); + } + void visit(tools_v1::ast::VarAccess &) override {} + void visit(tools_v1::ast::BExpr &) override {} + void visit(tools_v1::ast::UExpr &) override {} + void visit(tools_v1::ast::PiExpr &) override {} + void visit(tools_v1::ast::IntExpr &) override {} + void visit(tools_v1::ast::RealExpr &) override {} + void visit(tools_v1::ast::VarExpr &) override {} + void visit(tools_v1::ast::MeasureStmt &) override {} + void visit(tools_v1::ast::ResetStmt &) override {} + void visit(tools_v1::ast::IfStmt &) override {} + void visit(tools_v1::ast::UGate &) override {} + void visit(tools_v1::ast::CNOTGate &) override {} + void visit(tools_v1::ast::BarrierGate &) override {} + void visit(tools_v1::ast::PhaseGate &) override {} + void visit(tools_v1::ast::ExpPauli &) override {} + void visit(tools_v1::ast::ControlGate &) override {} + void visit(tools_v1::ast::MultiControlGate &gate) override { + converted_gate = tools_v1::ast::object::clone(gate); + } + void visit(tools_v1::ast::GateDecl &) override {} + void visit(tools_v1::ast::OracleDecl &) override {} + void visit(tools_v1::ast::RegisterDecl &) override {} + void visit(tools_v1::ast::AncillaDecl &) override {} + void visit(tools_v1::ast::Program &) override {} +}; + +inline tools_v1::ast::ptr stmt_to_gate(tools_v1::ast::Stmt &st){ + GateConverter gc; + st.accept(gc); + return std::move(gc.converted_gate); +} + +struct GateCloner : public tools_v1::ast::Visitor { + tools_v1::ast::ptr cloned_gate; + void visit(tools_v1::ast::DeclaredGate &gate) override { + cloned_gate = tools_v1::ast::object::clone(gate); + } + void visit(tools_v1::ast::PauliString &gate) override { + cloned_gate = tools_v1::ast::object::clone(gate); + } + void visit(tools_v1::ast::VarAccess &) override {} + void visit(tools_v1::ast::BExpr &) override {} + void visit(tools_v1::ast::UExpr &) override {} + void visit(tools_v1::ast::PiExpr &) override {} + void visit(tools_v1::ast::IntExpr &) override {} + void visit(tools_v1::ast::RealExpr &) override {} + void visit(tools_v1::ast::VarExpr &) override {} + void visit(tools_v1::ast::MeasureStmt &) override {} + void visit(tools_v1::ast::ResetStmt &) override {} + void visit(tools_v1::ast::IfStmt &) override {} + void visit(tools_v1::ast::UGate &) override {} + void visit(tools_v1::ast::CNOTGate &gate) override { + cloned_gate = tools_v1::ast::object::clone(gate); + } + void visit(tools_v1::ast::BarrierGate &) override {} + void visit(tools_v1::ast::PhaseGate &) override {} + void visit(tools_v1::ast::ExpPauli &) override {} + void visit(tools_v1::ast::ControlGate &) override {} + void visit(tools_v1::ast::MultiControlGate &) override {} + void visit(tools_v1::ast::GateDecl &) override {} + void visit(tools_v1::ast::OracleDecl &) override {} + void visit(tools_v1::ast::RegisterDecl &) override {} + void visit(tools_v1::ast::AncillaDecl &) override {} + void visit(tools_v1::ast::Program &) override {} +}; + +struct GateToStmt : public tools_v1::ast::Visitor { + tools_v1::ast::ptr cloned_gate; + + void visit(tools_v1::ast::DeclaredGate &gate) override { + std::vector> c_args; + for (int i = 0; i < gate.num_cargs(); ++i) { + c_args.push_back(tools_v1::ast::object::clone(gate.carg(i))); + } + std::vector q_args; + for (int i = 0; i < gate.num_qargs(); ++i) { + q_args.push_back(gate.qarg(i)); + } + cloned_gate = tools_v1::ast::DeclaredGate::create( + gate.pos(), gate.name(), std::move(c_args), std::move(q_args)); + } + + void visit(tools_v1::ast::PauliString &gate) override { + std::vector qubits; + for (const auto &qubit : gate.qargs()) { + qubits.push_back(qubit); + } + std::vector paulis; + for (int i = 0; i < gate.num_qargs(); ++i) { + paulis.push_back(tools_v1::ast::PauliType::X); + } + cloned_gate = tools_v1::ast::PauliString::create(gate.pos(), std::move(qubits), + std::move(paulis)); + } + + void visit(tools_v1::ast::MultiControlGate &gate) override { + std::vector ctrl1; + for (const auto &qubit : gate.ctrl1()) { + ctrl1.push_back(qubit); + } + std::vector ctrl2; + for (const auto &qubit : gate.ctrl2()) { + ctrl2.push_back(qubit); + } + GateToStmt target_cloner; + gate.target_gate().accept(target_cloner); + tools_v1::ast::ptr target_gate( + static_cast(target_cloner.cloned_gate.release())); + cloned_gate = tools_v1::ast::MultiControlGate::create(gate.pos(), std::move(ctrl1), + std::move(ctrl2), + std::move(target_gate)); + } + + void visit(tools_v1::ast::VarAccess &) override {} + void visit(tools_v1::ast::BExpr &) override {} + void visit(tools_v1::ast::UExpr &) override {} + void visit(tools_v1::ast::PiExpr &) override {} + void visit(tools_v1::ast::IntExpr &) override {} + void visit(tools_v1::ast::RealExpr &) override {} + void visit(tools_v1::ast::VarExpr &) override {} + void visit(tools_v1::ast::MeasureStmt &) override {} + void visit(tools_v1::ast::ResetStmt &) override {} + void visit(tools_v1::ast::IfStmt &) override {} + void visit(tools_v1::ast::UGate &) override {} + void visit(tools_v1::ast::CNOTGate &gate) override { + cloned_gate = tools_v1::ast::object::clone(gate); + } + void visit(tools_v1::ast::BarrierGate &) override {} + void visit(tools_v1::ast::PhaseGate &) override {} + void visit(tools_v1::ast::ExpPauli &) override {} + void visit(tools_v1::ast::ControlGate &gate) override { + cloned_gate = tools_v1::ast::object::clone(gate); + } + void visit(tools_v1::ast::GateDecl &) override {} + void visit(tools_v1::ast::OracleDecl &) override {} + void visit(tools_v1::ast::RegisterDecl &) override {} + void visit(tools_v1::ast::AncillaDecl &) override {} + void visit(tools_v1::ast::Program &) override {} +}; + +#endif diff --git a/experimental/include/resource_estimator.hpp b/experimental/include/resource_estimator.hpp new file mode 100644 index 00000000..53b67565 --- /dev/null +++ b/experimental/include/resource_estimator.hpp @@ -0,0 +1,355 @@ +/** + * \file resource_estimator.hpp + * \brief Resource estimation + */ + +#ifndef RESOURCE_ESTIMATOR_HPP_ +#define RESOURCE_ESTIMATOR_HPP_ + +#include +#include +#include +#include +#include + +namespace tools_v1 { +namespace tools { + +namespace ast = tools_v1::ast; + +using resource_count = std::unordered_map; + +void add_counts(resource_count &A, const resource_count &B) { + for (auto &[gate, num] : B) { + A[gate] += num; + } +} + +class ResourceEstimator final : public ast::Visitor { +public: + struct config { + bool unbox = true; + bool merge_dagger = true; + std::set overrides = ast::qelib_defs; + std::optional rotation_precision; + }; + + ResourceEstimator() = default; + ResourceEstimator(const config ¶ms) : Visitor(), config_(params) {} + ~ResourceEstimator() = default; + + resource_count run(ast::ASTNode &node) { + reset(); + + node.accept(*this); + + // Unboxing the running estimate + auto &[counts, depths] = running_estimate_; + + // Get maximum critical path length + int depth = 0; + for (auto &[id, length] : depths) { + if (length > depth) { + depth = length; + } + } + + // Set depth and add any improved T counts + counts["depth"] = depth; + finalize_t_counts(counts); + return counts; + } + + /* Variables */ + void visit(ast::VarAccess &) {} + + /* Expressions */ + void visit(ast::BExpr &) {} + void visit(ast::UExpr &) {} + void visit(ast::PiExpr &) {} + void visit(ast::IntExpr &) {} + void visit(ast::RealExpr &) {} + void visit(ast::VarExpr &) {} + + void visit(ast::PauliString&) { + throw "not supported"; + } + void visit(ast::PhaseGate&) { + throw "not supported"; + } + void visit(ast::ExpPauli&) { + throw "not supported"; + } + void visit(ast::ControlGate&) { + throw "not supported"; + } + void visit(ast::MultiControlGate&) { + throw "not supported"; + } + + /* Statements */ + void visit(ast::MeasureStmt &stmt) { + auto &[counts, depths] = running_estimate_; + + // Gate count + counts["measurement"] += 1; + + // Depth + int in_depth = std::max(depths[stmt.c_arg()], depths[stmt.q_arg()]); + depths[stmt.c_arg()] = in_depth + 1; + depths[stmt.q_arg()] = in_depth + 1; + } + void visit(ast::ResetStmt &stmt) { + auto &[counts, depths] = running_estimate_; + + // Gate count + counts["reset"] += 1; + + // Depth + depths[stmt.arg()] += 1; + } + void visit(ast::IfStmt &stmt) { stmt.then().accept(*this); } + + /* Gates */ + void visit(ast::UGate &gate) { + auto &[counts, depths] = running_estimate_; + + // Gate count + std::stringstream ss; + auto theta = gate.theta().constant_eval(); + auto phi = gate.phi().constant_eval(); + auto lambda = gate.lambda().constant_eval(); + + if (theta && phi && lambda) { + ss << "U(" << *theta << "," << *phi << "," << *lambda << ")"; + } else { + ss << "U"; + } + + counts[ss.str()] += 1; + + // Depth + depths[gate.arg()] += 1; + } + void visit(ast::CNOTGate &gate) { + auto &[counts, depths] = running_estimate_; + + // Gate count + counts["CX"] += 1; + + // Depth + int in_depth = std::max(depths[gate.ctrl()], depths[gate.tgt()]); + depths[gate.ctrl()] = in_depth + 1; + depths[gate.tgt()] = in_depth + 1; + } + void visit(ast::BarrierGate &gate) { + auto &[counts, depths] = running_estimate_; + + // Gate count + counts["barrier"] += 1; + + // Depth + int in_depth = -1; + gate.foreach_arg([&in_depth, this](auto &arg) { + in_depth = std::max(in_depth, running_estimate_.second[arg]); + }); + gate.foreach_arg([in_depth, this](auto &arg) { + running_estimate_.second[arg] = in_depth + 1; + }); + } + void visit(ast::DeclaredGate &gate) { + auto &[counts, depths] = running_estimate_; + + // Gate prefix, appropriately stripped of daggers + auto tmp = gate.name(); + if (config_.merge_dagger) { + strip_dagger(tmp); + } + + // Gate sufix. Only included if the parameters are constants + std::stringstream ss; + bool all_constant = true; + if (gate.num_cargs() > 0) { + bool flag = true; + ss << "("; + gate.foreach_carg([&ss, &flag, &all_constant](auto &arg) { + auto val = arg.constant_eval(); + + // Correct commas + if (flag) { + flag = false; + } else { + ss << ","; + } + + if (val) { + ss << *val; + } else { + all_constant = false; + } + }); + ss << ")"; + } + + // Gate name + std::string name; + if (all_constant) { + name = tmp + ss.str(); + } else { + name = tmp; + } + + track_rotation_gate(name); + + // Get the longest critical path into the gate + int in_depth = -1; + gate.foreach_qarg([&in_depth, this](auto &arg) { + in_depth = std::max(in_depth, running_estimate_.second[arg]); + }); + + // Get the pre-computed resource counts + auto &[gate_counts, depth_counts] = resource_map_[name]; + + if (config_.unbox && + (config_.overrides.find(name) == config_.overrides.end()) && + (gate.num_cargs() == 0)) { + add_counts(counts, gate_counts); + + // Note that this gives the depth as if there were a barrier on + // all involved gates before and after the sub-circuit. Not + // super ideal + int gate_depth = gate_counts["depth"]; + gate.foreach_qarg([in_depth, this, gate_depth](auto &arg) { + running_estimate_.second[arg] = in_depth + gate_depth; + }); + } else { + counts[name] += 1; + + gate.foreach_qarg([in_depth, this](auto &arg) { + running_estimate_.second[arg] = in_depth + 1; + }); + } + } + + /* Declarations */ + void visit(ast::GateDecl &decl) { + // Initialize a new resource count + auto &local_state = resource_map_[decl.id()]; + std::swap(running_estimate_, local_state); + + decl.foreach_stmt([this](auto &gate) { gate.accept(*this); }); + + // Get maximum critical path length + auto &[counts, depths] = running_estimate_; + int depth = 0; + for (auto &[id, length] : depths) { + if (length > depth) { + depth = length; + } + } + + // Set depth and return + counts["depth"] = depth; + + std::swap(running_estimate_, local_state); + } + void visit(ast::OracleDecl &) {} + void visit(ast::RegisterDecl &decl) { + auto &[counts, depths] = running_estimate_; + + if (decl.is_quantum()) { + counts["qubits"] += decl.size(); + } else { + counts["cbits"] += decl.size(); + } + } + void visit(ast::AncillaDecl &decl) { + auto &[counts, depths] = running_estimate_; + + if (!decl.is_dirty()) { + counts["ancillas"] += decl.size(); + } + } + + /* Program */ + void visit(ast::Program &prog) { + prog.foreach_stmt([this](auto &stmt) { stmt.accept(*this); }); + } + +private: + using depth_count = std::unordered_map; + using resource_state = std::pair; + + config config_; + std::unordered_map resource_map_; + + resource_state running_estimate_; + + void reset() { + resource_map_.clear(); + running_estimate_.first.clear(); + running_estimate_.second.clear(); + rotation_gate_cost_ = + config_.rotation_precision + ? predict_t_count(std::max(0.0, *config_.rotation_precision)) + : 0.0; + } + + void strip_dagger(std::string &str) { + auto len = str.size(); + + if (len > 2 && str[len - 2] == 'd' && str[len - 1] == 'g') { + str.resize(len - 2); + } + } + + static double predict_t_count(double precision) { + const double SLOPE = 10.03; + const double INTERCEPT = 0.21; + return std::ceil((SLOPE * precision) + INTERCEPT); + } + + void track_rotation_gate(const std::string &name) { + if (!config_.rotation_precision || rotation_gate_cost_ <= 0.0) { + return; + } + auto base = base_gate_name(name); + if (base == "rx" || base == "ry" || base == "rz") { + running_estimate_.first["t_rotations"] += rotation_gate_cost_; + } + } + + std::string_view base_gate_name(const std::string &name) const { + std::string_view view{name}; + auto pos = view.find('('); + if (pos != std::string_view::npos) { + view = view.substr(0, pos); + } + return view; + } + + void finalize_t_counts(resource_count &counts) { + if (!config_.rotation_precision) { + return; + } + counts["t_improved"] = counts["t"] + counts["t_rotations"]; + } + + double rotation_gate_cost_ = 0.0; +}; + +resource_count estimate_resources(ast::ASTNode &node) { + ResourceEstimator estimator; + return estimator.run(node); +} + +resource_count estimate_resources(ast::ASTNode &node, + const ResourceEstimator::config ¶ms) { + ResourceEstimator estimator(params); + return estimator.run(node); +} + +} /* namespace tools */ +} // namespace tools_v1 + +#endif /* TOOLS_RESOURCE_ESTIMATOR_HPP_ */ diff --git a/experimental/include/square_hubbard_circ.hpp b/experimental/include/square_hubbard_circ.hpp new file mode 100644 index 00000000..e7e8bb3f --- /dev/null +++ b/experimental/include/square_hubbard_circ.hpp @@ -0,0 +1,35 @@ +#ifndef SQUARE_HUBBARD_CIRC_HPP_ +#define SQUARE_HUBBARD_CIRC_HPP_ + +#include +#include +#include +#include +#include +#include + +// using qbit = tools_v1::tools::qbit; +// using circuit = tools_v1::tools::circuit; + +class square_hubbard_circ { +private: + std::vector _data_qubits; + square_hubbard_config _config; + +public: + square_hubbard_circ(int n, unsigned int L, double t, double U) + : _data_qubits(n), _config(L, t, U) {} + + tools_v1::tools::circuit creation_op(int i); + tools_v1::tools::circuit annihilation_op(int i); + + tools_v1::tools::circuit kinetic_op(); + tools_v1::tools::circuit interaction_op(); + std::pair n_to_nx_ny(int n); + int nx_ny_to_n(int nx, int ny); + + std::vector generate_kinetic_coefficients(); + std::vector generate_kinetic_unitaries(); +}; + +#endif diff --git a/experimental/include/square_hubbard_config.hpp b/experimental/include/square_hubbard_config.hpp new file mode 100644 index 00000000..a7abe61b --- /dev/null +++ b/experimental/include/square_hubbard_config.hpp @@ -0,0 +1,144 @@ +#ifndef SQUARE_HUBBARD_CONFIG_HPP_ +#define SQUARE_HUBBARD_CONFIG_HPP_ + +#include +#include +#include +#include +#include + +// Hash function for std::pair +namespace std { +template <> struct hash> { + size_t operator()(const std::pair &p) const { + return hash()(p.first) ^ (hash()(p.second) << 1); + } +}; +} // namespace std + +struct Ferm_Occ_Idx { + int nx,ny; + unsigned int sg; +}; + +class square_hubbard_config { +private: + unsigned int _L = 10; // in units of a + double _t = 1.0; + double _U = 0.0; + int _Lmin; + int _Lmax; + std::unordered_map, int> _encoding; + std::vector>> _decoding; + +public: + square_hubbard_config(unsigned int L, double t, double U) + : _L(L), _t(t), _U(U) { + _decoding.reserve(L * L); + _Lmax = (static_cast(L)/2); + _Lmin = (-static_cast(L+1) / 2 + 1); + assert( _Lmax-_Lmin+1 == _L ); + for (int nx = -static_cast(L+1) / 2 + 1; nx <= static_cast(L) / 2; ++nx) { + for (int ny = -static_cast(L+1) / 2 + 1; ny <= static_cast(L) / 2; ++ny) { + int enc = encoding_formula(nx, ny); + _encoding[std::make_pair(nx, ny)] = enc; + } + } + for (auto &[k, v] : _encoding) { + _decoding.emplace_back(v, k); + } + std::sort(_decoding.begin(), _decoding.end()); + } + + int brillouin_zone_normalize(int coord){ + while (coord > _Lmax){ + coord -= _L; + } + while (coord < _Lmin){ + coord += _L; + } + assert( _Lmin <= coord && coord <= _Lmax ); + return coord; + } + + const int Lmin(){ return _Lmin; } + const int Lmax(){ return _Lmax; } + + Ferm_Occ_Idx occupation_index(const int& i){ + int mu = i/2; + int sg = i%2; + if(sg < 0) + sg += 2; + + return Ferm_Occ_Idx( + _decoding[mu].second.first, + _decoding[mu].second.second, + sg + ); + } + + int index_from_occupation(Ferm_Occ_Idx& f){ + assert(f.sg == 0 || f.sg == 1); + for(int i = 0; i < _decoding.size(); ++i){ + if(_decoding[i].second.first == f.nx && _decoding[i].second.second == f.ny){ + return 2*i+f.sg; + } + } + std::stringstream ss; + ss << "qubit index not found for (nx,ny) = (" << f.nx << "," << f.ny << ") with L = " << _L; + throw ss.str(); + } + + unsigned int L() { return _L; } + double t() { return _t; } + double U() { return _U; } + + + const std::vector>>& decoding_vector() const { + return _decoding; + } + + const std::pair> fermion_vals(int& i){ + return _decoding[i]; + } + + const int encode(const int& nx, const int& ny){ + return _encoding[std::make_pair(nx,ny)]; + } + + double e_bare(int nx, int ny) { + return -2.0 * _t * std::cos(2.0 * std::numbers::pi * nx / double(_L)) - + 2.0 * _t * std::cos(2.0 * std::numbers::pi * ny / double(_L)); + } + + unsigned int encoding_formula(int nx, int ny) { + // Hybrid formula to handle y=0 case correctly + // For y != 0: P(x,y) = 2x² + 4sgn(x)sgn(y)xy + 2y² - 2H(x)sgn(y)x - y + 1 + // For y = 0: P(x,0) = 2x² - 2H(x)x + 1 + if (nx == 0 && ny == 0) + return 0; + + // Calculate sign functions + int sgn_x = (nx > 0) ? 1 : ((nx < 0) ? -1 : 0); + int sgn_y = (ny > 0) ? 1 : ((ny < 0) ? -1 : 0); + + // Calculate Heaviside function H(x) + int H_x = (nx > 0) ? 1 : 0; + + if (ny == 0) { + // Special case for y=0 + return 2 * nx * nx + 1 - 2 * H_x * nx; + } else { + // General case + return 2 * nx * nx + 1 - 2 * H_x * sgn_y * nx + + 4 * sgn_x * sgn_y * nx * ny + 2 * ny * ny - ny; + } + } + + +}; + + + + +#endif diff --git a/experimental/include/to_qasm_converter.hpp b/experimental/include/to_qasm_converter.hpp new file mode 100644 index 00000000..ab6dc70b --- /dev/null +++ b/experimental/include/to_qasm_converter.hpp @@ -0,0 +1,425 @@ +#ifndef TO_QASM_CONVERTER_HPP_ +#define TO_QASM_CONVERTER_HPP_ + +#include +#include +#include +#include +#include + +namespace tools_v1 { +namespace ast { + +class QASMify : public Visitor { +private: + int cnt_mcg = 0; + int max_a_tof = 0; + ptr new_prog; + +public: + QASMify() { + tools_v1::parser::Position pos; + bool std_include = true; + int num_cbits = 0; + int num_qbits = 0; + new_prog = Program::create(pos, std_include, {}, 0, 0); + } + // Variables + void visit(VarAccess &) override {} + // Expressions + void visit(BExpr &) override {} + void visit(UExpr &) override {} + void visit(PiExpr &) override {} + void visit(IntExpr &) override {} + void visit(RealExpr &) override {} + void visit(VarExpr &) override {} + // Statements + void visit(MeasureStmt &ms) override { + new_prog->body().push_back(tools_v1::ast::object::clone(ms)); + } + void visit(ResetStmt &rs) override { + new_prog->body().push_back(tools_v1::ast::object::clone(rs)); + } + void visit(IfStmt &is) override { + new_prog->body().push_back(tools_v1::ast::object::clone(is)); + } + // Gates + void visit(UGate &ug) override { + new_prog->body().push_back(tools_v1::ast::object::clone(ug)); + } + void visit(CNOTGate &cg) override { + new_prog->body().push_back(tools_v1::ast::object::clone(cg)); + } + void visit(BarrierGate &bg) override { + new_prog->body().push_back(tools_v1::ast::object::clone(bg)); + } + void visit(DeclaredGate &dg) override { + new_prog->body().push_back(tools_v1::ast::object::clone(dg)); + } + + // New Gates + void visit(PauliString &ps) override { + auto f = [this](VarAccess &va, PauliType pt) { + tools_v1::parser::Position pos; + ptr dg; + switch (pt) { + case PauliType::I: + break; + case PauliType::X: + dg = DeclaredGate::create(pos, "x", {}, {std::move(va)}); + case PauliType::Y: + dg = DeclaredGate::create(pos, "y", {}, {std::move(va)}); + case PauliType::Z: + dg = DeclaredGate::create(pos, "z", {}, {std::move(va)}); + } + this->new_prog->body().push_back(tools_v1::ast::object::clone(*dg)); + }; + ps.foreach_pauli(f); + } + + void visit(PhaseGate &phg) override { + // This is a r_z rotation + ptr phg_angle = tools_v1::ast::object::clone(phg.angle()); + std::vector phg_qargs = phg.qargs(); + + tools_v1::parser::Position pos; + std::vector> phg_cargs; + phg_cargs.emplace_back(std::move(phg_angle)); + + ptr dg = DeclaredGate::create(pos, "rz", std::move(phg_cargs), + std::move(phg_qargs)); + + this->new_prog->body().push_back(tools_v1::ast::object::clone(*dg)); + } + + void visit(ExpPauli &ep) override { + ptr ep_angle = tools_v1::ast::object::clone(ep.angle()); + std::vector ep_qargs = ep.qargs(); + std::vector ep_paulis = ep.paulis(); + // helper: diagonalizes paulis translating them into z + auto z_pauli_translator = [this](VarAccess va, PauliType pt, + bool dag = false) -> void { + tools_v1::parser::Position pos; + assert(tools_v1::ast::PauliType::I != pt); + ptr dg_full; + switch (pt) { + case PauliType::I: + throw "pauli conversion from I to Z failed"; + break; + case PauliType::Z: + dg_full = + tools_v1::ast::DeclaredGate::create(pos, "I", {}, {std::move(va)}); + case PauliType::X: + dg_full = + tools_v1::ast::DeclaredGate::create(pos, "h", {}, {std::move(va)}); + case PauliType::Y: + ptr dg0; + ptr dg; + // TODO: check the correct order + dg0 = (!dag ? tools_v1::ast::DeclaredGate::create(pos, "h", {}, + {std::move(va)}) + : tools_v1::ast::DeclaredGate::create(pos, "sdag", {}, + {std::move(va)})); + dg = (!dag ? tools_v1::ast::DeclaredGate::create(pos, "s", {}, + {std::move(va)}) + : tools_v1::ast::DeclaredGate::create(pos, "h", {}, + {std::move(va)})); + std::vector> dg_full; + dg_full.emplace_back(tools_v1::ast::object::clone(*dg0)); + dg_full.emplace_back(tools_v1::ast::object::clone(*dg)); + }; + this->new_prog->body().push_back(tools_v1::ast::object::clone(*dg_full)); + }; + // UGLY: copied function from MC gate + auto create_cnot = [this](VarAccess c, VarAccess t) { + tools_v1::parser::Position pos; + auto cnot = CNOTGate::create(pos, std::move(c), std::move(t)); + this->new_prog->body().push_back(tools_v1::ast::object::clone(*cnot)); + }; + + tools_v1::parser::Position pos; + ptr double_angle = tools_v1::ast::BExpr::create( + pos, tools_v1::ast::RealExpr::create(pos, 2.0), + tools_v1::ast::BinaryOp::Times, + tools_v1::ast::object::clone(*ep_angle)); + if (ep_paulis.size() >= 1) { + // case of non-trivial pauli string + assert(ep_qargs.size() == ep_paulis.size()); + int L = ep_paulis.size(); + // U operation + z_pauli_translator(ep_qargs[0], ep_paulis[0], false); + for (int i = 1; i < L; ++i) { + z_pauli_translator(ep_qargs[i - 1], ep_paulis[i - 1], false); + create_cnot(ep_qargs[i - 1], ep_qargs[i]); + } + std::vector> vec_ptr_expr; + vec_ptr_expr.push_back(std::move(double_angle)); + // rotation + this->new_prog->body().push_back(tools_v1::ast::DeclaredGate::create( + pos, "rz", std::move(vec_ptr_expr), {std::move(ep_qargs[L - 1])})); + // undo U operation: U^dag + for (int i = L - 1; i > 0; --i) { + create_cnot(ep_qargs[i - 1], ep_qargs[i]); + z_pauli_translator(ep_qargs[i], ep_paulis[i], true); + } + z_pauli_translator(ep_qargs[0], ep_paulis[0], true); + } + + else { // case of empty pauli string + throw "ExpPauli filed. No Pauli String found"; + } + } + + void visit(ControlGate &cg) override { + VarAccess ctrl = cg.ctrl(); + auto t_gate = tools_v1::ast::object::clone(cg.target_gate()); + + tools_v1::parser::Position pos; + ptr mc_gate = + MultiControlGate::create(pos, {std::move(ctrl)}, {}, std::move(t_gate)); + + mc_gate->accept(*this); + } + + void visit(MultiControlGate &gate) override { // <- + tools_v1::parser::Position pos; + + auto create_hadamard = [this](VarAccess &q) { + tools_v1::parser::Position pos; + auto h = DeclaredGate::create(pos, "h", {}, {std::move(q)}); + this->new_prog->body().push_back(tools_v1::ast::object::clone(*h)); + }; + + auto create_cnot = [this](VarAccess &c, VarAccess &t) { + tools_v1::parser::Position pos; + auto cnot = CNOTGate::create(pos, std::move(c), std::move(t)); + this->new_prog->body().push_back(tools_v1::ast::object::clone(*cnot)); + }; + + auto create_t_tdag = [this](VarAccess &q, bool dag = false) { + tools_v1::parser::Position pos; + ptr dg; + if (!dag) + dg = DeclaredGate::create(pos, "t", {}, {std::move(q)}); + else + dg = DeclaredGate::create(pos, "tdg", {}, {std::move(q)}); + this->new_prog->body().push_back(tools_v1::ast::object::clone(*dg)); + }; + + auto create_toffoli = [this, &create_hadamard, &create_cnot, + &create_t_tdag](VarAccess &c0, VarAccess &c1, + VarAccess &t) { + tools_v1::parser::Position pos; + create_hadamard(t); + create_cnot(c1, t); + create_t_tdag(t, true); + create_cnot(c0, t); + create_t_tdag(t, false); + create_cnot(c1, t); + create_t_tdag(t, true); + create_cnot(c0, t); + create_t_tdag(c1, true); + create_t_tdag(t, false); + create_cnot(c0, c1); + create_t_tdag(c1, true); + create_cnot(c0, c1); + create_t_tdag(c0, false); + + // S = T^2 + create_t_tdag(c1, false); + create_t_tdag(c1, false); + + create_hadamard(t); + }; + + auto create_pauli_to_X_U = [this](VarAccess &q, PauliType p, + bool dag = false) -> void { + tools_v1::parser::Position pos; + assert(tools_v1::ast::PauliType::I != p); + ptr dg; + switch (p) { + case tools_v1::ast::PauliType::I: + throw "pauli conversion from I to X failed"; + break; + case tools_v1::ast::PauliType::X: + dg = tools_v1::ast::DeclaredGate::create(pos, "I", {}, {std::move(q)}); + case tools_v1::ast::PauliType::Y: + dg = (!dag ? tools_v1::ast::DeclaredGate::create(pos, "s", {}, + {std::move(q)}) + : tools_v1::ast::DeclaredGate::create(pos, "sdag", {}, + {std::move(q)})); + case tools_v1::ast::PauliType::Z: + dg = tools_v1::ast::DeclaredGate::create(pos, "h", {}, {std::move(q)}); + }; + this->new_prog->body().push_back(tools_v1::ast::object::clone(*dg)); + }; + + auto pack_controls = [this](MultiControlGate &gate, + MultiControlGate *&ptr_t_gate) { + std::vector ctrl1_outer = gate.ctrl1(); + std::vector ctrl1_inner = ptr_t_gate->ctrl1(); + + std::vector ctrl1_new; + ctrl1_new.reserve(ctrl1_outer.size() + ctrl1_inner.size()); + for (auto &va : ctrl1_outer) { + ctrl1_new.push_back(va); + } + + std::vector ctrl2_outer = gate.ctrl2(); + std::vector ctrl2_inner = ptr_t_gate->ctrl2(); + + std::vector ctrl2_new; + ctrl2_new.reserve(ctrl2_outer.size() + ctrl2_inner.size()); + for (auto &va : ctrl2_outer) { + ctrl2_new.push_back(va); + } + + tools_v1::parser::Position pos; + ptr cloned_t_gate = + tools_v1::ast::object::clone(ptr_t_gate->target_gate()); + + return tools_v1::ast::MultiControlGate::create(pos, std::move(ctrl1_new), + std::move(ctrl2_new), + std::move(cloned_t_gate)); + }; + + auto ptr_t_gate = dynamic_cast(&gate.target_gate()); + if (ptr_t_gate != nullptr) { + ptr new_gate = pack_controls(gate, ptr_t_gate); + new_gate->accept(*this); + return; + } + + auto ps_ref = dynamic_cast(&gate.target_gate()); + if (ps_ref != nullptr) { + + std::vector new_ctrl1_ref = gate.ctrl1(); + std::vector new_ctrl2_ref = gate.ctrl2(); + + auto gen_toff_transpiler = [&new_ctrl1_ref, &new_ctrl2_ref, + &create_hadamard, &create_cnot, + &create_toffoli, &create_pauli_to_X_U, + this](VarAccess &t, PauliType p) { + // add unitary, U, so that P = UXU^dag + create_pauli_to_X_U(t, p, false); + + // convert 0-control to 1-control + for (auto &va : new_ctrl2_ref) { + tools_v1::parser::Position pos; + auto new_targ = va; + auto dg = DeclaredGate::create(pos, "x", {}, {std::move(new_targ)}); + this->new_prog->body().push_back(std::move(dg)); + } + + // unify all ctrls (prev 0 and prev 1) into one vector of ctrls for + // generalized toffoli + std::vector merge_ctrl; + merge_ctrl.reserve(new_ctrl1_ref.size() + new_ctrl2_ref.size()); + for (auto &va : new_ctrl1_ref) { + merge_ctrl.push_back(va); + } + for (auto &va : new_ctrl2_ref) { + merge_ctrl.push_back(va); + } + + // assert(merge_ctrl.size() >= 3); + if (merge_ctrl.size() >= 3) { + std::vector a; + a.reserve(merge_ctrl.size() - 1); + max_a_tof = std::max(static_cast(merge_ctrl.size()), max_a_tof); + for (int i = 0; i < merge_ctrl.size() - 1; ++i) { + tools_v1::parser::Position pos; + a.emplace_back(pos, "a_tof", i); + } + + create_toffoli(merge_ctrl[0], merge_ctrl[1], a[0]); + for (int i = 2; i < merge_ctrl.size(); ++i) { + create_toffoli(merge_ctrl[i], a[i - 2], a[i - 1]); + } + create_cnot(a.back(), t); + for (int i = merge_ctrl.size() - 1; i >= 2; --i) { + create_toffoli(merge_ctrl[i], a[i - 2], a[i - 1]); + } + create_toffoli(merge_ctrl[0], merge_ctrl[1], a[0]); + } else if (merge_ctrl.size() == 2) { + // add one toffoli + create_toffoli(merge_ctrl[0], merge_ctrl[1], t); + } else if (merge_ctrl.size() == 1) { + // add cnot + create_cnot(merge_ctrl[0], t); + } else { + tools_v1::parser::Position pos; + auto new_targ = t; + auto dg = tools_v1::ast::DeclaredGate::create(pos, "x", {}, + {std::move(new_targ)}); + this->new_prog->body().push_back(std::move(dg)); + } + + // convert 0-controls to 1-controls, see above + for (auto &va : new_ctrl2_ref) { + tools_v1::parser::Position pos; + auto new_targ = va; + auto dg = DeclaredGate::create(pos, "x", {}, {std::move(new_targ)}); + this->new_prog->body().push_back(std::move(dg)); + } + + // add U^dag, so that P = UXU^dag, see above + create_pauli_to_X_U(t, p, true); + }; + ps_ref->foreach_pauli(gen_toff_transpiler); + return; + } + + auto dg_ref = dynamic_cast(&gate.target_gate()); + if (dg_ref != nullptr) { + symbol cur_name = dg_ref->name(); + if (cur_name == "x") { + auto c1 = gate.ctrl1(); + auto c2 = gate.ctrl2(); + auto qargs = dg_ref->qargs(); + std::vector paulis = {tools_v1::ast::PauliType::X}; + ptr new_target_gate = + PauliString::create(pos, std::move(qargs), std::move(paulis)); + auto new_multi_control_gate = MultiControlGate::create( + pos, std::move(c1), std::move(c2), std::move(new_target_gate)); + new_multi_control_gate->accept(*this); + } + return; + } + + throw "Not supported MultiControlGate transpilation operation."; + } + // Declarations + void visit(GateDecl &) override {} + void visit(OracleDecl &) override {} + void visit(RegisterDecl & rg) override { + new_prog->body().push_back(tools_v1::ast::object::clone(rg)); + } + void visit(AncillaDecl &) override {} + // Program + void visit(Program &prog) override { + auto it = prog.body().begin(); + while (it != prog.end()) { + (*it)->accept(*this); + ++it; + } + auto rg_tof = RegisterDecl::create(tools_v1::parser::Position(), "a_tof", true, max_a_tof); + new_prog->body().push_front(std::move(rg_tof)); + } + + ptr& prog(){ + return new_prog; + } + + void print_num_multicontrolgates() { std::cout << cnt_mcg << std::endl; } + + std::ostream &pretty_print(std::ostream &os) const { + new_prog->pretty_print(os); + return os; + } +}; + +} // namespace ast +} // namespace tools_v1 + +#endif diff --git a/experimental/include/tools_v1/algorithm/Interaction.hpp b/experimental/include/tools_v1/algorithm/Interaction.hpp new file mode 100644 index 00000000..ce985eba --- /dev/null +++ b/experimental/include/tools_v1/algorithm/Interaction.hpp @@ -0,0 +1,165 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace tools_v1::algorithm { + +using namespace tools_v1::tools; + +// Generate the Hubbard interaction term B +// B ∝ ∑_{k,p,q} c_{k↑}^† c_{p↓}^† c_{q↓} c_{(k+p-q)↑} + Hermitian conjugate +// This is simplified for demonstration - in practice would need momentum encoding +inline circuit generate_B_term(int lattice_size, double interaction_strength = 1.0) { + circuit B_circuit; + tools_v1::parser::Position pos; + + // For demonstration, we'll create a simplified version that represents + // the structure of the interaction term + // In a full implementation, this would encode the momentum space structure + + // Add some gates to represent the interaction structure + // This is a placeholder for the actual complex interaction circuit + + // Example: Add some gates to represent the interaction + if (lattice_size >= 2) { + // Add some gates to show the interaction pattern + B_circuit.push_back(hadamard(qbit(0))); + B_circuit.push_back(hadamard(qbit(1))); + // For demonstration, we'll use a simple gate instead of controlled_not + B_circuit.push_back(rz_gate(1.0, qbit(0))); + } + + return B_circuit; +} + +// Generate the i-B term (complex combination needed for inversion) +// i-B = i·I - 1·B +inline circuit generate_iB_term(int lattice_size, double interaction_strength = 1.0) { + circuit iB_circuit; + + // First generate the B term + circuit B_term = generate_B_term(lattice_size, interaction_strength); + + // For the i-B term, we need to implement the LCU: i·I - 1·B + // This follows the two-unitaries LCU pattern from the paper + + // Create identity circuit (I) + circuit identity_circuit; + // Identity is represented by no gates (or could add identity gates if needed) + + // Use the LCU pattern for i·I - 1·B + // Coefficients: c0 = i, c1 = -1 + double c0_abs = 1.0; // |i| = 1 + double c1_abs = 1.0; // |-1| = 1 + + // Calculate parameters for LCU + double theta = 2.0 * std::acos(std::sqrt(c0_abs / (c0_abs + c1_abs))); + double mu = std::numbers::pi / 2.0; // arg(i/-1) = π/2 + + // Create ancilla qubit for LCU + qbit ancilla(0); + + // Data qubits (adjust based on lattice size) + std::vector data_qubits; + for (int i = 1; i <= lattice_size; ++i) { + data_qubits.push_back(qbit(i)); + } + + // Step 1: R_y(θ) + iB_circuit.push_back(ry_gate(theta, ancilla)); + + // Step 2: R_z(μ) + iB_circuit.push_back(rz_gate(mu, ancilla)); + + // Step 3: Controlled-I (control on |0⟩) + // For identity, we don't need to add any gates + + // Step 4: Controlled-B (control on |1⟩) + // Add the B term gates with appropriate control + for (const auto &gate : B_term) { + iB_circuit.push_back(tools_v1::ast::object::clone(*gate)); + } + + // Step 5: R_y(-θ) + iB_circuit.push_back(ry_gate(-theta, ancilla)); + + return iB_circuit; +} + +// Generate block encoding for the interaction term with proper momentum structure +// This is a more sophisticated version that would encode the actual +// c_{k↑}^† c_{p↓}^† c_{q↓} c_{(k+p-q)↑} structure +inline circuit generate_interaction_block_encoding(int lattice_size, + double interaction_strength = 1.0, + bool include_hermitian_conjugate = true) { + circuit interaction_circuit; + + // This would implement the full block encoding shown in Figure 6 of the paper + // For now, we create a simplified representation + + int num_qubits_per_site = 2; // spin up and down + int total_qubits = lattice_size * lattice_size * num_qubits_per_site; + + // Add some representative gates to show the structure + if (total_qubits >= 4) { + // Add gates that represent the four-fermion interaction structure + interaction_circuit.push_back(hadamard(qbit(0))); + interaction_circuit.push_back(hadamard(qbit(1))); + // Use simple gates instead of controlled_not for demonstration + interaction_circuit.push_back(rz_gate(0.5, qbit(2))); + interaction_circuit.push_back(rz_gate(0.5, qbit(3))); + + // Add phase gates to represent the interaction strength + interaction_circuit.push_back(rz_gate(interaction_strength, qbit(0))); + } + + return interaction_circuit; +} + +// Generate the complete interaction circuit for the Hubbard model +// This combines both kinetic and interaction terms +inline circuit generate_hubbard_interaction(int lattice_size, + double hopping_strength = 1.0, + double interaction_strength = 1.0) { + circuit hubbard_circuit; + + // Add interaction term (B) + circuit B_term = generate_B_term(lattice_size, interaction_strength); + for (const auto &gate : B_term) { + hubbard_circuit.push_back(tools_v1::ast::object::clone(*gate)); + } + + return hubbard_circuit; +} + +// Helper function to analyze interaction circuit properties +inline void analyze_interaction_circuit(const circuit &interaction_circuit, + int lattice_size) { + std::cout << "Interaction Circuit Analysis:" << std::endl; + std::cout << "- Lattice size: " << lattice_size << std::endl; + std::cout << "- Number of gates: " << interaction_circuit.size() << std::endl; + std::cout << "- Circuit depth: " << "[to be calculated]" << std::endl; + + // Count different gate types + int hadamard_count = 0; + int rotation_count = 0; + + for (const auto &gate : interaction_circuit) { + if (gate) { + // For demonstration, we'll count gates based on their type + // In a real implementation, we would inspect the gate type + hadamard_count++; // Simple approximation for demonstration + rotation_count++; // Simple approximation for demonstration + } + } + + std::cout << "- Hadamard gates: " << hadamard_count << std::endl; + std::cout << "- Rotation gates: " << rotation_count << std::endl; +} + +} // namespace tools_v1::algorithm \ No newline at end of file diff --git a/experimental/include/tools_v1/algorithm/LCU.hpp b/experimental/include/tools_v1/algorithm/LCU.hpp new file mode 100644 index 00000000..4fc8a484 --- /dev/null +++ b/experimental/include/tools_v1/algorithm/LCU.hpp @@ -0,0 +1,366 @@ +#ifndef LCU_HPP_ +#define LCU_HPP_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace tools_v1::algorithm { + +using namespace tools_v1::tools; + +inline circuit lcu_prepare(const std::vector &ancillas) { + circuit prep_circuit; + tools_v1::parser::Position pos; + const int num_ancillas = ancillas.size(); + + for (int L = 0; L < num_ancillas; ++L) { + auto h = + ast::DeclaredGate::create(pos, "h", {}, {ancillas[L].to_va()}); + prep_circuit.push_back(std::move(h)); + } + + return prep_circuit; +} + +inline circuit lcu_prepare(const std::vector &coefficients, + const std::vector &ancillas) { + circuit prep_circuit; + tools_v1::parser::Position pos; + const int num_ancillas = ancillas.size(); + const int num_coeffs = coefficients.size(); + + assert(num_coeffs == (1 << num_ancillas)); + + std::vector cs; + cs.reserve(coefficients.size()); + std::partial_sum(coefficients.begin(), coefficients.end(), + std::back_inserter(cs)); + + auto S = [&cs, &num_ancillas](int L, int k) -> double { + assert(L <= num_ancillas); + assert(k <= (1 << L) - 1); + int ind_max = (k + 1) * (1 << (num_ancillas - L)) - 1; + int ind_min = k * (1 << (num_ancillas - L)) - 1; + assert(ind_max < cs.size()); + if (k == 0) + return cs[ind_max]; + else + return cs[ind_max] - cs[ind_min]; + }; + + auto mu = [&cs, &num_ancillas, &S](int L, int k) -> double { + assert(L <= num_ancillas - 1); + assert(k <= (1 << L) - 1); + return std::sqrt(S(L + 1, 2 * k) / S(L, k)); + }; + + for (int L = 1; L < num_ancillas; ++L) { + for (int k = 0; k < (1 << L); ++k) { + + std::vector controls_1; + std::vector controls_0; + + for (int j = 0; j < L; ++j) { + if (k & (1 << (L - 1 - j))) { + controls_1.push_back(j); + } else { + controls_0.push_back(j); + } + } + + std::vector ctrl1_qubits; + std::vector ctrl2_qubits; + + for (int idx : controls_1) { + ctrl1_qubits.push_back(ancillas[idx].to_va()); + } + + for (int idx : controls_0) { + ctrl2_qubits.push_back(ancillas[idx].to_va()); + } + + auto tmp = ry_gate(-std::acos(mu(L, k)), ancillas[L]); + + auto g = ast::MultiControlGate::create(pos, std::move(ctrl1_qubits), + std::move(ctrl2_qubits), + std::move(tmp)); + prep_circuit.push_back(std::move(g)); + } + } + + return prep_circuit; +} + +inline circuit lcu_select(const std::vector &ancillas, + const std::vector &unitaries) { + circuit c; + tools_v1::parser::Position pos; + + int N = unitaries.size(); + int A = ancillas.size(); + assert(N == (1 << A)); + + auto it = unitaries.begin(); + for (int i = 0; i < N; ++i, ++it) { + std::vector controls_1; + std::vector controls_0; + + for (int j = 0; j < ancillas.size(); ++j) { + if (i & (1 << j)) { + controls_1.push_back(j); + } else { + controls_0.push_back(j); + } + } + + std::vector ctrl1_qubits; + std::vector ctrl2_qubits; + + for (int idx : controls_1) { + ctrl1_qubits.push_back(ancillas[idx].to_va()); + } + + for (int idx : controls_0) { + ctrl2_qubits.push_back(ancillas[idx].to_va()); + } + + for (auto &v : *it) { + ast::ptr tmp = ast::object::clone(*v); + auto g_s = stmt_to_gate(*tmp); + auto g = ast::MultiControlGate::create(pos, std::move(ctrl1_qubits), + std::move(ctrl2_qubits), + std::move(g_s)); + c.push_back(std::move(g)); + } + } + + return c; +} + +inline circuit lcu_select(const std::vector &ancillas, + const std::vector> &unitaries) { + circuit c; + tools_v1::parser::Position pos; + + int N = unitaries.size(); + int A = ancillas.size(); + assert(N == (1 << A)); + + auto it = unitaries.begin(); + for (int i = 0; i < N; ++i, ++it) { + std::vector controls_1; + std::vector controls_0; + + for (int j = 0; j < ancillas.size(); ++j) { + if (i & (1 << j)) { + controls_1.push_back(j); + } else { + controls_0.push_back(j); + } + } + + std::vector ctrl1_qubits; + std::vector ctrl2_qubits; + + for (int idx : controls_1) { + ctrl1_qubits.push_back(ancillas[idx].to_va()); + } + + for (int idx : controls_0) { + ctrl2_qubits.push_back(ancillas[idx].to_va()); + } + + // FIX: what's going on here? why does this work? + auto tmp = ast::object::clone(**it); + + auto g = ast::MultiControlGate::create( + pos, std::move(ctrl1_qubits), std::move(ctrl2_qubits), std::move(tmp)); + c.push_back(std::move(g)); + } + + return c; +} + +inline circuit LCU(const std::vector &coefficients, + const std::vector &ancilla_qubits, + const std::vector &unitaries) { + circuit lcu_circuit; + circuit prep = lcu_prepare(coefficients, ancilla_qubits); + circuit sel = lcu_select(ancilla_qubits, unitaries); + + for (const auto &x : ancilla_qubits) + lcu_circuit.save_ancilla(x); + + for (const auto &gate : prep) { + lcu_circuit.push_back(tools_v1::ast::object::clone(*gate)); + } + + for (const auto &gate : sel) { + lcu_circuit.push_back(tools_v1::ast::object::clone(*gate)); + } + + // circuit prep_dagger = dagger_circuit(prep); + auto prep_dagger = ast::circuit_dagger(prep); + for (const auto &gate : prep_dagger) { + lcu_circuit.push_back(tools_v1::ast::object::clone(*gate)); + } + + return lcu_circuit; +} + +inline circuit LCU(const std::vector &coefficients, + const std::vector &ancilla_qubits, + const std::vector> &unitaries) { + circuit lcu_circuit; + circuit prep = lcu_prepare(coefficients, ancilla_qubits); + circuit sel = lcu_select(ancilla_qubits, unitaries); + + for (const auto &x : ancilla_qubits) + lcu_circuit.save_ancilla(x); + + for (const auto &gate : prep) { + lcu_circuit.push_back(tools_v1::ast::object::clone(*gate)); + } + + for (const auto &gate : sel) { + lcu_circuit.push_back(tools_v1::ast::object::clone(*gate)); + } + + // circuit prep_dagger = dagger_circuit(prep); + auto prep_dagger = ast::circuit_dagger(prep); + for (const auto &gate : prep_dagger) { + lcu_circuit.push_back(tools_v1::ast::object::clone(*gate)); + } + + return lcu_circuit; +} + +inline circuit LCU(const std::vector> &unitaries, ANC_MEM& anc_mem) { + circuit lcu_circuit; + std::vector ancilla_qubits; + const int NA = std::log2(unitaries.size()); + ancilla_qubits.reserve(NA); + for (int i = 0; i < NA; ++i) + ancilla_qubits.emplace_back(anc_mem.generate_ancilla("LCU")); + + circuit prep = lcu_prepare(ancilla_qubits); + circuit sel = lcu_select(ancilla_qubits, unitaries); + + for (const auto &x : ancilla_qubits) + lcu_circuit.save_ancilla(x); + + for (const auto &gate : prep) { + lcu_circuit.push_back(tools_v1::ast::object::clone(*gate)); + } + + for (const auto &gate : sel) { + lcu_circuit.push_back(tools_v1::ast::object::clone(*gate)); + } + + // circuit prep_dagger = dagger_circuit(prep); + auto prep_dagger = ast::circuit_dagger(prep); + for (const auto &gate : prep_dagger) { + lcu_circuit.push_back(tools_v1::ast::object::clone(*gate)); + } + + return lcu_circuit; +} + +inline circuit LCU(const std::vector &unitaries, ANC_MEM& anc_mem) { + circuit lcu_circuit; + std::vector ancilla_qubits; + const int NA = std::log2(unitaries.size()); + ancilla_qubits.reserve(NA); + for (int i = 0; i < NA; ++i) + ancilla_qubits.emplace_back(anc_mem.generate_ancilla("LCU")); + + circuit prep = lcu_prepare(ancilla_qubits); + circuit sel = lcu_select(ancilla_qubits, unitaries); + + for (const auto &x : ancilla_qubits) + lcu_circuit.save_ancilla(x); + + for (const auto &gate : prep) { + lcu_circuit.push_back(tools_v1::ast::object::clone(*gate)); + } + + for (const auto &gate : sel) { + lcu_circuit.push_back(tools_v1::ast::object::clone(*gate)); + } + + // circuit prep_dagger = dagger_circuit(prep); + auto prep_dagger = ast::circuit_dagger(prep); + for (const auto &gate : prep_dagger) { + lcu_circuit.push_back(tools_v1::ast::object::clone(*gate)); + } + + return lcu_circuit; +} + +inline circuit LCU_two_unitaries(std::complex c0, + std::complex c1, const circuit &U0, + const circuit &U1, const qbit &ancilla) { + circuit lcu_circuit; + tools_v1::parser::Position pos; + // save ancillas + lcu_circuit.save_ancilla(ancilla); + for (auto it = U0.ancilla_begin(); it != U0.ancilla_end(); ++it) + lcu_circuit.save_ancilla(**it); + for (auto it = U1.ancilla_begin(); it != U1.ancilla_end(); ++it) + lcu_circuit.save_ancilla(**it); + + double theta = + 2.0 * std::acos(std::sqrt(std::abs(c0) / (std::abs(c0) + std::abs(c1)))); + double mu = std::arg(c0) - std::arg(c1); + + lcu_circuit.push_back(ry_gate(theta, ancilla)); + lcu_circuit.push_back(rz_gate(mu, ancilla)); + + std::vector ctrl1_qubits, + ctrl2_qubits = {ancilla.to_va()}; + for (const auto &stmt : U0) { + GateConverter conv; + stmt->accept(conv); + auto _ctr1 = ctrl1_qubits; + auto _ctr2 = ctrl2_qubits; + auto g = + ast::MultiControlGate::create(pos, std::move(_ctr1), std::move(_ctr2), + std::move(conv.converted_gate)); + lcu_circuit.push_back(std::move(g)); + } + + ctrl2_qubits.clear(); + ctrl1_qubits = {ancilla.to_va()}; + for (const auto &stmt : U1) { + GateConverter conv; + stmt->accept(conv); + auto _ctr1 = ctrl1_qubits; + auto _ctr2 = ctrl2_qubits; + auto g = + ast::MultiControlGate::create(pos, std::move(_ctr1), std::move(_ctr2), + std::move(conv.converted_gate)); + lcu_circuit.push_back(std::move(g)); + } + + lcu_circuit.push_back(ry_gate(-theta, ancilla)); + return lcu_circuit; +} + +inline circuit LCU_two_unitaries(std::complex c0, + std::complex c1, const circuit &U0, + const circuit &U1, ANC_MEM& anc_mem) { + qbit ancilla = anc_mem.generate_ancilla("LCU_two"); + return LCU_two_unitaries(c0, c1, U0, U1, ancilla); +} + +} // namespace tools_v1::algorithm + +#endif diff --git a/experimental/include/tools_v1/algorithm/Multiplication.hpp b/experimental/include/tools_v1/algorithm/Multiplication.hpp new file mode 100644 index 00000000..fe9ffd6e --- /dev/null +++ b/experimental/include/tools_v1/algorithm/Multiplication.hpp @@ -0,0 +1,42 @@ +#ifndef MULTIPLICATION_HPP_ +#define MULTIPLICATION_HPP_ + +#include + +namespace tools_v1 { +namespace algorithm { + +inline tools::circuit circuit_combine(const tools::circuit& u1, + const tools::circuit& u2) { + tools::circuit c; + // + for (const auto &x : u1) + c.push_back(ast::object::clone(*x)); + for (const auto &x : u2) + c.push_back(ast::object::clone(*x)); + // + for (auto it = u1.ancilla_begin(); it != u1.ancilla_end(); ++it) + c.save_ancilla(**it); + for (auto it = u2.ancilla_begin(); it != u2.ancilla_end(); ++it) + c.save_ancilla(**it); + return c; +} + +inline tools::circuit circuit_combine( + const std::vector &unitaries){ + tools::circuit c; + // + for(const auto &u : unitaries){ + for (const auto &x : u) + c.push_back(ast::object::clone(*x)); + for (auto it = u.ancilla_begin(); it != u.ancilla_end(); ++it) + c.save_ancilla(**it); + } + return c; +} + + +} // namespace algorithm +} // namespace tools_v1 + +#endif diff --git a/experimental/include/tools_v1/algorithm/Observable.hpp b/experimental/include/tools_v1/algorithm/Observable.hpp new file mode 100644 index 00000000..94ca4cc3 --- /dev/null +++ b/experimental/include/tools_v1/algorithm/Observable.hpp @@ -0,0 +1,216 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace tools_v1::algorithm { + +using namespace tools_v1::tools; + +// Create a creation operator c† block encoding +// Based on paper Figure for c† +inline circuit create_creation_operator(int site_index, int total_sites, const qbit& ancilla) { + circuit creation_circuit; + tools_v1::parser::Position pos; + + creation_circuit.push_back(hadamard(ancilla)); + + // Add some representative gates to show the structure + if (site_index < total_sites) { + creation_circuit.push_back(hadamard(qbit(site_index))); + } + + return creation_circuit; +} + +// Create an annihilation operator c block encoding +// Based on paper Figure for c +inline circuit create_annihilation_operator(int site_index, int total_sites, const qbit& ancilla) { + circuit annihilation_circuit; + tools_v1::parser::Position pos; + + // Simplified implementation for demonstration + // In a full implementation, this would encode the fermionic annihilation operator + // with proper Jordan-Wigner strings + + // Add Hadamard on ancilla + annihilation_circuit.push_back(hadamard(ancilla)); + + // Add some representative gates to show the structure + if (site_index < total_sites) { + annihilation_circuit.push_back(hadamard(qbit(site_index))); + } + + return annihilation_circuit; +} + +// Create block encoding for A = ∑_{k,σ} ε(k) c_{k,σ}^† c_{k,σ} +// This uses the LCU pattern from the paper +inline circuit create_kinetic_term_A(int lattice_size, double hopping_strength = 1.0) { + circuit A_circuit; + + // For demonstration, create a simple circuit representing the kinetic term + // In a full implementation, this would use the LCU pattern with proper coefficients + + // Add some representative gates + if (lattice_size >= 2) { + A_circuit.push_back(hadamard(qbit(0))); + A_circuit.push_back(hadamard(qbit(1))); + A_circuit.push_back(rz_gate(hopping_strength, qbit(0))); + } + + return A_circuit; +} + +// Create block encoding for (z-i-A+E)^{-1} using QSVT +// This implements the first inversion in the observable +inline circuit create_first_inversion(int lattice_size, std::complex z, double E, + const std::vector& qsvt_phases) { + circuit inversion_circuit; + + // Create the kinetic term A + circuit A_term = create_kinetic_term_A(lattice_size); + + // For demonstration, create a simple QSVT circuit + // In a full implementation, this would use proper QSVT with the calculated phases + + // Create ancilla qubits for QSVT + for (int i = 0; i < 2; ++i) { + qbit ancilla_qubit(lattice_size + i); + A_term.save_ancilla(ancilla_qubit); + } + qbit qsvt_ancilla(lattice_size + 2); + + // Apply QSVT for inversion + inversion_circuit = QSVT(qsvt_phases, A_term, qsvt_ancilla); + + return inversion_circuit; +} + +// Create block encoding for (I + (z-i-A+E)^{-1}(i-B))^{-1} using QSVT +// This implements the second inversion in the observable +inline circuit create_second_inversion(int lattice_size, std::complex z, double E, + const circuit& first_inversion, + const std::vector& qsvt_phases) { + circuit second_inversion_circuit; + + // Create the i-B term + circuit iB_term = generate_iB_term(lattice_size); + + // For demonstration, create a simple QSVT circuit + // In a full implementation, this would combine first_inversion and iB_term properly + + // Create ancilla qubits for QSVT + circuit combined_circuit; + for (int i = 0; i < 2; ++i) { + qbit ancilla_qubit(lattice_size + i); + combined_circuit.save_ancilla(ancilla_qubit); + } + qbit qsvt_ancilla(lattice_size + 2); + + // Create a combined circuit for the QSVT input + for (const auto& gate : first_inversion) { + combined_circuit.push_back(tools_v1::ast::object::clone(*gate)); + } + for (const auto& gate : iB_term) { + combined_circuit.push_back(tools_v1::ast::object::clone(*gate)); + } + + // Apply QSVT for inversion + second_inversion_circuit = + QSVT(qsvt_phases, combined_circuit, qsvt_ancilla); + + return second_inversion_circuit; +} + +// Create the complete observable circuit: c_i (z-H+E)^{-1} c_j^† +// This combines all the components according to the paper +inline circuit create_observable_circuit(int lattice_size, int site_i, int site_j, + std::complex z, double E, + const std::vector& qsvt_phases_first, + const std::vector& qsvt_phases_second) { + circuit observable_circuit; + + // Create ancilla qubits for the observable + qbit observable_ancilla(lattice_size * 2); + + // Step 1: Apply creation operator c_j^† + circuit creation_op = create_creation_operator(site_j, lattice_size, observable_ancilla); + for (const auto& gate : creation_op) { + observable_circuit.push_back(tools_v1::ast::object::clone(*gate)); + } + + // Step 2: Apply first inversion (z-i-A+E)^{-1} + circuit first_inversion = create_first_inversion(lattice_size, z, E, qsvt_phases_first); + for (const auto& gate : first_inversion) { + observable_circuit.push_back(tools_v1::ast::object::clone(*gate)); + } + + // Step 3: Apply second inversion (I + (z-i-A+E)^{-1}(i-B))^{-1} + circuit second_inversion = create_second_inversion(lattice_size, z, E, first_inversion, qsvt_phases_second); + for (const auto& gate : second_inversion) { + observable_circuit.push_back(tools_v1::ast::object::clone(*gate)); + } + + // Step 4: Apply annihilation operator c_i + circuit annihilation_op = create_annihilation_operator(site_i, lattice_size, observable_ancilla); + for (const auto& gate : annihilation_op) { + observable_circuit.push_back(tools_v1::ast::object::clone(*gate)); + } + + return observable_circuit; +} + +// Create Hadamard test circuit for measuring expectation values +// Based on paper Figure for Hadamard test +inline circuit create_hadamard_test(const circuit& observable_circuit, const qbit& test_ancilla) { + circuit hadamard_test_circuit; + + // Initial Hadamard on test ancilla + hadamard_test_circuit.push_back(hadamard(test_ancilla)); + + // Controlled application of observable circuit + // For demonstration, we'll add the observable circuit directly + // In a full implementation, this would be properly controlled + for (const auto& gate : observable_circuit) { + hadamard_test_circuit.push_back(tools_v1::ast::object::clone(*gate)); + } + + // Final Hadamard on test ancilla + hadamard_test_circuit.push_back(hadamard(test_ancilla)); + + return hadamard_test_circuit; +} + +// Helper function to analyze observable circuit properties +inline void analyze_observable_circuit(const circuit& observable_circuit, int lattice_size) { + std::cout << "Observable Circuit Analysis:" << std::endl; + std::cout << "- Lattice size: " << lattice_size << std::endl; + std::cout << "- Number of gates: " << observable_circuit.size() << std::endl; + std::cout << "- Circuit depth: " << "[to be calculated]" << std::endl; + + // Count different gate types + int hadamard_count = 0; + int rotation_count = 0; + + for (const auto& gate : observable_circuit) { + if (gate) { + // For demonstration, we'll count gates based on their type + // In a real implementation, we would inspect the gate type + hadamard_count++; // Simple approximation for demonstration + rotation_count++; // Simple approximation for demonstration + } + } + + std::cout << "- Hadamard gates: " << hadamard_count << std::endl; + std::cout << "- Rotation gates: " << rotation_count << std::endl; +} + +} // namespace tools_v1::algorithm diff --git a/experimental/include/tools_v1/algorithm/QFT.hpp b/experimental/include/tools_v1/algorithm/QFT.hpp new file mode 100644 index 00000000..e4f7c8ce --- /dev/null +++ b/experimental/include/tools_v1/algorithm/QFT.hpp @@ -0,0 +1,83 @@ +#pragma once + +#include +#include +#include + +namespace tools_v1::algorithm { + +using namespace tools_v1::tools; + +// Create a phase rotation gate R_k = diag(1, exp(2*pi*i/2^k)) +inline tools_v1::ast::ptr phase_rotation(int k, + const qbit &target) { + tools_v1::parser::Position pos; + double angle = 2.0 * std::numbers::pi / std::pow(2.0, k); + + // Create a U gate with the phase rotation + auto angle_expr = tools_v1::ast::RealExpr::create(pos, angle); + std::vector target_qubits = { + target.to_var_access()}; + + return tools_v1::ast::PhaseGate::create(pos, std::move(angle_expr), + std::move(target_qubits)); +} + +// Create a controlled phase rotation gate +inline tools_v1::ast::ptr +controlled_phase_rotation(int k, const qbit &control, const qbit &target) { + tools_v1::parser::Position pos; + + // Create the target phase rotation gate + auto target_gate = phase_rotation(k, target); + + // Create controlled version + auto control_qubit = control.to_var_access(); + + return tools_v1::ast::ControlGate::create(pos, std::move(control_qubit), + std::move(target_gate)); +} + +// Quantum Fourier Transform implementation +inline circuit QFT(std::vector qubits) { + circuit qft_circuit; + int n = qubits.size(); + + for (int i = 0; i < n; ++i) { + // Add Hadamard gate on current qubit + qft_circuit.push_back(hadamard(qubits[i])); + + // Add controlled phase rotations + for (int j = i + 1; j < n; ++j) { + int k = j - i + 1; // R_k where k = j-i+1 + auto controlled_phase = + controlled_phase_rotation(k, qubits[j], qubits[i]); + qft_circuit.push_back(std::move(controlled_phase)); + } + } + + return qft_circuit; +} + +// Inverse Quantum Fourier Transform +inline circuit inverse_QFT(std::vector qubits) { + circuit inv_qft_circuit; + int n = qubits.size(); + + for (int i = n - 1; i >= 0; --i) { + // Add controlled phase rotations (in reverse order) + for (int j = n - 1; j > i; --j) { + int k = j - i + 1; // R_k where k = j-i+1 + auto controlled_phase = + controlled_phase_rotation(-k, qubits[j], qubits[i]); + inv_qft_circuit.push_back(std::move(controlled_phase)); + } + + // Add Hadamard gate on current qubit + inv_qft_circuit.push_back(hadamard(qubits[i])); + } + + return inv_qft_circuit; +} + +} // namespace tools_v1::algorithm \ No newline at end of file diff --git a/experimental/include/tools_v1/algorithm/QSVT.hpp b/experimental/include/tools_v1/algorithm/QSVT.hpp new file mode 100644 index 00000000..32312df6 --- /dev/null +++ b/experimental/include/tools_v1/algorithm/QSVT.hpp @@ -0,0 +1,136 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace tools_v1::algorithm { + +using namespace tools_v1::tools; + +// Create a controlled Z rotation gate +inline tools_v1::ast::ptr +controlled_rz_gate(double angle, const qbit &control, const qbit &target) { + tools_v1::parser::Position pos; + auto angle_expr = tools_v1::ast::RealExpr::create(pos, angle); + + // Create controlled Rz gate + auto target_gate = rz(target, std::move(angle_expr)); + + // Create controlled version + auto control_qubit = control.to_va(); + + return tools_v1::ast::ControlGate::create(pos, std::move(control_qubit), + std::move(target_gate)); +} + +// Create a multi-controlled Z rotation gate +inline tools_v1::ast::ptr +multi_controlled_rz_gate(double angle, const std::vector &controls, + const qbit &target) { + tools_v1::parser::Position pos; + auto angle_expr = tools_v1::ast::RealExpr::create(pos, angle); + + // Create the target Z rotation gate + auto target_gate = rz(target, std::move(angle_expr)); + + // Create multi-controlled version + std::vector control_qubits; + for (const auto &control : controls) { + control_qubits.push_back(control.to_va()); + } + + return tools_v1::ast::MultiControlGate::create( + pos, std::move(control_qubits), std::vector{}, + std::move(target_gate)); +} + +// Quantum Singular Value Transform (QSVT) implementation +inline circuit QSVT(const std::vector &phi, const circuit &u, + const qbit &qsvt_ancilla) { + circuit qsvt_circuit; + assert(phi.size() % 2 == 1); + const int d = (phi.size() - 1) / 2; + tools_v1::parser::Position pos; + + // Prepare ancillas + std::vector> all_ancillas; + for (auto it = u.ancilla_begin(); it != u.ancilla_end(); ++it) { + all_ancillas.push_back(std::make_unique(**it)); + } + auto convert_to_vec_varaccess = + [](const std::vector> &v) + -> std::vector { + tools_v1::parser::Position pos; + std::vector qubits; + qubits.reserve(v.size()); + for (auto &q : v) { + qubits.emplace_back(q->to_va()); + } + return qubits; + }; + + // Prepare helper functions for creating multi-controlled gates + auto create_x_gate = [&qsvt_ancilla, &pos]() { + return tools_v1::ast::DeclaredGate::create(pos, "x", {}, + {qsvt_ancilla.to_va()}); + }; + auto create_mct_gate = [&all_ancillas, &pos, &convert_to_vec_varaccess, + &create_x_gate]() { + auto x = create_x_gate(); + return tools_v1::ast::MultiControlGate::create( + pos, {}, convert_to_vec_varaccess(all_ancillas), std::move(x)); + }; + ast::ptr qsvt_mct_gate; + ast::ptr qsvt_rz_gate; + + // Initial Hadamard on QSVT ancilla + qsvt_circuit.push_back(hadamard(qsvt_ancilla)); + + // Iterate through all phases (2d+1 total phases) + qsvt_circuit.push_back(std::move(create_mct_gate())); + qsvt_circuit.push_back(std::move(rz_gate(phi.back(), qsvt_ancilla))); + qsvt_circuit.push_back(std::move(create_mct_gate())); + + // prepare helper functions to add unitaries + + auto add_u = [&qsvt_circuit, &u](){ + for(auto it = u.begin(); it != u.end(); ++it){ + qsvt_circuit.push_back(ast::object::clone(**it)); + } + }; + auto u_dag = ast::circuit_dagger(u); + auto add_u_dag = [&qsvt_circuit, &u_dag](){ + for(auto it = u_dag.begin(); it != u_dag.end(); ++it){ + qsvt_circuit.push_back(ast::object::clone(**it)); + } + }; + + for (int k = 2 * d; k >= 0; k -= 2) { + + add_u(); + + qsvt_circuit.push_back(std::move(create_mct_gate())); + qsvt_circuit.push_back(std::move(rz_gate(phi[k-1], qsvt_ancilla))); + qsvt_circuit.push_back(std::move(create_mct_gate())); + + add_u_dag(); + + qsvt_circuit.push_back(std::move(create_mct_gate())); + qsvt_circuit.push_back(std::move(rz_gate(phi[k-2], qsvt_ancilla))); + qsvt_circuit.push_back(std::move(create_mct_gate())); + } + + qsvt_circuit.push_back(hadamard(qsvt_ancilla)); + + return qsvt_circuit; +} + +inline circuit QSVT(const std::vector &phi, const circuit &u, ANC_MEM& anc_mem) { + qbit qsvt_ancilla = anc_mem.generate_ancilla("QSVT"); + return QSVT(phi, u, qsvt_ancilla); +} + +} // namespace tools_v1::algorithm diff --git a/experimental/include/tools_v1/algorithm/Utils.hpp b/experimental/include/tools_v1/algorithm/Utils.hpp new file mode 100644 index 00000000..5f59c006 --- /dev/null +++ b/experimental/include/tools_v1/algorithm/Utils.hpp @@ -0,0 +1,41 @@ +#ifndef TOOLS_V1_UTILS_HPP_ +#define TOOLS_V1_UTILS_HPP_ + +#include + +namespace tools_v1::algorithm { + +using namespace tools_v1::tools; + +// Create a rotation gate R_Y(θ) = [[cos(θ/2), -sin(θ/2)], [sin(θ/2), cos(θ/2)]] +inline tools_v1::ast::ptr ry_gate(double angle, + const qbit &target) { + tools_v1::parser::Position pos; + auto angle_expr = tools_v1::ast::RealExpr::create(pos, angle); + auto target_qubit = target.to_va(); + + // Use DeclaredGate with "ry" for rotation around Y axis + std::vector> args; + args.push_back(std::move(angle_expr)); + return tools_v1::ast::DeclaredGate::create( + pos, "ry", std::move(args), + std::vector{target_qubit}); +} + +// Create a rotation gate R_Z(μ) +inline tools_v1::ast::ptr rz_gate(double angle, + const qbit &target) { + tools_v1::parser::Position pos; + auto angle_expr = tools_v1::ast::RealExpr::create(pos, angle); + auto target_qubit = target.to_va(); + + std::vector> args; + args.push_back(std::move(angle_expr)); + return tools_v1::ast::DeclaredGate::create( + pos, "rz", std::move(args), + std::vector{target_qubit}); +} + +} // namespace tools_v1::algorithm + +#endif diff --git a/experimental/include/tools_v1/ast/ast.hpp b/experimental/include/tools_v1/ast/ast.hpp new file mode 100644 index 00000000..2a9f5b7d --- /dev/null +++ b/experimental/include/tools_v1/ast/ast.hpp @@ -0,0 +1,42 @@ +/* + * This file is part of qasmtools. + * + * Copyright (c) 2019 - 2025 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 qasmtools/ast/ast.hpp + */ + +#ifndef TOOLS_V1_AST_AST_HPP_ +#define TOOLS_V1_AST_AST_HPP_ + +#include "base.hpp" +#include "decl.hpp" +#include "expr.hpp" +#include "program.hpp" +#include "semantic.hpp" +#include "stmt.hpp" +#include "visitor.hpp" + +#endif /* TOOLS_V1_AST_AST_HPP_ */ diff --git a/experimental/include/tools_v1/ast/base.hpp b/experimental/include/tools_v1/ast/base.hpp new file mode 100644 index 00000000..349ac8f0 --- /dev/null +++ b/experimental/include/tools_v1/ast/base.hpp @@ -0,0 +1,111 @@ +/* + * This file is part of tools_v1. + * + * Copyright (c) 2019 - 2025 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 tools_v1/ast/base.hpp + * \brief OpenQASM syntax trees + */ + +#ifndef TOOLS_V1_AST_BASE_HPP_ +#define TOOLS_V1_AST_BASE_HPP_ + +#include +#include +#include + +#include "../parser/position.hpp" +#include "cloneable.hpp" +#include "visitor.hpp" + +namespace tools_v1 { +namespace ast { + +template +using ptr = std::unique_ptr; + +using symbol = std::string; + +/** + * \class tools_v1::ast::ASTNode + * \brief Base class for AST nodes + */ +class ASTNode : public object::cloneable { + static int& max_uid_() { + static int v; + return v; + } ///< the maximum uid that has been assigned + + protected: + const int uid_; ///< the node's unique ID + const parser::Position pos_; ///< the node's source code position + + public: + ASTNode(parser::Position pos) : uid_(++max_uid_()), pos_(pos) {} + virtual ~ASTNode() = default; + + /** + * \brief Get the ID of the node + * + * \return The node's unique ID + */ + int uid() const { return uid_; } + + /** + * \brief Get the position of the node + * + * \return The node's position in source + */ + parser::Position pos() const { return pos_; } + + /** + * \brief Provides dispatch for the Visitor pattern + */ + virtual void accept(Visitor& visitor) = 0; + + /** + * \brief Print the formatted QASM source code of the node + * + * \param os Output stream + */ + virtual std::ostream& pretty_print(std::ostream& os) const = 0; + + /** + * \brief Extraction operator override + * + * Extraction is non-virtual and delegates to pretty_print + * + * \param os Output stream + * \param node Node to print + */ + friend std::ostream& operator<<(std::ostream& os, const ASTNode& node) { + return node.pretty_print(os); + } +}; + +} /* namespace ast */ +} /* namespace tools_v1 */ + +#endif /* TOOLS_V1_AST_BASE_HPP_ */ diff --git a/experimental/include/tools_v1/ast/cloneable.hpp b/experimental/include/tools_v1/ast/cloneable.hpp new file mode 100644 index 00000000..d1e1f70e --- /dev/null +++ b/experimental/include/tools_v1/ast/cloneable.hpp @@ -0,0 +1,71 @@ +/* + * Covariance and smart pointers. Adapted from + * https://github.com/CppCodeReviewers/Covariant-Return-Types-and-Smart-Pointers + * + * The MIT License (MIT) + * + * Copyright (c) 2014 C++ Code Revievers + * + * 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 tools_v1/ast/cloneable.hpp + * \brief Covariance and smart pointers + */ + +#ifndef TOOLS_V1_AST_CLONEABLE_HPP_ +#define TOOLS_V1_AST_CLONEABLE_HPP_ + +#include + +namespace tools_v1 { +namespace ast { + +template +using ptr = std::unique_ptr; + +namespace object { +template +inline ptr clone(const T& object) { + using base_type = typename T::base_type; + static_assert(std::is_base_of::value, + "T object has to derived from T::base_type"); + auto ptrr = static_cast(object).clone(); + return ptr(static_cast(ptrr)); +} + +template +struct cloneable { + using base_type = T; + + virtual ~cloneable() = default; + + protected: + virtual T* clone() const = 0; + + template + friend ptr object::clone(const X&); +}; +} /* namespace object */ + +} /* namespace ast */ +} /* namespace tools_v1 */ + +#endif /* TOOLS_V1_AST_CLONEABLE_HPP_ */ diff --git a/experimental/include/tools_v1/ast/control_gate.hpp b/experimental/include/tools_v1/ast/control_gate.hpp new file mode 100644 index 00000000..a2fad4f7 --- /dev/null +++ b/experimental/include/tools_v1/ast/control_gate.hpp @@ -0,0 +1,212 @@ +/* + * This file is part of tools_v1. + * + * Copyright (c) 2019 - 2025 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 tools_v1/ast/control_gate.hpp + * \brief Control gate implementation + */ + +#ifndef TOOLS_V1_AST_CONTROL_GATE_HPP_ +#define TOOLS_V1_AST_CONTROL_GATE_HPP_ + +#include "stmt.hpp" +#include + +namespace tools_v1 { +namespace ast { + +/** + * \class tools_v1::ast::ControlGate + * \brief Class for controlled gates + * \see tools_v1::ast::Gate + */ +class ControlGate final : public Gate { + VarAccess ctrl_; ///< control qubit + ptr target_gate_; ///< gate to be controlled + +public: + /** + * \brief Constructs a control gate + * + * \param pos The source position + * \param ctrl Rvalue reference to the control qubit + * \param target_gate Rvalue reference to the target gate + */ + ControlGate(parser::Position pos, VarAccess &&ctrl, ptr &&target_gate) + : Gate(pos), ctrl_(std::move(ctrl)), + target_gate_(std::move(target_gate)) {} + + /** + * \brief Protected heap-allocated construction + */ + static ptr create(parser::Position pos, VarAccess &&ctrl, + ptr &&target_gate) { + return std::make_unique(pos, std::move(ctrl), + std::move(target_gate)); + } + + /** + * \brief Get the control qubit + * + * \return Reference to the control qubit + */ + VarAccess &ctrl() { return ctrl_; } + + /** + * \brief Get the target gate + * + * \return Reference to the target gate + */ + Gate &target_gate() { return *target_gate_; } + + /** + * \brief Set the control qubit + * + * \param ctrl The new control qubit + */ + void set_ctrl(const VarAccess &ctrl) { ctrl_ = ctrl; } + + /** + * \brief Set the target gate + * + * \param target_gate The new target gate + */ + void set_target_gate(ptr target_gate) { + target_gate_ = std::move(target_gate); + } + + void accept(Visitor &visitor) override { visitor.visit(*this); } + std::ostream &pretty_print(std::ostream &os, + bool suppress_std) const override { + os << "control " << ctrl_ << " :: " << *target_gate_; + return os; + } + +protected: + ControlGate *clone() const override { + return new ControlGate(pos_, VarAccess(ctrl_), + object::clone(*target_gate_)); + } +}; + +class MultiControlGate final : public Gate { + symbol name_ = "MultiControlGate"; + std::vector ctrl_1_; ///< control qubit + std::vector ctrl_2_; ///< control qubit + ptr target_gate_; ///< gate to be controlled + +public: + /** + * \brief Constructs a control gate + * + * \param pos The source position + * \param ctrl Rvalue reference to the control qubit + * \param target_gate Rvalue reference to the target gate + */ + MultiControlGate(parser::Position pos, std::vector &&ctrl1, + std::vector &&ctrl2, ptr &&target_gate) + : Gate(pos), ctrl_1_(std::move(ctrl1)), ctrl_2_(std::move(ctrl2)), + target_gate_(std::move(target_gate)) {} + + /** + * \brief Protected heap-allocated construction + */ + static ptr create(parser::Position pos, + std::vector &&ctrl1, + std::vector &&ctrl2, + ptr &&target_gate) { + return std::make_unique( + pos, std::move(ctrl1), std::move(ctrl2), std::move(target_gate)); + } + + /** + * \brief Get the control qubit + * + * \return Reference to the control qubit + */ + std::vector &ctrl1() { return ctrl_1_; } + std::vector &ctrl2() { return ctrl_2_; } + + /** + * \brief Get the target gate + * + * \return Reference to the target gate + */ + Gate &target_gate() { return *target_gate_; } + + /** + * \brief Set the control qubit + * + * \param ctrl The new control qubit + */ + void set_ctrl_1(const std::vector &ctrl) { ctrl_1_ = ctrl; } + void set_ctrl_2(const std::vector &ctrl) { ctrl_2_ = ctrl; } + + /** + * \brief Set the target gate + * + * \param target_gate The new target gate + */ + void set_target_gate(ptr target_gate) { + target_gate_ = std::move(target_gate); + } + + std::string name(){ return name_; } + + void accept(Visitor &visitor) override { visitor.visit(*this); } + std::ostream &pretty_print(std::ostream &os, + bool suppress_std) const override { + os << "multicontrol ["; + if (ctrl_1_.size() > 0) { + os << ctrl_1_[0]; + } + for (int i = 1; i < ctrl_1_.size(); i++) { + os << ", " << ctrl_1_[i]; + } + os << "] :: ["; + if (ctrl_2_.size() > 0) { + os << ctrl_2_[0]; + } + for (int i = 1; i < ctrl_2_.size(); i++) { + os << ", " << ctrl_2_[i]; + } + os << "] :: "; + os << *target_gate_; + return os; + } + +protected: + MultiControlGate *clone() const override { + return new MultiControlGate(pos_, std::vector(ctrl_1_), + std::vector(ctrl_2_), + object::clone(*target_gate_)); + } +}; + +} /* namespace ast */ +} /* namespace tools_v1 */ + +#endif /* TOOLS_V1_AST_CONTROL_GATE_HPP_ */ diff --git a/experimental/include/tools_v1/ast/decl.hpp b/experimental/include/tools_v1/ast/decl.hpp new file mode 100644 index 00000000..39f2f060 --- /dev/null +++ b/experimental/include/tools_v1/ast/decl.hpp @@ -0,0 +1,402 @@ +/* + * This file is part of tools_v1. + * + * Copyright (c) 2019 - 2025 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 tools_v1/ast/decl.hpp + * \brief OpenQASM declarations + */ + +#ifndef TOOLS_V1_AST_DECL_HPP_ +#define TOOLS_V1_AST_DECL_HPP_ + +#include + +#include "stmt.hpp" + +namespace tools_v1 { +namespace ast { + +#if TOOLS_V1_QASM2_SPECS +static const std::set qelib_defs{ + "u3", "u2", "u1", "cx", "id", "u0", "x", "y", "z", + "h", "s", "sdg", "t", "tdg", "rx", "ry", "rz", "cz", + "cy", "swap", "ch", "ccx", "crz", "cu1", "cu3"}; +#else +/** + * \brief Qiskit definitions include r and cswap gates, see + * tools_v1/parser/preprocessor.hpp + */ +static const std::set qelib_defs{ + "u3", "u2", "u1", "cx", "id", "u0", "x", "y", "z", + "h", "s", "sdg", "t", "tdg", "r", "rx", "ry", "rz", + "cz", "cy", "swap", "ch", "ccx", "cswap", "crz", "cu1", "cu3"}; +#endif + +/** + * \brief Tests whether identifier is part of the standard OpenQASM qelib or not + * + * \param id Identifier + * \return True if \a id is part of the standard OpenQASM qelib, false otherwise + */ +inline bool is_std_qelib(const std::string& id) { + return qelib_defs.find(id) != qelib_defs.end(); +} + +/** + * \class tools_v1::ast::Decl + * \brief Base class for OpenQASM declarations + * + * Declarations are attribute classes as they can occur in different + * statement contexts. To avoid diamond inheritance, any derived declaration + * should also inherit from a statement class + */ +class Decl { + protected: + symbol id_; ///< the name of the declaration + + public: + Decl(symbol id) : id_(id) {} + virtual ~Decl() = default; + + /** + * \brief Return the name being declared + * + * \return Constant reference to the identifier + */ + const symbol& id() { return id_; } +}; + +/** + * \class tools_v1::ast::GateDecl + * \brief Class for gate declarations + * \see tools_v1::ast::Stmt + * \see tools_v1::ast::Decl + */ +class GateDecl final : public Stmt, public Decl { + bool opaque_; ///< whether the declaration is opaque + std::vector c_params_; ///< classical parameters + std::vector q_params_; ///< quantum parameters + std::list> body_; ///< gate body + + public: + /** + * \brief Constructs a gate declaration + * + * \param pos The source position + * \param id The gate identifier + * \param c_params List of classical parameters + * \param q_params List of quantum parameters + * \param body List of gate statements + */ + GateDecl(parser::Position pos, symbol id, bool opaque, + std::vector c_params, std::vector q_params, + std::list>&& body) + : Stmt(pos), Decl(id), opaque_(opaque), c_params_(c_params), + q_params_(q_params), body_(std::move(body)) {} + + /** + * \brief Protected heap-allocated construction + */ + static ptr create(parser::Position pos, symbol id, bool opaque, + std::vector c_params, + std::vector q_params, + std::list>&& body) { + return std::make_unique(pos, id, opaque, c_params, q_params, + std::move(body)); + } + + /** + * \brief Whether the declaration is opaque + * + * \return true is the declaration is opaque + */ + bool is_opaque() { return opaque_; } + + /** + * \brief Get the classical parameter list + * + * \return Reference to the list of classical parameter names + */ + std::vector& c_params() { return c_params_; } + + /** + * \brief Get the quantum parameter list + * + * \return Reference to the list of quantum parameter names + */ + std::vector& q_params() { return q_params_; } + + /** + * \brief Get the gate body + * + * \return Reference to the body of the gate as a list of gate statements + */ + std::list>& body() { return body_; } + + /** + * \brief Apply a function to each statement of the gate + * + * \param f A void function taking a reference to a Gate + */ + void foreach_stmt(std::function f) { + for (auto it = body_.begin(); it != body_.end(); it++) { + f(**it); + } + } + + /** + * \brief Get an iterator to the beginning of the body + * + * \return std::list iterator + */ + std::list>::iterator begin() { return body_.begin(); } + + /** + * \brief Get an iterator to the end of the body + * + * \return std::list iterator + */ + std::list>::iterator end() { return body_.end(); } + + void accept(Visitor& visitor) override { visitor.visit(*this); } + std::ostream& pretty_print(std::ostream& os, + bool suppress_std) const override { + if (suppress_std && is_std_qelib(id_)) { + return os; + } + + os << (opaque_ ? "opaque " : "gate ") << id_; + if (c_params_.size() > 0) { + os << "("; + for (auto it = c_params_.begin(); it != c_params_.end(); it++) { + os << (it == c_params_.begin() ? "" : ",") << *it; + } + os << ")"; + } + os << " "; + for (auto it = q_params_.begin(); it != q_params_.end(); it++) { + os << (it == q_params_.begin() ? "" : ",") << *it; + } + if (opaque_) { + os << ";\n"; + } else { + os << " {\n"; + for (auto it = body_.begin(); it != body_.end(); it++) { + os << "\t" << **it; + } + os << "}\n"; + } + return os; + } + + protected: + GateDecl* clone() const override { + std::list> tmp; + for (auto it = body_.begin(); it != body_.end(); it++) { + tmp.emplace_back(object::clone(**it)); + } + return new GateDecl(pos_, id_, opaque_, c_params_, q_params_, + std::move(tmp)); + } +}; + +/** + * \class tools_v1::ast::OracleDecl + * \brief Class for oracle declarations + * \see tools_v1::ast::Decl + */ +class OracleDecl final : public Stmt, public Decl { + std::vector params_; ///< quantum parameters + symbol fname_; ///< filename of external declaration + + public: + /** + * \brief Constructs an oracle declaration + * + * \param pos The source position + * \param id The gate identifier + * \param params List of quantum parameters + * \param fname Filename defining the classical logic + */ + OracleDecl(parser::Position pos, symbol id, std::vector params, + symbol fname) + : Stmt(pos), Decl(id), params_(params), fname_(fname) {} + + /** + * \brief Protected heap-allocated construction + */ + static ptr create(parser::Position pos, symbol id, + std::vector params, symbol fname) { + return std::make_unique(pos, id, params, fname); + } + + /** + * \brief Get the parameter list + * + * \return Reference to the list of quantum parameter names + */ + std::vector& params() { return params_; } + + /** + * \brief Get the filename + * + * \return Constant reference to the filename + */ + const symbol& fname() { return fname_; } + + void accept(Visitor& visitor) override { visitor.visit(*this); } + std::ostream& pretty_print(std::ostream& os, bool) const override { + os << "oracle " << id_ << " "; + for (auto it = params_.begin(); it != params_.end(); it++) { + os << (it == params_.begin() ? "" : ",") << *it; + } + os << " { \"" << fname_ << "\" }\n"; + return os; + } + + protected: + OracleDecl* clone() const override { + return new OracleDecl(pos_, id_, params_, fname_); + } +}; + +/** + * \class tools_v1::ast::RegisterDecl + * \brief Class for register declarations + * \see tools_v1::ast::Decl + */ +class RegisterDecl final : public Stmt, public Decl { + bool quantum_; ///< whether the register is quantum + int size_; ///< the size of the register + + public: + /** + * \brief Constructs a register declaration + * + * \param pos The source position + * \param id The register identifier + * \param quantum whether the register is a quantum register + * \param size the size of the register + */ + RegisterDecl(parser::Position pos, symbol id, bool quantum, int size) + : Stmt(pos), Decl(id), quantum_(quantum), size_(size) {} + + /** + * \brief Protected heap-allocated construction + */ + static ptr create(parser::Position pos, symbol id, + bool quantum, int size) { + return std::make_unique(pos, id, quantum, size); + } + + /** + * \brief Whether the register is quantum or classical + * + * \return true if the register is quantum + */ + bool is_quantum() { return quantum_; } + + /** + * \brief Get the size of the register + * + * \return The size of the register + */ + int size() { return size_; } + + void accept(Visitor& visitor) override { visitor.visit(*this); } + std::ostream& pretty_print(std::ostream& os, bool) const override { + os << (quantum_ ? "qreg " : "creg ") << id_ << "[" << size_ << "];\n"; + return os; + } + + protected: + RegisterDecl* clone() const override { + return new RegisterDecl(pos_, id_, quantum_, size_); + } +}; + +/** + * \class tools_v1::ast::AncillaDecl + * \brief Class for local register declarations + * \see tools_v1::ast::Decl + */ +class AncillaDecl final : public Gate, public Decl { + bool dirty_; ///< whether the register can be dirty + int size_; ///< the size of the register + + public: + /** + * \brief Constructs a register declaration + * + * \param pos The source position + * \param id The register identifier + * \param dirty Whether the register is dirty + * \param size The size of the register + */ + AncillaDecl(parser::Position pos, symbol id, bool dirty, int size) + : Gate(pos), Decl(id), dirty_(dirty), size_(size) {} + + /** + * \brief Protected heap-allocated construction + */ + static ptr create(parser::Position pos, symbol id, bool dirty, + int size) { + return std::make_unique(pos, id, dirty, size); + } + + /** + * \brief Whether the register is dirty + * + * \return true if the register is dirty + */ + bool is_dirty() { return dirty_; } + + /** + * \brief Get the size of the register + * + * \return The size of the register + */ + int size() { return size_; } + + void accept(Visitor& visitor) override { visitor.visit(*this); } + std::ostream& pretty_print(std::ostream& os, bool) const override { + if (dirty_) { + os << "dirty "; + } + os << "ancilla " << id_ << "[" << size_ << "];\n"; + return os; + } + + protected: + AncillaDecl* clone() const override { + return new AncillaDecl(pos_, id_, dirty_, size_); + } +}; + +} /* namespace ast */ +} /* namespace tools_v1 */ + +#endif /* TOOLS_V1_AST_DECL_HPP_ */ diff --git a/experimental/include/tools_v1/ast/expr.hpp b/experimental/include/tools_v1/ast/expr.hpp new file mode 100644 index 00000000..cd6bf8f0 --- /dev/null +++ b/experimental/include/tools_v1/ast/expr.hpp @@ -0,0 +1,550 @@ +/* + * This file is part of tools_v1. + * + * Copyright (c) 2019 - 2025 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 tools_v1/ast/expr.hpp + * \brief OpenQASM expressions + */ + +#ifndef TOOLS_V1_AST_EXPR_HPP_ +#define TOOLS_V1_AST_EXPR_HPP_ + +#include +#include + +#include "../utils/angle.hpp" +#include "base.hpp" + +namespace tools_v1 { +namespace ast { + +/** + * \brief Enum of binary operators + */ +enum class BinaryOp { Plus, Minus, Times, Divide, Pow }; +inline std::ostream& operator<<(std::ostream& os, const BinaryOp& bop) { + switch (bop) { + case BinaryOp::Plus: + os << "+"; + break; + case BinaryOp::Minus: + os << "-"; + break; + case BinaryOp::Times: + os << "*"; + break; + case BinaryOp::Divide: + os << "/"; + break; + case BinaryOp::Pow: + os << "^"; + break; + } + return os; +} + +/** + * \brief Enum of unary operators + */ +enum class UnaryOp { Neg, Sin, Cos, Tan, Ln, Sqrt, Exp }; +inline std::ostream& operator<<(std::ostream& os, const UnaryOp& uop) { + switch (uop) { + case UnaryOp::Neg: + os << "-"; + break; + case UnaryOp::Sin: + os << "sin"; + break; + case UnaryOp::Cos: + os << "cos"; + break; + case UnaryOp::Tan: + os << "tan"; + break; + case UnaryOp::Ln: + os << "ln"; + break; + case UnaryOp::Sqrt: + os << "sqrt"; + break; + case UnaryOp::Exp: + os << "exp"; + break; + } + return os; +} + +/** + * \class tools_v1::ast::Expr + * \brief Base class for OpenQASM expressions + */ +class Expr : public ASTNode { + public: + Expr(parser::Position pos) : ASTNode(pos) {} + virtual ~Expr() = default; + + /** + * \brief Evaluate constant expressions + * + * All sub-classes must override this + * + * \return Returns the value of the expression if it + * is constant, or nullopt otherwise + */ + virtual std::optional constant_eval() const = 0; + + /** + * \brief Internal pretty-printer with associative context + * + * \param ctx Whether the current associative context is ambiguous + */ + virtual std::ostream& pretty_print(std::ostream& os, bool ctx) const = 0; + std::ostream& pretty_print(std::ostream& os) const override { + return pretty_print(os, false); + } + + protected: + virtual Expr* clone() const override = 0; +}; + +/** + * \class tools_v1::ast::BExpr + * \brief Class for binary operator expressions + * \see tools_v1::ast::Expr + */ +class BExpr final : public Expr { + ptr lexp_; ///< the left sub-expression + BinaryOp op_; ///< the binary operator + ptr rexp_; ///< the right sub-expression + + public: + /** + * \brief Constructs a Binary expression + * + * \param pos The source position + * \param lexp The left sub-expression + * \param op The binary operator + * \param rexp The right sub-expression + */ + BExpr(parser::Position pos, ptr lexp, BinaryOp op, ptr rexp) + : Expr(pos), lexp_(std::move(lexp)), op_(op), rexp_(std::move(rexp)) {} + + /** + * \brief Protected heap-allocated construction + */ + static ptr create(parser::Position pos, ptr lexp, BinaryOp op, + ptr rexp) { + return std::make_unique(pos, std::move(lexp), op, + std::move(rexp)); + } + + /** + * \brief Get the binary operator + * + * \return A binary operator enum + */ + BinaryOp op() const { return op_; } + + /** + * \brief Get the left sub-expression + * + * \return A reference to the left sub-expression + */ + Expr& lexp() { return *lexp_; } + + /** + * \brief Get the right sub-expression + * + * \return A reference to the right sub-expression + */ + Expr& rexp() { return *rexp_; } + + /** + * \brief Set the left sub-expression + * + * \param exp The new left sub-expression + */ + void set_lexp(ptr exp) { lexp_ = std::move(exp); } + + /** + * \brief Set the right sub-expression + * + * \param exp The new right sub-expression + */ + void set_rexp(ptr exp) { rexp_ = std::move(exp); } + + std::optional constant_eval() const override { + auto lexp = lexp_->constant_eval(); + auto rexp = rexp_->constant_eval(); + + if (!lexp || !rexp) { + return std::nullopt; + } + + switch (op_) { + case BinaryOp::Plus: + return *lexp + *rexp; + case BinaryOp::Minus: + return *lexp - *rexp; + case BinaryOp::Times: + return *lexp * *rexp; + case BinaryOp::Divide: + return *lexp / *rexp; + case BinaryOp::Pow: + return std::pow(*lexp, *rexp); + default: + return 0; // inaccessible + } + } + void accept(Visitor& visitor) override { visitor.visit(*this); } + std::ostream& pretty_print(std::ostream& os, bool ctx) const override { + if (ctx) { + os << "("; + lexp_->pretty_print(os, true); + os << op_; + rexp_->pretty_print(os, true); + os << ")"; + } else { + lexp_->pretty_print(os, true); + os << op_; + rexp_->pretty_print(os, true); + } + + return os; + } + + protected: + BExpr* clone() const override { + return new BExpr(pos_, object::clone(*lexp_), op_, + object::clone(*rexp_)); + } +}; + +/** + * \class tools_v1::ast::UExpr + * \brief Class for unary operator expressions + * \see tools_v1::ast::Expr + */ +class UExpr final : public Expr { + UnaryOp op_; ///< the unary operator + ptr exp_; ///< the sub-expression + + public: + /** + * \brief Constructs a Unary expression + * + * \param pos The source position + * \param op The unary operator + * \param exp The sub-expression + */ + UExpr(parser::Position pos, UnaryOp op, ptr exp) + : Expr(pos), op_(op), exp_(std::move(exp)) {} + + /** + * \brief Protected heap-allocated construction + */ + static ptr create(parser::Position pos, UnaryOp op, ptr exp) { + return std::make_unique(pos, op, std::move(exp)); + } + + /** + * \brief Get the operator + * + * \return A unary operator enum + */ + UnaryOp op() const { return op_; } + + /** + * \brief Get the sub-expression + * + * \return A reference to the sub-expression + */ + Expr& subexp() { return *exp_; } + + /** + * \brief Set the sub-expression + * + * \param exp The new sub-expression + */ + void set_subexp(ptr exp) { exp_ = std::move(exp); } + + std::optional constant_eval() const override { + auto expr = exp_->constant_eval(); + + if (!expr) { + return std::nullopt; + } + + switch (op_) { + case UnaryOp::Neg: + return -(*expr); + case UnaryOp::Sin: + return std::sin(*expr); + case UnaryOp::Cos: + return std::cos(*expr); + case UnaryOp::Tan: + return std::tan(*expr); + case UnaryOp::Sqrt: + return std::sqrt(*expr); + case UnaryOp::Ln: + return std::log(*expr); + case UnaryOp::Exp: + return std::exp(*expr); + default: + return 0; // inaccessible + } + } + void accept(Visitor& visitor) override { visitor.visit(*this); } + std::ostream& pretty_print(std::ostream& os, bool ctx) const override { + (void)ctx; + + os << op_; + if (op_ == UnaryOp::Neg) { + exp_->pretty_print(os, true); + } else { + os << "("; + exp_->pretty_print(os, false); + os << ")"; + } + + return os; + } + + protected: + UExpr* clone() const override { + return new UExpr(pos_, op_, object::clone(*exp_)); + } +}; + +/** + * \class tools_v1::ast::PiExpr + * \brief Class for pi constants + * \see tools_v1::ast::Expr + */ +class PiExpr final : public Expr { + + public: + /** + * \brief Construct a Pi expression + * + * \param pos The source position + */ + PiExpr(parser::Position pos) : Expr(pos) {} + + /** + * \brief Protected heap-allocated construction + */ + static ptr create(parser::Position pos) { + return std::make_unique(pos); + } + + std::optional constant_eval() const override { return utils::pi; } + void accept(Visitor& visitor) override { visitor.visit(*this); } + std::ostream& pretty_print(std::ostream& os, bool ctx) const override { + (void)ctx; + + os << "pi"; + return os; + } + + protected: + PiExpr* clone() const override { return new PiExpr(pos_); } +}; + +/** + * \class tools_v1::ast::IntExpr + * \brief Class for integer literal expressions + * \see tools_v1::ast::Expr + */ +class IntExpr final : public Expr { + int value_; ///< the integer value + + public: + /** + * \brief Construct an integer expression + * + * \param pos The source position + * \param val The integer value + */ + IntExpr(parser::Position pos, int value) : Expr(pos), value_(value) {} + + /** + * \brief Protected heap-allocated construction + */ + static ptr create(parser::Position pos, int value) { + return std::make_unique(pos, value); + } + + /** + * \brief Get the integer value + * + * \return The integer value + */ + int value() const { return value_; } + + std::optional constant_eval() const override { + return (double)value_; + } + void accept(Visitor& visitor) override { visitor.visit(*this); } + std::ostream& pretty_print(std::ostream& os, bool ctx) const override { + (void)ctx; + + os << value_; + return os; + } + + protected: + IntExpr* clone() const override { return new IntExpr(pos_, value_); } +}; + +/** + * \class tools_v1::ast::RealExpr + * \brief Class for floating point literal expressions + * \see tools_v1::ast::Expr + */ +class RealExpr final : public Expr { + double value_; ///< the floating point value + + public: + /** + * \brief Construct a real-value expression + * + * \param pos The source position + * \param val The floating point value + */ + RealExpr(parser::Position pos, double value) : Expr(pos), value_(value) {} + + /** + * \brief Protected heap-allocated construction + */ + static ptr create(parser::Position pos, double value) { + return std::make_unique(pos, value); + } + + /** + * \brief Get the real value + * + * \return The floating point value + */ + double value() const { return value_; } + + std::optional constant_eval() const override { return value_; } + void accept(Visitor& visitor) override { visitor.visit(*this); } + std::ostream& pretty_print(std::ostream& os, bool ctx) const override { + (void)ctx; + + std::streamsize ss = os.precision(); + os << std::setprecision(15) << value_ << std::setprecision(ss); + return os; + } + + protected: + RealExpr* clone() const override { return new RealExpr(pos_, value_); } +}; + +/** + * \class tools_v1::ast::VarExpr + * \brief Class for variable expressions + * \see tools_v1::ast::Expr + */ +class VarExpr final : public Expr { + symbol var_; ///< the identifier + + public: + /** + * \brief Construct a variable expression + * + * \param pos The source position + * \param var The variable name + */ + VarExpr(parser::Position pos, symbol var) : Expr(pos), var_(var) {} + + /** + * \brief Protected heap-allocated construction + */ + static ptr create(parser::Position pos, symbol var) { + return std::make_unique(pos, var); + } + + /** + * \brief Get the variable name + * + * \return Constant reference to the name + */ + const symbol& var() const { return var_; } + + std::optional constant_eval() const override { + return std::nullopt; + } + void accept(Visitor& visitor) override { visitor.visit(*this); } + std::ostream& pretty_print(std::ostream& os, bool ctx) const override { + (void)ctx; + os << var_; + return os; + } + + protected: + VarExpr* clone() const override { return new VarExpr(pos_, var_); } +}; + +/** + * \brief Returns an Expr representing the given angle + * + * \param theta The angle + * \return The equivalent QASM expression + */ +inline ptr angle_to_expr(const utils::Angle& theta) { + parser::Position pos; + + if (theta.is_symbolic()) { + // Angle is of the form pi*(a/b) for a & b integers + auto [a, b] = *(theta.symbolic_value()); + + if (a == 0) { + return std::make_unique(IntExpr(pos, 0)); + } else if (a == 1) { + return std::make_unique( + pos, std::make_unique(PiExpr(pos)), BinaryOp::Divide, + std::make_unique(IntExpr(pos, b))); + } else { + auto subexpr = std::make_unique( + pos, std::make_unique(PiExpr(pos)), BinaryOp::Times, + std::make_unique(IntExpr(pos, a))); + + return std::make_unique( + pos, std::move(subexpr), BinaryOp::Divide, + std::make_unique(IntExpr(pos, b))); + } + } else { + // Angle is real-valued + return std::make_unique(RealExpr(pos, theta.numeric_value())); + } +} + +} /* namespace ast */ +} /* namespace tools_v1 */ + +#endif /* TOOLS_V1_AST_EXPR_HPP_ */ diff --git a/experimental/include/tools_v1/ast/gate_builder.hpp b/experimental/include/tools_v1/ast/gate_builder.hpp new file mode 100644 index 00000000..94bec60f --- /dev/null +++ b/experimental/include/tools_v1/ast/gate_builder.hpp @@ -0,0 +1,68 @@ +#ifndef GATE_BUILDER_HPP_ +#define GATE_BUILDER_HPP_ + +#include "base.hpp" +#include "expr.hpp" +#include "stmt.hpp" +#include + +namespace tools_v1::ast { + +namespace PrimitiveGate { +enum Type { + PAULI_STRING, + CONTROL, + MULTI_CONTROL, + EXP_PAULI, + DECLARED_GATE, + CNOT, + HADAMARD +}; + +PauliType pauli_from_string(const std::string &str); +} // namespace PrimitiveGate + +template class GateBuilder { +private: + T data; + PrimitiveGate::Type current_type; + std::vector qubits; + std::vector paulis; + ptr target_gate; + ptr angle; + tools_v1::parser::Position pos; + + ptr build_pauli_string(); + ptr build_control_gate(); + ptr build_exp_pauli(); + +public: + GateBuilder() : pos() {} + GateBuilder &operator+=(PrimitiveGate::Type gate_type); + GateBuilder &operator,(const VarAccess &qubit); + GateBuilder &operator,(const std::string &qubit_name); + GateBuilder &operator,(PauliType pauli); + GateBuilder &operator,(double angle_value); + GateBuilder &operator,(ptr expr); + GateBuilder &operator,(PrimitiveGate::Type nested_gate_type); + T submit(); + void reset(); +}; + +// Specialization for vector of gates +class GateVectorBuilder : public GateBuilder>> { +private: + std::vector> gates; + +public: + GateVectorBuilder &operator+=(PrimitiveGate::Type gate_type); + GateVectorBuilder &operator,(PrimitiveGate::Type next_gate_type); + std::vector> submit(); +}; + +GateVectorBuilder gates(); +GateBuilder> gate(); + +} // namespace tools_v1::ast + +#endif diff --git a/experimental/include/tools_v1/ast/gate_builder_simple.hpp b/experimental/include/tools_v1/ast/gate_builder_simple.hpp new file mode 100644 index 00000000..041333d9 --- /dev/null +++ b/experimental/include/tools_v1/ast/gate_builder_simple.hpp @@ -0,0 +1,269 @@ +#ifndef GATE_BUILDER_SIMPLE_HPP_ +#define GATE_BUILDER_SIMPLE_HPP_ + +#include "base.hpp" +#include "control_gate.hpp" +#include "expr.hpp" +#include "stmt.hpp" +#include +#include +#include +#include + +namespace tools_v1::ast { + +namespace PrimitiveGate { +enum Type { + PAULI_STRING, + CONTROL, + MULTI_CONTROL, + EXP_PAULI, + DECLARED_GATE, + CNOT, + HADAMARD +}; + +inline PauliType pauli_from_string(const std::string &str) { + if (str == "X" || str == "x") + return PauliType::X; + if (str == "Y" || str == "y") + return PauliType::Y; + if (str == "Z" || str == "z") + return PauliType::Z; + if (str == "I" || str == "i") + return PauliType::I; + throw std::invalid_argument("Invalid Pauli string: " + str); +} +} // namespace PrimitiveGate + +class GateBuilder { +private: + std::vector> gates; + std::list> stmts; + PrimitiveGate::Type current_type; + std::vector qubits; + std::vector paulis; + ptr target_gate; + ptr angle; + tools_v1::parser::Position pos; + + // For control gates + bool building_nested_gate = false; + + // For MultiControlGate: track which qubits go to ctrl1 vs ctrl2 + bool after_separator = false; + std::vector ctrl1_qubits; + std::vector ctrl2_qubits; + + void build_and_add_gate() { + ptr built_gate; + + switch (current_type) { + case PrimitiveGate::PAULI_STRING: + if (qubits.size() != paulis.size()) { + throw std::runtime_error( + "PauliString requires equal number of qubits and Pauli operators"); + } + built_gate = + PauliString::create(pos, std::move(qubits), std::move(paulis)); + break; + case PrimitiveGate::EXP_PAULI: + if (qubits.size() != paulis.size()) { + throw std::runtime_error( + "ExpPauli requires equal number of qubits and Pauli operators"); + } + if (!angle) { + throw std::runtime_error("ExpPauli requires an angle expression"); + } + built_gate = ExpPauli::create(pos, std::move(angle), std::move(qubits), + std::move(paulis)); + break; + case PrimitiveGate::CNOT: + if (qubits.size() != 2) { + throw std::runtime_error( + "ControlGate requires exactly one control qubit"); + } + built_gate = + CNOTGate::create(pos, std::move(qubits[0]), std::move(qubits[1])); + break; + case PrimitiveGate::CONTROL: + if (qubits.size() != 1) { + throw std::runtime_error( + "ControlGate requires exactly one control qubit"); + } + if (!target_gate) { + throw std::runtime_error("ControlGate requires a target gate"); + } + built_gate = ControlGate::create(pos, std::move(qubits[0]), + std::move(target_gate)); + break; + case PrimitiveGate::MULTI_CONTROL: + if (!target_gate) { + throw std::runtime_error("MultiControlGate requires a target gate"); + } + // For MultiControlGate, use the separated ctrl1 and ctrl2 qubits + built_gate = MultiControlGate::create(pos, std::move(ctrl1_qubits), + std::move(ctrl2_qubits), + std::move(target_gate)); + break; + default: + throw std::runtime_error("Gate type not yet implemented"); + } + + if (built_gate) { + gates.push_back(std::move(built_gate)); + } + + // Reset for next gate + qubits.clear(); + paulis.clear(); + target_gate.reset(); + angle.reset(); + building_nested_gate = false; + after_separator = false; + ctrl1_qubits.clear(); + ctrl2_qubits.clear(); + } + +public: + GateBuilder() : pos() {} + // GateBuilder(PrimitiveGate::Type type) : pos(), current_type(type) {} + + GateBuilder &operator+=(PrimitiveGate::Type gate_type) { + if (!qubits.empty() || !paulis.empty() || target_gate) { + build_and_add_gate(); + } + current_type = gate_type; + return *this; + } + + GateBuilder &operator[](PrimitiveGate::Type gate_type) { + assert(qubits.empty() && paulis.empty() && after_separator == false); + current_type = gate_type; + return *this; + } + + // ExpPauli ONLY + GateBuilder &operator,(const std::string &str) { + // FIX: uncomment in the future + // assert(current_type == PrimitiveGate::EXP_PAULI); + if (str == "pi/4" || str == "π/4") { + angle = BExpr::create(pos, PiExpr::create(pos), BinaryOp::Divide, RealExpr::create(pos, 4.0)); + } else if (str == "pi/2" || str == "π/2") { + angle = BExpr::create(pos, PiExpr::create(pos), BinaryOp::Divide, RealExpr::create(pos, 2.0)); + } else if (str == "pi" || str == "π") { + angle = PiExpr::create(pos); + } else { + paulis.push_back(PrimitiveGate::pauli_from_string(str)); + } + return *this; + } + + // ExpPauli ONLY + GateBuilder &operator,(double angle_value) { + // FIX: uncomment in the future + // assert(current_type == PrimitiveGate::EXP_PAULI); + angle = RealExpr::create(pos, angle_value); + return *this; + } + + // Pauli or ExpPauli + GateBuilder &operator,(PauliType pauli) { + paulis.push_back(pauli); + return *this; + } + + // original implementation, kept for backward compatibility + GateBuilder &operator,(ptr gate) { + if (current_type == PrimitiveGate::CONTROL || + current_type == PrimitiveGate::MULTI_CONTROL) { + target_gate = std::move(gate); + } + return *this; + } + + + GateBuilder &operator*(PauliType pauli) { + assert(current_type == PrimitiveGate::PAULI_STRING || + current_type == PrimitiveGate::EXP_PAULI); + paulis.push_back(pauli); + return *this; + } + + + GateBuilder &operator*(const VarAccess &qubit) { + // assert(current_type == PrimitiveGate::CONTROL || + // current_type == PrimitiveGate::MULTI_CONTROL); + if (current_type == PrimitiveGate::MULTI_CONTROL) { + if (after_separator) { + ctrl2_qubits.push_back(qubit); + } else { + ctrl1_qubits.push_back(qubit); + } + } else { + qubits.push_back(qubit); + } + return *this; + } + + GateBuilder &operator*(ptr gate) { + assert(current_type == PrimitiveGate::CONTROL || + current_type == PrimitiveGate::MULTI_CONTROL); + target_gate = std::move(gate); + return *this; + } + + GateBuilder &operator%(ptr gate) { + assert(current_type == PrimitiveGate::CONTROL || + current_type == PrimitiveGate::MULTI_CONTROL); + target_gate = std::move(gate); + return *this; + } + + GateBuilder &operator/(const VarAccess &qubit) { + assert(current_type == PrimitiveGate::MULTI_CONTROL); + assert(after_separator == false); + after_separator = true; + ctrl2_qubits.push_back(qubit); + return *this; + } + + GateBuilder &operator/(ptr gate) { + assert(current_type == PrimitiveGate::MULTI_CONTROL); + assert(after_separator == false); + after_separator = true; + target_gate = std::move(gate); + return *this; + } + + GateBuilder &separate() { + if (current_type == PrimitiveGate::MULTI_CONTROL) { + after_separator = true; + } + return *this; + } + + std::vector> submit() { + // Finish current gate if any + if (!qubits.empty() || !paulis.empty() || target_gate) { + build_and_add_gate(); + } + return std::move(gates); + } + + std::list> submit_list() { + auto vec_gates = submit(); + std::list> lst; + std::transform(vec_gates.begin(), vec_gates.end(), std::back_inserter(lst), + [](const ptr &gate) -> ptr { + return object::clone(*gate); + }); + return lst; + } +}; + +inline GateBuilder gates() { return GateBuilder{}; } + +} // namespace tools_v1::ast + +#endif diff --git a/experimental/include/tools_v1/ast/program.hpp b/experimental/include/tools_v1/ast/program.hpp new file mode 100644 index 00000000..60ad326e --- /dev/null +++ b/experimental/include/tools_v1/ast/program.hpp @@ -0,0 +1,148 @@ +/* + * This file is part of tools_v1. + * + * Copyright (c) 2019 - 2025 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 tools_v1/ast/program.hpp + * \brief OpenQASM programs + */ + +#ifndef TOOLS_V1_AST_PROGRAM_HPP_ +#define TOOLS_V1_AST_PROGRAM_HPP_ + +#include "decl.hpp" + +namespace tools_v1 { +namespace ast { + +/** + * \class tools_v1::ast::Program + * \brief Program class + */ +class Program : public ASTNode { + bool std_include_; ///< whether the program includes qelib1 + std::list> body_; ///< the body of the program + int bits_ = 0; ///< number of bits + int qubits_ = 0; ///< number of qubits + + public: + /** + * \brief Constructs a QASM program + * + * \param pos The source position + * \param std_include Whether the standard library has been included + * \param body The program body + * \param bits The number of bits + * \param qubits The number of qubits + */ + Program(parser::Position pos, bool std_include, std::list>&& body, + int bits, int qubits) + : ASTNode(pos), std_include_(std_include), body_(std::move(body)), + bits_(bits), qubits_(qubits) {} + + /** + * \brief Protected heap-allocated construction + */ + static ptr create(parser::Position pos, bool std_include, + std::list>&& body, int bits, + int qubits) { + return std::make_unique(pos, std_include, std::move(body), + bits, qubits); + } + + /** + * \brief Get the program body + * + * \return Reference to the body as a list of statements + */ + std::list>& body() { return body_; } + + /** + * \brief Get the number of bits + * + * \return The number of bits + */ + int bits() { return bits_; } + + /** + * \brief Get the number of qubits + * + * \return The number of qubits + */ + int qubits() { return qubits_; } + + /** + * \brief Apply a function to each statement in order + * + * \param f Void function accepting a reference to a statement + */ + void foreach_stmt(std::function f) { + for (auto it = body_.begin(); it != body_.end(); it++) { + f(**it); + } + } + + /** + * \brief Get an iterator to the beginning of the body + * + * \return std::list iterator + */ + std::list>::iterator begin() { return body_.begin(); } + + /** + * \brief Get an iterator to the end of the body + * + * \return std::list iterator + */ + std::list>::iterator end() { return body_.end(); } + + void accept(Visitor& visitor) override { visitor.visit(*this); } + std::ostream& pretty_print(std::ostream& os) const override { + os << "OPENQASM 2.0;\n"; + if (std_include_) { + os << "include \"qelib1.inc\";\n"; + } + os << "\n"; + for (auto it = body_.begin(); it != body_.end(); it++) { + (*it)->pretty_print(os, std_include_); + } + + return os; + } + + protected: + Program* clone() const override { + std::list> tmp; + for (auto it = body_.begin(); it != body_.end(); it++) { + tmp.emplace_back(object::clone(**it)); + } + return new Program(pos_, std_include_, std::move(tmp), bits_, qubits_); + } +}; + +} /* namespace ast */ +} /* namespace tools_v1 */ + +#endif /* TOOLS_V1_AST_PROGRAM_HPP_ */ diff --git a/experimental/include/tools_v1/ast/replacer.hpp b/experimental/include/tools_v1/ast/replacer.hpp new file mode 100644 index 00000000..47fb8d4d --- /dev/null +++ b/experimental/include/tools_v1/ast/replacer.hpp @@ -0,0 +1,402 @@ +/* + * This file is part of tools_v1. + * + * Copyright (c) 2019 - 2025 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 tools_v1/ast/replacer.hpp + * \brief Node replacement for syntax trees + */ + +#ifndef TOOLS_V1_AST_REPLACER_HPP_ +#define TOOLS_V1_AST_REPLACER_HPP_ + +#include + +#include "program.hpp" +#include "visitor.hpp" + +namespace tools_v1 { +namespace ast { +/** + * \class tools_v1::ast::Replacer + * \brief Generic complete traversal with post-order node replacement + * \see tools_v1::ast::Visitor + * + * The replacer provides a visitor-style interface where the visitor + * of a node optionally returns a node of the same base type (in the + * case of statements or gates, a possibly empty list of statements or + * gates). The visitor logic then replaces the visited node with the + * returned node, if non-empty, or otherwise leaves the node unchanged. + * + * Standard usage is to derive from Replacer and override the replace + * methods only for the relevant nodes. The replace method is called + * post-order -- after visiting all children -- and overriding a replace + * method does not kill traversal to the node's children. To stop + * descending into the children of a node, the node's visit overload + * can be overridden. + */ +class Replacer : public Visitor { + std::optional replacement_var_; + std::optional> replacement_expr_; + std::optional>> replacement_stmts_; + std::optional>> replacement_gates_; + + public: + // Variables + virtual std::optional replace(VarAccess&) { + return std::nullopt; + } + // Expressions + virtual std::optional> replace(BExpr&) { return std::nullopt; } + virtual std::optional> replace(UExpr&) { return std::nullopt; } + virtual std::optional> replace(PiExpr&) { return std::nullopt; } + virtual std::optional> replace(IntExpr&) { return std::nullopt; } + virtual std::optional> replace(RealExpr&) { return std::nullopt; } + virtual std::optional> replace(VarExpr&) { return std::nullopt; } + // Statements + virtual std::optional>> replace(MeasureStmt&) { + return std::nullopt; + } + virtual std::optional>> replace(ResetStmt&) { + return std::nullopt; + } + virtual std::optional>> replace(IfStmt&) { + return std::nullopt; + } + // Gates + virtual std::optional>> replace(UGate&) { + return std::nullopt; + } + virtual std::optional>> replace(CNOTGate&) { + return std::nullopt; + } + virtual std::optional>> replace(BarrierGate&) { + return std::nullopt; + } + virtual std::optional>> replace(DeclaredGate&) { + return std::nullopt; + } + // Declarations + virtual std::optional>> replace(GateDecl&) { + return std::nullopt; + } + virtual std::optional>> replace(OracleDecl&) { + return std::nullopt; + } + virtual std::optional>> replace(RegisterDecl&) { + return std::nullopt; + } + virtual std::optional>> replace(AncillaDecl&) { + return std::nullopt; + } + + /* Visitor overrides */ + void visit(VarAccess& var) override { replacement_var_ = replace(var); } + + void visit(BExpr& expr) override { + expr.lexp().accept(*this); + if (replacement_expr_) { + expr.set_lexp(std::move(*replacement_expr_)); + replacement_expr_ = std::nullopt; + } + + expr.rexp().accept(*this); + if (replacement_expr_) { + expr.set_rexp(std::move(*replacement_expr_)); + replacement_expr_ = std::nullopt; + } + + replacement_expr_ = replace(expr); + } + + void visit(UExpr& expr) override { + expr.subexp().accept(*this); + if (replacement_expr_) { + expr.set_subexp(std::move(*replacement_expr_)); + replacement_expr_ = std::nullopt; + } + + replacement_expr_ = replace(expr); + } + + void visit(PiExpr& expr) override { replacement_expr_ = replace(expr); } + void visit(IntExpr& expr) override { replacement_expr_ = replace(expr); } + void visit(RealExpr& expr) override { replacement_expr_ = replace(expr); } + void visit(VarExpr& expr) override { replacement_expr_ = replace(expr); } + + void visit(MeasureStmt& stmt) override { + stmt.q_arg().accept(*this); + if (replacement_var_) { + stmt.set_qarg(std::move(*replacement_var_)); + replacement_var_ = std::nullopt; + } + + stmt.c_arg().accept(*this); + if (replacement_var_) { + stmt.set_carg(std::move(*replacement_var_)); + replacement_var_ = std::nullopt; + } + + replacement_stmts_ = replace(stmt); + } + + void visit(ResetStmt& stmt) override { + stmt.arg().accept(*this); + if (replacement_var_) { + stmt.set_arg(std::move(*replacement_var_)); + replacement_var_ = std::nullopt; + } + + replacement_stmts_ = replace(stmt); + } + + // Vanilla QASM only allows a single statement in + // the "then" branch, so we need to clone the statement + // for each gate in the result + void visit(IfStmt& stmt) override { + stmt.then().accept(*this); + if (replacement_stmts_) { + std::optional>> ret = std::nullopt; + for (auto& rep : *replacement_stmts_) { + auto tmp = object::clone(stmt); + tmp->set_then(std::move(rep)); + auto stmts = replace(*tmp); + if (!ret) { + ret = std::move(stmts); + } else if (stmts) { + ret->splice(ret->end(), *stmts); + } + } + replacement_stmts_ = std::move(ret); + } else if (replacement_gates_) { + std::optional>> ret = std::nullopt; + for (auto& rep : *replacement_gates_) { + auto tmp = object::clone(stmt); + tmp->set_then(std::move(rep)); + auto stmts = replace(*tmp); + if (!ret) { + ret = std::move(stmts); + } else if (stmts) { + ret->splice(ret->end(), *stmts); + } + } + replacement_gates_ = std::nullopt; + replacement_stmts_ = std::move(ret); + } else { + replacement_stmts_ = replace(stmt); + } + } + + void visit(UGate& gate) override { + gate.theta().accept(*this); + if (replacement_expr_) { + gate.set_theta(std::move(*replacement_expr_)); + replacement_expr_ = std::nullopt; + } + + gate.phi().accept(*this); + if (replacement_expr_) { + gate.set_phi(std::move(*replacement_expr_)); + replacement_expr_ = std::nullopt; + } + + gate.lambda().accept(*this); + if (replacement_expr_) { + gate.set_lambda(std::move(*replacement_expr_)); + replacement_expr_ = std::nullopt; + } + + gate.arg().accept(*this); + if (replacement_var_) { + gate.set_arg(std::move(*replacement_var_)); + replacement_var_ = std::nullopt; + } + + replacement_gates_ = replace(gate); + } + + void visit(CNOTGate& gate) override { + gate.ctrl().accept(*this); + if (replacement_var_) { + gate.set_ctrl(std::move(*replacement_var_)); + replacement_var_ = std::nullopt; + } + + gate.tgt().accept(*this); + if (replacement_var_) { + gate.set_tgt(std::move(*replacement_var_)); + replacement_var_ = std::nullopt; + } + + replacement_gates_ = replace(gate); + } + + void visit(BarrierGate& gate) override { + for (int i = 0; i < gate.num_args(); i++) { + gate.arg(i).accept(*this); + if (replacement_var_) { + gate.set_arg(i, std::move(*replacement_var_)); + replacement_var_ = std::nullopt; + } + } + + replacement_gates_ = replace(gate); + } + + void visit(DeclaredGate& gate) override { + for (int i = 0; i < gate.num_cargs(); i++) { + gate.carg(i).accept(*this); + if (replacement_expr_) { + gate.set_carg(i, std::move(*replacement_expr_)); + replacement_expr_ = std::nullopt; + } + } + + for (int i = 0; i < gate.num_qargs(); i++) { + gate.qarg(i).accept(*this); + if (replacement_var_) { + gate.set_qarg(i, std::move(*replacement_var_)); + replacement_var_ = std::nullopt; + } + } + + replacement_gates_ = replace(gate); + } + + void visit(GateDecl& decl) override { + auto it = decl.body().begin(); + + while (it != decl.body().end()) { + (**it).accept(*this); + if (replacement_gates_) { + it = decl.body().erase(it); + decl.body().splice(it, std::move(*replacement_gates_)); + replacement_gates_ = std::nullopt; + } else { + ++it; + } + } + + replacement_stmts_ = replace(decl); + } + + void visit(OracleDecl& decl) override { + replacement_stmts_ = replace(decl); + } + void visit(RegisterDecl& decl) override { + replacement_stmts_ = replace(decl); + } + + void visit(AncillaDecl& decl) override { + replacement_gates_ = replace(decl); + } + + void visit(Program& prog) override { + auto it = prog.body().begin(); + + while (it != prog.end()) { + (**it).accept(*this); + if (replacement_stmts_) { + it = prog.body().erase(it); + prog.body().splice(it, std::move(*replacement_stmts_)); + replacement_stmts_ = std::nullopt; + } else if (replacement_gates_) { + it = prog.body().erase(it); + for (auto ti = replacement_gates_->begin(); + ti != replacement_gates_->end(); ti++) { + prog.body().emplace(it, std::move(*ti)); + } + replacement_gates_ = std::nullopt; + } else { + ++it; + } + } + } +}; + +/** + * \class tools_v1::ast::GateReplacer + * \brief Bulk gate replacement + * \see tools_v1::ast::Replacer + * + * Implements bulk replacement of gates given by a hash map. Use the + * functional interface tools_v1::ast::replace_gates rather than + * the class. + */ +class GateReplacer final : public Replacer { + public: + GateReplacer(std::unordered_map>>&& replacements) + : replacements_(std::move(replacements)) {} + + std::optional>> replace(UGate& g) { + return replace_gate(g); + } + std::optional>> replace(CNOTGate& g) { + return replace_gate(g); + } + std::optional>> replace(BarrierGate& g) { + return replace_gate(g); + } + std::optional>> replace(DeclaredGate& g) { + return replace_gate(g); + } + + private: + std::unordered_map>> replacements_; + + std::optional>> replace_gate(Gate& gate) { + auto it = replacements_.find(gate.uid()); + + if (it != replacements_.end()) { + return std::move(it->second); + } else { + return std::nullopt; + } + } +}; + +/** + * \brief Replaces the specified gates within an AST + * + * Used to perform a list of gate replacements in one traversal. All keys in the + * hash map should refer to gates (U, CNOT, barrier or a declared gate). + * For replacement of other types of nodes, use the Replacer class. + * + * \param node Reference to the root of the AST in which replacement will take + * place + * \param replacements Hash map from gate UID's to a list of gates which + * should replace it + */ +inline void +replace_gates(ASTNode& node, + std::unordered_map>>&& replacements) { + GateReplacer replacer(std::move(replacements)); + node.accept(replacer); +} + +} /* namespace ast */ +} /* namespace tools_v1 */ + +#endif /* TOOLS_V1_AST_REPLACER_HPP_ */ diff --git a/experimental/include/tools_v1/ast/semantic.hpp b/experimental/include/tools_v1/ast/semantic.hpp new file mode 100644 index 00000000..b123e861 --- /dev/null +++ b/experimental/include/tools_v1/ast/semantic.hpp @@ -0,0 +1,504 @@ +/* + * This file is part of tools_v1. + * + * Copyright (c) 2019 - 2025 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 tools_v1/ast/semantic.hpp + * \brief Semantic analysis for syntax trees + */ + +#ifndef TOOLS_V1_AST_SEMANTIC_HPP_ +#define TOOLS_V1_AST_SEMANTIC_HPP_ + +#include +#include +#include +#include + +#include "ast.hpp" +#include "control_gate.hpp" +#include "visitor.hpp" + +namespace tools_v1 { +namespace ast { + +/** + * \class tools_v1::ast::SemanticError + * \brief Exception class for semantic errors + */ +class SemanticError : public std::exception { +public: + SemanticError() noexcept = default; + ~SemanticError() = default; + const char *what() const noexcept { return "Error(s) occurred"; } +}; + +/** + * \class tools_v1::ast::BitType + * \brief Enum for types of bits + */ +enum class BitType { Cbit, Qubit }; + +/** + * \struct tools_v1::ast::GateType + * \brief Data struct for gate types + */ +struct GateType { + int num_c_params; + int num_q_params; +}; + +/** + * \struct tools_v1::ast::RegisterType + * \brief Data struct for register types + */ +struct RegisterType { + BitType type; + int length; +}; + +/** + * \struct tools_v1::ast::RealType + * \brief Empty structure denoting a real type + */ +struct RealType {}; + +/** + * \brief OpenQASM types as a std::variant + * + * Functional-style syntax trees in C++17 as a simpler alternative + * to inheritance hierarchy. Support is still lacking for large-scale. + */ +using Type = std::variant; + +/** + * \class tools_v1::ast::SemanticChecker + * \brief Implementation of the semantic analysis compiler phase + * \see tools_v1::ast::Visitor + * + * Checks for anything that could cause a run-time error -- notably, + * type errors, invalid uniform gates, etc. Use the functional + * interface tools_v1::ast::check_source instead. + */ +class SemanticChecker final : public Visitor { +public: + bool run(Program &prog) { + prog.accept(*this); + return error_; + } + + void visit(VarAccess &) {} + + void visit(BExpr &expr) { + expr.lexp().accept(*this); + expr.rexp().accept(*this); + } + void visit(UExpr &expr) { expr.subexp().accept(*this); } + void visit(PiExpr &) {} + void visit(IntExpr &) {} + void visit(RealExpr &) {} + void visit(VarExpr &expr) { + auto entry = lookup(expr.var()); + + if (!entry) { + std::cerr << expr.pos() << ": Identifier \"" << expr.var() + << "\" undeclared\n"; + error_ = true; + } else if (!std::holds_alternative(*entry)) { + std::cerr << expr.pos() << ": Identifier \"" << expr.var(); + std::cerr << "\" does not have numeric type\n"; + error_ = true; + } + } + + void visit(MeasureStmt &stmt) { + check_uniform({stmt.q_arg(), stmt.c_arg()}, + {BitType::Qubit, BitType::Cbit}); + } + void visit(ResetStmt &stmt) { check_uniform({stmt.arg()}, {BitType::Qubit}); } + void visit(IfStmt &stmt) { + auto entry = lookup(stmt.var()); + + if (!entry) { + std::cerr << stmt.pos() << ": Identifier \"" << stmt.var() + << "\" undeclared\n"; + error_ = true; + } else if (!std::holds_alternative(*entry) || + !(std::get(*entry).type == BitType::Cbit)) { + std::cerr << stmt.pos() << ": Identifier \"" << stmt.var(); + std::cerr << "\" does not have classical register type\n"; + error_ = true; + } else { + stmt.then().accept(*this); + } + } + + void visit(UGate &gate) { + gate.theta().accept(*this); + gate.phi().accept(*this); + gate.lambda().accept(*this); + + check_uniform({gate.arg()}, {BitType::Qubit}); + } + void visit(CNOTGate &gate) { + check_uniform({gate.ctrl(), gate.tgt()}, {BitType::Qubit, BitType::Qubit}); + } + void visit(BarrierGate &gate) { + std::vector> types(gate.args().size(), std::nullopt); + check_uniform(gate.args(), types); + } + void visit(DeclaredGate &gate) { + auto entry = lookup(gate.name()); + + if (!entry) { + std::cerr << gate.pos() << ": Gate \"" << gate.name() + << "\" undeclared\n"; + error_ = true; + } else if (std::holds_alternative(*entry)) { + auto ty = std::get(*entry); + if (ty.num_c_params != gate.num_cargs()) { + std::cerr << gate.pos() << ": Gate \"" << gate.name() << "\" expects " + << ty.num_c_params; + std::cerr << " classical arguments, got " << gate.num_cargs() << "\n"; + error_ = true; + } else if (ty.num_q_params != gate.num_qargs()) { + std::cerr << gate.pos() << ": Gate \"" << gate.name() << "\" expects " + << ty.num_q_params; + std::cerr << " quantum arguments, got " << gate.num_qargs() << "\n"; + error_ = true; + } else { + gate.foreach_carg([this](Expr &expr) { expr.accept(*this); }); + + std::vector> types(ty.num_q_params, + BitType::Qubit); + check_uniform(gate.qargs(), types); + } + } else { + std::cerr << gate.pos() << ": Identifier \"" << gate.name() + << "\" is not a gate\n"; + error_ = true; + } + } + + void visit(ControlGate &gate) { + // Check control qubit is valid + check_uniform({gate.ctrl()}, {BitType::Qubit}); + + // Check target gate is valid + gate.target_gate().accept(*this); + + // For primitive gates, we can do additional validation + // For now, we rely on the target gate's own validation + } + + void visit(MultiControlGate &gate) { + // Check control qubits is valid + check_uniform(gate.ctrl1(), std::vector>(gate.ctrl1().size(), BitType::Qubit)); + check_uniform(gate.ctrl2(), std::vector>(gate.ctrl2().size(), BitType::Qubit)); + + // Check target gate is valid + gate.target_gate().accept(*this); + + // For primitive gates, we can do additional validation + // For now, we rely on the target gate's own validation + } + + + void visit(PauliString &gate) { + // Check all quantum arguments are valid qubits + std::vector> types(gate.num_qargs(), BitType::Qubit); + check_uniform(gate.qargs(), types); + } + + void visit(PhaseGate &gate) { + // Check angle expression + gate.angle().accept(*this); + + // Check all quantum arguments are valid qubits + std::vector> types(gate.num_qargs(), BitType::Qubit); + check_uniform(gate.qargs(), types); + } + + void visit(ExpPauli &gate) { + // Check angle expression + gate.angle().accept(*this); + + // Check all quantum arguments are valid qubits + std::vector> types(gate.num_qargs(), BitType::Qubit); + check_uniform(gate.qargs(), types); + } + + void visit(GateDecl &decl) { + if (lookup_local(decl.id())) { + std::cerr << decl.pos() << ": Identifier \"" << decl.id() + << "\" previously declared\n"; + error_ = true; + } else { + // Check the body + push_scope(); + for (const ast::symbol ¶m : decl.c_params()) { + set(param, RealType{}); + } + for (const ast::symbol ¶m : decl.q_params()) { + set(param, BitType::Qubit); + } + + decl.foreach_stmt([this](Gate &gate) { gate.accept(*this); }); + + pop_scope(); + + // Add declaration + set(decl.id(), + GateType{(int)decl.c_params().size(), (int)decl.q_params().size()}); + } + } + void visit(OracleDecl &decl) { + if (lookup_local(decl.id())) { + std::cerr << decl.pos() << ": Identifier \"" << decl.id() + << "\" previously declared\n"; + error_ = true; + } else { + set(decl.id(), GateType{0, (int)decl.params().size()}); + } + } + void visit(RegisterDecl &decl) { + if (lookup_local(decl.id())) { + std::cerr << decl.pos() << ": Identifier \"" << decl.id() + << "\" previously declared\n"; + error_ = true; + } else if (decl.size() < 0) { + std::cerr << decl.pos() << ": Registers must have non-negative size\n"; + error_ = true; + } else { + set(decl.id(), + RegisterType{decl.is_quantum() ? BitType::Qubit : BitType::Cbit, + decl.size()}); + } + } + void visit(AncillaDecl &decl) { + if (lookup_local(decl.id())) { + std::cerr << decl.pos() << ": Identifier \"" << decl.id() + << "\" previously declared\n"; + error_ = true; + } else if (decl.size() < 0) { + std::cerr << decl.pos() << ": Registers must have non-negative size\n"; + error_ = true; + } else { + set(decl.id(), RegisterType{BitType::Qubit, decl.size()}); + } + } + + void visit(Program &prog) { + push_scope(); + + prog.foreach_stmt([this](Stmt &stmt) { stmt.accept(*this); }); + + pop_scope(); + } + +private: + bool error_ = false; ///< whether errors have occurred + std::list> symbol_table_{ + {}}; ///< a stack of symbol tables + + /** + * \brief Enters a new scope + */ + void push_scope() { symbol_table_.push_front({}); } + + /** + * \brief Exits the current scope + */ + void pop_scope() { symbol_table_.pop_front(); } + + /** + * \brief Looks up a symbol in the symbol table + * + * Lookup checks in each symbol table going backwards up the enclosing + * scopes. + * + * \param id Const reference to a symbol + * \return The type of the symbol, if found + */ + std::optional lookup(const ast::symbol &id) { + for (auto &table : symbol_table_) { + if (auto it = table.find(id); it != table.end()) { + return it->second; + } + } + return std::nullopt; + } + + /** + * \brief Looks up a symbol in the local scope. + * + * \param id Const reference to a symbol + * \return The type of the symbol, if found + */ + std::optional lookup_local(const ast::symbol &id) { + if (!(symbol_table_.empty())) { + const auto &local = symbol_table_.front(); + if (auto it = local.find(id); it != local.end()) { + return it->second; + } + } + return std::nullopt; + } + + /** + * \brief Assigns a symbol in the current scope + * + * \param id Const reference to a symbol + * \param typ The type of the symbol + */ + void set(const ast::symbol &id, Type typ) { + if (symbol_table_.empty()) { + throw std::logic_error("No current symbol table!"); + } + + symbol_table_.front()[id] = typ; + } + + /** + * \brief Checks a vector of bit accesses + * + * Given a vector of variable access and a vector of optional bit types, + * checks that each variable access is well-formed, is of the correct type + * if its type is specific, that all **register** accesses are of the + * same length, and that no bit is used multiple times. + * + * For instance, + * qreg q[2]; + * qreg r[2]; + * CX q,r; + * will pass the check, while + * qreg q[2]; + * qreg r[1]; + * CX q,r; + * will not. + * + * \param args Const reference to a vector of arguments + * \param types Const reference to a vector of optional bit types + * \note Sets the error flag if an error is found + */ + void check_uniform(const std::vector &args, + const std::vector> &types) { + int mapping_size = -1; + std::set seen; + + for (std::size_t i = 0; i < args.size(); i++) { + auto entry = lookup(args[i].var()); + + if (!entry) { + std::cerr << args[i].pos() << ": Identifier \"" << args[i].var() << "\" undeclared\n"; + error_ = true; + } else if (std::holds_alternative(*entry)) { + auto ty = std::get(*entry); + + // Check that the bit is not a dereference + if (args[i].offset()) { + std::cerr << args[i].pos() << ": Illegal dereference of non-register type\n"; + error_ = true; + } + + // Check that it's compatible with the type list + if (types[i] && ty != *(types[i])) { + std::cerr << args[i].pos() << ": Argument " << args[i] << " has incorrect type\n"; + error_ = true; + } + + // Check that the bit hasn't been used previously + if (seen.find(args[i]) != seen.end()) { + std::cerr << args[i].pos() << ": Repeated argument " << args[i] << "\n"; + error_ = true; + } + } else if (std::holds_alternative(*entry) && args[i].offset()) { + auto ty = std::get(*entry); + + // Check that it's within bounds + if (0 > *(args[i].offset()) || *(args[i].offset()) >= ty.length) { + std::cerr << args[i].pos() << ": Register access " << args[i] << " out of bounds\n"; + error_ = true; + } + + // Check if it's compatible with the type list + if (types[i] && ty.type != *(types[i])) { + std::cerr << args[i].pos() << ": Argument " << args[i] << " has incorrect type\n"; + error_ = true; + } + + // Check that it hasn't been used previously + if (seen.find(args[i]) != seen.end() || seen.find(args[i].root()) != seen.end()) { + std::cerr << args[i].pos() << ": Repeated argument " << args[i] << "\n"; + error_ = true; + } + + } else if (std::holds_alternative(*entry)) { + auto ty = std::get(*entry); + + // Check that the register length is consistent + if (mapping_size == -1) { + mapping_size = ty.length; + } else if (mapping_size != ty.length) { + std::cerr << args[i].pos() << ": Register " << args[i] << " has incompatible length\n"; + error_ = true; + } + + // Check if it's compatible with the type list + if (types[i] && ty.type != *(types[i])) { + std::cerr << args[i].pos() << ": Argument " << args[i] << " has incorrect type\n"; + error_ = true; + } + + // Check that it hasn't been used previously + if (std::any_of(seen.begin(), seen.end(), [&args, &i](auto &v) { return args[i].contains(v); })) { + std::cerr << args[i].pos() << ": Repeated argument " << args[i] << "\n"; + error_ = true; + } + + } else { + std::cerr << args[i].pos() << ": Identifier " << args[i] << " is not a bit or register\n"; + error_ = true; + } + + seen.insert(args[i]); + } + } +}; + +/** + * \brief Checks a program for semantic errors + */ +inline void check_source(Program &prog) { + SemanticChecker analysis; + if (analysis.run(prog)) { + throw SemanticError(); + } +} + +} /* namespace ast */ +} /* namespace tools_v1 */ + +#endif /* TOOLS_V1_AST_SEMANTIC_HPP_ */ diff --git a/experimental/include/tools_v1/ast/stmt.hpp b/experimental/include/tools_v1/ast/stmt.hpp new file mode 100644 index 00000000..2afe2400 --- /dev/null +++ b/experimental/include/tools_v1/ast/stmt.hpp @@ -0,0 +1,939 @@ +/* + * This file is part of tools_v1. + * + * Copyright (c) 2019 - 2025 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 tools_v1/ast/stmt.hpp + * \brief OpenQASM statements + */ + +#ifndef TOOLS_V1_AST_STMT_HPP_ +#define TOOLS_V1_AST_STMT_HPP_ + +#include +#include +#include + +#include "base.hpp" +#include "expr.hpp" +#include "var.hpp" + +namespace tools_v1 { +namespace ast { + +/** + * \class tools_v1::ast::Stmt + * \brief Base class for OpenQASM statements + */ +class Stmt : public ASTNode { +public: + Stmt(parser::Position pos) : ASTNode(pos) {} + virtual ~Stmt() = default; + + /** + * \brief Internal pretty-printer which can suppress the output of the + * stdlib + * + * \param suppress_std Whether to suppress output of the standard library + */ + virtual std::ostream &pretty_print(std::ostream &os, + bool suppress_std) const = 0; + + std::ostream &pretty_print(std::ostream &os) const override { + return pretty_print(os, false); + } + +protected: + virtual Stmt *clone() const override = 0; +}; + +/** + * \class tools_v1::ast::MeasureStmt + * \brief Class for measurement statements + * \see tools_v1::ast::Stmt + */ +class MeasureStmt final : public Stmt { + VarAccess q_arg_; ///< the quantum bit|register + VarAccess c_arg_; ///< the classical bit|register + +public: + /** + * \brief Constructs a measurement statement + * + * \param pos The source position + * \param q_arg Rvalue reference to the quantum argument + * \param c_arg Rvalue reference to the classical argument + */ + MeasureStmt(parser::Position pos, VarAccess &&q_arg, VarAccess &&c_arg) + : Stmt(pos), q_arg_(std::move(q_arg)), c_arg_(std::move(c_arg)) {} + + /** + * \brief Protected heap-allocated construction + */ + static ptr create(parser::Position pos, VarAccess &&q_arg, + VarAccess &&c_arg) { + return std::make_unique(pos, std::move(q_arg), + std::move(c_arg)); + } + + /** + * \brief Get the quantum argument + * + * \return Reference to the quantum argument + */ + VarAccess &q_arg() { return q_arg_; } + + /** + * \brief Get the classical argument + * + * \return Reference to the classical argument + */ + VarAccess &c_arg() { return c_arg_; } + + /** + * \brief Set the quantum argument + * + * \param arg Const reference to a new argument + */ + void set_qarg(const VarAccess &arg) { q_arg_ = arg; } + + /** + * \brief Set the classical argument + * + * \param arg Const reference to a new argument + */ + void set_carg(const VarAccess &arg) { c_arg_ = arg; } + + void accept(Visitor &visitor) override { visitor.visit(*this); } + std::ostream &pretty_print(std::ostream &os, bool) const override { + os << "measure " << q_arg_ << " -> " << c_arg_ << ";\n"; + return os; + } + +protected: + MeasureStmt *clone() const override { + return new MeasureStmt(pos_, VarAccess(q_arg_), VarAccess(c_arg_)); + } +}; + +/** + * \class tools_v1::ast::ResetStmt + * \brief Class for reset statements + * \see tools_v1::ast::Stmt + */ +class ResetStmt final : public Stmt { + VarAccess arg_; ///< the qbit|qreg + +public: + /** + * \brief Constructs a reset statement + * + * \param pos The source position + * \param arg Rvalue reference to the argument + */ + ResetStmt(parser::Position pos, VarAccess &&arg) + : Stmt(pos), arg_(std::move(arg)) {} + + /** + * \brief Protected heap-allocated construction + */ + static ptr create(parser::Position pos, VarAccess &&arg) { + return std::make_unique(pos, std::move(arg)); + } + + /** + * \brief Get the argument + * + * \return Reference to the argument + */ + VarAccess &arg() { return arg_; } + + /** + * \brief Set the argument + * + * \param arg Const reference to a new argument + */ + void set_arg(const VarAccess &arg) { arg_ = arg; } + + void accept(Visitor &visitor) override { visitor.visit(*this); } + std::ostream &pretty_print(std::ostream &os, bool) const override { + os << "reset " << arg_ << ";\n"; + return os; + } + +protected: + ResetStmt *clone() const override { + return new ResetStmt(pos_, VarAccess(arg_)); + } +}; + +/** + * \class tools_v1::ast::IfStmt + * \brief Class for if statements + * \see tools_v1::ast::Stmt + */ +class IfStmt final : public Stmt { + symbol var_; ///< classical register name + int cond_; ///< value to check against + ptr then_; ///< statement to be executed if true + +public: + /** + * \brief Constructs an if statement + * + * \param pos The source position + * \param var The variable (classical register) being tested + * \param cond The integer value to test against + * \param then The statement to execute in the then branch + */ + IfStmt(parser::Position pos, symbol var, int cond, ptr then) + : Stmt(pos), var_(var), cond_(cond), then_(std::move(then)) {} + + /** + * \brief Protected heap-allocated construction + */ + static ptr create(parser::Position pos, symbol var, int cond, + ptr then) { + return std::make_unique(pos, var, cond, std::move(then)); + } + + /** + * \brief Get the tested variable + * + * \return Const reference to the variable name + */ + const symbol &var() const { return var_; } + + /** + * \brief Get the integer condition + * + * \return The integer tested against + */ + int cond() const { return cond_; } + + /** + * \brief Get the then branch + * + * \return Reference to the "then" statement + */ + Stmt &then() { return *then_; } + + /** + * \brief Set the then branch + * + * \param then The new statement + */ + void set_then(ptr then) { then_ = std::move(then); } + + void accept(Visitor &visitor) override { visitor.visit(*this); } + std::ostream &pretty_print(std::ostream &os, bool) const override { + os << "if (" << var_ << "==" << cond_ << ") " << *then_; + return os; + } + +protected: + IfStmt *clone() const override { + return new IfStmt(pos_, var_, cond_, object::clone(*then_)); + } +}; + +/** + * \class tools_v1::ast::Gate + * \brief Statement sub-class for gate + */ +class Gate : public Stmt { +public: + Gate(parser::Position pos) : Stmt(pos) {} + virtual ~Gate() = default; + +protected: + virtual Gate *clone() const = 0; +}; + +/** + * \class tools_v1::ast::UGate + * \brief Class for U gates + * \see tools_v1::ast::Gate + */ +class UGate final : public Gate { + ptr theta_; ///< theta angle + ptr phi_; ///< phi angle + ptr lambda_; ///< lambda angle + + VarAccess arg_; ///< quantum bit|register + +public: + /** + * \brief Constructs a U gate + * + * \param pos The source position + * \param theta The theta angle + * \param phi The phi angle + * \param lambda The lambda angle + * \param arg Rvalue reference to the quantum argument + */ + UGate(parser::Position pos, ptr theta, ptr phi, ptr lambda, + VarAccess &&arg) + : Gate(pos), theta_(std::move(theta)), phi_(std::move(phi)), + lambda_(std::move(lambda)), arg_(std::move(arg)) {} + + /** + * \brief Protected heap-allocated construction + */ + static ptr create(parser::Position pos, ptr theta, ptr phi, + ptr lambda, VarAccess &&arg) { + return std::make_unique(pos, std::move(theta), std::move(phi), + std::move(lambda), std::move(arg)); + } + + /** + * \brief Get the theta angle + * + * \return Reference to the angle expression + */ + Expr &theta() { return *theta_; } + + /** + * \brief Get the phi angle + * + * \return Reference to the angle expression + */ + Expr &phi() { return *phi_; } + + /** + * \brief Get the lambda angle + * + * \return Reference to the angle expression + */ + Expr &lambda() { return *lambda_; } + + /** + * \brief Get the argument + * + * \return Reference to the quantum argument + */ + VarAccess &arg() { return arg_; } + + /** + * \brief Set the theta angle + * + * \param theta The new angle expression + */ + void set_theta(ptr theta) { theta_ = std::move(theta); } + + /** + * \brief Set the phi angle + * + * \param theta The new angle expression + */ + void set_phi(ptr phi) { phi_ = std::move(phi); } + + /** + * \brief Set the lambda angle + * + * \param theta The new angle expression + */ + void set_lambda(ptr lambda) { lambda_ = std::move(lambda); } + + /** + * \brief Set the argument + * + * \param arg The new argument + */ + void set_arg(const VarAccess &arg) { arg_ = arg; } + + void accept(Visitor &visitor) override { visitor.visit(*this); } + std::ostream &pretty_print(std::ostream &os, bool) const override { + os << "U(" << *theta_ << "," << *phi_ << "," << *lambda_ << ") " << arg_ + << ";\n"; + return os; + } + +protected: + UGate *clone() const override { + return new UGate(pos_, object::clone(*theta_), object::clone(*phi_), + object::clone(*lambda_), VarAccess(arg_)); + } +}; + +/** + * \class tools_v1::ast::CNOTGate + * \brief Class for CX gates + * \see tools_v1::ast::Gate + */ +class CNOTGate final : public Gate { + VarAccess ctrl_; ///< control qubit|qreg + VarAccess tgt_; ///< target qubit|qreg + +public: + /** + * \brief Constructs a CNOT gate + * + * \param pos The source position + * \param ctrl Rvalue reference to the control argument + * \param tgt Rvalue reference to the target argument + */ + CNOTGate(parser::Position pos, VarAccess &&ctrl, VarAccess &&tgt) + : Gate(pos), ctrl_(std::move(ctrl)), tgt_(std::move(tgt)) {} + + /** + * \brief Protected heap-allocated construction + */ + static ptr create(parser::Position pos, VarAccess &&ctrl, + VarAccess &&tgt) { + return std::make_unique(pos, std::move(ctrl), std::move(tgt)); + } + + /** + * \brief Get the control argument + * + * \return Reference to the quantum argument + */ + VarAccess &ctrl() { return ctrl_; } + + /** + * \brief Get the target argument + * + * \return Reference to the quantum argument + */ + VarAccess &tgt() { return tgt_; } + + /** + * \brief Set the control argument + * + * \param ctrl The new argument + */ + void set_ctrl(const VarAccess &ctrl) { ctrl_ = ctrl; } + + /** + * \brief Set the target argument + * + * \param tgt The new argument + */ + void set_tgt(const VarAccess &tgt) { tgt_ = tgt; } + + void accept(Visitor &visitor) override { visitor.visit(*this); } + std::ostream &pretty_print(std::ostream &os, bool) const override { + os << "CX " << ctrl_ << "," << tgt_ << ";\n"; + return os; + } + +protected: + CNOTGate *clone() const override { + return new CNOTGate(pos_, VarAccess(ctrl_), VarAccess(tgt_)); + } +}; + +/** + * \class tools_v1::ast::BarrierGate + * \brief Class for barrier gates + * \see tools_v1::ast::Gate + */ +class BarrierGate final : public Gate { + std::vector args_; ///< list of quantum bits|registers + +public: + /** + * \brief Constructs a barrier gate + * + * \param pos The source position + * \param args Rvalue reference to a list of arguments + */ + BarrierGate(parser::Position pos, std::vector &&args) + : Gate(pos), args_(std::move(args)) {} + + /** + * \brief Protected heap-allocated construction + */ + static ptr create(parser::Position pos, + std::vector &&args) { + return std::make_unique(pos, std::move(args)); + } + + /** + * \brief Get the number of arguments + * + * \return The number of arguments + */ + int num_args() const { return static_cast(args_.size()); } + + /** + * \brief Get the list of arguments + * + * \return Reference to the list of arguments + */ + std::vector &args() { return args_; } + + /** + * \brief Get the ith argument + * + * \param i The number of the argument (0 indexed) + * \return Reference to the ith argument + */ + VarAccess &arg(int i) { return args_[i]; } + + /** + * \brief Apply a function to each argument + * + * \param f Void function accepting a reference to the argument + */ + void foreach_arg(std::function f) { + for (auto it = args_.begin(); it != args_.end(); it++) { + f(*it); + } + } + + /** + * \brief Set the ith argument + * + * \param i The number of the argument (0 indexed) + * \param arg The new argument + */ + void set_arg(int i, const VarAccess &arg) { args_[i] = arg; } + + void accept(Visitor &visitor) override { visitor.visit(*this); } + std::ostream &pretty_print(std::ostream &os, bool) const override { + os << "barrier "; + for (auto it = args_.begin(); it != args_.end(); it++) { + os << (it == args_.begin() ? "" : ",") << *it; + } + os << ";\n"; + return os; + } + +protected: + BarrierGate *clone() const override { + return new BarrierGate(pos_, std::vector(args_)); + } +}; + +/** + * \class tools_v1::ast::DeclaredGate + * \brief Class for declared gate applications + * \see tools_v1::ast::Gate + */ +class DeclaredGate final : public Gate { + symbol name_; ///< gate identifier + std::vector> c_args_; ///< list of classical arguments + std::vector q_args_; ///< list of quantum arguments + +public: + /** + * \brief Constructs an application of a declared gate + * + * \param pos The source position + * \param name The gate name + * \param c_args Rvalue reference to a list of classical arguments + * \param q_args Rvalue reference to a list of quantum arguments + */ + DeclaredGate(parser::Position pos, symbol name, + std::vector> &&c_args, std::vector &&q_args) + : Gate(pos), name_(name), c_args_(std::move(c_args)), + q_args_(std::move(q_args)) {} + + /** + * \brief Protected heap-allocated construction + */ + static ptr create(parser::Position pos, symbol name, + std::vector> &&c_args, + std::vector &&q_args) { + return std::make_unique(pos, name, std::move(c_args), + std::move(q_args)); + } + + /** + * \brief Get the gate name + * + * \return Const reference to the gate name + */ + const symbol &name() const { return name_; } + + /** + * \brief Get the number of classical arguments + * + * \return The number of arguments + */ + int num_cargs() const { return static_cast(c_args_.size()); } + + /** + * \brief Get the number of quantum arguments + * + * \return The number of arguments + */ + int num_qargs() const { return static_cast(q_args_.size()); } + + /** + * \brief Get the ith classical argument + * + * \param i The number of the argument, 0-indexed + * \return Reference to an expression + */ + Expr &carg(int i) { return *(c_args_[i]); } + + /** + * \brief Get the ith quantum argument + * + * \param i The number of the argument, 0-indexed + * \return Reference to the argument + */ + VarAccess &qarg(int i) { return q_args_[i]; } + + /** + * \brief Get the list of quantum arguments + * + * \return Reference to the list of arguments + */ + std::vector &qargs() { return q_args_; } + + /** + * \brief Apply a function to each classical argument + * + * \param f Void function accepting an expression reference + */ + void foreach_carg(std::function f) { + for (auto it = c_args_.begin(); it != c_args_.end(); it++) { + f(**it); + } + } + + /** + * \brief Apply a function to each quantum argument + * + * \param f Void function accepting a reference to an argument + */ + void foreach_qarg(std::function f) { + for (auto it = q_args_.begin(); it != q_args_.end(); it++) { + f(*it); + } + } + + /** + * \brief Set the ith classical argument + * + * \param i The number of the argument, 0-indexed + * \param expr An expression giving the new argument + */ + void set_carg(int i, ptr expr) { c_args_[i] = std::move(expr); } + + /** + * \brief Set the ith quantum argument + * + * \param i The number of the argument, 0-indexed + * \param arg The new argument + */ + void set_qarg(int i, const VarAccess &arg) { q_args_[i] = arg; } + + void accept(Visitor &visitor) override { visitor.visit(*this); } + std::ostream &pretty_print(std::ostream &os, bool) const override { + os << name_; + if (c_args_.size() > 0) { + os << "("; + for (auto it = c_args_.begin(); it != c_args_.end(); it++) { + os << (it == c_args_.begin() ? "" : ",") << **it; + } + os << ")"; + } + os << " "; + for (auto it = q_args_.begin(); it != q_args_.end(); it++) { + os << (it == q_args_.begin() ? "" : ",") << *it; + } + os << ";\n"; + return os; + } + +protected: + DeclaredGate *clone() const override { + std::vector> c_tmp; + for (auto it = c_args_.begin(); it != c_args_.end(); it++) { + c_tmp.emplace_back(object::clone(**it)); + } + + return new DeclaredGate(pos_, name_, std::move(c_tmp), + std::vector(q_args_)); + } +}; + +// key new gates are: +// paulis, exp paulis, control-gate, mutli-control-gate + +enum class PauliType : char { I = 'I', X = 'X', Y = 'Y', Z = 'Z' }; + +class PauliString final : public Gate { + symbol name_ = "PauliString"; + std::vector q_args_; + std::vector paulis_; + +public: + void foreach_pauli(std::function f){ + assert(q_args_.size() == paulis_.size()); + int L = q_args_.size(); + for(int i = 0; i < L; ++i){ + f(q_args_[i], paulis_[i]); + } + } + + PauliString(parser::Position pos, std::vector &&q_args, std::vector &&paulis) + : Gate(pos), q_args_(std::move(q_args)), paulis_(std::move(paulis)) {} + + /** + * \brief Protected heap-allocated construction + */ + static ptr create(parser::Position pos, + std::vector &&q_args, + std::vector &&paulis) { + return std::make_unique(pos, std::move(q_args), + std::move(paulis)); + } + + /** + * \brief Get the number of quantum arguments + * + * \return The number of arguments + */ + int num_qargs() const { return static_cast(q_args_.size()); } + + /** + * \brief Get the ith quantum argument + * + * \param i The number of the argument, 0-indexed + * \return Reference to the argument + */ + VarAccess &qarg(int i) { return q_args_[i]; } + + /** + * \brief Get the list of quantum arguments + * + * \return Reference to the list of arguments + */ + std::vector &qargs() { return q_args_; } + + /** + * \brief Apply a function to each quantum argument + * + * \param f Void function accepting a reference to an argument + */ + void foreach_qarg(std::function f) { + for (auto it = q_args_.begin(); it != q_args_.end(); it++) { + f(*it); + } + } + + /** + * \brief Set the ith quantum argument + * + * \param i The number of the argument, 0-indexed + * \param arg The new argument + */ + void set_qarg(int i, const VarAccess &arg) { q_args_[i] = arg; } + + void accept(Visitor &visitor) override { visitor.visit(*this); } + std::ostream &pretty_print(std::ostream &os, bool) const override { + os << "Pauli "; + if (!q_args_.empty()) { + os << static_cast(paulis_[0]); + os << q_args_[0]; + } + for (int i = 1; i < q_args_.size(); ++i) { + os << ", "; + os << static_cast(paulis_[i]); + os << q_args_[i]; + } + os << ";\n"; + return os; + } + +protected: + PauliString *clone() const override { + return new PauliString(pos_, std::vector(q_args_), + std::vector(paulis_)); + } +}; + +class PhaseGate final : public Gate { + // diag(1, exp(2*pi*i * angle)) + symbol name_ = "PhaseGate"; + ptr angle_; + std::vector q_args_; + +public: + PhaseGate(parser::Position pos, ptr &&angle, + std::vector &&q_args) + : Gate(pos), angle_(std::move(angle)), q_args_(std::move(q_args)) {} + + /** + * \brief Protected heap-allocated construction + */ + static ptr create(parser::Position pos, ptr &&angle, + std::vector &&q_args) { + return std::make_unique(pos, std::move(angle), + std::move(q_args)); + } + + int num_qargs() const { return static_cast(q_args_.size()); } + Expr &angle() { return *angle_; } + + /** + * \brief Get the ith quantum argument + * + * \param i The number of the argument, 0-indexed + * \return Reference to the argument + */ + VarAccess &qarg(int i) { return q_args_[i]; } + + /** + * \brief Get the list of quantum arguments + * + * \return Reference to the list of arguments + */ + std::vector &qargs() { return q_args_; } + + /** + * \brief Apply a function to each quantum argument + * + * \param f Void function accepting a reference to an argument + */ + void foreach_qarg(std::function f) { + for (auto it = q_args_.begin(); it != q_args_.end(); it++) { + f(*it); + } + } + + /** + * \brief Set the ith quantum argument + * + * \param i The number of the argument, 0-indexed + * \param arg The new argument + */ + void set_qarg(int i, const VarAccess &arg) { q_args_[i] = arg; } + + void accept(Visitor &visitor) override { visitor.visit(*this); } + std::ostream &pretty_print(std::ostream &os, bool) const override { + os << "PhaseGate "; + os << "(" << *angle_ << ") "; + if(!q_args_.empty()) + os << q_args_[0]; + for (int i = 1; i < q_args_.size(); ++i) { + os << ", "; + os << q_args_[i]; + } + os << ";\n"; + return os; + } + +protected: + PhaseGate *clone() const override { + return new PhaseGate(pos_, object::clone(*angle_), std::vector(q_args_)); + } +}; + +class ExpPauli final : public Gate { + symbol name_ = "ExpPauli"; + ptr angle_; + std::vector q_args_; + std::vector paulis_; + +public: + ExpPauli(parser::Position pos, ptr &&angle, + std::vector &&q_args, std::vector &&paulis) + : Gate(pos), angle_(std::move(angle)), q_args_(std::move(q_args)), + paulis_(std::move(paulis)) {} + + /** + * \brief Protected heap-allocated construction + */ + static ptr create(parser::Position pos, ptr &&angle, + std::vector &&q_args, + std::vector &&paulis) { + return std::make_unique(pos, std::move(angle), std::move(q_args), + std::move(paulis)); + } + + int num_qargs() const { return static_cast(q_args_.size()); } + Expr &angle() { return *angle_; } + + /** + * \brief Get the ith quantum argument + * + * \param i The number of the argument, 0-indexed + * \return Reference to the argument + */ + VarAccess &qarg(int i) { return q_args_[i]; } + + /** + * \brief Get the list of quantum arguments + * + * \return Reference to the list of arguments + */ + std::vector &qargs() { return q_args_; } + std::vector &paulis() { return paulis_; } + + /** + * \brief Apply a function to each quantum argument + * + * \param f Void function accepting a reference to an argument + */ + void foreach_qarg(std::function f) { + for (auto it = q_args_.begin(); it != q_args_.end(); it++) { + f(*it); + } + } + + /** + * \brief Set the ith quantum argument + * + * \param i The number of the argument, 0-indexed + * \param arg The new argument + */ + void set_qarg(int i, const VarAccess &arg) { q_args_[i] = arg; } + + void accept(Visitor &visitor) override { visitor.visit(*this); } + std::ostream &pretty_print(std::ostream &os, bool) const override { + os << "ExpPauli "; + os << "(" << *angle_ << ") "; + if (!q_args_.empty()) { + os << static_cast(paulis_[0]); + os << q_args_[0]; + } + for (int i = 1; i < q_args_.size(); ++i) { + os << ", "; + os << static_cast(paulis_[i]); + os << q_args_[i]; + } + os << ";\n"; + return os; + } + +protected: + ExpPauli *clone() const override { + return new ExpPauli(pos_, object::clone(*angle_), + std::vector(q_args_), + std::vector(paulis_)); + } +}; + +} /* namespace ast */ +} /* namespace tools_v1 */ + +#endif /* TOOLS_V1_AST_STMT_HPP_ */ diff --git a/experimental/include/tools_v1/ast/traversal.hpp b/experimental/include/tools_v1/ast/traversal.hpp new file mode 100644 index 00000000..40c4b801 --- /dev/null +++ b/experimental/include/tools_v1/ast/traversal.hpp @@ -0,0 +1,118 @@ +/* + * This file is part of tools_v1. + * + * Copyright (c) 2019 - 2025 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 tools_v1/ast/traversal.hpp + * \brief Node traversal for syntax trees + */ + +#ifndef TOOLS_V1_AST_TRAVERSAL_HPP_ +#define TOOLS_V1_AST_TRAVERSAL_HPP_ + +#include "program.hpp" +#include "visitor.hpp" + +namespace tools_v1 { +namespace ast { + +/** + * \class tools_v1::ast::Traverse + * \brief Generic complete traversal of ASTs + * \see tools_v1::ast::Visitor + * + * Implements a generic, pass-through traversal of the entire + * AST. Standard usage is to derive from this class and override only + * the nodes desired. + * + * Note that overriding a node kills traversal to + * children of that node. This can be useful for cutting off traversal + * to certain subtrees. Traversal logic can be accessed through the + * parent class, e.g. by calling Traverse::visit. In this way, the + * Traverse class can be used to implement post-order, pre-order, or + * mixed pre/post algorithms by directly calling the traversal logic. + */ +class Traverse : public Visitor { + public: + void visit(VarAccess& var) override {} + void visit(BExpr& expr) override { + expr.lexp().accept(*this); + expr.rexp().accept(*this); + } + void visit(UExpr& expr) override { expr.subexp().accept(*this); } + void visit(PiExpr& expr) override {} + void visit(IntExpr& expr) override {} + void visit(RealExpr& expr) override {} + void visit(VarExpr& expr) override {} + void visit(MeasureStmt& stmt) override { + stmt.q_arg().accept(*this); + stmt.c_arg().accept(*this); + } + void visit(ResetStmt& stmt) override { stmt.arg().accept(*this); } + void visit(IfStmt& stmt) override { stmt.then().accept(*this); } + void visit(UGate& gate) override { + gate.theta().accept(*this); + gate.phi().accept(*this); + gate.lambda().accept(*this); + gate.arg().accept(*this); + } + void visit(CNOTGate& gate) override { + gate.ctrl().accept(*this); + gate.tgt().accept(*this); + } + void visit(BarrierGate& gate) override { + for (int i = 0; i < gate.num_args(); i++) { + gate.arg(i).accept(*this); + } + } + void visit(DeclaredGate& gate) override { + for (int i = 0; i < gate.num_cargs(); i++) { + gate.carg(i).accept(*this); + } + for (int i = 0; i < gate.num_qargs(); i++) { + gate.qarg(i).accept(*this); + } + } + + void visit(GateDecl& decl) override { + for (auto it = decl.begin(); it != decl.end(); it++) { + (**it).accept(*this); + } + } + + void visit(OracleDecl& decl) override {} + void visit(RegisterDecl& decl) override {} + void visit(AncillaDecl& decl) override {} + void visit(Program& prog) override { + for (auto it = prog.begin(); it != prog.end(); it++) { + (**it).accept(*this); + } + } +}; + +} /* namespace ast */ +} /* namespace tools_v1 */ + +#endif /* TOOLS_V1_AST_TRAVERSAL_HPP_ */ diff --git a/experimental/include/tools_v1/ast/var.hpp b/experimental/include/tools_v1/ast/var.hpp new file mode 100644 index 00000000..216b2150 --- /dev/null +++ b/experimental/include/tools_v1/ast/var.hpp @@ -0,0 +1,196 @@ +/* + * This file is part of tools_v1. + * + * Copyright (c) 2019 - 2025 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 tools_v1/ast/var.hpp + * \brief OpenQASM variable utilities + */ + +#ifndef TOOLS_V1_AST_VAR_HPP_ +#define TOOLS_V1_AST_VAR_HPP_ + +#include +#include + +#include "base.hpp" + +namespace tools_v1 { +namespace ast { + +/** + * \class tools_v1::ast::VarAccess + * \brief Class for variable accesses + * + * Represents accesses into a register by the register name and an optional + * offset or index into the register. If the offset is empty, the entire + * register is the access -- e.g. in gates applied in parallel across registers. + * + * As leaf nodes that do not usually need to be used in polymorphic contexts, + * variable accesses are the only nodes which by convention are **NOT** + * allocated on the heap. + */ +class VarAccess final : public ASTNode { + symbol var_; ///< the identifier + std::optional offset_; ///< optional offset into a register variable + + public: + friend std::hash; ///< Hash function + + /** + * \brief Construct a variable access + * + * \param pos The source position + * \param var The register name + * \param offset Optional integer offset into the register (default = + * std::nullopt) + */ + VarAccess(parser::Position pos, symbol var, + std::optional offset = std::nullopt) + : ASTNode(pos), var_(var), offset_(offset) {} + + + /** + * \brief Copy constructor + */ + VarAccess(const VarAccess& va) + : ASTNode(va.pos_), var_(va.var_), offset_(va.offset_) {} + + /** + * \brief Get the register name + * + * return Const reference to the register name + */ + const symbol& var() const { return var_; } + + /** + * \brief Get the offset + * + * return std::optional integer offset + */ + std::optional offset() const { return offset_; } + + /** + * \brief Copy assignment overload + */ + VarAccess& operator=(const VarAccess& v) { + var_ = v.var_; + offset_ = v.offset_; + return *this; + } + + /** + * \brief Equal operator overload + */ + bool operator==(const VarAccess& v) const { + return var_ == v.var_ && offset_ == v.offset_; + } + + /** + * \brief Less operator overload + * + * Used to allow variable accesses as keys in ordered maps + */ + bool operator<(const VarAccess& v) const { + if (var_ == v.var_) { + return offset_ < v.offset_; + } else { + return var_ < v.var_; + } + } + + /** + * \brief Check whether the variable access contains another + * + * A variable access u contains v if u == v or if u is a register + * and v is an offset into that register. Mainly useful for determining + * dependencies between gates in the "sugared" source + * + * \param v Const reference to a variable access + * \return true if the variable access contains v + */ + bool contains(const VarAccess& v) const { + if (offset_) { + return *this == v; + } else { + return v.var_ == var_; + } + } + + /** + * \brief Return the root of a variable access + * + * Strips any dereferences and returns a new variable access. + * Satisfies root(v).contains(v) == true + * + * \param v Const reference to a variable access + * \return var access for the root variable + */ + VarAccess root() const { return VarAccess(pos_, var_); } + + friend std::size_t hash_value(const VarAccess& v) { + std::size_t lhs = std::hash{}(v.var_); + lhs ^= std::hash>{}(v.offset_) + 0x9e3779b9 + + (lhs << 6) + (lhs >> 2); + return lhs; + } + + void accept(Visitor& visitor) override { visitor.visit(*this); } + std::ostream& pretty_print(std::ostream& os) const override { + os << var_; + if (offset_) { + os << "[" << *offset_ << "]"; + } + return os; + } + + protected: + VarAccess* clone() const override { + return new VarAccess(pos_, var_, offset_); + } +}; + +} /* namespace ast */ +} /* namespace tools_v1 */ + +namespace std { +/** + * \brief Hash function for variable accesses + * + * Allows variable accesses to be used as keys in std::unordered_map. + * Implementation and magic numbers taken from boost::hash_combine. + */ +template <> +struct hash { + std::size_t operator()(const tools_v1::ast::VarAccess& v) const { + std::size_t lhs = std::hash{}(v.var_); + lhs ^= std::hash>{}(v.offset_) + 0x9e3779b9 + + (lhs << 6) + (lhs >> 2); + return lhs; + } +}; +} /* namespace std */ + +#endif /* TOOLS_V1_AST_VAR_HPP_ */ diff --git a/experimental/include/tools_v1/ast/visitor.hpp b/experimental/include/tools_v1/ast/visitor.hpp new file mode 100644 index 00000000..0fdae2a4 --- /dev/null +++ b/experimental/include/tools_v1/ast/visitor.hpp @@ -0,0 +1,117 @@ +/* + * This file is part of tools_v1. + * + * Copyright (c) 2019 - 2025 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 tools_v1/ast/visitor.hpp + * \brief Visitor interface for syntax trees + */ + +#ifndef TOOLS_V1_AST_VISITOR_HPP_ +#define TOOLS_V1_AST_VISITOR_HPP_ + +namespace tools_v1 { +namespace ast { + +/* Forward declarations */ +class VarAccess; +class BExpr; +class UExpr; +class PiExpr; +class IntExpr; +class RealExpr; +class VarExpr; +class MeasureStmt; +class ResetStmt; +class IfStmt; +class UGate; +class CNOTGate; +class BarrierGate; +class DeclaredGate; +class PauliString; +class PhaseGate; +class ExpPauli; +class ControlGate; +class MultiControlGate; +class GateDecl; +class OracleDecl; +class RegisterDecl; +class AncillaDecl; +class Program; + +/** + * \class tools_v1::ast::Visitor + * \brief Base visitor interface + * + * Classic visitor via (virtual) double dispatch. Standard usage is to + * derive from this class and provide implementations of visit for **every** + * node type. + * + * Traversal to sub-nodes is handled by the particular visitor, not the + * node class. For a visitor that automatically handles traversal and also + * allows picking and choosing the particular visit overloads, see + * tools_v1::ast::Traverse. + */ +class Visitor { +public: + // Variables + virtual void visit(VarAccess &) = 0; + // Expressions + virtual void visit(BExpr &) = 0; + virtual void visit(UExpr &) = 0; + virtual void visit(PiExpr &) = 0; + virtual void visit(IntExpr &) = 0; + virtual void visit(RealExpr &) = 0; + virtual void visit(VarExpr &) = 0; + // Statements + virtual void visit(MeasureStmt &) = 0; + virtual void visit(ResetStmt &) = 0; + virtual void visit(IfStmt &) = 0; + // Gates + virtual void visit(UGate &) = 0; + virtual void visit(CNOTGate &) = 0; + virtual void visit(BarrierGate &) = 0; + virtual void visit(DeclaredGate &) = 0; + // New Gates + virtual void visit(PauliString&) = 0; + virtual void visit(PhaseGate&) = 0; + virtual void visit(ExpPauli&) = 0; + virtual void visit(ControlGate&) = 0; + virtual void visit(MultiControlGate&) = 0; + // Declarations + virtual void visit(GateDecl &) = 0; + virtual void visit(OracleDecl &) = 0; + virtual void visit(RegisterDecl &) = 0; + virtual void visit(AncillaDecl &) = 0; + // Program + virtual void visit(Program &) = 0; + // Destructor + virtual ~Visitor() = default; +}; + +} /* namespace ast */ +} /* namespace tools_v1 */ + +#endif /* TOOLS_V1_AST_VISITOR_HPP_ */ diff --git a/experimental/include/tools_v1/main.tex b/experimental/include/tools_v1/main.tex new file mode 100644 index 00000000..8cd6db89 --- /dev/null +++ b/experimental/include/tools_v1/main.tex @@ -0,0 +1,776 @@ +\documentclass[english,notitlepage,runningheads]{llncs} + +%\usepackage[T1]{fontenc} +%\usepackage[latin9]{inputenc} +\usepackage{babel} +\usepackage{mathtools} +\usepackage{amsmath} +\usepackage{amsfonts} +\usepackage{amssymb} +\usepackage{hyperref} +\usepackage{xspace} +\usepackage{booktabs} +\usepackage{semantic} +\usepackage[usenames,dvipsnames]{xcolor} +\usepackage{listings} +\usepackage{qcircuit} +\usepackage{wrapfig} +\usepackage{cleveref} + +\lstset{ + language=C, +% numbers=left, + basicstyle=\ttfamily, + frame=tb, + float, + tabsize=4, + otherkeywords={&&, <>, /, \\, ->, forall, not, ==>, =, =>}, + keywordstyle=\color{MidnightBlue},%\rm\bf, + commentstyle=\color{BurntOrange}, + numberblanklines=true, + numberstyle=\small\it\color{BurntOrange}, + morekeywords={measure, reset, family, gate, instance, fun, case, of, include}, + classoffset=1, + morekeywords={Qbit, Lemma, ensures, requires, OPENQASM, Family},keywordstyle=\color{BurntOrange}, + classoffset=0, + classoffset=2, + %morekeywords={cx, h, t, tdg},keywordstyle=\color{OliveGreen}, + classoffset=0,} + + +% Algebraic domains +\newcommand{\B}{\{0,1\}} +\newcommand{\F}{\mathbb{F}_2} +\newcommand{\Z}{\mathbb{Z}} +\newcommand{\R}{\mathbb{R}} +\newcommand{\N}{\mathbb{N}} +\newcommand{\D}{\mathbb{D}} +\newcommand{\GL}[1]{\mathrm{GL}(#1, \F)} +\newcommand{\GA}[1]{\mathrm{GA}(#1, \F)} + +% Standard operators +\newcommand{\argmax}{\operatorname*{arg\,max}} +\newcommand{\powerset}{\mathcal{P}} +\newcommand{\supp}{\operatorname{supp}} +\newcommand{\card}[1]{\lvert#1\rvert} +\renewcommand{\deg}{\operatorname{deg}} +\newcommand{\diag}{\operatorname{diag}} +\newcommand{\dist}{\eta} +\newcommand{\res}{\operatorname{Res}} +%\newcommand{\concat}{\oplus} +\newcommand{\concat}{\mathbin{\|}} +\newcommand{\subgroup}{\triangleleft} +\newcommand{\iso}{\simeq} +\newcommand{\semidirect}{\rtimes} + +% Quantum +\newcommand{\ket}[1]{|#1\rangle} +\newcommand{\bra}[1]{\langle#1|} +\newcommand{\braket}[2]{\langle#1|#2\rangle} + +% Gates +\newcommand{\cnot}{\mathrm{CNOT}} +\newcommand{\NOT}{\mathrm{NOT}} +\newcommand{\rz}{R_Z} + +% Data structures +\newcommand{\append}{+\!\!+\;} + +% Fourier analysis +\newcommand{\fourier}[1]{\widehat{#1}} +\newcommand{\parity}[1]{\chi_{#1}} + +% Misc +\newcommand{\skeleton}{skeleton\xspace} + +%Style +\newcommand{\etal}{{\it et al. }} +\setlength{\lightrulewidth}{.5pt} +\setlength{\heavyrulewidth}{1.1pt} + +% +\renewcommand{\comp}[1]{\left|#1\right|} +\newcommand{\x}{\mathbf{x}} +\newcommand{\y}{\mathbf{y}} + +\newcommand{\metaQASM}{metaQASM\xspace} +\newcommand{\openQASM}{openQASM\xspace} + +\title{Sized Types for low-level Quantum Metaprogramming} +\author{Matthew Amy\inst{1}\orcidID{0000-0003-3514-420X}} +\institute{University of Waterloo, Waterloo, Canada \\ \email{meamy@uwaterloo.ca}} + +\begin{document} + +\maketitle + +\abstract{One of the most fundamental aspects of quantum circuit design is the concept of \emph{families} of circuits parametrized by an instance size. As in classical programming, metaprogramming allows the programmer to write entire families of circuits simultaneously, an ability which is of particular importance in the context of quantum computing as algorithms frequently use arithmetic over non-standard word lengths. In this work, we introduce metaQASM, a typed extension of the openQASM language supporting the metaprogramming of circuit families. Our language and type system, built around a lightweight implementation of \emph{sized types}, supports subtyping over register sizes and is moreover type-safe. In particular, we prove that our system is strongly normalizing, and as such any well-typed metaQASM program can be statically unrolled into a finite circuit. + +\keywords{Quantum programming, Circuit description languages, \\ Metaprogramming.} +} + +\section{Introduction} + +Quantum computers have the potential to solve a number of important problems, including integer factorization \cite{s94}, quantum simulation \cite{l96}, approximating the Jones polynomial \cite{ajl06} and unstructured searching \cite{g96} asymptotically faster than the best known classical algorithms. These algorithms are typically described abstractly and make heavy use of classical arithmetic such as modular exponentiation. To make such algorithms concrete, efficient, reversible implementations of large swaths of a classical arithmetic and computation is needed -- moreover, due to the limited space constraints and special-purpose nature of quantum circuits, these operations are typically needed in a multitude of bit sizes. + +In part due to the increasing viability of quantum computing and the scaling of NISQ \cite{p18} devices, there has been a recent explosion in quantum programming tools. Such tools range from software development kits (e.g., Qiskit \cite{qiskit}, ProjectQ \cite{sht18}, Strawberry Fields \cite{kiqbaw18}, Pyquil \cite{scz16}) to Embedded domain-specific languages (e.g., Quipper \cite{glrsv13}, Qwire \cite{prz17}, $Q\ket{SI}$ \cite{lwglhdy17}) and standalone languages and compilers (e.g., QCL \cite{o00}, QML \cite{ag05}, ScaffCC \cite{jpkhlcm15}, Q\# \cite{sgtaghkmpr18}). Going beyond strict programming tools, software for the synthesis, optimization, and simulation of quantum circuits and programs (e.g., Revkit \cite{revkit}, TOpt \cite{hc18}, Feynman \cite{feynman}, PyZX \cite{kw19}, Quantum++ \cite{qpp}, QX \cite{qx}) are becoming more and more abundant. + +The proliferation of both hardware and software tools for quantum computing has in turn spurred a need for standardization and portability \cite{hsst18,mr19}. One such standard which has recently grown in popularity is the Quantum Assembly Language and its many various dialects (e.g., openQASM \cite{cbsg17}, QASM-HL \cite{jpkhlcm15}, cQASM \cite{kgahab18}). As a lightweight, modular language for specifying simple quantum circuits, programs with a well-defined syntax, QASM support -- in particular, for the openQASM dialect -- has been built-in to an increasingly large number of software tools, particularly standalone programs like circuit optimizers, as a way to support interoperability. + +One feature that is noticeably lacking in these dialects is the ability to define \emph{families} of quantum circuits parametrized over different register sizes, and by extension to \emph{generate} concrete instances. This creates a barrier for the use of QASM in writing portable libraries of quantum circuit families, particularly for classical operations such as arithmetic. As a result, software designers typically end up re-implementing code -- typically implemented in the host language for EDSLs, and hence not easily re-usable -- for generating instances of simple operations such as adders and multipliers. Alternatively, programmers resort to using other compilers such as Quipper, Q\# or ReVerC \cite{ars17} to generate individual instances, which complicates the compilation or simulation process. While recent progress towards the development of portable libraries of circuit families with high-level non-embedded languages, standardization remains an on-going process, and moreover a low-level approach is preferable in many situations, including as compilation targets and middle-ends. + +In this paper we make progress towards the design of a low-level language for quantum programming that supports the metaprogramming of sized circuit families. In particular, we develop a typed extension of the untyped open quantum assembly language (openQASM) with metaprogramming over lightweight \emph{sized types} \`{a} la dependent ML \cite{x01}. Our language, metaQASM, is further shown to be type-safe and strongly-normalizing, while the non-meta fragment is both more expressive than openQASM and admits a simpler syntax, owing to the type system. For the purposes of this paper, we focus on the type system design and metatheory of such a language, leaving implementation to future work. + +\subsection{Quantum metaprogramming} + +Most QRAM-based quantum programming languages are metaprogramming languages -- called \emph{circuit description languages} -- in that they typically operate by building quantum circuits to be sent in a single batch to a quantum processor. Such quantum circuits can typically be composed, reversed, and depend on the result of classical computations. + +In this paper, we are interested in a particular type of quantum circuit metaprogramming, wherein circuit families are parametrized over \emph{shapes} \cite{glrsv13,prz17}, such as the number of input qubits. Existing languages offer varying support for such metaprogramming, either implicitly (e.g., uniform or \emph{transversal} families of circuits in openQASM, iteration and qubit arrays in Q\#), or more explicitly (e.g., the generic \texttt{QData} type-class in Quipper, which can be instantiated via explicit type applications). Our approach differs from previous attempts by explicitly parametrizing registers and circuit families with \emph{size} parameters. We adopt a typed approach for a number of reasons: +\begin{itemize} + \item it allows the light-weight verification of libraries of circuit generators, + \item it provides a means of self-documentation, and + \item it allows explicit generation of sized-specialized instances. +\end{itemize} +The ability to generate instances of circuit families in various sizes \emph{without executing them} is particularly important for the purposes of resource estimation, and for benchmarking tools that operate on fixed-size but arbitrary input circuits, such as circuit optimizers \cite{hsst18}. + +As an illustration, given an in-place family of adders written in the style of (imperative) Quipper with the type + +\medskip +\centerline{ + \texttt{inplace\_add :: [Qubit] -> [Qubit] -> Circ ()}, +} +\medskip + +one may wish to generate a static, optimized instance of \texttt{inplace\_add} operating on $2$-qubit registers, using an external circuit optimizer. Doing so requires the specialization to (and serialization of) a function + +\medskip +\centerline{ + \texttt{inplace\_add2 :: (Qubit, Qubit) -> (Qubit, Qubit) -> Circ ()}. +} +\medskip + +One possible method of generating such a function is to write the body of \texttt{inplace\_add2} using a call to the generic \texttt{inplace\_add} applied to the $4$ input qubits. However, this quickly gets unwieldy, both in the boilerplate code defining a particular instance, and in the large number of parameters. + +A more common solution is to use \emph{dummy parameters}, whereby the generic function is ``applied'' to lists of qubits, which are then taken by the serialization method as meaning arbitrary inputs. For instance, the following Quipper\footnote{The function \texttt{inplace\_add2} could instead be directly generated by writing the adder as \texttt{inplace\_add :: QData qa => qa -> qa -> Circ ()}, then specializing \texttt{qa} to the finite type \texttt{(Qubit, Qubit)} using \emph{type applications}. However, the non-generic serialization functions in Quipper appear to work only for small finite tuple types.} code \cite{quipper} prints out a PDF representation of \texttt{inplace\_add2} using dummy parameters \texttt{qubit :: Qubit} + +\medskip +\centerline{ + \texttt{print\_generic PDF inplace\_add [qubit, qubit] [qubit, qubit]}. +} +\medskip + +The use of dummy parameters is partly a question of style, though it can cause problems when combining optimizations with \emph{initialized} dummy parameters. In either case, the use explicitly sized circuit families carries further benefits to both readability and correctness \cite{prz17}. + +\subsection{Organization} + +The remainder of this paper is organized as follows. \Cref{sec:overview} gives a brief overview of quantum computing. \Cref{sec:qasm} reviews the openQASM language and defines a formal semantics for it. \Cref{sec:tqasm,sec:mqasm} extend openQASM with types and metaprogramming capabilities, and finally \Cref{sec:conclusion} concludes the paper. + +\section{Quantum computing}\label{sec:overview} + +We give a brief overview of the basics of quantum computing. For a more in-depth introduction of quantum computation we direct the reader to \cite{nc00}, while an overview of quantum programming can be found in \cite{g06}. + +In the circuit model, the state of an $n$-qubit quantum system is described as a unit vector in a dimension $2^n$ complex vector space. The $2^n$ elementary basis vectors form the \emph{computational} basis, and are denoted by $\ket{\x}$ for bit strings $\x\in\{0,1\}^n$ -- these are called the \emph{classical} states. A general quantum state may then be written as a \emph{superposition} of classical states +\[ + \ket{\psi} = \sum_{\x\in\F^n} \alpha_{\x}\ket{\x}, +\] +for complex $\alpha_{\x}$ and having unit norm. The states of two $n$ and $m$ qubit quantum systems $\ket{\psi}$ and $\ket{\psi}$ may be combined into an $n+m$ qubit state by taking their tensor product $\ket{\psi}\otimes\ket{\psi}$. If to the contrary the state of two qubits cannot be written as a tensor product the two qubits are said to be \emph{entangled}. + +Quantum circuits, in analogy to classical circuits, carry qubits from left to right along \emph{wires} through \emph{gates} which transform the state. In the unitary circuit model gates are required to implement unitary operators on the state space -- that is, quantum gates are modelled by complex-valued matrices $U$ satisfying $UU^\dagger = U^\dagger U = I$, where $U^\dagger$ is the complex conjugate of $U$. As a result, unitary quantum computations must be \emph{reversible}, and in particular the quantum circuits performing classical computations are precisely the set of reversible circuits. + +The standard universal quantum gate set, known as Clifford+$T$, consists of the two-qubit controlled-NOT gate ($\cnot$), and the single-qubit Hadamard ($H$) and $T$ gates. As quantum circuits implement linear operators, we may define the above three gates by their effect on classical states: +\[ + \cnot\ket{x}\ket{y} = \ket{x}\ket{x\oplus y}, + \qquad T\ket{x} = e^{\frac{2\pi i}{8}x}\ket{x}, +\] +\[ + H\ket{x}=\frac{1}{\sqrt{2}}\sum_{x'\in\{0,1\}}(-1)^{x\cdot x'}\ket{x'}. +\] +Figure~\ref{fig:toffoli} gives a pictorial representation of a quantum circuit over $\cnot$, $H$, and $T$ gates. $\cnot$ gates are written as a solid dot on their first argument and an exclusive-OR symbol ($\oplus$) on their second argument. + +\begin{figure}[b] +\centerline{ +\Qcircuit @C=1em @R=.3em { +& \qw & \gate{T} & \ctrl{1} & \ctrl{2} & \qw & \qw & \targ & \gate{T} & \targ & \ctrl{1} & \qw & \qw & \qw \\ +& \qw & \gate{T} & \targ & \qw & \gate{T^\dagger} & \ctrl{1} & \qw & \qw & \qw & \targ & \ctrl{1} & \qw & \qw \\ +& \gate{H} & \gate{T} & \qw & \targ & \gate{T^\dagger} & \targ & \ctrl{-2} & \gate{T^\dagger} & \ctrl{-2} & \qw & \targ & \gate{H} & \qw +} +} +\caption{An example of a quantum circuit implementing the Toffoli gate.} +\label{fig:toffoli} +\end{figure} + +More general quantum operations include qubit initialization and measurement, which effectively convert between classical and quantum data. As neither operation is unitary and hence not (directly) reversible, we regard them as functions of the classical computer rather than gates in a quantum circuit. + +\section{openQASM}\label{sec:qasm} + +The open quantum assembly language (openQASM \cite{cbsg17}) is a low-level, untyped imperative quantum programming language, developed as a dialect of the informal QASM language. One of the key additions of the openQASM language is that of \emph{modularity}, in the form of a simple module and import system. As this work is largely concerned with the question of \emph{making this modularity more powerful} -- specifically, to support the modular definition of entire circuit families -- we first give a brief overview of the openQASM language. + +The official specification of openQASM can be found in \cite{cbsg17}. Programs in openQASM are structured as sequences of declarations and commands. Programmers can declare statically-sized classical or quantum registers, define unitary circuits (called \emph{gates} in openQASM), apply gates or circuits, measure or initialize qubits and condition commands on the value of classical bits. Gate arguments are restricted to individual qubits, where the application of gates to one or more register \emph{of the same size} is syntactic sugar for the application of a single gate in parallel across the registers. The listing below gives an example of an openQASM program performing quantum teleportation: +\begin{lstlisting} +OPENQASM 2.0; +qreg q[3]; +creg c0[1]; +creg c1[1]; + +h q[1]; +cx q[1],q[2]; +cx q[0],q[1]; +h q[0]; +measure q[0] -> c0[0]; +measure q[1] -> c1[0]; +if(c0==1) z q[2]; +if(c1==1) x q[2]; +\end{lstlisting} + +We give a slightly different syntax from the above, and from the concrete syntax \cite{cbsg17}, as it will be more convenient and readable for our purposes. As is common in imperative languages, we leave some of the concrete syntactic classes of openQASM \cite{cbsg17} separate in our formalization -- since all operations in openQASM nominally have unit type, this allows terms with unitary and non-unitary \emph{effects} to be distinguished, without relying on an effect system or monadic types. In particular, terms of the class $U$ of unitary statements represent computations with purely unitary effects, while commands $C$ may have non-unitary effects, such as measurement. Statements of the form + +\medskip +\centerline{ + \texttt{$E$($E_1,\dots,E_n$)} +} +\medskip + +represent the application of a unitary gate or named circuit $E$ to the (quantum) arguments $E_1$ through $E_n$. While the openQASM specification includes built-in \texttt{cx} (controlled-NOT) and parametrized single qubit gates \texttt{U}, we drop the parametrized \texttt{U} gate in favour of built-in Hadamard and $T/T^\dagger$ gates \texttt{h} and \texttt{t}/\texttt{tdg}, respectively. + +The commands \texttt{creg}, \texttt{qreg} and \texttt{gate} declare classical registers, quantum registers, and unitary circuits, respectively. The \texttt{if} statement differs from the formal openQASM definition by testing the value of a \emph{single} classical bit, rather than a classical register -- this was done to simplify the semantics of the language. Locations $l$ and values $V$ do not appear directly in openQASM programs, but are used to define the semantics. In particular, values of the form $(l_0, \dots, l_{I-1})$ denote registers and $\lambda x_1, \dots, x_n.U$ denote unitary circuits. We leave out a number of features of openQASM which are orthogonal to the extensions we describe here, namely classical arithmetic and the \texttt{barrier} and \texttt{opaque} terms. We also write parentheses around arguments and parameters. + +\begin{figure}[t] + \begin{tabular}{rrl} + Identifier $x$ & & \\ + Index $I$ & $::=$ & $i\in\N$ \\ + Expression $E$ & $::=$ & $x$ $\mid$ $x[I]$ \\ + Unitary Stmt $U$ & $::=$ & \texttt{cx}$(E_1,E_2)$ $\mid$ \texttt{h}$(E)$ $\mid$ \texttt{t}$(E)$ $\mid$ \texttt{tdg}$(E)$ $\mid$ $E(E_1,\dots, E_n)$ $\mid$ \texttt{$U_1$; $U_2$} \\ + Command $C$ & $::=$ & \texttt{creg} $x[I]$ $ \mid$ \texttt{qreg} $x[I]$ $\mid$ \texttt{gate $x(x_1,\dots, x_n)$ \{ $U$ \}} \\ + & $\mid$ & \texttt{measure $E_1$ -> $E_2$} $\mid$ \texttt{reset} $E$ $\mid$ \texttt{$U$} \\ + & $\mid$ & \texttt{if($E$==$I$) \{ $U$ \}} $\mid$ \texttt{$C_1$; $C_2$} \\ + & & \\ + Location $l$ & $\in\N$ & \\ + Value $V$ & $::=$ & $(l_0, \dots, l_{I-1})$ $\mid$ $\lambda x_1, \dots, x_n.U$ + \end{tabular} + \caption{openQASM (abstract) syntax}\label{fig:syntax} +\end{figure} + +As no formal semantics of openQASM is given in \cite{cbsg17}, we define an operational semantics in \Cref{fig:semantics}. Our semantics is defined with respect to a \emph{configuration} $\langle S, \sigma, \eta, \ket{\psi}\rangle$, which stores a term $S$ taken from some syntactic class (e.g., $C$, $U$, $E$), an environment $\sigma$ which maps variables to values, a classical heap $\eta$ storing the value of the classical bits, and a quantum state $\ket{\psi}$. Gates applied to qubit $l$ of a quantum state are written by added a subscript to the intended gate, e.g., +\[ + H_l\ket{\psi} = (I^{\otimes l-1}\otimes H \otimes I^{\otimes n - l})\ket{\psi} +\] +$\sigma[x\leftarrow v]$ denotes the environment mapping $x$ to $v$ or $\sigma(x)$ otherwise, and $S\{X/x\}$ denotes the substitution of $X$ for $x$ in $S$. We assume for convenience that no valid program will run out of classical memory or quantum bits. We say $\langle S, \sigma, \eta, \ket{\psi}\rangle\Downarrow v$ if $S$ reduces to $v$, where the form of $v$ depends on the syntactic class of $S$ -- for instance, expressions evaluate to locations, arrays or circuits while commands produce a new environment, heap and quantum state. Note that we use a call-by-name evaluation strategy, as openQASM has only globally scoped variables. + +Rather than give a full probabilistic reduction system to account for measurement probabilities, it suffices for our purposes to make the semantics non-deterministic. In particular, rules are given for both of the possible measurement outcomes in \texttt{measure $E_1$ -> $E_2$}, setting the classical bit to the result $c\in\{0,1\}$ and non-destructively applying the projector $P^c=\ket{c}\bra{c}$ (appropriately normalized) to the measured qubit. + +\begin{figure} + +Expressions: + \begin{gather*} + \inference{x\in\textsf{dom}(\sigma)}{\langle x, \sigma, \eta, \ket{\psi}\rangle\Downarrow \sigma(x)} \qquad + \inference{\langle x, \sigma, \eta, \ket{\psi}\rangle\Downarrow (l_0, \dots, l_{I'}) \qquad I\leq I'}{\langle x[I], \sigma, \eta, \ket{\psi}\rangle\Downarrow l_I} + \end{gather*} +Unitary statements: + \begin{gather*} + \inference{\langle E, \sigma, \eta, \ket{\psi}\rangle\Downarrow l} + {\langle \texttt{h}(E), \sigma,\eta, \ket{\psi}\rangle \Downarrow H_{l}\ket{\psi}} + \inference{\langle E, \sigma, \eta, \ket{\psi}\rangle\Downarrow l} + {\langle \texttt{t}(E), \sigma,\eta, \ket{\psi}\rangle \Downarrow T_{l}\ket{\psi}} + \inference{\langle E, \sigma, \eta, \ket{\psi}\rangle\Downarrow l} + {\langle \texttt{tdg}(E), \sigma,\eta, \ket{\psi}\rangle \Downarrow T_{l}^\dagger\ket{\psi}} \\ + \inference{\langle E_1, \sigma, \eta, \ket{\psi}\rangle\Downarrow l_1 \quad \langle E_2, \sigma, \eta, \ket{\psi}\rangle\Downarrow l_2} + {\langle \texttt{cx}(E_1, E_2), \sigma, \eta, \ket{\psi}\rangle\Downarrow \cnot_{l_1, l_2}\ket{\psi}} \; + \inference{\langle E, \sigma, \eta, \ket{\psi}\rangle\Downarrow \lambda x_1,\dots,x_n.U, \\ + \langle U\{E_1/x_1, \dots, E_n/x_n\}, \sigma, \eta, \ket{\psi}\rangle \Downarrow \ket{\psi'}} + {\langle E(E_1,\dots, E_n), \sigma, \eta, \ket{\psi}\rangle \Downarrow \ket{\psi'}} \\ + \inference{\langle U_1, \sigma, \eta, \ket{\psi}\rangle \Downarrow \ket{\psi'} \qquad + \langle U_2, \sigma, \eta, \ket{\psi'}\rangle \Downarrow \ket{\psi''}} + {\langle \texttt{$U_1$; $U_2$}, \sigma, \eta, \ket{\psi}\rangle \Downarrow\ket{\psi''}} + \end{gather*} +Commands: + \begin{gather*} + \inference{l_0,\dots, l_{I-1} \text{ are fresh heap indices}} + {\langle\texttt{creg $x[I]$}, \sigma, \eta, \ket{\psi}\rangle \Downarrow \langle \sigma[x\leftarrow (l_0,\dots, l_{I-1})],\eta,\ket{\psi}\rangle} \\ + \inference{l_0,\dots, l_{I-1} \text{ are fresh qubit indices}} + {\langle\texttt{qreg $x[I]$}, \sigma,\eta, \ket{\psi}\rangle \Downarrow \langle \sigma[x\leftarrow (l_0,\dots, l_{I-1})],\eta,\ket{\psi}\rangle} \\ + \inference{} + {\langle\texttt{gate $x(x_1,\dots, x_n)$ \{ $U$ \}}, \sigma,\eta, \ket{\psi}\rangle \Downarrow + \langle \sigma[x\leftarrow \lambda x_1,\dots,x_n.U],\eta,\ket{\psi}\rangle} \\ + \inference{\langle E_1, \sigma, \eta, \ket{\psi}\rangle\Downarrow l_1 \qquad \langle E_2, \sigma, \eta, \ket{\psi}\rangle\Downarrow l_2} {\langle \texttt{measure $E_1$ -> $E_2$}, \sigma, \eta, \ket{\psi}\rangle \Downarrow + \langle\sigma, \eta[l_2\leftarrow 0], P_{l_1}^0\ket{\psi}\rangle} \\ + \inference{\langle E_1, \sigma, \eta, \ket{\psi}\rangle\Downarrow l_1 \qquad \langle E_2, \sigma, \eta, \ket{\psi}\rangle\Downarrow l_2} + {\langle \texttt{measure $E_1$ -> $E_2$}, \sigma, \eta, \ket{\psi}\rangle \Downarrow + \langle\sigma, \eta[l_2\leftarrow 1], P_{l_1}^1\ket{\psi}\rangle} \\ + \inference{\langle E, \sigma, \eta, \ket{\psi}\rangle\Downarrow l} + {\langle \texttt{reset $E$}, \sigma, \eta, \ket{\psi}\rangle \Downarrow \langle\sigma, \eta, P_{l}^0\ket{\psi}\rangle} \quad + \inference{\langle E, \sigma, \eta, \ket{\psi}\rangle\Downarrow l \qquad \eta(l)\neq I} + {\langle \texttt{if($E$==$I$) \{ $U$ \}}, \sigma, \eta, \ket{\psi}\rangle \Downarrow \langle \sigma, \eta, \ket{\psi}\rangle}\\ + \inference{\langle E, \sigma, \eta, \ket{\psi}\rangle\Downarrow l \qquad \eta(l)=I \\ + \langle U, \sigma, \eta, \ket{\psi}\rangle \Downarrow \ket{\psi'}} + {\langle \texttt{if($E$==$I$) \{ $U$ \}}, \sigma, \eta, \ket{\psi}\rangle \Downarrow \langle \sigma, \eta, \ket{\psi'}\rangle} \\ + \inference{\langle C_1, \sigma, \eta, \ket{\psi}\rangle \Downarrow \langle \sigma', \eta', \ket{\psi'}\rangle \qquad + \langle C_2, \sigma', \eta', \ket{\psi'}\rangle \Downarrow \langle \sigma'', \eta'', \ket{\psi''}\rangle} + {\langle \texttt{$C_1$; $C_2$}, \sigma, \eta, \ket{\psi}\rangle \Downarrow \langle \sigma'', \eta'', \ket{\psi''}\rangle} + \end{gather*} + \caption{openQASM semantics}\label{fig:semantics} +\end{figure} + +\section{Adding types to QASM}\label{sec:tqasm} + +Run-time errors may occur in syntactically valid openQASM programs in a number of ways -- particularly when either an array access is out of bounds and the program halts, or a classical (resp. quantum) location is used in a context when a quantum (resp. classical) location is expected. In the official openQASM specification, the latter error is eliminated by the requirement that only (global) variables can be declared as quantum registers may be used as arguments to gates, for instance. In either case however, it is desirable to check that an openQASM program \emph{will not go wrong}, as circuit simulations are frequently run on large, expensive supercomputers (e.g., \cite{hs17}). + +In this section we developed a typed variant of openQASM, called typedQASM, which provably rules out such runtime errors. Moreover, the type system uses \emph{sized types} to eliminate out-of-bound accesses, which we later develop into the core of our metaprogramming type system. The use of a type system in this case actually allows \emph{more} valid programs to be written than the standard openQASM specification, as the type system allows us to remove some syntactic distinctions and instead make them in the type system. In particular, our type system allows registers and circuits to be passed as functions to other circuits, whereas the formal specification restricts circuit arguments to only individual qubits. + +\begin{figure}[t] + \begin{tabular}{rrl} + Base types $\beta$ & $::=$ & \texttt{Bit} $\mid$ \texttt{Qbit} \\ + Types $\tau$ & $::=$ & $\beta$ $\mid$ $\beta[I]$ $\mid$ \texttt{Circuit}$(\tau_1,\dots, \tau_n)$ \\ + Command $C$ & $::=$ & $\dots$ $\mid$ \texttt{creg $x[I]$ in \{ $C$ \}} $\mid$ \texttt{qreg $x[I]$ in \{ $C$ \}} \\ + & $\mid$ & \texttt{gate $x(x_1:\tau_1,\dots, x_n:\tau_n)$ \{ $U$ \} in \{ $C$ \}} + \end{tabular} + \caption{typedQASM specification}\label{fig:typedsyntax} +\end{figure} + +\Cref{fig:typedsyntax} gives the syntax of typedQASM. We only show the syntactic elements which are different from openQASM or otherwise new. To simplify our analysis, declarations are given explicit block scope, though we leave textual examples in the regular openQASM style of declaration. As the semantics of typedQASM is effectively identical, modulo the block scoping, to openQASM we don't explicitly give the semantics. + +\subsection{The type system} + +Figure~\ref{fig:typing} gives the rules of our type system. As is standard, the judgement $\Gamma\vdash S:\tau$ states that in the context $\Gamma$ consisting of pairs of identifiers and types, $S$ can be assigned type $\tau$. We overload $\vdash$ to allow environment judgements of the form $\vdash \sigma:\Gamma$ stating that the $\sigma$ maps identifiers $x$ to values of the type $\tau$ if $x:\tau\in\Gamma$. + +\begin{figure}[h] +Environment:\vspace*{-1em} + \begin{gather*} + \inference{}{\vdash \cdot:\cdot} \quad + \inference{\vdash \sigma:\Gamma}{\vdash \sigma[x\leftarrow (l_0,\dots, l_{I-1})]:\Gamma, x:\beta[I]} \\ + \inference{\vdash \sigma:\Gamma \qquad + \Gamma, x_1:\tau_1,\dots, x_n:\tau_n\vdash U:\texttt{Unit}} + {\vdash \sigma[x\leftarrow \lambda x_1:\tau_1,\dots, x_n:\tau_n. U]:\Gamma,x:\texttt{Circuit$(\tau_1,\dots,\tau_n)$}} + \end{gather*} +Expressions: + \begin{gather*} + \inference{x:\tau\in\Gamma}{\Gamma\vdash x : \tau} \quad + \inference{\Gamma \vdash x:\beta[I'] \qquad I \leq I'-1}{\Gamma\vdash x[I] : \beta} \quad + \inference{\Gamma \vdash E:\beta[I'] \qquad I \leq I'}{\Gamma\vdash E:\beta[I]} + \end{gather*} +Unitary statements: + \begin{gather*} + \inference{\Gamma\vdash E_1:\texttt{Qbit} \qquad \Gamma\vdash E_2:\texttt{Qbit}} + {\Gamma\vdash \texttt{cx}(E_1,E_2):\texttt{Unit}} \quad + \inference{\Gamma\vdash E:\texttt{Qbit} \qquad g\in \{\texttt{h},\texttt{t},\texttt{tdg}\}}{\Gamma\vdash g(E):\texttt{Unit}} \\ + \inference{\Gamma\vdash E:\texttt{Circuit}(\tau_1,\dots, \tau_n) \\ + \Gamma\vdash E_1:\tau_1 \quad \cdots \quad \Gamma\vdash E_n:\tau_n} + {\Gamma\vdash E(E_1,\dots, E_n):\texttt{Unit}} \quad + \inference{\Gamma\vdash U_1:\texttt{Unit} \qquad \Gamma\vdash U_2:\texttt{Unit}}{\Gamma\vdash \texttt{$U_1$; $U_2$}:\texttt{Unit}} + \end{gather*} +Commands: + \begin{gather*} + \inference{\Gamma,x:\texttt{Bit}[I]\vdash C:\texttt{Unit}}{\Gamma\vdash \texttt{creg $x[I]$ in \{ $C$ \}}:\texttt{Unit}} \quad + \inference{\Gamma,x:\texttt{Qbit}[I]\vdash C:\texttt{Unit}}{\Gamma\vdash \texttt{qreg $x[I]$ in \{ $C$ \}}:\texttt{Unit}} \\ + \inference{\Gamma, x_1:\tau_1,\dots,x_n:\tau_n\vdash U:\texttt{Unit} + \quad \Gamma,x:\texttt{Circuit($\tau_1,\dots, \tau_n$)}\vdash C:\texttt{Unit}} + {\Gamma\vdash \texttt{gate $x(x_1:\tau_1,\dots, x_n:\tau_n)$ \{ $U$ \} in \{ $C$ \}}:\texttt{Unit}} \\ + \inference{\Gamma\vdash E_1:\texttt{Qbit} \qquad \Gamma\vdash E_2:\texttt{Bit}} + {\Gamma\vdash \texttt{measure $E_1$ -> $E_2$}:\texttt{Unit}} \quad + \inference{\Gamma\vdash E:\texttt{Qbit}}{\Gamma\vdash \texttt{reset $E$}:\texttt{Unit}} \\ + \inference{\Gamma\vdash E:\texttt{Bit} \qquad \Gamma\vdash U:\texttt{Unit}} + {\Gamma\vdash \texttt{if($E$==$I$) \{ $U$ \}}:\texttt{Unit}} \quad + \inference{\Gamma\vdash C_1:\texttt{Unit} \qquad \Gamma\vdash C_2:\texttt{Unit}}{\Gamma\vdash \texttt{$C_1$; $C_2$}:\texttt{Unit}} + \end{gather*} + \caption{typedQASM typing rules}\label{fig:typing} +\end{figure} + +The type system of typedQASM is mostly as expected, with the exception of static-length registers and register bounds checks in the typing rules for dereferences. To give the programmer flexibility to apply gates and circuits to just parts of a larger register -- for instance, when performing an $n$-bit addition into a length $2n$ register as in binary multiplication -- the type system also implicitly supports subtyping of static length registers. Specifically, any length $I$ array can be used in a context requiring \emph{at most} $I$ cells. While this adds a great deal of flexibility on the side of the programmer, as a downside typedQASM typing derivations are not unique. %It is left as a question for practical implementations to give an algorithmic type-checking algorithm. + +As an example of a well-typed QASM program, we show an implementation of the Toffoli circuit from \Cref{fig:toffoli} below: +\begin{lstlisting} +gate toffoli(x:Qbit, y:Qbit, z:Qbit) { + h(z); + t(x); t(y); t(z); + cx(x,y); cx(x,z); + tdg(y); tdg(z); + cx(y,z); cx(z,x); + t(x); tdg(z); + cx(z,x); cx(x,y); cx(y,z); + h(z) +} +\end{lstlisting} + +\subsection{Type safety} + +We now briefly sketch a proof of type safety for typedQASM. In particular, we show that typedQASM is strongly normalizing, as expected. + +As is standard, we establish strong normalization by giving type preservation and progress lemmas. While type preservation is effectively implicit in the semantics of typedQASM due to the different syntactic classes, expressions may return different types of values and so we give a form of type preservation for such terms. +\begin{lemma}[Preservation (expressions)] +If $\Gamma\vdash E:\tau$, $\vdash \sigma:\Gamma$ and $\langle E, \sigma, \eta, \ket{\psi}\rangle \Downarrow v$, then either +\begin{itemize} + \item $\tau=\beta$ and $v=l$ for some base type $\beta$ \& location $l$, + \item $\tau=\beta[I]$ and $v=(l_0,\dots, l_{I'})$ where $I'\geq I$, or + \item $\tau=\textnormal{\texttt{Circuit$(\tau_1,\dots,\tau_n)$}}$ and $v=\lambda x_1:\tau_1, \dots, x_n:\tau_n. U$. +\end{itemize} +\end{lemma} +\begin{proof} +If $\tau=\beta$ then we must have $E=x[I]$, hence by the definition of $\Downarrow$, $v=l$. Likewise if $\tau=\beta[I]$ then we must have $E=x$ where $x:\beta[I']\in\Gamma$ for some $I'\geq I$, and since $\vdash \sigma:\Gamma$ then $v=\sigma(x)=(l_0,\dots, l_{I'})$. The case for $\tau=\texttt{Circuit$(\tau_1,\dots,\tau_n)$}$ is similar. +\end{proof} + +The following lemmas give progress properties -- the fact that for a well-typed program, evaluation can always continue -- for the different syntactic classes of typedQASM. Together with type preservation, the result is that any well-typed typedQASM program evaluates to a value, i.e. that typedQASM is strongly normalizing. +\begin{lemma}[Progress (expressions)] +If $\Gamma\vdash E:\tau$ and $\vdash \sigma:\Gamma$, then for any $\eta, \ket{\psi}$, $\langle E, \sigma, \eta, \ket{\psi}\rangle\Downarrow v$. +\end{lemma} +\begin{proof} +By case analysis on $E$. If $E=x$ the proof is trivial, as $x:\tau\in\Gamma$ by inversion and $\vdash \sigma:\Gamma$ implies $x\in\textsf{dom}(x)$. If on the other hand $E=x[I]$, we must have $x:\beta[I']\in\Gamma$ for some $I'>I$. Then by preservation, $\langle x, \sigma, \eta, \ket{\psi}\rangle\Downarrow (l_0,\dots, l_{I''})$ for some $I''\geq I'-1$, hence $\langle x, \sigma, \eta, \ket{\psi}\rangle\Downarrow l_I$ +\end{proof} + +\begin{lemma}[Progress (unitary stmts)] +If $\Gamma\vdash U:\textnormal{\texttt{Unit}}$ and $\vdash \sigma:\Gamma$, then for any $\eta, \ket{\psi}$, $\langle U, \sigma, \eta, \ket{\psi}\rangle\Downarrow \ket{\psi'}$. +\end{lemma} +\begin{proof} +For the case $U=E(E_1,\dots,E_n)$, by the typing derivation we have $\Gamma\vdash E:\texttt{Circuit$(\tau_1,\dots,\tau_n)$}$ so by progress and preservation for expressions, $\langle E, \sigma, \eta, \ket{\psi}\rangle \Downarrow \lambda x_1:\tau_1, \dots, x_n:\tau_n. U$. By the substitution lemma below, $\Gamma\vdash U\{E_1/x_1,\dots, E_n/x_n\}:\textnormal{\texttt{Unit}}$ and hence we can structural induction to show that $\langle U, \sigma, \eta, \ket{\psi}\rangle\Downarrow \ket{\psi'}$. + +\begin{lemma}[Substitution] +If $\Gamma, x_1:\tau_1,\dots, x_n:\tau_n\vdash U:\textnormal{\texttt{Unit}}$, and $\Gamma\vdash E_i:\tau_i$ for each $1\leq i\leq n$ then $\Gamma\vdash U\{E_1/x_1,\dots, E_n/x_n\}:\textnormal{\texttt{Unit}}$ +\end{lemma} +\end{proof} + +\begin{lemma}[Progress (commands)]\label{lem:pcommands} +If $\Gamma\vdash C:\textnormal{\texttt{Unit}}$ and $\vdash \sigma:\Gamma$, then for any $\eta, \ket{\psi}$, $\langle C, \sigma, \eta, \ket{\psi}\rangle\Downarrow \langle \sigma', \eta', \ket{\psi'}\rangle$. +\end{lemma} +\begin{proof} +Proof by induction on the structure of $C$. We show one case: $$C=\texttt{gate $x(x_1:\tau_1,\dots, x_n:\tau_n)$ \{ $U$ \} in \{ $C$ \}}$$ + +We know that +\[ +\resizebox{\linewidth}{!}{$ + \langle \texttt{gate $x(x_1:\tau_1,\dots, x_n:\tau_n)$ \{ $U$ \}}, \sigma, \eta, \ket{\psi}\rangle \Downarrow + \langle \sigma[x\leftarrow \lambda x_1,\dots,x_n.U],\eta,\ket{\psi}\rangle. +$} +\] +By the typing derivation, $\Gamma,x_1:\tau_1,\dots, x_n:\tau_n\vdash U:\texttt{Unit}$ and $\Gamma,x:\texttt{Circuit$(\tau_1,\dots, \tau_n)$}\vdash C:\texttt{Unit}$. It then follows that \[\vdash \sigma[x\leftarrow \lambda x_1:\tau_1,\dots, x_n:\tau_n. U]:\Gamma,x:\texttt{Circuit$(\tau_1,\dots, \tau_n)$},\] and hence we can apply the inductive hypothesis to complete the case. + +The remaining cases are similar. +\end{proof} + +\begin{theorem}[Strong normalization] +If $\vdash C:\textnormal{\texttt{Unit}}$, then $$\langle C, \emptyset, \lambda l.0, \ket{00\cdots}\rangle\Downarrow \langle \sigma, \eta, \ket{\psi}\rangle.$$ +\end{theorem} +\begin{proof} +Direct consequence of \Cref{lem:pcommands}. +\end{proof} + +\section{MetaQASM}\label{sec:mqasm} + +Now that we have a safe, array-bounds-checked, typed language, we can add metaprogramming features. In particular, we wish to support\footnote{Controlled circuits are another desirable metaprogramming feature found in many quantum circuit description languages. While metaQASM gates are in fact closed over qubit controls, they require \emph{ancillae} to construct \cite{kmm12}. This complicates the inclusion of a control instruction in metaQASM, and further abstracts away from concrete, resource-driven nature of QASM.} +\begin{itemize} + \item circuit inversion/reversal, and + \item circuits parametrized by sizes. +\end{itemize} +While the latter could be accomplished in an ad-hoc way, allowing \emph{type-level} integers allows for more safety in that array bounds can be statically checked, and increases the readability of programs. Moreover, it enforces a clear separation between circuits and families of circuits, which naturally support different operations -- for instance, a family of circuits can't easily be visualized diagrammatically, while a particular instance can \cite{prz17}. + +\begin{figure}[t] + \resizebox{\textwidth}{!}{ + \begin{tabular}{rrl} + Types $\tau$ & $::=$ & $\dots$ $\mid$ \texttt{Family$(y_1,\dots, y_m)(\tau_1,\dots, \tau_n)$} \\ + Index $I$ & $::=$ & $\dots$ $\mid$ $y$ $\mid$ $\infty$ $\mid$ $I_1 + I_2$ $\mid$ $I_1 - I_2$ $\mid$ $I_1\cdot I_2$ \\ + Range $\iota$ & $::=$ & [$I_1$,$I_2$] \\ + Expression $E$ & $::=$ & $\dots$ $\mid$ \texttt{instance$(I_1, \dots, I_m)$ $E$} \\ + Unitary Stmt $U$ & $::=$ & $\dots$ $\mid$ \texttt{reverse $U$} $\mid$ \texttt{for $y=I_1..I_2$ do \{ $U$ \}} \\ + Command $C$ & $::=$ & $\dots$ $\mid$ + \texttt{family$(y_1,\dots, y_m)$ $x(x_1:\tau_1,\dots, x_n:\tau_n)$ \{ $U$ \} in \{ $C$ \}} \\ + Value $V$ & $::=$ & $\dots$ $\mid$ $\Pi y_1,\dots, y_m.V$ + \end{tabular} + } + \caption{metaQASM syntax}\label{fig:msyntax} +\end{figure} + +Figure~\ref{fig:msyntax} gives the new syntax for metaQASM. Indices $I$ are extended with index variables $y$ and integer arithmetic, and a new syntactic form defining a family of quantum circuits parametrized over index variables is given. The index $\infty$ only exists in the process of type checking and is not valid syntax in source code. Intuitively, the declaration \[\texttt{family$(y_1,\dots, y_m)$ $x(x_1:\tau_1,\dots, x_n:\tau_n)$ \{ $U$ \} in \{ $C$ \}}\] introduces index variables $y_1, \dots, y_m$ into the evaluation and type checking contexts for $\tau_i$ and $U$. + +Figure~\ref{fig:msemantics} gives the semantics of the new syntax. Since index variables cannot be modified or captured, we use a substitution style of evaluation for circuit families. The \texttt{reverse} command introduces a new reduction relation $\langle \texttt{$U$}, \sigma, \ket{\psi}\rangle\Uparrow v$ for which reduction of $U$ is inverted. We give a concrete semantics rather than an abstract rule such as +\[ + \inference{\langle \texttt{$U$}, \sigma, \eta, \ket{\psi'}\rangle\Downarrow \ket{\psi}} + {\langle \texttt{reverse $U$}, \sigma, \eta, \ket{\psi}\rangle\Downarrow \ket{\psi'}} +\] +so that metaQASM has a concrete execution model. Inversion of circuits is straightforward in metaQASM, as in any closed context a unitary statement can be statically unrolled to a finite sequence of gates. + +\begin{figure} +Indices: +\vspace{-1em} + \begin{gather*} + \inference{}{\langle i, \sigma, \eta, \ket{\psi}\rangle\Downarrow i} \qquad + \inference{\langle I_1, \sigma, \eta, \ket{\psi}\rangle\Downarrow i_1 \quad + \langle I_2, \sigma, \eta, \ket{\psi}\rangle\Downarrow i_2 \quad + \star\in\{+, -, \cdot\}} + {\langle I_1\star I_2, \sigma, \eta, \ket{\psi}\rangle\Downarrow i_1 \star i_2} + \end{gather*} +Expressions: + \begin{gather*} + \inference{\langle E, \sigma, \eta, \ket{\psi}\Downarrow \Pi y_1,\dots, y_m.\lambda x_1:\tau_1,\dots, x_n:\tau_n. U} + {\langle \texttt{instance$(I_1, \dots, I_m)$ $E$}, \sigma, \eta, \ket{\psi} \Downarrow (\lambda x_1:\tau_1,\dots, x_n:\tau_n. U)\{I_1/y_1, \dots, I_m/y_m\}} + \end{gather*} +Unitary statements: + \begin{gather*} + \inference{\langle \texttt{$U$}, \sigma, \eta, \ket{\psi}\rangle\Uparrow \ket{\psi'}} + {\langle \texttt{reverse $U$}, \sigma, \eta, \ket{\psi}\rangle\Downarrow \ket{\psi'}} \quad + \inference{\langle I_1, \sigma, \eta, \ket{\psi}\rangle\Downarrow i_1 \quad + \langle I_2, \sigma, \eta, \ket{\psi}\rangle\Downarrow i_2 \quad i_1>i_2} + {\langle \texttt{for $y=I_1..I_2$ do \{ $U$ \}}, \sigma, \eta, \ket{\psi}\rangle \Downarrow \ket{\psi}} \\ + \inference{\langle I_1, \sigma, \eta, \ket{\psi}\rangle\Downarrow i_1 \quad + \langle I_2, \sigma, \eta, \ket{\psi}\rangle\Downarrow i_2 \quad i_1\leq i_2 \\ + \langle U\{i_1/y\}, \sigma, \eta, \ket{\psi}\rangle \Downarrow \ket{\psi'} \\ + \langle \texttt{for $y=i_1+1..i_2$ do \{ $U$ \}}, \sigma,\eta, \ket{\psi'}\rangle \Downarrow \ket{\psi''}} + {\langle \texttt{for $y=I_1..I_2$ do \{ $U$ \}}, \sigma,\eta, \ket{\psi}\rangle \Downarrow \ket{\psi''}} + \end{gather*} +Reverse reduction: + \begin{gather*} + \inference{\langle E, \sigma, \eta, \ket{\psi}\rangle\Downarrow l} + {\langle \texttt{h$(E)$}, \sigma,\eta, \ket{\psi}\rangle \Uparrow H_{l}\ket{\psi}} \; + \inference{\langle E, \sigma, \eta, \ket{\psi}\rangle\Downarrow l} + {\langle \texttt{t$(E)$}, \sigma,\eta, \ket{\psi}\rangle \Uparrow T_{l}^\dagger\ket{\psi}} \; + \inference{\langle E, \sigma, \eta, \ket{\psi}\rangle\Downarrow l} + {\langle \texttt{tdg$(E)$}, \sigma,\eta, \ket{\psi}\rangle \Uparrow T_{l}\ket{\psi}} \\ + \inference{\langle E_1, \sigma, \eta, \ket{\psi}\rangle\Downarrow l_1 \quad \langle E_2, \sigma, \eta, \ket{\psi}\rangle\Downarrow l_2} + {\langle \texttt{cx$(E_1, E_2)$}, \sigma,\eta, \ket{\psi}\rangle\Uparrow \cnot_{l_1, l_2}\ket{\psi}} \; + \inference{\langle E, \sigma, \eta, \ket{\psi}\rangle\Downarrow \lambda x_1,\dots,x_n.U, \\ + \langle U\{E_1/x_1, \dots, E_n/x_n\}, \sigma, \eta, \ket{\psi}\rangle \Uparrow \ket{\psi'}} + {\langle E(E_1,\dots, E_n), \sigma,\eta, \ket{\psi}\rangle \Uparrow \ket{\psi'}} \\ + \inference{\langle U_2, \sigma, \eta, \ket{\psi}\rangle \Uparrow \ket{\psi'} \qquad + \langle U_1, \sigma, \eta, \ket{\psi'}\rangle \Uparrow \ket{\psi''}} + {\langle U_1 ;\; U_2, \sigma, \eta, \ket{\psi}\rangle \Uparrow\ket{\psi''}} \\ + \inference{\langle \texttt{$U$}, \sigma, \eta, \ket{\psi}\rangle\Downarrow \ket{\psi'}} + {\langle \texttt{reverse $U$}, \sigma, \eta, \ket{\psi}\rangle\Uparrow \ket{\psi'}} \quad + \inference{\langle I_1, \sigma, \eta, \ket{\psi}\rangle\Downarrow i_1 \quad + \langle I_2, \sigma, \eta, \ket{\psi}\rangle\Downarrow i_2 \quad i_2 +#include + +#include "token.hpp" + +namespace tools_v1 { +namespace parser { + +/** + * \class tools_v1::parser::Lexer + * \brief openPARSER lexer class + * + * The Lexer reads from (a shared_ptr to) an istream object given during + * initialization. Rather than lex the entire buffer at once, tokens are + * lexed and returned on-demand + */ +class Lexer { + public: + Lexer(const Lexer&) = delete; + Lexer& operator=(const Lexer&) = delete; + + Lexer(std::shared_ptr buffer, const std::string& fname = "") + : pos_(fname, 1, 1), buf_(buffer) {} + + /** + * \brief Lex and return the next token + * + * \note Advances the buffer to the position after the consumed token + * + * \return The token that was lexed + */ + Token next_token() { return lex(); } + + private: + Position pos_; ///< current position in the source stream + std::shared_ptr buf_; ///< stream buffer being lexed + + /** + * \brief Skips the specified number of characters + * + * \param n The number of characters to skip ahead (optional, default is 1) + */ + void skip_char(int n = 1) { + buf_->ignore(n); + pos_.advance_column(n); + } + + /** + * \brief Skips over whitespace + * + * \return True if and only if whitespace was actually consumed + */ + bool skip_whitespace() { + int consumed = 0; + + while (buf_->peek() == ' ' || buf_->peek() == '\t') { + buf_->ignore(); + ++consumed; + } + + pos_.advance_column(consumed); + if (consumed != 0) { + return true; + } else { + return false; + } + } + + /** + * \brief Skips the rest of the line + */ + void skip_line_comment() { + int consumed = 0; + + int c = buf_->peek(); + while (c != 0 && c != '\n' && c != '\r' && c != EOF) { + buf_->ignore(); + ++consumed; + c = buf_->peek(); + } + + pos_.advance_column(consumed); + } + + /** + * \brief Lex a numeric constant + * + * \note [0-9]+(.[0-9]*)([eE][+-][0-9]+)? + * + * \param tok_start The position of the beginning of the token + * \return An integer or real type token + */ + Token lex_numeric_constant(Position tok_start) { + std::string str; + str.reserve(64); // Reserve space to avoid reallocation + bool integral = true; + + while (std::isdigit(buf_->peek())) { + str.push_back(buf_->peek()); + skip_char(); + } + + // lex decimal + if (buf_->peek() == '.') { + integral = false; + str.push_back(buf_->peek()); + skip_char(); + + while (std::isdigit(buf_->peek())) { + str.push_back(buf_->peek()); + skip_char(); + } + } + + // lex exponent + if (buf_->peek() == 'e' || buf_->peek() == 'E') { + integral = false; + str.push_back(buf_->peek()); + skip_char(); + + if (buf_->peek() == '-' || buf_->peek() == '+') { + str.push_back(buf_->peek()); + skip_char(); + } + + while (std::isdigit(buf_->peek())) { + str.push_back(buf_->peek()); + skip_char(); + } + } + + if (integral) { + return Token(tok_start, Token::Kind::nninteger, str, + std::stoi(str)); + } else { + return Token(tok_start, Token::Kind::real, str, std::stof(str)); + } + } + + /** + * \brief Lex an identifier + * + * \note [A-Za-z][_A-Za-z0-9]* + * + * \param tok_start The position of the beginning of the token + * \return An identifier type token + */ + Token lex_identifier(Position tok_start) { + std::string str; + str.reserve(64); // Reserve space to avoid reallocation + + while (std::isalpha(buf_->peek()) || std::isdigit(buf_->peek()) || + buf_->peek() == '_') { + str.push_back(buf_->peek()); + skip_char(); + } + + // Check if the identifier is a known keyword + auto keyword = keywords.find(str); + if (keyword != keywords.end()) { + return Token(tok_start, keyword->second, str); + } + + return Token(tok_start, Token::Kind::identifier, str, str); + } + + /** + * \brief Lex a string literal + * + * \note "[.]*" + * + * \param tok_start The position of the beginning of the token + * \return A string type token + */ + Token lex_string(Position tok_start) { + std::string str; + str.reserve(64); // Reserve space to avoid reallocation + + while (buf_->peek() != '"' && buf_->peek() != '\n' && + buf_->peek() != '\r') { + str.push_back(buf_->peek()); + skip_char(); + } + + if (buf_->peek() != '"') { + std::cerr << "Lexical error at " << tok_start << ": unmatched \"\n"; + return Token(tok_start, Token::Kind::error, str); + } + + skip_char(); + return Token(tok_start, Token::Kind::string, str, str); + } + + /** + * \brief Lex a token + * + * \note See arXiv:1707.03429 for the full grammar + * + * \return The lexed token + */ + Token lex() { + Position tok_start = pos_; + skip_whitespace(); + + switch (buf_->peek()) { + case EOF: + skip_char(); + return Token(tok_start, Token::Kind::eof, ""); + + case '\r': + skip_char(); + if (buf_->peek() != '\n') { + buf_->ignore(); + pos_.advance_line(); + return lex(); + } + // FALLTHROUGH + case '\n': + buf_->ignore(); + pos_.advance_line(); + return lex(); + + case '/': + skip_char(); + if (buf_->peek() == '/') { + skip_line_comment(); + return lex(); + } + return Token(tok_start, Token::Kind::slash, "/"); + + // clang-format off + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + case '.': + return lex_numeric_constant(tok_start); + // clang-format on + + case 'C': + skip_char(); + if (buf_->peek() == 'X') { + skip_char(); + return Token(tok_start, Token::Kind::kw_cx, "CX"); + } + + skip_char(); + std::cerr + << "Lexical error at " << tok_start + << ": identifiers must start with lowercase letters\n"; + return Token(tok_start, Token::Kind::error, + std::string({'C', (char)buf_->get()})); + + case 'U': + skip_char(); + return Token(tok_start, Token::Kind::kw_u, "U"); + + // clang-format off + case 'O': + case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': + case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': + case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': + case 'v': case 'w': case 'x': case 'y': case 'z': + return lex_identifier(tok_start); + // clang-format on + + case '[': + skip_char(); + return Token(tok_start, Token::Kind::l_square, "["); + + case ']': + skip_char(); + return Token(tok_start, Token::Kind::r_square, "]"); + + case '(': + skip_char(); + return Token(tok_start, Token::Kind::l_paren, "("); + + case ')': + skip_char(); + return Token(tok_start, Token::Kind::r_paren, ")"); + + case '{': + skip_char(); + return Token(tok_start, Token::Kind::l_brace, "{"); + + case '}': + skip_char(); + return Token(tok_start, Token::Kind::r_brace, "}"); + + case '*': + skip_char(); + return Token(tok_start, Token::Kind::star, "*"); + + case '+': + skip_char(); + return Token(tok_start, Token::Kind::plus, "+"); + + case '-': + skip_char(); + + if (buf_->peek() == '>') { + skip_char(); + return Token(tok_start, Token::Kind::arrow, "->"); + } + + return Token(tok_start, Token::Kind::minus, "-"); + + case '^': + skip_char(); + return Token(tok_start, Token::Kind::caret, "^"); + + case ';': + skip_char(); + return Token(tok_start, Token::Kind::semicolon, ";"); + + case '=': + skip_char(); + if (buf_->peek() == '=') { + skip_char(); + return Token(tok_start, Token::Kind::equalequal, "=="); + } + + skip_char(); + std::cerr << "Lexical error at " << tok_start + << ": expected \"=\" after \"=\"\n"; + return Token(tok_start, Token::Kind::error, + std::string({'=', (char)buf_->get()})); + + case ',': + skip_char(); + return Token(tok_start, Token::Kind::comma, ";"); + + case '"': + skip_char(); + return lex_string(tok_start); + + default: + skip_char(); + return Token(tok_start, Token::Kind::error, + std::string({(char)buf_->get()})); + } + } +}; + +} /* namespace parser */ +} /* namespace tools_v1 */ + +#endif /* TOOLS_V1_PARSER_LEXER_HPP_ */ diff --git a/experimental/include/tools_v1/parser/parser.hpp b/experimental/include/tools_v1/parser/parser.hpp new file mode 100644 index 00000000..7ad72b91 --- /dev/null +++ b/experimental/include/tools_v1/parser/parser.hpp @@ -0,0 +1,1062 @@ +/* + * This file is part of tools_v1. + * + * Copyright (c) 2019 - 2025 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. + * + * Adapted from Bruno Schmitt's Tweedledee library + */ + +/** + * \file tools_v1/parser/parser.hpp + * \brief OpenQASM parsing + */ + +#ifndef TOOLS_V1_PARSER_PARSER_HPP_ +#define TOOLS_V1_PARSER_PARSER_HPP_ + +#include + +#include "../ast/ast.hpp" +#include "preprocessor.hpp" + +namespace tools_v1 { +namespace parser { + +/** + * \class tools_v1::parser::ParseError + * \brief Exception class for parse errors + */ +class ParseError : public std::exception { + public: + ParseError() noexcept = default; + ~ParseError() = default; + const char* what() const noexcept { return "Parse error(s)"; } +}; + +/** + * \class tools_v1::parser::Parser + * \brief OpenQASM parser class + * \see tools_v1::parser::Preprocessor + */ +class Parser { + Preprocessor& pp_lexer_; ///< preprocessed, tokenized input stream + + bool error_ = false; ///< whether a parse error has occured + bool supress_errors_ = false; ///< whether to supress errors + Token current_token_; ///< current token + int bits_ = 0; ///< number of bits + int qubits_ = 0; ///< number of qubits + + public: + /** + * \brief Constructs a parser for a given preprocessor stream + * + * \param pp_lexer The preprocessed lexer stream to be parsed + */ + Parser(Preprocessor& pp_lexer) + : pp_lexer_(pp_lexer), current_token_(pp_lexer_.next_token()) {} + + /** + * \brief Parses the tokenized stream as a QCircuit object + * + * \return A unique pointer to a QCircuit object + */ + ast::ptr parse(bool check = true) { + // Parse the program + auto result = parse_program(); + if (error_) { + throw ParseError(); + } + + // Perform semantic analysis before returning + if (check) { + ast::check_source(*result); + } + + return result; + } + + private: + /** + * \brief Consume a token and retrieve the next one + * + * \param reset Whether to unsupress errors (optional, default is false) + */ + void consume_token(bool reset = false) { + current_token_ = pp_lexer_.next_token(); + if (reset) { + supress_errors_ = false; + } + } + + /** + * \brief Consume a particular type of token + * + * Checks that the current token is of type given by the argument + * and consumes the token, setting the error flag if it is not + * of the correct type + * + * \param expected The type of token to be consumed + * \return The current token + */ + Token expect_and_consume_token(Token::Kind expected) { + if (current_token_.is_not(expected)) { + error_ = true; + if (!supress_errors_) { + std::cerr << current_token_.position(); + std::cerr << ": expected " << expected; + std::cerr << " but got " << current_token_.kind() << "\n"; + ; + supress_errors_ = true; + } + return current_token_; + } + + auto return_token = current_token_; + consume_token(); + return return_token; + } + + /** + * \brief Consume tokens until a particular token is found + * + * Repeatedly skips tokens, setting the error flag if necessary, + * until the given token or eof is found + * + * \param expected The type of token to be consumed + * \return The next expected token, or eof + */ + Token consume_until(Token::Kind expected) { + while (current_token_.is_not(expected) && + current_token_.is_not(Token::Kind::eof)) { + error_ = true; + if (!supress_errors_) { + std::cerr << current_token_.position(); + std::cerr << ": expected " << expected; + std::cerr << " but got " << current_token_.kind() << "\n"; + ; + supress_errors_ = true; + } + consume_token(); + } + + auto return_token = current_token_; + consume_token(true); + return return_token; + } + + /** + * \brief Try to consume a particular type of token + * + * Attempts to parse a particular type of token, only consuming + * the token if it is of the correct type + * + * \param expected The type of token to be consumed + * \return True if and only if a token of type expected was consumed + */ + bool try_and_consume_token(Token::Kind expected) { + if (current_token_.is_not(expected)) { + return false; + } + consume_token(); + return true; + } + + /** + * \brief Parse an OpenQASM 2.0 program + * + * = OPENQASM ; + * = | + * = + * | } + * | } + * | ; + * | + * | if ( == ) + * | barrier ; + * + * \return A QASM AST object + */ + ast::ptr parse_program() { + auto pos = current_token_.position(); + std::list> ret; + + // The first (non-comment) line of an Open QASM program must be + // OPENQASM M.m; indicating a major version M and minor version m. + parse_header(); + + while (!current_token_.is(Token::Kind::eof)) { + switch (current_token_.kind()) { + // Parse declarations () + case Token::Kind::kw_creg: + ret.emplace_back(parse_reg_decl(false)); + break; + case Token::Kind::kw_qreg: + ret.emplace_back(parse_reg_decl(true)); + break; + + case Token::Kind::kw_gate: + ret.emplace_back(parse_gate_decl()); + break; + + case Token::Kind::kw_opaque: + ret.emplace_back(parse_opaque_decl()); + break; + case Token::Kind::kw_oracle: + ret.emplace_back(parse_oracle_decl()); + break; + + // Parse quantum operations () + case Token::Kind::identifier: + case Token::Kind::kw_cx: + case Token::Kind::kw_measure: + case Token::Kind::kw_reset: + case Token::Kind::kw_u: + ret.emplace_back(parse_qop()); + break; + + case Token::Kind::kw_barrier: + ret.emplace_back(parse_barrier()); + break; + + case Token::Kind::kw_if: + ret.emplace_back(parse_if()); + break; + + default: + error_ = true; + if (!supress_errors_) { + std::cerr << current_token_.position(); + std::cerr + << ": expected a global declaration or statement"; + std::cerr << " but got " << current_token_.kind() + << "\n"; + ; + supress_errors_ = true; + } + + consume_until(Token::Kind::semicolon); + break; + } + } + + return ast::Program::create(pos, pp_lexer_.includes_stdlib(), + std::move(ret), bits_, qubits_); + } + + /** + * \brief Parse an OpenQASM 2.0 header + * + * OPENQASM ; + */ + void parse_header() { + consume_token(); + expect_and_consume_token(Token::Kind::kw_openqasm); + expect_and_consume_token(Token::Kind::real); + consume_until(Token::Kind::semicolon); + } + + /** + * \brief Parse a register declaration + * \note + * + * = qreg [ ] ; + * | creg [ ] ; + * + * \return Unique pointer to a statement object + */ + ast::ptr parse_reg_decl(bool quantum) { + auto pos = current_token_.position(); + + consume_token(); + auto id = expect_and_consume_token(Token::Kind::identifier); + expect_and_consume_token(Token::Kind::l_square); + auto size = expect_and_consume_token(Token::Kind::nninteger); + expect_and_consume_token(Token::Kind::r_square); + consume_until(Token::Kind::semicolon); + + quantum ? qubits_ += size.as_int() : bits_ += size.as_int(); + return ast::RegisterDecl::create(pos, id.as_string(), quantum, + size.as_int()); + } + + /** + * \brief Parse an ancilla declaration + * \note + * + * = ancilla [ ] ; + * | dirty ancilla [ ] ; + * + * \return Unique pointer to a (gate) statement object + */ + ast::ptr parse_ancilla_decl() { + bool dirty = false; + auto pos = current_token_.position(); + + if (try_and_consume_token(Token::Kind::kw_dirty)) { + dirty = true; + } + + expect_and_consume_token(Token::Kind::kw_ancilla); + auto id = expect_and_consume_token(Token::Kind::identifier); + expect_and_consume_token(Token::Kind::l_square); + auto size = expect_and_consume_token(Token::Kind::nninteger); + expect_and_consume_token(Token::Kind::r_square); + consume_until(Token::Kind::semicolon); + + return ast::AncillaDecl::create(pos, id.as_string(), dirty, + size.as_int()); + } + + /** + * \brief Parse a gate declaration + * \note + * + * = gate { + * | gate ( ) { + * | gate ( ) { + * + * \return Unique pointer to a statement object + */ + ast::ptr parse_gate_decl() { + auto pos = current_token_.position(); + + consume_token(); + auto id = expect_and_consume_token(Token::Kind::identifier); + + std::vector c_params; + if (try_and_consume_token(Token::Kind::l_paren)) { + c_params = parse_idlist(); + expect_and_consume_token(Token::Kind::r_paren); + } + + auto q_params = parse_idlist(); + + expect_and_consume_token(Token::Kind::l_brace); + std::list> body; + if (!try_and_consume_token(Token::Kind::r_brace)) { + body = parse_goplist(); + expect_and_consume_token(Token::Kind::r_brace); + } + + return ast::GateDecl::create(pos, id.as_string(), false, c_params, + q_params, std::move(body)); + } + + /** + * \brief Parse an opaque gate declaration + * \note + * + * = opaque ; + * | opaque ( ) ; + * | opaque ( ) ; + * + * \return Unique pointer to a statement object + */ + ast::ptr parse_opaque_decl() { + auto pos = current_token_.position(); + + consume_token(); + auto id = expect_and_consume_token(Token::Kind::identifier); + + std::vector c_params; + if (try_and_consume_token(Token::Kind::l_paren)) { + c_params = parse_idlist(); + expect_and_consume_token(Token::Kind::r_paren); + } + + auto q_params = parse_idlist(); + + consume_until(Token::Kind::semicolon); + return ast::GateDecl::create(pos, id.as_string(), true, c_params, + q_params, {}); + } + + /** + * \brief Parse an oracle declaration + * \note + * + * = oracle { } + * + * \return Unique pointer to a statement object + */ + ast::ptr parse_oracle_decl() { + auto pos = current_token_.position(); + + consume_token(); + auto id = expect_and_consume_token(Token::Kind::identifier); + auto params = parse_idlist(); + expect_and_consume_token(Token::Kind::l_brace); + auto file = expect_and_consume_token(Token::Kind::string); + expect_and_consume_token(Token::Kind::r_brace); + + return ast::OracleDecl::create(pos, id.as_string(), params, + file.as_string()); + } + + /** + * \brief Parse a list of gate operations + * \note + * + * = + * | + * + * \return Vector of gate objects + */ + std::list> parse_goplist() { + std::list> ret; + bool finished = false; + + while (!finished) { + switch (current_token_.kind()) { + case Token::Kind::kw_dirty: + case Token::Kind::kw_ancilla: + case Token::Kind::kw_cx: + case Token::Kind::kw_u: + case Token::Kind::identifier: + case Token::Kind::kw_barrier: + ret.emplace_back(parse_gop()); + break; + + default: + finished = true; + break; + } + } + + return ret; + } + + /** + * \brief Parse a quantum operation + * \note + * + * = + * | measure -> ; + * | reset ; + * + * \return Unique pointer to a statement object + */ + ast::ptr parse_qop() { + switch (current_token_.kind()) { + case Token::Kind::kw_cx: + case Token::Kind::kw_u: + case Token::Kind::identifier: + return parse_gop(); + + case Token::Kind::kw_measure: + return parse_measure(); + + case Token::Kind::kw_reset: + return parse_reset(); + + default: + error_ = true; + if (!supress_errors_) { + std::cerr << current_token_.position(); + std::cerr << ": expected a quantum operation, but got "; + std::cerr << current_token_.kind() << "\n"; + ; + supress_errors_ = true; + } + + return nullptr; + } + } + + /** + * \brief Parse a gate operation + * \note + * + * = U ( ) ; + * | CX , ; + * | ; + * | ( ) ; + * | ( ) ; + * | barrier ; + * + * \return Unique pointer to a gate object + */ + ast::ptr parse_gop() { + switch (current_token_.kind()) { + case Token::Kind::kw_dirty: + case Token::Kind::kw_ancilla: + return parse_ancilla_decl(); + + case Token::Kind::kw_u: + return parse_unitary(); + + case Token::Kind::kw_cx: + return parse_cnot(); + + case Token::Kind::identifier: + return parse_gate_statement(); + + case Token::Kind::kw_barrier: + return parse_barrier(); + + default: + error_ = true; + if (!supress_errors_) { + std::cerr << current_token_.position(); + std::cerr << ": expected a gate operation but got "; + std::cerr << current_token_.kind() << "\n"; + ; + supress_errors_ = true; + } + + return nullptr; + } + } + + /** + * \brief Parse a list of identifiers + * \note + * + * = + * | , + * + * \return Vector of identifiers + */ + std::vector parse_idlist() { + std::vector ret; + + // doesn't accept empty lists + while (true) { + auto id = expect_and_consume_token(Token::Kind::identifier); + ret.push_back(id.as_string()); + if (!try_and_consume_token(Token::Kind::comma)) { + break; + } + } + + return ret; + } + + /** + * \brief Parse a variable expression + * \note + * + * = + * | [ ] + * + * \return Variable object + */ + ast::VarAccess parse_argument() { + auto pos = current_token_.position(); + + auto id = expect_and_consume_token(Token::Kind::identifier); + if (try_and_consume_token(Token::Kind::l_square)) { + auto offset = expect_and_consume_token(Token::Kind::nninteger); + expect_and_consume_token(Token::Kind::r_square); + + return ast::VarAccess(pos, id.as_string(), offset.as_int()); + } + + return ast::VarAccess(pos, id.as_string()); + } + + /** + * \brief Parse a list of variable expressions + * \note + * + * = + * | , + * + * \return Vector of variable objects + */ + std::vector parse_anylist() { + std::vector ret; + + // doesn't accept empty lists + while (true) { + ret.emplace_back(parse_argument()); + if (!try_and_consume_token(Token::Kind::comma)) { + break; + } + } + + return ret; + } + + /** + * \brief Parse a list of expressions + * \note + * + * = + * | , + * + * \return Vector of expressions + */ + std::vector> parse_explist() { + // doesn't accept empty lists + std::vector> ret; + + while (true) { + ret.emplace_back(parse_exp()); + if (!try_and_consume_token(Token::Kind::comma)) { + break; + } + } + + return ret; + } + + /** + * \brief Parse an expression + * \note + * + * = | | pi | + * | + | - + * | * | / + * | - + * | ^ + * | ( ) + * | ( ) + * + * \return Unique pointer to an expression object + */ + ast::ptr parse_exp(int min_precedence = 1) { + auto pos = current_token_.position(); + + auto lexp = parse_atom(); + while (1) { + auto next_min_precedence = min_precedence; + + switch (current_token_.kind()) { + case Token::Kind::plus: + if (min_precedence > 1) { + return lexp; + } + next_min_precedence = 2; + break; + + case Token::Kind::minus: + if (min_precedence > 1) { + return lexp; + } + next_min_precedence = 2; + break; + + case Token::Kind::star: + if (min_precedence > 2) { + return lexp; + } + next_min_precedence = 3; + break; + + case Token::Kind::slash: + if (min_precedence > 2) { + return lexp; + } + next_min_precedence = 3; + break; + + case Token::Kind::caret: + if (min_precedence > 3) { + return lexp; + } + next_min_precedence = 4; + break; + + default: + return lexp; + } + + auto bop = parse_binaryop(); + auto rexp = parse_exp(next_min_precedence); + lexp = + ast::BExpr::create(pos, std::move(lexp), bop, std::move(rexp)); + } + + return lexp; + } + + /** + * \brief Parse an atomic expression + * \see qpp:tools_v1::Parser::parse_exp() + * + * \return Unique pointer to an expression object + */ + ast::ptr parse_atom() { + auto pos = current_token_.position(); + + switch (current_token_.kind()) { + case Token::Kind::l_paren: { + consume_token(); + auto exp = parse_exp(); + expect_and_consume_token(Token::Kind::r_paren); + return exp; + } + + case Token::Kind::minus: { + consume_token(); + auto exp = parse_atom(); + return ast::UExpr::create(pos, ast::UnaryOp::Neg, + std::move(exp)); + } + + case Token::Kind::plus: { + consume_token(); + return parse_atom(); + } + + case Token::Kind::identifier: { + auto id = current_token_; + consume_token(); + return ast::VarExpr::create(pos, id.as_string()); + } + case Token::Kind::nninteger: { + auto integer = current_token_; + consume_token(); + return ast::IntExpr::create(pos, integer.as_int()); + } + case Token::Kind::kw_pi: + consume_token(); + return ast::PiExpr::create(pos); + case Token::Kind::real: { + auto real = current_token_; + consume_token(); + return ast::RealExpr::create(pos, real.as_real()); + } + + case Token::Kind::kw_sin: + case Token::Kind::kw_cos: + case Token::Kind::kw_tan: + case Token::Kind::kw_exp: + case Token::Kind::kw_ln: + case Token::Kind::kw_sqrt: { + auto op = parse_unaryop(); + expect_and_consume_token(Token::Kind::l_paren); + auto exp = parse_exp(); + expect_and_consume_token(Token::Kind::r_paren); + return ast::UExpr::create(pos, op, std::move(exp)); + } + + default: + error_ = true; + if (!supress_errors_) { + std::cerr << current_token_.position(); + std::cerr << ": expected an atomic expression but got "; + std::cerr << current_token_.kind() << "\n"; + ; + supress_errors_ = true; + } + + return nullptr; + } + } + + /** + * \brief Parse a binary operator + * \see qpp:tools_v1::Parser::parse_exp() + * + * \return Binary operator + */ + ast::BinaryOp parse_binaryop() { + switch (current_token_.kind()) { + case Token::Kind::plus: + consume_token(); + return ast::BinaryOp::Plus; + case Token::Kind::minus: + consume_token(); + return ast::BinaryOp::Minus; + case Token::Kind::star: + consume_token(); + return ast::BinaryOp::Times; + case Token::Kind::slash: + consume_token(); + return ast::BinaryOp::Divide; + case Token::Kind::caret: + consume_token(); + return ast::BinaryOp::Pow; + default: + error_ = true; + if (!supress_errors_) { + std::cerr << current_token_.position(); + std::cerr << ": expected a binary operator but got "; + std::cerr << current_token_.kind() << "\n"; + ; + supress_errors_ = true; + } + + return ast::BinaryOp::Plus; + } + } + + /** + * \brief Parse a unary operator + * \see qpp:tools_v1::Parser::parse_exp() + * + * \return Unary operator + */ + ast::UnaryOp parse_unaryop() { + switch (current_token_.kind()) { + case Token::Kind::kw_sin: + consume_token(); + return ast::UnaryOp::Sin; + case Token::Kind::kw_cos: + consume_token(); + return ast::UnaryOp::Cos; + case Token::Kind::kw_tan: + consume_token(); + return ast::UnaryOp::Tan; + case Token::Kind::kw_exp: + consume_token(); + return ast::UnaryOp::Exp; + case Token::Kind::kw_ln: + consume_token(); + return ast::UnaryOp::Ln; + case Token::Kind::kw_sqrt: + consume_token(); + return ast::UnaryOp::Sqrt; + default: + error_ = true; + if (!supress_errors_) { + std::cerr << current_token_.position(); + std::cerr << ": expected a unary operator but got "; + std::cerr << current_token_.kind() << "\n"; + ; + supress_errors_ = true; + } + + return ast::UnaryOp::Neg; + } + } + + /** + * \brief Parse a CNOT gate + * + * CX , ; + * + * \return Unique pointer to a gate object + */ + ast::ptr parse_cnot() { + auto pos = current_token_.position(); + + expect_and_consume_token(Token::Kind::kw_cx); + auto ctrl = parse_argument(); + expect_and_consume_token(Token::Kind::comma); + auto tgt = parse_argument(); + consume_until(Token::Kind::semicolon); + + return ast::CNOTGate::create(pos, std::move(ctrl), std::move(tgt)); + } + + /** + * \brief Parse a single qubit U gate + * + * U ( , , ) ; + * + * \return Unique pointer to a gate object + */ + ast::ptr parse_unitary() { + auto pos = current_token_.position(); + + expect_and_consume_token(Token::Kind::kw_u); + expect_and_consume_token(Token::Kind::l_paren); + auto theta = parse_exp(); + expect_and_consume_token(Token::Kind::comma); + auto phi = parse_exp(); + expect_and_consume_token(Token::Kind::comma); + auto lambda = parse_exp(); + expect_and_consume_token(Token::Kind::r_paren); + auto arg = parse_argument(); + consume_until(Token::Kind::semicolon); + + return ast::UGate::create(pos, std::move(theta), std::move(phi), + std::move(lambda), std::move(arg)); + } + + /** + * \brief Parse a declared gate application + * + * ; + * ( ) ; + * + * \return Unique pointer to a gate object + */ + ast::ptr parse_gate_statement() { + auto pos = current_token_.position(); + + auto id = expect_and_consume_token(Token::Kind::identifier); + std::vector> c_args; + if (try_and_consume_token(Token::Kind::l_paren)) { + c_args = parse_explist(); + expect_and_consume_token(Token::Kind::r_paren); + } + + auto q_args = parse_anylist(); + consume_until(Token::Kind::semicolon); + + return ast::DeclaredGate::create(pos, id.as_string(), std::move(c_args), + std::move(q_args)); + } + + /** + * \brief Parse a measurement + * + * measure -> ; + * + * \return Unique pointer to a statement object + */ + ast::ptr parse_measure() { + auto pos = current_token_.position(); + + expect_and_consume_token(Token::Kind::kw_measure); + auto q_arg = parse_argument(); + expect_and_consume_token(Token::Kind::arrow); + auto c_arg = parse_argument(); + consume_until(Token::Kind::semicolon); + + return ast::MeasureStmt::create(pos, std::move(q_arg), + std::move(c_arg)); + } + + /** + * \brief Parse a reset statement + * + * reset ; + * + * \return Unique pointer to a statement object + */ + ast::ptr parse_reset() { + auto pos = current_token_.position(); + + expect_and_consume_token(Token::Kind::kw_reset); + auto arg = parse_argument(); + consume_until(Token::Kind::semicolon); + + return ast::ResetStmt::create(pos, std::move(arg)); + } + + /** + * \brief Parse a barrier statement + * + * barrier ; + * + * \return Unique pointer to a gate object + */ + ast::ptr parse_barrier() { + auto pos = current_token_.position(); + + expect_and_consume_token(Token::Kind::kw_barrier); + auto args = parse_anylist(); + consume_until(Token::Kind::semicolon); + + return ast::BarrierGate::create(pos, std::move(args)); + } + + /** + * \brief Parse an if statement + * + * if ( == ) + * + * \return Unique pointer to a statement object + */ + ast::ptr parse_if() { + auto pos = current_token_.position(); + + expect_and_consume_token(Token::Kind::kw_if); + expect_and_consume_token(Token::Kind::l_paren); + auto id = expect_and_consume_token(Token::Kind::identifier); + expect_and_consume_token(Token::Kind::equalequal); + auto integer = expect_and_consume_token(Token::Kind::nninteger); + expect_and_consume_token(Token::Kind::r_paren); + auto op = parse_qop(); + + return ast::IfStmt::create(pos, id.as_string(), integer.as_int(), + std::move(op)); + } +}; + +/** + * \brief Parse a specified file + */ +inline ast::ptr parse_file(std::string fname) { + Preprocessor pp; + Parser parser(pp); + + std::shared_ptr ifs(new std::ifstream); + + ifs->open(fname, std::ifstream::in); + if (!ifs->good()) { + ifs->close(); + std::cerr << "File \"" << fname << "\" not found!\n"; + throw ParseError(); + } + + pp.add_target_stream(ifs, fname); + + return parser.parse(); +} + +/** + * \brief Parse input from stdin + */ +inline ast::ptr parse_stdin(std::string name = "") { + Preprocessor pp; + Parser parser(pp); + + // This is a bad idea, but it's necessary for automatic bookkeeping + // accross all different forms and sources of source streams + pp.add_target_stream( + std::shared_ptr(&std::cin, [](std::istream*) {}), name); + + return parser.parse(); +} + +/** + * \brief Parse input stream + */ +inline ast::ptr parse_stream(std::istream& stream) { + Preprocessor pp; + Parser parser(pp); + + // do not manage the stream, use [](std::istream*){} as shared_ptr deleter + pp.add_target_stream( + std::shared_ptr(&stream, [](std::istream*) {})); + + return parser.parse(); +} + +/** + * \brief Parse a string + * \note For small programs + */ +inline ast::ptr parse_string(const std::string& str, + std::string name = "") { + Preprocessor pp; + Parser parser(pp); + std::shared_ptr is = + std::make_shared(str); + + pp.add_target_stream(is, name); + + return parser.parse(); +} + +} /* namespace parser */ +} /* namespace tools_v1 */ + +#endif /* TOOLS_V1_PARSER_PARSER_HPP_ */ diff --git a/experimental/include/tools_v1/parser/position.hpp b/experimental/include/tools_v1/parser/position.hpp new file mode 100644 index 00000000..bbf6ac48 --- /dev/null +++ b/experimental/include/tools_v1/parser/position.hpp @@ -0,0 +1,121 @@ +/* + * This file is part of tools_v1. + * + * Copyright (c) 2019 - 2025 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 tools_v1/parser/position.hpp + */ + +#ifndef TOOLS_V1_PARSER_POSITION_HPP_ +#define TOOLS_V1_PARSER_POSITION_HPP_ + +#include +#include + +namespace tools_v1 { +namespace parser { + +/** + * \class tools_v1::parser::Position + * \brief Positions in source code + */ +class Position { + std::string fname_ = ""; ///< name of the containing file + int line_ = 1; ///< line number + int column_ = 1; ///< column number + + public: + /** + * \brief Default constructor + */ + Position() = default; + + /** + * \brief Constructs a position within a file + * + * \param fname Filename + * \param line Line number + * \param column Column number + */ + Position(const std::string& fname, int line, int column) + : fname_(fname), line_(line), column_(column) {} + + /** + * \brief Extraction operator overload + * + * \param os Output stream + * \param pos tools_v1::parser::Position + * \return Reference to the output stream + */ + friend std::ostream& operator<<(std::ostream& os, const Position& pos) { + os << pos.fname_ << ":" << pos.line_ << ":" << pos.column_; + return os; + } + + /** + * \brief The name of the containing file + * + * \return Const reference to the filename + */ + const std::string& get_filename() const { return fname_; } + + /** + * \brief The line of the position + * + * \return The line number + */ + int get_linenum() const { return line_; } + + /** + * \brief The column of the position + * + * \return The column number + */ + int get_column() const { return column_; } + + /** + * \brief Advance the line number by a specified amount + * + * \note Sets the column to 0 + * + * \param num Number of lines to advance (optional, default is 1) + */ + void advance_line(int num = 1) { + line_ += num; + column_ = 1; + } + + /** + * \brief Advance the column number by a specified amount + * + * \param num Number of columns to advance (optional, default is 1) + */ + void advance_column(int num = 1) { column_ += num; } +}; + +} /* namespace parser */ +} /* namespace tools_v1 */ + +#endif /* TOOLS_V1_PARSER_POSITION_HPP_ */ diff --git a/experimental/include/tools_v1/parser/preprocessor.hpp b/experimental/include/tools_v1/parser/preprocessor.hpp new file mode 100644 index 00000000..afdace9c --- /dev/null +++ b/experimental/include/tools_v1/parser/preprocessor.hpp @@ -0,0 +1,275 @@ +/* + * This file is part of tools_v1. + * + * Copyright (c) 2019 - 2025 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. + * + * Adapted from Bruno Schmitt's Tweedledee library + */ + +/** + * \file tools_v1/parser/preprocessor.hpp + * \brief Manages includes for OpenQASM parsing + */ + +#ifndef TOOLS_V1_PARSER_PREPROCESSOR_HPP_ +#define TOOLS_V1_PARSER_PREPROCESSOR_HPP_ + +#include +#include +#include + +#include "lexer.hpp" + +namespace tools_v1 { +namespace parser { + +#if TOOLS_V1_QASM2_SPECS +/** + * \brief OpenQASM 2.0 standard library (qelib1.inc) as a string constant + */ +static const std::string std_include = + "gate u3(theta,phi,lambda) q { U(theta,phi,lambda) q; }\n" + "gate u2(phi,lambda) q { U(pi/2,phi,lambda) q; }\n" + "gate u1(lambda) q { U(0,0,lambda) q; }\n" + "gate cx c,t { CX c,t; }\n" + "gate id a { U(0,0,0) a; }\n" + "gate u0(gamma) q { U(0,0,0) q; }\n" + "gate x a { u3(pi,0,pi) a; }\n" + "gate y a { u3(pi,pi/2,pi/2) a; }\n" + "gate z a { u1(pi) a; }\n" + "gate h a { u2(0,pi) a; }\n" + "gate s a { u1(pi/2) a; }\n" + "gate sdg a { u1(-pi/2) a; }\n" + "gate t a { u1(pi/4) a; }\n" + "gate tdg a { u1(-pi/4) a; }\n" + "gate rx(theta) a { u3(theta, -pi/2,pi/2) a; }\n" + "gate ry(theta) a { u3(theta,0,0) a; }\n" + "gate rz(phi) a { u1(phi) a; }\n" + "gate cz a,b { h b; cx a,b; h b; }\n" + "gate cy a,b { sdg b; cx a,b; s b; }\n" + "gate swap a,b { cx a,b; cx b,a; cx a,b; }\n" + "gate ch a,b { h b; sdg b;cx a,b;h b; t b;cx a,b;t b; h b; s b; x b; s " + "a;}\n" + "gate ccx a,b,c{ h c; cx b,c; tdg c; cx a,c; t c; cx b,c; tdg c; cx a,c; t " + "b; t c; h c; cx a,b; t a; tdg b; cx a,b;}\n" + "gate crz(lambda) a,b{ u1(lambda/2) b; cx a,b; u1(-lambda/2) b; cx a,b;}\n" + "gate cu1(lambda) a,b{ u1(lambda/2) a; cx a,b; u1(-lambda/2) b; cx a,b; " + "u1(lambda/2) b;}\n" + "gate cu3(theta,phi,lambda) c,t { u1((lambda-phi)/2) t; cx c,t; " + "u3(-theta/2,0,-(phi+lambda)/2) t; cx c,t; u3(theta/2,phi,0) t;}\n"; +#else +/** + * \brief OpenQASM 2.0 standard library + r and cswap gates, as a string + * constant, as defined by Qiskit in + * https://github.com/Qiskit/qiskit-terra/tree/master/qiskit/circuit/library/standard_gates + */ +static const std::string std_include = + "gate u3(theta,phi,lambda) q { U(theta,phi,lambda) q; }\n" + "gate u2(phi,lambda) q { U(pi/2,phi,lambda) q; }\n" + "gate u1(lambda) q { U(0,0,lambda) q; }\n" + "gate cx c,t { CX c,t; }\n" + "gate id a { U(0,0,0) a; }\n" + "gate u0(gamma) q { U(0,0,0) q; }\n" + "gate x a { u3(pi,0,pi) a; }\n" + "gate y a { u3(pi,pi/2,pi/2) a; }\n" + "gate z a { u1(pi) a; }\n" + "gate h a { u2(0,pi) a; }\n" + "gate s a { u1(pi/2) a; }\n" + "gate sdg a { u1(-pi/2) a; }\n" + "gate t a { u1(pi/4) a; }\n" + "gate tdg a { u1(-pi/4) a; }\n" + "gate r(theta, phi) a { u3(theta,phi-pi/2,-phi+pi/2) a; }\n" + "gate rx(theta) a { r(theta,0) a; } \n" + "gate ry(theta) a { r(theta,pi/2) a; }\n" + /** + * Rz = e^{-i*phi/2} U1 + * This differs from the docstring here: + * https://github.com/Qiskit/qiskit-terra/blob/main/qiskit/circuit/library/standard_gates/rz.py#L65 + */ + "gate rz(phi) a { x a; u1(-phi/2) a; x a; u1(phi/2) a; } \n" + "gate cz a,b { h b; cx a,b; h b; }\n" + "gate cy a,b { sdg b; cx a,b; s b; }\n" + "gate swap a,b { cx a,b; cx b,a; cx a,b; }\n" + "gate ch a,b { s b; h b; t b; cx a,b; tdg b; h b; sdg b; }\n" + "gate ccx a,b,c { h c; cx b,c; tdg c; cx a,c; t c; cx b,c; tdg c; cx a,c; " + "t b; t c; h c; cx a,b; t a; tdg b; cx a,b; }\n" + "gate cswap a,b,c { cx c,b; ccx a,b,c; cx c,b; }\n" + "gate crz(lambda) a,b { u1(lambda/2) b; cx a,b; u1(-lambda/2) b; cx a,b; " + "}\n" + "gate cu1(lambda) a,b { u1(lambda/2) a; cx a,b; u1(-lambda/2) b; cx a,b; " + "u1(lambda/2) b; }\n" + "gate cu3(theta,phi,lambda) c,t { u1((lambda+phi)/2) c; " + "u1((lambda-phi)/2) t; cx c,t; u3(-theta/2,0,-(phi+lambda)/2) t; cx c,t; " + "u3(theta/2,phi,0) t; }\n"; +#endif + +/** + * \class tools_v1::parser::Preprocessor + * \brief OpenQASM preprocessor class + * \see tools_v1::parser::Lexer + * + * The preprocessor acts as a wrapper around the lexer, providing a token stream + * that matches the stream produced by explicitly inserting included code. + * Effectively, the preprocessor acts as the lexer on preprocessed code. + */ +class Preprocessor { + using LexerPtr = std::unique_ptr; + + std::vector lexer_stack_{}; ///< owning stack of lexers + LexerPtr current_lexer_ = nullptr; ///< current lexer + + bool std_include_ = false; ///< whether qelib1 has been included + + public: + /** + * \brief Default constructor + */ + Preprocessor() = default; + + /** + * \brief Inserts a file into the current lexing context + * + * Buffers the given file, then pushes the current buffer onto + * the stack and sets the new buffer as the current buffer + * + * \param file_path The file to insert + * \return True on success + */ + bool add_target_file(const std::string& file_path) { + std::shared_ptr ifs(new std::ifstream); + + ifs->open(file_path, std::ifstream::in); + + if (!ifs->good()) { + return false; + } + + if (current_lexer_ != nullptr) { + lexer_stack_.push_back(std::move(current_lexer_)); + } + current_lexer_ = std::unique_ptr(new Lexer(ifs, file_path)); + return true; + } + + /** + * \brief Inserts a buffer into the current lexing context + * + * Pushes the current buffer onto the stack and sets the new buffer as the + * current buffer + * + * \param buffer Shared pointer to an input buffer + * \param fname Filename associated with the buffer (optional) + */ + void add_target_stream(std::shared_ptr buffer, + const std::string& fname = "") { + if (current_lexer_ != nullptr) { + lexer_stack_.push_back(std::move(current_lexer_)); + } + current_lexer_ = std::unique_ptr(new Lexer(buffer, fname)); + } + + /** + * \brief Gets the next token in the current buffer + * \note Returns unknown token if there is no buffer to lex + * + * Lexes and returns the next token from the current buffer, + * popping the next buffer off the stack if necessary + * + * \return The next lexed token + */ + Token next_token() { + if (current_lexer_ == nullptr) { + return Token(Position(), Token::Kind::eof, ""); + } + auto token = current_lexer_->next_token(); + if (token.is(Token::Kind::kw_include)) { + handle_include(); + token = current_lexer_->next_token(); + } else if (token.is(Token::Kind::eof)) { + if (!lexer_stack_.empty()) { + current_lexer_ = std::move(lexer_stack_.back()); + lexer_stack_.pop_back(); + token = current_lexer_->next_token(); + } else { + current_lexer_ = nullptr; + } + } + return token; + } + + /** + * \brief Prints and consumes all tokens buffered + */ + void print_all_tokens() { + Token current = next_token(); + + while (!current.is(Token::Kind::eof)) { + std::cout << current.position() << ": " << current << " " + << current.raw() << "\n"; + current = next_token(); + } + } + + bool includes_stdlib() { return std_include_; } + + private: + /** + * \brief Handles include statements + * \note Expects STRING SEMICOLON + * + * Reads a filename from a string token and attempts to open and switch + * lexing context to the file + */ + void handle_include() { + auto token = current_lexer_->next_token(); + if (token.is_not(Token::Kind::string)) { + std::cerr << "Error: Include must be followed by a file name\n"; + return; + } + + auto target = token.as_string(); + if (target == "qelib1.inc") { + std_include_ = true; + } + + token = current_lexer_->next_token(); + if (token.is_not(Token::Kind::semicolon)) { + std::cerr << "Warning: Missing a ';'\n"; + } + if (add_target_file(target)) { + return; + } else if (target == "qelib1.inc") { + add_target_stream(std::shared_ptr( + new std::stringstream(std_include)), + "qelib1.inc"); + return; + } else { + std::cerr << "Error: Couldn't open file " << target << "\n"; + } + } +}; + +} /* namespace parser */ +} /* namespace tools_v1 */ + +#endif /* TOOLS_V1_PARSER_PREPROCESSOR_HPP_ */ diff --git a/experimental/include/tools_v1/parser/token.hpp b/experimental/include/tools_v1/parser/token.hpp new file mode 100644 index 00000000..eb1fca6a --- /dev/null +++ b/experimental/include/tools_v1/parser/token.hpp @@ -0,0 +1,392 @@ +/* + * This file is part of tools_v1. + * + * Copyright (c) 2019 - 2025 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. + * + * Adapted from Bruno Schmitt's Tweedledee library + */ + +/** + * \file tools_v1/parser/token.hpp + * \brief Tokens + */ + +#ifndef TOOLS_V1_PARSER_TOKEN_HPP_ +#define TOOLS_V1_PARSER_TOKEN_HPP_ + +#include +#include + +#include "position.hpp" + +namespace tools_v1 { +namespace parser { + +/** + * \class tools_v1::parser::Token + * \brief OpenQASM token class + * \see tools_v1::parser::Lexer + */ +class Token { + public: + /** + * \brief Token types + */ + enum class Kind { + error, + eof, + comment, + identifier, + real, + nninteger, + string, + l_square, + r_square, + l_paren, + r_paren, + l_brace, + r_brace, + period, + star, + plus, + minus, + arrow, + slash, + caret, + semicolon, + equalequal, + comma, + colon, + kw_include, + kw_barrier, + kw_creg, + kw_cx, + kw_gate, + kw_if, + kw_measure, + kw_pi, + kw_opaque, + kw_openqasm, + kw_qreg, + kw_reset, + kw_u, + kw_sin, + kw_cos, + kw_tan, + kw_exp, + kw_ln, + kw_sqrt, + kw_oracle, + kw_dirty, + kw_ancilla + }; + + /** + * \brief Extraction operator overload + * + * \param os Output stream passed by reference + * \param k tools_v1::parser::Kind enum class + * \return Reference to the output stream + */ + friend std::ostream& operator<<(std::ostream& os, const Kind& k) { + switch (k) { + case Kind::error: + os << "ERROR"; + break; + case Kind::eof: + os << "EOF"; + break; + case Kind::comment: + os << "COMMENT"; + break; + case Kind::identifier: + os << "ID"; + break; + case Kind::real: + os << "REAL"; + break; + case Kind::nninteger: + os << "INT"; + break; + case Kind::string: + os << "STRING"; + break; + case Kind::l_square: + os << "LBRACKET"; + break; + case Kind::r_square: + os << "RBRACKET"; + break; + case Kind::l_paren: + os << "LPAREN"; + break; + case Kind::r_paren: + os << "RPAREN"; + break; + case Kind::l_brace: + os << "LBRACE"; + break; + case Kind::r_brace: + os << "RBRACE"; + break; + case Kind::period: + os << "PERIOD"; + break; + case Kind::star: + os << "STAR"; + break; + case Kind::plus: + os << "PLUS"; + break; + case Kind::minus: + os << "MINUS"; + break; + case Kind::arrow: + os << "RARROW"; + break; + case Kind::slash: + os << "SLASH"; + break; + case Kind::caret: + os << "CARAT"; + break; + case Kind::semicolon: + os << "SEMICOLON"; + break; + case Kind::equalequal: + os << "EQUALS"; + break; + case Kind::comma: + os << "COMMA"; + break; + case Kind::colon: + os << "COLON"; + break; + case Kind::kw_include: + os << "INCLUDE"; + break; + case Kind::kw_barrier: + os << "BARRIER"; + break; + case Kind::kw_creg: + os << "CREG"; + break; + case Kind::kw_cx: + os << "CX"; + break; + case Kind::kw_gate: + os << "GATE"; + break; + case Kind::kw_if: + os << "IF"; + break; + case Kind::kw_measure: + os << "MEASURE"; + break; + case Kind::kw_pi: + os << "PI"; + break; + case Kind::kw_opaque: + os << "OPAQUE"; + break; + case Kind::kw_openqasm: + os << "OPENQASM"; + break; + case Kind::kw_qreg: + os << "QREG"; + break; + case Kind::kw_reset: + os << "RESET"; + break; + case Kind::kw_u: + os << "UNITARY"; + break; + case Kind::kw_sin: + os << "SIN"; + break; + case Kind::kw_cos: + os << "COS"; + break; + case Kind::kw_tan: + os << "TAN"; + break; + case Kind::kw_exp: + os << "EXP"; + break; + case Kind::kw_ln: + os << "LN"; + break; + case Kind::kw_sqrt: + os << "SQRT"; + break; + case Kind::kw_oracle: + os << "ORACLE"; + break; + case Kind::kw_ancilla: + os << "ANCILLA"; + break; + case Kind::kw_dirty: + os << "DIRTY"; + break; + } + return os; + } + + Token() = delete; + Token(Position pos, Kind k, const std::string& str, + const std::variant& value = {}) + : pos_(pos), kind_(k), str_(str), value_(value) {} + + /** + * \brief Contextually return the token kind + * + * Allows switching directly on the token + */ + operator Kind() const { return kind_; } + + /** + * \brief Get the type of token + * + * \return The token type + */ + Kind kind() const { return kind_; } + + /** + * \brief Check whether the token is a particular type + * + * \param k The token type to compare against + * \return True if and only if the token is of type k + */ + bool is(Kind k) const { return kind_ == k; } + + /** + * \brief Check whether the token is not a particular type + * + * \param k The token type to compare against + * \return True if and only if the token is not of type k + */ + bool is_not(Kind k) const { return kind_ != k; } + + /** + * \brief User-defined conversion to int + * + * \note Does not perform validity checks + * + * \return The value of the token as an integer + */ + int as_int() const { return std::get(value_); } + + /** + * \brief Get the floating point value + * + * \note Does not perform validity checks + * + * \return The value of the token as a floating point number + */ + double as_real() const { return std::get(value_); } + + /** + * \brief Get the string value + * + * \note Does not perform validity checks + * + * \return The value of the token as a string + */ + std::string as_string() const { return std::get(value_); } + + /** + * \brief Return the position of the token + * + * \return Const reference to the token position + */ + const Position& position() const { return pos_; } + + /** + * \brief Get the raw string + * + * \return The raw source string + */ + const std::string& raw() const { return str_; } + + /** + * \brief Extraction operator override + * + * Writes to the output stream a textual representation of the token + * + * \param os Output stream passed by reference + * \return Reference to the output stream + */ + friend std::ostream& operator<<(std::ostream& os, const Token& t) { + os << t.kind_; + switch (t) { + case Kind::identifier: + case Kind::real: + case Kind::nninteger: + case Kind::string: + std::visit([&os](const auto& v) { os << "(" << v << ")"; }, + t.value_); + break; + default: + break; + } + return os; + } + + private: + Position pos_; ///< the token position + Kind kind_; ///< the token type + std::string str_; ///< the raw string + std::variant value_; ///< the token value +}; + +/** + * \brief Hash-map of OpenQASM keywords and their token type + */ +static const std::unordered_map keywords{ + {"include", Token::Kind::kw_include}, + {"barrier", Token::Kind::kw_barrier}, + {"creg", Token::Kind::kw_creg}, + {"CX", Token::Kind::kw_cx}, + {"gate", Token::Kind::kw_gate}, + {"if", Token::Kind::kw_if}, + {"measure", Token::Kind::kw_measure}, + {"pi", Token::Kind::kw_pi}, + {"opaque", Token::Kind::kw_opaque}, + {"OPENQASM", Token::Kind::kw_openqasm}, + {"qreg", Token::Kind::kw_qreg}, + {"reset", Token::Kind::kw_reset}, + {"U", Token::Kind::kw_u}, + {"sin", Token::Kind::kw_sin}, + {"cos", Token::Kind::kw_cos}, + {"tan", Token::Kind::kw_tan}, + {"exp", Token::Kind::kw_exp}, + {"ln", Token::Kind::kw_ln}, + {"sqrt", Token::Kind::kw_sqrt}, + {"oracle", Token::Kind::kw_oracle}, + {"dirty", Token::Kind::kw_dirty}, + {"ancilla", Token::Kind::kw_ancilla}}; + +} /* namespace parser */ +} /* namespace tools_v1 */ + +#endif /* TOOLS_V1_PARSER_TOKEN_HPP_ */ diff --git a/experimental/include/tools_v1/tools/ancilla_management.hpp b/experimental/include/tools_v1/tools/ancilla_management.hpp new file mode 100644 index 00000000..7a2364c1 --- /dev/null +++ b/experimental/include/tools_v1/tools/ancilla_management.hpp @@ -0,0 +1,100 @@ +#ifndef ANCILLA_MANAGEMENT_HPP_ +#define ANCILLA_MANAGEMENT_HPP_ + +#include +#include +#include +#include +#include +#include + +namespace tools_v1 { +namespace tools { + +class ANC_MEM { + std::set mem_; + std::string cur_name; + // the last index used corresponding to any of the names + std::unordered_map last_idx; + +public: + ANC_MEM() = default; + ~ANC_MEM() = default; + + qbit generate_ancilla(const std::string &prefix = "anc"); + + // Get all ancillas for circuit saving + const std::set &get_all_ancillas() const { return mem_; } + + // // Save all ancillas to a circuit + // void save_all_ancillas(circuit &c) const { + // for (const auto &anc : mem_) { + // c.save_ancilla(anc); + // } + // } + + const std::unordered_map& registers(){ + return last_idx; + } + + + // Clear all ancillas (for reset) + void clear() { + mem_.clear(); + last_idx.clear(); + cur_name.clear(); + } + + // Get count of ancillas + size_t size() const { return mem_.size(); } + +private: + std::string gen_name(); + std::string rand_string(int length); +}; + +inline std::string ANC_MEM::rand_string(int length) { + static const char alphanum[] = "0123456789" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz"; + + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_int_distribution<> dis(0, sizeof(alphanum) - 2); + + std::string result; + result.reserve(length); + for (int i = 0; i < length; ++i) { + result += alphanum[dis(gen)]; + } + return result; +} + +inline std::string ANC_MEM::gen_name() { + if (cur_name.empty()) { + cur_name = "a_" + rand_string(3); + last_idx[cur_name] = -1; + } + + // Check if current name has reached limit + if (last_idx[cur_name] >= 99) { + cur_name = "a_" + rand_string(3); + last_idx[cur_name] = -1; + } + + return cur_name; +} + +inline qbit ANC_MEM::generate_ancilla(const std::string &prefix) { + // TODO: take into account the prefix (ignored at this point) + std::string name = gen_name(); + unsigned int idx = ++last_idx[name]; + qbit ancilla(name, idx); + mem_.insert(ancilla); + return ancilla; +} + +} // namespace tools +} // namespace tools_v1 + +#endif diff --git a/experimental/include/tools_v1/tools/ast_printer.hpp b/experimental/include/tools_v1/tools/ast_printer.hpp new file mode 100644 index 00000000..84778c48 --- /dev/null +++ b/experimental/include/tools_v1/tools/ast_printer.hpp @@ -0,0 +1,219 @@ +/* + * This file is part of tools_v1. + * + * Copyright (c) 2019 - 2025 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 tools_v1/tools/ast_printer.hpp + * \brief Direct AST printer for debugging + */ + +#ifndef TOOLS_V1_TOOLS_ASTPRINTER_HPP_ +#define TOOLS_V1_TOOLS_ASTPRINTER_HPP_ + +#include "../ast/ast.hpp" + +namespace tools_v1 { +namespace tools { + +class ASTPrinter final : public ast::Visitor { + std::ostream& os_; + std::string prefix_; + + public: + ASTPrinter(std::ostream& os) : os_(os), prefix_("") {} + + // Variables + void visit(ast::VarAccess& ap) { + os_ << prefix_ << "|- Var(" << ap.var(); + if (ap.offset()) { + os_ << "[" << *ap.offset() << "]"; + } + os_ << ")\n"; + } + + // Expressions + void visit(ast::BExpr& expr) { + os_ << prefix_ << "|- BExpr(" << expr.op() << ")\n"; + + prefix_ += " "; + expr.lexp().accept(*this); + expr.rexp().accept(*this); + prefix_.resize(prefix_.length() - 2); + } + + void visit(ast::UExpr& expr) { + os_ << prefix_ << "|- UExpr(" << expr.op() << ")\n"; + + prefix_ += " "; + expr.subexp().accept(*this); + prefix_.resize(prefix_.length() - 2); + } + + void visit(ast::PiExpr&) { os_ << prefix_ << "|- Pi\n"; } + + void visit(ast::IntExpr& expr) { + os_ << prefix_ << "|- Int(" << expr.value() << ")\n"; + } + + void visit(ast::RealExpr& expr) { + os_ << prefix_ << "|- Real(" << expr.value() << ")\n"; + } + + void visit(ast::VarExpr& expr) { + os_ << prefix_ << "|- Var(" << expr.var(); + os_ << ")\n"; + } + + // Statements + void visit(ast::MeasureStmt& stmt) { + os_ << prefix_ << "|- Measure\n"; + + prefix_ += " "; + stmt.q_arg().accept(*this); + stmt.c_arg().accept(*this); + prefix_.resize(prefix_.length() - 2); + } + + void visit(ast::ResetStmt& stmt) { + os_ << prefix_ << "|- Reset\n"; + + prefix_ += " "; + stmt.arg().accept(*this); + prefix_.resize(prefix_.length() - 2); + } + + void visit(ast::IfStmt& stmt) { + os_ << prefix_ << "|- If(" << stmt.var() << "==" << stmt.cond() + << ")\n"; + + prefix_ += " "; + stmt.then().accept(*this); + prefix_.resize(prefix_.length() - 2); + } + + // Gates + void visit(ast::UGate& gate) { + os_ << prefix_ << "|- UGate\n"; + + prefix_ += " "; + gate.theta().accept(*this); + gate.phi().accept(*this); + gate.lambda().accept(*this); + gate.arg().accept(*this); + prefix_.resize(prefix_.length() - 2); + } + + void visit(ast::CNOTGate& gate) { + os_ << prefix_ << "|- CXGate\n"; + + prefix_ += " "; + gate.ctrl().accept(*this); + gate.tgt().accept(*this); + prefix_.resize(prefix_.length() - 2); + } + + void visit(ast::BarrierGate& gate) { + os_ << prefix_ << "|- Barrier\n"; + + prefix_ += " "; + gate.foreach_arg([this](auto& arg) { arg.accept(*this); }); + prefix_.resize(prefix_.length() - 2); + } + + void visit(ast::DeclaredGate& gate) { + os_ << prefix_ << "|- Declared(" << gate.name() << ")\n"; + + prefix_ += " "; + gate.foreach_qarg([this](auto& arg) { arg.accept(*this); }); + gate.foreach_carg([this](auto& arg) { arg.accept(*this); }); + prefix_.resize(prefix_.length() - 2); + } + + // Declarations + void visit(ast::GateDecl& decl) { + os_ << prefix_ << "|- Gate Decl(" << decl.id() << "("; + for (auto& param : decl.c_params()) { + os_ << param << ","; + } + os_ << ")["; + for (auto& param : decl.q_params()) { + os_ << param << ","; + } + os_ << "]"; + if (decl.is_opaque()) { + os_ << ", opaque"; + } + os_ << ")\n"; + + prefix_ += " "; + decl.foreach_stmt([this](auto& stmt) { stmt.accept(*this); }); + prefix_.resize(prefix_.length() - 2); + } + + void visit(ast::OracleDecl& decl) { + os_ << prefix_ << "|- Oracle Decl(" << decl.id() << "["; + for (auto& param : decl.params()) { + os_ << param << ","; + } + os_ << "] = " << decl.fname() << ")\n"; + } + + void visit(ast::RegisterDecl& decl) { + os_ << prefix_ << "|- Register Decl(" << decl.id() << "[" << decl.size() + << "]"; + if (decl.is_quantum()) { + os_ << ", quantum"; + } + os_ << ")\n"; + } + + void visit(ast::AncillaDecl& decl) { + os_ << prefix_ << "|- Ancilla Decl(" << decl.id() << "[" << decl.size() + << "]"; + if (decl.is_dirty()) { + os_ << ", dirty"; + } + os_ << ")\n"; + } + + // Program + void visit(ast::Program& prog) { + os_ << prefix_ << "|- Program\n"; + + prefix_ += " "; + prog.foreach_stmt([this](auto& stmt) { stmt.accept(*this); }); + prefix_.resize(prefix_.length() - 2); + } +}; + +void print_tree(ast::ASTNode& node, std::ostream& os = std::cout) { + ASTPrinter printer(os); + node.accept(printer); +} + +} /* namespace tools */ +} /* namespace tools_v1 */ + +#endif /* TOOLS_V1_TOOLS_ASTPRINTER_HPP_ */ diff --git a/experimental/include/tools_v1/tools/staq_builder.hpp b/experimental/include/tools_v1/tools/staq_builder.hpp new file mode 100644 index 00000000..3de81bf3 --- /dev/null +++ b/experimental/include/tools_v1/tools/staq_builder.hpp @@ -0,0 +1,286 @@ +#ifndef TOOLS_V1_STAQBUILDER_HPP_ +#define TOOLS_V1_STAQBUILDER_HPP_ + +#include "node_conversion.hpp" +#include "tools_v1/ast/ast.hpp" +#include "tools_v1/ast/decl.hpp" +#include "tools_v1/ast/expr.hpp" +#include "tools_v1/ast/gate_builder_simple.hpp" +#include "tools_v1/ast/program.hpp" +#include "tools_v1/ast/stmt.hpp" +#include "tools_v1/ast/visitor.hpp" +#include "tools_v1/parser/parser.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include "node_conversion.hpp" +#include + +namespace tools_v1::tools { + +class pauli_literal { +private: + std::string type_; + int qubit_index_; + +public: + pauli_literal(const std::string &type, int qubit_index) + : type_(type), qubit_index_(qubit_index) {} + + const std::string &type() const { return type_; } + int qubit_index() const { return qubit_index_; } +}; + +inline pauli_literal operator""_x(unsigned long long int x) noexcept { + return pauli_literal("X", static_cast(x)); +} + +inline pauli_literal operator""_y(unsigned long long int x) noexcept { + return pauli_literal("Y", static_cast(x)); +} + +inline pauli_literal operator""_z(unsigned long long int x) noexcept { + return pauli_literal("Z", static_cast(x)); +} + +class qbit { +private: + std::string register_name_; + int index_; + +public: + qbit() : register_name_("q"), index_(0) {} + qbit(const std::string ®ister_name, int index) + : register_name_(register_name), index_(index) {} + + qbit(int index) : register_name_("q"), index_(index) {} + + const std::string ®ister_name() const { return register_name_; } + int index() const { return index_; } + + ast::VarAccess to_va() const { + tools_v1::parser::Position pos; + return tools_v1::ast::VarAccess(pos, register_name_, index_); + } + + pauli_literal x() const { return pauli_literal("X", index_); } + pauli_literal y() const { return pauli_literal("Y", index_); } + pauli_literal z() const { return pauli_literal("Z", index_); } + + // Comparison operator for std::set + bool operator<(const qbit& other) const { + if (register_name_ != other.register_name_) { + return register_name_ < other.register_name_; + } + return index_ < other.index_; + } +}; + +class circuit { +private: + std::vector> _gates; + std::set> _ancilla; + std::set> _data; + +public: + using iterator = std::vector>::iterator; + using iterator_gate = std::vector>::iterator; + using const_iterator = std::vector>::const_iterator; + using reverse_iterator = std::vector>::reverse_iterator; + using const_reverse_iterator = + std::vector>::const_reverse_iterator; + + circuit() = default; + + // FIX: What are these for? + circuit(const circuit &) = delete; + circuit &operator=(const circuit &) = delete; + circuit(circuit &&) = default; + circuit &operator=(circuit &&) = default; + + void push_back(ast::ptr gate) { + _gates.push_back(std::move(gate)); + } + + void reserve(size_t n) { _gates.reserve(n); } + + size_t size() const { return _gates.size(); } + + bool empty() const { return _gates.empty(); } + + iterator begin() { return _gates.begin(); } + iterator end() { return _gates.end(); } + const_iterator begin() const { return _gates.begin(); } + const_iterator end() const { return _gates.end(); } + reverse_iterator rbegin() { return _gates.rbegin(); } + reverse_iterator rend() { return _gates.rend(); } + const_reverse_iterator rbegin() const { return _gates.rbegin(); } + const_reverse_iterator rend() const { return _gates.rend(); } + + void splice(iterator pos, iterator first, iterator last) { + auto pos_idx = std::distance(_gates.begin(), pos); + auto first_idx = std::distance(_gates.begin(), first); + auto last_idx = std::distance(_gates.begin(), last); + + std::vector> temp; + temp.reserve(last_idx - first_idx); + + for (auto it = first; it != last; ++it) { + temp.push_back(std::move(*it)); + } + + _gates.insert(_gates.begin() + pos_idx, + std::make_move_iterator(temp.begin()), + std::make_move_iterator(temp.end())); + } + + void operator+=(circuit c) { this->splice(this->end(), c.begin(), c.end()); } + + inline std::list> body_list() const { + using ptrStmt = tools_v1::ast::ptr; + std::list lst; + std::transform(_gates.begin(), _gates.end(), std::back_inserter(lst), + [](const ptrStmt &gate) -> ptrStmt { + return tools_v1::ast::object::clone(*gate); + }); + return lst; + } + + inline void save_data(qbit q) { _data.insert(std::make_unique(q)); } + + inline void save_ancilla(qbit q) { + _ancilla.insert(std::make_unique(q)); + auto it = _ancilla.begin(); + } + + std::set>::iterator ancilla_begin() const { + return _ancilla.begin(); + } + std::set>::iterator ancilla_end() const { + return _ancilla.end(); + } +}; + +// pauli_string function +inline ast::ptr +pauli_string(std::initializer_list paulis) { + tools_v1::parser::Position pos; + std::vector qubits; + std::vector pauli_types; + + for (const auto &p : paulis) { + qubits.emplace_back(pos, "q", p.qubit_index()); + if (p.type() == "X") { + pauli_types.push_back(ast::PauliType::X); + } else if (p.type() == "Y") { + pauli_types.push_back(ast::PauliType::Y); + } else if (p.type() == "Z") { + pauli_types.push_back(ast::PauliType::Z); + } + } + + return ast::PauliString::create(pos, std::move(qubits), + std::move(pauli_types)); +} + +inline circuit prepare(int k, std::string reg_name = "a") { + circuit c; + tools_v1::parser::Position pos; + + for (int i = 0; i < k; ++i) { + auto q = ast::VarAccess(pos, "a", i); + auto h = + ast::DeclaredGate::create(pos, "h", std::vector>(), + std::vector{q}); + c.push_back(std::move(h)); + } + + return c; +} + +inline circuit reverse_circuit(const circuit &c) { + circuit result; + for (auto it = c.rbegin(); it != c.rend(); ++it) { + result.push_back(tools_v1::ast::object::clone(**it)); + } + return result; +} + +[[deprecated("This function requires fixing: does not implement dagger.")]] +inline circuit dagger_circuit(const circuit &c) { + // FIX: + circuit result; + for (auto it = c.rbegin(); it != c.rend(); ++it) { + result.push_back(tools_v1::ast::object::clone(**it)); + } + return result; +} + +// Convenience array indexing for qubits +struct qbit_indexer { + qbit operator[](int index) const { return qbit(index); } +}; + +inline qbit_indexer qbit_access; + +inline ast::ptr hadamard(const qbit &q) { + tools_v1::parser::Position pos; + auto qubit = q.to_va(); + return ast::DeclaredGate::create(pos, "h", std::vector>(), + std::vector{qubit}); +} + +inline ast::ptr rz(const qbit &q, ast::ptr alpha) { + tools_v1::parser::Position pos; + auto qubit = q.to_va(); + std::vector> args; + args.push_back(std::move(alpha)); + return ast::DeclaredGate::create(pos, "rz", std::move(args), + std::vector{qubit}); +} + +inline ast::ptr cnot(const qbit &c, const qbit &t) { + tools_v1::parser::Position pos; + auto ctl = c.to_va(); + auto tgt = t.to_va(); + return ast::CNOTGate::create(pos, std::move(ctl), std::move(tgt)); +} + +// Output streaming operator for circuit +inline std::ostream &operator<<(std::ostream &os, const circuit &c) { + tools_v1::parser::Position pos; + std::list> body; + + auto qreg = ast::RegisterDecl::create(pos, "q", true, 16); + body.push_back(std::move(qreg)); + + auto areg = ast::RegisterDecl::create(pos, "a", true, 16); + body.push_back(std::move(areg)); + + for (const auto &gate : c) { + if (gate) { + GateToStmt cloner; + gate->accept(cloner); + if (cloner.cloned_gate) { + body.push_back(std::move(cloner.cloned_gate)); + } + } + } + + auto program = ast::Program::create(pos, true, std::move(body), 0, 16); + if (program) { + os << *program; + } else { + os << "Empty circuit"; + } + return os; +} + +} // namespace tools_v1::tools + +#endif diff --git a/experimental/include/tools_v1/utils/angle.hpp b/experimental/include/tools_v1/utils/angle.hpp new file mode 100644 index 00000000..24b5a4d7 --- /dev/null +++ b/experimental/include/tools_v1/utils/angle.hpp @@ -0,0 +1,271 @@ +/* + * This file is part of tools_v1. + * + * Copyright (c) 2019 - 2025 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. + * + * Adapted from Bruno Schmitt's Tweedledum library + */ + +/** + * \file tools_v1/utils/angle.hpp + * \brief Either symbolic or concrete representation of rotation angles + */ + +#ifndef TOOLS_V1_UTILS_ANGLE_HPP_ +#define TOOLS_V1_UTILS_ANGLE_HPP_ + +#include +#include +#include +#include +#include + +namespace tools_v1 { +namespace utils { + +/** + * \brief \f$ \pi \f$ + */ +constexpr double pi = 3.141592653589793238462643383279502884; + +/** + * \brief Simple class to represent rotation angles + * + * A angle can be defined symbolically or numerically. When defined symbolic the + * angle is always a multiple of pi, i.e., the symbolic value will always + * multiplied by pi. + * + * The numeric value of a rotation angle is given in radians (rad). + */ +class Angle { + using fraction = std::pair; + + std::variant value_; + + public: + constexpr Angle(int n, int d) : value_(std::make_pair(n, d)) { + if (d == 0) { + throw std::invalid_argument( + "Trying to construct angle with denominator 0"); + } + + normalize(); + } + constexpr Angle(fraction angle) : value_(angle) { + if (angle.second == 0) { + throw std::invalid_argument( + "Trying to construct angle with denominator 0"); + } + + normalize(); + } + constexpr Angle(double angle) : value_(angle) {} + + /** \brief Returns true if this angle is symbolically defined. */ + constexpr bool is_symbolic() const { + return std::holds_alternative(value_); + } + + /*! \brief Returns true if this angle is symbolically defined. */ + constexpr bool is_numeric() const { + return std::holds_alternative(value_); + } + + /*! \brief Returns the symbolic value of this angle. */ + constexpr std::optional symbolic_value() const { + if (std::holds_alternative(value_)) { + return std::get(value_); + } else { + return std::nullopt; + } + } + + /*! \brief Returns the numeric value of this angle. */ + constexpr double numeric_value() const { + if (std::holds_alternative(value_)) { + auto frac = std::get(value_); + return ((double)frac.first * pi) / (double)frac.second; + } else { + return std::get(value_); + } + } + + constexpr Angle operator-() const { + if (std::holds_alternative(value_)) { + auto frac = std::get(value_); + return Angle(2 * frac.second - frac.first, frac.second); + } else { + return Angle(-std::get(value_)); + } + } + + bool operator==(const Angle& other) const { + return numeric_value() == other.numeric_value(); + } + + bool operator!=(const Angle& other) const { + return numeric_value() != other.numeric_value(); + } + + Angle& operator+=(const Angle& rhs) { + if (is_symbolic() && rhs.is_symbolic()) { + auto [a, b] = std::get(value_); + auto [c, d] = std::get(rhs.value_); + value_ = fraction(a * d + c * b, b * d); + normalize(); + } else { + auto a = numeric_value(); + auto b = rhs.numeric_value(); + value_ = a + b; + } + + return *this; + } + + Angle& operator-=(const Angle& rhs) { + *this += -rhs; + return *this; + } + + Angle& operator*=(int fac) { + if (is_symbolic()) { + auto [a, b] = std::get(value_); + value_ = fraction(a * fac, b); + normalize(); + } else { + auto a = numeric_value(); + value_ = a * (double)fac; + } + + return *this; + } + + Angle& operator/=(int div) { + if (is_symbolic()) { + auto [a, b] = std::get(value_); + value_ = fraction(a, b * div); + normalize(); + } else { + auto a = numeric_value(); + value_ = a / (double)div; + } + + return *this; + } + + friend Angle operator+(const Angle& lhs, const Angle& rhs) { + auto tmp = lhs; + tmp += rhs; + return tmp; + } + + friend Angle operator-(const Angle& lhs, const Angle& rhs) { + return lhs + (-rhs); + } + + friend Angle operator*(const Angle& lhs, int rhs) { + auto tmp = lhs; + tmp *= rhs; + return tmp; + } + + friend Angle operator/(const Angle& lhs, int rhs) { + auto tmp = lhs; + tmp /= rhs; + return tmp; + } + + friend std::ostream& operator<<(std::ostream& os, const Angle& angle) { + if (angle.is_symbolic()) { + auto [a, b] = std::get(angle.value_); + switch (a) { + case 1: + break; + case -1: + os << '-'; + break; + default: + os << a << '*'; + break; + } + os << "pi"; + if (b != 1) { + os << "/" << b; + } + } else { + os << std::get(angle.value_); + } + + return os; + } + + private: + constexpr void normalize() { + if (is_numeric()) { + return; + } + + auto& [n, d] = std::get(value_); + if (n == 0) { + d = 1; + } else { + // calculate the sign & get absolute values + int sgn = 1; + if (n < 0) { + sgn = -1; + n = std::abs(n); + } + if (d < 0) { + sgn *= -1; + d = std::abs(d); + } + + // bring into lowest form + auto tmp = std::gcd(n, d); + n = n / tmp; + d = d / tmp; + + // bring into [0, 2pi) range + n = n % (2 * d); + if (sgn == -1) { + n = (2 * d) - n; + } + } + } +}; + +namespace angles { +/*! \brief identity */ +constexpr Angle zero(0, 1); +/*! \brief rotation angle of a T gate */ +constexpr Angle pi_quarter(1, 4); +/*! \brief rotation angle of a S gate (phase gate) */ +constexpr Angle pi_half(1, 2); +/*! \brief rotation angle of a Pauli-Z gate, Pauli-X (NOT) */ +constexpr Angle pi(1, 1); +} /* namespace angles */ + +} /* namespace utils */ +} /* namespace tools_v1 */ + +#endif /* TOOLS_V1_UTILS_ANGLE_HPP_ */ diff --git a/experimental/include/tools_v1/utils/templates.hpp b/experimental/include/tools_v1/utils/templates.hpp new file mode 100644 index 00000000..11807294 --- /dev/null +++ b/experimental/include/tools_v1/utils/templates.hpp @@ -0,0 +1,50 @@ +/* + * This file is part of tools_v1. + * + * Copyright (c) 2019 - 2025 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 tools_v1/utils/templates.hpp + * \brief Helper templates + */ + +#ifndef TOOLS_V1_UTILS_TEMPLATES_HPP_ +#define TOOLS_V1_UTILS_TEMPLATES_HPP_ + +namespace tools_v1 { +namespace utils { +/** + * \brief Convenience template for variant visitors + */ +template +struct overloaded : Ts... { + using Ts::operator()...; +}; +template +overloaded(Ts...) -> overloaded; + +} /* namespace utils */ +} /* namespace tools_v1 */ + +#endif /* TOOLS_V1_UTILS_TEMPLATES_HPP_ */ diff --git a/experimental/src/FastInversion.cpp b/experimental/src/FastInversion.cpp new file mode 100644 index 00000000..7b06e6a2 --- /dev/null +++ b/experimental/src/FastInversion.cpp @@ -0,0 +1,30 @@ +#include "node_conversion.hpp" +#include "square_hubbard_config.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Input: diagonal matrix D = diag(D_0, D_1, ..., D_n) // assume n is power of 2 +// 1. convert each of D_i -> binary D_i0, D_i1, ... D_il +// 2. create a multicontrolled gate that controls on i and flips the encoding qubits according to binary rep of D_i +// eg. i = 0, control [] :: [0,1,2,..] :: PauliString X[a] X[b] X[c]; +// eg. i = 1, control [0] :: [1,2,..] :: PauliString X[e] X[f] X[g]; +// eg. i = 2, control [1] :: [0,2,..] :: PauliString X[alpha] X[beta] X[gamma]; +// eg. i = 3, control [0,1] :: [2,..] :: PauliString X[zeta] X[rho] X[mu]; +// ... +// 3. aggregate these multicontrolled gates together to get a circuit OD +// 4. compute alphaDprime = (min_i D_i)^{-1} +// 5. Construct INV circuit that implements: INV|zeta>|0> = |zeta>( \frac{1}{alphaDprime zeta} |0> + (...)|1> ) + + +int main (int argc, char *argv[]) { + return 0; +} diff --git a/experimental/src/GS.cpp b/experimental/src/GS.cpp new file mode 100644 index 00000000..ce8534ef --- /dev/null +++ b/experimental/src/GS.cpp @@ -0,0 +1,320 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "square_hubbard_config.hpp" + +// ANSI color codes for terminal output +namespace colors { + constexpr auto reset = "\033[0m"; + constexpr auto red = "\033[31m"; + constexpr auto green = "\033[32m"; + constexpr auto yellow = "\033[33m"; + constexpr auto blue = "\033[34m"; + constexpr auto magenta = "\033[35m"; + constexpr auto cyan = "\033[36m"; + constexpr auto bold = "\033[1m"; + constexpr auto underline = "\033[4m"; + + // Combined styles + constexpr auto bold_red = "\033[1;31m"; + constexpr auto bold_green = "\033[1;32m"; + constexpr auto bold_yellow = "\033[1;33m"; + constexpr auto bold_blue = "\033[1;34m"; + constexpr auto bold_magenta = "\033[1;35m"; + constexpr auto bold_cyan = "\033[1;36m"; +} + +using namespace tools_v1::tools; + +circuit gs_guess(double mu, square_hubbard_config &HC) { + circuit gs_prep; + auto L = HC.L(); + + int L_int = static_cast(L); + assert(L_int % 2 == 0); + + std::set s; + for (int x = -L_int / 2 + 1; x <= L_int / 2-1; ++x) { + for (int y = -L_int / 2 + 1; y <= L_int / 2-1; ++y) { + if (HC.e_bare(x, y) <= mu) { + int n = HC.encoding_formula(x, y); + s.insert(n); + qbit q(n); + gs_prep.push_back(pauli_string({q.x()})); + } + } + } + std::print("{}Selected qubits: {}", colors::green, colors::reset); + for(auto& x : s) + std::print("{}{}{} ", colors::bold_blue, x, colors::reset); + std::println(""); + + return gs_prep; +} + +// Test function 1: Print encodings for various lattice sizes +void test_encodings() { + std::println("\n{}=== TEST 1: Encoding Verification ==={}", colors::bold_cyan, colors::reset); + + // Test small lattice (L=3) + std::println("\n{}L = 3 (3x3 grid):{}", colors::bold_yellow, colors::reset); + square_hubbard_config HC3(3, 1.0, 0.0); + for(int r = 1; r >= -1; --r){ + for(int c = -1; c <= 1; ++c){ + std::print("{}{:2}{} ", colors::green, HC3.encoding_formula(c, r), colors::reset); + } + std::println(""); + } + + // Test medium lattice (L=5) + std::println("\n{}L = 5 (5x5 grid center):{}", colors::bold_yellow, colors::reset); + square_hubbard_config HC5(5, 1.0, 0.0); + for(int r = 2; r >= -2; --r){ + for(int c = -2; c <= 2; ++c){ + std::print("{}{:3}{} ", colors::blue, HC5.encoding_formula(c, r), colors::reset); + } + std::println(""); + } + + // Test large lattice (L=7) - just show pattern + std::println("\n{}L = 7 (7x7 grid center):{}", colors::bold_yellow, colors::reset); + square_hubbard_config HC7(7, 1.0, 0.0); + for(int r = 3; r >= -3; --r){ + for(int c = -3; c <= 3; ++c){ + std::print("{}{:3}{}\t", colors::magenta, HC7.encoding_formula(c, r), colors::reset); + } + std::println(""); + } +} + +// Test function 2: Show excitations for different chemical potentials +void test_excitations() { + std::println("\n{}=== TEST 2: Excitation Selection ==={}", colors::bold_cyan, colors::reset); + + const int L = 4; + square_hubbard_config HC(L, 1.0, 0.0); + int L_int = static_cast(L); + + // Test different chemical potentials + std::vector mu_values = {-3.0, -2.0, -1.0, 0.0, 1.0, 2.0}; + + for(double mu : mu_values) { + std::println("\n{}Chemical potential μ = {}{}", colors::bold_yellow, mu, colors::reset); + + std::set selected_qubits; + for (int x = -L_int / 2 + 1; x <= L_int / 2; ++x) { + for (int y = -L_int / 2 + 1; y <= L_int / 2; ++y) { + if (HC.e_bare(x, y) <= mu) { + int n = HC.encoding_formula(x, y); + selected_qubits.insert(n); + } + } + } + + std::print("{}Selected qubits: {}", colors::green, colors::reset); + for(int q : selected_qubits) { + std::print("{}{}{} ", colors::bold_blue, q, colors::reset); + } + std::println("{} ({}{}{} total){}", colors::green, colors::bold_red, selected_qubits.size(), colors::reset, colors::reset); + + // Show corresponding coordinates + std::print("{}Corresponding coordinates: {}", colors::green, colors::reset); + for (int x = -L_int / 2 + 1; x <= L_int / 2; ++x) { + for (int y = -L_int / 2 + 1; y <= L_int / 2; ++y) { + if (HC.e_bare(x, y) <= mu) { + std::print("{} ({},{}) {}", colors::magenta, x, y, colors::reset); + } + } + } + std::println(""); + } +} + +// Test function 3: Verify specific known patterns +void test_known_patterns() { + std::println("\n{}=== TEST 3: Known Pattern Verification ==={}", colors::bold_cyan, colors::reset); + + square_hubbard_config HC(3, 1.0, 0.0); + + // Test specific coordinates that should match Figure 6 + std::vector> test_coords = { + {-1, 1}, {0, 1}, {1, 1}, + {-1, 0}, {0, 0}, {1, 0}, + {-1, -1}, {0, -1}, {1, -1} + }; + + std::vector expected = {8, 2, 6, 3, 0, 1, 10, 4, 12}; + + std::println("{}Expected pattern from Figure 6:{}", colors::bold_yellow, colors::reset); + for(size_t i = 0; i < test_coords.size(); ++i) { + int x = test_coords[i].first; + int y = test_coords[i].second; + int actual = HC.encoding_formula(x, y); + std::print("{} ({},{}) -> {}{}", colors::blue, x, y, colors::bold_magenta, actual); + if(actual == expected[i]) { + std::println(" {}✓{}", colors::bold_green, colors::reset); + } else { + std::println(" {}✗ (expected {}){}", colors::bold_red, expected[i], colors::reset); + } + } +} + +// Test function 4: Print grid with e_bare values instead of encoding +void test_energy_grid() { + std::println("\n{}=== TEST 4: Energy Grid Visualization ==={}", colors::bold_cyan, colors::reset); + + // Test with L=3 + std::println("\n{}L = 3 (3x3 grid) - e_bare values:{} ", colors::bold_yellow, colors::reset); + square_hubbard_config HC3(3, 1.0, 0.0); + for(int r = 1; r >= -1; --r){ + for(int c = -1; c <= 1; ++c){ + double energy = HC3.e_bare(c, r); + std::print("{}{:6.3f}{} ", colors::green, energy, colors::reset); + } + std::println(""); + } + + // Test with L=5 + std::println("\n{}L = 5 (5x5 grid center) - e_bare values:{} ", colors::bold_yellow, colors::reset); + square_hubbard_config HC5(5, 1.0, 0.0); + for(int r = 2; r >= -2; --r){ + for(int c = -2; c <= 2; ++c){ + double energy = HC5.e_bare(c, r); + std::print("{}{:6.3f}{} ", colors::blue, energy, colors::reset); + } + std::println(""); + } + + // Test with L=7 + std::println("\n{}L = 7 (7x7 grid center) - e_bare values:{} ", colors::bold_yellow, colors::reset); + square_hubbard_config HC7(7, 1.0, 0.0); + for(int r = 3; r >= -3; --r){ + for(int c = -3; c <= 3; ++c){ + double energy = HC7.e_bare(c, r); + std::print("{}{:6.3f}{} ", colors::magenta, energy, colors::reset); + } + std::println(""); + } +} + +// Test function 5: Colored grid showing ground state selection for various mu values +void test_ground_state_selection_grid() { + std::println("\n{}=== TEST 5: Ground State Selection Grid ==={}", colors::bold_cyan, colors::reset); + + const int L = 16; + square_hubbard_config HC(L, 1.0, 0.0); + int L_int = static_cast(L); + + // Test different chemical potentials + std::vector mu_values = {-3.0, -2.0, -1.0, 0.0, 1.0, 2.0}; + + for(double mu : mu_values) { + std::println("\n{}Chemical potential μ = {:.1f}{}", colors::bold_yellow, mu, colors::reset); + std::println("{}Legend: {}●{} = selected (e_bare ≤ μ), {}○{} = not selected{}", + colors::green, colors::bold_green, colors::green, colors::bold_red, colors::green, colors::reset); + + for(int r = L_int / 2 -1; r >= -L_int / 2 + 1; --r){ + for(int c = -L_int / 2 + 1; c <= L_int / 2-1; ++c){ + double energy = HC.e_bare(c, r); + if (energy <= mu) { + // Selected for ground state - green filled circle + std::print("{}●{} ", colors::bold_green, colors::reset); + } else { + // Not selected - red hollow circle + std::print("{}○{} ", colors::bold_red, colors::reset); + } + } + std::println(""); + } + + // Show coordinates and energies for selected qubits + std::print("{}Selected coordinates (e_bare ≤ μ): {}", colors::green, colors::reset); + for (int x = -L_int / 2 + 1; x <= L_int / 2 - 1; ++x) { + for (int y = -L_int / 2 + 1; y <= L_int / 2 - 1; ++y) { + if (HC.e_bare(x, y) <= mu) { + double energy = HC.e_bare(x, y); + std::print("{} ({},{}) [{:.3f}] {}", colors::magenta, x, y, energy, colors::reset); + } + } + } + std::println(""); + } +} + +// Test function 6: Detailed energy and selection analysis +void test_detailed_energy_analysis() { + std::println("\n{}=== TEST 6: Detailed Energy Analysis ==={}", colors::bold_cyan, colors::reset); + + const int L = 10; + square_hubbard_config HC(L, 1.0, 0.0); + int L_int = static_cast(L); + + std::println("{}L = {} grid:{} ", colors::bold_yellow, L, colors::reset); + + // Print header + std::print("{} ", colors::reset); + for(int c = -L_int / 2 + 1; c <= L_int / 2; ++c){ + std::print("{:>8} ", c); + } + std::println(""); + + // Print grid with both encoding and energy + for(int r = L_int / 2 - 1; r >= -L_int / 2 + 1; --r){ + std::print("{:>3} | ", r); + for(int c = -L_int / 2 + 1; c <= L_int / 2 -1; ++c){ + int encoding = HC.encoding_formula(c, r); + double energy = HC.e_bare(c, r); + std::print("{}[{:2d}:{:6.3f}]{} ", colors::cyan, encoding, energy, colors::reset); + } + std::println(""); + } + + // Show selection for different mu values + std::vector mu_values = {-2.5, -1.5, -0.5, 0.5, 1.5}; + for(double mu : mu_values) { + std::println("\n{}μ = {:.1f}:{}", colors::bold_yellow, mu, colors::reset); + + for(int r = L_int / 2-1; r >= -L_int / 2 + 1; --r){ + for(int c = -L_int / 2 + 1; c <= L_int / 2-1; ++c){ + double energy = HC.e_bare(c, r); + if (energy <= mu) { + std::print("{}[{:2d}:{:6.3f}]{} ", colors::bold_green, HC.encoding_formula(c, r), energy, colors::reset); + } else { + std::print("{}[{:2d}:{:6.3f}]{} ", colors::bold_red, HC.encoding_formula(c, r), energy, colors::reset); + } + } + std::println(""); + } + } +} + +int main() { + std::println("{}Running GS.cpp Tests{}", colors::bold_cyan, colors::reset); + std::println("{}===================={}", colors::bold_cyan, colors::reset); + + // Run all tests + test_encodings(); + test_excitations(); + test_known_patterns(); + test_energy_grid(); + test_ground_state_selection_grid(); + test_detailed_energy_analysis(); + + // Original functionality + std::println("\n{}=== Original Functionality ==={}", colors::bold_cyan, colors::reset); + const int L = 10; + square_hubbard_config HC(L, 1.0, 0.0); + double mu = -1.5; + circuit ground_state = gs_guess(mu, HC); + std::println("{}Ground state preparation circuit for μ = {}{}", colors::bold_yellow, mu, colors::reset); + std::cout << colors::green << ground_state << colors::reset << std::endl; + + return 0; +} diff --git a/experimental/src/Interaction.cpp b/experimental/src/Interaction.cpp new file mode 100644 index 00000000..7cefeecf --- /dev/null +++ b/experimental/src/Interaction.cpp @@ -0,0 +1,252 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// ANSI color codes for terminal output +namespace colors { +constexpr auto reset = "\033[0m"; +constexpr auto red = "\033[31m"; +constexpr auto green = "\033[32m"; +constexpr auto yellow = "\033[33m"; +constexpr auto blue = "\033[34m"; +constexpr auto magenta = "\033[35m"; +constexpr auto cyan = "\033[36m"; +constexpr auto bold = "\033[1m"; +constexpr auto underline = "\033[4m"; + +// Combined styles +constexpr auto bold_red = "\033[1;31m"; +constexpr auto bold_green = "\033[1;32m"; +constexpr auto bold_yellow = "\033[1;33m"; +constexpr auto bold_blue = "\033[1;34m"; +constexpr auto bold_magenta = "\033[1;35m"; +constexpr auto bold_cyan = "\033[1;36m"; +} // namespace colors + +using namespace tools_v1::algorithm; + +// Test function 1: Basic B term generation +void test_basic_B_term() { + std::cout << "\n" << colors::bold_cyan << "=== TEST 1: Basic B Term Generation ===" << colors::reset << std::endl; + + // Test with different lattice sizes + std::vector lattice_sizes = {2, 4, 8}; + + for (int L : lattice_sizes) { + std::cout << "\n" << colors::bold_yellow << "B term for L = " << L << " lattice:" << colors::reset << std::endl; + + circuit B_circuit = generate_B_term(L); + + std::cout << colors::bold_green << "B term circuit:" << colors::reset << std::endl; + std::cout << colors::green << B_circuit << colors::reset << std::endl; + + // Analyze the circuit + analyze_interaction_circuit(B_circuit, L); + } +} + +// Test function 2: i-B term generation +void test_iB_term() { + std::cout << "\n" << colors::bold_cyan << "=== TEST 2: i-B Term Generation ===" << colors::reset << std::endl; + + // Test with different lattice sizes + std::vector lattice_sizes = {2, 4, 6}; + + for (int L : lattice_sizes) { + std::cout << "\n" << colors::bold_yellow << "i-B term for L = " << L << " lattice:" << colors::reset << std::endl; + + circuit iB_circuit = generate_iB_term(L); + + std::cout << colors::bold_green << "i-B term circuit:" << colors::reset << std::endl; + std::cout << colors::green << iB_circuit << colors::reset << std::endl; + + // Analyze the circuit + analyze_interaction_circuit(iB_circuit, L); + } +} + +// Test function 3: Interaction block encoding +void test_interaction_block_encoding() { + std::cout << "\n" << colors::bold_cyan << "=== TEST 3: Interaction Block Encoding ===" << colors::reset << std::endl; + + std::vector lattice_sizes = {2, 3, 4}; + std::vector interaction_strengths = {0.5, 1.0, 2.0}; + + for (int L : lattice_sizes) { + for (double U : interaction_strengths) { + std::cout << "\n" << colors::bold_yellow << "Block encoding for L = " << L + << ", U = " << U << ":" << colors::reset << std::endl; + + circuit block_encoding = generate_interaction_block_encoding(L, U); + + std::cout << colors::bold_green << "Block encoding circuit:" << colors::reset << std::endl; + std::cout << colors::green << block_encoding << colors::reset << std::endl; + + // Analyze the circuit + analyze_interaction_circuit(block_encoding, L); + } + } +} + +// Test function 4: Complete Hubbard interaction +void test_hubbard_interaction() { + std::cout << "\n" << colors::bold_cyan << "=== TEST 4: Complete Hubbard Interaction ===" << colors::reset << std::endl; + + // Test different parameter combinations + std::vector> parameters = { + {1.0, 0.5}, // t=1.0, U=0.5 + {1.0, 1.0}, // t=1.0, U=1.0 + {1.0, 2.0}, // t=1.0, U=2.0 + {0.5, 1.0}, // t=0.5, U=1.0 + }; + + int lattice_size = 4; + + for (const auto &[t, U] : parameters) { + std::cout << "\n" << colors::bold_yellow << "Hubbard interaction for t = " + << t << ", U = " << U << ":" << colors::reset << std::endl; + + circuit hubbard_circuit = generate_hubbard_interaction(lattice_size, t, U); + + std::cout << colors::bold_green << "Hubbard interaction circuit:" << colors::reset << std::endl; + std::cout << colors::green << hubbard_circuit << colors::reset << std::endl; + + // Analyze the circuit + analyze_interaction_circuit(hubbard_circuit, lattice_size); + } +} + +// Test function 5: Circuit scaling analysis +void test_circuit_scaling() { + std::cout << "\n" << colors::bold_cyan << "=== TEST 5: Circuit Scaling Analysis ===" << colors::reset << std::endl; + + std::vector lattice_sizes = {2, 4, 6, 8}; + + std::cout << "\n" << colors::bold_yellow << "Circuit size scaling with lattice size:" << colors::reset << std::endl; + std::cout << colors::bold_magenta << "Lattice Size | B Term Gates | i-B Term Gates | Block Encoding Gates" << colors::reset << std::endl; + std::cout << colors::bold_magenta << "-------------|--------------|----------------|-------------------" << colors::reset << std::endl; + + for (int L : lattice_sizes) { + circuit B_circuit = generate_B_term(L); + circuit iB_circuit = generate_iB_term(L); + circuit block_encoding = generate_interaction_block_encoding(L); + + std::cout << colors::cyan << " " << L << " |" + << " " << B_circuit.size() << " |" + << " " << iB_circuit.size() << " |" + << " " << block_encoding.size() << colors::reset << std::endl; + } +} + +// Test function 6: Gate type analysis +void test_gate_type_analysis() { + std::cout << "\n" << colors::bold_cyan << "=== TEST 6: Gate Type Analysis ===" << colors::reset << std::endl; + + int lattice_size = 4; + double interaction_strength = 1.0; + + std::cout << "\n" << colors::bold_yellow << "Gate type distribution for L = " + << lattice_size << ", U = " << interaction_strength << ":" << colors::reset << std::endl; + + // Test different circuit types + std::cout << "\n" << colors::bold_green << "B Term:" << colors::reset << std::endl; + analyze_interaction_circuit(generate_B_term(lattice_size, interaction_strength), lattice_size); + + std::cout << "\n" << colors::bold_green << "i-B Term:" << colors::reset << std::endl; + analyze_interaction_circuit(generate_iB_term(lattice_size, interaction_strength), lattice_size); + + std::cout << "\n" << colors::bold_green << "Block Encoding:" << colors::reset << std::endl; + analyze_interaction_circuit(generate_interaction_block_encoding(lattice_size, interaction_strength), lattice_size); + + std::cout << "\n" << colors::bold_green << "Hubbard Interaction:" << colors::reset << std::endl; + analyze_interaction_circuit(generate_hubbard_interaction(lattice_size, 1.0, interaction_strength), lattice_size); +} + +// Test function 7: Parameter sensitivity +void test_parameter_sensitivity() { + std::cout << "\n" << colors::bold_cyan << "=== TEST 7: Parameter Sensitivity ===" << colors::reset << std::endl; + + int lattice_size = 4; + std::vector interaction_strengths = {0.1, 0.5, 1.0, 2.0, 5.0}; + + std::cout << "\n" << colors::bold_yellow << "Circuit size vs interaction strength (L = " + << lattice_size << "):" << colors::reset << std::endl; + std::cout << colors::bold_magenta << "Interaction Strength | B Term Gates | i-B Term Gates" << colors::reset << std::endl; + std::cout << colors::bold_magenta << "-------------------|--------------|----------------" << colors::reset << std::endl; + + for (double U : interaction_strengths) { + circuit B_circuit = generate_B_term(lattice_size, U); + circuit iB_circuit = generate_iB_term(lattice_size, U); + + std::cout << colors::cyan << " " << U << " |" + << " " << B_circuit.size() << " |" + << " " << iB_circuit.size() << colors::reset << std::endl; + } +} + +// Test function 8: Circuit structure verification +void test_circuit_structure() { + std::cout << "\n" << colors::bold_cyan << "=== TEST 8: Circuit Structure Verification ===" << colors::reset << std::endl; + + int lattice_size = 3; + double interaction_strength = 1.0; + + std::cout << "\n" << colors::bold_yellow << "Detailed circuit structure for L = " + << lattice_size << ":" << colors::reset << std::endl; + + // Test B term + std::cout << "\n" << colors::bold_green << "B Term Circuit Gates:" << colors::reset << std::endl; + circuit B_circuit = generate_B_term(lattice_size, interaction_strength); + int gate_count = 0; + for (const auto &gate : B_circuit) { + if (gate) { + std::cout << " Gate " << gate_count++ << ": " << *gate << std::endl; + } + } + + // Test i-B term + std::cout << "\n" << colors::bold_blue << "i-B Term Circuit Gates:" << colors::reset << std::endl; + circuit iB_circuit = generate_iB_term(lattice_size, interaction_strength); + gate_count = 0; + for (const auto &gate : iB_circuit) { + if (gate) { + std::cout << " Gate " << gate_count++ << ": " << *gate << std::endl; + } + } + + // Test block encoding + std::cout << "\n" << colors::bold_magenta << "Block Encoding Circuit Gates:" << colors::reset << std::endl; + circuit block_encoding = generate_interaction_block_encoding(lattice_size, interaction_strength); + gate_count = 0; + for (const auto &gate : block_encoding) { + if (gate) { + std::cout << " Gate " << gate_count++ << ": " << *gate << std::endl; + } + } +} + +int main() { + std::cout << colors::bold_cyan << "Running Interaction.cpp Tests" << colors::reset << std::endl; + std::cout << colors::bold_cyan << "=============================" << colors::reset << std::endl; + + // Run all tests + test_basic_B_term(); + test_iB_term(); + test_interaction_block_encoding(); + test_hubbard_interaction(); + test_circuit_scaling(); + test_gate_type_analysis(); + test_parameter_sensitivity(); + test_circuit_structure(); + + std::cout << "\n" << colors::bold_green << "All Interaction tests completed successfully!" << colors::reset << std::endl; + + return 0; +} \ No newline at end of file diff --git a/experimental/src/LCU.cpp b/experimental/src/LCU.cpp new file mode 100644 index 00000000..b57b7c93 --- /dev/null +++ b/experimental/src/LCU.cpp @@ -0,0 +1,219 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// ANSI color codes for terminal output +namespace colors { +constexpr auto reset = "\033[0m"; +constexpr auto red = "\033[31m"; +constexpr auto green = "\033[32m"; +constexpr auto yellow = "\033[33m"; +constexpr auto blue = "\033[34m"; +constexpr auto magenta = "\033[35m"; +constexpr auto cyan = "\033[36m"; +constexpr auto bold = "\033[1m"; +constexpr auto underline = "\033[4m"; + +// Combined styles +constexpr auto bold_red = "\033[1;31m"; +constexpr auto bold_green = "\033[1;32m"; +constexpr auto bold_yellow = "\033[1;33m"; +constexpr auto bold_blue = "\033[1;34m"; +constexpr auto bold_magenta = "\033[1;35m"; +constexpr auto bold_cyan = "\033[1;36m"; +} // namespace colors + +using namespace tools_v1::algorithm; + +// Test function 1: Basic LCU functionality +void test_basic_lcu() { + std::cout << "\n" << colors::bold_cyan << "=== TEST 1: Basic LCU Functionality ===" << colors::reset << std::endl; + + // Test with 2 ancillas and 4 coefficients + std::cout << "\n" << colors::bold_yellow << "LCU with 2 ancillas (4 coefficients):" << colors::reset << std::endl; + + std::vector ancillas = {qbit(0), qbit(1)}; + std::vector coefficients = {0.25, 0.25, 0.25, 0.25}; // Uniform + + // Create simple unitaries (identity gates for testing) + std::vector unitaries; + for (int i = 0; i < 4; ++i) { + circuit unitary; + // Add some simple gates for testing + unitary.push_back(hadamard(qbit(2))); + unitaries.push_back(std::move(unitary)); + } + + circuit lcu_circuit = LCU(coefficients, ancillas, unitaries); + + std::cout << colors::bold_green << "Full LCU circuit:" << colors::reset + << std::endl; + std::cout << colors::green << lcu_circuit << colors::reset << std::endl; +} + +// Test function 2: Two-unitaries LCU (as described in paper) +void test_two_unitaries_lcu() { + std::cout << "\n" + << colors::bold_cyan + << "=== TEST 2: Two-Unitaries LCU ===" << colors::reset + << std::endl; + + std::cout << "\n" + << colors::bold_yellow + << "LCU for two unitaries (paper example):" << colors::reset + << std::endl; + + qbit ancilla(0); + // Test coefficients + double c0 = 1.0; // Identity + double c1 = 0.5; // Some other coefficient + + // Create simple unitaries + circuit U0; // Identity-like + U0.push_back(hadamard(qbit(1))); + + circuit U1; // Another simple unitary + U1.push_back(hadamard(qbit(2))); + + circuit lcu_circuit = LCU_two_unitaries(c0, c1, U0, U1, ancilla); + + std::cout << colors::bold_green + << "Two-unitaries LCU circuit:" << colors::reset << std::endl; + std::cout << colors::green << lcu_circuit << colors::reset << std::endl; +} + +// Test function 3: LCU Prepare circuit +void test_lcu_prepare() { + std::cout << "\n" + << colors::bold_cyan + << "=== TEST 3: LCU Prepare Circuit ===" << colors::reset + << std::endl; + + // Test with different coefficient patterns + std::vector> test_coefficients = { + {0.25, 0.25, 0.25, 0.25}, // Uniform + {0.5, 0.3, 0.15, 0.05}, // Decreasing + {0.1, 0.4, 0.4, 0.1} // Symmetric + }; + + for (size_t i = 0; i < test_coefficients.size(); ++i) { + std::cout << "\n" + << colors::bold_yellow << "Prepare circuit for coefficients " + << (i + 1) << ":" << colors::reset << std::endl; + + std::vector ancillas = {qbit(0), qbit(1)}; + circuit prep_circuit = lcu_prepare(test_coefficients[i], ancillas); + + std::cout << colors::blue << prep_circuit << colors::reset << std::endl; + } +} + +// Test function 4: LCU Select circuit +void test_lcu_select() { + std::cout << "\n" + << colors::bold_cyan + << "=== TEST 4: LCU Select Circuit ===" << colors::reset + << std::endl; + + std::cout << "\n" + << colors::bold_yellow + << "Select circuit for 2 ancillas:" << colors::reset << std::endl; + + std::vector ancillas = {qbit(0), qbit(1)}; + std::vector unitaries; + + // Create different unitaries for testing + for (int i = 0; i < 4; ++i) { + circuit unitary; + // Each unitary applies different gates + unitary.push_back(hadamard(qbit(2))); + if (i % 2 == 0) { + unitary.push_back(hadamard(qbit(3))); + } + unitaries.push_back(std::move(unitary)); + } + + circuit select_circuit = lcu_select(ancillas, unitaries); + + std::cout << colors::magenta << select_circuit << colors::reset << std::endl; +} + +// Test function 5: LCU circuit analysis +void test_lcu_analysis() { + std::cout << "\n" + << colors::bold_cyan + << "=== TEST 5: LCU Circuit Analysis ===" << colors::reset + << std::endl; + + std::vector ancilla_counts = {1, 2, 3}; + + for (int n : ancilla_counts) { + std::cout << "\n" + << colors::bold_yellow << "LCU with " << n + << " ancilla qubits:" << colors::reset << std::endl; + + std::vector ancillas; + for (int i = 0; i < n; ++i) { + ancillas.push_back(qbit(i)); + } + + int num_coeffs = 1 << n; + std::vector coefficients(num_coeffs, 1.0 / num_coeffs); // Uniform + + std::vector unitaries; + for (int i = 0; i < num_coeffs; ++i) { + circuit unitary; + unitary.push_back(hadamard(qbit(n))); // Simple test gate + unitaries.push_back(std::move(unitary)); + } + + circuit lcu_circuit = LCU(coefficients, ancillas, unitaries); + + std::cout << colors::green << "Number of gates: " << lcu_circuit.size() + << colors::reset << std::endl; + + // Show first few gates for larger circuits + if (n > 1) { + std::cout << colors::magenta << "First 5 gates:" << colors::reset + << std::endl; + int count = 0; + for (const auto &gate : lcu_circuit) { + if (count >= 5) + break; + if (gate) { + std::cout << " " << *gate << std::endl; + count++; + } + } + if (lcu_circuit.size() > 5) { + std::cout << colors::cyan << "... and " << (lcu_circuit.size() - 5) + << " more gates" << colors::reset << std::endl; + } + } else { + std::cout << colors::green << lcu_circuit << colors::reset << std::endl; + } + } +} + +int main() { + std::cout << colors::bold_cyan << "Running LCU.cpp Tests" << colors::reset + << std::endl; + std::cout << colors::bold_cyan << "====================" << colors::reset + << std::endl; + + // Run all tests + test_basic_lcu(); + test_two_unitaries_lcu(); + test_lcu_prepare(); + test_lcu_select(); + test_lcu_analysis(); + + return 0; +} diff --git a/experimental/src/Observable.cpp b/experimental/src/Observable.cpp new file mode 100644 index 00000000..3103bd9a --- /dev/null +++ b/experimental/src/Observable.cpp @@ -0,0 +1,294 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// ANSI color codes for terminal output +namespace colors { +constexpr auto reset = "\033[0m"; +constexpr auto red = "\033[31m"; +constexpr auto green = "\033[32m"; +constexpr auto yellow = "\033[33m"; +constexpr auto blue = "\033[34m"; +constexpr auto magenta = "\033[35m"; +constexpr auto cyan = "\033[36m"; +constexpr auto bold = "\033[1m"; +constexpr auto underline = "\033[4m"; + +// Combined styles +constexpr auto bold_red = "\033[1;31m"; +constexpr auto bold_green = "\033[1;32m"; +constexpr auto bold_yellow = "\033[1;33m"; +constexpr auto bold_blue = "\033[1;34m"; +constexpr auto bold_magenta = "\033[1;35m"; +constexpr auto bold_cyan = "\033[1;36m"; +} // namespace colors + +using namespace tools_v1::algorithm; + +// Test function 1: Creation and annihilation operators +void test_creation_annihilation_operators() { + std::cout << "\n" << colors::bold_cyan << "=== TEST 1: Creation and Annihilation Operators ===" << colors::reset << std::endl; + + int lattice_size = 4; + qbit ancilla(0); + + // Test creation operator + std::cout << "\n" << colors::bold_yellow << "Creation operator for site 2:" << colors::reset << std::endl; + circuit creation_circuit = create_creation_operator(2, lattice_size, ancilla); + std::cout << colors::bold_green << "Creation operator circuit:" << colors::reset << std::endl; + std::cout << colors::green << creation_circuit << colors::reset << std::endl; + analyze_observable_circuit(creation_circuit, lattice_size); + + // Test annihilation operator + std::cout << "\n" << colors::bold_yellow << "Annihilation operator for site 1:" << colors::reset << std::endl; + circuit annihilation_circuit = create_annihilation_operator(1, lattice_size, ancilla); + std::cout << colors::bold_green << "Annihilation operator circuit:" << colors::reset << std::endl; + std::cout << colors::green << annihilation_circuit << colors::reset << std::endl; + analyze_observable_circuit(annihilation_circuit, lattice_size); +} + +// Test function 2: Kinetic term A +void test_kinetic_term_A() { + std::cout << "\n" << colors::bold_cyan << "=== TEST 2: Kinetic Term A ===" << colors::reset << std::endl; + + std::vector lattice_sizes = {2, 4, 6}; + std::vector hopping_strengths = {0.5, 1.0, 2.0}; + + for (int L : lattice_sizes) { + for (double t : hopping_strengths) { + std::cout << "\n" << colors::bold_yellow << "Kinetic term A for L = " << L + << ", t = " << t << ":" << colors::reset << std::endl; + + circuit A_circuit = create_kinetic_term_A(L, t); + std::cout << colors::bold_green << "Kinetic term A circuit:" << colors::reset << std::endl; + std::cout << colors::green << A_circuit << colors::reset << std::endl; + analyze_observable_circuit(A_circuit, L); + } + } +} + +// Test function 3: First inversion (z-i-A+E)^{-1} +void test_first_inversion() { + std::cout << "\n" << colors::bold_cyan << "=== TEST 3: First Inversion (z-i-A+E)^{-1} ===" << colors::reset << std::endl; + + int lattice_size = 4; + std::complex z(1.0, 0.5); + double E = 0.1; + std::vector qsvt_phases = {0.1, 0.2, 0.3, 0.4, 0.5}; + + std::cout << "\n" << colors::bold_yellow << "First inversion for z = " << z.real() << " + i" << z.imag() + << ", E = " << E << ":" << colors::reset << std::endl; + + circuit first_inversion = create_first_inversion(lattice_size, z, E, qsvt_phases); + std::cout << colors::bold_green << "First inversion circuit:" << colors::reset << std::endl; + std::cout << colors::green << first_inversion << colors::reset << std::endl; + analyze_observable_circuit(first_inversion, lattice_size); +} + +// Test function 4: Second inversion (I + (z-i-A+E)^{-1}(i-B))^{-1} +void test_second_inversion() { + std::cout << "\n" << colors::bold_cyan << "=== TEST 4: Second Inversion (I + (z-i-A+E)^{-1}(i-B))^{-1} ===" << colors::reset << std::endl; + + int lattice_size = 4; + std::complex z(1.0, 0.5); + double E = 0.1; + std::vector qsvt_phases_first = {0.1, 0.2, 0.3, 0.4, 0.5}; + std::vector qsvt_phases_second = {0.2, 0.3, 0.4, 0.5, 0.6}; + + // Create first inversion for input + circuit first_inversion = create_first_inversion(lattice_size, z, E, qsvt_phases_first); + + std::cout << "\n" << colors::bold_yellow << "Second inversion for z = " << z.real() << " + i" << z.imag() + << ", E = " << E << ":" << colors::reset << std::endl; + + circuit second_inversion = create_second_inversion(lattice_size, z, E, first_inversion, qsvt_phases_second); + std::cout << colors::bold_green << "Second inversion circuit:" << colors::reset << std::endl; + std::cout << colors::green << second_inversion << colors::reset << std::endl; + analyze_observable_circuit(second_inversion, lattice_size); +} + +// Test function 5: Complete observable circuit +void test_complete_observable() { + std::cout << "\n" << colors::bold_cyan << "=== TEST 5: Complete Observable Circuit ===" << colors::reset << std::endl; + + int lattice_size = 4; + int site_i = 1; + int site_j = 2; + std::complex z(1.0, 0.5); + double E = 0.1; + std::vector qsvt_phases_first = {0.1, 0.2, 0.3, 0.4, 0.5}; + std::vector qsvt_phases_second = {0.2, 0.3, 0.4, 0.5, 0.6}; + + std::cout << "\n" << colors::bold_yellow << "Complete observable for sites " << site_i << " -> " << site_j + << ", z = " << z.real() << " + i" << z.imag() << ", E = " << E << ":" << colors::reset << std::endl; + + circuit observable_circuit = create_observable_circuit(lattice_size, site_i, site_j, z, E, + qsvt_phases_first, qsvt_phases_second); + std::cout << colors::bold_green << "Complete observable circuit:" << colors::reset << std::endl; + std::cout << colors::green << observable_circuit << colors::reset << std::endl; + analyze_observable_circuit(observable_circuit, lattice_size); +} + +// Test function 6: Hadamard test for expectation values +void test_hadamard_test() { + std::cout << "\n" << colors::bold_cyan << "=== TEST 6: Hadamard Test for Expectation Values ===" << colors::reset << std::endl; + + int lattice_size = 4; + int site_i = 1; + int site_j = 2; + std::complex z(1.0, 0.5); + double E = 0.1; + std::vector qsvt_phases_first = {0.1, 0.2, 0.3, 0.4, 0.5}; + std::vector qsvt_phases_second = {0.2, 0.3, 0.4, 0.5, 0.6}; + + // Create observable circuit + circuit observable_circuit = create_observable_circuit(lattice_size, site_i, site_j, z, E, + qsvt_phases_first, qsvt_phases_second); + + // Create Hadamard test circuit + qbit test_ancilla(lattice_size * 2 + 1); + circuit hadamard_test = create_hadamard_test(observable_circuit, test_ancilla); + + std::cout << "\n" << colors::bold_yellow << "Hadamard test circuit for observable measurement:" << colors::reset << std::endl; + std::cout << colors::bold_green << "Hadamard test circuit:" << colors::reset << std::endl; + std::cout << colors::green << hadamard_test << colors::reset << std::endl; + analyze_observable_circuit(hadamard_test, lattice_size); +} + +// Test function 7: Circuit scaling analysis +void test_circuit_scaling() { + std::cout << "\n" << colors::bold_cyan << "=== TEST 7: Circuit Scaling Analysis ===" << colors::reset << std::endl; + + std::vector lattice_sizes = {2, 4, 6}; + std::complex z(1.0, 0.5); + double E = 0.1; + std::vector qsvt_phases = {0.1, 0.2, 0.3, 0.4, 0.5}; + + std::cout << "\n" << colors::bold_yellow << "Circuit size scaling with lattice size:" << colors::reset << std::endl; + std::cout << colors::bold_magenta << "Lattice Size | Creation Op | Annihilation Op | Kinetic A | First Inv | Second Inv | Complete Obs" << colors::reset << std::endl; + std::cout << colors::bold_magenta << "-------------|------------|-----------------|-----------|-----------|------------|-------------" << colors::reset << std::endl; + + for (int L : lattice_sizes) { + qbit ancilla(0); + circuit creation_op = create_creation_operator(1, L, ancilla); + circuit annihilation_op = create_annihilation_operator(1, L, ancilla); + circuit kinetic_A = create_kinetic_term_A(L); + circuit first_inv = create_first_inversion(L, z, E, qsvt_phases); + circuit second_inv = create_second_inversion(L, z, E, first_inv, qsvt_phases); + circuit complete_obs = create_observable_circuit(L, 1, 2, z, E, qsvt_phases, qsvt_phases); + + std::cout << colors::cyan << " " << L << " |" + << " " << creation_op.size() << " |" + << " " << annihilation_op.size() << " |" + << " " << kinetic_A.size() << " |" + << " " << first_inv.size() << " |" + << " " << second_inv.size() << " |" + << " " << complete_obs.size() << colors::reset << std::endl; + } +} + +// Test function 8: Parameter sensitivity +void test_parameter_sensitivity() { + std::cout << "\n" << colors::bold_cyan << "=== TEST 8: Parameter Sensitivity ===" << colors::reset << std::endl; + + int lattice_size = 4; + int site_i = 1; + int site_j = 2; + std::vector qsvt_phases = {0.1, 0.2, 0.3, 0.4, 0.5}; + + std::vector, double>> parameters = { + {{1.0, 0.0}, 0.1}, // z = 1+0i, E = 0.1 + {{1.0, 0.5}, 0.1}, // z = 1+0.5i, E = 0.1 + {{1.0, 1.0}, 0.1}, // z = 1+1i, E = 0.1 + {{1.0, 0.5}, 0.5}, // z = 1+0.5i, E = 0.5 + {{1.0, 0.5}, 1.0}, // z = 1+0.5i, E = 1.0 + }; + + std::cout << "\n" << colors::bold_yellow << "Circuit size vs parameters (L = " << lattice_size << "):" << colors::reset << std::endl; + std::cout << colors::bold_magenta << "z (real) | z (imag) | E | Complete Observable Gates" << colors::reset << std::endl; + std::cout << colors::bold_magenta << "---------|----------|---|-------------------------" << colors::reset << std::endl; + + for (const auto &[z, E] : parameters) { + circuit complete_obs = create_observable_circuit(lattice_size, site_i, site_j, z, E, + qsvt_phases, qsvt_phases); + + std::cout << colors::cyan << " " << z.real() << " |" + << " " << z.imag() << " |" + << " " << E << " |" + << " " << complete_obs.size() << colors::reset << std::endl; + } +} + +// Test function 9: Circuit structure verification +void test_circuit_structure() { + std::cout << "\n" << colors::bold_cyan << "=== TEST 9: Circuit Structure Verification ===" << colors::reset << std::endl; + + int lattice_size = 3; + int site_i = 1; + int site_j = 2; + std::complex z(1.0, 0.5); + double E = 0.1; + std::vector qsvt_phases = {0.1, 0.2, 0.3, 0.4, 0.5}; + + std::cout << "\n" << colors::bold_yellow << "Detailed circuit structure for L = " << lattice_size << ":" << colors::reset << std::endl; + + // Test creation operator + std::cout << "\n" << colors::bold_green << "Creation Operator Gates:" << colors::reset << std::endl; + qbit ancilla(0); + circuit creation_circuit = create_creation_operator(site_j, lattice_size, ancilla); + int gate_count = 0; + for (const auto &gate : creation_circuit) { + if (gate) { + std::cout << " Gate " << gate_count++ << ": " << *gate << std::endl; + } + } + + // Test annihilation operator + std::cout << "\n" << colors::bold_blue << "Annihilation Operator Gates:" << colors::reset << std::endl; + circuit annihilation_circuit = create_annihilation_operator(site_i, lattice_size, ancilla); + gate_count = 0; + for (const auto &gate : annihilation_circuit) { + if (gate) { + std::cout << " Gate " << gate_count++ << ": " << *gate << std::endl; + } + } + + // Test complete observable + std::cout << "\n" << colors::bold_magenta << "Complete Observable Circuit Gates:" << colors::reset << std::endl; + circuit observable_circuit = create_observable_circuit(lattice_size, site_i, site_j, z, E, + qsvt_phases, qsvt_phases); + gate_count = 0; + for (const auto &gate : observable_circuit) { + if (gate) { + std::cout << " Gate " << gate_count++ << ": " << *gate << std::endl; + } + } +} + +int main() { + std::cout << colors::bold_cyan << "Running Observable.cpp Tests" << colors::reset << std::endl; + std::cout << colors::bold_cyan << "============================" << colors::reset << std::endl; + + // Run all tests + test_creation_annihilation_operators(); + test_kinetic_term_A(); + test_first_inversion(); + test_second_inversion(); + test_complete_observable(); + test_hadamard_test(); + test_circuit_scaling(); + test_parameter_sensitivity(); + test_circuit_structure(); + + std::cout << "\n" << colors::bold_green << "All Observable tests completed successfully!" << colors::reset << std::endl; + + return 0; +} \ No newline at end of file diff --git a/experimental/src/QFT.cpp b/experimental/src/QFT.cpp new file mode 100644 index 00000000..f4fceec2 --- /dev/null +++ b/experimental/src/QFT.cpp @@ -0,0 +1,225 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// ANSI color codes for terminal output +namespace colors { +constexpr auto reset = "\033[0m"; +constexpr auto red = "\033[31m"; +constexpr auto green = "\033[32m"; +constexpr auto yellow = "\033[33m"; +constexpr auto blue = "\033[34m"; +constexpr auto magenta = "\033[35m"; +constexpr auto cyan = "\033[36m"; +constexpr auto bold = "\033[1m"; +constexpr auto underline = "\033[4m"; + +// Combined styles +constexpr auto bold_red = "\033[1;31m"; +constexpr auto bold_green = "\033[1;32m"; +constexpr auto bold_yellow = "\033[1;33m"; +constexpr auto bold_blue = "\033[1;34m"; +constexpr auto bold_magenta = "\033[1;35m"; +constexpr auto bold_cyan = "\033[1;36m"; +} // namespace colors + +using namespace tools_v1::algorithm; + +// Test function 1: Basic QFT functionality +void test_basic_qft() { + std::println("\n{}=== TEST 1: Basic QFT Functionality ==={}", + colors::bold_cyan, colors::reset); + + // Test with 3 qubits + std::println("\n{}QFT on 3 qubits:{}", colors::bold_yellow, colors::reset); + std::vector qubits3 = {qbit(0), qbit(1), qbit(2)}; + circuit qft3 = QFT(qubits3); + + // Debug: Print each gate individually + std::println("{}Debug - Individual gates in QFT circuit:{}", colors::bold_red, + colors::reset); + int gate_count = 0; + for (const auto &gate : qft3) { + if (gate) { + std::cout << " Gate " << gate_count++ << ": " << *gate; + } + } + + std::println("\n{}Full circuit output:{}", colors::bold_green, colors::reset); + std::cout << colors::green << qft3 << colors::reset << std::endl; + + // Test with 4 qubits + std::println("\n{}QFT on 4 qubits:{}", colors::bold_yellow, colors::reset); + std::vector qubits4 = {qbit(0), qbit(1), qbit(2), qbit(3)}; + circuit qft4 = QFT(qubits4); + std::cout << colors::blue << qft4 << colors::reset << std::endl; +} + +// Test function 2: Inverse QFT +void test_inverse_qft() { + std::println("\n{}=== TEST 2: Inverse QFT ==={}", colors::bold_cyan, + colors::reset); + + // Test with 3 qubits + std::println("\n{}Inverse QFT on 3 qubits:{}", colors::bold_yellow, + colors::reset); + std::vector qubits3 = {qbit(0), qbit(1), qbit(2)}; + circuit inv_qft3 = inverse_QFT(qubits3); + std::cout << colors::green << inv_qft3 << colors::reset << std::endl; +} + +// Test function 3: QFT followed by inverse QFT (should be identity) +void test_qft_roundtrip() { + std::println("\n{}=== TEST 3: QFT Roundtrip ==={}", colors::bold_cyan, + colors::reset); + + std::println("\n{}QFT followed by inverse QFT on 2 qubits:{}", + colors::bold_yellow, colors::reset); + std::vector qubits2 = {qbit(0), qbit(1)}; + + circuit qft2 = QFT(qubits2); + circuit inv_qft2 = inverse_QFT(qubits2); + + std::println("{}Forward QFT:{}", colors::bold_green, colors::reset); + std::cout << colors::green << qft2 << colors::reset << std::endl; + + std::println("{}Inverse QFT:{}", colors::bold_blue, colors::reset); + std::cout << colors::blue << inv_qft2 << colors::reset << std::endl; + + std::println("{}Note: QFT followed by inverse QFT should be identity{}", + colors::bold_magenta, colors::reset); +} + +// Test function 4: Phase rotation gates +void test_phase_rotations() { + std::println("\n{}=== TEST 4: Phase Rotation Gates ==={}", colors::bold_cyan, + colors::reset); + + std::println("\n{}Individual phase rotation gates:{}", colors::bold_yellow, + colors::reset); + + // Test R2 gate + std::println("\n{}R2 gate (π/2 rotation):{}", colors::bold_green, + colors::reset); + auto r2 = phase_rotation(2, qbit(0)); + if (r2) { + std::cout << colors::green << *r2 << colors::reset << std::endl; + } else { + std::println("{}Failed to create R2 gate{}", colors::bold_red, + colors::reset); + } + + // Test R3 gate + std::println("\n{}R3 gate (π/4 rotation):{}", colors::bold_blue, + colors::reset); + auto r3 = phase_rotation(3, qbit(0)); + if (r3) { + std::cout << colors::blue << *r3 << colors::reset << std::endl; + } else { + std::println("{}Failed to create R3 gate{}", colors::bold_red, + colors::reset); + } + + // Test controlled R2 gate + std::println("\n{}Controlled R2 gate:{}", colors::bold_magenta, + colors::reset); + auto controlled_r2 = controlled_phase_rotation(2, qbit(1), qbit(0)); + if (controlled_r2) { + std::cout << colors::magenta << *controlled_r2 << colors::reset + << std::endl; + } else { + std::println("{}Failed to create controlled R2 gate{}", colors::bold_red, + colors::reset); + } + + // Test a small circuit with phase gates + std::println("\n{}Small circuit with phase gates:{}", colors::bold_yellow, + colors::reset); + circuit phase_circuit; + phase_circuit.push_back(phase_rotation(2, qbit(0))); + phase_circuit.push_back(phase_rotation(3, qbit(1))); + phase_circuit.push_back(controlled_phase_rotation(2, qbit(1), qbit(0))); + std::cout << colors::cyan << phase_circuit << colors::reset << std::endl; +} + +// Test function 5: QFT circuit analysis +void test_qft_analysis() { + std::println("\n{}=== TEST 5: QFT Circuit Analysis ==={}", colors::bold_cyan, + colors::reset); + + std::vector qubit_counts = {2, 3, 4, 5}; + + for (int n : qubit_counts) { + std::println("\n{}QFT with {} qubits:{}", colors::bold_yellow, n, + colors::reset); + + std::vector qubits; + for (int i = 0; i < n; ++i) { + qubits.push_back(qbit(i)); + } + + circuit qft_circuit = QFT(qubits); + + std::println("{}Number of gates: {}{}", colors::green, qft_circuit.size(), + colors::reset); + + // Calculate expected number of gates + int expected_hadamards = n; + int expected_controlled_rotations = n * (n - 1) / 2; + int expected_total = expected_hadamards + expected_controlled_rotations; + + std::println( + "{}Expected gates: {} ({} Hadamards + {} controlled rotations){}", + colors::blue, expected_total, expected_hadamards, + expected_controlled_rotations, colors::reset); + + if (qft_circuit.size() == expected_total) { + std::println("{}✓ Gate count matches expected{}", colors::bold_green, + colors::reset); + } else { + std::println("{}✗ Gate count mismatch{}", colors::bold_red, + colors::reset); + } + + // Show first few gates for larger circuits + if (n > 3) { + std::println("{}First 5 gates:{}", colors::magenta, colors::reset); + int count = 0; + for (const auto &gate : qft_circuit) { + if (count >= 5) + break; + if (gate) { + std::cout << " " << *gate << std::endl; + count++; + } + } + if (qft_circuit.size() > 5) { + std::println("{}... and {} more gates{}", colors::cyan, + qft_circuit.size() - 5, colors::reset); + } + } else { + std::cout << colors::green << qft_circuit << colors::reset << std::endl; + } + } +} + +int main() { + std::println("{}Running QFT.cpp Tests{}", colors::bold_cyan, colors::reset); + std::println("{}===================={}", colors::bold_cyan, colors::reset); + + // Run all tests + test_basic_qft(); + test_inverse_qft(); + test_qft_roundtrip(); + test_phase_rotations(); + test_qft_analysis(); + + return 0; +} diff --git a/experimental/src/QSVT.cpp b/experimental/src/QSVT.cpp new file mode 100644 index 00000000..acb0f3cb --- /dev/null +++ b/experimental/src/QSVT.cpp @@ -0,0 +1,334 @@ +#include +#include +#include +#include +#include +#include + +// ANSI color codes for terminal output +namespace colors { +constexpr auto reset = "\033[0m"; +constexpr auto red = "\033[31m"; +constexpr auto green = "\033[32m"; +constexpr auto yellow = "\033[33m"; +constexpr auto blue = "\033[34m"; +constexpr auto magenta = "\033[35m"; +constexpr auto cyan = "\033[36m"; +constexpr auto bold = "\033[1m"; +constexpr auto underline = "\033[4m"; + +// Combined styles +constexpr auto bold_red = "\033[1;31m"; +constexpr auto bold_green = "\033[1;32m"; +constexpr auto bold_yellow = "\033[1;33m"; +constexpr auto bold_blue = "\033[1;34m"; +constexpr auto bold_magenta = "\033[1;35m"; +constexpr auto bold_cyan = "\033[1;36m"; +} // namespace colors + +using namespace tools_v1::algorithm; + +// Test function 1: Basic QSVT functionality +void test_basic_qsvt() { + std::cout << "\n" + << colors::bold_cyan + << "=== TEST 1: Basic QSVT Functionality ===" << colors::reset + << std::endl; + + // Test parameters for d=1 polynomial (3 phases) + std::vector phi = {0.1, 0.2, 0.3}; + + std::cout << "\n" + << colors::bold_yellow + << "QSVT with 3 phases (d=1):" << colors::reset << std::endl; + + // Create test qubits + std::vector control_ancilla = {qbit(1), qbit(2)}; // 2 control ancillas + qbit qsvt_ancilla(0); // QSVT ancilla + + // Create a simple test unitary (Hadamard on data qubit) + circuit test_u; + qbit data_qubit(3); + test_u.push_back(hadamard(data_qubit)); + for (const auto &ctrl : control_ancilla) { + test_u.save_ancilla(ctrl); + } + + std::cout << "Test parameters:" << std::endl; + std::cout << "- Number of phases: " << phi.size() << std::endl; + std::cout << "- Control ancillas: " << control_ancilla.size() << std::endl; + + try { + circuit qsvt_result = QSVT(phi, test_u, qsvt_ancilla); + + std::cout << "\n" + << colors::bold_green << "✅ QSVT function executed successfully!" + << colors::reset << std::endl; + std::cout << "Circuit size: " << qsvt_result.size() << " gates" + << std::endl; + + std::cout << "\n" + << colors::bold_green << "Full QSVT circuit:" << colors::reset + << std::endl; + std::cout << colors::green << qsvt_result << colors::reset << std::endl; + + } catch (const std::exception &e) { + std::cout << "\n" + << colors::bold_red << "❌ Error in QSVT function: " << e.what() + << colors::reset << std::endl; + } +} + +// Test function 2: QSVT with different phase counts +void test_qsvt_phase_counts() { + std::cout << "\n" + << colors::bold_cyan + << "=== TEST 2: QSVT with Different Phase Counts ===" + << colors::reset << std::endl; + + std::vector phase_counts = {3, 5, 7}; // d=1, d=2, d=3 + + for (int num_phases : phase_counts) { + std::cout << "\n" + << colors::bold_yellow << "QSVT with " << num_phases + << " phases:" << colors::reset << std::endl; + + // Create phases + std::vector phi; + for (int i = 0; i < num_phases; ++i) { + phi.push_back(0.1 * (i + 1)); + } + + // Create test qubits + std::vector control_ancilla = {qbit(1)}; // 1 control ancilla + qbit qsvt_ancilla(0); + + // Create test unitary + circuit test_u; + qbit data_qubit(2); + test_u.push_back(hadamard(data_qubit)); + for (const auto &ctrl : control_ancilla) { + test_u.save_ancilla(ctrl); + } + + try { + circuit qsvt_result = QSVT(phi, test_u, qsvt_ancilla); + + std::cout << "Circuit size: " << qsvt_result.size() << " gates" + << std::endl; + + // Show first few gates for larger circuits + if (num_phases > 3) { + std::cout << colors::magenta << "First 5 gates:" << colors::reset + << std::endl; + int count = 0; + for (const auto &gate : qsvt_result) { + if (count >= 5) + break; + if (gate) { + std::cout << " " << *gate << std::endl; + count++; + } + } + if (qsvt_result.size() > 5) { + std::cout << colors::cyan << "... and " << qsvt_result.size() - 5 + << " more gates" << colors::reset << std::endl; + } + } + + } catch (const std::exception &e) { + std::cout << colors::bold_red << "❌ Error: " << e.what() << colors::reset + << std::endl; + } + } +} + +// Test function 3: QSVT circuit analysis +void test_qsvt_analysis() { + std::cout << "\n" + << colors::bold_cyan + << "=== TEST 3: QSVT Circuit Analysis ===" << colors::reset + << std::endl; + + // Test with different numbers of ancilla qubits + std::vector ancilla_counts = {1, 2, 3}; + + for (int num_ancilla : ancilla_counts) { + std::cout << "\n" + << colors::bold_yellow << "QSVT with " << num_ancilla + << " ancilla qubits:" << colors::reset << std::endl; + + // Create phases for d=1 polynomial + std::vector phi = {0.1, 0.2, 0.3}; + + // Create ancilla qubits + std::vector control_ancilla; + for (int i = 0; i < num_ancilla; ++i) { + control_ancilla.push_back(qbit(i + 1)); + } + qbit qsvt_ancilla(0); + + // Create test unitary + circuit test_u; + qbit data_qubit(num_ancilla + 1); + test_u.push_back(hadamard(data_qubit)); + for (const auto &ctrl : control_ancilla) { + test_u.save_ancilla(ctrl); + } + + try { + circuit qsvt_result = QSVT(phi, test_u, qsvt_ancilla); + + std::cout << colors::green << "Number of gates: " << qsvt_result.size() + << colors::reset << std::endl; + + // Calculate expected gate counts + int expected_cnots = 2 * num_ancilla * phi.size(); // CNOTs for each phase + int expected_rotations = phi.size(); // One rotation per phase + int expected_hadamards = 2; // Initial and final + int expected_unitaries = phi.size() - 1; // U/U† applications + int expected_total = expected_cnots + expected_rotations + + expected_hadamards + expected_unitaries; + + std::cout << colors::blue << "Expected gates: " << expected_total << " (" + << expected_cnots << " CNOTs + " << expected_rotations + << " rotations + " << expected_hadamards << " Hadamards + " + << expected_unitaries << " unitaries)" << colors::reset + << std::endl; + + if (qsvt_result.size() == expected_total) { + std::cout << colors::bold_green << "✓ Gate count matches expected" + << colors::reset << std::endl; + } else { + std::cout << colors::bold_red << "✗ Gate count mismatch" + << colors::reset << std::endl; + } + + } catch (const std::exception &e) { + std::cout << colors::bold_red << "❌ Error: " << e.what() << colors::reset + << std::endl; + } + } +} + +// Test function 4: Alternative QSVT implementation +void test_alternative_qsvt() { + std::cout << "\n" + << colors::bold_cyan + << "=== TEST 4: Alternative QSVT Implementation ===" + << colors::reset << std::endl; + + std::vector phi = {0.1, 0.2, 0.3, 0.4, 0.5}; // 5 phases + + std::cout << "\n" + << colors::bold_yellow + << "Alternative QSVT with 5 phases:" << colors::reset << std::endl; + + std::vector control_ancilla = {qbit(1)}; + qbit qsvt_ancilla(0); + + circuit test_u; + qbit data_qubit(2); + test_u.push_back(hadamard(data_qubit)); + for (const auto &ctrl : control_ancilla) { + test_u.save_ancilla(ctrl); + } + + try { + std::cout << colors::yellow + << "Alternative implementation unavailable; running baseline QSVT" + << colors::reset << std::endl; + circuit qsvt_result = QSVT(phi, test_u, qsvt_ancilla); + + std::cout << colors::green + << "Alternative QSVT circuit size: " << qsvt_result.size() + << " gates" << colors::reset << std::endl; + + std::cout << colors::blue << "First 8 gates:" << colors::reset << std::endl; + int count = 0; + for (const auto &gate : qsvt_result) { + if (count >= 8) + break; + if (gate) { + std::cout << " " << *gate << std::endl; + count++; + } + } + if (qsvt_result.size() > 8) { + std::cout << colors::cyan << "... and " << qsvt_result.size() - 8 + << " more gates" << colors::reset << std::endl; + } + + } catch (const std::exception &e) { + std::cout << colors::bold_red + << "❌ Error in alternative QSVT: " << e.what() << colors::reset + << std::endl; + } +} + +// Test function 5: Z rotation gates +void test_z_rotations() { + std::cout << "\n" + << colors::bold_cyan + << "=== TEST 5: Z Rotation Gates ===" << colors::reset << std::endl; + + std::cout << "\n" + << colors::bold_yellow + << "Individual Z rotation gates:" << colors::reset << std::endl; + + // Test Rz(π/4) gate + std::cout << "\n" + << colors::bold_green << "Rz(π/4) gate:" << colors::reset + << std::endl; + auto rz_pi4 = rz_gate(std::numbers::pi / 4.0, qbit(0)); + if (rz_pi4) { + std::cout << colors::green << *rz_pi4 << colors::reset << std::endl; + } else { + std::cout << colors::bold_red << "Failed to create Rz(π/4) gate" + << colors::reset << std::endl; + } + + // Test controlled Rz(π/2) gate + std::cout << "\n" + << colors::bold_blue << "Controlled Rz(π/2) gate:" << colors::reset + << std::endl; + auto controlled_rz = + controlled_rz_gate(std::numbers::pi / 2.0, qbit(1), qbit(0)); + if (controlled_rz) { + std::cout << colors::blue << *controlled_rz << colors::reset << std::endl; + } else { + std::cout << colors::bold_red << "Failed to create controlled Rz(π/2) gate" + << colors::reset << std::endl; + } + + // Test multi-controlled Rz gate + std::cout << "\n" + << colors::bold_magenta + << "Multi-controlled Rz(π/3) gate:" << colors::reset << std::endl; + std::vector controls = {qbit(1), qbit(2)}; + auto multi_controlled_rz = + multi_controlled_rz_gate(std::numbers::pi / 3.0, controls, qbit(0)); + if (multi_controlled_rz) { + std::cout << colors::magenta << *multi_controlled_rz << colors::reset + << std::endl; + } else { + std::cout << colors::bold_red << "Failed to create multi-controlled Rz gate" + << colors::reset << std::endl; + } +} + +int main() { + std::cout << colors::bold_cyan << "Running QSVT.cpp Tests" << colors::reset + << std::endl; + std::cout << colors::bold_cyan << "=====================" << colors::reset + << std::endl; + + // Run all tests + test_basic_qsvt(); + test_qsvt_phase_counts(); + test_qsvt_analysis(); + test_alternative_qsvt(); + test_z_rotations(); + + return 0; +} diff --git a/experimental/src/TrotterHam1.cpp b/experimental/src/TrotterHam1.cpp new file mode 100644 index 00000000..13552997 --- /dev/null +++ b/experimental/src/TrotterHam1.cpp @@ -0,0 +1,801 @@ +#include "node_conversion.hpp" +#include "square_hubbard_config.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// ANSI color codes for terminal output +namespace colors { +constexpr auto reset = "\033[0m"; +constexpr auto red = "\033[31m"; +constexpr auto green = "\033[32m"; +constexpr auto yellow = "\033[33m"; +constexpr auto blue = "\033[34m"; +constexpr auto magenta = "\033[35m"; +constexpr auto cyan = "\033[36m"; +constexpr auto bold = "\033[1m"; +constexpr auto underline = "\033[4m"; + +// Combined styles +constexpr auto bold_red = "\033[1;31m"; +constexpr auto bold_green = "\033[1;32m"; +constexpr auto bold_yellow = "\033[1;33m"; +constexpr auto bold_blue = "\033[1;34m"; +constexpr auto bold_magenta = "\033[1;35m"; +constexpr auto bold_cyan = "\033[1;36m"; +} // namespace colors + +using namespace tools_v1::tools; + +// Block Encoding of Exp( i alpha c_{a}^\dagger c_{a} ) +circuit exp_c_dag_c(qbit a, tools_v1::ast::ptr alpha) { + circuit t; + t.push_back(rz(a, std::move(alpha))); + return t; +} + +// Block Encoding of Exp( i alpha [ c_{a}^\dagger c_{b}^\dagger c_{c} c_{d} + +// c_{a} c_{b} c_{c}^\dagger c_{d} ] ) +circuit exp_four_fermion(qbit a, qbit b, qbit c, qbit d, + tools_v1::ast::ptr alpha) { + circuit t; + tools_v1::parser::Position pos; + + // Create -2*alpha expression for the controlled rotation + auto minus_two_alpha = tools_v1::ast::BExpr::create( + pos, tools_v1::ast::RealExpr::create(pos, -2.0), + tools_v1::ast::BinaryOp::Times, tools_v1::ast::object::clone(*alpha)); + + // Step 1: Apply X gates to qubits a, b, d + t.push_back(pauli_string({a.x()})); + t.push_back(pauli_string({b.x()})); + t.push_back(pauli_string({d.x()})); + + // Step 2: Apply CNOT gates: CNOT[d,a], CNOT[d,b], CNOT[d,c] + t.push_back(tools_v1::ast::CNOTGate::create(pos, d.to_var_access(), + a.to_var_access())); + t.push_back(tools_v1::ast::CNOTGate::create(pos, d.to_var_access(), + b.to_var_access())); + t.push_back(tools_v1::ast::CNOTGate::create(pos, d.to_var_access(), + c.to_var_access())); + + // Step 3: Apply Hadamard to qubit d + t.push_back(hadamard(d)); + + // Step 4: Apply controlled Rz(-2*alpha) with controls [a,b,c] on target d + std::vector controls = { + a.to_var_access(), b.to_var_access(), c.to_var_access()}; + auto rz_gate = rz(d, std::move(minus_two_alpha)); + + // GateConverter rz_converter; + // rz_gate->accept(rz_converter); + + auto multi_controlled_rz = tools_v1::ast::MultiControlGate::create( + pos, std::move(controls), std::vector{}, + // std::move(rz_converter.converted_gate) + tools_v1::ast::object::clone(*rz_gate)); + t.push_back(std::move(multi_controlled_rz)); + + // Step 5: Apply Hadamard to qubit d + t.push_back(hadamard(d)); + + // Step 6: Apply CNOT gates in reverse: CNOT[d,c], CNOT[d,b], CNOT[d,a] + t.push_back(tools_v1::ast::CNOTGate::create(pos, d.to_var_access(), + c.to_var_access())); + t.push_back(tools_v1::ast::CNOTGate::create(pos, d.to_var_access(), + b.to_var_access())); + t.push_back(tools_v1::ast::CNOTGate::create(pos, d.to_var_access(), + a.to_var_access())); + + // Step 7: Apply X gates to qubits d, b, a + t.push_back(pauli_string({d.x()})); + t.push_back(pauli_string({b.x()})); + t.push_back(pauli_string({a.x()})); + + return t; +} + +// Block encoding for full kinetic term +circuit exp_kinetic(std::vector qubits, + tools_v1::ast::ptr alpha) { + /* input: qubits = { q0, q1, ... } + * alpha = expr + * output: + * product_{q in qubits} exp(i alpha c_{q}^dagger c_{q} ) + */ + circuit t; + // I think the result should be: + for (auto &q : qubits) { + auto a = tools_v1::ast::object::clone(*alpha); + auto c = exp_c_dag_c(q, std::move(a)); + for (auto &s : c) { + t.push_back(std::move(s)); + } + } + return t; +} + +// Block encoding for full interacting term +circuit exp_interaction(std::vector> pairings, + tools_v1::ast::ptr alpha) { + /* input: pairings = { {a0,a1,a2,a3}, {b0,b1,b2,b3}, ... } + * alpha = expr + * output: + * product_{p in pairings} exp(i alpha c_{p[0]}^dagger c_{p[1]}^dagger + * c_{p[2]} c_{p[3]} ) + */ + circuit t; + // I think the result should be: + for (auto &p : pairings) { + auto a = tools_v1::ast::object::clone(*alpha); + auto c = exp_four_fermion(p[0], p[1], p[2], p[3], std::move(a)); + for (auto &s : c) { + t.push_back(std::move(s)); + } + } + return t; +} + +// Test function 1: Basic exp_c_dag_c functionality +void test_exp_c_dag_c() { + std::println("\n{}=== TEST 1: exp_c_dag_c Functionality ==={}", + colors::bold_cyan, colors::reset); + + tools_v1::parser::Position pos; + auto alpha = tools_v1::ast::RealExpr::create(pos, 1.5); + + std::println("\n{}exp_c_dag_c on qubit 0 with α = 1.5:{}", + colors::bold_yellow, colors::reset); + circuit c_dag_c = exp_c_dag_c(qbit(0), std::move(alpha)); + std::cout << colors::green << c_dag_c << colors::reset << std::endl; + + // Test with different qubit + auto alpha2 = tools_v1::ast::RealExpr::create(pos, 0.8); + std::println("\n{}exp_c_dag_c on qubit 3 with α = 0.8:{}", + colors::bold_yellow, colors::reset); + circuit c_dag_c2 = exp_c_dag_c(qbit(3), std::move(alpha2)); + std::cout << colors::blue << c_dag_c2 << colors::reset << std::endl; +} + +// Test function 2: Basic exp_four_fermion functionality +void test_exp_four_fermion() { + std::println("\n{}=== TEST 2: exp_four_fermion Functionality ==={}", + colors::bold_cyan, colors::reset); + + tools_v1::parser::Position pos; + auto alpha = tools_v1::ast::RealExpr::create(pos, 0.5); + + std::println("\n{}exp_four_fermion on qubits [0,1,2,3] with α = 0.5:{}", + colors::bold_yellow, colors::reset); + circuit four_fermion = + exp_four_fermion(qbit(0), qbit(1), qbit(2), qbit(3), std::move(alpha)); + + // Debug: Print each gate individually + std::println("{}Debug - Individual gates in four_fermion circuit:{}", + colors::bold_red, colors::reset); + int gate_count = 0; + for (const auto &gate : four_fermion) { + if (gate) { + std::cout << " Gate " << gate_count++ << ": " << *gate; + } + } + + std::println("\n{}Full circuit output:{}", colors::bold_green, colors::reset); + std::cout << colors::green << four_fermion << colors::reset << std::endl; + + // Test with different qubits + auto alpha2 = tools_v1::ast::RealExpr::create(pos, 1.2); + std::println("\n{}exp_four_fermion on qubits [4,5,6,7] with α = 1.2:{}", + colors::bold_yellow, colors::reset); + circuit four_fermion2 = + exp_four_fermion(qbit(4), qbit(5), qbit(6), qbit(7), std::move(alpha2)); + std::cout << colors::blue << four_fermion2 << colors::reset << std::endl; +} + +// Test function 3: Circuit analysis +void test_circuit_analysis() { + std::println("\n{}=== TEST 3: Circuit Analysis ==={}", colors::bold_cyan, + colors::reset); + + tools_v1::parser::Position pos; + + // Test exp_c_dag_c gate count + std::println("\n{}exp_c_dag_c analysis:{}", colors::bold_yellow, + colors::reset); + auto alpha1 = tools_v1::ast::RealExpr::create(pos, 1.0); + circuit c_dag_c = exp_c_dag_c(qbit(0), std::move(alpha1)); + std::println("{}Number of gates: {}{}", colors::green, c_dag_c.size(), + colors::reset); + std::println("{}Expected: 1 (single Rz gate){}", colors::blue, colors::reset); + if (c_dag_c.size() == 1) { + std::println("{}✓ Gate count matches expected{}", colors::bold_green, + colors::reset); + } else { + std::println("{}✗ Gate count mismatch{}", colors::bold_red, colors::reset); + } + + // Test exp_four_fermion gate count + std::println("\n{}exp_four_fermion analysis:{}", colors::bold_yellow, + colors::reset); + auto alpha2 = tools_v1::ast::RealExpr::create(pos, 1.0); + circuit four_fermion = + exp_four_fermion(qbit(0), qbit(1), qbit(2), qbit(3), std::move(alpha2)); + std::println("{}Number of gates: {}{}", colors::green, four_fermion.size(), + colors::reset); + std::println( + "{}Expected: 13 (3 X + 6 CNOT + 2 H + 1 MultiControlGate + 1 Rz){}", + colors::blue, colors::reset); + if (four_fermion.size() == 13) { + std::println("{}✓ Gate count matches expected{}", colors::bold_green, + colors::reset); + } else { + std::println("{}✗ Gate count mismatch{}", colors::bold_red, colors::reset); + } + + // Show first few gates + std::println("\n{}First 5 gates:{}", colors::magenta, colors::reset); + int count = 0; + for (const auto &gate : four_fermion) { + if (count >= 5) + break; + if (gate) { + std::cout << " " << *gate << std::endl; + count++; + } + } + if (four_fermion.size() > 5) { + std::println("{}... and {} more gates{}", colors::cyan, + four_fermion.size() - 5, colors::reset); + } +} + +// Test function 4: exp_kinetic functionality +void test_exp_kinetic() { + std::println("\n{}=== TEST 4: exp_kinetic Functionality ==={}", + colors::bold_cyan, colors::reset); + + tools_v1::parser::Position pos; + + // Test with multiple qubits + std::println("\n{}exp_kinetic on qubits [0,1,2] with α = 0.5:{}", + colors::bold_yellow, colors::reset); + auto alpha = tools_v1::ast::RealExpr::create(pos, 0.5); + std::vector qubits = {qbit(0), qbit(1), qbit(2)}; + circuit kinetic = exp_kinetic(qubits, std::move(alpha)); + + std::println("{}Number of gates: {}{}", colors::green, kinetic.size(), + colors::reset); + std::println("{}Expected: 3 (one Rz gate per qubit){}", colors::blue, + colors::reset); + if (kinetic.size() == 3) { + std::println("{}✓ Gate count matches expected{}", colors::bold_green, + colors::reset); + } else { + std::println("{}✗ Gate count mismatch{}", colors::bold_red, colors::reset); + } + + std::cout << colors::green << kinetic << colors::reset << std::endl; + + // Test with single qubit + std::println("\n{}exp_kinetic on single qubit [5] with α = 1.2:{}", + colors::bold_yellow, colors::reset); + auto alpha2 = tools_v1::ast::RealExpr::create(pos, 1.2); + std::vector single_qubit = {qbit(5)}; + circuit kinetic_single = exp_kinetic(single_qubit, std::move(alpha2)); + + std::println("{}Number of gates: {}{}", colors::green, kinetic_single.size(), + colors::reset); + std::println("{}Expected: 1 (single Rz gate){}", colors::blue, colors::reset); + if (kinetic_single.size() == 1) { + std::println("{}✓ Gate count matches expected{}", colors::bold_green, + colors::reset); + } else { + std::println("{}✗ Gate count mismatch{}", colors::bold_red, colors::reset); + } + + std::cout << colors::blue << kinetic_single << colors::reset << std::endl; +} + +// Test function 5: exp_interaction functionality +void test_exp_interaction() { + std::println("\n{}=== TEST 5: exp_interaction Functionality ==={}", + colors::bold_cyan, colors::reset); + + tools_v1::parser::Position pos; + + // Test with multiple pairings + std::println("\n{}exp_interaction with two pairings and α = 0.3:{}", + colors::bold_yellow, colors::reset); + auto alpha = tools_v1::ast::RealExpr::create(pos, 0.3); + std::vector> pairings = { + {qbit(0), qbit(1), qbit(2), qbit(3)}, + {qbit(4), qbit(5), qbit(6), qbit(7)}}; + circuit interaction = exp_interaction(pairings, std::move(alpha)); + + std::println("{}Number of gates: {}{}", colors::green, interaction.size(), + colors::reset); + std::println("{}Expected: 30 (15 gates per four_fermion * 2 pairings){}", + colors::blue, colors::reset); + if (interaction.size() == 30) { + std::println("{}✓ Gate count matches expected{}", colors::bold_green, + colors::reset); + } else { + std::println("{}✗ Gate count mismatch{}", colors::bold_red, colors::reset); + } + + // Show first 10 gates to avoid overwhelming output + std::println("\n{}First 10 gates:{}", colors::magenta, colors::reset); + int count = 0; + for (const auto &gate : interaction) { + if (count >= 10) + break; + if (gate) { + std::cout << " " << *gate << std::endl; + count++; + } + } + if (interaction.size() > 10) { + std::println("{}... and {} more gates{}", colors::cyan, + interaction.size() - 10, colors::reset); + } + + // Test with single pairing + std::println("\n{}exp_interaction with single pairing and α = 0.8:{}", + colors::bold_yellow, colors::reset); + auto alpha2 = tools_v1::ast::RealExpr::create(pos, 0.8); + std::vector> single_pairing = { + {qbit(8), qbit(9), qbit(10), qbit(11)}}; + circuit interaction_single = + exp_interaction(single_pairing, std::move(alpha2)); + + std::println("{}Number of gates: {}{}", colors::green, + interaction_single.size(), colors::reset); + std::println("{}Expected: 15 (15 gates per four_fermion){}", colors::blue, + colors::reset); + if (interaction_single.size() == 15) { + std::println("{}✓ Gate count matches expected{}", colors::bold_green, + colors::reset); + } else { + std::println("{}✗ Gate count mismatch{}", colors::bold_red, colors::reset); + } + + std::cout << colors::blue << interaction_single << colors::reset << std::endl; +} + +// Test function 6: Combined circuits +void test_combined_circuits() { + std::println("\n{}=== TEST 6: Combined Circuits ==={}", colors::bold_cyan, + colors::reset); + + tools_v1::parser::Position pos; + + std::println("\n{}Combined circuit with exp_kinetic and exp_interaction:{}", + colors::bold_yellow, colors::reset); + + circuit combined; + + // Add exp_kinetic + auto alpha1 = tools_v1::ast::RealExpr::create(pos, 0.7); + std::vector kinetic_qubits = {qbit(0), qbit(1)}; + circuit kinetic = exp_kinetic(kinetic_qubits, std::move(alpha1)); + for (const auto &gate : kinetic) { + if (gate) { + combined.push_back(tools_v1::ast::object::clone(*gate)); + } + } + + // Add exp_interaction + auto alpha2 = tools_v1::ast::RealExpr::create(pos, 0.3); + std::vector> interaction_pairings = { + {qbit(2), qbit(3), qbit(4), qbit(5)}}; + circuit interaction = + exp_interaction(interaction_pairings, std::move(alpha2)); + for (const auto &gate : interaction) { + if (gate) { + combined.push_back(tools_v1::ast::object::clone(*gate)); + } + } + + std::println("{}Total gates in combined circuit: {}{}", colors::green, + combined.size(), colors::reset); + std::println("{}Expected: 17 (2 from kinetic + 15 from interaction){}", + colors::blue, colors::reset); + if (combined.size() == 17) { + std::println("{}✓ Gate count matches expected{}", colors::bold_green, + colors::reset); + } else { + std::println("{}✗ Gate count mismatch{}", colors::bold_red, colors::reset); + } + + std::cout << colors::cyan << combined << colors::reset << std::endl; +} + +// Test function 7: Time evolution of ground state +void test_time_evolution_ground_state() { + std::println("\n{}=== TEST 7: Time Evolution of Ground State ==={}", + colors::bold_cyan, colors::reset); + + tools_v1::parser::Position pos; + + // Create L=6 lattice configuration + const int L = 6; + square_hubbard_config HC(L, 1.0, 0.0); + int L_int = static_cast(L); + + std::println("\n{}L = {} lattice configuration:{}", colors::bold_yellow, L, + colors::reset); + + // Create ground state preparation (similar to GS.cpp) + double mu = -1.5; + circuit ground_state; + std::set selected_qubits; + + for (int x = -L_int / 2 + 1; x <= L_int / 2; ++x) { + for (int y = -L_int / 2 + 1; y <= L_int / 2; ++y) { + if (HC.e_bare(x, y) <= mu) { + int n = HC.encoding_formula(x, y); + selected_qubits.insert(n); + qbit q(n); + ground_state.push_back(pauli_string({q.x()})); + } + } + } + + std::print("{}Ground state selected qubits: {}", colors::green, + colors::reset); + for (auto &x : selected_qubits) + std::print("{}{}{} ", colors::bold_blue, x, colors::reset); + std::println(""); + + std::println("{}Ground state preparation circuit: {}", colors::bold_yellow, + colors::reset); + std::cout << colors::green << ground_state << colors::reset << std::endl; + + // Create kinetic term acting on all lattice sites + std::println("\n{}Kinetic term acting on all lattice sites:{}", + colors::bold_yellow, colors::reset); + + std::vector all_qubits; + for (int x = -L_int / 2 + 1; x <= L_int / 2; ++x) { + for (int y = -L_int / 2 + 1; y <= L_int / 2; ++y) { + int n = HC.encoding_formula(x, y); + all_qubits.emplace_back(n); + } + } + + auto kinetic_alpha = tools_v1::ast::RealExpr::create(pos, 0.1); + circuit kinetic_term = exp_kinetic(all_qubits, std::move(kinetic_alpha)); + + std::println("{}Kinetic term gates: {}{}", colors::green, kinetic_term.size(), + colors::reset); + std::println("{}Expected: {} (one Rz gate per site){}", colors::blue, + all_qubits.size(), colors::reset); + + // Show first 5 gates of kinetic term + std::println("\n{}First 5 gates of kinetic term:{}", colors::magenta, + colors::reset); + int count = 0; + for (const auto &gate : kinetic_term) { + if (count >= 5) + break; + if (gate) { + std::cout << " " << *gate << std::endl; + count++; + } + } + if (kinetic_term.size() > 5) { + std::println("{}... and {} more gates{}", colors::cyan, + kinetic_term.size() - 5, colors::reset); + } + + // Create interaction terms for ALL possible quadruples + std::println("\n{}Generating ALL possible interaction quadruples:{}", + colors::bold_yellow, colors::reset); + + std::vector> interaction_pairings; + + // Get all lattice site encodings + std::vector all_encodings; + for (int x = -L_int / 2 + 1; x <= L_int / 2; ++x) { + for (int y = -L_int / 2 + 1; y <= L_int / 2; ++y) { + all_encodings.push_back(HC.encoding_formula(x, y)); + } + } + + std::println("{}Total lattice sites: {}{}", colors::green, + all_encodings.size(), colors::reset); + + // Generate all possible quadruples (a,b,c,d) with a < b and c < d + // This avoids duplicates from permutations + int quadruple_count = 0; + for (size_t i = 0; i < all_encodings.size(); ++i) { + for (size_t j = i + 1; j < all_encodings.size(); ++j) { + for (size_t k = 0; k < all_encodings.size(); ++k) { + for (size_t l = k + 1; l < all_encodings.size(); ++l) { + // Ensure all four sites are distinct + if (i != k && i != l && j != k && j != l) { + interaction_pairings.push_back( + {qbit(all_encodings[i]), qbit(all_encodings[j]), + qbit(all_encodings[k]), qbit(all_encodings[l])}); + quadruple_count++; + } + } + } + } + } + + std::println("{}Generated {} unique quadruples{}", colors::blue, + quadruple_count, colors::reset); + + auto interaction_alpha = tools_v1::ast::RealExpr::create(pos, 0.05); + circuit interaction_term = + exp_interaction(interaction_pairings, std::move(interaction_alpha)); + + std::println("{}Interaction term gates: {}{}", colors::green, + interaction_term.size(), colors::reset); + std::println("{}Expected: {} (15 gates per quadruple * {} quadruples){}", + colors::blue, 15 * interaction_pairings.size(), + interaction_pairings.size(), colors::reset); + + // Combine ground state + kinetic + interaction for time evolution + std::println("\n{}Complete time evolution circuit:{}", colors::bold_yellow, + colors::reset); + + circuit time_evolution; + + // Add ground state preparation + for (const auto &gate : ground_state) { + if (gate) { + time_evolution.push_back(tools_v1::ast::object::clone(*gate)); + } + } + + // Add kinetic term + for (const auto &gate : kinetic_term) { + if (gate) { + time_evolution.push_back(tools_v1::ast::object::clone(*gate)); + } + } + + // Add interaction term + for (const auto &gate : interaction_term) { + if (gate) { + time_evolution.push_back(tools_v1::ast::object::clone(*gate)); + } + } + + std::println("{}Total gates in time evolution circuit: {}{}", colors::green, + time_evolution.size(), colors::reset); + std::println( + "{}Expected: {} (ground state) + {} (kinetic) + {} (interaction){}", + colors::blue, ground_state.size(), kinetic_term.size(), + interaction_term.size(), colors::reset); + + // Show summary of the complete circuit + std::println("\n{}Time evolution circuit summary:{}", colors::bold_magenta, + colors::reset); + std::println(" {}Ground state preparation: {} gates{}", colors::green, + ground_state.size(), colors::reset); + std::println(" {}Kinetic term: {} gates{}", colors::blue, + kinetic_term.size(), colors::reset); + std::println(" {}Interaction term: {} gates{}", colors::cyan, + interaction_term.size(), colors::reset); + std::println(" {}Total: {} gates{}", colors::bold_yellow, + time_evolution.size(), colors::reset); +} + +// Test function 8: Detailed lattice analysis for L=6 +void test_lattice_analysis_L6() { + std::println("\n{}=== TEST 8: L=6 Lattice Analysis ==={}", colors::bold_cyan, + colors::reset); + + const int L = 6; + square_hubbard_config HC(L, 1.0, 0.0); + int L_int = static_cast(L); + + std::println("\n{}L = {} lattice encoding and energies:{}", + colors::bold_yellow, L, colors::reset); + + // Print encoding grid + for (int r = L_int / 2; r >= -L_int / 2 + 1; --r) { + for (int c = -L_int / 2 + 1; c <= L_int / 2; ++c) { + int encoding = HC.encoding_formula(c, r); + double energy = HC.e_bare(c, r); + std::print("{}[{:2d}:{:6.3f}]{} ", colors::cyan, encoding, energy, + colors::reset); + } + std::println(""); + } + + // Show which sites would be selected for ground state + double mu = -1.5; + std::println("\n{}Ground state selection for μ = {:.1f}:{}", + colors::bold_yellow, mu, colors::reset); + + for (int r = L_int / 2; r >= -L_int / 2 + 1; --r) { + for (int c = -L_int / 2 + 1; c <= L_int / 2; ++c) { + double energy = HC.e_bare(c, r); + if (energy <= mu) { + std::print("{}[{:2d}:{:6.3f}]{} ", colors::bold_green, + HC.encoding_formula(c, r), energy, colors::reset); + } else { + std::print("{}[{:2d}:{:6.3f}]{} ", colors::bold_red, HC.encoding_formula(c, r), + energy, colors::reset); + } + } + std::println(""); + } + + // Count total number of possible interaction quadruples + std::println("\n{}Interaction quadruple analysis:{}", colors::bold_yellow, + colors::reset); + + int total_sites = L_int * L_int; + std::println("{}Total lattice sites: {}{}", colors::green, total_sites, + colors::reset); + + // For a real Hubbard model, the number of interaction terms is huge + // (all combinations of 4 distinct sites) + long long possible_quadruples = total_sites * (total_sites - 1) * + (total_sites - 2) * (total_sites - 3) / 24; + std::println("{}Possible interaction quadruples: {}{}", colors::blue, + possible_quadruples, colors::reset); + std::println( + "{}Note: This is the total number of possible 4-site interactions{}", + colors::magenta, colors::reset); +} + +// Test function 9: Write complete time evolution circuit to file +void test_write_time_evolution_to_file() { + std::println("\n{}=== TEST 9: Writing Time Evolution to File ==={}", + colors::bold_cyan, colors::reset); + + tools_v1::parser::Position pos; + + // Create L=6 lattice configuration + const int L = 6; + square_hubbard_config HC(L, 1.0, 0.4); + int L_int = static_cast(L); + + std::println( + "\n{}Creating complete time evolution circuit for L={} lattice{}", + colors::bold_yellow, L, colors::reset); + + // Create ground state preparation + double mu = -1.5; + circuit ground_state; + std::set selected_qubits; + + for (int x = -L_int / 2 + 1; x <= L_int / 2; ++x) { + for (int y = -L_int / 2 + 1; y <= L_int / 2; ++y) { + if (HC.e_bare(x, y) <= mu) { + int n = HC.encoding_formula(x, y); + selected_qubits.insert(n); + qbit q(n); + ground_state.push_back(pauli_string({q.x()})); + } + } + } + + // Create kinetic term acting on all lattice sites + std::vector all_qubits; + for (int x = -L_int / 2 + 1; x <= L_int / 2; ++x) { + for (int y = -L_int / 2 + 1; y <= L_int / 2; ++y) { + int n = HC.encoding_formula(x, y); + all_qubits.emplace_back(n); + } + } + + auto kinetic_alpha = tools_v1::ast::RealExpr::create(pos, 0.1); + circuit kinetic_term = exp_kinetic(all_qubits, std::move(kinetic_alpha)); + + // Create interaction terms for ALL possible quadruples + std::vector> interaction_pairings; + + // Get all lattice site encodings + std::vector all_encodings; + for (int x = -L_int / 2 + 1; x <= L_int / 2; ++x) { + for (int y = -L_int / 2 + 1; y <= L_int / 2; ++y) { + all_encodings.push_back(HC.encoding_formula(x, y)); + } + } + + // Generate all possible quadruples (a,b,c,d) with a < b and c < d + int quadruple_count = 0; + for (size_t i = 0; i < all_encodings.size(); ++i) { + for (size_t j = i + 1; j < all_encodings.size(); ++j) { + for (size_t k = 0; k < all_encodings.size(); ++k) { + for (size_t l = k + 1; l < all_encodings.size(); ++l) { + // Ensure all four sites are distinct + if (i != k && i != l && j != k && j != l) { + interaction_pairings.push_back( + {qbit(all_encodings[i]), qbit(all_encodings[j]), + qbit(all_encodings[k]), qbit(all_encodings[l])}); + quadruple_count++; + } + } + } + } + } + + auto interaction_alpha = tools_v1::ast::RealExpr::create(pos, 0.05); + circuit interaction_term = + exp_interaction(interaction_pairings, std::move(interaction_alpha)); + + // Combine all components for time evolution + circuit time_evolution; + + // Add ground state preparation + for (const auto &gate : ground_state) { + if (gate) { + time_evolution.push_back(tools_v1::ast::object::clone(*gate)); + } + } + + // Add kinetic term + for (const auto &gate : kinetic_term) { + if (gate) { + time_evolution.push_back(tools_v1::ast::object::clone(*gate)); + } + } + + // Add interaction term + for (const auto &gate : interaction_term) { + if (gate) { + time_evolution.push_back(tools_v1::ast::object::clone(*gate)); + } + } + + std::println("{}Circuit statistics:{}", colors::bold_magenta, colors::reset); + std::println(" {}Ground state preparation: {} gates{}", colors::green, + ground_state.size(), colors::reset); + std::println(" {}Kinetic term: {} gates{}", colors::blue, + kinetic_term.size(), colors::reset); + std::println(" {}Interaction term: {} gates ({} quadruples){}", colors::cyan, + interaction_term.size(), quadruple_count, colors::reset); + std::println(" {}Total: {} gates{}", colors::bold_yellow, + time_evolution.size(), colors::reset); + + // Write circuit to file + std::string filename = "time_evolution.qasm"; + std::ofstream file(filename); + if (file.is_open()) { + file << time_evolution; + file.close(); + std::println("\n{}✓ Successfully wrote circuit to {}{}", colors::bold_green, + filename, colors::reset); + std::println("{}File size: {} gates{}", colors::blue, time_evolution.size(), + colors::reset); + } else { + std::println("\n{}✗ Failed to open file {}{}", colors::bold_red, filename, + colors::reset); + } +} + +int main() { + std::println("{}Running TrotterHam1.cpp Tests{}", colors::bold_cyan, + colors::reset); + std::println("{}=========================={}", colors::bold_cyan, + colors::reset); + + // Run all tests + test_exp_c_dag_c(); + test_exp_four_fermion(); + test_circuit_analysis(); + test_exp_kinetic(); + test_exp_interaction(); + test_combined_circuits(); + test_time_evolution_ground_state(); + test_lattice_analysis_L6(); + test_write_time_evolution_to_file(); + + return 0; +} diff --git a/experimental/src/gate_builder.cpp b/experimental/src/gate_builder.cpp new file mode 100644 index 00000000..7be2dc8d --- /dev/null +++ b/experimental/src/gate_builder.cpp @@ -0,0 +1,181 @@ +#include "tools_v1/ast/gate_builder.hpp" +#include "tools_v1/ast/expr.hpp" +#include "tools_v1/ast/stmt.hpp" +#include "tools_v1/ast/control_gate.hpp" +#include + +namespace tools_v1::ast { + +namespace PrimitiveGate { + PauliType pauli_from_string(const std::string& str) { + if (str == "X" || str == "x") return PauliType::X; + if (str == "Y" || str == "y") return PauliType::Y; + if (str == "Z" || str == "z") return PauliType::Z; + if (str == "I" || str == "i") return PauliType::I; + throw std::invalid_argument("Invalid Pauli string: " + str); + } +} + +// Helper method implementations +template +ptr GateBuilder::build_pauli_string() { + if (qubits.size() != paulis.size()) { + throw std::runtime_error("PauliString requires equal number of qubits and Pauli operators"); + } + return PauliString::create(pos, std::move(qubits), std::move(paulis)); +} + +template +ptr GateBuilder::build_control_gate() { + if (qubits.size() != 1) { + throw std::runtime_error("ControlGate requires exactly one control qubit"); + } + if (!target_gate) { + throw std::runtime_error("ControlGate requires a target gate"); + } + return ControlGate::create(pos, std::move(qubits[0]), std::move(target_gate)); +} + +template +ptr GateBuilder::build_exp_pauli() { + if (qubits.size() != paulis.size()) { + throw std::runtime_error("ExpPauli requires equal number of qubits and Pauli operators"); + } + if (!angle) { + throw std::runtime_error("ExpPauli requires an angle expression"); + } + return ExpPauli::create(pos, std::move(angle), std::move(qubits), std::move(paulis)); +} + +template +GateBuilder& GateBuilder::operator+=(PrimitiveGate::Type gate_type) { + reset(); + current_type = gate_type; + return *this; +} + +template +GateBuilder& GateBuilder::operator,(const VarAccess& qubit) { + qubits.push_back(qubit); + return *this; +} + +template +GateBuilder& GateBuilder::operator,(const std::string& qubit_name) { + // This is a placeholder - in real implementation, we'd need to parse "q", 1 format + // For now, we'll assume VarAccess objects are passed directly + throw std::runtime_error("String qubit parsing not yet implemented"); + return *this; +} + +template +GateBuilder& GateBuilder::operator,(PauliType pauli) { + paulis.push_back(pauli); + return *this; +} + +template +GateBuilder& GateBuilder::operator,(double angle_value) { + angle = RealExpr::create(pos, angle_value); + return *this; +} + +template +GateBuilder& GateBuilder::operator,(ptr expr) { + angle = std::move(expr); + return *this; +} + +template +GateBuilder& GateBuilder::operator,(PrimitiveGate::Type nested_gate_type) { + // For nested gates like ControlGate, we need to build the target gate first + // This is a complex case that would require more sophisticated state management + throw std::runtime_error("Nested gate building not yet implemented"); + return *this; +} + +template +T GateBuilder::submit() { + ptr built_gate; + + switch (current_type) { + case PrimitiveGate::PAULI_STRING: + built_gate = build_pauli_string(); + break; + case PrimitiveGate::CONTROL: + built_gate = build_control_gate(); + break; + case PrimitiveGate::EXP_PAULI: + built_gate = build_exp_pauli(); + break; + default: + throw std::runtime_error("Gate type not yet implemented"); + } + + reset(); + + // For single gate builder, return the gate + if constexpr (std::is_same_v>) { + return built_gate; + } + // For vector builder, this will be handled in specialization + + return T{}; +} + +template +void GateBuilder::reset() { + qubits.clear(); + paulis.clear(); + target_gate.reset(); + angle.reset(); +} + +// GateVectorBuilder specialization +GateVectorBuilder& GateVectorBuilder::operator+=(PrimitiveGate::Type gate_type) { + GateBuilder>>::operator+=(gate_type); + return *this; +} + +GateVectorBuilder& GateVectorBuilder::operator,(PrimitiveGate::Type next_gate_type) { + // Submit current gate and start new one + auto gate = GateBuilder>>::submit(); + if (!gate.empty()) { + // Move all gates from the returned vector into our gates vector + for (auto& g : gate) { + if (g) { + gates.push_back(std::move(g)); + } + } + } + return operator+=(next_gate_type); +} + +std::vector> GateVectorBuilder::submit() { + // Submit final gate + auto gate = GateBuilder>>::submit(); + if (!gate.empty()) { + // Move all gates from the returned vector into our gates vector + for (auto& g : gate) { + if (g) { + gates.push_back(std::move(g)); + } + } + } + return std::move(gates); +} + +// Convenience functions +GateVectorBuilder gates() { + return GateVectorBuilder{}; +} + +GateBuilder> gate() { + return GateBuilder>{}; +} + +// Explicit template instantiation +template class GateBuilder>; +template class GateBuilder>>; + +} // namespace tools_v1::ast diff --git a/experimental/src/hubbard/builders.cpp b/experimental/src/hubbard/builders.cpp new file mode 100644 index 00000000..f9423e7e --- /dev/null +++ b/experimental/src/hubbard/builders.cpp @@ -0,0 +1,68 @@ +#include + +#include + +namespace hubbard { + +using tools_v1::ast::VarAccess; +using tools_v1::tools::circuit; +using tools_v1::tools::qbit; + +tools_v1::tools::circuit build_creation(int idx, BuildContext &ctx) { + circuit c; + qbit ancilla = ctx.anc_mem.generate_ancilla("creation"); + + std::vector cnot_qargs; + cnot_qargs.emplace_back(ctx.data[idx].to_va()); + cnot_qargs.emplace_back(ancilla.to_va()); + + auto xg1 = tools_v1::ast::DeclaredGate::create(ctx.pos, "x", {}, + {ctx.data[idx].to_va()}); + auto cnt = tools_v1::ast::DeclaredGate::create(ctx.pos, "cx", {}, + std::move(cnot_qargs)); + auto xg2 = tools_v1::ast::DeclaredGate::create(ctx.pos, "x", {}, + {ctx.data[idx].to_va()}); + + c.push_back(std::move(xg1)); + c.push_back(std::move(cnt)); + c.push_back(std::move(xg2)); + + for (int i = 0; i < idx; ++i) { + auto z_gate = tools_v1::ast::DeclaredGate::create(ctx.pos, "z", {}, + {ctx.data[i].to_va()}); + c.push_back(std::move(z_gate)); + } + + auto x3 = tools_v1::ast::DeclaredGate::create(ctx.pos, "x", {}, + {ctx.data[idx].to_va()}); + c.push_back(std::move(x3)); + + return c; +} + +tools_v1::tools::circuit build_annihilation(int idx, BuildContext &ctx) { + circuit c; + qbit ancilla = ctx.anc_mem.generate_ancilla("annihilation"); + + std::vector cnot_qargs; + cnot_qargs.emplace_back(ctx.data[idx].to_va()); + cnot_qargs.emplace_back(ancilla.to_va()); + + auto cnt = tools_v1::ast::DeclaredGate::create(ctx.pos, "cx", {}, + std::move(cnot_qargs)); + c.push_back(std::move(cnt)); + + for (int i = 0; i < idx; ++i) { + auto z_gate = tools_v1::ast::DeclaredGate::create(ctx.pos, "z", {}, + {ctx.data[i].to_va()}); + c.push_back(std::move(z_gate)); + } + + auto x_gate = tools_v1::ast::DeclaredGate::create(ctx.pos, "x", {}, + {ctx.data[idx].to_va()}); + c.push_back(std::move(x_gate)); + + return c; +} + +} // namespace hubbard diff --git a/experimental/src/hubbard/layout.cpp b/experimental/src/hubbard/layout.cpp new file mode 100644 index 00000000..d7642d56 --- /dev/null +++ b/experimental/src/hubbard/layout.cpp @@ -0,0 +1,52 @@ +#include + +#include +#include + +namespace hubbard { + +Layout::Layout(ModelParams params) + : params_(std::move(params)), config_(params_.L, params_.t, params_.U), + index_to_coord_(config_.decoding_vector().size()) { + for (const auto &entry : config_.decoding_vector()) { + int idx = entry.first; + if (idx < 0 || idx >= static_cast(index_to_coord_.size())) { + continue; + } + index_to_coord_[static_cast(idx)] = entry.second; + coord_to_index_[entry.second] = idx; + } +} + +const ModelParams &Layout::params() const { return params_; } + +const square_hubbard_config &Layout::config() const { return config_; } + +square_hubbard_config &Layout::config() { return config_; } + +int Layout::num_data_qubits() const { return params_.num_fermions(); } + +std::vector Layout::data_register(const std::string &name) const { + std::vector data(static_cast(num_data_qubits())); + for (int i = 0; i < num_data_qubits(); ++i) { + data[static_cast(i)] = qbit(name, i); + } + return data; +} + +std::pair Layout::n_to_nx_ny(int n) const { + if (n < 0 || n >= static_cast(index_to_coord_.size())) { + throw std::out_of_range("n_to_nx_ny: index out of range"); + } + return index_to_coord_[static_cast(n)]; +} + +int Layout::nx_ny_to_n(int nx, int ny) const { + auto it = coord_to_index_.find({nx, ny}); + if (it == coord_to_index_.end()) { + throw std::out_of_range("nx_ny_to_n: coordinate not found"); + } + return it->second; +} + +} // namespace hubbard diff --git a/experimental/src/hubbard/model_params.cpp b/experimental/src/hubbard/model_params.cpp new file mode 100644 index 00000000..cb1a22bc --- /dev/null +++ b/experimental/src/hubbard/model_params.cpp @@ -0,0 +1,18 @@ +#include + +namespace hubbard { + +ModelParams::ModelParams(unsigned ell_value, double t_value, double U_value, + double E0_value, std::complex z_value) + : ell(ell_value), L(1u << ell_value), t(t_value), U(U_value), E0(E0_value), + z(z_value) {} + +ModelParams ModelParams::real_space_defaults() { + return ModelParams(7u, 1.0, 4.0, 3.0, {3.0, 4.0}); +} + +int ModelParams::num_fermions() const { + return 2 * static_cast(L) * static_cast(L); +} + +} // namespace hubbard diff --git a/experimental/src/hubbard/operators.cpp b/experimental/src/hubbard/operators.cpp new file mode 100644 index 00000000..a543ab14 --- /dev/null +++ b/experimental/src/hubbard/operators.cpp @@ -0,0 +1,411 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace hubbard { + +using tools_v1::ast::DeclaredGate; +using tools_v1::ast::VarAccess; +using tools_v1::tools::circuit; +using tools_v1::tools::qbit; + +namespace { + +circuit make_identity_circuit(std::span data) { + circuit circ; + if (data.empty()) { + return circ; + } + auto gate = DeclaredGate::create(tools_v1::parser::Position(), "id", {}, + {data[0].to_va()}); + circ.push_back(std::move(gate)); + return circ; +} + +} // namespace + +tools_v1::tools::circuit +build_lcu_A(square_hubbard_config &config, double t, std::span data, tools_v1::tools::ANC_MEM &anc_mem, int num_fermions, unsigned ell) { + double total_energy = 0.0; + std::vector coeffs; + coeffs.reserve(num_fermions); + auto sites = config.decoding_vector(); + + for (const auto &[n, coords] : sites) { + int nx = coords.first; + int ny = coords.second; + double en = config.e_bare(nx, ny) + 4.1 * t; + total_energy += 2 * en; + coeffs.push_back(en); + coeffs.push_back(en); + } + + std::transform(coeffs.begin(), coeffs.end(), coeffs.begin(), [&total_energy](double en) { return en / total_energy; }); + + std::vector ancilla_lcu_A; + ancilla_lcu_A.reserve(2 * ell + 1); + for (unsigned i = 0; i < 2 * ell + 1; ++i) { + ancilla_lcu_A.emplace_back(anc_mem.generate_ancilla("lcu_A")); + } + + std::vector unitaries_lcu_A; + unitaries_lcu_A.reserve(num_fermions); + for (int i = 0; i < num_fermions; ++i) { + qbit ancilla_number_op = anc_mem.generate_ancilla("number_op"); + + std::vector cx_qarg; + cx_qarg.push_back(data[i].to_va()); + cx_qarg.push_back(ancilla_number_op.to_va()); + auto x1 = + DeclaredGate::create(tools_v1::parser::Position(), "x", {}, + {data[i].to_va()}); + auto cx = DeclaredGate::create(tools_v1::parser::Position(), "cx", {}, + std::move(cx_qarg)); + auto x2 = + DeclaredGate::create(tools_v1::parser::Position(), "x", {}, + {data[i].to_va()}); + + circuit c; + c.push_back(std::move(x1)); + c.push_back(std::move(cx)); + c.push_back(std::move(x2)); + c.save_ancilla(ancilla_number_op); + + unitaries_lcu_A.push_back(std::move(c)); + } + + return tools_v1::algorithm::LCU(coeffs, ancilla_lcu_A, unitaries_lcu_A); +} + +tools_v1::tools::circuit +build_lcu_A_real(square_hubbard_config &config, double t, std::span data, tools_v1::tools::ANC_MEM &anc_mem) { + std::vector unitaries_lcu_A; + + int Lmin = config.Lmin(); + int Lmax = config.Lmax(); + + auto create_dg = [](circuit &c, const std::string &name, std::vector &&qargs) { + auto g = DeclaredGate::create(tools_v1::parser::Position(), name, {}, std::move(qargs)); + c.push_back(std::move(g)); + }; + + for (int nx = Lmin; nx <= Lmax; ++nx) { + for (int ny = Lmin; ny <= Lmax; ++ny) { + for (unsigned int sg = 0; sg < 2; ++sg) { + circuit c_ija; + + Ferm_Occ_Idx fi{nx, ny, sg}; + Ferm_Occ_Idx fj{(nx < Lmax ? nx + 1 : Lmin), + (ny < Lmax ? ny + 1 : Lmin), sg}; + + const int i = config.index_from_occupation(fi); + const int j = config.index_from_occupation(fj); + + qbit ancilla_number_op = anc_mem.generate_ancilla("number_op"); + c_ija.save_ancilla(ancilla_number_op); + + create_dg(c_ija, "cx", {data[i].to_va(), data[j].to_va()}); + create_dg(c_ija, "x", {data[j].to_va()}); + create_dg(c_ija, "cx", {data[i].to_va(), ancilla_number_op.to_va()}); + create_dg(c_ija, "x", {data[j].to_va()}); + create_dg(c_ija, "cx", {data[i].to_va(), data[j].to_va()}); + + int MIN = std::min(i, j); + int MAX = std::max(i, j); + create_dg(c_ija, "x", {data[MIN].to_va()}); + for (int k = MIN + 1; k < MAX; ++k) { + create_dg(c_ija, "z", {data[k].to_va()}); + } + create_dg(c_ija, "x", {data[MAX].to_va()}); + unitaries_lcu_A.push_back(std::move(c_ija)); + } + } + } + + return tools_v1::algorithm::LCU(unitaries_lcu_A, anc_mem); +} + +tools_v1::tools::circuit build_B(square_hubbard_config &config, + std::span data, + tools_v1::tools::ANC_MEM &anc_mem, + int num_fermions) { + std::set> ancilla_cdag_cdag_c_c; + auto create_ancilla_cccc = [&]() { + qbit anc = anc_mem.generate_ancilla("cccc"); + ancilla_cdag_cdag_c_c.insert(std::make_unique(anc)); + return std::make_unique(anc); + }; + + std::vector unitaries_lcu_B; + + auto create_cnot_prep = [&data](circuit &c, const qbit &qc, + const qbit &qt) { + tools_v1::parser::Position pos; + std::vector v; + v.push_back(qc.to_va()); + v.push_back(qt.to_va()); + auto cnot = + DeclaredGate::create(pos, "cx", std::vector>{}, std::move(v)); + c.push_back(std::move(cnot)); + }; + + auto create_xgate_prep = [&data](circuit &c, const qbit &qc) { + tools_v1::parser::Position pos; + std::vector v; + v.push_back(qc.to_va()); + auto x = DeclaredGate::create(pos, "x", {}, std::move(v)); + c.push_back(std::move(x)); + }; + + auto create_zgate_prep = [&data](circuit &c, const qbit &qc) { + tools_v1::parser::Position pos; + std::vector v; + v.push_back(qc.to_va()); + auto z = DeclaredGate::create(pos, "z", {}, std::move(v)); + c.push_back(std::move(z)); + }; + + auto create_xz_tower = [&data, &create_xgate_prep, &create_zgate_prep]( + circuit &c, int idx) { + for (int i = 0; i < idx; ++i) { + create_zgate_prep(c, data[i]); + } + create_xgate_prep(c, data[idx]); + }; + + for (int i = 0; i < num_fermions; i += 2) { + for (int j = 1; j < num_fermions; j += 2) { + for (int k = 1; k < num_fermions; k += 2) { + circuit c_ijk; + + Ferm_Occ_Idx f0 = config.occupation_index(i); + Ferm_Occ_Idx f1 = config.occupation_index(j); + Ferm_Occ_Idx f2 = config.occupation_index(k); + + int nx3 = config.brillouin_zone_normalize(f0.nx + f1.nx - f2.nx); + int ny3 = config.brillouin_zone_normalize(f0.ny + f1.ny - f2.ny); + Ferm_Occ_Idx f3{nx3, ny3, 0}; + + int x0 = i; + int x1 = j; + int x2 = k; + int x3 = config.index_from_occupation(f3); + + if (x0 == x3 && x1 == x2) { + auto ancilla_op0 = create_ancilla_cccc(); + create_cnot_prep(c_ijk, data[x1], data[x0]); + create_cnot_prep(c_ijk, data[x0], *ancilla_op0); + create_cnot_prep(c_ijk, data[x1], data[x0]); + } else if (x0 != x3 && x1 != x2) { + auto ancilla_op0 = create_ancilla_cccc(); + auto ancilla_op1 = create_ancilla_cccc(); + auto ancilla_op2 = create_ancilla_cccc(); + + create_cnot_prep(c_ijk, data[x1], data[x0]); + create_cnot_prep(c_ijk, data[x0], *ancilla_op0); + create_cnot_prep(c_ijk, data[x1], data[x0]); + create_cnot_prep(c_ijk, data[x2], data[x0]); + create_xgate_prep(c_ijk, data[x0]); + create_cnot_prep(c_ijk, data[x0], *ancilla_op1); + create_xgate_prep(c_ijk, data[x0]); + create_cnot_prep(c_ijk, data[x2], data[x0]); + create_cnot_prep(c_ijk, data[x3], data[x0]); + create_xgate_prep(c_ijk, data[x0]); + create_cnot_prep(c_ijk, data[x0], *ancilla_op2); + create_xgate_prep(c_ijk, data[x0]); + create_cnot_prep(c_ijk, data[x3], data[x0]); + create_xz_tower(c_ijk, x3); + create_xz_tower(c_ijk, x2); + create_xz_tower(c_ijk, x1); + create_xz_tower(c_ijk, x0); + } else { + std::stringstream ss; + ss << "momentum conservation issue"; + throw ss.str(); + } + unitaries_lcu_B.emplace_back(std::move(c_ijk)); + } + } + } + + circuit lcu_3 = tools_v1::algorithm::LCU(unitaries_lcu_B, anc_mem); + for (const auto &anc : ancilla_cdag_cdag_c_c) { + if (anc) { + lcu_3.save_ancilla(*anc); + } + } + return lcu_3; +} + +tools_v1::tools::circuit build_B_real(square_hubbard_config &config, + std::span data, + tools_v1::tools::ANC_MEM &anc_mem) { + std::vector unitaries_lcu_B; + + int Lmin = config.Lmin(); + int Lmax = config.Lmax(); + + auto create_dg = [](circuit &c, const std::string &name, + std::vector &&qargs) { + auto g = DeclaredGate::create(tools_v1::parser::Position(), name, {}, + std::move(qargs)); + c.push_back(std::move(g)); + }; + + for (int nx = Lmin; nx <= Lmax; ++nx) { + for (int ny = Lmin; ny <= Lmax; ++ny) { + circuit c_ia; + + Ferm_Occ_Idx fi{nx, ny, 0}; + + const int i = config.index_from_occupation(fi); + const int j = i + 1; + + qbit ancilla_op_0 = anc_mem.generate_ancilla("number_op"); + qbit ancilla_op_1 = anc_mem.generate_ancilla("number_op"); + c_ia.save_ancilla(ancilla_op_0); + c_ia.save_ancilla(ancilla_op_1); + + create_dg(c_ia, "cx", + {data[i].to_va(), ancilla_op_0.to_va()}); + create_dg(c_ia, "cx", + {data[j].to_va(), ancilla_op_1.to_va()}); + + unitaries_lcu_B.push_back(std::move(c_ia)); + } + } + +circuit lcu_2 = tools_v1::algorithm::LCU(unitaries_lcu_B, anc_mem); + + return lcu_2; +} + +tools_v1::tools::circuit build_ziEA(square_hubbard_config &config, + double t, std::span data, + tools_v1::tools::ANC_MEM &anc_mem, + int num_fermions, unsigned ell, + double E0, std::complex z) { + const std::complex imag{0.0, 1.0}; + circuit id_circuit = make_identity_circuit(data); + auto lcu_1 = + build_lcu_A(config, t, data, anc_mem, num_fermions, ell); + return build_lcu_two_unitaries(z + E0 + imag, -1.0, + std::move(id_circuit), std::move(lcu_1), + anc_mem, "lcu_2"); +} + +tools_v1::tools::circuit build_ziEA_real(square_hubbard_config &config, + double t, std::span data, + tools_v1::tools::ANC_MEM &anc_mem, + double E0, std::complex z) { + const std::complex imag{0.0, 1.0}; + circuit id_circuit = make_identity_circuit(data); + auto lcu_1 = build_lcu_A_real(config, t, data, anc_mem); + return build_lcu_two_unitaries(z + E0 + imag, -t, + std::move(id_circuit), std::move(lcu_1), + anc_mem, "lcu_2"); +} + +tools_v1::tools::circuit build_ziEA_inverse(square_hubbard_config &config, + double t, + std::span data, + tools_v1::tools::ANC_MEM &anc_mem, + int num_fermions, unsigned ell, + double E0, + std::complex z) { + auto lcu = build_ziEA(config, t, data, anc_mem, num_fermions, ell, + E0, z); + qbit ancilla_qsvt_1 = anc_mem.generate_ancilla("qsvt_1"); + std::vector phi = {0.1777, 0.2777, 0.3777, 0.4777, 0.5777}; + return tools_v1::algorithm::QSVT(phi, lcu, ancilla_qsvt_1); +} + +tools_v1::tools::circuit +build_ziEA_inverse_real(square_hubbard_config &config, double t, std::span data, tools_v1::tools::ANC_MEM &anc_mem, double E0, std::complex z) { + auto lcu = build_ziEA_real(config, t, data, anc_mem, E0, z); + qbit ancilla_qsvt_1 = anc_mem.generate_ancilla("qsvt_1"); + std::vector phi = {0.1777, 0.2777, 0.3777, 0.4777, 0.5777}; + return tools_v1::algorithm::QSVT(phi, lcu, ancilla_qsvt_1); +} + +tools_v1::tools::circuit build_iUB(square_hubbard_config &config, std::span data, tools_v1::tools::ANC_MEM &anc_mem, int num_fermions) { + const std::complex imag{0.0, 1.0}; + circuit id_circuit = make_identity_circuit(data); + auto lcu_1 = build_B(config, data, anc_mem, num_fermions); + return build_lcu_two_unitaries(imag, -1.0, std::move(id_circuit), std::move(lcu_1), anc_mem, "lcu_iUB"); +} + +tools_v1::tools::circuit build_iUB_real(square_hubbard_config &config, + std::span data, + tools_v1::tools::ANC_MEM &anc_mem) { + const std::complex imag{0.0, 1.0}; + circuit id_circuit = make_identity_circuit(data); + auto lcu_1 = build_B_real(config, data, anc_mem); + return build_lcu_two_unitaries(imag, -1.0, std::move(id_circuit), + std::move(lcu_1), anc_mem, "lcu_iUB"); +} + +tools_v1::tools::circuit build_I_ziEA_inv_iUB( + std::span data, circuit ziEA_inv, circuit iUB, + tools_v1::tools::ANC_MEM &anc_mem) { + const std::complex c0 = 1.0; + const std::complex c1 = 1.0; + circuit combined = + combine_circuits(std::move(ziEA_inv), std::move(iUB)); + circuit id_circuit = make_identity_circuit(data); + return build_lcu_two_unitaries(c0, c1, std::move(id_circuit), + std::move(combined), anc_mem, "lcu_5"); +} + +tools_v1::tools::circuit build_AinvB_inverse( + circuit input, tools_v1::tools::ANC_MEM &anc_mem) { + qbit ancilla_qsvt_2 = anc_mem.generate_ancilla("qsvt_2"); + std::vector phi2 = {0.1888, 0.2888, 0.3888, 0.4888, 0.5888}; + return tools_v1::algorithm::QSVT(phi2, input, ancilla_qsvt_2); +} + +tools_v1::tools::circuit build_observable( + int creation_index, int annihilation_index, circuit AinvB_inv, + circuit ziEA_inv, BuildContext &ctx) { + std::vector circuits; + circuits.emplace_back(build_creation(creation_index, ctx)); + circuits.emplace_back(std::move(AinvB_inv)); + circuits.emplace_back(std::move(ziEA_inv)); + circuits.emplace_back(build_annihilation(annihilation_index, ctx)); + return tools_v1::algorithm::circuit_combine(std::move(circuits)); +} + +tools_v1::tools::circuit build_lcu_two_unitaries( + const std::complex &c0, const std::complex &c1, + circuit id_circuit, circuit target_circuit, + tools_v1::tools::ANC_MEM &anc_mem, const std::string &ancilla_label) { + qbit ancilla = anc_mem.generate_ancilla(ancilla_label); + return tools_v1::algorithm::LCU_two_unitaries( + c0, c1, id_circuit, target_circuit, ancilla); +} + +tools_v1::tools::circuit combine_circuits(circuit lhs, circuit rhs) { + std::vector circuits; + circuits.emplace_back(std::move(lhs)); + circuits.emplace_back(std::move(rhs)); + tools_v1::tools::circuit out; + for (auto &u : circuits) { + for (const auto &gate : u) { + out.push_back(tools_v1::ast::object::clone(*gate)); + } + for (auto it = u.ancilla_begin(); it != u.ancilla_end(); ++it) { + out.save_ancilla(**it); + } + } + return out; +} + +} // namespace hubbard diff --git a/experimental/src/hubbard/program_io.cpp b/experimental/src/hubbard/program_io.cpp new file mode 100644 index 00000000..ffb5c921 --- /dev/null +++ b/experimental/src/hubbard/program_io.cpp @@ -0,0 +1,82 @@ +#include + +#include +#include +#include +#include + +namespace hubbard { + +using tools_v1::ast::Program; +using tools_v1::ast::RegisterDecl; +using tools_v1::tools::circuit; + +void push_gate_builder(tools_v1::ast::ptr &prog, + tools_v1::ast::GateBuilder &gb) { + prog->body().splice(prog->end(), gb.submit_list()); +} + +void push_circuit(tools_v1::ast::ptr &prog, circuit &c) { + prog->body().splice(prog->end(), c.body_list()); +} + +void materialize_registers(Program &prog, + const tools_v1::tools::ANC_MEM &anc_mem, + const std::string &data_reg_name, + int num_data_qubits) { + tools_v1::parser::Position pos; + + auto ®isters = + const_cast(anc_mem).registers(); + for (const auto &[name, idx] : registers) { + auto reg = RegisterDecl::create(pos, name, true, idx + 1); + prog.body().push_back(std::move(reg)); + } + + auto data_reg = + RegisterDecl::create(pos, data_reg_name, true, num_data_qubits); + prog.body().push_back(std::move(data_reg)); +} + +void save_program(const Program &prog, const std::string &path) { + std::ofstream os(path); + prog.pretty_print(os); +} + +void save_qasm(const std::string &path, std::string_view contents) { + std::ofstream os(path); + os.write(contents.data(), + static_cast(contents.size())); +} + +QasmArtifacts qasmify_program(Program &prog) { + tools_v1::ast::QASMify to_qasm; + prog.accept(to_qasm); + + std::ostringstream oss; + to_qasm.pretty_print(oss); + + QasmArtifacts artifacts; + artifacts.code = oss.str(); + auto &qasm_prog = to_qasm.prog(); + artifacts.program = std::move(qasm_prog); + return artifacts; +} + +std::map +estimate_resources(const Program &prog, + std::optional rotation_precision) { + tools_v1::tools::ResourceEstimator::config cfg; + if (rotation_precision) { + cfg.rotation_precision = rotation_precision; + } + auto counts = + tools_v1::tools::estimate_resources(const_cast(prog), cfg); + std::map result; + for (const auto &[key, value] : counts) { + result.emplace(key, value); + } + return result; +} + +} // namespace hubbard diff --git a/experimental/src/main.cpp b/experimental/src/main.cpp new file mode 100644 index 00000000..c4102186 --- /dev/null +++ b/experimental/src/main.cpp @@ -0,0 +1,100 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +qasmtools::ast::ptr create_sample() { + using namespace qasmtools::ast; + + qasmtools::parser::Position pos; + std::list> body; + auto qreg = RegisterDecl::create(pos, "q", true, 16); + + auto q0 = VarAccess(pos, "q", 0); + auto q1 = VarAccess(pos, "q", 1); + auto dg = DeclaredGate::create(pos, "h", {}, {std::move(q0)}); + body.push_back(std::move(dg)); + + q0 = VarAccess(pos, "q", 0); + q1 = VarAccess(pos, "q", 1); + auto cx = CNOTGate::create(pos, std::move(q0), std::move(q1)); + + body.push_back(std::move(cx)); + + return Program::create(pos, true, std::move(body), 0, 16); +} + +std::list> +multicontrolgate(std::vector control0, + std::vector control1, + std::vector gates) { + using namespace qasmtools::ast; + + std::list> body; + + // defn goes here + + return body; +} + +std::list> +grover_rudolph(std::vector vals, + std::vector qubits) { + using namespace qasmtools::ast; + std::list> body; + // implementation goes here + return body; +} + +std::list> +uniform_linear_combination(std::vector qubits) { + using namespace qasmtools::ast; + qasmtools::parser::Position pos; + std::list> body; + + for (auto q : qubits) { + auto h = DeclaredGate::create(pos, "h", {}, {std::move(q)}); + body.push_back(std::move(h)); + } + + return body; +} + +// NOTES: +// 1. +// uniform_linear_combination implements the creation of a state +// ket(0^m) -> (ket(0...01) + ket(0..10) + ... + ket(1..11) ) / 2^(m/2) +// this is part of the prepare subroutine in the linear combination of +// unitaries. This will be suitable only for a uniformly weighted sum +// TODO: OK for proof of concept; should be generalized later. + +void run1() { + // example 1 + auto program = create_sample(); + qasmtools::parser::Position pos; + auto qubits = std::vector(); + qubits.reserve(16); + for (int i = 0; i < 16; ++i) + qubits.emplace_back(pos, "q", i); + auto new_gates = uniform_linear_combination(qubits); + program->body().splice(program->end(), new_gates); + std::cout << *program << std::endl; +} + +// void run2() { +// using namespace qasmtools; +// using namespace ast; +// auto p = qasmtools::parser::Position(); +// auto q = VarAccess(p, "q", 0); +// auto g = CNOTGate(p, VarAccess(p, "q", 1), VarAccess(p, "q", 2)); +// SimpleControl simple_control_gate(q, g); +// } + +int main() { + run1(); + return 0; +} diff --git a/experimental/src/test_qasmify.cpp b/experimental/src/test_qasmify.cpp new file mode 100644 index 00000000..ae50050e --- /dev/null +++ b/experimental/src/test_qasmify.cpp @@ -0,0 +1,13 @@ +#include +#include +#include + +int main() +{ + std::ifstream fin("observable.qasm"); + tools_v1::ast::QASMify to_qasm; + tools_v1::ast::ptr p = tools_v1::parser::parse_stream(fin); + std::cout << p << std::endl; + p->accept(to_qasm); + return 0; +} diff --git a/src/tools/CMakeLists.txt b/src/tools/CMakeLists.txt index a62af69b..db9c7c2d 100644 --- a/src/tools/CMakeLists.txt +++ b/src/tools/CMakeLists.txt @@ -4,22 +4,43 @@ add_custom_target(tools COMMENT "Build all binary tools.") include(${CMAKE_SOURCE_DIR}/cmake/grid_synth.cmake) +set(RESOURCE_ESTIMATOR_HUBBARD_SOURCES + ${CMAKE_SOURCE_DIR}/experimental/src/hubbard/builders.cpp + ${CMAKE_SOURCE_DIR}/experimental/src/hubbard/layout.cpp + ${CMAKE_SOURCE_DIR}/experimental/src/hubbard/model_params.cpp + ${CMAKE_SOURCE_DIR}/experimental/src/hubbard/operators.cpp + ${CMAKE_SOURCE_DIR}/experimental/src/hubbard/program_io.cpp) + foreach(file ${FILENAMES}) get_filename_component(basename ${file} NAME_WE) + if(NOT ${BUILD_GRID_SYNTH} AND ("${basename}" STREQUAL "grid_synth" + OR "${basename}" STREQUAL "qasm_synth")) + continue() + endif() + + set(target_sources ${file}) + if("${basename}" STREQUAL "resource_estimator") + list(APPEND target_sources ${RESOURCE_ESTIMATOR_HUBBARD_SOURCES}) + endif() + + add_executable("staq_${basename}" ${target_sources}) + if(${BUILD_GRID_SYNTH}) - add_executable("staq_${basename}" ${file}) if(MSVC) target_link_libraries(staq_${basename} PUBLIC PkgConfig::gmp PkgConfig::gmpxx) else() target_link_libraries(staq_${basename} PUBLIC gmp gmpxx) endif() - else() - if(${basename} STREQUAL "grid_synth" OR ${basename} STREQUAL "qasm_synth") - continue() - endif() - add_executable("staq_${basename}" ${file}) endif() + + if("${basename}" STREQUAL "resource_estimator") + target_include_directories(staq_${basename} + PRIVATE ${CMAKE_SOURCE_DIR}/experimental/include) + set_property(TARGET staq_${basename} PROPERTY CXX_STANDARD 20) + set_property(TARGET staq_${basename} PROPERTY CXX_STANDARD_REQUIRED ON) + endif() + target_link_libraries(staq_${basename} PUBLIC libstaq) add_dependencies(tools staq_${basename}) install(TARGETS staq_${basename} DESTINATION ${CMAKE_INSTALL_BINDIR}) diff --git a/src/tools/resource_estimator.cpp b/src/tools/resource_estimator.cpp index 2e1fee2f..21b7c528 100644 --- a/src/tools/resource_estimator.cpp +++ b/src/tools/resource_estimator.cpp @@ -24,12 +24,145 @@ * SOFTWARE. */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + #include #include "qasmtools/parser/parser.hpp" #include "staq/tools/resource_estimator.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +struct HubbardCliOptions { + unsigned ell = 7; + double t = 1.0; + double U = 4.0; + double E0 = 3.0; + double z_real = 3.0; + double z_imag = 4.0; + std::string output_dir = "."; + std::string observable_name = "observable.qasm"; + std::string qasm_name = "qasmify.qasm"; + bool use_real_space = true; + std::string layout_preset = "square"; + std::optional precision; +}; + +unsigned deduce_ell_from_L(unsigned L) { + if (L == 0 || (L & (L - 1)) != 0) { + throw std::invalid_argument("L must be a positive power of two"); + } + unsigned ell = 0; + while ((1u << ell) < L) { + ++ell; + } + return ell; +} + +std::string to_lower_copy(std::string value) { + std::transform( + value.begin(), value.end(), value.begin(), + [](unsigned char c) { return static_cast(std::tolower(c)); }); + return value; +} + +int run_hubbard_resource_estimator(const HubbardCliOptions& opts) { + if (to_lower_copy(opts.layout_preset) != "square") { + std::cerr << "Unsupported layout preset: " << opts.layout_preset + << std::endl; + return 1; + } + + hubbard::ModelParams params(opts.ell, opts.t, opts.U, opts.E0, + {opts.z_real, opts.z_imag}); + hubbard::Layout layout(params); + const unsigned ell = params.ell; + const double t = params.t; + const int num_fermions = layout.num_data_qubits(); + square_hubbard_config& hubbard_config = layout.config(); + const std::complex z = params.z; + const double E0 = params.E0; + + tools_v1::parser::Position pos; + std::list> body; + auto prog = + tools_v1::ast::Program::create(pos, true, std::move(body), 0, 0); + + auto data = layout.data_register("q"); + + tools_v1::tools::ANC_MEM anc_mem; + hubbard::BuildContext build_ctx{pos, data, anc_mem}; + + tools_v1::tools::circuit observable; + if (opts.use_real_space) { + auto ziEA_inv_real_for_combo = hubbard::build_ziEA_inverse_real(hubbard_config, t, data, anc_mem, E0, z); + auto iUB_real = hubbard::build_iUB_real(hubbard_config, data, anc_mem); + auto I_plus_real = hubbard::build_I_ziEA_inv_iUB(data, std::move(ziEA_inv_real_for_combo), std::move(iUB_real), anc_mem); + auto AinvB_inv_real = hubbard::build_AinvB_inverse(std::move(I_plus_real), anc_mem); + auto ziEA_inv_real = hubbard::build_ziEA_inverse_real(hubbard_config, t, data, anc_mem, E0, z); + observable = hubbard::build_observable(2, 3, std::move(AinvB_inv_real), std::move(ziEA_inv_real), build_ctx); + } else { + auto ziEA_inv_for_combo = hubbard::build_ziEA_inverse(hubbard_config, t, data, anc_mem, num_fermions, ell, E0, z); + auto iUB = hubbard::build_iUB(hubbard_config, data, anc_mem, num_fermions); + auto I_plus = hubbard::build_I_ziEA_inv_iUB(data, std::move(ziEA_inv_for_combo), std::move(iUB), anc_mem); + auto AinvB_inv = hubbard::build_AinvB_inverse(std::move(I_plus), anc_mem); + auto ziEA_inv = hubbard::build_ziEA_inverse(hubbard_config, t, data, anc_mem, num_fermions, ell, E0, z); + observable = hubbard::build_observable(2, 3, std::move(AinvB_inv), std::move(ziEA_inv), build_ctx); + } + + hubbard::materialize_registers(*prog, anc_mem, "q", num_fermions); + + hubbard::push_circuit(prog, observable); + + std::filesystem::path out_dir(opts.output_dir); + if (!opts.output_dir.empty()) { + std::error_code ec; + std::filesystem::create_directories(out_dir, ec); + if (ec) { + std::cerr << "Failed to create output directory '" + << opts.output_dir << "': " << ec.message() << std::endl; + return 1; + } + } + + auto observable_path = (out_dir / opts.observable_name).string(); + auto qasm_path = (out_dir / opts.qasm_name).string(); + auto qasm_artifacts = hubbard::qasmify_program(*prog); + if (qasm_artifacts.program) { + auto resources = hubbard::estimate_resources(*qasm_artifacts.program, opts.precision); + for (const auto& [name, value] : resources) { + std::cout << name << " :: " << value << std::endl; + } + } + + return 0; +} + +} // namespace + int main(int argc, char** argv) { using namespace staq; using namespace qasmtools; @@ -37,6 +170,11 @@ int main(int argc, char** argv) { bool unbox_qelib = false; bool box_gates = false; bool no_merge_dagger = false; + bool hubbard = false; + HubbardCliOptions hubbard_opts; + std::optional hubbard_L_override; + std::string hubbard_mode = + hubbard_opts.use_real_space ? "real" : "momentum"; CLI::App app{"QASM resource estimator"}; @@ -47,21 +185,62 @@ int main(int argc, char** argv) { app.add_flag("--no-merge-dagger", no_merge_dagger, "Counts gates and their inverses separately"); + app.add_flag("--hubbard", hubbard, "Hubbard Model Resource Estimator"); + app.add_option("--ell", hubbard_opts.ell, + "Log2 of the lattice size for the Hubbard estimator"); + app.add_option("--L", hubbard_L_override, + "Lattice size (must be a positive power of two)") + ->check(CLI::PositiveNumber); + app.add_option("--t", hubbard_opts.t, "Hopping amplitude t"); + app.add_option("--U", hubbard_opts.U, "On-site interaction strength U"); + app.add_option("--E0", hubbard_opts.E0, "Ground state energy offset E0"); + app.add_option("--ReZ,--z-real", hubbard_opts.z_real, + "Real part of the spectral shift z"); + app.add_option("--ImZ,--z-imag", hubbard_opts.z_imag, + "Imaginary part of the spectral shift z"); + app.add_option("--output-dir", hubbard_opts.output_dir, + "Directory used when emitting Hubbard artifacts"); + app.add_option("--observable-name,--observable-output", + hubbard_opts.observable_name, + "Filename used when saving the observable program"); + app.add_option("--qasm-name,--qasm-output", hubbard_opts.qasm_name, + "Filename used when saving the synthesized QASM"); + app.add_option("--mode", hubbard_mode, "Hubbard mode: 'real' or 'momentum'") + ->check(CLI::IsMember({"real", "momentum"}, CLI::ignore_case)); + app.add_option("--layout", hubbard_opts.layout_preset, + "Layout preset (only 'square' supported)"); + app.add_option("--prec", hubbard_opts.precision, + "Optional rotation precision used by the Hubbard estimator"); + CLI11_PARSE(app, argc, argv); - auto program = parser::parse_stdin(); - if (program) { + if (hubbard) { + if (hubbard_L_override) { + try { + hubbard_opts.ell = deduce_ell_from_L(*hubbard_L_override); + } catch (const std::exception& ex) { + std::cerr << "Invalid --L value: " << ex.what() << std::endl; + return 1; + } + } + hubbard_opts.use_real_space = + (to_lower_copy(hubbard_mode) != "momentum"); + return run_hubbard_resource_estimator(hubbard_opts); + } else { + auto program = parser::parse_stdin(); + if (program) { - std::set overrides = - unbox_qelib ? std::set() : ast::qelib_defs; - auto count = tools::estimate_resources( - *program, {!box_gates, !no_merge_dagger, overrides}); + std::set overrides = + unbox_qelib ? std::set() : ast::qelib_defs; + auto count = tools::estimate_resources( + *program, {!box_gates, !no_merge_dagger, overrides}); - std::cout << "Resources used:\n"; - for (auto& [name, num] : count) { - std::cout << " " << name << ": " << num << "\n"; + std::cout << "Resources used:\n"; + for (auto& [name, num] : count) { + std::cout << " " << name << ": " << num << "\n"; + } + } else { + std::cerr << "Parsing failed\n"; } - } else { - std::cerr << "Parsing failed\n"; } } From 496034243da21e88f4bdfb72dcca9686f12b0020 Mon Sep 17 00:00:00 2001 From: Anton Borissov Date: Wed, 19 Nov 2025 11:43:34 -0500 Subject: [PATCH 2/3] fix grid_synth complex constructors --- include/staq/grid_synth/complex.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/staq/grid_synth/complex.hpp b/include/staq/grid_synth/complex.hpp index 19a8d003..b7b6e828 100644 --- a/include/staq/grid_synth/complex.hpp +++ b/include/staq/grid_synth/complex.hpp @@ -45,8 +45,8 @@ class complex { public: // Default constructor sets real and imaginary components to zero - complex() : a_(0), b_(0) {} - complex(T a, T b) : a_(a), b_(b) {} + complex() : a_(0), b_(0) {} + complex(T a, T b) : a_(a), b_(b) {} complex conj() { return complex(a_, -b_); } From e26e13c5508016fa26aae13fa20678065c49ac10 Mon Sep 17 00:00:00 2001 From: Anton Borissov Date: Wed, 19 Nov 2025 12:08:14 -0500 Subject: [PATCH 3/3] include algorithm in gate builder --- experimental/include/tools_v1/ast/gate_builder_simple.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/experimental/include/tools_v1/ast/gate_builder_simple.hpp b/experimental/include/tools_v1/ast/gate_builder_simple.hpp index 041333d9..28ae5345 100644 --- a/experimental/include/tools_v1/ast/gate_builder_simple.hpp +++ b/experimental/include/tools_v1/ast/gate_builder_simple.hpp @@ -9,6 +9,7 @@ #include #include #include +#include namespace tools_v1::ast {