Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🚸 Improved error reporting in HSF and Path Simulator #431

Merged
merged 5 commits into from
Sep 12, 2024
Merged
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
4 changes: 2 additions & 2 deletions cmake/ExternalDependencies.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ endif()
# cmake-format: off
set(MQT_CORE_VERSION 2.6.1
CACHE STRING "MQT Core version")
set(MQT_CORE_REV "9be91674dddcf1f73143d9509e8e4ad806f47592"
set(MQT_CORE_REV "cd2e7678aac769c986c6ee1db76c4e03aea053a5"
CACHE STRING "MQT Core identifier (tag, branch or commit hash)")
set(MQT_CORE_REPO_OWNER "cda-tum"
CACHE STRING "MQT Core repository owner (change when using a fork)")
Expand Down Expand Up @@ -75,7 +75,7 @@ set(TF_BUILD_PROFILER
OFF
CACHE INTERNAL "")
set(TF_VERSION
3.6.0
3.7.0
CACHE STRING "Taskflow version")
set(TF_URL https://github.com/taskflow/taskflow/archive/refs/tags/v${TF_VERSION}.tar.gz)
if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.24)
Expand Down
2 changes: 2 additions & 0 deletions include/HybridSchrodingerFeynmanSimulator.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ class HybridSchrodingerFeynmanSimulator : public CircuitSimulator<Config> {
const std::size_t nthreads_ = 2)
: CircuitSimulator<Config>(std::move(qc_), approxInfo_), mode(mode_),
nthreads(nthreads_) {
qc::CircuitOptimizer::flattenOperations(*(CircuitSimulator<Config>::qc));
// remove final measurements
qc::CircuitOptimizer::removeFinalMeasurements(
*(CircuitSimulator<Config>::qc));
Expand All @@ -48,6 +49,7 @@ class HybridSchrodingerFeynmanSimulator : public CircuitSimulator<Config> {
: CircuitSimulator<Config>(std::move(qc_), approxInfo_, seed_),
mode(mode_), nthreads(nthreads_) {
// remove final measurements
qc::CircuitOptimizer::flattenOperations(*(CircuitSimulator<Config>::qc));
qc::CircuitOptimizer::removeFinalMeasurements(
*(CircuitSimulator<Config>::qc));
}
Expand Down
191 changes: 110 additions & 81 deletions src/HybridSchrodingerFeynmanSimulator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,27 +40,52 @@ HybridSchrodingerFeynmanSimulator<Config>::getNDecisions(qc::Qubit splitQubit) {
if (op->getType() == qc::Barrier) {
continue;
}
if (op->isStandardOperation()) {
bool targetInLowerSlice = false;
bool targetInUpperSlice = false;
bool controlInLowerSlice = false;
bool controlInUpperSlice = false;
for (const auto& target : op->getTargets()) {
targetInLowerSlice = targetInLowerSlice || target < splitQubit;
targetInUpperSlice = targetInUpperSlice || target >= splitQubit;

assert(op->isStandardOperation());

bool targetInLowerSlice = false;
bool targetInUpperSlice = false;
bool controlInLowerSlice = false;
size_t nControlsInLowerSlice = 0;
bool controlInUpperSlice = false;
size_t nControlsInUpperSlice = 0;
for (const auto& target : op->getTargets()) {
targetInLowerSlice = targetInLowerSlice || target < splitQubit;
targetInUpperSlice = targetInUpperSlice || target >= splitQubit;
}
for (const auto& control : op->getControls()) {
if (control.qubit < splitQubit) {
controlInLowerSlice = true;
nControlsInLowerSlice++;
} else {
controlInUpperSlice = true;
nControlsInUpperSlice++;
}
for (const auto& control : op->getControls()) {
controlInLowerSlice = controlInLowerSlice || control.qubit < splitQubit;
controlInUpperSlice =
controlInUpperSlice || control.qubit >= splitQubit;
}

if (targetInLowerSlice && targetInUpperSlice) {
throw std::invalid_argument(
"Multiple targets spread across the cut through the circuit are not "
"supported at the moment as this would require actually computing "
"the Schmidt decomposition of the gate being cut.");
}

if (targetInLowerSlice && controlInUpperSlice) {
if (nControlsInUpperSlice > 1) {
throw std::invalid_argument(
"Multiple controls in the control part of the gate being cut are "
"not supported at the moment as this would require actually "
"computing the Schmidt decomposition of the gate being cut.");
}
if ((targetInLowerSlice && controlInUpperSlice) ||
(targetInUpperSlice && controlInLowerSlice)) {
ndecisions++;
++ndecisions;
} else if (targetInUpperSlice && controlInLowerSlice) {
if (nControlsInLowerSlice > 1) {
throw std::invalid_argument(
"Multiple controls in the control part of the gate being cut are "
"not supported at the moment as this would require actually "
"computing the Schmidt decomposition of the gate being cut.");
}
} else {
throw std::invalid_argument(
"Only StandardOperations are supported for now.");
++ndecisions;
}
}
return ndecisions;
Expand All @@ -77,11 +102,10 @@ qc::VectorDD HybridSchrodingerFeynmanSimulator<Config>::simulateSlicing(
controls);

for (const auto& op : *CircuitSimulator<Config>::qc) {
if (op->isUnitary()) {
[[maybe_unused]] auto l = lower.apply(sliceDD, op);
[[maybe_unused]] auto u = upper.apply(sliceDD, op);
assert(l == u);
}
assert(op->isUnitary());
[[maybe_unused]] auto l = lower.apply(sliceDD, op);
[[maybe_unused]] auto u = upper.apply(sliceDD, op);
assert(l == u);
sliceDD->garbageCollect();
}

Expand All @@ -97,76 +121,67 @@ bool HybridSchrodingerFeynmanSimulator<Config>::Slice::apply(
std::unique_ptr<dd::Package<Config>>& sliceDD,
const std::unique_ptr<qc::Operation>& op) {
bool isSplitOp = false;
if (dynamic_cast<qc::StandardOperation*>(op.get()) !=
nullptr) { // TODO change control and target if wrong direction
qc::Targets opTargets{};
qc::Controls opControls{};

// check targets
bool targetInSplit = false;
bool targetInOtherSplit = false;
for (const auto& target : op->getTargets()) {
if (start <= target && target <= end) {
opTargets.push_back(target);
targetInSplit = true;
} else {
targetInOtherSplit = true;
}
}

if (targetInSplit && targetInOtherSplit && !op->getControls().empty()) {
throw std::invalid_argument("Multiple Targets that are in different "
"slices are not supported at the moment");
assert(op->isStandardOperation());
qc::Targets opTargets{};
qc::Controls opControls{};

// check targets
bool targetInSplit = false;
bool targetInOtherSplit = false;
for (const auto& target : op->getTargets()) {
if (start <= target && target <= end) {
opTargets.emplace_back(target);
targetInSplit = true;
} else {
targetInOtherSplit = true;
}
}

// check controls
for (const auto& control : op->getControls()) {
if (start <= control.qubit && control.qubit <= end) {
opControls.emplace(control.qubit, control.type);
} else { // other controls are set to the corresponding value
if (targetInSplit) {
isSplitOp = true;
const bool nextControl = getNextControl();
if ((control.type == qc::Control::Type::Pos &&
!nextControl) || // break if control is not activated
(control.type == qc::Control::Type::Neg && nextControl)) {
nDecisionsExecuted++;
return true;
}
// Ensured in the getNDecisions function
assert(!(targetInSplit && targetInOtherSplit));

// check controls
for (const auto& control : op->getControls()) {
if (start <= control.qubit && control.qubit <= end) {
opControls.emplace(control);
} else { // other controls are set to the corresponding value
if (targetInSplit) {
isSplitOp = true;
const bool nextControl = getNextControl();
// break if control is not activated
if ((control.type == qc::Control::Type::Pos && !nextControl) ||
(control.type == qc::Control::Type::Neg && nextControl)) {
nDecisionsExecuted++;
return true;
}
}
}
}

if (targetInOtherSplit && !opControls.empty()) { // control slice for split
if (opControls.size() > 1) {
throw std::invalid_argument(
"Multiple controls in control slice of operation are not supported "
"at the moment");
}
if (targetInOtherSplit && !opControls.empty()) { // control slice for split
// Ensured in the getNDecisions function
assert(opControls.size() == 1);

isSplitOp = true;
const bool control = getNextControl();
for (const auto& c : opControls) {
auto tmp = edge;
edge = sliceDD->deleteEdge(
edge, static_cast<dd::Qubit>(c.qubit),
control != (c.type == qc::Control::Type::Neg) ? 0 : 1);
// TODO incref and decref could be integrated in delete edge
sliceDD->incRef(edge);
sliceDD->decRef(tmp);
}
} else if (targetInSplit) { // target slice for split or operation in split
const auto& param = op->getParameter();
qc::StandardOperation newOp(opControls, opTargets, op->getType(), param);
isSplitOp = true;
const bool control = getNextControl();
for (const auto& c : opControls) {
auto tmp = edge;
edge = sliceDD->multiply(dd::getDD(&newOp, *sliceDD), edge);
edge = sliceDD->deleteEdge(
edge, static_cast<dd::Qubit>(c.qubit),
control != (c.type == qc::Control::Type::Neg) ? 0 : 1);
// TODO incref and decref could be integrated in delete edge
sliceDD->incRef(edge);
sliceDD->decRef(tmp);
}
} else {
throw std::invalid_argument(
"Only StandardOperations are supported for now.");
} else if (targetInSplit) { // target slice for split or operation in split
const auto& param = op->getParameter();
qc::StandardOperation newOp(opControls, opTargets, op->getType(), param);
auto tmp = edge;
edge = sliceDD->multiply(dd::getDD(&newOp, *sliceDD), edge);
sliceDD->incRef(edge);
sliceDD->decRef(tmp);
}

if (isSplitOp) {
nDecisionsExecuted++;
}
Expand All @@ -176,6 +191,20 @@ bool HybridSchrodingerFeynmanSimulator<Config>::Slice::apply(
template <class Config>
std::map<std::string, std::size_t>
HybridSchrodingerFeynmanSimulator<Config>::simulate(std::size_t shots) {
if (CircuitSimulator<Config>::qc->isDynamic()) {
throw std::invalid_argument(
"Dynamic quantum circuits containing mid-circuit measurements, resets, "
"or classical control flow are not supported by this simulator.");
}

for (const auto& op : *CircuitSimulator<Config>::qc) {
if (!op->isStandardOperation()) {
throw std::invalid_argument("This simulator only supports regular gates "
"(`StandardOperation`). \"" +
op->getName() + "\" is not supported.");
}
}

auto nqubits = CircuitSimulator<Config>::getNumberOfQubits();
auto splitQubit = static_cast<qc::Qubit>(nqubits / 2);
if (mode == Mode::DD) {
Expand Down
6 changes: 6 additions & 0 deletions src/PathSimulator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,12 @@ PathSimulator<Config>::SimulationPath::SimulationPath(
template <class Config>
std::map<std::string, std::size_t>
PathSimulator<Config>::simulate(std::size_t shots) {
if (CircuitSimulator<Config>::qc->isDynamic()) {
throw std::invalid_argument(
"Dynamic quantum circuits containing mid-circuit measurements, resets, "
"or classical control flow are not supported by this simulator.");
}

// build task graph from simulation path
constructTaskGraph();
/// Enable the following statements to generate a .dot file of the resulting
Expand Down
51 changes: 39 additions & 12 deletions test/test_hybridsim.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include "CircuitSimulator.hpp"
#include "HybridSchrodingerFeynmanSimulator.hpp"
#include "ir/QuantumComputation.hpp"
#include "ir/operations/OpType.hpp"

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

TEST(HybridSimTest, NonStandardOperation) {
auto quantumComputation = std::make_unique<qc::QuantumComputation>(1, 1);
quantumComputation->h(0);
quantumComputation->measure(0, 0);
quantumComputation->barrier(0);
quantumComputation->h(0);
quantumComputation->measure(0, 0);

HybridSchrodingerFeynmanSimulator ddsim(std::move(quantumComputation));
EXPECT_THROW(ddsim.simulate(0), std::invalid_argument);
}

TEST(HybridSimTest, TooManyQubitsForVectorTest) {
auto qc = std::make_unique<qc::QuantumComputation>(61);
const HybridSchrodingerFeynmanSimulator<> ddsim(
Expand Down Expand Up @@ -204,3 +193,41 @@ TEST(HybridSimTest, RegressionTestAmplitudeModeMoreChunksAsThreads) {
EXPECT_EQ(result.begin()->first, "10");
EXPECT_EQ(result.begin()->second, 1024U);
}

TEST(HybridSimTest, DynamicCircuitSupport) {
auto qc = std::make_unique<qc::QuantumComputation>(1, 1);
qc->h(0);
qc->measure(0, 0);
qc->classicControlled(qc::X, 0, {0, 1}, 1);
std::cout << *qc << "\n";

HybridSchrodingerFeynmanSimulator sim(std::move(qc));
EXPECT_THROW(sim.simulate(1024), std::invalid_argument);
}

TEST(HybridSimTest, TwoTargetGateSupport) {
auto qc = std::make_unique<qc::QuantumComputation>(2);
qc->rzz(1., 0, 1);
std::cout << *qc << "\n";

HybridSchrodingerFeynmanSimulator sim(std::move(qc));
EXPECT_THROW(sim.simulate(1024), std::invalid_argument);
}

TEST(HybridSimTest, TwoControlGateSupportLowerHalf) {
auto qc = std::make_unique<qc::QuantumComputation>(4);
qc->mcx({0, 1}, 2);
std::cout << *qc << "\n";

HybridSchrodingerFeynmanSimulator sim(std::move(qc));
EXPECT_THROW(sim.simulate(1024), std::invalid_argument);
}

TEST(HybridSimTest, TwoControlGateSupportUpperHalf) {
auto qc = std::make_unique<qc::QuantumComputation>(4);
qc->mcx({3, 2}, 1);
std::cout << *qc << "\n";

HybridSchrodingerFeynmanSimulator sim(std::move(qc));
EXPECT_THROW(sim.simulate(1024), std::invalid_argument);
}
12 changes: 12 additions & 0 deletions test/test_path_sim.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include "dd/DDDefinitions.hpp"
#include "dd/Export.hpp"
#include "ir/QuantumComputation.hpp"
#include "ir/operations/OpType.hpp"

#include <complex>
#include <gtest/gtest.h>
Expand Down Expand Up @@ -295,3 +296,14 @@ TEST(TaskBasedSimTest, SimpleCircuitGatecostConfigurationObject) {
std::cout << state << ": " << count << "\n";
}
}

TEST(TaskBasedSimTest, DynamicCircuitSupport) {
auto qc = std::make_unique<qc::QuantumComputation>(1, 1);
qc->h(0);
qc->measure(0, 0);
qc->classicControlled(qc::X, 0, {0, 1}, 1);
std::cout << *qc << "\n";

PathSimulator sim(std::move(qc));
EXPECT_THROW(sim.simulate(1024), std::invalid_argument);
}
Loading