Skip to content

Commit 22e3629

Browse files
authored
Add runtime C-API for arbitrary-basis measurement operations (#1674)
**Context:** A pass to convert the `mbqc.measure_in_basis` MLIR operation (in the `mbqc` dialect) into the LLVM dialect was added in #1679. In order to execute a compiled program containing this operation, we require a runtime function defined in the Catalyst runtime C-API. **Description of the Change:** Adds a new runtime stub to the Catalyst runtime C-API called `__catalyst__mbqc__measure_in_basis()`. This runtime function is effectively the same as the current `__catalyst__qis__Measure()` runtime function for computational-basis mid-circuit measurements since we will be validating the QJIT-compiled MBQC workloads on the null.qubit device and do not require that it fully simulates arbitrary-basis measurements—rather we treat it as a placeholder for now in order to make the workload executable (see Shortcut story linked below for details). [[sc-89637](https://app.shortcut.com/xanaduai/story/89637/add-runtime-c-api-for-arbitrary-basis-measurement-operations)]
1 parent ba3f751 commit 22e3629

File tree

6 files changed

+102
-1
lines changed

6 files changed

+102
-1
lines changed

doc/releases/changelog-dev.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,14 @@
6969
[(#1663)](https://github.com/PennyLaneAI/catalyst/pull/1663)
7070
[(#1679)](https://github.com/PennyLaneAI/catalyst/pull/1679)
7171

72+
* The Catalyst Runtime C-API now includes a stub for the experimental `mbqc.measure_in_basis`
73+
operation, `__catalyst__mbqc__measure_in_basis()`, allowing for mock execution of MBQC workloads
74+
containing parameterized arbitrary-basis measurements.
75+
[(#1674)](https://github.com/PennyLaneAI/catalyst/pull/1674)
76+
77+
This runtime stub is currently for mock execution only and should be treated as a placeholder
78+
operation. Internally, it functions just as a computational-basis measurement instruction.
79+
7280
* The utility function `EnsureFunctionDeclaration` is refactored into the `Utils` of the `Catalyst` dialect, instead of being duplicated in each individual dialect.
7381
[(#1683)](https://github.com/PennyLaneAI/catalyst/pull/1683)
7482

runtime/Makefile

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ ASAN_COMMAND = $(ASAN_FLAGS)
4141
endif
4242

4343
BUILD_TARGETS := rt_capi rtd_null_qubit
44-
TEST_TARGETS := runner_tests_qir_runtime
44+
TEST_TARGETS := runner_tests_qir_runtime runner_tests_mbqc_runtime
4545

4646
ifeq ($(ENABLE_OPENQASM), ON)
4747
BUILD_TARGETS += rtd_openqasm
@@ -97,6 +97,8 @@ test: BUILD_TYPE?=RelWithDebInfo
9797
test: test_runner
9898
@echo "Catalyst runtime test suite - NullQubit"
9999
$(ASAN_COMMAND) $(RT_BUILD_DIR)/tests/runner_tests_qir_runtime
100+
@echo "Catalyst MBQC runtime test suite"
101+
$(ASAN_COMMAND) $(RT_BUILD_DIR)/tests/runner_tests_mbqc_runtime
100102
ifeq ($(ENABLE_OPENQASM), ON)
101103
# Test the OpenQasm devices C++ tests
102104
$(ASAN_COMMAND) $(PY_ASAN_OPTIONS) $(RT_BUILD_DIR)/tests/runner_tests_openqasm
@@ -116,6 +118,7 @@ coverage: export LLVM_PROFILE_FILE := $(RT_BUILD_DIR)/tests/%m.profraw
116118
coverage: test_runner
117119
@echo "check C++ code coverage"
118120
$(ASAN_COMMAND) $(RT_BUILD_DIR)/tests/runner_tests_qir_runtime
121+
$(ASAN_COMMAND) $(RT_BUILD_DIR)/tests/runner_tests_mbqc_runtime
119122
ifeq ($(ENABLE_OPENQASM), ON)
120123
$(ASAN_COMMAND) $(PY_ASAN_OPTIONS) $(RT_BUILD_DIR)/tests/runner_tests_openqasm
121124
endif
@@ -131,6 +134,7 @@ else
131134
xcrun llvm-cov show -instr-profile $(RT_BUILD_DIR)/tests/rt_test_coverage.profdata \
132135
-object $(RT_BUILD_DIR)/tests/runner_tests_openqasm \
133136
$(RT_BUILD_DIR)/tests/runner_tests_qir_runtime \
137+
$(RT_BUILD_DIR)/tests/runner_tests_mbqc_runtime \
134138
-format=html -output-dir=$(RT_BUILD_DIR)/coverage_html \
135139
$(MK_DIR)/include $(MK_DIR)/lib $(MK_DIR)/tests
136140
endif

runtime/include/RuntimeCAPI.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,10 @@ void __catalyst__qis__State(MemRefT_CplxT_double_1d *, int64_t, /*qubits*/...);
104104
void __catalyst__qis__Gradient(int64_t, /*results*/...);
105105
void __catalyst__qis__Gradient_params(MemRefT_int64_1d *, int64_t, /*results*/...);
106106

107+
// MBQC operations
108+
RESULT *__catalyst__mbqc__measure_in_basis(QUBIT *, uint32_t, double, int32_t);
109+
110+
// Async runtime error
107111
void __catalyst__host__rt__unrecoverable_error();
108112

109113
#ifdef __cplusplus

runtime/lib/capi/RuntimeCAPI.cpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1031,4 +1031,26 @@ int8_t *__catalyst__rt__array_get_element_ptr_1d(QirArray *ptr, int64_t idx)
10311031
QubitIdType *data = qubit_vector_ptr->data();
10321032
return (int8_t *)&data[idx];
10331033
}
1034+
1035+
// -------------------------------------------------------------------------- //
1036+
// MBQC Runtime CAPI
1037+
// -------------------------------------------------------------------------- //
1038+
1039+
// NOTE: Currently this runtime operations is exactly the same as __catalyst__qis__Measure();
1040+
// we effectively treat it as a no-op for now. When hardware devices that natively support
1041+
// mid-circuit measurements in an arbitrary basis are available, we will create a new
1042+
// QuantumDevice to implement this functionality according to the hardware specs.
1043+
RESULT *__catalyst__mbqc__measure_in_basis(QUBIT *wire, uint32_t plane, double angle,
1044+
int32_t postselect)
1045+
{
1046+
std::optional<int32_t> postselectOpt{postselect};
1047+
1048+
// Any value different to 0 or 1 denotes absence of postselect, and it is hence turned into
1049+
// std::nullopt at the C++ interface
1050+
if (postselect != 0 && postselect != 1) {
1051+
postselectOpt = std::nullopt;
1052+
}
1053+
1054+
return getQuantumDevicePtr()->Measure(reinterpret_cast<QubitIdType>(wire), postselectOpt);
1055+
}
10341056
}

runtime/tests/CMakeLists.txt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,3 +116,18 @@ if(ENABLE_OQD)
116116

117117
catch_discover_tests(runner_tests_oqd)
118118
endif()
119+
120+
121+
# MBQC test suite
122+
add_executable(runner_tests_mbqc_runtime)
123+
target_sources(runner_tests_mbqc_runtime PRIVATE
124+
Test_MBQC.cpp
125+
)
126+
127+
target_link_libraries(runner_tests_mbqc_runtime PRIVATE
128+
Catch2WithMain
129+
catalyst_runtime_testing
130+
rtd_null_qubit
131+
)
132+
133+
catch_discover_tests(runner_tests_mbqc_runtime)

runtime/tests/Test_MBQC.cpp

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// Copyright 2025 Xanadu Quantum Technologies Inc.
2+
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#include <catch2/catch_approx.hpp>
16+
#include <catch2/catch_test_macros.hpp>
17+
18+
#include "RuntimeCAPI.h"
19+
20+
// -------------------------------------------------------------------------- //
21+
// MBQC Runtime Tests
22+
// -------------------------------------------------------------------------- //
23+
24+
TEST_CASE("Test __catalyst__mbqc__measure_in_basis, device=null.qubit", "[MBQC]")
25+
{
26+
__catalyst__rt__initialize(nullptr);
27+
28+
const std::string rtd_name{"null.qubit"};
29+
__catalyst__rt__device_init((int8_t *)rtd_name.c_str(), nullptr, nullptr, 0);
30+
31+
const size_t num_qubits = 1;
32+
QirArray *qs = __catalyst__rt__qubit_allocate_array(num_qubits);
33+
34+
QUBIT **q0 = (QUBIT **)__catalyst__rt__array_get_element_ptr_1d(qs, 0);
35+
36+
CHECK(reinterpret_cast<QubitIdType>(*q0) == 0);
37+
38+
// Recall that the basis states for arbitrary-basis measurements are parameterized by a plane
39+
// (either XY, YZ or ZX) and a rotation angle about that plane. See the `mbqc.measure_in_basis`
40+
// op definition in mlir/include/MBQC/IR/MBQCOps.td for details on these parameters and how they
41+
// are encoded.
42+
RESULT *mres = __catalyst__mbqc__measure_in_basis(*q0, 0U /*plane*/, 0.0 /*angle*/, -1);
43+
44+
CHECK(*mres == false); // For null.qubit, measurement result is always 0 (false)
45+
46+
__catalyst__rt__device_release();
47+
__catalyst__rt__finalize();
48+
}

0 commit comments

Comments
 (0)