From 23a4c653c1a5b018bdece98c71a3af502f51ae7e Mon Sep 17 00:00:00 2001 From: burgholzer Date: Fri, 19 Apr 2024 21:30:01 +0200 Subject: [PATCH 01/22] =?UTF-8?q?=F0=9F=94=A7=20=F0=9F=93=9D=20RtD=20confi?= =?UTF-8?q?g?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .readthedocs.yaml | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 .readthedocs.yaml diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 0000000..74b8f08 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,30 @@ +version: 2 + +formats: + - pdf + - htmlzip + +build: + os: ubuntu-22.04 + tools: + python: "3.11" + jobs: + post_checkout: + # Skip docs build if the commit message contains "skip ci" + - (git --no-pager log --pretty="tformat:%s -- %b" -1 | grep -viq "skip ci") || exit 183 + # Skip docs build if there are no changes related to docs + - | + if [ "$READTHEDOCS_VERSION_TYPE" = "external" ] && git diff --quiet origin/main -- docs/ .readthedocs.yaml src/mqt/ src/python include/python .github/contributing* .github/workflows/support*; + then + exit 183; + fi + +sphinx: + configuration: docs/conf.py + +python: + install: + - method: pip + path: . + extra_requirements: + - docs From 68f38db4a3e444cb0ac6a861930869fd731b1447 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Fri, 19 Apr 2024 21:30:13 +0200 Subject: [PATCH 02/22] =?UTF-8?q?=F0=9F=93=9D=20basic=20docs=20setup?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/_static/custom.css | 53 ++ docs/_static/erc_dark.svg | 1120 ++++++++++++++++++++++++++++++++ docs/_static/erc_light.svg | 1125 +++++++++++++++++++++++++++++++++ docs/_static/logo-bavaria.svg | 60 ++ docs/_static/logo-mqv.svg | 2 + docs/_static/mqt_dark.png | Bin 0 -> 61137 bytes docs/_static/mqt_light.png | Bin 0 -> 57266 bytes docs/_static/tum_dark.svg | 130 ++++ docs/_static/tum_light.svg | 130 ++++ docs/_templates/page.html | 61 ++ docs/conf.py | 204 ++++++ docs/index.md | 26 + docs/publications.md | 9 + docs/quickstart.md | 16 + docs/refs.bib | 7 + 15 files changed, 2943 insertions(+) create mode 100644 docs/_static/custom.css create mode 100644 docs/_static/erc_dark.svg create mode 100644 docs/_static/erc_light.svg create mode 100644 docs/_static/logo-bavaria.svg create mode 100644 docs/_static/logo-mqv.svg create mode 100644 docs/_static/mqt_dark.png create mode 100644 docs/_static/mqt_light.png create mode 100644 docs/_static/tum_dark.svg create mode 100644 docs/_static/tum_light.svg create mode 100644 docs/_templates/page.html create mode 100644 docs/conf.py create mode 100644 docs/index.md create mode 100644 docs/publications.md create mode 100644 docs/quickstart.md create mode 100644 docs/refs.bib diff --git a/docs/_static/custom.css b/docs/_static/custom.css new file mode 100644 index 0000000..04260a5 --- /dev/null +++ b/docs/_static/custom.css @@ -0,0 +1,53 @@ +.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, +.highlight .s { + background-color: #00000001; +} + +/* provide dark mode overrides for mystnb variables */ +body:not([data-theme="light"]) { + --mystnb-source-bg-color: #131416; + --mystnb-stdout-bg-color: #1a1c1e; + --mystnb-stderr-bg-color: #442222; + --mystnb-traceback-bg-color: #202020; +} + +body:not([data-theme="light"]) .highlight .gp { + color: #c65d09; +} diff --git a/docs/_static/erc_dark.svg b/docs/_static/erc_dark.svg new file mode 100644 index 0000000..f27e840 --- /dev/null +++ b/docs/_static/erc_dark.svg @@ -0,0 +1,1120 @@ + +image/svg+xml diff --git a/docs/_static/erc_light.svg b/docs/_static/erc_light.svg new file mode 100644 index 0000000..5f712dd --- /dev/null +++ b/docs/_static/erc_light.svg @@ -0,0 +1,1125 @@ + +image/svg+xml diff --git a/docs/_static/logo-bavaria.svg b/docs/_static/logo-bavaria.svg new file mode 100644 index 0000000..3af589f --- /dev/null +++ b/docs/_static/logo-bavaria.svg @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/_static/logo-mqv.svg b/docs/_static/logo-mqv.svg new file mode 100644 index 0000000..7eca062 --- /dev/null +++ b/docs/_static/logo-mqv.svg @@ -0,0 +1,2 @@ + +image/svg+xml diff --git a/docs/_static/mqt_dark.png b/docs/_static/mqt_dark.png new file mode 100644 index 0000000000000000000000000000000000000000..adc910ce84afc340b1cf899d40528786d6d971f7 GIT binary patch literal 61137 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 literal 0 HcmV?d00001 diff --git a/docs/_static/mqt_light.png b/docs/_static/mqt_light.png new file mode 100644 index 0000000000000000000000000000000000000000..be279e65bd931c10b154c39e76535a9312fdda78 GIT binary patch literal 57266 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 literal 0 HcmV?d00001 diff --git a/docs/_static/tum_dark.svg b/docs/_static/tum_dark.svg new file mode 100644 index 0000000..571a0cc --- /dev/null +++ b/docs/_static/tum_dark.svg @@ -0,0 +1,130 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/_static/tum_light.svg b/docs/_static/tum_light.svg new file mode 100644 index 0000000..763f70b --- /dev/null +++ b/docs/_static/tum_light.svg @@ -0,0 +1,130 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/_templates/page.html b/docs/_templates/page.html new file mode 100644 index 0000000..a5113ed --- /dev/null +++ b/docs/_templates/page.html @@ -0,0 +1,61 @@ +{% 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 +
+
+ TUM +
+
+ Bavaria +
+
+ ERC +
+
+ ERC +
+
+ MQV +
+
+
+{% endblock footer %} diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..94b673f --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,204 @@ +"""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() + + +try: + from mqt.qudits import __version__ as version +except ModuleNotFoundError: + try: + version = metadata.version("mqt.qudits") + 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 Qudits" +author = "Chair for Design Automation, Technical University of Munich" +language = "en" +project_copyright = "2024, Chair for Design Automation, Technical University of Munich" + +master_doc = "index" + +templates_path = ["_templates"] +html_css_files = ["custom.css"] + +extensions = [ + "myst_nb", + "autoapi.extension", + "sphinx.ext.autodoc", + "sphinx.ext.intersphinx", + "sphinx.ext.napoleon", + "sphinx_copybutton", + "sphinx_design", + "sphinx_inline_tabs", + "sphinxext.opengraph", + "sphinx.ext.viewcode", + "sphinx.ext.imgconverter", + "sphinxcontrib.bibtex", +] + +source_suffix = [".rst", ".md"] + +exclude_patterns = [ + "_build", + "**.ipynb_checkpoints", + "**.jupyter_cache", + "**jupyter_execute", + "Thumbs.db", + ".DS_Store", + ".env", + ".venv", +] + +pygments_style = "colorful" + +intersphinx_mapping = { + "python": ("https://docs.python.org/3", None), + "qiskit": ("https://docs.quantum.ibm.com/api/qiskit/", None), + "mqt": ("https://mqt.readthedocs.io/en/latest/", None), + "ddsim": ("https://mqt.readthedocs.io/projects/ddsim/en/latest/", None), + "qmap": ("https://mqt.readthedocs.io/projects/qmap/en/latest/", None), + "qcec": ("https://mqt.readthedocs.io/projects/qcec/en/latest/", None), + "qecc": ("https://mqt.readthedocs.io/projects/qecc/en/latest/", None), + "syrec": ("https://mqt.readthedocs.io/projects/syrec/en/latest/", None), +} + +myst_enable_extensions = [ + "amsmath", + "colon_fence", + "substitution", + "deflist", + "dollarmath", +] +myst_substitutions = { + "version": version, +} +myst_heading_anchors = 3 + +# -- Options for {MyST}NB ---------------------------------------------------- + +nb_execution_mode = "cache" +nb_mime_priority_overrides = [ + # builder name, mime type, priority + ("latex", "image/svg+xml", 15), +] + + +class CDAStyle(UnsrtStyle): + """Custom style for including PDF links.""" + + def format_url(self, _e: Entry) -> HRef: + """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 = "\\" + +modindex_common_prefix = ["mqt.qudits."] + +autoapi_dirs = ["../src/mqt"] +autoapi_python_use_implicit_namespaces = True +autoapi_root = "api" +autoapi_add_toctree_entry = False +autoapi_ignore = [ + "*/**/_version.py", +] +autoapi_options = [ + "members", + "imported-members", + "show-inheritance", + "special-members", + "undoc-members", +] +autoapi_keep_files = True + +add_module_names = False +toc_object_entries_show_parents = "hide" +python_use_unqualified_type_names = True +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-qudits/", + "source_branch": "main", + "source_directory": "docs/", + "navigation_with_keys": True, +} + +# -- Options for LaTeX output ------------------------------------------------ + +numfig = True +numfig_secnum_depth = 0 + +sd_fontawesome_latex = True +image_converter_args = ["-density", "300"] +latex_engine = "pdflatex" +latex_documents = [ + ( + master_doc, + "mqt_qudits.tex", + r"MQT Qudits\\{\Large A quantum computing software framework tailored for working with mixed-dimensional quantum circuits}", + r"Chair for Design Automation\\Technical University of Munich", + "howto", + False, + ), +] +latex_logo = "_static/mqt_dark.png" +latex_elements = { + "papersize": "a4paper", + "releasename": "Version", + "printindex": r"\footnotesize\raggedright\printindex", + "tableofcontents": "", + "extrapackages": r"\usepackage{qrcode,graphicx,calc,amsthm}", + "preamble": r""" +\newtheorem{example}{Example} +\clubpenalty=10000 +\widowpenalty=10000 +\interlinepenalty 10000 +\def\subparagraph{} % because IEEE classes don't define this, but titlesec assumes it's present +""", + "extraclassoptions": r"journal, onecolumn", + "fvset": r"\fvset{fontsize=\small}", +} +latex_domain_indices = False +latex_docclass = { + "howto": "IEEEtran", +} diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..236a305 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,26 @@ +# MQT Qudits - A framework for mixed-dimensional qudit quantum computing + +MQT Qudits is an open-source C++17 and Python framework for mixed-dimensional qudit quantum computing developed as part of the {doc}`Munich Quantum Toolkit (MQT) ` by the [Chair for Design Automation](https://www.cda.cit.tum.de/) at the [Technical University of Munich](https://www.tum.de/). + +We appreciate any feedback and contributions to the project. If you have any questions, feel free to contact us via [GitHub](https://github.com/cda-tum/mqt-misim) or [quantum.cda@xcit.tum.de](mailto:quantum.cda@xcit.tum.de). + +```{toctree} +:hidden: + +self +``` + +```{toctree} +:maxdepth: 2 +:caption: User Guide + +quickstart +publications +``` + +```{toctree} +:maxdepth: 3 +:caption: API Reference + +api/mqt/qudits/index +``` diff --git a/docs/publications.md b/docs/publications.md new file mode 100644 index 0000000..d2bab15 --- /dev/null +++ b/docs/publications.md @@ -0,0 +1,9 @@ +# Publications + +MQT Qudits is academic software. Thus, many of its built-in algorithms have been published as scientific papers. + +If you use _MQT Qudits_ in your work, we would appreciate if you cited {cite:p}`mato2023mixedDimensionalSimulation`. + +```{bibliography} + +``` diff --git a/docs/quickstart.md b/docs/quickstart.md new file mode 100644 index 0000000..260d562 --- /dev/null +++ b/docs/quickstart.md @@ -0,0 +1,16 @@ +--- +file_format: mystnb +kernelspec: + name: python3 +mystnb: + number_source_lines: true +--- + +```{code-cell} ipython3 +:tags: [remove-cell] +%config InlineBackend.figure_formats = ['svg'] +``` + +# Quickstart + +To come soon. diff --git a/docs/refs.bib b/docs/refs.bib new file mode 100644 index 0000000..5c51066 --- /dev/null +++ b/docs/refs.bib @@ -0,0 +1,7 @@ +@INPROCEEDINGS{mato2023mixedDimensionalSimulation, + AUTHOR = {K. Mato and S. Hillmich and R. Wille}, + TITLE = {{Mixed-Dimensional Quantum Circuit Simulation with Decision Diagrams}}, + BOOKTITLE = {IEEE International Conference on Quantum Computing and Engineering (QCE)}, + YEAR = {2023}, + DOI = {10.1109/QCE57702.2023.00112}, +} From ecd822c3f5aa5d7411a7c198bbfcc8693de3b9b6 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Fri, 19 Apr 2024 21:30:55 +0200 Subject: [PATCH 03/22] =?UTF-8?q?=E2=9C=A8=20MiSiM=20setup?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .clang-format | 3 + .clang-tidy | 74 + .cmake-format.yaml | 4 + .gitmodules | 4 + CMakeLists.txt | 62 + cmake/Cache.cmake | 24 + cmake/CompilerOptions.cmake | 71 + cmake/CompilerWarnings.cmake | 98 ++ cmake/ExternalDependencies.cmake | 48 + cmake/PackageAddTest.cmake | 16 + cmake/PreventInSourceBuilds.cmake | 17 + cmake/Sanitizers.cmake | 51 + cmake/StandardProjectSettings.cmake | 65 + extern/googletest | 1 + include/dd/Complex.hpp | 91 ++ include/dd/ComplexCache.hpp | 136 ++ include/dd/ComplexNumbers.hpp | 244 +++ include/dd/ComplexTable.hpp | 641 ++++++++ include/dd/ComplexValue.hpp | 278 ++++ include/dd/ComputeTable.hpp | 103 ++ include/dd/Control.hpp | 63 + include/dd/Definitions.hpp | 151 ++ include/dd/Edge.hpp | 101 ++ include/dd/GateMatrixDefinitions.hpp | 1068 ++++++++++++++ include/dd/MDDPackage.hpp | 2042 ++++++++++++++++++++++++++ include/dd/UnaryComputeTable.hpp | 91 ++ include/dd/UniqueTable.hpp | 418 ++++++ src/CMakeLists.txt | 65 + src/python/CMakeLists.txt | 17 + src/python/bindings.cpp | 769 ++++++++++ 30 files changed, 6816 insertions(+) create mode 100644 .clang-format create mode 100644 .clang-tidy create mode 100644 .cmake-format.yaml create mode 100644 .gitmodules create mode 100644 CMakeLists.txt create mode 100644 cmake/Cache.cmake create mode 100644 cmake/CompilerOptions.cmake create mode 100644 cmake/CompilerWarnings.cmake create mode 100644 cmake/ExternalDependencies.cmake create mode 100644 cmake/PackageAddTest.cmake create mode 100644 cmake/PreventInSourceBuilds.cmake create mode 100644 cmake/Sanitizers.cmake create mode 100644 cmake/StandardProjectSettings.cmake create mode 160000 extern/googletest create mode 100644 include/dd/Complex.hpp create mode 100644 include/dd/ComplexCache.hpp create mode 100644 include/dd/ComplexNumbers.hpp create mode 100644 include/dd/ComplexTable.hpp create mode 100644 include/dd/ComplexValue.hpp create mode 100644 include/dd/ComputeTable.hpp create mode 100644 include/dd/Control.hpp create mode 100644 include/dd/Definitions.hpp create mode 100644 include/dd/Edge.hpp create mode 100644 include/dd/GateMatrixDefinitions.hpp create mode 100644 include/dd/MDDPackage.hpp create mode 100644 include/dd/UnaryComputeTable.hpp create mode 100644 include/dd/UniqueTable.hpp create mode 100644 src/CMakeLists.txt create mode 100644 src/python/CMakeLists.txt create mode 100644 src/python/bindings.cpp diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..a519458 --- /dev/null +++ b/.clang-format @@ -0,0 +1,3 @@ +BasedOnStyle: LLVM +IncludeBlocks: Regroup +PointerAlignment: Left diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 0000000..b71e838 --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,74 @@ +FormatStyle: file + +Checks: | + clang-diagnostic-*, + clang-analyzer-*, + -clang-analyzer-core.NullDereference, + boost-*, + bugprone-*, + -bugprone-easily-swappable-parameters, + clang-analyzer-*, + cppcoreguidelines-*, + -cppcoreguidelines-non-private-member-variables-in-classes, + -cppcoreguidelines-special-member-functions, + -cppcoreguidelines-avoid-magic-numbers, + -cppcoreguidelines-macro-usage, + -cppcoreguidelines-pro-type-reinterpret-cast, + -cppcoreguidelines-pro-bounds-constant-array-index, + google-*, + -google-readability-todo, + -google-build-using-namespace, + misc-*, + -misc-no-recursion, + -misc-non-private-member-variables-in-classes, + modernize-*, + -modernize-use-trailing-return-type, + performance-*, + -performance-no-int-to-ptr, + portability-*, + readability-*, + -readability-identifier-length, + -readability-magic-numbers, + -readability-function-cognitive-complexity + +CheckOptions: + - key: readability-identifier-naming.ClassCase + value: CamelCase + - key: readability-identifier-naming.ClassIgnoredRegexp + value: ".*ZX.*|.*SWAP.*|.*CEX.*|.*DD.*|.*EQ.*" + - key: readability-identifier-naming.ConstantParameterCase + value: camelBack + - key: readability-identifier-naming.EnumCase + value: CamelCase + - key: readability-identifier-naming.EnumConstantCase + value: CamelCase + - key: readability-identifier-naming.FunctionCase + value: camelBack + - key: readability-identifier-naming.FunctionIgnoredRegexp + value: ".*ZX.*|.*SWAP.*|.*CEX.*|.*DD.*|.*EQ.*" + - key: readability-identifier-naming.GlobalConstantCase + value: UPPER_CASE + - key: readability-identifier-naming.IgnoreMainLikeFunctions + value: "true" + - key: readability-identifier-naming.LocalConstantCase + value: camelBack + - key: readability-identifier-naming.LocalVariableCase + value: camelBack + - key: readability-identifier-naming.MemberCase + value: camelBack + - key: readability-identifier-naming.MemberIgnoredRegexp + value: ".*ZX.*|.*SWAP.*|.*CEX.*|.*DD.*|.*EQ.*" + - key: readability-identifier-naming.MethodCase + value: camelBack + - key: readability-identifier-naming.ParameterCase + value: camelBack + - key: readability-identifier-naming.ParameterIgnoredRegexp + value: ".*ZX.*|.*SWAP.*|.*CEX.*|.*DD.*|.*EQ.*" + - key: readability-identifier-naming.NamespaceCase + value: lower_case + - key: readability-identifier-naming.StaticConstantCase + value: UPPER_CASE + - key: readability-identifier-naming.StructCase + value: CamelCase + - key: readability-identifier-naming.VariableCase + value: camelBack diff --git a/.cmake-format.yaml b/.cmake-format.yaml new file mode 100644 index 0000000..6a4e183 --- /dev/null +++ b/.cmake-format.yaml @@ -0,0 +1,4 @@ +format: + line_width: 100 + keyword_case: "upper" + autosort: true diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..5905cea --- /dev/null +++ b/.gitmodules @@ -0,0 +1,4 @@ +[submodule "extern/googletest"] + path = extern/googletest + url = https://github.com/google/googletest + branch = v1.14.x diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..e5764c1 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,62 @@ +# set required cmake version +cmake_minimum_required(VERSION 3.19...3.28) + +# project definition +project( + mqt-qudits + LANGUAGES CXX + VERSION 0.1.0 + DESCRIPTION "MQT Qudits - A framework for mixed-dimensional qudit quantum computing") + +include(cmake/StandardProjectSettings.cmake) +include(cmake/PreventInSourceBuilds.cmake) +include(cmake/PackageAddTest.cmake) +include(cmake/Cache.cmake) + +option(BUILD_MQT_QUDITS_BINDINGS "Build the MQT Qudits Python bindings" OFF) +if(BUILD_MQT_QUDITS_BINDINGS) + # ensure that the BINDINGS option is set + set(BINDINGS + ON + CACHE INTERNAL "Enable settings related to Python bindings") + # Some common settings for finding Python + set(Python_FIND_VIRTUALENV + FIRST + CACHE STRING "Give precedence to virtualenvs when searching for Python") + set(Python_FIND_FRAMEWORK + LAST + CACHE STRING "Prefer Brew/Conda to Apple framework Python") + set(Python_ARTIFACTS_INTERACTIVE + ON + CACHE BOOL "Prevent multiple searches for Python and instead cache the results.") + + # top-level call to find Python + find_package( + Python 3.8 REQUIRED + COMPONENTS Interpreter Development.Module + OPTIONAL_COMPONENTS Development.SABIModule) +endif() + +# check if this is the master project or used via add_subdirectory +if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) + set(MQT_QUDITS_MASTER_PROJECT ON) +else() + set(MQT_QUDITS_MASTER_PROJECT OFF) +endif() +option(BUILD_MQT_QUDITS_TESTS "Also build tests for the MQT MiSiM project" + ${MQT_QUDITS_MASTER_PROJECT}) + +include(cmake/ExternalDependencies.cmake) + +# set the include directory for the build tree +set(MQT_QUDITS_INCLUDE_BUILD_DIR "${CMAKE_CURRENT_SOURCE_DIR}/include") + +# add main library code +add_subdirectory(src) + +# add test code +if(BUILD_MQT_QUDITS_TESTS) + enable_testing() + include(GoogleTest) + add_subdirectory(test) +endif() diff --git a/cmake/Cache.cmake b/cmake/Cache.cmake new file mode 100644 index 0000000..bab903f --- /dev/null +++ b/cmake/Cache.cmake @@ -0,0 +1,24 @@ +option(ENABLE_CACHE "Enable compiler cache if available" ON) +if(NOT ENABLE_CACHE) + return() +endif() + +set(CACHE_OPTION_VALUES "ccache" "sccache") +set(CACHE_OPTION + "ccache" + CACHE STRING "Compiler cache to use") +set_property(CACHE CACHE_OPTION PROPERTY STRINGS ${CACHE_OPTION_VALUES}) +list(FIND CACHE_OPTION_VALUES ${CACHE_OPTION} CACHE_OPTION_INDEX) +if(CACHE_OPTION_INDEX EQUAL -1) + message(NOTICE + "Unknown compiler cache '${CACHE_OPTION}'. Available options are: ${CACHE_OPTION_VALUES}") +endif() + +find_program(CACHE_BINARY ${CACHE_OPTION}) +if(CACHE_BINARY) + message(STATUS "Compiler cache '${CACHE_OPTION}' found and enabled") + set(CMAKE_C_COMPILER_LAUNCHER ${CACHE_BINARY}) + set(CMAKE_CXX_COMPILER_LAUNCHER ${CACHE_BINARY}) +else() + message(NOTICE "${CACHE_OPTION} is enabled but was not found. Not using it") +endif() diff --git a/cmake/CompilerOptions.cmake b/cmake/CompilerOptions.cmake new file mode 100644 index 0000000..0c31747 --- /dev/null +++ b/cmake/CompilerOptions.cmake @@ -0,0 +1,71 @@ +# set common compiler options for projects +function(enable_project_options target_name) + include(CheckCXXCompilerFlag) + + # set required C++ standard and disable compiler specific extensions + target_compile_features(${target_name} INTERFACE cxx_std_17) + + # Option to enable time tracing with clang + if(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") + option(ENABLE_BUILD_WITH_TIME_TRACE + "Enable -ftime-trace to generate time tracing .json files on clang" OFF) + if(ENABLE_BUILD_WITH_TIME_TRACE) + target_compile_options(${target_name} INTERFACE -ftime-trace) + endif() + endif() + + if(MSVC) + target_compile_options(${target_name} INTERFACE /utf-8 /Zm10) + else() + # always include debug symbols (avoids common problems with LTO) + target_compile_options(${target_name} INTERFACE -g) + + # enable coverage collection options + option(ENABLE_COVERAGE "Enable coverage reporting for gcc/clang" FALSE) + if(ENABLE_COVERAGE) + target_compile_options(${target_name} INTERFACE --coverage -fprofile-arcs -ftest-coverage -O0) + target_link_libraries(${target_name} INTERFACE gcov --coverage) + endif() + + if(NOT DEPLOY) + # only include machine-specific optimizations when building for the host machine + check_cxx_compiler_flag(-mtune=native HAS_MTUNE_NATIVE) + if(HAS_MTUNE_NATIVE) + target_compile_options(${target_name} INTERFACE -mtune=native) + endif() + + check_cxx_compiler_flag(-march=native HAS_MARCH_NATIVE) + if(HAS_MARCH_NATIVE) + target_compile_options(${target_name} INTERFACE -march=native) + endif() + endif() + + # enable some more optimizations in release mode + target_compile_options( + ${target_name} INTERFACE $<$:-fno-math-errno -ffinite-math-only + -fno-trapping-math -fno-stack-protector>) + + # enable some more options for better debugging + target_compile_options( + ${target_name} INTERFACE $<$:-fno-omit-frame-pointer + -fno-optimize-sibling-calls -fno-inline-functions>) + endif() + + option(BINDINGS "Configure for building Python bindings") + if(BINDINGS) + check_cxx_compiler_flag(-fvisibility=hidden HAS_VISIBILITY_HIDDEN) + if(HAS_VISIBILITY_HIDDEN) + target_compile_options(${target_name} INTERFACE -fvisibility=hidden) + endif() + include(CheckPIESupported) + check_pie_supported() + set_target_properties(${target_name} PROPERTIES INTERFACE_POSITION_INDEPENDENT_CODE ON) + endif() + + # add a compile definition for _LIBCPP_REMOVE_TRANSITIVE_INCLUDES to remove transitive includes + # from libc++ headers. This is useful to avoid including system headers that are not needed and + # that may conflict with other headers. This is only supported by libc++. + if(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") + target_compile_definitions(${target_name} INTERFACE _LIBCPP_REMOVE_TRANSITIVE_INCLUDES) + endif() +endfunction() diff --git a/cmake/CompilerWarnings.cmake b/cmake/CompilerWarnings.cmake new file mode 100644 index 0000000..2b43f6a --- /dev/null +++ b/cmake/CompilerWarnings.cmake @@ -0,0 +1,98 @@ +# enable extensive compiler warnings from here: +# https://github.com/lefticus/cppbestpractices/blob/master/02-Use_the_Tools_Available.md +function(set_project_warnings target_name) + option(WARNINGS_AS_ERRORS "Treat compiler warnings as errors" OFF) + + set(microsoft_msvc_warnings + /W4 # Baseline reasonable warnings + /w14242 # 'identifier': conversion from 'type1' to 'type2', possible loss of data + /w14254 # 'operator': conversion from 'type1:field_bits' to 'type2:field_bits', possible loss + # of data + /w14263 # 'function': member function does not override any base class virtual member function + /w14265 # 'classname': class has virtual functions, but destructor is not virtual instances of + # this class may not + # be destructed correctly + /w14287 # 'operator': unsigned/negative constant mismatch + /we4289 # nonstandard extension used: 'variable': loop control variable declared in the + # for-loop is used outside + # the for-loop scope + /w14296 # 'operator': expression is always 'boolean_value' + /w14311 # 'variable': pointer truncation from 'type1' to 'type2' + /w14545 # expression before comma evaluates to a function which is missing an argument list + /w14546 # function call before comma missing argument list + /w14547 # 'operator': operator before comma has no effect; expected operator with side-effect + /w14549 # 'operator': operator before comma has no effect; did you intend 'operator'? + /w14555 # expression has no effect; expected expression with side- effect + /w14619 # pragma warning: there is no warning number 'number' + /w14640 # Enable warning on thread un-safe static member initialization + /w14826 # Conversion from 'type1' to 'type_2' is sign-extended. This may cause unexpected + # runtime behavior. + /w14905 # wide string literal cast to 'LPSTR' + /w14906 # string literal cast to 'LPWSTR' + /w14928 # illegal copy-initialization; more than one user-defined conversion has been + # implicitly applied + /permissive- # standards conformance mode for MSVC compiler. + ) + + set(clang_warnings + -Wall + -Wextra # reasonable and standard + -Wshadow # warn the user if a variable declaration shadows one from a parent context + -Wnon-virtual-dtor # warn the user if a class with virtual functions has a non-virtual + # destructor. This helps + # catch hard to track down memory errors + -Wold-style-cast # warn for c-style casts + -Wcast-align # warn for potential performance problem casts + -Wunused # warn on anything being unused + -Woverloaded-virtual # warn if you overload (not override) a virtual function + -Wpedantic # warn if non-standard C++ is used + -Wconversion # warn on type conversions that may lose data + -Wsign-conversion # warn on sign conversions + -Wnull-dereference # warn if a null dereference is detected + -Wdouble-promotion # warn if float is implicit promoted to double + -Wformat=2 # warn on security issues around functions that format output (ie printf) + -Wno-unknown-pragmas # do not warn if encountering unknown pragmas + -Wno-pragmas # do not warn if encountering unknown pragma options + $<$:-Wno-unknown-warning-option> # do not warn if + # encountering unknown + # warning options + ) + + if(WARNINGS_AS_ERRORS) + set(clang_warnings ${clang_warnings} -Werror) + set(microsoft_msvc_warnings ${microsoft_msvc_warnings} /WX) + endif() + + set(gcc_warnings + ${clang_warnings} + -Wmisleading-indentation # warn if indentation implies blocks where blocks do not exist + -Wduplicated-cond # warn if if / else chain has duplicated conditions + -Wduplicated-branches # warn if if / else branches have duplicated code + -Wlogical-op # warn about logical operations being used where bitwise were probably wanted + -Wuseless-cast # warn if you perform a cast to the same type + # -Wsuggest-attribute=pure # suggest attribute pure for functions with no effects except the + # return value -Wsuggest-attribute=const # suggest attribute const for functions with no + # effects except the return value and their arguments are not modified + -Wsuggest-attribute=noreturn # suggest attribute noreturn for functions that do not return + -Wmissing-noreturn # warn if a function that is declared with attribute noreturn does in fact + # return -Wsuggest-attribute=malloc # suggest attribute malloc for functions that return a + # pointer to newly allocated memory + ) + + if(MSVC) + set(project_warnings ${microsoft_msvc_warnings}) + elseif(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") + set(project_warnings ${clang_warnings}) + elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + set(project_warnings ${gcc_warnings}) + else() + message(AUTHOR_WARNING "No compiler warnings set for '${CMAKE_CXX_COMPILER_ID}' compiler.") + endif() + + target_compile_options(${target_name} INTERFACE ${project_warnings}) + + if(MSVC) + add_compile_options(/bigobj) + endif() + +endfunction() diff --git a/cmake/ExternalDependencies.cmake b/cmake/ExternalDependencies.cmake new file mode 100644 index 0000000..1a6638a --- /dev/null +++ b/cmake/ExternalDependencies.cmake @@ -0,0 +1,48 @@ +# Declare all external dependencies and make sure that they are available. + +include(FetchContent) +include(CMakeDependentOption) +set(FETCH_PACKAGES "") + +if(BUILD_MQT_QUDITS_BINDINGS) + if(NOT SKBUILD) + # Manually detect the installed pybind11 package and import it into CMake. + execute_process( + COMMAND "${Python_EXECUTABLE}" -m pybind11 --cmakedir + OUTPUT_STRIP_TRAILING_WHITESPACE + OUTPUT_VARIABLE pybind11_DIR) + list(APPEND CMAKE_PREFIX_PATH "${pybind11_DIR}") + endif() + + # add pybind11 library + find_package(pybind11 CONFIG REQUIRED) +endif() + +if(BUILD_MQT_QUDITS_TESTS) + set(FETCHCONTENT_SOURCE_DIR_GOOGLETEST + ${PROJECT_SOURCE_DIR}/extern/googletest + CACHE + PATH + "Path to the source directory of the gtest submodule. This variable is used by FetchContent to download the library if it is not already available." + ) + set(gtest_force_shared_crt + ON + CACHE BOOL "" FORCE) + set(GTEST_VERSION + 1.14.0 + CACHE STRING "Google Test version") + set(GTEST_URL https://github.com/google/googletest/archive/refs/tags/v${GTEST_VERSION}.tar.gz) + if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.24) + FetchContent_Declare(googletest URL ${GTEST_URL} FIND_PACKAGE_ARGS ${GTEST_VERSION} NAMES GTest) + list(APPEND FETCH_PACKAGES googletest) + else() + find_package(googletest ${GTEST_VERSION} QUIET NAMES GTest) + if(NOT googletest_FOUND) + FetchContent_Declare(googletest URL ${GTEST_URL}) + list(APPEND FETCH_PACKAGES googletest) + endif() + endif() +endif() + +# Make all declared dependencies available. +FetchContent_MakeAvailable(${FETCH_PACKAGES}) diff --git a/cmake/PackageAddTest.cmake b/cmake/PackageAddTest.cmake new file mode 100644 index 0000000..3423dda --- /dev/null +++ b/cmake/PackageAddTest.cmake @@ -0,0 +1,16 @@ +# macro to add a test executable for one of the project libraries +macro(PACKAGE_ADD_TEST testname linklibs) + if(NOT TARGET ${testname}) + # create an executable in which the tests will be stored + add_executable(${testname} ${ARGN}) + # link the Google test infrastructure and a default main function to the test executable. + target_link_libraries(${testname} PRIVATE ${linklibs} gmock gtest_main MQT::ProjectOptions + MQT::ProjectWarnings) + # discover tests + gtest_discover_tests( + ${testname} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" DISCOVERY_TIMEOUT 60) + set_target_properties(${testname} PROPERTIES FOLDER tests) + endif() +endmacro() diff --git a/cmake/PreventInSourceBuilds.cmake b/cmake/PreventInSourceBuilds.cmake new file mode 100644 index 0000000..0e58b79 --- /dev/null +++ b/cmake/PreventInSourceBuilds.cmake @@ -0,0 +1,17 @@ +# This function will prevent in-source builds +function(assure_out_of_source_builds) + # make sure the user doesn't play dirty with symlinks + get_filename_component(srcdir "${CMAKE_SOURCE_DIR}" REALPATH) + get_filename_component(bindir "${CMAKE_BINARY_DIR}" REALPATH) + + # disallow in-source builds + if("${srcdir}" STREQUAL "${bindir}") + message("######################################################") + message("Warning: in-source builds are disabled") + message("Please create a separate build directory and run cmake from there") + message("######################################################") + message(FATAL_ERROR "Quitting configuration") + endif() +endfunction() + +assure_out_of_source_builds() diff --git a/cmake/Sanitizers.cmake b/cmake/Sanitizers.cmake new file mode 100644 index 0000000..f2979e8 --- /dev/null +++ b/cmake/Sanitizers.cmake @@ -0,0 +1,51 @@ +# enable support for all kinds of sanitizers +function(enable_sanitizers target_name) + if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") + set(sanitizers "") + + option(ENABLE_SANITIZER_ADDRESS "Enable address sanitizer" FALSE) + if(ENABLE_SANITIZER_ADDRESS) + list(APPEND sanitizers "address") + endif() + + option(ENABLE_SANITIZER_LEAK "Enable leak sanitizer" FALSE) + if(ENABLE_SANITIZER_LEAK) + list(APPEND sanitizers "leak") + endif() + + option(ENABLE_SANITIZER_UNDEFINED_BEHAVIOR "Enable undefined behavior sanitizer" FALSE) + if(ENABLE_SANITIZER_UNDEFINED_BEHAVIOR) + list(APPEND sanitizers "undefined") + endif() + + option(ENABLE_SANITIZER_THREAD "Enable thread sanitizer" FALSE) + if(ENABLE_SANITIZER_THREAD) + if("address" IN_LIST sanitizers OR "leak" IN_LIST sanitizers) + message(WARNING "Thread sanitizer does not work with Address and Leak sanitizer enabled") + else() + list(APPEND sanitizers "thread") + endif() + endif() + + option(ENABLE_SANITIZER_MEMORY "Enable memory sanitizer" FALSE) + if(ENABLE_SANITIZER_MEMORY AND CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") + if("address" IN_LIST sanitizers + OR "thread" IN_LIST sanitizers + OR "leak" IN_LIST sanitizers) + message( + WARNING "Memory sanitizer does not work with Address, Thread and Leak sanitizer enabled") + else() + list(APPEND sanitizers "memory") + endif() + endif() + + list(JOIN sanitizers "," list_of_sanitizers) + endif() + + if(list_of_sanitizers) + if(NOT "${list_of_sanitizers}" STREQUAL "") + target_compile_options(${target_name} INTERFACE -fsanitize=${list_of_sanitizers}) + target_link_options(${target_name} INTERFACE -fsanitize=${list_of_sanitizers}) + endif() + endif() +endfunction() diff --git a/cmake/StandardProjectSettings.cmake b/cmake/StandardProjectSettings.cmake new file mode 100644 index 0000000..e7bdf1f --- /dev/null +++ b/cmake/StandardProjectSettings.cmake @@ -0,0 +1,65 @@ +# enable organization of targets into folders +set_property(GLOBAL PROPERTY USE_FOLDERS ON) + +# Set a default build type if none was specified +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + message(STATUS "Setting build type to 'Release' as none was specified.") + set(CMAKE_BUILD_TYPE + Release + CACHE STRING "Choose the type of build." FORCE) + # Set the possible values of build type for cmake-gui, ccmake + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "MinSizeRel" + "RelWithDebInfo") +endif() + +# Require C++ standard +set_property(GLOBAL PROPERTY CMAKE_CXX_STANDARD_REQUIRED ON) +set_property(GLOBAL PROPERTY CXX_EXTENSIONS OFF) + +# Generate compile_commands.json to make it easier to work with clang based tools +set(CMAKE_EXPORT_COMPILE_COMMANDS + ON + CACHE BOOL "Export compile commands" FORCE) + +option(ENABLE_IPO "Enable Interprocedural Optimization, aka Link Time Optimization (LTO)" OFF) +if(ENABLE_IPO) + include(CheckIPOSupported) + check_ipo_supported(RESULT ipo_supported OUTPUT ipo_output) + # enable inter-procedural optimization if it is supported + if(ipo_supported) + set(CMAKE_INTERPROCEDURAL_OPTIMIZATION + TRUE + CACHE BOOL "Enable Interprocedural Optimization" FORCE) + else() + message(DEBUG "IPO is not supported: ${ipo_output}") + endif() +endif() + +if(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") + add_compile_options(-fcolor-diagnostics) +elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + add_compile_options(-fdiagnostics-color=always) +else() + message(STATUS "No colored compiler diagnostic set for '${CMAKE_CXX_COMPILER_ID}' compiler.") +endif() + +option(DEPLOY "Configure for deployment") +if(DEFINED ENV{DEPLOY}) + set(DEPLOY + $ENV{DEPLOY} + CACHE BOOL "Use deployment configuration from environment" FORCE) + message(STATUS "Setting deployment configuration to '${DEPLOY}' from environment") +elseif(DEFINED ENV{CI}) + set(DEPLOY + ON + CACHE BOOL "Set deployment configuration to ON for CI" FORCE) + message(STATUS "Setting deployment configuration to '${DEPLOY}' for CI") +endif() + +# set deployment specific options +if(DEPLOY) + # set the macOS deployment target appropriately + set(CMAKE_OSX_DEPLOYMENT_TARGET + "10.15" + CACHE STRING "" FORCE) +endif() diff --git a/extern/googletest b/extern/googletest new file mode 160000 index 0000000..f8d7d77 --- /dev/null +++ b/extern/googletest @@ -0,0 +1 @@ +Subproject commit f8d7d77c06936315286eb55f8de22cd23c188571 diff --git a/include/dd/Complex.hpp b/include/dd/Complex.hpp new file mode 100644 index 0000000..eedd2e9 --- /dev/null +++ b/include/dd/Complex.hpp @@ -0,0 +1,91 @@ +/* + * This file is part of the MQT DD Package which is released under the MIT + * license. See file README.md or go to + * https://www.cda.cit.tum.de/research/quantum_dd/ for more information. + */ + +#ifndef DD_PACKAGE_COMPLEX_HPP +#define DD_PACKAGE_COMPLEX_HPP + +#include "ComplexTable.hpp" +#include "ComplexValue.hpp" + +#include +#include +#include + +namespace dd { +using CTEntry = ComplexTable<>::Entry; + +struct Complex { + CTEntry* real; + CTEntry* img; + + static Complex + zero; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables): + // Making it const breaks the code + static Complex + one; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables): + // Making it const breaks the code + + void setVal(const Complex& complexNum) const { + real->value = CTEntry::val(complexNum.real); + img->value = CTEntry::val(complexNum.img); + } + + [[nodiscard]] inline bool + approximatelyEquals(const Complex& complexNum) const { + return CTEntry::approximatelyEquals(real, complexNum.real) && + CTEntry::approximatelyEquals(img, complexNum.img); + }; + + [[nodiscard]] inline bool approximatelyZero() const { + return CTEntry::approximatelyZero(real) && CTEntry::approximatelyZero(img); + } + + [[nodiscard]] inline bool approximatelyOne() const { + return CTEntry::approximatelyOne(real) && CTEntry::approximatelyZero(img); + } + + inline bool operator==(const Complex& other) const { + return real == other.real && img == other.img; + } + + inline bool operator!=(const Complex& other) const { + return !operator==(other); + } + + [[nodiscard]] std::string toString(bool formatted = true, + int precision = -1) const { + return ComplexValue::toString(CTEntry::val(real), CTEntry::val(img), + formatted, precision); + } + + void writeBinary(std::ostream& os) const { + CTEntry::writeBinary(real, os); + CTEntry::writeBinary(img, os); + } +}; + +inline std::ostream& operator<<(std::ostream& os, const Complex& complexNum) { + return os << complexNum.toString(); +} +// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables): Making it +// const breaks the code +inline Complex Complex::zero{&ComplexTable<>::zero, &ComplexTable<>::zero}; +// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables): Making it +// const breaks the code +inline Complex Complex::one{&ComplexTable<>::one, &ComplexTable<>::zero}; +} // namespace dd + +namespace std { +template <> struct hash { + std::size_t operator()(dd::Complex const& complexNum) const noexcept { + auto h1 = dd::murmur64(reinterpret_cast(complexNum.real)); + auto h2 = dd::murmur64(reinterpret_cast(complexNum.img)); + return dd::combineHash(h1, h2); + } +}; +} // namespace std + +#endif // DD_PACKAGE_COMPLEX_HPP diff --git a/include/dd/ComplexCache.hpp b/include/dd/ComplexCache.hpp new file mode 100644 index 0000000..c804aa5 --- /dev/null +++ b/include/dd/ComplexCache.hpp @@ -0,0 +1,136 @@ +/* + * This file is part of the MQT DD Package which is released under the MIT + * license. See file README.md or go to + * https://www.cda.cit.tum.de/research/quantum_dd/ for more information. + */ + +#ifndef DD_PACKAGE_COMPLEXCACHE_HPP +#define DD_PACKAGE_COMPLEXCACHE_HPP + +#include "Complex.hpp" +#include "ComplexTable.hpp" + +#include +#include +#include + +namespace dd { + +template +class ComplexCache { + using Entry = ComplexTable<>::Entry; + +public: + ComplexCache() : allocationSize(INITIAL_ALLOCATION_SIZE) { + // allocate first chunk of cache entries + chunks.emplace_back(allocationSize); + allocations += allocationSize; + allocationSize *= GROWTH_FACTOR; + chunkIt = chunks[0].begin(); + chunkEndIt = chunks[0].end(); + } + + ~ComplexCache() = default; + + // access functions + [[nodiscard]] std::size_t getCount() const { return count; } + [[nodiscard]] std::size_t getPeakCount() const { return peakCount; } + [[nodiscard]] std::size_t getAllocations() const { return allocations; } + [[nodiscard]] std::size_t getGrowthFactor() const { return GROWTH_FACTOR; } + + [[nodiscard]] Complex getCachedComplex() { + // an entry is available on the stack + if (available != nullptr) { + assert(available->next != nullptr); + auto entry = Complex{available, available->next}; + available = entry.img->next; + count += 2; + return entry; + } + + // new chunk has to be allocated + if (chunkIt == chunkEndIt) { + chunks.emplace_back(allocationSize); + allocations += allocationSize; + allocationSize *= GROWTH_FACTOR; + chunkID++; + chunkIt = chunks[chunkID].begin(); + chunkEndIt = chunks[chunkID].end(); + } + + Complex c{}; + c.real = &(*chunkIt); + ++chunkIt; + c.img = &(*chunkIt); + ++chunkIt; + count += 2; + return c; + } + + [[nodiscard]] Complex getTemporaryComplex() { + // an entry is available on the stack + if (available != nullptr) { + assert(available->next != nullptr); + return {available, available->next}; + } + + // new chunk has to be allocated + if (chunkIt == chunkEndIt) { + chunks.emplace_back(allocationSize); + allocations += allocationSize; + allocationSize *= GROWTH_FACTOR; + chunkID++; + chunkIt = chunks[chunkID].begin(); + chunkEndIt = chunks[chunkID].end(); + } + return {&(*chunkIt), &(*(chunkIt + 1))}; + } + + void returnToCache(Complex& c) { + assert(count >= 2); + assert(c != Complex::zero); + assert(c != Complex::one); + assert(c.real->refCount == 0); + assert(c.img->refCount == 0); + c.img->next = available; + c.real->next = c.img; + available = c.real; + count -= 2; + } + + void clear() { + // clear available stack + available = nullptr; + + // release memory of all but the first chunk TODO: it could be desirable to + // keep the memory + while (chunkID > 0) { + chunks.pop_back(); + chunkID--; + } + // restore initial chunk setting + chunkIt = chunks[0].begin(); + chunkEndIt = chunks[0].end(); + allocationSize = INITIAL_ALLOCATION_SIZE * GROWTH_FACTOR; + allocations = INITIAL_ALLOCATION_SIZE; + + count = 0; + peakCount = 0; + }; + +private: + Entry* available{}; + std::vector> chunks{}; + std::size_t chunkID{0}; + typename std::vector::iterator chunkIt; + typename std::vector::iterator chunkEndIt; + std::size_t allocationSize; + + std::size_t allocations = 0; + std::size_t count = 0; + std::size_t peakCount = 0; +}; +} // namespace dd + +#endif // DD_PACKAGE_COMPLEXCACHE_HPP diff --git a/include/dd/ComplexNumbers.hpp b/include/dd/ComplexNumbers.hpp new file mode 100644 index 0000000..400c5d4 --- /dev/null +++ b/include/dd/ComplexNumbers.hpp @@ -0,0 +1,244 @@ +/* + * This file is part of the MQT DD Package which is released under the MIT + * license. See file README.md or go to + * https://www.cda.cit.tum.de/research/quantum_dd/ for more information. + */ + +#ifndef DDcomplex_H +#define DDcomplex_H + +#include "Complex.hpp" +#include "ComplexCache.hpp" +#include "ComplexTable.hpp" +#include "ComplexValue.hpp" +#include "Definitions.hpp" + +#include +#include +#include + +namespace dd { +struct ComplexNumbers { + ComplexTable<> complexTable{}; + ComplexCache<> complexCache{}; + + ComplexNumbers() = default; + ~ComplexNumbers() = default; + + void clear() { + complexTable.clear(); + complexCache.clear(); + } + + static void setTolerance(fp tol) { ComplexTable<>::setTolerance(tol); } + + // operations on complex numbers + // meanings are self-evident from the names + static void add(Complex& r, const Complex& a, const Complex& b) { + assert(r != Complex::zero); + assert(r != Complex::one); + r.real->value = CTEntry::val(a.real) + CTEntry::val(b.real); + r.img->value = CTEntry::val(a.img) + CTEntry::val(b.img); + } + static void sub(Complex& r, const Complex& a, const Complex& b) { + assert(r != Complex::zero); + assert(r != Complex::one); + r.real->value = CTEntry::val(a.real) - CTEntry::val(b.real); + r.img->value = CTEntry::val(a.img) - CTEntry::val(b.img); + } + static void mul(Complex& r, const Complex& a, const Complex& b) { + assert(r != Complex::zero); + assert(r != Complex::one); + if (a.approximatelyOne()) { + r.setVal(b); + } else if (b.approximatelyOne()) { + r.setVal(a); + } else if (a.approximatelyZero() || b.approximatelyZero()) { + r.real->value = 0.; + r.img->value = 0.; + } else { + const auto ar = CTEntry::val(a.real); + const auto ai = CTEntry::val(a.img); + const auto br = CTEntry::val(b.real); + const auto bi = CTEntry::val(b.img); + + r.real->value = ar * br - ai * bi; + r.img->value = ar * bi + ai * br; + } + } + static void div(Complex& r, const Complex& a, const Complex& b) { + assert(r != Complex::zero); + assert(r != Complex::one); + if (a.approximatelyEquals(b)) { + r.real->value = 1.; + r.img->value = 0.; + } else if (b.approximatelyOne()) { + r.setVal(a); + } else { + const auto ar = CTEntry::val(a.real); + const auto ai = CTEntry::val(a.img); + const auto br = CTEntry::val(b.real); + const auto bi = CTEntry::val(b.img); + + const auto cmag = br * br + bi * bi; + + r.real->value = (ar * br + ai * bi) / cmag; + r.img->value = (ai * br - ar * bi) / cmag; + } + } + static inline fp mag2(const Complex& a) { + auto ar = CTEntry::val(a.real); + auto ai = CTEntry::val(a.img); + + return ar * ar + ai * ai; + } + static inline fp mag(const Complex& a) { return std::sqrt(mag2(a)); } + static inline fp arg(const Complex& a) { + auto ar = CTEntry::val(a.real); + auto ai = CTEntry::val(a.img); + return std::atan2(ai, ar); + } + static Complex conj(const Complex& a) { + auto ret = a; + if (a.img != Complex::zero.img) { + ret.img = CTEntry::flipPointerSign(a.img); + } + return ret; + } + static Complex neg(const Complex& a) { + auto ret = a; + if (a.img != Complex::zero.img) { + ret.img = CTEntry::flipPointerSign(a.img); + } + if (a.real != Complex::zero.img) { + ret.real = CTEntry::flipPointerSign(a.real); + } + return ret; + } + + inline Complex addCached(const Complex& a, const Complex& b) { + auto c = getCached(); + add(c, a, b); + return c; + } + + inline Complex subCached(const Complex& a, const Complex& b) { + auto c = getCached(); + sub(c, a, b); + return c; + } + + inline Complex mulCached(const Complex& a, const Complex& b) { + auto c = getCached(); + mul(c, a, b); + return c; + } + + inline Complex divCached(const Complex& a, const Complex& b) { + auto c = getCached(); + div(c, a, b); + return c; + } + + // lookup a complex value in the complex table; if not found add it + Complex lookup(const Complex& c) { + if (c == Complex::zero) { + return Complex::zero; + } + if (c == Complex::one) { + return Complex::one; + } + + auto valr = CTEntry::val(c.real); + auto vali = CTEntry::val(c.img); + return lookup(valr, vali); + } + Complex lookup(const fp& r, const fp& i) { + Complex ret{}; + + const auto signR = std::signbit(r); + if (signR) { + const auto absr = std::abs(r); + // if absolute value is close enough to zero, just return the zero entry + // (avoiding -0.0) + if (absr < decltype(complexTable)::tolerance()) { + ret.real = &decltype(complexTable)::zero; + } else { + ret.real = CTEntry::getNegativePointer(complexTable.lookup(absr)); + } + } else { + ret.real = complexTable.lookup(r); + } + + const auto signI = std::signbit(i); + if (signI) { + const auto absi = std::abs(i); + // if absolute value is close enough to zero, just return the zero entry + // (avoiding -0.0) + if (absi < decltype(complexTable)::tolerance()) { + ret.img = &decltype(complexTable)::zero; + } else { + ret.img = CTEntry::getNegativePointer(complexTable.lookup(absi)); + } + } else { + ret.img = complexTable.lookup(i); + } + + return ret; + } + inline Complex lookup(const ComplexValue& c) { return lookup(c.r, c.i); } + + // reference counting and garbage collection + static void incRef(const Complex& c) { + // `zero` and `one` are static and never altered + if (c != Complex::zero && c != Complex::one) { + ComplexTable<>::incRef(c.real); + ComplexTable<>::incRef(c.img); + } + } + static void decRef(const Complex& c) { + // `zero` and `one` are static and never altered + if (c != Complex::zero && c != Complex::one) { + ComplexTable<>::decRef(c.real); + ComplexTable<>::decRef(c.img); + } + } + std::size_t garbageCollect(bool force = false) { + return complexTable.garbageCollect(force); + } + + // provide (temporary) cached complex number + inline Complex getTemporary() { return complexCache.getTemporaryComplex(); } + + inline Complex getTemporary(const fp& r, const fp& i) { + auto c = complexCache.getTemporaryComplex(); + c.real->value = r; + c.img->value = i; + return c; + } + + inline Complex getTemporary(const ComplexValue& c) { + return getTemporary(c.r, c.i); + } + + inline Complex getCached() { return complexCache.getCachedComplex(); } + + inline Complex getCached(const fp& r, const fp& i) { + auto c = complexCache.getCachedComplex(); + c.real->value = r; + c.img->value = i; + return c; + } + + inline Complex getCached(const ComplexValue& c) { + return getCached(c.r, c.i); + } + + void returnToCache(Complex& c) { complexCache.returnToCache(c); } + + [[nodiscard]] std::size_t cacheCount() const { + return complexCache.getCount(); + } +}; +} // namespace dd +#endif diff --git a/include/dd/ComplexTable.hpp b/include/dd/ComplexTable.hpp new file mode 100644 index 0000000..2a691dd --- /dev/null +++ b/include/dd/ComplexTable.hpp @@ -0,0 +1,641 @@ +/* + * This file is part of the MQT DD Package which is released under the MIT + * license. See file README.md or go to + * https://www.cda.cit.tum.de/research/quantum_dd/ for more information. + */ + +#ifndef DD_PACKAGE_COMPLEXTABLE_HPP +#define DD_PACKAGE_COMPLEXTABLE_HPP + +#include "Definitions.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace dd { +template +class ComplexTable { +public: + struct Entry { + fp value{}; + Entry* next{}; + RefCount refCount{}; + + /// + /// The sign of number is encoded in the least significant bit of its entry + /// pointer If not handled properly, this causes misaligned access These + /// routines allow to obtain safe pointers + /// + [[nodiscard]] static inline Entry* getAlignedPointer(const Entry* e) { + return reinterpret_cast(reinterpret_cast(e) & + ~static_cast(1U)); + } + + [[nodiscard]] static inline Entry* getNegativePointer(const Entry* e) { + return reinterpret_cast(reinterpret_cast(e) | + static_cast(1U)); + } + + [[nodiscard]] static inline Entry* flipPointerSign(const Entry* e) { + return reinterpret_cast(reinterpret_cast(e) ^ + static_cast(1U)); + } + + [[nodiscard]] static inline bool isNegativePointer(const Entry* e) { + return (reinterpret_cast(e) & + static_cast(1U)) != 0U; + } + + [[nodiscard]] static inline fp val(const Entry* e) { + if (isNegativePointer(e)) { + return -getAlignedPointer(e)->value; + } + return e->value; + } + + [[nodiscard]] static inline RefCount ref(const Entry* e) { + if (isNegativePointer(e)) { + return -getAlignedPointer(e)->refCount; + } + return e->refCount; + } + + [[nodiscard]] static constexpr bool + approximatelyEquals(const Entry* left, const Entry* right) { + return left == right || approximatelyEquals(val(left), val(right)); + } + [[nodiscard]] static constexpr bool approximatelyEquals(const fp left, + const fp right) { + return left == right || std::abs(left - right) <= TOLERANCE; + } + + [[nodiscard]] static constexpr bool approximatelyZero(const Entry* e) { + return e == &zero || approximatelyZero(val(e)); + } + [[nodiscard]] static constexpr bool approximatelyZero(const fp e) { + return std::abs(e) <= TOLERANCE; + } + + [[nodiscard]] static constexpr bool approximatelyOne(const Entry* e) { + return e == &one || approximatelyOne(val(e)); + } + [[nodiscard]] static constexpr bool approximatelyOne(fp e) { + return approximatelyEquals(e, 1.0); + } + + static void writeBinary(const Entry* e, std::ostream& os) { + auto temp = val(e); + os.write(reinterpret_cast(&temp), sizeof(decltype(temp))); + } + }; + + static inline Entry zero{ + 0., nullptr, + 1}; // NOLINT(readability-identifier-naming,cppcoreguidelines-avoid-non-const-global-variables) + // automatic renaming does not work reliably, so skip linting + static inline Entry sqrt2_2{ + SQRT2_2, nullptr, + 1}; // NOLINT(readability-identifier-naming,cppcoreguidelines-avoid-non-const-global-variables) + // automatic renaming does not work reliably, so skip linting + static inline Entry one{ + 1., nullptr, + 1}; // NOLINT(readability-identifier-naming,cppcoreguidelines-avoid-non-const-global-variables) + // automatic renaming does not work reliably, so skip linting + + ComplexTable() { + // add 1/2 to the complex table and increase its ref count (so that it is + // not collected) + lookup(0.5L)->refCount++; + } + + ~ComplexTable() = default; + + static fp tolerance() { return TOLERANCE; } + + static void setTolerance(fp tol) { TOLERANCE = tol; } + + static constexpr std::int64_t MASK = NBUCKET - 1; + + // linear (clipped) hash function + static constexpr std::int64_t hash(const fp val) { + assert(val >= 0); + auto key = static_cast(std::nearbyint(val * MASK)); + return std::min(key, MASK); + } + + // access functions + [[nodiscard]] std::size_t getCount() const { return count; } + + [[nodiscard]] std::size_t getPeakCount() const { return peakCount; } + + [[nodiscard]] std::size_t getAllocations() const { return allocations; } + + [[nodiscard]] std::size_t getGrowthFactor() const { return GROWTH_FACTOR; } + + [[nodiscard]] const auto& getTable() const { return table; } + + [[nodiscard]] bool availableEmpty() const { return available == nullptr; }; + + Entry* lookup(const fp& val) { + assert(!std::isnan(val)); + assert(val >= 0); // required anyway for the hash function + ++lookups; + if (Entry::approximatelyZero(val)) { + ++hits; + return &zero; + } + + if (Entry::approximatelyOne(val)) { + ++hits; + return &one; + } + + if (Entry::approximatelyEquals(val, SQRT2_2)) { + ++hits; + return &sqrt2_2; + } + + assert(val - TOLERANCE >= 0); // should be handle above as special case + + const auto lowerKey = static_cast(hash(val - TOLERANCE)); + const auto upperKey = static_cast(hash(val + TOLERANCE)); + + if (upperKey == lowerKey) { + ++findOrInserts; + return findOrInsert(lowerKey, val); + } + + // code below is to properly handle border cases |----(-|-)----| + // in case a value close to a border is looked up, + // only the last entry in the lower bucket and the first entry in the upper + // bucket need to be checked + + const auto key = static_cast(hash(val)); + + Entry* pLower; // NOLINT(cppcoreguidelines-init-variables) + Entry* pUpper; // NOLINT(cppcoreguidelines-init-variables) + if (lowerKey != key) { + pLower = tailTable[lowerKey]; + pUpper = table[key]; + ++lowerNeighbors; + // std::cout << "Border case between lower bucket " << + // lowerKey << " and actual bucket " << key << ". "; + } else { + pLower = tailTable[key]; + pUpper = table[upperKey]; + ++upperNeighbors; + // std::cout << "Border case between actual bucket " << key + // << " and upper bucket " << upperKey << ". "; + } + + bool lowerMatchFound = + (pLower != nullptr && Entry::approximatelyEquals(val, pLower->value)); + bool upperMatchFound = + (pUpper != nullptr && Entry::approximatelyEquals(val, pUpper->value)); + + if (lowerMatchFound && upperMatchFound) { + // std::cout << "Double match. "; + ++hits; + const auto diffToLower = std::abs(pLower->value - val); + const auto diffToUpper = std::abs(pUpper->value - val); + // val is actually closer to p_lower than to p_upper + if (diffToLower < diffToUpper) { + // std::cout << val << " is closer to lower val " << + // p_lower->value << " than to upper val " << + // p_upper->value << std::endl; + return pLower; + } + // std::cout << val << " is closer to upper val " << + // p_upper->value << " than to lower val " << + // p_upper->value << std::endl; + return pUpper; + } + + if (lowerMatchFound) { + ++hits; + // std::cout << "Matched " << val << " in lower bucket to " + // << p_lower->value << std::endl; + return pLower; + } + + if (upperMatchFound) { + ++hits; + // std::cout << "Matched " << val << " in upper bucket to " + // << p_upper->value << std::endl; + return pUpper; + } + + // value was not found in the table -> get a new entry and add it to the + // central bucket + Entry* entry = insert(key, val); + return entry; + } + + [[nodiscard]] Entry* getEntry() { + // an entry is available on the stack + if (!availableEmpty()) { + Entry* entry = available; + available = entry->next; + // returned entries could have a ref count != 0 + entry->refCount = 0; + return entry; + } + + // new chunk has to be allocated + if (chunkIt == chunkEndIt) { + chunks.emplace_back(allocationSize); + allocations += allocationSize; + allocationSize *= GROWTH_FACTOR; + chunkID++; + chunkIt = chunks[chunkID].begin(); + chunkEndIt = chunks[chunkID].end(); + } + + auto entry = &(*chunkIt); + ++chunkIt; + return entry; + } + + void returnEntry(Entry* entry) { + entry->next = available; + available = entry; + } + + // increment reference count for corresponding entry + static void incRef(Entry* entry) { + // get valid pointer + auto entryPtr = Entry::getAlignedPointer(entry); + + if (entryPtr == nullptr) { + return; + } + + // important (static) numbers are never altered + if (entryPtr != &one && entryPtr != &zero && entryPtr != &sqrt2_2) { + if (entryPtr->refCount == std::numeric_limits::max()) { + std::clog << "[WARN] MAXREFCNT reached for " << entryPtr->value + << ". Number will never be collected." << std::endl; + return; + } + + // increase reference count + entryPtr->refCount++; + } + } + + // decrement reference count for corresponding entry + static void decRef(Entry* entry) { + // get valid pointer + auto entryPtr = Entry::getAlignedPointer(entry); + + if (entryPtr == nullptr) { + return; + } + + // important (static) numbers are never altered + if (entryPtr != &one && entryPtr != &zero && entryPtr != &sqrt2_2) { + if (entryPtr->refCount == std::numeric_limits::max()) { + return; + } + if (entryPtr->refCount == 0) { + throw std::runtime_error("In ComplexTable: RefCount of entry " + + std::to_string(entryPtr->value) + + " is zero before decrement"); + } + + // decrease reference count + entryPtr->refCount--; + } + } + + [[nodiscard]] bool possiblyNeedsCollection() const { + return count >= gcLimit; + } + + std::size_t garbageCollect(bool force = false) { + gcCalls++; + // nothing to be done if garbage collection is not forced, and the limit has + // not been reached, or the current count is minimal (the complex table + // always contains at least 0.5) + if ((!force && count < gcLimit) || count <= 1) { + return 0; + } + + gcRuns++; + std::size_t collected = 0; + std::size_t remaining = 0; + for (std::size_t key = 0; key < table.size(); ++key) { + Entry* p = table[key]; + Entry* lastp = nullptr; + while (p != nullptr) { + if (p->refCount == 0) { + Entry* next = p->next; + if (lastp == nullptr) { + table[key] = next; + } else { + lastp->next = next; + } + returnEntry(p); + p = next; + collected++; + } else { + lastp = p; + p = p->next; + remaining++; + } + tailTable[key] = lastp; + } + } + // The garbage collection limit changes dynamically depending on the number + // of remaining (active) nodes. If it were not changed, garbage collection + // would run through the complete table on each successive call once the + // number of remaining entries reaches the garbage collection limit. It is + // increased whenever the number of remaining entries is rather close to the + // garbage collection threshold and decreased if the number of remaining + // entries is much lower than the current limit. + if (remaining > gcLimit / 10 * 9) { + gcLimit = remaining + INITIAL_GC_LIMIT; + } else if (remaining < gcLimit / 128) { + gcLimit /= 2; + } + count = remaining; + return collected; + } + + void clear() { + // clear table buckets + for (auto& bucket : table) { + bucket = nullptr; + } + for (auto& entry : tailTable) { + entry = nullptr; + } + + // clear available stack + available = nullptr; + + // release memory of all but the first chunk TODO: it could be desirable to + // keep the memory + while (chunkID > 0) { + chunks.pop_back(); + chunkID--; + } + // restore initial chunk setting + chunkIt = chunks[0].begin(); + chunkEndIt = chunks[0].end(); + allocationSize = INITIAL_ALLOCATION_SIZE * GROWTH_FACTOR; + allocations = INITIAL_ALLOCATION_SIZE; + + for (auto& entry : chunks[0]) { + entry.refCount = 0; + } + + count = 0; + peakCount = 0; + + collisions = 0; + insertCollisions = 0; + hits = 0; + findOrInserts = 0; + lookups = 0; + inserts = 0; + lowerNeighbors = 0; + upperNeighbors = 0; + + gcCalls = 0; + gcRuns = 0; + gcLimit = INITIAL_GC_LIMIT; + }; + + void print() { + const auto precision = std::cout.precision(); + std::cout.precision(std::numeric_limits::max_digits10); + for (std::size_t key = 0; key < table.size(); ++key) { + auto p = table[key]; + if (p != nullptr) { + std::cout << key << ": " << "\n"; + } + + while (p != nullptr) { + std::cout << "\t\t" << p->value << " " + << reinterpret_cast(p) << " " << p->refCount + << "\n"; + p = p->next; + } + + if (table[key] != nullptr) { + std::cout << "\n"; + } + } + std::cout.precision(precision); + } + + [[nodiscard]] fp hitRatio() const { return static_cast(hits) / lookups; } + + [[nodiscard]] fp colRatio() const { + return static_cast(collisions) / lookups; + } + + std::map getStatistics() { + return { + {"hits", hits}, + {"collisions", collisions}, + {"lookups", lookups}, + {"inserts", inserts}, + {"insertCollisions", insertCollisions}, + {"findOrInserts", findOrInserts}, + {"upperNeighbors", upperNeighbors}, + {"lowerNeighbors", lowerNeighbors}, + {"gcCalls", gcCalls}, + {"gcRuns", gcRuns}, + }; + } + + std::ostream& printStatistics(std::ostream& os = std::cout) { + // clang-format off + os << "hits: " << hits + << ", collisions: " << collisions + << ", looks: " << lookups + << ", inserts: " << inserts + << ", insertCollisions: " << insertCollisions + << ", findOrInserts: " << findOrInserts + << ", upperNeighbors: " << upperNeighbors + << ", lowerNeighbors: " << lowerNeighbors + << ", hitRatio: " << hitRatio() + << ", colRatio: " << colRatio() + << ", gc calls: " << gcCalls + << ", gc runs: " << gcRuns + << "\n"; + // clang-format on + return os; + } + + std::ostream& printBucketDistribution(std::ostream& os = std::cout) { + for (auto bucket : table) { + if (bucket == nullptr) { + os << "0\n"; + continue; + } + std::size_t bucketCount = 0; + while (bucket != nullptr) { + ++bucketCount; + bucket = bucket->next; + } + os << bucketCount << "\n"; + } + os << std::endl; + return os; + } + +private: + using Bucket = Entry*; + using Table = std::array; + + Table table{}; + + std::array tailTable{}; + + // table lookup statistics + std::size_t collisions = 0; + std::size_t insertCollisions = 0; + std::size_t hits = 0; + std::size_t findOrInserts = 0; + std::size_t lookups = 0; + std::size_t inserts = 0; + std::size_t lowerNeighbors = 0; + std::size_t upperNeighbors = 0; + + // numerical tolerance to be used for floating point values + static inline fp TOLERANCE = + std::numeric_limits::epsilon() * + 1024; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables,readability-identifier-naming) + + Entry* available{}; + std::vector> chunks{ + 1, std::vector{INITIAL_ALLOCATION_SIZE}}; + std::size_t chunkID{0}; + typename std::vector::iterator chunkIt{chunks.at(0).begin()}; + typename std::vector::iterator chunkEndIt{chunks.at(0).end()}; + std::size_t allocationSize{INITIAL_ALLOCATION_SIZE * GROWTH_FACTOR}; + + std::size_t allocations = INITIAL_ALLOCATION_SIZE; + std::size_t count = 0; + std::size_t peakCount = 0; + + // garbage collection + std::size_t gcCalls = 0; + std::size_t gcRuns = 0; + std::size_t gcLimit = INITIAL_GC_LIMIT; + + inline Entry* findOrInsert(const std::size_t key, const fp val) { + [[maybe_unused]] const fp valTol = val + TOLERANCE; + + Entry* curr = table[key]; + Entry* prev = nullptr; + + while (curr != nullptr && curr->value <= valTol) { + if (Entry::approximatelyEquals(curr->value, val)) { + // check if val is actually closer to the next element in the list (if + // there is one) + if (curr->next != nullptr) { + const auto& next = curr->next; + // potential candidate in range + if (valTol >= next->value) { + const auto diffToCurr = std::abs(curr->value - val); + const auto diffToNext = std::abs(next->value - val); + // val is actually closer to next than to curr + if (diffToNext < diffToCurr) { + // std::cout << "Second hit in + // bucket" << key << "! " << val << + // " is closer to " << next->value + // << " than to " << curr->value << + // std::endl; + ++hits; + return next; + } + } + } + // std::cout << "General hit in bucket " << key << "! + // " << val << " matches " << curr->value << + // std::endl; + ++hits; + return curr; + } + ++collisions; + prev = curr; + curr = curr->next; + } + + ++inserts; + Entry* entry = getEntry(); + entry->value = val; + + // std::cout << "Insert " << val << " in middle of bucket " << + // key << std::endl; + + if (prev == nullptr) { + // table bucket is empty + table[key] = entry; + } else { + prev->next = entry; + } + entry->next = curr; + if (curr == nullptr) { + tailTable[key] = entry; + } + count++; + peakCount = std::max(peakCount, count); + return entry; + } + + /** + * Inserts a value into the bucket indexed by key. This function assumes no + * element within TOLERANCE is present in the bucket. + * @param key index to the bucket + * @param val value to be inserted + * @return pointer to the inserted entry + */ + inline Entry* insert(const std::size_t key, const fp val) { + ++inserts; + Entry* entry = getEntry(); + entry->value = val; + + Entry* curr = table[key]; + Entry* prev = nullptr; + + while (curr != nullptr && curr->value <= val) { + ++insertCollisions; + prev = curr; + curr = curr->next; + } + + if (prev == nullptr) { + // table bucket is empty + table[key] = entry; + } else { + prev->next = entry; + } + entry->next = curr; + if (curr == nullptr) { + tailTable[key] = entry; + } + count++; + peakCount = std::max(peakCount, count); + return entry; + } +}; +} // namespace dd +#endif // DD_PACKAGE_COMPLEXTABLE_HPP diff --git a/include/dd/ComplexValue.hpp b/include/dd/ComplexValue.hpp new file mode 100644 index 0000000..a45d15a --- /dev/null +++ b/include/dd/ComplexValue.hpp @@ -0,0 +1,278 @@ +/* + * This file is part of the MQT DD Package which is released under the MIT + * license. See file README.md or go to + * https://www.cda.cit.tum.de/research/quantum_dd/ for more information. + */ + +#ifndef DD_PACKAGE_COMPLEXVALUE_HPP +#define DD_PACKAGE_COMPLEXVALUE_HPP + +#include "ComplexTable.hpp" +#include "Definitions.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace dd { +struct ComplexValue { + fp r; + fp i; + + [[nodiscard]] constexpr bool + approximatelyEquals(const ComplexValue& c) const { + return ComplexTable<>::Entry::approximatelyEquals(r, c.r) && + ComplexTable<>::Entry::approximatelyEquals(i, c.i); + } + + [[nodiscard]] constexpr bool approximatelyZero() const { + return ComplexTable<>::Entry::approximatelyZero(r) && + ComplexTable<>::Entry::approximatelyZero(i); + } + + [[nodiscard]] constexpr bool approximatelyOne() const { + return ComplexTable<>::Entry::approximatelyOne(r) && + ComplexTable<>::Entry::approximatelyZero(i); + } + + constexpr bool operator==(const ComplexValue& other) const { + return r == other.r && i == other.i; + } + + constexpr bool operator!=(const ComplexValue& other) const { + return !operator==(other); + } + + void readBinary(std::istream& is) { + is.read(reinterpret_cast(&r), sizeof(decltype(r))); + is.read(reinterpret_cast(&i), sizeof(decltype(i))); + } + + void writeBinary(std::ostream& os) const { + os.write(reinterpret_cast(&r), sizeof(decltype(r))); + os.write(reinterpret_cast(&i), sizeof(decltype(i))); + } + + void fromString(const std::string& realStr, std::string imagStr) { + const fp real = realStr.empty() ? 0. : std::stod(realStr); + + imagStr.erase(remove(imagStr.begin(), imagStr.end(), ' '), imagStr.end()); + imagStr.erase(remove(imagStr.begin(), imagStr.end(), 'i'), imagStr.end()); + if (imagStr == "+" || imagStr == "-") { + imagStr = imagStr + "1"; + } + const fp imag = imagStr.empty() ? 0. : std::stod(imagStr); + r = {real}; + i = {imag}; + } + + static auto + getLowestFraction(const double x, + const std::uint64_t maxDenominator = 1U << 10, + const fp tol = dd::ComplexTable<>::tolerance()) { + assert(x >= 0.); + + std::pair lowerBound{0U, 1U}; + std::pair upperBound{1U, 0U}; + + while ((lowerBound.second <= maxDenominator) && + (upperBound.second <= maxDenominator)) { + auto num = lowerBound.first + upperBound.first; + auto den = lowerBound.second + upperBound.second; + auto median = static_cast(num) / static_cast(den); + if (std::abs(x - median) <= tol) { + if (den <= maxDenominator) { + return std::pair{num, den}; + } + if (upperBound.second > lowerBound.second) { + return upperBound; + } + return lowerBound; + } + if (x > median) { + lowerBound = {num, den}; + } else { + upperBound = {num, den}; + } + } + if (lowerBound.second > maxDenominator) { + return upperBound; + } + return lowerBound; + } + + static void printFormatted(std::ostream& os, fp num, bool imaginary = false) { + if (std::abs(num) <= ComplexTable<>::tolerance()) { + os << (std::signbit(num) ? "-" : "+") << "0" << (imaginary ? "i" : ""); + return; + } + + const auto absnum = std::abs(num); + auto fraction = getLowestFraction(absnum); + auto approx = + static_cast(fraction.first) / static_cast(fraction.second); + auto error = std::abs(absnum - approx); + + if (error <= ComplexTable<>::tolerance()) { // suitable fraction a/b found + const std::string sign = std::signbit(num) ? "-" : (imaginary ? "+" : ""); + + if (fraction.first == 1U && fraction.second == 1U) { + os << sign << (imaginary ? "i" : "1"); + } else if (fraction.second == 1U) { + os << sign << fraction.first << (imaginary ? "i" : ""); + } else if (fraction.first == 1U) { + os << sign << (imaginary ? "i" : "1") << "/" << fraction.second; + } else { + os << sign << fraction.first << (imaginary ? "i" : "") << "/" + << fraction.second; + } + + return; + } + + const auto abssqrt = absnum / SQRT2_2; + fraction = getLowestFraction(abssqrt); + approx = static_cast(fraction.first) / static_cast(fraction.second); + error = std::abs(abssqrt - approx); + + if (error <= ComplexTable<>::tolerance()) { // suitable fraction a/(b * + // sqrt(2)) found + const std::string sign = std::signbit(num) ? "-" : (imaginary ? "+" : ""); + + if (fraction.first == 1U && fraction.second == 1U) { + os << sign << (imaginary ? "i" : "1") << "/√2"; + } else if (fraction.second == 1U) { + os << sign << fraction.first << (imaginary ? "i" : "") << "/√2"; + } else if (fraction.first == 1U) { + os << sign << (imaginary ? "i" : "1") << "/(" << fraction.second + << "√2)"; + } else { + os << sign << fraction.first << (imaginary ? "i" : "") << "/(" + << fraction.second << "√2)"; + } + return; + } + + const auto abspi = absnum / PI; + fraction = getLowestFraction(abspi); + approx = static_cast(fraction.first) / static_cast(fraction.second); + error = std::abs(abspi - approx); + + if (error <= ComplexTable<>::tolerance()) { // suitable fraction a/b π found + const std::string sign = std::signbit(num) ? "-" : (imaginary ? "+" : ""); + const std::string imagUnit = imaginary ? "i" : ""; + + if (fraction.first == 1U && fraction.second == 1U) { + os << sign << "π" << imagUnit; + } else if (fraction.second == 1U) { + os << sign << fraction.first << "π" << imagUnit; + } else if (fraction.first == 1U) { + os << sign << "π" << imagUnit << "/" << fraction.second; + } else { + os << sign << fraction.first << "π" << imagUnit << "/" + << fraction.second; + } + return; + } + + if (imaginary) { // default + os << (std::signbit(num) ? "" : "+") << num << "i"; + } else { + os << num; + } + } + + static std::string toString(const fp& real, const fp& imag, + bool formatted = true, int precision = -1) { + std::ostringstream ss{}; + + if (precision >= 0) { + ss << std::setprecision(precision); + } + const auto tol = ComplexTable<>::tolerance(); + + if (std::abs(real) <= tol && std::abs(imag) <= tol) { + return "0"; + } + + if (std::abs(real) > tol) { + if (formatted) { + printFormatted(ss, real); + } else { + ss << real; + } + } + if (std::abs(imag) > tol) { + if (formatted) { + if (std::abs(real - imag) <= tol) { + ss << "(1+i)"; + return ss.str(); + } + if (std::abs(real + imag) <= tol) { + ss << "(1-i)"; + return ss.str(); + } + printFormatted(ss, imag, true); + } else { + if (std::abs(real) <= tol) { + ss << imag; + } else { + if (imag > 0.) { + ss << "+"; + } + ss << imag; + } + ss << "i"; + } + } + + return ss.str(); + } + + explicit operator auto() const { return std::complex{r, i}; } + + ComplexValue& operator+=(const ComplexValue& rhs) { + r += rhs.r; + i += rhs.i; + return *this; + } + ComplexValue& operator*=(const ComplexValue& rhs) { + const auto tempr = (this->r * rhs.r - this->i * rhs.i); + const auto tempi = (this->r * rhs.i + this->i * rhs.r); + r = tempr; + i = tempi; + return *this; + } + friend ComplexValue operator+(ComplexValue lhs, const ComplexValue& rhs) { + lhs += rhs; + return lhs; + } + friend ComplexValue operator*(ComplexValue lhs, const ComplexValue& rhs) { + lhs *= rhs; + return lhs; + } +}; + +inline std::ostream& operator<<(std::ostream& os, const ComplexValue& c) { + return os << ComplexValue::toString(c.r, c.i); +} +} // namespace dd + +namespace std { +template <> struct hash { + std::size_t operator()(dd::ComplexValue const& c) const noexcept { + auto h1 = dd::murmur64(static_cast( + std::round(c.r / dd::ComplexTable<>::tolerance()))); + auto h2 = dd::murmur64(static_cast( + std::round(c.i / dd::ComplexTable<>::tolerance()))); + return dd::combineHash(h1, h2); + } +}; +} // namespace std +#endif // DD_PACKAGE_COMPLEXVALUE_HPP diff --git a/include/dd/ComputeTable.hpp b/include/dd/ComputeTable.hpp new file mode 100644 index 0000000..a71e1b7 --- /dev/null +++ b/include/dd/ComputeTable.hpp @@ -0,0 +1,103 @@ +/* + * This file is part of the MQT DD Package which is released under the MIT + * license. See file README.md or go to + * https://www.cda.cit.tum.de/research/quantum_dd/ for more information. + */ + +#ifndef DDpackage_COMPUTETABLE_HPP +#define DDpackage_COMPUTETABLE_HPP + +#include "Definitions.hpp" + +#include +#include +#include +#include + +namespace dd { + +/// Data structure for caching computed results +/// \tparam LeftOperandType type of the operation's left operand +/// \tparam RightOperandType type of the operation's right operand +/// \tparam ResultType type of the operation's result +/// \tparam NBUCKET number of hash buckets to use (has to be a power of two) +template +class ComputeTable { +public: + ComputeTable() = default; + + struct Entry { + LeftOperandType leftOperand; + RightOperandType rightOperand; + ResultType result; + }; + + static constexpr std::size_t MASK = NBUCKET - 1; + + static std::size_t hash(const LeftOperandType& leftOperand, + const RightOperandType& rightOperand) { + const auto h1 = std::hash{}(leftOperand); + const auto h2 = std::hash{}(rightOperand); + const auto hash = dd::combineHash(h1, h2); + return hash & MASK; + } + + // access functions + [[nodiscard]] const auto& getTable() const { return table; } + + void insert(const LeftOperandType& leftOperand, + const RightOperandType& rightOperand, const ResultType& result) { + const auto key = hash(leftOperand, rightOperand); + table[key] = {leftOperand, rightOperand, result}; + ++count; + } + + ResultType lookup(const LeftOperandType& leftOperand, + const RightOperandType& rightOperand) { + ResultType result{}; + lookups++; + const auto key = hash(leftOperand, rightOperand); + auto& entry = table[key]; + if (entry.result.nextNode == nullptr) { + return result; + } + if (entry.leftOperand != leftOperand) { + return result; + } + if (entry.rightOperand != rightOperand) { + return result; + } + + hits++; + return entry.result; + } + + void clear() { + if (count > 0) { + for (auto& entry : table) { + entry.result.p = nullptr; + } + count = 0; + } + hits = 0; + lookups = 0; + } + + [[nodiscard]] fp hitRatio() const { return static_cast(hits) / lookups; } + std::ostream& printStatistics(std::ostream& os = std::cout) { + os << "hits: " << hits << ", looks: " << lookups + << ", ratio: " << hitRatio() << std::endl; + return os; + } + +private: + std::array table{}; + // compute table lookup statistics + std::size_t hits = 0; + std::size_t lookups = 0; + std::size_t count = 0; +}; +} // namespace dd + +#endif // DDpackage_COMPUTETABLE_HPP diff --git a/include/dd/Control.hpp b/include/dd/Control.hpp new file mode 100644 index 0000000..ddcc556 --- /dev/null +++ b/include/dd/Control.hpp @@ -0,0 +1,63 @@ +/* + * This file is part of the MQT DD Package which is released under the MIT + * license. See file README.md or go to + * https://www.cda.cit.tum.de/research/quantum_dd/ for more information. + */ + +#ifndef DD_PACKAGE_CONTROL_HPP +#define DD_PACKAGE_CONTROL_HPP + +#include "Definitions.hpp" + +#include + +namespace dd { +struct Control { + // make dd -> op you need -> polarized control, unsigned integer + using Type = std::uint_fast8_t; + + QuantumRegister quantumRegister{}; + Type type = 1; // default value of a control level +}; + +inline bool operator<(const Control& lhs, const Control& rhs) { + return lhs.quantumRegister < rhs.quantumRegister || + (lhs.quantumRegister == rhs.quantumRegister && lhs.type < rhs.type); +} + +inline bool operator==(const Control& lhs, const Control& rhs) { + return lhs.quantumRegister == rhs.quantumRegister && lhs.type == rhs.type; +} + +inline bool operator!=(const Control& lhs, const Control& rhs) { + return !(lhs == rhs); +} + +// this allows a set of controls to be indexed by a quantum register, namely a +// qudit w/ d>=2 +struct CompareControl { + using is_transparent = void; + + inline bool operator()(const Control& lhs, const Control& rhs) const { + return lhs < rhs; + } + + inline bool operator()(QuantumRegister lhs, const Control& rhs) const { + return lhs < rhs.quantumRegister; + } + + inline bool operator()(const Control& lhs, QuantumRegister rhs) const { + return lhs.quantumRegister < rhs; + } +}; +using Controls = std::set; + +inline namespace literals { +// NOLINTNEXTLINE(google-runtime-int): Standard mandates usage of ULL +inline Control operator""_pc(unsigned long long int qreg) { + return {static_cast(qreg)}; +} +} // namespace literals +} // namespace dd + +#endif // DD_PACKAGE_CONTROL_HPP diff --git a/include/dd/Definitions.hpp b/include/dd/Definitions.hpp new file mode 100644 index 0000000..efa2c78 --- /dev/null +++ b/include/dd/Definitions.hpp @@ -0,0 +1,151 @@ +/* + * This file is part of the MQT DD Package which is released under the MIT + * license. See file README.md or go to + * https://www.cda.cit.tum.de/research/quantum_dd/ for more information. + */ + +#ifndef DDpackage_DATATYPES_HPP +#define DDpackage_DATATYPES_HPP + +#include "MDDPackage.hpp" + +#include +#include +#include +#include +#include +#include + +namespace dd { +// integer type used for indexing QuantumRegisters +// needs to be a signed type to encode -1 as the index for the terminal +// std::int_fast8_t can at least address 128 QuantumRegisters as [0, ..., 127] +// TODO understand how many quantum registers to put in a circuit, depending on +// the dimensions + +using QuantumRegister = std::int_fast8_t; + +static_assert(std::is_signed_v, + "Type QuantumRegister must be signed."); + +// integer type used for specifying numbers of QuantumRegisters +using QuantumRegisterCount = std::make_unsigned::type; + +// integer type used for reference counting +// 32bit suffice for a max ref count of around 4 billion +using RefCount = std::uint_fast32_t; +static_assert(std::is_unsigned_v, "RefCount should be unsigned."); + +// floating point type to use +using fp = double; +static_assert( + std::is_floating_point_v, + "fp should be a floating point type (float, *double*, long double)"); + +//----------------------------------------------------------------------------------------- +// TODO BELOW TEMPORARY LEGACY CODE +// Gate matrices +static constexpr std::uint_fast8_t RADIX_2 = 2; +// max no. of edges = RADIX_3^2 +static constexpr std::uint_fast8_t EDGE2 = RADIX_2 * RADIX_2; +// Gate matrices +static constexpr std::uint_fast8_t RADIX_3 = 3; +// max no. of edges = RADIX_3^2 +static constexpr std::uint_fast8_t EDGE3 = RADIX_3 * RADIX_3; + +static constexpr std::uint_fast8_t RADIX_4 = 4; + +static constexpr std::uint_fast8_t EDGE4 = RADIX_4 * RADIX_4; + +static constexpr std::uint_fast8_t RADIX_5 = 5; + +static constexpr std::uint_fast8_t EDGE5 = RADIX_5 * RADIX_5; + +static constexpr std::uint_fast8_t RADIX_6 = 6; + +static constexpr std::uint_fast8_t EDGE6 = RADIX_6 * RADIX_6; + +static constexpr std::uint_fast8_t RADIX_7 = 7; + +static constexpr std::uint_fast8_t EDGE7 = RADIX_7 * RADIX_7; +// TODO ABOVE TEMPORARY LEGACY CODE +//----------------------------------------------------------------------------------------- + +enum class BasisStates { Zero, One, Plus, Minus, Right, Left }; + +static constexpr fp SQRT2_2 = static_cast( + 0.707106781186547524400844362104849039284835937688474036588L); +static constexpr fp SQRT3_3 = static_cast( + 0.577350269189625764509148780501957455647601751270126876018L); +static constexpr fp SQRT4_4 = static_cast( + 0.500000000000000000000000000000000000000000000000000000000L); +static constexpr fp SQRT5_5 = static_cast( + 0.447213595499957939281834733746255247088123671922305144854L); +static constexpr fp SQRT6_6 = static_cast( + 0.408248290463863016366214012450981898660991246776111688072L); +static constexpr fp SQRT7_7 = static_cast( + 0.377964473009227227214516536234180060815751311868921454338L); + +static constexpr fp PI = static_cast( + 3.141592653589793238462643383279502884197169399375105820974L); +static constexpr fp PI_2 = static_cast( + 1.570796326794896619231321691639751442098584699687552910487L); +static constexpr fp PI_4 = static_cast( + 0.785398163397448309615660845819875721049292349843776455243L); + +using CVec = std::vector>; +using CMat = std::vector; + +// use hash maps for representing sparse vectors of probabilities +using ProbabilityVector = std::unordered_map; + +static constexpr std::uint_least64_t SERIALIZATION_VERSION = 1; + +// 64bit mixing hash (from MurmurHash3, +// https://github.com/aappleby/smhasher/blob/master/src/MurmurHash3.cpp) +constexpr std::size_t murmur64(std::size_t k) { + k ^= k >> 33; + k *= 0xff51afd7ed558ccdULL; + k ^= k >> 33; + k *= 0xc4ceb9fe1a85ec53ULL; + k ^= k >> 33; + return k; +} + +// combine two 64bit hashes into one 64bit hash (boost::hash_combine, +// https://www.boost.org/LICENSE_1_0.txt) +constexpr std::size_t combineHash(std::size_t lhs, std::size_t rhs) { + lhs ^= rhs + 0x9e3779b97f4a7c15ULL + (lhs << 6) + (lhs >> 2); + return lhs; +} + +// alternative hash combinator (from Google's city hash, +// https://github.com/google/cityhash/blob/master/COPYING) +// constexpr std::size_t combineHash(std::size_t lhs, std::size_t rhs) { +// const std::size_t kMul = 0x9ddfea08eb382d69ULL; +// std::size_t a = (lhs ^ rhs) * kMul; +// a ^= (a >> 47); +// std::size_t b = (rhs ^ a) * kMul; +// b ^= (b >> 47); +// b *= kMul; +// return b; +// } + +// calculates the Units in Last Place (ULP) distance of two floating point +// numbers +[[maybe_unused]] static std::size_t ulpDistance(fp a, fp b) { + if (a == b) { + return 0; + } + + std::size_t ulps = 1; + fp nextFP = std::nextafter(a, b); + while (nextFP != b) { + ulps++; + nextFP = std::nextafter(nextFP, b); + } + return ulps; +} + +} // namespace dd +#endif // DDpackage_DATATYPES_HPP diff --git a/include/dd/Edge.hpp b/include/dd/Edge.hpp new file mode 100644 index 0000000..9181755 --- /dev/null +++ b/include/dd/Edge.hpp @@ -0,0 +1,101 @@ +/* + * This file is part of the MQT DD Package which is released under the MIT + * license. See file README.md or go to + * https://www.cda.cit.tum.de/research/quantum_dd/ for more information. + */ + +#ifndef DD_PACKAGE_EDGE_HPP +#define DD_PACKAGE_EDGE_HPP + +#include "Complex.hpp" +#include "ComplexValue.hpp" + +#include +#include + +namespace dd { +template struct Edge { + Node* nextNode; + Complex weight; + + /// Comparing two DD edges with another involves comparing the respective + /// pointers and checking whether the corresponding weights are "close enough" + /// according to a given tolerance this notion of equivalence is chosen to + /// counter floating point inaccuracies + constexpr bool operator==(const Edge& other) const { + return nextNode == other.nextNode && + weight.approximatelyEquals(other.weight); + } + constexpr bool operator!=(const Edge& other) const { + return !operator==(other); + } + + [[nodiscard]] constexpr bool isTerminal() const { + return Node::isTerminal(nextNode); + } + + // edges pointing to zero and one terminals + static const inline Edge one{ + Node::terminal, + Complex::one}; // NOLINT(readability-identifier-naming) automatic renaming + // does not work reliably, so skip linting + static const inline Edge zero{ + Node::terminal, + Complex::zero}; // NOLINT(readability-identifier-naming) automatic + // renaming does not work reliably, so skip linting + + [[nodiscard]] static constexpr Edge terminal(const Complex& weight) { + return {Node::terminal, weight}; + } + [[nodiscard]] constexpr bool isZeroTerminal() const { + return Node::isTerminal(nextNode) && weight == Complex::zero; + } + [[nodiscard]] constexpr bool isOneTerminal() const { + return Node::isTerminal(nextNode) && weight == Complex::one; + } +}; + +template struct CachedEdge { + Node* nextNode{}; + ComplexValue weight{}; + + CachedEdge() = default; + CachedEdge(Node* nextNode, const ComplexValue& weightOriginal) + : nextNode(nextNode), weight(weightOriginal) {} + CachedEdge(Node* nextNode, const Complex& weightComplexNumber) + : nextNode(nextNode) { + weight.r = CTEntry::val(weightComplexNumber.real); + weight.i = CTEntry::val(weightComplexNumber.img); + } + + /// Comparing two DD edges with another involves comparing the respective + /// pointers and checking whether the corresponding weights are "close + /// enough" according to a given tolerance this notion of equivalence is + /// chosen to counter floating point inaccuracies + bool operator==(const CachedEdge& other) const { + return nextNode == other.nextNode && + weight.approximatelyEquals(other.weight); + } + bool operator!=(const CachedEdge& other) const { return !operator==(other); } +}; +} // namespace dd + +namespace std { +template struct hash> { + std::size_t operator()(dd::Edge const& edge) const noexcept { + auto h1 = dd::murmur64(reinterpret_cast(edge.nextNode)); + auto h2 = std::hash{}(edge.weight); + return dd::combineHash(h1, h2); + } +}; + +template struct hash> { + std::size_t operator()(dd::CachedEdge const& edge) const noexcept { + auto h1 = dd::murmur64(reinterpret_cast(edge.nextNode)); + auto h2 = std::hash{}(edge.weight); + return dd::combineHash(h1, h2); + } +}; +} // namespace std + +#endif // DD_PACKAGE_EDGE_HPP diff --git a/include/dd/GateMatrixDefinitions.hpp b/include/dd/GateMatrixDefinitions.hpp new file mode 100644 index 0000000..19e8bfa --- /dev/null +++ b/include/dd/GateMatrixDefinitions.hpp @@ -0,0 +1,1068 @@ +/* + * This file is part of the MQT DD Package which is released under the MIT + * license. See file README.md or go to + * https://www.cda.cit.tum.de/research/quantum_dd/ for more information. + */ + +#ifndef DD_PACKAGE_GATEMATRIXDEFINITIONS_H +#define DD_PACKAGE_GATEMATRIXDEFINITIONS_H + +#include "ComplexValue.hpp" +#include "Definitions.hpp" + +#include +#include +#include + +namespace dd { +// Complex constants +constexpr ComplexValue COMPLEX_ONE = {1., 0.}; +constexpr ComplexValue COMPLEX_MONE = {-1., 0.}; +constexpr ComplexValue COMPLEX_ZERO = {0., 0.}; +constexpr ComplexValue COMPLEX_I = {0., 1.}; +constexpr ComplexValue COMPLEX_MI = {0., -1.}; +constexpr ComplexValue COMPLEX_SQRT2_2 = {SQRT2_2, 0.}; +constexpr ComplexValue COMPLEX_MSQRT2_2 = {-SQRT2_2, 0.}; +constexpr ComplexValue COMPLEX_ISQRT2_2 = {0., SQRT2_2}; +constexpr ComplexValue COMPLEX_MISQRT2_2 = {0., -SQRT2_2}; +constexpr ComplexValue COMPLEX_1PLUSI = {SQRT2_2, SQRT2_2}; +constexpr ComplexValue COMPLEX_1MINUSI = {SQRT2_2, -SQRT2_2}; +constexpr ComplexValue COMPLEX_1PLUSI_2 = {0.5, 0.5}; +constexpr ComplexValue COMPLEX_1MINUSI_2 = {0.5, -0.5}; + +constexpr ComplexValue COMPLEX_SQRT3_3 = {SQRT3_3, 0.}; +constexpr ComplexValue COMPLEX_SQRT4_4 = {SQRT4_4, 0.}; +constexpr ComplexValue COMPLEX_SQRT5_5 = {SQRT5_5, 0.}; +constexpr ComplexValue COMPLEX_SQRT6_6 = {SQRT6_6, 0.}; +constexpr ComplexValue COMPLEX_SQRT7_7 = {SQRT7_7, 0.}; + +using GateMatrix = std::array; +using TritMatrix = std::array; +using QuartMatrix = std::array; +using QuintMatrix = std::array; +using SextMatrix = std::array; +using SeptMatrix = std::array; + +// NOLINTBEGIN(readability-identifier-naming) As these constants are used by +// other projects, we keep the naming +constexpr GateMatrix Imat{COMPLEX_ONE, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ONE}; +constexpr GateMatrix Hmat{COMPLEX_SQRT2_2, COMPLEX_SQRT2_2, COMPLEX_SQRT2_2, + COMPLEX_MSQRT2_2}; +constexpr GateMatrix Xmat{COMPLEX_ZERO, COMPLEX_ONE, COMPLEX_ONE, COMPLEX_ZERO}; +constexpr GateMatrix Ymat{COMPLEX_ZERO, COMPLEX_MI, COMPLEX_I, COMPLEX_ZERO}; +constexpr GateMatrix Zmat{COMPLEX_ONE, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_MONE}; +constexpr GateMatrix Smat{COMPLEX_ONE, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_I}; +constexpr GateMatrix Sdagmat{COMPLEX_ONE, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_MI}; +constexpr GateMatrix Tmat{COMPLEX_ONE, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_1PLUSI}; +constexpr GateMatrix Tdagmat{COMPLEX_ONE, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_1MINUSI}; +constexpr GateMatrix SXmat{COMPLEX_1PLUSI_2, COMPLEX_1MINUSI_2, + COMPLEX_1MINUSI_2, COMPLEX_1PLUSI_2}; +constexpr GateMatrix SXdagmat{COMPLEX_1MINUSI_2, COMPLEX_1PLUSI_2, + COMPLEX_1PLUSI_2, COMPLEX_1MINUSI_2}; +constexpr GateMatrix Vmat{COMPLEX_SQRT2_2, COMPLEX_MISQRT2_2, COMPLEX_MISQRT2_2, + COMPLEX_SQRT2_2}; +constexpr GateMatrix Vdagmat{COMPLEX_SQRT2_2, COMPLEX_ISQRT2_2, + COMPLEX_ISQRT2_2, COMPLEX_SQRT2_2}; + +inline GateMatrix Pimat(size_t i) { + GateMatrix zero = {COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO}; + zero.at(i + i * 2) = COMPLEX_ONE; + return zero; +} + +inline GateMatrix U3mat(fp lambda, fp phi, fp theta) { + return GateMatrix{{{std::cos(theta / 2.), 0.}, + {-std::cos(lambda) * std::sin(theta / 2.), + -std::sin(lambda) * std::sin(theta / 2.)}, + {std::cos(phi) * std::sin(theta / 2.), + std::sin(phi) * std::sin(theta / 2.)}, + {std::cos(lambda + phi) * std::cos(theta / 2.), + std::sin(lambda + phi) * std::cos(theta / 2.)}}}; +} + +inline GateMatrix U2mat(fp lambda, fp phi) { + return GateMatrix{ + COMPLEX_SQRT2_2, + {-std::cos(lambda) * SQRT2_2, -std::sin(lambda) * SQRT2_2}, + {std::cos(phi) * SQRT2_2, std::sin(phi) * SQRT2_2}, + {std::cos(lambda + phi) * SQRT2_2, std::sin(lambda + phi) * SQRT2_2}}; +} + +inline GateMatrix Phasemat(fp lambda) { + return GateMatrix{COMPLEX_ONE, + COMPLEX_ZERO, + COMPLEX_ZERO, + {std::cos(lambda), std::sin(lambda)}}; +} + +inline GateMatrix RXmat(fp lambda) { + return GateMatrix{{{std::cos(lambda / 2.), 0.}, + {0., -std::sin(lambda / 2.)}, + {0., -std::sin(lambda / 2.)}, + {std::cos(lambda / 2.), 0.}}}; +} + +inline GateMatrix RYmat(fp lambda) { + return GateMatrix{{{std::cos(lambda / 2.), 0.}, + {-std::sin(lambda / 2.), 0.}, + {std::sin(lambda / 2.), 0.}, + {std::cos(lambda / 2.), 0.}}}; +} + +inline GateMatrix RZmat(fp lambda) { + return GateMatrix{{{std::cos(lambda / 2.), -std::sin(lambda / 2.)}, + COMPLEX_ZERO, + COMPLEX_ZERO, + {std::cos(lambda / 2.), std::sin(lambda / 2.)}}}; +} + +inline GateMatrix H() { + return GateMatrix{COMPLEX_SQRT2_2, COMPLEX_SQRT2_2, COMPLEX_SQRT2_2, + COMPLEX_MSQRT2_2}; +} +inline GateMatrix RXY(fp theta, fp phi) { + GateMatrix rotation = { + dd::ComplexValue{std::cos(theta / 2.), 0.}, + dd::ComplexValue{std::sin(theta / 2.) * std::sin(phi), + -std::sin(theta / 2.) * std::cos(phi)}, + dd::ComplexValue{-std::sin(theta / 2.) * std::sin(phi), + -std::sin(theta / 2.) * std::cos(phi)}, + dd::ComplexValue{std::cos(theta / 2.), 0.}}; + return rotation; +} + +inline GateMatrix RZ(fp phi) { + GateMatrix rotation = { + dd::ComplexValue{std::cos(phi / 2), -std::sin(phi / 2)}, COMPLEX_ZERO, + COMPLEX_ZERO, dd::ComplexValue{std::cos(phi / 2), std::sin(phi / 2)}}; + return rotation; +} + +inline GateMatrix VirtRZ(fp phi, size_t i) { + GateMatrix zero = {COMPLEX_ONE, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ONE}; + zero.at(i + i * 2) = dd::ComplexValue{std::cos(phi), -std::sin(phi)}; + return zero; +} + +inline GateMatrix embX2(fp phi) { + GateMatrix identity = {COMPLEX_ONE, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ONE}; + identity.at(0) = COMPLEX_ZERO; + identity.at(2) = dd::ComplexValue{-std::sin(phi), -std::cos(phi)}; + identity.at(1) = dd::ComplexValue{std::sin(phi), -std::cos(phi)}; + identity.at(3) = COMPLEX_ZERO; + return identity; +} +/////////////////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////// + +constexpr TritMatrix I3{COMPLEX_ONE, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ONE, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ONE}; + +inline TritMatrix Pi3(size_t i) { + TritMatrix zero = {COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO}; + zero.at(i + i * 3) = COMPLEX_ONE; + return zero; +} + +inline TritMatrix H3() { + return TritMatrix{COMPLEX_SQRT3_3, + COMPLEX_SQRT3_3, + COMPLEX_SQRT3_3, + + COMPLEX_SQRT3_3, + COMPLEX_SQRT3_3 * dd::ComplexValue{std::cos(2. * PI / 3.), + std::sin(2. * PI / 3.)}, + COMPLEX_SQRT3_3 * dd::ComplexValue{std::cos(4. * PI / 3.), + std::sin(4. * PI / 3.)}, + + COMPLEX_SQRT3_3, + COMPLEX_SQRT3_3 * dd::ComplexValue{std::cos(4. * PI / 3.), + std::sin(4. * PI / 3.)}, + COMPLEX_SQRT3_3 * dd::ComplexValue{std::cos(2. * PI / 3.), + std::sin(2. * PI / 3.)}}; +} + +constexpr TritMatrix X3dag{COMPLEX_ZERO, COMPLEX_ONE, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ONE, + COMPLEX_ONE, COMPLEX_ZERO, COMPLEX_ZERO}; + +constexpr TritMatrix X3{COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ONE, + COMPLEX_ONE, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ONE, COMPLEX_ZERO}; + +constexpr TritMatrix PI02{COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_MONE, + COMPLEX_ZERO, COMPLEX_ONE, COMPLEX_ZERO, + COMPLEX_ONE, COMPLEX_ZERO, COMPLEX_ZERO}; +constexpr TritMatrix P_I02DAG{COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ONE, + COMPLEX_ZERO, COMPLEX_ONE, COMPLEX_ZERO, + COMPLEX_MONE, COMPLEX_ZERO, COMPLEX_ZERO}; +constexpr TritMatrix X01{COMPLEX_ZERO, COMPLEX_ONE, COMPLEX_ZERO, + COMPLEX_ONE, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ONE}; +constexpr TritMatrix Z01{COMPLEX_ONE, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_MONE, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ONE}; + +inline TritMatrix U301(fp lambda, fp phi, fp theta) { + return TritMatrix{{{std::cos(theta / 2.), 0.}, + {-std::cos(lambda) * std::sin(theta / 2.), + -std::sin(lambda) * std::sin(theta / 2.)}, + COMPLEX_ZERO, + {std::cos(phi) * std::sin(theta / 2.), + std::sin(phi) * std::sin(theta / 2.)}, + {std::cos(lambda + phi) * std::cos(theta / 2.), + std::sin(lambda + phi) * std::cos(theta / 2.)}, + COMPLEX_ZERO, + COMPLEX_ZERO, + COMPLEX_ZERO, + COMPLEX_ONE}}; +} + +inline TritMatrix RXY3(fp theta, fp phi, size_t leva, size_t levb) { + if (leva > levb or leva > 2 or levb > 2) { + throw std::invalid_argument("LEV A cannot be higher than LEV B"); + } + TritMatrix identity = {COMPLEX_ONE, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ONE, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ONE}; + identity.at(3 * leva + leva) = dd::ComplexValue{std::cos(theta / 2.), 0.}; + identity.at(3 * levb + leva) = + dd::ComplexValue{-std::sin(theta / 2.) * std::sin(phi), + -std::sin(theta / 2.) * std::cos(phi)}; + identity.at(3 * leva + levb) = + dd::ComplexValue{std::sin(theta / 2.) * std::sin(phi), + -std::sin(theta / 2.) * std::cos(phi)}; + identity.at(3 * levb + levb) = dd::ComplexValue{std::cos(theta / 2.), 0.}; + return identity; +} +inline TritMatrix RZ3(fp phi, size_t leva, size_t levb) { + if (leva > levb or leva > 2 or levb > 2) { + throw std::invalid_argument("LEV A cannot be higher than LEV B"); + } + TritMatrix identity = {COMPLEX_ONE, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ONE, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ONE}; + identity.at(3 * leva + leva) = + dd::ComplexValue{std::cos(phi / 2), -std::sin(phi / 2)}; + identity.at(3 * levb + levb) = + dd::ComplexValue{std::cos(phi / 2), +std::sin(phi / 2)}; + return identity; +} + +inline TritMatrix VirtRZ3(fp phi, size_t i) { + TritMatrix zero = {COMPLEX_ONE, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ONE, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ONE}; + zero.at(i + i * 3) = dd::ComplexValue{std::cos(phi), -std::sin(phi)}; + return zero; +} + +inline TritMatrix Z3() { + TritMatrix id = {COMPLEX_ONE, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ONE, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ONE}; + for (int level = 0; level < 3; ++level) { + const double angle = fmod(2.0 * level / 3, 2.0) * PI; + id.at(level + level * 3) = dd::ComplexValue{cos(angle), sin(angle)}; + } + + return id; +} +inline TritMatrix S3() { + TritMatrix id = {COMPLEX_ONE, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ONE, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ONE}; + for (int level = 0; level < 3; ++level) { + const double omegaArg = fmod(2.0 / 3 * level * (level + 1) / 2.0, 2.0); + const auto omega = dd::ComplexValue{cos(omegaArg * PI), sin(omegaArg * PI)}; + id.at(level + level * 3) = omega; + } + return id; +} + +inline TritMatrix embX3(fp phi, size_t leva, size_t levb) { + if (leva > levb or leva > 2 or levb > 2) { + throw std::invalid_argument("LEV A cannot be higher than LEV B"); + } + TritMatrix identity = {COMPLEX_ONE, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ONE, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ONE}; + identity.at(3 * leva + leva) = COMPLEX_ZERO; + identity.at(3 * leva + levb) = + dd::ComplexValue{std::sin(phi), -std::cos(phi)}; + identity.at(3 * levb + leva) = + dd::ComplexValue{-std::sin(phi), -std::cos(phi)}; + identity.at(3 * levb + levb) = COMPLEX_ZERO; + return identity; +} + +/////////////////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////// + +constexpr QuartMatrix I4{COMPLEX_ONE, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ONE, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ONE, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ONE}; + +inline QuartMatrix Pi4(size_t i) { + QuartMatrix zero = {COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO}; + zero.at(i + i * 4) = COMPLEX_ONE; + return zero; +} + +inline QuartMatrix H4() { + return QuartMatrix{ + COMPLEX_SQRT4_4, + COMPLEX_SQRT4_4, + COMPLEX_SQRT4_4, + COMPLEX_SQRT4_4, + + COMPLEX_SQRT4_4, + COMPLEX_SQRT4_4 * + dd::ComplexValue{std::cos(2. * PI / 4.), std::sin(2. * PI / 4.)}, + COMPLEX_SQRT4_4 * dd::ComplexValue{std::cos(2. * 2. * PI / 4.), + std::sin(2. * 2. * PI / 4.)}, + COMPLEX_SQRT4_4 * dd::ComplexValue{std::cos(3. * 2. * PI / 4.), + std::sin(3. * 2. * PI / 4.)}, + + COMPLEX_SQRT4_4, + COMPLEX_SQRT4_4 * dd::ComplexValue{std::cos(2. * 2. * PI / 4.), + std::sin(2. * 2. * PI / 4.)}, + COMPLEX_SQRT4_4 * dd::ComplexValue{std::cos(4. * 2. * PI / 4.), + std::sin(4. * 2. * PI / 4.)}, + COMPLEX_SQRT4_4 * dd::ComplexValue{std::cos(6. * 2. * PI / 4.), + std::sin(6. * 2. * PI / 4.)}, + + COMPLEX_SQRT4_4, + COMPLEX_SQRT4_4 * dd::ComplexValue{std::cos(3. * 2. * PI / 4.), + std::sin(3. * 2. * PI / 4.)}, + COMPLEX_SQRT4_4 * dd::ComplexValue{std::cos(6. * 2. * PI / 4.), + std::sin(6. * 2. * PI / 4.)}, + COMPLEX_SQRT4_4 * dd::ComplexValue{std::cos(9. * 2. * PI / 4.), + std::sin(9. * 2. * PI / 4.)}}; +} +inline QuartMatrix RXY4(fp theta, fp phi, size_t leva, size_t levb) { + if (leva > levb or leva > 3 or levb > 3) { + throw std::invalid_argument("LEV A cannot be higher than LEV B"); + } + QuartMatrix identity = { + COMPLEX_ONE, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, + + COMPLEX_ZERO, COMPLEX_ONE, COMPLEX_ZERO, COMPLEX_ZERO, + + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ONE, COMPLEX_ZERO, + + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ONE}; + identity.at(4 * leva + leva) = dd::ComplexValue{std::cos(theta / 2.), 0.}; + identity.at(4 * levb + leva) = + dd::ComplexValue{-std::sin(theta / 2.) * std::sin(phi), + -std::sin(theta / 2.) * std::cos(phi)}; + identity.at(4 * leva + levb) = + dd::ComplexValue{std::sin(theta / 2.) * std::sin(phi), + -std::sin(theta / 2.) * std::cos(phi)}; + identity.at(4 * levb + levb) = dd::ComplexValue{std::cos(theta / 2.), 0.}; + return identity; +} +constexpr QuartMatrix X4dag{ + COMPLEX_ZERO, COMPLEX_ONE, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ONE, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ONE, + COMPLEX_ONE, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO}; + +constexpr QuartMatrix X4{ + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ONE, + COMPLEX_ONE, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ONE, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ONE, COMPLEX_ZERO}; + +inline QuartMatrix RZ4(fp phi, size_t leva, size_t levb) { + if (leva > levb or leva > 3 or levb > 3) { + throw std::invalid_argument("LEV A cannot be higher than LEV B"); + } + QuartMatrix identity = { + COMPLEX_ONE, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, + + COMPLEX_ZERO, COMPLEX_ONE, COMPLEX_ZERO, COMPLEX_ZERO, + + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ONE, COMPLEX_ZERO, + + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ONE}; + identity.at(4 * leva + leva) = + dd::ComplexValue{std::cos(phi / 2), -std::sin(phi / 2)}; + identity.at(4 * levb + levb) = + dd::ComplexValue{std::cos(phi / 2), +std::sin(phi / 2)}; + return identity; +} + +inline QuartMatrix VirtRZ4(fp phi, size_t i) { + QuartMatrix zero = {COMPLEX_ONE, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ONE, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ONE, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ONE}; + zero.at(i + i * 4) = dd::ComplexValue{std::cos(phi), -std::sin(phi)}; + return zero; +} + +inline QuartMatrix Z4() { + QuartMatrix id = {COMPLEX_ONE, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ONE, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ONE, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ONE}; + + for (auto level = 0; level < 4; level++) { + const double angle = fmod(2.0 * level / 4, 2.0) * PI; + id.at(level + level * 4) = + dd::ComplexValue{std::cos(angle), std::sin(angle)}; + } + + return id; +} +inline QuartMatrix S4() { + QuartMatrix id = {COMPLEX_ONE, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ONE, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ONE, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ONE}; + for (auto level = 0; level < 4; level++) { + const double omegaArg = fmod(2.0 / 4 * level * (level + 1) / 2.0, 2.0); + const auto omega = + dd::ComplexValue{std::cos(omegaArg * PI), std::sin(omegaArg * PI)}; + id.at(level + level * 4) = omega; + } + return id; +} + +inline QuartMatrix embX4(fp phi, size_t leva, size_t levb) { + if (leva > levb or leva > 3 or levb > 3) { + throw std::invalid_argument("LEV A cannot be higher than LEV B"); + } + QuartMatrix identity = { + COMPLEX_ONE, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ONE, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ONE, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ONE}; + identity.at(4 * leva + leva) = COMPLEX_ZERO; + identity.at(4 * leva + levb) = + dd::ComplexValue{std::sin(phi), -std::cos(phi)}; + identity.at(4 * levb + leva) = + dd::ComplexValue{-std::sin(phi), -std::cos(phi)}; + identity.at(4 * levb + levb) = COMPLEX_ZERO; + return identity; +} +/////////////////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////// + +constexpr QuintMatrix I5{ + COMPLEX_ONE, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ONE, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ONE, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ONE, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ONE}; + +inline QuintMatrix Pi5(size_t i) { + QuintMatrix zero = { + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO}; + zero.at(i + i * 5) = COMPLEX_ONE; + return zero; +} +inline QuintMatrix H5() { + return QuintMatrix{ + COMPLEX_SQRT5_5, + COMPLEX_SQRT5_5, + COMPLEX_SQRT5_5, + COMPLEX_SQRT5_5, + COMPLEX_SQRT5_5, + + COMPLEX_SQRT5_5, + COMPLEX_SQRT5_5 * + dd::ComplexValue{std::cos(2. * PI / 5.), std::sin(2. * PI / 5.)}, + COMPLEX_SQRT5_5 * dd::ComplexValue{std::cos(2. * 2. * PI / 5.), + std::sin(2. * 2. * PI / 5.)}, + COMPLEX_SQRT5_5 * dd::ComplexValue{std::cos(3. * 2. * PI / 5.), + std::sin(3. * 2. * PI / 5.)}, + COMPLEX_SQRT5_5 * dd::ComplexValue{std::cos(4. * 2. * PI / 5.), + std::sin(4. * 2. * PI / 5.)}, + + COMPLEX_SQRT5_5, + COMPLEX_SQRT5_5 * dd::ComplexValue{std::cos(2. * 2. * PI / 5.), + std::sin(2. * 2. * PI / 5.)}, + COMPLEX_SQRT5_5 * dd::ComplexValue{std::cos(4. * 2. * PI / 5.), + std::sin(4. * 2. * PI / 5.)}, + COMPLEX_SQRT5_5 * dd::ComplexValue{std::cos(6. * 2. * PI / 5.), + std::sin(6. * 2. * PI / 5.)}, + COMPLEX_SQRT5_5 * dd::ComplexValue{std::cos(8. * 2. * PI / 5.), + std::sin(8. * 2. * PI / 5.)}, + + COMPLEX_SQRT5_5, + COMPLEX_SQRT5_5 * dd::ComplexValue{std::cos(3. * 2. * PI / 5.), + std::sin(3. * 2. * PI / 5.)}, + COMPLEX_SQRT5_5 * dd::ComplexValue{std::cos(6. * 2. * PI / 5.), + std::sin(6. * 2. * PI / 5.)}, + COMPLEX_SQRT5_5 * dd::ComplexValue{std::cos(9. * 2. * PI / 5.), + std::sin(9. * 2. * PI / 5.)}, + COMPLEX_SQRT5_5 * dd::ComplexValue{std::cos(12. * 2. * PI / 5.), + std::sin(12. * 2. * PI / 5.)}, + + COMPLEX_SQRT5_5, + COMPLEX_SQRT5_5 * dd::ComplexValue{std::cos(4. * 2. * PI / 5.), + std::sin(4. * 2. * PI / 5.)}, + COMPLEX_SQRT5_5 * dd::ComplexValue{std::cos(8. * 2. * PI / 5.), + std::sin(8. * 2. * PI / 5.)}, + COMPLEX_SQRT5_5 * dd::ComplexValue{std::cos(12. * 2. * PI / 5.), + std::sin(12. * 2. * PI / 5.)}, + COMPLEX_SQRT5_5 * dd::ComplexValue{std::cos(16. * 2. * PI / 5.), + std::sin(16. * 2. * PI / 5.)}}; +} + +inline QuintMatrix RXY5(fp theta, fp phi, size_t leva, size_t levb) { + if (leva > levb or leva > 4 or levb > 4) { + throw std::invalid_argument("LEV A cannot be higher than LEV B"); + } + QuintMatrix identity = { + COMPLEX_ONE, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, + + COMPLEX_ZERO, COMPLEX_ONE, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, + + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ONE, COMPLEX_ZERO, COMPLEX_ZERO, + + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ONE, COMPLEX_ZERO, + + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ONE}; + identity.at(5 * leva + leva) = dd::ComplexValue{std::cos(theta / 2.), 0.}; + identity.at(5 * levb + leva) = + dd::ComplexValue{-std::sin(theta / 2.) * std::sin(phi), + -std::sin(theta / 2.) * std::cos(phi)}; + identity.at(5 * leva + levb) = + dd::ComplexValue{std::sin(theta / 2.) * std::sin(phi), + -std::sin(theta / 2.) * std::cos(phi)}; + identity.at(5 * levb + levb) = dd::ComplexValue{std::cos(theta / 2.), 0.}; + return identity; +} + +constexpr QuintMatrix X5dag{ + COMPLEX_ZERO, COMPLEX_ONE, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ONE, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ONE, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ONE, + COMPLEX_ONE, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, +}; + +constexpr QuintMatrix X5{ + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ONE, + COMPLEX_ONE, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ONE, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ONE, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ONE, COMPLEX_ZERO}; + +inline QuintMatrix RZ5(fp phi, size_t leva, size_t levb) { + if (leva > levb or leva > 4 or levb > 4) { + throw std::invalid_argument("LEV A cannot be higher than LEV B"); + } + QuintMatrix identity = { + COMPLEX_ONE, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, + + COMPLEX_ZERO, COMPLEX_ONE, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, + + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ONE, COMPLEX_ZERO, COMPLEX_ZERO, + + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ONE, COMPLEX_ZERO, + + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ONE}; + identity.at(5 * leva + leva) = + dd::ComplexValue{std::cos(phi / 2), -std::sin(phi / 2)}; + identity.at(5 * levb + levb) = + dd::ComplexValue{std::cos(phi / 2), +std::sin(phi / 2)}; + return identity; +} +inline QuintMatrix VirtRZ5(fp phi, size_t i) { + QuintMatrix zero = { + COMPLEX_ONE, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ONE, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ONE, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ONE, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ONE}; + zero.at(i + i * 5) = dd::ComplexValue{std::cos(phi), -std::sin(phi)}; + return zero; +} + +inline QuintMatrix Z5() { + QuintMatrix id = { + COMPLEX_ONE, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ONE, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ONE, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ONE, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ONE}; + for (int level = 0; level < 5; ++level) { + const double angle = fmod(2.0 * level / 5, 2.0) * PI; + id.at(level + level * 5) = + dd::ComplexValue{std::cos(angle), std::sin(angle)}; + } + + return id; +} +inline QuintMatrix S5() { + QuintMatrix id = { + COMPLEX_ONE, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ONE, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ONE, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ONE, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ONE}; + for (int level = 0; level < 5; ++level) { + const double omegaArg = fmod(2.0 / 5 * level * (level + 1) / 2.0, 2.0); + const auto omega = + dd::ComplexValue{std::cos(omegaArg * PI), std::sin(omegaArg * PI)}; + id.at(level + level * 5) = omega; + } + return id; +} +inline QuintMatrix embX5(fp phi, size_t leva, size_t levb) { + if (leva > levb or leva > 4 or levb > 4) { + throw std::invalid_argument("LEV A cannot be higher than LEV B"); + } + QuintMatrix identity = { + COMPLEX_ONE, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ONE, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ONE, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ONE, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ONE}; + identity.at(5 * leva + leva) = COMPLEX_ZERO; + identity.at(5 * leva + levb) = + dd::ComplexValue{std::sin(phi), -std::cos(phi)}; + identity.at(5 * levb + leva) = + dd::ComplexValue{-std::sin(phi), -std::cos(phi)}; + identity.at(5 * levb + levb) = COMPLEX_ZERO; + return identity; +} +/////////////////////////////////////////////////////////////////////////////////////////// +constexpr SextMatrix I6{COMPLEX_ONE, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ONE, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ONE, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ONE, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ONE, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ONE}; + +inline SextMatrix Pi6(size_t i) { + SextMatrix zero = { + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, + }; + zero.at(i + i * 6) = COMPLEX_ONE; + return zero; +} +inline SextMatrix H6() { + return SextMatrix{ + COMPLEX_SQRT6_6, + COMPLEX_SQRT6_6, + COMPLEX_SQRT6_6, + COMPLEX_SQRT6_6, + COMPLEX_SQRT6_6, + COMPLEX_SQRT6_6, + + COMPLEX_SQRT6_6, + COMPLEX_SQRT6_6 * + dd::ComplexValue{std::cos(2. * PI / 6.), std::sin(2. * PI / 6.)}, + COMPLEX_SQRT6_6 * dd::ComplexValue{std::cos(2. * 2. * PI / 6.), + std::sin(2. * 2. * PI / 6.)}, + COMPLEX_SQRT6_6 * dd::ComplexValue{std::cos(3. * 2. * PI / 6.), + std::sin(3. * 2. * PI / 6.)}, + COMPLEX_SQRT6_6 * dd::ComplexValue{std::cos(4. * 2. * PI / 6.), + std::sin(4. * 2. * PI / 6.)}, + COMPLEX_SQRT6_6 * dd::ComplexValue{std::cos(5. * 2. * PI / 6.), + std::sin(5. * 2. * PI / 6.)}, + + COMPLEX_SQRT6_6, + COMPLEX_SQRT6_6 * dd::ComplexValue{std::cos(2. * 2. * PI / 6.), + std::sin(2. * 2. * PI / 6.)}, + COMPLEX_SQRT6_6 * dd::ComplexValue{std::cos(4. * 2. * PI / 6.), + std::sin(4. * 2. * PI / 6.)}, + COMPLEX_SQRT6_6 * dd::ComplexValue{std::cos(6. * 2. * PI / 6.), + std::sin(6. * 2. * PI / 6.)}, + COMPLEX_SQRT6_6 * dd::ComplexValue{std::cos(8. * 2. * PI / 6.), + std::sin(8. * 2. * PI / 6.)}, + COMPLEX_SQRT6_6 * dd::ComplexValue{std::cos(10. * 2. * PI / 6.), + std::sin(10. * 2. * PI / 6.)}, + + COMPLEX_SQRT6_6, + COMPLEX_SQRT6_6 * dd::ComplexValue{std::cos(3. * 2. * PI / 6.), + std::sin(3. * 2. * PI / 6.)}, + COMPLEX_SQRT6_6 * dd::ComplexValue{std::cos(6. * 2. * PI / 6.), + std::sin(6. * 2. * PI / 6.)}, + COMPLEX_SQRT6_6 * dd::ComplexValue{std::cos(9. * 2. * PI / 6.), + std::sin(9. * 2. * PI / 6.)}, + COMPLEX_SQRT6_6 * dd::ComplexValue{std::cos(12. * 2. * PI / 6.), + std::sin(12. * 2. * PI / 6.)}, + COMPLEX_SQRT6_6 * dd::ComplexValue{std::cos(15. * 2. * PI / 6.), + std::sin(15. * 2. * PI / 6.)}, + + COMPLEX_SQRT6_6, + COMPLEX_SQRT6_6 * dd::ComplexValue{std::cos(4. * 2. * PI / 6.), + std::sin(4. * 2. * PI / 6.)}, + COMPLEX_SQRT6_6 * dd::ComplexValue{std::cos(8. * 2. * PI / 6.), + std::sin(8. * 2. * PI / 6.)}, + COMPLEX_SQRT6_6 * dd::ComplexValue{std::cos(12. * 2. * PI / 6.), + std::sin(12. * 2. * PI / 6.)}, + COMPLEX_SQRT6_6 * dd::ComplexValue{std::cos(16. * 2. * PI / 6.), + std::sin(16. * 2. * PI / 6.)}, + COMPLEX_SQRT6_6 * dd::ComplexValue{std::cos(20. * 2. * PI / 6.), + std::sin(20. * 2. * PI / 6.)}, + + COMPLEX_SQRT6_6, + COMPLEX_SQRT6_6 * dd::ComplexValue{std::cos(5. * 2. * PI / 6.), + std::sin(5. * 2. * PI / 6.)}, + COMPLEX_SQRT6_6 * dd::ComplexValue{std::cos(10. * 2. * PI / 6.), + std::sin(10. * 2. * PI / 6.)}, + COMPLEX_SQRT6_6 * dd::ComplexValue{std::cos(15. * 2. * PI / 6.), + std::sin(15. * 2. * PI / 6.)}, + COMPLEX_SQRT6_6 * dd::ComplexValue{std::cos(20. * 2. * PI / 6.), + std::sin(20. * 2. * PI / 6.)}, + COMPLEX_SQRT6_6 * dd::ComplexValue{std::cos(25. * 2. * PI / 6.), + std::sin(25. * 2. * PI / 6.)}, + }; +} + +inline SextMatrix RXY6(fp theta, fp phi, size_t leva, size_t levb) { + if (leva > levb or leva > 5 or levb > 5) { + throw std::invalid_argument("LEV A cannot be higher than LEV B"); + } + SextMatrix identity = I6; + identity.at(6 * leva + leva) = dd::ComplexValue{std::cos(theta / 2.), 0.}; + identity.at(6 * levb + leva) = + dd::ComplexValue{-std::sin(theta / 2.) * std::sin(phi), + -std::sin(theta / 2.) * std::cos(phi)}; + identity.at(6 * leva + levb) = + dd::ComplexValue{std::sin(theta / 2.) * std::sin(phi), + -std::sin(theta / 2.) * std::cos(phi)}; + identity.at(6 * levb + levb) = dd::ComplexValue{std::cos(theta / 2.), 0.}; + return identity; +} + +constexpr SextMatrix X6dag{COMPLEX_ZERO, COMPLEX_ONE, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, + + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ONE, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, + + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ONE, COMPLEX_ZERO, COMPLEX_ZERO, + + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ONE, COMPLEX_ZERO, + + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ONE, + + COMPLEX_ONE, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO}; + +constexpr SextMatrix X6{COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ONE, COMPLEX_ONE, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ONE, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ONE, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ONE, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ONE, COMPLEX_ZERO}; + +inline SextMatrix RZ6(fp phi, size_t leva, size_t levb) { + if (leva > levb or leva > 5 or levb > 5) { + throw std::invalid_argument("LEV A cannot be higher than LEV B"); + } + SextMatrix identity = I6; + identity.at(6 * leva + leva) = + dd::ComplexValue{std::cos(phi / 2), -std::sin(phi / 2)}; + identity.at(6 * levb + levb) = + dd::ComplexValue{std::cos(phi / 2), +std::sin(phi / 2)}; + return identity; +} +inline SextMatrix VirtRZ6(fp phi, size_t i) { + SextMatrix identity = I6; + identity.at(i + i * 6) = dd::ComplexValue{std::cos(phi), -std::sin(phi)}; + return identity; +} + +inline SextMatrix Z6() { + SextMatrix id = I6; + for (int level = 0; level < 6; ++level) { + const double angle = fmod(2.0 * level / 6, 2.0) * PI; + id.at(level + level * 6) = + dd::ComplexValue{std::cos(angle), std::sin(angle)}; + } + + return id; +} +inline SextMatrix S6() { + SextMatrix id = I6; + for (int level = 0; level < 6; ++level) { + const double omegaArg = fmod(2.0 / 6 * level * (level + 1) / 2.0, 2.0); + const auto omega = + dd::ComplexValue{std::cos(omegaArg * PI), std::sin(omegaArg * PI)}; + id.at(level + level * 6) = omega; + } + return id; +} +inline SextMatrix embX6(fp phi, size_t leva, size_t levb) { + if (leva > levb or leva > 5 or levb > 5) { + throw std::invalid_argument("LEV A cannot be higher than LEV B"); + } + SextMatrix identity = I6; + identity.at(6 * leva + leva) = COMPLEX_ZERO; + identity.at(6 * leva + levb) = + dd::ComplexValue{std::sin(phi), -std::cos(phi)}; + identity.at(6 * levb + leva) = + dd::ComplexValue{-std::sin(phi), -std::cos(phi)}; + identity.at(6 * levb + levb) = COMPLEX_ZERO; + return identity; +} +/////////////////////////////////////////////////////////////////////////////////////////// +constexpr SeptMatrix I7{ + COMPLEX_ONE, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ONE, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ONE, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ONE, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ONE, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ONE, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ONE}; + +inline SeptMatrix Pi7(size_t i) { + SeptMatrix zero = { + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO}; + zero.at(i + i * 7) = COMPLEX_ONE; + return zero; +} +inline SeptMatrix H7() { + return SeptMatrix{ + COMPLEX_SQRT7_7, + COMPLEX_SQRT7_7, + COMPLEX_SQRT7_7, + COMPLEX_SQRT7_7, + COMPLEX_SQRT7_7, + COMPLEX_SQRT7_7, + COMPLEX_SQRT7_7, + + COMPLEX_SQRT7_7, + COMPLEX_SQRT7_7 * + dd::ComplexValue{std::cos(2. * PI / 7.), std::sin(2. * PI / 7.)}, + COMPLEX_SQRT7_7 * dd::ComplexValue{std::cos(2. * 2. * PI / 7.), + std::sin(2. * 2. * PI / 7.)}, + COMPLEX_SQRT7_7 * dd::ComplexValue{std::cos(3. * 2. * PI / 7.), + std::sin(3. * 2. * PI / 7.)}, + COMPLEX_SQRT7_7 * dd::ComplexValue{std::cos(4. * 2. * PI / 7.), + std::sin(4. * 2. * PI / 7.)}, + COMPLEX_SQRT7_7 * dd::ComplexValue{std::cos(5. * 2. * PI / 7.), + std::sin(5. * 2. * PI / 7.)}, + COMPLEX_SQRT7_7 * dd::ComplexValue{std::cos(6. * 2. * PI / 7.), + std::sin(6. * 2. * PI / 7.)}, + + COMPLEX_SQRT7_7, + COMPLEX_SQRT7_7 * dd::ComplexValue{std::cos(2. * 2. * PI / 7.), + std::sin(2. * 2. * PI / 7.)}, + COMPLEX_SQRT7_7 * dd::ComplexValue{std::cos(4. * 2. * PI / 7.), + std::sin(4. * 2. * PI / 7.)}, + COMPLEX_SQRT7_7 * dd::ComplexValue{std::cos(6. * 2. * PI / 7.), + std::sin(6. * 2. * PI / 7.)}, + COMPLEX_SQRT7_7 * dd::ComplexValue{std::cos(8. * 2. * PI / 7.), + std::sin(8. * 2. * PI / 7.)}, + COMPLEX_SQRT7_7 * dd::ComplexValue{std::cos(10. * 2. * PI / 7.), + std::sin(10. * 2. * PI / 7.)}, + COMPLEX_SQRT7_7 * dd::ComplexValue{std::cos(12. * 2. * PI / 7.), + std::sin(12. * 2. * PI / 7.)}, + + COMPLEX_SQRT7_7, + COMPLEX_SQRT7_7 * dd::ComplexValue{std::cos(3. * 2. * PI / 7.), + std::sin(3. * 2. * PI / 7.)}, + COMPLEX_SQRT7_7 * dd::ComplexValue{std::cos(6. * 2. * PI / 7.), + std::sin(6. * 2. * PI / 7.)}, + COMPLEX_SQRT7_7 * dd::ComplexValue{std::cos(9. * 2. * PI / 7.), + std::sin(9. * 2. * PI / 7.)}, + COMPLEX_SQRT7_7 * dd::ComplexValue{std::cos(12. * 2. * PI / 7.), + std::sin(12. * 2. * PI / 7.)}, + COMPLEX_SQRT7_7 * dd::ComplexValue{std::cos(15. * 2. * PI / 7.), + std::sin(15. * 2. * PI / 7.)}, + COMPLEX_SQRT7_7 * dd::ComplexValue{std::cos(18. * 2. * PI / 7.), + std::sin(18. * 2. * PI / 7.)}, + + COMPLEX_SQRT7_7, + COMPLEX_SQRT7_7 * dd::ComplexValue{std::cos(4. * 2. * PI / 7.), + std::sin(4. * 2. * PI / 7.)}, + COMPLEX_SQRT7_7 * dd::ComplexValue{std::cos(8. * 2. * PI / 7.), + std::sin(8. * 2. * PI / 7.)}, + COMPLEX_SQRT7_7 * dd::ComplexValue{std::cos(12. * 2. * PI / 7.), + std::sin(12. * 2. * PI / 7.)}, + COMPLEX_SQRT7_7 * dd::ComplexValue{std::cos(16. * 2. * PI / 7.), + std::sin(16. * 2. * PI / 7.)}, + COMPLEX_SQRT7_7 * dd::ComplexValue{std::cos(20. * 2. * PI / 7.), + std::sin(20. * 2. * PI / 7.)}, + COMPLEX_SQRT7_7 * dd::ComplexValue{std::cos(24. * 2. * PI / 7.), + std::sin(24. * 2. * PI / 7.)}, + + COMPLEX_SQRT7_7, + COMPLEX_SQRT7_7 * dd::ComplexValue{std::cos(5. * 2. * PI / 7.), + std::sin(5. * 2. * PI / 7.)}, + COMPLEX_SQRT7_7 * dd::ComplexValue{std::cos(10. * 2. * PI / 7.), + std::sin(10. * 2. * PI / 7.)}, + COMPLEX_SQRT7_7 * dd::ComplexValue{std::cos(15. * 2. * PI / 7.), + std::sin(15. * 2. * PI / 7.)}, + COMPLEX_SQRT7_7 * dd::ComplexValue{std::cos(20. * 2. * PI / 7.), + std::sin(20. * 2. * PI / 7.)}, + COMPLEX_SQRT7_7 * dd::ComplexValue{std::cos(25. * 2. * PI / 7.), + std::sin(25. * 2. * PI / 7.)}, + COMPLEX_SQRT7_7 * dd::ComplexValue{std::cos(30. * 2. * PI / 7.), + std::sin(30. * 2. * PI / 7.)}, + + COMPLEX_SQRT7_7, + COMPLEX_SQRT7_7 * dd::ComplexValue{std::cos(6. * 2. * PI / 7.), + std::sin(6. * 2. * PI / 7.)}, + COMPLEX_SQRT7_7 * dd::ComplexValue{std::cos(12. * 2. * PI / 7.), + std::sin(12. * 2. * PI / 7.)}, + COMPLEX_SQRT7_7 * dd::ComplexValue{std::cos(18. * 2. * PI / 7.), + std::sin(18. * 2. * PI / 7.)}, + COMPLEX_SQRT7_7 * dd::ComplexValue{std::cos(24. * 2. * PI / 7.), + std::sin(24. * 2. * PI / 7.)}, + COMPLEX_SQRT7_7 * dd::ComplexValue{std::cos(30. * 2. * PI / 7.), + std::sin(30. * 2. * PI / 7.)}, + COMPLEX_SQRT7_7 * dd::ComplexValue{std::cos(36. * 2. * PI / 7.), + std::sin(36. * 2. * PI / 7.)}}; +} + +inline SeptMatrix RXY7(fp theta, fp phi, size_t leva, size_t levb) { + if (leva > levb or leva > 6 or levb > 6) { + throw std::invalid_argument("LEV A cannot be higher than LEV B"); + } + SeptMatrix identity = I7; + identity.at(7 * leva + leva) = dd::ComplexValue{std::cos(theta / 2.), 0.}; + identity.at(7 * levb + leva) = + dd::ComplexValue{-std::sin(theta / 2.) * std::sin(phi), + -std::sin(theta / 2.) * std::cos(phi)}; + identity.at(7 * leva + levb) = + dd::ComplexValue{std::sin(theta / 2.) * std::sin(phi), + -std::sin(theta / 2.) * std::cos(phi)}; + identity.at(7 * levb + levb) = dd::ComplexValue{std::cos(theta / 2.), 0.}; + return identity; +} + +constexpr SeptMatrix X7dag{ + COMPLEX_ZERO, COMPLEX_ONE, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, + + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ONE, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, + + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ONE, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, + + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ONE, COMPLEX_ZERO, COMPLEX_ZERO, + + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ONE, COMPLEX_ZERO, + + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ONE, + + COMPLEX_ONE, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO}; + +constexpr SeptMatrix X7{ + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ONE, COMPLEX_ONE, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ONE, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ONE, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ONE, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ONE, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ZERO, + COMPLEX_ZERO, COMPLEX_ZERO, COMPLEX_ONE, COMPLEX_ZERO}; + +inline SeptMatrix RZ7(fp phi, size_t leva, size_t levb) { + if (leva > levb or leva > 6 or levb > 6) { + throw std::invalid_argument("LEV A cannot be higher than LEV B"); + } + SeptMatrix identity = I7; + identity.at(7 * leva + leva) = + dd::ComplexValue{std::cos(phi / 2), -std::sin(phi / 2)}; + identity.at(7 * levb + levb) = + dd::ComplexValue{std::cos(phi / 2), +std::sin(phi / 2)}; + return identity; +} +inline SeptMatrix VirtRZ7(fp phi, size_t i) { + SeptMatrix identity = I7; + identity.at(i + i * 7) = dd::ComplexValue{std::cos(phi), -std::sin(phi)}; + return identity; +} + +inline SeptMatrix Z7() { + SeptMatrix id = I7; + for (int level = 0; level < 7; ++level) { + const double angle = fmod(2.0 * level / 7, 2.0) * PI; + id.at(level + level * 7) = + dd::ComplexValue{std::cos(angle), std::sin(angle)}; + } + + return id; +} +inline SeptMatrix S7() { + SeptMatrix id = I7; + for (int level = 0; level < 7; ++level) { + const double omegaArg = fmod(2.0 / 7 * level * (level + 1) / 2.0, 2.0); + const auto omega = + dd::ComplexValue{std::cos(omegaArg * PI), std::sin(omegaArg * PI)}; + id.at(level + level * 7) = omega; + } + return id; +} +inline SeptMatrix embX7(fp phi, size_t leva, size_t levb) { + if (leva > levb or leva > 6 or levb > 6) { + throw std::invalid_argument("LEV A cannot be higher than LEV B"); + } + SeptMatrix identity = I7; + identity.at(7 * leva + leva) = COMPLEX_ZERO; + identity.at(7 * leva + levb) = + dd::ComplexValue{std::sin(phi), -std::cos(phi)}; + identity.at(7 * levb + leva) = + dd::ComplexValue{-std::sin(phi), -std::cos(phi)}; + identity.at(7 * levb + levb) = COMPLEX_ZERO; + return identity; +} + +// NOLINTEND(readability-identifier-naming) +} // namespace dd +#endif // DD_PACKAGE_GATEMATRIXDEFINITIONS_H diff --git a/include/dd/MDDPackage.hpp b/include/dd/MDDPackage.hpp new file mode 100644 index 0000000..12a81bd --- /dev/null +++ b/include/dd/MDDPackage.hpp @@ -0,0 +1,2042 @@ +/* + * This file is part of the MQT DD Package which is released under the MIT + * license. See file README.md or go to + * https://www.cda.cit.tum.de/research/quantum_dd/ for more information. + */ +#ifndef DDMDDPackage_H +#define DDMDDPackage_H + +#include "Complex.hpp" +#include "ComplexNumbers.hpp" +#include "ComplexTable.hpp" +#include "ComplexValue.hpp" +#include "ComputeTable.hpp" +#include "Control.hpp" +#include "Definitions.hpp" +#include "Edge.hpp" +#include "GateMatrixDefinitions.hpp" +#include "UnaryComputeTable.hpp" +#include "UniqueTable.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace dd { +class MDDPackage { + /// + /// Complex number handling + /// +public: + ComplexNumbers complexNumber{}; + + /// + /// Construction, destruction, information and reset + /// +public: + static constexpr std::size_t MAX_POSSIBLE_REGISTERS = + static_cast>( + std::numeric_limits::max()) + + 1U; + static constexpr std::size_t DEFAULT_REGISTERS = 128; + + explicit MDDPackage(std::size_t nqr, std::vector sizes) + : numberOfQuantumRegisters(nqr), registersSizes(std::move(sizes)) { + resize(nqr); + }; + + ~MDDPackage() = default; + + MDDPackage(const MDDPackage& MDDPackage) = delete; // no copy constructor + MDDPackage& operator=(const MDDPackage& MDDPackage) = + delete; // no copy assignment constructor + + // TODO RESIZE + // resize the package instance + void resize(std::size_t nq) { + // TODO DISCUSS THIS FEATURE + if (nq > MAX_POSSIBLE_REGISTERS) { + throw std::invalid_argument( + "Requested too many qubits from package. Qubit datatype only " + "allows up to " + + std::to_string(MAX_POSSIBLE_REGISTERS) + " qubits, while " + + std::to_string(nq) + + " were requested. Please recompile the package with a wider " + "Qubit type!"); + } + numberOfQuantumRegisters = nq; + vUniqueTable.resize(numberOfQuantumRegisters); + mUniqueTable.resize(numberOfQuantumRegisters); + // dUniqueTable.resize(number_of_quantum_registers); + // stochasticNoiseOperationCache.resize(number_of_quantum_registers); + idTable.resize(numberOfQuantumRegisters); + } + + // reset package state + void reset() { + // TODO IMPLEMENT + // clearUniqueTables(); + // clearComputeTables(); + complexNumber.clear(); + } + + // TODO CHECK SYNTAX OF SETTERS AND GETTERS + // getter for number qudits + + [[nodiscard]] auto qregisters() const { return numberOfQuantumRegisters; } + + // setter dimensionalisties + [[nodiscard]] auto registerDimensions(const std::vector& regs) { + registersSizes = regs; + } + + // getter for sizes + [[nodiscard]] auto regsSize() const { return registersSizes; } + +public: + std::size_t numberOfQuantumRegisters; + // TODO THIS IS NOT CONST RIGHT? + // from LSB TO MSB + std::vector registersSizes; + + /// + /// Vector nodes, edges and quantum states + /// +public: + // NOLINTNEXTLINE(readability-identifier-naming) + struct vNode { + std::vector> edges{}; // edges out of this node + vNode* next{}; // used to link nodes in unique table + RefCount refCount{}; // reference count, how many active dd are using + // the node + QuantumRegister + varIndx{}; // variable index (nonterminal) value (-1 for terminal), + // index in the circuit endianness 0 from below + + static vNode + terminalNode; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) + constexpr static vNode* terminal{ + &terminalNode}; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables,readability-identifier-naming) + + static constexpr bool isTerminal(const vNode* nodePoint) { + return nodePoint == terminal; + } + }; + using vEdge = Edge; + using vCachedEdge = CachedEdge; + + vEdge normalize(const vEdge& edge, bool cached) { + std::vector zero; + // find indices that are not zero + std::vector nonZeroIndices; + std::size_t counter = 0UL; + for (auto const& i : edge.nextNode->edges) { + if (i.weight.approximatelyZero()) { + zero.push_back(true); + } else { + zero.push_back(false); + nonZeroIndices.push_back(counter); + } + counter++; + } + + // make sure to release cached numbers approximately zero, but not exactly + // zero + if (cached) { + for (auto i = 0UL; i < zero.size(); i++) { + if (zero.at(i) && edge.nextNode->edges.at(i).weight != Complex::zero) { + complexNumber.returnToCache(edge.nextNode->edges.at(i).weight); + edge.nextNode->edges.at(i) = vEdge::zero; + } + } + } + + // all equal to zero + if (none_of(cbegin(zero), cend(zero), std::logical_not<>())) { + if (!cached && !edge.isTerminal()) { + // If it is not a cached computation, the node has to be put back into + // the chain + vUniqueTable.returnNode(edge.nextNode); + } + return vEdge::zero; + } + + if (nonZeroIndices.size() == 1) { + // search for first element different from zero + auto currentEdge = edge; + auto& weightFromChild = + currentEdge.nextNode->edges.at(nonZeroIndices.front()).weight; + + if (cached && weightFromChild != Complex::one) { + currentEdge.weight = weightFromChild; + } else { + currentEdge.weight = complexNumber.lookup(weightFromChild); + } + + weightFromChild = Complex::one; + return currentEdge; + } + + // calculate normalizing factor + auto sumNorm2 = ComplexNumbers::mag2(edge.nextNode->edges.at(0).weight); + auto mag2Max = ComplexNumbers::mag2(edge.nextNode->edges.at(0).weight); + auto argMax = 0UL; + + // TODO FIX BECAUSE AT THIS STAGE IT TRIES ALWAYS TO GET THE FIRST EDGE AND + // I WANT THE FIRST BEH BASED ON PREVIOUS CODE + for (auto i = 1UL; i < edge.nextNode->edges.size(); i++) { + sumNorm2 = + sumNorm2 + ComplexNumbers::mag2(edge.nextNode->edges.at(i).weight); + } + for (auto i = 1UL; i <= edge.nextNode->edges.size(); i++) { + auto counterBack = edge.nextNode->edges.size() - i; + if (ComplexNumbers::mag2(edge.nextNode->edges.at(counterBack).weight) + + ComplexTable<>::tolerance() >= + mag2Max) { + mag2Max = + ComplexNumbers::mag2(edge.nextNode->edges.at(counterBack).weight); + argMax = counterBack; + } + } + + const auto norm = std::sqrt(sumNorm2); + const auto magMax = std::sqrt(mag2Max); + const auto commonFactor = norm / magMax; + + // set incoming edge weight to max + auto currentEdge = edge; + auto& max = currentEdge.nextNode->edges.at(argMax); + + if (cached && max.weight != Complex::one) { + // if(cached && !currentEdge.weight.approximatelyOne()){ + currentEdge.weight = max.weight; + currentEdge.weight.real->value *= commonFactor; + currentEdge.weight.img->value *= commonFactor; + } else { + auto realPart = CTEntry::val(currentEdge.weight.real) * commonFactor; + auto imgPart = CTEntry::val(currentEdge.weight.img) * commonFactor; + currentEdge.weight = complexNumber.lookup(realPart, imgPart); + if (currentEdge.weight.approximatelyZero()) { + return vEdge::zero; + } + } + + max.weight = complexNumber.lookup(magMax / norm, 0.); + if (max.weight == Complex::zero) { + max = vEdge::zero; + } + + // actual normalization of the edges + // TODO CHECK IF CHANGE MADE IN THE CACHED IF IS CORRECT + for (auto i = 0UL; i < edge.nextNode->edges.size(); ++i) { + if (i != argMax) { + auto& iEdge = edge.nextNode->edges.at(i); + + if (cached && + iEdge.weight != Complex::zero) { // TODO CHECK EXACTLY HERE + complexNumber.returnToCache(iEdge.weight); + ComplexNumbers::div(iEdge.weight, iEdge.weight, currentEdge.weight); + iEdge.weight = complexNumber.lookup(iEdge.weight); + } else { + auto c = complexNumber.getTemporary(); + ComplexNumbers::div(c, iEdge.weight, currentEdge.weight); + iEdge.weight = complexNumber.lookup(c); + } + if (iEdge.weight == Complex::zero) { + iEdge = vEdge::zero; + } + } + } + + return currentEdge; + } + + // generate |0...0> with N quantum registers + vEdge makeZeroState(QuantumRegisterCount n, std::size_t start = 0) { + if (n + start > numberOfQuantumRegisters) { + // TODO UNDERSTAND RESIZING + throw std::runtime_error("Requested state with " + + std::to_string(n + start) + + " QUANTUM REGISTERS, but current package " + "configuration only supports up to " + + std::to_string(numberOfQuantumRegisters) + + " QUANTUM REGISTERS. Please allocate a " + "larger package instance."); + } + auto first = vEdge::one; + for (std::size_t nodeIdx = start; nodeIdx < n + start; nodeIdx++) { + std::vector> newOutgoingEdges; + newOutgoingEdges.reserve(registersSizes.at(nodeIdx)); + newOutgoingEdges.push_back(first); + for (auto i = 1U; i < registersSizes.at(nodeIdx); i++) { + newOutgoingEdges.push_back(vEdge::zero); + } + + first = + makeDDNode(static_cast(nodeIdx), newOutgoingEdges); + } + return first; + } + + // generate computational basis state |i> with n quantum registers + vEdge makeBasisState(QuantumRegisterCount n, const std::vector& state, + std::size_t start = 0) { + if (n + start > numberOfQuantumRegisters) { + throw std::runtime_error( + "Requested state with " + std::to_string(n + start) + + " qubits, but current package configuration only supports up " + "to " + + std::to_string(numberOfQuantumRegisters) + + " qubits. Please allocate a larger package instance."); + } + auto f = vEdge::one; + for (std::size_t pos = start; pos < n + start; ++pos) { + std::vector edges(registersSizes.at(pos), vEdge::zero); + edges.at(state.at(pos)) = f; + f = makeDDNode(static_cast(pos), edges); + } + return f; + } + + // create a normalized DD node and return an edge pointing to it. The + // node is not recreated if it already exists. + template + Edge makeDDNode(QuantumRegister varidx, + const std::vector>& edges, + bool cached = false) { + auto& uniqueTable = getUniqueTable(); + + Edge newEdge{uniqueTable.getNode(), Complex::one}; + newEdge.nextNode->varIndx = varidx; + newEdge.nextNode->edges = edges; + + assert(newEdge.nextNode->refCount == 0); + + for ([[maybe_unused]] const auto& edge : edges) { + assert(edge.nextNode->varIndx == varidx - 1 || edge.isTerminal()); + } + + // normalize it + newEdge = normalize(newEdge, cached); + assert(newEdge.nextNode->varIndx == varidx || newEdge.isTerminal()); + + // look it up in the unique tables + auto lookedUpEdge = uniqueTable.lookup(newEdge, false); + assert(lookedUpEdge.nextNode->varIndx == varidx || + lookedUpEdge.isTerminal()); + + // set specific node properties for matrices + if constexpr (std::is_same_v) { + if (lookedUpEdge.nextNode == newEdge.nextNode) { + checkSpecialMatrices(lookedUpEdge.nextNode); + } + } + + return lookedUpEdge; + } + +public: + // NOLINTNEXTLINE(readability-identifier-naming) + struct mNode { + std::vector> edges{}; // edges out of this node + mNode* next{}; // used to link nodes in unique table + RefCount refCount{}; // reference count + QuantumRegister varIndx{}; // variable index (nonterminal) value (-1 + // for terminal) + bool symmetric = false; // node is symmetric + bool identity = false; // node resembles identity + + static mNode + terminalNode; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) + constexpr static mNode* terminal{ + &terminalNode}; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables,readability-identifier-naming) + + static constexpr bool isTerminal(const mNode* nodePoint) { + return nodePoint == terminal; + } + }; + using mEdge = Edge; + using mCachedEdge = CachedEdge; + + mEdge normalize(const mEdge& edge, bool cached) { + auto argmax = -1; + + std::vector zero; + zero.reserve(edge.nextNode->edges.size()); + for (auto& i : edge.nextNode->edges) { + zero.emplace_back(i.weight.approximatelyZero()); + } + + // make sure to release cached numbers approximately zero, but not + // exactly zero + if (cached) { + for (auto i = 0U; i < zero.size(); i++) { + if (zero.at(i) && edge.nextNode->edges.at(i).weight != Complex::zero) { + // TODO what is returnToCache + + complexNumber.returnToCache(edge.nextNode->edges.at(i).weight); + edge.nextNode->edges.at(i) = mEdge::zero; + } + } + } + + fp maxMagnitude = 0; + auto maxWeight = Complex::one; + // determine max amplitude + for (auto i = 0U; i < zero.size(); ++i) { + if (zero.at(i)) { + continue; + } + if (argmax == -1) { + argmax = static_cast(i); + maxMagnitude = ComplexNumbers::mag2(edge.nextNode->edges.at(i).weight); + maxWeight = edge.nextNode->edges.at(i).weight; + } else { + auto currentMagnitude = + ComplexNumbers::mag2(edge.nextNode->edges.at(i).weight); + if (currentMagnitude - maxMagnitude > ComplexTable<>::tolerance()) { + argmax = static_cast(i); + maxMagnitude = currentMagnitude; + maxWeight = edge.nextNode->edges.at(i).weight; + } + } + } + + // all equal to zero + if (argmax == -1) { + if (!cached && !edge.isTerminal()) { + // If it is not a cached computation, the node has to be put + // back into the chain + mUniqueTable.returnNode(edge.nextNode); + } + return mEdge::zero; + } + + auto currentEdge = edge; + // divide each entry by max + for (auto i = 0U; i < edge.nextNode->edges.size(); ++i) { + if (static_cast(i) == argmax) { + if (cached) { + if (currentEdge.weight == Complex::one) { + currentEdge.weight = maxWeight; + } else { + ComplexNumbers::mul(currentEdge.weight, currentEdge.weight, + maxWeight); + } + } else { + if (currentEdge.weight == Complex::one) { + currentEdge.weight = maxWeight; + } else { + auto newComplexNumb = complexNumber.getTemporary(); + ComplexNumbers::mul(newComplexNumb, currentEdge.weight, maxWeight); + currentEdge.weight = complexNumber.lookup(newComplexNumb); + } + } + currentEdge.nextNode->edges.at(i).weight = Complex::one; + } else { + if (cached && !zero.at(i) && + currentEdge.nextNode->edges.at(i).weight != Complex::one) { + complexNumber.returnToCache(currentEdge.nextNode->edges.at(i).weight); + } + if (currentEdge.nextNode->edges.at(i).weight.approximatelyOne()) { + currentEdge.nextNode->edges.at(i).weight = Complex::one; + } + auto newComplexNumb = complexNumber.getTemporary(); + + ComplexNumbers::div(newComplexNumb, + currentEdge.nextNode->edges.at(i).weight, + maxWeight); + currentEdge.nextNode->edges.at(i).weight = + complexNumber.lookup(newComplexNumb); + } + } + return currentEdge; + } + + /// Make GATE DD + // SIZE => EDGE (number of successors) + // build matrix representation for a single gate on an n-qubit circuit + + template + mEdge makeGateDD(const Matrix& mat, QuantumRegisterCount n, + QuantumRegister target, std::size_t start = 0) { + return makeGateDD(mat, n, Controls{}, target, start); + } + + template + mEdge makeGateDD(const Matrix& mat, QuantumRegisterCount n, + const Control& control, QuantumRegister target, + std::size_t start = 0) { + return makeGateDD(mat, n, Controls{control}, target, start); + } + + template + mEdge makeGateDD(const Matrix& mat, QuantumRegisterCount n, + const Controls& controls, QuantumRegister target, + std::size_t start = 0) { + if (n + start > numberOfQuantumRegisters) { + throw std::runtime_error( + "Requested gate with " + std::to_string(n + start) + + " qubits, but current package configuration only supports up " + "to " + + std::to_string(numberOfQuantumRegisters) + + " qubits. Please allocate a larger package instance."); + } + + auto targetRadix = registersSizes.at(static_cast(target)); + auto edges = targetRadix * targetRadix; + std::vector edgesMat(edges, mEdge::zero); + + auto currentControl = controls.begin(); + + for (auto i = 0U; i < edges; ++i) { + if (mat.at(i).r != 0 || mat.at(i).i != 0) { + edgesMat.at(i) = mEdge::terminal(complexNumber.lookup(mat.at(i))); + } + } + auto currentReg = static_cast(start); + // process lines below target + for (; currentReg < target; currentReg++) { + auto radix = registersSizes.at(static_cast(currentReg)); + for (auto rowMat = 0U; rowMat < targetRadix; ++rowMat) { + for (auto colMat = 0U; colMat < targetRadix; ++colMat) { + auto entryPos = (rowMat * targetRadix) + colMat; + + std::vector quadEdges(radix * radix, mEdge::zero); + + if (currentControl != controls.end() && + currentControl->quantumRegister == currentReg) { + if (rowMat == colMat) { + for (auto i = 0U; i < radix; i++) { + auto diagInd = i * radix + i; + if (i == currentControl->type) { + quadEdges.at(diagInd) = edgesMat.at(entryPos); + } else { + quadEdges.at(diagInd) = + makeIdent(static_cast(start), + static_cast(currentReg - 1)); + } + } + } else { + quadEdges.at(currentControl->type + + radix * currentControl->type) = + edgesMat.at(entryPos); + } + edgesMat.at(entryPos) = makeDDNode(currentReg, quadEdges); + + } else { // not connected + for (auto iD = 0U; iD < radix; iD++) { + quadEdges.at(iD * radix + iD) = edgesMat.at(entryPos); + } + edgesMat.at(entryPos) = makeDDNode(currentReg, quadEdges); + } + } + } + + if (currentControl != controls.end() && + currentControl->quantumRegister == currentReg) { + ++currentControl; + } + } + + // target line + auto targetNodeEdge = makeDDNode(currentReg, edgesMat); + + // process lines above target + for (; currentReg < static_cast(n - 1 + start); + currentReg++) { + auto nextReg = static_cast(currentReg + 1); + auto nextRadix = registersSizes.at(static_cast(nextReg)); + std::vector nextEdges(nextRadix * nextRadix, mEdge::zero); + + if (currentControl != controls.end() && + currentControl->quantumRegister == nextReg) { + for (auto i = 0U; i < nextRadix; i++) { + auto diagInd = i * nextRadix + i; + if (i == currentControl->type) { + nextEdges.at(diagInd) = targetNodeEdge; + } else { + nextEdges.at(diagInd) = + makeIdent(static_cast(start), + static_cast(nextReg - 1)); + } + } + + ++currentControl; + + } else { // not connected + for (auto iD = 0U; iD < nextRadix; iD++) { + nextEdges.at(iD * nextRadix + iD) = targetNodeEdge; + } + } + targetNodeEdge = makeDDNode(nextReg, nextEdges); + } + return targetNodeEdge; + } + + /// + /// Identity matrices + /// +public: + // create n-qudit identity DD. makeIdent(n) === makeIdent(0, n-1) + mEdge makeIdent(QuantumRegisterCount n) { + return makeIdent(0, static_cast(n - 1)); + } + + mEdge makeIdent(QuantumRegister leastSignificantQubit, + QuantumRegister mostSignificantQubit) { + if (mostSignificantQubit < leastSignificantQubit) { + return mEdge::one; + } + + if (leastSignificantQubit == 0 && + idTable.at(static_cast(mostSignificantQubit)).nextNode != + nullptr) { + return idTable.at(static_cast(mostSignificantQubit)); + } + + if (mostSignificantQubit >= 1 && + (idTable.at(static_cast(mostSignificantQubit) - 1)) + .nextNode != nullptr) { + auto basicDimMost = + registersSizes.at(static_cast(mostSignificantQubit)); + std::vector identityEdges{}; + + for (auto i = 0UL; i < basicDimMost; i++) { + for (auto j = 0UL; j < basicDimMost; j++) { + if (i == j) { + identityEdges.push_back( + idTable[static_cast(mostSignificantQubit) - 1]); + } else { + identityEdges.push_back(mEdge::zero); + } + } + } + idTable.at(static_cast(mostSignificantQubit)) = makeDDNode( + static_cast(mostSignificantQubit), identityEdges); + + return idTable.at(static_cast(mostSignificantQubit)); + } + + // create an Identity DD from scratch + auto basicDimLeast = + registersSizes.at(static_cast(leastSignificantQubit)); + std::vector identityEdgesLeast{}; + + for (auto i = 0UL; i < basicDimLeast; i++) { + for (auto j = 0UL; j < basicDimLeast; j++) { + if (i == j) { + identityEdgesLeast.push_back(mEdge::one); + } else { + identityEdgesLeast.push_back(mEdge::zero); + } + } + } + + auto e = makeDDNode(static_cast(leastSignificantQubit), + identityEdgesLeast); + + for (std::size_t intermediaryRegs = + static_cast(leastSignificantQubit) + 1; + intermediaryRegs <= static_cast(mostSignificantQubit); + intermediaryRegs++) { + auto basicDimInt = registersSizes.at(intermediaryRegs); + std::vector identityEdgesInt{}; + + for (auto i = 0UL; i < basicDimInt; i++) { + for (auto j = 0UL; j < basicDimInt; j++) { + if (i == j) { + identityEdgesInt.push_back(e); + } else { + identityEdgesInt.push_back(mEdge::zero); + } + } + } + e = makeDDNode(static_cast(intermediaryRegs), + identityEdgesInt); + } + + if (leastSignificantQubit == 0) { + idTable.at(static_cast(mostSignificantQubit)) = e; + } + return e; + } + + // identity table access and reset + [[nodiscard]] const auto& getIdentityTable() const { return idTable; } + + void clearIdentityTable() { + for (auto& entry : idTable) { + entry.nextNode = nullptr; + } + } + +private: + std::vector idTable{}; + +public: + /// + /// Addition + /// + ComputeTable vectorAdd{}; + ComputeTable matrixAdd{}; + + template + [[nodiscard]] ComputeTable, CachedEdge, + CachedEdge>& + getAddComputeTable(); + + template Edge add(const Edge& x, const Edge& y) { + [[maybe_unused]] const auto before = complexNumber.cacheCount(); + + auto result = add2(x, y); + + if (result.weight != Complex::zero) { + complexNumber.returnToCache(result.weight); + result.weight = complexNumber.lookup(result.weight); + } + + [[maybe_unused]] const auto after = complexNumber.complexCache.getCount(); + assert(after == before); + + return result; + } + + template + Edge add2(const Edge& x, const Edge& y) { + // no sum performed + if (x.nextNode == nullptr) { + return y; + } + if (y.nextNode == nullptr) { + return x; + } + + if (x.weight == Complex::zero) { + if (y.weight == Complex::zero) { + return y; + } + auto result = y; + result.weight = complexNumber.getCached(CTEntry::val(y.weight.real), + CTEntry::val(y.weight.img)); + return result; + } + if (y.weight == Complex::zero) { + auto result = x; + result.weight = complexNumber.getCached(CTEntry::val(x.weight.real), + CTEntry::val(x.weight.img)); + return result; + } + if (x.nextNode == y.nextNode) { + auto result = y; + result.weight = complexNumber.addCached(x.weight, y.weight); + if (result.weight.approximatelyZero()) { + complexNumber.returnToCache(result.weight); + return Edge::zero; + } + return result; + } + + auto& computeTable = getAddComputeTable(); + auto result = + computeTable.lookup({x.nextNode, x.weight}, {y.nextNode, y.weight}); + if (result.nextNode != nullptr) { + if (result.weight.approximatelyZero()) { + return Edge::zero; + } + return {result.nextNode, complexNumber.getCached(result.weight)}; + } + + QuantumRegister newSuccessor = 0; + + if (x.isTerminal()) { + newSuccessor = y.nextNode->varIndx; + } else { + newSuccessor = x.nextNode->varIndx; + if (!y.isTerminal() && y.nextNode->varIndx > newSuccessor) { + newSuccessor = y.nextNode->varIndx; + } + } + + // constexpr std::size_t N = std::tuple_size_ve)>; + // TODO CHECK HERE IF MAKES SENSE + std::vector> edgeSum(x.nextNode->edges.size(), + dd::Edge::zero); + + for (auto i = 0U; i < x.nextNode->edges.size(); i++) { + Edge e1{}; + + if (!x.isTerminal() && x.nextNode->varIndx == newSuccessor) { + e1 = x.nextNode->edges.at(i); + + if (e1.weight != Complex::zero) { + e1.weight = complexNumber.mulCached(e1.weight, x.weight); + } + } else { + e1 = x; + if (y.nextNode->edges.at(i).nextNode == nullptr) { + e1 = {nullptr, Complex::zero}; + } + } + + Edge e2{}; + if (!y.isTerminal() && y.nextNode->varIndx == newSuccessor) { + e2 = y.nextNode->edges.at(i); + + if (e2.weight != Complex::zero) { + e2.weight = complexNumber.mulCached(e2.weight, y.weight); + } + } else { + e2 = y; + if (x.nextNode->edges.at(i).nextNode == nullptr) { + e2 = {nullptr, Complex::zero}; + } + } + + edgeSum.at(i) = add2(e1, e2); + + if (!x.isTerminal() && x.nextNode->varIndx == newSuccessor && + e1.weight != Complex::zero) { + complexNumber.returnToCache(e1.weight); + } + + if (!y.isTerminal() && y.nextNode->varIndx == newSuccessor && + e2.weight != Complex::zero) { + complexNumber.returnToCache(e2.weight); + } + } + + auto e = makeDDNode(newSuccessor, edgeSum, true); + computeTable.insert({x.nextNode, x.weight}, {y.nextNode, y.weight}, + {e.nextNode, e.weight}); + return e; + } + /// + /// Multiplication + /// +public: + ComputeTable matrixVectorMultiplication{}; + ComputeTable matrixMatrixMultiplication{}; + + template + [[nodiscard]] ComputeTable, Edge, + CachedEdge>& + getMultiplicationComputeTable(); + + template + RightOperand multiply(const LeftOperand& x, const RightOperand& y, + dd::QuantumRegister start = 0) { + [[maybe_unused]] const auto before = complexNumber.cacheCount(); + + QuantumRegister var = -1; + + if (!x.isTerminal()) { + var = x.nextNode->varIndx; + } + if (!y.isTerminal() && (y.nextNode->varIndx) > var) { + var = y.nextNode->varIndx; + } + + RightOperand e = multiply2(x, y, var, start); + + if (e.weight != Complex::zero && e.weight != Complex::one) { + complexNumber.returnToCache(e.weight); + e.weight = complexNumber.lookup(e.weight); + } + + [[maybe_unused]] const auto after = complexNumber.cacheCount(); + assert(before == after); + + return e; + } + +private: + template + Edge + multiply2(const Edge& x, const Edge& y, + QuantumRegister var, QuantumRegister start = 0) { + using LEdge = Edge; + using REdge = Edge; + using ResultEdge = Edge; + + if (x.nextNode == nullptr) { + return {nullptr, Complex::zero}; + } + if (y.nextNode == nullptr) { + return y; + } + + if (x.weight == Complex::zero || y.weight == Complex::zero) { + return ResultEdge::zero; + } + + if (var == start - 1) { + return ResultEdge::terminal(complexNumber.mulCached(x.weight, y.weight)); + } + + auto xCopy = x; + xCopy.weight = Complex::one; + auto yCopy = y; + yCopy.weight = Complex::one; + + auto& computeTable = + getMultiplicationComputeTable(); + auto lookupResult = computeTable.lookup(xCopy, yCopy); + + if (lookupResult.nextNode != nullptr) { + if (lookupResult.weight.approximatelyZero()) { + return ResultEdge::zero; + } + + auto resEdgeInit = ResultEdge{ + lookupResult.nextNode, complexNumber.getCached(lookupResult.weight)}; + + ComplexNumbers::mul(resEdgeInit.weight, resEdgeInit.weight, x.weight); + ComplexNumbers::mul(resEdgeInit.weight, resEdgeInit.weight, y.weight); + + if (resEdgeInit.weight.approximatelyZero()) { + complexNumber.returnToCache(resEdgeInit.weight); + return ResultEdge::zero; + } + return resEdgeInit; + } + + ResultEdge resultEdge{}; + + if (x.nextNode->varIndx == var && + x.nextNode->varIndx == y.nextNode->varIndx) { + if (x.nextNode->identity) { + if constexpr (std::is_same_v) { + // additionally check if y is the identity in case of matrix + // multiplication + if (y.nextNode->identity) { + resultEdge = makeIdent(start, var); + } else { + resultEdge = yCopy; + } + } else { + resultEdge = yCopy; + } + + computeTable.insert(xCopy, yCopy, + {resultEdge.nextNode, resultEdge.weight}); + resultEdge.weight = complexNumber.mulCached(x.weight, y.weight); + + if (resultEdge.weight.approximatelyZero()) { + complexNumber.returnToCache(resultEdge.weight); + return ResultEdge::zero; + } + return resultEdge; + } + + if constexpr (std::is_same_v) { + // additionally check if y is the identity in case of matrix + // multiplication + if (y.nextNode->identity) { + resultEdge = xCopy; + computeTable.insert(xCopy, yCopy, + {resultEdge.nextNode, resultEdge.weight}); + resultEdge.weight = complexNumber.mulCached(x.weight, y.weight); + + if (resultEdge.weight.approximatelyZero()) { + complexNumber.returnToCache(resultEdge.weight); + return ResultEdge::zero; + } + return resultEdge; + } + } + } + + // TODO CHECK AGAIN THIS COULD BE WRONG + const std::size_t rows = + x.isTerminal() + ? 1U + : registersSizes.at(static_cast(x.nextNode->varIndx)); + const std::size_t cols = + (std::is_same_v) + ? y.isTerminal() ? 1U + : registersSizes.at(static_cast( + y.nextNode->varIndx)) + : 1U; + const std::size_t multiplicationBoundary = + x.isTerminal() + ? (y.isTerminal() ? 1U + : registersSizes.at(static_cast( + y.nextNode->varIndx))) + : registersSizes.at(static_cast(x.nextNode->varIndx)); + + std::vector edge(multiplicationBoundary * cols, + ResultEdge::zero); + + for (auto i = 0U; i < rows; i++) { + for (auto j = 0U; j < cols; j++) { + auto idx = cols * i + j; + // edge.at(idx) = ResultEdge::zero; + + for (auto k = 0U; k < multiplicationBoundary; k++) { + LEdge e1{}; + if (!x.isTerminal() && x.nextNode->varIndx == var) { + e1 = x.nextNode->edges.at(rows * i + k); + } else { + e1 = xCopy; + } + + REdge e2{}; + if (!y.isTerminal() && y.nextNode->varIndx == var) { + e2 = y.nextNode->edges.at(j + cols * k); + } else { + e2 = yCopy; + } + + auto multipliedRecurRes = + multiply2(e1, e2, static_cast(var - 1), start); + + if (k == 0 || edge.at(idx).weight == Complex::zero) { + edge.at(idx) = multipliedRecurRes; + } else if (multipliedRecurRes.weight != Complex::zero) { + auto oldEdge = edge.at(idx); + edge.at(idx) = add2(edge.at(idx), multipliedRecurRes); + complexNumber.returnToCache(oldEdge.weight); + complexNumber.returnToCache(multipliedRecurRes.weight); + } + } + } + } + resultEdge = makeDDNode(var, edge, true); + + computeTable.insert(xCopy, yCopy, {resultEdge.nextNode, resultEdge.weight}); + + if (resultEdge.weight != Complex::zero && + (x.weight != Complex::one || y.weight != Complex::one)) { + if (resultEdge.weight == Complex::one) { + resultEdge.weight = complexNumber.mulCached(x.weight, y.weight); + } else { + ComplexNumbers::mul(resultEdge.weight, resultEdge.weight, x.weight); + ComplexNumbers::mul(resultEdge.weight, resultEdge.weight, y.weight); + } + if (resultEdge.weight.approximatelyZero()) { + complexNumber.returnToCache(resultEdge.weight); + return ResultEdge::zero; + } + } + return resultEdge; + } + /// + /// Inner product, fidelity, expectation value + /// +public: + ComputeTable vectorInnerProduct{}; + + ComplexValue innerProduct(const vEdge& x, const vEdge& y) { + if (x.nextNode == nullptr || y.nextNode == nullptr || + x.weight.approximatelyZero() || + y.weight.approximatelyZero()) { // the 0 case + return {0, 0}; + } + + [[maybe_unused]] const auto before = complexNumber.cacheCount(); + + auto circWidth = x.nextNode->varIndx; + if (y.nextNode->varIndx > circWidth) { + circWidth = y.nextNode->varIndx; + } + const ComplexValue ip = + innerProduct(x, y, static_cast(circWidth + 1)); + + [[maybe_unused]] const auto after = complexNumber.cacheCount(); + assert(after == before); + + return ip; + } + + fp fidelity(const vEdge& x, const vEdge& y) { + const auto fid = innerProduct(x, y); + return fid.r * fid.r + fid.i * fid.i; + } + +private: + ComplexValue innerProduct(const vEdge& x, const vEdge& y, + QuantumRegister var) { + if (x.nextNode == nullptr || y.nextNode == nullptr || + x.weight.approximatelyZero() || + y.weight.approximatelyZero()) { // the 0 case + return {0.0, 0.0}; + } + + if (var == 0) { + auto c = complexNumber.getTemporary(); + ComplexNumbers::mul(c, x.weight, y.weight); + return {c.real->value, c.img->value}; + } + + auto xCopy = x; + xCopy.weight = Complex::one; + auto yCopy = y; + yCopy.weight = Complex::one; + + auto nodeLookup = vectorInnerProduct.lookup(xCopy, yCopy); + if (nodeLookup.nextNode != nullptr) { + auto c = complexNumber.getTemporary(nodeLookup.weight); + ComplexNumbers::mul(c, c, x.weight); + ComplexNumbers::mul(c, c, y.weight); + return {CTEntry::val(c.real), CTEntry::val(c.img)}; + } + + auto width = static_cast(var - 1); + + ComplexValue sum{0.0, 0.0}; + for (auto i = 0U; i < registersSizes.at(static_cast(width)); + i++) { + vEdge e1{}; + if (!x.isTerminal() && x.nextNode->varIndx == width) { + e1 = x.nextNode->edges.at(i); + } else { + e1 = xCopy; + } + vEdge e2{}; + if (!y.isTerminal() && y.nextNode->varIndx == width) { + e2 = y.nextNode->edges.at(i); + e2.weight = ComplexNumbers::conj(e2.weight); + } else { + e2 = yCopy; + } + auto cv = innerProduct(e1, e2, width); + sum.r += cv.r; + sum.i += cv.i; + } + nodeLookup.nextNode = vNode::terminal; + nodeLookup.weight = sum; + + vectorInnerProduct.insert(xCopy, yCopy, nodeLookup); + auto c = complexNumber.getTemporary(sum); + ComplexNumbers::mul(c, c, x.weight); + ComplexNumbers::mul(c, c, y.weight); + return {CTEntry::val(c.real), CTEntry::val(c.img)}; + } + + /// + /// Kronecker/tensor product + /// +public: + ComputeTable vectorKronecker{}; + ComputeTable matrixKronecker{}; + + template + [[nodiscard]] ComputeTable, Edge, CachedEdge, 4096>& + getKroneckerComputeTable(); + + template + Edge kronecker(const Edge& x, const Edge& y, bool incIdx = true) { + auto e = kronecker2(x, y, incIdx); + + if (e.weight != Complex::zero && e.weight != Complex::one) { + complexNumber.returnToCache(e.weight); + e.weight = complexNumber.lookup(e.weight); + } + + return e; + } + + // extent the DD pointed to by `e` with `h` identities on top and `l` + // identities at the bottom + mEdge extend(const mEdge& e, QuantumRegister h, QuantumRegister l = 0) { + auto f = (l > 0) + ? kronecker(e, makeIdent(static_cast(l))) + : e; + auto g = (h > 0) + ? kronecker(makeIdent(static_cast(h)), f) + : f; + return g; + } + +private: + template + Edge kronecker2(const Edge& x, const Edge& y, + bool incIdx = true) { + if (x.weight.approximatelyZero() || y.weight.approximatelyZero()) { + return Edge::zero; + } + + if (x.isTerminal()) { + auto r = y; + r.weight = complexNumber.mulCached(x.weight, y.weight); + return r; + } + + auto& computeTable = getKroneckerComputeTable(); + auto r = computeTable.lookup(x, y); + if (r.nextNode != nullptr) { + if (r.weight.approximatelyZero()) { + return Edge::zero; + } + return {r.nextNode, complexNumber.getCached(r.weight)}; + } + + if (x.nextNode->identity) { + std::vector> newEdges(x.nextNode->edges.size(), + dd::Edge::zero); + + for (auto i = 0U; + i < registersSizes.at(static_cast(x.nextNode->varIndx)); + i++) { + newEdges.at(i + i * (registersSizes.at(static_cast( + x.nextNode->varIndx)))) = y; + } + auto idx = incIdx ? static_cast(y.nextNode->varIndx + 1) + : y.nextNode->varIndx; + + auto e = makeDDNode(idx, newEdges); + + for (auto i = 0; i < x.nextNode->varIndx; ++i) { + std::vector> eSucc(e.nextNode->edges.size(), + dd::Edge::zero); + for (auto j = 0U; + j < + registersSizes.at(static_cast(e.nextNode->varIndx)); + j++) { + eSucc.at(j + j * (registersSizes.at(static_cast( + e.nextNode->varIndx)))) = e; + } + + idx = incIdx ? static_cast(e.nextNode->varIndx + 1) + : e.nextNode->varIndx; + + e = makeDDNode(idx, eSucc); + } + + e.weight = complexNumber.getCached(CTEntry::val(y.weight.real), + CTEntry::val(y.weight.img)); + computeTable.insert(x, y, {e.nextNode, e.weight}); + return e; + } + + std::vector> edge(x.nextNode->edges.size(), + dd::Edge::zero); + for (auto i = 0U; i < x.nextNode->edges.size(); ++i) { + edge.at(i) = kronecker2(x.nextNode->edges.at(i), y, incIdx); + } + + auto idx = incIdx ? static_cast(y.nextNode->varIndx + + x.nextNode->varIndx + 1) + : x.nextNode->varIndx; + auto e = makeDDNode(idx, edge, true); + ComplexNumbers::mul(e.weight, e.weight, x.weight); + computeTable.insert(x, y, {e.nextNode, e.weight}); + return e; + } + +public: + mEdge cex(QuantumRegisterCount numberRegs, dd::Control::Type level, fp phi, + size_t leva, size_t levb, QuantumRegister cReg, + QuantumRegister target, bool isDagger = false) { + const dd::Control control{cReg, level}; + + if (registersSizes.at(static_cast(target)) == 2) { + const auto matrix = dd::embX2(phi); + auto gate = + makeGateDD(matrix, numberRegs, control, target); + if (isDagger) { + gate = conjugateTranspose(gate); + } + return gate; + } + if (registersSizes.at(static_cast(target)) == 3) { + const auto matrix = dd::embX3(phi, leva, levb); + auto gate = + makeGateDD(matrix, numberRegs, control, target); + if (isDagger) { + gate = conjugateTranspose(gate); + } + return gate; + } + if (registersSizes.at(static_cast(target)) == 4) { + const auto matrix = dd::embX4(phi, leva, levb); + auto gate = + makeGateDD(matrix, numberRegs, control, target); + if (isDagger) { + gate = conjugateTranspose(gate); + } + return gate; + } + if (registersSizes.at(static_cast(target)) == 5) { + const auto matrix = dd::embX5(phi, leva, levb); + auto gate = + makeGateDD(matrix, numberRegs, control, target); + if (isDagger) { + gate = conjugateTranspose(gate); + } + return gate; + } + if (registersSizes.at(static_cast(target)) == 6) { + const auto matrix = dd::embX6(phi, leva, levb); + auto gate = + makeGateDD(matrix, numberRegs, control, target); + if (isDagger) { + gate = conjugateTranspose(gate); + } + return gate; + } + if (registersSizes.at(static_cast(target)) == 7) { + const auto matrix = dd::embX7(phi, leva, levb); + auto gate = + makeGateDD(matrix, numberRegs, control, target); + if (isDagger) { + gate = conjugateTranspose(gate); + } + return gate; + } + throw std::invalid_argument("Dimensions of target not implemented"); + } + + mEdge csum(QuantumRegisterCount numberRegs, QuantumRegister cReg, + QuantumRegister target, bool isDagger = false) { + auto res = makeIdent(numberRegs); + if (registersSizes.at(static_cast(target)) == 2) { + for (auto i = 0U; i < registersSizes.at(static_cast(cReg)); + i++) { + const dd::Control control{cReg, static_cast(i)}; + const auto matrix = dd::Xmat; + auto gate = + makeGateDD(matrix, numberRegs, control, target); + for (auto counter = 0U; counter < i; counter++) { + res = multiply(res, gate); + } + } + if (isDagger) { + res = conjugateTranspose(res); + } + return res; + } + if (registersSizes.at(static_cast(target)) == 3) { + for (auto i = 0U; i < registersSizes.at(static_cast(cReg)); + i++) { + const dd::Control control{cReg, static_cast(i)}; + const auto matrix = dd::X3; + auto gate = + makeGateDD(matrix, numberRegs, control, target); + for (auto counter = 0U; counter < i; counter++) { + res = multiply(res, gate); + } + } + if (isDagger) { + res = conjugateTranspose(res); + } + return res; + } + if (registersSizes.at(static_cast(target)) == 4) { + for (auto i = 0U; i < registersSizes.at(static_cast(cReg)); + i++) { + const dd::Control control{cReg, static_cast(i)}; + const auto matrix = dd::X4; + auto gate = + makeGateDD(matrix, numberRegs, control, target); + for (auto counter = 0U; counter < i; counter++) { + res = multiply(res, gate); + } + } + if (isDagger) { + res = conjugateTranspose(res); + } + return res; + } + if (registersSizes.at(static_cast(target)) == 5) { + for (auto i = 0U; i < registersSizes.at(static_cast(cReg)); + i++) { + const dd::Control control{cReg, static_cast(i)}; + const auto matrix = dd::X5; + auto gate = + makeGateDD(matrix, numberRegs, control, target); + for (auto counter = 0U; counter < i; counter++) { + res = multiply(res, gate); + } + } + if (isDagger) { + res = conjugateTranspose(res); + } + return res; + } + if (registersSizes.at(static_cast(target)) == 6) { + for (auto i = 0U; i < registersSizes.at(static_cast(cReg)); + i++) { + const dd::Control control{cReg, static_cast(i)}; + const auto matrix = dd::X6; + auto gate = + makeGateDD(matrix, numberRegs, control, target); + for (auto counter = 0U; counter < i; counter++) { + res = multiply(res, gate); + } + } + if (isDagger) { + res = conjugateTranspose(res); + } + return res; + } + if (registersSizes.at(static_cast(target)) == 7) { + for (auto i = 0U; i < registersSizes.at(static_cast(cReg)); + i++) { + const dd::Control control{cReg, static_cast(i)}; + const auto matrix = dd::X7; + auto gate = + makeGateDD(matrix, numberRegs, control, target); + for (auto counter = 0U; counter < i; counter++) { + res = multiply(res, gate); + } + } + if (isDagger) { + res = conjugateTranspose(res); + } + return res; + } + throw std::invalid_argument("Dimensions of target not implemented"); + } + + vEdge spread2(QuantumRegisterCount n, + const std::vector& lines, vEdge& state) { + dd::Controls const control01{{lines.at(0), 1}}; + auto cH = makeGateDD(dd::Hmat, n, control01, lines.at(1)); + + dd::Controls const control10{{lines.at(1), 0}}; + mEdge minus = mEdge::zero; + mEdge xp10 = mEdge::zero; + + if (registersSizes.at(static_cast(lines.at(0))) == 2) { + minus = makeGateDD(dd::Xmat, n, lines.at(0)); + xp10 = makeGateDD(dd::Xmat, n, control10, lines.at(0)); + } + + if (registersSizes.at(static_cast(lines.at(0))) == 3) { + minus = makeGateDD(dd::X3dag, n, lines.at(0)); + xp10 = makeGateDD(dd::X3, n, control10, lines.at(0)); + } + + else if (registersSizes.at(static_cast(lines.at(0))) == 5) { + minus = makeGateDD(dd::X5dag, n, lines.at(0)); + xp10 = makeGateDD(dd::X5, n, control10, lines.at(0)); + } + + state = multiply(cH, state); + state = multiply(minus, state); + state = multiply(xp10, state); + + return state; + } + vEdge spread3(QuantumRegisterCount n, std::vector lines, + vEdge& state) { + dd::Controls const control01{{lines.at(0), 1}}; + auto cH = makeGateDD(dd::H3(), n, control01, lines.at(1)); + + dd::Controls const control10{{lines.at(1), 0}}; + mEdge minus = mEdge::zero; + mEdge xp10 = mEdge::zero; + + if (registersSizes.at(static_cast(lines.at(0))) == 2) { + minus = makeGateDD(dd::Xmat, n, lines.at(0)); + xp10 = makeGateDD(dd::Xmat, n, control10, lines.at(0)); + } + + if (registersSizes.at(static_cast(lines.at(0))) == 3) { + minus = makeGateDD(dd::X3dag, n, lines.at(0)); + xp10 = makeGateDD(dd::X3, n, control10, lines.at(0)); + } + + else if (registersSizes.at(static_cast(lines.at(0))) == 5) { + minus = makeGateDD(dd::X5dag, n, lines.at(0)); + xp10 = makeGateDD(dd::X5, n, control10, lines.at(0)); + } + + dd::Controls const control12{{lines.at(1), 2}}; + auto xp12 = makeGateDD(dd::X3, n, control12, lines.at(2)); + auto csum21 = csum(n, lines.at(2), lines.at(1), true); + + state = multiply(cH, state); + state = multiply(minus, state); + state = multiply(xp10, state); + state = multiply(xp12, state); + state = multiply(csum21, state); + state = multiply(csum21, state); + + return state; + } + vEdge spread5(QuantumRegisterCount n, std::vector lines, + vEdge& state) { + dd::Controls const control01{{lines.at(0), 1}}; + auto cH = makeGateDD(dd::H5(), n, control01, lines.at(1)); + + dd::Controls const control10{{lines.at(1), 0}}; + mEdge minus = mEdge::zero; + mEdge xp10 = mEdge::zero; + + if (registersSizes.at(static_cast(lines.at(0))) == 2) { + minus = makeGateDD(dd::Xmat, n, lines.at(0)); + xp10 = makeGateDD(dd::Xmat, n, control10, lines.at(0)); + } + + if (registersSizes.at(static_cast(lines.at(0))) == 3) { + minus = makeGateDD(dd::X3dag, n, lines.at(0)); + xp10 = makeGateDD(dd::X3, n, control10, lines.at(0)); + } + + else if (registersSizes.at(static_cast(lines.at(0))) == 5) { + minus = makeGateDD(dd::X5dag, n, lines.at(0)); + xp10 = makeGateDD(dd::X5, n, control10, lines.at(0)); + } + + dd::Controls const control12{{lines.at(1), 2}}; + auto xp12 = makeGateDD(dd::X5, n, control12, lines.at(2)); + dd::Controls const control13{{lines.at(1), 3}}; + auto xp13 = makeGateDD(dd::X5, n, control13, lines.at(3)); + dd::Controls const control14{{lines.at(1), 4}}; + auto xp14 = makeGateDD(dd::X5, n, control14, lines.at(4)); + + auto csum21 = csum(n, lines.at(2), lines.at(1), true); + auto csum31 = csum(n, lines.at(3), lines.at(1), true); + auto csum41 = csum(n, lines.at(4), lines.at(1), true); + + state = multiply(cH, state); + state = multiply(minus, state); + + state = multiply(xp10, state); + + state = multiply(xp12, state); + + state = multiply(csum21, state); + state = multiply(csum21, state); + + state = multiply(xp13, state); + + state = multiply(csum31, state); + state = multiply(csum31, state); + state = multiply(csum31, state); + + state = multiply(xp14, state); + + state = multiply(csum41, state); + state = multiply(csum41, state); + state = multiply(csum41, state); + state = multiply(csum41, state); + + return state; + } + +public: + template + unsigned int nodeCount(const Edge& e, + std::unordered_set& v) const { + v.insert(e.nextNode); + unsigned int sum = 1; + if (!e.isTerminal()) { + for (const auto& edge : e.nextNode->edges) { + if (edge.nextNode != nullptr && !v.count(edge.nextNode)) { + sum += nodeCount(edge, v); + } + } + } + return sum; + } + + /// + /// Vector and matrix extraction from DDs + /// +public: + /// Get a single element of the vector or matrix represented by the dd + /// with root edge e \tparam Edge type of edge to use (vector or matrix) + /// \param e edge to traverse + /// \param path_elements string {0, 1, 2, 3}^n describing which outgoing + /// edge should be followed + /// (for vectors entries are limited to 0 and 1) + /// If string is longer than required, the additional characters + /// are ignored. + /// \return the complex amplitude of the specified element + + template + ComplexValue getValueByPath(const Edge& edge, + const std::string& pathElements) { + if (edge.isTerminal()) { + return {CTEntry::val(edge.weight.real), CTEntry::val(edge.weight.img)}; + } + + auto tempCompNumb = complexNumber.getTemporary(1, 0); + auto currentEdge = edge; + // NOLINTNEXTLINE(cppcoreguidelines-avoid-do-while) + do { + ComplexNumbers::mul(tempCompNumb, tempCompNumb, currentEdge.weight); + const auto tmp = + static_cast(pathElements.at(static_cast( + currentEdge.nextNode->varIndx)) - + '0'); + assert(tmp <= currentEdge.nextNode->edges.size()); + currentEdge = currentEdge.nextNode->edges.at(tmp); + } while (!currentEdge.isTerminal()); + + ComplexNumbers::mul(tempCompNumb, tempCompNumb, currentEdge.weight); + + return {CTEntry::val(tempCompNumb.real), CTEntry::val(tempCompNumb.img)}; + } + + ComplexValue getValueByPath(const vEdge& edge, + std::vector& reprI) { + if (edge.isTerminal()) { + return {CTEntry::val(edge.weight.real), CTEntry::val(edge.weight.img)}; + } + return getValueByPath(edge, Complex::one, reprI); + } + + ComplexValue getValueByPath(const vEdge& edge, const Complex& amp, + std::vector& repr) { + auto cNumb = complexNumber.mulCached(edge.weight, amp); + + if (edge.isTerminal()) { + complexNumber.returnToCache(cNumb); + return {CTEntry::val(cNumb.real), CTEntry::val(cNumb.img)}; + } + + ComplexValue returnAmp{}; + + if (!edge.nextNode->edges.at(repr.front()).weight.approximatelyZero()) { + std::vector reprSlice(repr.begin() + 1, repr.end()); + returnAmp = getValueByPath(edge.nextNode->edges.at(repr.front()), cNumb, + reprSlice); + } + + complexNumber.returnToCache(cNumb); + return returnAmp; + } + + ComplexValue getValueByPath(const mEdge& edge, + std::vector& reprI, + std::vector& reprJ) { + if (edge.isTerminal()) { + return {CTEntry::val(edge.weight.real), CTEntry::val(edge.weight.img)}; + } + return getValueByPath(edge, Complex::one, reprI, reprJ); + } + + ComplexValue getValueByPath(const mEdge& edge, const Complex& amp, + std::vector& reprI, + std::vector& reprJ) { + // row major encoding + + auto cNumb = complexNumber.mulCached(edge.weight, amp); + + if (edge.isTerminal()) { + complexNumber.returnToCache(cNumb); + return {CTEntry::val(cNumb.real), CTEntry::val(cNumb.img)}; + } + + const auto row = reprI.front(); + const auto col = reprJ.front(); + const auto rowMajorIndex = row * edge.nextNode->edges.size() + col; + ComplexValue returnAmp{}; + + if (!edge.nextNode->edges.at(rowMajorIndex).weight.approximatelyZero()) { + std::vector reprSliceI(reprI.begin() + 1, reprI.end()); + std::vector reprSliceJ(reprJ.begin() + 1, reprJ.end()); + returnAmp = getValueByPath(edge.nextNode->edges.at(rowMajorIndex), cNumb, + reprSliceI, reprSliceJ); + } + complexNumber.returnToCache(cNumb); + return returnAmp; + } + + CVec getVector(const vEdge& edge) { + const auto dim = static_cast(std::accumulate( + registersSizes.begin(), registersSizes.end(), 1, std::multiplies<>())); + // allocate resulting vector + auto vec = CVec(dim, {0.0, 0.0}); + getVector(edge, Complex::one, 0, vec, dim); + return vec; + } + + CVec getVectorizedMatrix(const mEdge& edge) { + std::size_t dim = 1U; + + for (const auto registersSize : registersSizes) { + dim = dim * registersSize * registersSize; + } + // allocate resulting vector + auto vec = CVec(dim, {0.0, 0.0}); + getVector(edge, Complex::one, 0, vec, dim); + return vec; + } + + template + void getVector(const Edge& edge, const Complex& amp, std::size_t i, + CVec& vec, std::size_t next, + std::vector pathTracker = {}) { + // calculate new accumulated amplitude + auto cNumb = complexNumber.mulCached(edge.weight, amp); + + // base case + if (edge.isTerminal()) { + if (std::is_same::value) { + for (const auto j : pathTracker) { + std::cout << j; + } + std::cout << ": "; + std::cout << cNumb << std::endl; + } + vec.at(i) = {CTEntry::val(cNumb.real), CTEntry::val(cNumb.img)}; + complexNumber.returnToCache(cNumb); + return; + } + + auto offset = (next - i) / edge.nextNode->edges.size(); + + for (auto k = 0UL; k < edge.nextNode->edges.size(); k++) { + if (std::is_same::value) { + pathTracker.push_back(k); + getVector(edge.nextNode->edges.at(k), cNumb, i + (k * offset), vec, + i + ((k + 1) * offset), pathTracker); + pathTracker.pop_back(); + } else { + if (!edge.nextNode->edges.at(k).weight.approximatelyZero()) { + getVector(edge.nextNode->edges.at(k), cNumb, i + (k * offset), vec, + i + ((k + 1) * offset)); + } + } + } + + complexNumber.returnToCache(cNumb); + } + + std::vector getReprOfIndex(const std::size_t i, + const std::size_t numEntries) { + std::vector repr; + repr.reserve(numberOfQuantumRegisters); + // get representation + auto iIndex = i; + auto pathWay = 0UL; + auto cardinality = numEntries; + + auto counter = 0UL; + auto index = 0UL; + + while (counter < numberOfQuantumRegisters) { + try { + index = numberOfQuantumRegisters - counter - 1; + cardinality = cardinality / registersSizes.at(index); + pathWay = iIndex / cardinality; + iIndex = iIndex % cardinality; + } catch (const std::exception& e) { + std::cout << "index = " << index << ", cardinality = " << cardinality + << " ,counter = " << counter << std::endl; + } + + repr.push_back(pathWay); + counter = counter + 1; + } + + return repr; + } + + void printVector(const vEdge& edge, bool nonZero = false) { + // unsigned long long numEntries = static_cast(std::accumulate(registersSizes.begin(), registersSizes.end(), + // 1,std::multiplies<>())); + + std::size_t numEntries = 1ULL; + for (const auto registersSize : registersSizes) { + numEntries = numEntries * registersSize; + } + + for (auto i = 0ULL; i < numEntries; i++) { + auto reprI = getReprOfIndex(i, numEntries); + // get amplitude + const auto amplitude = getValueByPath(edge, reprI); + + if (!amplitude.approximatelyZero() || !nonZero) { + for (const auto coeff : reprI) { + std::cout << coeff; + } + reprI.clear(); + + constexpr auto precision = 3; + // set fixed width to maximum of a printed number + // (-) 0.precision plus/minus 0.precision i + constexpr auto width = 1 + 2 + precision + 1 + 2 + precision + 1; + std::cout << ": " << std::setw(width) + << ComplexValue::toString(amplitude.r, amplitude.i, false, + precision) + << "\n"; + } + } + std::cout << std::flush; + } + + static void printComplexVector(const CVec& vector) { + for (auto i = 0ULL; i < vector.size(); i++) { + std::cout << i; + + constexpr auto precision = 3; + // set fixed width to maximum of a printed number + // (-) 0.precision plus/minus 0.precision i + constexpr auto width = 1 + 2 + precision + 1 + 2 + precision + 1; + std::cout << ": " << std::setw(width) << vector.at(i) << "\n"; + } + std::cout << std::flush; + } + +private: + // check whether node represents a symmetric matrix or the identity + void checkSpecialMatrices(mNode* node) { + if (node->varIndx == -1) { + return; + } + + node->identity = false; // assume not identity + node->symmetric = false; // assume symmetric + + // check if matrix is symmetric + auto basicDim = registersSizes.at(static_cast(node->varIndx)); + + for (auto i = 0UL; i < basicDim; i++) { + if (!node->edges.at(i * basicDim + i).nextNode->symmetric) { + return; + } + } + // TODO WHY RETURN IF DIAGONAL IS SYMMETRIC?? + // if (!node->edges.at(0).nextNode->symmetric || + // !node->edges.at(3).nextNode->symmetric) return; + + for (auto i = 0UL; i < basicDim; i++) { + for (auto j = 0UL; j < basicDim; j++) { + if (i != j) { + // row major indexing - enable optimization here + if (transpose(node->edges.at(i * basicDim + j)) != + node->edges.at(j * basicDim + i)) { + return; + } + } + } + } + // if (transpose(node->edges.at(1)) != node->edges.at(2)) return; + + node->symmetric = true; + + // check if matrix resembles identity + for (auto i = 0UL; i < basicDim; i++) { + for (auto j = 0UL; j < basicDim; j++) { + // row major indexing - enable optimization here + if (i == j) { + if (!(node->edges[i * basicDim + j].nextNode->identity) || + (node->edges[i * basicDim + j].weight) != Complex::one) { + return; + } + } else { + if ((node->edges[i * basicDim + j].weight) != Complex::zero) { + return; + } + } + } + } + /* + if (!(p->e[0].p->ident) || (p->e[1].w) != Complex::zero || + (p->e[2].w) != Complex::zero || (p->e[0].w) != Complex::one || + (p->e[3].w) != Complex::one || !(p->e[3].p->ident)) + return; + */ + node->identity = true; + } + + /// + /// Matrix (conjugate) transpose + /// +public: + // todo figure out the parameters here + UnaryComputeTable matrixTranspose{}; + UnaryComputeTable conjugateMatrixTranspose{}; + + mEdge transpose(const mEdge& edge) { + if (edge.nextNode == nullptr || edge.isTerminal() || + edge.nextNode->symmetric) { + return edge; + } + + // check in compute table + auto result = matrixTranspose.lookup(edge); + if (result.nextNode != nullptr) { + return result; + } + + std::vector newEdge{}; + auto basicDim = + registersSizes.at(static_cast(edge.nextNode->varIndx)); + + // transpose sub-matrices and rearrange as required + for (auto i = 0U; i < basicDim; i++) { + for (auto j = 0U; j < basicDim; j++) { + newEdge.at(basicDim * i + j) = + transpose(edge.nextNode->edges.at(basicDim * j + i)); + } + } + // create new top node + result = makeDDNode(edge.nextNode->varIndx, newEdge); + // adjust top weight + auto c = complexNumber.getTemporary(); + ComplexNumbers::mul(c, result.weight, edge.weight); + result.weight = complexNumber.lookup(c); + + // put in compute table + matrixTranspose.insert(edge, result); + return result; + } + mEdge conjugateTranspose(const mEdge& edge) { + if (edge.nextNode == nullptr) { + return edge; + } + if (edge.isTerminal()) { // terminal case + auto result = edge; + result.weight = ComplexNumbers::conj(edge.weight); + return result; + } + + // check if in compute table + auto result = conjugateMatrixTranspose.lookup(edge); + if (result.nextNode != nullptr) { + return result; + } + + std::vector newEdge(edge.nextNode->edges.size(), + dd::Edge::zero); + auto basicDim = + registersSizes.at(static_cast(edge.nextNode->varIndx)); + + // conjugate transpose submatrices and rearrange as required + for (auto i = 0U; i < basicDim; ++i) { + for (auto j = 0U; j < basicDim; ++j) { + newEdge.at(basicDim * i + j) = + conjugateTranspose(edge.nextNode->edges.at(basicDim * j + i)); + } + } + // create new top node + result = makeDDNode(edge.nextNode->varIndx, newEdge); + + auto c = complexNumber.getTemporary(); + // adjust top weight including conjugate + ComplexNumbers::mul(c, result.weight, ComplexNumbers::conj(edge.weight)); + result.weight = complexNumber.lookup(c); + + // put it in the compute table + conjugateMatrixTranspose.insert(edge, result); + return result; + } + + /// + /// Unique tables, Reference counting and garbage collection + /// +public: + // unique tables + template [[nodiscard]] UniqueTable& getUniqueTable(); + + template void incRef(const Edge& e) { + getUniqueTable().incRef(e); + } + + template void decRef(const Edge& e) { + getUniqueTable().decRef(e); + } + + UniqueTable vUniqueTable{numberOfQuantumRegisters}; + UniqueTable mUniqueTable{numberOfQuantumRegisters}; +}; + +inline void clearUniqueTables() { + // TODO IMPLEMENT + // vUniqueTable.clear(); + // mUniqueTable.clear(); +} +// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) +inline MDDPackage::vNode MDDPackage::vNode::terminalNode{ + {{{nullptr, Complex::zero}, {nullptr, Complex::zero}}}, nullptr, 0, -1}; + +// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) +inline MDDPackage::mNode MDDPackage::mNode::terminalNode{ + {{{nullptr, Complex::zero}, + {nullptr, Complex::zero}, + {nullptr, Complex::zero}, + {nullptr, Complex::zero}}}, + nullptr, + 0, + -1, + true, + true}; + +template <> +[[nodiscard]] inline UniqueTable& +MDDPackage::getUniqueTable() { + return vUniqueTable; +} + +template <> +[[nodiscard]] inline UniqueTable& +MDDPackage::getUniqueTable() { + return mUniqueTable; +} + +template <> +[[nodiscard]] inline ComputeTable< + MDDPackage::vCachedEdge, MDDPackage::vCachedEdge, MDDPackage::vCachedEdge>& +MDDPackage::getAddComputeTable() { + return vectorAdd; +} + +template <> +[[nodiscard]] inline ComputeTable< + MDDPackage::mCachedEdge, MDDPackage::mCachedEdge, MDDPackage::mCachedEdge>& +MDDPackage::getAddComputeTable() { + return matrixAdd; +} + +template <> +[[nodiscard]] inline ComputeTable& +MDDPackage::getMultiplicationComputeTable() { + return matrixVectorMultiplication; +} + +template <> +[[nodiscard]] inline ComputeTable& +MDDPackage::getMultiplicationComputeTable() { + return matrixMatrixMultiplication; +} + +template <> +[[nodiscard]] inline ComputeTable& +MDDPackage::getKroneckerComputeTable() { + return vectorKronecker; +} + +template <> +[[nodiscard]] inline ComputeTable& +MDDPackage::getKroneckerComputeTable() { + return matrixKronecker; +} + +} // namespace dd + +#endif diff --git a/include/dd/UnaryComputeTable.hpp b/include/dd/UnaryComputeTable.hpp new file mode 100644 index 0000000..fb85484 --- /dev/null +++ b/include/dd/UnaryComputeTable.hpp @@ -0,0 +1,91 @@ +/* + * This file is part of the MQT DD Package which is released under the MIT + * license. See file README.md or go to + * https://www.cda.cit.tum.de/research/quantum_dd/ for more information. + */ + +#ifndef DDpackage_UNARYCOMPUTETABLE_HPP +#define DDpackage_UNARYCOMPUTETABLE_HPP + +#include "Definitions.hpp" + +#include +#include +#include +#include + +namespace dd { + +/// Data structure for caching computed results of unary operations +/// \tparam OperandType type of the operation's operand +/// \tparam ResultType type of the operation's result +/// \tparam NBUCKET number of hash buckets to use (has to be a power of two) +template +class UnaryComputeTable { +public: + UnaryComputeTable() = default; + + struct Entry { + OperandType operand; + ResultType result; + }; + + static constexpr size_t MASK = NBUCKET - 1; + + // access functions + [[nodiscard]] const auto& getTable() const { return table; } + + static std::size_t hash(const OperandType& a) { + return std::hash{}(a)&MASK; + } + + void insert(const OperandType& operand, const ResultType& result) { + const auto key = hash(operand); + table[key] = {operand, result}; + ++count; + } + + ResultType lookup(const OperandType& operand) { + ResultType result{}; + lookups++; + const auto key = hash(operand); + auto& entry = table[key]; + if (entry.result.nextNode == nullptr) { + return result; + } + if (entry.operand != operand) { + return result; + } + + hits++; + return entry.result; + } + + void clear() { + if (count > 0) { + for (auto& entry : table) { + entry.result.nextNode = nullptr; + } + count = 0; + } + hits = 0; + lookups = 0; + } + + [[nodiscard]] fp hitRatio() const { return static_cast(hits) / lookups; } + std::ostream& printStatistics(std::ostream& os = std::cout) { + os << "hits: " << hits << ", looks: " << lookups + << ", ratio: " << hitRatio() << std::endl; + return os; + } + +private: + std::array table{}; + // compute table lookup statistics + std::size_t hits = 0; + std::size_t lookups = 0; + std::size_t count = 0; +}; +} // namespace dd + +#endif // DDpackage_UNARYCOMPUTETABLE_HPP diff --git a/include/dd/UniqueTable.hpp b/include/dd/UniqueTable.hpp new file mode 100644 index 0000000..aa9ca8c --- /dev/null +++ b/include/dd/UniqueTable.hpp @@ -0,0 +1,418 @@ +/* + * This file is part of the MQT DD Package which is released under the MIT + * license. See file README.md or go to + * https://www.cda.cit.tum.de/research/quantum_dd/ for more information. + */ + +#ifndef DDpackage_UNIQUETABLE_HPP +#define DDpackage_UNIQUETABLE_HPP + +#include "ComplexNumbers.hpp" +#include "Definitions.hpp" +#include "Edge.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace dd { + +/// Data structure for providing and uniquely storing DD nodes +/// \tparam Node class of nodes to provide/store +/// \tparam NBUCKET number of hash buckets to use (has to be a power of two) +/// \tparam INITIAL_ALLOCATION_SIZE number if nodes initially allocated +/// \tparam GROWTH_PERCENTAGE percentage that the allocations' size shall grow +/// over time \tparam INITIAL_GC_LIMIT number of nodes initially used as garbage +/// collection threshold \tparam GC_INCREMENT absolute number of nodes to +/// increase the garbage collection threshold after garbage collection has been +/// performed +template +class UniqueTable { +public: + explicit UniqueTable(std::size_t nvars) : nvars(nvars) {} + + ~UniqueTable() = default; + + static constexpr std::size_t MASK = NBUCKET - 1; + + void resize(std::size_t nq) { + nvars = nq; + tables.resize(nq); + // TODO: if the new size is smaller than the old one we might have to + // release the unique table entries for the superfluous variables + active.resize(nq); + activeNodeCount = std::accumulate(active.begin(), active.end(), 0UL); + } + + static std::size_t hash(const Node* p) { + std::size_t key = 0; + for (std::size_t i = 0; i < p->edges.size(); ++i) { + key = dd::combineHash(key, std::hash>{}(p->edges.at(i))); + // old hash function: + // key += ((reinterpret_cast(p->e[i].p) >> i) + + // (reinterpret_cast(p->e[i].w.r) >> i) + + // (reinterpret_cast(p->e[i].w.i) >> (i + + // 1))) & MASK; + } + key &= MASK; + return key; + } + + // access functions + [[nodiscard]] std::size_t getNodeCount() const { return nodeCount; } + + [[nodiscard]] std::size_t getPeakNodeCount() const { return peakNodeCount; } + + [[nodiscard]] std::size_t getMaxActiveNodes() const { return maxActive; } + + [[nodiscard]] std::size_t getAllocations() const { return allocations; } + + [[nodiscard]] float getGrowthFactor() const { return GROWTH_FACTOR; } + + [[nodiscard]] const auto& getTables() const { return tables; } + + // lookup a node in the unique table for the appropriate variable; insert it, + // if it has not been found NOTE: reference counting is to be adjusted by + // function invoking the table lookup and only normalized nodes shall be + // stored. + Edge lookup(const Edge& e, bool keepNode = false) { + // there are unique terminal nodes + if (e.isTerminal()) { + return e; + } + + lookups++; + const auto key = static_cast(hash(e.nextNode)); + const auto v = e.nextNode->varIndx; + + // successors of a node shall either have successive variable numbers + // or be terminals + for ([[maybe_unused]] const auto& edge : e.nextNode->edges) { + assert(edge.nextNode->varIndx == v - 1 || edge.isTerminal()); + } + + Node* p = tables[static_cast(v)][key]; + while (p != nullptr) { + if (e.nextNode->edges == p->edges) { + // Match found + if (e.nextNode != p && !keepNode) { + // put node pointed to by e.p on available chain + returnNode(e.nextNode); + } + hits++; + + // variables should stay the same + assert(p->varIndx == e.nextNode->varIndx); + + // successors of a node shall either have successive variable + // numbers or be terminals + for ([[maybe_unused]] const auto& edge : e.nextNode->edges) { + assert(edge.nextNode->varIndx == v - 1 || edge.isTerminal()); + } + + return {p, e.weight}; + } + collisions++; + p = p->next; + } + + // node was not found -> add it to front of unique table bucket + e.nextNode->next = tables[static_cast(v)][key]; + tables[static_cast(v)][key] = e.nextNode; + nodeCount++; + peakNodeCount = std::max(peakNodeCount, nodeCount); + + return e; + } + + [[nodiscard]] Node* getNode() { + // a node is available on the stack + if (available != nullptr) { + Node* p = available; + available = p->next; + // returned nodes could have a ref count != 0 + p->refCount = 0; + return p; + } + + // new chunk has to be allocated + if (chunkIt == chunkEndIt) { + chunks.emplace_back(allocationSize); + allocations += allocationSize; + allocationSize *= GROWTH_FACTOR; + chunkID++; + chunkIt = chunks[chunkID].begin(); + chunkEndIt = chunks[chunkID].end(); + } + + auto p = &(*chunkIt); + ++chunkIt; + return p; + } + + void returnNode(Node* p) { + p->next = available; + available = p; + } + + // increment reference counter for node e points to + // and recursively increment reference counter for + // each child if this is the first reference + void incRef(const Edge& e) { + dd::ComplexNumbers::incRef(e.w); + if (e.p == nullptr || e.isTerminal()) { + return; + } + + if (e.p->ref == std::numeric_limitsref)>::max()) { + std::clog << "[WARN] MAXREFCNT reached for p=" + << reinterpret_cast(e.p) + << ". Node will never be collected." << std::endl; + return; + } + + e.p->ref++; + + if (e.p->ref == 1) { + for (const auto& edge : e.p->e) { + if (edge.p != nullptr) { + incRef(edge); + } + } + active[e.p->v]++; + activeNodeCount++; + maxActive = std::max(maxActive, activeNodeCount); + } + } + + // decrement reference counter for node e points to + // and recursively decrement reference counter for + // each child if this is the last reference + void decRef(const Edge& e) { + dd::ComplexNumbers::decRef(e.w); + if (e.p == nullptr || e.isTerminal()) { + return; + } + if (e.p->ref == std::numeric_limitsref)>::max()) { + return; + } + + if (e.p->ref == 0) { + throw std::runtime_error("In decref: ref==0 before decref\n"); + } + + e.p->ref--; + + if (e.p->ref == 0) { + for (const auto& edge : e.p->e) { + if (edge.p != nullptr) { + decRef(edge); + } + } + active[e.p->v]--; + activeNodeCount--; + } + } + + [[nodiscard]] bool possiblyNeedsCollection() const { + return nodeCount >= gcLimit; + } + + std::size_t garbageCollect(bool force = false) { + gcCalls++; + if ((!force && nodeCount < gcLimit) || nodeCount == 0) { + return 0; + } + + gcRuns++; + std::size_t collected = 0; + std::size_t remaining = 0; + for (auto& table : tables) { + for (auto& bucket : table) { + Node* p = bucket; + Node* lastp = nullptr; + while (p != nullptr) { + if (p->ref == 0) { + assert(!Node::isTerminal(p)); + Node* next = p->next; + if (lastp == nullptr) { + bucket = next; + } else { + lastp->next = next; + } + returnNode(p); + p = next; + collected++; + } else { + lastp = p; + p = p->next; + remaining++; + } + } + } + } + // The garbage collection limit changes dynamically depending on the number + // of remaining (active) nodes. If it were not changed, garbage collection + // would run through the complete table on each successive call once the + // number of remaining entries reaches the garbage collection limit. It is + // increased whenever the number of remaining entries is rather close to the + // garbage collection threshold and decreased if the number of remaining + // entries is much lower than the current limit. + if (remaining > gcLimit / 10 * 9) { + gcLimit = remaining + INITIAL_GC_LIMIT; + } + // else if (remaining < gcLimit / 32) { + // gcLimit /= 4; + // } + nodeCount = remaining; + return collected; + } + + void clear() { + // clear unique table buckets + for (auto& table : tables) { + for (auto& bucket : table) { + bucket = nullptr; + } + } + // clear available stack + available = nullptr; + + // release memory of all but the first chunk TODO: it could be desirable to + // keep the memory + while (chunkID > 0) { + chunks.pop_back(); + chunkID--; + } + // restore initial chunk setting + chunkIt = chunks[0].begin(); + chunkEndIt = chunks[0].end(); + allocationSize = INITIAL_ALLOCATION_SIZE * GROWTH_FACTOR; + allocations = INITIAL_ALLOCATION_SIZE; + + for (auto& node : chunks[0]) { + node.ref = 0; + } + + nodeCount = 0; + peakNodeCount = 0; + + collisions = 0; + hits = 0; + lookups = 0; + + std::fill(active.begin(), active.end(), 0); + activeNodeCount = 0; + maxActive = 0; + + gcCalls = 0; + gcRuns = 0; + gcLimit = INITIAL_GC_LIMIT; + }; + + void print() { + QuantumRegister q = nvars - 1; + for (auto it = tables.rbegin(); it != tables.rend(); ++it) { + auto& table = *it; + std::cout << "\tq" << static_cast(q) << ":" << "\n"; + for (std::size_t key = 0; key < table.size(); ++key) { + auto p = table[key]; + if (p != nullptr) { + std::cout << "\tkey=" << key << ": "; + } + + while (p != nullptr) { + std::cout << "\t\t" << std::hex << reinterpret_cast(p) + << std::dec << " " << p->ref << std::hex; + for (const auto& e : p->e) { + std::cout << " p" << reinterpret_cast(e.p) << "(r" + << reinterpret_cast(e.w.r) << " i" + << reinterpret_cast(e.w.i) << ")"; + } + std::cout << std::dec << "\n"; + p = p->next; + } + } + --q; + } + } + + void printActive() { + std::cout << "#printActive: " << activeNodeCount << ", "; + for (const auto& a : active) { + std::cout << a << " "; + } + std::cout << "\n"; + } + + [[nodiscard]] fp hitRatio() const { return static_cast(hits) / lookups; } + + [[nodiscard]] fp colRatio() const { + return static_cast(collisions) / lookups; + } + + [[nodiscard]] std::size_t getActiveNodeCount() const { + return activeNodeCount; + } + + [[nodiscard]] std::size_t getActiveNodeCount(QuantumRegister var) { + return active.at(var); + } + + std::ostream& printStatistics(std::ostream& os = std::cout) { + os << "hits: " << hits << ", collisions: " << collisions + << ", looks: " << lookups << ", hitRatio: " << hitRatio() + << ", colRatio: " << colRatio() << ", gc calls: " << gcCalls + << ", gc runs: " << gcRuns << "\n"; + return os; + } + +private: + using NodeBucket = Node*; + using Table = std::array; + + // unique tables (one per input variable) + std::size_t nvars = 0; + std::vector tables{nvars}; + + Node* available{}; + std::vector> chunks{ + 1, std::vector{INITIAL_ALLOCATION_SIZE}}; + std::size_t chunkID{0}; + typename std::vector::iterator chunkIt{chunks[0].begin()}; + typename std::vector::iterator chunkEndIt{chunks[0].end()}; + std::size_t allocationSize{INITIAL_ALLOCATION_SIZE * GROWTH_FACTOR}; + + std::size_t allocations = INITIAL_ALLOCATION_SIZE; + std::size_t nodeCount = 0; + std::size_t peakNodeCount = 0; + + // unique table lookup statistics + std::size_t collisions = 0; + std::size_t hits = 0; + std::size_t lookups = 0; + + // (max) active nodes + // number of active vector nodes for each variable + std::vector active{std::vector(nvars, 0)}; + std::size_t activeNodeCount = 0; + std::size_t maxActive = 0; + + // garbage collection + std::size_t gcCalls = 0; + std::size_t gcRuns = 0; + std::size_t gcLimit = 250000; +}; + +} // namespace dd + +#endif // DDpackage_UNIQUETABLE_HPP diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..a89a257 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,65 @@ +set(MQT_QUDITS_TARGET_NAME "mqt-qudits") + +if(NOT TARGET project_warnings) + # Use the warnings specified in CompilerWarnings.cmake + add_library(project_warnings INTERFACE) + + # Standard compiler warnings + include(${PROJECT_SOURCE_DIR}/cmake/CompilerWarnings.cmake) + set_project_warnings(project_warnings) + + # Add MQT alias + add_library(MQT::ProjectWarnings ALIAS project_warnings) + set_target_properties(project_warnings PROPERTIES EXPORT_NAME ProjectWarnings) +endif() + +if(NOT TARGET project_options) + # Use the options specified in CompilerOptions.cmake + add_library(project_options INTERFACE) + + # Standard compiler options + include(${PROJECT_SOURCE_DIR}/cmake/CompilerOptions.cmake) + enable_project_options(project_options) + + # Sanitizer options if supported by compiler + include(${PROJECT_SOURCE_DIR}/cmake/Sanitizers.cmake) + enable_sanitizers(project_options) + + # Add MQT alias + add_library(MQT::ProjectOptions ALIAS project_options) + set_target_properties(project_options PROPERTIES EXPORT_NAME ProjectOptions) +endif() + +if(NOT TARGET ${MQT_QUDITS_TARGET_NAME}) + add_library( + ${MQT_QUDITS_TARGET_NAME} + INTERFACE + ${MQT_QUDITS_INCLUDE_BUILD_DIR}/dd/Complex.hpp + ${MQT_QUDITS_INCLUDE_BUILD_DIR}/dd/ComplexCache.hpp + ${MQT_QUDITS_INCLUDE_BUILD_DIR}/dd/ComplexNumbers.hpp + ${MQT_QUDITS_INCLUDE_BUILD_DIR}/dd/ComplexTable.hpp + ${MQT_QUDITS_INCLUDE_BUILD_DIR}/dd/ComplexValue.hpp + ${MQT_QUDITS_INCLUDE_BUILD_DIR}/dd/ComputeTable.hpp + ${MQT_QUDITS_INCLUDE_BUILD_DIR}/dd/Control.hpp + ${MQT_QUDITS_INCLUDE_BUILD_DIR}/dd/Definitions.hpp + ${MQT_QUDITS_INCLUDE_BUILD_DIR}/dd/Edge.hpp + ${MQT_QUDITS_INCLUDE_BUILD_DIR}/dd/GateMatrixDefinitions.hpp + ${MQT_QUDITS_INCLUDE_BUILD_DIR}/dd/MDDPackage.hpp + ${MQT_QUDITS_INCLUDE_BUILD_DIR}/dd/UnaryComputeTable.hpp + ${MQT_QUDITS_INCLUDE_BUILD_DIR}/dd/UniqueTable.hpp) + + # set include directories + target_include_directories(${MQT_QUDITS_TARGET_NAME} + INTERFACE $) + + # add options and warnings to the library + target_link_libraries(${MQT_QUDITS_TARGET_NAME} INTERFACE MQT::ProjectOptions + MQT::ProjectWarnings) + + # add MQT alias + add_library(MQT::Qudits ALIAS ${MQT_QUDITS_TARGET_NAME}) +endif() + +if(BUILD_MQT_QUDITS_BINDINGS) + add_subdirectory(python) +endif() diff --git a/src/python/CMakeLists.txt b/src/python/CMakeLists.txt new file mode 100644 index 0000000..01a12ff --- /dev/null +++ b/src/python/CMakeLists.txt @@ -0,0 +1,17 @@ +if(NOT TARGET _qudits) + pybind11_add_module( + _qudits + # Prefer thin LTO if available + THIN_LTO + # Optimize the bindings for size + OPT_SIZE + # Source code goes here + bindings.cpp) + target_link_libraries(_qudits PRIVATE MQT::Qudits MQT::ProjectOptions MQT::ProjectWarnings) + + # Install directive for scikit-build-core + install( + TARGETS _qudits + DESTINATION . + COMPONENT mqt-qudits_PythonModule) +endif() diff --git a/src/python/bindings.cpp b/src/python/bindings.cpp new file mode 100644 index 0000000..f18f076 --- /dev/null +++ b/src/python/bindings.cpp @@ -0,0 +1,769 @@ +#include "dd/MDDPackage.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace py = pybind11; +using namespace py::literals; + +using Instruction = std::tuple, std::string, + std::vector, py::object, + std::tuple, + std::vector>>; +using Circuit = std::vector; +using Circuit_info = std::tuple, Circuit>; + +using Noise = std::tuple; +using NoiseType = std::map>, Noise>; +using NoiseModel = std::map; +using CVec = std::vector>; + +// ======================================================================================================= +// ======================================================================================================= +// ======================================================================================================= +// ============@@@@@@@@@@@@@@@@@@@@ PRINTING FUNCTION +// @@@@@@@@@@@@@@@@@@@@================================ +// ======================================================================================================= +// ======================================================================================================= + +// Function to print NoiseType +void printNoiseType(const NoiseType& noiseType, int indent = 0) { + for (const auto& [key, noise] : noiseType) { + // Print the key + for (int i = 0; i < indent; ++i) + std::cout << " "; + if (std::holds_alternative(key)) + std::cout << std::get(key) << " -> "; + else { + std::cout << "["; + for (int val : std::get>(key)) + std::cout << val << " "; + std::cout << "] -> "; + } + + // Print the noise tuple + std::cout << "(" << std::get<0>(noise) << ", " << std::get<1>(noise) << ")" + << std::endl; + } +} + +// Function to print NoiseModel +void printNoiseModel(const NoiseModel& noiseModel) { + for (const auto& [key, noiseType] : noiseModel) { + std::cout << key << ":" << std::endl; + printNoiseType(noiseType, 1); + } +} + +void printCvec(CVec vector) { + for (const auto& cn : vector) { + std::cout << cn << std::endl; + } +} + +void printCircuit(const Circuit& circuit) { + for (const auto& instruction : circuit) { + auto [tag, dag, dims, gate_type, target_qudits, params, control_set] = + instruction; + std::cout << "Tag: " << tag << std::endl; + std::cout << "Dag: " << dag << std::endl; + std::cout << "Dimensions: "; + for (const auto& dim : dims) { + std::cout << dim << " "; + } + std::cout << std::endl; + std::cout << "Gate Type: " << gate_type << std::endl; + std::cout << "Target Qudits: "; + for (const auto& qubit : target_qudits) { + std::cout << qubit << " "; + } + std::cout << std::endl; + + // Printing control_set + auto [control1, control2] = control_set; + std::cout << "Control Set: "; + for (const auto& control : control1) { + std::cout << control << " "; + } + std::cout << "| "; + for (const auto& control : control2) { + std::cout << control << " "; + } + std::cout << std::endl; + } +} + +// ======================================================================================================= +// ======================================================================================================= +// ======================================================================================================= +// ============================================= HELPER FUNCTION +// ========================================= +// ======================================================================================================= +// ======================================================================================================= + +bool is_none_or_empty(const py::object& obj) { + if (obj.is_none()) + return true; + + if (py::isinstance(obj)) { + auto seq = obj.cast(); + return seq.size() == 0; + } + + // Add additional checks for other types if needed + + return false; +} + +bool checkDim(const std::vector& dims, + const std::variant>& target) { + if (std::holds_alternative(target)) { + // If target is a single integer + if (dims.size() != 1) { + return false; // Different sizes, not exactly equal + } + return dims[0] == std::get(target); + } else { + // If target is a vector + const auto& targetVec = std::get>(target); + if (dims.size() != targetVec.size()) { + return false; // Different sizes, not exactly equal + } + for (size_t i = 0; i < dims.size(); ++i) { + if (dims[i] != targetVec[i]) { + return false; // Different elements, not exactly equal + } + } + return true; // All elements are the same, exactly equal + } +} + +// Function to convert C++ vector of complex numbers to a Python list +py::list complex_vector_to_list(const CVec& vec) { + py::list pyList; + for (const auto& elem : vec) { + try { + // Convert std::complex to Python complex object + py::object pyComplex = py::cast(elem); + // Append Python complex object to the list + pyList.append(pyComplex); + } catch (const std::exception& e) { + // Handle any exceptions + std::cerr << "Error appending Python object: " << e.what() << std::endl; + } + } + return pyList; +} + +// ======================================================================================================= +// ======================================================================================================= +// ======================================================================================================= +// ===============::::::::::::::::::::::PARSING FUNCTIONS +// ::::::::::::::::::::::=========================== +// ======================================================================================================= +// ======================================================================================================= + +Circuit_info readCircuit(py::object& circ) { + Circuit result; + + unsigned int num_qudits = circ.attr("_num_qudits").cast(); + std::vector dimensions = + circ.attr("_dimensions").cast>(); + + bool result_empty = py::isinstance(circ.attr("instructions")); + + // Get Python iterable + py::iterator it = py::iter(circ.attr("instructions")); + + // Iterate over the Python iterable + while (it != py::iterator::sentinel()) { + py::handle obj_handle = *it; + py::object obj = py::reinterpret_borrow(obj_handle); + + std::string tag = obj.attr("qasm_tag").cast(); + + bool dagger = obj.attr("dagger").cast(); + + std::string gate_type = + py::cast(obj.attr("gate_type").attr("name")); + + // Extracting dimensions + py::object dims_obj = obj.attr("_dimensions"); + std::vector dims; + if (py::isinstance(dims_obj)) { + dims.push_back(py::cast(dims_obj)); + } else if (py::isinstance(dims_obj)) { + dims = py::cast>(dims_obj); + } + + // Extracting target_qudits + py::object target_qudits_obj = obj.attr("_target_qudits"); + std::vector target_qudits; + if (py::isinstance(target_qudits_obj)) { + target_qudits.push_back(py::cast(target_qudits_obj)); + } else if (py::isinstance(target_qudits_obj)) { + target_qudits = py::cast>(target_qudits_obj); + } + + py::object params; + if (is_none_or_empty(obj.attr("_params"))) { + } else { + params = obj.attr("_params"); + } + + std::tuple, std::vector> + control_set = {}; + if (is_none_or_empty(obj.attr("_controls_data"))) { + // std::cout << "control empty"<< std::endl; + } else { + py::object controls_data = obj.attr("_controls_data"); + auto indices = controls_data.attr("indices") + .cast>(); + auto ctrlStates = controls_data.attr("ctrl_states") + .cast>(); + + control_set = std::make_tuple(indices, ctrlStates); + } + + result.push_back(std::make_tuple(tag, dagger, dims, gate_type, + target_qudits, params, control_set)); + + // Increment the iterator + ++it; + } + + return std::make_tuple(num_qudits, dimensions, result); +} + +NoiseModel parse_noise_model(const py::dict& noise_model) { + NoiseModel newNoiseModel; + + for (const auto& gate : noise_model) { + auto gateName = gate.first.cast(); + + py::dict gateNoise = gate.second.cast(); + + NoiseType newNoiseType; + + std::variant> + noiseSpread; // Declared outside the if blocks + + for (const auto& noiseTypesPair : gateNoise) { + if (py::isinstance(noiseTypesPair.first)) { + std::string noiseSpreadString = + noiseTypesPair.first.cast(); + noiseSpread = noiseSpreadString; + // Handle string case + } else if (py::isinstance(noiseTypesPair.first)) { + std::vector noiseSpreadTuple = + noiseTypesPair.first.cast>(); + noiseSpread = noiseSpreadTuple; + } + + double depo = + noiseTypesPair.second.attr("probability_depolarizing").cast(); + double deph = + noiseTypesPair.second.attr("probability_dephasing").cast(); + std::tuple noiseProb = std::make_tuple(depo, deph); + + newNoiseType[noiseSpread] = noiseProb; + } + newNoiseModel[gateName] = newNoiseType; + } + return newNoiseModel; +} + +Circuit generateCircuit(const Circuit_info& circuitInfo, + const NoiseModel& noiseModel) { + // Get current time in milliseconds + auto currentTimeMs = std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()) + .count(); + const auto& [num_qudits, dimensions, circuit] = circuitInfo; + + std::random_device rd; + std::mt19937_64 gen(rd() + currentTimeMs); + + Circuit noisyCircuit; + + for (const Instruction& instruction : circuit) { + noisyCircuit.push_back(instruction); + + const auto& [tag, dag, dims_gate, gate_type, target_qudits, params, + control_set] = instruction; + std::vector referenceLines(target_qudits.begin(), target_qudits.end()); + + if (!(std::get<0>(control_set).empty()) && + (std::get<1>(control_set).empty())) { + auto [ctrl_dits, levels] = control_set; // Decompose the tuple + + referenceLines.insert(referenceLines.end(), ctrl_dits.begin(), + ctrl_dits.end()); + } + + if (noiseModel.find(tag) != noiseModel.end()) { + for (const auto& mode_noise : noiseModel.at(tag)) { + auto mode = mode_noise.first; + auto noise_info = mode_noise.second; + double depo = std::get<0>(noise_info); + double deph = std::get<1>(noise_info); + + std::discrete_distribution x_dist({1.0 - depo, depo}); + std::discrete_distribution z_dist({1.0 - deph, deph}); + + int x_choice = x_dist(gen); + int z_choice = z_dist(gen); + + if (x_choice == 1 || z_choice == 1) { + std::vector qudits; + + if (std::holds_alternative>(mode)) { + qudits = std::get>(mode); + + } else if (std::holds_alternative(mode)) { + std::string modeStr = std::get(mode); + + if (modeStr == "local") { + qudits = referenceLines; + } else if (modeStr == "all") { + for (int i = 0; i < num_qudits; ++i) + qudits.push_back(i); + } else if (modeStr == "nonlocal") { + assert(gate_type == "TWO" || gate_type == "MULTI"); + qudits = referenceLines; + } else if (modeStr == "control") { + assert(gate_type == "TWO"); + qudits.push_back(target_qudits.at(0)); + } else if (modeStr == "target") { + assert(gate_type == "TWO"); + qudits.push_back(target_qudits.at(1)); + } + } + + if (x_choice == 1) { + for (auto dit : qudits) { + if (tag == "rxy" || tag == "rz" || tag == "virtrz") { + std::vector dims; + dims.push_back(static_cast( + dimensions[static_cast(dit)])); + + py::list params_new; + + // Retrieve field 0 and 1 from params + py::object field_0 = params.attr("__getitem__")(0); + py::object field_1 = params.attr("__getitem__")(1); + + // Convert field_0 and field_1 to integers (assuming they are + // integers) + int value_0 = py::cast(field_0); + int value_1 = py::cast(field_1); + + // Create a new list and append value_0 and value_1 + params_new.append(value_0); + params_new.append(value_1); + + // Append pi and pi/2 + double pi = 3.14159265358979323846; + double pi_over_2 = pi / 2.0; + params_new.append(py::float_(pi)); + params_new.append(py::float_(pi_over_2)); + Instruction new_inst = std::make_tuple( + "rxy", false, dims, "SINGLE", std::vector{dit}, + py::cast(params_new), + std::tuple, + std::vector>()); + noisyCircuit.push_back(new_inst); + } else { + py::object params_new; + std::vector dims; + dims.push_back(static_cast( + dimensions[static_cast(dit)])); + + Instruction new_inst = std::make_tuple( + "x", false, dims, "SINGLE", std::vector{dit}, + params_new, + std::tuple, + std::vector>()); + noisyCircuit.push_back(new_inst); + } + } + } + + if (z_choice == 1) { + for (auto dit : qudits) { + if (tag == "rxy" || tag == "rz" || tag == "virtrz") { + py::list params_new; + + std::vector dims; + dims.push_back(static_cast( + dimensions[static_cast(dit)])); + + // Retrieve field 0 and 1 from params + py::object field_0 = params.attr("__getitem__")(0); + py::object field_1 = params.attr("__getitem__")(1); + + // Convert field_0 and field_1 to integers (assuming they are + // integers) + int value_0 = py::cast(field_0); + int value_1 = py::cast(field_1); + + // Create a new list and append value_0 and value_1 + params_new.append(value_0); + params_new.append(value_1); + + // Append pi and pi/2 + double pi = 3.14159265358979323846; + params_new.append(py::float_(pi)); + Instruction newInst = std::make_tuple( + "rz", false, dims, "SINGLE", std::vector{dit}, + py::cast(params_new), + std::tuple, + std::vector>()); + noisyCircuit.push_back(newInst); + } else { + py::object paramsNew; + std::vector dims; + dims.push_back(static_cast( + dimensions[static_cast(dit)])); + + Instruction newInst = std::make_tuple( + "z", false, dims, "SINGLE", std::vector{dit}, + paramsNew, + std::tuple, + std::vector>()); + noisyCircuit.push_back(newInst); + } + } + } + } + } + } + } + + return noisyCircuit; +} + +// ======================================================================================================= +// ======================================================================================================= +// ======================================================================================================= +// ===============()()()()()()()()()()() SIMULATION FUNCTIONS +// ()()()()()()()()()========================== +// ======================================================================================================= +// ======================================================================================================= +// ======================================================================================================= +// ======================================================================================================= +/* + * SUPPORTED GATES AT THE MOMENT UNTIL DIMENSION 7 +"csum": "csum", +"cx": "cx", +"h": "h", +"rxy": "r", +"rz": "rz", +"virtrz": "virtrz", +"s": "s", +"x": "x", +"z": "z" + */ +using ddpkg = std::unique_ptr; + +dd::MDDPackage::mEdge getGate(const ddpkg& dd, const Instruction& instruction) { + const auto& [tag, dag, dims, gate_type, target_qudits, params, control_set] = + instruction; + + dd::MDDPackage::mEdge gate; + auto numberRegs = + static_cast(dd->numberOfQuantumRegisters); + + dd::QuantumRegister tq = 0; + tq = static_cast(target_qudits.at(0)); + + dd::Controls controlSet{}; + if ((std::get<0>(control_set).size() > 0) && + (std::get<1>(control_set).size() > 0)) { + std::vector ctrlQudits = std::get<0>(control_set); + std::vector ctrlLevels = std::get<1>(control_set); + } + + if (tag == "rxy") { + // Handle rxy tag with dimension 2 + auto pl = params.cast(); + auto leva = pl[0].cast(); + auto levb = pl[1].cast(); + auto theta = pl[2].cast(); + auto phi = pl[3].cast(); + + if (checkDim(dims, 2)) { + dd::GateMatrix matrix = dd::RXY(theta, phi); + gate = dd->makeGateDD(matrix, numberRegs, controlSet, tq); + + } else if (checkDim(dims, 3)) { + // Handle rxy tag with dimension 3 + dd::TritMatrix matrix = dd::RXY3(theta, phi, leva, levb); + gate = dd->makeGateDD(matrix, numberRegs, controlSet, tq); + + } else if (checkDim(dims, 4)) { + // Handle rxy tag with dimension 4 + dd::QuartMatrix matrix = dd::RXY4(theta, phi, leva, levb); + gate = + dd->makeGateDD(matrix, numberRegs, controlSet, tq); + + } else if (checkDim(dims, 5)) { + dd::QuintMatrix matrix = dd::RXY5(theta, phi, leva, levb); + gate = + dd->makeGateDD(matrix, numberRegs, controlSet, tq); + } else if (checkDim(dims, 6)) { + dd::SextMatrix matrix = dd::RXY6(theta, phi, leva, levb); + gate = dd->makeGateDD(matrix, numberRegs, controlSet, tq); + } else if (checkDim(dims, 7)) { + dd::SeptMatrix matrix = dd::RXY7(theta, phi, leva, levb); + gate = dd->makeGateDD(matrix, numberRegs, controlSet, tq); + } + } else if (tag == "rz") { + auto pl = params.cast(); + + auto leva = pl[0].cast(); + auto levb = pl[1].cast(); + auto phi = pl[2].cast(); + + if (checkDim(dims, 2)) { + dd::GateMatrix matrix = dd::RZ(phi); + gate = dd->makeGateDD(matrix, numberRegs, controlSet, tq); + + } else if (checkDim(dims, 3)) { + dd::TritMatrix matrix = dd::RZ3(phi, leva, levb); + gate = dd->makeGateDD(matrix, numberRegs, controlSet, tq); + + } else if (checkDim(dims, 4)) { + dd::QuartMatrix matrix = dd::RZ4(phi, leva, levb); + gate = + dd->makeGateDD(matrix, numberRegs, controlSet, tq); + + } else if (checkDim(dims, 5)) { + dd::QuintMatrix matrix = dd::RZ5(phi, leva, levb); + gate = + dd->makeGateDD(matrix, numberRegs, controlSet, tq); + } else if (checkDim(dims, 6)) { + dd::SextMatrix matrix = dd::RZ6(phi, leva, levb); + gate = dd->makeGateDD(matrix, numberRegs, controlSet, tq); + } else if (checkDim(dims, 7)) { + dd::SeptMatrix matrix = dd::RZ7(phi, leva, levb); + gate = dd->makeGateDD(matrix, numberRegs, controlSet, tq); + } + + } else if (tag == "virtrz") { + auto pl = params.cast(); + + auto leva = pl[0].cast(); + auto phi = pl[1].cast(); + + if (checkDim(dims, 2)) { + dd::GateMatrix matrix = dd::VirtRZ(phi, leva); + gate = dd->makeGateDD(matrix, numberRegs, controlSet, tq); + + } else if (checkDim(dims, 3)) { + dd::TritMatrix matrix = dd::VirtRZ3(phi, leva); + gate = dd->makeGateDD(matrix, numberRegs, controlSet, tq); + + } else if (checkDim(dims, 4)) { + dd::QuartMatrix matrix = dd::VirtRZ4(phi, leva); + gate = + dd->makeGateDD(matrix, numberRegs, controlSet, tq); + + } else if (checkDim(dims, 5)) { + dd::QuintMatrix matrix = dd::VirtRZ5(phi, leva); + gate = + dd->makeGateDD(matrix, numberRegs, controlSet, tq); + } else if (checkDim(dims, 6)) { + dd::SextMatrix matrix = dd::VirtRZ6(phi, leva); + gate = dd->makeGateDD(matrix, numberRegs, controlSet, tq); + } else if (checkDim(dims, 7)) { + dd::SeptMatrix matrix = dd::VirtRZ7(phi, leva); + gate = dd->makeGateDD(matrix, numberRegs, controlSet, tq); + } + } else if (tag == "x") { + if (checkDim(dims, 2)) { + dd::GateMatrix matrix = dd::Xmat; + gate = dd->makeGateDD(matrix, numberRegs, controlSet, tq); + + } else if (checkDim(dims, 3)) { + dd::TritMatrix matrix = dd::X3; + gate = dd->makeGateDD(matrix, numberRegs, controlSet, tq); + + } else if (checkDim(dims, 4)) { + dd::QuartMatrix matrix = dd::X4; + gate = + dd->makeGateDD(matrix, numberRegs, controlSet, tq); + + } else if (checkDim(dims, 5)) { + dd::QuintMatrix matrix = dd::X5; + gate = + dd->makeGateDD(matrix, numberRegs, controlSet, tq); + } else if (checkDim(dims, 6)) { + dd::SextMatrix matrix = dd::X6; + gate = dd->makeGateDD(matrix, numberRegs, controlSet, tq); + } else if (checkDim(dims, 7)) { + dd::SeptMatrix matrix = dd::X7; + gate = dd->makeGateDD(matrix, numberRegs, controlSet, tq); + } + } else if (tag == "s") { + if (checkDim(dims, 2)) { + dd::GateMatrix matrix = dd::Smat; + gate = dd->makeGateDD(matrix, numberRegs, controlSet, tq); + + } else if (checkDim(dims, 3)) { + dd::TritMatrix matrix = dd::S3(); + gate = dd->makeGateDD(matrix, numberRegs, controlSet, tq); + + } else if (checkDim(dims, 4)) { + dd::QuartMatrix matrix = dd::S4(); + gate = + dd->makeGateDD(matrix, numberRegs, controlSet, tq); + + } else if (checkDim(dims, 5)) { + dd::QuintMatrix matrix = dd::S5(); + gate = + dd->makeGateDD(matrix, numberRegs, controlSet, tq); + } else if (checkDim(dims, 6)) { + dd::SextMatrix matrix = dd::S6(); + gate = dd->makeGateDD(matrix, numberRegs, controlSet, tq); + } else if (checkDim(dims, 7)) { + dd::SeptMatrix matrix = dd::S7(); + gate = dd->makeGateDD(matrix, numberRegs, controlSet, tq); + } + } else if (tag == "z") { + if (checkDim(dims, 2)) { + dd::GateMatrix matrix = dd::Zmat; + gate = dd->makeGateDD(matrix, numberRegs, controlSet, tq); + + } else if (checkDim(dims, 3)) { + dd::TritMatrix matrix = dd::Z3(); + gate = dd->makeGateDD(matrix, numberRegs, controlSet, tq); + + } else if (checkDim(dims, 4)) { + dd::QuartMatrix matrix = dd::Z4(); + gate = + dd->makeGateDD(matrix, numberRegs, controlSet, tq); + + } else if (checkDim(dims, 5)) { + dd::QuintMatrix matrix = dd::Z5(); + gate = + dd->makeGateDD(matrix, numberRegs, controlSet, tq); + } else if (checkDim(dims, 6)) { + dd::SextMatrix matrix = dd::Z6(); + gate = dd->makeGateDD(matrix, numberRegs, controlSet, tq); + } else if (checkDim(dims, 7)) { + dd::SeptMatrix matrix = dd::Z7(); + gate = dd->makeGateDD(matrix, numberRegs, controlSet, tq); + } + + } else if (tag == "h") { + if (checkDim(dims, 2)) { + dd::GateMatrix matrix = dd::H(); + gate = dd->makeGateDD(matrix, numberRegs, controlSet, tq); + + } else if (checkDim(dims, 3)) { + dd::TritMatrix matrix = dd::H3(); + gate = dd->makeGateDD(matrix, numberRegs, controlSet, tq); + + } else if (checkDim(dims, 4)) { + dd::QuartMatrix matrix = dd::H4(); + gate = + dd->makeGateDD(matrix, numberRegs, controlSet, tq); + + } else if (checkDim(dims, 5)) { + dd::QuintMatrix matrix = dd::H5(); + gate = + dd->makeGateDD(matrix, numberRegs, controlSet, tq); + } else if (checkDim(dims, 6)) { + dd::SextMatrix matrix = dd::H6(); + gate = dd->makeGateDD(matrix, numberRegs, controlSet, tq); + } else if (checkDim(dims, 7)) { + dd::SeptMatrix matrix = dd::H7(); + gate = dd->makeGateDD(matrix, numberRegs, controlSet, tq); + } + } else if (tag == "cx") { + auto pl = params.cast(); + + auto leva = pl[0].cast(); + auto levb = pl[1].cast(); + auto ctrlLev = pl[2].cast(); + auto phi = pl[3].cast(); + + auto cReg = static_cast(target_qudits.at(0)); + auto target = static_cast(target_qudits.at(1)); + return dd->cex(numberRegs, ctrlLev, phi, leva, levb, cReg, target, dag); + } else if (tag == "csum") { + auto cReg = static_cast(target_qudits.at(0)); + auto target = static_cast(target_qudits.at(1)); + return dd->csum(numberRegs, cReg, target, dag); + } + if (dag) { + gate = dd->conjugateTranspose(gate); + } + return gate; +} + +CVec ddsimulator(dd::QuantumRegisterCount numLines, + const std::vector& dims, const Circuit& circuit) { + const ddpkg dd = std::make_unique(numLines, dims); + auto psi = dd->makeZeroState(numLines); + + for (const Instruction& instruction : circuit) { + dd::MDDPackage::mEdge gate; + try { + gate = getGate(dd, instruction); + } catch (const std::exception& e) { + std::cerr << "Caught exception in gate creation: " << e.what() + << std::endl; + throw; // Re-throw the exception to propagate it further + } + try { + psi = dd->multiply(gate, psi); + } catch (const std::exception& e) { + printCircuit(circuit); + std::cout << "THE MATRIX " << std::endl; + dd->getVectorizedMatrix(gate); + std::cout << "THE VECTOR " << std::endl; + dd->printVector(psi); + std::cerr << "Problem is in multiplication " << e.what() << std::endl; + throw; // Re-throw the exception to propagate it further + } + } + + return dd->getVector(psi); +} + +py::list stateVectorSimulation(py::object& circ, py::object& noiseModel) { + auto parsedCircuitInfo = readCircuit(circ); + auto [numQudits, dims, original_circuit] = parsedCircuitInfo; + + Circuit noisyCircuit = original_circuit; + + py::dict noiseModelDict = noiseModel.attr("quantum_errors").cast(); + NoiseModel newNoiseModel = parse_noise_model(noiseModelDict); + + noisyCircuit = generateCircuit(parsedCircuitInfo, newNoiseModel); + + CVec myList = + ddsimulator(static_cast(numQudits), + static_cast>(dims), noisyCircuit); + + py::list result = complex_vector_to_list(myList); + + return result; +} + +PYBIND11_MODULE(_qudits, m) { + auto misim = m.def_submodule("misim"); + misim.def("state_vector_simulation", &stateVectorSimulation, "circuit"_a, + "noise_model"_a); +} From 5b95deba80075dd222ca8567f33de4aebf25204a Mon Sep 17 00:00:00 2001 From: burgholzer Date: Fri, 19 Apr 2024 21:31:05 +0200 Subject: [PATCH 04/22] =?UTF-8?q?=F0=9F=93=9D=20readme=20update?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 85 ++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 56 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 7b84a89..1fa1f90 100644 --- a/README.md +++ b/README.md @@ -1,50 +1,51 @@ +[![PyPI](https://img.shields.io/pypi/v/mqt.qudits?logo=pypi&style=flat-square)](https://pypi.org/project/mqt.qudits/) +![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-qudits/ci.yml?branch=main&style=flat-square&logo=github&label=ci)](https://github.com/cda-tum/mqt-qudits/actions/workflows/ci.yml) +[![CD](https://img.shields.io/github/actions/workflow/status/cda-tum/mqt-qudits/cd.yml?style=flat-square&logo=github&label=cd)](https://github.com/cda-tum/mqt-qudits/actions/workflows/cd.yml) +[![Documentation](https://img.shields.io/readthedocs/mqt-qudits?logo=readthedocs&style=flat-square)](https://mqt.readthedocs.io/projects/qudits) +[![codecov](https://img.shields.io/codecov/c/github/cda-tum/mqt-qudits?style=flat-square&logo=codecov)](https://codecov.io/gh/cda-tum/mqt-qudits)

- - - - + + + +

-# MQT Qudit - A quantum computing software framework tailored for working with mixed-dimensional quantum circuits. +# MQT Qudits - A framework for mixed-dimensional qudit quantum computing -A tool for research and education for mixed-dimensional quantum computing by -the [Chair for Design Automation](https://www.cda.cit.tum.de/) at -the [Technical University of Munich](https://www.tum.de/). +A tool for research and education for mixed-dimensional qudit quantum computing developed as part of the _Munich Quantum Toolkit_ (_MQT_) by the [Chair for Design Automation](https://www.cda.cit.tum.de/) at the [Technical University of Munich](https://www.tum.de/). -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](https://github.com/cda-tum/mqt-qudit/issues) on GitHub. For more -information [www.cda.cit.tum.de/research/](https://www.cda.cit.tum.de/research/quantum/). - -### System Requirements - -Building (and running) is continuously tested under Linux, MacOS using -the [latest available system versions for GitHub Actions](https://github.com/actions/virtual-environments). -The implementation is compatible with a minimimum version of Python 3.8. - -Git is required for the installation process. +

+ + Documentation + +

-### Setup, Build, and Run +If you have any questions, feel free to create a [discussion](https://github.com/cda-tum/mqt-qudits/discussions) or an [issue](https://github.com/cda-tum/mqt-qudits/issues) on [GitHub](https://github.com/cda-tum/mqt-qudits). -The tool demands only for the resolution of dependencies, to solve run in terminal. +## Getting Started -## Usage +`mqt.qudits` is available via [PyPI](https://pypi.org/project/mqt.qudits/) for all major operating systems and supports Python 3.8 to 3.12. -Remember to activate the python environment of the project, with the following lines in the terminal: +```console +(venv) $ pip install mqt.qudits +``` -## Documentation +**Detailed documentation and examples are available at [ReadTheDocs](https://mqt.readthedocs.io/projects/qudits).** -## System Requirements and Building +## System Requirements -The implementation is compatible with a minimimum version of Python 3.10. +The implementation is compatible with any C++17 compiler, a minimum CMake version of 3.19, and Python 3.8+. +Please refer to the [documentation](https://mqt.readthedocs.io/projects/qudits) 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 Qudits has been developed based on methods proposed in the following papers: + - K. Mato, S. Hillmich and R. Wille, "[Mixed-Dimensional Qudit State Preparation Using Edge-Weighted Decision Diagrams]()," 2024 61st Design Automation Conference (DAC), San Francisco, USA, 2024. - K. Mato, S. Hillmich and R. Wille, "[Mixed-Dimensional Quantum Circuit Simulation with Decision Diagrams](https://www.cda.cit.tum.de/files/eda/2023_mixed_dimensional_quantum_circuit_simulation_with_decision_diagrams.pdf)," 2023 IEEE International Conference on Quantum Computing and Engineering (QCE), Bellevue, Washington, USA, 2023. @@ -54,3 +55,29 @@ Building (and running) is continuously tested under Linux, macOS, and Windows us - K. Mato, M. Ringbauer, S. Hillmich and R. Wille, "[Compilation of Entangling Gates for High-Dimensional Quantum Systems](https://www.cda.cit.tum.de/files/eda/2023_aspdac_qudit_entanglement_compilation.pdf)," 2023 28th Asia and South Pacific Design Automation Conference (ASP-DAC), Tokyo, Japan, 2023, pp. 202-208. - K. Mato, M. Ringbauer, S. Hillmich and R. Wille, "[Adaptive Compilation of Multi-Level Quantum Operations](https://www.cda.cit.tum.de/files/eda/2022_qce_adaptive_compilation_of_multi_level_quantum_operations.pdf)," 2022 IEEE International Conference on Quantum Computing and Engineering (QCE), Broomfield, CO, USA, 2022, pp. 484-491, doi: 10.1109/QCE53715.2022.00070. + +--- + +## Acknowledgements + +The Munich Quantum Toolkit has been supported by the European +Research Council (ERC) under the European Union's Horizon 2020 research and innovation program (grant agreement +No. 101001318), the Bavarian State Ministry for Science and Arts through the Distinguished Professorship Program, as well as the +Munich Quantum Valley, which is supported by the Bavarian state government with funds from the Hightech Agenda Bayern Plus. + +

+ + + + + + + + + + + + + + +

From c1aac0b80579afa834ba99fee299783e63ff0d51 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Fri, 19 Apr 2024 21:31:19 +0200 Subject: [PATCH 05/22] =?UTF-8?q?=F0=9F=94=A7=20noxfile?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- noxfile.py | 115 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 noxfile.py diff --git a/noxfile.py b/noxfile.py new file mode 100644 index 0000000..f4b98fe --- /dev/null +++ b/noxfile.py @@ -0,0 +1,115 @@ +"""Nox sessions.""" + +from __future__ import annotations + +import argparse +import os +import shutil +import sys +from typing import TYPE_CHECKING + +import nox + +if TYPE_CHECKING: + from collections.abc import Sequence + +nox.needs_version = ">=2024.3.2" +nox.options.default_venv_backend = "uv|virtualenv" + +nox.options.sessions = ["lint", "tests"] + +PYTHON_ALL_VERSIONS = ["3.8", "3.9", "3.10", "3.11", "3.12"] + +BUILD_REQUIREMENTS = [ + "scikit-build-core[pyproject]>=0.8.1", + "setuptools_scm>=7", + "pybind11>=2.12", +] + +if os.environ.get("CI", None): + nox.options.error_on_missing_interpreters = True + + +@nox.session(reuse_venv=True) +def lint(session: nox.Session) -> None: + """Run the linter.""" + session.install("pre-commit") + session.run("pre-commit", "run", "--all-files", *session.posargs) + + +def _run_tests( + session: nox.Session, + *, + install_args: Sequence[str] = (), + run_args: Sequence[str] = (), + extras: Sequence[str] = (), +) -> None: + posargs = list(session.posargs) + env = {"PIP_DISABLE_PIP_VERSION_CHECK": "1"} + + if os.environ.get("CI", None) and sys.platform == "win32": + env["SKBUILD_CMAKE_ARGS"] = "-T ClangCL" + + if shutil.which("cmake") is None and shutil.which("cmake3") is None: + session.install("cmake") + if shutil.which("ninja") is None: + session.install("ninja") + + _extras = ["test", *extras] + if "--cov" in posargs: + _extras.append("coverage") + posargs.append("--cov-config=pyproject.toml") + + session.install(*BUILD_REQUIREMENTS, *install_args, env=env) + install_arg = f"-ve.[{','.join(_extras)}]" + session.install("--no-build-isolation", install_arg, *install_args, env=env) + session.run("pytest", *run_args, *posargs, env=env) + + +@nox.session(reuse_venv=True, python=PYTHON_ALL_VERSIONS) +def tests(session: nox.Session) -> None: + """Run the test suite.""" + _run_tests(session) + + +@nox.session(reuse_venv=True, venv_backend="uv") +def minimums(session: nox.Session) -> None: + """Test the minimum versions of dependencies.""" + _run_tests( + session, + install_args=["--resolution=lowest-direct"], + run_args=["-Wdefault"], + ) + session.run("uv", "pip", "list") + + +@nox.session(reuse_venv=True) +def docs(session: nox.Session) -> None: + """Build the docs. Use "--non-interactive" to avoid serving. Pass "-b linkcheck" to check links.""" + parser = argparse.ArgumentParser() + parser.add_argument("-b", dest="builder", default="html", help="Build target (default: html)") + args, posargs = parser.parse_known_args(session.posargs) + + serve = args.builder == "html" and session.interactive + extra_installs = ["sphinx-autobuild"] if serve else [] + session.install(*BUILD_REQUIREMENTS, *extra_installs) + session.install("--no-build-isolation", "-ve.[docs]") + session.chdir("docs") + + if args.builder == "linkcheck": + session.run("sphinx-build", "-b", "linkcheck", ".", "_build/linkcheck", *posargs) + return + + shared_args = ( + "-n", # nitpicky mode + "-T", # full tracebacks + f"-b={args.builder}", + ".", + f"_build/{args.builder}", + *posargs, + ) + + if serve: + session.run("sphinx-autobuild", "--ignore", "**jupyter**", *shared_args) + else: + session.run("sphinx-build", "--keep-going", *shared_args) From 4ea2efb654872dd2587c44e6901c8f77d4ba2b98 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Fri, 19 Apr 2024 21:31:28 +0200 Subject: [PATCH 06/22] =?UTF-8?q?=F0=9F=99=88=20update=20gitignore?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 89 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 50 insertions(+), 39 deletions(-) diff --git a/.gitignore b/.gitignore index f74adfb..9c2cf70 100644 --- a/.gitignore +++ b/.gitignore @@ -5,11 +5,13 @@ __pycache__/ # C extensions *.so +.ccache/ +cmake-build-* # Distribution / packaging - .Python -build/ +/build/ +/test/*/build develop-eggs/ dist/ downloads/ @@ -71,6 +73,7 @@ instance/ # Sphinx documentation docs/_build/ +docs/api/ # PyBuilder .pybuilder/ @@ -83,34 +86,7 @@ target/ profile_default/ ipython_config.py -# pyenv -# For a library or package, you might want to ignore these files since the code is -# intended to run in multiple environments; otherwise, check them in: -# .python-version - -# pipenv -# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. -# However, in case of collaboration, if having platform-specific dependencies or dependencies -# having no cross-platform support, pipenv may install dependencies that don't work, or not -# install all needed dependencies. -#Pipfile.lock - -# poetry -# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. -# This is especially recommended for binary packages to ensure reproducibility, and is more -# commonly ignored for libraries. -# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control -#poetry.lock - -# pdm -# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. -#pdm.lock -# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it -# in version control. -# https://pdm.fming.dev/#use-with-ide -.pdm.toml - -# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +# PEP 582; used by e.g. github.com/David-OConnor/pyflow __pypackages__/ # Celery stuff @@ -121,10 +97,10 @@ celerybeat.pid *.sage.py # Environments -.env -.venv -env/ -venv/ +.env* +.venv* +env*/ +venv*/ ENV/ env.bak/ venv.bak/ @@ -153,9 +129,44 @@ dmypy.json # Cython debug symbols cython_debug/ -# PyCharm -# JetBrains specific template is maintained in a separate JetBrains.gitignore that can -# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore -# and can be added to the global gitignore or merged into this file. For a more nuclear -# option (not recommended) you can uncomment the following to ignore the entire idea folder. +# setuptools_scm +src/*/_version.py + +# SKBuild cache dir +_skbuild/ + +# Any build dirs in the tests +test/**/build/ +/src/mqt/qudits/_version.py + +# Common editor files +*~ +*.swp + +# RPM spec file +!/distro/*.spec +/distro/*.tar.gz +*.rpm + +# ruff +.ruff_cache/ + +# OS specific stuff +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + .idea/ +.vscode/ +# tmt setup +/distro/main.fmf +/distro/plans/main.fmf +/distro/tests/main.fmf + +/docs/**/build +.vs +out/build From 79ab160b0c3077f89815be6c1d03ab89d71c061c Mon Sep 17 00:00:00 2001 From: burgholzer Date: Fri, 19 Apr 2024 21:31:40 +0200 Subject: [PATCH 07/22] =?UTF-8?q?=F0=9F=93=84=20update=20license?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index c3ae309..6206835 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023 Technical University Munich, Chair for Design Automation +Copyright (c) 2024 Chair for Design Automation, Technical University of Munich, Germany Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From e49adbd7e09f085f7004bc9037e5451f34d81f55 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Fri, 19 Apr 2024 21:31:50 +0200 Subject: [PATCH 08/22] =?UTF-8?q?=F0=9F=93=A6=20git=20archival=20support?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .git_archival.txt | 4 ++++ .gitattributes | 2 ++ 2 files changed, 6 insertions(+) create mode 100644 .git_archival.txt create mode 100644 .gitattributes diff --git a/.git_archival.txt b/.git_archival.txt new file mode 100644 index 0000000..8fb235d --- /dev/null +++ b/.git_archival.txt @@ -0,0 +1,4 @@ +node: $Format:%H$ +node-date: $Format:%cI$ +describe-name: $Format:%(describe:tags=true,match=*[0-9]*)$ +ref-names: $Format:%D$ diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..5def89d --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +test/circuits/** linguist-vendored +.git_archival.txt export-subst From 536242417243cf7d4e37906eeb03675432df9f4a Mon Sep 17 00:00:00 2001 From: burgholzer Date: Fri, 19 Apr 2024 21:32:21 +0200 Subject: [PATCH 09/22] =?UTF-8?q?=E2=9C=85=20restructure=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/CMakeLists.txt | 7 + test/data_collect.cpp | 532 +++++++++ test/main.cpp | 46 + .../python}/__init__.py | 0 .../python/compiler}/__init__.py | 0 .../python/compiler/onedit}/__init__.py | 0 .../python/compiler/twodit}/__init__.py | 0 .../compiler/twodit/entangled_qr}/__init__.py | 0 .../twodit/entangled_qr/test_entangled_qr.py | 0 .../twodit/entangled_qr/test_search.py | 0 .../python/qudits_circuits}/__init__.py | 0 .../qudits_circuits/components}/__init__.py | 0 .../components/instructions}/__init__.py | 0 .../instructions/gate_set}/__init__.py | 0 .../instructions/gate_set/test_csum.py | 0 .../instructions/gate_set/test_cx.py | 0 .../instructions/gate_set/test_s.py | 0 .../instructions/gate_set/test_x.py | 0 .../instructions/gate_set/test_z.py | 0 .../test_qudits => test/python}/test.py | 0 test/test_pkg.cpp | 1060 +++++++++++++++++ tests/__init__.py | 0 tests/mqt_test/__init__.py | 0 tests/mqt_test/test_qudits/__init__.py | 0 .../mqt_test/test_qudits/compiler/__init__.py | 0 .../test_qudits/compiler/onedit/__init__.py | 0 .../test_qudits/compiler/twodit/__init__.py | 0 .../compiler/twodit/entangled_qr/__init__.py | 0 .../test_qudits/qudits_circuits/__init__.py | 0 .../qudits_circuits/components/__init__.py | 0 .../components/instructions/__init__.py | 0 .../instructions/gate_set/__init__.py | 0 32 files changed, 1645 insertions(+) create mode 100644 test/CMakeLists.txt create mode 100644 test/data_collect.cpp create mode 100644 test/main.cpp rename {src/mqt/qudits/compiler/twodit/mapping_un_aware_transpilation/variational_twodit_compilation => test/python}/__init__.py (100%) mode change 100755 => 100644 rename {src/mqt/qudits/compiler/twodit/mapping_un_aware_transpilation/variational_twodit_compilation/ansatz => test/python/compiler}/__init__.py (100%) mode change 100755 => 100644 rename {src/mqt/qudits/compiler/twodit/mapping_un_aware_transpilation/variational_twodit_compilation/opt => test/python/compiler/onedit}/__init__.py (100%) mode change 100755 => 100644 rename {src/mqt/qudits/compression => test/python/compiler/twodit}/__init__.py (100%) rename {src/mqt/qudits/compression/qubit_mapping => test/python/compiler/twodit/entangled_qr}/__init__.py (100%) rename {tests/mqt_test/test_qudits => test/python}/compiler/twodit/entangled_qr/test_entangled_qr.py (100%) rename {tests/mqt_test/test_qudits => test/python}/compiler/twodit/entangled_qr/test_search.py (100%) rename {src/mqt/qudits/core/structures => test/python/qudits_circuits}/__init__.py (100%) rename {src/mqt/qudits/core/structures/ddpy => test/python/qudits_circuits/components}/__init__.py (100%) rename {src/mqt/qudits/core/structures/energy_level_graph => test/python/qudits_circuits/components/instructions}/__init__.py (100%) rename {src/mqt/qudits/core/structures/trees => test/python/qudits_circuits/components/instructions/gate_set}/__init__.py (100%) rename {tests/mqt_test/test_qudits => test/python}/qudits_circuits/components/instructions/gate_set/test_csum.py (100%) rename {tests/mqt_test/test_qudits => test/python}/qudits_circuits/components/instructions/gate_set/test_cx.py (100%) rename {tests/mqt_test/test_qudits => test/python}/qudits_circuits/components/instructions/gate_set/test_s.py (100%) rename {tests/mqt_test/test_qudits => test/python}/qudits_circuits/components/instructions/gate_set/test_x.py (100%) rename {tests/mqt_test/test_qudits => test/python}/qudits_circuits/components/instructions/gate_set/test_z.py (100%) rename {tests/mqt_test/test_qudits => test/python}/test.py (100%) create mode 100644 test/test_pkg.cpp delete mode 100644 tests/__init__.py delete mode 100644 tests/mqt_test/__init__.py delete mode 100644 tests/mqt_test/test_qudits/__init__.py delete mode 100644 tests/mqt_test/test_qudits/compiler/__init__.py delete mode 100644 tests/mqt_test/test_qudits/compiler/onedit/__init__.py delete mode 100644 tests/mqt_test/test_qudits/compiler/twodit/__init__.py delete mode 100644 tests/mqt_test/test_qudits/compiler/twodit/entangled_qr/__init__.py delete mode 100644 tests/mqt_test/test_qudits/qudits_circuits/__init__.py delete mode 100644 tests/mqt_test/test_qudits/qudits_circuits/components/__init__.py delete mode 100644 tests/mqt_test/test_qudits/qudits_circuits/components/instructions/__init__.py delete mode 100644 tests/mqt_test/test_qudits/qudits_circuits/components/instructions/gate_set/__init__.py diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 0000000..4ca3afb --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,7 @@ +# add unit tests +package_add_test(mqt-qudits-test mqt-qudits test_pkg.cpp) +package_add_test(mqt-qudits-example mqt-qudits main.cpp) + +add_executable(mqt-qudits-collect data_collect.cpp) +target_link_libraries(mqt-qudits-collect PRIVATE mqt-qudits) +set_target_properties(mqt-qudits-collect PROPERTIES FOLDER tests) diff --git a/test/data_collect.cpp b/test/data_collect.cpp new file mode 100644 index 0000000..9b99756 --- /dev/null +++ b/test/data_collect.cpp @@ -0,0 +1,532 @@ +#include "dd/MDDPackage.hpp" + +#include +#include +#include +#include +#include + +dd::Edge +fullMixWState([[maybe_unused]] std::ofstream& file, + std::vector orderOfLayers) { + std::vector lines{}; + dd::QuantumRegisterCount numLines = 0U; + std::map> application; + std::vector> indexes{{}}; + std::size_t sizeTracker = 0; + std::size_t numop = 0; + std::map quditcounter; + quditcounter[2] = 0; + quditcounter[3] = 0; + quditcounter[5] = 0; + + bool initial = true; + for (auto i = 0U; i < orderOfLayers.size(); i++) { + // Update cardinality of each line, put them in order + // Update the application indexes + if (initial) { + for (auto j = 0U; j < orderOfLayers.at(i); j++) { + lines.push_back(orderOfLayers.at(i)); + indexes.at(0).push_back(static_cast(j)); + numLines++; + } + application[i] = std::vector{sizeTracker}; + application[i + 1] = {}; + sizeTracker++; + for (auto j = 0U; j < indexes.at(0).size(); j++) { + indexes.push_back( + std::vector{indexes.at(0).at(j)}); + application[i + 1].push_back(sizeTracker); + sizeTracker++; + } + + initial = false; + } else { + auto tempLine = lines.size(); + auto counter = 0U; + for (auto k = 0U; k < tempLine; k++) { + auto adder = k + counter; + for (auto j = 1U; j < orderOfLayers.at(i); j++) { + lines.insert(lines.begin() + adder + j, orderOfLayers.at(i)); + numLines++; + counter++; + } + } + for (auto& indexe : indexes) { + for (auto& f : indexe) { + f = static_cast( + f + + f * (static_cast(orderOfLayers.at(i) - 1))); + } + } + std::vector toAdd{}; + for (auto& indexe : indexes) { + if (indexe.size() == 1) { + toAdd.push_back(indexe.at(0)); + for (auto j = 1U; j < orderOfLayers.at(i); j++) { + indexe.push_back(static_cast( + indexe.at(0) + static_cast(j))); + toAdd.push_back(static_cast( + indexe.at(0) + static_cast(j))); + } + } + } + if (i < orderOfLayers.size() - 1) { + for (auto& l : toAdd) { + indexes.push_back(std::vector{l}); + application[i + 1].push_back(sizeTracker); + sizeTracker++; + } + application[i + 2] = {}; + } + } + } + + auto dd = std::make_unique(numLines, lines); + + std::vector initState(numLines, 0); + initState.at(0) = 1; + + auto begin = std::chrono::high_resolution_clock::now(); + + auto evolution = dd->makeBasisState(numLines, initState); + + for (auto i = 0U; i < orderOfLayers.size(); i++) { + if (orderOfLayers.at(i) == 2) { + for (const auto g : application[i]) { + const std::vector inputLines = indexes.at(g); + evolution = dd->spread2(numLines, inputLines, evolution); + numop = numop + 3; + } + } else if (orderOfLayers.at(i) == 3) { + for (const auto g : application[i]) { + evolution = dd->spread3( + numLines, + reinterpret_cast&>( + indexes.at(g)), + evolution); + numop = numop + 6; + } + } else if (orderOfLayers.at(i) == 5) { + for (const auto g : application[i]) { + evolution = dd->spread5( + numLines, + reinterpret_cast&>( + indexes.at(g)), + evolution); + numop = numop + 15; + } + } + } + + auto end = std::chrono::high_resolution_clock::now(); + auto elapsed = + std::chrono::duration_cast(end - begin); + + std::unordered_set nodeset{}; + auto numnodes = dd->nodeCount(evolution, nodeset); + auto numcplx = dd->complexNumber.complexTable.getPeakCount(); + + // Build a comma-separated string of the elements + std::ostringstream oss; + for (const auto line : lines) { + oss << line; + } + + for (const auto line : lines) { + quditcounter[line] = quditcounter[line] + 1; + } + + std::cout << "FullMix, "; + // Iterate over the map and print the key-value pairs + for (auto const& pair : quditcounter) { + std::cout << pair.first << ": " << pair.second << " -"; + } + std::cout << "//"; + std::cout << lines.size() << ", " << numop << ", " << numnodes << ", " + << numcplx << ", " << (static_cast(elapsed.count()) * 1e-9) + << "\n"; + return evolution; +} + +dd::Edge +ghzQutritStateScaled(std::ofstream& file, dd::QuantumRegisterCount i) { + const std::vector init(i, 3); + auto dd = std::make_unique(i, init); + + auto begin = std::chrono::high_resolution_clock::now(); + // Gates + auto h3Gate = dd->makeGateDD(dd::H3(), i, 0); + std::vector gates = {}; + + for (dd::QuantumRegister target = 1; static_cast(target) < i; target++) { + dd::Controls target1{}; + dd::Controls target2{}; + + for (dd::QuantumRegister control = 0; control < target; control++) { + const dd::Control c1{static_cast(control), 1}; + const dd::Control c2{static_cast(control), 2}; + target1.insert(c1); + target2.insert(c2); + } + + gates.push_back(dd->makeGateDD(dd::X3, i, target1, target)); + gates.push_back( + dd->makeGateDD(dd::X3dag, i, target2, target)); + } + + auto evolution = dd->makeZeroState(i); + evolution = dd->multiply(h3Gate, evolution); + + for (auto& gate : gates) { + evolution = dd->multiply(gate, evolution); + } + auto end = std::chrono::high_resolution_clock::now(); + auto elapsed = + std::chrono::duration_cast(end - begin); + + auto numop = gates.size(); + std::unordered_set nodeset{}; + auto numnodes = dd->nodeCount(evolution, nodeset); + auto numcplx = dd->complexNumber.complexTable.getPeakCount(); + + // Build a comma-separated string of the elements + std::ostringstream oss; + for (auto j : init) { + oss << j; + } + + file << "GHZ, " << init.size() << ", " << oss.str() << ", " << numop << ", " + << numnodes << ", " << numcplx << ", " + << (static_cast(elapsed.count()) * 1e-9) << "\n"; + + return evolution; +} + +dd::Edge +randomCircuits(dd::QuantumRegisterCount w, std::size_t d, std::ofstream& file) { + const dd::QuantumRegisterCount width = w; + const std::size_t depth = d; + const std::size_t maxD = 5; + + std::mt19937 gen(1592645427); // seed the generator + + std::vector particles = {}; + + std::uniform_int_distribution dimdistr(2, maxD); + particles.reserve(width); + for (auto i = 0U; i < width; i++) { + particles.push_back(dimdistr(gen)); + } + + auto dd = std::make_unique(width, particles); + + std::uniform_int_distribution<> pickbool(0, 1); + std::uniform_int_distribution pickcontrols(1, width - 1); + std::uniform_real_distribution<> angles(0.0, 2. * dd::PI); + + auto beginClock = std::chrono::high_resolution_clock::now(); + + auto evolution = + dd->makeZeroState(static_cast(width)); + + for (auto timeStep = 0U; timeStep < depth; timeStep++) { + for (auto line = 0U; line < width; line++) { + // chose if local gate or entangling gate + auto randomChoice = pickbool(gen); + + if (randomChoice == 0) { // local op + + auto localChoice = pickbool(gen); + + if (localChoice == 0) { // hadamard + if (particles.at(line) == 2) { + auto chosenGate = dd->makeGateDD( + dd::H(), width, static_cast(line)); + evolution = dd->multiply(chosenGate, evolution); + } else if (particles.at(line) == 3) { + auto chosenGate = dd->makeGateDD( + dd::H3(), width, static_cast(line)); + evolution = dd->multiply(chosenGate, evolution); + } else if (particles.at(line) == 4) { + auto chosenGate = dd->makeGateDD( + dd::H4(), width, static_cast(line)); + evolution = dd->multiply(chosenGate, evolution); + } else if (particles.at(line) == 5) { + auto chosenGate = dd->makeGateDD( + dd::H5(), width, static_cast(line)); + evolution = dd->multiply(chosenGate, evolution); + } + } else { // givens + if (particles.at(line) == 2) { + const double theta = 0.; + const double phi = 0.; + auto chosenGate = dd->makeGateDD( + dd::RXY(theta, phi), width, + static_cast(line)); + evolution = dd->multiply(chosenGate, evolution); + } else if (particles.at(line) == 3) { + const double theta = angles(gen); + const double phi = angles(gen); + std::uniform_int_distribution picklevel(0, 2); + + auto levelA = picklevel(gen); + auto levelB = (levelA + 1) % 3; + if (levelA > levelB) { + auto temp = levelA; + levelA = levelB; + levelB = temp; + } + + auto chosenGate = dd->makeGateDD( + dd::RXY3(theta, phi, levelA, levelB), width, + static_cast(line)); + evolution = dd->multiply(chosenGate, evolution); + } else if (particles.at(line) == 4) { + const double theta = angles(gen); + const double phi = angles(gen); + std::uniform_int_distribution picklevel(0, 3); + + auto levelA = picklevel(gen); + auto levelB = (levelA + 1) % 4; + if (levelA > levelB) { + auto temp = levelA; + levelA = levelB; + levelB = temp; + } + + auto chosenGate = dd->makeGateDD( + dd::RXY4(theta, phi, levelA, levelB), width, + static_cast(line)); + evolution = dd->multiply(chosenGate, evolution); + } else if (particles.at(line) == 5) { + const double theta = angles(gen); + const double phi = angles(gen); + std::uniform_int_distribution picklevel(0, 4); + + auto levelA = picklevel(gen); + auto levelB = (levelA + 1) % 5; + if (levelA > levelB) { + auto temp = levelA; + levelA = levelB; + levelB = temp; + } + + auto chosenGate = dd->makeGateDD( + dd::RXY5(theta, phi, levelA, levelB), width, + static_cast(line)); + evolution = dd->multiply(chosenGate, evolution); + } + } + } else { // entangling + + auto entChoice = pickbool(gen); + auto numberOfControls = pickcontrols(gen); + + std::vector controlLines; + + for (auto i = 0U; i < width; i++) { + if (i != line) { + controlLines.push_back(i); + } + } + + std::vector controlParticles; + controlParticles.resize(numberOfControls); + std::sample(std::begin(controlLines), std::end(controlLines), + std::back_inserter(controlParticles), numberOfControls, + gen); + std::sort(controlParticles.begin(), controlParticles.end()); + + dd::Controls control{}; + for (auto i = 0U; i < numberOfControls; i++) { + std::uniform_int_distribution picklevel( + 0, particles.at(controlParticles.at(i)) - 1); + auto level = picklevel(gen); + + const dd::Control c{ + static_cast(controlParticles.at(i)), + static_cast(level)}; + control.insert(c); + } + + if (entChoice == 0) { // CEX based + // selection of controls + if (particles.at(line) == 2) { + const double theta = angles(gen); + const double phi = angles(gen); + auto chosenGate = dd->makeGateDD( + dd::RXY(theta, phi), width, control, + static_cast(line)); + evolution = dd->multiply(chosenGate, evolution); + } else if (particles.at(line) == 3) { + std::uniform_int_distribution picklevel(0, 2); + + const double theta = angles(gen); + const double phi = angles(gen); + auto levelA = picklevel(gen); + auto levelB = (levelA + 1) % 3; + if (levelA > levelB) { + auto temp = levelA; + levelA = levelB; + levelB = temp; + } + + auto chosenGate = dd->makeGateDD( + dd::RXY3(theta, phi, levelA, levelB), width, control, + static_cast(line)); + evolution = dd->multiply(chosenGate, evolution); + } else if (particles.at(line) == 4) { + std::uniform_int_distribution picklevel(0, 3); + + const double theta = angles(gen); + const double phi = angles(gen); + auto levelA = picklevel(gen); + auto levelB = (levelA + 1) % 4; + if (levelA > levelB) { + auto temp = levelA; + levelA = levelB; + levelB = temp; + } + + auto chosenGate = dd->makeGateDD( + dd::RXY4(theta, phi, levelA, levelB), width, control, + static_cast(line)); + evolution = dd->multiply(chosenGate, evolution); + } else if (particles.at(line) == 5) { + std::uniform_int_distribution picklevel(0, 4); + + const double theta = angles(gen); + const double phi = angles(gen); + auto levelA = picklevel(gen); + auto levelB = (levelA + 1) % 5; + if (levelA > levelB) { + auto temp = levelA; + levelA = levelB; + levelB = temp; + } + + auto chosenGate = dd->makeGateDD( + dd::RXY5(theta, phi, levelA, levelB), width, control, + static_cast(line)); + evolution = dd->multiply(chosenGate, evolution); + } + } else { // Controlled clifford + if (particles.at(line) == 2) { + auto chosenGate = dd->makeGateDD( + dd::Xmat, width, control, + static_cast(line)); + evolution = dd->multiply(chosenGate, evolution); + } else if (particles.at(line) == 3) { + auto chosenGate = dd->makeGateDD( + dd::X3, width, control, static_cast(line)); + evolution = dd->multiply(chosenGate, evolution); + } else if (particles.at(line) == 4) { + auto chosenGate = dd->makeGateDD( + dd::X4, width, control, static_cast(line)); + evolution = dd->multiply(chosenGate, evolution); + } else if (particles.at(line) == 5) { + auto chosenGate = dd->makeGateDD( + dd::X5, width, control, static_cast(line)); + evolution = dd->multiply(chosenGate, evolution); + } + } + } + } + } + + auto end = std::chrono::high_resolution_clock::now(); + auto elapsed = + std::chrono::duration_cast(end - beginClock); + + auto numop = width * depth; + std::unordered_set nodeset{}; + auto numnodes = dd->nodeCount(evolution, nodeset); + auto numcplx = dd->complexNumber.complexTable.getPeakCount(); + + // Build a comma-separated string of the elements + std::ostringstream oss; + for (const auto particle : particles) { + oss << particle; + } + + file << "Random, " << particles.size() << ", " << oss.str() << ", " << numop + << ", " << numnodes << ", " << numcplx << ", " + << (static_cast(elapsed.count()) * 1e-9) << "\n"; + return evolution; +} +int main() { // NOLINT(bugprone-exception-escape) + std::ofstream myfile; + myfile.open("/home/k3vn/Desktop/trycollect.csv", std::ios_base::app); + myfile << "Bench, NumLines, Qudits, Operations, Nodes, CplxNum, time\n"; + + fullMixWState(myfile, {2, 3}); + fullMixWState(myfile, {3, 5, 2}); + fullMixWState(myfile, {3, 5, 2, 3}); + fullMixWState(myfile, {2, 3, 2, 3, 3}); + fullMixWState(myfile, {2, 5, 3, 3}); + fullMixWState(myfile, {2, 3, 2, 5}); + fullMixWState(myfile, {2, 3, 3, 3}); + fullMixWState(myfile, {5, 5, 2, 2}); + std::cout << "First set" << "\n"; + myfile.close(); + myfile.open("/home/k3vn/Desktop/trycollect.csv", std::ios_base::app); + + ghzQutritStateScaled(myfile, 5); + ghzQutritStateScaled(myfile, 10); + ghzQutritStateScaled(myfile, 30); + ghzQutritStateScaled(myfile, 60); + ghzQutritStateScaled(myfile, 120); + ghzQutritStateScaled(myfile, 128); + std::cout << "Second set" << "\n"; + myfile.close(); + myfile.open("/home/k3vn/Desktop/trycollect.csv", std::ios_base::app); + randomCircuits(3, 1000, myfile); + randomCircuits(4, 1000, myfile); + randomCircuits(5, 1000, myfile); + randomCircuits(6, 1000, myfile); + randomCircuits(7, 1000, myfile); + + std::cout << "Third set" << "\n"; + myfile.close(); + myfile.open("/home/k3vn/Desktop/trycollect.csv", std::ios_base::app); + randomCircuits(3, 1000, myfile); + randomCircuits(4, 1000, myfile); + randomCircuits(5, 1000, myfile); + randomCircuits(6, 1000, myfile); + randomCircuits(7, 1000, myfile); + + std::cout << "fourth set" << "\n"; + myfile.close(); + myfile.open("/home/k3vn/Desktop/trycollect.csv", std::ios_base::app); + randomCircuits(3, 1000, myfile); + randomCircuits(4, 1000, myfile); + randomCircuits(5, 1000, myfile); + randomCircuits(6, 1000, myfile); + randomCircuits(7, 1000, myfile); + + std::cout << "fifth set" << "\n"; + myfile.close(); + myfile.open("/home/k3vn/Desktop/trycollect.csv", std::ios_base::app); + randomCircuits(8, 1000, myfile); + randomCircuits(9, 1000, myfile); + randomCircuits(10, 1000, myfile); + randomCircuits(11, 1000, myfile); + randomCircuits(12, 1000, myfile); + randomCircuits(13, 1000, myfile); + + std::cout << "6 set" << "\n"; + myfile.close(); + myfile.open("/home/k3vn/Desktop/trycollect.csv", std::ios_base::app); + randomCircuits(8, 1000, myfile); + randomCircuits(9, 1000, myfile); + randomCircuits(10, 1000, myfile); + randomCircuits(11, 1000, myfile); + randomCircuits(12, 1000, myfile); + randomCircuits(13, 1000, myfile); + + std::cout << "7 set" << "\n"; + + myfile.close(); + return 0; +} diff --git a/test/main.cpp b/test/main.cpp new file mode 100644 index 0000000..51c1e23 --- /dev/null +++ b/test/main.cpp @@ -0,0 +1,46 @@ +#include "dd/MDDPackage.hpp" + +#include + +int main() { // NOLINT(bugprone-exception-escape) + std::vector const lines{2, 3}; + dd::QuantumRegisterCount numLines = 2U; + + auto dd = std::make_unique( + numLines, lines); // Create new package instance capable of handling a + // qubit and a qutrit + auto zeroState = dd->makeZeroState(numLines); // zero_state = |0> + + /* Creating a DD requires the following inputs: + * 1. A matrix describing a single-qubit/qudit operation (here: the Hadamard + * matrix) + * 2. The number of qudits the DD will operate on (here: two lines) + * 3. The operations are applied to the qubit q0 and the qutrit q1 + * (4. Controlled operations can be created by additionally specifying a list + * of control qubits before the target declaration) + */ + auto hOnQubit = dd->makeGateDD(dd::H(), numLines, 0); + // auto h_on_qutrit = dd->makeGateDD(dd::H3(), 2, 1); + + // Multiplying the operation and the state results in a new state, here a + // single qubit in superposition + auto psi = dd->multiply(hOnQubit, zeroState); + + // Multiplying the operation and the state results in a new state, here a + // single qutrit in superposition psi = dd->multiply(h_on_qutrit, zero_state); + + // An example of how to create a set of controls and add them together to + // create a more complex controlled operation + dd::Controls control{}; + const dd::Control c{0, 1}; + control.insert(c); + + // An example of a controlled qutrit X operation, controlled on the level 1 of + // the qubit + auto cex = dd->makeGateDD(dd::X3, numLines, control, 1); + + psi = dd->multiply(cex, psi); + + // The last lines retrieves the state vector and prints it + dd->printVector(psi); +} diff --git a/src/mqt/qudits/compiler/twodit/mapping_un_aware_transpilation/variational_twodit_compilation/__init__.py b/test/python/__init__.py old mode 100755 new mode 100644 similarity index 100% rename from src/mqt/qudits/compiler/twodit/mapping_un_aware_transpilation/variational_twodit_compilation/__init__.py rename to test/python/__init__.py diff --git a/src/mqt/qudits/compiler/twodit/mapping_un_aware_transpilation/variational_twodit_compilation/ansatz/__init__.py b/test/python/compiler/__init__.py old mode 100755 new mode 100644 similarity index 100% rename from src/mqt/qudits/compiler/twodit/mapping_un_aware_transpilation/variational_twodit_compilation/ansatz/__init__.py rename to test/python/compiler/__init__.py diff --git a/src/mqt/qudits/compiler/twodit/mapping_un_aware_transpilation/variational_twodit_compilation/opt/__init__.py b/test/python/compiler/onedit/__init__.py old mode 100755 new mode 100644 similarity index 100% rename from src/mqt/qudits/compiler/twodit/mapping_un_aware_transpilation/variational_twodit_compilation/opt/__init__.py rename to test/python/compiler/onedit/__init__.py diff --git a/src/mqt/qudits/compression/__init__.py b/test/python/compiler/twodit/__init__.py similarity index 100% rename from src/mqt/qudits/compression/__init__.py rename to test/python/compiler/twodit/__init__.py diff --git a/src/mqt/qudits/compression/qubit_mapping/__init__.py b/test/python/compiler/twodit/entangled_qr/__init__.py similarity index 100% rename from src/mqt/qudits/compression/qubit_mapping/__init__.py rename to test/python/compiler/twodit/entangled_qr/__init__.py diff --git a/tests/mqt_test/test_qudits/compiler/twodit/entangled_qr/test_entangled_qr.py b/test/python/compiler/twodit/entangled_qr/test_entangled_qr.py similarity index 100% rename from tests/mqt_test/test_qudits/compiler/twodit/entangled_qr/test_entangled_qr.py rename to test/python/compiler/twodit/entangled_qr/test_entangled_qr.py diff --git a/tests/mqt_test/test_qudits/compiler/twodit/entangled_qr/test_search.py b/test/python/compiler/twodit/entangled_qr/test_search.py similarity index 100% rename from tests/mqt_test/test_qudits/compiler/twodit/entangled_qr/test_search.py rename to test/python/compiler/twodit/entangled_qr/test_search.py diff --git a/src/mqt/qudits/core/structures/__init__.py b/test/python/qudits_circuits/__init__.py similarity index 100% rename from src/mqt/qudits/core/structures/__init__.py rename to test/python/qudits_circuits/__init__.py diff --git a/src/mqt/qudits/core/structures/ddpy/__init__.py b/test/python/qudits_circuits/components/__init__.py similarity index 100% rename from src/mqt/qudits/core/structures/ddpy/__init__.py rename to test/python/qudits_circuits/components/__init__.py diff --git a/src/mqt/qudits/core/structures/energy_level_graph/__init__.py b/test/python/qudits_circuits/components/instructions/__init__.py similarity index 100% rename from src/mqt/qudits/core/structures/energy_level_graph/__init__.py rename to test/python/qudits_circuits/components/instructions/__init__.py diff --git a/src/mqt/qudits/core/structures/trees/__init__.py b/test/python/qudits_circuits/components/instructions/gate_set/__init__.py similarity index 100% rename from src/mqt/qudits/core/structures/trees/__init__.py rename to test/python/qudits_circuits/components/instructions/gate_set/__init__.py diff --git a/tests/mqt_test/test_qudits/qudits_circuits/components/instructions/gate_set/test_csum.py b/test/python/qudits_circuits/components/instructions/gate_set/test_csum.py similarity index 100% rename from tests/mqt_test/test_qudits/qudits_circuits/components/instructions/gate_set/test_csum.py rename to test/python/qudits_circuits/components/instructions/gate_set/test_csum.py diff --git a/tests/mqt_test/test_qudits/qudits_circuits/components/instructions/gate_set/test_cx.py b/test/python/qudits_circuits/components/instructions/gate_set/test_cx.py similarity index 100% rename from tests/mqt_test/test_qudits/qudits_circuits/components/instructions/gate_set/test_cx.py rename to test/python/qudits_circuits/components/instructions/gate_set/test_cx.py diff --git a/tests/mqt_test/test_qudits/qudits_circuits/components/instructions/gate_set/test_s.py b/test/python/qudits_circuits/components/instructions/gate_set/test_s.py similarity index 100% rename from tests/mqt_test/test_qudits/qudits_circuits/components/instructions/gate_set/test_s.py rename to test/python/qudits_circuits/components/instructions/gate_set/test_s.py diff --git a/tests/mqt_test/test_qudits/qudits_circuits/components/instructions/gate_set/test_x.py b/test/python/qudits_circuits/components/instructions/gate_set/test_x.py similarity index 100% rename from tests/mqt_test/test_qudits/qudits_circuits/components/instructions/gate_set/test_x.py rename to test/python/qudits_circuits/components/instructions/gate_set/test_x.py diff --git a/tests/mqt_test/test_qudits/qudits_circuits/components/instructions/gate_set/test_z.py b/test/python/qudits_circuits/components/instructions/gate_set/test_z.py similarity index 100% rename from tests/mqt_test/test_qudits/qudits_circuits/components/instructions/gate_set/test_z.py rename to test/python/qudits_circuits/components/instructions/gate_set/test_z.py diff --git a/tests/mqt_test/test_qudits/test.py b/test/python/test.py similarity index 100% rename from tests/mqt_test/test_qudits/test.py rename to test/python/test.py diff --git a/test/test_pkg.cpp b/test/test_pkg.cpp new file mode 100644 index 0000000..1e50057 --- /dev/null +++ b/test/test_pkg.cpp @@ -0,0 +1,1060 @@ +#include "dd/MDDPackage.hpp" + +#include "gtest/gtest.h" + +using namespace dd::literals; + +TEST(DDPackageTest, RequestInvalidPackageSize) { + EXPECT_THROW(auto dd = std::make_unique( + std::numeric_limits::max() + 2, + std::vector( + 2, std::numeric_limits::max() + 2)), + std::invalid_argument); +} + +TEST(DDPackageTest, TrivialTest) { + auto dd = std::make_unique(2, std::vector{3, 2}); + EXPECT_EQ(dd->qregisters(), 2); + + dd::ComplexValue const h3plus = + dd::COMPLEX_SQRT3_3 * + dd::ComplexValue{std::cos(2. * dd::PI / 3.), std::sin(2. * dd::PI / 3.)}; + dd::ComplexValue const h3minus = + dd::COMPLEX_SQRT3_3 * + dd::ComplexValue{std::cos(4. * dd::PI / 3.), std::sin(4. * dd::PI / 3.)}; + + auto hGate0 = dd->makeGateDD(dd::H3(), 2, 0); + + ASSERT_TRUE(dd->getValueByPath(hGate0, "00") + .approximatelyEquals(dd::COMPLEX_SQRT3_3)); + ASSERT_TRUE(dd->getValueByPath(hGate0, "10") + .approximatelyEquals(dd::COMPLEX_SQRT3_3)); + ASSERT_TRUE(dd->getValueByPath(hGate0, "20") + .approximatelyEquals(dd::COMPLEX_SQRT3_3)); + ASSERT_TRUE(dd->getValueByPath(hGate0, "30") + .approximatelyEquals(dd::COMPLEX_SQRT3_3)); + + ASSERT_TRUE(dd->getValueByPath(hGate0, "40").approximatelyEquals(h3plus)); + + ASSERT_TRUE(dd->getValueByPath(hGate0, "50").approximatelyEquals(h3minus)); + + ASSERT_TRUE(dd->getValueByPath(hGate0, "60") + .approximatelyEquals(dd::COMPLEX_SQRT3_3)); + + ASSERT_TRUE(dd->getValueByPath(hGate0, "70").approximatelyEquals(h3minus)); + + ASSERT_TRUE(dd->getValueByPath(hGate0, "80").approximatelyEquals(h3plus)); + + ASSERT_TRUE( + dd->getValueByPath(hGate0, "01").approximatelyEquals(dd::COMPLEX_ZERO)); + ASSERT_TRUE( + dd->getValueByPath(hGate0, "11").approximatelyEquals(dd::COMPLEX_ZERO)); + ASSERT_TRUE( + dd->getValueByPath(hGate0, "21").approximatelyEquals(dd::COMPLEX_ZERO)); + ASSERT_TRUE( + dd->getValueByPath(hGate0, "31").approximatelyEquals(dd::COMPLEX_ZERO)); + ASSERT_TRUE( + dd->getValueByPath(hGate0, "41").approximatelyEquals(dd::COMPLEX_ZERO)); + ASSERT_TRUE( + dd->getValueByPath(hGate0, "51").approximatelyEquals(dd::COMPLEX_ZERO)); + ASSERT_TRUE( + dd->getValueByPath(hGate0, "61").approximatelyEquals(dd::COMPLEX_ZERO)); + ASSERT_TRUE( + dd->getValueByPath(hGate0, "71").approximatelyEquals(dd::COMPLEX_ZERO)); + ASSERT_TRUE( + dd->getValueByPath(hGate0, "81").approximatelyEquals(dd::COMPLEX_ZERO)); + + ASSERT_TRUE( + dd->getValueByPath(hGate0, "02").approximatelyEquals(dd::COMPLEX_ZERO)); + ASSERT_TRUE( + dd->getValueByPath(hGate0, "12").approximatelyEquals(dd::COMPLEX_ZERO)); + ASSERT_TRUE( + dd->getValueByPath(hGate0, "22").approximatelyEquals(dd::COMPLEX_ZERO)); + ASSERT_TRUE( + dd->getValueByPath(hGate0, "32").approximatelyEquals(dd::COMPLEX_ZERO)); + ASSERT_TRUE( + dd->getValueByPath(hGate0, "42").approximatelyEquals(dd::COMPLEX_ZERO)); + ASSERT_TRUE( + dd->getValueByPath(hGate0, "52").approximatelyEquals(dd::COMPLEX_ZERO)); + ASSERT_TRUE( + dd->getValueByPath(hGate0, "62").approximatelyEquals(dd::COMPLEX_ZERO)); + ASSERT_TRUE( + dd->getValueByPath(hGate0, "72").approximatelyEquals(dd::COMPLEX_ZERO)); + ASSERT_TRUE( + dd->getValueByPath(hGate0, "82").approximatelyEquals(dd::COMPLEX_ZERO)); + + ASSERT_TRUE(dd->getValueByPath(hGate0, "03") + .approximatelyEquals(dd::COMPLEX_SQRT3_3)); + ASSERT_TRUE(dd->getValueByPath(hGate0, "13") + .approximatelyEquals(dd::COMPLEX_SQRT3_3)); + ASSERT_TRUE(dd->getValueByPath(hGate0, "23") + .approximatelyEquals(dd::COMPLEX_SQRT3_3)); + ASSERT_TRUE(dd->getValueByPath(hGate0, "33") + .approximatelyEquals(dd::COMPLEX_SQRT3_3)); + + ASSERT_TRUE(dd->getValueByPath(hGate0, "43").approximatelyEquals(h3plus)); + ASSERT_TRUE(dd->getValueByPath(hGate0, "53").approximatelyEquals(h3minus)); + + ASSERT_TRUE(dd->getValueByPath(hGate0, "63") + .approximatelyEquals(dd::COMPLEX_SQRT3_3)); + + ASSERT_TRUE(dd->getValueByPath(hGate0, "73").approximatelyEquals(h3minus)); + ASSERT_TRUE(dd->getValueByPath(hGate0, "83").approximatelyEquals(h3plus)); + + // case with higher target + auto hGate1 = dd->makeGateDD(dd::Hmat, 2, 1); + ASSERT_EQ(dd->getValueByPath(hGate1, "00"), + (dd::ComplexValue{dd::SQRT2_2, 0})); + ASSERT_EQ(dd->getValueByPath(hGate1, "10"), (dd::ComplexValue{0, 0})); + ASSERT_EQ(dd->getValueByPath(hGate1, "20"), (dd::ComplexValue{0, 0})); + ASSERT_EQ(dd->getValueByPath(hGate1, "30"), (dd::ComplexValue{0, 0})); + ASSERT_EQ(dd->getValueByPath(hGate1, "40"), + (dd::ComplexValue{dd::SQRT2_2, 0})); + ASSERT_EQ(dd->getValueByPath(hGate1, "50"), (dd::ComplexValue{0, 0})); + ASSERT_EQ(dd->getValueByPath(hGate1, "60"), (dd::ComplexValue{0, 0})); + ASSERT_EQ(dd->getValueByPath(hGate1, "70"), (dd::ComplexValue{0, 0})); + ASSERT_EQ(dd->getValueByPath(hGate1, "80"), + (dd::ComplexValue{dd::SQRT2_2, 0})); + + ASSERT_EQ(dd->getValueByPath(hGate1, "01"), + (dd::ComplexValue{dd::SQRT2_2, 0})); + ASSERT_EQ(dd->getValueByPath(hGate1, "11"), (dd::ComplexValue{0, 0})); + ASSERT_EQ(dd->getValueByPath(hGate1, "21"), (dd::ComplexValue{0, 0})); + ASSERT_EQ(dd->getValueByPath(hGate1, "31"), (dd::ComplexValue{0, 0})); + ASSERT_EQ(dd->getValueByPath(hGate1, "41"), + (dd::ComplexValue{dd::SQRT2_2, 0})); + ASSERT_EQ(dd->getValueByPath(hGate1, "51"), (dd::ComplexValue{0, 0})); + ASSERT_EQ(dd->getValueByPath(hGate1, "61"), (dd::ComplexValue{0, 0})); + ASSERT_EQ(dd->getValueByPath(hGate1, "71"), (dd::ComplexValue{0, 0})); + ASSERT_EQ(dd->getValueByPath(hGate1, "81"), + (dd::ComplexValue{dd::SQRT2_2, 0})); + + ASSERT_EQ(dd->getValueByPath(hGate1, "02"), + (dd::ComplexValue{dd::SQRT2_2, 0})); + ASSERT_EQ(dd->getValueByPath(hGate1, "12"), (dd::ComplexValue{0, 0})); + ASSERT_EQ(dd->getValueByPath(hGate1, "22"), (dd::ComplexValue{0, 0})); + ASSERT_EQ(dd->getValueByPath(hGate1, "32"), (dd::ComplexValue{0, 0})); + ASSERT_EQ(dd->getValueByPath(hGate1, "42"), + (dd::ComplexValue{dd::SQRT2_2, 0})); + ASSERT_EQ(dd->getValueByPath(hGate1, "52"), (dd::ComplexValue{0, 0})); + ASSERT_EQ(dd->getValueByPath(hGate1, "62"), (dd::ComplexValue{0, 0})); + ASSERT_EQ(dd->getValueByPath(hGate1, "72"), (dd::ComplexValue{0, 0})); + ASSERT_EQ(dd->getValueByPath(hGate1, "82"), + (dd::ComplexValue{dd::SQRT2_2, 0})); + + ASSERT_EQ(dd->getValueByPath(hGate1, "03"), + (dd::ComplexValue{-dd::SQRT2_2, 0})); + ASSERT_EQ(dd->getValueByPath(hGate1, "13"), (dd::ComplexValue{0, 0})); + ASSERT_EQ(dd->getValueByPath(hGate1, "23"), (dd::ComplexValue{0, 0})); + ASSERT_EQ(dd->getValueByPath(hGate1, "33"), (dd::ComplexValue{0, 0})); + ASSERT_EQ(dd->getValueByPath(hGate1, "43"), + (dd::ComplexValue{-dd::SQRT2_2, 0})); + ASSERT_EQ(dd->getValueByPath(hGate1, "53"), (dd::ComplexValue{0, 0})); + ASSERT_EQ(dd->getValueByPath(hGate1, "63"), (dd::ComplexValue{0, 0})); + ASSERT_EQ(dd->getValueByPath(hGate1, "73"), (dd::ComplexValue{0, 0})); + ASSERT_EQ(dd->getValueByPath(hGate1, "83"), + (dd::ComplexValue{-dd::SQRT2_2, 0})); + + dd::Controls const controls1{{1, 1}}; + auto controlledH3 = dd->makeGateDD(dd::H3(), 2, controls1, 0); + + ASSERT_TRUE(dd->getValueByPath(controlledH3, "00") + .approximatelyEquals(dd::COMPLEX_ONE)); + ASSERT_TRUE(dd->getValueByPath(controlledH3, "10") + .approximatelyEquals(dd::COMPLEX_ZERO)); + ASSERT_TRUE(dd->getValueByPath(controlledH3, "20") + .approximatelyEquals(dd::COMPLEX_ZERO)); + ASSERT_TRUE(dd->getValueByPath(controlledH3, "30") + .approximatelyEquals(dd::COMPLEX_ZERO)); + ASSERT_TRUE(dd->getValueByPath(controlledH3, "40") + .approximatelyEquals(dd::COMPLEX_ONE)); + ASSERT_TRUE(dd->getValueByPath(controlledH3, "50") + .approximatelyEquals(dd::COMPLEX_ZERO)); + ASSERT_TRUE(dd->getValueByPath(controlledH3, "60") + .approximatelyEquals(dd::COMPLEX_ZERO)); + ASSERT_TRUE(dd->getValueByPath(controlledH3, "70") + .approximatelyEquals(dd::COMPLEX_ZERO)); + ASSERT_TRUE(dd->getValueByPath(controlledH3, "80") + .approximatelyEquals(dd::COMPLEX_ONE)); + + ASSERT_TRUE(dd->getValueByPath(controlledH3, "01") + .approximatelyEquals(dd::COMPLEX_ZERO)); + ASSERT_TRUE(dd->getValueByPath(controlledH3, "11") + .approximatelyEquals(dd::COMPLEX_ZERO)); + ASSERT_TRUE(dd->getValueByPath(controlledH3, "21") + .approximatelyEquals(dd::COMPLEX_ZERO)); + ASSERT_TRUE(dd->getValueByPath(controlledH3, "31") + .approximatelyEquals(dd::COMPLEX_ZERO)); + ASSERT_TRUE(dd->getValueByPath(controlledH3, "41") + .approximatelyEquals(dd::COMPLEX_ZERO)); + ASSERT_TRUE(dd->getValueByPath(controlledH3, "51") + .approximatelyEquals(dd::COMPLEX_ZERO)); + ASSERT_TRUE(dd->getValueByPath(controlledH3, "61") + .approximatelyEquals(dd::COMPLEX_ZERO)); + ASSERT_TRUE(dd->getValueByPath(controlledH3, "71") + .approximatelyEquals(dd::COMPLEX_ZERO)); + ASSERT_TRUE(dd->getValueByPath(controlledH3, "81") + .approximatelyEquals(dd::COMPLEX_ZERO)); + + ASSERT_TRUE(dd->getValueByPath(controlledH3, "02") + .approximatelyEquals(dd::COMPLEX_ZERO)); + ASSERT_TRUE(dd->getValueByPath(controlledH3, "12") + .approximatelyEquals(dd::COMPLEX_ZERO)); + ASSERT_TRUE(dd->getValueByPath(controlledH3, "22") + .approximatelyEquals(dd::COMPLEX_ZERO)); + ASSERT_TRUE(dd->getValueByPath(controlledH3, "32") + .approximatelyEquals(dd::COMPLEX_ZERO)); + ASSERT_TRUE(dd->getValueByPath(controlledH3, "42") + .approximatelyEquals(dd::COMPLEX_ZERO)); + ASSERT_TRUE(dd->getValueByPath(controlledH3, "52") + .approximatelyEquals(dd::COMPLEX_ZERO)); + ASSERT_TRUE(dd->getValueByPath(controlledH3, "62") + .approximatelyEquals(dd::COMPLEX_ZERO)); + ASSERT_TRUE(dd->getValueByPath(controlledH3, "72") + .approximatelyEquals(dd::COMPLEX_ZERO)); + ASSERT_TRUE(dd->getValueByPath(controlledH3, "82") + .approximatelyEquals(dd::COMPLEX_ZERO)); + + ASSERT_TRUE(dd->getValueByPath(controlledH3, "03") + .approximatelyEquals(dd::COMPLEX_SQRT3_3)); + ASSERT_TRUE(dd->getValueByPath(controlledH3, "13") + .approximatelyEquals(dd::COMPLEX_SQRT3_3)); + ASSERT_TRUE(dd->getValueByPath(controlledH3, "23") + .approximatelyEquals(dd::COMPLEX_SQRT3_3)); + ASSERT_TRUE(dd->getValueByPath(controlledH3, "33") + .approximatelyEquals(dd::COMPLEX_SQRT3_3)); + ASSERT_TRUE( + dd->getValueByPath(controlledH3, "43").approximatelyEquals(h3plus)); + ASSERT_TRUE( + dd->getValueByPath(controlledH3, "53").approximatelyEquals(h3minus)); + ASSERT_TRUE(dd->getValueByPath(controlledH3, "63") + .approximatelyEquals(dd::COMPLEX_SQRT3_3)); + ASSERT_TRUE( + dd->getValueByPath(controlledH3, "73").approximatelyEquals(h3minus)); + ASSERT_TRUE( + dd->getValueByPath(controlledH3, "83").approximatelyEquals(h3plus)); + + dd::Controls const controls0{{0, 1}}; + auto controlled0H2 = + dd->makeGateDD(dd::Hmat, 2, controls0, 1); + + ASSERT_TRUE(dd->getValueByPath(controlled0H2, "00") + .approximatelyEquals(dd::COMPLEX_ONE)); + ASSERT_TRUE(dd->getValueByPath(controlled0H2, "10") + .approximatelyEquals(dd::COMPLEX_ZERO)); + ASSERT_TRUE(dd->getValueByPath(controlled0H2, "20") + .approximatelyEquals(dd::COMPLEX_ZERO)); + ASSERT_TRUE(dd->getValueByPath(controlled0H2, "30") + .approximatelyEquals(dd::COMPLEX_ZERO)); + ASSERT_TRUE(dd->getValueByPath(controlled0H2, "40") + .approximatelyEquals(dd::ComplexValue{dd::SQRT2_2, 0})); + ASSERT_TRUE(dd->getValueByPath(controlled0H2, "50") + .approximatelyEquals(dd::COMPLEX_ZERO)); + ASSERT_TRUE(dd->getValueByPath(controlled0H2, "60") + .approximatelyEquals(dd::COMPLEX_ZERO)); + ASSERT_TRUE(dd->getValueByPath(controlled0H2, "70") + .approximatelyEquals(dd::COMPLEX_ZERO)); + ASSERT_TRUE(dd->getValueByPath(controlled0H2, "80") + .approximatelyEquals(dd::COMPLEX_ONE)); + + ASSERT_TRUE(dd->getValueByPath(controlled0H2, "01") + .approximatelyEquals(dd::COMPLEX_ZERO)); + ASSERT_TRUE(dd->getValueByPath(controlled0H2, "11") + .approximatelyEquals(dd::COMPLEX_ZERO)); + ASSERT_TRUE(dd->getValueByPath(controlled0H2, "21") + .approximatelyEquals(dd::COMPLEX_ZERO)); + ASSERT_TRUE(dd->getValueByPath(controlled0H2, "31") + .approximatelyEquals(dd::COMPLEX_ZERO)); + ASSERT_TRUE(dd->getValueByPath(controlled0H2, "41") + .approximatelyEquals(dd::ComplexValue{dd::SQRT2_2, 0})); + ASSERT_TRUE(dd->getValueByPath(controlled0H2, "51") + .approximatelyEquals(dd::COMPLEX_ZERO)); + ASSERT_TRUE(dd->getValueByPath(controlled0H2, "61") + .approximatelyEquals(dd::COMPLEX_ZERO)); + ASSERT_TRUE(dd->getValueByPath(controlled0H2, "71") + .approximatelyEquals(dd::COMPLEX_ZERO)); + ASSERT_TRUE(dd->getValueByPath(controlled0H2, "81") + .approximatelyEquals(dd::COMPLEX_ZERO)); + + ASSERT_TRUE(dd->getValueByPath(controlled0H2, "02") + .approximatelyEquals(dd::COMPLEX_ZERO)); + ASSERT_TRUE(dd->getValueByPath(controlled0H2, "12") + .approximatelyEquals(dd::COMPLEX_ZERO)); + ASSERT_TRUE(dd->getValueByPath(controlled0H2, "22") + .approximatelyEquals(dd::COMPLEX_ZERO)); + ASSERT_TRUE(dd->getValueByPath(controlled0H2, "32") + .approximatelyEquals(dd::COMPLEX_ZERO)); + ASSERT_TRUE(dd->getValueByPath(controlled0H2, "42") + .approximatelyEquals(dd::ComplexValue{dd::SQRT2_2, 0})); + ASSERT_TRUE(dd->getValueByPath(controlled0H2, "52") + .approximatelyEquals(dd::COMPLEX_ZERO)); + ASSERT_TRUE(dd->getValueByPath(controlled0H2, "62") + .approximatelyEquals(dd::COMPLEX_ZERO)); + ASSERT_TRUE(dd->getValueByPath(controlled0H2, "72") + .approximatelyEquals(dd::COMPLEX_ZERO)); + ASSERT_TRUE(dd->getValueByPath(controlled0H2, "82") + .approximatelyEquals(dd::COMPLEX_ZERO)); + + ASSERT_TRUE(dd->getValueByPath(controlled0H2, "03") + .approximatelyEquals(dd::COMPLEX_ONE)); + ASSERT_TRUE(dd->getValueByPath(controlled0H2, "13") + .approximatelyEquals(dd::COMPLEX_ZERO)); + ASSERT_TRUE(dd->getValueByPath(controlled0H2, "23") + .approximatelyEquals(dd::COMPLEX_ZERO)); + ASSERT_TRUE(dd->getValueByPath(controlled0H2, "33") + .approximatelyEquals(dd::COMPLEX_ZERO)); + ASSERT_TRUE(dd->getValueByPath(controlled0H2, "43") + .approximatelyEquals(dd::ComplexValue{-dd::SQRT2_2, 0})); + ASSERT_TRUE(dd->getValueByPath(controlled0H2, "53") + .approximatelyEquals(dd::COMPLEX_ZERO)); + ASSERT_TRUE(dd->getValueByPath(controlled0H2, "63") + .approximatelyEquals(dd::COMPLEX_ZERO)); + ASSERT_TRUE(dd->getValueByPath(controlled0H2, "73") + .approximatelyEquals(dd::COMPLEX_ZERO)); + ASSERT_TRUE(dd->getValueByPath(controlled0H2, "83") + .approximatelyEquals(dd::COMPLEX_ONE)); + + using namespace dd::literals; +} + +TEST(DDPackageTest, Identity) { + auto dd = + std::make_unique(4, std::vector{2, 3, 2, 4}); + EXPECT_EQ(dd->qregisters(), 4); + EXPECT_TRUE(dd->makeIdent(0).isOneTerminal()); + EXPECT_TRUE(dd->makeIdent(0, -1).isOneTerminal()); + + auto id3 = dd->makeIdent(3); + EXPECT_EQ(dd->makeIdent(0, 2), id3); + const auto& table = dd->getIdentityTable(); + EXPECT_NE(table[2].nextNode, nullptr); + + auto id2 = dd->makeIdent(0, 1); // should be found in idTable + EXPECT_EQ(dd->makeIdent(2), id2); + + auto id4 = dd->makeIdent(0, 3); // should use id3 and extend it + EXPECT_EQ(dd->makeIdent(0, 3), id4); + EXPECT_NE(table[3].nextNode, nullptr); + + auto idCached = dd->makeIdent(4); + EXPECT_EQ(id4, idCached); +} + +TEST(DDPackageTest, Multiplication) { + auto dd = + std::make_unique(3, std::vector{2, 2, 3}); + EXPECT_EQ(dd->qregisters(), 3); + + auto xGate = dd->makeGateDD(dd::Xmat, 3, 0); + auto x3dagGate = dd->makeGateDD(dd::X3dag, 3, 2); + auto x3Gate = dd->makeGateDD(dd::X3, 3, 2); + + dd::Controls const control{{0, 1}, {2, 1}}; + auto ctrlxGate = dd->makeGateDD(dd::Xmat, 3, control, 1); + + auto zeroState = dd->makeZeroState(3); + + auto evolution = dd->multiply(xGate, zeroState); + + evolution = dd->multiply(x3Gate, evolution); + + evolution = dd->multiply(ctrlxGate, evolution); + + evolution = dd->multiply(x3dagGate, evolution); + + auto basis110State = dd->makeBasisState(3, {1, 1, 0}); + + ASSERT_EQ(dd->fidelity(zeroState, evolution), 0.0); + ASSERT_EQ(dd->fidelity(evolution, basis110State), 1.0); +} + +TEST(DDPackageTest, ConjugateTranspose) { + const std::vector lines{3}; + const dd::QuantumRegisterCount numLines = 1U; + auto dd = std::make_unique(numLines, lines); + auto zeroState = dd->makeZeroState(numLines); + auto h = dd->makeGateDD(dd::H3(), numLines, 0); + auto psi = dd->multiply(h, zeroState); + psi = dd->multiply(dd->conjugateTranspose(h), psi); + ASSERT_EQ(dd->fidelity(psi, zeroState), 1.0); +} + +TEST(DDPackageTest, QutritBellState) { + auto dd = std::make_unique(2, std::vector{3, 3}); + EXPECT_EQ(dd->qregisters(), 2); + // Gates + auto h3Gate = dd->makeGateDD(dd::H3(), 2, 0); + + dd::Controls const control01{{0, 1}}; + auto ctrlx1Gate = dd->makeGateDD(dd::X3, 2, control01, 1); + + dd::Controls const control02{{0, 2}}; + auto ctrlx2Gate = dd->makeGateDD(dd::X3, 2, control02, 1); + + // Final Unitary + auto op = dd->multiply(ctrlx1Gate, h3Gate); + op = dd->multiply(ctrlx2Gate, op); + op = dd->multiply(ctrlx2Gate, op); + + // Evolution + auto evolution = dd->makeZeroState(2); + + evolution = dd->multiply(op, evolution); + + auto basis00State = dd->makeBasisState(2, {0, 0}); + auto basis11State = dd->makeBasisState(2, {1, 1}); + auto basis22State = dd->makeBasisState(2, {2, 2}); + + ASSERT_NEAR(dd->fidelity(basis00State, evolution), 0.3333333333333333, + dd::ComplexTable<>::tolerance()); + ASSERT_NEAR(dd->fidelity(basis11State, evolution), 0.3333333333333333, + dd::ComplexTable<>::tolerance()); + ASSERT_NEAR(dd->fidelity(basis22State, evolution), 0.3333333333333333, + dd::ComplexTable<>::tolerance()); + + // Evolution gate times state + + evolution = dd->makeZeroState(2); + + evolution = dd->multiply(h3Gate, evolution); + evolution = dd->multiply(ctrlx1Gate, evolution); + evolution = dd->multiply(ctrlx2Gate, evolution); + evolution = dd->multiply(ctrlx2Gate, evolution); + + ASSERT_NEAR(dd->fidelity(basis00State, evolution), 0.3333333333333333, + dd::ComplexTable<>::tolerance()); + ASSERT_NEAR(dd->fidelity(basis11State, evolution), 0.3333333333333333, + dd::ComplexTable<>::tolerance()); + ASSERT_NEAR(dd->fidelity(basis22State, evolution), 0.3333333333333333, + dd::ComplexTable<>::tolerance()); +} + +TEST(DDPackageTest, W3State) { + auto dd = + std::make_unique(3, std::vector{3, 3, 3}); + EXPECT_EQ(dd->qregisters(), 3); + + auto evolution = dd->makeZeroState(3); + + auto h3Gate = dd->makeGateDD(dd::H3(), 3, 1); + dd::Controls const control10{{1, 0}}; + auto xp10 = dd->makeGateDD(dd::X3, 3, control10, 0); + dd::Controls const control12{{1, 2}}; + auto xp12 = dd->makeGateDD(dd::X3, 3, control12, 2); + + auto csum21 = dd->csum(3, 2, 1, true); + + evolution = dd->multiply(h3Gate, evolution); + evolution = dd->multiply(xp10, evolution); + evolution = dd->multiply(xp12, evolution); + evolution = dd->multiply(csum21, evolution); + evolution = dd->multiply(csum21, evolution); +} +TEST(DDPackageTest, Mix23WState) { + auto dd = std::make_unique( + 6, std::vector{2, 3, 3, 2, 3, 3}); + EXPECT_EQ(dd->qregisters(), 6); + + auto evolution = dd->makeBasisState(6, {1, 0, 0, 0, 0, 0}); + + evolution = dd->spread2(6, std::vector{0, 3}, evolution); + evolution = + dd->spread3(6, std::vector{0, 1, 2}, evolution); + evolution = + dd->spread3(6, std::vector{3, 4, 5}, evolution); + + ASSERT_NEAR( + dd->fidelity(dd->makeBasisState(6, {1, 0, 0, 0, 0, 0}), evolution), + 1.0 / 6.0, dd::ComplexTable<>::tolerance()); + ASSERT_NEAR( + dd->fidelity(dd->makeBasisState(6, {0, 1, 0, 0, 0, 0}), evolution), + 1.0 / 6.0, dd::ComplexTable<>::tolerance()); + ASSERT_NEAR( + dd->fidelity(dd->makeBasisState(6, {0, 0, 1, 0, 0, 0}), evolution), + 1.0 / 6.0, dd::ComplexTable<>::tolerance()); + ASSERT_NEAR( + dd->fidelity(dd->makeBasisState(6, {0, 0, 0, 1, 0, 0}), evolution), + 1.0 / 6.0, dd::ComplexTable<>::tolerance()); + ASSERT_NEAR( + dd->fidelity(dd->makeBasisState(6, {0, 0, 0, 0, 1, 0}), evolution), + 1.0 / 6.0, dd::ComplexTable<>::tolerance()); + ASSERT_NEAR( + dd->fidelity(dd->makeBasisState(6, {0, 0, 0, 0, 0, 1}), evolution), + 1.0 / 6.0, dd::ComplexTable<>::tolerance()); +} + +TEST(DDPackageTest, W35State) { + auto dd = std::make_unique( + 15, + std::vector{3, 3, 3, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5}); + EXPECT_EQ(dd->qregisters(), 15); + + auto evolution = + dd->makeBasisState(15, {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}); + + evolution = + dd->spread3(15, std::vector{0, 1, 2}, evolution); + evolution = dd->spread5(15, std::vector{0, 3, 4, 5, 6}, + evolution); + evolution = dd->spread5(15, std::vector{1, 7, 8, 9, 10}, + evolution); + evolution = dd->spread5( + 15, std::vector{2, 11, 12, 13, 14}, evolution); + + ASSERT_NEAR(dd->fidelity(dd->makeBasisState(15, {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0}), + evolution), + 1.0 / 15.0, dd::ComplexTable<>::tolerance()); + ASSERT_NEAR(dd->fidelity(dd->makeBasisState(15, {0, 1, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0}), + evolution), + 1.0 / 15.0, dd::ComplexTable<>::tolerance()); + ASSERT_NEAR(dd->fidelity(dd->makeBasisState(15, {0, 0, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0}), + evolution), + 1.0 / 15.0, dd::ComplexTable<>::tolerance()); + ASSERT_NEAR(dd->fidelity(dd->makeBasisState(15, {0, 0, 0, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0}), + evolution), + 1.0 / 15.0, dd::ComplexTable<>::tolerance()); + ASSERT_NEAR(dd->fidelity(dd->makeBasisState(15, {0, 0, 0, 0, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0}), + evolution), + 1.0 / 15.0, dd::ComplexTable<>::tolerance()); + ASSERT_NEAR(dd->fidelity(dd->makeBasisState(15, {0, 0, 0, 0, 0, 1, 0, 0, 0, 0, + 0, 0, 0, 0, 0}), + evolution), + 1.0 / 15.0, dd::ComplexTable<>::tolerance()); + ASSERT_NEAR(dd->fidelity(dd->makeBasisState(15, {0, 0, 0, 0, 0, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 0}), + evolution), + 1.0 / 15.0, dd::ComplexTable<>::tolerance()); + ASSERT_NEAR(dd->fidelity(dd->makeBasisState(15, {0, 0, 0, 0, 0, 0, 0, 1, 0, 0, + 0, 0, 0, 0, 0}), + evolution), + 1.0 / 15.0, dd::ComplexTable<>::tolerance()); + ASSERT_NEAR(dd->fidelity(dd->makeBasisState(15, {0, 0, 0, 0, 0, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 0}), + evolution), + 1.0 / 15.0, dd::ComplexTable<>::tolerance()); + ASSERT_NEAR(dd->fidelity(dd->makeBasisState(15, {0, 0, 0, 0, 0, 0, 0, 0, 0, 1, + 0, 0, 0, 0, 0}), + evolution), + 1.0 / 15.0, dd::ComplexTable<>::tolerance()); + ASSERT_NEAR(dd->fidelity(dd->makeBasisState(15, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0}), + evolution), + 1.0 / 15.0, dd::ComplexTable<>::tolerance()); + ASSERT_NEAR(dd->fidelity(dd->makeBasisState(15, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 1, 0, 0, 0}), + evolution), + 1.0 / 15.0, dd::ComplexTable<>::tolerance()); + ASSERT_NEAR(dd->fidelity(dd->makeBasisState(15, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0}), + evolution), + 1.0 / 15.0, dd::ComplexTable<>::tolerance()); + ASSERT_NEAR(dd->fidelity(dd->makeBasisState(15, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, 0}), + evolution), + 1.0 / 15.0, dd::ComplexTable<>::tolerance()); + ASSERT_NEAR(dd->fidelity(dd->makeBasisState(15, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1}), + evolution), + 1.0 / 15.0, dd::ComplexTable<>::tolerance()); +} + +TEST(DDPackageTest, W5State) { + auto dd = std::make_unique( + 5, std::vector{5, 5, 5, 5, 5}); + + EXPECT_EQ(dd->qregisters(), 5); + + auto evolution = dd->makeZeroState(5); + + auto h5Gate = dd->makeGateDD(dd::H5(), 5, 1); + + dd::Controls const control10{{1, 0}}; + auto xp10 = dd->makeGateDD(dd::X5, 5, control10, 0); + dd::Controls const control12{{1, 2}}; + auto xp12 = dd->makeGateDD(dd::X5, 5, control12, 2); + dd::Controls const control13{{1, 3}}; + auto xp13 = dd->makeGateDD(dd::X5, 5, control13, 3); + dd::Controls const control14{{1, 4}}; + auto xp14 = dd->makeGateDD(dd::X5, 5, control14, 4); + + auto csum21 = dd->csum(5, 2, 1, true); + auto csum31 = dd->csum(5, 3, 1, true); + auto csum41 = dd->csum(5, 4, 1, true); + + evolution = dd->multiply(h5Gate, evolution); + evolution = dd->multiply(xp10, evolution); + evolution = dd->multiply(xp12, evolution); + evolution = dd->multiply(csum21, evolution); + evolution = dd->multiply(csum21, evolution); + evolution = dd->multiply(xp13, evolution); + evolution = dd->multiply(csum31, evolution); + evolution = dd->multiply(csum31, evolution); + evolution = dd->multiply(csum31, evolution); + evolution = dd->multiply(xp14, evolution); + evolution = dd->multiply(csum41, evolution); + evolution = dd->multiply(csum41, evolution); + evolution = dd->multiply(csum41, evolution); + evolution = dd->multiply(csum41, evolution); +} + +TEST(DDPackageTest, FullMixWState) { + std::vector orderOfLayers{2, 3, 2, 3, 3}; + std::vector lines{}; + dd::QuantumRegisterCount numLines = 0U; + std::map> application; + std::vector> indexes{{}}; + std::size_t sizeTracker = 0; + + bool initial = true; + for (auto i = 0U; i < orderOfLayers.size(); i++) { + // Update cardinality of each line, put them in order + // Update the application indexes + if (initial) { + for (auto j = 0U; j < orderOfLayers.at(i); j++) { + lines.push_back(orderOfLayers.at(i)); + indexes.at(0).push_back(static_cast(j)); + numLines++; + } + application[i] = std::vector{sizeTracker}; + application[i + 1] = {}; + sizeTracker++; + for (auto j = 0U; j < indexes.at(0).size(); j++) { + indexes.push_back(std::vector{ + indexes.at(0).at(static_cast(j))}); + application[i + 1].push_back(sizeTracker); + sizeTracker++; + } + + initial = false; + } else { + auto tempLine = lines.size(); + auto counter = 0U; + for (auto k = 0U; k < tempLine; k++) { + auto adder = k + counter; + for (auto j = 1U; j < orderOfLayers.at(i); j++) { + lines.insert( + lines.begin() + adder + j, + orderOfLayers.at(i)); // check if eventually gets out of range + numLines++; + counter++; + } + } + for (auto& index : indexes) { + for (auto& f : index) { + f = static_cast( + f + + f * (static_cast(orderOfLayers.at(i) - 1))); + } + } + std::vector toAdd{}; + for (auto& index : indexes) { + if (index.size() == 1) { + toAdd.push_back(index.at(0)); + for (auto j = 1U; j < orderOfLayers.at(i); j++) { + index.push_back(static_cast( + index.at(0) + static_cast(j))); + toAdd.push_back(static_cast( + index.at(0) + static_cast(j))); + } + } + } + if (i < orderOfLayers.size() - 1) { + for (const dd::QuantumRegister& l : toAdd) { + indexes.push_back(std::vector{l}); + application[i + 1].push_back(sizeTracker); + sizeTracker++; + } + application[i + 2] = {}; + } + } + } + + auto dd = std::make_unique(numLines, lines); + EXPECT_EQ(dd->qregisters(), numLines); + + std::vector initState(numLines, 0); + initState.at(0) = 1; + auto evolution = dd->makeBasisState(numLines, initState); + + for (auto i = 0U; i < orderOfLayers.size(); i++) { + if (orderOfLayers.at(i) == 2) { + for (auto g = 0U; g < application[i].size(); g++) { + std::vector const inputLines = + indexes.at(application[i].at(g)); + evolution = dd->spread2(numLines, inputLines, evolution); + } + } else if (orderOfLayers.at(i) == 3) { + for (auto g = 0U; g < application[i].size(); g++) { + evolution = dd->spread3( + numLines, + reinterpret_cast&>( + indexes.at(application[i].at(g))), + evolution); + } + } else if (orderOfLayers.at(i) == 5) { + for (auto g = 0U; g < application[i].size(); g++) { + evolution = dd->spread5( + numLines, + reinterpret_cast&>( + indexes.at(application[i].at(g))), + evolution); + } + } + } + + for (auto h = 0U; h < numLines; h++) { + std::vector checkState(numLines, 0); + checkState.at(h) = 1; + ASSERT_NEAR( + dd->fidelity(dd->makeBasisState(numLines, checkState), evolution), + 1.0 / numLines, dd::ComplexTable<>::tolerance()); + } +} + +TEST(DDPackageTest, GHZQutritState) { + auto dd = + std::make_unique(3, std::vector{3, 3, 3}); + EXPECT_EQ(dd->qregisters(), 3); + // Gates + auto h3Gate = dd->makeGateDD(dd::H3(), 3, 0); + + dd::Controls const control01{{0, 1}}; + auto cX011 = dd->makeGateDD(dd::X3, 3, control01, 1); + dd::Controls const control02{{0, 2}}; + auto cX021 = dd->makeGateDD(dd::X3dag, 3, control02, 1); + + dd::Controls const control011{{0, 1}, {1, 1}}; + auto cXc01l1t2 = dd->makeGateDD(dd::X3, 3, control011, 2); + dd::Controls const control012{{0, 2}, {1, 2}}; + auto cX0122 = dd->makeGateDD(dd::X3dag, 3, control012, 2); + + // auto testvec = dd->getVectorizedMatrix(cX0122); + + // Evolution + auto evolution = dd->makeZeroState(3); + + evolution = dd->multiply(h3Gate, evolution); + + evolution = dd->multiply(cX011, evolution); + evolution = dd->multiply(cX021, evolution); + + evolution = dd->multiply(cXc01l1t2, evolution); + + evolution = dd->multiply(cX0122, evolution); + + auto basis00State = dd->makeBasisState(3, {0, 0, 0}); + auto basis11State = dd->makeBasisState(3, {1, 1, 1}); + auto basis22State = dd->makeBasisState(3, {2, 2, 2}); + + ASSERT_NEAR(dd->fidelity(basis00State, evolution), 0.3333333333333333, + dd::ComplexTable<>::tolerance()); + ASSERT_NEAR(dd->fidelity(basis11State, evolution), 0.3333333333333333, + dd::ComplexTable<>::tolerance()); + ASSERT_NEAR(dd->fidelity(basis22State, evolution), 0.3333333333333333, + dd::ComplexTable<>::tolerance()); +} + +TEST(DDPackageTest, GHZQutritStateScaled) { + for (dd::QuantumRegisterCount i = 120U; i < 129U; i++) { + const std::vector init(i, 3); + auto dd = std::make_unique(i, init); + EXPECT_EQ(dd->qregisters(), i); + + // Gates + auto h3Gate = dd->makeGateDD(dd::H3(), i, 0); + std::vector gates = {}; + + for (int target = 1; target < i; target++) { + dd::Controls target1{}; + dd::Controls target2{}; + + for (int control = 0; control < target; control++) { + const dd::Control c1{static_cast(control), 1}; + const dd::Control c2{static_cast(control), 2}; + target1.insert(c1); + target2.insert(c2); + } + + gates.push_back(dd->makeGateDD( + dd::X3, i, target1, static_cast(target))); + gates.push_back(dd->makeGateDD( + dd::X3dag, i, target2, static_cast(target))); + } + + auto evolution = dd->makeZeroState(i); + evolution = dd->multiply(h3Gate, evolution); + + for (auto& gate : gates) { + evolution = dd->multiply(gate, evolution); + } + if (dd->qregisters() < 10) { + std::cout << "\n" << std::endl; + dd->printVector(evolution); + } + + auto basis00State = dd->makeBasisState(i, std::vector(i, 0)); + auto basis11State = dd->makeBasisState(i, std::vector(i, 1)); + auto basis22State = dd->makeBasisState(i, std::vector(i, 2)); + + ASSERT_NEAR(dd->fidelity(basis00State, evolution), 0.3333333333333333, + dd::ComplexTable<>::tolerance()); + ASSERT_NEAR(dd->fidelity(basis11State, evolution), 0.3333333333333333, + dd::ComplexTable<>::tolerance()); + ASSERT_NEAR(dd->fidelity(basis22State, evolution), 0.3333333333333333, + dd::ComplexTable<>::tolerance()); + } +} + +TEST(DDPackageTest, RandomCircuits) { + const dd::QuantumRegisterCount width = 3; + const std::size_t depth = 1000; + const std::size_t maxD = 5; + + std::mt19937 gen(12345); // NOLINT(cert-msc51-cpp): seed the generator with + // fixed value for reproducibility + + std::vector particles = {}; + + std::uniform_int_distribution dimdistr(2, maxD); + particles.reserve(width); + for (auto i = 0; i < width; i++) { + particles.emplace_back(dimdistr(gen)); + } + + auto dd = std::make_unique(width, particles); + + EXPECT_EQ(dd->qregisters(), width); + + auto evolution = dd->makeZeroState(width); + + std::cout << "\n" + << "STARTED" << "\n" + << std::endl; + + std::uniform_int_distribution<> pickbool(0, 1); + std::uniform_int_distribution pickcontrols(1, width - 1); + std::uniform_real_distribution<> angles(0.0, 2. * dd::PI); + + for (std::size_t timeStep = 0; timeStep < depth; timeStep++) { + for (std::size_t line = 0; line < width; line++) { + // chose if local gate or entangling gate + auto randomChoice = pickbool(gen); + + if (randomChoice == 0) { // local op + + auto localChoice = pickbool(gen); + + if (localChoice == 0) { // hadamard + std::cout << "\n" + << "hadamard" << "\n" + << std::endl; + if (particles.at(line) == 2) { + auto chosenGate = dd->makeGateDD( + dd::H(), width, static_cast(line)); + evolution = dd->multiply(chosenGate, evolution); + } else if (particles.at(line) == 3) { + auto chosenGate = dd->makeGateDD( + dd::H3(), width, static_cast(line)); + evolution = dd->multiply(chosenGate, evolution); + } else if (particles.at(line) == 4) { + auto chosenGate = dd->makeGateDD( + dd::H4(), width, static_cast(line)); + evolution = dd->multiply(chosenGate, evolution); + } else if (particles.at(line) == 5) { + auto chosenGate = dd->makeGateDD( + dd::H5(), width, static_cast(line)); + evolution = dd->multiply(chosenGate, evolution); + } + } else { // givens + std::cout << "\n" + << "givens" << "\n" + << std::endl; + if (particles.at(line) == 2) { + double const theta = 0.; + double const phi = 0.; + auto chosenGate = dd->makeGateDD( + dd::RXY(theta, phi), width, + static_cast(line)); + evolution = dd->multiply(chosenGate, evolution); + } else if (particles.at(line) == 3) { + double const theta = angles(gen); + double const phi = angles(gen); + std::uniform_int_distribution picklevel(0, 2); + + auto levelA = picklevel(gen); + auto levelB = (levelA + 1) % 3; + if (levelA > levelB) { + auto temp = levelA; + levelA = levelB; + levelB = temp; + } + + auto chosenGate = dd->makeGateDD( + dd::RXY3(theta, phi, levelA, levelB), width, + static_cast(line)); + evolution = dd->multiply(chosenGate, evolution); + } else if (particles.at(line) == 4) { + double const theta = angles(gen); + double const phi = angles(gen); + std::uniform_int_distribution picklevel(0, 3); + + auto levelA = picklevel(gen); + auto levelB = (levelA + 1) % 4; + if (levelA > levelB) { + auto temp = levelA; + levelA = levelB; + levelB = temp; + } + + auto chosenGate = dd->makeGateDD( + dd::RXY4(theta, phi, levelA, levelB), width, + static_cast(line)); + evolution = dd->multiply(chosenGate, evolution); + } else if (particles.at(line) == 5) { + double const theta = angles(gen); + double const phi = angles(gen); + std::uniform_int_distribution picklevel(0, 4); + + auto levelA = picklevel(gen); + auto levelB = (levelA + 1) % 5; + if (levelA > levelB) { + auto temp = levelA; + levelA = levelB; + levelB = temp; + } + + auto chosenGate = dd->makeGateDD( + dd::RXY5(theta, phi, levelA, levelB), width, + static_cast(line)); + evolution = dd->multiply(chosenGate, evolution); + } + } + } else { // entangling + std::cout << "\n" + << "entangling" << "\n" + << std::endl; + + auto entChoice = pickbool(gen); + auto numberOfControls = pickcontrols(gen); + + std::vector controlLines; + + for (auto i = 0U; i < width; i++) { + if (i != line) { + controlLines.push_back(i); + } + } + + std::shuffle(begin(controlLines), end(controlLines), gen); + std::vector controlParticles( + controlLines.begin(), controlLines.begin() + numberOfControls); + std::sort(controlParticles.begin(), controlParticles.end()); + + dd::Controls control{}; + for (std::size_t i = 0; i < numberOfControls; i++) { + std::uniform_int_distribution picklevel( + 0, particles.at(controlParticles.at(i)) - 1); + auto level = picklevel(gen); + + const dd::Control c{ + static_cast(controlParticles.at(i)), + static_cast(level)}; + control.insert(c); + } + + if (entChoice == 0) { // CEX based + // selection of controls + std::cout << "\n" + << "CEX" << "\n" + << std::endl; + if (particles.at(line) == 2) { + double const theta = angles(gen); + double const phi = angles(gen); + auto chosenGate = dd->makeGateDD( + dd::RXY(theta, phi), width, control, + static_cast(line)); + evolution = dd->multiply(chosenGate, evolution); + } else if (particles.at(line) == 3) { + std::uniform_int_distribution picklevel(0, 2); + + double const theta = angles(gen); + double const phi = angles(gen); + auto levelA = picklevel(gen); + auto levelB = (levelA + 1) % 3; + if (levelA > levelB) { + auto temp = levelA; + levelA = levelB; + levelB = temp; + } + + auto chosenGate = dd->makeGateDD( + dd::RXY3(theta, phi, levelA, levelB), width, control, + static_cast(line)); + evolution = dd->multiply(chosenGate, evolution); + } else if (particles.at(line) == 4) { + std::uniform_int_distribution picklevel(0, 3); + + double const theta = angles(gen); + double const phi = angles(gen); + auto levelA = picklevel(gen); + auto levelB = (levelA + 1) % 4; + if (levelA > levelB) { + auto temp = levelA; + levelA = levelB; + levelB = temp; + } + + auto chosenGate = dd->makeGateDD( + dd::RXY4(theta, phi, levelA, levelB), width, control, + static_cast(line)); + evolution = dd->multiply(chosenGate, evolution); + } else if (particles.at(line) == 5) { + std::uniform_int_distribution picklevel(0, 4); + + double const theta = angles(gen); + double const phi = angles(gen); + auto levelA = picklevel(gen); + auto levelB = (levelA + 1) % 5; + if (levelA > levelB) { + auto temp = levelA; + levelA = levelB; + levelB = temp; + } + + auto chosenGate = dd->makeGateDD( + dd::RXY5(theta, phi, levelA, levelB), width, control, + static_cast(line)); + evolution = dd->multiply(chosenGate, evolution); + } + } else { // Controlled clifford + std::cout << "\n" + << "clifford" << "\n" + << std::endl; + if (particles.at(line) == 2) { + auto chosenGate = dd->makeGateDD( + dd::Xmat, width, control, + static_cast(line)); + evolution = dd->multiply(chosenGate, evolution); + } else if (particles.at(line) == 3) { + auto chosenGate = dd->makeGateDD( + dd::X3, width, control, static_cast(line)); + evolution = dd->multiply(chosenGate, evolution); + } else if (particles.at(line) == 4) { + auto chosenGate = dd->makeGateDD( + dd::X4, width, control, static_cast(line)); + evolution = dd->multiply(chosenGate, evolution); + } else if (particles.at(line) == 5) { + auto chosenGate = dd->makeGateDD( + dd::X5, width, control, static_cast(line)); + evolution = dd->multiply(chosenGate, evolution); + } + } + } + } + } +} diff --git a/tests/__init__.py b/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/mqt_test/__init__.py b/tests/mqt_test/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/mqt_test/test_qudits/__init__.py b/tests/mqt_test/test_qudits/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/mqt_test/test_qudits/compiler/__init__.py b/tests/mqt_test/test_qudits/compiler/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/mqt_test/test_qudits/compiler/onedit/__init__.py b/tests/mqt_test/test_qudits/compiler/onedit/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/mqt_test/test_qudits/compiler/twodit/__init__.py b/tests/mqt_test/test_qudits/compiler/twodit/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/mqt_test/test_qudits/compiler/twodit/entangled_qr/__init__.py b/tests/mqt_test/test_qudits/compiler/twodit/entangled_qr/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/mqt_test/test_qudits/qudits_circuits/__init__.py b/tests/mqt_test/test_qudits/qudits_circuits/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/mqt_test/test_qudits/qudits_circuits/components/__init__.py b/tests/mqt_test/test_qudits/qudits_circuits/components/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/mqt_test/test_qudits/qudits_circuits/components/instructions/__init__.py b/tests/mqt_test/test_qudits/qudits_circuits/components/instructions/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/mqt_test/test_qudits/qudits_circuits/components/instructions/gate_set/__init__.py b/tests/mqt_test/test_qudits/qudits_circuits/components/instructions/gate_set/__init__.py deleted file mode 100644 index e69de29..0000000 From b1f0a5151c277bfac3d57b762d7cd54b539b5d4f Mon Sep 17 00:00:00 2001 From: burgholzer Date: Fri, 19 Apr 2024 21:32:37 +0200 Subject: [PATCH 10/22] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20update=20pre-commit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .pre-commit-config.yaml | 94 +++++++++++++++++++++++++++++++---------- 1 file changed, 71 insertions(+), 23 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f09d421..0bca55c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,28 +10,43 @@ ci: autoupdate_commit_msg: "⬆️🪝 update pre-commit hooks" autofix_commit_msg: "🎨 pre-commit fixes" + skip: [mypy] repos: # Standard hooks - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + rev: v4.6.0 hooks: - id: check-added-large-files - id: check-case-conflict - id: check-docstring-first - id: check-merge-conflict + - id: check-symlinks - id: check-toml - id: check-yaml - id: debug-statements - id: end-of-file-fixer - id: mixed-line-ending + - id: requirements-txt-fixer - id: trailing-whitespace # Clean jupyter notebooks - repo: https://github.com/srstevenson/nb-clean - rev: "2.4.0" + rev: 3.2.0 hooks: - id: nb-clean + args: + - --remove-empty-cells + - --preserve-cell-metadata + - raw_mimetype + - -- + + # Handling unwanted unicode characters + - repo: https://github.com/sirosen/texthooks + rev: 0.6.6 + hooks: + - id: fix-ligatures + - id: fix-smartquotes # Check for common mistakes - repo: https://github.com/pre-commit/pygrep-hooks @@ -41,43 +56,76 @@ repos: - id: rst-directive-colons - id: rst-inline-touching-normal - # Handling unwanted unicode characters - - repo: https://github.com/sirosen/texthooks - rev: 0.5.0 - hooks: - - id: fix-ligatures - - id: fix-smartquotes - - - repo: https://github.com/charliermarsh/ruff-pre-commit - rev: v0.0.275 + # Python linting using ruff + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.3.5 hooks: - id: ruff - args: ["--fix"] + args: ["--fix", "--show-fixes"] + types_or: [python, pyi, jupyter] + - id: ruff-format + types_or: [python, pyi, jupyter] - # Run code formatting with Black - - repo: https://github.com/psf/black - rev: 23.3.0 # Keep in sync with blacken-docs + # Static type checking using mypy + - repo: https://github.com/pre-commit/mirrors-mypy + rev: v1.9.0 hooks: - - id: black-jupyter + - id: mypy + files: ^(src/mqt|test/python) + args: [] + additional_dependencies: + - pytest + - pandas-stubs # Also run Black on examples in the documentation - - repo: https://github.com/asottile/blacken-docs - rev: 1.14.0 + - repo: https://github.com/adamchainz/blacken-docs + rev: 1.16.0 hooks: - id: blacken-docs - additional_dependencies: - - black==23.3.0 # qudits2keep in sync with black hook + additional_dependencies: [black==23.*] + + # Clang-format the C++ part of the code base automatically + - repo: https://github.com/pre-commit/mirrors-clang-format + rev: v18.1.3 + hooks: + - id: clang-format + types_or: [c++, c, cuda] + + # CMake format and lint the CMakeLists.txt files + - repo: https://github.com/cheshirekow/cmake-format-precommit + rev: v0.6.13 + hooks: + - id: cmake-format + additional_dependencies: [pyyaml] + types: [file] + files: (\.cmake|CMakeLists.txt)(.in)?$ # Format configuration files with prettier - repo: https://github.com/pre-commit/mirrors-prettier - rev: "v3.0.0-alpha.9-for-vscode" + rev: v4.0.0-alpha.8 hooks: - id: prettier types_or: [yaml, markdown, html, css, scss, javascript, json] # Check for spelling - repo: https://github.com/codespell-project/codespell - rev: v2.2.5 + rev: v2.2.6 hooks: - id: codespell - args: ["-L", "wille,braket,coo", "--skip", "*.ipynb"] + args: ["-L", "wille,linz", "--skip", "*.ipynb"] + + # Catch common capitalization mistakes + - repo: local + hooks: + - id: disallow-caps + name: Disallow improper capitalization + language: pygrep + entry: PyBind|Numpy|Cmake|CCache|Github|PyTest|Mqt|Tum + exclude: .pre-commit-config.yaml + + # Check best practices for scientific Python code + - repo: https://github.com/scientific-python/cookie + rev: 2024.03.10 + hooks: + - id: sp-repo-review + additional_dependencies: ["repo-review[cli]"] From 3744adbc5d54fc8fa340fa124df2c51dd8ec88e6 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Fri, 19 Apr 2024 21:32:53 +0200 Subject: [PATCH 11/22] =?UTF-8?q?=F0=9F=94=A7=20pyproject.toml=20configura?= =?UTF-8?q?tion?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 345 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 226 insertions(+), 119 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 5d74e31..355fa7d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,154 +1,261 @@ [build-system] -requires = [ - "setuptools>=61", - "setuptools_scm[toml]>=7" -] -build-backend = "setuptools.build_meta" +requires = ["scikit-build-core>=0.8.1", "setuptools-scm>=7", "pybind11>=2.12"] +build-backend = "scikit_build_core.build" [project] -name = "mqt.qudit" -description = "MQT Qudits: A framework for mixed-dimensional qudit quantum computing" +name = "mqt.qudits" +description = "A framework for mixed-dimensional qudit quantum computing" readme = "README.md" authors = [ { name = "Kevin Mato", email = "kevin.mato@tum.de" }, + { name = "Lukas Burgholzer", email = "lukas.burgholzer@tum.de"}, ] -keywords = ["MQT", "quantum computing", "design automation", "quantum circuit", "qudits"] +keywords = ["MQT", "quantum-computing", "design-automation", "qudits"] license = { file = "LICENSE" } classifiers = [ + "Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)", + "Intended Audience :: Science/Research", + "Natural Language :: English", + "Operating System :: POSIX :: Linux", + "Operating System :: MacOS", + "Operating System :: Microsoft :: Windows", + "License :: OSI Approved :: MIT License", + "Programming Language :: C++", + "Programming Language :: Python", "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", - "Programming Language :: C++", - "License :: OSI Approved :: MIT License", - "Operating System :: MacOS", - "Operating System :: POSIX :: Linux", - "Intended Audience :: Science/Research", - "Natural Language :: English", - "Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)", + "Programming Language :: Python :: 3.12", + "Development Status :: 4 - Beta", + "Typing :: Typed", ] requires-python = ">=3.8" dependencies = [ - "numpy>=1.25", - "mqt.misim @ git+https://github.com/KevinMTO/misim-binding.git", - "asttokens==2.4.1", - "comm==0.2.2", - "contourpy==1.2.0", - "cycler==0.12.1", - "debugpy==1.8.1", - "decorator==5.1.1", - "exceptiongroup==1.2.0", - "executing==2.0.1", - "fonttools==4.45.0", - "graphviz==0.20.1", - "h5py==3.10.0", - "jedi==0.19.1", - "kiwisolver==1.4.5", - "matplotlib==3.8.2", - "matplotlib-inline==0.1.6", - "mpmath==1.3.0", - "nest-asyncio==1.6.0", - "networkx==3.2.1", - "numexpr==2.8.8", - "opt-einsum==3.3.0", - "packaging==23.2", - "parso==0.8.3", - "pexpect==4.9.0", - "Pillow==10.1.0", - "platformdirs==4.2.0", - "prompt-toolkit==3.0.43", - "psutil==5.9.8", - "ptyprocess==0.7.0", - "pure-eval==0.2.2", - "Pygments==2.17.2", - "pyparsing==3.1.1", - "python-dateutil==2.8.2", - "pyzmq==25.1.2", - "scipy==1.11.3", - "six==1.16.0", - "stack-data==0.6.3", - "sympy==1.12", - "tensornetwork==0.4.6", - "tornado==6.4", - "traitlets==5.14.2", - "wcwidth==0.2.13" + "numpy>=1.24", + "networkx>=3.0", + "scipy>=1.10", + "h5py>=3.3", + "tensornetwork>=0.4", + "matplotlib>=3.7", ] - dynamic = ["version"] [project.optional-dependencies] - +test = ["pytest>=7.0"] +coverage = ["mqt.qudits[test]", "pytest-cov>=4"] +docs = [ + "furo>=2023.08.17", + "sphinx", + "myst_nb>=1.1.0", + "setuptools-scm>=7", + "sphinx-copybutton", + "sphinx_design", + "sphinx-inline-tabs", + "sphinxext-opengraph", + "sphinxcontrib-bibtex>=2.4.2", + "sphinxcontrib-svg2pdfconverter", + "pybtex>=0.24", + "ipython", + "ipykernel", + "sphinx-autoapi", +] +dev = ["mqt.qudits[coverage,docs]"] [project.urls] Homepage = "https://github.com/cda-tum/mqt-qudits" +Documentation = "https://mqt.readthedocs.io/projects/qudits" +Issues = "https://github.com/cda-tum/mqt-qudits/issues" Discussions = "https://github.com/cda-tum/mqt-qudits/discussions" -Research = "https://www.cda.cit.tum.de/research/" + +[tool.scikit-build] +# Protect the configuration against future changes in scikit-build-core +minimum-version = "0.8.1" + +# Set the wheel install directory +wheel.install-dir = "mqt/qudits" + +# Set required CMake and Ninja versions +cmake.version = ">=3.19" +ninja.version = ">=1.10" + +# Setuptools-style build caching in a local directory +build-dir = "build/{wheel_tag}" + +# Explicitly set the package directory +wheel.packages = ["src/mqt"] + +metadata.version.provider = "scikit_build_core.metadata.setuptools_scm" +sdist.include = ["src/mqt/qudits/_version.py"] +sdist.exclude = [ + "**/.github", + "**/doc", + "**/docs", + "**/meta", + "**/plots", + "**/test", + "**/tests", +] + +[tool.check-sdist] +sdist-only = ["src/mqt/core/_version.py"] +git-only = [ + "docs/*", + "extern/*", + "test/*", +] + +[tool.scikit-build.cmake.define] +BUILD_MQT_MISIM_TESTS = "OFF" +BUILD_MQT_MISIM_BINDINGS = "ON" + + +[tool.setuptools_scm] +write_to = "src/mqt/qudits/_version.py" + + +[tool.pytest.ini_options] +minversion = "7.0" +addopts = ["-ra", "--strict-markers", "--strict-config"] +xfail_strict = true +filterwarnings = [ + "error", + 'ignore:.*datetime\.datetime\.utcfromtimestamp.*:DeprecationWarning:', +] +log_cli_level = "info" +testpaths = ["test/python"] + +[tool.coverage] +run.source = ["mqt.qudits"] +report.exclude_also = [ + '\.\.\.', + 'if TYPE_CHECKING:', + 'raise AssertionError', + 'raise NotImplementedError', +] + + +[tool.mypy] +files = ["src/mqt", "test/python"] +mypy_path = ["$MYPY_CONFIG_FILE_DIR/src"] +python_version = "3.8" +warn_unused_configs = true +enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"] +strict = true +disallow_untyped_defs = false +explicit_package_bases = true +warn_unreachable = true [tool.ruff] -select = [ - "E", "F", "W", # flake8 - "A", # flake8-builtins - "ARG", # flake8-unused-arguments - "B", "B904", # flake8-bugbear - "C4", # flake8-comprehensions - "EM", # flake8-errmsg - "EXE", # flake8-executable - "I", # isort - "ICN", # flake8-import-conventions - "ISC", # flake8-implicit-str-concat - "N", # flake8-naming - "PGH", # pygrep-hooks - "PIE", # flake8-pie - "PL", # pylint - "PT", # flake8-pytest-style - "PTH", # flake8-use-pathlib - "PYI", # flake8-pyi - "Q", # flake8-quotes - "RET", # flake8-return - "RSE", # flake8-raise - "RUF", # Ruff-specific - "SIM", # flake8-simplify - "TCH", # flake8-type-checking - "TID", # flake8-tidy-imports - "TRY", # tryceratops - "UP", # pyupgrade - "YTT", # flake8-2020 +line-length = 120 +extend-include = ["*.ipynb"] +src = ["src"] +preview = true +unsafe-fixes = true + +[tool.ruff.lint] +extend-select = [ + "A", # flake8-builtins + "ANN", # flake8-annotations + "ARG", # flake8-unused-arguments + "ASYNC", # flake8-async + "B", "B904", # flake8-bugbear + "C4", # flake8-comprehensions +# "D", # pydocstyle + "EM", # flake8-errmsg + "EXE", # flake8-executable + "FA", # flake8-future-annotations + "FLY", # flynt + "FURB", # refurb + "I", # isort + "ICN", # flake8-import-conventions + "ISC", # flake8-implicit-str-concat + "LOG", # flake8-logging-format + "N", # flake8-naming + "NPY", # numpy + "PD", # pandas-vet + "PERF", # perflint + "PGH", # pygrep-hooks + "PIE", # flake8-pie + "PL", # pylint + "PT", # flake8-pytest-style + "PTH", # flake8-use-pathlib + "PYI", # flake8-pyi + "Q", # flake8-quotes + "RET", # flake8-return + "RSE", # flake8-raise + "RUF", # Ruff-specific + "S", # flake8-bandit + "SLF", # flake8-self + "SLOT", # flake8-slots + "SIM", # flake8-simplify + "T20", # flake8-print + "TCH", # flake8-type-checking + "TID251", # flake8-tidy-imports + "TRY", # tryceratops + "UP", # pyupgrade + "YTT", # flake8-2020 ] ignore = [ - "E501", # Line too long (Black is enough) - "PLR2004", # Magic values - "PLR0913", # Too many arguments + "ANN101", # Missing type annotation for `self` in method + "ANN102", # Missing type annotation for `cls` in classmethod + "ISC001", # Conflicts with formatter + "PLR09", # Too many <...> + "PLR2004", # Magic value used in comparison + "PLC0415", # Import should be at top of file + "PT004", # Incorrect, just usefixtures instead. + "S101", # Use of assert detected + "S404", # `subprocess` module is possibly insecure ] -target-version = "py38" - -# Exclude a variety of commonly ignored directories. -exclude = [ - ".bzr", - ".direnv", - ".eggs", - ".git", - ".hg", - ".mypy_cache", - ".nox", - ".pants.d", - ".ruff_cache", - ".svn", - ".tox", - ".venv", - "__pypackages__", - "_build", - "buck-out", - "build", - "dist", - "node_modules", - "venv", - "extern", +isort.required-imports = ["from __future__ import annotations"] + +[tool.ruff.lint.flake8-tidy-imports.banned-api] +"typing.Callable".msg = "Use collections.abc.Callable instead." +"typing.Iterator".msg = "Use collections.abc.Iterator instead." +"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 scikit_build_core._compat.typing.Self instead." +"typing_extensions.Self".msg = "Use scikit_build_core._compat.typing.Self instead." +"typing.assert_never".msg = "Use scikit_build_core._compat.typing.assert_never instead." +"importlib.resources".msg = "Use scikit_build_core._compat.importlib.resources instead." +"importlib_resources".msg = "Use scikit_build_core._compat.importlib.resources instead." + +[tool.ruff.lint.per-file-ignores] +"test/python/**" = ["T20", "ANN"] +"src/mqt/qudits/visualisation/**" = ["T20"] +"docs/**" = ["T20"] +"noxfile.py" = ["T20", "TID251"] +"*.pyi" = ["D418", "PYI021"] # pydocstyle +"*.ipynb" = [ + "D", # pydocstyle + "E402", # Allow imports to appear anywhere in Jupyter notebooks + "I002", # Allow missing `from __future__ import annotations` import ] -line-length = 120 +[tool.ruff.lint.pydocstyle] +convention = "google" -[tool.black] -line-length = 120 + +[tool.cibuildwheel] +build = "cp3*" +skip = "*-musllinux_*" +archs = "auto64" +test-command = "python -c \"from mqt import qudits\"" +test-skip = "cp38-macosx_arm64" +build-frontend = "build" + +[tool.cibuildwheel.linux] +environment = { DEPLOY="ON" } + +[tool.cibuildwheel.macos] +environment = { MACOSX_DEPLOYMENT_TARGET = "10.15" } + +[tool.cibuildwheel.windows] +before-build = "pip install delvewheel" +repair-wheel-command = "delvewheel repair -v -w {dest_dir} {wheel}" +environment = { CMAKE_ARGS = "-T ClangCL" } From d35d1b076e060d074b3ca717b37d471bfaba7a14 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Fri, 19 Apr 2024 21:33:03 +0200 Subject: [PATCH 12/22] =?UTF-8?q?=F0=9F=91=B7=20CI=20setup?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/codecov.yml | 52 +++++++++++++++++ .github/dependabot.yml | 36 ++++++++++++ .github/release-drafter.yml | 54 ++++++++++++++++++ .github/workflows/cd.yml | 26 ++++++++- .github/workflows/ci.yml | 81 ++++++++++++++++++++++++++- .github/workflows/codecov.yml | 4 -- .github/workflows/python-publish.yml | 36 ------------ .github/workflows/release-drafter.yml | 23 ++++++++ 8 files changed, 270 insertions(+), 42 deletions(-) create mode 100644 .github/codecov.yml create mode 100644 .github/dependabot.yml create mode 100644 .github/release-drafter.yml delete mode 100644 .github/workflows/codecov.yml delete mode 100644 .github/workflows/python-publish.yml create mode 100644 .github/workflows/release-drafter.yml diff --git a/.github/codecov.yml b/.github/codecov.yml new file mode 100644 index 0000000..0a91a39 --- /dev/null +++ b/.github/codecov.yml @@ -0,0 +1,52 @@ +ignore: + - "extern/**/*" + - "include/GateMatrixDefinitions.h" + - "**/python" + - "test/**/*" + +coverage: + range: 60..90 + precision: 1 + status: + project: off + patch: off + +flag_management: + default_rules: + carryforward: true + statuses: + - type: project + target: auto + threshold: 0.5% + removed_code_behavior: adjust_base + - type: patch + target: 90% + threshold: 1% + individual_flags: + - name: cpp + paths: + - "include" + - "src" + after_n_builds: 1 + - name: python + paths: + - "src/mqt/**/*.py" + after_n_builds: 12 + statuses: + - type: project + threshold: 0.5% + removed_code_behavior: adjust_base + - type: patch + target: 95% + threshold: 1% + +parsers: + gcov: + branch_detection: + conditional: no + loop: no + +comment: + layout: "reach, diff, flags, files" + require_changes: true + show_carryforward_flags: true diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..ea60e7e --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,36 @@ +version: 2 +updates: + - package-ecosystem: "gitsubmodule" + directory: "/" + groups: + submodules: + patterns: + - "*" + schedule: + interval: "monthly" + time: "06:00" + timezone: "Europe/Vienna" + + - package-ecosystem: "github-actions" + directory: "/" + groups: + github-actions: + patterns: + - "*" + schedule: + interval: "weekly" + day: "friday" + time: "06:00" + timezone: "Europe/Vienna" + + - package-ecosystem: "pip" + directory: "/" + groups: + python-dependencies: + patterns: + - "*" + schedule: + interval: "weekly" + day: "friday" + time: "06:00" + timezone: "Europe/Vienna" diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml new file mode 100644 index 0000000..8fe48a0 --- /dev/null +++ b/.github/release-drafter.yml @@ -0,0 +1,54 @@ +name-template: "MQT Qudits $RESOLVED_VERSION Release" +tag-template: "v$RESOLVED_VERSION" +categories: + - title: "🚀 Features and Enhancements" + labels: + - "feature" + - "enhancement" + - "usability" + - title: "🐛 Bug Fixes" + labels: + - "bug" + - "fix" + - title: "📄 Documentation" + labels: + - "documentation" + - title: "🤖 CI" + labels: + - "continuous integration" + - title: "📦 Packaging" + labels: + - "packaging" + - title: "🧹 Code Quality" + labels: + - "code quality" + - title: "⬆️ Dependencies" + collapse-after: 5 + labels: + - "dependencies" + - "submodules" + - "github_actions" +change-template: "- $TITLE @$AUTHOR (#$NUMBER)" +change-title-escapes: '\<*_&' +version-resolver: + major: + labels: + - "major" + minor: + labels: + - "minor" + patch: + labels: + - "patch" + default: patch +autolabeler: + - label: "dependencies" + title: + - "/update pre-commit hooks/i" + +template: | + ## 👀 What Changed + + $CHANGES + + **Full Changelog**: https://github.com/$OWNER/$REPOSITORY/compare/$PREVIOUS_TAG...v$RESOLVED_VERSION diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index c5a96ff..d5c82e5 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -1,4 +1,28 @@ -name: cd.yml +name: CD on: + release: + types: [published] + workflow_dispatch: jobs: + python-packaging: + name: 🐍 Packaging + uses: cda-tum/mqt-core/.github/workflows/reusable-python-packaging.yml@v2.4.0 + + deploy: + if: github.event_name == 'release' && github.event.action == 'published' + name: 🚀 Deploy to PyPI + runs-on: ubuntu-latest + environment: + name: pypi + url: https://pypi.org/p/mqt.qudits + permissions: + id-token: write + needs: [python-packaging] + steps: + - uses: actions/download-artifact@v4 + with: + pattern: cibw-* + path: dist + merge-multiple: true + - uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c3bdb95..477341c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,4 +1,83 @@ -name: ci.yml +name: CI on: + push: + branches: + - main + pull_request: + merge_group: + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true jobs: + change-detection: + name: 🔍 Change + uses: cda-tum/mqt-core/.github/workflows/reusable-change-detection.yml@v2.4.0 + + cpp-tests: + name: 🇨‌ Test + needs: change-detection + if: fromJSON(needs.change-detection.outputs.run-cpp-tests) + uses: cda-tum/mqt-core/.github/workflows/reusable-cpp-ci.yml@v2.4.0 + secrets: + token: ${{ secrets.CODECOV_TOKEN }} + with: + cmake-args: "" + cmake-args-ubuntu: -G Ninja + cmake-args-macos: -G Ninja + cmake-args-windows: -T ClangCL + + cpp-linter: + name: 🇨‌ Lint + needs: change-detection + if: fromJSON(needs.change-detection.outputs.run-cpp-linter) + uses: cda-tum/mqt-core/.github/workflows/reusable-cpp-linter.yml@v2.4.0 + + python-tests: + name: 🐍 Test + needs: change-detection + if: fromJSON(needs.change-detection.outputs.run-python-tests) + uses: cda-tum/mqt-core/.github/workflows/reusable-python-ci.yml@v2.4.0 + secrets: + token: ${{ secrets.CODECOV_TOKEN }} + + code-ql: + name: 📝 CodeQL + needs: change-detection + if: fromJSON(needs.change-detection.outputs.run-code-ql) + uses: cda-tum/mqt-core/.github/workflows/reusable-code-ql.yml@v2.4.0 + + required-checks-pass: # This job does nothing and is only used for branch protection + name: 🚦 Check + if: always() + needs: + - change-detection + - cpp-tests + - cpp-linter + - python-tests + - code-ql + runs-on: ubuntu-latest + steps: + - name: Decide whether the needed jobs succeeded or failed + uses: re-actors/alls-green@release/v1 + with: + allowed-skips: >- + ${{ + fromJSON(needs.change-detection.outputs.run-cpp-tests) + && '' || 'cpp-tests,' + }} + ${{ + fromJSON(needs.change-detection.outputs.run-cpp-linter) + && '' || 'cpp-linter,' + }} + ${{ + fromJSON(needs.change-detection.outputs.run-python-tests) + && '' || 'python-tests,' + }} + ${{ + fromJSON(needs.change-detection.outputs.run-code-ql) + && '' || 'code-ql,' + }} + jobs: ${{ toJSON(needs) }} diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml deleted file mode 100644 index e2264b4..0000000 --- a/.github/workflows/codecov.yml +++ /dev/null @@ -1,4 +0,0 @@ -name: codecov.yml -on: - -jobs: diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml deleted file mode 100644 index 6e3d4ce..0000000 --- a/.github/workflows/python-publish.yml +++ /dev/null @@ -1,36 +0,0 @@ -name: Deploy to PyPI - -on: - release: - types: [published] - push: - branches: [main] - pull_request: - branches: [main] - workflow_dispatch: - -jobs: - test_routine: - name: Test Python Routine - - runs-on: ${{ matrix.os }} - - strategy: - matrix: - os: [ubuntu-latest, windows-latest, macos-latest] - - steps: - - uses: actions/checkout@v3 - with: - submodules: recursive - - uses: actions/setup-python@v4 - name: Install Python - with: - python-version: "3.9" - - name: Install package - run: | - pip install -U pip setuptools wheel - pip3 install . - - - name: Test package - run: python -m unittest discover -s ./tests diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml new file mode 100644 index 0000000..02b6951 --- /dev/null +++ b/.github/workflows/release-drafter.yml @@ -0,0 +1,23 @@ +name: Release Drafter + +on: + push: + branches: + - main + pull_request_target: + types: [opened, reopened, synchronize] + +permissions: + contents: read + +jobs: + update_release_draft: + name: Run + permissions: + contents: write + pull-requests: write + runs-on: ubuntu-latest + steps: + - uses: release-drafter/release-drafter@v6 + env: + GITHUB_TOKEN: ${{ github.token }} From 9b75dcbcbb0f5483f8e991b9639264fc2a65bcb0 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Fri, 19 Apr 2024 21:33:42 +0200 Subject: [PATCH 13/22] =?UTF-8?q?=F0=9F=8F=B7=EF=B8=8F=20type=20stubs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/mqt/qudits/__init__.py | 11 +++++++++++ .../mqt/qudits/_qudits/__init__.pyi | 0 src/mqt/qudits/_qudits/misim.pyi | 17 +++++++++++++++++ src/mqt/qudits/_version.pyi | 4 ++++ src/mqt/qudits/py.typed | 2 ++ 5 files changed, 34 insertions(+) rename debug_branch => src/mqt/qudits/_qudits/__init__.pyi (100%) create mode 100644 src/mqt/qudits/_qudits/misim.pyi create mode 100644 src/mqt/qudits/_version.pyi create mode 100644 src/mqt/qudits/py.typed diff --git a/src/mqt/qudits/__init__.py b/src/mqt/qudits/__init__.py index e69de29..c6056bd 100644 --- a/src/mqt/qudits/__init__.py +++ b/src/mqt/qudits/__init__.py @@ -0,0 +1,11 @@ +"""MQT Qudits - A framework for mixed-dimensional qudit quantum computing.""" + +from __future__ import annotations + +from ._version import version as __version__ +from ._version import version_tuple as version_info + +__all__ = [ + "__version__", + "version_info", +] diff --git a/debug_branch b/src/mqt/qudits/_qudits/__init__.pyi similarity index 100% rename from debug_branch rename to src/mqt/qudits/_qudits/__init__.pyi diff --git a/src/mqt/qudits/_qudits/misim.pyi b/src/mqt/qudits/_qudits/misim.pyi new file mode 100644 index 0000000..a92f14f --- /dev/null +++ b/src/mqt/qudits/_qudits/misim.pyi @@ -0,0 +1,17 @@ +from typing import Any + + +# todo: this need significantly better typing and a better docstring +def state_vector_simulation(circuit: Any, noise_model: dict[str, Any]) -> list[complex]: # noqa: ANN401 + """Simulate the state vector of a quantum circuit with noise model. + + Args: + circuit: The quantum circuit to simulate + noise_model: The noise model to apply + + Returns: + list: The state vector of the quantum circuit + """ + + +__all__ = ["state_vector_simulation"] diff --git a/src/mqt/qudits/_version.pyi b/src/mqt/qudits/_version.pyi new file mode 100644 index 0000000..9312dff --- /dev/null +++ b/src/mqt/qudits/_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/qudits/py.typed b/src/mqt/qudits/py.typed new file mode 100644 index 0000000..5f3ea3d --- /dev/null +++ b/src/mqt/qudits/py.typed @@ -0,0 +1,2 @@ +# Instruct type checkers to look for inline type annotations in this package. +# See PEP 561. From c71d4efb7ea3b56af2e2269e23a7c5a7a01cc379 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Fri, 19 Apr 2024 21:38:45 +0200 Subject: [PATCH 14/22] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20initial=20refactor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/mqt/qudits/compiler/__init__.py | 9 ++ .../compilation_minitools/__init__.py | 30 ++++ .../naive_unitary_verifier.py | 10 +- src/mqt/qudits/compiler/dit_manager.py | 13 +- src/mqt/qudits/compiler/onedit/__init__.py | 10 ++ .../onedit/local_operation_swap/__init__.py | 17 ++ .../local_operation_swap/swap_routine.py | 23 +-- .../local_phases_transpilation/__init__.py | 9 ++ .../propagate_virtrz.py | 15 +- .../remove_phase_rotations.py | 17 +- .../mapping_aware_transpilation/__init__.py | 11 ++ .../phy_local_adaptive_decomp.py | 128 ++++++++------- .../phy_local_qr_decomp.py | 33 ++-- .../__init__.py | 10 ++ .../log_local_adaptive_decomp.py | 125 +++++++-------- .../log_local_qr_decomp.py | 33 ++-- src/mqt/qudits/compiler/twodit/__init__.py | 7 + .../twodit/entanglement_qr/__init__.py | 12 ++ .../crotgen.py | 52 ++++--- .../log_ent_qr_cex_decomp.py | 29 ++-- .../pswap.py | 61 ++++---- .../__init__.py | 0 .../entanglement_qr/__init__.py | 0 .../cex_decomposition/__init__.py | 0 .../customization_variables/__init__.py | 0 .../ent_qr_customize_vars.py | 1 - .../ansatz_solve_n_search.py | 88 ----------- .../opt/optimizer.py | 93 ----------- .../variational_customize_vars.py | 15 -- .../__init__.py | 0 .../ansatz/__init__.py | 15 ++ .../ansatz/ansatz_gen.py | 22 ++- .../ansatz/instantiate.py | 20 +-- .../ansatz/parametrize.py | 11 +- .../ansatz_solve_n_search.py | 79 ++++++++++ .../opt/__init__.py | 21 +++ .../opt/distance_measures.py | 7 +- .../opt/optimizer.py | 92 +++++++++++ src/mqt/qudits/core/__init__.py | 12 ++ .../core/{structures/trees => }/dfs_tree.py | 48 +++--- .../energy_level_graph => }/level_graph.py | 17 +- src/mqt/qudits/exceptions/__init__.py | 19 +++ src/mqt/qudits/quantum_circuit/__init__.py | 9 ++ .../circuit.py | 135 +++++++++------- .../instructions => quantum_circuit}/gate.py | 48 ++++-- .../qudits/quantum_circuit/gates/__init__.py | 43 ++++++ .../gates}/csum.py | 17 +- .../gates}/custom_multi.py | 11 +- .../gates}/custom_one.py | 11 +- .../gates}/custom_two.py | 11 +- .../gate_set => quantum_circuit/gates}/cx.py | 20 ++- .../gates}/gellman.py | 15 +- .../gate_set => quantum_circuit/gates}/h.py | 12 +- .../gate_set => quantum_circuit/gates}/ls.py | 19 +-- .../gate_set => quantum_circuit/gates}/ms.py | 15 +- .../gates}/perm.py | 16 +- .../gate_set => quantum_circuit/gates}/r.py | 20 ++- .../gates}/randu.py | 19 +-- .../gate_set => quantum_circuit/gates}/rh.py | 18 +-- .../gate_set => quantum_circuit/gates}/rz.py | 20 ++- .../gate_set => quantum_circuit/gates}/s.py | 13 +- .../gates}/virt_rz.py | 15 +- .../gate_set => quantum_circuit/gates}/x.py | 13 +- .../gate_set => quantum_circuit/gates}/z.py | 13 +- .../matrix_factory.py | 48 +++++- .../qasm.py | 16 +- src/mqt/qudits/qudit_circuits/__init__.py | 0 .../qudit_circuits/components/__init__.py | 0 .../components/instructions/__init__.py | 0 .../instructions/gate_extensions/__init__.py | 0 .../instructions/gate_extensions/controls.py | 9 -- .../gate_extensions/gate_types.py | 12 -- .../instructions/gate_set/__init__.py | 0 .../components/instructions/instruction.py | 7 - .../instructions/mini_tools/__init__.py | 0 .../mini_tools/matrix_factory_tools.py | 40 ----- .../components/registers/__init__.py | 0 .../components/registers/quantum_register.py | 40 ----- .../qudit_circuits/qasm_interface/__init__.py | 0 src/mqt/qudits/simulation/__init__.py | 7 + .../qudits/simulation/backends/__init__.py | 9 ++ .../qudits/simulation/backends/backendv2.py | 91 +++++++++++ .../backends/fake_backends/__init__.py | 6 + .../backends/fake_backends/fake_traps2six.py | 136 ++++------------ .../fake_backends/fake_traps2three.py | 137 ++++------------ src/mqt/qudits/simulation/backends/misim.py | 54 +++++++ .../stochastic_sim.py} | 26 ++-- .../backends/engines => backends}/tnsim.py | 61 +++----- .../qudits/simulation/data_log/__init__.py | 0 src/mqt/qudits/simulation/jobs/__init__.py | 11 ++ .../simulation/{provider => }/jobs/job.py | 20 +-- .../jobs/job_result => jobs}/job_result.py | 0 .../{provider => }/jobs/jobstatus.py | 0 .../qudits/simulation/noise_tools/__init__.py | 6 + .../{provider => }/noise_tools/noise.py | 0 .../noise_tools/noisy_circuit_factory.py | 26 ++-- .../qudits/simulation/provider/__init__.py | 0 .../provider/backend_properties/__init__.py | 0 .../backend_properties/quditproperties.py | 3 - .../simulation/provider/backends/__init__.py | 0 .../simulation/provider/backends/backendv2.py | 146 ------------------ .../provider/backends/engines/__init__.py | 0 .../provider/backends/engines/misim.py | 73 --------- .../backends/fake_backends/__init__.py | 0 .../backends/stocastic_components/__init__.py | 0 .../simulation/provider/jobs/__init__.py | 0 .../provider/jobs/job_result/__init__.py | 0 .../provider/noise_tools/__init__.py | 0 .../qudits/simulation/provider/provider.py | 31 ---- .../simulation/provider/qudit_provider.py | 33 ---- src/mqt/qudits/simulation/qudit_provider.py | 47 ++++++ .../simulation/{data_log => }/save_info.py | 0 src/mqt/qudits/visualisation/__init__.py | 13 ++ .../qudits/visualisation/drawing_routines.py | 16 +- .../visualisation/mini_quantum_information.py | 7 +- .../qudits/visualisation/plot_information.py | 13 +- 116 files changed, 1413 insertions(+), 1458 deletions(-) create mode 100755 src/mqt/qudits/compiler/twodit/entanglement_qr/__init__.py rename src/mqt/qudits/compiler/twodit/{mapping_un_aware_transpilation/entanglement_qr/cex_decomposition => entanglement_qr}/crotgen.py (72%) rename src/mqt/qudits/compiler/twodit/{mapping_un_aware_transpilation/entanglement_qr/cex_decomposition => entanglement_qr}/log_ent_qr_cex_decomp.py (82%) rename src/mqt/qudits/compiler/twodit/{mapping_un_aware_transpilation/entanglement_qr/cex_decomposition => entanglement_qr}/pswap.py (77%) delete mode 100644 src/mqt/qudits/compiler/twodit/mapping_un_aware_transpilation/__init__.py delete mode 100755 src/mqt/qudits/compiler/twodit/mapping_un_aware_transpilation/entanglement_qr/__init__.py delete mode 100755 src/mqt/qudits/compiler/twodit/mapping_un_aware_transpilation/entanglement_qr/cex_decomposition/__init__.py delete mode 100644 src/mqt/qudits/compiler/twodit/mapping_un_aware_transpilation/entanglement_qr/customization_variables/__init__.py delete mode 100644 src/mqt/qudits/compiler/twodit/mapping_un_aware_transpilation/entanglement_qr/customization_variables/ent_qr_customize_vars.py delete mode 100755 src/mqt/qudits/compiler/twodit/mapping_un_aware_transpilation/variational_twodit_compilation/ansatz_solve_n_search.py delete mode 100755 src/mqt/qudits/compiler/twodit/mapping_un_aware_transpilation/variational_twodit_compilation/opt/optimizer.py delete mode 100755 src/mqt/qudits/compiler/twodit/mapping_un_aware_transpilation/variational_twodit_compilation/variational_customize_vars.py rename src/mqt/qudits/compiler/{state_prep => twodit/variational_twodit_compilation}/__init__.py (100%) mode change 100644 => 100755 create mode 100755 src/mqt/qudits/compiler/twodit/variational_twodit_compilation/ansatz/__init__.py rename src/mqt/qudits/compiler/twodit/{mapping_un_aware_transpilation => }/variational_twodit_compilation/ansatz/ansatz_gen.py (65%) rename src/mqt/qudits/compiler/twodit/{mapping_un_aware_transpilation => }/variational_twodit_compilation/ansatz/instantiate.py (66%) rename src/mqt/qudits/compiler/twodit/{mapping_un_aware_transpilation => }/variational_twodit_compilation/ansatz/parametrize.py (89%) create mode 100755 src/mqt/qudits/compiler/twodit/variational_twodit_compilation/ansatz_solve_n_search.py create mode 100755 src/mqt/qudits/compiler/twodit/variational_twodit_compilation/opt/__init__.py rename src/mqt/qudits/compiler/twodit/{mapping_un_aware_transpilation => }/variational_twodit_compilation/opt/distance_measures.py (93%) create mode 100755 src/mqt/qudits/compiler/twodit/variational_twodit_compilation/opt/optimizer.py rename src/mqt/qudits/core/{structures/trees => }/dfs_tree.py (83%) rename src/mqt/qudits/core/{structures/energy_level_graph => }/level_graph.py (95%) create mode 100644 src/mqt/qudits/quantum_circuit/__init__.py rename src/mqt/qudits/{qudit_circuits => quantum_circuit}/circuit.py (73%) rename src/mqt/qudits/{qudit_circuits/components/instructions => quantum_circuit}/gate.py (88%) create mode 100644 src/mqt/qudits/quantum_circuit/gates/__init__.py rename src/mqt/qudits/{qudit_circuits/components/instructions/gate_set => quantum_circuit/gates}/csum.py (72%) rename src/mqt/qudits/{qudit_circuits/components/instructions/gate_set => quantum_circuit/gates}/custom_multi.py (72%) rename src/mqt/qudits/{qudit_circuits/components/instructions/gate_set => quantum_circuit/gates}/custom_one.py (73%) rename src/mqt/qudits/{qudit_circuits/components/instructions/gate_set => quantum_circuit/gates}/custom_two.py (72%) rename src/mqt/qudits/{qudit_circuits/components/instructions/gate_set => quantum_circuit/gates}/cx.py (84%) rename src/mqt/qudits/{qudit_circuits/components/instructions/gate_set => quantum_circuit/gates}/gellman.py (81%) rename src/mqt/qudits/{qudit_circuits/components/instructions/gate_set => quantum_circuit/gates}/h.py (79%) rename src/mqt/qudits/{qudit_circuits/components/instructions/gate_set => quantum_circuit/gates}/ls.py (74%) rename src/mqt/qudits/{qudit_circuits/components/instructions/gate_set => quantum_circuit/gates}/ms.py (86%) rename src/mqt/qudits/{qudit_circuits/components/instructions/gate_set => quantum_circuit/gates}/perm.py (68%) rename src/mqt/qudits/{qudit_circuits/components/instructions/gate_set => quantum_circuit/gates}/r.py (80%) rename src/mqt/qudits/{qudit_circuits/components/instructions/gate_set => quantum_circuit/gates}/randu.py (63%) rename src/mqt/qudits/{qudit_circuits/components/instructions/gate_set => quantum_circuit/gates}/rh.py (79%) rename src/mqt/qudits/{qudit_circuits/components/instructions/gate_set => quantum_circuit/gates}/rz.py (77%) rename src/mqt/qudits/{qudit_circuits/components/instructions/gate_set => quantum_circuit/gates}/s.py (77%) rename src/mqt/qudits/{qudit_circuits/components/instructions/gate_set => quantum_circuit/gates}/virt_rz.py (72%) rename src/mqt/qudits/{qudit_circuits/components/instructions/gate_set => quantum_circuit/gates}/x.py (76%) rename src/mqt/qudits/{qudit_circuits/components/instructions/gate_set => quantum_circuit/gates}/z.py (77%) rename src/mqt/qudits/{qudit_circuits/components/instructions/gate_extensions => quantum_circuit}/matrix_factory.py (83%) rename src/mqt/qudits/{qudit_circuits/qasm_interface => quantum_circuit}/qasm.py (95%) delete mode 100644 src/mqt/qudits/qudit_circuits/__init__.py delete mode 100644 src/mqt/qudits/qudit_circuits/components/__init__.py delete mode 100644 src/mqt/qudits/qudit_circuits/components/instructions/__init__.py delete mode 100644 src/mqt/qudits/qudit_circuits/components/instructions/gate_extensions/__init__.py delete mode 100644 src/mqt/qudits/qudit_circuits/components/instructions/gate_extensions/controls.py delete mode 100644 src/mqt/qudits/qudit_circuits/components/instructions/gate_extensions/gate_types.py delete mode 100644 src/mqt/qudits/qudit_circuits/components/instructions/gate_set/__init__.py delete mode 100644 src/mqt/qudits/qudit_circuits/components/instructions/instruction.py delete mode 100644 src/mqt/qudits/qudit_circuits/components/instructions/mini_tools/__init__.py delete mode 100644 src/mqt/qudits/qudit_circuits/components/instructions/mini_tools/matrix_factory_tools.py delete mode 100644 src/mqt/qudits/qudit_circuits/components/registers/__init__.py delete mode 100644 src/mqt/qudits/qudit_circuits/components/registers/quantum_register.py delete mode 100644 src/mqt/qudits/qudit_circuits/qasm_interface/__init__.py create mode 100644 src/mqt/qudits/simulation/backends/__init__.py create mode 100644 src/mqt/qudits/simulation/backends/backendv2.py create mode 100644 src/mqt/qudits/simulation/backends/fake_backends/__init__.py rename src/mqt/qudits/simulation/{provider => }/backends/fake_backends/fake_traps2six.py (56%) rename src/mqt/qudits/simulation/{provider => }/backends/fake_backends/fake_traps2three.py (53%) create mode 100644 src/mqt/qudits/simulation/backends/misim.py rename src/mqt/qudits/simulation/{provider/backends/stocastic_components/stocastic_sim.py => backends/stochastic_sim.py} (77%) rename src/mqt/qudits/simulation/{provider/backends/engines => backends}/tnsim.py (62%) delete mode 100644 src/mqt/qudits/simulation/data_log/__init__.py create mode 100644 src/mqt/qudits/simulation/jobs/__init__.py rename src/mqt/qudits/simulation/{provider => }/jobs/job.py (87%) rename src/mqt/qudits/simulation/{provider/jobs/job_result => jobs}/job_result.py (100%) rename src/mqt/qudits/simulation/{provider => }/jobs/jobstatus.py (100%) create mode 100644 src/mqt/qudits/simulation/noise_tools/__init__.py rename src/mqt/qudits/simulation/{provider => }/noise_tools/noise.py (100%) rename src/mqt/qudits/simulation/{provider => }/noise_tools/noisy_circuit_factory.py (81%) delete mode 100644 src/mqt/qudits/simulation/provider/__init__.py delete mode 100644 src/mqt/qudits/simulation/provider/backend_properties/__init__.py delete mode 100644 src/mqt/qudits/simulation/provider/backend_properties/quditproperties.py delete mode 100644 src/mqt/qudits/simulation/provider/backends/__init__.py delete mode 100644 src/mqt/qudits/simulation/provider/backends/backendv2.py delete mode 100644 src/mqt/qudits/simulation/provider/backends/engines/__init__.py delete mode 100644 src/mqt/qudits/simulation/provider/backends/engines/misim.py delete mode 100644 src/mqt/qudits/simulation/provider/backends/fake_backends/__init__.py delete mode 100644 src/mqt/qudits/simulation/provider/backends/stocastic_components/__init__.py delete mode 100644 src/mqt/qudits/simulation/provider/jobs/__init__.py delete mode 100644 src/mqt/qudits/simulation/provider/jobs/job_result/__init__.py delete mode 100644 src/mqt/qudits/simulation/provider/noise_tools/__init__.py delete mode 100644 src/mqt/qudits/simulation/provider/provider.py delete mode 100644 src/mqt/qudits/simulation/provider/qudit_provider.py create mode 100644 src/mqt/qudits/simulation/qudit_provider.py rename src/mqt/qudits/simulation/{data_log => }/save_info.py (100%) diff --git a/src/mqt/qudits/compiler/__init__.py b/src/mqt/qudits/compiler/__init__.py index e69de29..03ef224 100644 --- a/src/mqt/qudits/compiler/__init__.py +++ b/src/mqt/qudits/compiler/__init__.py @@ -0,0 +1,9 @@ +from __future__ import annotations + +from .compiler_pass import CompilerPass +from .dit_manager import QuditCompiler + +__all__ = [ + "CompilerPass", + "QuditCompiler", +] diff --git a/src/mqt/qudits/compiler/compilation_minitools/__init__.py b/src/mqt/qudits/compiler/compilation_minitools/__init__.py index e69de29..ed28507 100644 --- a/src/mqt/qudits/compiler/compilation_minitools/__init__.py +++ b/src/mqt/qudits/compiler/compilation_minitools/__init__.py @@ -0,0 +1,30 @@ +"""Common utilities for compilation.""" + +from __future__ import annotations + +from .local_compilation_minitools import ( + new_mod, + phi_cost, + pi_mod, + regulate_theta, + rotation_cost_calc, + swap_elements, + theta_cost, +) +from .naive_unitary_verifier import UnitaryVerifier +from .numerical_ansatz_utils import apply_gate_to_tlines, gate_expand_to_circuit, on0, on1 + +__all__ = [ + "UnitaryVerifier", + "apply_gate_to_tlines", + "gate_expand_to_circuit", + "new_mod", + "on0", + "on1", + "phi_cost", + "pi_mod", + "regulate_theta", + "rotation_cost_calc", + "swap_elements", + "theta_cost", +] diff --git a/src/mqt/qudits/compiler/compilation_minitools/naive_unitary_verifier.py b/src/mqt/qudits/compiler/compilation_minitools/naive_unitary_verifier.py index bb3d2a3..90db209 100755 --- a/src/mqt/qudits/compiler/compilation_minitools/naive_unitary_verifier.py +++ b/src/mqt/qudits/compiler/compilation_minitools/naive_unitary_verifier.py @@ -1,7 +1,9 @@ +from __future__ import annotations + +import operator from functools import reduce import numpy as np -from numpy.linalg import inv class UnitaryVerifier: @@ -14,10 +16,10 @@ class UnitaryVerifier: final_map is a list representing the mapping of the logic states to the physical ones at the end of the computation """ - def __init__(self, sequence, target, dimensions, nodes=None, initial_map=None, final_map=None): + def __init__(self, sequence, target, dimensions, nodes=None, initial_map=None, final_map=None) -> None: self.decomposition = sequence self.target = target.copy() - self.dimension = reduce(lambda x, y: x * y, dimensions) + self.dimension = reduce(operator.mul, dimensions) if nodes is not None and initial_map is not None and final_map is not None: self.permutation_matrix_initial = self.get_perm_matrix(nodes, initial_map) @@ -49,7 +51,7 @@ def verify(self): target = rotation @ target if self.permutation_matrix_final is not None: - target = inv(self.permutation_matrix_final) @ target + target = np.linalg.inv(self.permutation_matrix_final) @ target target = target / target[0][0] diff --git a/src/mqt/qudits/compiler/dit_manager.py b/src/mqt/qudits/compiler/dit_manager.py index f81a93f..55b7a10 100644 --- a/src/mqt/qudits/compiler/dit_manager.py +++ b/src/mqt/qudits/compiler/dit_manager.py @@ -1,10 +1,7 @@ -from mqt.qudits.compiler.onedit.mapping_aware_transpilation.phy_local_adaptive_decomp import PhyLocAdaPass -from mqt.qudits.compiler.onedit.mapping_aware_transpilation.phy_local_qr_decomp import PhyLocQRPass -from mqt.qudits.compiler.onedit.mapping_un_aware_transpilation.log_local_adaptive_decomp import LogLocAdaPass -from mqt.qudits.compiler.onedit.mapping_un_aware_transpilation.log_local_qr_decomp import LogLocQRPass -from mqt.qudits.compiler.twodit.mapping_un_aware_transpilation.entanglement_qr.cex_decomposition.log_ent_qr_cex_decomp import ( - LogEntQRCEXPass, -) +from __future__ import annotations + +from .onedit import LogLocAdaPass, LogLocQRPass, PhyLocAdaPass, PhyLocQRPass +from .twodit import LogEntQRCEXPass class QuditCompiler: @@ -18,7 +15,7 @@ class QuditCompiler: "LogEntQRCEXPass": LogEntQRCEXPass, } - def __init__(self): + def __init__(self) -> None: pass def compile(self, backend, circuit, passes_names): diff --git a/src/mqt/qudits/compiler/onedit/__init__.py b/src/mqt/qudits/compiler/onedit/__init__.py index 8b13789..e263b2b 100644 --- a/src/mqt/qudits/compiler/onedit/__init__.py +++ b/src/mqt/qudits/compiler/onedit/__init__.py @@ -1 +1,11 @@ +from __future__ import annotations +from .mapping_aware_transpilation import PhyLocAdaPass, PhyLocQRPass +from .mapping_un_aware_transpilation import LogLocAdaPass, LogLocQRPass + +__all__ = [ + "LogLocAdaPass", + "LogLocQRPass", + "PhyLocAdaPass", + "PhyLocQRPass", +] diff --git a/src/mqt/qudits/compiler/onedit/local_operation_swap/__init__.py b/src/mqt/qudits/compiler/onedit/local_operation_swap/__init__.py index e69de29..953137e 100644 --- a/src/mqt/qudits/compiler/onedit/local_operation_swap/__init__.py +++ b/src/mqt/qudits/compiler/onedit/local_operation_swap/__init__.py @@ -0,0 +1,17 @@ +from __future__ import annotations + +from .swap_routine import ( + cost_calculator, + gate_chain_condition, + graph_rule_ongate, + graph_rule_update, + route_states2rotate_basic, +) + +__all__ = [ + "cost_calculator", + "gate_chain_condition", + "graph_rule_ongate", + "graph_rule_update", + "route_states2rotate_basic", +] diff --git a/src/mqt/qudits/compiler/onedit/local_operation_swap/swap_routine.py b/src/mqt/qudits/compiler/onedit/local_operation_swap/swap_routine.py index 67198b4..abbcc6b 100644 --- a/src/mqt/qudits/compiler/onedit/local_operation_swap/swap_routine.py +++ b/src/mqt/qudits/compiler/onedit/local_operation_swap/swap_routine.py @@ -1,12 +1,17 @@ +from __future__ import annotations + +import math + import networkx as nx import numpy as np -from mqt.qudits.compiler.compilation_minitools.local_compilation_minitools import ( + +from ....quantum_circuit.gates.r import R +from ...compilation_minitools import ( new_mod, pi_mod, rotation_cost_calc, swap_elements, ) -from mqt.qudits.qudit_circuits.components.instructions.gate_set.r import R def find_logic_from_phys(lev_a, lev_b, graph): @@ -21,8 +26,8 @@ def find_logic_from_phys(lev_a, lev_b, graph): return logic_nodes -def graph_rule_update(gate, graph): - if abs(abs(gate.theta) - 3.14) < 1e-2: +def graph_rule_update(gate, graph) -> None: + if abs(abs(gate.theta) - math.pi) < 1e-2: inode = graph._1stInode if "phase_storage" not in graph.nodes[inode]: return @@ -86,15 +91,11 @@ def gate_chain_condition(previous_gates, current): # all phi flips are removed because already applied if new_source == last_source: - if new_target > last_target: # changed lower one with lower one - pass - elif new_target < last_target: # changed higher one one with lower + if new_target > last_target or new_target < last_target: # changed lower one with lower one pass elif new_target == last_target: - if new_source < last_source: - theta = theta * -1 - elif new_source > last_source: + if new_source < last_source or new_source > last_source: theta = theta * -1 elif new_source == last_target: @@ -120,7 +121,7 @@ def route_states2rotate_basic(gate, orig_placement): pi_pulses_routing = [] source = gate.original_lev_a # Original code requires to know the direction of rotations - target = gate.original_lev_b # + target = gate.original_lev_b path = nx.shortest_path(placement, source, target) diff --git a/src/mqt/qudits/compiler/onedit/local_phases_transpilation/__init__.py b/src/mqt/qudits/compiler/onedit/local_phases_transpilation/__init__.py index e69de29..74d336a 100644 --- a/src/mqt/qudits/compiler/onedit/local_phases_transpilation/__init__.py +++ b/src/mqt/qudits/compiler/onedit/local_phases_transpilation/__init__.py @@ -0,0 +1,9 @@ +from __future__ import annotations + +from .propagate_virtrz import ZPropagationPass +from .remove_phase_rotations import ZRemovalPass + +__all__ = [ + "ZPropagationPass", + "ZRemovalPass", +] diff --git a/src/mqt/qudits/compiler/onedit/local_phases_transpilation/propagate_virtrz.py b/src/mqt/qudits/compiler/onedit/local_phases_transpilation/propagate_virtrz.py index f525b73..4d9cbd9 100644 --- a/src/mqt/qudits/compiler/onedit/local_phases_transpilation/propagate_virtrz.py +++ b/src/mqt/qudits/compiler/onedit/local_phases_transpilation/propagate_virtrz.py @@ -1,13 +1,14 @@ +from __future__ import annotations + import copy -from mqt.qudits.compiler.compilation_minitools.local_compilation_minitools import pi_mod -from mqt.qudits.compiler.compiler_pass import CompilerPass -from mqt.qudits.qudit_circuits.components.instructions.gate_set.r import R -from mqt.qudits.qudit_circuits.components.instructions.gate_set.virt_rz import VirtRz +from ....quantum_circuit import gates +from ... import CompilerPass +from ...compilation_minitools import pi_mod class ZPropagationPass(CompilerPass): - def __init__(self, backend, back=True): + def __init__(self, backend, back=True) -> None: super().__init__(backend) self.back = back @@ -41,7 +42,7 @@ def propagate_z(self, circuit, line, back): ) list_of_XYrots.append( - R( + gates.R( circuit, "R", qudit_index, @@ -62,7 +63,7 @@ def propagate_z(self, circuit, line, back): Zseq = [] for e_lev in list(Z_angles): - Zseq.append(VirtRz(circuit, "VRz", qudit_index, [e_lev, Z_angles[e_lev]], dimension)) + Zseq.append(gates.VirtRz(circuit, "VRz", qudit_index, [e_lev, Z_angles[e_lev]], dimension)) # Zseq.append(Rz(Z_angles[e_lev], e_lev, QC.dimension)) return list_of_XYrots, Zseq diff --git a/src/mqt/qudits/compiler/onedit/local_phases_transpilation/remove_phase_rotations.py b/src/mqt/qudits/compiler/onedit/local_phases_transpilation/remove_phase_rotations.py index b8f136e..7658c5b 100644 --- a/src/mqt/qudits/compiler/onedit/local_phases_transpilation/remove_phase_rotations.py +++ b/src/mqt/qudits/compiler/onedit/local_phases_transpilation/remove_phase_rotations.py @@ -1,13 +1,13 @@ +from __future__ import annotations + import copy -from mqt.qudits.compiler.compiler_pass import CompilerPass -from mqt.qudits.qudit_circuits.components.instructions.gate_set.rz import Rz -from mqt.qudits.qudit_circuits.components.instructions.gate_set.virt_rz import VirtRz -from mqt.qudits.qudit_circuits.components.instructions.gate_set.z import Z +from ....quantum_circuit import gates +from ... import CompilerPass class ZRemovalPass(CompilerPass): - def __init__(self, backend): + def __init__(self, backend) -> None: super().__init__(backend) def transpile(self, circuit): @@ -34,11 +34,10 @@ def remove_rz_gates(self, original_circuit, reverse=False): # continue to the next iteration because we work only with single gates seen_target_qudits.update(target_qudits) continue + if target_qudits not in seen_target_qudits and isinstance(instruction, (gates.Rz, gates.VirtRz, gates.Z)): + indices_to_remove.append(i) else: - if target_qudits not in seen_target_qudits and isinstance(instruction, (Rz, VirtRz, Z)): - indices_to_remove.append(i) - else: - seen_target_qudits.add(target_qudits) + seen_target_qudits.add(target_qudits) new_instructions = [op for index, op in enumerate(new_instructions) if index not in indices_to_remove] return circuit.set_instructions(new_instructions) diff --git a/src/mqt/qudits/compiler/onedit/mapping_aware_transpilation/__init__.py b/src/mqt/qudits/compiler/onedit/mapping_aware_transpilation/__init__.py index e69de29..77c9484 100644 --- a/src/mqt/qudits/compiler/onedit/mapping_aware_transpilation/__init__.py +++ b/src/mqt/qudits/compiler/onedit/mapping_aware_transpilation/__init__.py @@ -0,0 +1,11 @@ +from __future__ import annotations + +from .phy_local_adaptive_decomp import PhyAdaptiveDecomposition, PhyLocAdaPass +from .phy_local_qr_decomp import PhyLocQRPass, PhyQrDecomp + +__all__ = [ + "PhyAdaptiveDecomposition", + "PhyLocAdaPass", + "PhyLocQRPass", + "PhyQrDecomp", +] diff --git a/src/mqt/qudits/compiler/onedit/mapping_aware_transpilation/phy_local_adaptive_decomp.py b/src/mqt/qudits/compiler/onedit/mapping_aware_transpilation/phy_local_adaptive_decomp.py index fc12894..2aeaa87 100644 --- a/src/mqt/qudits/compiler/onedit/mapping_aware_transpilation/phy_local_adaptive_decomp.py +++ b/src/mqt/qudits/compiler/onedit/mapping_aware_transpilation/phy_local_adaptive_decomp.py @@ -1,27 +1,28 @@ +from __future__ import annotations + import gc import numpy as np -from mqt.qudits.compiler.compilation_minitools.local_compilation_minitools import new_mod -from mqt.qudits.compiler.compiler_pass import CompilerPass -from mqt.qudits.compiler.onedit.local_operation_swap.swap_routine import ( + +from ....core import NAryTree +from ....exceptions import SequenceFoundException +from ....quantum_circuit import gates +from ....quantum_circuit.gate import GateTypes +from ... import CompilerPass +from ...compilation_minitools import new_mod +from ..local_operation_swap import ( cost_calculator, gate_chain_condition, graph_rule_ongate, graph_rule_update, ) -from mqt.qudits.compiler.onedit.mapping_aware_transpilation.phy_local_qr_decomp import PhyQrDecomp -from mqt.qudits.core.structures.trees.dfs_tree import NAryTree -from mqt.qudits.exceptions.compilerexception import SequenceFoundException -from mqt.qudits.qudit_circuits.components.instructions.gate_extensions.gate_types import GateTypes -from mqt.qudits.qudit_circuits.components.instructions.gate_set.custom_one import CustomOne -from mqt.qudits.qudit_circuits.components.instructions.gate_set.r import R -from mqt.qudits.qudit_circuits.components.instructions.gate_set.virt_rz import VirtRz +from ..mapping_aware_transpilation import PhyQrDecomp np.seterr(all="ignore") class PhyLocAdaPass(CompilerPass): - def __init__(self, backend): + def __init__(self, backend) -> None: super().__init__(backend) def transpile(self, circuit): @@ -29,7 +30,7 @@ def transpile(self, circuit): instructions = circuit.instructions new_instructions = [] - for _i, gate in enumerate(instructions): + for gate in instructions: if gate.gate_type == GateTypes.SINGLE: energy_graph_i = self.backend.energy_level_graphs[gate._target_qudits] # ini_lpmap = list(self.backend.energy_level_graphs.log_phy_map) @@ -44,7 +45,7 @@ def transpile(self, circuit): QR = PhyQrDecomp(gate, energy_graph_i) - decomp, algorithmic_cost, total_cost = QR.execute() + _decomp, algorithmic_cost, total_cost = QR.execute() Adaptive = PhyAdaptiveDecomposition( gate, energy_graph_i, (algorithmic_cost, total_cost), gate._dimensions @@ -52,7 +53,7 @@ def transpile(self, circuit): ( matrices_decomposed, - best_cost, + _best_cost, self.backend.energy_level_graphs[gate._target_qudits], ) = Adaptive.execute() @@ -84,7 +85,7 @@ def transpile(self, circuit): class PhyAdaptiveDecomposition: - def __init__(self, gate, graph_orig, cost_limit=(0, 0), dimension=-1, Z_prop=False): + def __init__(self, gate, graph_orig, cost_limit=(0, 0), dimension=-1, Z_prop=False) -> None: self.circuit = gate.parent_circuit self.U = gate.to_matrix(identities=0) self.qudit_index = gate._target_qudits @@ -98,7 +99,7 @@ def __init__(self, gate, graph_orig, cost_limit=(0, 0), dimension=-1, Z_prop=Fal def execute(self): self.TREE.add( 0, - CustomOne( + gates.CustomOne( self.circuit, "CUo", self.qudit_index, np.identity(self.dimension, dtype="complex"), self.dimension ), self.U, @@ -120,7 +121,7 @@ def execute(self): matrices_decomposed, final_graph, self.phase_propagation ) else: - print("couldn't decompose\n") + pass self.TREE.print_tree(self.TREE.root, "TREE: ") @@ -149,54 +150,51 @@ def Z_extraction(self, decomposition, placement, phase_propagation): not_diag = filtered_Ucopy.any() if not_diag or not valid_diag: # if is diagonal enough then somehow signal end of algorithm - msg = "Matrix isnt close to diagonal!" + msg = "Matrix isn't close to diagonal!" raise Exception(msg) - else: - diag_U = np.diag(U_) - dimension = U_.shape[0] - - for i in range(dimension): - if abs(np.angle(diag_U[i])) > 1.0e-4: - if phase_propagation: - inode = placement._1stInode - if "phase_storage" in placement.nodes[inode]: - placement.nodes[i]["phase_storage"] = placement.nodes[i]["phase_storage"] + np.angle( - diag_U[i] - ) - placement.nodes[i]["phase_storage"] = new_mod(placement.nodes[i]["phase_storage"]) - else: - phy_n_i = placement.nodes[i]["lpmap"] + diag_U = np.diag(U_) + dimension = U_.shape[0] - phase_gate = VirtRz( - self.circuit, "VRz", self.qudit_index, [phy_n_i, np.angle(diag_U[i])], self.dimension - ) # old version: VirtRz(np.angle(diag_U[i]), phy_n_i, + for i in range(dimension): + if abs(np.angle(diag_U[i])) > 1.0e-4: + if phase_propagation: + inode = placement._1stInode + if "phase_storage" in placement.nodes[inode]: + placement.nodes[i]["phase_storage"] = placement.nodes[i]["phase_storage"] + np.angle(diag_U[i]) + placement.nodes[i]["phase_storage"] = new_mod(placement.nodes[i]["phase_storage"]) + else: + phy_n_i = placement.nodes[i]["lpmap"] + + phase_gate = gates.VirtRz( + self.circuit, "VRz", self.qudit_index, [phy_n_i, np.angle(diag_U[i])], self.dimension + ) # old version: VirtRz(np.angle(diag_U[i]), phy_n_i, + # dimension) + + U_ = phase_gate.to_matrix(identities=0) @ U_ # matmul(phase_gate.to_matrix(identities=0), U_) + + matrices.append(phase_gate) + + if not phase_propagation: + inode = placement._1stInode + if "phase_storage" in placement.nodes[inode]: + for i in range(len(list(placement.nodes))): + thetaZ = new_mod(placement.nodes[i]["phase_storage"]) + if abs(thetaZ) > 1.0e-4: + phase_gate = gates.VirtRz( + self.circuit, + "VRz", + self.qudit_index, + [placement.nodes[i]["lpmap"], thetaZ], + self.dimension, + ) # VirtRz(thetaZ, placement.nodes[i]['lpmap'], # dimension) - - U_ = phase_gate.to_matrix(identities=0) @ U_ # matmul(phase_gate.to_matrix(identities=0), U_) - matrices.append(phase_gate) + # reset the node + placement.nodes[i]["phase_storage"] = 0 - if not phase_propagation: - inode = placement._1stInode - if "phase_storage" in placement.nodes[inode]: - for i in range(len(list(placement.nodes))): - thetaZ = new_mod(placement.nodes[i]["phase_storage"]) - if abs(thetaZ) > 1.0e-4: - phase_gate = VirtRz( - self.circuit, - "VRz", - self.qudit_index, - [placement.nodes[i]["lpmap"], thetaZ], - self.dimension, - ) # VirtRz(thetaZ, placement.nodes[i]['lpmap'], - # dimension) - matrices.append(phase_gate) - # reset the node - placement.nodes[i]["phase_storage"] = 0 - - return matrices, placement + return matrices, placement - def DFS(self, current_root, level=0): + def DFS(self, current_root, level=0) -> None: # check if close to diagonal Ucopy = current_root.U_of_level.copy() @@ -235,7 +233,7 @@ def DFS(self, current_root, level=0): phi = -(np.pi / 2 + np.angle(U_[r, c]) - np.angle(U_[r2, c])) - rotation_involved = R( + rotation_involved = gates.R( self.circuit, "R", self.qudit_index, [r, r2, theta, phi], self.dimension ) # R(theta, phi, r, r2, dimension) @@ -256,7 +254,7 @@ def DFS(self, current_root, level=0): branch_condition = ( current_root.max_cost[1] - decomp_next_step_cost - ) # SECOND POSITION IS PHYISCAL COST + ) # SECOND POSITION IS PHYSICAL COST # branch_condition_2 = current_root.max_cost[0] - next_step_cost # deprecated: FIRST IS ALGORITHMIC COST if branch_condition > 0 or abs(branch_condition) < 1.0e-12: @@ -265,11 +263,9 @@ def DFS(self, current_root, level=0): self.TREE.global_id_counter = self.TREE.global_id_counter + 1 new_key = self.TREE.global_id_counter - # if new_placement.nodes[r]["lpmap"] > new_placement.nodes[r2]["lpmap"]: phi = phi * -1 - # - physical_rotation = R( + physical_rotation = gates.R( self.circuit, "R", self.qudit_index, @@ -279,15 +275,13 @@ def DFS(self, current_root, level=0): # R(theta, phi, new_placement.nodes[r]['lpmap'], new_placement.nodes[r2]['lpmap'], dimension) # physical_rotation = gate_chain_condition(pi_pulses_routing, physical_rotation) - # physical_rotation = graph_rule_ongate(physical_rotation, new_placement) - # # take care of phases accumulated by not pi-pulsing back p_backs = [] for ppulse in pi_pulses_routing: p_backs.append( - R( + gates.R( self.circuit, "R", self.qudit_index, diff --git a/src/mqt/qudits/compiler/onedit/mapping_aware_transpilation/phy_local_qr_decomp.py b/src/mqt/qudits/compiler/onedit/mapping_aware_transpilation/phy_local_qr_decomp.py index 3b09fb8..cfbc55c 100644 --- a/src/mqt/qudits/compiler/onedit/mapping_aware_transpilation/phy_local_qr_decomp.py +++ b/src/mqt/qudits/compiler/onedit/mapping_aware_transpilation/phy_local_qr_decomp.py @@ -1,16 +1,18 @@ +from __future__ import annotations + import gc import numpy as np -from mqt.qudits.compiler.compilation_minitools.local_compilation_minitools import new_mod -from mqt.qudits.compiler.compiler_pass import CompilerPass -from mqt.qudits.compiler.onedit.local_operation_swap.swap_routine import cost_calculator, gate_chain_condition -from mqt.qudits.qudit_circuits.components.instructions.gate_extensions.gate_types import GateTypes -from mqt.qudits.qudit_circuits.components.instructions.gate_set.r import R -from mqt.qudits.qudit_circuits.components.instructions.gate_set.virt_rz import VirtRz + +from ....quantum_circuit import gates +from ....quantum_circuit.gate import GateTypes +from ... import CompilerPass +from ...compilation_minitools import new_mod +from ..local_operation_swap import cost_calculator, gate_chain_condition class PhyLocQRPass(CompilerPass): - def __init__(self, backend): + def __init__(self, backend) -> None: super().__init__(backend) def transpile(self, circuit): @@ -18,11 +20,11 @@ def transpile(self, circuit): instructions = circuit.instructions new_instructions = [] - for _i, gate in enumerate(instructions): + for gate in instructions: if gate.gate_type == GateTypes.SINGLE: energy_graph_i = self.backend.energy_level_graphs[gate._target_qudits] QR = PhyQrDecomp(gate, energy_graph_i, not_stand_alone=False) - decomp, algorithmic_cost, total_cost = QR.execute() + decomp, _algorithmic_cost, _total_cost = QR.execute() new_instructions += decomp gc.collect() else: @@ -32,7 +34,7 @@ def transpile(self, circuit): class PhyQrDecomp: - def __init__(self, gate, graph_orig, Z_prop=False, not_stand_alone=True): + def __init__(self, gate, graph_orig, Z_prop=False, not_stand_alone=True) -> None: self.gate = gate self.circuit = gate.parent_circuit self.dimension = gate._dimensions @@ -58,7 +60,7 @@ def execute(self): for i in range(len(list(self.graph.nodes))): thetaZ = new_mod(self.graph.nodes[i]["phase_storage"]) if abs(thetaZ) > 1.0e-4: - phase_gate = VirtRz( + phase_gate = gates.VirtRz( self.gate.parent_circuit, "VRz", self.gate._target_qudits, @@ -70,7 +72,6 @@ def execute(self): # reset the node self.graph.nodes[i]["phase_storage"] = 0 - # l = list(range(self.U.shape[0])) l.reverse() @@ -84,7 +85,7 @@ def execute(self): phi = -(np.pi / 2 + np.angle(U_[r - 1, c]) - np.angle(U_[r, c])) - rotation_involved = R( + rotation_involved = gates.R( self.circuit, "R", self.qudit_index, [r - 1, r, theta, phi], self.dimension ) # R(theta, phi, r - 1, r, dimension) @@ -101,7 +102,7 @@ def execute(self): if temp_placement.nodes[r - 1]["lpmap"] > temp_placement.nodes[r]["lpmap"]: phi = phi * -1 - physical_rotation = R( + physical_rotation = gates.R( self.circuit, "R", self.qudit_index, @@ -115,7 +116,7 @@ def execute(self): for pi_g in reversed(pi_pulses_routing): decomp.append( - R( + gates.R( self.circuit, "R", self.qudit_index, @@ -134,7 +135,7 @@ def execute(self): if abs(np.angle(diag_U[i])) > 1.0e-4: phy_n_i = self.graph.nodes[i]["lpmap"] - phase_gate = VirtRz( + phase_gate = gates.VirtRz( self.gate.parent_circuit, "VRz", self.gate._target_qudits, diff --git a/src/mqt/qudits/compiler/onedit/mapping_un_aware_transpilation/__init__.py b/src/mqt/qudits/compiler/onedit/mapping_un_aware_transpilation/__init__.py index e69de29..f22eda4 100644 --- a/src/mqt/qudits/compiler/onedit/mapping_un_aware_transpilation/__init__.py +++ b/src/mqt/qudits/compiler/onedit/mapping_un_aware_transpilation/__init__.py @@ -0,0 +1,10 @@ +from __future__ import annotations + +from .log_local_adaptive_decomp import LogAdaptiveDecomposition, LogLocAdaPass +from .log_local_qr_decomp import LogLocQRPass + +__all__ = [ + "LogAdaptiveDecomposition", + "LogLocAdaPass", + "LogLocQRPass", +] diff --git a/src/mqt/qudits/compiler/onedit/mapping_un_aware_transpilation/log_local_adaptive_decomp.py b/src/mqt/qudits/compiler/onedit/mapping_un_aware_transpilation/log_local_adaptive_decomp.py index 0eb26fc..e607d1b 100644 --- a/src/mqt/qudits/compiler/onedit/mapping_un_aware_transpilation/log_local_adaptive_decomp.py +++ b/src/mqt/qudits/compiler/onedit/mapping_un_aware_transpilation/log_local_adaptive_decomp.py @@ -1,27 +1,28 @@ +from __future__ import annotations + import gc import numpy as np -from mqt.qudits.compiler.compilation_minitools.local_compilation_minitools import new_mod -from mqt.qudits.compiler.compiler_pass import CompilerPass -from mqt.qudits.compiler.onedit.local_operation_swap.swap_routine import ( + +from ....core import NAryTree +from ....exceptions import SequenceFoundException +from ....quantum_circuit import gates +from ....quantum_circuit.gate import GateTypes +from ... import CompilerPass +from ...compilation_minitools import new_mod +from ..local_operation_swap import ( cost_calculator, gate_chain_condition, graph_rule_ongate, graph_rule_update, ) -from mqt.qudits.compiler.onedit.mapping_aware_transpilation.phy_local_qr_decomp import PhyQrDecomp -from mqt.qudits.core.structures.trees.dfs_tree import NAryTree -from mqt.qudits.exceptions.compilerexception import SequenceFoundException -from mqt.qudits.qudit_circuits.components.instructions.gate_extensions.gate_types import GateTypes -from mqt.qudits.qudit_circuits.components.instructions.gate_set.custom_one import CustomOne -from mqt.qudits.qudit_circuits.components.instructions.gate_set.r import R -from mqt.qudits.qudit_circuits.components.instructions.gate_set.virt_rz import VirtRz +from ..mapping_aware_transpilation import PhyQrDecomp np.seterr(all="ignore") class LogLocAdaPass(CompilerPass): - def __init__(self, backend): + def __init__(self, backend) -> None: super().__init__(backend) def transpile(self, circuit): @@ -29,13 +30,13 @@ def transpile(self, circuit): instructions = circuit.instructions new_instructions = [] - for _i, gate in enumerate(instructions): + for gate in instructions: if gate.gate_type == GateTypes.SINGLE: energy_graph_i = self.backend.energy_level_graphs[gate._target_qudits] QR = PhyQrDecomp(gate, energy_graph_i) - decomp, algorithmic_cost, total_cost = QR.execute() + _decomp, algorithmic_cost, total_cost = QR.execute() Adaptive = LogAdaptiveDecomposition( gate, energy_graph_i, (algorithmic_cost, total_cost), gate._dimensions @@ -43,7 +44,7 @@ def transpile(self, circuit): ( matrices_decomposed, - best_cost, + _best_cost, self.backend.energy_level_graphs[gate._target_qudits], ) = Adaptive.execute() @@ -57,7 +58,7 @@ def transpile(self, circuit): class LogAdaptiveDecomposition: - def __init__(self, gate, graph_orig, cost_limit=(0, 0), dimension=-1, Z_prop=False): + def __init__(self, gate, graph_orig, cost_limit=(0, 0), dimension=-1, Z_prop=False) -> None: self.circuit = gate.parent_circuit self.U = gate.to_matrix(identities=0) self.qudit_index = gate._target_qudits @@ -71,7 +72,7 @@ def __init__(self, gate, graph_orig, cost_limit=(0, 0), dimension=-1, Z_prop=Fal def execute(self): self.TREE.add( 0, - CustomOne( + gates.CustomOne( self.circuit, "CUo", self.qudit_index, np.identity(self.dimension, dtype="complex"), self.dimension ), self.U, @@ -94,7 +95,7 @@ def execute(self): # matrices_decomposed, final_graph, self.phase_propagation # ) else: - print("couldn't decompose\n") + pass self.TREE.print_tree(self.TREE.root, "TREE: ") @@ -123,54 +124,51 @@ def Z_extraction(self, decomposition, placement, phase_propagation): not_diag = filtered_Ucopy.any() if not_diag or not valid_diag: # if is diagonal enough then somehow signal end of algorithm - msg = "Matrix isnt close to diagonal!" + msg = "Matrix isn't close to diagonal!" raise Exception(msg) - else: - diag_U = np.diag(U_) - dimension = U_.shape[0] - - for i in range(dimension): - if abs(np.angle(diag_U[i])) > 1.0e-4: - if phase_propagation: - inode = placement._1stInode - if "phase_storage" in placement.nodes[inode]: - placement.nodes[i]["phase_storage"] = placement.nodes[i]["phase_storage"] + np.angle( - diag_U[i] - ) - placement.nodes[i]["phase_storage"] = new_mod(placement.nodes[i]["phase_storage"]) - else: - n_i = placement.nodes[i] # ["lpmap"] + diag_U = np.diag(U_) + dimension = U_.shape[0] - phase_gate = VirtRz( - self.circuit, "VRz", self.qudit_index, [n_i, np.angle(diag_U[i])], self.dimension - ) # old version: VirtRz(np.angle(diag_U[i]), phy_n_i, + for i in range(dimension): + if abs(np.angle(diag_U[i])) > 1.0e-4: + if phase_propagation: + inode = placement._1stInode + if "phase_storage" in placement.nodes[inode]: + placement.nodes[i]["phase_storage"] = placement.nodes[i]["phase_storage"] + np.angle(diag_U[i]) + placement.nodes[i]["phase_storage"] = new_mod(placement.nodes[i]["phase_storage"]) + else: + n_i = placement.nodes[i] # ["lpmap"] + + phase_gate = gates.VirtRz( + self.circuit, "VRz", self.qudit_index, [n_i, np.angle(diag_U[i])], self.dimension + ) # old version: VirtRz(np.angle(diag_U[i]), phy_n_i, + # dimension) + + U_ = phase_gate.to_matrix(identities=0) @ U_ # matmul(phase_gate.to_matrix(identities=0), U_) + + matrices.append(phase_gate) + + if not phase_propagation: + inode = placement._1stInode + if "phase_storage" in placement.nodes[inode]: + for i in range(len(list(placement.nodes))): + thetaZ = new_mod(placement.nodes[i]["phase_storage"]) + if abs(thetaZ) > 1.0e-4: + phase_gate = gates.VirtRz( + self.circuit, + "VRz", + self.qudit_index, + [placement.nodes[i], thetaZ], + self.dimension, + ) # VirtRz(thetaZ, placement.nodes[i]['lpmap'], # [placement.nodes[i]["lpmap"], thetaZ], # dimension) - - U_ = phase_gate.to_matrix(identities=0) @ U_ # matmul(phase_gate.to_matrix(identities=0), U_) - matrices.append(phase_gate) + # reset the node + placement.nodes[i]["phase_storage"] = 0 - if not phase_propagation: - inode = placement._1stInode - if "phase_storage" in placement.nodes[inode]: - for i in range(len(list(placement.nodes))): - thetaZ = new_mod(placement.nodes[i]["phase_storage"]) - if abs(thetaZ) > 1.0e-4: - phase_gate = VirtRz( - self.circuit, - "VRz", - self.qudit_index, - [placement.nodes[i], thetaZ], - self.dimension, - ) # VirtRz(thetaZ, placement.nodes[i]['lpmap'], # [placement.nodes[i]["lpmap"], thetaZ], - # dimension) - matrices.append(phase_gate) - # reset the node - placement.nodes[i]["phase_storage"] = 0 - - return matrices, placement + return matrices, placement - def DFS(self, current_root, level=0): + def DFS(self, current_root, level=0) -> None: # check if close to diagonal Ucopy = current_root.U_of_level.copy() @@ -209,7 +207,7 @@ def DFS(self, current_root, level=0): phi = -(np.pi / 2 + np.angle(U_[r, c]) - np.angle(U_[r2, c])) - rotation_involved = R( + rotation_involved = gates.R( self.circuit, "R", self.qudit_index, [r, r2, theta, phi], self.dimension ) # R(theta, phi, r, r2, dimension) @@ -238,8 +236,7 @@ def DFS(self, current_root, level=0): if new_placement.nodes[r]["lpmap"] > new_placement.nodes[r2]["lpmap"]: phi = phi * -1 - # - physical_rotation = R( + physical_rotation = gates.R( self.circuit, "R", self.qudit_index, @@ -249,15 +246,13 @@ def DFS(self, current_root, level=0): # R(theta, phi, new_placement.nodes[r]['lpmap'], new_placement.nodes[r2]['lpmap'], dimension) # physical_rotation = gate_chain_condition(pi_pulses_routing, physical_rotation) - # physical_rotation = graph_rule_ongate(physical_rotation, new_placement) - # # take care of phases accumulated by not pi-pulsing back p_backs = [] for ppulse in pi_pulses_routing: p_backs.append( - R( + gates.R( self.circuit, "R", self.qudit_index, diff --git a/src/mqt/qudits/compiler/onedit/mapping_un_aware_transpilation/log_local_qr_decomp.py b/src/mqt/qudits/compiler/onedit/mapping_un_aware_transpilation/log_local_qr_decomp.py index fed22e0..5467fc9 100644 --- a/src/mqt/qudits/compiler/onedit/mapping_un_aware_transpilation/log_local_qr_decomp.py +++ b/src/mqt/qudits/compiler/onedit/mapping_un_aware_transpilation/log_local_qr_decomp.py @@ -1,16 +1,20 @@ +from __future__ import annotations + import gc import numpy as np -from mqt.qudits.compiler.compilation_minitools.local_compilation_minitools import new_mod -from mqt.qudits.compiler.compiler_pass import CompilerPass -from mqt.qudits.compiler.onedit.local_operation_swap.swap_routine import cost_calculator -from mqt.qudits.qudit_circuits.components.instructions.gate_extensions.gate_types import GateTypes -from mqt.qudits.qudit_circuits.components.instructions.gate_set.r import R -from mqt.qudits.qudit_circuits.components.instructions.gate_set.virt_rz import VirtRz + +from ....quantum_circuit import gates +from ....quantum_circuit.gate import GateTypes +from ... import CompilerPass +from ...compilation_minitools import new_mod +from ..local_operation_swap import ( + cost_calculator, +) class LogLocQRPass(CompilerPass): - def __init__(self, backend): + def __init__(self, backend) -> None: super().__init__(backend) def transpile(self, circuit): @@ -18,11 +22,11 @@ def transpile(self, circuit): instructions = circuit.instructions new_instructions = [] - for _i, gate in enumerate(instructions): + for gate in instructions: if gate.gate_type == GateTypes.SINGLE: energy_graph_i = self.backend.energy_level_graphs[gate._target_qudits] QR = QrDecomp(gate, energy_graph_i, not_stand_alone=False) - decomp, algorithmic_cost, total_cost = QR.execute() + decomp, _algorithmic_cost, _total_cost = QR.execute() new_instructions += decomp gc.collect() else: @@ -32,7 +36,7 @@ def transpile(self, circuit): class QrDecomp: - def __init__(self, gate, graph_orig, Z_prop=False, not_stand_alone=True): + def __init__(self, gate, graph_orig, Z_prop=False, not_stand_alone=True) -> None: self.gate = gate self.circuit = gate.parent_circuit self.dimension = gate._dimensions @@ -58,7 +62,7 @@ def execute(self): for i in range(len(list(self.graph.nodes))): thetaZ = new_mod(self.graph.nodes[i]["phase_storage"]) if abs(thetaZ) > 1.0e-4: - phase_gate = VirtRz( + phase_gate = gates.VirtRz( self.gate.parent_circuit, "VRz", self.gate._target_qudits, @@ -70,7 +74,6 @@ def execute(self): # reset the node self.graph.nodes[i]["phase_storage"] = 0 - # l = list(range(self.U.shape[0])) l.reverse() @@ -84,7 +87,7 @@ def execute(self): phi = -(np.pi / 2 + np.angle(U_[r - 1, c]) - np.angle(U_[r, c])) - rotation_involved = R( + rotation_involved = gates.R( self.circuit, "R", self.qudit_index, [r - 1, r, theta, phi], self.dimension ) # R(theta, phi, r - 1, r, dimension) @@ -92,7 +95,7 @@ def execute(self): non_zeros = np.count_nonzero(abs(U_) > 1.0e-4) - estimated_cost, pi_pulses_routing, temp_placement, cost_of_pi_pulses, gate_cost = cost_calculator( + estimated_cost, _pi_pulses_routing, _temp_placement, cost_of_pi_pulses, gate_cost = cost_calculator( rotation_involved, self.graph, non_zeros ) """ @@ -136,7 +139,7 @@ def execute(self): if abs(np.angle(diag_U[i])) > 1.0e-4: n_i = self.graph.nodes[i] # self.graph.nodes[i]["lpmap"] - phase_gate = VirtRz( + phase_gate = gates.VirtRz( self.gate.parent_circuit, "VRz", self.gate._target_qudits, diff --git a/src/mqt/qudits/compiler/twodit/__init__.py b/src/mqt/qudits/compiler/twodit/__init__.py index e69de29..2bbf0f8 100644 --- a/src/mqt/qudits/compiler/twodit/__init__.py +++ b/src/mqt/qudits/compiler/twodit/__init__.py @@ -0,0 +1,7 @@ +from __future__ import annotations + +from .entanglement_qr import LogEntQRCEXPass + +__all__ = [ + "LogEntQRCEXPass", +] diff --git a/src/mqt/qudits/compiler/twodit/entanglement_qr/__init__.py b/src/mqt/qudits/compiler/twodit/entanglement_qr/__init__.py new file mode 100755 index 0000000..3567ae7 --- /dev/null +++ b/src/mqt/qudits/compiler/twodit/entanglement_qr/__init__.py @@ -0,0 +1,12 @@ +from __future__ import annotations + +from .crotgen import CRotGen +from .log_ent_qr_cex_decomp import EntangledQRCEX, LogEntQRCEXPass +from .pswap import PSwapGen + +__all__ = [ + "CRotGen", + "EntangledQRCEX", + "LogEntQRCEXPass", + "PSwapGen", +] diff --git a/src/mqt/qudits/compiler/twodit/mapping_un_aware_transpilation/entanglement_qr/cex_decomposition/crotgen.py b/src/mqt/qudits/compiler/twodit/entanglement_qr/crotgen.py similarity index 72% rename from src/mqt/qudits/compiler/twodit/mapping_un_aware_transpilation/entanglement_qr/cex_decomposition/crotgen.py rename to src/mqt/qudits/compiler/twodit/entanglement_qr/crotgen.py index cd0df39..b29044b 100755 --- a/src/mqt/qudits/compiler/twodit/mapping_un_aware_transpilation/entanglement_qr/cex_decomposition/crotgen.py +++ b/src/mqt/qudits/compiler/twodit/entanglement_qr/crotgen.py @@ -1,14 +1,14 @@ +from __future__ import annotations + import numpy as np -from mqt.qudits.compiler.twodit.mapping_un_aware_transpilation.entanglement_qr.customization_variables.ent_qr_customize_vars import ( - CEX_SEQUENCE, -) -from mqt.qudits.qudit_circuits.components.instructions.gate_set.cx import CEx -from mqt.qudits.qudit_circuits.components.instructions.gate_set.r import R -from mqt.qudits.qudit_circuits.components.instructions.gate_set.rz import Rz + +from ....quantum_circuit import gates + +CEX_SEQUENCE = None # list of numpy arrays class CRotGen: - def __init__(self, circuit, indices): + def __init__(self, circuit, indices) -> None: self.circuit = circuit self.indices = indices @@ -17,20 +17,20 @@ def crot_101_as_list(self, theta, phi): index_target = self.indices[1] dim_target = self.circuit.dimensions[index_target] - frame_back = R(self.circuit, "R", index_target, [0, 1, -np.pi / 2, -phi - np.pi / 2], dim_target) + frame_back = gates.R(self.circuit, "R", index_target, [0, 1, -np.pi / 2, -phi - np.pi / 2], dim_target) # on1(R(-np.pi / 2, -phi - np.pi / 2, 0, 1, d).matrix, d) - tminus = Rz(self.circuit, "Rz", index_target, [0, 1, -theta / 2], dim_target) + tminus = gates.Rz(self.circuit, "Rz", index_target, [0, 1, -theta / 2], dim_target) # on1(ZditR(-theta / 2, 0, 1, d).matrix, d)) - tplus = Rz(self.circuit, "Rz", index_target, [0, 1, +theta / 2], dim_target) + tplus = gates.Rz(self.circuit, "Rz", index_target, [0, 1, +theta / 2], dim_target) # on1(ZditR(theta / 2, 0, 1, d).matrix, d) - frame_there = R(self.circuit, "R", index_target, [0, 1, np.pi / 2, -phi - np.pi / 2], dim_target) + frame_there = gates.R(self.circuit, "R", index_target, [0, 1, np.pi / 2, -phi - np.pi / 2], dim_target) # on1(R(np.pi / 2, -phi - np.pi / 2, 0, 1, d).matrix, d) if CEX_SEQUENCE is None: - cex = CEx( + cex = gates.CEx( self.circuit, "CEx" + str([self.circuit.dimensions[i] for i in self.indices]), self.indices, @@ -82,13 +82,15 @@ def permute_crot_101_as_list(self, i, theta, phase): return rotation if q1_i != 0: - permute_there_10 = R(self.circuit, "R", index_target, [0, q1_i, np.pi, -np.pi / 2], dim_target) + permute_there_10 = gates.R(self.circuit, "R", index_target, [0, q1_i, np.pi, -np.pi / 2], dim_target) # on1(R(np.pi, -np.pi / 2, 0, q1_i, d).matrix, d) - permute_there_11 = R(self.circuit, "R", index_target, [1, q1_i + 1, -np.pi, np.pi / 2], dim_target) + permute_there_11 = gates.R(self.circuit, "R", index_target, [1, q1_i + 1, -np.pi, np.pi / 2], dim_target) # on1(R(-np.pi, np.pi / 2, 1, q1_i + 1, d).matrix, d) - permute_there_10_dag = R(self.circuit, "R", index_target, [0, q1_i, np.pi, -np.pi / 2], dim_target).dag() - permute_there_11_dag = R( + permute_there_10_dag = gates.R( + self.circuit, "R", index_target, [0, q1_i, np.pi, -np.pi / 2], dim_target + ).dag() + permute_there_11_dag = gates.R( self.circuit, "R", index_target, [1, q1_i + 1, -np.pi, np.pi / 2], dim_target ).dag() @@ -99,9 +101,9 @@ def permute_crot_101_as_list(self, i, theta, phase): rot_back += perm_back if q0_i != 1: - permute_there_00 = R(self.circuit, "R", index_ctrl, [1, q0_i, np.pi, -np.pi / 2], dim_ctrl) + permute_there_00 = gates.R(self.circuit, "R", index_ctrl, [1, q0_i, np.pi, -np.pi / 2], dim_ctrl) # on0(R(np.pi, -np.pi / 2, 1, q0_i, d).matrix, d) - permute_back_00 = R(self.circuit, "R", index_ctrl, [1, q0_i, np.pi, np.pi / 2], dim_ctrl) + permute_back_00 = gates.R(self.circuit, "R", index_ctrl, [1, q0_i, np.pi, np.pi / 2], dim_ctrl) # on0(R(np.pi, np.pi / 2, 1, q0_i, d).matrix, d) rot_there.append(permute_there_00) @@ -128,13 +130,15 @@ def permute_doubled_crot_101_as_list(self, i, theta, phase): return rotation + rotation if q1_i != 0: - permute_there_10 = R(self.circuit, "R", index_target, [0, q1_i, np.pi, -np.pi / 2], dim_target) + permute_there_10 = gates.R(self.circuit, "R", index_target, [0, q1_i, np.pi, -np.pi / 2], dim_target) # on1(R(np.pi, -np.pi / 2, 0, q1_i, d).matrix, d) - permute_there_11 = R(self.circuit, "R", index_target, [1, q1_i + 1, -np.pi, np.pi / 2], dim_target) + permute_there_11 = gates.R(self.circuit, "R", index_target, [1, q1_i + 1, -np.pi, np.pi / 2], dim_target) # on1(R(-np.pi, np.pi / 2, 1, q1_i + 1, d).matrix, d) - permute_there_10_dag = R(self.circuit, "R", index_target, [0, q1_i, np.pi, -np.pi / 2], dim_target).dag() - permute_there_11_dag = R( + permute_there_10_dag = gates.R( + self.circuit, "R", index_target, [0, q1_i, np.pi, -np.pi / 2], dim_target + ).dag() + permute_there_11_dag = gates.R( self.circuit, "R", index_target, [1, q1_i + 1, -np.pi, np.pi / 2], dim_target ).dag() @@ -156,9 +160,9 @@ def permute_doubled_crot_101_as_list(self, i, theta, phase): rot_back.append(permb)""" if q0_i != 1: - permute_there_00 = R(self.circuit, "R", index_ctrl, [1, q0_i, np.pi, -np.pi / 2], dim_ctrl) + permute_there_00 = gates.R(self.circuit, "R", index_ctrl, [1, q0_i, np.pi, -np.pi / 2], dim_ctrl) # on0(R(np.pi, -np.pi / 2, 1, q0_i, d).matrix, d) - permute_back_00 = R(self.circuit, "R", index_ctrl, [1, q0_i, np.pi, np.pi / 2], dim_ctrl) + permute_back_00 = gates.R(self.circuit, "R", index_ctrl, [1, q0_i, np.pi, np.pi / 2], dim_ctrl) # on0(R(np.pi, np.pi / 2, 1, q0_i, d).matrix, d) rot_there.append(permute_there_00) diff --git a/src/mqt/qudits/compiler/twodit/mapping_un_aware_transpilation/entanglement_qr/cex_decomposition/log_ent_qr_cex_decomp.py b/src/mqt/qudits/compiler/twodit/entanglement_qr/log_ent_qr_cex_decomp.py similarity index 82% rename from src/mqt/qudits/compiler/twodit/mapping_un_aware_transpilation/entanglement_qr/cex_decomposition/log_ent_qr_cex_decomp.py rename to src/mqt/qudits/compiler/twodit/entanglement_qr/log_ent_qr_cex_decomp.py index 6c508fd..5a9d675 100755 --- a/src/mqt/qudits/compiler/twodit/mapping_un_aware_transpilation/entanglement_qr/cex_decomposition/log_ent_qr_cex_decomp.py +++ b/src/mqt/qudits/compiler/twodit/entanglement_qr/log_ent_qr_cex_decomp.py @@ -1,17 +1,18 @@ +from __future__ import annotations + import gc import numpy as np -from mqt.qudits.compiler.compilation_minitools.local_compilation_minitools import pi_mod -from mqt.qudits.compiler.compiler_pass import CompilerPass -from mqt.qudits.compiler.twodit.mapping_un_aware_transpilation.entanglement_qr.cex_decomposition.crotgen import CRotGen -from mqt.qudits.compiler.twodit.mapping_un_aware_transpilation.entanglement_qr.cex_decomposition.pswap import PSwapGen -from mqt.qudits.qudit_circuits.components.instructions.gate_extensions.gate_types import GateTypes -from numpy import matmul as mml -from numpy.linalg import det, solve + +from ....quantum_circuit.gate import GateTypes +from ...compilation_minitools import pi_mod +from ...compiler_pass import CompilerPass +from .crotgen import CRotGen +from .pswap import PSwapGen class LogEntQRCEXPass(CompilerPass): - def __init__(self, backend): + def __init__(self, backend) -> None: super().__init__(backend) def transpile(self, circuit): @@ -19,7 +20,7 @@ def transpile(self, circuit): instructions = circuit.instructions new_instructions = [] - for _i, gate in enumerate(instructions): + for gate in instructions: if gate.gate_type == GateTypes.TWO: energy_graph_c = self.backend.energy_level_graphs[gate._target_qudits[0]] energy_graph_t = self.backend.energy_level_graphs[gate._target_qudits[1]] @@ -35,7 +36,7 @@ def transpile(self, circuit): class EntangledQRCEX: - def __init__(self, gate, graph_orig_c, graph_orig_t): + def __init__(self, gate, graph_orig_c, graph_orig_t) -> None: self.gate = gate self.circuit = gate.parent_circuit self.dimensions = gate._dimensions @@ -109,13 +110,13 @@ def execute(self): last_1 = i phases_t = phase_equations.conj().T - pseudo_inv = mml(phases_t, phase_equations) - pseudo_diag = mml(phases_t, np.array(args_of_diag)) + pseudo_inv = np.matmul(phases_t, phase_equations) + pseudo_diag = np.matmul(phases_t, np.array(args_of_diag)) - if det(pseudo_inv) == 0: + if np.linalg.det(pseudo_inv) == 0: raise Exception - phases = solve(pseudo_inv, pseudo_diag) + phases = np.linalg.solve(pseudo_inv, pseudo_diag) for i, phase in enumerate(phases): if abs(phase * 2) > 1.0e-4: diff --git a/src/mqt/qudits/compiler/twodit/mapping_un_aware_transpilation/entanglement_qr/cex_decomposition/pswap.py b/src/mqt/qudits/compiler/twodit/entanglement_qr/pswap.py similarity index 77% rename from src/mqt/qudits/compiler/twodit/mapping_un_aware_transpilation/entanglement_qr/cex_decomposition/pswap.py rename to src/mqt/qudits/compiler/twodit/entanglement_qr/pswap.py index c99a017..d3d5ac4 100755 --- a/src/mqt/qudits/compiler/twodit/mapping_un_aware_transpilation/entanglement_qr/cex_decomposition/pswap.py +++ b/src/mqt/qudits/compiler/twodit/entanglement_qr/pswap.py @@ -1,18 +1,15 @@ +from __future__ import annotations + from math import floor import numpy as np -from mqt.qudits.compiler.twodit.mapping_un_aware_transpilation.entanglement_qr.customization_variables.ent_qr_customize_vars import ( - CEX_SEQUENCE, -) -from mqt.qudits.qudit_circuits.components.instructions.gate_set.custom_one import CustomOne -from mqt.qudits.qudit_circuits.components.instructions.gate_set.cx import CEx -from mqt.qudits.qudit_circuits.components.instructions.gate_set.r import R -from mqt.qudits.qudit_circuits.components.instructions.gate_set.rh import Rh -from mqt.qudits.qudit_circuits.components.instructions.gate_set.rz import Rz + +from ....quantum_circuit import gates +from .crotgen import CEX_SEQUENCE class PSwapGen: - def __init__(self, circuit, indices): + def __init__(self, circuit, indices) -> None: self.circuit = circuit self.indices = indices @@ -22,24 +19,24 @@ def pswap_101_as_list(self, teta, phi): index_target = self.indices[1] dim_target = self.circuit.dimensions[index_target] - h_0 = Rh(self.circuit, "Rh", index_ctrl, [0, 1], dim_ctrl) - h_1 = Rh(self.circuit, "Rh", index_target, [0, 1], dim_target) + h_0 = gates.Rh(self.circuit, "Rh", index_ctrl, [0, 1], dim_ctrl) + h_1 = gates.Rh(self.circuit, "Rh", index_target, [0, 1], dim_target) # HditR(0, 1, d).matrix - zpiov2_0 = Rz(self.circuit, "Rz-zpiov2", index_ctrl, [0, 1, np.pi / 2], dim_ctrl) + zpiov2_0 = gates.Rz(self.circuit, "Rz-zpiov2", index_ctrl, [0, 1, np.pi / 2], dim_ctrl) # ZditR(np.pi / 2, 0, 1, d).matrix - zp_0 = Rz(self.circuit, "Rz-zp", index_ctrl, [0, 1, np.pi], dim_ctrl) + zp_0 = gates.Rz(self.circuit, "Rz-zp", index_ctrl, [0, 1, np.pi], dim_ctrl) # ZditR(np.pi, 0, 1, d).matrix - rphi_there_1 = R(self.circuit, "R", index_target, [0, 1, np.pi / 2, -phi - np.pi / 2], dim_target) + rphi_there_1 = gates.R(self.circuit, "R", index_target, [0, 1, np.pi / 2, -phi - np.pi / 2], dim_target) # R(np.pi / 2, -phi - np.pi / 2, 0, 1, d).matrix - rphi_back_1 = R(self.circuit, "R", index_target, [0, 1, -np.pi / 2, -phi - np.pi / 2], dim_target) + rphi_back_1 = gates.R(self.circuit, "R", index_target, [0, 1, -np.pi / 2, -phi - np.pi / 2], dim_target) # R(-np.pi / 2, -phi - np.pi / 2, 0, 1, d).matrix if CEX_SEQUENCE is None: - cex = CEx( + cex = gates.CEx( self.circuit, "CEx" + str([self.circuit.dimensions[i] for i in self.indices]), self.indices, @@ -54,7 +51,7 @@ def pswap_101_as_list(self, teta, phi): ph1 = -1 * np.identity(d, dtype="complex") ph1[0][0] = 1 ph1[1][1] = 1 - ph1 = CustomOne( + ph1 = gates.CustomOne( self.circuit, "PH1" + str(self.circuit.dimensions[index_ctrl]), index_ctrl, @@ -70,7 +67,7 @@ def pswap_101_as_list(self, teta, phi): ################################# if dim_target != 2: - r_flip_1 = R(self.circuit, "R", index_target, [1, dim_target - 1, np.pi, np.pi / 2], dim_target) + r_flip_1 = gates.R(self.circuit, "R", index_target, [1, dim_target - 1, np.pi, np.pi / 2], dim_target) compose.append(r_flip_1) # (on1(R(np.pi, np.pi / 2, 1, d - 1, d).matrix, d)) compose.append(h_1) # (on1(h_, d)) @@ -98,7 +95,7 @@ def pswap_101_as_list(self, teta, phi): else: compose = compose + cex - zth_1 = Rz(self.circuit, "Rz-th_1", index_target, [0, 1, teta / 2], dim_target) + zth_1 = gates.Rz(self.circuit, "Rz-th_1", index_target, [0, 1, teta / 2], dim_target) compose.append(zth_1) # (on1(ZditR(teta / 2, 0, 1, d).matrix, d)) if CEX_SEQUENCE is None: @@ -106,7 +103,7 @@ def pswap_101_as_list(self, teta, phi): else: compose = compose + cex - zth_back_1 = Rz(self.circuit, "Rz-thback_1", index_target, [0, 1, -teta / 2], dim_target) + zth_back_1 = gates.Rz(self.circuit, "Rz-thback_1", index_target, [0, 1, -teta / 2], dim_target) compose.append(zth_back_1) # compose.append(on1(ZditR(-teta / 2, 0, 1, d).matrix, d)) @@ -133,7 +130,7 @@ def pswap_101_as_list(self, teta, phi): ########################## if dim_target != 2: - r_flip_back_1 = R( + r_flip_back_1 = gates.R( self.circuit, "R_flip_back", index_target, [1, dim_target - 1, -np.pi, np.pi / 2], dim_target ) compose.append(r_flip_back_1) # (on1(R(-np.pi, np.pi / 2, 1, d - 1, d).matrix, d)) @@ -150,19 +147,19 @@ def permute_pswap_101_as_list(self, pos, theta, phase): rotation = self.pswap_101_as_list(theta, phase) if control_block != 0: - permute_there_00 = R( + permute_there_00 = gates.R( self.circuit, "R_there_00", index_ctrl, [0, control_block, np.pi, -np.pi / 2], dim_ctrl ) # on0(R(np.pi, -np.pi / 2, 0, j, d).matrix, d) - permute_there_01 = R( + permute_there_01 = gates.R( self.circuit, "R_there_01", index_ctrl, [1, control_block + 1, -np.pi, np.pi / 2], dim_ctrl ) # on0(R(-np.pi, np.pi / 2, 1, j + 1, d).matrix, d)) - permute_there_00_dag = R( + permute_there_00_dag = gates.R( self.circuit, "R_there_00", index_ctrl, [0, control_block, np.pi, -np.pi / 2], dim_ctrl ).dag() - permute_there_01_dag = R( + permute_there_01_dag = gates.R( self.circuit, "R_there_01", index_ctrl, [1, control_block + 1, -np.pi, np.pi / 2], dim_ctrl ).dag() @@ -170,8 +167,7 @@ def permute_pswap_101_as_list(self, pos, theta, phase): permb = [permute_there_01_dag, permute_there_00_dag] return perm + rotation + permb - else: - return rotation + return rotation def permute_quad_pswap_101_as_list(self, pos, theta, phase): index_ctrl = self.indices[0] @@ -184,19 +180,19 @@ def permute_quad_pswap_101_as_list(self, pos, theta, phase): rotation.reverse() if control_block != 0: - permute_there_00 = R( + permute_there_00 = gates.R( self.circuit, "R_there_00", index_ctrl, [0, control_block, np.pi, -np.pi / 2], dim_ctrl ) # on0(R(np.pi, -np.pi / 2, 0, j, d).matrix, d) - permute_there_01 = R( + permute_there_01 = gates.R( self.circuit, "R_there_01", index_ctrl, [1, control_block + 1, -np.pi, np.pi / 2], dim_ctrl ) # on0(R(-np.pi, np.pi / 2, 1, j + 1, d).matrix, d)) - permute_there_00_dag = R( + permute_there_00_dag = gates.R( self.circuit, "R_there_00", index_ctrl, [0, control_block, np.pi, -np.pi / 2], dim_ctrl ).dag() - permute_there_01_dag = R( + permute_there_01_dag = gates.R( self.circuit, "R_there_01", index_ctrl, [1, control_block + 1, -np.pi, np.pi / 2], dim_ctrl ).dag() @@ -210,8 +206,7 @@ def permute_quad_pswap_101_as_list(self, pos, theta, phase): perm = [permute_there_00, permute_there_01] permb = [permute_there_01_dag, permute_there_00_dag] return permb + rotation + rotation + rotation + rotation + perm - else: - return rotation + rotation + rotation + rotation + return rotation + rotation + rotation + rotation def z_pswap_101_as_list(self, i, phase, dimension_single): pi_there = self.permute_quad_pswap_101_as_list(i, np.pi / 2, 0.0) diff --git a/src/mqt/qudits/compiler/twodit/mapping_un_aware_transpilation/__init__.py b/src/mqt/qudits/compiler/twodit/mapping_un_aware_transpilation/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/mqt/qudits/compiler/twodit/mapping_un_aware_transpilation/entanglement_qr/__init__.py b/src/mqt/qudits/compiler/twodit/mapping_un_aware_transpilation/entanglement_qr/__init__.py deleted file mode 100755 index e69de29..0000000 diff --git a/src/mqt/qudits/compiler/twodit/mapping_un_aware_transpilation/entanglement_qr/cex_decomposition/__init__.py b/src/mqt/qudits/compiler/twodit/mapping_un_aware_transpilation/entanglement_qr/cex_decomposition/__init__.py deleted file mode 100755 index e69de29..0000000 diff --git a/src/mqt/qudits/compiler/twodit/mapping_un_aware_transpilation/entanglement_qr/customization_variables/__init__.py b/src/mqt/qudits/compiler/twodit/mapping_un_aware_transpilation/entanglement_qr/customization_variables/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/mqt/qudits/compiler/twodit/mapping_un_aware_transpilation/entanglement_qr/customization_variables/ent_qr_customize_vars.py b/src/mqt/qudits/compiler/twodit/mapping_un_aware_transpilation/entanglement_qr/customization_variables/ent_qr_customize_vars.py deleted file mode 100644 index 14dc5a9..0000000 --- a/src/mqt/qudits/compiler/twodit/mapping_un_aware_transpilation/entanglement_qr/customization_variables/ent_qr_customize_vars.py +++ /dev/null @@ -1 +0,0 @@ -CEX_SEQUENCE = None # list of numpy arrays diff --git a/src/mqt/qudits/compiler/twodit/mapping_un_aware_transpilation/variational_twodit_compilation/ansatz_solve_n_search.py b/src/mqt/qudits/compiler/twodit/mapping_un_aware_transpilation/variational_twodit_compilation/ansatz_solve_n_search.py deleted file mode 100755 index aac3a3e..0000000 --- a/src/mqt/qudits/compiler/twodit/mapping_un_aware_transpilation/variational_twodit_compilation/ansatz_solve_n_search.py +++ /dev/null @@ -1,88 +0,0 @@ -import queue -import threading - -import numpy as np -from mqt.qudits.compiler.twodit.mapping_un_aware_transpilation.variational_twodit_compilation import ( - variational_customize_vars, -) -from mqt.qudits.compiler.twodit.mapping_un_aware_transpilation.variational_twodit_compilation.ansatz.parametrize import ( - bound_1, - bound_2, - bound_3, -) -from mqt.qudits.compiler.twodit.mapping_un_aware_transpilation.variational_twodit_compilation.opt.optimizer import ( - bounds_assigner, - solve_anneal, -) -from mqt.qudits.compiler.twodit.mapping_un_aware_transpilation.variational_twodit_compilation.variational_customize_vars import ( - SINGLE_DIM_0, - SINGLE_DIM_1, -) - - -def interrupt_function(): - variational_customize_vars.timer_var = True - - -def binary_search_compile(max_num_layer, ansatz_type): - if max_num_layer < 0: - raise Exception - - counter = 0 - low = 0 - high = max_num_layer - - tol = variational_customize_vars.OBJ_FIDELITY - - best_layer, best_error, best_xi = (low + (high - low) // 2, np.inf, []) - mid, error, xi = (low + (high - low) // 2, np.inf, []) - - # Repeat until the pointers low and high meet each other - while low <= high: - print("counter :", counter) - mid = low + (high - low) // 2 - print("number layer: ", mid) - - error, xi = run(mid, ansatz_type) - - if error > tol: - low = mid + 1 - else: - high = mid - 1 - best_layer, best_error, best_xi = (mid, error, xi) - - counter += 1 - - return best_layer, best_error, best_xi - - -def run(num_layer, ansatz_type): - num_params_single_unitary_line_0 = -1 + variational_customize_vars.SINGLE_DIM_0**2 - num_params_single_unitary_line_1 = -1 + variational_customize_vars.SINGLE_DIM_1**2 - - bounds_line_0 = bounds_assigner(bound_1, bound_2, bound_3, num_params_single_unitary_line_0**2, SINGLE_DIM_0) * ( - num_layer + 1 - ) - bounds_line_1 = bounds_assigner(bound_1, bound_2, bound_3, num_params_single_unitary_line_1**2, SINGLE_DIM_1) * ( - num_layer + 1 - ) - - bounds = [ - bounds_line_0[i] if i % 2 == 0 else bounds_line_1[i] for i in range(max(len(bounds_line_0), len(bounds_line_1))) - ] - - duration = 3600 * (variational_customize_vars.SINGLE_DIM_0 * variational_customize_vars.SINGLE_DIM_1 / 4) - - result_queue = queue.Queue() - - thread = threading.Thread(target=solve_anneal, args=(bounds, ansatz_type, result_queue)) - thread.start() - - timer = threading.Timer(duration, interrupt_function) - timer.start() - - thread.join() - f, x = result_queue.get() - # f, x = solve_anneal(bounds, ansatz_type) - - return f, x diff --git a/src/mqt/qudits/compiler/twodit/mapping_un_aware_transpilation/variational_twodit_compilation/opt/optimizer.py b/src/mqt/qudits/compiler/twodit/mapping_un_aware_transpilation/variational_twodit_compilation/opt/optimizer.py deleted file mode 100755 index 1ce74ea..0000000 --- a/src/mqt/qudits/compiler/twodit/mapping_un_aware_transpilation/variational_twodit_compilation/opt/optimizer.py +++ /dev/null @@ -1,93 +0,0 @@ -from mqt.qudits.compiler.twodit.mapping_un_aware_transpilation.variational_twodit_compilation import ( - variational_customize_vars, -) -from mqt.qudits.compiler.twodit.mapping_un_aware_transpilation.variational_twodit_compilation.ansatz.ansatz_gen import ( - cu_ansatz, - ls_ansatz, - ms_ansatz, -) -from mqt.qudits.compiler.twodit.mapping_un_aware_transpilation.variational_twodit_compilation.ansatz.parametrize import ( - reindex, -) -from mqt.qudits.compiler.twodit.mapping_un_aware_transpilation.variational_twodit_compilation.opt.distance_measures import ( - fidelity_on_unitares, -) -from mqt.qudits.compiler.twodit.mapping_un_aware_transpilation.variational_twodit_compilation.variational_customize_vars import ( - OBJ_FIDELITY, - SINGLE_DIM_0, - SINGLE_DIM_1, - TARGET_GATE, - timer_var, -) -from mqt.qudits.exceptions.compilerexception import FidelityReachException -from scipy.optimize import dual_annealing - - -def bounds_assigner(b1, b2, b3, num_params_single, d): - assignment = [None] * (num_params_single + 1) - - for m in range(0, d): - for n in range(0, d): - if m == n: - assignment[reindex(m, n, d)] = b3 - elif m > n: - assignment[reindex(m, n, d)] = b1 - else: - assignment[reindex(m, n, d)] = b2 - - return assignment[:-1] # dont return last eleement which is just a global phase - - -def obj_fun_core(ansatz, lambdas): - print(1 - fidelity_on_unitares(ansatz, TARGET_GATE)) - - if (1 - fidelity_on_unitares(ansatz, TARGET_GATE)) < OBJ_FIDELITY: - variational_customize_vars.X_SOLUTION = lambdas - - variational_customize_vars.FUN_SOLUTION = 1 - fidelity_on_unitares(ansatz, TARGET_GATE) - - raise FidelityReachException - if timer_var: - raise TimeoutError - - return 1 - fidelity_on_unitares(ansatz, TARGET_GATE) - - -def objective_fnc_ms(lambdas): - ansatz = ms_ansatz(lambdas, [SINGLE_DIM_0, SINGLE_DIM_1]) - return obj_fun_core(ansatz, lambdas) - - -def objective_fnc_ls(lambdas): - ansatz = ls_ansatz(lambdas, [SINGLE_DIM_0, SINGLE_DIM_1]) - return obj_fun_core(ansatz, lambdas) - - -def objective_fnc_cu(lambdas): - ansatz = cu_ansatz(lambdas, [SINGLE_DIM_0, SINGLE_DIM_1]) - return obj_fun_core(ansatz, lambdas) - - -def solve_anneal(bounds, ansatz_type, result_queue): - try: - if ansatz_type == "MS": # MS is 0 - opt = dual_annealing(objective_fnc_ms, bounds=bounds) - elif ansatz_type == "LS": # LS is 1 - opt = dual_annealing(objective_fnc_ls, bounds=bounds) - elif ansatz_type == "CU": - opt = dual_annealing(objective_fnc_cu, bounds=bounds) - else: - opt = None - - x = opt.x - fun = opt.fun - - result_queue.put((fun, x)) - - except FidelityReachException as e: - print("FidelityReachException ", e) - result_queue.put((variational_customize_vars.FUN_SOLUTION, variational_customize_vars.X_SOLUTION)) - - except TimeoutError as e: - print("Execution Time Out", e) - result_queue.put((variational_customize_vars.FUN_SOLUTION, variational_customize_vars.X_SOLUTION)) diff --git a/src/mqt/qudits/compiler/twodit/mapping_un_aware_transpilation/variational_twodit_compilation/variational_customize_vars.py b/src/mqt/qudits/compiler/twodit/mapping_un_aware_transpilation/variational_twodit_compilation/variational_customize_vars.py deleted file mode 100755 index d3cdd7e..0000000 --- a/src/mqt/qudits/compiler/twodit/mapping_un_aware_transpilation/variational_twodit_compilation/variational_customize_vars.py +++ /dev/null @@ -1,15 +0,0 @@ -OBJ_FIDELITY = 1e-4 - -SINGLE_DIM_0 = None -SINGLE_DIM_1 = None - -TARGET_GATE = None -MAX_NUM_LAYERS = None # (2 * SINGLE_DIM_0 * SINGLE_DIM_1) - -X_SOLUTION = [] -FUN_SOLUTION = [] - - -CUSTOM_PRIMITIVE = None # numpy array - -timer_var = False diff --git a/src/mqt/qudits/compiler/state_prep/__init__.py b/src/mqt/qudits/compiler/twodit/variational_twodit_compilation/__init__.py old mode 100644 new mode 100755 similarity index 100% rename from src/mqt/qudits/compiler/state_prep/__init__.py rename to src/mqt/qudits/compiler/twodit/variational_twodit_compilation/__init__.py diff --git a/src/mqt/qudits/compiler/twodit/variational_twodit_compilation/ansatz/__init__.py b/src/mqt/qudits/compiler/twodit/variational_twodit_compilation/ansatz/__init__.py new file mode 100755 index 0000000..fb5637c --- /dev/null +++ b/src/mqt/qudits/compiler/twodit/variational_twodit_compilation/ansatz/__init__.py @@ -0,0 +1,15 @@ +from __future__ import annotations + +from .ansatz_gen import cu_ansatz, ls_ansatz, ms_ansatz +from .instantiate import create_cu_instance, create_ls_instance, create_ms_instance +from .parametrize import reindex + +__all__ = [ + "create_cu_instance", + "create_ls_instance", + "create_ms_instance", + "cu_ansatz", + "ls_ansatz", + "ms_ansatz", + "reindex", +] diff --git a/src/mqt/qudits/compiler/twodit/mapping_un_aware_transpilation/variational_twodit_compilation/ansatz/ansatz_gen.py b/src/mqt/qudits/compiler/twodit/variational_twodit_compilation/ansatz/ansatz_gen.py similarity index 65% rename from src/mqt/qudits/compiler/twodit/mapping_un_aware_transpilation/variational_twodit_compilation/ansatz/ansatz_gen.py rename to src/mqt/qudits/compiler/twodit/variational_twodit_compilation/ansatz/ansatz_gen.py index 818e127..cc66a5b 100755 --- a/src/mqt/qudits/compiler/twodit/mapping_un_aware_transpilation/variational_twodit_compilation/ansatz/ansatz_gen.py +++ b/src/mqt/qudits/compiler/twodit/variational_twodit_compilation/ansatz/ansatz_gen.py @@ -1,14 +1,10 @@ +from __future__ import annotations + import numpy as np -from mqt.qudits.compiler.compilation_minitools.numerical_ansatz_utils import gate_expand_to_circuit -from mqt.qudits.compiler.twodit.mapping_un_aware_transpilation.variational_twodit_compilation import ( - variational_customize_vars, -) -from mqt.qudits.compiler.twodit.mapping_un_aware_transpilation.variational_twodit_compilation.ansatz.parametrize import ( - generic_sud, - params_splitter, -) -from mqt.qudits.qudit_circuits.components.instructions.gate_set.ls import LS -from mqt.qudits.qudit_circuits.components.instructions.gate_set.ms import MS + +from .....quantum_circuit import gates +from ....compilation_minitools import gate_expand_to_circuit +from .parametrize import CUSTOM_PRIMITIVE, generic_sud, params_splitter def prepare_ansatz(u, params, dims): @@ -31,13 +27,13 @@ def prepare_ansatz(u, params, dims): def cu_ansatz(P, dims): params = params_splitter(P, dims) - cu = variational_customize_vars.CUSTOM_PRIMITIVE + cu = CUSTOM_PRIMITIVE return prepare_ansatz(cu, params, dims) def ms_ansatz(P, dims): params = params_splitter(P, dims) - ms = MS( + ms = gates.MS( None, "MS", None, @@ -59,7 +55,7 @@ def ls_ansatz(P, dims): else: theta = np.pi - ls = LS( + ls = gates.LS( None, "LS", None, diff --git a/src/mqt/qudits/compiler/twodit/mapping_un_aware_transpilation/variational_twodit_compilation/ansatz/instantiate.py b/src/mqt/qudits/compiler/twodit/variational_twodit_compilation/ansatz/instantiate.py similarity index 66% rename from src/mqt/qudits/compiler/twodit/mapping_un_aware_transpilation/variational_twodit_compilation/ansatz/instantiate.py rename to src/mqt/qudits/compiler/twodit/variational_twodit_compilation/ansatz/instantiate.py index dac5552..6942400 100755 --- a/src/mqt/qudits/compiler/twodit/mapping_un_aware_transpilation/variational_twodit_compilation/ansatz/instantiate.py +++ b/src/mqt/qudits/compiler/twodit/variational_twodit_compilation/ansatz/instantiate.py @@ -1,14 +1,10 @@ +from __future__ import annotations + import numpy as np -from mqt.qudits.compiler.compilation_minitools.numerical_ansatz_utils import gate_expand_to_circuit -from mqt.qudits.compiler.twodit.mapping_un_aware_transpilation.variational_twodit_compilation.ansatz.parametrize import ( - generic_sud, - params_splitter, -) -from mqt.qudits.compiler.twodit.mapping_un_aware_transpilation.variational_twodit_compilation.variational_customize_vars import ( - CUSTOM_PRIMITIVE, -) -from mqt.qudits.qudit_circuits.components.instructions.gate_set.ls import LS -from mqt.qudits.qudit_circuits.components.instructions.gate_set.ms import MS + +from .....quantum_circuit import gates +from ....compilation_minitools import gate_expand_to_circuit +from .parametrize import CUSTOM_PRIMITIVE, generic_sud, params_splitter def ansatz_decompose(u, params, dims): @@ -37,7 +33,7 @@ def create_cu_instance(P, dims): def create_ms_instance(P, dims): params = params_splitter(P, dims) - ms = MS( + ms = gates.MS( None, "MS", None, @@ -59,7 +55,7 @@ def create_ls_instance(P, dims): else: theta = np.pi - ls = LS( + ls = gates.LS( None, "LS", None, diff --git a/src/mqt/qudits/compiler/twodit/mapping_un_aware_transpilation/variational_twodit_compilation/ansatz/parametrize.py b/src/mqt/qudits/compiler/twodit/variational_twodit_compilation/ansatz/parametrize.py similarity index 89% rename from src/mqt/qudits/compiler/twodit/mapping_un_aware_transpilation/variational_twodit_compilation/ansatz/parametrize.py rename to src/mqt/qudits/compiler/twodit/variational_twodit_compilation/ansatz/parametrize.py index 888d855..150f5ae 100755 --- a/src/mqt/qudits/compiler/twodit/mapping_un_aware_transpilation/variational_twodit_compilation/ansatz/parametrize.py +++ b/src/mqt/qudits/compiler/twodit/variational_twodit_compilation/ansatz/parametrize.py @@ -1,7 +1,12 @@ +from __future__ import annotations + import numpy as np -from mqt.qudits.qudit_circuits.components.instructions.mini_tools.matrix_factory_tools import from_dirac_to_basis from scipy.linalg import expm +from .....quantum_circuit.matrix_factory import from_dirac_to_basis + +CUSTOM_PRIMITIVE = None # numpy array + """ def params_splitter(params, dim): n = (-1 + dim ** 2) @@ -44,14 +49,14 @@ def reindex(ir, jc, num_col): def generic_sud(params, dimension) -> np.ndarray: # required well-structured d2 -1 params c_unitary = np.identity(dimension, dtype="complex") - for diag_index in range(0, dimension - 1): + for diag_index in range(dimension - 1): l_vec = from_dirac_to_basis([diag_index], dimension) d_vec = from_dirac_to_basis([dimension - 1], dimension) zld = np.outer(np.array(l_vec), np.array(l_vec).T.conj()) - np.outer(np.array(d_vec), np.array(d_vec).T.conj()) c_unitary = c_unitary @ expm(1j * params[reindex(diag_index, diag_index, dimension)] * zld) - for m in range(0, dimension - 1): + for m in range(dimension - 1): for n in range(m + 1, dimension): m_vec = from_dirac_to_basis([m], dimension) n_vec = from_dirac_to_basis([n], dimension) diff --git a/src/mqt/qudits/compiler/twodit/variational_twodit_compilation/ansatz_solve_n_search.py b/src/mqt/qudits/compiler/twodit/variational_twodit_compilation/ansatz_solve_n_search.py new file mode 100755 index 0000000..3f9ae5b --- /dev/null +++ b/src/mqt/qudits/compiler/twodit/variational_twodit_compilation/ansatz_solve_n_search.py @@ -0,0 +1,79 @@ +from __future__ import annotations + +import queue +import threading + +import numpy as np + +from .ansatz.parametrize import ( + bound_1, + bound_2, + bound_3, +) +from .opt import Optimizer + + +def interrupt_function() -> None: + Optimizer.timer_var = True + + +def binary_search_compile(max_num_layer, ansatz_type): + if max_num_layer < 0: + raise Exception + + counter = 0 + low = 0 + high = max_num_layer + + tol = Optimizer.OBJ_FIDELITY + + best_layer, best_error, best_xi = (low + (high - low) // 2, np.inf, []) + mid, error, xi = (low + (high - low) // 2, np.inf, []) + + # Repeat until the pointers low and high meet each other + while low <= high: + mid = low + (high - low) // 2 + + error, xi = run(mid, ansatz_type) + + if error > tol: + low = mid + 1 + else: + high = mid - 1 + best_layer, best_error, best_xi = (mid, error, xi) + + counter += 1 + + return best_layer, best_error, best_xi + + +def run(num_layer, ansatz_type): + num_params_single_unitary_line_0 = -1 + Optimizer.SINGLE_DIM_0**2 + num_params_single_unitary_line_1 = -1 + Optimizer.SINGLE_DIM_1**2 + + bounds_line_0 = Optimizer.bounds_assigner( + bound_1, bound_2, bound_3, num_params_single_unitary_line_0**2, Optimizer.SINGLE_DIM_0 + ) * (num_layer + 1) + bounds_line_1 = Optimizer.bounds_assigner( + bound_1, bound_2, bound_3, num_params_single_unitary_line_1**2, Optimizer.SINGLE_DIM_1 + ) * (num_layer + 1) + + bounds = [ + bounds_line_0[i] if i % 2 == 0 else bounds_line_1[i] for i in range(max(len(bounds_line_0), len(bounds_line_1))) + ] + + duration = 3600 * (Optimizer.SINGLE_DIM_0 * Optimizer.SINGLE_DIM_1 / 4) + + result_queue = queue.Queue() + + thread = threading.Thread(target=Optimizer.solve_anneal, args=(bounds, ansatz_type, result_queue)) + thread.start() + + timer = threading.Timer(duration, interrupt_function) + timer.start() + + thread.join() + f, x = result_queue.get() + # f, x = solve_anneal(bounds, ansatz_type) + + return f, x diff --git a/src/mqt/qudits/compiler/twodit/variational_twodit_compilation/opt/__init__.py b/src/mqt/qudits/compiler/twodit/variational_twodit_compilation/opt/__init__.py new file mode 100755 index 0000000..cc26b51 --- /dev/null +++ b/src/mqt/qudits/compiler/twodit/variational_twodit_compilation/opt/__init__.py @@ -0,0 +1,21 @@ +from __future__ import annotations + +from .distance_measures import ( + density_operator, + fidelity_on_density_operator, + fidelity_on_operator, + fidelity_on_unitares, + frobenius_dist, + size_check, +) +from .optimizer import Optimizer + +__all__ = [ + "Optimizer", + "density_operator", + "fidelity_on_density_operator", + "fidelity_on_operator", + "fidelity_on_unitares", + "frobenius_dist", + "size_check", +] diff --git a/src/mqt/qudits/compiler/twodit/mapping_un_aware_transpilation/variational_twodit_compilation/opt/distance_measures.py b/src/mqt/qudits/compiler/twodit/variational_twodit_compilation/opt/distance_measures.py similarity index 93% rename from src/mqt/qudits/compiler/twodit/mapping_un_aware_transpilation/variational_twodit_compilation/opt/distance_measures.py rename to src/mqt/qudits/compiler/twodit/variational_twodit_compilation/opt/distance_measures.py index 2941a7c..97fb330 100755 --- a/src/mqt/qudits/compiler/twodit/mapping_un_aware_transpilation/variational_twodit_compilation/opt/distance_measures.py +++ b/src/mqt/qudits/compiler/twodit/variational_twodit_compilation/opt/distance_measures.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import typing import numpy as np @@ -7,10 +9,7 @@ def size_check(a: np.ndarray, b: np.ndarray) -> bool: - if a.shape == b.shape and a.shape[0] == a.shape[1]: - return True - - return False + return bool(a.shape == b.shape and a.shape[0] == a.shape[1]) def fidelity_on_operator(a: np.ndarray, b: np.ndarray) -> float: diff --git a/src/mqt/qudits/compiler/twodit/variational_twodit_compilation/opt/optimizer.py b/src/mqt/qudits/compiler/twodit/variational_twodit_compilation/opt/optimizer.py new file mode 100755 index 0000000..c53ec34 --- /dev/null +++ b/src/mqt/qudits/compiler/twodit/variational_twodit_compilation/opt/optimizer.py @@ -0,0 +1,92 @@ +from __future__ import annotations + +from scipy.optimize import dual_annealing + +from .....exceptions import FidelityReachException +from ..ansatz import ( + cu_ansatz, + ls_ansatz, + ms_ansatz, + reindex, +) +from .distance_measures import fidelity_on_unitares + + +class Optimizer: + OBJ_FIDELITY = 1e-4 + + SINGLE_DIM_0 = None + SINGLE_DIM_1 = None + + TARGET_GATE = None + MAX_NUM_LAYERS = None # (2 * SINGLE_DIM_0 * SINGLE_DIM_1) + + X_SOLUTION = [] + FUN_SOLUTION = [] + + timer_var = False + + @staticmethod + def bounds_assigner(b1, b2, b3, num_params_single, d): + assignment = [None] * (num_params_single + 1) + + for m in range(d): + for n in range(d): + if m == n: + assignment[reindex(m, n, d)] = b3 + elif m > n: + assignment[reindex(m, n, d)] = b1 + else: + assignment[reindex(m, n, d)] = b2 + + return assignment[:-1] # dont return last eleement which is just a global phase + + @classmethod + def obj_fun_core(cls, ansatz, lambdas): + if (1 - fidelity_on_unitares(ansatz, cls.TARGET_GATE)) < cls.OBJ_FIDELITY: + cls.X_SOLUTION = lambdas + cls.FUN_SOLUTION = 1 - fidelity_on_unitares(ansatz, cls.TARGET_GATE) + + raise FidelityReachException + if cls.timer_var: + raise TimeoutError + + return 1 - fidelity_on_unitares(ansatz, cls.TARGET_GATE) + + @classmethod + def objective_fnc_ms(cls, lambdas): + ansatz = ms_ansatz(lambdas, [cls.SINGLE_DIM_0, cls.SINGLE_DIM_1]) + return cls.obj_fun_core(ansatz, lambdas) + + @classmethod + def objective_fnc_ls(cls, lambdas): + ansatz = ls_ansatz(lambdas, [cls.SINGLE_DIM_0, cls.SINGLE_DIM_1]) + return cls.obj_fun_core(ansatz, lambdas) + + @classmethod + def objective_fnc_cu(cls, lambdas): + ansatz = cu_ansatz(lambdas, [cls.SINGLE_DIM_0, cls.SINGLE_DIM_1]) + return cls.obj_fun_core(ansatz, lambdas) + + @classmethod + def solve_anneal(cls, bounds, ansatz_type, result_queue) -> None: + try: + if ansatz_type == "MS": # MS is 0 + opt = dual_annealing(cls.objective_fnc_ms, bounds=bounds) + elif ansatz_type == "LS": # LS is 1 + opt = dual_annealing(cls.objective_fnc_ls, bounds=bounds) + elif ansatz_type == "CU": + opt = dual_annealing(cls.objective_fnc_cu, bounds=bounds) + else: + opt = None + + x = opt.x + fun = opt.fun + + result_queue.put((fun, x)) + + except FidelityReachException: + result_queue.put((cls.FUN_SOLUTION, cls.X_SOLUTION)) + + except TimeoutError: + result_queue.put((cls.FUN_SOLUTION, cls.X_SOLUTION)) diff --git a/src/mqt/qudits/core/__init__.py b/src/mqt/qudits/core/__init__.py index e69de29..d43fbe3 100644 --- a/src/mqt/qudits/core/__init__.py +++ b/src/mqt/qudits/core/__init__.py @@ -0,0 +1,12 @@ +"""Core structure used in the package.""" + +from __future__ import annotations + +from .dfs_tree import NAryTree, Node +from .level_graph import LevelGraph + +__all__ = [ + "LevelGraph", + "NAryTree", + "Node", +] diff --git a/src/mqt/qudits/core/structures/trees/dfs_tree.py b/src/mqt/qudits/core/dfs_tree.py similarity index 83% rename from src/mqt/qudits/core/structures/trees/dfs_tree.py rename to src/mqt/qudits/core/dfs_tree.py index 2f6480e..ae2cf62 100644 --- a/src/mqt/qudits/core/structures/trees/dfs_tree.py +++ b/src/mqt/qudits/core/dfs_tree.py @@ -1,4 +1,6 @@ -from mqt.qudits.exceptions.compilerexception import NodeNotFoundException +from __future__ import annotations + +from ..exceptions import NodeNotFoundException class Node: @@ -13,10 +15,10 @@ def __init__( max_cost, pi_pulses, parent_key, - childs=None, - ): + children=None, + ) -> None: self.key = key - self.children = childs + self.children = children self.rotation = rotation self.U_of_level = U_of_level self.finished = False @@ -28,7 +30,9 @@ def __init__( self.graph = graph_current self.PI_PULSES = pi_pulses - def add(self, new_key, rotation, U_of_level, graph_current, current_cost, current_decomp_cost, max_cost, pi_pulses): + def add( + self, new_key, rotation, U_of_level, graph_current, current_cost, current_decomp_cost, max_cost, pi_pulses + ) -> None: # TODO refactor so that size is kept track also in the tree upper structure new_node = Node( @@ -49,14 +53,14 @@ def add(self, new_key, rotation, U_of_level, graph_current, current_cost, curren self.size += 1 - def __str__(self): + def __str__(self) -> str: return str(self.key) class NAryTree: - # todo put method to refresh size when algortihm has finished + # todo put method to refresh size when algorithm has finished - def __init__(self): + def __init__(self) -> None: self.root = None self.size = 0 self.global_id_counter = 0 @@ -72,7 +76,7 @@ def add( max_cost, pi_pulses, parent_key=None, - ): + ) -> None: if parent_key is None: self.root = Node( new_key, @@ -126,12 +130,11 @@ def max_depth(self, node): def size_refresh(self, node): if node.children is None or len(node.children) == 0: return 0 - else: - children_size = len(node.children) - for child in node.children: - children_size = children_size + self.size_refresh(child) + children_size = len(node.children) + for child in node.children: + children_size = children_size + self.size_refresh(child) - return children_size + return children_size def found_checker(self, node): if not node.children: @@ -148,16 +151,15 @@ def found_checker(self, node): def min_cost_decomp(self, node): if not node.children: return [node], (node.current_cost, node.current_decomp_cost), node.graph - else: - children_cost = [] + children_cost = [] - for child in node.children: - if child.finished: - children_cost.append(self.min_cost_decomp(child)) + for child in node.children: + if child.finished: + children_cost.append(self.min_cost_decomp(child)) - minimum_child, best_cost, final_graph = min(children_cost, key=lambda t: t[1][0]) - minimum_child.insert(0, node) - return minimum_child, best_cost, final_graph + minimum_child, best_cost, final_graph = min(children_cost, key=lambda t: t[1][0]) + minimum_child.insert(0, node) + return minimum_child, best_cost, final_graph def retrieve_decomposition(self, node): self.found_checker(node) @@ -198,5 +200,5 @@ def print_tree(self, node, str_aux): str_aux += ")" return str_aux - def __str__(self): + def __str__(self) -> str: return self.print_tree(self.root, "") diff --git a/src/mqt/qudits/core/structures/energy_level_graph/level_graph.py b/src/mqt/qudits/core/level_graph.py similarity index 95% rename from src/mqt/qudits/core/structures/energy_level_graph/level_graph.py rename to src/mqt/qudits/core/level_graph.py index 1d99bab..d54fb56 100644 --- a/src/mqt/qudits/core/structures/energy_level_graph/level_graph.py +++ b/src/mqt/qudits/core/level_graph.py @@ -1,12 +1,15 @@ +from __future__ import annotations + import copy import networkx as nx import numpy as np -from mqt.qudits.qudit_circuits.components.instructions.gate_set.virt_rz import VirtRz + +from ..quantum_circuit.gates.virt_rz import VirtRz class LevelGraph(nx.Graph): - def __init__(self, edges, nodes, nodes_physical_mapping=None, initialization_nodes=None): + def __init__(self, edges, nodes, nodes_physical_mapping=None, initialization_nodes=None) -> None: super().__init__() self.logic_nodes = nodes self.add_nodes_from(self.logic_nodes) @@ -20,7 +23,7 @@ def __init__(self, edges, nodes, nodes_physical_mapping=None, initialization_nod inreach_nodes = [x for x in nodes if x not in initialization_nodes] self.define__states(initialization_nodes, inreach_nodes) - def phase_storing_setup(self): + def phase_storing_setup(self) -> None: for node in self.nodes: node_dict = self.nodes[node] if "phase_storage" not in node_dict: @@ -41,11 +44,11 @@ def distance_nodes_pi_pulses_fixed_ancilla(self, source, target): negs += 1 return (2 * negs) - 1 + (pos) - 1 - def logic_physical_map(self, physical_nodes): + def logic_physical_map(self, physical_nodes) -> None: logic_phy_map = dict(zip(self.logic_nodes, physical_nodes)) nx.set_node_attributes(self, logic_phy_map, "lpmap") - def define__states(self, initialization_nodes, inreach_nodes): + def define__states(self, initialization_nodes, inreach_nodes) -> None: inreach_dictionary = dict.fromkeys(inreach_nodes, "r") initialization_dictionary = dict.fromkeys(initialization_nodes, "i") @@ -118,7 +121,7 @@ def swap_node_attributes(self, node_a, node_b): return nodelistcopy - def swap_node_attr_simple(self, node_a, node_b): + def swap_node_attr_simple(self, node_a, node_b) -> None: res_list = [x[0] for x in self.nodes(data=True)] node_a = res_list.index(node_a) node_b = res_list.index(node_b) @@ -205,5 +208,5 @@ def log_phy_map(self): map_as_list.append(N[1]["lpmap"]) return map_as_list - def __str__(self): + def __str__(self) -> str: return str(self.nodes(data=True)) + "\n" + str(self.edges(data=True)) diff --git a/src/mqt/qudits/exceptions/__init__.py b/src/mqt/qudits/exceptions/__init__.py index e69de29..e29e71f 100644 --- a/src/mqt/qudits/exceptions/__init__.py +++ b/src/mqt/qudits/exceptions/__init__.py @@ -0,0 +1,19 @@ +"""Exceptions modul.""" + +from __future__ import annotations + +from .backenderror import BackendNotFoundError +from .circuiterror import CircuitError +from .compilerexception import FidelityReachException, NodeNotFoundException, RoutingException, SequenceFoundException +from .joberror import JobError, JobTimeoutError + +__all__ = [ + "BackendNotFoundError", + "CircuitError", + "FidelityReachException", + "JobError", + "JobTimeoutError", + "NodeNotFoundException", + "RoutingException", + "SequenceFoundException", +] diff --git a/src/mqt/qudits/quantum_circuit/__init__.py b/src/mqt/qudits/quantum_circuit/__init__.py new file mode 100644 index 0000000..dcd9b9e --- /dev/null +++ b/src/mqt/qudits/quantum_circuit/__init__.py @@ -0,0 +1,9 @@ +"""Qudit Quantum Circuit Module.""" + +from __future__ import annotations + +from .circuit import QuantumCircuit + +__all__ = [ + "QuantumCircuit", +] diff --git a/src/mqt/qudits/qudit_circuits/circuit.py b/src/mqt/qudits/quantum_circuit/circuit.py similarity index 73% rename from src/mqt/qudits/qudit_circuits/circuit.py rename to src/mqt/qudits/quantum_circuit/circuit.py index 53e06c8..26e9c88 100644 --- a/src/mqt/qudits/qudit_circuits/circuit.py +++ b/src/mqt/qudits/quantum_circuit/circuit.py @@ -1,34 +1,55 @@ from __future__ import annotations import copy +import locale import warnings from typing import TYPE_CHECKING -from mqt.qudits.qudit_circuits.components.instructions.gate_set.csum import CSum -from mqt.qudits.qudit_circuits.components.instructions.gate_set.custom_multi import CustomMulti -from mqt.qudits.qudit_circuits.components.instructions.gate_set.custom_one import CustomOne -from mqt.qudits.qudit_circuits.components.instructions.gate_set.custom_two import CustomTwo -from mqt.qudits.qudit_circuits.components.instructions.gate_set.cx import CEx -from mqt.qudits.qudit_circuits.components.instructions.gate_set.gellman import GellMann -from mqt.qudits.qudit_circuits.components.instructions.gate_set.h import H -from mqt.qudits.qudit_circuits.components.instructions.gate_set.ls import LS -from mqt.qudits.qudit_circuits.components.instructions.gate_set.ms import MS -from mqt.qudits.qudit_circuits.components.instructions.gate_set.perm import Perm -from mqt.qudits.qudit_circuits.components.instructions.gate_set.r import R -from mqt.qudits.qudit_circuits.components.instructions.gate_set.randu import RandU -from mqt.qudits.qudit_circuits.components.instructions.gate_set.rh import Rh -from mqt.qudits.qudit_circuits.components.instructions.gate_set.rz import Rz -from mqt.qudits.qudit_circuits.components.instructions.gate_set.s import S -from mqt.qudits.qudit_circuits.components.instructions.gate_set.virt_rz import VirtRz -from mqt.qudits.qudit_circuits.components.instructions.gate_set.x import X -from mqt.qudits.qudit_circuits.components.instructions.gate_set.z import Z -from mqt.qudits.qudit_circuits.components.registers.quantum_register import QuantumRegister -from mqt.qudits.qudit_circuits.qasm_interface.qasm import QASM +import gates + +from .qasm import QASM if TYPE_CHECKING: import numpy as np - from mqt.qudits.qudit_circuits.components.instructions.gate import Gate - from mqt.qudits.qudit_circuits.components.instructions.gate_extensions.controls import ControlData + + +class QuantumRegister: + @classmethod + def from_map(cls, sitemap: dict) -> list[QuantumRegister]: + registers_map = {} + + for qreg_with_index, line_info in sitemap.items(): + reg_name, inreg_line_index = qreg_with_index + if reg_name not in registers_map: + registers_map[reg_name] = [{inreg_line_index: line_info[0]}, [line_info[1]]] + else: + registers_map[reg_name][0][inreg_line_index] = line_info[0] + registers_map[reg_name][1].append(line_info[1]) + # print(registers_map) + registers_from_qasm = [] + for label, data in registers_map.items(): + temp = QuantumRegister(label, len(data[0]), data[1]) + temp.local_sitemap = data[0] + registers_from_qasm.append(temp) + # print(registers_from_qasm) + return registers_from_qasm + + def __init__(self, name, size, dims=None) -> None: + self.label = name + self.size = size + self.dimensions = size * [2] if dims is None else dims + self.local_sitemap = {} + + def __qasm__(self): + string_dims = str(self.dimensions).replace(" ", "") + return "qreg " + self.label + " [" + str(self.size) + "]" + string_dims + ";" + + def __getitem__(self, key): + if isinstance(key, slice): + start, stop = key.start, key.stop + return [self.local_sitemap[i] for i in range(start, stop)] + + return self.local_sitemap[key] def add_gate_decorator(func): @@ -63,7 +84,7 @@ class QuantumCircuit: "z": "z", } - def __init__(self, *args): + def __init__(self, *args) -> None: self.inverse_sitemap = {} self.number_gates = 0 self.instructions = [] @@ -100,7 +121,7 @@ def num_qudits(self): def dimensions(self): return self._dimensions - def reset(self): + def reset(self) -> None: self.number_gates = 0 self.instructions = [] self.quantum_registers = [] @@ -113,7 +134,7 @@ def reset(self): def copy(self): return copy.deepcopy(self) - def append(self, qreg: QuantumRegister): + def append(self, qreg: QuantumRegister) -> None: self.quantum_registers.append(qreg) self._num_qudits += qreg.size self._dimensions += qreg.dimensions @@ -126,19 +147,19 @@ def append(self, qreg: QuantumRegister): @add_gate_decorator def csum(self, qudits: list[int]): - return CSum( + return gates.CSum( self, "CSum" + str([self.dimensions[i] for i in qudits]), qudits, [self.dimensions[i] for i in qudits], None ) @add_gate_decorator def cu_one(self, qudits: int, parameters: np.ndarray, controls: ControlData | None = None): - return CustomOne( + return gates.CustomOne( self, "CUo" + str(self.dimensions[qudits]), qudits, parameters, self.dimensions[qudits], controls ) @add_gate_decorator def cu_two(self, qudits: int, parameters: np.ndarray, controls: ControlData | None = None): - return CustomTwo( + return gates.CustomTwo( self, "CUt" + str([self.dimensions[i] for i in qudits]), qudits, @@ -149,7 +170,7 @@ def cu_two(self, qudits: int, parameters: np.ndarray, controls: ControlData | No @add_gate_decorator def cu_multi(self, qudits: int, parameters: np.ndarray, controls: ControlData | None = None): - return CustomMulti( + return gates.CustomMulti( self, "CUm" + str([self.dimensions[i] for i in qudits]), qudits, @@ -160,7 +181,7 @@ def cu_multi(self, qudits: int, parameters: np.ndarray, controls: ControlData | @add_gate_decorator def cx(self, qudits: list[int], parameters: list | None = None): - return CEx( + return gates.CEx( self, "CEx" + str([self.dimensions[i] for i in qudits]), qudits, @@ -172,19 +193,21 @@ def cx(self, qudits: list[int], parameters: list | None = None): @add_gate_decorator def gellmann(self, qudit: int, parameters: list | None = None, controls: ControlData | None = None): warnings.warn("Using this matrix in a circuit will not allow simulation.", UserWarning) - return GellMann(self, "Gell" + str(self.dimensions[qudit]), qudit, parameters, self.dimensions[qudit], controls) + return gates.GellMann( + self, "Gell" + str(self.dimensions[qudit]), qudit, parameters, self.dimensions[qudit], controls + ) @add_gate_decorator def h(self, qudit: int, controls: ControlData | None = None): - return H(self, "H" + str(self.dimensions[qudit]), qudit, self.dimensions[qudit], controls) + return gates.H(self, "H" + str(self.dimensions[qudit]), qudit, self.dimensions[qudit], controls) @add_gate_decorator def rh(self, qudit: int, controls: ControlData | None = None): - return Rh(self, "Rh" + str(self.dimensions[qudit]), qudit, self.dimensions[qudit], controls) + return gates.Rh(self, "Rh" + str(self.dimensions[qudit]), qudit, self.dimensions[qudit], controls) @add_gate_decorator def ls(self, qudits: list[int], parameters: list | None = None): - return LS( + return gates.LS( self, "LS" + str([self.dimensions[i] for i in qudits]), qudits, @@ -195,7 +218,7 @@ def ls(self, qudits: list[int], parameters: list | None = None): @add_gate_decorator def ms(self, qudits: list[int], parameters: list | None = None): - return MS( + return gates.MS( self, "MS" + str([self.dimensions[i] for i in qudits]), qudits, @@ -206,7 +229,7 @@ def ms(self, qudits: list[int], parameters: list | None = None): @add_gate_decorator def pm(self, qudits: list[int], parameters: list): - return Perm( + return gates.Perm( self, "Pm" + str([self.dimensions[i] for i in qudits]), qudits, @@ -217,34 +240,36 @@ def pm(self, qudits: list[int], parameters: list): @add_gate_decorator def r(self, qudit: int, parameters: list, controls: ControlData | None = None): - return R(self, "R" + str(self.dimensions[qudit]), qudit, parameters, self.dimensions[qudit], controls) + return gates.R(self, "R" + str(self.dimensions[qudit]), qudit, parameters, self.dimensions[qudit], controls) @add_gate_decorator def randu(self, qudits: list[int]): - return RandU( + return gates.RandU( self, "RandU" + str([self.dimensions[i] for i in qudits]), qudits, [self.dimensions[i] for i in qudits] ) @add_gate_decorator def rz(self, qudit: int, parameters: list, controls: ControlData | None = None): - return Rz(self, "Rz" + str(self.dimensions[qudit]), qudit, parameters, self.dimensions[qudit], controls) + return gates.Rz(self, "Rz" + str(self.dimensions[qudit]), qudit, parameters, self.dimensions[qudit], controls) def virtrz(self, qudit: int, parameters: list, controls: ControlData | None = None): - return VirtRz(self, "VirtRz" + str(self.dimensions[qudit]), qudit, parameters, self.dimensions[qudit], controls) + return gates.VirtRz( + self, "VirtRz" + str(self.dimensions[qudit]), qudit, parameters, self.dimensions[qudit], controls + ) @add_gate_decorator def s(self, qudit: int, controls: ControlData | None = None): - return S(self, "S" + str(self.dimensions[qudit]), qudit, self.dimensions[qudit], controls) + return gates.S(self, "S" + str(self.dimensions[qudit]), qudit, self.dimensions[qudit], controls) @add_gate_decorator def x(self, qudit: int, controls: ControlData | None = None): - return X(self, "X" + str(self.dimensions[qudit]), qudit, self.dimensions[qudit], controls) + return gates.X(self, "X" + str(self.dimensions[qudit]), qudit, self.dimensions[qudit], controls) @add_gate_decorator def z(self, qudit: int, controls: ControlData | None = None): - return Z(self, "Z" + str(self.dimensions[qudit]), qudit, self.dimensions[qudit], controls) + return gates.Z(self, "Z" + str(self.dimensions[qudit]), qudit, self.dimensions[qudit], controls) - def replace_gate(self, gate_index: int, sequence: list[Gate]): + def replace_gate(self, gate_index: int, sequence: list[Gate]) -> None: self.instructions[gate_index : gate_index + 1] = sequence self.number_gates = (self.number_gates - 1) + len(sequence) @@ -254,7 +279,7 @@ def set_instructions(self, sequence: list[Gate]): self.number_gates = len(sequence) return self - def from_qasm(self, qasm_prog): + def from_qasm(self, qasm_prog) -> None: self.reset() qasm_parser = QASM().parse_ditqasm2_str(qasm_prog) instructions = qasm_parser["instructions"] @@ -286,11 +311,10 @@ def from_qasm(self, qasm_prog): function(qudits_call, op["params"], op["controls"]) else: function(qudits_call, op["params"]) + elif op["controls"]: + function(qudits_call, op["controls"]) else: - if op["controls"]: - function(qudits_call, op["controls"]) - else: - function(qudits_call) + function(qudits_call) else: msg = "the required gate_matrix is not available anymore." raise NotImplementedError(msg) @@ -329,7 +353,7 @@ def save_to_file(self, file_name, file_path="/"): full_file_path = f"{file_path}/{file_name}.qasm" # Write the text to the file - with open(full_file_path, "w+") as file: + with open(full_file_path, "w+", encoding=locale.getpreferredencoding(False)) as file: file.write(self.to_qasm()) return full_file_path @@ -345,18 +369,17 @@ def load_from_file(self, file_path): str: The text loaded from the file. """ try: - with open(file_path) as file: + with open(file_path, encoding=locale.getpreferredencoding(False)) as file: text = file.read() return self.from_qasm(text) except FileNotFoundError: - print(f"File '{file_path}' not found.") return None - def draw(self): + def draw(self) -> None: # TODO pass @property - def gate_set(self): - for _, item in self.qasm_to_gate_set_dict.items(): - print(item) + def gate_set(self) -> None: + for _item in self.qasm_to_gate_set_dict.values(): + pass diff --git a/src/mqt/qudits/qudit_circuits/components/instructions/gate.py b/src/mqt/qudits/quantum_circuit/gate.py similarity index 88% rename from src/mqt/qudits/qudit_circuits/components/instructions/gate.py rename to src/mqt/qudits/quantum_circuit/gate.py index 8f888e5..28f1a04 100644 --- a/src/mqt/qudits/qudit_circuits/components/instructions/gate.py +++ b/src/mqt/qudits/quantum_circuit/gate.py @@ -1,23 +1,43 @@ from __future__ import annotations +import enum from abc import ABC, abstractmethod +from dataclasses import dataclass from typing import TYPE_CHECKING, Callable -from mqt.qudits.exceptions.circuiterror import CircuitError -from mqt.qudits.qudit_circuits.components.instructions.gate_extensions.controls import ControlData -from mqt.qudits.qudit_circuits.components.instructions.gate_extensions.gate_types import GateTypes -from mqt.qudits.qudit_circuits.components.instructions.gate_extensions.matrix_factory import MatrixFactory -from mqt.qudits.qudit_circuits.components.instructions.instruction import Instruction +from ..exceptions import CircuitError +from .matrix_factory import MatrixFactory if TYPE_CHECKING: - import enum - import numpy as np - from mqt.qudits.qudit_circuits.circuit import QuantumCircuit - from numpy import ndarray + + from .circuit import QuantumCircuit + + +class GateTypes(enum.Enum): + """Enumeration for job status.""" + + SINGLE = "Single Qudit Gate" + TWO = "Two Qudit Gate" + MULTI = "Multi Qudit Gate" + + +CORE_GATE_TYPES = (GateTypes.SINGLE, GateTypes.TWO, GateTypes.MULTI) + + +class Instruction(ABC): + @abstractmethod + def __init__(self, name: str) -> None: + pass + + +@dataclass +class ControlData: + indices: list[int] | int + ctrl_states: list[int] | int -class Gate(Instruction, ABC): +class Gate(Instruction): """Unitary gate_matrix.""" def __init__( @@ -147,7 +167,7 @@ def __qasm__(self) -> str: return string + ";\n" @abstractmethod - def __str__(self): + def __str__(self) -> str: # String representation for drawing? pass @@ -157,13 +177,13 @@ def check_long_range(self): self.is_long_range = any((b - a) > 1 for a, b in zip(sorted(target_qudits)[:-1], sorted(target_qudits)[1:])) return self.is_long_range - def set_gate_type_single(self): + def set_gate_type_single(self) -> None: self.gate_type = GateTypes.SINGLE - def set_gate_type_two(self): + def set_gate_type_two(self) -> None: self.gate_type = GateTypes.TWO - def set_gate_type_multi(self): + def set_gate_type_multi(self) -> None: self.gate_type = GateTypes.MULTI @property diff --git a/src/mqt/qudits/quantum_circuit/gates/__init__.py b/src/mqt/qudits/quantum_circuit/gates/__init__.py new file mode 100644 index 0000000..ed29851 --- /dev/null +++ b/src/mqt/qudits/quantum_circuit/gates/__init__.py @@ -0,0 +1,43 @@ +"""Instructions module.""" + +from __future__ import annotations + +from .csum import CSum +from .custom_multi import CustomMulti +from .custom_one import CustomOne +from .custom_two import CustomTwo +from .cx import CEx +from .gellman import GellMann +from .h import H +from .ls import LS +from .ms import MS +from .perm import Perm +from .r import R +from .randu import RandU +from .rh import Rh +from .rz import Rz +from .s import S +from .virt_rz import VirtRz +from .x import X +from .z import Z + +__all__ = [ + "LS", + "MS", + "CEx", + "CSum", + "CustomMulti", + "CustomOne", + "CustomTwo", + "GellMann", + "H", + "Perm", + "R", + "RandU", + "Rh", + "Rz", + "S", + "VirtRz", + "X", + "Z", +] diff --git a/src/mqt/qudits/qudit_circuits/components/instructions/gate_set/csum.py b/src/mqt/qudits/quantum_circuit/gates/csum.py similarity index 72% rename from src/mqt/qudits/qudit_circuits/components/instructions/gate_set/csum.py rename to src/mqt/qudits/quantum_circuit/gates/csum.py index d593d40..84f6d3c 100644 --- a/src/mqt/qudits/qudit_circuits/components/instructions/gate_set/csum.py +++ b/src/mqt/qudits/quantum_circuit/gates/csum.py @@ -3,14 +3,13 @@ from typing import TYPE_CHECKING import numpy as np -from mqt.qudits.qudit_circuits.components.instructions.gate import Gate -from mqt.qudits.qudit_circuits.components.instructions.gate_extensions.gate_types import GateTypes -from mqt.qudits.qudit_circuits.components.instructions.gate_set.x import X -from mqt.qudits.qudit_circuits.components.instructions.mini_tools.matrix_factory_tools import from_dirac_to_basis + +from ..gate import ControlData, Gate, GateTypes +from ..matrix_factory import from_dirac_to_basis +from .x import X if TYPE_CHECKING: - from mqt.qudits.qudit_circuits.circuit import QuantumCircuit - from mqt.qudits.qudit_circuits.components.instructions.gate_extensions.controls import ControlData + from ..circuit import QuantumCircuit class CSum(Gate): @@ -21,7 +20,7 @@ def __init__( target_qudits: list[int] | int, dimensions: list[int] | int, controls: ControlData | None = None, - ): + ) -> None: super().__init__( circuit=circuit, name=name, @@ -56,9 +55,9 @@ def __array__(self, dtype: str = "complex") -> np.ndarray: return matrix - def validate_parameter(self, parameter=None): + def validate_parameter(self, parameter=None) -> bool: return True - def __str__(self): + def __str__(self) -> str: # TODO pass diff --git a/src/mqt/qudits/qudit_circuits/components/instructions/gate_set/custom_multi.py b/src/mqt/qudits/quantum_circuit/gates/custom_multi.py similarity index 72% rename from src/mqt/qudits/qudit_circuits/components/instructions/gate_set/custom_multi.py rename to src/mqt/qudits/quantum_circuit/gates/custom_multi.py index a0bd166..4c1d366 100644 --- a/src/mqt/qudits/qudit_circuits/components/instructions/gate_set/custom_multi.py +++ b/src/mqt/qudits/quantum_circuit/gates/custom_multi.py @@ -3,12 +3,11 @@ from typing import TYPE_CHECKING import numpy as np -from mqt.qudits.qudit_circuits.components.instructions.gate import Gate -from mqt.qudits.qudit_circuits.components.instructions.gate_extensions.gate_types import GateTypes + +from ..gate import ControlData, Gate, GateTypes if TYPE_CHECKING: - from mqt.qudits.qudit_circuits.circuit import QuantumCircuit - from mqt.qudits.qudit_circuits.components.instructions.gate_extensions.controls import ControlData + from ..circuit import QuantumCircuit class CustomMulti(Gate): @@ -20,7 +19,7 @@ def __init__( parameters: np.ndarray, dimensions: list[int] | int, controls: ControlData | None = None, - ): + ) -> None: super().__init__( circuit=circuit, name=name, @@ -40,6 +39,6 @@ def __array__(self, dtype: str = "complex") -> np.ndarray: def validate_parameter(self, parameter=None): return isinstance(parameter, np.ndarray) - def __str__(self): + def __str__(self) -> str: # TODO pass diff --git a/src/mqt/qudits/qudit_circuits/components/instructions/gate_set/custom_one.py b/src/mqt/qudits/quantum_circuit/gates/custom_one.py similarity index 73% rename from src/mqt/qudits/qudit_circuits/components/instructions/gate_set/custom_one.py rename to src/mqt/qudits/quantum_circuit/gates/custom_one.py index 4db2843..49c6785 100644 --- a/src/mqt/qudits/qudit_circuits/components/instructions/gate_set/custom_one.py +++ b/src/mqt/qudits/quantum_circuit/gates/custom_one.py @@ -3,12 +3,11 @@ from typing import TYPE_CHECKING import numpy as np -from mqt.qudits.qudit_circuits.components.instructions.gate import Gate -from mqt.qudits.qudit_circuits.components.instructions.gate_extensions.gate_types import GateTypes + +from ..gate import ControlData, Gate, GateTypes if TYPE_CHECKING: - from mqt.qudits.qudit_circuits.circuit import QuantumCircuit - from mqt.qudits.qudit_circuits.components.instructions.gate_extensions.controls import ControlData + from ..circuit import QuantumCircuit class CustomOne(Gate): @@ -20,7 +19,7 @@ def __init__( parameters: np.ndarray, dimensions: list[int] | int, controls: ControlData | None = None, - ): + ) -> None: super().__init__( circuit=circuit, name=name, @@ -40,6 +39,6 @@ def __array__(self, dtype: str = "complex") -> np.ndarray: def validate_parameter(self, parameter=None): return isinstance(parameter, np.ndarray) - def __str__(self): + def __str__(self) -> str: # TODO pass diff --git a/src/mqt/qudits/qudit_circuits/components/instructions/gate_set/custom_two.py b/src/mqt/qudits/quantum_circuit/gates/custom_two.py similarity index 72% rename from src/mqt/qudits/qudit_circuits/components/instructions/gate_set/custom_two.py rename to src/mqt/qudits/quantum_circuit/gates/custom_two.py index 1f3c005..1481366 100644 --- a/src/mqt/qudits/qudit_circuits/components/instructions/gate_set/custom_two.py +++ b/src/mqt/qudits/quantum_circuit/gates/custom_two.py @@ -3,12 +3,11 @@ from typing import TYPE_CHECKING import numpy as np -from mqt.qudits.qudit_circuits.components.instructions.gate import Gate -from mqt.qudits.qudit_circuits.components.instructions.gate_extensions.gate_types import GateTypes + +from ..gate import ControlData, Gate, GateTypes if TYPE_CHECKING: - from mqt.qudits.qudit_circuits.circuit import QuantumCircuit - from mqt.qudits.qudit_circuits.components.instructions.gate_extensions.controls import ControlData + from ..circuit import QuantumCircuit class CustomTwo(Gate): @@ -20,7 +19,7 @@ def __init__( parameters: np.ndarray, dimensions: list[int] | int, controls: ControlData | None = None, - ): + ) -> None: super().__init__( circuit=circuit, name=name, @@ -40,6 +39,6 @@ def __array__(self, dtype: str = "complex") -> np.ndarray: def validate_parameter(self, parameter=None): return isinstance(parameter, np.ndarray) - def __str__(self): + def __str__(self) -> str: # TODO pass diff --git a/src/mqt/qudits/qudit_circuits/components/instructions/gate_set/cx.py b/src/mqt/qudits/quantum_circuit/gates/cx.py similarity index 84% rename from src/mqt/qudits/qudit_circuits/components/instructions/gate_set/cx.py rename to src/mqt/qudits/quantum_circuit/gates/cx.py index 38b751f..82e107f 100644 --- a/src/mqt/qudits/qudit_circuits/components/instructions/gate_set/cx.py +++ b/src/mqt/qudits/quantum_circuit/gates/cx.py @@ -1,18 +1,16 @@ from __future__ import annotations +import operator from functools import reduce from typing import TYPE_CHECKING import numpy as np -from mqt.qudits.qudit_circuits.components.instructions.gate import Gate -from mqt.qudits.qudit_circuits.components.instructions.gate_extensions.gate_types import GateTypes -from mqt.qudits.qudit_circuits.components.instructions.mini_tools.matrix_factory_tools import ( - from_dirac_to_basis, -) + +from ..gate import ControlData, Gate, GateTypes +from ..matrix_factory import from_dirac_to_basis if TYPE_CHECKING: - from mqt.qudits.qudit_circuits.circuit import QuantumCircuit - from mqt.qudits.qudit_circuits.components.instructions.gate_extensions.controls import ControlData + from ..circuit import QuantumCircuit class CEx(Gate): @@ -24,7 +22,7 @@ def __init__( parameters: list | None, dimensions: list[int] | int, controls: ControlData | None = None, - ): + ) -> None: super().__init__( circuit=circuit, name=name, @@ -51,7 +49,7 @@ def __array__(self, dtype: str = "complex") -> np.ndarray: else: levels_swap_low, levels_swap_high, ctrl_level, ang = self._params - dimension = reduce(lambda x, y: x * y, self._dimensions) + dimension = reduce(operator.mul, self._dimensions) dimension_ctrl, dimension_target = self._dimensions result = np.zeros((dimension, dimension), dtype="complex") @@ -78,7 +76,7 @@ def __array__(self, dtype: str = "complex") -> np.ndarray: return result - def validate_parameter(self, parameter): + def validate_parameter(self, parameter) -> bool: if parameter is None: return False assert isinstance(parameter[0], int) @@ -92,6 +90,6 @@ def validate_parameter(self, parameter): return True - def __str__(self): + def __str__(self) -> str: # TODO pass diff --git a/src/mqt/qudits/qudit_circuits/components/instructions/gate_set/gellman.py b/src/mqt/qudits/quantum_circuit/gates/gellman.py similarity index 81% rename from src/mqt/qudits/qudit_circuits/components/instructions/gate_set/gellman.py rename to src/mqt/qudits/quantum_circuit/gates/gellman.py index bc330ca..2e59a75 100644 --- a/src/mqt/qudits/qudit_circuits/components/instructions/gate_set/gellman.py +++ b/src/mqt/qudits/quantum_circuit/gates/gellman.py @@ -3,12 +3,11 @@ from typing import TYPE_CHECKING import numpy as np -from mqt.qudits.qudit_circuits.components.instructions.gate import Gate -from mqt.qudits.qudit_circuits.components.instructions.gate_extensions.gate_types import GateTypes + +from ..gate import ControlData, Gate, GateTypes if TYPE_CHECKING: - from mqt.qudits.qudit_circuits.circuit import QuantumCircuit - from mqt.qudits.qudit_circuits.components.instructions.gate_extensions.controls import ControlData + from ..circuit import QuantumCircuit class GellMann(Gate): @@ -24,7 +23,7 @@ def __init__( parameters: list, dimensions: list[int] | int, controls: ControlData | None = None, - ): + ) -> None: super().__init__( circuit=circuit, name=name, @@ -51,7 +50,7 @@ def __array__(self, dtype: str = "complex") -> np.ndarray: else: E = np.zeros((d, d), dtype=complex) - for j_ind in range(0, self.lev_b): + for j_ind in range(self.lev_b): E[j_ind, j_ind] += 1 E[self.lev_b, self.lev_b] -= self.lev_b @@ -64,7 +63,7 @@ def __array__(self, dtype: str = "complex") -> np.ndarray: return matrix - def validate_parameter(self, parameter): + def validate_parameter(self, parameter) -> bool: assert isinstance(parameter[0], int) assert isinstance(parameter[1], int) assert isinstance(parameter[2], str) @@ -75,6 +74,6 @@ def validate_parameter(self, parameter): return True - def __str__(self): + def __str__(self) -> str: # TODO pass diff --git a/src/mqt/qudits/qudit_circuits/components/instructions/gate_set/h.py b/src/mqt/qudits/quantum_circuit/gates/h.py similarity index 79% rename from src/mqt/qudits/qudit_circuits/components/instructions/gate_set/h.py rename to src/mqt/qudits/quantum_circuit/gates/h.py index d3a0c59..0a06b30 100644 --- a/src/mqt/qudits/qudit_circuits/components/instructions/gate_set/h.py +++ b/src/mqt/qudits/quantum_circuit/gates/h.py @@ -5,12 +5,10 @@ import numpy as np -from mqt.qudits.qudit_circuits.components.instructions.gate import Gate -from mqt.qudits.qudit_circuits.components.instructions.gate_extensions.gate_types import GateTypes +from ..gate import ControlData, Gate, GateTypes if TYPE_CHECKING: - from mqt.qudits.qudit_circuits.circuit import QuantumCircuit - from mqt.qudits.qudit_circuits.components.instructions.gate_extensions.controls import ControlData + from ..circuit import QuantumCircuit class H(Gate): @@ -21,7 +19,7 @@ def __init__( target_qudits: list[int] | int, dimensions: list[int] | int, controls: ControlData | None = None, - ): + ) -> None: super().__init__( circuit=circuit, name=name, @@ -58,9 +56,9 @@ def __array__(self, dtype: str = "complex") -> np.ndarray: return matrix - def validate_parameter(self, parameter=None): + def validate_parameter(self, parameter=None) -> bool: return True - def __str__(self): + def __str__(self) -> str: # TODO pass diff --git a/src/mqt/qudits/qudit_circuits/components/instructions/gate_set/ls.py b/src/mqt/qudits/quantum_circuit/gates/ls.py similarity index 74% rename from src/mqt/qudits/qudit_circuits/components/instructions/gate_set/ls.py rename to src/mqt/qudits/quantum_circuit/gates/ls.py index dc4037a..0df2070 100644 --- a/src/mqt/qudits/qudit_circuits/components/instructions/gate_set/ls.py +++ b/src/mqt/qudits/quantum_circuit/gates/ls.py @@ -3,14 +3,15 @@ from typing import TYPE_CHECKING import numpy as np -from mqt.qudits.qudit_circuits.components.instructions.gate import Gate -from mqt.qudits.qudit_circuits.components.instructions.gate_extensions.gate_types import GateTypes -from mqt.qudits.qudit_circuits.components.instructions.mini_tools.matrix_factory_tools import from_dirac_to_basis -from scipy.linalg import expm + +from ..gate import ControlData, Gate, GateTypes +from ..matrix_factory import from_dirac_to_basis if TYPE_CHECKING: - from mqt.qudits.qudit_circuits.circuit import QuantumCircuit - from mqt.qudits.qudit_circuits.components.instructions.gate_extensions.controls import ControlData + from ..circuit import QuantumCircuit + + +from scipy.linalg import expm class LS(Gate): @@ -22,7 +23,7 @@ def __init__( parameters: list | None, dimensions: list[int] | int, controls: ControlData | None = None, - ): + ) -> None: super().__init__( circuit=circuit, name=name, @@ -50,10 +51,10 @@ def __array__(self, dtype: str = "complex") -> np.ndarray: return expm(-1j * self.theta * exp_matrix) - def validate_parameter(self, parameter): + def validate_parameter(self, parameter) -> bool: assert 0 <= parameter[0] <= 2 * np.pi, f"Angle should be in the range [0, 2*pi]: {parameter[0]}" return True - def __str__(self): + def __str__(self) -> str: # TODO pass diff --git a/src/mqt/qudits/qudit_circuits/components/instructions/gate_set/ms.py b/src/mqt/qudits/quantum_circuit/gates/ms.py similarity index 86% rename from src/mqt/qudits/qudit_circuits/components/instructions/gate_set/ms.py rename to src/mqt/qudits/quantum_circuit/gates/ms.py index ea12c0f..fc2ec1a 100644 --- a/src/mqt/qudits/qudit_circuits/components/instructions/gate_set/ms.py +++ b/src/mqt/qudits/quantum_circuit/gates/ms.py @@ -3,14 +3,13 @@ from typing import TYPE_CHECKING import numpy as np -from mqt.qudits.qudit_circuits.components.instructions.gate import Gate -from mqt.qudits.qudit_circuits.components.instructions.gate_extensions.gate_types import GateTypes -from mqt.qudits.qudit_circuits.components.instructions.gate_set.gellman import GellMann from scipy.linalg import expm +from ..gate import ControlData, Gate, GateTypes +from .gellman import GellMann + if TYPE_CHECKING: - from mqt.qudits.qudit_circuits.circuit import QuantumCircuit - from mqt.qudits.qudit_circuits.components.instructions.gate_extensions.controls import ControlData + from ..circuit import QuantumCircuit class MS(Gate): @@ -22,7 +21,7 @@ def __init__( parameters: list | None, dimensions: list[int] | int, controls: ControlData | None = None, - ): + ) -> None: super().__init__( circuit=circuit, name=name, @@ -97,10 +96,10 @@ def __array__(self, dtype: str = "complex") -> np.ndarray: / 4 ) - def validate_parameter(self, parameter): + def validate_parameter(self, parameter) -> bool: assert 0 <= parameter[0] <= 2 * np.pi, f"Angle should be in the range [0, 2*pi]: {parameter[0]}" return True - def __str__(self): + def __str__(self) -> str: # TODO pass diff --git a/src/mqt/qudits/qudit_circuits/components/instructions/gate_set/perm.py b/src/mqt/qudits/quantum_circuit/gates/perm.py similarity index 68% rename from src/mqt/qudits/qudit_circuits/components/instructions/gate_set/perm.py rename to src/mqt/qudits/quantum_circuit/gates/perm.py index 63f7281..54e7c6d 100644 --- a/src/mqt/qudits/qudit_circuits/components/instructions/gate_set/perm.py +++ b/src/mqt/qudits/quantum_circuit/gates/perm.py @@ -1,15 +1,15 @@ from __future__ import annotations +import operator from functools import reduce from typing import TYPE_CHECKING import numpy as np -from mqt.qudits.qudit_circuits.components.instructions.gate import Gate -from mqt.qudits.qudit_circuits.components.instructions.gate_extensions.gate_types import GateTypes + +from ..gate import ControlData, Gate, GateTypes if TYPE_CHECKING: - from mqt.qudits.qudit_circuits.circuit import QuantumCircuit - from mqt.qudits.qudit_circuits.components.instructions.gate_extensions.controls import ControlData + from ..circuit import QuantumCircuit class Perm(Gate): @@ -21,7 +21,7 @@ def __init__( parameters: list, dimensions: list[int] | int, controls: ControlData | None = None, - ): + ) -> None: super().__init__( circuit=circuit, name=name, @@ -36,15 +36,15 @@ def __init__( self.qasm_tag = "pm" def __array__(self, dtype: str = "complex") -> np.ndarray: - return np.eye(reduce(lambda x, y: x * y, self._dimensions))[:, self.perm_data] + return np.eye(reduce(operator.mul, self._dimensions))[:, self.perm_data] - def validate_parameter(self, parameter): + def validate_parameter(self, parameter) -> bool: assert isinstance(parameter, list), "Input is not a list" assert all( 0 <= num < len(parameter) for num in parameter ), "Numbers are not within the range of the list length" return True - def __str__(self): + def __str__(self) -> str: # TODO pass diff --git a/src/mqt/qudits/qudit_circuits/components/instructions/gate_set/r.py b/src/mqt/qudits/quantum_circuit/gates/r.py similarity index 80% rename from src/mqt/qudits/qudit_circuits/components/instructions/gate_set/r.py rename to src/mqt/qudits/quantum_circuit/gates/r.py index 8507d61..7080630 100644 --- a/src/mqt/qudits/qudit_circuits/components/instructions/gate_set/r.py +++ b/src/mqt/qudits/quantum_circuit/gates/r.py @@ -3,14 +3,13 @@ from typing import TYPE_CHECKING import numpy as np -from mqt.qudits.compiler.compilation_minitools.local_compilation_minitools import regulate_theta, theta_cost -from mqt.qudits.qudit_circuits.components.instructions.gate import Gate -from mqt.qudits.qudit_circuits.components.instructions.gate_extensions.gate_types import GateTypes -from mqt.qudits.qudit_circuits.components.instructions.gate_set.gellman import GellMann + +from ...compiler.compilation_minitools.local_compilation_minitools import regulate_theta, theta_cost +from ..gate import ControlData, Gate, GateTypes +from .gellman import GellMann if TYPE_CHECKING: - from mqt.qudits.qudit_circuits.circuit import QuantumCircuit - from mqt.qudits.qudit_circuits.components.instructions.gate_extensions.controls import ControlData + from ..circuit import QuantumCircuit class R(Gate): @@ -22,7 +21,7 @@ def __init__( parameters: list | None, dimensions: list[int] | int, controls: ControlData | None = None, - ): + ) -> None: super().__init__( circuit=circuit, name=name, @@ -73,10 +72,9 @@ def __array__(self, dtype: str = "complex") -> np.ndarray: def levels_setter(self, la, lb): if la < lb: return la, lb - else: - return lb, la + return lb, la - def validate_parameter(self, parameter): + def validate_parameter(self, parameter) -> bool: assert isinstance(parameter[0], int) assert isinstance(parameter[1], int) assert isinstance(parameter[2], float) @@ -92,7 +90,7 @@ def validate_parameter(self, parameter): return True - def __str__(self): + def __str__(self) -> str: # TODO pass diff --git a/src/mqt/qudits/qudit_circuits/components/instructions/gate_set/randu.py b/src/mqt/qudits/quantum_circuit/gates/randu.py similarity index 63% rename from src/mqt/qudits/qudit_circuits/components/instructions/gate_set/randu.py rename to src/mqt/qudits/quantum_circuit/gates/randu.py index 6641d31..54bbfc2 100644 --- a/src/mqt/qudits/qudit_circuits/components/instructions/gate_set/randu.py +++ b/src/mqt/qudits/quantum_circuit/gates/randu.py @@ -3,14 +3,15 @@ from functools import reduce from typing import TYPE_CHECKING -from mqt.qudits.qudit_circuits.components.instructions.gate import Gate -from mqt.qudits.qudit_circuits.components.instructions.gate_extensions.gate_types import GateTypes -from scipy.stats import unitary_group +from ..gate import ControlData, Gate, GateTypes if TYPE_CHECKING: import numpy as np - from mqt.qudits.qudit_circuits.circuit import QuantumCircuit - from mqt.qudits.qudit_circuits.components.instructions.gate_extensions.controls import ControlData + + from ..circuit import QuantumCircuit +import operator + +from scipy.stats import unitary_group class RandU(Gate): @@ -21,7 +22,7 @@ def __init__( target_qudits: list[int] | int, dimensions: list[int] | int, controls: ControlData | None = None, - ): + ) -> None: super().__init__( circuit=circuit, name=name, @@ -33,12 +34,12 @@ def __init__( self.qasm_tag = "rdu" def __array__(self, dtype: str = "complex") -> np.ndarray: - dim = reduce(lambda x, y: x * y, self._dimensions) + dim = reduce(operator.mul, self._dimensions) return unitary_group.rvs(dim) - def validate_parameter(self, parameter): + def validate_parameter(self, parameter) -> bool: return True - def __str__(self): + def __str__(self) -> str: # TODO pass diff --git a/src/mqt/qudits/qudit_circuits/components/instructions/gate_set/rh.py b/src/mqt/qudits/quantum_circuit/gates/rh.py similarity index 79% rename from src/mqt/qudits/qudit_circuits/components/instructions/gate_set/rh.py rename to src/mqt/qudits/quantum_circuit/gates/rh.py index b66fd6a..afce94d 100644 --- a/src/mqt/qudits/qudit_circuits/components/instructions/gate_set/rh.py +++ b/src/mqt/qudits/quantum_circuit/gates/rh.py @@ -3,13 +3,12 @@ from typing import TYPE_CHECKING import numpy as np -from mqt.qudits.qudit_circuits.components.instructions.gate import Gate -from mqt.qudits.qudit_circuits.components.instructions.gate_extensions.gate_types import GateTypes -from mqt.qudits.qudit_circuits.components.instructions.gate_set.r import R + +from ..gate import ControlData, Gate, GateTypes +from .r import R if TYPE_CHECKING: - from mqt.qudits.qudit_circuits.circuit import QuantumCircuit - from mqt.qudits.qudit_circuits.components.instructions.gate_extensions.controls import ControlData + from ..circuit import QuantumCircuit class Rh(Gate): @@ -23,7 +22,7 @@ def __init__( parameters: list | None, dimensions: list[int] | int, controls: ControlData | None = None, - ): + ) -> None: super().__init__( circuit=circuit, name=name, @@ -63,10 +62,9 @@ def __array__(self, dtype: str = "complex") -> np.ndarray: def levels_setter(self, la, lb): if la < lb: return la, lb - else: - return lb, la + return lb, la - def validate_parameter(self, parameter): + def validate_parameter(self, parameter) -> bool: assert isinstance(parameter[0], int) assert isinstance(parameter[1], int) @@ -81,6 +79,6 @@ def validate_parameter(self, parameter): return True - def __str__(self): + def __str__(self) -> str: # TODO pass diff --git a/src/mqt/qudits/qudit_circuits/components/instructions/gate_set/rz.py b/src/mqt/qudits/quantum_circuit/gates/rz.py similarity index 77% rename from src/mqt/qudits/qudit_circuits/components/instructions/gate_set/rz.py rename to src/mqt/qudits/quantum_circuit/gates/rz.py index 8440815..49ef117 100644 --- a/src/mqt/qudits/qudit_circuits/components/instructions/gate_set/rz.py +++ b/src/mqt/qudits/quantum_circuit/gates/rz.py @@ -3,14 +3,13 @@ from typing import TYPE_CHECKING import numpy as np -from mqt.qudits.compiler.compilation_minitools.local_compilation_minitools import regulate_theta -from mqt.qudits.qudit_circuits.components.instructions.gate import Gate -from mqt.qudits.qudit_circuits.components.instructions.gate_extensions.gate_types import GateTypes -from mqt.qudits.qudit_circuits.components.instructions.gate_set.r import R + +from ...compiler.compilation_minitools.local_compilation_minitools import regulate_theta +from ..gate import ControlData, Gate, GateTypes +from .r import R if TYPE_CHECKING: - from mqt.qudits.qudit_circuits.circuit import QuantumCircuit - from mqt.qudits.qudit_circuits.components.instructions.gate_extensions.controls import ControlData + from ..circuit import QuantumCircuit class Rz(Gate): @@ -22,7 +21,7 @@ def __init__( parameters: list | None, dimensions: list[int] | int, controls: ControlData | None = None, - ): + ) -> None: super().__init__( circuit=circuit, name=name, @@ -57,10 +56,9 @@ def __array__(self, dtype: str = "complex") -> np.ndarray: def levels_setter(self, la, lb): if la < lb: return la, lb - else: - return lb, la + return lb, la - def validate_parameter(self, parameter): + def validate_parameter(self, parameter) -> bool: assert isinstance(parameter[0], int) assert isinstance(parameter[1], int) assert isinstance(parameter[2], float) @@ -76,6 +74,6 @@ def validate_parameter(self, parameter): return True - def __str__(self): + def __str__(self) -> str: # TODO pass diff --git a/src/mqt/qudits/qudit_circuits/components/instructions/gate_set/s.py b/src/mqt/qudits/quantum_circuit/gates/s.py similarity index 77% rename from src/mqt/qudits/qudit_circuits/components/instructions/gate_set/s.py rename to src/mqt/qudits/quantum_circuit/gates/s.py index c9172b8..a09ef51 100644 --- a/src/mqt/qudits/qudit_circuits/components/instructions/gate_set/s.py +++ b/src/mqt/qudits/quantum_circuit/gates/s.py @@ -3,12 +3,11 @@ from typing import TYPE_CHECKING import numpy as np -from mqt.qudits.qudit_circuits.components.instructions.gate import Gate -from mqt.qudits.qudit_circuits.components.instructions.gate_extensions.gate_types import GateTypes + +from ..gate import ControlData, Gate, GateTypes if TYPE_CHECKING: - from mqt.qudits.qudit_circuits.circuit import QuantumCircuit - from mqt.qudits.qudit_circuits.components.instructions.gate_extensions.controls import ControlData + from ..circuit import QuantumCircuit class S(Gate): @@ -19,7 +18,7 @@ def __init__( target_qudits: list[int] | int, dimensions: list[int] | int, controls: ControlData | None = None, - ): + ) -> None: super().__init__( circuit=circuit, name=name, @@ -55,9 +54,9 @@ def __array__(self, dtype: str = "complex") -> np.ndarray: return matrix - def validate_parameter(self, parameter=None): + def validate_parameter(self, parameter=None) -> bool: return True - def __str__(self): + def __str__(self) -> str: # TODO pass diff --git a/src/mqt/qudits/qudit_circuits/components/instructions/gate_set/virt_rz.py b/src/mqt/qudits/quantum_circuit/gates/virt_rz.py similarity index 72% rename from src/mqt/qudits/qudit_circuits/components/instructions/gate_set/virt_rz.py rename to src/mqt/qudits/quantum_circuit/gates/virt_rz.py index de42dab..f2c1f2b 100644 --- a/src/mqt/qudits/qudit_circuits/components/instructions/gate_set/virt_rz.py +++ b/src/mqt/qudits/quantum_circuit/gates/virt_rz.py @@ -3,13 +3,12 @@ from typing import TYPE_CHECKING import numpy as np -from mqt.qudits.compiler.compilation_minitools.local_compilation_minitools import phi_cost, regulate_theta -from mqt.qudits.qudit_circuits.components.instructions.gate import Gate -from mqt.qudits.qudit_circuits.components.instructions.gate_extensions.gate_types import GateTypes + +from ...compiler.compilation_minitools.local_compilation_minitools import phi_cost, regulate_theta +from ..gate import ControlData, Gate, GateTypes if TYPE_CHECKING: - from mqt.qudits.qudit_circuits.circuit import QuantumCircuit - from mqt.qudits.qudit_circuits.components.instructions.gate_extensions.controls import ControlData + from ..circuit import QuantumCircuit class VirtRz(Gate): @@ -21,7 +20,7 @@ def __init__( parameters: list | None, dimensions: list[int] | int, controls: ControlData | None = None, - ): + ) -> None: super().__init__( circuit=circuit, name=name, @@ -44,13 +43,13 @@ def __array__(self, dtype: str = "complex") -> np.ndarray: return matrix - def validate_parameter(self, parameter): + def validate_parameter(self, parameter) -> bool: assert isinstance(parameter[0], int) assert isinstance(parameter[1], float) assert 0 <= parameter[0] <= self._dimensions return True - def __str__(self): + def __str__(self) -> str: # TODO pass diff --git a/src/mqt/qudits/qudit_circuits/components/instructions/gate_set/x.py b/src/mqt/qudits/quantum_circuit/gates/x.py similarity index 76% rename from src/mqt/qudits/qudit_circuits/components/instructions/gate_set/x.py rename to src/mqt/qudits/quantum_circuit/gates/x.py index 9115ffc..7a3fea1 100644 --- a/src/mqt/qudits/qudit_circuits/components/instructions/gate_set/x.py +++ b/src/mqt/qudits/quantum_circuit/gates/x.py @@ -3,12 +3,11 @@ from typing import TYPE_CHECKING import numpy as np -from mqt.qudits.qudit_circuits.components.instructions.gate import Gate -from mqt.qudits.qudit_circuits.components.instructions.gate_extensions.gate_types import GateTypes + +from ..gate import ControlData, Gate, GateTypes if TYPE_CHECKING: - from mqt.qudits.qudit_circuits.circuit import QuantumCircuit - from mqt.qudits.qudit_circuits.components.instructions.gate_extensions.controls import ControlData + from ..circuit import QuantumCircuit class X(Gate): @@ -19,7 +18,7 @@ def __init__( target_qudits: list[int] | int, dimensions: list[int] | int, controls: ControlData | None = None, - ): + ) -> None: super().__init__( circuit=circuit, name=name, @@ -51,9 +50,9 @@ def __array__(self, dtype: str = "complex") -> np.ndarray: return matrix - def validate_parameter(self, parameter=None): + def validate_parameter(self, parameter=None) -> bool: return True - def __str__(self): + def __str__(self) -> str: # TODO pass diff --git a/src/mqt/qudits/qudit_circuits/components/instructions/gate_set/z.py b/src/mqt/qudits/quantum_circuit/gates/z.py similarity index 77% rename from src/mqt/qudits/qudit_circuits/components/instructions/gate_set/z.py rename to src/mqt/qudits/quantum_circuit/gates/z.py index f9cb0d2..575f676 100644 --- a/src/mqt/qudits/qudit_circuits/components/instructions/gate_set/z.py +++ b/src/mqt/qudits/quantum_circuit/gates/z.py @@ -3,12 +3,11 @@ from typing import TYPE_CHECKING import numpy as np -from mqt.qudits.qudit_circuits.components.instructions.gate import Gate -from mqt.qudits.qudit_circuits.components.instructions.gate_extensions.gate_types import GateTypes + +from ..gate import ControlData, Gate, GateTypes if TYPE_CHECKING: - from mqt.qudits.qudit_circuits.circuit import QuantumCircuit - from mqt.qudits.qudit_circuits.components.instructions.gate_extensions.controls import ControlData + from ..circuit import QuantumCircuit class Z(Gate): @@ -19,7 +18,7 @@ def __init__( target_qudits: list[int] | int, dimensions: list[int] | int, controls: ControlData | None = None, - ): + ) -> None: super().__init__( circuit=circuit, name=name, @@ -55,9 +54,9 @@ def __array__(self, dtype: str = "complex") -> np.ndarray: return matrix - def validate_parameter(self, parameter=None): + def validate_parameter(self, parameter=None) -> bool: return True - def __str__(self): + def __str__(self) -> str: # TODO pass diff --git a/src/mqt/qudits/qudit_circuits/components/instructions/gate_extensions/matrix_factory.py b/src/mqt/qudits/quantum_circuit/matrix_factory.py similarity index 83% rename from src/mqt/qudits/qudit_circuits/components/instructions/gate_extensions/matrix_factory.py rename to src/mqt/qudits/quantum_circuit/matrix_factory.py index 2ceade4..72b5dd5 100644 --- a/src/mqt/qudits/qudit_circuits/components/instructions/gate_extensions/matrix_factory.py +++ b/src/mqt/qudits/quantum_circuit/matrix_factory.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import itertools import operator from functools import reduce @@ -6,7 +8,7 @@ class MatrixFactory: - def __init__(self, gate, identities_flag): + def __init__(self, gate, identities_flag) -> None: self.gate = gate self.ids = identities_flag @@ -139,10 +141,48 @@ def wrap_in_identities(cls, matrix, indices, sizes): while i < len(sizes): if i == indices[0]: result = matrix if i == 0 else np.kron(matrix, result) - else: - if i > indices[-1]: - result = np.kron(np.identity(sizes[i]), result) + elif i > indices[-1]: + result = np.kron(np.identity(sizes[i]), result) i += 1 return result + + +def from_dirac_to_basis(vec, d): # |00> -> [1,0,...,0] -> len() == other_size**2 + if isinstance(d, int): + d = [d] * len(vec) + + basis_vecs = [] + for i, basis in enumerate(vec): + temp = [0] * d[i] + temp[basis] = 1 + basis_vecs.append(temp) + + ret = basis_vecs[0] + for e_i in range(1, len(basis_vecs)): + ret = np.kron(np.array(ret), np.array(basis_vecs[e_i])) + + return ret + + +def calculate_q0_q1(lev, dim): + q1 = lev % dim + q0 = (lev - q1) // dim + + return q0, q1 + + +def insert_at(big_arr, pos, to_insert_arr): + """Quite a forceful way of embedding a parameters into big_arr.""" + x1 = pos[0] + y1 = pos[1] + x2 = x1 + to_insert_arr.shape[0] + y2 = y1 + to_insert_arr.shape[1] + + assert x2 <= big_arr.shape[0], "the position will make the small parameters exceed the boundaries at x" + assert y2 <= big_arr.shape[1], "the position will make the small parameters exceed the boundaries at y" + + big_arr[x1:x2, y1:y2] = to_insert_arr + + return big_arr diff --git a/src/mqt/qudits/qudit_circuits/qasm_interface/qasm.py b/src/mqt/qudits/quantum_circuit/qasm.py similarity index 95% rename from src/mqt/qudits/qudit_circuits/qasm_interface/qasm.py rename to src/mqt/qudits/quantum_circuit/qasm.py index 7e96bc2..abb0928 100644 --- a/src/mqt/qudits/qudit_circuits/qasm_interface/qasm.py +++ b/src/mqt/qudits/quantum_circuit/qasm.py @@ -1,12 +1,15 @@ +from __future__ import annotations + import re from pathlib import Path import numpy as np -from mqt.qudits.qudit_circuits.components.instructions.gate_extensions.controls import ControlData + +from .gate import ControlData class QASM: - def __init__(self): + def __init__(self) -> None: self._program = None def parse_nonspecial_lines(self, line, rgxs, in_comment_flag): @@ -30,7 +33,7 @@ def parse_nonspecial_lines(self, line, rgxs, in_comment_flag): return True, in_comment return False, in_comment - def parse_qreg(self, line, rgxs, sitemap): + def parse_qreg(self, line, rgxs, sitemap) -> bool: match = rgxs["qreg"].match(line) if match: name, nq, qdims = match.groups() @@ -55,11 +58,10 @@ def safe_eval_math_expression(self, expression): allowed_names = {"__builtins__": None, "pi": np.pi} # Use eval with restricted names return eval(expression, allowed_names) - except (ValueError, SyntaxError) as e: - print(f"Error evaluating expression: {e}") + except (ValueError, SyntaxError): return None - def parse_gate(self, line, rgxs, sitemap, gates): + def parse_gate(self, line, rgxs, sitemap, gates) -> bool: match = rgxs["gate_matrix"].search(line) if match: label = match.group(1) @@ -117,7 +119,7 @@ def parse_gate(self, line, rgxs, sitemap, gates): return True return False - def parse_ignore(self, line, rgxs, warned): + def parse_ignore(self, line, rgxs, warned) -> bool: match = rgxs["ignore"].match(line) if match: # certain operations we can just ignore and warn about diff --git a/src/mqt/qudits/qudit_circuits/__init__.py b/src/mqt/qudits/qudit_circuits/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/mqt/qudits/qudit_circuits/components/__init__.py b/src/mqt/qudits/qudit_circuits/components/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/mqt/qudits/qudit_circuits/components/instructions/__init__.py b/src/mqt/qudits/qudit_circuits/components/instructions/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/mqt/qudits/qudit_circuits/components/instructions/gate_extensions/__init__.py b/src/mqt/qudits/qudit_circuits/components/instructions/gate_extensions/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/mqt/qudits/qudit_circuits/components/instructions/gate_extensions/controls.py b/src/mqt/qudits/qudit_circuits/components/instructions/gate_extensions/controls.py deleted file mode 100644 index 5eff3e4..0000000 --- a/src/mqt/qudits/qudit_circuits/components/instructions/gate_extensions/controls.py +++ /dev/null @@ -1,9 +0,0 @@ -from __future__ import annotations - -from dataclasses import dataclass - - -@dataclass -class ControlData: - indices: list[int] | int - ctrl_states: list[int] | int diff --git a/src/mqt/qudits/qudit_circuits/components/instructions/gate_extensions/gate_types.py b/src/mqt/qudits/qudit_circuits/components/instructions/gate_extensions/gate_types.py deleted file mode 100644 index beaaf96..0000000 --- a/src/mqt/qudits/qudit_circuits/components/instructions/gate_extensions/gate_types.py +++ /dev/null @@ -1,12 +0,0 @@ -import enum - - -class GateTypes(enum.Enum): - """Enumeration for job status.""" - - SINGLE = "Single Qudit Gate" - TWO = "Two Qudit Gate" - MULTI = "Multi Qudit Gate" - - -CORE_GATE_TYPES = (GateTypes.SINGLE, GateTypes.TWO, GateTypes.MULTI) diff --git a/src/mqt/qudits/qudit_circuits/components/instructions/gate_set/__init__.py b/src/mqt/qudits/qudit_circuits/components/instructions/gate_set/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/mqt/qudits/qudit_circuits/components/instructions/instruction.py b/src/mqt/qudits/qudit_circuits/components/instructions/instruction.py deleted file mode 100644 index 178fb74..0000000 --- a/src/mqt/qudits/qudit_circuits/components/instructions/instruction.py +++ /dev/null @@ -1,7 +0,0 @@ -from abc import ABC, abstractmethod - - -class Instruction(ABC): - @abstractmethod - def __init__(self, name): - pass diff --git a/src/mqt/qudits/qudit_circuits/components/instructions/mini_tools/__init__.py b/src/mqt/qudits/qudit_circuits/components/instructions/mini_tools/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/mqt/qudits/qudit_circuits/components/instructions/mini_tools/matrix_factory_tools.py b/src/mqt/qudits/qudit_circuits/components/instructions/mini_tools/matrix_factory_tools.py deleted file mode 100644 index 375c6e7..0000000 --- a/src/mqt/qudits/qudit_circuits/components/instructions/mini_tools/matrix_factory_tools.py +++ /dev/null @@ -1,40 +0,0 @@ -import numpy as np - - -def from_dirac_to_basis(vec, d): # |00> -> [1,0,...,0] -> len() == other_size**2 - if isinstance(d, int): - d = [d] * len(vec) - - basis_vecs = [] - for i, basis in enumerate(vec): - temp = [0] * d[i] - temp[basis] = 1 - basis_vecs.append(temp) - - ret = basis_vecs[0] - for e_i in range(1, len(basis_vecs)): - ret = np.kron(np.array(ret), np.array(basis_vecs[e_i])) - - return ret - - -def calculate_q0_q1(lev, dim): - q1 = lev % dim - q0 = (lev - q1) // dim - - return q0, q1 - - -def insert_at(big_arr, pos, to_insert_arr): - """Quite a forceful way of embedding a parameters into big_arr""" - x1 = pos[0] - y1 = pos[1] - x2 = x1 + to_insert_arr.shape[0] - y2 = y1 + to_insert_arr.shape[1] - - assert x2 <= big_arr.shape[0], "the position will make the small parameters exceed the boundaries at x" - assert y2 <= big_arr.shape[1], "the position will make the small parameters exceed the boundaries at y" - - big_arr[x1:x2, y1:y2] = to_insert_arr - - return big_arr diff --git a/src/mqt/qudits/qudit_circuits/components/registers/__init__.py b/src/mqt/qudits/qudit_circuits/components/registers/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/mqt/qudits/qudit_circuits/components/registers/quantum_register.py b/src/mqt/qudits/qudit_circuits/components/registers/quantum_register.py deleted file mode 100644 index be92939..0000000 --- a/src/mqt/qudits/qudit_circuits/components/registers/quantum_register.py +++ /dev/null @@ -1,40 +0,0 @@ -from typing import List - - -class QuantumRegister: - @classmethod - def from_map(cls, sitemap: dict) -> List["QuantumRegister"]: - registers_map = {} - - for qreg_with_index, line_info in sitemap.items(): - reg_name, inreg_line_index = qreg_with_index - if reg_name not in registers_map: - registers_map[reg_name] = [{inreg_line_index: line_info[0]}, [line_info[1]]] - else: - registers_map[reg_name][0][inreg_line_index] = line_info[0] - registers_map[reg_name][1].append(line_info[1]) - # print(registers_map) - registers_from_qasm = [] - for label, data in registers_map.items(): - temp = QuantumRegister(label, len(data[0]), data[1]) - temp.local_sitemap = data[0] - registers_from_qasm.append(temp) - # print(registers_from_qasm) - return registers_from_qasm - - def __init__(self, name, size, dims=None): - self.label = name - self.size = size - self.dimensions = size * [2] if dims is None else dims - self.local_sitemap = {} - - def __qasm__(self): - string_dims = str(self.dimensions).replace(" ", "") - return "qreg " + self.label + " [" + str(self.size) + "]" + string_dims + ";" - - def __getitem__(self, key): - if isinstance(key, slice): - start, stop = key.start, key.stop - return [self.local_sitemap[i] for i in range(start, stop)] - - return self.local_sitemap[key] diff --git a/src/mqt/qudits/qudit_circuits/qasm_interface/__init__.py b/src/mqt/qudits/qudit_circuits/qasm_interface/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/mqt/qudits/simulation/__init__.py b/src/mqt/qudits/simulation/__init__.py index e69de29..0f97a19 100644 --- a/src/mqt/qudits/simulation/__init__.py +++ b/src/mqt/qudits/simulation/__init__.py @@ -0,0 +1,7 @@ +from __future__ import annotations + +from .qudit_provider import MQTQuditProvider + +__all__ = [ + "MQTQuditProvider", +] diff --git a/src/mqt/qudits/simulation/backends/__init__.py b/src/mqt/qudits/simulation/backends/__init__.py new file mode 100644 index 0000000..7ce318e --- /dev/null +++ b/src/mqt/qudits/simulation/backends/__init__.py @@ -0,0 +1,9 @@ +from __future__ import annotations + +from .misim import MISim +from .tnsim import TNSim + +__all__ = [ + "MISim", + "TNSim", +] diff --git a/src/mqt/qudits/simulation/backends/backendv2.py b/src/mqt/qudits/simulation/backends/backendv2.py new file mode 100644 index 0000000..f99c501 --- /dev/null +++ b/src/mqt/qudits/simulation/backends/backendv2.py @@ -0,0 +1,91 @@ +from __future__ import annotations + +from abc import ABC, abstractmethod +from typing import TYPE_CHECKING, Any + +if TYPE_CHECKING: + from datetime import datetime + + from ...core import LevelGraph + from ...quantum_circuit.gate import Gate + from ..jobs import Job + from ..qudit_provider import QuditProvider as Provider + + +class Backend(ABC): + @property + def version(self) -> int: + return 2 + + def __init__( + self, + provider: Provider | None = None, + name: str | None = None, + description: str | None = None, + online_date: datetime | None = None, + backend_version: str | None = None, + **fields: Any, + ) -> None: + self._options = self._default_options() + self._provider = provider + + if fields: + # for field in fields: + # if field not in self._options.data: + # msg = f"Options field '{field}' is not valid for this backend" + # raise AttributeError(msg) + self._options.update(fields) + + self.name = name + self.description = description + self.online_date = online_date + self.backend_version = backend_version + self._coupling_map = None + self._energy_level_graphs = None + + @property + def instructions(self) -> list[tuple[Gate, tuple[int]]]: + return self.target.instructions + + @property + def operations(self) -> list[Gate]: + return list(self.target.operations) + + @property + def operation_names(self) -> list[str]: + return list(self.target.operation_names) + + @property + @abstractmethod + def target(self): + pass + + @property + def num_qudits(self) -> int: + return self.target.num_qudits + + @property + def energy_level_graphs(self) -> list[(LevelGraph, LevelGraph)]: + raise NotImplementedError + + def _default_options(self): + return {"shots": 1000, "memory": False} + + def set_options(self, **fields) -> None: + for field in fields: + if not hasattr(self._options, field): + msg = f"Options field '{field}' is not valid for this backend" + raise AttributeError(msg) + self._options.update_options(**fields) + + @property + def options(self): + return self._options + + @property + def provider(self): + return self._provider + + @abstractmethod + def run(self, run_input, **options) -> Job: + pass diff --git a/src/mqt/qudits/simulation/backends/fake_backends/__init__.py b/src/mqt/qudits/simulation/backends/fake_backends/__init__.py new file mode 100644 index 0000000..76e00f3 --- /dev/null +++ b/src/mqt/qudits/simulation/backends/fake_backends/__init__.py @@ -0,0 +1,6 @@ +from __future__ import annotations + +from .fake_traps2six import FakeIonTraps2Six +from .fake_traps2three import FakeIonTraps2Trits + +__all__ = ["FakeIonTraps2Six", "FakeIonTraps2Trits"] diff --git a/src/mqt/qudits/simulation/provider/backends/fake_backends/fake_traps2six.py b/src/mqt/qudits/simulation/backends/fake_backends/fake_traps2six.py similarity index 56% rename from src/mqt/qudits/simulation/provider/backends/fake_backends/fake_traps2six.py rename to src/mqt/qudits/simulation/backends/fake_backends/fake_traps2six.py index 3cddd6b..6a90748 100644 --- a/src/mqt/qudits/simulation/provider/backends/fake_backends/fake_traps2six.py +++ b/src/mqt/qudits/simulation/backends/fake_backends/fake_traps2six.py @@ -1,28 +1,31 @@ -from datetime import datetime -from typing import Iterable, List, Optional, Tuple, Union +from __future__ import annotations -from mqt.qudits.core.structures.energy_level_graph.level_graph import LevelGraph -from mqt.qudits.qudit_circuits.components.instructions.instruction import Instruction -from mqt.qudits.simulation.provider.backend_properties.quditproperties import QuditProperties -from mqt.qudits.simulation.provider.backends.engines.tnsim import TNSim -from mqt.qudits.simulation.provider.noise_tools.noise import Noise, NoiseModel -from mqt.qudits.simulation.provider.provider import Provider +from typing import TYPE_CHECKING + +from ....core import LevelGraph +from ...noise_tools import Noise, NoiseModel +from ..tnsim import TNSim + +if TYPE_CHECKING: + from datetime import datetime + + from ...qudit_provider import QuditProvider as Provider class FakeIonTraps2Six(TNSim): @property - def version(self): + def version(self) -> int: return 0 def __init__( self, - provider: Optional[Provider] = None, - name: Optional[str] = None, - description: Optional[str] = None, - online_date: Optional[datetime] = None, - backend_version: Optional[str] = None, + provider: Provider | None = None, + name: str | None = None, + description: str | None = None, + online_date: datetime | None = None, + backend_version: str | None = None, **fields, - ): + ) -> None: self._options = self._default_options() self._provider = provider @@ -44,36 +47,7 @@ def __init__( self._energy_level_graphs = None @property - def instructions(self) -> List[Tuple[Instruction, Tuple[int]]]: - return self.target.instructions - - @property - def operations(self) -> List[Instruction]: - return list(self.target.operations) - - @property - def operation_names(self) -> List[str]: - return list(self.target.operation_names) - - @property - def target(self): - raise NotImplementedError - - @property - def num_qudits(self) -> int: - return self.target.num_qudits - - @property - def coupling_map(self): - raise NotImplementedError - """ - if self._coupling_map is None: - self._coupling_map = self.target.build_coupling_map() - return self._coupling_map - """ - - @property - def energy_level_graphs(self): + def energy_level_graphs(self) -> list[(LevelGraph, LevelGraph)]: e_graphs = [] # declare the edges on the energy level graph between logic states . @@ -89,11 +63,11 @@ def energy_level_graphs(self): ] # name explicitly the logic states . nodes = [0, 1, 2, 3, 4, 5] - # declare physical levels in order of maping of the logic states just declared . + # declare physical levels in order of mapping of the logic states just declared . # i.e. here we will have Logic 0 -> Phys. 0, have Logic 1 -> Phys. 1, have Logic 2 -> Phys. 2 . nmap = [0, 1, 2, 3, 4, 5] # Construct the qudit energy level graph, the last field is the list of logic state that are used for the - # calibrations of the operations. note: only the first is one counts in our current cost fucntion. + # calibrations of the operations. note: only the first is one counts in our current cost function. graph_0 = LevelGraph(edges, nodes, nmap, [1]) # declare the edges on the energy level graph between logic states . edges_1 = [ @@ -108,22 +82,18 @@ def energy_level_graphs(self): ] # name explicitly the logic states . nodes_1 = [0, 1, 2, 3, 4, 5] - # declare physical levels in order of maping of the logic states just declared . + # declare physical levels in order of mapping of the logic states just declared . # i.e. here we will have Logic 0 -> Phys. 0, have Logic 1 -> Phys. 1, have Logic 2 -> Phys. 2 . nmap_1 = [0, 1, 2, 3, 4, 5] # Construct the qudit energy level graph, the last field is the list of logic state that are used for the - # calibrations of the operations. note: only the first is one counts in our current cost fucntion. + # calibrations of the operations. note: only the first is one counts in our current cost function. graph_1 = LevelGraph(edges_1, nodes_1, nmap_1, [1]) - e_graphs.append(graph_0) - e_graphs.append(graph_1) + e_graphs.extend((graph_0, graph_1)) return e_graphs - def __noise_model(self): - """ - Noise model coded in plain sight, just for prototyping reasons - :return: NoideModel - """ + @staticmethod + def __noise_model() -> NoiseModel: # Depolarizing quantum errors local_error = Noise(probability_depolarizing=0.001, probability_dephasing=0.001) local_error_rz = Noise(probability_depolarizing=0.03, probability_dephasing=0.03) @@ -150,59 +120,5 @@ def __noise_model(self): return noise_model - @property - def instruction_durations(self): - raise NotImplementedError - - @property - def max_circuits(self): - raise NotImplementedError - def _default_options(self): return {"shots": 1000, "memory": False, "noise_model": self.__noise_model()} - - @property - def dt(self) -> Union[float, None]: - raise NotImplementedError - - @property - def dtm(self) -> float: - raise NotImplementedError - - @property - def meas_map(self) -> List[List[int]]: - raise NotImplementedError - - @property - def instruction_schedule_map(self): - raise NotImplementedError - - def qudit_properties(self, qudit: Union[int, List[int]]) -> Union[QuditProperties, List[QuditProperties]]: - raise NotImplementedError - - def drive_channel(self, qudit: int): - raise NotImplementedError - - def measure_channel(self, qudit: int): - raise NotImplementedError - - def acquire_channel(self, qudit: int): - raise NotImplementedError - - def control_channel(self, qudits: Iterable[int]): - raise NotImplementedError - - def set_options(self, **fields): - for field in fields: - if not hasattr(self._options, field): - msg = f"Options field '{field}' is not valid for this backend" - raise AttributeError(msg) - self._options.update_options(**fields) - - @property - def options(self): - return self._options - - @property - def provider(self): - return self._provider diff --git a/src/mqt/qudits/simulation/provider/backends/fake_backends/fake_traps2three.py b/src/mqt/qudits/simulation/backends/fake_backends/fake_traps2three.py similarity index 53% rename from src/mqt/qudits/simulation/provider/backends/fake_backends/fake_traps2three.py rename to src/mqt/qudits/simulation/backends/fake_backends/fake_traps2three.py index 7ef2c53..264892d 100644 --- a/src/mqt/qudits/simulation/provider/backends/fake_backends/fake_traps2three.py +++ b/src/mqt/qudits/simulation/backends/fake_backends/fake_traps2three.py @@ -1,28 +1,31 @@ -from datetime import datetime -from typing import Iterable, List, Optional, Tuple, Union +from __future__ import annotations -from mqt.qudits.core.structures.energy_level_graph.level_graph import LevelGraph -from mqt.qudits.qudit_circuits.components.instructions.instruction import Instruction -from mqt.qudits.simulation.provider.backend_properties.quditproperties import QuditProperties -from mqt.qudits.simulation.provider.backends.engines.tnsim import TNSim -from mqt.qudits.simulation.provider.noise_tools.noise import Noise, NoiseModel -from mqt.qudits.simulation.provider.provider import Provider +from typing import TYPE_CHECKING + +from ....core import LevelGraph +from ...noise_tools import Noise, NoiseModel +from ..tnsim import TNSim + +if TYPE_CHECKING: + from datetime import datetime + + from ...qudit_provider import QuditProvider as Provider class FakeIonTraps2Trits(TNSim): @property - def version(self): + def version(self) -> int: return 0 def __init__( self, - provider: Optional[Provider] = None, - name: Optional[str] = None, - description: Optional[str] = None, - online_date: Optional[datetime] = None, - backend_version: Optional[str] = None, + provider: Provider | None = None, + name: str | None = None, + description: str | None = None, + online_date: datetime | None = None, + backend_version: str | None = None, **fields, - ): + ) -> None: self._options = self._default_options() self._provider = provider @@ -44,36 +47,7 @@ def __init__( self._energy_level_graphs = None @property - def instructions(self) -> List[Tuple[Instruction, Tuple[int]]]: - return self.target.instructions - - @property - def operations(self) -> List[Instruction]: - return list(self.target.operations) - - @property - def operation_names(self) -> List[str]: - return list(self.target.operation_names) - - @property - def target(self): - raise NotImplementedError - - @property - def num_qudits(self) -> int: - return self.target.num_qudits - - @property - def coupling_map(self): - raise NotImplementedError - """ - if self._coupling_map is None: - self._coupling_map = self.target.build_coupling_map() - return self._coupling_map - """ - - @property - def energy_level_graphs(self): + def energy_level_graphs(self) -> list[(LevelGraph, LevelGraph)]: e_graphs = [] # declare the edges on the energy level graph between logic states . @@ -83,11 +57,11 @@ def energy_level_graphs(self): ] # name explicitly the logic states . nodes = [0, 1, 2] - # declare physical levels in order of maping of the logic states just declared . + # declare physical levels in order of mapping of the logic states just declared . # i.e. here we will have Logic 0 -> Phys. 0, have Logic 1 -> Phys. 1, have Logic 2 -> Phys. 2 . nmap = [0, 1, 2] # Construct the qudit energy level graph, the last field is the list of logic state that are used for the - # calibrations of the operations. note: only the first is one counts in our current cost fucntion. + # calibrations of the operations. note: only the first is one counts in our current cost function. graph_0 = LevelGraph(edges, nodes, nmap, [1]) # declare the edges on the energy level graph between logic states . edges = [ @@ -96,21 +70,20 @@ def energy_level_graphs(self): ] # name explicitly the logic states . nodes = [0, 1, 2] - # declare physical levels in order of maping of the logic states just declared . + # declare physical levels in order of mapping of the logic states just declared . # i.e. here we will have Logic 0 -> Phys. 0, have Logic 1 -> Phys. 1, have Logic 2 -> Phys. 2 . nmap = [0, 1, 2] # Construct the qudit energy level graph, the last field is the list of logic state that are used for the - # calibrations of the operations. note: only the first is one counts in our current cost fucntion. + # calibrations of the operations. note: only the first is one counts in our current cost function. graph_1 = LevelGraph(edges, nodes, nmap, [1]) - e_graphs.append(graph_0) - e_graphs.append(graph_1) + e_graphs.extend((graph_0, graph_1)) return e_graphs - def __noise_model(self): - """ - Noise model coded in plain sight, just for prototyping reasons - :return: NoideModel + @staticmethod + def __noise_model() -> NoiseModel: + """Noise model coded in plain sight, just for prototyping reasons + :return: NoideModel. """ # Depolarizing quantum errors local_error = Noise(probability_depolarizing=0.001, probability_dephasing=0.001) @@ -138,59 +111,5 @@ def __noise_model(self): return noise_model - @property - def instruction_durations(self): - raise NotImplementedError - - @property - def max_circuits(self): - raise NotImplementedError - def _default_options(self): return {"shots": 1000, "memory": False, "noise_model": self.__noise_model()} - - @property - def dt(self) -> Union[float, None]: - raise NotImplementedError - - @property - def dtm(self) -> float: - raise NotImplementedError - - @property - def meas_map(self) -> List[List[int]]: - raise NotImplementedError - - @property - def instruction_schedule_map(self): - raise NotImplementedError - - def qudit_properties(self, qudit: Union[int, List[int]]) -> Union[QuditProperties, List[QuditProperties]]: - raise NotImplementedError - - def drive_channel(self, qudit: int): - raise NotImplementedError - - def measure_channel(self, qudit: int): - raise NotImplementedError - - def acquire_channel(self, qudit: int): - raise NotImplementedError - - def control_channel(self, qudits: Iterable[int]): - raise NotImplementedError - - def set_options(self, **fields): - for field in fields: - if not hasattr(self._options, field): - msg = f"Options field '{field}' is not valid for this backend" - raise AttributeError(msg) - self._options.update_options(**fields) - - @property - def options(self): - return self._options - - @property - def provider(self): - return self._provider diff --git a/src/mqt/qudits/simulation/backends/misim.py b/src/mqt/qudits/simulation/backends/misim.py new file mode 100644 index 0000000..d7e613b --- /dev/null +++ b/src/mqt/qudits/simulation/backends/misim.py @@ -0,0 +1,54 @@ +from __future__ import annotations + +import operator +from functools import reduce +from typing import TYPE_CHECKING + +import numpy as np + +from ..._qudits.misim import state_vector_simulation +from ..jobs import Job, JobResult +from ..noise_tools import NoiseModel +from .backendv2 import Backend +from .stochastic_sim import stochastic_simulation_misim + +if TYPE_CHECKING: + from ...quantum_circuit import QuantumCircuit + + +class MISim(Backend): + def run(self, circuit: QuantumCircuit, **options) -> Job: + job = Job(self) + + self._options.update(options) + self.noise_model = self._options.get("noise_model", None) + self.shots = self._options.get("shots", 1 if self.noise_model is None else 1000) + self.memory = self._options.get("memory", False) + self.full_state_memory = self._options.get("full_state_memory", False) + self.file_path = self._options.get("file_path", None) + self.file_name = self._options.get("file_name", None) + + if self.noise_model is not None: + assert self.shots >= 1000, "Number of shots should be above 1000" + job.set_result( + JobResult(state_vector=self.execute(circuit), counts=stochastic_simulation_misim(self, circuit)) + ) + else: + job.set_result(JobResult(state_vector=self.execute(circuit), counts=None)) + + return job + + def execute(self, circuit: QuantumCircuit, noise_model: NoiseModel | None = None) -> np.ndarray: + self.system_sizes = circuit.dimensions + self.circ_operations = circuit.instructions + if noise_model is None: + noise_model = NoiseModel() + result = state_vector_simulation(circuit, noise_model) + state = np.array(result) + state_size = reduce(operator.mul, self.system_sizes, 1) + return state.reshape(1, state_size) + + def __init__(self, **fields) -> None: + self.system_sizes = None + self.circ_operations = None + super().__init__(**fields) diff --git a/src/mqt/qudits/simulation/provider/backends/stocastic_components/stocastic_sim.py b/src/mqt/qudits/simulation/backends/stochastic_sim.py similarity index 77% rename from src/mqt/qudits/simulation/provider/backends/stocastic_components/stocastic_sim.py rename to src/mqt/qudits/simulation/backends/stochastic_sim.py index 0849fba..d44deba 100644 --- a/src/mqt/qudits/simulation/provider/backends/stocastic_components/stocastic_sim.py +++ b/src/mqt/qudits/simulation/backends/stochastic_sim.py @@ -1,15 +1,21 @@ +from __future__ import annotations + import multiprocessing as mp import os import time +from typing import TYPE_CHECKING import numpy as np -from mqt.qudits.qudit_circuits.circuit import QuantumCircuit -from mqt.qudits.simulation.data_log.save_info import save_full_states, save_shots -from mqt.qudits.simulation.provider.backends.backendv2 import Backend -from mqt.qudits.simulation.provider.noise_tools.noisy_circuit_factory import NoisyCircuitFactory + +from ..noise_tools import NoisyCircuitFactory +from ..save_info import save_full_states, save_shots + +if TYPE_CHECKING: + from ...quantum_circuit import QuantumCircuit + from .backendv2 import Backend -def stocastic_simulation(backend: Backend, circuit: QuantumCircuit): +def stochastic_simulation(backend: Backend, circuit: QuantumCircuit): noise_model = backend.noise_model shots = backend.shots num_processes = mp.cpu_count() @@ -17,7 +23,7 @@ def stocastic_simulation(backend: Backend, circuit: QuantumCircuit): with mp.Pool(processes=num_processes) as process: execution_args = [(backend, factory) for _ in range(shots)] - results = process.map(stocastic_execution, execution_args) + results = process.map(stochastic_execution, execution_args) if backend.full_state_memory: filepath = backend.file_path @@ -31,7 +37,7 @@ def stocastic_simulation(backend: Backend, circuit: QuantumCircuit): return results -def stocastic_execution(args): +def stochastic_execution(args): backend, factory = args circuit = factory.generate_circuit() vector_data = backend.execute(circuit) @@ -47,7 +53,7 @@ def stocastic_execution(args): return vector_data -def stocastic_simulation_misim(backend: Backend, circuit: QuantumCircuit): +def stochastic_simulation_misim(backend: Backend, circuit: QuantumCircuit): noise_model = backend.noise_model shots = backend.shots num_processes = mp.cpu_count() @@ -55,7 +61,7 @@ def stocastic_simulation_misim(backend: Backend, circuit: QuantumCircuit): execution_pack = (circuit, noise_model) with mp.Pool(processes=num_processes) as process: execution_args = [(backend, execution_pack) for _ in range(shots)] - results = process.map(stocastic_execution_misim, execution_args) + results = process.map(stochastic_execution_misim, execution_args) if backend.full_state_memory: filepath = backend.file_path @@ -69,7 +75,7 @@ def stocastic_simulation_misim(backend: Backend, circuit: QuantumCircuit): return results -def stocastic_execution_misim(args): +def stochastic_execution_misim(args): backend, execution_pack = args circuit, noise_model = execution_pack vector_data = backend.execute(circuit, noise_model) diff --git a/src/mqt/qudits/simulation/provider/backends/engines/tnsim.py b/src/mqt/qudits/simulation/backends/tnsim.py similarity index 62% rename from src/mqt/qudits/simulation/provider/backends/engines/tnsim.py rename to src/mqt/qudits/simulation/backends/tnsim.py index 547b4e9..adad327 100644 --- a/src/mqt/qudits/simulation/provider/backends/engines/tnsim.py +++ b/src/mqt/qudits/simulation/backends/tnsim.py @@ -1,42 +1,22 @@ +from __future__ import annotations + +import operator from functools import reduce -from typing import Iterable, List, Union +from typing import TYPE_CHECKING import numpy as np import tensornetwork as tn -from mqt.qudits.qudit_circuits.circuit import QuantumCircuit -from mqt.qudits.qudit_circuits.components.instructions.gate import Gate -from mqt.qudits.qudit_circuits.components.instructions.gate_extensions.gate_types import GateTypes -from mqt.qudits.simulation.provider.backend_properties.quditproperties import QuditProperties -from mqt.qudits.simulation.provider.backends.backendv2 import Backend -from mqt.qudits.simulation.provider.backends.stocastic_components.stocastic_sim import stocastic_simulation -from mqt.qudits.simulation.provider.jobs.job import Job -from mqt.qudits.simulation.provider.jobs.job_result.job_result import JobResult - - -class TNSim(Backend): - @property - def target(self): - raise NotImplementedError - @property - def max_circuits(self): - raise NotImplementedError +from ...quantum_circuit.gate import Gate, GateTypes +from ..jobs import Job, JobResult +from .backendv2 import Backend +from .stochastic_sim import stochastic_simulation - def qudit_properties(self, qudit: Union[int, List[int]]) -> Union[QuditProperties, List[QuditProperties]]: - raise NotImplementedError +if TYPE_CHECKING: + from ...quantum_circuit import QuantumCircuit - def drive_channel(self, qudit: int): - raise NotImplementedError - - def measure_channel(self, qudit: int): - raise NotImplementedError - - def acquire_channel(self, qudit: int): - raise NotImplementedError - - def control_channel(self, qudits: Iterable[int]): - raise NotImplementedError +class TNSim(Backend): def run(self, circuit: QuantumCircuit, **options): job = Job(self) @@ -50,7 +30,7 @@ def run(self, circuit: QuantumCircuit, **options): if self.noise_model is not None: assert self.shots >= 1000, "Number of shots should be above 1000" - job.set_result(JobResult(state_vector=self.execute(circuit), counts=stocastic_simulation(self, circuit))) + job.set_result(JobResult(state_vector=self.execute(circuit), counts=stochastic_simulation(self, circuit))) else: job.set_result(JobResult(state_vector=self.execute(circuit), counts=None)) @@ -64,21 +44,21 @@ def execute(self, circuit: QuantumCircuit): result = np.transpose(result.tensor, list(reversed(range(len(self.system_sizes))))) - state_size = reduce(lambda x, y: x * y, self.system_sizes, 1) + state_size = reduce(operator.mul, self.system_sizes, 1) return result.reshape(1, state_size) - def __init__(self, **fields): + def __init__(self, **fields) -> None: self.system_sizes = None self.circ_operations = None super().__init__(**fields) - def __apply_gate(self, qudit_edges, gate, operating_qudits): + def __apply_gate(self, qudit_edges, gate, operating_qudits) -> None: op = tn.Node(gate) for i, bit in enumerate(operating_qudits): tn.connect(qudit_edges[bit], op[i]) qudit_edges[bit] = op[i + len(operating_qudits)] - def __contract_circuit(self, system_sizes, operations: List[Gate]): + def __contract_circuit(self, system_sizes, operations: list[Gate]): all_nodes = [] with tn.NodeCollection(all_nodes): @@ -98,9 +78,12 @@ def __contract_circuit(self, system_sizes, operations: List[Gate]): op_matrix = op_matrix.reshape((system_sizes[lines[0]], system_sizes[lines[0]])) elif op.gate_type == GateTypes.TWO and not op.is_long_range: - op_matrix = op_matrix.reshape( - (system_sizes[lines[0]], system_sizes[lines[1]], system_sizes[lines[0]], system_sizes[lines[1]]) - ) + op_matrix = op_matrix.reshape(( + system_sizes[lines[0]], + system_sizes[lines[1]], + system_sizes[lines[0]], + system_sizes[lines[1]], + )) elif op.is_long_range or op.gate_type == GateTypes.MULTI: minimum_line, maximum_line = min(lines), max(lines) diff --git a/src/mqt/qudits/simulation/data_log/__init__.py b/src/mqt/qudits/simulation/data_log/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/mqt/qudits/simulation/jobs/__init__.py b/src/mqt/qudits/simulation/jobs/__init__.py new file mode 100644 index 0000000..b6261de --- /dev/null +++ b/src/mqt/qudits/simulation/jobs/__init__.py @@ -0,0 +1,11 @@ +from __future__ import annotations + +from .job import Job +from .job_result import JobResult +from .jobstatus import JobStatus + +__all__ = [ + "Job", + "JobResult", + "JobStatus", +] diff --git a/src/mqt/qudits/simulation/provider/jobs/job.py b/src/mqt/qudits/simulation/jobs/job.py similarity index 87% rename from src/mqt/qudits/simulation/provider/jobs/job.py rename to src/mqt/qudits/simulation/jobs/job.py index eaae20c..5e417c1 100644 --- a/src/mqt/qudits/simulation/provider/jobs/job.py +++ b/src/mqt/qudits/simulation/jobs/job.py @@ -1,9 +1,11 @@ +from __future__ import annotations + import os import time -from typing import Callable, Optional +from typing import Callable, NoReturn -from mqt.qudits.exceptions.joberror import JobError, JobTimeoutError -from mqt.qudits.simulation.provider.jobs.jobstatus import JobStatus +from ...exceptions import JobError, JobTimeoutError +from .jobstatus import JobStatus class Job: @@ -19,7 +21,7 @@ class Job: version = 1 _async = True - def __init__(self, backend: Optional["Backend"], job_id: str = "auto", **kwargs) -> None: + def __init__(self, backend: Backend | None, job_id: str = "auto", **kwargs) -> None: """Initializes the asynchronous job. Args: @@ -39,7 +41,7 @@ def job_id(self) -> str: """Return a unique id identifying the job.""" return self._job_id - def backend(self) -> "Backend": + def backend(self) -> Backend: """Return the backend where this job was executed.""" if self._backend is None: msg = "The job does not have any backend." @@ -63,7 +65,7 @@ def in_final_state(self) -> bool: return self.status() in JobStatus.JOB_FINAL_STATES def wait_for_final_state( - self, timeout: Optional[float] = None, wait: float = 5, callback: Optional[Callable] = None + self, timeout: float | None = None, wait: float = 5, callback: Callable | None = None ) -> None: """Poll the job status until it progresses to a final state such as DONE or ERROR. @@ -89,7 +91,7 @@ def wait_for_final_state( time.sleep(wait) status = self.status() - def submit(self): + def submit(self) -> NoReturn: """Submit the job to the backend for execution.""" raise NotImplementedError @@ -97,10 +99,10 @@ def result(self): """Return the results of the job.""" return self._result - def set_result(self, result): + def set_result(self, result) -> None: self._result = result - def cancel(self): + def cancel(self) -> NoReturn: """Attempt to cancel the job.""" raise NotImplementedError diff --git a/src/mqt/qudits/simulation/provider/jobs/job_result/job_result.py b/src/mqt/qudits/simulation/jobs/job_result.py similarity index 100% rename from src/mqt/qudits/simulation/provider/jobs/job_result/job_result.py rename to src/mqt/qudits/simulation/jobs/job_result.py diff --git a/src/mqt/qudits/simulation/provider/jobs/jobstatus.py b/src/mqt/qudits/simulation/jobs/jobstatus.py similarity index 100% rename from src/mqt/qudits/simulation/provider/jobs/jobstatus.py rename to src/mqt/qudits/simulation/jobs/jobstatus.py diff --git a/src/mqt/qudits/simulation/noise_tools/__init__.py b/src/mqt/qudits/simulation/noise_tools/__init__.py new file mode 100644 index 0000000..21302e3 --- /dev/null +++ b/src/mqt/qudits/simulation/noise_tools/__init__.py @@ -0,0 +1,6 @@ +from __future__ import annotations + +from .noise import Noise, NoiseModel +from .noisy_circuit_factory import NoisyCircuitFactory + +__all__ = ["Noise", "NoiseModel", "NoisyCircuitFactory"] diff --git a/src/mqt/qudits/simulation/provider/noise_tools/noise.py b/src/mqt/qudits/simulation/noise_tools/noise.py similarity index 100% rename from src/mqt/qudits/simulation/provider/noise_tools/noise.py rename to src/mqt/qudits/simulation/noise_tools/noise.py diff --git a/src/mqt/qudits/simulation/provider/noise_tools/noisy_circuit_factory.py b/src/mqt/qudits/simulation/noise_tools/noisy_circuit_factory.py similarity index 81% rename from src/mqt/qudits/simulation/provider/noise_tools/noisy_circuit_factory.py rename to src/mqt/qudits/simulation/noise_tools/noisy_circuit_factory.py index fa9835d..1505369 100644 --- a/src/mqt/qudits/simulation/provider/noise_tools/noisy_circuit_factory.py +++ b/src/mqt/qudits/simulation/noise_tools/noisy_circuit_factory.py @@ -1,17 +1,21 @@ +from __future__ import annotations + import copy import os import time +from typing import TYPE_CHECKING import numpy as np -from mqt.qudits.qudit_circuits.circuit import QuantumCircuit -from mqt.qudits.qudit_circuits.components.instructions.gate_extensions.gate_types import GateTypes -from mqt.qudits.qudit_circuits.components.instructions.gate_set.r import R -from mqt.qudits.qudit_circuits.components.instructions.gate_set.rz import Rz -from mqt.qudits.simulation.provider.noise_tools.noise import NoiseModel + +from ...quantum_circuit import QuantumCircuit, gates +from ...quantum_circuit.gate import GateTypes + +if TYPE_CHECKING: + from .noise import NoiseModel class NoisyCircuitFactory: - def __init__(self, noise_model: NoiseModel, circuit: QuantumCircuit): + def __init__(self, noise_model: NoiseModel, circuit: QuantumCircuit) -> None: self.noise_model = noise_model self.circuit = circuit @@ -30,7 +34,7 @@ def generate_circuit(self): noisy_circuit.instructions.append(copied_instruction) noisy_circuit.number_gates += 1 - # Append an error depening on the prob + # Append an error depending on the prob if instruction.qasm_tag in self.noise_model.quantum_errors: for mode, noise_info in self.noise_model.quantum_errors[instruction.qasm_tag].items(): x_prob = [(1 - noise_info.probability_depolarizing), noise_info.probability_depolarizing] @@ -47,9 +51,7 @@ def generate_circuit(self): elif mode == "all": qudits = list(range(instruction.parent_circuit.num_qudits)) elif mode == "nonlocal": - assert ( - instruction.gate_type == GateTypes.TWO or instruction.gate_type == GateTypes.MULTI - ) + assert instruction.gate_type in {GateTypes.TWO, GateTypes.MULTI} qudits = instruction.reference_lines elif mode == "control": assert instruction.gate_type == GateTypes.TWO @@ -61,7 +63,7 @@ def generate_circuit(self): pass if x_choice == 1: - if isinstance(instruction, (R, Rz)): + if isinstance(instruction, (gates.R, gates.Rz)): for dit in qudits: noisy_circuit.r(dit, [instruction.lev_a, instruction.lev_b, np.pi, np.pi / 2]) else: @@ -69,7 +71,7 @@ def generate_circuit(self): noisy_circuit.x(dit) noisy_circuit.number_gates += 1 if z_choice == 1: - if isinstance(instruction, (R, Rz)): + if isinstance(instruction, (gates.R, gates.Rz)): for dit in qudits: noisy_circuit.rz(dit, [instruction.lev_a, instruction.lev_b, np.pi]) else: diff --git a/src/mqt/qudits/simulation/provider/__init__.py b/src/mqt/qudits/simulation/provider/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/mqt/qudits/simulation/provider/backend_properties/__init__.py b/src/mqt/qudits/simulation/provider/backend_properties/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/mqt/qudits/simulation/provider/backend_properties/quditproperties.py b/src/mqt/qudits/simulation/provider/backend_properties/quditproperties.py deleted file mode 100644 index 6b882c7..0000000 --- a/src/mqt/qudits/simulation/provider/backend_properties/quditproperties.py +++ /dev/null @@ -1,3 +0,0 @@ -class QuditProperties: - def __init__(self): - pass diff --git a/src/mqt/qudits/simulation/provider/backends/__init__.py b/src/mqt/qudits/simulation/provider/backends/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/mqt/qudits/simulation/provider/backends/backendv2.py b/src/mqt/qudits/simulation/provider/backends/backendv2.py deleted file mode 100644 index 4bfbad3..0000000 --- a/src/mqt/qudits/simulation/provider/backends/backendv2.py +++ /dev/null @@ -1,146 +0,0 @@ -from abc import ABC, abstractmethod -from datetime import datetime -from typing import Iterable, List, Optional, Tuple, Union - -from mqt.qudits.qudit_circuits.components.instructions.instruction import Instruction -from mqt.qudits.simulation.provider.backend_properties.quditproperties import QuditProperties -from mqt.qudits.simulation.provider.jobs.job import Job -from mqt.qudits.simulation.provider.provider import Provider - - -class Backend(ABC): - @property - def version(self): - return 2 - - def __init__( - self, - provider: Optional[Provider] = None, - name: Optional[str] = None, - description: Optional[str] = None, - online_date: Optional[datetime] = None, - backend_version: Optional[str] = None, - **fields, - ): - self._options = self._default_options() - self._provider = provider - - if fields: - # for field in fields: - # if field not in self._options.data: - # msg = f"Options field '{field}' is not valid for this backend" - # raise AttributeError(msg) - self._options.update(fields) - - self.name = name - self.description = description - self.online_date = online_date - self.backend_version = backend_version - self._coupling_map = None - self._energy_level_graphs = None - - @property - def instructions(self) -> List[Tuple[Instruction, Tuple[int]]]: - return self.target.instructions - - @property - def operations(self) -> List[Instruction]: - return list(self.target.operations) - - @property - def operation_names(self) -> List[str]: - return list(self.target.operation_names) - - @property - @abstractmethod - def target(self): - pass - - @property - def num_qudits(self) -> int: - return self.target.num_qudits - - @property - def coupling_map(self): - raise NotImplementedError - """ - if self._coupling_map is None: - self._coupling_map = self.target.build_coupling_map() - return self._coupling_map - """ - - @property - def energy_level_graphs(self): - raise NotImplementedError - """ - if self._energy_level_graphs is None: - self._energy_level_graphs = self.target.build_energy_level_graphs() - return self._energy_level_graphs - """ - - @property - def instruction_durations(self): - raise NotImplementedError - - @property - @abstractmethod - def max_circuits(self): - pass - - def _default_options(self): - return {"shots": 1000, "memory": False} - - @property - def dt(self) -> Union[float, None]: - raise NotImplementedError - - @property - def dtm(self) -> float: - raise NotImplementedError - - @property - def meas_map(self) -> List[List[int]]: - raise NotImplementedError - - @property - def instruction_schedule_map(self): - raise NotImplementedError - - @abstractmethod - def qudit_properties(self, qudit: Union[int, List[int]]) -> Union[QuditProperties, List[QuditProperties]]: - raise NotImplementedError - - @abstractmethod - def drive_channel(self, qudit: int): - raise NotImplementedError - - @abstractmethod - def measure_channel(self, qudit: int): - raise NotImplementedError - - @abstractmethod - def acquire_channel(self, qudit: int): - raise NotImplementedError - - @abstractmethod - def control_channel(self, qudits: Iterable[int]): - raise NotImplementedError - - def set_options(self, **fields): - for field in fields: - if not hasattr(self._options, field): - msg = f"Options field '{field}' is not valid for this backend" - raise AttributeError(msg) - self._options.update_options(**fields) - - @property - def options(self): - return self._options - - @property - def provider(self): - return self._provider - - @abstractmethod - def run(self, run_input, **options) -> Job: - pass diff --git a/src/mqt/qudits/simulation/provider/backends/engines/__init__.py b/src/mqt/qudits/simulation/provider/backends/engines/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/mqt/qudits/simulation/provider/backends/engines/misim.py b/src/mqt/qudits/simulation/provider/backends/engines/misim.py deleted file mode 100644 index 4e564f1..0000000 --- a/src/mqt/qudits/simulation/provider/backends/engines/misim.py +++ /dev/null @@ -1,73 +0,0 @@ -from functools import reduce -from typing import Iterable, List, Union - -import numpy as np -from mqt.misim import pymisim as misim -from mqt.qudits.qudit_circuits.circuit import QuantumCircuit -from mqt.qudits.simulation.provider.backend_properties.quditproperties import QuditProperties -from mqt.qudits.simulation.provider.backends.backendv2 import Backend -from mqt.qudits.simulation.provider.backends.stocastic_components.stocastic_sim import stocastic_simulation_misim -from mqt.qudits.simulation.provider.jobs.job import Job -from mqt.qudits.simulation.provider.jobs.job_result.job_result import JobResult -from mqt.qudits.simulation.provider.noise_tools.noise import NoiseModel - - -class MISim(Backend): - @property - def target(self): - raise NotImplementedError - - @property - def max_circuits(self): - raise NotImplementedError - - def qudit_properties(self, qudit: Union[int, List[int]]) -> Union[QuditProperties, List[QuditProperties]]: - raise NotImplementedError - - def drive_channel(self, qudit: int): - raise NotImplementedError - - def measure_channel(self, qudit: int): - raise NotImplementedError - - def acquire_channel(self, qudit: int): - raise NotImplementedError - - def control_channel(self, qudits: Iterable[int]): - raise NotImplementedError - - def run(self, circuit: QuantumCircuit, **options): - job = Job(self) - - self._options.update(options) - self.noise_model = self._options.get("noise_model", None) - self.shots = self._options.get("shots", 1 if self.noise_model is None else 1000) - self.memory = self._options.get("memory", False) - self.full_state_memory = self._options.get("full_state_memory", False) - self.file_path = self._options.get("file_path", None) - self.file_name = self._options.get("file_name", None) - - if self.noise_model is not None: - assert self.shots >= 1000, "Number of shots should be above 1000" - job.set_result( - JobResult(state_vector=self.execute(circuit), counts=stocastic_simulation_misim(self, circuit)) - ) - else: - job.set_result(JobResult(state_vector=self.execute(circuit), counts=None)) - - return job - - def execute(self, circuit: QuantumCircuit, noise_model=None): - self.system_sizes = circuit.dimensions - self.circ_operations = circuit.instructions - if noise_model is None: - noise_model = NoiseModel() - result = misim.state_vector_simulation(circuit, noise_model) - state = np.array(result) - state_size = reduce(lambda x, y: x * y, self.system_sizes, 1) - return state.reshape(1, state_size) - - def __init__(self, **fields): - self.system_sizes = None - self.circ_operations = None - super().__init__(**fields) diff --git a/src/mqt/qudits/simulation/provider/backends/fake_backends/__init__.py b/src/mqt/qudits/simulation/provider/backends/fake_backends/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/mqt/qudits/simulation/provider/backends/stocastic_components/__init__.py b/src/mqt/qudits/simulation/provider/backends/stocastic_components/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/mqt/qudits/simulation/provider/jobs/__init__.py b/src/mqt/qudits/simulation/provider/jobs/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/mqt/qudits/simulation/provider/jobs/job_result/__init__.py b/src/mqt/qudits/simulation/provider/jobs/job_result/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/mqt/qudits/simulation/provider/noise_tools/__init__.py b/src/mqt/qudits/simulation/provider/noise_tools/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/mqt/qudits/simulation/provider/provider.py b/src/mqt/qudits/simulation/provider/provider.py deleted file mode 100644 index 072d145..0000000 --- a/src/mqt/qudits/simulation/provider/provider.py +++ /dev/null @@ -1,31 +0,0 @@ -from abc import ABC, abstractmethod -from typing import List, Optional - -from mqt.qudits.exceptions.backenderror import BackendNotFoundError - - -class Provider(ABC): - """Base class for a Backend Provider.""" - - @property - def version(self): - return 0 - - @abstractmethod - def get_backend(self, name: Optional[str] = None, **kwargs) -> "Backend": - """Return a single backend matching the specified filtering.""" - backends = self.backends(name, **kwargs) - if len(backends) > 1: - msg = "More than one backend matches the criteria" - raise BackendNotFoundError(msg) - if not backends: - msg = "No backend matches the criteria" - raise BackendNotFoundError(msg) - return backends[0] - - @abstractmethod - def backends(self, name: Optional[str] = None, **kwargs) -> List["Backend"]: - """Return a list of backends matching the specified filtering.""" - - def __eq__(self, other): - return type(self).__name__ == type(other).__name__ diff --git a/src/mqt/qudits/simulation/provider/qudit_provider.py b/src/mqt/qudits/simulation/provider/qudit_provider.py deleted file mode 100644 index 31f5b36..0000000 --- a/src/mqt/qudits/simulation/provider/qudit_provider.py +++ /dev/null @@ -1,33 +0,0 @@ -import re -from typing import ClassVar, List, Optional - -from mqt.qudits.simulation.provider.backends.engines.misim import MISim -from mqt.qudits.simulation.provider.backends.engines.tnsim import TNSim -from mqt.qudits.simulation.provider.backends.fake_backends.fake_traps2six import FakeIonTraps2Six -from mqt.qudits.simulation.provider.backends.fake_backends.fake_traps2three import FakeIonTraps2Trits -from mqt.qudits.simulation.provider.provider import Provider - - -class MQTQuditProvider(Provider): - __backends: ClassVar[dict] = { - "tnsim": TNSim, - "misim": MISim, - "faketraps2trits": FakeIonTraps2Trits, - "faketraps2six": FakeIonTraps2Six, - } - - def get_backend(self, name: Optional[str] = None, **kwargs) -> "Backend": - keys_with_pattern = None - regex = re.compile(name) - for key in self.__backends: - if regex.search(key): - keys_with_pattern = key - return self.__backends[keys_with_pattern](**kwargs) - - def backends(self, name: Optional[str] = None, **kwargs) -> List["Backend"]: - keys_with_pattern = [] - regex = re.compile(name) - for key in self.__backends: - if regex.search(key): - keys_with_pattern.append(key) - return keys_with_pattern diff --git a/src/mqt/qudits/simulation/qudit_provider.py b/src/mqt/qudits/simulation/qudit_provider.py new file mode 100644 index 0000000..c4cd869 --- /dev/null +++ b/src/mqt/qudits/simulation/qudit_provider.py @@ -0,0 +1,47 @@ +from __future__ import annotations + +import re +from typing import TYPE_CHECKING, ClassVar + +from .backends import MISim, TNSim +from .backends.fake_backends import FakeIonTraps2Six, FakeIonTraps2Trits + +if TYPE_CHECKING: + from .backends.backendv2 import Backend + + +class MQTQuditProvider: + @property + def version(self) -> int: + return 0 + + __backends: ClassVar[dict] = { + "tnsim": TNSim, + "misim": MISim, + "faketraps2trits": FakeIonTraps2Trits, + "faketraps2six": FakeIonTraps2Six, + } + + def get_backend(self, name: str | None = None, **kwargs) -> Backend: + """Return a single backend matching the specified filtering.""" + keys_with_pattern = None + regex = re.compile(name) + for key in self.__backends: + if regex.search(key): + keys_with_pattern = key + return self.__backends[keys_with_pattern](**kwargs) + + def backends(self, name: str | None = None, **kwargs) -> list[Backend]: + """Return a list of backends matching the specified filtering.""" + keys_with_pattern = [] + regex = re.compile(name) + for key in self.__backends: + if regex.search(key): + keys_with_pattern.append(key) + return keys_with_pattern + + def __eq__(self, other: object) -> bool: + return type(self).__name__ == type(other).__name__ + + def __hash__(self) -> int: + return hash(type(self).__name__) diff --git a/src/mqt/qudits/simulation/data_log/save_info.py b/src/mqt/qudits/simulation/save_info.py similarity index 100% rename from src/mqt/qudits/simulation/data_log/save_info.py rename to src/mqt/qudits/simulation/save_info.py diff --git a/src/mqt/qudits/visualisation/__init__.py b/src/mqt/qudits/visualisation/__init__.py index e69de29..cf3d571 100644 --- a/src/mqt/qudits/visualisation/__init__.py +++ b/src/mqt/qudits/visualisation/__init__.py @@ -0,0 +1,13 @@ +from __future__ import annotations + +from .drawing_routines import draw_qudit_local +from .mini_quantum_information import get_density_matrix_from_counts, partial_trace +from .plot_information import plot_counts, plot_state + +__all__ = [ + "draw_qudit_local", + "get_density_matrix_from_counts", + "partial_trace", + "plot_counts", + "plot_state", +] diff --git a/src/mqt/qudits/visualisation/drawing_routines.py b/src/mqt/qudits/visualisation/drawing_routines.py index bfde118..b4207f0 100644 --- a/src/mqt/qudits/visualisation/drawing_routines.py +++ b/src/mqt/qudits/visualisation/drawing_routines.py @@ -1,18 +1,18 @@ -from mqt.qudits.qudit_circuits.components.instructions.gate_extensions.gate_types import GateTypes -from mqt.qudits.qudit_circuits.components.instructions.gate_set.custom_one import CustomOne -from mqt.qudits.qudit_circuits.components.instructions.gate_set.r import R -from mqt.qudits.qudit_circuits.components.instructions.gate_set.virt_rz import VirtRz +from __future__ import annotations +from ..quantum_circuit import QuantumCircuit, gates +from ..quantum_circuit.gate import GateTypes -def draw_qudit_local(circuit): + +def draw_qudit_local(circuit: QuantumCircuit) -> None: for line in range(circuit.num_qudits): print("|0>---", end="") for gate in circuit.instructions: if gate.gate_type == GateTypes.SINGLE and line == gate._target_qudits: - if isinstance(gate, VirtRz): + if isinstance(gate, gates.VirtRz): print("--[VRz" + str(gate.lev_a) + "(" + str(round(gate.phi, 2)) + ")]--", end="") - elif isinstance(gate, R): + elif isinstance(gate, gates.R): print( "--[R" + str(gate.lev_a) @@ -25,7 +25,7 @@ def draw_qudit_local(circuit): end="", ) - elif isinstance(gate, CustomOne): + elif isinstance(gate, gates.CustomOne): print("--[CuOne]--", end="") else: diff --git a/src/mqt/qudits/visualisation/mini_quantum_information.py b/src/mqt/qudits/visualisation/mini_quantum_information.py index 0ce854a..aea835f 100644 --- a/src/mqt/qudits/visualisation/mini_quantum_information.py +++ b/src/mqt/qudits/visualisation/mini_quantum_information.py @@ -1,10 +1,13 @@ +from __future__ import annotations + import operator from collections import Counter from functools import reduce import numpy as np -from mqt.qudits.qudit_circuits.components.instructions.mini_tools.matrix_factory_tools import from_dirac_to_basis -from mqt.qudits.visualisation.plot_information import state_labels + +from ..quantum_circuit.matrix_factory import from_dirac_to_basis +from .plot_information import state_labels def get_density_matrix_from_counts(results, circuit): diff --git a/src/mqt/qudits/visualisation/plot_information.py b/src/mqt/qudits/visualisation/plot_information.py index 56fb92c..7342544 100644 --- a/src/mqt/qudits/visualisation/plot_information.py +++ b/src/mqt/qudits/visualisation/plot_information.py @@ -1,18 +1,23 @@ +from __future__ import annotations + import itertools +from typing import TYPE_CHECKING import matplotlib.pyplot as plt import numpy as np -from mqt.qudits.qudit_circuits.circuit import QuantumCircuit + +if TYPE_CHECKING: + from ..quantum_circuit import QuantumCircuit class HistogramWithErrors: - def __init__(self, labels, counts, errors, title="Simulation"): + def __init__(self, labels, counts, errors, title="Simulation") -> None: self.labels = labels self.counts = counts self.errors = errors self.title = title - def generate_histogram(self): + def generate_histogram(self) -> None: plt.bar(self.labels, self.counts, yerr=self.errors, capsize=5, color="b", alpha=0.7, align="center") plt.xlabel("States") plt.ylabel("Pr") @@ -21,7 +26,7 @@ def generate_histogram(self): plt.tight_layout() plt.show() - def save_to_png(self, filename): + def save_to_png(self, filename) -> None: plt.bar(self.labels, self.counts, yerr=self.errors, capsize=5, color="b", alpha=0.7, align="center") plt.xlabel("States") plt.ylabel("Pr") From c1b01d477ed291613798bc2fb42bcb9f405de1f6 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Fri, 19 Apr 2024 21:49:17 +0200 Subject: [PATCH 15/22] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor=20tests=20(?= =?UTF-8?q?which=20are=20hardly=20existing)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/mqt/qudits/quantum_circuit/__init__.py | 3 +- test/python/compiler/__init__.py | 0 test/python/compiler/onedit/__init__.py | 0 test/python/compiler/twodit/__init__.py | 0 .../compiler/twodit/entangled_qr/__init__.py | 0 .../twodit/entangled_qr/test_entangled_qr.py | 15 ++-- .../twodit/entangled_qr/test_search.py | 67 --------------- test/python/qudits_circuits/__init__.py | 0 .../qudits_circuits/components/__init__.py | 0 .../components/instructions/__init__.py | 0 .../instructions/gate_set/__init__.py | 0 .../instructions/gate_set/test_csum.py | 86 ------------------- test/python/qudits_circuits/test_csum.py | 81 +++++++++++++++++ .../instructions/gate_set => }/test_cx.py | 41 +++++---- .../instructions/gate_set => }/test_s.py | 5 +- .../instructions/gate_set => }/test_x.py | 5 +- .../instructions/gate_set => }/test_z.py | 7 +- test/python/test.py | 11 +-- 18 files changed, 129 insertions(+), 192 deletions(-) delete mode 100644 test/python/compiler/__init__.py delete mode 100644 test/python/compiler/onedit/__init__.py delete mode 100644 test/python/compiler/twodit/__init__.py delete mode 100644 test/python/compiler/twodit/entangled_qr/__init__.py delete mode 100644 test/python/compiler/twodit/entangled_qr/test_search.py delete mode 100644 test/python/qudits_circuits/__init__.py delete mode 100644 test/python/qudits_circuits/components/__init__.py delete mode 100644 test/python/qudits_circuits/components/instructions/__init__.py delete mode 100644 test/python/qudits_circuits/components/instructions/gate_set/__init__.py delete mode 100644 test/python/qudits_circuits/components/instructions/gate_set/test_csum.py create mode 100644 test/python/qudits_circuits/test_csum.py rename test/python/qudits_circuits/{components/instructions/gate_set => }/test_cx.py (58%) rename test/python/qudits_circuits/{components/instructions/gate_set => }/test_s.py (84%) rename test/python/qudits_circuits/{components/instructions/gate_set => }/test_x.py (86%) rename test/python/qudits_circuits/{components/instructions/gate_set => }/test_z.py (82%) diff --git a/src/mqt/qudits/quantum_circuit/__init__.py b/src/mqt/qudits/quantum_circuit/__init__.py index dcd9b9e..bcdfbd6 100644 --- a/src/mqt/qudits/quantum_circuit/__init__.py +++ b/src/mqt/qudits/quantum_circuit/__init__.py @@ -2,8 +2,9 @@ from __future__ import annotations -from .circuit import QuantumCircuit +from .circuit import QuantumCircuit, QuantumRegister __all__ = [ "QuantumCircuit", + "QuantumRegister", ] diff --git a/test/python/compiler/__init__.py b/test/python/compiler/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/test/python/compiler/onedit/__init__.py b/test/python/compiler/onedit/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/test/python/compiler/twodit/__init__.py b/test/python/compiler/twodit/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/test/python/compiler/twodit/entangled_qr/__init__.py b/test/python/compiler/twodit/entangled_qr/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/test/python/compiler/twodit/entangled_qr/test_entangled_qr.py b/test/python/compiler/twodit/entangled_qr/test_entangled_qr.py index 9dbf09d..48ab5a4 100644 --- a/test/python/compiler/twodit/entangled_qr/test_entangled_qr.py +++ b/test/python/compiler/twodit/entangled_qr/test_entangled_qr.py @@ -1,25 +1,24 @@ -from unittest import TestCase +from __future__ import annotations -import numpy as np +from unittest import TestCase from mqt.qudits.compiler.dit_manager import QuditCompiler -from mqt.qudits.qudit_circuits.circuit import QuantumCircuit -from mqt.qudits.simulation.provider.qudit_provider import MQTQuditProvider +from mqt.qudits.quantum_circuit import QuantumCircuit +from mqt.qudits.simulation import MQTQuditProvider class TestEntangledQR(TestCase): def setUp(self) -> None: - provider = MQTQuditProvider() + MQTQuditProvider() self.circuit_33 = QuantumCircuit(2, [3, 3], 0) self.cx = self.circuit_33.cx([0, 1]) def test_entangling_qr(self): - target = self.cx.to_matrix() + self.cx.to_matrix() provider = MQTQuditProvider() backend_ion = provider.get_backend("faketraps2trits", shots=1000) qudit_compiler = QuditCompiler() passes = ["LogEntQRCEXPass"] - compiled_circuit_qr = qudit_compiler.compile(backend_ion, self.circuit_33, passes) - + qudit_compiler.compile(backend_ion, self.circuit_33, passes) diff --git a/test/python/compiler/twodit/entangled_qr/test_search.py b/test/python/compiler/twodit/entangled_qr/test_search.py deleted file mode 100644 index 26b5836..0000000 --- a/test/python/compiler/twodit/entangled_qr/test_search.py +++ /dev/null @@ -1,67 +0,0 @@ -from unittest import TestCase -import numpy as np - -from gates.entangling_gates import ms_gate -from src import global_vars, customize_vars -from src.decomposer.cex import Cex -from src.layered.compile_pkg.ansatz.instantiate import create_cu_instance, create_ls_instance, create_ms_instance -from src.layered.compile_pkg.opt.distance_measures import fidelity_on_unitares -from src.layered.compile_pkg.search import binary_search_compile - -from src.utils.qudit_circ_utils import gate_expand_to_circuit -from src.utils.rotation_utils import matmul - - -class TestSearch(TestCase): - def setUp(self) -> None: - global_vars.OBJ_FIDELITY = 1e-4 - global_vars.SINGLE_DIM = 2 - global_vars.TARGET_GATE = Cex().cex_101(global_vars.SINGLE_DIM) - global_vars.MAX_NUM_LAYERS = (2 * global_vars.SINGLE_DIM ** 2) - - def test_binary_search_compile_ms(self): - - best_layer, best_error, best_xi = binary_search_compile(global_vars.SINGLE_DIM, global_vars.MAX_NUM_LAYERS, - "MS") - - decomposed_target = create_ms_instance(best_xi, global_vars.SINGLE_DIM) - - unitary = gate_expand_to_circuit(np.identity(global_vars.SINGLE_DIM, dtype=complex), n=2, target=0, - dim=global_vars.SINGLE_DIM) - - for rot in decomposed_target: - unitary = matmul(unitary, rot) - - print((1 - fidelity_on_unitares(unitary, global_vars.TARGET_GATE)) < 1e-4) # outcome should be true - - def test_binary_search_compile_ls(self): - - best_layer, best_error, best_xi = binary_search_compile(global_vars.SINGLE_DIM, global_vars.MAX_NUM_LAYERS, - "LS") - - decomposed_target = create_ls_instance(best_xi, global_vars.SINGLE_DIM) - - unitary = gate_expand_to_circuit(np.identity(global_vars.SINGLE_DIM, dtype=complex), n=2, target=0, - dim=global_vars.SINGLE_DIM) - - for rot in decomposed_target: - unitary = matmul(unitary, rot) - - print((1 - fidelity_on_unitares(unitary, global_vars.TARGET_GATE)) < 1e-4) # outcome should be true - - def test_binary_search_compile_cu(self): - - customize_vars.CUSTOM_PRIMITIVE = ms_gate(np.pi / 2, global_vars.SINGLE_DIM) - - best_layer, best_error, best_xi = binary_search_compile(global_vars.SINGLE_DIM, global_vars.MAX_NUM_LAYERS, - "CU") - - decomposed_target = create_cu_instance(best_xi, global_vars.SINGLE_DIM) - - unitary = gate_expand_to_circuit(np.identity(global_vars.SINGLE_DIM, dtype=complex), n=2, target=0, - dim=global_vars.SINGLE_DIM) - - for rot in decomposed_target: - unitary = matmul(unitary, rot) - - print((1 - fidelity_on_unitares(unitary, global_vars.TARGET_GATE)) < 1e-4) # outcome should be true diff --git a/test/python/qudits_circuits/__init__.py b/test/python/qudits_circuits/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/test/python/qudits_circuits/components/__init__.py b/test/python/qudits_circuits/components/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/test/python/qudits_circuits/components/instructions/__init__.py b/test/python/qudits_circuits/components/instructions/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/test/python/qudits_circuits/components/instructions/gate_set/__init__.py b/test/python/qudits_circuits/components/instructions/gate_set/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/test/python/qudits_circuits/components/instructions/gate_set/test_csum.py b/test/python/qudits_circuits/components/instructions/gate_set/test_csum.py deleted file mode 100644 index 11d36c7..0000000 --- a/test/python/qudits_circuits/components/instructions/gate_set/test_csum.py +++ /dev/null @@ -1,86 +0,0 @@ -from unittest import TestCase - -import numpy as np -from mqt.qudits.qudit_circuits.circuit import QuantumCircuit - - -class TestCSum(TestCase): - def setUp(self): - self.circuit_33 = QuantumCircuit(2, [3, 3], 0) - self.circuit_23 = QuantumCircuit(2, [2, 3], 0) - self.circuit_32 = QuantumCircuit(2, [3, 2], 0) - - def test___array__(self): - csum = self.circuit_33.csum([0, 1]) - matrix = csum.to_matrix(identities=0) - assert np.allclose( - np.array( - [ - [1, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 1, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 1, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 1, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 1, 0, 0, 0], - [0, 0, 0, 1, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 1], - [0, 0, 0, 0, 0, 0, 1, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 1, 0], - ] - ), - matrix, - ) - - csum_10 = self.circuit_33.csum([1, 0]) - matrix_10 = csum_10.to_matrix(identities=0) - assert np.allclose( - np.array( - [ - [1, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 1, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 1, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 1, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 1, 0, 0, 0], - [0, 0, 0, 1, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 1], - [0, 0, 0, 0, 0, 0, 1, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 1, 0], - ] - ), - matrix_10, - ) - - csum_23_01 = self.circuit_23.csum([0, 1]) - matrix_23_01 = csum_23_01.to_matrix(identities=0) - assert np.allclose( - np.array( - [ - [1, 0, 0, 0, 0, 0], - [0, 1, 0, 0, 0, 0], - [0, 0, 1, 0, 0, 0], - [0, 0, 0, 0, 1, 0], - [0, 0, 0, 0, 0, 1], - [0, 0, 0, 1, 0, 0], - ] - ), - matrix_23_01, - ) - - csum_32_01 = self.circuit_32.csum([0, 1]) - matrix_32_01 = csum_32_01.to_matrix(identities=0) - assert np.allclose( - np.array( - [ - [1, 0, 0, 0, 0, 0], - [0, 1, 0, 0, 0, 0], - [0, 0, 0, 1, 0, 0], - [0, 0, 1, 0, 0, 0], - [0, 0, 0, 0, 1, 0], - [0, 0, 0, 0, 0, 1], - ] - ), - matrix_32_01, - ) - - def test_validate_parameter(self): - csum = self.circuit_33.csum([0, 1]) - assert csum.validate_parameter() diff --git a/test/python/qudits_circuits/test_csum.py b/test/python/qudits_circuits/test_csum.py new file mode 100644 index 0000000..ed17b52 --- /dev/null +++ b/test/python/qudits_circuits/test_csum.py @@ -0,0 +1,81 @@ +from __future__ import annotations + +from unittest import TestCase + +import numpy as np + +from mqt.qudits.quantum_circuit import QuantumCircuit + + +class TestCSum(TestCase): + def setUp(self): + self.circuit_33 = QuantumCircuit(2, [3, 3], 0) + self.circuit_23 = QuantumCircuit(2, [2, 3], 0) + self.circuit_32 = QuantumCircuit(2, [3, 2], 0) + + def test___array__(self): + csum = self.circuit_33.csum([0, 1]) + matrix = csum.to_matrix(identities=0) + assert np.allclose( + np.array([ + [1, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 1, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 1, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 1, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 1, 0, 0, 0], + [0, 0, 0, 1, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 1], + [0, 0, 0, 0, 0, 0, 1, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 1, 0], + ]), + matrix, + ) + + csum_10 = self.circuit_33.csum([1, 0]) + matrix_10 = csum_10.to_matrix(identities=0) + assert np.allclose( + np.array([ + [1, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 1, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 1, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 1, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 1, 0, 0, 0], + [0, 0, 0, 1, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 1], + [0, 0, 0, 0, 0, 0, 1, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 1, 0], + ]), + matrix_10, + ) + + csum_23_01 = self.circuit_23.csum([0, 1]) + matrix_23_01 = csum_23_01.to_matrix(identities=0) + assert np.allclose( + np.array([ + [1, 0, 0, 0, 0, 0], + [0, 1, 0, 0, 0, 0], + [0, 0, 1, 0, 0, 0], + [0, 0, 0, 0, 1, 0], + [0, 0, 0, 0, 0, 1], + [0, 0, 0, 1, 0, 0], + ]), + matrix_23_01, + ) + + csum_32_01 = self.circuit_32.csum([0, 1]) + matrix_32_01 = csum_32_01.to_matrix(identities=0) + assert np.allclose( + np.array([ + [1, 0, 0, 0, 0, 0], + [0, 1, 0, 0, 0, 0], + [0, 0, 0, 1, 0, 0], + [0, 0, 1, 0, 0, 0], + [0, 0, 0, 0, 1, 0], + [0, 0, 0, 0, 0, 1], + ]), + matrix_32_01, + ) + + def test_validate_parameter(self): + csum = self.circuit_33.csum([0, 1]) + assert csum.validate_parameter() diff --git a/test/python/qudits_circuits/components/instructions/gate_set/test_cx.py b/test/python/qudits_circuits/test_cx.py similarity index 58% rename from test/python/qudits_circuits/components/instructions/gate_set/test_cx.py rename to test/python/qudits_circuits/test_cx.py index 46f4b41..ffb871d 100644 --- a/test/python/qudits_circuits/components/instructions/gate_set/test_cx.py +++ b/test/python/qudits_circuits/test_cx.py @@ -1,7 +1,10 @@ +from __future__ import annotations + from unittest import TestCase import numpy as np -from mqt.qudits.qudit_circuits.circuit import QuantumCircuit + +from mqt.qudits.quantum_circuit import QuantumCircuit class TestCEx(TestCase): @@ -16,29 +19,25 @@ def test___array__(self): matrix_23_10 = cx_23_10.to_matrix(identities=0) assert np.allclose( - np.array( - [ - [1, 0, 0, 0, 0, 0], - [0, 1, 0, 0, 0, 0], - [0, 0, 1, 0, 0, 0], - [0, 0, 0, 0, -1j, 0], - [0, 0, 0, -1j, 0, 0], - [0, 0, 0, 0, 0, 1], - ] - ), + np.array([ + [1, 0, 0, 0, 0, 0], + [0, 1, 0, 0, 0, 0], + [0, 0, 1, 0, 0, 0], + [0, 0, 0, 0, -1j, 0], + [0, 0, 0, -1j, 0, 0], + [0, 0, 0, 0, 0, 1], + ]), matrix_23_01, ) assert np.allclose( - np.array( - [ - [1, 0, 0, 0, 0, 0], - [0, 1, 0, 0, 0, 0], - [0, 0, 1, 0, 0, 0], - [0, 0, 0, 1, 0, 0], - [0, 0, 0, 0, 1, 0], - [0, 0, 0, 0, 0, 1], - ] - ), + np.array([ + [1, 0, 0, 0, 0, 0], + [0, 1, 0, 0, 0, 0], + [0, 0, 1, 0, 0, 0], + [0, 0, 0, 1, 0, 0], + [0, 0, 0, 0, 1, 0], + [0, 0, 0, 0, 0, 1], + ]), matrix_23_10, ) diff --git a/test/python/qudits_circuits/components/instructions/gate_set/test_s.py b/test/python/qudits_circuits/test_s.py similarity index 84% rename from test/python/qudits_circuits/components/instructions/gate_set/test_s.py rename to test/python/qudits_circuits/test_s.py index 918fe4c..f3dd8c5 100644 --- a/test/python/qudits_circuits/components/instructions/gate_set/test_s.py +++ b/test/python/qudits_circuits/test_s.py @@ -1,7 +1,10 @@ +from __future__ import annotations + from unittest import TestCase import numpy as np -from mqt.qudits.qudit_circuits.circuit import QuantumCircuit + +from mqt.qudits.quantum_circuit import QuantumCircuit def omega_d(d): diff --git a/test/python/qudits_circuits/components/instructions/gate_set/test_x.py b/test/python/qudits_circuits/test_x.py similarity index 86% rename from test/python/qudits_circuits/components/instructions/gate_set/test_x.py rename to test/python/qudits_circuits/test_x.py index 9b93f7c..74333dc 100644 --- a/test/python/qudits_circuits/components/instructions/gate_set/test_x.py +++ b/test/python/qudits_circuits/test_x.py @@ -1,7 +1,10 @@ +from __future__ import annotations + from unittest import TestCase import numpy as np -from mqt.qudits.qudit_circuits.circuit import QuantumCircuit + +from mqt.qudits.quantum_circuit import QuantumCircuit class TestX(TestCase): diff --git a/test/python/qudits_circuits/components/instructions/gate_set/test_z.py b/test/python/qudits_circuits/test_z.py similarity index 82% rename from test/python/qudits_circuits/components/instructions/gate_set/test_z.py rename to test/python/qudits_circuits/test_z.py index 90ff59c..20038fa 100644 --- a/test/python/qudits_circuits/components/instructions/gate_set/test_z.py +++ b/test/python/qudits_circuits/test_z.py @@ -1,7 +1,10 @@ +from __future__ import annotations + from unittest import TestCase import numpy as np -from mqt.qudits.qudit_circuits.circuit import QuantumCircuit + +from mqt.qudits.quantum_circuit import QuantumCircuit def omega_d(d): @@ -19,7 +22,7 @@ def test___array__(self): z_1 = self.circuit_23.z(1) matrix_1 = z_1.to_matrix(identities=0) - assert np.allclose(np.array([[1, 0, 0], [0, omega_d(3), 0], [0, 0, (omega_d(3)**2)]]), matrix_1) + assert np.allclose(np.array([[1, 0, 0], [0, omega_d(3), 0], [0, 0, (omega_d(3) ** 2)]]), matrix_1) def test_validate_parameter(self): z = self.circuit_23.z(0) diff --git a/test/python/test.py b/test/python/test.py index b3deb97..5e69f4c 100644 --- a/test/python/test.py +++ b/test/python/test.py @@ -1,7 +1,8 @@ -import mqt.misim.pymisim as misim -from mqt.qudits.qudit_circuits.circuit import QuantumCircuit -from mqt.qudits.qudit_circuits.components.registers.quantum_register import QuantumRegister -from mqt.qudits.simulation.provider.noise_tools.noise import Noise, NoiseModel +from __future__ import annotations + +from mqt.qudits._qudits.misim import state_vector_simulation +from mqt.qudits.quantum_circuit import QuantumCircuit, QuantumRegister +from mqt.qudits.simulation.noise_tools import Noise, NoiseModel qreg_example = QuantumRegister("reg", 2, [2, 3]) circ = QuantumCircuit(qreg_example) @@ -38,4 +39,4 @@ print("HELLOO") # num_qudits, dimensions, props = qcread.get_quantum_circuit_properties(circ) -print(misim.state_vector_simulation(circ, NoiseModel())) +print(state_vector_simulation(circ, NoiseModel())) From 0dc22b4ffe22c0b2e0d15d042f76fd24776e14da Mon Sep 17 00:00:00 2001 From: burgholzer Date: Fri, 19 Apr 2024 21:50:09 +0200 Subject: [PATCH 16/22] =?UTF-8?q?=F0=9F=9A=A8=20run=20ruff=20format?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MQT Tutorial/MQT Qudits Tutorial.ipynb | 23 ++++++++++++++--------- src/mqt/qudits/_qudits/misim.pyi | 2 -- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/MQT Tutorial/MQT Qudits Tutorial.ipynb b/MQT Tutorial/MQT Qudits Tutorial.ipynb index e16ee65..3725f0b 100644 --- a/MQT Tutorial/MQT Qudits Tutorial.ipynb +++ b/MQT Tutorial/MQT Qudits Tutorial.ipynb @@ -431,7 +431,7 @@ "Q, R = np.linalg.qr(random_matrix)\n", "\n", "unitary_matrix = Q\n", - "cu = circuit.cu_one(field_reg[0], unitary_matrix)\n" + "cu = circuit.cu_one(field_reg[0], unitary_matrix)" ] }, { @@ -452,7 +452,7 @@ "metadata": {}, "outputs": [], "source": [ - "r = circuit.r(field_reg[0], [0, 1, np.pi/5, np.pi/7]) " + "r = circuit.r(field_reg[0], [0, 1, np.pi / 5, np.pi / 7])" ] }, { @@ -464,7 +464,7 @@ "source": [ "from mqt.qudits.qudit_circuits.components.instructions.gate_extensions.controls import ControlData\n", "\n", - "r_c1 = circuit.r(field_reg[0], [0, 1, np.pi/5, np.pi/7], ControlData( [matter_reg[0]] , [1]))" + "r_c1 = circuit.r(field_reg[0], [0, 1, np.pi / 5, np.pi / 7], ControlData([matter_reg[0]], [1]))" ] }, { @@ -474,7 +474,7 @@ "metadata": {}, "outputs": [], "source": [ - "r_c2 = circuit.r(field_reg[0], [0, 1, np.pi/5, np.pi/7]).control([matter_reg[0]] , [1])" + "r_c2 = circuit.r(field_reg[0], [0, 1, np.pi / 5, np.pi / 7]).control([matter_reg[0]], [1])" ] }, { @@ -950,7 +950,7 @@ "source": [ "backend = provider.get_backend(\"tnsim\")\n", "\n", - "job = backend.run(circuit, noise_model = noise_model)\n", + "job = backend.run(circuit, noise_model=noise_model)\n", "\n", "result = job.result()\n", "counts = result.get_counts()\n", @@ -1023,7 +1023,7 @@ "mapping = backend_ion.energy_level_graphs\n", "\n", "pos = nx.circular_layout(mapping[0])\n", - "nx.draw(mapping[0], pos, with_labels=True, node_size=2000, node_color='lightblue', font_size=12, font_weight='bold')\n", + "nx.draw(mapping[0], pos, with_labels=True, node_size=2000, node_color=\"lightblue\", font_size=12, font_weight=\"bold\")\n", "plt.show()" ] }, @@ -1133,7 +1133,9 @@ "source": [ "compiled_circuit_qr = qudit_compiler.compile(backend_ion, circuit, passes)\n", "\n", - "print(f\"\\n Number of operations: {len(compiled_circuit_qr.instructions)}, \\n Number of qudits in the circuit: {compiled_circuit_qr.num_qudits}\")" + "print(\n", + " f\"\\n Number of operations: {len(compiled_circuit_qr.instructions)}, \\n Number of qudits in the circuit: {compiled_circuit_qr.num_qudits}\"\n", + ")" ] }, { @@ -1201,7 +1203,9 @@ "\n", "compiled_circuit_ada = qudit_compiler.compile(backend_ion, circuit, passes)\n", "\n", - "print(f\"\\n Number of operations: {len(compiled_circuit_ada.instructions)}, \\n Number of qudits in the circuit: {compiled_circuit_ada.num_qudits}\")" + "print(\n", + " f\"\\n Number of operations: {len(compiled_circuit_ada.instructions)}, \\n Number of qudits in the circuit: {compiled_circuit_ada.num_qudits}\"\n", + ")" ] }, { @@ -1264,7 +1268,8 @@ } ], "source": [ - "from mqt.qudits.visualisation.drawing_routines import draw_qudit_local\n", + "from mqt.qudits.visualisation.drawing_routines import draw_qudit_local\n", + "\n", "draw_qudit_local(compiled_circuit_ada)" ] }, diff --git a/src/mqt/qudits/_qudits/misim.pyi b/src/mqt/qudits/_qudits/misim.pyi index a92f14f..8b043ab 100644 --- a/src/mqt/qudits/_qudits/misim.pyi +++ b/src/mqt/qudits/_qudits/misim.pyi @@ -1,6 +1,5 @@ from typing import Any - # todo: this need significantly better typing and a better docstring def state_vector_simulation(circuit: Any, noise_model: dict[str, Any]) -> list[complex]: # noqa: ANN401 """Simulate the state vector of a quantum circuit with noise model. @@ -13,5 +12,4 @@ def state_vector_simulation(circuit: Any, noise_model: dict[str, Any]) -> list[c list: The state vector of the quantum circuit """ - __all__ = ["state_vector_simulation"] From 9e03778f76e1597bb908d1b81f53f7513e2657df Mon Sep 17 00:00:00 2001 From: burgholzer Date: Fri, 19 Apr 2024 21:52:50 +0200 Subject: [PATCH 17/22] =?UTF-8?q?=F0=9F=9A=A8=20autofixes=20from=20ruff?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MQT Tutorial/MQT Qudits Tutorial.ipynb | 6 ++---- MQT Tutorial/MQT Qudits in Short.ipynb | 2 -- pyproject.toml | 2 +- .../local_compilation_minitools.py | 2 ++ .../numerical_ansatz_utils.py | 2 ++ src/mqt/qudits/compiler/compiler_pass.py | 4 +++- src/mqt/qudits/exceptions/backenderror.py | 5 ++++- src/mqt/qudits/exceptions/circuiterror.py | 5 ++++- src/mqt/qudits/exceptions/compilerexception.py | 17 ++++++++++------- src/mqt/qudits/exceptions/joberror.py | 7 +++++-- src/mqt/qudits/simulation/jobs/job_result.py | 13 ++++++++----- src/mqt/qudits/simulation/jobs/jobstatus.py | 2 ++ src/mqt/qudits/simulation/noise_tools/noise.py | 18 ++++++++++-------- src/mqt/qudits/simulation/save_info.py | 6 ++++-- 14 files changed, 57 insertions(+), 34 deletions(-) diff --git a/MQT Tutorial/MQT Qudits Tutorial.ipynb b/MQT Tutorial/MQT Qudits Tutorial.ipynb index 3725f0b..f7060ed 100644 --- a/MQT Tutorial/MQT Qudits Tutorial.ipynb +++ b/MQT Tutorial/MQT Qudits Tutorial.ipynb @@ -69,6 +69,7 @@ "outputs": [], "source": [ "import numpy as np\n", + "\n", "from mqt.qudits.qudit_circuits.circuit import QuantumCircuit" ] }, @@ -203,7 +204,6 @@ "source": [ "from mqt.qudits.qudit_circuits.components.registers.quantum_register import QuantumRegister\n", "\n", - "\n", "circuit = QuantumCircuit()\n", "\n", "field_reg = QuantumRegister(\"fields\", 1, [7])\n", @@ -734,7 +734,6 @@ "source": [ "from mqt.qudits.qudit_circuits.components.registers.quantum_register import QuantumRegister\n", "\n", - "\n", "circuit = QuantumCircuit()\n", "\n", "field_reg = QuantumRegister(\"fields\", 1, [3])\n", @@ -769,7 +768,6 @@ "source": [ "from mqt.qudits.simulation.provider.qudit_provider import MQTQuditProvider\n", "\n", - "\n", "provider = MQTQuditProvider()\n", "provider.backends(\"sim\")" ] @@ -1017,8 +1015,8 @@ } ], "source": [ - "import networkx as nx\n", "import matplotlib.pyplot as plt\n", + "import networkx as nx\n", "\n", "mapping = backend_ion.energy_level_graphs\n", "\n", diff --git a/MQT Tutorial/MQT Qudits in Short.ipynb b/MQT Tutorial/MQT Qudits in Short.ipynb index cce1659..b239166 100644 --- a/MQT Tutorial/MQT Qudits in Short.ipynb +++ b/MQT Tutorial/MQT Qudits in Short.ipynb @@ -115,7 +115,6 @@ "source": [ "from mqt.qudits.qudit_circuits.components.registers.quantum_register import QuantumRegister\n", "\n", - "\n", "circuit = QuantumCircuit()\n", "\n", "field_reg = QuantumRegister(\"fields\", 1, [3])\n", @@ -174,7 +173,6 @@ "source": [ "from mqt.qudits.simulation.provider.qudit_provider import MQTQuditProvider\n", "\n", - "\n", "provider = MQTQuditProvider()\n", "provider.backends(\"sim\")" ] diff --git a/pyproject.toml b/pyproject.toml index 355fa7d..4a64229 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -193,7 +193,7 @@ extend-select = [ "SLF", # flake8-self "SLOT", # flake8-slots "SIM", # flake8-simplify - "T20", # flake8-print +# "T20", # flake8-print "TCH", # flake8-type-checking "TID251", # flake8-tidy-imports "TRY", # tryceratops diff --git a/src/mqt/qudits/compiler/compilation_minitools/local_compilation_minitools.py b/src/mqt/qudits/compiler/compilation_minitools/local_compilation_minitools.py index 5dd833d..a2b2804 100644 --- a/src/mqt/qudits/compiler/compilation_minitools/local_compilation_minitools.py +++ b/src/mqt/qudits/compiler/compilation_minitools/local_compilation_minitools.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import numpy as np diff --git a/src/mqt/qudits/compiler/compilation_minitools/numerical_ansatz_utils.py b/src/mqt/qudits/compiler/compilation_minitools/numerical_ansatz_utils.py index c59fe16..faef818 100755 --- a/src/mqt/qudits/compiler/compilation_minitools/numerical_ansatz_utils.py +++ b/src/mqt/qudits/compiler/compilation_minitools/numerical_ansatz_utils.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import numpy as np """def calculate_q0_q1(lev, dim): diff --git a/src/mqt/qudits/compiler/compiler_pass.py b/src/mqt/qudits/compiler/compiler_pass.py index bd28466..f9915be 100644 --- a/src/mqt/qudits/compiler/compiler_pass.py +++ b/src/mqt/qudits/compiler/compiler_pass.py @@ -1,8 +1,10 @@ +from __future__ import annotations + from abc import ABC, abstractmethod class CompilerPass(ABC): - def __init__(self, backend, **kwargs): + def __init__(self, backend, **kwargs) -> None: self.backend = backend @abstractmethod diff --git a/src/mqt/qudits/exceptions/backenderror.py b/src/mqt/qudits/exceptions/backenderror.py index c4ae032..62288c6 100644 --- a/src/mqt/qudits/exceptions/backenderror.py +++ b/src/mqt/qudits/exceptions/backenderror.py @@ -1,3 +1,6 @@ +from __future__ import annotations + + class BackendNotFoundError(Exception): - def __init_(self, message): + def __init_(self, message) -> None: print(message) diff --git a/src/mqt/qudits/exceptions/circuiterror.py b/src/mqt/qudits/exceptions/circuiterror.py index 00dc31c..1dcd5d3 100644 --- a/src/mqt/qudits/exceptions/circuiterror.py +++ b/src/mqt/qudits/exceptions/circuiterror.py @@ -1,3 +1,6 @@ +from __future__ import annotations + + class CircuitError(Exception): - def __init_(self, message): + def __init_(self, message) -> None: print(message) diff --git a/src/mqt/qudits/exceptions/compilerexception.py b/src/mqt/qudits/exceptions/compilerexception.py index 265d1db..24ec0b9 100644 --- a/src/mqt/qudits/exceptions/compilerexception.py +++ b/src/mqt/qudits/exceptions/compilerexception.py @@ -1,27 +1,30 @@ +from __future__ import annotations + + class NodeNotFoundException(Exception): - def __init__(self, value): + def __init__(self, value) -> None: self.value = value - def __str__(self): + def __str__(self) -> str: return repr(self.value) class SequenceFoundException(Exception): - def __init__(self, node_key=-1): + def __init__(self, node_key=-1) -> None: self.last_node_id = node_key - def __str__(self): + def __str__(self) -> str: return repr(self.last_node_id) class RoutingException(Exception): - def __init__(self): + def __init__(self) -> None: self.message = "ROUTING PROBLEM STUCK!" - def __str__(self): + def __str__(self) -> str: return repr(self.message) class FidelityReachException(Exception): - def __init__(self, message=""): + def __init__(self, message="") -> None: self.message = message diff --git a/src/mqt/qudits/exceptions/joberror.py b/src/mqt/qudits/exceptions/joberror.py index 2a71744..d899a3c 100644 --- a/src/mqt/qudits/exceptions/joberror.py +++ b/src/mqt/qudits/exceptions/joberror.py @@ -1,8 +1,11 @@ +from __future__ import annotations + + class JobError(Exception): - def __init_(self, message): + def __init_(self, message) -> None: print(message) class JobTimeoutError: - def __init_(self, message): + def __init_(self, message) -> None: print(message) diff --git a/src/mqt/qudits/simulation/jobs/job_result.py b/src/mqt/qudits/simulation/jobs/job_result.py index bf2e369..6083779 100644 --- a/src/mqt/qudits/simulation/jobs/job_result.py +++ b/src/mqt/qudits/simulation/jobs/job_result.py @@ -1,15 +1,18 @@ -from typing import List +from __future__ import annotations -import numpy as np +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + import numpy as np class JobResult: - def __init__(self, state_vector: List[np.complex128], counts: List[int]): + def __init__(self, state_vector: list[np.complex128], counts: list[int]) -> None: self.state_vector = state_vector self.counts = counts - def get_counts(self) -> List[int]: + def get_counts(self) -> list[int]: return self.counts - def get_state_vector(self) -> List[np.complex128]: + def get_state_vector(self) -> list[np.complex128]: return self.state_vector diff --git a/src/mqt/qudits/simulation/jobs/jobstatus.py b/src/mqt/qudits/simulation/jobs/jobstatus.py index cc8e6c0..6a36a4c 100644 --- a/src/mqt/qudits/simulation/jobs/jobstatus.py +++ b/src/mqt/qudits/simulation/jobs/jobstatus.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import enum diff --git a/src/mqt/qudits/simulation/noise_tools/noise.py b/src/mqt/qudits/simulation/noise_tools/noise.py index 2b8f79e..1b4be21 100644 --- a/src/mqt/qudits/simulation/noise_tools/noise.py +++ b/src/mqt/qudits/simulation/noise_tools/noise.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from dataclasses import dataclass @@ -8,42 +10,42 @@ class Noise: class NoiseModel: - def __init__(self): + def __init__(self) -> None: self.quantum_errors = {} - def add_recurrent_quantum_error_locally(self, noise, gates, qudits): + def add_recurrent_quantum_error_locally(self, noise, gates, qudits) -> None: set_of_qudits = tuple(sorted(qudits)) for gate in gates: if gate not in self.quantum_errors: self.quantum_errors[gate] = {} self.quantum_errors[gate][set_of_qudits] = noise - def add_quantum_error_locally(self, noise, gates): + def add_quantum_error_locally(self, noise, gates) -> None: for gate in gates: if gate not in self.quantum_errors: self.quantum_errors[gate] = {} self.quantum_errors[gate]["local"] = noise - def add_all_qudit_quantum_error(self, noise, gates): + def add_all_qudit_quantum_error(self, noise, gates) -> None: for gate in gates: if gate not in self.quantum_errors: self.quantum_errors[gate] = {} self.quantum_errors[gate]["all"] = noise - def add_nonlocal_quantum_error(self, noise, gates): + def add_nonlocal_quantum_error(self, noise, gates) -> None: for gate in gates: if gate not in self.quantum_errors: self.quantum_errors[gate] = {} self.quantum_errors[gate]["nonlocal"] = noise # self.add_quantum_error_locally(noise, gates, crtl_qudits + target_qudits) - def add_nonlocal_quantum_error_on_target(self, noise, gates): + def add_nonlocal_quantum_error_on_target(self, noise, gates) -> None: for gate in gates: if gate not in self.quantum_errors: self.quantum_errors[gate] = {} self.quantum_errors[gate]["target"] = noise - def add_nonlocal_quantum_error_on_control(self, noise, gates): + def add_nonlocal_quantum_error_on_control(self, noise, gates) -> None: for gate in gates: if gate not in self.quantum_errors: self.quantum_errors[gate] = {} @@ -53,7 +55,7 @@ def add_nonlocal_quantum_error_on_control(self, noise, gates): def basis_gates(self): return list(self.quantum_errors.keys()) - def __str__(self): + def __str__(self) -> str: info_str = "NoiseModel Info:\n" for gate_errors, qudits in self.quantum_errors.items(): info_str += f"Qudits: {qudits}, Gates: {', '.join(gate_errors.keys())}, Error: {gate_errors}\n" diff --git a/src/mqt/qudits/simulation/save_info.py b/src/mqt/qudits/simulation/save_info.py index aa378c7..f978b0b 100644 --- a/src/mqt/qudits/simulation/save_info.py +++ b/src/mqt/qudits/simulation/save_info.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import getpass import os import uuid @@ -6,7 +8,7 @@ import numpy as np -def save_full_states(list_of_vectors, file_path=None, file_name=None): +def save_full_states(list_of_vectors, file_path=None, file_name=None) -> None: if file_name is None: file_name = "experiment_states.h5" @@ -35,7 +37,7 @@ def save_full_states(list_of_vectors, file_path=None, file_name=None): hdf_file.create_dataset("vectors", data=table_data) -def save_shots(shots, file_path=None, file_name=None): +def save_shots(shots, file_path=None, file_name=None) -> None: if file_name is None: file_name = "experiment_shots.h5" From 68e81e728050ecebb3968529015548ae7a5abfe7 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Fri, 19 Apr 2024 21:53:59 +0200 Subject: [PATCH 18/22] =?UTF-8?q?=F0=9F=9A=A8=20running=20pre-commit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MQT Tutorial/MQT Qudits Tutorial.ipynb | 599 +++---------------------- MQT Tutorial/MQT Qudits in Short.ipynb | 8 - src/python/bindings.cpp | 2 +- 3 files changed, 69 insertions(+), 540 deletions(-) diff --git a/MQT Tutorial/MQT Qudits Tutorial.ipynb b/MQT Tutorial/MQT Qudits Tutorial.ipynb index f7060ed..11a8aa6 100644 --- a/MQT Tutorial/MQT Qudits Tutorial.ipynb +++ b/MQT Tutorial/MQT Qudits Tutorial.ipynb @@ -63,7 +63,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "id": "c248f22a", "metadata": {}, "outputs": [], @@ -97,7 +97,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "id": "5274efd5", "metadata": {}, "outputs": [], @@ -134,20 +134,10 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "id": "356d82c2", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - " Number of operations: 4, \n", - " Number of qudits in the circuit: 9\n" - ] - } - ], + "outputs": [], "source": [ "circuit = QuantumCircuit()\n", "circuit.from_qasm(qasm)\n", @@ -157,21 +147,10 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": null, "id": "4d2b2cf2", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[5, 5, 5, 5, 5, 5, 5, 2, 2]" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "circuit.dimensions" ] @@ -187,20 +166,10 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": null, "id": "9a283781", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - " Number of operations: 0, \n", - " Number of qudits in the circuit: 2\n" - ] - } - ], + "outputs": [], "source": [ "from mqt.qudits.qudit_circuits.components.registers.quantum_register import QuantumRegister\n", "\n", @@ -217,34 +186,10 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": null, "id": "192d09b0", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "csum\n", - "cu_one\n", - "cu_two\n", - "cu_multi\n", - "cx\n", - "gellmann\n", - "h\n", - "ls\n", - "ms\n", - "pm\n", - "r\n", - "randu\n", - "rz\n", - "virtrz\n", - "s\n", - "x\n", - "z\n" - ] - } - ], + "outputs": [], "source": [ "circuit.gate_set" ] @@ -267,7 +212,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": null, "id": "0ed0c548", "metadata": {}, "outputs": [], @@ -277,7 +222,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": null, "id": "22bd7e9b", "metadata": {}, "outputs": [], @@ -287,20 +232,10 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": null, "id": "bc8489ec", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - " Number of operations: 2, \n", - " Number of qudits in the circuit: 2\n" - ] - } - ], + "outputs": [], "source": [ "print(f\"\\n Number of operations: {len(circuit.instructions)}, \\n Number of qudits in the circuit: {circuit.num_qudits}\")" ] @@ -317,26 +252,10 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": null, "id": "b79ad7c3", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "DITQASM 2.0;\n", - "qreg fields [1][7];\n", - "qreg matter [1][2];\n", - "creg meas[2];\n", - "h fields[0];\n", - "csum fields[0], matter[0];\n", - "measure fields[0] -> meas[0];\n", - "measure matter[0] -> meas[1];\n", - "\n" - ] - } - ], + "outputs": [], "source": [ "print(circuit.to_qasm())" ] @@ -351,21 +270,10 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": null, "id": "a6e17342", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'/home/k3vn/Desktop/my_circuit.qasm'" - ] - }, - "execution_count": 11, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "circuit.save_to_file(\"my_circuit\", \"/home/k3vn/Desktop\")" ] @@ -380,29 +288,10 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": null, "id": "c6f66e02", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Program:\n", - "\n", - " DITQASM 2.0;\n", - "qreg fields [1][7];\n", - "qreg matter [1][2];\n", - "creg meas[2];\n", - "h fields[0];\n", - "csum fields[0], matter[0];\n", - "measure fields[0] -> meas[0];\n", - "measure matter[0] -> meas[1];\n", - "\n", - "Dimensions: [7, 2]\n" - ] - } - ], + "outputs": [], "source": [ "circuit.load_from_file(\"/home/k3vn/Desktop/my_circuit.qasm\")\n", "\n", @@ -420,7 +309,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": null, "id": "cf9b9720", "metadata": {}, "outputs": [], @@ -447,7 +336,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": null, "id": "f9dcf9b4", "metadata": {}, "outputs": [], @@ -457,7 +346,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": null, "id": "43221ade", "metadata": {}, "outputs": [], @@ -469,7 +358,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": null, "id": "480417f5", "metadata": {}, "outputs": [], @@ -490,66 +379,20 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": null, "id": "20f638dc", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "R7_dag\n" - ] - } - ], + "outputs": [], "source": [ "print(r._name)" ] }, { "cell_type": "code", - "execution_count": 25, + "execution_count": null, "id": "effbb9a5", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "array([[ 0.95105652-0.j , 0.13407745+0.27841469j,\n", - " 0. -0.j , 0. -0.j ,\n", - " 0. -0.j , 0. -0.j ,\n", - " 0. -0.j ],\n", - " [-0.13407745+0.27841469j, 0.95105652-0.j ,\n", - " 0. -0.j , 0. -0.j ,\n", - " 0. -0.j , 0. -0.j ,\n", - " 0. -0.j ],\n", - " [ 0. -0.j , 0. -0.j ,\n", - " 1. -0.j , 0. -0.j ,\n", - " 0. -0.j , 0. -0.j ,\n", - " 0. -0.j ],\n", - " [ 0. -0.j , 0. -0.j ,\n", - " 0. -0.j , 1. -0.j ,\n", - " 0. -0.j , 0. -0.j ,\n", - " 0. -0.j ],\n", - " [ 0. -0.j , 0. -0.j ,\n", - " 0. -0.j , 0. -0.j ,\n", - " 1. -0.j , 0. -0.j ,\n", - " 0. -0.j ],\n", - " [ 0. -0.j , 0. -0.j ,\n", - " 0. -0.j , 0. -0.j ,\n", - " 0. -0.j , 1. -0.j ,\n", - " 0. -0.j ],\n", - " [ 0. -0.j , 0. -0.j ,\n", - " 0. -0.j , 0. -0.j ,\n", - " 0. -0.j , 0. -0.j ,\n", - " 1. -0.j ]])" - ] - }, - "execution_count": 25, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "r.to_matrix()" ] @@ -564,18 +407,10 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": null, "id": "720bb90b", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "R7_dag_dag\n" - ] - } - ], + "outputs": [], "source": [ "rd = r.dag()\n", "print(rd._name)" @@ -583,72 +418,20 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": null, "id": "f42f7ec4", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "array([[ 0.95105652-0.j , 0.13407745+0.27841469j,\n", - " 0. -0.j , 0. -0.j ,\n", - " 0. -0.j , 0. -0.j ,\n", - " 0. -0.j ],\n", - " [-0.13407745+0.27841469j, 0.95105652-0.j ,\n", - " 0. -0.j , 0. -0.j ,\n", - " 0. -0.j , 0. -0.j ,\n", - " 0. -0.j ],\n", - " [ 0. -0.j , 0. -0.j ,\n", - " 1. -0.j , 0. -0.j ,\n", - " 0. -0.j , 0. -0.j ,\n", - " 0. -0.j ],\n", - " [ 0. -0.j , 0. -0.j ,\n", - " 0. -0.j , 1. -0.j ,\n", - " 0. -0.j , 0. -0.j ,\n", - " 0. -0.j ],\n", - " [ 0. -0.j , 0. -0.j ,\n", - " 0. -0.j , 0. -0.j ,\n", - " 1. -0.j , 0. -0.j ,\n", - " 0. -0.j ],\n", - " [ 0. -0.j , 0. -0.j ,\n", - " 0. -0.j , 0. -0.j ,\n", - " 0. -0.j , 1. -0.j ,\n", - " 0. -0.j ],\n", - " [ 0. -0.j , 0. -0.j ,\n", - " 0. -0.j , 0. -0.j ,\n", - " 0. -0.j , 0. -0.j ,\n", - " 1. -0.j ]])" - ] - }, - "execution_count": 27, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "rd.to_matrix()" ] }, { "cell_type": "code", - "execution_count": 28, + "execution_count": null, "id": "8695a95e", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'target': 0,\n", - " 'dimensions_slice': 7,\n", - " 'params': [0, 1, 0.6283185307179586, 0.4487989505128276],\n", - " 'controls': ControlData(indices=[1], ctrl_states=[1])}" - ] - }, - "execution_count": 28, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "r_c1.control_info" ] @@ -665,21 +448,10 @@ }, { "cell_type": "code", - "execution_count": 29, + "execution_count": null, "id": "bc118b90", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[1, 0]" - ] - }, - "execution_count": 29, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "r_c1.reference_lines" ] @@ -717,20 +489,10 @@ }, { "cell_type": "code", - "execution_count": 30, + "execution_count": null, "id": "39904844", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - " Number of operations: 2, \n", - " Number of qudits in the circuit: 2\n" - ] - } - ], + "outputs": [], "source": [ "from mqt.qudits.qudit_circuits.components.registers.quantum_register import QuantumRegister\n", "\n", @@ -750,21 +512,10 @@ }, { "cell_type": "code", - "execution_count": 32, + "execution_count": null, "id": "13be385f", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "['tnsim', 'misim']" - ] - }, - "execution_count": 32, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "from mqt.qudits.simulation.provider.qudit_provider import MQTQuditProvider\n", "\n", @@ -774,21 +525,10 @@ }, { "cell_type": "code", - "execution_count": 33, + "execution_count": null, "id": "ab6d2c80", "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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PuOKvJrs+PHj5s477zQjR440c+fONQ6Hwzz00ENegc3lcplXX33VREZGmqioKNOuXTtz2WWX+dVB9Wx9ni3cFRcXm3vvvdc0aNDAb96fFe2xa9eupnv37iYoKMhs2LChmqu9cD/++KO57rrrzH333ec13qdPHzN+/HivscLCQvPwww/73bG2PD3+djbL5XKZ5557zu+OsxXt01+Ps1Wljq8/Crad+f83S+zWrZu+/fZb7dy5U23btpV06uqeu+66Szt37tRf//pXTZgwQdOmTZPkf3fQPl+fd955p3bu3Klly5Zp7NixCg0NVUxMjHbs2OG5sa0/KG+fy5cvV2pqquLi4nT11Vdr7ty56tSpk4+rLz+n06m4uDhdcsklSk5OVnh4uIYNGyZJeuihh9S0aVM5nU4NHz5cvXr10v79+3XixAl16tRJUVFRPq6+/M7V58MPP6zw8HCv9T///HN98sknysrKUocOHXxRcoWVt0eXy6WCggJ99913On78uLZs2eJXv7OlpaU6evSohgwZIunXY+h//Md/6KeffpJ06v1rjFGDBg00ffp0r/X8QXl6/O3NeU+fk+Zvx9mK9umvx9kq49NYWYvs2rXLhIeHmzvvvNMcO3bMGPPrXxz79+83DofDrFixwrO+P5znUZby9Lly5UpfllgpytPn3/72N+Nyuczx48d9WeoF+/e63377beNwOMyECRM8sz2lpaVm3759viiv0pyrz8OHDxtjTs187N+/3xhjzE8//VTtNV6s8vRYWlpqDh06ZFatWmW2bdvmizIv2m/vv3f6qvxJkyaZO+64w2u9396T0N+OteXtsbCwsFrrqmzl7fP08ddfj7NVgRm7atKqVSv93//9n/7whz+obt26mjJliucv5cDAQHXu3FmNGzf2rO+vX4lSnj5t+Hqm8vTZsGFDOZ1O1a9f38fVXpjTdbtcLjmdTiUnJ8sYo9tuu00Oh0MPPPCAZs2apX379um1115TvXr1/PL3trx97tmzR2+++abX+9RflLfHvXv36vXXX1e9evV8XPGFOT0r5Xa7FRgYKOnULF1+fr5nnYyMDAUHB+u+++5TnTp1/O539kJ69Efl7TMoKEgPPPCA3x5nq4J//o/7qb59+2rx4sW65ZZbdPDgQQ0dOlSdO3fWa6+9pvz8fEVHR/u6xEpBn6f6bN68ua9LrBQBAQEyxsjtdmvYsGFyOBy64447tGzZMu3evVsbNmyw4qB6vj7Xr1+vunXr+rrMi3KuHnft2qWNGzf6baj7LafT6fWdoac/ak1PT9cTTzyhLVu2+G3gOa029CiVr8+AgABflljjOIzxw9ts+7nNmzcrNTVVe/fuVZ06dRQQEKC3335bXbt29XVplYo+7erz9KHC4XDo2muv1datW7VmzRrrzmmpDX3Whh5Pn5c1ZcoUHTx4UG3atNGkSZO0bt06devWzdflVYra0KNUe/qsLP4f5/1Qt27dtGzZMv300086duyYLr300jNO0rYBfdrF4XDI5XLpoYce0urVq7V161argsBptaHP2tDj6ZmdwMBAzZ8/Xw0bNtTatWutCgK1oUep9vRZWZixA1BuLpdLCxcuVFxcnNf3NdqmNvRZG3qUpI0bN6pHjx7atm2b31zJXFG1oUep9vR5sQh2ACrkt+e72Kw29FkbepROfS2VDeeBnktt6FGqPX1eDIIdAACAJfzjrowAAAA4L4IdAACAJQh2AAAAliDYAQAAWIJgBwAAYAmCHQAAgCUIdgAAAJYg2AGo1Q4dOqTRo0erefPmCg4OVmRkpJKSkvTpp59KOvX1W0uXLq3w68bExGjOnDmVWywAnAffFQugVhs8eLBKSkr06quvqmXLlsrLy1NWVpZ+/PFHX5cGABXGjB2AWuvo0aP65JNPNH36dPXt21ctWrRQjx49lJaWphtvvFExMTGSpJtvvlkOh8PzePfu3brpppsUERGh0NBQde/eXR999JHndfv06aN9+/bpwQcflMPh8PrarrVr1+qaa65R3bp1FR0drfvuu09FRUWe51944QW1adNGISEhioiI0JAhQ6rlZwHADgQ7ALVWaGioQkNDtXTpUhUXF5/x/IYNGyRJr7zyig4ePOh5fPz4cV1//fXKysrSli1bNGDAAA0cOFD79++XJC1ZskSXX365pk6dqoMHD+rgwYOSTgXCAQMGaPDgwfriiy+0aNEirV27VuPGjZN06kvO77vvPk2dOlU7d+7UqlWr1KtXr+r4UQCwBN8VC6BW++tf/6pRo0bp559/Vrdu3dS7d28NGzZMnTt3lnTqHLt3331XgwYNOufrdOzYUffee68npMXExOiBBx7QAw884Fnn7rvvVkBAgF588UXP2Nq1a9W7d28VFRVp5cqVSklJ0ffff68GDRpUeq8A7MeMHYBabfDgwfrhhx+0bNkyDRgwQGvWrFG3bt20cOHCs25z/PhxTZgwQe3bt1ejRo0UGhqqHTt2eGbszubzzz/XwoULPTOFoaGhSkpKktvt1p49e9S/f3+1aNFCLVu21B133KE33nhDJ06cqOSOAdiMYAeg1gsJCVH//v31pz/9SevWrdPIkSM1efLks64/YcIEvfvuu3rqqaf0ySefaOvWrerUqZNKSkrOuZ/jx4/rnnvu0datWz3L559/rm+//VatWrVSgwYNtHnzZr311lu69NJLlZ6eri5duujo0aOV3DEAW3FVLAD8mw4dOnhucRIYGCiXy+X1/KeffqqRI0fq5ptvlnQqsO3du9drnaCgoDO269atm7766iu1bt36rPuuU6eOEhMTlZiYqMmTJ6tRo0b6xz/+of/8z/+8+MYAWI8ZOwC11o8//qh+/frp9ddf1xdffKE9e/Zo8eLFmjFjhm666SZJp86Vy8rKUm5uro4cOSJJatOmjZYsWeKZcbvtttvkdru9XjsmJkb//Oc/deDAAR0+fFiS9Mgjj2jdunUaN26ctm7dqm+//Vbvvfee57y8999/X88++6y2bt2qffv26bXXXpPb7Vbbtm2r8acCwJ8R7ADUWqGhoerZs6f+53/+R7169VLHjh31pz/9SaNGjdLzzz8vSXr66af14YcfKjo6Wl27dpUkzZ49W40bN9ZVV12lgQMHKikpSd26dfN67alTp2rv3r1q1aqVmjZtKknq3LmzPv74Y33zzTe65ppr1LVrV6Wnp+uyyy6TJDVq1EhLlixRv3791L59e2VmZuqtt97SFVdcUY0/FQD+jKtiAQAALMGMHQAAgCUIdgAAAJYg2AEAAFiCYAcAAGAJgh0AAIAlCHYAAACWINgBAABYgmAHAABgCYIdAACAJQh2AAAAliDYAQAAWIJgBwAAYIn/Bwl3VtRIyj9uAAAAAElFTkSuQmCC", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "from mqt.qudits.visualisation.plot_information import plot_counts, plot_state\n", "\n", @@ -804,21 +544,10 @@ }, { "cell_type": "code", - "execution_count": 34, + "execution_count": null, "id": "e18653e8", "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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PuOKvJrs+PHj5s477zQjR440c+fONQ6Hwzz00ENegc3lcplXX33VREZGmqioKNOuXTtz2WWX+dVB9Wx9ni3cFRcXm3vvvdc0aNDAb96fFe2xa9eupnv37iYoKMhs2LChmqu9cD/++KO57rrrzH333ec13qdPHzN+/HivscLCQvPwww/73bG2PD3+djbL5XKZ5557zu+OsxXt01+Ps1Wljq8/Crad+f83S+zWrZu+/fZb7dy5U23btpV06uqeu+66Szt37tRf//pXTZgwQdOmTZPkf3fQPl+fd955p3bu3Klly5Zp7NixCg0NVUxMjHbs2OG5sa0/KG+fy5cvV2pqquLi4nT11Vdr7ty56tSpk4+rLz+n06m4uDhdcsklSk5OVnh4uIYNGyZJeuihh9S0aVM5nU4NHz5cvXr10v79+3XixAl16tRJUVFRPq6+/M7V58MPP6zw8HCv9T///HN98sknysrKUocOHXxRcoWVt0eXy6WCggJ99913On78uLZs2eJXv7OlpaU6evSohgwZIunXY+h//Md/6KeffpJ06v1rjFGDBg00ffp0r/X8QXl6/O3NeU+fk+Zvx9mK9umvx9kq49NYWYvs2rXLhIeHmzvvvNMcO3bMGPPrXxz79+83DofDrFixwrO+P5znUZby9Lly5UpfllgpytPn3/72N+Nyuczx48d9WeoF+/e63377beNwOMyECRM8sz2lpaVm3759viiv0pyrz8OHDxtjTs187N+/3xhjzE8//VTtNV6s8vRYWlpqDh06ZFatWmW2bdvmizIv2m/vv3f6qvxJkyaZO+64w2u9396T0N+OteXtsbCwsFrrqmzl7fP08ddfj7NVgRm7atKqVSv93//9n/7whz+obt26mjJliucv5cDAQHXu3FmNGzf2rO+vX4lSnj5t+Hqm8vTZsGFDOZ1O1a9f38fVXpjTdbtcLjmdTiUnJ8sYo9tuu00Oh0MPPPCAZs2apX379um1115TvXr1/PL3trx97tmzR2+++abX+9RflLfHvXv36vXXX1e9evV8XPGFOT0r5Xa7FRgYKOnULF1+fr5nnYyMDAUHB+u+++5TnTp1/O539kJ69Efl7TMoKEgPPPCA3x5nq4J//o/7qb59+2rx4sW65ZZbdPDgQQ0dOlSdO3fWa6+9pvz8fEVHR/u6xEpBn6f6bN68ua9LrBQBAQEyxsjtdmvYsGFyOBy64447tGzZMu3evVsbNmyw4qB6vj7Xr1+vunXr+rrMi3KuHnft2qWNGzf6baj7LafT6fWdoac/ak1PT9cTTzyhLVu2+G3gOa029CiVr8+AgABflljjOIzxw9ts+7nNmzcrNTVVe/fuVZ06dRQQEKC3335bXbt29XVplYo+7erz9KHC4XDo2muv1datW7VmzRrrzmmpDX3Whh5Pn5c1ZcoUHTx4UG3atNGkSZO0bt06devWzdflVYra0KNUe/qsLP4f5/1Qt27dtGzZMv300086duyYLr300jNO0rYBfdrF4XDI5XLpoYce0urVq7V161argsBptaHP2tDj6ZmdwMBAzZ8/Xw0bNtTatWutCgK1oUep9vRZWZixA1BuLpdLCxcuVFxcnNf3NdqmNvRZG3qUpI0bN6pHjx7atm2b31zJXFG1oUep9vR5sQh2ACrkt+e72Kw29FkbepROfS2VDeeBnktt6FGqPX1eDIIdAACAJfzjrowAAAA4L4IdAACAJQh2AAAAliDYAQAAWIJgBwAAYAmCHQAAgCUIdgAAAJYg2AGo1Q4dOqTRo0erefPmCg4OVmRkpJKSkvTpp59KOvX1W0uXLq3w68bExGjOnDmVWywAnAffFQugVhs8eLBKSkr06quvqmXLlsrLy1NWVpZ+/PFHX5cGABXGjB2AWuvo0aP65JNPNH36dPXt21ctWrRQjx49lJaWphtvvFExMTGSpJtvvlkOh8PzePfu3brpppsUERGh0NBQde/eXR999JHndfv06aN9+/bpwQcflMPh8PrarrVr1+qaa65R3bp1FR0drfvuu09FRUWe51944QW1adNGISEhioiI0JAhQ6rlZwHADgQ7ALVWaGioQkNDtXTpUhUXF5/x/IYNGyRJr7zyig4ePOh5fPz4cV1//fXKysrSli1bNGDAAA0cOFD79++XJC1ZskSXX365pk6dqoMHD+rgwYOSTgXCAQMGaPDgwfriiy+0aNEirV27VuPGjZN06kvO77vvPk2dOlU7d+7UqlWr1KtXr+r4UQCwBN8VC6BW++tf/6pRo0bp559/Vrdu3dS7d28NGzZMnTt3lnTqHLt3331XgwYNOufrdOzYUffee68npMXExOiBBx7QAw884Fnn7rvvVkBAgF588UXP2Nq1a9W7d28VFRVp5cqVSklJ0ffff68GDRpUeq8A7MeMHYBabfDgwfrhhx+0bNkyDRgwQGvWrFG3bt20cOHCs25z/PhxTZgwQe3bt1ejRo0UGhqqHTt2eGbszubzzz/XwoULPTOFoaGhSkpKktvt1p49e9S/f3+1aNFCLVu21B133KE33nhDJ06cqOSOAdiMYAeg1gsJCVH//v31pz/9SevWrdPIkSM1efLks64/YcIEvfvuu3rqqaf0ySefaOvWrerUqZNKSkrOuZ/jx4/rnnvu0datWz3L559/rm+//VatWrVSgwYNtHnzZr311lu69NJLlZ6eri5duujo0aOV3DEAW3FVLAD8mw4dOnhucRIYGCiXy+X1/KeffqqRI0fq5ptvlnQqsO3du9drnaCgoDO269atm7766iu1bt36rPuuU6eOEhMTlZiYqMmTJ6tRo0b6xz/+of/8z/+8+MYAWI8ZOwC11o8//qh+/frp9ddf1xdffKE9e/Zo8eLFmjFjhm666SZJp86Vy8rKUm5uro4cOSJJatOmjZYsWeKZcbvtttvkdru9XjsmJkb//Oc/deDAAR0+fFiS9Mgjj2jdunUaN26ctm7dqm+//Vbvvfee57y8999/X88++6y2bt2qffv26bXXXpPb7Vbbtm2r8acCwJ8R7ADUWqGhoerZs6f+53/+R7169VLHjh31pz/9SaNGjdLzzz8vSXr66af14YcfKjo6Wl27dpUkzZ49W40bN9ZVV12lgQMHKikpSd26dfN67alTp2rv3r1q1aqVmjZtKknq3LmzPv74Y33zzTe65ppr1LVrV6Wnp+uyyy6TJDVq1EhLlixRv3791L59e2VmZuqtt97SFVdcUY0/FQD+jKtiAQAALMGMHQAAgCUIdgAAAJYg2AEAAFiCYAcAAGAJgh0AAIAlCHYAAACWINgBAABYgmAHAABgCYIdAACAJQh2AAAAliDYAQAAWIJgBwAAYIn/Bwl3VtRIyj9uAAAAAElFTkSuQmCC", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "backend = provider.get_backend(\"misim\")\n", "\n", @@ -853,18 +582,10 @@ }, { "cell_type": "code", - "execution_count": 35, + "execution_count": null, "id": "7ddd23dc", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "{'csum': {'all': Noise(probability_depolarizing=0.001, probability_dephasing=0.001), (0,): Noise(probability_depolarizing=0.001, probability_dephasing=0.001), 'control': Noise(probability_depolarizing=0.01, probability_dephasing=0.0), 'nonlocal': Noise(probability_depolarizing=0.1, probability_dephasing=0.1)}, 'cx': {'nonlocal': Noise(probability_depolarizing=0.1, probability_dephasing=0.001), 'target': Noise(probability_depolarizing=0.1, probability_dephasing=0.0), 'control': Noise(probability_depolarizing=0.01, probability_dephasing=0.0)}, 'ls': {'nonlocal': Noise(probability_depolarizing=0.1, probability_dephasing=0.001), 'target': Noise(probability_depolarizing=0.1, probability_dephasing=0.0), 'control': Noise(probability_depolarizing=0.01, probability_dephasing=0.0)}, 'ms': {'nonlocal': Noise(probability_depolarizing=0.1, probability_dephasing=0.001), 'target': Noise(probability_depolarizing=0.1, probability_dephasing=0.0), 'control': Noise(probability_depolarizing=0.01, probability_dephasing=0.0)}, 'h': {'local': Noise(probability_depolarizing=0.001, probability_dephasing=0.001)}, 'rxy': {'local': Noise(probability_depolarizing=0.001, probability_dephasing=0.001)}, 's': {'local': Noise(probability_depolarizing=0.001, probability_dephasing=0.001)}, 'x': {'local': Noise(probability_depolarizing=0.001, probability_dephasing=0.001)}, 'z': {'local': Noise(probability_depolarizing=0.001, probability_dephasing=0.001)}, 'rz': {'local': Noise(probability_depolarizing=0.03, probability_dephasing=0.03)}, 'virtrz': {'local': Noise(probability_depolarizing=0.03, probability_dephasing=0.03)}}\n" - ] - } - ], + "outputs": [], "source": [ "from mqt.qudits.simulation.provider.noise_tools.noise import Noise, NoiseModel\n", "\n", @@ -912,39 +633,10 @@ }, { "cell_type": "code", - "execution_count": 36, + "execution_count": null, "id": "505715d1", "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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rVixQm+88Yby8/NVt25dPfbYY5owYcJFb1xcWl577TUVFBRozpw5CggI0L333uu6393lNGrUSO+9954mTJigJ554QhERERoxYoRq1qyphx56yG3sxIkT9f3332v69Ok6efKkbr311osGO+ncjYcrV66sadOmafz48apSpYruuusuvfDCC1f1+wCgfOOzYgEAACzBOXYAAACWINgBAABYgmAHAABgCYIdAACAJQh2AAAAliDYAQAAWIL72OncZzUeOXJEVatWLfJH+gAAAJQFY4xOnjyp2rVry8fn8nNyBDtJR44cUWRkpKfLAAAAuKTDhw+rTp06lx1DsJNUtWpVSee+YUFBQR6uBgAA4H9ycnIUGRnpyiuXQ7CTXG+/BgUFEewAAEC5VJTTxbh4AgAAwBIEOwAAAEt4NNjNnj1bLVq0cL0FGhsbqw8//NC1vXPnznI4HG6P4cOHu+3j0KFD6t27typXrqywsDA9+eSTOnv2bFm3AgAA4HEePceuTp06mjZtmho2bChjjN566y3deeed2r59u5o2bSpJGjZsmKZOnep6TuXKlV1fO51O9e7dWxEREdq4caPS09M1aNAg+fn56fnnny/zfgAAADzJYYwxni7i10JDQ/Xiiy9q6NCh6ty5s1q1aqWZM2dedOyHH36oP/7xjzpy5IjCw8MlSXPmzNH48eN19OhR+fv7F+k1c3JyFBwcrOzsbC6eAAAA5Upxckq5OcfO6XRqwYIFys3NVWxsrGv922+/rRo1aqhZs2ZKTEzU6dOnXdtSU1PVvHlzV6iTpPj4eOXk5GjPnj1lWj8AAICnefx2J7t27VJsbKzOnDmjwMBAvf/++4qJiZEk3XfffYqKilLt2rW1c+dOjR8/Xvv27dOiRYskSRkZGW6hTpJrOSMj45KvmZeXp7y8PNdyTk5OabcFAABQ5jwe7Bo1aqQdO3YoOztb7733ngYPHqz169crJiZGjzzyiGtc8+bNVatWLXXr1k1paWmqX79+iV8zKSlJU6ZMKY3yAQAAyg2PvxXr7++vBg0aqG3btkpKSlLLli31yiuvXHRshw4dJEn79++XJEVERCgzM9NtzPnliIiIS75mYmKisrOzXY/Dhw+XRisAAAAe5fFg91uFhYVub5P+2o4dOyRJtWrVkiTFxsZq165dysrKco1ZvXq1goKCXG/nXkxAQIDrFit8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AIAlCHZegAuX7VFYWOjpEgD8CsdXO3Bs/R9ud1IOpaen6/Dhw/r5558VFxcnX19fT5dU6pxOp3x9fVVYWCgfH3v/vjh27JiOHj2qEydOqGPHjpJkfc/nGWO88rOXYTeOr3a4lo+tv4dgV87s3LlTd9xxhwICApSZmalatWpp4sSJio+PV2hoqKfLKxW7d+/WqFGjNH/+fEVGRlr7y7hr1y49/PDDys7O1s8//6zWrVtr5cqVkuwKPd98843efPNNZWVlqVWrVurVq5caNmwoyZ4+s7Ky5O/vb8VnSl/KgQMHtHjxYv3www+68cYb1b9/f0+XVOo4vtrhWjm2lphBuZGVlWUaN25snn76aZOWlmZ+/PFH079/f9OkSRMzadIkk5WV5ekSr9iBAwdMgwYNjMPhMA0bNjSHDx82xhjjdDo9XFnp+vrrr02NGjXMU089ZVJTU82qVatMvXr1TGJioqdLK1V79uwxwcHBpkePHqZv374mODjYxMXFmX/+85+uMYWFhR6s8Mrt3bvX+Pv7m379+pns7GxPl3NV7Ny509SpU8d069bN3HTTTcbHx8dMnz7d02WVKo6vdrhWjq1XgmBXjuzZs8dER0ebLVu2uK0fP368ad68uZk+fbrJzc31UHVX7pdffjETJkwwd911l0lJSTGdOnUyUVFR1h18Tp48ae69917z2GOPudY5nU4zatQoc8cdd3iwstKVl5dnHnjgATNs2DDXum+//db079/fdOzY0bzyyiserK50ZGRkmJtuusl07drV1KhRw9xzzz3WhbuDBw+aBg0amHHjxrl+B998800THh5uvvnmGw9XV3o4vnr/8fVaObZeKbvmZ71cfn6+CgoKdPr0aUnSL7/8IkmaNm2aunTpotmzZ2v//v2SvPOE34oVKyomJkb9+/dX165dNX/+fNWtW1c333yzfvjhB/n4+FhzAmyVKlXUsmVL17KPj49uvvlmHThwwPXv7O38/f2VmZnpetvDGKMGDRpo+vTpaty4sd577z0tXbrUw1Veme3btys6OlovvPCCli9frpSUFD388MPKycnxdGmlorCwUAsWLFCDBg309NNPu96ya9++vfz8/Kz5fZSkgoICnT171urja7NmzTRgwACrj69Vq1ZVq1atXMs2HluvmIeD5TXP6XSas2fPupZvvvlm06lTJ9fymTNnXF+3a9fO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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/plain": [ - "{'00': 323,\n", - " '01': 5,\n", - " '02': 0,\n", - " '10': 0,\n", - " '11': 322,\n", - " '12': 6,\n", - " '20': 2,\n", - " '21': 0,\n", - " '22': 342}" - ] - }, - "execution_count": 36, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "backend = provider.get_backend(\"tnsim\")\n", "\n", @@ -967,21 +659,10 @@ }, { "cell_type": "code", - "execution_count": 37, + "execution_count": null, "id": "f8a44ca3", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "['faketraps2trits', 'faketraps2six']" - ] - }, - "execution_count": 37, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "provider = MQTQuditProvider()\n", "provider.backends(\"fake\")" @@ -989,7 +670,7 @@ }, { "cell_type": "code", - "execution_count": 38, + "execution_count": null, "id": "3f8d02e3", "metadata": {}, "outputs": [], @@ -999,21 +680,10 @@ }, { "cell_type": "code", - "execution_count": 39, + "execution_count": null, "id": "792435eb", "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", "import networkx as nx\n", @@ -1027,39 +697,10 @@ }, { "cell_type": "code", - "execution_count": 40, + "execution_count": null, "id": "0d712293", "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAnYAAAHWCAYAAAD6oMSKAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8g+/7EAAAACXBIWXMAAA9hAAAPYQGoP6dpAAA7nElEQVR4nO3de1RVdf7/8dcBAS8IiApoInhLxUuamtKUeSHxMpalpVN5KbM0tK86peGYt2mizCmrMZ1mNVprIvvamHlLU1LLxFLLvBUlaVpyMU1QTMDD5/eHP8+3k5cAkcP58HysddZi7/05+7zfCNsXn7P3Pg5jjBEAAAC8no+nCwAAAEDZINgBAABYgmAHAABgCYIdAACAJQh2AAAAliDYAQAAWIJgBwAAYAmCHQAAgCUIdgAAAJYg2AGwTnR0tEaMGOGR154xY4YcDkeZ7nPjxo1yOBzauHFjme4XgH0IdgC8yu7duzVo0CBFRUWpatWquuaaa3Trrbfq5Zdf9nRpV+yVV17RokWLPF0GAC/m4LNiAXiLLVu2qHv37mrYsKGGDx+uiIgIHT58WFu3blV6err2798vScrPz5ePj4/8/PzKvcYZM2Zo5syZKs2htXXr1qpTp84FM3NFRUUqKCiQv7+/fHz4exzApVXxdAEAUFx/+9vfFBwcrG3btikkJMRtW3Z2tuvrgICAcq7s6vLx8VHVqlU9XQYAL8CffgC8Rnp6ulq1anVBqJOksLAw19e/Pcdu0aJFcjgc2rx5sx599FHVrVtXISEhevjhh1VQUKATJ05o2LBhqlWrlmrVqqVJkya5zbhd6hy3gwcPyuFw/O7bpwsXLlSPHj0UFhamgIAAxcTEaP78+W5joqOjtXfvXm3atEkOh0MOh0PdunW77OsvWbJEHTp0ULVq1VSnTh3dd999+vHHH93GjBgxQoGBgfrxxx81YMAABQYGqm7dunrsscfkdDovWzcA78OMHQCvERUVpdTUVO3Zs0etW7cu8fPHjRuniIgIzZw5U1u3btWrr76qkJAQbdmyRQ0bNtTTTz+t1atX67nnnlPr1q01bNiwMql7/vz5atWqlW677TZVqVJFK1as0COPPKKioiIlJCRIkubOnatx48YpMDBQf/nLXyRJ4eHhl9znokWLdP/996tTp05KSkpSVlaWXnzxRX3yySf64osv3MKv0+lUfHy8OnfurDlz5mj9+vX6+9//riZNmmjMmDFl0iOACsIAgJf44IMPjK+vr/H19TWxsbFm0qRJZu3ataagoMBtXFRUlBk+fLhreeHChUaSiY+PN0VFRa71sbGxxuFwmNGjR7vWnT171jRo0MDccsstrnUbNmwwksyGDRvcXufAgQNGklm4cKFr3fTp081vD62nT5++oJf4+HjTuHFjt3WtWrVye91LvX5BQYEJCwszrVu3Nr/88otr3MqVK40kM23aNNe64cOHG0lm1qxZbvts37696dChwwWvBcC78VYsAK9x6623KjU1Vbfddpu+/PJLzZ49W/Hx8brmmmu0fPny333+yJEj3W5F0rlzZxljNHLkSNc6X19fdezYUd99912Z1V2tWjXX1zk5Ofrpp590yy236LvvvlNOTk6J97d9+3ZlZ2frkUcecTv3rl+/fmrRooVWrVp1wXNGjx7ttnzzzTeXaY8AKgaCHQCv0qlTJy1dulQ///yzPvvsMyUmJurkyZMaNGiQ9u3bd9nnNmzY0G05ODhYkhQZGXnB+p9//rnMav7kk08UFxenGjVqKCQkRHXr1tWUKVMkqVTB7vvvv5ckNW/e/IJtLVq0cG0/r2rVqqpbt67bulq1apVpjwAqBoIdAK/k7++vTp066emnn9b8+fNVWFioJUuWXPY5vr6+xV5vfnXxxKVuOFyciw/S09PVs2dP/fTTT3r++ee1atUqrVu3ThMmTJB07lYmV9ul+gZgHy6eAOD1OnbsKEnKyMi4KvuvVauWJOnEiRNu6387M3YxK1asUH5+vpYvX+42Y7hhw4YLxhb3EyuioqIkSWlpaerRo4fbtrS0NNd2AJUPM3YAvMaGDRsueuPf1atXS7r4W5NlISoqSr6+vvroo4/c1r/yyiu/+9zzs2W/rjsnJ0cLFy68YGyNGjUuCI8X07FjR4WFhWnBggXKz893rX///ff11VdfqV+/fr+7DwB2YsYOgNcYN26cTp8+rTvuuEMtWrRQQUGBtmzZorffflvR0dG6//77r8rrBgcH66677tLLL78sh8OhJk2aaOXKlW43Rb6UXr16yd/fX/3799fDDz+sU6dO6V//+pfCwsIumGHs0KGD5s+fr6eeekpNmzZVWFjYBTNykuTn56dnn31W999/v2655Rb96U9/ct3uJDo62vU2L4DKh2AHwGvMmTNHS5Ys0erVq/Xqq6+qoKBADRs21COPPKKpU6de9MbFZeXll19WYWGhFixYoICAAN19992u+91dTvPmzfXOO+9o6tSpeuyxxxQREaExY8aobt26euCBB9zGTps2Td9//71mz56tkydP6pZbbrlosJPO3Xi4evXqeuaZZzR58mTVqFFDd9xxh5599tmr+n0AULHxWbEAAACW4Bw7AAAASxDsAAAALEGwAwAAsATBDgAAwBIEOwAAAEsQ7AAAACzBfex07rMajxw5opo1axb7I30AAADKgzFGJ0+eVP369eXjc/k5OYKdpCNHjigyMtLTZQAAAFzS4cOH1aBBg8uOIdhJqlmzpqRz37CgoCAPVwMAAPB/cnNzFRkZ6corl0Owk1xvvwYFBRHsAABAhVSc08W4eAIAAMASHg128+fPV9u2bV0zZbGxsXr//fdd27t16yaHw+H2GD16tNs+Dh06pH79+ql69eoKCwvT448/rrNnz5Z3KwAAAB7n0bdiGzRooGeeeUbNmjWTMUavv/66br/9dn3xxRdq1aqVJGnUqFGaNWuW6znVq1d3fe10OtWvXz9FRERoy5YtysjI0LBhw+Tn56enn3663PsBAADwJIcxxni6iF8LDQ3Vc889p5EjR6pbt25q166d5s6de9Gx77//vv74xz/qyJEjCg8PlyQtWLBAkydP1tGjR+Xv71+s18zNzVVwcLBycnI4xw4AAFQoJckpFeYcO6fTqcWLFysvL0+xsbGu9W+++abq1Kmj1q1bKzExUadPn3ZtS01NVZs2bVyhTpLi4+OVm5urvXv3lmv9AAAAnubxq2J3796t2NhYnTlzRoGBgXr33XcVExMjSbrnnnsUFRWl+vXra9euXZo8ebLS0tK0dOlSSVJmZqZbqJPkWs7MzLzka+bn5ys/P9+1nJubW9ZtAQAAlDuPB7vmzZtr586dysnJ0TvvvKPhw4dr06ZNiomJ0UMPPeQa16ZNG9WrV089e/ZUenq6mjRpUurXTEpK0syZM8uifAAAgArD42/F+vv7q2nTpurQoYOSkpJ03XXX6cUXX7zo2M6dO0uS9u/fL0mKiIhQVlaW25jzyxEREZd8zcTEROXk5Lgehw8fLotWAAAAPMrjwe63ioqK3N4m/bWdO3dKkurVqydJio2N1e7du5Wdne0as27dOgUFBbnezr2YgIAA1y1WuCkxAACw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EOy8ABcu26OoqMjTJQD4FY6vduDY+n+43UkFlJGRocOHD+vnn39WXFycfH19PV1SmXM6nfL19VVRUZF8fOz9++LYsWM6evSoTpw4oS5dukiS9T2fZ4zxys9eht04vtqhMh9bfw/BroLZtWuXbrvtNgUEBCgrK0v16tXTtGnTFB8fr9DQUE+XVyb27NmjcePG6Y033lBkZKS1v4y7d+/Wgw8+qJycHP38889q37691qxZI8mu0PPNN9/otddeU3Z2ttq1a6e+ffuqWbNmkuzpMzs7W/7+/lZ8pvSlHDhwQMuWLdMPP/ygG264QYMHD/Z0SWWO46sdKsuxtdQMKozs7GzTokULM2XKFJOenm5+/PFHM3jwYNOyZUszffp0k52d7ekSr9iBAwdM06ZNjcPhMM2aNTOHDx82xhjjdDo9XFnZ+vrrr02dOnXME088YVJTU83atWtN48aNTWJioqdLK1N79+41wcHBpnfv3mbgwIEmODjYxMXFmX/961+uMUVFRR6s8Mrt27fP+Pv7m0GDBpmcnBxPl3NV7Nq1yzRo0MD07NnT3HjjjcbHx8fMnj3b02WVKY6vdqgsx9YrQbCrQPbu3Wuio6PN9u3b3dZPnjzZtGnTxsyePdvk5eV5qLor98svv5ipU6eaO+64w6SkpJiuXbuaqKgo6w4+J0+eNHfffbd55JFHXOucTqcZN26cue222zxYWdnKz8839913nxk1apRr3bfffmsGDx5sunTpYl588UUPVlc2MjMzzY033mh69Ohh6tSpY+666y7rwt3BgwdN06ZNzaRJk1y/g6+99poJDw8333zzjYerKzscX73/+FpZjq1Xyq75WS9XUFCgwsJCnT59WpL0yy+/SJKeeeYZde/eXfPnz9f+/fsleecJv1WrVlVMTIwGDx6sHj166I033lDDhg1100036YcffpCPj481J8DWqFFD1113nWvZx8dHN910kw4cOOD6d/Z2/v7+ysrKcr3tYYxR06ZNNXv2bLVo0ULvvPOOVqxY4eEqr8wXX3yh6OhoPfvss1q1apVSUlL04IMPKjc319OllYmioiItXrxYTZs21ZQpU1xv2XXq1El+fn7W/D5KUmFhoc6ePWv18bV169YaMmSI1cfXmjVrql27dq5lG4+tV8zDwbLSczqd5uzZs67lm266yXTt2tW1fObMGdfXHTt2NEOG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c21/5ZVX1KxZM1WtWlXh4eEaNGhQuXwvAHg3gh2ASi8wMFCBgYFatmyZ8vPzL9i+bds2SdLChQuVkZHhWj516pT69u2rlJQUffHFF+rdu7f69++vQ4cOSZKWLl2qBg0aaNasWcrIyFBGRoakc4Gwd+/eGjhwoHbt2qW3335bmzdv1tixYyWd+7DzRx99VLNmzVJaWprWrFmjrl27lse3AoCX47NiAUDSf//7X40aNUq//PKLrr/+et1yyy0aMmSI2rZtK+ncOXbvvvuuBgwYcNn9tG7dWqNHj3aFtOjoaI0fP17jx493jXnwwQfl6+urf/7zn651mzdv1i233KK8vDytXr1a999/v3744QfVrFmzzHsFYC9m7ABA586xO3LkiJYvX67evXtr48aNuv7667Vo0aJLPufUqVN67LHH1LJlS4WEhCgwMFBfffWVa8buUr788kstWrTINVMYGBio+Ph4FRUV6cCBA7r11lsVFRWlxo0ba+jQoXrzzTd1+vTpMu4YgI0IdgDw/1WtWlW33nqrnnzySW3ZskUjRozQ9OnTLzn+scce07vvvqunn35aH3/8sXbu3Kk2bdqooKDgsq9z6tQpPfzww9q5c6fr8eWXX+rbb79VkyZNVLNmTX3++ed66623VK9ePU2bNk3XXXedTpw4UcYdA7ANV8UCwCXExMS4bnHi5+cnp9Pptv2TTz7RiBEjdMcdd0g6F9gOHjzoNsbf3/+C511//fXat2+fmjZtesnXrlKliuLi4hQXF6fp06crJCREH374oe68884rbwyAtZixA1DpHTt2TD169NB//vMf7dq1SwcOHNCSJUs0e/Zs3X777ZLOnSuXkpKizMxM/fzzz5KkZs2aaenSpa4Zt3vuuUdFRUVu+46OjtZHH32kH3/8UT/99JMkafLkydqyZYvGjh2rnTt36ttvv9V7773nOi9v5cqVeumll7Rz5059//33euONN1RUVKTmzZuX43cFgDci2AGo9AIDA9W5c2e98MIL6tq1q1q3bq0nn3xSo0aN0j/+8Q9J0t///netW7dOkZGRat++vSTp+eefV61atXTjjTeqf//+io+P1/XXX++271mzZungwYNq0qSJ6tatK0lq27atNm3apG+++UY333yz2rdvr2nTpql+/fqSpJCQEC1dulQ9evRQy5YttWDBAr311ltq1apVOX5XAHgjrooFAACwBDN2AAAAliDYAQAAWIJgBwAAYAmCHQAAgCUIdgAAAJYg2AEAAFiCYAcAAGAJgh0AAIAlCHYAAACWINgBAABYgmAHAABgCYIdAACAJf4fKhy2DzdtmjEAAAAASUVORK5CYII=", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/plain": [ - "{'00': 341,\n", - " '01': 2,\n", - " '02': 0,\n", - " '10': 0,\n", - " '11': 337,\n", - " '12': 3,\n", - " '20': 5,\n", - " '21': 0,\n", - " '22': 312}" - ] - }, - "execution_count": 40, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "job = backend_ion.run(circuit)\n", "result = job.result()\n", @@ -1092,7 +733,7 @@ }, { "cell_type": "code", - "execution_count": 41, + "execution_count": null, "id": "8e187e94", "metadata": {}, "outputs": [], @@ -1102,7 +743,7 @@ }, { "cell_type": "code", - "execution_count": 42, + "execution_count": null, "id": "58daadf2", "metadata": {}, "outputs": [], @@ -1114,20 +755,10 @@ }, { "cell_type": "code", - "execution_count": 43, + "execution_count": null, "id": "d21fe400", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - " Number of operations: 10, \n", - " Number of qudits in the circuit: 2\n" - ] - } - ], + "outputs": [], "source": [ "compiled_circuit_qr = qudit_compiler.compile(backend_ion, circuit, passes)\n", "\n", @@ -1138,39 +769,10 @@ }, { "cell_type": "code", - "execution_count": 44, + "execution_count": null, "id": "85b295ef", "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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AABgCYIdAACAJQh2AAAAliDYAQAAWIJg5wO4cNkeJSUl3i4BwK9wfLUDx9b/4XYnldDBgwe1f/9+/fTTT0pISJC/v7+3S6pwLpdL/v7+KikpkZ+fvf9fHD58WIcOHdLRo0fVtWtXSbK+59OMMT752cuwG8dXO1TlY+vvIdhVMtu3b9eNN96ooKAg5ebmqkGDBpoyZYoSExMVHh7u7fIqxM6dOzVu3Di9+uqrio6OtvaPcceOHbr33nuVn5+vn376SR06dNCKFSsk2RV6vv76a7388svKy8tT+/bt1a9fPzVv3lySPX3m5eUpMDDQis+UPpfs7Gy9++67+v7773X11Vdr8ODB3i6pwnF8tUNVObaWm0GlkZeXZ1q2bGkeffRRk5WVZX744QczePBg06pVK/P444+bvLw8b5d4wbKzs02zZs2Mw+EwzZs3N/v37zfGGONyubxcWcX66quvTL169cwjjzxiMjIyzMqVK02TJk1MSkqKt0urULt27TKhoaGmT58+ZtCgQSY0NNQkJCSYf/3rX+4xJSUlXqzwwu3evdsEBgaaW2+91eTn53u7nIti+/btplGjRqZXr17mmmuuMX5+fmbmzJneLqtCcXy1Q1U5tl4Igl0lsmvXLhMbG2s2b97ssX7SpEmmbdu2ZubMmaagoMBL1V24X375xUyePNncfPPNJj093XTr1s3ExMRYd/A5duyYuf32280DDzzgXudyucy4cePMjTfe6MXKKlZhYaG56667zKhRo9zrvvnmGzN48GDTtWtX89xzz3mxuoqRk5NjrrnmGtOzZ09Tr149c9ttt1kX7vbu3WuaNWtmJk6c6P4bfPnll01kZKT5+uuvvVxdxeH46vvH16pybL1Qds3P+riioiIVFxfr559/liT98ssvkqQZM2aoR48emjt3rvbs2SPJN0/4rV69uuLi4jR48GD17NlTr776qho3bqxrr71W33//vfz8/Kw5AbZWrVq68sor3ct+fn669tprlZ2d7f45+7rAwEDl5ua63/YwxqhZs2aaOXOmWrZsqbfffltLlizxcpUX5vPPP1dsbKyeeuopLVu2TOnp6br33nvldDq9XVqFKCkp0ZtvvqlmzZrp0Ucfdb9l17lzZwUEBFjz9yhJxcXFOnnypNXH1zZt2mjIkCFWH19r166t9u3bu5dtPLZeMC8HyyrP5XKZkydPupevvfZa061bN/fyiRMn3F936tTJDBky5JLWVxFcLpcpKio6Y31JSYnJyspy/2f5/fffG2NO9bx161af++/Z5XKZ4uJiY8ypWZDTTr8V+dZbb5m2bdt6PMfXejzt5MmTpqioyNx9993m1ltvNSdOnDAlJSXuWYGsrCwTHx9vBg8e7OVKL0xeXp5Zs2aNezkjI8OEh4eb2267zRw9etS93pffbl63bp155JFHPNa5XC4TGxvr0bsNOnfubHr06OFetuH4ei62HV+NOfV7afuxtSIwY+dFu3fv1rBhw5SYmKhRo0Zp3bp1eu655/TDDz/o9ttvlyQFBQXp5MmTkqRu3bqpoKDAmyWX2eke+/btq9GjR2vZsmUe25s0aaJ///vfiomJ0R/+8AdlZ2frz3/+s+677z4VFRV5qeqyO91nnz59lJSUpJ07d7q3uVwuSTrjP+Y///nPGjx4sHu7Lzhdq7+/vwICAjR8+HC98847+uc//ymHwyE/Pz+5XC41adJEqampWrhwoXbt2uXlqsvm1z+P+vXrq3v37pJOzW517dpVy5cvV3p6ukaNGiWn06ni4mLNmzdPq1at8lLFZffrHrt166bU1FRJnjNVDofDY/YjPT1dhw4dunRFXqCCggIdO3bMY3b1n//8p3bt2qU77rhDku8fX8/Wo3Tqd9XhcFhxfP11j35+foqJiZH0vx4lO46tFcrbybKq+uqrr0xoaKgZMmSIeeSRR8yVV15pOnfubMaMGWPS0tJMkyZNzMCBA01RUZF7FuSuu+4yQ4YMMcXFxT4xQ3C2Hjt16mTGjx/vHnO6j6ysLNO9e3fjcDhMrVq1zGeffeatssusNH0aY8yyZctMixYtjDHGpKSkmBo1apiMjAxvlFwumZmZZtasWebAgQMe62fNmmX8/Pw8LpgwxpgtW7aYVq1amezs7EtY5YU5V4+/9emnn5rw8HBz++23m7vvvtsEBASYPXv2XKIqL8zZevz18aS4uNgcP37cNGvWzGzcuNEYc+r31eFwmB9++OGS11seu3btMr179zYdOnQwDRs2NK+99pox5tR5aG+88YapV6+eufXWW336+HquHs9Wu68eX0vbo68fWysawc4LSkpKzKOPPmpuv/129zqn02mmT59urr76anPHHXeYd99911xxxRXmiiuuMAMHDjS33367qVWrltmxY4cXKy+9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X79e1113nWrUqKHo6Gg9+OCDKigocG9/8cUX1bx5c1WvXl2RkZG69dZbL8n3AoBvI9gBqPKCg4MVHBysd999V4WFhWds37RpkyRp/vz5OnjwoHv5+PHj6tevn9LT0/X555+rT58+GjBggPbt2ydJWrRokRo1aqTp06fr4MGDOnjwoKRTgbBPnz4aNGiQtm/frrfeekvr16/X2LFjJZ36sPMHH3xQ06dPV2ZmplasWKFu3bpdim8FAB/HZ8UCgKT//ve/GjVqlH755RddddVVuv766zVkyBC1a9dO0qlz7N555x0NHDjwvPtp06aNRo8e7Q5psbGxGj9+vMaPH+8ec++998rf31///Oc/3evWr1+v66+/XgUFBVq+fLnuvvtuff/996pdu3aF9wrAXszYAYBOnWN34MABLV68WH369NHatWt11VVXacGCBed8zvHjx/XQQw+pVatWCgsLU3BwsL788kv3jN25fPHFF1qwYIF7pjA4OFiJiYkqKSlRdna2brjhBsXExKhJkyYaOnSoXn/9df38888V3DEAGxHsAOD/q169um644QY99thj2rBhg0aMGKHHH3/8nOMfeughvfPOO3ryySf18ccfa9u2bWrbtq2KiorO+zrHjx/X/fffr23btrkfX3zxhb755hs1bdpUtWvX1tatW/XGG2+oQYMGmjJliq688kodPXq0gjsGYBuuigWAc4iLi3Pf4iQgIEAul8tj+yeffKIRI0bo5ptvlnQqsO3du9djTGBg4BnPu+qqq7R79241a9bsnK9drVo1JSQkKCEhQY8//rjCwsL04Ycf6pZbbrnwxgBYixk7AFXe4cOH1bNnT7322mvavn27srOztXDhQs2cOVM33XSTpFPnyqWnpysnJ0c//fSTJKl58+ZatGiRe8btjjvuUElJice+Y2Nj9dFHH+mHH37Qjz/+KEmaNGmSNmzYoLFjx2rbtm365ptv9N5777nPy1u6dKmef/55bdu2Td99951effVVlZSUqEWLFpfwuwLAFxHsAFR5wcHB6tKli5599ll169ZNbdq00WOPPaZRo0bpH//4hyTp73//u1atWqXo6Gh16NBBkvTMM8+oTp06uuaaazRgwAAlJibqqquu8tj39OnTtXfvXjVt2lT169eXJLVr107r1q3T119/reuuu04dOnTQlClT1LBhQ0lSWFiYFi1apJ49e6pVq1aaN2+e3njjDbVu3foSflcA+CKuigUAALAEM3YAAACWINgBAABYgmAHAABgCYIdAACAJQh2AAAAliDYAQAAWIJgBwAAYAmCHQAAgCUIdgAAAJYg2AEAAFiCYAcAAGAJgh0AAIAl/h+zp7LssIQ6GwAAAABJRU5ErkJggg==", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/plain": [ - "{'00': 330,\n", - " '01': 3,\n", - " '02': 0,\n", - " '10': 0,\n", - " '11': 351,\n", - " '12': 2,\n", - " '20': 3,\n", - " '21': 0,\n", - " '22': 311}" - ] - }, - "execution_count": 44, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "job = backend_ion.run(compiled_circuit_qr)\n", "\n", @@ -1182,20 +784,10 @@ }, { "cell_type": "code", - "execution_count": 45, + "execution_count": null, "id": "a1b22b44", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - " Number of operations: 5, \n", - " Number of qudits in the circuit: 2\n" - ] - } - ], + "outputs": [], "source": [ "passes = [\"PhyLocAdaPass\", \"ZPropagationPass\", \"ZRemovalPass\"]\n", "\n", @@ -1208,39 +800,10 @@ }, { "cell_type": "code", - "execution_count": 46, + "execution_count": null, "id": "5a807930", "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAnYAAAHWCAYAAAD6oMSKAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8g+/7EAAAACXBIWXMAAA9hAAAPYQGoP6dpAAA4zUlEQVR4nO3deViVdf7/8dcBAVdAVEATwS33faUxcyFx+VqWpk7lllkZ2lcd02jMrSbSnDbHdJqrkfxOjo0z5ZZjKS5lYuOSuZWpYa6AaYJYAh4+vz/8eaaTS4DI4Xx4Pq7rXBf3fX/Ofd5vlvt68Tn3fR+HMcYIAAAAXs/H0wUAAACgaBDsAAAALEGwAwAAsATBDgAAwBIEOwAAAEsQ7AAAACxBsAMAALAEwQ4AAMASBDsAAABLEOwAWCcqKkrDhw/3yGtPnz5dDoejSPe5ceNGORwObdy4sUj3C8A+BDsAXmXPnj0aMGCAIiMjVbZsWd122226++67NXfuXE+XdtPefPNNJSYmeroMAF7MwWfFAvAWW7ZsUdeuXVWrVi0NGzZM4eHhOnbsmLZu3arDhw/r0KFDkqTs7Gz5+PjIz8+v2GucPn26ZsyYocIcWps2baqqVateNTOXl5ennJwc+fv7y8eH/8cBXF8ZTxcAAPn1hz/8QUFBQdq2bZuCg4PdtqWnp7u+DggIKObKbi0fHx+VLVvW02UA8AL86wfAaxw+fFhNmjS5KtRJUmhoqOvrX55jl5iYKIfDoc2bN+upp55StWrVFBwcrMcff1w5OTk6d+6chg4dqsqVK6ty5cqaNGmS24zb9c5xO3LkiBwOx6++fbpw4UJ169ZNoaGhCggIUOPGjTV//ny3MVFRUdq3b582bdokh8Mhh8OhLl263PD1ly5dqjZt2qhcuXKqWrWqHn74YZ04ccJtzPDhw1WxYkWdOHFC/fr1U8WKFVWtWjVNnDhRTqfzhnUD8D7M2AHwGpGRkUpOTtbevXvVtGnTAj9/7NixCg8P14wZM7R161a99dZbCg4O1pYtW1SrVi29+OKLWr16tV5++WU1bdpUQ4cOLZK658+fryZNmuiee+5RmTJltHLlSj355JPKy8tTXFycJOm1117T2LFjVbFiRf3+97+XJIWFhV13n4mJiRoxYoTatWunhIQEpaWl6fXXX9dnn32mL774wi38Op1OxcbGqkOHDpozZ47WrVunP/7xj6pbt65Gjx5dJD0CKCEMAHiJjz/+2Pj6+hpfX18THR1tJk2aZD766COTk5PjNi4yMtIMGzbMtbxw4UIjycTGxpq8vDzX+ujoaONwOMwTTzzhWnfp0iVTs2ZNc9ddd7nWbdiwwUgyGzZscHudlJQUI8ksXLjQtW7atGnml4fWH3/88apeYmNjTZ06ddzWNWnSxO11r/f6OTk5JjQ01DRt2tT89NNPrnGrVq0ykszUqVNd64YNG2YkmZkzZ7rts1WrVqZNmzZXvRYA78ZbsQC8xt13363k5GTdc889+vLLLzV79mzFxsbqtttu04oVK371+SNHjnS7FUmHDh1kjNHIkSNd63x9fdW2bVt9++23RVZ3uXLlXF9nZGTo+++/11133aVvv/1WGRkZBd7f9u3blZ6erieffNLt3Ls+ffqoYcOG+vDDD696zhNPPOG2fOeddxZpjwBKBoIdAK/Srl07vf/++/rhhx/0n//8R/Hx8Tp//rwGDBig/fv33/C5tWrVclsOCgqSJEVERFy1/ocffiiymj/77DPFxMSoQoUKCg4OVrVq1fTss89KUqGC3XfffSdJatCgwVXbGjZs6Np+RdmyZVWtWjW3dZUrVy7SHgGUDAQ7AF7J399f7dq104svvqj58+crNzdXS5cuveFzfH19873e/OziievdcDg/Fx8cPnxY3bt31/fff69XXnlFH374odauXavx48dLunwrk1vten0DsA8XTwDwem3btpUknTp16pbsv3LlypKkc+fOua3/5czYtaxcuVLZ2dlasWKF24zhhg0brhqb30+siIyMlCQdOHBA3bp1c9t24MAB13YApQ8zdgC8xoYNG65549/Vq1dLuvZbk0UhMjJSvr6++uSTT9zWv/nmm7/63CuzZT+vOyMjQwsXLrxqbIUKFa4Kj9fStm1bhYaGasGCBcrOznat//e//62vvvpKffr0+dV9ALATM3YAvMbYsWP1448/6r777lPDhg2Vk5OjLVu26L333lNUVJRGjBhxS143KChIDzzwgObOnSuHw6G6detq1apVbjdFvp4ePXrI399fffv21eOPP66srCz95S9/UWho6FUzjG3atNH8+fP1wgsvqF69egoNDb1qRk6S/Pz8NGvWLI0YMUJ33XWXfvvb37pudxIVFeV6mxdA6UOwA+A15syZo6VL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1eDBg1W+fHktXbpUkyZNKtICAQAAkD+FCnbffPONWrZsKUlaunSpOnfurMWLFysxMVH/+te/irI+AAAA5FOhgp0xRnl5eZKkdevWqXfv3pKkiIgIff/990VXHQAAAPKtUMGubdu2euGFF/R///d/2rRpk/r06SNJSklJuerjvQAAAFA8ChXsXn31Ve3cuVNjxozR73//e9WrV0+S9M9//lN33HFHkRYIAACA/CnU7U5atGjhdlXsFS+//LLKlPHoHVQAAABKrULN2NWpU0dnzpy5av3Fixd1++2333RRAAAAKLhCBbsjR47I6XRetT47O1vHjx+/6aIAAABQcAV633TFihWurz/66CMFBQW5lp1Op5KSklS7du2iqw4AAAD5VqBg169fP0mSw+HQsGHD3Lb5+fkpKipKf/zjH4usOAAAAORfgYLdlXvX1a5dW9u2bVPVqlVvSVEAAAAouEJdwpqSklLUdQAAAOAmFfreJElJSUpKSlJ6erprJu+Kv/71rzddGAAAAAqmUMFuxowZmjlzptq2bavq1avL4XAUdV0AAAAooEIFuwULFigxMVFDhgwp6noAAABQSIW6j11OTg4fHQYAAFDCFCrYPfroo1q8eHFR1wIAAICbUKi3Yi9evKi33npL69atU/PmzeXn5+e2/ZVXXimS4gAAAJB/hQp2u3fvVsuWLSVJe/fuddvGhRQAAACeUahgt2HDhqKuAwAAADepUOfYAQAAoOQp1Ixd165db/iW6/r16wtdEAAAAAqnUMHuyvl1V+Tm5mrXrl3au3evhg0bVhR1AQAAoIAKFexeffXVa66fPn26srKybqogAAAAFE6RnmP38MMP8zmxAAAAHlKkwS45OVlly5Ytyl0CAAAgnwr1Vuz999/vtmyM0alTp7R9+3Y999xzRVIYAAAACqZQwS4oKMht2cfHRw0aNNDMmTPVo0ePIikMAAAABVOoYLdw4cKirgMAAAA3qVDB7oodO3boq6++kiQ1adJErVq1KpKiAAAAUHCFCnbp6ekaPHiwNm7cqODgYEnSuXPn1LVrVy1ZskTVqlUryhoBAACQD4W6Knbs2LE6f/689u3bp7Nnz+rs2bPau3evMjMz9dRTTxV1jQAAAMiHQs3YrVmzRuvWrVOjRo1c6xo3bqx58+Zx8QQAAICHFGrGLi8vT35+flet9/PzU15e3k0X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VaqkWbNmuY3zBvnp8ec36L1yXpo3HV8L2qO3HltvGY/GylLs0KFDpmrVquaRRx4x58+fN8b89z+Qo0ePGofDYT788EPXeG845+OX8tPj6tWrPVlikchPn//+97+N0+k0WVlZniy10H5Z95IlS4zD4TATJ050zfjk5uaa7777zhPlFYkb9fj9998bYy7Pfhw9etQYY8zZs2eLvcablZ8ec3NzzenTp82aNWvM3r17PVHmTfn5vfeuXI0/ZcoUM2TIELdxP78fobcdX/PbY2ZmZrHWVZTy2+OVY663HltvBWbsPKRu3br6xz/+oV69eqlcuXKaPn266z9mPz8/NW/eXJUrV3aN98aPSMlPjzZ8RFN++gwMDJSPj48qVKjg4WoL50rdTqdTPj4+GjRokIwxevDBB+VwODRu3DjNmTNH3333nRYtWqTy5ct73e9sfntMSUnR4sWL3f4+vUV+ezxy5Ij+9re/qXz58h6uuOCuzErl5eXJz89P0uVZuvT0dNeYhIQEBQQE6KmnnlKZMmW87ne1MD16m/z26O/vr3HjxnntsfVW8L6ftkW6du2qpUuX6oEHHtCpU6c0cOBANW/eXIsWLVJ6eroiIiI8XeJNKw09Sr/eZ61atTxdYpHw9fWVMUZ5eXkaPHiwHA6HhgwZohUrVujw4cPatm2b1x9gf63H//znPypXrpyny7wpN+rx0KFD2r59u1eGup/z8fFx+9zQK2+1Tp06VS+88IK++OILrww8P0ePl3v09fX1ZIkljsMYL7zFtmV27typCRMm6MiRIypTpox8fX21ZMkStWrVytOlFZnS0KNUevq8cthwOBzq3r27du3apY0bN1p1fgs9er8r52ZNnz5dp06dUv369TVlyhRt2bJFrVu39nR5RYIe7eixKHl3lLdE69attWLFCp09e1bnz59X9erVrzpZ29uVhh6l0tOnw+GQ0+nU008/rQ0bNmjXrl3WhIEr6NH7XZnd8fPz01/+8hcFBgZq8+bNVoUBesQvMWMHoFCcTqcSExPVpk0bt89utAk92mH79u1q37699u7d6zVXMRcUPeIKgh2AQvv5uS+2okc7XLhwwevP//w19AiJYAcAAGAN77gjIwAAAH4VwQ4AAMASBDsAAABLEOwAAAAsQbADAACwBMEOAADAEgQ7AAAASxDsAEDS6dOnNXr0aNWqVUsBAQEKDw9XbGysPvvsM0mXP35r2bJlBd5vVFSUXnvttaItFgCug8+KBQBJ/fv3V05Ojt555x3VqVNHaWlpSkpK0pkzZzxdGgDkGzN2AEq9c+fO6dNPP9WsWbPUtWtXRUZGqn379oqPj9c999yjqKgoSdJ9990nh8PhWj58+LDuvfdehYWFqWLFimrXrp3WrVvn2m+XLl303Xffafz48XI4HG4f27V582bdeeedKleunCIiIvTUU0/pwoULru1vvvmm6tevr7JlyyosLEwDBgwolu8FAO9GsANQ6lWsWFEVK1bUsmXLlJ2dfdX2bdu2SZIWLlyoU6dOuZazsrLUu3dvJSUl6YsvvlDPnj3Vt29fHT16VJL0/vvvq2bNmpo5c6ZOnTqlU6dOSbocCHv27Kn+/ftr9+7deu+997R582aNGTNG0uUPO3/qqac0c+ZMHThwQGvWrFHnzp2L41sBwMvxWbEAIOlf//qXRo0apZ9++kmtW7fWXXfdpcGDB6t58+aSLp9j98EHH6hfv3433E/Tpk31xBNPuEJaVFSUxo0bp3HjxrnGPProo/L19dWf//xn17rNmzfrrrvu0oULF7R69WqNGDFCx48fV6VKlYq8VwD2YsYOAHT5HLuTJ09qxYoV6tmzpzZu3KjWrVsrMTHxus/JysrSxIkT1ahRIwUHB6tixYr66quvXDN21/Pll18qMTHRNVNYsWJFxcbGKi8vTykpKbr77rsVGRmpOnXqaMiQIXr33Xf1448/FnHHAGxEsAOA/69s2bK6++679dxzz2nLli0aPny4pk2bdt3xEydO1AcffKAXX3xRn376qXbt2qVmzZopJyfnhq+TlZWlxx9/XLt27XI9vvzySx08eFB169ZVpUqVtHPnTv39739X9erVNXXqVLVo0ULnzp0r4o4B2IarYgHgOho3buy6xYmfn5+cTqfb9s8++0zDhw/XfffdJ+lyYDty5IjbGH9//6ue17p1a+3fv1/16tW77muXKVNGMTExiomJ0bRp0xQcHKz169fr/vvvv/nGAFiLGTsApd6ZM2fUrVs3/e1vf9Pu3buVkpKipUuXavbs2br33nslXT5XLikpSampqfrhhx8kSfXr19f777/vmnF78MEHlZeX57bvqKgoffLJJzpx4oS+//57SdLkyZO1ZcsWjRkzRrt27dLBgwe1fPly13l5q1at0htvvKFdu3bpu+++06JFi5SXl6cGDRoU43cFgDci2AEo9SpWrKgOHTro1VdfVefOndW0aVM999xzGjVqlP70pz9Jkv74xz9q7dq1ioiIUKtWrSRJr7zyiipXrqw77rhDffv2VWxsrFq3bu2275kzZ+rIkSOqW7euqlWrJklq3ry5Nm3apG+++UZ33nmnWrVqpalTp6pGjRqSpODgYL3//vvq1q2bGjVqpAULFujvf/+7mjRpUozfFQDeiKtiAQAALMGMHQAAgCUIdgAAAJYg2AEAAFiCYAcAAGAJgh0AAIAlCHYAAACWINgBAABYgmAHAABgCYIdAACAJQh2AAAAliDYAQAAWIJgBwAAYIn/BxC6mX/wpUnFAAAAAElFTkSuQmCC", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/plain": [ - "{'00': 331,\n", - " '01': 8,\n", - " '02': 0,\n", - " '10': 0,\n", - " '11': 323,\n", - " '12': 6,\n", - " '20': 4,\n", - " '21': 0,\n", - " '22': 328}" - ] - }, - "execution_count": 46, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "job = backend_ion.run(compiled_circuit_ada)\n", "\n", @@ -1252,40 +815,15 @@ }, { "cell_type": "code", - "execution_count": 47, + "execution_count": null, "id": "632aca35", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "|0>-----[R01(1.57,-2.09)]----[R02(1.23,-2.62)]----[R02(3.14,-2.62)]----[R01(1.57,-0.52)]----MG-----=||\n", - "|0>-----MG----MG----MG----MG----MG-----=||\n" - ] - } - ], + "outputs": [], "source": [ "from mqt.qudits.visualisation.drawing_routines import draw_qudit_local\n", "\n", "draw_qudit_local(compiled_circuit_ada)" ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "f0304591", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "854179a8", - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { @@ -1303,8 +841,7 @@ "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.12" + "pygments_lexer": "ipython3" } }, "nbformat": 4, diff --git a/MQT Tutorial/MQT Qudits in Short.ipynb b/MQT Tutorial/MQT Qudits in Short.ipynb index b239166..9fa9b8e 100644 --- a/MQT Tutorial/MQT Qudits in Short.ipynb +++ b/MQT Tutorial/MQT Qudits in Short.ipynb @@ -253,14 +253,6 @@ "\n", "plot_counts(counts, compiled_circuit_qr)" ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "91b7145d", - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { diff --git a/src/python/bindings.cpp b/src/python/bindings.cpp index f18f076..00e1f89 100644 --- a/src/python/bindings.cpp +++ b/src/python/bindings.cpp @@ -765,5 +765,5 @@ py::list stateVectorSimulation(py::object& circ, py::object& noiseModel) { PYBIND11_MODULE(_qudits, m) { auto misim = m.def_submodule("misim"); misim.def("state_vector_simulation", &stateVectorSimulation, "circuit"_a, - "noise_model"_a); + "noise_model"_a); } From a03df27718ab35f946a7281d13f962495b165544 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Fri, 19 Apr 2024 22:00:56 +0200 Subject: [PATCH 19/22] =?UTF-8?q?=F0=9F=9A=A8=20some=20easy=20pre-commit?= =?UTF-8?q?=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MQT Tutorial/MQT Qudits Tutorial.ipynb | 13 ------------- .../numerical_ansatz_utils.py | 5 ----- .../ansatz/parametrize.py | 18 ------------------ .../opt/distance_measures.py | 6 +++--- src/mqt/qudits/exceptions/__init__.py | 2 +- src/mqt/qudits/exceptions/backenderror.py | 2 +- src/mqt/qudits/exceptions/circuiterror.py | 2 +- src/mqt/qudits/exceptions/compilerexception.py | 4 ++-- src/mqt/qudits/exceptions/joberror.py | 4 ++-- 9 files changed, 10 insertions(+), 46 deletions(-) diff --git a/MQT Tutorial/MQT Qudits Tutorial.ipynb b/MQT Tutorial/MQT Qudits Tutorial.ipynb index 11a8aa6..c463e83 100644 --- a/MQT Tutorial/MQT Qudits Tutorial.ipynb +++ b/MQT Tutorial/MQT Qudits Tutorial.ipynb @@ -82,19 +82,6 @@ "In the next cell the program is explicitly written, although several methods for importing programs from files are present in the library." ] }, - { - "attachments": { - "2d_example.png": { - "image/png": "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T+q5BDMzMz/1Uz91+umnQ0qec845T3ziE+fm5vbt2wc5ePHFF//1X//1aaedBner1UJkbD7vec/75Cc/ee+992ryKIqe8pSnnHjiiQjauXPnz/7szyZJokE5C1MtutNFY65sj4ZhGEeR1DAMY4341re+deWVV0I5/eZv/uYPf/hD+PzoRz96xSte8cEPfvCnf/qnsQmN9ZCHPOR973sfHGeeeeaHPvQheO7Zs+eFL3whHK961auuu+46OECz2URWe/fuhfv3fu/3vvKVryDDN7zhDRoKxsbGbrjhhu9///v/+3//b/WB5KpWqzfddBPc73znO6HesK9PfOITGvqZz3wG6lPd4IwzzvjCF77gNjIWplp0p3AsjLmyPRqGYRw9bKXQMIy1YX5+/rLLLnvKU57yD//wDzt37vzlX/5l9Xz3u9/90Y9+9Ld+67ewCVH1/ve//7d/+7ef//znX3rppc961rPgOTk5+c///M9wYAj727/9WzgAkkBiIh+4wzAsl8unn37661//eg2FMoMnZOXZZ5/9yle+Uj2vvfbaE088EdoL7he/+MUjIyPqryDJ+eef7zZKpQsuuOD66693GxkLUy26U7gXxlzZHg3DMI4eJgoNw1gbGo3GzTff/Cu/8isnnHDCc5/73G984xtJkkBFQfO94Q1veOpTn6rRoJOe/vSn/+u//uub3vQm9cl53vOe9+EPf7jT6cB9zTXXQDiq/5vf/OaLL75Y3eDuu+++4oor3vnOd46OjqrP1VdfDfkI3QldiGKoZw8oxsTEhNsolcbHxw8dOuQ2BmDhTg/LEe7RMAzjCDFRaBjG2pCmKdTbwx72sCc96UkvfvGL4zjWR+iCIDjttNM0Drjhhhs+9rGPXX755b/3e7/nvDLOO++8U0455TOf+czU1NSnPvUp6EsX4AGt+YQnPOH1r389dKfzKpXg/uY3v/m6173uaU97GtSb8y0CsTgzM+M2SqXp6emehb0lWHSnh+VI9mgYhnHkmCg0DGNt+OAHP/jv//7vX/3qVz//+c//3//7f51vqVQW1D07O3vFFVe8/e1vf9e73nXdddfpXWOf5z3vee9///v/9V//FSLsxBNPdL4ZyPzZz372e9/7XmSiPnfddRcUG+TXqaee+qIXvejss8/+j//4Dw3q4ayzzrr99tvdRqkEN3zcxpIs3OmArHiPhmEY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OGxkweDBg9nvSBZ76nyHomoM2ffTn/4UPUeFFxYWopMOO+wwwu++++5DDz301FNPofZaW1tRLSr1TjvttDPOOGPixInkgABCIS1YsODxxx9/9NFHkY8oOcQNO46sOKmbOnXq2WefPW7cuI4KyYddT0nq6upef/31Rx555J133kEbsUbaAyulJZAVqx4xYgSrPv7444cPH068r72AxWfMmHHDDTeg1Uii5JSZeHIeOnTopEmT9Nhy+OGHX3TRRddee+28efMoJEoXS8zYBNay//776+vTkaFXX301ApEwC2otaQ5Uy9y5c9lSqoWNpYTkgwGLl5WVHXDAASi5Y445hgaMPSUhB4VlNQfCs2fPpqhsL7MjR4685pprUL3U27Rp06hw8m9sbGTr7r333uOOOy4/E6MTqETDMHow7jw8gzLgDJ6jrRdr9Giamprw02iC55577qWXXlq1ahW7nmbgJRs7Bq3kJUuWIIAQH1BSUoLi4azspptuGjNmDNIEKYPC01RmmcZisf322+/hhx9GISHdHnzwQURkQUGBpop6cvpPl8J4ypQp7NMt9GWk1SuvvHLWWWdVVFTIahzkBur3yVAzJ7dRo0b9z//8z4oVK7yFHWT+73//G1nmW7pSSFgDmsk555yD3qK0mj8xsqY8dHVDhgx58cUX2TptgVpLiL9ly5YhKCkAy7K9frW4NQiE0a+HHHLI/fffr+9X8snPiswHDhyo69p3330XLVqECL7yyivJVvMkK8L//Oc/t1BphmKS2TB6IPRt/6DJLIdFjv6cfHNwVAOjB8N+5wRALzuyx2kJdXV1uGTb+zsacaptI1igcgQ18yvH0qVLNZU94tszy66ZMWPGT3/603ffffeRRx754Q9/OH36dCI1B8w0H3+Rt956Cy2F9CGsJJNJejqQM7z22muXXnrp448/jmJjEeJZnCMAChWdh/xilkhNolS33HLLj370o+rqambJjam2FmDVZEhAVw0ENIkAYAzM+kntzIAwMboV5KZLLV++HN3GqilAu3x86QlsGuc23/nOd37+85+vW7eOxTHQ4slqXAXmr4tMamtrb7311t///veaJzZqoMsSNrZAmKbgBQ3D6P7oUQ81oJd78AR6rASXbvR82Nc0g4aGBhwqAXWNFRUVhYWFnoWxY6C2qXxq/p577kFjMYsSam1tfeKJJ9DlkUhk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22+/fdttty1YsOC73/3uiy++qG/TuPfee7/whS+oMWSz2f32289f/JhjjvnKV74yatSolpYWFv/973+/cOHCz3zmM2vXrmUWg5tvvvniiy9WY/jJT35yzTXXEMCttrvnqW/fvvpW3srKyssvv3zYsGGrVq0677zzhgwZogaG8Qli3VANDKNHgSg0jG4HJ/1f/epX/ZvTt0AwGDz11FNXr17tLdkZr732WkFBgbdAB0488cRkMnnEEUfo7F/+8hdvsTYWL148aNAgTe3I6NGjcUWHH364zv7iF7/wFnNcf/31Go838qLauOyyyzQpH9yel2wYnzTWDQ2jh2GXj41uSUVFxZ133jlnzpwrr7xy4sSJHd9YG4lEJk+ezJn93Llz//73v2/BW8CUKVNeeumlgw8+2JtvY9iwYTfccMNjjz0WjUb9G+E7vups5MiROInzzjuv3dcUevXq9Y1vfOP1118fMGCA/0WvrX9TGo7qjDPO8GYcMYc3YxifNNYNDaOHYZePjZ5AXV3d4sWL77777t/85jca8/zzzx955JEa3npWr1793nvv1dbWlpeX44omTZoUCn2EE6empqa333573bp1eK/Bgwd/6lOf2vJHt7aG5cuXz5s3D+fat2/f8ePHmzcydlqsGxpGd8dEodFzWLNmzdChQ/WbV1/60pf++te/arxhGB8b1g0No/til4+NnsOgQYP8Cz333nvva6+9pmHDMD42rBsaRvfFRgqNHsX777+/zz776Gfv+/fv//DDDx966KGaZBjGx4N1Q8PoppgoNHoa11577Y9//GNvJhA46KCDDjjggHA4vHLlyiuvvHLy5MlegmEYOwzrhobRHTFRaPQ0aNKXXHLJLbfc4s3n8fTTTx977LHejGEYOwzrhobRHbF7Co2eRjAYvPnmm5966qkTTjihqKjIjxw9enTv3r111jCMHYp1Q8PojthIodGTyWQyGzZsoJHjh+wtEobxiWDd0DC6CyYKDcMwDMMwDLt8bBiGYRiGYZgoNAzDMAzDMMBEoWEYhmEYhmGi0DAMwzAMwzBRaBiGYRiGYYCJQsMwDMMwDMNEoWEYhmEYhhEI/H/490LCtPVzDgAAAABJRU5ErkJggg==" - } - }, - "cell_type": "markdown", - "id": "8affdafc", - "metadata": {}, - "source": [ - "![2d_example.png](attachment:2d_example.png)" - ] - }, { "cell_type": "code", "execution_count": null, diff --git a/src/mqt/qudits/compiler/compilation_minitools/numerical_ansatz_utils.py b/src/mqt/qudits/compiler/compilation_minitools/numerical_ansatz_utils.py index faef818..cea9038 100755 --- a/src/mqt/qudits/compiler/compilation_minitools/numerical_ansatz_utils.py +++ b/src/mqt/qudits/compiler/compilation_minitools/numerical_ansatz_utils.py @@ -2,11 +2,6 @@ import numpy as np -"""def calculate_q0_q1(lev, dim): - q1 = lev % dim # Calculate the remainder - q0 = (lev - q1) // dim # Calculate the quotient - return q0, q1""" - def on1(gate, other_size): return np.kron(np.identity(other_size, dtype="complex"), gate) diff --git a/src/mqt/qudits/compiler/twodit/variational_twodit_compilation/ansatz/parametrize.py b/src/mqt/qudits/compiler/twodit/variational_twodit_compilation/ansatz/parametrize.py index 150f5ae..4afa942 100755 --- a/src/mqt/qudits/compiler/twodit/variational_twodit_compilation/ansatz/parametrize.py +++ b/src/mqt/qudits/compiler/twodit/variational_twodit_compilation/ansatz/parametrize.py @@ -7,13 +7,6 @@ CUSTOM_PRIMITIVE = None # numpy array -""" -def params_splitter(params, dim): - n = (-1 + dim ** 2) - ret = [params[i:i + n] for i in range(0, len(params), n)] - return ret -""" - def params_splitter(params, dims): ret = [] @@ -26,17 +19,6 @@ def params_splitter(params, dims): return ret -""" -def params_splitter(params, dims): - ret = [] - n = -1 + dims[0] ** 2 - m = -1 + dims[1] ** 2 - for i in range(max(len(params) // (n + m) + 1, 1)): - ret.append(params[i * (n + m):(i + 1) * (n + m)]) - return ret -""" - - def reindex(ir, jc, num_col): return ir * num_col + jc diff --git a/src/mqt/qudits/compiler/twodit/variational_twodit_compilation/opt/distance_measures.py b/src/mqt/qudits/compiler/twodit/variational_twodit_compilation/opt/distance_measures.py index 97fb330..1f5fd06 100755 --- a/src/mqt/qudits/compiler/twodit/variational_twodit_compilation/opt/distance_measures.py +++ b/src/mqt/qudits/compiler/twodit/variational_twodit_compilation/opt/distance_measures.py @@ -1,12 +1,12 @@ +"""FROM An alternative quantum fidelity for mixed states of qudits Xiaoguang Wang, 1, 2, * Chang-Shui Yu, 3 and x. x. +Yi 3""" + from __future__ import annotations import typing import numpy as np -"""FROM An alternative quantum fidelity for mixed states of qudits Xiaoguang Wang, 1, 2, * Chang-Shui Yu, 3 and x. x. -Yi 3 """ - def size_check(a: np.ndarray, b: np.ndarray) -> bool: return bool(a.shape == b.shape and a.shape[0] == a.shape[1]) diff --git a/src/mqt/qudits/exceptions/__init__.py b/src/mqt/qudits/exceptions/__init__.py index e29e71f..13c948c 100644 --- a/src/mqt/qudits/exceptions/__init__.py +++ b/src/mqt/qudits/exceptions/__init__.py @@ -1,4 +1,4 @@ -"""Exceptions modul.""" +"""Exceptions module.""" from __future__ import annotations diff --git a/src/mqt/qudits/exceptions/backenderror.py b/src/mqt/qudits/exceptions/backenderror.py index 62288c6..42b99c0 100644 --- a/src/mqt/qudits/exceptions/backenderror.py +++ b/src/mqt/qudits/exceptions/backenderror.py @@ -2,5 +2,5 @@ class BackendNotFoundError(Exception): - def __init_(self, message) -> None: + def __init__(self, message: str) -> None: print(message) diff --git a/src/mqt/qudits/exceptions/circuiterror.py b/src/mqt/qudits/exceptions/circuiterror.py index 1dcd5d3..cfff914 100644 --- a/src/mqt/qudits/exceptions/circuiterror.py +++ b/src/mqt/qudits/exceptions/circuiterror.py @@ -2,5 +2,5 @@ class CircuitError(Exception): - def __init_(self, message) -> None: + def __init__(self, message: str) -> None: print(message) diff --git a/src/mqt/qudits/exceptions/compilerexception.py b/src/mqt/qudits/exceptions/compilerexception.py index 24ec0b9..c1a9743 100644 --- a/src/mqt/qudits/exceptions/compilerexception.py +++ b/src/mqt/qudits/exceptions/compilerexception.py @@ -10,7 +10,7 @@ def __str__(self) -> str: class SequenceFoundException(Exception): - def __init__(self, node_key=-1) -> None: + def __init__(self, node_key: int = -1) -> None: self.last_node_id = node_key def __str__(self) -> str: @@ -26,5 +26,5 @@ def __str__(self) -> str: class FidelityReachException(Exception): - def __init__(self, message="") -> None: + def __init__(self, message: str = "") -> None: self.message = message diff --git a/src/mqt/qudits/exceptions/joberror.py b/src/mqt/qudits/exceptions/joberror.py index d899a3c..11fb970 100644 --- a/src/mqt/qudits/exceptions/joberror.py +++ b/src/mqt/qudits/exceptions/joberror.py @@ -2,10 +2,10 @@ class JobError(Exception): - def __init_(self, message) -> None: + def __init__(self, message: str) -> None: print(message) class JobTimeoutError: - def __init_(self, message) -> None: + def __init__(self, message: str) -> None: print(message) From 6bb3760d2570957fb5668a41e4a188902a068faa Mon Sep 17 00:00:00 2001 From: burgholzer Date: Fri, 19 Apr 2024 22:20:27 +0200 Subject: [PATCH 20/22] =?UTF-8?q?=F0=9F=A9=B9=20fix=20cmake=20defines?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 4a64229..cb658a5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -110,8 +110,8 @@ git-only = [ ] [tool.scikit-build.cmake.define] -BUILD_MQT_MISIM_TESTS = "OFF" -BUILD_MQT_MISIM_BINDINGS = "ON" +BUILD_MQT_QUDITS_TESTS = "OFF" +BUILD_MQT_QUDITS_BINDINGS = "ON" [tool.setuptools_scm] From cf4adf3c9bd5431bacc3a55f3b953f705e7b0f32 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Fri, 19 Apr 2024 22:38:08 +0200 Subject: [PATCH 21/22] =?UTF-8?q?=F0=9F=A9=B9=20fix=20a=20couple=20of=20ea?= =?UTF-8?q?sy=20mistakes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../phy_local_adaptive_decomp.py | 2 +- src/mqt/qudits/quantum_circuit/circuit.py | 64 ++++++++++++------- .../qudits/simulation/backends/backendv2.py | 6 +- 3 files changed, 43 insertions(+), 29 deletions(-) diff --git a/src/mqt/qudits/compiler/onedit/mapping_aware_transpilation/phy_local_adaptive_decomp.py b/src/mqt/qudits/compiler/onedit/mapping_aware_transpilation/phy_local_adaptive_decomp.py index 2aeaa87..685dbab 100644 --- a/src/mqt/qudits/compiler/onedit/mapping_aware_transpilation/phy_local_adaptive_decomp.py +++ b/src/mqt/qudits/compiler/onedit/mapping_aware_transpilation/phy_local_adaptive_decomp.py @@ -16,7 +16,7 @@ graph_rule_ongate, graph_rule_update, ) -from ..mapping_aware_transpilation import PhyQrDecomp +from ..mapping_aware_transpilation.phy_local_qr_decomp import PhyQrDecomp np.seterr(all="ignore") diff --git a/src/mqt/qudits/quantum_circuit/circuit.py b/src/mqt/qudits/quantum_circuit/circuit.py index 26e9c88..c4b2e41 100644 --- a/src/mqt/qudits/quantum_circuit/circuit.py +++ b/src/mqt/qudits/quantum_circuit/circuit.py @@ -5,13 +5,33 @@ import warnings from typing import TYPE_CHECKING -import gates - +from .gates import ( + LS, + MS, + CEx, + CSum, + CustomMulti, + CustomOne, + CustomTwo, + GellMann, + H, + Perm, + R, + RandU, + Rh, + Rz, + S, + VirtRz, + X, + Z, +) from .qasm import QASM if TYPE_CHECKING: import numpy as np + from .gate import ControlData, Gate + class QuantumRegister: @classmethod @@ -147,19 +167,19 @@ def append(self, qreg: QuantumRegister) -> None: @add_gate_decorator def csum(self, qudits: list[int]): - return gates.CSum( + return CSum( self, "CSum" + str([self.dimensions[i] for i in qudits]), qudits, [self.dimensions[i] for i in qudits], None ) @add_gate_decorator def cu_one(self, qudits: int, parameters: np.ndarray, controls: ControlData | None = None): - return gates.CustomOne( + return CustomOne( self, "CUo" + str(self.dimensions[qudits]), qudits, parameters, self.dimensions[qudits], controls ) @add_gate_decorator def cu_two(self, qudits: int, parameters: np.ndarray, controls: ControlData | None = None): - return gates.CustomTwo( + return CustomTwo( self, "CUt" + str([self.dimensions[i] for i in qudits]), qudits, @@ -170,7 +190,7 @@ def cu_two(self, qudits: int, parameters: np.ndarray, controls: ControlData | No @add_gate_decorator def cu_multi(self, qudits: int, parameters: np.ndarray, controls: ControlData | None = None): - return gates.CustomMulti( + return CustomMulti( self, "CUm" + str([self.dimensions[i] for i in qudits]), qudits, @@ -181,7 +201,7 @@ def cu_multi(self, qudits: int, parameters: np.ndarray, controls: ControlData | @add_gate_decorator def cx(self, qudits: list[int], parameters: list | None = None): - return gates.CEx( + return CEx( self, "CEx" + str([self.dimensions[i] for i in qudits]), qudits, @@ -193,21 +213,19 @@ def cx(self, qudits: list[int], parameters: list | None = None): @add_gate_decorator def gellmann(self, qudit: int, parameters: list | None = None, controls: ControlData | None = None): warnings.warn("Using this matrix in a circuit will not allow simulation.", UserWarning) - return gates.GellMann( - self, "Gell" + str(self.dimensions[qudit]), qudit, parameters, self.dimensions[qudit], controls - ) + return GellMann(self, "Gell" + str(self.dimensions[qudit]), qudit, parameters, self.dimensions[qudit], controls) @add_gate_decorator def h(self, qudit: int, controls: ControlData | None = None): - return gates.H(self, "H" + str(self.dimensions[qudit]), qudit, self.dimensions[qudit], controls) + return H(self, "H" + str(self.dimensions[qudit]), qudit, self.dimensions[qudit], controls) @add_gate_decorator def rh(self, qudit: int, controls: ControlData | None = None): - return gates.Rh(self, "Rh" + str(self.dimensions[qudit]), qudit, self.dimensions[qudit], controls) + return Rh(self, "Rh" + str(self.dimensions[qudit]), qudit, self.dimensions[qudit], controls) @add_gate_decorator def ls(self, qudits: list[int], parameters: list | None = None): - return gates.LS( + return LS( self, "LS" + str([self.dimensions[i] for i in qudits]), qudits, @@ -218,7 +236,7 @@ def ls(self, qudits: list[int], parameters: list | None = None): @add_gate_decorator def ms(self, qudits: list[int], parameters: list | None = None): - return gates.MS( + return MS( self, "MS" + str([self.dimensions[i] for i in qudits]), qudits, @@ -229,7 +247,7 @@ def ms(self, qudits: list[int], parameters: list | None = None): @add_gate_decorator def pm(self, qudits: list[int], parameters: list): - return gates.Perm( + return Perm( self, "Pm" + str([self.dimensions[i] for i in qudits]), qudits, @@ -240,34 +258,32 @@ def pm(self, qudits: list[int], parameters: list): @add_gate_decorator def r(self, qudit: int, parameters: list, controls: ControlData | None = None): - return gates.R(self, "R" + str(self.dimensions[qudit]), qudit, parameters, self.dimensions[qudit], controls) + return R(self, "R" + str(self.dimensions[qudit]), qudit, parameters, self.dimensions[qudit], controls) @add_gate_decorator def randu(self, qudits: list[int]): - return gates.RandU( + return RandU( self, "RandU" + str([self.dimensions[i] for i in qudits]), qudits, [self.dimensions[i] for i in qudits] ) @add_gate_decorator def rz(self, qudit: int, parameters: list, controls: ControlData | None = None): - return gates.Rz(self, "Rz" + str(self.dimensions[qudit]), qudit, parameters, self.dimensions[qudit], controls) + return Rz(self, "Rz" + str(self.dimensions[qudit]), qudit, parameters, self.dimensions[qudit], controls) def virtrz(self, qudit: int, parameters: list, controls: ControlData | None = None): - return gates.VirtRz( - self, "VirtRz" + str(self.dimensions[qudit]), qudit, parameters, self.dimensions[qudit], controls - ) + return VirtRz(self, "VirtRz" + str(self.dimensions[qudit]), qudit, parameters, self.dimensions[qudit], controls) @add_gate_decorator def s(self, qudit: int, controls: ControlData | None = None): - return gates.S(self, "S" + str(self.dimensions[qudit]), qudit, self.dimensions[qudit], controls) + return S(self, "S" + str(self.dimensions[qudit]), qudit, self.dimensions[qudit], controls) @add_gate_decorator def x(self, qudit: int, controls: ControlData | None = None): - return gates.X(self, "X" + str(self.dimensions[qudit]), qudit, self.dimensions[qudit], controls) + return X(self, "X" + str(self.dimensions[qudit]), qudit, self.dimensions[qudit], controls) @add_gate_decorator def z(self, qudit: int, controls: ControlData | None = None): - return gates.Z(self, "Z" + str(self.dimensions[qudit]), qudit, self.dimensions[qudit], controls) + return Z(self, "Z" + str(self.dimensions[qudit]), qudit, self.dimensions[qudit], controls) def replace_gate(self, gate_index: int, sequence: list[Gate]) -> None: self.instructions[gate_index : gate_index + 1] = sequence diff --git a/src/mqt/qudits/simulation/backends/backendv2.py b/src/mqt/qudits/simulation/backends/backendv2.py index f99c501..b9dd6cd 100644 --- a/src/mqt/qudits/simulation/backends/backendv2.py +++ b/src/mqt/qudits/simulation/backends/backendv2.py @@ -55,10 +55,8 @@ def operations(self) -> list[Gate]: def operation_names(self) -> list[str]: return list(self.target.operation_names) - @property - @abstractmethod - def target(self): - pass + # todo: this has to be defined properly + target = Any @property def num_qudits(self) -> int: From da9619a352959d16b394317f0c4cfdf6e0498bc6 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Fri, 19 Apr 2024 22:39:59 +0200 Subject: [PATCH 22/22] =?UTF-8?q?=F0=9F=A9=B9=20fix=20minimum=20version?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index cb658a5..b88d3a4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,7 +39,7 @@ dependencies = [ "numpy>=1.24", "networkx>=3.0", "scipy>=1.10", - "h5py>=3.3", + "h5py>=3.7", "tensornetwork>=0.4", "matplotlib>=3.7", ]