From e288da136d34dfa78cbf1c3f3d2c11097d1e35c6 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Tue, 17 Dec 2024 09:41:59 +0100 Subject: [PATCH 1/7] =?UTF-8?q?=F0=9F=93=9D=20update=20contributing=20guid?= =?UTF-8?q?e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- .github/contributing.md | 42 +++++++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/.github/contributing.md b/.github/contributing.md index 4dcd12bb3..f6dd9fb81 100644 --- a/.github/contributing.md +++ b/.github/contributing.md @@ -39,28 +39,34 @@ Nevertheless, please try to follow the guidelines below as well as you can to he ## Core Guidelines - ["Commit early and push often"](https://www.worklytics.co/blog/commit-early-push-often). -- Write meaningful commit messages (preferably using [gitmoji](https://gitmoji.dev) to give additional context to your commits). -- Focus on a single feature/bug at a time and only touch relevant files. Split multiple features into multiple contributions. -- If you added a new feature, you should add tests that ensure it works as intended. Furthermore, the new feature should be documented appropriately. -- If you fixed a bug, you should add tests that demonstrate that the bug has been fixed. -- Document your code thoroughly and write readable code. -- Keep your code clean. Remove any debug statements, left-over comments, or code unrelated to your contribution. -- Run `pre-commit run -a` to check your code for style and linting errors before committing. +- Write meaningful commit messages, preferably using [gitmoji](https://gitmoji.dev) for additional context. +- Focus on a single feature or bug at a time and only touch relevant files. Split multiple features into separate contributions. +- Add tests for new features to ensure they work as intended. Document new features appropriately. +- Add tests for bug fixes to demonstrate that the bug has been resolved. +- Document your code thoroughly and ensure it is readable. +- Keep your code clean by removing debug statements, leftover comments, and unrelated code. +- Check your code for style and linting errors before committing. +- Follow the project's coding standards and conventions. +- Be open to feedback and willing to make necessary changes based on code reviews. ## Pull Request Workflow - Create PRs early. It is ok to create work-in-progress PRs. You may mark these as draft PRs on GitHub. -- Describe your PR. Start with a descriptive title, reference any related issues by including the issue number in the PR description, and add a comprehensive description of the changes. We provide a PR template that you can (and should) follow to create a PR. Do not delete any sections from the template. -- Whenever a PR is created or updated, several workflows on all supported platforms and versions of Python are executed. These workflows ensure that the project still builds, that all tests pass, and that the code is properly formatted and introduces no new linting errors. Your PR is expected to pass all these continuous integration (CI) checks before it can be merged. Here are some tips for finding the cause of certain failures: - - If any of the `CI / 🇨‌ Test` checks fail, this most likely indicates build errors or test failures in the C++ part of the code base. Look through the respective logs on GitHub for any error or failure messages. - - If any of the `CI / 🐍 Test` checks fail, this most likely indicates build errors or test failures in the Python part of the code base. Look through the respective logs on GitHub for any error or failure messages. - - If any of the `codecov/\*` checks fail, this means that your changes are not appropriately covered by tests or that the overall project coverage decreased too much. Ensure that you include tests for all your changes in the PR. - - If `cpp-linter` comments on your PR with a list of warnings, these have been raised by `clang-tidy` when checking the C++ part of your changes for warnings or style guideline violations. The individual messages frequently provide helpful suggestions on how to fix the warnings. If you don't see any messages, but the `🇨‌ Lint / 🚨 Lint` check is red, click on the `Details` link to see the full log of the check and a step summary. - - If the `pre-commit.ci` check fails, some of the `pre-commit` checks failed and could not be fixed automatically by the _pre-commit.ci_ bot. Such failures are most likely related to the Python part of the code base. The individual log messages frequently provide helpful suggestions on how to fix the warnings. - - If the `docs/readthedocs.org:mqt-core` check fails, the documentation could not be built properly. Inspect the corresponding log file for any errors. -- Once your PR is ready, change it from a draft PR to a regular PR and request a review from one of the project maintainers. Please make sure to only request a review once you are done with your changes and the PR is ready to be merged. If you are unsure whether your PR is ready for review, please ask in the PR comments. -- If your PR gets a "Changes requested" review, you will need to address the feedback and update your PR by pushing to the same branch. You don't need to close the PR and open a new one. Respond to review comments on the PR (e.g., with "done 👍" or "done in @\") to let the reviewer know that you have addressed the feedback. Note that reviewers do not get a notification if you just react to the review comment with an emoji. You need to write a comment to notify the reviewer. -- Be sure to re-request review once you have made changes after a code review so that maintainers know that the requests have been addressed. +- Describe your PR with a descriptive title, reference any related issues by including the issue number in the PR description, and add a comprehensive description of the changes. Follow the provided PR template and do not delete any sections, except for the issue reference if your PR is not related to an issue. +- Whenever a PR is created or updated, several workflows on all supported platforms and versions of Python are executed. These workflows ensure that the project still builds, all tests pass, the code is properly formatted, and no new linting errors are introduced. Your PR must pass all these continuous integration (CI) checks before it can be merged. +- Once your PR is ready, change it from a draft PR to a regular PR and request a review from one of the project maintainers. Only request a review once you are done with your changes and the PR is ready to be reviewed. If you are unsure whether your PR is ready, ask in the PR comments. If you are a first-time contributor, request a review from one of the maintainers by mentioning them in a comment on the PR. +- If your PR gets a "Changes requested" review, address the feedback and update your PR by pushing to the same branch. Do not close the PR and open a new one. Respond to review comments on the PR (e.g., with "done 👍" or "done in @") to let the reviewer know that you have addressed the feedback. Note that reviewers do not get a notification if you just react to the review comment with an emoji. Write a comment to notify the reviewer. Do not resolve the review comments yourself. The reviewer will mark the comments as resolved once they are satisfied with the changes. +- Be sure to re-request a review once you have made changes after a code review so that maintainers know that the requests have been addressed. +- No need to squash commits before merging; we usually squash them to keep the history clean. We only merge without squashing if the commit history is clean and meaningful. Avoid rebasing or force-pushing your PR branch before merging, as it complicates reviews. You can rebase or clean up commits after addressing all review comments if desired. + +Here are some tips for finding the cause of certain failures: + +- If any of the `CI / 🇨‌ Test` checks fail, this indicates build errors or test failures in the C++ part of the code base. Look through the respective logs on GitHub for any error or failure messages. +- If any of the `CI / 🐍 Test` checks fail, this indicates build errors or test failures in the Python part of the code base. Look through the respective logs on GitHub for any error or failure messages. +- If any of the `codecov/\*` checks fail, this means that your changes are not appropriately covered by tests or that the overall project coverage decreased too much. Ensure that you include tests for all your changes in the PR. +- If `cpp-linter` comments on your PR with a list of warnings, these have been raised by `clang-tidy` when checking the C++ part of your changes for warnings or style guideline violations. The individual messages frequently provide helpful suggestions on how to fix the warnings. If you don't see any messages, but the `🇨‌ Lint / 🚨 Lint` check is red, click on the `Details` link to see the full log of the check and a step summary. +- If the `pre-commit.ci` check fails, some of the `pre-commit` checks failed and could not be fixed automatically by the _pre-commit.ci_ bot. Such failures are most likely related to the Python part of the code base. The individual log messages frequently provide helpful suggestions on how to fix the warnings. +- If the `docs/readthedocs.org:\*` check fails, the documentation could not be built properly. Inspect the corresponding log file for any errors. --- From 9a4a40cdea32c6513e638df5d454bfe0dd3d77d0 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Tue, 17 Dec 2024 10:29:13 +0100 Subject: [PATCH 2/7] =?UTF-8?q?=F0=9F=93=9D=20update=20installation=20guid?= =?UTF-8?q?e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- docs/installation.md | 157 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 139 insertions(+), 18 deletions(-) diff --git a/docs/installation.md b/docs/installation.md index 93db69484..7a470169f 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -1,14 +1,69 @@ # Installation MQT Core is mainly developed as a C++17 library with Python bindings. -The resulting Python package is available on [PyPI](https://pypi.org/project/mqt.core/) and can be installed via `pip` for all major operating systems and all modern Python versions. +The resulting Python package is available on [PyPI](https://pypi.org/project/mqt.core/) and can be installed on all major operating systems using all modern Python versions. + +:::::{tip} +We highly recommend using [`uv`](https://docs.astral.sh/uv/) for working with Python projects. +It is an extremely fast Python package and project manager, written in Rust and developed by [Astral](https://astral.sh/) (the same team behind [`ruff`](https://docs.astral.sh/ruff/)). +It can act as a drop-in replacement for `pip` and `virtualenv`, and provides a more modern and faster alternative to the traditional Python package management tools. +It automatically handles the creation of virtual environments and the installation of packages, and is much faster than `pip`. +Additionally, it can even set up Python for you if it is not installed yet. + +If you do not have `uv` installed yet, you can install it via: + +::::{tab-set} +:::{tab-item} macOS and Linux + +```console +$ curl -LsSf https://astral.sh/uv/install.sh | sh +``` + +::: +:::{tab-item} Windows + +```console +$ powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex" +``` + +:::: + +Check out their excellent [documentation](https://docs.astral.sh/uv/) for more information. + +::::: + +::::{tab-set} +:sync-group: installer + +:::{tab-item} uv _(recommended)_ +:sync: uv + +```console +$ uv pip install mqt.core +``` + +::: + +:::{tab-item} pip +:sync: pip ```console -(.venv) $ pip install mqt.core +(.venv) $ python -m pip install mqt.core ``` +::: +:::: + In most practical cases (under 64-bit Linux, MacOS incl. Apple Silicon, and Windows), this requires no compilation and merely downloads and installs a platform-specific pre-built wheel. +Once installed, you can check if the installation was successful by running: + +```console +(.venv) $ python -c "import mqt.core; print(mqt.core.__version__)" +``` + +which should print the installed version of the library. + :::{attention} As of version 2.7.0, support for Python 3.8 has been officially dropped. We strongly recommend that users upgrade to a more recent version of Python to ensure compatibility and continue receiving updates and support. @@ -19,10 +74,28 @@ Thank you for your understanding. In order to get the best performance and enable platform-specific optimizations that cannot be enabled on portable wheels, it is recommended to build the library from source via: +::::{tab-set} +:sync-group: installer + +:::{tab-item} uv _(recommended)_ +:sync: uv + +```console +$ uv pip install mqt.core --no-binary mqt.core +``` + +::: + +:::{tab-item} pip +:sync: pip + ```console (.venv) $ pip install mqt.core --no-binary mqt.core ``` +::: +:::: + This requires a [C++ compiler supporting C++17](https://en.wikipedia.org/wiki/List_of_compilers#C++_compilers) and a minimum [CMake](https://cmake.org/) version of 3.19. The library is continuously tested under Linux, MacOS, and Windows using the [latest available system versions for GitHub Actions](https://github.com/actions/virtual-environments). In order to access the latest build logs, visit the [GitHub Actions page](https://github.com/cda-tum/mqt-core/actions/workflows/ci.yml). @@ -33,12 +106,21 @@ If you want to use the MQT Core Python package in your own project, you can simp This will automatically install the MQT Core package when your project is installed. ::::{tab-set} + +:::{tab-item} uv _(recommended)_ + +```console +$ uv add mqt.core +``` + +::: + :::{tab-item} pyproject.toml ```toml [project] # ... -dependencies = ["mqt.core>=2.4.0"] +dependencies = ["mqt.core>=2.7.0"] # ... ``` @@ -51,7 +133,7 @@ from setuptools import setup setup( # ... - install_requires=["mqt.core>=2.4.0"], + install_requires=["mqt.core>=2.7.0"], # ... ) ``` @@ -75,12 +157,19 @@ Furthermore, CMake's [FetchContent](https://cmake.org/cmake/help/latest/module/F include(FetchContent) set(FETCH_PACKAGES "") -set(MQT_CORE_VERSION 2.4.0 CACHE STRING "MQT Core version") +# cmake-format: off +set(MQT_CORE_VERSION 2.7.0 + CACHE STRING "MQT Core version") +set(MQT_CORE_REV "2ccf532b66998af376c256ae94a39eed802b990c" + CACHE STRING "MQT Core identifier (tag, branch or commit hash)") +set(MQT_CORE_REPO_OWNER "cda-tum" + CACHE STRING "MQT Core repository owner (change when using a fork)") +# cmake-format: on if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.24) FetchContent_Declare( mqt-core - GIT_REPOSITORY https://github.com/cda-tum/mqt-core.git - GIT_TAG v${MQT_CORE_VERSION} + GIT_REPOSITORY https://github.com/${MQT_CORE_REPO_OWNER}/mqt-core.git + GIT_TAG ${MQT_CORE_REV} FIND_PACKAGE_ARGS ${MQT_CORE_VERSION}) list(APPEND FETCH_PACKAGES mqt-core) else() @@ -88,8 +177,8 @@ else() if(NOT mqt-core_FOUND) FetchContent_Declare( mqt-core - GIT_REPOSITORY https://github.com/cda-tum/mqt-core.git - GIT_TAG v${MQT_CORE_VERSION}) + GIT_REPOSITORY https://github.com/${MQT_CORE_REPO_OWNER}/mqt-core.git + GIT_TAG ${MQT_CORE_REV}) list(APPEND FETCH_PACKAGES mqt-core) endif() endif() @@ -98,6 +187,38 @@ endif() FetchContent_MakeAvailable(${FETCH_PACKAGES}) ``` +We even offer a Dependabot-like GitHub workflow that automatically updates the `MQT_CORE_VERSION` and `MQT_CORE_REV` variables in your `CMakeLists.txt` file. +Simply add the following workflow to your project's `.github/workflows` directory: + +```yaml +name: Update MQT Core +on: + schedule: + # run once a month on the first day of the month at 00:00 UTC + - cron: "0 0 1 * *" + workflow_dispatch: + inputs: + update-to-head: + description: "Update to the latest commit on the default branch" + type: boolean + required: false + default: false + pull_request: + paths: + - .github/workflows/update-mqt-core.yml + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + update-mqt-core: + name: ⬆️ Update MQT Core + uses: cda-tum/mqt-workflows/.github/workflows/reusable-mqt-core-update.yml@v1.5 + with: + update-to-head: ${{ github.event.inputs.update-to-head == 'true' }} +``` + ::: :::{tab-item} git submodule @@ -106,8 +227,8 @@ Integrating the library as a git submodule is the simplest approach. However, handling git submodules can be cumbersome, especially when working with multiple branches or versions of the library. First, add the submodule to your project (e.g., in the `external` directory) via: -```bash -git submodule add https://github.com/cda-tum/mqt-core.git external/mqt-core +```console +$ git submodule add https://github.com/cda-tum/mqt-core.git external/mqt-core ``` Then, add the following lines to your `CMakeLists.txt` to make the library's targets available in your project: @@ -122,18 +243,18 @@ add_subdirectory(external/mqt-core) MQT Core can be installed on your system after building it from source. -```bash -git clone https://github.com/cda-tum/mqt-core.git -cd mqt-core -cmake -S . -B build -cmake --build build -cmake --install build +```console +$ git clone https://github.com/cda-tum/mqt-core.git +$ cd mqt-core +$ cmake -S . -B build +$ cmake --build build +$ cmake --install build ``` Then, in your project's `CMakeLists.txt`, you can use the `find_package` command to locate the installed library: ```cmake -find_package(mqt-core 2.4.0 REQUIRED) +find_package(mqt-core 2.7.0 REQUIRED) ``` :::: From e615b115fe9802e1cdb07bb576cffbbdf424cb22 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Fri, 3 Jan 2025 15:15:10 +0100 Subject: [PATCH 3/7] =?UTF-8?q?=E2=9E=95=20add=20`openqasm-pygments`=20dep?= =?UTF-8?q?endency=20for=20pretty=20printing=20OpenQASM=203=20output=20as?= =?UTF-8?q?=20part=20of=20the=20documentation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- pyproject.toml | 1 + uv.lock | 16 ++++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 2571db8bb..c29b3eb41 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -277,6 +277,7 @@ docs = [ "sphinxext-opengraph>=0.9.1", "pandas[output-formatting]>=2.1.2", "qiskit[qasm3-import,visualization]>=1.0.0", + "openqasm-pygments>=0.1.2", ] test = [ "pytest>=8.3.3", diff --git a/uv.lock b/uv.lock index 74157b4bc..789f16574 100644 --- a/uv.lock +++ b/uv.lock @@ -1429,6 +1429,7 @@ build = [ dev = [ { name = "furo" }, { name = "myst-nb" }, + { name = "openqasm-pygments" }, { name = "pandas", extra = ["output-formatting"] }, { name = "pybind11" }, { name = "pytest" }, @@ -1447,6 +1448,7 @@ dev = [ docs = [ { name = "furo" }, { name = "myst-nb" }, + { name = "openqasm-pygments" }, { name = "pandas", extra = ["output-formatting"] }, { name = "qiskit", extra = ["qasm3-import", "visualization"] }, { name = "setuptools-scm" }, @@ -1480,6 +1482,7 @@ build = [ dev = [ { name = "furo", specifier = ">=2024.8.6" }, { name = "myst-nb", specifier = ">=1.1.2" }, + { name = "openqasm-pygments", specifier = ">=0.1.2" }, { name = "pandas", extras = ["output-formatting"], specifier = ">=2.1.2" }, { name = "pandas", extras = ["output-formatting"], marker = "python_full_version >= '3.13'", specifier = ">=2.2.3" }, { name = "pybind11", specifier = ">=2.13.6" }, @@ -1500,6 +1503,7 @@ dev = [ docs = [ { name = "furo", specifier = ">=2024.8.6" }, { name = "myst-nb", specifier = ">=1.1.2" }, + { name = "openqasm-pygments", specifier = ">=0.1.2" }, { name = "pandas", extras = ["output-formatting"], specifier = ">=2.1.2" }, { name = "qiskit", extras = ["qasm3-import", "visualization"], specifier = ">=1.0.0" }, { name = "setuptools-scm", specifier = ">=8.1" }, @@ -1748,6 +1752,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/13/3e/1959d5219a9e6d200638d924cedda6a606392f7186a4ed56478252e70d55/numpy-2.2.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5c5cc0cbabe9452038ed984d05ac87910f89370b9242371bd9079cb4af61811e", size = 12820057 }, ] +[[package]] +name = "openqasm-pygments" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/81/4c/c4722f133b5bbef71bc8043dc1610bb72d1d724630373f046fa477aa9e3e/openqasm-pygments-0.1.2.tar.gz", hash = "sha256:9740ce2eb13adbb1de53a1bb447eeaf5cc212aeb4560e4dbed6015c8f50e5bf9", size = 56016 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/37/61/a69266cbbddd686229947052d672154589bcd5cc3fee1573be8909c657da/openqasm_pygments-0.1.2-py3-none-any.whl", hash = "sha256:b224632f8a535ad81609a991ffd82222330a742c6fbd444ed707f8586653ecc0", size = 18387 }, +] + [[package]] name = "openqasm3" version = "1.0.0" From 9b4d2b62eeecc4955cbdff9a3faccebee25d806d Mon Sep 17 00:00:00 2001 From: burgholzer Date: Fri, 3 Jan 2025 15:16:11 +0100 Subject: [PATCH 4/7] =?UTF-8?q?=F0=9F=A9=B9=20fix=20swapped=20default=20ar?= =?UTF-8?q?gument=20names=20in=20QuantumComputation=20docs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- src/mqt/core/ir/__init__.pyi | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mqt/core/ir/__init__.pyi b/src/mqt/core/ir/__init__.pyi index 8078259cf..96989611f 100644 --- a/src/mqt/core/ir/__init__.pyi +++ b/src/mqt/core/ir/__init__.pyi @@ -297,7 +297,7 @@ class QuantumComputation(MutableSequence[Operation]): # (Qu)Bit Registers # -------------------------------------------------------------------------- - def add_ancillary_register(self, n: int, name: str = "q") -> None: + def add_ancillary_register(self, n: int, name: str = "anc") -> None: """Add an ancillary register to the quantum computation. Args: @@ -313,7 +313,7 @@ class QuantumComputation(MutableSequence[Operation]): name: The name of the classical register. """ - def add_qubit_register(self, n: int, name: str = "anc") -> None: + def add_qubit_register(self, n: int, name: str = "q") -> None: """Add a qubit register to the quantum computation. Args: From af331f00bb4a95af587af94e665f5d70ce51efac Mon Sep 17 00:00:00 2001 From: burgholzer Date: Fri, 3 Jan 2025 15:19:04 +0100 Subject: [PATCH 5/7] =?UTF-8?q?=F0=9F=90=9B=20fix=20OpenQASM=20output=20of?= =?UTF-8?q?=20classic=20controlled=20operations?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- .../operations/ClassicControlledOperation.hpp | 16 +++++++- test/ir/test_io.cpp | 37 +++++++++++++++++++ 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/include/mqt-core/ir/operations/ClassicControlledOperation.hpp b/include/mqt-core/ir/operations/ClassicControlledOperation.hpp index 0395b592f..0c48a3065 100644 --- a/include/mqt-core/ir/operations/ClassicControlledOperation.hpp +++ b/include/mqt-core/ir/operations/ClassicControlledOperation.hpp @@ -9,11 +9,11 @@ #pragma once -#include "../Permutation.hpp" #include "Control.hpp" #include "Definitions.hpp" #include "OpType.hpp" #include "Operation.hpp" +#include "ir/Permutation.hpp" #include #include @@ -158,7 +158,19 @@ class ClassicControlledOperation final : public Operation { bool openQASM3) const override { of << std::string(indent * OUTPUT_INDENT_SIZE, ' '); of << "if ("; - of << creg[controlRegister.first].first; + if (isWholeQubitRegister(creg, controlRegister.first, + controlRegister.first + controlRegister.second - + 1)) { + of << creg[controlRegister.first].first; + } else { + // This might use slices in the future to address multiple bits. + if (controlRegister.second != 1) { + throw QFRException( + "Control register of classically controlled operation may either" + " be a single bit or a whole register."); + } + of << creg[controlRegister.first].second; + } of << " " << comparisonKind << " " << expectedValue << ") "; if (openQASM3) { of << "{\n"; diff --git a/test/ir/test_io.cpp b/test/ir/test_io.cpp index 8c6ec4fd4..37255a51a 100644 --- a/test/ir/test_io.cpp +++ b/test/ir/test_io.cpp @@ -705,3 +705,40 @@ TEST_F(IO, fromCompoundOperation) { const auto actual = qc2.toQASM(); EXPECT_EQ(expected, actual); } + +TEST_F(IO, classicalControlledOperationToOpenQASM3) { + qc->addQubitRegister(2); + qc->addClassicalRegister(2); + qc->classicControlled(qc::X, 0, {0, 1}); + qc->classicControlled(qc::X, 1, {0, 2}); + const std::string expected = "// i 0 1\n" + "// o 0 1\n" + "OPENQASM 3.0;\n" + "include \"stdgates.inc\";\n" + "qubit[2] q;\n" + "bit[2] c;\n" + "if (c[0] == 1) {\n" + " x q[0];\n" + "}\n" + "if (c == 1) {\n" + " x q[1];\n" + "}\n"; + + const auto actual = qc->toQASM(); + EXPECT_EQ(expected, actual); +} + +TEST_F(IO, classicalControlledOperationToOpenQASM3MoreThanOneRegister) { + qc->addQubitRegister(1); + qc->addClassicalRegister(1); + qc->addClassicalRegister(1, "d"); + qc->classicControlled(qc::X, 0, {0, 2}); + EXPECT_THROW(qc->dumpOpenQASM3(std::cout), qc::QFRException); +} + +TEST_F(IO, classicalControlledOperationToOpenQASM3NotEntireRegister) { + qc->addQubitRegister(1); + qc->addClassicalRegister(3); + qc->classicControlled(qc::X, 0, {0, 2}); + EXPECT_THROW(qc->dumpOpenQASM3(std::cout), qc::QFRException); +} From e10566b2b8e2c6f28efe55928ebaef3a79acd818 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Fri, 3 Jan 2025 15:19:51 +0100 Subject: [PATCH 6/7] =?UTF-8?q?=F0=9F=93=9D=20rework=20the=20quickstart=20?= =?UTF-8?q?guide?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- docs/index.md | 4 +- docs/mqt_core_ir.md | 324 ++++++++++++++++++++++++++++++++++++++++++++ docs/quickstart.md | 271 ------------------------------------ 3 files changed, 326 insertions(+), 273 deletions(-) create mode 100644 docs/mqt_core_ir.md delete mode 100644 docs/quickstart.md diff --git a/docs/index.md b/docs/index.md index d622e3246..51e05d40a 100644 --- a/docs/index.md +++ b/docs/index.md @@ -6,7 +6,7 @@ MQT Core is an open-source C++17 and Python library for quantum computing that forms the backbone of the quantum software tools 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/). To this end, it consists of multiple components that are used throughout the MQT, including a fully fledged intermediate representation (IR) for quantum computations, a state-of-the-art decision diagram (DD) package for quantum computing, and a state-of-the-art ZX-diagram package for working with the ZX-calculus. -This documentation provides a comprehensive guide to the MQT Core library, including {doc}`installation instructions `, a {doc}`quickstart guide `, and detailed {doc}`API documentation `. +This documentation provides a comprehensive guide to the MQT Core library, including {doc}`installation instructions `, a {doc}`quickstart guide for the MQT Core IR `, and detailed {doc}`API documentation `. The source code of MQT Core is publicly available on GitHub at [cda-tum/mqt-core](https://github.com/cda-tum/mqt-core), while pre-built binaries are available via [PyPI](https://pypi.org/project/mqt.core/) for all major operating systems and all modern Python versions. MQT Core is fully compatible with Qiskit 1.0 and above. @@ -33,7 +33,7 @@ self :caption: User Guide installation -quickstart +mqt_core_ir ``` ````{only} not latex diff --git a/docs/mqt_core_ir.md b/docs/mqt_core_ir.md new file mode 100644 index 000000000..d132e353c --- /dev/null +++ b/docs/mqt_core_ir.md @@ -0,0 +1,324 @@ +--- +file_format: mystnb +kernelspec: + name: python3 +mystnb: + number_source_lines: true +--- + +```{code-cell} ipython3 +:tags: [remove-cell] +%config InlineBackend.figure_formats = ['svg'] +``` + +# MQT Core IR + +The central interface for working with quantum computations throughout the Munich Quantum Toolkit is the {py:class}`~mqt.core.ir.QuantumComputation` class. +It effectively represents quantum computations as sequential lists of operation, similar to Qiskit's {py:class}`~qiskit.circuit.QuantumCircuit` class. + +The following will demonstrate how to work with the {py:class}`~mqt.core.ir.QuantumComputation` class in Python. + +:::{note} +MQT Core is primarily designed in C++ with a thin Python wrapper. +Historically, the C++ part of MQT Core was the focus and the Python interface was added later. +As the standards we hold ourselves to have evolved, the Python interface is much better documented than the C++ interface. +Contributions to the C++ documentation are welcome. See the [contribution guidelines](contributing.md) for more information. +::: + +## Quickstart + +The following code snippet demonstrates how to construct a quantum computation for an +instance of the Iterative Quantum Phase Estimation algorithm that aims to estimate +the phase of a unitary operator $U=p(3\pi/8)$ using 3 bits of precision. + +```{code-cell} ipython3 +--- +mystnb: + text_lexer: 'qasm3' +--- +from mqt.core.ir import QuantumComputation + +from math import pi + +theta = 3 * pi / 8 +precision = 3 + +# Create an empty quantum computation +qc = QuantumComputation() + +# Counting register +qc.add_qubit_register(1, "q") + +# Eigenstate register +qc.add_qubit_register(1, "psi") + +# Classical register for the result, the estimated phase is `0.c_2 c_1 c_0 * pi` +qc.add_classical_register(precision, "c") + +# Prepare psi in the eigenstate |1> +qc.x(1) + +for i in range(precision): + # Hadamard on the working qubit + qc.h(0) + + # Controlled phase gate + qc.cp(2**(precision - i - 1) * theta, 0, 1) + + # Iterative inverse QFT + for j in range(i): + qc.classic_controlled(op="p", target=0, creg=(j, 1), params=[-pi / 2**(i - j)]) + qc.h(0) + + # Measure the result + qc.measure(0, i) + + # Reset the qubit if not finished + if i < precision - 1: + qc.reset(0) +``` + +The circuit class provides lots of flexibility when it comes to the kind of gates that can be applied. +Check out the full API documentation of the {py:class}`~mqt.core.ir.QuantumComputation` class for more details. + +## Visualizing Circuits + +Circuits can be printed in a human-readable, text-based format. +The output is to be read from top to bottom and left to right. +Each line represents a single operation in the circuit. + +:::{note} +The first and last lines have a special meaning: the first line contains the initial layout information, while the last line contains the output permutation. This is explained in more detail in the [Layout Information](#layout-information) section. +::: + +```{code-cell} ipython3 +print(qc) +``` + +Circuits can also easily be exported to OpenQASM 3 using the {py:meth}`~mqt.core.ir.QuantumComputation.qasm3_str` method. + +```{code-cell} ipython3 +--- +mystnb: + text_lexer: 'qasm3' +--- +print(qc.qasm3_str()) +``` + +## Layout Information + +When compiling a quantum circuit for a specific quantum device, it is necessary to map the qubits of the circuit to the qubits of the device. +In addition, SWAP operations might be necessary to ensure that gates are only applied to qubits connected on the device. +These SWAP operations permute the assignment of circuit qubits to device qubits. +At the end of the computation, the values of the circuit qubits are measured at specific device qubits. +This kind of _layout information_ is important for reasoning about the functionality of the compiled circuit. +As such, preserving this information is essential for verification and debugging purposes. + +:::{note} +In the literature, the qubits used in the circuit are often referred to as _logical qubits_ or _virtual qubits_, while the qubits of the device are also called _physical qubits_. +Within the MQT, we try to avoid the terms _logical_ and _physical_ qubits, as they can be misleading due to the connection to error correction. +Instead, we use the terms _circuit qubits_ and _device qubits_. +::: + +To this end, the {py:class}`~mqt.core.ir.QuantumComputation` class contains two members, {py:attr}`~mqt.core.ir.QuantumComputation.initial_layout` and {py:attr}`~mqt.core.ir.QuantumComputation.output_permutation`, which are instances of the {py:class}`~mqt.core.ir.Permutation` class. +The initial layout tracks the mapping of circuit qubits to device qubits at the beginning of the computation, while the output permutation tracks where a particular circuit qubit is measured at the end of the computation. +While the output permutation can generally be inferred from the measurements in the circuit (using {py:meth}`~mqt.core.ir.QuantumComputation.initialize_io_mapping`), the initial layout is not always clear. +OpenQASM, for example, lacks a way to express the initial layout of a circuit and preserve this information. +Therefore, MQT Core will output the layout information as comments in the first two lines of the QASM string using the following format: + +- `// i Q_0, Q_1, ..., Q_n`, meaning circuit qubit $i$ is mapped to device qubit $Q_i$. +- `// o Q_0, Q_1, ..., Q_n` meaning the value of circuit qubit $i$ (assumed to be stored in classical bit $c[i]$) is measured at device qubit $Q_i$. + +An example illustrates the idea: + +```{code-cell} ipython3 +--- +mystnb: + text_lexer: 'qasm3' +--- +# 3 qubits, 3 classical bits +qc = QuantumComputation(3, 3) + +qc.h(0) +qc.x(1) +qc.s(2) + +# c[0] is measured at device qubit 1 +qc.measure(1, 0) +# c[1] is measured at device qubit 2 +qc.measure(2, 1) +# c[2] is measured at device qubit 0 +qc.measure(0, 2) + +# determine permutation from measurement +qc.initialize_io_mapping() + +print(qc.qasm3_str()) +``` + +In the example above, the initial layout is not explicitly specified. +A trivial layout is thus assumed, where the circuit qubits are mapped to the device qubits in order. +The output permutation is determined from the measurements and is printed as comments in the QASM string. + +:::{note} +This layout information is not part of the OpenQASM 3 standard. +It is a feature of MQT Core to help with debugging and verification. +MQT Core's QASM export will always include this layout information in the first two lines of the QASM string. +MQT Core's QASM import will parse these lines and set the initial layout and output permutation accordingly. +::: + +## Operations + +The operations in a {py:class}`~mqt.core.ir.QuantumComputation` object are of type {py:class}`~mqt.core.ir.operations.Operation`. +Every type of operation in `mqt-core` is derived from this class. +Operations can also be explicitly constructed. +Each {py:class}`~mqt.core.ir.operations.Operation` has a type in the form of an {py:class}`~mqt.core.ir.operations.OpType`. + +### `StandardOperation` + +A {py:class}`~mqt.core.ir.operations.StandardOperation` is used to represent basic unitary gates. +These can also be declared with arbitrarily many controls. + +```{code-cell} ipython3 +from mqt.core.ir.operations import OpType, StandardOperation, Control + +# u3 gate on qubit 0 +u_gate = StandardOperation(target=0, params=[pi / 4, pi, -pi / 2], op_type=OpType.u) + +# controlled x-rotation +crx = StandardOperation(control=Control(0), target=1, params=[pi], op_type=OpType.rx) + +# multi-controlled x-gate +mcx = StandardOperation(controls={Control(0), Control(1)}, target=2, op_type=OpType.x) + +# add operations to a quantum computation +qc = QuantumComputation(3) +qc.append(u_gate) +qc.append(crx) +qc.append(mcx) + +print(qc) +``` + +### `NonUnitaryOperation` + +A {py:class}`~mqt.core.ir.operations.NonUnitaryOperation` is used to represent operations involving measurements or resets. + +```{code-cell} ipython3 +from mqt.core.ir.operations import NonUnitaryOperation + +nqubits = 2 +qc = QuantumComputation(nqubits, nqubits) +qc.h(0) + +# measure qubit 0 on classical bit 0 +meas_0 = NonUnitaryOperation(target=0, classic=0) + +# reset all qubits +reset = NonUnitaryOperation(targets=[0, 1], op_type=OpType.reset) + +qc.append(meas_0) +qc.append(reset) + +print(qc) +``` + +### `SymbolicOperation` + +A {py:class}`~mqt.core.ir.operations.SymbolicOperation` can represent all gates of a {py:class}`~mqt.core.ir.operations.StandardOperation` but the gate parameters can be symbolic. +Symbolic expressions are represented in MQT using the {py:class}`~mqt.core.ir.symbolic.Expression` type, which represent linear combinations of symbolic {py:class}`~mqt.core.ir.symbolic.Term` objects over some set of {py:class}`~mqt.core.ir.symbolic.Variable` objects. + +```{code-cell} ipython3 +from mqt.core.ir.operations import SymbolicOperation +from mqt.core.ir.symbolic import Expression, Term, Variable + +x = Variable("x") +y = Variable("y") +sym = Expression([Term(x, 2), Term(y, 3)]) +print(sym) + +sym += 1 +print(sym) + +# Create symbolic gate +u1_symb = SymbolicOperation(target=0, params=[sym], op_type=OpType.p) + +# Mixed symbolic and instantiated parameters +u2_symb = SymbolicOperation(target=0, params=[sym, 2.0], op_type=OpType.u2) +``` + +### `CompoundOperation` + +A {py:class}`~mqt.core.ir.operations.CompoundOperation` bundles multiple {py:class}`~mqt.core.ir.operations.Operation` objects together. + +```{code-cell} ipython3 +from mqt.core.ir.operations import CompoundOperation + +comp_op = CompoundOperation() + +# create bell pair circuit +comp_op.append(StandardOperation(0, op_type=OpType.h)) +comp_op.append(StandardOperation(target=0, control=Control(1), op_type=OpType.x)) + +qc = QuantumComputation(2) +qc.append(comp_op) + +print(qc) +``` + +Circuits can be conveniently turned into operations which allows to create nested circuits: + +```{code-cell} ipython3 +nqubits = 2 +comp = QuantumComputation(nqubits) +comp.h(0) +comp.cx(0, 1) + +qc = QuantumComputation(nqubits) +qc.append(comp.to_operation()) + +print(qc) +``` + +### `ClassicControlledOperation` + +A {py:class}`~mqt.core.ir.operations.ClassicControlledOperation` is a controlled operation where the control is a classical bit or a classical register. + +```{code-cell} ipython3 +from mqt.core.ir.operations import ClassicControlledOperation + +qc = QuantumComputation(1, 1) + +qc.h(0) +qc.measure(0, 0) + +classic_controlled = ClassicControlledOperation(operation=StandardOperation(target=0, op_type=OpType.x), control_register=(0, 1)) +qc.append(classic_controlled) + +print(qc) +``` + +## Interfacing with other SDKs + +Since a {py:class}`~mqt.core.ir.QuantumComputation` can be imported from and exported to an OpenQASM 3.0 (or OpenQASM 2.0) string, any library that can work with OpenQASM is easy to use in conjunction with the {py:class}`~mqt.core.ir.QuantumComputation` class. + +In addition, `mqt-core` can import [Qiskit](https://qiskit.org/) {py:class}`~qiskit.circuit.QuantumCircuit` objects directly. + +```{code-cell} ipython3 +from qiskit import QuantumCircuit + +from mqt.core.plugins.qiskit import qiskit_to_mqt + +# GHZ circuit in qiskit +qiskit_qc = QuantumCircuit(3) +qiskit_qc.h(0) +qiskit_qc.cx(0, 1) +qiskit_qc.cx(0, 2) + +qiskit_qc.draw(output="mpl", style="iqp") +``` + +```{code-cell} ipython3 +mqt_qc = qiskit_to_mqt(qiskit_qc) +print(mqt_qc) +``` diff --git a/docs/quickstart.md b/docs/quickstart.md deleted file mode 100644 index d9691f5d6..000000000 --- a/docs/quickstart.md +++ /dev/null @@ -1,271 +0,0 @@ ---- -file_format: mystnb -kernelspec: - name: python3 -mystnb: - number_source_lines: true ---- - -```{code-cell} ipython3 -:tags: [remove-cell] -%config InlineBackend.figure_formats = ['svg'] -``` - -# Quickstart - -The central interface for working with quantum circuits in the Munich Quantum Toolkit is the {py:class}`~mqt.core.ir.QuantumComputation` class. -It represents quantum circuits as a sequential list of operations. -Operations can be directly applied to the {py:class}`~mqt.core.ir.QuantumComputation`: - -```{code-cell} ipython3 -from mqt.core import QuantumComputation - -# Build a `QuantumComputation` representing a Bell-state preparation circuit. -nqubits = 2 -qc = QuantumComputation(nqubits) - -qc.h(0) # Apply Hadamard gate on qubit 0 -qc.cx(0, 1) # Apply a CNOT (controlled X-Gate) with control on qubit 0 and target on qubit 1 - -# Get Circuit in OpenQASM 3.0 format -print(qc.qasm3_str()) -``` - -The circuit class provides a lot of flexibility as every unitary gate can be declared as a controlled gate: - -```{code-cell} ipython3 -from mqt.core.ir.operations import Control - -nqubits = 2 -qc = QuantumComputation(nqubits) - -# Controlled Hadamard Gate -qc.ch(0, 1) - -# Negatively controlled S-gate: S-Gate on target is performed if control is in |0> state. -qc.cs(Control(0, Control.Type.Neg), 1) - -print(qc.qasm3_str()) -``` - -Providing a set of `Control` objects allows declaring any (unitary) gate as a multi-controlled gate: - -```{code-cell} ipython3 -nqubits = 3 -qc = QuantumComputation(nqubits) - -# Toffoli gate in mqt-core: -qc.mcx({0, 1}, 2) - -# Control type can be individually declared -qc.mcs({Control(0, Control.Type.Neg), Control(1, Control.Type.Pos)}, 2) - -print(qc.qasm3_str()) -``` - -## Layout Information - -A {py:class}`~mqt.core.ir.QuantumComputation` also contains information about the mapping of algorithmic (or logical/virtual/circuit) qubits to and from device (or physical) qubits. -These are contained in the {py:attr}`~mqt.core.ir.QuantumComputation.initial_layout` and {py:attr}`~mqt.core.ir.QuantumComputation.output_permutation` members which are instances of the {py:class}`~mqt.core.ir.Permutation` class. If no layout is given the trivial layout is assumed. - -When printing the OpenQASM representation of the {py:class}`~mqt.core.ir.QuantumComputation` the input and output permutations are given as comments in the first two lines of the QASM string. The format is: - -`// i Q_0, Q_1, ..., Q_n` ... algorithmic qubit $i$ is mapped to device qubit $Q_i$. - -`// o Q_0, Q_1, ..., Q_n` ... the value of algorithmic qubit $i$ (assumed to be stored in classical bit $c[i]$) is measured at device qubit $Q_i$. - -```{code-cell} ipython3 -nqubits = 3 -qc = QuantumComputation(nqubits) -qc.initial_layout[2] = 0 -qc.initial_layout[0] = 1 -qc.initial_layout[1] = 2 - -qc.output_permutation[2] = 0 -qc.output_permutation[0] = 1 -qc.output_permutation[1] = 2 - - -print(qc.qasm3_str()) -``` - -The layout information can also be automatically determined from measurements -using the {py:meth}`~mqt.core.ir.QuantumComputation.initialize_io_mapping` method: - -```{code-cell} ipython3 -nqubits = 3 -qc = QuantumComputation(nqubits, nqubits) # 3 qubits, 3 classical bits - -qc.h(0) -qc.x(1) -qc.s(2) -qc.measure(1, 0) # c[0] is measured at qubit 1 -qc.measure(2, 1) # c[1] is measured at qubit 2 -qc.measure(0, 2) # c[2] is measured at qubit 0 -qc.initialize_io_mapping() # determine permutation from measurement - -print(qc.qasm3_str()) -``` - -## Visualizing Circuits - -Circuits can be printed in a human-readable format: - -```{code-cell} ipython3 -from mqt.core import QuantumComputation - -nqubits = 2 -qc = QuantumComputation(nqubits, 1) - -qc.h(0) -qc.cx(0, 1) -qc.measure(1, 0) - -print(qc) -``` - -## Operations - -The operations in a {py:class}`~mqt.core.ir.QuantumComputation` object are of type {py:class}`~mqt.core.ir.operations.Operation`. -Every type of operation in `mqt-core` is derived from this class. -Operations can also be explicitly constructed. -Each {py:class}`~mqt.core.ir.operations.Operation` has a type in the form of an {py:class}`~mqt.core.ir.operations.OpType`. - -### `StandardOperation` - -A {py:class}`~mqt.core.ir.operations.StandardOperation` is used to represent basic unitary gates. These can also be declared with arbitrary targets and controls. - -```{code-cell} ipython3 -from math import pi - -from mqt.core.ir.operations import OpType, StandardOperation - -nqubits = 3 - -# u3 gate on qubit 0 in a 3-qubit circuit -u_gate = StandardOperation(target=0, params=[pi / 4, pi, -pi / 2], op_type=OpType.u) - -# controlled x-rotation -crx = StandardOperation(control=Control(0), target=1, params=[pi], op_type=OpType.rx) - -# multi-controlled x-gate -mcx = StandardOperation(controls={Control(0), Control(1)}, target=2, op_type=OpType.x) - -# add operations to a quantum computation -qc = QuantumComputation(nqubits) -qc.append(u_gate) -qc.append(crx) -qc.append(mcx) - -print(qc) -``` - -### `NonUnitaryOperation` - -A {py:class}`~mqt.core.ir.operations.NonUnitaryOperation` is used to represent operations involving measurements or resets. - -```{code-cell} ipython3 -from mqt.core.ir.operations import NonUnitaryOperation - -nqubits = 2 -qc = QuantumComputation(nqubits, nqubits) -qc.h(0) - -# measure qubit 0 on classical bit 0 -meas_0 = NonUnitaryOperation(target=0, classic=0) - -# reset all qubits -reset = NonUnitaryOperation(targets=[0, 1], op_type=OpType.reset) - -qc.append(meas_0) -qc.append(reset) - -print(qc.qasm3_str()) -``` - -### `SymbolicOperation` - -A {py:class}`~mqt.core.ir.operations.SymbolicOperation` can represent all gates of a {py:class}`~mqt.core.ir.operations.StandardOperation` but the gate parameters can be symbolic. -Symbolic expressions are represented in MQT using the {py:class}`~mqt.core.ir.symbolic.Expression` type, which represent linear combinations of symbolic {py:class}`~mqt.core.ir.symbolic.Term` objects over some set of {py:class}`~mqt.core.ir.symbolic.Variable` objects. - -```{code-cell} ipython3 -from mqt.core.ir.operations import SymbolicOperation -from mqt.core.ir.symbolic import Expression, Term, Variable - -nqubits = 1 - -x = Variable("x") -y = Variable("y") -sym = Expression([Term(x, 2), Term(y, 3)]) -print(sym) - -sym += 1 -print(sym) - -# Create symbolic gate -u1_symb = SymbolicOperation(target=0, params=[sym], op_type=OpType.p) - -# Mixed symbolic and instantiated parameters -u2_symb = SymbolicOperation(target=0, params=[sym, 2.0], op_type=OpType.u2) -``` - -### `CompoundOperation` - -A {py:class}`~mqt.core.ir.operations.CompoundOperation` bundles multiple {py:class}`~mqt.core.ir.operations.Operation` objects together. - -```{code-cell} ipython3 -from mqt.core.ir.operations import CompoundOperation - -nqubits = 2 -comp_op = CompoundOperation() - -# create bell pair circuit -comp_op.append(StandardOperation(0, op_type=OpType.h)) -comp_op.append(StandardOperation(target=0, control=Control(1), op_type=OpType.x)) - -qc = QuantumComputation(nqubits) -qc.append(comp_op) - -print(qc) -``` - -Circuits can be conveniently turned into operations which allows to create nested circuits: - -```{code-cell} ipython3 -from mqt.core import QuantumComputation - -nqubits = 2 -comp = QuantumComputation(nqubits) -comp.h(0) -comp.cx(0, 1) - -qc = QuantumComputation(nqubits) -qc.append(comp.to_operation()) - -print(qc) -``` - -## Interfacing with other SDKs - -Since a {py:class}`~mqt.core.ir.QuantumComputation` can be imported from and exported to an OpenQASM 3.0 (or OpenQASM 2.0) string, any library that can work with OpenQASM is easy to use in conjunction with the {py:class}`~mqt.core.ir.QuantumComputation` class. - -In addition, `mqt-core` can import [Qiskit](https://qiskit.org/) {py:class}`~qiskit.circuit.QuantumCircuit` objects directly. - -```{code-cell} ipython3 -from qiskit import QuantumCircuit - -from mqt.core.plugins.qiskit import qiskit_to_mqt - -# GHZ circuit in qiskit -qiskit_qc = QuantumCircuit(3) -qiskit_qc.h(0) -qiskit_qc.cx(0, 1) -qiskit_qc.cx(0, 2) - -qiskit_qc.draw(output="mpl", style="iqp") -``` - -```{code-cell} ipython3 -mqt_qc = qiskit_to_mqt(qiskit_qc) -print(mqt_qc) -``` From 5ca5391ccb86d2c4a9ce3699f49fed513b182403 Mon Sep 17 00:00:00 2001 From: burgholzer Date: Fri, 3 Jan 2025 19:37:27 +0100 Subject: [PATCH 7/7] =?UTF-8?q?=F0=9F=A9=B9=20avoid=20unicode=20characters?= =?UTF-8?q?=20in=20docs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: burgholzer --- docs/installation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/installation.md b/docs/installation.md index 7a470169f..c1b73f246 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -213,7 +213,7 @@ concurrency: jobs: update-mqt-core: - name: ⬆️ Update MQT Core + name: Update MQT Core uses: cda-tum/mqt-workflows/.github/workflows/reusable-mqt-core-update.yml@v1.5 with: update-to-head: ${{ github.event.inputs.update-to-head == 'true' }}