Skip to content
Merged
5 changes: 5 additions & 0 deletions include/common/parsing/CodePreprocessing.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#pragma once

#include "AssertionParsing.hpp"
#include "ir/operations/IfElseOperation.hpp"

#include <cstddef>
#include <map>
Expand Down Expand Up @@ -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;
};

/**
Expand Down
41 changes: 30 additions & 11 deletions src/backend/dd/DDSimDebug.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 <kind> 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.
Expand Down Expand Up @@ -132,7 +157,7 @@ std::optional<bool> evaluateClassicConditionFromCode(DDSimulationState* ddsim,
}
}

return registerValue == parsed->expectedValue;
return applyComparison(registerValue, parsed->expectedValue, parsed->kind);
}

/**
Expand Down Expand Up @@ -1103,10 +1128,6 @@ Result ddsimStepForward(SimulationState* self) {
// register first.
const auto* op =
dynamic_cast<qc::IfElseOperation*>((*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;
Expand All @@ -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();
Expand Down Expand Up @@ -1201,10 +1223,6 @@ Result ddsimStepBackward(SimulationState* self) {
if ((*ddsim->iterator)->isIfElseOperation()) {
const auto* op =
dynamic_cast<qc::IfElseOperation*>((*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;
Expand All @@ -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();
Expand Down
46 changes: 38 additions & 8 deletions src/common/parsing/CodePreprocessing.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 <algorithm>
#include <array>
#include <cctype>
#include <cstddef>
#include <exception>
Expand All @@ -30,6 +32,7 @@
#include <sstream>
#include <stdexcept>
#include <string>
#include <string_view>
#include <utility>
#include <vector>

Expand Down Expand Up @@ -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<OperatorMatch, 6>;
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<OperatorMatch> 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;
}
Expand Down Expand Up @@ -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<ClassicCondition>
Expand Down
164 changes: 164 additions & 0 deletions test/test_custom_code.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<Complex, 4> 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<Complex, 4> 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<Complex, 4> 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<Complex, 4> 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<Complex, 4> 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<Complex, 4> amplitudes{};
Statevector sv{2, 4, amplitudes.data()};
state->getStateVectorFull(state, &sv);
ASSERT_TRUE(complexEquality(amplitudes[1], 1, 0.0));
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

/**
* @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<Complex, 4> 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<Complex, 4> 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.
*/
Expand Down
Loading