From 6fd5af10bd19d76a7c7cc84b4bc93ea1a7a9bfe1 Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Mon, 3 Nov 2025 18:23:47 +0100 Subject: [PATCH 001/145] qubit size adjustment --- .../mqt/debugger/dap/messages/variables_dap_message.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/python/mqt/debugger/dap/messages/variables_dap_message.py b/python/mqt/debugger/dap/messages/variables_dap_message.py index ae180d09..dd916273 100644 --- a/python/mqt/debugger/dap/messages/variables_dap_message.py +++ b/python/mqt/debugger/dap/messages/variables_dap_message.py @@ -147,10 +147,11 @@ def _get_quantum_state_variables(server: DAPServer, start: int, count: int, filt ] result = [] num_q = server.simulation_state.get_num_qubits() - start = 0 - count = 10 - num_variables = 2**num_q if count == 0 else count - for i in range(start, start + num_variables): + total_states = 2**num_q + start_index = max(0, min(start, total_states)) + requested_count = total_states - start_index if count == 0 else count + end_index = min(start_index + requested_count, total_states) + for i in range(start_index, end_index): bitstring = format(i, f"0{num_q}b") result.append({ "name": f"|{bitstring}>", From 8accf50a2d66434f080676e1e3697ff711d07d38 Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Mon, 10 Nov 2025 21:19:31 +0100 Subject: [PATCH 002/145] removing runtime error --- bindings/InterfaceBindings.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bindings/InterfaceBindings.cpp b/bindings/InterfaceBindings.cpp index 9ac883ff..808aaa70 100644 --- a/bindings/InterfaceBindings.cpp +++ b/bindings/InterfaceBindings.cpp @@ -44,9 +44,9 @@ namespace { * @param result The result to check. */ void checkOrThrow(Result result) { - if (result != OK) { - throw std::runtime_error("An error occurred while executing the operation"); - } + //if (result != OK) { + // throw std::runtime_error("An error occurred while executing the operation"); + //} } } // namespace From f4b00380ff00054614c8cac538d1a94bd92d6823 Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Wed, 19 Nov 2025 12:01:41 +0100 Subject: [PATCH 003/145] change classical varaible --- bindings/InterfaceBindings.cpp | 8 ++++ include/backend/dd/DDSimDebug.hpp | 7 +++ include/backend/debug.h | 8 ++++ python/mqt/debugger/dap/dap_server.py | 1 + python/mqt/debugger/dap/messages/__init__.py | 2 + .../dap/messages/change_bit_dap_message.py | 48 +++++++++++++++++++ src/backend/dd/DDSimDebug.cpp | 17 +++++++ 7 files changed, 91 insertions(+) create mode 100644 python/mqt/debugger/dap/messages/change_bit_dap_message.py diff --git a/bindings/InterfaceBindings.cpp b/bindings/InterfaceBindings.cpp index 808aaa70..76b72ceb 100644 --- a/bindings/InterfaceBindings.cpp +++ b/bindings/InterfaceBindings.cpp @@ -282,6 +282,14 @@ bool: True, if the simulation can step forward.)") The simulation is unable to step backward if it is at the beginning or if the simulation has not been set up yet. +Returns: +bool: is giving back the new state of the classical bit variable.)") + .def( + "change_bit", + [](SimulationState* self) { self->changeBit(self); }, + R"(Changes the value of the current classical bit variable to its +opposite value. + Returns: bool: True, if the simulation can step backward.)") .def( diff --git a/include/backend/dd/DDSimDebug.hpp b/include/backend/dd/DDSimDebug.hpp index c3be4d7b..30fe3fb9 100644 --- a/include/backend/dd/DDSimDebug.hpp +++ b/include/backend/dd/DDSimDebug.hpp @@ -484,6 +484,13 @@ Result ddsimGetAmplitudeBitstring(SimulationState* self, const char* bitstring, Result ddsimGetClassicalVariable(SimulationState* self, const char* name, Variable* output); +/** + * @brief change the value of the current classical bit variable to its opposite value. + * + * @param self The instance to query. + * @return The result of the operation. + */ +Result ddsimChangeClassicalVariable(SimulationState* self); /** * @brief Gets the number of classical variables in the simulation. * diff --git a/include/backend/debug.h b/include/backend/debug.h index c39186e4..801d9614 100644 --- a/include/backend/debug.h +++ b/include/backend/debug.h @@ -174,6 +174,14 @@ struct SimulationStateStruct { */ bool (*canStepBackward)(SimulationState* self); + /** + * @brief Indicates whether the variable is changed + * + * @param self The instance to query. + * @return True if the variable is changed, false otherwise. + */ + bool (*changeClassicalVariable)(SimulationState* self); + /** * @brief Indicates whether the execution has finished. * diff --git a/python/mqt/debugger/dap/dap_server.py b/python/mqt/debugger/dap/dap_server.py index ea9c3cf8..e19ff596 100644 --- a/python/mqt/debugger/dap/dap_server.py +++ b/python/mqt/debugger/dap/dap_server.py @@ -20,6 +20,7 @@ from .messages import ( ConfigurationDoneDAPMessage, ContinueDAPMessage, + BitChangeDAPMessage, DisconnectDAPMessage, ExceptionInfoDAPMessage, InitializeDAPMessage, diff --git a/python/mqt/debugger/dap/messages/__init__.py b/python/mqt/debugger/dap/messages/__init__.py index c1a4b592..d4cdb10a 100644 --- a/python/mqt/debugger/dap/messages/__init__.py +++ b/python/mqt/debugger/dap/messages/__init__.py @@ -13,6 +13,7 @@ from .capabilities_dap_event import CapabilitiesDAPEvent from .configuration_done_dap_message import ConfigurationDoneDAPMessage from .continue_dap_message import ContinueDAPMessage +from .change_bit_dap_message import BitChangeDAPMessage from .dap_event import DAPEvent from .dap_message import DAPMessage from .disconnect_dap_message import DisconnectDAPMessage @@ -47,6 +48,7 @@ "CapabilitiesDAPEvent", "ConfigurationDoneDAPMessage", "ContinueDAPMessage", + "BitChangeDAPMessage", "DAPEvent", "DAPMessage", "DisconnectDAPMessage", diff --git a/python/mqt/debugger/dap/messages/change_bit_dap_message.py b/python/mqt/debugger/dap/messages/change_bit_dap_message.py new file mode 100644 index 00000000..85ffb72c --- /dev/null +++ b/python/mqt/debugger/dap/messages/change_bit_dap_message.py @@ -0,0 +1,48 @@ +# Copyright (c) 2024 - 2025 Chair for Design Automation, TUM +# Copyright (c) 2025 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +"""Represents the bit chagenge DAP request.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Any + +from .dap_message import DAPMessage + +if TYPE_CHECKING: + from .. import DAPServer + + +class BitChangeDAPMessage(DAPMessage): + """Represents the 'bitChange' DAP request.""" + + message_type_name: str = "bitChange" + + def __init__(self, message: dict[str, Any]) -> None: + """Initializes the 'BitChangeDAPMessage' instance. + + Args: + message (dict[str, Any]): The object representing the 'bitChange' request. + """ + super().__init__(message) + + def validate(self) -> None: + """Validates the 'BitChangeDAPMessage' instance.""" + + def handle(self, server: DAPServer) -> dict[str, Any]: + """Performs the action requested by the 'bitChange' DAP request. + + Args: + server (DAPServer): The DAP server that received the request. + + Returns: + dict[str, Any]: The response to the request. + """ + server.simulation_state.change_classical_value() + return super().handle(server) + diff --git a/src/backend/dd/DDSimDebug.cpp b/src/backend/dd/DDSimDebug.cpp index 6c2fe219..ee162187 100644 --- a/src/backend/dd/DDSimDebug.cpp +++ b/src/backend/dd/DDSimDebug.cpp @@ -525,6 +525,7 @@ Result createDDSimulationState(DDSimulationState* self) { self->interface.pauseSimulation = ddsimPauseSimulation; self->interface.canStepForward = ddsimCanStepForward; self->interface.canStepBackward = ddsimCanStepBackward; + self->interface.changeClassicalVariable = ddsimChangeClassicalVariable; self->interface.isFinished = ddsimIsFinished; self->interface.didAssertionFail = ddsimDidAssertionFail; self->interface.wasBreakpointHit = ddsimWasBreakpointHit; @@ -613,6 +614,21 @@ Result ddsimLoadCode(SimulationState* self, const char* code) { return OK; } +Result ddsimChangeClassicalVariable(SimulationState* self, const char* variableName) { + auto* ddsim = toDDSimulationState(self); + const auto it = ddsim->variables.find(variableName); + if (it == ddsim->variables.end()) { + return ERROR; // no such classical bit + } + auto& var = it->second; + if (var.type != VariableType::VarBool) { + return ERROR; // can only toggle bits + } + var.value.boolValue = !var.value.boolValue; + return OK; +} + + Result ddsimStepOverForward(SimulationState* self) { if (!self->canStepForward(self)) { return ERROR; @@ -1137,6 +1153,7 @@ Result ddsimGetAmplitudeBitstring(SimulationState* self, const char* bitstring, return OK; } + Result ddsimGetClassicalVariable(SimulationState* self, const char* name, Variable* output) { auto* ddsim = toDDSimulationState(self); From f7f27bd64387a61f74268062067ef1ee3b0d4747 Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Wed, 19 Nov 2025 13:09:07 +0100 Subject: [PATCH 004/145] new adjustments --- bindings/InterfaceBindings.cpp | 2 +- python/mqt/debugger/pydebugger.pyi | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/bindings/InterfaceBindings.cpp b/bindings/InterfaceBindings.cpp index 76b72ceb..b9f90265 100644 --- a/bindings/InterfaceBindings.cpp +++ b/bindings/InterfaceBindings.cpp @@ -285,7 +285,7 @@ the simulation has not been set up yet. Returns: bool: is giving back the new state of the classical bit variable.)") .def( - "change_bit", + "change_classical_value", [](SimulationState* self) { self->changeBit(self); }, R"(Changes the value of the current classical bit variable to its opposite value. diff --git a/python/mqt/debugger/pydebugger.pyi b/python/mqt/debugger/pydebugger.pyi index bcf34c97..e53eecc4 100644 --- a/python/mqt/debugger/pydebugger.pyi +++ b/python/mqt/debugger/pydebugger.pyi @@ -159,6 +159,13 @@ class SimulationState: int: The number of assertions that failed during execution. """ + def change_classical_values(self, changes: dict[str, VariableValue]) -> None: + """Changes the values of classical variables in the simulation. + + Args: + changes (dict[str, VariableValue]): A mapping from variable names to their new values. + """ + def run_simulation(self) -> None: """Runs the simulation until it finishes or an assertion fails. From 0cca5ca3df4778a1a6bb1f81e2ba5a4ad55b5d88 Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Wed, 19 Nov 2025 13:35:28 +0100 Subject: [PATCH 005/145] adjustment --- include/backend/debug.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/backend/debug.h b/include/backend/debug.h index 801d9614..268bf1d1 100644 --- a/include/backend/debug.h +++ b/include/backend/debug.h @@ -180,7 +180,7 @@ struct SimulationStateStruct { * @param self The instance to query. * @return True if the variable is changed, false otherwise. */ - bool (*changeClassicalVariable)(SimulationState* self); + Result (*changeClassicalVariable)(SimulationState* self); /** * @brief Indicates whether the execution has finished. From a5c3eca0d010e96e10c3b07f6b5894f655f3cdd9 Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Wed, 19 Nov 2025 14:57:56 +0100 Subject: [PATCH 006/145] adjustment --- bindings/InterfaceBindings.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bindings/InterfaceBindings.cpp b/bindings/InterfaceBindings.cpp index b9f90265..cff79ef1 100644 --- a/bindings/InterfaceBindings.cpp +++ b/bindings/InterfaceBindings.cpp @@ -286,7 +286,7 @@ the simulation has not been set up yet. bool: is giving back the new state of the classical bit variable.)") .def( "change_classical_value", - [](SimulationState* self) { self->changeBit(self); }, + [](SimulationState* self) {return self->changeClassicalVariable(self); }, R"(Changes the value of the current classical bit variable to its opposite value. From 522268234da75f13aeb25c10665a07c00098ddf9 Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Wed, 19 Nov 2025 19:48:43 +0100 Subject: [PATCH 007/145] work and progress --- bindings/InterfaceBindings.cpp | 11 ++++++----- include/backend/dd/DDSimDebug.hpp | 8 +++++--- include/backend/debug.h | 10 ++++++---- .../debugger/dap/messages/change_bit_dap_message.py | 10 ++++++++-- python/mqt/debugger/pydebugger.pyi | 6 +++--- 5 files changed, 28 insertions(+), 17 deletions(-) diff --git a/bindings/InterfaceBindings.cpp b/bindings/InterfaceBindings.cpp index cff79ef1..09436271 100644 --- a/bindings/InterfaceBindings.cpp +++ b/bindings/InterfaceBindings.cpp @@ -286,12 +286,13 @@ the simulation has not been set up yet. bool: is giving back the new state of the classical bit variable.)") .def( "change_classical_value", - [](SimulationState* self) {return self->changeClassicalVariable(self); }, - R"(Changes the value of the current classical bit variable to its -opposite value. + [](SimulationState* self, const std::string& variableName) { + checkOrThrow(self->changeClassicalVariable(self, variableName.c_str())); + }, + R"(Changes the value of the given classical bit variable to its opposite value. -Returns: -bool: True, if the simulation can step backward.)") +Args: + variableName (str): The name of the classical bit that should be toggled.)") .def( "is_finished", [](SimulationState* self) { return self->isFinished(self); }, diff --git a/include/backend/dd/DDSimDebug.hpp b/include/backend/dd/DDSimDebug.hpp index 30fe3fb9..b02a8fbd 100644 --- a/include/backend/dd/DDSimDebug.hpp +++ b/include/backend/dd/DDSimDebug.hpp @@ -485,12 +485,14 @@ Result ddsimGetClassicalVariable(SimulationState* self, const char* name, Variable* output); /** - * @brief change the value of the current classical bit variable to its opposite value. - * + * @brief Change the value of a classical bit variable to its opposite value. + * * @param self The instance to query. + * @param variableName The name of the classical bit variable to toggle. * @return The result of the operation. */ -Result ddsimChangeClassicalVariable(SimulationState* self); +Result ddsimChangeClassicalVariable(SimulationState* self, + const char* variableName); /** * @brief Gets the number of classical variables in the simulation. * diff --git a/include/backend/debug.h b/include/backend/debug.h index 268bf1d1..0b3579e3 100644 --- a/include/backend/debug.h +++ b/include/backend/debug.h @@ -175,12 +175,14 @@ struct SimulationStateStruct { bool (*canStepBackward)(SimulationState* self); /** - * @brief Indicates whether the variable is changed - * + * @brief Toggles the value of a classical bit variable. + * * @param self The instance to query. - * @return True if the variable is changed, false otherwise. + * @param variableName The name of the classical bit that should be toggled. + * @return The result of the operation. */ - Result (*changeClassicalVariable)(SimulationState* self); + Result (*changeClassicalVariable)(SimulationState* self, + const char* variableName); /** * @brief Indicates whether the execution has finished. diff --git a/python/mqt/debugger/dap/messages/change_bit_dap_message.py b/python/mqt/debugger/dap/messages/change_bit_dap_message.py index 85ffb72c..8349f457 100644 --- a/python/mqt/debugger/dap/messages/change_bit_dap_message.py +++ b/python/mqt/debugger/dap/messages/change_bit_dap_message.py @@ -23,16 +23,23 @@ class BitChangeDAPMessage(DAPMessage): message_type_name: str = "bitChange" + variable_name: str | None + def __init__(self, message: dict[str, Any]) -> None: """Initializes the 'BitChangeDAPMessage' instance. Args: message (dict[str, Any]): The object representing the 'bitChange' request. """ + arguments = message.get("arguments", {}) + self.variable_name = arguments.get("variableName") super().__init__(message) def validate(self) -> None: """Validates the 'BitChangeDAPMessage' instance.""" + if not isinstance(self.variable_name, str) or not self.variable_name: + msg = "The 'bitChange' request requires a non-empty 'variableName' argument." + raise ValueError(msg) def handle(self, server: DAPServer) -> dict[str, Any]: """Performs the action requested by the 'bitChange' DAP request. @@ -43,6 +50,5 @@ def handle(self, server: DAPServer) -> dict[str, Any]: Returns: dict[str, Any]: The response to the request. """ - server.simulation_state.change_classical_value() + server.simulation_state.change_classical_value(self.variable_name) return super().handle(server) - diff --git a/python/mqt/debugger/pydebugger.pyi b/python/mqt/debugger/pydebugger.pyi index e53eecc4..d6939b08 100644 --- a/python/mqt/debugger/pydebugger.pyi +++ b/python/mqt/debugger/pydebugger.pyi @@ -159,11 +159,11 @@ class SimulationState: int: The number of assertions that failed during execution. """ - def change_classical_values(self, changes: dict[str, VariableValue]) -> None: - """Changes the values of classical variables in the simulation. + def change_classical_value(self, variable_name: str) -> None: + """Changes the value of a classical bit variable in the simulation. Args: - changes (dict[str, VariableValue]): A mapping from variable names to their new values. + variable_name (str): The name of the classical bit that should be toggled. """ def run_simulation(self) -> None: From 7e8218095a3e9b215eda61f0ffbbad50422ca087 Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Wed, 19 Nov 2025 22:23:15 +0100 Subject: [PATCH 008/145] work and progress --- python/mqt/debugger/dap/dap_server.py | 1 + .../dap/messages/change_bit_dap_message.py | 90 +++++++++++++++---- python/mqt/debugger/dap/messages/utils.py | 2 +- src/backend/dd/DDSimDebug.cpp | 30 ++++++- 4 files changed, 105 insertions(+), 18 deletions(-) diff --git a/python/mqt/debugger/dap/dap_server.py b/python/mqt/debugger/dap/dap_server.py index e19ff596..bea3e5ae 100644 --- a/python/mqt/debugger/dap/dap_server.py +++ b/python/mqt/debugger/dap/dap_server.py @@ -61,6 +61,7 @@ RestartDAPMessage, ScopesDAPMessage, VariablesDAPMessage, + BitChangeDAPMessage, ReverseContinueDAPMessage, StepOutDAPMessage, PauseDAPMessage, diff --git a/python/mqt/debugger/dap/messages/change_bit_dap_message.py b/python/mqt/debugger/dap/messages/change_bit_dap_message.py index 8349f457..c298d32b 100644 --- a/python/mqt/debugger/dap/messages/change_bit_dap_message.py +++ b/python/mqt/debugger/dap/messages/change_bit_dap_message.py @@ -6,49 +6,109 @@ # # Licensed under the MIT License -"""Represents the bit chagenge DAP request.""" +"""Handles the custom 'bitChange' DAP request.""" from __future__ import annotations from typing import TYPE_CHECKING, Any +import mqt.debugger + from .dap_message import DAPMessage +_TRUE_VALUES = {"1", "true", "t", "yes", "on"} +_FALSE_VALUES = {"0", "false", "f", "no", "off"} + if TYPE_CHECKING: from .. import DAPServer class BitChangeDAPMessage(DAPMessage): - """Represents the 'bitChange' DAP request.""" + """Represents the 'setVariable' (aka 'bitChange') DAP request for classical bits.""" - message_type_name: str = "bitChange" + message_type_name: str = "setVariable" - variable_name: str | None + variables_reference: int | None + variable_name: str + new_value: str | bool | None def __init__(self, message: dict[str, Any]) -> None: """Initializes the 'BitChangeDAPMessage' instance. Args: - message (dict[str, Any]): The object representing the 'bitChange' request. + message (dict[str, Any]): The object representing the 'bitChange' or 'setVariable' request. """ arguments = message.get("arguments", {}) - self.variable_name = arguments.get("variableName") + self.variables_reference = arguments.get("variablesReference") + self.variable_name = arguments.get("variableName") or arguments.get("name", "") + self.new_value = arguments.get("value") super().__init__(message) def validate(self) -> None: """Validates the 'BitChangeDAPMessage' instance.""" + if self.variables_reference is not None and not isinstance(self.variables_reference, int): + msg = "The 'setVariable' request requires an integer 'variablesReference' argument." + raise ValueError(msg) if not isinstance(self.variable_name, str) or not self.variable_name: - msg = "The 'bitChange' request requires a non-empty 'variableName' argument." + msg = "The 'bitChange' request requires a non-empty 'variableName' or 'name' argument." + raise ValueError(msg) + if self.new_value is not None and not isinstance(self.new_value, (bool, str)): + msg = "The 'bitChange' request only accepts boolean or string values." raise ValueError(msg) def handle(self, server: DAPServer) -> dict[str, Any]: - """Performs the action requested by the 'bitChange' DAP request. + """Performs the action requested by the 'bitChange' DAP request.""" + response = super().handle(server) + try: + target_name = self._get_target_variable_name() + updated_value = self._apply_change(server, target_name) + except ValueError as exc: + response["success"] = False + response["message"] = str(exc) + return response - Args: - server (DAPServer): The DAP server that received the request. + response["body"] = { + "value": str(updated_value), + "type": "boolean", + "variablesReference": 0, + } + return response - Returns: - dict[str, Any]: The response to the request. - """ - server.simulation_state.change_classical_value(self.variable_name) - return super().handle(server) + def _parse_boolean_value(self, current_value: bool) -> bool: + if self.new_value is None: + return not current_value + if isinstance(self.new_value, bool): + return self.new_value + normalized_value = self.new_value.strip().lower() + if normalized_value in _TRUE_VALUES: + return True + if normalized_value in _FALSE_VALUES: + return False + msg = "Only boolean values (0/1/true/false) are supported for classical bits." + raise ValueError(msg) + + def _get_target_variable_name(self) -> str: + if self.variables_reference is None: + return self.variable_name + if self.variables_reference == 1 or self.variables_reference >= 10: + return self.variable_name + msg = "Only classical variables can be changed." + raise ValueError(msg) + + def _apply_change(self, server: DAPServer, name: str) -> bool: + try: + variable = server.simulation_state.get_classical_variable(name) + except Exception as exc: # noqa: BLE001 + msg = f"The variable '{name}' is not a classical bit." + raise ValueError(msg) from exc + + if variable.type != mqt.debugger.VariableType.VarBool: + msg = "Only boolean classical variables can be changed." + raise ValueError(msg) + + current_value = bool(variable.value.bool_value) + desired_value = self._parse_boolean_value(current_value) + if current_value != desired_value: + server.simulation_state.change_classical_value(name) + variable = server.simulation_state.get_classical_variable(name) + return bool(variable.value.bool_value) diff --git a/python/mqt/debugger/dap/messages/utils.py b/python/mqt/debugger/dap/messages/utils.py index 33abaed4..af4c697c 100644 --- a/python/mqt/debugger/dap/messages/utils.py +++ b/python/mqt/debugger/dap/messages/utils.py @@ -28,7 +28,7 @@ def get_default_capabilities() -> dict[str, Any]: "supportsExceptionInfoRequest": True, "exceptionBreakpointFilters": [], "supportsStepBack": True, - "supportsSetVariable": False, + "supportsSetVariable": True, "supportsRestartFrame": True, "supportsTerminateRequest": True, "supportsRestartRequest": True, diff --git a/src/backend/dd/DDSimDebug.cpp b/src/backend/dd/DDSimDebug.cpp index ee162187..03507d70 100644 --- a/src/backend/dd/DDSimDebug.cpp +++ b/src/backend/dd/DDSimDebug.cpp @@ -39,6 +39,7 @@ #include #include #include +#include #include #include #include @@ -616,7 +617,28 @@ Result ddsimLoadCode(SimulationState* self, const char* code) { Result ddsimChangeClassicalVariable(SimulationState* self, const char* variableName) { auto* ddsim = toDDSimulationState(self); - const auto it = ddsim->variables.find(variableName); + std::string fullName{variableName}; + bool hasExplicitValue = false; + bool explicitValue = false; + if (const auto pos = fullName.find('='); pos != std::string::npos) { + auto valueToken = fullName.substr(pos + 1); + fullName = fullName.substr(0, pos); + std::transform(valueToken.begin(), valueToken.end(), valueToken.begin(), + [](unsigned char c) { return static_cast(std::tolower(c)); }); + if (valueToken == "1" || valueToken == "true" || valueToken == "t" || + valueToken == "yes" || valueToken == "on") { + explicitValue = true; + hasExplicitValue = true; + } else if (valueToken == "0" || valueToken == "false" || + valueToken == "f" || valueToken == "no" || + valueToken == "off") { + explicitValue = false; + hasExplicitValue = true; + } else { + return ERROR; // unsupported literal + } + } + const auto it = ddsim->variables.find(fullName); if (it == ddsim->variables.end()) { return ERROR; // no such classical bit } @@ -624,7 +646,11 @@ Result ddsimChangeClassicalVariable(SimulationState* self, const char* variableN if (var.type != VariableType::VarBool) { return ERROR; // can only toggle bits } - var.value.boolValue = !var.value.boolValue; + if (hasExplicitValue) { + var.value.boolValue = explicitValue; + } else { + var.value.boolValue = !var.value.boolValue; + } return OK; } From e4155c4c84175eb30971ba643d592a9aceeaf998 Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Mon, 24 Nov 2025 22:17:06 +0100 Subject: [PATCH 009/145] amplitude adjustments --- bindings/InterfaceBindings.cpp | 13 ++ include/backend/dd/DDSimDebug.hpp | 13 ++ include/backend/debug.h | 11 ++ python/mqt/debugger/dap/dap_server.py | 8 ++ python/mqt/debugger/dap/messages/__init__.py | 2 + .../messages/change_amplitude_dap_message.py | 127 ++++++++++++++++++ python/mqt/debugger/pydebugger.pyi | 8 ++ src/backend/dd/DDSimDebug.cpp | 100 ++++++++++++++ test/python/test_python_bindings.py | 18 +++ 9 files changed, 300 insertions(+) create mode 100644 python/mqt/debugger/dap/messages/change_amplitude_dap_message.py diff --git a/bindings/InterfaceBindings.cpp b/bindings/InterfaceBindings.cpp index 09436271..06932084 100644 --- a/bindings/InterfaceBindings.cpp +++ b/bindings/InterfaceBindings.cpp @@ -293,6 +293,19 @@ bool: is giving back the new state of the classical bit variable.)") Args: variableName (str): The name of the classical bit that should be toggled.)") + .def( + "change_amplitude_value", + [](SimulationState* self, const std::string& basisState, const Complex& value) { + checkOrThrow(self->changeAmplitudeVariable(self, basisState.c_str(), &value)); + }, + R"(Sets the amplitude of the given computational basis state. + +The basis state must be provided as a bitstring (e.g., ``"010"``) whose length +matches the number of qubits in the circuit. + +Args: + basisState (str): The bitstring describing the basis state whose amplitude should be changed. + value (Complex): The desired complex amplitude.)") .def( "is_finished", [](SimulationState* self) { return self->isFinished(self); }, diff --git a/include/backend/dd/DDSimDebug.hpp b/include/backend/dd/DDSimDebug.hpp index b02a8fbd..2f39b2eb 100644 --- a/include/backend/dd/DDSimDebug.hpp +++ b/include/backend/dd/DDSimDebug.hpp @@ -493,6 +493,19 @@ Result ddsimGetClassicalVariable(SimulationState* self, const char* name, */ Result ddsimChangeClassicalVariable(SimulationState* self, const char* variableName); + +/** + * @brief Updates the amplitude of a given computational basis state. + * + * @param self The instance to query. + * @param basisState The bitstring identifying the basis state to modify. + * @param value The desired complex amplitude. + * @return The result of the operation. + */ +Result ddsimChangeAmplitudeVariable(SimulationState* self, + const char* basisState, + const Complex* value); + /** * @brief Gets the number of classical variables in the simulation. * diff --git a/include/backend/debug.h b/include/backend/debug.h index 0b3579e3..5bbefee7 100644 --- a/include/backend/debug.h +++ b/include/backend/debug.h @@ -183,6 +183,17 @@ struct SimulationStateStruct { */ Result (*changeClassicalVariable)(SimulationState* self, const char* variableName); + /** + * @brief Changes the amplitude of a computational basis state. + * + * @param self The instance to query. + * @param basisState The bitstring identifying the basis state to update. + * @param value The desired complex amplitude. + * @return The result of the operation. + */ + Result (*changeAmplitudeVariable)(SimulationState* self, + const char* basisState, + const Complex* value); /** * @brief Indicates whether the execution has finished. diff --git a/python/mqt/debugger/dap/dap_server.py b/python/mqt/debugger/dap/dap_server.py index bea3e5ae..9812515e 100644 --- a/python/mqt/debugger/dap/dap_server.py +++ b/python/mqt/debugger/dap/dap_server.py @@ -21,6 +21,7 @@ ConfigurationDoneDAPMessage, ContinueDAPMessage, BitChangeDAPMessage, + AmplitudeChangeDAPMessage, DisconnectDAPMessage, ExceptionInfoDAPMessage, InitializeDAPMessage, @@ -62,6 +63,7 @@ ScopesDAPMessage, VariablesDAPMessage, BitChangeDAPMessage, + AmplitudeChangeDAPMessage, ReverseContinueDAPMessage, StepOutDAPMessage, PauseDAPMessage, @@ -263,6 +265,12 @@ def handle_command(self, command: dict[str, Any]) -> tuple[dict[str, Any], mqt.d Returns: tuple[dict[str, Any], mqt.debugger.dap.messages.DAPMessage]: The response to the message as a dictionary and the message object. """ + if command["command"] == "setVariable": + arguments = command.get("arguments", {}) + variables_reference = arguments.get("variablesReference") + message_type = AmplitudeChangeDAPMessage if variables_reference == 2 else BitChangeDAPMessage + message = message_type(command) + return (message.handle(self), message) for message_type in supported_messages: if message_type.message_type_name == command["command"]: message = message_type(command) diff --git a/python/mqt/debugger/dap/messages/__init__.py b/python/mqt/debugger/dap/messages/__init__.py index d4cdb10a..1ce992e3 100644 --- a/python/mqt/debugger/dap/messages/__init__.py +++ b/python/mqt/debugger/dap/messages/__init__.py @@ -13,6 +13,7 @@ from .capabilities_dap_event import CapabilitiesDAPEvent from .configuration_done_dap_message import ConfigurationDoneDAPMessage from .continue_dap_message import ContinueDAPMessage +from .change_amplitude_dap_message import AmplitudeChangeDAPMessage from .change_bit_dap_message import BitChangeDAPMessage from .dap_event import DAPEvent from .dap_message import DAPMessage @@ -48,6 +49,7 @@ "CapabilitiesDAPEvent", "ConfigurationDoneDAPMessage", "ContinueDAPMessage", + "AmplitudeChangeDAPMessage", "BitChangeDAPMessage", "DAPEvent", "DAPMessage", diff --git a/python/mqt/debugger/dap/messages/change_amplitude_dap_message.py b/python/mqt/debugger/dap/messages/change_amplitude_dap_message.py new file mode 100644 index 00000000..28c1a03f --- /dev/null +++ b/python/mqt/debugger/dap/messages/change_amplitude_dap_message.py @@ -0,0 +1,127 @@ +"""Handles edits of quantum amplitudes via the DAP ``setVariable`` request.""" + +from __future__ import annotations + +from dataclasses import dataclass +from typing import TYPE_CHECKING, Any + +import mqt.debugger + +from .dap_message import DAPMessage + +if TYPE_CHECKING: + from .. import DAPServer + +_QUANTUM_REFERENCE = 2 +_EPS = 1e-9 + +@dataclass +class _TargetAmplitude: + bitstring: str + desired: complex + + +def _format_complex(value: mqt.debugger.Complex) -> str: + real = value.real + imag = value.imaginary + sign = "+" if imag >= 0 else "-" + return f"{real:.6g} {sign} {abs(imag):.6g}i" + + +def _complex_matches(current: mqt.debugger.Complex, desired: complex) -> bool: + return abs(current.real - desired.real) <= _EPS and abs(current.imaginary - desired.imag) <= _EPS + + +def _normalize_value(value: str) -> str: + """Normalize complex literals so that Python's complex() can parse them.""" + normalized = value.strip().replace(" ", "") + if not normalized: + msg = "The new amplitude value must not be empty." + raise ValueError(msg) + # Visual Studio Code allows using `i` as the imaginary unit. Python expects `j`. + if "i" in normalized and "j" not in normalized: + normalized = normalized.replace("i", "j") + return normalized + + +class AmplitudeChangeDAPMessage(DAPMessage): + """Represents the 'setVariable' DAP request for quantum amplitude edits.""" + + message_type_name: str = "setVariable" + + variables_reference: int | None + variable_name: str + new_value: str | None + + def __init__(self, message: dict[str, Any]) -> None: + arguments = message.get("arguments", {}) + self.variables_reference = arguments.get("variablesReference") + self.variable_name = arguments.get("variableName") or arguments.get("name", "") + raw_value = arguments.get("value") + self.new_value = raw_value if isinstance(raw_value, str) else None + super().__init__(message) + + def validate(self) -> None: + if self.variables_reference != _QUANTUM_REFERENCE: + msg = "This handler only supports quantum amplitudes." + raise ValueError(msg) + if not isinstance(self.variable_name, str) or not self.variable_name: + msg = "The 'setVariable' request requires a non-empty 'variableName' argument." + raise ValueError(msg) + if self.new_value is None: + msg = "The 'setVariable' request for quantum amplitudes must provide the new complex value as a string." + raise ValueError(msg) + + def handle(self, server: DAPServer) -> dict[str, Any]: + response = super().handle(server) + try: + target = self._parse_request(server) + updated = self._apply_change(server, target) + except ValueError as exc: + response["success"] = False + response["message"] = str(exc) + return response + + response["body"] = { + "value": _format_complex(updated), + "type": "complex", + "variablesReference": 0, + } + return response + + def _parse_request(self, server: DAPServer) -> _TargetAmplitude: + bitstring = self._extract_bitstring() + if len(bitstring) != server.simulation_state.get_num_qubits(): + msg = f"The bitstring '{bitstring}' must have length {server.simulation_state.get_num_qubits()}." + raise ValueError(msg) + normalized = _normalize_value(self.new_value or "") + try: + desired = complex(normalized) + except ValueError as exc: # noqa: BLE001 + msg = f"The provided value '{self.new_value}' is not a valid complex number." + raise ValueError(msg) from exc + return _TargetAmplitude(bitstring, desired) + + def _extract_bitstring(self) -> str: + name = self.variable_name.strip() + if not name.startswith("|") or not name.endswith(">"): + msg = "Quantum amplitudes must be addressed using the '|...>' notation." + raise ValueError(msg) + bitstring = name[1:-1] + if not bitstring or any(ch not in "01" for ch in bitstring): + msg = f"'{self.variable_name}' is not a valid computational basis state." + raise ValueError(msg) + return bitstring + + def _apply_change(self, server: DAPServer, target: _TargetAmplitude) -> mqt.debugger.Complex: + current = server.simulation_state.get_amplitude_bitstring(target.bitstring) + if _complex_matches(current, target.desired): + return current + + desired_value = mqt.debugger.Complex(target.desired.real, target.desired.imag) + try: + server.simulation_state.change_amplitude_value(target.bitstring, desired_value) + except RuntimeError as exc: + msg = str(exc) + raise ValueError(msg) from exc + return server.simulation_state.get_amplitude_bitstring(target.bitstring) diff --git a/python/mqt/debugger/pydebugger.pyi b/python/mqt/debugger/pydebugger.pyi index d6939b08..6affce6d 100644 --- a/python/mqt/debugger/pydebugger.pyi +++ b/python/mqt/debugger/pydebugger.pyi @@ -166,6 +166,14 @@ class SimulationState: variable_name (str): The name of the classical bit that should be toggled. """ + def change_amplitude_value(self, basis_state: str, value: Complex) -> None: + """Sets the amplitude of a computational basis state in the simulation. + + Args: + basis_state (str): The bitstring identifying the basis state (e.g. ``"010"``). + value (Complex): The desired complex amplitude. + """ + def run_simulation(self) -> None: """Runs the simulation until it finishes or an assertion fails. diff --git a/src/backend/dd/DDSimDebug.cpp b/src/backend/dd/DDSimDebug.cpp index 03507d70..12e3b393 100644 --- a/src/backend/dd/DDSimDebug.cpp +++ b/src/backend/dd/DDSimDebug.cpp @@ -45,6 +45,7 @@ #include #include #include +#include #include #include #include @@ -527,6 +528,7 @@ Result createDDSimulationState(DDSimulationState* self) { self->interface.canStepForward = ddsimCanStepForward; self->interface.canStepBackward = ddsimCanStepBackward; self->interface.changeClassicalVariable = ddsimChangeClassicalVariable; + self->interface.changeAmplitudeVariable = ddsimChangeAmplitudeVariable; self->interface.isFinished = ddsimIsFinished; self->interface.didAssertionFail = ddsimDidAssertionFail; self->interface.wasBreakpointHit = ddsimWasBreakpointHit; @@ -654,6 +656,104 @@ Result ddsimChangeClassicalVariable(SimulationState* self, const char* variableN return OK; } +Result ddsimChangeAmplitudeVariable(SimulationState* self, + const char* basisState, + const Complex* value) { + if (basisState == nullptr || value == nullptr) { + return ERROR; + } + auto* ddsim = toDDSimulationState(self); + const auto numQubits = ddsim->qc->getNqubits(); + const std::string state{basisState}; + if (state.size() != numQubits) { + return ERROR; + } + if (std::ranges::any_of(state, + [](char c) { return c != '0' && c != '1'; })) { + return ERROR; + } + + std::size_t index = 0; + for (const char bit : state) { + index <<= 1U; + if (bit == '1') { + index |= 1U; + } + } + + const auto numStates = 1ULL << numQubits; + std::vector amplitudes(numStates); + Statevector sv{numQubits, numStates, amplitudes.data()}; + if (self->getStateVectorFull(self, &sv) != OK) { + return ERROR; + } + + constexpr double tolerance = 1e-9; + double otherNormSquared = 0.0; + for (std::size_t i = 0; i < numStates; i++) { + if (i == index) { + continue; + } + const auto& amp = amplitudes[i]; + otherNormSquared += + amp.real * amp.real + amp.imaginary * amp.imaginary; + } + + const double desiredReal = value->real; + const double desiredImag = value->imaginary; + const double desiredNormSquared = + desiredReal * desiredReal + desiredImag * desiredImag; + if (desiredNormSquared > 1.0 + tolerance) { + return ERROR; + } + if (otherNormSquared <= tolerance && + std::abs(desiredNormSquared - 1.0) > tolerance) { + return ERROR; + } + + double scalingFactor = 0.0; + if (otherNormSquared > tolerance) { + const double remaining = 1.0 - desiredNormSquared; + if (remaining < -tolerance) { + return ERROR; + } + if (remaining <= tolerance) { + scalingFactor = 0.0; + } else { + scalingFactor = std::sqrt(remaining / otherNormSquared); + } + } + + amplitudes[index] = *value; + if (otherNormSquared > tolerance) { + for (std::size_t i = 0; i < numStates; i++) { + if (i == index) { + continue; + } + amplitudes[i].real *= scalingFactor; + amplitudes[i].imaginary *= scalingFactor; + } + } + + dd::CVec ddVector; + ddVector.reserve(numStates); + for (const auto& amp : amplitudes) { + ddVector.emplace_back(amp.real, amp.imaginary); + } + + try { + auto newState = dd::makeStateFromVector(ddVector, *(ddsim->dd)); + ddsim->dd->incRef(newState); + if (ddsim->simulationState.p != nullptr) { + ddsim->dd->decRef(ddsim->simulationState); + } + ddsim->simulationState = newState; + } catch (const std::exception& e) { + std::cerr << e.what() << "\n"; + return ERROR; + } + return OK; +} Result ddsimStepOverForward(SimulationState* self) { if (!self->canStepForward(self)) { diff --git a/test/python/test_python_bindings.py b/test/python/test_python_bindings.py index f6515cec..8088494c 100644 --- a/test/python/test_python_bindings.py +++ b/test/python/test_python_bindings.py @@ -15,6 +15,7 @@ import locale from pathlib import Path +import math from typing import TYPE_CHECKING, cast import pytest @@ -221,6 +222,23 @@ def test_access_state(simulation_instance_jumps: SimulationInstance) -> None: assert abs(c.real) < 1e-6 +def test_change_amplitude_value(simulation_instance_ghz: SimulationInstance) -> None: + """Tests manipulating amplitudes through the bindings.""" + (simulation_state, _state_id) = simulation_instance_ghz + simulation_state.run_simulation() + + desired = mqt.debugger.Complex(0.25, 0.0) + simulation_state.change_amplitude_value("111", desired) + updated = simulation_state.get_amplitude_bitstring("111") + assert updated.real == pytest.approx(0.25, abs=1e-9) + assert updated.imaginary == pytest.approx(0.0, abs=1e-9) + + zero_state = simulation_state.get_amplitude_bitstring("000") + expected_magnitude = math.sqrt(1 - 0.25**2) + assert zero_state.real == pytest.approx(expected_magnitude, abs=1e-9) + assert zero_state.imaginary == pytest.approx(0.0, abs=1e-9) + + def test_get_state_vector_sub(simulation_instance_classical: SimulationInstance) -> None: """Tests the `get_state_vector_sub()` method.""" (simulation_state, _state_id) = simulation_instance_classical From 64de683b6e450391a44c5973cccd676ed81f3d2b Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Mon, 1 Dec 2025 20:20:42 +0100 Subject: [PATCH 010/145] License text --- .../debugger/dap/messages/change_amplitude_dap_message.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/python/mqt/debugger/dap/messages/change_amplitude_dap_message.py b/python/mqt/debugger/dap/messages/change_amplitude_dap_message.py index 28c1a03f..71695a33 100644 --- a/python/mqt/debugger/dap/messages/change_amplitude_dap_message.py +++ b/python/mqt/debugger/dap/messages/change_amplitude_dap_message.py @@ -1,3 +1,11 @@ +# Copyright (c) 2024 - 2025 Chair for Design Automation, TUM +# Copyright (c) 2025 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + """Handles edits of quantum amplitudes via the DAP ``setVariable`` request.""" from __future__ import annotations From 1e99c9680e918276c561262c0d31153edbeb0542 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 1 Dec 2025 20:02:54 +0000 Subject: [PATCH 011/145] =?UTF-8?q?=F0=9F=8E=A8=20pre-commit=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bindings/InterfaceBindings.cpp | 16 +++++++++------ python/mqt/debugger/dap/dap_server.py | 4 ++-- python/mqt/debugger/dap/messages/__init__.py | 8 ++++---- .../messages/change_amplitude_dap_message.py | 3 ++- .../dap/messages/change_bit_dap_message.py | 2 +- src/backend/dd/DDSimDebug.cpp | 20 +++++++++---------- test/python/test_python_bindings.py | 2 +- 7 files changed, 29 insertions(+), 26 deletions(-) diff --git a/bindings/InterfaceBindings.cpp b/bindings/InterfaceBindings.cpp index 06932084..e98b4be5 100644 --- a/bindings/InterfaceBindings.cpp +++ b/bindings/InterfaceBindings.cpp @@ -44,9 +44,10 @@ namespace { * @param result The result to check. */ void checkOrThrow(Result result) { - //if (result != OK) { - // throw std::runtime_error("An error occurred while executing the operation"); - //} + // if (result != OK) { + // throw std::runtime_error("An error occurred while executing the + // operation"); + // } } } // namespace @@ -287,7 +288,8 @@ bool: is giving back the new state of the classical bit variable.)") .def( "change_classical_value", [](SimulationState* self, const std::string& variableName) { - checkOrThrow(self->changeClassicalVariable(self, variableName.c_str())); + checkOrThrow( + self->changeClassicalVariable(self, variableName.c_str())); }, R"(Changes the value of the given classical bit variable to its opposite value. @@ -295,8 +297,10 @@ bool: is giving back the new state of the classical bit variable.)") variableName (str): The name of the classical bit that should be toggled.)") .def( "change_amplitude_value", - [](SimulationState* self, const std::string& basisState, const Complex& value) { - checkOrThrow(self->changeAmplitudeVariable(self, basisState.c_str(), &value)); + [](SimulationState* self, const std::string& basisState, + const Complex& value) { + checkOrThrow(self->changeAmplitudeVariable(self, basisState.c_str(), + &value)); }, R"(Sets the amplitude of the given computational basis state. diff --git a/python/mqt/debugger/dap/dap_server.py b/python/mqt/debugger/dap/dap_server.py index 9812515e..2569e1c6 100644 --- a/python/mqt/debugger/dap/dap_server.py +++ b/python/mqt/debugger/dap/dap_server.py @@ -18,10 +18,10 @@ import mqt.debugger from .messages import ( + AmplitudeChangeDAPMessage, + BitChangeDAPMessage, ConfigurationDoneDAPMessage, ContinueDAPMessage, - BitChangeDAPMessage, - AmplitudeChangeDAPMessage, DisconnectDAPMessage, ExceptionInfoDAPMessage, InitializeDAPMessage, diff --git a/python/mqt/debugger/dap/messages/__init__.py b/python/mqt/debugger/dap/messages/__init__.py index 1ce992e3..f15bad76 100644 --- a/python/mqt/debugger/dap/messages/__init__.py +++ b/python/mqt/debugger/dap/messages/__init__.py @@ -11,10 +11,10 @@ from __future__ import annotations from .capabilities_dap_event import CapabilitiesDAPEvent -from .configuration_done_dap_message import ConfigurationDoneDAPMessage -from .continue_dap_message import ContinueDAPMessage from .change_amplitude_dap_message import AmplitudeChangeDAPMessage from .change_bit_dap_message import BitChangeDAPMessage +from .configuration_done_dap_message import ConfigurationDoneDAPMessage +from .continue_dap_message import ContinueDAPMessage from .dap_event import DAPEvent from .dap_message import DAPMessage from .disconnect_dap_message import DisconnectDAPMessage @@ -46,11 +46,11 @@ Request = DAPMessage __all__ = [ + "AmplitudeChangeDAPMessage", + "BitChangeDAPMessage", "CapabilitiesDAPEvent", "ConfigurationDoneDAPMessage", "ContinueDAPMessage", - "AmplitudeChangeDAPMessage", - "BitChangeDAPMessage", "DAPEvent", "DAPMessage", "DisconnectDAPMessage", diff --git a/python/mqt/debugger/dap/messages/change_amplitude_dap_message.py b/python/mqt/debugger/dap/messages/change_amplitude_dap_message.py index 71695a33..d69b9234 100644 --- a/python/mqt/debugger/dap/messages/change_amplitude_dap_message.py +++ b/python/mqt/debugger/dap/messages/change_amplitude_dap_message.py @@ -23,6 +23,7 @@ _QUANTUM_REFERENCE = 2 _EPS = 1e-9 + @dataclass class _TargetAmplitude: bitstring: str @@ -105,7 +106,7 @@ def _parse_request(self, server: DAPServer) -> _TargetAmplitude: normalized = _normalize_value(self.new_value or "") try: desired = complex(normalized) - except ValueError as exc: # noqa: BLE001 + except ValueError as exc: msg = f"The provided value '{self.new_value}' is not a valid complex number." raise ValueError(msg) from exc return _TargetAmplitude(bitstring, desired) diff --git a/python/mqt/debugger/dap/messages/change_bit_dap_message.py b/python/mqt/debugger/dap/messages/change_bit_dap_message.py index c298d32b..036c3a47 100644 --- a/python/mqt/debugger/dap/messages/change_bit_dap_message.py +++ b/python/mqt/debugger/dap/messages/change_bit_dap_message.py @@ -98,7 +98,7 @@ def _get_target_variable_name(self) -> str: def _apply_change(self, server: DAPServer, name: str) -> bool: try: variable = server.simulation_state.get_classical_variable(name) - except Exception as exc: # noqa: BLE001 + except Exception as exc: msg = f"The variable '{name}' is not a classical bit." raise ValueError(msg) from exc diff --git a/src/backend/dd/DDSimDebug.cpp b/src/backend/dd/DDSimDebug.cpp index 12e3b393..ee288e9f 100644 --- a/src/backend/dd/DDSimDebug.cpp +++ b/src/backend/dd/DDSimDebug.cpp @@ -38,8 +38,8 @@ #include #include -#include #include +#include #include #include #include @@ -617,7 +617,8 @@ Result ddsimLoadCode(SimulationState* self, const char* code) { return OK; } -Result ddsimChangeClassicalVariable(SimulationState* self, const char* variableName) { +Result ddsimChangeClassicalVariable(SimulationState* self, + const char* variableName) { auto* ddsim = toDDSimulationState(self); std::string fullName{variableName}; bool hasExplicitValue = false; @@ -625,15 +626,15 @@ Result ddsimChangeClassicalVariable(SimulationState* self, const char* variableN if (const auto pos = fullName.find('='); pos != std::string::npos) { auto valueToken = fullName.substr(pos + 1); fullName = fullName.substr(0, pos); - std::transform(valueToken.begin(), valueToken.end(), valueToken.begin(), - [](unsigned char c) { return static_cast(std::tolower(c)); }); + std::transform( + valueToken.begin(), valueToken.end(), valueToken.begin(), + [](unsigned char c) { return static_cast(std::tolower(c)); }); if (valueToken == "1" || valueToken == "true" || valueToken == "t" || valueToken == "yes" || valueToken == "on") { explicitValue = true; hasExplicitValue = true; } else if (valueToken == "0" || valueToken == "false" || - valueToken == "f" || valueToken == "no" || - valueToken == "off") { + valueToken == "f" || valueToken == "no" || valueToken == "off") { explicitValue = false; hasExplicitValue = true; } else { @@ -668,8 +669,7 @@ Result ddsimChangeAmplitudeVariable(SimulationState* self, if (state.size() != numQubits) { return ERROR; } - if (std::ranges::any_of(state, - [](char c) { return c != '0' && c != '1'; })) { + if (std::ranges::any_of(state, [](char c) { return c != '0' && c != '1'; })) { return ERROR; } @@ -695,8 +695,7 @@ Result ddsimChangeAmplitudeVariable(SimulationState* self, continue; } const auto& amp = amplitudes[i]; - otherNormSquared += - amp.real * amp.real + amp.imaginary * amp.imaginary; + otherNormSquared += amp.real * amp.real + amp.imaginary * amp.imaginary; } const double desiredReal = value->real; @@ -1279,7 +1278,6 @@ Result ddsimGetAmplitudeBitstring(SimulationState* self, const char* bitstring, return OK; } - Result ddsimGetClassicalVariable(SimulationState* self, const char* name, Variable* output) { auto* ddsim = toDDSimulationState(self); diff --git a/test/python/test_python_bindings.py b/test/python/test_python_bindings.py index 8088494c..76f1baa6 100644 --- a/test/python/test_python_bindings.py +++ b/test/python/test_python_bindings.py @@ -14,8 +14,8 @@ from __future__ import annotations import locale -from pathlib import Path import math +from pathlib import Path from typing import TYPE_CHECKING, cast import pytest From 88de5e5e8f068338d10511935ea413d5d4a92b99 Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Tue, 2 Dec 2025 19:08:17 +0100 Subject: [PATCH 012/145] docstrings --- .../messages/change_amplitude_dap_message.py | 49 ++++++++++++++++++- .../dap/messages/change_bit_dap_message.py | 35 +++++++++++-- 2 files changed, 80 insertions(+), 4 deletions(-) diff --git a/python/mqt/debugger/dap/messages/change_amplitude_dap_message.py b/python/mqt/debugger/dap/messages/change_amplitude_dap_message.py index d69b9234..cd0c61cc 100644 --- a/python/mqt/debugger/dap/messages/change_amplitude_dap_message.py +++ b/python/mqt/debugger/dap/messages/change_amplitude_dap_message.py @@ -26,11 +26,14 @@ @dataclass class _TargetAmplitude: + """Container describing the requested bitstring and desired complex value.""" + bitstring: str desired: complex def _format_complex(value: mqt.debugger.Complex) -> str: + """Represent a complex number in the format expected by Visual Studio Code.""" real = value.real imag = value.imaginary sign = "+" if imag >= 0 else "-" @@ -38,11 +41,19 @@ def _format_complex(value: mqt.debugger.Complex) -> str: def _complex_matches(current: mqt.debugger.Complex, desired: complex) -> bool: + """Return ``True`` if the debugger amplitude equals the desired value.""" return abs(current.real - desired.real) <= _EPS and abs(current.imaginary - desired.imag) <= _EPS def _normalize_value(value: str) -> str: - """Normalize complex literals so that Python's complex() can parse them.""" + """Normalize a string literal so Python's :func:`complex` can parse it. + + Args: + value (str): Raw value received from the DAP client. + + Returns: + str: Normalized literal accepted by ``complex``. + """ normalized = value.strip().replace(" ", "") if not normalized: msg = "The new amplitude value must not be empty." @@ -63,6 +74,11 @@ class AmplitudeChangeDAPMessage(DAPMessage): new_value: str | None def __init__(self, message: dict[str, Any]) -> None: + """Initialize the amplitude 'setVariable' handler instance. + + Args: + message (dict[str, Any]): The raw DAP request. + """ arguments = message.get("arguments", {}) self.variables_reference = arguments.get("variablesReference") self.variable_name = arguments.get("variableName") or arguments.get("name", "") @@ -71,6 +87,7 @@ def __init__(self, message: dict[str, Any]) -> None: super().__init__(message) def validate(self) -> None: + """Validate that the request targets amplitudes and provides a new value.""" if self.variables_reference != _QUANTUM_REFERENCE: msg = "This handler only supports quantum amplitudes." raise ValueError(msg) @@ -82,6 +99,14 @@ def validate(self) -> None: raise ValueError(msg) def handle(self, server: DAPServer) -> dict[str, Any]: + """Apply the amplitude change and return the new complex value. + + Args: + server (DAPServer): The DAP server handling the request. + + Returns: + dict[str, Any]: The DAP response with the updated value. + """ response = super().handle(server) try: target = self._parse_request(server) @@ -99,6 +124,14 @@ def handle(self, server: DAPServer) -> dict[str, Any]: return response def _parse_request(self, server: DAPServer) -> _TargetAmplitude: + """Extract the targeted bitstring and desired complex amplitude. + + Args: + server (DAPServer): The DAP server providing simulator metadata. + + Returns: + _TargetAmplitude: Bitstring and target value requested by VS Code. + """ bitstring = self._extract_bitstring() if len(bitstring) != server.simulation_state.get_num_qubits(): msg = f"The bitstring '{bitstring}' must have length {server.simulation_state.get_num_qubits()}." @@ -112,6 +145,11 @@ def _parse_request(self, server: DAPServer) -> _TargetAmplitude: return _TargetAmplitude(bitstring, desired) def _extract_bitstring(self) -> str: + """Return the ``|...>`` bitstring referenced in the request. + + Returns: + str: The computational basis state name without delimiters. + """ name = self.variable_name.strip() if not name.startswith("|") or not name.endswith(">"): msg = "Quantum amplitudes must be addressed using the '|...>' notation." @@ -123,6 +161,15 @@ def _extract_bitstring(self) -> str: return bitstring def _apply_change(self, server: DAPServer, target: _TargetAmplitude) -> mqt.debugger.Complex: + """Write the requested amplitude into the simulation state if needed. + + Args: + server (DAPServer): The DAP server providing simulator access. + target (_TargetAmplitude): The desired bitstring/value pair. + + Returns: + mqt.debugger.Complex: The amplitude returned by the simulator after the update. + """ current = server.simulation_state.get_amplitude_bitstring(target.bitstring) if _complex_matches(current, target.desired): return current diff --git a/python/mqt/debugger/dap/messages/change_bit_dap_message.py b/python/mqt/debugger/dap/messages/change_bit_dap_message.py index 036c3a47..e0ecaa60 100644 --- a/python/mqt/debugger/dap/messages/change_bit_dap_message.py +++ b/python/mqt/debugger/dap/messages/change_bit_dap_message.py @@ -33,7 +33,7 @@ class BitChangeDAPMessage(DAPMessage): new_value: str | bool | None def __init__(self, message: dict[str, Any]) -> None: - """Initializes the 'BitChangeDAPMessage' instance. + """Initialize the 'BitChangeDAPMessage' instance. Args: message (dict[str, Any]): The object representing the 'bitChange' or 'setVariable' request. @@ -45,7 +45,7 @@ def __init__(self, message: dict[str, Any]) -> None: super().__init__(message) def validate(self) -> None: - """Validates the 'BitChangeDAPMessage' instance.""" + """Validate that the request targets classical bits and uses boolean data.""" if self.variables_reference is not None and not isinstance(self.variables_reference, int): msg = "The 'setVariable' request requires an integer 'variablesReference' argument." raise ValueError(msg) @@ -57,7 +57,14 @@ def validate(self) -> None: raise ValueError(msg) def handle(self, server: DAPServer) -> dict[str, Any]: - """Performs the action requested by the 'bitChange' DAP request.""" + """Perform the action requested by the 'bitChange' DAP request. + + Args: + server (DAPServer): The DAP server handling the request. + + Returns: + dict[str, Any]: The DAP response describing the resulting boolean value. + """ response = super().handle(server) try: target_name = self._get_target_variable_name() @@ -75,6 +82,14 @@ def handle(self, server: DAPServer) -> dict[str, Any]: return response def _parse_boolean_value(self, current_value: bool) -> bool: + """Interpret ``self.new_value`` (or flip the bit if absent) as a boolean. + + Args: + current_value (bool): Value currently stored by the simulator. + + Returns: + bool: Desired value after interpreting the request payload. + """ if self.new_value is None: return not current_value if isinstance(self.new_value, bool): @@ -88,6 +103,11 @@ def _parse_boolean_value(self, current_value: bool) -> bool: raise ValueError(msg) def _get_target_variable_name(self) -> str: + """Return the variable name if the reference points to classical data. + + Returns: + str: Name of the classical variable that should be updated. + """ if self.variables_reference is None: return self.variable_name if self.variables_reference == 1 or self.variables_reference >= 10: @@ -96,6 +116,15 @@ def _get_target_variable_name(self) -> str: raise ValueError(msg) def _apply_change(self, server: DAPServer, name: str) -> bool: + """Toggle the classical bit in the simulation state if necessary. + + Args: + server (DAPServer): The DAP server exposing simulator APIs. + name (str): The classical variable requested by the client. + + Returns: + bool: Resulting value reported by the simulator. + """ try: variable = server.simulation_state.get_classical_variable(name) except Exception as exc: From 057b5ddfa249334071e0451bdb9078bd53b2bf93 Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Tue, 2 Dec 2025 19:13:33 +0100 Subject: [PATCH 013/145] _normalize_value updated --- .../dap/messages/change_amplitude_dap_message.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/python/mqt/debugger/dap/messages/change_amplitude_dap_message.py b/python/mqt/debugger/dap/messages/change_amplitude_dap_message.py index cd0c61cc..2515b752 100644 --- a/python/mqt/debugger/dap/messages/change_amplitude_dap_message.py +++ b/python/mqt/debugger/dap/messages/change_amplitude_dap_message.py @@ -46,7 +46,13 @@ def _complex_matches(current: mqt.debugger.Complex, desired: complex) -> bool: def _normalize_value(value: str) -> str: - """Normalize a string literal so Python's :func:`complex` can parse it. + """Normalize DAP complex literals so Python's :func:`complex` can parse them. + + Visual Studio Code currently sends amplitudes in the ``a+bi`` / ``a-bi`` form, + but also accepts real-only (``a``) or imaginary-only (``bi``) literals with + arbitrary whitespace. Plain ``i``/``-i`` or inputs mixing ``i`` and ``j`` are + intentionally unsupported. When a literal contains ``i`` but no ``j``, this + function rewrites ``i`` to ``j`` so Python understands the imaginary unit. Args: value (str): Raw value received from the DAP client. @@ -56,7 +62,11 @@ def _normalize_value(value: str) -> str: """ normalized = value.strip().replace(" ", "") if not normalized: - msg = "The new amplitude value must not be empty." + msg = ( + "The new amplitude value must not be empty; use literals such as " + "'a+bi', 'a-bi', 'a', or 'bi'. Plain 'i'/'-i' and mixed 'i'/'j' " + "inputs are rejected, and 'i' is only converted to 'j' when no 'j' is present." + ) raise ValueError(msg) # Visual Studio Code allows using `i` as the imaginary unit. Python expects `j`. if "i" in normalized and "j" not in normalized: From 1bed4cc033f24660e02eab49bb6ace1b1bd76f06 Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Tue, 2 Dec 2025 19:30:26 +0100 Subject: [PATCH 014/145] solving issue --- .../mqt/debugger/dap/messages/change_amplitude_dap_message.py | 2 +- python/mqt/debugger/dap/messages/change_bit_dap_message.py | 4 ++-- src/backend/dd/DDSimDebug.cpp | 1 - 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/python/mqt/debugger/dap/messages/change_amplitude_dap_message.py b/python/mqt/debugger/dap/messages/change_amplitude_dap_message.py index 2515b752..ad829d27 100644 --- a/python/mqt/debugger/dap/messages/change_amplitude_dap_message.py +++ b/python/mqt/debugger/dap/messages/change_amplitude_dap_message.py @@ -80,7 +80,7 @@ class AmplitudeChangeDAPMessage(DAPMessage): message_type_name: str = "setVariable" variables_reference: int | None - variable_name: str + variable_name: Any new_value: str | None def __init__(self, message: dict[str, Any]) -> None: diff --git a/python/mqt/debugger/dap/messages/change_bit_dap_message.py b/python/mqt/debugger/dap/messages/change_bit_dap_message.py index e0ecaa60..232a3459 100644 --- a/python/mqt/debugger/dap/messages/change_bit_dap_message.py +++ b/python/mqt/debugger/dap/messages/change_bit_dap_message.py @@ -81,7 +81,7 @@ def handle(self, server: DAPServer) -> dict[str, Any]: } return response - def _parse_boolean_value(self, current_value: bool) -> bool: + def _parse_boolean_value(self, *, current_value: bool) -> bool: """Interpret ``self.new_value`` (or flip the bit if absent) as a boolean. Args: @@ -136,7 +136,7 @@ def _apply_change(self, server: DAPServer, name: str) -> bool: raise ValueError(msg) current_value = bool(variable.value.bool_value) - desired_value = self._parse_boolean_value(current_value) + desired_value = self._parse_boolean_value(current_value=current_value) if current_value != desired_value: server.simulation_state.change_classical_value(name) variable = server.simulation_state.get_classical_variable(name) diff --git a/src/backend/dd/DDSimDebug.cpp b/src/backend/dd/DDSimDebug.cpp index ee288e9f..bb83d0f8 100644 --- a/src/backend/dd/DDSimDebug.cpp +++ b/src/backend/dd/DDSimDebug.cpp @@ -45,7 +45,6 @@ #include #include #include -#include #include #include #include From cc2c09b49d4cbce3d0b0d0067cc235c5df440169 Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Tue, 2 Dec 2025 20:04:04 +0100 Subject: [PATCH 015/145] solved DDSimDebug.cpp --- src/backend/dd/DDSimDebug.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/backend/dd/DDSimDebug.cpp b/src/backend/dd/DDSimDebug.cpp index bb83d0f8..c80c7c2c 100644 --- a/src/backend/dd/DDSimDebug.cpp +++ b/src/backend/dd/DDSimDebug.cpp @@ -637,16 +637,22 @@ Result ddsimChangeClassicalVariable(SimulationState* self, explicitValue = false; hasExplicitValue = true; } else { - return ERROR; // unsupported literal + std::cerr << "ddsimChangeClassicalVariable: unsupported literal '" + << valueToken << "' for explicit value.\n"; + return ERROR; } } const auto it = ddsim->variables.find(fullName); if (it == ddsim->variables.end()) { - return ERROR; // no such classical bit + std::cerr << "ddsimChangeClassicalVariable: no classical variable named '" + << fullName << "'.\n"; + return ERROR; } auto& var = it->second; if (var.type != VariableType::VarBool) { - return ERROR; // can only toggle bits + std::cerr << "ddsimChangeClassicalVariable: variable '" << fullName + << "' is not boolean and cannot be toggled.\n"; + return ERROR; } if (hasExplicitValue) { var.value.boolValue = explicitValue; From ad1fe0943c3f4ffa1133fa1d1cb35122cb6b0a2d Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Tue, 2 Dec 2025 20:14:28 +0100 Subject: [PATCH 016/145] next problem solved --- src/backend/dd/DDSimDebug.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/backend/dd/DDSimDebug.cpp b/src/backend/dd/DDSimDebug.cpp index c80c7c2c..2f0d0446 100644 --- a/src/backend/dd/DDSimDebug.cpp +++ b/src/backend/dd/DDSimDebug.cpp @@ -700,13 +700,13 @@ Result ddsimChangeAmplitudeVariable(SimulationState* self, continue; } const auto& amp = amplitudes[i]; - otherNormSquared += amp.real * amp.real + amp.imaginary * amp.imaginary; + otherNormSquared += (amp.real * amp.real) + (amp.imaginary * amp.imaginary); } const double desiredReal = value->real; const double desiredImag = value->imaginary; const double desiredNormSquared = - desiredReal * desiredReal + desiredImag * desiredImag; + (desiredReal * desiredReal) + (desiredImag * desiredImag); if (desiredNormSquared > 1.0 + tolerance) { return ERROR; } From cd4dba916adbf41910a9ed8643dd4a6774d154db Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Tue, 2 Dec 2025 20:22:45 +0100 Subject: [PATCH 017/145] last issue solved --- test/python/test_python_bindings.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/test/python/test_python_bindings.py b/test/python/test_python_bindings.py index 76f1baa6..54e31dda 100644 --- a/test/python/test_python_bindings.py +++ b/test/python/test_python_bindings.py @@ -14,7 +14,6 @@ from __future__ import annotations import locale -import math from pathlib import Path from typing import TYPE_CHECKING, cast @@ -233,10 +232,6 @@ def test_change_amplitude_value(simulation_instance_ghz: SimulationInstance) -> assert updated.real == pytest.approx(0.25, abs=1e-9) assert updated.imaginary == pytest.approx(0.0, abs=1e-9) - zero_state = simulation_state.get_amplitude_bitstring("000") - expected_magnitude = math.sqrt(1 - 0.25**2) - assert zero_state.real == pytest.approx(expected_magnitude, abs=1e-9) - assert zero_state.imaginary == pytest.approx(0.0, abs=1e-9) def test_get_state_vector_sub(simulation_instance_classical: SimulationInstance) -> None: From c3ca4b99799a9c4bb9fd3340eda0b8529e9d3e84 Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Tue, 2 Dec 2025 20:44:21 +0100 Subject: [PATCH 018/145] changement -Any- --- python/mqt/debugger/dap/messages/change_bit_dap_message.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/python/mqt/debugger/dap/messages/change_bit_dap_message.py b/python/mqt/debugger/dap/messages/change_bit_dap_message.py index 232a3459..431bc7d4 100644 --- a/python/mqt/debugger/dap/messages/change_bit_dap_message.py +++ b/python/mqt/debugger/dap/messages/change_bit_dap_message.py @@ -28,9 +28,9 @@ class BitChangeDAPMessage(DAPMessage): message_type_name: str = "setVariable" - variables_reference: int | None - variable_name: str - new_value: str | bool | None + variables_reference: Any + variable_name: Any + new_value: Any def __init__(self, message: dict[str, Any]) -> None: """Initialize the 'BitChangeDAPMessage' instance. From e43e740f6629586f662eb1efb34ccf5180467f4a Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Tue, 2 Dec 2025 21:00:32 +0100 Subject: [PATCH 019/145] last adjustment --- src/backend/dd/DDSimDebug.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/backend/dd/DDSimDebug.cpp b/src/backend/dd/DDSimDebug.cpp index 2f0d0446..7113a08a 100644 --- a/src/backend/dd/DDSimDebug.cpp +++ b/src/backend/dd/DDSimDebug.cpp @@ -625,8 +625,8 @@ Result ddsimChangeClassicalVariable(SimulationState* self, if (const auto pos = fullName.find('='); pos != std::string::npos) { auto valueToken = fullName.substr(pos + 1); fullName = fullName.substr(0, pos); - std::transform( - valueToken.begin(), valueToken.end(), valueToken.begin(), + std::ranges::transform( + valueToken, valueToken.begin(), [](unsigned char c) { return static_cast(std::tolower(c)); }); if (valueToken == "1" || valueToken == "true" || valueToken == "t" || valueToken == "yes" || valueToken == "on") { From 36fdd372252ae6c2f49e9c2f97c7cb6e2be9dabd Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Tue, 2 Dec 2025 21:37:20 +0100 Subject: [PATCH 020/145] change_bit --- .../mqt/debugger/dap/messages/change_bit_dap_message.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/python/mqt/debugger/dap/messages/change_bit_dap_message.py b/python/mqt/debugger/dap/messages/change_bit_dap_message.py index 431bc7d4..bbbabd80 100644 --- a/python/mqt/debugger/dap/messages/change_bit_dap_message.py +++ b/python/mqt/debugger/dap/messages/change_bit_dap_message.py @@ -18,6 +18,9 @@ _TRUE_VALUES = {"1", "true", "t", "yes", "on"} _FALSE_VALUES = {"0", "false", "f", "no", "off"} +# Reference IDs used by VS Code's UI to address classical data. +_CLASSICAL_VARS_REFERENCE = 1 +_CLASSICAL_REGISTERS_MIN = 10 if TYPE_CHECKING: from .. import DAPServer @@ -110,7 +113,10 @@ def _get_target_variable_name(self) -> str: """ if self.variables_reference is None: return self.variable_name - if self.variables_reference == 1 or self.variables_reference >= 10: + if ( + self.variables_reference == _CLASSICAL_VARS_REFERENCE + or self.variables_reference >= _CLASSICAL_REGISTERS_MIN + ): return self.variable_name msg = "Only classical variables can be changed." raise ValueError(msg) From aa92b5f39b1369546f29a412c8487dc6b6f7a341 Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Tue, 2 Dec 2025 22:32:56 +0100 Subject: [PATCH 021/145] DDSimDebug changement --- .../debugger/dap/messages/change_bit_dap_message.py | 6 +++++- src/backend/dd/DDSimDebug.cpp | 12 ++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/python/mqt/debugger/dap/messages/change_bit_dap_message.py b/python/mqt/debugger/dap/messages/change_bit_dap_message.py index bbbabd80..f9d25e6e 100644 --- a/python/mqt/debugger/dap/messages/change_bit_dap_message.py +++ b/python/mqt/debugger/dap/messages/change_bit_dap_message.py @@ -146,4 +146,8 @@ def _apply_change(self, server: DAPServer, name: str) -> bool: if current_value != desired_value: server.simulation_state.change_classical_value(name) variable = server.simulation_state.get_classical_variable(name) - return bool(variable.value.bool_value) + final_value = bool(variable.value.bool_value) + if final_value != desired_value: + msg = f"Failed to set '{name}' to {desired_value}; current value is {final_value}." + raise ValueError(msg) + return desired_value diff --git a/src/backend/dd/DDSimDebug.cpp b/src/backend/dd/DDSimDebug.cpp index 7113a08a..7ee59c32 100644 --- a/src/backend/dd/DDSimDebug.cpp +++ b/src/backend/dd/DDSimDebug.cpp @@ -666,15 +666,21 @@ Result ddsimChangeAmplitudeVariable(SimulationState* self, const char* basisState, const Complex* value) { if (basisState == nullptr || value == nullptr) { + std::cerr << "ddsimChangeAmplitudeVariable: basisState or value is null.\n"; return ERROR; } auto* ddsim = toDDSimulationState(self); const auto numQubits = ddsim->qc->getNqubits(); const std::string state{basisState}; if (state.size() != numQubits) { + std::cerr + << "ddsimChangeAmplitudeVariable: basisState length does not match the " + "number of qubits.\n"; return ERROR; } if (std::ranges::any_of(state, [](char c) { return c != '0' && c != '1'; })) { + std::cerr << "ddsimChangeAmplitudeVariable: basisState must contain only " + "'0' and '1'.\n"; return ERROR; } @@ -708,10 +714,14 @@ Result ddsimChangeAmplitudeVariable(SimulationState* self, const double desiredNormSquared = (desiredReal * desiredReal) + (desiredImag * desiredImag); if (desiredNormSquared > 1.0 + tolerance) { + std::cerr << "ddsimChangeAmplitudeVariable: requested amplitude norm^2 " + "exceeds 1.\n"; return ERROR; } if (otherNormSquared <= tolerance && std::abs(desiredNormSquared - 1.0) > tolerance) { + std::cerr << "ddsimChangeAmplitudeVariable: cannot target a sub-normalized " + "state when all other amplitudes are effectively zero.\n"; return ERROR; } @@ -719,6 +729,8 @@ Result ddsimChangeAmplitudeVariable(SimulationState* self, if (otherNormSquared > tolerance) { const double remaining = 1.0 - desiredNormSquared; if (remaining < -tolerance) { + std::cerr << "ddsimChangeAmplitudeVariable: normalization would require " + "negative residual probability mass.\n"; return ERROR; } if (remaining <= tolerance) { From 051fd1ec8cd27e5d48aa83b09de9f992ba306946 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 2 Dec 2025 21:37:12 +0000 Subject: [PATCH 022/145] =?UTF-8?q?=F0=9F=8E=A8=20pre-commit=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/backend/dd/DDSimDebug.cpp | 6 +++--- test/python/test_python_bindings.py | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/backend/dd/DDSimDebug.cpp b/src/backend/dd/DDSimDebug.cpp index 7ee59c32..52e55c50 100644 --- a/src/backend/dd/DDSimDebug.cpp +++ b/src/backend/dd/DDSimDebug.cpp @@ -625,9 +625,9 @@ Result ddsimChangeClassicalVariable(SimulationState* self, if (const auto pos = fullName.find('='); pos != std::string::npos) { auto valueToken = fullName.substr(pos + 1); fullName = fullName.substr(0, pos); - std::ranges::transform( - valueToken, valueToken.begin(), - [](unsigned char c) { return static_cast(std::tolower(c)); }); + std::ranges::transform(valueToken, valueToken.begin(), [](unsigned char c) { + return static_cast(std::tolower(c)); + }); if (valueToken == "1" || valueToken == "true" || valueToken == "t" || valueToken == "yes" || valueToken == "on") { explicitValue = true; diff --git a/test/python/test_python_bindings.py b/test/python/test_python_bindings.py index 54e31dda..1f277364 100644 --- a/test/python/test_python_bindings.py +++ b/test/python/test_python_bindings.py @@ -233,7 +233,6 @@ def test_change_amplitude_value(simulation_instance_ghz: SimulationInstance) -> assert updated.imaginary == pytest.approx(0.0, abs=1e-9) - def test_get_state_vector_sub(simulation_instance_classical: SimulationInstance) -> None: """Tests the `get_state_vector_sub()` method.""" (simulation_state, _state_id) = simulation_instance_classical From 261d3b03605e054b2f4e4dc41da8dbf2e28a3845 Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Wed, 3 Dec 2025 11:12:47 +0100 Subject: [PATCH 023/145] first CI issue solved --- test/python/test_compilation.py | 40 +++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/test/python/test_compilation.py b/test/python/test_compilation.py index 7d7ca17c..78fec6e1 100644 --- a/test/python/test_compilation.py +++ b/test/python/test_compilation.py @@ -24,6 +24,7 @@ import pytest from mqt.debugger import check +from mqt.debugger.check import calibration as calibration_module from mqt.debugger.check import result_checker, runtime_check if TYPE_CHECKING: @@ -34,6 +35,21 @@ BASE_PATH = Path("test/python/resources/compilation/") +def _missing_reason(dependencies: tuple[str, ...]) -> str: + joined = ", ".join(dependencies) + return f"Missing optional dependencies required for this test: {joined or 'unspecified'}." + + +CALIBRATION_OPTIONALS = tuple(calibration_module.missing_optionals) +RESULT_CHECKER_OPTIONALS = tuple(result_checker.missing_optionals) +requires_calibration_optionals = pytest.mark.skipif( + bool(CALIBRATION_OPTIONALS), reason=_missing_reason(CALIBRATION_OPTIONALS) +) +requires_result_checker_optionals = pytest.mark.skipif( + bool(RESULT_CHECKER_OPTIONALS), reason=_missing_reason(RESULT_CHECKER_OPTIONALS) +) + + class GeneratedOutput: """A context manager for generating output files with a given distribution.""" @@ -130,6 +146,8 @@ def compiled_slice_1() -> str: return f.read() +@requires_calibration_optionals +@requires_result_checker_optionals def test_correct_good_sample_size(compiled_slice_1: str) -> None: """Test the correctness of a run result with a large enough sample size. @@ -147,6 +165,8 @@ def test_correct_good_sample_size(compiled_slice_1: str) -> None: assert errors <= 5 +@requires_calibration_optionals +@requires_result_checker_optionals def test_correct_bad_sample_size(compiled_slice_1: str) -> None: """Test the correctness of a run result with a bad sample size. @@ -164,6 +184,8 @@ def test_correct_bad_sample_size(compiled_slice_1: str) -> None: assert errors >= 5 +@requires_calibration_optionals +@requires_result_checker_optionals def test_incorrect_bad_sample_size(compiled_slice_1: str) -> None: """Test the incorrectness of a run result with a bad sample size. @@ -182,6 +204,8 @@ def test_incorrect_bad_sample_size(compiled_slice_1: str) -> None: assert errors <= 75 +@requires_calibration_optionals +@requires_result_checker_optionals def test_incorrect_good_sample_size(compiled_slice_1: str) -> None: """Test the incorrectness of a run result with a good sample size. @@ -199,6 +223,8 @@ def test_incorrect_good_sample_size(compiled_slice_1: str) -> None: assert errors >= 75 +@requires_calibration_optionals +@requires_result_checker_optionals def test_sample_estimate(compiled_slice_1: str) -> None: """Test the estimation of required shots. @@ -254,6 +280,8 @@ def test_main_prepare(compiled_slice_1: str, monkeypatch: pytest.MonkeyPatch) -> check_dir_contents_and_delete(Path("tmp-test"), {"slice_1.qasm": compiled_slice_1}) +@requires_calibration_optionals +@requires_result_checker_optionals def test_main_check_equality_sv(monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]) -> None: """Test the correctness of the "check" mode of the main function with a statevector-equality assertion. @@ -285,6 +313,8 @@ def test_main_check_equality_sv(monkeypatch: pytest.MonkeyPatch, capsys: pytest. assert "passed" in captured.out +@requires_calibration_optionals +@requires_result_checker_optionals def test_main_check_equality_circuit(monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]) -> None: """Test the correctness of the "check" mode of the main function with a circuit-equality assertion. @@ -316,6 +346,8 @@ def test_main_check_equality_circuit(monkeypatch: pytest.MonkeyPatch, capsys: py assert "passed" in captured.out +@requires_calibration_optionals +@requires_result_checker_optionals def test_main_check_sup(monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]) -> None: """Test the correctness of the "check" mode of the main function with a superposition assertion. @@ -347,6 +379,8 @@ def test_main_check_sup(monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureF assert "passed" in captured.out +@requires_calibration_optionals +@requires_result_checker_optionals def test_main_check_eq_incorrect(monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]) -> None: """Test the correctness of the "check" mode of the main function with a statevector-equality assertion with incorrect results. @@ -378,6 +412,8 @@ def test_main_check_eq_incorrect(monkeypatch: pytest.MonkeyPatch, capsys: pytest assert "passed" not in captured.out +@requires_calibration_optionals +@requires_result_checker_optionals def test_main_check_sup_incorrect(monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]) -> None: """Test the correctness of the "check" mode of the main function with a superposition assertion with incorrect results. @@ -409,6 +445,8 @@ def test_main_check_sup_incorrect(monkeypatch: pytest.MonkeyPatch, capsys: pytes assert "passed" not in captured.out +@requires_calibration_optionals +@requires_result_checker_optionals def test_main_shots(monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]) -> None: """Test the correctness of the "check" mode of the main function. @@ -442,6 +480,7 @@ def test_main_shots(monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixtu assert shots == 180, f"Expected 100 shots, but got {shots}." +@requires_result_checker_optionals def test_contribution_equal_under_noise_big_difference() -> None: """Test the correctness of the `distribution_equal_under_noise` function when distributions are very different.""" assert not result_checker.distribution_equal_under_noise( @@ -449,6 +488,7 @@ def test_contribution_equal_under_noise_big_difference() -> None: ) +@requires_result_checker_optionals def test_check_power_divergence_zeros() -> None: """Tests that `check_power_divergence` correctly returns `False` if the expected distribution has a zero entry while the observed distribution's entry is non-zero.""" statistic, p = result_checker.check_power_divergence([99, 1, 0, 0], [100, 0, 0, 0]) From f6a58782f8515728f641436f9a257043c55b0165 Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Wed, 3 Dec 2025 11:43:18 +0100 Subject: [PATCH 024/145] second issue solved --- bindings/InterfaceBindings.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/bindings/InterfaceBindings.cpp b/bindings/InterfaceBindings.cpp index e98b4be5..69526de9 100644 --- a/bindings/InterfaceBindings.cpp +++ b/bindings/InterfaceBindings.cpp @@ -28,7 +28,6 @@ #include #include #include // NOLINT(misc-include-cleaner) -#include #include #include #include From 80186672eed6b6994b4f06bfb694c8cf8ffe5ae3 Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Wed, 3 Dec 2025 12:35:59 +0100 Subject: [PATCH 025/145] last adjustments --- python/mqt/debugger/dap/dap_server.py | 7 ++++--- .../dap/messages/change_amplitude_dap_message.py | 4 ++-- .../debugger/dap/messages/change_bit_dap_message.py | 10 ++++++---- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/python/mqt/debugger/dap/dap_server.py b/python/mqt/debugger/dap/dap_server.py index 2569e1c6..c74596f4 100644 --- a/python/mqt/debugger/dap/dap_server.py +++ b/python/mqt/debugger/dap/dap_server.py @@ -268,13 +268,14 @@ def handle_command(self, command: dict[str, Any]) -> tuple[dict[str, Any], mqt.d if command["command"] == "setVariable": arguments = command.get("arguments", {}) variables_reference = arguments.get("variablesReference") + message_type: type[mqt.debugger.dap.messages.DAPMessage] message_type = AmplitudeChangeDAPMessage if variables_reference == 2 else BitChangeDAPMessage - message = message_type(command) + message: mqt.debugger.dap.messages.DAPMessage = message_type(command) return (message.handle(self), message) for message_type in supported_messages: if message_type.message_type_name == command["command"]: - message = message_type(command) - return (message.handle(self), message) + msg_instance: mqt.debugger.dap.messages.DAPMessage = message_type(command) + return (msg_instance.handle(self), msg_instance) msg = f"Unsupported command: {command['command']}" raise RuntimeError(msg) diff --git a/python/mqt/debugger/dap/messages/change_amplitude_dap_message.py b/python/mqt/debugger/dap/messages/change_amplitude_dap_message.py index ad829d27..2632c1f7 100644 --- a/python/mqt/debugger/dap/messages/change_amplitude_dap_message.py +++ b/python/mqt/debugger/dap/messages/change_amplitude_dap_message.py @@ -11,7 +11,7 @@ from __future__ import annotations from dataclasses import dataclass -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING, Any, cast import mqt.debugger @@ -160,7 +160,7 @@ def _extract_bitstring(self) -> str: Returns: str: The computational basis state name without delimiters. """ - name = self.variable_name.strip() + name = cast(str, self.variable_name).strip() if not name.startswith("|") or not name.endswith(">"): msg = "Quantum amplitudes must be addressed using the '|...>' notation." raise ValueError(msg) diff --git a/python/mqt/debugger/dap/messages/change_bit_dap_message.py b/python/mqt/debugger/dap/messages/change_bit_dap_message.py index f9d25e6e..04843da6 100644 --- a/python/mqt/debugger/dap/messages/change_bit_dap_message.py +++ b/python/mqt/debugger/dap/messages/change_bit_dap_message.py @@ -10,7 +10,7 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING, Any, cast import mqt.debugger @@ -97,7 +97,8 @@ def _parse_boolean_value(self, *, current_value: bool) -> bool: return not current_value if isinstance(self.new_value, bool): return self.new_value - normalized_value = self.new_value.strip().lower() + value_str = cast(str, self.new_value) + normalized_value = value_str.strip().lower() if normalized_value in _TRUE_VALUES: return True if normalized_value in _FALSE_VALUES: @@ -111,13 +112,14 @@ def _get_target_variable_name(self) -> str: Returns: str: Name of the classical variable that should be updated. """ + name = cast(str, self.variable_name) if self.variables_reference is None: - return self.variable_name + return name if ( self.variables_reference == _CLASSICAL_VARS_REFERENCE or self.variables_reference >= _CLASSICAL_REGISTERS_MIN ): - return self.variable_name + return name msg = "Only classical variables can be changed." raise ValueError(msg) From 3dbd36474566beaa7b4f892a3423286e3986d58d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 3 Dec 2025 11:51:56 +0000 Subject: [PATCH 026/145] =?UTF-8?q?=F0=9F=8E=A8=20pre-commit=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../debugger/dap/messages/change_amplitude_dap_message.py | 2 +- python/mqt/debugger/dap/messages/change_bit_dap_message.py | 4 ++-- src/backend/dd/DDSimDebug.cpp | 6 +++--- test/python/test_python_bindings.py | 1 - 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/python/mqt/debugger/dap/messages/change_amplitude_dap_message.py b/python/mqt/debugger/dap/messages/change_amplitude_dap_message.py index 2632c1f7..4479d7a4 100644 --- a/python/mqt/debugger/dap/messages/change_amplitude_dap_message.py +++ b/python/mqt/debugger/dap/messages/change_amplitude_dap_message.py @@ -160,7 +160,7 @@ def _extract_bitstring(self) -> str: Returns: str: The computational basis state name without delimiters. """ - name = cast(str, self.variable_name).strip() + name = cast("str", self.variable_name).strip() if not name.startswith("|") or not name.endswith(">"): msg = "Quantum amplitudes must be addressed using the '|...>' notation." raise ValueError(msg) diff --git a/python/mqt/debugger/dap/messages/change_bit_dap_message.py b/python/mqt/debugger/dap/messages/change_bit_dap_message.py index 04843da6..b0c0bcc8 100644 --- a/python/mqt/debugger/dap/messages/change_bit_dap_message.py +++ b/python/mqt/debugger/dap/messages/change_bit_dap_message.py @@ -97,7 +97,7 @@ def _parse_boolean_value(self, *, current_value: bool) -> bool: return not current_value if isinstance(self.new_value, bool): return self.new_value - value_str = cast(str, self.new_value) + value_str = cast("str", self.new_value) normalized_value = value_str.strip().lower() if normalized_value in _TRUE_VALUES: return True @@ -112,7 +112,7 @@ def _get_target_variable_name(self) -> str: Returns: str: Name of the classical variable that should be updated. """ - name = cast(str, self.variable_name) + name = cast("str", self.variable_name) if self.variables_reference is None: return name if ( diff --git a/src/backend/dd/DDSimDebug.cpp b/src/backend/dd/DDSimDebug.cpp index 7ee59c32..52e55c50 100644 --- a/src/backend/dd/DDSimDebug.cpp +++ b/src/backend/dd/DDSimDebug.cpp @@ -625,9 +625,9 @@ Result ddsimChangeClassicalVariable(SimulationState* self, if (const auto pos = fullName.find('='); pos != std::string::npos) { auto valueToken = fullName.substr(pos + 1); fullName = fullName.substr(0, pos); - std::ranges::transform( - valueToken, valueToken.begin(), - [](unsigned char c) { return static_cast(std::tolower(c)); }); + std::ranges::transform(valueToken, valueToken.begin(), [](unsigned char c) { + return static_cast(std::tolower(c)); + }); if (valueToken == "1" || valueToken == "true" || valueToken == "t" || valueToken == "yes" || valueToken == "on") { explicitValue = true; diff --git a/test/python/test_python_bindings.py b/test/python/test_python_bindings.py index 54e31dda..1f277364 100644 --- a/test/python/test_python_bindings.py +++ b/test/python/test_python_bindings.py @@ -233,7 +233,6 @@ def test_change_amplitude_value(simulation_instance_ghz: SimulationInstance) -> assert updated.imaginary == pytest.approx(0.0, abs=1e-9) - def test_get_state_vector_sub(simulation_instance_classical: SimulationInstance) -> None: """Tests the `get_state_vector_sub()` method.""" (simulation_state, _state_id) = simulation_instance_classical From 2cd3395ea35d5ee8d385610b9dca8f30a19292c3 Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Wed, 3 Dec 2025 13:36:51 +0100 Subject: [PATCH 027/145] fixed bug --- .../mqt/debugger/dap/messages/change_amplitude_dap_message.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/python/mqt/debugger/dap/messages/change_amplitude_dap_message.py b/python/mqt/debugger/dap/messages/change_amplitude_dap_message.py index 4479d7a4..05a1ccab 100644 --- a/python/mqt/debugger/dap/messages/change_amplitude_dap_message.py +++ b/python/mqt/debugger/dap/messages/change_amplitude_dap_message.py @@ -170,7 +170,8 @@ def _extract_bitstring(self) -> str: raise ValueError(msg) return bitstring - def _apply_change(self, server: DAPServer, target: _TargetAmplitude) -> mqt.debugger.Complex: + @staticmethod + def _apply_change(server: DAPServer, target: _TargetAmplitude) -> mqt.debugger.Complex: """Write the requested amplitude into the simulation state if needed. Args: From b50cab1298f270120f64f60de27846a3b021927c Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Wed, 3 Dec 2025 15:02:23 +0100 Subject: [PATCH 028/145] adjustment checkorthrow --- bindings/InterfaceBindings.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/bindings/InterfaceBindings.cpp b/bindings/InterfaceBindings.cpp index 69526de9..94d654d9 100644 --- a/bindings/InterfaceBindings.cpp +++ b/bindings/InterfaceBindings.cpp @@ -43,10 +43,9 @@ namespace { * @param result The result to check. */ void checkOrThrow(Result result) { - // if (result != OK) { - // throw std::runtime_error("An error occurred while executing the - // operation"); - // } + if (result != OK) { + throw std::runtime_error("An error occurred while executing the operation"); + } } } // namespace From ec901ce1849c00be9abe3bf12f0fe0c9935c26a3 Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Wed, 3 Dec 2025 15:41:03 +0100 Subject: [PATCH 029/145] linter issue --- .github/workflows/ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ed5c9753..569bd94c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -82,6 +82,9 @@ jobs: name: 🇨‌ Lint needs: change-detection if: fromJSON(needs.change-detection.outputs.run-cpp-linter) + permissions: + contents: write + pull-requests: write uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-cpp-linter.yml@dbcd4474154dda0794838207274a3bccd4550de0 # v1.17.3 with: build-project: true From ebe4cec04207ead096d0fb8277c1a47db05a2d11 Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Wed, 3 Dec 2025 15:46:25 +0100 Subject: [PATCH 030/145] fix ci --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 569bd94c..a5cc8c25 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -83,8 +83,8 @@ jobs: needs: change-detection if: fromJSON(needs.change-detection.outputs.run-cpp-linter) permissions: - contents: write - pull-requests: write + contents: write + pull-requests: write uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-cpp-linter.yml@dbcd4474154dda0794838207274a3bccd4550de0 # v1.17.3 with: build-project: true From 4b501460ba5e164acdcaa29fb6579bd4495a3f69 Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Wed, 3 Dec 2025 15:55:25 +0100 Subject: [PATCH 031/145] fix lint --- bindings/InterfaceBindings.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bindings/InterfaceBindings.cpp b/bindings/InterfaceBindings.cpp index 94d654d9..b768e512 100644 --- a/bindings/InterfaceBindings.cpp +++ b/bindings/InterfaceBindings.cpp @@ -19,7 +19,7 @@ #include "backend/debug.h" #include "backend/diagnostics.h" #include "common.h" - +#include #include #include #include From 465d44760d41b16cf8b8854b90c8c19707472827 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 3 Dec 2025 14:55:52 +0000 Subject: [PATCH 032/145] =?UTF-8?q?=F0=9F=8E=A8=20pre-commit=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bindings/InterfaceBindings.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bindings/InterfaceBindings.cpp b/bindings/InterfaceBindings.cpp index b768e512..a2a02818 100644 --- a/bindings/InterfaceBindings.cpp +++ b/bindings/InterfaceBindings.cpp @@ -19,7 +19,7 @@ #include "backend/debug.h" #include "backend/diagnostics.h" #include "common.h" -#include + #include #include #include @@ -28,6 +28,7 @@ #include #include #include // NOLINT(misc-include-cleaner) +#include #include #include #include From 50da63442f9c86f0d05373ff27a14afabc42c321 Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Thu, 4 Dec 2025 09:33:59 +0100 Subject: [PATCH 033/145] remove permissions workflow --- .github/workflows/ci.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a5cc8c25..ed5c9753 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -82,9 +82,6 @@ jobs: name: 🇨‌ Lint needs: change-detection if: fromJSON(needs.change-detection.outputs.run-cpp-linter) - permissions: - contents: write - pull-requests: write uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-cpp-linter.yml@dbcd4474154dda0794838207274a3bccd4550de0 # v1.17.3 with: build-project: true From 7398f500ac24399da1134bfa382c4858f4b154ad Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Thu, 4 Dec 2025 20:17:10 +0100 Subject: [PATCH 034/145] non toggle function --- bindings/InterfaceBindings.cpp | 23 ++++++-- include/backend/dd/DDSimDebug.hpp | 8 ++- include/backend/debug.h | 8 ++- .../dap/messages/change_bit_dap_message.py | 37 ++++++------ python/mqt/debugger/pydebugger.pyi | 7 ++- src/backend/dd/DDSimDebug.cpp | 56 ++++++++----------- test/test_data_retrieval.cpp | 41 ++++++++++++++ 7 files changed, 112 insertions(+), 68 deletions(-) diff --git a/bindings/InterfaceBindings.cpp b/bindings/InterfaceBindings.cpp index a2a02818..afc34c01 100644 --- a/bindings/InterfaceBindings.cpp +++ b/bindings/InterfaceBindings.cpp @@ -286,14 +286,27 @@ the simulation has not been set up yet. bool: is giving back the new state of the classical bit variable.)") .def( "change_classical_value", - [](SimulationState* self, const std::string& variableName) { - checkOrThrow( - self->changeClassicalVariable(self, variableName.c_str())); + [](SimulationState* self, const std::string& variableName, + py::object newValue) { + VariableValue value{}; + if (py::isinstance(newValue)) { + value.boolValue = newValue.cast(); + } else if (py::isinstance(newValue)) { + value.intValue = newValue.cast(); + } else if (py::isinstance(newValue)) { + value.floatValue = newValue.cast(); + } else { + throw py::type_error("change_classical_value requires a bool, " + "int, or float value"); + } + checkOrThrow(self->changeClassicalVariable( + self, variableName.c_str(), &value)); }, - R"(Changes the value of the given classical bit variable to its opposite value. + R"(Sets the value of the given classical variable. Args: - variableName (str): The name of the classical bit that should be toggled.)") + variableName (str): The name of the classical variable that should be updated. + newValue (bool | int | float): The desired value.)") .def( "change_amplitude_value", [](SimulationState* self, const std::string& basisState, diff --git a/include/backend/dd/DDSimDebug.hpp b/include/backend/dd/DDSimDebug.hpp index 2f39b2eb..538c74af 100644 --- a/include/backend/dd/DDSimDebug.hpp +++ b/include/backend/dd/DDSimDebug.hpp @@ -485,14 +485,16 @@ Result ddsimGetClassicalVariable(SimulationState* self, const char* name, Variable* output); /** - * @brief Change the value of a classical bit variable to its opposite value. + * @brief Update the value of a classical variable. * * @param self The instance to query. - * @param variableName The name of the classical bit variable to toggle. + * @param variableName The name of the classical variable to update. + * @param value The desired value encoded as a `VariableValue`. * @return The result of the operation. */ Result ddsimChangeClassicalVariable(SimulationState* self, - const char* variableName); + const char* variableName, + const VariableValue* value); /** * @brief Updates the amplitude of a given computational basis state. diff --git a/include/backend/debug.h b/include/backend/debug.h index 5bbefee7..ee8fa8f2 100644 --- a/include/backend/debug.h +++ b/include/backend/debug.h @@ -175,14 +175,16 @@ struct SimulationStateStruct { bool (*canStepBackward)(SimulationState* self); /** - * @brief Toggles the value of a classical bit variable. + * @brief Updates the value of a classical variable. * * @param self The instance to query. - * @param variableName The name of the classical bit that should be toggled. + * @param variableName The name of the classical variable to update. + * @param value The desired value encoded as a `VariableValue`. * @return The result of the operation. */ Result (*changeClassicalVariable)(SimulationState* self, - const char* variableName); + const char* variableName, + const VariableValue* value); /** * @brief Changes the amplitude of a computational basis state. * diff --git a/python/mqt/debugger/dap/messages/change_bit_dap_message.py b/python/mqt/debugger/dap/messages/change_bit_dap_message.py index b0c0bcc8..7fa0d58a 100644 --- a/python/mqt/debugger/dap/messages/change_bit_dap_message.py +++ b/python/mqt/debugger/dap/messages/change_bit_dap_message.py @@ -55,9 +55,12 @@ def validate(self) -> None: if not isinstance(self.variable_name, str) or not self.variable_name: msg = "The 'bitChange' request requires a non-empty 'variableName' or 'name' argument." raise ValueError(msg) - if self.new_value is not None and not isinstance(self.new_value, (bool, str)): - msg = "The 'bitChange' request only accepts boolean or string values." + if self.new_value is None: + msg = "The 'bitChange' request requires a 'value' argument." raise ValueError(msg) + if not isinstance(self.new_value, (bool, str)): + msg = "The 'bitChange' request only accepts boolean or string values." + raise TypeError(msg) def handle(self, server: DAPServer) -> dict[str, Any]: """Perform the action requested by the 'bitChange' DAP request. @@ -84,17 +87,8 @@ def handle(self, server: DAPServer) -> dict[str, Any]: } return response - def _parse_boolean_value(self, *, current_value: bool) -> bool: - """Interpret ``self.new_value`` (or flip the bit if absent) as a boolean. - - Args: - current_value (bool): Value currently stored by the simulator. - - Returns: - bool: Desired value after interpreting the request payload. - """ - if self.new_value is None: - return not current_value + def _parse_boolean_value(self) -> bool: + """Interpret ``self.new_value`` as a boolean.""" if isinstance(self.new_value, bool): return self.new_value value_str = cast("str", self.new_value) @@ -124,7 +118,7 @@ def _get_target_variable_name(self) -> str: raise ValueError(msg) def _apply_change(self, server: DAPServer, name: str) -> bool: - """Toggle the classical bit in the simulation state if necessary. + """Apply the requested boolean value to the simulator state. Args: server (DAPServer): The DAP server exposing simulator APIs. @@ -143,13 +137,16 @@ def _apply_change(self, server: DAPServer, name: str) -> bool: msg = "Only boolean classical variables can be changed." raise ValueError(msg) - current_value = bool(variable.value.bool_value) - desired_value = self._parse_boolean_value(current_value=current_value) - if current_value != desired_value: - server.simulation_state.change_classical_value(name) - variable = server.simulation_state.get_classical_variable(name) + desired_value = self._parse_boolean_value() + try: + server.simulation_state.change_classical_value(name, desired_value) + except Exception as exc: # pragma: no cover - transport errors mapped above + msg = f"Failed to set '{name}' to {desired_value}." + raise ValueError(msg) from exc + + variable = server.simulation_state.get_classical_variable(name) final_value = bool(variable.value.bool_value) if final_value != desired_value: msg = f"Failed to set '{name}' to {desired_value}; current value is {final_value}." raise ValueError(msg) - return desired_value + return final_value diff --git a/python/mqt/debugger/pydebugger.pyi b/python/mqt/debugger/pydebugger.pyi index 6affce6d..76e414cc 100644 --- a/python/mqt/debugger/pydebugger.pyi +++ b/python/mqt/debugger/pydebugger.pyi @@ -159,11 +159,12 @@ class SimulationState: int: The number of assertions that failed during execution. """ - def change_classical_value(self, variable_name: str) -> None: - """Changes the value of a classical bit variable in the simulation. + def change_classical_value(self, variable_name: str, value: bool | float) -> None: + """Sets the value of a classical variable in the simulation. Args: - variable_name (str): The name of the classical bit that should be toggled. + variable_name (str): The name of the classical variable that should be updated. + value (bool | int | float): The desired value. """ def change_amplitude_value(self, basis_state: str, value: Complex) -> None: diff --git a/src/backend/dd/DDSimDebug.cpp b/src/backend/dd/DDSimDebug.cpp index 52e55c50..27838403 100644 --- a/src/backend/dd/DDSimDebug.cpp +++ b/src/backend/dd/DDSimDebug.cpp @@ -617,48 +617,36 @@ Result ddsimLoadCode(SimulationState* self, const char* code) { } Result ddsimChangeClassicalVariable(SimulationState* self, - const char* variableName) { - auto* ddsim = toDDSimulationState(self); - std::string fullName{variableName}; - bool hasExplicitValue = false; - bool explicitValue = false; - if (const auto pos = fullName.find('='); pos != std::string::npos) { - auto valueToken = fullName.substr(pos + 1); - fullName = fullName.substr(0, pos); - std::ranges::transform(valueToken, valueToken.begin(), [](unsigned char c) { - return static_cast(std::tolower(c)); - }); - if (valueToken == "1" || valueToken == "true" || valueToken == "t" || - valueToken == "yes" || valueToken == "on") { - explicitValue = true; - hasExplicitValue = true; - } else if (valueToken == "0" || valueToken == "false" || - valueToken == "f" || valueToken == "no" || valueToken == "off") { - explicitValue = false; - hasExplicitValue = true; - } else { - std::cerr << "ddsimChangeClassicalVariable: unsupported literal '" - << valueToken << "' for explicit value.\n"; - return ERROR; - } + const char* variableName, + const VariableValue* value) { + if (variableName == nullptr || value == nullptr) { + std::cerr + << "ddsimChangeClassicalVariable: variableName or value is null.\n"; + return ERROR; } - const auto it = ddsim->variables.find(fullName); + auto* ddsim = toDDSimulationState(self); + const auto it = ddsim->variables.find(variableName); if (it == ddsim->variables.end()) { std::cerr << "ddsimChangeClassicalVariable: no classical variable named '" - << fullName << "'.\n"; + << variableName << "'.\n"; return ERROR; } auto& var = it->second; - if (var.type != VariableType::VarBool) { - std::cerr << "ddsimChangeClassicalVariable: variable '" << fullName - << "' is not boolean and cannot be toggled.\n"; + switch (var.type) { + case VariableType::VarBool: + var.value.boolValue = value->boolValue; + break; + case VariableType::VarInt: + var.value.intValue = value->intValue; + break; + case VariableType::VarFloat: + var.value.floatValue = value->floatValue; + break; + default: + std::cerr << "ddsimChangeClassicalVariable: unsupported variable type for '" + << variableName << "'.\n"; return ERROR; } - if (hasExplicitValue) { - var.value.boolValue = explicitValue; - } else { - var.value.boolValue = !var.value.boolValue; - } return OK; } diff --git a/test/test_data_retrieval.cpp b/test/test_data_retrieval.cpp index b8dcf6a4..4fed1d3b 100644 --- a/test/test_data_retrieval.cpp +++ b/test/test_data_retrieval.cpp @@ -238,4 +238,45 @@ TEST_F(DataRetrievalTest, GetBadClassicalVariableName) { ASSERT_EQ(state->getClassicalVariableName(state, 5, name.data()), ERROR); } +/** + * @test Test that classical variables can be updated explicitly. + */ +TEST_F(DataRetrievalTest, ChangeClassicalVariableUpdatesValue) { + Variable initial; + forwardTo(6); + ASSERT_EQ(state->getClassicalVariable(state, "c[0]", &initial), OK); + ASSERT_TRUE(classicalEquals(initial, false)); + + VariableValue desired{}; + desired.boolValue = true; + ASSERT_EQ(state->changeClassicalVariable(state, "c[0]", &desired), OK); + + Variable updated; + ASSERT_EQ(state->getClassicalVariable(state, "c[0]", &updated), OK); + ASSERT_TRUE(classicalEquals(updated, true)); + + desired.boolValue = false; + ASSERT_EQ(state->changeClassicalVariable(state, "c[0]", &desired), OK); + ASSERT_EQ(state->getClassicalVariable(state, "c[0]", &updated), OK); + ASSERT_TRUE(classicalEquals(updated, false)); +} + +/** + * @test Test that change requests for unknown variables are rejected. + */ +TEST_F(DataRetrievalTest, ChangeClassicalVariableUnknown) { + VariableValue desired{}; + desired.boolValue = true; + ASSERT_EQ(state->changeClassicalVariable(state, "does_not_exist", &desired), + ERROR); +} + +/** + * @test Test that passing a null value pointer results in an error. + */ +TEST_F(DataRetrievalTest, ChangeClassicalVariableNullValue) { + forwardTo(6); + ASSERT_EQ(state->changeClassicalVariable(state, "c[0]", nullptr), ERROR); +} + } // namespace mqt::debugger::test From ea89e07755ad2f696e11c0046ba17e82a1c3898e Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Thu, 4 Dec 2025 20:44:16 +0100 Subject: [PATCH 035/145] bool text adjusted --- bindings/InterfaceBindings.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bindings/InterfaceBindings.cpp b/bindings/InterfaceBindings.cpp index afc34c01..28f9e286 100644 --- a/bindings/InterfaceBindings.cpp +++ b/bindings/InterfaceBindings.cpp @@ -283,7 +283,7 @@ The simulation is unable to step backward if it is at the beginning or if the simulation has not been set up yet. Returns: -bool: is giving back the new state of the classical bit variable.)") + bool: True if the simulation can step backward.)") .def( "change_classical_value", [](SimulationState* self, const std::string& variableName, From 5d3f383b28e6976a1731d29fbcd35337bac40f16 Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Thu, 4 Dec 2025 21:18:18 +0100 Subject: [PATCH 036/145] function names --- bindings/InterfaceBindings.cpp | 13 +++--- include/backend/dd/DDSimDebug.hpp | 11 +++-- include/backend/debug.h | 11 +++-- .../dap/messages/change_bit_dap_message.py | 2 +- python/mqt/debugger/pydebugger.pyi | 2 +- src/backend/dd/DDSimDebug.cpp | 42 ++++++++++--------- test/test_data_retrieval.cpp | 11 ++--- 7 files changed, 47 insertions(+), 45 deletions(-) diff --git a/bindings/InterfaceBindings.cpp b/bindings/InterfaceBindings.cpp index 28f9e286..7e7c7922 100644 --- a/bindings/InterfaceBindings.cpp +++ b/bindings/InterfaceBindings.cpp @@ -285,7 +285,7 @@ the simulation has not been set up yet. Returns: bool: True if the simulation can step backward.)") .def( - "change_classical_value", + "change_classical_variable_value", [](SimulationState* self, const std::string& variableName, py::object newValue) { VariableValue value{}; @@ -296,10 +296,11 @@ the simulation has not been set up yet. } else if (py::isinstance(newValue)) { value.floatValue = newValue.cast(); } else { - throw py::type_error("change_classical_value requires a bool, " - "int, or float value"); + throw py::type_error( + "change_classical_variable_value requires a bool, " + "int, or float value"); } - checkOrThrow(self->changeClassicalVariable( + checkOrThrow(self->changeClassicalVariableValue( self, variableName.c_str(), &value)); }, R"(Sets the value of the given classical variable. @@ -311,8 +312,8 @@ the simulation has not been set up yet. "change_amplitude_value", [](SimulationState* self, const std::string& basisState, const Complex& value) { - checkOrThrow(self->changeAmplitudeVariable(self, basisState.c_str(), - &value)); + checkOrThrow( + self->changeAmplitudeValue(self, basisState.c_str(), &value)); }, R"(Sets the amplitude of the given computational basis state. diff --git a/include/backend/dd/DDSimDebug.hpp b/include/backend/dd/DDSimDebug.hpp index 538c74af..c9a19f24 100644 --- a/include/backend/dd/DDSimDebug.hpp +++ b/include/backend/dd/DDSimDebug.hpp @@ -492,9 +492,9 @@ Result ddsimGetClassicalVariable(SimulationState* self, const char* name, * @param value The desired value encoded as a `VariableValue`. * @return The result of the operation. */ -Result ddsimChangeClassicalVariable(SimulationState* self, - const char* variableName, - const VariableValue* value); +Result ddsimChangeClassicalVariableValue(SimulationState* self, + const char* variableName, + const VariableValue* value); /** * @brief Updates the amplitude of a given computational basis state. @@ -504,9 +504,8 @@ Result ddsimChangeClassicalVariable(SimulationState* self, * @param value The desired complex amplitude. * @return The result of the operation. */ -Result ddsimChangeAmplitudeVariable(SimulationState* self, - const char* basisState, - const Complex* value); +Result ddsimChangeAmplitudeValue(SimulationState* self, const char* basisState, + const Complex* value); /** * @brief Gets the number of classical variables in the simulation. diff --git a/include/backend/debug.h b/include/backend/debug.h index ee8fa8f2..90751d2a 100644 --- a/include/backend/debug.h +++ b/include/backend/debug.h @@ -182,9 +182,9 @@ struct SimulationStateStruct { * @param value The desired value encoded as a `VariableValue`. * @return The result of the operation. */ - Result (*changeClassicalVariable)(SimulationState* self, - const char* variableName, - const VariableValue* value); + Result (*changeClassicalVariableValue)(SimulationState* self, + const char* variableName, + const VariableValue* value); /** * @brief Changes the amplitude of a computational basis state. * @@ -193,9 +193,8 @@ struct SimulationStateStruct { * @param value The desired complex amplitude. * @return The result of the operation. */ - Result (*changeAmplitudeVariable)(SimulationState* self, - const char* basisState, - const Complex* value); + Result (*changeAmplitudeValue)(SimulationState* self, const char* basisState, + const Complex* value); /** * @brief Indicates whether the execution has finished. diff --git a/python/mqt/debugger/dap/messages/change_bit_dap_message.py b/python/mqt/debugger/dap/messages/change_bit_dap_message.py index 7fa0d58a..494ef33c 100644 --- a/python/mqt/debugger/dap/messages/change_bit_dap_message.py +++ b/python/mqt/debugger/dap/messages/change_bit_dap_message.py @@ -139,7 +139,7 @@ def _apply_change(self, server: DAPServer, name: str) -> bool: desired_value = self._parse_boolean_value() try: - server.simulation_state.change_classical_value(name, desired_value) + server.simulation_state.change_classical_variable_value(name, desired_value) except Exception as exc: # pragma: no cover - transport errors mapped above msg = f"Failed to set '{name}' to {desired_value}." raise ValueError(msg) from exc diff --git a/python/mqt/debugger/pydebugger.pyi b/python/mqt/debugger/pydebugger.pyi index 76e414cc..a146c761 100644 --- a/python/mqt/debugger/pydebugger.pyi +++ b/python/mqt/debugger/pydebugger.pyi @@ -159,7 +159,7 @@ class SimulationState: int: The number of assertions that failed during execution. """ - def change_classical_value(self, variable_name: str, value: bool | float) -> None: + def change_classical_variable_value(self, variable_name: str, value: bool | float) -> None: """Sets the value of a classical variable in the simulation. Args: diff --git a/src/backend/dd/DDSimDebug.cpp b/src/backend/dd/DDSimDebug.cpp index 27838403..3bf6ba4c 100644 --- a/src/backend/dd/DDSimDebug.cpp +++ b/src/backend/dd/DDSimDebug.cpp @@ -526,8 +526,9 @@ Result createDDSimulationState(DDSimulationState* self) { self->interface.pauseSimulation = ddsimPauseSimulation; self->interface.canStepForward = ddsimCanStepForward; self->interface.canStepBackward = ddsimCanStepBackward; - self->interface.changeClassicalVariable = ddsimChangeClassicalVariable; - self->interface.changeAmplitudeVariable = ddsimChangeAmplitudeVariable; + self->interface.changeClassicalVariableValue = + ddsimChangeClassicalVariableValue; + self->interface.changeAmplitudeValue = ddsimChangeAmplitudeValue; self->interface.isFinished = ddsimIsFinished; self->interface.didAssertionFail = ddsimDidAssertionFail; self->interface.wasBreakpointHit = ddsimWasBreakpointHit; @@ -616,19 +617,20 @@ Result ddsimLoadCode(SimulationState* self, const char* code) { return OK; } -Result ddsimChangeClassicalVariable(SimulationState* self, - const char* variableName, - const VariableValue* value) { +Result ddsimChangeClassicalVariableValue(SimulationState* self, + const char* variableName, + const VariableValue* value) { if (variableName == nullptr || value == nullptr) { - std::cerr - << "ddsimChangeClassicalVariable: variableName or value is null.\n"; + std::cerr << "ddsimChangeClassicalVariableValue: variableName or value is " + "null.\n"; return ERROR; } auto* ddsim = toDDSimulationState(self); const auto it = ddsim->variables.find(variableName); if (it == ddsim->variables.end()) { - std::cerr << "ddsimChangeClassicalVariable: no classical variable named '" - << variableName << "'.\n"; + std::cerr + << "ddsimChangeClassicalVariableValue: no classical variable named '" + << variableName << "'.\n"; return ERROR; } auto& var = it->second; @@ -643,18 +645,18 @@ Result ddsimChangeClassicalVariable(SimulationState* self, var.value.floatValue = value->floatValue; break; default: - std::cerr << "ddsimChangeClassicalVariable: unsupported variable type for '" - << variableName << "'.\n"; + std::cerr + << "ddsimChangeClassicalVariableValue: unsupported variable type for '" + << variableName << "'.\n"; return ERROR; } return OK; } -Result ddsimChangeAmplitudeVariable(SimulationState* self, - const char* basisState, - const Complex* value) { +Result ddsimChangeAmplitudeValue(SimulationState* self, const char* basisState, + const Complex* value) { if (basisState == nullptr || value == nullptr) { - std::cerr << "ddsimChangeAmplitudeVariable: basisState or value is null.\n"; + std::cerr << "ddsimChangeAmplitudeValue: basisState or value is null.\n"; return ERROR; } auto* ddsim = toDDSimulationState(self); @@ -662,12 +664,12 @@ Result ddsimChangeAmplitudeVariable(SimulationState* self, const std::string state{basisState}; if (state.size() != numQubits) { std::cerr - << "ddsimChangeAmplitudeVariable: basisState length does not match the " + << "ddsimChangeAmplitudeValue: basisState length does not match the " "number of qubits.\n"; return ERROR; } if (std::ranges::any_of(state, [](char c) { return c != '0' && c != '1'; })) { - std::cerr << "ddsimChangeAmplitudeVariable: basisState must contain only " + std::cerr << "ddsimChangeAmplitudeValue: basisState must contain only " "'0' and '1'.\n"; return ERROR; } @@ -702,13 +704,13 @@ Result ddsimChangeAmplitudeVariable(SimulationState* self, const double desiredNormSquared = (desiredReal * desiredReal) + (desiredImag * desiredImag); if (desiredNormSquared > 1.0 + tolerance) { - std::cerr << "ddsimChangeAmplitudeVariable: requested amplitude norm^2 " + std::cerr << "ddsimChangeAmplitudeValue: requested amplitude norm^2 " "exceeds 1.\n"; return ERROR; } if (otherNormSquared <= tolerance && std::abs(desiredNormSquared - 1.0) > tolerance) { - std::cerr << "ddsimChangeAmplitudeVariable: cannot target a sub-normalized " + std::cerr << "ddsimChangeAmplitudeValue: cannot target a sub-normalized " "state when all other amplitudes are effectively zero.\n"; return ERROR; } @@ -717,7 +719,7 @@ Result ddsimChangeAmplitudeVariable(SimulationState* self, if (otherNormSquared > tolerance) { const double remaining = 1.0 - desiredNormSquared; if (remaining < -tolerance) { - std::cerr << "ddsimChangeAmplitudeVariable: normalization would require " + std::cerr << "ddsimChangeAmplitudeValue: normalization would require " "negative residual probability mass.\n"; return ERROR; } diff --git a/test/test_data_retrieval.cpp b/test/test_data_retrieval.cpp index 4fed1d3b..6d994a5b 100644 --- a/test/test_data_retrieval.cpp +++ b/test/test_data_retrieval.cpp @@ -249,14 +249,14 @@ TEST_F(DataRetrievalTest, ChangeClassicalVariableUpdatesValue) { VariableValue desired{}; desired.boolValue = true; - ASSERT_EQ(state->changeClassicalVariable(state, "c[0]", &desired), OK); + ASSERT_EQ(state->changeClassicalVariableValue(state, "c[0]", &desired), OK); Variable updated; ASSERT_EQ(state->getClassicalVariable(state, "c[0]", &updated), OK); ASSERT_TRUE(classicalEquals(updated, true)); desired.boolValue = false; - ASSERT_EQ(state->changeClassicalVariable(state, "c[0]", &desired), OK); + ASSERT_EQ(state->changeClassicalVariableValue(state, "c[0]", &desired), OK); ASSERT_EQ(state->getClassicalVariable(state, "c[0]", &updated), OK); ASSERT_TRUE(classicalEquals(updated, false)); } @@ -267,8 +267,9 @@ TEST_F(DataRetrievalTest, ChangeClassicalVariableUpdatesValue) { TEST_F(DataRetrievalTest, ChangeClassicalVariableUnknown) { VariableValue desired{}; desired.boolValue = true; - ASSERT_EQ(state->changeClassicalVariable(state, "does_not_exist", &desired), - ERROR); + ASSERT_EQ( + state->changeClassicalVariableValue(state, "does_not_exist", &desired), + ERROR); } /** @@ -276,7 +277,7 @@ TEST_F(DataRetrievalTest, ChangeClassicalVariableUnknown) { */ TEST_F(DataRetrievalTest, ChangeClassicalVariableNullValue) { forwardTo(6); - ASSERT_EQ(state->changeClassicalVariable(state, "c[0]", nullptr), ERROR); + ASSERT_EQ(state->changeClassicalVariableValue(state, "c[0]", nullptr), ERROR); } } // namespace mqt::debugger::test From e8e9f40465d8b3894f6b5bf6769f1a2c0905f64a Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Thu, 4 Dec 2025 21:49:54 +0100 Subject: [PATCH 037/145] dotstrings --- bindings/InterfaceBindings.cpp | 5 ++++- include/backend/debug.h | 5 +++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/bindings/InterfaceBindings.cpp b/bindings/InterfaceBindings.cpp index 7e7c7922..8cd40e18 100644 --- a/bindings/InterfaceBindings.cpp +++ b/bindings/InterfaceBindings.cpp @@ -318,7 +318,10 @@ the simulation has not been set up yet. R"(Sets the amplitude of the given computational basis state. The basis state must be provided as a bitstring (e.g., ``"010"``) whose length -matches the number of qubits in the circuit. +matches the number of qubits in the circuit. The simulator rescales the +remaining amplitudes to keep the state normalized. Attempts to set amplitudes +that violate normalization, target out-of-range states, or use invalid +bitstrings raise an error. Args: basisState (str): The bitstring describing the basis state whose amplitude should be changed. diff --git a/include/backend/debug.h b/include/backend/debug.h index 90751d2a..d4510f53 100644 --- a/include/backend/debug.h +++ b/include/backend/debug.h @@ -188,6 +188,11 @@ struct SimulationStateStruct { /** * @brief Changes the amplitude of a computational basis state. * + * The basis state is provided as a bitstring whose length matches the + * current number of qubits. Implementations are expected to renormalize the + * remaining amplitudes so that the state vector stays normalized and to + * reject invalid bitstrings or amplitudes that violate normalization. + * * @param self The instance to query. * @param basisState The bitstring identifying the basis state to update. * @param value The desired complex amplitude. From 817e49ee3572b73420c1ca246c5e413bf7794caa Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Mon, 8 Dec 2025 14:51:44 +0100 Subject: [PATCH 038/145] github review changes --- python/mqt/debugger/dap/dap_server.py | 5 ++- .../messages/change_amplitude_dap_message.py | 27 ++++++++----- test/python/test_compilation.py | 40 ------------------- test/python/test_python_bindings.py | 11 +++++ test/test_data_retrieval.cpp | 37 +++++++++++++++++ 5 files changed, 68 insertions(+), 52 deletions(-) diff --git a/python/mqt/debugger/dap/dap_server.py b/python/mqt/debugger/dap/dap_server.py index c74596f4..504b77b7 100644 --- a/python/mqt/debugger/dap/dap_server.py +++ b/python/mqt/debugger/dap/dap_server.py @@ -42,6 +42,7 @@ ThreadsDAPMessage, VariablesDAPMessage, ) +from .messages.change_amplitude_dap_message import _QUANTUM_REFERENCE if TYPE_CHECKING: from .messages import Request @@ -269,7 +270,9 @@ def handle_command(self, command: dict[str, Any]) -> tuple[dict[str, Any], mqt.d arguments = command.get("arguments", {}) variables_reference = arguments.get("variablesReference") message_type: type[mqt.debugger.dap.messages.DAPMessage] - message_type = AmplitudeChangeDAPMessage if variables_reference == 2 else BitChangeDAPMessage + message_type = ( + AmplitudeChangeDAPMessage if variables_reference == _QUANTUM_REFERENCE else BitChangeDAPMessage + ) message: mqt.debugger.dap.messages.DAPMessage = message_type(command) return (message.handle(self), message) for message_type in supported_messages: diff --git a/python/mqt/debugger/dap/messages/change_amplitude_dap_message.py b/python/mqt/debugger/dap/messages/change_amplitude_dap_message.py index 05a1ccab..d4478021 100644 --- a/python/mqt/debugger/dap/messages/change_amplitude_dap_message.py +++ b/python/mqt/debugger/dap/messages/change_amplitude_dap_message.py @@ -6,7 +6,7 @@ # # Licensed under the MIT License -"""Handles edits of quantum amplitudes via the DAP ``setVariable`` request.""" +"""Handles edits of quantum amplitudes via the DAP `setVariable` request.""" from __future__ import annotations @@ -41,33 +41,38 @@ def _format_complex(value: mqt.debugger.Complex) -> str: def _complex_matches(current: mqt.debugger.Complex, desired: complex) -> bool: - """Return ``True`` if the debugger amplitude equals the desired value.""" + """Return "True" if the debugger amplitude equals the desired value.""" return abs(current.real - desired.real) <= _EPS and abs(current.imaginary - desired.imag) <= _EPS def _normalize_value(value: str) -> str: """Normalize DAP complex literals so Python's :func:`complex` can parse them. - Visual Studio Code currently sends amplitudes in the ``a+bi`` / ``a-bi`` form, - but also accepts real-only (``a``) or imaginary-only (``bi``) literals with - arbitrary whitespace. Plain ``i``/``-i`` or inputs mixing ``i`` and ``j`` are - intentionally unsupported. When a literal contains ``i`` but no ``j``, this - function rewrites ``i`` to ``j`` so Python understands the imaginary unit. + Visual Studio Code currently sends amplitudes in the `a+bi` / `a-bi` form, + but also accepts real-only (`a`) or imaginary-only (`bi`) literals with + arbitrary whitespace. Plain `i`/`-i` inputs are normalized to `1i`/`-1i`, + while strings mixing `i` and `j` remain unsupported. When a literal contains + `i` but no `j`, this function rewrites `i` to `j` so Python understands the + imaginary unit. Args: value (str): Raw value received from the DAP client. Returns: - str: Normalized literal accepted by ``complex``. + str: Normalized literal accepted by `complex`. """ normalized = value.strip().replace(" ", "") if not normalized: msg = ( "The new amplitude value must not be empty; use literals such as " - "'a+bi', 'a-bi', 'a', or 'bi'. Plain 'i'/'-i' and mixed 'i'/'j' " - "inputs are rejected, and 'i' is only converted to 'j' when no 'j' is present." + "'a+bi', 'a-bi', 'a', or 'bi'. Mixed 'i'/'j' inputs are rejected, " + "and 'i' is only converted to 'j' when no 'j' is present." ) raise ValueError(msg) + if normalized in {"i", "+i"}: + normalized = "1i" + elif normalized == "-i": + normalized = "-1i" # Visual Studio Code allows using `i` as the imaginary unit. Python expects `j`. if "i" in normalized and "j" not in normalized: normalized = normalized.replace("i", "j") @@ -155,7 +160,7 @@ def _parse_request(self, server: DAPServer) -> _TargetAmplitude: return _TargetAmplitude(bitstring, desired) def _extract_bitstring(self) -> str: - """Return the ``|...>`` bitstring referenced in the request. + """Return the `|...>` bitstring referenced in the request. Returns: str: The computational basis state name without delimiters. diff --git a/test/python/test_compilation.py b/test/python/test_compilation.py index 78fec6e1..7d7ca17c 100644 --- a/test/python/test_compilation.py +++ b/test/python/test_compilation.py @@ -24,7 +24,6 @@ import pytest from mqt.debugger import check -from mqt.debugger.check import calibration as calibration_module from mqt.debugger.check import result_checker, runtime_check if TYPE_CHECKING: @@ -35,21 +34,6 @@ BASE_PATH = Path("test/python/resources/compilation/") -def _missing_reason(dependencies: tuple[str, ...]) -> str: - joined = ", ".join(dependencies) - return f"Missing optional dependencies required for this test: {joined or 'unspecified'}." - - -CALIBRATION_OPTIONALS = tuple(calibration_module.missing_optionals) -RESULT_CHECKER_OPTIONALS = tuple(result_checker.missing_optionals) -requires_calibration_optionals = pytest.mark.skipif( - bool(CALIBRATION_OPTIONALS), reason=_missing_reason(CALIBRATION_OPTIONALS) -) -requires_result_checker_optionals = pytest.mark.skipif( - bool(RESULT_CHECKER_OPTIONALS), reason=_missing_reason(RESULT_CHECKER_OPTIONALS) -) - - class GeneratedOutput: """A context manager for generating output files with a given distribution.""" @@ -146,8 +130,6 @@ def compiled_slice_1() -> str: return f.read() -@requires_calibration_optionals -@requires_result_checker_optionals def test_correct_good_sample_size(compiled_slice_1: str) -> None: """Test the correctness of a run result with a large enough sample size. @@ -165,8 +147,6 @@ def test_correct_good_sample_size(compiled_slice_1: str) -> None: assert errors <= 5 -@requires_calibration_optionals -@requires_result_checker_optionals def test_correct_bad_sample_size(compiled_slice_1: str) -> None: """Test the correctness of a run result with a bad sample size. @@ -184,8 +164,6 @@ def test_correct_bad_sample_size(compiled_slice_1: str) -> None: assert errors >= 5 -@requires_calibration_optionals -@requires_result_checker_optionals def test_incorrect_bad_sample_size(compiled_slice_1: str) -> None: """Test the incorrectness of a run result with a bad sample size. @@ -204,8 +182,6 @@ def test_incorrect_bad_sample_size(compiled_slice_1: str) -> None: assert errors <= 75 -@requires_calibration_optionals -@requires_result_checker_optionals def test_incorrect_good_sample_size(compiled_slice_1: str) -> None: """Test the incorrectness of a run result with a good sample size. @@ -223,8 +199,6 @@ def test_incorrect_good_sample_size(compiled_slice_1: str) -> None: assert errors >= 75 -@requires_calibration_optionals -@requires_result_checker_optionals def test_sample_estimate(compiled_slice_1: str) -> None: """Test the estimation of required shots. @@ -280,8 +254,6 @@ def test_main_prepare(compiled_slice_1: str, monkeypatch: pytest.MonkeyPatch) -> check_dir_contents_and_delete(Path("tmp-test"), {"slice_1.qasm": compiled_slice_1}) -@requires_calibration_optionals -@requires_result_checker_optionals def test_main_check_equality_sv(monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]) -> None: """Test the correctness of the "check" mode of the main function with a statevector-equality assertion. @@ -313,8 +285,6 @@ def test_main_check_equality_sv(monkeypatch: pytest.MonkeyPatch, capsys: pytest. assert "passed" in captured.out -@requires_calibration_optionals -@requires_result_checker_optionals def test_main_check_equality_circuit(monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]) -> None: """Test the correctness of the "check" mode of the main function with a circuit-equality assertion. @@ -346,8 +316,6 @@ def test_main_check_equality_circuit(monkeypatch: pytest.MonkeyPatch, capsys: py assert "passed" in captured.out -@requires_calibration_optionals -@requires_result_checker_optionals def test_main_check_sup(monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]) -> None: """Test the correctness of the "check" mode of the main function with a superposition assertion. @@ -379,8 +347,6 @@ def test_main_check_sup(monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureF assert "passed" in captured.out -@requires_calibration_optionals -@requires_result_checker_optionals def test_main_check_eq_incorrect(monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]) -> None: """Test the correctness of the "check" mode of the main function with a statevector-equality assertion with incorrect results. @@ -412,8 +378,6 @@ def test_main_check_eq_incorrect(monkeypatch: pytest.MonkeyPatch, capsys: pytest assert "passed" not in captured.out -@requires_calibration_optionals -@requires_result_checker_optionals def test_main_check_sup_incorrect(monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]) -> None: """Test the correctness of the "check" mode of the main function with a superposition assertion with incorrect results. @@ -445,8 +409,6 @@ def test_main_check_sup_incorrect(monkeypatch: pytest.MonkeyPatch, capsys: pytes assert "passed" not in captured.out -@requires_calibration_optionals -@requires_result_checker_optionals def test_main_shots(monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]) -> None: """Test the correctness of the "check" mode of the main function. @@ -480,7 +442,6 @@ def test_main_shots(monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixtu assert shots == 180, f"Expected 100 shots, but got {shots}." -@requires_result_checker_optionals def test_contribution_equal_under_noise_big_difference() -> None: """Test the correctness of the `distribution_equal_under_noise` function when distributions are very different.""" assert not result_checker.distribution_equal_under_noise( @@ -488,7 +449,6 @@ def test_contribution_equal_under_noise_big_difference() -> None: ) -@requires_result_checker_optionals def test_check_power_divergence_zeros() -> None: """Tests that `check_power_divergence` correctly returns `False` if the expected distribution has a zero entry while the observed distribution's entry is non-zero.""" statistic, p = result_checker.check_power_divergence([99, 1, 0, 0], [100, 0, 0, 0]) diff --git a/test/python/test_python_bindings.py b/test/python/test_python_bindings.py index 1f277364..c0f81939 100644 --- a/test/python/test_python_bindings.py +++ b/test/python/test_python_bindings.py @@ -233,6 +233,17 @@ def test_change_amplitude_value(simulation_instance_ghz: SimulationInstance) -> assert updated.imaginary == pytest.approx(0.0, abs=1e-9) +def test_change_amplitude_value_invalid_bitstring(simulation_instance_ghz: SimulationInstance) -> None: + """Ensure invalid amplitude edit requests raise an error at the Python layer.""" + (simulation_state, _state_id) = simulation_instance_ghz + simulation_state.run_simulation() + desired = mqt.debugger.Complex(0.25, 0.0) + with pytest.raises(RuntimeError): + simulation_state.change_amplitude_value("11", desired) + with pytest.raises(RuntimeError): + simulation_state.change_amplitude_value("11a", desired) + + def test_get_state_vector_sub(simulation_instance_classical: SimulationInstance) -> None: """Tests the `get_state_vector_sub()` method.""" (simulation_state, _state_id) = simulation_instance_classical diff --git a/test/test_data_retrieval.cpp b/test/test_data_retrieval.cpp index 6d994a5b..de28214a 100644 --- a/test/test_data_retrieval.cpp +++ b/test/test_data_retrieval.cpp @@ -280,4 +280,41 @@ TEST_F(DataRetrievalTest, ChangeClassicalVariableNullValue) { ASSERT_EQ(state->changeClassicalVariableValue(state, "c[0]", nullptr), ERROR); } +/** + * @test Test that amplitudes can be updated and the remaining state is + * rescaled. + */ +TEST_F(DataRetrievalTest, ChangeAmplitudeValueRescalesOtherStates) { + forwardTo(12); + Complex desired{0.5, 0.0}; + ASSERT_EQ(state->changeAmplitudeValue(state, "0010", &desired), OK); + + Complex updated{}; + ASSERT_EQ(state->getAmplitudeBitstring(state, "0010", &updated), OK); + ASSERT_TRUE(complexEquality(updated, 0.5, 0.0)); + + ASSERT_EQ(state->getAmplitudeBitstring(state, "1011", &updated), OK); + ASSERT_TRUE(complexEquality(updated, -0.866, 0.0)); +} + +/** + * @test Test that invalid bitstrings for amplitude updates are rejected. + */ +TEST_F(DataRetrievalTest, ChangeAmplitudeValueRejectsInvalidBitstring) { + Complex desired{0.25, 0.0}; + forwardTo(12); + ASSERT_EQ(state->changeAmplitudeValue(state, "10", &desired), ERROR); + ASSERT_EQ(state->changeAmplitudeValue(state, "10a1", &desired), ERROR); +} + +/** + * @test Test that sub-normalized targets without residual amplitudes are + * refused. + */ +TEST_F(DataRetrievalTest, ChangeAmplitudeValueRejectsSubNormalizedVacuum) { + Complex desired{0.5, 0.0}; + ASSERT_EQ(state->changeAmplitudeValue(state, "0000", &desired), ERROR); + ASSERT_EQ(state->changeAmplitudeValue(state, "0000", nullptr), ERROR); +} + } // namespace mqt::debugger::test From c6b102feeea42890412053a619e874cacbd0fb28 Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Mon, 8 Dec 2025 15:12:30 +0100 Subject: [PATCH 039/145] fix linter --- .github/workflows/ci.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 15e16567..68584549 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -83,6 +83,10 @@ jobs: needs: change-detection if: fromJSON(needs.change-detection.outputs.run-cpp-linter) uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-cpp-linter.yml@89734354f64f30a80dd16602d4e271df34348987 # v1.17.4 + permissions: + contents: read + pull-requests: write + issues: write with: build-project: true clang-version: 20 From 1cefe3e8808377eda7edf8df3e1eb0aa65ff89a5 Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Mon, 8 Dec 2025 15:17:24 +0100 Subject: [PATCH 040/145] workflow file, not changed --- .github/workflows/ci.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 68584549..15e16567 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -83,10 +83,6 @@ jobs: needs: change-detection if: fromJSON(needs.change-detection.outputs.run-cpp-linter) uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-cpp-linter.yml@89734354f64f30a80dd16602d4e271df34348987 # v1.17.4 - permissions: - contents: read - pull-requests: write - issues: write with: build-project: true clang-version: 20 From e10b1a0f1f181a18854e66dbf2b5c5966d46037f Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Mon, 8 Dec 2025 19:53:34 +0100 Subject: [PATCH 041/145] highlight error --- python/mqt/debugger/dap/dap_server.py | 23 ++-- python/mqt/debugger/dap/messages/__init__.py | 2 + .../messages/highlight_error_dap_message.py | 107 ++++++++++++++++++ 3 files changed, 125 insertions(+), 7 deletions(-) create mode 100644 python/mqt/debugger/dap/messages/highlight_error_dap_message.py diff --git a/python/mqt/debugger/dap/dap_server.py b/python/mqt/debugger/dap/dap_server.py index 504b77b7..46dc4ea2 100644 --- a/python/mqt/debugger/dap/dap_server.py +++ b/python/mqt/debugger/dap/dap_server.py @@ -24,6 +24,7 @@ ContinueDAPMessage, DisconnectDAPMessage, ExceptionInfoDAPMessage, + HighlightError, InitializeDAPMessage, LaunchDAPMessage, NextDAPMessage, @@ -51,6 +52,7 @@ InitializeDAPMessage, DisconnectDAPMessage, LaunchDAPMessage, + HighlightError, SetBreakpointsDAPMessage, ThreadsDAPMessage, StackTraceDAPMessage, @@ -325,6 +327,7 @@ def handle_assertion_fail(self, connection: socket.socket) -> None: line, column, connection, + "stderr", ) def code_pos_to_coordinates(self, pos: int) -> tuple[int, int]: @@ -392,7 +395,12 @@ def format_error_cause(self, cause: mqt.debugger.ErrorCause) -> str: ) def send_message_hierarchy( - self, message: dict[str, str | list[Any] | dict[str, Any]], line: int, column: int, connection: socket.socket + self, + message: dict[str, str | list[Any] | dict[str, Any]], + line: int, + column: int, + connection: socket.socket, + category: str = "console", ) -> None: """Send a hierarchy of messages to the client. @@ -401,10 +409,11 @@ def send_message_hierarchy( line (int): The line number. column (int): The column number. connection (socket.socket): The client socket. + category (str): The output category (console/stdout/stderr). """ if "title" in message: title_event = mqt.debugger.dap.messages.OutputDAPEvent( - "console", cast("str", message["title"]), "start", line, column, self.source_file + category, cast("str", message["title"]), "start", line, column, self.source_file ) send_message(json.dumps(title_event.encode()), connection) @@ -413,22 +422,22 @@ def send_message_hierarchy( if isinstance(body, list): for msg in body: if isinstance(msg, dict): - self.send_message_hierarchy(msg, line, column, connection) + self.send_message_hierarchy(msg, line, column, connection, category) else: output_event = mqt.debugger.dap.messages.OutputDAPEvent( - "console", msg, None, line, column, self.source_file + category, 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) + self.send_message_hierarchy(body, line, column, connection, category) elif isinstance(body, str): output_event = mqt.debugger.dap.messages.OutputDAPEvent( - "console", body, None, line, column, self.source_file + category, 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.debugger.dap.messages.OutputDAPEvent( - "console", cast("str", message.get("end")), "end", line, column, self.source_file + category, cast("str", message.get("end")), "end", line, column, self.source_file ) send_message(json.dumps(end_event.encode()), connection) diff --git a/python/mqt/debugger/dap/messages/__init__.py b/python/mqt/debugger/dap/messages/__init__.py index f15bad76..9fafc80c 100644 --- a/python/mqt/debugger/dap/messages/__init__.py +++ b/python/mqt/debugger/dap/messages/__init__.py @@ -21,6 +21,7 @@ from .exception_info_message import ExceptionInfoDAPMessage from .exited_dap_event import ExitedDAPEvent from .gray_out_event import GrayOutDAPEvent +from .highlight_error_dap_message import HighlightError from .initialize_dap_message import InitializeDAPMessage from .initialized_dap_event import InitializedDAPEvent from .launch_dap_message import LaunchDAPMessage @@ -57,6 +58,7 @@ "ExceptionInfoDAPMessage", "ExitedDAPEvent", "GrayOutDAPEvent", + "HighlightError", "InitializeDAPMessage", "InitializedDAPEvent", "LaunchDAPMessage", diff --git a/python/mqt/debugger/dap/messages/highlight_error_dap_message.py b/python/mqt/debugger/dap/messages/highlight_error_dap_message.py new file mode 100644 index 00000000..ff92b95f --- /dev/null +++ b/python/mqt/debugger/dap/messages/highlight_error_dap_message.py @@ -0,0 +1,107 @@ +"""Represents the custom 'highlightError' DAP request.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Any + +from mqt.debugger import ErrorCauseType + +from .dap_message import DAPMessage + +if TYPE_CHECKING: + from .. import DAPServer + + +class HighlightError(DAPMessage): + """Represents the 'highlightError' custom DAP request.""" + + message_type_name = "highlightError" + + def __init__(self, message: dict[str, Any]) -> None: + """Initialize the 'highlightError' request instance. + + Args: + message (dict[str, Any]): The object representing the request. + """ + arguments = message.get("arguments", {}) or {} + self._arguments = arguments + self._instruction_filter = arguments.get("instruction") + super().__init__(message) + + def validate(self) -> None: + """Validate the provided request arguments.""" + if not isinstance(self._arguments, dict): + msg = "The 'arguments' property must be an object." + raise ValueError(msg) + if self._instruction_filter is not None and not isinstance(self._instruction_filter, int): + msg = "The 'instruction' argument must be an integer if provided." + raise ValueError(msg) + + def handle(self, server: DAPServer) -> dict[str, Any]: + """Execute the request and return diagnostic highlights. + + Args: + server (DAPServer): The server handling the request. + + Returns: + dict[str, Any]: The response containing highlight metadata. + """ + response = super().handle(server) + response["body"] = { + "highlights": self._collect_highlights(server), + "source": getattr(server, "source_file", {}) or {}, + } + return response + + def _collect_highlights(self, server: DAPServer) -> list[dict[str, Any]]: + """Collect highlight information from the diagnostics backend.""" + if not getattr(server, "source_code", ""): + return [] + + try: + diagnostics = server.simulation_state.get_diagnostics() + error_causes = diagnostics.potential_error_causes() + except RuntimeError: + return [] + + highlights: list[dict[str, Any]] = [] + for cause in error_causes: + if self._instruction_filter is not None and cause.instruction != self._instruction_filter: + continue + + highlight = self._encode_highlight(server, cause) + if highlight is not None: + highlights.append(highlight) + + return highlights + + def _encode_highlight(self, server: DAPServer, cause: Any) -> dict[str, Any] | None: + """Encode a single highlight entry for the response payload.""" + try: + start_pos, end_pos = server.simulation_state.get_instruction_position(cause.instruction) + except RuntimeError: + return None + + message = server.format_error_cause(cause) + start_line, start_column = server.code_pos_to_coordinates(start_pos) + end_line, end_column = server.code_pos_to_coordinates(end_pos) + snippet = server.source_code[start_pos : end_pos + 1].replace("\r", "") + + return { + "instruction": int(cause.instruction), + "range": { + "start": {"line": start_line, "column": start_column}, + "end": {"line": end_line, "column": end_column}, + }, + "reason": self._format_reason(cause.type), + "code": snippet.strip(), + "message": message, + } + + def _format_reason(self, cause_type: ErrorCauseType) -> str: + """Format the cause type into a short identifier for clients.""" + if cause_type == ErrorCauseType.MissingInteraction: + return "missingInteraction" + if cause_type == ErrorCauseType.ControlAlwaysZero: + return "controlAlwaysZero" + return "unknown" From ceaedcfccfbda485411106bffdc9cd915b50d1b6 Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Mon, 8 Dec 2025 20:39:40 +0100 Subject: [PATCH 042/145] highlight error v2 --- python/mqt/debugger/dap/dap_server.py | 61 +++++++++- .../messages/highlight_error_dap_message.py | 113 ++++-------------- 2 files changed, 81 insertions(+), 93 deletions(-) diff --git a/python/mqt/debugger/dap/dap_server.py b/python/mqt/debugger/dap/dap_server.py index 46dc4ea2..bbc81c2f 100644 --- a/python/mqt/debugger/dap/dap_server.py +++ b/python/mqt/debugger/dap/dap_server.py @@ -52,7 +52,6 @@ InitializeDAPMessage, DisconnectDAPMessage, LaunchDAPMessage, - HighlightError, SetBreakpointsDAPMessage, ThreadsDAPMessage, StackTraceDAPMessage, @@ -329,6 +328,10 @@ def handle_assertion_fail(self, connection: socket.socket) -> None: connection, "stderr", ) + highlight_entries = self.collect_highlight_entries(current_instruction) + if highlight_entries: + highlight_event = mqt.debugger.dap.messages.HighlightError(highlight_entries, self.source_file) + send_message(json.dumps(highlight_event.encode()), connection) def code_pos_to_coordinates(self, pos: int) -> tuple[int, int]: """Helper method to convert a code position to line and column. @@ -394,6 +397,62 @@ def format_error_cause(self, cause: mqt.debugger.ErrorCause) -> str: else "" ) + def collect_highlight_entries(self, failing_instruction: int) -> list[dict[str, Any]]: + """Collect highlight entries for the current assertion failure.""" + highlights: list[dict[str, Any]] = [] + if getattr(self, "source_code", ""): + try: + diagnostics = self.simulation_state.get_diagnostics() + error_causes = diagnostics.potential_error_causes() + except RuntimeError: + error_causes = [] + + for cause in error_causes: + message = self.format_error_cause(cause) + reason = self._format_highlight_reason(cause.type) + entry = self._build_highlight_entry(cause.instruction, reason, message) + if entry is not None: + highlights.append(entry) + + if not highlights: + entry = self._build_highlight_entry( + failing_instruction, + "assertionFailed", + "Assertion failed at this instruction.", + ) + if entry is not None: + highlights.append(entry) + + return highlights + + def _build_highlight_entry(self, instruction: int, reason: str, message: str) -> dict[str, Any] | None: + """Create a highlight entry for a specific instruction.""" + try: + start_pos, end_pos = self.simulation_state.get_instruction_position(instruction) + except RuntimeError: + return None + start_line, start_column = self.code_pos_to_coordinates(start_pos) + end_line, end_column = self.code_pos_to_coordinates(end_pos) + snippet = self.source_code[start_pos : end_pos + 1].replace("\r", "") + return { + "instruction": int(instruction), + "range": { + "start": {"line": start_line, "column": start_column}, + "end": {"line": end_line, "column": end_column}, + }, + "reason": reason, + "code": snippet.strip(), + "message": message, + } + + def _format_highlight_reason(self, cause_type: mqt.debugger.ErrorCauseType | None) -> str: + """Return a short identifier for the highlight reason.""" + if cause_type == mqt.debugger.ErrorCauseType.MissingInteraction: + return "missingInteraction" + if cause_type == mqt.debugger.ErrorCauseType.ControlAlwaysZero: + return "controlAlwaysZero" + return "unknown" + def send_message_hierarchy( self, message: dict[str, str | list[Any] | dict[str, Any]], diff --git a/python/mqt/debugger/dap/messages/highlight_error_dap_message.py b/python/mqt/debugger/dap/messages/highlight_error_dap_message.py index ff92b95f..bc99ff67 100644 --- a/python/mqt/debugger/dap/messages/highlight_error_dap_message.py +++ b/python/mqt/debugger/dap/messages/highlight_error_dap_message.py @@ -1,107 +1,36 @@ -"""Represents the custom 'highlightError' DAP request.""" +"""Represents the custom 'highlightError' DAP event.""" from __future__ import annotations -from typing import TYPE_CHECKING, Any +from typing import Any -from mqt.debugger import ErrorCauseType +from .dap_event import DAPEvent -from .dap_message import DAPMessage -if TYPE_CHECKING: - from .. import DAPServer +class HighlightError(DAPEvent): + """Represents the 'highlightError' custom DAP event.""" + event_name = "highlightError" -class HighlightError(DAPMessage): - """Represents the 'highlightError' custom DAP request.""" + highlights: list[dict[str, Any]] + source: dict[str, Any] - message_type_name = "highlightError" - - def __init__(self, message: dict[str, Any]) -> None: - """Initialize the 'highlightError' request instance. + def __init__(self, highlights: list[dict[str, Any]], source: dict[str, Any]) -> None: + """Create a new 'highlightError' DAP event message. Args: - message (dict[str, Any]): The object representing the request. + highlights (list[dict[str, Any]]): Ranges and metadata that should be highlighted. + source (dict[str, Any]): Information about the current source file. """ - arguments = message.get("arguments", {}) or {} - self._arguments = arguments - self._instruction_filter = arguments.get("instruction") - super().__init__(message) + self.highlights = highlights + self.source = source + super().__init__() def validate(self) -> None: - """Validate the provided request arguments.""" - if not isinstance(self._arguments, dict): - msg = "The 'arguments' property must be an object." - raise ValueError(msg) - if self._instruction_filter is not None and not isinstance(self._instruction_filter, int): - msg = "The 'instruction' argument must be an integer if provided." - raise ValueError(msg) - - def handle(self, server: DAPServer) -> dict[str, Any]: - """Execute the request and return diagnostic highlights. - - Args: - server (DAPServer): The server handling the request. - - Returns: - dict[str, Any]: The response containing highlight metadata. - """ - response = super().handle(server) - response["body"] = { - "highlights": self._collect_highlights(server), - "source": getattr(server, "source_file", {}) or {}, - } - return response - - def _collect_highlights(self, server: DAPServer) -> list[dict[str, Any]]: - """Collect highlight information from the diagnostics backend.""" - if not getattr(server, "source_code", ""): - return [] - - try: - diagnostics = server.simulation_state.get_diagnostics() - error_causes = diagnostics.potential_error_causes() - except RuntimeError: - return [] - - highlights: list[dict[str, Any]] = [] - for cause in error_causes: - if self._instruction_filter is not None and cause.instruction != self._instruction_filter: - continue - - highlight = self._encode_highlight(server, cause) - if highlight is not None: - highlights.append(highlight) - - return highlights - - def _encode_highlight(self, server: DAPServer, cause: Any) -> dict[str, Any] | None: - """Encode a single highlight entry for the response payload.""" - try: - start_pos, end_pos = server.simulation_state.get_instruction_position(cause.instruction) - except RuntimeError: - return None - - message = server.format_error_cause(cause) - start_line, start_column = server.code_pos_to_coordinates(start_pos) - end_line, end_column = server.code_pos_to_coordinates(end_pos) - snippet = server.source_code[start_pos : end_pos + 1].replace("\r", "") - - return { - "instruction": int(cause.instruction), - "range": { - "start": {"line": start_line, "column": start_column}, - "end": {"line": end_line, "column": end_column}, - }, - "reason": self._format_reason(cause.type), - "code": snippet.strip(), - "message": message, - } + """Validate the 'highlightError' DAP event message after creation.""" - def _format_reason(self, cause_type: ErrorCauseType) -> str: - """Format the cause type into a short identifier for clients.""" - if cause_type == ErrorCauseType.MissingInteraction: - return "missingInteraction" - if cause_type == ErrorCauseType.ControlAlwaysZero: - return "controlAlwaysZero" - return "unknown" + def encode(self) -> dict[str, Any]: + """Encode the 'highlightError' DAP event message as a dictionary.""" + encoded = super().encode() + encoded["body"] = {"highlights": self.highlights, "source": self.source} + return encoded From 52ad47a5c42ebf1a939265c9eb6678a7f4dc537f Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Mon, 8 Dec 2025 20:44:55 +0100 Subject: [PATCH 043/145] highlight error v3 --- python/mqt/debugger/dap/dap_server.py | 4 ++-- .../debugger/dap/messages/highlight_error_dap_message.py | 8 ++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/python/mqt/debugger/dap/dap_server.py b/python/mqt/debugger/dap/dap_server.py index bbc81c2f..8f36e6cd 100644 --- a/python/mqt/debugger/dap/dap_server.py +++ b/python/mqt/debugger/dap/dap_server.py @@ -24,7 +24,6 @@ ContinueDAPMessage, DisconnectDAPMessage, ExceptionInfoDAPMessage, - HighlightError, InitializeDAPMessage, LaunchDAPMessage, NextDAPMessage, @@ -445,7 +444,8 @@ def _build_highlight_entry(self, instruction: int, reason: str, message: str) -> "message": message, } - def _format_highlight_reason(self, cause_type: mqt.debugger.ErrorCauseType | None) -> str: + @staticmethod + def _format_highlight_reason(cause_type: mqt.debugger.ErrorCauseType | None) -> str: """Return a short identifier for the highlight reason.""" if cause_type == mqt.debugger.ErrorCauseType.MissingInteraction: return "missingInteraction" diff --git a/python/mqt/debugger/dap/messages/highlight_error_dap_message.py b/python/mqt/debugger/dap/messages/highlight_error_dap_message.py index bc99ff67..c760a8cb 100644 --- a/python/mqt/debugger/dap/messages/highlight_error_dap_message.py +++ b/python/mqt/debugger/dap/messages/highlight_error_dap_message.py @@ -1,3 +1,11 @@ +# Copyright (c) 2024 - 2025 Chair for Design Automation, TUM +# Copyright (c) 2025 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + """Represents the custom 'highlightError' DAP event.""" from __future__ import annotations From 84c38f1cc58ce1e592e0ca3605058628c07ae798 Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Mon, 8 Dec 2025 21:15:44 +0100 Subject: [PATCH 044/145] highlight error v4 --- bindings/InterfaceBindings.cpp | 17 ++++++++++++++++- .../debugger/dap/messages/launch_dap_message.py | 5 +++-- .../dap/messages/restart_dap_message.py | 12 +++++++++++- 3 files changed, 30 insertions(+), 4 deletions(-) diff --git a/bindings/InterfaceBindings.cpp b/bindings/InterfaceBindings.cpp index 8cd40e18..00d70f9a 100644 --- a/bindings/InterfaceBindings.cpp +++ b/bindings/InterfaceBindings.cpp @@ -23,8 +23,10 @@ #include #include #include +#include #include #include +#include #include #include #include // NOLINT(misc-include-cleaner) @@ -173,7 +175,20 @@ Contains one element for each of the `num_states` states in the state vector.)") .def( "load_code", [](SimulationState* self, const char* code) { - checkOrThrow(self->loadCode(self, code)); + py::module io = py::module::import("io"); + py::object string_io = io.attr("StringIO")(); + Result result = OK; + { + py::scoped_ostream_redirect redirect(std::cerr, string_io); + result = self->loadCode(self, code); + } + if (result != OK) { + auto message = string_io.attr("getvalue")().cast(); + if (message.empty()) { + message = "An error occurred while executing the operation"; + } + throw std::runtime_error(message); + } }, R"(Loads the given code into the simulation state. diff --git a/python/mqt/debugger/dap/messages/launch_dap_message.py b/python/mqt/debugger/dap/messages/launch_dap_message.py index 65aabd19..b880330d 100644 --- a/python/mqt/debugger/dap/messages/launch_dap_message.py +++ b/python/mqt/debugger/dap/messages/launch_dap_message.py @@ -68,13 +68,14 @@ def handle(self, server: DAPServer) -> dict[str, Any]: server.source_code = code try: server.simulation_state.load_code(code) - except RuntimeError: + except RuntimeError as exc: + message = str(exc) or "An error occurred while parsing the code." return { "type": "response", "request_seq": self.sequence_number, "success": False, "command": "launch", - "message": "An error occurred while parsing the code.", + "message": message, } if not self.stop_on_entry: server.simulation_state.run_simulation() diff --git a/python/mqt/debugger/dap/messages/restart_dap_message.py b/python/mqt/debugger/dap/messages/restart_dap_message.py index b455b2e1..b62f8d80 100644 --- a/python/mqt/debugger/dap/messages/restart_dap_message.py +++ b/python/mqt/debugger/dap/messages/restart_dap_message.py @@ -67,7 +67,17 @@ def handle(self, server: DAPServer) -> dict[str, Any]: with program_path.open("r", encoding=locale.getpreferredencoding(False)) as f: code = f.read() server.source_code = code - server.simulation_state.load_code(code) + try: + server.simulation_state.load_code(code) + except RuntimeError as exc: + message = str(exc) or "An error occurred while parsing the code." + return { + "type": "response", + "request_seq": self.sequence_number, + "success": False, + "command": "launch", + "message": message, + } if not self.stop_on_entry: server.simulation_state.run_simulation() server.source_file = {"name": program_path.name, "path": self.program} From 0d12f43e4a8800a287541b0068cc0121db89b679 Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Mon, 8 Dec 2025 21:46:44 +0100 Subject: [PATCH 045/145] higlight error v4 --- python/mqt/debugger/dap/dap_server.py | 49 +++++++++++++++++++ .../dap/messages/launch_dap_message.py | 5 +- .../dap/messages/restart_dap_message.py | 5 +- 3 files changed, 53 insertions(+), 6 deletions(-) diff --git a/python/mqt/debugger/dap/dap_server.py b/python/mqt/debugger/dap/dap_server.py index 8f36e6cd..940d0500 100644 --- a/python/mqt/debugger/dap/dap_server.py +++ b/python/mqt/debugger/dap/dap_server.py @@ -11,6 +11,7 @@ from __future__ import annotations import json +import re import socket import sys from typing import TYPE_CHECKING, Any, cast @@ -114,6 +115,7 @@ def __init__(self, host: str = "127.0.0.1", port: int = 4711) -> None: self.simulation_state = mqt.debugger.SimulationState() self.lines_start_at_one = True self.columns_start_at_one = True + self.pending_highlights: list[dict[str, Any]] = [] def start(self) -> None: """Start the DAP server and listen for one connection.""" @@ -236,6 +238,10 @@ def handle_client(self, connection: socket.socket) -> None: ) event_payload = json.dumps(e.encode()) send_message(event_payload, connection) + if self.pending_highlights: + highlight_event = mqt.debugger.dap.messages.HighlightError(self.pending_highlights, self.source_file) + send_message(json.dumps(highlight_event.encode()), connection) + self.pending_highlights = [] self.regular_checks(connection) def regular_checks(self, connection: socket.socket) -> None: @@ -453,6 +459,49 @@ def _format_highlight_reason(cause_type: mqt.debugger.ErrorCauseType | None) -> return "controlAlwaysZero" return "unknown" + def queue_parse_error(self, error_message: str) -> None: + """Store highlight data for a parse error to be emitted later.""" + line, column, detail = self._parse_error_location(error_message) + entry = self._build_parse_error_highlight(line, column, detail) + if entry is not None: + self.pending_highlights = [entry] + + @staticmethod + def _parse_error_location(error_message: str) -> tuple[int, int, str]: + """Parse a compiler error string and extract the source location.""" + match = re.match(r":(\d+):(\d+):\s*(.*)", error_message.strip()) + if match: + line = int(match.group(1)) + column = int(match.group(2)) + detail = match.group(3).strip() + else: + line = 1 + column = 1 + detail = error_message.strip() + return (line, column, detail) + + def _build_parse_error_highlight(self, line: int, column: int, detail: str) -> dict[str, Any] | None: + """Create a highlight entry for a parse error.""" + if not getattr(self, "source_code", ""): + return None + lines = self.source_code.split("\n") + if not lines: + return None + line = max(1, min(line, len(lines))) + line_text = lines[line - 1] + end_column = max(column, len(line_text)) + snippet = line_text.strip() or line_text + return { + "instruction": -1, + "range": { + "start": {"line": line, "column": column}, + "end": {"line": line, "column": end_column if end_column > 0 else column}, + }, + "reason": "parseError", + "code": snippet, + "message": detail, + } + def send_message_hierarchy( self, message: dict[str, str | list[Any] | dict[str, Any]], diff --git a/python/mqt/debugger/dap/messages/launch_dap_message.py b/python/mqt/debugger/dap/messages/launch_dap_message.py index b880330d..df7b1c2f 100644 --- a/python/mqt/debugger/dap/messages/launch_dap_message.py +++ b/python/mqt/debugger/dap/messages/launch_dap_message.py @@ -63,23 +63,22 @@ def handle(self, server: DAPServer) -> dict[str, Any]: dict[str, Any]: The response to the request. """ program_path = Path(self.program) + server.source_file = {"name": program_path.name, "path": self.program} with program_path.open("r", encoding=locale.getpreferredencoding(False)) as f: code = f.read() server.source_code = code try: server.simulation_state.load_code(code) except RuntimeError as exc: - message = str(exc) or "An error occurred while parsing the code." + server.queue_parse_error(str(exc)) return { "type": "response", "request_seq": self.sequence_number, "success": False, "command": "launch", - "message": message, } if not self.stop_on_entry: server.simulation_state.run_simulation() - server.source_file = {"name": program_path.name, "path": self.program} return { "type": "response", "request_seq": self.sequence_number, diff --git a/python/mqt/debugger/dap/messages/restart_dap_message.py b/python/mqt/debugger/dap/messages/restart_dap_message.py index b62f8d80..6e8dad1d 100644 --- a/python/mqt/debugger/dap/messages/restart_dap_message.py +++ b/python/mqt/debugger/dap/messages/restart_dap_message.py @@ -64,23 +64,22 @@ def handle(self, server: DAPServer) -> dict[str, Any]: """ server.simulation_state.reset_simulation() program_path = Path(self.program) + server.source_file = {"name": program_path.name, "path": self.program} with program_path.open("r", encoding=locale.getpreferredencoding(False)) as f: code = f.read() server.source_code = code try: server.simulation_state.load_code(code) except RuntimeError as exc: - message = str(exc) or "An error occurred while parsing the code." + server.queue_parse_error(str(exc)) return { "type": "response", "request_seq": self.sequence_number, "success": False, "command": "launch", - "message": message, } if not self.stop_on_entry: server.simulation_state.run_simulation() - server.source_file = {"name": program_path.name, "path": self.program} return { "type": "response", "request_seq": self.sequence_number, From 3ca8940bf176aec11644816d29a52189337d9ab0 Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Tue, 9 Dec 2025 15:57:48 +0100 Subject: [PATCH 046/145] highlight_error --- .../messages/highlight_error_dap_message.py | 105 ++++++++++++++++-- .../dap/messages/launch_dap_message.py | 15 +-- .../dap/messages/restart_dap_message.py | 15 +-- 3 files changed, 113 insertions(+), 22 deletions(-) diff --git a/python/mqt/debugger/dap/messages/highlight_error_dap_message.py b/python/mqt/debugger/dap/messages/highlight_error_dap_message.py index c760a8cb..340d9f87 100644 --- a/python/mqt/debugger/dap/messages/highlight_error_dap_message.py +++ b/python/mqt/debugger/dap/messages/highlight_error_dap_message.py @@ -7,14 +7,12 @@ # Licensed under the MIT License """Represents the custom 'highlightError' DAP event.""" - from __future__ import annotations -from typing import Any +from typing import Any, Mapping, Sequence from .dap_event import DAPEvent - class HighlightError(DAPEvent): """Represents the 'highlightError' custom DAP event.""" @@ -23,22 +21,113 @@ class HighlightError(DAPEvent): highlights: list[dict[str, Any]] source: dict[str, Any] - def __init__(self, highlights: list[dict[str, Any]], source: dict[str, Any]) -> None: + def __init__(self, highlights: Sequence[Mapping[str, Any]], source: Mapping[str, Any]) -> None: """Create a new 'highlightError' DAP event message. Args: - highlights (list[dict[str, Any]]): Ranges and metadata that should be highlighted. - source (dict[str, Any]): Information about the current source file. + highlights (Sequence[Mapping[str, Any]]): Highlight entries describing the problematic ranges. + source (Mapping[str, Any]): Information about the current source file. """ - self.highlights = highlights - self.source = source + self.highlights = [self._normalize_highlight(entry) for entry in highlights] + self.source = self._normalize_source(source) super().__init__() def validate(self) -> None: """Validate the 'highlightError' DAP event message after creation.""" + if not self.highlights: + msg = "At least one highlight entry is required to show the issue location." + raise ValueError(msg) + + for highlight in self.highlights: + if "message" not in highlight or not str(highlight["message"]).strip(): + msg = "Each highlight entry must contain a descriptive 'message'." + raise ValueError(msg) + highlight_range = highlight.get("range") + if not isinstance(highlight_range, dict): + msg = "Each highlight entry must provide a 'range' dictionary." + raise ValueError(msg) + start = highlight_range.get("start") + end = highlight_range.get("end") + if not isinstance(start, dict) or not isinstance(end, dict): + msg = "Highlight ranges must define 'start' and 'end' coordinates." + raise ValueError(msg) + if self._later_than(start, end): + msg = "Highlight range 'end' must not precede 'start'." + raise ValueError(msg) + + if "name" not in self.source or "path" not in self.source: + msg = "Source information must at least expose 'name' and 'path'." + raise ValueError(msg) def encode(self) -> dict[str, Any]: """Encode the 'highlightError' DAP event message as a dictionary.""" encoded = super().encode() encoded["body"] = {"highlights": self.highlights, "source": self.source} return encoded + + @staticmethod + def _normalize_highlight(entry: Mapping[str, Any]) -> dict[str, Any]: + """Return a shallow copy of a highlight entry with guaranteed structure.""" + if "range" not in entry: + msg = "A highlight entry must contain a 'range'." + raise ValueError(msg) + highlight_range = entry["range"] + if not isinstance(highlight_range, Mapping): + msg = "Highlight range must be a mapping with 'start' and 'end'." + raise TypeError(msg) + + start = HighlightError._normalize_position(highlight_range.get("start")) + end = HighlightError._normalize_position(highlight_range.get("end")) + if HighlightError._later_than(start, end): + msg = "Highlight range 'end' must be after 'start'." + raise ValueError(msg) + + normalized = dict(entry) + normalized["instruction"] = int(normalized.get("instruction", -1)) + normalized["reason"] = str(normalized.get("reason", "unknown")) + normalized["code"] = str(normalized.get("code", "")) + normalized["message"] = str(normalized.get("message", "")).strip() + normalized["range"] = { + "start": start, + "end": end, + } + return normalized + + @staticmethod + def _normalize_position(position: Mapping[str, Any] | None) -> dict[str, int]: + """Normalize a position mapping, ensuring it includes a line and column.""" + if not isinstance(position, Mapping): + msg = "Highlight positions must be mappings with 'line' and 'column'." + raise TypeError(msg) + try: + line = int(position["line"]) + column = int(position["column"]) + except KeyError as exc: + raise ValueError("Highlight positions require 'line' and 'column'.") from exc + return { + "line": line, + "column": column, + } + + @staticmethod + def _normalize_source(source: Mapping[str, Any]) -> dict[str, Any]: + """Create a defensive copy of the provided DAP Source information.""" + if not isinstance(source, Mapping): + msg = "Source information must be provided as a mapping." + raise TypeError(msg) + normalized = dict(source) + if "name" not in normalized or "path" not in normalized: + msg = "Source mappings must at least provide 'name' and 'path'." + raise ValueError(msg) + normalized["name"] = str(normalized["name"]) + normalized["path"] = str(normalized["path"]) + return normalized + + @staticmethod + def _later_than(start: Mapping[str, Any], end: Mapping[str, Any]) -> bool: + """Return True if 'end' describes a position before 'start'.""" + start_line = int(start.get("line", 0)) + start_column = int(start.get("column", 0)) + end_line = int(end.get("line", 0)) + end_column = int(end.get("column", 0)) + return (end_line, end_column) < (start_line, start_column) \ No newline at end of file diff --git a/python/mqt/debugger/dap/messages/launch_dap_message.py b/python/mqt/debugger/dap/messages/launch_dap_message.py index df7b1c2f..d3e3db1f 100644 --- a/python/mqt/debugger/dap/messages/launch_dap_message.py +++ b/python/mqt/debugger/dap/messages/launch_dap_message.py @@ -64,21 +64,22 @@ def handle(self, server: DAPServer) -> dict[str, Any]: """ program_path = Path(self.program) server.source_file = {"name": program_path.name, "path": self.program} + parsed_successfully = True with program_path.open("r", encoding=locale.getpreferredencoding(False)) as f: code = f.read() server.source_code = code try: server.simulation_state.load_code(code) except RuntimeError as exc: + parsed_successfully = False server.queue_parse_error(str(exc)) - return { - "type": "response", - "request_seq": self.sequence_number, - "success": False, - "command": "launch", - } - if not self.stop_on_entry: + if parsed_successfully and not self.stop_on_entry: server.simulation_state.run_simulation() + if not parsed_successfully: + try: + server.simulation_state.reset_simulation() + except RuntimeError: + pass return { "type": "response", "request_seq": self.sequence_number, diff --git a/python/mqt/debugger/dap/messages/restart_dap_message.py b/python/mqt/debugger/dap/messages/restart_dap_message.py index 6e8dad1d..036b0bdf 100644 --- a/python/mqt/debugger/dap/messages/restart_dap_message.py +++ b/python/mqt/debugger/dap/messages/restart_dap_message.py @@ -65,21 +65,22 @@ def handle(self, server: DAPServer) -> dict[str, Any]: server.simulation_state.reset_simulation() program_path = Path(self.program) server.source_file = {"name": program_path.name, "path": self.program} + parsed_successfully = True with program_path.open("r", encoding=locale.getpreferredencoding(False)) as f: code = f.read() server.source_code = code try: server.simulation_state.load_code(code) except RuntimeError as exc: + parsed_successfully = False server.queue_parse_error(str(exc)) - return { - "type": "response", - "request_seq": self.sequence_number, - "success": False, - "command": "launch", - } - if not self.stop_on_entry: + if parsed_successfully and not self.stop_on_entry: server.simulation_state.run_simulation() + if not parsed_successfully: + try: + server.simulation_state.reset_simulation() + except RuntimeError: + pass return { "type": "response", "request_seq": self.sequence_number, From 543bd22f7f7157111a5c2a5f73a2227f1e9e3cef Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Tue, 9 Dec 2025 16:03:39 +0100 Subject: [PATCH 047/145] big fix --- .../messages/highlight_error_dap_message.py | 23 ++++++++++++------- .../dap/messages/launch_dap_message.py | 5 ++-- .../dap/messages/restart_dap_message.py | 5 ++-- 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/python/mqt/debugger/dap/messages/highlight_error_dap_message.py b/python/mqt/debugger/dap/messages/highlight_error_dap_message.py index 340d9f87..7f5bda5d 100644 --- a/python/mqt/debugger/dap/messages/highlight_error_dap_message.py +++ b/python/mqt/debugger/dap/messages/highlight_error_dap_message.py @@ -7,12 +7,18 @@ # Licensed under the MIT License """Represents the custom 'highlightError' DAP event.""" + from __future__ import annotations -from typing import Any, Mapping, Sequence +from collections.abc import Mapping +from typing import TYPE_CHECKING, Any from .dap_event import DAPEvent +if TYPE_CHECKING: + from collections.abc import Sequence + + class HighlightError(DAPEvent): """Represents the 'highlightError' custom DAP event.""" @@ -21,12 +27,12 @@ class HighlightError(DAPEvent): highlights: list[dict[str, Any]] source: dict[str, Any] - def __init__(self, highlights: Sequence[Mapping[str, Any]], source: Mapping[str, Any]) -> None: + def __init__(self, highlights: Sequence[Mapping[str, Any]], source: Mapping[str, Any] | None) -> None: """Create a new 'highlightError' DAP event message. Args: highlights (Sequence[Mapping[str, Any]]): Highlight entries describing the problematic ranges. - source (Mapping[str, Any]): Information about the current source file. + source (Mapping[str, Any] | None): Information about the current source file. """ self.highlights = [self._normalize_highlight(entry) for entry in highlights] self.source = self._normalize_source(source) @@ -45,12 +51,12 @@ def validate(self) -> None: highlight_range = highlight.get("range") if not isinstance(highlight_range, dict): msg = "Each highlight entry must provide a 'range' dictionary." - raise ValueError(msg) + raise TypeError(msg) start = highlight_range.get("start") end = highlight_range.get("end") if not isinstance(start, dict) or not isinstance(end, dict): msg = "Highlight ranges must define 'start' and 'end' coordinates." - raise ValueError(msg) + raise TypeError(msg) if self._later_than(start, end): msg = "Highlight range 'end' must not precede 'start'." raise ValueError(msg) @@ -103,14 +109,15 @@ def _normalize_position(position: Mapping[str, Any] | None) -> dict[str, int]: line = int(position["line"]) column = int(position["column"]) except KeyError as exc: - raise ValueError("Highlight positions require 'line' and 'column'.") from exc + msg = "Highlight positions require 'line' and 'column'." + raise ValueError(msg) from exc return { "line": line, "column": column, } @staticmethod - def _normalize_source(source: Mapping[str, Any]) -> dict[str, Any]: + def _normalize_source(source: Mapping[str, Any] | None) -> dict[str, Any]: """Create a defensive copy of the provided DAP Source information.""" if not isinstance(source, Mapping): msg = "Source information must be provided as a mapping." @@ -130,4 +137,4 @@ def _later_than(start: Mapping[str, Any], end: Mapping[str, Any]) -> bool: start_column = int(start.get("column", 0)) end_line = int(end.get("line", 0)) end_column = int(end.get("column", 0)) - return (end_line, end_column) < (start_line, start_column) \ No newline at end of file + return (end_line, end_column) < (start_line, start_column) diff --git a/python/mqt/debugger/dap/messages/launch_dap_message.py b/python/mqt/debugger/dap/messages/launch_dap_message.py index d3e3db1f..10d09a5d 100644 --- a/python/mqt/debugger/dap/messages/launch_dap_message.py +++ b/python/mqt/debugger/dap/messages/launch_dap_message.py @@ -10,6 +10,7 @@ from __future__ import annotations +import contextlib import locale from pathlib import Path from typing import TYPE_CHECKING, Any @@ -76,10 +77,8 @@ def handle(self, server: DAPServer) -> dict[str, Any]: if parsed_successfully and not self.stop_on_entry: server.simulation_state.run_simulation() if not parsed_successfully: - try: + with contextlib.suppress(RuntimeError): server.simulation_state.reset_simulation() - except RuntimeError: - pass return { "type": "response", "request_seq": self.sequence_number, diff --git a/python/mqt/debugger/dap/messages/restart_dap_message.py b/python/mqt/debugger/dap/messages/restart_dap_message.py index 036b0bdf..d19973fe 100644 --- a/python/mqt/debugger/dap/messages/restart_dap_message.py +++ b/python/mqt/debugger/dap/messages/restart_dap_message.py @@ -10,6 +10,7 @@ from __future__ import annotations +import contextlib import locale from pathlib import Path from typing import TYPE_CHECKING, Any @@ -77,10 +78,8 @@ def handle(self, server: DAPServer) -> dict[str, Any]: if parsed_successfully and not self.stop_on_entry: server.simulation_state.run_simulation() if not parsed_successfully: - try: + with contextlib.suppress(RuntimeError): server.simulation_state.reset_simulation() - except RuntimeError: - pass return { "type": "response", "request_seq": self.sequence_number, From 9f13bd2402a4a5e363e3e90615ca366af938a186 Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Tue, 9 Dec 2025 17:52:17 +0100 Subject: [PATCH 048/145] bug fix --- python/mqt/debugger/dap/dap_server.py | 159 ++++++++++++++++++++------ test/python/test_dap_server.py | 31 +++++ 2 files changed, 156 insertions(+), 34 deletions(-) create mode 100644 test/python/test_dap_server.py diff --git a/python/mqt/debugger/dap/dap_server.py b/python/mqt/debugger/dap/dap_server.py index 940d0500..7cfa143c 100644 --- a/python/mqt/debugger/dap/dap_server.py +++ b/python/mqt/debugger/dap/dap_server.py @@ -14,7 +14,7 @@ import re import socket import sys -from typing import TYPE_CHECKING, Any, cast +from typing import TYPE_CHECKING, Any import mqt.debugger @@ -116,6 +116,7 @@ def __init__(self, host: str = "127.0.0.1", port: int = 4711) -> None: self.lines_start_at_one = True self.columns_start_at_one = True self.pending_highlights: list[dict[str, Any]] = [] + self._prevent_exit = False def start(self) -> None: """Start the DAP server and listen for one connection.""" @@ -168,6 +169,21 @@ def handle_client(self, connection: socket.socket) -> None: result, cmd = self.handle_command(payload) result_payload = json.dumps(result) send_message(result_payload, connection) + if isinstance( + cmd, + ( + mqt.debugger.dap.messages.NextDAPMessage, + mqt.debugger.dap.messages.StepBackDAPMessage, + mqt.debugger.dap.messages.StepInDAPMessage, + mqt.debugger.dap.messages.StepOutDAPMessage, + mqt.debugger.dap.messages.ContinueDAPMessage, + mqt.debugger.dap.messages.ReverseContinueDAPMessage, + mqt.debugger.dap.messages.RestartFrameDAPMessage, + mqt.debugger.dap.messages.RestartDAPMessage, + mqt.debugger.dap.messages.LaunchDAPMessage, + ), + ): + self._prevent_exit = False e: mqt.debugger.dap.messages.DAPEvent | None = None if isinstance(cmd, mqt.debugger.dap.messages.LaunchDAPMessage): @@ -242,6 +258,7 @@ def handle_client(self, connection: socket.socket) -> None: highlight_event = mqt.debugger.dap.messages.HighlightError(self.pending_highlights, self.source_file) send_message(json.dumps(highlight_event.encode()), connection) self.pending_highlights = [] + self._prevent_exit = True self.regular_checks(connection) def regular_checks(self, connection: socket.socket) -> None: @@ -251,7 +268,11 @@ def regular_checks(self, connection: socket.socket) -> None: connection (socket.socket): The client socket. """ e: mqt.debugger.dap.messages.DAPEvent | None = None - if self.simulation_state.is_finished() and self.simulation_state.get_instruction_count() != 0: + if ( + self.simulation_state.is_finished() + and self.simulation_state.get_instruction_count() != 0 + and not self._prevent_exit + ): e = mqt.debugger.dap.messages.ExitedDAPEvent(0) event_payload = json.dumps(e.encode()) send_message(event_payload, connection) @@ -337,6 +358,7 @@ def handle_assertion_fail(self, connection: socket.socket) -> None: if highlight_entries: highlight_event = mqt.debugger.dap.messages.HighlightError(highlight_entries, self.source_file) send_message(json.dumps(highlight_event.encode()), connection) + self._prevent_exit = True def code_pos_to_coordinates(self, pos: int) -> tuple[int, int]: """Helper method to convert a code position to line and column. @@ -348,14 +370,18 @@ def code_pos_to_coordinates(self, pos: int) -> tuple[int, int]: tuple[int, int]: The line and column, 0-or-1-indexed. """ lines = self.source_code.split("\n") - line = 0 + line = 1 if lines else 0 col = 0 for i, line_code in enumerate(lines): - if pos < len(line_code): + if pos <= len(line_code): line = i + 1 col = pos break pos -= len(line_code) + 1 + else: + if lines: + line = len(lines) + col = len(lines[-1]) if self.columns_start_at_one: col += 1 if not self.lines_start_at_one: @@ -437,7 +463,8 @@ def _build_highlight_entry(self, instruction: int, reason: str, message: str) -> except RuntimeError: return None start_line, start_column = self.code_pos_to_coordinates(start_pos) - end_line, end_column = self.code_pos_to_coordinates(end_pos) + end_position_exclusive = min(len(self.source_code), end_pos + 1) + end_line, end_column = self.code_pos_to_coordinates(end_position_exclusive) snippet = self.source_code[start_pos : end_pos + 1].replace("\r", "") return { "instruction": int(instruction), @@ -502,6 +529,31 @@ def _build_parse_error_highlight(self, line: int, column: int, detail: str) -> d "message": detail, } + def _flatten_message_parts(self, parts: list[Any]) -> list[str]: + """Flatten nested message structures into plain text lines.""" + flattened: list[str] = [] + for part in parts: + if isinstance(part, str): + if part: + flattened.append(part) + elif isinstance(part, dict): + title = part.get("title") + if isinstance(title, str) and title: + flattened.append(title) + body = part.get("body") + if isinstance(body, list): + flattened.extend(self._flatten_message_parts(body)) + elif isinstance(body, str) and body: + flattened.append(body) + end = part.get("end") + if isinstance(end, str) and end: + flattened.append(end) + elif isinstance(part, list): + flattened.extend(self._flatten_message_parts(part)) + elif part is not None: + flattened.append(str(part)) + return flattened + def send_message_hierarchy( self, message: dict[str, str | list[Any] | dict[str, Any]], @@ -519,33 +571,72 @@ def send_message_hierarchy( connection (socket.socket): The client socket. category (str): The output category (console/stdout/stderr). """ - if "title" in message: - title_event = mqt.debugger.dap.messages.OutputDAPEvent( - category, cast("str", message["title"]), "start", line, column, self.source_file - ) - send_message(json.dumps(title_event.encode()), connection) - - if "body" in message: - body = message["body"] - if isinstance(body, list): - for msg in body: - if isinstance(msg, dict): - self.send_message_hierarchy(msg, line, column, connection, category) - else: - output_event = mqt.debugger.dap.messages.OutputDAPEvent( - category, 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, category) - elif isinstance(body, str): - output_event = mqt.debugger.dap.messages.OutputDAPEvent( - category, body, None, line, column, self.source_file - ) - send_message(json.dumps(output_event.encode()), connection) + raw_body = message.get("body") + body: list[str] | None = None + if isinstance(raw_body, list): + body = self._flatten_message_parts(raw_body) + elif isinstance(raw_body, str): + body = [raw_body] + end_value = message.get("end") + end = end_value if isinstance(end_value, str) else None + title = str(message.get("title", "")) + self.send_message_simple(title, body, end, line, column, connection, category) + + def send_message_simple( + self, + title: str, + body: list[str] | None, + end: str | None, + line: int, + column: int, + connection: socket.socket, + category: str = "console", + ) -> None: + """Send a simple message to the client. - if "end" in message or "title" in message: - end_event = mqt.debugger.dap.messages.OutputDAPEvent( - category, cast("str", message.get("end")), "end", line, column, self.source_file - ) - send_message(json.dumps(end_event.encode()), connection) + Args: + title (str): The title of the message. + body (list[str]): The body of the message. + end (str | None): The end of the message. + line (int): The line number. + column (int): The column number. + connection (socket.socket): The client socket. + category (str): The output category (console/stdout/stderr). + """ + segments: list[str] = [] + if title: + segments.append(title) + if body: + segments.extend(body) + if end: + segments.append(end) + if not segments: + return + output_text = "\n".join(segments) + event = mqt.debugger.dap.messages.OutputDAPEvent( + category, + output_text, + None, + line, + column, + self.source_file, + ) + send_message(json.dumps(event.encode()), connection) + + def send_state(self, connection: socket.socket) -> None: + """Send the state of the current execution to the client. + + Args: + connection (socket.socket): The client socket. + """ + output_lines = [] + if self.simulation_state.did_assertion_fail(): + output_lines.append("Assertion failed") + if self.simulation_state.was_breakpoint_hit(): + output_lines.append("Breakpoint hit") + if self.simulation_state.is_finished(): + output_lines.append("Finished") + if not output_lines: + output_lines.append("Running") + for line_text in output_lines: + self.send_message_simple(line_text, None, None, 0, 0, connection) diff --git a/test/python/test_dap_server.py b/test/python/test_dap_server.py new file mode 100644 index 00000000..4edd52bf --- /dev/null +++ b/test/python/test_dap_server.py @@ -0,0 +1,31 @@ +"""Tests for the DAP server helper utilities.""" + +from __future__ import annotations + +from types import SimpleNamespace + +from mqt.debugger.dap.dap_server import DAPServer + + +def test_code_pos_to_coordinates_handles_line_end() -> None: + """Ensure coordinates for newline positions stay on the current line.""" + server = DAPServer() + server.source_code = "measure q[0] -> c[0];\nmeasure q[1] -> c[1];\n" + line, column = server.code_pos_to_coordinates(server.source_code.index("\n")) + assert line == 1 + # Column is 1-based because the DAP client requests it that way. + assert column == len("measure q[0] -> c[0];") + 1 + + +def test_build_highlight_entry_does_not_span_next_instruction() -> None: + """Ensure highlight ranges stop at the end of the instruction.""" + server = DAPServer() + server.source_code = "measure q[0] -> c[0];\nmeasure q[1] -> c[1];\n" + first_line_end = server.source_code.index("\n") + fake_state = SimpleNamespace(get_instruction_position=lambda _instr: (0, first_line_end)) + server.simulation_state = fake_state # type: ignore[assignment] + + entry = server._build_highlight_entry(0, "reason", "message") + assert entry is not None + assert entry["range"]["start"]["line"] == 1 + assert entry["range"]["end"]["line"] == 1 From 4ff44dba90e90eda143e020e82b91318fe969aa0 Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Tue, 9 Dec 2025 18:03:22 +0100 Subject: [PATCH 049/145] bug fix 2 --- test/python/test_dap_server.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/test/python/test_dap_server.py b/test/python/test_dap_server.py index 4edd52bf..156c020a 100644 --- a/test/python/test_dap_server.py +++ b/test/python/test_dap_server.py @@ -1,3 +1,11 @@ +# Copyright (c) 2024 - 2025 Chair for Design Automation, TUM +# Copyright (c) 2025 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + """Tests for the DAP server helper utilities.""" from __future__ import annotations @@ -22,10 +30,15 @@ def test_build_highlight_entry_does_not_span_next_instruction() -> None: server = DAPServer() server.source_code = "measure q[0] -> c[0];\nmeasure q[1] -> c[1];\n" first_line_end = server.source_code.index("\n") - fake_state = SimpleNamespace(get_instruction_position=lambda _instr: (0, first_line_end)) + fake_diagnostics = SimpleNamespace(potential_error_causes=list) + fake_state = SimpleNamespace( + get_instruction_position=lambda _instr: (0, first_line_end), + get_diagnostics=lambda: fake_diagnostics, + ) server.simulation_state = fake_state # type: ignore[assignment] - entry = server._build_highlight_entry(0, "reason", "message") - assert entry is not None + entries = server.collect_highlight_entries(0) + assert entries + entry = entries[0] assert entry["range"]["start"]["line"] == 1 assert entry["range"]["end"]["line"] == 1 From 8e3b8017dc29e951ea4f5961f5e2b29edf68e8e7 Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Tue, 9 Dec 2025 19:17:53 +0100 Subject: [PATCH 050/145] shows issue --- python/mqt/debugger/dap/dap_server.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/python/mqt/debugger/dap/dap_server.py b/python/mqt/debugger/dap/dap_server.py index 7cfa143c..677e74bc 100644 --- a/python/mqt/debugger/dap/dap_server.py +++ b/python/mqt/debugger/dap/dap_server.py @@ -515,7 +515,24 @@ def _build_parse_error_highlight(self, line: int, column: int, detail: str) -> d if not lines: return None line = max(1, min(line, len(lines))) - line_text = lines[line - 1] + column = max(1, column) + line_index = line - 1 + line_text = lines[line_index] + + if column <= 1 and line_index > 0: + prev_index = line_index - 1 + while prev_index >= 0 and not lines[prev_index].strip(): + prev_index -= 1 + if prev_index >= 0: + line_index = prev_index + line = line_index + 1 + line_text = lines[line_index] + stripped = line_text.lstrip() + if stripped: + column = max(1, len(line_text) - len(stripped) + 1) + else: + column = 1 + end_column = max(column, len(line_text)) snippet = line_text.strip() or line_text return { From d1c343ef48ae151e47830ee2d054a1a62a4f6b13 Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Tue, 9 Dec 2025 19:40:28 +0100 Subject: [PATCH 051/145] entire row --- python/mqt/debugger/dap/dap_server.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/mqt/debugger/dap/dap_server.py b/python/mqt/debugger/dap/dap_server.py index 677e74bc..98a15bc8 100644 --- a/python/mqt/debugger/dap/dap_server.py +++ b/python/mqt/debugger/dap/dap_server.py @@ -515,7 +515,7 @@ def _build_parse_error_highlight(self, line: int, column: int, detail: str) -> d if not lines: return None line = max(1, min(line, len(lines))) - column = max(1, column) + column = max(1, column)^ line_index = line - 1 line_text = lines[line_index] @@ -533,7 +533,7 @@ def _build_parse_error_highlight(self, line: int, column: int, detail: str) -> d else: column = 1 - end_column = max(column, len(line_text)) + end_column = max(column, len(line_text) + 1) snippet = line_text.strip() or line_text return { "instruction": -1, From 59680cbcae270a4517864264c9bb26f8df359b9d Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Tue, 9 Dec 2025 21:16:04 +0100 Subject: [PATCH 052/145] bug fix --- python/mqt/debugger/dap/dap_server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/mqt/debugger/dap/dap_server.py b/python/mqt/debugger/dap/dap_server.py index 98a15bc8..3dc5f74e 100644 --- a/python/mqt/debugger/dap/dap_server.py +++ b/python/mqt/debugger/dap/dap_server.py @@ -515,7 +515,7 @@ def _build_parse_error_highlight(self, line: int, column: int, detail: str) -> d if not lines: return None line = max(1, min(line, len(lines))) - column = max(1, column)^ + column = max(1, column) line_index = line - 1 line_text = lines[line_index] From 5532817a6cf8f5072a0508971584941eca0e1ccd Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Wed, 10 Dec 2025 21:35:44 +0100 Subject: [PATCH 053/145] fix bug --- bindings/InterfaceBindings.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bindings/InterfaceBindings.cpp b/bindings/InterfaceBindings.cpp index 8cd40e18..e500e0ae 100644 --- a/bindings/InterfaceBindings.cpp +++ b/bindings/InterfaceBindings.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include // NOLINT(misc-include-cleaner) #include #include @@ -287,7 +288,7 @@ the simulation has not been set up yet. .def( "change_classical_variable_value", [](SimulationState* self, const std::string& variableName, - py::object newValue) { + const py::object& newValue) { VariableValue value{}; if (py::isinstance(newValue)) { value.boolValue = newValue.cast(); From b6e415267762751a5018478f7cc439063120477f Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Wed, 10 Dec 2025 22:09:00 +0100 Subject: [PATCH 054/145] linter fix --- test/test_data_retrieval.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/test_data_retrieval.cpp b/test/test_data_retrieval.cpp index de28214a..ea14bfbf 100644 --- a/test/test_data_retrieval.cpp +++ b/test/test_data_retrieval.cpp @@ -286,7 +286,7 @@ TEST_F(DataRetrievalTest, ChangeClassicalVariableNullValue) { */ TEST_F(DataRetrievalTest, ChangeAmplitudeValueRescalesOtherStates) { forwardTo(12); - Complex desired{0.5, 0.0}; + const Complex desired{0.5, 0.0}; ASSERT_EQ(state->changeAmplitudeValue(state, "0010", &desired), OK); Complex updated{}; @@ -301,7 +301,7 @@ TEST_F(DataRetrievalTest, ChangeAmplitudeValueRescalesOtherStates) { * @test Test that invalid bitstrings for amplitude updates are rejected. */ TEST_F(DataRetrievalTest, ChangeAmplitudeValueRejectsInvalidBitstring) { - Complex desired{0.25, 0.0}; + const Complex desired{0.25, 0.0}; forwardTo(12); ASSERT_EQ(state->changeAmplitudeValue(state, "10", &desired), ERROR); ASSERT_EQ(state->changeAmplitudeValue(state, "10a1", &desired), ERROR); @@ -312,7 +312,7 @@ TEST_F(DataRetrievalTest, ChangeAmplitudeValueRejectsInvalidBitstring) { * refused. */ TEST_F(DataRetrievalTest, ChangeAmplitudeValueRejectsSubNormalizedVacuum) { - Complex desired{0.5, 0.0}; + const Complex desired{0.5, 0.0}; ASSERT_EQ(state->changeAmplitudeValue(state, "0000", &desired), ERROR); ASSERT_EQ(state->changeAmplitudeValue(state, "0000", nullptr), ERROR); } From 3e02a9792aea66e92746247c8aac974177912183 Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Sat, 13 Dec 2025 10:53:58 +0100 Subject: [PATCH 055/145] work and progress --- bindings/InterfaceBindings.cpp | 2 +- docs/library/interface/Debug.rst | 2 +- docs/library/parsing/Parsing.rst | 2 +- python/mqt/debugger/pydebugger.pyi | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/bindings/InterfaceBindings.cpp b/bindings/InterfaceBindings.cpp index e500e0ae..eaa5ab8a 100644 --- a/bindings/InterfaceBindings.cpp +++ b/bindings/InterfaceBindings.cpp @@ -284,7 +284,7 @@ The simulation is unable to step backward if it is at the beginning or if the simulation has not been set up yet. Returns: - bool: True if the simulation can step backward.)") +bool: True, if the simulation can step backward.)") .def( "change_classical_variable_value", [](SimulationState* self, const std::string& variableName, diff --git a/docs/library/interface/Debug.rst b/docs/library/interface/Debug.rst index 567f3d25..6261510e 100644 --- a/docs/library/interface/Debug.rst +++ b/docs/library/interface/Debug.rst @@ -1,5 +1,5 @@ debug.h -------- +======= .. note:: diff --git a/docs/library/parsing/Parsing.rst b/docs/library/parsing/Parsing.rst index 9ee8d852..de08cdbc 100644 --- a/docs/library/parsing/Parsing.rst +++ b/docs/library/parsing/Parsing.rst @@ -1,5 +1,5 @@ Parsing -------- +======= This section documents the parsing and preprocessing functionalities developed for this framework, partially for the :doc:`DD implementation <../dd/Dd>` of the debugger, and partially for general-purpose use. diff --git a/python/mqt/debugger/pydebugger.pyi b/python/mqt/debugger/pydebugger.pyi index a146c761..497dfcce 100644 --- a/python/mqt/debugger/pydebugger.pyi +++ b/python/mqt/debugger/pydebugger.pyi @@ -171,7 +171,7 @@ class SimulationState: """Sets the amplitude of a computational basis state in the simulation. Args: - basis_state (str): The bitstring identifying the basis state (e.g. ``"010"``). + basis_state (str): The bitstring identifying the basis state (e.g. `010`). value (Complex): The desired complex amplitude. """ From a7b5ce1a86e1e3a9d17d8aee9fb85ad229edab4b Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Sat, 13 Dec 2025 11:27:48 +0100 Subject: [PATCH 056/145] work and progress --- docs/library/Library.rst | 2 +- src/backend/dd/DDSimDebug.cpp | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/library/Library.rst b/docs/library/Library.rst index b021dc15..d7c1953c 100644 --- a/docs/library/Library.rst +++ b/docs/library/Library.rst @@ -1,5 +1,5 @@ Library -------- +======= .. toctree:: :maxdepth: 4 diff --git a/src/backend/dd/DDSimDebug.cpp b/src/backend/dd/DDSimDebug.cpp index 3bf6ba4c..2045a501 100644 --- a/src/backend/dd/DDSimDebug.cpp +++ b/src/backend/dd/DDSimDebug.cpp @@ -532,7 +532,6 @@ Result createDDSimulationState(DDSimulationState* self) { self->interface.isFinished = ddsimIsFinished; self->interface.didAssertionFail = ddsimDidAssertionFail; self->interface.wasBreakpointHit = ddsimWasBreakpointHit; - self->interface.getCurrentInstruction = ddsimGetCurrentInstruction; self->interface.getInstructionCount = ddsimGetInstructionCount; self->interface.getInstructionPosition = ddsimGetInstructionPosition; From c8b03ee452a036b6c96321ab8b6043b65456fce0 Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Sat, 13 Dec 2025 11:33:05 +0100 Subject: [PATCH 057/145] work and progress --- bindings/InterfaceBindings.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bindings/InterfaceBindings.cpp b/bindings/InterfaceBindings.cpp index eaa5ab8a..414f7c91 100644 --- a/bindings/InterfaceBindings.cpp +++ b/bindings/InterfaceBindings.cpp @@ -318,7 +318,7 @@ bool: True, if the simulation can step backward.)") }, R"(Sets the amplitude of the given computational basis state. -The basis state must be provided as a bitstring (e.g., ``"010"``) whose length +The basis state must be provided as a bitstring (e.g., `010`) whose length matches the number of qubits in the circuit. The simulator rescales the remaining amplitudes to keep the state normalized. Attempts to set amplitudes that violate normalization, target out-of-range states, or use invalid From ddc8b455674896251ab5e0933c92b1f67534d2e4 Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Tue, 16 Dec 2025 10:51:02 +0800 Subject: [PATCH 058/145] adjusted dotstrings --- bindings/InterfaceBindings.cpp | 19 +++++++++---------- include/backend/dd/DDSimDebug.hpp | 11 ++++++++--- include/backend/debug.h | 4 ++-- python/mqt/debugger/pydebugger.pyi | 15 ++++++++++----- 4 files changed, 29 insertions(+), 20 deletions(-) diff --git a/bindings/InterfaceBindings.cpp b/bindings/InterfaceBindings.cpp index 414f7c91..ebeb619c 100644 --- a/bindings/InterfaceBindings.cpp +++ b/bindings/InterfaceBindings.cpp @@ -304,11 +304,11 @@ bool: True, if the simulation can step backward.)") checkOrThrow(self->changeClassicalVariableValue( self, variableName.c_str(), &value)); }, - R"(Sets the value of the given classical variable. + R"(Updates the value of a classical variable. Args: - variableName (str): The name of the classical variable that should be updated. - newValue (bool | int | float): The desired value.)") + variableName (str): The name of the classical variable to update. + newValue (bool | float): The desired value.)") .def( "change_amplitude_value", [](SimulationState* self, const std::string& basisState, @@ -316,16 +316,15 @@ bool: True, if the simulation can step backward.)") checkOrThrow( self->changeAmplitudeValue(self, basisState.c_str(), &value)); }, - R"(Sets the amplitude of the given computational basis state. + R"(Updates the amplitude of a given computational basis state. -The basis state must be provided as a bitstring (e.g., `010`) whose length -matches the number of qubits in the circuit. The simulator rescales the -remaining amplitudes to keep the state normalized. Attempts to set amplitudes -that violate normalization, target out-of-range states, or use invalid -bitstrings raise an error. +The basis state is provided as a bitstring whose length matches the +current number of qubits. Implementations are expected to renormalize the +remaining amplitudes so that the state vector stays normalized and to +reject invalid bitstrings or amplitudes that violate normalization. Args: - basisState (str): The bitstring describing the basis state whose amplitude should be changed. + basisState (str): The bitstring identifying the basis state to update. value (Complex): The desired complex amplitude.)") .def( "is_finished", diff --git a/include/backend/dd/DDSimDebug.hpp b/include/backend/dd/DDSimDebug.hpp index c9a19f24..bc8ab10e 100644 --- a/include/backend/dd/DDSimDebug.hpp +++ b/include/backend/dd/DDSimDebug.hpp @@ -485,11 +485,11 @@ Result ddsimGetClassicalVariable(SimulationState* self, const char* name, Variable* output); /** - * @brief Update the value of a classical variable. + * @brief Updates the value of a classical variable. * * @param self The instance to query. * @param variableName The name of the classical variable to update. - * @param value The desired value encoded as a `VariableValue`. + * @param value The desired value. * @return The result of the operation. */ Result ddsimChangeClassicalVariableValue(SimulationState* self, @@ -499,8 +499,13 @@ Result ddsimChangeClassicalVariableValue(SimulationState* self, /** * @brief Updates the amplitude of a given computational basis state. * + * The basis state is provided as a bitstring whose length matches the + * current number of qubits. Implementations are expected to renormalize the + * remaining amplitudes so that the state vector stays normalized and to + * reject invalid bitstrings or amplitudes that violate normalization. + * * @param self The instance to query. - * @param basisState The bitstring identifying the basis state to modify. + * @param basisState The bitstring identifying the basis state to update. * @param value The desired complex amplitude. * @return The result of the operation. */ diff --git a/include/backend/debug.h b/include/backend/debug.h index d4510f53..da83f5d5 100644 --- a/include/backend/debug.h +++ b/include/backend/debug.h @@ -179,14 +179,14 @@ struct SimulationStateStruct { * * @param self The instance to query. * @param variableName The name of the classical variable to update. - * @param value The desired value encoded as a `VariableValue`. + * @param value The desired value. * @return The result of the operation. */ Result (*changeClassicalVariableValue)(SimulationState* self, const char* variableName, const VariableValue* value); /** - * @brief Changes the amplitude of a computational basis state. + * @brief Updates the amplitude of a given computational basis state. * * The basis state is provided as a bitstring whose length matches the * current number of qubits. Implementations are expected to renormalize the diff --git a/python/mqt/debugger/pydebugger.pyi b/python/mqt/debugger/pydebugger.pyi index 497dfcce..0ebabad1 100644 --- a/python/mqt/debugger/pydebugger.pyi +++ b/python/mqt/debugger/pydebugger.pyi @@ -160,18 +160,23 @@ class SimulationState: """ def change_classical_variable_value(self, variable_name: str, value: bool | float) -> None: - """Sets the value of a classical variable in the simulation. + """Updates the value of a classical variable. Args: - variable_name (str): The name of the classical variable that should be updated. - value (bool | int | float): The desired value. + variable_name (str): The name of the classical variable to update. + value (bool | float): The desired value. """ def change_amplitude_value(self, basis_state: str, value: Complex) -> None: - """Sets the amplitude of a computational basis state in the simulation. + """Updates the amplitude of a given computational basis state. + + The basis state is provided as a bitstring whose length matches the + current number of qubits. Implementations are expected to renormalize the + remaining amplitudes so that the state vector stays normalized and to + reject invalid bitstrings or amplitudes that violate normalization. Args: - basis_state (str): The bitstring identifying the basis state (e.g. `010`). + basis_state (str): The bitstring identifying the basis state to update. value (Complex): The desired complex amplitude. """ From 4e67409ff81034fa535bdf1267bf9a103480184c Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Tue, 16 Dec 2025 11:09:19 +0800 Subject: [PATCH 059/145] adjusted test_data_retrieval --- test/test_data_retrieval.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/test_data_retrieval.cpp b/test/test_data_retrieval.cpp index ea14bfbf..6ea1edcf 100644 --- a/test/test_data_retrieval.cpp +++ b/test/test_data_retrieval.cpp @@ -307,6 +307,15 @@ TEST_F(DataRetrievalTest, ChangeAmplitudeValueRejectsInvalidBitstring) { ASSERT_EQ(state->changeAmplitudeValue(state, "10a1", &desired), ERROR); } +/** + * @test Test that over-normalized targets are rejected. + */ +TEST_F(DataRetrievalTest, ChangeAmplitudeValueRejectsOverNormalizedTarget) { + const Complex desired{1.1, 0.0}; + forwardTo(12); + ASSERT_EQ(state->changeAmplitudeValue(state, "0010", &desired), ERROR); +} + /** * @test Test that sub-normalized targets without residual amplitudes are * refused. From 7308dab54f0f3d0c1a906a33c1f9657b6ee97857 Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Tue, 16 Dec 2025 11:49:36 +0800 Subject: [PATCH 060/145] adjusted test_python_bindings --- test/python/test_python_bindings.py | 58 +++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/test/python/test_python_bindings.py b/test/python/test_python_bindings.py index c0f81939..7dafa4ca 100644 --- a/test/python/test_python_bindings.py +++ b/test/python/test_python_bindings.py @@ -14,6 +14,7 @@ from __future__ import annotations import locale +import math from pathlib import Path from typing import TYPE_CHECKING, cast @@ -233,6 +234,45 @@ def test_change_amplitude_value(simulation_instance_ghz: SimulationInstance) -> assert updated.imaginary == pytest.approx(0.0, abs=1e-9) +def test_change_amplitude_value_rescales_other_states(simulation_instance_ghz: SimulationInstance) -> None: + """Ensure rewriting a single amplitude rescales the remaining state.""" + (simulation_state, _state_id) = simulation_instance_ghz + simulation_state.run_simulation() + + target_state = "111" + desired = mqt.debugger.Complex(0.25, 0.0) + target_index = int(target_state, 2) + + state_vector = simulation_state.get_state_vector_full() + other_norm_squared = sum( + (amp.real * amp.real) + (amp.imaginary * amp.imaginary) + for idx, amp in enumerate(state_vector.amplitudes) + if idx != target_index + ) + assert other_norm_squared > 0.0 + + interesting_states = ("000", "011") + original_amplitudes = { + bitstring: simulation_state.get_amplitude_bitstring(bitstring) + for bitstring in interesting_states + } + + simulation_state.change_amplitude_value(target_state, desired) + + updated = simulation_state.get_amplitude_bitstring(target_state) + assert updated.real == pytest.approx(desired.real, abs=1e-9) + assert updated.imaginary == pytest.approx(desired.imaginary, abs=1e-9) + + desired_norm = (desired.real * desired.real) + (desired.imaginary * desired.imaginary) + remaining_prob = max(0.0, 1.0 - desired_norm) + expected_scale = math.sqrt(remaining_prob / other_norm_squared) + for bitstring in interesting_states: + current = simulation_state.get_amplitude_bitstring(bitstring) + original = original_amplitudes[bitstring] + assert current.real == pytest.approx(original.real * expected_scale, abs=1e-9) + assert current.imaginary == pytest.approx(original.imaginary * expected_scale, abs=1e-9) + + def test_change_amplitude_value_invalid_bitstring(simulation_instance_ghz: SimulationInstance) -> None: """Ensure invalid amplitude edit requests raise an error at the Python layer.""" (simulation_state, _state_id) = simulation_instance_ghz @@ -244,6 +284,24 @@ def test_change_amplitude_value_invalid_bitstring(simulation_instance_ghz: Simul simulation_state.change_amplitude_value("11a", desired) +def test_change_amplitude_value_rejects_over_normalized_target(simulation_instance_ghz: SimulationInstance) -> None: + """Ensure overly large amplitudes are rejected and surface as Python errors.""" + (simulation_state, _state_id) = simulation_instance_ghz + simulation_state.run_simulation() + desired = mqt.debugger.Complex(1.1, 0.0) + with pytest.raises(RuntimeError): + simulation_state.change_amplitude_value("111", desired) + + +def test_change_amplitude_value_rejects_subnormalized_vacuum(simulation_instance_ghz: SimulationInstance) -> None: + """Ensure we cannot lower the only populated basis state.""" + (simulation_state, _state_id) = simulation_instance_ghz + simulation_state.reset_simulation() + desired = mqt.debugger.Complex(0.5, 0.0) + with pytest.raises(RuntimeError): + simulation_state.change_amplitude_value("000", desired) + + def test_get_state_vector_sub(simulation_instance_classical: SimulationInstance) -> None: """Tests the `get_state_vector_sub()` method.""" (simulation_state, _state_id) = simulation_instance_classical From 42a4bcf1c1166792d718e4f6870af42a98fe362f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 16 Dec 2025 03:51:37 +0000 Subject: [PATCH 061/145] =?UTF-8?q?=F0=9F=8E=A8=20pre-commit=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/python/test_python_bindings.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/python/test_python_bindings.py b/test/python/test_python_bindings.py index 7dafa4ca..4424b2f8 100644 --- a/test/python/test_python_bindings.py +++ b/test/python/test_python_bindings.py @@ -253,8 +253,7 @@ def test_change_amplitude_value_rescales_other_states(simulation_instance_ghz: S interesting_states = ("000", "011") original_amplitudes = { - bitstring: simulation_state.get_amplitude_bitstring(bitstring) - for bitstring in interesting_states + bitstring: simulation_state.get_amplitude_bitstring(bitstring) for bitstring in interesting_states } simulation_state.change_amplitude_value(target_state, desired) From 86cc5c9b0c0a35cf943a70ba1af01d633596b962 Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Tue, 16 Dec 2025 11:58:02 +0800 Subject: [PATCH 062/145] work and progress --- python/mqt/debugger/dap/messages/change_bit_dap_message.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/python/mqt/debugger/dap/messages/change_bit_dap_message.py b/python/mqt/debugger/dap/messages/change_bit_dap_message.py index 494ef33c..83c79a32 100644 --- a/python/mqt/debugger/dap/messages/change_bit_dap_message.py +++ b/python/mqt/debugger/dap/messages/change_bit_dap_message.py @@ -144,9 +144,4 @@ def _apply_change(self, server: DAPServer, name: str) -> bool: msg = f"Failed to set '{name}' to {desired_value}." raise ValueError(msg) from exc - variable = server.simulation_state.get_classical_variable(name) - final_value = bool(variable.value.bool_value) - if final_value != desired_value: - msg = f"Failed to set '{name}' to {desired_value}; current value is {final_value}." - raise ValueError(msg) - return final_value + return desired_value From 5160144fdb700c184a2ab4d114823c7bb5231631 Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Tue, 16 Dec 2025 12:26:26 +0800 Subject: [PATCH 063/145] adjusted test_data_retrieval --- test/test_data_retrieval.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/test_data_retrieval.cpp b/test/test_data_retrieval.cpp index 6ea1edcf..357820d5 100644 --- a/test/test_data_retrieval.cpp +++ b/test/test_data_retrieval.cpp @@ -307,6 +307,15 @@ TEST_F(DataRetrievalTest, ChangeAmplitudeValueRejectsInvalidBitstring) { ASSERT_EQ(state->changeAmplitudeValue(state, "10a1", &desired), ERROR); } +/** + * @test Test that amplitudes with magnitude larger than one are rejected. + */ +TEST_F(DataRetrievalTest, ChangeAmplitudeValueRejectsMagnitudeAboveOne) { + const Complex desired{0.9, 0.6}; // norm^2 > 1, should fail. + forwardTo(12); + ASSERT_EQ(state->changeAmplitudeValue(state, "0010", &desired), ERROR); +} + /** * @test Test that over-normalized targets are rejected. */ From 68a8ffe17c492047bd792504382416697df74ad4 Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Thu, 18 Dec 2025 09:40:01 +0800 Subject: [PATCH 064/145] highlight issue --- python/mqt/debugger/dap/dap_server.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/python/mqt/debugger/dap/dap_server.py b/python/mqt/debugger/dap/dap_server.py index 3dc5f74e..8e38c9d4 100644 --- a/python/mqt/debugger/dap/dap_server.py +++ b/python/mqt/debugger/dap/dap_server.py @@ -528,10 +528,7 @@ def _build_parse_error_highlight(self, line: int, column: int, detail: str) -> d line = line_index + 1 line_text = lines[line_index] stripped = line_text.lstrip() - if stripped: - column = max(1, len(line_text) - len(stripped) + 1) - else: - column = 1 + column = max(1, len(line_text) - len(stripped) + 1) if stripped else 1 end_column = max(column, len(line_text) + 1) snippet = line_text.strip() or line_text From a7c6b72c00fef4e409c03415e67b775992ac4fc8 Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Thu, 18 Dec 2025 21:29:25 +0800 Subject: [PATCH 065/145] assertion issue solved --- python/mqt/debugger/dap/dap_server.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/python/mqt/debugger/dap/dap_server.py b/python/mqt/debugger/dap/dap_server.py index 504b77b7..71a51938 100644 --- a/python/mqt/debugger/dap/dap_server.py +++ b/python/mqt/debugger/dap/dap_server.py @@ -112,6 +112,7 @@ def __init__(self, host: str = "127.0.0.1", port: int = 4711) -> None: self.port = port self.can_step_back = False self.simulation_state = mqt.debugger.SimulationState() + self.source_file = {} self.lines_start_at_one = True self.columns_start_at_one = True @@ -172,6 +173,10 @@ def handle_client(self, connection: socket.socket) -> None: e = mqt.debugger.dap.messages.InitializedDAPEvent() event_payload = json.dumps(e.encode()) send_message(event_payload, connection) + if isinstance( + cmd, (mqt.debugger.dap.messages.LaunchDAPMessage, mqt.debugger.dap.messages.RestartDAPMessage) + ): + self.reset_gray_out(connection) if ( isinstance( cmd, (mqt.debugger.dap.messages.LaunchDAPMessage, mqt.debugger.dap.messages.RestartDAPMessage) @@ -282,6 +287,15 @@ def handle_command(self, command: dict[str, Any]) -> tuple[dict[str, Any], mqt.d msg = f"Unsupported command: {command['command']}" raise RuntimeError(msg) + def reset_gray_out(self, connection: socket.socket) -> None: + """Reset all gray-out highlights in the client.""" + + if not self.source_file: + return + e = mqt.debugger.dap.messages.GrayOutDAPEvent([], self.source_file) + event_payload = json.dumps(e.encode()) + send_message(event_payload, connection) + def handle_assertion_fail(self, connection: socket.socket) -> None: """Handles the sending of output events when an assertion fails. From 8ef979f2651099cb778a879e9a3e9e70e4222bb2 Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Thu, 18 Dec 2025 21:31:01 +0800 Subject: [PATCH 066/145] assertation issue solved --- python/mqt/debugger/dap/dap_server.py | 1 - 1 file changed, 1 deletion(-) diff --git a/python/mqt/debugger/dap/dap_server.py b/python/mqt/debugger/dap/dap_server.py index 71a51938..cdfa1466 100644 --- a/python/mqt/debugger/dap/dap_server.py +++ b/python/mqt/debugger/dap/dap_server.py @@ -289,7 +289,6 @@ def handle_command(self, command: dict[str, Any]) -> tuple[dict[str, Any], mqt.d def reset_gray_out(self, connection: socket.socket) -> None: """Reset all gray-out highlights in the client.""" - if not self.source_file: return e = mqt.debugger.dap.messages.GrayOutDAPEvent([], self.source_file) From 47edfc0900e5ce7b248a531aa3d39cd13d76df15 Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Tue, 23 Dec 2025 00:48:25 +0800 Subject: [PATCH 067/145] highlight issue fix bug --- python/mqt/debugger/dap/dap_server.py | 2 +- src/common/parsing/CodePreprocessing.cpp | 159 ++++++++++++++++++++--- 2 files changed, 143 insertions(+), 18 deletions(-) diff --git a/python/mqt/debugger/dap/dap_server.py b/python/mqt/debugger/dap/dap_server.py index 77cf709a..b1e35817 100644 --- a/python/mqt/debugger/dap/dap_server.py +++ b/python/mqt/debugger/dap/dap_server.py @@ -532,7 +532,7 @@ def _build_parse_error_highlight(self, line: int, column: int, detail: str) -> d line_index = line - 1 line_text = lines[line_index] - if column <= 1 and line_index > 0: + if column <= 1 and line_index > 0 and not line_text.strip(): prev_index = line_index - 1 while prev_index >= 0 and not lines[prev_index].strip(): prev_index -= 1 diff --git a/src/common/parsing/CodePreprocessing.cpp b/src/common/parsing/CodePreprocessing.cpp index e908230a..22aa2225 100644 --- a/src/common/parsing/CodePreprocessing.cpp +++ b/src/common/parsing/CodePreprocessing.cpp @@ -20,6 +20,7 @@ #include "common/parsing/Utils.hpp" #include +#include #include #include #include @@ -33,6 +34,121 @@ namespace mqt::debugger { namespace { +bool isDigits(const std::string& text) { + if (text.empty()) { + return false; + } + return std::all_of(text.begin(), text.end(), + [](unsigned char c) { return std::isdigit(c) != 0; }); +} + +struct LineColumn { + size_t line = 1; + size_t column = 1; +}; + +LineColumn lineColumnForOffset(const std::string& code, size_t offset) { + LineColumn location; + const auto lineStartPos = code.rfind('\n', offset); + const size_t lineStart = (lineStartPos == std::string::npos) + ? 0 + : static_cast(lineStartPos + 1); + location.line = 1; + for (size_t i = 0; i < lineStart; i++) { + if (code[i] == '\n') { + location.line++; + } + } + location.column = offset - lineStart + 1; + return location; +} + +LineColumn lineColumnForTarget(const std::string& code, size_t instructionStart, + const std::string& target) { + LineColumn location = lineColumnForOffset(code, instructionStart); + const auto lineStartPos = code.rfind('\n', instructionStart); + const size_t lineStart = (lineStartPos == std::string::npos) + ? 0 + : static_cast(lineStartPos + 1); + auto lineEndPos = code.find('\n', instructionStart); + const size_t lineEnd = (lineEndPos == std::string::npos) + ? code.size() + : static_cast(lineEndPos); + const auto lineText = code.substr(lineStart, lineEnd - lineStart); + if (!target.empty()) { + const auto targetPos = lineText.find(target); + if (targetPos != std::string::npos) { + location.column = targetPos + 1; + return location; + } + } + const auto nonSpace = lineText.find_first_not_of(" \t"); + if (nonSpace != std::string::npos) { + location.column = nonSpace + 1; + } + return location; +} + +std::string formatParseError(const std::string& code, size_t instructionStart, + const std::string& detail, + const std::string& target = "") { + const auto location = lineColumnForTarget(code, instructionStart, target); + return ":" + std::to_string(location.line) + ":" + + std::to_string(location.column) + ": " + detail; +} + +void validateTargets(const std::string& code, size_t instructionStart, + const std::vector& targets, + const std::map& definedRegisters, + const std::vector& shadowedRegisters, + const std::string& context) { + for (const auto& target : targets) { + if (target.empty()) { + continue; + } + const auto open = target.find('['); + if (open == std::string::npos) { + continue; + } + const auto close = target.find(']', open + 1); + if (open == 0 || close == std::string::npos || + close != target.size() - 1) { + throw ParsingError( + formatParseError(code, instructionStart, + "Invalid target qubit " + target + context + ".", + target)); + } + const auto registerName = target.substr(0, open); + const auto indexText = target.substr(open + 1, close - open - 1); + if (!isDigits(indexText)) { + throw ParsingError( + formatParseError(code, instructionStart, + "Invalid target qubit " + target + context + ".", + target)); + } + size_t registerIndex = 0; + try { + registerIndex = std::stoul(indexText); + } catch (const std::exception&) { + throw ParsingError( + formatParseError(code, instructionStart, + "Invalid target qubit " + target + context + ".", + target)); + } + if (std::ranges::find(shadowedRegisters, registerName) != + shadowedRegisters.end()) { + continue; + } + const auto found = definedRegisters.find(registerName); + if (found == definedRegisters.end() || found->second <= registerIndex) { + throw ParsingError( + formatParseError(code, instructionStart, + "Invalid target qubit " + target + context + ".", + target)); + } + } +} + /** * @brief Sweep a given code string for blocks and replace them with a unique * identifier. @@ -332,7 +448,12 @@ preprocessCode(const std::string& code, size_t startIndex, auto isAssert = isAssertion(line); auto blockPos = line.find("$__block"); - const size_t trueStart = pos + blocksOffset; + const auto leadingPos = + blocksRemoved.find_first_not_of(" \t\r\n", pos); + const size_t trueStart = + ((leadingPos != std::string::npos && leadingPos < end) ? leadingPos + : pos) + + blocksOffset; Block block{.valid = false, .code = ""}; if (blockPos != std::string::npos) { @@ -358,7 +479,20 @@ preprocessCode(const std::string& code, size_t startIndex, replaceString(replaceString(trimmedLine, "creg", ""), "qreg", "")); const auto parts = splitString(declaration, {'[', ']'}); const auto& name = parts[0]; - const auto size = std::stoi(parts[1]); + const auto sizeText = parts.size() > 1 ? parts[1] : ""; + if (name.empty() || !isDigits(sizeText)) { + throw ParsingError(formatParseError( + code, trueStart, + "Invalid register declaration " + trimmedLine + ".")); + } + size_t size = 0; + try { + size = std::stoul(sizeText); + } catch (const std::exception&) { + throw ParsingError(formatParseError( + code, trueStart, + "Invalid register declaration " + trimmedLine + ".")); + } definedRegisters.insert({name, size}); } @@ -423,25 +557,16 @@ preprocessCode(const std::string& code, size_t startIndex, auto a = parseAssertion(line, block.code); unfoldAssertionTargetRegisters(*a, definedRegisters, shadowedRegisters); a->validate(); - for (const auto& target : a->getTargetQubits()) { - if (std::ranges::find(shadowedRegisters, target) != - shadowedRegisters.end()) { - continue; - } - const auto registerName = variableBaseName(target); - const auto registerIndex = - std::stoul(splitString(splitString(target, '[')[1], ']')[0]); - - if (!definedRegisters.contains(registerName) || - definedRegisters[registerName] <= registerIndex) { - throw ParsingError("Invalid target qubit " + target + - " in assertion."); - } - } + validateTargets(code, trueStart, a->getTargetQubits(), definedRegisters, + shadowedRegisters, " in assertion"); instructions.emplace_back(i, line, a, a->getTargetQubits(), trueStart, trueEnd, i + 1, isFunctionCall, calledFunction, false, false, block); } else { + if (!isVariableDeclaration(line)) { + validateTargets(code, trueStart, targets, definedRegisters, + shadowedRegisters, ""); + } std::unique_ptr a(nullptr); instructions.emplace_back(i, line, a, targets, trueStart, trueEnd, i + 1, isFunctionCall, calledFunction, false, false, From 7b8fa091515ccba8df82c580ddac0c84611599b3 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 23 Dec 2025 12:38:29 +0000 Subject: [PATCH 068/145] =?UTF-8?q?=F0=9F=8E=A8=20pre-commit=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/common/parsing/CodePreprocessing.cpp | 46 +++++++++++------------- 1 file changed, 20 insertions(+), 26 deletions(-) diff --git a/src/common/parsing/CodePreprocessing.cpp b/src/common/parsing/CodePreprocessing.cpp index 22aa2225..bfdff312 100644 --- a/src/common/parsing/CodePreprocessing.cpp +++ b/src/common/parsing/CodePreprocessing.cpp @@ -111,29 +111,25 @@ void validateTargets(const std::string& code, size_t instructionStart, continue; } const auto close = target.find(']', open + 1); - if (open == 0 || close == std::string::npos || - close != target.size() - 1) { - throw ParsingError( - formatParseError(code, instructionStart, - "Invalid target qubit " + target + context + ".", - target)); + if (open == 0 || close == std::string::npos || close != target.size() - 1) { + throw ParsingError(formatParseError( + code, instructionStart, + "Invalid target qubit " + target + context + ".", target)); } const auto registerName = target.substr(0, open); const auto indexText = target.substr(open + 1, close - open - 1); if (!isDigits(indexText)) { - throw ParsingError( - formatParseError(code, instructionStart, - "Invalid target qubit " + target + context + ".", - target)); + throw ParsingError(formatParseError( + code, instructionStart, + "Invalid target qubit " + target + context + ".", target)); } size_t registerIndex = 0; try { registerIndex = std::stoul(indexText); } catch (const std::exception&) { - throw ParsingError( - formatParseError(code, instructionStart, - "Invalid target qubit " + target + context + ".", - target)); + throw ParsingError(formatParseError( + code, instructionStart, + "Invalid target qubit " + target + context + ".", target)); } if (std::ranges::find(shadowedRegisters, registerName) != shadowedRegisters.end()) { @@ -141,10 +137,9 @@ void validateTargets(const std::string& code, size_t instructionStart, } const auto found = definedRegisters.find(registerName); if (found == definedRegisters.end() || found->second <= registerIndex) { - throw ParsingError( - formatParseError(code, instructionStart, - "Invalid target qubit " + target + context + ".", - target)); + throw ParsingError(formatParseError( + code, instructionStart, + "Invalid target qubit " + target + context + ".", target)); } } } @@ -448,8 +443,7 @@ preprocessCode(const std::string& code, size_t startIndex, auto isAssert = isAssertion(line); auto blockPos = line.find("$__block"); - const auto leadingPos = - blocksRemoved.find_first_not_of(" \t\r\n", pos); + const auto leadingPos = blocksRemoved.find_first_not_of(" \t\r\n", pos); const size_t trueStart = ((leadingPos != std::string::npos && leadingPos < end) ? leadingPos : pos) + @@ -481,17 +475,17 @@ preprocessCode(const std::string& code, size_t startIndex, const auto& name = parts[0]; const auto sizeText = parts.size() > 1 ? parts[1] : ""; if (name.empty() || !isDigits(sizeText)) { - throw ParsingError(formatParseError( - code, trueStart, - "Invalid register declaration " + trimmedLine + ".")); + throw ParsingError(formatParseError(code, trueStart, + "Invalid register declaration " + + trimmedLine + ".")); } size_t size = 0; try { size = std::stoul(sizeText); } catch (const std::exception&) { - throw ParsingError(formatParseError( - code, trueStart, - "Invalid register declaration " + trimmedLine + ".")); + throw ParsingError(formatParseError(code, trueStart, + "Invalid register declaration " + + trimmedLine + ".")); } definedRegisters.insert({name, size}); } From ec64afdc8944edb144d1f1a8aa793dcc9c24371a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 20 Dec 2025 04:02:47 +0000 Subject: [PATCH 069/145] =?UTF-8?q?=E2=AC=86=EF=B8=8F=F0=9F=91=A8=E2=80=8D?= =?UTF-8?q?=F0=9F=92=BB=20Update=20actions/attest-build-provenance=20actio?= =?UTF-8?q?n=20to=20v3.1.0=20(#234)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/cd.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 2a76c2e5..6ecf084c 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -52,7 +52,7 @@ jobs: path: dist merge-multiple: true - name: Generate artifact attestation for sdist and wheel(s) - uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a # v3.0.0 + uses: actions/attest-build-provenance@00014ed6ed5efc5b1ab7f7f34a39eb55d41aa4f8 # v3.1.0 with: subject-path: dist/* - uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0 From 32d14d860ef7a2ef8877ae0b7f40b04450c2bdea Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 22 Dec 2025 03:10:03 +0000 Subject: [PATCH 070/145] =?UTF-8?q?=E2=AC=86=EF=B8=8F=F0=9F=94=92=EF=B8=8F?= =?UTF-8?q?=20Lock=20file=20maintenance=20(#236)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- uv.lock | 300 +++++++++++++++++++++++++++++--------------------------- 1 file changed, 155 insertions(+), 145 deletions(-) diff --git a/uv.lock b/uv.lock index b636ae4c..5c57ec95 100644 --- a/uv.lock +++ b/uv.lock @@ -419,7 +419,7 @@ resolution-markers = [ "python_full_version == '3.11.*'", ] dependencies = [ - { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "numpy", version = "2.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/58/01/1253e6698a07380cd31a736d248a3f2a50a7c88779a1813da27503cadc2a/contourpy-1.3.3.tar.gz", hash = "sha256:083e12155b210502d0bca491432bb04d56dc3432f95a979b429f2848c3dbe880", size = 13466174, upload-time = "2025-07-26T12:03:12.549Z" } wheels = [ @@ -611,31 +611,31 @@ wheels = [ [[package]] name = "debugpy" -version = "1.8.18" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/62/1a/7cb5531840d7ba5d9329644109e62adee41f2f0083d9f8a4039f01de58cf/debugpy-1.8.18.tar.gz", hash = "sha256:02551b1b84a91faadd2db9bc4948873f2398190c95b3cc6f97dc706f43e8c433", size = 1644467, upload-time = "2025-12-10T19:48:07.236Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e7/38/0136815d2425fda176b30f0ec0b0f299d7316db46b36420e48399eca42e2/debugpy-1.8.18-cp310-cp310-macosx_15_0_x86_64.whl", hash = "sha256:d44e9c531f2519ec4b856ddde8f536615918f5b7886c658a81bf200c90315f77", size = 2098460, upload-time = "2025-12-10T19:48:08.924Z" }, - { url = "https://files.pythonhosted.org/packages/bc/d9/2f00867bea3e50fee298b37602ac7aec9915bdb7227756d4cef889671c4a/debugpy-1.8.18-cp310-cp310-manylinux_2_34_x86_64.whl", hash = "sha256:a69ef7d6050e5d26cf8e0081c6b591a41383dc18db734c4acafdd49568bb7a6f", size = 3087841, upload-time = "2025-12-10T19:48:10.326Z" }, - { url = "https://files.pythonhosted.org/packages/0e/c1/54e50f376d394e0d3d355149d3d85b575e861d57ec0d0ff409c4bd51f531/debugpy-1.8.18-cp310-cp310-win32.whl", hash = "sha256:971965e264faed48ae961ff1e1ad2ce32d8e0cc550a4baa7643a25f1782b7125", size = 5233663, upload-time = "2025-12-10T19:48:12.668Z" }, - { url = "https://files.pythonhosted.org/packages/14/84/1142d16ee87f9bf4db5857b0b38468af602815eb73a9927436c79619beed/debugpy-1.8.18-cp310-cp310-win_amd64.whl", hash = "sha256:0701d83c4c1a74ed2c9abdabce102b1daf24cf81e1802421980871c9ee41f371", size = 5265361, upload-time = "2025-12-10T19:48:14.071Z" }, - { url = "https://files.pythonhosted.org/packages/ac/72/93167809b44a8e6971a1ff0b3e956cca4832fd7e8e47ce7b2b16be95795a/debugpy-1.8.18-cp311-cp311-macosx_15_0_universal2.whl", hash = "sha256:3dae1d65e581406a4d7c1bb44391f47e621b8c87c5639b6607e6007a5d823205", size = 2207588, upload-time = "2025-12-10T19:48:15.44Z" }, - { url = "https://files.pythonhosted.org/packages/05/8b/0f5a54b239dac880ccc16e0b29fdecfb444635f2495cc3705548e24938ab/debugpy-1.8.18-cp311-cp311-manylinux_2_34_x86_64.whl", hash = "sha256:8804d1288e6006629a87d53eb44b7b66e695d428ac529ffd75bfc7d730a9c821", size = 3170762, upload-time = "2025-12-10T19:48:17.192Z" }, - { url = "https://files.pythonhosted.org/packages/e6/e4/7631d0ecd102085aa1cf5eb38f50e00036dec2c4571f236d2189ed842ee3/debugpy-1.8.18-cp311-cp311-win32.whl", hash = "sha256:ded8a5a413bd0a249b3c0be9f43128f437755180ac431222a6354c7d76a76a54", size = 5158530, upload-time = "2025-12-10T19:48:18.701Z" }, - { url = "https://files.pythonhosted.org/packages/c0/51/97674a4af4dc960a4eb0882b6c41c111e6a0a79c6b275df202f392e751cb/debugpy-1.8.18-cp311-cp311-win_amd64.whl", hash = "sha256:df6c1243dedcb6bf9a5dc1c5668009e2b5508b8525f27d9821be91da57827743", size = 5182452, upload-time = "2025-12-10T19:48:20.328Z" }, - { url = "https://files.pythonhosted.org/packages/83/01/439626e3572a33ac543f25bc1dac1e80bc01c7ce83f3c24dc4441302ca13/debugpy-1.8.18-cp312-cp312-macosx_15_0_universal2.whl", hash = "sha256:530c38114725505a7e4ea95328dbc24aabb9be708c6570623c8163412e6d1d6b", size = 2549961, upload-time = "2025-12-10T19:48:21.73Z" }, - { url = "https://files.pythonhosted.org/packages/cd/73/1eeaa15c20a2b627be57a65bc1ebf2edd8d896950eac323588b127d776f2/debugpy-1.8.18-cp312-cp312-manylinux_2_34_x86_64.whl", hash = "sha256:a114865099283cbed4c9330cb0c9cb7a04cfa92e803577843657302d526141ec", size = 4309855, upload-time = "2025-12-10T19:48:23.41Z" }, - { url = "https://files.pythonhosted.org/packages/e4/6f/2da8ded21ae55df7067e57bd7f67ffed7e08b634f29bdba30c03d3f19918/debugpy-1.8.18-cp312-cp312-win32.whl", hash = "sha256:4d26736dfabf404e9f3032015ec7b0189e7396d0664e29e5bdbe7ac453043c95", size = 5280577, upload-time = "2025-12-10T19:48:25.386Z" }, - { url = "https://files.pythonhosted.org/packages/f5/8e/ebe887218c5b84f9421de7eb7bb7cdf196e84535c3f504a562219297d755/debugpy-1.8.18-cp312-cp312-win_amd64.whl", hash = "sha256:7e68ba950acbcf95ee862210133681f408cbb78d1c9badbb515230ec55ed6487", size = 5322458, upload-time = "2025-12-10T19:48:28.049Z" }, - { url = "https://files.pythonhosted.org/packages/fe/3f/45af037e91e308274a092eb6a86282865fb1f11148cdb7616e811aae33d7/debugpy-1.8.18-cp313-cp313-macosx_15_0_universal2.whl", hash = "sha256:75d14dd04b617ee38e46786394ec0dd5e1ac5e3d10ffb034fd6c7b72111174c2", size = 2538826, upload-time = "2025-12-10T19:48:29.434Z" }, - { url = "https://files.pythonhosted.org/packages/cc/f4/2de6bf624de05134d1bbe0a8750d484363cd212c3ade3d04f5c77d47d0ce/debugpy-1.8.18-cp313-cp313-manylinux_2_34_x86_64.whl", hash = "sha256:1b224887af5121fa702f9f542968170d104e3f9cac827d85fdefe89702dc235c", size = 4292542, upload-time = "2025-12-10T19:48:30.836Z" }, - { url = "https://files.pythonhosted.org/packages/93/54/89de7ef84d5ac39fc64a773feaedd902536cc5295814cd22d19c6d9dea35/debugpy-1.8.18-cp313-cp313-win32.whl", hash = "sha256:636a5445a3336e4aba323a3545ca2bb373b04b0bc14084a4eb20c989db44429f", size = 5280460, upload-time = "2025-12-10T19:48:32.696Z" }, - { url = "https://files.pythonhosted.org/packages/4f/59/651329e618406229edbef6508a5aa05e43cd027f042740c5b27e46854b23/debugpy-1.8.18-cp313-cp313-win_amd64.whl", hash = "sha256:6da217ac8c1152d698b9809484d50c75bef9cc02fd6886a893a6df81ec952ff8", size = 5322399, upload-time = "2025-12-10T19:48:35.057Z" }, - { url = "https://files.pythonhosted.org/packages/36/59/5e8bf46a66ca9dfcd0ce4f35c07085aeb60d99bf5c52135973a4e197ed41/debugpy-1.8.18-cp314-cp314-macosx_15_0_universal2.whl", hash = "sha256:be7f622d250fe3429571e84572eb771023f1da22c754f28d2c60a10d74a4cc1b", size = 2537336, upload-time = "2025-12-10T19:48:36.463Z" }, - { url = "https://files.pythonhosted.org/packages/a1/5a/3b37cc266a69da83a4febaa4267bb2062d4bec5287036e2f23d9a30a788c/debugpy-1.8.18-cp314-cp314-manylinux_2_34_x86_64.whl", hash = "sha256:df8bf7cd78019d5d155213bf5a1818b36403d0c3758d669e76827d4db026b840", size = 4268696, upload-time = "2025-12-10T19:48:37.855Z" }, - { url = "https://files.pythonhosted.org/packages/de/4b/1e13586444440e5754b70055449b70afa187aaa167fa4c20c0c05d9c3b80/debugpy-1.8.18-cp314-cp314-win32.whl", hash = "sha256:32dd56d50fe15c47d0f930a7f0b9d3e5eb8ed04770bc6c313fba6d226f87e1e8", size = 5280624, upload-time = "2025-12-10T19:48:39.28Z" }, - { url = "https://files.pythonhosted.org/packages/7a/21/f8c12baa16212859269dc4c3e4b413778ec1154d332896d3c4cca96ac660/debugpy-1.8.18-cp314-cp314-win_amd64.whl", hash = "sha256:714b61d753cfe3ed5e7bf0aad131506d750e271726ac86e3e265fd7eeebbe765", size = 5321982, upload-time = "2025-12-10T19:48:41.086Z" }, - { url = "https://files.pythonhosted.org/packages/dc/0d/bf7ac329c132436c57124202b5b5ccd6366e5d8e75eeb184cf078c826e8d/debugpy-1.8.18-py2.py3-none-any.whl", hash = "sha256:ab8cf0abe0fe2dfe1f7e65abc04b1db8740f9be80c1274acb625855c5c3ece6e", size = 5286576, upload-time = "2025-12-10T19:48:56.071Z" }, +version = "1.8.19" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/73/75/9e12d4d42349b817cd545b89247696c67917aab907012ae5b64bbfea3199/debugpy-1.8.19.tar.gz", hash = "sha256:eea7e5987445ab0b5ed258093722d5ecb8bb72217c5c9b1e21f64efe23ddebdb", size = 1644590, upload-time = "2025-12-15T21:53:28.044Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bf/98/d57054371887f37d3c959a7a8dc3c76b763acb65f5e78d849d7db7cadc5b/debugpy-1.8.19-cp310-cp310-macosx_15_0_x86_64.whl", hash = "sha256:fce6da15d73be5935b4438435c53adb512326a3e11e4f90793ea87cd9f018254", size = 2098493, upload-time = "2025-12-15T21:53:30.149Z" }, + { url = "https://files.pythonhosted.org/packages/ee/dd/c517b9aa3500157a30e4f4c4f5149f880026bd039d2b940acd2383a85d8e/debugpy-1.8.19-cp310-cp310-manylinux_2_34_x86_64.whl", hash = "sha256:e24b1652a1df1ab04d81e7ead446a91c226de704ff5dde6bd0a0dbaab07aa3f2", size = 3087875, upload-time = "2025-12-15T21:53:31.511Z" }, + { url = "https://files.pythonhosted.org/packages/d8/57/3d5a5b0da9b63445253107ead151eff29190c6ad7440c68d1a59d56613aa/debugpy-1.8.19-cp310-cp310-win32.whl", hash = "sha256:327cb28c3ad9e17bc925efc7f7018195fd4787c2fe4b7af1eec11f1d19bdec62", size = 5239378, upload-time = "2025-12-15T21:53:32.979Z" }, + { url = "https://files.pythonhosted.org/packages/a6/36/7f9053c4c549160c87ae7e43800138f2695578c8b65947114c97250983b6/debugpy-1.8.19-cp310-cp310-win_amd64.whl", hash = "sha256:b7dd275cf2c99e53adb9654f5ae015f70415bbe2bacbe24cfee30d54b6aa03c5", size = 5271129, upload-time = "2025-12-15T21:53:35.085Z" }, + { url = "https://files.pythonhosted.org/packages/80/e2/48531a609b5a2aa94c6b6853afdfec8da05630ab9aaa96f1349e772119e9/debugpy-1.8.19-cp311-cp311-macosx_15_0_universal2.whl", hash = "sha256:c5dcfa21de1f735a4f7ced4556339a109aa0f618d366ede9da0a3600f2516d8b", size = 2207620, upload-time = "2025-12-15T21:53:37.1Z" }, + { url = "https://files.pythonhosted.org/packages/1b/d4/97775c01d56071969f57d93928899e5616a4cfbbf4c8cc75390d3a51c4a4/debugpy-1.8.19-cp311-cp311-manylinux_2_34_x86_64.whl", hash = "sha256:806d6800246244004625d5222d7765874ab2d22f3ba5f615416cf1342d61c488", size = 3170796, upload-time = "2025-12-15T21:53:38.513Z" }, + { url = "https://files.pythonhosted.org/packages/8d/7e/8c7681bdb05be9ec972bbb1245eb7c4c7b0679bb6a9e6408d808bc876d3d/debugpy-1.8.19-cp311-cp311-win32.whl", hash = "sha256:783a519e6dfb1f3cd773a9bda592f4887a65040cb0c7bd38dde410f4e53c40d4", size = 5164287, upload-time = "2025-12-15T21:53:40.857Z" }, + { url = "https://files.pythonhosted.org/packages/f2/a8/aaac7ff12ddf5d68a39e13a423a8490426f5f661384f5ad8d9062761bd8e/debugpy-1.8.19-cp311-cp311-win_amd64.whl", hash = "sha256:14035cbdbb1fe4b642babcdcb5935c2da3b1067ac211c5c5a8fdc0bb31adbcaa", size = 5188269, upload-time = "2025-12-15T21:53:42.359Z" }, + { url = "https://files.pythonhosted.org/packages/4a/15/d762e5263d9e25b763b78be72dc084c7a32113a0bac119e2f7acae7700ed/debugpy-1.8.19-cp312-cp312-macosx_15_0_universal2.whl", hash = "sha256:bccb1540a49cde77edc7ce7d9d075c1dbeb2414751bc0048c7a11e1b597a4c2e", size = 2549995, upload-time = "2025-12-15T21:53:43.773Z" }, + { url = "https://files.pythonhosted.org/packages/a7/88/f7d25c68b18873b7c53d7c156ca7a7ffd8e77073aa0eac170a9b679cf786/debugpy-1.8.19-cp312-cp312-manylinux_2_34_x86_64.whl", hash = "sha256:e9c68d9a382ec754dc05ed1d1b4ed5bd824b9f7c1a8cd1083adb84b3c93501de", size = 4309891, upload-time = "2025-12-15T21:53:45.26Z" }, + { url = "https://files.pythonhosted.org/packages/c5/4f/a65e973aba3865794da65f71971dca01ae66666132c7b2647182d5be0c5f/debugpy-1.8.19-cp312-cp312-win32.whl", hash = "sha256:6599cab8a783d1496ae9984c52cb13b7c4a3bd06a8e6c33446832a5d97ce0bee", size = 5286355, upload-time = "2025-12-15T21:53:46.763Z" }, + { url = "https://files.pythonhosted.org/packages/d8/3a/d3d8b48fec96e3d824e404bf428276fb8419dfa766f78f10b08da1cb2986/debugpy-1.8.19-cp312-cp312-win_amd64.whl", hash = "sha256:66e3d2fd8f2035a8f111eb127fa508469dfa40928a89b460b41fd988684dc83d", size = 5328239, upload-time = "2025-12-15T21:53:48.868Z" }, + { url = "https://files.pythonhosted.org/packages/71/3d/388035a31a59c26f1ecc8d86af607d0c42e20ef80074147cd07b180c4349/debugpy-1.8.19-cp313-cp313-macosx_15_0_universal2.whl", hash = "sha256:91e35db2672a0abaf325f4868fcac9c1674a0d9ad9bb8a8c849c03a5ebba3e6d", size = 2538859, upload-time = "2025-12-15T21:53:50.478Z" }, + { url = "https://files.pythonhosted.org/packages/4a/19/c93a0772d0962294f083dbdb113af1a7427bb632d36e5314297068f55db7/debugpy-1.8.19-cp313-cp313-manylinux_2_34_x86_64.whl", hash = "sha256:85016a73ab84dea1c1f1dcd88ec692993bcbe4532d1b49ecb5f3c688ae50c606", size = 4292575, upload-time = "2025-12-15T21:53:51.821Z" }, + { url = "https://files.pythonhosted.org/packages/5c/56/09e48ab796b0a77e3d7dc250f95251832b8bf6838c9632f6100c98bdf426/debugpy-1.8.19-cp313-cp313-win32.whl", hash = "sha256:b605f17e89ba0ecee994391194285fada89cee111cfcd29d6f2ee11cbdc40976", size = 5286209, upload-time = "2025-12-15T21:53:53.602Z" }, + { url = "https://files.pythonhosted.org/packages/fb/4e/931480b9552c7d0feebe40c73725dd7703dcc578ba9efc14fe0e6d31cfd1/debugpy-1.8.19-cp313-cp313-win_amd64.whl", hash = "sha256:c30639998a9f9cd9699b4b621942c0179a6527f083c72351f95c6ab1728d5b73", size = 5328206, upload-time = "2025-12-15T21:53:55.433Z" }, + { url = "https://files.pythonhosted.org/packages/f6/b9/cbec520c3a00508327476c7fce26fbafef98f412707e511eb9d19a2ef467/debugpy-1.8.19-cp314-cp314-macosx_15_0_universal2.whl", hash = "sha256:1e8c4d1bd230067bf1bbcdbd6032e5a57068638eb28b9153d008ecde288152af", size = 2537372, upload-time = "2025-12-15T21:53:57.318Z" }, + { url = "https://files.pythonhosted.org/packages/88/5e/cf4e4dc712a141e10d58405c58c8268554aec3c35c09cdcda7535ff13f76/debugpy-1.8.19-cp314-cp314-manylinux_2_34_x86_64.whl", hash = "sha256:d40c016c1f538dbf1762936e3aeb43a89b965069d9f60f9e39d35d9d25e6b809", size = 4268729, upload-time = "2025-12-15T21:53:58.712Z" }, + { url = "https://files.pythonhosted.org/packages/82/a3/c91a087ab21f1047db328c1d3eb5d1ff0e52de9e74f9f6f6fa14cdd93d58/debugpy-1.8.19-cp314-cp314-win32.whl", hash = "sha256:0601708223fe1cd0e27c6cce67a899d92c7d68e73690211e6788a4b0e1903f5b", size = 5286388, upload-time = "2025-12-15T21:54:00.687Z" }, + { url = "https://files.pythonhosted.org/packages/17/b8/bfdc30b6e94f1eff09f2dc9cc1f9cd1c6cde3d996bcbd36ce2d9a4956e99/debugpy-1.8.19-cp314-cp314-win_amd64.whl", hash = "sha256:8e19a725f5d486f20e53a1dde2ab8bb2c9607c40c00a42ab646def962b41125f", size = 5327741, upload-time = "2025-12-15T21:54:02.148Z" }, + { url = "https://files.pythonhosted.org/packages/25/3e/e27078370414ef35fafad2c06d182110073daaeb5d3bf734b0b1eeefe452/debugpy-1.8.19-py2.py3-none-any.whl", hash = "sha256:360ffd231a780abbc414ba0f005dad409e71c78637efe8f2bd75837132a41d38", size = 5292321, upload-time = "2025-12-15T21:54:16.024Z" }, ] [[package]] @@ -728,11 +728,11 @@ wheels = [ [[package]] name = "filelock" -version = "3.20.0" +version = "3.20.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/58/46/0028a82567109b5ef6e4d2a1f04a583fb513e6cf9527fcdd09afd817deeb/filelock-3.20.0.tar.gz", hash = "sha256:711e943b4ec6be42e1d4e6690b48dc175c822967466bb31c0c293f34334c13f4", size = 18922, upload-time = "2025-10-08T18:03:50.056Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a7/23/ce7a1126827cedeb958fc043d61745754464eb56c5937c35bbf2b8e26f34/filelock-3.20.1.tar.gz", hash = "sha256:b8360948b351b80f420878d8516519a2204b07aefcdcfd24912a5d33127f188c", size = 19476, upload-time = "2025-12-15T23:54:28.027Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/76/91/7216b27286936c16f5b4d0c530087e4a54eead683e6b0b73dd0c64844af6/filelock-3.20.0-py3-none-any.whl", hash = "sha256:339b4732ffda5cd79b13f4e2711a31b0365ce445d95d243bb996273d072546a2", size = 16054, upload-time = "2025-10-08T18:03:48.35Z" }, + { url = "https://files.pythonhosted.org/packages/e3/7f/a1a97644e39e7316d850784c642093c99df1290a460df4ede27659056834/filelock-3.20.1-py3-none-any.whl", hash = "sha256:15d9e9a67306188a44baa72f569d2bfd803076269365fdea0934385da4dc361a", size = 16666, upload-time = "2025-12-15T23:54:26.874Z" }, ] [[package]] @@ -794,7 +794,7 @@ wheels = [ [[package]] name = "furo" -version = "2025.9.25" +version = "2025.12.19" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "accessible-pygments" }, @@ -804,9 +804,9 @@ dependencies = [ { name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "sphinx-basic-ng" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4e/29/ff3b83a1ffce74676043ab3e7540d398e0b1ce7660917a00d7c4958b93da/furo-2025.9.25.tar.gz", hash = "sha256:3eac05582768fdbbc2bdfa1cdbcdd5d33cfc8b4bd2051729ff4e026a1d7e0a98", size = 1662007, upload-time = "2025-09-25T21:37:19.221Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ec/20/5f5ad4da6a5a27c80f2ed2ee9aee3f9e36c66e56e21c00fde467b2f8f88f/furo-2025.12.19.tar.gz", hash = "sha256:188d1f942037d8b37cd3985b955839fea62baa1730087dc29d157677c857e2a7", size = 1661473, upload-time = "2025-12-19T17:34:40.889Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ba/69/964b55f389c289e16ba2a5dfe587c3c462aac09e24123f09ddf703889584/furo-2025.9.25-py3-none-any.whl", hash = "sha256:2937f68e823b8e37b410c972c371bc2b1d88026709534927158e0cb3fac95afe", size = 340409, upload-time = "2025-09-25T21:37:17.244Z" }, + { url = "https://files.pythonhosted.org/packages/f4/b2/50e9b292b5cac13e9e81272c7171301abc753a60460d21505b606e15cf21/furo-2025.12.19-py3-none-any.whl", hash = "sha256:bb0ead5309f9500130665a26bee87693c41ce4dbdff864dbfb6b0dae4673d24f", size = 339262, upload-time = "2025-12-19T17:34:38.905Z" }, ] [[package]] @@ -875,11 +875,11 @@ wheels = [ [[package]] name = "humanize" -version = "4.14.0" +version = "4.15.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b6/43/50033d25ad96a7f3845f40999b4778f753c3901a11808a584fed7c00d9f5/humanize-4.14.0.tar.gz", hash = "sha256:2fa092705ea640d605c435b1ca82b2866a1b601cdf96f076d70b79a855eba90d", size = 82939, upload-time = "2025-10-15T13:04:51.214Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/66/a3921783d54be8a6870ac4ccffcd15c4dc0dd7fcce51c6d63b8c63935276/humanize-4.15.0.tar.gz", hash = "sha256:1dd098483eb1c7ee8e32eb2e99ad1910baefa4b75c3aff3a82f4d78688993b10", size = 83599, upload-time = "2025-12-20T20:16:13.19Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c3/5b/9512c5fb6c8218332b530f13500c6ff5f3ce3342f35e0dd7be9ac3856fd3/humanize-4.14.0-py3-none-any.whl", hash = "sha256:d57701248d040ad456092820e6fde56c930f17749956ac47f4f655c0c547bfff", size = 132092, upload-time = "2025-10-15T13:04:49.404Z" }, + { url = "https://files.pythonhosted.org/packages/c5/7b/bca5613a0c3b542420cf92bd5e5fb8ebd5435ce1011a091f66bb7693285e/humanize-4.15.0-py3-none-any.whl", hash = "sha256:b1186eb9f5a9749cd9cb8565aee77919dd7c8d076161cf44d70e59e3301e1769", size = 132203, upload-time = "2025-12-20T20:16:11.67Z" }, ] [[package]] @@ -902,14 +902,14 @@ wheels = [ [[package]] name = "importlib-metadata" -version = "8.7.0" +version = "8.7.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "zipp" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/76/66/650a33bd90f786193e4de4b3ad86ea60b53c89b669a5c7be931fac31cdb0/importlib_metadata-8.7.0.tar.gz", hash = "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000", size = 56641, upload-time = "2025-04-27T15:29:01.736Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/49/3b30cad09e7771a4982d9975a8cbf64f00d4a1ececb53297f1d9a7be1b10/importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb", size = 57107, upload-time = "2025-12-21T10:00:19.278Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/20/b0/36bd937216ec521246249be3bf9855081de4c5e06a0c9b4219dbeda50373/importlib_metadata-8.7.0-py3-none-any.whl", hash = "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd", size = 27656, upload-time = "2025-04-27T15:29:00.214Z" }, + { url = "https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151", size = 27865, upload-time = "2025-12-21T10:00:18.329Z" }, ] [[package]] @@ -1335,7 +1335,7 @@ dependencies = [ { name = "fonttools" }, { name = "kiwisolver" }, { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "numpy", version = "2.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "packaging" }, { name = "pillow" }, { name = "pyparsing" }, @@ -1447,7 +1447,7 @@ source = { editable = "." } [package.optional-dependencies] check = [ - { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, + { name = "numpy", version = "2.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.13'" }, { name = "qiskit" }, { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "scipy", version = "1.16.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, @@ -1593,7 +1593,7 @@ wheels = [ [[package]] name = "nbclient" -version = "0.10.2" +version = "0.10.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jupyter-client" }, @@ -1601,9 +1601,9 @@ dependencies = [ { name = "nbformat" }, { name = "traitlets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/87/66/7ffd18d58eae90d5721f9f39212327695b749e23ad44b3881744eaf4d9e8/nbclient-0.10.2.tar.gz", hash = "sha256:90b7fc6b810630db87a6d0c2250b1f0ab4cf4d3c27a299b0cde78a4ed3fd9193", size = 62424, upload-time = "2024-12-19T10:32:27.164Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8d/f3/1f6cf2ede4b026bc5f0b424cb41adf22f9c804e90a4dbd4fdb42291a35d5/nbclient-0.10.3.tar.gz", hash = "sha256:0baf171ee246e3bb2391da0635e719f27dc77d99aef59e0b04dcb935ee04c575", size = 62564, upload-time = "2025-12-19T15:50:09.331Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/34/6d/e7fa07f03a4a7b221d94b4d586edb754a9b0dc3c9e2c93353e9fa4e0d117/nbclient-0.10.2-py3-none-any.whl", hash = "sha256:4ffee11e788b4a27fabeb7955547e4318a5298f34342a4bfd01f2e1faaeadc3d", size = 25434, upload-time = "2024-12-19T10:32:24.139Z" }, + { url = "https://files.pythonhosted.org/packages/b2/77/0c73678f5260501a271fd7342bee5d639440f2e9e07d590f1100a056d87c/nbclient-0.10.3-py3-none-any.whl", hash = "sha256:39e9bd403504dd2484dd0fd25235bb6a683ce8cd9873356e40d880696adc9e35", size = 25473, upload-time = "2025-12-19T15:50:07.671Z" }, ] [[package]] @@ -1716,7 +1716,7 @@ wheels = [ [[package]] name = "numpy" -version = "2.3.5" +version = "2.4.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ "python_full_version >= '3.14'", @@ -1724,81 +1724,79 @@ resolution-markers = [ "python_full_version == '3.12.*'", "python_full_version == '3.11.*'", ] -sdist = { url = "https://files.pythonhosted.org/packages/76/65/21b3bc86aac7b8f2862db1e808f1ea22b028e30a225a34a5ede9bf8678f2/numpy-2.3.5.tar.gz", hash = "sha256:784db1dcdab56bf0517743e746dfb0f885fc68d948aba86eeec2cba234bdf1c0", size = 20584950, upload-time = "2025-11-16T22:52:42.067Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/43/77/84dd1d2e34d7e2792a236ba180b5e8fcc1e3e414e761ce0253f63d7f572e/numpy-2.3.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:de5672f4a7b200c15a4127042170a694d4df43c992948f5e1af57f0174beed10", size = 17034641, upload-time = "2025-11-16T22:49:19.336Z" }, - { url = "https://files.pythonhosted.org/packages/2a/ea/25e26fa5837106cde46ae7d0b667e20f69cbbc0efd64cba8221411ab26ae/numpy-2.3.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:acfd89508504a19ed06ef963ad544ec6664518c863436306153e13e94605c218", size = 12528324, upload-time = "2025-11-16T22:49:22.582Z" }, - { url = "https://files.pythonhosted.org/packages/4d/1a/e85f0eea4cf03d6a0228f5c0256b53f2df4bc794706e7df019fc622e47f1/numpy-2.3.5-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:ffe22d2b05504f786c867c8395de703937f934272eb67586817b46188b4ded6d", size = 5356872, upload-time = "2025-11-16T22:49:25.408Z" }, - { url = "https://files.pythonhosted.org/packages/5c/bb/35ef04afd567f4c989c2060cde39211e4ac5357155c1833bcd1166055c61/numpy-2.3.5-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:872a5cf366aec6bb1147336480fef14c9164b154aeb6542327de4970282cd2f5", size = 6893148, upload-time = "2025-11-16T22:49:27.549Z" }, - { url = "https://files.pythonhosted.org/packages/f2/2b/05bbeb06e2dff5eab512dfc678b1cc5ee94d8ac5956a0885c64b6b26252b/numpy-2.3.5-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3095bdb8dd297e5920b010e96134ed91d852d81d490e787beca7e35ae1d89cf7", size = 14557282, upload-time = "2025-11-16T22:49:30.964Z" }, - { url = "https://files.pythonhosted.org/packages/65/fb/2b23769462b34398d9326081fad5655198fcf18966fcb1f1e49db44fbf31/numpy-2.3.5-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8cba086a43d54ca804ce711b2a940b16e452807acebe7852ff327f1ecd49b0d4", size = 16897903, upload-time = "2025-11-16T22:49:34.191Z" }, - { url = "https://files.pythonhosted.org/packages/ac/14/085f4cf05fc3f1e8aa95e85404e984ffca9b2275a5dc2b1aae18a67538b8/numpy-2.3.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6cf9b429b21df6b99f4dee7a1218b8b7ffbbe7df8764dc0bd60ce8a0708fed1e", size = 16341672, upload-time = "2025-11-16T22:49:37.2Z" }, - { url = "https://files.pythonhosted.org/packages/6f/3b/1f73994904142b2aa290449b3bb99772477b5fd94d787093e4f24f5af763/numpy-2.3.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:396084a36abdb603546b119d96528c2f6263921c50df3c8fd7cb28873a237748", size = 18838896, upload-time = "2025-11-16T22:49:39.727Z" }, - { url = "https://files.pythonhosted.org/packages/cd/b9/cf6649b2124f288309ffc353070792caf42ad69047dcc60da85ee85fea58/numpy-2.3.5-cp311-cp311-win32.whl", hash = "sha256:b0c7088a73aef3d687c4deef8452a3ac7c1be4e29ed8bf3b366c8111128ac60c", size = 6563608, upload-time = "2025-11-16T22:49:42.079Z" }, - { url = "https://files.pythonhosted.org/packages/aa/44/9fe81ae1dcc29c531843852e2874080dc441338574ccc4306b39e2ff6e59/numpy-2.3.5-cp311-cp311-win_amd64.whl", hash = "sha256:a414504bef8945eae5f2d7cb7be2d4af77c5d1cb5e20b296c2c25b61dff2900c", size = 13078442, upload-time = "2025-11-16T22:49:43.99Z" }, - { url = "https://files.pythonhosted.org/packages/6d/a7/f99a41553d2da82a20a2f22e93c94f928e4490bb447c9ff3c4ff230581d3/numpy-2.3.5-cp311-cp311-win_arm64.whl", hash = "sha256:0cd00b7b36e35398fa2d16af7b907b65304ef8bb4817a550e06e5012929830fa", size = 10458555, upload-time = "2025-11-16T22:49:47.092Z" }, - { url = "https://files.pythonhosted.org/packages/44/37/e669fe6cbb2b96c62f6bbedc6a81c0f3b7362f6a59230b23caa673a85721/numpy-2.3.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:74ae7b798248fe62021dbf3c914245ad45d1a6b0cb4a29ecb4b31d0bfbc4cc3e", size = 16733873, upload-time = "2025-11-16T22:49:49.84Z" }, - { url = "https://files.pythonhosted.org/packages/c5/65/df0db6c097892c9380851ab9e44b52d4f7ba576b833996e0080181c0c439/numpy-2.3.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ee3888d9ff7c14604052b2ca5535a30216aa0a58e948cdd3eeb8d3415f638769", size = 12259838, upload-time = "2025-11-16T22:49:52.863Z" }, - { url = "https://files.pythonhosted.org/packages/5b/e1/1ee06e70eb2136797abe847d386e7c0e830b67ad1d43f364dd04fa50d338/numpy-2.3.5-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:612a95a17655e213502f60cfb9bf9408efdc9eb1d5f50535cc6eb365d11b42b5", size = 5088378, upload-time = "2025-11-16T22:49:55.055Z" }, - { url = "https://files.pythonhosted.org/packages/6d/9c/1ca85fb86708724275103b81ec4cf1ac1d08f465368acfc8da7ab545bdae/numpy-2.3.5-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:3101e5177d114a593d79dd79658650fe28b5a0d8abeb8ce6f437c0e6df5be1a4", size = 6628559, upload-time = "2025-11-16T22:49:57.371Z" }, - { url = "https://files.pythonhosted.org/packages/74/78/fcd41e5a0ce4f3f7b003da85825acddae6d7ecb60cf25194741b036ca7d6/numpy-2.3.5-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b973c57ff8e184109db042c842423ff4f60446239bd585a5131cc47f06f789d", size = 14250702, upload-time = "2025-11-16T22:49:59.632Z" }, - { url = "https://files.pythonhosted.org/packages/b6/23/2a1b231b8ff672b4c450dac27164a8b2ca7d9b7144f9c02d2396518352eb/numpy-2.3.5-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0d8163f43acde9a73c2a33605353a4f1bc4798745a8b1d73183b28e5b435ae28", size = 16606086, upload-time = "2025-11-16T22:50:02.127Z" }, - { url = "https://files.pythonhosted.org/packages/a0/c5/5ad26fbfbe2012e190cc7d5003e4d874b88bb18861d0829edc140a713021/numpy-2.3.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:51c1e14eb1e154ebd80e860722f9e6ed6ec89714ad2db2d3aa33c31d7c12179b", size = 16025985, upload-time = "2025-11-16T22:50:04.536Z" }, - { url = "https://files.pythonhosted.org/packages/d2/fa/dd48e225c46c819288148d9d060b047fd2a6fb1eb37eae25112ee4cb4453/numpy-2.3.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b46b4ec24f7293f23adcd2d146960559aaf8020213de8ad1909dba6c013bf89c", size = 18542976, upload-time = "2025-11-16T22:50:07.557Z" }, - { url = "https://files.pythonhosted.org/packages/05/79/ccbd23a75862d95af03d28b5c6901a1b7da4803181513d52f3b86ed9446e/numpy-2.3.5-cp312-cp312-win32.whl", hash = "sha256:3997b5b3c9a771e157f9aae01dd579ee35ad7109be18db0e85dbdbe1de06e952", size = 6285274, upload-time = "2025-11-16T22:50:10.746Z" }, - { url = "https://files.pythonhosted.org/packages/2d/57/8aeaf160312f7f489dea47ab61e430b5cb051f59a98ae68b7133ce8fa06a/numpy-2.3.5-cp312-cp312-win_amd64.whl", hash = "sha256:86945f2ee6d10cdfd67bcb4069c1662dd711f7e2a4343db5cecec06b87cf31aa", size = 12782922, upload-time = "2025-11-16T22:50:12.811Z" }, - { url = "https://files.pythonhosted.org/packages/78/a6/aae5cc2ca78c45e64b9ef22f089141d661516856cf7c8a54ba434576900d/numpy-2.3.5-cp312-cp312-win_arm64.whl", hash = "sha256:f28620fe26bee16243be2b7b874da327312240a7cdc38b769a697578d2100013", size = 10194667, upload-time = "2025-11-16T22:50:16.16Z" }, - { url = "https://files.pythonhosted.org/packages/db/69/9cde09f36da4b5a505341180a3f2e6fadc352fd4d2b7096ce9778db83f1a/numpy-2.3.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d0f23b44f57077c1ede8c5f26b30f706498b4862d3ff0a7298b8411dd2f043ff", size = 16728251, upload-time = "2025-11-16T22:50:19.013Z" }, - { url = "https://files.pythonhosted.org/packages/79/fb/f505c95ceddd7027347b067689db71ca80bd5ecc926f913f1a23e65cf09b/numpy-2.3.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:aa5bc7c5d59d831d9773d1170acac7893ce3a5e130540605770ade83280e7188", size = 12254652, upload-time = "2025-11-16T22:50:21.487Z" }, - { url = "https://files.pythonhosted.org/packages/78/da/8c7738060ca9c31b30e9301ee0cf6c5ffdbf889d9593285a1cead337f9a5/numpy-2.3.5-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:ccc933afd4d20aad3c00bcef049cb40049f7f196e0397f1109dba6fed63267b0", size = 5083172, upload-time = "2025-11-16T22:50:24.562Z" }, - { url = "https://files.pythonhosted.org/packages/a4/b4/ee5bb2537fb9430fd2ef30a616c3672b991a4129bb1c7dcc42aa0abbe5d7/numpy-2.3.5-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:afaffc4393205524af9dfa400fa250143a6c3bc646c08c9f5e25a9f4b4d6a903", size = 6622990, upload-time = "2025-11-16T22:50:26.47Z" }, - { url = "https://files.pythonhosted.org/packages/95/03/dc0723a013c7d7c19de5ef29e932c3081df1c14ba582b8b86b5de9db7f0f/numpy-2.3.5-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c75442b2209b8470d6d5d8b1c25714270686f14c749028d2199c54e29f20b4d", size = 14248902, upload-time = "2025-11-16T22:50:28.861Z" }, - { url = "https://files.pythonhosted.org/packages/f5/10/ca162f45a102738958dcec8023062dad0cbc17d1ab99d68c4e4a6c45fb2b/numpy-2.3.5-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11e06aa0af8c0f05104d56450d6093ee639e15f24ecf62d417329d06e522e017", size = 16597430, upload-time = "2025-11-16T22:50:31.56Z" }, - { url = "https://files.pythonhosted.org/packages/2a/51/c1e29be863588db58175175f057286900b4b3327a1351e706d5e0f8dd679/numpy-2.3.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ed89927b86296067b4f81f108a2271d8926467a8868e554eaf370fc27fa3ccaf", size = 16024551, upload-time = "2025-11-16T22:50:34.242Z" }, - { url = "https://files.pythonhosted.org/packages/83/68/8236589d4dbb87253d28259d04d9b814ec0ecce7cb1c7fed29729f4c3a78/numpy-2.3.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51c55fe3451421f3a6ef9a9c1439e82101c57a2c9eab9feb196a62b1a10b58ce", size = 18533275, upload-time = "2025-11-16T22:50:37.651Z" }, - { url = "https://files.pythonhosted.org/packages/40/56/2932d75b6f13465239e3b7b7e511be27f1b8161ca2510854f0b6e521c395/numpy-2.3.5-cp313-cp313-win32.whl", hash = "sha256:1978155dd49972084bd6ef388d66ab70f0c323ddee6f693d539376498720fb7e", size = 6277637, upload-time = "2025-11-16T22:50:40.11Z" }, - { url = "https://files.pythonhosted.org/packages/0c/88/e2eaa6cffb115b85ed7c7c87775cb8bcf0816816bc98ca8dbfa2ee33fe6e/numpy-2.3.5-cp313-cp313-win_amd64.whl", hash = "sha256:00dc4e846108a382c5869e77c6ed514394bdeb3403461d25a829711041217d5b", size = 12779090, upload-time = "2025-11-16T22:50:42.503Z" }, - { url = "https://files.pythonhosted.org/packages/8f/88/3f41e13a44ebd4034ee17baa384acac29ba6a4fcc2aca95f6f08ca0447d1/numpy-2.3.5-cp313-cp313-win_arm64.whl", hash = "sha256:0472f11f6ec23a74a906a00b48a4dcf3849209696dff7c189714511268d103ae", size = 10194710, upload-time = "2025-11-16T22:50:44.971Z" }, - { url = "https://files.pythonhosted.org/packages/13/cb/71744144e13389d577f867f745b7df2d8489463654a918eea2eeb166dfc9/numpy-2.3.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:414802f3b97f3c1eef41e530aaba3b3c1620649871d8cb38c6eaff034c2e16bd", size = 16827292, upload-time = "2025-11-16T22:50:47.715Z" }, - { url = "https://files.pythonhosted.org/packages/71/80/ba9dc6f2a4398e7f42b708a7fdc841bb638d353be255655498edbf9a15a8/numpy-2.3.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5ee6609ac3604fa7780e30a03e5e241a7956f8e2fcfe547d51e3afa5247ac47f", size = 12378897, upload-time = "2025-11-16T22:50:51.327Z" }, - { url = "https://files.pythonhosted.org/packages/2e/6d/db2151b9f64264bcceccd51741aa39b50150de9b602d98ecfe7e0c4bff39/numpy-2.3.5-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:86d835afea1eaa143012a2d7a3f45a3adce2d7adc8b4961f0b362214d800846a", size = 5207391, upload-time = "2025-11-16T22:50:54.542Z" }, - { url = "https://files.pythonhosted.org/packages/80/ae/429bacace5ccad48a14c4ae5332f6aa8ab9f69524193511d60ccdfdc65fa/numpy-2.3.5-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:30bc11310e8153ca664b14c5f1b73e94bd0503681fcf136a163de856f3a50139", size = 6721275, upload-time = "2025-11-16T22:50:56.794Z" }, - { url = "https://files.pythonhosted.org/packages/74/5b/1919abf32d8722646a38cd527bc3771eb229a32724ee6ba340ead9b92249/numpy-2.3.5-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1062fde1dcf469571705945b0f221b73928f34a20c904ffb45db101907c3454e", size = 14306855, upload-time = "2025-11-16T22:50:59.208Z" }, - { url = "https://files.pythonhosted.org/packages/a5/87/6831980559434973bebc30cd9c1f21e541a0f2b0c280d43d3afd909b66d0/numpy-2.3.5-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ce581db493ea1a96c0556360ede6607496e8bf9b3a8efa66e06477267bc831e9", size = 16657359, upload-time = "2025-11-16T22:51:01.991Z" }, - { url = "https://files.pythonhosted.org/packages/dd/91/c797f544491ee99fd00495f12ebb7802c440c1915811d72ac5b4479a3356/numpy-2.3.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:cc8920d2ec5fa99875b670bb86ddeb21e295cb07aa331810d9e486e0b969d946", size = 16093374, upload-time = "2025-11-16T22:51:05.291Z" }, - { url = "https://files.pythonhosted.org/packages/74/a6/54da03253afcbe7a72785ec4da9c69fb7a17710141ff9ac5fcb2e32dbe64/numpy-2.3.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:9ee2197ef8c4f0dfe405d835f3b6a14f5fee7782b5de51ba06fb65fc9b36e9f1", size = 18594587, upload-time = "2025-11-16T22:51:08.585Z" }, - { url = "https://files.pythonhosted.org/packages/80/e9/aff53abbdd41b0ecca94285f325aff42357c6b5abc482a3fcb4994290b18/numpy-2.3.5-cp313-cp313t-win32.whl", hash = "sha256:70b37199913c1bd300ff6e2693316c6f869c7ee16378faf10e4f5e3275b299c3", size = 6405940, upload-time = "2025-11-16T22:51:11.541Z" }, - { url = "https://files.pythonhosted.org/packages/d5/81/50613fec9d4de5480de18d4f8ef59ad7e344d497edbef3cfd80f24f98461/numpy-2.3.5-cp313-cp313t-win_amd64.whl", hash = "sha256:b501b5fa195cc9e24fe102f21ec0a44dffc231d2af79950b451e0d99cea02234", size = 12920341, upload-time = "2025-11-16T22:51:14.312Z" }, - { url = "https://files.pythonhosted.org/packages/bb/ab/08fd63b9a74303947f34f0bd7c5903b9c5532c2d287bead5bdf4c556c486/numpy-2.3.5-cp313-cp313t-win_arm64.whl", hash = "sha256:a80afd79f45f3c4a7d341f13acbe058d1ca8ac017c165d3fa0d3de6bc1a079d7", size = 10262507, upload-time = "2025-11-16T22:51:16.846Z" }, - { url = "https://files.pythonhosted.org/packages/ba/97/1a914559c19e32d6b2e233cf9a6a114e67c856d35b1d6babca571a3e880f/numpy-2.3.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:bf06bc2af43fa8d32d30fae16ad965663e966b1a3202ed407b84c989c3221e82", size = 16735706, upload-time = "2025-11-16T22:51:19.558Z" }, - { url = "https://files.pythonhosted.org/packages/57/d4/51233b1c1b13ecd796311216ae417796b88b0616cfd8a33ae4536330748a/numpy-2.3.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:052e8c42e0c49d2575621c158934920524f6c5da05a1d3b9bab5d8e259e045f0", size = 12264507, upload-time = "2025-11-16T22:51:22.492Z" }, - { url = "https://files.pythonhosted.org/packages/45/98/2fe46c5c2675b8306d0b4a3ec3494273e93e1226a490f766e84298576956/numpy-2.3.5-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:1ed1ec893cff7040a02c8aa1c8611b94d395590d553f6b53629a4461dc7f7b63", size = 5093049, upload-time = "2025-11-16T22:51:25.171Z" }, - { url = "https://files.pythonhosted.org/packages/ce/0e/0698378989bb0ac5f1660c81c78ab1fe5476c1a521ca9ee9d0710ce54099/numpy-2.3.5-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:2dcd0808a421a482a080f89859a18beb0b3d1e905b81e617a188bd80422d62e9", size = 6626603, upload-time = "2025-11-16T22:51:27Z" }, - { url = "https://files.pythonhosted.org/packages/5e/a6/9ca0eecc489640615642a6cbc0ca9e10df70df38c4d43f5a928ff18d8827/numpy-2.3.5-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:727fd05b57df37dc0bcf1a27767a3d9a78cbbc92822445f32cc3436ba797337b", size = 14262696, upload-time = "2025-11-16T22:51:29.402Z" }, - { url = "https://files.pythonhosted.org/packages/c8/f6/07ec185b90ec9d7217a00eeeed7383b73d7e709dae2a9a021b051542a708/numpy-2.3.5-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fffe29a1ef00883599d1dc2c51aa2e5d80afe49523c261a74933df395c15c520", size = 16597350, upload-time = "2025-11-16T22:51:32.167Z" }, - { url = "https://files.pythonhosted.org/packages/75/37/164071d1dde6a1a84c9b8e5b414fa127981bad47adf3a6b7e23917e52190/numpy-2.3.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8f7f0e05112916223d3f438f293abf0727e1181b5983f413dfa2fefc4098245c", size = 16040190, upload-time = "2025-11-16T22:51:35.403Z" }, - { url = "https://files.pythonhosted.org/packages/08/3c/f18b82a406b04859eb026d204e4e1773eb41c5be58410f41ffa511d114ae/numpy-2.3.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2e2eb32ddb9ccb817d620ac1d8dae7c3f641c1e5f55f531a33e8ab97960a75b8", size = 18536749, upload-time = "2025-11-16T22:51:39.698Z" }, - { url = "https://files.pythonhosted.org/packages/40/79/f82f572bf44cf0023a2fe8588768e23e1592585020d638999f15158609e1/numpy-2.3.5-cp314-cp314-win32.whl", hash = "sha256:66f85ce62c70b843bab1fb14a05d5737741e74e28c7b8b5a064de10142fad248", size = 6335432, upload-time = "2025-11-16T22:51:42.476Z" }, - { url = "https://files.pythonhosted.org/packages/a3/2e/235b4d96619931192c91660805e5e49242389742a7a82c27665021db690c/numpy-2.3.5-cp314-cp314-win_amd64.whl", hash = "sha256:e6a0bc88393d65807d751a614207b7129a310ca4fe76a74e5c7da5fa5671417e", size = 12919388, upload-time = "2025-11-16T22:51:45.275Z" }, - { url = "https://files.pythonhosted.org/packages/07/2b/29fd75ce45d22a39c61aad74f3d718e7ab67ccf839ca8b60866054eb15f8/numpy-2.3.5-cp314-cp314-win_arm64.whl", hash = "sha256:aeffcab3d4b43712bb7a60b65f6044d444e75e563ff6180af8f98dd4b905dfd2", size = 10476651, upload-time = "2025-11-16T22:51:47.749Z" }, - { url = "https://files.pythonhosted.org/packages/17/e1/f6a721234ebd4d87084cfa68d081bcba2f5cfe1974f7de4e0e8b9b2a2ba1/numpy-2.3.5-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:17531366a2e3a9e30762c000f2c43a9aaa05728712e25c11ce1dbe700c53ad41", size = 16834503, upload-time = "2025-11-16T22:51:50.443Z" }, - { url = "https://files.pythonhosted.org/packages/5c/1c/baf7ffdc3af9c356e1c135e57ab7cf8d247931b9554f55c467efe2c69eff/numpy-2.3.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d21644de1b609825ede2f48be98dfde4656aefc713654eeee280e37cadc4e0ad", size = 12381612, upload-time = "2025-11-16T22:51:53.609Z" }, - { url = "https://files.pythonhosted.org/packages/74/91/f7f0295151407ddc9ba34e699013c32c3c91944f9b35fcf9281163dc1468/numpy-2.3.5-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:c804e3a5aba5460c73955c955bdbd5c08c354954e9270a2c1565f62e866bdc39", size = 5210042, upload-time = "2025-11-16T22:51:56.213Z" }, - { url = "https://files.pythonhosted.org/packages/2e/3b/78aebf345104ec50dd50a4d06ddeb46a9ff5261c33bcc58b1c4f12f85ec2/numpy-2.3.5-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:cc0a57f895b96ec78969c34f682c602bf8da1a0270b09bc65673df2e7638ec20", size = 6724502, upload-time = "2025-11-16T22:51:58.584Z" }, - { url = "https://files.pythonhosted.org/packages/02/c6/7c34b528740512e57ef1b7c8337ab0b4f0bddf34c723b8996c675bc2bc91/numpy-2.3.5-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:900218e456384ea676e24ea6a0417f030a3b07306d29d7ad843957b40a9d8d52", size = 14308962, upload-time = "2025-11-16T22:52:01.698Z" }, - { url = "https://files.pythonhosted.org/packages/80/35/09d433c5262bc32d725bafc619e095b6a6651caf94027a03da624146f655/numpy-2.3.5-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:09a1bea522b25109bf8e6f3027bd810f7c1085c64a0c7ce050c1676ad0ba010b", size = 16655054, upload-time = "2025-11-16T22:52:04.267Z" }, - { url = "https://files.pythonhosted.org/packages/7a/ab/6a7b259703c09a88804fa2430b43d6457b692378f6b74b356155283566ac/numpy-2.3.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:04822c00b5fd0323c8166d66c701dc31b7fbd252c100acd708c48f763968d6a3", size = 16091613, upload-time = "2025-11-16T22:52:08.651Z" }, - { url = "https://files.pythonhosted.org/packages/c2/88/330da2071e8771e60d1038166ff9d73f29da37b01ec3eb43cb1427464e10/numpy-2.3.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d6889ec4ec662a1a37eb4b4fb26b6100841804dac55bd9df579e326cdc146227", size = 18591147, upload-time = "2025-11-16T22:52:11.453Z" }, - { url = "https://files.pythonhosted.org/packages/51/41/851c4b4082402d9ea860c3626db5d5df47164a712cb23b54be028b184c1c/numpy-2.3.5-cp314-cp314t-win32.whl", hash = "sha256:93eebbcf1aafdf7e2ddd44c2923e2672e1010bddc014138b229e49725b4d6be5", size = 6479806, upload-time = "2025-11-16T22:52:14.641Z" }, - { url = "https://files.pythonhosted.org/packages/90/30/d48bde1dfd93332fa557cff1972fbc039e055a52021fbef4c2c4b1eefd17/numpy-2.3.5-cp314-cp314t-win_amd64.whl", hash = "sha256:c8a9958e88b65c3b27e22ca2a076311636850b612d6bbfb76e8d156aacde2aaf", size = 13105760, upload-time = "2025-11-16T22:52:17.975Z" }, - { url = "https://files.pythonhosted.org/packages/2d/fd/4b5eb0b3e888d86aee4d198c23acec7d214baaf17ea93c1adec94c9518b9/numpy-2.3.5-cp314-cp314t-win_arm64.whl", hash = "sha256:6203fdf9f3dc5bdaed7319ad8698e685c7a3be10819f41d32a0723e611733b42", size = 10545459, upload-time = "2025-11-16T22:52:20.55Z" }, - { url = "https://files.pythonhosted.org/packages/c6/65/f9dea8e109371ade9c782b4e4756a82edf9d3366bca495d84d79859a0b79/numpy-2.3.5-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:f0963b55cdd70fad460fa4c1341f12f976bb26cb66021a5580329bd498988310", size = 16910689, upload-time = "2025-11-16T22:52:23.247Z" }, - { url = "https://files.pythonhosted.org/packages/00/4f/edb00032a8fb92ec0a679d3830368355da91a69cab6f3e9c21b64d0bb986/numpy-2.3.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:f4255143f5160d0de972d28c8f9665d882b5f61309d8362fdd3e103cf7bf010c", size = 12457053, upload-time = "2025-11-16T22:52:26.367Z" }, - { url = "https://files.pythonhosted.org/packages/16/a4/e8a53b5abd500a63836a29ebe145fc1ab1f2eefe1cfe59276020373ae0aa/numpy-2.3.5-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:a4b9159734b326535f4dd01d947f919c6eefd2d9827466a696c44ced82dfbc18", size = 5285635, upload-time = "2025-11-16T22:52:29.266Z" }, - { url = "https://files.pythonhosted.org/packages/a3/2f/37eeb9014d9c8b3e9c55bc599c68263ca44fdbc12a93e45a21d1d56df737/numpy-2.3.5-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:2feae0d2c91d46e59fcd62784a3a83b3fb677fead592ce51b5a6fbb4f95965ff", size = 6801770, upload-time = "2025-11-16T22:52:31.421Z" }, - { url = "https://files.pythonhosted.org/packages/7d/e4/68d2f474df2cb671b2b6c2986a02e520671295647dad82484cde80ca427b/numpy-2.3.5-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ffac52f28a7849ad7576293c0cb7b9f08304e8f7d738a8cb8a90ec4c55a998eb", size = 14391768, upload-time = "2025-11-16T22:52:33.593Z" }, - { url = "https://files.pythonhosted.org/packages/b8/50/94ccd8a2b141cb50651fddd4f6a48874acb3c91c8f0842b08a6afc4b0b21/numpy-2.3.5-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:63c0e9e7eea69588479ebf4a8a270d5ac22763cc5854e9a7eae952a3908103f7", size = 16729263, upload-time = "2025-11-16T22:52:36.369Z" }, - { url = "https://files.pythonhosted.org/packages/2d/ee/346fa473e666fe14c52fcdd19ec2424157290a032d4c41f98127bfb31ac7/numpy-2.3.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:f16417ec91f12f814b10bafe79ef77e70113a2f5f7018640e7425ff979253425", size = 12967213, upload-time = "2025-11-16T22:52:39.38Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/a4/7a/6a3d14e205d292b738db449d0de649b373a59edb0d0b4493821d0a3e8718/numpy-2.4.0.tar.gz", hash = "sha256:6e504f7b16118198f138ef31ba24d985b124c2c469fe8467007cf30fd992f934", size = 20685720, upload-time = "2025-12-20T16:18:19.023Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/7e/7bae7cbcc2f8132271967aa03e03954fc1e48aa1f3bf32b29ca95fbef352/numpy-2.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:316b2f2584682318539f0bcaca5a496ce9ca78c88066579ebd11fd06f8e4741e", size = 16940166, upload-time = "2025-12-20T16:15:43.434Z" }, + { url = "https://files.pythonhosted.org/packages/0f/27/6c13f5b46776d6246ec884ac5817452672156a506d08a1f2abb39961930a/numpy-2.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a2718c1de8504121714234b6f8241d0019450353276c88b9453c9c3d92e101db", size = 12641781, upload-time = "2025-12-20T16:15:45.701Z" }, + { url = "https://files.pythonhosted.org/packages/14/1c/83b4998d4860d15283241d9e5215f28b40ac31f497c04b12fa7f428ff370/numpy-2.4.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:21555da4ec4a0c942520ead42c3b0dc9477441e085c42b0fbdd6a084869a6f6b", size = 5470247, upload-time = "2025-12-20T16:15:47.943Z" }, + { url = "https://files.pythonhosted.org/packages/54/08/cbce72c835d937795571b0464b52069f869c9e78b0c076d416c5269d2718/numpy-2.4.0-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:413aa561266a4be2d06cd2b9665e89d9f54c543f418773076a76adcf2af08bc7", size = 6799807, upload-time = "2025-12-20T16:15:49.795Z" }, + { url = "https://files.pythonhosted.org/packages/ff/be/2e647961cd8c980591d75cdcd9e8f647d69fbe05e2a25613dc0a2ea5fb1a/numpy-2.4.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0feafc9e03128074689183031181fac0897ff169692d8492066e949041096548", size = 14701992, upload-time = "2025-12-20T16:15:51.615Z" }, + { url = "https://files.pythonhosted.org/packages/a2/fb/e1652fb8b6fd91ce6ed429143fe2e01ce714711e03e5b762615e7b36172c/numpy-2.4.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8fdfed3deaf1928fb7667d96e0567cdf58c2b370ea2ee7e586aa383ec2cb346", size = 16646871, upload-time = "2025-12-20T16:15:54.129Z" }, + { url = "https://files.pythonhosted.org/packages/62/23/d841207e63c4322842f7cd042ae981cffe715c73376dcad8235fb31debf1/numpy-2.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e06a922a469cae9a57100864caf4f8a97a1026513793969f8ba5b63137a35d25", size = 16487190, upload-time = "2025-12-20T16:15:56.147Z" }, + { url = "https://files.pythonhosted.org/packages/bc/a0/6a842c8421ebfdec0a230e65f61e0dabda6edbef443d999d79b87c273965/numpy-2.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:927ccf5cd17c48f801f4ed43a7e5673a2724bd2171460be3e3894e6e332ef83a", size = 18580762, upload-time = "2025-12-20T16:15:58.524Z" }, + { url = "https://files.pythonhosted.org/packages/0a/d1/c79e0046641186f2134dde05e6181825b911f8bdcef31b19ddd16e232847/numpy-2.4.0-cp311-cp311-win32.whl", hash = "sha256:882567b7ae57c1b1a0250208cc21a7976d8cbcc49d5a322e607e6f09c9e0bd53", size = 6233359, upload-time = "2025-12-20T16:16:00.938Z" }, + { url = "https://files.pythonhosted.org/packages/fc/f0/74965001d231f28184d6305b8cdc1b6fcd4bf23033f6cb039cfe76c9fca7/numpy-2.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:8b986403023c8f3bf8f487c2e6186afda156174d31c175f747d8934dfddf3479", size = 12601132, upload-time = "2025-12-20T16:16:02.484Z" }, + { url = "https://files.pythonhosted.org/packages/65/32/55408d0f46dfebce38017f5bd931affa7256ad6beac1a92a012e1fbc67a7/numpy-2.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:3f3096405acc48887458bbf9f6814d43785ac7ba2a57ea6442b581dedbc60ce6", size = 10573977, upload-time = "2025-12-20T16:16:04.77Z" }, + { url = "https://files.pythonhosted.org/packages/8b/ff/f6400ffec95de41c74b8e73df32e3fff1830633193a7b1e409be7fb1bb8c/numpy-2.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2a8b6bb8369abefb8bd1801b054ad50e02b3275c8614dc6e5b0373c305291037", size = 16653117, upload-time = "2025-12-20T16:16:06.709Z" }, + { url = "https://files.pythonhosted.org/packages/fd/28/6c23e97450035072e8d830a3c411bf1abd1f42c611ff9d29e3d8f55c6252/numpy-2.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2e284ca13d5a8367e43734148622caf0b261b275673823593e3e3634a6490f83", size = 12369711, upload-time = "2025-12-20T16:16:08.758Z" }, + { url = "https://files.pythonhosted.org/packages/bc/af/acbef97b630ab1bb45e6a7d01d1452e4251aa88ce680ac36e56c272120ec/numpy-2.4.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:49ff32b09f5aa0cd30a20c2b39db3e669c845589f2b7fc910365210887e39344", size = 5198355, upload-time = "2025-12-20T16:16:10.902Z" }, + { url = "https://files.pythonhosted.org/packages/c1/c8/4e0d436b66b826f2e53330adaa6311f5cac9871a5b5c31ad773b27f25a74/numpy-2.4.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:36cbfb13c152b1c7c184ddac43765db8ad672567e7bafff2cc755a09917ed2e6", size = 6545298, upload-time = "2025-12-20T16:16:12.607Z" }, + { url = "https://files.pythonhosted.org/packages/ef/27/e1f5d144ab54eac34875e79037011d511ac57b21b220063310cb96c80fbc/numpy-2.4.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:35ddc8f4914466e6fc954c76527aa91aa763682a4f6d73249ef20b418fe6effb", size = 14398387, upload-time = "2025-12-20T16:16:14.257Z" }, + { url = "https://files.pythonhosted.org/packages/67/64/4cb909dd5ab09a9a5d086eff9586e69e827b88a5585517386879474f4cf7/numpy-2.4.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dc578891de1db95b2a35001b695451767b580bb45753717498213c5ff3c41d63", size = 16363091, upload-time = "2025-12-20T16:16:17.32Z" }, + { url = "https://files.pythonhosted.org/packages/9d/9c/8efe24577523ec6809261859737cf117b0eb6fdb655abdfdc81b2e468ce4/numpy-2.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:98e81648e0b36e325ab67e46b5400a7a6d4a22b8a7c8e8bbfe20e7db7906bf95", size = 16176394, upload-time = "2025-12-20T16:16:19.524Z" }, + { url = "https://files.pythonhosted.org/packages/61/f0/1687441ece7b47a62e45a1f82015352c240765c707928edd8aef875d5951/numpy-2.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d57b5046c120561ba8fa8e4030fbb8b822f3063910fa901ffadf16e2b7128ad6", size = 18287378, upload-time = "2025-12-20T16:16:22.866Z" }, + { url = "https://files.pythonhosted.org/packages/d3/6f/f868765d44e6fc466467ed810ba9d8d6db1add7d4a748abfa2a4c99a3194/numpy-2.4.0-cp312-cp312-win32.whl", hash = "sha256:92190db305a6f48734d3982f2c60fa30d6b5ee9bff10f2887b930d7b40119f4c", size = 5955432, upload-time = "2025-12-20T16:16:25.06Z" }, + { url = "https://files.pythonhosted.org/packages/d4/b5/94c1e79fcbab38d1ca15e13777477b2914dd2d559b410f96949d6637b085/numpy-2.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:680060061adb2d74ce352628cb798cfdec399068aa7f07ba9fb818b2b3305f98", size = 12306201, upload-time = "2025-12-20T16:16:26.979Z" }, + { url = "https://files.pythonhosted.org/packages/70/09/c39dadf0b13bb0768cd29d6a3aaff1fb7c6905ac40e9aaeca26b1c086e06/numpy-2.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:39699233bc72dd482da1415dcb06076e32f60eddc796a796c5fb6c5efce94667", size = 10308234, upload-time = "2025-12-20T16:16:29.417Z" }, + { url = "https://files.pythonhosted.org/packages/a7/0d/853fd96372eda07c824d24adf02e8bc92bb3731b43a9b2a39161c3667cc4/numpy-2.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a152d86a3ae00ba5f47b3acf3b827509fd0b6cb7d3259665e63dafbad22a75ea", size = 16649088, upload-time = "2025-12-20T16:16:31.421Z" }, + { url = "https://files.pythonhosted.org/packages/e3/37/cc636f1f2a9f585434e20a3e6e63422f70bfe4f7f6698e941db52ea1ac9a/numpy-2.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:39b19251dec4de8ff8496cd0806cbe27bf0684f765abb1f4809554de93785f2d", size = 12364065, upload-time = "2025-12-20T16:16:33.491Z" }, + { url = "https://files.pythonhosted.org/packages/ed/69/0b78f37ca3690969beee54103ce5f6021709134e8020767e93ba691a72f1/numpy-2.4.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:009bd0ea12d3c784b6639a8457537016ce5172109e585338e11334f6a7bb88ee", size = 5192640, upload-time = "2025-12-20T16:16:35.636Z" }, + { url = "https://files.pythonhosted.org/packages/1d/2a/08569f8252abf590294dbb09a430543ec8f8cc710383abfb3e75cc73aeda/numpy-2.4.0-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:5fe44e277225fd3dff6882d86d3d447205d43532c3627313d17e754fb3905a0e", size = 6541556, upload-time = "2025-12-20T16:16:37.276Z" }, + { url = "https://files.pythonhosted.org/packages/93/e9/a949885a4e177493d61519377952186b6cbfdf1d6002764c664ba28349b5/numpy-2.4.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f935c4493eda9069851058fa0d9e39dbf6286be690066509305e52912714dbb2", size = 14396562, upload-time = "2025-12-20T16:16:38.953Z" }, + { url = "https://files.pythonhosted.org/packages/99/98/9d4ad53b0e9ef901c2ef1d550d2136f5ac42d3fd2988390a6def32e23e48/numpy-2.4.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8cfa5f29a695cb7438965e6c3e8d06e0416060cf0d709c1b1c1653a939bf5c2a", size = 16351719, upload-time = "2025-12-20T16:16:41.503Z" }, + { url = "https://files.pythonhosted.org/packages/28/de/5f3711a38341d6e8dd619f6353251a0cdd07f3d6d101a8fd46f4ef87f895/numpy-2.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ba0cb30acd3ef11c94dc27fbfba68940652492bc107075e7ffe23057f9425681", size = 16176053, upload-time = "2025-12-20T16:16:44.552Z" }, + { url = "https://files.pythonhosted.org/packages/2a/5b/2a3753dc43916501b4183532e7ace862e13211042bceafa253afb5c71272/numpy-2.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:60e8c196cd82cbbd4f130b5290007e13e6de3eca79f0d4d38014769d96a7c475", size = 18277859, upload-time = "2025-12-20T16:16:47.174Z" }, + { url = "https://files.pythonhosted.org/packages/2c/c5/a18bcdd07a941db3076ef489d036ab16d2bfc2eae0cf27e5a26e29189434/numpy-2.4.0-cp313-cp313-win32.whl", hash = "sha256:5f48cb3e88fbc294dc90e215d86fbaf1c852c63dbdb6c3a3e63f45c4b57f7344", size = 5953849, upload-time = "2025-12-20T16:16:49.554Z" }, + { url = "https://files.pythonhosted.org/packages/4f/f1/719010ff8061da6e8a26e1980cf090412d4f5f8060b31f0c45d77dd67a01/numpy-2.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:a899699294f28f7be8992853c0c60741f16ff199205e2e6cdca155762cbaa59d", size = 12302840, upload-time = "2025-12-20T16:16:51.227Z" }, + { url = "https://files.pythonhosted.org/packages/f5/5a/b3d259083ed8b4d335270c76966cb6cf14a5d1b69e1a608994ac57a659e6/numpy-2.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:9198f447e1dc5647d07c9a6bbe2063cc0132728cc7175b39dbc796da5b54920d", size = 10308509, upload-time = "2025-12-20T16:16:53.313Z" }, + { url = "https://files.pythonhosted.org/packages/31/01/95edcffd1bb6c0633df4e808130545c4f07383ab629ac7e316fb44fff677/numpy-2.4.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74623f2ab5cc3f7c886add4f735d1031a1d2be4a4ae63c0546cfd74e7a31ddf6", size = 12491815, upload-time = "2025-12-20T16:16:55.496Z" }, + { url = "https://files.pythonhosted.org/packages/59/ea/5644b8baa92cc1c7163b4b4458c8679852733fa74ca49c942cfa82ded4e0/numpy-2.4.0-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:0804a8e4ab070d1d35496e65ffd3cf8114c136a2b81f61dfab0de4b218aacfd5", size = 5320321, upload-time = "2025-12-20T16:16:57.468Z" }, + { url = "https://files.pythonhosted.org/packages/26/4e/e10938106d70bc21319bd6a86ae726da37edc802ce35a3a71ecdf1fdfe7f/numpy-2.4.0-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:02a2038eb27f9443a8b266a66911e926566b5a6ffd1a689b588f7f35b81e7dc3", size = 6641635, upload-time = "2025-12-20T16:16:59.379Z" }, + { url = "https://files.pythonhosted.org/packages/b3/8d/a8828e3eaf5c0b4ab116924df82f24ce3416fa38d0674d8f708ddc6c8aac/numpy-2.4.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1889b3a3f47a7b5bee16bc25a2145bd7cb91897f815ce3499db64c7458b6d91d", size = 14456053, upload-time = "2025-12-20T16:17:01.768Z" }, + { url = "https://files.pythonhosted.org/packages/68/a1/17d97609d87d4520aa5ae2dcfb32305654550ac6a35effb946d303e594ce/numpy-2.4.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:85eef4cb5625c47ee6425c58a3502555e10f45ee973da878ac8248ad58c136f3", size = 16401702, upload-time = "2025-12-20T16:17:04.235Z" }, + { url = "https://files.pythonhosted.org/packages/18/32/0f13c1b2d22bea1118356b8b963195446f3af124ed7a5adfa8fdecb1b6ca/numpy-2.4.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6dc8b7e2f4eb184b37655195f421836cfae6f58197b67e3ffc501f1333d993fa", size = 16242493, upload-time = "2025-12-20T16:17:06.856Z" }, + { url = "https://files.pythonhosted.org/packages/ae/23/48f21e3d309fbc137c068a1475358cbd3a901b3987dcfc97a029ab3068e2/numpy-2.4.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:44aba2f0cafd287871a495fb3163408b0bd25bbce135c6f621534a07f4f7875c", size = 18324222, upload-time = "2025-12-20T16:17:09.392Z" }, + { url = "https://files.pythonhosted.org/packages/ac/52/41f3d71296a3dcaa4f456aaa3c6fc8e745b43d0552b6bde56571bb4b4a0f/numpy-2.4.0-cp313-cp313t-win32.whl", hash = "sha256:20c115517513831860c573996e395707aa9fb691eb179200125c250e895fcd93", size = 6076216, upload-time = "2025-12-20T16:17:11.437Z" }, + { url = "https://files.pythonhosted.org/packages/35/ff/46fbfe60ab0710d2a2b16995f708750307d30eccbb4c38371ea9e986866e/numpy-2.4.0-cp313-cp313t-win_amd64.whl", hash = "sha256:b48e35f4ab6f6a7597c46e301126ceba4c44cd3280e3750f85db48b082624fa4", size = 12444263, upload-time = "2025-12-20T16:17:13.182Z" }, + { url = "https://files.pythonhosted.org/packages/a3/e3/9189ab319c01d2ed556c932ccf55064c5d75bb5850d1df7a482ce0badead/numpy-2.4.0-cp313-cp313t-win_arm64.whl", hash = "sha256:4d1cfce39e511069b11e67cd0bd78ceff31443b7c9e5c04db73c7a19f572967c", size = 10378265, upload-time = "2025-12-20T16:17:15.211Z" }, + { url = "https://files.pythonhosted.org/packages/ab/ed/52eac27de39d5e5a6c9aadabe672bc06f55e24a3d9010cd1183948055d76/numpy-2.4.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:c95eb6db2884917d86cde0b4d4cf31adf485c8ec36bf8696dd66fa70de96f36b", size = 16647476, upload-time = "2025-12-20T16:17:17.671Z" }, + { url = "https://files.pythonhosted.org/packages/77/c0/990ce1b7fcd4e09aeaa574e2a0a839589e4b08b2ca68070f1acb1fea6736/numpy-2.4.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:65167da969cd1ec3a1df31cb221ca3a19a8aaa25370ecb17d428415e93c1935e", size = 12374563, upload-time = "2025-12-20T16:17:20.216Z" }, + { url = "https://files.pythonhosted.org/packages/37/7c/8c5e389c6ae8f5fd2277a988600d79e9625db3fff011a2d87ac80b881a4c/numpy-2.4.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:3de19cfecd1465d0dcf8a5b5ea8b3155b42ed0b639dba4b71e323d74f2a3be5e", size = 5203107, upload-time = "2025-12-20T16:17:22.47Z" }, + { url = "https://files.pythonhosted.org/packages/e6/94/ca5b3bd6a8a70a5eec9a0b8dd7f980c1eff4b8a54970a9a7fef248ef564f/numpy-2.4.0-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:6c05483c3136ac4c91b4e81903cb53a8707d316f488124d0398499a4f8e8ef51", size = 6538067, upload-time = "2025-12-20T16:17:24.001Z" }, + { url = "https://files.pythonhosted.org/packages/79/43/993eb7bb5be6761dde2b3a3a594d689cec83398e3f58f4758010f3b85727/numpy-2.4.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:36667db4d6c1cea79c8930ab72fadfb4060feb4bfe724141cd4bd064d2e5f8ce", size = 14411926, upload-time = "2025-12-20T16:17:25.822Z" }, + { url = "https://files.pythonhosted.org/packages/03/75/d4c43b61de473912496317a854dac54f1efec3eeb158438da6884b70bb90/numpy-2.4.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9a818668b674047fd88c4cddada7ab8f1c298812783e8328e956b78dc4807f9f", size = 16354295, upload-time = "2025-12-20T16:17:28.308Z" }, + { url = "https://files.pythonhosted.org/packages/b8/0a/b54615b47ee8736a6461a4bb6749128dd3435c5a759d5663f11f0e9af4ac/numpy-2.4.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1ee32359fb7543b7b7bd0b2f46294db27e29e7bbdf70541e81b190836cd83ded", size = 16190242, upload-time = "2025-12-20T16:17:30.993Z" }, + { url = "https://files.pythonhosted.org/packages/98/ce/ea207769aacad6246525ec6c6bbd66a2bf56c72443dc10e2f90feed29290/numpy-2.4.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e493962256a38f58283de033d8af176c5c91c084ea30f15834f7545451c42059", size = 18280875, upload-time = "2025-12-20T16:17:33.327Z" }, + { url = "https://files.pythonhosted.org/packages/17/ef/ec409437aa962ea372ed601c519a2b141701683ff028f894b7466f0ab42b/numpy-2.4.0-cp314-cp314-win32.whl", hash = "sha256:6bbaebf0d11567fa8926215ae731e1d58e6ec28a8a25235b8a47405d301332db", size = 6002530, upload-time = "2025-12-20T16:17:35.729Z" }, + { url = "https://files.pythonhosted.org/packages/5f/4a/5cb94c787a3ed1ac65e1271b968686521169a7b3ec0b6544bb3ca32960b0/numpy-2.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:3d857f55e7fdf7c38ab96c4558c95b97d1c685be6b05c249f5fdafcbd6f9899e", size = 12435890, upload-time = "2025-12-20T16:17:37.599Z" }, + { url = "https://files.pythonhosted.org/packages/48/a0/04b89db963af9de1104975e2544f30de89adbf75b9e75f7dd2599be12c79/numpy-2.4.0-cp314-cp314-win_arm64.whl", hash = "sha256:bb50ce5fb202a26fd5404620e7ef820ad1ab3558b444cb0b55beb7ef66cd2d63", size = 10591892, upload-time = "2025-12-20T16:17:39.649Z" }, + { url = "https://files.pythonhosted.org/packages/53/e5/d74b5ccf6712c06c7a545025a6a71bfa03bdc7e0568b405b0d655232fd92/numpy-2.4.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:355354388cba60f2132df297e2d53053d4063f79077b67b481d21276d61fc4df", size = 12494312, upload-time = "2025-12-20T16:17:41.714Z" }, + { url = "https://files.pythonhosted.org/packages/c2/08/3ca9cc2ddf54dfee7ae9a6479c071092a228c68aef08252aa08dac2af002/numpy-2.4.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:1d8f9fde5f6dc1b6fc34df8162f3b3079365468703fee7f31d4e0cc8c63baed9", size = 5322862, upload-time = "2025-12-20T16:17:44.145Z" }, + { url = "https://files.pythonhosted.org/packages/87/74/0bb63a68394c0c1e52670cfff2e309afa41edbe11b3327d9af29e4383f34/numpy-2.4.0-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:e0434aa22c821f44eeb4c650b81c7fbdd8c0122c6c4b5a576a76d5a35625ecd9", size = 6644986, upload-time = "2025-12-20T16:17:46.203Z" }, + { url = "https://files.pythonhosted.org/packages/06/8f/9264d9bdbcf8236af2823623fe2f3981d740fc3461e2787e231d97c38c28/numpy-2.4.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:40483b2f2d3ba7aad426443767ff5632ec3156ef09742b96913787d13c336471", size = 14457958, upload-time = "2025-12-20T16:17:48.017Z" }, + { url = "https://files.pythonhosted.org/packages/8c/d9/f9a69ae564bbc7236a35aa883319364ef5fd41f72aa320cc1cbe66148fe2/numpy-2.4.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d9e6a7664ddd9746e20b7325351fe1a8408d0a2bf9c63b5e898290ddc8f09544", size = 16398394, upload-time = "2025-12-20T16:17:50.409Z" }, + { url = "https://files.pythonhosted.org/packages/34/c7/39241501408dde7f885d241a98caba5421061a2c6d2b2197ac5e3aa842d8/numpy-2.4.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ecb0019d44f4cdb50b676c5d0cb4b1eae8e15d1ed3d3e6639f986fc92b2ec52c", size = 16241044, upload-time = "2025-12-20T16:17:52.661Z" }, + { url = "https://files.pythonhosted.org/packages/7c/95/cae7effd90e065a95e59fe710eeee05d7328ed169776dfdd9f789e032125/numpy-2.4.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d0ffd9e2e4441c96a9c91ec1783285d80bf835b677853fc2770a89d50c1e48ac", size = 18321772, upload-time = "2025-12-20T16:17:54.947Z" }, + { url = "https://files.pythonhosted.org/packages/96/df/3c6c279accd2bfb968a76298e5b276310bd55d243df4fa8ac5816d79347d/numpy-2.4.0-cp314-cp314t-win32.whl", hash = "sha256:77f0d13fa87036d7553bf81f0e1fe3ce68d14c9976c9851744e4d3e91127e95f", size = 6148320, upload-time = "2025-12-20T16:17:57.249Z" }, + { url = "https://files.pythonhosted.org/packages/92/8d/f23033cce252e7a75cae853d17f582e86534c46404dea1c8ee094a9d6d84/numpy-2.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b1f5b45829ac1848893f0ddf5cb326110604d6df96cdc255b0bf9edd154104d4", size = 12623460, upload-time = "2025-12-20T16:17:58.963Z" }, + { url = "https://files.pythonhosted.org/packages/a4/4f/1f8475907d1a7c4ef9020edf7f39ea2422ec896849245f00688e4b268a71/numpy-2.4.0-cp314-cp314t-win_arm64.whl", hash = "sha256:23a3e9d1a6f360267e8fbb38ba5db355a6a7e9be71d7fce7ab3125e88bb646c8", size = 10661799, upload-time = "2025-12-20T16:18:01.078Z" }, + { url = "https://files.pythonhosted.org/packages/4b/ef/088e7c7342f300aaf3ee5f2c821c4b9996a1bef2aaf6a49cc8ab4883758e/numpy-2.4.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b54c83f1c0c0f1d748dca0af516062b8829d53d1f0c402be24b4257a9c48ada6", size = 16819003, upload-time = "2025-12-20T16:18:03.41Z" }, + { url = "https://files.pythonhosted.org/packages/ff/ce/a53017b5443b4b84517182d463fc7bcc2adb4faa8b20813f8e5f5aeb5faa/numpy-2.4.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:aabb081ca0ec5d39591fc33018cd4b3f96e1a2dd6756282029986d00a785fba4", size = 12567105, upload-time = "2025-12-20T16:18:05.594Z" }, + { url = "https://files.pythonhosted.org/packages/77/58/5ff91b161f2ec650c88a626c3905d938c89aaadabd0431e6d9c1330c83e2/numpy-2.4.0-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:8eafe7c36c8430b7794edeab3087dec7bf31d634d92f2af9949434b9d1964cba", size = 5395590, upload-time = "2025-12-20T16:18:08.031Z" }, + { url = "https://files.pythonhosted.org/packages/1d/4e/f1a084106df8c2df8132fc437e56987308e0524836aa7733721c8429d4fe/numpy-2.4.0-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:2f585f52b2baf07ff3356158d9268ea095e221371f1074fadea2f42544d58b4d", size = 6709947, upload-time = "2025-12-20T16:18:09.836Z" }, + { url = "https://files.pythonhosted.org/packages/63/09/3d8aeb809c0332c3f642da812ac2e3d74fc9252b3021f8c30c82e99e3f3d/numpy-2.4.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:32ed06d0fe9cae27d8fb5f400c63ccee72370599c75e683a6358dd3a4fb50aaf", size = 14535119, upload-time = "2025-12-20T16:18:12.105Z" }, + { url = "https://files.pythonhosted.org/packages/fd/7f/68f0fc43a2cbdc6bb239160c754d87c922f60fbaa0fa3cd3d312b8a7f5ee/numpy-2.4.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:57c540ed8fb1f05cb997c6761cd56db72395b0d6985e90571ff660452ade4f98", size = 16475815, upload-time = "2025-12-20T16:18:14.433Z" }, + { url = "https://files.pythonhosted.org/packages/11/73/edeacba3167b1ca66d51b1a5a14697c2c40098b5ffa01811c67b1785a5ab/numpy-2.4.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:a39fb973a726e63223287adc6dafe444ce75af952d711e400f3bf2b36ef55a7b", size = 12489376, upload-time = "2025-12-20T16:18:16.524Z" }, ] [[package]] @@ -1842,7 +1840,7 @@ version = "2.3.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "numpy", version = "2.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "python-dateutil" }, { name = "pytz" }, { name = "tzdata" }, @@ -2421,7 +2419,7 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "dill" }, { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "numpy", version = "2.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "rustworkx" }, { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "scipy", version = "1.16.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, @@ -2494,13 +2492,25 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, ] +[[package]] +name = "roman-numerals" +version = "4.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ae/f9/41dc953bbeb056c17d5f7a519f50fdf010bd0553be2d630bc69d1e022703/roman_numerals-4.1.0.tar.gz", hash = "sha256:1af8b147eb1405d5839e78aeb93131690495fe9da5c91856cb33ad55a7f1e5b2", size = 9077, upload-time = "2025-12-17T18:25:34.381Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/54/6f679c435d28e0a568d8e8a7c0a93a09010818634c3c3907fc98d8983770/roman_numerals-4.1.0-py3-none-any.whl", hash = "sha256:647ba99caddc2cc1e55a51e4360689115551bf4476d90e8162cf8c345fe233c7", size = 7676, upload-time = "2025-12-17T18:25:33.098Z" }, +] + [[package]] name = "roman-numerals-py" -version = "3.1.0" +version = "4.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/30/76/48fd56d17c5bdbdf65609abbc67288728a98ed4c02919428d4f52d23b24b/roman_numerals_py-3.1.0.tar.gz", hash = "sha256:be4bf804f083a4ce001b5eb7e3c0862479d10f94c936f6c4e5f250aa5ff5bd2d", size = 9017, upload-time = "2025-02-22T07:34:54.333Z" } +dependencies = [ + { name = "roman-numerals", marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cb/b5/de96fca640f4f656eb79bbee0e79aeec52e3e0e359f8a3e6a0d366378b64/roman_numerals_py-4.1.0.tar.gz", hash = "sha256:f5d7b2b4ca52dd855ef7ab8eb3590f428c0b1ea480736ce32b01fef2a5f8daf9", size = 4274, upload-time = "2025-12-17T18:25:41.153Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/53/97/d2cbbaa10c9b826af0e10fdf836e1bf344d9f0abb873ebc34d1f49642d3f/roman_numerals_py-3.1.0-py3-none-any.whl", hash = "sha256:9da2ad2fb670bcf24e81070ceb3be72f6c11c440d73bd579fbeca1e9f330954c", size = 7742, upload-time = "2025-02-22T07:34:52.422Z" }, + { url = "https://files.pythonhosted.org/packages/27/2c/daca29684cbe9fd4bc711f8246da3c10adca1ccc4d24436b17572eb2590e/roman_numerals_py-4.1.0-py3-none-any.whl", hash = "sha256:553114c1167141c1283a51743759723ecd05604a1b6b507225e91dc1a6df0780", size = 4547, upload-time = "2025-12-17T18:25:40.136Z" }, ] [[package]] @@ -2631,7 +2641,7 @@ version = "0.17.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "numpy", version = "2.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/e7/b0/66d96f02120f79eeed86b5c5be04029b6821155f31ed4907a4e9f1460671/rustworkx-0.17.1.tar.gz", hash = "sha256:59ea01b4e603daffa4e8827316c1641eef18ae9032f0b1b14aa0181687e3108e", size = 399407, upload-time = "2025-09-15T16:29:46.429Z" } wheels = [ @@ -2733,7 +2743,7 @@ resolution-markers = [ "python_full_version == '3.11.*'", ] dependencies = [ - { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "numpy", version = "2.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/0a/ca/d8ace4f98322d01abcd52d381134344bf7b431eba7ed8b42bdea5a3c2ac9/scipy-1.16.3.tar.gz", hash = "sha256:01e87659402762f43bd2fee13370553a17ada367d42e7487800bf2916535aecb", size = 30597883, upload-time = "2025-10-28T17:38:54.068Z" } wheels = [ @@ -2806,7 +2816,7 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "matplotlib" }, { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "numpy", version = "2.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "pandas" }, ] sdist = { url = "https://files.pythonhosted.org/packages/86/59/a451d7420a77ab0b98f7affa3a1d78a313d2f7281a57afb1a34bae8ab412/seaborn-0.13.2.tar.gz", hash = "sha256:93e60a40988f4d65e9f4885df477e2fdaff6b73a9ded434c1ab356dd57eefff7", size = 1457696, upload-time = "2024-01-25T13:21:52.551Z" } @@ -2857,11 +2867,11 @@ wheels = [ [[package]] name = "soupsieve" -version = "2.8" +version = "2.8.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6d/e6/21ccce3262dd4889aa3332e5a119a3491a95e8f60939870a3a035aabac0d/soupsieve-2.8.tar.gz", hash = "sha256:e2dd4a40a628cb5f28f6d4b0db8800b8f581b65bb380b97de22ba5ca8d72572f", size = 103472, upload-time = "2025-08-27T15:39:51.78Z" } +sdist = { url = "https://files.pythonhosted.org/packages/89/23/adf3796d740536d63a6fbda113d07e60c734b6ed5d3058d1e47fc0495e47/soupsieve-2.8.1.tar.gz", hash = "sha256:4cf733bc50fa805f5df4b8ef4740fc0e0fa6218cf3006269afd3f9d6d80fd350", size = 117856, upload-time = "2025-12-18T13:50:34.655Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/14/a0/bb38d3b76b8cae341dad93a2dd83ab7462e6dbcdd84d43f54ee60a8dc167/soupsieve-2.8-py3-none-any.whl", hash = "sha256:0cc76456a30e20f5d7f2e14a98a4ae2ee4e5abdc7c5ea0aafe795f344bc7984c", size = 36679, upload-time = "2025-08-27T15:39:50.179Z" }, + { url = "https://files.pythonhosted.org/packages/48/f3/b67d6ea49ca9154453b6d70b34ea22f3996b9fa55da105a79d8732227adc/soupsieve-2.8.1-py3-none-any.whl", hash = "sha256:a11fe2a6f3d76ab3cf2de04eb339c1be5b506a8a47f2ceb6d139803177f85434", size = 36710, upload-time = "2025-12-18T13:50:33.267Z" }, ] [[package]] @@ -3244,21 +3254,21 @@ wheels = [ [[package]] name = "tornado" -version = "6.5.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7f/2e/3d22d478f27cb4b41edd4db7f10cd7846d0a28ea443342de3dba97035166/tornado-6.5.3.tar.gz", hash = "sha256:16abdeb0211796ffc73765bc0a20119712d68afeeaf93d1a3f2edf6b3aee8d5a", size = 513348, upload-time = "2025-12-11T04:16:42.225Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d3/e9/bf22f66e1d5d112c0617974b5ce86666683b32c09b355dfcd59f8d5c8ef6/tornado-6.5.3-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:2dd7d7e8d3e4635447a8afd4987951e3d4e8d1fb9ad1908c54c4002aabab0520", size = 443860, upload-time = "2025-12-11T04:16:26.638Z" }, - { url = "https://files.pythonhosted.org/packages/ca/9c/594b631f0b8dc5977080c7093d1e96f1377c10552577d2c31bb0208c9362/tornado-6.5.3-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:5977a396f83496657779f59a48c38096ef01edfe4f42f1c0634b791dde8165d0", size = 442118, upload-time = "2025-12-11T04:16:28.32Z" }, - { url = "https://files.pythonhosted.org/packages/78/f6/685b869f5b5b9d9547571be838c6106172082751696355b60fc32a4988ed/tornado-6.5.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f72ac800be2ac73ddc1504f7aa21069a4137e8d70c387172c063d363d04f2208", size = 445700, upload-time = "2025-12-11T04:16:29.64Z" }, - { url = "https://files.pythonhosted.org/packages/91/4c/f0d19edf24912b7f21ae5e941f7798d132ad4d9b71441c1e70917a297265/tornado-6.5.3-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c43c4fc4f5419c6561cfb8b884a8f6db7b142787d47821e1a0e1296253458265", size = 445041, upload-time = "2025-12-11T04:16:30.799Z" }, - { url = "https://files.pythonhosted.org/packages/eb/2b/e02da94f4a4aef2bb3b923c838ef284a77548a5f06bac2a8682b36b4eead/tornado-6.5.3-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de8b3fed4b3afb65d542d7702ac8767b567e240f6a43020be8eaef59328f117b", size = 445270, upload-time = "2025-12-11T04:16:32.316Z" }, - { url = "https://files.pythonhosted.org/packages/58/e2/7a7535d23133443552719dba526dacbb7415f980157da9f14950ddb88ad6/tornado-6.5.3-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:dbc4b4c32245b952566e17a20d5c1648fbed0e16aec3fc7e19f3974b36e0e47c", size = 445957, upload-time = "2025-12-11T04:16:33.913Z" }, - { url = "https://files.pythonhosted.org/packages/a0/1f/9ff92eca81ff17a86286ec440dcd5eab0400326eb81761aa9a4eecb1ffb9/tornado-6.5.3-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:db238e8a174b4bfd0d0238b8cfcff1c14aebb4e2fcdafbf0ea5da3b81caceb4c", size = 445371, upload-time = "2025-12-11T04:16:35.093Z" }, - { url = "https://files.pythonhosted.org/packages/70/b1/1d03ae4526a393b0b839472a844397337f03c7f3a1e6b5c82241f0e18281/tornado-6.5.3-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:892595c100cd9b53a768cbfc109dfc55dec884afe2de5290611a566078d9692d", size = 445348, upload-time = "2025-12-11T04:16:36.679Z" }, - { url = "https://files.pythonhosted.org/packages/4b/7d/7c181feadc8941f418d0d26c3790ee34ffa4bd0a294bc5201d44ebd19c1e/tornado-6.5.3-cp39-abi3-win32.whl", hash = "sha256:88141456525fe291e47bbe1ba3ffb7982549329f09b4299a56813923af2bd197", size = 446433, upload-time = "2025-12-11T04:16:38.332Z" }, - { url = "https://files.pythonhosted.org/packages/34/98/4f7f938606e21d0baea8c6c39a7c8e95bdf8e50b0595b1bb6f0de2af7a6e/tornado-6.5.3-cp39-abi3-win_amd64.whl", hash = "sha256:ba4b513d221cc7f795a532c1e296f36bcf6a60e54b15efd3f092889458c69af1", size = 446842, upload-time = "2025-12-11T04:16:39.867Z" }, - { url = "https://files.pythonhosted.org/packages/7a/27/0e3fca4c4edf33fb6ee079e784c63961cd816971a45e5e4cacebe794158d/tornado-6.5.3-cp39-abi3-win_arm64.whl", hash = "sha256:278c54d262911365075dd45e0b6314308c74badd6ff9a54490e7daccdd5ed0ea", size = 445863, upload-time = "2025-12-11T04:16:41.099Z" }, +version = "6.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/37/1d/0a336abf618272d53f62ebe274f712e213f5a03c0b2339575430b8362ef2/tornado-6.5.4.tar.gz", hash = "sha256:a22fa9047405d03260b483980635f0b041989d8bcc9a313f8fe18b411d84b1d7", size = 513632, upload-time = "2025-12-15T19:21:03.836Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ab/a9/e94a9d5224107d7ce3cc1fab8d5dc97f5ea351ccc6322ee4fb661da94e35/tornado-6.5.4-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:d6241c1a16b1c9e4cc28148b1cda97dd1c6cb4fb7068ac1bedc610768dff0ba9", size = 443909, upload-time = "2025-12-15T19:20:48.382Z" }, + { url = "https://files.pythonhosted.org/packages/db/7e/f7b8d8c4453f305a51f80dbb49014257bb7d28ccb4bbb8dd328ea995ecad/tornado-6.5.4-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2d50f63dda1d2cac3ae1fa23d254e16b5e38153758470e9956cbc3d813d40843", size = 442163, upload-time = "2025-12-15T19:20:49.791Z" }, + { url = "https://files.pythonhosted.org/packages/ba/b5/206f82d51e1bfa940ba366a8d2f83904b15942c45a78dd978b599870ab44/tornado-6.5.4-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1cf66105dc6acb5af613c054955b8137e34a03698aa53272dbda4afe252be17", size = 445746, upload-time = "2025-12-15T19:20:51.491Z" }, + { url = "https://files.pythonhosted.org/packages/8e/9d/1a3338e0bd30ada6ad4356c13a0a6c35fbc859063fa7eddb309183364ac1/tornado-6.5.4-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50ff0a58b0dc97939d29da29cd624da010e7f804746621c78d14b80238669335", size = 445083, upload-time = "2025-12-15T19:20:52.778Z" }, + { url = "https://files.pythonhosted.org/packages/50/d4/e51d52047e7eb9a582da59f32125d17c0482d065afd5d3bc435ff2120dc5/tornado-6.5.4-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5fb5e04efa54cf0baabdd10061eb4148e0be137166146fff835745f59ab9f7f", size = 445315, upload-time = "2025-12-15T19:20:53.996Z" }, + { url = "https://files.pythonhosted.org/packages/27/07/2273972f69ca63dbc139694a3fc4684edec3ea3f9efabf77ed32483b875c/tornado-6.5.4-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9c86b1643b33a4cd415f8d0fe53045f913bf07b4a3ef646b735a6a86047dda84", size = 446003, upload-time = "2025-12-15T19:20:56.101Z" }, + { url = "https://files.pythonhosted.org/packages/d1/83/41c52e47502bf7260044413b6770d1a48dda2f0246f95ee1384a3cd9c44a/tornado-6.5.4-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:6eb82872335a53dd063a4f10917b3efd28270b56a33db69009606a0312660a6f", size = 445412, upload-time = "2025-12-15T19:20:57.398Z" }, + { url = "https://files.pythonhosted.org/packages/10/c7/bc96917f06cbee182d44735d4ecde9c432e25b84f4c2086143013e7b9e52/tornado-6.5.4-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6076d5dda368c9328ff41ab5d9dd3608e695e8225d1cd0fd1e006f05da3635a8", size = 445392, upload-time = "2025-12-15T19:20:58.692Z" }, + { url = "https://files.pythonhosted.org/packages/0c/1a/d7592328d037d36f2d2462f4bc1fbb383eec9278bc786c1b111cbbd44cfa/tornado-6.5.4-cp39-abi3-win32.whl", hash = "sha256:1768110f2411d5cd281bac0a090f707223ce77fd110424361092859e089b38d1", size = 446481, upload-time = "2025-12-15T19:21:00.008Z" }, + { url = "https://files.pythonhosted.org/packages/d6/6d/c69be695a0a64fd37a97db12355a035a6d90f79067a3cf936ec2b1dc38cd/tornado-6.5.4-cp39-abi3-win_amd64.whl", hash = "sha256:fa07d31e0cd85c60713f2b995da613588aa03e1303d75705dca6af8babc18ddc", size = 446886, upload-time = "2025-12-15T19:21:01.287Z" }, + { url = "https://files.pythonhosted.org/packages/50/49/8dc3fd90902f70084bd2cd059d576ddb4f8bb44c2c7c0e33a11422acb17e/tornado-6.5.4-cp39-abi3-win_arm64.whl", hash = "sha256:053e6e16701eb6cbe641f308f4c1a9541f91b6261991160391bfc342e8a551a1", size = 445910, upload-time = "2025-12-15T19:21:02.571Z" }, ] [[package]] From 92f4b4bfdd713a2917a5bfd2859809cb9479c378 Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Wed, 24 Dec 2025 12:09:52 +0800 Subject: [PATCH 071/145] Revert "Merge branch 'main' into highlight_error" This reverts commit 83e928acb6bf489988d3c7891f60335e70211e60, reversing changes made to 68a8ffe17c492047bd792504382416697df74ad4. --- python/mqt/debugger/dap/dap_server.py | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/python/mqt/debugger/dap/dap_server.py b/python/mqt/debugger/dap/dap_server.py index b1e35817..e241d81d 100644 --- a/python/mqt/debugger/dap/dap_server.py +++ b/python/mqt/debugger/dap/dap_server.py @@ -113,7 +113,6 @@ def __init__(self, host: str = "127.0.0.1", port: int = 4711) -> None: self.port = port self.can_step_back = False self.simulation_state = mqt.debugger.SimulationState() - self.source_file = {} self.lines_start_at_one = True self.columns_start_at_one = True self.pending_highlights: list[dict[str, Any]] = [] @@ -191,10 +190,6 @@ def handle_client(self, connection: socket.socket) -> None: e = mqt.debugger.dap.messages.InitializedDAPEvent() event_payload = json.dumps(e.encode()) send_message(event_payload, connection) - if isinstance( - cmd, (mqt.debugger.dap.messages.LaunchDAPMessage, mqt.debugger.dap.messages.RestartDAPMessage) - ): - self.reset_gray_out(connection) if ( isinstance( cmd, (mqt.debugger.dap.messages.LaunchDAPMessage, mqt.debugger.dap.messages.RestartDAPMessage) @@ -314,14 +309,6 @@ def handle_command(self, command: dict[str, Any]) -> tuple[dict[str, Any], mqt.d msg = f"Unsupported command: {command['command']}" raise RuntimeError(msg) - def reset_gray_out(self, connection: socket.socket) -> None: - """Reset all gray-out highlights in the client.""" - if not self.source_file: - return - e = mqt.debugger.dap.messages.GrayOutDAPEvent([], self.source_file) - event_payload = json.dumps(e.encode()) - send_message(event_payload, connection) - def handle_assertion_fail(self, connection: socket.socket) -> None: """Handles the sending of output events when an assertion fails. From e151e5d86dd3e0b3cf36b3fdab240388f2e6ab19 Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Tue, 30 Dec 2025 11:44:29 +1100 Subject: [PATCH 072/145] cc fix --- src/backend/dd/DDSimDebug.cpp | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/backend/dd/DDSimDebug.cpp b/src/backend/dd/DDSimDebug.cpp index 2045a501..90d9f07b 100644 --- a/src/backend/dd/DDSimDebug.cpp +++ b/src/backend/dd/DDSimDebug.cpp @@ -590,9 +590,22 @@ Result ddsimLoadCode(SimulationState* self, const char* code) { ddsim->callReturnStack.clear(); ddsim->callSubstitutions.clear(); ddsim->restoreCallReturnStack.clear(); + ddsim->ready = false; ddsim->code = code; ddsim->variables.clear(); ddsim->variableNames.clear(); + ddsim->instructionTypes.clear(); + ddsim->instructionStarts.clear(); + ddsim->instructionEnds.clear(); + ddsim->functionDefinitions.clear(); + ddsim->assertionInstructions.clear(); + ddsim->successorInstructions.clear(); + ddsim->classicalRegisters.clear(); + ddsim->qubitRegisters.clear(); + ddsim->dataDependencies.clear(); + ddsim->functionCallers.clear(); + ddsim->targetQubits.clear(); + ddsim->instructionObjects.clear(); try { std::stringstream ss{preprocessAssertionCode(code, ddsim)}; @@ -1119,6 +1132,10 @@ Result ddsimStepBackward(SimulationState* self) { } Result ddsimRunAll(SimulationState* self, size_t* failedAssertions) { + auto* ddsim = toDDSimulationState(self); + if (!ddsim->ready) { + return ERROR; + } size_t errorCount = 0; while (!self->isFinished(self)) { const Result result = self->runSimulation(self); From d5cd2e77512338c06cfc7b56ed58d411ca1ffeb2 Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Tue, 30 Dec 2025 12:02:22 +1100 Subject: [PATCH 073/145] cc fix 2 --- src/backend/dd/DDSimDebug.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/backend/dd/DDSimDebug.cpp b/src/backend/dd/DDSimDebug.cpp index 90d9f07b..7bc8c09e 100644 --- a/src/backend/dd/DDSimDebug.cpp +++ b/src/backend/dd/DDSimDebug.cpp @@ -1406,6 +1406,11 @@ Result ddsimSetBreakpoint(SimulationState* self, size_t desiredPosition, for (auto i = 0ULL; i < ddsim->instructionTypes.size(); i++) { const size_t start = ddsim->instructionStarts[i]; const size_t end = ddsim->instructionEnds[i]; + if (desiredPosition < start) { + *targetInstruction = i; + ddsim->breakpoints.insert(i); + return OK; + } if (desiredPosition >= start && desiredPosition <= end) { if (ddsim->functionDefinitions.contains(i)) { // Breakpoint may be located in a sub-gate of the gate definition. From ff0e4b38089555dc8d015966912e1a85d48289ad Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Tue, 30 Dec 2025 12:13:51 +1100 Subject: [PATCH 074/145] cc fix 3 --- bindings/InterfaceBindings.cpp | 8 ++-- src/common/parsing/CodePreprocessing.cpp | 51 +++++++++++++++--------- 2 files changed, 37 insertions(+), 22 deletions(-) diff --git a/bindings/InterfaceBindings.cpp b/bindings/InterfaceBindings.cpp index 3315db5d..7f93d4c0 100644 --- a/bindings/InterfaceBindings.cpp +++ b/bindings/InterfaceBindings.cpp @@ -176,15 +176,15 @@ Contains one element for each of the `num_states` states in the state vector.)") .def( "load_code", [](SimulationState* self, const char* code) { - py::module io = py::module::import("io"); - py::object string_io = io.attr("StringIO")(); + const py::module io = py::module::import("io"); + const py::object stringIo = io.attr("StringIO")(); Result result = OK; { - py::scoped_ostream_redirect redirect(std::cerr, string_io); + const py::scoped_ostream_redirect redirect(std::cerr, stringIo); result = self->loadCode(self, code); } if (result != OK) { - auto message = string_io.attr("getvalue")().cast(); + auto message = stringIo.attr("getvalue")().cast(); if (message.empty()) { message = "An error occurred while executing the operation"; } diff --git a/src/common/parsing/CodePreprocessing.cpp b/src/common/parsing/CodePreprocessing.cpp index bfdff312..5342bc05 100644 --- a/src/common/parsing/CodePreprocessing.cpp +++ b/src/common/parsing/CodePreprocessing.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -97,6 +98,22 @@ std::string formatParseError(const std::string& code, size_t instructionStart, std::to_string(location.column) + ": " + detail; } +std::string invalidTargetDetail(const std::string& target, + const std::string& context) { + std::string detail = "Invalid target qubit "; + detail += target; + detail += context; + detail += "."; + return detail; +} + +std::string invalidRegisterDetail(const std::string& trimmedLine) { + std::string detail = "Invalid register declaration "; + detail += trimmedLine; + detail += "."; + return detail; +} + void validateTargets(const std::string& code, size_t instructionStart, const std::vector& targets, const std::map& definedRegisters, @@ -112,24 +129,24 @@ void validateTargets(const std::string& code, size_t instructionStart, } const auto close = target.find(']', open + 1); if (open == 0 || close == std::string::npos || close != target.size() - 1) { - throw ParsingError(formatParseError( - code, instructionStart, - "Invalid target qubit " + target + context + ".", target)); + throw ParsingError(formatParseError(code, instructionStart, + invalidTargetDetail(target, context), + target)); } const auto registerName = target.substr(0, open); const auto indexText = target.substr(open + 1, close - open - 1); if (!isDigits(indexText)) { - throw ParsingError(formatParseError( - code, instructionStart, - "Invalid target qubit " + target + context + ".", target)); + throw ParsingError(formatParseError(code, instructionStart, + invalidTargetDetail(target, context), + target)); } size_t registerIndex = 0; try { registerIndex = std::stoul(indexText); } catch (const std::exception&) { - throw ParsingError(formatParseError( - code, instructionStart, - "Invalid target qubit " + target + context + ".", target)); + throw ParsingError(formatParseError(code, instructionStart, + invalidTargetDetail(target, context), + target)); } if (std::ranges::find(shadowedRegisters, registerName) != shadowedRegisters.end()) { @@ -137,9 +154,9 @@ void validateTargets(const std::string& code, size_t instructionStart, } const auto found = definedRegisters.find(registerName); if (found == definedRegisters.end() || found->second <= registerIndex) { - throw ParsingError(formatParseError( - code, instructionStart, - "Invalid target qubit " + target + context + ".", target)); + throw ParsingError(formatParseError(code, instructionStart, + invalidTargetDetail(target, context), + target)); } } } @@ -475,17 +492,15 @@ preprocessCode(const std::string& code, size_t startIndex, const auto& name = parts[0]; const auto sizeText = parts.size() > 1 ? parts[1] : ""; if (name.empty() || !isDigits(sizeText)) { - throw ParsingError(formatParseError(code, trueStart, - "Invalid register declaration " + - trimmedLine + ".")); + throw ParsingError(formatParseError( + code, trueStart, invalidRegisterDetail(trimmedLine))); } size_t size = 0; try { size = std::stoul(sizeText); } catch (const std::exception&) { - throw ParsingError(formatParseError(code, trueStart, - "Invalid register declaration " + - trimmedLine + ".")); + throw ParsingError(formatParseError( + code, trueStart, invalidRegisterDetail(trimmedLine))); } definedRegisters.insert({name, size}); } From f2d9c9d310a53abc32fe11d68c9a219d5dd7c081 Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Tue, 30 Dec 2025 12:22:16 +1100 Subject: [PATCH 075/145] cc fix 4 --- src/common/parsing/CodePreprocessing.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/common/parsing/CodePreprocessing.cpp b/src/common/parsing/CodePreprocessing.cpp index 5342bc05..920c9c4e 100644 --- a/src/common/parsing/CodePreprocessing.cpp +++ b/src/common/parsing/CodePreprocessing.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -39,8 +40,8 @@ bool isDigits(const std::string& text) { if (text.empty()) { return false; } - return std::all_of(text.begin(), text.end(), - [](unsigned char c) { return std::isdigit(c) != 0; }); + return std::ranges::all_of( + text, [](unsigned char c) { return std::isdigit(c) != 0; }); } struct LineColumn { From 8d6d7f0a64a0da8c132b2ffce6a3077a38faf2cf Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Tue, 30 Dec 2025 12:34:33 +1100 Subject: [PATCH 076/145] cc fix 5 --- .../dap/messages/highlight_error_dap_message.py | 16 ---------------- src/common/parsing/CodePreprocessing.cpp | 1 - 2 files changed, 17 deletions(-) diff --git a/python/mqt/debugger/dap/messages/highlight_error_dap_message.py b/python/mqt/debugger/dap/messages/highlight_error_dap_message.py index 7f5bda5d..1198c758 100644 --- a/python/mqt/debugger/dap/messages/highlight_error_dap_message.py +++ b/python/mqt/debugger/dap/messages/highlight_error_dap_message.py @@ -48,22 +48,6 @@ def validate(self) -> None: if "message" not in highlight or not str(highlight["message"]).strip(): msg = "Each highlight entry must contain a descriptive 'message'." raise ValueError(msg) - highlight_range = highlight.get("range") - if not isinstance(highlight_range, dict): - msg = "Each highlight entry must provide a 'range' dictionary." - raise TypeError(msg) - start = highlight_range.get("start") - end = highlight_range.get("end") - if not isinstance(start, dict) or not isinstance(end, dict): - msg = "Highlight ranges must define 'start' and 'end' coordinates." - raise TypeError(msg) - if self._later_than(start, end): - msg = "Highlight range 'end' must not precede 'start'." - raise ValueError(msg) - - if "name" not in self.source or "path" not in self.source: - msg = "Source information must at least expose 'name' and 'path'." - raise ValueError(msg) def encode(self) -> dict[str, Any]: """Encode the 'highlightError' DAP event message as a dictionary.""" diff --git a/src/common/parsing/CodePreprocessing.cpp b/src/common/parsing/CodePreprocessing.cpp index 920c9c4e..bf13ed45 100644 --- a/src/common/parsing/CodePreprocessing.cpp +++ b/src/common/parsing/CodePreprocessing.cpp @@ -26,7 +26,6 @@ #include #include #include -#include #include #include #include From fbb9bd1ada8a5ed3975dff23e09cdb3c7d85d480 Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Tue, 30 Dec 2025 12:52:23 +1100 Subject: [PATCH 077/145] cc fix 6 --- python/mqt/debugger/dap/dap_server.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/python/mqt/debugger/dap/dap_server.py b/python/mqt/debugger/dap/dap_server.py index e241d81d..f4a37eeb 100644 --- a/python/mqt/debugger/dap/dap_server.py +++ b/python/mqt/debugger/dap/dap_server.py @@ -463,7 +463,10 @@ def _build_highlight_entry(self, instruction: int, reason: str, message: str) -> except RuntimeError: return None start_line, start_column = self.code_pos_to_coordinates(start_pos) - end_position_exclusive = min(len(self.source_code), end_pos + 1) + if end_pos < len(self.source_code) and self.source_code[end_pos] == "\n": + end_position_exclusive = end_pos + else: + end_position_exclusive = min(len(self.source_code), end_pos + 1) end_line, end_column = self.code_pos_to_coordinates(end_position_exclusive) snippet = self.source_code[start_pos : end_pos + 1].replace("\r", "") return { From 3653c608d29506e646d439148fc7a87f3759a10b Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Wed, 31 Dec 2025 14:17:24 +1100 Subject: [PATCH 078/145] more docstrings --- .../messages/highlight_error_dap_message.py | 69 +++++++++++++++++-- 1 file changed, 62 insertions(+), 7 deletions(-) diff --git a/python/mqt/debugger/dap/messages/highlight_error_dap_message.py b/python/mqt/debugger/dap/messages/highlight_error_dap_message.py index 1198c758..a5f87ec9 100644 --- a/python/mqt/debugger/dap/messages/highlight_error_dap_message.py +++ b/python/mqt/debugger/dap/messages/highlight_error_dap_message.py @@ -20,7 +20,13 @@ class HighlightError(DAPEvent): - """Represents the 'highlightError' custom DAP event.""" + """Represents the 'highlightError' custom DAP event. + + Attributes: + event_name (str): DAP event identifier emitted by this message. + highlights (list[dict[str, Any]]): Normalized highlight entries with ranges and metadata. + source (dict[str, Any]): Normalized DAP source information for the highlighted file. + """ event_name = "highlightError" @@ -39,7 +45,11 @@ def __init__(self, highlights: Sequence[Mapping[str, Any]], source: Mapping[str, super().__init__() def validate(self) -> None: - """Validate the 'highlightError' DAP event message after creation.""" + """Validate the 'highlightError' DAP event message after creation. + + Raises: + ValueError: If required highlight fields are missing or empty. + """ if not self.highlights: msg = "At least one highlight entry is required to show the issue location." raise ValueError(msg) @@ -50,14 +60,29 @@ def validate(self) -> None: raise ValueError(msg) def encode(self) -> dict[str, Any]: - """Encode the 'highlightError' DAP event message as a dictionary.""" + """Encode the 'highlightError' DAP event message as a dictionary. + + Returns: + dict[str, Any]: The encoded DAP event payload. + """ encoded = super().encode() encoded["body"] = {"highlights": self.highlights, "source": self.source} return encoded @staticmethod def _normalize_highlight(entry: Mapping[str, Any]) -> dict[str, Any]: - """Return a shallow copy of a highlight entry with guaranteed structure.""" + """Return a shallow copy of a highlight entry with guaranteed structure. + + Args: + entry (Mapping[str, Any]): Highlight metadata including a range mapping. + + Returns: + dict[str, Any]: A normalized highlight entry suitable for serialization. + + Raises: + TypeError: If the range mapping or its positions are not mappings. + ValueError: If required fields are missing or malformed. + """ if "range" not in entry: msg = "A highlight entry must contain a 'range'." raise ValueError(msg) @@ -85,7 +110,18 @@ def _normalize_highlight(entry: Mapping[str, Any]) -> dict[str, Any]: @staticmethod def _normalize_position(position: Mapping[str, Any] | None) -> dict[str, int]: - """Normalize a position mapping, ensuring it includes a line and column.""" + """Normalize a position mapping, ensuring it includes a line and column. + + Args: + position (Mapping[str, Any] | None): The position mapping to normalize. + + Returns: + dict[str, int]: A normalized position with integer line and column. + + Raises: + TypeError: If the provided position is not a mapping. + ValueError: If required keys are missing. + """ if not isinstance(position, Mapping): msg = "Highlight positions must be mappings with 'line' and 'column'." raise TypeError(msg) @@ -102,7 +138,18 @@ def _normalize_position(position: Mapping[str, Any] | None) -> dict[str, int]: @staticmethod def _normalize_source(source: Mapping[str, Any] | None) -> dict[str, Any]: - """Create a defensive copy of the provided DAP Source information.""" + """Create a defensive copy of the provided DAP Source information. + + Args: + source (Mapping[str, Any] | None): The source mapping to normalize. + + Returns: + dict[str, Any]: Normalized source information with string fields. + + Raises: + TypeError: If the source is not a mapping. + ValueError: If required keys are missing. + """ if not isinstance(source, Mapping): msg = "Source information must be provided as a mapping." raise TypeError(msg) @@ -116,7 +163,15 @@ def _normalize_source(source: Mapping[str, Any] | None) -> dict[str, Any]: @staticmethod def _later_than(start: Mapping[str, Any], end: Mapping[str, Any]) -> bool: - """Return True if 'end' describes a position before 'start'.""" + """Return True if 'end' describes a position before 'start'. + + Args: + start (Mapping[str, Any]): The start position mapping. + end (Mapping[str, Any]): The end position mapping. + + Returns: + bool: True when the end position is before the start position. + """ start_line = int(start.get("line", 0)) start_column = int(start.get("column", 0)) end_line = int(end.get("line", 0)) From 4bb141eab38c861c025b60e1f5548955c788249a Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Sat, 10 Jan 2026 14:07:22 +0100 Subject: [PATCH 079/145] adjustemnts - coderabbit --- .../debugger/dap/messages/highlight_error_dap_message.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/python/mqt/debugger/dap/messages/highlight_error_dap_message.py b/python/mqt/debugger/dap/messages/highlight_error_dap_message.py index a5f87ec9..e7eca310 100644 --- a/python/mqt/debugger/dap/messages/highlight_error_dap_message.py +++ b/python/mqt/debugger/dap/messages/highlight_error_dap_message.py @@ -33,12 +33,12 @@ class HighlightError(DAPEvent): highlights: list[dict[str, Any]] source: dict[str, Any] - def __init__(self, highlights: Sequence[Mapping[str, Any]], source: Mapping[str, Any] | None) -> None: + def __init__(self, highlights: Sequence[Mapping[str, Any]], source: Mapping[str, Any]) -> None: """Create a new 'highlightError' DAP event message. Args: highlights (Sequence[Mapping[str, Any]]): Highlight entries describing the problematic ranges. - source (Mapping[str, Any] | None): Information about the current source file. + source (Mapping[str, Any]): Information about the current source file. """ self.highlights = [self._normalize_highlight(entry) for entry in highlights] self.source = self._normalize_source(source) @@ -93,7 +93,7 @@ def _normalize_highlight(entry: Mapping[str, Any]) -> dict[str, Any]: start = HighlightError._normalize_position(highlight_range.get("start")) end = HighlightError._normalize_position(highlight_range.get("end")) - if HighlightError._later_than(start, end): + if HighlightError._end_before_start(start, end): msg = "Highlight range 'end' must be after 'start'." raise ValueError(msg) @@ -162,7 +162,7 @@ def _normalize_source(source: Mapping[str, Any] | None) -> dict[str, Any]: return normalized @staticmethod - def _later_than(start: Mapping[str, Any], end: Mapping[str, Any]) -> bool: + def _end_before_start(start: Mapping[str, Any], end: Mapping[str, Any]) -> bool: """Return True if 'end' describes a position before 'start'. Args: From 62ea2cc6c4f27be83a31c1e9e800a6e9292b496f Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Sat, 10 Jan 2026 14:33:05 +0100 Subject: [PATCH 080/145] solution without STDERR --- bindings/InterfaceBindings.cpp | 15 +++++---------- include/backend/dd/DDSimDebug.hpp | 4 ++++ include/backend/debug.h | 11 +++++++++++ src/backend/dd/DDSimDebug.cpp | 20 +++++++++++++++++++- src/frontend/cli/CliFrontEnd.cpp | 10 +++++++++- 5 files changed, 48 insertions(+), 12 deletions(-) diff --git a/bindings/InterfaceBindings.cpp b/bindings/InterfaceBindings.cpp index 7f93d4c0..a1ff0d42 100644 --- a/bindings/InterfaceBindings.cpp +++ b/bindings/InterfaceBindings.cpp @@ -23,10 +23,8 @@ #include #include #include -#include #include #include -#include #include #include #include @@ -176,15 +174,12 @@ Contains one element for each of the `num_states` states in the state vector.)") .def( "load_code", [](SimulationState* self, const char* code) { - const py::module io = py::module::import("io"); - const py::object stringIo = io.attr("StringIO")(); - Result result = OK; - { - const py::scoped_ostream_redirect redirect(std::cerr, stringIo); - result = self->loadCode(self, code); - } + const Result result = self->loadCode(self, code); if (result != OK) { - auto message = stringIo.attr("getvalue")().cast(); + const char* messagePtr = self->getLastErrorMessage + ? self->getLastErrorMessage(self) + : nullptr; + std::string message = messagePtr ? messagePtr : ""; if (message.empty()) { message = "An error occurred while executing the operation"; } diff --git a/include/backend/dd/DDSimDebug.hpp b/include/backend/dd/DDSimDebug.hpp index bc8ab10e..c8957bf7 100644 --- a/include/backend/dd/DDSimDebug.hpp +++ b/include/backend/dd/DDSimDebug.hpp @@ -119,6 +119,10 @@ struct DDSimulationState { * @brief The code being executed, after preprocessing. */ std::string processedCode; + /** + * @brief The last error message produced by the interface. + */ + std::string lastErrorMessage; /** * @brief Indicates whether the debugger is ready to start simulation. */ diff --git a/include/backend/debug.h b/include/backend/debug.h index da83f5d5..23ae5292 100644 --- a/include/backend/debug.h +++ b/include/backend/debug.h @@ -54,6 +54,17 @@ struct SimulationStateStruct { */ Result (*loadCode)(SimulationState* self, const char* code); + /** + * @brief Gets the last error message from the interface. + * + * The returned pointer is owned by the implementation and remains valid + * until the next interface call that modifies the error state. + * + * @param self The instance to query. + * @return A null-terminated error message, or nullptr if none is available. + */ + const char* (*getLastErrorMessage)(SimulationState* self); + /** * @brief Steps the simulation forward by one instruction. * @param self The instance to step forward. diff --git a/src/backend/dd/DDSimDebug.cpp b/src/backend/dd/DDSimDebug.cpp index 7bc8c09e..4fd5c2fb 100644 --- a/src/backend/dd/DDSimDebug.cpp +++ b/src/backend/dd/DDSimDebug.cpp @@ -75,6 +75,14 @@ DDSimulationState* toDDSimulationState(SimulationState* state) { // NOLINTEND(cppcoreguidelines-pro-type-reinterpret-cast) } +const char* ddsimGetLastErrorMessage(SimulationState* self) { + const auto* ddsim = toDDSimulationState(self); + if (ddsim->lastErrorMessage.empty()) { + return nullptr; + } + return ddsim->lastErrorMessage.c_str(); +} + /** * @brief Generate a random number between 0 and 1. * @@ -513,6 +521,7 @@ Result createDDSimulationState(DDSimulationState* self) { self->interface.init = ddsimInit; self->interface.loadCode = ddsimLoadCode; + self->interface.getLastErrorMessage = ddsimGetLastErrorMessage; self->interface.stepForward = ddsimStepForward; self->interface.stepBackward = ddsimStepBackward; self->interface.stepOverForward = ddsimStepOverForward; @@ -572,6 +581,7 @@ Result ddsimInit(SimulationState* self) { ddsim->breakpoints.clear(); ddsim->lastFailedAssertion = -1ULL; ddsim->lastMetBreakpoint = -1ULL; + ddsim->lastErrorMessage.clear(); destroyDDDiagnostics(&ddsim->diagnostics); createDDDiagnostics(&ddsim->diagnostics, ddsim); @@ -606,6 +616,7 @@ Result ddsimLoadCode(SimulationState* self, const char* code) { ddsim->functionCallers.clear(); ddsim->targetQubits.clear(); ddsim->instructionObjects.clear(); + ddsim->lastErrorMessage.clear(); try { std::stringstream ss{preprocessAssertionCode(code, ddsim)}; @@ -613,7 +624,14 @@ Result ddsimLoadCode(SimulationState* self, const char* code) { ddsim->qc = std::make_unique(imported); qc::CircuitOptimizer::flattenOperations(*ddsim->qc, true); } catch (const std::exception& e) { - std::cerr << e.what() << "\n"; + ddsim->lastErrorMessage = e.what(); + if (ddsim->lastErrorMessage.empty()) { + ddsim->lastErrorMessage = + "An error occurred while executing the operation"; + } + return ERROR; + } catch (...) { + ddsim->lastErrorMessage = "An error occurred while executing the operation"; return ERROR; } diff --git a/src/frontend/cli/CliFrontEnd.cpp b/src/frontend/cli/CliFrontEnd.cpp index 27dd20f9..829d95d9 100644 --- a/src/frontend/cli/CliFrontEnd.cpp +++ b/src/frontend/cli/CliFrontEnd.cpp @@ -69,7 +69,15 @@ void CliFrontEnd::run(const char* code, SimulationState* state) { const auto result = state->loadCode(state, code); state->resetSimulation(state); if (result == ERROR) { - std::cout << "Error loading code\n"; + const char* message = nullptr; + if (state->getLastErrorMessage != nullptr) { + message = state->getLastErrorMessage(state); + } + if (message != nullptr && message[0] != '\0') { + std::cout << "Error loading code: " << message << "\n"; + } else { + std::cout << "Error loading code\n"; + } return; } From 9563c25da9e8c15105fb2ae75466cc55ac17712e Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Sat, 10 Jan 2026 14:54:23 +0100 Subject: [PATCH 081/145] adjustments ddSimAll --- python/mqt/debugger/dap/dap_server.py | 13 +++++++++---- src/backend/dd/DDSimDebug.cpp | 3 --- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/python/mqt/debugger/dap/dap_server.py b/python/mqt/debugger/dap/dap_server.py index f4a37eeb..006fa335 100644 --- a/python/mqt/debugger/dap/dap_server.py +++ b/python/mqt/debugger/dap/dap_server.py @@ -354,7 +354,7 @@ def handle_assertion_fail(self, connection: socket.socket) -> None: connection, "stderr", ) - highlight_entries = self.collect_highlight_entries(current_instruction) + highlight_entries = self.collect_highlight_entries(current_instruction, error_causes) if highlight_entries: highlight_event = mqt.debugger.dap.messages.HighlightError(highlight_entries, self.source_file) send_message(json.dumps(highlight_event.encode()), connection) @@ -428,13 +428,18 @@ def format_error_cause(self, cause: mqt.debugger.ErrorCause) -> str: else "" ) - def collect_highlight_entries(self, failing_instruction: int) -> list[dict[str, Any]]: + def collect_highlight_entries( + self, + failing_instruction: int, + error_causes: list[mqt.debugger.ErrorCause] | None = None, + ) -> list[dict[str, Any]]: """Collect highlight entries for the current assertion failure.""" highlights: list[dict[str, Any]] = [] if getattr(self, "source_code", ""): try: - diagnostics = self.simulation_state.get_diagnostics() - error_causes = diagnostics.potential_error_causes() + if error_causes is None: + diagnostics = self.simulation_state.get_diagnostics() + error_causes = diagnostics.potential_error_causes() except RuntimeError: error_causes = [] diff --git a/src/backend/dd/DDSimDebug.cpp b/src/backend/dd/DDSimDebug.cpp index 4fd5c2fb..875e579d 100644 --- a/src/backend/dd/DDSimDebug.cpp +++ b/src/backend/dd/DDSimDebug.cpp @@ -1151,9 +1151,6 @@ Result ddsimStepBackward(SimulationState* self) { Result ddsimRunAll(SimulationState* self, size_t* failedAssertions) { auto* ddsim = toDDSimulationState(self); - if (!ddsim->ready) { - return ERROR; - } size_t errorCount = 0; while (!self->isFinished(self)) { const Result result = self->runSimulation(self); From 4c34277a272035c4583a49db9b1d0a39f771c70e Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Sat, 10 Jan 2026 15:18:47 +0100 Subject: [PATCH 082/145] removed unused methods --- python/mqt/debugger/dap/dap_server.py | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/python/mqt/debugger/dap/dap_server.py b/python/mqt/debugger/dap/dap_server.py index 006fa335..ff39a13c 100644 --- a/python/mqt/debugger/dap/dap_server.py +++ b/python/mqt/debugger/dap/dap_server.py @@ -644,21 +644,3 @@ def send_message_simple( self.source_file, ) send_message(json.dumps(event.encode()), connection) - - def send_state(self, connection: socket.socket) -> None: - """Send the state of the current execution to the client. - - Args: - connection (socket.socket): The client socket. - """ - output_lines = [] - if self.simulation_state.did_assertion_fail(): - output_lines.append("Assertion failed") - if self.simulation_state.was_breakpoint_hit(): - output_lines.append("Breakpoint hit") - if self.simulation_state.is_finished(): - output_lines.append("Finished") - if not output_lines: - output_lines.append("Running") - for line_text in output_lines: - self.send_message_simple(line_text, None, None, 0, 0, connection) From ccfd5bb8aa67acc9710e26b750a85b176bf64365 Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Sat, 10 Jan 2026 15:39:07 +0100 Subject: [PATCH 083/145] docstring CodePreprocessing --- src/common/parsing/CodePreprocessing.cpp | 46 ++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/src/common/parsing/CodePreprocessing.cpp b/src/common/parsing/CodePreprocessing.cpp index bf13ed45..cbe6cbbd 100644 --- a/src/common/parsing/CodePreprocessing.cpp +++ b/src/common/parsing/CodePreprocessing.cpp @@ -35,6 +35,11 @@ namespace mqt::debugger { namespace { +/** + * @brief Check whether a string is non-empty and contains only digits. + * @param text The string to validate. + * @return True if the string is non-empty and all characters are digits. + */ bool isDigits(const std::string& text) { if (text.empty()) { return false; @@ -48,6 +53,12 @@ struct LineColumn { size_t column = 1; }; +/** + * @brief Compute the 1-based line and column for a given character offset. + * @param code The source code to inspect. + * @param offset The zero-based character offset in the source code. + * @return The line and column of the offset in the source code. + */ LineColumn lineColumnForOffset(const std::string& code, size_t offset) { LineColumn location; const auto lineStartPos = code.rfind('\n', offset); @@ -64,6 +75,13 @@ LineColumn lineColumnForOffset(const std::string& code, size_t offset) { return location; } +/** + * @brief Compute the 1-based line and column for a target within a line. + * @param code The source code to inspect. + * @param instructionStart The zero-based offset of the instruction start. + * @param target The target token to locate on the line. + * @return The line and column of the target, or the first non-space column. + */ LineColumn lineColumnForTarget(const std::string& code, size_t instructionStart, const std::string& target) { LineColumn location = lineColumnForOffset(code, instructionStart); @@ -90,6 +108,14 @@ LineColumn lineColumnForTarget(const std::string& code, size_t instructionStart, return location; } +/** + * @brief Format a parse error with line/column location information. + * @param code The source code to inspect. + * @param instructionStart The zero-based offset of the instruction start. + * @param detail The error detail text. + * @param target Optional target token to locate more precisely. + * @return The formatted error string. + */ std::string formatParseError(const std::string& code, size_t instructionStart, const std::string& detail, const std::string& target = "") { @@ -98,6 +124,12 @@ std::string formatParseError(const std::string& code, size_t instructionStart, std::to_string(location.column) + ": " + detail; } +/** + * @brief Build an error detail string for an invalid target. + * @param target The invalid target token. + * @param context Additional context to append. + * @return The formatted detail string. + */ std::string invalidTargetDetail(const std::string& target, const std::string& context) { std::string detail = "Invalid target qubit "; @@ -107,6 +139,11 @@ std::string invalidTargetDetail(const std::string& target, return detail; } +/** + * @brief Build an error detail string for an invalid register declaration. + * @param trimmedLine The register declaration line. + * @return The formatted detail string. + */ std::string invalidRegisterDetail(const std::string& trimmedLine) { std::string detail = "Invalid register declaration "; detail += trimmedLine; @@ -114,6 +151,15 @@ std::string invalidRegisterDetail(const std::string& trimmedLine) { return detail; } +/** + * @brief Validate target references against known registers and indices. + * @param code The source code to inspect. + * @param instructionStart The zero-based offset of the instruction start. + * @param targets The target tokens to validate. + * @param definedRegisters The registers defined in the current scope. + * @param shadowedRegisters The shadowed register names in the current scope. + * @param context Additional context to append to error messages. + */ void validateTargets(const std::string& code, size_t instructionStart, const std::vector& targets, const std::map& definedRegisters, From c2566904d808700f662b800c0b723f846125d2a1 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 11 Jan 2026 11:55:18 +0000 Subject: [PATCH 084/145] =?UTF-8?q?=F0=9F=8E=A8=20pre-commit=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mqt/debugger/dap/messages/highlight_error_dap_message.py | 4 ++-- python/mqt/debugger/dap/messages/launch_dap_message.py | 4 ++-- python/mqt/debugger/dap/messages/restart_dap_message.py | 4 ++-- test/python/test_dap_server.py | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/python/mqt/debugger/dap/messages/highlight_error_dap_message.py b/python/mqt/debugger/dap/messages/highlight_error_dap_message.py index e7eca310..64d8ae59 100644 --- a/python/mqt/debugger/dap/messages/highlight_error_dap_message.py +++ b/python/mqt/debugger/dap/messages/highlight_error_dap_message.py @@ -1,5 +1,5 @@ -# Copyright (c) 2024 - 2025 Chair for Design Automation, TUM -# Copyright (c) 2025 Munich Quantum Software Company GmbH +# Copyright (c) 2024 - 2026 Chair for Design Automation, TUM +# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH # All rights reserved. # # SPDX-License-Identifier: MIT diff --git a/python/mqt/debugger/dap/messages/launch_dap_message.py b/python/mqt/debugger/dap/messages/launch_dap_message.py index 10d09a5d..c153723a 100644 --- a/python/mqt/debugger/dap/messages/launch_dap_message.py +++ b/python/mqt/debugger/dap/messages/launch_dap_message.py @@ -1,5 +1,5 @@ -# Copyright (c) 2024 - 2025 Chair for Design Automation, TUM -# Copyright (c) 2025 Munich Quantum Software Company GmbH +# Copyright (c) 2024 - 2026 Chair for Design Automation, TUM +# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH # All rights reserved. # # SPDX-License-Identifier: MIT diff --git a/python/mqt/debugger/dap/messages/restart_dap_message.py b/python/mqt/debugger/dap/messages/restart_dap_message.py index d19973fe..7f84658a 100644 --- a/python/mqt/debugger/dap/messages/restart_dap_message.py +++ b/python/mqt/debugger/dap/messages/restart_dap_message.py @@ -1,5 +1,5 @@ -# Copyright (c) 2024 - 2025 Chair for Design Automation, TUM -# Copyright (c) 2025 Munich Quantum Software Company GmbH +# Copyright (c) 2024 - 2026 Chair for Design Automation, TUM +# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH # All rights reserved. # # SPDX-License-Identifier: MIT diff --git a/test/python/test_dap_server.py b/test/python/test_dap_server.py index 156c020a..229b8bdf 100644 --- a/test/python/test_dap_server.py +++ b/test/python/test_dap_server.py @@ -1,5 +1,5 @@ -# Copyright (c) 2024 - 2025 Chair for Design Automation, TUM -# Copyright (c) 2025 Munich Quantum Software Company GmbH +# Copyright (c) 2024 - 2026 Chair for Design Automation, TUM +# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH # All rights reserved. # # SPDX-License-Identifier: MIT From cf270eb3c736d6fc3a1c4b92f3870c86ec1a4412 Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Sun, 11 Jan 2026 14:23:14 +0100 Subject: [PATCH 085/145] launch,restart, LoadResult --- bindings/InterfaceBindings.cpp | 33 +++++++++++++ include/backend/dd/DDSimDebug.hpp | 16 +++++++ include/backend/debug.h | 31 ++++++++++++ python/mqt/debugger/dap/dap_server.py | 18 +------ .../messages/highlight_error_dap_message.py | 4 +- .../dap/messages/launch_dap_message.py | 14 +++--- .../dap/messages/restart_dap_message.py | 14 +++--- python/mqt/debugger/pydebugger.pyi | 25 ++++++++++ src/backend/dd/DDSimDebug.cpp | 47 +++++++++++++++++++ 9 files changed, 167 insertions(+), 35 deletions(-) diff --git a/bindings/InterfaceBindings.cpp b/bindings/InterfaceBindings.cpp index 1608625e..1ec5777c 100644 --- a/bindings/InterfaceBindings.cpp +++ b/bindings/InterfaceBindings.cpp @@ -166,6 +166,23 @@ Contains one element for each of the `num_states` states in the state vector.)") .doc() = "The settings that should be used to compile an assertion program."; + py::class_(m, "LoadResult") + .def(py::init<>(), "Creates a new `LoadResult` instance.") + .def_readonly("success", &LoadResult::success, + "True if the code was loaded successfully.") + .def_readonly("line", &LoadResult::line, + "The 1-based line of the error location, or 0 if unknown.") + .def_readonly( + "column", &LoadResult::column, + "The 1-based column of the error location, or 0 if unknown.") + .def_property_readonly( + "message", + [](const LoadResult& self) { + return std::string(self.message ? self.message : ""); + }, + "The error message, or an empty string if none is available.") + .doc() = "Represents the result of loading code into the simulator."; + py::class_(m, "SimulationState") .def(py::init<>(), "Creates a new `SimulationState` instance.") .def( @@ -190,6 +207,22 @@ Contains one element for each of the `num_states` states in the state vector.)") Args: code (str): The code to load.)") + .def( + "load_code_with_result", + [](SimulationState* self, const char* code) { + if (self->loadCodeWithResult == nullptr) { + throw std::runtime_error( + "load_code_with_result is not available"); + } + return self->loadCodeWithResult(self, code); + }, + R"(Loads the given code into the simulation state and returns diagnostics. + +Args: + code (str): The code to load. + +Returns: + LoadResult: The load result containing diagnostics.)") .def( "step_forward", [](SimulationState* self) { checkOrThrow(self->stepForward(self)); }, diff --git a/include/backend/dd/DDSimDebug.hpp b/include/backend/dd/DDSimDebug.hpp index 13a3e9b7..df460ba9 100644 --- a/include/backend/dd/DDSimDebug.hpp +++ b/include/backend/dd/DDSimDebug.hpp @@ -123,6 +123,14 @@ struct DDSimulationState { * @brief The last error message produced by the interface. */ std::string lastErrorMessage; + /** + * @brief The line number associated with the last error, if available. + */ + size_t lastErrorLine = 0; + /** + * @brief The column number associated with the last error, if available. + */ + size_t lastErrorColumn = 0; /** * @brief Indicates whether the debugger is ready to start simulation. */ @@ -284,6 +292,14 @@ Result ddsimInit(SimulationState* self); * @return The result of the operation. */ Result ddsimLoadCode(SimulationState* self, const char* code); +/** + * @brief Loads the given code into the simulation state and returns + * diagnostics. + * @param self The instance to load the code into. + * @param code The code to load. + * @return The load result containing diagnostics. + */ +LoadResult ddsimLoadCodeWithResult(SimulationState* self, const char* code); /** * @brief Steps the simulation forward by one instruction. * @param self The instance to step forward. diff --git a/include/backend/debug.h b/include/backend/debug.h index 0eacae23..911cb0aa 100644 --- a/include/backend/debug.h +++ b/include/backend/debug.h @@ -38,6 +38,28 @@ extern "C" { */ typedef struct SimulationStateStruct SimulationState; +/** + * @brief Represents the result of loading code into the simulation state. + */ +typedef struct { + /** + * @brief True if the code was loaded successfully. + */ + bool success; + /** + * @brief The 1-based line of the error location, or 0 if unavailable. + */ + size_t line; + /** + * @brief The 1-based column of the error location, or 0 if unavailable. + */ + size_t column; + /** + * @brief The error message, or an empty string if none is available. + */ + const char* message; +} LoadResult; + struct SimulationStateStruct { /** * @brief Initializes the simulation state. @@ -54,6 +76,15 @@ struct SimulationStateStruct { */ Result (*loadCode)(SimulationState* self, const char* code); + /** + * @brief Loads the given code into the simulation state and returns detailed + * diagnostics. + * @param self The instance to load the code into. + * @param code The code to load. + * @return The load result containing diagnostics. + */ + LoadResult (*loadCodeWithResult)(SimulationState* self, const char* code); + /** * @brief Gets the last error message from the interface. * diff --git a/python/mqt/debugger/dap/dap_server.py b/python/mqt/debugger/dap/dap_server.py index 2aa6c43b..0aa48e79 100644 --- a/python/mqt/debugger/dap/dap_server.py +++ b/python/mqt/debugger/dap/dap_server.py @@ -11,7 +11,6 @@ from __future__ import annotations import json -import re import socket import sys from typing import TYPE_CHECKING, Any @@ -494,27 +493,12 @@ def _format_highlight_reason(cause_type: mqt.debugger.ErrorCauseType | None) -> return "controlAlwaysZero" return "unknown" - def queue_parse_error(self, error_message: str) -> None: + def queue_parse_error(self, line: int, column: int, detail: str) -> None: """Store highlight data for a parse error to be emitted later.""" - line, column, detail = self._parse_error_location(error_message) entry = self._build_parse_error_highlight(line, column, detail) if entry is not None: self.pending_highlights = [entry] - @staticmethod - def _parse_error_location(error_message: str) -> tuple[int, int, str]: - """Parse a compiler error string and extract the source location.""" - match = re.match(r":(\d+):(\d+):\s*(.*)", error_message.strip()) - if match: - line = int(match.group(1)) - column = int(match.group(2)) - detail = match.group(3).strip() - else: - line = 1 - column = 1 - detail = error_message.strip() - return (line, column, detail) - def _build_parse_error_highlight(self, line: int, column: int, detail: str) -> dict[str, Any] | None: """Create a highlight entry for a parse error.""" if not getattr(self, "source_code", ""): diff --git a/python/mqt/debugger/dap/messages/highlight_error_dap_message.py b/python/mqt/debugger/dap/messages/highlight_error_dap_message.py index 64d8ae59..9c898453 100644 --- a/python/mqt/debugger/dap/messages/highlight_error_dap_message.py +++ b/python/mqt/debugger/dap/messages/highlight_error_dap_message.py @@ -93,7 +93,7 @@ def _normalize_highlight(entry: Mapping[str, Any]) -> dict[str, Any]: start = HighlightError._normalize_position(highlight_range.get("start")) end = HighlightError._normalize_position(highlight_range.get("end")) - if HighlightError._end_before_start(start, end): + if HighlightError._start_comes_after_end(start, end): msg = "Highlight range 'end' must be after 'start'." raise ValueError(msg) @@ -162,7 +162,7 @@ def _normalize_source(source: Mapping[str, Any] | None) -> dict[str, Any]: return normalized @staticmethod - def _end_before_start(start: Mapping[str, Any], end: Mapping[str, Any]) -> bool: + def _start_comes_after_end(start: Mapping[str, Any], end: Mapping[str, Any]) -> bool: """Return True if 'end' describes a position before 'start'. Args: diff --git a/python/mqt/debugger/dap/messages/launch_dap_message.py b/python/mqt/debugger/dap/messages/launch_dap_message.py index c153723a..309271c9 100644 --- a/python/mqt/debugger/dap/messages/launch_dap_message.py +++ b/python/mqt/debugger/dap/messages/launch_dap_message.py @@ -66,14 +66,12 @@ def handle(self, server: DAPServer) -> dict[str, Any]: program_path = Path(self.program) server.source_file = {"name": program_path.name, "path": self.program} parsed_successfully = True - with program_path.open("r", encoding=locale.getpreferredencoding(False)) as f: - code = f.read() - server.source_code = code - try: - server.simulation_state.load_code(code) - except RuntimeError as exc: - parsed_successfully = False - server.queue_parse_error(str(exc)) + code = program_path.read_text(encoding=locale.getpreferredencoding(False)) + server.source_code = code + load_result = server.simulation_state.load_code_with_result(code) + if not load_result.success: + parsed_successfully = False + server.queue_parse_error(load_result.line, load_result.column, load_result.message) if parsed_successfully and not self.stop_on_entry: server.simulation_state.run_simulation() if not parsed_successfully: diff --git a/python/mqt/debugger/dap/messages/restart_dap_message.py b/python/mqt/debugger/dap/messages/restart_dap_message.py index 7f84658a..62224a23 100644 --- a/python/mqt/debugger/dap/messages/restart_dap_message.py +++ b/python/mqt/debugger/dap/messages/restart_dap_message.py @@ -67,14 +67,12 @@ def handle(self, server: DAPServer) -> dict[str, Any]: program_path = Path(self.program) server.source_file = {"name": program_path.name, "path": self.program} parsed_successfully = True - with program_path.open("r", encoding=locale.getpreferredencoding(False)) as f: - code = f.read() - server.source_code = code - try: - server.simulation_state.load_code(code) - except RuntimeError as exc: - parsed_successfully = False - server.queue_parse_error(str(exc)) + code = program_path.read_text(encoding=locale.getpreferredencoding(False)) + server.source_code = code + load_result = server.simulation_state.load_code_with_result(code) + if not load_result.success: + parsed_successfully = False + server.queue_parse_error(load_result.line, load_result.column, load_result.message) if parsed_successfully and not self.stop_on_entry: server.simulation_state.run_simulation() if not parsed_successfully: diff --git a/python/mqt/debugger/pydebugger.pyi b/python/mqt/debugger/pydebugger.pyi index c4476b00..50807ec4 100644 --- a/python/mqt/debugger/pydebugger.pyi +++ b/python/mqt/debugger/pydebugger.pyi @@ -95,6 +95,21 @@ class CompilationSettings: slice_index (int, optional): The index of the slice that should be compiled (defaults to 0). """ +class LoadResult: + """Represents the result of loading code into the simulator.""" + + success: bool + """True if the code was loaded successfully.""" + line: int + """The 1-based line of the error location, or 0 if unknown.""" + column: int + """The 1-based column of the error location, or 0 if unknown.""" + message: str + """The error message, or an empty string if none is available.""" + + def __init__(self) -> None: + """Creates a new `LoadResult` instance.""" + class Statevector: """Represents a state vector.""" @@ -134,6 +149,16 @@ class SimulationState: code (str): The code to load. """ + def load_code_with_result(self, code: str) -> LoadResult: + """Loads the given code into the simulation state and returns diagnostics. + + Args: + code (str): The code to load. + + Returns: + LoadResult: The load result containing diagnostics. + """ + def step_forward(self) -> None: """Steps the simulation forward by one instruction.""" diff --git a/src/backend/dd/DDSimDebug.cpp b/src/backend/dd/DDSimDebug.cpp index a8d0e3da..00351cca 100644 --- a/src/backend/dd/DDSimDebug.cpp +++ b/src/backend/dd/DDSimDebug.cpp @@ -34,6 +34,7 @@ #include "ir/Register.hpp" #include "ir/operations/IfElseOperation.hpp" #include "ir/operations/OpType.hpp" +#include "qasm3/Exception.hpp" #include "qasm3/Importer.hpp" #include @@ -521,6 +522,7 @@ Result createDDSimulationState(DDSimulationState* self) { self->interface.init = ddsimInit; self->interface.loadCode = ddsimLoadCode; + self->interface.loadCodeWithResult = ddsimLoadCodeWithResult; self->interface.getLastErrorMessage = ddsimGetLastErrorMessage; self->interface.stepForward = ddsimStepForward; self->interface.stepBackward = ddsimStepBackward; @@ -582,6 +584,8 @@ Result ddsimInit(SimulationState* self) { ddsim->lastFailedAssertion = -1ULL; ddsim->lastMetBreakpoint = -1ULL; ddsim->lastErrorMessage.clear(); + ddsim->lastErrorLine = 0; + ddsim->lastErrorColumn = 0; destroyDDDiagnostics(&ddsim->diagnostics); createDDDiagnostics(&ddsim->diagnostics, ddsim); @@ -617,21 +621,52 @@ Result ddsimLoadCode(SimulationState* self, const char* code) { ddsim->targetQubits.clear(); ddsim->instructionObjects.clear(); ddsim->lastErrorMessage.clear(); + ddsim->lastErrorLine = 0; + ddsim->lastErrorColumn = 0; try { std::stringstream ss{preprocessAssertionCode(code, ddsim)}; const auto imported = qasm3::Importer::import(ss); ddsim->qc = std::make_unique(imported); qc::CircuitOptimizer::flattenOperations(*ddsim->qc, true); + } catch (const qasm3::CompilerError& e) { + if (e.message.empty()) { + ddsim->lastErrorMessage = + "An error occurred while executing the operation"; + } else { + ddsim->lastErrorMessage = e.message; + } + if (e.debugInfo != nullptr) { + ddsim->lastErrorLine = e.debugInfo->line; + ddsim->lastErrorColumn = e.debugInfo->column; + } else { + ddsim->lastErrorLine = 1; + ddsim->lastErrorColumn = 1; + } + return ERROR; + } catch (const qasm3::ConstEvalError& e) { + ddsim->lastErrorMessage = e.what(); + ddsim->lastErrorLine = 1; + ddsim->lastErrorColumn = 1; + return ERROR; + } catch (const qasm3::TypeCheckError& e) { + ddsim->lastErrorMessage = e.what(); + ddsim->lastErrorLine = 1; + ddsim->lastErrorColumn = 1; + return ERROR; } catch (const std::exception& e) { ddsim->lastErrorMessage = e.what(); if (ddsim->lastErrorMessage.empty()) { ddsim->lastErrorMessage = "An error occurred while executing the operation"; } + ddsim->lastErrorLine = 1; + ddsim->lastErrorColumn = 1; return ERROR; } catch (...) { ddsim->lastErrorMessage = "An error occurred while executing the operation"; + ddsim->lastErrorLine = 1; + ddsim->lastErrorColumn = 1; return ERROR; } @@ -647,6 +682,18 @@ Result ddsimLoadCode(SimulationState* self, const char* code) { return OK; } +LoadResult ddsimLoadCodeWithResult(SimulationState* self, const char* code) { + const auto result = ddsimLoadCode(self, code); + const auto* ddsim = toDDSimulationState(self); + LoadResult loadResult{}; + loadResult.success = (result == OK); + loadResult.line = ddsim->lastErrorLine; + loadResult.column = ddsim->lastErrorColumn; + loadResult.message = + ddsim->lastErrorMessage.empty() ? "" : ddsim->lastErrorMessage.c_str(); + return loadResult; +} + Result ddsimChangeClassicalVariableValue(SimulationState* self, const char* variableName, const VariableValue* value) { From 489592b90d10fdef5af4d6c67dbb03126578d743 Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Sun, 11 Jan 2026 14:38:03 +0100 Subject: [PATCH 086/145] Revert "launch,restart, LoadResult" This reverts commit cf270eb3c736d6fc3a1c4b92f3870c86ec1a4412. --- bindings/InterfaceBindings.cpp | 33 ------------- include/backend/dd/DDSimDebug.hpp | 16 ------- include/backend/debug.h | 31 ------------ python/mqt/debugger/dap/dap_server.py | 18 ++++++- .../messages/highlight_error_dap_message.py | 4 +- .../dap/messages/launch_dap_message.py | 14 +++--- .../dap/messages/restart_dap_message.py | 14 +++--- python/mqt/debugger/pydebugger.pyi | 25 ---------- src/backend/dd/DDSimDebug.cpp | 47 ------------------- 9 files changed, 35 insertions(+), 167 deletions(-) diff --git a/bindings/InterfaceBindings.cpp b/bindings/InterfaceBindings.cpp index 1ec5777c..1608625e 100644 --- a/bindings/InterfaceBindings.cpp +++ b/bindings/InterfaceBindings.cpp @@ -166,23 +166,6 @@ Contains one element for each of the `num_states` states in the state vector.)") .doc() = "The settings that should be used to compile an assertion program."; - py::class_(m, "LoadResult") - .def(py::init<>(), "Creates a new `LoadResult` instance.") - .def_readonly("success", &LoadResult::success, - "True if the code was loaded successfully.") - .def_readonly("line", &LoadResult::line, - "The 1-based line of the error location, or 0 if unknown.") - .def_readonly( - "column", &LoadResult::column, - "The 1-based column of the error location, or 0 if unknown.") - .def_property_readonly( - "message", - [](const LoadResult& self) { - return std::string(self.message ? self.message : ""); - }, - "The error message, or an empty string if none is available.") - .doc() = "Represents the result of loading code into the simulator."; - py::class_(m, "SimulationState") .def(py::init<>(), "Creates a new `SimulationState` instance.") .def( @@ -207,22 +190,6 @@ Contains one element for each of the `num_states` states in the state vector.)") Args: code (str): The code to load.)") - .def( - "load_code_with_result", - [](SimulationState* self, const char* code) { - if (self->loadCodeWithResult == nullptr) { - throw std::runtime_error( - "load_code_with_result is not available"); - } - return self->loadCodeWithResult(self, code); - }, - R"(Loads the given code into the simulation state and returns diagnostics. - -Args: - code (str): The code to load. - -Returns: - LoadResult: The load result containing diagnostics.)") .def( "step_forward", [](SimulationState* self) { checkOrThrow(self->stepForward(self)); }, diff --git a/include/backend/dd/DDSimDebug.hpp b/include/backend/dd/DDSimDebug.hpp index df460ba9..13a3e9b7 100644 --- a/include/backend/dd/DDSimDebug.hpp +++ b/include/backend/dd/DDSimDebug.hpp @@ -123,14 +123,6 @@ struct DDSimulationState { * @brief The last error message produced by the interface. */ std::string lastErrorMessage; - /** - * @brief The line number associated with the last error, if available. - */ - size_t lastErrorLine = 0; - /** - * @brief The column number associated with the last error, if available. - */ - size_t lastErrorColumn = 0; /** * @brief Indicates whether the debugger is ready to start simulation. */ @@ -292,14 +284,6 @@ Result ddsimInit(SimulationState* self); * @return The result of the operation. */ Result ddsimLoadCode(SimulationState* self, const char* code); -/** - * @brief Loads the given code into the simulation state and returns - * diagnostics. - * @param self The instance to load the code into. - * @param code The code to load. - * @return The load result containing diagnostics. - */ -LoadResult ddsimLoadCodeWithResult(SimulationState* self, const char* code); /** * @brief Steps the simulation forward by one instruction. * @param self The instance to step forward. diff --git a/include/backend/debug.h b/include/backend/debug.h index 911cb0aa..0eacae23 100644 --- a/include/backend/debug.h +++ b/include/backend/debug.h @@ -38,28 +38,6 @@ extern "C" { */ typedef struct SimulationStateStruct SimulationState; -/** - * @brief Represents the result of loading code into the simulation state. - */ -typedef struct { - /** - * @brief True if the code was loaded successfully. - */ - bool success; - /** - * @brief The 1-based line of the error location, or 0 if unavailable. - */ - size_t line; - /** - * @brief The 1-based column of the error location, or 0 if unavailable. - */ - size_t column; - /** - * @brief The error message, or an empty string if none is available. - */ - const char* message; -} LoadResult; - struct SimulationStateStruct { /** * @brief Initializes the simulation state. @@ -76,15 +54,6 @@ struct SimulationStateStruct { */ Result (*loadCode)(SimulationState* self, const char* code); - /** - * @brief Loads the given code into the simulation state and returns detailed - * diagnostics. - * @param self The instance to load the code into. - * @param code The code to load. - * @return The load result containing diagnostics. - */ - LoadResult (*loadCodeWithResult)(SimulationState* self, const char* code); - /** * @brief Gets the last error message from the interface. * diff --git a/python/mqt/debugger/dap/dap_server.py b/python/mqt/debugger/dap/dap_server.py index 0aa48e79..2aa6c43b 100644 --- a/python/mqt/debugger/dap/dap_server.py +++ b/python/mqt/debugger/dap/dap_server.py @@ -11,6 +11,7 @@ from __future__ import annotations import json +import re import socket import sys from typing import TYPE_CHECKING, Any @@ -493,12 +494,27 @@ def _format_highlight_reason(cause_type: mqt.debugger.ErrorCauseType | None) -> return "controlAlwaysZero" return "unknown" - def queue_parse_error(self, line: int, column: int, detail: str) -> None: + def queue_parse_error(self, error_message: str) -> None: """Store highlight data for a parse error to be emitted later.""" + line, column, detail = self._parse_error_location(error_message) entry = self._build_parse_error_highlight(line, column, detail) if entry is not None: self.pending_highlights = [entry] + @staticmethod + def _parse_error_location(error_message: str) -> tuple[int, int, str]: + """Parse a compiler error string and extract the source location.""" + match = re.match(r":(\d+):(\d+):\s*(.*)", error_message.strip()) + if match: + line = int(match.group(1)) + column = int(match.group(2)) + detail = match.group(3).strip() + else: + line = 1 + column = 1 + detail = error_message.strip() + return (line, column, detail) + def _build_parse_error_highlight(self, line: int, column: int, detail: str) -> dict[str, Any] | None: """Create a highlight entry for a parse error.""" if not getattr(self, "source_code", ""): diff --git a/python/mqt/debugger/dap/messages/highlight_error_dap_message.py b/python/mqt/debugger/dap/messages/highlight_error_dap_message.py index 9c898453..64d8ae59 100644 --- a/python/mqt/debugger/dap/messages/highlight_error_dap_message.py +++ b/python/mqt/debugger/dap/messages/highlight_error_dap_message.py @@ -93,7 +93,7 @@ def _normalize_highlight(entry: Mapping[str, Any]) -> dict[str, Any]: start = HighlightError._normalize_position(highlight_range.get("start")) end = HighlightError._normalize_position(highlight_range.get("end")) - if HighlightError._start_comes_after_end(start, end): + if HighlightError._end_before_start(start, end): msg = "Highlight range 'end' must be after 'start'." raise ValueError(msg) @@ -162,7 +162,7 @@ def _normalize_source(source: Mapping[str, Any] | None) -> dict[str, Any]: return normalized @staticmethod - def _start_comes_after_end(start: Mapping[str, Any], end: Mapping[str, Any]) -> bool: + def _end_before_start(start: Mapping[str, Any], end: Mapping[str, Any]) -> bool: """Return True if 'end' describes a position before 'start'. Args: diff --git a/python/mqt/debugger/dap/messages/launch_dap_message.py b/python/mqt/debugger/dap/messages/launch_dap_message.py index 309271c9..c153723a 100644 --- a/python/mqt/debugger/dap/messages/launch_dap_message.py +++ b/python/mqt/debugger/dap/messages/launch_dap_message.py @@ -66,12 +66,14 @@ def handle(self, server: DAPServer) -> dict[str, Any]: program_path = Path(self.program) server.source_file = {"name": program_path.name, "path": self.program} parsed_successfully = True - code = program_path.read_text(encoding=locale.getpreferredencoding(False)) - server.source_code = code - load_result = server.simulation_state.load_code_with_result(code) - if not load_result.success: - parsed_successfully = False - server.queue_parse_error(load_result.line, load_result.column, load_result.message) + with program_path.open("r", encoding=locale.getpreferredencoding(False)) as f: + code = f.read() + server.source_code = code + try: + server.simulation_state.load_code(code) + except RuntimeError as exc: + parsed_successfully = False + server.queue_parse_error(str(exc)) if parsed_successfully and not self.stop_on_entry: server.simulation_state.run_simulation() if not parsed_successfully: diff --git a/python/mqt/debugger/dap/messages/restart_dap_message.py b/python/mqt/debugger/dap/messages/restart_dap_message.py index 62224a23..7f84658a 100644 --- a/python/mqt/debugger/dap/messages/restart_dap_message.py +++ b/python/mqt/debugger/dap/messages/restart_dap_message.py @@ -67,12 +67,14 @@ def handle(self, server: DAPServer) -> dict[str, Any]: program_path = Path(self.program) server.source_file = {"name": program_path.name, "path": self.program} parsed_successfully = True - code = program_path.read_text(encoding=locale.getpreferredencoding(False)) - server.source_code = code - load_result = server.simulation_state.load_code_with_result(code) - if not load_result.success: - parsed_successfully = False - server.queue_parse_error(load_result.line, load_result.column, load_result.message) + with program_path.open("r", encoding=locale.getpreferredencoding(False)) as f: + code = f.read() + server.source_code = code + try: + server.simulation_state.load_code(code) + except RuntimeError as exc: + parsed_successfully = False + server.queue_parse_error(str(exc)) if parsed_successfully and not self.stop_on_entry: server.simulation_state.run_simulation() if not parsed_successfully: diff --git a/python/mqt/debugger/pydebugger.pyi b/python/mqt/debugger/pydebugger.pyi index 50807ec4..c4476b00 100644 --- a/python/mqt/debugger/pydebugger.pyi +++ b/python/mqt/debugger/pydebugger.pyi @@ -95,21 +95,6 @@ class CompilationSettings: slice_index (int, optional): The index of the slice that should be compiled (defaults to 0). """ -class LoadResult: - """Represents the result of loading code into the simulator.""" - - success: bool - """True if the code was loaded successfully.""" - line: int - """The 1-based line of the error location, or 0 if unknown.""" - column: int - """The 1-based column of the error location, or 0 if unknown.""" - message: str - """The error message, or an empty string if none is available.""" - - def __init__(self) -> None: - """Creates a new `LoadResult` instance.""" - class Statevector: """Represents a state vector.""" @@ -149,16 +134,6 @@ class SimulationState: code (str): The code to load. """ - def load_code_with_result(self, code: str) -> LoadResult: - """Loads the given code into the simulation state and returns diagnostics. - - Args: - code (str): The code to load. - - Returns: - LoadResult: The load result containing diagnostics. - """ - def step_forward(self) -> None: """Steps the simulation forward by one instruction.""" diff --git a/src/backend/dd/DDSimDebug.cpp b/src/backend/dd/DDSimDebug.cpp index 00351cca..a8d0e3da 100644 --- a/src/backend/dd/DDSimDebug.cpp +++ b/src/backend/dd/DDSimDebug.cpp @@ -34,7 +34,6 @@ #include "ir/Register.hpp" #include "ir/operations/IfElseOperation.hpp" #include "ir/operations/OpType.hpp" -#include "qasm3/Exception.hpp" #include "qasm3/Importer.hpp" #include @@ -522,7 +521,6 @@ Result createDDSimulationState(DDSimulationState* self) { self->interface.init = ddsimInit; self->interface.loadCode = ddsimLoadCode; - self->interface.loadCodeWithResult = ddsimLoadCodeWithResult; self->interface.getLastErrorMessage = ddsimGetLastErrorMessage; self->interface.stepForward = ddsimStepForward; self->interface.stepBackward = ddsimStepBackward; @@ -584,8 +582,6 @@ Result ddsimInit(SimulationState* self) { ddsim->lastFailedAssertion = -1ULL; ddsim->lastMetBreakpoint = -1ULL; ddsim->lastErrorMessage.clear(); - ddsim->lastErrorLine = 0; - ddsim->lastErrorColumn = 0; destroyDDDiagnostics(&ddsim->diagnostics); createDDDiagnostics(&ddsim->diagnostics, ddsim); @@ -621,52 +617,21 @@ Result ddsimLoadCode(SimulationState* self, const char* code) { ddsim->targetQubits.clear(); ddsim->instructionObjects.clear(); ddsim->lastErrorMessage.clear(); - ddsim->lastErrorLine = 0; - ddsim->lastErrorColumn = 0; try { std::stringstream ss{preprocessAssertionCode(code, ddsim)}; const auto imported = qasm3::Importer::import(ss); ddsim->qc = std::make_unique(imported); qc::CircuitOptimizer::flattenOperations(*ddsim->qc, true); - } catch (const qasm3::CompilerError& e) { - if (e.message.empty()) { - ddsim->lastErrorMessage = - "An error occurred while executing the operation"; - } else { - ddsim->lastErrorMessage = e.message; - } - if (e.debugInfo != nullptr) { - ddsim->lastErrorLine = e.debugInfo->line; - ddsim->lastErrorColumn = e.debugInfo->column; - } else { - ddsim->lastErrorLine = 1; - ddsim->lastErrorColumn = 1; - } - return ERROR; - } catch (const qasm3::ConstEvalError& e) { - ddsim->lastErrorMessage = e.what(); - ddsim->lastErrorLine = 1; - ddsim->lastErrorColumn = 1; - return ERROR; - } catch (const qasm3::TypeCheckError& e) { - ddsim->lastErrorMessage = e.what(); - ddsim->lastErrorLine = 1; - ddsim->lastErrorColumn = 1; - return ERROR; } catch (const std::exception& e) { ddsim->lastErrorMessage = e.what(); if (ddsim->lastErrorMessage.empty()) { ddsim->lastErrorMessage = "An error occurred while executing the operation"; } - ddsim->lastErrorLine = 1; - ddsim->lastErrorColumn = 1; return ERROR; } catch (...) { ddsim->lastErrorMessage = "An error occurred while executing the operation"; - ddsim->lastErrorLine = 1; - ddsim->lastErrorColumn = 1; return ERROR; } @@ -682,18 +647,6 @@ Result ddsimLoadCode(SimulationState* self, const char* code) { return OK; } -LoadResult ddsimLoadCodeWithResult(SimulationState* self, const char* code) { - const auto result = ddsimLoadCode(self, code); - const auto* ddsim = toDDSimulationState(self); - LoadResult loadResult{}; - loadResult.success = (result == OK); - loadResult.line = ddsim->lastErrorLine; - loadResult.column = ddsim->lastErrorColumn; - loadResult.message = - ddsim->lastErrorMessage.empty() ? "" : ddsim->lastErrorMessage.c_str(); - return loadResult; -} - Result ddsimChangeClassicalVariableValue(SimulationState* self, const char* variableName, const VariableValue* value) { From 4de1403cef86e36a9df8ece32eabe4c7844951b4 Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Sun, 11 Jan 2026 21:10:13 +0100 Subject: [PATCH 087/145] fix launch and restart --- .../debugger/dap/messages/launch_dap_message.py | 15 +++++++-------- .../debugger/dap/messages/restart_dap_message.py | 15 +++++++-------- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/python/mqt/debugger/dap/messages/launch_dap_message.py b/python/mqt/debugger/dap/messages/launch_dap_message.py index c153723a..612db3f0 100644 --- a/python/mqt/debugger/dap/messages/launch_dap_message.py +++ b/python/mqt/debugger/dap/messages/launch_dap_message.py @@ -66,14 +66,13 @@ def handle(self, server: DAPServer) -> dict[str, Any]: program_path = Path(self.program) server.source_file = {"name": program_path.name, "path": self.program} parsed_successfully = True - with program_path.open("r", encoding=locale.getpreferredencoding(False)) as f: - code = f.read() - server.source_code = code - try: - server.simulation_state.load_code(code) - except RuntimeError as exc: - parsed_successfully = False - server.queue_parse_error(str(exc)) + code = program_path.read_text(encoding=locale.getpreferredencoding(False)) + server.source_code = code + try: + server.simulation_state.load_code(code) + except RuntimeError as exc: + parsed_successfully = False + server.queue_parse_error(str(exc)) if parsed_successfully and not self.stop_on_entry: server.simulation_state.run_simulation() if not parsed_successfully: diff --git a/python/mqt/debugger/dap/messages/restart_dap_message.py b/python/mqt/debugger/dap/messages/restart_dap_message.py index 7f84658a..5ed100fe 100644 --- a/python/mqt/debugger/dap/messages/restart_dap_message.py +++ b/python/mqt/debugger/dap/messages/restart_dap_message.py @@ -67,14 +67,13 @@ def handle(self, server: DAPServer) -> dict[str, Any]: program_path = Path(self.program) server.source_file = {"name": program_path.name, "path": self.program} parsed_successfully = True - with program_path.open("r", encoding=locale.getpreferredencoding(False)) as f: - code = f.read() - server.source_code = code - try: - server.simulation_state.load_code(code) - except RuntimeError as exc: - parsed_successfully = False - server.queue_parse_error(str(exc)) + code = program_path.read_text(encoding=locale.getpreferredencoding(False)) + server.source_code = code + try: + server.simulation_state.load_code(code) + except RuntimeError as exc: + parsed_successfully = False + server.queue_parse_error(str(exc)) if parsed_successfully and not self.stop_on_entry: server.simulation_state.run_simulation() if not parsed_successfully: From 29607afe18d1dae78bec4adc033c7dc2be17048e Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Sun, 11 Jan 2026 21:13:59 +0100 Subject: [PATCH 088/145] Revert "qubit size adjustment" This reverts commit 6fd5af10bd19d76a7c7cc84b4bc93ea1a7a9bfe1. --- .../mqt/debugger/dap/messages/variables_dap_message.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/python/mqt/debugger/dap/messages/variables_dap_message.py b/python/mqt/debugger/dap/messages/variables_dap_message.py index e5a41f95..e77b65cd 100644 --- a/python/mqt/debugger/dap/messages/variables_dap_message.py +++ b/python/mqt/debugger/dap/messages/variables_dap_message.py @@ -147,11 +147,10 @@ def _get_quantum_state_variables(server: DAPServer, start: int, count: int, filt ] result = [] num_q = server.simulation_state.get_num_qubits() - total_states = 2**num_q - start_index = max(0, min(start, total_states)) - requested_count = total_states - start_index if count == 0 else count - end_index = min(start_index + requested_count, total_states) - for i in range(start_index, end_index): + start = 0 + count = 10 + num_variables = 2**num_q if count == 0 else count + for i in range(start, start + num_variables): bitstring = format(i, f"0{num_q}b") result.append({ "name": f"|{bitstring}>", From 5a7413ff64511259c5f09e20b2b3c9fa1c4d697f Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Sun, 11 Jan 2026 21:28:14 +0100 Subject: [PATCH 089/145] _end_before_start --- .../debugger/dap/messages/highlight_error_dap_message.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/python/mqt/debugger/dap/messages/highlight_error_dap_message.py b/python/mqt/debugger/dap/messages/highlight_error_dap_message.py index 64d8ae59..b73fb035 100644 --- a/python/mqt/debugger/dap/messages/highlight_error_dap_message.py +++ b/python/mqt/debugger/dap/messages/highlight_error_dap_message.py @@ -93,7 +93,7 @@ def _normalize_highlight(entry: Mapping[str, Any]) -> dict[str, Any]: start = HighlightError._normalize_position(highlight_range.get("start")) end = HighlightError._normalize_position(highlight_range.get("end")) - if HighlightError._end_before_start(start, end): + if HighlightError._start_comes_after_end(start, end): msg = "Highlight range 'end' must be after 'start'." raise ValueError(msg) @@ -162,15 +162,15 @@ def _normalize_source(source: Mapping[str, Any] | None) -> dict[str, Any]: return normalized @staticmethod - def _end_before_start(start: Mapping[str, Any], end: Mapping[str, Any]) -> bool: - """Return True if 'end' describes a position before 'start'. + def _start_comes_after_end(start: Mapping[str, Any], end: Mapping[str, Any]) -> bool: + """Return True if 'start' describes a position after 'end'. Args: start (Mapping[str, Any]): The start position mapping. end (Mapping[str, Any]): The end position mapping. Returns: - bool: True when the end position is before the start position. + bool: True when the start position is after the end position. """ start_line = int(start.get("line", 0)) start_column = int(start.get("column", 0)) From 9d98879b11831ec3c9ecc296497f623f012430a9 Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Sun, 11 Jan 2026 21:37:03 +0100 Subject: [PATCH 090/145] enum implemented --- python/mqt/debugger/dap/dap_server.py | 21 ++++++++++++------- python/mqt/debugger/dap/messages/__init__.py | 3 ++- .../messages/highlight_error_dap_message.py | 18 +++++++++++++++- 3 files changed, 33 insertions(+), 9 deletions(-) diff --git a/python/mqt/debugger/dap/dap_server.py b/python/mqt/debugger/dap/dap_server.py index 2aa6c43b..76071834 100644 --- a/python/mqt/debugger/dap/dap_server.py +++ b/python/mqt/debugger/dap/dap_server.py @@ -453,7 +453,7 @@ def collect_highlight_entries( if not highlights: entry = self._build_highlight_entry( failing_instruction, - "assertionFailed", + mqt.debugger.dap.messages.HighlightReason.ASSERTION_FAILED, "Assertion failed at this instruction.", ) if entry is not None: @@ -461,7 +461,12 @@ def collect_highlight_entries( return highlights - def _build_highlight_entry(self, instruction: int, reason: str, message: str) -> dict[str, Any] | None: + def _build_highlight_entry( + self, + instruction: int, + reason: mqt.debugger.dap.messages.HighlightReason, + message: str, + ) -> dict[str, Any] | None: """Create a highlight entry for a specific instruction.""" try: start_pos, end_pos = self.simulation_state.get_instruction_position(instruction) @@ -486,13 +491,15 @@ def _build_highlight_entry(self, instruction: int, reason: str, message: str) -> } @staticmethod - def _format_highlight_reason(cause_type: mqt.debugger.ErrorCauseType | None) -> str: + def _format_highlight_reason( + cause_type: mqt.debugger.ErrorCauseType | None, + ) -> mqt.debugger.dap.messages.HighlightReason: """Return a short identifier for the highlight reason.""" if cause_type == mqt.debugger.ErrorCauseType.MissingInteraction: - return "missingInteraction" + return mqt.debugger.dap.messages.HighlightReason.MISSING_INTERACTION if cause_type == mqt.debugger.ErrorCauseType.ControlAlwaysZero: - return "controlAlwaysZero" - return "unknown" + return mqt.debugger.dap.messages.HighlightReason.CONTROL_ALWAYS_ZERO + return mqt.debugger.dap.messages.HighlightReason.UNKNOWN def queue_parse_error(self, error_message: str) -> None: """Store highlight data for a parse error to be emitted later.""" @@ -546,7 +553,7 @@ def _build_parse_error_highlight(self, line: int, column: int, detail: str) -> d "start": {"line": line, "column": column}, "end": {"line": line, "column": end_column if end_column > 0 else column}, }, - "reason": "parseError", + "reason": mqt.debugger.dap.messages.HighlightReason.PARSE_ERROR, "code": snippet, "message": detail, } diff --git a/python/mqt/debugger/dap/messages/__init__.py b/python/mqt/debugger/dap/messages/__init__.py index 3b0709ad..1b5193f0 100644 --- a/python/mqt/debugger/dap/messages/__init__.py +++ b/python/mqt/debugger/dap/messages/__init__.py @@ -21,7 +21,7 @@ from .exception_info_message import ExceptionInfoDAPMessage from .exited_dap_event import ExitedDAPEvent from .gray_out_event import GrayOutDAPEvent -from .highlight_error_dap_message import HighlightError +from .highlight_error_dap_message import HighlightError, HighlightReason from .initialize_dap_message import InitializeDAPMessage from .initialized_dap_event import InitializedDAPEvent from .launch_dap_message import LaunchDAPMessage @@ -59,6 +59,7 @@ "ExitedDAPEvent", "GrayOutDAPEvent", "HighlightError", + "HighlightReason", "InitializeDAPMessage", "InitializedDAPEvent", "LaunchDAPMessage", diff --git a/python/mqt/debugger/dap/messages/highlight_error_dap_message.py b/python/mqt/debugger/dap/messages/highlight_error_dap_message.py index b73fb035..8122d78e 100644 --- a/python/mqt/debugger/dap/messages/highlight_error_dap_message.py +++ b/python/mqt/debugger/dap/messages/highlight_error_dap_message.py @@ -10,11 +10,23 @@ from __future__ import annotations +import enum from collections.abc import Mapping from typing import TYPE_CHECKING, Any from .dap_event import DAPEvent + +class HighlightReason(enum.Enum): + """Represents the reason for highlighting a range.""" + + MISSING_INTERACTION = "missingInteraction" + CONTROL_ALWAYS_ZERO = "controlAlwaysZero" + ASSERTION_FAILED = "assertionFailed" + PARSE_ERROR = "parseError" + UNKNOWN = "unknown" + + if TYPE_CHECKING: from collections.abc import Sequence @@ -99,7 +111,11 @@ def _normalize_highlight(entry: Mapping[str, Any]) -> dict[str, Any]: normalized = dict(entry) normalized["instruction"] = int(normalized.get("instruction", -1)) - normalized["reason"] = str(normalized.get("reason", "unknown")) + reason = normalized.get("reason", HighlightReason.UNKNOWN) + if isinstance(reason, HighlightReason): + normalized["reason"] = reason.value + else: + normalized["reason"] = str(reason) normalized["code"] = str(normalized.get("code", "")) normalized["message"] = str(normalized.get("message", "")).strip() normalized["range"] = { From a8c25e27ee58ff3338f2c907ba322e9e3cbe6041 Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Sun, 11 Jan 2026 21:45:46 +0100 Subject: [PATCH 091/145] Reapply "qubit size adjustment" This reverts commit 29607afe18d1dae78bec4adc033c7dc2be17048e. --- .../mqt/debugger/dap/messages/variables_dap_message.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/python/mqt/debugger/dap/messages/variables_dap_message.py b/python/mqt/debugger/dap/messages/variables_dap_message.py index e77b65cd..e5a41f95 100644 --- a/python/mqt/debugger/dap/messages/variables_dap_message.py +++ b/python/mqt/debugger/dap/messages/variables_dap_message.py @@ -147,10 +147,11 @@ def _get_quantum_state_variables(server: DAPServer, start: int, count: int, filt ] result = [] num_q = server.simulation_state.get_num_qubits() - start = 0 - count = 10 - num_variables = 2**num_q if count == 0 else count - for i in range(start, start + num_variables): + total_states = 2**num_q + start_index = max(0, min(start, total_states)) + requested_count = total_states - start_index if count == 0 else count + end_index = min(start_index + requested_count, total_states) + for i in range(start_index, end_index): bitstring = format(i, f"0{num_q}b") result.append({ "name": f"|{bitstring}>", From 15bb5d9f4a7a1aab86cf54c45910e8b7ca27fef1 Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Sun, 11 Jan 2026 22:37:18 +0100 Subject: [PATCH 092/145] LoadResult --- bindings/InterfaceBindings.cpp | 49 +++++++++++++++++++ include/backend/dd/DDSimDebug.hpp | 20 ++++++++ include/backend/debug.h | 34 +++++++++++++ include/common/parsing/ParsingError.hpp | 25 ++++++++++ python/mqt/debugger/__init__.py | 4 ++ python/mqt/debugger/dap/dap_server.py | 21 ++------ .../dap/messages/launch_dap_message.py | 9 ++-- .../dap/messages/restart_dap_message.py | 9 ++-- python/mqt/debugger/pydebugger.pyi | 32 ++++++++++++ src/backend/dd/DDSimDebug.cpp | 43 ++++++++++++++-- src/common/parsing/CodePreprocessing.cpp | 37 ++++++-------- src/common/parsing/ParsingError.cpp | 16 ++++++ 12 files changed, 250 insertions(+), 49 deletions(-) diff --git a/bindings/InterfaceBindings.cpp b/bindings/InterfaceBindings.cpp index 1608625e..ec768aa6 100644 --- a/bindings/InterfaceBindings.cpp +++ b/bindings/InterfaceBindings.cpp @@ -74,6 +74,14 @@ void bindFramework(py::module& m) { .export_values() .finalize(); + // Bind the Result enum + py::native_enum(m, "Result", "enum.Enum", + "The result of an operation.") + .value("OK", OK, "Indicates that the operation was successful.") + .value("ERROR", ERROR, "Indicates that an error occurred.") + .export_values() + .finalize(); + // Bind the VariableValue union py::class_(m, "VariableValue") .def(py::init<>()) @@ -166,6 +174,35 @@ Contains one element for each of the `num_states` states in the state vector.)") .doc() = "The settings that should be used to compile an assertion program."; + py::class_(m, "LoadResult") + .def_readonly("result", &LoadResult::result, + "The result of the load operation.") + .def_readonly( + "line", &LoadResult::line, + "The 1-based line of the error location, or 0 if unavailable.") + .def_readonly( + "column", &LoadResult::column, + "The 1-based column of the error location, or 0 if unavailable.") + .def_property_readonly( + "detail", + [](const LoadResult& result) -> py::object { + if (result.detail == nullptr) { + return py::none(); + } + return py::str(result.detail); + }, + "The error detail text, or None if unavailable.") + .def_property_readonly( + "message", + [](const LoadResult& result) -> py::object { + if (result.message == nullptr) { + return py::none(); + } + return py::str(result.message); + }, + "The full error message, or None if unavailable.") + .doc() = "Represents the structured result of a load operation."; + py::class_(m, "SimulationState") .def(py::init<>(), "Creates a new `SimulationState` instance.") .def( @@ -190,6 +227,18 @@ Contains one element for each of the `num_states` states in the state vector.)") Args: code (str): The code to load.)") + .def( + "load_code_with_result", + [](SimulationState* self, const char* code) { + return self->loadCodeWithResult(self, code); + }, + R"(Loads the given code into the simulation state and returns details. + +Args: + code (str): The code to load. + +Returns: + LoadResult: The structured load result.)") .def( "step_forward", [](SimulationState* self) { checkOrThrow(self->stepForward(self)); }, diff --git a/include/backend/dd/DDSimDebug.hpp b/include/backend/dd/DDSimDebug.hpp index 13a3e9b7..46c7c88d 100644 --- a/include/backend/dd/DDSimDebug.hpp +++ b/include/backend/dd/DDSimDebug.hpp @@ -123,6 +123,18 @@ struct DDSimulationState { * @brief The last error message produced by the interface. */ std::string lastErrorMessage; + /** + * @brief The last error detail produced by the interface. + */ + std::string lastErrorDetail; + /** + * @brief The 1-based line of the last error, or 0 if unavailable. + */ + size_t lastErrorLine = 0; + /** + * @brief The 1-based column of the last error, or 0 if unavailable. + */ + size_t lastErrorColumn = 0; /** * @brief Indicates whether the debugger is ready to start simulation. */ @@ -284,6 +296,14 @@ Result ddsimInit(SimulationState* self); * @return The result of the operation. */ Result ddsimLoadCode(SimulationState* self, const char* code); + +/** + * @brief Loads the given code into the simulation state and returns details. + * @param self The instance to load the code into. + * @param code The code to load. + * @return The structured load result. + */ +LoadResult ddsimLoadCodeWithResult(SimulationState* self, const char* code); /** * @brief Steps the simulation forward by one instruction. * @param self The instance to step forward. diff --git a/include/backend/debug.h b/include/backend/debug.h index 0eacae23..9650893f 100644 --- a/include/backend/debug.h +++ b/include/backend/debug.h @@ -38,6 +38,32 @@ extern "C" { */ typedef struct SimulationStateStruct SimulationState; +/** + * @brief Represents the structured result of a load operation. + */ +typedef struct { + /** + * @brief The result of the load operation. + */ + Result result; + /** + * @brief The 1-based line of the error location, or 0 if unavailable. + */ + size_t line; + /** + * @brief The 1-based column of the error location, or 0 if unavailable. + */ + size_t column; + /** + * @brief The error detail text, or nullptr if unavailable. + */ + const char* detail; + /** + * @brief The full error message, or nullptr if unavailable. + */ + const char* message; +} LoadResult; + struct SimulationStateStruct { /** * @brief Initializes the simulation state. @@ -54,6 +80,14 @@ struct SimulationStateStruct { */ Result (*loadCode)(SimulationState* self, const char* code); + /** + * @brief Loads the given code into the simulation state and returns details. + * @param self The instance to load the code into. + * @param code The code to load. + * @return The structured load result. + */ + LoadResult (*loadCodeWithResult)(SimulationState* self, const char* code); + /** * @brief Gets the last error message from the interface. * diff --git a/include/common/parsing/ParsingError.hpp b/include/common/parsing/ParsingError.hpp index 68aa3459..520975f3 100644 --- a/include/common/parsing/ParsingError.hpp +++ b/include/common/parsing/ParsingError.hpp @@ -15,6 +15,8 @@ #pragma once +#include +#include #include #include @@ -23,6 +25,12 @@ namespace mqt::debugger { /** * @brief Represents an error that occurred during parsing. */ +struct ParsingErrorLocation { + size_t line; + size_t column; + std::string detail; +}; + class ParsingError : public std::runtime_error { public: /** @@ -30,6 +38,23 @@ class ParsingError : public std::runtime_error { * @param msg The error message. */ explicit ParsingError(const std::string& msg); + + /** + * @brief Constructs a new ParsingError with a structured location. + * @param line The 1-based line number. + * @param column The 1-based column number. + * @param detail The error detail text. + */ + ParsingError(size_t line, size_t column, std::string detail); + + /** + * @brief Returns the location information if available. + * @return Pointer to the location info, or nullptr if absent. + */ + const ParsingErrorLocation* location() const noexcept; + +private: + std::optional location_; }; } // namespace mqt::debugger diff --git a/python/mqt/debugger/__init__.py b/python/mqt/debugger/__init__.py index 24ec7766..27a056e1 100644 --- a/python/mqt/debugger/__init__.py +++ b/python/mqt/debugger/__init__.py @@ -18,6 +18,8 @@ Diagnostics, ErrorCause, ErrorCauseType, + LoadResult, + Result, SimulationState, Statevector, Variable, @@ -33,6 +35,8 @@ "Diagnostics", "ErrorCause", "ErrorCauseType", + "LoadResult", + "Result", "SimulationState", "Statevector", "Variable", diff --git a/python/mqt/debugger/dap/dap_server.py b/python/mqt/debugger/dap/dap_server.py index 76071834..3fc4e7f5 100644 --- a/python/mqt/debugger/dap/dap_server.py +++ b/python/mqt/debugger/dap/dap_server.py @@ -11,7 +11,6 @@ from __future__ import annotations import json -import re import socket import sys from typing import TYPE_CHECKING, Any @@ -501,27 +500,15 @@ def _format_highlight_reason( return mqt.debugger.dap.messages.HighlightReason.CONTROL_ALWAYS_ZERO return mqt.debugger.dap.messages.HighlightReason.UNKNOWN - def queue_parse_error(self, error_message: str) -> None: + def queue_parse_error_result(self, load_result: mqt.debugger.LoadResult) -> None: """Store highlight data for a parse error to be emitted later.""" - line, column, detail = self._parse_error_location(error_message) + line = load_result.line or 1 + column = load_result.column or 1 + detail = load_result.detail or load_result.message or "Failed to parse program." entry = self._build_parse_error_highlight(line, column, detail) if entry is not None: self.pending_highlights = [entry] - @staticmethod - def _parse_error_location(error_message: str) -> tuple[int, int, str]: - """Parse a compiler error string and extract the source location.""" - match = re.match(r":(\d+):(\d+):\s*(.*)", error_message.strip()) - if match: - line = int(match.group(1)) - column = int(match.group(2)) - detail = match.group(3).strip() - else: - line = 1 - column = 1 - detail = error_message.strip() - return (line, column, detail) - def _build_parse_error_highlight(self, line: int, column: int, detail: str) -> dict[str, Any] | None: """Create a highlight entry for a parse error.""" if not getattr(self, "source_code", ""): diff --git a/python/mqt/debugger/dap/messages/launch_dap_message.py b/python/mqt/debugger/dap/messages/launch_dap_message.py index 612db3f0..f832c4f4 100644 --- a/python/mqt/debugger/dap/messages/launch_dap_message.py +++ b/python/mqt/debugger/dap/messages/launch_dap_message.py @@ -15,6 +15,8 @@ from pathlib import Path from typing import TYPE_CHECKING, Any +import mqt.debugger + from .dap_message import DAPMessage if TYPE_CHECKING: @@ -68,11 +70,10 @@ def handle(self, server: DAPServer) -> dict[str, Any]: parsed_successfully = True code = program_path.read_text(encoding=locale.getpreferredencoding(False)) server.source_code = code - try: - server.simulation_state.load_code(code) - except RuntimeError as exc: + load_result = server.simulation_state.load_code_with_result(code) + if load_result.result != mqt.debugger.Result.OK: parsed_successfully = False - server.queue_parse_error(str(exc)) + server.queue_parse_error_result(load_result) if parsed_successfully and not self.stop_on_entry: server.simulation_state.run_simulation() if not parsed_successfully: diff --git a/python/mqt/debugger/dap/messages/restart_dap_message.py b/python/mqt/debugger/dap/messages/restart_dap_message.py index 5ed100fe..f3bb98ad 100644 --- a/python/mqt/debugger/dap/messages/restart_dap_message.py +++ b/python/mqt/debugger/dap/messages/restart_dap_message.py @@ -15,6 +15,8 @@ from pathlib import Path from typing import TYPE_CHECKING, Any +import mqt.debugger + from .dap_message import DAPMessage if TYPE_CHECKING: @@ -69,11 +71,10 @@ def handle(self, server: DAPServer) -> dict[str, Any]: parsed_successfully = True code = program_path.read_text(encoding=locale.getpreferredencoding(False)) server.source_code = code - try: - server.simulation_state.load_code(code) - except RuntimeError as exc: + load_result = server.simulation_state.load_code_with_result(code) + if load_result.result != mqt.debugger.Result.OK: parsed_successfully = False - server.queue_parse_error(str(exc)) + server.queue_parse_error_result(load_result) if parsed_successfully and not self.stop_on_entry: server.simulation_state.run_simulation() if not parsed_successfully: diff --git a/python/mqt/debugger/pydebugger.pyi b/python/mqt/debugger/pydebugger.pyi index c4476b00..91f8ee24 100644 --- a/python/mqt/debugger/pydebugger.pyi +++ b/python/mqt/debugger/pydebugger.pyi @@ -22,6 +22,14 @@ class VariableType(enum.Enum): VarFloat = 2 """A floating-point variable.""" +class Result(enum.Enum): + """Represents the result of an operation.""" + + OK = 0 + """Indicates that the operation was successful.""" + ERROR = 1 + """Indicates that an error occurred.""" + class ErrorCauseType(enum.Enum): """Represents the type of a potential error cause.""" @@ -95,6 +103,20 @@ class CompilationSettings: slice_index (int, optional): The index of the slice that should be compiled (defaults to 0). """ +class LoadResult: + """Represents the structured result of a load operation.""" + + result: Result + """The result of the load operation.""" + line: int + """The 1-based line of the error location, or 0 if unavailable.""" + column: int + """The 1-based column of the error location, or 0 if unavailable.""" + detail: str | None + """The error detail text, or None if unavailable.""" + message: str | None + """The full error message, or None if unavailable.""" + class Statevector: """Represents a state vector.""" @@ -134,6 +156,16 @@ class SimulationState: code (str): The code to load. """ + def load_code_with_result(self, code: str) -> LoadResult: + """Loads the given code into the simulation state and returns details. + + Args: + code (str): The code to load. + + Returns: + LoadResult: The structured load result. + """ + def step_forward(self) -> None: """Steps the simulation forward by one instruction.""" diff --git a/src/backend/dd/DDSimDebug.cpp b/src/backend/dd/DDSimDebug.cpp index a8d0e3da..2cbbbc26 100644 --- a/src/backend/dd/DDSimDebug.cpp +++ b/src/backend/dd/DDSimDebug.cpp @@ -25,6 +25,7 @@ #include "common/parsing/AssertionParsing.hpp" #include "common/parsing/AssertionTools.hpp" #include "common/parsing/CodePreprocessing.hpp" +#include "common/parsing/ParsingError.hpp" #include "common/parsing/Utils.hpp" #include "dd/DDDefinitions.hpp" #include "dd/Operations.hpp" @@ -83,6 +84,13 @@ const char* ddsimGetLastErrorMessage(SimulationState* self) { return ddsim->lastErrorMessage.c_str(); } +void clearLastError(DDSimulationState* ddsim) { + clearLastError(ddsim); + ddsim->lastErrorDetail.clear(); + ddsim->lastErrorLine = 0; + ddsim->lastErrorColumn = 0; +} + /** * @brief Generate a random number between 0 and 1. * @@ -521,6 +529,7 @@ Result createDDSimulationState(DDSimulationState* self) { self->interface.init = ddsimInit; self->interface.loadCode = ddsimLoadCode; + self->interface.loadCodeWithResult = ddsimLoadCodeWithResult; self->interface.getLastErrorMessage = ddsimGetLastErrorMessage; self->interface.stepForward = ddsimStepForward; self->interface.stepBackward = ddsimStepBackward; @@ -593,8 +602,7 @@ Result ddsimInit(SimulationState* self) { return OK; } -Result ddsimLoadCode(SimulationState* self, const char* code) { - auto* ddsim = toDDSimulationState(self); +Result ddsimLoadCodeInternal(DDSimulationState* ddsim, const char* code) { ddsim->currentInstruction = 0; ddsim->previousInstructionStack.clear(); ddsim->callReturnStack.clear(); @@ -616,13 +624,25 @@ Result ddsimLoadCode(SimulationState* self, const char* code) { ddsim->functionCallers.clear(); ddsim->targetQubits.clear(); ddsim->instructionObjects.clear(); - ddsim->lastErrorMessage.clear(); + clearLastError(ddsim); try { std::stringstream ss{preprocessAssertionCode(code, ddsim)}; const auto imported = qasm3::Importer::import(ss); ddsim->qc = std::make_unique(imported); qc::CircuitOptimizer::flattenOperations(*ddsim->qc, true); + } catch (const ParsingError& e) { + ddsim->lastErrorMessage = e.what(); + if (const auto* location = e.location()) { + ddsim->lastErrorLine = location->line; + ddsim->lastErrorColumn = location->column; + ddsim->lastErrorDetail = location->detail; + } + if (ddsim->lastErrorMessage.empty()) { + ddsim->lastErrorMessage = + "An error occurred while executing the operation"; + } + return ERROR; } catch (const std::exception& e) { ddsim->lastErrorMessage = e.what(); if (ddsim->lastErrorMessage.empty()) { @@ -647,6 +667,23 @@ Result ddsimLoadCode(SimulationState* self, const char* code) { return OK; } +Result ddsimLoadCode(SimulationState* self, const char* code) { + auto* ddsim = toDDSimulationState(self); + return ddsimLoadCodeInternal(ddsim, code); +} + +LoadResult ddsimLoadCodeWithResult(SimulationState* self, const char* code) { + auto* ddsim = toDDSimulationState(self); + const Result result = ddsimLoadCodeInternal(ddsim, code); + const char* message = ddsim->lastErrorMessage.empty() + ? nullptr + : ddsim->lastErrorMessage.c_str(); + const char* detail = + ddsim->lastErrorDetail.empty() ? nullptr : ddsim->lastErrorDetail.c_str(); + return LoadResult{result, ddsim->lastErrorLine, ddsim->lastErrorColumn, + detail, message}; +} + Result ddsimChangeClassicalVariableValue(SimulationState* self, const char* variableName, const VariableValue* value) { diff --git a/src/common/parsing/CodePreprocessing.cpp b/src/common/parsing/CodePreprocessing.cpp index af8d0ae0..1223fef9 100644 --- a/src/common/parsing/CodePreprocessing.cpp +++ b/src/common/parsing/CodePreprocessing.cpp @@ -109,19 +109,18 @@ LineColumn lineColumnForTarget(const std::string& code, size_t instructionStart, } /** - * @brief Format a parse error with line/column location information. + * @brief Build a ParsingError with line/column location information. * @param code The source code to inspect. * @param instructionStart The zero-based offset of the instruction start. * @param detail The error detail text. * @param target Optional target token to locate more precisely. - * @return The formatted error string. + * @return A ParsingError containing the location details. */ -std::string formatParseError(const std::string& code, size_t instructionStart, +ParsingError buildParseError(const std::string& code, size_t instructionStart, const std::string& detail, const std::string& target = "") { const auto location = lineColumnForTarget(code, instructionStart, target); - return ":" + std::to_string(location.line) + ":" + - std::to_string(location.column) + ": " + detail; + return ParsingError(location.line, location.column, detail); } /** @@ -175,24 +174,21 @@ void validateTargets(const std::string& code, size_t instructionStart, } const auto close = target.find(']', open + 1); if (open == 0 || close == std::string::npos || close != target.size() - 1) { - throw ParsingError(formatParseError(code, instructionStart, - invalidTargetDetail(target, context), - target)); + throw buildParseError(code, instructionStart, + invalidTargetDetail(target, context), target); } const auto registerName = target.substr(0, open); const auto indexText = target.substr(open + 1, close - open - 1); if (!isDigits(indexText)) { - throw ParsingError(formatParseError(code, instructionStart, - invalidTargetDetail(target, context), - target)); + throw buildParseError(code, instructionStart, + invalidTargetDetail(target, context), target); } size_t registerIndex = 0; try { registerIndex = std::stoul(indexText); } catch (const std::exception&) { - throw ParsingError(formatParseError(code, instructionStart, - invalidTargetDetail(target, context), - target)); + throw buildParseError(code, instructionStart, + invalidTargetDetail(target, context), target); } if (std::ranges::find(shadowedRegisters, registerName) != shadowedRegisters.end()) { @@ -200,9 +196,8 @@ void validateTargets(const std::string& code, size_t instructionStart, } const auto found = definedRegisters.find(registerName); if (found == definedRegisters.end() || found->second <= registerIndex) { - throw ParsingError(formatParseError(code, instructionStart, - invalidTargetDetail(target, context), - target)); + throw buildParseError(code, instructionStart, + invalidTargetDetail(target, context), target); } } } @@ -538,15 +533,15 @@ preprocessCode(const std::string& code, size_t startIndex, const auto& name = parts[0]; const auto sizeText = parts.size() > 1 ? parts[1] : ""; if (name.empty() || !isDigits(sizeText)) { - throw ParsingError(formatParseError( - code, trueStart, invalidRegisterDetail(trimmedLine))); + throw buildParseError(code, trueStart, + invalidRegisterDetail(trimmedLine)); } size_t size = 0; try { size = std::stoul(sizeText); } catch (const std::exception&) { - throw ParsingError(formatParseError( - code, trueStart, invalidRegisterDetail(trimmedLine))); + throw buildParseError(code, trueStart, + invalidRegisterDetail(trimmedLine)); } definedRegisters.insert({name, size}); } diff --git a/src/common/parsing/ParsingError.cpp b/src/common/parsing/ParsingError.cpp index 0b60a322..f6a5ca3b 100644 --- a/src/common/parsing/ParsingError.cpp +++ b/src/common/parsing/ParsingError.cpp @@ -15,6 +15,7 @@ #include "common/parsing/ParsingError.hpp" +#include #include #include @@ -22,4 +23,19 @@ namespace mqt::debugger { ParsingError::ParsingError(const std::string& msg) : std::runtime_error(msg) {} +ParsingError::ParsingError(size_t line, size_t column, std::string detail) + : std::runtime_error([line, column, &detail]() { + std::ostringstream message; + message << ":" << line << ":" << column << ": " << detail; + return message.str(); + }()), + location_(ParsingErrorLocation{line, column, std::move(detail)}) {} + +const ParsingErrorLocation* ParsingError::location() const noexcept { + if (!location_.has_value()) { + return nullptr; + } + return &location_.value(); +} + } // namespace mqt::debugger From 175bb084d86de30be0b1a5f1db776be1d84f2d67 Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Sun, 11 Jan 2026 22:43:43 +0100 Subject: [PATCH 093/145] Revert "LoadResult" This reverts commit 15bb5d9f4a7a1aab86cf54c45910e8b7ca27fef1. --- bindings/InterfaceBindings.cpp | 49 ------------------- include/backend/dd/DDSimDebug.hpp | 20 -------- include/backend/debug.h | 34 ------------- include/common/parsing/ParsingError.hpp | 25 ---------- python/mqt/debugger/__init__.py | 4 -- python/mqt/debugger/dap/dap_server.py | 21 ++++++-- .../dap/messages/launch_dap_message.py | 9 ++-- .../dap/messages/restart_dap_message.py | 9 ++-- python/mqt/debugger/pydebugger.pyi | 32 ------------ src/backend/dd/DDSimDebug.cpp | 43 ++-------------- src/common/parsing/CodePreprocessing.cpp | 37 ++++++++------ src/common/parsing/ParsingError.cpp | 16 ------ 12 files changed, 49 insertions(+), 250 deletions(-) diff --git a/bindings/InterfaceBindings.cpp b/bindings/InterfaceBindings.cpp index ec768aa6..1608625e 100644 --- a/bindings/InterfaceBindings.cpp +++ b/bindings/InterfaceBindings.cpp @@ -74,14 +74,6 @@ void bindFramework(py::module& m) { .export_values() .finalize(); - // Bind the Result enum - py::native_enum(m, "Result", "enum.Enum", - "The result of an operation.") - .value("OK", OK, "Indicates that the operation was successful.") - .value("ERROR", ERROR, "Indicates that an error occurred.") - .export_values() - .finalize(); - // Bind the VariableValue union py::class_(m, "VariableValue") .def(py::init<>()) @@ -174,35 +166,6 @@ Contains one element for each of the `num_states` states in the state vector.)") .doc() = "The settings that should be used to compile an assertion program."; - py::class_(m, "LoadResult") - .def_readonly("result", &LoadResult::result, - "The result of the load operation.") - .def_readonly( - "line", &LoadResult::line, - "The 1-based line of the error location, or 0 if unavailable.") - .def_readonly( - "column", &LoadResult::column, - "The 1-based column of the error location, or 0 if unavailable.") - .def_property_readonly( - "detail", - [](const LoadResult& result) -> py::object { - if (result.detail == nullptr) { - return py::none(); - } - return py::str(result.detail); - }, - "The error detail text, or None if unavailable.") - .def_property_readonly( - "message", - [](const LoadResult& result) -> py::object { - if (result.message == nullptr) { - return py::none(); - } - return py::str(result.message); - }, - "The full error message, or None if unavailable.") - .doc() = "Represents the structured result of a load operation."; - py::class_(m, "SimulationState") .def(py::init<>(), "Creates a new `SimulationState` instance.") .def( @@ -227,18 +190,6 @@ Contains one element for each of the `num_states` states in the state vector.)") Args: code (str): The code to load.)") - .def( - "load_code_with_result", - [](SimulationState* self, const char* code) { - return self->loadCodeWithResult(self, code); - }, - R"(Loads the given code into the simulation state and returns details. - -Args: - code (str): The code to load. - -Returns: - LoadResult: The structured load result.)") .def( "step_forward", [](SimulationState* self) { checkOrThrow(self->stepForward(self)); }, diff --git a/include/backend/dd/DDSimDebug.hpp b/include/backend/dd/DDSimDebug.hpp index 46c7c88d..13a3e9b7 100644 --- a/include/backend/dd/DDSimDebug.hpp +++ b/include/backend/dd/DDSimDebug.hpp @@ -123,18 +123,6 @@ struct DDSimulationState { * @brief The last error message produced by the interface. */ std::string lastErrorMessage; - /** - * @brief The last error detail produced by the interface. - */ - std::string lastErrorDetail; - /** - * @brief The 1-based line of the last error, or 0 if unavailable. - */ - size_t lastErrorLine = 0; - /** - * @brief The 1-based column of the last error, or 0 if unavailable. - */ - size_t lastErrorColumn = 0; /** * @brief Indicates whether the debugger is ready to start simulation. */ @@ -296,14 +284,6 @@ Result ddsimInit(SimulationState* self); * @return The result of the operation. */ Result ddsimLoadCode(SimulationState* self, const char* code); - -/** - * @brief Loads the given code into the simulation state and returns details. - * @param self The instance to load the code into. - * @param code The code to load. - * @return The structured load result. - */ -LoadResult ddsimLoadCodeWithResult(SimulationState* self, const char* code); /** * @brief Steps the simulation forward by one instruction. * @param self The instance to step forward. diff --git a/include/backend/debug.h b/include/backend/debug.h index 9650893f..0eacae23 100644 --- a/include/backend/debug.h +++ b/include/backend/debug.h @@ -38,32 +38,6 @@ extern "C" { */ typedef struct SimulationStateStruct SimulationState; -/** - * @brief Represents the structured result of a load operation. - */ -typedef struct { - /** - * @brief The result of the load operation. - */ - Result result; - /** - * @brief The 1-based line of the error location, or 0 if unavailable. - */ - size_t line; - /** - * @brief The 1-based column of the error location, or 0 if unavailable. - */ - size_t column; - /** - * @brief The error detail text, or nullptr if unavailable. - */ - const char* detail; - /** - * @brief The full error message, or nullptr if unavailable. - */ - const char* message; -} LoadResult; - struct SimulationStateStruct { /** * @brief Initializes the simulation state. @@ -80,14 +54,6 @@ struct SimulationStateStruct { */ Result (*loadCode)(SimulationState* self, const char* code); - /** - * @brief Loads the given code into the simulation state and returns details. - * @param self The instance to load the code into. - * @param code The code to load. - * @return The structured load result. - */ - LoadResult (*loadCodeWithResult)(SimulationState* self, const char* code); - /** * @brief Gets the last error message from the interface. * diff --git a/include/common/parsing/ParsingError.hpp b/include/common/parsing/ParsingError.hpp index 520975f3..68aa3459 100644 --- a/include/common/parsing/ParsingError.hpp +++ b/include/common/parsing/ParsingError.hpp @@ -15,8 +15,6 @@ #pragma once -#include -#include #include #include @@ -25,12 +23,6 @@ namespace mqt::debugger { /** * @brief Represents an error that occurred during parsing. */ -struct ParsingErrorLocation { - size_t line; - size_t column; - std::string detail; -}; - class ParsingError : public std::runtime_error { public: /** @@ -38,23 +30,6 @@ class ParsingError : public std::runtime_error { * @param msg The error message. */ explicit ParsingError(const std::string& msg); - - /** - * @brief Constructs a new ParsingError with a structured location. - * @param line The 1-based line number. - * @param column The 1-based column number. - * @param detail The error detail text. - */ - ParsingError(size_t line, size_t column, std::string detail); - - /** - * @brief Returns the location information if available. - * @return Pointer to the location info, or nullptr if absent. - */ - const ParsingErrorLocation* location() const noexcept; - -private: - std::optional location_; }; } // namespace mqt::debugger diff --git a/python/mqt/debugger/__init__.py b/python/mqt/debugger/__init__.py index 27a056e1..24ec7766 100644 --- a/python/mqt/debugger/__init__.py +++ b/python/mqt/debugger/__init__.py @@ -18,8 +18,6 @@ Diagnostics, ErrorCause, ErrorCauseType, - LoadResult, - Result, SimulationState, Statevector, Variable, @@ -35,8 +33,6 @@ "Diagnostics", "ErrorCause", "ErrorCauseType", - "LoadResult", - "Result", "SimulationState", "Statevector", "Variable", diff --git a/python/mqt/debugger/dap/dap_server.py b/python/mqt/debugger/dap/dap_server.py index 3fc4e7f5..76071834 100644 --- a/python/mqt/debugger/dap/dap_server.py +++ b/python/mqt/debugger/dap/dap_server.py @@ -11,6 +11,7 @@ from __future__ import annotations import json +import re import socket import sys from typing import TYPE_CHECKING, Any @@ -500,15 +501,27 @@ def _format_highlight_reason( return mqt.debugger.dap.messages.HighlightReason.CONTROL_ALWAYS_ZERO return mqt.debugger.dap.messages.HighlightReason.UNKNOWN - def queue_parse_error_result(self, load_result: mqt.debugger.LoadResult) -> None: + def queue_parse_error(self, error_message: str) -> None: """Store highlight data for a parse error to be emitted later.""" - line = load_result.line or 1 - column = load_result.column or 1 - detail = load_result.detail or load_result.message or "Failed to parse program." + line, column, detail = self._parse_error_location(error_message) entry = self._build_parse_error_highlight(line, column, detail) if entry is not None: self.pending_highlights = [entry] + @staticmethod + def _parse_error_location(error_message: str) -> tuple[int, int, str]: + """Parse a compiler error string and extract the source location.""" + match = re.match(r":(\d+):(\d+):\s*(.*)", error_message.strip()) + if match: + line = int(match.group(1)) + column = int(match.group(2)) + detail = match.group(3).strip() + else: + line = 1 + column = 1 + detail = error_message.strip() + return (line, column, detail) + def _build_parse_error_highlight(self, line: int, column: int, detail: str) -> dict[str, Any] | None: """Create a highlight entry for a parse error.""" if not getattr(self, "source_code", ""): diff --git a/python/mqt/debugger/dap/messages/launch_dap_message.py b/python/mqt/debugger/dap/messages/launch_dap_message.py index f832c4f4..612db3f0 100644 --- a/python/mqt/debugger/dap/messages/launch_dap_message.py +++ b/python/mqt/debugger/dap/messages/launch_dap_message.py @@ -15,8 +15,6 @@ from pathlib import Path from typing import TYPE_CHECKING, Any -import mqt.debugger - from .dap_message import DAPMessage if TYPE_CHECKING: @@ -70,10 +68,11 @@ def handle(self, server: DAPServer) -> dict[str, Any]: parsed_successfully = True code = program_path.read_text(encoding=locale.getpreferredencoding(False)) server.source_code = code - load_result = server.simulation_state.load_code_with_result(code) - if load_result.result != mqt.debugger.Result.OK: + try: + server.simulation_state.load_code(code) + except RuntimeError as exc: parsed_successfully = False - server.queue_parse_error_result(load_result) + server.queue_parse_error(str(exc)) if parsed_successfully and not self.stop_on_entry: server.simulation_state.run_simulation() if not parsed_successfully: diff --git a/python/mqt/debugger/dap/messages/restart_dap_message.py b/python/mqt/debugger/dap/messages/restart_dap_message.py index f3bb98ad..5ed100fe 100644 --- a/python/mqt/debugger/dap/messages/restart_dap_message.py +++ b/python/mqt/debugger/dap/messages/restart_dap_message.py @@ -15,8 +15,6 @@ from pathlib import Path from typing import TYPE_CHECKING, Any -import mqt.debugger - from .dap_message import DAPMessage if TYPE_CHECKING: @@ -71,10 +69,11 @@ def handle(self, server: DAPServer) -> dict[str, Any]: parsed_successfully = True code = program_path.read_text(encoding=locale.getpreferredencoding(False)) server.source_code = code - load_result = server.simulation_state.load_code_with_result(code) - if load_result.result != mqt.debugger.Result.OK: + try: + server.simulation_state.load_code(code) + except RuntimeError as exc: parsed_successfully = False - server.queue_parse_error_result(load_result) + server.queue_parse_error(str(exc)) if parsed_successfully and not self.stop_on_entry: server.simulation_state.run_simulation() if not parsed_successfully: diff --git a/python/mqt/debugger/pydebugger.pyi b/python/mqt/debugger/pydebugger.pyi index 91f8ee24..c4476b00 100644 --- a/python/mqt/debugger/pydebugger.pyi +++ b/python/mqt/debugger/pydebugger.pyi @@ -22,14 +22,6 @@ class VariableType(enum.Enum): VarFloat = 2 """A floating-point variable.""" -class Result(enum.Enum): - """Represents the result of an operation.""" - - OK = 0 - """Indicates that the operation was successful.""" - ERROR = 1 - """Indicates that an error occurred.""" - class ErrorCauseType(enum.Enum): """Represents the type of a potential error cause.""" @@ -103,20 +95,6 @@ class CompilationSettings: slice_index (int, optional): The index of the slice that should be compiled (defaults to 0). """ -class LoadResult: - """Represents the structured result of a load operation.""" - - result: Result - """The result of the load operation.""" - line: int - """The 1-based line of the error location, or 0 if unavailable.""" - column: int - """The 1-based column of the error location, or 0 if unavailable.""" - detail: str | None - """The error detail text, or None if unavailable.""" - message: str | None - """The full error message, or None if unavailable.""" - class Statevector: """Represents a state vector.""" @@ -156,16 +134,6 @@ class SimulationState: code (str): The code to load. """ - def load_code_with_result(self, code: str) -> LoadResult: - """Loads the given code into the simulation state and returns details. - - Args: - code (str): The code to load. - - Returns: - LoadResult: The structured load result. - """ - def step_forward(self) -> None: """Steps the simulation forward by one instruction.""" diff --git a/src/backend/dd/DDSimDebug.cpp b/src/backend/dd/DDSimDebug.cpp index 2cbbbc26..a8d0e3da 100644 --- a/src/backend/dd/DDSimDebug.cpp +++ b/src/backend/dd/DDSimDebug.cpp @@ -25,7 +25,6 @@ #include "common/parsing/AssertionParsing.hpp" #include "common/parsing/AssertionTools.hpp" #include "common/parsing/CodePreprocessing.hpp" -#include "common/parsing/ParsingError.hpp" #include "common/parsing/Utils.hpp" #include "dd/DDDefinitions.hpp" #include "dd/Operations.hpp" @@ -84,13 +83,6 @@ const char* ddsimGetLastErrorMessage(SimulationState* self) { return ddsim->lastErrorMessage.c_str(); } -void clearLastError(DDSimulationState* ddsim) { - clearLastError(ddsim); - ddsim->lastErrorDetail.clear(); - ddsim->lastErrorLine = 0; - ddsim->lastErrorColumn = 0; -} - /** * @brief Generate a random number between 0 and 1. * @@ -529,7 +521,6 @@ Result createDDSimulationState(DDSimulationState* self) { self->interface.init = ddsimInit; self->interface.loadCode = ddsimLoadCode; - self->interface.loadCodeWithResult = ddsimLoadCodeWithResult; self->interface.getLastErrorMessage = ddsimGetLastErrorMessage; self->interface.stepForward = ddsimStepForward; self->interface.stepBackward = ddsimStepBackward; @@ -602,7 +593,8 @@ Result ddsimInit(SimulationState* self) { return OK; } -Result ddsimLoadCodeInternal(DDSimulationState* ddsim, const char* code) { +Result ddsimLoadCode(SimulationState* self, const char* code) { + auto* ddsim = toDDSimulationState(self); ddsim->currentInstruction = 0; ddsim->previousInstructionStack.clear(); ddsim->callReturnStack.clear(); @@ -624,25 +616,13 @@ Result ddsimLoadCodeInternal(DDSimulationState* ddsim, const char* code) { ddsim->functionCallers.clear(); ddsim->targetQubits.clear(); ddsim->instructionObjects.clear(); - clearLastError(ddsim); + ddsim->lastErrorMessage.clear(); try { std::stringstream ss{preprocessAssertionCode(code, ddsim)}; const auto imported = qasm3::Importer::import(ss); ddsim->qc = std::make_unique(imported); qc::CircuitOptimizer::flattenOperations(*ddsim->qc, true); - } catch (const ParsingError& e) { - ddsim->lastErrorMessage = e.what(); - if (const auto* location = e.location()) { - ddsim->lastErrorLine = location->line; - ddsim->lastErrorColumn = location->column; - ddsim->lastErrorDetail = location->detail; - } - if (ddsim->lastErrorMessage.empty()) { - ddsim->lastErrorMessage = - "An error occurred while executing the operation"; - } - return ERROR; } catch (const std::exception& e) { ddsim->lastErrorMessage = e.what(); if (ddsim->lastErrorMessage.empty()) { @@ -667,23 +647,6 @@ Result ddsimLoadCodeInternal(DDSimulationState* ddsim, const char* code) { return OK; } -Result ddsimLoadCode(SimulationState* self, const char* code) { - auto* ddsim = toDDSimulationState(self); - return ddsimLoadCodeInternal(ddsim, code); -} - -LoadResult ddsimLoadCodeWithResult(SimulationState* self, const char* code) { - auto* ddsim = toDDSimulationState(self); - const Result result = ddsimLoadCodeInternal(ddsim, code); - const char* message = ddsim->lastErrorMessage.empty() - ? nullptr - : ddsim->lastErrorMessage.c_str(); - const char* detail = - ddsim->lastErrorDetail.empty() ? nullptr : ddsim->lastErrorDetail.c_str(); - return LoadResult{result, ddsim->lastErrorLine, ddsim->lastErrorColumn, - detail, message}; -} - Result ddsimChangeClassicalVariableValue(SimulationState* self, const char* variableName, const VariableValue* value) { diff --git a/src/common/parsing/CodePreprocessing.cpp b/src/common/parsing/CodePreprocessing.cpp index 1223fef9..af8d0ae0 100644 --- a/src/common/parsing/CodePreprocessing.cpp +++ b/src/common/parsing/CodePreprocessing.cpp @@ -109,18 +109,19 @@ LineColumn lineColumnForTarget(const std::string& code, size_t instructionStart, } /** - * @brief Build a ParsingError with line/column location information. + * @brief Format a parse error with line/column location information. * @param code The source code to inspect. * @param instructionStart The zero-based offset of the instruction start. * @param detail The error detail text. * @param target Optional target token to locate more precisely. - * @return A ParsingError containing the location details. + * @return The formatted error string. */ -ParsingError buildParseError(const std::string& code, size_t instructionStart, +std::string formatParseError(const std::string& code, size_t instructionStart, const std::string& detail, const std::string& target = "") { const auto location = lineColumnForTarget(code, instructionStart, target); - return ParsingError(location.line, location.column, detail); + return ":" + std::to_string(location.line) + ":" + + std::to_string(location.column) + ": " + detail; } /** @@ -174,21 +175,24 @@ void validateTargets(const std::string& code, size_t instructionStart, } const auto close = target.find(']', open + 1); if (open == 0 || close == std::string::npos || close != target.size() - 1) { - throw buildParseError(code, instructionStart, - invalidTargetDetail(target, context), target); + throw ParsingError(formatParseError(code, instructionStart, + invalidTargetDetail(target, context), + target)); } const auto registerName = target.substr(0, open); const auto indexText = target.substr(open + 1, close - open - 1); if (!isDigits(indexText)) { - throw buildParseError(code, instructionStart, - invalidTargetDetail(target, context), target); + throw ParsingError(formatParseError(code, instructionStart, + invalidTargetDetail(target, context), + target)); } size_t registerIndex = 0; try { registerIndex = std::stoul(indexText); } catch (const std::exception&) { - throw buildParseError(code, instructionStart, - invalidTargetDetail(target, context), target); + throw ParsingError(formatParseError(code, instructionStart, + invalidTargetDetail(target, context), + target)); } if (std::ranges::find(shadowedRegisters, registerName) != shadowedRegisters.end()) { @@ -196,8 +200,9 @@ void validateTargets(const std::string& code, size_t instructionStart, } const auto found = definedRegisters.find(registerName); if (found == definedRegisters.end() || found->second <= registerIndex) { - throw buildParseError(code, instructionStart, - invalidTargetDetail(target, context), target); + throw ParsingError(formatParseError(code, instructionStart, + invalidTargetDetail(target, context), + target)); } } } @@ -533,15 +538,15 @@ preprocessCode(const std::string& code, size_t startIndex, const auto& name = parts[0]; const auto sizeText = parts.size() > 1 ? parts[1] : ""; if (name.empty() || !isDigits(sizeText)) { - throw buildParseError(code, trueStart, - invalidRegisterDetail(trimmedLine)); + throw ParsingError(formatParseError( + code, trueStart, invalidRegisterDetail(trimmedLine))); } size_t size = 0; try { size = std::stoul(sizeText); } catch (const std::exception&) { - throw buildParseError(code, trueStart, - invalidRegisterDetail(trimmedLine)); + throw ParsingError(formatParseError( + code, trueStart, invalidRegisterDetail(trimmedLine))); } definedRegisters.insert({name, size}); } diff --git a/src/common/parsing/ParsingError.cpp b/src/common/parsing/ParsingError.cpp index f6a5ca3b..0b60a322 100644 --- a/src/common/parsing/ParsingError.cpp +++ b/src/common/parsing/ParsingError.cpp @@ -15,7 +15,6 @@ #include "common/parsing/ParsingError.hpp" -#include #include #include @@ -23,19 +22,4 @@ namespace mqt::debugger { ParsingError::ParsingError(const std::string& msg) : std::runtime_error(msg) {} -ParsingError::ParsingError(size_t line, size_t column, std::string detail) - : std::runtime_error([line, column, &detail]() { - std::ostringstream message; - message << ":" << line << ":" << column << ": " << detail; - return message.str(); - }()), - location_(ParsingErrorLocation{line, column, std::move(detail)}) {} - -const ParsingErrorLocation* ParsingError::location() const noexcept { - if (!location_.has_value()) { - return nullptr; - } - return &location_.value(); -} - } // namespace mqt::debugger From f53a848a00ae9db9c3797e07dfd2078a0296bc65 Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Wed, 14 Jan 2026 20:23:07 +0100 Subject: [PATCH 094/145] adjustments LoadResult --- bindings/InterfaceBindings.cpp | 45 ++++++++++++++ include/backend/dd/DDSimDebug.hpp | 15 +++++ include/backend/debug.h | 8 +++ include/common.h | 22 +++++++ python/mqt/debugger/__init__.py | 4 ++ python/mqt/debugger/dap/dap_server.py | 14 ++++- .../dap/messages/launch_dap_message.py | 12 ++-- .../dap/messages/restart_dap_message.py | 12 ++-- python/mqt/debugger/pydebugger.pyi | 33 ++++++++++ src/backend/dd/DDSimDebug.cpp | 62 +++++++++++++++++++ 10 files changed, 217 insertions(+), 10 deletions(-) diff --git a/bindings/InterfaceBindings.cpp b/bindings/InterfaceBindings.cpp index 1608625e..7bcb7dc9 100644 --- a/bindings/InterfaceBindings.cpp +++ b/bindings/InterfaceBindings.cpp @@ -65,6 +65,14 @@ struct StatevectorCPP { }; void bindFramework(py::module& m) { + // Bind the Result enum + py::native_enum(m, "Result", "enum.Enum", + "Represents the result of an operation.") + .value("OK", OK, "Indicates that the operation was successful.") + .value("ERROR", ERROR, "Indicates that an error occurred.") + .export_values() + .finalize(); + // Bind the VariableType enum py::native_enum(m, "VariableType", "enum.Enum", "The type of a classical variable.") @@ -166,6 +174,20 @@ Contains one element for each of the `num_states` states in the state vector.)") .doc() = "The settings that should be used to compile an assertion program."; + py::class_(m, "LoadResult") + .def(py::init<>(), "Creates a new `LoadResult` instance.") + .def_readwrite("status", &LoadResult::status, + "The result status of the load operation.") + .def_readwrite("line", &LoadResult::line, + "The line number of the error location, or 0 if unknown.") + .def_readwrite( + "column", &LoadResult::column, + "The column number of the error location, or 0 if unknown.") + .def_readwrite( + "message", &LoadResult::message, + "A human-readable error message, or None if none is available.") + .doc() = "Represents the result of loading code."; + py::class_(m, "SimulationState") .def(py::init<>(), "Creates a new `SimulationState` instance.") .def( @@ -190,6 +212,29 @@ Contains one element for each of the `num_states` states in the state vector.)") Args: code (str): The code to load.)") + .def( + "load_code_with_result", + [](SimulationState* self, const char* code) { + if (self->loadCodeWithResult != nullptr) { + return self->loadCodeWithResult(self, code); + } + LoadResult result{OK, 0, 0, nullptr}; + const Result status = self->loadCode(self, code); + result.status = status; + if (status != OK) { + result.message = self->getLastErrorMessage + ? self->getLastErrorMessage(self) + : nullptr; + } + return result; + }, + R"(Loads the given code and returns details about any errors. + +Args: + code (str): The code to load. + +Returns: + LoadResult: The result of the load operation.)") .def( "step_forward", [](SimulationState* self) { checkOrThrow(self->stepForward(self)); }, diff --git a/include/backend/dd/DDSimDebug.hpp b/include/backend/dd/DDSimDebug.hpp index 13a3e9b7..f50a37c5 100644 --- a/include/backend/dd/DDSimDebug.hpp +++ b/include/backend/dd/DDSimDebug.hpp @@ -123,6 +123,14 @@ struct DDSimulationState { * @brief The last error message produced by the interface. */ std::string lastErrorMessage; + /** + * @brief The last load error message without location prefixes. + */ + std::string lastLoadErrorDetail; + /** + * @brief The last load result produced by the interface. + */ + LoadResult lastLoadResult; /** * @brief Indicates whether the debugger is ready to start simulation. */ @@ -284,6 +292,13 @@ Result ddsimInit(SimulationState* self); * @return The result of the operation. */ Result ddsimLoadCode(SimulationState* self, const char* code); +/** + * @brief Loads the given code into the simulation state and returns details. + * @param self The instance to load the code into. + * @param code The code to load. + * @return The result of the load operation. + */ +LoadResult ddsimLoadCodeWithResult(SimulationState* self, const char* code); /** * @brief Steps the simulation forward by one instruction. * @param self The instance to step forward. diff --git a/include/backend/debug.h b/include/backend/debug.h index 0eacae23..0a89fcaa 100644 --- a/include/backend/debug.h +++ b/include/backend/debug.h @@ -54,6 +54,14 @@ struct SimulationStateStruct { */ Result (*loadCode)(SimulationState* self, const char* code); + /** + * @brief Loads the given code and returns detailed information about errors. + * @param self The instance to load the code into. + * @param code The code to load. + * @return The result of the load operation. + */ + LoadResult (*loadCodeWithResult)(SimulationState* self, const char* code); + /** * @brief Gets the last error message from the interface. * diff --git a/include/common.h b/include/common.h index c31e2ff7..498327e0 100644 --- a/include/common.h +++ b/include/common.h @@ -42,6 +42,28 @@ typedef enum { ERROR, } Result; +/** + * @brief The result of a code loading operation. + */ +typedef struct { + /** + * @brief Indicates whether the load was successful. + */ + Result status; + /** + * @brief The line number of the error location, or 0 if unknown. + */ + size_t line; + /** + * @brief The column number of the error location, or 0 if unknown. + */ + size_t column; + /** + * @brief A human-readable error message, or nullptr if none is available. + */ + const char* message; +} LoadResult; + /** * @brief The type of classical variables. * diff --git a/python/mqt/debugger/__init__.py b/python/mqt/debugger/__init__.py index 24ec7766..27a056e1 100644 --- a/python/mqt/debugger/__init__.py +++ b/python/mqt/debugger/__init__.py @@ -18,6 +18,8 @@ Diagnostics, ErrorCause, ErrorCauseType, + LoadResult, + Result, SimulationState, Statevector, Variable, @@ -33,6 +35,8 @@ "Diagnostics", "ErrorCause", "ErrorCauseType", + "LoadResult", + "Result", "SimulationState", "Statevector", "Variable", diff --git a/python/mqt/debugger/dap/dap_server.py b/python/mqt/debugger/dap/dap_server.py index 76071834..f11e816f 100644 --- a/python/mqt/debugger/dap/dap_server.py +++ b/python/mqt/debugger/dap/dap_server.py @@ -501,9 +501,19 @@ def _format_highlight_reason( return mqt.debugger.dap.messages.HighlightReason.CONTROL_ALWAYS_ZERO return mqt.debugger.dap.messages.HighlightReason.UNKNOWN - def queue_parse_error(self, error_message: str) -> None: + def queue_parse_error( + self, + error_message: str, + line: int | None = None, + column: int | None = None, + ) -> None: """Store highlight data for a parse error to be emitted later.""" - line, column, detail = self._parse_error_location(error_message) + if line is None or column is None: + line, column, detail = self._parse_error_location(error_message) + else: + detail = error_message.strip() + if not detail: + detail = "An error occurred while parsing the code." entry = self._build_parse_error_highlight(line, column, detail) if entry is not None: self.pending_highlights = [entry] diff --git a/python/mqt/debugger/dap/messages/launch_dap_message.py b/python/mqt/debugger/dap/messages/launch_dap_message.py index 612db3f0..8f20f72a 100644 --- a/python/mqt/debugger/dap/messages/launch_dap_message.py +++ b/python/mqt/debugger/dap/messages/launch_dap_message.py @@ -15,6 +15,8 @@ from pathlib import Path from typing import TYPE_CHECKING, Any +import mqt.debugger + from .dap_message import DAPMessage if TYPE_CHECKING: @@ -68,11 +70,13 @@ def handle(self, server: DAPServer) -> dict[str, Any]: parsed_successfully = True code = program_path.read_text(encoding=locale.getpreferredencoding(False)) server.source_code = code - try: - server.simulation_state.load_code(code) - except RuntimeError as exc: + load_result = server.simulation_state.load_code_with_result(code) + if load_result.status != mqt.debugger.Result.OK: parsed_successfully = False - server.queue_parse_error(str(exc)) + line = load_result.line if load_result.line > 0 else None + column = load_result.column if load_result.column > 0 else None + message = load_result.message or "" + server.queue_parse_error(message, line, column) if parsed_successfully and not self.stop_on_entry: server.simulation_state.run_simulation() if not parsed_successfully: diff --git a/python/mqt/debugger/dap/messages/restart_dap_message.py b/python/mqt/debugger/dap/messages/restart_dap_message.py index 5ed100fe..3262a410 100644 --- a/python/mqt/debugger/dap/messages/restart_dap_message.py +++ b/python/mqt/debugger/dap/messages/restart_dap_message.py @@ -15,6 +15,8 @@ from pathlib import Path from typing import TYPE_CHECKING, Any +import mqt.debugger + from .dap_message import DAPMessage if TYPE_CHECKING: @@ -69,11 +71,13 @@ def handle(self, server: DAPServer) -> dict[str, Any]: parsed_successfully = True code = program_path.read_text(encoding=locale.getpreferredencoding(False)) server.source_code = code - try: - server.simulation_state.load_code(code) - except RuntimeError as exc: + load_result = server.simulation_state.load_code_with_result(code) + if load_result.status != mqt.debugger.Result.OK: parsed_successfully = False - server.queue_parse_error(str(exc)) + line = load_result.line if load_result.line > 0 else None + column = load_result.column if load_result.column > 0 else None + message = load_result.message or "" + server.queue_parse_error(message, line, column) if parsed_successfully and not self.stop_on_entry: server.simulation_state.run_simulation() if not parsed_successfully: diff --git a/python/mqt/debugger/pydebugger.pyi b/python/mqt/debugger/pydebugger.pyi index c4476b00..da9cb22c 100644 --- a/python/mqt/debugger/pydebugger.pyi +++ b/python/mqt/debugger/pydebugger.pyi @@ -22,6 +22,14 @@ class VariableType(enum.Enum): VarFloat = 2 """A floating-point variable.""" +class Result(enum.Enum): + """Represents the result of an operation.""" + + OK = 0 + """Indicates that the operation was successful.""" + ERROR = 1 + """Indicates that an error occurred.""" + class ErrorCauseType(enum.Enum): """Represents the type of a potential error cause.""" @@ -95,6 +103,21 @@ class CompilationSettings: slice_index (int, optional): The index of the slice that should be compiled (defaults to 0). """ +class LoadResult: + """Represents the result of loading code.""" + + status: Result + """The result status of the load operation.""" + line: int + """The line number of the error location, or 0 if unknown.""" + column: int + """The column number of the error location, or 0 if unknown.""" + message: str | None + """A human-readable error message, or None if none is available.""" + + def __init__(self) -> None: + """Creates a new `LoadResult` instance.""" + class Statevector: """Represents a state vector.""" @@ -134,6 +157,16 @@ class SimulationState: code (str): The code to load. """ + def load_code_with_result(self, code: str) -> LoadResult: + """Loads the given code and returns details about any errors. + + Args: + code (str): The code to load. + + Returns: + LoadResult: The result of the load operation. + """ + def step_forward(self) -> None: """Steps the simulation forward by one instruction.""" diff --git a/src/backend/dd/DDSimDebug.cpp b/src/backend/dd/DDSimDebug.cpp index a8d0e3da..afcf8608 100644 --- a/src/backend/dd/DDSimDebug.cpp +++ b/src/backend/dd/DDSimDebug.cpp @@ -75,6 +75,48 @@ DDSimulationState* toDDSimulationState(SimulationState* state) { // NOLINTEND(cppcoreguidelines-pro-type-reinterpret-cast) } +struct ParsedLoadError { + size_t line; + size_t column; + std::string detail; +}; + +ParsedLoadError parseLoadErrorMessage(const std::string& message) { + const std::string trimmed = trim(message); + const std::string prefix = ":"; + if (!trimmed.starts_with(prefix)) { + return {0, 0, trimmed}; + } + + const size_t lineStart = prefix.size(); + const size_t lineEnd = trimmed.find(':', lineStart); + if (lineEnd == std::string::npos) { + return {0, 0, trimmed}; + } + const size_t columnEnd = trimmed.find(':', lineEnd + 1); + if (columnEnd == std::string::npos) { + return {0, 0, trimmed}; + } + + const std::string lineStr = trimmed.substr(lineStart, lineEnd - lineStart); + const std::string columnStr = + trimmed.substr(lineEnd + 1, columnEnd - lineEnd - 1); + auto isDigit = [](unsigned char c) { return std::isdigit(c) != 0; }; + if (lineStr.empty() || columnStr.empty() || + !std::ranges::all_of(lineStr, isDigit) || + !std::ranges::all_of(columnStr, isDigit)) { + return {0, 0, trimmed}; + } + + const size_t line = std::stoul(lineStr); + const size_t column = std::stoul(columnStr); + std::string detail = trim(trimmed.substr(columnEnd + 1)); + if (detail.empty()) { + detail = trimmed; + } + return {line, column, detail}; +} + const char* ddsimGetLastErrorMessage(SimulationState* self) { const auto* ddsim = toDDSimulationState(self); if (ddsim->lastErrorMessage.empty()) { @@ -521,6 +563,7 @@ Result createDDSimulationState(DDSimulationState* self) { self->interface.init = ddsimInit; self->interface.loadCode = ddsimLoadCode; + self->interface.loadCodeWithResult = ddsimLoadCodeWithResult; self->interface.getLastErrorMessage = ddsimGetLastErrorMessage; self->interface.stepForward = ddsimStepForward; self->interface.stepBackward = ddsimStepBackward; @@ -582,6 +625,8 @@ Result ddsimInit(SimulationState* self) { ddsim->lastFailedAssertion = -1ULL; ddsim->lastMetBreakpoint = -1ULL; ddsim->lastErrorMessage.clear(); + ddsim->lastLoadErrorDetail.clear(); + ddsim->lastLoadResult = {OK, 0, 0, nullptr}; destroyDDDiagnostics(&ddsim->diagnostics); createDDDiagnostics(&ddsim->diagnostics, ddsim); @@ -617,6 +662,8 @@ Result ddsimLoadCode(SimulationState* self, const char* code) { ddsim->targetQubits.clear(); ddsim->instructionObjects.clear(); ddsim->lastErrorMessage.clear(); + ddsim->lastLoadErrorDetail.clear(); + ddsim->lastLoadResult = {OK, 0, 0, nullptr}; try { std::stringstream ss{preprocessAssertionCode(code, ddsim)}; @@ -629,9 +676,17 @@ Result ddsimLoadCode(SimulationState* self, const char* code) { ddsim->lastErrorMessage = "An error occurred while executing the operation"; } + const auto parsed = parseLoadErrorMessage(ddsim->lastErrorMessage); + ddsim->lastLoadErrorDetail = parsed.detail; + ddsim->lastLoadResult = {ERROR, parsed.line, parsed.column, + ddsim->lastLoadErrorDetail.empty() + ? nullptr + : ddsim->lastLoadErrorDetail.c_str()}; return ERROR; } catch (...) { ddsim->lastErrorMessage = "An error occurred while executing the operation"; + ddsim->lastLoadErrorDetail = ddsim->lastErrorMessage; + ddsim->lastLoadResult = {ERROR, 0, 0, ddsim->lastLoadErrorDetail.c_str()}; return ERROR; } @@ -643,10 +698,17 @@ Result ddsimLoadCode(SimulationState* self, const char* code) { resetSimulationState(ddsim); ddsim->ready = true; + ddsim->lastLoadResult = {OK, 0, 0, nullptr}; return OK; } +LoadResult ddsimLoadCodeWithResult(SimulationState* self, const char* code) { + ddsimLoadCode(self, code); + auto* ddsim = toDDSimulationState(self); + return ddsim->lastLoadResult; +} + Result ddsimChangeClassicalVariableValue(SimulationState* self, const char* variableName, const VariableValue* value) { From d406d13273aff20af71c5384aef97d3f5cb35857 Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Wed, 14 Jan 2026 21:25:12 +0100 Subject: [PATCH 095/145] fix cl --- src/backend/dd/DDSimDebug.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/backend/dd/DDSimDebug.cpp b/src/backend/dd/DDSimDebug.cpp index afcf8608..5aed534b 100644 --- a/src/backend/dd/DDSimDebug.cpp +++ b/src/backend/dd/DDSimDebug.cpp @@ -85,17 +85,17 @@ ParsedLoadError parseLoadErrorMessage(const std::string& message) { const std::string trimmed = trim(message); const std::string prefix = ":"; if (!trimmed.starts_with(prefix)) { - return {0, 0, trimmed}; + return {.line = 0, .column = 0, .detail = trimmed}; } const size_t lineStart = prefix.size(); const size_t lineEnd = trimmed.find(':', lineStart); if (lineEnd == std::string::npos) { - return {0, 0, trimmed}; + return {.line = 0, .column = 0, .detail = trimmed}; } const size_t columnEnd = trimmed.find(':', lineEnd + 1); if (columnEnd == std::string::npos) { - return {0, 0, trimmed}; + return {.line = 0, .column = 0, .detail = trimmed}; } const std::string lineStr = trimmed.substr(lineStart, lineEnd - lineStart); @@ -105,7 +105,7 @@ ParsedLoadError parseLoadErrorMessage(const std::string& message) { if (lineStr.empty() || columnStr.empty() || !std::ranges::all_of(lineStr, isDigit) || !std::ranges::all_of(columnStr, isDigit)) { - return {0, 0, trimmed}; + return {.line = 0, .column = 0, .detail = trimmed}; } const size_t line = std::stoul(lineStr); @@ -114,7 +114,7 @@ ParsedLoadError parseLoadErrorMessage(const std::string& message) { if (detail.empty()) { detail = trimmed; } - return {line, column, detail}; + return {.line = line, .column = column, .detail = detail}; } const char* ddsimGetLastErrorMessage(SimulationState* self) { From 65f230c56275fc48f04177028e584447f6314fb5 Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Wed, 14 Jan 2026 21:40:04 +0100 Subject: [PATCH 096/145] adjustments --- python/mqt/debugger/dap/dap_server.py | 25 +++++-------------------- 1 file changed, 5 insertions(+), 20 deletions(-) diff --git a/python/mqt/debugger/dap/dap_server.py b/python/mqt/debugger/dap/dap_server.py index f11e816f..6f6c9058 100644 --- a/python/mqt/debugger/dap/dap_server.py +++ b/python/mqt/debugger/dap/dap_server.py @@ -11,7 +11,6 @@ from __future__ import annotations import json -import re import socket import sys from typing import TYPE_CHECKING, Any @@ -508,30 +507,16 @@ def queue_parse_error( column: int | None = None, ) -> None: """Store highlight data for a parse error to be emitted later.""" + detail = error_message.strip() + if not detail: + detail = "An error occurred while parsing the code." if line is None or column is None: - line, column, detail = self._parse_error_location(error_message) - else: - detail = error_message.strip() - if not detail: - detail = "An error occurred while parsing the code." + line = 1 + column = 1 entry = self._build_parse_error_highlight(line, column, detail) if entry is not None: self.pending_highlights = [entry] - @staticmethod - def _parse_error_location(error_message: str) -> tuple[int, int, str]: - """Parse a compiler error string and extract the source location.""" - match = re.match(r":(\d+):(\d+):\s*(.*)", error_message.strip()) - if match: - line = int(match.group(1)) - column = int(match.group(2)) - detail = match.group(3).strip() - else: - line = 1 - column = 1 - detail = error_message.strip() - return (line, column, detail) - def _build_parse_error_highlight(self, line: int, column: int, detail: str) -> dict[str, Any] | None: """Create a highlight entry for a parse error.""" if not getattr(self, "source_code", ""): From 3169e22806e2199531510cf3a2c330d43b81147a Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Wed, 14 Jan 2026 23:05:26 +0100 Subject: [PATCH 097/145] cl bug fix --- src/backend/dd/DDSimDebug.cpp | 26 +++++++++++++++++--------- src/frontend/cli/CliFrontEnd.cpp | 2 +- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/src/backend/dd/DDSimDebug.cpp b/src/backend/dd/DDSimDebug.cpp index 5aed534b..887afa33 100644 --- a/src/backend/dd/DDSimDebug.cpp +++ b/src/backend/dd/DDSimDebug.cpp @@ -626,7 +626,8 @@ Result ddsimInit(SimulationState* self) { ddsim->lastMetBreakpoint = -1ULL; ddsim->lastErrorMessage.clear(); ddsim->lastLoadErrorDetail.clear(); - ddsim->lastLoadResult = {OK, 0, 0, nullptr}; + ddsim->lastLoadResult = { + .status = OK, .line = 0, .column = 0, .message = nullptr}; destroyDDDiagnostics(&ddsim->diagnostics); createDDDiagnostics(&ddsim->diagnostics, ddsim); @@ -663,7 +664,8 @@ Result ddsimLoadCode(SimulationState* self, const char* code) { ddsim->instructionObjects.clear(); ddsim->lastErrorMessage.clear(); ddsim->lastLoadErrorDetail.clear(); - ddsim->lastLoadResult = {OK, 0, 0, nullptr}; + ddsim->lastLoadResult = { + .status = OK, .line = 0, .column = 0, .message = nullptr}; try { std::stringstream ss{preprocessAssertionCode(code, ddsim)}; @@ -678,15 +680,21 @@ Result ddsimLoadCode(SimulationState* self, const char* code) { } const auto parsed = parseLoadErrorMessage(ddsim->lastErrorMessage); ddsim->lastLoadErrorDetail = parsed.detail; - ddsim->lastLoadResult = {ERROR, parsed.line, parsed.column, - ddsim->lastLoadErrorDetail.empty() - ? nullptr - : ddsim->lastLoadErrorDetail.c_str()}; + ddsim->lastLoadResult = { + .status = ERROR, + .line = parsed.line, + .column = parsed.column, + .message = ddsim->lastLoadErrorDetail.empty() + ? nullptr + : ddsim->lastLoadErrorDetail.c_str()}; return ERROR; } catch (...) { ddsim->lastErrorMessage = "An error occurred while executing the operation"; ddsim->lastLoadErrorDetail = ddsim->lastErrorMessage; - ddsim->lastLoadResult = {ERROR, 0, 0, ddsim->lastLoadErrorDetail.c_str()}; + ddsim->lastLoadResult = {.status = ERROR, + .line = 0, + .column = 0, + .message = ddsim->lastLoadErrorDetail.c_str()}; return ERROR; } @@ -698,7 +706,8 @@ Result ddsimLoadCode(SimulationState* self, const char* code) { resetSimulationState(ddsim); ddsim->ready = true; - ddsim->lastLoadResult = {OK, 0, 0, nullptr}; + ddsim->lastLoadResult = { + .status = OK, .line = 0, .column = 0, .message = nullptr}; return OK; } @@ -1212,7 +1221,6 @@ Result ddsimStepBackward(SimulationState* self) { } Result ddsimRunAll(SimulationState* self, size_t* failedAssertions) { - auto* ddsim = toDDSimulationState(self); size_t errorCount = 0; while (!self->isFinished(self)) { const Result result = self->runSimulation(self); diff --git a/src/frontend/cli/CliFrontEnd.cpp b/src/frontend/cli/CliFrontEnd.cpp index 88796ad0..ede8b6d3 100644 --- a/src/frontend/cli/CliFrontEnd.cpp +++ b/src/frontend/cli/CliFrontEnd.cpp @@ -73,7 +73,7 @@ void CliFrontEnd::run(const char* code, SimulationState* state) { if (state->getLastErrorMessage != nullptr) { message = state->getLastErrorMessage(state); } - if (message != nullptr && message[0] != '\0') { + if (message != nullptr && *message != '\0') { std::cout << "Error loading code: " << message << "\n"; } else { std::cout << "Error loading code\n"; From 090de190993b485c5c17e773b5f06803232a8c6e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 14 Jan 2026 22:05:47 +0000 Subject: [PATCH 098/145] =?UTF-8?q?=F0=9F=8E=A8=20pre-commit=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/backend/dd/DDSimDebug.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/backend/dd/DDSimDebug.cpp b/src/backend/dd/DDSimDebug.cpp index 887afa33..2db31e2f 100644 --- a/src/backend/dd/DDSimDebug.cpp +++ b/src/backend/dd/DDSimDebug.cpp @@ -680,13 +680,13 @@ Result ddsimLoadCode(SimulationState* self, const char* code) { } const auto parsed = parseLoadErrorMessage(ddsim->lastErrorMessage); ddsim->lastLoadErrorDetail = parsed.detail; - ddsim->lastLoadResult = { - .status = ERROR, - .line = parsed.line, - .column = parsed.column, - .message = ddsim->lastLoadErrorDetail.empty() - ? nullptr - : ddsim->lastLoadErrorDetail.c_str()}; + ddsim->lastLoadResult = {.status = ERROR, + .line = parsed.line, + .column = parsed.column, + .message = + ddsim->lastLoadErrorDetail.empty() + ? nullptr + : ddsim->lastLoadErrorDetail.c_str()}; return ERROR; } catch (...) { ddsim->lastErrorMessage = "An error occurred while executing the operation"; From c6faa23ee6d5f8266524227b64f0165fec323dbb Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Wed, 14 Jan 2026 23:23:53 +0100 Subject: [PATCH 099/145] fix but --- src/backend/dd/DDSimDebug.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/backend/dd/DDSimDebug.cpp b/src/backend/dd/DDSimDebug.cpp index 2db31e2f..372c75c3 100644 --- a/src/backend/dd/DDSimDebug.cpp +++ b/src/backend/dd/DDSimDebug.cpp @@ -1221,6 +1221,10 @@ Result ddsimStepBackward(SimulationState* self) { } Result ddsimRunAll(SimulationState* self, size_t* failedAssertions) { + auto* ddsim = toDDSimulationState(self); + if (!ddsim->ready) { + return ERROR; + } size_t errorCount = 0; while (!self->isFinished(self)) { const Result result = self->runSimulation(self); @@ -1231,7 +1235,9 @@ Result ddsimRunAll(SimulationState* self, size_t* failedAssertions) { errorCount++; } } - *failedAssertions = errorCount; + if (failedAssertions != nullptr) { + *failedAssertions = errorCount; + } return OK; } From 6b159410aa4228faaa299886c65369e0341b94dd Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Thu, 15 Jan 2026 11:32:35 +0100 Subject: [PATCH 100/145] assortation fix + classical issue --- python/mqt/debugger/dap/dap_server.py | 3 + src/backend/dd/DDSimDebug.cpp | 150 +++++++++++++++++++++----- 2 files changed, 127 insertions(+), 26 deletions(-) diff --git a/python/mqt/debugger/dap/dap_server.py b/python/mqt/debugger/dap/dap_server.py index 6f6c9058..292f9188 100644 --- a/python/mqt/debugger/dap/dap_server.py +++ b/python/mqt/debugger/dap/dap_server.py @@ -189,6 +189,9 @@ def handle_client(self, connection: socket.socket) -> None: e = mqt.debugger.dap.messages.InitializedDAPEvent() event_payload = json.dumps(e.encode()) send_message(event_payload, connection) + if isinstance(cmd, (mqt.debugger.dap.messages.LaunchDAPMessage, mqt.debugger.dap.messages.RestartDAPMessage)): + clear_event = mqt.debugger.dap.messages.GrayOutDAPEvent([], self.source_file) + send_message(json.dumps(clear_event.encode()), connection) if ( isinstance( cmd, (mqt.debugger.dap.messages.LaunchDAPMessage, mqt.debugger.dap.messages.RestartDAPMessage) diff --git a/src/backend/dd/DDSimDebug.cpp b/src/backend/dd/DDSimDebug.cpp index 372c75c3..14ecbd7f 100644 --- a/src/backend/dd/DDSimDebug.cpp +++ b/src/backend/dd/DDSimDebug.cpp @@ -48,6 +48,7 @@ #include #include #include +#include #include #include #include @@ -81,6 +82,81 @@ struct ParsedLoadError { std::string detail; }; +std::optional evaluateClassicConditionFromCode(DDSimulationState* ddsim, + size_t instructionIndex) { + if (instructionIndex >= ddsim->instructionObjects.size()) { + return std::nullopt; + } + const auto& code = ddsim->instructionObjects[instructionIndex].code; + if (!isClassicControlledGate(code)) { + return std::nullopt; + } + const auto condition = parseClassicControlledGate(code).condition; + auto normalized = removeWhitespace(condition); + if (!normalized.empty() && normalized.front() == '(') { + normalized.erase(0, 1); + } + const auto eqPos = normalized.find("=="); + if (eqPos == std::string::npos) { + return std::nullopt; + } + const auto lhs = normalized.substr(0, eqPos); + const auto rhs = normalized.substr(eqPos + 2); + if (lhs.empty() || rhs.empty()) { + return std::nullopt; + } + + const auto parseIndex = [](const std::string& text, + size_t& value) -> bool { + if (text.empty()) { + return false; + } + if (std::ranges::any_of(text, [](unsigned char c) { return !std::isdigit(c); })) { + return false; + } + value = std::stoull(text); + return true; + }; + + size_t expected = 0; + if (!parseIndex(rhs, expected)) { + return std::nullopt; + } + + size_t registerValue = 0; + const auto bracketPos = lhs.find('['); + if (bracketPos != std::string::npos) { + const auto closePos = lhs.find(']', bracketPos + 1); + if (closePos == std::string::npos) { + return std::nullopt; + } + const auto base = lhs.substr(0, bracketPos); + const auto indexText = + lhs.substr(bracketPos + 1, closePos - bracketPos - 1); + size_t bitIndex = 0; + if (!parseIndex(indexText, bitIndex)) { + return std::nullopt; + } + const auto bitName = base + "[" + std::to_string(bitIndex) + "]"; + const auto& value = ddsim->variables[bitName].value.boolValue; + registerValue = value ? 1ULL : 0ULL; + } else { + const auto regIt = std::ranges::find_if( + ddsim->classicalRegisters, + [&lhs](const auto& reg) { return reg.name == lhs; }); + if (regIt == ddsim->classicalRegisters.end()) { + return std::nullopt; + } + for (size_t i = 0; i < regIt->size; i++) { + const auto name = getClassicalBitName(ddsim, regIt->index + i); + const auto& value = ddsim->variables[name].value.boolValue; + registerValue |= (value ? 1ULL : 0ULL) << i; + } + } + + return registerValue == expected; +} + ParsedLoadError parseLoadErrorMessage(const std::string& message) { const std::string trimmed = trim(message); const std::string prefix = ":"; @@ -1096,20 +1172,31 @@ Result ddsimStepForward(SimulationState* self) { throw std::runtime_error("If-else operations with non-equality " "comparisons are currently not supported"); } - if (op->getControlBit().has_value()) { - throw std::runtime_error("If-else operations controlled by a single " - "classical bit are currently not supported"); - } - const auto& controls = op->getControlRegister(); - const auto& exp = op->getExpectedValueRegister(); - size_t registerValue = 0; - for (size_t i = 0; i < controls->getSize(); i++) { - const auto name = - getClassicalBitName(ddsim, controls->getStartIndex() + i); - const auto& value = ddsim->variables[name].value.boolValue; - registerValue |= (value ? 1ULL : 0ULL) << i; + const auto condition = + evaluateClassicConditionFromCode(ddsim, currentInstruction); + bool conditionMet = false; + if (condition.has_value()) { + conditionMet = condition.value(); + } else { + const auto& exp = op->getExpectedValueRegister(); + size_t registerValue = 0; + if (op->getControlBit().has_value()) { + const auto controlBit = op->getControlBit().value(); + const auto name = getClassicalBitName(ddsim, controlBit); + const auto& value = ddsim->variables[name].value.boolValue; + registerValue = value ? 1ULL : 0ULL; + } else { + const auto& controls = op->getControlRegister(); + for (size_t i = 0; i < controls->getSize(); i++) { + const auto name = + getClassicalBitName(ddsim, controls->getStartIndex() + i); + const auto& value = ddsim->variables[name].value.boolValue; + registerValue |= (value ? 1ULL : 0ULL) << i; + } + } + conditionMet = (registerValue == exp); } - if (registerValue == exp) { + if (conditionMet) { auto* thenOp = op->getThenOp(); currDD = dd::getDD(*thenOp, *ddsim->dd); } else if (op->getElseOp() != nullptr) { @@ -1183,20 +1270,31 @@ Result ddsimStepBackward(SimulationState* self) { throw std::runtime_error("If-else operations with non-equality " "comparisons are currently not supported"); } - if (op->getControlBit().has_value()) { - throw std::runtime_error("If-else operations controlled by a single " - "classical bit are currently not supported"); - } - const auto& controls = op->getControlRegister(); - const auto& exp = op->getExpectedValueRegister(); - size_t registerValue = 0; - for (size_t i = 0; i < controls->getSize(); i++) { - const auto name = - getClassicalBitName(ddsim, controls->getStartIndex() + i); - const auto& value = ddsim->variables[name].value.boolValue; - registerValue |= (value ? 1ULL : 0ULL) << i; + const auto condition = evaluateClassicConditionFromCode( + ddsim, ddsim->currentInstruction); + bool conditionMet = false; + if (condition.has_value()) { + conditionMet = condition.value(); + } else { + const auto& exp = op->getExpectedValueRegister(); + size_t registerValue = 0; + if (op->getControlBit().has_value()) { + const auto controlBit = op->getControlBit().value(); + const auto name = getClassicalBitName(ddsim, controlBit); + const auto& value = ddsim->variables[name].value.boolValue; + registerValue = value ? 1ULL : 0ULL; + } else { + const auto& controls = op->getControlRegister(); + for (size_t i = 0; i < controls->getSize(); i++) { + const auto name = + getClassicalBitName(ddsim, controls->getStartIndex() + i); + const auto& value = ddsim->variables[name].value.boolValue; + registerValue |= (value ? 1ULL : 0ULL) << i; + } + } + conditionMet = (registerValue == exp); } - if (registerValue == exp) { + if (conditionMet) { auto* thenOp = op->getThenOp(); currDD = dd::getInverseDD(*thenOp, *ddsim->dd); } else if (op->getElseOp() != nullptr) { From 6fec6a9830b6931c2dafb9756d355b92b84d545e Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Thu, 15 Jan 2026 11:33:39 +0100 Subject: [PATCH 101/145] pre- commit fix --- python/mqt/debugger/dap/dap_server.py | 4 +++- src/backend/dd/DDSimDebug.cpp | 10 +++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/python/mqt/debugger/dap/dap_server.py b/python/mqt/debugger/dap/dap_server.py index 292f9188..9c176825 100644 --- a/python/mqt/debugger/dap/dap_server.py +++ b/python/mqt/debugger/dap/dap_server.py @@ -189,7 +189,9 @@ def handle_client(self, connection: socket.socket) -> None: e = mqt.debugger.dap.messages.InitializedDAPEvent() event_payload = json.dumps(e.encode()) send_message(event_payload, connection) - if isinstance(cmd, (mqt.debugger.dap.messages.LaunchDAPMessage, mqt.debugger.dap.messages.RestartDAPMessage)): + if isinstance( + cmd, (mqt.debugger.dap.messages.LaunchDAPMessage, mqt.debugger.dap.messages.RestartDAPMessage) + ): clear_event = mqt.debugger.dap.messages.GrayOutDAPEvent([], self.source_file) send_message(json.dumps(clear_event.encode()), connection) if ( diff --git a/src/backend/dd/DDSimDebug.cpp b/src/backend/dd/DDSimDebug.cpp index 14ecbd7f..36682691 100644 --- a/src/backend/dd/DDSimDebug.cpp +++ b/src/backend/dd/DDSimDebug.cpp @@ -106,12 +106,12 @@ std::optional evaluateClassicConditionFromCode(DDSimulationState* ddsim, return std::nullopt; } - const auto parseIndex = [](const std::string& text, - size_t& value) -> bool { + const auto parseIndex = [](const std::string& text, size_t& value) -> bool { if (text.empty()) { return false; } - if (std::ranges::any_of(text, [](unsigned char c) { return !std::isdigit(c); })) { + if (std::ranges::any_of(text, + [](unsigned char c) { return !std::isdigit(c); })) { return false; } value = std::stoull(text); @@ -1270,8 +1270,8 @@ Result ddsimStepBackward(SimulationState* self) { throw std::runtime_error("If-else operations with non-equality " "comparisons are currently not supported"); } - const auto condition = evaluateClassicConditionFromCode( - ddsim, ddsim->currentInstruction); + const auto condition = + evaluateClassicConditionFromCode(ddsim, ddsim->currentInstruction); bool conditionMet = false; if (condition.has_value()) { conditionMet = condition.value(); From af49c190131c6a072718e5991b81f3621af7da2e Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Thu, 15 Jan 2026 07:06:12 +0100 Subject: [PATCH 102/145] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Replace=20`pybind1?= =?UTF-8?q?1`=20with=20`nanobind`=20(#248)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update mqt-core * Replace pybind11 with nanobind * Enable Stable ABI wheels * Auto-generate stub file * Ignore linter errors * Clean up docstrings * Simplify ExternalDependencies.cmake * Improve stubs session --- .github/workflows/ci.yml | 4 +- CMakeLists.txt | 6 +- bindings/CMakeLists.txt | 5 +- bindings/InterfaceBindings.cpp | 311 +++++----- bindings/bindings.cpp | 15 +- bindings/dd/DDSimDebugBindings.cpp | 18 +- bindings/debugger_patterns.txt | 3 + cmake/ExternalDependencies.cmake | 127 +--- include/python/InterfaceBindings.hpp | 31 - include/python/dd/DDSimDebugBindings.hpp | 25 - noxfile.py | 49 ++ pyproject.toml | 16 +- python/mqt/debugger/dap/dap_server.py | 4 +- .../dap/messages/change_bit_dap_message.py | 2 +- python/mqt/debugger/pydebugger.pyi | 578 ++++++++++-------- test/python/test_diagnosis.py | 8 +- test/python/test_python_bindings.py | 6 +- uv.lock | 26 +- 18 files changed, 602 insertions(+), 632 deletions(-) create mode 100644 bindings/debugger_patterns.txt delete mode 100644 include/python/InterfaceBindings.hpp delete mode 100644 include/python/dd/DDSimDebugBindings.hpp diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 21c24063..ca3bb843 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -88,7 +88,7 @@ jobs: clang-version: 20 cmake-args: -DBUILD_MQT_DEBUGGER_BINDINGS=ON files-changed-only: true - install-pkgs: "pybind11==3.0.1" + install-pkgs: "nanobind==2.10.2" setup-python: true cpp-linter-extra-args: "-std=c++20" @@ -125,6 +125,8 @@ jobs: needs: change-detection if: fromJSON(needs.change-detection.outputs.run-python-tests) uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-linter.yml@d6314c45667c131055a0389afc110e8dedc6da3f # v1.17.11 + with: + check-stubs: true build-sdist: name: 🚀 CD diff --git a/CMakeLists.txt b/CMakeLists.txt index 62c727d9..3bb59992 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -42,10 +42,8 @@ if(BUILD_MQT_DEBUGGER_BINDINGS) CACHE BOOL "Prevent multiple searches for Python and instead cache the results.") # top-level call to find Python - find_package( - Python 3.10 REQUIRED - COMPONENTS Interpreter Development.Module - OPTIONAL_COMPONENTS Development.SABIModule) + find_package(Python 3.10 REQUIRED COMPONENTS Interpreter Development.Module + ${SKBUILD_SABI_COMPONENT}) endif() include(cmake/ExternalDependencies.cmake) diff --git a/bindings/CMakeLists.txt b/bindings/CMakeLists.txt index 2462db4e..099dd206 100644 --- a/bindings/CMakeLists.txt +++ b/bindings/CMakeLists.txt @@ -5,7 +5,7 @@ # # Licensed under the MIT License -add_mqt_python_binding( +add_mqt_python_binding_nanobind( DEBUGGER ${MQT_DEBUGGER_TARGET_NAME}-bindings bindings.cpp @@ -16,5 +16,4 @@ add_mqt_python_binding( INSTALL_DIR . LINK_LIBS - MQT::Debugger - pybind11_json) + MQT::Debugger) diff --git a/bindings/InterfaceBindings.cpp b/bindings/InterfaceBindings.cpp index 7bcb7dc9..ac1a052a 100644 --- a/bindings/InterfaceBindings.cpp +++ b/bindings/InterfaceBindings.cpp @@ -14,8 +14,6 @@ * diagnostics interfaces. */ -#include "python/InterfaceBindings.hpp" - #include "backend/debug.h" #include "backend/diagnostics.h" #include "common.h" @@ -23,19 +21,17 @@ #include #include #include -#include -#include -#include -#include -#include -#include // NOLINT(misc-include-cleaner) +#include +#include // NOLINT(misc-include-cleaner) +#include // NOLINT(misc-include-cleaner) +#include // NOLINT(misc-include-cleaner) #include #include #include #include -namespace py = pybind11; -using namespace pybind11::literals; +namespace nb = nanobind; +using namespace nb::literals; namespace { @@ -64,61 +60,48 @@ struct StatevectorCPP { std::vector amplitudes; }; -void bindFramework(py::module& m) { - // Bind the Result enum - py::native_enum(m, "Result", "enum.Enum", - "Represents the result of an operation.") - .value("OK", OK, "Indicates that the operation was successful.") - .value("ERROR", ERROR, "Indicates that an error occurred.") - .export_values() - .finalize(); - +// NOLINTNEXTLINE(misc-use-internal-linkage) +void bindFramework(nb::module_& m) { // Bind the VariableType enum - py::native_enum(m, "VariableType", "enum.Enum", - "The type of a classical variable.") + nb::enum_(m, "VariableType", + "The type of a classical variable.") .value("VarBool", VarBool, "A boolean variable.") .value("VarInt", VarInt, "An integer variable.") - .value("VarFloat", VarFloat, "A floating-point variable.") - .export_values() - .finalize(); + .value("VarFloat", VarFloat, "A floating-point variable."); // Bind the VariableValue union - py::class_(m, "VariableValue") - .def(py::init<>()) - .def_readwrite("bool_value", &VariableValue::boolValue, - "The value of a boolean variable.") - .def_readwrite("int_value", &VariableValue::intValue, - "The value of an integer variable.") - .def_readwrite("float_value", &VariableValue::floatValue, - "The value of a floating-point variable.") + nb::class_(m, "VariableValue") + .def(nb::init<>()) + .def_rw("bool_value", &VariableValue::boolValue, + "The value of a boolean variable.") + .def_rw("int_value", &VariableValue::intValue, + "The value of an integer variable.") + .def_rw("float_value", &VariableValue::floatValue, + "The value of a floating-point variable.") .doc() = R"(Represents the value of a classical variable. Only one of these fields has a valid value at a time, based on the variable's `VariableType`.)"; // Bind the Variable struct - py::class_(m, "Variable") - .def(py::init<>(), "Creates a new `Variable` instance.") - .def_readwrite("name", &Variable::name, "The name of the variable.") - .def_readwrite("type", &Variable::type, "The type of the variable.") - .def_readwrite("value", &Variable::value, "The value of the variable.") + nb::class_(m, "Variable") + .def(nb::init<>(), "Creates a new `Variable` instance.") + .def_rw("name", &Variable::name, "The name of the variable.") + .def_rw("type_", &Variable::type, "The type of the variable.") + .def_rw("value", &Variable::value, "The value of the variable.") .doc() = "Represents a classical variable."; // Bind the Complex struct - py::class_(m, "Complex") - .def(py::init<>(), R"(Initializes a new complex number. - -Args: - real (float, optional): The real part of the complex number. Defaults to 0.0. - imaginary (float, optional): The imaginary part of the complex number. Defaults to 0.0.)") - .def(py::init(), R"(Initializes a new complex number. + nb::class_(m, "Complex") + .def(nb::init<>(), R"(Initializes a new complex number.)") + .def(nb::init(), "real"_a = 0.0, "imaginary"_a = 0.0, + R"(Initializes a new complex number. Args: - real (float, optional): The real part of the complex number. Defaults to 0.0. - imaginary (float, optional): The imaginary part of the complex number. Defaults to 0.0.)") - .def_readwrite("real", &Complex::real, - "The real part of the complex number.") - .def_readwrite("imaginary", &Complex::imaginary, - "The imaginary part of the complex number.") + real: The real part of the complex number. Defaults to 0.0. + imaginary: The imaginary part of the complex number. Defaults to 0.0.)") + .def_rw("real", &Complex::real, "The real part of the complex number.") + .def_rw("imaginary", &Complex::imaginary, + "The imaginary part of the complex number.") .def( "__str__", [](const Complex& self) { @@ -126,8 +109,9 @@ Only one of these fields has a valid value at a time, based on the variable's `V std::to_string(self.imaginary) + "i"; }, R"(Returns a string representation of the complex number. + Returns: - str: The string representation of the complex number. + The string representation of the complex number. )") .def( "__repr__", @@ -138,58 +122,42 @@ Only one of these fields has a valid value at a time, based on the variable's `V R"(Returns a string representation of the complex number. Returns: - str: The string representation of the complex number.)") + The string representation of the complex number.)") .doc() = "Represents a complex number."; // Bind the Statevector struct - py::class_(m, "Statevector") - .def(py::init<>(), "Creates a new `Statevector` instance.") - .def_readwrite("num_qubits", &StatevectorCPP::numQubits, - "The number of qubits in the state vector.") - .def_readwrite("num_states", &StatevectorCPP::numStates, - R"(The number of states in the state vector. + nb::class_(m, "Statevector") + .def(nb::init<>(), "Creates a new `Statevector` instance.") + .def_rw("num_qubits", &StatevectorCPP::numQubits, + "The number of qubits in the state vector.") + .def_rw("num_states", &StatevectorCPP::numStates, + R"(The number of states in the state vector. This is always equal to 2^`num_qubits`.)") - .def_readwrite("amplitudes", &StatevectorCPP::amplitudes, - R"(The amplitudes of the state vector. + .def_rw("amplitudes", &StatevectorCPP::amplitudes, + R"(The amplitudes of the state vector. Contains one element for each of the `num_states` states in the state vector.)") .doc() = "Represents a state vector."; - py::class_(m, "CompilationSettings") - .def(py::init(), py::arg("opt"), - py::arg("slice_index") = 0, + nb::class_(m, "CompilationSettings") + .def(nb::init(), "opt"_a, "slice_index"_a = 0, R"(Initializes a new set of compilation settings. Args: - opt (int): The optimization level that should be used. - slice_index (int, optional): The index of the slice that should be compiled (defaults to 0).)") - .def_readwrite( + opt: The optimization level that should be used. + slice_index: The index of the slice that should be compiled (defaults to 0).)") + .def_rw( "opt", &CompilationSettings ::opt, "The optimization level that should be used. Exact meaning depends " - "on " - "the implementation, but typically 0 means no optimization.") - .def_readwrite("slice_index", &CompilationSettings::sliceIndex, - "The index of the slice that should be compiled.") + "on the implementation, but typically 0 means no optimization.") + .def_rw("slice_index", &CompilationSettings::sliceIndex, + "The index of the slice that should be compiled.") .doc() = "The settings that should be used to compile an assertion program."; - py::class_(m, "LoadResult") - .def(py::init<>(), "Creates a new `LoadResult` instance.") - .def_readwrite("status", &LoadResult::status, - "The result status of the load operation.") - .def_readwrite("line", &LoadResult::line, - "The line number of the error location, or 0 if unknown.") - .def_readwrite( - "column", &LoadResult::column, - "The column number of the error location, or 0 if unknown.") - .def_readwrite( - "message", &LoadResult::message, - "A human-readable error message, or None if none is available.") - .doc() = "Represents the result of loading code."; - - py::class_(m, "SimulationState") - .def(py::init<>(), "Creates a new `SimulationState` instance.") + nb::class_(m, "SimulationState") + .def(nb::init<>(), "Creates a new `SimulationState` instance.") .def( "init", [](SimulationState* self) { checkOrThrow(self->init(self)); }, "Initializes the simulation state.") @@ -208,10 +176,11 @@ Contains one element for each of the `num_states` states in the state vector.)") throw std::runtime_error(message); } }, + "code"_a, R"(Loads the given code into the simulation state. Args: - code (str): The code to load.)") + code: The code to load.)") .def( "load_code_with_result", [](SimulationState* self, const char* code) { @@ -281,7 +250,7 @@ Contains one element for each of the `num_states` states in the state vector.)") R"(Runs the simulation until it finishes, even if assertions fail. Returns: -int: The number of assertions that failed during execution.)") + The number of assertions that failed during execution.)") .def( "run_simulation", [](SimulationState* self) { @@ -329,7 +298,7 @@ The simulation is unable to step forward if it has finished or if the simulation has not been set up yet. Returns: -bool: True, if the simulation can step forward.)") + True, if the simulation can step forward.)") .def( "can_step_backward", [](SimulationState* self) { return self->canStepBackward(self); }, @@ -339,31 +308,32 @@ The simulation is unable to step backward if it is at the beginning or if the simulation has not been set up yet. Returns: -bool: True, if the simulation can step backward.)") + True, if the simulation can step backward.)") .def( "change_classical_variable_value", [](SimulationState* self, const std::string& variableName, - const py::object& newValue) { + const nb::object& newValue) { VariableValue value{}; - if (py::isinstance(newValue)) { - value.boolValue = newValue.cast(); - } else if (py::isinstance(newValue)) { - value.intValue = newValue.cast(); - } else if (py::isinstance(newValue)) { - value.floatValue = newValue.cast(); + if (nb::isinstance(newValue)) { + value.boolValue = nb::cast(newValue); + } else if (nb::isinstance(newValue)) { + value.intValue = nb::cast(newValue); + } else if (nb::isinstance(newValue)) { + value.floatValue = nb::cast(newValue); } else { - throw py::type_error( + throw nb::type_error( "change_classical_variable_value requires a bool, " "int, or float value"); } checkOrThrow(self->changeClassicalVariableValue( self, variableName.c_str(), &value)); }, + "variable_name"_a, "new_value"_a, R"(Updates the value of a classical variable. Args: - variableName (str): The name of the classical variable to update. - newValue (bool | float): The desired value.)") + variable_name: The name of the classical variable to update. + new_value: The desired value.)") .def( "change_amplitude_value", [](SimulationState* self, const std::string& basisState, @@ -371,6 +341,7 @@ bool: True, if the simulation can step backward.)") checkOrThrow( self->changeAmplitudeValue(self, basisState.c_str(), &value)); }, + "basis_state"_a, "value"_a, R"(Updates the amplitude of a given computational basis state. The basis state is provided as a bitstring whose length matches the @@ -379,8 +350,8 @@ remaining amplitudes so that the state vector stays normalized and to reject invalid bitstrings or amplitudes that violate normalization. Args: - basisState (str): The bitstring identifying the basis state to update. - value (Complex): The desired complex amplitude.)") + basis_state: The bitstring identifying the basis state to update. + value: The desired complex amplitude.)") .def( "is_finished", [](SimulationState* self) { return self->isFinished(self); }, @@ -389,7 +360,7 @@ reject invalid bitstrings or amplitudes that violate normalization. The execution is considered finished if it has reached the end of the code. Returns: -bool: True, if the execution has finished.)") + True, if the execution has finished.)") .def( "did_assertion_fail", [](SimulationState* self) { return self->didAssertionFail(self); }, @@ -399,7 +370,7 @@ If execution is continued after a failed assertion, then this flag will be set to false again. Returns: -bool: True, if an assertion failed during the last step.)") + True, if an assertion failed during the last step.)") .def( "was_breakpoint_hit", [](SimulationState* self) { return self->wasBreakpointHit(self); }, @@ -409,7 +380,7 @@ If execution is continued after a breakpoint, then this flag will be set to false again. Returns: -bool: True, if a breakpoint was hit during the last step.)") + True, if a breakpoint was hit during the last step.)") .def( "get_current_instruction", [](SimulationState* self) { @@ -418,14 +389,14 @@ bool: True, if a breakpoint was hit during the last step.)") R"(Gets the current instruction index. Returns: -int: The index of the current instruction.)") + The index of the current instruction.)") .def( "get_instruction_count", [](SimulationState* self) { return self->getInstructionCount(self); }, R"(Gets the number of instructions in the code. Returns: - int: The number of instructions in the code.)") + The number of instructions in the code.)") .def( "get_instruction_position", [](SimulationState* self, size_t instruction) { @@ -435,22 +406,23 @@ int: The index of the current instruction.)") self->getInstructionPosition(self, instruction, &start, &end)); return std::make_pair(start, end); }, + "instruction"_a, R"(Gets the position of the given instruction in the code. Start and end positions are inclusive and white-spaces are ignored. Args: - instruction (int): The instruction to find. + instruction: The instruction to find. Returns: - tuple[int, int]: The start and end positions of the instruction.)") + The start and end positions of the instruction.)") .def( "get_num_qubits", [](SimulationState* self) { return self->getNumQubits(self); }, R"(Gets the number of qubits used by the program. Returns: - int: The number of qubits used by the program.)") + The number of qubits used by the program.)") .def( "get_amplitude_index", [](SimulationState* self, size_t qubit) { @@ -458,16 +430,17 @@ Start and end positions are inclusive and white-spaces are ignored. checkOrThrow(self->getAmplitudeIndex(self, qubit, &output)); return output; }, + "index"_a, R"(Gets the complex amplitude of a state in the full state vector. The amplitude is selected by an integer index that corresponds to the binary representation of the state. Args: - index (int): The index of the state in the full state vector. + index: The index of the state in the full state vector. Returns: - Complex: The complex amplitude of the state.)") + The complex amplitude of the state.)") .def( "get_amplitude_bitstring", [](SimulationState* self, const char* bitstring) { @@ -475,15 +448,16 @@ binary representation of the state. checkOrThrow(self->getAmplitudeBitstring(self, bitstring, &output)); return output; }, + "bitstring"_a, R"(Gets the complex amplitude of a state in the full state vector. The amplitude is selected by a bitstring representing the state. Args: - bitstring (str): The index of the state as a bitstring. + bitstring: The index of the state as a bitstring. Returns: - Complex: The complex amplitude of the state.)") + The complex amplitude of the state.)") .def( "get_classical_variable", [](SimulationState* self, const char* name) { @@ -491,16 +465,17 @@ The amplitude is selected by a bitstring representing the state. checkOrThrow(self->getClassicalVariable(self, name, &output)); return output; }, + "name"_a, R"(Gets a classical variable by name. For registers, the name should be the register name followed by the index in square brackets. Args: - name (str): The name of the variable. + name: The name of the variable. Returns: - Variable: The fetched variable.)") + The fetched variable.)") .def( "get_num_classical_variables", [](SimulationState* self) { @@ -511,7 +486,7 @@ in square brackets. For registers, each index is counted as a separate variable. Returns: - int: The number of classical variables in the simulation.)") + The number of classical variables in the simulation.)") .def( "get_classical_variable_name", [](SimulationState* self, size_t variableIndex) { @@ -524,6 +499,7 @@ For registers, each index is counted as a separate variable. } return output; }, + "index"_a, R"(Gets the name of a classical variable by its index. For registers, each index is counted as a separate variable and can be @@ -531,10 +507,10 @@ accessed separately. This method will return the name of the specific index of the register. Args: - index (int): The index of the variable. + index: The index of the variable. Returns: - str: The name of the variable.)") + The name of the variable.)") .def( "get_quantum_variable_name", [](SimulationState* self, size_t variableIndex) { @@ -547,6 +523,7 @@ index of the register. } return output; }, + "index"_a, R"(Gets the name of a quantum variable by its index. For registers, each index is counted as a separate variable and can be @@ -554,10 +531,10 @@ accessed separately. This method will return the name of the specific index of the register. Args: - index (int): The index of the variable. + index: The index of the variable. Returns: - str: The name of the variable.)") + The name of the variable.)") .def( "get_state_vector_full", [](SimulationState* self) { @@ -574,11 +551,8 @@ index of the register. }, R"(Gets the full state vector of the simulation at the current time. -The state vector is expected to be initialized with the correct number of -qubits and allocated space for the amplitudes before calling this method. - Returns: - Statevector: The full state vector of the current simulation state.)") + The full state vector of the current simulation state.)") .def( "get_state_vector_sub", [](SimulationState* self, std::vector qubits) { @@ -594,19 +568,17 @@ qubits and allocated space for the amplitudes before calling this method. &output)); return result; }, + "qubits"_a, R"(Gets a sub-state of the state vector of the simulation at the current time. -The state vector is expected to be initialized with the correct number of -qubits and allocated space for the amplitudes before calling this method. - This method also supports the re-ordering of qubits, but does not allow qubits to be repeated. Args: - qubits (list[int]): The qubits to include in the sub-state. + qubits: The qubits to include in the sub-state. Returns: - Statevector: The sub-state vector of the current simulation state.)") + The sub-state vector of the current simulation state.)") .def( "set_breakpoint", [](SimulationState* self, size_t desiredPosition) { @@ -615,16 +587,17 @@ qubits to be repeated. self->setBreakpoint(self, desiredPosition, &actualPosition)); return actualPosition; }, + "desired_position"_a, R"(Sets a breakpoint at the desired position in the code. The position is given as a 0-indexed character position in the full code string. Args: - desired_position (int): The position in the code to set the breakpoint. + desired_position: The position in the code to set the breakpoint. Returns: - int: The index of the instruction where the breakpoint was set.)") + The index of the instruction where the breakpoint was set.)") .def( "clear_breakpoints", [](SimulationState* self) { @@ -643,7 +616,7 @@ string. Each custom gate call corresponds to one stack entry. Returns: - int: The current stack depth of the simulation.)") + The current stack depth of the simulation.)") .def( "get_stack_trace", [](SimulationState* self, size_t maxDepth) { @@ -655,6 +628,7 @@ Each custom gate call corresponds to one stack entry. self->getStackTrace(self, maxDepth, stackTrace.data())); return stackTrace; }, + "max_depth"_a, R"(Gets the current stack trace of the simulation. The stack trace is represented as a list of instruction indices. Each @@ -662,18 +636,18 @@ instruction index represents a single return address for the corresponding stack entry. Args: - max_depth (int): The maximum depth of the stack trace. + max_depth: The maximum depth of the stack trace. Returns: - list[int]: The stack trace of the simulation.)") + The stack trace of the simulation.)") .def( "get_diagnostics", [](SimulationState* self) { return self->getDiagnostics(self); }, - py::return_value_policy::reference_internal, + nb::rv_policy::reference_internal, R"(Gets the diagnostics instance employed by this debugger. Returns: - Diagnostics: The diagnostics instance employed by this debugger.)") + The diagnostics instance employed by this debugger.)") .def( "compile", [](SimulationState* self, CompilationSettings& settings) { @@ -686,44 +660,44 @@ stack entry. std::string result(buffer.data(), size - 1); return result; }, + "settings"_a, R"(Compiles the given code into a quantum circuit without assertions. Args: - settings (CompilationSettings): The settings to use for the compilation. + settings: The settings to use for the compilation. Returns: - str: The compiled code.)") + The compiled code.)") .doc() = R"(Represents the state of a quantum simulation for debugging. -"This is the main class of the `mqt-debugger` library, allowing developers to step through the code and inspect the state of the simulation.)"; +This is the main class of the `mqt-debugger` library, allowing developers to step through the code and inspect the state of the simulation.)"; } -void bindDiagnostics(py::module& m) { +// NOLINTNEXTLINE(misc-use-internal-linkage) +void bindDiagnostics(nb::module_& m) { // Bind the ErrorCauseType enum - py::native_enum(m, "ErrorCauseType", "enum.Enum", - "The type of a potential error cause.") + nb::enum_(m, "ErrorCauseType", + "The type of a potential error cause.") .value("Unknown", Unknown, "An unknown error cause.") .value("MissingInteraction", MissingInteraction, "Indicates that an entanglement error may be caused by a missing " "interaction.") .value("ControlAlwaysZero", ControlAlwaysZero, "Indicates that an error may be related to a controlled gate with " - "a control that is always zero.") - .export_values() - .finalize(); + "a control that is always zero."); // Bind the ErrorCause struct - py::class_(m, "ErrorCause") - .def(py::init<>()) - .def_readwrite("instruction", &ErrorCause ::instruction, - "The instruction where the error may originate from or " - "where the error can be detected.") - .def_readwrite("type", &ErrorCause ::type, - "The type of the potential error cause.") + nb::class_(m, "ErrorCause") + .def(nb::init<>()) + .def_rw("instruction", &ErrorCause ::instruction, + "The instruction where the error may originate from or " + "where the error can be detected.") + .def_rw("type_", &ErrorCause ::type, + "The type of the potential error cause.") .doc() = "Represents a potential cause of an assertion error."; - py::class_(m, "Diagnostics") - .def(py::init<>(), "Creates a new `Diagnostics` instance.") + nb::class_(m, "Diagnostics") + .def(nb::init<>(), "Creates a new `Diagnostics` instance.") .def( "init", [](Diagnostics* self) { checkOrThrow(self->init(self)); }, "Initializes the diagnostics instance.") @@ -733,14 +707,14 @@ void bindDiagnostics(py::module& m) { R"(Get the number of qubits in the system. Returns: - int: The number of qubits in the system.)") + The number of qubits in the system.)") .def( "get_instruction_count", [](Diagnostics* self) { return self->getInstructionCount(self); }, R"(Get the number of instructions in the code. Returns: - int: The number of instructions in the code.)") + The number of instructions in the code.)") .def( "get_data_dependencies", [](Diagnostics* self, size_t instruction, bool includeCallers) { @@ -758,7 +732,7 @@ void bindDiagnostics(py::module& m) { } return result; }, - py::arg("instruction"), py::arg("include_callers") = false, + "instruction"_a, "include_callers"_a = false, R"(Extract all data dependencies for a given instruction. If the instruction is inside a custom gate definition, the data @@ -774,11 +748,11 @@ This method can be performed without running the program, as it is a static analysis method. Args: - instruction (int): The instruction to extract the data dependencies for. - include_callers (bool, optional): True, if the data dependencies should include all possible callers of the containing custom gate. Defaults to False. + instruction: The instruction to extract the data dependencies for. + include_callers: True, if the data dependencies should include all possible callers of the containing custom gate. Defaults to False. Returns: - list[int]: A list of instruction indices that are data dependencies of the given instruction.)") + A list of instruction indices that are data dependencies of the given instruction.)") .def( "get_interactions", [](Diagnostics* self, size_t beforeInstruction, size_t qubit) { @@ -796,6 +770,7 @@ analysis method. } return result; }, + "before_instruction"_a, "qubit"_a, R"(Extract all qubits that interact with a given qubit up to a specific instruction. If the target instruction is inside a custom gate definition, the @@ -808,11 +783,11 @@ This method can be performed without running the program, as it is a static analysis method. Args: - before_instruction (int): The instruction to extract the interactions up to (excluding). - qubit (int): The qubit to extract the interactions for. + before_instruction: The instruction to extract the interactions up to (excluding). + qubit: The qubit to extract the interactions for. Returns: - list[int]: A list of qubit indices that interact with the given qubit up to the target instruction.)") + A list of qubit indices that interact with the given qubit up to the target instruction.)") .def( "get_zero_control_instructions", [](Diagnostics* self) { @@ -840,7 +815,7 @@ This method can only be performed at runtime, as it is a dynamic analysis method. Returns: - list[int]: The indices of instructions that are controlled gates with only zero controls.)") + The indices of instructions that are controlled gates with only zero controls.)") .def( "potential_error_causes", [](Diagnostics* self) { @@ -862,7 +837,7 @@ This method should be run after the program has been executed and reached an assertion error. Returns: - list[ErrorCause]: A list of potential error causes encountered during execution.)") + A list of potential error causes encountered during execution.)") .def( "suggest_assertion_movements", [](Diagnostics* self) { @@ -886,7 +861,7 @@ Each entry of the resulting list consists of the original position of the assert suggested position. Returns: - list[tuple[int, int]]: A list of moved assertions. + A list of moved assertions. )") .def( "suggest_new_assertions", @@ -932,7 +907,7 @@ Each entry of the resulting list consists of the suggested position for the new string representation. Returns: - list[tuple[int, str]]: A list of new assertions. + A list of new assertions. )") .doc() = "Provides diagnostics capabilities such as different analysis " "methods for the debugger."; diff --git a/bindings/bindings.cpp b/bindings/bindings.cpp index af2cb34e..68bfb037 100644 --- a/bindings/bindings.cpp +++ b/bindings/bindings.cpp @@ -15,16 +15,17 @@ * Central file for defining the Python bindings for the framework. */ -#include "python/InterfaceBindings.hpp" -#include "python/dd/DDSimDebugBindings.hpp" +#include -#include -#include +namespace nb = nanobind; +using namespace nb::literals; -namespace py = pybind11; -using namespace pybind11::literals; +// forward declarations +void bindFramework(nb::module_& m); +void bindDiagnostics(nb::module_& m); +void bindBackend(nb::module_& m); -PYBIND11_MODULE(MQT_DEBUGGER_MODULE_NAME, m, py::mod_gil_not_used()) { +NB_MODULE(MQT_DEBUGGER_MODULE_NAME, m) { bindDiagnostics(m); bindFramework(m); bindBackend(m); diff --git a/bindings/dd/DDSimDebugBindings.cpp b/bindings/dd/DDSimDebugBindings.cpp index 9fffb32f..7f1cb938 100644 --- a/bindings/dd/DDSimDebugBindings.cpp +++ b/bindings/dd/DDSimDebugBindings.cpp @@ -16,16 +16,16 @@ * Diagnostics states. */ -#include "python/dd/DDSimDebugBindings.hpp" - #include "backend/dd/DDSimDebug.hpp" #include "backend/debug.h" -#include "pybind11/pybind11.h" +#include "nanobind/nanobind.h" +namespace nb = nanobind; +using namespace nb::literals; using namespace mqt::debugger; -void bindBackend(pybind11::module& m) { - +// NOLINTNEXTLINE(misc-use-internal-linkage) +void bindBackend(nb::module_& m) { m.def( "create_ddsim_simulation_state", []() { @@ -37,17 +37,17 @@ void bindBackend(pybind11::module& m) { R"(Creates a new `SimulationState` instance using the DD backend for simulation and the OpenQASM language as input format. Returns: - SimulationState: The created simulation state.)"); + The created simulation state.)"); m.def( "destroy_ddsim_simulation_state", [](SimulationState* state) { - // NOLINTBEGIN(cppcoreguidelines-pro-type-reinterpret-cast) + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) destroyDDSimulationState(reinterpret_cast(state)); - // NOLINTEND(cppcoreguidelines-pro-type-reinterpret-cast) }, + "state"_a, R"(Delete a given DD-based `SimulationState` instance and free up resources. Args: - state (SimulationState): The simulation state to delete.)"); + state: The simulation state to delete.)"); } diff --git a/bindings/debugger_patterns.txt b/bindings/debugger_patterns.txt new file mode 100644 index 00000000..55bcfafb --- /dev/null +++ b/bindings/debugger_patterns.txt @@ -0,0 +1,3 @@ +_hashable_values_: + +_unhashable_values_map_: diff --git a/cmake/ExternalDependencies.cmake b/cmake/ExternalDependencies.cmake index 7d6c9119..a45b82d6 100644 --- a/cmake/ExternalDependencies.cmake +++ b/cmake/ExternalDependencies.cmake @@ -10,92 +10,50 @@ include(FetchContent) set(FETCH_PACKAGES "") if(BUILD_MQT_DEBUGGER_BINDINGS) - if(NOT SKBUILD) - # Manually detect the installed pybind11 package. - execute_process( - COMMAND "${Python_EXECUTABLE}" -m pybind11 --cmakedir - OUTPUT_STRIP_TRAILING_WHITESPACE - OUTPUT_VARIABLE pybind11_DIR) - - # Add the detected directory to the CMake prefix path. - list(APPEND CMAKE_PREFIX_PATH "${pybind11_DIR}") - endif() - - # add pybind11 library - find_package(pybind11 3.0.1 CONFIG REQUIRED) + execute_process( + COMMAND "${Python_EXECUTABLE}" -m nanobind --cmake_dir + OUTPUT_STRIP_TRAILING_WHITESPACE + OUTPUT_VARIABLE nanobind_ROOT) + find_package(nanobind CONFIG REQUIRED) endif() # ---------------------------------------------------------------------------------Fetch MQT Core # cmake-format: off -set(MQT_CORE_MINIMUM_VERSION 3.3.1 +set(MQT_CORE_MINIMUM_VERSION 3.4.0 CACHE STRING "MQT Core minimum version") -set(MQT_CORE_VERSION 3.3.3 +set(MQT_CORE_VERSION 3.4.0 CACHE STRING "MQT Core version") -set(MQT_CORE_REV "8c9f6ab24968401e450812fc0ff7d05b5ae07a63" +set(MQT_CORE_REV "6bcc01e7d135058c6439c64fdd5f14b65ab88816" CACHE STRING "MQT Core identifier (tag, branch or commit hash)") set(MQT_CORE_REPO_OWNER "munich-quantum-toolkit" CACHE STRING "MQT Core repository owner (change when using a fork)") # cmake-format: on -if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.24) - # Fetch MQT Core - FetchContent_Declare( - mqt-core - GIT_REPOSITORY https://github.com/${MQT_CORE_REPO_OWNER}/core.git - GIT_TAG ${MQT_CORE_REV}) - list(APPEND FETCH_PACKAGES mqt-core) -else() - find_package(mqt-core ${MQT_CORE_MINIMUM_VERSION} QUIET) - if(NOT mqt-core_FOUND) - FetchContent_Declare( - mqt-core - GIT_REPOSITORY https://github.com/${MQT_CORE_REPO_OWNER}/core.git - GIT_TAG ${MQT_CORE_REV}) - list(APPEND FETCH_PACKAGES mqt-core) - endif() -endif() +FetchContent_Declare( + mqt-core + GIT_REPOSITORY https://github.com/${MQT_CORE_REPO_OWNER}/core.git + GIT_TAG ${MQT_CORE_REV}) +list(APPEND FETCH_PACKAGES mqt-core) # ---------------------------------------------------------------------------------Fetch Eigen3 # cmake-format: off set(EIGEN_VERSION 3.4.0 CACHE STRING "Eigen3 version") # cmake-format: on -if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.24) - # Fetch Eigen3 - FetchContent_Declare( - Eigen3 - GIT_REPOSITORY https://gitlab.com/libeigen/eigen.git - GIT_TAG ${EIGEN_VERSION} - GIT_SHALLOW TRUE) - list(APPEND FETCH_PACKAGES Eigen3) - set(EIGEN_BUILD_TESTING - OFF - CACHE BOOL "Disable testing for Eigen") - set(BUILD_TESTING - OFF - CACHE BOOL "Disable general testing") - set(EIGEN_BUILD_DOC - OFF - CACHE BOOL "Disable documentation build for Eigen") -else() - find_package(Eigen3 ${EIGEN3_VERSION} REQUIRED NO_MODULE) - if(NOT Eigen3_FOUND) - FetchContent_Declare( - Eigen3 - GIT_REPOSITORY https://gitlab.com/libeigen/eigen.git - GIT_TAG ${EIGEN3_VERSION} - GIT_SHALLOW TRUE) - list(APPEND FETCH_PACKAGES Eigen3) - set(EIGEN_BUILD_TESTING - OFF - CACHE BOOL "Disable testing for Eigen") - set(BUILD_TESTING - OFF - CACHE BOOL "Disable general testing") - set(EIGEN_BUILD_DOC - OFF - CACHE BOOL "Disable documentation build for Eigen") - endif() -endif() +FetchContent_Declare( + Eigen3 + GIT_REPOSITORY https://gitlab.com/libeigen/eigen.git + GIT_TAG ${EIGEN_VERSION} + GIT_SHALLOW TRUE) +list(APPEND FETCH_PACKAGES Eigen3) +set(EIGEN_BUILD_TESTING + OFF + CACHE BOOL "Disable testing for Eigen") +set(BUILD_TESTING + OFF + CACHE BOOL "Disable general testing") +set(EIGEN_BUILD_DOC + OFF + CACHE BOOL "Disable documentation build for Eigen") if(BUILD_MQT_DEBUGGER_TESTS) set(gtest_force_shared_crt @@ -105,33 +63,8 @@ if(BUILD_MQT_DEBUGGER_TESTS) 1.17.0 CACHE STRING "Google Test version") set(GTEST_URL https://github.com/google/googletest/archive/refs/tags/v${GTEST_VERSION}.tar.gz) - if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.24) - FetchContent_Declare(googletest URL ${GTEST_URL} FIND_PACKAGE_ARGS ${GTEST_VERSION} NAMES GTest) - list(APPEND FETCH_PACKAGES googletest) - else() - find_package(googletest ${GTEST_VERSION} QUIET NAMES GTest) - if(NOT googletest_FOUND) - FetchContent_Declare(googletest URL ${GTEST_URL}) - list(APPEND FETCH_PACKAGES googletest) - endif() - endif() -endif() - -if(BUILD_MQT_DEBUGGER_BINDINGS) - # add pybind11_json library - if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.24) - FetchContent_Declare( - pybind11_json - GIT_REPOSITORY https://github.com/pybind/pybind11_json - FIND_PACKAGE_ARGS) - list(APPEND FETCH_PACKAGES pybind11_json) - else() - find_package(pybind11_json QUIET) - if(NOT pybind11_json_FOUND) - FetchContent_Declare(pybind11_json GIT_REPOSITORY https://github.com/pybind/pybind11_json) - list(APPEND FETCH_PACKAGES pybind11_json) - endif() - endif() + FetchContent_Declare(googletest URL ${GTEST_URL} FIND_PACKAGE_ARGS ${GTEST_VERSION} NAMES GTest) + list(APPEND FETCH_PACKAGES googletest) endif() # Make all declared dependencies available. diff --git a/include/python/InterfaceBindings.hpp b/include/python/InterfaceBindings.hpp deleted file mode 100644 index a8cf9b33..00000000 --- a/include/python/InterfaceBindings.hpp +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (c) 2024 - 2026 Chair for Design Automation, TUM - * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -/** - * @file InterfaceBindings.hpp - * @brief This file defines methods to be used for defining Python bindings for - * the debugging and diagnostics backends. - */ - -#pragma once - -#include "pybind11/pybind11.h" - -/** - * @brief Binds the main debugging framework to Python. - * @param m The `pybind11` module. - */ -void bindFramework(pybind11::module& m); - -/** - * @brief Binds the diagnostics backend to Python. - * @param m The `pybind11` module. - */ -void bindDiagnostics(pybind11::module& m); diff --git a/include/python/dd/DDSimDebugBindings.hpp b/include/python/dd/DDSimDebugBindings.hpp deleted file mode 100644 index 0f7deb45..00000000 --- a/include/python/dd/DDSimDebugBindings.hpp +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) 2024 - 2026 Chair for Design Automation, TUM - * Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH - * All rights reserved. - * - * SPDX-License-Identifier: MIT - * - * Licensed under the MIT License - */ - -/** - * @file DDSimDebugBindings.hpp - * @brief This file defines methods to be used for defining Python bindings for - * the DD Debugger. - */ - -#pragma once - -#include "pybind11/pybind11.h" - -/** - * @brief Binds the dd debugging backend to Python. - * @param m The `pybind11` module. - */ -void bindBackend(pybind11::module& m); diff --git a/noxfile.py b/noxfile.py index 57f3a49f..8e183ec0 100755 --- a/noxfile.py +++ b/noxfile.py @@ -209,5 +209,54 @@ def docs(session: nox.Session) -> None: ) +@nox.session(reuse_venv=True, venv_backend="uv") +def stubs(session: nox.Session) -> None: + """Generate type stubs for Python bindings using nanobind.""" + env = {"UV_PROJECT_ENVIRONMENT": session.virtualenv.location} + session.run( + "uv", + "sync", + "--no-dev", + "--group", + "build", + env=env, + ) + + package_root = Path(__file__).parent / "python" / "mqt" / "debugger" + pattern_file = Path(__file__).parent / "bindings" / "debugger_patterns.txt" + + session.run( + "python", + "-m", + "nanobind.stubgen", + "--recursive", + "--include-private", + "--output-dir", + str(package_root), + "--pattern-file", + str(pattern_file), + "--module", + "mqt.debugger.pydebugger", + ) + + pyi_files = list(package_root.glob("**/*.pyi")) + + if not pyi_files: + session.warn("No .pyi files found") + return + + if shutil.which("prek") is None: + session.install("prek") + + # Allow both 0 (no issues) and 1 as success codes for fixing up stubs + success_codes = [0, 1] + session.run("prek", "run", "license-tools", "--files", *pyi_files, external=True, success_codes=success_codes) + session.run("prek", "run", "ruff-check", "--files", *pyi_files, external=True, success_codes=success_codes) + session.run("prek", "run", "ruff-format", "--files", *pyi_files, external=True, success_codes=success_codes) + + # Run ruff-check again to ensure everything is clean + session.run("prek", "run", "ruff-check", "--files", *pyi_files, external=True) + + if __name__ == "__main__": nox.main() diff --git a/pyproject.toml b/pyproject.toml index 8500f773..5066f1b2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ [build-system] requires = [ - "pybind11>=3.0.1", + "nanobind>=2.10.2", "setuptools-scm>=9.2.2", "scikit-build-core>=0.11.6", ] @@ -79,6 +79,10 @@ wheel.install-dir = "mqt/debugger" # Explicitly set the package directory wheel.packages = ["python/mqt"] +# Enable Stable ABI builds for CPython 3.12+ +wheel.py-api = "cp312" + +# Set required Ninja version ninja.version = ">=1.10" # Setuptools-style build caching in a local directory @@ -233,7 +237,7 @@ known-first-party = ["mqt.debugger"] "test/python/**" = ["T20", "ANN"] "docs/**" = ["T20"] "noxfile.py" = ["T20", "TID251"] -"*.pyi" = ["D418", "PYI021"] # pydocstyle +"*.pyi" = ["D418", "E501", "PYI021"] "*.ipynb" = [ "D", # pydocstyle "E402", # Allow imports to appear anywhere in Jupyter notebooks @@ -291,7 +295,6 @@ test-skip = [ "cp*-win_arm64", # no numpy, qiskit, ... wheels ] - [tool.cibuildwheel.linux] environment = { DEPLOY = "ON" } @@ -302,10 +305,15 @@ environment = { MACOSX_DEPLOYMENT_TARGET = "11.0" } before-build = "uv pip install delvewheel>=1.11.2" repair-wheel-command = "delvewheel repair -w {dest_dir} {wheel} --namespace-pkg mqt --ignore-existing" +[[tool.cibuildwheel.overrides]] +select = "cp312-*" +inherit.repair-wheel-command = "append" +repair-wheel-command = "uvx abi3audit --strict --report {wheel}" + [dependency-groups] build = [ - "pybind11>=3.0.1", + "nanobind>=2.10.2", "setuptools-scm>=9.2.2", "scikit-build-core>=0.11.6", ] diff --git a/python/mqt/debugger/dap/dap_server.py b/python/mqt/debugger/dap/dap_server.py index 9c176825..e83533c9 100644 --- a/python/mqt/debugger/dap/dap_server.py +++ b/python/mqt/debugger/dap/dap_server.py @@ -426,9 +426,9 @@ def format_error_cause(self, cause: mqt.debugger.ErrorCause) -> str: start_line, _ = self.code_pos_to_coordinates(start_pos) return ( "The qubits never interact with each other. Are you missing a CX gate?" - if cause.type == mqt.debugger.ErrorCauseType.MissingInteraction + if cause.type_ == mqt.debugger.ErrorCauseType.MissingInteraction else f"Control qubit is always zero in line {start_line}." - if cause.type == mqt.debugger.ErrorCauseType.ControlAlwaysZero + if cause.type_ == mqt.debugger.ErrorCauseType.ControlAlwaysZero else "" ) diff --git a/python/mqt/debugger/dap/messages/change_bit_dap_message.py b/python/mqt/debugger/dap/messages/change_bit_dap_message.py index 30a69fc1..fdd63155 100644 --- a/python/mqt/debugger/dap/messages/change_bit_dap_message.py +++ b/python/mqt/debugger/dap/messages/change_bit_dap_message.py @@ -133,7 +133,7 @@ def _apply_change(self, server: DAPServer, name: str) -> bool: msg = f"The variable '{name}' is not a classical bit." raise ValueError(msg) from exc - if variable.type != mqt.debugger.VariableType.VarBool: + if variable.type_ != mqt.debugger.VariableType.VarBool: msg = "Only boolean classical variables can be changed." raise ValueError(msg) diff --git a/python/mqt/debugger/pydebugger.pyi b/python/mqt/debugger/pydebugger.pyi index da9cb22c..b7aa9229 100644 --- a/python/mqt/debugger/pydebugger.pyi +++ b/python/mqt/debugger/pydebugger.pyi @@ -6,21 +6,9 @@ # # Licensed under the MIT License -"""Type stubs for python bindings of the debug module.""" - import enum - -# Enums - -class VariableType(enum.Enum): - """Represents possible types of classical variables.""" - - VarBool = 0 - """A boolean variable.""" - VarInt = 1 - """A 32-bit integer variable.""" - VarFloat = 2 - """A floating-point variable.""" +from collections.abc import Sequence +from typing import overload class Result(enum.Enum): """Represents the result of an operation.""" @@ -31,16 +19,160 @@ class Result(enum.Enum): """Indicates that an error occurred.""" class ErrorCauseType(enum.Enum): - """Represents the type of a potential error cause.""" + """The type of a potential error cause.""" Unknown = 0 """An unknown error cause.""" + MissingInteraction = 1 - """Indicates that an entanglement error may be caused by a missing interaction.""" + """ + Indicates that an entanglement error may be caused by a missing interaction. + """ + ControlAlwaysZero = 2 - """Indicates that an error may be related to a controlled gate with a control that is always zero.""" + """ + Indicates that an error may be related to a controlled gate with a control that is always zero. + """ + +class ErrorCause: + """Represents a potential cause of an assertion error.""" + + def __init__(self) -> None: ... + @property + def instruction(self) -> int: + """The instruction where the error may originate from or where the error can be detected.""" + + @instruction.setter + def instruction(self, arg: int, /) -> None: ... + @property + def type_(self) -> ErrorCauseType: + """The type of the potential error cause.""" + + @type_.setter + def type_(self, arg: ErrorCauseType, /) -> None: ... + +class Diagnostics: + """Provides diagnostics capabilities such as different analysis methods for the debugger.""" + + def __init__(self) -> None: + """Creates a new `Diagnostics` instance.""" + + def init(self) -> None: + """Initializes the diagnostics instance.""" + + def get_num_qubits(self) -> int: + """Get the number of qubits in the system. + + Returns: + The number of qubits in the system. + """ + + def get_instruction_count(self) -> int: + """Get the number of instructions in the code. + + Returns: + The number of instructions in the code. + """ + + def get_data_dependencies(self, instruction: int, include_callers: bool = False) -> list[int]: + """Extract all data dependencies for a given instruction. + + If the instruction is inside a custom gate definition, the data + dependencies will by default not go outside of the custom gate, unless a + new call instruction is found. By setting `include_callers` to `True`, all + possible callers of the custom gate will also be included and further + dependencies outside the custom gate will be taken from there. + + The line itself will also be counted as a dependency. Gate and register + declarations will not. + + This method can be performed without running the program, as it is a static + analysis method. + + Args: + instruction: The instruction to extract the data dependencies for. + include_callers: True, if the data dependencies should include all possible callers of the containing custom gate. Defaults to False. + + Returns: + A list of instruction indices that are data dependencies of the given instruction. + """ + + def get_interactions(self, before_instruction: int, qubit: int) -> list[int]: + """Extract all qubits that interact with a given qubit up to a specific instruction. + + If the target instruction is inside a custom gate definition, the + interactions will only be searched inside the custom gate, unless a new + call instruction is found. + + The qubit itself will also be counted as an interaction. + + This method can be performed without running the program, as it is a static + analysis method. + + Args: + before_instruction: The instruction to extract the interactions up to (excluding). + qubit: The qubit to extract the interactions for. + + Returns: + A list of qubit indices that interact with the given qubit up to the target instruction. + """ + + def get_zero_control_instructions(self) -> list[int]: + """Extract all controlled gates that have been marked as only having controls with value zero. + + This method expects a continuous memory block of booleans with size equal + to the number of instructions. Each element represents an instruction and + will be set to true if the instruction is a controlled gate with only zero + controls. + + This method can only be performed at runtime, as it is a dynamic analysis + method. + + Returns: + The indices of instructions that are controlled gates with only zero controls. + """ + + def potential_error_causes(self) -> list[ErrorCause]: + """Extract a list of potential error causes encountered during execution. + + This method should be run after the program has been executed and reached + an assertion error. + + Returns: + A list of potential error causes encountered during execution. + """ + + def suggest_assertion_movements(self) -> list[tuple[int, int]]: + """Suggest movements of assertions to better positions. + + Each entry of the resulting list consists of the original position of the assertion, followed by its new + suggested position. + + Returns: + A list of moved assertions. + """ + + def suggest_new_assertions(self) -> list[tuple[int, str]]: + """Suggest new assertions to be added to the program. + + Each entry of the resulting list consists of the suggested position for the new assertion, followed by its + string representation. + + Returns: + A list of new assertions. + """ + +class VariableType(enum.Enum): + """The type of a classical variable.""" + + VarBool = 0 + """A boolean variable.""" -# Classes + VarInt = 1 + """An integer variable.""" + + VarFloat = 2 + """A floating-point variable.""" class VariableValue: """Represents the value of a classical variable. @@ -48,95 +180,134 @@ class VariableValue: Only one of these fields has a valid value at a time, based on the variable's `VariableType`. """ - bool_value: bool - """The value of a boolean variable.""" - int_value: int - """The value of a 32-bit integer variable.""" - float_value: float - """The value of a floating-point variable.""" + def __init__(self) -> None: ... + @property + def bool_value(self) -> bool: + """The value of a boolean variable.""" - def __init__(self) -> None: - """Creates a new `VariableValue` instance.""" + @bool_value.setter + def bool_value(self, arg: bool, /) -> None: ... + @property + def int_value(self) -> int: + """The value of an integer variable.""" + + @int_value.setter + def int_value(self, arg: int, /) -> None: ... + @property + def float_value(self) -> float: + """The value of a floating-point variable.""" + + @float_value.setter + def float_value(self, arg: float, /) -> None: ... class Variable: """Represents a classical variable.""" - name: str - """The name of the variable.""" - type: VariableType - """The type of the variable.""" - value: VariableValue - """The value of the variable.""" - def __init__(self) -> None: """Creates a new `Variable` instance.""" + @property + def name(self) -> str: + """The name of the variable.""" + + @name.setter + def name(self, arg: str, /) -> None: ... + @property + def type_(self) -> VariableType: + """The type of the variable.""" + + @type_.setter + def type_(self, arg: VariableType, /) -> None: ... + @property + def value(self) -> VariableValue: + """The value of the variable.""" + + @value.setter + def value(self, arg: VariableValue, /) -> None: ... + class Complex: """Represents a complex number.""" - real: float - """The real part of the complex number.""" - imaginary: float - """The imaginary part of the complex number.""" + @overload + def __init__(self) -> None: + """Initializes a new complex number.""" + @overload def __init__(self, real: float = 0.0, imaginary: float = 0.0) -> None: """Initializes a new complex number. Args: - real (float, optional): The real part of the complex number. Defaults to 0.0. - imaginary (float, optional): The imaginary part of the complex number. Defaults to 0.0. + real: The real part of the complex number. Defaults to 0.0. + imaginary: The imaginary part of the complex number. Defaults to 0.0. """ -class CompilationSettings: - """The settings that should be used to compile an assertion program.""" + @property + def real(self) -> float: + """The real part of the complex number.""" - opt: int - """The optimization level that should be used. Exact meaning depends on the implementation, but typically 0 means no optimization.""" - slice_index: int - """The index of the slice that should be compiled.""" + @real.setter + def real(self, arg: float, /) -> None: ... + @property + def imaginary(self) -> float: + """The imaginary part of the complex number.""" - def __init__(self, opt: int, slice_index: int = 0) -> None: - """Initializes a new set of compilation settings. + @imaginary.setter + def imaginary(self, arg: float, /) -> None: ... - Args: - opt (int): The optimization level that should be used. - slice_index (int, optional): The index of the slice that should be compiled (defaults to 0). +class Statevector: + """Represents a state vector.""" + + def __init__(self) -> None: + """Creates a new `Statevector` instance.""" + + @property + def num_qubits(self) -> int: + """The number of qubits in the state vector.""" + + @num_qubits.setter + def num_qubits(self, arg: int, /) -> None: ... + @property + def num_states(self) -> int: + """The number of states in the state vector. + + This is always equal to 2^`num_qubits`. """ -class LoadResult: - """Represents the result of loading code.""" + @num_states.setter + def num_states(self, arg: int, /) -> None: ... + @property + def amplitudes(self) -> list[Complex]: + """The amplitudes of the state vector. - status: Result - """The result status of the load operation.""" - line: int - """The line number of the error location, or 0 if unknown.""" - column: int - """The column number of the error location, or 0 if unknown.""" - message: str | None - """A human-readable error message, or None if none is available.""" + Contains one element for each of the `num_states` states in the state vector. + """ - def __init__(self) -> None: - """Creates a new `LoadResult` instance.""" + @amplitudes.setter + def amplitudes(self, arg: Sequence[Complex], /) -> None: ... -class Statevector: - """Represents a state vector.""" +class CompilationSettings: + """The settings that should be used to compile an assertion program.""" - num_qubits: int - """The number of qubits in the state vector.""" - num_states: int - """The number of states in the state vector. + def __init__(self, opt: int, slice_index: int = 0) -> None: + """Initializes a new set of compilation settings. - This is always equal to 2^`num_qubits`. - """ + Args: + opt: The optimization level that should be used. + slice_index: The index of the slice that should be compiled (defaults to 0). + """ - amplitudes: list[Complex] - """The amplitudes of the state vector. + @property + def opt(self) -> int: + """The optimization level that should be used. Exact meaning depends on the implementation, but typically 0 means no optimization.""" - Contains one element for each of the `num_states` states in the state vector. - """ + @opt.setter + def opt(self, arg: int, /) -> None: ... + @property + def slice_index(self) -> int: + """The index of the slice that should be compiled.""" - def __init__(self) -> None: - """Creates a new `Statevector` instance.""" + @slice_index.setter + def slice_index(self, arg: int, /) -> None: ... class SimulationState: """Represents the state of a quantum simulation for debugging. @@ -154,7 +325,7 @@ class SimulationState: """Loads the given code into the simulation state. Args: - code (str): The code to load. + code: The code to load. """ def load_code_with_result(self, code: str) -> LoadResult: @@ -189,28 +360,7 @@ class SimulationState: """Runs the simulation until it finishes, even if assertions fail. Returns: - int: The number of assertions that failed during execution. - """ - - def change_classical_variable_value(self, variable_name: str, value: bool | float) -> None: - """Updates the value of a classical variable. - - Args: - variable_name (str): The name of the classical variable to update. - value (bool | float): The desired value. - """ - - def change_amplitude_value(self, basis_state: str, value: Complex) -> None: - """Updates the amplitude of a given computational basis state. - - The basis state is provided as a bitstring whose length matches the - current number of qubits. Implementations are expected to renormalize the - remaining amplitudes so that the state vector stays normalized and to - reject invalid bitstrings or amplitudes that violate normalization. - - Args: - basis_state (str): The bitstring identifying the basis state to update. - value (Complex): The desired complex amplitude. + The number of assertions that failed during execution. """ def run_simulation(self) -> None: @@ -248,7 +398,7 @@ class SimulationState: simulation has not been set up yet. Returns: - bool: True, if the simulation can step forward. + True, if the simulation can step forward. """ def can_step_backward(self) -> bool: @@ -258,7 +408,28 @@ class SimulationState: the simulation has not been set up yet. Returns: - bool: True, if the simulation can step backward. + True, if the simulation can step backward. + """ + + def change_classical_variable_value(self, variable_name: str, new_value: object) -> None: + """Updates the value of a classical variable. + + Args: + variable_name: The name of the classical variable to update. + new_value: The desired value. + """ + + def change_amplitude_value(self, basis_state: str, value: Complex) -> None: + """Updates the amplitude of a given computational basis state. + + The basis state is provided as a bitstring whose length matches the + current number of qubits. Implementations are expected to renormalize the + remaining amplitudes so that the state vector stays normalized and to + reject invalid bitstrings or amplitudes that violate normalization. + + Args: + basis_state: The bitstring identifying the basis state to update. + value: The desired complex amplitude. """ def is_finished(self) -> bool: @@ -267,7 +438,7 @@ class SimulationState: The execution is considered finished if it has reached the end of the code. Returns: - bool: True, if the execution has finished. + True, if the execution has finished. """ def did_assertion_fail(self) -> bool: @@ -277,7 +448,7 @@ class SimulationState: be set to false again. Returns: - bool: True, if an assertion failed during the last step. + True, if an assertion failed during the last step. """ def was_breakpoint_hit(self) -> bool: @@ -287,21 +458,21 @@ class SimulationState: be set to false again. Returns: - bool: True, if a breakpoint was hit during the last step. + True, if a breakpoint was hit during the last step. """ def get_current_instruction(self) -> int: """Gets the current instruction index. Returns: - int: The index of the current instruction. + The index of the current instruction. """ def get_instruction_count(self) -> int: """Gets the number of instructions in the code. Returns: - int: The number of instructions in the code. + The number of instructions in the code. """ def get_instruction_position(self, instruction: int) -> tuple[int, int]: @@ -310,17 +481,17 @@ class SimulationState: Start and end positions are inclusive and white-spaces are ignored. Args: - instruction (int): The instruction to find. + instruction: The instruction to find. Returns: - tuple[int, int]: The start and end positions of the instruction. + The start and end positions of the instruction. """ def get_num_qubits(self) -> int: """Gets the number of qubits used by the program. Returns: - int: The number of qubits used by the program. + The number of qubits used by the program. """ def get_amplitude_index(self, index: int) -> Complex: @@ -330,10 +501,10 @@ class SimulationState: binary representation of the state. Args: - index (int): The index of the state in the full state vector. + index: The index of the state in the full state vector. Returns: - Complex: The complex amplitude of the state. + The complex amplitude of the state. """ def get_amplitude_bitstring(self, bitstring: str) -> Complex: @@ -342,10 +513,10 @@ class SimulationState: The amplitude is selected by a bitstring representing the state. Args: - bitstring (str): The index of the state as a bitstring. + bitstring: The index of the state as a bitstring. Returns: - Complex: The complex amplitude of the state. + The complex amplitude of the state. """ def get_classical_variable(self, name: str) -> Variable: @@ -355,10 +526,10 @@ class SimulationState: in square brackets. Args: - name (str): The name of the variable. + name: The name of the variable. Returns: - Variable: The fetched variable. + The fetched variable. """ def get_num_classical_variables(self) -> int: @@ -367,7 +538,7 @@ class SimulationState: For registers, each index is counted as a separate variable. Returns: - int: The number of classical variables in the simulation. + The number of classical variables in the simulation. """ def get_classical_variable_name(self, index: int) -> str: @@ -378,36 +549,44 @@ class SimulationState: index of the register. Args: - index (int): The index of the variable. + index: The index of the variable. + + Returns: + The name of the variable. + """ + + def get_quantum_variable_name(self, index: int) -> str: + """Gets the name of a quantum variable by its index. + + For registers, each index is counted as a separate variable and can be + accessed separately. This method will return the name of the specific + index of the register. + + Args: + index: The index of the variable. Returns: - str: The name of the variable. + The name of the variable. """ def get_state_vector_full(self) -> Statevector: """Gets the full state vector of the simulation at the current time. - The state vector is expected to be initialized with the correct number of - qubits and allocated space for the amplitudes before calling this method. - Returns: - Statevector: The full state vector of the current simulation state. + The full state vector of the current simulation state. """ - def get_state_vector_sub(self, qubits: list[int]) -> Statevector: + def get_state_vector_sub(self, qubits: Sequence[int]) -> Statevector: """Gets a sub-state of the state vector of the simulation at the current time. - The state vector is expected to be initialized with the correct number of - qubits and allocated space for the amplitudes before calling this method. - This method also supports the re-ordering of qubits, but does not allow qubits to be repeated. Args: - qubits (list[int]): The qubits to include in the sub-state. + qubits: The qubits to include in the sub-state. Returns: - Statevector: The sub-state vector of the current simulation state. + The sub-state vector of the current simulation state. """ def set_breakpoint(self, desired_position: int) -> int: @@ -417,10 +596,10 @@ class SimulationState: string. Args: - desired_position (int): The position in the code to set the breakpoint. + desired_position: The position in the code to set the breakpoint. Returns: - int: The index of the instruction where the breakpoint was set. + The index of the instruction where the breakpoint was set. """ def clear_breakpoints(self) -> None: @@ -432,7 +611,7 @@ class SimulationState: Each custom gate call corresponds to one stack entry. Returns: - int: The current stack depth of the simulation. + The current stack depth of the simulation. """ def get_stack_trace(self, max_depth: int) -> list[int]: @@ -443,160 +622,39 @@ class SimulationState: stack entry. Args: - max_depth (int): The maximum depth of the stack trace. + max_depth: The maximum depth of the stack trace. Returns: - list[int]: The stack trace of the simulation. + The stack trace of the simulation. """ def get_diagnostics(self) -> Diagnostics: """Gets the diagnostics instance employed by this debugger. Returns: - Diagnostics: The diagnostics instance employed by this debugger. + The diagnostics instance employed by this debugger. """ def compile(self, settings: CompilationSettings) -> str: - """Compiles the program in the current state. + """Compiles the given code into a quantum circuit without assertions. Args: - settings (CompilationSettings): The settings to use for the compilation. - - Returns: - str: The compiled code. - """ - -class ErrorCause: - """Represents a potential cause of an assertion error.""" - - instruction: int - """The instruction where the error may originate from or where the error can be detected.""" - type: ErrorCauseType - """The type of the potential error cause.""" - - def __init__(self) -> None: - """Creates a new `ErrorCause` instance.""" - -class Diagnostics: - """Provides diagnostics capabilities such as different analysis methods for the debugger.""" - def __init__(self) -> None: - """Creates a new `Diagnostics` instance.""" - - def init(self) -> None: - """Initializes the diagnostics instance.""" - - def get_num_qubits(self) -> int: - """Get the number of qubits in the system. - - Returns: - int: The number of qubits in the system. - """ - - def get_instruction_count(self) -> int: - """Get the number of instructions in the code. - - Returns: - int: The number of instructions in the code. - """ - - def get_data_dependencies(self, instruction: int, include_callers: bool = False) -> list[int]: - """Extract all data dependencies for a given instruction. - - If the instruction is inside a custom gate definition, the data - dependencies will by default not go outside of the custom gate, unless a - new call instruction is found. By setting `include_callers` to `True`, all - possible callers of the custom gate will also be included and further - dependencies outside the custom gate will be taken from there. - - The line itself will also be counted as a dependency. Gate and register - declarations will not. - - This method can be performed without running the program, as it is a static - analysis method. - - Args: - instruction (int): The instruction to extract the data dependencies for. - include_callers (bool, optional): True, if the data dependencies should include all possible callers of the containing custom gate. Defaults to False. - - Returns: - list[int]: A list of instruction indices that are data dependencies of the given instruction. - """ - - def get_interactions(self, before_instruction: int, qubit: int) -> list[int]: - """Extract all qubits that interact with a given qubit up to a specific instruction. - - If the target instruction is inside a custom gate definition, the - interactions will only be searched inside the custom gate, unless a new - call instruction is found. - - The qubit itself will also be counted as an interaction. - - This method can be performed without running the program, as it is a static - analysis method. - - Args: - before_instruction (int): The instruction to extract the interactions up to (excluding). - qubit (int): The qubit to extract the interactions for. - - Returns: - list[int]: A list of qubit indices that interact with the given qubit up to the target instruction. - """ - - def get_zero_control_instructions(self) -> list[int]: - """Extract all controlled gates that have been marked as only having controls with value zero. - - This method expects a continuous memory block of booleans with size equal - to the number of instructions. Each element represents an instruction and - will be set to true if the instruction is a controlled gate with only zero - controls. - - This method can only be performed at runtime, as it is a dynamic analysis - method. - - Returns: - list[int]: The indices of instructions that are controlled gates with only zero controls. - """ - - def potential_error_causes(self) -> list[ErrorCause]: - """Extract a list of potential error causes encountered during execution. - - This method should be run after the program has been executed and reached - an assertion error. - - Returns: - list[ErrorCause]: A list of potential error causes encountered during execution. - """ - - def suggest_assertion_movements(self) -> tuple[int, int]: - """Suggest movements of assertions to better positions. - - Each entry of the resulting list consists of the original position of the assertion, followed by its new - suggested position. - - Returns: - list[tuple[int, int]]: A list of moved assertions. - """ - - def suggest_new_assertions(self) -> tuple[int, str]: - """Suggest new assertions to be added to the program. - - Each entry of the resulting list consists of the suggested position for the new assertion, followed by its - string representation. + settings: The settings to use for the compilation. Returns: - list[tupke[int, str]]: A list of new assertions. + The compiled code. """ def create_ddsim_simulation_state() -> SimulationState: """Creates a new `SimulationState` instance using the DD backend for simulation and the OpenQASM language as input format. Returns: - SimulationState: The created simulation state. + The created simulation state. """ def destroy_ddsim_simulation_state(state: SimulationState) -> None: """Delete a given DD-based `SimulationState` instance and free up resources. Args: - state (SimulationState): The simulation state to delete. + state: The simulation state to delete. """ diff --git a/test/python/test_diagnosis.py b/test/python/test_diagnosis.py index 122d4117..2341b197 100644 --- a/test/python/test_diagnosis.py +++ b/test/python/test_diagnosis.py @@ -67,10 +67,10 @@ def test_control_always_zero() -> None: assert len(causes) == 2 - assert causes[0].type == ErrorCauseType.ControlAlwaysZero + assert causes[0].type_ == ErrorCauseType.ControlAlwaysZero assert causes[0].instruction == 3 - assert causes[1].type == ErrorCauseType.ControlAlwaysZero + assert causes[1].type_ == ErrorCauseType.ControlAlwaysZero assert causes[1].instruction == 12 @@ -78,10 +78,10 @@ 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] + 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] + 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_python_bindings.py b/test/python/test_python_bindings.py index 14dc7b56..d7f5a3f2 100644 --- a/test/python/test_python_bindings.py +++ b/test/python/test_python_bindings.py @@ -322,9 +322,9 @@ def test_classical_get(simulation_instance_classical: SimulationInstance) -> Non 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.debugger.VariableType.VarBool - assert simulation_state.get_classical_variable("c[1]").type == mqt.debugger.VariableType.VarBool - assert simulation_state.get_classical_variable("c[2]").type == mqt.debugger.VariableType.VarBool + assert simulation_state.get_classical_variable("c[0]").type_ == mqt.debugger.VariableType.VarBool + assert simulation_state.get_classical_variable("c[1]").type_ == mqt.debugger.VariableType.VarBool + assert simulation_state.get_classical_variable("c[2]").type_ == mqt.debugger.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 diff --git a/uv.lock b/uv.lock index e97940ad..dff76829 100644 --- a/uv.lock +++ b/uv.lock @@ -1449,13 +1449,13 @@ check = [ [package.dev-dependencies] build = [ - { name = "pybind11" }, + { name = "nanobind" }, { name = "scikit-build-core" }, { name = "setuptools-scm" }, ] dev = [ + { name = "nanobind" }, { name = "nox" }, - { name = "pybind11" }, { name = "pytest" }, { name = "pytest-console-scripts" }, { name = "pytest-cov" }, @@ -1503,13 +1503,13 @@ provides-extras = ["check"] [package.metadata.requires-dev] build = [ - { name = "pybind11", specifier = ">=3.0.1" }, + { name = "nanobind", specifier = ">=2.10.2" }, { name = "scikit-build-core", specifier = ">=0.11.6" }, { name = "setuptools-scm", specifier = ">=9.2.2" }, ] dev = [ + { name = "nanobind", specifier = ">=2.10.2" }, { name = "nox", specifier = ">=2025.11.12" }, - { name = "pybind11", specifier = ">=3.0.1" }, { name = "pytest", specifier = ">=9.0.1" }, { name = "pytest-console-scripts", specifier = ">=1.4.1" }, { name = "pytest-cov", specifier = ">=7.0.0" }, @@ -1585,6 +1585,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5f/df/76d0321c3797b54b60fef9ec3bd6f4cfd124b9e422182156a1dd418722cf/myst_parser-4.0.1-py3-none-any.whl", hash = "sha256:9134e88959ec3b5780aedf8a99680ea242869d012e8821db3126d427edc9c95d", size = 84579, upload-time = "2025-02-12T10:53:02.078Z" }, ] +[[package]] +name = "nanobind" +version = "2.10.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d1/7b/818fe4f6d1fdd516a14386ba86f2cbbac1b7304930da0f029724e9001658/nanobind-2.10.2.tar.gz", hash = "sha256:08509910ce6d1fadeed69cb0880d4d4fcb77739c6af9bd8fb4419391a3ca4c6b", size = 993651, upload-time = "2025-12-10T10:55:32.335Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/06/cb08965f985a5e1b9cb55ed96337c1f6daaa6b9cbdaeabe6bb3f7a1a11df/nanobind-2.10.2-py3-none-any.whl", hash = "sha256:6976c1b04b90481d2612b346485a3063818c6faa5077fe9d8bbc9b5fbe29c380", size = 246514, upload-time = "2025-12-10T10:55:30.741Z" }, +] + [[package]] name = "nbclient" version = "0.10.4" @@ -2100,15 +2109,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", size = 11842, upload-time = "2024-07-21T12:58:20.04Z" }, ] -[[package]] -name = "pybind11" -version = "3.0.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2f/7b/a6d8dcb83c457e24a9df1e4d8fd5fb8034d4bbc62f3c324681e8a9ba57c2/pybind11-3.0.1.tar.gz", hash = "sha256:9c0f40056a016da59bab516efb523089139fcc6f2ba7e4930854c61efb932051", size = 546914, upload-time = "2025-08-22T20:09:27.265Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cd/8a/37362fc2b949d5f733a8b0f2ff51ba423914cabefe69f1d1b6aab710f5fe/pybind11-3.0.1-py3-none-any.whl", hash = "sha256:aa8f0aa6e0a94d3b64adfc38f560f33f15e589be2175e103c0a33c6bce55ee89", size = 293611, upload-time = "2025-08-22T20:09:25.235Z" }, -] - [[package]] name = "pybtex" version = "0.25.1" From 03920d7bf8a4a4428d5e7ccc85a6596a880808ec Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Thu, 15 Jan 2026 22:03:17 +0100 Subject: [PATCH 103/145] fix issue --- bindings/InterfaceBindings.cpp | 25 +++++++++++++++++++++++++ python/mqt/debugger/dap/dap_server.py | 2 +- python/mqt/debugger/pydebugger.pyi | 26 ++++++++++++++++++++++++++ 3 files changed, 52 insertions(+), 1 deletion(-) diff --git a/bindings/InterfaceBindings.cpp b/bindings/InterfaceBindings.cpp index ac1a052a..b5504d44 100644 --- a/bindings/InterfaceBindings.cpp +++ b/bindings/InterfaceBindings.cpp @@ -62,6 +62,31 @@ struct StatevectorCPP { // NOLINTNEXTLINE(misc-use-internal-linkage) void bindFramework(nb::module_& m) { + // Bind the Result enum + nb::enum_(m, "Result", "Represents the result of an operation.") + .value("OK", OK, "Indicates that the operation was successful.") + .value("ERROR", ERROR, "Indicates that an error occurred."); + + // Bind the LoadResult struct + nb::class_(m, "LoadResult") + .def(nb::init<>()) + .def_rw("status", &LoadResult::status, + "Indicates whether the load was successful.") + .def_rw("line", &LoadResult::line, + "The line number of the error location, or 0 if unknown.") + .def_rw("column", &LoadResult::column, + "The column number of the error location, or 0 if unknown.") + .def_prop_ro( + "message", + [](const LoadResult& self) { + if (self.message == nullptr) { + return nb::none(); + } + return nb::cast(std::string(self.message)); + }, + "A human-readable error message, or None if none is available.") + .doc() = "The result of a code loading operation."; + // Bind the VariableType enum nb::enum_(m, "VariableType", "The type of a classical variable.") diff --git a/python/mqt/debugger/dap/dap_server.py b/python/mqt/debugger/dap/dap_server.py index e83533c9..9646c4b6 100644 --- a/python/mqt/debugger/dap/dap_server.py +++ b/python/mqt/debugger/dap/dap_server.py @@ -449,7 +449,7 @@ def collect_highlight_entries( for cause in error_causes: message = self.format_error_cause(cause) - reason = self._format_highlight_reason(cause.type) + reason = self._format_highlight_reason(cause.type_) entry = self._build_highlight_entry(cause.instruction, reason, message) if entry is not None: highlights.append(entry) diff --git a/python/mqt/debugger/pydebugger.pyi b/python/mqt/debugger/pydebugger.pyi index b7aa9229..27a2191f 100644 --- a/python/mqt/debugger/pydebugger.pyi +++ b/python/mqt/debugger/pydebugger.pyi @@ -18,6 +18,32 @@ class Result(enum.Enum): ERROR = 1 """Indicates that an error occurred.""" +class LoadResult: + """The result of a code loading operation.""" + + def __init__(self) -> None: ... + @property + def status(self) -> Result: + """Indicates whether the load was successful.""" + + @status.setter + def status(self, arg: Result, /) -> None: ... + @property + def line(self) -> int: + """The line number of the error location, or 0 if unknown.""" + + @line.setter + def line(self, arg: int, /) -> None: ... + @property + def column(self) -> int: + """The column number of the error location, or 0 if unknown.""" + + @column.setter + def column(self, arg: int, /) -> None: ... + @property + def message(self) -> str | None: + """A human-readable error message, or None if none is available.""" + class ErrorCauseType(enum.Enum): """The type of a potential error cause.""" From efbcd5cd661e75a9b596d23345c020e2aaee3daf Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Thu, 15 Jan 2026 22:14:00 +0100 Subject: [PATCH 104/145] Cl issue fix --- bindings/InterfaceBindings.cpp | 3 ++- python/mqt/debugger/pydebugger.pyi | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/bindings/InterfaceBindings.cpp b/bindings/InterfaceBindings.cpp index b5504d44..b2c5894c 100644 --- a/bindings/InterfaceBindings.cpp +++ b/bindings/InterfaceBindings.cpp @@ -222,10 +222,11 @@ Contains one element for each of the `num_states` states in the state vector.)") } return result; }, + "code"_a, R"(Loads the given code and returns details about any errors. Args: - code (str): The code to load. + code: The code to load. Returns: LoadResult: The result of the load operation.)") diff --git a/python/mqt/debugger/pydebugger.pyi b/python/mqt/debugger/pydebugger.pyi index 27a2191f..19311d58 100644 --- a/python/mqt/debugger/pydebugger.pyi +++ b/python/mqt/debugger/pydebugger.pyi @@ -358,7 +358,7 @@ class SimulationState: """Loads the given code and returns details about any errors. Args: - code (str): The code to load. + code: The code to load. Returns: LoadResult: The result of the load operation. From 160c29459b70cbe949ad827cb0b11c53b06485ea Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Thu, 15 Jan 2026 22:34:58 +0100 Subject: [PATCH 105/145] Code rabbit issue --- python/mqt/debugger/dap/dap_server.py | 25 +++++++++++++------ .../messages/highlight_error_dap_message.py | 2 +- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/python/mqt/debugger/dap/dap_server.py b/python/mqt/debugger/dap/dap_server.py index 9646c4b6..ad00c014 100644 --- a/python/mqt/debugger/dap/dap_server.py +++ b/python/mqt/debugger/dap/dap_server.py @@ -115,6 +115,7 @@ def __init__(self, host: str = "127.0.0.1", port: int = 4711) -> None: self.lines_start_at_one = True self.columns_start_at_one = True self.pending_highlights: list[dict[str, Any]] = [] + self.source_file = {"name": "", "path": ""} self._prevent_exit = False def start(self) -> None: @@ -259,10 +260,17 @@ def handle_client(self, connection: socket.socket) -> None: event_payload = json.dumps(e.encode()) send_message(event_payload, connection) if self.pending_highlights: - highlight_event = mqt.debugger.dap.messages.HighlightError(self.pending_highlights, self.source_file) - send_message(json.dumps(highlight_event.encode()), connection) - self.pending_highlights = [] - self._prevent_exit = True + try: + highlight_event = mqt.debugger.dap.messages.HighlightError( + self.pending_highlights, + self.source_file, + ) + send_message(json.dumps(highlight_event.encode()), connection) + self._prevent_exit = True + except (TypeError, ValueError): + pass + finally: + self.pending_highlights = [] self.regular_checks(connection) def regular_checks(self, connection: socket.socket) -> None: @@ -360,9 +368,12 @@ def handle_assertion_fail(self, connection: socket.socket) -> None: ) highlight_entries = self.collect_highlight_entries(current_instruction, error_causes) if highlight_entries: - highlight_event = mqt.debugger.dap.messages.HighlightError(highlight_entries, self.source_file) - send_message(json.dumps(highlight_event.encode()), connection) - self._prevent_exit = True + try: + highlight_event = mqt.debugger.dap.messages.HighlightError(highlight_entries, self.source_file) + send_message(json.dumps(highlight_event.encode()), connection) + self._prevent_exit = True + except (TypeError, ValueError): + pass def code_pos_to_coordinates(self, pos: int) -> tuple[int, int]: """Helper method to convert a code position to line and column. diff --git a/python/mqt/debugger/dap/messages/highlight_error_dap_message.py b/python/mqt/debugger/dap/messages/highlight_error_dap_message.py index 8122d78e..d6525c1e 100644 --- a/python/mqt/debugger/dap/messages/highlight_error_dap_message.py +++ b/python/mqt/debugger/dap/messages/highlight_error_dap_message.py @@ -144,7 +144,7 @@ def _normalize_position(position: Mapping[str, Any] | None) -> dict[str, int]: try: line = int(position["line"]) column = int(position["column"]) - except KeyError as exc: + except (KeyError, TypeError, ValueError) as exc: msg = "Highlight positions require 'line' and 'column'." raise ValueError(msg) from exc return { From 70897c14249a0ec5483eb3dcc99b98cbbdc69795 Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Thu, 15 Jan 2026 22:48:08 +0100 Subject: [PATCH 106/145] Cl fix bug --- python/mqt/debugger/pydebugger.pyi | 69 +++++++++++++++--------------- 1 file changed, 35 insertions(+), 34 deletions(-) diff --git a/python/mqt/debugger/pydebugger.pyi b/python/mqt/debugger/pydebugger.pyi index 19311d58..afa3224e 100644 --- a/python/mqt/debugger/pydebugger.pyi +++ b/python/mqt/debugger/pydebugger.pyi @@ -10,40 +10,6 @@ import enum from collections.abc import Sequence from typing import overload -class Result(enum.Enum): - """Represents the result of an operation.""" - - OK = 0 - """Indicates that the operation was successful.""" - ERROR = 1 - """Indicates that an error occurred.""" - -class LoadResult: - """The result of a code loading operation.""" - - def __init__(self) -> None: ... - @property - def status(self) -> Result: - """Indicates whether the load was successful.""" - - @status.setter - def status(self, arg: Result, /) -> None: ... - @property - def line(self) -> int: - """The line number of the error location, or 0 if unknown.""" - - @line.setter - def line(self, arg: int, /) -> None: ... - @property - def column(self) -> int: - """The column number of the error location, or 0 if unknown.""" - - @column.setter - def column(self, arg: int, /) -> None: ... - @property - def message(self) -> str | None: - """A human-readable error message, or None if none is available.""" - class ErrorCauseType(enum.Enum): """The type of a potential error cause.""" @@ -188,6 +154,41 @@ class Diagnostics: A list of new assertions. """ +class Result(enum.Enum): + """Represents the result of an operation.""" + + OK = 0 + """Indicates that the operation was successful.""" + + ERROR = 1 + """Indicates that an error occurred.""" + +class LoadResult: + """The result of a code loading operation.""" + + def __init__(self) -> None: ... + @property + def status(self) -> Result: + """Indicates whether the load was successful.""" + + @status.setter + def status(self, arg: Result, /) -> None: ... + @property + def line(self) -> int: + """The line number of the error location, or 0 if unknown.""" + + @line.setter + def line(self, arg: int, /) -> None: ... + @property + def column(self) -> int: + """The column number of the error location, or 0 if unknown.""" + + @column.setter + def column(self, arg: int, /) -> None: ... + @property + def message(self) -> object: + """A human-readable error message, or None if none is available.""" + class VariableType(enum.Enum): """The type of a classical variable.""" From 4695b8935e6e08eea8d959d09df1ea9f58eda3bb Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Thu, 15 Jan 2026 23:02:27 +0100 Subject: [PATCH 107/145] Cl fix bug 2 --- python/mqt/debugger/dap/messages/launch_dap_message.py | 2 +- python/mqt/debugger/dap/messages/restart_dap_message.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/python/mqt/debugger/dap/messages/launch_dap_message.py b/python/mqt/debugger/dap/messages/launch_dap_message.py index 8f20f72a..953fe68c 100644 --- a/python/mqt/debugger/dap/messages/launch_dap_message.py +++ b/python/mqt/debugger/dap/messages/launch_dap_message.py @@ -75,7 +75,7 @@ def handle(self, server: DAPServer) -> dict[str, Any]: parsed_successfully = False line = load_result.line if load_result.line > 0 else None column = load_result.column if load_result.column > 0 else None - message = load_result.message or "" + message = str(load_result.message or "") server.queue_parse_error(message, line, column) if parsed_successfully and not self.stop_on_entry: server.simulation_state.run_simulation() diff --git a/python/mqt/debugger/dap/messages/restart_dap_message.py b/python/mqt/debugger/dap/messages/restart_dap_message.py index 3262a410..fa943c44 100644 --- a/python/mqt/debugger/dap/messages/restart_dap_message.py +++ b/python/mqt/debugger/dap/messages/restart_dap_message.py @@ -76,7 +76,7 @@ def handle(self, server: DAPServer) -> dict[str, Any]: parsed_successfully = False line = load_result.line if load_result.line > 0 else None column = load_result.column if load_result.column > 0 else None - message = load_result.message or "" + message = str(load_result.message or "") server.queue_parse_error(message, line, column) if parsed_successfully and not self.stop_on_entry: server.simulation_state.run_simulation() From 9349194633121af4d6112dec3776f45bbeba43b2 Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Wed, 21 Jan 2026 16:13:20 +0100 Subject: [PATCH 108/145] fix loadResult --- bindings/InterfaceBindings.cpp | 33 +------- include/backend/dd/DDSimDebug.hpp | 21 +---- include/backend/debug.h | 21 +---- python/mqt/debugger/check/run_preparation.py | 5 +- .../dap/messages/launch_dap_message.py | 2 +- .../dap/messages/restart_dap_message.py | 2 +- python/mqt/debugger/pydebugger.pyi | 9 +- src/backend/dd/DDSimDebug.cpp | 82 +++++++------------ src/frontend/cli/CliFrontEnd.cpp | 7 +- test/test_simulation.cpp | 3 +- test/utils/common_fixtures.hpp | 7 +- 11 files changed, 48 insertions(+), 144 deletions(-) diff --git a/bindings/InterfaceBindings.cpp b/bindings/InterfaceBindings.cpp index b2c5894c..161ee072 100644 --- a/bindings/InterfaceBindings.cpp +++ b/bindings/InterfaceBindings.cpp @@ -189,42 +189,11 @@ Contains one element for each of the `num_states` states in the state vector.)") .def( "load_code", [](SimulationState* self, const char* code) { - const Result result = self->loadCode(self, code); - if (result != OK) { - const char* messagePtr = self->getLastErrorMessage - ? self->getLastErrorMessage(self) - : nullptr; - std::string message = messagePtr ? messagePtr : ""; - if (message.empty()) { - message = "An error occurred while executing the operation"; - } - throw std::runtime_error(message); - } + return self->loadCode(self, code); }, "code"_a, R"(Loads the given code into the simulation state. -Args: - code: The code to load.)") - .def( - "load_code_with_result", - [](SimulationState* self, const char* code) { - if (self->loadCodeWithResult != nullptr) { - return self->loadCodeWithResult(self, code); - } - LoadResult result{OK, 0, 0, nullptr}; - const Result status = self->loadCode(self, code); - result.status = status; - if (status != OK) { - result.message = self->getLastErrorMessage - ? self->getLastErrorMessage(self) - : nullptr; - } - return result; - }, - "code"_a, - R"(Loads the given code and returns details about any errors. - Args: code: The code to load. diff --git a/include/backend/dd/DDSimDebug.hpp b/include/backend/dd/DDSimDebug.hpp index f50a37c5..94e195f2 100644 --- a/include/backend/dd/DDSimDebug.hpp +++ b/include/backend/dd/DDSimDebug.hpp @@ -119,18 +119,6 @@ struct DDSimulationState { * @brief The code being executed, after preprocessing. */ std::string processedCode; - /** - * @brief The last error message produced by the interface. - */ - std::string lastErrorMessage; - /** - * @brief The last load error message without location prefixes. - */ - std::string lastLoadErrorDetail; - /** - * @brief The last load result produced by the interface. - */ - LoadResult lastLoadResult; /** * @brief Indicates whether the debugger is ready to start simulation. */ @@ -289,16 +277,9 @@ Result ddsimInit(SimulationState* self); * @brief Loads the given code into the simulation state. * @param self The instance to load the code into. * @param code The code to load. - * @return The result of the operation. - */ -Result ddsimLoadCode(SimulationState* self, const char* code); -/** - * @brief Loads the given code into the simulation state and returns details. - * @param self The instance to load the code into. - * @param code The code to load. * @return The result of the load operation. */ -LoadResult ddsimLoadCodeWithResult(SimulationState* self, const char* code); +LoadResult ddsimLoadCode(SimulationState* self, const char* code); /** * @brief Steps the simulation forward by one instruction. * @param self The instance to step forward. diff --git a/include/backend/debug.h b/include/backend/debug.h index 0a89fcaa..b3840132 100644 --- a/include/backend/debug.h +++ b/include/backend/debug.h @@ -50,28 +50,9 @@ struct SimulationStateStruct { * @brief Loads the given code into the simulation state. * @param self The instance to load the code into. * @param code The code to load. - * @return The result of the operation. - */ - Result (*loadCode)(SimulationState* self, const char* code); - - /** - * @brief Loads the given code and returns detailed information about errors. - * @param self The instance to load the code into. - * @param code The code to load. * @return The result of the load operation. */ - LoadResult (*loadCodeWithResult)(SimulationState* self, const char* code); - - /** - * @brief Gets the last error message from the interface. - * - * The returned pointer is owned by the implementation and remains valid - * until the next interface call that modifies the error state. - * - * @param self The instance to query. - * @return A null-terminated error message, or nullptr if none is available. - */ - const char* (*getLastErrorMessage)(SimulationState* self); + LoadResult (*loadCode)(SimulationState* self, const char* code); /** * @brief Steps the simulation forward by one instruction. diff --git a/python/mqt/debugger/check/run_preparation.py b/python/mqt/debugger/check/run_preparation.py index e3da834f..a605970f 100644 --- a/python/mqt/debugger/check/run_preparation.py +++ b/python/mqt/debugger/check/run_preparation.py @@ -44,7 +44,10 @@ def start_compilation(code: Path, output_dir: Path) -> None: state = dbg.create_ddsim_simulation_state() with code.open("r", encoding="utf-8") as f: code_str = f.read() - state.load_code(code_str) + load_result = state.load_code(code_str) + if load_result.status != dbg.Result.OK: + message = load_result.message or "Error loading code" + raise RuntimeError(message) i = 0 while True: i += 1 diff --git a/python/mqt/debugger/dap/messages/launch_dap_message.py b/python/mqt/debugger/dap/messages/launch_dap_message.py index 953fe68c..088b6a34 100644 --- a/python/mqt/debugger/dap/messages/launch_dap_message.py +++ b/python/mqt/debugger/dap/messages/launch_dap_message.py @@ -70,7 +70,7 @@ def handle(self, server: DAPServer) -> dict[str, Any]: parsed_successfully = True code = program_path.read_text(encoding=locale.getpreferredencoding(False)) server.source_code = code - load_result = server.simulation_state.load_code_with_result(code) + load_result = server.simulation_state.load_code(code) if load_result.status != mqt.debugger.Result.OK: parsed_successfully = False line = load_result.line if load_result.line > 0 else None diff --git a/python/mqt/debugger/dap/messages/restart_dap_message.py b/python/mqt/debugger/dap/messages/restart_dap_message.py index fa943c44..52f0436e 100644 --- a/python/mqt/debugger/dap/messages/restart_dap_message.py +++ b/python/mqt/debugger/dap/messages/restart_dap_message.py @@ -71,7 +71,7 @@ def handle(self, server: DAPServer) -> dict[str, Any]: parsed_successfully = True code = program_path.read_text(encoding=locale.getpreferredencoding(False)) server.source_code = code - load_result = server.simulation_state.load_code_with_result(code) + load_result = server.simulation_state.load_code(code) if load_result.status != mqt.debugger.Result.OK: parsed_successfully = False line = load_result.line if load_result.line > 0 else None diff --git a/python/mqt/debugger/pydebugger.pyi b/python/mqt/debugger/pydebugger.pyi index afa3224e..12df9bd3 100644 --- a/python/mqt/debugger/pydebugger.pyi +++ b/python/mqt/debugger/pydebugger.pyi @@ -348,16 +348,9 @@ class SimulationState: def init(self) -> None: """Initializes the simulation state.""" - def load_code(self, code: str) -> None: + def load_code(self, code: str) -> LoadResult: """Loads the given code into the simulation state. - Args: - code: The code to load. - """ - - def load_code_with_result(self, code: str) -> LoadResult: - """Loads the given code and returns details about any errors. - Args: code: The code to load. diff --git a/src/backend/dd/DDSimDebug.cpp b/src/backend/dd/DDSimDebug.cpp index 36682691..15eed42a 100644 --- a/src/backend/dd/DDSimDebug.cpp +++ b/src/backend/dd/DDSimDebug.cpp @@ -193,14 +193,6 @@ ParsedLoadError parseLoadErrorMessage(const std::string& message) { return {.line = line, .column = column, .detail = detail}; } -const char* ddsimGetLastErrorMessage(SimulationState* self) { - const auto* ddsim = toDDSimulationState(self); - if (ddsim->lastErrorMessage.empty()) { - return nullptr; - } - return ddsim->lastErrorMessage.c_str(); -} - /** * @brief Generate a random number between 0 and 1. * @@ -378,8 +370,16 @@ bool checkAssertionEqualityCircuit( DDSimulationState secondSimulation; createDDSimulationState(&secondSimulation); - secondSimulation.interface.loadCode(&secondSimulation.interface, - assertion->getCircuitCode().c_str()); + const auto loadResult = secondSimulation.interface.loadCode( + &secondSimulation.interface, assertion->getCircuitCode().c_str()); + if (loadResult.status != OK) { + const char* message = loadResult.message; + destroyDDSimulationState(&secondSimulation); + throw std::runtime_error( + message && *message != '\0' + ? message + : "Failed to load circuit for equality assertion."); + } if (!secondSimulation.assertionInstructions.empty()) { destroyDDSimulationState(&secondSimulation); throw std::runtime_error( @@ -639,8 +639,6 @@ Result createDDSimulationState(DDSimulationState* self) { self->interface.init = ddsimInit; self->interface.loadCode = ddsimLoadCode; - self->interface.loadCodeWithResult = ddsimLoadCodeWithResult; - self->interface.getLastErrorMessage = ddsimGetLastErrorMessage; self->interface.stepForward = ddsimStepForward; self->interface.stepBackward = ddsimStepBackward; self->interface.stepOverForward = ddsimStepOverForward; @@ -700,10 +698,6 @@ Result ddsimInit(SimulationState* self) { ddsim->breakpoints.clear(); ddsim->lastFailedAssertion = -1ULL; ddsim->lastMetBreakpoint = -1ULL; - ddsim->lastErrorMessage.clear(); - ddsim->lastLoadErrorDetail.clear(); - ddsim->lastLoadResult = { - .status = OK, .line = 0, .column = 0, .message = nullptr}; destroyDDDiagnostics(&ddsim->diagnostics); createDDDiagnostics(&ddsim->diagnostics, ddsim); @@ -715,7 +709,8 @@ Result ddsimInit(SimulationState* self) { return OK; } -Result ddsimLoadCode(SimulationState* self, const char* code) { +LoadResult ddsimLoadCode(SimulationState* self, const char* code) { + static thread_local std::string lastLoadErrorDetail; auto* ddsim = toDDSimulationState(self); ddsim->currentInstruction = 0; ddsim->previousInstructionStack.clear(); @@ -738,10 +733,6 @@ Result ddsimLoadCode(SimulationState* self, const char* code) { ddsim->functionCallers.clear(); ddsim->targetQubits.clear(); ddsim->instructionObjects.clear(); - ddsim->lastErrorMessage.clear(); - ddsim->lastLoadErrorDetail.clear(); - ddsim->lastLoadResult = { - .status = OK, .line = 0, .column = 0, .message = nullptr}; try { std::stringstream ss{preprocessAssertionCode(code, ddsim)}; @@ -749,29 +740,24 @@ Result ddsimLoadCode(SimulationState* self, const char* code) { ddsim->qc = std::make_unique(imported); qc::CircuitOptimizer::flattenOperations(*ddsim->qc, true); } catch (const std::exception& e) { - ddsim->lastErrorMessage = e.what(); - if (ddsim->lastErrorMessage.empty()) { - ddsim->lastErrorMessage = - "An error occurred while executing the operation"; - } - const auto parsed = parseLoadErrorMessage(ddsim->lastErrorMessage); - ddsim->lastLoadErrorDetail = parsed.detail; - ddsim->lastLoadResult = {.status = ERROR, - .line = parsed.line, - .column = parsed.column, - .message = - ddsim->lastLoadErrorDetail.empty() - ? nullptr - : ddsim->lastLoadErrorDetail.c_str()}; - return ERROR; + std::string message = e.what(); + if (message.empty()) { + message = "An error occurred while executing the operation"; + } + const auto parsed = parseLoadErrorMessage(message); + lastLoadErrorDetail = parsed.detail; + return {.status = ERROR, + .line = parsed.line, + .column = parsed.column, + .message = lastLoadErrorDetail.empty() + ? nullptr + : lastLoadErrorDetail.c_str()}; } catch (...) { - ddsim->lastErrorMessage = "An error occurred while executing the operation"; - ddsim->lastLoadErrorDetail = ddsim->lastErrorMessage; - ddsim->lastLoadResult = {.status = ERROR, - .line = 0, - .column = 0, - .message = ddsim->lastLoadErrorDetail.c_str()}; - return ERROR; + lastLoadErrorDetail = "An error occurred while executing the operation"; + return {.status = ERROR, + .line = 0, + .column = 0, + .message = lastLoadErrorDetail.c_str()}; } ddsim->iterator = ddsim->qc->begin(); @@ -782,16 +768,8 @@ Result ddsimLoadCode(SimulationState* self, const char* code) { resetSimulationState(ddsim); ddsim->ready = true; - ddsim->lastLoadResult = { - .status = OK, .line = 0, .column = 0, .message = nullptr}; - - return OK; -} -LoadResult ddsimLoadCodeWithResult(SimulationState* self, const char* code) { - ddsimLoadCode(self, code); - auto* ddsim = toDDSimulationState(self); - return ddsim->lastLoadResult; + return {.status = OK, .line = 0, .column = 0, .message = nullptr}; } Result ddsimChangeClassicalVariableValue(SimulationState* self, diff --git a/src/frontend/cli/CliFrontEnd.cpp b/src/frontend/cli/CliFrontEnd.cpp index ede8b6d3..00d8a06e 100644 --- a/src/frontend/cli/CliFrontEnd.cpp +++ b/src/frontend/cli/CliFrontEnd.cpp @@ -68,11 +68,8 @@ void CliFrontEnd::run(const char* code, SimulationState* state) { std::string command; const auto result = state->loadCode(state, code); state->resetSimulation(state); - if (result == ERROR) { - const char* message = nullptr; - if (state->getLastErrorMessage != nullptr) { - message = state->getLastErrorMessage(state); - } + if (result.status == ERROR) { + const char* message = result.message; if (message != nullptr && *message != '\0') { std::cout << "Error loading code: " << message << "\n"; } else { diff --git a/test/test_simulation.cpp b/test/test_simulation.cpp index 1e575cd6..29130cfd 100644 --- a/test/test_simulation.cpp +++ b/test/test_simulation.cpp @@ -60,7 +60,8 @@ class SimulationTest : public testing::TestWithParam { */ void loadFromFile(const std::string& testName) { const auto code = readFromCircuitsPath(testName); - state->loadCode(state, code.c_str()); + const auto result = state->loadCode(state, code.c_str()); + ASSERT_EQ(result.status, OK); } /** diff --git a/test/utils/common_fixtures.hpp b/test/utils/common_fixtures.hpp index 3fb0c1ca..2eb8eaa6 100644 --- a/test/utils/common_fixtures.hpp +++ b/test/utils/common_fixtures.hpp @@ -123,8 +123,8 @@ class CustomCodeFixture : public testing::Test { bool shouldFail = false, const char* preamble = "") { userCode = code; fullCode = addBoilerplate(numQubits, numClassics, code, preamble); - ASSERT_EQ(state->loadCode(state, fullCode.c_str()), - shouldFail ? ERROR : OK); + const auto result = state->loadCode(state, fullCode.c_str()); + ASSERT_EQ(result.status, shouldFail ? ERROR : OK); } /** @@ -181,7 +181,8 @@ class LoadFromFileFixture : public virtual testing::Test { */ void loadFromFile(const std::string& testName) { const auto code = readFromCircuitsPath(testName); - state->loadCode(state, code.c_str()); + const auto result = state->loadCode(state, code.c_str()); + ASSERT_EQ(result.status, OK); } /** From 2f77e27ba13d21a06ff2b18d76f0b45b34ca9606 Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Wed, 21 Jan 2026 16:35:11 +0100 Subject: [PATCH 109/145] categorizing different errors --- bindings/InterfaceBindings.cpp | 12 +++++++++- include/common.h | 22 +++++++++++++++++-- python/mqt/debugger/__init__.py | 2 ++ python/mqt/debugger/check/run_preparation.py | 2 +- .../dap/messages/launch_dap_message.py | 2 +- .../dap/messages/restart_dap_message.py | 4 ++-- python/mqt/debugger/pydebugger.pyi | 18 ++++++++++++--- src/backend/dd/DDSimDebug.cpp | 11 ++++++---- 8 files changed, 59 insertions(+), 14 deletions(-) diff --git a/bindings/InterfaceBindings.cpp b/bindings/InterfaceBindings.cpp index 161ee072..ae469181 100644 --- a/bindings/InterfaceBindings.cpp +++ b/bindings/InterfaceBindings.cpp @@ -67,11 +67,21 @@ void bindFramework(nb::module_& m) { .value("OK", OK, "Indicates that the operation was successful.") .value("ERROR", ERROR, "Indicates that an error occurred."); + // Bind the LoadResultStatus enum + nb::enum_( + m, "LoadResultStatus", + "Represents the result of a code loading operation.") + .value("OK", LOAD_OK, "Indicates that the code was loaded successfully.") + .value("PARSE_ERROR", LOAD_PARSE_ERROR, + "Indicates that the code could not be parsed.") + .value("INTERNAL_ERROR", LOAD_INTERNAL_ERROR, + "Indicates that an internal error occurred while loading."); + // Bind the LoadResult struct nb::class_(m, "LoadResult") .def(nb::init<>()) .def_rw("status", &LoadResult::status, - "Indicates whether the load was successful.") + "Indicates whether the load was successful and why it failed.") .def_rw("line", &LoadResult::line, "The line number of the error location, or 0 if unknown.") .def_rw("column", &LoadResult::column, diff --git a/include/common.h b/include/common.h index 498327e0..b869e8e1 100644 --- a/include/common.h +++ b/include/common.h @@ -42,14 +42,32 @@ typedef enum { ERROR, } Result; +/** + * @brief The result of a code loading operation. + */ +typedef enum { + /** + * @brief Indicates that the code was loaded successfully. + */ + LOAD_OK, + /** + * @brief Indicates that the code could not be parsed. + */ + LOAD_PARSE_ERROR, + /** + * @brief Indicates that an internal error occurred while loading the code. + */ + LOAD_INTERNAL_ERROR, +} LoadResultStatus; + /** * @brief The result of a code loading operation. */ typedef struct { /** - * @brief Indicates whether the load was successful. + * @brief Indicates whether the load was successful and why it failed. */ - Result status; + LoadResultStatus status; /** * @brief The line number of the error location, or 0 if unknown. */ diff --git a/python/mqt/debugger/__init__.py b/python/mqt/debugger/__init__.py index 27a056e1..135a7d59 100644 --- a/python/mqt/debugger/__init__.py +++ b/python/mqt/debugger/__init__.py @@ -19,6 +19,7 @@ ErrorCause, ErrorCauseType, LoadResult, + LoadResultStatus, Result, SimulationState, Statevector, @@ -36,6 +37,7 @@ "ErrorCause", "ErrorCauseType", "LoadResult", + "LoadResultStatus", "Result", "SimulationState", "Statevector", diff --git a/python/mqt/debugger/check/run_preparation.py b/python/mqt/debugger/check/run_preparation.py index a605970f..6c0acf5a 100644 --- a/python/mqt/debugger/check/run_preparation.py +++ b/python/mqt/debugger/check/run_preparation.py @@ -45,7 +45,7 @@ def start_compilation(code: Path, output_dir: Path) -> None: with code.open("r", encoding="utf-8") as f: code_str = f.read() load_result = state.load_code(code_str) - if load_result.status != dbg.Result.OK: + if load_result.status != dbg.LoadResultStatus.OK: message = load_result.message or "Error loading code" raise RuntimeError(message) i = 0 diff --git a/python/mqt/debugger/dap/messages/launch_dap_message.py b/python/mqt/debugger/dap/messages/launch_dap_message.py index 088b6a34..5c000f39 100644 --- a/python/mqt/debugger/dap/messages/launch_dap_message.py +++ b/python/mqt/debugger/dap/messages/launch_dap_message.py @@ -71,7 +71,7 @@ def handle(self, server: DAPServer) -> dict[str, Any]: code = program_path.read_text(encoding=locale.getpreferredencoding(False)) server.source_code = code load_result = server.simulation_state.load_code(code) - if load_result.status != mqt.debugger.Result.OK: + if load_result.status != mqt.debugger.LoadResultStatus.OK: parsed_successfully = False line = load_result.line if load_result.line > 0 else None column = load_result.column if load_result.column > 0 else None diff --git a/python/mqt/debugger/dap/messages/restart_dap_message.py b/python/mqt/debugger/dap/messages/restart_dap_message.py index 52f0436e..3e563d5d 100644 --- a/python/mqt/debugger/dap/messages/restart_dap_message.py +++ b/python/mqt/debugger/dap/messages/restart_dap_message.py @@ -69,10 +69,10 @@ def handle(self, server: DAPServer) -> dict[str, Any]: program_path = Path(self.program) server.source_file = {"name": program_path.name, "path": self.program} parsed_successfully = True - code = program_path.read_text(encoding=locale.getpreferredencoding(False)) + code = program_path.read_text(encoding=locale.getpreferredencoding(do_setlocale=False)) server.source_code = code load_result = server.simulation_state.load_code(code) - if load_result.status != mqt.debugger.Result.OK: + if load_result.status != mqt.debugger.LoadResultStatus.OK: parsed_successfully = False line = load_result.line if load_result.line > 0 else None column = load_result.column if load_result.column > 0 else None diff --git a/python/mqt/debugger/pydebugger.pyi b/python/mqt/debugger/pydebugger.pyi index 12df9bd3..30386e54 100644 --- a/python/mqt/debugger/pydebugger.pyi +++ b/python/mqt/debugger/pydebugger.pyi @@ -163,16 +163,28 @@ class Result(enum.Enum): ERROR = 1 """Indicates that an error occurred.""" +class LoadResultStatus(enum.Enum): + """Represents the result of a code loading operation.""" + + OK = 0 + """Indicates that the code was loaded successfully.""" + + PARSE_ERROR = 1 + """Indicates that the code could not be parsed.""" + + INTERNAL_ERROR = 2 + """Indicates that an internal error occurred while loading.""" + class LoadResult: """The result of a code loading operation.""" def __init__(self) -> None: ... @property - def status(self) -> Result: - """Indicates whether the load was successful.""" + def status(self) -> LoadResultStatus: + """Indicates whether the load was successful and why it failed.""" @status.setter - def status(self, arg: Result, /) -> None: ... + def status(self, arg: LoadResultStatus, /) -> None: ... @property def line(self) -> int: """The line number of the error location, or 0 if unknown.""" diff --git a/src/backend/dd/DDSimDebug.cpp b/src/backend/dd/DDSimDebug.cpp index 15eed42a..676fb57c 100644 --- a/src/backend/dd/DDSimDebug.cpp +++ b/src/backend/dd/DDSimDebug.cpp @@ -372,7 +372,7 @@ bool checkAssertionEqualityCircuit( createDDSimulationState(&secondSimulation); const auto loadResult = secondSimulation.interface.loadCode( &secondSimulation.interface, assertion->getCircuitCode().c_str()); - if (loadResult.status != OK) { + if (loadResult.status != LOAD_OK) { const char* message = loadResult.message; destroyDDSimulationState(&secondSimulation); throw std::runtime_error( @@ -746,7 +746,10 @@ LoadResult ddsimLoadCode(SimulationState* self, const char* code) { } const auto parsed = parseLoadErrorMessage(message); lastLoadErrorDetail = parsed.detail; - return {.status = ERROR, + const LoadResultStatus status = (parsed.line > 0 || parsed.column > 0) + ? LOAD_PARSE_ERROR + : LOAD_INTERNAL_ERROR; + return {.status = status, .line = parsed.line, .column = parsed.column, .message = lastLoadErrorDetail.empty() @@ -754,7 +757,7 @@ LoadResult ddsimLoadCode(SimulationState* self, const char* code) { : lastLoadErrorDetail.c_str()}; } catch (...) { lastLoadErrorDetail = "An error occurred while executing the operation"; - return {.status = ERROR, + return {.status = LOAD_INTERNAL_ERROR, .line = 0, .column = 0, .message = lastLoadErrorDetail.c_str()}; @@ -769,7 +772,7 @@ LoadResult ddsimLoadCode(SimulationState* self, const char* code) { ddsim->ready = true; - return {.status = OK, .line = 0, .column = 0, .message = nullptr}; + return {.status = LOAD_OK, .line = 0, .column = 0, .message = nullptr}; } Result ddsimChangeClassicalVariableValue(SimulationState* self, From 6b9c1d749029b465889fa233c7ca943a07b94c6f Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Wed, 21 Jan 2026 16:58:40 +0100 Subject: [PATCH 110/145] improvement doctring and cleaner version --- python/mqt/debugger/dap/dap_server.py | 5 +++-- src/backend/dd/DDSimDebug.cpp | 14 ++++++++++++++ src/common/parsing/CodePreprocessing.cpp | 5 ++++- 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/python/mqt/debugger/dap/dap_server.py b/python/mqt/debugger/dap/dap_server.py index ad00c014..6413bdea 100644 --- a/python/mqt/debugger/dap/dap_server.py +++ b/python/mqt/debugger/dap/dap_server.py @@ -116,6 +116,7 @@ def __init__(self, host: str = "127.0.0.1", port: int = 4711) -> None: self.columns_start_at_one = True self.pending_highlights: list[dict[str, Any]] = [] self.source_file = {"name": "", "path": ""} + self.source_code = "" self._prevent_exit = False def start(self) -> None: @@ -450,7 +451,7 @@ def collect_highlight_entries( ) -> list[dict[str, Any]]: """Collect highlight entries for the current assertion failure.""" highlights: list[dict[str, Any]] = [] - if getattr(self, "source_code", ""): + if self.source_code: try: if error_causes is None: diagnostics = self.simulation_state.get_diagnostics() @@ -535,7 +536,7 @@ def queue_parse_error( def _build_parse_error_highlight(self, line: int, column: int, detail: str) -> dict[str, Any] | None: """Create a highlight entry for a parse error.""" - if not getattr(self, "source_code", ""): + if not self.source_code: return None lines = self.source_code.split("\n") if not lines: diff --git a/src/backend/dd/DDSimDebug.cpp b/src/backend/dd/DDSimDebug.cpp index 676fb57c..a46a7708 100644 --- a/src/backend/dd/DDSimDebug.cpp +++ b/src/backend/dd/DDSimDebug.cpp @@ -76,12 +76,21 @@ DDSimulationState* toDDSimulationState(SimulationState* state) { // NOLINTEND(cppcoreguidelines-pro-type-reinterpret-cast) } +/** + * @brief Represents a parsed load error with optional location metadata. + */ struct ParsedLoadError { size_t line; size_t column; std::string detail; }; +/** + * @brief Evaluate a classic-controlled condition from the original code. + * @param ddsim The simulation state. + * @param instructionIndex The instruction index to inspect. + * @return The evaluated condition, or std::nullopt if it cannot be evaluated. + */ std::optional evaluateClassicConditionFromCode(DDSimulationState* ddsim, size_t instructionIndex) { if (instructionIndex >= ddsim->instructionObjects.size()) { @@ -157,6 +166,11 @@ std::optional evaluateClassicConditionFromCode(DDSimulationState* ddsim, return registerValue == expected; } +/** + * @brief Parse a load error message into a structured error descriptor. + * @param message The raw error message. + * @return The parsed load error. + */ ParsedLoadError parseLoadErrorMessage(const std::string& message) { const std::string trimmed = trim(message); const std::string prefix = ":"; diff --git a/src/common/parsing/CodePreprocessing.cpp b/src/common/parsing/CodePreprocessing.cpp index af8d0ae0..23711cbb 100644 --- a/src/common/parsing/CodePreprocessing.cpp +++ b/src/common/parsing/CodePreprocessing.cpp @@ -167,7 +167,10 @@ void validateTargets(const std::string& code, size_t instructionStart, const std::string& context) { for (const auto& target : targets) { if (target.empty()) { - continue; + std::string detail = "Empty target"; + detail += context; + detail += "."; + throw ParsingError(formatParseError(code, instructionStart, detail)); } const auto open = target.find('['); if (open == std::string::npos) { From c1eaccdfc834c63f1069aa47bef4ee7d441519b4 Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Wed, 21 Jan 2026 17:32:43 +0100 Subject: [PATCH 111/145] improvement launch dap --- python/mqt/debugger/dap/messages/launch_dap_message.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/python/mqt/debugger/dap/messages/launch_dap_message.py b/python/mqt/debugger/dap/messages/launch_dap_message.py index 5c000f39..bddca36b 100644 --- a/python/mqt/debugger/dap/messages/launch_dap_message.py +++ b/python/mqt/debugger/dap/messages/launch_dap_message.py @@ -80,8 +80,10 @@ def handle(self, server: DAPServer) -> dict[str, Any]: if parsed_successfully and not self.stop_on_entry: server.simulation_state.run_simulation() if not parsed_successfully: - with contextlib.suppress(RuntimeError): + try: server.simulation_state.reset_simulation() + except RuntimeError: + pass return { "type": "response", "request_seq": self.sequence_number, From fa522a61eac813ce07c37782c48cb15803ddd3fc Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Wed, 21 Jan 2026 18:35:53 +0100 Subject: [PATCH 112/145] fix Cl --- python/mqt/debugger/dap/messages/launch_dap_message.py | 4 +--- src/backend/dd/DDSimDebug.cpp | 2 +- src/frontend/cli/CliFrontEnd.cpp | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/python/mqt/debugger/dap/messages/launch_dap_message.py b/python/mqt/debugger/dap/messages/launch_dap_message.py index bddca36b..5c000f39 100644 --- a/python/mqt/debugger/dap/messages/launch_dap_message.py +++ b/python/mqt/debugger/dap/messages/launch_dap_message.py @@ -80,10 +80,8 @@ def handle(self, server: DAPServer) -> dict[str, Any]: if parsed_successfully and not self.stop_on_entry: server.simulation_state.run_simulation() if not parsed_successfully: - try: + with contextlib.suppress(RuntimeError): server.simulation_state.reset_simulation() - except RuntimeError: - pass return { "type": "response", "request_seq": self.sequence_number, diff --git a/src/backend/dd/DDSimDebug.cpp b/src/backend/dd/DDSimDebug.cpp index a46a7708..bdffc22c 100644 --- a/src/backend/dd/DDSimDebug.cpp +++ b/src/backend/dd/DDSimDebug.cpp @@ -390,7 +390,7 @@ bool checkAssertionEqualityCircuit( const char* message = loadResult.message; destroyDDSimulationState(&secondSimulation); throw std::runtime_error( - message && *message != '\0' + message != nullptr && *message != '\0' ? message : "Failed to load circuit for equality assertion."); } diff --git a/src/frontend/cli/CliFrontEnd.cpp b/src/frontend/cli/CliFrontEnd.cpp index 00d8a06e..4eb26c27 100644 --- a/src/frontend/cli/CliFrontEnd.cpp +++ b/src/frontend/cli/CliFrontEnd.cpp @@ -68,7 +68,7 @@ void CliFrontEnd::run(const char* code, SimulationState* state) { std::string command; const auto result = state->loadCode(state, code); state->resetSimulation(state); - if (result.status == ERROR) { + if (result.status != LOAD_OK) { const char* message = result.message; if (message != nullptr && *message != '\0') { std::cout << "Error loading code: " << message << "\n"; From 1f5e0ca0308593b6e031ccbbddd2919eb22832ce Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Wed, 21 Jan 2026 18:45:27 +0100 Subject: [PATCH 113/145] cl fix 2 --- test/test_custom_code.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/test/test_custom_code.cpp b/test/test_custom_code.cpp index bf1bd69b..8528bbb6 100644 --- a/test/test_custom_code.cpp +++ b/test/test_custom_code.cpp @@ -86,8 +86,13 @@ TEST_F(CustomCodeTest, IfElseOperationMulti) { loadCode(2, 1, "x q[0];" "measure q[0] -> c[0];" - "if(c==1) { x q[0]; x q[1]; }", - true); + "if(c==1) { x q[0]; x 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[2], 1, 0.0)); } /** From 0e6bfa8295c487e94fc51b85d0ae8049e11472c4 Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Wed, 21 Jan 2026 18:54:02 +0100 Subject: [PATCH 114/145] cl fix 3 --- Testing/Temporary/CTestCostData.txt | 1 + src/common/parsing/CodePreprocessing.cpp | 23 ++++++++++++++--------- 2 files changed, 15 insertions(+), 9 deletions(-) create mode 100644 Testing/Temporary/CTestCostData.txt diff --git a/Testing/Temporary/CTestCostData.txt b/Testing/Temporary/CTestCostData.txt new file mode 100644 index 00000000..ed97d539 --- /dev/null +++ b/Testing/Temporary/CTestCostData.txt @@ -0,0 +1 @@ +--- diff --git a/src/common/parsing/CodePreprocessing.cpp b/src/common/parsing/CodePreprocessing.cpp index 23711cbb..34b3d830 100644 --- a/src/common/parsing/CodePreprocessing.cpp +++ b/src/common/parsing/CodePreprocessing.cpp @@ -427,10 +427,13 @@ std::vector parseParameters(const std::string& instruction) { } if (isClassicControlledGate(instruction)) { - const auto end = instruction.find(')'); - - return parseParameters( - instruction.substr(end + 1, instruction.length() - end - 1)); + const auto classic = parseClassicControlledGate(instruction); + std::vector parameters; + for (const auto& op : classic.operations) { + const auto targets = parseParameters(op); + parameters.insert(parameters.end(), targets.begin(), targets.end()); + } + return parameters; } auto parts = splitString( @@ -530,6 +533,12 @@ preprocessCode(const std::string& code, size_t startIndex, line.replace(blockPos, endPos - blockPos + 1, ""); } + if (block.valid && isClassicControlledGate(line)) { + line = line + " { " + block.code + " }"; + block.valid = false; + block.code.clear(); + } + const auto targets = parseParameters(line); const size_t trueEnd = end + blocksOffset; @@ -596,11 +605,7 @@ preprocessCode(const std::string& code, size_t startIndex, } if (isClassicControlledGate(line)) { - if (block.valid) { - throw ParsingError( - "Classic-controlled gates with body blocks are not supported. Use " - "individual `if` statements for each operation."); - } + // Body blocks are handled by inlining their code into the instruction. } bool isFunctionCall = false; From 09f5ace8f592b01bf497833525eacb3683d4264d Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Wed, 21 Jan 2026 19:03:43 +0100 Subject: [PATCH 115/145] cl fix 4 --- 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 34b3d830..320c79b7 100644 --- a/src/common/parsing/CodePreprocessing.cpp +++ b/src/common/parsing/CodePreprocessing.cpp @@ -534,7 +534,7 @@ preprocessCode(const std::string& code, size_t startIndex, } if (block.valid && isClassicControlledGate(line)) { - line = line + " { " + block.code + " }"; + line.append(" { ").append(block.code).append(" }"); block.valid = false; block.code.clear(); } From 4f3e00d50278f733d56c3a6dee23d654ae3d4a48 Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Wed, 21 Jan 2026 20:56:06 +0100 Subject: [PATCH 116/145] remove file --- Testing/Temporary/CTestCostData.txt | 1 - 1 file changed, 1 deletion(-) delete mode 100644 Testing/Temporary/CTestCostData.txt diff --git a/Testing/Temporary/CTestCostData.txt b/Testing/Temporary/CTestCostData.txt deleted file mode 100644 index ed97d539..00000000 --- a/Testing/Temporary/CTestCostData.txt +++ /dev/null @@ -1 +0,0 @@ ---- From f2d905c745ced1d8e4429912338b09a0b169958a Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Thu, 22 Jan 2026 12:17:24 +0100 Subject: [PATCH 117/145] fix cl --- test/python/test_compilation.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/python/test_compilation.py b/test/python/test_compilation.py index 36ffe113..040c0723 100644 --- a/test/python/test_compilation.py +++ b/test/python/test_compilation.py @@ -199,6 +199,18 @@ def test_incorrect_good_sample_size(compiled_slice_1: str) -> None: assert errors >= 75 +def test_start_compilation_raises_on_invalid_code(tmp_path: Path) -> None: + """Ensure invalid input code surfaces as a RuntimeError.""" + invalid_code = tmp_path / "invalid.qasm" + invalid_code.write_text("INVALID QASM", encoding="utf-8") + + output_dir = tmp_path / "out" + output_dir.mkdir() + + with pytest.raises(RuntimeError): + check.start_compilation(invalid_code, output_dir) + + def test_sample_estimate(compiled_slice_1: str) -> None: """Test the estimation of required shots. From 819d0f47c8144d18cdf51a263fdd26d370e528bf Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Thu, 22 Jan 2026 12:26:54 +0100 Subject: [PATCH 118/145] remove destroyDDSimulationState --- src/backend/dd/DDSimDebug.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/backend/dd/DDSimDebug.cpp b/src/backend/dd/DDSimDebug.cpp index bdffc22c..b843b1c5 100644 --- a/src/backend/dd/DDSimDebug.cpp +++ b/src/backend/dd/DDSimDebug.cpp @@ -388,14 +388,12 @@ bool checkAssertionEqualityCircuit( &secondSimulation.interface, assertion->getCircuitCode().c_str()); if (loadResult.status != LOAD_OK) { const char* message = loadResult.message; - destroyDDSimulationState(&secondSimulation); throw std::runtime_error( message != nullptr && *message != '\0' ? message : "Failed to load circuit for equality assertion."); } if (!secondSimulation.assertionInstructions.empty()) { - destroyDDSimulationState(&secondSimulation); throw std::runtime_error( "Circuit equality assertions cannot contain nested assertions"); } @@ -409,7 +407,6 @@ bool checkAssertionEqualityCircuit( sv2.amplitudes = amplitudes2.data(); secondSimulation.interface.getStateVectorFull(&secondSimulation.interface, &sv2); - destroyDDSimulationState(&secondSimulation); Statevector sv; sv.numQubits = qubits.size(); From dd89f76b671824633104ec93d7b9812c2bf3eccf Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Thu, 22 Jan 2026 13:05:33 +0100 Subject: [PATCH 119/145] fix pre-commit --- .pre-commit-config.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ff897a81..7d249452 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -77,8 +77,9 @@ repos: - id: ty-check name: ty check entry: uvx python -c "import subprocess; import sys; r = subprocess.run(['uv', 'sync', '--no-install-project', '--inexact']); sys.exit(r.returncode or subprocess.run(['uv', 'run', '--no-sync', 'ty', 'check']).returncode)" - language: unsupported + language: system require_serial: true + pass_filenames: false types_or: [python, pyi, jupyter] exclude: ^(docs/) From 5822fbdc6aad4440378dcb5cb1f8236d59745ace Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Thu, 22 Jan 2026 13:13:11 +0100 Subject: [PATCH 120/145] cl fix 5 --- python/mqt/debugger/check/run_preparation.py | 45 +++++++++++--------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/python/mqt/debugger/check/run_preparation.py b/python/mqt/debugger/check/run_preparation.py index 6c0acf5a..c52302c2 100644 --- a/python/mqt/debugger/check/run_preparation.py +++ b/python/mqt/debugger/check/run_preparation.py @@ -42,25 +42,32 @@ def start_compilation(code: Path, output_dir: Path) -> None: output_dir (Path): The directory to store the compiled slices. """ state = dbg.create_ddsim_simulation_state() - with code.open("r", encoding="utf-8") as f: - code_str = f.read() - load_result = state.load_code(code_str) - if load_result.status != dbg.LoadResultStatus.OK: - message = load_result.message or "Error loading code" - raise RuntimeError(message) - i = 0 - while True: - i += 1 - settings = dbg.CompilationSettings( - opt=0, - slice_index=i - 1, - ) - compiled = state.compile(settings) - if not compiled: - break - with (output_dir / f"slice_{i}.qasm").open("w") as f: - f.write(compiled) - dbg.destroy_ddsim_simulation_state(state) + try: + with code.open("r", encoding="utf-8") as f: + code_str = f.read() + load_result = state.load_code(code_str) + if load_result.status != dbg.LoadResultStatus.OK: + message = load_result.message or "Error loading code" + raise RuntimeError(message) + i = 0 + compiled_any = False + while True: + i += 1 + settings = dbg.CompilationSettings( + opt=0, + slice_index=i - 1, + ) + compiled = state.compile(settings) + if not compiled: + break + compiled_any = True + with (output_dir / f"slice_{i}.qasm").open("w") as f: + f.write(compiled) + if not compiled_any: + msg = "No compiled slices produced; check input code for validity." + raise RuntimeError(msg) + finally: + dbg.destroy_ddsim_simulation_state(state) # ------------------------- From adecd2249cd06290fe6cac38c7df2d2305ef5034 Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Thu, 22 Jan 2026 13:42:39 +0100 Subject: [PATCH 121/145] fix codecov --- test/python/test_compilation.py | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/test/python/test_compilation.py b/test/python/test_compilation.py index 040c0723..24ade641 100644 --- a/test/python/test_compilation.py +++ b/test/python/test_compilation.py @@ -23,8 +23,9 @@ import pytest +import mqt.debugger as dbg from mqt.debugger import check -from mqt.debugger.check import result_checker, runtime_check +from mqt.debugger.check import result_checker, run_preparation, runtime_check if TYPE_CHECKING: import types @@ -211,6 +212,36 @@ def test_start_compilation_raises_on_invalid_code(tmp_path: Path) -> None: check.start_compilation(invalid_code, output_dir) +def test_start_compilation_raises_on_load_error(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + """Ensure load errors surface as RuntimeError with the provided message.""" + + class DummyLoadResult: + status = dbg.LoadResultStatus.PARSE_ERROR + message = "Bad input" + + class DummyState: + @staticmethod + def load_code(_code: str) -> DummyLoadResult: + return DummyLoadResult() + + @staticmethod + def compile(_settings: dbg.CompilationSettings) -> str: + msg = "compile should not be called" + raise AssertionError(msg) + + monkeypatch.setattr(run_preparation.dbg, "create_ddsim_simulation_state", DummyState) + monkeypatch.setattr(run_preparation.dbg, "destroy_ddsim_simulation_state", lambda _state: None) + + invalid_code = tmp_path / "invalid.qasm" + invalid_code.write_text("INVALID QASM", encoding="utf-8") + + output_dir = tmp_path / "out" + output_dir.mkdir() + + with pytest.raises(RuntimeError, match="Bad input"): + check.start_compilation(invalid_code, output_dir) + + def test_sample_estimate(compiled_slice_1: str) -> None: """Test the estimation of required shots. From 0f9fd530defa0c037630d1d83a8b851f463b287a Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Thu, 22 Jan 2026 14:15:29 +0100 Subject: [PATCH 122/145] avoid boolean positional arg in locale.getpreferredencoding --- python/mqt/debugger/dap/messages/launch_dap_message.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/python/mqt/debugger/dap/messages/launch_dap_message.py b/python/mqt/debugger/dap/messages/launch_dap_message.py index 5c000f39..6a4640f0 100644 --- a/python/mqt/debugger/dap/messages/launch_dap_message.py +++ b/python/mqt/debugger/dap/messages/launch_dap_message.py @@ -68,7 +68,9 @@ def handle(self, server: DAPServer) -> dict[str, Any]: program_path = Path(self.program) server.source_file = {"name": program_path.name, "path": self.program} parsed_successfully = True - code = program_path.read_text(encoding=locale.getpreferredencoding(False)) + code = program_path.read_text( + encoding=locale.getpreferredencoding(do_setlocale=False) + ) server.source_code = code load_result = server.simulation_state.load_code(code) if load_result.status != mqt.debugger.LoadResultStatus.OK: From 50abd273aff698706f5a606a9f58e78d6c784992 Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Thu, 22 Jan 2026 14:16:43 +0100 Subject: [PATCH 123/145] pre-commit fix --- python/mqt/debugger/dap/messages/launch_dap_message.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/python/mqt/debugger/dap/messages/launch_dap_message.py b/python/mqt/debugger/dap/messages/launch_dap_message.py index 6a4640f0..6830a370 100644 --- a/python/mqt/debugger/dap/messages/launch_dap_message.py +++ b/python/mqt/debugger/dap/messages/launch_dap_message.py @@ -68,9 +68,7 @@ def handle(self, server: DAPServer) -> dict[str, Any]: program_path = Path(self.program) server.source_file = {"name": program_path.name, "path": self.program} parsed_successfully = True - code = program_path.read_text( - encoding=locale.getpreferredencoding(do_setlocale=False) - ) + code = program_path.read_text(encoding=locale.getpreferredencoding(do_setlocale=False)) server.source_code = code load_result = server.simulation_state.load_code(code) if load_result.status != mqt.debugger.LoadResultStatus.OK: From 8e20311af9a9339aad1a451a0b36f79c60d69a88 Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Thu, 22 Jan 2026 14:53:58 +0100 Subject: [PATCH 124/145] Revert "fix pre-commit" This reverts commit dd89f76b671824633104ec93d7b9812c2bf3eccf. --- .pre-commit-config.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7d249452..ff897a81 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -77,9 +77,8 @@ repos: - id: ty-check name: ty check entry: uvx python -c "import subprocess; import sys; r = subprocess.run(['uv', 'sync', '--no-install-project', '--inexact']); sys.exit(r.returncode or subprocess.run(['uv', 'run', '--no-sync', 'ty', 'check']).returncode)" - language: system + language: unsupported require_serial: true - pass_filenames: false types_or: [python, pyi, jupyter] exclude: ^(docs/) From 526da85f29ba2cd8d0601c13b85cabb5d9cd8284 Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Tue, 10 Feb 2026 17:18:08 +0100 Subject: [PATCH 125/145] adjustments LOAD_OK and ASSERT_EQ --- test/utils/common_fixtures.hpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test/utils/common_fixtures.hpp b/test/utils/common_fixtures.hpp index 2eb8eaa6..b138c6c7 100644 --- a/test/utils/common_fixtures.hpp +++ b/test/utils/common_fixtures.hpp @@ -124,7 +124,11 @@ class CustomCodeFixture : public testing::Test { userCode = code; fullCode = addBoilerplate(numQubits, numClassics, code, preamble); const auto result = state->loadCode(state, fullCode.c_str()); - ASSERT_EQ(result.status, shouldFail ? ERROR : OK); + if (shouldFail) { + ASSERT_NE(result.status, LOAD_OK); + } else { + ASSERT_EQ(result.status, LOAD_OK); + } } /** @@ -182,7 +186,7 @@ class LoadFromFileFixture : public virtual testing::Test { void loadFromFile(const std::string& testName) { const auto code = readFromCircuitsPath(testName); const auto result = state->loadCode(state, code.c_str()); - ASSERT_EQ(result.status, OK); + ASSERT_EQ(result.status, LOAD_OK); } /** From 13731ab65a5a3b21746bde9049444c1601ba1764 Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Tue, 10 Feb 2026 17:27:25 +0100 Subject: [PATCH 126/145] implemented doctring --- .pre-commit-config.yaml | 2 +- src/common/parsing/CodePreprocessing.cpp | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6d2192c7..41f38399 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -157,7 +157,7 @@ repos: - id: ty-check name: ty check entry: uvx python -c "import subprocess; import sys; r = subprocess.run(['uv', 'sync', '--no-install-project', '--inexact']); sys.exit(r.returncode or subprocess.run(['uv', 'run', '--no-sync', 'ty', 'check']).returncode)" - language: unsupported + language: system require_serial: true types_or: [python, pyi, jupyter] exclude: ^(docs/) diff --git a/src/common/parsing/CodePreprocessing.cpp b/src/common/parsing/CodePreprocessing.cpp index 320c79b7..dca7c826 100644 --- a/src/common/parsing/CodePreprocessing.cpp +++ b/src/common/parsing/CodePreprocessing.cpp @@ -48,6 +48,9 @@ bool isDigits(const std::string& text) { text, [](unsigned char c) { return std::isdigit(c) != 0; }); } +/** + * @brief 1-based line/column location within source text. + */ struct LineColumn { size_t line = 1; size_t column = 1; From 1cf068f6758ed92d9538cd99aba9fd41afec278c Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Tue, 10 Feb 2026 17:37:28 +0100 Subject: [PATCH 127/145] more specific exception catching --- src/common/parsing/CodePreprocessing.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/common/parsing/CodePreprocessing.cpp b/src/common/parsing/CodePreprocessing.cpp index dca7c826..46d3d48d 100644 --- a/src/common/parsing/CodePreprocessing.cpp +++ b/src/common/parsing/CodePreprocessing.cpp @@ -195,7 +195,11 @@ void validateTargets(const std::string& code, size_t instructionStart, size_t registerIndex = 0; try { registerIndex = std::stoul(indexText); - } catch (const std::exception&) { + } catch (const std::invalid_argument&) { + throw ParsingError(formatParseError(code, instructionStart, + invalidTargetDetail(target, context), + target)); + } catch (const std::out_of_range&) { throw ParsingError(formatParseError(code, instructionStart, invalidTargetDetail(target, context), target)); From a3d385e25febd30fc34b4a62ad0bb00a207b08b5 Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Tue, 10 Feb 2026 17:43:32 +0100 Subject: [PATCH 128/145] remove if --- src/common/parsing/CodePreprocessing.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/common/parsing/CodePreprocessing.cpp b/src/common/parsing/CodePreprocessing.cpp index 46d3d48d..7e871b79 100644 --- a/src/common/parsing/CodePreprocessing.cpp +++ b/src/common/parsing/CodePreprocessing.cpp @@ -611,10 +611,6 @@ preprocessCode(const std::string& code, size_t startIndex, continue; } - if (isClassicControlledGate(line)) { - // Body blocks are handled by inlining their code into the instruction. - } - bool isFunctionCall = false; std::string calledFunction; if (!tokens.empty() && From 4283c11defa4b3fd0ef910fb612bf5c567cf6da3 Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Wed, 11 Feb 2026 16:50:31 +0100 Subject: [PATCH 129/145] parsing related materials to CodePreprocessing.cpp --- include/common/parsing/CodePreprocessing.hpp | 51 +++++++++ src/backend/dd/DDSimDebug.cpp | 107 ++----------------- src/common/parsing/CodePreprocessing.cpp | 103 ++++++++++++++++++ 3 files changed, 163 insertions(+), 98 deletions(-) diff --git a/include/common/parsing/CodePreprocessing.hpp b/include/common/parsing/CodePreprocessing.hpp index fe2f94b6..a8c46bba 100644 --- a/include/common/parsing/CodePreprocessing.hpp +++ b/include/common/parsing/CodePreprocessing.hpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -211,6 +212,33 @@ struct ClassicControlledGate { std::vector operations; }; +/** + * @brief Represents a parsed classic-controlled condition. + */ +struct ClassicCondition { + /** + * @brief The name of the classical register. + */ + std::string registerName; + /** + * @brief Optional bit index if the condition targets a single bit. + */ + std::optional bitIndex; + /** + * @brief The expected value in the condition comparison. + */ + size_t expectedValue; +}; + +/** + * @brief Represents a parsed load error with optional location metadata. + */ +struct ParsedLoadError { + size_t line; + size_t column; + std::string detail; +}; + /** * @brief Represents a function definition in the code. */ @@ -286,6 +314,22 @@ bool isClassicControlledGate(const std::string& line); */ ClassicControlledGate parseClassicControlledGate(const std::string& code); +/** + * @brief Parse a classic-controlled condition expression. + * @param condition The condition string to parse. + * @return The parsed condition, or std::nullopt if it cannot be parsed. + */ +std::optional +parseClassicConditionExpression(const std::string& condition); + +/** + * @brief Parse a classic-controlled condition from a classic-controlled gate. + * @param code The code to parse. + * @return The parsed condition, or std::nullopt if it cannot be parsed. + */ +std::optional parseClassicConditionFromCode( + const std::string& code); + /** * @brief Check if a given line is a variable declaration. * @@ -329,4 +373,11 @@ bool isBarrier(const std::string& line); */ std::vector parseParameters(const std::string& instruction); +/** + * @brief Parse a load error message into a structured error descriptor. + * @param message The raw error message. + * @return The parsed load error. + */ +ParsedLoadError parseLoadErrorMessage(const std::string& message); + } // namespace mqt::debugger diff --git a/src/backend/dd/DDSimDebug.cpp b/src/backend/dd/DDSimDebug.cpp index b843b1c5..bc62b5b4 100644 --- a/src/backend/dd/DDSimDebug.cpp +++ b/src/backend/dd/DDSimDebug.cpp @@ -76,15 +76,6 @@ DDSimulationState* toDDSimulationState(SimulationState* state) { // NOLINTEND(cppcoreguidelines-pro-type-reinterpret-cast) } -/** - * @brief Represents a parsed load error with optional location metadata. - */ -struct ParsedLoadError { - size_t line; - size_t column; - std::string detail; -}; - /** * @brief Evaluate a classic-controlled condition from the original code. * @param ddsim The simulation state. @@ -97,62 +88,23 @@ std::optional evaluateClassicConditionFromCode(DDSimulationState* ddsim, return std::nullopt; } const auto& code = ddsim->instructionObjects[instructionIndex].code; - if (!isClassicControlledGate(code)) { - return std::nullopt; - } - const auto condition = parseClassicControlledGate(code).condition; - auto normalized = removeWhitespace(condition); - if (!normalized.empty() && normalized.front() == '(') { - normalized.erase(0, 1); - } - const auto eqPos = normalized.find("=="); - if (eqPos == std::string::npos) { - return std::nullopt; - } - const auto lhs = normalized.substr(0, eqPos); - const auto rhs = normalized.substr(eqPos + 2); - if (lhs.empty() || rhs.empty()) { - return std::nullopt; - } - - const auto parseIndex = [](const std::string& text, size_t& value) -> bool { - if (text.empty()) { - return false; - } - if (std::ranges::any_of(text, - [](unsigned char c) { return !std::isdigit(c); })) { - return false; - } - value = std::stoull(text); - return true; - }; - - size_t expected = 0; - if (!parseIndex(rhs, expected)) { + const auto parsed = parseClassicConditionFromCode(code); + if (!parsed.has_value()) { return std::nullopt; } size_t registerValue = 0; - const auto bracketPos = lhs.find('['); - if (bracketPos != std::string::npos) { - const auto closePos = lhs.find(']', bracketPos + 1); - if (closePos == std::string::npos) { - return std::nullopt; - } - const auto base = lhs.substr(0, bracketPos); - const auto indexText = - lhs.substr(bracketPos + 1, closePos - bracketPos - 1); - size_t bitIndex = 0; - if (!parseIndex(indexText, bitIndex)) { - return std::nullopt; - } - const auto bitName = base + "[" + std::to_string(bitIndex) + "]"; + if (parsed->bitIndex.has_value()) { + const auto bitName = parsed->registerName + "[" + + std::to_string(parsed->bitIndex.value()) + "]"; const auto& value = ddsim->variables[bitName].value.boolValue; registerValue = value ? 1ULL : 0ULL; } else { const auto regIt = std::ranges::find_if( ddsim->classicalRegisters, - [&lhs](const auto& reg) { return reg.name == lhs; }); + [&parsed](const auto& reg) { + return reg.name == parsed->registerName; + }); if (regIt == ddsim->classicalRegisters.end()) { return std::nullopt; } @@ -163,48 +115,7 @@ std::optional evaluateClassicConditionFromCode(DDSimulationState* ddsim, } } - return registerValue == expected; -} - -/** - * @brief Parse a load error message into a structured error descriptor. - * @param message The raw error message. - * @return The parsed load error. - */ -ParsedLoadError parseLoadErrorMessage(const std::string& message) { - const std::string trimmed = trim(message); - const std::string prefix = ":"; - if (!trimmed.starts_with(prefix)) { - return {.line = 0, .column = 0, .detail = trimmed}; - } - - const size_t lineStart = prefix.size(); - const size_t lineEnd = trimmed.find(':', lineStart); - if (lineEnd == std::string::npos) { - return {.line = 0, .column = 0, .detail = trimmed}; - } - const size_t columnEnd = trimmed.find(':', lineEnd + 1); - if (columnEnd == std::string::npos) { - return {.line = 0, .column = 0, .detail = trimmed}; - } - - const std::string lineStr = trimmed.substr(lineStart, lineEnd - lineStart); - const std::string columnStr = - trimmed.substr(lineEnd + 1, columnEnd - lineEnd - 1); - auto isDigit = [](unsigned char c) { return std::isdigit(c) != 0; }; - if (lineStr.empty() || columnStr.empty() || - !std::ranges::all_of(lineStr, isDigit) || - !std::ranges::all_of(columnStr, isDigit)) { - return {.line = 0, .column = 0, .detail = trimmed}; - } - - const size_t line = std::stoul(lineStr); - const size_t column = std::stoul(columnStr); - std::string detail = trim(trimmed.substr(columnEnd + 1)); - if (detail.empty()) { - detail = trimmed; - } - return {.line = line, .column = column, .detail = detail}; + return registerValue == parsed->expectedValue; } /** diff --git a/src/common/parsing/CodePreprocessing.cpp b/src/common/parsing/CodePreprocessing.cpp index 7e871b79..d5fc1360 100644 --- a/src/common/parsing/CodePreprocessing.cpp +++ b/src/common/parsing/CodePreprocessing.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -414,6 +415,72 @@ ClassicControlledGate parseClassicControlledGate(const std::string& code) { return {.condition = condition.str(), .operations = operations}; } +std::optional +parseClassicConditionExpression(const std::string& condition) { + auto normalized = removeWhitespace(condition); + if (!normalized.empty() && normalized.front() == '(') { + normalized.erase(0, 1); + } + const auto eqPos = normalized.find("=="); + if (eqPos == std::string::npos) { + return std::nullopt; + } + const auto lhs = normalized.substr(0, eqPos); + const auto rhs = normalized.substr(eqPos + 2); + if (lhs.empty() || rhs.empty()) { + return std::nullopt; + } + + if (!isDigits(rhs)) { + return std::nullopt; + } + size_t expected = 0; + try { + expected = std::stoull(rhs); + } catch (const std::invalid_argument&) { + return std::nullopt; + } catch (const std::out_of_range&) { + return std::nullopt; + } + + const auto bracketPos = lhs.find('['); + if (bracketPos != std::string::npos) { + const auto closePos = lhs.find(']', bracketPos + 1); + if (bracketPos == 0 || closePos == std::string::npos || + closePos != lhs.size() - 1) { + return std::nullopt; + } + const auto base = lhs.substr(0, bracketPos); + const auto indexText = + lhs.substr(bracketPos + 1, closePos - bracketPos - 1); + if (!isDigits(indexText)) { + return std::nullopt; + } + size_t bitIndex = 0; + try { + bitIndex = std::stoull(indexText); + } catch (const std::invalid_argument&) { + return std::nullopt; + } catch (const std::out_of_range&) { + return std::nullopt; + } + return ClassicCondition{ + .registerName = base, .bitIndex = bitIndex, .expectedValue = expected}; + } + + return ClassicCondition{ + .registerName = lhs, .bitIndex = std::nullopt, .expectedValue = expected}; +} + +std::optional +parseClassicConditionFromCode(const std::string& code) { + if (!isClassicControlledGate(code)) { + return std::nullopt; + } + const auto condition = parseClassicControlledGate(code).condition; + return parseClassicConditionExpression(condition); +} + bool isMeasurement(const std::string& line) { return line.find("->") != std::string::npos; } @@ -475,6 +542,42 @@ std::vector parseParameters(const std::string& instruction) { return parameters; } +ParsedLoadError parseLoadErrorMessage(const std::string& message) { + const std::string trimmed = trim(message); + const std::string prefix = ":"; + if (!trimmed.starts_with(prefix)) { + return {.line = 0, .column = 0, .detail = trimmed}; + } + + const size_t lineStart = prefix.size(); + const size_t lineEnd = trimmed.find(':', lineStart); + if (lineEnd == std::string::npos) { + return {.line = 0, .column = 0, .detail = trimmed}; + } + const size_t columnEnd = trimmed.find(':', lineEnd + 1); + if (columnEnd == std::string::npos) { + return {.line = 0, .column = 0, .detail = trimmed}; + } + + const std::string lineStr = trimmed.substr(lineStart, lineEnd - lineStart); + const std::string columnStr = + trimmed.substr(lineEnd + 1, columnEnd - lineEnd - 1); + auto isDigit = [](unsigned char c) { return std::isdigit(c) != 0; }; + if (lineStr.empty() || columnStr.empty() || + !std::ranges::all_of(lineStr, isDigit) || + !std::ranges::all_of(columnStr, isDigit)) { + return {.line = 0, .column = 0, .detail = trimmed}; + } + + const size_t line = std::stoul(lineStr); + const size_t column = std::stoul(columnStr); + std::string detail = trim(trimmed.substr(columnEnd + 1)); + if (detail.empty()) { + detail = trimmed; + } + return {.line = line, .column = column, .detail = detail}; +} + std::vector preprocessCode(const std::string& code, std::string& processedCode) { std::map definedRegisters; From 3ab07334d76706548eb1788656688a85d9ef89c0 Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Wed, 11 Feb 2026 17:49:28 +0100 Subject: [PATCH 130/145] removed ParsingError --- include/backend/dd/DDSimDebug.hpp | 4 ++ include/common/parsing/CodePreprocessing.hpp | 4 +- include/common/parsing/ParsingError.hpp | 39 +++++++++++++++++ src/backend/dd/DDSimDebug.cpp | 35 +++++++++------- src/common/parsing/CodePreprocessing.cpp | 44 +++++++++++--------- src/common/parsing/ParsingError.cpp | 18 +++++++- 6 files changed, 105 insertions(+), 39 deletions(-) diff --git a/include/backend/dd/DDSimDebug.hpp b/include/backend/dd/DDSimDebug.hpp index 94e195f2..ac477e12 100644 --- a/include/backend/dd/DDSimDebug.hpp +++ b/include/backend/dd/DDSimDebug.hpp @@ -119,6 +119,10 @@ struct DDSimulationState { * @brief The code being executed, after preprocessing. */ std::string processedCode; + /** + * @brief The most recent load error detail message. + */ + std::string lastLoadErrorDetail; /** * @brief Indicates whether the debugger is ready to start simulation. */ diff --git a/include/common/parsing/CodePreprocessing.hpp b/include/common/parsing/CodePreprocessing.hpp index a8c46bba..1fc3d5df 100644 --- a/include/common/parsing/CodePreprocessing.hpp +++ b/include/common/parsing/CodePreprocessing.hpp @@ -327,8 +327,8 @@ parseClassicConditionExpression(const std::string& condition); * @param code The code to parse. * @return The parsed condition, or std::nullopt if it cannot be parsed. */ -std::optional parseClassicConditionFromCode( - const std::string& code); +std::optional +parseClassicConditionFromCode(const std::string& code); /** * @brief Check if a given line is a variable declaration. diff --git a/include/common/parsing/ParsingError.hpp b/include/common/parsing/ParsingError.hpp index 68aa3459..7078d8f8 100644 --- a/include/common/parsing/ParsingError.hpp +++ b/include/common/parsing/ParsingError.hpp @@ -15,6 +15,7 @@ #pragma once +#include #include #include @@ -30,6 +31,44 @@ class ParsingError : public std::runtime_error { * @param msg The error message. */ explicit ParsingError(const std::string& msg); + + /** + * @brief Constructs a new ParsingError with location information. + * @param line The one-based line number, or 0 if unknown. + * @param column The one-based column number, or 0 if unknown. + * @param detail The error detail message. + */ + ParsingError(size_t line, size_t column, std::string detail); + + /** + * @brief Constructs a new ParsingError with location information and message. + * @param line The one-based line number, or 0 if unknown. + * @param column The one-based column number, or 0 if unknown. + * @param detail The error detail message. + * @param message The formatted error message. + */ + ParsingError(size_t line, size_t column, std::string detail, + const std::string& message); + + /** + * @brief Gets the line number of the error location, or 0 if unknown. + */ + size_t line() const noexcept; + + /** + * @brief Gets the column number of the error location, or 0 if unknown. + */ + size_t column() const noexcept; + + /** + * @brief Gets the error detail message. + */ + const std::string& detail() const noexcept; + +private: + size_t line_ = 0; + size_t column_ = 0; + std::string detail_; }; } // namespace mqt::debugger diff --git a/src/backend/dd/DDSimDebug.cpp b/src/backend/dd/DDSimDebug.cpp index bc62b5b4..39666543 100644 --- a/src/backend/dd/DDSimDebug.cpp +++ b/src/backend/dd/DDSimDebug.cpp @@ -25,6 +25,7 @@ #include "common/parsing/AssertionParsing.hpp" #include "common/parsing/AssertionTools.hpp" #include "common/parsing/CodePreprocessing.hpp" +#include "common/parsing/ParsingError.hpp" #include "common/parsing/Utils.hpp" #include "dd/DDDefinitions.hpp" #include "dd/Operations.hpp" @@ -101,8 +102,7 @@ std::optional evaluateClassicConditionFromCode(DDSimulationState* ddsim, registerValue = value ? 1ULL : 0ULL; } else { const auto regIt = std::ranges::find_if( - ddsim->classicalRegisters, - [&parsed](const auto& reg) { + ddsim->classicalRegisters, [&parsed](const auto& reg) { return reg.name == parsed->registerName; }); if (regIt == ddsim->classicalRegisters.end()) { @@ -632,8 +632,8 @@ Result ddsimInit(SimulationState* self) { } LoadResult ddsimLoadCode(SimulationState* self, const char* code) { - static thread_local std::string lastLoadErrorDetail; auto* ddsim = toDDSimulationState(self); + ddsim->lastLoadErrorDetail.clear(); ddsim->currentInstruction = 0; ddsim->previousInstructionStack.clear(); ddsim->callReturnStack.clear(); @@ -661,28 +661,31 @@ LoadResult ddsimLoadCode(SimulationState* self, const char* code) { const auto imported = qasm3::Importer::import(ss); ddsim->qc = std::make_unique(imported); qc::CircuitOptimizer::flattenOperations(*ddsim->qc, true); + } catch (const ParsingError& e) { + ddsim->lastLoadErrorDetail = e.detail(); + return {.status = LOAD_PARSE_ERROR, + .line = e.line(), + .column = e.column(), + .message = ddsim->lastLoadErrorDetail.empty() + ? nullptr + : ddsim->lastLoadErrorDetail.c_str()}; } catch (const std::exception& e) { std::string message = e.what(); if (message.empty()) { message = "An error occurred while executing the operation"; } - const auto parsed = parseLoadErrorMessage(message); - lastLoadErrorDetail = parsed.detail; - const LoadResultStatus status = (parsed.line > 0 || parsed.column > 0) - ? LOAD_PARSE_ERROR - : LOAD_INTERNAL_ERROR; - return {.status = status, - .line = parsed.line, - .column = parsed.column, - .message = lastLoadErrorDetail.empty() - ? nullptr - : lastLoadErrorDetail.c_str()}; + ddsim->lastLoadErrorDetail = message; + return {.status = LOAD_INTERNAL_ERROR, + .line = 0, + .column = 0, + .message = ddsim->lastLoadErrorDetail.c_str()}; } catch (...) { - lastLoadErrorDetail = "An error occurred while executing the operation"; + ddsim->lastLoadErrorDetail = + "An error occurred while executing the operation"; return {.status = LOAD_INTERNAL_ERROR, .line = 0, .column = 0, - .message = lastLoadErrorDetail.c_str()}; + .message = ddsim->lastLoadErrorDetail.c_str()}; } ddsim->iterator = ddsim->qc->begin(); diff --git a/src/common/parsing/CodePreprocessing.cpp b/src/common/parsing/CodePreprocessing.cpp index d5fc1360..2a2a18c4 100644 --- a/src/common/parsing/CodePreprocessing.cpp +++ b/src/common/parsing/CodePreprocessing.cpp @@ -128,6 +128,15 @@ std::string formatParseError(const std::string& code, size_t instructionStart, std::to_string(location.column) + ": " + detail; } +ParsingError makeParseError(const std::string& code, size_t instructionStart, + const std::string& detail, + const std::string& target = "") { + const auto location = lineColumnForTarget(code, instructionStart, target); + const std::string message = ":" + std::to_string(location.line) + ":" + + std::to_string(location.column) + ": " + detail; + return ParsingError(location.line, location.column, detail, message); +} + /** * @brief Build an error detail string for an invalid target. * @param target The invalid target token. @@ -174,7 +183,7 @@ void validateTargets(const std::string& code, size_t instructionStart, std::string detail = "Empty target"; detail += context; detail += "."; - throw ParsingError(formatParseError(code, instructionStart, detail)); + throw makeParseError(code, instructionStart, detail); } const auto open = target.find('['); if (open == std::string::npos) { @@ -182,28 +191,24 @@ void validateTargets(const std::string& code, size_t instructionStart, } const auto close = target.find(']', open + 1); if (open == 0 || close == std::string::npos || close != target.size() - 1) { - throw ParsingError(formatParseError(code, instructionStart, - invalidTargetDetail(target, context), - target)); + throw makeParseError(code, instructionStart, + invalidTargetDetail(target, context), target); } const auto registerName = target.substr(0, open); const auto indexText = target.substr(open + 1, close - open - 1); if (!isDigits(indexText)) { - throw ParsingError(formatParseError(code, instructionStart, - invalidTargetDetail(target, context), - target)); + throw makeParseError(code, instructionStart, + invalidTargetDetail(target, context), target); } size_t registerIndex = 0; try { registerIndex = std::stoul(indexText); } catch (const std::invalid_argument&) { - throw ParsingError(formatParseError(code, instructionStart, - invalidTargetDetail(target, context), - target)); + throw makeParseError(code, instructionStart, + invalidTargetDetail(target, context), target); } catch (const std::out_of_range&) { - throw ParsingError(formatParseError(code, instructionStart, - invalidTargetDetail(target, context), - target)); + throw makeParseError(code, instructionStart, + invalidTargetDetail(target, context), target); } if (std::ranges::find(shadowedRegisters, registerName) != shadowedRegisters.end()) { @@ -211,9 +216,8 @@ void validateTargets(const std::string& code, size_t instructionStart, } const auto found = definedRegisters.find(registerName); if (found == definedRegisters.end() || found->second <= registerIndex) { - throw ParsingError(formatParseError(code, instructionStart, - invalidTargetDetail(target, context), - target)); + throw makeParseError(code, instructionStart, + invalidTargetDetail(target, context), target); } } } @@ -660,15 +664,15 @@ preprocessCode(const std::string& code, size_t startIndex, const auto& name = parts[0]; const auto sizeText = parts.size() > 1 ? parts[1] : ""; if (name.empty() || !isDigits(sizeText)) { - throw ParsingError(formatParseError( - code, trueStart, invalidRegisterDetail(trimmedLine))); + throw makeParseError(code, trueStart, + invalidRegisterDetail(trimmedLine)); } size_t size = 0; try { size = std::stoul(sizeText); } catch (const std::exception&) { - throw ParsingError(formatParseError( - code, trueStart, invalidRegisterDetail(trimmedLine))); + throw makeParseError(code, trueStart, + invalidRegisterDetail(trimmedLine)); } definedRegisters.insert({name, size}); } diff --git a/src/common/parsing/ParsingError.cpp b/src/common/parsing/ParsingError.cpp index 0b60a322..226acc0b 100644 --- a/src/common/parsing/ParsingError.cpp +++ b/src/common/parsing/ParsingError.cpp @@ -20,6 +20,22 @@ namespace mqt::debugger { -ParsingError::ParsingError(const std::string& msg) : std::runtime_error(msg) {} +ParsingError::ParsingError(const std::string& msg) + : std::runtime_error(msg), detail_(msg) {} + +ParsingError::ParsingError(size_t line, size_t column, std::string detail) + : std::runtime_error(detail), line_(line), column_(column), + detail_(std::move(detail)) {} + +ParsingError::ParsingError(size_t line, size_t column, std::string detail, + const std::string& message) + : std::runtime_error(message), line_(line), column_(column), + detail_(std::move(detail)) {} + +size_t ParsingError::line() const noexcept { return line_; } + +size_t ParsingError::column() const noexcept { return column_; } + +const std::string& ParsingError::detail() const noexcept { return detail_; } } // namespace mqt::debugger From c7da6a3cddfefd6e355708b76a4963d129f7495a Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Wed, 11 Feb 2026 18:35:13 +0100 Subject: [PATCH 131/145] changing back pre-commit --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 41f38399..6d2192c7 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -157,7 +157,7 @@ repos: - id: ty-check name: ty check entry: uvx python -c "import subprocess; import sys; r = subprocess.run(['uv', 'sync', '--no-install-project', '--inexact']); sys.exit(r.returncode or subprocess.run(['uv', 'run', '--no-sync', 'ty', 'check']).returncode)" - language: system + language: unsupported require_serial: true types_or: [python, pyi, jupyter] exclude: ^(docs/) From 8a07f36130a99a394e693dfb3e7056ab6f96dcdb Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Wed, 11 Feb 2026 19:45:15 +0100 Subject: [PATCH 132/145] fix linter --- src/common/parsing/CodePreprocessing.cpp | 20 ++------------------ src/common/parsing/ParsingError.cpp | 2 ++ 2 files changed, 4 insertions(+), 18 deletions(-) diff --git a/src/common/parsing/CodePreprocessing.cpp b/src/common/parsing/CodePreprocessing.cpp index 2a2a18c4..1a100455 100644 --- a/src/common/parsing/CodePreprocessing.cpp +++ b/src/common/parsing/CodePreprocessing.cpp @@ -22,12 +22,12 @@ #include #include #include -#include #include #include #include #include #include +#include #include #include #include @@ -112,29 +112,13 @@ LineColumn lineColumnForTarget(const std::string& code, size_t instructionStart, return location; } -/** - * @brief Format a parse error with line/column location information. - * @param code The source code to inspect. - * @param instructionStart The zero-based offset of the instruction start. - * @param detail The error detail text. - * @param target Optional target token to locate more precisely. - * @return The formatted error string. - */ -std::string formatParseError(const std::string& code, size_t instructionStart, - const std::string& detail, - const std::string& target = "") { - const auto location = lineColumnForTarget(code, instructionStart, target); - return ":" + std::to_string(location.line) + ":" + - std::to_string(location.column) + ": " + detail; -} - ParsingError makeParseError(const std::string& code, size_t instructionStart, const std::string& detail, const std::string& target = "") { const auto location = lineColumnForTarget(code, instructionStart, target); const std::string message = ":" + std::to_string(location.line) + ":" + std::to_string(location.column) + ": " + detail; - return ParsingError(location.line, location.column, detail, message); + return {location.line, location.column, detail, message}; } /** diff --git a/src/common/parsing/ParsingError.cpp b/src/common/parsing/ParsingError.cpp index 226acc0b..50634bd6 100644 --- a/src/common/parsing/ParsingError.cpp +++ b/src/common/parsing/ParsingError.cpp @@ -15,8 +15,10 @@ #include "common/parsing/ParsingError.hpp" +#include #include #include +#include namespace mqt::debugger { From 6ce28c0a66f2acaeb987f6939d05984ac4b165c6 Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Wed, 11 Feb 2026 19:57:28 +0100 Subject: [PATCH 133/145] fix linter 2 --- src/common/parsing/CodePreprocessing.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/common/parsing/CodePreprocessing.cpp b/src/common/parsing/CodePreprocessing.cpp index 1a100455..ce4cb53d 100644 --- a/src/common/parsing/CodePreprocessing.cpp +++ b/src/common/parsing/CodePreprocessing.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include From 7ea10847de49e5ae38ce72996d2e83931f5257d7 Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Thu, 12 Feb 2026 22:25:25 +0100 Subject: [PATCH 134/145] parsing removed --- include/common/parsing/CodePreprocessing.hpp | 16 --------- src/common/parsing/CodePreprocessing.cpp | 36 -------------------- 2 files changed, 52 deletions(-) diff --git a/include/common/parsing/CodePreprocessing.hpp b/include/common/parsing/CodePreprocessing.hpp index 1fc3d5df..f777d234 100644 --- a/include/common/parsing/CodePreprocessing.hpp +++ b/include/common/parsing/CodePreprocessing.hpp @@ -230,15 +230,6 @@ struct ClassicCondition { size_t expectedValue; }; -/** - * @brief Represents a parsed load error with optional location metadata. - */ -struct ParsedLoadError { - size_t line; - size_t column; - std::string detail; -}; - /** * @brief Represents a function definition in the code. */ @@ -373,11 +364,4 @@ bool isBarrier(const std::string& line); */ std::vector parseParameters(const std::string& instruction); -/** - * @brief Parse a load error message into a structured error descriptor. - * @param message The raw error message. - * @return The parsed load error. - */ -ParsedLoadError parseLoadErrorMessage(const std::string& message); - } // namespace mqt::debugger diff --git a/src/common/parsing/CodePreprocessing.cpp b/src/common/parsing/CodePreprocessing.cpp index ce4cb53d..4c0b3112 100644 --- a/src/common/parsing/CodePreprocessing.cpp +++ b/src/common/parsing/CodePreprocessing.cpp @@ -531,42 +531,6 @@ std::vector parseParameters(const std::string& instruction) { return parameters; } -ParsedLoadError parseLoadErrorMessage(const std::string& message) { - const std::string trimmed = trim(message); - const std::string prefix = ":"; - if (!trimmed.starts_with(prefix)) { - return {.line = 0, .column = 0, .detail = trimmed}; - } - - const size_t lineStart = prefix.size(); - const size_t lineEnd = trimmed.find(':', lineStart); - if (lineEnd == std::string::npos) { - return {.line = 0, .column = 0, .detail = trimmed}; - } - const size_t columnEnd = trimmed.find(':', lineEnd + 1); - if (columnEnd == std::string::npos) { - return {.line = 0, .column = 0, .detail = trimmed}; - } - - const std::string lineStr = trimmed.substr(lineStart, lineEnd - lineStart); - const std::string columnStr = - trimmed.substr(lineEnd + 1, columnEnd - lineEnd - 1); - auto isDigit = [](unsigned char c) { return std::isdigit(c) != 0; }; - if (lineStr.empty() || columnStr.empty() || - !std::ranges::all_of(lineStr, isDigit) || - !std::ranges::all_of(columnStr, isDigit)) { - return {.line = 0, .column = 0, .detail = trimmed}; - } - - const size_t line = std::stoul(lineStr); - const size_t column = std::stoul(columnStr); - std::string detail = trim(trimmed.substr(columnEnd + 1)); - if (detail.empty()) { - detail = trimmed; - } - return {.line = line, .column = column, .detail = detail}; -} - std::vector preprocessCode(const std::string& code, std::string& processedCode) { std::map definedRegisters; From e200d4ad97c59ae7ccb46eaa946fb572f5a58c61 Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Wed, 4 Mar 2026 17:25:00 +0200 Subject: [PATCH 135/145] destroyDDSimulationState --- src/backend/dd/DDSimDebug.cpp | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/backend/dd/DDSimDebug.cpp b/src/backend/dd/DDSimDebug.cpp index 39666543..2f58f0a7 100644 --- a/src/backend/dd/DDSimDebug.cpp +++ b/src/backend/dd/DDSimDebug.cpp @@ -77,6 +77,18 @@ DDSimulationState* toDDSimulationState(SimulationState* state) { // NOLINTEND(cppcoreguidelines-pro-type-reinterpret-cast) } +struct DDSimulationStateGuard { + explicit DDSimulationStateGuard(DDSimulationState* state) : state(state) {} + DDSimulationStateGuard(const DDSimulationStateGuard&) = delete; + DDSimulationStateGuard& operator=(const DDSimulationStateGuard&) = delete; + ~DDSimulationStateGuard() { + if (state != nullptr) { + destroyDDSimulationState(state); + } + } + DDSimulationState* state; +}; + /** * @brief Evaluate a classic-controlled condition from the original code. * @param ddsim The simulation state. @@ -294,7 +306,11 @@ bool checkAssertionEqualityCircuit( } DDSimulationState secondSimulation; - createDDSimulationState(&secondSimulation); + if (createDDSimulationState(&secondSimulation) == ERROR) { + throw std::runtime_error( + "Failed to initialize simulation for equality assertion."); + } + DDSimulationStateGuard secondSimulationGuard(&secondSimulation); const auto loadResult = secondSimulation.interface.loadCode( &secondSimulation.interface, assertion->getCircuitCode().c_str()); if (loadResult.status != LOAD_OK) { @@ -633,6 +649,7 @@ Result ddsimInit(SimulationState* self) { LoadResult ddsimLoadCode(SimulationState* self, const char* code) { auto* ddsim = toDDSimulationState(self); + // Keep backing storage for LoadResult::message valid after return. ddsim->lastLoadErrorDetail.clear(); ddsim->currentInstruction = 0; ddsim->previousInstructionStack.clear(); From 5f87c32f91583e5e5d0b1806c774076719e1e3b0 Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Wed, 4 Mar 2026 17:33:24 +0200 Subject: [PATCH 136/145] LoadResult store a character array --- bindings/InterfaceBindings.cpp | 2 +- include/backend/dd/DDSimDebug.hpp | 4 --- include/common.h | 9 ++++-- src/backend/dd/DDSimDebug.cpp | 51 ++++++++++++++++--------------- src/frontend/cli/CliFrontEnd.cpp | 5 ++- 5 files changed, 37 insertions(+), 34 deletions(-) diff --git a/bindings/InterfaceBindings.cpp b/bindings/InterfaceBindings.cpp index ae469181..578ba2b1 100644 --- a/bindings/InterfaceBindings.cpp +++ b/bindings/InterfaceBindings.cpp @@ -89,7 +89,7 @@ void bindFramework(nb::module_& m) { .def_prop_ro( "message", [](const LoadResult& self) { - if (self.message == nullptr) { + if (self.message[0] == '\0') { return nb::none(); } return nb::cast(std::string(self.message)); diff --git a/include/backend/dd/DDSimDebug.hpp b/include/backend/dd/DDSimDebug.hpp index ac477e12..94e195f2 100644 --- a/include/backend/dd/DDSimDebug.hpp +++ b/include/backend/dd/DDSimDebug.hpp @@ -119,10 +119,6 @@ struct DDSimulationState { * @brief The code being executed, after preprocessing. */ std::string processedCode; - /** - * @brief The most recent load error detail message. - */ - std::string lastLoadErrorDetail; /** * @brief Indicates whether the debugger is ready to start simulation. */ diff --git a/include/common.h b/include/common.h index b869e8e1..48a34988 100644 --- a/include/common.h +++ b/include/common.h @@ -60,6 +60,11 @@ typedef enum { LOAD_INTERNAL_ERROR, } LoadResultStatus; +/** + * @brief Maximum length of a load error message (including null terminator). + */ +#define LOAD_RESULT_MESSAGE_MAX 1024 + /** * @brief The result of a code loading operation. */ @@ -77,9 +82,9 @@ typedef struct { */ size_t column; /** - * @brief A human-readable error message, or nullptr if none is available. + * @brief A human-readable error message, or empty string if none is available. */ - const char* message; + char message[LOAD_RESULT_MESSAGE_MAX]; } LoadResult; /** diff --git a/src/backend/dd/DDSimDebug.cpp b/src/backend/dd/DDSimDebug.cpp index 2f58f0a7..e1680726 100644 --- a/src/backend/dd/DDSimDebug.cpp +++ b/src/backend/dd/DDSimDebug.cpp @@ -314,10 +314,9 @@ bool checkAssertionEqualityCircuit( const auto loadResult = secondSimulation.interface.loadCode( &secondSimulation.interface, assertion->getCircuitCode().c_str()); if (loadResult.status != LOAD_OK) { - const char* message = loadResult.message; throw std::runtime_error( - message != nullptr && *message != '\0' - ? message + loadResult.message[0] != '\0' + ? loadResult.message : "Failed to load circuit for equality assertion."); } if (!secondSimulation.assertionInstructions.empty()) { @@ -531,6 +530,25 @@ bool areAssertionsIndependent(DDSimulationState* ddsim, }); } +void setLoadResultMessage(LoadResult& result, const std::string& message) { + result.message[0] = '\0'; + if (message.empty()) { + return; + } + std::strncpy(result.message, message.c_str(), LOAD_RESULT_MESSAGE_MAX - 1); + result.message[LOAD_RESULT_MESSAGE_MAX - 1] = '\0'; +} + +LoadResult makeLoadResult(LoadResultStatus status, size_t line, size_t column, + const std::string& message) { + LoadResult result{}; + result.status = status; + result.line = line; + result.column = column; + setLoadResultMessage(result, message); + return result; +} + /** * @brief Compile an assertion using projective measurements. * @param ddsim The simulation state. @@ -649,8 +667,6 @@ Result ddsimInit(SimulationState* self) { LoadResult ddsimLoadCode(SimulationState* self, const char* code) { auto* ddsim = toDDSimulationState(self); - // Keep backing storage for LoadResult::message valid after return. - ddsim->lastLoadErrorDetail.clear(); ddsim->currentInstruction = 0; ddsim->previousInstructionStack.clear(); ddsim->callReturnStack.clear(); @@ -679,30 +695,17 @@ LoadResult ddsimLoadCode(SimulationState* self, const char* code) { ddsim->qc = std::make_unique(imported); qc::CircuitOptimizer::flattenOperations(*ddsim->qc, true); } catch (const ParsingError& e) { - ddsim->lastLoadErrorDetail = e.detail(); - return {.status = LOAD_PARSE_ERROR, - .line = e.line(), - .column = e.column(), - .message = ddsim->lastLoadErrorDetail.empty() - ? nullptr - : ddsim->lastLoadErrorDetail.c_str()}; + return makeLoadResult(LOAD_PARSE_ERROR, e.line(), e.column(), e.detail()); } catch (const std::exception& e) { std::string message = e.what(); if (message.empty()) { message = "An error occurred while executing the operation"; } - ddsim->lastLoadErrorDetail = message; - return {.status = LOAD_INTERNAL_ERROR, - .line = 0, - .column = 0, - .message = ddsim->lastLoadErrorDetail.c_str()}; + return makeLoadResult(LOAD_INTERNAL_ERROR, 0, 0, message); } catch (...) { - ddsim->lastLoadErrorDetail = - "An error occurred while executing the operation"; - return {.status = LOAD_INTERNAL_ERROR, - .line = 0, - .column = 0, - .message = ddsim->lastLoadErrorDetail.c_str()}; + return makeLoadResult( + LOAD_INTERNAL_ERROR, 0, 0, + "An error occurred while executing the operation"); } ddsim->iterator = ddsim->qc->begin(); @@ -714,7 +717,7 @@ LoadResult ddsimLoadCode(SimulationState* self, const char* code) { ddsim->ready = true; - return {.status = LOAD_OK, .line = 0, .column = 0, .message = nullptr}; + return makeLoadResult(LOAD_OK, 0, 0, ""); } Result ddsimChangeClassicalVariableValue(SimulationState* self, diff --git a/src/frontend/cli/CliFrontEnd.cpp b/src/frontend/cli/CliFrontEnd.cpp index 4eb26c27..c276d6a1 100644 --- a/src/frontend/cli/CliFrontEnd.cpp +++ b/src/frontend/cli/CliFrontEnd.cpp @@ -69,9 +69,8 @@ void CliFrontEnd::run(const char* code, SimulationState* state) { const auto result = state->loadCode(state, code); state->resetSimulation(state); if (result.status != LOAD_OK) { - const char* message = result.message; - if (message != nullptr && *message != '\0') { - std::cout << "Error loading code: " << message << "\n"; + if (result.message[0] != '\0') { + std::cout << "Error loading code: " << result.message << "\n"; } else { std::cout << "Error loading code\n"; } From 928d9f2698a46004122d7570b35ebaf6d383d736 Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Thu, 5 Mar 2026 00:26:36 +0200 Subject: [PATCH 137/145] fix linter --- bindings/InterfaceBindings.cpp | 10 ++++++++-- src/backend/dd/DDSimDebug.cpp | 14 ++++++++++---- src/frontend/cli/CliFrontEnd.cpp | 12 ++++++++++-- 3 files changed, 28 insertions(+), 8 deletions(-) diff --git a/bindings/InterfaceBindings.cpp b/bindings/InterfaceBindings.cpp index 578ba2b1..af74deb6 100644 --- a/bindings/InterfaceBindings.cpp +++ b/bindings/InterfaceBindings.cpp @@ -21,12 +21,15 @@ #include #include #include +#include +#include #include #include // NOLINT(misc-include-cleaner) #include // NOLINT(misc-include-cleaner) #include // NOLINT(misc-include-cleaner) #include #include +#include #include #include @@ -89,10 +92,13 @@ void bindFramework(nb::module_& m) { .def_prop_ro( "message", [](const LoadResult& self) { - if (self.message[0] == '\0') { + const auto* data = std::data(self.message); + const std::string_view message_view( + data, std::strnlen(data, LOAD_RESULT_MESSAGE_MAX)); + if (message_view.empty()) { return nb::none(); } - return nb::cast(std::string(self.message)); + return nb::cast(std::string(message_view)); }, "A human-readable error message, or None if none is available.") .doc() = "The result of a code loading operation."; diff --git a/src/backend/dd/DDSimDebug.cpp b/src/backend/dd/DDSimDebug.cpp index e1680726..c1c70789 100644 --- a/src/backend/dd/DDSimDebug.cpp +++ b/src/backend/dd/DDSimDebug.cpp @@ -56,6 +56,7 @@ #include #include #include +#include #include #include @@ -310,13 +311,16 @@ bool checkAssertionEqualityCircuit( throw std::runtime_error( "Failed to initialize simulation for equality assertion."); } - DDSimulationStateGuard secondSimulationGuard(&secondSimulation); + const DDSimulationStateGuard secondSimulationGuard(&secondSimulation); const auto loadResult = secondSimulation.interface.loadCode( &secondSimulation.interface, assertion->getCircuitCode().c_str()); if (loadResult.status != LOAD_OK) { + const auto* data = std::data(loadResult.message); + const std::string_view message_view( + data, std::strnlen(data, LOAD_RESULT_MESSAGE_MAX)); throw std::runtime_error( - loadResult.message[0] != '\0' - ? loadResult.message + !message_view.empty() + ? std::string(message_view) : "Failed to load circuit for equality assertion."); } if (!secondSimulation.assertionInstructions.empty()) { @@ -535,7 +539,9 @@ void setLoadResultMessage(LoadResult& result, const std::string& message) { if (message.empty()) { return; } - std::strncpy(result.message, message.c_str(), LOAD_RESULT_MESSAGE_MAX - 1); + const auto copy_len = + std::min(message.size(), LOAD_RESULT_MESSAGE_MAX - 1); + std::copy_n(message.data(), copy_len, std::begin(result.message)); result.message[LOAD_RESULT_MESSAGE_MAX - 1] = '\0'; } diff --git a/src/frontend/cli/CliFrontEnd.cpp b/src/frontend/cli/CliFrontEnd.cpp index c276d6a1..56c04791 100644 --- a/src/frontend/cli/CliFrontEnd.cpp +++ b/src/frontend/cli/CliFrontEnd.cpp @@ -22,15 +22,22 @@ #include #include #include +#include #include #include #include +#include #include namespace mqt::debugger { namespace { +std::string_view loadResultMessageView(const LoadResult& result) { + const auto* data = std::data(result.message); + return {data, std::strnlen(data, LOAD_RESULT_MESSAGE_MAX)}; +} + /** * @brief ANSI escape sequence for resetting the background color. * @@ -69,8 +76,9 @@ void CliFrontEnd::run(const char* code, SimulationState* state) { const auto result = state->loadCode(state, code); state->resetSimulation(state); if (result.status != LOAD_OK) { - if (result.message[0] != '\0') { - std::cout << "Error loading code: " << result.message << "\n"; + const auto message_view = loadResultMessageView(result); + if (!message_view.empty()) { + std::cout << "Error loading code: " << message_view << "\n"; } else { std::cout << "Error loading code\n"; } From 3b610f2de7f44ca10b341ba675e0c70eb6d620af Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Thu, 5 Mar 2026 00:37:12 +0200 Subject: [PATCH 138/145] fix pre-commit --- include/common.h | 3 ++- python/mqt/debugger/dap/dap_server.py | 1 + src/backend/dd/DDSimDebug.cpp | 8 +++----- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/include/common.h b/include/common.h index 48a34988..247e1e1a 100644 --- a/include/common.h +++ b/include/common.h @@ -82,7 +82,8 @@ typedef struct { */ size_t column; /** - * @brief A human-readable error message, or empty string if none is available. + * @brief A human-readable error message, or empty string if none is + * available. */ char message[LOAD_RESULT_MESSAGE_MAX]; } LoadResult; diff --git a/python/mqt/debugger/dap/dap_server.py b/python/mqt/debugger/dap/dap_server.py index 96bdbb08..faea5d3d 100644 --- a/python/mqt/debugger/dap/dap_server.py +++ b/python/mqt/debugger/dap/dap_server.py @@ -610,6 +610,7 @@ def send_message_hierarchy( line: The line number. column: The column number. connection: The client socket. + category: The output category (console/stdout/stderr). """ raw_body = message.get("body") body: list[str] | None = None diff --git a/src/backend/dd/DDSimDebug.cpp b/src/backend/dd/DDSimDebug.cpp index c1c70789..c30a45a2 100644 --- a/src/backend/dd/DDSimDebug.cpp +++ b/src/backend/dd/DDSimDebug.cpp @@ -539,8 +539,7 @@ void setLoadResultMessage(LoadResult& result, const std::string& message) { if (message.empty()) { return; } - const auto copy_len = - std::min(message.size(), LOAD_RESULT_MESSAGE_MAX - 1); + const auto copy_len = std::min(message.size(), LOAD_RESULT_MESSAGE_MAX - 1); std::copy_n(message.data(), copy_len, std::begin(result.message)); result.message[LOAD_RESULT_MESSAGE_MAX - 1] = '\0'; } @@ -709,9 +708,8 @@ LoadResult ddsimLoadCode(SimulationState* self, const char* code) { } return makeLoadResult(LOAD_INTERNAL_ERROR, 0, 0, message); } catch (...) { - return makeLoadResult( - LOAD_INTERNAL_ERROR, 0, 0, - "An error occurred while executing the operation"); + return makeLoadResult(LOAD_INTERNAL_ERROR, 0, 0, + "An error occurred while executing the operation"); } ddsim->iterator = ddsim->qc->begin(); From 7355dfbe62da77e115ed39d6273141fee25b022e Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Thu, 5 Mar 2026 10:46:26 +0200 Subject: [PATCH 139/145] fix linter --- src/backend/dd/DDSimDebug.cpp | 5 +++-- src/frontend/cli/CliFrontEnd.cpp | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/backend/dd/DDSimDebug.cpp b/src/backend/dd/DDSimDebug.cpp index c30a45a2..93e5dd6c 100644 --- a/src/backend/dd/DDSimDebug.cpp +++ b/src/backend/dd/DDSimDebug.cpp @@ -317,7 +317,7 @@ bool checkAssertionEqualityCircuit( if (loadResult.status != LOAD_OK) { const auto* data = std::data(loadResult.message); const std::string_view message_view( - data, std::strnlen(data, LOAD_RESULT_MESSAGE_MAX)); + data, ::strnlen(data, LOAD_RESULT_MESSAGE_MAX)); throw std::runtime_error( !message_view.empty() ? std::string(message_view) @@ -539,7 +539,8 @@ void setLoadResultMessage(LoadResult& result, const std::string& message) { if (message.empty()) { return; } - const auto copy_len = std::min(message.size(), LOAD_RESULT_MESSAGE_MAX - 1); + const auto copy_len = std::min( + message.size(), static_cast(LOAD_RESULT_MESSAGE_MAX - 1)); std::copy_n(message.data(), copy_len, std::begin(result.message)); result.message[LOAD_RESULT_MESSAGE_MAX - 1] = '\0'; } diff --git a/src/frontend/cli/CliFrontEnd.cpp b/src/frontend/cli/CliFrontEnd.cpp index 56c04791..e6ab2a6a 100644 --- a/src/frontend/cli/CliFrontEnd.cpp +++ b/src/frontend/cli/CliFrontEnd.cpp @@ -35,7 +35,7 @@ namespace { std::string_view loadResultMessageView(const LoadResult& result) { const auto* data = std::data(result.message); - return {data, std::strnlen(data, LOAD_RESULT_MESSAGE_MAX)}; + return {data, ::strnlen(data, LOAD_RESULT_MESSAGE_MAX)}; } /** From e9fff7727eec03ca26ed0cf825d6e79df5e5a310 Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Thu, 5 Mar 2026 10:50:50 +0200 Subject: [PATCH 140/145] fix linter 2 --- bindings/InterfaceBindings.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bindings/InterfaceBindings.cpp b/bindings/InterfaceBindings.cpp index af74deb6..4074aefd 100644 --- a/bindings/InterfaceBindings.cpp +++ b/bindings/InterfaceBindings.cpp @@ -94,7 +94,7 @@ void bindFramework(nb::module_& m) { [](const LoadResult& self) { const auto* data = std::data(self.message); const std::string_view message_view( - data, std::strnlen(data, LOAD_RESULT_MESSAGE_MAX)); + data, ::strnlen(data, LOAD_RESULT_MESSAGE_MAX)); if (message_view.empty()) { return nb::none(); } From 9514845db87eaaa879c9e71634a066143c7bfb4d Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Thu, 5 Mar 2026 11:02:20 +0200 Subject: [PATCH 141/145] fix linter 3 --- bindings/InterfaceBindings.cpp | 6 +++--- src/backend/dd/DDSimDebug.cpp | 10 +++++----- src/frontend/cli/CliFrontEnd.cpp | 6 +++--- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/bindings/InterfaceBindings.cpp b/bindings/InterfaceBindings.cpp index 4074aefd..1f59afcc 100644 --- a/bindings/InterfaceBindings.cpp +++ b/bindings/InterfaceBindings.cpp @@ -93,12 +93,12 @@ void bindFramework(nb::module_& m) { "message", [](const LoadResult& self) { const auto* data = std::data(self.message); - const std::string_view message_view( + const std::string_view messageView( data, ::strnlen(data, LOAD_RESULT_MESSAGE_MAX)); - if (message_view.empty()) { + if (messageView.empty()) { return nb::none(); } - return nb::cast(std::string(message_view)); + return nb::cast(std::string(messageView)); }, "A human-readable error message, or None if none is available.") .doc() = "The result of a code loading operation."; diff --git a/src/backend/dd/DDSimDebug.cpp b/src/backend/dd/DDSimDebug.cpp index 93e5dd6c..dabc1bec 100644 --- a/src/backend/dd/DDSimDebug.cpp +++ b/src/backend/dd/DDSimDebug.cpp @@ -316,11 +316,11 @@ bool checkAssertionEqualityCircuit( &secondSimulation.interface, assertion->getCircuitCode().c_str()); if (loadResult.status != LOAD_OK) { const auto* data = std::data(loadResult.message); - const std::string_view message_view( + const std::string_view messageView( data, ::strnlen(data, LOAD_RESULT_MESSAGE_MAX)); throw std::runtime_error( - !message_view.empty() - ? std::string(message_view) + !messageView.empty() + ? std::string(messageView) : "Failed to load circuit for equality assertion."); } if (!secondSimulation.assertionInstructions.empty()) { @@ -539,9 +539,9 @@ void setLoadResultMessage(LoadResult& result, const std::string& message) { if (message.empty()) { return; } - const auto copy_len = std::min( + const auto copyLen = std::min( message.size(), static_cast(LOAD_RESULT_MESSAGE_MAX - 1)); - std::copy_n(message.data(), copy_len, std::begin(result.message)); + std::copy_n(message.data(), copyLen, std::begin(result.message)); result.message[LOAD_RESULT_MESSAGE_MAX - 1] = '\0'; } diff --git a/src/frontend/cli/CliFrontEnd.cpp b/src/frontend/cli/CliFrontEnd.cpp index e6ab2a6a..efea108d 100644 --- a/src/frontend/cli/CliFrontEnd.cpp +++ b/src/frontend/cli/CliFrontEnd.cpp @@ -76,9 +76,9 @@ void CliFrontEnd::run(const char* code, SimulationState* state) { const auto result = state->loadCode(state, code); state->resetSimulation(state); if (result.status != LOAD_OK) { - const auto message_view = loadResultMessageView(result); - if (!message_view.empty()) { - std::cout << "Error loading code: " << message_view << "\n"; + const auto messageView = loadResultMessageView(result); + if (!messageView.empty()) { + std::cout << "Error loading code: " << messageView << "\n"; } else { std::cout << "Error loading code\n"; } From 529936551ab8b9335d50eb5a72fb59fd04b50486 Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Thu, 5 Mar 2026 11:19:29 +0200 Subject: [PATCH 142/145] Clamp parse-error highlight columns to end-of-line --- python/mqt/debugger/dap/dap_server.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/python/mqt/debugger/dap/dap_server.py b/python/mqt/debugger/dap/dap_server.py index faea5d3d..90ef27dd 100644 --- a/python/mqt/debugger/dap/dap_server.py +++ b/python/mqt/debugger/dap/dap_server.py @@ -557,7 +557,10 @@ def _build_parse_error_highlight(self, line: int, column: int, detail: str) -> d stripped = line_text.lstrip() column = max(1, len(line_text) - len(stripped) + 1) if stripped else 1 - end_column = max(column, len(line_text) + 1) + # Clamp to end-of-line to keep columns within bounds while preserving end >= start. + max_column = len(line_text) + 1 + column = min(column, max_column) + end_column = max_column snippet = line_text.strip() or line_text return { "instruction": -1, From b7e66abf9508ecb2fcedff77b3c579f4981595cc Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Thu, 5 Mar 2026 11:31:05 +0200 Subject: [PATCH 143/145] fix linter 4 --- bindings/InterfaceBindings.cpp | 2 +- src/backend/dd/DDSimDebug.cpp | 2 +- src/frontend/cli/CliFrontEnd.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bindings/InterfaceBindings.cpp b/bindings/InterfaceBindings.cpp index 1f59afcc..477ed72f 100644 --- a/bindings/InterfaceBindings.cpp +++ b/bindings/InterfaceBindings.cpp @@ -21,13 +21,13 @@ #include #include #include -#include #include #include #include // NOLINT(misc-include-cleaner) #include // NOLINT(misc-include-cleaner) #include // NOLINT(misc-include-cleaner) #include +#include #include #include #include diff --git a/src/backend/dd/DDSimDebug.cpp b/src/backend/dd/DDSimDebug.cpp index dabc1bec..19370e35 100644 --- a/src/backend/dd/DDSimDebug.cpp +++ b/src/backend/dd/DDSimDebug.cpp @@ -42,7 +42,6 @@ #include #include #include -#include #include #include #include @@ -55,6 +54,7 @@ #include #include #include +#include #include #include #include diff --git a/src/frontend/cli/CliFrontEnd.cpp b/src/frontend/cli/CliFrontEnd.cpp index efea108d..a2d780a3 100644 --- a/src/frontend/cli/CliFrontEnd.cpp +++ b/src/frontend/cli/CliFrontEnd.cpp @@ -22,9 +22,9 @@ #include #include #include -#include #include #include +#include #include #include #include From cab3e5ef4f7382c94ee6f566f34b3a678716186a Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Thu, 5 Mar 2026 11:53:32 +0200 Subject: [PATCH 144/145] fix linter 5 --- bindings/InterfaceBindings.cpp | 9 +++++++-- src/backend/dd/DDSimDebug.cpp | 9 +++++++-- src/frontend/cli/CliFrontEnd.cpp | 9 +++++++-- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/bindings/InterfaceBindings.cpp b/bindings/InterfaceBindings.cpp index 477ed72f..78a6170e 100644 --- a/bindings/InterfaceBindings.cpp +++ b/bindings/InterfaceBindings.cpp @@ -21,13 +21,13 @@ #include #include #include +#include #include #include #include // NOLINT(misc-include-cleaner) #include // NOLINT(misc-include-cleaner) #include // NOLINT(misc-include-cleaner) #include -#include #include #include #include @@ -38,6 +38,11 @@ using namespace nb::literals; namespace { +size_t boundedStrnlen(const char* data, size_t max) { + const auto* end = static_cast(std::memchr(data, '\0', max)); + return end ? static_cast(end - data) : max; +} + /** * @brief Checks whether the given result is OK, and throws a runtime_error * otherwise. @@ -94,7 +99,7 @@ void bindFramework(nb::module_& m) { [](const LoadResult& self) { const auto* data = std::data(self.message); const std::string_view messageView( - data, ::strnlen(data, LOAD_RESULT_MESSAGE_MAX)); + data, boundedStrnlen(data, LOAD_RESULT_MESSAGE_MAX)); if (messageView.empty()) { return nb::none(); } diff --git a/src/backend/dd/DDSimDebug.cpp b/src/backend/dd/DDSimDebug.cpp index 19370e35..94ce762c 100644 --- a/src/backend/dd/DDSimDebug.cpp +++ b/src/backend/dd/DDSimDebug.cpp @@ -42,6 +42,7 @@ #include #include #include +#include #include #include #include @@ -54,7 +55,6 @@ #include #include #include -#include #include #include #include @@ -64,6 +64,11 @@ namespace mqt::debugger { namespace { +size_t boundedStrnlen(const char* data, size_t max) { + const auto* end = static_cast(std::memchr(data, '\0', max)); + return end ? static_cast(end - data) : max; +} + /** * @brief Cast a `SimulationState` pointer to a `DDSimulationState` pointer. * @@ -317,7 +322,7 @@ bool checkAssertionEqualityCircuit( if (loadResult.status != LOAD_OK) { const auto* data = std::data(loadResult.message); const std::string_view messageView( - data, ::strnlen(data, LOAD_RESULT_MESSAGE_MAX)); + data, boundedStrnlen(data, LOAD_RESULT_MESSAGE_MAX)); throw std::runtime_error( !messageView.empty() ? std::string(messageView) diff --git a/src/frontend/cli/CliFrontEnd.cpp b/src/frontend/cli/CliFrontEnd.cpp index a2d780a3..0a9b5ace 100644 --- a/src/frontend/cli/CliFrontEnd.cpp +++ b/src/frontend/cli/CliFrontEnd.cpp @@ -22,9 +22,9 @@ #include #include #include +#include #include #include -#include #include #include #include @@ -33,9 +33,14 @@ namespace mqt::debugger { namespace { +size_t boundedStrnlen(const char* data, size_t max) { + const auto* end = static_cast(std::memchr(data, '\0', max)); + return end ? static_cast(end - data) : max; +} + std::string_view loadResultMessageView(const LoadResult& result) { const auto* data = std::data(result.message); - return {data, ::strnlen(data, LOAD_RESULT_MESSAGE_MAX)}; + return {data, boundedStrnlen(data, LOAD_RESULT_MESSAGE_MAX)}; } /** From fbfdd2e09c643761397e5952b0c2421d16f90ef3 Mon Sep 17 00:00:00 2001 From: gitAccountS Date: Thu, 5 Mar 2026 12:03:42 +0200 Subject: [PATCH 145/145] fix linter 6 --- bindings/InterfaceBindings.cpp | 2 +- src/backend/dd/DDSimDebug.cpp | 2 +- src/frontend/cli/CliFrontEnd.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bindings/InterfaceBindings.cpp b/bindings/InterfaceBindings.cpp index 78a6170e..5896f349 100644 --- a/bindings/InterfaceBindings.cpp +++ b/bindings/InterfaceBindings.cpp @@ -40,7 +40,7 @@ namespace { size_t boundedStrnlen(const char* data, size_t max) { const auto* end = static_cast(std::memchr(data, '\0', max)); - return end ? static_cast(end - data) : max; + return end != nullptr ? static_cast(end - data) : max; } /** diff --git a/src/backend/dd/DDSimDebug.cpp b/src/backend/dd/DDSimDebug.cpp index 94ce762c..1bed2617 100644 --- a/src/backend/dd/DDSimDebug.cpp +++ b/src/backend/dd/DDSimDebug.cpp @@ -66,7 +66,7 @@ namespace { size_t boundedStrnlen(const char* data, size_t max) { const auto* end = static_cast(std::memchr(data, '\0', max)); - return end ? static_cast(end - data) : max; + return end != nullptr ? static_cast(end - data) : max; } /** diff --git a/src/frontend/cli/CliFrontEnd.cpp b/src/frontend/cli/CliFrontEnd.cpp index 0a9b5ace..52d14755 100644 --- a/src/frontend/cli/CliFrontEnd.cpp +++ b/src/frontend/cli/CliFrontEnd.cpp @@ -35,7 +35,7 @@ namespace { size_t boundedStrnlen(const char* data, size_t max) { const auto* end = static_cast(std::memchr(data, '\0', max)); - return end ? static_cast(end - data) : max; + return end != nullptr ? static_cast(end - data) : max; } std::string_view loadResultMessageView(const LoadResult& result) {