Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions include/mqt-core/qir/runtime/QIR.h
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,31 @@ bool __quantum__rt__read_result(Result*);
/// the label is included in the output or omitted.
void __quantum__rt__result_record_output(Result*, const char*);

/// Adds a boolean value to the generated output. The second parameter defines
/// a string label for the value. Depending on the output schema, the label is
/// included in the output or omitted.
void __quantum__rt__bool_record_output(bool, const char*);

/// Adds an integer value to the generated output. The second parameter defines
/// a string label for the value. Depending on the output schema, the label is
/// included in the output or omitted.
void __quantum__rt__int_record_output(int64_t, const char*);

/// Adds a floating-point value to the generated output. The second parameter
/// defines a string label for the value. Depending on the output schema, the
/// label is included in the output or omitted.
void __quantum__rt__float_record_output(double, const char*);

/// Inserts a marker in the generated output indicating that the next
/// `elementCount` recorded values form the contents of a tuple. The second
/// parameter defines a string label for the tuple.
void __quantum__rt__tuple_record_output(int64_t elementCount, const char*);

/// Inserts a marker in the generated output indicating that the next `size`
/// recorded values form the contents of an array. The second parameter defines
/// a string label for the array.
void __quantum__rt__array_record_output(int64_t size, const char*);

// NOLINTEND(readability-identifier-naming)
// NOLINTEND(modernize-deprecated-headers)
// NOLINTEND(modernize-use-using)
Expand Down
21 changes: 13 additions & 8 deletions include/mqt-core/qir/runtime/Runtime.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
#include <sstream>
#include <stdexcept>
#include <string>
#include <string_view>
#include <tuple>
#include <type_traits>
#include <unordered_map>
Expand Down Expand Up @@ -237,7 +238,7 @@ class Runtime {
std::vector<qc::Qubit> qubitPermutation;
static constexpr uintptr_t MIN_DYN_RESULT_ADDRESS = 0x10000;
std::unordered_map<Result*, ResultStruct> rRegister;
std::string recordedOutputs;
std::string measurements;
uintptr_t currentMaxQubitAddress;
qc::Qubit currentMaxQubitId;
uintptr_t currentMaxResultAddress;
Expand Down Expand Up @@ -391,13 +392,17 @@ class Runtime {
auto rFree(Result* result) -> void;
auto equal(Result* result1, Result* result2) -> bool;

/// Append the value referenced by `result` to the recorded outputs bit
/// string in record order.
auto recordOutput(Result* result) -> void;
/// Append a measurement bit to the measurement string.
auto appendMeasurementBit(bool result) -> void;

/// @returns the outputs declared by the program as a bit string in record
/// order.
auto getRecordedOutputs() const -> const std::string&;
/// @returns the accumulated measurement string.
auto getMeasurements() const -> const std::string&;

/// Emit `label:\n` to the output stream.
auto outputContainer(const char* label, int64_t elementCount) const -> void;

/// Emit `label: valueStr\n` to the output stream.
auto outputValue(const char* label, std::string_view valueStr) const -> void;

/// Move the quantum state out of the runtime.
/// Then reset the runtime to a clean state ready for the next job.
Expand All @@ -406,7 +411,7 @@ class Runtime {
/// @returns the moved @c QState from the runtime.
auto takeState() -> QState;

auto getOstream() -> std::ostream&;
auto getOstream() const -> std::ostream&;
auto setOstream(std::ostream& other) -> void;
auto resetOstream() -> void;
};
Expand Down
2 changes: 1 addition & 1 deletion src/qdmi/devices/dd/Device.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -490,7 +490,7 @@ auto MQT_DDSIM_QDMI_Device_Job_impl_d::submitQIRProgramSampling()
llvm::formatv("QIR program failed with error: {}", rc));
}
// Update the measurement counts.
++counts_[runtime.getRecordedOutputs()];
++counts_[runtime.getMeasurements()];
}
});
}
Expand Down
5 changes: 5 additions & 0 deletions src/qir/jit/Session.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,11 @@ void JitSession::registerRuntimeSymbols() {
REGISTER_SYMBOL(__quantum__rt__initialize);
REGISTER_SYMBOL(__quantum__rt__read_result);
REGISTER_SYMBOL(__quantum__rt__result_record_output);
REGISTER_SYMBOL(__quantum__rt__bool_record_output);
REGISTER_SYMBOL(__quantum__rt__int_record_output);
REGISTER_SYMBOL(__quantum__rt__float_record_output);
REGISTER_SYMBOL(__quantum__rt__tuple_record_output);
REGISTER_SYMBOL(__quantum__rt__array_record_output);
});
}

