Skip to content

Commit 189baa0

Browse files
authored
🚸 Improved error reporting in HSF and Path Simulator (#431)
## Description This PR improves the error management of the HSF and the Path Simulator and makes them report a little more user-friendly error messages if certain constructs of a circuit are not supported by the simulators. Fixes #251 and #269 ## Checklist: <!--- This checklist serves as a reminder of a couple of things that ensure your pull request will be merged swiftly. --> - [x] The pull request only contains commits that are related to it. - [x] I have added appropriate tests and documentation. - [x] I have made sure that all CI jobs on GitHub pass. - [x] The pull request introduces no new warnings and follows the project's style guidelines.
2 parents 4b51d54 + 6d6c716 commit 189baa0

6 files changed

+171
-95
lines changed

cmake/ExternalDependencies.cmake

+2-2
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ endif()
2222
# cmake-format: off
2323
set(MQT_CORE_VERSION 2.6.1
2424
CACHE STRING "MQT Core version")
25-
set(MQT_CORE_REV "9be91674dddcf1f73143d9509e8e4ad806f47592"
25+
set(MQT_CORE_REV "cd2e7678aac769c986c6ee1db76c4e03aea053a5"
2626
CACHE STRING "MQT Core identifier (tag, branch or commit hash)")
2727
set(MQT_CORE_REPO_OWNER "cda-tum"
2828
CACHE STRING "MQT Core repository owner (change when using a fork)")
@@ -75,7 +75,7 @@ set(TF_BUILD_PROFILER
7575
OFF
7676
CACHE INTERNAL "")
7777
set(TF_VERSION
78-
3.6.0
78+
3.7.0
7979
CACHE STRING "Taskflow version")
8080
set(TF_URL https://github.com/taskflow/taskflow/archive/refs/tags/v${TF_VERSION}.tar.gz)
8181
if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.24)

include/HybridSchrodingerFeynmanSimulator.hpp

+2
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ class HybridSchrodingerFeynmanSimulator : public CircuitSimulator<Config> {
3030
const std::size_t nthreads_ = 2)
3131
: CircuitSimulator<Config>(std::move(qc_), approxInfo_), mode(mode_),
3232
nthreads(nthreads_) {
33+
qc::CircuitOptimizer::flattenOperations(*(CircuitSimulator<Config>::qc));
3334
// remove final measurements
3435
qc::CircuitOptimizer::removeFinalMeasurements(
3536
*(CircuitSimulator<Config>::qc));
@@ -48,6 +49,7 @@ class HybridSchrodingerFeynmanSimulator : public CircuitSimulator<Config> {
4849
: CircuitSimulator<Config>(std::move(qc_), approxInfo_, seed_),
4950
mode(mode_), nthreads(nthreads_) {
5051
// remove final measurements
52+
qc::CircuitOptimizer::flattenOperations(*(CircuitSimulator<Config>::qc));
5153
qc::CircuitOptimizer::removeFinalMeasurements(
5254
*(CircuitSimulator<Config>::qc));
5355
}

src/HybridSchrodingerFeynmanSimulator.cpp

+110-81
Original file line numberDiff line numberDiff line change
@@ -40,27 +40,52 @@ HybridSchrodingerFeynmanSimulator<Config>::getNDecisions(qc::Qubit splitQubit) {
4040
if (op->getType() == qc::Barrier) {
4141
continue;
4242
}
43-
if (op->isStandardOperation()) {
44-
bool targetInLowerSlice = false;
45-
bool targetInUpperSlice = false;
46-
bool controlInLowerSlice = false;
47-
bool controlInUpperSlice = false;
48-
for (const auto& target : op->getTargets()) {
49-
targetInLowerSlice = targetInLowerSlice || target < splitQubit;
50-
targetInUpperSlice = targetInUpperSlice || target >= splitQubit;
43+
44+
assert(op->isStandardOperation());
45+
46+
bool targetInLowerSlice = false;
47+
bool targetInUpperSlice = false;
48+
bool controlInLowerSlice = false;
49+
size_t nControlsInLowerSlice = 0;
50+
bool controlInUpperSlice = false;
51+
size_t nControlsInUpperSlice = 0;
52+
for (const auto& target : op->getTargets()) {
53+
targetInLowerSlice = targetInLowerSlice || target < splitQubit;
54+
targetInUpperSlice = targetInUpperSlice || target >= splitQubit;
55+
}
56+
for (const auto& control : op->getControls()) {
57+
if (control.qubit < splitQubit) {
58+
controlInLowerSlice = true;
59+
nControlsInLowerSlice++;
60+
} else {
61+
controlInUpperSlice = true;
62+
nControlsInUpperSlice++;
5163
}
52-
for (const auto& control : op->getControls()) {
53-
controlInLowerSlice = controlInLowerSlice || control.qubit < splitQubit;
54-
controlInUpperSlice =
55-
controlInUpperSlice || control.qubit >= splitQubit;
64+
}
65+
66+
if (targetInLowerSlice && targetInUpperSlice) {
67+
throw std::invalid_argument(
68+
"Multiple targets spread across the cut through the circuit are not "
69+
"supported at the moment as this would require actually computing "
70+
"the Schmidt decomposition of the gate being cut.");
71+
}
72+
73+
if (targetInLowerSlice && controlInUpperSlice) {
74+
if (nControlsInUpperSlice > 1) {
75+
throw std::invalid_argument(
76+
"Multiple controls in the control part of the gate being cut are "
77+
"not supported at the moment as this would require actually "
78+
"computing the Schmidt decomposition of the gate being cut.");
5679
}
57-
if ((targetInLowerSlice && controlInUpperSlice) ||
58-
(targetInUpperSlice && controlInLowerSlice)) {
59-
ndecisions++;
80+
++ndecisions;
81+
} else if (targetInUpperSlice && controlInLowerSlice) {
82+
if (nControlsInLowerSlice > 1) {
83+
throw std::invalid_argument(
84+
"Multiple controls in the control part of the gate being cut are "
85+
"not supported at the moment as this would require actually "
86+
"computing the Schmidt decomposition of the gate being cut.");
6087
}
61-
} else {
62-
throw std::invalid_argument(
63-
"Only StandardOperations are supported for now.");
88+
++ndecisions;
6489
}
6590
}
6691
return ndecisions;
@@ -77,11 +102,10 @@ qc::VectorDD HybridSchrodingerFeynmanSimulator<Config>::simulateSlicing(
77102
controls);
78103

79104
for (const auto& op : *CircuitSimulator<Config>::qc) {
80-
if (op->isUnitary()) {
81-
[[maybe_unused]] auto l = lower.apply(sliceDD, op);
82-
[[maybe_unused]] auto u = upper.apply(sliceDD, op);
83-
assert(l == u);
84-
}
105+
assert(op->isUnitary());
106+
[[maybe_unused]] auto l = lower.apply(sliceDD, op);
107+
[[maybe_unused]] auto u = upper.apply(sliceDD, op);
108+
assert(l == u);
85109
sliceDD->garbageCollect();
86110
}
87111

@@ -97,76 +121,67 @@ bool HybridSchrodingerFeynmanSimulator<Config>::Slice::apply(
97121
std::unique_ptr<dd::Package<Config>>& sliceDD,
98122
const std::unique_ptr<qc::Operation>& op) {
99123
bool isSplitOp = false;
100-
if (dynamic_cast<qc::StandardOperation*>(op.get()) !=
101-
nullptr) { // TODO change control and target if wrong direction
102-
qc::Targets opTargets{};
103-
qc::Controls opControls{};
104-
105-
// check targets
106-
bool targetInSplit = false;
107-
bool targetInOtherSplit = false;
108-
for (const auto& target : op->getTargets()) {
109-
if (start <= target && target <= end) {
110-
opTargets.push_back(target);
111-
targetInSplit = true;
112-
} else {
113-
targetInOtherSplit = true;
114-
}
115-
}
116-
117-
if (targetInSplit && targetInOtherSplit && !op->getControls().empty()) {
118-
throw std::invalid_argument("Multiple Targets that are in different "
119-
"slices are not supported at the moment");
124+
assert(op->isStandardOperation());
125+
qc::Targets opTargets{};
126+
qc::Controls opControls{};
127+
128+
// check targets
129+
bool targetInSplit = false;
130+
bool targetInOtherSplit = false;
131+
for (const auto& target : op->getTargets()) {
132+
if (start <= target && target <= end) {
133+
opTargets.emplace_back(target);
134+
targetInSplit = true;
135+
} else {
136+
targetInOtherSplit = true;
120137
}
138+
}
121139

122-
// check controls
123-
for (const auto& control : op->getControls()) {
124-
if (start <= control.qubit && control.qubit <= end) {
125-
opControls.emplace(control.qubit, control.type);
126-
} else { // other controls are set to the corresponding value
127-
if (targetInSplit) {
128-
isSplitOp = true;
129-
const bool nextControl = getNextControl();
130-
if ((control.type == qc::Control::Type::Pos &&
131-
!nextControl) || // break if control is not activated
132-
(control.type == qc::Control::Type::Neg && nextControl)) {
133-
nDecisionsExecuted++;
134-
return true;
135-
}
140+
// Ensured in the getNDecisions function
141+
assert(!(targetInSplit && targetInOtherSplit));
142+
143+
// check controls
144+
for (const auto& control : op->getControls()) {
145+
if (start <= control.qubit && control.qubit <= end) {
146+
opControls.emplace(control);
147+
} else { // other controls are set to the corresponding value
148+
if (targetInSplit) {
149+
isSplitOp = true;
150+
const bool nextControl = getNextControl();
151+
// break if control is not activated
152+
if ((control.type == qc::Control::Type::Pos && !nextControl) ||
153+
(control.type == qc::Control::Type::Neg && nextControl)) {
154+
nDecisionsExecuted++;
155+
return true;
136156
}
137157
}
138158
}
159+
}
139160

140-
if (targetInOtherSplit && !opControls.empty()) { // control slice for split
141-
if (opControls.size() > 1) {
142-
throw std::invalid_argument(
143-
"Multiple controls in control slice of operation are not supported "
144-
"at the moment");
145-
}
161+
if (targetInOtherSplit && !opControls.empty()) { // control slice for split
162+
// Ensured in the getNDecisions function
163+
assert(opControls.size() == 1);
146164

147-
isSplitOp = true;
148-
const bool control = getNextControl();
149-
for (const auto& c : opControls) {
150-
auto tmp = edge;
151-
edge = sliceDD->deleteEdge(
152-
edge, static_cast<dd::Qubit>(c.qubit),
153-
control != (c.type == qc::Control::Type::Neg) ? 0 : 1);
154-
// TODO incref and decref could be integrated in delete edge
155-
sliceDD->incRef(edge);
156-
sliceDD->decRef(tmp);
157-
}
158-
} else if (targetInSplit) { // target slice for split or operation in split
159-
const auto& param = op->getParameter();
160-
qc::StandardOperation newOp(opControls, opTargets, op->getType(), param);
165+
isSplitOp = true;
166+
const bool control = getNextControl();
167+
for (const auto& c : opControls) {
161168
auto tmp = edge;
162-
edge = sliceDD->multiply(dd::getDD(&newOp, *sliceDD), edge);
169+
edge = sliceDD->deleteEdge(
170+
edge, static_cast<dd::Qubit>(c.qubit),
171+
control != (c.type == qc::Control::Type::Neg) ? 0 : 1);
172+
// TODO incref and decref could be integrated in delete edge
163173
sliceDD->incRef(edge);
164174
sliceDD->decRef(tmp);
165175
}
166-
} else {
167-
throw std::invalid_argument(
168-
"Only StandardOperations are supported for now.");
176+
} else if (targetInSplit) { // target slice for split or operation in split
177+
const auto& param = op->getParameter();
178+
qc::StandardOperation newOp(opControls, opTargets, op->getType(), param);
179+
auto tmp = edge;
180+
edge = sliceDD->multiply(dd::getDD(&newOp, *sliceDD), edge);
181+
sliceDD->incRef(edge);
182+
sliceDD->decRef(tmp);
169183
}
184+
170185
if (isSplitOp) {
171186
nDecisionsExecuted++;
172187
}
@@ -176,6 +191,20 @@ bool HybridSchrodingerFeynmanSimulator<Config>::Slice::apply(
176191
template <class Config>
177192
std::map<std::string, std::size_t>
178193
HybridSchrodingerFeynmanSimulator<Config>::simulate(std::size_t shots) {
194+
if (CircuitSimulator<Config>::qc->isDynamic()) {
195+
throw std::invalid_argument(
196+
"Dynamic quantum circuits containing mid-circuit measurements, resets, "
197+
"or classical control flow are not supported by this simulator.");
198+
}
199+
200+
for (const auto& op : *CircuitSimulator<Config>::qc) {
201+
if (!op->isStandardOperation()) {
202+
throw std::invalid_argument("This simulator only supports regular gates "
203+
"(`StandardOperation`). \"" +
204+
op->getName() + "\" is not supported.");
205+
}
206+
}
207+
179208
auto nqubits = CircuitSimulator<Config>::getNumberOfQubits();
180209
auto splitQubit = static_cast<qc::Qubit>(nqubits / 2);
181210
if (mode == Mode::DD) {

src/PathSimulator.cpp

+6
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,12 @@ PathSimulator<Config>::SimulationPath::SimulationPath(
142142
template <class Config>
143143
std::map<std::string, std::size_t>
144144
PathSimulator<Config>::simulate(std::size_t shots) {
145+
if (CircuitSimulator<Config>::qc->isDynamic()) {
146+
throw std::invalid_argument(
147+
"Dynamic quantum circuits containing mid-circuit measurements, resets, "
148+
"or classical control flow are not supported by this simulator.");
149+
}
150+
145151
// build task graph from simulation path
146152
constructTaskGraph();
147153
/// Enable the following statements to generate a .dot file of the resulting

test/test_hybridsim.cpp

+39-12
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include "CircuitSimulator.hpp"
22
#include "HybridSchrodingerFeynmanSimulator.hpp"
33
#include "ir/QuantumComputation.hpp"
4+
#include "ir/operations/OpType.hpp"
45

56
#include <cstdlib>
67
#include <gtest/gtest.h>
@@ -81,18 +82,6 @@ TEST(HybridSimTest, TrivialParallelAmplitude) {
8182
EXPECT_NEAR(static_cast<double>(it->second), 2048, 128);
8283
}
8384

84-
TEST(HybridSimTest, NonStandardOperation) {
85-
auto quantumComputation = std::make_unique<qc::QuantumComputation>(1, 1);
86-
quantumComputation->h(0);
87-
quantumComputation->measure(0, 0);
88-
quantumComputation->barrier(0);
89-
quantumComputation->h(0);
90-
quantumComputation->measure(0, 0);
91-
92-
HybridSchrodingerFeynmanSimulator ddsim(std::move(quantumComputation));
93-
EXPECT_THROW(ddsim.simulate(0), std::invalid_argument);
94-
}
95-
9685
TEST(HybridSimTest, TooManyQubitsForVectorTest) {
9786
auto qc = std::make_unique<qc::QuantumComputation>(61);
9887
const HybridSchrodingerFeynmanSimulator<> ddsim(
@@ -204,3 +193,41 @@ TEST(HybridSimTest, RegressionTestAmplitudeModeMoreChunksAsThreads) {
204193
EXPECT_EQ(result.begin()->first, "10");
205194
EXPECT_EQ(result.begin()->second, 1024U);
206195
}
196+
197+
TEST(HybridSimTest, DynamicCircuitSupport) {
198+
auto qc = std::make_unique<qc::QuantumComputation>(1, 1);
199+
qc->h(0);
200+
qc->measure(0, 0);
201+
qc->classicControlled(qc::X, 0, {0, 1}, 1);
202+
std::cout << *qc << "\n";
203+
204+
HybridSchrodingerFeynmanSimulator sim(std::move(qc));
205+
EXPECT_THROW(sim.simulate(1024), std::invalid_argument);
206+
}
207+
208+
TEST(HybridSimTest, TwoTargetGateSupport) {
209+
auto qc = std::make_unique<qc::QuantumComputation>(2);
210+
qc->rzz(1., 0, 1);
211+
std::cout << *qc << "\n";
212+
213+
HybridSchrodingerFeynmanSimulator sim(std::move(qc));
214+
EXPECT_THROW(sim.simulate(1024), std::invalid_argument);
215+
}
216+
217+
TEST(HybridSimTest, TwoControlGateSupportLowerHalf) {
218+
auto qc = std::make_unique<qc::QuantumComputation>(4);
219+
qc->mcx({0, 1}, 2);
220+
std::cout << *qc << "\n";
221+
222+
HybridSchrodingerFeynmanSimulator sim(std::move(qc));
223+
EXPECT_THROW(sim.simulate(1024), std::invalid_argument);
224+
}
225+
226+
TEST(HybridSimTest, TwoControlGateSupportUpperHalf) {
227+
auto qc = std::make_unique<qc::QuantumComputation>(4);
228+
qc->mcx({3, 2}, 1);
229+
std::cout << *qc << "\n";
230+
231+
HybridSchrodingerFeynmanSimulator sim(std::move(qc));
232+
EXPECT_THROW(sim.simulate(1024), std::invalid_argument);
233+
}

test/test_path_sim.cpp

+12
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include "dd/DDDefinitions.hpp"
44
#include "dd/Export.hpp"
55
#include "ir/QuantumComputation.hpp"
6+
#include "ir/operations/OpType.hpp"
67

78
#include <complex>
89
#include <gtest/gtest.h>
@@ -295,3 +296,14 @@ TEST(TaskBasedSimTest, SimpleCircuitGatecostConfigurationObject) {
295296
std::cout << state << ": " << count << "\n";
296297
}
297298
}
299+
300+
TEST(TaskBasedSimTest, DynamicCircuitSupport) {
301+
auto qc = std::make_unique<qc::QuantumComputation>(1, 1);
302+
qc->h(0);
303+
qc->measure(0, 0);
304+
qc->classicControlled(qc::X, 0, {0, 1}, 1);
305+
std::cout << *qc << "\n";
306+
307+
PathSimulator sim(std::move(qc));
308+
EXPECT_THROW(sim.simulate(1024), std::invalid_argument);
309+
}

0 commit comments

Comments
 (0)