diff --git a/include/common/parsing/CodePreprocessing.hpp b/include/common/parsing/CodePreprocessing.hpp index f777d234..d2896120 100644 --- a/include/common/parsing/CodePreprocessing.hpp +++ b/include/common/parsing/CodePreprocessing.hpp @@ -17,6 +17,7 @@ #pragma once #include "AssertionParsing.hpp" +#include "ir/operations/IfElseOperation.hpp" #include #include @@ -228,6 +229,10 @@ struct ClassicCondition { * @brief The expected value in the condition comparison. */ size_t expectedValue; + /** + * @brief The comparison operator used in the condition. + */ + qc::ComparisonKind kind = qc::Eq; }; /** diff --git a/src/backend/dd/DDSimDebug.cpp b/src/backend/dd/DDSimDebug.cpp index 6b12db34..4e2af625 100644 --- a/src/backend/dd/DDSimDebug.cpp +++ b/src/backend/dd/DDSimDebug.cpp @@ -94,6 +94,31 @@ struct DDSimulationStateGuard { DDSimulationState* state; }; +/** + * @brief Apply a classical comparison operator between two unsigned values. + * @param lhs The left-hand side of the comparison. + * @param rhs The right-hand side of the comparison. + * @param kind The comparison operator to apply. + * @return The boolean result of `lhs rhs`. + */ +bool applyComparison(size_t lhs, size_t rhs, qc::ComparisonKind kind) { + switch (kind) { + case qc::Eq: + return lhs == rhs; + case qc::Neq: + return lhs != rhs; + case qc::Lt: + return lhs < rhs; + case qc::Leq: + return lhs <= rhs; + case qc::Gt: + return lhs > rhs; + case qc::Geq: + return lhs >= rhs; + } + return false; +} + /** * @brief Evaluate a classic-controlled condition from the original code. * @param ddsim The simulation state. @@ -132,7 +157,7 @@ std::optional evaluateClassicConditionFromCode(DDSimulationState* ddsim, } } - return registerValue == parsed->expectedValue; + return applyComparison(registerValue, parsed->expectedValue, parsed->kind); } /** @@ -1103,10 +1128,6 @@ Result ddsimStepForward(SimulationState* self) { // register first. const auto* op = dynamic_cast((*ddsim->iterator).get()); - if (op->getComparisonKind() != qc::Eq) { - throw std::runtime_error("If-else operations with non-equality " - "comparisons are currently not supported"); - } const auto condition = evaluateClassicConditionFromCode(ddsim, currentInstruction); bool conditionMet = false; @@ -1129,7 +1150,8 @@ Result ddsimStepForward(SimulationState* self) { registerValue |= (value ? 1ULL : 0ULL) << i; } } - conditionMet = (registerValue == exp); + conditionMet = + applyComparison(registerValue, exp, op->getComparisonKind()); } if (conditionMet) { auto* thenOp = op->getThenOp(); @@ -1201,10 +1223,6 @@ Result ddsimStepBackward(SimulationState* self) { if ((*ddsim->iterator)->isIfElseOperation()) { const auto* op = dynamic_cast((*ddsim->iterator).get()); - if (op->getComparisonKind() != qc::Eq) { - throw std::runtime_error("If-else operations with non-equality " - "comparisons are currently not supported"); - } const auto condition = evaluateClassicConditionFromCode(ddsim, ddsim->currentInstruction); bool conditionMet = false; @@ -1227,7 +1245,8 @@ Result ddsimStepBackward(SimulationState* self) { registerValue |= (value ? 1ULL : 0ULL) << i; } } - conditionMet = (registerValue == exp); + conditionMet = + applyComparison(registerValue, exp, op->getComparisonKind()); } if (conditionMet) { auto* thenOp = op->getThenOp(); diff --git a/src/common/parsing/CodePreprocessing.cpp b/src/common/parsing/CodePreprocessing.cpp index 4c0b3112..19409a6f 100644 --- a/src/common/parsing/CodePreprocessing.cpp +++ b/src/common/parsing/CodePreprocessing.cpp @@ -18,8 +18,10 @@ #include "common/parsing/AssertionParsing.hpp" #include "common/parsing/ParsingError.hpp" #include "common/parsing/Utils.hpp" +#include "ir/operations/IfElseOperation.hpp" #include +#include #include #include #include @@ -30,6 +32,7 @@ #include #include #include +#include #include #include @@ -410,12 +413,35 @@ parseClassicConditionExpression(const std::string& condition) { if (!normalized.empty() && normalized.front() == '(') { normalized.erase(0, 1); } - const auto eqPos = normalized.find("=="); - if (eqPos == std::string::npos) { + + // Operators must be scanned longest-first so that "<=" is not misread as "<". + struct OperatorMatch { + std::string_view text; + qc::ComparisonKind kind; + }; + using OperatorsT = std::array; + static constexpr OperatorsT OPERATORS{{ + {.text = "<=", .kind = qc::Leq}, + {.text = ">=", .kind = qc::Geq}, + {.text = "==", .kind = qc::Eq}, + {.text = "!=", .kind = qc::Neq}, + {.text = "<", .kind = qc::Lt}, + {.text = ">", .kind = qc::Gt}, + }}; + + std::optional match; + for (const auto& op : OPERATORS) { + if (normalized.find(op.text) != std::string::npos) { + match = op; + break; + } + } + if (!match.has_value()) { return std::nullopt; } - const auto lhs = normalized.substr(0, eqPos); - const auto rhs = normalized.substr(eqPos + 2); + const auto opPos = normalized.find(match->text); + const auto lhs = normalized.substr(0, opPos); + const auto rhs = normalized.substr(opPos + match->text.size()); if (lhs.empty() || rhs.empty()) { return std::nullopt; } @@ -453,12 +479,16 @@ parseClassicConditionExpression(const std::string& condition) { } catch (const std::out_of_range&) { return std::nullopt; } - return ClassicCondition{ - .registerName = base, .bitIndex = bitIndex, .expectedValue = expected}; + return ClassicCondition{.registerName = base, + .bitIndex = bitIndex, + .expectedValue = expected, + .kind = match->kind}; } - return ClassicCondition{ - .registerName = lhs, .bitIndex = std::nullopt, .expectedValue = expected}; + return ClassicCondition{.registerName = lhs, + .bitIndex = std::nullopt, + .expectedValue = expected, + .kind = match->kind}; } std::optional diff --git a/test/test_custom_code.cpp b/test/test_custom_code.cpp index 8528bbb6..e55337ad 100644 --- a/test/test_custom_code.cpp +++ b/test/test_custom_code.cpp @@ -95,6 +95,170 @@ TEST_F(CustomCodeTest, IfElseOperationMulti) { ASSERT_TRUE(complexEquality(amplitudes[2], 1, 0.0)); } +/** + * @test Same behaviour as `IfElseOperationMulti`, but with the `if` body + * written as a real multi-line block. + * Ensures the parser handles line breaks inside a classic-controlled body, + * not only the single-line form. + */ +TEST_F(CustomCodeTest, IfElseOperationMultiMultilineBlock) { + loadCode(2, 1, + "x q[0];\n" + "measure q[0] -> c[0];\n" + "if(c==1) {\n" + " x q[0];\n" + " x q[1];\n" + "}\n"); + ASSERT_EQ(state->runSimulation(state), OK); + + std::array amplitudes{}; + Statevector sv{2, 4, amplitudes.data()}; + state->getStateVectorFull(state, &sv); + ASSERT_TRUE(complexEquality(amplitudes[2], 1, 0.0)); +} + +/** + * @test Test classic-controlled operations that use the `!=` comparator. + * The measured value of `c` is 1, so `c != 0` triggers and `c != 1` does not. + */ +TEST_F(CustomCodeTest, IfElseOperationNeq) { + loadCode(2, 1, + "x q[0];" + "cx q[0], q[1];" + "measure q[0] -> c[0];" + "if(c!=0) x q[1];" + "if(c!=1) z q[1];"); + ASSERT_EQ(state->runSimulation(state), OK); + + std::array amplitudes{}; + Statevector sv{2, 4, amplitudes.data()}; + state->getStateVectorFull(state, &sv); + ASSERT_TRUE(complexEquality(amplitudes[1], 1, 0.0)); +} + +/** + * @test Test classic-controlled operations that use the `<` comparator. + * The measured value of `c` is 1, so `c < 2` triggers and `c < 1` does not. + */ +TEST_F(CustomCodeTest, IfElseOperationLt) { + loadCode(2, 1, + "x q[0];" + "cx q[0], q[1];" + "measure q[0] -> c[0];" + "if(c<2) x q[1];" + "if(c<1) z q[1];"); + ASSERT_EQ(state->runSimulation(state), OK); + + std::array amplitudes{}; + Statevector sv{2, 4, amplitudes.data()}; + state->getStateVectorFull(state, &sv); + ASSERT_TRUE(complexEquality(amplitudes[1], 1, 0.0)); +} + +/** + * @test Test classic-controlled operations that use the `<=` comparator. + * The measured value of `c` is 1, so `c <= 1` triggers and `c <= 0` does not. + */ +TEST_F(CustomCodeTest, IfElseOperationLeq) { + loadCode(2, 1, + "x q[0];" + "cx q[0], q[1];" + "measure q[0] -> c[0];" + "if(c<=1) x q[1];" + "if(c<=0) z q[1];"); + ASSERT_EQ(state->runSimulation(state), OK); + + std::array amplitudes{}; + Statevector sv{2, 4, amplitudes.data()}; + state->getStateVectorFull(state, &sv); + ASSERT_TRUE(complexEquality(amplitudes[1], 1, 0.0)); +} + +/** + * @test Test classic-controlled operations that use the `>` comparator. + * The measured value of `c` is 1, so `c > 0` triggers and `c > 1` does not. + */ +TEST_F(CustomCodeTest, IfElseOperationGt) { + loadCode(2, 1, + "x q[0];" + "cx q[0], q[1];" + "measure q[0] -> c[0];" + "if(c>0) x q[1];" + "if(c>1) z q[1];"); + ASSERT_EQ(state->runSimulation(state), OK); + + std::array amplitudes{}; + Statevector sv{2, 4, amplitudes.data()}; + state->getStateVectorFull(state, &sv); + ASSERT_TRUE(complexEquality(amplitudes[1], 1, 0.0)); +} + +/** + * @test Test classic-controlled operations that use the `>=` comparator. + * The measured value of `c` is 1, so `c >= 1` triggers and `c >= 2` does not. + */ +TEST_F(CustomCodeTest, IfElseOperationGeq) { + loadCode(2, 1, + "x q[0];" + "cx q[0], q[1];" + "measure q[0] -> c[0];" + "if(c>=1) x q[1];" + "if(c>=2) z q[1];"); + ASSERT_EQ(state->runSimulation(state), OK); + + std::array amplitudes{}; + Statevector sv{2, 4, amplitudes.data()}; + state->getStateVectorFull(state, &sv); + ASSERT_TRUE(complexEquality(amplitudes[1], 1, 0.0)); +} + +/** + * @test Test classic-controlled operations that condition on a single bit + * of a classical register, using a non-equality comparator. + * The measured value of `c[0]` is 1, so `c[0] > 0` triggers and + * `c[0] > 1` does not. + */ +TEST_F(CustomCodeTest, IfElseOperationSingleBit) { + loadCode(2, 1, + "x q[0];" + "cx q[0], q[1];" + "measure q[0] -> c[0];" + "if(c[0]>0) x q[1];" + "if(c[0]>1) z q[1];"); + ASSERT_EQ(state->runSimulation(state), OK); + + std::array amplitudes{}; + Statevector sv{2, 4, amplitudes.data()}; + state->getStateVectorFull(state, &sv); + ASSERT_TRUE(complexEquality(amplitudes[1], 1, 0.0)); +} + +/** + * @test Exercise the backward-execution branch of the DD backend for a + * classic-controlled operation with a non-equality comparator. + * The measured value of `c` is 1, so `c > 0` triggers and `x q[1]` is + * applied on the forward pass; stepping backward undoes that `x q[1]`. + */ +TEST_F(CustomCodeTest, IfElseOperationBackwardStep) { + loadCode(2, 1, + "x q[0];" + "cx q[0], q[1];" + "measure q[0] -> c[0];" + "if(c>0) x q[1];"); + ASSERT_EQ(state->runSimulation(state), OK); + + std::array amplitudes{}; + Statevector sv{2, 4, amplitudes.data()}; + state->getStateVectorFull(state, &sv); + // q[1] = 0, q[0] = 1 after the `if` fires, so amplitude index 1. + ASSERT_TRUE(complexEquality(amplitudes[1], 1, 0.0)); + + ASSERT_EQ(state->stepBackward(state), OK); + state->getStateVectorFull(state, &sv); + // Undoing the `if`'s `x q[1]` restores q[1] = 1, q[0] = 1, i.e. index 3. + ASSERT_TRUE(complexEquality(amplitudes[3], 1, 0.0)); +} + /** * @test Test the `reset` instruction. */