diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml index c9b57a5..b56a717 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.yml +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -8,9 +8,9 @@ body: **Thank you for wanting to report a bug for this project!** ⚠ - Verify first that your issue is not [already reported on GitHub](https://github.com/cda-tum/qcec/search?q=is%3Aissue&type=issues). + Verify first that your issue is not [already reported on GitHub](https://github.com/cda-tum/mqt-debugger/search?q=is%3Aissue&type=issues). - If you have general questions, please consider [starting a discussion](https://github.com/cda-tum/qcec/discussions). + If you have general questions, please consider [starting a discussion](https://github.com/cda-tum/mqt-debugger/discussions). - type: textarea attributes: label: Environment information diff --git a/.github/ISSUE_TEMPLATE/feature-request.yml b/.github/ISSUE_TEMPLATE/feature-request.yml index 812010c..e5d8da8 100644 --- a/.github/ISSUE_TEMPLATE/feature-request.yml +++ b/.github/ISSUE_TEMPLATE/feature-request.yml @@ -8,7 +8,7 @@ body: **Thank you for wanting to suggest a feature for this project!** ⚠ - Verify first that your idea is not [already requested on GitHub](https://github.com/cda-tum/qcec/search?q=is%3Aissue&type=issues). + Verify first that your idea is not [already requested on GitHub](https://github.com/cda-tum/mqt-debugger/search?q=is%3Aissue&type=issues). - type: textarea attributes: diff --git a/.github/codecov.yml b/.github/codecov.yml index 20f3769..61b40c8 100644 --- a/.github/codecov.yml +++ b/.github/codecov.yml @@ -2,8 +2,8 @@ ignore: - "extern/**/*" - "**/python" - "test/**/*" - - "src/mqt/debug/dap/**/*" - - "src/mqt/debug/dap/*" + - "src/mqt/debugger/dap/**/*" + - "src/mqt/debugger/dap/*" - "app/*" - "src/frontend/**/*" diff --git a/.github/contributing.rst b/.github/contributing.rst index 7704d3c..99b62a7 100644 --- a/.github/contributing.rst +++ b/.github/contributing.rst @@ -6,7 +6,7 @@ We value contributions from people with all levels of experience. In particular if this is your first pull request not everything has to be perfect. We will guide you through the process. -We use GitHub to `host code `_, to `track issues and feature requests `_, as well as accept `pull requests `_. +We use GitHub to `host code `_, to `track issues and feature requests `_, as well as accept `pull requests `_. See https://docs.github.com/en/get-started/quickstart for a general introduction to working with GitHub and contributing to projects. Types of Contributions @@ -15,24 +15,24 @@ Types of Contributions You can contribute in several ways: - 🐛 Report Bugs - Report bugs at https://github.com/cda-tum/mqt-debug/issues using the *🐛 Bug report* issue template. Please make sure to fill out all relevant information in the respective issue form. + Report bugs at https://github.com/cda-tum/mqt-debugger/issues using the *🐛 Bug report* issue template. Please make sure to fill out all relevant information in the respective issue form. - 🐛 Fix Bugs - Look through the `GitHub Issues `_ for bugs. Anything tagged with "bug" is open to whoever wants to try and fix it. + Look through the `GitHub Issues `_ for bugs. Anything tagged with "bug" is open to whoever wants to try and fix it. - ✨ Propose New Features - Propose new features at https://github.com/cda-tum/mqt-debug/issues using the *✨ Feature request* issue template. Please make sure to fill out all relevant information in the respective issue form. + Propose new features at https://github.com/cda-tum/mqt-debugger/issues using the *✨ Feature request* issue template. Please make sure to fill out all relevant information in the respective issue form. - ✨ Implement New Features - Look through the `GitHub Issues `_ for features. Anything tagged with "feature" is open to whoever wants to implement it. We highly appreciate external contributions to the project. + Look through the `GitHub Issues `_ for features. Anything tagged with "feature" is open to whoever wants to implement it. We highly appreciate external contributions to the project. - 📝 Write Documentation - MQT Debug could always use some more `documentation `_, and we appreciate any help with that. + MQT Debugger could always use some more `documentation `_, and we appreciate any help with that. 🎉 Get Started ############## -Ready to contribute? Check out the :doc:`Development Guide ` to set up MQT Debug for local development and learn about the style guidelines and conventions used throughout the project. +Ready to contribute? Check out the :doc:`Development Guide ` to set up MQT Debugger for local development and learn about the style guidelines and conventions used throughout the project. We value contributions from people with all levels of experience. In particular if this is your first PR not everything has to be perfect. @@ -62,7 +62,7 @@ Pull Request Workflow - If any of the :code:`Python Packaging/\*` checks fail, this indicates an error in the Python bindings or creation of the Python wheels and/or source distribution. Look through the respective logs on GitHub for any error or failure messages. - If any of the :code:`Python/\*` checks fail, this indicates an error in the Python part of the code base. Look through the respective logs on GitHub for any error or failure messages. - If any of the :code:`codecov/\*` checks fail, this means that your changes are not appropriately covered by tests or that the overall project coverage decreased too much. Ensure that you include tests for all your changes in the PR. - - If the :code:`docs/readthedocs.org:debug` check fails, the documentation could not be built properly. Inspect the corresponding log file for any errors. + - If the :code:`docs/readthedocs.org:debugger` check fails, the documentation could not be built properly. Inspect the corresponding log file for any errors. - If :code:`cpp-linter` comments on your PR with a list of warnings, these have been raised by :code:`clang-tidy` when checking the C++ part of your changes for warnings or style guideline violations. The individual messages frequently provide helpful suggestions on how to fix the warnings. - If the :code:`pre-commit.ci` check fails, some of the :code:`pre-commit` checks failed and could not be fixed automatically by the *pre-commit.ci* bot. Such failures are most likely related to the Python part of the code base. The individual log messages frequently provide helpful suggestions on how to fix the warnings. diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml index 8ec9986..03ef4c0 100644 --- a/.github/release-drafter.yml +++ b/.github/release-drafter.yml @@ -1,4 +1,4 @@ -name-template: "MQT Debug $RESOLVED_VERSION Release" +name-template: "MQT Debugger $RESOLVED_VERSION Release" tag-template: "v$RESOLVED_VERSION" categories: - title: "🚀 Features and Enhancements" diff --git a/.github/support.rst b/.github/support.rst index 1d8dc2b..b4bdc3f 100644 --- a/.github/support.rst +++ b/.github/support.rst @@ -1,12 +1,12 @@ Support ======= -If you are stuck with a problem using MQT Debug or are having questions, please do get in touch at our `Issues `_ or `Discussions `_. We'd love to help. +If you are stuck with a problem using MQT Debugger or are having questions, please do get in touch at our `Issues `_ or `Discussions `_. We'd love to help. You can save time by following this procedure when reporting a problem: -- Do try to solve the problem on your own first. Make sure to consult the `Documentation `_. -- Search through past `Issues `_ to see if someone else already had the same problem. +- Do try to solve the problem on your own first. Make sure to consult the `Documentation `_. +- Search through past `Issues `_ to see if someone else already had the same problem. - Before filing a bug report, try to create a minimal working example (MWE) that reproduces the problem. It's much easier to identify the cause for the problem if a handful of lines suffice to show that something isn't working. You can also always reach us at `quantum.cda@xcit.tum.de `_. diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 281f402..7d76acb 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -18,7 +18,7 @@ jobs: runs-on: ubuntu-latest environment: name: pypi - url: https://pypi.org/p/mqt.qcec + url: https://pypi.org/p/mqt.debugger permissions: id-token: write attestations: write diff --git a/CMakeLists.txt b/CMakeLists.txt index 944dbf1..42aba74 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,15 +1,16 @@ cmake_minimum_required(VERSION 3.26) project( - mqt_debug + mqt_debugger LANGUAGES CXX - DESCRIPTION "MQT Debug - A debugging tool for Quantum Circuits") + DESCRIPTION "MQT Debugger - A debugging tool for Quantum Circuits") -option(BUILD_MQT_DEBUG_BINDINGS "Build the MQT DEBUG Python bindings" OFF) -option(BUILD_MQT_DEBUG_TESTS "Also build tests for the MQT QCEC project" ON) +option(BUILD_MQT_DEBUGGER_BINDINGS "Build the MQT Debugger Python bindings" OFF) +option(BUILD_MQT_DEBUGGER_TESTS "Also build tests for the MQT Debugger project" ON) +option(BUILD_MQT_DEBUGGER_APP "Also build the CLI app for the MQT Debugger project" ON) set(CMAKE_CXX_STANDARD 17) -if(BUILD_MQT_DEBUG_BINDINGS) +if(BUILD_MQT_DEBUGGER_BINDINGS) # ensure that the BINDINGS option is set set(BINDINGS ON @@ -41,10 +42,12 @@ include(cmake/ExternalDependencies.cmake) add_subdirectory(src) # add test app -add_subdirectory(app) +if(BUILD_MQT_DEBUGGER_APP) + add_subdirectory(app) +endif() # add test code -if(BUILD_MQT_DEBUG_TESTS) +if(BUILD_MQT_DEBUGGER_TESTS) enable_testing() include(GoogleTest) add_subdirectory(test) @@ -52,5 +55,5 @@ endif() configure_file(${CMAKE_CURRENT_SOURCE_DIR}/cmake/cmake_uninstall.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake IMMEDIATE @ONLY) -add_custom_target(uninstall-debug COMMAND ${CMAKE_COMMAND} -P - ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake) +add_custom_target(uninstall-debugger COMMAND ${CMAKE_COMMAND} -P + ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake) diff --git a/README.md b/README.md index b1802c4..01250ae 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ -[![PyPI](https://img.shields.io/pypi/v/mqt.debug?logo=pypi&style=flat-square)](https://pypi.org/project/mqt.debug/) +[![PyPI](https://img.shields.io/pypi/v/mqt.debugger?logo=pypi&style=flat-square)](https://pypi.org/project/mqt.debugger/) ![OS](https://img.shields.io/badge/os-linux%20%7C%20macos%20%7C%20windows-blue?style=flat-square) [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square)](https://opensource.org/licenses/MIT) -[![CI](https://img.shields.io/github/actions/workflow/status/cda-tum/mqt-debug/ci.yml?branch=main&style=flat-square&logo=github&label=ci)](https://github.com/cda-tum/mqt-debug/actions/workflows/ci.yml) -[![CD](https://img.shields.io/github/actions/workflow/status/cda-tum/mqt-debug/cd.yml?style=flat-square&logo=github&label=cd)](https://github.com/cda-tum/mqt-debug/actions/workflows/cd.yml) -[![Documentation](https://img.shields.io/readthedocs/debug?logo=readthedocs&style=flat-square)](https://mqt.readthedocs.io/projects/debug) -[![codecov](https://img.shields.io/codecov/c/github/cda-tum/mqt-debug?style=flat-square&logo=codecov)](https://codecov.io/gh/cda-tum/mqt-debug) +[![CI](https://img.shields.io/github/actions/workflow/status/cda-tum/mqt-debugger/ci.yml?branch=main&style=flat-square&logo=github&label=ci)](https://github.com/cda-tum/mqt-debugger/actions/workflows/ci.yml) +[![CD](https://img.shields.io/github/actions/workflow/status/cda-tum/mqt-debugger/cd.yml?style=flat-square&logo=github&label=cd)](https://github.com/cda-tum/mqt-debugger/actions/workflows/cd.yml) +[![Documentation](https://img.shields.io/readthedocs/debugger?logo=readthedocs&style=flat-square)](https://mqt.readthedocs.io/projects/debugger) +[![codecov](https://img.shields.io/codecov/c/github/cda-tum/mqt-debugger?style=flat-square&logo=codecov)](https://codecov.io/gh/cda-tum/mqt-debugger)

@@ -15,54 +15,52 @@

-# MQT Debug - A Quantum Circuit Debugging Tool +# MQT Debugger - A Quantum Circuit Debugging Tool A tool for debugging quantum circuits developed as part of the [_Munich Quantum Toolkit (MQT)_](https://mqt.readthedocs.io) by the [Chair for Design Automation](https://www.cda.cit.tum.de/) at the [Technical University of Munich](https://www.tum.de/). It proposes an interface for the simulation of circuits and diagnosis of errors and provides a base implementation built upon [MQT Core](https://github.com/cda-tum/mqt-core), which forms the backbone of the MQT. It also provides a Debugger Adapter Protocol (DAP) server that can be used to integrate the debugger into IDEs.

- + Documentation

-If you have any questions, feel free to contact us via [quantum.cda@xcit.tum.de](mailto:quantum.cda@xcit.tum.de) or by creating an issue on [GitHub](https://github.com/cda-tum/mqt-debug/issues). +If you have any questions, feel free to contact us via [quantum.cda@xcit.tum.de](mailto:quantum.cda@xcit.tum.de) or by creating an issue on [GitHub](https://github.com/cda-tum/mqt-debugger/issues). ## Getting Started -MQT Debug is available via [PyPI](https://pypi.org/project/mqt.debug/) for Linux, macOS, and Windows and supports Python 3.8 to 3.12. +MQT Debugger is available via [PyPI](https://pypi.org/project/mqt.debugger/) for Linux, macOS, and Windows and supports Python 3.8 to 3.12. ```console -(venv) $ pip install mqt.debug +(venv) $ pip install mqt.debugger ``` The following code gives an example on the usage: ```python3 -from mqt import debug +from mqt import debugger -state = debug.create_ddsim_simulation_state() +state = debugger.create_ddsim_simulation_state() with open("code.qasm", "r") as f: state.load_code(f.read()) f.run_simulation() print(f.get_state_vector_full()) ``` -**Detailed documentation on all available methods, options, and input formats is available at [ReadTheDocs](https://mqt.readthedocs.io/projects/debug).** +**Detailed documentation on all available methods, options, and input formats is available at [ReadTheDocs](https://mqt.readthedocs.io/projects/debugger).** ## System Requirements and Building The implementation is compatible with any C++20 compiler, a minimum CMake version of 3.19, and Python 3.8+. -Please refer to the [documentation](https://mqt.readthedocs.io/projects/debug) on how to build the project. +Please refer to the [documentation](https://mqt.readthedocs.io/projects/debugger) on how to build the project. Building (and running) is continuously tested under Linux, macOS, and Windows using the [latest available system versions for GitHub Actions](https://github.com/actions/virtual-environments). ## References -MQT Debug has been developed based on methods proposed in the following papers: - -TODO +MQT Debugger has been developed based on methods proposed in a scientific paper that has not been released yet. ## Acknowledgements diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 62deed2..25ba47c 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -1,10 +1,10 @@ -add_executable(mqt_debug_app testDDSimDebugger.cpp) +add_executable(mqt_debugger_app testDDSimDebugger.cpp) # set include directories -target_include_directories(mqt_debug_app PUBLIC ${PROJECT_SOURCE_DIR}/include - ${PROJECT_BINARY_DIR}/include) +target_include_directories(mqt_debugger_app PUBLIC ${PROJECT_SOURCE_DIR}/include + ${PROJECT_BINARY_DIR}/include) # link to the MQT::Core libraries -target_link_libraries(mqt_debug_app PRIVATE ${PROJECT_NAME}) -target_link_libraries(mqt_debug_app PUBLIC MQT::CoreDD) -target_link_libraries(mqt_debug_app PRIVATE MQT::ProjectWarnings MQT::ProjectOptions) +target_link_libraries(mqt_debugger_app PRIVATE ${PROJECT_NAME}) +target_link_libraries(mqt_debugger_app PUBLIC MQT::CoreDD) +target_link_libraries(mqt_debugger_app PRIVATE MQT::ProjectWarnings MQT::ProjectOptions) diff --git a/app/testDDSimDebugger.cpp b/app/testDDSimDebugger.cpp index 7c2ea03..b3d31be 100644 --- a/app/testDDSimDebugger.cpp +++ b/app/testDDSimDebugger.cpp @@ -8,19 +8,25 @@ #include #include -#include +#include #include int main() { - std::ifstream file("../../app/code/test" - ".qasm"); + std::ifstream file("program.qasm"); + if (!file.is_open()) { + file.open("../../app/code/test" + ".qasm"); + } if (!file.is_open()) { std::cerr << "Could not open file\n"; file.close(); return 1; } - const std::string code((std::istreambuf_iterator(file)), - std::istreambuf_iterator()); + + std::stringstream buffer; + buffer << file.rdbuf(); + + const std::string code = buffer.str(); DDSimulationState state; createDDSimulationState(&state); diff --git a/cmake/ExternalDependencies.cmake b/cmake/ExternalDependencies.cmake index a15a016..1d0fcfc 100644 --- a/cmake/ExternalDependencies.cmake +++ b/cmake/ExternalDependencies.cmake @@ -1,7 +1,7 @@ include(FetchContent) set(FETCH_PACKAGES "") -if(BUILD_MQT_DEBUG_BINDINGS) +if(BUILD_MQT_DEBUGGER_BINDINGS) if(NOT SKBUILD) # Manually detect the installed pybind11 package. execute_process( @@ -87,7 +87,7 @@ else() endif() endif() -if(BUILD_MQT_DEBUG_TESTS) +if(BUILD_MQT_DEBUGGER_TESTS) set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) @@ -107,7 +107,7 @@ if(BUILD_MQT_DEBUG_TESTS) endif() endif() -if(BUILD_MQT_DEBUG_BINDINGS) +if(BUILD_MQT_DEBUGGER_BINDINGS) # add pybind11_json library if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.24) FetchContent_Declare( diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 0000000..cf076a7 --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1,2 @@ +build/ +doxygen/ diff --git a/docs/Doxyfile b/docs/Doxyfile new file mode 100644 index 0000000..6e7c9e7 --- /dev/null +++ b/docs/Doxyfile @@ -0,0 +1,143 @@ +# Doxyfile 1.9.8 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project. +# +# All text after a double hash (##) is considered a comment and is placed in +# front of the TAG it is preceding. +# +# All text after a single hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists, items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (\" \"). + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by +# double-quotes, unless you are using Doxywizard) that should identify the +# project for which the documentation is generated. This name is used in the +# title of most generated pages and in a few other places. +# The default value is: My Project. + +PROJECT_NAME = "MQT Debugger" + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer a +# quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = "A quantum circuit simulator based on decision diagrams written in C++" + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path +# into which the generated documentation will be written. If a relative path is +# entered, it will be relative to the location where doxygen was started. If +# left blank the current directory will be used. + +OUTPUT_DIRECTORY = doxygen + +#--------------------------------------------------------------------------- +# Configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag is used to specify the files and/or directories that contain +# documented source files. You may enter file names like myfile.cpp or +# directories like /usr/src/myproject. Separate the files or directories with +# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING +# Note: If this tag is empty the current directory is searched. + +INPUT = ../include ../src + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and +# *.h) to filter out the source-files in the directories. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# read by doxygen. +# +# Note the list of default checked file patterns might differ from the list of +# default file extension mappings. +# +# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cxxm, +# *.cpp, *.cppm, *.c++, *.c++m, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, +# *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp, *.h++, *.ixx, *.l, *.cs, *.d, *.php, +# *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to be +# provided as doxygen C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, +# *.f18, *.f, *.for, *.vhd, *.vhdl, *.ucf, *.qsf and *.ice. + +FILE_PATTERNS = *.c \ + *.cc \ + *.cxx \ + *.cpp \ + *.c++ \ + *.hh \ + *.hxx \ + *.hpp \ + *.h++ \ + *.h \ + *.py \ + *.pyi + +# The RECURSIVE tag can be used to specify whether or not subdirectories should +# be searched for input files as well. +# The default value is: NO. + +RECURSIVE = YES + +#--------------------------------------------------------------------------- +# Configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output +# The default value is: YES. + +GENERATE_HTML = NO + +#--------------------------------------------------------------------------- +# Configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES, doxygen will generate an XML file that +# captures the structure of the code including all documentation. +# The default value is: NO. + +GENERATE_XML = YES + +# The XML_OUTPUT tag is used to specify where the XML pages will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: xml. +# This tag requires that the tag GENERATE_XML is set to YES. + +XML_OUTPUT = xml + +#--------------------------------------------------------------------------- +# Configuration options related to diagram generator tools +#--------------------------------------------------------------------------- + +# If set to YES the inheritance and collaboration graphs will hide inheritance +# and usage relations if the target is undocumented or is not a class. +# The default value is: YES. + +HIDE_UNDOC_RELATIONS = YES + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz (see: +# https://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent +# Bell Labs. The other options in this section have no effect if this option is +# set to NO +# The default value is: YES. + +HAVE_DOT = NO + +#--------------------------------------------------------------------------- +# Configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output. +# The default value is: YES. + +GENERATE_LATEX = NO diff --git a/docs/source/Assertions.rst b/docs/source/Assertions.rst new file mode 100644 index 0000000..8f5e3e0 --- /dev/null +++ b/docs/source/Assertions.rst @@ -0,0 +1,116 @@ +Assertions +========== + +This document describes the syntax and semantics of assertions in MQT Debugger. +Assertions are a useful tool to test the correctness of programs by comparing the current state of the system to an expected state. +The assertions in MQT Debugger allow developers to test either the exact or approximate state or certain properties, such as entanglement or superposition. + +The following sections will give an overview of the assertion syntax and the different types of assertions that can be used in MQT Debugger. + +.. note:: + + The targets of an assertion can be either individual qubits or full quantum registers. However, when the selected targets are subset of the full quantum state, + then none of the qubits in this sub-state is allowed to be entangled with any other qubits outside of the sub-state. This is, because in many cases, exact sub-states + as state vectors are not well-defined if they are entangled with other qubits. + +Entanglement Assertion +###################### + +Entanglement assertions check whether a set of qubits is entangled. For this purpose, every qubit in the set is compared to all other qubits in the set, and +entanglement must exist between each possible pair. + +**QASM Syntax**: + +.. code-block:: + + assert-ent [, ...]*; + +*Example*: + +.. code-block:: + + assert-ent q[0], q[1], q[2]; + +This assertions checks for entanglement between qubits ``q[0]`` and ``q[1]``, ``q[0]`` and ``q[2]``, as well as ``q[1]`` and ``q[2]``. + +An example for a quantum state that would pass this assertion is the GHZ state: :math:`\frac{1}{\sqrt{2}}(|000\rangle + |111\rangle)`. +As none of the individual qubits is separable, this state is entangled. + +An example for a quantum state that would fail this assertion is the state :math:`\frac{1}{\sqrt{2}}(|000\rangle + |110\rangle)`. +As the least-significant qubit is separable, this state is not entangled. + + +Superposition Assertion +####################### + +Superposition assertions check whether a set of qubits is in a superposition state. +A set of qubits is in a superposition state if there exist at least two basis states of the full system that have different assignments for the target qubits and a non-zero amplitude. +This means, that not every qubit in the set must be in a superposition state individually, but the full set must be in a superposition state. + +**QASM Syntax**: + +.. code-block:: + + assert-sup [, ...]*; + +*Example*: + +.. code-block:: + + assert-sup q[0], q[1], q[2]; + +This assertion checks for superposition of qubits ``q[0]``, ``q[1]``, and ``q[2]``. + +An example for a quantum state that would pass this assertion is the state :math:`\frac{1}{\sqrt{2}}(|0\rangle + |1\rangle) \otimes |0\rangle`. +As two basis states have non-zero amplitudes (:math:`|00\rangle` and :math:`|10\rangle`), this state is in a superposition. + +An example for a quantum state that would fail this assertion is the state :math:`|00\rangle`. +In this case, only a single state (:math:`|00\rangle`) has a non-zero amplitude, so the state is not in a superposition. + + +Equality Assertion +################## + +Equality assertions compare the state of a set of qubits to a given state and fail if the states are not equal. +Furthermore, a similarity threshold can be passed to the assertion, allowing for approximate comparisons. The similarity is computed through the +cosine similarity of two functions and can be set between 0 and 1. If no similarity threshold is passed, the default value of 1 is used. + +The target state to compare to can be expressed as a state vector or as a new quantum circuit. +**QASM Syntax**: + +.. code-block:: + + assert-eq [similarity], [, ...]* { STATE_REPRESENTATION } + +.. code-block:: + + STATE_REPRESENTATION = + | + | + + STATEVECTOR = [, ...]* + + CIRCUIT = + +If the selected state representation is a state vector, it must represent a system of the same number of qubits as the assertion compares to +and use :math:`2^n` amplitudes. Amplitudes can be real or complex numbers using :math:`i` or :math:`j` for the imaginary unit. + +If the selected state representation is a circuit, it must be defined as a new quantum program in QASM format. The circuit must not contain any further assertions and +must use the same number of qubits as the assertion compares to. The current system state will then be compared to the state after running the circuit. + +*Example*: + +.. code-block:: + + assert-eq 0.9, q[0], q[1] { 0.5, 0.5, 0.5, 0.5 }; + +This assertion checks whether the state of qubits ``q[0]`` and ``q[1]`` is equal to the state :math:`\frac{1}{2}(|00\rangle + |01\rangle + |10\rangle + |11\rangle)` with a similarity threshold of 0.9. + +.. code-block:: + + assert-eq q[0], q[1] { + h q[0]; + cx q[0], q[1]; + }; + +This assertion checks whether the state of qubits ``q[0]`` and ``q[1]`` is equal to the bell state :math:`\frac{1}{\sqrt{2}}(|00\rangle + |11\rangle)`. diff --git a/docs/source/Contributing.rst b/docs/source/Contributing.rst new file mode 100644 index 0000000..1da5d54 --- /dev/null +++ b/docs/source/Contributing.rst @@ -0,0 +1 @@ +.. include:: ../../.github/contributing.rst diff --git a/docs/source/Debugging.rst b/docs/source/Debugging.rst new file mode 100644 index 0000000..dd040da --- /dev/null +++ b/docs/source/Debugging.rst @@ -0,0 +1,88 @@ +Interactive Debugging +===================== + +MQT Debugger provides various methods to interactively debug quantum programs. +It can be used to step through the program and inspect the state of the system. +This document further details the debugging capabilities provided by this framework. + +As MQT Debugger currently only supports OpenQASM as input language, this document will give examples and details related to OpenQASM programs. +However, due to the modular nature of this framework, it can be extended to support other languages as well. + +.. _stepping: + +Stepping Through the Program +---------------------------- + +The most basic debugging feature is stepping through the program. +Like many typical interactive debuggers, this framework supports three types of steps: + +- **Single Step**: Executes the next instruction and stops afterwards. If the next instruction is a function call, it will step into the function. +- **Step Over**: Executes the next instruction and stops afterwards. If the next instruction is a function call, it will execute the entire function at once. +- **Step Out**: Continues execution until the current function returns. + +However, due to the reversibility of quantum programs, the debugger can also step backward: + +- **Single Step Backward**: Reverts the last instruction and stops afterwards. If the last instruction was a function call, it will only stop at the last instruction of that function. +- **Step Over Backward**: Reverts the last instruction and stops afterwards. If the last instruction was a function call, it will revert the entire function at once. +- **Step Out Backward**: Reverts all instructions in the current function scope and stops at the instruction calling the function of the current scope. + +Continuing and Pausing Execution +-------------------------------- + +In contrast to individual steps, the debugger can also be used to continue the entire program execution, using +the :cpp:member:`SimulationState::runSimulation `/:py:meth:`SimulationState.run_simulation ` method. +This will execute the program until the end or until a :ref:`breakpoint ` or a :ref:`failing assertion ` is reached. + +Additionally, the :cpp:member:`SimulationState::runAll `/:py:meth:`SimulationState.run_all ` method can be used to run the program without stopping, +instead counting how many failing assertions were encountered. + +Furthermore, the :cpp:member:`SimulationState::pauseSimulation `/:py:meth:`SimulationState.pause_simulation ` method can be used to pause the execution at any point in time. + +Inspecting the State +-------------------- + +MQT Debugger distinguishes between classical variables and quantum variables. For OpenQASM, currently only boolean classical variables are supported. + +The framework provides different methods to inspect the state of the system at runtime. Classical variables can accessed using the +:cpp:member:`SimulationState::getClassicalVariable `/:py:meth:`SimulationState.get_classical_variable ` method, passing the name of the desired variable, +which returns an object representing the variable. + +Quantum variables cannot be accessed directly, but the developer can instead access the statevector to inspect the quantum state at any point in time. +:cpp:member:`SimulationState::getStateVectorFull `/:py:meth:`SimulationState.get_state_vector_full ` can be used to obtain the full statevector of the system. As this statevector can +be very large, :cpp:member:`SimulationState::getStateVectorSub `/:py:meth:`SimulationState.get_state_vector_sub ` can be used to obtain a sub-statevector of the system, containing +just a subset of all qubits. In this case, the qubits included in the sub-statevector must not be entangled with any qubits outside it. + +Furthermore, the framework also allows to inspect individual amplitude values of the statevector using +:cpp:member:`SimulationState::getAmplitudeIndex `:py:meth:`SimulationState.get_amplitude_index ` or :cpp:member:`SimulationState::getAmplitudeBitstring `/:py:meth:`SimulationState.get_amplitude_bitstring `. +In these cases, the developer must identify the desired amplitude by passing either the index of the amplitude or the bitstring that represents the desired state. + +.. _breakpoints: + +Breakpoints +----------- + +Breakpoints can be set to force execution to stop at a specific instruction. To be compatible with the typical protocols, setting a breakpoint +requires the character index in the source code, at which the breakpoint should be set. MQT Debugger will then determine the instruction that +corresponds to this location in the code and stop execution there in the future. + +To set a breakpoint, the :cpp:member:`SimulationState::setBreakpoint `/:py:meth:`SimulationState.set_breakpoint ` method can be used, passing the desired character index. This +method will return the instruction index at which the breakpoint was set (Python) or store it in the provided reference (C++). + +To remove breakpoints, the :cpp:member:`SimulationState::clearBreakpoints `/:py:meth:`SimulationState.clear_breakpoints ` method can be used, removing all breakpoints. + +When a program is paused during execution, the methods :cpp:member:`SimulationState::wasBreakpointHit `/:py:meth:`SimulationState.was_breakpoint_hit ` can be used to check whether the +current pause was caused by a breakpoint. This allows developers to distinguish between pauses due to breakpoints and pauses due to other reasons, such as :ref:`failed assertions `. + +.. _assertions: + +Assertions +---------- + +MQT Debugger supports :doc:`assertions ` to check the state of the system at runtime. +If an assertion fails, the debugger will pause execution **before** the failing instruction. + +Any methods to :ref:`step through the program ` will pause as a failing assertion. Continuing execution after hitting a failed assertion will skip +the failing instruction and continue with the next one. + +When a program is paused during execution, the methods :cpp:member:`SimulationState::didAssertionFail `/:py:meth:`SimulationState.did_assertion_fail ` can be used to check whether the +current pause was caused by a failing assertion. This allows developers to distinguish between pauses due to assertions and pauses due to other reasons, such as :ref:`breakpoints `. diff --git a/docs/source/DevelopmentGuide.rst b/docs/source/DevelopmentGuide.rst new file mode 100644 index 0000000..02fa627 --- /dev/null +++ b/docs/source/DevelopmentGuide.rst @@ -0,0 +1,59 @@ +Development Guide +================= + + +Installation +############ + +In order to start developing, clone the MQT Debugger repository using + + .. code-block:: console + + $ git clone https://github.com/cda-tum/mqt-debugger + +A C++ compiler supporting C++17 and a minimum CMake version of 3.19 are required to build the project. + +Working on the core C++ library +############################### + +Our projects use CMake as the main build configuration tool. +Building a project using CMake is a two-stage process. +First, CMake needs to be configured by calling + + .. code-block:: console + + $ cmake -S . -B build -DCMAKE_BUILD_TYPE=Release + +This tells CMake to search the current directory :code:`.` (passed via :code:`-S` for source) for a :code:`CMakeLists.txt` file and process it into a directory :code:`build` (passed via :code:`-B`). +The flag :code:`-DCMAKE_BUILD_TYPE=Release` tells CMake to configure a *Release* build (as opposed to, e.g., a *Debug* build). We also recommend setting the flags +:code:`-DEIGEN_BUILD_TESTING=OFF` and :code:`-DEIGEN_BUILD_DOC=OFF` to prevent the documentation and tests of the ``Eigen3`` library included in the project from being built. + +After configuring with CMake, the project can be built by calling + + .. code-block:: console + + $ cmake --build build --config Release + +This tries to build the project in the :code:`build` directory (passed via :code:`--build`). +Some operating systems and developer environments explicitly require a configuration to be set, which is why the :code:`--config` flag is also passed to the build command. The flag :code:`--parallel ` may be added to trigger a parallel build. + +Building the project this way generates + +- the main library :code:`libmqt_debugger.a` (Unix) / :code:`mqt_debugger.lib` (Windows) in the :code:`build/src` directory +- a test executable :code:`mqt_debugger_test` containing unit tests in the :code:`build/test` directory (this requires passing :code:`-DBUILD_MQT_DEBUGGER_TESTS=ON` to CMake during configuration) +- the Python bindings library :code:`pydebugger.<...>` in the :code:`build/src/python` directory (this requires passing :code:`-DBUILD_MQT_DEBUGGER_BINDINGS=ON` to CMake during configuration) +- the CLI App :code:`mqt_debugger_app` in the :code:`build/app` directory (this requires passing :code:`-DBUILD_MQT_DEBUGGER_APP=ON` to CMake during configuration) + +Working on the Python module +############################ + +The :code:`mqt.debugger` Python module can be conveniently built locally by calling + + .. code-block:: console + + (venv) $ pip install --editable . + +The :code:`--editable` flag ensures that changes in the Python code are instantly available without re-running the command. + +`Pybind11 `_ is used for providing bindings of the C++ core library to Python (see `bindings.cpp `_). +If parts of the C++ code have been changed, the above command has to be run again to make the changes visible in Python. diff --git a/docs/source/Diagnosis.rst b/docs/source/Diagnosis.rst new file mode 100644 index 0000000..3fd09f8 --- /dev/null +++ b/docs/source/Diagnosis.rst @@ -0,0 +1,86 @@ +Diagnosis Methods +================= + +This document describes the diagnostics methods available in MQT Debugger. +The diagnostics methods can be used to analyze the state of the system, in particular to find potential error causes when an :doc:`assertion ` fails. + +All of these methods require a :py:class:`Diagnostics ` object to be executed, which can be obtained from the :py:class:`SimulationState ` object using +:cpp:member:`SimulationState::getDiagnostics `/:py:meth:`SimulationState.get_diagnostics `. + +Error Cause Analysis +##################### + +When an assertion fails, several methods can be used to find potential error clauses. + +.. For further information, please refer to :cite:labelpar:`rovara2025debugging`. + +Cone of Influence Analysis +-------------------------- + +Cone of Influence Analysis partitions a given quantum program into two parts: the part that influences the failed assertion and the part that does not. +This analysis method can be used to reduce the number of instructions that need to be checked for potential error causes. If the Cone of Influence analysis manages to +narrow down just a small subset of the program, the error cause can be found more quickly. Furthermore, other analysis methods use the Cone of Influence as input +to find potential error causes more efficiently. + +The cone of influence can be obtained using :cpp:member:`Diagnostics::getDataDependencies `/:py:meth:`Diagnostics.get_data_dependencies `. +In the Python version, it requires a single instruction to be passed as an argument, and returns the indices of all instructions that influence it. +In the C++ version, in addition to the desired instruction, a pointer to a boolean array must be passed. Each element of the array corresponds to an instruction in the program and is set to ``true`` if the instruction is part of the cone of influence. +Finally, a boolean Flag (``includeCallers``) can be passed to also include instructions outside the current scope in the cone of influence. Otherwise, if the function is called +inside a custom gate definition, it will only include data dependencies within the gate. + +The cone of influence is computed by recursively iterating over the instructions in the current cone of influence and adding all instructions that influence them, until it reaches a fixed point. + +Interaction Analysis +-------------------- + +Interaction Analysis is a method to find potential error causes by analyzing the interactions between qubits in the system. +It is automatically called when using :cpp:member:`Diagnostics::potentialErrorCauses `/:py:meth:`Diagnostics.potential_error_causes `. + +This analysis method can be used to find reasons for failing entanglement assertions: +Whenever a failed entanglement assertion is encountered, the Interaction Analysis checks, whether the target qubits of the assertion interact with each other. + +If this is not the case, then clearly, no entanglement can be prepared, so it is treated as a potential error cause. The following code shows an example situation, +in which Interaction Analysis would find a potential error cause: + +.. code-block:: + + qreg q[3]; + + h q[0]; + cx q[0], q[1]; + + assert-ent q; + +Here, calling ``potential_error_causes()`` yields two errors: ``Missing interaction between q[0] and q[2]`` and ``Missing interaction between q[1] and q[2]``. + +.. note:: + Interaction Analysis is generally a static analysis method. However, when it is performed at runtime for the `potential_error_causes()` method, + it further uses dynamically obtained information to improve its results. During execution, the diagnostics tool keeps track of all actual qubits that + were involved in instructions, even inside custom gate definitions, where static analysis would not be able to determine the exact qubits involved. + This way, it can extend the interaction analysis throughout the entire program, even in other scopes. This is not always possible when performing interaction analysis statically. + +Control-Value Analysis +---------------------- + +Control-Value Analysis is a method that dynamically analyzes the program during execution to find incorrectly defined controlled gates. +In particular, it looks for controlled gates for which the control is always purely in the state :math:`|0\rangle`. In these cases, +the controlled gate will never affect the full state, which could be a sign for an error. + +This analysis also similarly checks for inverse-controlled gates (i.e., controlled gates that tirgger when the control value is :math:`|1\rangle`) that always +have the state :math:`|0\rangle` as control. + +It is automatically called when using :cpp:member:`Diagnostics::potentialErrorCauses `/:py:meth:`Diagnostics.potential_error_causes `. + +The following code shows an example situation, in which Control-Value Analysis would find a potential error cause: + +.. code-block:: + + qreg q[3]; + + h q[0]; + cx q[0], q[1]; + cx q[2], q[0]; + + assert-ent q; + +Here, calling ``potential_error_causes()`` yields the error ``Controlled gate with constant control value`` for instruction ``cx q[2], q[0]``. diff --git a/docs/source/Installation.rst b/docs/source/Installation.rst new file mode 100644 index 0000000..5e2649d --- /dev/null +++ b/docs/source/Installation.rst @@ -0,0 +1,96 @@ +Installation +============ + +MQT Debugger is mainly developed as a C++ library with many of its interfaces for important modules being defined in C. +Parts of the implementation build upon `MQT Core `_, which forms the backbone of the `MQT `_. +In order to make the tool as accessible as possible, it comes with an easy-to-use Python interface. + +We encourage installing MQT Debugger via pip (preferably in a `virtual environment `_): + + .. code-block:: console + + (venv) & pip install mqt.debugger + +In most practical cases (under 64-bit Linux, MacOS incl. Apple Silicon, and Windows), this requires no compilation and merely downloads and installs a platform-specific pre-built wheel. + +.. note:: + In order to set up a virtual environment, you can use the following commands: + + .. code-block:: console + + $ python3 -m venv venv + $ source venv/bin/activate + + If you are using Windows, you can use the following commands instead: + + .. code-block:: console + + $ python3 -m venv venv + $ venv\Scripts\activate.bat + + It is recommended to make sure that you are using the latest version of pip, setuptools, and wheel before trying to install the project: + + .. code-block:: console + + (venv) $ pip install --upgrade pip setuptools wheel + +A Detailed Walk Through +####################### + +First, save the following lines as :code:`ghz.qasm` in a folder where you want to install MQT Debugger: + + .. code-block:: + + qreg q[3]; + + h q[0]; + cx q[0], q[1]; + cx q[2], q[0]; + + assert-ent q; + +Then, create the following Python script as :code:`debug_ghz.py` in the same folder: + + .. code-block:: python + + import mqt.debugger as dbg + + state = dbg.create_ddsim_simulation_state() + with open("ghz.qasm") as f: + state.load_code(f.read()) + + state.run_simulation() + + if state.did_assertion_fail(): + print(f"Assertion failed at instruction {state.get_current_instruction() + 1}") + problems = state.get_diagnostics().potential_error_causes() + print("Potential Errors:") + for problem in problems: + print(f"{problem.type} at instruction {problem.instruction + 1}") + +The following snippet shows the installation process from setting up the virtual environment to running a small example program. + + .. code-block:: console + + $ python3 -m venv venv + $ . venv/bin/activate + (venv) $ pip install -U pip setuptools wheel + (venv) $ pip install mqt.debugger + (venv) $ python3 debug_ghz.py + +Building from Source for Performance +#################################### + +In order to get the best performance out of MQT Debugger and enable platform-specific compiler optimizations that cannot be enabled on portable wheels, it is recommended to build the package from source via: + + .. code-block:: console + + (venv) $ pip install mqt.debugger --no-binary mqt.debugger + +This requires a `C++ compiler `_ compiler supporting *C++17* and a minimum `CMake `_ version of *3.19*. + +The library is continuously tested under Linux, MacOS, and Windows using the `latest available system versions for GitHub Actions `_. +In order to access the latest build logs, visit `mqt-debugger/actions/workflows/ci.yml `_. + +.. note:: + We noticed some issues when compiling with Microsoft's *MSCV* compiler toolchain. If you want to start development on this project under Windows, consider using the *clang* compiler toolchain. A detailed description of how to set this up can be found `here `_. diff --git a/docs/source/Publications.rst b/docs/source/Publications.rst new file mode 100644 index 0000000..84ee863 --- /dev/null +++ b/docs/source/Publications.rst @@ -0,0 +1,14 @@ +Publications +============ + +.. *MQT Debugger* is academic software. Thus, many of its built-in algorithms have been published as scientific papers. + +.. *MQT Debugger* is academic software. Thus, its built-in algorithms have been published as a scientific paper. + +*MQT Debugger* is academic software. Thus, many of its built-in algorithms are being published as scientific papers. Currently, these publications are still under review but will be listed here in the future. + +.. If you use *MQT Debugger* in your work, we would appreciate if you cited: + +.. If you use *MQT Debugger* in your work, we would appreciate if you cited: :cite:labelpar:`rovara2025debugging`. + +.. bibliography:: diff --git a/docs/source/Quickstart.rst b/docs/source/Quickstart.rst new file mode 100644 index 0000000..de73699 --- /dev/null +++ b/docs/source/Quickstart.rst @@ -0,0 +1,100 @@ +Quickstart +========== + +This documentation gives a quick overview on how to get started with MQT Debugger using: +- The Python library +- The DAP server +- The CLI app + +Python Library +############## + +The Python bindings are offered to give an easy start into using MQT Debugger. + +Working with the Python library of MQT Debugger requires several preparation steps: + +.. code-block:: python + + import mqt.debugger as dbg + +All main functionalities are included in the module ``mqt.debugger``, no other modules need to be imported. + +.. code-block:: python + + state = dbg.create_ddsim_simulation_state() + +The first step is to create a simulation state. The current implementation of MQT Debugger implements a single simulation backend based on +decision diagrams from `MQT Core `_. This backend is instantiated by calling :py:func:`create_ddsim_simulation_state `. + +.. code-block:: python + + state.load_code(your_code) + +Before running the debugger, a quantum program must be loaded into the state. This is done by calling :py:meth:`SimulationState.load_code ` with the quantum program as a string argument. +Currently, the supported quantum program format is `QASM 2.0 `_. + +After this setup is done, the debugging process can be started by stepping through the code one instruction at a time: + +.. code-block:: python + + state.step_forward() + +or by running the full simulation: + +.. code-block:: python + + state.run_simulation() + +In the second case, simulation will be run until the program ends, a failing assertion is encountered, or a breakpoint is reached. +Further details on how to step through the program can be found in the :doc:`reference documentation `. + +Breakpoints can be set using + +.. code-block:: python + + state.set_breakpoint(character_index) + +where ``character_index`` is the index of the character in the original code's string, at which the breakpoint should be set. + +Assertions can be added to the code following the :doc:`assertion syntax ` and are automatically evaluated. + +When an assertion fails, the diagnostics methods can be used to get more information about the failure. To this end, first access the diagnostics interface: + +.. code-block:: python + + diagnostics = state.get_diagnostics() + +Then, the potential error causes can be retrieved: + +.. code-block:: python + + problems = diagnostics.potential_error_causes() + print(problems) + +DAP Server +########## + +This library provides a DAP Server that can be connected to from existing IDEs like Visual Studio Code or CLion. + +It can be started by calling + +.. code-block:: console + + python3 -m mqt.debugger.dap.adapter + +The server will then start on port 4711 and can accept one single connection from debugging clients. + +.. note:: + Connecting to the server requires a client compatible with the `Debug Adapter Protocol `_. + While most common IDEs already support it by default, some additional setup or extensions may be required to allow communication with arbitrary clients. + +The DAP Server provides all simulation methods that are accessible via the Python library. +On assertion failures, the server will automatically pause the simulation and send a message to the client containing possible error causes. + +CLI App +####### + +The CLI app is a standalone application that can be used to debug quantum programs from the command line. It is mainly supposed to be used as a testing tool +and thus does not provide all the features of the framework or full accessibility through CLI parameters. + +Instead, the CLI app will open a OpenQASM file with the name :code:`program.qasm` in the current working directory and start the debugging process for it. diff --git a/docs/source/Support.rst b/docs/source/Support.rst new file mode 100644 index 0000000..a88fd1a --- /dev/null +++ b/docs/source/Support.rst @@ -0,0 +1 @@ +.. include:: ../../.github/support.rst diff --git a/docs/source/_static/custom.css b/docs/source/_static/custom.css new file mode 100644 index 0000000..4aeb0aa --- /dev/null +++ b/docs/source/_static/custom.css @@ -0,0 +1,40 @@ +.acknowledgements { + margin-top: 1rem; + padding-bottom: 1rem; + padding-top: 1rem; + border-top: 1px solid var(--color-background-border); + font-size: var(--font-size--small); + color: var(--color-foreground-secondary); +} + +.acknowledgements-logos { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(100px, 1fr)); + grid-gap: 1em; + align-items: center; + margin-top: 0.5rem; +} +.acknowledgement { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +/* override the default background color for literal strings */ +body:not([data-theme="light"]) .highlight .sa, +.highlight .sb, +.highlight .sc, +.highlight .dl, +.highlight .sd, +.highlight .s2, +.highlight .se, +.highlight .sh, +.highlight .si, +.highlight .sx, +.highlight .sr, +.highlight .s1, +.highlight .ss, +.highlight .s1 { + background-color: #00000001; +} diff --git a/docs/source/_static/mqt_dark.png b/docs/source/_static/mqt_dark.png new file mode 100644 index 0000000..adc910c Binary files /dev/null and b/docs/source/_static/mqt_dark.png differ diff --git a/docs/source/_static/mqt_light.png b/docs/source/_static/mqt_light.png new file mode 100644 index 0000000..be279e6 Binary files /dev/null and b/docs/source/_static/mqt_light.png differ diff --git a/docs/source/_templates/page.html b/docs/source/_templates/page.html new file mode 100644 index 0000000..4aad54d --- /dev/null +++ b/docs/source/_templates/page.html @@ -0,0 +1,76 @@ +{% extends "furo/page.html" %} {% block footer %} + +
+ The Munich Quantum Toolkit has been supported by the European Research Council + (ERC) under the European Union's Horizon 2020 research and innovation program + (grant agreement No. 101001318), the Bavarian State Ministry for Science and + Arts through the Distinguished Professorship Program, as well as the Munich + Quantum Valley, which is supported by the Bavarian state government with funds + from the Hightech Agenda Bayern Plus. +
+
+ TUM Logo +
+
+ TUM Logo +
+
+ Coat of Arms of Bavaria +
+
+ ERC Logo +
+
+ ERC Logo +
+
+ MQV Logo +
+
+
+{% endblock footer %} diff --git a/docs/source/conf.py b/docs/source/conf.py new file mode 100644 index 0000000..c51e87b --- /dev/null +++ b/docs/source/conf.py @@ -0,0 +1,153 @@ +"""Sphinx configuration file.""" + +from __future__ import annotations + +import warnings +from importlib import metadata +from pathlib import Path +from typing import TYPE_CHECKING + +import pybtex.plugin +from pybtex.style.formatting.unsrt import Style as UnsrtStyle +from pybtex.style.template import field, href + +if TYPE_CHECKING: + from pybtex.database import Entry + from pybtex.richtext import HRef + +ROOT = Path(__file__).parent.parent.resolve() + +set_type_checking_flag = True + + +try: + from mqt.debugger import __version__ as version +except ModuleNotFoundError: + try: + version = metadata.version("mqt.debugger") + except ModuleNotFoundError: + msg = ( + "Package should be installed to produce documentation! " + "Assuming a modern git archive was used for version discovery." + ) + warnings.warn(msg, stacklevel=1) + + from setuptools_scm import get_version + + version = get_version(root=str(ROOT), fallback_root=ROOT) + +# Filter git details from version +release = version.split("+")[0] + +project = "MQT Debugger" +author = "Damian Rovara" +language = "en" +project_copyright = "Chair for Design Automation, Technical University of Munich" + +master_doc = "index" + +templates_path = ["_templates"] +html_css_files = ["custom.css"] + +extensions = [ + "sphinx.ext.napoleon", + "sphinx.ext.autodoc", + "sphinx.ext.autosummary", + "sphinx.ext.mathjax", + "sphinx.ext.intersphinx", + "sphinx.ext.autosectionlabel", + "sphinx.ext.viewcode", + "sphinx.ext.githubpages", + "sphinxcontrib.bibtex", + "sphinx_copybutton", + "hoverxref.extension", + "nbsphinx", + "sphinxext.opengraph", + "sphinx_autodoc_typehints", + "breathe", +] + +breathe_projects = {"mqt-debugger": str(ROOT / "doxygen/xml")} +breathe_default_project = "MQT Debugger" + +pygments_style = "colorful" + +add_module_names = False + +modindex_common_prefix = ["mqt.debugger."] + +intersphinx_mapping = { + "python": ("https://docs.python.org/3", None), + "typing_extensions": ("https://typing-extensions.readthedocs.io/en/latest", None), + "qiskit": ("https://qiskit.org/documentation", None), + "mqt": ("https://mqt.readthedocs.io/en/latest", None), + "core": ("https://mqt.readthedocs.io/projects/core/en/latest", None), + "ddsim": ("https://mqt.readthedocs.io/projects/ddsim/en/latest", None), + "qmap": ("https://mqt.readthedocs.io/projects/qmap/en/latest", None), + "qecc": ("https://mqt.readthedocs.io/projects/qecc/en/latest", None), + "syrec": ("https://mqt.readthedocs.io/projects/syrec/en/latest", None), +} + +nbsphinx_execute = "auto" +highlight_language = "python3" +nbsphinx_execute_arguments = [ + "--InlineBackend.figure_formats={'svg', 'pdf'}", + "--InlineBackend.rc=figure.dpi=200", +] +nbsphinx_kernel_name = "python3" + +autosectionlabel_prefix_document = True + +hoverxref_auto_ref = True +hoverxref_domains = ["cite", "py"] +hoverxref_roles = [] +hoverxref_mathjax = True +hoverxref_role_types = { + "ref": "tooltip", + "p": "tooltip", + "labelpar": "tooltip", + "class": "tooltip", + "meth": "tooltip", + "func": "tooltip", + "attr": "tooltip", + "property": "tooltip", +} +exclude_patterns = ["_build", "build", "**.ipynb_checkpoints", "Thumbs.db", ".DS_Store", ".env"] + + +class CDAStyle(UnsrtStyle): + """Custom style for including PDF links.""" + + def format_url(self, _e: Entry) -> HRef: # noqa: PLR6301 + """Format URL field as a link to the PDF.""" + url = field("url", raw=True) + return href()[url, "[PDF]"] + + +pybtex.plugin.register_plugin("pybtex.style.formatting", "cda_style", CDAStyle) + +bibtex_bibfiles = ["refs.bib"] +bibtex_default_style = "cda_style" + +copybutton_prompt_text = r"(?:\(venv\) )?(?:\[.*\] )?\$ " +copybutton_prompt_is_regexp = True +copybutton_line_continuation_character = "\\" + +autosummary_generate = True + +typehints_use_rtype = False +napoleon_use_rtype = False +napoleon_google_docstring = True +napoleon_numpy_docstring = False + +# -- Options for HTML output ------------------------------------------------- +html_theme = "furo" +html_static_path = ["_static"] +html_theme_options = { + "light_logo": "mqt_dark.png", + "dark_logo": "mqt_light.png", + "source_repository": "https://github.com/cda-tum/mqt-debugger/", + "source_branch": "main", + "source_directory": "docs/source", + "navigation_with_keys": True, +} diff --git a/docs/source/index.rst b/docs/source/index.rst new file mode 100644 index 0000000..c96b8a4 --- /dev/null +++ b/docs/source/index.rst @@ -0,0 +1,50 @@ +Welcome to MQT Debugger's documentation! +======================================== + +MQT Debugger is a comprehensive framework for debugging and analyzing the behaviour of quantum programs. It is developed by the `Chair for Design Automation `_ at the `Technical University of Munich `_ as part of the :doc:`Munich Quantum Toolkit ` (*MQT*). + +The framework provides tools for running quantum programs in a simulated environment, stepping through the code instruction-by-instruction, and displaying the quantum state at each step. +It also allows developers to include assertions in their code, testing the correctness of provided programs, and, when an assertion fails, suggesting possible causes of the failure. + +MQT Debugger is accessible as a stand-alone application, as a C++ and Python library to include in custom programs, and as a DAP server that can be accessed by popular IDEs such as Visual Studio Code and CLion. + +We recommend you to start with the :doc:`installation instructions `. +Then proceed to the :doc:`quickstart guide ` and read the :doc:`reference documentation `. +If you are interested in the theory behind MQT Debugger, have a look at the publications in the :doc:`publication list `. + +We appreciate any feedback and contributions to the project. If you want to contribute, you can find more information in the :doc:`Contribution ` guide. If you are having trouble with the installation or the usage of QCEC, please let us know at our :doc:`Support ` page. + +---- + + .. toctree:: + :hidden: + + self + + .. toctree:: + :maxdepth: 2 + :caption: User Guide + :glob: + + Installation + Quickstart + Debugging + Assertions + Diagnosis + Publications + + .. toctree:: + :maxdepth: 2 + :caption: Developers + :glob: + + Contributing + DevelopmentGuide + Support + + .. toctree:: + :maxdepth: 6 + :caption: API Reference + :glob: + + library/Library diff --git a/docs/source/library/Library.rst b/docs/source/library/Library.rst new file mode 100644 index 0000000..d7c1953 --- /dev/null +++ b/docs/source/library/Library.rst @@ -0,0 +1,10 @@ +Library +======= + + .. toctree:: + :maxdepth: 4 + + interface/Interface + dd/Dd + parsing/Parsing + python/Python diff --git a/docs/source/library/dd/DDSimDebug.rst b/docs/source/library/dd/DDSimDebug.rst new file mode 100644 index 0000000..264ed04 --- /dev/null +++ b/docs/source/library/dd/DDSimDebug.rst @@ -0,0 +1,5 @@ +DDSimDebug.hpp +============== + +.. doxygenfile:: DDSimDebug.hpp + :project: mqt-debugger diff --git a/docs/source/library/dd/DDSimDiagnostics.rst b/docs/source/library/dd/DDSimDiagnostics.rst new file mode 100644 index 0000000..f73241d --- /dev/null +++ b/docs/source/library/dd/DDSimDiagnostics.rst @@ -0,0 +1,5 @@ +DDSimDiagnostics.hpp +==================== + +.. doxygenfile:: DDSimDiagnostics.hpp + :project: mqt-debugger diff --git a/docs/source/library/dd/Dd.rst b/docs/source/library/dd/Dd.rst new file mode 100644 index 0000000..de2bf6c --- /dev/null +++ b/docs/source/library/dd/Dd.rst @@ -0,0 +1,11 @@ +DD Implementation +================= + +This section documents the implementation of the C interfaces specifically using the DD package from `MQT Core `_ +as a simulation backend and OpenQASM as the input language. + + .. toctree:: + :maxdepth: 4 + + DDSimDebug + DDSimDiagnostics diff --git a/docs/source/library/interface/Common.rst b/docs/source/library/interface/Common.rst new file mode 100644 index 0000000..f177ffe --- /dev/null +++ b/docs/source/library/interface/Common.rst @@ -0,0 +1,5 @@ +common.h +======== + +.. doxygenfile:: common.h + :project: mqt-debugger diff --git a/docs/source/library/interface/Debug.rst b/docs/source/library/interface/Debug.rst new file mode 100644 index 0000000..6261510 --- /dev/null +++ b/docs/source/library/interface/Debug.rst @@ -0,0 +1,10 @@ +debug.h +======= + +.. note:: + + As the `SimulationState` interface is defined in C, "member functions" are declared as function pointers. + When using the interface, these function pointers can be accessed like normal C++ methods. + +.. doxygenfile:: debug.h + :project: mqt-debugger diff --git a/docs/source/library/interface/Diagnostics.rst b/docs/source/library/interface/Diagnostics.rst new file mode 100644 index 0000000..2c421ac --- /dev/null +++ b/docs/source/library/interface/Diagnostics.rst @@ -0,0 +1,10 @@ +diagnostics.h +============= + +.. note:: + + As the `Diagnostics` interface is defined in C, "member functions" are declared as function pointers. + When using the interface, these function pointers can be accessed like normal C++ methods. + +.. doxygenfile:: diagnostics.h + :project: mqt-debugger diff --git a/docs/source/library/interface/Interface.rst b/docs/source/library/interface/Interface.rst new file mode 100644 index 0000000..1e7fe4e --- /dev/null +++ b/docs/source/library/interface/Interface.rst @@ -0,0 +1,16 @@ +C Interface +=========== + +This section documents the interface that must be implemented for a debugger to be compatible with this framework. + +The interface is written in C and can be implemented with any compatible language. We provide an :doc:`example implementation <../dd/Dd>` in C++ +based on the DD simulation backend from `MQT Core `_. + + .. toctree:: + :maxdepth: 4 + :caption: C-Interfaces + :glob: + + Common + Debug + Diagnostics diff --git a/docs/source/library/parsing/AssertionParsing.rst b/docs/source/library/parsing/AssertionParsing.rst new file mode 100644 index 0000000..f2ffd91 --- /dev/null +++ b/docs/source/library/parsing/AssertionParsing.rst @@ -0,0 +1,5 @@ +AssertionParsing.hpp +===================== + +.. doxygenfile:: AssertionParsing.hpp + :project: mqt-debugger diff --git a/docs/source/library/parsing/CodePreprocessing.rst b/docs/source/library/parsing/CodePreprocessing.rst new file mode 100644 index 0000000..fe29b5c --- /dev/null +++ b/docs/source/library/parsing/CodePreprocessing.rst @@ -0,0 +1,5 @@ +CodePreprocessing.hpp +===================== + +.. doxygenfile:: CodePreprocessing.hpp + :project: mqt-debugger diff --git a/docs/source/library/parsing/Parsing.rst b/docs/source/library/parsing/Parsing.rst new file mode 100644 index 0000000..de08cdb --- /dev/null +++ b/docs/source/library/parsing/Parsing.rst @@ -0,0 +1,13 @@ +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. + + .. toctree:: + :maxdepth: 4 + + AssertionParsing + CodePreprocessing + ParsingError + Utils diff --git a/docs/source/library/parsing/ParsingError.rst b/docs/source/library/parsing/ParsingError.rst new file mode 100644 index 0000000..2e3b673 --- /dev/null +++ b/docs/source/library/parsing/ParsingError.rst @@ -0,0 +1,5 @@ +ParsingError.hpp +===================== + +.. doxygenfile:: ParsingError.hpp + :project: mqt-debugger diff --git a/docs/source/library/parsing/Utils.rst b/docs/source/library/parsing/Utils.rst new file mode 100644 index 0000000..1deb33b --- /dev/null +++ b/docs/source/library/parsing/Utils.rst @@ -0,0 +1,5 @@ +Utils.hpp +===================== + +.. doxygenfile:: Utils.hpp + :project: mqt-debugger diff --git a/docs/source/library/python/Debug.rst b/docs/source/library/python/Debug.rst new file mode 100644 index 0000000..1cb594a --- /dev/null +++ b/docs/source/library/python/Debug.rst @@ -0,0 +1,5 @@ +mqt.debugger +============ + +.. automodule:: mqt.debugger + :members: diff --git a/docs/source/library/python/Python.rst b/docs/source/library/python/Python.rst new file mode 100644 index 0000000..e8bfcee --- /dev/null +++ b/docs/source/library/python/Python.rst @@ -0,0 +1,10 @@ +Python +====== + +This section documents the Python modules that are provided by this framework. + + .. toctree:: + :maxdepth: 4 + + Debug + dap/DAP diff --git a/docs/source/library/python/dap/Adapter.rst b/docs/source/library/python/dap/Adapter.rst new file mode 100644 index 0000000..444e3f0 --- /dev/null +++ b/docs/source/library/python/dap/Adapter.rst @@ -0,0 +1,16 @@ +mqt.debugger.dap.adapter +============================ + +.. automodule:: mqt.debugger.dap.adapter + :members: + :undoc-members: + :show-inheritance: + + +Starting this module directly using + +.. code-block:: console + + python -m mqt.debugger.dap.adapter + +will start the Adapter server on the default port 4711. diff --git a/docs/source/library/python/dap/DAP.rst b/docs/source/library/python/dap/DAP.rst new file mode 100644 index 0000000..ef4e2c4 --- /dev/null +++ b/docs/source/library/python/dap/DAP.rst @@ -0,0 +1,13 @@ +mqt.debugger.dap +================= + +.. automodule:: mqt.debugger.dap + :members: + :undoc-members: + :show-inheritance: + +.. toctree:: + :maxdepth: 4 + + Adapter + Messages diff --git a/docs/source/library/python/dap/Messages.rst b/docs/source/library/python/dap/Messages.rst new file mode 100644 index 0000000..5f45cd1 --- /dev/null +++ b/docs/source/library/python/dap/Messages.rst @@ -0,0 +1,7 @@ +mqt.debugger.dap.messages +========================= + +.. automodule:: mqt.debugger.dap.messages + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/refs.bib b/docs/source/refs.bib new file mode 100644 index 0000000..e69de29 diff --git a/include/backend/dd/DDSimDebug.hpp b/include/backend/dd/DDSimDebug.hpp index 7a06edf..4a51ae4 100644 --- a/include/backend/dd/DDSimDebug.hpp +++ b/include/backend/dd/DDSimDebug.hpp @@ -434,11 +434,11 @@ size_t ddsimGetNumQubits(SimulationState* self); * representation of the state. * * @param self The instance to query. - * @param qubit The index of the qubit. + * @param index The index of the state. * @param output A reference to a `Complex` instance to store the amplitude. * @return The result of the operation. */ -Result ddsimGetAmplitudeIndex(SimulationState* self, size_t qubit, +Result ddsimGetAmplitudeIndex(SimulationState* self, size_t index, Complex* output); /** * @brief Gets the complex amplitude of a state in the full state vector. @@ -446,7 +446,7 @@ Result ddsimGetAmplitudeIndex(SimulationState* self, size_t qubit, * The amplitude is selected by a bitstring representing the state. * * @param self The instance to query. - * @param qubit The index of the qubit as a bitstring. + * @param bitstring The index of the qubit as a bitstring. * @param output A reference to a `Complex` instance to store the amplitude. * @return The result of the operation. */ diff --git a/include/backend/debug.h b/include/backend/debug.h index ded0a4c..cb12ce5 100644 --- a/include/backend/debug.h +++ b/include/backend/debug.h @@ -15,11 +15,6 @@ extern "C" { #endif -/** - * @brief A C-style interface for the debugging and simulation interface. - */ -typedef struct SimulationState SimulationState; - /** * @brief A C-style interface for the debugging and simulation interface. * @@ -31,7 +26,9 @@ typedef struct SimulationState SimulationState; * Continuing the simulation from there will skip the failing assertion and * `didAssertionFail` will return `false` until another assertion fails. */ -struct SimulationState { +typedef struct SimulationStateStruct SimulationState; + +struct SimulationStateStruct { /** * @brief Initializes the simulation state. * @param self The instance to initialize. @@ -168,11 +165,11 @@ struct SimulationState { bool (*canStepBackward)(SimulationState* self); /** - * @brief Indicates whether the simulation has finished. + * @brief Indicates whether the execution has finished. * - * The simulation is considered finished if it has reached the end of the code + * The execution is considered finished if it has reached the end of the code. * @param self The instance to query. - * @return True if the simulation has finished, false otherwise. + * @return True if the execution has finished, false otherwise. */ bool (*isFinished)(SimulationState* self); @@ -237,11 +234,11 @@ struct SimulationState { * binary representation of the state. * * @param self The instance to query. - * @param qubit The index of the qubit. + * @param index The index of the state. * @param output A reference to a `Complex` instance to store the amplitude. * @return The result of the operation. */ - Result (*getAmplitudeIndex)(SimulationState* self, size_t qubit, + Result (*getAmplitudeIndex)(SimulationState* self, size_t index, Complex* output); /** @@ -250,7 +247,7 @@ struct SimulationState { * The amplitude is selected by a bitstring representing the state. * * @param self The instance to query. - * @param qubit The index of the qubit as a bitstring. + * @param bitstring The index of the state as a bitstring. * @param output A reference to a `Complex` instance to store the amplitude. * @return The result of the operation. */ diff --git a/include/backend/diagnostics.h b/include/backend/diagnostics.h index de39d92..36400d5 100644 --- a/include/backend/diagnostics.h +++ b/include/backend/diagnostics.h @@ -40,8 +40,9 @@ typedef enum { /** * @brief Represents an error cause. */ -typedef struct ErrorCause ErrorCause; -struct ErrorCause { +typedef struct ErrorCauseStruct ErrorCause; + +struct ErrorCauseStruct { /** * @brief The type of the error cause. */ @@ -56,11 +57,9 @@ struct ErrorCause { /** * @brief An interface representing the diagnostic capabilities of a debugger. */ -typedef struct Diagnostics Diagnostics; -/** - * @brief An interface representing the diagnostic capabilities of a debugger. - */ -struct Diagnostics { +typedef struct DiagnosticsStruct Diagnostics; + +struct DiagnosticsStruct { /** * @brief Initializes the diagnostics interface. * @param self The instance to initialize. @@ -103,7 +102,7 @@ struct Diagnostics { * * @param self The diagnostics instance to query. * @param instruction The instruction to extract the data dependencies for. - * @param includeCallers True if the data dependencies should include all + * @param includeCallers True, if the data dependencies should include all * possible callers of the containing custom gate. * @param instructions An array of booleans that will be set to true for each * instruction that is a data dependency. diff --git a/include/common.h b/include/common.h index 7e21223..0954132 100644 --- a/include/common.h +++ b/include/common.h @@ -10,7 +10,6 @@ // NOLINTBEGIN(modernize-use-using, performance-enum-size) #ifndef __cplusplus -typedef char bool; #else #include extern "C" { diff --git a/pyproject.toml b/pyproject.toml index af60f1e..4f79387 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,7 @@ requires = ["scikit-build-core>=0.8.1", "setuptools-scm>=7", "pybind11>=2.13"] build-backend = "scikit_build_core.build" [project] -name = "mqt-debug" +name = "mqt-debugger" description = "A quantum circuit debugging tool" readme = "README.md" authors = [ @@ -36,8 +36,8 @@ requires-python = ">=3.8" dynamic = ["version"] [project.optional-dependencies] -test = ["pytest>=7.0", "pytest-console-scripts>=1.4", "mqt.debug[qiskit, evaluation]"] -coverage = ["mqt.debug[test]", "pytest-cov>=4"] +test = ["pytest>=7.0", "pytest-console-scripts>=1.4", "mqt.debugger[qiskit, evaluation]"] +coverage = ["mqt.debugger[test]", "pytest-cov>=4"] evaluation = [ "pandas[output_formatting]>=2.0; python_version < '3.9'", "pandas[output-formatting]>=2.1.2; python_version >= '3.9'", @@ -57,21 +57,21 @@ docs = [ "ipykernel", "sphinx-autoapi", "qiskit[visualization]", - "mqt.debug[evaluation]" + "mqt.debugger[evaluation]" ] qiskit = [ "qiskit[qasm3-import]>=1.0.0", ] -dev = ["mqt.debug[coverage, docs]",] +dev = ["mqt.debugger[coverage, docs]",] [project.scripts] -mqt-debug-compare = "mqt.debug.evaluation:main" +mqt-debugger-compare = "mqt.debugger.evaluation:main" [project.urls] -Homepage = "https://github.com/cda-tum/mqt-debug" -Documentation = "https://mqt.readthedocs.io/projects/debug" -Issues = "https://github.com/cda-tum/mqt-debug/issues" -Discussions = "https://github.com/cda-tum/mqt-debug/discussions" +Homepage = "https://github.com/cda-tum/mqt-debugger" +Documentation = "https://mqt.readthedocs.io/projects/debugger" +Issues = "https://github.com/cda-tum/mqt-debugger/issues" +Discussions = "https://github.com/cda-tum/mqt-debugger/discussions" [tool.scikit-build] @@ -79,7 +79,7 @@ Discussions = "https://github.com/cda-tum/mqt-debug/discussions" minimum-version = "0.8.1" # Set the wheel install directory -wheel.install-dir = "mqt/debug" +wheel.install-dir = "mqt/debugger" # Set required CMake and Ninja versions cmake.version = ">=3.19" @@ -92,13 +92,13 @@ build-dir = "build/{wheel_tag}" wheel.packages = ["src/mqt"] # Only build the Python bindings target -cmake.targets = ["pydebug"] +cmake.targets = ["pydebugger"] # Only install the Python package component -install.components = ["mqt-debug_Python"] +install.components = ["mqt-debugger_Python"] metadata.version.provider = "scikit_build_core.metadata.setuptools_scm" -sdist.include = ["src/mqt/debug/_version.py"] +sdist.include = ["src/mqt/debugger/_version.py"] sdist.exclude = [ "**/.github", "**/doc", @@ -110,19 +110,20 @@ sdist.exclude = [ ] [tool.check-sdist] -sdist-only = ["src/mqt/debug/_version.py"] +sdist-only = ["src/mqt/debugger/_version.py"] git-only = [ "docs/*", "test/*", ] [tool.scikit-build.cmake.define] -BUILD_MQT_DEBUG_BINDINGS = "ON" -BUILD_MQT_DEBUG_TESTS = "OFF" +BUILD_MQT_DEBUGGER_BINDINGS = "ON" +BUILD_MQT_DEBUGGER_TESTS = "OFF" +BUILD_MQT_DEBUGGER_APP = "OFF" [tool.setuptools_scm] -write_to = "src/mqt/debug/_version.py" +write_to = "src/mqt/debugger/_version.py" [tool.pytest.ini_options] @@ -140,7 +141,7 @@ testpaths = ["test/python"] [tool.coverage] -run.source = ["mqt.debug"] +run.source = ["mqt.debugger"] run.omit = [ '*/_compat/*', ] @@ -228,7 +229,7 @@ ignore = [ "S101", # Use of assert detected "S404", # `subprocess` module is possibly insecure ] -typing-modules = ["mqt.debug._compat.typing"] +typing-modules = ["mqt.debugger._compat.typing"] isort.required-imports = ["from __future__ import annotations"] [tool.ruff.lint.flake8-tidy-imports.banned-api] @@ -237,9 +238,9 @@ isort.required-imports = ["from __future__ import annotations"] "typing.Mapping".msg = "Use collections.abc.Mapping instead." "typing.Sequence".msg = "Use collections.abc.Sequence instead." "typing.Set".msg = "Use collections.abc.Set instead." -"typing.Self".msg = "Use mqt.debug._compat.typing.Self instead." -"typing_extensions.Self".msg = "Use mqt.debug._compat.typing.Self instead." -"typing.assert_never".msg = "Use mqt.debug._compat.typing.assert_never instead." +"typing.Self".msg = "Use mqt.debugger._compat.typing.Self instead." +"typing_extensions.Self".msg = "Use mqt.debugger._compat.typing.Self instead." +"typing.assert_never".msg = "Use mqt.debugger._compat.typing.assert_never instead." [tool.ruff.lint.per-file-ignores] "test/python/**" = ["T20", "ANN"] @@ -251,9 +252,9 @@ isort.required-imports = ["from __future__ import annotations"] "E402", # Allow imports to appear anywhere in Jupyter notebooks "I002", # Allow missing `from __future__ import annotations` import ] -"src/mqt/debug/_compat/**.py" = ["TID251"] -"src/mqt/debug/evaluation.py" = ["T201"] -"src/mqt/debug/__main__.py" = ["T201"] +"src/mqt/debugger/_compat/**.py" = ["TID251"] +"src/mqt/debugger/evaluation.py" = ["T201"] +"src/mqt/debugger/__main__.py" = ["T201"] [tool.ruff.lint.pydocstyle] convention = "google" @@ -276,7 +277,7 @@ ignore = [ build = "cp3*" skip = "*-musllinux_*" archs = "auto64" -test-command = "python -c \"from mqt import debug\"" +test-command = "python -c \"from mqt import debugger\"" test-skip = "cp38-macosx_arm64" build-frontend = "build[uv]" @@ -289,4 +290,3 @@ environment = { MACOSX_DEPLOYMENT_TARGET = "10.15" } [tool.cibuildwheel.windows] before-build = "uv pip install delvewheel>=1.4.0" repair-wheel-command = "delvewheel repair -w {dest_dir} {wheel} --namespace-pkg mqt" -environment = { CMAKE_GENERATOR = "Ninja" } diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index abb9c7b..8d56e88 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -18,8 +18,8 @@ target_link_libraries(${PROJECT_NAME} PRIVATE MQT::ProjectWarnings MQT::ProjectO target_link_libraries(${PROJECT_NAME} PRIVATE Eigen3::Eigen) # add MQT alias -add_library(MQT::Debug ALIAS ${PROJECT_NAME}) +add_library(MQT::Debugger ALIAS ${PROJECT_NAME}) -if(BUILD_MQT_DEBUG_BINDINGS) +if(BUILD_MQT_DEBUGGER_BINDINGS) add_subdirectory(python) endif() diff --git a/src/backend/dd/DDSimDebug.cpp b/src/backend/dd/DDSimDebug.cpp index 7de4f09..a08b174 100644 --- a/src/backend/dd/DDSimDebug.cpp +++ b/src/backend/dd/DDSimDebug.cpp @@ -665,10 +665,10 @@ size_t ddsimGetNumQubits(SimulationState* self) { return ddsim->qc->getNqubits(); } -Result ddsimGetAmplitudeIndex(SimulationState* self, size_t qubit, +Result ddsimGetAmplitudeIndex(SimulationState* self, size_t index, Complex* output) { auto* ddsim = toDDSimulationState(self); - auto result = ddsim->simulationState.getValueByIndex(qubit); + auto result = ddsim->simulationState.getValueByIndex(index); output->real = result.real(); output->imaginary = result.imag(); return OK; diff --git a/src/mqt/debug/pydebug.pyi b/src/mqt/debug/pydebug.pyi deleted file mode 100644 index f6235ea..0000000 --- a/src/mqt/debug/pydebug.pyi +++ /dev/null @@ -1,98 +0,0 @@ -"""Type stubs for python bindings of the debug module.""" - -# Enums - -class VariableType: - VarBool: VariableType - VarInt: VariableType - VarFloat: VariableType - -# Classes -class VariableValue: - bool_value: bool - int_value: int - float_value: float - - def __init__(self) -> None: ... - -class Variable: - name: str - type: VariableType - value: VariableValue - - def __init__(self) -> None: ... - -class Complex: - real: float - imaginary: float - - def __init__(self, real: float = 0.0, imaginary: float = 0.0) -> None: ... - -class Statevector: - num_qubits: int - num_states: int - amplitudes: list[Complex] - - def __init__(self) -> None: ... - -class SimulationState: - def __init__(self) -> None: ... - def init(self) -> None: ... - def load_code(self, code: str) -> None: ... - def step_forward(self) -> None: ... - def step_over_forward(self) -> None: ... - def step_out_forward(self) -> None: ... - def step_backward(self) -> None: ... - def step_over_backward(self) -> None: ... - def step_out_backward(self) -> None: ... - def run_all(self) -> int: ... - def run_simulation(self) -> None: ... - def run_simulation_backward(self) -> None: ... - def reset_simulation(self) -> None: ... - def pause_simulation(self) -> None: ... - def can_step_forward(self) -> bool: ... - def can_step_backward(self) -> bool: ... - def is_finished(self) -> bool: ... - def did_assertion_fail(self) -> bool: ... - def was_breakpoint_hit(self) -> bool: ... - def get_current_instruction(self) -> int: ... - def get_previous_instruction(self) -> int: ... - def get_instruction_count(self) -> int: ... - def get_instruction_position(self, instruction: int) -> tuple[int, int]: ... - def get_num_qubits(self) -> int: ... - def get_amplitude_index(self, qubit: int) -> Complex: ... - def get_amplitude_bitstring(self, bitstring: str) -> Complex: ... - def get_classical_variable(self, name: str) -> Variable: ... - def get_num_classical_variables(self) -> int: ... - def get_classical_variable_name(self, index: int) -> str: ... - def get_state_vector_full(self) -> Statevector: ... - def get_state_vector_sub(self, qubits: list[int]) -> Statevector: ... - def set_breakpoint(self, desired_position: int) -> int: ... - def clear_breakpoints(self) -> None: ... - def get_stack_depth(self) -> int: ... - def get_stack_trace(self, max_depth: int) -> list[int]: ... - def get_diagnostics(self) -> Diagnostics: ... - -class ErrorCauseType: - Unknown: ErrorCauseType - MissingInteraction: ErrorCauseType - ControlAlwaysZero: ErrorCauseType - -class ErrorCause: - instruction: int - type: ErrorCauseType - - def __init__(self) -> None: ... - -class Diagnostics: - def __init__(self) -> None: ... - def init(self) -> None: ... - def get_num_qubits(self) -> int: ... - def get_instruction_count(self) -> int: ... - def get_data_dependencies(self, instruction: int, include_callers: bool = False) -> list[int]: ... - def get_interactions(self, before_instruction: int, qubit: int) -> list[int]: ... - def get_zero_control_instructions(self) -> list[int]: ... - def potential_error_causes(self) -> list[ErrorCause]: ... - -def create_ddsim_simulation_state() -> SimulationState: ... -def destroy_ddsim_simulation_state(state: SimulationState) -> None: ... diff --git a/src/mqt/debug/__init__.py b/src/mqt/debugger/__init__.py similarity index 73% rename from src/mqt/debug/__init__.py rename to src/mqt/debugger/__init__.py index db99109..7f2c61d 100644 --- a/src/mqt/debug/__init__.py +++ b/src/mqt/debugger/__init__.py @@ -1,10 +1,12 @@ -"""A module for interfacing with the mqt-debug library.""" +"""A module for interfacing with the mqt-debugger library.""" from __future__ import annotations from . import dap -from .pydebug import ( +from ._version import version as __version__ +from .pydebugger import ( Complex, + Diagnostics, ErrorCause, ErrorCauseType, SimulationState, @@ -18,6 +20,7 @@ __all__ = [ "Complex", + "Diagnostics", "ErrorCause", "ErrorCauseType", "SimulationState", @@ -25,6 +28,7 @@ "Variable", "VariableType", "VariableValue", + "__version__", "create_ddsim_simulation_state", "dap", "destroy_ddsim_simulation_state", diff --git a/src/mqt/debugger/_version.pyi b/src/mqt/debugger/_version.pyi new file mode 100644 index 0000000..9312dff --- /dev/null +++ b/src/mqt/debugger/_version.pyi @@ -0,0 +1,4 @@ +__version__: str +version: str +__version_tuple__: tuple[int, int, int, str, str] | tuple[int, int, int] +version_tuple: tuple[int, int, int, str, str] | tuple[int, int, int] diff --git a/src/mqt/debug/dap/__init__.py b/src/mqt/debugger/dap/__init__.py similarity index 100% rename from src/mqt/debug/dap/__init__.py rename to src/mqt/debugger/dap/__init__.py diff --git a/src/mqt/debug/dap/adapter.py b/src/mqt/debugger/dap/adapter.py similarity index 100% rename from src/mqt/debug/dap/adapter.py rename to src/mqt/debugger/dap/adapter.py diff --git a/src/mqt/debug/dap/dap_server.py b/src/mqt/debugger/dap/dap_server.py similarity index 81% rename from src/mqt/debug/dap/dap_server.py rename to src/mqt/debugger/dap/dap_server.py index eb6990c..2646b12 100644 --- a/src/mqt/debug/dap/dap_server.py +++ b/src/mqt/debugger/dap/dap_server.py @@ -7,9 +7,8 @@ import sys from typing import Any, cast -import mqt.debug +import mqt.debugger -from ..pydebug import ErrorCauseType, SimulationState from .messages import ( ConfigurationDoneDAPMessage, ContinueDAPMessage, @@ -79,7 +78,7 @@ class DAPServer: host: str port: int - simulation_state: SimulationState + simulation_state: mqt.debugger.SimulationState source_file: dict[str, Any] source_code: str can_step_back: bool @@ -97,7 +96,7 @@ def __init__(self, host: str = "127.0.0.1", port: int = 4711) -> None: self.host = host self.port = port self.can_step_back = False - self.simulation_state = SimulationState() + self.simulation_state = mqt.debugger.SimulationState() self.lines_start_at_one = True self.columns_start_at_one = True @@ -153,45 +152,49 @@ def handle_client(self, connection: socket.socket) -> None: result_payload = json.dumps(result) send_message(result_payload, connection) - e: mqt.debug.dap.messages.DAPEvent | None = None - if isinstance(cmd, mqt.debug.dap.messages.LaunchDAPMessage): - e = mqt.debug.dap.messages.InitializedDAPEvent() + e: mqt.debugger.dap.messages.DAPEvent | None = None + if isinstance(cmd, mqt.debugger.dap.messages.LaunchDAPMessage): + e = mqt.debugger.dap.messages.InitializedDAPEvent() event_payload = json.dumps(e.encode()) send_message(event_payload, connection) if ( - isinstance(cmd, (mqt.debug.dap.messages.LaunchDAPMessage, mqt.debug.dap.messages.RestartDAPMessage)) + isinstance( + cmd, (mqt.debugger.dap.messages.LaunchDAPMessage, mqt.debugger.dap.messages.RestartDAPMessage) + ) and cmd.stop_on_entry ): - e = mqt.debug.dap.messages.StoppedDAPEvent(mqt.debug.dap.messages.StopReason.ENTRY, "Stopped on entry") + e = mqt.debugger.dap.messages.StoppedDAPEvent( + mqt.debugger.dap.messages.StopReason.ENTRY, "Stopped on entry" + ) event_payload = json.dumps(e.encode()) send_message(event_payload, connection) if isinstance( cmd, ( - mqt.debug.dap.messages.NextDAPMessage, - mqt.debug.dap.messages.StepBackDAPMessage, - mqt.debug.dap.messages.StepInDAPMessage, - mqt.debug.dap.messages.StepOutDAPMessage, - mqt.debug.dap.messages.ContinueDAPMessage, - mqt.debug.dap.messages.ReverseContinueDAPMessage, - mqt.debug.dap.messages.RestartFrameDAPMessage, + 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, ), ) or ( isinstance( cmd, ( - mqt.debug.dap.messages.LaunchDAPMessage, - mqt.debug.dap.messages.RestartDAPMessage, + mqt.debugger.dap.messages.LaunchDAPMessage, + mqt.debugger.dap.messages.RestartDAPMessage, ), ) and not cmd.stop_on_entry ): event = ( - mqt.debug.dap.messages.StopReason.EXCEPTION + mqt.debugger.dap.messages.StopReason.EXCEPTION if self.simulation_state.did_assertion_fail() - else mqt.debug.dap.messages.StopReason.BREAKPOINT_INSTRUCTION + else mqt.debugger.dap.messages.StopReason.BREAKPOINT_INSTRUCTION if self.simulation_state.was_breakpoint_hit() - else mqt.debug.dap.messages.StopReason.STEP + else mqt.debugger.dap.messages.StopReason.STEP ) message = ( "An assertion failed" @@ -200,21 +203,21 @@ def handle_client(self, connection: socket.socket) -> None: if self.simulation_state.was_breakpoint_hit() else "Stopped after step" ) - e = mqt.debug.dap.messages.StoppedDAPEvent(event, message) + e = mqt.debugger.dap.messages.StoppedDAPEvent(event, message) event_payload = json.dumps(e.encode()) send_message(event_payload, connection) if self.simulation_state.did_assertion_fail(): self.handle_assertion_fail(connection) - if isinstance(cmd, mqt.debug.dap.messages.TerminateDAPMessage): - e = mqt.debug.dap.messages.TerminatedDAPEvent() + if isinstance(cmd, mqt.debugger.dap.messages.TerminateDAPMessage): + e = mqt.debugger.dap.messages.TerminatedDAPEvent() event_payload = json.dumps(e.encode()) send_message(event_payload, connection) - e = mqt.debug.dap.messages.ExitedDAPEvent(143) + e = mqt.debugger.dap.messages.ExitedDAPEvent(143) event_payload = json.dumps(e.encode()) send_message(event_payload, connection) - if isinstance(cmd, mqt.debug.dap.messages.PauseDAPMessage): - e = mqt.debug.dap.messages.StoppedDAPEvent( - mqt.debug.dap.messages.StopReason.PAUSE, "Stopped after pause" + if isinstance(cmd, mqt.debugger.dap.messages.PauseDAPMessage): + e = mqt.debugger.dap.messages.StoppedDAPEvent( + mqt.debugger.dap.messages.StopReason.PAUSE, "Stopped after pause" ) event_payload = json.dumps(e.encode()) send_message(event_payload, connection) @@ -226,17 +229,17 @@ def regular_checks(self, connection: socket.socket) -> None: Args: connection (socket.socket): The client socket. """ - e: mqt.debug.dap.messages.DAPEvent | None = None + e: mqt.debugger.dap.messages.DAPEvent | None = None if self.simulation_state.is_finished() and self.simulation_state.get_instruction_count() != 0: - e = mqt.debug.dap.messages.ExitedDAPEvent(0) + e = mqt.debugger.dap.messages.ExitedDAPEvent(0) event_payload = json.dumps(e.encode()) send_message(event_payload, connection) if self.can_step_back != self.simulation_state.can_step_backward(): self.can_step_back = self.simulation_state.can_step_backward() - e = mqt.debug.dap.messages.CapabilitiesDAPEvent({"supportsStepBack": self.can_step_back}) + e = mqt.debugger.dap.messages.CapabilitiesDAPEvent({"supportsStepBack": self.can_step_back}) event_payload = json.dumps(e.encode()) - def handle_command(self, command: dict[str, Any]) -> tuple[dict[str, Any], mqt.debug.dap.messages.DAPMessage]: + def handle_command(self, command: dict[str, Any]) -> tuple[dict[str, Any], mqt.debugger.dap.messages.DAPMessage]: """Handle an incoming command from the client and return the corresponding response. Args: @@ -246,7 +249,7 @@ def handle_command(self, command: dict[str, Any]) -> tuple[dict[str, Any], mqt.d RuntimeError: If the command is not supported. Returns: - tuple[dict[str, Any], mqt.debug.dap.messages.DAPMessage]: The response to the message as a dictionary and the message object. + tuple[dict[str, Any], mqt.debugger.dap.messages.DAPMessage]: The response to the message as a dictionary and the message object. """ for message_type in supported_messages: if message_type.message_type_name == command["command"]: @@ -270,7 +273,7 @@ def handle_assertion_fail(self, connection: socket.socket) -> None: start, end = self.simulation_state.get_instruction_position(i) gray_out_areas.append((start, end)) - e = mqt.debug.dap.messages.GrayOutDAPEvent(gray_out_areas, self.source_file) + e = mqt.debugger.dap.messages.GrayOutDAPEvent(gray_out_areas, self.source_file) event_payload = json.dumps(e.encode()) send_message(event_payload, connection) @@ -345,11 +348,11 @@ def code_coordinates_to_pos(self, line: int, col: int) -> int: pos -= 1 return pos - def format_error_cause(self, cause: mqt.debug.ErrorCause) -> str: + def format_error_cause(self, cause: mqt.debugger.ErrorCause) -> str: """Format an error cause for output. Args: - cause (mqt.debug.ErrorCause): The error cause. + cause (mqt.debugger.ErrorCause): The error cause. Returns: str: The formatted error cause. @@ -358,9 +361,9 @@ def format_error_cause(self, cause: mqt.debug.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 == ErrorCauseType.MissingInteraction + if cause.type == mqt.debugger.ErrorCauseType.MissingInteraction else f"Control qubit is always zero in line {start_line}." - if cause.type == ErrorCauseType.ControlAlwaysZero + if cause.type == mqt.debugger.ErrorCauseType.ControlAlwaysZero else "" ) @@ -370,13 +373,13 @@ def send_message_hierarchy( """Send a hierarchy of messages to the client. Args: - message (dict[str, str | list[str], dict[str, Any]]): An object representing the message to send. Supported keys are `title`, `body`, `end`. + message (dict[str, str | list[str], dict[str, Any]]): An object representing the message to send. Supported keys are "title", "body", "end". line (int): The line number. column (int): The column number. connection (socket.socket): The client socket. """ if "title" in message: - title_event = mqt.debug.dap.messages.OutputDAPEvent( + title_event = mqt.debugger.dap.messages.OutputDAPEvent( "console", cast(str, message["title"]), "start", line, column, self.source_file ) send_message(json.dumps(title_event.encode()), connection) @@ -388,20 +391,20 @@ def send_message_hierarchy( if isinstance(msg, dict): self.send_message_hierarchy(msg, line, column, connection) else: - output_event = mqt.debug.dap.messages.OutputDAPEvent( + output_event = mqt.debugger.dap.messages.OutputDAPEvent( "console", msg, None, line, column, self.source_file ) send_message(json.dumps(output_event.encode()), connection) elif isinstance(body, dict): self.send_message_hierarchy(body, line, column, connection) elif isinstance(body, str): - output_event = mqt.debug.dap.messages.OutputDAPEvent( + output_event = mqt.debugger.dap.messages.OutputDAPEvent( "console", body, None, line, column, self.source_file ) send_message(json.dumps(output_event.encode()), connection) if "end" in message or "title" in message: - end_event = mqt.debug.dap.messages.OutputDAPEvent( + end_event = mqt.debugger.dap.messages.OutputDAPEvent( "console", cast(str, message.get("end")), "end", line, column, self.source_file ) send_message(json.dumps(end_event.encode()), connection) diff --git a/src/mqt/debug/dap/messages/__init__.py b/src/mqt/debugger/dap/messages/__init__.py similarity index 100% rename from src/mqt/debug/dap/messages/__init__.py rename to src/mqt/debugger/dap/messages/__init__.py diff --git a/src/mqt/debug/dap/messages/capabilities_dap_event.py b/src/mqt/debugger/dap/messages/capabilities_dap_event.py similarity index 100% rename from src/mqt/debug/dap/messages/capabilities_dap_event.py rename to src/mqt/debugger/dap/messages/capabilities_dap_event.py diff --git a/src/mqt/debug/dap/messages/configuration_done_dap_message.py b/src/mqt/debugger/dap/messages/configuration_done_dap_message.py similarity index 100% rename from src/mqt/debug/dap/messages/configuration_done_dap_message.py rename to src/mqt/debugger/dap/messages/configuration_done_dap_message.py diff --git a/src/mqt/debug/dap/messages/continue_dap_message.py b/src/mqt/debugger/dap/messages/continue_dap_message.py similarity index 100% rename from src/mqt/debug/dap/messages/continue_dap_message.py rename to src/mqt/debugger/dap/messages/continue_dap_message.py diff --git a/src/mqt/debug/dap/messages/dap_event.py b/src/mqt/debugger/dap/messages/dap_event.py similarity index 100% rename from src/mqt/debug/dap/messages/dap_event.py rename to src/mqt/debugger/dap/messages/dap_event.py diff --git a/src/mqt/debug/dap/messages/dap_message.py b/src/mqt/debugger/dap/messages/dap_message.py similarity index 100% rename from src/mqt/debug/dap/messages/dap_message.py rename to src/mqt/debugger/dap/messages/dap_message.py diff --git a/src/mqt/debug/dap/messages/disconnect_dap_message.py b/src/mqt/debugger/dap/messages/disconnect_dap_message.py similarity index 91% rename from src/mqt/debug/dap/messages/disconnect_dap_message.py rename to src/mqt/debugger/dap/messages/disconnect_dap_message.py index f8e89fb..b73558d 100644 --- a/src/mqt/debug/dap/messages/disconnect_dap_message.py +++ b/src/mqt/debugger/dap/messages/disconnect_dap_message.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Any -import mqt.debug +import mqt.debugger from .dap_message import DAPMessage @@ -37,5 +37,5 @@ def handle(self, server: DAPServer) -> dict[str, Any]: Returns: dict[str, Any]: The response to the request. """ - mqt.debug.destroy_ddsim_simulation_state(server.simulation_state) + mqt.debugger.destroy_ddsim_simulation_state(server.simulation_state) return super().handle(server) diff --git a/src/mqt/debug/dap/messages/exception_info_message.py b/src/mqt/debugger/dap/messages/exception_info_message.py similarity index 100% rename from src/mqt/debug/dap/messages/exception_info_message.py rename to src/mqt/debugger/dap/messages/exception_info_message.py diff --git a/src/mqt/debug/dap/messages/exited_dap_event.py b/src/mqt/debugger/dap/messages/exited_dap_event.py similarity index 100% rename from src/mqt/debug/dap/messages/exited_dap_event.py rename to src/mqt/debugger/dap/messages/exited_dap_event.py diff --git a/src/mqt/debug/dap/messages/gray_out_event.py b/src/mqt/debugger/dap/messages/gray_out_event.py similarity index 100% rename from src/mqt/debug/dap/messages/gray_out_event.py rename to src/mqt/debugger/dap/messages/gray_out_event.py diff --git a/src/mqt/debug/dap/messages/initialize_dap_message.py b/src/mqt/debugger/dap/messages/initialize_dap_message.py similarity index 95% rename from src/mqt/debug/dap/messages/initialize_dap_message.py rename to src/mqt/debugger/dap/messages/initialize_dap_message.py index ea050e4..0dd1962 100644 --- a/src/mqt/debug/dap/messages/initialize_dap_message.py +++ b/src/mqt/debugger/dap/messages/initialize_dap_message.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Any -import mqt.debug +import mqt.debugger from .dap_message import DAPMessage from .utils import get_default_capabilities @@ -60,7 +60,7 @@ def handle(self, server: DAPServer) -> dict[str, Any]: """ server.columns_start_at_one = self.columns_start_at1 server.lines_start_at_one = self.lines_start_at1 - server.simulation_state = mqt.debug.create_ddsim_simulation_state() + server.simulation_state = mqt.debugger.create_ddsim_simulation_state() return { "type": "response", "request_seq": self.sequence_number, diff --git a/src/mqt/debug/dap/messages/initialized_dap_event.py b/src/mqt/debugger/dap/messages/initialized_dap_event.py similarity index 100% rename from src/mqt/debug/dap/messages/initialized_dap_event.py rename to src/mqt/debugger/dap/messages/initialized_dap_event.py diff --git a/src/mqt/debug/dap/messages/launch_dap_message.py b/src/mqt/debugger/dap/messages/launch_dap_message.py similarity index 100% rename from src/mqt/debug/dap/messages/launch_dap_message.py rename to src/mqt/debugger/dap/messages/launch_dap_message.py diff --git a/src/mqt/debug/dap/messages/next_dap_message.py b/src/mqt/debugger/dap/messages/next_dap_message.py similarity index 100% rename from src/mqt/debug/dap/messages/next_dap_message.py rename to src/mqt/debugger/dap/messages/next_dap_message.py diff --git a/src/mqt/debug/dap/messages/output_dap_event.py b/src/mqt/debugger/dap/messages/output_dap_event.py similarity index 100% rename from src/mqt/debug/dap/messages/output_dap_event.py rename to src/mqt/debugger/dap/messages/output_dap_event.py diff --git a/src/mqt/debug/dap/messages/pause_dap_message.py b/src/mqt/debugger/dap/messages/pause_dap_message.py similarity index 100% rename from src/mqt/debug/dap/messages/pause_dap_message.py rename to src/mqt/debugger/dap/messages/pause_dap_message.py diff --git a/src/mqt/debug/dap/messages/restart_dap_message.py b/src/mqt/debugger/dap/messages/restart_dap_message.py similarity index 100% rename from src/mqt/debug/dap/messages/restart_dap_message.py rename to src/mqt/debugger/dap/messages/restart_dap_message.py diff --git a/src/mqt/debug/dap/messages/restart_frame_dap_message.py b/src/mqt/debugger/dap/messages/restart_frame_dap_message.py similarity index 100% rename from src/mqt/debug/dap/messages/restart_frame_dap_message.py rename to src/mqt/debugger/dap/messages/restart_frame_dap_message.py diff --git a/src/mqt/debug/dap/messages/reverse_continue_dap_message.py b/src/mqt/debugger/dap/messages/reverse_continue_dap_message.py similarity index 100% rename from src/mqt/debug/dap/messages/reverse_continue_dap_message.py rename to src/mqt/debugger/dap/messages/reverse_continue_dap_message.py diff --git a/src/mqt/debug/dap/messages/scopes_dap_message.py b/src/mqt/debugger/dap/messages/scopes_dap_message.py similarity index 100% rename from src/mqt/debug/dap/messages/scopes_dap_message.py rename to src/mqt/debugger/dap/messages/scopes_dap_message.py diff --git a/src/mqt/debug/dap/messages/set_breakpoints_dap_message.py b/src/mqt/debugger/dap/messages/set_breakpoints_dap_message.py similarity index 100% rename from src/mqt/debug/dap/messages/set_breakpoints_dap_message.py rename to src/mqt/debugger/dap/messages/set_breakpoints_dap_message.py diff --git a/src/mqt/debug/dap/messages/set_exception_breakpoints_dap_message.py b/src/mqt/debugger/dap/messages/set_exception_breakpoints_dap_message.py similarity index 100% rename from src/mqt/debug/dap/messages/set_exception_breakpoints_dap_message.py rename to src/mqt/debugger/dap/messages/set_exception_breakpoints_dap_message.py diff --git a/src/mqt/debug/dap/messages/stack_trace_dap_message.py b/src/mqt/debugger/dap/messages/stack_trace_dap_message.py similarity index 100% rename from src/mqt/debug/dap/messages/stack_trace_dap_message.py rename to src/mqt/debugger/dap/messages/stack_trace_dap_message.py diff --git a/src/mqt/debug/dap/messages/step_back_dap_message.py b/src/mqt/debugger/dap/messages/step_back_dap_message.py similarity index 100% rename from src/mqt/debug/dap/messages/step_back_dap_message.py rename to src/mqt/debugger/dap/messages/step_back_dap_message.py diff --git a/src/mqt/debug/dap/messages/step_in_dap_message.py b/src/mqt/debugger/dap/messages/step_in_dap_message.py similarity index 100% rename from src/mqt/debug/dap/messages/step_in_dap_message.py rename to src/mqt/debugger/dap/messages/step_in_dap_message.py diff --git a/src/mqt/debug/dap/messages/step_out_dap_message.py b/src/mqt/debugger/dap/messages/step_out_dap_message.py similarity index 100% rename from src/mqt/debug/dap/messages/step_out_dap_message.py rename to src/mqt/debugger/dap/messages/step_out_dap_message.py diff --git a/src/mqt/debug/dap/messages/stopped_dap_event.py b/src/mqt/debugger/dap/messages/stopped_dap_event.py similarity index 100% rename from src/mqt/debug/dap/messages/stopped_dap_event.py rename to src/mqt/debugger/dap/messages/stopped_dap_event.py diff --git a/src/mqt/debug/dap/messages/terminate_dap_message.py b/src/mqt/debugger/dap/messages/terminate_dap_message.py similarity index 91% rename from src/mqt/debug/dap/messages/terminate_dap_message.py rename to src/mqt/debugger/dap/messages/terminate_dap_message.py index e117fd6..0ea625c 100644 --- a/src/mqt/debug/dap/messages/terminate_dap_message.py +++ b/src/mqt/debugger/dap/messages/terminate_dap_message.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Any -import mqt.debug +import mqt.debugger from .dap_message import DAPMessage @@ -37,5 +37,5 @@ def handle(self, server: DAPServer) -> dict[str, Any]: Returns: dict[str, Any]: The response to the request. """ - mqt.debug.destroy_ddsim_simulation_state(server.simulation_state) + mqt.debugger.destroy_ddsim_simulation_state(server.simulation_state) return super().handle(server) diff --git a/src/mqt/debug/dap/messages/terminated_dap_event.py b/src/mqt/debugger/dap/messages/terminated_dap_event.py similarity index 100% rename from src/mqt/debug/dap/messages/terminated_dap_event.py rename to src/mqt/debugger/dap/messages/terminated_dap_event.py diff --git a/src/mqt/debug/dap/messages/threads_dap_message.py b/src/mqt/debugger/dap/messages/threads_dap_message.py similarity index 100% rename from src/mqt/debug/dap/messages/threads_dap_message.py rename to src/mqt/debugger/dap/messages/threads_dap_message.py diff --git a/src/mqt/debug/dap/messages/utils.py b/src/mqt/debugger/dap/messages/utils.py similarity index 100% rename from src/mqt/debug/dap/messages/utils.py rename to src/mqt/debugger/dap/messages/utils.py diff --git a/src/mqt/debug/dap/messages/variables_dap_message.py b/src/mqt/debugger/dap/messages/variables_dap_message.py similarity index 100% rename from src/mqt/debug/dap/messages/variables_dap_message.py rename to src/mqt/debugger/dap/messages/variables_dap_message.py diff --git a/src/mqt/debugger/py.typed b/src/mqt/debugger/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/src/mqt/debugger/pydebugger.pyi b/src/mqt/debugger/pydebugger.pyi new file mode 100644 index 0000000..26cd18e --- /dev/null +++ b/src/mqt/debugger/pydebugger.pyi @@ -0,0 +1,493 @@ +"""Type stubs for python bindings of the debug module.""" + +import enum + +# Enums + +class VariableType(enum.Enum): + """Represents possible types of classical variables.""" + + VarBool: VariableType + """A boolean variable.""" + VarInt: VariableType + """A 32-bit integer variable.""" + VarFloat: VariableType + """A floating-point variable.""" + +# Classes +class VariableValue: + """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`. + """ + + 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: + """Creates a new `VariableValue` instance.""" + +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.""" + +class Complex: + """Represents a complex number.""" + + real: float + """The real part of the complex number.""" + imaginary: float + """The imaginary part of the complex number.""" + + 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. + """ + +class Statevector: + """Represents a state vector.""" + + num_qubits: int + """The number of qubits in the state vector.""" + num_states: int + """The number of states in the state vector. + + This is always equal to 2^`num_qubits`. + """ + + amplitudes: list[Complex] + """The amplitudes of the state vector. + + Contains one element for each of the `num_states` states in the state vector. + """ + + def __init__(self) -> None: + """Creates a new `Statevector` instance.""" + +class SimulationState: + """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. + """ + + def __init__(self) -> None: + """Creates a new `SimulationState` instance.""" + + def init(self) -> None: + """Initializes the simulation state.""" + + def load_code(self, code: str) -> None: + """Loads the given code into the simulation state. + + Args: + code (str): The code to load. + """ + + def step_forward(self) -> None: + """Steps the simulation forward by one instruction.""" + + def step_over_forward(self) -> None: + """Steps the simulation forward by one instruction, skipping over possible custom gate calls.""" + + def step_out_forward(self) -> None: + """Steps the simulation forward until the current custom gate call returns.""" + + def step_backward(self) -> None: + """Steps the simulation backward by one instruction.""" + + def step_over_backward(self) -> None: + """Steps the simulation backward by one instruction, skipping over possible custom gate calls.""" + + def step_out_backward(self) -> None: + """Steps the simulation backward until the instruction calling the current custom gate is encountered.""" + + def run_all(self) -> int: + """Runs the simulation until it finishes, even if assertions fail. + + Returns: + int: The number of assertions that failed during execution. + """ + + def run_simulation(self) -> None: + """Runs the simulation until it finishes or an assertion fails. + + If an assertion fails, the simulation stops and the `did_assertion_fail` + method will return `True`. + """ + + def run_simulation_backward(self) -> None: + """Runs the simulation backward until it finishes or an assertion fails.""" + + def reset_simulation(self) -> None: + """Resets the simulation to the initial state. + + This will reset measured variables and state vectors and go back to the + start of the code. + """ + + def pause_simulation(self) -> None: + """Pauses the simulation. + + If the simulation is running in a concurrent thread, the execution will + stop as soon as possible, but it is not guaranteed to stop immediately. + + If the simulation is not running, then the next call to continue the + simulation will stop as soon as possible. `step over` and `step out` + methods, in particular, may still execute the next instruction. + """ + + def can_step_forward(self) -> bool: + """Indicates whether the simulation can step forward. + + 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. + """ + + def can_step_backward(self) -> bool: + """Indicates whether the simulation can step backward. + + 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. + """ + + def is_finished(self) -> bool: + """Indicates whether the execution has finished. + + The execution is considered finished if it has reached the end of the code. + + Returns: + bool: True, if the execution has finished. + """ + + def did_assertion_fail(self) -> bool: + """Indicates whether an assertion has failed in the previous step. + + 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. + """ + + def was_breakpoint_hit(self) -> bool: + """Indicates whether a breakpoint was hit in the previous step. + + 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. + """ + + def get_current_instruction(self) -> int: + """Gets the current instruction index. + + Returns: + int: 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. + """ + + def get_instruction_position(self, instruction: int) -> tuple[int, int]: + """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. + + Returns: + tuple[int, int]: 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. + """ + + def get_amplitude_index(self, index: int) -> Complex: + """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. + + Returns: + Complex: The complex amplitude of the state. + """ + + def get_amplitude_bitstring(self, bitstring: str) -> Complex: + """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. + + Returns: + Complex: The complex amplitude of the state. + """ + + def get_classical_variable(self, name: str) -> Variable: + """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. + + Returns: + Variable: The fetched variable. + """ + + def get_num_classical_variables(self) -> int: + """Gets the number of classical variables in the simulation. + + For registers, each index is counted as a separate variable. + + Returns: + int: The number of classical variables in the simulation. + """ + + def get_classical_variable_name(self, index: int) -> str: + """Gets the name of a classical 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 (int): The index of the variable. + + Returns: + str: 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. + """ + + def get_state_vector_sub(self, qubits: list[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. + + Returns: + Statevector: The sub-state vector of the current simulation state. + """ + + def set_breakpoint(self, desired_position: int) -> int: + """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. + + Returns: + int: The index of the instruction where the breakpoint was set. + """ + + def clear_breakpoints(self) -> None: + """Clears all breakpoints set in the simulation.""" + + def get_stack_depth(self) -> int: + """Gets the current stack depth of the simulation. + + Each custom gate call corresponds to one stack entry. + + Returns: + int: The current stack depth of the simulation. + """ + + def get_stack_trace(self, max_depth: int) -> list[int]: + """Gets the current stack trace of the simulation. + + The stack trace is represented as a list of instruction indices. Each + instruction index represents a single return address for the corresponding + stack entry. + + Args: + max_depth (int): The maximum depth of the stack trace. + + Returns: + list[int]: 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. + """ + +class ErrorCauseType(enum.Enum): + """Represents the type of a potential error cause.""" + + Unknown: ErrorCauseType + """An unknown error cause.""" + MissingInteraction: ErrorCauseType + """Indicates that an entanglement error may be caused by a missing interaction.""" + ControlAlwaysZero: ErrorCauseType + """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.""" + + 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 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. + """ + +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. + """ diff --git a/src/python/CMakeLists.txt b/src/python/CMakeLists.txt index 5009707..fdf0404 100644 --- a/src/python/CMakeLists.txt +++ b/src/python/CMakeLists.txt @@ -1,5 +1,5 @@ pybind11_add_module( - pydebug + pydebugger # Prefer thin LTO if available THIN_LTO # Optimize the bindings for size @@ -8,11 +8,11 @@ pybind11_add_module( bindings.cpp InterfaceBindings.cpp dd/DDSimDebugBindings.cpp) -target_link_libraries(pydebug PRIVATE MQT::Debug MQT::CorePython pybind11_json MQT::ProjectOptions - MQT::ProjectWarnings) +target_link_libraries(pydebugger PRIVATE MQT::Debugger MQT::CorePython pybind11_json + MQT::ProjectOptions MQT::ProjectWarnings) message(STATUS "CMAKE_INSTALL_PREFIX is set to ${CMAKE_INSTALL_PREFIX}") # Install directive for scikit-build-core install( - TARGETS pydebug + TARGETS pydebugger DESTINATION . - COMPONENT mqt-debug_Python) + COMPONENT mqt-debugger_Python) diff --git a/src/python/InterfaceBindings.cpp b/src/python/InterfaceBindings.cpp index 95bb646..229e36b 100644 --- a/src/python/InterfaceBindings.cpp +++ b/src/python/InterfaceBindings.cpp @@ -47,235 +47,514 @@ struct StatevectorCPP { void bindFramework(py::module& m) { // Bind the VariableType enum py::enum_(m, "VariableType") - .value("VarBool", VarBool) - .value("VarInt", VarInt) - .value("VarFloat", VarFloat) + .value("VarBool", VarBool, "A boolean variable.") + .value("VarInt", VarInt, "An integer variable.") + .value("VarFloat", VarFloat, "A floating-point variable.") .export_values(); // Bind the VariableValue union py::class_(m, "VariableValue") .def(py::init<>()) - .def_readwrite("bool_value", &VariableValue::boolValue) - .def_readwrite("int_value", &VariableValue::intValue) - .def_readwrite("float_value", &VariableValue::floatValue); + .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.") + .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<>()) - .def_readwrite("name", &Variable::name) - .def_readwrite("type", &Variable::type) - .def_readwrite("value", &Variable::value); + .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.") + .doc() = "Represents a classical variable."; // Bind the Complex struct py::class_(m, "Complex") - .def(py::init<>()) - .def(py::init()) - .def_readwrite("real", &Complex::real) - .def_readwrite("imaginary", &Complex::imaginary) - .def("__str__", - [](const Complex& self) { - return std::to_string(self.real) + " + " + - std::to_string(self.imaginary) + "i"; - }) - .def("__repr__", [](const Complex& self) { - return "Complex(" + std::to_string(self.real) + ", " + - std::to_string(self.imaginary) + ")"; - }); + .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. + +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.") + .def( + "__str__", + [](const Complex& self) { + return std::to_string(self.real) + " + " + + std::to_string(self.imaginary) + "i"; + }, + R"(Returns a string representation of the complex number. +Returns: + str: The string representation of the complex number. +)") + .def( + "__repr__", + [](const Complex& self) { + return "Complex(" + std::to_string(self.real) + ", " + + std::to_string(self.imaginary) + ")"; + }, + R"(Returns a string representation of the complex number. + +Returns: + str: The string representation of the complex number.)") + .doc() = "Represents a complex number."; // Bind the Statevector struct py::class_(m, "Statevector") - .def(py::init<>()) - .def_readwrite("num_qubits", &StatevectorCPP::numQubits) - .def_readwrite("num_states", &StatevectorCPP::numStates) - .def_readwrite("amplitudes", &StatevectorCPP::amplitudes); + .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. + +This is always equal to 2^`num_qubits`.)") + .def_readwrite("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, "SimulationState") - .def(py::init<>()) - .def("init", - [](SimulationState* self) { checkOrThrow(self->init(self)); }) - .def("load_code", - [](SimulationState* self, const char* code) { - checkOrThrow(self->loadCode(self, code)); - }) - .def("step_forward", - [](SimulationState* self) { checkOrThrow(self->stepForward(self)); }) - .def("step_over_forward", - [](SimulationState* self) { - checkOrThrow(self->stepOverForward(self)); - }) - .def("step_out_forward", - [](SimulationState* self) { - checkOrThrow(self->stepOutForward(self)); - }) + .def(py::init<>(), "Creates a new `SimulationState` instance.") + .def( + "init", [](SimulationState* self) { checkOrThrow(self->init(self)); }, + "Initializes the simulation state.") + .def( + "load_code", + [](SimulationState* self, const char* code) { + checkOrThrow(self->loadCode(self, code)); + }, + R"(Loads the given code into the simulation state. + +Args: + code (str): The code to load.)") + .def( + "step_forward", + [](SimulationState* self) { checkOrThrow(self->stepForward(self)); }, + "Steps the simulation forward by one instruction.") + .def( + "step_over_forward", + [](SimulationState* self) { + checkOrThrow(self->stepOverForward(self)); + }, + "Steps the simulation forward by one instruction, skipping over " + "possible custom gate calls.") + .def( + "step_out_forward", + [](SimulationState* self) { + checkOrThrow(self->stepOutForward(self)); + }, + "Steps the simulation forward until the current custom gate call " + "returns.") .def( "step_backward", - [](SimulationState* self) { checkOrThrow(self->stepBackward(self)); }) - .def("step_over_backward", - [](SimulationState* self) { - checkOrThrow(self->stepOverBackward(self)); - }) - .def("step_out_backward", - [](SimulationState* self) { - checkOrThrow(self->stepOutBackward(self)); - }) - .def("run_all", - [](SimulationState* self) { - size_t errors = 0; - checkOrThrow(self->runAll(self, &errors)); - return errors; - }) - .def("run_simulation", - [](SimulationState* self) { - checkOrThrow(self->runSimulation(self)); - }) - .def("run_simulation_backward", - [](SimulationState* self) { - checkOrThrow(self->runSimulationBackward(self)); - }) - .def("reset_simulation", - [](SimulationState* self) { - checkOrThrow(self->resetSimulation(self)); - }) - .def("pause_simulation", - [](SimulationState* self) { - checkOrThrow(self->pauseSimulation(self)); - }) - .def("can_step_forward", - [](SimulationState* self) { return self->canStepForward(self); }) - .def("can_step_backward", - [](SimulationState* self) { return self->canStepBackward(self); }) - .def("is_finished", - [](SimulationState* self) { return self->isFinished(self); }) - .def("did_assertion_fail", - [](SimulationState* self) { return self->didAssertionFail(self); }) - .def("was_breakpoint_hit", - [](SimulationState* self) { return self->wasBreakpointHit(self); }) - .def("get_current_instruction", - [](SimulationState* self) { - return self->getCurrentInstruction(self); - }) + [](SimulationState* self) { checkOrThrow(self->stepBackward(self)); }, + "Steps the simulation backward by one instruction.") + .def( + "step_over_backward", + [](SimulationState* self) { + checkOrThrow(self->stepOverBackward(self)); + }, + "Steps the simulation backward by one instruction, skipping over " + "possible custom gate calls.") + .def( + "step_out_backward", + [](SimulationState* self) { + checkOrThrow(self->stepOutBackward(self)); + }, + "Steps the simulation backward until the instruction calling the " + "current custom gate is encountered.") + .def( + "run_all", + [](SimulationState* self) { + size_t errors = 0; + checkOrThrow(self->runAll(self, &errors)); + return errors; + }, + R"(Runs the simulation until it finishes, even if assertions fail. + +Returns: +int: The number of assertions that failed during execution.)") + .def( + "run_simulation", + [](SimulationState* self) { + checkOrThrow(self->runSimulation(self)); + }, + R"(Runs the simulation until it finishes or an assertion fails. + +If an assertion fails, the simulation stops and the `did_assertion_fail` +method will return `True`.)") + .def( + "run_simulation_backward", + [](SimulationState* self) { + checkOrThrow(self->runSimulationBackward(self)); + }, + "Runs the simulation backward until it finishes or an assertion " + "fails.") + .def( + "reset_simulation", + [](SimulationState* self) { + checkOrThrow(self->resetSimulation(self)); + }, + R"(Resets the simulation to the initial state. + +This will reset measured variables and state vectors and go back to the +start of the code.)") + .def( + "pause_simulation", + [](SimulationState* self) { + checkOrThrow(self->pauseSimulation(self)); + }, + R"(Pauses the simulation. + +If the simulation is running in a concurrent thread, the execution will +stop as soon as possible, but it is not guaranteed to stop immediately. + +If the simulation is not running, then the next call to continue the +simulation will stop as soon as possible. `step over` and `step out` +methods, in particular, may still execute the next instruction.)") + .def( + "can_step_forward", + [](SimulationState* self) { return self->canStepForward(self); }, + R"(Indicates whether the simulation can step forward. + +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.)") + .def( + "can_step_backward", + [](SimulationState* self) { return self->canStepBackward(self); }, + R"(Indicates whether the simulation can step backward. + +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.)") + .def( + "is_finished", + [](SimulationState* self) { return self->isFinished(self); }, + R"(Indicates whether the execution has finished. + +The execution is considered finished if it has reached the end of the code. + +Returns: +bool: True, if the execution has finished.)") + .def( + "did_assertion_fail", + [](SimulationState* self) { return self->didAssertionFail(self); }, + R"(Indicates whether an assertion has failed in the previous step. + +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.)") + .def( + "was_breakpoint_hit", + [](SimulationState* self) { return self->wasBreakpointHit(self); }, + R"(Indicates whether a breakpoint was hit in the previous step. + +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.)") + .def( + "get_current_instruction", + [](SimulationState* self) { + return self->getCurrentInstruction(self); + }, + R"(Gets the current instruction index. + +Returns: +int: The index of the current instruction.)") .def( "get_instruction_count", - [](SimulationState* self) { return self->getInstructionCount(self); }) - .def("get_instruction_position", - [](SimulationState* self, size_t instruction) { - size_t start = 0; - size_t end = 0; - checkOrThrow( - self->getInstructionPosition(self, instruction, &start, &end)); - return std::make_pair(start, end); - }) - .def("get_num_qubits", - [](SimulationState* self) { return self->getNumQubits(self); }) - .def("get_amplitude_index", - [](SimulationState* self, size_t qubit) { - Complex output; - checkOrThrow(self->getAmplitudeIndex(self, qubit, &output)); - return output; - }) - .def("get_amplitude_bitstring", - [](SimulationState* self, const char* bitstring) { - Complex output; - checkOrThrow( - self->getAmplitudeBitstring(self, bitstring, &output)); - return output; - }) - .def("get_classical_variable", - [](SimulationState* self, const char* name) { - Variable output; - checkOrThrow(self->getClassicalVariable(self, name, &output)); - return output; - }) - .def("get_num_classical_variables", - [](SimulationState* self) { - return self->getNumClassicalVariables(self); - }) - .def("get_classical_variable_name", - [](SimulationState* self, size_t variableIndex) { - std::string output(255, '\0'); - checkOrThrow(self->getClassicalVariableName(self, variableIndex, - output.data())); - const std::size_t pos = output.find_first_of('\0'); - if (pos != std::string::npos) { - output = output.substr(0, pos); - } - return output; - }) - .def("get_state_vector_full", - [](SimulationState* self) { - const size_t numQubits = self->getNumQubits(self); - const std::vector amplitudes(1 << numQubits); - StatevectorCPP result{numQubits, 1ULL << numQubits, amplitudes}; - Statevector output{numQubits, result.numStates, - result.amplitudes.data()}; - checkOrThrow(self->getStateVectorFull(self, &output)); - return result; - }) - .def("get_state_vector_sub", - [](SimulationState* self, std::vector qubits) { - const size_t numQubits = qubits.size(); - const std::vector amplitudes(1 << numQubits); - StatevectorCPP result{numQubits, 1ULL << numQubits, amplitudes}; - Statevector output{numQubits, result.numStates, - result.amplitudes.data()}; - checkOrThrow(self->getStateVectorSub(self, numQubits, - qubits.data(), &output)); - return result; - }) - .def("set_breakpoint", - [](SimulationState* self, size_t desiredPosition) { - size_t actualPosition = 0; - checkOrThrow( - self->setBreakpoint(self, desiredPosition, &actualPosition)); - return actualPosition; - }) - .def("clear_breakpoints", - [](SimulationState* self) { - checkOrThrow(self->clearBreakpoints(self)); - }) - .def("get_stack_depth", - [](SimulationState* self) { - size_t depth = 0; - checkOrThrow(self->getStackDepth(self, &depth)); - return depth; - }) - .def("get_stack_trace", - [](SimulationState* self, size_t maxDepth) { - size_t trueSize = 0; - checkOrThrow(self->getStackDepth(self, &trueSize)); - const size_t stackSize = std::min(maxDepth, trueSize); - std::vector stackTrace(stackSize); - checkOrThrow( - self->getStackTrace(self, maxDepth, stackTrace.data())); - return stackTrace; - }) + [](SimulationState* self) { return self->getInstructionCount(self); }, + R"(Gets the number of instructions in the code. + +Returns: + int: The number of instructions in the code.)") + .def( + "get_instruction_position", + [](SimulationState* self, size_t instruction) { + size_t start = 0; + size_t end = 0; + checkOrThrow( + self->getInstructionPosition(self, instruction, &start, &end)); + return std::make_pair(start, end); + }, + 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. + +Returns: + tuple[int, int]: 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.)") + .def( + "get_amplitude_index", + [](SimulationState* self, size_t qubit) { + Complex output; + checkOrThrow(self->getAmplitudeIndex(self, qubit, &output)); + return output; + }, + 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. + +Returns: + Complex: The complex amplitude of the state.)") + .def( + "get_amplitude_bitstring", + [](SimulationState* self, const char* bitstring) { + Complex output; + checkOrThrow(self->getAmplitudeBitstring(self, bitstring, &output)); + return output; + }, + 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. + +Returns: + Complex: The complex amplitude of the state.)") + .def( + "get_classical_variable", + [](SimulationState* self, const char* name) { + Variable output; + checkOrThrow(self->getClassicalVariable(self, name, &output)); + return output; + }, + 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. + +Returns: + Variable: The fetched variable.)") + .def( + "get_num_classical_variables", + [](SimulationState* self) { + return self->getNumClassicalVariables(self); + }, + R"(Gets the number of classical variables in the simulation. + +For registers, each index is counted as a separate variable. + +Returns: + int: The number of classical variables in the simulation.)") + .def( + "get_classical_variable_name", + [](SimulationState* self, size_t variableIndex) { + std::string output(255, '\0'); + checkOrThrow(self->getClassicalVariableName(self, variableIndex, + output.data())); + const std::size_t pos = output.find_first_of('\0'); + if (pos != std::string::npos) { + output = output.substr(0, pos); + } + return output; + }, + R"(Gets the name of a classical 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 (int): The index of the variable. + +Returns: + str: The name of the variable.)") + .def( + "get_state_vector_full", + [](SimulationState* self) { + const size_t numQubits = self->getNumQubits(self); + const std::vector amplitudes(1 << numQubits); + StatevectorCPP result{numQubits, 1ULL << numQubits, amplitudes}; + Statevector output{numQubits, result.numStates, + result.amplitudes.data()}; + checkOrThrow(self->getStateVectorFull(self, &output)); + return result; + }, + 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.)") + .def( + "get_state_vector_sub", + [](SimulationState* self, std::vector qubits) { + const size_t numQubits = qubits.size(); + const std::vector amplitudes(1 << numQubits); + StatevectorCPP result{numQubits, 1ULL << numQubits, amplitudes}; + Statevector output{numQubits, result.numStates, + result.amplitudes.data()}; + checkOrThrow(self->getStateVectorSub(self, numQubits, qubits.data(), + &output)); + return result; + }, + 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. + +Returns: + Statevector: The sub-state vector of the current simulation state.)") + .def( + "set_breakpoint", + [](SimulationState* self, size_t desiredPosition) { + size_t actualPosition = 0; + checkOrThrow( + self->setBreakpoint(self, desiredPosition, &actualPosition)); + return actualPosition; + }, + 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. + +Returns: + int: The index of the instruction where the breakpoint was set.)") + .def( + "clear_breakpoints", + [](SimulationState* self) { + checkOrThrow(self->clearBreakpoints(self)); + }, + "Clears all breakpoints set in the simulation.") + .def( + "get_stack_depth", + [](SimulationState* self) { + size_t depth = 0; + checkOrThrow(self->getStackDepth(self, &depth)); + return depth; + }, + R"(Gets the current stack depth of the simulation. + +Each custom gate call corresponds to one stack entry. + +Returns: + int: The current stack depth of the simulation.)") + .def( + "get_stack_trace", + [](SimulationState* self, size_t maxDepth) { + size_t trueSize = 0; + checkOrThrow(self->getStackDepth(self, &trueSize)); + const size_t stackSize = std::min(maxDepth, trueSize); + std::vector stackTrace(stackSize); + checkOrThrow( + self->getStackTrace(self, maxDepth, stackTrace.data())); + return stackTrace; + }, + R"(Gets the current stack trace of the simulation. + +The stack trace is represented as a list of instruction indices. Each +instruction index represents a single return address for the corresponding +stack entry. + +Args: + max_depth (int): The maximum depth of the stack trace. + +Returns: + list[int]: The stack trace of the simulation.)") .def( "get_diagnostics", [](SimulationState* self) { return self->getDiagnostics(self); }, - py::return_value_policy::reference_internal); + py::return_value_policy::reference_internal, + R"(Gets the diagnostics instance employed by this debugger. + +Returns: + Diagnostics: The diagnostics instance employed by this debugger.)") + .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.)"; } void bindDiagnostics(py::module& m) { // Bind the ErrorCauseType enum py::enum_(m, "ErrorCauseType") - .value("Unknown", Unknown) - .value("MissingInteraction", MissingInteraction) - .value("ControlAlwaysZero", ControlAlwaysZero) + .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(); // Bind the ErrorCause struct py::class_(m, "ErrorCause") .def(py::init<>()) - .def_readwrite("instruction", &ErrorCause ::instruction) - .def_readwrite("type", &ErrorCause ::type); + .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.") + .doc() = "Represents a potential cause of an assertion error."; py::class_(m, "Diagnostics") - .def(py::init<>()) - .def("init", [](Diagnostics* self) { checkOrThrow(self->init(self)); }) - .def("get_num_qubits", - [](Diagnostics* self) { return self->getNumQubits(self); }) - .def("get_instruction_count", - [](Diagnostics* self) { return self->getInstructionCount(self); }) + .def(py::init<>(), "Creates a new `Diagnostics` instance.") + .def( + "init", [](Diagnostics* self) { checkOrThrow(self->init(self)); }, + "Initializes the diagnostics instance.") + .def( + "get_num_qubits", + [](Diagnostics* self) { return self->getNumQubits(self); }, + R"(Get the number of qubits in the system. + +Returns: + int: 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.)") .def( "get_data_dependencies", [](Diagnostics* self, size_t instruction, bool includeCallers) { @@ -293,49 +572,111 @@ void bindDiagnostics(py::module& m) { } return result; }, - py::arg("instruction"), py::arg("include_callers") = false) - .def("get_interactions", - [](Diagnostics* self, size_t beforeInstruction, size_t qubit) { - std::vector qubits(self->getNumQubits(self)); - // NOLINTBEGIN(cppcoreguidelines-pro-type-reinterpret-cast) - checkOrThrow( - self->getInteractions(self, beforeInstruction, qubit, - reinterpret_cast(qubits.data()))); - // NOLINTEND(cppcoreguidelines-pro-type-reinterpret-cast) - std::vector result; - for (size_t i = 0; i < qubits.size(); i++) { - if (qubits[i] != 0) { - result.push_back(i); - } - } - return result; - }) - .def("get_zero_control_instructions", - [](Diagnostics* self) { - std::vector instructions(self->getInstructionCount(self)); - // NOLINTBEGIN(cppcoreguidelines-pro-type-reinterpret-cast) - checkOrThrow(self->getZeroControlInstructions( - self, reinterpret_cast(instructions.data()))); - // NOLINTEND(cppcoreguidelines-pro-type-reinterpret-cast) - std::vector result; - for (size_t i = 0; i < instructions.size(); i++) { - if (instructions[i] != 0) { - result.push_back(i); - } - } - return result; - }) - .def("potential_error_causes", [](Diagnostics* self) { - size_t nextSize = 10; - while (true) { - std::vector output(nextSize); - const auto actualSize = - self->potentialErrorCauses(self, output.data(), nextSize); - if (actualSize <= nextSize) { - output.resize(actualSize); - return output; - } - nextSize = nextSize * 2; - } - }); + py::arg("instruction"), py::arg("include_callers") = false, + R"(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", + [](Diagnostics* self, size_t beforeInstruction, size_t qubit) { + std::vector qubits(self->getNumQubits(self)); + // NOLINTBEGIN(cppcoreguidelines-pro-type-reinterpret-cast) + checkOrThrow( + self->getInteractions(self, beforeInstruction, qubit, + reinterpret_cast(qubits.data()))); + // NOLINTEND(cppcoreguidelines-pro-type-reinterpret-cast) + std::vector result; + for (size_t i = 0; i < qubits.size(); i++) { + if (qubits[i] != 0) { + result.push_back(i); + } + } + return result; + }, + 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 +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", + [](Diagnostics* self) { + std::vector instructions(self->getInstructionCount(self)); + // NOLINTBEGIN(cppcoreguidelines-pro-type-reinterpret-cast) + checkOrThrow(self->getZeroControlInstructions( + self, reinterpret_cast(instructions.data()))); + // NOLINTEND(cppcoreguidelines-pro-type-reinterpret-cast) + std::vector result; + for (size_t i = 0; i < instructions.size(); i++) { + if (instructions[i] != 0) { + result.push_back(i); + } + } + return result; + }, + R"(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", + [](Diagnostics* self) { + size_t nextSize = 10; + while (true) { + std::vector output(nextSize); + const auto actualSize = + self->potentialErrorCauses(self, output.data(), nextSize); + if (actualSize <= nextSize) { + output.resize(actualSize); + return output; + } + nextSize = nextSize * 2; + } + }, + R"(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.)") + .doc() = "Provides diagnostics capabilities such as different analysis " + "methods for the debugger."; } diff --git a/src/python/bindings.cpp b/src/python/bindings.cpp index 6ce70de..eb162e2 100644 --- a/src/python/bindings.cpp +++ b/src/python/bindings.cpp @@ -1,6 +1,6 @@ /** * @file bindings.cpp - * @brief Python bindings for the debug module. + * @brief Python bindings for the debugger module. * * Central file for defining the Python bindings for the framework. */ @@ -10,7 +10,7 @@ #include -PYBIND11_MODULE(pydebug, m) { +PYBIND11_MODULE(pydebugger, m) { bindDiagnostics(m); bindFramework(m); bindBackend(m); diff --git a/src/python/dd/DDSimDebugBindings.cpp b/src/python/dd/DDSimDebugBindings.cpp index dae418a..e50ed43 100644 --- a/src/python/dd/DDSimDebugBindings.cpp +++ b/src/python/dd/DDSimDebugBindings.cpp @@ -14,16 +14,28 @@ void bindBackend(pybind11::module& m) { - m.def("create_ddsim_simulation_state", []() { - // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) - auto* state = new DDSimulationState(); - createDDSimulationState(state); - return &state->interface; - }); + m.def( + "create_ddsim_simulation_state", + []() { + // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) + auto* state = new DDSimulationState(); + createDDSimulationState(state); + return &state->interface; + }, + R"(Creates a new `SimulationState` instance using the DD backend for simulation and the OpenQASM language as input format. - m.def("destroy_ddsim_simulation_state", [](SimulationState* state) { - // NOLINTBEGIN(cppcoreguidelines-pro-type-reinterpret-cast) - destroyDDSimulationState(reinterpret_cast(state)); - // NOLINTEND(cppcoreguidelines-pro-type-reinterpret-cast) - }); +Returns: + SimulationState: The created simulation state.)"); + + m.def( + "destroy_ddsim_simulation_state", + [](SimulationState* state) { + // NOLINTBEGIN(cppcoreguidelines-pro-type-reinterpret-cast) + destroyDDSimulationState(reinterpret_cast(state)); + // NOLINTEND(cppcoreguidelines-pro-type-reinterpret-cast) + }, + R"(Delete a given DD-based `SimulationState` instance and free up resources. + +Args: + state (SimulationState): The simulation state to delete.)"); } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 0e33a05..3e0b574 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,6 +1,6 @@ package_add_test( - mqt_debug_test - MQT::Debug + mqt_debugger_test + MQT::Debugger utils_test.cpp test_utility.cpp test_simulation.cpp @@ -10,4 +10,4 @@ package_add_test( test_parsing.cpp) # set include directories -target_include_directories(mqt_debug_test PUBLIC ${PROJECT_SOURCE_DIR}/test/utils) +target_include_directories(mqt_debugger_test PUBLIC ${PROJECT_SOURCE_DIR}/test/utils) diff --git a/test/python/test_diagnosis.py b/test/python/test_diagnosis.py index b4aa97c..709aa6b 100644 --- a/test/python/test_diagnosis.py +++ b/test/python/test_diagnosis.py @@ -5,7 +5,7 @@ import locale from pathlib import Path -from mqt.debug import ( +from mqt.debugger import ( ErrorCauseType, SimulationState, create_ddsim_simulation_state, diff --git a/test/python/test_end_to_end.py b/test/python/test_end_to_end.py index 5993cd3..9ba30da 100644 --- a/test/python/test_end_to_end.py +++ b/test/python/test_end_to_end.py @@ -9,8 +9,8 @@ import pytest -from mqt.debug import Complex, SimulationState, Statevector, create_ddsim_simulation_state -from mqt.debug.pydebug import destroy_ddsim_simulation_state +from mqt.debugger import Complex, SimulationState, Statevector, create_ddsim_simulation_state +from mqt.debugger.pydebugger import destroy_ddsim_simulation_state @dataclass diff --git a/test/python/test_python_bindings.py b/test/python/test_python_bindings.py index 289960a..e0c860c 100644 --- a/test/python/test_python_bindings.py +++ b/test/python/test_python_bindings.py @@ -11,44 +11,44 @@ import pytest -import mqt.debug +import mqt.debugger -SimulationInstance = Tuple[mqt.debug.SimulationState, int] +SimulationInstance = Tuple[mqt.debugger.SimulationState, int] @pytest.fixture(scope="module") def simulation_instance_ghz() -> Generator[SimulationInstance, None, None]: """Fixture for the GHZ state simulation instance.""" - simulation_state = mqt.debug.create_ddsim_simulation_state() + simulation_state = mqt.debugger.create_ddsim_simulation_state() with Path("test/python/resources/bindings/ghz-incorrect.qasm").open( encoding=locale.getpreferredencoding(False) ) as f: code = f.read() simulation_state.load_code(code) yield (simulation_state, 0) - mqt.debug.destroy_ddsim_simulation_state(simulation_state) + mqt.debugger.destroy_ddsim_simulation_state(simulation_state) @pytest.fixture(scope="module") def simulation_instance_jumps() -> Generator[SimulationInstance, None, None]: """Fixture for the Jumps simulation instance.""" - simulation_state = mqt.debug.create_ddsim_simulation_state() + simulation_state = mqt.debugger.create_ddsim_simulation_state() with Path("test/python/resources/bindings/jumps.qasm").open(encoding=locale.getpreferredencoding(False)) as f: code = f.read() simulation_state.load_code(code) yield (simulation_state, 1) - mqt.debug.destroy_ddsim_simulation_state(simulation_state) + mqt.debugger.destroy_ddsim_simulation_state(simulation_state) @pytest.fixture(scope="module") def simulation_instance_classical() -> Generator[SimulationInstance, None, None]: """Fixture for the Classical simulation instance.""" - simulation_state = mqt.debug.create_ddsim_simulation_state() + simulation_state = mqt.debugger.create_ddsim_simulation_state() with Path("test/python/resources/bindings/classical.qasm").open(encoding=locale.getpreferredencoding(False)) as f: code = f.read() simulation_state.load_code(code) yield (simulation_state, 2) - mqt.debug.destroy_ddsim_simulation_state(simulation_state) + mqt.debugger.destroy_ddsim_simulation_state(simulation_state) @pytest.fixture(autouse=True) @@ -67,9 +67,9 @@ def simulation_state_cleanup( simulation_instance_classical[0].clear_breakpoints() -def load_fixture(request: pytest.FixtureRequest, name: str) -> tuple[mqt.debug.SimulationState, int]: +def load_fixture(request: pytest.FixtureRequest, name: str) -> tuple[mqt.debugger.SimulationState, int]: """Loads a fixture with the given name.""" - return cast(Tuple[mqt.debug.SimulationState, int], request.getfixturevalue(name)) + return cast(Tuple[mqt.debugger.SimulationState, int], request.getfixturevalue(name)) @pytest.mark.parametrize( @@ -234,9 +234,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.debug.VariableType.VarBool - assert simulation_state.get_classical_variable("c[1]").type == mqt.debug.VariableType.VarBool - assert simulation_state.get_classical_variable("c[2]").type == mqt.debug.VariableType.VarBool + 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