From 625bb69f95b4615f20f4e2bdd241a85a4227b253 Mon Sep 17 00:00:00 2001 From: DRovara Date: Mon, 15 Jul 2024 18:41:12 +0200 Subject: [PATCH 01/37] feat: :sparkles: Add `runAll` method This will be useful for testing. `runAll` will not stop at assertions and instead count the total number of failures. --- app/testDDSimDebugger.cpp | 10 +++++++--- include/backend/dd/DDSimDebug.hpp | 1 + include/backend/debug.h | 1 + src/backend/dd/DDSimDebug.cpp | 16 ++++++++++++++++ src/mqt/debug/pydebug.pyi | 1 + src/python/InterfaceBindings.cpp | 6 ++++++ 6 files changed, 32 insertions(+), 3 deletions(-) diff --git a/app/testDDSimDebugger.cpp b/app/testDDSimDebugger.cpp index 86506061..d0cf36b3 100644 --- a/app/testDDSimDebugger.cpp +++ b/app/testDDSimDebugger.cpp @@ -5,7 +5,7 @@ #include int main() { - std::ifstream file("../../app/code/test_complex_jumps" + std::ifstream file("../../app/code/entanglement_test_wrong" ".qasm"); if (!file.is_open()) { std::cerr << "Could not open file\n"; @@ -20,8 +20,12 @@ int main() { file.close(); - CliFrontEnd cli; - cli.run(code.c_str(), &state.interface); + size_t errors = 0; + Result result = ddsimLoadCode(&state.interface, code.c_str()); + result = state.interface.runAll(&state.interface, &errors); + std::cout << errors << "\n"; + // CliFrontEnd cli; + // cli.run(code.c_str(), &state.interface); destroyDDSimulationState(&state); diff --git a/include/backend/dd/DDSimDebug.hpp b/include/backend/dd/DDSimDebug.hpp index c09d5d78..461fcd4d 100644 --- a/include/backend/dd/DDSimDebug.hpp +++ b/include/backend/dd/DDSimDebug.hpp @@ -64,6 +64,7 @@ Result ddsimStepOverForward(SimulationState* self); Result ddsimStepOverBackward(SimulationState* self); Result ddsimStepOutForward(SimulationState* self); Result ddsimStepOutBackward(SimulationState* self); +Result ddsimRunAll(SimulationState* self, size_t* failedAssertions); Result ddsimRunSimulation(SimulationState* self); Result ddsimRunSimulationBackward(SimulationState* self); Result ddsimResetSimulation(SimulationState* self); diff --git a/include/backend/debug.h b/include/backend/debug.h index 5535a65e..4053af48 100644 --- a/include/backend/debug.h +++ b/include/backend/debug.h @@ -19,6 +19,7 @@ struct SimulationState { Result (*stepBackward)(SimulationState* self); Result (*stepOverBackward)(SimulationState* self); Result (*stepOutBackward)(SimulationState* self); + Result (*runAll)(SimulationState* self, size_t* failedAssertions); Result (*runSimulation)(SimulationState* self); Result (*runSimulationBackward)(SimulationState* self); Result (*resetSimulation)(SimulationState* self); diff --git a/src/backend/dd/DDSimDebug.cpp b/src/backend/dd/DDSimDebug.cpp index a1480bfa..6df415d7 100644 --- a/src/backend/dd/DDSimDebug.cpp +++ b/src/backend/dd/DDSimDebug.cpp @@ -37,6 +37,7 @@ Result createDDSimulationState(DDSimulationState* self) { self->interface.stepOverBackward = ddsimStepOverBackward; self->interface.stepOutForward = ddsimStepOutForward; self->interface.stepOutBackward = ddsimStepOutBackward; + self->interface.runAll = ddsimRunAll; self->interface.runSimulation = ddsimRunSimulation; self->interface.runSimulationBackward = ddsimRunSimulationBackward; self->interface.resetSimulation = ddsimResetSimulation; @@ -446,6 +447,21 @@ Result ddsimStepBackward(SimulationState* self) { return OK; } +Result ddsimRunAll(SimulationState* self, size_t* failedAssertions) { + size_t errorCount = 0; + while (!self->isFinished(self)) { + Result result = self->runSimulation(self); + if (result != OK) { + return result; + } + if (self->didAssertionFail(self)) { + errorCount++; + } + } + *failedAssertions = errorCount; + return OK; +} + Result ddsimRunSimulation(SimulationState* self) { auto* ddsim = reinterpret_cast(self); while (!self->isFinished(self)) { diff --git a/src/mqt/debug/pydebug.pyi b/src/mqt/debug/pydebug.pyi index 0c437fde..a3eea957 100644 --- a/src/mqt/debug/pydebug.pyi +++ b/src/mqt/debug/pydebug.pyi @@ -48,6 +48,7 @@ class SimulationState: def step_backward(self) -> None: ... def step_over_backward(self) -> None: ... def step_out_backward(self) -> None: ... + def run_all(self) -> int: ... def run_simulation(self) -> None: ... def run_simulation_backward(self) -> None: ... def reset_simulation(self) -> None: ... diff --git a/src/python/InterfaceBindings.cpp b/src/python/InterfaceBindings.cpp index 2461eebc..5aca393c 100644 --- a/src/python/InterfaceBindings.cpp +++ b/src/python/InterfaceBindings.cpp @@ -93,6 +93,12 @@ void bindFramework(py::module& m) { [](SimulationState* self) { checkOrThrow(self->stepOutBackward(self)); }) + .def("run_all", + [](SimulationState* self) { + size_t errors = 0; + checkOrThrow(self->runAll(self, &errors)); + return errors; + }) .def("run_simulation", [](SimulationState* self) { checkOrThrow(self->runSimulation(self)); From 6a1c3c9c4c34f42e04cf00b42d24a48c86e66374 Mon Sep 17 00:00:00 2001 From: DRovara Date: Mon, 22 Jul 2024 15:02:15 +0200 Subject: [PATCH 02/37] test: :construction: Initial setup for pytest --- app/code/test.qasm | 3 +- app/testDDSimDebugger.cpp | 14 +-- src/backend/dd/DDSimDebug.cpp | 5 +- src/frontend/cli/CliFrontEnd.cpp | 7 +- test.txt | 2 + test/python/resources/bindings/classical.qasm | 14 +++ .../resources/bindings/ghz-incorrect.qasm | 11 ++ test/python/resources/bindings/jumps.qasm | 32 ++++++ test/python/test.py | 34 ------ test/python/test_dap.py | 15 --- test/python/test_python_bindings.py | 100 ++++++++++++++++++ 11 files changed, 171 insertions(+), 66 deletions(-) create mode 100644 test.txt create mode 100644 test/python/resources/bindings/classical.qasm create mode 100644 test/python/resources/bindings/ghz-incorrect.qasm create mode 100644 test/python/resources/bindings/jumps.qasm delete mode 100644 test/python/test.py delete mode 100644 test/python/test_dap.py create mode 100644 test/python/test_python_bindings.py diff --git a/app/code/test.qasm b/app/code/test.qasm index 04d515b6..1d0dbfdb 100644 --- a/app/code/test.qasm +++ b/app/code/test.qasm @@ -1,5 +1,4 @@ qreg q[3]; -creg c[3]; gate my_cx q0, q1 { cx q0, q1; @@ -29,3 +28,5 @@ assert-eq q[0], q[1] { h q[0]; cx q[0], q[1]; } + +assert-eq 0.9, q[0] { 0.7, 0.7 } diff --git a/app/testDDSimDebugger.cpp b/app/testDDSimDebugger.cpp index d0cf36b3..63fd8024 100644 --- a/app/testDDSimDebugger.cpp +++ b/app/testDDSimDebugger.cpp @@ -5,7 +5,7 @@ #include int main() { - std::ifstream file("../../app/code/entanglement_test_wrong" + std::ifstream file("../../app/code/test" ".qasm"); if (!file.is_open()) { std::cerr << "Could not open file\n"; @@ -20,12 +20,12 @@ int main() { file.close(); - size_t errors = 0; - Result result = ddsimLoadCode(&state.interface, code.c_str()); - result = state.interface.runAll(&state.interface, &errors); - std::cout << errors << "\n"; - // CliFrontEnd cli; - // cli.run(code.c_str(), &state.interface); + // size_t errors = 0; + // Result result = ddsimLoadCode(&state.interface, code.c_str()); + // result = state.interface.runAll(&state.interface, &errors); + // std::cout << errors << "\n"; + CliFrontEnd cli; + cli.run(code.c_str(), &state.interface); destroyDDSimulationState(&state); diff --git a/src/backend/dd/DDSimDebug.cpp b/src/backend/dd/DDSimDebug.cpp index 6df415d7..24549a25 100644 --- a/src/backend/dd/DDSimDebug.cpp +++ b/src/backend/dd/DDSimDebug.cpp @@ -111,6 +111,8 @@ Result ddsimLoadCode(SimulationState* self, const char* code) { ddsim->callSubstitutions.clear(); ddsim->restoreCallReturnStack.clear(); ddsim->code = code; + ddsim->variables.clear(); + ddsim->variableNames.clear(); try { std::stringstream ss{preprocessAssertionCode(code, ddsim)}; @@ -503,14 +505,11 @@ Result ddsimResetSimulation(SimulationState* self) { ddsim->currentInstruction = 0; ddsim->previousInstructionStack.clear(); ddsim->callReturnStack.clear(); - ddsim->callSubstitutions.clear(); ddsim->restoreCallReturnStack.clear(); ddsim->iterator = ddsim->qc->begin(); ddsim->lastFailedAssertion = -1ULL; ddsim->lastMetBreakpoint = -1ULL; - ddsim->variables.clear(); - ddsim->variableNames.clear(); resetSimulationState(ddsim); return OK; diff --git a/src/frontend/cli/CliFrontEnd.cpp b/src/frontend/cli/CliFrontEnd.cpp index 79f7442f..ba0e7e69 100644 --- a/src/frontend/cli/CliFrontEnd.cpp +++ b/src/frontend/cli/CliFrontEnd.cpp @@ -22,17 +22,12 @@ void CliFrontEnd::run(const char* code, SimulationState* state) { std::string command; const auto result = state->loadCode(state, code); + state->resetSimulation(state); if (result == ERROR) { std::cout << "Error loading code\n"; return; } - size_t breakpoint; - Result r = state->setBreakpoint( - state, 38, - &breakpoint); // set breakpoint at line 38 (entanglement_test_wrong.qasm - std::cout << "Breakpoint set at " << breakpoint << " " << r << "\n"; - bool wasError = false; bool wasGet = false; size_t inspecting = -1ULL; diff --git a/test.txt b/test.txt new file mode 100644 index 00000000..f5cefa6d --- /dev/null +++ b/test.txt @@ -0,0 +1,2 @@ +Hello +1 2 3 4 5 diff --git a/test/python/resources/bindings/classical.qasm b/test/python/resources/bindings/classical.qasm new file mode 100644 index 00000000..bd91842a --- /dev/null +++ b/test/python/resources/bindings/classical.qasm @@ -0,0 +1,14 @@ +qreg q[3]; +creg c[3]; + +h q[0]; +cx q[0], q[1]; +cx q[0], q[2]; + +assert-ent q[0], q[1]; +assert-ent q[1], q[2]; +assert-ent q[0], q[2]; + +measure q[0] -> c[0]; +measure q[1] -> c[1]; +measure q[2] -> c[2]; diff --git a/test/python/resources/bindings/ghz-incorrect.qasm b/test/python/resources/bindings/ghz-incorrect.qasm new file mode 100644 index 00000000..dfd318d6 --- /dev/null +++ b/test/python/resources/bindings/ghz-incorrect.qasm @@ -0,0 +1,11 @@ +qreg q[3]; + +h q[0]; +cx q[0], q[1]; +cx q[2], q[0]; + +assert-ent q[0], q[1]; +assert-ent q[1], q[2]; +assert-ent q[0], q[2]; + +assert-sup q[0], q[1], q[2]; diff --git a/test/python/resources/bindings/jumps.qasm b/test/python/resources/bindings/jumps.qasm new file mode 100644 index 00000000..1d0dbfdb --- /dev/null +++ b/test/python/resources/bindings/jumps.qasm @@ -0,0 +1,32 @@ +qreg q[3]; + +gate my_cx q0, q1 { + cx q0, q1; +} + +gate my_h q0 { + h q0; +} + +gate superposition q { + my_h q; +} + +gate create_ghz q0, q1, q2 { + superposition q0; + assert-sup q0; + my_cx q0, q1; + my_cx q1, q2; + assert-sup q0; assert-sup q1; assert-sup q2; + assert-ent q0, q1, q2; +} + +create_ghz q[0], q[1], q[2]; + +assert-eq q[0], q[1] { + qreg q[2]; + h q[0]; + cx q[0], q[1]; +} + +assert-eq 0.9, q[0] { 0.7, 0.7 } diff --git a/test/python/test.py b/test/python/test.py deleted file mode 100644 index 41939ddc..00000000 --- a/test/python/test.py +++ /dev/null @@ -1,34 +0,0 @@ -"""A simple file to test the functionality of mqt-debug python bindings.""" - -from __future__ import annotations - -from mqt import debug - -code = """qreg q[3]; -creg c[3]; -h q[0]; -cx q[0], q[1]; -cx q[2], q[0]; -assert-ent q[0], q[1]; -assert-ent q[2], q[0]; -assert-ent q[2], q[1]; -""" - - -def main() -> None: - """A simple test for the mqt-debug library.""" - s = debug.create_ddsim_simulation_state() - s.load_code(code) - s.run_simulation() - if s.did_assertion_fail(): - print("Assertion failed") - current = s.get_current_instruction() - print(current) - (start, end) = s.get_instruction_position(current) - print(start, end) - print(s.get_amplitude_index(0).real) - print(s.get_state_vector_full().amplitudes) - - -if __name__ == "__main__": - main() diff --git a/test/python/test_dap.py b/test/python/test_dap.py deleted file mode 100644 index 1fa0d0a2..00000000 --- a/test/python/test_dap.py +++ /dev/null @@ -1,15 +0,0 @@ -"""Run a single instance of the DAP server.""" - -from __future__ import annotations - -from mqt.debug import DAPServer - - -def main() -> None: - """The main entry point of the DAP server test.""" - server = DAPServer() - server.start() - - -if __name__ == "__main__": - main() diff --git a/test/python/test_python_bindings.py b/test/python/test_python_bindings.py new file mode 100644 index 00000000..747312f8 --- /dev/null +++ b/test/python/test_python_bindings.py @@ -0,0 +1,100 @@ +from __future__ import annotations + +import locale +from typing import Generator + +import pytest + +import mqt.debug + +type SimulationInstance = tuple[mqt.debug.SimulationState, int] + + +@pytest.fixture(scope="module") +def simulation_instance_ghz() -> Generator[SimulationInstance, None, None]: + simulation_state = mqt.debug.create_ddsim_simulation_state() + with open("test/python/resources/bindings/ghz-incorrect.qasm", encoding=locale.getpreferredencoding(False)) as f: + code = f.read() + print(code) + simulation_state.load_code(code) + yield (simulation_state, 0) + mqt.debug.destroy_ddsim_simulation_state(simulation_state) + + +@pytest.fixture(scope="module") +def simulation_instance_jumps() -> Generator[SimulationInstance, None, None]: + simulation_state = mqt.debug.create_ddsim_simulation_state() + with open("test/python/resources/bindings/jumps.qasm", encoding=locale.getpreferredencoding(False)) as f: + code = f.read() + print(code) + simulation_state.load_code(code) + yield (simulation_state, 1) + mqt.debug.destroy_ddsim_simulation_state(simulation_state) + + +@pytest.fixture(scope="module") +def simulation_instance_classical() -> Generator[SimulationInstance, None, None]: + simulation_state = mqt.debug.create_ddsim_simulation_state() + with open("test/python/resources/bindings/classical.qasm", encoding=locale.getpreferredencoding(False)) as f: + code = f.read() + print(code) + simulation_state.load_code(code) + yield (simulation_state, 2) + mqt.debug.destroy_ddsim_simulation_state(simulation_state) + + +@pytest.fixture(autouse=True) +def simulation_state_cleanup( + simulation_instance_ghz: SimulationInstance, + simulation_instance_jumps: SimulationInstance, + simulation_instance_classical: SimulationInstance, +) -> Generator[None, None, None]: + yield + simulation_instance_ghz[0].reset_simulation() + simulation_instance_jumps[0].reset_simulation() + simulation_instance_classical[0].reset_simulation() + + +def load_fixture(request, name: str) -> tuple[mqt.debug.SimulationState, int]: + return request.getfixturevalue(name) + + +@pytest.mark.parametrize( + "simulation_instance", ["simulation_instance_ghz", "simulation_instance_jumps", "simulation_instance_classical"] +) +def test_run(simulation_instance: SimulationInstance, request) -> None: + (simulation_state, id) = load_fixture(request, simulation_instance) + simulation_state.run_simulation() + print(simulation_state.get_instruction_count()) + assert simulation_state.is_finished() == (id != 0) + + +@pytest.mark.parametrize( + "simulation_instance", ["simulation_instance_ghz", "simulation_instance_jumps", "simulation_instance_classical"] +) +def test_current_instruction(simulation_instance: SimulationInstance, request) -> None: + (simulation_state, id) = load_fixture(request, simulation_instance) + assert simulation_state.get_current_instruction() == 0 + simulation_state.step_forward() + assert simulation_state.get_current_instruction() == 1 + simulation_state.step_forward() + assert simulation_state.get_current_instruction() == (2 if id != 1 else 4) + simulation_state.step_backward() + assert simulation_state.get_current_instruction() == 1 + simulation_state.step_over_backward() + assert simulation_state.get_current_instruction() == 0 + simulation_state.step_over_forward() + assert simulation_state.get_current_instruction() == 1 + + +def test_step_out(simulation_instance_jumps: SimulationInstance) -> None: + (simulation_state, _id) = simulation_instance_jumps + simulation_state.set_breakpoint(183) + simulation_state.run_simulation() + assert simulation_state.get_current_instruction() == 12 + simulation_state.step_out_backward() + assert simulation_state.get_current_instruction() == 18 + simulation_state.run_simulation() + assert simulation_state.get_current_instruction() == 12 + simulation_state.step_out_forward() + assert simulation_state.get_current_instruction() == 19 From 5539981114f33dbdb03eccf1bd1ac0d6ebc32b2c Mon Sep 17 00:00:00 2001 From: DRovara Date: Tue, 23 Jul 2024 18:17:49 +0200 Subject: [PATCH 03/37] refactor: :recycle: Remove `getPreviousInstruction` debugging method This method just didn't make much sense --- include/backend/dd/DDSimDebug.hpp | 1 - include/backend/debug.h | 1 - src/backend/dd/DDSimDebug.cpp | 9 +-------- src/mqt/debug/pydebug.pyi | 2 +- src/python/InterfaceBindings.cpp | 4 ---- 5 files changed, 2 insertions(+), 15 deletions(-) diff --git a/include/backend/dd/DDSimDebug.hpp b/include/backend/dd/DDSimDebug.hpp index 21505415..cb1b398e 100644 --- a/include/backend/dd/DDSimDebug.hpp +++ b/include/backend/dd/DDSimDebug.hpp @@ -80,7 +80,6 @@ bool ddsimDidAssertionFail(SimulationState* self); bool ddsimWasBreakpointHit(SimulationState* self); size_t ddsimGetCurrentInstruction(SimulationState* self); -size_t ddsimGetPreviousInstruction(SimulationState* self); size_t ddsimGetInstructionCount(SimulationState* self); Result ddsimGetInstructionPosition(SimulationState* self, size_t instruction, size_t* start, size_t* end); diff --git a/include/backend/debug.h b/include/backend/debug.h index 766fffdc..b55ed757 100644 --- a/include/backend/debug.h +++ b/include/backend/debug.h @@ -32,7 +32,6 @@ struct SimulationState { bool (*wasBreakpointHit)(SimulationState* self); size_t (*getCurrentInstruction)(SimulationState* self); - size_t (*getPreviousInstruction)(SimulationState* self); size_t (*getInstructionCount)(SimulationState* self); Result (*getInstructionPosition)(SimulationState* self, size_t instruction, size_t* start, size_t* end); diff --git a/src/backend/dd/DDSimDebug.cpp b/src/backend/dd/DDSimDebug.cpp index 00ccd03b..8062e93e 100644 --- a/src/backend/dd/DDSimDebug.cpp +++ b/src/backend/dd/DDSimDebug.cpp @@ -49,7 +49,6 @@ Result createDDSimulationState(DDSimulationState* self) { self->interface.wasBreakpointHit = ddsimWasBreakpointHit; self->interface.getCurrentInstruction = ddsimGetCurrentInstruction; - self->interface.getPreviousInstruction = ddsimGetPreviousInstruction; self->interface.getInstructionCount = ddsimGetInstructionCount; self->interface.getInstructionPosition = ddsimGetInstructionPosition; self->interface.getNumQubits = ddsimGetNumQubits; @@ -76,7 +75,6 @@ void resetSimulationState(DDSimulationState* ddsim) { } ddsim->simulationState = ddsim->dd->makeZeroState(ddsim->qc->getNqubits()); ddsim->dd->incRef(ddsim->simulationState); - ddsim->breakpoints.clear(); ddsim->paused = false; } @@ -172,7 +170,7 @@ Result ddsimStepOverBackward(SimulationState* self) { return ERROR; } auto* ddsim = reinterpret_cast(self); - const auto prev = self->getPreviousInstruction(self); + const auto prev = ddsim->previousInstructionStack.back(); if (ddsim->instructionTypes[prev] != RETURN) { return self->stepBackward(self); } @@ -555,11 +553,6 @@ size_t ddsimGetCurrentInstruction(SimulationState* self) { return ddsim->currentInstruction; } -size_t ddsimGetPreviousInstruction(SimulationState* self) { - auto* ddsim = reinterpret_cast(self); - return ddsim->previousInstructionStack.back(); -} - size_t ddsimGetInstructionCount(SimulationState* self) { auto* ddsim = reinterpret_cast(self); return ddsim->instructionTypes.size(); diff --git a/src/mqt/debug/pydebug.pyi b/src/mqt/debug/pydebug.pyi index 2a42d181..86aeae6e 100644 --- a/src/mqt/debug/pydebug.pyi +++ b/src/mqt/debug/pydebug.pyi @@ -66,7 +66,7 @@ class SimulationState: def get_num_classical_variables(self) -> int: ... def get_classical_variable_name(self, index: int) -> str: ... def get_state_vector_full(self) -> Statevector: ... - def get_state_vector_sub(self, sub_state_size: int, qubits: list[int]) -> Statevector: ... + def get_state_vector_sub(self, qubits: list[int]) -> Statevector: ... def set_breakpoint(self, desired_position: int) -> int: ... def clear_breakpoints(self) -> None: ... def get_stack_depth(self) -> int: ... diff --git a/src/python/InterfaceBindings.cpp b/src/python/InterfaceBindings.cpp index 20ed7270..99727b1d 100644 --- a/src/python/InterfaceBindings.cpp +++ b/src/python/InterfaceBindings.cpp @@ -131,10 +131,6 @@ void bindFramework(py::module& m) { [](SimulationState* self) { return self->getCurrentInstruction(self); }) - .def("get_previous_instruction", - [](SimulationState* self) { - return self->getPreviousInstruction(self); - }) .def( "get_instruction_count", [](SimulationState* self) { return self->getInstructionCount(self); }) From 062743862ea1cef70cbbff57cca95d33945a0e0f Mon Sep 17 00:00:00 2001 From: DRovara Date: Tue, 23 Jul 2024 18:18:04 +0200 Subject: [PATCH 04/37] test: :white_check_mark: Add python-bindings test --- app/code/test.qasm | 38 +--- test/python/resources/bindings/classical.qasm | 1 + test/python/test_python_bindings.py | 187 +++++++++++++++--- 3 files changed, 176 insertions(+), 50 deletions(-) diff --git a/app/code/test.qasm b/app/code/test.qasm index 1d0dbfdb..bd91842a 100644 --- a/app/code/test.qasm +++ b/app/code/test.qasm @@ -1,32 +1,14 @@ qreg q[3]; +creg c[3]; -gate my_cx q0, q1 { - cx q0, q1; -} +h q[0]; +cx q[0], q[1]; +cx q[0], q[2]; -gate my_h q0 { - h q0; -} +assert-ent q[0], q[1]; +assert-ent q[1], q[2]; +assert-ent q[0], q[2]; -gate superposition q { - my_h q; -} - -gate create_ghz q0, q1, q2 { - superposition q0; - assert-sup q0; - my_cx q0, q1; - my_cx q1, q2; - assert-sup q0; assert-sup q1; assert-sup q2; - assert-ent q0, q1, q2; -} - -create_ghz q[0], q[1], q[2]; - -assert-eq q[0], q[1] { - qreg q[2]; - h q[0]; - cx q[0], q[1]; -} - -assert-eq 0.9, q[0] { 0.7, 0.7 } +measure q[0] -> c[0]; +measure q[1] -> c[1]; +measure q[2] -> c[2]; diff --git a/test/python/resources/bindings/classical.qasm b/test/python/resources/bindings/classical.qasm index bd91842a..be6e5d59 100644 --- a/test/python/resources/bindings/classical.qasm +++ b/test/python/resources/bindings/classical.qasm @@ -1,5 +1,6 @@ qreg q[3]; creg c[3]; +qreg q2[1]; h q[0]; cx q[0], q[1]; diff --git a/test/python/test_python_bindings.py b/test/python/test_python_bindings.py index 747312f8..25bc182d 100644 --- a/test/python/test_python_bindings.py +++ b/test/python/test_python_bindings.py @@ -1,21 +1,29 @@ +"""This module contains tests for the Python bindings of the Debugging interface. + +It only tests whether the bindings are working correctly. It does not stress-test the functionality or test edge cases. +""" + from __future__ import annotations import locale -from typing import Generator +from pathlib import Path +from typing import Generator, Tuple, cast import pytest import mqt.debug -type SimulationInstance = tuple[mqt.debug.SimulationState, int] +SimulationInstance = Tuple[mqt.debug.SimulationState, int] @pytest.fixture(scope="module") def simulation_instance_ghz() -> Generator[SimulationInstance, None, None]: + """Fixture for the GHZ state simulation instance.""" simulation_state = mqt.debug.create_ddsim_simulation_state() - with open("test/python/resources/bindings/ghz-incorrect.qasm", encoding=locale.getpreferredencoding(False)) as f: + with Path("test/python/resources/bindings/ghz-incorrect.qasm").open( + encoding=locale.getpreferredencoding(False) + ) as f: code = f.read() - print(code) simulation_state.load_code(code) yield (simulation_state, 0) mqt.debug.destroy_ddsim_simulation_state(simulation_state) @@ -23,10 +31,10 @@ def simulation_instance_ghz() -> Generator[SimulationInstance, None, None]: @pytest.fixture(scope="module") def simulation_instance_jumps() -> Generator[SimulationInstance, None, None]: + """Fixture for the Jumps simulation instance.""" simulation_state = mqt.debug.create_ddsim_simulation_state() - with open("test/python/resources/bindings/jumps.qasm", encoding=locale.getpreferredencoding(False)) as f: + with Path("test/python/resources/bindings/jumps.qasm").open(encoding=locale.getpreferredencoding(False)) as f: code = f.read() - print(code) simulation_state.load_code(code) yield (simulation_state, 1) mqt.debug.destroy_ddsim_simulation_state(simulation_state) @@ -34,10 +42,10 @@ def simulation_instance_jumps() -> Generator[SimulationInstance, None, None]: @pytest.fixture(scope="module") def simulation_instance_classical() -> Generator[SimulationInstance, None, None]: + """Fixture for the Classical simulation instance.""" simulation_state = mqt.debug.create_ddsim_simulation_state() - with open("test/python/resources/bindings/classical.qasm", encoding=locale.getpreferredencoding(False)) as f: + with Path("test/python/resources/bindings/classical.qasm").open(encoding=locale.getpreferredencoding(False)) as f: code = f.read() - print(code) simulation_state.load_code(code) yield (simulation_state, 2) mqt.debug.destroy_ddsim_simulation_state(simulation_state) @@ -49,36 +57,45 @@ def simulation_state_cleanup( simulation_instance_jumps: SimulationInstance, simulation_instance_classical: SimulationInstance, ) -> Generator[None, None, None]: + """Fixture that resets the simulation state after each test.""" yield simulation_instance_ghz[0].reset_simulation() simulation_instance_jumps[0].reset_simulation() simulation_instance_classical[0].reset_simulation() + simulation_instance_ghz[0].clear_breakpoints() + simulation_instance_jumps[0].clear_breakpoints() + simulation_instance_classical[0].clear_breakpoints() -def load_fixture(request, name: str) -> tuple[mqt.debug.SimulationState, int]: - return request.getfixturevalue(name) +def load_fixture(request: pytest.FixtureRequest, name: str) -> tuple[mqt.debug.SimulationState, int]: + """Loads a fixture with the given name.""" + return cast(Tuple[mqt.debug.SimulationState, int], request.getfixturevalue(name)) @pytest.mark.parametrize( "simulation_instance", ["simulation_instance_ghz", "simulation_instance_jumps", "simulation_instance_classical"] ) -def test_run(simulation_instance: SimulationInstance, request) -> None: - (simulation_state, id) = load_fixture(request, simulation_instance) +def test_run(simulation_instance: str, request: pytest.FixtureRequest) -> None: + """Tests the `run_simulation()` method.""" + (simulation_state, state_id) = load_fixture(request, simulation_instance) + assert simulation_state.can_step_backward() is False simulation_state.run_simulation() - print(simulation_state.get_instruction_count()) - assert simulation_state.is_finished() == (id != 0) + assert simulation_state.is_finished() == (state_id != 0) + assert simulation_state.can_step_forward() != (state_id != 0) + assert simulation_state.did_assertion_fail() == (state_id == 0) @pytest.mark.parametrize( "simulation_instance", ["simulation_instance_ghz", "simulation_instance_jumps", "simulation_instance_classical"] ) -def test_current_instruction(simulation_instance: SimulationInstance, request) -> None: - (simulation_state, id) = load_fixture(request, simulation_instance) +def test_current_instruction(simulation_instance: str, request: pytest.FixtureRequest) -> None: + """Tests the `get_current_instruction()` method.""" + (simulation_state, state_id) = load_fixture(request, simulation_instance) assert simulation_state.get_current_instruction() == 0 simulation_state.step_forward() assert simulation_state.get_current_instruction() == 1 simulation_state.step_forward() - assert simulation_state.get_current_instruction() == (2 if id != 1 else 4) + assert simulation_state.get_current_instruction() == (2 if state_id != 1 else 4) simulation_state.step_backward() assert simulation_state.get_current_instruction() == 1 simulation_state.step_over_backward() @@ -88,13 +105,139 @@ def test_current_instruction(simulation_instance: SimulationInstance, request) - def test_step_out(simulation_instance_jumps: SimulationInstance) -> None: - (simulation_state, _id) = simulation_instance_jumps - simulation_state.set_breakpoint(183) + """Tests the `step_out()` methods.""" + (simulation_state, _state_id) = simulation_instance_jumps + simulation_state.set_breakpoint(320) simulation_state.run_simulation() + simulation_state.step_forward() + simulation_state.step_over_forward() assert simulation_state.get_current_instruction() == 12 + assert simulation_state.get_stack_depth() == 2 + assert simulation_state.get_stack_trace(2) == [12, 20] simulation_state.step_out_backward() - assert simulation_state.get_current_instruction() == 18 - simulation_state.run_simulation() + assert simulation_state.get_current_instruction() == 20 + assert simulation_state.get_stack_depth() == 1 + simulation_state.step_forward() + simulation_state.step_over_forward() assert simulation_state.get_current_instruction() == 12 + assert simulation_state.get_stack_depth() == 2 simulation_state.step_out_forward() - assert simulation_state.get_current_instruction() == 19 + assert simulation_state.get_current_instruction() == 21 + + +@pytest.mark.parametrize( + "simulation_instance", ["simulation_instance_ghz", "simulation_instance_jumps", "simulation_instance_classical"] +) +def test_run_all(simulation_instance: str, request: pytest.FixtureRequest) -> None: + """Tests the `run_all()` method.""" + (simulation_state, state_id) = load_fixture(request, simulation_instance) + failures = simulation_state.run_all() + assert failures == (2 if state_id == 0 else 0) + + +@pytest.mark.parametrize("simulation_instance", ["simulation_instance_ghz", "simulation_instance_jumps"]) +def test_run_backward(simulation_instance: str, request: pytest.FixtureRequest) -> None: + """Tests the `run_simulation_backward()` method.""" + (simulation_state, _state_id) = load_fixture(request, simulation_instance) + bp = simulation_state.set_breakpoint(60) + simulation_state.run_simulation() + assert simulation_state.get_current_instruction() == bp + assert simulation_state.was_breakpoint_hit() + simulation_state.run_simulation() + simulation_state.run_simulation_backward() + assert simulation_state.get_current_instruction() == bp + simulation_state.run_simulation_backward() + assert simulation_state.get_current_instruction() == 0 + + +@pytest.mark.parametrize( + "simulation_instance", ["simulation_instance_ghz", "simulation_instance_jumps", "simulation_instance_classical"] +) +def test_instruction_count(simulation_instance: str, request: pytest.FixtureRequest) -> None: + """Tests the `get_instruction_count()` method.""" + (simulation_state, state_id) = load_fixture(request, simulation_instance) + true_counts = [8, 23, 12] + assert simulation_state.get_instruction_count() == true_counts[state_id] + + +def test_instruction_positions(simulation_instance_jumps: SimulationInstance) -> None: + """Tests the `get_instruction_position(instruction)` method.""" + (simulation_state, _state_id) = simulation_instance_jumps + assert simulation_state.get_instruction_position(0) == (0, 9) + assert simulation_state.get_instruction_position(1) == (12, 47) + assert simulation_state.get_instruction_position(16) == (241, 254) + + +@pytest.mark.parametrize( + "simulation_instance", ["simulation_instance_ghz", "simulation_instance_jumps", "simulation_instance_classical"] +) +def test_get_num_qubits(simulation_instance: str, request: pytest.FixtureRequest) -> None: + """Tests the `get_num_qubits()` method.""" + (simulation_state, state_id) = load_fixture(request, simulation_instance) + assert simulation_state.get_num_qubits() == (3 if state_id != 2 else 4) + + +def test_access_state(simulation_instance_jumps: SimulationInstance) -> None: + """Tests the quantum-state-access methods.""" + (simulation_state, _state_id) = simulation_instance_jumps + simulation_state.run_simulation() + + c = simulation_state.get_amplitude_bitstring("111") + assert abs(c.imaginary) < 1e-6 + assert abs(c.real - 1 / (2**0.5)) < 1e-6 + c = simulation_state.get_amplitude_bitstring("101") + assert abs(c.imaginary) < 1e-6 + assert abs(c.real) < 1e-6 + + c = simulation_state.get_amplitude_index(0) + assert abs(c.imaginary) < 1e-6 + assert abs(c.real - 1 / (2**0.5)) < 1e-6 + c = simulation_state.get_amplitude_index(7) + assert abs(c.imaginary) < 1e-6 + assert abs(c.real - 1 / (2**0.5)) < 1e-6 + c = simulation_state.get_amplitude_index(3) + assert abs(c.imaginary) < 1e-6 + assert abs(c.real) < 1e-6 + + sv = simulation_state.get_state_vector_full() + assert sv.num_qubits == 3 + assert sv.num_states == 8 + c = sv.amplitudes[7] + assert abs(c.imaginary) < 1e-6 + assert abs(c.real - 1 / (2**0.5)) < 1e-6 + c = sv.amplitudes[2] + assert abs(c.imaginary) < 1e-6 + assert abs(c.real) < 1e-6 + + sv = simulation_state.get_state_vector_sub([0, 2]) + assert sv.num_qubits == 2 + assert sv.num_states == 4 + c = sv.amplitudes[3] + assert abs(c.imaginary) < 1e-6 + assert abs(c.real - 1 / (2**0.5)) < 1e-6 + c = sv.amplitudes[2] + assert abs(c.imaginary) < 1e-6 + assert abs(c.real) < 1e-6 + + +def test_classical_get(simulation_instance_classical: SimulationInstance) -> None: + """Tests the classical-state-access methods.""" + (simulation_state, _state_id) = simulation_instance_classical + simulation_state.run_all() + assert simulation_state.get_num_classical_variables() == 3 + + assert simulation_state.get_classical_variable_name(0) == "c[0]" + assert simulation_state.get_classical_variable_name(1) == "c[1]" + assert simulation_state.get_classical_variable_name(2) == "c[2]" + + assert simulation_state.get_classical_variable("c[0]").name == "c[0]" + assert simulation_state.get_classical_variable("c[1]").name == "c[1]" + assert simulation_state.get_classical_variable("c[2]").name == "c[2]" + + assert simulation_state.get_classical_variable("c[0]").type == mqt.debug.VariableType.VarBool + assert simulation_state.get_classical_variable("c[1]").type == mqt.debug.VariableType.VarBool + assert simulation_state.get_classical_variable("c[2]").type == mqt.debug.VariableType.VarBool + + first = simulation_state.get_classical_variable("c[0]").value.bool_value + assert simulation_state.get_classical_variable("c[1]").value.bool_value == first + assert simulation_state.get_classical_variable("c[2]").value.bool_value == first From 3783f32153476747f3f72fc8dfec47a4e56c87c0 Mon Sep 17 00:00:00 2001 From: DRovara Date: Tue, 23 Jul 2024 18:28:34 +0200 Subject: [PATCH 05/37] fix: :bug: No longer clear breakpoints when reloading code --- src/backend/dd/DDSimDebug.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/backend/dd/DDSimDebug.cpp b/src/backend/dd/DDSimDebug.cpp index 8062e93e..4ab63626 100644 --- a/src/backend/dd/DDSimDebug.cpp +++ b/src/backend/dd/DDSimDebug.cpp @@ -1007,7 +1007,6 @@ std::string preprocessAssertionCode(const char* code, ddsim->qubitRegisters.clear(); ddsim->successorInstructions.clear(); ddsim->dataDependencies.clear(); - ddsim->breakpoints.clear(); ddsim->targetQubits.clear(); for (auto& instruction : instructions) { From db9eb76a3cd49d397f1789a049644af080ceb02d Mon Sep 17 00:00:00 2001 From: DRovara Date: Wed, 24 Jul 2024 14:32:37 +0200 Subject: [PATCH 06/37] feat: :sparkles: Code preprocessing now ignores comments --- include/backend/dd/DDSimDebug.hpp | 1 + include/common/parsing/CodePreprocessing.hpp | 10 +++--- src/backend/dd/DDSimDebug.cpp | 14 +++++--- src/common/parsing/CodePreprocessing.cpp | 34 ++++++++++++++++---- 4 files changed, 43 insertions(+), 16 deletions(-) diff --git a/include/backend/dd/DDSimDebug.hpp b/include/backend/dd/DDSimDebug.hpp index cb1b398e..7fb05ec8 100644 --- a/include/backend/dd/DDSimDebug.hpp +++ b/include/backend/dd/DDSimDebug.hpp @@ -28,6 +28,7 @@ struct DDSimulationState { SimulationState interface; size_t currentInstruction; std::string code; + std::string processedCode; bool ready; std::unique_ptr qc; diff --git a/include/common/parsing/CodePreprocessing.hpp b/include/common/parsing/CodePreprocessing.hpp index 94432cbe..db40cd3b 100644 --- a/include/common/parsing/CodePreprocessing.hpp +++ b/include/common/parsing/CodePreprocessing.hpp @@ -45,8 +45,8 @@ struct FunctionDefinition { std::vector parameters; }; -std::vector preprocessCode(const std::string& code); -std::vector -preprocessCode(const std::string& code, size_t startIndex, - size_t initialCodeOffset, - const std::vector& functionNames); +std::vector preprocessCode(const std::string& code, + std::string& processedCode); +std::vector preprocessCode( + const std::string& code, size_t startIndex, size_t initialCodeOffset, + const std::vector& functionNames, std::string& processedCode); diff --git a/src/backend/dd/DDSimDebug.cpp b/src/backend/dd/DDSimDebug.cpp index 4ab63626..7d92d6e0 100644 --- a/src/backend/dd/DDSimDebug.cpp +++ b/src/backend/dd/DDSimDebug.cpp @@ -567,12 +567,16 @@ Result ddsimGetInstructionPosition(SimulationState* self, size_t instruction, size_t start_index = ddsim->instructionStarts[instruction]; size_t end_index = ddsim->instructionEnds[instruction]; - while (ddsim->code[start_index] == ' ' || ddsim->code[start_index] == '\n' || - ddsim->code[start_index] == '\r' || ddsim->code[start_index] == '\t') { + while (ddsim->processedCode[start_index] == ' ' || + ddsim->processedCode[start_index] == '\n' || + ddsim->processedCode[start_index] == '\r' || + ddsim->processedCode[start_index] == '\t') { start_index++; } - while (ddsim->code[end_index] == ' ' || ddsim->code[end_index] == '\n' || - ddsim->code[end_index] == '\r' || ddsim->code[end_index] == '\t') { + while (ddsim->processedCode[end_index] == ' ' || + ddsim->processedCode[end_index] == '\n' || + ddsim->processedCode[end_index] == '\r' || + ddsim->processedCode[end_index] == '\t') { end_index++; } *start = start_index; @@ -997,7 +1001,7 @@ std::string validCodeFromChildren(const Instruction& parent, std::string preprocessAssertionCode(const char* code, DDSimulationState* ddsim) { - auto instructions = preprocessCode(code); + auto instructions = preprocessCode(code, ddsim->processedCode); std::vector correctLines; ddsim->instructionTypes.clear(); ddsim->instructionStarts.clear(); diff --git a/src/common/parsing/CodePreprocessing.cpp b/src/common/parsing/CodePreprocessing.cpp index aca81863..e48b475d 100644 --- a/src/common/parsing/CodePreprocessing.cpp +++ b/src/common/parsing/CodePreprocessing.cpp @@ -46,6 +46,23 @@ std::string sweepBlocks(const std::string& code, return result; } +std::string removeComments(const std::string& code) { + std::string result = code; + for (size_t pos = 0; pos < result.size(); pos++) { + auto nextComment = result.find("//", pos); + if (nextComment == std::string::npos) { + break; + } + auto commentEnd = result.find("\n", nextComment); + if (commentEnd == std::string::npos) { + commentEnd = result.size(); + } + std::string spaces(commentEnd - nextComment, ' '); + result.replace(nextComment, commentEnd - nextComment, spaces); + } + return result; +} + bool isFunctionDefinition(const std::string& line) { return startsWith(trim(line), "gate "); } @@ -112,20 +129,22 @@ std::vector sweepFunctionNames(const std::string& code) { return result; } -std::vector preprocessCode(const std::string& code) { - return preprocessCode(code, 0, 0, {}); +std::vector preprocessCode(const std::string& code, + std::string& processedCode) { + return preprocessCode(code, 0, 0, {}, processedCode); } std::vector preprocessCode(const std::string& code, size_t startIndex, size_t initialCodeOffset, - const std::vector& allFunctionNames) { + const std::vector& allFunctionNames, + std::string& processedCode) { std::map blocks; std::map functionFirstLine; std::map functionDefinitions; std::map> variableUsages; - const std::string blocksRemoved = sweepBlocks(code, blocks); + const std::string blocksRemoved = removeComments(sweepBlocks(code, blocks)); std::vector functionNames = sweepFunctionNames(code); for (const auto& name : allFunctionNames) { functionNames.push_back(name); @@ -178,8 +197,10 @@ preprocessCode(const std::string& code, size_t startIndex, const auto f = parseFunctionDefinition(line); functionDefinitions.insert({f.name, f}); i++; - auto subInstructions = preprocessCode( - block.code, i, code.find('{', trueStart) + 1, functionNames); + std::string processedSubCode; + auto subInstructions = + preprocessCode(block.code, i, code.find('{', trueStart) + 1, + functionNames, processedSubCode); for (auto& instr : subInstructions) { instr.inFunctionDefinition = true; } @@ -267,5 +288,6 @@ preprocessCode(const std::string& code, size_t startIndex, } } + processedCode = blocksRemoved; return instructions; } From ee3801cb25ebd7ffe6162573c259c118842df902 Mon Sep 17 00:00:00 2001 From: DRovara Date: Wed, 24 Jul 2024 14:39:28 +0200 Subject: [PATCH 07/37] test: :bug: Add end-to-end test cases --- app/code/test.qasm | 60 +++++-- src/mqt/debug/pydebug.pyi | 2 +- src/python/InterfaceBindings.cpp | 1 + test/python/resources/end-to-end/bell.out | 5 + test/python/resources/end-to-end/bell.qasm | 9 + test/python/resources/end-to-end/ghz_3.out | 5 + test/python/resources/end-to-end/ghz_3.qasm | 10 ++ test/python/resources/end-to-end/ghz_5.out | 5 + test/python/resources/end-to-end/ghz_5.qasm | 15 ++ test/python/resources/end-to-end/grover_3.out | 5 + .../python/resources/end-to-end/grover_3.qasm | 23 +++ test/python/resources/end-to-end/qpe.out | 5 + test/python/resources/end-to-end/qpe.qasm | 54 ++++++ test/python/test_end_to_end.py | 154 ++++++++++++++++++ test/python/test_python_bindings.py | 4 +- 15 files changed, 344 insertions(+), 13 deletions(-) create mode 100644 test/python/resources/end-to-end/bell.out create mode 100644 test/python/resources/end-to-end/bell.qasm create mode 100644 test/python/resources/end-to-end/ghz_3.out create mode 100644 test/python/resources/end-to-end/ghz_3.qasm create mode 100644 test/python/resources/end-to-end/ghz_5.out create mode 100644 test/python/resources/end-to-end/ghz_5.qasm create mode 100644 test/python/resources/end-to-end/grover_3.out create mode 100644 test/python/resources/end-to-end/grover_3.qasm create mode 100644 test/python/resources/end-to-end/qpe.out create mode 100644 test/python/resources/end-to-end/qpe.qasm create mode 100644 test/python/test_end_to_end.py diff --git a/app/code/test.qasm b/app/code/test.qasm index bd91842a..a27770a6 100644 --- a/app/code/test.qasm +++ b/app/code/test.qasm @@ -1,14 +1,54 @@ -qreg q[3]; -creg c[3]; +// iteratively estimating the phase of U=p(3pi/8) with 4-bit precision +// we seek theta=3/16=0.0011_2, which is exactly representable using 4 bits +// thus we expect to measure 0.c_3 c_2 c_1 c_0 = 0011 with certainty -h q[0]; -cx q[0], q[1]; -cx q[0], q[2]; +// counting register +qreg q[1]; -assert-ent q[0], q[1]; -assert-ent q[1], q[2]; -assert-ent q[0], q[2]; +// eigenstate register +qreg psi[1]; +// classical registers +creg c[4]; + +// initialize eigenstate psi = |1> +x psi; +barrier psi; + +// start by computing LSB c_0 +h q; +cp(8 * (3*pi/8)) psi, q; +h q; measure q[0] -> c[0]; -measure q[1] -> c[1]; -measure q[2] -> c[2]; + +// reset counting qubit and compute next bit c_1 +reset q; +h q; +cp(4 * (3*pi/8)) psi, q; +if (c == 1) p(-pi/2) q; +h q; +measure q[0] -> c[1]; + +// reset counting qubit and compute next bit c_2 +reset q; +h q; +cp(2 * (3*pi/8)) psi, q; +if (c == 1) p(1 * -pi/4) q; +if (c == 2) p(2 * -pi/4) q; +if (c == 3) p(3 * -pi/4) q; +h q; +measure q[0] -> c[2]; + +// reset counting qubit and compute MSB c_3 +reset q; +h q; +cp(1 * (3*pi/8)) psi, q; +if (c == 1) p(1 * -pi/8) q; +if (c == 2) p(2 * -pi/8) q; +if (c == 3) p(3 * -pi/8) q; +if (c == 4) p(4 * -pi/8) q; +if (c == 5) p(5 * -pi/8) q; +if (c == 6) p(6 * -pi/8) q; +if (c == 7) p(7 * -pi/8) q; +h q; +measure q[0] -> c[3]; diff --git a/src/mqt/debug/pydebug.pyi b/src/mqt/debug/pydebug.pyi index 86aeae6e..0a4303a0 100644 --- a/src/mqt/debug/pydebug.pyi +++ b/src/mqt/debug/pydebug.pyi @@ -26,7 +26,7 @@ class Complex: real: float imaginary: float - def __init__(self) -> None: ... + def __init__(self, real: float = 0.0, imaginary: float = 0.0) -> None: ... class Statevector: num_qubits: int diff --git a/src/python/InterfaceBindings.cpp b/src/python/InterfaceBindings.cpp index 99727b1d..3c7e9df9 100644 --- a/src/python/InterfaceBindings.cpp +++ b/src/python/InterfaceBindings.cpp @@ -47,6 +47,7 @@ void bindFramework(py::module& m) { // Bind the Complex struct py::class_(m, "Complex") .def(py::init<>()) + .def(py::init()) .def_readwrite("real", &Complex::real) .def_readwrite("imaginary", &Complex::imaginary) .def("__str__", diff --git a/test/python/resources/end-to-end/bell.out b/test/python/resources/end-to-end/bell.out new file mode 100644 index 00000000..2a3642fd --- /dev/null +++ b/test/python/resources/end-to-end/bell.out @@ -0,0 +1,5 @@ +0.707,0,0,0.707 +--- + +--- +0 diff --git a/test/python/resources/end-to-end/bell.qasm b/test/python/resources/end-to-end/bell.qasm new file mode 100644 index 00000000..0d247d41 --- /dev/null +++ b/test/python/resources/end-to-end/bell.qasm @@ -0,0 +1,9 @@ +qreg q[2]; + +h q[0]; +cx q[0], q[1]; + +assert-ent q[0], q[1]; +assert-sup q[0]; +assert-sup q[1]; +assert-sup q[0], q[1]; diff --git a/test/python/resources/end-to-end/ghz_3.out b/test/python/resources/end-to-end/ghz_3.out new file mode 100644 index 00000000..5b2c9a4c --- /dev/null +++ b/test/python/resources/end-to-end/ghz_3.out @@ -0,0 +1,5 @@ +0.707,0,0,0,0,0,0,0.707 +--- + +--- +0 diff --git a/test/python/resources/end-to-end/ghz_3.qasm b/test/python/resources/end-to-end/ghz_3.qasm new file mode 100644 index 00000000..fce2c888 --- /dev/null +++ b/test/python/resources/end-to-end/ghz_3.qasm @@ -0,0 +1,10 @@ +qreg q[3]; + +h q[0]; +cx q[0], q[1]; +cx q[0], q[2]; + +assert-ent q[0], q[1], q[2]; +assert-sup q[0]; +assert-sup q[1]; +assert-sup q[0], q[1]; diff --git a/test/python/resources/end-to-end/ghz_5.out b/test/python/resources/end-to-end/ghz_5.out new file mode 100644 index 00000000..37b2cec0 --- /dev/null +++ b/test/python/resources/end-to-end/ghz_5.out @@ -0,0 +1,5 @@ +0.707,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.707 +--- + +--- +0 diff --git a/test/python/resources/end-to-end/ghz_5.qasm b/test/python/resources/end-to-end/ghz_5.qasm new file mode 100644 index 00000000..8e272d5e --- /dev/null +++ b/test/python/resources/end-to-end/ghz_5.qasm @@ -0,0 +1,15 @@ +qreg q[5]; + +h q[0]; +cx q[0], q[1]; +cx q[1], q[2]; +cx q[2], q[3]; +cx q[3], q[4]; + +assert-ent q[0], q[1], q[2]; +assert-ent q[0], q[1], q[2], q[3], q[4]; +assert-sup q[0]; +assert-sup q[1]; +assert-sup q[2]; +assert-sup q[3]; +assert-sup q[4]; diff --git a/test/python/resources/end-to-end/grover_3.out b/test/python/resources/end-to-end/grover_3.out new file mode 100644 index 00000000..2cde65a1 --- /dev/null +++ b/test/python/resources/end-to-end/grover_3.out @@ -0,0 +1,5 @@ +0,0,0,0,0,0,0,-1 +--- +c[0] = 1,c[1] = 1 +--- +0 diff --git a/test/python/resources/end-to-end/grover_3.qasm b/test/python/resources/end-to-end/grover_3.qasm new file mode 100644 index 00000000..7b03c4ef --- /dev/null +++ b/test/python/resources/end-to-end/grover_3.qasm @@ -0,0 +1,23 @@ +qreg q[2]; +qreg flag[1]; +creg c[2]; + +// initialization +h q; +x flag; +barrier q; + +// oracle: mark target state |11> +ccz q[0], q[1], flag; +barrier q; + +// diffusion +h q; +x q; +h q[1]; +cx q[0], q[1]; +h q[1]; +x q; +h q; + +measure q -> c; diff --git a/test/python/resources/end-to-end/qpe.out b/test/python/resources/end-to-end/qpe.out new file mode 100644 index 00000000..9f7474b9 --- /dev/null +++ b/test/python/resources/end-to-end/qpe.out @@ -0,0 +1,5 @@ +0,0,1,0 +--- +c[0] = 1,c[1] = 1,c[2]=0,c[3]=0 +--- +0 diff --git a/test/python/resources/end-to-end/qpe.qasm b/test/python/resources/end-to-end/qpe.qasm new file mode 100644 index 00000000..a27770a6 --- /dev/null +++ b/test/python/resources/end-to-end/qpe.qasm @@ -0,0 +1,54 @@ +// iteratively estimating the phase of U=p(3pi/8) with 4-bit precision +// we seek theta=3/16=0.0011_2, which is exactly representable using 4 bits +// thus we expect to measure 0.c_3 c_2 c_1 c_0 = 0011 with certainty + +// counting register +qreg q[1]; + +// eigenstate register +qreg psi[1]; + +// classical registers +creg c[4]; + +// initialize eigenstate psi = |1> +x psi; +barrier psi; + +// start by computing LSB c_0 +h q; +cp(8 * (3*pi/8)) psi, q; +h q; +measure q[0] -> c[0]; + +// reset counting qubit and compute next bit c_1 +reset q; +h q; +cp(4 * (3*pi/8)) psi, q; +if (c == 1) p(-pi/2) q; +h q; +measure q[0] -> c[1]; + +// reset counting qubit and compute next bit c_2 +reset q; +h q; +cp(2 * (3*pi/8)) psi, q; +if (c == 1) p(1 * -pi/4) q; +if (c == 2) p(2 * -pi/4) q; +if (c == 3) p(3 * -pi/4) q; +h q; +measure q[0] -> c[2]; + +// reset counting qubit and compute MSB c_3 +reset q; +h q; +cp(1 * (3*pi/8)) psi, q; +if (c == 1) p(1 * -pi/8) q; +if (c == 2) p(2 * -pi/8) q; +if (c == 3) p(3 * -pi/8) q; +if (c == 4) p(4 * -pi/8) q; +if (c == 5) p(5 * -pi/8) q; +if (c == 6) p(6 * -pi/8) q; +if (c == 7) p(7 * -pi/8) q; +h q; +measure q[0] -> c[3]; diff --git a/test/python/test_end_to_end.py b/test/python/test_end_to_end.py new file mode 100644 index 00000000..d9c68d90 --- /dev/null +++ b/test/python/test_end_to_end.py @@ -0,0 +1,154 @@ +"""This module tests the end-to-end functionality of the simulator by running a set of QASM files and comparing their results with the expected ones.""" + +from __future__ import annotations + +import locale +from dataclasses import dataclass +from math import log2 +from pathlib import Path + +import pytest + +from mqt.debug import Complex, SimulationState, Statevector, create_ddsim_simulation_state + + +@dataclass +class ExpectedSolution: + """Represents the expected solution of a quantum program.""" + + statevector: Statevector + classical: dict[str, bool] + failed_assertions: int + + +def parse_complex(text: str) -> Complex: + """Parses a complex number from a string. + + Args: + text (str): The string to parse. + + Returns: + Complex: The complex number represented by the string. + """ + text = text.strip() + if "+" in text: + parts = text.split("+") + elif "-" in text: + parts = text.split("-") + else: + parts = [text] + real = 0.0 + imag = 0.0 + for part in parts: + if "i" in part: + imag += float(part.replace("i", "")) + else: + real += float(part) + return Complex(real, imag) + + +def parse_solution(text: str) -> ExpectedSolution: + """Parses the expected solution of a quantum program from a string. + + Args: + text (str): The string to parse. + + Returns: + ExpectedSolution: The expected solution of the quantum program. + """ + parts = text.replace("\n", " ").replace(" ", " ").split("---") + amplitudes = [parse_complex(x) for x in parts[0].split(",")] + sv = Statevector() + sv.amplitudes = amplitudes + sv.num_qubits = int(log2(len(amplitudes))) + sv.num_states = len(amplitudes) + + classical_values = parts[1].strip().split(",") if parts[1].strip() else [] + classical_dict: dict[str, bool] = {} + for value in classical_values: + key, val = value.split("=") + classical_dict[key.strip()] = val.strip() == "1" + + failed_assertions = int(parts[2].strip()) + + return ExpectedSolution(statevector=sv, classical=classical_dict, failed_assertions=failed_assertions) + + +def load_instance(name: str) -> tuple[SimulationState, ExpectedSolution]: + """Loads a quantum simulation and debugging instance from a file. + + Args: + name (str): The name of the instance to load. + + Returns: + tuple[SimulationState, ExpectedSolution]: The simulation state and the expected solution. + """ + state = create_ddsim_simulation_state() + with Path(f"test/python/resources/end-to-end/{name}.qasm").open(encoding=locale.getpreferredencoding(False)) as f: + state.load_code(f.read()) + with Path(f"test/python/resources/end-to-end/{name}.out").open(encoding=locale.getpreferredencoding(False)) as f: + solution = parse_solution(f.read()) + return (state, solution) + + +def complex_approximately_equal(a: Complex, b: Complex, epsilon: float = 1e-2) -> bool: + """Checks if two complex numbers are approximately equal. + + Args: + a (Complex): The first complex number. + b (Complex): The second complex number. + epsilon (float, optional): The comparison threshold. Defaults to 1e-2. + + Returns: + bool: True if the numbers are approximately equal, False otherwise. + """ + return abs(a.real - b.real) < epsilon and abs(a.imaginary - b.imaginary) < epsilon + + +def assert_statevectors_equal(result: Statevector, expected: Statevector) -> None: + """Asserts that two statevectors are equal. + + Args: + result (Statevector): The statevector to check. + expected (Statevector): The expected statevector. + """ + assert result.num_qubits == expected.num_qubits, f"Expected {expected.num_qubits} qubits, got {result.num_qubits}" + assert ( + result.num_states == expected.num_states + ), f"Expected {expected.num_states} amplitudes, got {result.num_states}" + for i in range(result.num_states): + assert complex_approximately_equal( + result.amplitudes[i], expected.amplitudes[i] + ), f"Expected amplitude {expected.amplitudes[i]} at index {i}, got {result.amplitudes[i]}" + + +@pytest.mark.parametrize( + "instance", + [ + "bell", + "ghz_3", + "ghz_5", + # "grover_3", # unknown gate czz, problems expanding gates over multiple qubits. + "qpe", + ], +) +def test_end_to_end(instance: str) -> None: + """Test multiple quantum programs end-to-end. + + Args: + instance (str): The name of the instance to test. + """ + state, solution = load_instance(instance) + errors = state.run_all() + assert ( + errors == solution.failed_assertions + ), f"Expected {solution.failed_assertions} failed assertions, got {errors}" + assert_statevectors_equal(state.get_state_vector_full(), solution.statevector) + + for key, value in solution.classical.items(): + try: + result_value = state.get_classical_variable(key).value.bool_value + except RuntimeError: + msg = f"Expected classical value {key} to be {value}, but it was not found" + raise AssertionError(msg) from None + assert value == result_value, f"Expected classical value {key} to be {value}, got {result_value}" diff --git a/test/python/test_python_bindings.py b/test/python/test_python_bindings.py index 25bc182d..7b64d89f 100644 --- a/test/python/test_python_bindings.py +++ b/test/python/test_python_bindings.py @@ -164,8 +164,8 @@ def test_instruction_positions(simulation_instance_jumps: SimulationInstance) -> """Tests the `get_instruction_position(instruction)` method.""" (simulation_state, _state_id) = simulation_instance_jumps assert simulation_state.get_instruction_position(0) == (0, 9) - assert simulation_state.get_instruction_position(1) == (12, 47) - assert simulation_state.get_instruction_position(16) == (241, 254) + assert simulation_state.get_instruction_position(1) == (12, 48) + assert simulation_state.get_instruction_position(16) == (240, 254) @pytest.mark.parametrize( From 27419cef1ed2dc925f02335edea47a57f44dba4b Mon Sep 17 00:00:00 2001 From: DRovara Date: Sun, 4 Aug 2024 19:34:35 +0200 Subject: [PATCH 08/37] test: :white_check_mark: Add more tests and fix minor bugs --- app/code/test.qasm | 58 ++------------- cmake/ExternalDependencies.cmake | 7 +- include/frontend/cli/CliFrontEnd.hpp | 3 +- src/backend/dd/DDSimDebug.cpp | 27 ++++++- src/backend/dd/DDSimDiagnostics.cpp | 8 ++- src/common/parsing/CodePreprocessing.cpp | 11 +-- src/frontend/cli/CliFrontEnd.cpp | 27 ++++--- .../diagnosis/control-always-zero.qasm | 21 ++++++ .../diagnosis/dependencies-with-jumps.qasm | 15 ++++ .../resources/diagnosis/dependencies.qasm | 13 ++++ .../diagnosis/missing-interaction.qasm | 6 ++ test/python/resources/end-to-end/bv.out | 4 ++ test/python/resources/end-to-end/bv.qasm | 17 +++++ test/python/resources/end-to-end/dj_4.out | 5 ++ test/python/resources/end-to-end/dj_4.qasm | 16 +++++ test/python/resources/end-to-end/fail_eq.out | 5 ++ test/python/resources/end-to-end/fail_eq.qasm | 16 +++++ test/python/resources/end-to-end/fail_ghz.out | 5 ++ .../python/resources/end-to-end/fail_ghz.qasm | 17 +++++ .../python/resources/end-to-end/grover_3.qasm | 4 +- test/python/test_diagnosis.py | 72 +++++++++++++++++++ test/python/test_end_to_end.py | 25 +++---- test/python/test_python_bindings.py | 4 +- 23 files changed, 293 insertions(+), 93 deletions(-) create mode 100644 test/python/resources/diagnosis/control-always-zero.qasm create mode 100644 test/python/resources/diagnosis/dependencies-with-jumps.qasm create mode 100644 test/python/resources/diagnosis/dependencies.qasm create mode 100644 test/python/resources/diagnosis/missing-interaction.qasm create mode 100644 test/python/resources/end-to-end/bv.out create mode 100644 test/python/resources/end-to-end/bv.qasm create mode 100644 test/python/resources/end-to-end/dj_4.out create mode 100644 test/python/resources/end-to-end/dj_4.qasm create mode 100644 test/python/resources/end-to-end/fail_eq.out create mode 100644 test/python/resources/end-to-end/fail_eq.qasm create mode 100644 test/python/resources/end-to-end/fail_ghz.out create mode 100644 test/python/resources/end-to-end/fail_ghz.qasm create mode 100644 test/python/test_diagnosis.py diff --git a/app/code/test.qasm b/app/code/test.qasm index a27770a6..dd0a7259 100644 --- a/app/code/test.qasm +++ b/app/code/test.qasm @@ -1,54 +1,6 @@ -// iteratively estimating the phase of U=p(3pi/8) with 4-bit precision -// we seek theta=3/16=0.0011_2, which is exactly representable using 4 bits -// thus we expect to measure 0.c_3 c_2 c_1 c_0 = 0011 with certainty +qreg q[4]; -// counting register -qreg q[1]; - -// eigenstate register -qreg psi[1]; - -// classical registers -creg c[4]; - -// initialize eigenstate psi = |1> -x psi; -barrier psi; - -// start by computing LSB c_0 -h q; -cp(8 * (3*pi/8)) psi, q; -h q; -measure q[0] -> c[0]; - -// reset counting qubit and compute next bit c_1 -reset q; -h q; -cp(4 * (3*pi/8)) psi, q; -if (c == 1) p(-pi/2) q; -h q; -measure q[0] -> c[1]; - -// reset counting qubit and compute next bit c_2 -reset q; -h q; -cp(2 * (3*pi/8)) psi, q; -if (c == 1) p(1 * -pi/4) q; -if (c == 2) p(2 * -pi/4) q; -if (c == 3) p(3 * -pi/4) q; -h q; -measure q[0] -> c[2]; - -// reset counting qubit and compute MSB c_3 -reset q; -h q; -cp(1 * (3*pi/8)) psi, q; -if (c == 1) p(1 * -pi/8) q; -if (c == 2) p(2 * -pi/8) q; -if (c == 3) p(3 * -pi/8) q; -if (c == 4) p(4 * -pi/8) q; -if (c == 5) p(5 * -pi/8) q; -if (c == 6) p(6 * -pi/8) q; -if (c == 7) p(7 * -pi/8) q; -h q; -measure q[0] -> c[3]; +cx q[0], q[1]; +cx q[1], q[2]; +assert-ent q[0], q[2]; // fails without missing interaction +assert-ent q[0], q[3]; // fails with missing interaction diff --git a/cmake/ExternalDependencies.cmake b/cmake/ExternalDependencies.cmake index 2008034b..e278fb04 100644 --- a/cmake/ExternalDependencies.cmake +++ b/cmake/ExternalDependencies.cmake @@ -18,9 +18,9 @@ if(BUILD_MQT_DEBUG_BINDINGS) endif() # cmake-format: off -set(MQT_CORE_VERSION 2.5.2 +set(MQT_CORE_VERSION 2.5.1 CACHE STRING "MQT Core version") -set(MQT_CORE_REV "3471f989648fda4237ffed94f80fdc35b5b1ee3b" +set(MQT_CORE_REV "44d836368f85d1883241ebd3beffa607c689e9a2" 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)") @@ -29,8 +29,7 @@ if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.24) FetchContent_Declare( mqt-core GIT_REPOSITORY https://github.com/${MQT_CORE_REPO_OWNER}/mqt-core.git - GIT_TAG ${MQT_CORE_REV} - FIND_PACKAGE_ARGS ${MQT_CORE_VERSION}) + GIT_TAG ${MQT_CORE_REV}) list(APPEND FETCH_PACKAGES mqt-core) else() find_package(mqt-core ${MQT_CORE_VERSION} QUIET) diff --git a/include/frontend/cli/CliFrontEnd.hpp b/include/frontend/cli/CliFrontEnd.hpp index 0caa970e..32e94b65 100644 --- a/include/frontend/cli/CliFrontEnd.hpp +++ b/include/frontend/cli/CliFrontEnd.hpp @@ -16,6 +16,7 @@ class CliFrontEnd { private: std::string currentCode; - void printState(SimulationState* state, size_t inspecting); + void printState(SimulationState* state, size_t inspecting, + bool codeOnly = false); void initCode(const char* code); }; diff --git a/src/backend/dd/DDSimDebug.cpp b/src/backend/dd/DDSimDebug.cpp index 7d92d6e0..ac3da4a3 100644 --- a/src/backend/dd/DDSimDebug.cpp +++ b/src/backend/dd/DDSimDebug.cpp @@ -118,7 +118,7 @@ Result ddsimLoadCode(SimulationState* self, const char* code) { try { std::stringstream ss{preprocessAssertionCode(code, ddsim)}; ddsim->qc->import(ss, qc::Format::OpenQASM3); - qc::CircuitOptimizer::flattenOperations(*ddsim->qc); + qc::CircuitOptimizer::flattenOperations(*ddsim->qc, true); } catch (const std::exception& e) { std::cerr << e.what() << "\n"; return ERROR; @@ -776,6 +776,21 @@ double complexMagnitude(Complex& c) { return std::sqrt(c.real * c.real + c.imaginary * c.imaginary); } +Complex complexDivision(const Complex& c1, const Complex& c2) { + const double denominator = c2.real * c2.real + c2.imaginary * c2.imaginary; + const double real = + (c1.real * c2.real + c1.imaginary * c2.imaginary) / denominator; + const double imaginary = + (c1.imaginary * c2.real - c1.real * c2.imaginary) / denominator; + return {real, imaginary}; +} + +bool areComplexEqual(const Complex& c1, const Complex& c2) { + const double epsilon = 0.00000001; + return std::abs(c1.real - c2.real) < epsilon && + std::abs(c1.imaginary - c2.imaginary) < epsilon; +} + double dotProduct(const Statevector& sv1, const Statevector& sv2) { double resultReal = 0; double resultImag = 0; @@ -801,6 +816,15 @@ bool areQubitsEntangled(Statevector* sv) { const bool canBe10 = complexMagnitude(amplitudes[2]) > epsilon; const bool canBe11 = complexMagnitude(amplitudes[3]) > epsilon; + const int nonZeroCount = (canBe00 ? 1 : 0) + (canBe01 ? 1 : 0) + + (canBe10 ? 1 : 0) + (canBe11 ? 1 : 0); + + if (nonZeroCount == 0) { + const auto c1 = complexDivision(amplitudes[0], amplitudes[2]); + const auto c2 = complexDivision(amplitudes[1], amplitudes[3]); + return !areComplexEqual(c1, c2); + } + return (canBe00 && canBe11 && !(canBe01 && canBe10)) || (canBe01 && canBe10 && !(canBe00 && canBe11)); } @@ -1045,6 +1069,7 @@ std::string preprocessAssertionCode(const char* code, } else if (instruction.code.find("qreg") != std::string::npos) { auto declaration = replaceString(instruction.code, "qreg", ""); declaration = replaceString(declaration, " ", ""); + declaration = replaceString(declaration, "\n", ""); declaration = replaceString(declaration, "\t", ""); declaration = replaceString(declaration, ";", ""); auto parts = splitString(declaration, '['); diff --git a/src/backend/dd/DDSimDiagnostics.cpp b/src/backend/dd/DDSimDiagnostics.cpp index 53508030..5b7322b8 100644 --- a/src/backend/dd/DDSimDiagnostics.cpp +++ b/src/backend/dd/DDSimDiagnostics.cpp @@ -130,14 +130,20 @@ size_t tryFindMissingInteraction(DDDiagnostics* diagnostics, return variableToQubit(state, target); }); + std::map> allInteractions; + for (size_t i = 0; i < targets.size(); i++) { std::vector interactions( diagnostics->interface.getNumQubits(&diagnostics->interface)); diagnostics->interface.getInteractions( &diagnostics->interface, instruction, targetQubits[i], reinterpret_cast(interactions.data())); + allInteractions.insert({targetQubits[i], interactions}); + } + for (size_t i = 0; i < targets.size(); i++) { for (size_t j = i + 1; j < targets.size(); j++) { - if (interactions[targetQubits[j]] == 0) { + if (allInteractions[targetQubits[i]][targetQubits[j]] == 0 && + allInteractions[targetQubits[j]][targetQubits[i]] == 0) { outputs[index].type = ErrorCauseType::MissingInteraction; outputs[index].instruction = instruction; index++; diff --git a/src/common/parsing/CodePreprocessing.cpp b/src/common/parsing/CodePreprocessing.cpp index e48b475d..bbf6a979 100644 --- a/src/common/parsing/CodePreprocessing.cpp +++ b/src/common/parsing/CodePreprocessing.cpp @@ -144,7 +144,8 @@ preprocessCode(const std::string& code, size_t startIndex, std::map functionDefinitions; std::map> variableUsages; - const std::string blocksRemoved = removeComments(sweepBlocks(code, blocks)); + processedCode = removeComments(code); + const std::string blocksRemoved = sweepBlocks(processedCode, blocks); std::vector functionNames = sweepFunctionNames(code); for (const auto& name : allFunctionNames) { functionNames.push_back(name); @@ -256,7 +257,8 @@ preprocessCode(const std::string& code, size_t startIndex, for (auto& instr : instructions) { auto vars = parseParameters(instr.code); size_t idx = instr.lineNumber - 1; - while (!vars.empty() && idx < instructions.size()) { + while (!vars.empty() && (instr.lineNumber < instructions.size() || + idx > instr.lineNumber - instructions.size())) { bool found = false; for (const auto& var : variableUsages[idx]) { if (std::find(vars.begin(), vars.end(), var) != vars.end()) { @@ -267,6 +269,9 @@ preprocessCode(const std::string& code, size_t startIndex, if (found) { instr.dataDependencies.push_back(idx); } + if (idx - 1 == instr.lineNumber - instructions.size()) { + break; + } idx--; } if (instr.isFunctionCall) { @@ -287,7 +292,5 @@ preprocessCode(const std::string& code, size_t startIndex, } } } - - processedCode = blocksRemoved; return instructions; } diff --git a/src/frontend/cli/CliFrontEnd.cpp b/src/frontend/cli/CliFrontEnd.cpp index 9eb1468d..4876e8a8 100644 --- a/src/frontend/cli/CliFrontEnd.cpp +++ b/src/frontend/cli/CliFrontEnd.cpp @@ -4,6 +4,7 @@ #include "frontend/cli/CliFrontEnd.hpp" +#include "backend/dd/DDSimDebug.hpp" #include "common/parsing/Utils.hpp" #include @@ -22,6 +23,9 @@ void CliFrontEnd::run(const char* code, SimulationState* state) { std::string command; const auto result = state->loadCode(state, code); + // const auto* ddsim = reinterpret_cast(state); + // std::cout << ddsim->processedCode; + // std::cin >> command; state->resetSimulation(state); if (result == ERROR) { std::cout << "Error loading code\n"; @@ -30,7 +34,7 @@ void CliFrontEnd::run(const char* code, SimulationState* state) { bool wasError = false; bool wasGet = false; - size_t inspecting = -1ULL; + size_t inspecting = 23870; while (command != "exit") { clearScreen(); @@ -68,7 +72,7 @@ void CliFrontEnd::run(const char* code, SimulationState* state) { } wasGet = false; } - printState(state, inspecting); + printState(state, inspecting, true); std::cout << "Enter command: "; std::getline(std::cin, command); @@ -101,7 +105,8 @@ void CliFrontEnd::run(const char* code, SimulationState* state) { } } -void CliFrontEnd::printState(SimulationState* state, size_t inspecting) { +void CliFrontEnd::printState(SimulationState* state, size_t inspecting, + bool codeOnly) { std::vector highlightIntervals; if (inspecting != -1ULL) { std::vector inspectingDependencies( @@ -157,14 +162,16 @@ void CliFrontEnd::printState(SimulationState* state, size_t inspecting) { } std::cout << "\n"; - const std::array bitStrings = {"000", "001", "010", "011", - "100", "101", "110", "111"}; - Complex c; - for (const auto* bitString : bitStrings) { - state->getAmplitudeBitstring(state, bitString, &c); - std::cout << bitString << " " << c.real << "\t||\t"; + if (!codeOnly) { + const std::array bitStrings = {"000", "001", "010", "011", + "100", "101", "110", "111"}; + Complex c; + for (const auto* bitString : bitStrings) { + state->getAmplitudeBitstring(state, bitString, &c); + std::cout << bitString << " " << c.real << "\t||\t"; + } + std::cout << "\n"; } - std::cout << "\n"; if (state->didAssertionFail(state)) { std::cout << "THIS LINE FAILED AN ASSERTION\n"; } diff --git a/test/python/resources/diagnosis/control-always-zero.qasm b/test/python/resources/diagnosis/control-always-zero.qasm new file mode 100644 index 00000000..f5b9b32a --- /dev/null +++ b/test/python/resources/diagnosis/control-always-zero.qasm @@ -0,0 +1,21 @@ +qreg q[3]; + +x q[0]; + +gate test1 q0, q1 { + cx q0, q1; +} + +gate test2 q0, q1 { + cx q0, q1; +} + +test1 q[2], q[1]; +test1 q[1], q[2]; + +test2 q[0], q[2]; +test2 q[1], q[2]; + +cx q[1], q[2]; + +assert-eq q[0], q[1], q[2] { 0, 0, 0, 0, 0, 0, 0, 0 } diff --git a/test/python/resources/diagnosis/dependencies-with-jumps.qasm b/test/python/resources/diagnosis/dependencies-with-jumps.qasm new file mode 100644 index 00000000..a0b56a0c --- /dev/null +++ b/test/python/resources/diagnosis/dependencies-with-jumps.qasm @@ -0,0 +1,15 @@ +qreg q[3]; + +gate entangle q0, q1, q2 { + cx q0, q1; + cx q0, q2; + barrier q2; +} + +h q[0]; + +entangle q[0], q[1], q[2]; + +h q[2]; + +barrier q[2]; diff --git a/test/python/resources/diagnosis/dependencies.qasm b/test/python/resources/diagnosis/dependencies.qasm new file mode 100644 index 00000000..0521769b --- /dev/null +++ b/test/python/resources/diagnosis/dependencies.qasm @@ -0,0 +1,13 @@ +qreg q[3]; + +h q[2]; + +cx q[0], q[2]; +cx q[0], q[1]; + +h q[0]; + +cx q[0], q[2]; + +barrier q[1]; +barrier q[2]; diff --git a/test/python/resources/diagnosis/missing-interaction.qasm b/test/python/resources/diagnosis/missing-interaction.qasm new file mode 100644 index 00000000..dd0a7259 --- /dev/null +++ b/test/python/resources/diagnosis/missing-interaction.qasm @@ -0,0 +1,6 @@ +qreg q[4]; + +cx q[0], q[1]; +cx q[1], q[2]; +assert-ent q[0], q[2]; // fails without missing interaction +assert-ent q[0], q[3]; // fails with missing interaction diff --git a/test/python/resources/end-to-end/bv.out b/test/python/resources/end-to-end/bv.out new file mode 100644 index 00000000..1e7f3b31 --- /dev/null +++ b/test/python/resources/end-to-end/bv.out @@ -0,0 +1,4 @@ +0, 0, 0, 0, 0, 0.707, 0, 0, 0, 0, 0, 0, 0, -0.707, 0, 0 +--- +--- +0 diff --git a/test/python/resources/end-to-end/bv.qasm b/test/python/resources/end-to-end/bv.qasm new file mode 100644 index 00000000..13420810 --- /dev/null +++ b/test/python/resources/end-to-end/bv.qasm @@ -0,0 +1,17 @@ +qreg q[3]; +qreg ancilla[1]; + +gate oracle q0, q1, q2, anc { + cx q0, anc; + cx q2, anc; +} + +h q; +x ancilla; +h ancilla; + +oracle q[0], q[1], q[2], ancilla; + +h q; + +assert-eq 0.9, q[0], q[1], q[2], ancilla { 0, 0, 0, 0, 0, 0.707, 0, 0, 0, 0, 0, 0, 0, -0.707, 0, 0 } diff --git a/test/python/resources/end-to-end/dj_4.out b/test/python/resources/end-to-end/dj_4.out new file mode 100644 index 00000000..6b1bf5e8 --- /dev/null +++ b/test/python/resources/end-to-end/dj_4.out @@ -0,0 +1,5 @@ +0,0,0,0,0,0,0,0.707,0,0,0,0,0,0,0,-0.707 +--- +c[0]=1,c[1]=1,c[2]=1 +--- +0 diff --git a/test/python/resources/end-to-end/dj_4.qasm b/test/python/resources/end-to-end/dj_4.qasm new file mode 100644 index 00000000..2bbe03e6 --- /dev/null +++ b/test/python/resources/end-to-end/dj_4.qasm @@ -0,0 +1,16 @@ +qreg q[4]; +creg c[3]; +u2(0,0) q[0]; +u2(0,0) q[1]; +h q[2]; +u2(-pi,-pi) q[3]; +cx q[0],q[3]; +u2(-pi,-pi) q[0]; +cx q[1],q[3]; +u2(-pi,-pi) q[1]; +cx q[2],q[3]; +h q[2]; +barrier q[0],q[1],q[2],q[3]; +measure q[0] -> c[0]; +measure q[1] -> c[1]; +measure q[2] -> c[2]; diff --git a/test/python/resources/end-to-end/fail_eq.out b/test/python/resources/end-to-end/fail_eq.out new file mode 100644 index 00000000..7fcc0ea6 --- /dev/null +++ b/test/python/resources/end-to-end/fail_eq.out @@ -0,0 +1,5 @@ +0, 0.707, 0, 0.707 +--- + +--- +7 diff --git a/test/python/resources/end-to-end/fail_eq.qasm b/test/python/resources/end-to-end/fail_eq.qasm new file mode 100644 index 00000000..ff525f4c --- /dev/null +++ b/test/python/resources/end-to-end/fail_eq.qasm @@ -0,0 +1,16 @@ +qreg q[2]; + +x q[0]; +h q[1]; + +assert-eq q[0], q[1] { 0, 0.707, 0, 0.707} // should fail +assert-eq 1.0, q[0], q[1] { 0, 0.707, 0, 0.707} // should fail +assert-eq 0.9, q[0], q[1] { 0, 0.707, 0, 0.707} // should pass + +assert-eq q[1], q[0] { 0, 0.707, 0, 0.707} // should fail +assert-eq 1.0, q[1], q[0] { 0, 0.707, 0, 0.707} // should fail +assert-eq 0.9, q[1], q[0] { 0, 0.707, 0, 0.707} // should fail + +assert-eq q[1], q[0] { 0, 0, 0.707, 0.707} // should fail +assert-eq 1.0, q[1], q[0] { 0, 0, 0.707, 0.707} // should fail +assert-eq 0.9, q[1], q[0] { 0, 0, 0.707, 0.707} // should pass diff --git a/test/python/resources/end-to-end/fail_ghz.out b/test/python/resources/end-to-end/fail_ghz.out new file mode 100644 index 00000000..688a9231 --- /dev/null +++ b/test/python/resources/end-to-end/fail_ghz.out @@ -0,0 +1,5 @@ +0.707,0,0,0.707,0,0,0,0 +--- + +--- +4 diff --git a/test/python/resources/end-to-end/fail_ghz.qasm b/test/python/resources/end-to-end/fail_ghz.qasm new file mode 100644 index 00000000..358a90e0 --- /dev/null +++ b/test/python/resources/end-to-end/fail_ghz.qasm @@ -0,0 +1,17 @@ +qreg q[3]; + +h q[0]; +cx q[0], q[1]; +cx q[2], q[0]; + +assert-ent q[0], q[1], q[2]; +assert-ent q[0], q[1]; +assert-ent q[0], q[2]; +assert-ent q[1], q[2]; +assert-sup q[0]; +assert-sup q[1]; +assert-sup q[2]; +assert-sup q[0], q[1]; +assert-sup q[0], q[2]; +assert-sup q[1], q[2]; +assert-sup q[0], q[1], q[2]; diff --git a/test/python/resources/end-to-end/grover_3.qasm b/test/python/resources/end-to-end/grover_3.qasm index 7b03c4ef..959ab2a8 100644 --- a/test/python/resources/end-to-end/grover_3.qasm +++ b/test/python/resources/end-to-end/grover_3.qasm @@ -8,7 +8,9 @@ x flag; barrier q; // oracle: mark target state |11> -ccz q[0], q[1], flag; +h flag; +ccx q[0], q[1], flag; +h flag; barrier q; // diffusion diff --git a/test/python/test_diagnosis.py b/test/python/test_diagnosis.py new file mode 100644 index 00000000..e9beab09 --- /dev/null +++ b/test/python/test_diagnosis.py @@ -0,0 +1,72 @@ +"""Tests diagnosis methods of the debugger.""" + +from __future__ import annotations + +import locale +from pathlib import Path + +from mqt.debug import ( + ErrorCauseType, + SimulationState, + create_ddsim_simulation_state, + destroy_ddsim_simulation_state, +) + + +def load_instance(name: str) -> SimulationState: + """Loads a quantum simulation and debugging instance from a file. + + Args: + name (str): The name of the instance to load. + + Returns: + SimulationState: The generated simulation state. + """ + state = create_ddsim_simulation_state() + with Path(f"test/python/resources/diagnosis/{name}.qasm").open(encoding=locale.getpreferredencoding(False)) as f: + state.load_code(f.read()) + return state + + +def test_data_dependencies() -> None: + """Test the data dependency analysis.""" + s = load_instance("dependencies") + dependencies = s.get_diagnostics().get_data_dependencies(6) + assert dependencies == [1, 2, 3, 6] + dependencies = s.get_diagnostics().get_data_dependencies(7) + assert dependencies == [1, 2, 3, 4, 5, 7] + destroy_ddsim_simulation_state(s) + + +def test_data_dependencies_jumps() -> None: + """Test the data dependency analysis in code with jumps.""" + s = load_instance("dependencies-with-jumps") + dependencies = s.get_diagnostics().get_data_dependencies(4) + assert dependencies == [2, 3, 4] + dependencies = s.get_diagnostics().get_data_dependencies(9) + assert dependencies == [6, 7, 8, 9] + destroy_ddsim_simulation_state(s) + + +def test_control_always_zero() -> None: + """Test the control-always-zero error diagnosis.""" + s = load_instance("control-always-zero") + s.run_simulation() + causes = s.get_diagnostics().potential_error_causes() + + assert len(causes) == 1 # once diagnosis can step into jumps, this should be 2 + + assert causes[0].type == ErrorCauseType.ControlAlwaysZero + assert causes[0].instruction == 12 + + +def test_missing_interaction() -> None: + """Test the missing-interaction error diagnosis.""" + s = load_instance("missing-interaction") + s.run_simulation() + causes = [x for x in s.get_diagnostics().potential_error_causes() if x.type == ErrorCauseType.MissingInteraction] + assert len(causes) == 0 + s.run_simulation() + causes = [x for x in s.get_diagnostics().potential_error_causes() if x.type == ErrorCauseType.MissingInteraction] + assert len(causes) == 1 + assert causes[0].instruction == 4 diff --git a/test/python/test_end_to_end.py b/test/python/test_end_to_end.py index d9c68d90..5993cd33 100644 --- a/test/python/test_end_to_end.py +++ b/test/python/test_end_to_end.py @@ -10,6 +10,7 @@ import pytest from mqt.debug import Complex, SimulationState, Statevector, create_ddsim_simulation_state +from mqt.debug.pydebug import destroy_ddsim_simulation_state @dataclass @@ -30,21 +31,8 @@ def parse_complex(text: str) -> Complex: Returns: Complex: The complex number represented by the string. """ - text = text.strip() - if "+" in text: - parts = text.split("+") - elif "-" in text: - parts = text.split("-") - else: - parts = [text] - real = 0.0 - imag = 0.0 - for part in parts: - if "i" in part: - imag += float(part.replace("i", "")) - else: - real += float(part) - return Complex(real, imag) + c = complex(text) + return Complex(c.real, c.imag) def parse_solution(text: str) -> ExpectedSolution: @@ -128,8 +116,12 @@ def assert_statevectors_equal(result: Statevector, expected: Statevector) -> Non "bell", "ghz_3", "ghz_5", - # "grover_3", # unknown gate czz, problems expanding gates over multiple qubits. + "grover_3", "qpe", + "dj_4", + "bv", + "fail_ghz", + "fail_eq", ], ) def test_end_to_end(instance: str) -> None: @@ -152,3 +144,4 @@ def test_end_to_end(instance: str) -> None: msg = f"Expected classical value {key} to be {value}, but it was not found" raise AssertionError(msg) from None assert value == result_value, f"Expected classical value {key} to be {value}, got {result_value}" + destroy_ddsim_simulation_state(state) diff --git a/test/python/test_python_bindings.py b/test/python/test_python_bindings.py index 7b64d89f..25bc182d 100644 --- a/test/python/test_python_bindings.py +++ b/test/python/test_python_bindings.py @@ -164,8 +164,8 @@ def test_instruction_positions(simulation_instance_jumps: SimulationInstance) -> """Tests the `get_instruction_position(instruction)` method.""" (simulation_state, _state_id) = simulation_instance_jumps assert simulation_state.get_instruction_position(0) == (0, 9) - assert simulation_state.get_instruction_position(1) == (12, 48) - assert simulation_state.get_instruction_position(16) == (240, 254) + assert simulation_state.get_instruction_position(1) == (12, 47) + assert simulation_state.get_instruction_position(16) == (241, 254) @pytest.mark.parametrize( From c396d82591fc873e601232ce344e24623c508be2 Mon Sep 17 00:00:00 2001 From: DRovara Date: Mon, 5 Aug 2024 16:03:25 +0200 Subject: [PATCH 09/37] revert: :recycle: Revert debugging-changes to CliFrontEnd.cpp that slid into the commit --- src/frontend/cli/CliFrontEnd.cpp | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/frontend/cli/CliFrontEnd.cpp b/src/frontend/cli/CliFrontEnd.cpp index 4876e8a8..1eb4ab0d 100644 --- a/src/frontend/cli/CliFrontEnd.cpp +++ b/src/frontend/cli/CliFrontEnd.cpp @@ -4,7 +4,6 @@ #include "frontend/cli/CliFrontEnd.hpp" -#include "backend/dd/DDSimDebug.hpp" #include "common/parsing/Utils.hpp" #include @@ -23,9 +22,6 @@ void CliFrontEnd::run(const char* code, SimulationState* state) { std::string command; const auto result = state->loadCode(state, code); - // const auto* ddsim = reinterpret_cast(state); - // std::cout << ddsim->processedCode; - // std::cin >> command; state->resetSimulation(state); if (result == ERROR) { std::cout << "Error loading code\n"; @@ -34,7 +30,7 @@ void CliFrontEnd::run(const char* code, SimulationState* state) { bool wasError = false; bool wasGet = false; - size_t inspecting = 23870; + size_t inspecting = -1ULL; while (command != "exit") { clearScreen(); @@ -72,7 +68,7 @@ void CliFrontEnd::run(const char* code, SimulationState* state) { } wasGet = false; } - printState(state, inspecting, true); + printState(state, inspecting, state->getNumQubits(state) != 3); std::cout << "Enter command: "; std::getline(std::cin, command); From e7bd98c81ce7cd9e71d9139466220649806539ab Mon Sep 17 00:00:00 2001 From: DRovara Date: Mon, 5 Aug 2024 16:04:18 +0200 Subject: [PATCH 10/37] fix: :bug: Fix DAP Server missing some messages if multiple messages are sent at once --- src/mqt/debug/dap_server.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/mqt/debug/dap_server.py b/src/mqt/debug/dap_server.py index 57274b34..34f51b9b 100644 --- a/src/mqt/debug/dap_server.py +++ b/src/mqt/debug/dap_server.py @@ -129,9 +129,23 @@ def handle_client(self, connection: socket.socket) -> None: Args: connection (socket.socket): The client socket. """ + data_str = "" + message_str = "" while True: - data = connection.recv(1024) - parts = data.decode().split("\n") + if not message_str or not data_str: + data = connection.recv(1024) + data_str += data.decode() + first_end = data_str.find("Content-Length:", 1) + if first_end != -1: + message_str = data_str[:first_end] + data_str = data_str[first_end:] + elif data_str.count("{") == data_str.count("}"): + message_str = data_str + data_str = "" + else: + message_str = "" + continue + parts = message_str.split("\n") if not parts or not data: break payload = json.loads(parts[-1]) From 47722c0b8f83ace8ef30d7d42ed3ac29c3c51d2f Mon Sep 17 00:00:00 2001 From: DRovara Date: Mon, 5 Aug 2024 16:04:54 +0200 Subject: [PATCH 11/37] fix: :bug: Fix bug that makes breakpoints coming right after a "return" be ignored --- app/code/test.qasm | 39 ++++++++++++++++++++++++++++++----- src/backend/dd/DDSimDebug.cpp | 10 +++++---- 2 files changed, 40 insertions(+), 9 deletions(-) diff --git a/app/code/test.qasm b/app/code/test.qasm index dd0a7259..30ea2bf7 100644 --- a/app/code/test.qasm +++ b/app/code/test.qasm @@ -1,6 +1,35 @@ -qreg q[4]; +gate oracle q0, q1, q2, flag { + // mark target state |111> + assert-sup q0, q1, q2; + mcphase(pi) q0, q1, q2, flag; +} -cx q[0], q[1]; -cx q[1], q[2]; -assert-ent q[0], q[2]; // fails without missing interaction -assert-ent q[0], q[3]; // fails with missing interaction +gate diffusion q0, q1, q2 { + h q0; h q1; h q2; + x q0; x q1; x q2; + h q2; + ccx q0, q1, q2; + h q2; + x q2; x q1; x q0; + h q2; h q1; h q0; +} + +qreg q[3]; +qreg flag[1]; +creg c[3]; + +// initialization +h q; +x flag; + +// repetition 1 +oracle q[0], q[1], q[2], flag; +diffusion q[0], q[1], q[2]; +assert-eq 0.8, q[0], q[1], q[2] { 0, 0, 0, 0, 0, 0, 0, 1 } + +// repetition 2 +oracle q[0], q[1], q[2], flag; +diffusion q[0], q[1], q[2]; +assert-eq 0.9, q[0], q[1], q[2] { 0, 0, 0, 0, 0, 0, 0, 1 } + +measure q -> c; diff --git a/src/backend/dd/DDSimDebug.cpp b/src/backend/dd/DDSimDebug.cpp index ac3da4a3..849cb195 100644 --- a/src/backend/dd/DDSimDebug.cpp +++ b/src/backend/dd/DDSimDebug.cpp @@ -252,9 +252,6 @@ Result ddsimStepForward(SimulationState* self) { const auto currentInstruction = ddsim->currentInstruction; dddiagnosticsOnStepForward(&ddsim->diagnostics, currentInstruction); ddsim->currentInstruction = ddsim->successorInstructions[currentInstruction]; - if (ddsim->breakpoints.contains(ddsim->currentInstruction)) { - ddsim->lastMetBreakpoint = ddsim->currentInstruction; - } if (ddsim->currentInstruction == 0) { ddsim->currentInstruction = ddsim->callReturnStack.back() + 1; @@ -262,6 +259,11 @@ Result ddsimStepForward(SimulationState* self) { ddsim->callReturnStack.back()); ddsim->callReturnStack.pop_back(); } + + if (ddsim->breakpoints.contains(ddsim->currentInstruction)) { + ddsim->lastMetBreakpoint = ddsim->currentInstruction; + } + if (ddsim->instructionTypes[currentInstruction] == CALL) { ddsim->callReturnStack.push_back(currentInstruction); } @@ -473,7 +475,7 @@ Result ddsimRunSimulation(SimulationState* self) { ddsim->paused = false; return OK; } - Result res = self->stepForward(self); + const Result res = self->stepForward(self); if (res != OK) { return res; } From 96916b5284238a5fe6932597904614d9b78f4c72 Mon Sep 17 00:00:00 2001 From: DRovara Date: Mon, 5 Aug 2024 16:57:56 +0200 Subject: [PATCH 12/37] fix: :bug: Fix quotation marks used in f-string --- src/mqt/debug/dap_server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mqt/debug/dap_server.py b/src/mqt/debug/dap_server.py index 34f51b9b..86e1f85f 100644 --- a/src/mqt/debug/dap_server.py +++ b/src/mqt/debug/dap_server.py @@ -280,7 +280,7 @@ def handle_assertion_fail(self, connection: socket.socket) -> None: error_causes_body = "○ No potential error causes found" else: error_causes_body = { - "title": f"Found {len(error_causes)} potential error cause{"s" if len(error_causes) > 1 else ""}:", + "title": f"Found {len(error_causes)} potential error cause{'s' if len(error_causes) > 1 else ''}:", "body": [f"({i + 1}) {msg}" for i, msg in enumerate(error_cause_messages)], "end": None, } From 5ee33c8a3599f67e7315481f636cea83788f37ea Mon Sep 17 00:00:00 2001 From: DRovara Date: Mon, 5 Aug 2024 16:58:23 +0200 Subject: [PATCH 13/37] feat: :construction_worker: Add noxfile for organised testing --- noxfile.py | 123 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 noxfile.py diff --git a/noxfile.py b/noxfile.py new file mode 100644 index 00000000..f59d35c9 --- /dev/null +++ b/noxfile.py @@ -0,0 +1,123 @@ +"""Nox sessions.""" + +from __future__ import annotations + +import argparse +import os +import shutil +import sys +from typing import TYPE_CHECKING + +import nox + +if TYPE_CHECKING: + from collections.abc import Sequence + +nox.needs_version = ">=2024.3.2" +nox.options.default_venv_backend = "uv|virtualenv" + +nox.options.sessions = ["lint", "tests"] + +PYTHON_ALL_VERSIONS = ["3.8", "3.9", "3.10", "3.11", "3.12"] + +BUILD_REQUIREMENTS = [ + "scikit-build-core[pyproject]>=0.8.1", + "setuptools_scm>=8.1", + "setuptools>=66.1", + "wheel>=0.40.0", +] + +if os.environ.get("CI", None): + nox.options.error_on_missing_interpreters = True + + +@nox.session(reuse_venv=True) +def lint(session: nox.Session) -> None: + """Lint the Python part of the codebase using pre-commit. + + Simply execute `nox -rs lint` to run all configured hooks. + """ + session.install("pre-commit") + session.run("pre-commit", "run", "--all-files", *session.posargs) + + +def _run_tests( + session: nox.Session, + *, + install_args: Sequence[str] = (), + run_args: Sequence[str] = (), + extras: Sequence[str] = (), +) -> None: + posargs = list(session.posargs) + env = {"PIP_DISABLE_PIP_VERSION_CHECK": "1"} + + if os.environ.get("CI", None) and sys.platform == "win32": + env["SKBUILD_CMAKE_ARGS"] = "-T ClangCL" + + if shutil.which("cmake") is None and shutil.which("cmake3") is None: + session.install("cmake") + if shutil.which("ninja") is None: + session.install("ninja") + + _extras = ["test", *extras] + if "--cov" in posargs: + _extras.append("coverage") + posargs.append("--cov-config=pyproject.toml") + + session.install(*BUILD_REQUIREMENTS, *install_args, env=env) + install_arg = f"-ve.[{','.join(_extras)}]" + session.install("--no-build-isolation", install_arg, *install_args, env=env) + session.run("pytest", *run_args, *posargs, env=env) + + +@nox.session(reuse_venv=True, python=PYTHON_ALL_VERSIONS) +def tests(session: nox.Session) -> None: + """Run the test suite.""" + # enable profile check when running locally or when running on Linux with Python 3.12 in CI + if os.environ.get("CI", None) is None or (sys.platform == "linux" and session.python == "3.12"): + session.env["CHECK_PROFILES"] = "1" + + _run_tests(session) + + +@nox.session(reuse_venv=True, venv_backend="uv", python=PYTHON_ALL_VERSIONS) +def minimums(session: nox.Session) -> None: + """Test the minimum versions of dependencies.""" + _run_tests( + session, + install_args=["--resolution=lowest-direct"], + run_args=["-Wdefault"], + ) + session.run("uv", "pip", "list") + + +@nox.session(reuse_venv=True) +def docs(session: nox.Session) -> None: + """Build the docs. Use "--non-interactive" to avoid serving. Pass "-b linkcheck" to check links.""" + parser = argparse.ArgumentParser() + parser.add_argument("-b", dest="builder", default="html", help="Build target (default: html)") + args, posargs = parser.parse_known_args(session.posargs) + + serve = args.builder == "html" and session.interactive + extra_installs = ["sphinx-autobuild"] if serve else [] + session.install(*BUILD_REQUIREMENTS, *extra_installs) + session.install("--no-build-isolation", "-ve.[docs]") + session.chdir("docs") + + if args.builder == "linkcheck": + session.run("sphinx-build", "-b", "linkcheck", ".", "_build/linkcheck", *posargs) + return + + shared_args = ( + "-n", # nitpicky mode + "-T", # full tracebacks + f"-b={args.builder}", + ".", + f"_build/{args.builder}", + *posargs, + ) + + if serve: + session.run("sphinx-autobuild", *shared_args) + else: + session.run("sphinx-build", "--keep-going", *shared_args) From 6e0aefa4c9a1fabc4b18984a58e4ef265ee9e301 Mon Sep 17 00:00:00 2001 From: DRovara Date: Mon, 5 Aug 2024 17:02:33 +0200 Subject: [PATCH 14/37] fix: :bug: Remove unused variable in CodePreprocessing --- src/common/parsing/CodePreprocessing.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/parsing/CodePreprocessing.cpp b/src/common/parsing/CodePreprocessing.cpp index bbf6a979..167e8333 100644 --- a/src/common/parsing/CodePreprocessing.cpp +++ b/src/common/parsing/CodePreprocessing.cpp @@ -263,7 +263,7 @@ preprocessCode(const std::string& code, size_t startIndex, for (const auto& var : variableUsages[idx]) { if (std::find(vars.begin(), vars.end(), var) != vars.end()) { found = true; - const auto rem = std::remove(vars.begin(), vars.end(), var); + std::remove(vars.begin(), vars.end(), var); } } if (found) { From d761bab3022b99a8912ed0762d1abf6e7d31109d Mon Sep 17 00:00:00 2001 From: DRovara Date: Tue, 6 Aug 2024 16:42:51 +0200 Subject: [PATCH 15/37] fix: :bug: Fix error that makes custom gate definitions not recognised if the previous line contains a comment --- src/common/parsing/CodePreprocessing.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/common/parsing/CodePreprocessing.cpp b/src/common/parsing/CodePreprocessing.cpp index 167e8333..722b22e2 100644 --- a/src/common/parsing/CodePreprocessing.cpp +++ b/src/common/parsing/CodePreprocessing.cpp @@ -146,7 +146,7 @@ preprocessCode(const std::string& code, size_t startIndex, processedCode = removeComments(code); const std::string blocksRemoved = sweepBlocks(processedCode, blocks); - std::vector functionNames = sweepFunctionNames(code); + std::vector functionNames = sweepFunctionNames(processedCode); for (const auto& name : allFunctionNames) { functionNames.push_back(name); } @@ -223,7 +223,7 @@ preprocessCode(const std::string& code, size_t startIndex, '}', instructions[instructions.size() - 1].originalCodeEndPosition); const Block noBlock{false, ""}; instructions.emplace_back(i, "RETURN", a, targets, closingBrace, - closingBrace + 1, 0, false, "", true, noBlock); + closingBrace, 0, false, "", true, noBlock); i++; pos = end + 1; From 25a4f668caf21a7f53f428b6e9745103562117d9 Mon Sep 17 00:00:00 2001 From: DRovara Date: Tue, 6 Aug 2024 16:43:53 +0200 Subject: [PATCH 16/37] fix: :bug: Fix `getInteractions` Transitive interactions are not always found. This update repeats interaction discovery multiple times to find transitive interactions --- src/backend/dd/DDSimDiagnostics.cpp | 40 +++++++++++++++++------------ 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/src/backend/dd/DDSimDiagnostics.cpp b/src/backend/dd/DDSimDiagnostics.cpp index 5b7322b8..ba882f46 100644 --- a/src/backend/dd/DDSimDiagnostics.cpp +++ b/src/backend/dd/DDSimDiagnostics.cpp @@ -64,22 +64,30 @@ Result dddiagnosticsGetInteractions(Diagnostics* self, size_t beforeInstruction, auto* ddsim = ddd->simulationState; std::set interactions; interactions.insert(qubit); - for (auto i = beforeInstruction - 1; i < beforeInstruction; i--) { - if (ddsim->instructionTypes[i] != SIMULATE && - ddsim->instructionTypes[i] != CALL) { - continue; - } - auto& targets = ddsim->targetQubits[i]; - std::set targetQubits; - for (auto target : targets) { - targetQubits.insert(variableToQubit(ddsim, target)); - } - if (!std::none_of(targetQubits.begin(), targetQubits.end(), - [&interactions](size_t elem) { - return interactions.find(elem) != interactions.end(); - })) { - for (const auto& target : targetQubits) { - interactions.insert(target); + bool found = true; + + while (found) { + found = false; + for (auto i = beforeInstruction - 1; i < beforeInstruction; i--) { + if (ddsim->instructionTypes[i] != SIMULATE && + ddsim->instructionTypes[i] != CALL) { + continue; + } + auto& targets = ddsim->targetQubits[i]; + std::set targetQubits; + for (const auto& target : targets) { + targetQubits.insert(variableToQubit(ddsim, target)); + } + if (!std::none_of(targetQubits.begin(), targetQubits.end(), + [&interactions](size_t elem) { + return interactions.find(elem) != interactions.end(); + })) { + for (const auto& target : targetQubits) { + if (interactions.find(target) == interactions.end()) { + found = true; + } + interactions.insert(target); + } } } } From 60a32f0c63cc53048bd71554c44501836f8206d4 Mon Sep 17 00:00:00 2001 From: DRovara Date: Tue, 6 Aug 2024 16:44:13 +0200 Subject: [PATCH 17/37] test: :white_check_mark: Add C++ tests --- CMakeLists.txt | 8 +- test/CMakeLists.txt | 12 + test/circuits/classical-storage.qasm | 25 ++ test/circuits/complex-jumps.qasm | 20 ++ ...ailing-assertions-missing-interaction.qasm | 11 + test/circuits/failing-assertions.qasm | 18 + test/test_custom_code.cpp | 84 +++++ test/test_data_retrieval.cpp | 172 +++++++++ test/test_diagnostics.cpp | 84 +++++ test/test_simulation.cpp | 326 ++++++++++++++++++ test/test_utility.cpp | 44 +++ test/utils/utils_test.hpp | 8 + test/utils_test.cpp | 36 ++ 13 files changed, 846 insertions(+), 2 deletions(-) create mode 100644 test/CMakeLists.txt create mode 100644 test/circuits/classical-storage.qasm create mode 100644 test/circuits/complex-jumps.qasm create mode 100644 test/circuits/failing-assertions-missing-interaction.qasm create mode 100644 test/circuits/failing-assertions.qasm create mode 100644 test/test_custom_code.cpp create mode 100644 test/test_data_retrieval.cpp create mode 100644 test/test_diagnostics.cpp create mode 100644 test/test_simulation.cpp create mode 100644 test/test_utility.cpp create mode 100644 test/utils/utils_test.hpp create mode 100644 test/utils_test.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 89a61c21..2ca1dbb0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -43,8 +43,12 @@ add_subdirectory(src) # add test app add_subdirectory(app) -# add test code if(BUILD_MQT_DEBUG_TESTS) enable_testing() include(GoogleTest) -# add_subdirectory(test) endif() +# add test code +if(BUILD_MQT_DEBUG_TESTS) + enable_testing() + include(GoogleTest) + add_subdirectory(test) +endif() configure_file(${CMAKE_CURRENT_SOURCE_DIR}/cmake/cmake_uninstall.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake IMMEDIATE @ONLY) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 00000000..dfa496c1 --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,12 @@ +package_add_test( + mqt_debug_test + MQT::Debug + utils_test.cpp + test_utility.cpp + test_simulation.cpp + test_data_retrieval.cpp + test_diagnostics.cpp + test_custom_code.cpp) + +# set include directories +target_include_directories(mqt_debug_test PUBLIC ${PROJECT_SOURCE_DIR}/test/utils) diff --git a/test/circuits/classical-storage.qasm b/test/circuits/classical-storage.qasm new file mode 100644 index 00000000..1c98909e --- /dev/null +++ b/test/circuits/classical-storage.qasm @@ -0,0 +1,25 @@ +qreg q[3]; // 0 +qreg p[1]; // 1 +creg c[3]; // 2 +creg hello[1]; // 3 + +x q[0]; // 4 +x q[1]; // 5 + +measure q -> c; // 6 + +x q[2]; // 7 +measure q[2] -> c[2]; // 8 +x q[2]; // 9 + +h q[0]; // 10 +cx q[0], p[0]; // 11 + +measure p[0] -> hello[0]; // 12 + +y q[2]; // 13 +y q[1]; // 14 + +measure q[0] -> c[0]; // 15 + +barrier; // 16 diff --git a/test/circuits/complex-jumps.qasm b/test/circuits/complex-jumps.qasm new file mode 100644 index 00000000..3e34c126 --- /dev/null +++ b/test/circuits/complex-jumps.qasm @@ -0,0 +1,20 @@ +qreg q[3]; // 0 + +gate my_cx q0, q1 { // 1 + cx q0, q1; // 2 +} // 3 + +gate make_ghz q0, q1, q2 { // 4 + h q0; // 5 + entangle q0, q1; // 6 + entangle q0, q2; // 7 +} // 8 + +gate entangle q1, q2 { // 9 + my_cx q1, q2; // 10 +} // 11 + + make_ghz q[0], q[1], q[2]; // 12 + +entangle q[2], q[0]; // 13 +entangle q[1], q[0]; // 14 diff --git a/test/circuits/failing-assertions-missing-interaction.qasm b/test/circuits/failing-assertions-missing-interaction.qasm new file mode 100644 index 00000000..ccee5dc4 --- /dev/null +++ b/test/circuits/failing-assertions-missing-interaction.qasm @@ -0,0 +1,11 @@ +qreg q[5]; // 0 + +h q[0]; // 1 +h q[3]; // 2 + +cx q[0], q[1]; // 3 +cx q[1], q[2]; // 4 +cx q[3], q[4]; // 5 + +assert-ent q[0], q[2]; // 6 +assert-ent q[0], q[3]; // 7 (fails) diff --git a/test/circuits/failing-assertions.qasm b/test/circuits/failing-assertions.qasm new file mode 100644 index 00000000..c594a369 --- /dev/null +++ b/test/circuits/failing-assertions.qasm @@ -0,0 +1,18 @@ +qreg q[4]; // 0 + +h q[0]; // 1 + +cx q[0], q[1]; // 2 +cx q[1], q[2]; // 3 +// here the control and target were swapped +cx q[3], q[0]; // 4 + +assert-ent q[0], q[2]; // 5 +assert-ent q[2], q[3]; // 6 (fails) + +z q[0]; // 7 + +h q[0]; // 8 + +assert-sup q[3]; // 9 (fails) +assert-sup q[0], q[1], q[2], q[3]; // 10 diff --git a/test/test_custom_code.cpp b/test/test_custom_code.cpp new file mode 100644 index 00000000..76d483f4 --- /dev/null +++ b/test/test_custom_code.cpp @@ -0,0 +1,84 @@ +#include "backend/dd/DDSimDebug.hpp" +#include "backend/debug.h" +#include "utils_test.hpp" + +#include +#include + +class CustomCodeTest : public testing::Test { + void SetUp() override { + createDDSimulationState(&ddState); + state = &ddState.interface; + } + +protected: + DDSimulationState ddState; + SimulationState* state = nullptr; + + std::string fullCode; + std::string userCode; + size_t offset = 0; + + void loadCode(size_t numQubits, size_t numClassics, const char* code) { + if (numQubits < 1) { + numQubits = 1; + } + if (numClassics < 1) { + numClassics = 1; + } + std::ostringstream ss; + + ss << "qreg q[" << numQubits << "];\n"; + ss << "creg c[" << numClassics << "];\n"; + + offset = ss.str().size(); + + ss << code; + + userCode = code; + fullCode = ss.str(); + state->loadCode(state, fullCode.c_str()); + } + + void forwardTo(size_t instruction) { + instruction += 2; // Skip the qreg and creg declarations + size_t currentInstruction = state->getCurrentInstruction(state); + while (currentInstruction < instruction) { + state->stepForward(state); + currentInstruction = state->getCurrentInstruction(state); + } + } +}; + +TEST_F(CustomCodeTest, ClassicControlledOperation) { + loadCode(2, 1, + "h q[0];" + "cx q[0], q[1];" + "measure q[0] -> c[0];" + "if(c==1) x q[1];" + "if(c==0) z q[1];"); + ASSERT_EQ(state->runSimulation(state), OK); + + std::array amplitudes{}; + Statevector sv{2, 4, amplitudes.data()}; + state->getStateVectorFull(state, &sv); + ASSERT_TRUE(complexEquality(amplitudes[0], 1, 0.0) || + complexEquality(amplitudes[1], 1, 0.0)); +} + +TEST_F(CustomCodeTest, ResetGate) { + loadCode(1, 1, + "x q[0];" + "z q[0];" + "reset q[0];" + "barrier;"); + Complex result; + + forwardTo(2); + ASSERT_EQ(state->getAmplitudeIndex(state, 1, &result), OK); + ASSERT_TRUE(complexEquality(result, -1.0, 0.0)); + + forwardTo(3); + ASSERT_EQ(state->getAmplitudeIndex(state, 0, &result), OK); + ASSERT_TRUE(complexEquality(result, -1.0, 0.0)); +} diff --git a/test/test_data_retrieval.cpp b/test/test_data_retrieval.cpp new file mode 100644 index 00000000..4ce71e06 --- /dev/null +++ b/test/test_data_retrieval.cpp @@ -0,0 +1,172 @@ +#include "backend/dd/DDSimDebug.hpp" +#include "backend/debug.h" +#include "utils_test.hpp" + +#include +#include + +class DataRetrievalTest : public testing::Test { + void SetUp() override { + createDDSimulationState(&ddState); + state = &ddState.interface; + loadFromFile("classical-storage"); + } + +protected: + DDSimulationState ddState; + SimulationState* state = nullptr; + + void loadFromFile(const std::string& testName) { + const auto code = readFromCircuitsPath(testName); + state->loadCode(state, code.c_str()); + } + + void forwardTo(size_t instruction) { + size_t currentInstruction = state->getCurrentInstruction(state); + while (currentInstruction < instruction) { + state->stepForward(state); + currentInstruction = state->getCurrentInstruction(state); + } + } +}; + +TEST_F(DataRetrievalTest, GetNumQubits) { + ASSERT_EQ(state->getNumQubits(state), 4); +} + +TEST_F(DataRetrievalTest, GetNumClassicalVariables) { + ASSERT_EQ(state->getNumClassicalVariables(state), 4); +} + +TEST_F(DataRetrievalTest, GetAmplitudes) { + Complex result; + + forwardTo(6); + ASSERT_EQ(state->getAmplitudeIndex(state, 0, &result), OK); + ASSERT_TRUE(complexEquality(result, 0.0, 0.0)); + ASSERT_EQ(state->getAmplitudeIndex(state, 3, &result), OK); + ASSERT_TRUE(complexEquality(result, 1.0, 0.0)); + ASSERT_EQ(state->getAmplitudeBitstring(state, "0011", &result), OK); + ASSERT_TRUE(complexEquality(result, 1.0, 0.0)); + ASSERT_EQ(state->getAmplitudeBitstring(state, "1011", &result), OK); + ASSERT_TRUE(complexEquality(result, 0.0, 0.0)); + + forwardTo(12); + ASSERT_EQ(state->getAmplitudeBitstring(state, "1011", &result), OK); + ASSERT_TRUE(complexEquality(result, -0.707, 0.0)); + ASSERT_EQ(state->getAmplitudeBitstring(state, "0010", &result), OK); + ASSERT_TRUE(complexEquality(result, 0.707, 0.0)); + ASSERT_EQ(state->getAmplitudeBitstring(state, "1010", &result), OK); + ASSERT_TRUE(complexEquality(result, 0.0, 0.0)); + ASSERT_EQ(state->getAmplitudeIndex(state, 3, &result), OK); + ASSERT_TRUE(complexEquality(result, 0.0, 0.0)); + + forwardTo(13); + Complex c1; + Complex c2; + ASSERT_EQ(state->getAmplitudeBitstring(state, "0010", &c1), OK); + ASSERT_EQ(state->getAmplitudeBitstring(state, "1011", &c2), OK); + ASSERT_TRUE( + (complexEquality(c1, 0.0, 0.0) && complexEquality(c2, -1.0, 0.0)) || + (complexEquality(c1, 1.0, 0.0) && complexEquality(c2, 0.0, 0.0))); + const size_t baseIndex = complexEquality(c1, 1.0, 0.0) ? 2 : 11; + + forwardTo(14); + ASSERT_EQ(state->getAmplitudeIndex(state, baseIndex + 4, &result), OK); + ASSERT_TRUE(complexEquality(result, 0.0, baseIndex == 2 ? 1 : -1.0)); + ASSERT_EQ(state->getAmplitudeIndex(state, baseIndex, &result), OK); + ASSERT_TRUE(complexEquality(result, 0.0, 0.0)); + + forwardTo(15); + ASSERT_EQ(state->getAmplitudeIndex(state, baseIndex + 2, &result), OK); + ASSERT_TRUE(complexEquality(result, baseIndex == 2 ? 1.0 : -1.0, 0.0)); + ASSERT_EQ(state->getAmplitudeIndex(state, baseIndex, &result), OK); + ASSERT_TRUE(complexEquality(result, 0.0, 0.0)); +} + +TEST_F(DataRetrievalTest, GetClassicalVariableNames) { + std::array name = {0}; + std::vector expectedNames = {"c[0]", "c[1]", "c[2]", "hello[0]"}; + for (size_t i = 0; i < expectedNames.size(); i++) { + ASSERT_EQ(state->getClassicalVariableName(state, i, name.data()), OK); + ASSERT_STREQ(name.data(), expectedNames[i].c_str()); + } +} + +TEST_F(DataRetrievalTest, GetClassicalVariable) { + Variable v; + + forwardTo(6); + ASSERT_EQ(state->getClassicalVariable(state, "c[0]", &v), OK); + ASSERT_TRUE(classicalEquals(v, false)); + + forwardTo(7); + ASSERT_EQ(state->getClassicalVariable(state, "c[0]", &v), OK); + ASSERT_TRUE(classicalEquals(v, true)); + ASSERT_EQ(state->getClassicalVariable(state, "c[1]", &v), OK); + ASSERT_TRUE(classicalEquals(v, true)); + + forwardTo(10); + ASSERT_EQ(state->getClassicalVariable(state, "c[2]", &v), OK); + ASSERT_TRUE(classicalEquals(v, true)); + + forwardTo(13); + ASSERT_EQ(state->getClassicalVariable(state, "hello[0]", &v), OK); + const bool entangledValue = v.value.boolValue; + if (entangledValue) { + Complex c; + ASSERT_EQ(state->getAmplitudeBitstring(state, "1011", &c), OK); + ASSERT_TRUE(c.real == -1 || c.real == 1); + } else { + Complex c; + ASSERT_EQ(state->getAmplitudeBitstring(state, "0010", &c), OK); + ASSERT_TRUE(c.real == -1 || c.real == 1); + } + + forwardTo(16); + ASSERT_EQ(state->getClassicalVariable(state, "c[0]", &v), OK); + ASSERT_TRUE(classicalEquals(v, entangledValue)); +} + +TEST_F(DataRetrievalTest, GetStateVectorFull) { + std::array amplitudes; + Statevector sv{4, 16, amplitudes.data()}; + + ASSERT_EQ(state->getStateVectorFull(state, &sv), OK); + ASSERT_TRUE(complexEquality(amplitudes[0], 1.0, 0.0)); + ASSERT_TRUE(complexEquality(amplitudes[1], 0.0, 0.0)); + + forwardTo(12); + ASSERT_EQ(state->getStateVectorFull(state, &sv), OK); + ASSERT_TRUE(complexEquality(amplitudes[2], 0.707, 0.0)); + ASSERT_TRUE(complexEquality(amplitudes[11], -0.707, 0.0)); +} + +TEST_F(DataRetrievalTest, GetStateVectorSub) { + std::array amplitudes; + Statevector sv{2, 4, amplitudes.data()}; + + forwardTo(6); + size_t qubits[] = {0, 1}; + ASSERT_EQ(state->getStateVectorSub(state, 2, qubits, &sv), OK); + ASSERT_TRUE(complexEquality(amplitudes[3], 1, 0.0)); + ASSERT_TRUE(complexEquality(amplitudes[0], 0.0, 0.0)); + + qubits[1] = 2; + ASSERT_EQ(state->getStateVectorSub(state, 2, qubits, &sv), OK); + ASSERT_TRUE(complexEquality(amplitudes[3], 0.0, 0.0)); + ASSERT_TRUE(complexEquality(amplitudes[1], 1.0, 0.0)); + + forwardTo(11); + ASSERT_EQ(state->getStateVectorSub(state, 2, qubits, &sv), OK); + ASSERT_TRUE(complexEquality(amplitudes[0], 0.707, 0.0)); + ASSERT_TRUE(complexEquality(amplitudes[1], -0.707, 0.0)); + + qubits[0] = 1; + ASSERT_EQ(state->getStateVectorSub(state, 2, qubits, &sv), OK); + ASSERT_TRUE(complexEquality(amplitudes[0], 0.0, 0.0)); + ASSERT_TRUE(complexEquality(amplitudes[1], 0.0, + 0.0)); // 0 because of destructive interference + ASSERT_TRUE(complexEquality(amplitudes[2], 0.0, 0.0)); + ASSERT_TRUE(complexEquality(amplitudes[3], 0.0, 0.0)); +} diff --git a/test/test_diagnostics.cpp b/test/test_diagnostics.cpp new file mode 100644 index 00000000..f1a8593d --- /dev/null +++ b/test/test_diagnostics.cpp @@ -0,0 +1,84 @@ +#include "backend/dd/DDSimDebug.hpp" +#include "backend/debug.h" +#include "utils_test.hpp" + +#include +#include + +class DiagnosticsTest : public testing::Test { + void SetUp() override { + createDDSimulationState(&ddState); + state = &ddState.interface; + diagnostics = state->getDiagnostics(state); + } + +protected: + DDSimulationState ddState; + SimulationState* state = nullptr; + Diagnostics* diagnostics = nullptr; + + void loadFromFile(const std::string& testName) { + const auto code = readFromCircuitsPath(testName); + state->loadCode(state, code.c_str()); + } + + void forwardTo(size_t instruction) { + size_t currentInstruction = state->getCurrentInstruction(state); + while (currentInstruction < instruction) { + state->stepForward(state); + currentInstruction = state->getCurrentInstruction(state); + } + } +}; + +TEST_F(DiagnosticsTest, DataDependencies) { + loadFromFile("failing-assertions"); + const std::map> expected = { + {1, {1}}, + {2, {1, 2}}, + {3, {1, 2, 3}}, + {4, {1, 2, 4}}, + {5, {1, 2, 3, 4, 5}}, + {6, {1, 2, 3, 4, 6}}, + {7, {1, 2, 4, 7}}, + {8, {1, 2, 4, 7, 8}}, + {9, {1, 2, 4, 9}}, + {10, {1, 2, 3, 4, 7, 8, 10}}}; + + for (const auto& [instruction, expectedDependencies] : expected) { + std::vector dependencies(state->getInstructionCount(state), 0); + diagnostics->getDataDependencies( + diagnostics, instruction, reinterpret_cast(dependencies.data())); + std::set dependenciesSet; + for (size_t i = 0; i < dependencies.size(); ++i) { + if (dependencies[i] != 0) { + dependenciesSet.insert(i); + } + } + ASSERT_EQ(dependenciesSet, expectedDependencies) + << "Failed for instruction " << instruction; + } +} + +TEST_F(DiagnosticsTest, ControlAlwaysZeroTest) { + loadFromFile("failing-assertions"); + state->runSimulation(state); + std::array problems{}; + const size_t count = + diagnostics->potentialErrorCauses(diagnostics, problems.data(), 10); + ASSERT_EQ(count, 1); + ASSERT_EQ(problems[0].type, ErrorCauseType::ControlAlwaysZero); + ASSERT_EQ(problems[0].instruction, 4); +} + +TEST_F(DiagnosticsTest, MissingInteraction) { + loadFromFile("failing-assertions-missing-interaction"); + state->runSimulation(state); + ASSERT_EQ(state->getCurrentInstruction(state), 7); + std::array problems{}; + const size_t count = + diagnostics->potentialErrorCauses(diagnostics, problems.data(), 10); + ASSERT_EQ(count, 1); + ASSERT_EQ(problems[0].type, ErrorCauseType::MissingInteraction); + ASSERT_EQ(problems[0].instruction, 7); +} diff --git a/test/test_simulation.cpp b/test/test_simulation.cpp new file mode 100644 index 00000000..9b0e424e --- /dev/null +++ b/test/test_simulation.cpp @@ -0,0 +1,326 @@ +#include "backend/dd/DDSimDebug.hpp" +#include "backend/debug.h" +#include "utils_test.hpp" + +#include +#include + +class SimulationTest : public testing::TestWithParam { + void SetUp() override { + createDDSimulationState(&ddState); + state = &ddState.interface; + loadFromFile(GetParam()); + } + +protected: + DDSimulationState ddState; + SimulationState* state = nullptr; + + void loadFromFile(const std::string& testName) { + const auto code = readFromCircuitsPath(testName); + state->loadCode(state, code.c_str()); + } + + void moveAndCheck( + const std::vector>& movements) { + size_t step = 0; + for (const auto& movement : movements) { + Result (*movementFunction)(SimulationState*) = nullptr; + if (movement.first == "sf") { + movementFunction = state->stepForward; + } else if (movement.first == "of") { + movementFunction = state->stepOverForward; + } else if (movement.first == "uf") { + movementFunction = state->stepOutForward; + } else if (movement.first == "rf") { + movementFunction = state->runSimulation; + } else if (movement.first == "sb") { + movementFunction = state->stepBackward; + } else if (movement.first == "ob") { + movementFunction = state->stepOverBackward; + } else if (movement.first == "ub") { + movementFunction = state->stepOutBackward; + } else if (movement.first == "rb") { + movementFunction = state->runSimulationBackward; + } else if (movement.first == "assertion") { + ASSERT_TRUE(state->didAssertionFail(state)) + << "Expected assertion to fail at step " << step << " in " + << GetParam() << "\n"; + step++; + continue; + } else { + FAIL() << "Unknown movement type " << movement.first << "\n"; + } + ASSERT_EQ(movementFunction(state), Result::OK) + << "Movement " << movement.first << " failed at step " << step + << " in " << GetParam() << "\n"; + ASSERT_EQ(state->getCurrentInstruction(state), movement.second) + << "Movement " << movement.first + << " did not reach expected instruction " << movement.second + << " at step " << step << " in " << GetParam() << "\n"; + step++; + } + } +}; + +TEST_P(SimulationTest, StepThroughCode) { + const std::map> expected = { + {"complex-jumps", {0, 1, 4, 9, 12, 5, 6, 10, 2, 3, 11, 7, 10, 2, + 3, 11, 8, 13, 10, 2, 3, 11, 14, 10, 2, 3, 11}}, + {"failing-assertions", {0, 1, 2, 3, 4, 5, 6, 6, 7, 8, 9, 9, 10}}}; + for (const auto exp : expected.at(GetParam())) { + ASSERT_EQ(state->getCurrentInstruction(state), exp); + state->stepForward(state); + } +} + +TEST_P(SimulationTest, StackTraceRetrieval) { + const std::map> expectedDepths = { + {"complex-jumps", {1, 1, 1, 1, 1, 2, 2, 3, 4, 4, 3, 2, 3, 4, + 4, 3, 2, 1, 2, 3, 3, 2, 1, 2, 3, 3, 2}}, + {"failing-assertions", {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}}}; + const std::map>> + expectedStacks = { + {"complex-jumps", + {{0}, + {1}, + {4}, + {9}, + {12}, + {5, 12}, + {6, 12}, + {10, 6, 12}, + {2, 10, 6, 12}, + {3, 10, 6, 12}, + {11, 6, 12}, + {7, 12}, + {10, 7, 12}, + {2, 10, 7, 12}, + {3, 10, 7, 12}, + {11, 7, 12}, + {8, 12}, + {13}, + {10, 13}, + {2, 10, 13}, + {3, 10, 13}, + {11, 13}, + {14}, + {10, 14}, + {2, 10, 14}, + {3, 10, 14}, + {11, 14}}}, + {"failing-assertions", + {{0}, {1}, {2}, {3}, {4}, {5}, {6}, {6}, {7}, {8}, {9}, {9}, {10}}}}; + + for (size_t index = 0; index < expectedDepths.at(GetParam()).size(); + index++) { + const auto exp = expectedDepths.at(GetParam())[index]; + size_t depth = 0; + ASSERT_EQ(state->getStackDepth(state, &depth), Result::OK); + ASSERT_EQ(depth, exp) << "Depth computation failed for instruction " + << state->getCurrentInstruction(state) << " at index " + << index << " in " << GetParam() << "\n"; + for (size_t depthToTest = 1; depthToTest <= depth; depthToTest++) { + std::vector stack(depthToTest); + ASSERT_EQ(state->getStackTrace(state, depthToTest, stack.data()), + Result::OK) + << "Failed to get stack trace for depth " << depthToTest + << " for instruction " << state->getCurrentInstruction(state) + << " in " << GetParam() << "\n"; + const std::vector& expectedStack = + expectedStacks.at(GetParam()).at(index); + for (size_t i = 0; i < depthToTest; i++) { + ASSERT_EQ(stack[i], expectedStack[i]) + << "Failed for index " << i << " at depth " << depthToTest + << " for instruction " << state->getCurrentInstruction(state) + << " in " << GetParam() << "\n"; + } + } + + state->stepForward(state); + } +} + +TEST_P(SimulationTest, TopLevelBreakpoints) { + const std::map> breakpointPositions = { + {"complex-jumps", {174, 451, 488, 525}}, + {"failing-assertions", {58, 322, 374, 427, 487}}}; + const std::map> + expectedBreakpointPositions = {{"complex-jumps", {4, 12, 13, 14}}, + {"failing-assertions", {1, 5, 6, 7, 8}}}; + + for (size_t index = 0; index < breakpointPositions.at(GetParam()).size(); + index++) { + const auto breakpoint = breakpointPositions.at(GetParam())[index]; + size_t targetInstruction = 0; + ASSERT_EQ(state->setBreakpoint(state, breakpoint, &targetInstruction), + Result::OK) + << "Failed to set breakpoint at instruction " << breakpoint << " in " + << GetParam() << "\n"; + ASSERT_EQ(targetInstruction, + expectedBreakpointPositions.at(GetParam())[index]) + << "Breakpoint set at wrong instruction for breakpoint " << breakpoint + << " in " << GetParam() << "\n"; + } + + for (const auto instruction : expectedBreakpointPositions.at(GetParam())) { + ASSERT_EQ(state->runSimulation(state), Result::OK) + << "Failed to run simulation in " << GetParam() << "\n"; + while (state->didAssertionFail(state)) { + ASSERT_EQ(state->runSimulation(state), Result::OK) + << "Failed to run simulation in " << GetParam() << "\n"; + } + ASSERT_EQ(state->getCurrentInstruction(state), instruction) + << "Breakpoint not hit at expected instruction " << instruction + << " in " << GetParam() << "\n"; + ASSERT_TRUE(state->wasBreakpointHit(state)); + } + + ASSERT_EQ(state->clearBreakpoints(state), Result::OK) + << "Failed to clear breakpoints in " << GetParam() << "\n"; + ASSERT_EQ(state->runSimulationBackward(state), Result::OK); + ASSERT_FALSE(state->wasBreakpointHit(state)) + << "Breakpoint hit after clearing in " << GetParam() << "\n"; + while (!state->isFinished(state)) { + ASSERT_EQ(state->runSimulation(state), Result::OK); + ASSERT_FALSE(state->wasBreakpointHit(state)) + << "Breakpoint hit after clearing in " << GetParam() << "\n"; + } +} + +TEST_P(SimulationTest, PauseSimulation) { + // The protocol allows us to be lenient with the 'pause' operation. We do not + // need to pause immediately, instead we can pause at the next convenient + // time. 'stepOver' and 'stepOut' methods therefore do not necessarily stop if + // 'pause' was called before they started. `run` (in both directions), on the + // other hand, will ALWAYS pause immediately, even if 'pause' was called + // before they started. + + ASSERT_EQ(state->stepForward(state), Result::OK); + + size_t currentPosition = state->getCurrentInstruction(state); + ASSERT_EQ(state->pauseSimulation(state), Result::OK); + ASSERT_EQ(state->runSimulation(state), Result::OK); + ASSERT_EQ(state->getCurrentInstruction(state), currentPosition) + << "Simulation with 'run' continued after pause in " << GetParam() + << "\n"; + ASSERT_EQ(state->stepOverForward(state), Result::OK); + ASSERT_NE(state->getCurrentInstruction(state), currentPosition) + << "Simulation still paused after second 'step over' in " << GetParam() + << "\n"; + + currentPosition = state->getCurrentInstruction(state); + ASSERT_EQ(state->pauseSimulation(state), Result::OK); + ASSERT_EQ(state->runSimulationBackward(state), Result::OK); + ASSERT_EQ(state->getCurrentInstruction(state), currentPosition) + << "Simulation with 'runBackward' continued after pause in " << GetParam() + << "\n"; + ASSERT_EQ(state->stepOverForward(state), Result::OK); + ASSERT_NE(state->getCurrentInstruction(state), currentPosition) + << "Simulation still paused after second 'step over' in " << GetParam() + << "\n"; +} + +TEST_P(SimulationTest, ResetSimulation) { + for (size_t i = 0; i < 10; i++) { + ASSERT_EQ(state->stepOverForward(state), Result::OK); + ASSERT_EQ(state->stepOverForward(state), Result::OK); + ASSERT_EQ(state->stepOverForward(state), Result::OK); + ASSERT_EQ(state->resetSimulation(state), Result::OK); + ASSERT_FALSE(state->canStepBackward(state)); + ASSERT_EQ(state->getCurrentInstruction(state), 0); + ASSERT_EQ(state->runSimulation(state), Result::OK); + while (state->didAssertionFail(state)) { + ASSERT_EQ(state->runSimulation(state), Result::OK); + } + ASSERT_FALSE(state->canStepForward(state)); + ASSERT_EQ(state->resetSimulation(state), Result::OK); + ASSERT_FALSE(state->canStepBackward(state)); + ASSERT_EQ(state->getCurrentInstruction(state), 0); + } +} + +TEST_P(SimulationTest, StepOver) { + const std::map>> + expected = { + {"complex-jumps", + { + {"of", 1}, {"of", 4}, {"sf", 9}, {"sf", 12}, {"of", 13}, + {"ob", 12}, {"sf", 5}, {"of", 6}, {"of", 7}, {"ob", 6}, + {"sf", 10}, {"of", 11}, {"ob", 10}, {"sf", 2}, {"of", 3}, + {"of", 11}, {"of", 7}, {"sb", 11}, {"of", 7}, {"of", 8}, + {"of", 13}, {"sb", 8}, {"of", 13}, {"of", 14}, + }}, + {"failing-assertions", + {{"of", 1}, {"of", 2}, {"of", 3}, + {"of", 4}, {"of", 5}, {"of", 6}, + {"of", 6}, {"assertion", 6}, {"of", 7}, + {"ob", 6}, {"assertion", 6}, {"of", 7}, + {"ob", 6}, {"assertion", 6}, {"ob", 5}, + {"of", 6}, {"of", 6}, {"assertion", 6}, + {"of", 7}, {"of", 8}, {"sf", 9}, + {"sb", 8}, {"sf", 9}, {"sf", 9}, + {"assertion", 9}, {"sb", 8}, {"sf", 9}, + {"sf", 9}, {"assertion", 9}, {"sf", 10}, + {"sb", 9}, {"assertion", 9}, {"sf", 10}}}}; + moveAndCheck(expected.at(GetParam())); +} + +TEST_P(SimulationTest, StepOut) { + const std::map>> + expected = {{"complex-jumps", + {{"sf", 1}, {"sf", 4}, {"sf", 9}, {"sf", 12}, {"sf", 5}, + {"ub", 12}, {"sf", 5}, {"sf", 6}, {"ub", 12}, {"sf", 5}, + {"of", 6}, {"of", 7}, {"ub", 12}, {"sf", 5}, {"sf", 6}, + {"sf", 10}, {"ub", 6}, {"sf", 10}, {"sf", 2}, {"sf", 3}, + {"ub", 10}, {"uf", 7}, {"ob", 6}, {"sf", 10}, {"sf", 2}, + {"uf", 11}, {"uf", 7}, {"uf", 13}}}, + {"failing-assertions", + {{"sf", 1}, + {"uf", 6}, + {"uf", 9}, + {"uf", 11}, + {"ub", 0}, + {"uf", 6}, + {"ub", 0}, + {"uf", 6}, + {"uf", 9}}}}; + moveAndCheck(expected.at(GetParam())); +} + +TEST_P(SimulationTest, RunSimulation) { + const std::map>> + expected = {{"complex-jumps", + {{"sf", 1}, + {"rf", 15}, + {"rb", 0}, + {"sf", 1}, + {"sf", 4}, + {"sf", 9}, + {"sf", 12}, + {"sf", 5}, + {"sf", 6}, + {"rf", 15}, + {"rb", 0}, + {"rf", 15}}}, + {"failing-assertions", + {{"sf", 1}, + {"rf", 6}, + {"rf", 9}, + {"rf", 11}, + {"rb", 0}, + {"rf", 6}, + {"rb", 0}, + {"rf", 6}, + {"rf", 9}, + {"rf", 11}}}}; + moveAndCheck(expected.at(GetParam())); + ASSERT_TRUE(state->isFinished(state)); +} + +INSTANTIATE_TEST_SUITE_P(StringParams, SimulationTest, + ::testing::Values("complex-jumps", + "failing-assertions")); diff --git a/test/test_utility.cpp b/test/test_utility.cpp new file mode 100644 index 00000000..eacddec8 --- /dev/null +++ b/test/test_utility.cpp @@ -0,0 +1,44 @@ +#include "backend/dd/DDSimDebug.hpp" +#include "backend/debug.h" +#include "utils_test.hpp" + +#include +#include + +class UtilityTest : public testing::Test { + void SetUp() override { + createDDSimulationState(&ddState); + state = &ddState.interface; + } + +protected: + DDSimulationState ddState; + SimulationState* state = nullptr; + + void loadFromFile(const std::string& testName) { + const auto code = readFromCircuitsPath(testName); + state->loadCode(state, code.c_str()); + } +}; + +TEST_F(UtilityTest, GetInstructionCount) { + loadFromFile("complex-jumps"); + ASSERT_EQ(state->getInstructionCount(state), 15); +} + +TEST_F(UtilityTest, GetInstructionPosition) { + loadFromFile("complex-jumps"); + + const std::map> expected = { + {0, {0, 9}}, {1, {38, 112}}, {2, {79, 88}}, + {3, {112, 112}}, {4, {150, 298}}, {12, {452, 477}}}; + for (const auto& [instruction, expectedPosition] : expected) { + size_t start = 0; + size_t end = 0; + state->getInstructionPosition(state, instruction, &start, &end); + ASSERT_EQ(start, expectedPosition.first) + << "Failed for instruction " << instruction; + ASSERT_EQ(end, expectedPosition.second) + << "Failed for instruction " << instruction; + } +} diff --git a/test/utils/utils_test.hpp b/test/utils/utils_test.hpp new file mode 100644 index 00000000..5b04ab65 --- /dev/null +++ b/test/utils/utils_test.hpp @@ -0,0 +1,8 @@ +#pragma once +#include "common.h" + +#include + +bool complexEquality(const Complex& c, double real, double imaginary); +bool classicalEquals(const Variable& v, bool value); +std::string readFromCircuitsPath(const std::string& testName); diff --git a/test/utils_test.cpp b/test/utils_test.cpp new file mode 100644 index 00000000..266ffc16 --- /dev/null +++ b/test/utils_test.cpp @@ -0,0 +1,36 @@ +#include "utils/utils_test.hpp" + +#include +#include + +bool complexEquality(const Complex& c, double real, double imaginary) { + const double epsilon = 0.001; + if (real - c.real > epsilon || c.real - real > epsilon) { + return false; + } + if (imaginary - c.imaginary > epsilon || c.imaginary - imaginary > epsilon) { + return false; + } + return true; +} + +bool classicalEquals(const Variable& v, bool value) { + return v.type == VarBool && v.value.boolValue == value; +} + +std::string readFromCircuitsPath(const std::string& testName) { + std::ifstream file("circuits/" + testName + ".qasm"); + if (!file.is_open()) { + file = std::ifstream("../../test/circuits/" + testName + ".qasm"); + if (!file.is_open()) { + std::cerr << "Could not open file\n"; + file.close(); + return ""; + } + } + + const std::string code((std::istreambuf_iterator(file)), + std::istreambuf_iterator()); + file.close(); + return code; +} From 1b459fa08d7aa44fb771db4b1c5cdbf24f01b017 Mon Sep 17 00:00:00 2001 From: DRovara Date: Tue, 6 Aug 2024 16:58:32 +0200 Subject: [PATCH 18/37] ci: :construction_worker: Add CD/CI workflows and other .github files --- .github/ISSUE_TEMPLATE/bug-report.yml | 45 ++++++++++++ .github/ISSUE_TEMPLATE/feature-request.yml | 32 +++++++++ .github/codecov.yml | 49 +++++++++++++ .github/contributing.rst | 81 ++++++++++++++++++++++ .github/dependabot.yml | 36 ++++++++++ .github/pull_request_template.md | 16 +++++ .github/release-drafter.yml | 54 +++++++++++++++ .github/support.rst | 12 ++++ .github/workflows/cd.yml | 37 ++++++++++ .github/workflows/ci.yml | 79 +++++++++++++++++++++ .github/workflows/release-drafter.yml | 23 ++++++ .github/workflows/update-mqt-core.yml | 26 +++++++ 12 files changed, 490 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug-report.yml create mode 100644 .github/ISSUE_TEMPLATE/feature-request.yml create mode 100644 .github/codecov.yml create mode 100644 .github/contributing.rst create mode 100644 .github/dependabot.yml create mode 100644 .github/pull_request_template.md create mode 100644 .github/release-drafter.yml create mode 100644 .github/support.rst create mode 100644 .github/workflows/cd.yml create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/release-drafter.yml create mode 100644 .github/workflows/update-mqt-core.yml diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml new file mode 100644 index 00000000..c9b57a53 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -0,0 +1,45 @@ +name: 🐛 Bug report +description: Something is not working correctly. +title: "🐛 " +body: + - type: markdown + attributes: + value: >- + **Thank you for wanting to report a bug for this project!** + + ⚠ + Verify first that your issue is not [already reported on GitHub](https://github.com/cda-tum/qcec/search?q=is%3Aissue&type=issues). + + If you have general questions, please consider [starting a discussion](https://github.com/cda-tum/qcec/discussions). + - type: textarea + attributes: + label: Environment information + description: >- + Please provide information about your environment. For example, OS, C++ compiler, mqt.core version etc. + placeholder: | + - OS: + - C++ compiler: + - mqt.core version: + - Additional environment information: + validations: + required: true + - type: textarea + attributes: + label: Description + description: A clear and concise description of what the bug is. + validations: + required: true + - type: textarea + attributes: + label: Expected behavior + description: A clear and concise description of what you expected to happen. + - type: textarea + attributes: + label: How to Reproduce + description: Please provide steps to reproduce this bug. + placeholder: | + 1. Get package from '...' + 2. Then run '...' + 3. An error occurs. + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/feature-request.yml b/.github/ISSUE_TEMPLATE/feature-request.yml new file mode 100644 index 00000000..812010c3 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature-request.yml @@ -0,0 +1,32 @@ +name: ✨ Feature request +description: Suggest an idea +title: "✨ <title>" +body: + - type: markdown + attributes: + value: > + **Thank you for wanting to suggest a feature for this project!** + + ⚠ + Verify first that your idea is not [already requested on GitHub](https://github.com/cda-tum/qcec/search?q=is%3Aissue&type=issues). + + - type: textarea + attributes: + label: What's the problem this feature will solve? + description: >- + What are you trying to do, that you are unable to achieve as it currently stands? + placeholder: >- + I'm trying to do X and I'm missing feature Y for this to be + easily achievable. + validations: + required: true + + - type: textarea + attributes: + label: Describe the solution you'd like + description: > + Clear and concise description of what you want to happen. + placeholder: >- + When I do X, I want to achieve Y in a situation when Z. + validations: + required: true diff --git a/.github/codecov.yml b/.github/codecov.yml new file mode 100644 index 00000000..fd383a06 --- /dev/null +++ b/.github/codecov.yml @@ -0,0 +1,49 @@ +ignore: + - "extern/**/*" + - "**/python" + - "test/**/*" + +coverage: + range: 60..90 + precision: 1 + status: + project: off + patch: off + +flag_management: + default_rules: + carryforward: true + statuses: + - type: project + target: auto + threshold: 0.5% + removed_code_behavior: adjust_base + - type: patch + target: 90% + threshold: 1% + individual_flags: + - name: cpp + paths: + - "include" + - "src" + - name: python + paths: + - "src/mqt/**/*.py" + statuses: + - type: project + threshold: 0.5% + removed_code_behavior: adjust_base + - type: patch + target: 95% + threshold: 1% + +parsers: + gcov: + branch_detection: + conditional: no + loop: no + +comment: + layout: "reach, diff, flags, files" + require_changes: true + show_carryforward_flags: true diff --git a/.github/contributing.rst b/.github/contributing.rst new file mode 100644 index 00000000..7704d3c1 --- /dev/null +++ b/.github/contributing.rst @@ -0,0 +1,81 @@ +Contributing +============ + +Thank you for your interest in contributing to this project. +We value contributions from people with all levels of experience. +In particular if this is your first pull request not everything has to be perfect. +We will guide you through the process. + +We use GitHub to `host code <https://github.com/cda-tum/mqt-debug>`_, to `track issues and feature requests <https://github.com/cda-tum/mqt-debug/issues>`_, as well as accept `pull requests <https://github.com/cda-tum/mqt-debug/pulls>`_. +See https://docs.github.com/en/get-started/quickstart for a general introduction to working with GitHub and contributing to projects. + +Types of Contributions +###################### + +You can contribute in several ways: + +- 🐛 Report Bugs + Report bugs at https://github.com/cda-tum/mqt-debug/issues using the *🐛 Bug report* issue template. Please make sure to fill out all relevant information in the respective issue form. + +- 🐛 Fix Bugs + Look through the `GitHub Issues <https://github.com/cda-tum/mqt-debug/issues>`_ for bugs. Anything tagged with "bug" is open to whoever wants to try and fix it. + +- ✨ Propose New Features + Propose new features at https://github.com/cda-tum/mqt-debug/issues using the *✨ Feature request* issue template. Please make sure to fill out all relevant information in the respective issue form. + +- ✨ Implement New Features + Look through the `GitHub Issues <https://github.com/cda-tum/mqt-debug/issues>`_ for features. Anything tagged with "feature" is open to whoever wants to implement it. We highly appreciate external contributions to the project. + +- 📝 Write Documentation + MQT Debug could always use some more `documentation <https://mqt.readthedocs.io/projects/debug>`_, and we appreciate any help with that. + +🎉 Get Started +############## + +Ready to contribute? Check out the :doc:`Development Guide <DevelopmentGuide>` to set up MQT Debug for local development and learn about the style guidelines and conventions used throughout the project. + +We value contributions from people with all levels of experience. +In particular if this is your first PR not everything has to be perfect. +We will guide you through the PR process. +Nevertheless, please try to follow the guidelines below as well as you can to help make the PR process quick and smooth. + +Core Guidelines +############### + +- `"Commit early and push often" <https://www.worklytics.co/blog/commit-early-push-often>`_. +- Write meaningful commit messages (preferably using `gitmoji <https://gitmoji.dev>`_ to give additional context to your commits). +- Focus on a single feature/bug at a time and only touch relevant files. Split multiple features into multiple contributions. +- If you added a new feature, you should add tests that ensure it works as intended. Furthermore, the new feature should be documented appropriately. +- If you fixed a bug, you should add tests that demonstrate that the bug has been fixed. +- Document your code thoroughly and write readable code. +- Keep your code clean. Remove any debug statements, left-over comments, or code unrelated to your contribution. +- Run :code:`nox -rs lint` to check your code for style and linting errors before committing. + +Pull Request Workflow +##################### + +- Create PRs early. It is ok to create work-in-progress PRs. You may mark these as draft PRs on GitHub. +- Describe your PR. Start with a descriptive title, reference any related issues by including the issue number in the PR description, and add a comprehensive description of the changes. We provide a PR template that you can (and should) follow to create a PR. +- Whenever a PR is created or updated, several workflows on all supported platforms and versions of Python are executed. Make sure your PR passes *all* these continuous integration (CI) checks. Here are some tips for finding the cause of certain failures: + - If any of the *C++/\** checks fail, this most likely indicates build errors or test failures in the C++ part of the code base. Look through the respective logs on GitHub for any error or failure messages. + + - If any of the :code:`Python Packaging/\*` checks fail, this indicates an error in the Python bindings or creation of the Python wheels and/or source distribution. Look through the respective logs on GitHub for any error or failure messages. + - If any of the :code:`Python/\*` checks fail, this indicates an error in the Python part of the code base. Look through the respective logs on GitHub for any error or failure messages. + - If any of the :code:`codecov/\*` checks fail, this means that your changes are not appropriately covered by tests or that the overall project coverage decreased too much. Ensure that you include tests for all your changes in the PR. + - If the :code:`docs/readthedocs.org:debug` check fails, the documentation could not be built properly. Inspect the corresponding log file for any errors. + - If :code:`cpp-linter` comments on your PR with a list of warnings, these have been raised by :code:`clang-tidy` when checking the C++ part of your changes for warnings or style guideline violations. The individual messages frequently provide helpful suggestions on how to fix the warnings. + - If the :code:`pre-commit.ci` check fails, some of the :code:`pre-commit` checks failed and could not be fixed automatically by the *pre-commit.ci* bot. Such failures are most likely related to the Python part of the code base. The individual log messages frequently provide helpful suggestions on how to fix the warnings. + +- Once your PR is ready, change it from a draft PR to a regular PR and request a review from one of the project maintainers. +- If your PR gets a "Changes requested" review, you will need to address the feedback and update your PR by pushing to the same branch. You don't need to close the PR and open a new one. Respond to review comments on the PR (e.g., with "done 👍"). Be sure to re-request review once you have made changes after a code review so that maintainers know that the requests have been addressed. + +.. raw:: html + + <hr> + +This document was inspired by and partially adapted from + +- https://matplotlib.org/stable/devel/coding_guide.html +- https://opensource.creativecommons.org/contributing-code/pr-guidelines/ +- https://yeoman.io/contributing/pull-request.html +- https://github.com/scikit-build/scikit-build diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..ea60e7e9 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,36 @@ +version: 2 +updates: + - package-ecosystem: "gitsubmodule" + directory: "/" + groups: + submodules: + patterns: + - "*" + schedule: + interval: "monthly" + time: "06:00" + timezone: "Europe/Vienna" + + - package-ecosystem: "github-actions" + directory: "/" + groups: + github-actions: + patterns: + - "*" + schedule: + interval: "weekly" + day: "friday" + time: "06:00" + timezone: "Europe/Vienna" + + - package-ecosystem: "pip" + directory: "/" + groups: + python-dependencies: + patterns: + - "*" + schedule: + interval: "weekly" + day: "friday" + time: "06:00" + timezone: "Europe/Vienna" diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 00000000..7ab5e2b4 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,16 @@ +## Description + +Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change. + +Fixes # (issue) + +## Checklist: + +<!--- +This checklist serves as a reminder of a couple of things that ensure your pull request will be merged swiftly. +--> + +- [ ] The pull request only contains commits that are related to it. +- [ ] I have added appropriate tests and documentation. +- [ ] I have made sure that all CI jobs on GitHub pass. +- [ ] The pull request introduces no new warnings and follows the project's style guidelines. diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml new file mode 100644 index 00000000..8ec99863 --- /dev/null +++ b/.github/release-drafter.yml @@ -0,0 +1,54 @@ +name-template: "MQT Debug $RESOLVED_VERSION Release" +tag-template: "v$RESOLVED_VERSION" +categories: + - title: "🚀 Features and Enhancements" + labels: + - "feature" + - "enhancement" + - "usability" + - title: "🐛 Bug Fixes" + labels: + - "bug" + - "fix" + - title: "📄 Documentation" + labels: + - "documentation" + - title: "🤖 CI" + labels: + - "continuous integration" + - title: "📦 Packaging" + labels: + - "packaging" + - title: "🧹 Code Quality" + labels: + - "code quality" + - title: "⬆️ Dependencies" + collapse-after: 5 + labels: + - "dependencies" + - "submodules" + - "github_actions" +change-template: "- $TITLE @$AUTHOR (#$NUMBER)" +change-title-escapes: '\<*_&' +version-resolver: + major: + labels: + - "major" + minor: + labels: + - "minor" + patch: + labels: + - "patch" + default: patch +autolabeler: + - label: "dependencies" + title: + - "/update pre-commit hooks/i" + +template: | + ## 👀 What Changed + + $CHANGES + + **Full Changelog**: https://github.com/$OWNER/$REPOSITORY/compare/$PREVIOUS_TAG...v$RESOLVED_VERSION diff --git a/.github/support.rst b/.github/support.rst new file mode 100644 index 00000000..1d8dc2b3 --- /dev/null +++ b/.github/support.rst @@ -0,0 +1,12 @@ +Support +======= + +If you are stuck with a problem using MQT Debug or are having questions, please do get in touch at our `Issues <https://github.com/cda-tum/mqt-debug/issues>`_ or `Discussions <https://github.com/cda-tum/mqt-debug/discussions>`_. We'd love to help. + +You can save time by following this procedure when reporting a problem: + +- Do try to solve the problem on your own first. Make sure to consult the `Documentation <https://mqt.readthedocs.io/projects/debug>`_. +- Search through past `Issues <https://github.com/cda-tum/mqt-debug/issues>`_ to see if someone else already had the same problem. +- Before filing a bug report, try to create a minimal working example (MWE) that reproduces the problem. It's much easier to identify the cause for the problem if a handful of lines suffice to show that something isn't working. + +You can also always reach us at `quantum.cda@xcit.tum.de <mailto:quantum.cda@xcit.tum.de>`_. diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml new file mode 100644 index 00000000..d34d0138 --- /dev/null +++ b/.github/workflows/cd.yml @@ -0,0 +1,37 @@ +name: CD +on: + release: + types: [published] + workflow_dispatch: + pull_request: + paths: + - .github/workflows/cd.yml + +jobs: + python-packaging: + name: 🐍 Packaging + uses: cda-tum/mqt-workflows/.github/workflows/reusable-python-packaging.yml@v1.1.5 + + deploy: + if: github.event_name == 'release' && github.event.action == 'published' + name: 🚀 Deploy to PyPI + runs-on: ubuntu-latest + environment: + name: pypi + url: https://pypi.org/p/mqt.qcec + permissions: + id-token: write + attestations: write + contents: read + needs: [python-packaging] + steps: + - uses: actions/download-artifact@v4 + with: + pattern: cibw-* + path: dist + merge-multiple: true + - name: Generate artifact attestation for sdist and wheel(s) + uses: actions/attest-build-provenance@v1.4.0 + with: + subject-path: "dist/*" + - uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..8ea69555 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,79 @@ +name: CI +on: + push: + branches: + - main + pull_request: + merge_group: + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + change-detection: + name: 🔍 Change + uses: cda-tum/mqt-workflows/.github/workflows/reusable-change-detection.yml@v1.1.5 + + cpp-tests: + name: 🇨‌ Test + needs: change-detection + if: fromJSON(needs.change-detection.outputs.run-cpp-tests) + uses: cda-tum/mqt-workflows/.github/workflows/reusable-cpp-ci.yml@v1.1.5 + with: + cmake-args: "" + cmake-args-ubuntu: -G Ninja + cmake-args-macos: -G Ninja -DMQT_CORE_WITH_GMP=ON + cmake-args-windows: -T ClangCL + + cpp-linter: + name: 🇨‌ Lint + needs: change-detection + if: fromJSON(needs.change-detection.outputs.run-cpp-linter) + uses: cda-tum/mqt-workflows/.github/workflows/reusable-cpp-linter.yml@v1.1.5 + + python-tests: + name: 🐍 Test + needs: change-detection + if: fromJSON(needs.change-detection.outputs.run-python-tests) + uses: cda-tum/mqt-workflows/.github/workflows/reusable-python-ci.yml@v1.1.5 + + code-ql: + name: 📝 CodeQL + needs: change-detection + if: fromJSON(needs.change-detection.outputs.run-code-ql) + uses: cda-tum/mqt-workflows/.github/workflows/reusable-code-ql.yml@v1.1.5 + + required-checks-pass: # This job does nothing and is only used for branch protection + name: 🚦 Check + if: always() + needs: + - change-detection + - cpp-tests + - cpp-linter + - python-tests + - code-ql + runs-on: ubuntu-latest + steps: + - name: Decide whether the needed jobs succeeded or failed + uses: re-actors/alls-green@release/v1 + with: + allowed-skips: >- + ${{ + fromJSON(needs.change-detection.outputs.run-cpp-tests) + && '' || 'cpp-tests,' + }} + ${{ + fromJSON(needs.change-detection.outputs.run-cpp-linter) + && '' || 'cpp-linter,' + }} + ${{ + fromJSON(needs.change-detection.outputs.run-python-tests) + && '' || 'python-tests,' + }} + ${{ + fromJSON(needs.change-detection.outputs.run-code-ql) + && '' || 'code-ql,' + }} + jobs: ${{ toJSON(needs) }} diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml new file mode 100644 index 00000000..02b69519 --- /dev/null +++ b/.github/workflows/release-drafter.yml @@ -0,0 +1,23 @@ +name: Release Drafter + +on: + push: + branches: + - main + pull_request_target: + types: [opened, reopened, synchronize] + +permissions: + contents: read + +jobs: + update_release_draft: + name: Run + permissions: + contents: write + pull-requests: write + runs-on: ubuntu-latest + steps: + - uses: release-drafter/release-drafter@v6 + env: + GITHUB_TOKEN: ${{ github.token }} diff --git a/.github/workflows/update-mqt-core.yml b/.github/workflows/update-mqt-core.yml new file mode 100644 index 00000000..a76522ea --- /dev/null +++ b/.github/workflows/update-mqt-core.yml @@ -0,0 +1,26 @@ +name: Update MQT Core +on: + schedule: + # run once a month on the first day of the month at 00:00 UTC + - cron: "0 0 1 * *" + workflow_dispatch: + inputs: + update-to-head: + description: "Update to the latest commit on the default branch" + type: boolean + required: false + default: false + pull_request: + paths: + - .github/workflows/update-mqt-core.yml + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + update-mqt-core: + name: ⬆️ Update MQT Core + uses: cda-tum/mqt-workflows/.github/workflows/reusable-mqt-core-update.yml@v1.1.5 + with: + update-to-head: ${{ fromJSON(github.event.inputs.update-to-head) || false }} From f7966b689c63a0fd4f583a9dcd01ff67f2253484 Mon Sep 17 00:00:00 2001 From: DRovara <damian.rovara@tum.de> Date: Tue, 6 Aug 2024 18:19:54 +0200 Subject: [PATCH 19/37] Empty commit to test CI From b3e8903c4a4773ed3c4d1ac3d37c11d330d2e3b8 Mon Sep 17 00:00:00 2001 From: DRovara <damian.rovara@tum.de> Date: Wed, 7 Aug 2024 06:39:05 +0200 Subject: [PATCH 20/37] refactor: :recycle: Remove need for `back_inserter` in CodePreprocessing.cpp --- src/common/parsing/CodePreprocessing.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/common/parsing/CodePreprocessing.cpp b/src/common/parsing/CodePreprocessing.cpp index 722b22e2..fac21018 100644 --- a/src/common/parsing/CodePreprocessing.cpp +++ b/src/common/parsing/CodePreprocessing.cpp @@ -213,11 +213,11 @@ preprocessCode(const std::string& code, size_t startIndex, instructions.emplace_back(i - subInstructions.size() - 1, line, a, targets, trueStart, trueEnd, i + 1, false, "", false, block); + auto& functionInstruction = instructions.back(); for (auto& instr : subInstructions) { - instructions.back().childInstructions.push_back(instr.lineNumber); + functionInstruction.childInstructions.push_back(instr.lineNumber); + instructions.push_back(std::move(instr)); } - std::move(subInstructions.begin(), subInstructions.end(), - std::back_inserter(instructions)); const auto closingBrace = code.find( '}', instructions[instructions.size() - 1].originalCodeEndPosition); From 18a2d97baecb19308cd62aaf7ca1d83bd1e6a5a6 Mon Sep 17 00:00:00 2001 From: DRovara <damian.rovara@tum.de> Date: Wed, 7 Aug 2024 12:51:29 +0200 Subject: [PATCH 21/37] docs: :memo: Add README.md (incomplete) --- README.md | 89 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/README.md b/README.md index e69de29b..b1802c49 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,89 @@ +[![PyPI](https://img.shields.io/pypi/v/mqt.debug?logo=pypi&style=flat-square)](https://pypi.org/project/mqt.debug/) +![OS](https://img.shields.io/badge/os-linux%20%7C%20macos%20%7C%20windows-blue?style=flat-square) +[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square)](https://opensource.org/licenses/MIT) +[![CI](https://img.shields.io/github/actions/workflow/status/cda-tum/mqt-debug/ci.yml?branch=main&style=flat-square&logo=github&label=ci)](https://github.com/cda-tum/mqt-debug/actions/workflows/ci.yml) +[![CD](https://img.shields.io/github/actions/workflow/status/cda-tum/mqt-debug/cd.yml?style=flat-square&logo=github&label=cd)](https://github.com/cda-tum/mqt-debug/actions/workflows/cd.yml) +[![Documentation](https://img.shields.io/readthedocs/debug?logo=readthedocs&style=flat-square)](https://mqt.readthedocs.io/projects/debug) +[![codecov](https://img.shields.io/codecov/c/github/cda-tum/mqt-debug?style=flat-square&logo=codecov)](https://codecov.io/gh/cda-tum/mqt-debug) + +<p align="center"> + <a href="https://mqt.readthedocs.io"> + <picture> + <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/cda-tum/mqt/main/docs/_static/mqt_light.png" width="60%"> + <img src="https://raw.githubusercontent.com/cda-tum/mqt/main/docs/_static/mqt_dark.png" width="60%"> + </picture> + </a> +</p> + +# MQT Debug - A Quantum Circuit Debugging Tool + +A tool for debugging quantum circuits developed as part of the [_Munich Quantum Toolkit (MQT)_](https://mqt.readthedocs.io) by the [Chair for Design Automation](https://www.cda.cit.tum.de/) at the [Technical University of Munich](https://www.tum.de/). +It proposes an interface for the simulation of circuits and diagnosis of errors and provides a base implementation built upon [MQT Core](https://github.com/cda-tum/mqt-core), which forms the backbone of the MQT. +It also provides a Debugger Adapter Protocol (DAP) server that can be used to integrate the debugger into IDEs. + +<p align="center"> + <a href="https://mqt.readthedocs.io/projects/debug"> + <img width=30% src="https://img.shields.io/badge/documentation-blue?style=for-the-badge&logo=read%20the%20docs" alt="Documentation" /> + </a> +</p> + +If you have any questions, feel free to contact us via [quantum.cda@xcit.tum.de](mailto:quantum.cda@xcit.tum.de) or by creating an issue on [GitHub](https://github.com/cda-tum/mqt-debug/issues). + +## Getting Started + +MQT Debug is available via [PyPI](https://pypi.org/project/mqt.debug/) for Linux, macOS, and Windows and supports Python 3.8 to 3.12. + +```console +(venv) $ pip install mqt.debug +``` + +The following code gives an example on the usage: + +```python3 +from mqt import debug + +state = debug.create_ddsim_simulation_state() +with open("code.qasm", "r") as f: + state.load_code(f.read()) +f.run_simulation() +print(f.get_state_vector_full()) +``` + +**Detailed documentation on all available methods, options, and input formats is available at [ReadTheDocs](https://mqt.readthedocs.io/projects/debug).** + +## System Requirements and Building + +The implementation is compatible with any C++20 compiler, a minimum CMake version of 3.19, and Python 3.8+. +Please refer to the [documentation](https://mqt.readthedocs.io/projects/debug) on how to build the project. + +Building (and running) is continuously tested under Linux, macOS, and Windows using the [latest available system versions for GitHub Actions](https://github.com/actions/virtual-environments). + +## References + +MQT Debug has been developed based on methods proposed in the following papers: + +TODO + +## Acknowledgements + +The Munich Quantum Toolkit has been supported by the European +Research Council (ERC) under the European Union's Horizon 2020 research and innovation program (grant agreement +No. 101001318), the Bavarian State Ministry for Science and Arts through the Distinguished Professorship Program, as well as the +Munich Quantum Valley, which is supported by the Bavarian state government with funds from the Hightech Agenda Bayern Plus. + +<p align="center"> +<picture> +<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/cda-tum/mqt/main/docs/_static/tum_dark.svg" width="28%"> +<img src="https://raw.githubusercontent.com/cda-tum/mqt/main/docs/_static/tum_light.svg" width="28%" alt="TUM Logo"> +</picture> +<picture> +<img src="https://raw.githubusercontent.com/cda-tum/mqt/main/docs/_static/logo-bavaria.svg" width="16%" alt="Coat of Arms of Bavaria"> +</picture> +<picture> +<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/cda-tum/mqt/main/docs/_static/erc_dark.svg" width="24%"> +<img src="https://raw.githubusercontent.com/cda-tum/mqt/main/docs/_static/erc_light.svg" width="24%" alt="ERC Logo"> +</picture> +<picture> +<img src="https://raw.githubusercontent.com/cda-tum/mqt/main/docs/_static/logo-mqv.svg" width="28%" alt="MQV Logo"> +</picture> +</p> From 4dc5027112ada011e42682aa8166a3c1fb99b0e9 Mon Sep 17 00:00:00 2001 From: DRovara <damian.rovara@tum.de> Date: Wed, 7 Aug 2024 16:20:52 +0200 Subject: [PATCH 22/37] ci: :construction_worker: Allow id-token: write for cpp-tests --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8ea69555..1039b984 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,6 +21,8 @@ jobs: needs: change-detection if: fromJSON(needs.change-detection.outputs.run-cpp-tests) uses: cda-tum/mqt-workflows/.github/workflows/reusable-cpp-ci.yml@v1.1.5 + permissions: + id-token: write # Explicitly allows the `id-token: write` permission for this job with: cmake-args: "" cmake-args-ubuntu: -G Ninja From d14a22804fdb4de46d820feeccfdd0f5848d4af5 Mon Sep 17 00:00:00 2001 From: DRovara <damian.rovara@tum.de> Date: Wed, 7 Aug 2024 16:21:53 +0200 Subject: [PATCH 23/37] ci: :construction_worker: Also allow id-token: write for python tests workflow --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1039b984..2d0a397f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,6 +38,8 @@ jobs: python-tests: name: 🐍 Test needs: change-detection + permissions: + id-token: write # Explicitly allows the `id-token: write` permission for this job if: fromJSON(needs.change-detection.outputs.run-python-tests) uses: cda-tum/mqt-workflows/.github/workflows/reusable-python-ci.yml@v1.1.5 From 3bccba64b3c6788930d9ac1db0b1597022be0832 Mon Sep 17 00:00:00 2001 From: DRovara <damian.rovara@tum.de> Date: Wed, 7 Aug 2024 16:39:11 +0200 Subject: [PATCH 24/37] fix: :construction_worker: Possibly fix error of Update MQT Core workflow --- .github/workflows/update-mqt-core.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/update-mqt-core.yml b/.github/workflows/update-mqt-core.yml index a76522ea..f1314064 100644 --- a/.github/workflows/update-mqt-core.yml +++ b/.github/workflows/update-mqt-core.yml @@ -23,4 +23,4 @@ jobs: name: ⬆️ Update MQT Core uses: cda-tum/mqt-workflows/.github/workflows/reusable-mqt-core-update.yml@v1.1.5 with: - update-to-head: ${{ fromJSON(github.event.inputs.update-to-head) || false }} + update-to-head: ${{ github.event.inputs.update-to-head != '' && fromJSON(github.event.inputs.update-to-head) || false }} From f61dcdd485e6e41d1073c0328cea0583f7c4a537 Mon Sep 17 00:00:00 2001 From: DRovara <damian.rovara@tum.de> Date: Thu, 8 Aug 2024 13:43:05 +0200 Subject: [PATCH 25/37] fix: :bug: Fix majority of linting errors --- app/code/test.qasm | 38 ++--- include/backend/dd/DDSimDebug.hpp | 12 +- include/backend/dd/DDSimDiagnostics.hpp | 4 +- include/common/parsing/AssertionParsing.hpp | 5 +- include/common/parsing/CodePreprocessing.hpp | 14 +- include/common/parsing/ParsingError.hpp | 2 + include/frontend/cli/CliFrontEnd.hpp | 2 +- include/python/InterfaceBindings.hpp | 2 +- include/python/dd/DDSimDebugBindings.hpp | 2 +- src/backend/dd/DDSimDebug.cpp | 149 +++++++++++-------- src/backend/dd/DDSimDiagnostics.cpp | 46 ++++-- src/common/parsing/AssertionParsing.cpp | 12 +- src/common/parsing/CodePreprocessing.cpp | 19 ++- src/common/parsing/ParsingError.cpp | 3 + src/common/parsing/Utils.cpp | 4 + src/frontend/cli/CliFrontEnd.cpp | 13 +- src/python/InterfaceBindings.cpp | 20 +-- src/python/bindings.cpp | 2 - src/python/dd/DDSimDebugBindings.cpp | 16 +- test/test_custom_code.cpp | 21 +++ test/test_data_retrieval.cpp | 20 ++- test/test_diagnostics.cpp | 19 +-- test/test_simulation.cpp | 9 +- test/test_utility.cpp | 5 +- test/utils_test.cpp | 12 +- 25 files changed, 284 insertions(+), 167 deletions(-) diff --git a/app/code/test.qasm b/app/code/test.qasm index 30ea2bf7..a0b56a0c 100644 --- a/app/code/test.qasm +++ b/app/code/test.qasm @@ -1,35 +1,15 @@ -gate oracle q0, q1, q2, flag { - // mark target state |111> - assert-sup q0, q1, q2; - mcphase(pi) q0, q1, q2, flag; -} +qreg q[3]; -gate diffusion q0, q1, q2 { - h q0; h q1; h q2; - x q0; x q1; x q2; - h q2; - ccx q0, q1, q2; - h q2; - x q2; x q1; x q0; - h q2; h q1; h q0; +gate entangle q0, q1, q2 { + cx q0, q1; + cx q0, q2; + barrier q2; } -qreg q[3]; -qreg flag[1]; -creg c[3]; - -// initialization -h q; -x flag; +h q[0]; -// repetition 1 -oracle q[0], q[1], q[2], flag; -diffusion q[0], q[1], q[2]; -assert-eq 0.8, q[0], q[1], q[2] { 0, 0, 0, 0, 0, 0, 0, 1 } +entangle q[0], q[1], q[2]; -// repetition 2 -oracle q[0], q[1], q[2], flag; -diffusion q[0], q[1], q[2]; -assert-eq 0.9, q[0], q[1], q[2] { 0, 0, 0, 0, 0, 0, 0, 1 } +h q[2]; -measure q -> c; +barrier q[2]; diff --git a/include/backend/dd/DDSimDebug.hpp b/include/backend/dd/DDSimDebug.hpp index 7fb05ec8..64d68f60 100644 --- a/include/backend/dd/DDSimDebug.hpp +++ b/include/backend/dd/DDSimDebug.hpp @@ -3,14 +3,22 @@ #include "DDSimDiagnostics.hpp" #include "QuantumComputation.hpp" #include "backend/debug.h" +#include "backend/diagnostics.h" #include "common.h" #include "common/parsing/AssertionParsing.hpp" -#include "dd/Operations.hpp" #include "dd/Package.hpp" +#include "operations/Operation.hpp" +#include <cstddef> +#include <cstdint> +#include <map> +#include <memory> +#include <set> #include <string> +#include <utility> +#include <vector> -enum InstructionType { NOP, SIMULATE, ASSERTION, CALL, RETURN }; +enum InstructionType : uint8_t { NOP, SIMULATE, ASSERTION, CALL, RETURN }; struct QubitRegisterDefinition { std::string name; diff --git a/include/backend/dd/DDSimDiagnostics.hpp b/include/backend/dd/DDSimDiagnostics.hpp index 3693187d..7174f72b 100644 --- a/include/backend/dd/DDSimDiagnostics.hpp +++ b/include/backend/dd/DDSimDiagnostics.hpp @@ -1,10 +1,12 @@ #pragma once -#include "backend/debug.h" #include "backend/diagnostics.h" +#include "common.h" #include "common/parsing/AssertionParsing.hpp" +#include <cstddef> #include <map> +#include <memory> #include <set> struct DDSimulationState; diff --git a/include/common/parsing/AssertionParsing.hpp b/include/common/parsing/AssertionParsing.hpp index 715ffed5..2c8c4a4c 100644 --- a/include/common/parsing/AssertionParsing.hpp +++ b/include/common/parsing/AssertionParsing.hpp @@ -2,11 +2,12 @@ #include "common.h" +#include <cstdint> #include <memory> #include <string> #include <vector> -enum class AssertionType { +enum class AssertionType : uint8_t { Entanglement, Superposition, Span, @@ -77,5 +78,5 @@ class CircuitEqualityAssertion : public EqualityAssertion { }; std::unique_ptr<Assertion> parseAssertion(std::string assertionString, - std::string blockContent); + const std::string& blockContent); bool isAssertion(std::string expression); diff --git a/include/common/parsing/CodePreprocessing.hpp b/include/common/parsing/CodePreprocessing.hpp index db40cd3b..7cd83966 100644 --- a/include/common/parsing/CodePreprocessing.hpp +++ b/include/common/parsing/CodePreprocessing.hpp @@ -2,9 +2,12 @@ #include "AssertionParsing.hpp" +#include <cstddef> #include <map> +#include <memory> #include <set> #include <string> +#include <vector> struct Block { bool valid; @@ -32,12 +35,11 @@ struct Instruction { Block block; std::vector<size_t> childInstructions; - Instruction(size_t lineNumber, std::string code, - std::unique_ptr<Assertion>& assertion, - std::set<std::string> targets, size_t originalCodeStartPosition, - size_t originalCodeEndPosition, size_t successorIndex, - bool isFunctionCall, std::string calledFunction, - bool inFunctionDefinition, Block block); + Instruction(size_t inputLineNumber, std::string inputCode, + std::unique_ptr<Assertion>& inputAssertion, + std::set<std::string> inputTargets, size_t startPos, + size_t endPos, size_t successor, bool isFuncCall, + std::string function, bool inFuncDef, Block inputBlock); }; struct FunctionDefinition { diff --git a/include/common/parsing/ParsingError.hpp b/include/common/parsing/ParsingError.hpp index 5613ae35..d5892248 100644 --- a/include/common/parsing/ParsingError.hpp +++ b/include/common/parsing/ParsingError.hpp @@ -1,6 +1,8 @@ #pragma once #include <stdexcept> +#include <string> + class ParsingError : public std::runtime_error { public: explicit ParsingError(const std::string& msg); diff --git a/include/frontend/cli/CliFrontEnd.hpp b/include/frontend/cli/CliFrontEnd.hpp index 32e94b65..d7935ba4 100644 --- a/include/frontend/cli/CliFrontEnd.hpp +++ b/include/frontend/cli/CliFrontEnd.hpp @@ -2,8 +2,8 @@ #include "backend/debug.h" +#include <cstddef> #include <string> -#include <vector> #define ANSI_BG_YELLOW "\x1b[43m" #define ANSI_BG_RESET "\x1b[0m" diff --git a/include/python/InterfaceBindings.hpp b/include/python/InterfaceBindings.hpp index 89beb74b..7448f1ba 100644 --- a/include/python/InterfaceBindings.hpp +++ b/include/python/InterfaceBindings.hpp @@ -1,6 +1,6 @@ #pragma once -#include <pybind11/pybind11.h> +#include "pybind11/pybind11.h" void bindFramework(pybind11::module& m); diff --git a/include/python/dd/DDSimDebugBindings.hpp b/include/python/dd/DDSimDebugBindings.hpp index 8692dc59..802caa6c 100644 --- a/include/python/dd/DDSimDebugBindings.hpp +++ b/include/python/dd/DDSimDebugBindings.hpp @@ -1,5 +1,5 @@ #pragma once -#include <pybind11/pybind11.h> +#include "pybind11/pybind11.h" void bindBackend(pybind11::module& m); diff --git a/src/backend/dd/DDSimDebug.cpp b/src/backend/dd/DDSimDebug.cpp index 849cb195..2fadc56b 100644 --- a/src/backend/dd/DDSimDebug.cpp +++ b/src/backend/dd/DDSimDebug.cpp @@ -1,21 +1,42 @@ -#pragma ide diagnostic ignored "cppcoreguidelines-pro-type-reinterpret-cast" - #include "backend/dd/DDSimDebug.hpp" #include "CircuitOptimizer.hpp" +#include "Definitions.hpp" +#include "backend/dd/DDSimDiagnostics.hpp" #include "backend/debug.h" +#include "backend/diagnostics.h" #include "common.h" #include "common/parsing/AssertionParsing.hpp" #include "common/parsing/CodePreprocessing.hpp" -#include "common/parsing/ParsingError.hpp" #include "common/parsing/Utils.hpp" +#include "dd/DDDefinitions.hpp" +#include "dd/Operations.hpp" +#include "dd/Package.hpp" +#include "operations/ClassicControlledOperation.hpp" +#include "operations/OpType.hpp" #include <algorithm> +#include <array> +#include <cmath> +#include <cstddef> +#include <cstring> +#include <exception> #include <iostream> +#include <memory> +#include <numeric> #include <random> #include <span> +#include <sstream> +#include <stdexcept> #include <string> #include <utility> +#include <vector> + +DDSimulationState* toDDSimulationState(SimulationState* state) { + // NOLINTBEGIN(cppcoreguidelines-pro-type-reinterpret-cast) + return reinterpret_cast<DDSimulationState*>(state); + // NOLINTEND(cppcoreguidelines-pro-type-reinterpret-cast) +} double generateRandomNumber() { std::random_device @@ -65,7 +86,9 @@ Result createDDSimulationState(DDSimulationState* self) { self->interface.getStackDepth = ddsimGetStackDepth; self->interface.getStackTrace = ddsimGetStackTrace; + // NOLINTBEGIN(cppcoreguidelines-pro-type-reinterpret-cast) return self->interface.init(reinterpret_cast<SimulationState*>(self)); + // NOLINTEND(cppcoreguidelines-pro-type-reinterpret-cast) } #pragma clang diagnostic pop @@ -79,7 +102,7 @@ void resetSimulationState(DDSimulationState* ddsim) { } Result ddsimInit(SimulationState* self) { - auto* ddsim = reinterpret_cast<DDSimulationState*>(self); + auto* ddsim = toDDSimulationState(self); ddsim->simulationState.p = nullptr; ddsim->qc = std::make_unique<qc::QuantumComputation>(); @@ -105,7 +128,7 @@ Result ddsimInit(SimulationState* self) { } Result ddsimLoadCode(SimulationState* self, const char* code) { - auto* ddsim = reinterpret_cast<DDSimulationState*>(self); + auto* ddsim = toDDSimulationState(self); ddsim->currentInstruction = 0; ddsim->previousInstructionStack.clear(); ddsim->callReturnStack.clear(); @@ -140,7 +163,7 @@ Result ddsimStepOverForward(SimulationState* self) { if (!self->canStepForward(self)) { return ERROR; } - auto* ddsim = reinterpret_cast<DDSimulationState*>(self); + auto* ddsim = toDDSimulationState(self); if (ddsim->instructionTypes[ddsim->currentInstruction] != CALL) { return self->stepForward(self); } @@ -169,7 +192,7 @@ Result ddsimStepOverBackward(SimulationState* self) { if (!self->canStepBackward(self)) { return ERROR; } - auto* ddsim = reinterpret_cast<DDSimulationState*>(self); + auto* ddsim = toDDSimulationState(self); const auto prev = ddsim->previousInstructionStack.back(); if (ddsim->instructionTypes[prev] != RETURN) { return self->stepBackward(self); @@ -195,7 +218,7 @@ Result ddsimStepOverBackward(SimulationState* self) { } Result ddsimStepOutForward(SimulationState* self) { - auto* ddsim = reinterpret_cast<DDSimulationState*>(self); + auto* ddsim = toDDSimulationState(self); if (ddsim->callReturnStack.empty()) { return self->runSimulation(self); } @@ -218,7 +241,7 @@ Result ddsimStepOutForward(SimulationState* self) { } Result ddsimStepOutBackward(SimulationState* self) { - auto* ddsim = reinterpret_cast<DDSimulationState*>(self); + auto* ddsim = toDDSimulationState(self); if (ddsim->callReturnStack.empty()) { return self->runSimulationBackward(self); } @@ -244,7 +267,7 @@ Result ddsimStepOutBackward(SimulationState* self) { } Result ddsimStepForward(SimulationState* self) { - auto* ddsim = reinterpret_cast<DDSimulationState*>(self); + auto* ddsim = toDDSimulationState(self); if (!self->canStepForward(self)) { return ERROR; } @@ -381,7 +404,7 @@ Result ddsimStepForward(SimulationState* self) { } Result ddsimStepBackward(SimulationState* self) { - auto* ddsim = reinterpret_cast<DDSimulationState*>(self); + auto* ddsim = toDDSimulationState(self); if (!self->canStepBackward(self)) { return ERROR; } @@ -456,7 +479,7 @@ Result ddsimStepBackward(SimulationState* self) { Result ddsimRunAll(SimulationState* self, size_t* failedAssertions) { size_t errorCount = 0; while (!self->isFinished(self)) { - Result result = self->runSimulation(self); + const Result result = self->runSimulation(self); if (result != OK) { return result; } @@ -469,7 +492,7 @@ Result ddsimRunAll(SimulationState* self, size_t* failedAssertions) { } Result ddsimRunSimulation(SimulationState* self) { - auto* ddsim = reinterpret_cast<DDSimulationState*>(self); + auto* ddsim = toDDSimulationState(self); while (!self->isFinished(self)) { if (ddsim->paused) { ddsim->paused = false; @@ -487,13 +510,13 @@ Result ddsimRunSimulation(SimulationState* self) { } Result ddsimRunSimulationBackward(SimulationState* self) { - auto* ddsim = reinterpret_cast<DDSimulationState*>(self); + auto* ddsim = toDDSimulationState(self); while (self->canStepBackward(self)) { if (ddsim->paused) { ddsim->paused = false; return OK; } - Result res = self->stepBackward(self); + const Result res = self->stepBackward(self); if (res != OK) { return res; } @@ -505,7 +528,7 @@ Result ddsimRunSimulationBackward(SimulationState* self) { } Result ddsimResetSimulation(SimulationState* self) { - auto* ddsim = reinterpret_cast<DDSimulationState*>(self); + auto* ddsim = toDDSimulationState(self); ddsim->currentInstruction = 0; ddsim->previousInstructionStack.clear(); ddsim->callReturnStack.clear(); @@ -520,80 +543,80 @@ Result ddsimResetSimulation(SimulationState* self) { } Result ddsimPauseSimulation(SimulationState* self) { - auto* ddsim = reinterpret_cast<DDSimulationState*>(self); + auto* ddsim = toDDSimulationState(self); ddsim->paused = true; return OK; } bool ddsimCanStepForward(SimulationState* self) { - auto* ddsim = reinterpret_cast<DDSimulationState*>(self); + auto* ddsim = toDDSimulationState(self); return ddsim->currentInstruction < ddsim->instructionTypes.size(); } bool ddsimCanStepBackward(SimulationState* self) { - auto* ddsim = reinterpret_cast<DDSimulationState*>(self); + auto* ddsim = toDDSimulationState(self); return !ddsim->previousInstructionStack.empty(); } bool ddsimIsFinished(SimulationState* self) { - auto* ddsim = reinterpret_cast<DDSimulationState*>(self); + auto* ddsim = toDDSimulationState(self); return ddsim->currentInstruction == ddsim->instructionTypes.size(); } bool ddsimDidAssertionFail(SimulationState* self) { - auto* ddsim = reinterpret_cast<DDSimulationState*>(self); + auto* ddsim = toDDSimulationState(self); return ddsim->lastFailedAssertion == ddsim->currentInstruction; } bool ddsimWasBreakpointHit(SimulationState* self) { - auto* ddsim = reinterpret_cast<DDSimulationState*>(self); + auto* ddsim = toDDSimulationState(self); return ddsim->lastMetBreakpoint == ddsim->currentInstruction; } size_t ddsimGetCurrentInstruction(SimulationState* self) { - auto* ddsim = reinterpret_cast<DDSimulationState*>(self); + auto* ddsim = toDDSimulationState(self); return ddsim->currentInstruction; } size_t ddsimGetInstructionCount(SimulationState* self) { - auto* ddsim = reinterpret_cast<DDSimulationState*>(self); + auto* ddsim = toDDSimulationState(self); return ddsim->instructionTypes.size(); } Result ddsimGetInstructionPosition(SimulationState* self, size_t instruction, size_t* start, size_t* end) { - auto* ddsim = reinterpret_cast<DDSimulationState*>(self); + auto* ddsim = toDDSimulationState(self); if (instruction >= ddsim->instructionStarts.size()) { return ERROR; } - size_t start_index = ddsim->instructionStarts[instruction]; - size_t end_index = ddsim->instructionEnds[instruction]; + size_t startIndex = ddsim->instructionStarts[instruction]; + size_t endIndex = ddsim->instructionEnds[instruction]; - while (ddsim->processedCode[start_index] == ' ' || - ddsim->processedCode[start_index] == '\n' || - ddsim->processedCode[start_index] == '\r' || - ddsim->processedCode[start_index] == '\t') { - start_index++; + while (ddsim->processedCode[startIndex] == ' ' || + ddsim->processedCode[startIndex] == '\n' || + ddsim->processedCode[startIndex] == '\r' || + ddsim->processedCode[startIndex] == '\t') { + startIndex++; } - while (ddsim->processedCode[end_index] == ' ' || - ddsim->processedCode[end_index] == '\n' || - ddsim->processedCode[end_index] == '\r' || - ddsim->processedCode[end_index] == '\t') { - end_index++; + while (ddsim->processedCode[endIndex] == ' ' || + ddsim->processedCode[endIndex] == '\n' || + ddsim->processedCode[endIndex] == '\r' || + ddsim->processedCode[endIndex] == '\t') { + endIndex++; } - *start = start_index; - *end = end_index; + *start = startIndex; + *end = endIndex; return OK; } size_t ddsimGetNumQubits(SimulationState* self) { - auto* ddsim = reinterpret_cast<DDSimulationState*>(self); + auto* ddsim = toDDSimulationState(self); return ddsim->qc->getNqubits(); } Result ddsimGetAmplitudeIndex(SimulationState* self, size_t qubit, Complex* output) { - auto* ddsim = reinterpret_cast<DDSimulationState*>(self); + auto* ddsim = toDDSimulationState(self); auto result = ddsim->simulationState.getValueByIndex(qubit); output->real = result.real(); output->imaginary = result.imag(); @@ -602,7 +625,7 @@ Result ddsimGetAmplitudeIndex(SimulationState* self, size_t qubit, Result ddsimGetAmplitudeBitstring(SimulationState* self, const char* bitstring, Complex* output) { - auto* ddsim = reinterpret_cast<DDSimulationState*>(self); + auto* ddsim = toDDSimulationState(self); auto path = std::string(bitstring); std::reverse(path.begin(), path.end()); auto result = @@ -614,7 +637,7 @@ Result ddsimGetAmplitudeBitstring(SimulationState* self, const char* bitstring, Result ddsimGetClassicalVariable(SimulationState* self, const char* name, Variable* output) { - auto* ddsim = reinterpret_cast<DDSimulationState*>(self); + auto* ddsim = toDDSimulationState(self); if (ddsim->variables.find(name) != ddsim->variables.end()) { *output = ddsim->variables[name]; return OK; @@ -622,12 +645,12 @@ Result ddsimGetClassicalVariable(SimulationState* self, const char* name, return ERROR; } size_t ddsimGetNumClassicalVariables(SimulationState* self) { - auto* ddsim = reinterpret_cast<DDSimulationState*>(self); + auto* ddsim = toDDSimulationState(self); return ddsim->variables.size(); } Result ddsimGetClassicalVariableName(SimulationState* self, size_t variableIndex, char* output) { - auto* ddsim = reinterpret_cast<DDSimulationState*>(self); + auto* ddsim = toDDSimulationState(self); if (variableIndex >= ddsim->variables.size()) { return ERROR; @@ -648,7 +671,7 @@ Result ddsimGetStateVectorFull(SimulationState* self, Statevector* output) { Result ddsimGetStateVectorSub(SimulationState* self, size_t subStateSize, const size_t* qubits, Statevector* output) { - auto* ddsim = reinterpret_cast<DDSimulationState*>(self); + auto* ddsim = toDDSimulationState(self); Statevector fullState; fullState.numQubits = ddsim->qc->getNqubits(); fullState.numStates = 1 << fullState.numQubits; @@ -677,16 +700,16 @@ Result ddsimGetStateVectorSub(SimulationState* self, size_t subStateSize, } Diagnostics* ddsimGetDiagnostics(SimulationState* self) { - auto* ddsim = reinterpret_cast<DDSimulationState*>(self); + auto* ddsim = toDDSimulationState(self); return &ddsim->diagnostics.interface; } Result ddsimSetBreakpoint(SimulationState* self, size_t desiredPosition, size_t* targetInstruction) { - auto* ddsim = reinterpret_cast<DDSimulationState*>(self); + auto* ddsim = toDDSimulationState(self); for (auto i = 0ULL; i < ddsim->instructionTypes.size(); i++) { - size_t start = ddsim->instructionStarts[i]; - size_t end = ddsim->instructionEnds[i]; + const size_t start = ddsim->instructionStarts[i]; + const size_t end = ddsim->instructionEnds[i]; if (desiredPosition >= start && desiredPosition <= end) { *targetInstruction = i; ddsim->breakpoints.insert(i); @@ -697,27 +720,29 @@ Result ddsimSetBreakpoint(SimulationState* self, size_t desiredPosition, } Result ddsimClearBreakpoints(SimulationState* self) { - auto* ddsim = reinterpret_cast<DDSimulationState*>(self); + auto* ddsim = toDDSimulationState(self); ddsim->breakpoints.clear(); return OK; } Result ddsimGetStackDepth(SimulationState* self, size_t* depth) { - auto* ddsim = reinterpret_cast<DDSimulationState*>(self); - if (!ddsim->ready) + auto* ddsim = toDDSimulationState(self); + if (!ddsim->ready) { return ERROR; + } *depth = ddsim->callReturnStack.size() + 1; return OK; } Result ddsimGetStackTrace(SimulationState* self, size_t maxDepth, size_t* output) { - auto* ddsim = reinterpret_cast<DDSimulationState*>(self); - if (!ddsim->ready || maxDepth == 0) + auto* ddsim = toDDSimulationState(self); + if (!ddsim->ready || maxDepth == 0) { return ERROR; + } size_t depth = 0; self->getStackDepth(self, &depth); - std::span<size_t> stackFrames(output, maxDepth); + const std::span<size_t> stackFrames(output, maxDepth); stackFrames[0] = self->getCurrentInstruction(self); for (auto i = 1ULL; i < maxDepth; i++) { if (i >= depth) { @@ -835,7 +860,7 @@ bool checkAssertionEntangled( DDSimulationState* ddsim, std::unique_ptr<EntanglementAssertion>& assertion) { std::vector<size_t> qubits; - for (auto variable : assertion->getTargetQubits()) { + for (const auto& variable : assertion->getTargetQubits()) { qubits.push_back(variableToQubit(ddsim, variable)); } @@ -870,7 +895,7 @@ bool checkAssertionSuperposition( DDSimulationState* ddsim, std::unique_ptr<SuperpositionAssertion>& assertion) { std::vector<size_t> qubits; - for (auto variable : assertion->getTargetQubits()) { + for (const auto& variable : assertion->getTargetQubits()) { qubits.push_back(variableToQubit(ddsim, variable)); } @@ -904,7 +929,7 @@ bool checkAssertionEqualityStatevector( DDSimulationState* ddsim, std::unique_ptr<StatevectorEqualityAssertion>& assertion) { std::vector<size_t> qubits; - for (auto variable : assertion->getTargetQubits()) { + for (const auto& variable : assertion->getTargetQubits()) { qubits.push_back(variableToQubit(ddsim, variable)); } @@ -949,7 +974,7 @@ bool checkAssertionEqualityCircuit( destroyDDSimulationState(&secondSimulation); std::vector<size_t> qubits; - for (auto variable : assertion->getTargetQubits()) { + for (const auto& variable : assertion->getTargetQubits()) { qubits.push_back(variableToQubit(ddsim, variable)); } Statevector sv; @@ -1129,9 +1154,11 @@ std::string preprocessAssertionCode(const char* code, } } - return std::accumulate( + const auto result = std::accumulate( correctLines.begin(), correctLines.end(), std::string(), [](const std::string& a, const std::string& b) { return a + b; }); + + return result; } std::string getClassicalBitName(DDSimulationState* ddsim, size_t index) { diff --git a/src/backend/dd/DDSimDiagnostics.cpp b/src/backend/dd/DDSimDiagnostics.cpp index ba882f46..14239f08 100644 --- a/src/backend/dd/DDSimDiagnostics.cpp +++ b/src/backend/dd/DDSimDiagnostics.cpp @@ -1,10 +1,31 @@ -#pragma ide diagnostic ignored "cppcoreguidelines-pro-type-reinterpret-cast" - #include "backend/dd/DDSimDiagnostics.hpp" #include "backend/dd/DDSimDebug.hpp" - +#include "backend/diagnostics.h" +#include "common.h" +#include "common/parsing/AssertionParsing.hpp" + +#include <algorithm> +#include <cstddef> +#include <cstdint> +#include <map> +#include <memory> +#include <set> #include <span> +#include <string> +#include <vector> + +DDDiagnostics* toDDDiagnostics(Diagnostics* diagnostics) { + // NOLINTBEGIN(cppcoreguidelines-pro-type-reinterpret-cast) + return reinterpret_cast<DDDiagnostics*>(diagnostics); + // NOLINTEND(cppcoreguidelines-pro-type-reinterpret-cast) +} + +bool* toBoolArray(uint8_t* array) { + // NOLINTBEGIN(cppcoreguidelines-pro-type-reinterpret-cast) + return reinterpret_cast<bool*>(array); + // NOLINTEND(cppcoreguidelines-pro-type-reinterpret-cast) +} Result createDDDiagnostics(DDDiagnostics* self, DDSimulationState* state) { self->simulationState = state; @@ -22,13 +43,13 @@ Result createDDDiagnostics(DDDiagnostics* self, DDSimulationState* state) { Result destroyDDDiagnostics([[maybe_unused]] DDDiagnostics* self) { return OK; } size_t dddiagnosticsGetNumQubits(Diagnostics* self) { - const auto* ddd = reinterpret_cast<DDDiagnostics*>(self); + const auto* ddd = toDDDiagnostics(self); return ddd->simulationState->interface.getNumQubits( &ddd->simulationState->interface); } size_t dddiagnosticsGetInstructionCount(Diagnostics* self) { - const auto* ddd = reinterpret_cast<DDDiagnostics*>(self); + const auto* ddd = toDDDiagnostics(self); return ddd->simulationState->interface.getInstructionCount( &ddd->simulationState->interface); } @@ -37,7 +58,7 @@ Result dddiagnosticsInit([[maybe_unused]] Diagnostics* self) { return OK; } Result dddiagnosticsGetDataDependencies(Diagnostics* self, size_t instruction, bool* instructions) { - auto* ddd = reinterpret_cast<DDDiagnostics*>(self); + auto* ddd = toDDDiagnostics(self); auto* ddsim = ddd->simulationState; const std::span<bool> isDependency( instructions, ddsim->interface.getInstructionCount(&ddsim->interface)); @@ -60,7 +81,7 @@ Result dddiagnosticsGetDataDependencies(Diagnostics* self, size_t instruction, Result dddiagnosticsGetInteractions(Diagnostics* self, size_t beforeInstruction, size_t qubit, bool* qubitsAreInteracting) { - auto* ddd = reinterpret_cast<DDDiagnostics*>(self); + auto* ddd = toDDDiagnostics(self); auto* ddsim = ddd->simulationState; std::set<size_t> interactions; interactions.insert(qubit); @@ -103,7 +124,7 @@ Result dddiagnosticsGetInteractions(Diagnostics* self, size_t beforeInstruction, size_t dddiagnosticsPotentialErrorCauses(Diagnostics* self, ErrorCause* output, size_t count) { - auto* ddd = reinterpret_cast<DDDiagnostics*>(self); + auto* ddd = toDDDiagnostics(self); auto* ddsim = ddd->simulationState; auto outputs = std::span(output, count); @@ -143,9 +164,9 @@ size_t tryFindMissingInteraction(DDDiagnostics* diagnostics, for (size_t i = 0; i < targets.size(); i++) { std::vector<uint8_t> interactions( diagnostics->interface.getNumQubits(&diagnostics->interface)); - diagnostics->interface.getInteractions( - &diagnostics->interface, instruction, targetQubits[i], - reinterpret_cast<bool*>(interactions.data())); + diagnostics->interface.getInteractions(&diagnostics->interface, instruction, + targetQubits[i], + toBoolArray(interactions.data())); allInteractions.insert({targetQubits[i], interactions}); } for (size_t i = 0; i < targets.size(); i++) { @@ -170,8 +191,7 @@ size_t tryFindZeroControls(DDDiagnostics* diagnostics, size_t instruction, std::vector<uint8_t> dependencies( diagnostics->interface.getInstructionCount(&diagnostics->interface)); diagnostics->interface.getDataDependencies( - &diagnostics->interface, instruction, - reinterpret_cast<bool*>(dependencies.data())); + &diagnostics->interface, instruction, toBoolArray(dependencies.data())); auto outputs = std::span(output, count); size_t index = 0; diff --git a/src/common/parsing/AssertionParsing.cpp b/src/common/parsing/AssertionParsing.cpp index 1d397093..e2f0fd17 100644 --- a/src/common/parsing/AssertionParsing.cpp +++ b/src/common/parsing/AssertionParsing.cpp @@ -1,12 +1,16 @@ #include "common/parsing/AssertionParsing.hpp" +#include "common.h" #include "common/parsing/ParsingError.hpp" #include "common/parsing/Utils.hpp" -#include <algorithm> #include <cmath> +#include <cstddef> #include <memory> +#include <stdexcept> +#include <string> #include <utility> +#include <vector> Assertion::Assertion(std::vector<std::string> inputTargetQubits, AssertionType assertionType) @@ -87,7 +91,7 @@ const std::string& CircuitEqualityAssertion::getCircuitCode() const { return circuitCode; } -std::vector<std::string> extractTargetQubits(std::string targetPart) { +std::vector<std::string> extractTargetQubits(const std::string& targetPart) { return splitString(targetPart, ','); } @@ -143,7 +147,7 @@ bool isAssertion(std::string expression) { } std::unique_ptr<Assertion> parseAssertion(std::string assertionString, - std::string blockContent) { + const std::string& blockContent) { assertionString = trim(replaceString(assertionString, ";", "")); if (startsWith(assertionString, "assert-ent")) { @@ -158,7 +162,7 @@ std::unique_ptr<Assertion> parseAssertion(std::string assertionString, auto sub = assertionString.substr(12); auto targets = extractTargetQubits(sub); auto statevectors = splitString(blockContent, ';'); - std::vector<Statevector> statevectorList; + std::vector<Statevector> statevectorList(statevectors.size()); for (auto& statevector : statevectors) { statevectorList.emplace_back(parseStatevector(statevector)); } diff --git a/src/common/parsing/CodePreprocessing.cpp b/src/common/parsing/CodePreprocessing.cpp index fac21018..165ec7db 100644 --- a/src/common/parsing/CodePreprocessing.cpp +++ b/src/common/parsing/CodePreprocessing.cpp @@ -1,11 +1,19 @@ #include "common/parsing/CodePreprocessing.hpp" +#include "common/parsing/AssertionParsing.hpp" #include "common/parsing/ParsingError.hpp" #include "common/parsing/Utils.hpp" #include <algorithm> +#include <cstddef> +#include <iostream> #include <map> +#include <memory> +#include <set> +#include <stdexcept> +#include <string> #include <utility> +#include <vector> Instruction::Instruction(size_t inputLineNumber, std::string inputCode, std::unique_ptr<Assertion>& inputAssertion, @@ -53,11 +61,11 @@ std::string removeComments(const std::string& code) { if (nextComment == std::string::npos) { break; } - auto commentEnd = result.find("\n", nextComment); + auto commentEnd = result.find('\n', nextComment); if (commentEnd == std::string::npos) { commentEnd = result.size(); } - std::string spaces(commentEnd - nextComment, ' '); + const std::string spaces(commentEnd - nextComment, ' '); result.replace(nextComment, commentEnd - nextComment, spaces); } return result; @@ -125,7 +133,6 @@ std::vector<std::string> sweepFunctionNames(const std::string& code) { result.push_back(f.name); } } - return result; } @@ -139,6 +146,7 @@ preprocessCode(const std::string& code, size_t startIndex, size_t initialCodeOffset, const std::vector<std::string>& allFunctionNames, std::string& processedCode) { + std::map<std::string, std::string> blocks; std::map<std::string, size_t> functionFirstLine; std::map<std::string, FunctionDefinition> functionDefinitions; @@ -214,6 +222,7 @@ preprocessCode(const std::string& code, size_t startIndex, targets, trueStart, trueEnd, i + 1, false, "", false, block); auto& functionInstruction = instructions.back(); + for (auto& instr : subInstructions) { functionInstruction.childInstructions.push_back(instr.lineNumber); instructions.push_back(std::move(instr)); @@ -263,7 +272,8 @@ preprocessCode(const std::string& code, size_t startIndex, for (const auto& var : variableUsages[idx]) { if (std::find(vars.begin(), vars.end(), var) != vars.end()) { found = true; - std::remove(vars.begin(), vars.end(), var); + const auto newEnd = std::remove(vars.begin(), vars.end(), var); + vars.erase(newEnd, vars.end()); } } if (found) { @@ -292,5 +302,6 @@ preprocessCode(const std::string& code, size_t startIndex, } } } + return instructions; } diff --git a/src/common/parsing/ParsingError.cpp b/src/common/parsing/ParsingError.cpp index 8f434cbc..35698769 100644 --- a/src/common/parsing/ParsingError.cpp +++ b/src/common/parsing/ParsingError.cpp @@ -1,3 +1,6 @@ #include "common/parsing/ParsingError.hpp" +#include <stdexcept> +#include <string> + ParsingError::ParsingError(const std::string& msg) : std::runtime_error(msg) {} diff --git a/src/common/parsing/Utils.cpp b/src/common/parsing/Utils.cpp index 970174b5..e6de9227 100644 --- a/src/common/parsing/Utils.cpp +++ b/src/common/parsing/Utils.cpp @@ -1,6 +1,10 @@ #include "common/parsing/Utils.hpp" #include <algorithm> +#include <cctype> +#include <cstddef> +#include <string> +#include <vector> std::string trim(const std::string& str) { auto start = std::find_if_not(str.begin(), str.end(), ::isspace); diff --git a/src/frontend/cli/CliFrontEnd.cpp b/src/frontend/cli/CliFrontEnd.cpp index 1eb4ab0d..7cb3e562 100644 --- a/src/frontend/cli/CliFrontEnd.cpp +++ b/src/frontend/cli/CliFrontEnd.cpp @@ -4,11 +4,16 @@ #include "frontend/cli/CliFrontEnd.hpp" -#include "common/parsing/Utils.hpp" +#include "backend/debug.h" +#include "backend/diagnostics.h" +#include "common.h" #include <array> +#include <cstddef> #include <cstdint> #include <iostream> +#include <string> +#include <vector> void clearScreen() { // Clear the screen using an ANSI escape sequence @@ -107,10 +112,12 @@ void CliFrontEnd::printState(SimulationState* state, size_t inspecting, if (inspecting != -1ULL) { std::vector<uint8_t> inspectingDependencies( state->getInstructionCount(state)); - auto deps = inspectingDependencies.data(); + auto* deps = inspectingDependencies.data(); + // NOLINTBEGIN(cppcoreguidelines-pro-type-reinterpret-cast) state->getDiagnostics(state)->getDataDependencies( state->getDiagnostics(state), inspecting, reinterpret_cast<bool*>(deps)); + // NOLINTEND(cppcoreguidelines-pro-type-reinterpret-cast) uint8_t on = 0; for (size_t i = 0; i < inspectingDependencies.size(); i++) { if (inspectingDependencies[i] != on) { @@ -134,7 +141,7 @@ void CliFrontEnd::printState(SimulationState* state, size_t inspecting, size_t currentPos = 0; bool on = false; for (const auto nextInterval : highlightIntervals) { - const auto textColor = on ? ANSI_BG_RESET : ANSI_COL_GRAY; + const auto* const textColor = on ? ANSI_BG_RESET : ANSI_COL_GRAY; if (res == OK && currentStart >= currentPos && currentStart < nextInterval) { std::cout << textColor diff --git a/src/python/InterfaceBindings.cpp b/src/python/InterfaceBindings.cpp index 3c7e9df9..4e02de46 100644 --- a/src/python/InterfaceBindings.cpp +++ b/src/python/InterfaceBindings.cpp @@ -1,11 +1,11 @@ -#include "python/InterfaceBindings.hpp" +#include "backend/debug.h" +#include "backend/diagnostics.h" +#include "common.h" +#include "pybind11/pybind11.h" +#include "pybind11/stl.h" -#include "backend/dd/DDSimDiagnostics.hpp" - -#include <backend/debug.h> -#include <common.h> -#include <pybind11/pybind11.h> -#include <pybind11/stl.h> +#include <cstddef> +#include <iostream> namespace py = pybind11; using namespace pybind11::literals; @@ -17,9 +17,9 @@ void checkOrThrow(Result result) { } struct StatevectorCPP { - size_t numQubits; - size_t numStates; - std::vector<Complex> amplitudes; + size_t numQubits = 0; + size_t numStates = 0; + std::vector<Complex> amplitudes{}; }; void bindFramework(py::module& m) { diff --git a/src/python/bindings.cpp b/src/python/bindings.cpp index 85add1f9..db017d1f 100644 --- a/src/python/bindings.cpp +++ b/src/python/bindings.cpp @@ -3,8 +3,6 @@ #include <pybind11/pybind11.h> -namespace py = pybind11; - PYBIND11_MODULE(pydebug, m) { bindDiagnostics(m); bindFramework(m); diff --git a/src/python/dd/DDSimDebugBindings.cpp b/src/python/dd/DDSimDebugBindings.cpp index 7ff14636..24a2d389 100644 --- a/src/python/dd/DDSimDebugBindings.cpp +++ b/src/python/dd/DDSimDebugBindings.cpp @@ -1,16 +1,22 @@ #include "python/dd/DDSimDebugBindings.hpp" #include "backend/dd/DDSimDebug.hpp" +#include "backend/debug.h" void bindBackend(pybind11::module& m) { - m.def("create_ddsim_simulation_state", []() { - auto* state = new DDSimulationState(); - createDDSimulationState(state); - return &state->interface; - }); + m.def( + "create_ddsim_simulation_state", + []() { + auto* state = new DDSimulationState(); + createDDSimulationState(state); + return &state->interface; + }, + pybind11::return_value_policy::take_ownership); m.def("destroy_ddsim_simulation_state", [](SimulationState* state) { + // NOLINTBEGIN(cppcoreguidelines-pro-type-reinterpret-cast) destroyDDSimulationState(reinterpret_cast<DDSimulationState*>(state)); + // NOLINTEND(cppcoreguidelines-pro-type-reinterpret-cast) }); } diff --git a/test/test_custom_code.cpp b/test/test_custom_code.cpp index 76d483f4..7021b950 100644 --- a/test/test_custom_code.cpp +++ b/test/test_custom_code.cpp @@ -1,9 +1,13 @@ #include "backend/dd/DDSimDebug.hpp" #include "backend/debug.h" +#include "common.h" #include "utils_test.hpp" +#include <array> +#include <cstddef> #include <gtest/gtest.h> #include <sstream> +#include <string> class CustomCodeTest : public testing::Test { void SetUp() override { @@ -82,3 +86,20 @@ TEST_F(CustomCodeTest, ResetGate) { ASSERT_EQ(state->getAmplitudeIndex(state, 0, &result), OK); ASSERT_TRUE(complexEquality(result, -1.0, 0.0)); } + +TEST_F(CustomCodeTest, DependenciesWithJumps) { + loadCode(3, 1, + "gate entangle q0, q1, q2 {\n" + " cx q0, q1;\n" + " cx q0, q2;\n" + " barrier q2;\n" + "}\n" + "\n" + "h q[0];\n" + "\n" + "entangle q[0], q[1], q[2];\n" + "\n" + "h q[2];\n" + "\n" + "barrier q[2];"); +} diff --git a/test/test_data_retrieval.cpp b/test/test_data_retrieval.cpp index 4ce71e06..1490cc14 100644 --- a/test/test_data_retrieval.cpp +++ b/test/test_data_retrieval.cpp @@ -1,9 +1,13 @@ #include "backend/dd/DDSimDebug.hpp" #include "backend/debug.h" +#include "common.h" #include "utils_test.hpp" +#include <array> +#include <cstddef> #include <gtest/gtest.h> -#include <memory> +#include <string> +#include <vector> class DataRetrievalTest : public testing::Test { void SetUp() override { @@ -129,7 +133,7 @@ TEST_F(DataRetrievalTest, GetClassicalVariable) { } TEST_F(DataRetrievalTest, GetStateVectorFull) { - std::array<Complex, 16> amplitudes; + std::array<Complex, 16> amplitudes{}; Statevector sv{4, 16, amplitudes.data()}; ASSERT_EQ(state->getStateVectorFull(state, &sv), OK); @@ -143,27 +147,27 @@ TEST_F(DataRetrievalTest, GetStateVectorFull) { } TEST_F(DataRetrievalTest, GetStateVectorSub) { - std::array<Complex, 4> amplitudes; + std::array<Complex, 4> amplitudes{}; Statevector sv{2, 4, amplitudes.data()}; forwardTo(6); - size_t qubits[] = {0, 1}; - ASSERT_EQ(state->getStateVectorSub(state, 2, qubits, &sv), OK); + std::array<size_t, 2> qubits = {0, 1}; + ASSERT_EQ(state->getStateVectorSub(state, 2, qubits.data(), &sv), OK); ASSERT_TRUE(complexEquality(amplitudes[3], 1, 0.0)); ASSERT_TRUE(complexEquality(amplitudes[0], 0.0, 0.0)); qubits[1] = 2; - ASSERT_EQ(state->getStateVectorSub(state, 2, qubits, &sv), OK); + ASSERT_EQ(state->getStateVectorSub(state, 2, qubits.data(), &sv), OK); ASSERT_TRUE(complexEquality(amplitudes[3], 0.0, 0.0)); ASSERT_TRUE(complexEquality(amplitudes[1], 1.0, 0.0)); forwardTo(11); - ASSERT_EQ(state->getStateVectorSub(state, 2, qubits, &sv), OK); + ASSERT_EQ(state->getStateVectorSub(state, 2, qubits.data(), &sv), OK); ASSERT_TRUE(complexEquality(amplitudes[0], 0.707, 0.0)); ASSERT_TRUE(complexEquality(amplitudes[1], -0.707, 0.0)); qubits[0] = 1; - ASSERT_EQ(state->getStateVectorSub(state, 2, qubits, &sv), OK); + ASSERT_EQ(state->getStateVectorSub(state, 2, qubits.data(), &sv), OK); ASSERT_TRUE(complexEquality(amplitudes[0], 0.0, 0.0)); ASSERT_TRUE(complexEquality(amplitudes[1], 0.0, 0.0)); // 0 because of destructive interference diff --git a/test/test_diagnostics.cpp b/test/test_diagnostics.cpp index f1a8593d..4209c5a9 100644 --- a/test/test_diagnostics.cpp +++ b/test/test_diagnostics.cpp @@ -1,9 +1,16 @@ #include "backend/dd/DDSimDebug.hpp" #include "backend/debug.h" +#include "backend/diagnostics.h" #include "utils_test.hpp" +#include <array> +#include <cstddef> +#include <cstdint> #include <gtest/gtest.h> -#include <memory> +#include <map> +#include <set> +#include <string> +#include <vector> class DiagnosticsTest : public testing::Test { void SetUp() override { @@ -21,14 +28,6 @@ class DiagnosticsTest : public testing::Test { const auto code = readFromCircuitsPath(testName); state->loadCode(state, code.c_str()); } - - void forwardTo(size_t instruction) { - size_t currentInstruction = state->getCurrentInstruction(state); - while (currentInstruction < instruction) { - state->stepForward(state); - currentInstruction = state->getCurrentInstruction(state); - } - } }; TEST_F(DiagnosticsTest, DataDependencies) { @@ -47,8 +46,10 @@ TEST_F(DiagnosticsTest, DataDependencies) { for (const auto& [instruction, expectedDependencies] : expected) { std::vector<uint8_t> dependencies(state->getInstructionCount(state), 0); + // NOLINTBEGIN(cppcoreguidelines-pro-type-reinterpret-cast) diagnostics->getDataDependencies( diagnostics, instruction, reinterpret_cast<bool*>(dependencies.data())); + // NOLINTEND(cppcoreguidelines-pro-type-reinterpret-cast) std::set<size_t> dependenciesSet; for (size_t i = 0; i < dependencies.size(); ++i) { if (dependencies[i] != 0) { diff --git a/test/test_simulation.cpp b/test/test_simulation.cpp index 9b0e424e..573039d9 100644 --- a/test/test_simulation.cpp +++ b/test/test_simulation.cpp @@ -1,9 +1,14 @@ #include "backend/dd/DDSimDebug.hpp" #include "backend/debug.h" +#include "common.h" #include "utils_test.hpp" +#include <cstddef> #include <gtest/gtest.h> -#include <memory> +#include <map> +#include <string> +#include <utility> +#include <vector> class SimulationTest : public testing::TestWithParam<std::string> { void SetUp() override { @@ -13,7 +18,7 @@ class SimulationTest : public testing::TestWithParam<std::string> { } protected: - DDSimulationState ddState; + DDSimulationState ddState{}; SimulationState* state = nullptr; void loadFromFile(const std::string& testName) { diff --git a/test/test_utility.cpp b/test/test_utility.cpp index eacddec8..6cb2bf03 100644 --- a/test/test_utility.cpp +++ b/test/test_utility.cpp @@ -2,8 +2,11 @@ #include "backend/debug.h" #include "utils_test.hpp" +#include <cstddef> #include <gtest/gtest.h> -#include <memory> +#include <map> +#include <string> +#include <utility> class UtilityTest : public testing::Test { void SetUp() override { diff --git a/test/utils_test.cpp b/test/utils_test.cpp index 266ffc16..3951a145 100644 --- a/test/utils_test.cpp +++ b/test/utils_test.cpp @@ -1,7 +1,12 @@ #include "utils/utils_test.hpp" +#include "common.h" + +#include <filesystem> #include <fstream> #include <iostream> +#include <iterator> +#include <string> bool complexEquality(const Complex& c, double real, double imaginary) { const double epsilon = 0.001; @@ -19,9 +24,12 @@ bool classicalEquals(const Variable& v, bool value) { } std::string readFromCircuitsPath(const std::string& testName) { - std::ifstream file("circuits/" + testName + ".qasm"); + const std::filesystem::path localPath = + std::filesystem::path("circuits") / (testName + std::string(".qasm")); + std::ifstream file(localPath); if (!file.is_open()) { - file = std::ifstream("../../test/circuits/" + testName + ".qasm"); + file = std::ifstream(std::filesystem::path("../../test/circuits") / + (testName + std::string(".qasm"))); if (!file.is_open()) { std::cerr << "Could not open file\n"; file.close(); From c18a881e7b4e435edb47329aa785897399ed49d2 Mon Sep 17 00:00:00 2001 From: DRovara <damian.rovara@tum.de> Date: Thu, 8 Aug 2024 14:06:35 +0200 Subject: [PATCH 26/37] fix: :bug: Resolve double-free issues in python bindings by going back to the old `back_inserter` approach and then replacing it with a supported alternative --- src/common/parsing/CodePreprocessing.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/common/parsing/CodePreprocessing.cpp b/src/common/parsing/CodePreprocessing.cpp index 165ec7db..b6e7980a 100644 --- a/src/common/parsing/CodePreprocessing.cpp +++ b/src/common/parsing/CodePreprocessing.cpp @@ -221,12 +221,12 @@ preprocessCode(const std::string& code, size_t startIndex, instructions.emplace_back(i - subInstructions.size() - 1, line, a, targets, trueStart, trueEnd, i + 1, false, "", false, block); - auto& functionInstruction = instructions.back(); - for (auto& instr : subInstructions) { - functionInstruction.childInstructions.push_back(instr.lineNumber); - instructions.push_back(std::move(instr)); + instructions.back().childInstructions.push_back(instr.lineNumber); } + instructions.insert(instructions.end(), + std::make_move_iterator(subInstructions.begin()), + std::make_move_iterator(subInstructions.end())); const auto closingBrace = code.find( '}', instructions[instructions.size() - 1].originalCodeEndPosition); From 46fce7da190ad1e17c92608d319a37bbe926f747 Mon Sep 17 00:00:00 2001 From: DRovara <damian.rovara@tum.de> Date: Thu, 8 Aug 2024 14:09:01 +0200 Subject: [PATCH 27/37] refactor: :recycle: Fix imports in CodePreprocessing.hpp --- src/common/parsing/CodePreprocessing.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/parsing/CodePreprocessing.cpp b/src/common/parsing/CodePreprocessing.cpp index b6e7980a..0d2512aa 100644 --- a/src/common/parsing/CodePreprocessing.cpp +++ b/src/common/parsing/CodePreprocessing.cpp @@ -6,7 +6,7 @@ #include <algorithm> #include <cstddef> -#include <iostream> +#include <iterator> #include <map> #include <memory> #include <set> From 3e017e3f496e66637b761dce0191eb24604c5ba7 Mon Sep 17 00:00:00 2001 From: DRovara <damian.rovara@tum.de> Date: Thu, 8 Aug 2024 14:16:33 +0200 Subject: [PATCH 28/37] chore: :see_no_evil: Add local-tools to gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 0d915af2..3320cd2e 100644 --- a/.gitignore +++ b/.gitignore @@ -174,3 +174,5 @@ out/build node_modules/ wheelhouse/ + +.local-tools From c23a220b708492fd53f6518c0eae0d6bcfcb4574 Mon Sep 17 00:00:00 2001 From: DRovara <damian.rovara@tum.de> Date: Thu, 8 Aug 2024 15:02:37 +0200 Subject: [PATCH 29/37] fix: :bug: Fix remaining linter issues and make compatible with C++17 Custom implementation of `Span` class that is C++20 only --- CMakeLists.txt | 2 +- include/common/Span.hpp | 21 +++++++++++++++++ src/backend/dd/DDSimDebug.cpp | 30 +++++++++++++------------ src/backend/dd/DDSimDiagnostics.cpp | 12 +++++----- src/common/parsing/AssertionParsing.cpp | 1 + src/python/InterfaceBindings.cpp | 15 ++++++++++--- src/python/dd/DDSimDebugBindings.cpp | 15 ++++++------- 7 files changed, 64 insertions(+), 32 deletions(-) create mode 100644 include/common/Span.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 2ca1dbb0..a76b76a0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,7 +7,7 @@ project( option(BUILD_MQT_DEBUG_BINDINGS "Build the MQT DEBUG Python bindings" OFF) option(BUILD_MQT_DEBUG_TESTS "Also build tests for the MQT QCEC project" ON) -set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD 17) if(BUILD_MQT_DEBUG_BINDINGS) # ensure that the BINDINGS option is set diff --git a/include/common/Span.hpp b/include/common/Span.hpp new file mode 100644 index 00000000..21e19f95 --- /dev/null +++ b/include/common/Span.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include <cstddef> + +template <typename T> class Span { +public: + Span(T* ptr, size_t count) : pointer(ptr), spanSize(count) {} + + T& operator[](size_t index) const { + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) + return pointer[index]; + } + + T* data() const { return pointer; } + + [[nodiscard]] size_t size() const { return spanSize; } + +private: + T* pointer; + size_t spanSize; +}; diff --git a/src/backend/dd/DDSimDebug.cpp b/src/backend/dd/DDSimDebug.cpp index 2fadc56b..27fdba67 100644 --- a/src/backend/dd/DDSimDebug.cpp +++ b/src/backend/dd/DDSimDebug.cpp @@ -6,6 +6,7 @@ #include "backend/debug.h" #include "backend/diagnostics.h" #include "common.h" +#include "common/Span.hpp" #include "common/parsing/AssertionParsing.hpp" #include "common/parsing/CodePreprocessing.hpp" #include "common/parsing/Utils.hpp" @@ -25,7 +26,6 @@ #include <memory> #include <numeric> #include <random> -#include <span> #include <sstream> #include <stdexcept> #include <string> @@ -283,7 +283,8 @@ Result ddsimStepForward(SimulationState* self) { ddsim->callReturnStack.pop_back(); } - if (ddsim->breakpoints.contains(ddsim->currentInstruction)) { + if (ddsim->breakpoints.find(ddsim->currentInstruction) != + ddsim->breakpoints.end()) { ddsim->lastMetBreakpoint = ddsim->currentInstruction; } @@ -427,7 +428,8 @@ Result ddsimStepBackward(SimulationState* self) { // When going backwards, we still run the instruction that hits the breakpoint // because we want to stop *before* it. - if (ddsim->breakpoints.contains(ddsim->currentInstruction)) { + if (ddsim->breakpoints.find(ddsim->currentInstruction) != + ddsim->breakpoints.end()) { ddsim->lastMetBreakpoint = ddsim->currentInstruction; } @@ -657,12 +659,12 @@ Result ddsimGetClassicalVariableName(SimulationState* self, } const auto name = getClassicalBitName(ddsim, variableIndex); - strcpy(output, name.c_str()); + name.copy(output, name.length()); return OK; } Result ddsimGetStateVectorFull(SimulationState* self, Statevector* output) { - const std::span<Complex> amplitudes(output->amplitudes, output->numStates); + const Span<Complex> amplitudes(output->amplitudes, output->numStates); for (size_t i = 0; i < output->numStates; i++) { self->getAmplitudeIndex(self, i, &litudes[i]); } @@ -676,15 +678,15 @@ Result ddsimGetStateVectorSub(SimulationState* self, size_t subStateSize, fullState.numQubits = ddsim->qc->getNqubits(); fullState.numStates = 1 << fullState.numQubits; std::vector<Complex> amplitudes(fullState.numStates); - const std::span<Complex> outAmplitudes(output->amplitudes, output->numStates); - const std::span<const size_t> qubitsSpan(qubits, subStateSize); + const Span<Complex> outAmplitudes(output->amplitudes, output->numStates); + const Span<const size_t> qubitsSpan(qubits, subStateSize); fullState.amplitudes = amplitudes.data(); self->getStateVectorFull(self, &fullState); - for (auto& amplitude : outAmplitudes) { - amplitude.real = 0; - amplitude.imaginary = 0; + for (size_t i = 0; i < outAmplitudes.size(); i++) { + outAmplitudes[i].real = 0; + outAmplitudes[i].imaginary = 0; } for (size_t i = 0; i < fullState.numStates; i++) { @@ -742,7 +744,7 @@ Result ddsimGetStackTrace(SimulationState* self, size_t maxDepth, } size_t depth = 0; self->getStackDepth(self, &depth); - const std::span<size_t> stackFrames(output, maxDepth); + const Span<size_t> stackFrames(output, maxDepth); stackFrames[0] = self->getCurrentInstruction(self); for (auto i = 1ULL; i < maxDepth; i++) { if (i >= depth) { @@ -822,8 +824,8 @@ double dotProduct(const Statevector& sv1, const Statevector& sv2) { double resultReal = 0; double resultImag = 0; - const std::span<Complex> amplitudes1(sv1.amplitudes, sv1.numStates); - const std::span<Complex> amplitudes2(sv2.amplitudes, sv2.numStates); + const Span<Complex> amplitudes1(sv1.amplitudes, sv1.numStates); + const Span<Complex> amplitudes2(sv2.amplitudes, sv2.numStates); for (size_t i = 0; i < sv1.numStates; i++) { resultReal += amplitudes1[i].real * amplitudes2[i].real - @@ -837,7 +839,7 @@ double dotProduct(const Statevector& sv1, const Statevector& sv2) { bool areQubitsEntangled(Statevector* sv) { const double epsilon = 0.0001; - const std::span<Complex> amplitudes(sv->amplitudes, sv->numStates); + const Span<Complex> amplitudes(sv->amplitudes, sv->numStates); const bool canBe00 = complexMagnitude(amplitudes[0]) > epsilon; const bool canBe01 = complexMagnitude(amplitudes[1]) > epsilon; const bool canBe10 = complexMagnitude(amplitudes[2]) > epsilon; diff --git a/src/backend/dd/DDSimDiagnostics.cpp b/src/backend/dd/DDSimDiagnostics.cpp index 14239f08..33c43543 100644 --- a/src/backend/dd/DDSimDiagnostics.cpp +++ b/src/backend/dd/DDSimDiagnostics.cpp @@ -3,6 +3,7 @@ #include "backend/dd/DDSimDebug.hpp" #include "backend/diagnostics.h" #include "common.h" +#include "common/Span.hpp" #include "common/parsing/AssertionParsing.hpp" #include <algorithm> @@ -11,7 +12,6 @@ #include <map> #include <memory> #include <set> -#include <span> #include <string> #include <vector> @@ -60,7 +60,7 @@ Result dddiagnosticsGetDataDependencies(Diagnostics* self, size_t instruction, bool* instructions) { auto* ddd = toDDDiagnostics(self); auto* ddsim = ddd->simulationState; - const std::span<bool> isDependency( + const Span<bool> isDependency( instructions, ddsim->interface.getInstructionCount(&ddsim->interface)); std::set<size_t> toVisit{instruction}; std::set<size_t> visited; @@ -113,7 +113,7 @@ Result dddiagnosticsGetInteractions(Diagnostics* self, size_t beforeInstruction, } } - const auto qubits = std::span<bool>( + const auto qubits = Span<bool>( qubitsAreInteracting, ddsim->interface.getNumQubits(&ddsim->interface)); for (auto interaction : interactions) { qubits[interaction] = true; @@ -126,7 +126,7 @@ size_t dddiagnosticsPotentialErrorCauses(Diagnostics* self, ErrorCause* output, size_t count) { auto* ddd = toDDDiagnostics(self); auto* ddsim = ddd->simulationState; - auto outputs = std::span(output, count); + auto outputs = Span(output, count); const size_t assertion = ddsim->lastFailedAssertion; if (assertion == -1ULL) { @@ -150,7 +150,7 @@ size_t tryFindMissingInteraction(DDDiagnostics* diagnostics, const std::unique_ptr<Assertion>& assertion, ErrorCause* output, size_t count) { auto targets = assertion->getTargetQubits(); - auto outputs = std::span(output, count); + auto outputs = Span(output, count); std::vector<size_t> targetQubits(targets.size()); size_t index = 0; @@ -192,7 +192,7 @@ size_t tryFindZeroControls(DDDiagnostics* diagnostics, size_t instruction, diagnostics->interface.getInstructionCount(&diagnostics->interface)); diagnostics->interface.getDataDependencies( &diagnostics->interface, instruction, toBoolArray(dependencies.data())); - auto outputs = std::span(output, count); + auto outputs = Span(output, count); size_t index = 0; for (size_t i = 0; i < dependencies.size(); i++) { diff --git a/src/common/parsing/AssertionParsing.cpp b/src/common/parsing/AssertionParsing.cpp index e2f0fd17..e5a6b7c8 100644 --- a/src/common/parsing/AssertionParsing.cpp +++ b/src/common/parsing/AssertionParsing.cpp @@ -46,6 +46,7 @@ const std::vector<Statevector>& SpanAssertion::getSpanVectors() const { } SpanAssertion::~SpanAssertion() { for (auto& statevector : spanVectors) { + // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) delete[] statevector.amplitudes; } } diff --git a/src/python/InterfaceBindings.cpp b/src/python/InterfaceBindings.cpp index 4e02de46..05b56767 100644 --- a/src/python/InterfaceBindings.cpp +++ b/src/python/InterfaceBindings.cpp @@ -4,8 +4,13 @@ #include "pybind11/pybind11.h" #include "pybind11/stl.h" +#include <algorithm> #include <cstddef> -#include <iostream> +#include <cstdint> +#include <stdexcept> +#include <string> +#include <utility> +#include <vector> namespace py = pybind11; using namespace pybind11::literals; @@ -19,7 +24,7 @@ void checkOrThrow(Result result) { struct StatevectorCPP { size_t numQubits = 0; size_t numStates = 0; - std::vector<Complex> amplitudes{}; + std::vector<Complex> amplitudes; }; void bindFramework(py::module& m) { @@ -202,7 +207,7 @@ void bindFramework(py::module& m) { }) .def("set_breakpoint", [](SimulationState* self, size_t desiredPosition) { - size_t actualPosition; + size_t actualPosition = 0; checkOrThrow( self->setBreakpoint(self, desiredPosition, &actualPosition)); return actualPosition; @@ -257,9 +262,11 @@ void bindDiagnostics(py::module& m) { .def("get_data_dependencies", [](Diagnostics* self, size_t instruction) { std::vector<uint8_t> instructions(self->getInstructionCount(self)); + // NOLINTBEGIN(cppcoreguidelines-pro-type-reinterpret-cast) checkOrThrow(self->getDataDependencies( self, instruction, reinterpret_cast<bool*>(instructions.data()))); + // NOLINTEND(cppcoreguidelines-pro-type-reinterpret-cast) std::vector<size_t> result; for (size_t i = 0; i < instructions.size(); i++) { if (instructions[i] != 0) { @@ -271,9 +278,11 @@ void bindDiagnostics(py::module& m) { .def("get_interactions", [](Diagnostics* self, size_t beforeInstruction, size_t qubit) { std::vector<uint8_t> qubits(self->getNumQubits(self)); + // NOLINTBEGIN(cppcoreguidelines-pro-type-reinterpret-cast) checkOrThrow( self->getInteractions(self, beforeInstruction, qubit, reinterpret_cast<bool*>(qubits.data()))); + // NOLINTEND(cppcoreguidelines-pro-type-reinterpret-cast) std::vector<size_t> result; for (size_t i = 0; i < qubits.size(); i++) { if (qubits[i] != 0) { diff --git a/src/python/dd/DDSimDebugBindings.cpp b/src/python/dd/DDSimDebugBindings.cpp index 24a2d389..c3d363fc 100644 --- a/src/python/dd/DDSimDebugBindings.cpp +++ b/src/python/dd/DDSimDebugBindings.cpp @@ -2,17 +2,16 @@ #include "backend/dd/DDSimDebug.hpp" #include "backend/debug.h" +#include "pybind11/pybind11.h" void bindBackend(pybind11::module& m) { - m.def( - "create_ddsim_simulation_state", - []() { - auto* state = new DDSimulationState(); - createDDSimulationState(state); - return &state->interface; - }, - pybind11::return_value_policy::take_ownership); + m.def("create_ddsim_simulation_state", []() { + // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) + auto* state = new DDSimulationState(); + createDDSimulationState(state); + return &state->interface; + }); m.def("destroy_ddsim_simulation_state", [](SimulationState* state) { // NOLINTBEGIN(cppcoreguidelines-pro-type-reinterpret-cast) From e2134af8148f766fe2971db5de611ab9cd149efe Mon Sep 17 00:00:00 2001 From: DRovara <damian.rovara@tum.de> Date: Thu, 8 Aug 2024 15:31:16 +0200 Subject: [PATCH 30/37] refactor: :recycle: Fix linter errors in `app` directory and disable C++-specific rules for `.h` files --- app/testDDSimDebugger.cpp | 6 ++---- include/backend/debug.h | 4 ++++ include/backend/diagnostics.h | 5 +++++ include/common.h | 3 +++ 4 files changed, 14 insertions(+), 4 deletions(-) diff --git a/app/testDDSimDebugger.cpp b/app/testDDSimDebugger.cpp index 63fd8024..3c8e12c3 100644 --- a/app/testDDSimDebugger.cpp +++ b/app/testDDSimDebugger.cpp @@ -3,6 +3,8 @@ #include <fstream> #include <iostream> +#include <iterator> +#include <string> int main() { std::ifstream file("../../app/code/test" @@ -20,10 +22,6 @@ int main() { file.close(); - // size_t errors = 0; - // Result result = ddsimLoadCode(&state.interface, code.c_str()); - // result = state.interface.runAll(&state.interface, &errors); - // std::cout << errors << "\n"; CliFrontEnd cli; cli.run(code.c_str(), &state.interface); diff --git a/include/backend/debug.h b/include/backend/debug.h index b55ed757..976d0cb8 100644 --- a/include/backend/debug.h +++ b/include/backend/debug.h @@ -1,9 +1,11 @@ #pragma once +// NOLINTBEGIN(modernize-use-using) #include "common.h" #include "diagnostics.h" #ifdef __cplusplus +#include <cstddef> extern "C" { #endif @@ -65,3 +67,5 @@ struct SimulationState { #ifdef __cplusplus } #endif + +// NOLINTEND(modernize-use-using) diff --git a/include/backend/diagnostics.h b/include/backend/diagnostics.h index c8d092c3..3f0ac9c4 100644 --- a/include/backend/diagnostics.h +++ b/include/backend/diagnostics.h @@ -1,8 +1,11 @@ #pragma once +// NOLINTBEGIN(modernize-use-using, performance-enum-size) + #include "common.h" #ifdef __cplusplus +#include <cstddef> extern "C" { #endif @@ -31,3 +34,5 @@ struct Diagnostics { #ifdef __cplusplus } #endif + +// NOLINTEND(modernize-use-using, performance-enum-size) diff --git a/include/common.h b/include/common.h index b96eb712..16b47e28 100644 --- a/include/common.h +++ b/include/common.h @@ -1,4 +1,5 @@ #pragma once +// NOLINTBEGIN(modernize-use-using, performance-enum-size) #ifndef __cplusplus typedef char bool; @@ -40,3 +41,5 @@ typedef struct { #ifdef __cplusplus } #endif + +// NOLINTEND(modernize-use-using, performance-enum-size) From 339efada8a6c7ab8127db13302f4137646ab603f Mon Sep 17 00:00:00 2001 From: DRovara <damian.rovara@tum.de> Date: Thu, 8 Aug 2024 15:59:01 +0200 Subject: [PATCH 31/37] build: :building_construction: Require specific version of pybind11 in ExternalDependencies --- cmake/ExternalDependencies.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/ExternalDependencies.cmake b/cmake/ExternalDependencies.cmake index e278fb04..a3efd122 100644 --- a/cmake/ExternalDependencies.cmake +++ b/cmake/ExternalDependencies.cmake @@ -14,7 +14,7 @@ if(BUILD_MQT_DEBUG_BINDINGS) endif() # add pybind11 library - find_package(pybind11 CONFIG REQUIRED) + find_package(pybind11 2.13 CONFIG REQUIRED) endif() # cmake-format: off From 61c59edc013cb434b1b11594bd4fe561dd20af72 Mon Sep 17 00:00:00 2001 From: DRovara <damian.rovara@tum.de> Date: Thu, 8 Aug 2024 16:17:08 +0200 Subject: [PATCH 32/37] fix: :bug: Add missing pybind11 dependencies to pyproject.toml and noxfile.py --- noxfile.py | 1 + pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/noxfile.py b/noxfile.py index f59d35c9..189098a6 100644 --- a/noxfile.py +++ b/noxfile.py @@ -25,6 +25,7 @@ "setuptools_scm>=8.1", "setuptools>=66.1", "wheel>=0.40.0", + "pybind11>=2.13", ] if os.environ.get("CI", None): diff --git a/pyproject.toml b/pyproject.toml index a2ac6e9d..ef518577 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [build-system] -requires = ["scikit-build-core>=0.8.1", "setuptools-scm>=7", "pybind11>=2.12"] +requires = ["scikit-build-core>=0.8.1", "setuptools-scm>=7", "pybind11>=2.13"] build-backend = "scikit_build_core.build" [project] From 52128a2bc270f9b916b1a050fe6a79afe07f776d Mon Sep 17 00:00:00 2001 From: DRovara <damian.rovara@tum.de> Date: Fri, 9 Aug 2024 10:18:53 +0200 Subject: [PATCH 33/37] refactor: :art: Resolve CodeQL warnings --- src/backend/dd/DDSimDebug.cpp | 26 +++++++++++++++++++++++- src/backend/dd/DDSimDiagnostics.cpp | 4 +++- src/common/parsing/CodePreprocessing.cpp | 4 +++- 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/src/backend/dd/DDSimDebug.cpp b/src/backend/dd/DDSimDebug.cpp index 27fdba67..10a9918a 100644 --- a/src/backend/dd/DDSimDebug.cpp +++ b/src/backend/dd/DDSimDebug.cpp @@ -293,6 +293,10 @@ Result ddsimStepForward(SimulationState* self) { } ddsim->previousInstructionStack.emplace_back(currentInstruction); + // The exact action we take depends on the type of the next instruction: + // - ASSERTION: check the assertion and step back if it fails. + // - Non-SIMULATE: just step to the next instruction. + // - SIMULATE: run the corresponding operation on the DD backend. if (ddsim->instructionTypes[currentInstruction] == ASSERTION) { auto& assertion = ddsim->assertionInstructions[currentInstruction]; const auto failed = !checkAssertion(ddsim, assertion); @@ -310,6 +314,8 @@ Result ddsimStepForward(SimulationState* self) { qc::MatrixDD currDD; if ((*ddsim->iterator)->getType() == qc::Measure) { + // Perform a measurement of the desired qubits, based on the amplitudes of + // the current state. auto qubitsToMeasure = (*ddsim->iterator)->getTargets(); auto classicalBits = dynamic_cast<qc::NonUnitaryOperation*>(ddsim->iterator->get()) @@ -340,11 +346,13 @@ Result ddsimStepForward(SimulationState* self) { } ddsim->iterator++; - ddsim->previousInstructionStack.clear(); + ddsim->previousInstructionStack + .clear(); // after measurements, we can no longer step back. ddsim->restoreCallReturnStack.clear(); return OK; } if ((*ddsim->iterator)->getType() == qc::Reset) { + // Perform the desired qubits. This will first perform a measurement. auto qubitsToMeasure = (*ddsim->iterator)->getTargets(); ddsim->iterator++; ddsim->previousInstructionStack.clear(); @@ -370,10 +378,13 @@ Result ddsimStepForward(SimulationState* self) { return OK; } if ((*ddsim->iterator)->getType() == qc::Barrier) { + // Do not do anything. ddsim->iterator++; return OK; } if ((*ddsim->iterator)->isClassicControlledOperation()) { + // For classic-controlled operations, we need to read the values of the + // classical register first. const auto* op = dynamic_cast<qc::ClassicControlledOperation*>((*ddsim->iterator).get()); const auto& controls = op->getControlRegister(); @@ -390,6 +401,7 @@ Result ddsimStepForward(SimulationState* self) { currDD = ddsim->dd->makeIdent(); } } else { + // For all other operations, we just take the next gate to apply. currDD = dd::getDD(ddsim->iterator->get(), *ddsim->dd); // retrieve the "new" current operation } @@ -1076,6 +1088,17 @@ std::string preprocessAssertionCode(const char* code, for (const auto& dependency : instruction.dataDependencies) { ddsim->dataDependencies[instruction.lineNumber].push_back(dependency); } + + // what exactly we do with each instruction depends on its type: + // - RETURN instructions are not simulated. + // - RETURN and ASSERTION instructions are not added to the final code. + // - Custom gate definitions are also not simulated. + // - Function calls are simulated but will be replaced by their flattened + // instructions later. + // - For register declarations, we store the register name. + // - Instructions inside function definitions are not added to the final + // code because they were already added when the function definition was + // first encountered. if (instruction.code == "RETURN") { ddsim->instructionTypes.push_back(RETURN); } else if (instruction.assertion != nullptr) { @@ -1156,6 +1179,7 @@ std::string preprocessAssertionCode(const char* code, } } + // Transform all the correct lines into a single code string. const auto result = std::accumulate( correctLines.begin(), correctLines.end(), std::string(), [](const std::string& a, const std::string& b) { return a + b; }); diff --git a/src/backend/dd/DDSimDiagnostics.cpp b/src/backend/dd/DDSimDiagnostics.cpp index 33c43543..352011a5 100644 --- a/src/backend/dd/DDSimDiagnostics.cpp +++ b/src/backend/dd/DDSimDiagnostics.cpp @@ -40,7 +40,9 @@ Result createDDDiagnostics(DDDiagnostics* self, DDSimulationState* state) { return self->interface.init(&self->interface); } -Result destroyDDDiagnostics([[maybe_unused]] DDDiagnostics* self) { return OK; } +Result destroyDDDiagnostics(DDDiagnostics* self) { + self->simulationState = nullptr; +} size_t dddiagnosticsGetNumQubits(Diagnostics* self) { const auto* ddd = toDDDiagnostics(self); diff --git a/src/common/parsing/CodePreprocessing.cpp b/src/common/parsing/CodePreprocessing.cpp index 0d2512aa..bcc951c7 100644 --- a/src/common/parsing/CodePreprocessing.cpp +++ b/src/common/parsing/CodePreprocessing.cpp @@ -32,7 +32,8 @@ std::string sweepBlocks(const std::string& code, std::string result = code; size_t start = 0; int level = 0; - for (size_t pos = 0; pos < result.size(); pos++) { + size_t pos = 0; + while (pos < result.size()) { auto c = result[pos]; if (c == '{') { if (level == 0) { @@ -50,6 +51,7 @@ std::string sweepBlocks(const std::string& code, pos = start; } } + pos++; } return result; } From 44cd24957a54fb8c2e09a0ec34de6b12250e9b46 Mon Sep 17 00:00:00 2001 From: DRovara <damian.rovara@tum.de> Date: Fri, 9 Aug 2024 10:23:55 +0200 Subject: [PATCH 34/37] fix: :bug: Fix missing return statement --- src/backend/dd/DDSimDiagnostics.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/backend/dd/DDSimDiagnostics.cpp b/src/backend/dd/DDSimDiagnostics.cpp index 352011a5..2daa119a 100644 --- a/src/backend/dd/DDSimDiagnostics.cpp +++ b/src/backend/dd/DDSimDiagnostics.cpp @@ -42,6 +42,7 @@ Result createDDDiagnostics(DDDiagnostics* self, DDSimulationState* state) { Result destroyDDDiagnostics(DDDiagnostics* self) { self->simulationState = nullptr; + return OK; } size_t dddiagnosticsGetNumQubits(Diagnostics* self) { From 6c8edd334346a90d0105540fa81e948a7b2b2e2d Mon Sep 17 00:00:00 2001 From: Damian Rovara <93778306+DRovara@users.noreply.github.com> Date: Fri, 9 Aug 2024 10:39:55 +0200 Subject: [PATCH 35/37] Update .github/workflows/update-mqt-core.yml Co-authored-by: Lukas Burgholzer <burgholzer@me.com> --- .github/workflows/update-mqt-core.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/update-mqt-core.yml b/.github/workflows/update-mqt-core.yml index f1314064..806db7fc 100644 --- a/.github/workflows/update-mqt-core.yml +++ b/.github/workflows/update-mqt-core.yml @@ -23,4 +23,4 @@ jobs: name: ⬆️ Update MQT Core uses: cda-tum/mqt-workflows/.github/workflows/reusable-mqt-core-update.yml@v1.1.5 with: - update-to-head: ${{ github.event.inputs.update-to-head != '' && fromJSON(github.event.inputs.update-to-head) || false }} + update-to-head: ${{ github.event.inputs.update-to-head || false }} From 97a51dc0a7d8901d9abaa381874bd27f5b3871e8 Mon Sep 17 00:00:00 2001 From: DRovara <damian.rovara@tum.de> Date: Fri, 9 Aug 2024 10:50:36 +0200 Subject: [PATCH 36/37] refactor: :recycle: Move DAP-related code to `dap` submodule --- src/mqt/debug/__init__.py | 6 +- src/mqt/debug/dap/__init__.py | 8 ++ src/mqt/debug/{ => dap}/adapter.py | 0 src/mqt/debug/{ => dap}/dap_server.py | 77 +++++++++---------- src/mqt/debug/{ => dap}/messages/__init__.py | 0 .../messages/capabilities_dap_event.py | 0 .../configuration_done_dap_message.py | 0 .../messages/continue_dap_message.py | 0 src/mqt/debug/{ => dap}/messages/dap_event.py | 0 .../debug/{ => dap}/messages/dap_message.py | 0 .../messages/disconnect_dap_message.py | 9 ++- .../messages/exception_info_message.py | 0 .../{ => dap}/messages/exited_dap_event.py | 0 .../{ => dap}/messages/gray_out_event.py | 0 .../messages/initialize_dap_message.py | 9 ++- .../messages/initialized_dap_event.py | 0 .../{ => dap}/messages/launch_dap_message.py | 0 .../{ => dap}/messages/next_dap_message.py | 0 .../{ => dap}/messages/output_dap_event.py | 0 .../{ => dap}/messages/pause_dap_message.py | 0 .../{ => dap}/messages/restart_dap_message.py | 0 .../messages/restart_frame_dap_message.py | 0 .../messages/reverse_continue_dap_message.py | 0 .../{ => dap}/messages/scopes_dap_message.py | 0 .../messages/set_breakpoints_dap_message.py | 0 .../set_exception_breakpoints_dap_message.py | 0 .../messages/stack_trace_dap_message.py | 0 .../messages/step_back_dap_message.py | 0 .../{ => dap}/messages/step_in_dap_message.py | 0 .../messages/step_out_dap_message.py | 0 .../{ => dap}/messages/stopped_dap_event.py | 0 .../messages/terminate_dap_message.py | 9 ++- .../messages/terminated_dap_event.py | 0 .../{ => dap}/messages/threads_dap_message.py | 0 src/mqt/debug/{ => dap}/messages/utils.py | 0 .../messages/variables_dap_message.py | 0 36 files changed, 66 insertions(+), 52 deletions(-) create mode 100644 src/mqt/debug/dap/__init__.py rename src/mqt/debug/{ => dap}/adapter.py (100%) rename src/mqt/debug/{ => dap}/dap_server.py (83%) rename src/mqt/debug/{ => dap}/messages/__init__.py (100%) rename src/mqt/debug/{ => dap}/messages/capabilities_dap_event.py (100%) rename src/mqt/debug/{ => dap}/messages/configuration_done_dap_message.py (100%) rename src/mqt/debug/{ => dap}/messages/continue_dap_message.py (100%) rename src/mqt/debug/{ => dap}/messages/dap_event.py (100%) rename src/mqt/debug/{ => dap}/messages/dap_message.py (100%) rename src/mqt/debug/{ => dap}/messages/disconnect_dap_message.py (80%) rename src/mqt/debug/{ => dap}/messages/exception_info_message.py (100%) rename src/mqt/debug/{ => dap}/messages/exited_dap_event.py (100%) rename src/mqt/debug/{ => dap}/messages/gray_out_event.py (100%) rename src/mqt/debug/{ => dap}/messages/initialize_dap_message.py (90%) rename src/mqt/debug/{ => dap}/messages/initialized_dap_event.py (100%) rename src/mqt/debug/{ => dap}/messages/launch_dap_message.py (100%) rename src/mqt/debug/{ => dap}/messages/next_dap_message.py (100%) rename src/mqt/debug/{ => dap}/messages/output_dap_event.py (100%) rename src/mqt/debug/{ => dap}/messages/pause_dap_message.py (100%) rename src/mqt/debug/{ => dap}/messages/restart_dap_message.py (100%) rename src/mqt/debug/{ => dap}/messages/restart_frame_dap_message.py (100%) rename src/mqt/debug/{ => dap}/messages/reverse_continue_dap_message.py (100%) rename src/mqt/debug/{ => dap}/messages/scopes_dap_message.py (100%) rename src/mqt/debug/{ => dap}/messages/set_breakpoints_dap_message.py (100%) rename src/mqt/debug/{ => dap}/messages/set_exception_breakpoints_dap_message.py (100%) rename src/mqt/debug/{ => dap}/messages/stack_trace_dap_message.py (100%) rename src/mqt/debug/{ => dap}/messages/step_back_dap_message.py (100%) rename src/mqt/debug/{ => dap}/messages/step_in_dap_message.py (100%) rename src/mqt/debug/{ => dap}/messages/step_out_dap_message.py (100%) rename src/mqt/debug/{ => dap}/messages/stopped_dap_event.py (100%) rename src/mqt/debug/{ => dap}/messages/terminate_dap_message.py (80%) rename src/mqt/debug/{ => dap}/messages/terminated_dap_event.py (100%) rename src/mqt/debug/{ => dap}/messages/threads_dap_message.py (100%) rename src/mqt/debug/{ => dap}/messages/utils.py (100%) rename src/mqt/debug/{ => dap}/messages/variables_dap_message.py (100%) diff --git a/src/mqt/debug/__init__.py b/src/mqt/debug/__init__.py index 3bb36654..db991093 100644 --- a/src/mqt/debug/__init__.py +++ b/src/mqt/debug/__init__.py @@ -2,8 +2,7 @@ from __future__ import annotations -from . import messages -from .dap_server import DAPServer +from . import dap from .pydebug import ( Complex, ErrorCause, @@ -19,7 +18,6 @@ __all__ = [ "Complex", - "DAPServer", "ErrorCause", "ErrorCauseType", "SimulationState", @@ -28,6 +26,6 @@ "VariableType", "VariableValue", "create_ddsim_simulation_state", + "dap", "destroy_ddsim_simulation_state", - "messages", ] diff --git a/src/mqt/debug/dap/__init__.py b/src/mqt/debug/dap/__init__.py new file mode 100644 index 00000000..9084e96a --- /dev/null +++ b/src/mqt/debug/dap/__init__.py @@ -0,0 +1,8 @@ +"""This module handles DAP capabilities of the debugger.""" + +from __future__ import annotations + +from . import messages +from .dap_server import DAPServer + +__all__ = ["DAPServer", "messages"] diff --git a/src/mqt/debug/adapter.py b/src/mqt/debug/dap/adapter.py similarity index 100% rename from src/mqt/debug/adapter.py rename to src/mqt/debug/dap/adapter.py diff --git a/src/mqt/debug/dap_server.py b/src/mqt/debug/dap/dap_server.py similarity index 83% rename from src/mqt/debug/dap_server.py rename to src/mqt/debug/dap/dap_server.py index 86e1f85f..eb6990c1 100644 --- a/src/mqt/debug/dap_server.py +++ b/src/mqt/debug/dap/dap_server.py @@ -9,6 +9,7 @@ import mqt.debug +from ..pydebug import ErrorCauseType, SimulationState from .messages import ( ConfigurationDoneDAPMessage, ContinueDAPMessage, @@ -33,7 +34,6 @@ ThreadsDAPMessage, VariablesDAPMessage, ) -from .pydebug import ErrorCauseType, SimulationState supported_messages: list[type[Request]] = [ InitializeDAPMessage, @@ -153,45 +153,45 @@ def handle_client(self, connection: socket.socket) -> None: result_payload = json.dumps(result) send_message(result_payload, connection) - e: mqt.debug.messages.DAPEvent | None = None - if isinstance(cmd, mqt.debug.messages.LaunchDAPMessage): - e = mqt.debug.messages.InitializedDAPEvent() + e: mqt.debug.dap.messages.DAPEvent | None = None + if isinstance(cmd, mqt.debug.dap.messages.LaunchDAPMessage): + e = mqt.debug.dap.messages.InitializedDAPEvent() event_payload = json.dumps(e.encode()) send_message(event_payload, connection) if ( - isinstance(cmd, (mqt.debug.messages.LaunchDAPMessage, mqt.debug.messages.RestartDAPMessage)) + isinstance(cmd, (mqt.debug.dap.messages.LaunchDAPMessage, mqt.debug.dap.messages.RestartDAPMessage)) and cmd.stop_on_entry ): - e = mqt.debug.messages.StoppedDAPEvent(mqt.debug.messages.StopReason.ENTRY, "Stopped on entry") + e = mqt.debug.dap.messages.StoppedDAPEvent(mqt.debug.dap.messages.StopReason.ENTRY, "Stopped on entry") event_payload = json.dumps(e.encode()) send_message(event_payload, connection) if isinstance( cmd, ( - mqt.debug.messages.NextDAPMessage, - mqt.debug.messages.StepBackDAPMessage, - mqt.debug.messages.StepInDAPMessage, - mqt.debug.messages.StepOutDAPMessage, - mqt.debug.messages.ContinueDAPMessage, - mqt.debug.messages.ReverseContinueDAPMessage, - mqt.debug.messages.RestartFrameDAPMessage, + mqt.debug.dap.messages.NextDAPMessage, + mqt.debug.dap.messages.StepBackDAPMessage, + mqt.debug.dap.messages.StepInDAPMessage, + mqt.debug.dap.messages.StepOutDAPMessage, + mqt.debug.dap.messages.ContinueDAPMessage, + mqt.debug.dap.messages.ReverseContinueDAPMessage, + mqt.debug.dap.messages.RestartFrameDAPMessage, ), ) or ( isinstance( cmd, ( - mqt.debug.messages.LaunchDAPMessage, - mqt.debug.messages.RestartDAPMessage, + mqt.debug.dap.messages.LaunchDAPMessage, + mqt.debug.dap.messages.RestartDAPMessage, ), ) and not cmd.stop_on_entry ): event = ( - mqt.debug.messages.StopReason.EXCEPTION + mqt.debug.dap.messages.StopReason.EXCEPTION if self.simulation_state.did_assertion_fail() - else mqt.debug.messages.StopReason.BREAKPOINT_INSTRUCTION + else mqt.debug.dap.messages.StopReason.BREAKPOINT_INSTRUCTION if self.simulation_state.was_breakpoint_hit() - else mqt.debug.messages.StopReason.STEP + else mqt.debug.dap.messages.StopReason.STEP ) message = ( "An assertion failed" @@ -200,20 +200,22 @@ def handle_client(self, connection: socket.socket) -> None: if self.simulation_state.was_breakpoint_hit() else "Stopped after step" ) - e = mqt.debug.messages.StoppedDAPEvent(event, message) + e = mqt.debug.dap.messages.StoppedDAPEvent(event, message) event_payload = json.dumps(e.encode()) send_message(event_payload, connection) if self.simulation_state.did_assertion_fail(): self.handle_assertion_fail(connection) - if isinstance(cmd, mqt.debug.messages.TerminateDAPMessage): - e = mqt.debug.messages.TerminatedDAPEvent() + if isinstance(cmd, mqt.debug.dap.messages.TerminateDAPMessage): + e = mqt.debug.dap.messages.TerminatedDAPEvent() event_payload = json.dumps(e.encode()) send_message(event_payload, connection) - e = mqt.debug.messages.ExitedDAPEvent(143) + e = mqt.debug.dap.messages.ExitedDAPEvent(143) event_payload = json.dumps(e.encode()) send_message(event_payload, connection) - if isinstance(cmd, mqt.debug.messages.PauseDAPMessage): - e = mqt.debug.messages.StoppedDAPEvent(mqt.debug.messages.StopReason.PAUSE, "Stopped after pause") + if isinstance(cmd, mqt.debug.dap.messages.PauseDAPMessage): + e = mqt.debug.dap.messages.StoppedDAPEvent( + mqt.debug.dap.messages.StopReason.PAUSE, "Stopped after pause" + ) event_payload = json.dumps(e.encode()) send_message(event_payload, connection) self.regular_checks(connection) @@ -224,17 +226,17 @@ def regular_checks(self, connection: socket.socket) -> None: Args: connection (socket.socket): The client socket. """ - e: mqt.debug.messages.DAPEvent | None = None + e: mqt.debug.dap.messages.DAPEvent | None = None if self.simulation_state.is_finished() and self.simulation_state.get_instruction_count() != 0: - e = mqt.debug.messages.ExitedDAPEvent(0) + e = mqt.debug.dap.messages.ExitedDAPEvent(0) event_payload = json.dumps(e.encode()) send_message(event_payload, connection) if self.can_step_back != self.simulation_state.can_step_backward(): self.can_step_back = self.simulation_state.can_step_backward() - e = mqt.debug.messages.CapabilitiesDAPEvent({"supportsStepBack": self.can_step_back}) + e = mqt.debug.dap.messages.CapabilitiesDAPEvent({"supportsStepBack": self.can_step_back}) event_payload = json.dumps(e.encode()) - def handle_command(self, command: dict[str, Any]) -> tuple[dict[str, Any], mqt.debug.messages.DAPMessage]: + def handle_command(self, command: dict[str, Any]) -> tuple[dict[str, Any], mqt.debug.dap.messages.DAPMessage]: """Handle an incoming command from the client and return the corresponding response. Args: @@ -244,7 +246,7 @@ def handle_command(self, command: dict[str, Any]) -> tuple[dict[str, Any], mqt.d RuntimeError: If the command is not supported. Returns: - tuple[dict[str, Any], mqt.debug.messages.DAPMessage]: The response to the message as a dictionary and the message object. + tuple[dict[str, Any], mqt.debug.dap.messages.DAPMessage]: The response to the message as a dictionary and the message object. """ for message_type in supported_messages: if message_type.message_type_name == command["command"]: @@ -268,7 +270,7 @@ def handle_assertion_fail(self, connection: socket.socket) -> None: start, end = self.simulation_state.get_instruction_position(i) gray_out_areas.append((start, end)) - e = mqt.debug.messages.GrayOutDAPEvent(gray_out_areas, self.source_file) + e = mqt.debug.dap.messages.GrayOutDAPEvent(gray_out_areas, self.source_file) event_payload = json.dumps(e.encode()) send_message(event_payload, connection) @@ -374,7 +376,7 @@ def send_message_hierarchy( connection (socket.socket): The client socket. """ if "title" in message: - title_event = mqt.debug.messages.OutputDAPEvent( + title_event = mqt.debug.dap.messages.OutputDAPEvent( "console", cast(str, message["title"]), "start", line, column, self.source_file ) send_message(json.dumps(title_event.encode()), connection) @@ -386,23 +388,20 @@ def send_message_hierarchy( if isinstance(msg, dict): self.send_message_hierarchy(msg, line, column, connection) else: - output_event = mqt.debug.messages.OutputDAPEvent( + output_event = mqt.debug.dap.messages.OutputDAPEvent( "console", msg, None, line, column, self.source_file ) send_message(json.dumps(output_event.encode()), connection) elif isinstance(body, dict): self.send_message_hierarchy(body, line, column, connection) elif isinstance(body, str): - output_event = mqt.debug.messages.OutputDAPEvent("console", body, None, line, column, self.source_file) + output_event = mqt.debug.dap.messages.OutputDAPEvent( + "console", body, None, line, column, self.source_file + ) send_message(json.dumps(output_event.encode()), connection) if "end" in message or "title" in message: - end_event = mqt.debug.messages.OutputDAPEvent( + end_event = mqt.debug.dap.messages.OutputDAPEvent( "console", cast(str, message.get("end")), "end", line, column, self.source_file ) send_message(json.dumps(end_event.encode()), connection) - - -if __name__ == "__main__": - server = DAPServer() - server.start() diff --git a/src/mqt/debug/messages/__init__.py b/src/mqt/debug/dap/messages/__init__.py similarity index 100% rename from src/mqt/debug/messages/__init__.py rename to src/mqt/debug/dap/messages/__init__.py diff --git a/src/mqt/debug/messages/capabilities_dap_event.py b/src/mqt/debug/dap/messages/capabilities_dap_event.py similarity index 100% rename from src/mqt/debug/messages/capabilities_dap_event.py rename to src/mqt/debug/dap/messages/capabilities_dap_event.py diff --git a/src/mqt/debug/messages/configuration_done_dap_message.py b/src/mqt/debug/dap/messages/configuration_done_dap_message.py similarity index 100% rename from src/mqt/debug/messages/configuration_done_dap_message.py rename to src/mqt/debug/dap/messages/configuration_done_dap_message.py diff --git a/src/mqt/debug/messages/continue_dap_message.py b/src/mqt/debug/dap/messages/continue_dap_message.py similarity index 100% rename from src/mqt/debug/messages/continue_dap_message.py rename to src/mqt/debug/dap/messages/continue_dap_message.py diff --git a/src/mqt/debug/messages/dap_event.py b/src/mqt/debug/dap/messages/dap_event.py similarity index 100% rename from src/mqt/debug/messages/dap_event.py rename to src/mqt/debug/dap/messages/dap_event.py diff --git a/src/mqt/debug/messages/dap_message.py b/src/mqt/debug/dap/messages/dap_message.py similarity index 100% rename from src/mqt/debug/messages/dap_message.py rename to src/mqt/debug/dap/messages/dap_message.py diff --git a/src/mqt/debug/messages/disconnect_dap_message.py b/src/mqt/debug/dap/messages/disconnect_dap_message.py similarity index 80% rename from src/mqt/debug/messages/disconnect_dap_message.py rename to src/mqt/debug/dap/messages/disconnect_dap_message.py index cfd9c45e..f8e89fb3 100644 --- a/src/mqt/debug/messages/disconnect_dap_message.py +++ b/src/mqt/debug/dap/messages/disconnect_dap_message.py @@ -2,12 +2,15 @@ from __future__ import annotations -from typing import Any +from typing import TYPE_CHECKING, Any import mqt.debug from .dap_message import DAPMessage +if TYPE_CHECKING: + from .. import DAPServer + class DisconnectDAPMessage(DAPMessage): """Represents the 'disconnect' DAP request.""" @@ -25,11 +28,11 @@ def __init__(self, message: dict[str, Any]) -> None: def validate(self) -> None: """Validates the 'DisconnectDAPMessage' instance.""" - def handle(self, server: mqt.debug.DAPServer) -> dict[str, Any]: + def handle(self, server: DAPServer) -> dict[str, Any]: """Performs the action requested by the 'disconnect' DAP request. Args: - server (mqt.debug.DAPServer): The DAP server that received the request. + server (DAPServer): The DAP server that received the request. Returns: dict[str, Any]: The response to the request. diff --git a/src/mqt/debug/messages/exception_info_message.py b/src/mqt/debug/dap/messages/exception_info_message.py similarity index 100% rename from src/mqt/debug/messages/exception_info_message.py rename to src/mqt/debug/dap/messages/exception_info_message.py diff --git a/src/mqt/debug/messages/exited_dap_event.py b/src/mqt/debug/dap/messages/exited_dap_event.py similarity index 100% rename from src/mqt/debug/messages/exited_dap_event.py rename to src/mqt/debug/dap/messages/exited_dap_event.py diff --git a/src/mqt/debug/messages/gray_out_event.py b/src/mqt/debug/dap/messages/gray_out_event.py similarity index 100% rename from src/mqt/debug/messages/gray_out_event.py rename to src/mqt/debug/dap/messages/gray_out_event.py diff --git a/src/mqt/debug/messages/initialize_dap_message.py b/src/mqt/debug/dap/messages/initialize_dap_message.py similarity index 90% rename from src/mqt/debug/messages/initialize_dap_message.py rename to src/mqt/debug/dap/messages/initialize_dap_message.py index 1f276513..ea050e47 100644 --- a/src/mqt/debug/messages/initialize_dap_message.py +++ b/src/mqt/debug/dap/messages/initialize_dap_message.py @@ -2,13 +2,16 @@ from __future__ import annotations -from typing import Any +from typing import TYPE_CHECKING, Any import mqt.debug from .dap_message import DAPMessage from .utils import get_default_capabilities +if TYPE_CHECKING: + from .. import DAPServer + class InitializeDAPMessage(DAPMessage): """Represents the 'initialize' DAP request.""" @@ -46,11 +49,11 @@ def validate(self) -> None: msg = f"Adapter ID must be `mqtqasm`, was {self.adapter_id}" raise ValueError(msg) - def handle(self, server: mqt.debug.DAPServer) -> dict[str, Any]: + def handle(self, server: DAPServer) -> dict[str, Any]: """Performs the action requested by the 'initialize' DAP request. Args: - server (mqt.debug.DAPServer): The DAP server that received the request. + server (DAPServer): The DAP server that received the request. Returns: dict[str, Any]: The response to the request. diff --git a/src/mqt/debug/messages/initialized_dap_event.py b/src/mqt/debug/dap/messages/initialized_dap_event.py similarity index 100% rename from src/mqt/debug/messages/initialized_dap_event.py rename to src/mqt/debug/dap/messages/initialized_dap_event.py diff --git a/src/mqt/debug/messages/launch_dap_message.py b/src/mqt/debug/dap/messages/launch_dap_message.py similarity index 100% rename from src/mqt/debug/messages/launch_dap_message.py rename to src/mqt/debug/dap/messages/launch_dap_message.py diff --git a/src/mqt/debug/messages/next_dap_message.py b/src/mqt/debug/dap/messages/next_dap_message.py similarity index 100% rename from src/mqt/debug/messages/next_dap_message.py rename to src/mqt/debug/dap/messages/next_dap_message.py diff --git a/src/mqt/debug/messages/output_dap_event.py b/src/mqt/debug/dap/messages/output_dap_event.py similarity index 100% rename from src/mqt/debug/messages/output_dap_event.py rename to src/mqt/debug/dap/messages/output_dap_event.py diff --git a/src/mqt/debug/messages/pause_dap_message.py b/src/mqt/debug/dap/messages/pause_dap_message.py similarity index 100% rename from src/mqt/debug/messages/pause_dap_message.py rename to src/mqt/debug/dap/messages/pause_dap_message.py diff --git a/src/mqt/debug/messages/restart_dap_message.py b/src/mqt/debug/dap/messages/restart_dap_message.py similarity index 100% rename from src/mqt/debug/messages/restart_dap_message.py rename to src/mqt/debug/dap/messages/restart_dap_message.py diff --git a/src/mqt/debug/messages/restart_frame_dap_message.py b/src/mqt/debug/dap/messages/restart_frame_dap_message.py similarity index 100% rename from src/mqt/debug/messages/restart_frame_dap_message.py rename to src/mqt/debug/dap/messages/restart_frame_dap_message.py diff --git a/src/mqt/debug/messages/reverse_continue_dap_message.py b/src/mqt/debug/dap/messages/reverse_continue_dap_message.py similarity index 100% rename from src/mqt/debug/messages/reverse_continue_dap_message.py rename to src/mqt/debug/dap/messages/reverse_continue_dap_message.py diff --git a/src/mqt/debug/messages/scopes_dap_message.py b/src/mqt/debug/dap/messages/scopes_dap_message.py similarity index 100% rename from src/mqt/debug/messages/scopes_dap_message.py rename to src/mqt/debug/dap/messages/scopes_dap_message.py diff --git a/src/mqt/debug/messages/set_breakpoints_dap_message.py b/src/mqt/debug/dap/messages/set_breakpoints_dap_message.py similarity index 100% rename from src/mqt/debug/messages/set_breakpoints_dap_message.py rename to src/mqt/debug/dap/messages/set_breakpoints_dap_message.py diff --git a/src/mqt/debug/messages/set_exception_breakpoints_dap_message.py b/src/mqt/debug/dap/messages/set_exception_breakpoints_dap_message.py similarity index 100% rename from src/mqt/debug/messages/set_exception_breakpoints_dap_message.py rename to src/mqt/debug/dap/messages/set_exception_breakpoints_dap_message.py diff --git a/src/mqt/debug/messages/stack_trace_dap_message.py b/src/mqt/debug/dap/messages/stack_trace_dap_message.py similarity index 100% rename from src/mqt/debug/messages/stack_trace_dap_message.py rename to src/mqt/debug/dap/messages/stack_trace_dap_message.py diff --git a/src/mqt/debug/messages/step_back_dap_message.py b/src/mqt/debug/dap/messages/step_back_dap_message.py similarity index 100% rename from src/mqt/debug/messages/step_back_dap_message.py rename to src/mqt/debug/dap/messages/step_back_dap_message.py diff --git a/src/mqt/debug/messages/step_in_dap_message.py b/src/mqt/debug/dap/messages/step_in_dap_message.py similarity index 100% rename from src/mqt/debug/messages/step_in_dap_message.py rename to src/mqt/debug/dap/messages/step_in_dap_message.py diff --git a/src/mqt/debug/messages/step_out_dap_message.py b/src/mqt/debug/dap/messages/step_out_dap_message.py similarity index 100% rename from src/mqt/debug/messages/step_out_dap_message.py rename to src/mqt/debug/dap/messages/step_out_dap_message.py diff --git a/src/mqt/debug/messages/stopped_dap_event.py b/src/mqt/debug/dap/messages/stopped_dap_event.py similarity index 100% rename from src/mqt/debug/messages/stopped_dap_event.py rename to src/mqt/debug/dap/messages/stopped_dap_event.py diff --git a/src/mqt/debug/messages/terminate_dap_message.py b/src/mqt/debug/dap/messages/terminate_dap_message.py similarity index 80% rename from src/mqt/debug/messages/terminate_dap_message.py rename to src/mqt/debug/dap/messages/terminate_dap_message.py index 6882325f..e117fd67 100644 --- a/src/mqt/debug/messages/terminate_dap_message.py +++ b/src/mqt/debug/dap/messages/terminate_dap_message.py @@ -2,12 +2,15 @@ from __future__ import annotations -from typing import Any +from typing import TYPE_CHECKING, Any import mqt.debug from .dap_message import DAPMessage +if TYPE_CHECKING: + from .. import DAPServer + class TerminateDAPMessage(DAPMessage): """Represents the 'terminate' DAP request.""" @@ -25,11 +28,11 @@ def __init__(self, message: dict[str, Any]) -> None: def validate(self) -> None: """Validates the 'TerminateDAPMessage' instance.""" - def handle(self, server: mqt.debug.DAPServer) -> dict[str, Any]: + def handle(self, server: DAPServer) -> dict[str, Any]: """Performs the action requested by the 'terminate' DAP request. Args: - server (mqt.debug.DAPServer): The DAP server that received the request. + server (DAPServer): The DAP server that received the request. Returns: dict[str, Any]: The response to the request. diff --git a/src/mqt/debug/messages/terminated_dap_event.py b/src/mqt/debug/dap/messages/terminated_dap_event.py similarity index 100% rename from src/mqt/debug/messages/terminated_dap_event.py rename to src/mqt/debug/dap/messages/terminated_dap_event.py diff --git a/src/mqt/debug/messages/threads_dap_message.py b/src/mqt/debug/dap/messages/threads_dap_message.py similarity index 100% rename from src/mqt/debug/messages/threads_dap_message.py rename to src/mqt/debug/dap/messages/threads_dap_message.py diff --git a/src/mqt/debug/messages/utils.py b/src/mqt/debug/dap/messages/utils.py similarity index 100% rename from src/mqt/debug/messages/utils.py rename to src/mqt/debug/dap/messages/utils.py diff --git a/src/mqt/debug/messages/variables_dap_message.py b/src/mqt/debug/dap/messages/variables_dap_message.py similarity index 100% rename from src/mqt/debug/messages/variables_dap_message.py rename to src/mqt/debug/dap/messages/variables_dap_message.py From b9f3c015e2dca2ed8fe14e12e548c6c08e1ed751 Mon Sep 17 00:00:00 2001 From: DRovara <damian.rovara@tum.de> Date: Fri, 9 Aug 2024 10:54:17 +0200 Subject: [PATCH 37/37] ci: :construction_worker: Ignore `dap` submodule from codecov --- .github/codecov.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/codecov.yml b/.github/codecov.yml index fd383a06..cfd5428e 100644 --- a/.github/codecov.yml +++ b/.github/codecov.yml @@ -2,6 +2,7 @@ ignore: - "extern/**/*" - "**/python" - "test/**/*" + - "src/mqt/debug/dap/**/*" coverage: range: 60..90