From f60d9b7c3643f28359812ccbbf6de121cc94468c Mon Sep 17 00:00:00 2001 From: Ondra Lengal Date: Tue, 20 Oct 2026 23:19:28 +0200 Subject: [PATCH] Fix --loopsum for amplitudes with an imaginary part (#22) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A loop-summarization variable v is encoded as vR + i·vI, so vI must be bound to the real number Im(x). eval_symbol bound it to FiveTuple::imag(), which is i·Im(x), so every summarized iteration ran on Re(x) - Im(x) instead of x. Rotate the value a quarter turn clockwise (multiply by -i) to get Im(x). Add a unit test that runs four loops both unrolled and summarized and requires the same printed states: the issue's example, (T·H)^3 on |0>, S·S on |1>, and a two-qubit loop with Y, H, CZ and T†. All four fail without the fix. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_013gtFQRPe1eSSpmA7EJ2KNY --- src/loop_summarization.cc | 6 +++- unit_tests/explicit_tree_aut_test.cc | 34 +++++++++++++++++++ .../testcase/ForLoopComplex/complex_pre.hsl | 2 ++ .../testcase/ForLoopComplex/h_once.qasm | 7 ++++ .../ForLoopComplex/ht_three_times.qasm | 8 +++++ .../testcase/ForLoopComplex/one_pre.hsl | 2 ++ .../testcase/ForLoopComplex/s_twice.qasm | 7 ++++ .../testcase/ForLoopComplex/two_qubit.qasm | 10 ++++++ .../testcase/ForLoopComplex/two_qubit_pre.hsl | 2 ++ .../testcase/ForLoopComplex/zero_pre.hsl | 2 ++ 10 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 unit_tests/testcase/ForLoopComplex/complex_pre.hsl create mode 100644 unit_tests/testcase/ForLoopComplex/h_once.qasm create mode 100644 unit_tests/testcase/ForLoopComplex/ht_three_times.qasm create mode 100644 unit_tests/testcase/ForLoopComplex/one_pre.hsl create mode 100644 unit_tests/testcase/ForLoopComplex/s_twice.qasm create mode 100644 unit_tests/testcase/ForLoopComplex/two_qubit.qasm create mode 100644 unit_tests/testcase/ForLoopComplex/two_qubit_pre.hsl create mode 100644 unit_tests/testcase/ForLoopComplex/zero_pre.hsl diff --git a/src/loop_summarization.cc b/src/loop_summarization.cc index fb94bad65..406113998 100644 --- a/src/loop_summarization.cc +++ b/src/loop_summarization.cc @@ -239,7 +239,11 @@ Symbol eval_symbol( const AUTOQ::Symbol::Symbolic& symbol, for(auto& [term, complex] : sym.complex){ for(auto& [string, cppint] : term){ if(string.back() == 'I'){ - mapping[term] = conc.complex.imag(); + // A variable v is encoded as vR + i·vI, so vI must be + // bound to the real number Im(x). imag() returns + // i·Im(x); rotating it by a quarter turn clockwise + // (multiplying by -i) gives Im(x). + mapping[term] = conc.complex.imag().clockwise(boost::rational(1, 4)); } else if(string.back() == 'R'){ mapping[term] = conc.complex.real(); diff --git a/unit_tests/explicit_tree_aut_test.cc b/unit_tests/explicit_tree_aut_test.cc index 99007e08f..098829ea4 100644 --- a/unit_tests/explicit_tree_aut_test.cc +++ b/unit_tests/explicit_tree_aut_test.cc @@ -13,6 +13,7 @@ #include #include #include +#include #include "autoq/error.hh" #include "autoq/inclusion.hh" @@ -298,6 +299,39 @@ BOOST_AUTO_TEST_CASE(for_loop_summarization){ BOOST_REQUIRE_MESSAGE(verify, folder + " failed!"); } +// Runs the circuit on the precondition and returns the printed set of states. +static std::string execute_and_print(const std::string& pre, const std::string& circuit, const std::string& loop) { + auto [autVec, qp] = AUTOQ::Parsing::TimbukParser::ReadTwoAutomata(pre, pre, circuit); + auto aut = autVec.at(0); + autVec.erase(autVec.begin(), autVec.begin() + 2); + ParameterMap params; + params["loop"] = loop; + aut.execute(circuit, qp, autVec, params); + std::ostringstream out; + auto* old = std::cout.rdbuf(out.rdbuf()); + aut.print_language(); + std::cout.rdbuf(old); + return out.str(); +} + +// Loop summarization must give exactly the states that unrolling gives, also +// when an iteration receives an amplitude with a nonzero imaginary part (#22). +BOOST_AUTO_TEST_CASE(for_loop_summarization_complex_amplitudes){ + std::string folder = test_dir_from_file(__FILE__, "testcase/ForLoopComplex/"); + const std::vector> cases = { + {"complex_pre.hsl", "h_once.qasm"}, + {"zero_pre.hsl", "ht_three_times.qasm"}, + {"one_pre.hsl", "s_twice.qasm"}, + {"two_qubit_pre.hsl", "two_qubit.qasm"}, + }; + for (const auto& [pre, circuit] : cases) { + auto unrolled = execute_and_print(folder + pre, folder + circuit, "manual"); + auto summarized = execute_and_print(folder + pre, folder + circuit, "symbolic"); + BOOST_CHECK_MESSAGE(unrolled == summarized, + pre + " + " + circuit + ": unrolled " + unrolled + " but summarized " + summarized); + } +} + static void grover_oracle(AUTOQ::ConcreteAutomata& aut, unsigned ans, int n) { for (int i=1; i<=n; i++) { diff --git a/unit_tests/testcase/ForLoopComplex/complex_pre.hsl b/unit_tests/testcase/ForLoopComplex/complex_pre.hsl new file mode 100644 index 000000000..6b70256a3 --- /dev/null +++ b/unit_tests/testcase/ForLoopComplex/complex_pre.hsl @@ -0,0 +1,2 @@ +Extended Dirac +{1/sqrt2 |0> + ei2pi(1/4)/sqrt2 |1>} diff --git a/unit_tests/testcase/ForLoopComplex/h_once.qasm b/unit_tests/testcase/ForLoopComplex/h_once.qasm new file mode 100644 index 000000000..9c88cf222 --- /dev/null +++ b/unit_tests/testcase/ForLoopComplex/h_once.qasm @@ -0,0 +1,7 @@ +OPENQASM 3; +include "stdgates.inc"; +qubit[1] q; + +for int i in [1:1] { + h q[0]; +} diff --git a/unit_tests/testcase/ForLoopComplex/ht_three_times.qasm b/unit_tests/testcase/ForLoopComplex/ht_three_times.qasm new file mode 100644 index 000000000..507b34224 --- /dev/null +++ b/unit_tests/testcase/ForLoopComplex/ht_three_times.qasm @@ -0,0 +1,8 @@ +OPENQASM 3; +include "stdgates.inc"; +qubit[1] q; + +for int i in [1:3] { + h q[0]; + t q[0]; +} diff --git a/unit_tests/testcase/ForLoopComplex/one_pre.hsl b/unit_tests/testcase/ForLoopComplex/one_pre.hsl new file mode 100644 index 000000000..7fac33481 --- /dev/null +++ b/unit_tests/testcase/ForLoopComplex/one_pre.hsl @@ -0,0 +1,2 @@ +Extended Dirac +{|1>} diff --git a/unit_tests/testcase/ForLoopComplex/s_twice.qasm b/unit_tests/testcase/ForLoopComplex/s_twice.qasm new file mode 100644 index 000000000..709debd18 --- /dev/null +++ b/unit_tests/testcase/ForLoopComplex/s_twice.qasm @@ -0,0 +1,7 @@ +OPENQASM 3; +include "stdgates.inc"; +qubit[1] q; + +for int i in [1:2] { + s q[0]; +} diff --git a/unit_tests/testcase/ForLoopComplex/two_qubit.qasm b/unit_tests/testcase/ForLoopComplex/two_qubit.qasm new file mode 100644 index 000000000..9f1e14ef1 --- /dev/null +++ b/unit_tests/testcase/ForLoopComplex/two_qubit.qasm @@ -0,0 +1,10 @@ +OPENQASM 3; +include "stdgates.inc"; +qubit[2] q; + +for int i in [1:3] { + y q[0]; + h q[1]; + cz q[0], q[1]; + tdg q[0]; +} diff --git a/unit_tests/testcase/ForLoopComplex/two_qubit_pre.hsl b/unit_tests/testcase/ForLoopComplex/two_qubit_pre.hsl new file mode 100644 index 000000000..9c51ba408 --- /dev/null +++ b/unit_tests/testcase/ForLoopComplex/two_qubit_pre.hsl @@ -0,0 +1,2 @@ +Extended Dirac +{1/sqrt2 |00> + ei2pi(1/8)/sqrt2 |11>} diff --git a/unit_tests/testcase/ForLoopComplex/zero_pre.hsl b/unit_tests/testcase/ForLoopComplex/zero_pre.hsl new file mode 100644 index 000000000..cdf60b305 --- /dev/null +++ b/unit_tests/testcase/ForLoopComplex/zero_pre.hsl @@ -0,0 +1,2 @@ +Extended Dirac +{|0>}