Expand Down
32 changes: 29 additions & 3 deletions src/qir/runtime/QIR.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
#include <cstddef>
#include <cstdint>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>

extern "C" {
Expand Down Expand Up @@ -381,10 +383,34 @@ bool __quantum__rt__read_result(Result* result) {
}

void __quantum__rt__result_record_output(Result* result, const char* label) {
const bool bit = __quantum__rt__read_result(result);
auto& runtime = qir::Runtime::getInstance();
runtime.recordOutput(result);
runtime.getOstream() << label << ": "
<< (__quantum__rt__read_result(result) ? 1 : 0) << "\n";
runtime.outputValue(label, bit ? "1" : "0");
// Accumulate new measurement bit.
runtime.appendMeasurementBit(bit);
}

void __quantum__rt__bool_record_output(bool value, const char* label) {
qir::Runtime::getInstance().outputValue(label, value ? "1" : "0");
}

void __quantum__rt__int_record_output(int64_t value, const char* label) {
qir::Runtime::getInstance().outputValue(label, std::to_string(value));
}

void __quantum__rt__float_record_output(double value, const char* label) {
std::ostringstream oss;
oss << value;
qir::Runtime::getInstance().outputValue(label, oss.str());
}

void __quantum__rt__tuple_record_output(int64_t elementCount,
const char* label) {
qir::Runtime::getInstance().outputContainer(label, elementCount);
}

void __quantum__rt__array_record_output(int64_t size, const char* label) {
qir::Runtime::getInstance().outputContainer(label, size);
}

} // extern "C"
24 changes: 18 additions & 6 deletions src/qir/runtime/Runtime.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#include <sstream>
#include <stdexcept>
#include <string>
#include <string_view>
#include <unordered_map>
#include <utility>
#include <vector>
Expand All @@ -52,13 +53,14 @@ auto Runtime::reset() -> void {
addressMode = AddressMode::UNKNOWN;
qRegister.clear();
rRegister.clear();
measurements.clear();
// NOLINTBEGIN(performance-no-int-to-ptr)
rRegister.emplace(reinterpret_cast<Result*>(RESULT_ZERO_ADDRESS),
ResultStruct{.refcount = 0, .r = false});
rRegister.emplace(reinterpret_cast<Result*>(RESULT_ONE_ADDRESS),
ResultStruct{.refcount = 0, .r = true});
// NOLINTEND(performance-no-int-to-ptr)
recordedOutputs.clear();
measurements.clear();
currentMaxQubitAddress = MIN_DYN_QUBIT_ADDRESS;
currentMaxQubitId = 0;
currentMaxResultAddress = MIN_DYN_RESULT_ADDRESS;
Expand Down Expand Up @@ -164,12 +166,12 @@ auto Runtime::equal(Result* result1, Result* result2) -> bool {
return deref(result1).r == deref(result2).r;
}

auto Runtime::recordOutput(Result* result) -> void {
recordedOutputs.push_back(deref(result).r ? '1' : '0');
auto Runtime::appendMeasurementBit(bool result) -> void {
measurements.push_back(result ? '1' : '0');
}

auto Runtime::getRecordedOutputs() const -> const std::string& {
return recordedOutputs;
auto Runtime::getMeasurements() const -> const std::string& {
return measurements;
}

auto Runtime::takeState() -> QState {
Expand All @@ -178,7 +180,17 @@ auto Runtime::takeState() -> QState {
return ret;
}

auto Runtime::getOstream() -> std::ostream& { return *os; }
auto Runtime::outputContainer(const char* label,
int64_t /* elementCount */) const -> void {
*os << (label != nullptr ? label : "") << ":\n";
}

auto Runtime::outputValue(const char* label, std::string_view valueStr) const
-> void {
*os << (label != nullptr ? label : "") << ": " << valueStr << "\n";
}

auto Runtime::getOstream() const -> std::ostream& { return *os; }

auto Runtime::setOstream(std::ostream& other) -> void { os = &other; }

Expand Down
102 changes: 102 additions & 0 deletions test/circuits/AdaptiveRecordOutputs.ll
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
; ModuleID = 'Adaptive module implementing a 3-qubit Hamming weight'
source_filename = "AdaptiveRecordOutputs.ll"

%Qubit = type opaque
%Result = type opaque

@r0_lbl = internal constant [3 x i8] c"r0\00"
@r1_lbl = internal constant [3 x i8] c"r1\00"
@r2_lbl = internal constant [3 x i8] c"r2\00"
@outputs_lbl = internal constant [8 x i8] c"outputs\00"
@measurements_lbl = internal constant [15 x i8] c" measurements\00"
@m0_lbl = internal constant [7 x i8] c" m0\00"
@m1_lbl = internal constant [7 x i8] c" m1\00"
@m2_lbl = internal constant [7 x i8] c" m2\00"
@weight_lbl = internal constant [17 x i8] c" hamming_weight\00"
@mean_lbl = internal constant [7 x i8] c" mean\00"

define i32 @main() #0 {
entry:
call void @__quantum__rt__initialize(i8* null)
%q0 = call %Qubit* @__quantum__rt__qubit_allocate()
%q1 = call %Qubit* @__quantum__rt__qubit_allocate()
%q2 = call %Qubit* @__quantum__rt__qubit_allocate()
call void @__quantum__qis__h__body(%Qubit* %q0)
call void @__quantum__qis__h__body(%Qubit* %q1)
call void @__quantum__qis__h__body(%Qubit* %q2)
%r0 = call %Result* @__quantum__qis__m__body(%Qubit* %q0)
%r1 = call %Result* @__quantum__qis__m__body(%Qubit* %q1)
%r2 = call %Result* @__quantum__qis__m__body(%Qubit* %q2)
%b0 = call i1 @__quantum__rt__read_result(%Result* %r0)
%b1 = call i1 @__quantum__rt__read_result(%Result* %r1)
%b2 = call i1 @__quantum__rt__read_result(%Result* %r2)

; Classical compute: Hamming weight and its mean.
%c0 = zext i1 %b0 to i64
%c1 = zext i1 %b1 to i64
%c2 = zext i1 %b2 to i64
%sum01 = add i64 %c0, %c1
%weight = add i64 %sum01, %c2
%weight_f = sitofp i64 %weight to double
%num_qubits_f = uitofp i64 3 to double
%mean_f = fdiv double %weight_f, %num_qubits_f

call void @__quantum__rt__qubit_release(%Qubit* %q0)
call void @__quantum__rt__qubit_release(%Qubit* %q1)
call void @__quantum__rt__qubit_release(%Qubit* %q2)

; Record the raw measurement bits (these feed the histogram bucketing key).
call void @__quantum__rt__result_record_output(%Result* %r0, i8* getelementptr inbounds ([3 x i8], [3 x i8]* @r0_lbl, i32 0, i32 0))
call void @__quantum__rt__result_record_output(%Result* %r1, i8* getelementptr inbounds ([3 x i8], [3 x i8]* @r1_lbl, i32 0, i32 0))
call void @__quantum__rt__result_record_output(%Result* %r2, i8* getelementptr inbounds ([3 x i8], [3 x i8]* @r2_lbl, i32 0, i32 0))

; Output: tuple of 3 elements (array of 3 bools, int count, float mean).
call void @__quantum__rt__tuple_record_output(i64 3, i8* getelementptr inbounds ([8 x i8], [8 x i8]* @outputs_lbl, i32 0, i32 0))
call void @__quantum__rt__array_record_output(i64 3, i8* getelementptr inbounds ([13 x i8], [13 x i8]* @measurements_lbl, i32 0, i32 0))
call void @__quantum__rt__bool_record_output(i1 %b0, i8* getelementptr inbounds ([3 x i8], [3 x i8]* @m0_lbl, i32 0, i32 0))
call void @__quantum__rt__bool_record_output(i1 %b1, i8* getelementptr inbounds ([3 x i8], [3 x i8]* @m1_lbl, i32 0, i32 0))
call void @__quantum__rt__bool_record_output(i1 %b2, i8* getelementptr inbounds ([3 x i8], [3 x i8]* @m2_lbl, i32 0, i32 0))
call void @__quantum__rt__int_record_output(i64 %weight, i8* getelementptr inbounds ([15 x i8], [15 x i8]* @weight_lbl, i32 0, i32 0))
call void @__quantum__rt__float_record_output(double %mean_f, i8* getelementptr inbounds ([5 x i8], [5 x i8]* @mean_lbl, i32 0, i32 0))

call void @__quantum__rt__result_update_reference_count(%Result* %r0, i32 -1)
call void @__quantum__rt__result_update_reference_count(%Result* %r1, i32 -1)
call void @__quantum__rt__result_update_reference_count(%Result* %r2, i32 -1)
ret i32 0
}

declare void @__quantum__qis__h__body(%Qubit*)

declare %Result* @__quantum__qis__m__body(%Qubit*) #1

declare i1 @__quantum__rt__read_result(%Result*)

declare void @__quantum__rt__initialize(i8*)

declare %Qubit* @__quantum__rt__qubit_allocate()

declare void @__quantum__rt__qubit_release(%Qubit*)

declare void @__quantum__rt__result_record_output(%Result*, i8*)

declare void @__quantum__rt__tuple_record_output(i64, i8*)

declare void @__quantum__rt__array_record_output(i64, i8*)

declare void @__quantum__rt__bool_record_output(i1, i8*)

declare void @__quantum__rt__int_record_output(i64, i8*)

declare void @__quantum__rt__float_record_output(double, i8*)

declare void @__quantum__rt__result_update_reference_count(%Result*, i32)

attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="custom" "required_num_qubits"="3" "required_num_results"="3" }
attributes #1 = { "irreversible" }

!llvm.module.flags = !{!0, !1, !2, !3}

!0 = !{i32 1, !"qir_major_version", i32 1}
!1 = !{i32 7, !"qir_minor_version", i32 0}
!2 = !{i32 1, !"dynamic_qubit_management", i1 true}
!3 = !{i32 1, !"dynamic_result_management", i1 true}
36 changes: 33 additions & 3 deletions test/qdmi/devices/dd/results_sampling_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,15 @@ class HistogramTest : public ::testing::Test {
#endif

using Histogram = std::pair<std::vector<std::string>, std::vector<size_t>>;
static constexpr size_t SHOTS = 1024;
static constexpr size_t NUM_SHOTS = 1024;
static constexpr size_t NUM_QUBITS = 3;

static Histogram runProgram(const QDMI_Program_Format format,
const std::string_view program) {
const qdmi_test::SessionGuard s{};
const qdmi_test::JobGuard j{s.session};
EXPECT_EQ(qdmi_test::setProgram(j.job, format, program), QDMI_SUCCESS);
EXPECT_EQ(qdmi_test::setShots(j.job, SHOTS), QDMI_SUCCESS);
EXPECT_EQ(qdmi_test::setShots(j.job, NUM_SHOTS), QDMI_SUCCESS);
EXPECT_EQ(qdmi_test::submitAndWait(j.job, 0), QDMI_SUCCESS);
return qdmi_test::getHistogram(j.job);
}
Expand All @@ -71,13 +72,30 @@ class HistogramTest : public ::testing::Test {
ASSERT_EQ(keys.size(), vals.size());
// Values should sum up to the number of SHOTS.
const auto sum = std::accumulate(vals.cbegin(), vals.cend(), size_t{0});
EXPECT_EQ(sum, SHOTS);
EXPECT_EQ(sum, NUM_SHOTS);
// Both keys '00' and '11' should be expected.
ASSERT_EQ(keys.size(), 2U);
// And no other keys should be expected.
EXPECT_TRUE(std::ranges::all_of(
keys, [](const auto& k) { return k == "00" || k == "11"; }));
}

/// Smoke check: used for circuits whose distribution we don't pin down (e.g.
/// multi-output adaptive programs).
static void checkSmokeHistogram(const Histogram& hist) {
const auto& [keys, vals] = hist;
// Both vectors have the same size.
ASSERT_EQ(keys.size(), vals.size());
// Values sum up to the number of SHOTS.
const auto sum = std::accumulate(vals.cbegin(), vals.cend(), size_t{0});
EXPECT_EQ(sum, NUM_SHOTS);
// Every key is a NUM_QUBITS-character bit string.
EXPECT_TRUE(std::ranges::all_of(keys, [](const auto& k) {
return k.size() == NUM_QUBITS && std::ranges::all_of(k, [](char c) {
return c == '0' || c == '1';
});
}));
}
};

#ifdef BUILD_MQT_CORE_QDMI_DDSIM_WITH_QIR
Expand Down Expand Up @@ -144,6 +162,18 @@ TEST_F(QIRHistogramTestString, Adaptive) {
checkHistogram(
runProgram(format, qir_test::getProgram("BellPairAdaptive.ll")));
}

TEST_F(QIRHistogramTestModule, AdaptiveRecordOutputs) {
constexpr auto format = QDMI_PROGRAM_FORMAT_QIRADAPTIVEMODULE;
checkSmokeHistogram(
runProgram(format, getProgram("AdaptiveRecordOutputs.ll")));
}

TEST_F(QIRHistogramTestString, AdaptiveRecordOutputs) {
constexpr auto format = QDMI_PROGRAM_FORMAT_QIRADAPTIVESTRING;
checkSmokeHistogram(
runProgram(format, qir_test::getProgram("AdaptiveRecordOutputs.ll")));
}
#endif

TEST(ResultsSampling, BufferTooSmallErrors) {
Expand Down
Loading
Loading