From 94b0eb8588176fcc2b77288684bfc8642bf964d9 Mon Sep 17 00:00:00 2001 From: Sergei Isakov <54642992+sergeisakov@users.noreply.github.com> Date: Mon, 27 Jul 2026 21:54:20 +0200 Subject: [PATCH 01/13] Add classical control. --- .bazelrc | 12 +- CMakeLists.txt | 2 +- Makefile | 4 +- apps/BUILD | 6 +- apps/make.sh | 22 +- apps/qsim_amplitudes.cc | 11 +- apps/qsim_base.cc | 10 +- apps/qsim_base_cuda.cu | 10 +- apps/qsim_base_custatevec.cu | 10 +- apps/qsim_base_custatevecex.cu | 8 +- apps/qsim_qtrajectory_cuda.cu | 11 +- apps/qsim_von_neumann.cc | 10 +- apps/qsimh_amplitudes.cc | 8 +- apps/qsimh_base.cc | 8 +- docs/classical_control.md | 197 ++ docs/qsim.md | 125 ++ docs/qsim_language.md | 304 +++ lib/BUILD | 100 +- lib/channel.h | 4 +- lib/channels_cirq.h | 2 +- lib/circuit.h | 4 +- lib/circuit_qsim_parser.h | 1776 ++++++++++++++--- lib/classical_control_error.h | 111 ++ lib/classical_control_expr.h | 317 +++ lib/classical_control_obs.h | 142 ++ lib/classical_control_parser.h | 907 +++++++++ lib/classical_control_symbol.h | 425 ++++ lib/classical_control_symtab.h | 266 +++ lib/classical_control_tokenizer.h | 364 ++++ lib/classical_control_util.h | 294 +++ lib/error.h | 49 + lib/fuser.h | 7 +- lib/fuser_mqubit.h | 107 +- lib/gate.h | 77 +- lib/gate_appl.h | 57 +- lib/gates_qsim.h | 139 +- lib/operation.h | 80 +- lib/operation_base.h | 2 + lib/run_qsim.h | 602 +++--- lib/run_qsim_deprecated.h | 317 +++ pybind_interface/Makefile | 6 +- pybind_interface/avx2/CMakeLists.txt | 2 +- pybind_interface/avx2/pybind_main_avx2.cpp | 2 +- pybind_interface/avx512/CMakeLists.txt | 2 +- .../avx512/pybind_main_avx512.cpp | 2 +- pybind_interface/basic/CMakeLists.txt | 2 +- pybind_interface/basic/pybind_main_basic.cpp | 2 +- pybind_interface/cuda/CMakeLists.txt | 2 +- pybind_interface/cuda/pybind_main_cuda.cpp | 2 +- pybind_interface/custatevec/CMakeLists.txt | 2 +- .../custatevec/pybind_main_custatevec.cpp | 2 +- pybind_interface/custatevecex/CMakeLists.txt | 2 +- pybind_interface/decide/CMakeLists.txt | 2 +- pybind_interface/hip/CMakeLists.txt | 2 +- pybind_interface/hip/pybind_main_hip.cpp | 2 +- pybind_interface/sse/CMakeLists.txt | 2 +- pybind_interface/sse/pybind_main_sse.cpp | 2 +- qsim/BUILD | 51 + qsim/Makefile | 13 + qsim/examples/factorial.qs | 9 + qsim/examples/noisy.qs | 7 + qsim/examples/readme.md | 23 + qsim/qsim.cc | 336 ++++ tests/BUILD | 197 +- tests/channels_cirq_test.cc | 2 +- tests/circuit_qsim_parser_test.cc | 499 +++-- tests/classical_control_expr_test.cc | 143 ++ tests/classical_control_obs_test.cc | 133 ++ tests/classical_control_parser_test.cc | 338 ++++ tests/classical_control_symbol_test.cc | 262 +++ tests/classical_control_symtab_test.cc | 143 ++ tests/classical_control_tokenizer_test.cc | 162 ++ tests/classical_control_util_test.cc | 131 ++ tests/fuser_basic_test.cc | 45 +- tests/hybrid_testfixture.h | 8 +- tests/make.sh | 100 +- tests/qtrajectory_avx_test.cc | 2 +- tests/qtrajectory_cuda_test.cu | 2 +- tests/qtrajectory_custatevec_test.cu | 2 +- tests/run_custatevecex_test.cu | 15 +- tests/run_qsim_deprecated_test.cc | 245 +++ tests/run_qsim_test.cc | 271 +-- tests/run_qsimh_test.cc | 5 +- tests/statespace_testfixture.h | 16 +- 84 files changed, 8889 insertions(+), 1216 deletions(-) create mode 100644 docs/classical_control.md create mode 100644 docs/qsim.md create mode 100644 docs/qsim_language.md create mode 100644 lib/classical_control_error.h create mode 100644 lib/classical_control_expr.h create mode 100644 lib/classical_control_obs.h create mode 100644 lib/classical_control_parser.h create mode 100644 lib/classical_control_symbol.h create mode 100644 lib/classical_control_symtab.h create mode 100644 lib/classical_control_tokenizer.h create mode 100644 lib/classical_control_util.h create mode 100644 lib/error.h create mode 100644 lib/run_qsim_deprecated.h create mode 100644 qsim/BUILD create mode 100644 qsim/Makefile create mode 100644 qsim/examples/factorial.qs create mode 100644 qsim/examples/noisy.qs create mode 100644 qsim/examples/readme.md create mode 100644 qsim/qsim.cc create mode 100644 tests/classical_control_expr_test.cc create mode 100644 tests/classical_control_obs_test.cc create mode 100644 tests/classical_control_parser_test.cc create mode 100644 tests/classical_control_symbol_test.cc create mode 100644 tests/classical_control_symtab_test.cc create mode 100644 tests/classical_control_tokenizer_test.cc create mode 100644 tests/classical_control_util_test.cc create mode 100644 tests/run_qsim_deprecated_test.cc diff --git a/.bazelrc b/.bazelrc index f2b63fda1..525553b5c 100644 --- a/.bazelrc +++ b/.bazelrc @@ -23,15 +23,15 @@ common --verbose_failures # Windows uses slightly different syntax. common --enable_platform_specific_config -build:linux --copt=-std=c++17 -build:linux --cxxopt=-std=c++17 +build:linux --copt=-std=c++20 +build:linux --cxxopt=-std=c++20 build:linux --per_file_copt="googletest/.*@-Xassembler,-W" -build:macos --copt=-std=c++17 -build:macos --cxxopt=-std=c++17 +build:macos --copt=-std=c++20 +build:macos --cxxopt=-std=c++20 -build:windows --copt=/std:c++17 -build:windows --cxxopt=/std:c++17 +build:windows --copt=/std:c++20 +build:windows --cxxopt=/std:c++20 build --copt=-D_GLIBCXX_USE_CXX11_ABI=1 build --cxxopt=-D_GLIBCXX_USE_CXX11_ABI=1 diff --git a/CMakeLists.txt b/CMakeLists.txt index 24a2a338e..3cefcc67d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -108,7 +108,7 @@ endif() # Additional miscellanous settings. # The following settings mirror what is in our hand-written Makefiles. -set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD 20) set(CMAKE_POSITION_INDEPENDENT_CODE ON) # Print additional useful info. diff --git a/Makefile b/Makefile index b3439b83e..24811df12 100644 --- a/Makefile +++ b/Makefile @@ -38,8 +38,8 @@ CXX ?= g++ NVCC ?= nvcc HIPCC ?= hipcc -BASE_CXXFLAGS := -std=c++17 -BASE_NVCCFLAGS := -std c++17 -Wno-deprecated-gpu-targets +BASE_CXXFLAGS := -std=c++20 +BASE_NVCCFLAGS := -std c++20 -Wno-deprecated-gpu-targets BASE_HIPCCFLAGS := CXXFLAGS := $(BASE_CXXFLAGS) $(CXXFLAGS) diff --git a/apps/BUILD b/apps/BUILD index 76c45e83a..9063eda94 100644 --- a/apps/BUILD +++ b/apps/BUILD @@ -28,7 +28,7 @@ cc_binary( copts = gsframe_copts, data = ["//circuits:circuit_q24"], deps = [ - "//lib:run_qsim_lib", + "//lib:run_qsim_deprecated_lib", ], ) @@ -37,7 +37,7 @@ cc_binary( srcs = ["qsim_von_neumann.cc"], copts = gsframe_copts, deps = [ - "//lib:run_qsim_lib", + "//lib:run_qsim_deprecated_lib", ], ) @@ -47,7 +47,7 @@ cc_binary( copts = gsframe_copts, deps = [ "//lib:bitstring", - "//lib:run_qsim_lib", + "//lib:run_qsim_deprecated_lib", ], ) diff --git a/apps/make.sh b/apps/make.sh index 7ebf00c7b..7dff0f0a6 100755 --- a/apps/make.sh +++ b/apps/make.sh @@ -16,15 +16,15 @@ # This file provides an alternate method for building apps in this directory. # Prefer using the Makefile (e.g. `make -C apps/`) if possible. -g++ -O3 -march=native -fopenmp -o qsim_base.x qsim_base.cc -g++ -O3 -march=native -fopenmp -o qsim_von_neumann.x qsim_von_neumann.cc -g++ -O3 -march=native -fopenmp -o qsim_amplitudes.x qsim_amplitudes.cc -g++ -O3 -march=native -fopenmp -o qsimh_base.x qsimh_base.cc -g++ -O3 -march=native -fopenmp -o qsimh_amplitudes.x qsimh_amplitudes.cc +g++ -O3 -std=c++20 -march=native -fopenmp -o qsim_base.x qsim_base.cc +g++ -O3 -std=c++20 -march=native -fopenmp -o qsim_von_neumann.x qsim_von_neumann.cc +g++ -O3 -std=c++20 -march=native -fopenmp -o qsim_amplitudes.x qsim_amplitudes.cc +g++ -O3 -std=c++20 -march=native -fopenmp -o qsimh_base.x qsimh_base.cc +g++ -O3 -std=c++20 -march=native -fopenmp -o qsimh_amplitudes.x qsimh_amplitudes.cc if command -v nvcc &>/dev/null; then - nvcc -O3 -o qsim_base_cuda.x qsim_base_cuda.cu - nvcc -O3 -o qsim_qtrajectory_cuda.x qsim_qtrajectory_cuda.cu + nvcc -O3 -std=c++20 -o qsim_base_cuda.x qsim_base_cuda.cu + nvcc -O3 -std=c++20 -o qsim_qtrajectory_cuda.x qsim_qtrajectory_cuda.cu if [ -n "$CUQUANTUM_ROOT" ]; then declare -a CUSTATEVECFLAGS @@ -35,12 +35,12 @@ if command -v nvcc &>/dev/null; then "-lcustatevec" "-lcublas" ) - nvcc -O3 "${CUSTATEVECFLAGS[@]}" \ + nvcc -O3 -std=c++20 "${CUSTATEVECFLAGS[@]}" \ -o qsim_base_custatevec.x qsim_base_custatevec.cu - nvcc -O3 "${CUSTATEVECFLAGS[@]}" \ + nvcc -O3 -std=c++20 "${CUSTATEVECFLAGS[@]}" \ -o qsim_base_custatevecex.x qsim_base_custatevecex.cu fi elif command -v hipcc &>/dev/null; then - hipcc -O3 -o qsim_base_hip.x qsim_base_cuda.cu - hipcc -O3 -o qsim_qtrajectory_hip.x qsim_qtrajectory_cuda.cu + hipcc -O3 -std=c++20 -o qsim_base_hip.x qsim_base_cuda.cu + hipcc -O3 -std=c++20 -o qsim_qtrajectory_hip.x qsim_qtrajectory_cuda.cu fi diff --git a/apps/qsim_amplitudes.cc b/apps/qsim_amplitudes.cc index 21461a19a..e5b0c1cb6 100644 --- a/apps/qsim_amplitudes.cc +++ b/apps/qsim_amplitudes.cc @@ -23,11 +23,12 @@ #include "../lib/bitstring.h" #include "../lib/circuit_qsim_parser.h" +#include "../lib/classical_control_util.h" #include "../lib/formux.h" #include "../lib/fuser_mqubit.h" #include "../lib/io_file.h" #include "../lib/operation.h" -#include "../lib/run_qsim.h" +#include "../lib/run_qsim_deprecated.h" #include "../lib/simmux.h" #include "../lib/util.h" #include "../lib/util_cpu.h" @@ -160,12 +161,8 @@ int main(int argc, char* argv[]) { return 1; } - Circuit> circuit; - unsigned maxtime = opt.times.back(); - if (!CircuitQsimParser::FromFile(maxtime, opt.circuit_file, - circuit)) { - return 1; - } + auto cstr = cc::ReadFile(opt.circuit_file); + auto circuit = CircuitQsimParser::Run(cstr, opt.times.back()); if (opt.denormals_are_zeros) { SetFlushToZeroAndDenormalsAreZeros(); diff --git a/apps/qsim_base.cc b/apps/qsim_base.cc index a5a7a0b1b..2f5f7e88f 100644 --- a/apps/qsim_base.cc +++ b/apps/qsim_base.cc @@ -20,11 +20,12 @@ #include #include "../lib/circuit_qsim_parser.h" +#include "../lib/classical_control_util.h" #include "../lib/formux.h" #include "../lib/fuser_mqubit.h" #include "../lib/io_file.h" #include "../lib/operation.h" -#include "../lib/run_qsim.h" +#include "../lib/run_qsim_deprecated.h" #include "../lib/simmux.h" #include "../lib/util_cpu.h" @@ -114,11 +115,8 @@ int main(int argc, char* argv[]) { return 1; } - Circuit> circuit; - if (!CircuitQsimParser::FromFile(opt.maxtime, opt.circuit_file, - circuit)) { - return 1; - } + auto cstr = cc::ReadFile(opt.circuit_file); + auto circuit = CircuitQsimParser::Run(cstr, opt.maxtime); if (opt.denormals_are_zeros) { SetFlushToZeroAndDenormalsAreZeros(); diff --git a/apps/qsim_base_cuda.cu b/apps/qsim_base_cuda.cu index f046055a5..3f262b8ea 100644 --- a/apps/qsim_base_cuda.cu +++ b/apps/qsim_base_cuda.cu @@ -20,11 +20,12 @@ #include #include "../lib/circuit_qsim_parser.h" +#include "../lib/classical_control_util.h" #include "../lib/formux.h" #include "../lib/fuser_mqubit.h" #include "../lib/io_file.h" #include "../lib/operation.h" -#include "../lib/run_qsim.h" +#include "../lib/run_qsim_deprecated.h" #include "../lib/simulator_cuda.h" struct Options { @@ -114,11 +115,8 @@ int main(int argc, char* argv[]) { using fp_type = float; - Circuit> circuit; - if (!CircuitQsimParser::FromFile(opt.maxtime, opt.circuit_file, - circuit)) { - return 1; - } + auto cstr = cc::ReadFile(opt.circuit_file); + auto circuit = CircuitQsimParser::Run(cstr, opt.maxtime); struct Factory { using Simulator = qsim::SimulatorCUDA; diff --git a/apps/qsim_base_custatevec.cu b/apps/qsim_base_custatevec.cu index 9bb2d3580..ed333b9f7 100644 --- a/apps/qsim_base_custatevec.cu +++ b/apps/qsim_base_custatevec.cu @@ -22,11 +22,12 @@ #include #include "../lib/circuit_qsim_parser.h" +#include "../lib/classical_control_util.h" #include "../lib/formux.h" #include "../lib/fuser_mqubit.h" #include "../lib/io_file.h" #include "../lib/operation.h" -#include "../lib/run_qsim.h" +#include "../lib/run_qsim_deprecated.h" #include "../lib/simulator_custatevec.h" #include "../lib/util_custatevec.h" @@ -108,11 +109,8 @@ int main(int argc, char* argv[]) { using fp_type = float; - Circuit> circuit; - if (!CircuitQsimParser::FromFile(opt.maxtime, opt.circuit_file, - circuit)) { - return 1; - } + auto cstr = cc::ReadFile(opt.circuit_file); + auto circuit = CircuitQsimParser::Run(cstr, opt.maxtime); struct Factory { using Simulator = qsim::SimulatorCuStateVec; diff --git a/apps/qsim_base_custatevecex.cu b/apps/qsim_base_custatevecex.cu index c34c39294..853005274 100644 --- a/apps/qsim_base_custatevecex.cu +++ b/apps/qsim_base_custatevecex.cu @@ -20,6 +20,7 @@ #include #include "../lib/circuit_qsim_parser.h" +#include "../lib/classical_control_util.h" #include "../lib/formux.h" #include "../lib/io_file.h" #include "../lib/multiprocess_custatevecex.h" @@ -106,11 +107,8 @@ int main(int argc, char* argv[]) { using fp_type = float; - Circuit> circuit; - if (!CircuitQsimParser::FromFile(opt.maxtime, opt.circuit_file, - circuit)) { - return 1; - } + auto cstr = cc::ReadFile(opt.circuit_file); + auto circuit = CircuitQsimParser::Run(cstr, opt.maxtime); struct Factory { using Simulator = qsim::SimulatorCuStateVecEx; diff --git a/apps/qsim_qtrajectory_cuda.cu b/apps/qsim_qtrajectory_cuda.cu index c839fb384..ae1db84c6 100644 --- a/apps/qsim_qtrajectory_cuda.cu +++ b/apps/qsim_qtrajectory_cuda.cu @@ -25,12 +25,13 @@ #include "../lib/channels_qsim.h" #include "../lib/circuit_qsim_parser.h" +#include "../lib/classical_control_util.h" #include "../lib/expect.h" #include "../lib/fuser_mqubit.h" #include "../lib/io_file.h" #include "../lib/operation.h" #include "../lib/qtrajectory.h" -#include "../lib/run_qsim.h" +#include "../lib/run_qsim_deprecated.h" #include "../lib/simulator_cuda.h" struct Options { @@ -223,12 +224,8 @@ int main(int argc, char* argv[]) { return 1; } - Circuit> circuit; - unsigned maxtime = opt.times.back(); - if (!CircuitQsimParser::FromFile(maxtime, opt.circuit_file, - circuit)) { - return 1; - } + auto cstr = cc::ReadFile(opt.circuit_file); + auto circuit = CircuitQsimParser::Run(cstr, opt.times.back()); if (opt.times.size() == 1 && opt.times[0] == std::numeric_limits::max()) { diff --git a/apps/qsim_von_neumann.cc b/apps/qsim_von_neumann.cc index fed4a04f9..7d5813903 100644 --- a/apps/qsim_von_neumann.cc +++ b/apps/qsim_von_neumann.cc @@ -22,11 +22,12 @@ #include #include "../lib/circuit_qsim_parser.h" +#include "../lib/classical_control_util.h" #include "../lib/formux.h" #include "../lib/fuser_mqubit.h" #include "../lib/io_file.h" #include "../lib/operation.h" -#include "../lib/run_qsim.h" +#include "../lib/run_qsim_deprecated.h" #include "../lib/simmux.h" #include "../lib/util_cpu.h" @@ -99,11 +100,8 @@ int main(int argc, char* argv[]) { return 1; } - Circuit> circuit; - if (!CircuitQsimParser::FromFile(opt.maxtime, opt.circuit_file, - circuit)) { - return 1; - } + auto cstr = cc::ReadFile(opt.circuit_file); + auto circuit = CircuitQsimParser::Run(cstr, opt.maxtime); if (opt.denormals_are_zeros) { SetFlushToZeroAndDenormalsAreZeros(); diff --git a/apps/qsimh_amplitudes.cc b/apps/qsimh_amplitudes.cc index e1983bca4..2aa667a26 100644 --- a/apps/qsimh_amplitudes.cc +++ b/apps/qsimh_amplitudes.cc @@ -23,6 +23,7 @@ #include "../lib/bitstring.h" #include "../lib/circuit_qsim_parser.h" +#include "../lib/classical_control_util.h" #include "../lib/formux.h" #include "../lib/fuser_basic.h" #include "../lib/io_file.h" @@ -174,11 +175,8 @@ int main(int argc, char* argv[]) { return 1; } - Circuit> circuit; - if (!CircuitQsimParser::FromFile(opt.maxtime, opt.circuit_file, - circuit)) { - return 1; - } + auto cstr = cc::ReadFile(opt.circuit_file); + auto circuit = CircuitQsimParser::Run(cstr, opt.maxtime); if (!ValidatePart1(circuit.num_qubits, opt.part1)) { return 1; diff --git a/apps/qsimh_base.cc b/apps/qsimh_base.cc index f4a3ee8c7..b41ce43a1 100644 --- a/apps/qsimh_base.cc +++ b/apps/qsimh_base.cc @@ -23,6 +23,7 @@ #include "../lib/bitstring.h" #include "../lib/circuit_qsim_parser.h" +#include "../lib/classical_control_util.h" #include "../lib/formux.h" #include "../lib/fuser_basic.h" #include "../lib/io_file.h" @@ -136,11 +137,8 @@ int main(int argc, char* argv[]) { return 1; } - Circuit> circuit; - if (!CircuitQsimParser::FromFile(opt.maxtime, opt.circuit_file, - circuit)) { - return 1; - } + auto cstr = cc::ReadFile(opt.circuit_file); + auto circuit = CircuitQsimParser::Run(cstr, opt.maxtime); if (!ValidatePart1(circuit.num_qubits, opt.part1)) { return 1; diff --git a/docs/classical_control.md b/docs/classical_control.md new file mode 100644 index 000000000..19c6c1650 --- /dev/null +++ b/docs/classical_control.md @@ -0,0 +1,197 @@ +# qsim Classical Control + +`qsim` natively supports classical control (feed-forward) in quantum circuits. Subcircuits and conditional gates can be executed dynamically based on boolean expressions that depend on classical variables, constants, and measurement outcomes. Both `while`-style and `do-while`-style loops are supported. In addition, quantum gate angles can be parameterized by arithmetic expressions evaluated at runtime. + +## Variables & Symbols + +`qsim` supports classical constants and variables (symbols) registered in a symbol table. Variables are assignable and updated at runtime. Supported types are: + +* **`int`**: 64-bit signed integer. +* **`float`**: Double-precision floating-point number. +* **`vector`** and **`vector`**: Dynamic arrays of scalars. + +Quantum measurement registers are represented as integers (`int`) and are updated dynamically via measurement operations. + +## Expressions + +Assignment statements, conditions, and gate parameterizations rely on expressions that compute numerical values from literals, classical symbols, and quantum measurement outcomes. There are two types of expressions: + +* **Constant Expressions:** If an expression consists entirely of literal constants or symbols marked as constant, it is evaluated and folded **at compile time**, producing a scalar literal. +* **Runtime Expressions:** If an expression involves mutable variables or measurement results, the parser yields an executable closure evaluated dynamically during execution. + +All constants, variables, and measurement outcomes referenced in expressions must be defined in the symbol table. + +### Types & Implicit Conversions + +`qsim` expressions support three evaluation types: `int`, `float`, and `bool`. Type conversions adhere to the following rules: + +* **`bool` -> `int`:** `false` becomes `0`, `true` becomes `1`. +* **`int` -> `bool`:** `0` becomes `false`, any non-zero value becomes `true`. +* **`int` / `bool` -> `float`:** Implicitly promoted when floating-point operations or operands are present. +* **`float` -> `int` / `bool`:** **Forbidden.** Floating-point values cannot be implicitly narrowed to integers or booleans. + +### Constant Literals + +Integer literals can be specified in decimal (`42`), hexadecimal (`0x2A`), or binary (`0b101010`) format. Floating-point literals must follow standard decimal notation (e.g., `1.23` or `1.2e-3`). + +### Indexing + +Individual elements of vectors and specific bits of measurement registers are accessed using bracket notation: + +```text +vec[index] +m1[bit_index] + +``` + +* **Unindexed Registers:** Unindexed measurement registers evaluate to their full integer bitstring value (e.g., if qubits measure $\vert{}11\rangle$, `m1` evaluates to `3`). +* **Index Types:** Any expression convertible to `int` is a valid index. +* **Bounds Checking:** Occurs at compile time for constant indices and at runtime for dynamic indices. + +### Operators & Precedence + +Operators are listed below in **decreasing order of precedence**: + +| Operator | Type | Description | Type Rules / Restrictions | +| --- | --- | --- | --- | +| `!` | Logical | Unary NOT | Requires operand convertible to `bool`. | +| `-` | Arithmetic | Unary Negation | Preserves `int` or `float`. | +| `~` | Bitwise | Unary Bitwise NOT | Inverts register bits for measurements. | +| `**` | Arithmetic | Power ($a^b$) | Always returns `float`. | +| `*`, `/`, `%` | Arithmetic | Multiplication, Division, Modulo | `%` requires integer operands. | +| `+`, `-` | Arithmetic | Addition, Subtraction | Mixed types promoted (`int` + `float` -> `float`). | +| `<<`, `>>` | Bitwise | Left / Right Shift | Requires integer operands. | +| `<`, `<=`, `>`, `>=` | Comparison | Relational | Yields `bool`. | +| `==`, `!=` | Comparison | Equality | Yields `bool`. | +| `&` | Bitwise | Bitwise AND | Requires integer operands. | +| `^` | Bitwise | Bitwise XOR | Requires integer operands. | +| `|` | Bitwise | Bitwise OR | Requires integer operands. | +| `&&` | Logical | Logical AND | Requires operands convertible to `bool`. | +| `^^` | Logical | Logical XOR | Requires operands convertible to `bool` (low-precedence version of `!=`). | +| `||` | Logical | Logical OR | Requires operands convertible to `bool`. | + +Parentheses (`(` and `)`) can be used to override default operator precedence, e.g., `(2 + 3) * 4`. + +### Parser Stop Rules + +The expression parser consumes tokens continuously until operators are exhausted. For example, in the string `"2 * i 3 * j"`, a single parser call parses `2 * i` and stops before token `3`. + +### Integer Expression Parser + +A specialized parser variant handles integer and boolean expressions exclusively. It throws an error if it encounters a floating-point number or the power operator (`**`). + +### Expression Examples + +```text +2 * 3 + 4 +0x0F & 0b1010 +3.14159 * 2.0 + +m0[0] ^ m1[1] +(v[i] + 1) % 8 + +2.0 ** theta +phase + 3.14159 / 2.0 + +(m0 != 0) && (m1[0] == 1) + +``` + +## C++ API Overview + +`qsim` provides a unified C++20 API for quantum circuit execution with classical control. It supports dynamic feed-forward control flow, measurement-driven gate parameterization, runtime symbol assignments, and trajectory filtering. + +### Classical Control Structures + +Classical control flow in `qsim` consists of six primary construct types: + +**1. Conditional Execution (`if / elsif / else`)** + +Branches execution based on conditional boolean expressions evaluated at runtime: + +```text +if condition1 + # Executed if condition1 is true +elsif condition2 + # Executed if condition1 is false and condition2 is true +else + # Executed if all preceding conditions are false +end + +``` + +**2. Pre-Condition Loop (`repeat`)** + +Repeats the inner block while the loop condition evaluates to true (evaluated before each iteration): + +```text +repeat condition + # Block executed while condition is true +end + +``` + +**3. Post-Condition Loop (`do ... while`)** + +Executes the inner block at least once, evaluating the loop condition at the end of each iteration: + +```text +do + # Block executed at least once +while condition + +``` + +**4. Runtime Symbol Assignment (`assign`)** + +Mutates classical variables or vector elements during execution based on evaluated expressions. Supports scalar, vector, and indexed vector assignments. + +**5. Quantum Trajectory Filtering (`discard`)** + +Immediately halts and aborts the current quantum trajectory simulation if the specified condition evaluates to true. + +**6. Debug Output (`println`)** + +Prints formatted strings or evaluated classical expression values to `stdout` during circuit execution. + +### C++ API Architecture & Types + +The API abstracts circuit execution into variant-based operations and parameterized containers: + +```text + ┌───────────────────────────────┐ + │ Circuit │ + └───────────────┬───────────────┘ + │ ops + ▼ + ┌───────────────────────────────┐ + │ Operation │ + └───────────────┬───────────────┘ + │ (variant) + ┌────────────────┬─────────────┼───────────────────┬────────────────┐ + ▼ ▼ ▼ ▼ ▼ +┌───────┐ ┌──────────────┐ ┌─────────────┐ ┌────────────────────┐ ┌─────────┐ +│ Gate │ │ControlledGate│ │ Measurement │ │RuntimeResolvedGate │ │ CCO │ +└───────┘ └──────────────┘ └─────────────┘ └────────────────────┘ └─────────┘ + +``` + +*(Note: `CCO` stands for `ClassicallyControlledOperation`.)* + +### Key API Components + +`FP` below represents the floating-point precision type (typically `float`, or `double` depending on simulator configuration). + +1. **`Circuit>`**: Top-level container representing a quantum program. Holds `num_qubits` and an ordered sequence of `Operation` instances. +2. **`Operation`**: `std::variant` type representing any executable circuit node: +* **`Gate`**: Standard fixed-matrix quantum gate (e.g., $H$, $X$, $CZ$). +* **`ControlledGate`**: Quantum gate controlled by specified control qubits (`controlled_by`). +* **`RuntimeResolvedGate`**: Dynamic gate whose parameters (`param_exprs`) depend on classical symbols or measurements, re-evaluated immediately prior to gate execution. +* **`Measurement`**: Measures a set of qubits at a given time step and writes results to the symbol table under `id`. +* **`Channel`**: Quantum noise channel modeled by a collection of `KrausOperator` objects. +* **`ClassicallyControlledOperation`**: Encapsulates classical control flow constructs, nested operations, and variable updates. +3. **`Symbol`**: Polymorphic container holding a single symbol. +4. **`SymbolTable`**: Scoped symbol table that holds measurement outcomes, constants, and variables. +5. **`Expr`**: Variant type holding compile-time constants or runtime-evaluated expressions. +6. **`ExprParser`**: Expression parser that parses an input string and returns an `Expr` instance. +7. **`QSimRunner`**: Main driver class executing quantum circuits (both clean and noisy) with optional classical control flow. \ No newline at end of file diff --git a/docs/qsim.md b/docs/qsim.md new file mode 100644 index 000000000..61c2543c4 --- /dev/null +++ b/docs/qsim.md @@ -0,0 +1,125 @@ +# `qsim` Application Reference + +The `qsim` application is a command-line simulator for quantum circuits with native support for classical control flow, noisy quantum channels, and parallel repetition sampling. The application source code is located in the `qsim/qsim/` directory. + +## Compilation + +To compile the `qsim` executable using `make`: + +```bash +cd qsim +make + +``` + +Alternatively, to build using Bazel: + +```bash +cd qsim +bazel build qsim && cp -p ../bazel-bin/qsim/qsim . && chmod 755 qsim + +``` + +## Command-Line Interface + +### Syntax + +```bash +./qsim -c [-s ] [-0 ] [-1 ] \ + [-w ] [-t ] \ + [-f ] [-v ] [-z] + +``` + +### Options & Flags + +| Flag | Argument | Description | Default | +| --- | --- | --- | --- | +| **`-c`** | `circuit_file` | Path to the quantum circuit input file. **(Required)** | `""` | +| **`-s`** | `"symbol_defs"` | Space- or semicolon-delimited initial symbol/constant definitions. | `""` | +| **`-0`** | `rep0` | Starting index for trajectory repetition sampling (`seed = r`). | `0` | +| **`-1`** | `rep1` | Upper-bound repetition index (exclusive; total runs = `rep1 - rep0`). | `1` | +| **`-w`** | `num_workers` | Number of parallel worker threads used to execute repetitions concurrently. | `1` | +| **`-t`** | `threads_per_worker` | Number of threads assigned to each worker for state-vector linear algebra. | `1` | +| **`-f`** | `max_fused_size` | Maximum gate fusion size (number of qubits per fused multi-qubit matrix). | `2` | +| **`-v`** | `verbosity` | Logging verbosity level (`0` = silent, `1+` = verbose progress output). | `0` | +| **`-z`** | *(None)* | Enables `Flush-to-Zero` (FTZ) and `Denormals-are-Zeros` (DAZ) in floating-point `MXCSR`. | `false` | + +## Detailed Flag Behavior + +### Circuit Input (`-c`) + +Specifies the file path to the input text file written in the [`qsim` circuit description language](input_format.md). + +### Symbol Injection (`-s`) + +Injects global constants in the root symbol table prior to circuit parsing. This is useful for parameter sweeps, variable initializations, or setting the circuit qubit count (`nq`): + +```bash +./qsim -c circuit.qs -s "nq = 5 gamma = 0.05 theta = 1.57" + +``` + +Types are inferred automatically based on whether the assigned expression is convertible to an integer. Symbol statements can be delimited by spaces or semicolons (`;`). + +### Repetitions & Random Seeds (`-0`, `-1`) + +Controls the trajectory sampling loop for noisy simulations, classical control branching, and measurement statistics. `qsim` executes trajectories using the following loop logic: + +```cpp +for (uint64_t r = rep0; r < rep1; ++r) { + // The repetition index acts directly as the trajectory RNG seed + uint64_t seed = r; + RunTrajectory(circuit, seed); +} + +``` + +* The total number of executed trajectories is **`rep1 - rep0`**. +* Each trajectory index `r` serves directly as the seed for the pseudorandom number generator, ensuring deterministic and reproducible runs. + +### Parallel Execution (`-w`, `-t`) + +* **`-w `**: Distributes trajectory repetitions across `w` worker threads in parallel. +* **`-t `**: Allocates `t` threads per worker for parallel state-vector matrix-vector multiplications. + +**Performance Tuning Recommendations:** + +* **Small Qubit Counts:** Set `-t 1` (default) and set `-w` to the number of physical CPU cores on your system for maximum throughput across repetitions. +* **Large Qubit Counts:** Set `-t` to the number of physical CPU cores on your system and set `-w 1` (default) to accelerate single-trajectory state-vector operations. + +### MXCSR Denormals Control (`-z`) + +Enables the SIMD hardware flags `Flush-to-Zero` (FTZ) and `Denormals-are-Zeros` (DAZ). Enabling this option can provide significant speedups during state-vector operations by preventing hardware performance penalties when state amplitudes approach floating-point underflow ($\approx 10^{-38}$). + +## Constants + +The `qsim` application injects several pre-defined constant identifiers into the symbol table, making them directly accessible within circuit files: + +| Identifier | Description | Value | +| --- | --- | --- | +| `pi` | Mathematical constant $\pi$ | `3.14159265358979323846` | +| `rep0` | Starting repetition index | Specified by the `-0` flag | +| `rep1` | Upper-bound repetition index | Specified by the `-1` flag | +| `rid` | Current repetition ID | Dynamic value in the range `[rep0, rep1)` | +| `wid` | Current worker ID | Dynamic value in the range `[0, num_workers)` | + +## Usage Examples + +### Compute 10 Factorial ($10!$) + +```bash +./qsim -c examples/factorial.qs -s "n = 10" + +``` + +### Run Parallel Trajectories + +Execute $100,000$ noisy circuit trajectories distributed across 5 parallel worker threads: + +```bash +./qsim -c examples/noisy.qs -0 0 -1 100000 -w 5 -s "gamma = 0.1" + +``` + +Sample circuits and additional demonstration files are available in the `qsim/qsim/examples/` directory. diff --git a/docs/qsim_language.md b/docs/qsim_language.md new file mode 100644 index 000000000..ffcc96e4c --- /dev/null +++ b/docs/qsim_language.md @@ -0,0 +1,304 @@ +# qsim Circuit Description Language + +This document describes the circuit description format for the `qsim` application (`qsim/qsim/`). A subset of this format is supported by the example applications in `qsim/apps/` (where noise, variables, and classical control structures are ignored). + +## Expressions, Variables, Constants, and Scopes + +### Expressions + +Circuit descriptions can include expressions for gate parameters, loop/branch conditions, and variable assignments. For more details, see [Expressions](classical_control.md%23Expressions). + +### Delimiters & Comments + +* Comments start with `#` and extend to the end of the line. +* Statements are delimited by semicolons `;` or newlines `\n`. + +### Constants & Variables + +Constants are read-only at runtime and evaluated during parsing. Variables are mutable and updated dynamically during simulation. + +```text +# Constants +const int a = 2 # Integer scalar +const float b = 2.3 * a # Floating-point scalar +const int c(3) = 1 a 3 # Integer vector of size 3 (c[0]=1, c[1]=a, c[2]=3) +const float d(3) # Uninitialized float vector (defaults to 0.0, 1.0, 2.0) + +# Variables +int i = 0 # Integer variable +float x = 1.5 # Float variable +int arr(4) = 1 2 3 i + 1 # Integer vector + +``` + +**Notes:** + +* **Vector Initialization:** Expression elements on the right-hand side of a vector assignment are delimited by spaces (and can be enclosed in parentheses for readability). If a vector declaration omits an explicit assignment, its elements default to consecutive integers from `0` to `size - 1`. + +### Scopes + +Nested control blocks (`if`, `repeat`, `do ... while`) define isolated symbol scopes. Variable resolution traverses outward through parent scopes. + +## Quantum Circuit Specification & Gates + +### Number of Qubits + +The first entry in a circuit definition specifies the total number of qubits. It is **optional** and must evaluate to a constant integer expression (which can reference existing integer constants in the symbol table). + +```text +5 # Explicit total circuit qubits + +``` + +How the parser determines qubit count: + +* **If present:** The parser evaluates the expression, sets the total qubit count, and stores or updates `nq` in the symbol table. +* **If omitted:** The parser checks the symbol table for `nq`: +* **`nq` exists:** Sets the qubit count to the value of `nq`. +* **`nq` is missing:** Defaults the qubit count to `1` and inserts `nq = 1` into the symbol table. + + + +### Optional Noise + +Noise can be specified at the top of a circuit file—either as the **second entry** (immediately following the qubit count) or as the **first entry** (if the qubit count is omitted). + +#### Syntax + +```text +noise channel parameters + +``` + +* **`channel`**: Name of the one-qubit noise channel. +* **`parameters`**: One or more space-separated floating-point expressions (can include existing constants in the symbol table). + +#### Supported Channels + +| Channel | Parameters | Description | +| --- | --- | --- | +| `amplitude_damp` | 1 | Amplitude damping | +| `phase_damp` | 1 | Phase damping | +| `phase_flip` | 1 | Phase flip | +| `bit_flip` | 1 | Bit flip | +| `depolarize` | 1 | Depolarization | +| `generalized_amplitude_damp` | 2 | Generalized amplitude damping | +| `asymmetric_depolarize` | 3 | Asymmetric depolarization | + +#### Example + +```text +noise amplitude_damp 0.01 + +``` + +When specified, the noise channel is automatically applied to **every qubit** after **every moment**. + +### Supported Quantum Gates + +Quantum gates are defined using the following format: + +```text +[time] gate_name qubit_indices... [parameters...] + +``` + +The optional `[time]` prefix defines the explicit moment/time step for the gate. If omitted, the time step is assigned automatically. Qubit indices can involve integer constant expressions, and gate parameters can involve runtime expressions (except for controlled gates). + +The supported gates are listed below: + +| Gate | Format | Example Usage | +| --- | --- | --- | +| Global Phase ($\phi$) | `p phi` | `p 3.14` | +| Hadamard | `h qi` | `h 0` | +| T | `t qi` | `t 1` | +| X | `x qi` | `x 2` | +| Y | `y qi` | `y 3` | +| Z | `z qi` | `z 4` | +| $\sqrt{X}$ | `x_1_2 qi` | `x_1_2 5` | +| $\sqrt{Y}$ | `y_1_2 qi` | `y_1_2 6` | +| $R_x(\phi) = \exp(-i \phi X / 2)$ | `rx qi phi` | `rx 7 0.79` | +| $R_y(\phi) = \exp(-i \phi Y / 2)$ | `ry qi phi` | `ry 8 1.05` | +| $R_z(\phi) = \exp(-i \phi Z / 2)$ | `rz qi phi` | `rz 9 0.79` | +| $R_{x,y}(\theta, \phi) = \exp(-i \phi[\cos(\theta) X + \sin(\theta) Y ] / 2)$ | `rxy qi theta phi` | `rxy 0 1.05 0.79` | +| $\sqrt{W} = \sqrt{i} R_{x,y}(\pi/4, \pi/2)$ | `hz_1_2 qi` | `hz_1_2 1` | +| S | `s qi` | `s 1` | +| CZ | `cz qi1 qi2` | `cz 2 3` | +| CNOT | `cnot qi1 qi2` (or `cx`) | `cnot 4 5` | +| SWAP | `sw qi1 qi2` | `sw 6 7` | +| iSWAP | `is qi1 qi2` | `is 6 7` | +| fSim($\theta, \phi$) | `fs qi1 qi2 theta phi` | `fs 6 7 3.14 1.57` | +| CPhase($\phi$) | `cp qi1 qi2 phi` | `cp 0 1 0.78` | +| Identity (1-qubit) | `id1 qi` | `id1 0` | +| Identity (2-qubit) | `id2 qi1 qi2` | `id2 0 1` | +| Measurement ($n$-qubit) | `m qi1 qi2 ... [tag]` | `m 0 1 2` or `m 0 1 2 m1` | +| Controlled Gate | `c cqi1 cqi2 ... gate` | `c 0 1 rx 4 0.5` | + +For the measurement gate, gn optional measurement tag can be specified following the qubit indices. This tag serves as the measurement variable name and can be referenced in subsequent expressions. + +```text +const int qs(nq) +m qs[2] m1 +fs qs[0] qs[1] pi * m1 / 2 pi / 4 + +``` + +## Classical Control Constructs + +Classical control flow in `qsim` consists of six core constructs (the `[time]` prefix is optional for all constructs): + +### 1. Conditional Execution (`if / elsif / else`) + +Branches execution based on boolean expressions. `elsif` and `else` blocks are optional; `elsif` cannot follow `else`. + +```text +[time] if condition1 + # Executed if condition1 is true +elsif condition2 + # Executed if condition1 is false and condition2 is true +elsif condition3 + # Executed if condition1 is false and condition3 is true +# ... +else + # Executed if all preceding conditions are false +end + +``` + +### 2. Pre-Condition Loop (`repeat`) + +Repeats the enclosed block while `condition` evaluates to `true` (evaluated prior to each iteration). + +```text +[time] repeat condition + # Block executed while condition is true +end + +``` + +### 3. Post-Condition Loop (`do ... while`) + +Executes the enclosed block at least once, evaluating `condition` at the end of each iteration. + +```text +[time] do + # Block executed at least once +while condition + +``` + +### 4. Runtime Variable Assignment + +Mutates classical variables or vector elements during circuit execution. + +```text +a = 2 # Integer scalar assignment +b = 2.3 + a # Float scalar assignment +c(3) = 2 * a 3 * a 4 * a # Vector assignment +vec[index] = expr # Single-element update + +``` + +### 5. Trajectory Filtering (`discard`) + +Immediately halts and aborts the current quantum trajectory simulation if `condition` evaluates to `true`. Discarded trajectories are excluded from observable calculations. + +```text +m 0 m1 +discard m1 == 0 + +``` + +### 6. Console Debug Output (`println`) + +Prints evaluated classical expressions or formatted strings (using C++20 formatting syntax) during simulation. Supports up to four expressions. + +```text +println 'Hello, World!' +println "Index = {}, Value = {}" i (i * 2) + +``` + +## Circuit Moments & Automatic Timing + +Each operation is assigned an integer `time` tag specifying its execution moment: + +1. **Explicit Timing:** Operations can be explicitly prefixed with a time tag (e.g., `0 h 0`). +2. **Automatic Timing:** If time tags are omitted, `qsim` assigns the earliest possible time step such that no qubit undergoes multiple operations within the same moment. +3. **Monotonic Ordering:** Time indices within a single scope must be non-decreasing. +4. **Control Structure Timing:** Automatic timing for `if`, `repeat`, and `do ... while` blocks accounts for measurement tags referenced in their conditional expressions. +5. **Control Scope Timing:** Time counters reset to `0` inside nested classical control blocks (`if`, `repeat`, `do ... while`) and resume tracking outer-scope time upon block exit. + +### Gate Ordering Rules + +Operations are executed in literal program order without reordering. In the following example: + +* Both `h 0` and `h 1` are assigned time `0`. +* Measurement `m 0 m1` is assigned time `1`. +* The `if` statement and `y 1` gate are assigned time `2` (the `if` block and `y 1` do not swap, and `y 1` cannot share time `1` with the measurement). +* The `x 0` gate inside the `if` block resets to time `0` within its local scope. + +```text +2 +h 0 +h 1 +m 0 m1 +if m1 + x 0 +end +y 1 + +``` + +## Observables + +### Measurement Histograms + +Histograms accumulate output bitstring distributions across simulated trajectories: + +```text +m 0 1 2 m1 +histogram m1 + +``` + +* All measurement tags referenced in `histogram` statements must be unique, even across different scopes. +* Generating histograms for a large number of measured qubits may increase simulation runtime. + +## Examples + +### 1. Basic Output + +```text +println 'Hello, World!' + +``` + +### 2. Iterative Factorial Computation ($12!$) + +```text +const int n = 12 +int f = 1 +int i = 1 + +repeat i <= n + f = f * i + i = i + 1 +end + +println f + +``` + +### 3. Noisy Single-Qubit Circuit with Histogram + +```text +1 +noise amplitude_damp 0.1 + +x 0 +m 0 m1 + +histogram m1 + +``` \ No newline at end of file diff --git a/lib/BUILD b/lib/BUILD index ddc2ed2bc..7d79355ac 100644 --- a/lib/BUILD +++ b/lib/BUILD @@ -31,6 +31,15 @@ cc_library( "circuit.h", "circuit_noisy.h", "circuit_qsim_parser.h", + "classical_control_error.h", + "classical_control_expr.h", + "classical_control_obs.h", + "classical_control_parser.h", + "classical_control_symbol.h", + "classical_control_symtab.h", + "classical_control_tokenizer.h", + "classical_control_util.h", + "error.h", "expect.h", "formux.h", "fuser.h", @@ -51,6 +60,7 @@ cc_library( "parfor.h", "qtrajectory.h", "run_qsim.h", + "run_qsim_deprecated.h", "run_qsimh.h", "seqfor.h", "simmux.h", @@ -108,6 +118,15 @@ cuda_library( "circuit.h", "circuit_noisy.h", "circuit_qsim_parser.h", + "classical_control_error.h", + "classical_control_expr.h", + "classical_control_obs.h", + "classical_control_parser.h", + "classical_control_symbol.h", + "classical_control_symtab.h", + "classical_control_tokenizer.h", + "classical_control_util.h", + "error.h", "expect.h", "formux.h", "fuser.h", @@ -128,6 +147,7 @@ cuda_library( "parfor.h", "qtrajectory.h", "run_qsim.h", + "run_qsim_deprecated.h", "run_qsimh.h", "seqfor.h", "simmux.h", @@ -182,6 +202,15 @@ cuda_library( "circuit.h", "circuit_noisy.h", "circuit_qsim_parser.h", + "classical_control_error.h", + "classical_control_expr.h", + "classical_control_obs.h", + "classical_control_parser.h", + "classical_control_symbol.h", + "classical_control_symtab.h", + "classical_control_tokenizer.h", + "classical_control_util.h", + "error.h", "expect.h", "formux.h", "fuser.h", @@ -204,6 +233,7 @@ cuda_library( "qtrajectory.h", "run_custatevecex.h", "run_qsim.h", + "run_qsim_deprecated.h", "run_qsimh.h", "seqfor.h", "simmux.h", @@ -260,6 +290,15 @@ cc_library( "channel.h", "circuit.h", "circuit_qsim_parser.h", + "classical_control_error.h", + "classical_control_expr.h", + "classical_control_obs.h", + "classical_control_parser.h", + "classical_control_symbol.h", + "classical_control_symtab.h", + "classical_control_tokenizer.h", + "classical_control_util.h", + "error.h", "expect.h", "formux.h", "fuser.h", @@ -275,6 +314,7 @@ cc_library( "operation_base.h", "parfor.h", "run_qsim.h", + "run_qsim_deprecated.h", "seqfor.h", "simmux.h", "simulator.h", @@ -351,6 +391,11 @@ cc_library( ### Utility libraries ### +cc_library( + name = "error", + hdrs = ["error.h"], +) + # Bitstring parser cc_library( name = "bitstring", @@ -430,6 +475,36 @@ cc_library( ], ) +### Classical control libraries ### + +cc_library( + name = "classical_control_expr", + hdrs = ["classical_control_expr.h"], + deps = [ + ":error", + ], +) + +cc_library( + name = "classical_control", + hdrs = [ + "classical_control_error.h", + "classical_control_obs.h", + "classical_control_parser.h", + "classical_control_symbol.h", + "classical_control_symtab.h", + "classical_control_tokenizer.h", + "classical_control_util.h", + ], + deps = [ + ":channel", + ":classical_control_expr", + ":error", + ":gate", + ":operation_base", + ], +) + ### Gate libraries ### cc_library( @@ -442,8 +517,8 @@ cc_library( hdrs = ["operation.h"], deps = [ ":channel", - ":gate", - ":operation_base", + ":classical_control_expr", + ":error", ], ) @@ -451,6 +526,7 @@ cc_library( name = "gate", hdrs = ["gate.h"], deps = [ + ":classical_control_expr", ":matrix", ":operation_base", ], @@ -496,7 +572,11 @@ cc_library( name = "circuit_qsim_parser", hdrs = ["circuit_qsim_parser.h"], deps = [ + ":channels_cirq", ":circuit", + ":classical_control", + ":classical_control_expr", + ":error", ":gates_qsim", ":operation", ":operation_base", @@ -555,6 +635,22 @@ cc_library( cc_library( name = "run_qsim", hdrs = ["run_qsim.h"], + deps = [ + ":circuit", + ":classical_control", + ":classical_control_expr", + ":error", + ":gate", + ":gate_appl", + ":operation", + ":operation_base", + ":util", + ], +) + +cc_library( + name = "run_qsim_deprecated", + hdrs = ["run_qsim_deprecated.h"], deps = [ ":circuit", ":gate", diff --git a/lib/channel.h b/lib/channel.h index 928ebd0a1..5a63e6f1a 100644 --- a/lib/channel.h +++ b/lib/channel.h @@ -112,7 +112,9 @@ struct KrausOperator { }; /** - * A Quantum channel. Currently `BaseOperation`s fields are not used. + * A Quantum noise channel represented by Kraus operators. + * Currently `BaseOperation`s fields are not used. + * @tparam FP Floating-point precision type (`float` or `double`). */ template struct Channel : public BaseOperation { diff --git a/lib/channels_cirq.h b/lib/channels_cirq.h index 6734881bc..9c42ecda8 100644 --- a/lib/channels_cirq.h +++ b/lib/channels_cirq.h @@ -316,7 +316,7 @@ inline AmplitudeDampingChannel amplitude_damp(double gamma) { */ template struct PhaseDampingChannel { - static constexpr char name[] = "phase_dump"; + static constexpr char name[] = "phase_damp"; PhaseDampingChannel(double gamma) : gamma(gamma) {} diff --git a/lib/circuit.h b/lib/circuit.h index bf9238fff..1afc611d0 100644 --- a/lib/circuit.h +++ b/lib/circuit.h @@ -1,4 +1,4 @@ -// Copyright 2019 Google LLC. All Rights Reserved. +// Copyright 2026 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -24,7 +24,9 @@ namespace qsim { */ template struct Circuit { + /** Total number of qubits in the circuit state space. */ unsigned num_qubits; + /** * The set of operations to be run. Operation time steps should be ordered. */ diff --git a/lib/circuit_qsim_parser.h b/lib/circuit_qsim_parser.h index 0d2404282..23db3c810 100644 --- a/lib/circuit_qsim_parser.h +++ b/lib/circuit_qsim_parser.h @@ -1,4 +1,4 @@ -// Copyright 2019 Google LLC. All Rights Reserved. +// Copyright 2026 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,436 +12,1580 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef CIRCUIT_QSIM_PARSER_H_ -#define CIRCUIT_QSIM_PARSER_H_ +#ifndef PARSER_H_ +#define PARSER_H_ -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include #include +#include "channels_cirq.h" #include "circuit.h" +#include "classical_control_error.h" +#include "classical_control_expr.h" +#include "classical_control_obs.h" +#include "classical_control_parser.h" +#include "classical_control_symbol.h" +#include "classical_control_symtab.h" +#include "classical_control_tokenizer.h" +#include "classical_control_util.h" +#include "error.h" #include "gates_qsim.h" +#include "matrix.h" #include "operation.h" #include "operation_base.h" namespace qsim { +namespace cc { + /** - * Parser for the (deprecated) qsim file input format. - * The primary supported interface for designing circuits to simulate with qsim - * is Cirq, which relies on - * the Python-based qsimcirq interface. For C++ applications, Cirq gates can be - * explicitly constructed in code. + * Parses space-, semicolon-, or line-delimited constant definitions from + * a string buffer. + * + * Populates the provided symbol table symtab with evaluation results from + * statements like `"nq = 5 c1 = 1 c2 = c1 * 2"`. Constant definitions do not + * use `const` or type keywords in this mode. Evaluated expressions convert to + * `TInt` if `IsConvertibleToInt` returns true, otherwise to `TFloat`. + * + * @tparam ParserError Error policy for syntax errors. + * @tparam RuntimeError Error policy for runtime errors. + * @tparam SymTable Symbol table type. + * @param sym_defs String view containing symbol assignment definitions. + * @param symtab Symbol table instance to populate with parsed symbols. + * @throws syntax_error on syntax or indexing errors during symbol parsing. */ -template -class CircuitQsimParser final { +template +inline void ParseSymbols(std::string_view sym_defs, SymTable& symtab) { + using PE = ParserError; + using RE = RuntimeError; + + // Ensure we are operating in a active scope + if (symtab.Empty()) { + auto scope = symtab.AddScope(); + symtab.EnterScope(scope); + } + + Tokenizer tok(sym_defs); + Token t = tok(); + + // Skip optional semicolons or delimiters + while (t.kind == Token::kDelimiter) { + t = tok(); + } + + while (t.kind != Token::kEndOfFile) { + if (t.kind != Token::kIdentifier) { + PE::Throw("expected an identifier", t.lc); + } + + if (auto* sym = symtab.LookupInCurrentScope(t.val)) { + PE::Throw("identifier '{}' is already defined", t.lc, t.val); + } + + Token n = tok(); + + if (n.val != "=") { + PE::Throw("expected '='", n.lc); + } + + Expr expr = ExprParser, PE, RE>::Run(symtab, tok); + + if (IsConvertibleToInt(symtab, expr)) { + symtab.Insert(t.val, + Symbol{Symbol::Int{EvalIntExpr(symtab, expr)}, true}); + } else { + symtab.Insert(t.val, Symbol{Symbol::Float{EvalExpr(symtab, expr)}, true}); + } + + t = tok(); + + // Skip optional semicolons or delimiters + while (t.kind == Token::kDelimiter) { + t = tok(); + } + } +} + +/** + * Parser for the qsim circuit definition language. + * @tparam FP Floating-point precision type (`float` or `double`). + * @tparam ParserError Error policy for syntax errors. + * @tparam RuntimeError Error policy for runtime errors. + */ +template +struct CircuitQsimParser final { + private: + using fp_type = FP; + using Operation = qsim::Operation; + using Operations = std::vector; + using ClassicallyControlledOperation = + qsim::ClassicallyControlledOperation; + using Circuit = qsim::Circuit; + using Channel = qsim::Channel; + + using RuntimeResolvedGate = qsim::RuntimeResolvedGate; + + using PE = ParserError; + using RE = RuntimeError; + + using IndexParser = cc::IndexParser; + using ExprParser = cc::ExprParser; + using IntExprParser = cc::ExprParser; + using CondExprParser = IntExprParser; + public: /** - * Parses the given input stream into a Circuit object, following the rules - * defined in "docs/input_format.md". - * @param maxtime Maximum gate "time" to read operations for (inclusive). - * @param provider Circuit source; only used for error reporting. - * @param fs The stream to read the circuit from. - * @param circuit Output circuit object. If parsing is successful, this will - * contain the circuit defined in 'fs'. - * @return True if parsing succeeds; false otherwise. + * Parses a string representation of a circuit into a Circuit object. + * @param circuit_str Input string containing the qsim DSL circuit program. + * @param max_depth Maximum gate depth/time (inclusive) up to which + * operations will be parsed. + * @return The parsed `Circuit` object. + * @throws syntax_error if syntax errors occur during parsing. */ - template - static bool FromStream(unsigned maxtime, const std::string& provider, - Stream& fs, Circuit>& circuit) { - circuit.num_qubits = 0; + static auto Run(std::string_view circuit_str, unsigned max_depth) { + auto [circuit, _] = Run(circuit_str, max_depth, SymTable{}); + return std::move(circuit); + } - circuit.ops.resize(0); - circuit.ops.reserve(1024); + /** + * Parses a string representation of a circuit and populates an existing + * symbol table. + * @tparam SymTable Symbols table container type. + * @param circuit_str Input string containing the qsim DSL circuit program. + * @param max_depth Maximum gate depth/time (inclusive) up to which + * operations will be parsed. + * @param symtab Symbol table instance populated during parsing. + * @return Pair containing the parsed `Circuit` and `Observables` objects. + * @throws syntax_error if syntax errors occur during parsing. + */ + template + static auto Run( + std::string_view circuit_str, unsigned max_depth, SymTable&& symtab) { + Circuit circuit; + Observables obss; + + if (symtab.Empty()) { + symtab.EnterScope(symtab.AddScope()); + } - unsigned k = 0; + Tokenizer tok(circuit_str); - std::string line; - line.reserve(128); + Token t = tok.Peek(); - unsigned time; - std::string gate_name; - gate_name.reserve(16); + while (t.kind == Token::kDelimiter) { + tok(); + t = tok.Peek(); + } - unsigned max_time = 0; - unsigned prev_mea_time = 0; + try { + Expr e = IntExprParser::Run(symtab, tok); - std::vector last_times; + t = tok.Peek(); + if (t.kind != Token::kDelimiter) { + Error::Throw(""); + } + + circuit.num_qubits = EvalIntExpr(symtab, e); + } catch (std::runtime_error&) { + PE::Throw("expected a delimiter", t.lc); + } catch (...) { + tok.Restart(); - while (std::getline(fs, line)) { - ++k; + Symbol* sym = symtab.Lookup("nq"); + if (sym == nullptr) { + circuit.num_qubits = 1; + } else { + if (sym->IsConvertibleToInt()) { + circuit.num_qubits = sym->GetInt(); + } else { + Error::Throw("'nq' is not integer"); + } + } + } - if (line.size() == 0 || line[0] == '#') continue; + Symbol* sym = symtab.Lookup("nq"); + if (sym != nullptr) { + sym->Assign(cc::Symbol::Int(circuit.num_qubits)); + } else { + symtab.Insert("nq", cc::Symbol::Int(circuit.num_qubits)); + } - std::stringstream ss(line); + t = tok.Peek(); - if (circuit.num_qubits == 0) { - ss >> circuit.num_qubits; - if (circuit.num_qubits == 0) { - IO::errorf("invalid number of qubits in %s in line %u.\n", - provider.c_str(), k); - return false; - } + while (t.kind == Token::kDelimiter) { + tok(); + t = tok.Peek(); + } + + std::optional channel; - last_times.resize(circuit.num_qubits, unsigned(-1)); + if (t.val == "noise") { + tok(); + channel = GetChannel(symtab, tok); - continue; + t = tok.Peek(); + if (t.kind != Token::kDelimiter) { + PE::Throw("expected a delimiter", t.lc); } + } + + std::vector acts_on(circuit.num_qubits, false); + + circuit.ops = ParseOps(0, circuit.num_qubits, max_depth, + symtab, obss, channel, acts_on, tok); + + return std::make_pair(circuit, obss); + } - ss >> time >> gate_name; + private: + static Operations ParseOps( + unsigned level, unsigned num_qubits, + unsigned max_depth, SymTable& symtab, Observables& obss, + const std::optional& channel, std::vector& acts_on, + Tokenizer& tok) { + Operations ops; + ops.reserve(2048); // Okay for not very long circuits. + + Token t = tok(); + + unsigned cur_time = 0; + std::vector qubit_times(num_qubits, -1); + + bool is_add_noise = IsAddNoise(channel.has_value(), level); - if (!ss) { - InvalidGateError(provider, k); - return false; + while (t.kind != Token::kEndOfFile) { + while (t.kind == Token::kDelimiter) { + t = tok(); } - if (time > maxtime) { + if (t.kind == Token::kEndOfFile) { break; } - if (gate_name == "c") { - if (!ParseControlledGate(ss, time, circuit.num_qubits, circuit.ops)) { - InvalidGateError(provider, k); - return false; + int time = -1; + unsigned prev_time = cur_time; + + if (t.kind == Token::kInteger) { + time = ToInt(t.val); + if (static_cast(time) > max_depth) { + break; } - } else if (!ParseGate(ss, time, circuit.num_qubits, - gate_name, false, circuit.ops)) { - InvalidGateError(provider, k); - return false; + + t = tok(); } - const auto& op = circuit.ops.back(); - bool is_measurement = OpGetAlternative(op) != nullptr; + int max_mea_time = -2; + auto f = [&symtab, &max_mea_time]( + const Symbol::Mea& mea, std::string_view name) { + if (symtab.LookupInCurrentScope(name) != nullptr) { + int time = mea.time; + if (time > max_mea_time) { + max_mea_time = time; + } + } + }; + + if (auto gate = GetGate(t.val, num_qubits, symtab, tok, f)) { + // Matrix gate. + + auto& bop = OpBaseOperation(*gate); + + cur_time = GetTime(t, time, cur_time, bop.qubits, qubit_times, + max_mea_time, is_add_noise); + + if (cur_time > max_depth) { + break; + } + + if (is_add_noise && cur_time != prev_time) { + AddNoise(num_qubits, prev_time, *channel, ops); + } + + if (level > 0) { + ActsOn(bop.qubits, acts_on); + } + + bop.time = TimeToNoisyTime(cur_time, is_add_noise); + ops.push_back(std::move(*gate)); + } else if (auto* sym = symtab.Lookup(t.val)) { + if (sym->IsReadOnly() || t.val == "wid" || t.val == "rid") { + PE::Throw("unexpected const identifier '{}'", t.lc, t.val); + } + + // Assignment. + + ClassicallyControlledOperation cop; + cop.time = TimeToNoisyTime(cur_time, is_add_noise); + cop.str.push_back(t.val); + cop.kind = ClassicallyControlledOperation::kAssign; + + Token n = tok.Peek(); + + if (n.val != "[") { + if (n.val != "=") { + PE::Throw("expected '='", n.lc); + } + + tok(); // Consume '='. + + Assign(&cop, t, symtab, sym, tok); + } else { + n = tok.Peek(1); + + Index index = IndexParser::Run(symtab, tok); + std::size_t i = EvalIndex(symtab, index); + + if (i >= sym->Size()) { + PE::Throw("index {} is out of range", n.lc, i); + } + + n = tok.Peek(); + + if (n.val != "=") { + PE::Throw("expected '='", n.lc); + } + + tok(); // Consume '='. + + Assign(cop, t, symtab, sym, std::move(index), tok); + } + + ops.push_back(std::move(cop)); + } else { + auto hash = Hash(t.val); + + switch (hash) { + case "c"_hash: + { + if (t.val != "c") { + PE::Throw("unexpected token", t.lc); + } + + // Controlled gate. + + std::set visited; + auto controlled_by = GetNQubits(num_qubits, symtab, visited, tok); + + if (controlled_by.size() == 0) { + PE::Throw("controlled gate should be controlled " + "by at least one qubit", t.lc); + } + + cur_time = GetTime(t, time, cur_time, controlled_by, qubit_times); + + if (cur_time > max_depth) { + return ops; + } + + if (level > 0) { + ActsOn(controlled_by, acts_on); + } + + Token n = tok(); + + if (auto gate = GetGate(n.val, num_qubits, symtab, tok)) { + if (auto* g = OpGetAlternative>(*gate)) { + for (unsigned q : g->qubits) { + if (visited.contains(q)) { + PE::Throw("control and gate qubit indices overlap", t.lc); + } + } + + cur_time = GetTime(t, time, cur_time, g->qubits, qubit_times); + + if (cur_time > max_depth) { + return ops; + } + + if (is_add_noise && cur_time != prev_time) { + AddNoise(num_qubits, prev_time, *channel, ops); + } + + if (level > 0) { + ActsOn(g->qubits, acts_on); + } + + g->time = TimeToNoisyTime(cur_time, is_add_noise); + + ops.push_back( + ControlledGate(*g, std::move(controlled_by))); + } else { + PE::Throw("runtime resolved gate cannot be controlled", t.lc); + } + } else { + PE::Throw("expected a gate to be controlled", t.lc); + } + } + + break; + case "m"_hash: + { + if (t.val != "m") { + PE::Throw("unexpected token", t.lc); + } + + // Measurement gate. + + std::set visited; + auto qubits = GetNQubits(num_qubits, symtab, visited, tok); + + if (qubits.size() == 0) { + PE::Throw( + "measurement gate should have at least one qubit", t.lc); + } + + cur_time = GetTime(t, time, cur_time, qubits, qubit_times); + unsigned mtime = TimeToNoisyTime(cur_time, is_add_noise); + + if (cur_time > max_depth) { + return ops; + } + + if (is_add_noise && cur_time != prev_time) { + AddNoise(num_qubits, prev_time, *channel, ops); + } + + if (level > 0) { + ActsOn(qubits, acts_on); + } + + if (tok.Peek().kind != Token::kIdentifier) { + ops.push_back(CreateMeasurement(mtime, std::move(qubits))); + } else { + Token n = tok(); + + if (IsKeyWord(n.val)) { + PE::Throw("identifier '{}' is reserved", n.lc, n.val); + } + + unsigned num_bits = qubits.size(); + symtab.Insert(n.val, Symbol{Symbol::Mea{0, num_bits, mtime}}); + + ops.push_back( + CreateMeasurement(mtime, std::move(qubits), n.val)); + } + } - if (time < prev_mea_time || (is_measurement && time < max_time)) { - IO::errorf("operation crosses the time boundary set by measurement " - "operations in line %u in/ %s.\n", k, provider.c_str()); - return false; + break; + case "if"_hash: + case "repeat"_hash: + { + if (t.val != "if" && t.val != "repeat") { + PE::Throw("unexpected token", t.lc); + } + + // Classical control if_else or repeat. + + ExpectCondition(tok); + + std::vector acts_on2(num_qubits, false); + unsigned max_depth = std::numeric_limits::max(); + + auto if_else = ClassicallyControlledOperation::kIfElse; + auto repeat = ClassicallyControlledOperation::kRepeat; + + ClassicallyControlledOperation cop; + + cop.qubits.reserve(num_qubits); + cop.kind = hash == "if"_hash ? if_else : repeat; + cop.exprs.push_back(CondExprParser::Run(symtab, tok, f)); + + enum State { + kStart = 1, + kElse, + }; + + State state = kStart; + + while (1) { + unsigned scope_index = symtab.AddScope(); + symtab.EnterScope(scope_index); + + cop.sub_ops.push_back(ParseOps(level + 1, num_qubits, max_depth, + symtab, obss, channel, acts_on2, tok)); + cop.scope_indices.push_back(scope_index); + + symtab.ExitScope(); + + Token c = tok.Current(); + + if (c.kind == Token::kEndOfFile) { + PE::Throw("unexpected end of file", c.lc); + } + + if (c.val == "end") { + break; + } + + if (cop.kind == repeat) { + PE::Throw("expected 'end'", c.lc); + } + + if (c.val == "else") { + if (state == kElse) { + PE::Throw("too many 'else' clauses", c.lc); + } + + if (tok.Peek().kind != Token::kDelimiter) { + PE::Throw("'else' should be followed be a delimiter", c.lc); + } + + state = kElse; + continue; + } + + if (c.val == "elsif") { + if (state == kElse) { + PE::Throw("'elsif' cannot follow 'else'", c.lc); + } + + ExpectCondition(tok); + cop.exprs.push_back(CondExprParser::Run(symtab, tok, f)); + continue; + } + + if (state == kElse) { + PE::Throw("expected 'end'", c.lc); + } else { + PE::Throw("expected 'else', 'elsif' or 'end'", c.lc); + } + } + + for (unsigned i = 0; i < num_qubits; ++i) { + if (acts_on2[i]) { + cop.qubits.push_back(i); + if (level > 0) { + acts_on[i] = true; + } + } + } + + cur_time = GetTime(t, time, cur_time, cop.qubits, qubit_times, + max_mea_time, is_add_noise); + + if (cur_time > max_depth) { + return ops; + } + + if (is_add_noise && cur_time != prev_time) { + AddNoise(num_qubits, prev_time, *channel, ops); + } + + cop.time = TimeToNoisyTime(cur_time, is_add_noise); + ops.push_back(std::move(cop)); + } + + break; + case "do"_hash: + { + if (t.val != "do") { + PE::Throw("unexpected token", t.lc); + } + + // Classical control do_while. + + std::vector acts_on2(num_qubits, false); + unsigned max_depth = std::numeric_limits::max(); + + auto f = [&symtab, &max_mea_time]( + const Symbol::Mea& mea, std::string_view name) { + if (symtab.LookupInCurrentScope(name) == nullptr && + symtab.LookupInPreviousScope(name) != nullptr) { + int time = mea.time; + if (time > max_mea_time) { + max_mea_time = time; + } + } + }; + + ClassicallyControlledOperation cop; + + unsigned scope_index = symtab.AddScope(); + symtab.EnterScope(scope_index); + + cop.sub_ops.push_back(ParseOps(level + 1, num_qubits, max_depth, + symtab, obss, channel, acts_on2, + tok)); + + Token n = tok.Current(); + + if (n.val != "while") { + PE::Throw("expected 'while'", n.lc); + } + + ExpectCondition(tok); + + cop.qubits.reserve(num_qubits); + cop.exprs.push_back(CondExprParser::Run(symtab, tok, f)); + cop.kind = ClassicallyControlledOperation::kDoWhile; + cop.scope_indices.push_back(scope_index); + + symtab.ExitScope(); + + for (unsigned i = 0; i < num_qubits; ++i) { + if (acts_on2[i]) { + cop.qubits.push_back(i); + if (level > 0) { + acts_on[i] = true; + } + } + } + + cur_time = GetTime(t, time, cur_time, cop.qubits, qubit_times, + max_mea_time, is_add_noise); + + if (cur_time > max_depth) { + return ops; + } + + if (is_add_noise && cur_time != prev_time) { + AddNoise(num_qubits, prev_time, *channel, ops); + } + + cop.time = TimeToNoisyTime(cur_time, is_add_noise); + ops.push_back(std::move(cop)); + } + + break; + case "int"_hash: + case "float"_hash: + { + Token n = tok(); + + if (t.val == "int") { + sym = DeclareSymbol(n, cur_time, symtab, ops, tok); + } else if (t.val == "float") { + sym = DeclareSymbol(n, cur_time, symtab, ops, tok); + } else { + PE::Throw("unexpected token", t.lc); + } + + if (tok.Peek().val == "=") { + tok(); // Consume '='. + + ClassicallyControlledOperation cop; + cop.time = TimeToNoisyTime(cur_time, is_add_noise); + cop.str.push_back(n.val); + cop.kind = ClassicallyControlledOperation::kAssign; + + Assign(&cop, n, symtab, sym, tok); + + ops.push_back(std::move(cop)); + } + } + + break; + case "const"_hash: + { + if (t.val != "const") { + PE::Throw("unexpected token", t.lc); + } + + Symbol* sym = nullptr; + Token n = tok(); + + if (n.val == "int") { + n = tok(); + sym = DeclareSymbol(n, cur_time, symtab, ops, tok); + } else if (n.val == "float") { + n = tok(); + sym = DeclareSymbol(n, cur_time, symtab, ops, tok); + } else { + PE::Throw("type '{}' after 'const' is unknown", n.lc, n.val); + } + + if (tok.Peek().val == "=") { + tok(); // Consume '='. + Assign(nullptr, n, symtab, sym, tok); + } + } + + break; + case "println"_hash: + { + if (t.val != "println") { + PE::Throw("unexpected token", t.lc); + } + + ClassicallyControlledOperation cop; + + Token n = tok.Peek(); + if (n.kind == Token::kString) { + cop.str.push_back(n.val); + tok(); + } + + cop.time = TimeToNoisyTime(cur_time, is_add_noise); + cop.exprs.reserve(4); + cop.kind = ClassicallyControlledOperation::kPrintLn; + + while (tok.Peek().kind != Token::kDelimiter && + tok.Peek().kind != Token::kEndOfFile) { + if (cop.exprs.size() == 4) { + PE::Throw("'println' supports only up to " + "four expression arguments", t.lc); + } + + cop.exprs.push_back(ExprParser::Run(symtab, tok)); + } + + ops.push_back(std::move(cop)); + } + + break; + case "discard"_hash: + { + if (t.val != "discard") { + PE::Throw("unexpected token", t.lc); + } + + ExpectCondition(tok); + + ClassicallyControlledOperation cop; + cop.time = TimeToNoisyTime(cur_time, is_add_noise); + cop.exprs.push_back(CondExprParser::Run(symtab, tok)); + cop.kind = ClassicallyControlledOperation::kDiscard; + + ops.push_back(std::move(cop)); + } + + break; + case "histogram"_hash: + { + if (t.val != "histogram") { + PE::Throw("unexpected token", t.lc); + } + + Token n = tok.Peek(); + + if (n.kind != Token::kIdentifier) { + PE::Throw("expected a measurement tag", n.lc); + } + + auto* sym = symtab.Lookup(n.val); + + if (sym == nullptr) { + PE::Throw("identifier '{}' is not defined", n.lc, n.val); + } + + if (!sym->HoldsMea()) { + PE::Throw("identifier '{}' is not a measurement", n.lc, n.val); + } + + if (obss.Lookup(n.val) != nullptr) { + PE::Throw("histogram for '{}' is already defined", n.lc, n.val); + } + + obss.Insert(n.val, MeasurementHistogram(sym->Size())); + + tok(); // Consume idetifier. + } + + break; + case "else"_hash: + if (t.val != "else") { + PE::Throw("unexpected token", t.lc); + } + + return ops; + case "elsif"_hash: + if (t.val != "elsif") { + PE::Throw("unexpected token", t.lc); + } + + return ops; + case "end"_hash: + if (t.val != "end") { + PE::Throw("unexpected token", t.lc); + } + + return ops; + case "while"_hash: + if (t.val != "while") { + PE::Throw("unexpected token", t.lc); + } + + return ops; + default: + PE::Throw("unexpected token", t.lc); + } } - if (is_measurement) { - prev_mea_time = time; + t = tok(); + + if (t.kind != Token::kEndOfFile && t.kind != Token::kDelimiter) { + PE::Throw("expected a delimiter or end of file", t.lc); + } + } + + return ops; + } + + static bool IsKeyWord(std::string_view s) { + static std::unordered_set keywords = { + "p", "id1", "h", "t", "x", "y", "z", "x_1_2", "y_1_2", "rx", "ry", "rz", + "rxy", "hz_1_2", "s", "id2", "cz", "cx", "cnot", "sw", "is", "fs", "cp", + "c", "m", "if", "elsif", "else", "end", "repeat", "do", "while", "int", + "float", "const", "println", "discard", "histogram", + }; + + return s.size() <= 8 && keywords.contains(s); + } + + static void ExpectCondition(Tokenizer& tok) { + Token n = tok.Peek(); + if (n.kind == Token::kDelimiter || n.kind == Token::kEndOfFile) { + PE::Throw("expected a condition", n.lc); + } + } + + template + static Symbol* DeclareSymbol(Token t, unsigned cur_time, SymTable& symtab, + Operations& ops, Tokenizer& tok) { + if (t.kind != Token::kIdentifier) { + PE::Throw("expected an identifier", t.lc); + } + + if (auto* sym = symtab.LookupInCurrentScope(t.val)) { + PE::Throw("identifier '{}' is already defined", t.lc, t.val); + } + + if (IsKeyWord(t.val)) { + PE::Throw("identifier '{}' is reserved", t.lc, t.val); + } + + Token n = tok.Peek(); + + if (n.val != "(") { + if constexpr (int_symbol) { + return symtab.Insert(t.val, Symbol{Symbol::Int{0}, read_only}); + } else { + return symtab.Insert(t.val, Symbol{Symbol::Float{0.0}, read_only}); } + } else { + // Vector. + + tok(); // Consume '('. + + auto e = IntExprParser::Run(symtab, tok); - const auto* pg = OpGetAlternative>(op); + tok(); // Consume ')'. - if (GateIsOutOfOrder(time, OpQubits(op), last_times) - || (pg && GateIsOutOfOrder(time, pg->controlled_by, last_times))) { - IO::errorf("operation is out of time order in line %u in %s.\n", - k, provider.c_str()); - return false; + if (!IsConstExpr(symtab, e)) { + PE::Throw("unknown vector size", n.lc); } - if (time > max_time) { - max_time = time; + unsigned size = EvalIntExpr(symtab, e); + auto v = std::views::iota(unsigned{0}, size); + + if constexpr (int_symbol) { + return symtab.Insert( + t.val, Symbol{Symbol::IntVector(v.begin(), v.end()), read_only}); + } else { + return symtab.Insert( + t.val, Symbol{Symbol::FloatVector(v.begin(), v.end()), read_only}); } } - - return true; } - /** - * Parses the given file into a Circuit object, following the rules defined - * in "docs/input_format.md". - * @param maxtime Maximum gate "time" to read operations for (inclusive). - * @param file The name of the file to read the circuit from. - * @param circuit Output circuit object. If parsing is successful, this will - * contain the circuit defined in 'file'. - * @return True if parsing succeeds; false otherwise. - */ - template - static bool FromFile(unsigned maxtime, const std::string& file, - Circuit>& circuit) { - auto fs = IO::StreamFromFile(file); + static auto Assign(ClassicallyControlledOperation* cop, Token t, + const SymTable& symtab, Symbol* sym, Tokenizer& tok) { + if (cop != nullptr && sym->IsReadOnly()) { + PE::Throw("cannot assign to the const identifier '{}'", t.lc, t.val); + } + + if (sym->HoldsMea()) { + PE::Throw("cannot assign to the measurement identifier '{}'", + t.lc, t.val); + } + + Token n = tok.Peek(); + bool is_float = sym->IsFloat(); + + if (!sym->HoldsVector()) { + auto e = ExprParser::Run(symtab, tok); - if (!fs) { - return false; + if (cop == nullptr && !IsConstExpr(symtab, e)) { + PE::Throw("cannot assign the const identifier '{}'", n.lc, t.val); + } + + if (is_float) { + sym->Assign(EvalExpr(symtab, e)); + } else { + if (!IsConvertibleToInt(symtab, e)) { + PE::Throw("'float' cannot be assigned to the integer identifer '{}'", + n.lc, t.val); + } + + sym->Assign(EvalIntExpr(symtab, e)); + } + + if (cop != nullptr) { + cop->exprs.push_back(std::move(e)); + } } else { - bool rc = FromStream(maxtime, file, fs, circuit); - IO::CloseStream(fs); - return rc; + unsigned count = 0; + unsigned size = sym->Size(); + + if (cop != nullptr) { + cop->exprs.reserve(size); + } + + while (n.kind != Token::kDelimiter && n.kind != Token::kEndOfFile) { + if (count >= size) { + PE::Throw("too many expressions in vector assignment", n.lc); + } + + auto e = ExprParser::Run(symtab, tok); + + if (cop == nullptr && !IsConstExpr(symtab, e)) { + PE::Throw("cannot assign to the const identifier '{}'", n.lc, t.val); + } + + if (is_float) { + sym->Assign(EvalExpr(symtab, e), count); + } else { + if (!IsConvertibleToInt(symtab, e)) { + PE::Throw( + "'float' cannot be assigned to the integer identifer '{}'", + n.lc, t.val); + } + + sym->Assign(EvalIntExpr(symtab, e), count); + } + + if (cop != nullptr) { + cop->exprs.push_back(std::move(e)); + } + + ++count; + n = tok.Peek(); + } } + + return cop; } - private: - static void InvalidGateError(const std::string& provider, unsigned line) { - IO::errorf("invalid gate in %s in line %u.\n", provider.c_str(), line); + static void Assign(ClassicallyControlledOperation& cop, Token t, + const SymTable& symtab, Symbol* sym, Index&& index, + Tokenizer& tok) { + if (sym->IsReadOnly()) { + PE::Throw("cannot assign to the const identifier '{}'", t.lc, t.val); + } + + if (sym->HoldsMea()) { + PE::Throw("cannot assign to the measurement identifier '{}'", + t.lc, t.val); + } + + Token n = tok.Peek(); + + auto e = ExprParser::Run(symtab, tok); + + if (sym->IsFloat()) { + sym->Assign(EvalExpr(symtab, e), EvalIndex(symtab, index)); + } else { + if (!IsConvertibleToInt(symtab, e)) { + PE::Throw("'float' cannot be assigned to the integer identifer '{}'", + n.lc, t.val); + } + + sym->Assign(EvalIntExpr(symtab, e), EvalIndex(symtab, index)); + } + + cop.exprs.push_back(std::move(e)); + cop.indices.push_back(std::move(index)); } - /** - * Checks formatting for a zero-qubit gate parsed from 'ss'. - * @param ss Input stream containing the gate specification. - */ - static bool ValidateGate(std::stringstream& ss) { - return ss && ss.peek() == std::stringstream::traits_type::eof(); + static void ActsOn( + const std::vector& qubits, std::vector& acts_on) { + for (auto q : qubits) { + acts_on[q] = true; + } } - /** - * Checks formatting for a single-qubit gate parsed from 'ss'. - * @param ss Input stream containing the gate specification. - * @param num_qubits Number of qubits, as defined at the start of the file. - * @param q0 Index of the affected qubit. - */ - static bool ValidateGate(std::stringstream& ss, - unsigned num_qubits, unsigned q0) { - return ss && ss.peek() == std::stringstream::traits_type::eof() - && q0 < num_qubits; + static unsigned GetTime(const Token& t, int time, unsigned cur_time, + const std::vector& qubits, + std::vector& qubit_times, + int max_mea_time = -2, bool is_add_noise = false) { + if (time > -1) { + if (static_cast(time) < cur_time) { + PE::Throw("gate time is not in order", t.lc); + } + + for (unsigned q : qubits) { + if (qubit_times[q] == time) { + PE::Throw("qubit indices overlap at time {}", t.lc, time); + } else if (qubit_times[q] > time) { + PE::Throw("gate time is not in order", t.lc); + } + + qubit_times[q] = time; + } + + cur_time = time; + } else { + int time = -1; + + for (unsigned q : qubits) { + if (qubit_times[q] > time) { + time = qubit_times[q]; + } + } + + max_mea_time = NoisyTimeToTime(max_mea_time, is_add_noise); + + if (static_cast(time) == cur_time || + static_cast(max_mea_time) == cur_time) { + ++cur_time; + } + + for (unsigned q : qubits) { + qubit_times[q] = cur_time; + } + } + + return cur_time; + }; + + static bool IsAddNoise(bool have_noise, unsigned level) { + return have_noise && level == 0; } - /** - * Checks formatting for a two-qubit gate parsed from 'ss'. - * @param ss Input stream containing the gate specification. - * @param num_qubits Number of qubits, as defined at the start of the file. - * @param q0 Index of the first affected qubit. - * @param q1 Index of the second affected qubit. - */ - static bool ValidateGate(std::stringstream& ss, - unsigned num_qubits, unsigned q0, unsigned q1) { - return ss && ss.peek() == std::stringstream::traits_type::eof() - && q0 < num_qubits && q1 < num_qubits && q0 != q1; + static unsigned TimeToNoisyTime(unsigned time, bool noisy) { + return noisy ? 2 * time : time; } - /** - * Checks formatting for a multiqubit gate parsed from 'ss'. - * @param ss Input stream containing the gate specification. - * @param num_qubits Number of qubits, as defined at the start of the file. - * @param qubits Indices of affected qubits. - */ - static bool ValidateGate(std::stringstream& ss, unsigned num_qubits, - const Qubits& qubits) { - return ss && ValidateQubits(num_qubits, qubits); + static unsigned NoisyTimeToTime(unsigned time, bool noisy) { + return noisy ? time / 2 : time; } - static bool ValidateControlledGate( - unsigned num_qubits, const Qubits& qubits, const Qubits& controlled_by) { - if (!ValidateQubits(num_qubits, controlled_by)) return false; + static void AddNoise( + unsigned num_qubits, unsigned time, Channel ch, Operations& ops) { + time = TimeToNoisyTime(time, true) + 1; - std::size_t i = 0, j = 0; + for (unsigned q = 0; q < num_qubits; ++q) { + ch.time = time; + ch.qubits[0] = q; + for (auto& kop : ch.kops) { + if (!kop.qubits.empty()) { + kop.qubits[0] = q; + } - while (i < qubits.size() && j < controlled_by.size()) { - if (qubits[i] == controlled_by[j]) { - return false; - } else if (qubits[i] < controlled_by[j]) { - ++i; - } else { - ++j; + for (auto& op : kop.ops) { + op.time = time; + op.qubits[0] = q; + } } + ops.push_back(ch); + } + } + + static auto GetOneQubit( + unsigned num_qubits, const SymTable& symtab, Tokenizer& tok) { + Token n = tok.Peek(); + + if (n.kind == Token::kDelimiter || n.kind == Token::kEndOfFile) { + PE::Throw("expected a qubit index", n.lc); } - return true; + Expr e = IntExprParser::Run(symtab, tok); + if (!IsConstExpr(symtab, e)) { + PE::Throw("qubit index should be const", n.lc); + } + + unsigned q = EvalIntExpr(symtab, e); + if (q >= num_qubits) { + PE::Throw("qubit index {} is out of range", n.lc, q); + } + + return q; } - static bool ValidateQubits(unsigned num_qubits, const Qubits& qubits) { - if (qubits.size() == 0 || qubits[0] >= num_qubits) return false; + static auto GetTwoQubits( + unsigned num_qubits, const SymTable& symtab, Tokenizer& tok) { + Token n0 = tok.Peek(); - // qubits should be sorted. + if (n0.kind == Token::kDelimiter || n0.kind == Token::kEndOfFile) { + PE::Throw("expected a qubit index", n0.lc); + } - for (std::size_t i = 1; i < qubits.size(); ++i) { - if (qubits[i] >= num_qubits || qubits[i] == qubits[i - 1]) { - return false; - } + Expr e0 = IntExprParser::Run(symtab, tok); + if (!IsConstExpr(symtab, e0)) { + PE::Throw("qubit index must be const", n0.lc); + } + + unsigned q0 = EvalIntExpr(symtab, e0); + if (q0 >= num_qubits) { + PE::Throw("qubit index {} is out of range", n0.lc, q0); + } + + Token n1 = tok.Peek(); + + if (n1.kind == Token::kDelimiter || n1.kind == Token::kEndOfFile) { + PE::Throw("expected a qubit index", n1.lc); + } + + Expr e1 = IntExprParser::Run(symtab, tok); + if (!IsConstExpr(symtab, e1)) { + PE::Throw("qubit index must be const", n1.lc); + } + + unsigned q1 = EvalIntExpr(symtab, e1); + if (q1 >= num_qubits) { + PE::Throw("qubit index {} is out of range", n1.lc, q1); + } + + if (q0 == q1) { + PE::Throw("the second qubit index is the same as the first one", n1.lc); } - return true; + return std::make_pair(q0, q1); } - static bool GateIsOutOfOrder(unsigned time, const Qubits& qubits, - std::vector& last_times) { - for (auto q : qubits) { - if (last_times[q] != unsigned(-1) && time <= last_times[q]) { - return true; + static auto GetNQubits(unsigned num_qubits, const SymTable& symtab, + std::set& visited, Tokenizer& tok) { + std::vector qubits; + qubits.reserve(num_qubits); + + Token n = tok.Peek(); + + while (n.kind != Token::kDelimiter) { + if (n.kind == Token::kIdentifier && + symtab.LookupInCurrentScope(n.val) == nullptr) { + break; } - last_times[q] = time; + Expr e = IntExprParser::Run(symtab, tok); + if (!IsConstExpr(symtab, e)) { + PE::Throw("qubit index must be const", n.lc); + } + + unsigned q = EvalIntExpr(symtab, e); + if (q >= num_qubits) { + PE::Throw("qubit index {} is out of range", n.lc, q); + } else if (visited.contains(q)) { + PE::Throw("repeated qubit indices", n.lc); + } + + qubits.push_back(q); + visited.insert(q); + + n = tok.Peek(); } - return false; + return qubits; } - template - static bool ParseGate(Stream& ss, unsigned time, unsigned num_qubits, - const std::string& gate_name, bool controlled, - std::vector>& ops) { - unsigned q0, q1; - fp_type phi, theta; - - if (gate_name == "p") { - ss >> phi; - if (!ValidateGate(ss)) return false; - ops.push_back(GateGPh::Create(time, phi)); - } else if (gate_name == "id1") { - ss >> q0; - if (!ValidateGate(ss, num_qubits, q0)) return false; - ops.push_back(GateId1::Create(time, q0)); - } else if (gate_name == "h") { - ss >> q0; - if (!ValidateGate(ss, num_qubits, q0)) return false; - ops.push_back(GateHd::Create(time, q0)); - } else if (gate_name == "t") { - ss >> q0; - if (!ValidateGate(ss, num_qubits, q0)) return false; - ops.push_back(GateT::Create(time, q0)); - } else if (gate_name == "x") { - ss >> q0; - if (!ValidateGate(ss, num_qubits, q0)) return false; - ops.push_back(GateX::Create(time, q0)); - } else if (gate_name == "y") { - ss >> q0; - if (!ValidateGate(ss, num_qubits, q0)) return false; - ops.push_back(GateY::Create(time, q0)); - } else if (gate_name == "z") { - ss >> q0; - if (!ValidateGate(ss, num_qubits, q0)) return false; - ops.push_back(GateZ::Create(time, q0)); - } else if (gate_name == "x_1_2") { - ss >> q0; - if (!ValidateGate(ss, num_qubits, q0)) return false; - ops.push_back(GateX2::Create(time, q0)); - } else if (gate_name == "y_1_2") { - ss >> q0; - if (!ValidateGate(ss, num_qubits, q0)) return false; - ops.push_back(GateY2::Create(time, q0)); - } else if (gate_name == "rx") { - ss >> q0 >> phi; - if (!ValidateGate(ss, num_qubits, q0)) return false; - ops.push_back(GateRX::Create(time, q0, phi)); - } else if (gate_name == "ry") { - ss >> q0 >> phi; - if (!ValidateGate(ss, num_qubits, q0)) return false; - ops.push_back(GateRY::Create(time, q0, phi)); - } else if (gate_name == "rz") { - ss >> q0 >> phi; - if (!ValidateGate(ss, num_qubits, q0)) return false; - ops.push_back(GateRZ::Create(time, q0, phi)); - } else if (gate_name == "rxy") { - ss >> q0 >> theta >> phi; - if (!ValidateGate(ss, num_qubits, q0)) return false; - ops.push_back(GateRXY::Create(time, q0, theta, phi)); - } else if (gate_name == "hz_1_2") { - ss >> q0; - if (!ValidateGate(ss, num_qubits, q0)) return false; - ops.push_back(GateHZ2::Create(time, q0)); - } else if (gate_name == "s") { - ss >> q0; - if (!ValidateGate(ss, num_qubits, q0)) return false; - ops.push_back(GateS::Create(time, q0)); - } else if (gate_name == "id2") { - ss >> q0 >> q1; - if (!ValidateGate(ss, num_qubits, q0, q1)) return false; - ops.push_back(GateId2::Create(time, q0, q1)); - } else if (gate_name == "cz") { - ss >> q0 >> q1; - if (!ValidateGate(ss, num_qubits, q0, q1)) return false; - ops.push_back(GateCZ::Create(time, q0, q1)); - } else if (gate_name == "cnot" || gate_name == "cx") { - ss >> q0 >> q1; - if (!ValidateGate(ss, num_qubits, q0, q1)) return false; - ops.push_back(GateCNot::Create(time, q0, q1)); - } else if (gate_name == "sw") { - ss >> q0 >> q1; - if (!ValidateGate(ss, num_qubits, q0, q1)) return false; - ops.push_back(GateSwap::Create(time, q0, q1)); - } else if (gate_name == "is") { - ss >> q0 >> q1; - if (!ValidateGate(ss, num_qubits, q0, q1)) return false; - ops.push_back(GateIS::Create(time, q0, q1)); - } else if (gate_name == "fs") { - ss >> q0 >> q1 >> theta >> phi; - if (!ValidateGate(ss, num_qubits, q0, q1)) return false; - ops.push_back(GateFS::Create(time, q0, q1, theta, phi)); - } else if (gate_name == "cp") { - ss >> q0 >> q1 >> phi; - if (!ValidateGate(ss, num_qubits, q0, q1)) return false; - ops.push_back(GateCP::Create(time, q0, q1, phi)); - } else if (gate_name == "m") { - if (controlled) { - IO::errorf("measurement gate cannot be controlled.\n"); - return false; - } - - Qubits qubits; - qubits.reserve(num_qubits); - - while (ss.good()) { - ss >> q0; - if (ss) { - qubits.push_back(q0); + template + static std::optional GetGate( + std::string_view name, unsigned num_qubits, + const SymTable& symtab, Tokenizer& tok, Args&&... args) { + if (name.size() > 6) { + return std::nullopt; + } + + switch (Hash(name)) { + case "p"_hash: + { + if (name != "p") { + break; + } + + auto e = ExprParser::Run(symtab, tok, args...); + + if (IsConstExpr(symtab, e)) { + double phi = EvalExpr(symtab, e); + return GateGPh::Create(0, phi); } else { - return false; + RuntimeResolvedGate g{GateGPh::Create(0, 0.0)}; + g.param_exprs = {std::move(e)}; + g.matrix_func = [](const auto& p, Matrix& m) { + return GateGPh::UpdateMatrix(p[0], m); + }; + + return g; } } + case "id1"_hash: + { + if (name != "id1") { + break; + } - if (!ValidateQubits(num_qubits, qubits)) return false; + auto q0 = GetOneQubit(num_qubits, symtab, tok); + return GateId1::Create(0, q0); + } + case "h"_hash: + { + if (name != "h") { + break; + } - ops.push_back(CreateMeasurement(time, std::move(qubits))); - } else { - return false; - } + auto q0 = GetOneQubit(num_qubits, symtab, tok); + return GateHd::Create(0, q0); + } + case "t"_hash: + { + if (name != "t") { + break; + } - return true; - } + auto q0 = GetOneQubit(num_qubits, symtab, tok); + return GateT::Create(0, q0); + } + case "x"_hash: + { + if (name != "x") { + break; + } - template - static bool ParseControlledGate(Stream& ss, unsigned time, - unsigned num_qubits, - std::vector>& ops) { - Qubits controlled_by; - controlled_by.reserve(64); + auto q0 = GetOneQubit(num_qubits, symtab, tok); + return GateX::Create(0, q0); + } + case "y"_hash: + { + if (name != "y") { + break; + } - std::string gate_name; - gate_name.reserve(16); + auto q0 = GetOneQubit(num_qubits, symtab, tok); + return GateY::Create(0, q0); + } + case "z"_hash: + { + if (name != "z") { + break; + } - while (1) { - while (ss.good()) { - if (!std::isblank(ss.get())) { - ss.unget(); + auto q0 = GetOneQubit(num_qubits, symtab, tok); + return GateZ::Create(0, q0); + } + case "x_1_2"_hash: + { + if (name != "x_1_2") { break; } + + auto q0 = GetOneQubit(num_qubits, symtab, tok); + return GateX2::Create(0, q0); } + case "y_1_2"_hash: + { + if (name != "y_1_2") { + break; + } - if (!ss.good()) { - return false; + auto q0 = GetOneQubit(num_qubits, symtab, tok); + return GateY2::Create(0, q0); } + case "rx"_hash: + { + if (name != "rx") { + break; + } - if (!std::isdigit(ss.peek())) { - break; - } else { - unsigned q; - ss >> q; + auto q0 = GetOneQubit(num_qubits, symtab, tok); + auto e = ExprParser::Run(symtab, tok, args...); - if (!ss.good() || !std::isblank(ss.get())) { - return false; + if (IsConstExpr(symtab, e)) { + double phi = EvalExpr(symtab, e); + return GateRX::Create(0, q0, phi); + } else { + RuntimeResolvedGate g{GateRX::Create(0, q0, 0.0)}; + g.param_exprs = {std::move(e)}; + g.matrix_func = [](const auto& p, Matrix& m) { + return GateRX::UpdateMatrix(p[0], m); + }; + + return g; + } + } + case "ry"_hash: + { + if (name != "ry") { + break; } - controlled_by.push_back(q); + auto q0 = GetOneQubit(num_qubits, symtab, tok); + auto e = ExprParser::Run(symtab, tok, args...); + + if (IsConstExpr(symtab, e)) { + double phi = EvalExpr(symtab, e); + return GateRY::Create(0, q0, phi); + } else { + RuntimeResolvedGate g{GateRY::Create(0, q0, 0.0)}; + g.param_exprs = {std::move(e)}; + g.matrix_func = [](const auto& p, Matrix& m) { + return GateRY::UpdateMatrix(p[0], m); + }; + + return g; + } + } + case "rz"_hash: + { + if (name != "rz") { + break; + } + + auto q0 = GetOneQubit(num_qubits, symtab, tok); + auto e = ExprParser::Run(symtab, tok, args...); + + if (IsConstExpr(symtab, e)) { + double phi = EvalExpr(symtab, e); + return GateRZ::Create(0, q0, phi); + } else { + RuntimeResolvedGate g{GateRZ::Create(0, q0, 0.0)}; + g.param_exprs = {std::move(e)}; + g.matrix_func = [](const auto& p, Matrix& m) { + return GateRZ::UpdateMatrix(p[0], m); + }; + + return g; + } + } + case "rxy"_hash: + { + if (name != "rxy") { + break; + } + + auto q0 = GetOneQubit(num_qubits, symtab, tok); + auto e1 = ExprParser::Run(symtab, tok, args...); + + Token t = tok.Peek(); + if (t.kind == Token::kDelimiter) { + PE::Throw("expected the second gate parameter", t.lc); + } + + auto e2 = ExprParser::Run(symtab, tok, args...); + + if (IsConstExpr(symtab, e1) && IsConstExpr(symtab, e2)) { + double theta = EvalExpr(symtab, e1); + double phi = EvalExpr(symtab, e2); + return GateRXY::Create(0, q0, theta, phi); + } else { + RuntimeResolvedGate g{GateRXY::Create(0, q0, 0.0, 0.0)}; + g.param_exprs = {std::move(e1), std::move(e2)}; + g.matrix_func = [](const auto& p, Matrix& m) { + return GateRXY::UpdateMatrix(p[0], p[1], m); + }; + + return g; + } + } + case "hz_1_2"_hash: + { + if (name != "hz_1_2") { + break; + } + + auto q0 = GetOneQubit(num_qubits, symtab, tok); + return GateHZ2::Create(0, q0); + } + case "s"_hash: + { + if (name != "s") { + break; + } + + auto q0 = GetOneQubit(num_qubits, symtab, tok); + return GateS::Create(0, q0); + } + case "id2"_hash: + { + if (name != "id2") { + break; + } + + auto [q0, q1] = GetTwoQubits(num_qubits, symtab, tok); + return GateId2::Create(0, q0, q1); + } + case "cz"_hash: + { + if (name != "cz") { + break; + } + + auto [q0, q1] = GetTwoQubits(num_qubits, symtab, tok); + return GateCZ::Create(0, q0, q1); + }; + case "cx"_hash: + case "cnot"_hash: + { + if (name != "cx" && name != "cnot") { + break; + } + + auto [q0, q1] = GetTwoQubits(num_qubits, symtab, tok); + return GateCNot::Create(0, q0, q1); + } + case "sw"_hash: + { + if (name != "sw") { + break; + } + + auto [q0, q1] = GetTwoQubits(num_qubits, symtab, tok); + return GateSwap::Create(0, q0, q1); + } + case "is"_hash: + { + if (name != "is") { + break; + } + + auto [q0, q1] = GetTwoQubits(num_qubits, symtab, tok); + return GateIS::Create(0, q0, q1); + } + case "fs"_hash: + { + if (name != "fs") { + break; + } + + auto [q0, q1] = GetTwoQubits(num_qubits, symtab, tok); + auto e1 = ExprParser::Run(symtab, tok, args...); + + Token t = tok.Peek(); + if (t.kind == Token::kDelimiter) { + PE::Throw("expected the second gate parameter", t.lc); + } + + auto e2 = ExprParser::Run(symtab, tok, args...); + + if (IsConstExpr(symtab, e1) && IsConstExpr(symtab, e2)) { + double theta = EvalExpr(symtab, e1); + double phi = EvalExpr(symtab, e2); + return GateFS::Create(0, q0, q1, theta, phi); + } else { + RuntimeResolvedGate g{GateFS::Create(0, q0, q1, 0.0, 0.0)}; + g.param_exprs = {std::move(e1), std::move(e2)}; + g.matrix_func = [](const auto& p, Matrix& m) { + return GateFS::UpdateMatrix(p[0], p[1], m); + }; + + return g; + } + } + case "cp"_hash: + { + if (name != "cp") { + break; + } + + auto [q0, q1] = GetTwoQubits(num_qubits, symtab, tok); + auto e = ExprParser::Run(symtab, tok, args...); + + if (IsConstExpr(symtab, e)) { + double phi = EvalExpr(symtab, e); + return GateCP::Create(0, q0, q1, phi); + } else { + RuntimeResolvedGate g{GateCP::Create(0, q0, q1, 0.0)}; + g.param_exprs = {std::move(e)}; + g.matrix_func = [](const auto& p, Matrix& m) { + return GateRX::UpdateMatrix(p[0], m); + }; + + return g; + } } } - if (controlled_by.size() == 0) { - return false; + return std::nullopt; + } + + static Channel GetChannel(const SymTable& symtab, Tokenizer& tok) { + Token t = tok(); + + if (t.kind != Token::kIdentifier) { + PE::Throw("expected a noise channel", t.lc); } - ss >> gate_name; + auto hash = Hash(t.val); - if (!ss.good() || !ParseGate(ss, time, num_qubits, gate_name, true, ops)) { - return false; + if (hash == "reset"_hash) { + return Cirq::ResetChannel::Create(0, 0); } - if (!ValidateControlledGate(num_qubits, OpQubits(ops.back()), - controlled_by)) { - return false; + Token n = tok.Peek(); + + Expr e1 = ExprParser::Run(symtab, tok); + if (!IsConstExpr(symtab, e1)) { + PE::Throw("expected a const expression", n.lc); } - auto& lop = ops.back(); - lop = OpGetAlternative>(lop)->ControlledBy( - std::move(controlled_by)); + TFloat p1 = EvalExpr(symtab, e1); + + switch (hash) { + case "amplitude_damp"_hash: + return Cirq::AmplitudeDampingChannel::Create(0, 0, p1); + case "phase_damp"_hash: + return Cirq::PhaseDampingChannel::Create(0, 0, p1); + case "phase_flip"_hash: + return Cirq::PhaseFlipChannel::Create(0, 0, p1); + case "bit_flip"_hash: + return Cirq::BitFlipChannel::Create(0, 0, p1); + case "depolarize"_hash: + return Cirq::DepolarizingChannel::Create(0, 0, p1); + case "generalized_amplitude_damp"_hash: + { + n = tok.Peek(); + + Expr e2 = ExprParser::Run(symtab, tok); + if (!IsConstExpr(symtab, e2)) { + PE::Throw("expected a const expression", n.lc); + } + + TFloat p2 = EvalExpr(symtab, e2); - return true; + return Cirq::GeneralizedAmplitudeDampingChannel::Create( + 0, 0, p1, p2); + } + case "asymmetric_depolarize"_hash: + { + n = tok.Peek(); + + Expr e2 = ExprParser::Run(symtab, tok); + if (!IsConstExpr(symtab, e2)) { + PE::Throw("expected a const expression", n.lc); + } + + TFloat p2 = EvalExpr(symtab, e2); + + n = tok.Peek(); + + Expr e3 = ExprParser::Run(symtab, tok); + if (!IsConstExpr(symtab, e3)) { + PE::Throw("expected a const expression", n.lc); + } + + TFloat p3 = EvalExpr(symtab, e3); + + return Cirq::AsymmetricDepolarizingChannel::Create( + 0, 0, p1, p2, p3); + } + default: + PE::Throw("unknown channel '{}'", t.lc, t.val); + } + + return {}; } }; +} // namespace cc + +template +using CircuitQsimParser = cc::CircuitQsimParser; + } // namespace qsim -#endif // CIRCUIT_QSIM_PARSER_H_ +#endif // PARSER_H_ diff --git a/lib/classical_control_error.h b/lib/classical_control_error.h new file mode 100644 index 000000000..10bb952df --- /dev/null +++ b/lib/classical_control_error.h @@ -0,0 +1,111 @@ +// Copyright 2026 Google LLC. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CLASSICAL_CONTROL_ERROR_H_ +#define CLASSICAL_CONTROL_ERROR_H_ + +#include +#include +#include +#include + +#include "classical_control_tokenizer.h" + +namespace qsim::cc { + +/** Utilities and custom exception types for expression parsing errors. */ +struct ParserError { + /** Custom exception thrown when a syntax error is detected during parsing. */ + class syntax_error : public std::exception { + private: + std::string message; + + public: + /** + * Constructs a syntax_error with the provided explanatory string. + * @param message Explanatory string describing the syntax error (moved). + */ + explicit syntax_error(std::string&& message) + : message(std::move(message)) {} + + /** + * Returns a pointer to the explanatory string. + * @return Null-terminated C string describing the syntax error. + */ + const char* what() const noexcept override { + return message.c_str(); + } + }; + + /** + * Unpacks source coordinates, formats an error message, and throws + * a syntax_error. Converts the packed line and column bits into 1-indexed + * numbers, applies variadic formatting arguments to the message string + * using std::vformat, and throws a syntax_error. + * @tparam Args Variadic types of the format arguments. + * @param message Format string explaining the error. + * @param lc Packed line and column value. + * @param args Formatting arguments to substitute into the message. + * @throws syntax_error Always throws with the formatted error string and + * location. + */ + template + static void Throw( + std::string_view message, unsigned lc, Args&&... args) { + auto [line, col] = Tokenizer::UnpackLineAndColumn(lc); + + ++line; ++col; + + auto error_message = std::vformat( + std::string("expression syntax error: ") + std::string(message) + + " at {}:{}", std::make_format_args(args..., line, col)); + + throw syntax_error(std::move(error_message)); + } +}; + +/** + * Utilities for reporting expression runtime errors. + */ +struct RuntimeError { + /** + * Unpacks source coordinates, formats an error message, and throws + * a std::runtime_error. Converts the packed line and column bits into + * 1-indexed numbers, applies variadic formatting arguments to the message + * string using std::vformat, and throws a std::runtime_error. + * @tparam Args Variadic types of the format arguments. + * @param message Format string explaining the error. + * @param lc Packed line and column value. + * @param args Formatting arguments to substitute into the message. + * @throws std::runtime_error Always throws with the formatted error string + * and location. + */ + template + static void Throw( + const std::string_view message, unsigned lc, Args&&... args) { + auto [line, col] = Tokenizer::UnpackLineAndColumn(lc); + + ++line; ++col; + + auto error_message = std::vformat( + std::string("expression runtime error: ") + std::string(message) + + " at {}:{}", std::make_format_args(args..., line, col)); + + throw std::runtime_error(error_message); + } +}; + +} // namespace qsim::cc + +#endif // CLASSICAL_CONTROL_ERROR_H_ diff --git a/lib/classical_control_expr.h b/lib/classical_control_expr.h new file mode 100644 index 000000000..f583993f2 --- /dev/null +++ b/lib/classical_control_expr.h @@ -0,0 +1,317 @@ +// Copyright 2026 Google LLC. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CLASSICAL_CONTROL_EXPR_H_ +#define CLASSICAL_CONTROL_EXPR_H_ + +#include +#include +#include +#include +#include +#include + +#include "error.h" + +namespace qsim::cc { + +struct SymTable; + +using TInt = int64_t; +using TFloat = double; +using TBool = bool; +using TFuncI = std::function; +using TFuncF = std::function; +using TFuncB = std::function; + +/** + * Variant representing an index expression used to access elements in vectors + * or measurements. + */ +using Index = std::variant; + +/** Represents a symbol reference. */ +using TSymbol = std::string_view; + +/** + * Represents an indexed symbol reference, such as a vector or measurement bit + * lookup (e.g., `v[i]`). + */ +using TSymbolInd = std::pair; + +/** + * Abstract representation of compile-time constants or runtime-evaluated + * expressions. + * + * Expressions can be: + * - Direct scalar constants (`TInt`, `TFloat`, `TBool`) + * - Runtime functional closures (`TFuncI`, `TFuncF`, `TFuncB`) + * - Plain or indexed symbol lookup identifiers (`TSymbol`, `TSymbolInd`) + */ +using Expr = std::variant; + +/** + * Computes the dimensionality/size of an Index. + * @return Returns 1 for scalar index expressions. + */ +inline unsigned IndexSize(const Index&) { + return 1; +} + +/** + * Checks if an index expression can be evaluated at compile-time. + * @tparam SymTable Symbol table type. + * @param symtab Symbol table instance used for symbol lookups. + * @param i The index expression to check. + * @return True if the index is a static constant (`TInt` or `TBool`). + */ +template +inline bool IsConstIndex(const SymTable& symtab, const Index& i) { + auto f = [&symtab](auto&& i) -> bool { + using I = std::decay_t; + return std::is_same_v || std::is_same_v; + }; + + return std::visit(f, i); +} + +/** + * Evaluates an Index variant to a concrete array/vector position index + * (`std::size_t`). + * @tparam SymTable Symbol table type. + * @param symtab Symbol table instance used for symbol lookups. + * @param index Index variant to evaluate. + * @return Non-negative target index position. + */ +template +inline std::size_t EvalIndex(const SymTable& symtab, const Index& index) { + auto f = [&symtab](auto&& index) -> std::size_t { + using I = std::decay_t; + + if constexpr (std::is_same_v) { + return index; + } else if constexpr (std::is_same_v) { + return index; + } else if constexpr (std::is_same_v) { + return index(symtab); + } else if constexpr (std::is_same_v) { + return index(symtab); + } + }; + + return std::visit(f, index); +} + +/** + * Checks if an index expression can be evaluated at compile-time. + * @tparam SymTable Symbol table type. + * @param symtab Symbol table instance used for symbol lookups. + * @param e Target expression to check. + * @return True for literals (`TInt`, `TFloat`, `TBool`) and read-only symbols; + * false for closures. + * @throws std::runtime_error If symbol lookup fails. + */ +template +inline bool IsConstExpr(const SymTable& symtab, const Expr& e) { + auto f = [&symtab](auto&& e) -> bool { + using E = std::decay_t; + + if constexpr (std::is_same_v) { + return true; + } else if constexpr (std::is_same_v) { + return true; + } else if constexpr (std::is_same_v) { + return true; + } else if constexpr (std::is_same_v) { + return false; + } else if constexpr (std::is_same_v) { + return false; + } else if constexpr (std::is_same_v) { + return false; + } else if constexpr (std::is_same_v) { + const auto* sym = symtab.LookupOrError( + e, "identifier '{}' is not defined", e); + return sym->IsReadOnly(); + } else if constexpr (std::is_same_v) { + const auto* sym = symtab.LookupOrError( + e.first, "identifier '{}' is not defined", e.first); + return sym->IsReadOnly(); + } + }; + + return std::visit(f, e); +} + +/** + * Checks if an expression can be converted/evaluated to an integer. + * @tparam SymTable Symbol table type. + * @param symtab Symbol table instance used for symbol lookups. + * @param e Expression to check. + * @return True for int, bool, int-closures, or int-convertible symbols; + * false for floats. + * @throws std::runtime_error If symbol lookup fails. + */ +template +inline bool IsConvertibleToInt(const SymTable& symtab, const Expr& e) { + auto f = [&symtab](auto&& e) -> bool { + using E = std::decay_t; + + if constexpr (std::is_same_v) { + return true; + } else if constexpr (std::is_same_v) { + return false; + } else if constexpr (std::is_same_v) { + return true; + } else if constexpr (std::is_same_v) { + return true; + } else if constexpr (std::is_same_v) { + return false; + } else if constexpr (std::is_same_v) { + return true; + } else if constexpr (std::is_same_v) { + const auto* sym = symtab.LookupOrError( + e, "identifier '{}' is not defined", e); + return sym->IsConvertibleToInt(); + } else if constexpr (std::is_same_v) { + const auto* sym = symtab.LookupOrError( + e.first, "identifier '{}' is not defined", e.first); + return sym->IsConvertibleToInt(); + } + }; + + return std::visit(f, e); +} + +/** + * Evaluates an expression and returns its scalar value widened/converted + * to TFloat for integer types. + * @tparam SymTable Symbol table type. + * @param symtab Symbol table instance used for symbol lookups. + * @param e Target expression to evaluate. + * @return Result as double-precision float (`TFloat`). + * @throws std::runtime_error If symbol lookup fails. + */ +template +inline TFloat EvalExpr(const SymTable& symtab, const Expr& e) { + auto f = [&symtab](auto&& e) -> TFloat { + using E = std::decay_t; + + if constexpr (std::is_same_v) { + return e; + } else if constexpr (std::is_same_v) { + return e; + } else if constexpr (std::is_same_v) { + return e; + } else if constexpr (std::is_same_v) { + return e(symtab); + } else if constexpr (std::is_same_v) { + return e(symtab); + } else if constexpr (std::is_same_v) { + return e(symtab); + } else if constexpr (std::is_same_v) { + const auto* sym = symtab.LookupOrError( + e, "identifier '{}' is not defined", e); + return sym->GetFloat(); + } else if constexpr (std::is_same_v) { + const auto* sym = symtab.LookupOrError( + e.first, "identifier '{}' is not defined", e.first); + return sym->GetFloat(EvalIndex(symtab, e.second)); + } + }; + + return std::visit(f, e); +} + +/** + * Evaluates an expression as an integer (`TInt`). + * @tparam SymTable Symbol table type. + * @param symtab Symbol table instance used for symbol lookups. + * @param e Target expression to evaluate. + * @return Integer result. + * @throws std::runtime_error If expression is a `float` type or if symbol + * lookup fails. + */ +template +inline TInt EvalIntExpr(const SymTable& symtab, const Expr& e) { + auto f = [&symtab](auto&& e) -> TInt { + using E = std::decay_t; + + if constexpr (std::is_same_v) { + return e; + } else if constexpr (std::is_same_v) { + return e; + } else if constexpr (std::is_same_v) { + return e(symtab); + } else if constexpr (std::is_same_v) { + return e(symtab); + } else if constexpr (std::is_same_v) { + const auto* sym = symtab.LookupOrError( + e, "identifier '{}' is not defined", e); + return sym->GetInt(); + } else if constexpr (std::is_same_v) { + const auto* sym = symtab.LookupOrError( + e.first, "identifier '{}' is not defined", e.first); + return sym->GetInt(EvalIndex(symtab, e.second)); + } else { + Error::Throw("'float' is not convertible to 'int'"); + return 0; + } + }; + + return std::visit(f, e); +} + +/** + * Evaluates an expression as a boolean condition (`TBool`). + * @tparam SymTable Symbol table type. + * @param symtab Symbol table instance used for symbol lookups. + * @param e Target expression to evaluate. + * @return True if integer value is non-zero or boolean condition holds. + * @throws std::runtime_error If expression evaluates from a `float` type or + * if symbol lookup fails. + */ +template +inline TBool EvalCondExpr(const SymTable& symtab, const Expr& e) { + auto f = [&symtab](auto&& e) -> TBool { + using E = std::decay_t; + + if constexpr (std::is_same_v) { + return e != 0; + } else if constexpr (std::is_same_v) { + return e; + } else if constexpr (std::is_same_v) { + return e(symtab) != 0; + } else if constexpr (std::is_same_v) { + return e(symtab); + } else if constexpr (std::is_same_v) { + const auto* sym = symtab.LookupOrError( + e, "identifier '{}' is not defined", e); + return sym->GetInt() != 0; + } else if constexpr (std::is_same_v) { + const auto* sym = symtab.LookupOrError( + e.first, "identifier '{}' is not defined", e.first); + return sym->GetInt(EvalIndex(symtab, e.second)) != 0; + } else { + Error::Throw("'float' is not convertible to 'bool'"); + return 0.0; + } + }; + + return std::visit(f, e); +} + +} // namespace qsim::cc + +#endif // CLASSICAL_CONTROL_EXPR_H_ diff --git a/lib/classical_control_obs.h b/lib/classical_control_obs.h new file mode 100644 index 000000000..3d60505bf --- /dev/null +++ b/lib/classical_control_obs.h @@ -0,0 +1,142 @@ +#ifndef CLASSICAL_CONTROL_OBS_H_ +#define CLASSICAL_CONTROL_OBS_H_ + +#include +#include +#include +#include +#include + +namespace qsim::cc { + +/** + * Tracks raw measurement counts for quantum circuit trajectories. + * Maintains two count vectors of size 2^num_qubits: + * - `cur_count`: Counts accumulated during the current active trajectory run. + * - `total_count`: Aggregate counts across all completed trajectories. + */ +struct MeasurementHistogram { + /** Constructs an empty, uninitialized MeasurementHistogram. */ + MeasurementHistogram() {} + + /** + * Constructs a MeasurementHistogram allocated for 2^num_qubits outcomes. + * @param num_qubits Number of measured qubits determining histogram bit + * width. + */ + MeasurementHistogram(unsigned num_qubits) : num_qubits(num_qubits) { + uint64_t size = uint64_t{1} << num_qubits; + cur_count.resize(size, 0); + total_count.resize(size, 0); + } + + /** + * Resets current trajectory counts without accumulating them into + * total counts. Called when a simulation trajectory is aborted via + * a `discard` condition. + */ + void Discard() { + for (auto& v : cur_count) { + v = 0; + } + } + + /** + * Flushes `cur_count` into `total_count` and zeroes `cur_count` for + * the next trajectory. + */ + void Update() { + for (std::size_t i = 0; i < cur_count.size(); ++i) { + total_count[i] += cur_count[i]; + cur_count[i] = 0; + } + } + + /** + * Returns the number of qubits tracked by this histogram. + * @return Qubit count. + */ + unsigned Size() const { + return num_qubits; + } + + /** Measurement counts for the active trajectory. */ + std::vector cur_count; + /** Accumulated measurement counts across all completed trajectories. */ + std::vector total_count; + /** Number of qubits tracked by this histogram. */ + unsigned num_qubits = 0; +}; + +/** + * Type alias for simulation observables (currently defaults to + * MeasurementHistogram). + */ +using Observable = MeasurementHistogram; + +/** + * Map container managing named `Observable` instances (measurement histograms). + */ +struct Observables { + /** + * Inserts or replaces a named observable. + * @param name Unique string identifier for the observable. + * @param obs Rvalue instance of Observable to move into storage. + * @return Pointer to the inserted Observable in storage. + */ + Observable* Insert(std::string_view name, Observable&& obs) { + return &(obss[name] = std::move(obs)); + } + + /** + * Searches for a named observable. + * @param name Target identifier name. + * @return Pointer to the matching Observable or `nullptr` if not found. + */ + Observable* Lookup(std::string_view name) { + auto o = obss.find(name); + return o != obss.end() ? &o->second : nullptr; + } + + /** Const version of Lookup */ + const Observable* Lookup(std::string_view name) const { + auto o = obss.find(name); + return o != obss.end() ? &o->second : nullptr; + } + + /** + * Checks if the container holds any observables. + * @return True if no observables are registered. + */ + bool Empty() const { + return obss.empty(); + } + + /** + * Invokes callable f on each `(name, observable)` pair in storage. + * @tparam F Callable type with signature + * `void(std::string_view, Observable&)`. + * @param f Function or lambda to execute per element. + */ + template + void Iterate(F&& f) { + for (auto& [name, obs] : obss) { + f(name, obs); + } + } + + /** Const version of Iterate. */ + template + void Iterate(F&& f) const { + for (const auto& [name, obs] : obss) { + f(name, obs); + } + } + + private: + std::unordered_map obss; +}; + +} // namespace qsim::cc + +#endif // CLASSICAL_CONTROL_OBS_H_ diff --git a/lib/classical_control_parser.h b/lib/classical_control_parser.h new file mode 100644 index 000000000..b213d4fe5 --- /dev/null +++ b/lib/classical_control_parser.h @@ -0,0 +1,907 @@ +// Copyright 2026 Google LLC. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CLASSICAL_CONTROL_PARSER_H_ +#define CLASSICAL_CONTROL_PARSER_H_ + +#include +#include +#include + +#include "classical_control_expr.h" +#include "classical_control_symbol.h" +#include "classical_control_symtab.h" +#include "classical_control_tokenizer.h" +#include "classical_control_util.h" +#include "error.h" + +namespace qsim::cc { + +/** + * Parses numerical and conditional expressions from a Tokenizer stream. + * @tparam IndexParser Parser struct used to parse vector/measurement bracket + * indices. + * @tparam ParserError Error policy for syntax errors. + * @tparam RuntimeError Error policy for runtime errors. Used in closures + * when division by zero is detected. + * @tparam int_only If true, restricts parsing strictly to integer/boolean + * expressions. Encountering floating-point literals, float symbols, or `**` + * throws an error. + */ +template +struct ExprParser { + private: + using PE = ParserError; + using RE = RuntimeError; + + static constexpr unsigned kOrPrec = 1; + static constexpr unsigned kXorPrec = 2; + static constexpr unsigned kAndPrec = 3; + static constexpr unsigned kBitOrPrec = 4; + static constexpr unsigned kBitXorPrec = 5; + static constexpr unsigned kBitAndPrec = 6; + static constexpr unsigned kEqPrec = 7; + static constexpr unsigned kNotEqPrec = 7; + static constexpr unsigned kLessPrec = 8; + static constexpr unsigned kLessEqPrec = 8; + static constexpr unsigned kGreaterPrec = 8; + static constexpr unsigned kGreaterEqPrec = 8; + static constexpr unsigned kBitLeftShiftPrec = 9; + static constexpr unsigned kBitRightShiftPrec = 9; + static constexpr unsigned kAddPrec = 10; + static constexpr unsigned kSubPrec = 10; + static constexpr unsigned kMulPrec = 11; + static constexpr unsigned kDivPrec = 11; + static constexpr unsigned kModPrec = 11; + static constexpr unsigned kPowPrec = 12; + + static constexpr auto empty = [](auto&&...) {}; + + public: + /** + * Parses an expression starting from the current tokenizer position. + * Stops automatically when operators are exhausted or when an opening + * (left) parenthesis is reached. + * If the expression involves only compile-time constants, the expression + * is folded directly into a scalar literal (`TInt`, `TFloat`, or `TBool`). + * @tparam F Callback function type with signature + * `void(const Symbol::Mea&, std::string_view)`. + * @param symtab Symbol table instance used for symbol lookups. + * @param tok Lexical analyzer stream positioned at the start of + * the expression. + * @param f Optional callback invoked whenever a measurement (`Mea`) symbol + * is parsed. + * @return Parsed `Expr` variant containing a constant scalar value or + * an executable function closure. + * @throws syntax_erorr on syntax errors, unexpected tokens, or type + * mismatches. + */ + template + static Expr Run( + const SymTable& symtab, Tokenizer& tok, F&& f = std::forward(empty)) { + return RunWithPrecedence(symtab, tok, 0, f); + } + + private: + static bool IsInt(const Expr& e) { + return std::holds_alternative(e); + } + + static bool IsSymbol(const Expr& e) { + return std::holds_alternative(e); + } + + static bool IsSymbolInd(const Expr& e) { + return std::holds_alternative(e); + } + + static unsigned GetPrecedence(const Token& t) { + switch (OpHash(t.val)) { + case "||"_ophash: + return kOrPrec; + case "^^"_ophash: + return kXorPrec; + case "&&"_ophash: + return kAndPrec; + case "|"_ophash: + return kBitOrPrec; + case "^"_ophash: + return kBitXorPrec; + case "&"_ophash: + return kBitAndPrec; + case "=="_ophash: + return kEqPrec; + case "!="_ophash: + return kNotEqPrec; + case "<"_ophash: + return kLessPrec; + case "<="_ophash: + return kLessEqPrec; + case ">"_ophash: + return kGreaterPrec; + case ">="_ophash: + return kGreaterEqPrec; + case "<<"_ophash: + return kBitLeftShiftPrec; + case ">>"_ophash: + return kBitRightShiftPrec; + case "+"_ophash: + return kAddPrec; + case "-"_ophash: + return kSubPrec; + case "*"_ophash: + return kMulPrec; + case "/"_ophash: + return kDivPrec; + case "%"_ophash: + return kModPrec; + case "**"_ophash: + return kPowPrec; + default: + PE::Throw("unknown operator", t.lc); + return 0; + }; + } + + template + static Expr RunWithPrecedence( + const SymTable& symtab, Tokenizer& tok, unsigned precedence, F&& f) { + Expr l = GetLeft(symtab, tok, f); + + while (1) { + Token n = tok.Peek(); + + if (n.kind != Token::kOperator || + n.val == "(" || n.val == ")" || n.val == "]") { + break; + } + + if (precedence >= GetPrecedence(n)) { + break; + } + + l = GetRight(symtab, tok, l, f); + } + + return l; + } + + template + static Expr GetLeft(const SymTable& symtab, Tokenizer& tok, F&& f) { + Token t = tok(); + + switch (OpHash(t.val)) { + case "!"_ophash: + { + if (t.val == "!") { + Token n = tok.Peek(); + + Expr l = GetLeft(symtab, tok, f); + + if (!IsConvertibleToInt(symtab, l)) { + PE::Throw("expression is not convertible to 'bool'", n.lc); + } + + if (IsConstExpr(symtab, l)) { + return TBool{!EvalCondExpr(symtab, l)}; + } else { + return TFuncB{[l](const SymTable& symtab) { + return !EvalCondExpr(symtab, l); + }}; + } + } + } + case "~"_ophash: + { + Token n = tok.Peek(); + unsigned lc = n.lc; + + Expr l = GetLeft(symtab, tok, f); + + if (!IsConvertibleToInt(symtab, l)) { + PE::Throw("expected an integer expression", t.lc); + } + + if (IsConstExpr(symtab, l)) { + return TInt{~EvalIntExpr(symtab, l)}; + } else { + return TFuncI{[l, lc](const SymTable& symtab) { + auto val = ~EvalIntExpr(symtab, l); + val = TruncateMea(symtab, l, val); + return TInt{val}; + }}; + } + } + case "-"_ophash: + { + Expr l = GetLeft(symtab, tok, f); + + if (IsConstExpr(symtab, l)) { + if (IsConvertibleToInt(symtab, l)) { + return TInt{-EvalIntExpr(symtab, l)}; + } else { + return TFloat{-EvalExpr(symtab, l)}; + } + } else { + if (IsConvertibleToInt(symtab, l)) { + return TFuncI{[l](const SymTable& symtab) { + return -EvalIntExpr(symtab, l); + }}; + } else { + return TFuncF{[l](const SymTable& symtab) { + return -EvalExpr(symtab, l); + }}; + } + } + } + case "("_ophash: + { + Expr l = RunWithPrecedence(symtab, tok, 0, f); + + Token t = tok(); + if (t.val != ")") { + PE::Throw("expected ')'", t.lc); + } + + return l; + } + } + + switch (t.kind) { + case Token::kInteger: + return TInt{ToInt(t.val)}; + case Token::kFloat: + if constexpr (int_only) { + PE::Throw("float value in an integer expression", t.lc); + } + + return TFloat{ToFloat(t.val)}; + case Token::kIdentifier: + { + const auto* sym = symtab.Lookup(t.val); + + if (sym == nullptr) { + PE::Throw("identifier '{}' is not defined", t.lc, t.val); + } + + bool convertible_to_int = sym->IsConvertibleToInt(); + + if (int_only && !convertible_to_int) { + PE::Throw("float identifier in an integer expression", t.lc); + } + + Token n = tok.Peek(); + + if (n.val != "[") { + if (sym->IsReadOnly()) { + if (convertible_to_int) { + return TInt{sym->GetInt()}; + } else { + return TFloat{sym->GetFloat()}; + } + } else { + if (sym->HoldsMea()) { + f(sym->GetMea(), t.val); + } + return TSymbol{t.val}; + } + } else { + if (!sym->HoldsMea() && !sym->HoldsVector()) { + PE::Throw("scalar identifier cannot be indexed", t.lc); + } + + Token n = tok.Peek(1); + + auto index = IndexParser::Run(symtab, tok); + uint64_t i = EvalIndex(symtab, index); + + if (i >= sym->Size()) { + PE::Throw("index is out of range", n.lc); + } + + if (sym->IsReadOnly() && IsConstIndex(symtab, index)) { + if (convertible_to_int) { + return TInt{sym->GetInt(i)}; + } else { + return TFloat{sym->GetFloat(i)}; + } + } else { + if (sym->HoldsMea()) { + f(sym->GetMea(), t.val); + } + return TSymbolInd{t.val, std::move(index)}; + } + } + } + default: + PE::Throw("unexpected token", t.lc); + return 0.0; + } + } + + template + static Expr GetRight( + const SymTable& symtab, Tokenizer& tok, const Expr& l, F&& f) { + Token t = tok(); + + switch (OpHash(t.val)) { + case "||"_ophash: + { + if (!IsConvertibleToInt(symtab, l)) { + PE::Throw("expected a left operand of type 'bool' or 'int'", t.lc); + } + + Expr r = RunWithPrecedence(symtab, tok, kOrPrec, f); + + if (!IsConvertibleToInt(symtab, r)) { + PE::Throw("expected a right operand of type 'bool' or 'int'", t.lc); + } + + if (IsConstExpr(symtab, l) && IsConstExpr(symtab, r)) { + return TBool{EvalCondExpr(symtab, l) || EvalCondExpr(symtab, r)}; + } else { + return TFuncB{[l, r](const SymTable& symtab) { + return EvalCondExpr(symtab, l) || EvalCondExpr(symtab, r); + }}; + } + } + case "^^"_ophash: + { + if (!IsConvertibleToInt(symtab, l)) { + PE::Throw("expected a left operand of type 'bool' or 'int'", t.lc); + } + + Expr r = RunWithPrecedence(symtab, tok, kXorPrec, f); + + if (!IsConvertibleToInt(symtab, r)) { + PE::Throw("expected a right operand of type 'bool' or 'int'", t.lc); + } + + if (IsConstExpr(symtab, l) && IsConstExpr(symtab, r)) { + return TBool{EvalCondExpr(symtab, l) != EvalCondExpr(symtab, r)}; + } else { + return TFuncB{[l, r](const SymTable& symtab) { + return EvalCondExpr(symtab, l) != EvalCondExpr(symtab, r); + }}; + } + } + case "&&"_ophash: + { + if (!IsConvertibleToInt(symtab, l)) { + PE::Throw("expected a left operand of type 'bool' or 'int'", t.lc); + } + + Expr r = RunWithPrecedence(symtab, tok, kAndPrec, f); + + if (!IsConvertibleToInt(symtab, r)) { + PE::Throw("expected a right operand of type 'bool' or 'int'", t.lc); + } + + if (IsConstExpr(symtab, l) && IsConstExpr(symtab, r)) { + return TBool{EvalCondExpr(symtab, l) && EvalCondExpr(symtab, r)}; + } else { + return TFuncB{[l, r](const SymTable& symtab) { + return EvalCondExpr(symtab, l) && EvalCondExpr(symtab, r); + }}; + } + } + case "|"_ophash: + { + if (!IsConvertibleToInt(symtab, l)) { + PE::Throw("expected a left operand of type 'bool' or 'int'", t.lc); + } + + Expr r = RunWithPrecedence(symtab, tok, kBitOrPrec, f); + + if (!IsConvertibleToInt(symtab, r)) { + PE::Throw("expected a right operand of type 'bool' or 'int'", t.lc); + } + + if (IsConstExpr(symtab, l) && IsConstExpr(symtab, r)) { + return TInt{EvalIntExpr(symtab, l) | EvalIntExpr(symtab, r)}; + } else { + return TFuncI{[l, r](const SymTable& symtab) { + return EvalIntExpr(symtab, l) | EvalIntExpr(symtab, r); + }}; + } + } + case "^"_ophash: + { + if (!IsConvertibleToInt(symtab, l)) { + PE::Throw("expected a left operand of type 'bool' or 'int'", t.lc); + } + + Expr r = RunWithPrecedence(symtab, tok, kBitXorPrec, f); + + if (!IsConvertibleToInt(symtab, r)) { + PE::Throw("expected a right operand of type 'bool' or 'int'", t.lc); + } + + if (IsConstExpr(symtab, l) && IsConstExpr(symtab, r)) { + return TInt{EvalIntExpr(symtab, l) ^ EvalIntExpr(symtab, r)}; + } else { + return TFuncI{[l, r](const SymTable& symtab) { + return EvalIntExpr(symtab, l) ^ EvalIntExpr(symtab, r); + }}; + } + } + case "&"_ophash: + { + if (!IsConvertibleToInt(symtab, l)) { + PE::Throw("expected a left operand of type 'bool' or 'int'", t.lc); + } + + Expr r = RunWithPrecedence(symtab, tok, kBitAndPrec, f); + + if (!IsConvertibleToInt(symtab, r)) { + PE::Throw("expected a right operand of type 'bool' or 'int'", t.lc); + } + + if (IsConstExpr(symtab, l) && IsConstExpr(symtab, r)) { + return TInt{EvalIntExpr(symtab, l) & EvalIntExpr(symtab, r)}; + } else { + return TFuncI{[l, r](const SymTable& symtab) { + return EvalIntExpr(symtab, l) & EvalIntExpr(symtab, r); + }}; + } + } + case "=="_ophash: + { + Expr r = RunWithPrecedence(symtab, tok, kEqPrec, f); + + if (IsConvertibleToInt(symtab, l) + && IsConvertibleToInt(symtab, r)) { + if (IsConstExpr(symtab, l) && IsConstExpr(symtab, r)) { + return TBool{EvalIntExpr(symtab, l) == EvalIntExpr(symtab, r)}; + } else { + return TFuncB{[l, r](const SymTable& symtab) { + return EvalIntExpr(symtab, l) == EvalIntExpr(symtab, r); + }}; + } + } else { + if (IsConstExpr(symtab, l) && IsConstExpr(symtab, r)) { + return TBool{EvalExpr(symtab, l) == EvalExpr(symtab, r)}; + } else { + return TFuncB{[l, r](const SymTable& symtab) { + return EvalExpr(symtab, l) == EvalExpr(symtab, r); + }}; + } + } + } + case "!="_ophash: + { + Expr r = RunWithPrecedence(symtab, tok, kNotEqPrec, f); + + if (IsConvertibleToInt(symtab, l) + && IsConvertibleToInt(symtab, r)) { + if (IsConstExpr(symtab, l) && IsConstExpr(symtab, r)) { + return TBool{EvalIntExpr(symtab, l) != EvalIntExpr(symtab, r)}; + } else { + return TFuncB{[l, r](const SymTable& symtab) { + return EvalIntExpr(symtab, l) != EvalIntExpr(symtab, r); + }}; + } + } else { + if (IsConstExpr(symtab, l) && IsConstExpr(symtab, r)) { + return TBool{EvalExpr(symtab, l) != EvalExpr(symtab, r)}; + } else { + return TFuncB{[l, r](const SymTable& symtab) { + return EvalExpr(symtab, l) != EvalExpr(symtab, r); + }}; + } + } + } + case "<"_ophash: + { + Expr r = RunWithPrecedence(symtab, tok, kLessPrec, f); + + if (IsConvertibleToInt(symtab, l) + && IsConvertibleToInt(symtab, r)) { + if (IsConstExpr(symtab, l) && IsConstExpr(symtab, r)) { + return TBool{EvalIntExpr(symtab, l) < EvalIntExpr(symtab, r)}; + } else { + return TFuncB{[l, r](const SymTable& symtab) { + return EvalIntExpr(symtab, l) < EvalIntExpr(symtab, r); + }}; + } + } else { + if (IsConstExpr(symtab, l) && IsConstExpr(symtab, r)) { + return TBool{EvalExpr(symtab, l) < EvalExpr(symtab, r)}; + } else { + return TFuncB{[l, r](const SymTable& symtab) { + return EvalExpr(symtab, l) < EvalExpr(symtab, r); + }}; + } + } + } + case "<="_ophash: + { + Expr r = RunWithPrecedence(symtab, tok, kLessEqPrec, f); + + if (IsConvertibleToInt(symtab, l) + && IsConvertibleToInt(symtab, r)) { + if (IsConstExpr(symtab, l) && IsConstExpr(symtab, r)) { + return TBool{EvalIntExpr(symtab, l) <= EvalIntExpr(symtab, r)}; + } else { + return TFuncB{[l, r](const SymTable& symtab) { + return EvalIntExpr(symtab, l) <= EvalIntExpr(symtab, r); + }}; + } + } else { + if (IsConstExpr(symtab, l) && IsConstExpr(symtab, r)) { + return TBool{EvalExpr(symtab, l) <= EvalExpr(symtab, r)}; + } else { + return TFuncB{[l, r](const SymTable& symtab) { + return EvalExpr(symtab, l) <= EvalExpr(symtab, r); + }}; + } + } + } + case ">"_ophash: + { + Expr r = RunWithPrecedence(symtab, tok, kGreaterPrec, f); + + if (IsConvertibleToInt(symtab, l) + && IsConvertibleToInt(symtab, r)) { + if (IsConstExpr(symtab, l) && IsConstExpr(symtab, r)) { + return TBool{EvalIntExpr(symtab, l) > EvalIntExpr(symtab, r)}; + } else { + return TFuncB{[l, r](const SymTable& symtab) { + return EvalIntExpr(symtab, l) > EvalIntExpr(symtab, r); + }}; + } + } else { + if (IsConstExpr(symtab, l) && IsConstExpr(symtab, r)) { + return TBool{EvalExpr(symtab, l) > EvalExpr(symtab, r)}; + } else { + return TFuncB{[l, r](const SymTable& symtab) { + return EvalExpr(symtab, l) > EvalExpr(symtab, r); + }}; + } + } + } + case ">="_ophash: + { + Expr r = RunWithPrecedence(symtab, tok, kGreaterEqPrec, f); + + if (IsConvertibleToInt(symtab, l) + && IsConvertibleToInt(symtab, r)) { + if (IsConstExpr(symtab, l) && IsConstExpr(symtab, r)) { + return TBool{EvalIntExpr(symtab, l) >= EvalIntExpr(symtab, r)}; + } else { + return TFuncB{[l, r](const SymTable& symtab) { + return EvalIntExpr(symtab, l) >= EvalIntExpr(symtab, r); + }}; + } + } else { + if (IsConstExpr(symtab, l) && IsConstExpr(symtab, r)) { + return TBool{EvalExpr(symtab, l) >= EvalExpr(symtab, r)}; + } else { + return TFuncB{[l, r](const SymTable& symtab) { + return EvalExpr(symtab, l) >= EvalExpr(symtab, r); + }}; + } + } + } + case "<<"_ophash: + { + if (!IsConvertibleToInt(symtab, l)) { + PE::Throw("expected a left operand of type 'bool' or 'int'", t.lc); + } + + Expr r = RunWithPrecedence(symtab, tok, kBitLeftShiftPrec, f); + + if (!IsConvertibleToInt(symtab, r)) { + PE::Throw("expected a right operand of type 'bool' or 'int'", t.lc); + } + + if (IsConstExpr(symtab, l) && IsConstExpr(symtab, r)) { + return TInt{EvalIntExpr(symtab, l) << EvalIntExpr(symtab, r)}; + } else { + return TFuncI{[l, r](const SymTable& symtab) { + auto val = EvalIntExpr(symtab, l); + return TruncateMea(symtab, l, val) << EvalIntExpr(symtab, r); + }}; + } + } + case ">>"_ophash: + { + if (!IsConvertibleToInt(symtab, l)) { + PE::Throw("expected a left operand of type 'bool' or 'int'", t.lc); + } + + Expr r = RunWithPrecedence(symtab, tok, kBitRightShiftPrec, f); + + if (!IsConvertibleToInt(symtab, r)) { + PE::Throw("expected a right operand of type 'bool' or 'int'", t.lc); + } + + if (IsConstExpr(symtab, l) && IsConstExpr(symtab, r)) { + return TInt{EvalIntExpr(symtab, l) >> EvalIntExpr(symtab, r)}; + } else { + return TFuncI{[l, r](const SymTable& symtab) { + auto val = EvalIntExpr(symtab, l); + return TruncateMea(symtab, l, val) >> EvalIntExpr(symtab, r); + }}; + } + } + case "+"_ophash: + { + Expr r = RunWithPrecedence(symtab, tok, kAddPrec, f); + + if (IsConvertibleToInt(symtab, l) + && IsConvertibleToInt(symtab, r)) { + if (IsConstExpr(symtab, l) && IsConstExpr(symtab, r)) { + return TInt{EvalIntExpr(symtab, l) + EvalIntExpr(symtab, r)}; + } else { + return TFuncI{[l, r](const SymTable& symtab) -> TInt { + return EvalIntExpr(symtab, l) + EvalIntExpr(symtab, r); + }}; + } + } else { + if (IsConstExpr(symtab, l) && IsConstExpr(symtab, r)) { + return TFloat{EvalExpr(symtab, l) + EvalExpr(symtab, r)}; + } else { + return TFuncF{[l, r](const SymTable& symtab) { + return EvalExpr(symtab, l) + EvalExpr(symtab, r); + }}; + } + } + } + case "-"_ophash: + { + Expr r = RunWithPrecedence(symtab, tok, kSubPrec, f); + + if (IsConvertibleToInt(symtab, l) + && IsConvertibleToInt(symtab, r)) { + if (IsConstExpr(symtab, l) && IsConstExpr(symtab, r)) { + return TInt{EvalIntExpr(symtab, l) - EvalIntExpr(symtab, r)}; + } else { + return TFuncI{[l, r](const SymTable& symtab) { + return EvalIntExpr(symtab, l) - EvalIntExpr(symtab, r); + }}; + } + } else { + if (IsConstExpr(symtab, l) && IsConstExpr(symtab, r)) { + return TFloat{EvalExpr(symtab, l) - EvalExpr(symtab, r)}; + } else { + return TFuncF{[l, r](const SymTable& symtab) { + return EvalExpr(symtab, l) - EvalExpr(symtab, r); + }}; + } + } + } + case "*"_ophash: + { + Expr r = RunWithPrecedence(symtab, tok, kMulPrec, f); + + if (IsConvertibleToInt(symtab, l) + && IsConvertibleToInt(symtab, r)) { + if (IsConstExpr(symtab, l) && IsConstExpr(symtab, r)) { + return TInt{EvalIntExpr(symtab, l) * EvalIntExpr(symtab, r)}; + } else { + return TFuncI{[l, r](const SymTable& symtab) { + return EvalIntExpr(symtab, l) * EvalIntExpr(symtab, r); + }}; + } + } else { + if (IsConstExpr(symtab, l) && IsConstExpr(symtab, r)) { + return TFloat{EvalExpr(symtab, l) * EvalExpr(symtab, r)}; + } else { + return TFuncF{[l, r](const SymTable& symtab) { + return EvalExpr(symtab, l) * EvalExpr(symtab, r); + }}; + } + } + } + case "/"_ophash: + { + auto lc = t.lc; + Expr r = RunWithPrecedence(symtab, tok, kDivPrec, f); + + if (IsConvertibleToInt(symtab, l) + && IsConvertibleToInt(symtab, r)) { + if (IsConstExpr(symtab, l) && IsConstExpr(symtab, r)) { + auto divisor = EvalIntExpr(symtab, r); + if (divisor == 0) { + PE::Throw("division by zero", lc); + } + return TInt{EvalIntExpr(symtab, l) / divisor}; + } else { + return TFuncI{[l, r, lc](const SymTable& symtab) { + auto divisor = EvalIntExpr(symtab, r); + if (divisor == 0) { + RE::Throw("division by zero", lc); + } + return EvalIntExpr(symtab, l) / divisor; + }}; + } + } else { + if (IsConstExpr(symtab, l) && IsConstExpr(symtab, r)) { + auto divisor = EvalExpr(symtab, r); + if (divisor == 0.0) { + PE::Throw("division by zero", lc); + } + return TFloat{EvalExpr(symtab, l) / divisor}; + } else { + return TFuncF{[l, r, lc](const SymTable& symtab) { + auto divisor = EvalExpr(symtab, r); + if (divisor == 0.0) { + RE::Throw("division by zero", lc); + } + return EvalExpr(symtab, l) / divisor; + }}; + } + } + } + case "%"_ophash: + { + if (!IsConvertibleToInt(symtab, l)) { + PE::Throw("expected a left operand of type 'bool' or 'int'", t.lc); + } + + Expr r = RunWithPrecedence(symtab, tok, kModPrec, f); + + if (!IsConvertibleToInt(symtab, r)) { + PE::Throw("expected a right operand of type 'bool' or 'int'", t.lc); + } + + auto lc = t.lc; + + if (IsConstExpr(symtab, l) && IsConstExpr(symtab, r)) { + auto divisor = EvalIntExpr(symtab, r); + if (divisor == 0) { + PE::Throw("division by zero", lc); + } + return TInt{EvalIntExpr(symtab, l) % divisor}; + } else { + return TFuncI{[l, r, lc](const SymTable& symtab) { + auto divisor = EvalIntExpr(symtab, r); + if (divisor == 0) { + RE::Throw("division by zero", lc); + } + return EvalIntExpr(symtab, l) % divisor; + }}; + } + } + case "**"_ophash: + { + if constexpr (int_only) { + PE::Throw( + "the operator ** is defined only for float expressions", t.lc); + } + + Expr r = RunWithPrecedence(symtab, tok, kPowPrec, f); + + if (IsConstExpr(symtab, l) && IsConstExpr(symtab, r)) { + return TFloat{std::pow(EvalExpr(symtab, l), EvalExpr(symtab, r))}; + } else { + return TFuncF{[l, r](const SymTable& symtab) { + return std::pow(EvalExpr(symtab, l), EvalExpr(symtab, r)); + }}; + } + } + default: + PE::Throw("unexpected operator", t.lc); + return 0.0; + } + } + + static TInt TruncateMea(const SymTable& symtab, const Expr& l, TInt val) { + if (IsSymbol(l)) { + auto name = std::get(l); + const auto* sym = symtab.Lookup(name); + + if (sym == nullptr) { + Error::Throw("identifier '{}' is not defined", name); + } + + if (sym->HoldsMea()) { + val &= (Symbol::Int{1} << sym->Size()) - 1; + } + } else if (IsSymbolInd(l)) { + const auto& p = std::get(l); + const auto* sym = symtab.Lookup(p.first); + + if (sym == nullptr) { + Error::Throw("identifier '{}' is not defined", p.first); + } + + if (sym->HoldsMea()) { + val &= (Symbol::Int{1} << IndexSize(p.second)) - 1; + } + } + + return val; + } +}; + +/** + * Parses bracketed index expressions (`[ ]`). + * @tparam ParserError Error policy for syntax errors. + * @tparam RuntimeError Error policy for runtime errors. + */ +template +struct IndexParser { + private: + using PE = ParserError; + using RE = RuntimeError; + + using IntExprParser = ExprParser; + + public: + /** + * Consumes a opening bracket '[', parses an integer expression, and + * consumes ']'. + * @param symtab Symbol table instance used for symbol lookups. + * @param tok Tokenizer stream positioned at '['. + * @return Index variant containing a constant scalar or integer evaluation + * closure (`TFuncI`/`TFuncB`). + * @throws syntax_error If the bracket syntax is malformed or + * if the expression is non-integer. + */ + static Index Run(const SymTable& symtab, Tokenizer& tok) { + Token n = tok(); // Consume '['. + n = tok.Peek(); + + auto e = IntExprParser::Run(symtab, tok); + + auto f = [&symtab, &n](auto&& e) -> Index { + using E = std::decay_t; + + if constexpr (std::is_same_v) { + return e; + } else if constexpr (std::is_same_v) { + return e; + } else if constexpr (std::is_same_v) { + return e; + } else if constexpr (std::is_same_v) { + return e; + } else if constexpr (std::is_same_v) { + const auto* sym = symtab.LookupOrError( + e, "identifier '{}' is not defined", e); + if (!sym->IsConvertibleToInt()) { + PE::Throw("index must be an integer expression", n.lc); + } + return TFuncI{[sym](const SymTable& symtab) { + return sym->GetInt(); + }}; + } else if constexpr (std::is_same_v) { + const auto* sym = symtab.LookupOrError( + e.first, "identifier '{}' is not defined", e.first); + if (!sym->IsConvertibleToInt()) { + PE::Throw("index must be an integer expression", n.lc); + } + return TFuncI{[sym, e](const SymTable& symtab) { + return sym->GetInt(EvalIndex(symtab, e.second)); + }}; + } else { + PE::Throw("index must be an integer expression", n.lc); + return TInt{0}; + } + }; + + n = tok(); + if (n.val != "]") { + PE::Throw("expected ']'", n.lc); + } + + return std::visit(f, e); + } +}; + +} // namespace qsim::cc + +#endif // CLASSICAL_CONTROL_PARSER_H_ diff --git a/lib/classical_control_symbol.h b/lib/classical_control_symbol.h new file mode 100644 index 000000000..1cc6898a7 --- /dev/null +++ b/lib/classical_control_symbol.h @@ -0,0 +1,425 @@ +// Copyright 2026 Google LLC. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CLASSICAL_CONTROL_SYMBOL_H_ +#define CLASSICAL_CONTROL_SYMBOL_H_ + +#include +#include +#include +#include +#include +#include + +#include "classical_control_expr.h" +#include "error.h" + +namespace qsim::cc { + +/** + * Represents a typed value container holding scalars, vectors, or quantum + * measurements. A Symbol encapsulates values used in quantum circuit + * simulation or classical computation, supporting typed access, indexing, + * conversions, assignment and and read-only flags. + */ +struct Symbol { + using Int = TInt; + using Float = TFloat; + using IntVector = std::vector; + using FloatVector = std::vector; + + /** + * Holds the result of a quantum measurement over a specified set of qubits. + */ + struct Mea { + /** Bitfield storing measurement outcomes. */ + uint64_t bits = 0; + /** Number of measured qubits. */ + unsigned num_bits = 0; + /** Circuit moment/time tag when the measurement occurred. */ + unsigned time = 0; + }; + + /** Variant over all supported concrete value types. */ + using Val = std::variant; + + /** Constructs an uninitialized/default Symbol. */ + Symbol() {} + + /** + * Constructs a Symbol inferring read-only status based on the value type. + * Mea instances default to read_only = false; all other types default to + * read_only = true. + * @tparam V Forwarded value type. + * @param v Value to store in the variant. + */ + template + Symbol(V&& v) : val(std::forward(v)) { + if constexpr (std::is_same_v, Mea>) { + read_only = false; + } + } + + /** + * Constructs a Symbol with an explicit read-only specification. + * @tparam V Forwarded value type. + * @param v Value to store in the variant. + * @param read_only Whether the symbol should be treated as + * constant/read-only. + */ + template + Symbol(V&& v, bool read_only) + : val(std::forward(v)), read_only(read_only) {} + + /** Checks if the underlying value is a quantum measurement (Mea). */ + bool HoldsMea() const { + return std::holds_alternative(val); + } + + /** + * Checks if the underlying value is a vector type (IntVector or + * FloatVector). + */ + bool HoldsVector() const { + return std::holds_alternative(val) + || std::holds_alternative(val); + } + + /** + * Checks if the underlying value uses floating-point precision (Float or + * FloatVector). + */ + bool IsFloat() const { + return std::holds_alternative(val) + || std::holds_alternative(val); + } + + /** + * Checks if the underlying value can be implicitly or explicitly converted + * to Int. + */ + bool IsConvertibleToInt() const { + return std::holds_alternative(val) || std::holds_alternative(val) + || std::holds_alternative(val); + } + + /** Returns whether this symbol is marked read-only. */ + bool IsReadOnly() const { + return read_only; + } + + /** + * Returns a mutable reference to the underlying Mea object. + * @throws std::bad_variant_access if variant does not hold a Mea. + */ + Mea& GetMea() { + return std::get(val); + } + + /** + * Returns a const reference to the underlying Mea object. + * @throws std::bad_variant_access if variant does not hold a Mea. + */ + const Mea& GetMea() const { + return std::get(val); + } + + /** + * Returns the size or element count of the stored value. + * @return Number of bits for Mea, number of elements for vectors, + * or 1 for scalars. + */ + unsigned Size() const { + auto f = [this](auto&& v) -> unsigned { + using V = std::decay_t; + + if constexpr (std::is_same_v) { + return v.num_bits; + } else if constexpr (std::is_same_v) { + return 1; + } else if constexpr (std::is_same_v) { + return 1; + } else if constexpr (std::is_same_v) { + return v.size(); + } else if constexpr (std::is_same_v) { + return v.size(); + } + }; + + return std::visit(f, val); + } + + /** + * @return The underlying integer value or raw bitfield of a measurement. + * @throws std::runtime_error If the stored value is a Float or Vector. + */ + Int GetInt() const { + auto f = [this](auto&& v) -> Int { + using V = std::decay_t; + + if constexpr (std::is_same_v) { + return GetMeaInt(v); + } else if constexpr (std::is_same_v) { + return v; + } else if constexpr (std::is_same_v) { + Error::Throw("'float' is not convertible to 'int'"); + return 0; + } else { + Error::Throw("'vector' is not convertible to 'int'"); + return 0; + } + }; + + return std::visit(f, val); + } + + /** + * @return The scalar float value or integer promoted to float. + * @throws std::runtime_error If the stored value is a Vector. + */ + Float GetFloat() const { + auto f = [this](auto&& v) -> Float { + using V = std::decay_t; + + if constexpr (std::is_same_v) { + return GetMeaInt(v); + } else if constexpr (std::is_same_v) { + return v; + } else if constexpr (std::is_same_v) { + return v; + } else { + Error::Throw("'vector' is not convertible to 'int'"); + return 0.0; + } + }; + + return std::visit(f, val); + } + + /** + * Fetches an integer value at index i. + * @param i Zero-based index position. + * @return The bit at i for Mea, or element at i for IntVector. + * @throws std::runtime_error If out of range, or called on FloatVector + * or non-indexable scalars. + */ + Int GetInt(std::size_t i) const { + auto f = [this, &i](auto&& v) -> Int { + using V = std::decay_t; + + if constexpr (std::is_same_v) { + return GetMeaInt(v, i); + } else if constexpr (std::is_same_v) { + if (i >= v.size()) { + Error::Throw("index {} is out of range", i); + } + return v[i]; + } else if constexpr (std::is_same_v) { + Error::Throw("'float' is not convertible to 'int'"); + return 0; + } else { + Error::Throw("scalar identifier cannot be indexed"); + return 0; + } + }; + + return std::visit(f, val); + } + + /** + * Fetches a float value at index i. + * @param i Zero-based index position. + * @return Value at i promoted/converted to Float. + * @throws std::runtime_error If out of range or called on non-indexable + * scalars. + */ + Float GetFloat(std::size_t i) const { + auto f = [this, &i](auto&& v) -> Float { + using V = std::decay_t; + + if constexpr (std::is_same_v) { + return GetMeaInt(v, i); + } else if constexpr (std::is_same_v) { + if (i >= v.size()) { + Error::Throw("index {} is out of range", i); + } + return v[i]; + } else if constexpr (std::is_same_v) { + if (i >= v.size()) { + Error::Throw("index {} is out of range", i); + } + return v[i]; + } else { + Error::Throw("scalar identifier cannot be indexed"); + return 0.0; + } + }; + + return std::visit(f, val); + } + + /** + * Extracts the raw bitfield representation of a measurement outcome. + * @param mea Measurement object. + * @return Full bitfield value as Int. + */ + static Int GetMeaInt(const Mea& mea) { + return mea.bits; + } + + /** + * Extracts a single bit from a measurement outcome at position i. + * @param mea Measurement object. + * @param i Zero-based bit position index. + * @return 0 or 1 corresponding to the target bit state. + * @throws std::runtime_error If i is out of range (`i >= mea.num_bits`). + */ + static Int GetMeaInt(const Mea& mea, std::size_t i) { + if (i >= mea.num_bits) { + Error::Throw("index {} is out of range", i); + } + + return (mea.bits >> i) & uint64_t{1}; + } + + /** + * Assigns a sequence of evaluated expressions to the symbol at runtime. + * For scalar types (`Int` or `Float`), assigns the first expression `es[0]`. + * For vector types (`IntVector` or `FloatVector`), evaluates and updates + * elements element-wise up to `std::min(es.size(), val.size())`. + * @tparam SymTable Symbol table type used for evaluating expressions. + * @param symtab Symbol table instance used for symbol lookups. + * @param es Vector of expressions to evaluate and assign. + * @throws std::runtime_error If trying to assign a float to an integer + * symbol/vector or if attempting to assign to a `Mea` symbol. + */ + template + void Assign(const SymTable& symtab, const std::vector& es) { + auto f = [this, &symtab, &es](auto&& val) { + using V = std::decay_t; + + if constexpr (std::is_same_v) { + if (!cc::IsConvertibleToInt(symtab, es[0])) { + Error::Throw("'float' cannot be assigned to an integer identifer"); + } + val = EvalIntExpr(symtab, es[0]); + } else if constexpr (std::is_same_v) { + val = EvalExpr(symtab, es[0]); + } else if constexpr (std::is_same_v) { + auto size = std::min(es.size(), val.size()); + for (unsigned i = 0; i < size; ++i) { + if (!cc::IsConvertibleToInt(symtab, es[i])) { + Error::Throw("'float' cannot be assigned to an integer identifer"); + } + val[i] = EvalIntExpr(symtab, es[i]); + } + } else if constexpr (std::is_same_v) { + auto size = std::min(es.size(), val.size()); + for (unsigned i = 0; i < size; ++i) { + val[i] = EvalExpr(symtab, es[i]); + } + } else { + Error::Throw("cannot assign to a measurement identifier"); + } + }; + + std::visit(f, val); + } + + /** + * Assigns an evaluated expression to a specific indexed element of a vector. + * Evaluates the index expression first to determine the target position, + * then evaluates the provided expression e and assigns it to `IntVector[i]` + * or `FloatVector[i]`. + * @tparam SymTable Symbol table type used for evaluation. + * @param symtab Symbol table instance used for symbol lookups. + * @param e Expression to evaluate and assigne. + * @param index Target index expression. + * @throws std::runtime_error If the evaluated index is out of bounds, + * if assigning float to an integer vector, or if called on non-indexable + * scalars, or if attempting to assign to a `Mea` symbol. + */ + template + void Assign(const SymTable& symtab, const Expr& e, const Index& index) { + std::size_t i = EvalIndex(symtab, index); + + auto g = [this, &symtab, &e, i](auto&& val) { + using V = std::decay_t; + + if constexpr (std::is_same_v) { + if (!cc::IsConvertibleToInt(symtab, e)) { + Error::Throw("'float' cannot be assigned to an integer identifer"); + } + + if (i >= val.size()) { + Error::Throw("index {} is out of range", i); + } + + val[i] = EvalIntExpr(symtab, e); + } else if constexpr (std::is_same_v) { + if (i >= val.size()) { + Error::Throw("index {} is out of range", i); + } + + val[i] = EvalExpr(symtab, e); + } else { + Error::Throw("cannot assign to a measurement identifier"); + } + }; + + std::visit(g, val); + } + + /** + * Replaces the stored scalar Int value. + * @note Caller must ensure the variant currently holds an Int. + */ + void Assign(Int v) { + std::get(val) = v; + } + + /** + * Replaces the stored scalar Float value. + * @note Caller must ensure the variant currently holds a Float. + */ + void Assign(Float v) { + std::get(val) = v; + } + + /** + * Mutates an element at index i in an IntVector. + * @note Caller must ensure variant currently holds an IntVector and i + * is valid. + */ + void Assign(Int v, std::size_t i) { + std::get(val)[i] = v; + } + + /** + * Mutates an element at index @p i in a FloatVector. + * @note Caller must ensure variant currently holds a FloatVector and i + * is valid. + */ + void Assign(Float v, std::size_t i) { + std::get(val)[i] = v; + } + + private: + Val val; + bool read_only = true; +}; + +} // namespace qsim::cc + +#endif // CLASSICAL_CONTROL_SYMBOL_H_ diff --git a/lib/classical_control_symtab.h b/lib/classical_control_symtab.h new file mode 100644 index 000000000..83c4f3fcb --- /dev/null +++ b/lib/classical_control_symtab.h @@ -0,0 +1,266 @@ +// Copyright 2026 Google LLC. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CLASSICAL_CONTROL_SYMTAB_H_ +#define CLASSICAL_CONTROL_SYMTAB_H_ + +#include +#include +#include +#include + +#include "classical_control_expr.h" +#include "error.h" + +namespace qsim::cc { + +/** + * Manages scoped symbols for the quantum circuit parser and interpreter. + * Maintains a collection of scope tables (`symtab`) and an active call stack + * (`path`). Scopes are created during parsing and later re-entered during + * interpretation in the same topological sequence. + */ +struct SymTable { + /** Constructs an empty symbol table with pre-allocated storage. */ + SymTable() { + symtab.reserve(1024); + path.reserve(32); + } + + /** + * Creates a new scope and returns its global index. + * @return The 0-based index of the newly created scope. + */ + std::size_t AddScope() { + symtab.emplace_back(); + return symtab.size() - 1; + } + + /** + * Enters the scope by pushing a scope index onto the active scope path. + * @param k The scope index returned previously by AddScope(). + * @throws std::runtime_error If k is out of range. + */ + void EnterScope(std::size_t k) { + if (k >= symtab.size()) { + Error::Throw("the scope index {} is too large", k); + return; + } + + path.push_back(k); + } + + /** Exits the current active scope by popping it from the scope path. */ + void ExitScope() { + if (path.size() > 0) { + path.pop_back(); + } + } + + /** + * Checks whether any scopes have been created. + * @return True if no scopes exist in the table. + */ + bool Empty() const { + return symtab.empty(); + } + + /** + * Inserts or retrieves a symbol in the current active scope. + * If a symbol with name already exists in the current scope, the existing + * symbol pointer is returned. Otherwise, sym is moved into place. + * @param name Name identifier for the symbol. + * @param sym Symbol rvalue instance to insert. + * @return Pointer to the inserted or existing Symbol in the current scope. + */ + Symbol* Insert(std::string_view name, Symbol&& sym) { + auto& cur = symtab[path.back()]; + + if (auto s = cur.find(name); s != cur.end()) { + return &s->second; + } + + return &(cur[name] = std::move(sym)); + } + + /** + * Searches for a symbol up the scope chain. + * @param name Symbol name to search for. + * @return Pointer to matching Symbol, or nullptr if not found. + */ + Symbol* Lookup(std::string_view name) { + for (auto it = path.rbegin(); it != path.rend(); ++it) { + auto& symbols = symtab[*it]; + if (auto s = symbols.find(name); s != symbols.end()) { + return &s->second; + } + } + + return nullptr; + } + + /** Const version of Lookup. */ + const Symbol* Lookup(std::string_view name) const { + for (auto it = path.rbegin(); it != path.rend(); ++it) { + const auto& symbols = symtab[*it]; + if (auto s = symbols.find(name); s != symbols.end()) { + return &s->second; + } + } + + return nullptr; + } + + /** + * Searches for a symbol exclusively in the current active scope. + * @param name Symbol name to search for. + * @return Pointer to matching Symbol in current scope, or nullptr if + * not found. + */ + Symbol* LookupInCurrentScope(std::string_view name) { + if (!path.empty()) { + auto& symbols = symtab[path.back()]; + if (auto s = symbols.find(name); s != symbols.end()) { + return &s->second; + } + } + + return nullptr; + } + + /** Const version of LookupInCurrentScope. */ + const Symbol* LookupInCurrentScope(std::string_view name) const { + if (!path.empty()) { + const auto& symbols = symtab[path.back()]; + if (auto s = symbols.find(name); s != symbols.end()) { + return &s->second; + } + } + + return nullptr; + } + + /** + * Searches for a symbol in the parent/enclosing scope (one level up path). + * @param name Symbol name to search for. + * @return Pointer to matching Symbol in previous scope, or nullptr + * if not found. + */ + Symbol* LookupInPreviousScope(std::string_view name) { + if (path.size() > 1) { + auto& symbols = symtab[path[path.size() - 2]]; + if (auto s = symbols.find(name); s != symbols.end()) { + return &s->second; + } + } + + return nullptr; + } + + /** Const version of LookupInPreviousScope. */ + const Symbol* LookupInPreviousScope(std::string_view name) const { + if (path.size() > 1) { + const auto& symbols = symtab[path[path.size() - 2]]; + if (auto s = symbols.find(name); s != symbols.end()) { + return &s->second; + } + } + + return nullptr; + } + + /** + * Searches for a symbol up the scope chain or throw an error if not found. + * @tparam Args Format string argument types. + * @param name Symbol name to search for. + * @param message Exception message (format) string. + * @param args Arguments passed to Error::Throw. + * @return Pointer to matching Symbol. + * @throws std::runtime_error If symbol is not found. + */ + template + Symbol* LookupOrError( + std::string_view name, std::string_view message, Args&&... args) { + for (auto it = path.rbegin(); it != path.rend(); ++it) { + auto& symbols = symtab[*it]; + if (auto s = symbols.find(name); s != symbols.end()) { + return &s->second; + } + } + + Error::Throw(message, args...); + return nullptr; + } + + /** Const version of LookupOrError. */ + template + const Symbol* LookupOrError( + std::string_view name, std::string_view message, Args&&... args) const { + for (auto it = path.rbegin(); it != path.rend(); ++it) { + const auto& symbols = symtab[*it]; + if (auto s = symbols.find(name); s != symbols.end()) { + return &s->second; + } + } + + Error::Throw(message, args...); + return nullptr; + } + + /** + * Searches for a symbol in the current scope or throw an error if not found. + * @tparam Args Format string argument types. + * @param name Symbol name to search for. + * @param message Exception message (format) string. + * @param args Arguments passed to Error::Throw. + * @return Pointer to matching Symbol in current scope. + * @throws std::runtime_error If symbol is not found. + */ + template + Symbol* LookupInCurrentScopeOrError( + std::string_view name, std::string_view message, Args&&... args) { + if (!path.empty()) { + auto& symbols = symtab[path.back()]; + if (auto s = symbols.find(name); s != symbols.end()) { + return &s->second; + } + } + + Error::Throw(message, args...); + return nullptr; + } + + /** Const version of LookupInCurrentScopeOrError. */ + template + const Symbol* LookupInCurrentScopeOrError( + std::string_view name, std::string_view message, Args&&... args) const { + if (!path.empty()) { + const auto& symbols = symtab[path.back()]; + if (auto s = symbols.find(name); s != symbols.end()) { + return &s->second; + } + } + + Error::Throw(message, args...); + return nullptr; + } + + private: + std::vector> symtab; + std::vector path; +}; + +} // namespace qsim::cc + +#endif // CLASSICAL_CONTROL_SYMTAB_H_ diff --git a/lib/classical_control_tokenizer.h b/lib/classical_control_tokenizer.h new file mode 100644 index 000000000..8d65e9ec2 --- /dev/null +++ b/lib/classical_control_tokenizer.h @@ -0,0 +1,364 @@ +// Copyright 2026 Google LLC. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CLASSICAL_CONTROL_TOKENIZER_H_ +#define CLASSICAL_CONTROL_TOKENIZER_H_ + +#include +#include +#include +#include +#include + +#include "classical_control_util.h" + +namespace qsim::cc { + +/** Represents a token produced by the \c Tokenizer. */ +struct Token { + /** Defines the syntactic classification of a token. */ + enum TokenKind { + /** Alphanumeric identifiers (e.g., `var_1`). */ + kIdentifier, + /** Decimal (`42`), hexadecimal (`0x1a`), or binary (`0b01`) integers. */ + kInteger, + /** Floating-point numbers (e.g., `301.34`, `1.2e-5`). */ + kFloat, + /** Single (`'...'`) or double (`"..."`) quoted string literals. */ + kString, + /** Single and two-character operators (e.g., `+`, `==`, `<<`). */ + kOperator, + /** Parentheses `()` and square brackets `[]`. */ + kBracket, + /** Semicolons `;` or newline characters `\n`. */ + kDelimiter, + /** Indicates end-of-string or file termination. */ + kEndOfFile, + /** Malformed token or unrecognized character stream. */ + kInvalid, + }; + + TokenKind kind; + unsigned lc; // Line and column. + std::string_view val; +}; + +/** A stateful lexical analyzer for tokenizing source text. */ +struct Tokenizer { + /** + * Constructs a tokenizer for the provided string view. + * @param content The source code buffer to tokenize. + */ + explicit Tokenizer(std::string_view content) : content(content) {} + + /** Resets the tokenizer back to the start of the source buffer. */ + void Restart() { + buffer.clear(); + last = Token{}; + pos = 0; + pos0 = 0; + line = 0; + } + + /** + * Inspects upcoming tokens without advancing the current reader position. + * @param distance Zero-based index of lookahead (0 = next token to be + * consumed). + * @return The token at the lookahead position. + */ + Token Peek(std::size_t distance = 0) { + while (buffer.size() <= distance) { + Token t = Next(); + buffer.push_back(t); + if (t.kind == Token::kEndOfFile) { + break; + } + } + + if (distance >= buffer.size()) { + return {Token::kEndOfFile, {}}; + } + + return buffer[distance]; + } + + /** + * Advances the stream and returns the next consumed token. + * @return The next \c Token in the stream. + */ + Token operator()() { + if (!buffer.empty()) { + Token t = buffer.front(); + buffer.pop_front(); + last = t; + return t; + } + + Token t = Next(); + last = t; + return t; + } + + /** + * Retrieves the most recently consumed token. + * @return Reference to the current token. + */ + const Token& Current() { + return last; + } + + /** + * Packs zero-based line and column numbers into a single 32-bit integer. + * @param line Zero-based line index (upper 20 bits). + * @param col Zero-based column index (lower 12 bits, max 4095). + * @return The packed 32-bit unsigned integer. + */ + static unsigned PackLineAndColumn(unsigned line, unsigned col) { + return (line << 12) | (col & ((1u << 12) - 1)); + } + + /** + * Decodes a packed line-and-column integer into a pair. + * @param lc Packed integer containing line and column information. + * @return A pair formatted as `{line, column}`. + */ + static std::pair UnpackLineAndColumn(unsigned lc) { + return {lc >> 12, lc & ((1u << 12) - 1)}; + } + +private: + std::string_view content; + std::deque buffer; + Token last; + std::size_t pos = 0; + std::size_t pos0 = 0; + unsigned line = 0; + + static bool IsIdentifierStart(char c) { + return std::isalpha(c) || c == '_'; + } + + static bool IsIdentifierBody(char c) { + return std::isalnum(c) || c == '_'; + } + + char PeekChar(std::size_t offset = 0) const { + return pos + offset < content.size() ? content[pos + offset] : '\0'; + } + + void ConsumeDigits() { + while (pos < content.size() && std::isdigit(content[pos])) { + ++pos; + } + } + + void ConsumeHexDigits() { + while (pos < content.size() && std::isxdigit(content[pos])) { + ++pos; + } + } + + void ConsumeBinDigits() { + while (pos < content.size() && isbdigit(content[pos])) { + ++pos; + } + } + + static bool isbdigit(char c) { + return c == '0' || c == '1'; + } + + Token Next() { + while (pos < content.size()) { + char ch = content[pos]; + + if (ch != ' ' && ch != '\t' && ch != '\r') { + break; + } + + ++pos; + } + + if (pos == content.size()) { + unsigned lc = PackLineAndColumn(line, content.size() - pos0); + return {Token::kEndOfFile, lc, {}}; + } + + if (content[pos] == '#') { + while (pos < content.size() && content[pos] != '\n') { + ++pos; + } + + if (pos == content.size()) { + unsigned lc = PackLineAndColumn(line, content.size() - pos0); + return {Token::kEndOfFile, lc, {}}; + } + } + + char cur = content[pos]; + std::size_t start = pos; + + unsigned lc = PackLineAndColumn(line, pos - pos0); + + if (cur == ';') { + ++pos; + return {Token::kDelimiter, lc, content.substr(pos - 1, 1)}; + } + + if (cur == '\n') { + ++line; + ++pos; + pos0 = pos; + return {Token::kDelimiter, lc, content.substr(pos - 1, 1)}; + } + + if (cur == '\'') { + while (++pos < content.size() && content[pos] != '\n' + && content[pos] != '\''); + + if (pos == content.size() || content[pos] == '\n') { + return {Token::kInvalid, lc, content.substr(start, 1)}; + } + + ++pos; + + return + {Token::kString, lc, content.substr(start + 1, pos - start - 2)}; + } + + if (cur == '\"') { + while (++pos < content.size() && content[pos] != '\n' + && content[pos] != '\"'); + + if (pos == content.size() || content[pos] == '\n') { + return {Token::kInvalid, lc, content.substr(start, 1)}; + } + + ++pos; + + return + {Token::kString, lc, content.substr(start + 1, pos - start - 2)}; + } + + if (IsIdentifierStart(cur)) { + while (pos < content.size() && IsIdentifierBody(content[pos])) { + ++pos; + } + return {Token::kIdentifier, lc, content.substr(start, pos - start)}; + } + + if (std::isdigit(cur)) { + char next = PeekChar(1); + + if (cur == '0' && (next == 'x' || next == 'X')) { + pos += 2; + bool is_valid = std::isxdigit(PeekChar(0)); + + ConsumeHexDigits(); + std::string_view val = content.substr(start, pos - start); + + return {is_valid ? Token::kInteger : Token::kInvalid, lc, val}; + } else if (cur == '0' && (next == 'b' || next == 'B')) { + pos += 2; + bool is_valid = isbdigit(PeekChar(0)); + + ConsumeBinDigits(); + std::string_view val = content.substr(start, pos - start); + + return {is_valid ? Token::kInteger : Token::kInvalid, lc, val}; + } else { + bool is_float = false; + + ConsumeDigits(); + + if (PeekChar(0) == '.' && std::isdigit(PeekChar(1))) { + ++pos; + is_float = true; + ConsumeDigits(); + } + + char next = PeekChar(0); + if (next == 'e' || next == 'E') { + ++pos; + next = PeekChar(0); + + if (next == '+' || next == '-') { + ++pos; + next = PeekChar(0); + } + + if (std::isdigit(next)) { + is_float = true; + ConsumeDigits(); + } else { + std::string_view val = content.substr(start, pos - start); + return {Token::kInvalid, lc, val}; + } + } + + std::string_view val = content.substr(start, pos - start); + return {is_float ? Token::kFloat : Token::kInteger, lc, val}; + } + } + + if (pos + 1 < content.size()) { + std::string_view val = content.substr(pos, 2); + + switch (OpHash(val)) { + case "||"_ophash: + case "^^"_ophash: + case "&&"_ophash: + case "=="_ophash: + case "!="_ophash: + case "<="_ophash: + case ">="_ophash: + case "**"_ophash: + case "<<"_ophash: + case ">>"_ophash: + pos += 2; + return {Token::kOperator, lc, val}; + } + } + + static constexpr std::array is_operator = []() { + std::array array = {false}; + for (char c : "-+*/%=<>!~|^&") { + array[static_cast(c)] = true; + } + return array; + }(); + + if (is_operator[cur]) { + return {Token::kOperator, lc, content.substr(pos++, 1)}; + } + + static constexpr std::array is_bracket = []() { + std::array array = {false}; + for (char c : "()[]") { + array[static_cast(c)] = true; + } + return array; + }(); + + if (is_bracket[cur]) { + return {Token::kBracket, lc, content.substr(pos++, 1)}; + } + + return {Token::kInvalid, lc, content.substr(pos++, 1)}; + } +}; + +} // namespace qsim::cc + +#endif // CLASSICAL_CONTROL_TOKENIZER_H_ diff --git a/lib/classical_control_util.h b/lib/classical_control_util.h new file mode 100644 index 000000000..723fe8e62 --- /dev/null +++ b/lib/classical_control_util.h @@ -0,0 +1,294 @@ +// Copyright 2026 Google LLC. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CLASSICAL_CONTROL_UTIL_H_ +#define CLASSICAL_CONTROL_UTIL_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "classical_control_expr.h" +#include "error.h" + +namespace qsim::cc { + +/** + * Parses a string representation of an integer in decimal, hex, or binary + * format. Supports decimal (e.g., `"123"`), hexadecimal (`"0x1a"` or + * `"0X1a"`), and binary (`"0b0101"` or `"0B0101"`) prefixes. + * @param s String view containing the numerical text to convert. + * @return Converted integer as an `int64_t`. + * @throws std::runtime_error If parsing fails or trailing non-numeric + * characters exist. + */ +inline int64_t ToInt(std::string_view s) { + int val; + unsigned base = 10; + + if (s.size() > 2 && s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) { + base = 16; + } else if (s.size() > 2 && s[0] == '0' && (s[1] == 'b' || s[1] == 'B')) { + base = 2; + } + + auto start = base == 10 ? s.data() : s.data() + 2; + + auto [p, ec] = std::from_chars(start, s.data() + s.size(), val, base); + + if (ec != std::errc{} || p != s.data() + s.size()) { + Error::Throw("cannot convert string to int."); + } + + return val; +} + +/** + * Parses a string representation of a floating-point number. + * @param s String view containing standard decimal or exponential + * floating-point text. + * @return Converted floating-point value as a `double`. + * @throws std::runtime_error If parsing fails or trailing non-numeric + * characters exist. + */ +inline double ToFloat(std::string_view s) { + double val; + + auto [p, ec] = std::from_chars(s.data(), s.data() + s.size(), val); + + if (ec != std::errc{} || p != s.data() + s.size()) { + Error::Throw("cannot convert string to int."); + } + + return val; +} + +namespace detail { + +/** + * Computes a compile-time 64-bit FNV-1a hash over a byte string. + * @param s Pointer to char array. + * @param len Length of char sequence. + * @return 64-bit FNV-1a unsigned hash value. + */ +inline constexpr unsigned fnv1a_hash(const char* s, std::size_t len) { + constexpr uint64_t offset_basis = 0xcbf29ce484222325; + constexpr uint64_t prime = 0x100000001b3; + + uint64_t hash = offset_basis; + for (std::size_t i = 0; i < len; ++i) { + hash ^= static_cast(s[i]); + hash *= prime; + } + return hash; +} + +} // namespace detail + +/** + * Computes a compile-time FNV-1a string hash. + * @param s Input string view. + * @return Calculated hash value. + */ +inline constexpr uint64_t Hash(std::string_view s) { + return detail::fnv1a_hash(s.data(), s.size()); +} + +/** + * User-defined literal operator for compile-time string hashing. + * Example usage: `"my_symbol"_hash`. + */ +inline constexpr uint64_t operator""_hash(const char* s, std::size_t len) { + return detail::fnv1a_hash(s, len); +} + +/** + * Packs a 1- or 2-character operator string into a 16-bit integer + * representation. Allows efficient switch-case matching over single- or + * double-character operators (e.g., `"+"`, `"=="`, `">="`). + * @param s Operator string view (must be length 1 or 2). + * @return Packed unsigned integer hash or `0` for invalid lengths. + */ +inline constexpr unsigned short OpHash(std::string_view s) { + switch (s.size()) { + case 1: + return s[0]; + case 2: + return (s[1] << 8) | s[0]; + default: + return 0; + } +} + +/** + * User-defined literal operator for packing operator strings into + * 16-bit hashes. Example usage: `"=="__ophash`. + */ +inline constexpr unsigned short operator""_ophash( + const char* s, std::size_t len) { + switch (len) { + case 1: + return s[0]; + case 2: + return (s[1] << 8) | s[0]; + default: + return 0; + } +} + +/** + * Reads the entire contents of a file on disk into an in-memory std::string + * buffer. + * @param path File system path string. + * @return File content string. + * @throws std::runtime_error If the file cannot be opened or read. + */ +inline std::string ReadFile(std::string_view path) { + std::ifstream file(std::filesystem::path(path), + std::ios::in | std::ios::binary | std::ios::ate); + + if (!file) { + Error::Throw("failed to open {}", path); + } + + std::streamsize size = file.tellg(); + file.seekg(0, std::ios::beg); + + std::string buffer; + buffer.resize(static_cast(size)); + + if (!file.read(buffer.data(), size)) { + Error::Throw("error reading data from {}", path); + } + + return buffer; +} + +/** + * Recursive variadic helper template for formatting expressions into a string. + * @tparam Count Remaining number of expressions to evaluate. + * @tparam Vals Accumulated evaluated expression types (`TInt` or `TFloat`). + */ +template +struct ExprToStr { + /** Formats expressions using an explicit C++ std::format string. */ + template + static std::string Run1(const SymTable& symtab, std::string_view fmt, + const std::vector& es, Vals&&... vs) { + if constexpr (Count == 0) { + return std::vformat(fmt, std::make_format_args(vs...)); + } else { + auto k = es.size() - Count; + + if (IsConvertibleToInt(symtab, es[k])) { + return ExprToStr::Run1( + symtab, fmt, es, std::move(vs)..., EvalIntExpr(symtab, es[k])); + } else { + return ExprToStr::Run1( + symtab, fmt, es, std::move(vs)..., EvalExpr(symtab, es[k])); + } + } + } + + /** + * Formats expressions sequentially with auto-generated space delimiters + * (`"{}"`). + */ + template + static std::string Run2(const SymTable& symtab, std::string&& fmt, + const std::vector& es, Vals&&... vs) { + if constexpr (Count == 0) { + return std::vformat(fmt, std::make_format_args(vs...)); + } else { + auto k = es.size() - Count; + if constexpr (Count > 1) { + fmt += "{} "; + } else { + fmt += "{}"; + } + + if (IsConvertibleToInt(symtab, es[k])) { + return ExprToStr::Run2( + symtab, std::move(fmt), es, std::move(vs)..., + EvalIntExpr(symtab, es[k])); + } else { + return ExprToStr::Run2( + symtab, std::move(fmt), es, std::move(vs)..., + EvalExpr(symtab, es[k])); + } + } + } +}; + +/** + * Evaluates up to 4 expressions and prints the formatted string to standard + * stdout (`puts`). + * @tparam SymTable Active symbol table type. + * @tparam Expr Expression variant type. + * @param symtab Symbol table instance used for symbol lookups. + * @param fmt Optional format string. If empty, expressions are printed + * space-separated. + * @param es Vector of expressions (up to 4 items). + */ +template +inline void PrintExpressions( + const SymTable& symtab, std::string_view fmt, const std::vector& es) { + if (!fmt.empty()) { + switch (es.size()) { + case 0: + puts(ExprToStr<0>::Run1(symtab, fmt, es).c_str()); + break; + case 1: + puts(ExprToStr<1>::Run1(symtab, fmt, es).c_str()); + break; + case 2: + puts(ExprToStr<2>::Run1(symtab, fmt, es).c_str()); + break; + case 3: + puts(ExprToStr<3>::Run1(symtab, fmt, es).c_str()); + break; + case 4: + puts(ExprToStr<4>::Run1(symtab, fmt, es).c_str()); + break; + } + } else { + switch (es.size()) { + case 0: + puts(ExprToStr<0>::Run2(symtab, std::string{}, es).c_str()); + break; + case 1: + puts(ExprToStr<1>::Run2(symtab, std::string{}, es).c_str()); + break; + case 2: + puts(ExprToStr<2>::Run2(symtab, std::string{}, es).c_str()); + break; + case 3: + puts(ExprToStr<3>::Run2(symtab, std::string{}, es).c_str()); + break; + case 4: + puts(ExprToStr<4>::Run2(symtab, std::string{}, es).c_str()); + break; + } + } +} + +} // namespace qsim::cc + +#endif // CLASSICAL_CONTROL_UTIL_H_ diff --git a/lib/error.h b/lib/error.h new file mode 100644 index 000000000..0ff308e7a --- /dev/null +++ b/lib/error.h @@ -0,0 +1,49 @@ +// Copyright 2026 Google LLC. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef ERROR_H_ +#define ERROR_H_ + +#include +#include +#include +#include + +namespace qsim { + +/** Utilities for reporting generic runtime errors. */ +struct Error { + /** + * Formats an error message and throws a std::runtime_error. Applies variadic + * formatting arguments to the message string using std::vformat, and throws + * a std::runtime_error. + * @tparam Args Variadic types of the format arguments. + * @param message Format string explaining the error. + * @param args Formatting arguments to substitute into the message. + * @throws std::runtime_error Always throws with the formatted error string. + */ + template + static void Throw( + const std::string_view message, Args&&... args) { + auto error_message = std::vformat( + std::string("error: ") + std::string(message), + std::make_format_args(args...)); + + throw std::runtime_error(error_message); + } +}; + +} // namespace qsim + +#endif // ERROR_H_ diff --git a/lib/fuser.h b/lib/fuser.h index 96272c3a5..55bda410f 100644 --- a/lib/fuser.h +++ b/lib/fuser.h @@ -151,10 +151,11 @@ inline void CalculateFusedMatrix(FusedGate& gate) { MatrixIdentity(unsigned{1} << gate.qubits.size(), gate.matrix); for (const auto& pgate : gate.gates) { - const auto* pg = OpGetAlternative>(pgate); + const auto* g = OpGetAlternative>(pgate); + const auto* d = OpGetAlternative>(pgate); + const auto* r = OpGetAlternative>(pgate); const auto& pqubits = OpQubits(pgate); - const auto& pmatrix = - pg ? pg->matrix : OpGetAlternative>(pgate)->matrix; + const auto& pmatrix = g ? g->matrix : (d ? d->matrix : r->matrix); if (pqubits.size() == 0) { MatrixScalarMultiply(pmatrix[0], pmatrix[1], gate.matrix); diff --git a/lib/fuser_mqubit.h b/lib/fuser_mqubit.h index d30d788ea..e19dc4332 100644 --- a/lib/fuser_mqubit.h +++ b/lib/fuser_mqubit.h @@ -99,6 +99,7 @@ class MultiQubitGateFuser final : public Fuser { std::vector*> links; // Gate "lattice" links. uint64_t mask; // Qubit mask. unsigned visited; + bool defer_matrix_computation; }; // Possible values for visited in GateF. @@ -289,6 +290,7 @@ class MultiQubitGateFuser final : public Fuser { using Operation = std::remove_pointer_t; using fp_type = OpFpType; using Gate = qsim::Gate; + using RuntimeResolvedGate = qsim::RuntimeResolvedGate; using ControlledGate = qsim::ControlledGate; using FusedGate = qsim::FusedGate; using PGate = typename FusedGate::PGate; @@ -367,12 +369,12 @@ class MultiQubitGateFuser final : public Fuser { // Fill in auxiliary data structures. - if (OpGetAlternative(op)) { + if (const auto* m = OpGetAlternative(op)) { // Measurement gate. if (last_measurement == nullptr || OpTime(*last_measurement->parent) != bop.time) { - gates_seq.push_back({&op, {}, {}, {}, 0, kUnfusible}); + gates_seq.push_back({&op, {}, {}, {}, 0, kUnfusible, false}); last_measurement = &gates_seq.back(); last_measurement->qubits.reserve(max_qubit1); @@ -390,12 +392,13 @@ class MultiQubitGateFuser final : public Fuser { ++stat.num_measurements; } else { - gates_seq.push_back({&op, {}, {}, {}, 0, kZero}); + gates_seq.push_back({&op, {}, {}, {}, 0, kZero, false}); auto& fgate = gates_seq.back(); unsigned num_gate_qubits = bop.qubits.size(); + bool is_dynamic_gate = OpGetAlternative(op); - if (OpGetAlternative(op)) { + if (OpGetAlternative(op) || is_dynamic_gate) { // Matrix gates that are fused. if (max_gate_size < num_gate_qubits) { @@ -408,6 +411,7 @@ class MultiQubitGateFuser final : public Fuser { fgate.links.reserve(size); fgate.gates.reserve(4 * size); fgate.links.reserve(size); + fgate.defer_matrix_computation = is_dynamic_gate; if (fgates[num_gate_qubits].empty()) { fgates[num_gate_qubits].reserve(num_ops); @@ -451,7 +455,7 @@ class MultiQubitGateFuser final : public Fuser { } // Fuse large gates with smaller gates. - FuseGates(max_gate_size, fgates); + FuseGates(max_gate_size, fgates); if (max_fused_size > 2) { FuseGateSequences( @@ -487,10 +491,14 @@ class MultiQubitGateFuser final : public Fuser { AddUnfusible(fgate, fused_ops); } else { // Assume fgate.qubits (gate.qubits) are sorted. - const Gate& parent = *OpGetAlternative(*fgate.parent); - fused_ops.push_back(FusedGate{parent.kind, parent.time, - std::move(fgate.qubits), &parent, - std::move(fgate.gates), {}}); + auto parent = FusedGate::OpToPGate(fgate.parent); + const auto& bop = + OpBaseOperation(Base::OperationToConstRef(*fgate.parent)); + + fused_ops.push_back(FusedGate{bop.kind, bop.time, + std::move(fgate.qubits), parent, + std::move(fgate.gates), {}, + fgate.defer_matrix_computation}); ++stat.num_fused_gates; } @@ -513,7 +521,7 @@ class MultiQubitGateFuser final : public Fuser { if (fuse_matrix) { for (auto& op : fused_ops) { if (auto* pg = OpGetAlternative(op)) { - if (!pg->ParentIsDecomposed()) { + if (!pg->defer_matrix_computation && !pg->ParentIsDecomposed()) { CalculateFusedMatrix(*pg); } } @@ -527,9 +535,11 @@ class MultiQubitGateFuser final : public Fuser { private: // Fuse large gates with smaller gates. - template + template static void FuseGates(uint64_t max_gate_size, std::vector>& fgates) { + using fp_type = typename GateF::fp_type; + // Traverse gates in order of decreasing size. for (uint64_t i = 0; i < max_gate_size; ++i) { std::size_t pos = 0; @@ -547,7 +557,7 @@ class MultiQubitGateFuser final : public Fuser { fgate->visited = kFirst; FusePrev(0, *fgate); - fgate->gates.push_back(OpGetAlternative(*fgate->parent)); + fgate->gates.push_back(FusedGate::OpToPGate(fgate->parent)); FuseNext(0, *fgate); } @@ -571,8 +581,6 @@ class MultiQubitGateFuser final : public Fuser { std::vector& gates_seq, Stat& stat, std::vector& fused_ops) { using FusedGate = std::variant_alternative_t<0, OperationF>; - using fp_type = typename FusedGate::fp_type; - using Gate = qsim::Gate; unsigned prev_time = 0; @@ -600,10 +608,13 @@ class MultiQubitGateFuser final : public Fuser { } if (fgate.qubits.size() >= max_fused_size) { - const Gate& parent = *OpGetAlternative(*fgate.parent); - fused_ops.push_back(FusedGate{parent.kind, parent.time, - std::move(fgate.qubits), &parent, - std::move(fgate.gates), {}}); + auto parent = FusedGate::OpToPGate(fgate.parent); + const auto& bop = + OpBaseOperation(Base::OperationToConstRef(*fgate.parent)); + fused_ops.push_back(FusedGate{bop.kind, bop.time, + std::move(fgate.qubits), parent, + std::move(fgate.gates), {}, + fgate.defer_matrix_computation}); fgate.visited = kFinal; ++stat.num_fused_gates; @@ -629,10 +640,13 @@ class MultiQubitGateFuser final : public Fuser { for (auto fgate : scratch.gates) { std::sort(fgate->qubits.begin(), fgate->qubits.end()); - const Gate& parent = *OpGetAlternative(*fgate->parent); - fused_ops.push_back(FusedGate{parent.kind, parent.time, - std::move(fgate->qubits), &parent, - std::move(fgate->gates), {}}); + auto parent = FusedGate::OpToPGate(fgate->parent); + const auto& bop = + OpBaseOperation(Base::OperationToConstRef(*fgate->parent)); + fused_ops.push_back(FusedGate{bop.kind, bop.time, + std::move(fgate->qubits), parent, + std::move(fgate->gates), {}, + fgate->defer_matrix_computation}); ++stat.num_fused_gates; } @@ -650,7 +664,6 @@ class MultiQubitGateFuser final : public Fuser { std::vector& fused_ops) { using FusedGate = std::variant_alternative_t<0, OperationF>; using fp_type = typename FusedGate::fp_type; - using Gate = qsim::Gate; for (std::size_t i = 0; i < orphaned_gates.size(); ++i) { auto ogate1 = orphaned_gates[i]; @@ -680,6 +693,10 @@ class MultiQubitGateFuser final : public Fuser { for (auto gate : ogate2->gates) { ogate1->gates.push_back(gate); + + if (OpGetAlternative>(gate)) { + ogate1->defer_matrix_computation = true; + } } } @@ -692,10 +709,13 @@ class MultiQubitGateFuser final : public Fuser { std::sort(ogate1->qubits.begin(), ogate1->qubits.end()); - const Gate& parent = *OpGetAlternative(*ogate1->parent); - fused_ops.push_back(FusedGate{parent.kind, parent.time, - std::move(ogate1->qubits), &parent, - std::move(ogate1->gates), {}}); + auto parent = FusedGate::OpToPGate(ogate1->parent); + const auto& bop = + OpBaseOperation(Base::OperationToConstRef(*ogate1->parent)); + fused_ops.push_back(FusedGate{bop.kind, bop.time, + std::move(ogate1->qubits), parent, + std::move(ogate1->gates), {}, + ogate1->defer_matrix_computation}); ++stat.num_fused_gates; } @@ -708,11 +728,11 @@ class MultiQubitGateFuser final : public Fuser { using fp_type = typename FusedGate::fp_type; using DecomposedGate = qsim::DecomposedGate; - if (const auto& pg = OpGetAlternative(*fgate.parent)) { - if (pg->qubits.size() == fgate.qubits.size()) { + if (const auto* m = OpGetAlternative(*fgate.parent)) { + if (m->qubits.size() == fgate.qubits.size()) { fused_ops.push_back(fgate.parent); } else { - Measurement mfused = *pg; + Measurement mfused = *m; mfused.qubits = fgate.qubits; fused_ops.push_back(std::move(mfused)); } @@ -799,16 +819,28 @@ class MultiQubitGateFuser final : public Fuser { template static void AddGatesFromNext(std::vector& gates, GateF& fgate) { + using fp_type = typename GateF::fp_type; + for (auto gate : gates) { fgate.gates.push_back(gate); + + if (OpGetAlternative>(gate)) { + fgate.defer_matrix_computation = true; + } } } template static void AddGatesFromPrev(unsigned max_fused_size, const GateF& pfgate, Scratch& scratch, GateF& fgate) { + using fp_type = typename GateF::fp_type; + for (auto gate : pfgate.gates) { - fgate.gates.push_back(gate); + fgate.gates.push_back(gate); + + if (OpGetAlternative>(gate)) { + fgate.defer_matrix_computation = true; + } } for (auto link : pfgate.links) { @@ -1079,8 +1111,8 @@ class MultiQubitGateFuser final : public Fuser { static void FusePrevOrNext(unsigned pass, Neighbor neighb, GateF& fgate, std::vector& gates) { + using fp_type = typename GateF::fp_type; using Link = Link; - using Gate = qsim::Gate::fp_type>; uint64_t bad_mask = 0; auto links = fgate.links; @@ -1119,11 +1151,18 @@ class MultiQubitGateFuser final : public Fuser { g->visited = pass == 0 ? kFirst : kFinal; if (pass == 0) { - // g->parent must hold the type Gate here. - gates.push_back(OpGetAlternative(*g->parent)); + gates.push_back(FusedGate::OpToPGate(g->parent)); + + if (OpGetAlternative>(g->parent)) { + fgate.defer_matrix_computation = true; + } } else { for (auto gate : g->gates) { gates.push_back(gate); + + if (OpGetAlternative>(gate)) { + fgate.defer_matrix_computation = true; + } } } diff --git a/lib/gate.h b/lib/gate.h index 99f2dece8..c09b883f3 100644 --- a/lib/gate.h +++ b/lib/gate.h @@ -17,10 +17,14 @@ #include #include +#include +#include +#include #include #include #include +#include "classical_control_expr.h" #include "matrix.h" #include "operation_base.h" @@ -32,6 +36,7 @@ struct Gate; /** * A matrix gate controlled by a number of qubits. + * @tparam FP The floating-point precision type (float or double). */ template struct ControlledGate : public Gate { @@ -74,6 +79,7 @@ struct ControlledGate : public Gate { * @param gate The base gate to be controlled. * @param controlled_by The control qubit indices. * @return The resulting controlled gate object. + * @tparam FP The floating-point precision type (float or double). */ template inline ControlledGate MakeControlledGate( @@ -90,6 +96,7 @@ inline ControlledGate MakeControlledGate( * @param controlled_by The control qubit indices. * @param control_values The control values (0 or 1) for each control qubit. * @return The resulting controlled gate object. + * @tparam FP The floating-point precision type (float or double). */ template inline ControlledGate MakeControlledGate( @@ -149,6 +156,7 @@ inline ControlledGate MakeControlledGate( /** * A generic matrix gate whose action is defined by a matrix. + * @tparam FP The floating-point precision type (float or double). */ template struct Gate : public BaseOperation { @@ -183,6 +191,7 @@ struct Gate : public BaseOperation { /** * Represents a gate that has undergone Schmidt decomposition. * Note: This struct is utilized only in the qsimh hybrid simulator. + * @tparam FP The floating-point precision type (float or double). */ template struct DecomposedGate : public Gate { @@ -196,10 +205,37 @@ struct DecomposedGate : public Gate { }; /** - * An operation that measures a specific set of qubits at a given time step. + * Represents a gate with parameter expressions resolved dynamically at runtime. + * Parameters depend on classical variables and measurement outcomes. Evaluated + * prior to gate application during simulation. + * @tparam FP Floating-point precision type (`float` or `double`). */ -struct Measurement : public BaseOperation {}; +template +struct RuntimeResolvedGate : public Gate { + RuntimeResolvedGate() {} + + /** + * Constructs a RuntimeResolvedGate by taking ownership of a base Gate. + * @param g An rvalue reference to a base Gate object to be moved. + */ + explicit RuntimeResolvedGate(Gate&& g) : Gate{std::move(g)} {} + + /** Expressions for gate parameters. */ + std::vector param_exprs; + /** + * A callable that consumes the runtime-resolved parameter values + * and computes the gate matrix. + */ + std::function& params, Matrix&)> matrix_func; +}; + +/** + * An operation that measures a specific set of qubits at a given time step. + */ +struct Measurement : public BaseOperation { + std::string id; +}; /** * A collection of gates fused into a single operation. @@ -208,29 +244,54 @@ struct Measurement : public BaseOperation {}; * `matrix` remains empty during the initial fusion pass. The fused * matrix is computed later once the Schmidt decomposition components * are populated. + * @tparam FP The floating-point precision type (float or double). */ template struct FusedGate : public BaseOperation { using fp_type = FP; - /** Pointer to either a standard matrix gate or a Schmidt-decomposed gate. */ + /** Pointer to a gate that can be fused. */ using PGate = std::variant*, + RuntimeResolvedGate*, const DecomposedGate*>; /** The primary gate that initiated this fusion block. */ PGate parent; + /** Ordered sequence of all component gates in this block. */ std::vector gates; + /** - * The fused matrix. May be empty if `fuse_matrix` is false or if the block - * contains a `DecomposedGate`. + * The fused matrix. May be empty if if the block contains a `DecomposedGate` + * or `RuntimeResolvedGate`. */ Matrix matrix; + /** Indicates if the matrix computation is deferred. */ + bool defer_matrix_computation; + /** Returns true if the primary gate is a decomposed gate. */ bool ParentIsDecomposed() const { return std::holds_alternative*>(parent); } + + static RuntimeResolvedGate* GetRuntimeResolvedGate(PGate& pgate) { + return OpGetAlternative>(pgate); + } + + template + static PGate OpToPGate(const Operation& op) { + if (auto* p = OpGetAlternative>(op)) { + return p; + } else if (auto* p = OpGetAlternative>(op)) { + return const_cast*>(p); + } else if (auto* p = OpGetAlternative>(op)) { + return p; + } + + // We cannot get here. + return {}; + } }; namespace detail { @@ -295,8 +356,10 @@ enum OtherGateKind { }; template -inline Measurement CreateMeasurement(unsigned time, Q&& qubits) { - return Measurement{kMeasurement, time, std::forward(qubits)}; +inline Measurement CreateMeasurement(unsigned time, Q&& qubits, + std::string_view id = "") { + return + Measurement{kMeasurement, time, std::forward(qubits), std::string{id}}; } template diff --git a/lib/gate_appl.h b/lib/gate_appl.h index 11ac5a996..3079d0b0c 100644 --- a/lib/gate_appl.h +++ b/lib/gate_appl.h @@ -37,13 +37,13 @@ inline void ApplyGate(const Simulator& simulator, const Operation& op, typename Simulator::State& state) { using FP = typename Simulator::fp_type; - if (const auto* pg = OpGetAlternative>(op)) { - simulator.ApplyGate(pg->qubits, pg->matrix.data(), state); - } else if (const auto* pg = OpGetAlternative>(op)) { - simulator.ApplyGate(pg->qubits, pg->matrix.data(), state); - } else if (const auto* pg = OpGetAlternative>(op)) { - simulator.ApplyControlledGate(pg->qubits, pg->controlled_by, - pg->cmask, pg->matrix.data(), state); + if (const auto* g = OpGetAlternative>(op)) { + simulator.ApplyGate(g->qubits, g->matrix.data(), state); + } else if (const auto* fg = OpGetAlternative>(op)) { + simulator.ApplyGate(fg->qubits, fg->matrix.data(), state); + } else if (const auto* cg = OpGetAlternative>(op)) { + simulator.ApplyControlledGate(cg->qubits, cg->controlled_by, + cg->cmask, cg->matrix.data(), state); } } @@ -61,19 +61,19 @@ inline void ApplyGateDagger(const Simulator& simulator, const Operation& op, typename Simulator::State& state) { using FP = typename Simulator::fp_type; - if (const auto* pg = OpGetAlternative>(op)) { - auto matrix = pg->matrix; - MatrixDagger(unsigned{1} << pg->qubits.size(), matrix); - simulator.ApplyGate(pg->qubits, matrix.data(), state); - } else if (const auto* pg = OpGetAlternative>(op)) { - auto matrix = pg->matrix; - MatrixDagger(unsigned{1} << pg->qubits.size(), matrix); - simulator.ApplyGate(pg->qubits, matrix.data(), state); - } else if (const auto* pg = OpGetAlternative>(op)) { - auto matrix = pg->matrix; - MatrixDagger(unsigned{1} << pg->qubits.size(), matrix); - simulator.ApplyControlledGate(pg->qubits, pg->controlled_by, - pg->cmask, matrix.data(), state); + if (const auto* g = OpGetAlternative>(op)) { + auto matrix = g->matrix; + MatrixDagger(unsigned{1} << g->qubits.size(), matrix); + simulator.ApplyGate(g->qubits, matrix.data(), state); + } else if (const auto* fg = OpGetAlternative>(op)) { + auto matrix = fg->matrix; + MatrixDagger(unsigned{1} << fg->qubits.size(), matrix); + simulator.ApplyGate(fg->qubits, matrix.data(), state); + } else if (const auto* cg = OpGetAlternative>(op)) { + auto matrix = cg->matrix; + MatrixDagger(unsigned{1} << cg->qubits.size(), matrix); + simulator.ApplyControlledGate(cg->qubits, cg->controlled_by, + cg->cmask, matrix.data(), state); } } @@ -97,13 +97,14 @@ inline bool ApplyGate( const Simulator& simulator, const Operation& op, Rgen& rgen, typename Simulator::State& state, std::vector& mresults) { - if (const auto* pg = OpGetAlternative(op)) { - auto measure_result = state_space.Measure(pg->qubits, rgen, state); - if (measure_result.valid) { - mresults.push_back(std::move(measure_result)); - } else { + if (const auto* m = OpGetAlternative(op)) { + auto mresult = state_space.Measure(m->qubits, rgen, state); + + if (!mresult.valid) { return false; } + + mresults.push_back(std::move(mresult)); } else { ApplyGate(simulator, op, state); } @@ -126,9 +127,9 @@ template inline bool ApplyGate(const typename Simulator::StateSpace& state_space, const Simulator& simulator, const Operation& op, Rgen& rgen, typename Simulator::State& state) { - if (const auto* pg = OpGetAlternative(op)) { - auto measure_result = state_space.Measure(pg->qubits, rgen, state); - if (!measure_result.valid) { + if (const auto* m = OpGetAlternative(op)) { + auto mresult = state_space.Measure(m->qubits, rgen, state); + if (!mresult.valid) { return false; } } else { diff --git a/lib/gates_qsim.h b/lib/gates_qsim.h index 8f186c090..974f8efd4 100644 --- a/lib/gates_qsim.h +++ b/lib/gates_qsim.h @@ -71,13 +71,28 @@ struct GateGPh { static constexpr unsigned num_qubits = 0; static constexpr bool symmetric = true; + static void UpdateMatrix(fp_type phi, Matrix& m) { + fp_type cp = std::cos(phi); + fp_type sp = std::sin(phi); + + m[0] = cp; m[1] = sp; + } + + static Matrix GetMatrix(fp_type phi) { + fp_type cp = std::cos(phi); + fp_type sp = std::sin(phi); + + return {cp, sp}; + } + static GateQSim Create(unsigned time, fp_type phi) { - return Create(time, std::cos(phi), std::sin(phi)); + return CreateGate, GateGPh>( + time, {}, GetMatrix(phi), {phi}); } static GateQSim Create(unsigned time, fp_type cp, fp_type sp) { return CreateGate, GateGPh>( - time, {}, {cp, sp}, {cp, sp}); + time, {}, {cp, sp}, {std::acos(cp)}); } }; @@ -230,13 +245,25 @@ struct GateRX { static constexpr unsigned num_qubits = 1; static constexpr bool symmetric = true; - static GateQSim Create(unsigned time, unsigned q0, fp_type phi) { + static void UpdateMatrix(fp_type phi, Matrix& m) { fp_type phi2 = -0.5 * phi; fp_type c = std::cos(phi2); fp_type s = std::sin(phi2); + m[0] = c; m[3] = s; m[5] = s; m[6] = c; + } + + static Matrix GetMatrix(fp_type phi) { + fp_type phi2 = -0.5 * phi; + fp_type c = std::cos(phi2); + fp_type s = std::sin(phi2); + + return {c, 0, 0, s, 0, s, c, 0}; + } + + static GateQSim Create(unsigned time, unsigned q0, fp_type phi) { return CreateGate, GateRX>( - time, {q0}, {c, 0, 0, s, 0, s, c, 0}, {phi}); + time, {q0}, GetMatrix(phi), {phi}); } }; @@ -251,13 +278,25 @@ struct GateRY { static constexpr unsigned num_qubits = 1; static constexpr bool symmetric = true; - static GateQSim Create(unsigned time, unsigned q0, fp_type phi) { + static void UpdateMatrix(fp_type phi, Matrix& m) { fp_type phi2 = -0.5 * phi; fp_type c = std::cos(phi2); fp_type s = std::sin(phi2); + m[0] = c; m[2] = s; m[4] = -s; m[6] = c; + } + + static Matrix GetMatrix(fp_type phi) { + fp_type phi2 = -0.5 * phi; + fp_type c = std::cos(phi2); + fp_type s = std::sin(phi2); + + return {c, 0, s, 0, -s, 0, c, 0}; + } + + static GateQSim Create(unsigned time, unsigned q0, fp_type phi) { return CreateGate, GateRY>( - time, {q0}, {c, 0, s, 0, -s, 0, c, 0}, {phi}); + time, {q0}, GetMatrix(phi), {phi}); } }; @@ -272,13 +311,25 @@ struct GateRZ { static constexpr unsigned num_qubits = 1; static constexpr bool symmetric = true; - static GateQSim Create(unsigned time, unsigned q0, fp_type phi) { + static void UpdateMatrix(fp_type phi, Matrix& m) { + fp_type phi2 = -0.5 * phi; + fp_type c = std::cos(phi2); + fp_type s = std::sin(phi2); + + m[0] = c; m[1] = s; m[6] = c; m[7] = -s; + } + + static Matrix GetMatrix(fp_type phi) { fp_type phi2 = -0.5 * phi; fp_type c = std::cos(phi2); fp_type s = std::sin(phi2); + return {c, s, 0, 0, 0, 0, c, -s}; + } + + static GateQSim Create(unsigned time, unsigned q0, fp_type phi) { return CreateGate, GateRZ>( - time, {q0}, {c, s, 0, 0, 0, 0, c, -s}, {phi}); + time, {q0}, GetMatrix(phi), {phi}); } }; @@ -292,16 +343,30 @@ struct GateRXY { static constexpr unsigned num_qubits = 1; static constexpr bool symmetric = true; - static GateQSim Create( - unsigned time, unsigned q0, fp_type theta, fp_type phi) { + static void UpdateMatrix(fp_type theta, fp_type phi, Matrix& m) { fp_type phi2 = -0.5 * phi; fp_type cp = std::cos(phi2); fp_type sp = std::sin(phi2); fp_type ct = std::cos(theta) * sp; fp_type st = std::sin(theta) * sp; + m[0] = cp; m[2] = st; m[3] = ct; m[4] = -st, m[5] = ct; m[6] = cp; + } + + static Matrix GetMatrix(fp_type theta, fp_type phi) { + fp_type phi2 = -0.5 * phi; + fp_type cp = std::cos(phi2); + fp_type sp = std::sin(phi2); + fp_type ct = std::cos(theta) * sp; + fp_type st = std::sin(theta) * sp; + + return {cp, 0, st, ct, -st, ct, cp, 0}; + } + + static GateQSim Create( + unsigned time, unsigned q0, fp_type theta, fp_type phi) { return CreateGate, GateRXY>( - time, {q0}, {cp, 0, st, ct, -st, ct, cp, 0}, {theta, phi}); + time, {q0}, GetMatrix(theta, phi), {theta, phi}); } }; @@ -514,8 +579,7 @@ struct GateFS { static constexpr fp_type is2 = static_cast(is2_double); - static GateQSim Create( - unsigned time, unsigned q0, unsigned q1, fp_type theta, fp_type phi) { + static void UpdateMatrix(fp_type theta, fp_type phi, Matrix& m) { if (phi < 0) { phi += 2 * 3.141592653589793; } @@ -525,11 +589,29 @@ struct GateFS { fp_type cp = std::cos(phi); fp_type sp = std::sin(phi); + m[10] = ct; m[13] = -st; m[19] = -st; m[20] = ct, m[30] = cp; m[31] = -sp; + } + + static Matrix GetMatrix(fp_type theta, fp_type phi) { + if (phi < 0) { + phi += 2 * 3.141592653589793; + } + + fp_type ct = std::cos(theta); + fp_type st = std::sin(theta); + fp_type cp = std::cos(phi); + fp_type sp = std::sin(phi); + + return {1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, ct, 0, 0, -st, 0, 0, + 0, 0, 0, -st, ct, 0, 0, 0, + 0, 0, 0, 0, 0, 0, cp, -sp}; + } + + static GateQSim Create( + unsigned time, unsigned q0, unsigned q1, fp_type theta, fp_type phi) { return CreateGate, GateFS>( - time, {q0, q1}, {1, 0, 0, 0, 0, 0, 0, 0, - 0, 0, ct, 0, 0, -st, 0, 0, - 0, 0, 0, -st, ct, 0, 0, 0, - 0, 0, 0, 0, 0, 0, cp, -sp}, {theta, phi}); + time, {q0, q1}, GetMatrix(theta, phi), {theta, phi}); } static schmidt_decomp_type SchmidtDecomp( @@ -585,16 +667,27 @@ struct GateCP { static constexpr unsigned num_qubits = 2; static constexpr bool symmetric = true; - static GateQSim Create( - unsigned time, unsigned q0, unsigned q1, fp_type phi) { + static void UpdateMatrix(fp_type phi, Matrix& m) { fp_type cp = std::cos(phi); fp_type sp = std::sin(phi); + m[30] = cp; m[31] = -sp; + } + + static Matrix GetMatrix(fp_type phi) { + fp_type cp = std::cos(phi); + fp_type sp = std::sin(phi); + + return {1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 0, cp, -sp}; + } + + static GateQSim Create( + unsigned time, unsigned q0, unsigned q1, fp_type phi) { return CreateGate, GateCP>( - time, {q0, q1}, {1, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 1, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 1, 0, 0, 0, - 0, 0, 0, 0, 0, 0, cp, -sp}, {phi}); + time, {q0, q1}, GetMatrix(phi), {phi}); } static schmidt_decomp_type SchmidtDecomp(fp_type phi) { diff --git a/lib/operation.h b/lib/operation.h index 83060abd8..5c608a8eb 100644 --- a/lib/operation.h +++ b/lib/operation.h @@ -15,11 +15,14 @@ #ifndef OPERATION_H_ #define OPERATION_H_ +#include +#include #include #include #include #include "channel.h" +#include "classical_control_expr.h" #include "gate.h" #include "operation_base.h" @@ -30,18 +33,85 @@ template struct ClassicallyControlledOperation; /** - * A generic operation. + * Polymorphic variant representing any executable circuit operation. + * @tparam FP Floating-point precision type (`float` or `double`). */ template -using Operation = std::variant, ControlledGate, Measurement, - Channel, ClassicallyControlledOperation>; +using Operation = std::variant, RuntimeResolvedGate, + ControlledGate, Measurement, Channel, + ClassicallyControlledOperation>; /** - * A classically controlled operation. Not implemented yet. + * Represents a classical control flow structure, runtime variable assignment, + * or I/O operation. + * + * A `ClassicallyControlledOperation` encapsulates `if/elsif/else` branching, + * loops, variable assignments, trajectory filtering, and printing. + * The exact role of struct fields depends on `kind`: + * + * - **`kIfElse`**: + * - `sub_ops`: Operations for each branch (`sub_ops[0]` for `if`, + * `sub_ops[1..n-1]` for `elsif`, last for `else`). + * - `exprs`: Condition expressions (`exprs[0]` for `if`, `exprs[1..n-1]` + * for `elsif`). + * - `scope_indices`: Symbol table scope indices for each branch block. + * + * - **`kDoWhile`**: + * - `sub_ops[0]`: Loop blody operations. + * - `exprs[0]`: Condition expression evaluated *after* each iteration. + * - `scope_indices[0]`: Scope index for loop block. + * + * - **`kRepeat`**: + * - `sub_ops[0]`: Loop block operations. + * - `exprs[0]`: Condition expression evaluated *before* each iteration. + * - `scope_indices[0]`: Scope index for loop block. + * + * - **`kAssign`**: + * - `str[0]`: Identifier string of the target variable/symbol. + * - `exprs`: Expression(s) assigned to the symbol (scalar or vector values). + * - `indices`: Single index expression (`indices[0]`) if updating + * a specific element (e.g. `vec[i] = x`). + * + * - **`kPrintLn`**: + * - `str[0]`: Optional string literal or format specifier. + * - `exprs`: Expressions to evaluate and print (up to 4 expressions). + * + * - **`kDiscard`**: + * - `exprs[0]`: Condition expression. If `true`, the simulator aborts + * current simulation trajectory and the return value indicates that this + * trajectory should be discarded. + * + * @tparam FP Floating-point precision type (`float` or `double`). */ template struct ClassicallyControlledOperation : public BaseOperation { - std::vector> sub_ops; + enum Kind { + /** Conditional branching block (`if / elsif / else`). */ + kIfElse = 1, + /** Loop block executed at least once (`do ... while`). */ + kDoWhile, + /** Pre-condition loop block (`repeat condition`). */ + kRepeat, + /** Runtime symbol/variable assignment (`var = expr`). */ + kAssign, + /** Console debugging/printing output (`println`). */ + kPrintLn, + /** Trajectory filtering (`discard condition`). */ + kDiscard, + }; + + /** Nested operations associated with control branches/blocks. */ + std::vector>> sub_ops; + /** Expressions used for conditions, assignment values, or printing. */ + std::vector exprs; + /** Array indices for vector element updates. */ + std::vector indices; + /** String parameters (variable names or print format strings). */ + std::vector str; + /** Symbol table scope indices corresponding to sub-blocks. */ + std::vector scope_indices; + /** Type classification of this classical operation. */ + Kind kind; }; namespace detail { diff --git a/lib/operation_base.h b/lib/operation_base.h index 4fa2bf2f8..c573235bb 100644 --- a/lib/operation_base.h +++ b/lib/operation_base.h @@ -31,7 +31,9 @@ using Qubits = std::vector; */ struct BaseOperation { unsigned kind; + /** Circuit moment / time tag. */ unsigned time; + /** Qubit indices targeted by this operation. */ Qubits qubits; }; diff --git a/lib/run_qsim.h b/lib/run_qsim.h index 1c3b8a970..4d891c8ba 100644 --- a/lib/run_qsim.h +++ b/lib/run_qsim.h @@ -1,4 +1,4 @@ -// Copyright 2019 Google LLC. All Rights Reserved. +// Copyright 2026 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -15,300 +15,452 @@ #ifndef RUN_QSIM_H_ #define RUN_QSIM_H_ +#include +#include +#include #include -#include #include #include "circuit.h" +#include "classical_control_expr.h" +#include "classical_control_symbol.h" +#include "error.h" #include "gate.h" #include "gate_appl.h" +#include "operation.h" #include "operation_base.h" -#include "util.h" namespace qsim { /** - * Helper struct for running qsim. + * Applies the given measurement to the simulator state. + * @tparam StateSpace State space implementation. + * @tparam RGen Random number generator type. + * @tparam SymTable Symbol table container holding classical variables and + * measurement identifiers. + * @tparam Obss Container holding observables (measurement histograms). + * @param state_space StateSpace object required to manipulate state vector. + * @param m The measurement to be applied. + * @param rgen Random number generator to perform measurements. + * @param state State of the system, to be updated by this method. + * @param symtab Symbol table containing classical variables and + * measurement identifiers. + * @param obss Output observables (measurement histograms). + * @return The measurement result. */ -template -struct QSimRunner final { - public: - using Simulator = typename Factory::Simulator; - using StateSpace = typename Simulator::StateSpace; - using State = typename StateSpace::State; - using MeasurementResult = typename StateSpace::MeasurementResult; +template +inline auto ApplyMeasurementGate( + const StateSpace& state_space, const Measurement& m, Rgen& rgen, + typename StateSpace::State& state, SymTable& symtab, Obss& obss) { + auto mresult = state_space.Measure(m.qubits, rgen, state); + + if (!mresult.valid) { + Error::Throw("measurement failed"); + } else if (!m.id.empty()) { + auto* mea_var = symtab.Lookup(m.id); + if (mea_var == nullptr) { + Error::Throw("measurement {} not found", m.id); + } + + auto* obs = obss.Lookup(m.id); + if (obs != nullptr && obs->Size() != m.qubits.size()) { + Error::Throw("malformed observable"); + } + + auto& mea = mea_var->GetMea(); + + if (mea.num_bits != mresult.bitstring.size()) { + Error::Throw("bitstring sizes mismatch"); + } + + mea.bits = 0; + for (unsigned i = 0; i < m.qubits.size(); ++i) { + mea.bits |= uint64_t{mresult.bitstring[i]} << i; + } + + if (obs != nullptr) { + ++obs->cur_count[cc::Symbol::GetMeaInt(mea)]; + } + } + + return mresult; +} + +/** + * Applies a classically controlled operation. + * @tparam Parameter Parameters type. + * @tparam ClassicallyControlledOperation Classically controlled operation type. + * @tparam Simulator State vector simulator implementation. + * @tparam RGen Random number generator type. + * @tparam SymTable Symbol table container holding classical variables and + * measurement identifiers. + * @tparam Obss Container holding observables (measurement histograms). + * @tparam Nested Callback function type. + * @param param Parameters for gate fusion and simulation. + * @param ccop Classically controlled operation to be applied. + * @param state_space StateSpace object required to manipulate state vector. + * @param simulator Simulator object for applying gates. + * @param rgen Random number generator to perform measurements. + * @param state State of the system, to be updated by this method. + * @param symtab Symbol table containing classical variables and + * measurement identifiers. + * @param obss Output observables (measurement histograms). + * @param run_nested Callback function to run nested blocks of classical + * control. + * @return True if the discard statement was triggered; false otherwise. + */ +template +inline bool RunClassicalControl( + const Parameter& param, const ClassicallyControlledOperation& ccop, + const typename Simulator::StateSpace& state_space, + const Simulator& simulator, Rgen& rgen, typename Simulator::State& state, + SymTable& symtab, Obss& obss, Nested&& run_nested) { + switch (ccop.kind) { + case ClassicallyControlledOperation::kIfElse: + if (ccop.sub_ops.size() > ccop.exprs.size() + 1) { + Error::Throw("malformed classical control operation"); + } + + for (unsigned i = 0; i < ccop.sub_ops.size(); ++i) { + if (i < ccop.exprs.size()) { + if (cc::EvalCondExpr(symtab, ccop.exprs[i])) { + symtab.EnterScope(ccop.scope_indices[i]); + bool rc = run_nested(param, ccop.sub_ops[i], state_space, + simulator, rgen, state, symtab, obss); + symtab.ExitScope(); + if (rc == false) { + return false; + } + + break; + } + } else { + symtab.EnterScope(ccop.scope_indices[i]); + bool rc = run_nested(param, ccop.sub_ops[i], state_space, + simulator, rgen, state, symtab, obss); + symtab.ExitScope(); + if (rc == false) { + return false; + } + } + } + + break; + case ClassicallyControlledOperation::kDoWhile: + symtab.EnterScope(ccop.scope_indices[0]); + do { + bool rc = run_nested(param, ccop.sub_ops[0], state_space, + simulator, rgen, state, symtab, obss); + if (rc == false) { + return false; + } + } while (cc::EvalCondExpr(symtab, ccop.exprs[0])); + symtab.ExitScope(); + + break; + case ClassicallyControlledOperation::kRepeat: + symtab.EnterScope(ccop.scope_indices[0]); + while (cc::EvalCondExpr(symtab, ccop.exprs[0])) { + bool rc = run_nested(param, ccop.sub_ops[0], state_space, + simulator, rgen, state, symtab, obss); + if (rc == false) { + return false; + } + }; + symtab.ExitScope(); + break; + case ClassicallyControlledOperation::kAssign: + { + auto* var = symtab.Lookup(ccop.str[0]); + + if (var == nullptr) { + Error::Throw("variable {} not found", ccop.str[0]); + } + + if (ccop.indices.empty()) { + var->Assign(symtab, ccop.exprs); + } else { + var->Assign(symtab, ccop.exprs[0], ccop.indices[0]); + } + } + + break; + case ClassicallyControlledOperation::kPrintLn: + { + std::string_view str = ccop.str.size() > 0 ? ccop.str[0] : ""; + PrintExpressions(symtab, str, ccop.exprs); + } + + break; + case ClassicallyControlledOperation::kDiscard: + if (cc::EvalCondExpr(symtab, ccop.exprs[0])) { + return false; + } + + break; + } + + return true; +} + +/** + * Helper runner for executing quantum circuits with (optionl) classical + * control flow. Can run clean and noisy circuits. Manages gate fusion and + * quantum trajectory execution. + * @tparam Fuser Fuser type. + * @tparam RGen Random number generator type (defaults to `std::mt19937`). + */ +template +class QSimRunner { + private: + template + using GateOrOperation = std::variant*, const Operation*>; + + public: /** * User-specified parameters for gate fusion and simulation. */ struct Parameter : public Fuser::Parameter { /** - * Random number generator seed to apply measurement gates. + * If true, normalize the state vector before performing measurements. */ - uint64_t seed; + bool normalize_before_mea_gates = true; }; /** - * Runs the given circuit, only measuring at the end. - * @param param Options for gate fusion, parallelism and logging. - * @param factory Object to create simulators and state spaces. - * @param circuit The circuit to be simulated. - * @param measure Function that performs measurements (in the sense of - * computing expectation values, etc). - * @return True if the simulation completed successfully; false otherwise. + * Executes a clean or noisy circuit trajectory with classical control. + * @tparam Operation Operation variant type. + * @tparam Simulator State vector simulator implementation. + * @tparam SymTable Symbol table container holding classical variables and + * measurement identifiers. + * @tparam Obss Container holding observables (measurement histograms). + * @param param Execution parameters. + * @param circuit Circuit to be simulated. + * @param state_space StateSpace object required to manipulate state vector. + * @param simulator Simulator object for applying gates. + * @param seed Seed for random number generation. + * @param state Input initial state vector; updated to final state on success. + * @param symtab Root scope symbol table containing classical variables and + * measurement identifiers. + * @param obss Output observables (measurement histograms). + * @return True if trajectory completed successfully; false if discarded + * via `kDiscard`. */ - template - static bool Run(const Parameter& param, const Factory& factory, - const Circuit& circuit, MeasurementFunc measure) { - unsigned time = OpTime(circuit.ops.back()); - return Run(param, factory, {time}, circuit, measure); + template + + static bool Run(const Parameter& param, const Circuit& circuit, + const typename Simulator::StateSpace& state_space, + const Simulator& simulator, uint64_t seed, + typename Simulator::State& state, SymTable& symtab, + Obss& obss) { + using fp_type = OpFpType; + + std::vector> deferred_ops; + deferred_ops.reserve(4 * circuit.ops.size()); + + RGen rgen(seed); + + return Run(param, circuit.ops, state_space, simulator, + deferred_ops, rgen, state, symtab, obss); } - /** - * Runs the given circuit, measuring at user-specified times. - * @param param Options for gate fusion, parallelism and logging. - * @param factory Object to create simulators and state spaces. - * @param times_to_measure_at Time steps at which to perform measurements. - * @param circuit The circuit to be simulated. - * @param measure Function that performs measurements (in the sense of - * computing expectation values, etc). - * @return True if the simulation completed successfully; false otherwise. - */ - template - static bool Run(const Parameter& param, const Factory& factory, - const std::vector& times_to_measure_at, - const Circuit& circuit, MeasurementFunc measure) { - double t0 = 0.0; - double t1 = 0.0; - - if (param.verbosity > 1) { - t0 = GetTime(); - } + private: + template + static bool Run(const Parameter& param, const std::vector& ops, + const typename Simulator::StateSpace& state_space, + const Simulator& simulator, DeferredOps& deferred_ops, + RGen& rgen, typename Simulator::State& state, + SymTable& symtab, Obss& obss) { + using fp_type = OpFpType; + using Channel = qsim::Channel; + using CCOP = qsim::ClassicallyControlledOperation; - RGen rgen(param.seed); + deferred_ops.clear(); - StateSpace state_space = factory.CreateStateSpace(); + std::uniform_real_distribution distr(0.0, 1.0); - auto state = state_space.Create(circuit.num_qubits); - if (state_space.IsNull(state)) { - IO::errorf("not enough memory: is the number of qubits too large?\n"); - return false; - } + // Flag to track non-unitary channels after state normalization. + bool non_unitary = false; - state_space.SetStateZero(state); - Simulator simulator = factory.CreateSimulator(); + for (const auto& op : ops) { + if (const auto* m = OpGetAlternative(op)) { + // Measurement gate. - if (param.verbosity > 1) { - t1 = GetTime(); - IO::messagef("init time is %g seconds.\n", t1 - t0); - t0 = GetTime(); - } + if (!ApplyDeferredOps(param, simulator, deferred_ops, state, symtab)) { + return false; + } - const auto& ops = Operations::get(circuit); - auto fused_ops = Fuser::FuseGates(param, circuit.num_qubits, - ops, times_to_measure_at); + bool normalize = non_unitary && param.normalize_before_mea_gates; + NormalizeState(normalize, state_space, non_unitary, state); - if (fused_ops.size() == 0 && circuit.ops.size() > 0) { - return false; - } + ApplyMeasurementGate(state_space, *m, rgen, state, symtab, obss); - if (param.verbosity > 1) { - t1 = GetTime(); - IO::messagef("fuse time is %g seconds.\n", t1 - t0); - } + continue; + } else if (const auto* ccop = OpGetAlternative(op)) { + // Classicaly controlled operation. - if (param.verbosity > 0) { - t0 = GetTime(); - } + if (!ApplyDeferredOps(param, simulator, deferred_ops, state, symtab)) { + return false; + } - unsigned cur_time_index = 0; + auto f = [&deferred_ops]( + const auto& param, const auto& ops, const auto& state_space, + const auto& simulator, auto& rgen, auto& state, auto& symtab, + auto& obss) { + return Run(param, ops, state_space, simulator, + deferred_ops, rgen, state, symtab, obss); + }; - // Apply fused operations. - for (std::size_t i = 0; i < fused_ops.size(); ++i) { - if (param.verbosity > 3) { - t1 = GetTime(); + bool rc = RunClassicalControl(param, *ccop, state_space, + simulator, rgen, state, symtab, obss, f); + + if (!rc) { + return false; + } + + continue; } - if (!ApplyGate(state_space, simulator, fused_ops[i], rgen, state)) { - IO::errorf("measurement failed.\n"); - return false; + const auto* c = OpGetAlternative(op); + + if (!c) { + DeferOp(op, deferred_ops); + continue; } - if (param.verbosity > 3) { - state_space.DeviceSync(); - double t2 = GetTime(); - IO::messagef("gate %lu done in %g seconds.\n", i, t2 - t1); + // Channel. + + double r = distr(rgen); + double cp = 0; + + const auto& channel = *c; + + // Perform sampling of Kraus operators using probability bounds. + for (std::size_t i = 0; i < channel.kops.size(); ++i) { + const auto& kop = channel.kops[i]; + + cp += kop.prob; + + if (r < cp) { + DeferOps(kop.ops, deferred_ops); + non_unitary = non_unitary || !kop.unitary; + break; + } } - unsigned t = times_to_measure_at[cur_time_index]; + if (r < cp) continue; - if (i == fused_ops.size() - 1 || t < OpTime(fused_ops[i + 1])) { - // Call back to perform measurements. - measure(cur_time_index, state_space, state); - ++cur_time_index; + if (!ApplyDeferredOps(param, simulator, deferred_ops, state, symtab)) { + return false; } - } - if (param.verbosity > 0) { - state_space.DeviceSync(); - double t2 = GetTime(); - IO::messagef("time is %g seconds.\n", t2 - t0); - } + NormalizeState(non_unitary, state_space, non_unitary, state); - return true; - } + double max_prob = 0; + std::size_t max_prob_index = 0; - /** - * Runs the given circuit and make the final state available to the caller, - * recording the result of any intermediate measurements in the circuit. - * @param param Options for gate fusion, parallelism and logging. - * @param factory Object to create simulators and state spaces. - * @param circuit The circuit to be simulated. - * @param state As an input parameter, this should contain the initial state - * of the system. After a successful run, it will be populated with the - * final state of the system. - * @param measure_results As an input parameter, this should be empty. - * After a successful run, this will contain all measurements results from - * the run, ordered by time and qubit index. - * @return True if the simulation completed successfully; false otherwise. - */ - template - static bool Run(const Parameter& param, const Factory& factory, - const Circuit& circuit, State& state, - std::vector& measure_results) { - StateSpace state_space = factory.CreateStateSpace(); - Simulator simulator = factory.CreateSimulator(); - - return Run(param, circuit, state_space, simulator, state, measure_results); - } + // Perform sampling of Kraus operators using norms of updated states. + for (std::size_t i = 0; i < channel.kops.size(); ++i) { + const auto& kop = channel.kops[i]; - /** - * Runs the given circuit and make the final state available to the caller, - * discarding the result of any intermediate measurements in the circuit. - * @param param Options for gate fusion, parallelism and logging. - * @param factory Object to create simulators and state spaces. - * @param circuit The circuit to be simulated. - * @param state As an input parameter, this should contain the initial state - * of the system. After a successful run, it will be populated with the - * final state of the system. - * @return True if the simulation completed successfully; false otherwise. - */ - template - static bool Run(const Parameter& param, const Factory& factory, - const Circuit& circuit, State& state) { - StateSpace state_space = factory.CreateStateSpace(); - Simulator simulator = factory.CreateSimulator(); + if (kop.unitary) continue; - std::vector discarded_results; + double prob = std::real( + simulator.ExpectationValue(kop.qubits, kop.kd_k.data(), state)); - return Run( - param, circuit, state_space, simulator, state, discarded_results); - } + if (prob > max_prob) { + max_prob = prob; + max_prob_index = i; + } - /** - * Runs the given circuit and make the final state available to the caller, - * recording the result of any intermediate measurements in the circuit. - * @param param Options for gate fusion, parallelism and logging. - * @param circuit The circuit to be simulated. - * @param state_space StateSpace object required to perform measurements. - * @param simulator Simulator object. Provides specific implementations for - * applying gates. - * @param state As an input parameter, this should contain the initial state - * of the system. After a successful run, it will be populated with the - * final state of the system. - * @param measure_results As an input parameter, this should be empty. - * After a successful run, this will contain all measurements results from - * the run, ordered by time and qubit index. - * @return True if the simulation completed successfully; false otherwise. - */ - template - static bool Run(const Parameter& param, const Circuit& circuit, - const StateSpace& state_space, const Simulator& simulator, - State& state, - std::vector& measure_results) { - double t0 = 0.0; - double t1 = 0.0; - - if (param.verbosity > 1) { - t0 = GetTime(); - } + cp += prob - kop.prob; - RGen rgen(param.seed); + if (r < cp || i == channel.kops.size() - 1) { + // Sample ith Kraus operator if r < cp + // Sample the highest probability Kraus operator if r is greater + // than the sum of all probabilities due to round-off errors. + uint64_t k = r < cp ? i : max_prob_index; - if (param.verbosity > 1) { - t1 = GetTime(); - IO::messagef("init time is %g seconds.\n", t1 - t0); - t0 = GetTime(); + DeferOps(channel.kops[k].ops, deferred_ops); + non_unitary = true; + break; + } + } } - const auto& ops = Operations::get(circuit); - auto fused_ops = Fuser::FuseGates(param, state.num_qubits(), ops); - - if (fused_ops.size() == 0 && ops.size() > 0) { + if (!ApplyDeferredOps(param, simulator, deferred_ops, state, symtab)) { return false; } - measure_results.reserve(fused_ops.size()); + NormalizeState(non_unitary, state_space, non_unitary, state); - if (param.verbosity > 1) { - t1 = GetTime(); - IO::messagef("fuse time is %g seconds.\n", t1 - t0); - } + return true; + } + + template + static bool ApplyDeferredOps( + const Parameter& param, const Simulator& simulator, + std::vector& ops, typename Simulator::State& state, + SymTable& symtab) { + if (ops.size() == 0) return true; - if (param.verbosity > 0) { - t0 = GetTime(); + auto fused_ops = Fuser::FuseGates(param, state.num_qubits(), ops); + if (fused_ops.size() == 0) { + Error::Throw("fuser failed"); } - // Apply fused operations. - for (std::size_t i = 0; i < fused_ops.size(); ++i) { - if (param.verbosity > 3) { - t1 = GetTime(); - } + ops.clear(); - if (!ApplyGate(state_space, simulator, fused_ops[i], rgen, state, - measure_results)) { - IO::errorf("measurement failed.\n"); - return false; - } - - if (param.verbosity > 3) { - state_space.DeviceSync(); - double t2 = GetTime(); - IO::messagef("gate %lu done in %g seconds.\n", i, t2 - t1); + // Apply fused operations. + for (auto& fop : fused_ops) { + using FusedGate = qsim::FusedGate>; + + if (auto* fg = OpGetAlternative(fop)) { + if (fg->defer_matrix_computation) { + for (auto& g : fg->gates) { + if (auto* ffg = FusedGate::GetRuntimeResolvedGate(g)) { + for (unsigned i = 0; i < ffg->params.size(); ++i) { + ffg->params[i] = cc::EvalExpr(symtab, ffg->param_exprs[i]); + } + ffg->matrix_func(ffg->params, ffg->matrix); + } + } + + CalculateFusedMatrix(*fg); + } } - } - if (param.verbosity > 0) { - state_space.DeviceSync(); - double t2 = GetTime(); - IO::messagef("simu time is %g seconds.\n", t2 - t0); + ApplyGate(simulator, fop, state); } return true; } - /** - * Runs the given circuit and make the final state available to the caller, - * discarding the result of any intermediate measurements in the circuit. - * @param param Options for gate fusion, parallelism and logging. - * @param circuit The circuit to be simulated. - * @param state_space StateSpace object required to perform measurements. - * @param simulator Simulator object. Provides specific implementations for - * applying gates. - * @param state As an input parameter, this should contain the initial state - * of the system. After a successful run, it will be populated with the - * final state of the system. - * @return True if the simulation completed successfully; false otherwise. - */ - template - static bool Run(const Parameter& param, const Circuit& circuit, - const StateSpace& state_space, const Simulator& simulator, - State& state) { - std::vector discarded_results; - - return Run( - param, circuit, state_space, simulator, state, discarded_results); + template + static void DeferOp(const Operation& op, std::vector& ops) { + ops.push_back(&op); + } + + template + static void DeferOps( + const std::vector& gates, std::vector& ops) { + for (const auto& gate : gates) { + ops.push_back(&gate); + } + } + + template + static void NormalizeState(bool normalize, const StateSpace& state_space, + bool& flag, typename StateSpace::State& state) { + if (normalize) { + double a = 1.0 / std::sqrt(state_space.Norm(state)); + state_space.Multiply(a, state); + flag = false; + } } }; diff --git a/lib/run_qsim_deprecated.h b/lib/run_qsim_deprecated.h new file mode 100644 index 000000000..a8fe41ab0 --- /dev/null +++ b/lib/run_qsim_deprecated.h @@ -0,0 +1,317 @@ +// Copyright 2019 Google LLC. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef RUN_QSIM_H_ +#define RUN_QSIM_H_ + +#include +#include +#include + +#include "circuit.h" +#include "gate.h" +#include "gate_appl.h" +#include "operation_base.h" +#include "util.h" + +namespace qsim { + +/** + * Helper struct for running qsim without classical control. + */ +template +struct QSimRunner final { + public: + using Simulator = typename Factory::Simulator; + using StateSpace = typename Simulator::StateSpace; + using State = typename StateSpace::State; + using MeasurementResult = typename StateSpace::MeasurementResult; + + /** + * User-specified parameters for gate fusion and simulation. + */ + struct Parameter : public Fuser::Parameter { + /** + * Random number generator seed to apply measurement gates. + */ + uint64_t seed; + }; + + /** + * Runs the given circuit, only measuring at the end. + * @param param Options for gate fusion, parallelism and logging. + * @param factory Object to create simulators and state spaces. + * @param circuit The circuit to be simulated. + * @param measure Function that performs measurements (in the sense of + * computing expectation values, etc). + * @return True if the simulation completed successfully; false otherwise. + */ + template + static bool Run(const Parameter& param, const Factory& factory, + const Circuit& circuit, MeasurementFunc measure) { + unsigned time = OpTime(circuit.ops.back()); + return Run(param, factory, {time}, circuit, measure); + } + + /** + * Runs the given circuit, measuring at user-specified times. + * @param param Options for gate fusion, parallelism and logging. + * @param factory Object to create simulators and state spaces. + * @param times_to_measure_at Time steps at which to perform measurements. + * @param circuit The circuit to be simulated. + * @param measure Function that performs measurements (in the sense of + * computing expectation values, etc). + * @return True if the simulation completed successfully; false otherwise. + */ + template + static bool Run(const Parameter& param, const Factory& factory, + const std::vector& times_to_measure_at, + const Circuit& circuit, MeasurementFunc measure) { + double t0 = 0.0; + double t1 = 0.0; + + if (param.verbosity > 1) { + t0 = GetTime(); + } + + RGen rgen(param.seed); + + StateSpace state_space = factory.CreateStateSpace(); + + auto state = state_space.Create(circuit.num_qubits); + if (state_space.IsNull(state)) { + IO::errorf("not enough memory: is the number of qubits too large?\n"); + return false; + } + + state_space.SetStateZero(state); + Simulator simulator = factory.CreateSimulator(); + + if (param.verbosity > 1) { + t1 = GetTime(); + IO::messagef("init time is %g seconds.\n", t1 - t0); + t0 = GetTime(); + } + + const auto& ops = Operations::get(circuit); + auto fused_ops = Fuser::FuseGates(param, circuit.num_qubits, + ops, times_to_measure_at); + + if (fused_ops.size() == 0 && circuit.ops.size() > 0) { + return false; + } + + if (param.verbosity > 1) { + t1 = GetTime(); + IO::messagef("fuse time is %g seconds.\n", t1 - t0); + } + + if (param.verbosity > 0) { + t0 = GetTime(); + } + + unsigned cur_time_index = 0; + + // Apply fused operations. + for (std::size_t i = 0; i < fused_ops.size(); ++i) { + if (param.verbosity > 3) { + t1 = GetTime(); + } + + if (!ApplyGate(state_space, simulator, fused_ops[i], rgen, state)) { + IO::errorf("measurement failed.\n"); + return false; + } + + if (param.verbosity > 3) { + state_space.DeviceSync(); + double t2 = GetTime(); + IO::messagef("gate %lu done in %g seconds.\n", i, t2 - t1); + } + + unsigned t = times_to_measure_at[cur_time_index]; + + if (i == fused_ops.size() - 1 || t < OpTime(fused_ops[i + 1])) { + // Call back to perform measurements. + measure(cur_time_index, state_space, state); + ++cur_time_index; + } + } + + if (param.verbosity > 0) { + state_space.DeviceSync(); + double t2 = GetTime(); + IO::messagef("time is %g seconds.\n", t2 - t0); + } + + return true; + } + + /** + * Runs the given circuit and make the final state available to the caller, + * recording the result of any intermediate measurements in the circuit. + * @param param Options for gate fusion, parallelism and logging. + * @param factory Object to create simulators and state spaces. + * @param circuit The circuit to be simulated. + * @param state As an input parameter, this should contain the initial state + * of the system. After a successful run, it will be populated with the + * final state of the system. + * @param measure_results As an input parameter, this should be empty. + * After a successful run, this will contain all measurements results from + * the run, ordered by time and qubit index. + * @return True if the simulation completed successfully; false otherwise. + */ + template + static bool Run(const Parameter& param, const Factory& factory, + const Circuit& circuit, State& state, + std::vector& measure_results) { + StateSpace state_space = factory.CreateStateSpace(); + Simulator simulator = factory.CreateSimulator(); + + return Run(param, circuit, state_space, simulator, state, measure_results); + } + + /** + * Runs the given circuit and make the final state available to the caller, + * discarding the result of any intermediate measurements in the circuit. + * @param param Options for gate fusion, parallelism and logging. + * @param factory Object to create simulators and state spaces. + * @param circuit The circuit to be simulated. + * @param state As an input parameter, this should contain the initial state + * of the system. After a successful run, it will be populated with the + * final state of the system. + * @return True if the simulation completed successfully; false otherwise. + */ + template + static bool Run(const Parameter& param, const Factory& factory, + const Circuit& circuit, State& state) { + StateSpace state_space = factory.CreateStateSpace(); + Simulator simulator = factory.CreateSimulator(); + + std::vector discarded_results; + + return Run( + param, circuit, state_space, simulator, state, discarded_results); + } + + /** + * Runs the given circuit and make the final state available to the caller, + * recording the result of any intermediate measurements in the circuit. + * @param param Options for gate fusion, parallelism and logging. + * @param circuit The circuit to be simulated. + * @param state_space StateSpace object required to perform measurements. + * @param simulator Simulator object. Provides specific implementations for + * applying gates. + * @param state As an input parameter, this should contain the initial state + * of the system. After a successful run, it will be populated with the + * final state of the system. + * @param measure_results As an input parameter, this should be empty. + * After a successful run, this will contain all measurements results from + * the run, ordered by time and qubit index. + * @return True if the simulation completed successfully; false otherwise. + */ + template + static bool Run(const Parameter& param, const Circuit& circuit, + const StateSpace& state_space, const Simulator& simulator, + State& state, + std::vector& measure_results) { + double t0 = 0.0; + double t1 = 0.0; + + if (param.verbosity > 1) { + t0 = GetTime(); + } + + RGen rgen(param.seed); + + if (param.verbosity > 1) { + t1 = GetTime(); + IO::messagef("init time is %g seconds.\n", t1 - t0); + t0 = GetTime(); + } + + const auto& ops = Operations::get(circuit); + auto fused_ops = Fuser::FuseGates(param, state.num_qubits(), ops); + + if (fused_ops.size() == 0 && ops.size() > 0) { + return false; + } + + measure_results.reserve(fused_ops.size()); + + if (param.verbosity > 1) { + t1 = GetTime(); + IO::messagef("fuse time is %g seconds.\n", t1 - t0); + } + + if (param.verbosity > 0) { + t0 = GetTime(); + } + + // Apply fused operations. + for (std::size_t i = 0; i < fused_ops.size(); ++i) { + if (param.verbosity > 3) { + t1 = GetTime(); + } + + if (!ApplyGate(state_space, simulator, fused_ops[i], rgen, state, + measure_results)) { + IO::errorf("measurement failed.\n"); + return false; + } + + if (param.verbosity > 3) { + state_space.DeviceSync(); + double t2 = GetTime(); + IO::messagef("gate %lu done in %g seconds.\n", i, t2 - t1); + } + } + + if (param.verbosity > 0) { + state_space.DeviceSync(); + double t2 = GetTime(); + IO::messagef("simu time is %g seconds.\n", t2 - t0); + } + + return true; + } + + /** + * Runs the given circuit and make the final state available to the caller, + * discarding the result of any intermediate measurements in the circuit. + * @param param Options for gate fusion, parallelism and logging. + * @param circuit The circuit to be simulated. + * @param state_space StateSpace object required to perform measurements. + * @param simulator Simulator object. Provides specific implementations for + * applying gates. + * @param state As an input parameter, this should contain the initial state + * of the system. After a successful run, it will be populated with the + * final state of the system. + * @return True if the simulation completed successfully; false otherwise. + */ + template + static bool Run(const Parameter& param, const Circuit& circuit, + const StateSpace& state_space, const Simulator& simulator, + State& state) { + std::vector discarded_results; + + return Run( + param, circuit, state_space, simulator, state, discarded_results); + } +}; + +} // namespace qsim + +#endif // RUN_QSIM_H_ diff --git a/pybind_interface/Makefile b/pybind_interface/Makefile index 3c9f8cb11..4bdf7d2dc 100644 --- a/pybind_interface/Makefile +++ b/pybind_interface/Makefile @@ -36,7 +36,7 @@ QSIMLIB_DECIDE = ../qsimcirq/qsim_decide$(SUFFIX) # Certain Pybind11 flags are common for all cases. PYBIND_INCLUDES := $(shell pybind11-config --includes) -PYBINDFLAGS = -Wall -shared -std=c++17 -fPIC $(PYBIND_INCLUDES) +PYBINDFLAGS = -Wall -shared -std=c++20 -fPIC $(PYBIND_INCLUDES) # GCC 12 produces false-positive array-bounds warnings in some code (see # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=115824). Pybind 3.x automatically @@ -55,13 +55,13 @@ PYBINDFLAGS_AVX2 = -mavx2 -mfma $(PYBINDFLAGS) PYBINDFLAGS_AVX512 = -mavx512f -mbmi2 $(PYBINDFLAGS) # The flags for the compilation of CUDA-specific Pybind11 interfaces -PYBINDFLAGS_CUDA = -std=c++17 -x cu -Xcompiler "-Wall -shared -fPIC $(PYBIND_INCLUDES)" +PYBINDFLAGS_CUDA = -std=c++20 -x cu -Xcompiler "-Wall -shared -fPIC $(PYBIND_INCLUDES)" # The flags for the compilation of cuStateVec-specific Pybind11 interfaces PYBINDFLAGS_CUSTATEVEC = $(CUSTATEVECFLAGS) $(PYBINDFLAGS_CUDA) # The flags for the compilation of HIP-specific Pybind11 interfaces -PYBINDFLAGS_HIP = -std=c++17 -Wall -shared -fPIC $(PYBIND_INCLUDES) +PYBINDFLAGS_HIP = -std=c++20 -Wall -shared -fPIC $(PYBIND_INCLUDES) # Check for nvcc to decide compilation mode. ifeq ($(shell which $(NVCC)),) diff --git a/pybind_interface/avx2/CMakeLists.txt b/pybind_interface/avx2/CMakeLists.txt index 0a844150e..738e9de8e 100644 --- a/pybind_interface/avx2/CMakeLists.txt +++ b/pybind_interface/avx2/CMakeLists.txt @@ -46,7 +46,7 @@ if(APPLE) ) endif() -set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD 20) include(../GetPybind11.cmake) pybind11_add_module(qsim_avx2 pybind_main_avx2.cpp) diff --git a/pybind_interface/avx2/pybind_main_avx2.cpp b/pybind_interface/avx2/pybind_main_avx2.cpp index cd3a9c058..aaf4945a8 100644 --- a/pybind_interface/avx2/pybind_main_avx2.cpp +++ b/pybind_interface/avx2/pybind_main_avx2.cpp @@ -18,7 +18,7 @@ #include "../../lib/fuser_mqubit.h" #include "../../lib/gates_cirq.h" #include "../../lib/io.h" -#include "../../lib/run_qsim.h" +#include "../../lib/run_qsim_deprecated.h" #include "../../lib/simulator_avx.h" #include "../../lib/util_cpu.h" diff --git a/pybind_interface/avx512/CMakeLists.txt b/pybind_interface/avx512/CMakeLists.txt index de77a8ade..6a90154a9 100644 --- a/pybind_interface/avx512/CMakeLists.txt +++ b/pybind_interface/avx512/CMakeLists.txt @@ -36,7 +36,7 @@ if(APPLE) ) endif() -set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD 20) include(../GetPybind11.cmake) pybind11_add_module(qsim_avx512 pybind_main_avx512.cpp) diff --git a/pybind_interface/avx512/pybind_main_avx512.cpp b/pybind_interface/avx512/pybind_main_avx512.cpp index 906eac8fe..ed536e84c 100644 --- a/pybind_interface/avx512/pybind_main_avx512.cpp +++ b/pybind_interface/avx512/pybind_main_avx512.cpp @@ -18,7 +18,7 @@ #include "../../lib/fuser_mqubit.h" #include "../../lib/gates_cirq.h" #include "../../lib/io.h" -#include "../../lib/run_qsim.h" +#include "../../lib/run_qsim_deprecated.h" #include "../../lib/simulator_avx512.h" #include "../../lib/util_cpu.h" diff --git a/pybind_interface/basic/CMakeLists.txt b/pybind_interface/basic/CMakeLists.txt index 2913eca3f..798328db5 100644 --- a/pybind_interface/basic/CMakeLists.txt +++ b/pybind_interface/basic/CMakeLists.txt @@ -36,7 +36,7 @@ if(APPLE) ) endif() -set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD 20) include(../GetPybind11.cmake) pybind11_add_module(qsim_basic pybind_main_basic.cpp) diff --git a/pybind_interface/basic/pybind_main_basic.cpp b/pybind_interface/basic/pybind_main_basic.cpp index b8e0191d8..777a038cb 100644 --- a/pybind_interface/basic/pybind_main_basic.cpp +++ b/pybind_interface/basic/pybind_main_basic.cpp @@ -18,7 +18,7 @@ #include "../../lib/fuser_mqubit.h" #include "../../lib/gates_cirq.h" #include "../../lib/io.h" -#include "../../lib/run_qsim.h" +#include "../../lib/run_qsim_deprecated.h" #include "../../lib/simulator_basic.h" #include "../../lib/util_cpu.h" diff --git a/pybind_interface/cuda/CMakeLists.txt b/pybind_interface/cuda/CMakeLists.txt index cdf76a6b4..d4857676b 100644 --- a/pybind_interface/cuda/CMakeLists.txt +++ b/pybind_interface/cuda/CMakeLists.txt @@ -37,7 +37,7 @@ if(APPLE) ) endif() -set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD 20) include(../GetPybind11.cmake) include(../GetCUDAARCHS.cmake) diff --git a/pybind_interface/cuda/pybind_main_cuda.cpp b/pybind_interface/cuda/pybind_main_cuda.cpp index 8d0e82dda..f808f4a28 100644 --- a/pybind_interface/cuda/pybind_main_cuda.cpp +++ b/pybind_interface/cuda/pybind_main_cuda.cpp @@ -17,7 +17,7 @@ #include "../../lib/fuser_mqubit.h" #include "../../lib/gates_cirq.h" #include "../../lib/io.h" -#include "../../lib/run_qsim.h" +#include "../../lib/run_qsim_deprecated.h" #include "../../lib/simulator_cuda.h" namespace qsim { diff --git a/pybind_interface/custatevec/CMakeLists.txt b/pybind_interface/custatevec/CMakeLists.txt index 0bb9a55e4..a2b8e0732 100644 --- a/pybind_interface/custatevec/CMakeLists.txt +++ b/pybind_interface/custatevec/CMakeLists.txt @@ -36,7 +36,7 @@ if(APPLE) ) endif() -set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD 20) include(../GetPybind11.cmake) find_package(Python3 3.10 REQUIRED) diff --git a/pybind_interface/custatevec/pybind_main_custatevec.cpp b/pybind_interface/custatevec/pybind_main_custatevec.cpp index 5e9a53e7b..a35046d00 100644 --- a/pybind_interface/custatevec/pybind_main_custatevec.cpp +++ b/pybind_interface/custatevec/pybind_main_custatevec.cpp @@ -20,7 +20,7 @@ #include "../../lib/fuser_mqubit.h" #include "../../lib/gates_cirq.h" #include "../../lib/io.h" -#include "../../lib/run_qsim.h" +#include "../../lib/run_qsim_deprecated.h" #include "../../lib/simulator_custatevec.h" namespace qsim { diff --git a/pybind_interface/custatevecex/CMakeLists.txt b/pybind_interface/custatevecex/CMakeLists.txt index 3255dc79d..2d99cbd1e 100644 --- a/pybind_interface/custatevecex/CMakeLists.txt +++ b/pybind_interface/custatevecex/CMakeLists.txt @@ -36,7 +36,7 @@ if(APPLE) ) endif() -set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD 20) include(../GetPybind11.cmake) find_package(Python3 3.10 REQUIRED) diff --git a/pybind_interface/decide/CMakeLists.txt b/pybind_interface/decide/CMakeLists.txt index 6165267b3..a13126f62 100644 --- a/pybind_interface/decide/CMakeLists.txt +++ b/pybind_interface/decide/CMakeLists.txt @@ -39,7 +39,7 @@ if(APPLE) ) endif() -set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD 20) include(../GetPybind11.cmake) diff --git a/pybind_interface/hip/CMakeLists.txt b/pybind_interface/hip/CMakeLists.txt index 5a3a7b50c..fbbf24bb0 100644 --- a/pybind_interface/hip/CMakeLists.txt +++ b/pybind_interface/hip/CMakeLists.txt @@ -21,7 +21,7 @@ else() add_compile_options(-fno-lto) endif() -set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD 20) include(../GetPybind11.cmake) find_package(PythonLibs 3.10 REQUIRED) diff --git a/pybind_interface/hip/pybind_main_hip.cpp b/pybind_interface/hip/pybind_main_hip.cpp index fc2e6789b..85612e048 100644 --- a/pybind_interface/hip/pybind_main_hip.cpp +++ b/pybind_interface/hip/pybind_main_hip.cpp @@ -17,7 +17,7 @@ #include "../../lib/fuser_mqubit.h" #include "../../lib/gates_cirq.h" #include "../../lib/io.h" -#include "../../lib/run_qsim.h" +#include "../../lib/run_qsim_deprecated.h" #include "../../lib/simulator_cuda.h" namespace qsim { diff --git a/pybind_interface/sse/CMakeLists.txt b/pybind_interface/sse/CMakeLists.txt index ff64c0098..6209baae4 100644 --- a/pybind_interface/sse/CMakeLists.txt +++ b/pybind_interface/sse/CMakeLists.txt @@ -36,7 +36,7 @@ if(APPLE) ) endif() -set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD 20) include(../GetPybind11.cmake) pybind11_add_module(qsim_sse pybind_main_sse.cpp) diff --git a/pybind_interface/sse/pybind_main_sse.cpp b/pybind_interface/sse/pybind_main_sse.cpp index cf4202eeb..298fb8838 100644 --- a/pybind_interface/sse/pybind_main_sse.cpp +++ b/pybind_interface/sse/pybind_main_sse.cpp @@ -18,7 +18,7 @@ #include "../../lib/fuser_mqubit.h" #include "../../lib/gates_cirq.h" #include "../../lib/io.h" -#include "../../lib/run_qsim.h" +#include "../../lib/run_qsim_deprecated.h" #include "../../lib/simulator_sse.h" #include "../../lib/util_cpu.h" diff --git a/qsim/BUILD b/qsim/BUILD new file mode 100644 index 000000000..a26036afe --- /dev/null +++ b/qsim/BUILD @@ -0,0 +1,51 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load("@local_compiler_config//:compiler_config.bzl", "SUPPORTS_GSFRAME") +load("@rules_cc//cc:defs.bzl", "cc_binary") + +gsframe_copts = select({ + "@platforms//os:linux": ["-Wa,--gsframe=no"] if SUPPORTS_GSFRAME else [], + "//conditions:default": [], +}) + +copts = [ + "-flto=auto", + "-fopenmp", + "-march=native", + "-O3", + "-std=c++20", +] + gsframe_copts + +cc_binary( + name = "qsim", + srcs = ["qsim.cc"], + copts = copts, + linkopts = [ + "-lgomp", + "-flto=auto", + ], + deps = [ + "//lib:circuit_qsim_parser", + "//lib:classical_control", + "//lib:formux", + "//lib:fuser_mqubit", + "//lib:io", + "//lib:run_qsim", + "//lib:simulator", + "//lib:util", + "//lib:util_cpu", + ], +) + diff --git a/qsim/Makefile b/qsim/Makefile new file mode 100644 index 000000000..58864cc3e --- /dev/null +++ b/qsim/Makefile @@ -0,0 +1,13 @@ +CXX = g++ +CXXFLAGS = -std=c++20 -O3 -march=native -fopenmp -Wa,--gsframe=no -flto=auto + +TARGET = qsim +SRC = qsim.cc + +all: $(TARGET) + +$(TARGET): $(SRC) + $(CXX) $(CXXFLAGS) -o $(TARGET) $(SRC) + +clean: + rm -f $(TARGET) diff --git a/qsim/examples/factorial.qs b/qsim/examples/factorial.qs new file mode 100644 index 000000000..97a562f0a --- /dev/null +++ b/qsim/examples/factorial.qs @@ -0,0 +1,9 @@ +int f = 1 +int i = 1 + +repeat i <= n + f = f * i + i = i + 1 +end + +println f diff --git a/qsim/examples/noisy.qs b/qsim/examples/noisy.qs new file mode 100644 index 000000000..742c17d56 --- /dev/null +++ b/qsim/examples/noisy.qs @@ -0,0 +1,7 @@ +1 # one-qubit circuit +noise amplitude_damp gamma + +x 0 +m 0 m1 + +histogram m1 diff --git a/qsim/examples/readme.md b/qsim/examples/readme.md new file mode 100644 index 000000000..6998a7e3b --- /dev/null +++ b/qsim/examples/readme.md @@ -0,0 +1,23 @@ +# Example Circuit Programs + +Sample circuit programs demonstrating `qsim` features are located in the `qsim/qsim/examples/` directory. + +## 1. Classical Computation: Factorial (`factorial.qs`) + +Calculates $n!$ using classical control loops and arithmetic expressions within the [`qsim` circuit description language](../docs/input_format.md). The value of $n$ can be injected dynamically via the command line: + +```bash +# Compute 12! (Outputs: 479001600) +./qsim -c examples/factorial.qs -s "n = 12" + +``` + +## 2. Noisy Simulation: Amplitude Damping (`noisy.qs`) + +Demonstrates quantum trajectory simulation of a noisy single-qubit circuit subjected to an amplitude damping channel with damping parameter $\gamma$: + +```bash +# Sample 100,000 trajectories across 5 parallel workers with gamma = 0.1 +./qsim -c examples/noisy.qs -1 100000 -w 5 -s "gamma = 0.1" + +``` diff --git a/qsim/qsim.cc b/qsim/qsim.cc new file mode 100644 index 000000000..2d3005bdd --- /dev/null +++ b/qsim/qsim.cc @@ -0,0 +1,336 @@ +// Copyright 2026 Google LLC. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../lib/circuit_qsim_parser.h" +#include "../lib/classical_control_error.h" +#include "../lib/classical_control_symbol.h" +#include "../lib/classical_control_symtab.h" +#include "../lib/classical_control_util.h" +#include "../lib/formux.h" +#include "../lib/fuser_mqubit.h" +#include "../lib/io.h" +#include "../lib/run_qsim.h" +#include "../lib/simmux.h" +#include "../lib/util.h" +#include "../lib/util_cpu.h" + +struct ParallelFor { + explicit ParallelFor(unsigned num_threads) : num_threads(num_threads) {} + + uint64_t GetIndex0(uint64_t size, unsigned thread_id) const { + return size * thread_id / num_threads; + } + + uint64_t GetIndex1(uint64_t size, unsigned thread_id) const { + return size * (thread_id + 1) / num_threads; + } + + template + void Run(uint64_t size, Function&& func, Args&&... args) const { + if (num_threads == 1) { + func(1, 0, 0, size, args...); + } else { + std::mutex mutex; + std::exception_ptr exception = nullptr; + + #pragma omp parallel num_threads(num_threads) + { + unsigned n = omp_get_num_threads(); + unsigned m = omp_get_thread_num(); + + uint64_t i0 = GetIndex0(size, m); + uint64_t i1 = GetIndex1(size, m); + + try { + func(n, m, i0, i1, args...); + } catch (...) { + std::lock_guard lock(mutex); + if (!exception) { + exception = std::current_exception(); + } + } + } + + if (exception) { + std::rethrow_exception(exception); + } + } + } + + unsigned num_threads; +}; + +constexpr char usage[] = "usage:\n ./qsim -c circuit -s symbol_defs " + "-0 rep0 -1 rep1 -w num_workers " + "-t num_threads_per_worker -f max_fused_size " + "-v verbosity -z\n"; + +struct Options { + std::string circuit_file; + std::string symbol_defs; + unsigned rep0 = 0; + unsigned rep1 = 1; + unsigned num_workers = 1; + unsigned num_threads = 1; + unsigned max_fused_size = 2; + unsigned verbosity = 0; + bool denormals_are_zeros = false; +}; + +Options GetOptions(int argc, char* argv[]) { + Options opt; + + int k; + + while ((k = getopt(argc, argv, "c:s:0:1:w:t:f:v:z")) != -1) { + switch (k) { + case 'c': + opt.circuit_file = optarg; + break; + case 's': + opt.symbol_defs = optarg; + break; + case '0': + opt.rep0 = std::atoi(optarg); + break; + case '1': + opt.rep1 = std::atoi(optarg); + break; + case 'w': + opt.num_workers = std::atoi(optarg); + break; + case 't': + opt.num_threads = std::atoi(optarg); + break; + case 'f': + opt.max_fused_size = std::atoi(optarg); + break; + case 'v': + opt.verbosity = std::atoi(optarg); + break; + case 'z': + opt.denormals_are_zeros = true; + break; + default: + qsim::IO::errorf(usage); + exit(1); + } + } + + return opt; +} + +bool ValidateOptions(const Options& opt) { + if (opt.circuit_file.empty()) { + qsim::IO::errorf("circuit file is not provided.\n"); + qsim::IO::errorf(usage); + return false; + } + + return true; +} + +int main(int argc, char* argv[]) { + using namespace qsim; + using namespace qsim::cc; + + auto opt = GetOptions(argc, argv); + if (!ValidateOptions(opt)) { + return 1; + } + + if (opt.denormals_are_zeros) { + SetFlushToZeroAndDenormalsAreZeros(); + } + + omp_set_max_active_levels(2); + + try { + double t0 = 0; + + if (opt.verbosity > 1) { + t0 = GetTime(); + } + + uint64_t num_reps = opt.rep1 - opt.rep0; + unsigned num_workers = std::min(uint64_t{opt.num_workers}, num_reps); + + SymTable symtab; + symtab.EnterScope(symtab.AddScope()); + + symtab.Insert("pi", Symbol::Float(M_PI)); + symtab.Insert("nw", Symbol::Int(num_workers)); + symtab.Insert("wid", {Symbol::Int(0), /*read_only=*/false}); + symtab.Insert("rid", {Symbol::Int(0), /*read_only=*/false}); + symtab.Insert("rep0", Symbol::Int(opt.rep0)); + symtab.Insert("rep1", Symbol::Int(opt.rep1)); + + ParseSymbols(opt.symbol_defs, symtab); + + auto cstr = ReadFile(opt.circuit_file); + unsigned maxtime = std::numeric_limits::max(); + auto [circuit, obss] = CircuitQsimParser::Run(cstr, maxtime, symtab); + + struct Factory { + Factory(unsigned num_threads) : num_threads(num_threads) {} + + using Simulator = qsim::Simulator; + using StateSpace = Simulator::StateSpace; + + StateSpace CreateStateSpace() const { + return StateSpace(num_threads); + } + + Simulator CreateSimulator() const { + return Simulator(num_threads); + } + + unsigned num_threads; + }; + + using Simulator = Factory::Simulator; + using StateSpace = Simulator::StateSpace; + using State = StateSpace::State; + using Fuser = MultiQubitGateFuser; + using Runner = qsim::QSimRunner; + + Factory factory(opt.num_threads); + + Runner::Parameter param; + param.max_fused_size = opt.max_fused_size; + param.verbosity = opt.verbosity; + + std::vector obsss; + obsss.resize(opt.num_workers); + + if (param.verbosity > 1) { + double t1 = GetTime(); + IO::messagef("# initialization runtime is %g seconds.\n", t1 - t0); + } + + auto f = [&opt, &circuit, &symtab, &obss, &factory, ¶m, &obsss]( + unsigned, unsigned m, uint64_t r0, uint64_t r1) { + auto circuit_m = circuit; + auto symtab_m = symtab; + auto obss_m = obss; + + Simulator simulator = factory.CreateSimulator(); + StateSpace state_space = factory.CreateStateSpace(); + + State state = state_space.Create(circuit.num_qubits); + + if (state_space.IsNull(state)) { + Error::Throw("not enough memory: is the number of qubits too large?\n"); + } + + Symbol* sym = symtab_m.Lookup("wid"); + sym->Assign(Symbol::Int(m)); + + r0 += opt.rep0; + r1 += opt.rep0; + + for (uint64_t r = r0; r < r1; r++) { + Symbol* sym = symtab_m.Lookup("rid"); + sym->Assign(Symbol::Int(r)); + + state_space.SetStateZero(state); + + uint64_t seed = 2 * r + 1; + + bool rc = Runner::Run(param, circuit_m, state_space, simulator, + seed, state, symtab_m, obss_m); + + if (rc) { + obss_m.Iterate([](auto, auto& obs) { obs.Update(); }); + } else { + obss_m.Iterate([](auto, auto& obs) { obs.Discard(); }); + } + } + + obsss[m] = std::move(obss_m); + }; + + if (opt.verbosity > 0) { + t0 = GetTime(); + } + + ::ParallelFor{num_workers}.Run(opt.rep1 - opt.rep0, f); + + if (param.verbosity > 0) { + double t1 = GetTime(); + IO::messagef("# simulation runtime is %g seconds.\n", t1 - t0); + } + + if (param.verbosity > 1) { + t0 = GetTime(); + } + + std::map> hists; + + auto h = [&hists](auto name, const auto& obs) { + auto& hist = hists[name]; + + if (hist.empty()) { + hist.reserve(obs.total_count.size()); + + for (auto val : obs.total_count) { + hist.emplace_back(val); + } + } else { + for (unsigned i = 0; i < obs.total_count.size(); ++i) { + hist[i] += obs.total_count[i]; + } + } + }; + + for (const auto& obss : obsss) { + obss.Iterate(h); + } + + if (param.verbosity > 1) { + double t1 = GetTime(); + IO::messagef("# postprocessing runtime is %g seconds.\n", t1 - t0); + } + + for (auto& [name, hist] : hists) { + IO::messagef("%6.*s [", (int) name.size(), name.data()); + + for (std::size_t i = 0; i < hist.size(); ++i) { + if (i > 0) { + IO::messagef(", "); + } + IO::messagef("%lu: %lu", i, hist[i]); + } + + puts("]"); + }; + } catch (std::exception& e) { + IO::errorf("%s\n", e.what()); + } + + return 0; +} diff --git a/tests/BUILD b/tests/BUILD index 9e296f39e..9a31c43fc 100644 --- a/tests/BUILD +++ b/tests/BUILD @@ -17,24 +17,33 @@ load("@rules_cc//cc:defs.bzl", "cc_library", "cc_test") gsframe_copts = ["-Wa,--gsframe=no"] if SUPPORTS_GSFRAME else [] +default_copts = ["-std=c++20"] + gsframe_copts + # Options for testing different simulator types. avx_copts = [ "-mavx2", "-mfma", + "-std=c++20", ] + gsframe_copts -avx512_copts = ["-march=native"] + gsframe_copts +avx512_copts = [ + "-march=native", + "-std=c++20", +] + gsframe_copts -sse_copts = ["-msse4"] + gsframe_copts +sse_copts = [ + "-msse4", + "-std=c++20", +] + gsframe_copts windows_copts = [ "/arch:AVX", - "/std:c++17", + "/std:c++20", ] windows_avx512_copts = [ "/arch:AVX512", - "/std:c++17", + "/std:c++20", ] config_setting( @@ -48,7 +57,7 @@ cc_test( srcs = ["bitstring_test.cc"], copts = select({ ":windows": windows_copts, - "//conditions:default": [], + "//conditions:default": default_copts, }), deps = [ "//lib:bitstring", @@ -62,7 +71,7 @@ cc_test( srcs = ["channel_test.cc"], copts = select({ ":windows": windows_copts, - "//conditions:default": [], + "//conditions:default": default_copts, }), deps = [ "//lib:channel", @@ -80,7 +89,7 @@ cc_test( srcs = ["channels_cirq_test.cc"], copts = select({ ":windows": windows_copts, - "//conditions:default": [], + "//conditions:default": default_copts, }), deps = [ "//lib:channels_cirq", @@ -92,7 +101,7 @@ cc_test( "//lib:io", "//lib:operation", "//lib:qtrajectory", - "//lib:run_qsim", + "//lib:run_qsim_deprecated", "//lib:simulator", "@com_google_googletest//:gtest_main", ], @@ -104,7 +113,7 @@ cc_test( srcs = ["circuit_qsim_parser_test.cc"], copts = select({ ":windows": windows_copts, - "//conditions:default": [], + "//conditions:default": default_copts, }), deps = [ "//lib:circuit_qsim_parser", @@ -115,13 +124,115 @@ cc_test( ], ) +cc_test( + name = "classical_control_expr_test", + size = "small", + srcs = ["classical_control_expr_test.cc"], + copts = select({ + ":windows": windows_copts, + "//conditions:default": default_copts, + }), + deps = [ + "//lib:classical_control", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "classical_control_obs_test", + size = "small", + srcs = ["classical_control_obs_test.cc"], + copts = select({ + ":windows": windows_copts, + "//conditions:default": default_copts, + }), + deps = [ + "//lib:classical_control", + "//lib:classical_control_expr", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "classical_control_parser_test", + size = "small", + srcs = ["classical_control_parser_test.cc"], + copts = select({ + ":windows": windows_copts, + "//conditions:default": default_copts, + }), + deps = [ + "//lib:classical_control", + "//lib:classical_control_expr", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "classical_control_symbol_test", + size = "small", + srcs = ["classical_control_symbol_test.cc"], + copts = select({ + ":windows": windows_copts, + "//conditions:default": default_copts, + }), + deps = [ + "//lib:classical_control", + "//lib:classical_control_expr", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "classical_control_symtab_test", + size = "small", + srcs = ["classical_control_symtab_test.cc"], + copts = select({ + ":windows": windows_copts, + "//conditions:default": default_copts, + }), + deps = [ + "//lib:classical_control", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "classical_control_tokenizer_test", + size = "small", + srcs = ["classical_control_tokenizer_test.cc"], + copts = select({ + ":windows": windows_copts, + "//conditions:default": default_copts, + }), + deps = [ + "//lib:classical_control", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "classical_control_util_test", + size = "small", + srcs = ["classical_control_util_test.cc"], + copts = select({ + ":windows": windows_copts, + "//conditions:default": default_copts, + }), + deps = [ + "//lib:classical_control", + "//lib:classical_control_expr", + "@com_google_googletest//:gtest_main", + ], +) + cc_test( name = "expect_test", size = "small", srcs = ["expect_test.cc"], copts = select({ ":windows": windows_copts, - "//conditions:default": [], + "//conditions:default": default_copts, }), deps = [ "//lib:expect", @@ -153,7 +264,7 @@ cc_test( srcs = ["fuser_basic_test.cc"], copts = select({ ":windows": windows_copts, - "//conditions:default": [], + "//conditions:default": default_copts, }), deps = [ ":fuser_testfixture", @@ -172,7 +283,7 @@ cc_test( srcs = ["fuser_mqubit_test.cc"], copts = select({ ":windows": windows_copts, - "//conditions:default": [], + "//conditions:default": default_copts, }), deps = [ ":fuser_testfixture", @@ -207,7 +318,7 @@ cc_test( srcs = ["gates_qsim_test.cc"], copts = select({ ":windows": windows_copts, - "//conditions:default": [], + "//conditions:default": default_copts, }), deps = [ "//lib:gate", @@ -222,7 +333,7 @@ cc_library( hdrs = ["hybrid_testfixture.h"], copts = select({ ":windows": windows_copts, - "//conditions:default": [], + "//conditions:default": default_copts, }), deps = [ "//lib:circuit_qsim_parser", @@ -258,7 +369,7 @@ cc_test( srcs = ["matrix_test.cc"], copts = select({ ":windows": windows_copts, - "//conditions:default": [], + "//conditions:default": default_copts, }), deps = [ "//lib:matrix", @@ -272,7 +383,7 @@ cc_test( srcs = ["operation_test.cc"], copts = select({ ":windows": windows_copts, - "//conditions:default": [], + "//conditions:default": default_copts, }), deps = [ "//lib:gate", @@ -288,7 +399,7 @@ cc_library( hdrs = ["qtrajectory_testfixture.h"], copts = select({ ":windows": windows_copts, - "//conditions:default": [], + "//conditions:default": default_copts, }), deps = [ "//lib:channel", @@ -322,7 +433,7 @@ cc_test( "//lib:fuser_mqubit", "//lib:gates_cirq", "//lib:io", - "//lib:run_qsim", + "//lib:run_qsim_deprecated", "//lib:seqfor", "//lib:simulator_avx", "@com_google_googletest//:gtest_main", @@ -335,11 +446,12 @@ cc_test( srcs = ["run_qsim_test.cc"], copts = select({ ":windows": windows_copts, - "//conditions:default": [], + "//conditions:default": default_copts, }), deps = [ ":gates_cirq_testfixture", "//lib:circuit_qsim_parser", + "//lib:classical_control", "//lib:formux", "//lib:fuser_basic", "//lib:io", @@ -350,13 +462,34 @@ cc_test( ], ) +cc_test( + name = "run_qsim_deprecated_test", + size = "small", + srcs = ["run_qsim_deprecated_test.cc"], + copts = select({ + ":windows": windows_copts, + "//conditions:default": default_copts, + }), + deps = [ + ":gates_cirq_testfixture", + "//lib:circuit_qsim_parser", + "//lib:formux", + "//lib:fuser_basic", + "//lib:io", + "//lib:operation", + "//lib:run_qsim_deprecated", + "//lib:simulator", + "@com_google_googletest//:gtest_main", + ], +) + cc_test( name = "run_qsimh_test", size = "small", srcs = ["run_qsimh_test.cc"], copts = select({ ":windows": windows_copts, - "//conditions:default": [], + "//conditions:default": default_copts, }), deps = [ ":gates_cirq_testfixture", @@ -377,7 +510,7 @@ cc_library( hdrs = ["simulator_testfixture.h"], copts = select({ ":windows": windows_copts, - "//conditions:default": [], + "//conditions:default": default_copts, }), deps = [ "//lib:expect", @@ -434,7 +567,7 @@ cc_test( srcs = ["simulator_basic_test.cc"], copts = select({ ":windows": windows_copts, - "//conditions:default": [], + "//conditions:default": default_copts, }), deps = [ ":simulator_testfixture", @@ -469,7 +602,7 @@ cc_library( hdrs = ["statespace_testfixture.h"], copts = select({ ":windows": windows_copts, - "//conditions:default": [], + "//conditions:default": default_copts, }), deps = [ "//lib:circuit_qsim_parser", @@ -477,7 +610,7 @@ cc_library( "//lib:gates_qsim", "//lib:io", "//lib:operation", - "//lib:run_qsim", + "//lib:run_qsim_deprecated", "@com_google_googletest//:gtest_main", ], ) @@ -526,7 +659,7 @@ cc_test( srcs = ["statespace_basic_test.cc"], copts = select({ ":windows": windows_copts, - "//conditions:default": [], + "//conditions:default": default_copts, }), deps = [ ":statespace_testfixture", @@ -563,7 +696,7 @@ cc_library( hdrs = ["unitaryspace_testfixture.h"], copts = select({ ":windows": windows_copts, - "//conditions:default": [], + "//conditions:default": default_copts, }), deps = [ "@com_google_googletest//:gtest_main", @@ -610,7 +743,7 @@ cc_test( srcs = ["unitaryspace_basic_test.cc"], copts = select({ ":windows": windows_copts, - "//conditions:default": [], + "//conditions:default": default_copts, }), deps = [ ":unitaryspace_testfixture", @@ -643,7 +776,7 @@ cc_library( hdrs = ["unitary_calculator_testfixture.h"], copts = select({ ":windows": windows_copts, - "//conditions:default": [], + "//conditions:default": default_copts, }), deps = [ "//lib:fuser", @@ -696,7 +829,7 @@ cc_test( srcs = ["unitary_calculator_basic_test.cc"], copts = select({ ":windows": windows_copts, - "//conditions:default": [], + "//conditions:default": default_copts, }), deps = [ ":unitary_calculator_testfixture", @@ -731,7 +864,7 @@ cc_test( srcs = ["vectorspace_test.cc"], copts = select({ ":windows": windows_copts, - "//conditions:default": [], + "//conditions:default": default_copts, }), deps = [ "//lib:formux", @@ -746,7 +879,7 @@ cc_test( srcs = ["mps_statespace_test.cc"], copts = select({ ":windows": windows_copts, - "//conditions:default": [], + "//conditions:default": default_copts, }), deps = [ "//lib:formux", @@ -761,7 +894,7 @@ cc_test( srcs = ["mps_simulator_test.cc"], copts = select({ ":windows": windows_copts, - "//conditions:default": [], + "//conditions:default": default_copts, }), deps = [ "//lib:formux", diff --git a/tests/channels_cirq_test.cc b/tests/channels_cirq_test.cc index 680b669c4..e50da4a44 100644 --- a/tests/channels_cirq_test.cc +++ b/tests/channels_cirq_test.cc @@ -27,7 +27,7 @@ #include "../lib/io.h" #include "../lib/operation.h" #include "../lib/qtrajectory.h" -#include "../lib/run_qsim.h" +#include "../lib/run_qsim_deprecated.h" #include "../lib/simmux.h" namespace qsim { diff --git a/tests/circuit_qsim_parser_test.cc b/tests/circuit_qsim_parser_test.cc index b61db1869..74ade4531 100644 --- a/tests/circuit_qsim_parser_test.cc +++ b/tests/circuit_qsim_parser_test.cc @@ -12,25 +12,50 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include +#include #include "gtest/gtest.h" #include "../lib/circuit_qsim_parser.h" +#include "../lib/classical_control_obs.h" +#include "../lib/classical_control_symbol.h" +#include "../lib/classical_control_symtab.h" #include "../lib/gate.h" #include "../lib/operation.h" #include "../lib/operation_base.h" -namespace qsim { +struct TestParserError { + template + [[noreturn]] static void Throw( + std::string_view msg, unsigned lc, Args&&... args) { + throw std::invalid_argument("syntax error"); + } +}; -struct IO { - static void errorf(const char* format, ...) {} - static void messagef(const char* format, ...) {} +struct TestRuntimeError { + template + [[noreturn]] static void Throw( + std::string_view msg, unsigned lc, Args&&... args) { + throw std::runtime_error("runtime error"); + } }; -constexpr char provider[] = "circuit_qsim_parser_test"; +using TestQSimParser = + qsim::CircuitQsimParser; + +class CircuitQsimParserTest : public ::testing::Test { + protected: + qsim::cc::SymTable symtab; -TEST(CircuitQsimParserTest, ValidCircuit) { + void SetUp() override { + auto scope = symtab.AddScope(); + symtab.EnterScope(scope); + } +}; + +namespace qsim { + +TEST_F(CircuitQsimParserTest, ValidCircuit) { constexpr char valid_circuit[] = R"(2 0 id1 0 @@ -59,21 +84,16 @@ R"(2 15 p 0.3 )"; - Circuit> circuit; - std::stringstream ss1(valid_circuit); - - EXPECT_TRUE(CircuitQsimParser::FromStream(99, provider, ss1, circuit)); - EXPECT_EQ(circuit.num_qubits, 2); - EXPECT_EQ(circuit.ops.size(), 22); - - std::stringstream ss2(valid_circuit); + auto circuit1 = TestQSimParser::Run(valid_circuit, 99); + EXPECT_EQ(circuit1.num_qubits, 2); + EXPECT_EQ(circuit1.ops.size(), 22); - EXPECT_TRUE(CircuitQsimParser::FromStream(4, provider, ss2, circuit)); - EXPECT_EQ(circuit.num_qubits, 2); - EXPECT_EQ(circuit.ops.size(), 10); + auto circuit2 = TestQSimParser::Run(valid_circuit, 4); + EXPECT_EQ(circuit2.num_qubits, 2); + EXPECT_EQ(circuit2.ops.size(), 10); } -TEST(CircuitQsimParserTest, ValidCircuitWithControlledGates) { +TEST_F(CircuitQsimParserTest, ValidCircuitWithControlledGates) { constexpr char valid_circuit[] = R"(5 0 c 0 1 h 2 @@ -83,9 +103,7 @@ R"(5 using CG = ControlledGate; - Circuit> circuit; - std::stringstream ss(valid_circuit); - EXPECT_TRUE(CircuitQsimParser::FromStream(99, provider, ss, circuit)); + auto circuit = TestQSimParser::Run(valid_circuit, 99); EXPECT_EQ(circuit.num_qubits, 5); EXPECT_EQ(circuit.ops.size(), 3); const auto* pg0 = OpGetAlternative(circuit.ops[0]); @@ -102,137 +120,101 @@ R"(5 EXPECT_EQ(pg2->controlled_by.size(), 2); } -TEST(CircuitQsimParserTest, ValidTimeOrder) { +TEST_F(CircuitQsimParserTest, ValidTimeOrder) { constexpr char valid_circuit[] = R"(4 0 cz 0 3 -2 cz 1 2 1 cz 0 3 +2 cz 1 2 3 m 1 3 h 2 4 cz 1 2 -6 cz 0 3 5 cz 1 2 -8 c 1 x 2 +6 cz 0 3 7 cz 0 3 -10 h 0 +8 c 1 x 2 9 h 1 -10 h 2 9 h 3 +10 h 0 +10 h 2 )"; - Circuit> circuit; - std::stringstream ss(valid_circuit); - EXPECT_TRUE(CircuitQsimParser::FromStream(99, provider, ss, circuit)); + auto circuit = TestQSimParser::Run(valid_circuit, 99); EXPECT_EQ(circuit.num_qubits, 4); EXPECT_EQ(circuit.ops.size(), 14); } -TEST(CircuitQsimParserTest, InvalidGateName) { +TEST_F(CircuitQsimParserTest, InvalidGateName) { constexpr char invalid_circuit[] = R"(2 0 h 0 0 h 1 1 badgate 0)"; - std::stringstream ss(invalid_circuit); - Circuit> circuit; - - EXPECT_FALSE(CircuitQsimParser::FromStream(99, provider, ss, circuit)); + EXPECT_THROW(TestQSimParser::Run(invalid_circuit, 99), std::invalid_argument); } -TEST(CircuitQsimParserTest, TrailingSpace1) { - constexpr char invalid_circuit[] = -R"(2 -0 h 0 -0 h 1 -1 cz 0 1 )"; - - std::stringstream ss(invalid_circuit); - Circuit> circuit; - - EXPECT_FALSE(CircuitQsimParser::FromStream(99, provider, ss, circuit)); -} - -TEST(CircuitQsimParserTest, TrailingCharacters) { +TEST_F(CircuitQsimParserTest, TrailingCharacters) { constexpr char invalid_circuit[] = R"(2 0 h 0 0 h 1 cc 1 cz 0 1)"; - std::stringstream ss(invalid_circuit); - Circuit> circuit; - - EXPECT_FALSE(CircuitQsimParser::FromStream(99, provider, ss, circuit)); + EXPECT_THROW(TestQSimParser::Run(invalid_circuit, 99), std::invalid_argument); } -TEST(CircuitQsimParserTest, InvalidQubitRange1) { +TEST_F(CircuitQsimParserTest, InvalidQubitRange1) { constexpr char invalid_circuit[] = R"(2 0 h 0 0 h 2 1 cz 0 1)"; - std::stringstream ss(invalid_circuit); - Circuit> circuit; - - EXPECT_FALSE(CircuitQsimParser::FromStream(99, provider, ss, circuit)); + EXPECT_THROW(TestQSimParser::Run(invalid_circuit, 99), std::invalid_argument); } -TEST(CircuitQsimParserTest, QubitIsNotNumber1) { +TEST_F(CircuitQsimParserTest, QubitIsNotNumber1) { constexpr char invalid_circuit[] = R"(2 0 h 0 0 h i 1 cz 0 1)"; - std::stringstream ss(invalid_circuit); - Circuit> circuit; - - EXPECT_FALSE(CircuitQsimParser::FromStream(99, provider, ss, circuit)); + EXPECT_THROW(TestQSimParser::Run(invalid_circuit, 99), std::invalid_argument); } -TEST(CircuitQsimParserTest, SameQubits1) { +TEST_F(CircuitQsimParserTest, SameQubits1) { constexpr char invalid_circuit[] = R"(2 0 h 0 -0 h 0 +0 h 1 1 cz 1 1)"; - std::stringstream ss(invalid_circuit); - Circuit> circuit; - - EXPECT_FALSE(CircuitQsimParser::FromStream(99, provider, ss, circuit)); + EXPECT_THROW(TestQSimParser::Run(invalid_circuit, 99), std::invalid_argument); } -TEST(CircuitQsimParserTest, InvalidSingleQubitGate) { +TEST_F(CircuitQsimParserTest, InvalidSingleQubitGate) { constexpr char invalid_circuit[] = R"(2 0 h 0 0 h 1 cz 0 1)"; - std::stringstream ss(invalid_circuit); - Circuit> circuit; - - EXPECT_FALSE(CircuitQsimParser::FromStream(99, provider, ss, circuit)); + EXPECT_THROW(TestQSimParser::Run(invalid_circuit, 99), std::invalid_argument); } -TEST(CircuitQsimParserTest, InvalidTwoQubitGate) { +TEST_F(CircuitQsimParserTest, InvalidTwoQubitGate) { constexpr char invalid_circuit[] = R"(2 0 h 0 0 h 1 cz 0)"; - std::stringstream ss(invalid_circuit); - Circuit> circuit; - - EXPECT_FALSE(CircuitQsimParser::FromStream(99, provider, ss, circuit)); + EXPECT_THROW(TestQSimParser::Run(invalid_circuit, 99), std::invalid_argument); } -TEST(CircuitQsimParserTest, InvalidRxGate) { +TEST_F(CircuitQsimParserTest, InvalidRxGate) { constexpr char invalid_circuit[] = R"(2 0 h 0 @@ -240,13 +222,10 @@ R"(2 1 cz 0 1 2 rx 0)"; - std::stringstream ss(invalid_circuit); - Circuit> circuit; - - EXPECT_FALSE(CircuitQsimParser::FromStream(99, provider, ss, circuit)); + EXPECT_THROW(TestQSimParser::Run(invalid_circuit, 99), std::invalid_argument); } -TEST(CircuitQsimParserTest, InvalidRyGate) { +TEST_F(CircuitQsimParserTest, InvalidRyGate) { constexpr char invalid_circuit[] = R"(2 0 h 0 @@ -254,13 +233,10 @@ R"(2 1 cz 0 1 2 ry 0)"; - std::stringstream ss(invalid_circuit); - Circuit> circuit; - - EXPECT_FALSE(CircuitQsimParser::FromStream(99, provider, ss, circuit)); + EXPECT_THROW(TestQSimParser::Run(invalid_circuit, 99), std::invalid_argument); } -TEST(CircuitQsimParserTest, InvalidRzGate) { +TEST_F(CircuitQsimParserTest, InvalidRzGate) { constexpr char invalid_circuit[] = R"(2 0 h 0 @@ -268,13 +244,10 @@ R"(2 1 cz 0 1 2 rz 0)"; - std::stringstream ss(invalid_circuit); - Circuit> circuit; - - EXPECT_FALSE(CircuitQsimParser::FromStream(99, provider, ss, circuit)); + EXPECT_THROW(TestQSimParser::Run(invalid_circuit, 99), std::invalid_argument); } -TEST(CircuitQsimParserTest, InvalidRxyGate) { +TEST_F(CircuitQsimParserTest, InvalidRxyGate) { constexpr char invalid_circuit[] = R"(2 0 h 0 @@ -282,167 +255,117 @@ R"(2 1 cz 0 1 2 rxy 0 0.7)"; - std::stringstream ss(invalid_circuit); - Circuit> circuit; - - EXPECT_FALSE(CircuitQsimParser::FromStream(99, provider, ss, circuit)); + EXPECT_THROW(TestQSimParser::Run(invalid_circuit, 99), std::invalid_argument); } -TEST(CircuitQsimParserTest, InvalidFsimGate) { +TEST_F(CircuitQsimParserTest, InvalidFsimGate) { constexpr char invalid_circuit[] = R"(2 0 h 0 0 h 1 1 fs 0 1 0.5)"; - std::stringstream ss(invalid_circuit); - Circuit> circuit; - - EXPECT_FALSE(CircuitQsimParser::FromStream(99, provider, ss, circuit)); + EXPECT_THROW(TestQSimParser::Run(invalid_circuit, 99), std::invalid_argument); } -TEST(CircuitQsimParserTest, InvalidCpGate) { +TEST_F(CircuitQsimParserTest, InvalidCpGate) { constexpr char invalid_circuit[] = R"(2 0 h 0 0 h 1 1 cp 0 1)"; - std::stringstream ss(invalid_circuit); - Circuit> circuit; - - EXPECT_FALSE(CircuitQsimParser::FromStream(99, provider, ss, circuit)); + EXPECT_THROW(TestQSimParser::Run(invalid_circuit, 99), std::invalid_argument); } -TEST(CircuitQsimParserTest, TimeOutOfOrder) { +TEST_F(CircuitQsimParserTest, TimeOutOfOrder) { constexpr char invalid_circuit[] = R"(2 0 h 0 0 h 1 -1 cz 0 1 2 t 0 2 t 1 1 cz 0 1)"; - std::stringstream ss(invalid_circuit); - Circuit> circuit; - - EXPECT_FALSE(CircuitQsimParser::FromStream(99, provider, ss, circuit)); + EXPECT_THROW(TestQSimParser::Run(invalid_circuit, 99), std::invalid_argument); } -TEST(CircuitQsimParserTest, InvalidQubitRange2) { +TEST_F(CircuitQsimParserTest, InvalidQubitRange2) { constexpr char invalid_circuit[] = R"(2 0 h 0 0 h 1 1 m 0 2)"; - std::stringstream ss(invalid_circuit); - Circuit> circuit; - - EXPECT_FALSE(CircuitQsimParser::FromStream(99, provider, ss, circuit)); + EXPECT_THROW(TestQSimParser::Run(invalid_circuit, 99), std::invalid_argument); } -TEST(CircuitQsimParserTest, QubitIsNotNumber2) { +TEST_F(CircuitQsimParserTest, QubitIsNotNumber2) { constexpr char invalid_circuit[] = R"(2 0 h 0 0 h 1 -1 m 0 i)"; - - std::stringstream ss(invalid_circuit); - Circuit> circuit; - - EXPECT_FALSE(CircuitQsimParser::FromStream(99, provider, ss, circuit)); -} - -TEST(CircuitQsimParserTest, TrailingSpace2) { - constexpr char invalid_circuit[] = -R"(2 -0 h 0 -0 h 1 -1 m 0 )"; - - std::stringstream ss(invalid_circuit); - Circuit> circuit; +1 m i 1)"; - EXPECT_FALSE(CircuitQsimParser::FromStream(99, provider, ss, circuit)); + EXPECT_THROW(TestQSimParser::Run(invalid_circuit, 99), std::invalid_argument); } -TEST(CircuitQsimParserTest, SameQubits2) { +TEST_F(CircuitQsimParserTest, SameQubits2) { constexpr char invalid_circuit[] = R"(2 0 h 0 0 h 1 1 m 0 0)"; - std::stringstream ss(invalid_circuit); - Circuit> circuit; - - EXPECT_FALSE(CircuitQsimParser::FromStream(99, provider, ss, circuit)); + EXPECT_THROW(TestQSimParser::Run(invalid_circuit, 99), std::invalid_argument); } -TEST(CircuitQsimParserTest, NoControlQubits) { +TEST_F(CircuitQsimParserTest, NoControlQubits) { constexpr char invalid_circuit[] = R"(4 0 c is 0 1 )"; - std::stringstream ss(invalid_circuit); - Circuit> circuit; - - EXPECT_FALSE(CircuitQsimParser::FromStream(99, provider, ss, circuit)); + EXPECT_THROW(TestQSimParser::Run(invalid_circuit, 99), std::invalid_argument); } -TEST(CircuitQsimParserTest, InvalidControlQubitRange) { +TEST_F(CircuitQsimParserTest, InvalidControlQubitRange) { constexpr char invalid_circuit[] = R"(4 0 c 1 5 is 2 3 )"; - std::stringstream ss(invalid_circuit); - Circuit> circuit; - - EXPECT_FALSE(CircuitQsimParser::FromStream(99, provider, ss, circuit)); + EXPECT_THROW(TestQSimParser::Run(invalid_circuit, 99), std::invalid_argument); } -TEST(CircuitQsimParserTest, ControlQubitIsNotNumber) { +TEST_F(CircuitQsimParserTest, ControlQubitIsNotNumber) { constexpr char invalid_circuit[] = R"(4 1 c 3 x is 0 1 )"; - std::stringstream ss(invalid_circuit); - Circuit> circuit; - - EXPECT_FALSE(CircuitQsimParser::FromStream(99, provider, ss, circuit)); + EXPECT_THROW(TestQSimParser::Run(invalid_circuit, 99), std::invalid_argument); } -TEST(CircuitQsimParserTest, SameControlQubits) { +TEST_F(CircuitQsimParserTest, SameControlQubits) { constexpr char invalid_circuit[] = R"(4 0 c 1 1 is 2 3 )"; - std::stringstream ss(invalid_circuit); - Circuit> circuit; - - EXPECT_FALSE(CircuitQsimParser::FromStream(99, provider, ss, circuit)); + EXPECT_THROW(TestQSimParser::Run(invalid_circuit, 99), std::invalid_argument); } -TEST(CircuitQsimParserTest, SameControlAndTargetQubits) { +TEST_F(CircuitQsimParserTest, SameControlAndTargetQubits) { constexpr char invalid_circuit[] = R"(4 0 c 1 2 is 0 1 )"; - std::stringstream ss(invalid_circuit); - Circuit> circuit; - - EXPECT_FALSE(CircuitQsimParser::FromStream(99, provider, ss, circuit)); + EXPECT_THROW(TestQSimParser::Run(invalid_circuit, 99), std::invalid_argument); } -TEST(CircuitQsimParserTest, OverlappingQubits1) { +TEST_F(CircuitQsimParserTest, OverlappingQubits1) { constexpr char invalid_circuit[] = R"(4 0 h 0 @@ -450,13 +373,10 @@ R"(4 0 t 0 )"; - std::stringstream ss(invalid_circuit); - Circuit> circuit; - - EXPECT_FALSE(CircuitQsimParser::FromStream(99, provider, ss, circuit)); + EXPECT_THROW(TestQSimParser::Run(invalid_circuit, 99), std::invalid_argument); } -TEST(CircuitQsimParserTest, OverlappingQubits2) { +TEST_F(CircuitQsimParserTest, OverlappingQubits2) { constexpr char invalid_circuit[] = R"(4 0 h 0 @@ -464,13 +384,10 @@ R"(4 0 c 0 2 t 3 )"; - std::stringstream ss(invalid_circuit); - Circuit> circuit; - - EXPECT_FALSE(CircuitQsimParser::FromStream(99, provider, ss, circuit)); + EXPECT_THROW(TestQSimParser::Run(invalid_circuit, 99), std::invalid_argument); } -TEST(CircuitQsimParserTest, InvalidTimeOrder1) { +TEST_F(CircuitQsimParserTest, InvalidTimeOrder1) { constexpr char invalid_circuit[] = R"(4 0 cz 0 1 @@ -478,13 +395,10 @@ R"(4 1 cz 1 2 )"; - std::stringstream ss(invalid_circuit); - Circuit> circuit; - - EXPECT_FALSE(CircuitQsimParser::FromStream(99, provider, ss, circuit)); + EXPECT_THROW(TestQSimParser::Run(invalid_circuit, 99), std::invalid_argument); } -TEST(CircuitQsimParserTest, InvalidTimeOrder2) { +TEST_F(CircuitQsimParserTest, InvalidTimeOrder2) { constexpr char invalid_circuit[] = R"(4 0 cz 0 1 @@ -493,13 +407,10 @@ R"(4 1 m 1 2 )"; - std::stringstream ss(invalid_circuit); - Circuit> circuit; - - EXPECT_FALSE(CircuitQsimParser::FromStream(99, provider, ss, circuit)); + EXPECT_THROW(TestQSimParser::Run(invalid_circuit, 99), std::invalid_argument); } -TEST(CircuitQsimParserTest, InvalidTimeOrder3) { +TEST_F(CircuitQsimParserTest, InvalidTimeOrder3) { constexpr char invalid_circuit[] = R"(4 0 cz 0 1 @@ -508,14 +419,212 @@ R"(4 1 cz 1 2 )"; - std::stringstream ss(invalid_circuit); - Circuit> circuit; - - EXPECT_FALSE(CircuitQsimParser::FromStream(99, provider, ss, circuit)); + EXPECT_THROW(TestQSimParser::Run(invalid_circuit, 99), std::invalid_argument); } } // namespace qsim +namespace qsim::cc { +namespace { + +TEST_F(CircuitQsimParserTest, ParsesSimpleSymbolDefinitions) { + std::string_view sym_defs = "nq = 5 c1 = 1 c2 = c1 * 2 f1 = 3.14 * c1"; + + ParseSymbols(sym_defs, symtab); + + const Symbol* nq = symtab.Lookup("nq"); + ASSERT_NE(nq, nullptr); + EXPECT_EQ(nq->GetInt(), 5); + + const Symbol* c1 = symtab.Lookup("c1"); + ASSERT_NE(c1, nullptr); + EXPECT_EQ(c1->GetInt(), 1); + + const Symbol* c2 = symtab.Lookup("c2"); + ASSERT_NE(c2, nullptr); + EXPECT_EQ(c2->GetInt(), 2); + + const Symbol* f1 = symtab.Lookup("f1"); + ASSERT_NE(f1, nullptr); + EXPECT_TRUE(f1->IsFloat()); + EXPECT_DOUBLE_EQ(f1->GetFloat(), 3.14); +} + +TEST_F(CircuitQsimParserTest, ParseSymbolsHandlesSemicolonsAndDelimiters) { + std::string_view sym_defs = "a = 10; b = a + 5;\n c = b * 2"; + + ParseSymbols(sym_defs, symtab); + + EXPECT_EQ(symtab.Lookup("a")->GetInt(), 10); + EXPECT_EQ(symtab.Lookup("b")->GetInt(), 15); + EXPECT_EQ(symtab.Lookup("c")->GetInt(), 30); +} + +TEST_F(CircuitQsimParserTest, ParseSymbolsThrowsOnInvalidSyntax) { + std::string_view bad_defs = "a 10"; // Missing '=' + + EXPECT_THROW( + (ParseSymbols(bad_defs, symtab)), + std::invalid_argument); +} + +TEST_F(CircuitQsimParserTest, ParsesConstantDeclarationsAndQubitCount) { + std::string circuit_str = R"( + 2 # 2-qubit circuit + const float phi = 3.14159 / 2.0 + 0 h 0 + 1 rx 1 phi + )"; + + auto [circuit, obss] = TestQSimParser::Run(circuit_str, + /*max_depth=*/100, symtab); + + EXPECT_EQ(circuit.num_qubits, 2u); + EXPECT_TRUE(obss.Empty()); + + // Verify symbol table contains constants from circuit + const Symbol* phi = symtab.Lookup("phi"); + ASSERT_NE(phi, nullptr); + EXPECT_TRUE(phi->IsFloat()); + EXPECT_TRUE(phi->IsReadOnly()); +} + +TEST_F(CircuitQsimParserTest, ParsesClassicalControlAndHistograms) { + std::string circuit_str = R"( + 2 + h 0 + m 0 1 m1 + if m1[0] ^ m1[1] + x 1 + end + histogram m1 + )"; + + auto [circuit, obss] = TestQSimParser::Run(circuit_str, 100, symtab); + + EXPECT_EQ(circuit.num_qubits, 2u); + + // Verify histogram observable was registered + const Observable* hist = obss.Lookup("m1"); + ASSERT_NE(hist, nullptr); + EXPECT_EQ(hist->Size(), 2u); // 2 qubits measured in m1 tag +} + +TEST_F(CircuitQsimParserTest, ImplicitTimeTagAssignmentOrdering) { + // Gates without time tags should automatically increment time step per qubit + std::string circuit_str = R"( + 2 + h 0 + h 1 + cz 0 1 + )"; + + auto [circuit, _] = TestQSimParser::Run(circuit_str, 100, symtab); + + ASSERT_EQ(circuit.ops.size(), 3u); + + auto& bop0 = OpBaseOperation(circuit.ops[0]); + EXPECT_EQ(bop0.time, 0); + EXPECT_EQ(bop0.qubits.size(), 1); + + auto& bop1 = OpBaseOperation(circuit.ops[1]); + EXPECT_EQ(bop1.time, 0); + EXPECT_EQ(bop1.qubits.size(), 1); + + auto& bop2 = OpBaseOperation(circuit.ops[2]); + EXPECT_EQ(bop2.time, 1); + EXPECT_EQ(bop2.qubits.size(), 2); +} + +TEST_F(CircuitQsimParserTest, ExpressionQubitIndices) { + std::string circuit_str = R"( + 5 + const int i = 0 + const int j = 1 + const int k = 2 + const int qs(nq) + h qs[0] + 1 + h qs[1] - 1 + h qs[2] + h qs[3] + h qs[4] + cz i + 1 j * k + m i k (i + 3) + m qs[0] i + k + k m1 + c qs[0] qs[3] + 1 cx qs[1] j + 1 + )"; + + auto [circuit, _] = TestQSimParser::Run(circuit_str, 100, symtab); + + ASSERT_EQ(circuit.ops.size(), 9u); + + // h. + auto& bop0 = OpBaseOperation(circuit.ops[0]); + EXPECT_EQ(bop0.time, 0); + ASSERT_EQ(bop0.qubits.size(), 1); + EXPECT_EQ(bop0.qubits[0], 1); + + // h. + auto& bop1 = OpBaseOperation(circuit.ops[1]); + EXPECT_EQ(bop1.time, 0); + ASSERT_EQ(bop1.qubits.size(), 1); + EXPECT_EQ(bop1.qubits[0], 0); + + // h. + auto& bop2 = OpBaseOperation(circuit.ops[2]); + EXPECT_EQ(bop2.time, 0); + ASSERT_EQ(bop2.qubits.size(), 1); + EXPECT_EQ(bop2.qubits[0], 2); + + // h. + auto& bop3 = OpBaseOperation(circuit.ops[3]); + EXPECT_EQ(bop3.time, 0); + ASSERT_EQ(bop3.qubits.size(), 1); + EXPECT_EQ(bop3.qubits[0], 3); + + // h. + auto& bop4 = OpBaseOperation(circuit.ops[4]); + EXPECT_EQ(bop4.time, 0); + ASSERT_EQ(bop4.qubits.size(), 1); + EXPECT_EQ(bop4.qubits[0], 4); + + // cz + auto& bop5 = OpBaseOperation(circuit.ops[5]); + EXPECT_EQ(bop5.time, 1); + ASSERT_EQ(bop5.qubits.size(), 2); + EXPECT_EQ(bop5.qubits[0], 1); + EXPECT_EQ(bop5.qubits[1], 2); + + // m. + auto& bop6 = OpBaseOperation(circuit.ops[6]); + EXPECT_EQ(bop6.time, 2); + ASSERT_EQ(bop6.qubits.size(), 3); + EXPECT_EQ(bop6.qubits[0], 0); + EXPECT_EQ(bop6.qubits[1], 2); + EXPECT_EQ(bop6.qubits[2], 3); + + // m. + auto& bop7 = OpBaseOperation(circuit.ops[7]); + EXPECT_EQ(bop7.time, 3); + ASSERT_EQ(bop7.qubits.size(), 2); + EXPECT_EQ(bop7.qubits[0], 0); + EXPECT_EQ(bop7.qubits[1], 4); + + // c. + auto* bop8 = OpGetAlternative>(circuit.ops[8]); + ASSERT_NE(bop8, nullptr); + EXPECT_EQ(bop8->time, 4); + ASSERT_EQ(bop8->qubits.size(), 2); + EXPECT_EQ(bop8->qubits[0], 1); + EXPECT_EQ(bop8->qubits[1], 2); + ASSERT_EQ(bop8->controlled_by.size(), 2); + EXPECT_EQ(bop8->controlled_by[0], 0); + EXPECT_EQ(bop8->controlled_by[1], 4); +} + +} // namespace +} // namespace qsim::cc + int main(int argc, char** argv) { testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); diff --git a/tests/classical_control_expr_test.cc b/tests/classical_control_expr_test.cc new file mode 100644 index 000000000..d04e95725 --- /dev/null +++ b/tests/classical_control_expr_test.cc @@ -0,0 +1,143 @@ +#include + +#include + +#include "../lib/classical_control_expr.h" +#include "../lib/classical_control_symbol.h" +#include "../lib/classical_control_symtab.h" + +namespace qsim::cc { +namespace { + +// Helper fixture to set up a populated SymTable +class ExprTest : public ::testing::Test { + protected: + SymTable symtab; + + void SetUp() override { + auto scope = symtab.AddScope(); + symtab.EnterScope(scope); + + // Insert constants and variables + symtab.Insert("const_int", Symbol(TInt{42}, /*read_only=*/true)); + symtab.Insert("const_float", Symbol(TFloat{3.14}, /*read_only=*/true)); + symtab.Insert("var_int", Symbol(TInt{10}, /*read_only=*/false)); + + // Vector symbol + Symbol::IntVector vec = {100, 200, 300}; + symtab.Insert("int_vec", Symbol(vec, /*read_only=*/true)); + + // Quantum Measurement symbol (0b1011 = bit0:1, bit1:1, bit2:0, bit3:1) + Symbol::Mea mea{.bits = 0b1011, .num_bits = 4, .time = 0}; + symtab.Insert("m_out", Symbol(mea)); // Mea is always read_only = false + } +}; + +TEST_F(ExprTest, IndexEvaluation) { + Index i_int = TInt{2}; + Index i_bool = TBool{true}; + Index i_func = TFuncI{[](const SymTable&) { return TInt{1}; }}; + + EXPECT_TRUE(IsConstIndex(symtab, i_int)); + EXPECT_TRUE(IsConstIndex(symtab, i_bool)); + EXPECT_FALSE(IsConstIndex(symtab, i_func)); + + EXPECT_EQ(EvalIndex(symtab, i_int), 2u); + EXPECT_EQ(EvalIndex(symtab, i_bool), 1u); + EXPECT_EQ(EvalIndex(symtab, i_func), 1u); +} + +TEST_F(ExprTest, EvalLiterals) { + Expr e_int = TInt{5}; + Expr e_float = TFloat{2.5}; + Expr e_bool = TBool{true}; + + EXPECT_DOUBLE_EQ(EvalExpr(symtab, e_int), 5.0); + EXPECT_DOUBLE_EQ(EvalExpr(symtab, e_float), 2.5); + EXPECT_DOUBLE_EQ(EvalExpr(symtab, e_bool), 1.0); + + EXPECT_EQ(EvalIntExpr(symtab, e_int), 5); + EXPECT_EQ(EvalIntExpr(symtab, e_bool), 1); + + EXPECT_TRUE(EvalCondExpr(symtab, e_int)); + EXPECT_TRUE(EvalCondExpr(symtab, e_bool)); +} + +TEST_F(ExprTest, EvalNestedFunctions) { + Expr l = TInt{10}; + Expr r = TInt{20}; + + // Addition function closure: l + r + Expr add_func = TFuncI{[l, r](const SymTable& st) -> TInt { + return EvalIntExpr(st, l) + EvalIntExpr(st, r); + }}; + + // Nested multiplication closure: (l + r) * 2 + Expr nested_func = TFuncI{[add_func](const SymTable& st) -> TInt { + return EvalIntExpr(st, add_func) * 2; + }}; + + EXPECT_EQ(EvalIntExpr(symtab, nested_func), 60); + EXPECT_DOUBLE_EQ(EvalExpr(symtab, nested_func), 60.0); + EXPECT_TRUE(EvalCondExpr(symtab, nested_func)); +} + +TEST_F(ExprTest, EvalSymbolLookups) { + Expr e_sym = TSymbol{"const_int"}; + EXPECT_EQ(EvalIntExpr(symtab, e_sym), 42); + EXPECT_DOUBLE_EQ(EvalExpr(symtab, e_sym), 42.0); + + // Indexing vector: int_vec[1] + Expr e_vec_ind = TSymbolInd{"int_vec", TInt{1}}; + EXPECT_EQ(EvalIntExpr(symtab, e_vec_ind), 200); + + // Indexing measurement bits: m_out[3] -> 1 + Expr e_mea_ind = TSymbolInd{"m_out", TInt{3}}; + EXPECT_EQ(EvalIntExpr(symtab, e_mea_ind), 1); +} + +TEST_F(ExprTest, ConvertibleToIntChecks) { + Expr e_int = TInt{1}; + Expr e_float = TFloat{1.5}; + Expr e_func_f = TFuncF{[](const SymTable&) { return 1.5; }}; + Expr e_sym_int = TSymbol{"const_int"}; + Expr e_sym_float = TSymbol{"const_float"}; + + EXPECT_TRUE(IsConvertibleToInt(symtab, e_int)); + EXPECT_FALSE(IsConvertibleToInt(symtab, e_float)); + EXPECT_FALSE(IsConvertibleToInt(symtab, e_func_f)); + EXPECT_TRUE(IsConvertibleToInt(symtab, e_sym_int)); + EXPECT_FALSE(IsConvertibleToInt(symtab, e_sym_float)); +} + +TEST_F(ExprTest, ConstExprChecks) { + Expr e_literal = TFloat{3.14}; + Expr e_func = TFuncI{[](const SymTable&) { return 1; }}; + Expr e_const_sym = TSymbol{"const_int"}; + Expr e_var_sym = TSymbol{"var_int"}; + + EXPECT_TRUE(IsConstExpr(symtab, e_literal)); + EXPECT_FALSE(IsConstExpr(symtab, e_func)); + EXPECT_TRUE(IsConstExpr(symtab, e_const_sym)); + EXPECT_FALSE(IsConstExpr(symtab, e_var_sym)); +} + +TEST_F(ExprTest, FloatToIntConversionThrows) { + Expr e_float = TFloat{3.14}; + EXPECT_THROW(EvalIntExpr(symtab, e_float), std::runtime_error); + EXPECT_THROW(EvalCondExpr(symtab, e_float), std::runtime_error); +} + +TEST_F(ExprTest, MissingSymbolLookupThrows) { + Expr e_missing = TSymbol{"nonexistent_var"}; + EXPECT_THROW(EvalExpr(symtab, e_missing), std::runtime_error); + EXPECT_THROW(EvalIntExpr(symtab, e_missing), std::runtime_error); +} + +} // namespace +} // namespace qsim::cc + +int main(int argc, char** argv) { + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/tests/classical_control_obs_test.cc b/tests/classical_control_obs_test.cc new file mode 100644 index 000000000..ca5b60372 --- /dev/null +++ b/tests/classical_control_obs_test.cc @@ -0,0 +1,133 @@ +#include + +#include +#include +#include + +#include "../lib/classical_control_obs.h" + +namespace qsim::cc { +namespace { + +TEST(MeasurementHistogramTest, DefaultConstructor) { + MeasurementHistogram hist; + EXPECT_EQ(hist.Size(), 0u); + EXPECT_TRUE(hist.cur_count.empty()); + EXPECT_TRUE(hist.total_count.empty()); +} + +TEST(MeasurementHistogramTest, ParameterizedConstructorAllocatesCorrectSize) { + unsigned num_qubits = 3; + MeasurementHistogram hist(num_qubits); + + EXPECT_EQ(hist.Size(), 3u); + // 2^3 = 8 outcome entries + EXPECT_EQ(hist.cur_count.size(), 8u); + EXPECT_EQ(hist.total_count.size(), 8u); + + for (uint64_t val : hist.cur_count) { + EXPECT_EQ(val, 0u); + } + for (uint64_t val : hist.total_count) { + EXPECT_EQ(val, 0u); + } +} + +TEST(MeasurementHistogramTest, UpdateAccumulatesAndResetsCurrentCounts) { + MeasurementHistogram hist(2); // 4 outcomes: 00, 01, 10, 11 + + // First Trajectory + hist.cur_count[0b00] = 5; + hist.cur_count[0b11] = 2; + hist.Update(); + + // Verify total count updated and cur_count reset + EXPECT_EQ(hist.total_count[0b00], 5u); + EXPECT_EQ(hist.total_count[0b11], 2u); + EXPECT_EQ(hist.cur_count[0b00], 0u); + EXPECT_EQ(hist.cur_count[0b11], 0u); + + // Second Trajectory + hist.cur_count[0b00] = 3; + hist.cur_count[0b01] = 4; + hist.Update(); + + EXPECT_EQ(hist.total_count[0b00], 8u); // 5 + 3 + EXPECT_EQ(hist.total_count[0b01], 4u); + EXPECT_EQ(hist.total_count[0b11], 2u); + EXPECT_EQ(hist.cur_count[0b00], 0u); +} + +TEST(MeasurementHistogramTest, DiscardClearsCurrentCountsWithoutUpdatingTotal) { + MeasurementHistogram hist(2); + + hist.total_count[0b00] = 10; + hist.cur_count[0b00] = 5; + hist.cur_count[0b10] = 3; + + hist.Discard(); + + // Verify cur_count cleared and total_count unchanged + EXPECT_EQ(hist.cur_count[0b00], 0u); + EXPECT_EQ(hist.cur_count[0b10], 0u); + EXPECT_EQ(hist.total_count[0b00], 10u); + EXPECT_EQ(hist.total_count[0b10], 0u); +} + +TEST(ObservablesTest, InitialStateIsEmpty) { + Observables obss; + EXPECT_TRUE(obss.Empty()); + EXPECT_EQ(obss.Lookup("nonexistent"), nullptr); +} + +TEST(ObservablesTest, InsertAndLookup) { + Observables obss; + + Observable* inserted = obss.Insert("m0", MeasurementHistogram(2)); + ASSERT_NE(inserted, nullptr); + EXPECT_FALSE(obss.Empty()); + EXPECT_EQ(inserted->Size(), 2u); + + // Lookup non-const + Observable* found = obss.Lookup("m0"); + ASSERT_NE(found, nullptr); + EXPECT_EQ(found, inserted); + + // Const Lookup + const Observables& const_obss = obss; + const Observable* const_found = const_obss.Lookup("m0"); + ASSERT_NE(const_found, nullptr); + EXPECT_EQ(const_found->Size(), 2u); +} + +TEST(ObservablesTest, IteratesOverRegisteredObservables) { + Observables obss; + obss.Insert("h1", MeasurementHistogram(1)); + obss.Insert("h2", MeasurementHistogram(2)); + + std::unordered_map visited; + + obss.Iterate([&visited](std::string_view name, Observable& obs) { + visited[name] = obs.Size(); + }); + + EXPECT_EQ(visited.size(), 2u); + EXPECT_EQ(visited["h1"], 1u); + EXPECT_EQ(visited["h2"], 2u); + + // Const iteration check + const Observables& const_obss = obss; + std::size_t const_count = 0; + const_obss.Iterate([&const_count](std::string_view, const Observable&) { + ++const_count; + }); + EXPECT_EQ(const_count, 2u); +} + +} // namespace +} // namespace qsim::cc + +int main(int argc, char** argv) { + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/tests/classical_control_parser_test.cc b/tests/classical_control_parser_test.cc new file mode 100644 index 000000000..3b717b8c9 --- /dev/null +++ b/tests/classical_control_parser_test.cc @@ -0,0 +1,338 @@ +#include + +#include +#include +#include +#include +#include + +#include "../lib/classical_control_expr.h" +#include "../lib/classical_control_parser.h" +#include "../lib/classical_control_symbol.h" +#include "../lib/classical_control_symtab.h" +#include "../lib/classical_control_tokenizer.h" + +namespace qsim::cc { +namespace { + +struct TestParserError { + template + [[noreturn]] static void Throw( + std::string_view msg, unsigned lc, Args&&... args) { + throw std::runtime_error("syntax error"); + } +}; + +struct TestRuntimeError { + template + [[noreturn]] static void Throw( + std::string_view msg, unsigned lc, Args&&... args) { + throw std::runtime_error("runtime error"); + } +}; + +using TestIndexParser = IndexParser; + +using TestExprParser = ExprParser; + +using TestIntExprParser = ExprParser; + +class ParserTest : public ::testing::Test { + protected: + SymTable symtab; + + void SetUp() override { + auto scope = symtab.AddScope(); + symtab.EnterScope(scope); + + // Constant symbols (must fold to literals at compile-time) + symtab.Insert("c_int", Symbol(TInt{10}, /*read_only=*/true)); + symtab.Insert("c_float", Symbol(TFloat{2.5}, /*read_only=*/true)); + + // Variable symbols (must generate runtime closures) + symtab.Insert("v_int", Symbol(TInt{5}, /*read_only=*/false)); + symtab.Insert("v_float", Symbol(TFloat{1.5}, /*read_only=*/false)); + + // Vector and measurement symbols + symtab.Insert( + "vec", Symbol(Symbol::IntVector{10, 20, 30}, /*read_only=*/true)); + + // Mea bits: b0=1, b1=0, b2=1 (val = 5) + Symbol::Mea mea{.bits = 0b101, .num_bits = 3, .time = 0}; + symtab.Insert("m0", Symbol(mea)); + } +}; + +TEST_F(ParserTest, ConstantExpressionsFoldToScalarLiterals) { + Tokenizer tok("2 * c_int + 4"); + Expr expr = TestExprParser::Run(symtab, tok); + + // Must hold TInt directly (folded at compile-time) + ASSERT_TRUE(std::holds_alternative(expr)); + EXPECT_EQ(std::get(expr), 24); +} + +TEST_F(ParserTest, ConstantFloatExpressionsFoldToFloatLiterals) { + Tokenizer tok("c_float * 2.0"); + Expr expr = TestExprParser::Run(symtab, tok); + + // Must hold TFloat directly + ASSERT_TRUE(std::holds_alternative(expr)); + EXPECT_DOUBLE_EQ(std::get(expr), 5.0); +} + +TEST_F(ParserTest, ConstantComparisonFoldsToBoolLiteral) { + Tokenizer tok("c_int == 10"); + Expr expr = TestExprParser::Run(symtab, tok); + + // Must hold TBool directly + ASSERT_TRUE(std::holds_alternative(expr)); + EXPECT_TRUE(std::get(expr)); +} + +TEST_F(ParserTest, VariableExpressionProducesRuntimeClosure) { + Tokenizer tok("v_int * 2 + 1"); + Expr expr = TestExprParser::Run(symtab, tok); + + // Must NOT be a scalar literal; must produce an executable TFuncI closure + EXPECT_FALSE(std::holds_alternative(expr)); + EXPECT_TRUE(std::holds_alternative(expr)); + + // Evaluate closure at runtime + EXPECT_EQ(EvalIntExpr(symtab, expr), 11); +} + +TEST_F(ParserTest, ParserStopsWhenOperatorsExhausted) { + Tokenizer tok("2 * c_int 3 * v_int"); + + // First run consumes "2 * c_int" + Expr e1 = TestExprParser::Run(symtab, tok); + ASSERT_TRUE(std::holds_alternative(e1)); + EXPECT_EQ(std::get(e1), 20); + + // Next token remaining in stream must be "3" + EXPECT_EQ(tok.Peek().val, "3"); + + // Second run consumes "3 * v_int" + Expr e2 = TestExprParser::Run(symtab, tok); + EXPECT_EQ(EvalIntExpr(symtab, e2), 15); +} + +TEST_F(ParserTest, ParserStopsAtParenthesis) { + Tokenizer tok("2 * c_int (-3 * v_int)"); + + // Should parse as single expression: "2 * c_int" = 20 + Expr expr = TestExprParser::Run(symtab, tok); + EXPECT_EQ(EvalIntExpr(symtab, expr), 20); +} + +TEST_F(ParserTest, IndexParserEvaluatesBracketedIndex) { + Tokenizer tok("[1 + 1]"); + Index idx = TestIndexParser::Run(symtab, tok); + + // Constant index folds to TInt + ASSERT_TRUE(std::holds_alternative(idx)); + EXPECT_EQ(EvalIndex(symtab, idx), 2u); +} + +TEST_F(ParserTest, VectorAndMeasurementIndexing) { + Tokenizer tok("vec[1] + m0[0]"); // vec[1] = 20, m0[0] = 1 -> 21 + Expr expr = TestExprParser::Run(symtab, tok); + + EXPECT_EQ(EvalIntExpr(symtab, expr), 21); +} + +TEST_F(ParserTest, IntOnlyParserRejectsFloatsAndPowerOperator) { + Tokenizer tok_float("2.5 + 1"); + EXPECT_THROW(TestIntExprParser::Run(symtab, tok_float), std::runtime_error); + + Tokenizer tok_pow("2 ** 3"); + EXPECT_THROW(TestIntExprParser::Run(symtab, tok_pow), std::runtime_error); +} + +TEST_F(ParserTest, TriggersMeasurementCallback) { + Tokenizer tok("m0 + 1"); + bool callback_called = false; + + auto callback = [&callback_called]( + const Symbol::Mea& mea, std::string_view name) { + callback_called = true; + EXPECT_EQ(name, "m0"); + EXPECT_EQ(mea.num_bits, 3u); + }; + + Expr expr = TestExprParser::Run(symtab, tok, callback); + EXPECT_TRUE(callback_called); + EXPECT_EQ(EvalIntExpr(symtab, expr), 6); // m0 bitstring = 5; 5 + 1 = 6 +} + +class ComplexParserTest : public ::testing::Test { + protected: + SymTable symtab; + + void SetUp() override { + auto scope = symtab.AddScope(); + symtab.EnterScope(scope); + + // Integer symbols and vector + symtab.Insert("a", Symbol(TInt{3}, /*read_only=*/true)); + symtab.Insert("b", Symbol(TInt{7}, /*read_only=*/true)); + symtab.Insert("c", Symbol(TInt{2}, /*read_only=*/true)); + symtab.Insert( + "ivec", Symbol(Symbol::IntVector{5, 12, 18, 42}, /*read_only=*/true)); + + // Float symbols and vector + symtab.Insert("x", Symbol(TFloat{2.5}, /*read_only=*/true)); + symtab.Insert("y", Symbol(TFloat{0.5}, /*read_only=*/true)); + symtab.Insert("z", Symbol(TFloat{4.0}, /*read_only=*/true)); + symtab.Insert("fvec", Symbol( + Symbol::FloatVector{1.5, 3.25, 8.0}, /*read_only=*/true)); + + // Mea bits: 0b1011 -> m[0]=1, m[1]=1, m[2]=0, m[3]=1 + Symbol::Mea mea{.bits = 0b1011, .num_bits = 4, .time = 0}; + symtab.Insert("m", Symbol(mea)); + } +}; + +TEST_F(ComplexParserTest, RandomIntExpressions) { + // Case 1: Precedence of bitwise, shift, and arithmetic operators + { + Tokenizer tok("(a << 2) + (b & 0x05) * ~c + (ivec[1] % 5)"); + TInt expected = (3 << 2) + (7 & 0x05) * ~2 + (12 % 5); + Expr expr = TestIntExprParser::Run(symtab, tok); + EXPECT_EQ(EvalIntExpr(symtab, expr), expected); + } + + // Case 2: Deep parenthetical nesting with comparisons and logical conversions + { + Tokenizer tok("((ivec[a - 1] + 2) * (b > a)) - (ivec[0] << (c + 1))"); + // (20 * 1) - 40 = -20 + TInt expected = ((18 + 2) * (7 > 3)) - (5 << (2 + 1)); + Expr expr = TestIntExprParser::Run(symtab, tok); + EXPECT_EQ(EvalIntExpr(symtab, expr), expected); + } + + // Case 3: Measurement bit indexing combined with bitwise XOR and binary + // literals + { + Tokenizer tok("(m[0] ^ m[2]) + (m[3] << 3) * (ivec[m[1]] - 0b0010)"); + // m[0]=1, m[2]=0, m[3]=1, m[1]=1 -> ivec[1]=12 + TInt expected = (1 ^ 0) + (1 << 3) * (12 - 2); // 1 + 8 * 10 = 81 + Expr expr = TestIntExprParser::Run(symtab, tok); + EXPECT_EQ(EvalIntExpr(symtab, expr), expected); + } + + // Case 4: Multi-operator arithmetic with hexadecimal constants + { + Tokenizer tok("0x1F & (ivec[3] / (a + 1)) + (0x0A * (b - c))"); + // 31 & (10 + 50) = 31 & 60 = 28 + TInt expected = 0x1F & ((42 / (3 + 1)) + (0x0A * (7 - 2))); + Expr expr = TestIntExprParser::Run(symtab, tok); + EXPECT_EQ(EvalIntExpr(symtab, expr), expected); + } + + // Case 5: Complex chain with relational booleans coerced to integers + { + Tokenizer tok("(a + b * c > 10) + 2 * (ivec[2] <= 20) + 3 * (b != 7)"); + // (3 + 14 > 10) -> 1 + // (18 <= 20) -> 1 + // (7 != 7) -> 0 + TInt expected = 1 + 2 * 1 + 3 * 0; // 3 + Expr expr = TestIntExprParser::Run(symtab, tok); + EXPECT_EQ(EvalIntExpr(symtab, expr), expected); + } +} + +TEST_F(ComplexParserTest, RandomFloatExpressions) { + // Case 1: Mixed float/int arithmetic and power (**) operator + { + Tokenizer tok("x ** 2.0 + fvec[0] * (z - y) / 0.5"); + // 6.25 + 10.5 = 16.75 + TFloat expected = std::pow(2.5, 2.0) + 1.5 * (4.0 - 0.5) / 0.5; + Expr expr = TestExprParser::Run(symtab, tok); + EXPECT_DOUBLE_EQ(EvalExpr(symtab, expr), expected); + } + + // Case 2: Deeply nested float expressions with vector indexing via dynamic + // int expressions + { + Tokenizer tok("(fvec[a - 2] * (z ** (y * 2.0))) - (x / (fvec[1] - 0.25))"); + // fvec[1] * (4.0 ** 1.0) - (2.5 / (3.25 - 0.25)) -> 3.25 * 4 - (2.5 / 3.0) + TFloat expected = (3.25 * 4.0) - (2.5 / 3.0); + Expr expr = TestExprParser::Run(symtab, tok); + EXPECT_DOUBLE_EQ(EvalExpr(symtab, expr), expected); + } + + // Case 3: Float expression with promoted integer symbols and float division + { + Tokenizer tok("(a + 1.5) * (ivec[0] ** 2) / (z + y)"); + // (4.5 * 25.0) / 4.5 = 25.0 + TFloat expected = (3.0 + 1.5) * std::pow(5.0, 2.0) / (4.0 + 0.5); + Expr expr = TestExprParser::Run(symtab, tok); + EXPECT_DOUBLE_EQ(EvalExpr(symtab, expr), expected); + } + + // Case 4: Precedence test with negation, addition, and power + { + Tokenizer tok("-x + (y + fvec[2]) ** 0.5 * z"); + // -2.5 + 3.0 * 4.0 = 9.5 + TFloat expected = -2.5 + std::pow(0.5 + 8.0, 0.5) * 4.0; + Expr expr = TestExprParser::Run(symtab, tok); + EXPECT_DOUBLE_EQ(EvalExpr(symtab, expr), expected); + } +} + +TEST_F(ComplexParserTest, RandomBoolExpressions) { + // Case 1: Logical AND, OR, NOT, and XOR (^^) chains over comparisons + { + Tokenizer tok("(a < b) && (ivec[1] == 12) ^^ !(x >= z) || (c == 0)"); + // (3 < 7)[1] && (12 == 12)[1] ^^ !(2.5 >= 4.0)[1] || (2 == 0)[0] + // (1 && 1) ^^ 1 || 0 -> 1 ^^ 1 || 0 -> 0 || 0 -> false + TBool expected = false; + Expr expr = TestExprParser::Run(symtab, tok); + EXPECT_EQ(EvalCondExpr(symtab, expr), expected); + } + + // Case 2: Mixed measurement bit conditions and boolean logical operators + { + Tokenizer tok("!(m[2] == 1) && ((m[0] ^^ m[1]) || (ivec[a] > 30))"); + // !(0 == 1)[1] && ((1 ^^ 1)[0] || (ivec[3]=42 > 30)[1]) + // 1 && (0 || 1) -> true + TBool expected = true; + Expr expr = TestExprParser::Run(symtab, tok); + EXPECT_EQ(EvalCondExpr(symtab, expr), expected); + } + + // Case 3: Compound conditional mixing floating-point and integer + // subexpressions + { + Tokenizer tok("(x * 2.0 == ivec[0]) && (fvec[1] > y) ^^ (b - a != 4)"); + // (5.0 == 5)[1] && (3.25 > 0.5)[1] ^^ (7 - 3 != 4)[0] + // (1 && 1) ^^ 0 -> 1 ^^ 0 -> true + TBool expected = true; + Expr expr = TestExprParser::Run(symtab, tok); + EXPECT_EQ(EvalCondExpr(symtab, expr), expected); + } + + // Case 4: Deep logical expression evaluating integer zero/non-zero values + // implicitly as booleans + { + Tokenizer tok("!((a & b) ^^ (ivec[0] % 2)) || ((z > 0.0) && (m[3]))"); + // (3 & 7) = 3 (true); (5 % 2) = 1 (true) -> (3 ^^ 1) => 1 ^^ 1 = 0 (false) + // !0 -> 1 (true) + // true || ... -> true + TBool expected = true; + Expr expr = TestExprParser::Run(symtab, tok); + EXPECT_EQ(EvalCondExpr(symtab, expr), expected); + } +} + +} // namespace +} // namespace qsim::cc + +int main(int argc, char** argv) { + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/tests/classical_control_symbol_test.cc b/tests/classical_control_symbol_test.cc new file mode 100644 index 000000000..65915bd2f --- /dev/null +++ b/tests/classical_control_symbol_test.cc @@ -0,0 +1,262 @@ +#include + +#include +#include + +#include "../lib/classical_control_expr.h" +#include "../lib/classical_control_symbol.h" +#include "../lib/classical_control_symtab.h" + +namespace qsim::cc { +namespace { + +TEST(SymbolTest, DefaultConstructor) { + Symbol s; + EXPECT_TRUE(s.IsReadOnly()); +} + +TEST(SymbolTest, ConstructsIntAndDefaultReadOnly) { + Symbol s(Symbol::Int{42}); + EXPECT_FALSE(s.HoldsMea()); + EXPECT_FALSE(s.HoldsVector()); + EXPECT_FALSE(s.IsFloat()); + EXPECT_TRUE(s.IsConvertibleToInt()); + EXPECT_TRUE(s.IsReadOnly()); + EXPECT_EQ(s.Size(), 1u); + EXPECT_EQ(s.GetInt(), 42); +} + +TEST(SymbolTest, ConstructsFloatAndExplicitReadOnly) { + Symbol s(Symbol::Float{3.14}, /*read_only=*/false); + EXPECT_FALSE(s.HoldsMea()); + EXPECT_FALSE(s.HoldsVector()); + EXPECT_TRUE(s.IsFloat()); + EXPECT_FALSE(s.IsConvertibleToInt()); + EXPECT_FALSE(s.IsReadOnly()); + EXPECT_EQ(s.Size(), 1u); + EXPECT_DOUBLE_EQ(s.GetFloat(), 3.14); +} + +TEST(SymbolTest, ConstructsMeaDefaultsToNotReadOnly) { + Symbol::Mea mea{.bits = 0b1011, .num_bits = 4, .time = 10}; + Symbol s(mea); + + EXPECT_TRUE(s.HoldsMea()); + EXPECT_FALSE(s.HoldsVector()); + EXPECT_FALSE(s.IsReadOnly()); + EXPECT_EQ(s.Size(), 4u); + EXPECT_EQ(s.GetMea().time, 10u); +} + +TEST(SymbolTest, ConstructsVectorsCorrectly) { + Symbol::IntVector iv = {1, 2, 3, 4}; + Symbol s_int_vec(iv); + + EXPECT_TRUE(s_int_vec.HoldsVector()); + EXPECT_FALSE(s_int_vec.IsFloat()); + EXPECT_TRUE(s_int_vec.IsConvertibleToInt()); + EXPECT_EQ(s_int_vec.Size(), 4u); + + Symbol::FloatVector fv = {1.1, 2.2}; + Symbol s_float_vec(fv); + + EXPECT_TRUE(s_float_vec.HoldsVector()); + EXPECT_TRUE(s_float_vec.IsFloat()); + EXPECT_FALSE(s_float_vec.IsConvertibleToInt()); + EXPECT_EQ(s_float_vec.Size(), 2u); +} + +TEST(SymbolTest, GetIntAndGetFloatFromMea) { + Symbol::Mea mea{.bits = 0b1101, .num_bits = 4, .time = 0}; + Symbol s(mea); + + EXPECT_EQ(s.GetInt(), 0b1101); + EXPECT_DOUBLE_EQ(s.GetFloat(), 13.0); +} + +TEST(SymbolTest, InvalidScalarConversionsThrow) { + Symbol s_float(Symbol::Float{5.5}); + EXPECT_THROW(s_float.GetInt(), std::runtime_error); + + Symbol s_vec(Symbol::IntVector{1, 2}); + EXPECT_THROW(s_vec.GetInt(), std::runtime_error); + EXPECT_THROW(s_vec.GetFloat(), std::runtime_error); +} + +TEST(SymbolTest, GetIntIndexedFromIntVector) { + Symbol s(Symbol::IntVector{10, 20, 30}); + + EXPECT_EQ(s.GetInt(0), 10); + EXPECT_EQ(s.GetInt(1), 20); + EXPECT_EQ(s.GetInt(2), 30); + EXPECT_THROW(s.GetInt(3), std::runtime_error); +} + +TEST(SymbolTest, GetFloatIndexedFromVectors) { + Symbol s_int(Symbol::IntVector{10, 20}); + EXPECT_DOUBLE_EQ(s_int.GetFloat(1), 20.0); + + Symbol s_float(Symbol::FloatVector{1.5, 2.5}); + EXPECT_DOUBLE_EQ(s_float.GetFloat(0), 1.5); + EXPECT_THROW(s_float.GetFloat(2), std::runtime_error); +} + +TEST(SymbolTest, GetIntFromFloatVectorThrows) { + Symbol s(Symbol::FloatVector{1.0, 2.0}); + EXPECT_THROW(s.GetInt(0), std::runtime_error); +} + +TEST(SymbolTest, IndexingScalarsThrows) { + Symbol s(Symbol::Int{100}); + EXPECT_THROW(s.GetInt(0), std::runtime_error); + EXPECT_THROW(s.GetFloat(0), std::runtime_error); +} + +TEST(SymbolTest, GetMeaIntBits) { + // 0b1010 -> Bit 0: 0, Bit 1: 1, Bit 2: 0, Bit 3: 1 + Symbol::Mea mea{.bits = 0b1010, .num_bits = 4, .time = 1}; + Symbol s(mea); + + EXPECT_EQ(s.GetInt(0), 0); + EXPECT_EQ(s.GetInt(1), 1); + EXPECT_EQ(s.GetInt(2), 0); + EXPECT_EQ(s.GetInt(3), 1); + + EXPECT_THROW(s.GetInt(4), std::runtime_error); +} + +TEST(SymbolTest, AssignScalarIntAndFloat) { + Symbol s_int(Symbol::Int{10}, /*read_only=*/false); + s_int.Assign(Symbol::Int{99}); + EXPECT_EQ(s_int.GetInt(), 99); + + Symbol s_float(Symbol::Float{1.0}, /*read_only=*/false); + s_float.Assign(Symbol::Float{4.5}); + EXPECT_DOUBLE_EQ(s_float.GetFloat(), 4.5); +} + +TEST(SymbolTest, AssignVectorElements) { + Symbol s_int_vec(Symbol::IntVector{1, 2, 3}, /*read_only=*/false); + s_int_vec.Assign(Symbol::Int{100}, 1); + EXPECT_EQ(s_int_vec.GetInt(1), 100); + + Symbol s_float_vec(Symbol::FloatVector{0.1, 0.2}, /*read_only=*/false); + s_float_vec.Assign(Symbol::Float{9.9}, 0); + EXPECT_DOUBLE_EQ(s_float_vec.GetFloat(0), 9.9); +} + +class SymbolAssignTest : public ::testing::Test { + protected: + SymTable symtab; + + void SetUp() override { + auto scope = symtab.AddScope(); + symtab.EnterScope(scope); + + symtab.Insert("rhs_var", Symbol(Symbol::Int{10})); + } +}; + +TEST_F(SymbolAssignTest, AssignsScalarInt) { + Symbol s(Symbol::Int{0}, /*read_only=*/false); + + // Assign with expression referencing a symbol table variable: rhs_var + 5 + Expr expr = TFuncI{[](const SymTable& st) -> TInt { + return EvalIntExpr(st, TSymbol{"rhs_var"}) + 5; + }}; + + s.Assign(symtab, std::vector{expr}); + EXPECT_EQ(s.GetInt(), 15); +} + +TEST_F(SymbolAssignTest, AssignsScalarFloatAndConvertsIntToFloat) { + Symbol s(Symbol::Float{0.0}, /*read_only=*/false); + + s.Assign(symtab, std::vector{TInt{42}}); + EXPECT_DOUBLE_EQ(s.GetFloat(), 42.0); +} + +TEST_F(SymbolAssignTest, AssigningFloatToScalarIntThrows) { + Symbol s(Symbol::Int{0}, /*read_only=*/false); + + EXPECT_THROW( + s.Assign(symtab, std::vector{TFloat{3.14}}), std::runtime_error); +} + +TEST_F(SymbolAssignTest, AssignsIntVectorElementWise) { + Symbol s(Symbol::IntVector{0, 0, 0}, /*read_only=*/false); + std::vector exprs = {TInt{10}, TInt{20}, TInt{30}}; + + s.Assign(symtab, exprs); + EXPECT_EQ(s.GetInt(0), 10); + EXPECT_EQ(s.GetInt(1), 20); + EXPECT_EQ(s.GetInt(2), 30); +} + +TEST_F(SymbolAssignTest, AssignsFloatVectorElementWise) { + Symbol s(Symbol::FloatVector{0.0, 0.0}, /*read_only=*/false); + std::vector exprs = {TFloat{1.1}, TInt{2}}; + + s.Assign(symtab, exprs); + EXPECT_DOUBLE_EQ(s.GetFloat(0), 1.1); + EXPECT_DOUBLE_EQ(s.GetFloat(1), 2.0); +} + +TEST_F(SymbolAssignTest, AssignsVectorUpToContainerSize) { + Symbol s(Symbol::IntVector{0, 0}, /*read_only=*/false); + std::vector exprs = {TInt{10}, TInt{20}, TInt{30}, TInt{40}}; + + s.Assign(symtab, exprs); + EXPECT_EQ(s.Size(), 2u); + EXPECT_EQ(s.GetInt(0), 10); + EXPECT_EQ(s.GetInt(1), 20); +} + +TEST_F(SymbolAssignTest, AssigningFloatInIntVectorThrows) { + Symbol s(Symbol::IntVector{0, 0}, /*read_only=*/false); + std::vector exprs = {TInt{10}, TFloat{2.5}}; + + EXPECT_THROW(s.Assign(symtab, exprs), std::runtime_error); +} + +TEST_F(SymbolAssignTest, AssignsIndexedVectorElement) { + Symbol s(Symbol::IntVector{100, 200, 300}, /*read_only=*/false); + Index idx = TInt{1}; + Expr expr = TInt{999}; + + s.Assign(symtab, expr, idx); + EXPECT_EQ(s.GetInt(1), 999); +} + +TEST_F(SymbolAssignTest, IndexedAssignmentOutOfBoundsThrows) { + Symbol s(Symbol::FloatVector{1.0, 2.0}, /*read_only=*/false); + Index idx = TInt{5}; + Expr expr = TFloat{9.9}; + + EXPECT_THROW(s.Assign(symtab, expr, idx), std::runtime_error); +} + +TEST_F(SymbolAssignTest, IndexedAssignmentTypeMismatchThrows) { + Symbol s(Symbol::IntVector{1, 2, 3}, /*read_only=*/false); + Index idx = TInt{0}; + Expr expr = TFloat{1.23}; + + EXPECT_THROW(s.Assign(symtab, expr, idx), std::runtime_error); +} + +TEST_F(SymbolAssignTest, AssigningToMeasurementThrows) { + Symbol::Mea mea{.bits = 0, .num_bits = 2, .time = 0}; + Symbol s(mea); + + EXPECT_THROW( + s.Assign(symtab, std::vector{TInt{1}}), std::runtime_error); + EXPECT_THROW(s.Assign(symtab, TInt{1}, TInt{0}), std::runtime_error); +} + +} // namespace +} // namespace qsim::cc + +int main(int argc, char** argv) { + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/tests/classical_control_symtab_test.cc b/tests/classical_control_symtab_test.cc new file mode 100644 index 000000000..3421b0add --- /dev/null +++ b/tests/classical_control_symtab_test.cc @@ -0,0 +1,143 @@ +#include + +#include + +#include "../lib/classical_control_symbol.h" +#include "../lib/classical_control_symtab.h" + +namespace qsim::cc { +namespace { + +TEST(SymTableTest, InitialStateIsEmpty) { + SymTable table; + EXPECT_TRUE(table.Empty()); +} + +TEST(SymTableTest, AddAndEnterScope) { + SymTable table; + std::size_t s0 = table.AddScope(); + EXPECT_EQ(s0, 0u); + EXPECT_FALSE(table.Empty()); + + table.EnterScope(s0); + + // Verify insertion in current active scope + Symbol* inserted = table.Insert("x", Symbol(Symbol::Int{10})); + ASSERT_NE(inserted, nullptr); + EXPECT_EQ(inserted->GetInt(), 10); + + table.ExitScope(); +} + +TEST(SymTableTest, OutOfBoundsScopeIndexThrows) { + SymTable table; + EXPECT_THROW(table.EnterScope(99), std::runtime_error); +} + +TEST(SymTableTest, LexicalScopingAndShadowing) { + SymTable table; + + // Root Scope + auto s0 = table.AddScope(); + table.EnterScope(s0); + table.Insert("x", Symbol(Symbol::Int{1})); + table.Insert("y", Symbol(Symbol::Int{2})); + + // Child Scope + auto s1 = table.AddScope(); + table.EnterScope(s1); + table.Insert("x", Symbol(Symbol::Int{100})); // Shadows root 'x' + + // Lookups in Child Scope + EXPECT_EQ(table.Lookup("x")->GetInt(), 100); // Inner 'x' + EXPECT_EQ(table.Lookup("y")->GetInt(), 2); // Inherited 'y' from root + + // Lookup in specific scopes + EXPECT_EQ(table.LookupInCurrentScope("x")->GetInt(), 100); + EXPECT_EQ(table.LookupInPreviousScope("x")->GetInt(), 1); + EXPECT_EQ(table.LookupInCurrentScope("y"), nullptr); // 'y' is in prev scope + + table.ExitScope(); + + // Back in Root Scope + EXPECT_EQ(table.Lookup("x")->GetInt(), 1); +} + +TEST(SymTableTest, ReEnteringScopesSimulatesInterpreterPhase) { + SymTable table; + + // Parser phase: create scope graph + auto root = table.AddScope(); + table.EnterScope(root); + table.Insert("a", Symbol(Symbol::Float{1.5})); + + auto inner = table.AddScope(); + table.EnterScope(inner); + table.Insert("b", Symbol(Symbol::Float{2.5})); + + table.ExitScope(); // Exit inner + table.ExitScope(); // Exit root + + // Interpreter phase: re-enter recorded scope indices + table.EnterScope(root); + EXPECT_NE(table.Lookup("a"), nullptr); + EXPECT_EQ(table.Lookup("b"), nullptr); + + table.EnterScope(inner); + EXPECT_NE(table.Lookup("a"), nullptr); + EXPECT_NE(table.Lookup("b"), nullptr); + + table.ExitScope(); + table.ExitScope(); +} + +TEST(SymTableTest, LookupOrErrorMethods) { + SymTable table; + auto s0 = table.AddScope(); + table.EnterScope(s0); + + table.Insert("valid_var", Symbol(Symbol::Int{42})); + + // Success path + EXPECT_NO_THROW({ + Symbol* s = table.LookupOrError( + "valid_var", "Variable '{}' not found", "valid_var"); + ASSERT_NE(s, nullptr); + EXPECT_EQ(s->GetInt(), 42); + }); + + // Failure paths + EXPECT_THROW( + table.LookupOrError("missing_var", "Variable not declared"), + std::runtime_error); + + EXPECT_THROW( + table.LookupInCurrentScopeOrError("missing_var", "Not in current scope"), + std::runtime_error); +} + +TEST(SymTableTest, ConstLookupMethods) { + SymTable table; + auto s0 = table.AddScope(); + table.EnterScope(s0); + table.Insert("const_var", Symbol(Symbol::Int{7})); + + const SymTable& const_table = table; + + const Symbol* s1 = const_table.Lookup("const_var"); + ASSERT_NE(s1, nullptr); + EXPECT_EQ(s1->GetInt(), 7); + + const Symbol* s2 = const_table.LookupInCurrentScope("const_var"); + ASSERT_NE(s2, nullptr); + + EXPECT_EQ(const_table.Lookup("nonexistent"), nullptr); +} + +} // namespace +} // namespace qsim::cc + +int main(int argc, char** argv) { + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/tests/classical_control_tokenizer_test.cc b/tests/classical_control_tokenizer_test.cc new file mode 100644 index 000000000..ad5d777f5 --- /dev/null +++ b/tests/classical_control_tokenizer_test.cc @@ -0,0 +1,162 @@ +#include + +#include "../lib/classical_control_tokenizer.h" + +namespace qsim::cc { +namespace { + +void AssertToken(const Token& t, Token::TokenKind expected_kind, + std::string_view expected_val) { + EXPECT_EQ(t.kind, expected_kind); + EXPECT_EQ(t.val, expected_val); +} + +TEST(TokenizerUtilsTest, PackAndUnpackLineColumn) { + unsigned line = 42; + unsigned col = 128; + + unsigned packed = Tokenizer::PackLineAndColumn(line, col); + auto [unpacked_line, unpacked_col] = Tokenizer::UnpackLineAndColumn(packed); + + EXPECT_EQ(unpacked_line, line); + EXPECT_EQ(unpacked_col, col); +} + +TEST(TokenizerUtilsTest, PackAndUnpackBoundaryValues) { + unsigned line = 1048575; // 20-bit max + unsigned col = 4095; // 12-bit max + + unsigned packed = Tokenizer::PackLineAndColumn(line, col); + auto [unpacked_line, unpacked_col] = Tokenizer::UnpackLineAndColumn(packed); + + EXPECT_EQ(unpacked_line, line); + EXPECT_EQ(unpacked_col, col); +} + +TEST(TokenizerTest, Identifiers) { + Tokenizer tok("foo _bar123 VAR_NAME_"); + + AssertToken(tok(), Token::kIdentifier, "foo"); + AssertToken(tok(), Token::kIdentifier, "_bar123"); + AssertToken(tok(), Token::kIdentifier, "VAR_NAME_"); + AssertToken(tok(), Token::kEndOfFile, ""); +} + +TEST(TokenizerTest, Integers) { + Tokenizer tok("123 0x1a 0X1A 0b01 0B10"); + + AssertToken(tok(), Token::kInteger, "123"); + AssertToken(tok(), Token::kInteger, "0x1a"); + AssertToken(tok(), Token::kInteger, "0X1A"); + AssertToken(tok(), Token::kInteger, "0b01"); + AssertToken(tok(), Token::kInteger, "0B10"); + AssertToken(tok(), Token::kEndOfFile, ""); +} + +TEST(TokenizerTest, FloatingPointNumbers) { + Tokenizer tok("301.34 1.2e-5 0.0 5.0e+10 1e9"); + + AssertToken(tok(), Token::kFloat, "301.34"); + AssertToken(tok(), Token::kFloat, "1.2e-5"); + AssertToken(tok(), Token::kFloat, "0.0"); + AssertToken(tok(), Token::kFloat, "5.0e+10"); + AssertToken(tok(), Token::kFloat, "1e9"); + AssertToken(tok(), Token::kEndOfFile, ""); +} + +TEST(TokenizerTest, StringLiterals) { + Tokenizer tok("\"hello world\" 'single quoted'"); + + AssertToken(tok(), Token::kString, "hello world"); + AssertToken(tok(), Token::kString, "single quoted"); + AssertToken(tok(), Token::kEndOfFile, ""); +} + +TEST(TokenizerTest, Operators) { + // Test both multi-character and single-character operators + Tokenizer tok("|| ^^ && == != <= >= ** << >> - + * / = < > ! ~ | ^ &"); + + const std::vector expected_ops = { + "||", "^^", "&&", "==", "!=", "<=", ">=", "**", "<<", ">>", "-", "+", + "*", "/", "=", "<", ">", "!", "~", "|", "^", "&"}; + + for (std::string_view op : expected_ops) { + AssertToken(tok(), Token::kOperator, op); + } + + AssertToken(tok(), Token::kEndOfFile, ""); +} + +TEST(TokenizerTest, BracketsAndDelimiters) { + Tokenizer tok("( ) [ ] ; \n"); + + AssertToken(tok(), Token::kBracket, "("); + AssertToken(tok(), Token::kBracket, ")"); + AssertToken(tok(), Token::kBracket, "["); + AssertToken(tok(), Token::kBracket, "]"); + AssertToken(tok(), Token::kDelimiter, ";"); + AssertToken(tok(), Token::kDelimiter, "\n"); + AssertToken(tok(), Token::kEndOfFile, ""); +} + +TEST(TokenizerTest, InvalidToken) { + // Non-ASCII or unclosed quote yields kInvalid + Tokenizer tok("\"unclosed string"); + + Token t = tok(); + EXPECT_EQ(t.kind, Token::kInvalid); +} + +TEST(TokenizerStateTest, CurrentTokenTracking) { + Tokenizer tok("x + 1"); + + tok(); + EXPECT_EQ(tok.Current().kind, Token::kIdentifier); + EXPECT_EQ(tok.Current().val, "x"); + + tok(); + EXPECT_EQ(tok.Current().kind, Token::kOperator); + EXPECT_EQ(tok.Current().val, "+"); +} + +TEST(TokenizerStateTest, PeekLookahead) { + Tokenizer tok("a + b"); + + // Peek(0) should preview 'a' without advancing + Token peek0 = tok.Peek(0); + AssertToken(peek0, Token::kIdentifier, "a"); + + // Peek(1) previews '+' + Token peek1 = tok.Peek(1); + AssertToken(peek1, Token::kOperator, "+"); + + // Peek(2) previews 'b' + Token peek2 = tok.Peek(2); + AssertToken(peek2, Token::kIdentifier, "b"); + + // Calling operator() should still retrieve 'a' + Token consumed = tok(); + AssertToken(consumed, Token::kIdentifier, "a"); +} + +TEST(TokenizerStateTest, Restart) { + Tokenizer tok("foo 123"); + + AssertToken(tok(), Token::kIdentifier, "foo"); + AssertToken(tok(), Token::kInteger, "123"); + + // Restart back to beginning + tok.Restart(); + + AssertToken(tok(), Token::kIdentifier, "foo"); + AssertToken(tok(), Token::kInteger, "123"); + AssertToken(tok(), Token::kEndOfFile, ""); +} + +} // namespace +} // namespace qsim::cc + +int main(int argc, char** argv) { + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/tests/classical_control_util_test.cc b/tests/classical_control_util_test.cc new file mode 100644 index 000000000..84f7c667b --- /dev/null +++ b/tests/classical_control_util_test.cc @@ -0,0 +1,131 @@ +#include + +#include +#include +#include +#include +#include + +#include "../lib/classical_control_expr.h" +#include "../lib/classical_control_symbol.h" +#include "../lib/classical_control_symtab.h" +#include "../lib/classical_control_util.h" + +namespace qsim::cc { +namespace { + +TEST(UtilsTest, ToIntDecimal) { + EXPECT_EQ(ToInt("0"), 0u); + EXPECT_EQ(ToInt("42"), 42u); + EXPECT_EQ(ToInt("1000"), 1000u); +} + +TEST(UtilsTest, ToIntHexadecimal) { + EXPECT_EQ(ToInt("0x1a"), 26u); + EXPECT_EQ(ToInt("0X1A"), 26u); + EXPECT_EQ(ToInt("0xFF"), 255u); +} + +TEST(UtilsTest, ToIntBinary) { + EXPECT_EQ(ToInt("0b1010"), 10u); + EXPECT_EQ(ToInt("0B1111"), 15u); + EXPECT_EQ(ToInt("0b0"), 0u); +} + +TEST(UtilsTest, ToIntInvalidThrows) { + EXPECT_THROW(ToInt("abc"), std::runtime_error); // Non-numeric + EXPECT_THROW(ToInt("12abc"), std::runtime_error); // Trailing non-numeric + EXPECT_THROW(ToInt("0xG1"), std::runtime_error); // Invalid hex + EXPECT_THROW(ToInt("0b1020"), std::runtime_error); // Invalid binary +} + +TEST(UtilsTest, ToFloatStandardAndExponent) { + EXPECT_DOUBLE_EQ(ToFloat("0.0"), 0.0); + EXPECT_DOUBLE_EQ(ToFloat("3.14159"), 3.14159); + EXPECT_DOUBLE_EQ(ToFloat("1.2e-3"), 0.0012); + EXPECT_DOUBLE_EQ(ToFloat("2.5E2"), 250.0); +} + +TEST(UtilsTest, ToFloatInvalidThrows) { + EXPECT_THROW(ToFloat("not_a_float"), std::runtime_error); + EXPECT_THROW(ToFloat("3.14foo"), std::runtime_error); +} + +TEST(UtilsTest, Fnv1aHashAndLiteralOperator) { + constexpr std::size_t h1 = Hash("my_variable"); + constexpr std::size_t h2 = "my_variable"_hash; + + EXPECT_EQ(h1, h2); + EXPECT_NE("var1"_hash, "var2"_hash); +} + +TEST(UtilsTest, OpHashPacking) { + // Single char op + constexpr unsigned short op1 = OpHash("+"); + constexpr unsigned short op1_lit = "+"_ophash; + EXPECT_EQ(op1, op1_lit); + + // Two char op + constexpr unsigned short op2 = OpHash("=="); + constexpr unsigned short op2_lit = "=="_ophash; + EXPECT_EQ(op2, op2_lit); + + // Over length op returns 0 + EXPECT_EQ(OpHash("+++"), 0); +} + +/* +TEST(UtilsTest, ReadFileSuccessAndFailure) { + // Create a temporary file + std::filesystem::path temp_path = + std::filesystem::temp_directory_path() / "qsim_test_file.txt"; + { + std::ofstream ofs(temp_path); + ofs << "hello qsim\nsecond line"; + } + + std::string content = ReadFile(temp_path.string()); + EXPECT_EQ(content, "hello qsim\nsecond line"); + + // Cleanup + std::filesystem::remove(temp_path); + + // Missing file read throws + EXPECT_THROW(ReadFile("nonexistent_file_path_12345.txt"), std::runtime_error); +} +*/ + +TEST(UtilsTest, ExprToStrRun1ExplicitFormat) { + SymTable symtab; + auto scope = symtab.AddScope(); + symtab.EnterScope(scope); + + symtab.Insert("x", Symbol(TInt{10})); + symtab.Insert("y", Symbol(TFloat{2.5})); + + std::vector es = {TSymbol{"x"}, TSymbol{"y"}}; + + // Run1 uses std::vformat with user string "x = {}, y = {}" + std::string res = ExprToStr<2>::Run1(symtab, "x = {}, y = {}", es); + EXPECT_EQ(res, "x = 10, y = 2.5"); +} + +TEST(UtilsTest, ExprToStrRun2AutoFormat) { + SymTable symtab; + auto scope = symtab.AddScope(); + symtab.EnterScope(scope); + + std::vector es = {TInt{100}, TFloat{3.14}}; + + // Run2 auto-builds space-separated "{}" format specifier string + std::string res = ExprToStr<2>::Run2(symtab, std::string{}, es); + EXPECT_EQ(res, "100 3.14"); +} + +} // namespace +} // namespace qsim::cc + +int main(int argc, char** argv) { + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/tests/fuser_basic_test.cc b/tests/fuser_basic_test.cc index c6c233969..980aff724 100644 --- a/tests/fuser_basic_test.cc +++ b/tests/fuser_basic_test.cc @@ -67,13 +67,9 @@ R"(4 TEST(FuserBasicTest, NoTimesToSplitAt) { using Gate = qsim::Gate; - using Operation = qsim::Operation; using FusedGate = qsim::FusedGate; - std::stringstream ss(circuit_string1); - Circuit circuit; - - EXPECT_TRUE(CircuitQsimParser::FromStream(99, provider, ss, circuit)); + auto circuit = CircuitQsimParser::Run(circuit_string1, 99); EXPECT_EQ(circuit.num_qubits, 4); EXPECT_EQ(circuit.ops.size(), 27); @@ -300,13 +296,9 @@ TEST(FuserBasicTest, NoTimesToSplitAt) { TEST(FuserBasicTest, TimesToSplitAt1) { using Gate = qsim::Gate; - using Operation = qsim::Operation; using FusedGate = qsim::FusedGate; - std::stringstream ss(circuit_string1); - Circuit circuit; - - EXPECT_TRUE(CircuitQsimParser::FromStream(99, provider, ss, circuit)); + auto circuit = CircuitQsimParser::Run(circuit_string1, 99); EXPECT_EQ(circuit.num_qubits, 4); EXPECT_EQ(circuit.ops.size(), 27); @@ -545,13 +537,9 @@ TEST(FuserBasicTest, TimesToSplitAt1) { TEST(FuserBasicTest, TimesToSplitAt2) { using Gate = qsim::Gate; - using Operation = qsim::Operation; using FusedGate = qsim::FusedGate; - std::stringstream ss(circuit_string1); - Circuit circuit; - - EXPECT_TRUE(CircuitQsimParser::FromStream(99, provider, ss, circuit)); + auto circuit = CircuitQsimParser::Run(circuit_string1, 99); EXPECT_EQ(circuit.num_qubits, 4); EXPECT_EQ(circuit.ops.size(), 27); @@ -794,13 +782,9 @@ R"(3 TEST(FuserBasicTest, OrphanedQubits1) { using Gate = qsim::Gate; - using Operation = qsim::Operation; using FusedGate = qsim::FusedGate; - std::stringstream ss(circuit_string2); - Circuit circuit; - - EXPECT_TRUE(CircuitQsimParser::FromStream(2, provider, ss, circuit)); + auto circuit = CircuitQsimParser::Run(circuit_string2, 2); EXPECT_EQ(circuit.num_qubits, 3); EXPECT_EQ(circuit.ops.size(), 7); @@ -873,13 +857,9 @@ TEST(FuserBasicTest, OrphanedQubits1) { TEST(FuserBasicTest, OrphanedQubits2) { using Gate = qsim::Gate; - using Operation = qsim::Operation; using FusedGate = qsim::FusedGate; - std::stringstream ss(circuit_string2); - Circuit circuit; - - EXPECT_TRUE(CircuitQsimParser::FromStream(99, provider, ss, circuit)); + auto circuit = CircuitQsimParser::Run(circuit_string2, 99); EXPECT_EQ(circuit.num_qubits, 3); EXPECT_EQ(circuit.ops.size(), 9); @@ -991,10 +971,7 @@ TEST(FuserBasicTest, DecomposedQubitGate) { using OperationD = detail::append_to_variant_t; - std::stringstream ss(circuit_string2); - Circuit circuit; - - EXPECT_TRUE(CircuitQsimParser::FromStream(2, provider, ss, circuit)); + auto circuit = CircuitQsimParser::Run(circuit_string2, 2); EXPECT_EQ(circuit.num_qubits, 3); EXPECT_EQ(circuit.ops.size(), 7); @@ -1108,10 +1085,7 @@ TEST(FuserBasicTest, MeasurementGate) { using Operation = qsim::Operation; using FusedGate = qsim::FusedGate; - std::stringstream ss(circuit_string3); - Circuit circuit; - - EXPECT_TRUE(CircuitQsimParser::FromStream(99, provider, ss, circuit)); + auto circuit = CircuitQsimParser::Run(circuit_string3, 99); EXPECT_EQ(circuit.num_qubits, 4); EXPECT_EQ(circuit.ops.size(), 17); @@ -1320,10 +1294,7 @@ TEST(FuserBasicTest, ControlledGate) { using ControlledGate = qsim::ControlledGate; using FusedGate = qsim::FusedGate; - std::stringstream ss(circuit_string4); - Circuit circuit; - - EXPECT_TRUE(CircuitQsimParser::FromStream(99, provider, ss, circuit)); + auto circuit = CircuitQsimParser::Run(circuit_string4, 99); EXPECT_EQ(circuit.num_qubits, 5); EXPECT_EQ(circuit.ops.size(), 13); diff --git a/tests/hybrid_testfixture.h b/tests/hybrid_testfixture.h index c3fd04eda..8de81a865 100644 --- a/tests/hybrid_testfixture.h +++ b/tests/hybrid_testfixture.h @@ -63,10 +63,8 @@ R"(2 using fp_type = typename Factory::fp_type; - std::stringstream ss(circuit_string); - Circuit> circuit; + auto circuit = CircuitQsimParser::Run(circuit_string, 99); - EXPECT_TRUE(CircuitQsimParser::FromStream(99, provider, ss, circuit)); EXPECT_EQ(circuit.num_qubits, 2); EXPECT_EQ(circuit.ops.size(), 23); @@ -242,10 +240,8 @@ R"(4 using fp_type = typename Factory::fp_type; - std::stringstream ss(circuit_string); - Circuit> circuit; + auto circuit = CircuitQsimParser::Run(circuit_string, 99); - EXPECT_TRUE(CircuitQsimParser::FromStream(99, provider, ss, circuit)); EXPECT_EQ(circuit.num_qubits, 4); EXPECT_EQ(circuit.ops.size(), 63); diff --git a/tests/make.sh b/tests/make.sh index f7c429090..4c70f7926 100755 --- a/tests/make.sh +++ b/tests/make.sh @@ -19,46 +19,54 @@ path_to_include=googletest/googletest/include path_to_lib=googletest/googletest/make/lib -g++ -O3 -I"$path_to_include" -L"$path_to_lib" -o bitstring_test.x bitstring_test.cc -lgtest -lpthread -g++ -O3 -I"$path_to_include" -L"$path_to_lib" -mavx2 -mfma -fopenmp -o channel_test.x channel_test.cc -lgtest -lpthread -g++ -O3 -I"$path_to_include" -L"$path_to_lib" -mavx2 -mfma -fopenmp -o channels_cirq_test.x channels_cirq_test.cc -lgtest -lpthread -g++ -O3 -I"$path_to_include" -L"$path_to_lib" -o circuit_qsim_parser_test.x circuit_qsim_parser_test.cc -lgtest -lpthread -g++ -O3 -I"$path_to_include" -L"$path_to_lib" -mavx2 -mfma -fopenmp -o expect_nobmi2_test.x expect_test.cc -lgtest -lpthread -g++ -O3 -I"$path_to_include" -L"$path_to_lib" -mavx2 -mfma -mbmi2 -fopenmp -o expect_test.x expect_test.cc -lgtest -lpthread -g++ -O3 -I"$path_to_include" -L"$path_to_lib" -o fuser_basic_test.x fuser_basic_test.cc -lgtest -lpthread -g++ -O3 -I"$path_to_include" -L"$path_to_lib" -o fuser_mqubit_test.x fuser_mqubit_test.cc -lgtest -lpthread -g++ -O3 -I"$path_to_include" -L"$path_to_lib" -o gates_qsim_test.x gates_qsim_test.cc -lgtest -lpthread -g++ -O3 -I"$path_to_include" -L"$path_to_lib" -mavx2 -mfma -fopenmp -o hybrid_avx_test.x hybrid_avx_test.cc -lgtest -lpthread -g++ -O3 -I"$path_to_include" -L"$path_to_lib" -o matrix_test.x matrix_test.cc -lgtest -lpthread -g++ -O3 -I"$path_to_include" -L"$path_to_lib" -mavx2 -mfma -fopenmp -o qtrajectory_avx_nobmi2_test.x qtrajectory_avx_test.cc -lgtest -lpthread -g++ -O3 -I"$path_to_include" -L"$path_to_lib" -mavx2 -mfma -fopenmp -mbmi2 -o qtrajectory_avx_test.x qtrajectory_avx_test.cc -lgtest -lpthread -g++ -O3 -I"$path_to_include" -L"$path_to_lib" -mavx2 -mfma -fopenmp -o run_qsim_test.x run_qsim_test.cc -lgtest -lpthread -g++ -O3 -I"$path_to_include" -L"$path_to_lib" -mavx2 -mfma -fopenmp -o run_qsimh_test.x run_qsimh_test.cc -lgtest -lpthread -g++ -O3 -I"$path_to_include" -L"$path_to_lib" -mavx2 -mfma -fopenmp -o simulator_avx_nobmi2_test.x simulator_avx_test.cc -lgtest -lpthread -g++ -O3 -I"$path_to_include" -L"$path_to_lib" -mavx2 -mfma -mbmi2 -fopenmp -o simulator_avx_test.x simulator_avx_test.cc -lgtest -lpthread -g++ -O3 -I"$path_to_include" -L"$path_to_lib" -mavx512f -mbmi2 -fopenmp -o simulator_avx512_test.x simulator_avx512_test.cc -lgtest -lpthread -g++ -O3 -I"$path_to_include" -L"$path_to_lib" -fopenmp -o simulator_basic_test.x simulator_basic_test.cc -lgtest -lpthread -g++ -O3 -I"$path_to_include" -L"$path_to_lib" -msse4 -fopenmp -o simulator_sse_test.x simulator_sse_test.cc -lgtest -lpthread -g++ -O3 -I"$path_to_include" -L"$path_to_lib" -mavx2 -mfma -fopenmp -o statespace_avx_test.x statespace_avx_test.cc -lgtest -lpthread -g++ -O3 -I"$path_to_include" -L"$path_to_lib" -mavx512f -mbmi2 -fopenmp -o statespace_avx512_test.x statespace_avx512_test.cc -lgtest -lpthread -g++ -O3 -I"$path_to_include" -L"$path_to_lib" -fopenmp -o statespace_basic_test.x statespace_basic_test.cc -lgtest -lpthread -g++ -O3 -I"$path_to_include" -L"$path_to_lib" -msse4 -fopenmp -o statespace_sse_test.x statespace_sse_test.cc -lgtest -lpthread -g++ -O3 -I"$path_to_include" -L"$path_to_lib" -mavx2 -mfma -fopenmp -o unitary_calculator_avx_nobmi2_test.x unitary_calculator_avx_test.cc -lgtest -lpthread -g++ -O3 -I"$path_to_include" -L"$path_to_lib" -mavx2 -mfma -mbmi2 -fopenmp -o unitary_calculator_avx_test.x unitary_calculator_avx_test.cc -lgtest -lpthread -g++ -O3 -I"$path_to_include" -L"$path_to_lib" -mavx512f -mbmi2 -mfma -fopenmp -o unitary_calculator_avx512_test.x unitary_calculator_avx512_test.cc -lgtest -lpthread -g++ -O3 -I"$path_to_include" -L"$path_to_lib" -fopenmp -o unitary_calculator_basic_test.x unitary_calculator_basic_test.cc -lgtest -lpthread -g++ -O3 -I"$path_to_include" -L"$path_to_lib" -msse4 -fopenmp -o unitary_calculator_sse_test.x unitary_calculator_sse_test.cc -lgtest -lpthread -g++ -O3 -I"$path_to_include" -L"$path_to_lib" -mavx2 -mfma -fopenmp -o unitaryspace_avx_test.x unitaryspace_avx_test.cc -lgtest -lpthread -g++ -O3 -I"$path_to_include" -L"$path_to_lib" -mavx512f -mbmi2 -fopenmp -o unitaryspace_avx512_test.x unitaryspace_avx512_test.cc -lgtest -lpthread -g++ -O3 -I"$path_to_include" -L"$path_to_lib" -fopenmp -o unitaryspace_basic_test.x unitaryspace_basic_test.cc -lgtest -lpthread -g++ -O3 -I"$path_to_include" -L"$path_to_lib" -msse4 -fopenmp -o unitaryspace_sse_test.x unitaryspace_sse_test.cc -lgtest -lpthread -g++ -O3 -I"$path_to_include" -L"$path_to_lib" -o vectorspace_test.x vectorspace_test.cc -lgtest -lpthread +g++ -O3 -std=c++20 -I"$path_to_include" -L"$path_to_lib" -o bitstring_test.x bitstring_test.cc -lgtest -lpthread +g++ -O3 -std=c++20 -I"$path_to_include" -L"$path_to_lib" -mavx2 -mfma -fopenmp -o channel_test.x channel_test.cc -lgtest -lpthread +g++ -O3 -std=c++20 -I"$path_to_include" -L"$path_to_lib" -mavx2 -mfma -fopenmp -o channels_cirq_test.x channels_cirq_test.cc -lgtest -lpthread +g++ -O3 -std=c++20 -I"$path_to_include" -L"$path_to_lib" -o circuit_qsim_parser_test.x circuit_qsim_parser_test.cc -lgtest -lpthread +g++ -O3 -std=c++20 -I"$path_to_include" -L"$path_to_lib" -mavx2 -mfma -fopenmp -o classical_control_expr_test.x classical_control_expr_test.cc -lgtest -lpthread +g++ -O3 -std=c++20 -I"$path_to_include" -L"$path_to_lib" -mavx2 -mfma -fopenmp -o classical_control_obs_test.x classical_control_obs_test.cc -lgtest -lpthread +g++ -O3 -std=c++20 -I"$path_to_include" -L"$path_to_lib" -mavx2 -mfma -fopenmp -o classical_control_parser_test.x classical_control_parser_test.cc -lgtest -lpthread +g++ -O3 -std=c++20 -I"$path_to_include" -L"$path_to_lib" -mavx2 -mfma -fopenmp -o classical_control_symbol_test.x classical_control_symbol_test.cc -lgtest -lpthread +g++ -O3 -std=c++20 -I"$path_to_include" -L"$path_to_lib" -mavx2 -mfma -fopenmp -o classical_control_symtab_test.x classical_control_symtab_test.cc -lgtest -lpthread +g++ -O3 -std=c++20 -I"$path_to_include" -L"$path_to_lib" -mavx2 -mfma -fopenmp -o classical_control_tokenizer_test.x classical_control_tokenizer_test.cc -lgtest -lpthread +g++ -O3 -std=c++20 -I"$path_to_include" -L"$path_to_lib" -mavx2 -mfma -fopenmp -o classical_control_util_test.x classical_control_util_test.cc -lgtest -lpthread +g++ -O3 -std=c++20 -I"$path_to_include" -L"$path_to_lib" -mavx2 -mfma -fopenmp -o expect_nobmi2_test.x expect_test.cc -lgtest -lpthread +g++ -O3 -std=c++20 -I"$path_to_include" -L"$path_to_lib" -mavx2 -mfma -mbmi2 -fopenmp -o expect_test.x expect_test.cc -lgtest -lpthread +g++ -O3 -std=c++20 -I"$path_to_include" -L"$path_to_lib" -o fuser_basic_test.x fuser_basic_test.cc -lgtest -lpthread +g++ -O3 -std=c++20 -I"$path_to_include" -L"$path_to_lib" -o fuser_mqubit_test.x fuser_mqubit_test.cc -lgtest -lpthread +g++ -O3 -std=c++20 -I"$path_to_include" -L"$path_to_lib" -o gates_qsim_test.x gates_qsim_test.cc -lgtest -lpthread +g++ -O3 -std=c++20 -I"$path_to_include" -L"$path_to_lib" -mavx2 -mfma -fopenmp -o hybrid_avx_test.x hybrid_avx_test.cc -lgtest -lpthread +g++ -O3 -std=c++20 -I"$path_to_include" -L"$path_to_lib" -o matrix_test.x matrix_test.cc -lgtest -lpthread +g++ -O3 -std=c++20 -I"$path_to_include" -L"$path_to_lib" -mavx2 -mfma -fopenmp -o qtrajectory_avx_nobmi2_test.x qtrajectory_avx_test.cc -lgtest -lpthread +g++ -O3 -std=c++20 -I"$path_to_include" -L"$path_to_lib" -mavx2 -mfma -fopenmp -mbmi2 -o qtrajectory_avx_test.x qtrajectory_avx_test.cc -lgtest -lpthread +g++ -O3 -std=c++20 -I"$path_to_include" -L"$path_to_lib" -mavx2 -mfma -fopenmp -o run_qsim_test.x run_qsim_test.cc -lgtest -lpthread +g++ -O3 -std=c++20 -I"$path_to_include" -L"$path_to_lib" -mavx2 -mfma -fopenmp -o run_qsim_deprecated_test.x run_qsim_deprecated_test.cc -lgtest -lpthread +g++ -O3 -std=c++20 -I"$path_to_include" -L"$path_to_lib" -mavx2 -mfma -fopenmp -o run_qsimh_test.x run_qsimh_test.cc -lgtest -lpthread +g++ -O3 -std=c++20 -I"$path_to_include" -L"$path_to_lib" -mavx2 -mfma -fopenmp -o simulator_avx_nobmi2_test.x simulator_avx_test.cc -lgtest -lpthread +g++ -O3 -std=c++20 -I"$path_to_include" -L"$path_to_lib" -mavx2 -mfma -mbmi2 -fopenmp -o simulator_avx_test.x simulator_avx_test.cc -lgtest -lpthread +g++ -O3 -std=c++20 -I"$path_to_include" -L"$path_to_lib" -mavx512f -mbmi2 -fopenmp -o simulator_avx512_test.x simulator_avx512_test.cc -lgtest -lpthread +g++ -O3 -std=c++20 -I"$path_to_include" -L"$path_to_lib" -fopenmp -o simulator_basic_test.x simulator_basic_test.cc -lgtest -lpthread +g++ -O3 -std=c++20 -I"$path_to_include" -L"$path_to_lib" -msse4 -fopenmp -o simulator_sse_test.x simulator_sse_test.cc -lgtest -lpthread +g++ -O3 -std=c++20 -I"$path_to_include" -L"$path_to_lib" -mavx2 -mfma -fopenmp -o statespace_avx_test.x statespace_avx_test.cc -lgtest -lpthread +g++ -O3 -std=c++20 -I"$path_to_include" -L"$path_to_lib" -mavx512f -mbmi2 -fopenmp -o statespace_avx512_test.x statespace_avx512_test.cc -lgtest -lpthread +g++ -O3 -std=c++20 -I"$path_to_include" -L"$path_to_lib" -fopenmp -o statespace_basic_test.x statespace_basic_test.cc -lgtest -lpthread +g++ -O3 -std=c++20 -I"$path_to_include" -L"$path_to_lib" -msse4 -fopenmp -o statespace_sse_test.x statespace_sse_test.cc -lgtest -lpthread +g++ -O3 -std=c++20 -I"$path_to_include" -L"$path_to_lib" -mavx2 -mfma -fopenmp -o unitary_calculator_avx_nobmi2_test.x unitary_calculator_avx_test.cc -lgtest -lpthread +g++ -O3 -std=c++20 -I"$path_to_include" -L"$path_to_lib" -mavx2 -mfma -mbmi2 -fopenmp -o unitary_calculator_avx_test.x unitary_calculator_avx_test.cc -lgtest -lpthread +g++ -O3 -std=c++20 -I"$path_to_include" -L"$path_to_lib" -mavx512f -mbmi2 -mfma -fopenmp -o unitary_calculator_avx512_test.x unitary_calculator_avx512_test.cc -lgtest -lpthread +g++ -O3 -std=c++20 -I"$path_to_include" -L"$path_to_lib" -fopenmp -o unitary_calculator_basic_test.x unitary_calculator_basic_test.cc -lgtest -lpthread +g++ -O3 -std=c++20 -I"$path_to_include" -L"$path_to_lib" -msse4 -fopenmp -o unitary_calculator_sse_test.x unitary_calculator_sse_test.cc -lgtest -lpthread +g++ -O3 -std=c++20 -I"$path_to_include" -L"$path_to_lib" -mavx2 -mfma -fopenmp -o unitaryspace_avx_test.x unitaryspace_avx_test.cc -lgtest -lpthread +g++ -O3 -std=c++20 -I"$path_to_include" -L"$path_to_lib" -mavx512f -mbmi2 -fopenmp -o unitaryspace_avx512_test.x unitaryspace_avx512_test.cc -lgtest -lpthread +g++ -O3 -std=c++20 -I"$path_to_include" -L"$path_to_lib" -fopenmp -o unitaryspace_basic_test.x unitaryspace_basic_test.cc -lgtest -lpthread +g++ -O3 -std=c++20 -I"$path_to_include" -L"$path_to_lib" -msse4 -fopenmp -o unitaryspace_sse_test.x unitaryspace_sse_test.cc -lgtest -lpthread +g++ -O3 -std=c++20 -I"$path_to_include" -L"$path_to_lib" -o vectorspace_test.x vectorspace_test.cc -lgtest -lpthread if command -v nvcc &>/dev/null; then - nvcc -O3 -I"$path_to_include" -L"$path_to_lib" -o hybrid_cuda_test.x hybrid_cuda_test.cu -lgtest -lpthread - nvcc -O3 -I"$path_to_include" -L"$path_to_lib" -o qtrajectory_cuda_test.x qtrajectory_cuda_test.cu -lgtest -lpthread - nvcc -O3 -I"$path_to_include" -L"$path_to_lib" -o simulator_cuda_test.x simulator_cuda_test.cu -lgtest -lpthread - nvcc -O3 -I"$path_to_include" -L"$path_to_lib" -o statespace_cuda_test.x statespace_cuda_test.cu -lgtest -lpthread + nvcc -O3 -std=c++20 -I"$path_to_include" -L"$path_to_lib" -o hybrid_cuda_test.x hybrid_cuda_test.cu -lgtest -lpthread + nvcc -O3 -std=c++20 -I"$path_to_include" -L"$path_to_lib" -o qtrajectory_cuda_test.x qtrajectory_cuda_test.cu -lgtest -lpthread + nvcc -O3 -std=c++20 -I"$path_to_include" -L"$path_to_lib" -o simulator_cuda_test.x simulator_cuda_test.cu -lgtest -lpthread + nvcc -O3 -std=c++20 -I"$path_to_include" -L"$path_to_lib" -o statespace_cuda_test.x statespace_cuda_test.cu -lgtest -lpthread if [ -n "$CUQUANTUM_ROOT" ]; then declare -a CUSTATEVECFLAGS @@ -69,14 +77,14 @@ if command -v nvcc &>/dev/null; then "-lcustatevec" "-lcublas" ) - nvcc -O3 "${CUSTATEVECFLAGS[@]}" -I"$path_to_include" -L"$path_to_lib" -o hybrid_custatevec_test.x hybrid_custatevec_test.cu -lgtest -lpthread - nvcc -O3 "${CUSTATEVECFLAGS[@]}" -I"$path_to_include" -L"$path_to_lib" -o qtrajectory_custatevec_test.x qtrajectory_custatevec_test.cu -lgtest -lpthread - nvcc -O3 "${CUSTATEVECFLAGS[@]}" -I"$path_to_include" -L"$path_to_lib" -o simulator_custatevec_test.x simulator_custatevec_test.cu -lgtest -lpthread - nvcc -O3 "${CUSTATEVECFLAGS[@]}" -I"$path_to_include" -L"$path_to_lib" -o statespace_custatevec_test.x statespace_custatevec_test.cu -lgtest -lpthread + nvcc -O3 -std=c++20 "${CUSTATEVECFLAGS[@]}" -I"$path_to_include" -L"$path_to_lib" -o hybrid_custatevec_test.x hybrid_custatevec_test.cu -lgtest -lpthread + nvcc -O3 -std=c++20 "${CUSTATEVECFLAGS[@]}" -I"$path_to_include" -L"$path_to_lib" -o qtrajectory_custatevec_test.x qtrajectory_custatevec_test.cu -lgtest -lpthread + nvcc -O3 -std=c++20 "${CUSTATEVECFLAGS[@]}" -I"$path_to_include" -L"$path_to_lib" -o simulator_custatevec_test.x simulator_custatevec_test.cu -lgtest -lpthread + nvcc -O3 -std=c++20 "${CUSTATEVECFLAGS[@]}" -I"$path_to_include" -L"$path_to_lib" -o statespace_custatevec_test.x statespace_custatevec_test.cu -lgtest -lpthread fi elif command -v hipcc &>/dev/null; then - hipcc -O3 -I"$path_to_include" -L"$path_to_lib" -o hybrid_hip_test.x hybrid_cuda_test.cu -lgtest -lpthread - hipcc -O3 -I"$path_to_include" -L"$path_to_lib" -o qtrajectory_hip_test.x qtrajectory_cuda_test.cu -lgtest -lpthread - hipcc -O3 -I"$path_to_include" -L"$path_to_lib" -o simulator_hip_test.x simulator_cuda_test.cu -lgtest -lpthread - hipcc -O3 -I"$path_to_include" -L"$path_to_lib" -o statespace_hip_test.x statespace_cuda_test.cu -lgtest -lpthread + hipcc -O3 -std=c++20 -I"$path_to_include" -L"$path_to_lib" -o hybrid_hip_test.x hybrid_cuda_test.cu -lgtest -lpthread + hipcc -O3 -std=c++20 -I"$path_to_include" -L"$path_to_lib" -o qtrajectory_hip_test.x qtrajectory_cuda_test.cu -lgtest -lpthread + hipcc -O3 -std=c++20 -I"$path_to_include" -L"$path_to_lib" -o simulator_hip_test.x simulator_cuda_test.cu -lgtest -lpthread + hipcc -O3 -std=c++20 -I"$path_to_include" -L"$path_to_lib" -o statespace_hip_test.x statespace_cuda_test.cu -lgtest -lpthread fi diff --git a/tests/qtrajectory_avx_test.cc b/tests/qtrajectory_avx_test.cc index e26670de6..a7a277dd2 100644 --- a/tests/qtrajectory_avx_test.cc +++ b/tests/qtrajectory_avx_test.cc @@ -19,7 +19,7 @@ #include "../lib/fuser_mqubit.h" #include "../lib/gates_cirq.h" #include "../lib/io.h" -#include "../lib/run_qsim.h" +#include "../lib/run_qsim_deprecated.h" #include "../lib/seqfor.h" #include "../lib/simulator_avx.h" diff --git a/tests/qtrajectory_cuda_test.cu b/tests/qtrajectory_cuda_test.cu index 73c291a55..d2e089221 100644 --- a/tests/qtrajectory_cuda_test.cu +++ b/tests/qtrajectory_cuda_test.cu @@ -19,7 +19,7 @@ #include "../lib/fuser_mqubit.h" #include "../lib/gates_cirq.h" #include "../lib/io.h" -#include "../lib/run_qsim.h" +#include "../lib/run_qsim_deprecated.h" #include "../lib/simulator_cuda.h" namespace qsim { diff --git a/tests/qtrajectory_custatevec_test.cu b/tests/qtrajectory_custatevec_test.cu index be6d65fe4..5bc97e7bc 100644 --- a/tests/qtrajectory_custatevec_test.cu +++ b/tests/qtrajectory_custatevec_test.cu @@ -22,7 +22,7 @@ #include "../lib/fuser_mqubit.h" #include "../lib/gates_cirq.h" #include "../lib/io.h" -#include "../lib/run_qsim.h" +#include "../lib/run_qsim_deprecated.h" #include "../lib/simulator_custatevec.h" namespace qsim { diff --git a/tests/run_custatevecex_test.cu b/tests/run_custatevecex_test.cu index aa94284b4..446026a46 100644 --- a/tests/run_custatevecex_test.cu +++ b/tests/run_custatevecex_test.cu @@ -80,10 +80,7 @@ struct Factory { }; TEST(RunQSimTest, QSimRunner1) { - std::stringstream ss(circuit_string); - Circuit> circuit; - - EXPECT_TRUE(CircuitQsimParser::FromStream(99, provider, ss, circuit)); + auto circuit = CircuitQsimParser::Run(circuit_string, 99); EXPECT_EQ(circuit.num_qubits, 4); EXPECT_EQ(circuit.ops.size(), 27); @@ -118,10 +115,7 @@ TEST(RunQSimTest, QSimRunner1) { } TEST(RunQSimTest, QSimRunner2) { - std::stringstream ss(circuit_string); - Circuit> circuit; - - EXPECT_TRUE(CircuitQsimParser::FromStream(99, provider, ss, circuit)); + auto circuit = CircuitQsimParser::Run(circuit_string, 99); EXPECT_EQ(circuit.num_qubits, 4); EXPECT_EQ(circuit.ops.size(), 27); @@ -174,10 +168,7 @@ R"(2 )"; TEST(RunQSimTest, QSimSampler) { - std::stringstream ss(sample_circuit_string); - Circuit> circuit; - - EXPECT_TRUE(CircuitQsimParser::FromStream(99, provider, ss, circuit)); + auto circuit = CircuitQsimParser::Run(sample_circuit_string, 99); EXPECT_EQ(circuit.num_qubits, 2); EXPECT_EQ(circuit.ops.size(), 11); diff --git a/tests/run_qsim_deprecated_test.cc b/tests/run_qsim_deprecated_test.cc new file mode 100644 index 000000000..f9fdde378 --- /dev/null +++ b/tests/run_qsim_deprecated_test.cc @@ -0,0 +1,245 @@ +// Copyright 2019 Google LLC. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include + +#include "gates_cirq_testfixture.h" + +#include "gtest/gtest.h" + +#include "../lib/circuit_qsim_parser.h" +#include "../lib/formux.h" +#include "../lib/fuser_basic.h" +#include "../lib/io.h" +#include "../lib/operation.h" +#include "../lib/run_qsim_deprecated.h" +#include "../lib/simmux.h" + +namespace qsim { + +constexpr char circuit_string[] = +R"(4 +0 h 0 +0 h 1 +0 h 2 +0 h 3 +1 cz 0 1 +1 cz 2 3 +2 t 0 +2 x 1 +2 y 2 +2 t 3 +3 y 0 +3 cz 1 2 +3 x 3 +4 t 1 +4 t 2 +5 cz 1 2 +6 x 1 +6 y 2 +7 cz 1 2 +8 t 1 +8 t 2 +9 cz 0 1 +9 cz 2 3 +10 h 0 +10 h 1 +10 h 2 +10 h 3 +)"; + +struct Factory { + using Simulator = qsim::Simulator; + using StateSpace = Simulator::StateSpace; + + static StateSpace CreateStateSpace() { + return StateSpace(1); + } + + static Simulator CreateSimulator() { + return Simulator(1); + } +}; + +TEST(RunQSimTest, QSimRunner1) { + auto circuit = CircuitQsimParser::Run(circuit_string, 99); + + EXPECT_EQ(circuit.num_qubits, 4); + EXPECT_EQ(circuit.ops.size(), 27); + + using Simulator = Factory::Simulator; + using StateSpace = Simulator::StateSpace; + using State = StateSpace::State; + using Runner = QSimRunner, Factory>; + + float entropy = 0; + + auto measure = [&entropy]( + unsigned k, const StateSpace& state_space, const State& state) { + // Calculate entropy. + + entropy = 0; + auto size = uint64_t{1} << state.num_qubits(); + + for (uint64_t i = 0; i < size; ++i) { + auto ampl = state_space.GetAmpl(state, i); + float p = std::norm(ampl); + entropy -= p * std::log(p); + } + }; + + Runner::Parameter param; + param.seed = 1; + param.verbosity = 0; + + EXPECT_TRUE(Runner::Run(param, Factory(), circuit, measure)); + + EXPECT_NEAR(entropy, 2.2192848, 1e-6); +} + +TEST(RunQSimTest, QSimRunner2) { + auto circuit = CircuitQsimParser::Run(circuit_string, 99); + + EXPECT_EQ(circuit.num_qubits, 4); + EXPECT_EQ(circuit.ops.size(), 27); + + using Simulator = Factory::Simulator; + using StateSpace = Simulator::StateSpace; + using State = StateSpace::State; + using Runner = QSimRunner, Factory>; + + StateSpace state_space = Factory::CreateStateSpace(); + State state = state_space.Create(circuit.num_qubits); + + EXPECT_FALSE(state_space.IsNull(state)); + + state_space.SetStateZero(state); + + Runner::Parameter param; + param.seed = 1; + param.verbosity = 0; + + EXPECT_TRUE(Runner::Run(param, Factory(), circuit, state)); + + // Calculate entropy. + + float entropy = 0; + auto size = uint64_t{1} << circuit.num_qubits; + + for (uint64_t i = 0; i < size; ++i) { + auto ampl = state_space.GetAmpl(state, i); + float p = std::norm(ampl); + entropy -= p * std::log(p); + } + + EXPECT_NEAR(entropy, 2.2192848, 1e-6); +} + +constexpr char sample_circuit_string[] = +R"(2 +0 h 0 +0 x 1 +1 m 1 +2 cx 0 1 +3 m 0 1 +4 m 0 +5 cx 1 0 +6 m 0 +7 x 0 +7 h 1 +8 m 0 1 +)"; + +TEST(RunQSimTest, QSimSampler) { + auto circuit = CircuitQsimParser::Run(sample_circuit_string, 99); + + EXPECT_EQ(circuit.num_qubits, 2); + EXPECT_EQ(circuit.ops.size(), 11); + + using Simulator = Factory::Simulator; + using StateSpace = Simulator::StateSpace; + using Result = StateSpace::MeasurementResult; + using State = StateSpace::State; + using Runner = QSimRunner, Factory>; + + StateSpace state_space = Factory::CreateStateSpace(); + State state = state_space.Create(circuit.num_qubits); + + EXPECT_FALSE(state_space.IsNull(state)); + + state_space.SetStateZero(state); + + std::vector results; + + Runner::Parameter param; + param.seed = 1; + param.verbosity = 0; + + EXPECT_TRUE(Runner::Run(param, Factory(), circuit, state, results)); + + // Results should contain (qubit @ time): + // (1 @ 1) - should be |01) + EXPECT_TRUE(results[0].bitstring[0]); + // (0 @ 3), (1 @ 3) - either |01) or |10) + EXPECT_EQ(results[1].bitstring[0], !results[1].bitstring[1]); + // (0 @ 4) - should match (0 @ 3) + EXPECT_EQ(results[1].bitstring[0], results[2].bitstring[0]); + // (0 @ 6) - either |11) or |10) + EXPECT_TRUE(results[3].bitstring[0]); + // (0 @ 8), (1 @ 8) - should be |00) + EXPECT_FALSE(results[4].bitstring[0]); + EXPECT_FALSE(results[4].bitstring[1]); +} + +TEST(RunQSimTest, CirqGates) { + auto circuit = CirqCircuit1::GetCircuit(true); + const auto& expected_results = CirqCircuit1::expected_results1; + + using Simulator = Factory::Simulator; + using StateSpace = Simulator::StateSpace; + using State = StateSpace::State; + using Runner = QSimRunner, Factory>; + + StateSpace state_space = Factory::CreateStateSpace(); + State state = state_space.Create(circuit.num_qubits); + + auto size = uint64_t{1} << circuit.num_qubits; + + EXPECT_FALSE(state_space.IsNull(state)); + EXPECT_EQ(size, expected_results.size()); + + state_space.SetStateZero(state); + + Runner::Parameter param; + param.seed = 1; + param.verbosity = 0; + + EXPECT_TRUE(Runner::Run(param, Factory(), circuit, state)); + + for (uint64_t i = 0; i < size; ++i) { + auto ampl = state_space.GetAmpl(state, i); + EXPECT_NEAR(std::real(ampl), std::real(expected_results[i]), 2e-6); + EXPECT_NEAR(std::imag(ampl), std::imag(expected_results[i]), 2e-6); + } +} + +} // namespace qsim + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/tests/run_qsim_test.cc b/tests/run_qsim_test.cc index a442b64c7..c360828e7 100644 --- a/tests/run_qsim_test.cc +++ b/tests/run_qsim_test.cc @@ -22,6 +22,8 @@ #include "gtest/gtest.h" #include "../lib/circuit_qsim_parser.h" +#include "../lib/classical_control_obs.h" +#include "../lib/classical_control_symtab.h" #include "../lib/formux.h" #include "../lib/fuser_basic.h" #include "../lib/io.h" @@ -29,39 +31,56 @@ #include "../lib/run_qsim.h" #include "../lib/simmux.h" -namespace qsim { +struct TestParserError { + template + [[noreturn]] static void Throw( + std::string_view msg, unsigned lc, Args&&... args) { + throw std::invalid_argument("syntax error"); + } +}; + +struct TestRuntimeError { + template + [[noreturn]] static void Throw( + std::string_view msg, unsigned lc, Args&&... args) { + throw std::runtime_error("runtime error"); + } +}; + +using TestQsimParser = + qsim::CircuitQsimParser; -constexpr char provider[] = "run_qsim_test"; +namespace qsim { constexpr char circuit_string[] = R"(4 -0 h 0 -0 h 1 -0 h 2 -0 h 3 -1 cz 0 1 -1 cz 2 3 -2 t 0 -2 x 1 -2 y 2 -2 t 3 -3 y 0 -3 cz 1 2 -3 x 3 -4 t 1 -4 t 2 -5 cz 1 2 -6 x 1 -6 y 2 -7 cz 1 2 -8 t 1 -8 t 2 -9 cz 0 1 -9 cz 2 3 -10 h 0 -10 h 1 -10 h 2 -10 h 3 +h 0 +h 1 +h 2 +h 3 +cz 0 1 +cz 2 3 +t 0 +x 1 +y 2 +t 3 +y 0 +cz 1 2 +x 3 +t 1 +t 2 +cz 1 2 +x 1 +y 2 +cz 1 2 +t 1 +t 2 +cz 0 1 +cz 2 3 +h 0 +h 1 +h 2 +h 3 )"; struct Factory { @@ -77,172 +96,170 @@ struct Factory { } }; -TEST(RunQSimTest, QSimRunner1) { - std::stringstream ss(circuit_string); - Circuit> circuit; +TEST(RunQSimTest, QSimRunner) { + auto circuit = CircuitQsimParser::Run(circuit_string, 99); - EXPECT_TRUE(CircuitQsimParser::FromStream(99, provider, ss, circuit)); EXPECT_EQ(circuit.num_qubits, 4); EXPECT_EQ(circuit.ops.size(), 27); using Simulator = Factory::Simulator; using StateSpace = Simulator::StateSpace; using State = StateSpace::State; - using Runner = QSimRunner, Factory>; - - float entropy = 0; + using Runner = QSimRunner>; - auto measure = [&entropy]( - unsigned k, const StateSpace& state_space, const State& state) { - // Calculate entropy. + Simulator simulator = Factory::CreateSimulator(); + StateSpace state_space = Factory::CreateStateSpace(); + State state = state_space.Create(circuit.num_qubits); - entropy = 0; - auto size = uint64_t{1} << state.num_qubits(); + EXPECT_FALSE(state_space.IsNull(state)); - for (uint64_t i = 0; i < size; ++i) { - auto ampl = state_space.GetAmpl(state, i); - float p = std::norm(ampl); - entropy -= p * std::log(p); - } - }; + state_space.SetStateZero(state); Runner::Parameter param; - param.seed = 1; param.verbosity = 0; - EXPECT_TRUE(Runner::Run(param, Factory(), circuit, measure)); + cc::SymTable symtab; + cc::Observables obss; + + EXPECT_TRUE(Runner::Run( + param, circuit, state_space, simulator, 1, state, symtab, obss)); + + // Calculate entropy. + + float entropy = 0; + auto size = uint64_t{1} << circuit.num_qubits; + + for (uint64_t i = 0; i < size; ++i) { + auto ampl = state_space.GetAmpl(state, i); + float p = std::norm(ampl); + entropy -= p * std::log(p); + } EXPECT_NEAR(entropy, 2.2192848, 1e-6); } -TEST(RunQSimTest, QSimRunner2) { - std::stringstream ss(circuit_string); - Circuit> circuit; - - EXPECT_TRUE(CircuitQsimParser::FromStream(99, provider, ss, circuit)); - EXPECT_EQ(circuit.num_qubits, 4); - EXPECT_EQ(circuit.ops.size(), 27); +TEST(RunQSimTest, CirqGates) { + auto circuit = CirqCircuit1::GetCircuit(true); + const auto& expected_results = CirqCircuit1::expected_results1; using Simulator = Factory::Simulator; using StateSpace = Simulator::StateSpace; using State = StateSpace::State; - using Runner = QSimRunner, Factory>; + using Runner = QSimRunner>; + Simulator simulator = Factory::CreateSimulator(); StateSpace state_space = Factory::CreateStateSpace(); State state = state_space.Create(circuit.num_qubits); + auto size = uint64_t{1} << circuit.num_qubits; + EXPECT_FALSE(state_space.IsNull(state)); + EXPECT_EQ(size, expected_results.size()); state_space.SetStateZero(state); Runner::Parameter param; - param.seed = 1; param.verbosity = 0; - EXPECT_TRUE(Runner::Run(param, Factory(), circuit, state)); + cc::SymTable symtab; + cc::Observables obss; - // Calculate entropy. - - float entropy = 0; - auto size = uint64_t{1} << circuit.num_qubits; + EXPECT_TRUE(Runner::Run( + param, circuit, state_space, simulator, 1, state, symtab, obss)); for (uint64_t i = 0; i < size; ++i) { auto ampl = state_space.GetAmpl(state, i); - float p = std::norm(ampl); - entropy -= p * std::log(p); + EXPECT_NEAR(std::real(ampl), std::real(expected_results[i]), 2e-6); + EXPECT_NEAR(std::imag(ampl), std::imag(expected_results[i]), 2e-6); } - - EXPECT_NEAR(entropy, 2.2192848, 1e-6); } -constexpr char sample_circuit_string[] = -R"(2 -0 h 0 -0 x 1 -1 m 1 -2 cx 0 1 -3 m 0 1 -4 m 0 -5 cx 1 0 -6 m 0 -7 x 0 -7 h 1 -8 m 0 1 -)"; +TEST(RunQSimTest, Factorial) { + std::string circuit_str = R"( + int f = 1 + int i = 1 + repeat i <= n + f = f * i + i = i + 1 + end + )"; -TEST(RunQSimTest, QSimSampler) { - std::stringstream ss(sample_circuit_string); - Circuit> circuit; + cc::SymTable symtab; + symtab.EnterScope(symtab.AddScope()); + symtab.Insert("n", cc::Symbol::Int(12)); - EXPECT_TRUE(CircuitQsimParser::FromStream(99, provider, ss, circuit)); - EXPECT_EQ(circuit.num_qubits, 2); - EXPECT_EQ(circuit.ops.size(), 11); + cc::Observables obss; + + auto [circuit, _] = TestQsimParser::Run(circuit_str, 100, symtab); using Simulator = Factory::Simulator; using StateSpace = Simulator::StateSpace; - using Result = StateSpace::MeasurementResult; using State = StateSpace::State; - using Runner = QSimRunner, Factory>; + using Runner = QSimRunner>; + Simulator simulator = Factory::CreateSimulator(); StateSpace state_space = Factory::CreateStateSpace(); State state = state_space.Create(circuit.num_qubits); - EXPECT_FALSE(state_space.IsNull(state)); - - state_space.SetStateZero(state); - - std::vector results; - Runner::Parameter param; - param.seed = 1; param.verbosity = 0; - EXPECT_TRUE(Runner::Run(param, Factory(), circuit, state, results)); - - // Results should contain (qubit @ time): - // (1 @ 1) - should be |01) - EXPECT_TRUE(results[0].bitstring[0]); - // (0 @ 3), (1 @ 3) - either |01) or |10) - EXPECT_EQ(results[1].bitstring[0], !results[1].bitstring[1]); - // (0 @ 4) - should match (0 @ 3) - EXPECT_EQ(results[1].bitstring[0], results[2].bitstring[0]); - // (0 @ 6) - either |11) or |10) - EXPECT_TRUE(results[3].bitstring[0]); - // (0 @ 8), (1 @ 8) - should be |00) - EXPECT_FALSE(results[4].bitstring[0]); - EXPECT_FALSE(results[4].bitstring[1]); + EXPECT_TRUE(Runner::Run( + param, circuit, state_space, simulator, 1, state, symtab, obss)); + + const auto* var = symtab.Lookup("f"); + + ASSERT_NE(var, nullptr); + EXPECT_EQ(var->GetInt(), 479001600); } -TEST(RunQSimTest, CirqGates) { - auto circuit = CirqCircuit1::GetCircuit(true); - const auto& expected_results = CirqCircuit1::expected_results1; +TEST(RunQSimTest, Histogram) { + std::string circuit_str = R"( + 2 + x 1 + m 0 1 m1 + histogram m1 + )"; + + cc::SymTable symtab; + + auto [circuit, obss] = TestQsimParser::Run(circuit_str, 100, symtab); using Simulator = Factory::Simulator; using StateSpace = Simulator::StateSpace; using State = StateSpace::State; - using Runner = QSimRunner, Factory>; + using Runner = QSimRunner>; + Simulator simulator = Factory::CreateSimulator(); StateSpace state_space = Factory::CreateStateSpace(); State state = state_space.Create(circuit.num_qubits); - auto size = uint64_t{1} << circuit.num_qubits; + Runner::Parameter param; + param.verbosity = 0; - EXPECT_FALSE(state_space.IsNull(state)); - EXPECT_EQ(size, expected_results.size()); + for (uint64_t r = 0; r < 100; ++r) { + state_space.SetStateZero(state); + EXPECT_TRUE(Runner::Run( + param, circuit, state_space, simulator, 1, state, symtab, obss)); + obss.Iterate([](auto, auto& obs) { obs.Update(); }); + } - state_space.SetStateZero(state); + const auto* h = obss.Lookup("m1"); + ASSERT_NE(h, nullptr); - Runner::Parameter param; - param.seed = 1; - param.verbosity = 0; + EXPECT_EQ(h->num_qubits, 2); - EXPECT_TRUE(Runner::Run(param, Factory(), circuit, state)); + ASSERT_EQ(h->cur_count.size(), 4); + EXPECT_EQ(h->cur_count[0], 0); + EXPECT_EQ(h->cur_count[1], 0); + EXPECT_EQ(h->cur_count[2], 0); + EXPECT_EQ(h->cur_count[3], 0); - for (uint64_t i = 0; i < size; ++i) { - auto ampl = state_space.GetAmpl(state, i); - EXPECT_NEAR(std::real(ampl), std::real(expected_results[i]), 2e-6); - EXPECT_NEAR(std::imag(ampl), std::imag(expected_results[i]), 2e-6); - } + ASSERT_EQ(h->total_count.size(), 4); + EXPECT_EQ(h->total_count[0], 0); + EXPECT_EQ(h->total_count[1], 0); + EXPECT_EQ(h->total_count[2], 100); + EXPECT_EQ(h->total_count[3], 0); } } // namespace qsim diff --git a/tests/run_qsimh_test.cc b/tests/run_qsimh_test.cc index 784629e81..249792e89 100644 --- a/tests/run_qsimh_test.cc +++ b/tests/run_qsimh_test.cc @@ -115,10 +115,7 @@ struct Factory { }; TEST(RunQSimHTest, QSimHRunner) { - std::stringstream ss(circuit_string); - Circuit> circuit; - - EXPECT_TRUE(CircuitQsimParser::FromStream(99, provider, ss, circuit)); + auto circuit = CircuitQsimParser::Run(circuit_string, 99); EXPECT_EQ(circuit.num_qubits, 4); EXPECT_EQ(circuit.ops.size(), 63); diff --git a/tests/statespace_testfixture.h b/tests/statespace_testfixture.h index 0bf9e4cd4..ac7328506 100644 --- a/tests/statespace_testfixture.h +++ b/tests/statespace_testfixture.h @@ -30,7 +30,7 @@ #include "../lib/gates_qsim.h" #include "../lib/io.h" #include "../lib/operation.h" -#include "../lib/run_qsim.h" +#include "../lib/run_qsim_deprecated.h" namespace qsim { @@ -472,9 +472,7 @@ void TestNormAndInnerProduct(const Factory& factory) { unsigned depth = 8; - std::stringstream ss(circuit_string); - Circuit> circuit; - EXPECT_TRUE(CircuitQsimParser::FromStream(depth, provider, ss, circuit)); + auto circuit = CircuitQsimParser::Run(circuit_string, depth); circuit.ops.push_back(GateT::Create(depth + 1, 0)); StateSpace state_space = factory.CreateStateSpace(); @@ -568,10 +566,7 @@ void TestSamplingCrossEntropyDifference(const Factory& factory) { unsigned depth = 30; uint64_t num_samples = 2000000; - std::stringstream ss(circuit_string); - Circuit> circuit; - EXPECT_TRUE(CircuitQsimParser::FromStream(depth, provider, ss, circuit)); - + auto circuit = CircuitQsimParser::Run(circuit_string, depth); StateSpace state_space = factory.CreateStateSpace(); State state = state_space.Create(circuit.num_qubits); @@ -783,10 +778,7 @@ void TestMeasurementLarge(const Factory& factory) { unsigned depth = 20; - std::stringstream ss(circuit_string); - Circuit> circuit; - EXPECT_TRUE(CircuitQsimParser::FromStream(depth, provider, ss, circuit)); - + auto circuit = CircuitQsimParser::Run(circuit_string, depth); StateSpace state_space = factory.CreateStateSpace(); State state = state_space.Create(circuit.num_qubits); From db502d87c5a66bae2b8e0edc35465a09cd4d2d4c Mon Sep 17 00:00:00 2001 From: Sergei Isakov <54642992+sergeisakov@users.noreply.github.com> Date: Mon, 27 Jul 2026 23:31:59 +0200 Subject: [PATCH 02/13] Attempt to make checks successful. --- lib/classical_control_symbol.h | 4 ++-- qsim/BUILD | 1 - qsim/qsim.cc | 3 ++- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/classical_control_symbol.h b/lib/classical_control_symbol.h index 1cc6898a7..b658554ce 100644 --- a/lib/classical_control_symbol.h +++ b/lib/classical_control_symbol.h @@ -54,8 +54,8 @@ struct Symbol { /** Variant over all supported concrete value types. */ using Val = std::variant; - /** Constructs an uninitialized/default Symbol. */ - Symbol() {} + /** Constructs a default Symbol. */ + Symbol() : val(Int{0}) {} /** * Constructs a Symbol inferring read-only status based on the value type. diff --git a/qsim/BUILD b/qsim/BUILD index a26036afe..b083bd185 100644 --- a/qsim/BUILD +++ b/qsim/BUILD @@ -48,4 +48,3 @@ cc_binary( "//lib:util_cpu", ], ) - diff --git a/qsim/qsim.cc b/qsim/qsim.cc index 2d3005bdd..f0ef947f6 100644 --- a/qsim/qsim.cc +++ b/qsim/qsim.cc @@ -193,7 +193,8 @@ int main(int argc, char* argv[]) { auto cstr = ReadFile(opt.circuit_file); unsigned maxtime = std::numeric_limits::max(); - auto [circuit, obss] = CircuitQsimParser::Run(cstr, maxtime, symtab); + auto [circuit, obss] = + qsim::CircuitQsimParser::Run(cstr, maxtime, symtab); struct Factory { Factory(unsigned num_threads) : num_threads(num_threads) {} From 5c2d419d721cec2ae1ad2fcca4f244cf9d674dfa Mon Sep 17 00:00:00 2001 From: Sergei Isakov <54642992+sergeisakov@users.noreply.github.com> Date: Mon, 27 Jul 2026 23:52:13 +0200 Subject: [PATCH 03/13] Address Gemini's comments. --- lib/circuit_qsim_parser.h | 3 +++ lib/classical_control_parser.h | 1 + qsim/qsim.cc | 15 +++++++++++++++ 3 files changed, 19 insertions(+) diff --git a/lib/circuit_qsim_parser.h b/lib/circuit_qsim_parser.h index 23db3c810..5a64a97dc 100644 --- a/lib/circuit_qsim_parser.h +++ b/lib/circuit_qsim_parser.h @@ -438,6 +438,9 @@ struct CircuitQsimParser final { if (qubits.size() == 0) { PE::Throw( "measurement gate should have at least one qubit", t.lc); + } else if (qubits.size() > 63) { + PE::Throw( + "measurement gate cannot measure more than 63 qubits", t.lc); } cur_time = GetTime(t, time, cur_time, qubits, qubit_times); diff --git a/lib/classical_control_parser.h b/lib/classical_control_parser.h index b213d4fe5..0045bd2eb 100644 --- a/lib/classical_control_parser.h +++ b/lib/classical_control_parser.h @@ -809,6 +809,7 @@ struct ExprParser { } if (sym->HoldsMea()) { + // sym->Size() should should not exceed 63. val &= (Symbol::Int{1} << sym->Size()) - 1; } } else if (IsSymbolInd(l)) { diff --git a/qsim/qsim.cc b/qsim/qsim.cc index f0ef947f6..fc22064ee 100644 --- a/qsim/qsim.cc +++ b/qsim/qsim.cc @@ -151,6 +151,21 @@ bool ValidateOptions(const Options& opt) { return false; } + if (opt.num_workers == 0) { + qsim::IO::errorf("number of workers must be greater than 0.\n"); + return false; + } + + if (opt.num_threads == 0) { + qsim::IO::errorf("number of threads per worker must be greater than 0.\n"); + return false; + } + + if (opt.rep1 <= opt.rep0) { + qsim::IO::errorf("rep1 must be greater than rep0.\n"); + return false; + } + return true; } From 6d8dc7b13f41f2f45e2fdc10aa0fcd9d0d2177d5 Mon Sep 17 00:00:00 2001 From: Sergei Isakov <54642992+sergeisakov@users.noreply.github.com> Date: Tue, 28 Jul 2026 00:54:27 +0200 Subject: [PATCH 04/13] Second attempt to make checks successful. --- apps/BUILD | 6 +++--- lib/parfor.h | 17 ++++++++++------- lib/seqfor.h | 13 ++++++++----- 3 files changed, 21 insertions(+), 15 deletions(-) diff --git a/apps/BUILD b/apps/BUILD index 9063eda94..76c45e83a 100644 --- a/apps/BUILD +++ b/apps/BUILD @@ -28,7 +28,7 @@ cc_binary( copts = gsframe_copts, data = ["//circuits:circuit_q24"], deps = [ - "//lib:run_qsim_deprecated_lib", + "//lib:run_qsim_lib", ], ) @@ -37,7 +37,7 @@ cc_binary( srcs = ["qsim_von_neumann.cc"], copts = gsframe_copts, deps = [ - "//lib:run_qsim_deprecated_lib", + "//lib:run_qsim_lib", ], ) @@ -47,7 +47,7 @@ cc_binary( copts = gsframe_copts, deps = [ "//lib:bitstring", - "//lib:run_qsim_deprecated_lib", + "//lib:run_qsim_lib", ], ) diff --git a/lib/parfor.h b/lib/parfor.h index 8a3a4d61b..f5bde45fa 100644 --- a/lib/parfor.h +++ b/lib/parfor.h @@ -64,9 +64,12 @@ struct ParallelForT { } template - std::vector RunReduceP( + auto RunReduceP( uint64_t size, Function&& func, Op&& op, Args&&... args) const { - std::vector partial_results; + using ResultType = + std::invoke_result_t; + + std::vector partial_results; if (num_threads > 1 && size >= MIN_SIZE) { partial_results.resize(num_threads, 0); @@ -79,7 +82,7 @@ struct ParallelForT { uint64_t i0 = GetIndex0(size, m); uint64_t i1 = GetIndex1(size, m); - typename Op::result_type partial_result = 0; + ResultType partial_result{}; for (uint64_t i = i0; i < i1; ++i) { partial_result = op(partial_result, func(n, m, i, args...)); @@ -88,7 +91,7 @@ struct ParallelForT { partial_results[m] = partial_result; } } else if (num_threads > 0) { - typename Op::result_type result = 0; + ResultType result{}; for (uint64_t i = 0; i < size; ++i) { result = op(result, func(1, 0, i, args...)); } @@ -100,11 +103,11 @@ struct ParallelForT { } template - typename Op::result_type RunReduce(uint64_t size, Function&& func, - Op&& op, Args&&... args) const { + auto RunReduce( + uint64_t size, Function&& func, Op&& op, Args&&... args) const { auto partial_results = RunReduceP(size, func, std::move(op), args...); - typename Op::result_type result = 0; + typename decltype(partial_results)::value_type result{}; for (auto partial_result : partial_results) { result = op(result, partial_result); diff --git a/lib/seqfor.h b/lib/seqfor.h index 3ebf07ce2..09e668903 100644 --- a/lib/seqfor.h +++ b/lib/seqfor.h @@ -45,20 +45,23 @@ struct SequentialFor { } template - static std::vector RunReduceP( + static auto RunReduceP( uint64_t size, Function&& func, Op&& op, Args&&... args) { - typename Op::result_type result = 0; + using ResultType = + std::invoke_result_t; + + ResultType result{}; for (uint64_t i = 0; i < size; ++i) { result = op(result, func(1, 0, i, args...)); } - return std::vector(1, result); + return std::vector(1, result); } template - static typename Op::result_type RunReduce(uint64_t size, Function&& func, - Op&& op, Args&&... args) { + static auto RunReduce( + uint64_t size, Function&& func, Op&& op, Args&&... args) { return RunReduceP(size, func, std::move(op), args...)[0]; } }; From afd0a57f6f978e1fd72afacafb8440114932955d Mon Sep 17 00:00:00 2001 From: Sergei Isakov <54642992+sergeisakov@users.noreply.github.com> Date: Tue, 28 Jul 2026 01:51:06 +0200 Subject: [PATCH 05/13] Third attempt to make checks successful. --- lib/BUILD | 2 ++ lib/classical_control_symbol.h | 10 +++++----- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/BUILD b/lib/BUILD index 7d79355ac..8d58db76f 100644 --- a/lib/BUILD +++ b/lib/BUILD @@ -288,6 +288,7 @@ cc_library( hdrs = [ "bits.h", "channel.h", + "channels_cirq.h", "circuit.h", "circuit_qsim_parser.h", "classical_control_error.h", @@ -349,6 +350,7 @@ cc_library( hdrs = [ "bits.h", "channel.h", + "channels_cirq.h", "circuit.h", "circuit_qsim_parser.h", "expect.h", diff --git a/lib/classical_control_symbol.h b/lib/classical_control_symbol.h index b658554ce..6fd96cdae 100644 --- a/lib/classical_control_symbol.h +++ b/lib/classical_control_symbol.h @@ -141,7 +141,7 @@ struct Symbol { * or 1 for scalars. */ unsigned Size() const { - auto f = [this](auto&& v) -> unsigned { + auto f = [](auto&& v) -> unsigned { using V = std::decay_t; if constexpr (std::is_same_v) { @@ -165,7 +165,7 @@ struct Symbol { * @throws std::runtime_error If the stored value is a Float or Vector. */ Int GetInt() const { - auto f = [this](auto&& v) -> Int { + auto f = [](auto&& v) -> Int { using V = std::decay_t; if constexpr (std::is_same_v) { @@ -189,7 +189,7 @@ struct Symbol { * @throws std::runtime_error If the stored value is a Vector. */ Float GetFloat() const { - auto f = [this](auto&& v) -> Float { + auto f = [](auto&& v) -> Float { using V = std::decay_t; if constexpr (std::is_same_v) { @@ -215,7 +215,7 @@ struct Symbol { * or non-indexable scalars. */ Int GetInt(std::size_t i) const { - auto f = [this, &i](auto&& v) -> Int { + auto f = [&i](auto&& v) -> Int { using V = std::decay_t; if constexpr (std::is_same_v) { @@ -245,7 +245,7 @@ struct Symbol { * scalars. */ Float GetFloat(std::size_t i) const { - auto f = [this, &i](auto&& v) -> Float { + auto f = [&i](auto&& v) -> Float { using V = std::decay_t; if constexpr (std::is_same_v) { From 50a103709a9d5c209a991af72bb7a287bfd27fc4 Mon Sep 17 00:00:00 2001 From: Sergei Isakov <54642992+sergeisakov@users.noreply.github.com> Date: Tue, 28 Jul 2026 02:09:12 +0200 Subject: [PATCH 06/13] Fourth attempt to make checks successful. --- lib/BUILD | 2 ++ tests/hybrid_testfixture.h | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/BUILD b/lib/BUILD index 8d58db76f..960fc6b43 100644 --- a/lib/BUILD +++ b/lib/BUILD @@ -307,6 +307,7 @@ cc_library( "fuser_mqubit.h", "gate.h", "gate_appl.h", + "gates_cirq", "gates_qsim.h", "io.h", "io_file.h", @@ -360,6 +361,7 @@ cc_library( "fuser_mqubit.h", "gate.h", "gate_appl.h", + "gates_cirq", "gates_qsim.h", "hybrid.h", "io.h", diff --git a/tests/hybrid_testfixture.h b/tests/hybrid_testfixture.h index 8de81a865..957aeb9cc 100644 --- a/tests/hybrid_testfixture.h +++ b/tests/hybrid_testfixture.h @@ -33,7 +33,6 @@ namespace qsim { template void TestHybrid2(const Factory& factory) { - constexpr char provider[] = "hybrid_test"; constexpr char circuit_string[] = R"(2 0 h 0 @@ -170,7 +169,6 @@ R"(2 template void TestHybrid4(const Factory& factory) { - constexpr char provider[] = "hybrid_test"; constexpr char circuit_string[] = R"(4 0 h 0 From d674a0a6b3dd8110b014657ef5c3c816416741d1 Mon Sep 17 00:00:00 2001 From: Sergei Isakov <54642992+sergeisakov@users.noreply.github.com> Date: Tue, 28 Jul 2026 02:19:22 +0200 Subject: [PATCH 07/13] Fifth attempt to make checks successful. --- lib/BUILD | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/BUILD b/lib/BUILD index 960fc6b43..c71b20e04 100644 --- a/lib/BUILD +++ b/lib/BUILD @@ -307,7 +307,7 @@ cc_library( "fuser_mqubit.h", "gate.h", "gate_appl.h", - "gates_cirq", + "gates_cirq.h", "gates_qsim.h", "io.h", "io_file.h", @@ -361,7 +361,7 @@ cc_library( "fuser_mqubit.h", "gate.h", "gate_appl.h", - "gates_cirq", + "gates_cirq.h", "gates_qsim.h", "hybrid.h", "io.h", From 74ddc3e958b2a6492c815073ccf3bc1f7e1a482a Mon Sep 17 00:00:00 2001 From: Sergei Isakov <54642992+sergeisakov@users.noreply.github.com> Date: Tue, 28 Jul 2026 14:59:55 +0200 Subject: [PATCH 08/13] Sixth attempt to make checks successful. --- lib/classical_control_util.h | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/lib/classical_control_util.h b/lib/classical_control_util.h index 723fe8e62..d76806a66 100644 --- a/lib/classical_control_util.h +++ b/lib/classical_control_util.h @@ -17,6 +17,7 @@ #include #include +#include #include #include #include @@ -71,11 +72,21 @@ inline int64_t ToInt(std::string_view s) { inline double ToFloat(std::string_view s) { double val; +#if defined(_MSC_VER) || (defined(__GLIBCXX__) && __GLIBCXX__ >= 20210427) || \ + (defined(_LIBCPP_VERSION) && _LIBCPP_VERSION >= 170000) auto [p, ec] = std::from_chars(s.data(), s.data() + s.size(), val); if (ec != std::errc{} || p != s.data() + s.size()) { - Error::Throw("cannot convert string to int."); + Error::Throw("cannot convert string to float."); + } +#else + char* end; + std::string str(s); + val = std::strtod(str.c_str(), &end); + if (end == str.c_str() || errno == ERANGE) { + Error::Throw("cannot convert string to float."); } +#endif return val; } From a45f95b6e72eb195f80d3f51bcd35b85ff5b285d Mon Sep 17 00:00:00 2001 From: Sergei Isakov <54642992+sergeisakov@users.noreply.github.com> Date: Tue, 28 Jul 2026 15:45:23 +0200 Subject: [PATCH 09/13] Silence warnings. --- lib/circuit_qsim_parser.h | 2 +- lib/classical_control_expr.h | 2 +- lib/classical_control_parser.h | 2 +- lib/classical_control_util.h | 2 +- lib/fuser.h | 2 +- lib/fuser_basic.h | 6 +++--- lib/fuser_mqubit.h | 18 +++++++++--------- lib/gate.h | 6 +++--- 8 files changed, 20 insertions(+), 20 deletions(-) diff --git a/lib/circuit_qsim_parser.h b/lib/circuit_qsim_parser.h index 5a64a97dc..a8d702974 100644 --- a/lib/circuit_qsim_parser.h +++ b/lib/circuit_qsim_parser.h @@ -855,7 +855,7 @@ struct CircuitQsimParser final { PE::Throw("expected an identifier", t.lc); } - if (auto* sym = symtab.LookupInCurrentScope(t.val)) { + if (symtab.LookupInCurrentScope(t.val) != nullptr) { PE::Throw("identifier '{}' is already defined", t.lc, t.val); } diff --git a/lib/classical_control_expr.h b/lib/classical_control_expr.h index f583993f2..b2ce81047 100644 --- a/lib/classical_control_expr.h +++ b/lib/classical_control_expr.h @@ -79,7 +79,7 @@ inline unsigned IndexSize(const Index&) { */ template inline bool IsConstIndex(const SymTable& symtab, const Index& i) { - auto f = [&symtab](auto&& i) -> bool { + auto f = [](auto&& i) -> bool { using I = std::decay_t; return std::is_same_v || std::is_same_v; }; diff --git a/lib/classical_control_parser.h b/lib/classical_control_parser.h index 0045bd2eb..d7156127a 100644 --- a/lib/classical_control_parser.h +++ b/lib/classical_control_parser.h @@ -217,7 +217,7 @@ struct ExprParser { if (IsConstExpr(symtab, l)) { return TInt{~EvalIntExpr(symtab, l)}; } else { - return TFuncI{[l, lc](const SymTable& symtab) { + return TFuncI{[l](const SymTable& symtab) { auto val = ~EvalIntExpr(symtab, l); val = TruncateMea(symtab, l, val); return TInt{val}; diff --git a/lib/classical_control_util.h b/lib/classical_control_util.h index d76806a66..d2ef8534d 100644 --- a/lib/classical_control_util.h +++ b/lib/classical_control_util.h @@ -73,7 +73,7 @@ inline double ToFloat(std::string_view s) { double val; #if defined(_MSC_VER) || (defined(__GLIBCXX__) && __GLIBCXX__ >= 20210427) || \ - (defined(_LIBCPP_VERSION) && _LIBCPP_VERSION >= 170000) + (defined(_LIBCPP_VERSION) && _LIBCPP_VERSION >= 200000) auto [p, ec] = std::from_chars(s.data(), s.data() + s.size(), val); if (ec != std::errc{} || p != s.data() + s.size()) { diff --git a/lib/fuser.h b/lib/fuser.h index 55bda410f..043b074b5 100644 --- a/lib/fuser.h +++ b/lib/fuser.h @@ -119,7 +119,7 @@ class Fuser { } } else { const auto& gate0 = *OpGetAlternative(*parent(gate_seq0[0])); - FusedGate fgate{gate0.kind, gate0.time, {}, &gate0, {&gate0}, {}}; + FusedGate fgate{{gate0.kind, gate0.time, {}}, &gate0, {&gate0}, {}}; for (std::size_t i = 1; i < gate_seq0.size(); ++i) { fgate.gates.push_back(OpGetAlternative(*parent(gate_seq0[i]))); diff --git a/lib/fuser_basic.h b/lib/fuser_basic.h index 6c1113da8..a10d8a8e9 100644 --- a/lib/fuser_basic.h +++ b/lib/fuser_basic.h @@ -278,7 +278,7 @@ class BasicGateFuser final : public Fuser { deferred_measurement = pop; } else if (const auto* pg = OpGetAlternative(*pop)) { unsigned q0 = qubits[0]; - FusedGate fgate{pg->kind, pg->time, {q0}, pg, {}, {}}; + FusedGate fgate{{pg->kind, pg->time, {q0}}, pg, {}, {}}; last[q0] = Advance(last[q0], ops_lat[q0], fgate.gates); @@ -320,7 +320,7 @@ class BasicGateFuser final : public Fuser { if (Done(last[q0], gate.time, ops_lat[q0])) continue; - FusedGate fgate = {gate.kind, gate.time, {q0, q1}, &gate, {}, {}}; + FusedGate fgate = {{gate.kind, gate.time, {q0, q1}}, &gate, {}, {}}; do { last[q0] = Advance(last[q0], ops_lat[q0], fgate.gates); @@ -438,7 +438,7 @@ class BasicGateFuser final : public Fuser { const auto& gate1 = *OpGetAlternative(*ops_lat[q][k]); - FusedGate fgate{gate1.kind, gate1.time, {q}, &gate1, {}, {}}; + FusedGate fgate{{gate1.kind, gate1.time, {q}}, &gate1, {}, {}}; fgate.gates.push_back(&gate1); k = Advance(k + 1, ops_lat[q], fgate.gates); diff --git a/lib/fuser_mqubit.h b/lib/fuser_mqubit.h index e19dc4332..bdc2165d0 100644 --- a/lib/fuser_mqubit.h +++ b/lib/fuser_mqubit.h @@ -495,8 +495,8 @@ class MultiQubitGateFuser final : public Fuser { const auto& bop = OpBaseOperation(Base::OperationToConstRef(*fgate.parent)); - fused_ops.push_back(FusedGate{bop.kind, bop.time, - std::move(fgate.qubits), parent, + fused_ops.push_back(FusedGate{{bop.kind, bop.time, + std::move(fgate.qubits)}, parent, std::move(fgate.gates), {}, fgate.defer_matrix_computation}); @@ -611,8 +611,8 @@ class MultiQubitGateFuser final : public Fuser { auto parent = FusedGate::OpToPGate(fgate.parent); const auto& bop = OpBaseOperation(Base::OperationToConstRef(*fgate.parent)); - fused_ops.push_back(FusedGate{bop.kind, bop.time, - std::move(fgate.qubits), parent, + fused_ops.push_back(FusedGate{{bop.kind, bop.time, + std::move(fgate.qubits)}, parent, std::move(fgate.gates), {}, fgate.defer_matrix_computation}); @@ -643,8 +643,8 @@ class MultiQubitGateFuser final : public Fuser { auto parent = FusedGate::OpToPGate(fgate->parent); const auto& bop = OpBaseOperation(Base::OperationToConstRef(*fgate->parent)); - fused_ops.push_back(FusedGate{bop.kind, bop.time, - std::move(fgate->qubits), parent, + fused_ops.push_back(FusedGate{{bop.kind, bop.time, + std::move(fgate->qubits)}, parent, std::move(fgate->gates), {}, fgate->defer_matrix_computation}); @@ -712,8 +712,8 @@ class MultiQubitGateFuser final : public Fuser { auto parent = FusedGate::OpToPGate(ogate1->parent); const auto& bop = OpBaseOperation(Base::OperationToConstRef(*ogate1->parent)); - fused_ops.push_back(FusedGate{bop.kind, bop.time, - std::move(ogate1->qubits), parent, + fused_ops.push_back(FusedGate{{bop.kind, bop.time, + std::move(ogate1->qubits)}, parent, std::move(ogate1->gates), {}, ogate1->defer_matrix_computation}); @@ -739,7 +739,7 @@ class MultiQubitGateFuser final : public Fuser { } else { if (const auto* pg = OpGetAlternative(*fgate.parent)) { fused_ops.push_back( - FusedGate{pg->kind, pg->time, {pg->qubits[0]}, pg, {pg}, {}}); + FusedGate{{pg->kind, pg->time, {pg->qubits[0]}}, pg, {pg}, {}}); } else { fused_ops.push_back(fgate.parent); } diff --git a/lib/gate.h b/lib/gate.h index c09b883f3..c7e4957b4 100644 --- a/lib/gate.h +++ b/lib/gate.h @@ -328,7 +328,7 @@ template > inline Gate CreateGate(unsigned time, Q&& qubits, M&& matrix = {}, std::vector&& params = {}) { - Gate gate = {GateDef::kind, time, std::forward(qubits), + Gate gate = {{GateDef::kind, time, std::forward(qubits)}, std::move(params), std::forward(matrix), false}; switch (gate.qubits.size()) { @@ -358,8 +358,8 @@ enum OtherGateKind { template inline Measurement CreateMeasurement(unsigned time, Q&& qubits, std::string_view id = "") { - return - Measurement{kMeasurement, time, std::forward(qubits), std::string{id}}; + return Measurement{{kMeasurement, time, std::forward(qubits)}, + std::string{id}}; } template From 32a5b30df165244efbf5661f0a62cc528a269632 Mon Sep 17 00:00:00 2001 From: Sergei Isakov <54642992+sergeisakov@users.noreply.github.com> Date: Tue, 28 Jul 2026 16:39:18 +0200 Subject: [PATCH 10/13] Silence warnings. --- lib/classical_control_parser.h | 1 - lib/classical_control_symbol.h | 4 +-- lib/hybrid.h | 40 +++++++++++++------------- tests/fuser_basic_test.cc | 2 -- tests/fuser_mqubit_test.cc | 4 +-- tests/mps_simulator_test.cc | 6 ++-- tests/operation_test.cc | 16 +++++------ tests/run_custatevecex_test.cu | 2 -- tests/run_qsimh_test.cc | 2 -- tests/simulator_testfixture.h | 10 +++---- tests/statespace_testfixture.h | 2 -- tests/unitary_calculator_testfixture.h | 2 +- 12 files changed, 41 insertions(+), 50 deletions(-) diff --git a/lib/classical_control_parser.h b/lib/classical_control_parser.h index d7156127a..07b2986d0 100644 --- a/lib/classical_control_parser.h +++ b/lib/classical_control_parser.h @@ -206,7 +206,6 @@ struct ExprParser { case "~"_ophash: { Token n = tok.Peek(); - unsigned lc = n.lc; Expr l = GetLeft(symtab, tok, f); diff --git a/lib/classical_control_symbol.h b/lib/classical_control_symbol.h index 6fd96cdae..e668627b2 100644 --- a/lib/classical_control_symbol.h +++ b/lib/classical_control_symbol.h @@ -306,7 +306,7 @@ struct Symbol { */ template void Assign(const SymTable& symtab, const std::vector& es) { - auto f = [this, &symtab, &es](auto&& val) { + auto f = [&symtab, &es](auto&& val) { using V = std::decay_t; if constexpr (std::is_same_v) { @@ -354,7 +354,7 @@ struct Symbol { void Assign(const SymTable& symtab, const Expr& e, const Index& index) { std::size_t i = EvalIndex(symtab, index); - auto g = [this, &symtab, &e, i](auto&& val) { + auto g = [&symtab, &e, i](auto&& val) { using V = std::decay_t; if constexpr (std::is_same_v) { diff --git a/lib/hybrid.h b/lib/hybrid.h index 6e0d77a80..1a47ad00c 100644 --- a/lib/hybrid.h +++ b/lib/hybrid.h @@ -174,45 +174,45 @@ struct HybridSimulator final { case 1: // Single qubit gates. switch (parts[gate.qubits[0]]) { case 0: - hd.ops0.push_back(Gate{gate.kind, gate.time, - {hd.qubit_map[gate.qubits[0]]}, gate.params, gate.matrix}); + hd.ops0.push_back(Gate{{gate.kind, gate.time, + {hd.qubit_map[gate.qubits[0]]}}, gate.params, gate.matrix}); break; case 1: - hd.ops1.push_back(Gate{gate.kind, gate.time, - {hd.qubit_map[gate.qubits[0]]}, gate.params, gate.matrix}); + hd.ops1.push_back(Gate{{gate.kind, gate.time, + {hd.qubit_map[gate.qubits[0]]}}, gate.params, gate.matrix}); break; } break; case 2: // Two qubit gates. switch ((parts[gate.qubits[1]] << 1) | parts[gate.qubits[0]]) { case 0: // Both qubits in part 0. - hd.ops0.push_back(Gate{gate.kind, gate.time, - {hd.qubit_map[gate.qubits[0]], hd.qubit_map[gate.qubits[1]]}, + hd.ops0.push_back(Gate{{gate.kind, gate.time, + {hd.qubit_map[gate.qubits[0]], hd.qubit_map[gate.qubits[1]]}}, gate.params, gate.matrix}); break; case 1: // Gate on the cut, qubit 0 in part 1, qubit 1 in part 0. - hd.ops0.push_back(DecomposedGate{gate.kind, gate.time, - {hd.qubit_map[gate.qubits[1]]}, gate.params, {}, - gate.swapped, &gate, hd.num_gatexs}); - hd.ops1.push_back(DecomposedGate{gate.kind, gate.time, - {hd.qubit_map[gate.qubits[0]]}, gate.params, {}, - gate.swapped, &gate, hd.num_gatexs}); + hd.ops0.push_back(DecomposedGate{{{gate.kind, gate.time, + {hd.qubit_map[gate.qubits[1]]}}, gate.params, {}, + gate.swapped}, &gate, hd.num_gatexs}); + hd.ops1.push_back(DecomposedGate{{{gate.kind, gate.time, + {hd.qubit_map[gate.qubits[0]]}}, gate.params, {}, + gate.swapped}, &gate, hd.num_gatexs}); ++hd.num_gatexs; break; case 2: // Gate on the cut, qubit 0 in part 0, qubit 1 in part 1. - hd.ops0.push_back(DecomposedGate{gate.kind, gate.time, - {hd.qubit_map[gate.qubits[0]]}, gate.params, {}, - gate.swapped, &gate, hd.num_gatexs}); - hd.ops1.push_back(DecomposedGate{gate.kind, gate.time, - {hd.qubit_map[gate.qubits[1]]}, gate.params, {}, - gate.swapped, &gate, hd.num_gatexs}); + hd.ops0.push_back(DecomposedGate{{{gate.kind, gate.time, + {hd.qubit_map[gate.qubits[0]]}}, gate.params, {}, + gate.swapped}, &gate, hd.num_gatexs}); + hd.ops1.push_back(DecomposedGate{{{gate.kind, gate.time, + {hd.qubit_map[gate.qubits[1]]}}, gate.params, {}, + gate.swapped}, &gate, hd.num_gatexs}); ++hd.num_gatexs; break; case 3: // Both qubits in part 1. - hd.ops1.push_back(Gate{gate.kind, gate.time, - {hd.qubit_map[gate.qubits[0]], hd.qubit_map[gate.qubits[1]]}, + hd.ops1.push_back(Gate{{gate.kind, gate.time, + {hd.qubit_map[gate.qubits[0]], hd.qubit_map[gate.qubits[1]]}}, gate.params, gate.matrix}); break; } diff --git a/tests/fuser_basic_test.cc b/tests/fuser_basic_test.cc index 980aff724..8df874833 100644 --- a/tests/fuser_basic_test.cc +++ b/tests/fuser_basic_test.cc @@ -32,8 +32,6 @@ struct IO { static void messagef(const char* format, ...) {} }; -constexpr char provider[] = "fuser_basic_test"; - constexpr char circuit_string1[] = R"(4 0 h 0 diff --git a/tests/fuser_mqubit_test.cc b/tests/fuser_mqubit_test.cc index c5060e75c..2b1548c5e 100644 --- a/tests/fuser_mqubit_test.cc +++ b/tests/fuser_mqubit_test.cc @@ -42,13 +42,13 @@ struct IO { namespace { Gate CreateGate(unsigned time, std::vector&& qubits) { - return {0, time, std::move(qubits), {}, {}, false}; + return {{0, time, std::move(qubits)}, {}, {}, false}; } ControlledGate CreateControlledGate( unsigned time, std::vector&& qubits, std::vector&& controlled_by) { - return Gate{0, time, std::move(qubits), {}, {}, false}.ControlledBy( + return Gate{{0, time, std::move(qubits)}, {}, {}, false}.ControlledBy( std::move(controlled_by)); } diff --git a/tests/mps_simulator_test.cc b/tests/mps_simulator_test.cc index 01f8d9fab..4bdd4d25b 100644 --- a/tests/mps_simulator_test.cc +++ b/tests/mps_simulator_test.cc @@ -825,7 +825,7 @@ TEST(MPSSimulator, ApplyFusedGateLeft) { auto gate2 = Cirq::H::Create(0, 0); auto gate3 = Cirq::H::Create(0, 1); - FusedGate fgate1{Cirq::kCZ, 2, {0, 1}, &gate1, {&gate2, &gate3}, {}}; + FusedGate fgate1{{Cirq::kCZ, 2, {0, 1}}, &gate1, {&gate2, &gate3}, {}}; CalculateFusedMatrix(fgate1); auto mps = ss.Create(3, 4); ss.SetStateZero(mps); @@ -859,7 +859,7 @@ TEST(MPSSimulator, ApplyFusedGateRight) { auto gate2 = Cirq::H::Create(0, 1); auto gate3 = Cirq::H::Create(0, 2); - FusedGate fgate1{Cirq::kCZ, 2, {1, 2}, &gate1, {&gate2, &gate3}, {}}; + FusedGate fgate1{{Cirq::kCZ, 2, {1, 2}}, &gate1, {&gate2, &gate3}, {}}; CalculateFusedMatrix(fgate1); auto mps = ss.Create(3, 4); ss.SetStateZero(mps); @@ -893,7 +893,7 @@ TEST(MPSSimulator, ApplyFusedGateMiddle) { auto gate2 = Cirq::H::Create(0, 1); auto gate3 = Cirq::H::Create(0, 2); - FusedGate fgate1{Cirq::kCZ, 2, {1, 2}, &gate1, {&gate2, &gate3}, {}}; + FusedGate fgate1{{Cirq::kCZ, 2, {1, 2}}, &gate1, {&gate2, &gate3}, {}}; CalculateFusedMatrix(fgate1); auto mps = ss.Create(4, 4); ss.SetStateZero(mps); diff --git a/tests/operation_test.cc b/tests/operation_test.cc index b001fd7d0..e77305de5 100644 --- a/tests/operation_test.cc +++ b/tests/operation_test.cc @@ -28,8 +28,8 @@ TEST(OperationTest, Test1) { using Operation = qsim::Operation; { - Gate op1 = {0, 1, {2}}; - const Gate op2 = {0, 1, {2}}; + Gate op1 = {{0, 1, {2}}}; + const Gate op2 = {{0, 1, {2}}}; Gate* pg1 = OpGetAlternative(op1); ASSERT_NE(pg1, nullptr); @@ -85,8 +85,8 @@ TEST(OperationTest, Test1) { } { - Operation op1 = Gate{0, 1, {2}}; - const Operation op2 = Gate{0, 1, {2}}; + Operation op1 = Gate{{0, 1, {2}}}; + const Operation op2 = Gate{{0, 1, {2}}}; Gate* pg1 = OpGetAlternative(op1); ASSERT_NE(pg1, nullptr); @@ -142,8 +142,8 @@ TEST(OperationTest, Test1) { } { - Operation op1 = ControlledGate{Gate{0, 1, {2}}, {0, 1}}; - const Operation op2 = ControlledGate{Gate{0, 1, {2}}, {0, 1}}; + Operation op1 = ControlledGate{Gate{{0, 1, {2}}}, {0, 1}}; + const Operation op2 = ControlledGate{Gate{{0, 1, {2}}}, {0, 1}}; Operation* po1 = &op1; ControlledGate* pg1 = OpGetAlternative(po1); @@ -190,7 +190,7 @@ TEST(OperationTest, Test2) { using OperationF = std::variant; { - Operation op1 = Gate{0, 1, {2}}; + Operation op1 = Gate{{0, 1, {2}}}; const OperationF opf1 = &op1; const Gate* pg1 = OpGetAlternative(opf1); @@ -259,7 +259,7 @@ TEST(OperationTest, Test3) { using OperationF = std::variant; { - Operation op1 = Gate{0, 1, {2}}; + Operation op1 = Gate{{0, 1, {2}}}; OperationF opf1 = &op1; const Gate* pg1 = OpGetAlternative(opf1); diff --git a/tests/run_custatevecex_test.cu b/tests/run_custatevecex_test.cu index 446026a46..7ef5b9a6d 100644 --- a/tests/run_custatevecex_test.cu +++ b/tests/run_custatevecex_test.cu @@ -31,8 +31,6 @@ namespace qsim { -constexpr char provider[] = "run_custatevecex_test"; - constexpr char circuit_string[] = R"(4 0 h 0 diff --git a/tests/run_qsimh_test.cc b/tests/run_qsimh_test.cc index 249792e89..9177c74e2 100644 --- a/tests/run_qsimh_test.cc +++ b/tests/run_qsimh_test.cc @@ -31,8 +31,6 @@ namespace qsim { -constexpr char provider[] = "run_qsimh_test"; - constexpr char circuit_string[] = R"(4 0 h 0 diff --git a/tests/simulator_testfixture.h b/tests/simulator_testfixture.h index 1a2e182ab..dc9014ba8 100644 --- a/tests/simulator_testfixture.h +++ b/tests/simulator_testfixture.h @@ -226,7 +226,7 @@ void TestApplyGate5(const Factory& factory) { auto gate25 = GateRX::Create(11, 4, 0.3); auto gate26 = GateGPh::Create(12, -1, 0); - FusedGate fgate1{kGateCZ, 2, {0, 1}, &gate11, + FusedGate fgate1{{kGateCZ, 2, {0, 1}}, &gate11, {&gate1, &gate2, &gate6, &gate7, &gate11, &gate12, &gate13}, {}}; CalculateFusedMatrix(fgate1); ApplyGate(simulator, fgate1, state); @@ -248,7 +248,7 @@ void TestApplyGate5(const Factory& factory) { EXPECT_NEAR(std::imag(ampl3), 0.5, 1e-6); } - FusedGate fgate2{kGateIS, 4, {1, 2}, &gate14, + FusedGate fgate2{{kGateIS, 4, {1, 2}}, &gate14, {&gate3, &gate8, &gate14, &gate15, &gate16}, {}}; CalculateFusedMatrix(fgate2); ApplyGate(simulator, fgate2, state); @@ -270,7 +270,7 @@ void TestApplyGate5(const Factory& factory) { EXPECT_NEAR(std::imag(ampl3), 0, 1e-6); } - FusedGate fgate3{kGateCNot, 6, {2, 3}, &gate17, + FusedGate fgate3{{kGateCNot, 6, {2, 3}}, &gate17, {&gate4, &gate9, &gate17, &gate18, &gate19},{}}; CalculateFusedMatrix(fgate3); ApplyGate(simulator, fgate3, state); @@ -292,7 +292,7 @@ void TestApplyGate5(const Factory& factory) { EXPECT_NEAR(std::imag(ampl3), 0.00031570, 1e-6); } - FusedGate fgate4{kGateFS, 8, {3, 4}, &gate20, + FusedGate fgate4{{kGateFS, 8, {3, 4}}, &gate20, {&gate5, &gate10, &gate20, &gate21, &gate22}, {}}; CalculateFusedMatrix(fgate4); ApplyGate(simulator, fgate4, state); @@ -314,7 +314,7 @@ void TestApplyGate5(const Factory& factory) { EXPECT_NEAR(std::imag(ampl3), -0.00987822, 1e-6); } - FusedGate fgate5{kGateCP, 10, {0, 1}, &gate23, + FusedGate fgate5{{kGateCP, 10, {0, 1}}, &gate23, {&gate23, &gate26}, {}}; CalculateFusedMatrix(fgate5); ApplyGate(simulator, fgate5, state); diff --git a/tests/statespace_testfixture.h b/tests/statespace_testfixture.h index ac7328506..076104bad 100644 --- a/tests/statespace_testfixture.h +++ b/tests/statespace_testfixture.h @@ -34,8 +34,6 @@ namespace qsim { -constexpr char provider[] = "statespace_test"; - constexpr char circuit_string[] = R"(20 0 h 0 diff --git a/tests/unitary_calculator_testfixture.h b/tests/unitary_calculator_testfixture.h index d9806fd9d..823e9f0ef 100644 --- a/tests/unitary_calculator_testfixture.h +++ b/tests/unitary_calculator_testfixture.h @@ -427,7 +427,7 @@ void TestApplyFusedGate() { std::vector gates = {Cirq::H::Create(0, 0), Cirq::H::Create(1, 0)}; - FusedGate fgate {Cirq::kH, 0, {0}, &gates[0], + FusedGate fgate {{Cirq::kH, 0, {0}}, &gates[0], {&gates[0], &gates[1]}, {}}; CalculateFusedMatrix(fgate); From c3cb45fdc257db2c5dd77ce6e9825671da87439b Mon Sep 17 00:00:00 2001 From: Sergei Isakov <54642992+sergeisakov@users.noreply.github.com> Date: Tue, 28 Jul 2026 16:39:38 +0200 Subject: [PATCH 11/13] Fix test. --- tests/classical_control_util_test.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/classical_control_util_test.cc b/tests/classical_control_util_test.cc index 84f7c667b..b3d67a0f8 100644 --- a/tests/classical_control_util_test.cc +++ b/tests/classical_control_util_test.cc @@ -48,7 +48,8 @@ TEST(UtilsTest, ToFloatStandardAndExponent) { TEST(UtilsTest, ToFloatInvalidThrows) { EXPECT_THROW(ToFloat("not_a_float"), std::runtime_error); - EXPECT_THROW(ToFloat("3.14foo"), std::runtime_error); + EXPECT_THROW(ToFloat("3.14e-1000"), std::runtime_error); + EXPECT_THROW(ToFloat("3.14e1000"), std::runtime_error); } TEST(UtilsTest, Fnv1aHashAndLiteralOperator) { From 71f0243d398e166e6fff8e62bb4b7f50e6221038 Mon Sep 17 00:00:00 2001 From: Sergei Isakov <54642992+sergeisakov@users.noreply.github.com> Date: Tue, 28 Jul 2026 17:04:07 +0200 Subject: [PATCH 12/13] Silence warnings. --- lib/classical_control_parser.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/classical_control_parser.h b/lib/classical_control_parser.h index 07b2986d0..4048805d7 100644 --- a/lib/classical_control_parser.h +++ b/lib/classical_control_parser.h @@ -205,8 +205,6 @@ struct ExprParser { } case "~"_ophash: { - Token n = tok.Peek(); - Expr l = GetLeft(symtab, tok, f); if (!IsConvertibleToInt(symtab, l)) { From 00d637e641c7001998d0f4c1bca3c1559736ec21 Mon Sep 17 00:00:00 2001 From: Sergei Isakov <54642992+sergeisakov@users.noreply.github.com> Date: Tue, 28 Jul 2026 17:05:07 +0200 Subject: [PATCH 13/13] Make naming consistent. --- apps/qsim_amplitudes.cc | 2 +- apps/qsim_base.cc | 2 +- apps/qsim_base_cuda.cu | 2 +- apps/qsim_base_custatevec.cu | 2 +- apps/qsim_base_custatevecex.cu | 2 +- apps/qsim_qtrajectory_cuda.cu | 2 +- apps/qsim_von_neumann.cc | 2 +- apps/qsimh_amplitudes.cc | 2 +- apps/qsimh_base.cc | 2 +- lib/circuit_qsim_parser.h | 4 +- qsim/qsim.cc | 2 +- tests/circuit_qsim_parser_test.cc | 78 +++++++++++++++---------------- tests/fuser_basic_test.cc | 16 +++---- tests/gates_qsim_test.cc | 14 +++--- tests/hybrid_testfixture.h | 4 +- tests/run_custatevecex_test.cu | 6 +-- tests/run_qsim_deprecated_test.cc | 6 +-- tests/run_qsim_test.cc | 10 ++-- tests/run_qsimh_test.cc | 2 +- tests/statespace_testfixture.h | 6 +-- 20 files changed, 83 insertions(+), 83 deletions(-) diff --git a/apps/qsim_amplitudes.cc b/apps/qsim_amplitudes.cc index e5b0c1cb6..0a636a965 100644 --- a/apps/qsim_amplitudes.cc +++ b/apps/qsim_amplitudes.cc @@ -162,7 +162,7 @@ int main(int argc, char* argv[]) { } auto cstr = cc::ReadFile(opt.circuit_file); - auto circuit = CircuitQsimParser::Run(cstr, opt.times.back()); + auto circuit = CircuitQSimParser::Run(cstr, opt.times.back()); if (opt.denormals_are_zeros) { SetFlushToZeroAndDenormalsAreZeros(); diff --git a/apps/qsim_base.cc b/apps/qsim_base.cc index 2f5f7e88f..0e17724eb 100644 --- a/apps/qsim_base.cc +++ b/apps/qsim_base.cc @@ -116,7 +116,7 @@ int main(int argc, char* argv[]) { } auto cstr = cc::ReadFile(opt.circuit_file); - auto circuit = CircuitQsimParser::Run(cstr, opt.maxtime); + auto circuit = CircuitQSimParser::Run(cstr, opt.maxtime); if (opt.denormals_are_zeros) { SetFlushToZeroAndDenormalsAreZeros(); diff --git a/apps/qsim_base_cuda.cu b/apps/qsim_base_cuda.cu index 3f262b8ea..1037018dd 100644 --- a/apps/qsim_base_cuda.cu +++ b/apps/qsim_base_cuda.cu @@ -116,7 +116,7 @@ int main(int argc, char* argv[]) { using fp_type = float; auto cstr = cc::ReadFile(opt.circuit_file); - auto circuit = CircuitQsimParser::Run(cstr, opt.maxtime); + auto circuit = CircuitQSimParser::Run(cstr, opt.maxtime); struct Factory { using Simulator = qsim::SimulatorCUDA; diff --git a/apps/qsim_base_custatevec.cu b/apps/qsim_base_custatevec.cu index ed333b9f7..a42d90de4 100644 --- a/apps/qsim_base_custatevec.cu +++ b/apps/qsim_base_custatevec.cu @@ -110,7 +110,7 @@ int main(int argc, char* argv[]) { using fp_type = float; auto cstr = cc::ReadFile(opt.circuit_file); - auto circuit = CircuitQsimParser::Run(cstr, opt.maxtime); + auto circuit = CircuitQSimParser::Run(cstr, opt.maxtime); struct Factory { using Simulator = qsim::SimulatorCuStateVec; diff --git a/apps/qsim_base_custatevecex.cu b/apps/qsim_base_custatevecex.cu index 853005274..59dafc0fb 100644 --- a/apps/qsim_base_custatevecex.cu +++ b/apps/qsim_base_custatevecex.cu @@ -108,7 +108,7 @@ int main(int argc, char* argv[]) { using fp_type = float; auto cstr = cc::ReadFile(opt.circuit_file); - auto circuit = CircuitQsimParser::Run(cstr, opt.maxtime); + auto circuit = CircuitQSimParser::Run(cstr, opt.maxtime); struct Factory { using Simulator = qsim::SimulatorCuStateVecEx; diff --git a/apps/qsim_qtrajectory_cuda.cu b/apps/qsim_qtrajectory_cuda.cu index ae1db84c6..18b615cee 100644 --- a/apps/qsim_qtrajectory_cuda.cu +++ b/apps/qsim_qtrajectory_cuda.cu @@ -225,7 +225,7 @@ int main(int argc, char* argv[]) { } auto cstr = cc::ReadFile(opt.circuit_file); - auto circuit = CircuitQsimParser::Run(cstr, opt.times.back()); + auto circuit = CircuitQSimParser::Run(cstr, opt.times.back()); if (opt.times.size() == 1 && opt.times[0] == std::numeric_limits::max()) { diff --git a/apps/qsim_von_neumann.cc b/apps/qsim_von_neumann.cc index 7d5813903..3a8d0ed80 100644 --- a/apps/qsim_von_neumann.cc +++ b/apps/qsim_von_neumann.cc @@ -101,7 +101,7 @@ int main(int argc, char* argv[]) { } auto cstr = cc::ReadFile(opt.circuit_file); - auto circuit = CircuitQsimParser::Run(cstr, opt.maxtime); + auto circuit = CircuitQSimParser::Run(cstr, opt.maxtime); if (opt.denormals_are_zeros) { SetFlushToZeroAndDenormalsAreZeros(); diff --git a/apps/qsimh_amplitudes.cc b/apps/qsimh_amplitudes.cc index 2aa667a26..a2ed88cf3 100644 --- a/apps/qsimh_amplitudes.cc +++ b/apps/qsimh_amplitudes.cc @@ -176,7 +176,7 @@ int main(int argc, char* argv[]) { } auto cstr = cc::ReadFile(opt.circuit_file); - auto circuit = CircuitQsimParser::Run(cstr, opt.maxtime); + auto circuit = CircuitQSimParser::Run(cstr, opt.maxtime); if (!ValidatePart1(circuit.num_qubits, opt.part1)) { return 1; diff --git a/apps/qsimh_base.cc b/apps/qsimh_base.cc index b41ce43a1..ab2394cd6 100644 --- a/apps/qsimh_base.cc +++ b/apps/qsimh_base.cc @@ -138,7 +138,7 @@ int main(int argc, char* argv[]) { } auto cstr = cc::ReadFile(opt.circuit_file); - auto circuit = CircuitQsimParser::Run(cstr, opt.maxtime); + auto circuit = CircuitQSimParser::Run(cstr, opt.maxtime); if (!ValidatePart1(circuit.num_qubits, opt.part1)) { return 1; diff --git a/lib/circuit_qsim_parser.h b/lib/circuit_qsim_parser.h index a8d702974..6cca4c401 100644 --- a/lib/circuit_qsim_parser.h +++ b/lib/circuit_qsim_parser.h @@ -119,7 +119,7 @@ inline void ParseSymbols(std::string_view sym_defs, SymTable& symtab) { * @tparam RuntimeError Error policy for runtime errors. */ template -struct CircuitQsimParser final { +struct CircuitQSimParser final { private: using fp_type = FP; using Operation = qsim::Operation; @@ -1587,7 +1587,7 @@ struct CircuitQsimParser final { template -using CircuitQsimParser = cc::CircuitQsimParser; +using CircuitQSimParser = cc::CircuitQSimParser; } // namespace qsim diff --git a/qsim/qsim.cc b/qsim/qsim.cc index fc22064ee..0bec91a37 100644 --- a/qsim/qsim.cc +++ b/qsim/qsim.cc @@ -209,7 +209,7 @@ int main(int argc, char* argv[]) { auto cstr = ReadFile(opt.circuit_file); unsigned maxtime = std::numeric_limits::max(); auto [circuit, obss] = - qsim::CircuitQsimParser::Run(cstr, maxtime, symtab); + qsim::CircuitQSimParser::Run(cstr, maxtime, symtab); struct Factory { Factory(unsigned num_threads) : num_threads(num_threads) {} diff --git a/tests/circuit_qsim_parser_test.cc b/tests/circuit_qsim_parser_test.cc index 74ade4531..27332da8c 100644 --- a/tests/circuit_qsim_parser_test.cc +++ b/tests/circuit_qsim_parser_test.cc @@ -41,9 +41,9 @@ struct TestRuntimeError { }; using TestQSimParser = - qsim::CircuitQsimParser; + qsim::CircuitQSimParser; -class CircuitQsimParserTest : public ::testing::Test { +class CircuitQSimParserTest : public ::testing::Test { protected: qsim::cc::SymTable symtab; @@ -55,7 +55,7 @@ class CircuitQsimParserTest : public ::testing::Test { namespace qsim { -TEST_F(CircuitQsimParserTest, ValidCircuit) { +TEST_F(CircuitQSimParserTest, ValidCircuit) { constexpr char valid_circuit[] = R"(2 0 id1 0 @@ -93,7 +93,7 @@ R"(2 EXPECT_EQ(circuit2.ops.size(), 10); } -TEST_F(CircuitQsimParserTest, ValidCircuitWithControlledGates) { +TEST_F(CircuitQSimParserTest, ValidCircuitWithControlledGates) { constexpr char valid_circuit[] = R"(5 0 c 0 1 h 2 @@ -120,7 +120,7 @@ R"(5 EXPECT_EQ(pg2->controlled_by.size(), 2); } -TEST_F(CircuitQsimParserTest, ValidTimeOrder) { +TEST_F(CircuitQSimParserTest, ValidTimeOrder) { constexpr char valid_circuit[] = R"(4 0 cz 0 3 @@ -144,7 +144,7 @@ R"(4 EXPECT_EQ(circuit.ops.size(), 14); } -TEST_F(CircuitQsimParserTest, InvalidGateName) { +TEST_F(CircuitQSimParserTest, InvalidGateName) { constexpr char invalid_circuit[] = R"(2 0 h 0 @@ -154,7 +154,7 @@ R"(2 EXPECT_THROW(TestQSimParser::Run(invalid_circuit, 99), std::invalid_argument); } -TEST_F(CircuitQsimParserTest, TrailingCharacters) { +TEST_F(CircuitQSimParserTest, TrailingCharacters) { constexpr char invalid_circuit[] = R"(2 0 h 0 @@ -164,7 +164,7 @@ R"(2 EXPECT_THROW(TestQSimParser::Run(invalid_circuit, 99), std::invalid_argument); } -TEST_F(CircuitQsimParserTest, InvalidQubitRange1) { +TEST_F(CircuitQSimParserTest, InvalidQubitRange1) { constexpr char invalid_circuit[] = R"(2 0 h 0 @@ -174,7 +174,7 @@ R"(2 EXPECT_THROW(TestQSimParser::Run(invalid_circuit, 99), std::invalid_argument); } -TEST_F(CircuitQsimParserTest, QubitIsNotNumber1) { +TEST_F(CircuitQSimParserTest, QubitIsNotNumber1) { constexpr char invalid_circuit[] = R"(2 0 h 0 @@ -184,7 +184,7 @@ R"(2 EXPECT_THROW(TestQSimParser::Run(invalid_circuit, 99), std::invalid_argument); } -TEST_F(CircuitQsimParserTest, SameQubits1) { +TEST_F(CircuitQSimParserTest, SameQubits1) { constexpr char invalid_circuit[] = R"(2 0 h 0 @@ -194,7 +194,7 @@ R"(2 EXPECT_THROW(TestQSimParser::Run(invalid_circuit, 99), std::invalid_argument); } -TEST_F(CircuitQsimParserTest, InvalidSingleQubitGate) { +TEST_F(CircuitQSimParserTest, InvalidSingleQubitGate) { constexpr char invalid_circuit[] = R"(2 0 h 0 @@ -204,7 +204,7 @@ R"(2 EXPECT_THROW(TestQSimParser::Run(invalid_circuit, 99), std::invalid_argument); } -TEST_F(CircuitQsimParserTest, InvalidTwoQubitGate) { +TEST_F(CircuitQSimParserTest, InvalidTwoQubitGate) { constexpr char invalid_circuit[] = R"(2 0 h 0 @@ -214,7 +214,7 @@ R"(2 EXPECT_THROW(TestQSimParser::Run(invalid_circuit, 99), std::invalid_argument); } -TEST_F(CircuitQsimParserTest, InvalidRxGate) { +TEST_F(CircuitQSimParserTest, InvalidRxGate) { constexpr char invalid_circuit[] = R"(2 0 h 0 @@ -225,7 +225,7 @@ R"(2 EXPECT_THROW(TestQSimParser::Run(invalid_circuit, 99), std::invalid_argument); } -TEST_F(CircuitQsimParserTest, InvalidRyGate) { +TEST_F(CircuitQSimParserTest, InvalidRyGate) { constexpr char invalid_circuit[] = R"(2 0 h 0 @@ -236,7 +236,7 @@ R"(2 EXPECT_THROW(TestQSimParser::Run(invalid_circuit, 99), std::invalid_argument); } -TEST_F(CircuitQsimParserTest, InvalidRzGate) { +TEST_F(CircuitQSimParserTest, InvalidRzGate) { constexpr char invalid_circuit[] = R"(2 0 h 0 @@ -247,7 +247,7 @@ R"(2 EXPECT_THROW(TestQSimParser::Run(invalid_circuit, 99), std::invalid_argument); } -TEST_F(CircuitQsimParserTest, InvalidRxyGate) { +TEST_F(CircuitQSimParserTest, InvalidRxyGate) { constexpr char invalid_circuit[] = R"(2 0 h 0 @@ -258,7 +258,7 @@ R"(2 EXPECT_THROW(TestQSimParser::Run(invalid_circuit, 99), std::invalid_argument); } -TEST_F(CircuitQsimParserTest, InvalidFsimGate) { +TEST_F(CircuitQSimParserTest, InvalidFsimGate) { constexpr char invalid_circuit[] = R"(2 0 h 0 @@ -268,7 +268,7 @@ R"(2 EXPECT_THROW(TestQSimParser::Run(invalid_circuit, 99), std::invalid_argument); } -TEST_F(CircuitQsimParserTest, InvalidCpGate) { +TEST_F(CircuitQSimParserTest, InvalidCpGate) { constexpr char invalid_circuit[] = R"(2 0 h 0 @@ -278,7 +278,7 @@ R"(2 EXPECT_THROW(TestQSimParser::Run(invalid_circuit, 99), std::invalid_argument); } -TEST_F(CircuitQsimParserTest, TimeOutOfOrder) { +TEST_F(CircuitQSimParserTest, TimeOutOfOrder) { constexpr char invalid_circuit[] = R"(2 0 h 0 @@ -290,7 +290,7 @@ R"(2 EXPECT_THROW(TestQSimParser::Run(invalid_circuit, 99), std::invalid_argument); } -TEST_F(CircuitQsimParserTest, InvalidQubitRange2) { +TEST_F(CircuitQSimParserTest, InvalidQubitRange2) { constexpr char invalid_circuit[] = R"(2 0 h 0 @@ -300,7 +300,7 @@ R"(2 EXPECT_THROW(TestQSimParser::Run(invalid_circuit, 99), std::invalid_argument); } -TEST_F(CircuitQsimParserTest, QubitIsNotNumber2) { +TEST_F(CircuitQSimParserTest, QubitIsNotNumber2) { constexpr char invalid_circuit[] = R"(2 0 h 0 @@ -310,7 +310,7 @@ R"(2 EXPECT_THROW(TestQSimParser::Run(invalid_circuit, 99), std::invalid_argument); } -TEST_F(CircuitQsimParserTest, SameQubits2) { +TEST_F(CircuitQSimParserTest, SameQubits2) { constexpr char invalid_circuit[] = R"(2 0 h 0 @@ -320,7 +320,7 @@ R"(2 EXPECT_THROW(TestQSimParser::Run(invalid_circuit, 99), std::invalid_argument); } -TEST_F(CircuitQsimParserTest, NoControlQubits) { +TEST_F(CircuitQSimParserTest, NoControlQubits) { constexpr char invalid_circuit[] = R"(4 0 c is 0 1 @@ -329,7 +329,7 @@ R"(4 EXPECT_THROW(TestQSimParser::Run(invalid_circuit, 99), std::invalid_argument); } -TEST_F(CircuitQsimParserTest, InvalidControlQubitRange) { +TEST_F(CircuitQSimParserTest, InvalidControlQubitRange) { constexpr char invalid_circuit[] = R"(4 0 c 1 5 is 2 3 @@ -338,7 +338,7 @@ R"(4 EXPECT_THROW(TestQSimParser::Run(invalid_circuit, 99), std::invalid_argument); } -TEST_F(CircuitQsimParserTest, ControlQubitIsNotNumber) { +TEST_F(CircuitQSimParserTest, ControlQubitIsNotNumber) { constexpr char invalid_circuit[] = R"(4 1 c 3 x is 0 1 @@ -347,7 +347,7 @@ R"(4 EXPECT_THROW(TestQSimParser::Run(invalid_circuit, 99), std::invalid_argument); } -TEST_F(CircuitQsimParserTest, SameControlQubits) { +TEST_F(CircuitQSimParserTest, SameControlQubits) { constexpr char invalid_circuit[] = R"(4 0 c 1 1 is 2 3 @@ -356,7 +356,7 @@ R"(4 EXPECT_THROW(TestQSimParser::Run(invalid_circuit, 99), std::invalid_argument); } -TEST_F(CircuitQsimParserTest, SameControlAndTargetQubits) { +TEST_F(CircuitQSimParserTest, SameControlAndTargetQubits) { constexpr char invalid_circuit[] = R"(4 0 c 1 2 is 0 1 @@ -365,7 +365,7 @@ R"(4 EXPECT_THROW(TestQSimParser::Run(invalid_circuit, 99), std::invalid_argument); } -TEST_F(CircuitQsimParserTest, OverlappingQubits1) { +TEST_F(CircuitQSimParserTest, OverlappingQubits1) { constexpr char invalid_circuit[] = R"(4 0 h 0 @@ -376,7 +376,7 @@ R"(4 EXPECT_THROW(TestQSimParser::Run(invalid_circuit, 99), std::invalid_argument); } -TEST_F(CircuitQsimParserTest, OverlappingQubits2) { +TEST_F(CircuitQSimParserTest, OverlappingQubits2) { constexpr char invalid_circuit[] = R"(4 0 h 0 @@ -387,7 +387,7 @@ R"(4 EXPECT_THROW(TestQSimParser::Run(invalid_circuit, 99), std::invalid_argument); } -TEST_F(CircuitQsimParserTest, InvalidTimeOrder1) { +TEST_F(CircuitQSimParserTest, InvalidTimeOrder1) { constexpr char invalid_circuit[] = R"(4 0 cz 0 1 @@ -398,7 +398,7 @@ R"(4 EXPECT_THROW(TestQSimParser::Run(invalid_circuit, 99), std::invalid_argument); } -TEST_F(CircuitQsimParserTest, InvalidTimeOrder2) { +TEST_F(CircuitQSimParserTest, InvalidTimeOrder2) { constexpr char invalid_circuit[] = R"(4 0 cz 0 1 @@ -410,7 +410,7 @@ R"(4 EXPECT_THROW(TestQSimParser::Run(invalid_circuit, 99), std::invalid_argument); } -TEST_F(CircuitQsimParserTest, InvalidTimeOrder3) { +TEST_F(CircuitQSimParserTest, InvalidTimeOrder3) { constexpr char invalid_circuit[] = R"(4 0 cz 0 1 @@ -427,7 +427,7 @@ R"(4 namespace qsim::cc { namespace { -TEST_F(CircuitQsimParserTest, ParsesSimpleSymbolDefinitions) { +TEST_F(CircuitQSimParserTest, ParsesSimpleSymbolDefinitions) { std::string_view sym_defs = "nq = 5 c1 = 1 c2 = c1 * 2 f1 = 3.14 * c1"; ParseSymbols(sym_defs, symtab); @@ -450,7 +450,7 @@ TEST_F(CircuitQsimParserTest, ParsesSimpleSymbolDefinitions) { EXPECT_DOUBLE_EQ(f1->GetFloat(), 3.14); } -TEST_F(CircuitQsimParserTest, ParseSymbolsHandlesSemicolonsAndDelimiters) { +TEST_F(CircuitQSimParserTest, ParseSymbolsHandlesSemicolonsAndDelimiters) { std::string_view sym_defs = "a = 10; b = a + 5;\n c = b * 2"; ParseSymbols(sym_defs, symtab); @@ -460,7 +460,7 @@ TEST_F(CircuitQsimParserTest, ParseSymbolsHandlesSemicolonsAndDelimiters) { EXPECT_EQ(symtab.Lookup("c")->GetInt(), 30); } -TEST_F(CircuitQsimParserTest, ParseSymbolsThrowsOnInvalidSyntax) { +TEST_F(CircuitQSimParserTest, ParseSymbolsThrowsOnInvalidSyntax) { std::string_view bad_defs = "a 10"; // Missing '=' EXPECT_THROW( @@ -468,7 +468,7 @@ TEST_F(CircuitQsimParserTest, ParseSymbolsThrowsOnInvalidSyntax) { std::invalid_argument); } -TEST_F(CircuitQsimParserTest, ParsesConstantDeclarationsAndQubitCount) { +TEST_F(CircuitQSimParserTest, ParsesConstantDeclarationsAndQubitCount) { std::string circuit_str = R"( 2 # 2-qubit circuit const float phi = 3.14159 / 2.0 @@ -489,7 +489,7 @@ TEST_F(CircuitQsimParserTest, ParsesConstantDeclarationsAndQubitCount) { EXPECT_TRUE(phi->IsReadOnly()); } -TEST_F(CircuitQsimParserTest, ParsesClassicalControlAndHistograms) { +TEST_F(CircuitQSimParserTest, ParsesClassicalControlAndHistograms) { std::string circuit_str = R"( 2 h 0 @@ -510,7 +510,7 @@ TEST_F(CircuitQsimParserTest, ParsesClassicalControlAndHistograms) { EXPECT_EQ(hist->Size(), 2u); // 2 qubits measured in m1 tag } -TEST_F(CircuitQsimParserTest, ImplicitTimeTagAssignmentOrdering) { +TEST_F(CircuitQSimParserTest, ImplicitTimeTagAssignmentOrdering) { // Gates without time tags should automatically increment time step per qubit std::string circuit_str = R"( 2 @@ -536,7 +536,7 @@ TEST_F(CircuitQsimParserTest, ImplicitTimeTagAssignmentOrdering) { EXPECT_EQ(bop2.qubits.size(), 2); } -TEST_F(CircuitQsimParserTest, ExpressionQubitIndices) { +TEST_F(CircuitQSimParserTest, ExpressionQubitIndices) { std::string circuit_str = R"( 5 const int i = 0 diff --git a/tests/fuser_basic_test.cc b/tests/fuser_basic_test.cc index 8df874833..229d24bbf 100644 --- a/tests/fuser_basic_test.cc +++ b/tests/fuser_basic_test.cc @@ -67,7 +67,7 @@ TEST(FuserBasicTest, NoTimesToSplitAt) { using Gate = qsim::Gate; using FusedGate = qsim::FusedGate; - auto circuit = CircuitQsimParser::Run(circuit_string1, 99); + auto circuit = CircuitQSimParser::Run(circuit_string1, 99); EXPECT_EQ(circuit.num_qubits, 4); EXPECT_EQ(circuit.ops.size(), 27); @@ -296,7 +296,7 @@ TEST(FuserBasicTest, TimesToSplitAt1) { using Gate = qsim::Gate; using FusedGate = qsim::FusedGate; - auto circuit = CircuitQsimParser::Run(circuit_string1, 99); + auto circuit = CircuitQSimParser::Run(circuit_string1, 99); EXPECT_EQ(circuit.num_qubits, 4); EXPECT_EQ(circuit.ops.size(), 27); @@ -537,7 +537,7 @@ TEST(FuserBasicTest, TimesToSplitAt2) { using Gate = qsim::Gate; using FusedGate = qsim::FusedGate; - auto circuit = CircuitQsimParser::Run(circuit_string1, 99); + auto circuit = CircuitQSimParser::Run(circuit_string1, 99); EXPECT_EQ(circuit.num_qubits, 4); EXPECT_EQ(circuit.ops.size(), 27); @@ -782,7 +782,7 @@ TEST(FuserBasicTest, OrphanedQubits1) { using Gate = qsim::Gate; using FusedGate = qsim::FusedGate; - auto circuit = CircuitQsimParser::Run(circuit_string2, 2); + auto circuit = CircuitQSimParser::Run(circuit_string2, 2); EXPECT_EQ(circuit.num_qubits, 3); EXPECT_EQ(circuit.ops.size(), 7); @@ -857,7 +857,7 @@ TEST(FuserBasicTest, OrphanedQubits2) { using Gate = qsim::Gate; using FusedGate = qsim::FusedGate; - auto circuit = CircuitQsimParser::Run(circuit_string2, 99); + auto circuit = CircuitQSimParser::Run(circuit_string2, 99); EXPECT_EQ(circuit.num_qubits, 3); EXPECT_EQ(circuit.ops.size(), 9); @@ -969,7 +969,7 @@ TEST(FuserBasicTest, DecomposedQubitGate) { using OperationD = detail::append_to_variant_t; - auto circuit = CircuitQsimParser::Run(circuit_string2, 2); + auto circuit = CircuitQSimParser::Run(circuit_string2, 2); EXPECT_EQ(circuit.num_qubits, 3); EXPECT_EQ(circuit.ops.size(), 7); @@ -1083,7 +1083,7 @@ TEST(FuserBasicTest, MeasurementGate) { using Operation = qsim::Operation; using FusedGate = qsim::FusedGate; - auto circuit = CircuitQsimParser::Run(circuit_string3, 99); + auto circuit = CircuitQSimParser::Run(circuit_string3, 99); EXPECT_EQ(circuit.num_qubits, 4); EXPECT_EQ(circuit.ops.size(), 17); @@ -1292,7 +1292,7 @@ TEST(FuserBasicTest, ControlledGate) { using ControlledGate = qsim::ControlledGate; using FusedGate = qsim::FusedGate; - auto circuit = CircuitQsimParser::Run(circuit_string4, 99); + auto circuit = CircuitQSimParser::Run(circuit_string4, 99); EXPECT_EQ(circuit.num_qubits, 5); EXPECT_EQ(circuit.ops.size(), 13); diff --git a/tests/gates_qsim_test.cc b/tests/gates_qsim_test.cc index 4b2be2ea0..4294ed1b6 100644 --- a/tests/gates_qsim_test.cc +++ b/tests/gates_qsim_test.cc @@ -21,7 +21,7 @@ namespace qsim { -TEST(GatesQsimTest, GateRX) { +TEST(GatesQSimTest, GateRX) { float phi = 0.42; unsigned time = 7; unsigned qubit = 11; @@ -45,7 +45,7 @@ TEST(GatesQsimTest, GateRX) { EXPECT_FLOAT_EQ(gate.matrix[7], 0); } -TEST(GatesQsimTest, GateRY) { +TEST(GatesQSimTest, GateRY) { float phi = 0.42; unsigned time = 7; unsigned qubit = 11; @@ -69,7 +69,7 @@ TEST(GatesQsimTest, GateRY) { EXPECT_FLOAT_EQ(gate.matrix[7], 0); } -TEST(GatesQsimTest, GateRZ) { +TEST(GatesQSimTest, GateRZ) { float phi = 0.42; unsigned time = 7; unsigned qubit = 11; @@ -93,7 +93,7 @@ TEST(GatesQsimTest, GateRZ) { EXPECT_FLOAT_EQ(gate.matrix[7], -s); } -TEST(GatesQsimTest, GateRXY) { +TEST(GatesQSimTest, GateRXY) { float theta = 0.84; float phi = 0.42; unsigned time = 7; @@ -120,7 +120,7 @@ TEST(GatesQsimTest, GateRXY) { EXPECT_FLOAT_EQ(gate.matrix[7], 0); } -TEST(GatesQsimTest, GateFS) { +TEST(GatesQSimTest, GateFS) { float theta = 0.84; float phi = 0.42; unsigned time = 7; @@ -241,7 +241,7 @@ TEST(GatesQsimTest, GateFS) { EXPECT_NEAR(schmidt_decomp[3][1][7], 0.16863659, 1e-6); } -TEST(GatesQsimTest, GateCP) { +TEST(GatesQSimTest, GateCP) { float phi = 0.42; unsigned time = 7; unsigned qubit0 = 11; @@ -327,7 +327,7 @@ TEST(GatesQsimTest, GateCP) { EXPECT_FLOAT_EQ(schmidt_decomp[1][1][7], -sp); } -TEST(GatesQsimTest, GateMeasurement) { +TEST(GatesQSimTest, GateMeasurement) { unsigned time = 5; std::vector qubits = {3, 2, 4, 0, 7, 5, 1}; auto mea = CreateMeasurement(time, qubits); diff --git a/tests/hybrid_testfixture.h b/tests/hybrid_testfixture.h index 957aeb9cc..cf41940e2 100644 --- a/tests/hybrid_testfixture.h +++ b/tests/hybrid_testfixture.h @@ -62,7 +62,7 @@ R"(2 using fp_type = typename Factory::fp_type; - auto circuit = CircuitQsimParser::Run(circuit_string, 99); + auto circuit = CircuitQSimParser::Run(circuit_string, 99); EXPECT_EQ(circuit.num_qubits, 2); EXPECT_EQ(circuit.ops.size(), 23); @@ -238,7 +238,7 @@ R"(4 using fp_type = typename Factory::fp_type; - auto circuit = CircuitQsimParser::Run(circuit_string, 99); + auto circuit = CircuitQSimParser::Run(circuit_string, 99); EXPECT_EQ(circuit.num_qubits, 4); EXPECT_EQ(circuit.ops.size(), 63); diff --git a/tests/run_custatevecex_test.cu b/tests/run_custatevecex_test.cu index 7ef5b9a6d..14e66ed78 100644 --- a/tests/run_custatevecex_test.cu +++ b/tests/run_custatevecex_test.cu @@ -78,7 +78,7 @@ struct Factory { }; TEST(RunQSimTest, QSimRunner1) { - auto circuit = CircuitQsimParser::Run(circuit_string, 99); + auto circuit = CircuitQSimParser::Run(circuit_string, 99); EXPECT_EQ(circuit.num_qubits, 4); EXPECT_EQ(circuit.ops.size(), 27); @@ -113,7 +113,7 @@ TEST(RunQSimTest, QSimRunner1) { } TEST(RunQSimTest, QSimRunner2) { - auto circuit = CircuitQsimParser::Run(circuit_string, 99); + auto circuit = CircuitQSimParser::Run(circuit_string, 99); EXPECT_EQ(circuit.num_qubits, 4); EXPECT_EQ(circuit.ops.size(), 27); @@ -166,7 +166,7 @@ R"(2 )"; TEST(RunQSimTest, QSimSampler) { - auto circuit = CircuitQsimParser::Run(sample_circuit_string, 99); + auto circuit = CircuitQSimParser::Run(sample_circuit_string, 99); EXPECT_EQ(circuit.num_qubits, 2); EXPECT_EQ(circuit.ops.size(), 11); diff --git a/tests/run_qsim_deprecated_test.cc b/tests/run_qsim_deprecated_test.cc index f9fdde378..7533072ff 100644 --- a/tests/run_qsim_deprecated_test.cc +++ b/tests/run_qsim_deprecated_test.cc @@ -76,7 +76,7 @@ struct Factory { }; TEST(RunQSimTest, QSimRunner1) { - auto circuit = CircuitQsimParser::Run(circuit_string, 99); + auto circuit = CircuitQSimParser::Run(circuit_string, 99); EXPECT_EQ(circuit.num_qubits, 4); EXPECT_EQ(circuit.ops.size(), 27); @@ -112,7 +112,7 @@ TEST(RunQSimTest, QSimRunner1) { } TEST(RunQSimTest, QSimRunner2) { - auto circuit = CircuitQsimParser::Run(circuit_string, 99); + auto circuit = CircuitQSimParser::Run(circuit_string, 99); EXPECT_EQ(circuit.num_qubits, 4); EXPECT_EQ(circuit.ops.size(), 27); @@ -165,7 +165,7 @@ R"(2 )"; TEST(RunQSimTest, QSimSampler) { - auto circuit = CircuitQsimParser::Run(sample_circuit_string, 99); + auto circuit = CircuitQSimParser::Run(sample_circuit_string, 99); EXPECT_EQ(circuit.num_qubits, 2); EXPECT_EQ(circuit.ops.size(), 11); diff --git a/tests/run_qsim_test.cc b/tests/run_qsim_test.cc index c360828e7..c38fc8d65 100644 --- a/tests/run_qsim_test.cc +++ b/tests/run_qsim_test.cc @@ -47,8 +47,8 @@ struct TestRuntimeError { } }; -using TestQsimParser = - qsim::CircuitQsimParser; +using TestQSimParser = + qsim::CircuitQSimParser; namespace qsim { @@ -97,7 +97,7 @@ struct Factory { }; TEST(RunQSimTest, QSimRunner) { - auto circuit = CircuitQsimParser::Run(circuit_string, 99); + auto circuit = CircuitQSimParser::Run(circuit_string, 99); EXPECT_EQ(circuit.num_qubits, 4); EXPECT_EQ(circuit.ops.size(), 27); @@ -190,7 +190,7 @@ TEST(RunQSimTest, Factorial) { cc::Observables obss; - auto [circuit, _] = TestQsimParser::Run(circuit_str, 100, symtab); + auto [circuit, _] = TestQSimParser::Run(circuit_str, 100, symtab); using Simulator = Factory::Simulator; using StateSpace = Simulator::StateSpace; @@ -223,7 +223,7 @@ TEST(RunQSimTest, Histogram) { cc::SymTable symtab; - auto [circuit, obss] = TestQsimParser::Run(circuit_str, 100, symtab); + auto [circuit, obss] = TestQSimParser::Run(circuit_str, 100, symtab); using Simulator = Factory::Simulator; using StateSpace = Simulator::StateSpace; diff --git a/tests/run_qsimh_test.cc b/tests/run_qsimh_test.cc index 9177c74e2..e653e254a 100644 --- a/tests/run_qsimh_test.cc +++ b/tests/run_qsimh_test.cc @@ -113,7 +113,7 @@ struct Factory { }; TEST(RunQSimHTest, QSimHRunner) { - auto circuit = CircuitQsimParser::Run(circuit_string, 99); + auto circuit = CircuitQSimParser::Run(circuit_string, 99); EXPECT_EQ(circuit.num_qubits, 4); EXPECT_EQ(circuit.ops.size(), 63); diff --git a/tests/statespace_testfixture.h b/tests/statespace_testfixture.h index 076104bad..59af908e4 100644 --- a/tests/statespace_testfixture.h +++ b/tests/statespace_testfixture.h @@ -470,7 +470,7 @@ void TestNormAndInnerProduct(const Factory& factory) { unsigned depth = 8; - auto circuit = CircuitQsimParser::Run(circuit_string, depth); + auto circuit = CircuitQSimParser::Run(circuit_string, depth); circuit.ops.push_back(GateT::Create(depth + 1, 0)); StateSpace state_space = factory.CreateStateSpace(); @@ -564,7 +564,7 @@ void TestSamplingCrossEntropyDifference(const Factory& factory) { unsigned depth = 30; uint64_t num_samples = 2000000; - auto circuit = CircuitQsimParser::Run(circuit_string, depth); + auto circuit = CircuitQSimParser::Run(circuit_string, depth); StateSpace state_space = factory.CreateStateSpace(); State state = state_space.Create(circuit.num_qubits); @@ -776,7 +776,7 @@ void TestMeasurementLarge(const Factory& factory) { unsigned depth = 20; - auto circuit = CircuitQsimParser::Run(circuit_string, depth); + auto circuit = CircuitQSimParser::Run(circuit_string, depth); StateSpace state_space = factory.CreateStateSpace(); State state = state_space.Create(circuit.num_qubits);