diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cce5165..a04e5ff 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -45,7 +45,7 @@ jobs: name: Coverage run: | cmake -S . -B buildCov -DCMAKE_BUILD_TYPE=Debug -DBUILD_DD_EVAL_TESTS=ON -DENABLE_COVERAGE=ON - cmake --build buildCov --config Debug --target DDEval_test + cmake --build buildCov --config Debug ctest -C Debug --output-on-failure --test-dir buildCov --repeat until-pass:3 --timeout 300 - if: runner.os == 'Linux' name: Upload coverage to Codecov diff --git a/.github/workflows/cpp-linter.yml b/.github/workflows/cpp-linter.yml index 36f8545..3ef8ecb 100644 --- a/.github/workflows/cpp-linter.yml +++ b/.github/workflows/cpp-linter.yml @@ -2,7 +2,6 @@ name: cpp-linter on: pull_request: - merge_group: push: branches: - main @@ -12,6 +11,9 @@ concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} cancel-in-progress: true +env: + clang-version: 16 + jobs: cpp-linter: runs-on: ubuntu-latest @@ -19,21 +21,42 @@ jobs: - uses: actions/checkout@v3 with: submodules: recursive + + - name: Install clang-${{ env.clang-version }} + run: | + sudo apt-get update + wget https://apt.llvm.org/llvm.sh -O ${{ runner.temp }}/llvm_install.sh + chmod +x ${{ runner.temp }}/llvm_install.sh + if sudo ${{ runner.temp }}/llvm_install.sh ${{ env.clang-version }}; then + sudo apt-get install -y clang-format-${{ env.clang-version }} clang-tidy-${{ env.clang-version }} + fi + echo "CC=clang-${{ env.clang-version }}" >> $GITHUB_ENV + echo "CXX=clang++-${{ env.clang-version }}" >> $GITHUB_ENV + - name: Generate compilation database - run: CC=clang-14 CXX=clang++-14 cmake -S . -B build -DBUILD_DD_EVAL_TESTS=ON + run: | + echo $CC + echo $CXX + $CC --version + $CXX --version + cmake -S . -B build -DBUILD_DD_EVAL_TESTS=ON + - name: Run cpp-linter + uses: cpp-linter/cpp-linter-action@v2 id: linter env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - pipx run cpp-linter \ - --version=14 \ - --style="" \ - --tidy-checks="" \ - --thread-comments=true \ - --files-changed-only=true \ - --ignore="build" \ - --database=build + with: + style: "" + tidy-checks: "" + version: 16 + ignore: build + thread-comments: true + step-summary: true + database: "build" + extra-args: -std=c++17 + files-changed-only: true + - name: Fail if linter found errors if: steps.linter.outputs.checks-failed > 0 run: echo "Linter found errors" && exit 1 diff --git a/include/Executor.hpp b/include/Executor.hpp new file mode 100644 index 0000000..4879301 --- /dev/null +++ b/include/Executor.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include "Task.hpp" + +#include +#include + +using json = nlohmann::json; + +template class Executor { + static_assert(std::is_base_of_v); + +public: + virtual ~Executor() = default; + + virtual json execute(const T& task) = 0; + + [[nodiscard]] virtual std::string getIdentifier() const = 0; +}; diff --git a/include/Task.hpp b/include/Task.hpp new file mode 100644 index 0000000..bb94571 --- /dev/null +++ b/include/Task.hpp @@ -0,0 +1,10 @@ +#pragma once + +#include + +class Task { +public: + virtual ~Task() = default; + + [[nodiscard]] virtual std::string getIdentifier() const = 0; +}; diff --git a/include/executors/AlternatingVerificationExecutor.hpp b/include/executors/AlternatingVerificationExecutor.hpp new file mode 100644 index 0000000..f133246 --- /dev/null +++ b/include/executors/AlternatingVerificationExecutor.hpp @@ -0,0 +1,13 @@ +#pragma once + +#include "Executor.hpp" +#include "tasks/VerificationTask.hpp" + +class AlternatingVerificationExecutor : public Executor { +public: + json execute(const VerificationTask& task) override; + + [[nodiscard]] std::string getIdentifier() const override { + return "alternating_verification"; + } +}; diff --git a/include/executors/CircuitSimulatorExecutor.hpp b/include/executors/CircuitSimulatorExecutor.hpp new file mode 100644 index 0000000..10987af --- /dev/null +++ b/include/executors/CircuitSimulatorExecutor.hpp @@ -0,0 +1,13 @@ +#pragma once + +#include "Executor.hpp" +#include "tasks/SimulationTask.hpp" + +class CircuitSimulatorExecutor : public Executor { +public: + json execute(const SimulationTask& task) override; + + [[nodiscard]] std::string getIdentifier() const override { + return "circuit_simulator"; + }; +}; diff --git a/include/tasks/SimulationTask.hpp b/include/tasks/SimulationTask.hpp new file mode 100644 index 0000000..17c6c36 --- /dev/null +++ b/include/tasks/SimulationTask.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include "Task.hpp" + +#include + +namespace qc { +class QuantumComputation; +} + +class SimulationTask : public Task { +public: + explicit SimulationTask() = default; + explicit SimulationTask(std::unique_ptr circ); + + [[nodiscard]] std::string getIdentifier() const override; + + [[nodiscard]] const std::unique_ptr& getQc() const { + return qc; + }; + +protected: + std::unique_ptr qc; +}; diff --git a/include/tasks/VerificationTask.hpp b/include/tasks/VerificationTask.hpp new file mode 100644 index 0000000..80b04d4 --- /dev/null +++ b/include/tasks/VerificationTask.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include "Task.hpp" + +#include + +namespace qc { +class QuantumComputation; +} + +class VerificationTask : public Task { +public: + explicit VerificationTask() = default; + VerificationTask(std::unique_ptr circ1, + std::unique_ptr circ2); + + [[nodiscard]] std::string getIdentifier() const override; + + [[nodiscard]] const std::unique_ptr& getQc1() const { + return qc1; + }; + + [[nodiscard]] const std::unique_ptr& getQc2() const { + return qc2; + }; + +protected: + std::unique_ptr qc1; + std::unique_ptr qc2; +}; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 120ed07..9dce18f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -6,26 +6,28 @@ add_subdirectory("${PROJECT_SOURCE_DIR}/extern/ddsim" "extern/ddsim" add_subdirectory("${PROJECT_SOURCE_DIR}/extern/qcec" "extern/qcec" EXCLUDE_FROM_ALL) -# add_library( ${PROJECT_NAME} ${PROJECT_SOURCE_DIR}/include/checker -# ${PROJECT_SOURCE_DIR}/include/Configuration.hpp -# ${PROJECT_SOURCE_DIR}/include/EquivalenceCriterion.hpp -# ${PROJECT_SOURCE_DIR}/include/EquivalenceCheckingManager.hpp -# ${PROJECT_SOURCE_DIR}/include/ThreadSafeQueue.hpp -# EquivalenceCheckingManager.cpp checker/EquivalenceChecker.cpp -# checker/dd/DDEquivalenceChecker.cpp checker/dd/DDConstructionChecker.cpp -# checker/dd/DDSimulationChecker.cpp checker/dd/DDAlternatingChecker.cpp -# checker/dd/applicationscheme/GateCostApplicationScheme.cpp -# checker/dd/simulation/StateGenerator.cpp checker/zx/ZXChecker.cpp) +if(NOT TARGET ${PROJECT_NAME}) + add_library( + ${PROJECT_NAME} + ${PROJECT_SOURCE_DIR}/include/Executor.hpp + ${PROJECT_SOURCE_DIR}/include/executors/CircuitSimulatorExecutor.hpp + ${PROJECT_SOURCE_DIR}/include/executors/AlternatingVerificationExecutor.hpp + ${PROJECT_SOURCE_DIR}/include/Task.hpp + ${PROJECT_SOURCE_DIR}/include/tasks/SimulationTask.hpp + ${PROJECT_SOURCE_DIR}/include/tasks/VerificationTask.hpp + executors/CircuitSimulatorExecutor.cpp + executors/AlternatingVerificationExecutor.cpp + tasks/SimulationTask.cpp + tasks/VerificationTask.cpp) -add_library(${PROJECT_NAME} INTERFACE) + # set include directories + target_include_directories( + ${PROJECT_NAME} PUBLIC ${PROJECT_SOURCE_DIR}/include + ${PROJECT_BINARY_DIR}/include) -# set include directories -target_include_directories( - ${PROJECT_NAME} INTERFACE ${PROJECT_SOURCE_DIR}/include - ${PROJECT_BINARY_DIR}/include) + # link to the MQT libraries + target_link_libraries(${PROJECT_NAME} PUBLIC MQT::ddsim MQT::qcec) -# link to the MQT libraries -target_link_libraries(${PROJECT_NAME} INTERFACE MQT::ddsim MQT::qcec) - -# add MQT alias -add_library(MQT::${PROJECT_NAME} ALIAS ${PROJECT_NAME}) + # add MQT alias + add_library(MQT::${PROJECT_NAME} ALIAS ${PROJECT_NAME}) +endif() diff --git a/src/executors/AlternatingVerificationExecutor.cpp b/src/executors/AlternatingVerificationExecutor.cpp new file mode 100644 index 0000000..1ebb051 --- /dev/null +++ b/src/executors/AlternatingVerificationExecutor.cpp @@ -0,0 +1,33 @@ +#include "executors/AlternatingVerificationExecutor.hpp" + +#include "EquivalenceCheckingManager.hpp" + +json AlternatingVerificationExecutor::execute(const VerificationTask& task) { + json result; + auto const constructionStart = std::chrono::steady_clock::now(); + + auto equivalenceCheckingManager = + std::make_unique(*task.getQc1(), + *task.getQc2()); + equivalenceCheckingManager->disableAllCheckers(); + equivalenceCheckingManager->setAlternatingChecker(true); + + auto const executionStart = std::chrono::steady_clock::now(); + + equivalenceCheckingManager->run(); + result["check_results"] = equivalenceCheckingManager->getResults().json(); + // Add memory usage + auto const executionStop = std::chrono::steady_clock::now(); + auto const constructionTime = + std::chrono::duration_cast(executionStart - + constructionStart); + auto const execTime = std::chrono::duration_cast( + executionStop - executionStart); + result["construction_time"] = constructionTime.count(); + result["execution_time"] = execTime.count(); + + result["executor"] = getIdentifier(); + result["task"] = task.getIdentifier(); + + return result; +} diff --git a/src/executors/CircuitSimulatorExecutor.cpp b/src/executors/CircuitSimulatorExecutor.cpp new file mode 100644 index 0000000..367989a --- /dev/null +++ b/src/executors/CircuitSimulatorExecutor.cpp @@ -0,0 +1,30 @@ +#include "executors/CircuitSimulatorExecutor.hpp" + +#include "CircuitSimulator.hpp" + +json CircuitSimulatorExecutor::execute(const SimulationTask& task) { + json result; + auto const constructionStart = std::chrono::steady_clock::now(); + + auto qc = std::make_unique(task.getQc()->clone()); + auto circuitSimulator = std::make_unique>(std::move(qc)); + + auto const executionStart = std::chrono::steady_clock::now(); + + result["measurement_results"] = circuitSimulator->simulate(1024U); + // Add memory usage + + auto const executionStop = std::chrono::steady_clock::now(); + auto const constructionTime = + std::chrono::duration_cast(executionStart - + constructionStart); + auto const execTime = std::chrono::duration_cast( + executionStop - executionStart); + result["construction_time"] = constructionTime.count(); + result["execution_time"] = execTime.count(); + + result["executor"] = getIdentifier(); + result["task"] = task.getIdentifier(); + + return result; +} diff --git a/src/tasks/SimulationTask.cpp b/src/tasks/SimulationTask.cpp new file mode 100644 index 0000000..48bf4dd --- /dev/null +++ b/src/tasks/SimulationTask.cpp @@ -0,0 +1,10 @@ +#include "tasks/SimulationTask.hpp" + +#include "QuantumComputation.hpp" + +SimulationTask::SimulationTask(std::unique_ptr circ) + : qc(std::move(circ)) {} + +std::string SimulationTask::getIdentifier() const { + return "sim_" + qc->getName(); +} diff --git a/src/tasks/VerificationTask.cpp b/src/tasks/VerificationTask.cpp new file mode 100644 index 0000000..a3d496e --- /dev/null +++ b/src/tasks/VerificationTask.cpp @@ -0,0 +1,12 @@ +#include "tasks/VerificationTask.hpp" + +#include "QuantumComputation.hpp" + +VerificationTask::VerificationTask( + std::unique_ptr circ1, + std::unique_ptr circ2) + : qc1(std::move(circ1)), qc2(std::move(circ2)) {} + +std::string VerificationTask::getIdentifier() const { + return "ver_" + qc1->getName() + "_" + qc2->getName(); +} diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 70d0463..6414a6c 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -9,6 +9,9 @@ if(NOT TARGET gtest OR NOT TARGET gmock) add_subdirectory("${PROJECT_SOURCE_DIR}/${GTEST_PATH}" "${GTEST_PATH}" EXCLUDE_FROM_ALL) endif() - -package_add_test(${PROJECT_NAME}_test ${PROJECT_NAME} test_ddsim_simple.cpp - test_qcec_simple.cpp) +configure_file(${CMAKE_SOURCE_DIR}/test/sim_circuits.json + ${CMAKE_CURRENT_BINARY_DIR}/sim_circuits.json COPYONLY) +configure_file(${CMAKE_SOURCE_DIR}/test/ver_circuits.json + ${CMAKE_CURRENT_BINARY_DIR}/ver_circuits.json COPYONLY) +package_add_test(${PROJECT_NAME}_sim_test ${PROJECT_NAME} test_simexec.cpp) +package_add_test(${PROJECT_NAME}_ver_test ${PROJECT_NAME} test_qcecexec.cpp) diff --git a/test/sim_circuits.json b/test/sim_circuits.json new file mode 100644 index 0000000..dd73f45 --- /dev/null +++ b/test/sim_circuits.json @@ -0,0 +1,10 @@ +[ + { + "description": "two_qubit_circuit_with_two_x_gates", + "circuit": "OPENQASM 2.0;include \"qelib1.inc\";qreg q[2];x q[0];x q[1];\n" + }, + { + "description": "two_qubit_circuit_with_h_z_controlled_x_swap", + "circuit": "OPENQASM 2.0;include \"qelib1.inc\";qreg q[2];cx q[0], q[1];h q[0];z q[0];h q[0];swap q[0], q[1];\n" + } +] diff --git a/test/test_ddsim_simple.cpp b/test/test_ddsim_simple.cpp deleted file mode 100644 index 210cce0..0000000 --- a/test/test_ddsim_simple.cpp +++ /dev/null @@ -1,20 +0,0 @@ -// A simple test file using googletest to ensure DDSIM is working properly - -#include "CircuitSimulator.hpp" - -#include -#include - -TEST(DDSIMSimpleTest, SimpleTest) { - using namespace qc::literals; - - // Create a simple circuit - auto qc = std::make_unique(2U); - qc->h(0U); - qc->x(1U, 0_pc); - - auto sim = CircuitSimulator(std::move(qc)); - const auto results = sim.simulate(1024U); - EXPECT_EQ(results.count("01"), 0U); - EXPECT_EQ(results.count("10"), 0U); -} diff --git a/test/test_qcec_simple.cpp b/test/test_qcec_simple.cpp deleted file mode 100644 index a4323f5..0000000 --- a/test/test_qcec_simple.cpp +++ /dev/null @@ -1,19 +0,0 @@ -// A simple test file using googletest to ensure QCEC is working properly - -#include "EquivalenceCheckingManager.hpp" - -#include - -TEST(QCECSimpleTest, SimpleTest) { - using namespace qc::literals; - - // Create a simple circuit - auto qc = qc::QuantumComputation(2U); - qc.h(0U); - qc.x(1U, 0_pc); - - // check the circuit against itself - auto ecm = ec::EquivalenceCheckingManager(qc, qc); - ecm.run(); - EXPECT_TRUE(ecm.getResults().consideredEquivalent()); -} diff --git a/test/test_qcecexec.cpp b/test/test_qcecexec.cpp new file mode 100644 index 0000000..da44a00 --- /dev/null +++ b/test/test_qcecexec.cpp @@ -0,0 +1,79 @@ +#include "QuantumComputation.hpp" +#include "executors/AlternatingVerificationExecutor.hpp" +#include "tasks/VerificationTask.hpp" + +#include "gtest/gtest.h" + +struct TestConfigurationQCEC { + // given input + std::string description; + std::string circuit1; + std::string circuit2; + + // expected output + std::string expectedEquivalence; +}; + +// NOLINTNEXTLINE (readability-identifier-naming) +inline void from_json(const nlohmann::json& j, TestConfigurationQCEC& test) { + test.description = j.at("description").get(); + test.circuit1 = j.at("circuit1").get(); + test.circuit2 = j.at("circuit2").get(); + test.expectedEquivalence = j.at("expected_equivalence").get(); +} + +namespace { +std::vector getTests(const std::string& path) { + std::ifstream input(path); + nlohmann::json j; + input >> j; + return j; +} +} // namespace + +class QCECExecTest : public ::testing::TestWithParam { +protected: + void SetUp() override { + test = GetParam(); + + std::stringstream ss1(test.circuit1); + auto qc1 = std::make_unique(); + qc1->import(ss1, qc::Format::OpenQASM); + std::cout << "Circuit 1:\n" << *qc1 << std::endl; + + std::stringstream ss2(test.circuit2); + auto qc2 = std::make_unique(); + qc2->import(ss2, qc::Format::OpenQASM); + std::cout << "Circuit 2:\n" << *qc2 << std::endl; + + verificationTask = VerificationTask(std::move(qc1), std::move(qc2)); + + alternatingVerificationExecutor = + std::make_unique(); + } + + VerificationTask verificationTask; + std::unique_ptr + alternatingVerificationExecutor; + TestConfigurationQCEC test; +}; + +INSTANTIATE_TEST_SUITE_P( + Circuits, QCECExecTest, testing::ValuesIn(getTests("ver_circuits.json")), + [](const testing::TestParamInfo& inf) { + return inf.param.description; + }); + +TEST_P(QCECExecTest, Tests) { + const auto result = + alternatingVerificationExecutor->execute(verificationTask); + std::cout << "Results:\n" << result.dump(2U) << std::endl; + + ASSERT_TRUE(result.contains("check_results")); + EXPECT_EQ(result["check_results"]["equivalence"], test.expectedEquivalence); + + EXPECT_TRUE(result.contains("construction_time")); + EXPECT_TRUE(result.contains("execution_time")); + EXPECT_TRUE(result.contains("executor")); + EXPECT_TRUE(result.contains("task")); +} diff --git a/test/test_simexec.cpp b/test/test_simexec.cpp new file mode 100644 index 0000000..4f740cd --- /dev/null +++ b/test/test_simexec.cpp @@ -0,0 +1,62 @@ +#include "QuantumComputation.hpp" +#include "executors/CircuitSimulatorExecutor.hpp" +#include "tasks/SimulationTask.hpp" + +#include "gtest/gtest.h" + +struct TestConfigurationDDSIM { + // given input + std::string description; + std::string circuit; +}; + +// NOLINTNEXTLINE (readability-identifier-naming) +inline void from_json(const nlohmann::json& j, TestConfigurationDDSIM& test) { + test.description = j.at("description").get(); + test.circuit = j.at("circuit").get(); +} + +namespace { +std::vector getTests(const std::string& path) { + std::ifstream input(path); + nlohmann::json j; + input >> j; + return j; +} +} // namespace + +class DDSIMExecTest : public ::testing::TestWithParam { +protected: + void SetUp() override { + test = GetParam(); + std::stringstream ss(test.circuit); + + auto qc = std::make_unique(); + qc->import(ss, qc::Format::OpenQASM); + std::cout << "Circuit:\n" << *qc; + + simulationTask = SimulationTask(std::move(qc)); + circuitSimulatorExecutor = std::make_unique(); + } + + SimulationTask simulationTask; + std::unique_ptr circuitSimulatorExecutor; + TestConfigurationDDSIM test; +}; + +INSTANTIATE_TEST_SUITE_P( + Circuits, DDSIMExecTest, testing::ValuesIn(getTests("sim_circuits.json")), + [](const testing::TestParamInfo& inf) { + return inf.param.description; + }); + +TEST_P(DDSIMExecTest, Tests) { + const auto result = circuitSimulatorExecutor->execute(simulationTask); + std::cout << "Results:\n" << result.dump(2U) << std::endl; + + ASSERT_TRUE(result.contains("measurement_results")); + EXPECT_TRUE(result.contains("construction_time")); + EXPECT_TRUE(result.contains("execution_time")); + EXPECT_TRUE(result.contains("executor")); + EXPECT_TRUE(result.contains("task")); +} diff --git a/test/ver_circuits.json b/test/ver_circuits.json new file mode 100644 index 0000000..0f7814a --- /dev/null +++ b/test/ver_circuits.json @@ -0,0 +1,14 @@ +[ + { + "description": "simple_circuits", + "circuit1": "OPENQASM 2.0;include \"qelib1.inc\";qreg q[2];x q[0];x q[1];\n", + "circuit2": "OPENQASM 2.0;include \"qelib1.inc\";qreg q[2];x q[0];x q[1];x q[0];x q[0];\n", + "expected_equivalence": "equivalent" + }, + { + "description": "non_equi_circuits", + "circuit1": "OPENQASM 2.0;include \"qelib1.inc\";qreg q[2];h q[0];t q[1];cx q[0], q[1];\n", + "circuit2": "OPENQASM 2.0;include \"qelib1.inc\";qreg q[2];h q[1];cx q[0], q[1];s q[0];\n", + "expected_equivalence": "not_equivalent" + } +]