parityos-cudaq 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- parityos_cudaq-0.1.0/.gitignore +96 -0
- parityos_cudaq-0.1.0/CHANGELOG.md +13 -0
- parityos_cudaq-0.1.0/LICENSE.txt +37 -0
- parityos_cudaq-0.1.0/PKG-INFO +121 -0
- parityos_cudaq-0.1.0/README.md +103 -0
- parityos_cudaq-0.1.0/pyproject.toml +40 -0
- parityos_cudaq-0.1.0/src/parityos_cudaq/__init__.py +3 -0
- parityos_cudaq-0.1.0/src/parityos_cudaq/_version.py +3 -0
- parityos_cudaq-0.1.0/src/parityos_cudaq/exporter.py +366 -0
- parityos_cudaq-0.1.0/src/parityos_cudaq/py.typed +0 -0
- parityos_cudaq-0.1.0/test_parityos_cudaq/__init__.py +0 -0
- parityos_cudaq-0.1.0/test_parityos_cudaq/quake_circuits.py +294 -0
- parityos_cudaq-0.1.0/test_parityos_cudaq/test_quake_exporter.py +87 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# Distribution / packaging
|
|
7
|
+
.Python
|
|
8
|
+
build/
|
|
9
|
+
develop-eggs/
|
|
10
|
+
dist/
|
|
11
|
+
downloads/
|
|
12
|
+
eggs/
|
|
13
|
+
.eggs/
|
|
14
|
+
lib/
|
|
15
|
+
lib64/
|
|
16
|
+
parts/
|
|
17
|
+
sdist/
|
|
18
|
+
var/
|
|
19
|
+
wheels/
|
|
20
|
+
pip-wheel-metadata/
|
|
21
|
+
share/python-wheels/
|
|
22
|
+
*.egg-info/
|
|
23
|
+
.installed.cfg
|
|
24
|
+
*.egg
|
|
25
|
+
MANIFEST
|
|
26
|
+
|
|
27
|
+
# Installer logs
|
|
28
|
+
pip-log.txt
|
|
29
|
+
pip-delete-this-directory.txt
|
|
30
|
+
|
|
31
|
+
# Unit test / coverage reports
|
|
32
|
+
htmlcov/
|
|
33
|
+
.tox/
|
|
34
|
+
.nox/
|
|
35
|
+
.coverage
|
|
36
|
+
.coverage.*
|
|
37
|
+
.cache
|
|
38
|
+
coverage.xml
|
|
39
|
+
report.xml
|
|
40
|
+
*.cover
|
|
41
|
+
*.py,cover
|
|
42
|
+
.hypothesis/
|
|
43
|
+
.pytest_cache/
|
|
44
|
+
|
|
45
|
+
# Sphinx documentation
|
|
46
|
+
**/docs/_build/
|
|
47
|
+
**/docs/**/generated/
|
|
48
|
+
|
|
49
|
+
# Jupyter Notebook
|
|
50
|
+
.ipynb_checkpoints
|
|
51
|
+
|
|
52
|
+
# pyenv
|
|
53
|
+
.python-version
|
|
54
|
+
|
|
55
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
|
|
56
|
+
__pypackages__/
|
|
57
|
+
|
|
58
|
+
# Celery
|
|
59
|
+
celerybeat-schedule
|
|
60
|
+
celerybeat.pid
|
|
61
|
+
|
|
62
|
+
# Environments
|
|
63
|
+
*.env
|
|
64
|
+
.venv
|
|
65
|
+
env/
|
|
66
|
+
venv/
|
|
67
|
+
ENV/
|
|
68
|
+
env.bak/
|
|
69
|
+
venv.bak/
|
|
70
|
+
uv.lock
|
|
71
|
+
|
|
72
|
+
# mypy
|
|
73
|
+
.mypy_cache/
|
|
74
|
+
.dmypy.json
|
|
75
|
+
dmypy.json
|
|
76
|
+
|
|
77
|
+
# Pyre type checker
|
|
78
|
+
.pyre/
|
|
79
|
+
|
|
80
|
+
# IDE stuff
|
|
81
|
+
*.iws
|
|
82
|
+
*.iml
|
|
83
|
+
**/.idea/
|
|
84
|
+
**/.idea/**
|
|
85
|
+
**/.vscode
|
|
86
|
+
|
|
87
|
+
# Apple stuff
|
|
88
|
+
.DS_Store
|
|
89
|
+
|
|
90
|
+
# artifacts
|
|
91
|
+
.png
|
|
92
|
+
.tar
|
|
93
|
+
|
|
94
|
+
# uv
|
|
95
|
+
uv.toml
|
|
96
|
+
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
## [0.1.0] - 2026-06-10
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- Separate packages for each exporter (#132)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
ParityOS Client Software License Terms (BSD-3-Clause)
|
|
2
|
+
=====================================================
|
|
3
|
+
|
|
4
|
+
Copyright 2023 Parity Quantum Computing GmbH (ParityQC)
|
|
5
|
+
|
|
6
|
+
Redistribution and use in source and binary forms, with or without
|
|
7
|
+
modification, are permitted provided that the following conditions are met:
|
|
8
|
+
|
|
9
|
+
1. Redistributions of source code must retain the above copyright notice,
|
|
10
|
+
this list of conditions and the following disclaimer.
|
|
11
|
+
|
|
12
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
13
|
+
this list of conditions and the following disclaimer in the documentation
|
|
14
|
+
and/or other materials provided with the distribution.
|
|
15
|
+
|
|
16
|
+
3. Neither the name of the copyright holder nor the names of its contributors
|
|
17
|
+
may be used to endorse or promote products derived from this software
|
|
18
|
+
without specific prior written permission.
|
|
19
|
+
|
|
20
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS”
|
|
21
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
22
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
23
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
24
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
25
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
26
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
27
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
28
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
29
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
30
|
+
|
|
31
|
+
===============================================================================
|
|
32
|
+
|
|
33
|
+
Parity Quantum Computing GmbH
|
|
34
|
+
Rennweg 1 / Top 314 / 6020 Innsbruck, Austria
|
|
35
|
+
info@parityqc.com / www.parityqc.com
|
|
36
|
+
|
|
37
|
+
===============================================================================
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: parityos-cudaq
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: CudaQ extension for ParityOS
|
|
5
|
+
Project-URL: Homepage, https://parityqc.com/
|
|
6
|
+
Author-email: ParityQC <parityos@parityqc.com>
|
|
7
|
+
License-Expression: BSD-3-Clause
|
|
8
|
+
License-File: LICENSE.txt
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
13
|
+
Classifier: Typing :: Typed
|
|
14
|
+
Requires-Python: >=3.11
|
|
15
|
+
Requires-Dist: attrs
|
|
16
|
+
Requires-Dist: parityos>=3.0.0
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
|
|
19
|
+
# ParityOS CudaQ
|
|
20
|
+
|
|
21
|
+
ParityOS extension adding an interface to [cudaq](https://developer.nvidia.com/cuda-q).
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
It is recommended to install this package in a separate Python virtual environment. For example:
|
|
26
|
+
|
|
27
|
+
```shell
|
|
28
|
+
# To create a standard Python virtual environment:
|
|
29
|
+
python -m venv my_new_venv && source my_new_venv/bin/activate
|
|
30
|
+
# Alternatively, to create a Anaconda/Miniconda environment:
|
|
31
|
+
conda create --name my_new_conda_env python=<version> && conda activate my_new_conda_env
|
|
32
|
+
# or a pyenv environment
|
|
33
|
+
pyenv virtualenv <version> my_new_venv && pyenv activate my_new_venv
|
|
34
|
+
# or a uv managed environment
|
|
35
|
+
uv venv -p <version>
|
|
36
|
+
```
|
|
37
|
+
where `<version>` is a python version and one of `[3.11, 3.12, 3.13]`.
|
|
38
|
+
|
|
39
|
+
After activating the virtual environment, install via your favorite package manager, e.g.:
|
|
40
|
+
|
|
41
|
+
```shell
|
|
42
|
+
# using pip
|
|
43
|
+
pip install parityos-cudaq
|
|
44
|
+
# using uv
|
|
45
|
+
uv pip install parityos-cudaq
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Exporter
|
|
49
|
+
|
|
50
|
+
`parityos-cudaq` offers an exporter of ParityOS to quantum circuits to the
|
|
51
|
+
[Quake dialect](https://nvidia.github.io/cuda-quantum/latest/specification/quake-dialect.html) .
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
from parityos.bits import get_q
|
|
55
|
+
from parityos.operators.circuit import Circuit
|
|
56
|
+
from parityos.operators.controlled_operator import CNOT
|
|
57
|
+
from parityos.operators.elementary_operator import H, Z
|
|
58
|
+
from parityos.operators.rotation_operator import RX
|
|
59
|
+
from parityos_cudaq.exporter import export_parityos_to_quake
|
|
60
|
+
|
|
61
|
+
# Create circuit qubits.
|
|
62
|
+
qubits = [get_q(name) for name in ("a", "b", "c")]
|
|
63
|
+
|
|
64
|
+
# Create the following circuit as sequence of gates, followed by a measurement of all qubits.
|
|
65
|
+
# ┌───────┐┌───┐┌───┐ ┌─┐
|
|
66
|
+
# qa: ┤ Rx(2) ├┤ H ├┤ Z ├───┤M├
|
|
67
|
+
# └───────┘├───┤└┬─┬┘ └╥┘
|
|
68
|
+
# qb: ─────────┤ X ├─┤M├─────╫─
|
|
69
|
+
# ┌───┐ └─┬─┘ └╥┘ ┌─┐ ║
|
|
70
|
+
# qc: ──┤ H ├────■────╫──┤M├─╫─
|
|
71
|
+
# └───┘ ║ └╥┘ ║
|
|
72
|
+
# c: 3/═══════════════╩═══╩══╩═
|
|
73
|
+
# 1 2 0
|
|
74
|
+
circuit = Circuit(
|
|
75
|
+
[
|
|
76
|
+
RX(qubits[0], 2),
|
|
77
|
+
H(qubits[0]),
|
|
78
|
+
H(qubits[2]),
|
|
79
|
+
CNOT(qubits[2], qubits[1]),
|
|
80
|
+
Z(qubits[0]),
|
|
81
|
+
]
|
|
82
|
+
).measure_all()
|
|
83
|
+
|
|
84
|
+
# Export to quake.
|
|
85
|
+
qiskit_result = export_parityos_to_quake(circuit, False)
|
|
86
|
+
|
|
87
|
+
print(qiskit_result.module)
|
|
88
|
+
# // Generated by ParityOS
|
|
89
|
+
# module {
|
|
90
|
+
# func.func @parityos_circuit() {
|
|
91
|
+
# // Allocate register
|
|
92
|
+
# %n_qubits = arith.constant 3 : i64
|
|
93
|
+
# %register = quake.alloca !quake.veq<?>[%n_qubits : i64]
|
|
94
|
+
# %q_0 = quake.extract_ref %register[0] : (!quake.veq<?>) -> !quake.ref
|
|
95
|
+
# %q_1 = quake.extract_ref %register[1] : (!quake.veq<?>) -> !quake.ref
|
|
96
|
+
# %q_2 = quake.extract_ref %register[2] : (!quake.veq<?>) -> !quake.ref
|
|
97
|
+
#
|
|
98
|
+
# %angle_0 = arith.constant 2.0 : f64
|
|
99
|
+
# quake.rx (%angle_0) %q_0 : (f64, !quake.ref) -> ()
|
|
100
|
+
# quake.h %q_0 : (!quake.ref) -> ()
|
|
101
|
+
# quake.h %q_2 : (!quake.ref) -> ()
|
|
102
|
+
# quake.x [%q_2] %q_1 : (!quake.ref, !quake.ref) -> ()
|
|
103
|
+
# quake.z %q_0 : (!quake.ref) -> ()
|
|
104
|
+
# %m_result_0 = quake.mz %q_0 : (!quake.ref) -> !quake.measure
|
|
105
|
+
#
|
|
106
|
+
# %m_result_1 = quake.mz %q_1 : (!quake.ref) -> !quake.measure
|
|
107
|
+
#
|
|
108
|
+
# %m_result_2 = quake.mz %q_2 : (!quake.ref) -> !quake.measure
|
|
109
|
+
#
|
|
110
|
+
# return
|
|
111
|
+
# }
|
|
112
|
+
# }
|
|
113
|
+
|
|
114
|
+
print(qiskit_result.qubit_map) # Maps ParityOS qubits to quake Qubit integer labels
|
|
115
|
+
# {Qubit(id='a'): 0, Qubit(id='b'): 1, Qubit(id='c'): 2}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## License
|
|
119
|
+
|
|
120
|
+
This software package is made available under the 3-Clause BSD License.
|
|
121
|
+
See `License.txt` for details.
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# ParityOS CudaQ
|
|
2
|
+
|
|
3
|
+
ParityOS extension adding an interface to [cudaq](https://developer.nvidia.com/cuda-q).
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
It is recommended to install this package in a separate Python virtual environment. For example:
|
|
8
|
+
|
|
9
|
+
```shell
|
|
10
|
+
# To create a standard Python virtual environment:
|
|
11
|
+
python -m venv my_new_venv && source my_new_venv/bin/activate
|
|
12
|
+
# Alternatively, to create a Anaconda/Miniconda environment:
|
|
13
|
+
conda create --name my_new_conda_env python=<version> && conda activate my_new_conda_env
|
|
14
|
+
# or a pyenv environment
|
|
15
|
+
pyenv virtualenv <version> my_new_venv && pyenv activate my_new_venv
|
|
16
|
+
# or a uv managed environment
|
|
17
|
+
uv venv -p <version>
|
|
18
|
+
```
|
|
19
|
+
where `<version>` is a python version and one of `[3.11, 3.12, 3.13]`.
|
|
20
|
+
|
|
21
|
+
After activating the virtual environment, install via your favorite package manager, e.g.:
|
|
22
|
+
|
|
23
|
+
```shell
|
|
24
|
+
# using pip
|
|
25
|
+
pip install parityos-cudaq
|
|
26
|
+
# using uv
|
|
27
|
+
uv pip install parityos-cudaq
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Exporter
|
|
31
|
+
|
|
32
|
+
`parityos-cudaq` offers an exporter of ParityOS to quantum circuits to the
|
|
33
|
+
[Quake dialect](https://nvidia.github.io/cuda-quantum/latest/specification/quake-dialect.html) .
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
from parityos.bits import get_q
|
|
37
|
+
from parityos.operators.circuit import Circuit
|
|
38
|
+
from parityos.operators.controlled_operator import CNOT
|
|
39
|
+
from parityos.operators.elementary_operator import H, Z
|
|
40
|
+
from parityos.operators.rotation_operator import RX
|
|
41
|
+
from parityos_cudaq.exporter import export_parityos_to_quake
|
|
42
|
+
|
|
43
|
+
# Create circuit qubits.
|
|
44
|
+
qubits = [get_q(name) for name in ("a", "b", "c")]
|
|
45
|
+
|
|
46
|
+
# Create the following circuit as sequence of gates, followed by a measurement of all qubits.
|
|
47
|
+
# ┌───────┐┌───┐┌───┐ ┌─┐
|
|
48
|
+
# qa: ┤ Rx(2) ├┤ H ├┤ Z ├───┤M├
|
|
49
|
+
# └───────┘├───┤└┬─┬┘ └╥┘
|
|
50
|
+
# qb: ─────────┤ X ├─┤M├─────╫─
|
|
51
|
+
# ┌───┐ └─┬─┘ └╥┘ ┌─┐ ║
|
|
52
|
+
# qc: ──┤ H ├────■────╫──┤M├─╫─
|
|
53
|
+
# └───┘ ║ └╥┘ ║
|
|
54
|
+
# c: 3/═══════════════╩═══╩══╩═
|
|
55
|
+
# 1 2 0
|
|
56
|
+
circuit = Circuit(
|
|
57
|
+
[
|
|
58
|
+
RX(qubits[0], 2),
|
|
59
|
+
H(qubits[0]),
|
|
60
|
+
H(qubits[2]),
|
|
61
|
+
CNOT(qubits[2], qubits[1]),
|
|
62
|
+
Z(qubits[0]),
|
|
63
|
+
]
|
|
64
|
+
).measure_all()
|
|
65
|
+
|
|
66
|
+
# Export to quake.
|
|
67
|
+
qiskit_result = export_parityos_to_quake(circuit, False)
|
|
68
|
+
|
|
69
|
+
print(qiskit_result.module)
|
|
70
|
+
# // Generated by ParityOS
|
|
71
|
+
# module {
|
|
72
|
+
# func.func @parityos_circuit() {
|
|
73
|
+
# // Allocate register
|
|
74
|
+
# %n_qubits = arith.constant 3 : i64
|
|
75
|
+
# %register = quake.alloca !quake.veq<?>[%n_qubits : i64]
|
|
76
|
+
# %q_0 = quake.extract_ref %register[0] : (!quake.veq<?>) -> !quake.ref
|
|
77
|
+
# %q_1 = quake.extract_ref %register[1] : (!quake.veq<?>) -> !quake.ref
|
|
78
|
+
# %q_2 = quake.extract_ref %register[2] : (!quake.veq<?>) -> !quake.ref
|
|
79
|
+
#
|
|
80
|
+
# %angle_0 = arith.constant 2.0 : f64
|
|
81
|
+
# quake.rx (%angle_0) %q_0 : (f64, !quake.ref) -> ()
|
|
82
|
+
# quake.h %q_0 : (!quake.ref) -> ()
|
|
83
|
+
# quake.h %q_2 : (!quake.ref) -> ()
|
|
84
|
+
# quake.x [%q_2] %q_1 : (!quake.ref, !quake.ref) -> ()
|
|
85
|
+
# quake.z %q_0 : (!quake.ref) -> ()
|
|
86
|
+
# %m_result_0 = quake.mz %q_0 : (!quake.ref) -> !quake.measure
|
|
87
|
+
#
|
|
88
|
+
# %m_result_1 = quake.mz %q_1 : (!quake.ref) -> !quake.measure
|
|
89
|
+
#
|
|
90
|
+
# %m_result_2 = quake.mz %q_2 : (!quake.ref) -> !quake.measure
|
|
91
|
+
#
|
|
92
|
+
# return
|
|
93
|
+
# }
|
|
94
|
+
# }
|
|
95
|
+
|
|
96
|
+
print(qiskit_result.qubit_map) # Maps ParityOS qubits to quake Qubit integer labels
|
|
97
|
+
# {Qubit(id='a'): 0, Qubit(id='b'): 1, Qubit(id='c'): 2}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## License
|
|
101
|
+
|
|
102
|
+
This software package is made available under the 3-Clause BSD License.
|
|
103
|
+
See `License.txt` for details.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "parityos-cudaq"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "CudaQ extension for ParityOS"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "BSD-3-Clause"
|
|
11
|
+
license-files = ["LICENSE.txt"]
|
|
12
|
+
requires-python = ">=3.11"
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Programming Language :: Python :: 3",
|
|
15
|
+
"Programming Language :: Python :: 3.11",
|
|
16
|
+
"Programming Language :: Python :: 3.12",
|
|
17
|
+
"Programming Language :: Python :: 3.13",
|
|
18
|
+
"Typing :: Typed",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
dependencies = [
|
|
22
|
+
"attrs",
|
|
23
|
+
"parityos>=3.0.0",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
[dependency-groups]
|
|
27
|
+
dev = [
|
|
28
|
+
"filecheck",
|
|
29
|
+
"cudaq",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
[[project.authors]]
|
|
33
|
+
name = "ParityQC"
|
|
34
|
+
email = "parityos@parityqc.com"
|
|
35
|
+
|
|
36
|
+
[project.urls]
|
|
37
|
+
Homepage = "https://parityqc.com/"
|
|
38
|
+
|
|
39
|
+
[tool.hatch.version]
|
|
40
|
+
path = "src/parityos_cudaq/_version.py"
|
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
"""Exporter for translating ParityOS circuits to the Nvidia CUDA-Q Quake MLIR dialect."""
|
|
2
|
+
# ParityQC © 2020-2026. See the LICENSE file in the top level directory for details.
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from types import MappingProxyType
|
|
7
|
+
from typing import Generic, final
|
|
8
|
+
|
|
9
|
+
from attrs import frozen
|
|
10
|
+
from parityos.bits import Qubit, QubitT
|
|
11
|
+
from parityos.operators.circuit import CircuitLike
|
|
12
|
+
from parityos.operators.conditional_operator import ConditionalOperator
|
|
13
|
+
from parityos.operators.controlled_operator import ControlledOperator
|
|
14
|
+
from parityos.operators.measurement import MX, MZ, Measurement, Reset
|
|
15
|
+
from parityos.operators.operator import Operator
|
|
16
|
+
from parityos.operators.pauli_basis import XBasis, ZBasis
|
|
17
|
+
from parityos.operators.rotation_operator import HasAngle
|
|
18
|
+
from parityos.utils.exceptions import ParityOSNotSupportedError
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@frozen
|
|
22
|
+
class QuakeExportResult:
|
|
23
|
+
"""Result of exporting a ParityOS circuit to a Quake IR program.
|
|
24
|
+
|
|
25
|
+
The exported module will extract qubit reference like this:
|
|
26
|
+
```
|
|
27
|
+
%q_0 = quake.extract_ref %register[0] : (!quake.veq<?>) -> !quake.ref
|
|
28
|
+
^^^^ ^
|
|
29
|
+
insignificant variable name meaningful register index
|
|
30
|
+
```
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
#: Exported Quake program.
|
|
34
|
+
module: str
|
|
35
|
+
#: Mapping from parityos qubits to their indices in the quake register.
|
|
36
|
+
qubit_map: dict[Qubit, int]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def export_parityos_to_quake(
|
|
40
|
+
parityos_circuit: CircuitLike[Qubit], emulate_nvqpp: bool
|
|
41
|
+
) -> QuakeExportResult:
|
|
42
|
+
"""Export a ParityOS circuit to Quake.
|
|
43
|
+
|
|
44
|
+
If the circuit name is a legal name (contains only alphanumerics and underscores, but does not
|
|
45
|
+
start with a numeric), the ``func.func`` is given the circuit name as symbol name, prefixed with
|
|
46
|
+
"@". Otherwise, the ``func.func`` symbol name defaults to ``@parityos_circuit``.
|
|
47
|
+
|
|
48
|
+
Example:
|
|
49
|
+
Bell state circuit:
|
|
50
|
+
```
|
|
51
|
+
module {
|
|
52
|
+
func.func @parityos_circuit() {
|
|
53
|
+
// Allocate register
|
|
54
|
+
%n_qubits = arith.constant 2 : i64
|
|
55
|
+
%register = quake.alloca !quake.veq<?>[%n_qubits : i64]
|
|
56
|
+
%q_0 = quake.extract_ref %register[0] : (!quake.veq<?>) -> !quake.ref
|
|
57
|
+
%q_1 = quake.extract_ref %register[1] : (!quake.veq<?>) -> !quake.ref
|
|
58
|
+
|
|
59
|
+
quake.h %q_0 : (!quake.ref) -> ()
|
|
60
|
+
quake.x [%q_0] %q_1 : (!quake.ref, !quake.ref) -> ()
|
|
61
|
+
|
|
62
|
+
%m_result_0 = quake.mz %q_0 : (!quake.ref) -> !quake.measure
|
|
63
|
+
%m_result_1 = quake.mz %q_1 : (!quake.ref) -> !quake.measure
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
parityos_circuit: Circuit to be exported.
|
|
70
|
+
emulate_nvqpp: Emulate the output of the nvqpp compiler from CUDA-Q.
|
|
71
|
+
Enabling this adds some attributes to the output module and function.
|
|
72
|
+
TODO: We do not know yet whether this will be needed in any context.
|
|
73
|
+
May be removed in the future.
|
|
74
|
+
"""
|
|
75
|
+
exporter = _QuakeExporter(emulate_nvqpp)
|
|
76
|
+
exporter.export(parityos_circuit)
|
|
77
|
+
|
|
78
|
+
return QuakeExportResult(
|
|
79
|
+
module=exporter.module,
|
|
80
|
+
qubit_map=exporter.qubit_map,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@final
|
|
85
|
+
class _QuakeExporter(Generic[QubitT]):
|
|
86
|
+
"""Exports a ParityOS circuit to the Cuda-Q Quake IR."""
|
|
87
|
+
|
|
88
|
+
# a map between ParityOS to Quake operations names
|
|
89
|
+
_PARITY_OS_TO_QUAKE = MappingProxyType(
|
|
90
|
+
{
|
|
91
|
+
# 1 target, 0 params:
|
|
92
|
+
"x": "x",
|
|
93
|
+
"y": "y",
|
|
94
|
+
"z": "z",
|
|
95
|
+
"h": "h",
|
|
96
|
+
"s": "s",
|
|
97
|
+
"sdg": "s<adj>",
|
|
98
|
+
"t": "t",
|
|
99
|
+
"tdg": "t<adj>",
|
|
100
|
+
# 1 target, 1 param:
|
|
101
|
+
"rx": "rx",
|
|
102
|
+
"ry": "ry",
|
|
103
|
+
"rz": "rz",
|
|
104
|
+
# 2 targets, 0 params
|
|
105
|
+
"swap": "swap",
|
|
106
|
+
}
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
@staticmethod
|
|
110
|
+
def _lookup_quake_name(parityos_name: str) -> str:
|
|
111
|
+
"""Look up the corresponding Quake operator name for a
|
|
112
|
+
given parityos gate.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
parityos_name: Name of the ParityOS gate to translate into Quake.
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
A string containing the name of the corresponding Quake gate.
|
|
119
|
+
|
|
120
|
+
Raises:
|
|
121
|
+
ParityOSNotSupportedError: When the gate is not supported for export.
|
|
122
|
+
"""
|
|
123
|
+
if parityos_name not in _QuakeExporter._PARITY_OS_TO_QUAKE:
|
|
124
|
+
raise ParityOSNotSupportedError(f"Gate {parityos_name} not supported for Quake export")
|
|
125
|
+
|
|
126
|
+
return _QuakeExporter._PARITY_OS_TO_QUAKE[parityos_name]
|
|
127
|
+
|
|
128
|
+
def __init__(self, emulate_nvqpp: bool) -> None:
|
|
129
|
+
"""Create a new Quake Exporter object.
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
emulate_nvqpp: Emulate the output of the nvqpp compiler from CUDA-Q.
|
|
133
|
+
Enabling this adds some attributes to the output module and function.
|
|
134
|
+
"""
|
|
135
|
+
self._module: str = ""
|
|
136
|
+
self._qubit_map: dict[QubitT, str] = dict()
|
|
137
|
+
self._qubit_index_map: dict[QubitT, int] = dict()
|
|
138
|
+
self._emulate_nvqpp = emulate_nvqpp
|
|
139
|
+
self._indentation = " " * 4
|
|
140
|
+
self._angle_count = 0
|
|
141
|
+
self._measurement_count = 0
|
|
142
|
+
|
|
143
|
+
@property
|
|
144
|
+
def module(self) -> str:
|
|
145
|
+
return self._module
|
|
146
|
+
|
|
147
|
+
@property
|
|
148
|
+
def qubit_map(self) -> dict[QubitT, int]:
|
|
149
|
+
"""Mapping of ParityOS Qubit to Quake register index."""
|
|
150
|
+
return self._qubit_index_map
|
|
151
|
+
|
|
152
|
+
def export(self, circuit: CircuitLike[QubitT]) -> None:
|
|
153
|
+
"""Export ParityOS Circuit to a Quake program.
|
|
154
|
+
|
|
155
|
+
Should only be called once.
|
|
156
|
+
|
|
157
|
+
Args:
|
|
158
|
+
circuit: ParityOS Circuit to be exported to Quake.
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
Translated Quake program.
|
|
162
|
+
"""
|
|
163
|
+
self._add_header(circuit)
|
|
164
|
+
|
|
165
|
+
self._add_allocation(circuit)
|
|
166
|
+
|
|
167
|
+
for op in circuit.operators:
|
|
168
|
+
self._add_operator(op)
|
|
169
|
+
|
|
170
|
+
self._add_termination()
|
|
171
|
+
|
|
172
|
+
@staticmethod
|
|
173
|
+
def _is_legal_circuit_name(name: str):
|
|
174
|
+
"""Checks if a circuit name is a valid alphanumeric identifier.
|
|
175
|
+
|
|
176
|
+
A valid identifier must be non-empty, must not start with a digit,
|
|
177
|
+
and can only contain alphanumeric characters or underscores.
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
name: A circuit name to be validated.
|
|
181
|
+
|
|
182
|
+
Returns:
|
|
183
|
+
``True`` if the name is a valid identifier, ``False`` otherwise.
|
|
184
|
+
"""
|
|
185
|
+
return (
|
|
186
|
+
len(name) > 0 and not name[0].isnumeric() and all(c.isalnum() or c in "_" for c in name)
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
def _add_header(self, circuit: CircuitLike[Qubit]):
|
|
190
|
+
"""Add the correct header string to the current conversion output.
|
|
191
|
+
|
|
192
|
+
Args:
|
|
193
|
+
circuit: The circuit to be exported.
|
|
194
|
+
"""
|
|
195
|
+
circuit_name = (
|
|
196
|
+
circuit.name if self._is_legal_circuit_name(circuit.name) else "parityos_circuit"
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
if self._emulate_nvqpp:
|
|
200
|
+
funcfunc_name = f"__nvqpp__mlirgen__{circuit_name}"
|
|
201
|
+
# The C++ name mangling is to be read as follows:
|
|
202
|
+
# "_ZN" indicates a C++ function.
|
|
203
|
+
# Next comes the function namespace and name. To know where the namespace ends and
|
|
204
|
+
# where the name begins, the string is prefixed by the length of the namespace.
|
|
205
|
+
# Here, by cuda-q convention, the namespace is the circuit name, and the function name
|
|
206
|
+
# is "cl".
|
|
207
|
+
# The last two characters indicate void return type ("E") and void arguments ("v").
|
|
208
|
+
circuit_name_mangled = f"_ZN{len(circuit_name)}{circuit_name}clEv"
|
|
209
|
+
self._module = (
|
|
210
|
+
"// Generated by ParityOS\n"
|
|
211
|
+
+ f'module attributes {{qtx.mangled_name_map = {{{funcfunc_name} = "{circuit_name_mangled}"}}}} {{\n'
|
|
212
|
+
+ f' func.func @{funcfunc_name}() attributes {{"cudaq-entrypoint", "cudaq-kernel"}} {{\n'
|
|
213
|
+
)
|
|
214
|
+
else:
|
|
215
|
+
funcfunc_name = f"{circuit_name}"
|
|
216
|
+
self._module = (
|
|
217
|
+
"// Generated by ParityOS\n" + "module {\n" + f" func.func @{funcfunc_name}() {{\n"
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
def _add_termination(self):
|
|
221
|
+
"""Add a return statement to the current conversion output."""
|
|
222
|
+
self._add_line("return")
|
|
223
|
+
self._module += " }\n}\n"
|
|
224
|
+
|
|
225
|
+
def _add_line(self, text: str):
|
|
226
|
+
"""Add one line to the current conversion output."""
|
|
227
|
+
self._module += f"{self._indentation}{text}\n"
|
|
228
|
+
|
|
229
|
+
def _add_comment(self, comment: str):
|
|
230
|
+
"""Add a comment to the current conversion output."""
|
|
231
|
+
self._add_line(f"// {comment}")
|
|
232
|
+
|
|
233
|
+
def _add_allocation(self, circuit: CircuitLike[QubitT]) -> None:
|
|
234
|
+
"""Allocate the qubits required for the circuit using Quake dialect."""
|
|
235
|
+
qubits = circuit.ordered_qubits
|
|
236
|
+
self._add_comment("Allocate register")
|
|
237
|
+
self._add_line(f"%n_qubits = arith.constant {len(qubits)} : i64")
|
|
238
|
+
self._add_line("%register = quake.alloca !quake.veq<?>[%n_qubits : i64]")
|
|
239
|
+
|
|
240
|
+
for index, qubit in enumerate(qubits):
|
|
241
|
+
self._qubit_map[qubit] = f"%q_{index}"
|
|
242
|
+
self._qubit_index_map[qubit] = index
|
|
243
|
+
self._add_line(
|
|
244
|
+
f"%q_{index} = quake.extract_ref %register[{index}] : (!quake.veq<?>) -> !quake.ref"
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
self._add_line("")
|
|
248
|
+
|
|
249
|
+
def _add_operator(self, operator: Operator[QubitT]) -> None:
|
|
250
|
+
"""Convert an operator to the Quake dialect.
|
|
251
|
+
|
|
252
|
+
Raises:
|
|
253
|
+
ParityOSNotSupportedError: When the operator is a conditional one.
|
|
254
|
+
"""
|
|
255
|
+
if isinstance(operator, ConditionalOperator):
|
|
256
|
+
raise ParityOSNotSupportedError("Conditional Operator not supported.")
|
|
257
|
+
|
|
258
|
+
if isinstance(operator, Measurement):
|
|
259
|
+
self._add_measurement(operator)
|
|
260
|
+
return
|
|
261
|
+
|
|
262
|
+
if isinstance(operator, Reset):
|
|
263
|
+
self._add_reset(operator)
|
|
264
|
+
return
|
|
265
|
+
|
|
266
|
+
controlled_qubits: tuple[QubitT, ...] | None
|
|
267
|
+
target_qubits: tuple[QubitT, ...]
|
|
268
|
+
quake_operation_name: str
|
|
269
|
+
|
|
270
|
+
if isinstance(operator, ControlledOperator):
|
|
271
|
+
controlled_qubits = tuple(sorted(operator.control_qubits))
|
|
272
|
+
target_qubits = operator.ordered_target_qubits
|
|
273
|
+
quake_operation_name = self._lookup_quake_name(operator.target_operator.name)
|
|
274
|
+
else:
|
|
275
|
+
controlled_qubits = None
|
|
276
|
+
target_qubits = operator.ordered_qubits
|
|
277
|
+
quake_operation_name = self._lookup_quake_name(operator.name)
|
|
278
|
+
angle = _extract_gate_angle(operator)
|
|
279
|
+
angle_var = None
|
|
280
|
+
n_qubits = len(operator.qubits)
|
|
281
|
+
|
|
282
|
+
# Code generation
|
|
283
|
+
if angle is not None:
|
|
284
|
+
angle_var = self._new_angle_var()
|
|
285
|
+
self._add_line(f"{angle_var} = arith.constant {angle} : f64")
|
|
286
|
+
self._add_line(
|
|
287
|
+
f"quake.{quake_operation_name} "
|
|
288
|
+
+ (f"({angle_var}) " if angle is not None else "")
|
|
289
|
+
+ (
|
|
290
|
+
f"[{', '.join([self._qubit_map[q] for q in controlled_qubits])}] "
|
|
291
|
+
if controlled_qubits is not None
|
|
292
|
+
else ""
|
|
293
|
+
)
|
|
294
|
+
+ f"{', '.join([self._qubit_map[q] for q in target_qubits])} : ("
|
|
295
|
+
+ ("f64, " if angle is not None else "")
|
|
296
|
+
+ ", ".join(["!quake.ref"] * n_qubits)
|
|
297
|
+
+ ") -> ()"
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
def _new_angle_var(self) -> str:
|
|
301
|
+
"""Generate a unique angle variable name, including % prefix."""
|
|
302
|
+
angle_var = f"%angle_{self._angle_count}"
|
|
303
|
+
self._angle_count += 1
|
|
304
|
+
return angle_var
|
|
305
|
+
|
|
306
|
+
def _new_measurement_var(self) -> str:
|
|
307
|
+
"""Generate a unique measurement result variable name, including % prefix."""
|
|
308
|
+
measurement_var = f"%m_result_{self._measurement_count}"
|
|
309
|
+
self._measurement_count += 1
|
|
310
|
+
return measurement_var
|
|
311
|
+
|
|
312
|
+
def _add_measurement(self, measure: Measurement[QubitT, XBasis | ZBasis]) -> None:
|
|
313
|
+
"""Convert a measurement operatin to the Quake dialect.
|
|
314
|
+
|
|
315
|
+
Args:
|
|
316
|
+
measure: the qubits to be measured and the basis they should be measured on
|
|
317
|
+
|
|
318
|
+
Raises:
|
|
319
|
+
ParityOSNotSupportedError: If a partial measurement is triggered or in a basis different
|
|
320
|
+
from X or Z.
|
|
321
|
+
"""
|
|
322
|
+
if len(measure.qubits) == 1:
|
|
323
|
+
target = self._qubit_map[measure.qubit]
|
|
324
|
+
source_type = "!quake.ref"
|
|
325
|
+
target_type = "!quake.measure"
|
|
326
|
+
elif len(measure.qubits) != len(self._qubit_map):
|
|
327
|
+
target = "%register"
|
|
328
|
+
source_type = "!quake.veq<?>"
|
|
329
|
+
target_type = "!cc.stdvec<!quake.measure>"
|
|
330
|
+
else:
|
|
331
|
+
raise ParityOSNotSupportedError(
|
|
332
|
+
"Only measurement of single qubits or the entire register is supported."
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
if isinstance(measure, MX):
|
|
336
|
+
op_name = "mx"
|
|
337
|
+
elif isinstance(measure, MZ):
|
|
338
|
+
op_name = "mz"
|
|
339
|
+
else:
|
|
340
|
+
raise ParityOSNotSupportedError(f"Unsupported measurement '{measure.name}'.")
|
|
341
|
+
|
|
342
|
+
self._add_line(
|
|
343
|
+
f"{self._new_measurement_var()} = "
|
|
344
|
+
+ f"quake.{op_name} {target} : ({source_type}) -> {target_type}"
|
|
345
|
+
)
|
|
346
|
+
self._add_line("")
|
|
347
|
+
|
|
348
|
+
def _add_reset(self, reset: Reset[QubitT]):
|
|
349
|
+
"""Convert a set of reset operatin to the Quake dialect."""
|
|
350
|
+
for qubit in reset.ordered_qubits:
|
|
351
|
+
self._add_line(f"quake.reset {self._qubit_map[qubit]} : (!quake.ref) -> ()")
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
def _extract_gate_angle(operator: Operator[QubitT]) -> float | None:
|
|
355
|
+
"""Extract the angle of a rotation gate, or return `None` if no angle exists.
|
|
356
|
+
|
|
357
|
+
Raises:
|
|
358
|
+
ParityOSNotSupportedError: If the angle is not an integer or float.
|
|
359
|
+
"""
|
|
360
|
+
if not isinstance(operator, HasAngle): # noqa: SIM108
|
|
361
|
+
return None
|
|
362
|
+
|
|
363
|
+
if not isinstance(operator.angle, float | int):
|
|
364
|
+
raise ParityOSNotSupportedError("Symbolic parameters not supported.")
|
|
365
|
+
|
|
366
|
+
return float(operator.angle)
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
"""This file contains the quake circuit equivalent of the ParityOS circuits defined in
|
|
2
|
+
parityos_circuits.py, given in FILECHECK match syntax.
|
|
3
|
+
|
|
4
|
+
In addition, a few more ParityOS test circuits are defined.
|
|
5
|
+
"""
|
|
6
|
+
# ParityQC © 2025. See the LICENSE file in the top level directory for details.
|
|
7
|
+
|
|
8
|
+
from parityos.bits import Qubit
|
|
9
|
+
from parityos.bits import get_q as q
|
|
10
|
+
from parityos.operators.circuit import Circuit
|
|
11
|
+
from parityos.operators.controlled_operator import CCX, CRX
|
|
12
|
+
from parityos.operators.elementary_operator import SDg, Swap, TDg
|
|
13
|
+
|
|
14
|
+
######################################################
|
|
15
|
+
## Additional Circuits
|
|
16
|
+
######################################################
|
|
17
|
+
|
|
18
|
+
# 17
|
|
19
|
+
pc17 = Circuit(
|
|
20
|
+
[
|
|
21
|
+
SDg(q(0)),
|
|
22
|
+
TDg(q(0)),
|
|
23
|
+
]
|
|
24
|
+
).measure_all()
|
|
25
|
+
|
|
26
|
+
# 18
|
|
27
|
+
pc18 = Circuit([CRX(q(0), q(1), 1.0), Swap((q(1), q(2))), CCX((q(0), q(2)), q(3))]).measure_all()
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
ADDITIONAL_PARITYOS_CIRCUITS: list[Circuit[Qubit]] = [pc17, pc18]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
"""
|
|
34
|
+
This string is used by the filecheck tests for matching.
|
|
35
|
+
|
|
36
|
+
# 0:
|
|
37
|
+
# ┌───┐
|
|
38
|
+
# 0: ┤ X ├
|
|
39
|
+
# └───┘
|
|
40
|
+
CHECK-LABEL: CIRCUIT NUMBER 0
|
|
41
|
+
CHECK: %n_qubits = arith.constant 1 : i64
|
|
42
|
+
CHECK: %register = quake.alloca !quake.veq<?>[%n_qubits : i64]
|
|
43
|
+
CHECK: %[[q0:.+]] = quake.extract_ref %register[0] : (!quake.veq<?>) -> !quake.ref
|
|
44
|
+
CHECK-DAG: quake.x %[[q0]] : (!quake.ref) -> ()
|
|
45
|
+
CHECK: return
|
|
46
|
+
|
|
47
|
+
# 1
|
|
48
|
+
# ┌───────┐
|
|
49
|
+
# 1: ┤ Rx(2) ├
|
|
50
|
+
# └───────┘
|
|
51
|
+
CHECK-LABEL: CIRCUIT NUMBER 1
|
|
52
|
+
CHECK: %n_qubits = arith.constant 1 : i64
|
|
53
|
+
CHECK: %register = quake.alloca !quake.veq<?>[%n_qubits : i64]
|
|
54
|
+
CHECK: %[[q1:.+]] = quake.extract_ref %register[0] : (!quake.veq<?>) -> !quake.ref
|
|
55
|
+
CHECK-DAG: %[[a0:.+]] = arith.constant 2.0 : f64
|
|
56
|
+
CHECK-DAG: quake.rx (%[[a0]]) %[[q1]] : (f64, !quake.ref) -> ()
|
|
57
|
+
CHECK: return
|
|
58
|
+
|
|
59
|
+
# 2
|
|
60
|
+
# ┌───────────────────┐
|
|
61
|
+
# a: ┤ Rz(2*phi - theta) ├
|
|
62
|
+
# └───────────────────┘
|
|
63
|
+
CHECK-LABEL: CIRCUIT NUMBER 2
|
|
64
|
+
CHECK: NOT SUPPORTED
|
|
65
|
+
Symbolics not allowed
|
|
66
|
+
|
|
67
|
+
# 3
|
|
68
|
+
# ┌─────────────┐
|
|
69
|
+
# (0, 0): ┤ Rx(2*theta) ├
|
|
70
|
+
# └──────┬──────┘
|
|
71
|
+
# c: ───────■───────
|
|
72
|
+
CHECK-LABEL: CIRCUIT NUMBER 3
|
|
73
|
+
CHECK: NOT SUPPORTED
|
|
74
|
+
Symbolics not allowed
|
|
75
|
+
|
|
76
|
+
# 4
|
|
77
|
+
# ┌───────┐┌───┐┌───┐
|
|
78
|
+
# a: ┤ Rx(2) ├┤ H ├┤ Z ├
|
|
79
|
+
# └───────┘├───┤└───┘
|
|
80
|
+
# b: ─────────┤ X ├─────
|
|
81
|
+
# ┌───┐ └─┬─┘
|
|
82
|
+
# c: ──┤ X ├────■───────
|
|
83
|
+
# └───┘
|
|
84
|
+
CHECK-LABEL: CIRCUIT NUMBER 4
|
|
85
|
+
CHECK: %n_qubits = arith.constant 3 : i64
|
|
86
|
+
CHECK: %register = quake.alloca !quake.veq<?>[%n_qubits : i64]
|
|
87
|
+
CHECK-DAG: %[[a:.+]] = quake.extract_ref %register[0] : (!quake.veq<?>) -> !quake.ref
|
|
88
|
+
CHECK-DAG: %[[b:.+]] = quake.extract_ref %register[1] : (!quake.veq<?>) -> !quake.ref
|
|
89
|
+
CHECK-DAG: %[[c:.+]] = quake.extract_ref %register[2] : (!quake.veq<?>) -> !quake.ref
|
|
90
|
+
CHECK-DAG: %[[angle:.+]] = arith.constant 2.0 : f64
|
|
91
|
+
CHECK-DAG: quake.rx (%[[angle]]) %[[a]] : (f64, !quake.ref) -> ()
|
|
92
|
+
CHECK-DAG: quake.h %[[a]] : (!quake.ref) -> ()
|
|
93
|
+
CHECK-DAG: quake.x %[[c]] : (!quake.ref) -> ()
|
|
94
|
+
CHECK-DAG: quake.x [%[[c]]] %[[b]] : (!quake.ref, !quake.ref) -> ()
|
|
95
|
+
CHECK-DAG: quake.z %[[a]] : (!quake.ref) -> ()
|
|
96
|
+
CHECK: return
|
|
97
|
+
|
|
98
|
+
# 5
|
|
99
|
+
# ┌───┐┌────────────────── ───────┐
|
|
100
|
+
# 0: ┤ H ├┤ ──■── ├──────
|
|
101
|
+
# ├───┤│ If-0 c[0] ^ c[1] ┌─┴─┐ End-0 │
|
|
102
|
+
# 1: ┤ H ├┤ ┤ X ├ ├───■──
|
|
103
|
+
# └───┘└────────╥───────── └───┘ ───────┘ ┌─┴─┐
|
|
104
|
+
# 2: ──────────────╫─────────────────────────┤ X ├
|
|
105
|
+
# ┌───╨────┐ └───┘
|
|
106
|
+
# c: 2/══════════╡ [expr] ╞═════════════════════════
|
|
107
|
+
# └────────┘
|
|
108
|
+
CHECK-LABEL: CIRCUIT NUMBER 5
|
|
109
|
+
CHECK: NOT SUPPORTED
|
|
110
|
+
Classical conditions not supported
|
|
111
|
+
|
|
112
|
+
# 6
|
|
113
|
+
# ┌───┐┌─┐
|
|
114
|
+
# 0: ┤ H ├┤M├──────
|
|
115
|
+
# ├───┤└╥┘┌─┐
|
|
116
|
+
# 1: ┤ H ├─╫─┤M├───
|
|
117
|
+
# ├───┤ ║ └╥┘┌─┐
|
|
118
|
+
# 2: ┤ H ├─╫──╫─┤M├
|
|
119
|
+
# └───┘ ║ ║ └╥┘
|
|
120
|
+
# c: 3/══════╩══╩══╩═
|
|
121
|
+
# 0 1 2
|
|
122
|
+
CHECK-LABEL: CIRCUIT NUMBER 6
|
|
123
|
+
CHECK: %n_qubits = arith.constant 3 : i64
|
|
124
|
+
CHECK: %register = quake.alloca !quake.veq<?>[%n_qubits : i64]
|
|
125
|
+
CHECK-DAG: %[[q0:.+]] = quake.extract_ref %register[0] : (!quake.veq<?>) -> !quake.ref
|
|
126
|
+
CHECK-DAG: %[[q1:.+]] = quake.extract_ref %register[1] : (!quake.veq<?>) -> !quake.ref
|
|
127
|
+
CHECK-DAG: %[[q2:.+]] = quake.extract_ref %register[2] : (!quake.veq<?>) -> !quake.ref
|
|
128
|
+
CHECK-DAG: quake.h %[[q0]] : (!quake.ref) -> ()
|
|
129
|
+
CHECK-DAG: quake.h %[[q1]] : (!quake.ref) -> ()
|
|
130
|
+
CHECK-DAG: quake.h %[[q2]] : (!quake.ref) -> ()
|
|
131
|
+
CHECK-DAG: %[[m0:.+]] = quake.mz %[[q0]] : (!quake.ref) -> !quake.measure
|
|
132
|
+
CHECK-DAG: %[[m1:.+]] = quake.mz %[[q1]] : (!quake.ref) -> !quake.measure
|
|
133
|
+
CHECK-DAG: %[[m2:.+]] = quake.mz %[[q2]] : (!quake.ref) -> !quake.measure
|
|
134
|
+
CHECK: return
|
|
135
|
+
|
|
136
|
+
# 7
|
|
137
|
+
# ┌───┐┌───┐┌─┐┌───┐ ┌─┐
|
|
138
|
+
# a: ┤ Z ├┤ H ├┤M├┤ H ├───────────────────────────────────────────────────────┤M├───
|
|
139
|
+
# ├───┤├───┤└╥┘└┬─┬┘┌───┐ ┌───┐└╥┘┌─┐
|
|
140
|
+
# b: ┤ X ├┤ H ├─╫──┤M├─┤ H ├─────────────────────────────────────────────┤ Z ├─╫─┤M├
|
|
141
|
+
# └───┘└───┘ ║ └╥┘ └───┘┌──────────────────────────── ┌───┐ ───────┐ └┬─┬┘ ║ └╥┘
|
|
142
|
+
# c: ───────────╫───╫───────┤ If-0 (c[1] | c[3]) == true ┤ X ├ End-0 ├──┤M├──╫──╫─
|
|
143
|
+
# ║ ║ └─────────────╥────────────── └───┘ ───────┘ └╥┘ ║ ║
|
|
144
|
+
# ║ ║ ┌───╨────┐ ║ ║ ║
|
|
145
|
+
# c: 5/═══════════╩═══╩═════════════════╡ [expr] ╞═══════════════════════════╩═══╩══╩═
|
|
146
|
+
# 1 3 └────────┘ 0 2 4
|
|
147
|
+
CHECK-LABEL: CIRCUIT NUMBER 7
|
|
148
|
+
CHECK: NOT SUPPORTED
|
|
149
|
+
Classical conditions not supported
|
|
150
|
+
|
|
151
|
+
# 8
|
|
152
|
+
# ┌───┐
|
|
153
|
+
# 0: ┤ X ├──■──────────────────────────────────────────■─
|
|
154
|
+
# └─┬─┘┌─┴─┐┌──────────────┐ ┌───────────┐ │
|
|
155
|
+
# 1: ──┼──┤ X ├┤0 ├─────┤0 ├──────┼─
|
|
156
|
+
# │ └───┘│ Rxx(-2*phi) │ │ │ │
|
|
157
|
+
# 2: ──┼───────┤1 ├──■──┤ Rzz(phi) ├──■───■─
|
|
158
|
+
# │ └──────────────┘ │ │ │ │ │
|
|
159
|
+
# 3: ──■─────────────────────────┼──┤1 ├──┼───┼─
|
|
160
|
+
# ┌─┴─┐└─────┬─────┘ │ │
|
|
161
|
+
# 4: ──────────────────────────┤ X ├──────■────────■───■─
|
|
162
|
+
# └───┘ ┌─┴─┐
|
|
163
|
+
# 5: ────────────────────────────────────────────┤ Y ├───
|
|
164
|
+
# └───┘
|
|
165
|
+
CHECK-LABEL: CIRCUIT NUMBER 8
|
|
166
|
+
CHECK: NOT SUPPORTED
|
|
167
|
+
RZZ gate not supported
|
|
168
|
+
|
|
169
|
+
# 9
|
|
170
|
+
# ┌───┐ ┌───┐
|
|
171
|
+
# 0: ┤ H ├─|0>─┤ H ├
|
|
172
|
+
# └───┘ └───┘
|
|
173
|
+
CHECK-LABEL: CIRCUIT NUMBER 9
|
|
174
|
+
CHECK: %n_qubits = arith.constant 1 : i64
|
|
175
|
+
CHECK: %register = quake.alloca !quake.veq<?>[%n_qubits : i64]
|
|
176
|
+
CHECK-DAG: %[[q0:.+]] = quake.extract_ref %register[0] : (!quake.veq<?>) -> !quake.ref
|
|
177
|
+
CHECK-DAG: quake.h %[[q0]] : (!quake.ref) -> ()
|
|
178
|
+
CHECK-DAG: quake.reset %[[q0]] : (!quake.ref) -> ()
|
|
179
|
+
CHECK-DAG: quake.h %[[q0]] : (!quake.ref) -> ()
|
|
180
|
+
CHECK: return
|
|
181
|
+
|
|
182
|
+
# 10
|
|
183
|
+
# ┌────┐┌───┐┌─────┐┌──────┐┌─────┐┌───┐
|
|
184
|
+
# 0: ┤ √X ├┤ T ├┤ Tdg ├┤ √Xdg ├┤ Sdg ├┤ S ├
|
|
185
|
+
# └────┘└───┘└─────┘└──────┘└─────┘└───┘
|
|
186
|
+
CHECK-LABEL: CIRCUIT NUMBER 10
|
|
187
|
+
CHECK: NOT SUPPORTED
|
|
188
|
+
SX gate not supported
|
|
189
|
+
|
|
190
|
+
# 11
|
|
191
|
+
# ┌────────────┐
|
|
192
|
+
# 0: ───────────┤0 ├
|
|
193
|
+
# │ Rxx(0.25) │
|
|
194
|
+
# 1: ─■─────────┤1 ├
|
|
195
|
+
# │ZZ(0.75) └────────────┘
|
|
196
|
+
# 2: ─■───────────────────────
|
|
197
|
+
CHECK-LABEL: CIRCUIT NUMBER 11
|
|
198
|
+
CHECK: NOT SUPPORTED
|
|
199
|
+
RZZ gate not supported
|
|
200
|
+
|
|
201
|
+
# 12
|
|
202
|
+
# ┌────── ┌───┐ ───────┐
|
|
203
|
+
# 0: ──┤ If-0 ─┤ X ├ End-0 ├─
|
|
204
|
+
# └──╥─── └───┘ ───────┘
|
|
205
|
+
# ┌────╨────┐
|
|
206
|
+
# c: 1/╡ c_0=0x1 ╞═══════════════
|
|
207
|
+
# └─────────┘
|
|
208
|
+
CHECK-LABEL: CIRCUIT NUMBER 12
|
|
209
|
+
CHECK: NOT SUPPORTED
|
|
210
|
+
Classical conditions not supported
|
|
211
|
+
|
|
212
|
+
# 13
|
|
213
|
+
# ┌────── ┌───┐ ───────┐
|
|
214
|
+
# q: ──┤ If-0 ─┤ X ├ End-0 ├─
|
|
215
|
+
# └──╥─── └───┘ ───────┘
|
|
216
|
+
# ┌────╨────┐
|
|
217
|
+
# c: 1/╡ c_0=0x0 ╞═══════════════
|
|
218
|
+
# └─────────┘
|
|
219
|
+
CHECK-LABEL: CIRCUIT NUMBER 13
|
|
220
|
+
CHECK: NOT SUPPORTED
|
|
221
|
+
Classical conditions not supported
|
|
222
|
+
|
|
223
|
+
# 14
|
|
224
|
+
# ┌──────────────────────────── ┌───┐ ───────┐ ┌─────────────────────────────────── ┌───┐ ───────┐
|
|
225
|
+
# q: ┤ If-0 (c[0] | c[2]) == true ┤ X ├ End-0 ├─┤ If-0 (c[0] ^ c[1] ^ c[2]) == true ┤ Z ├ End-0 ├─
|
|
226
|
+
# └─────────────╥────────────── └───┘ ───────┘ └─────────────────╥───────────────── └───┘ ───────┘
|
|
227
|
+
# ┌───╨────┐ ┌───╨────┐
|
|
228
|
+
# c: 3/══════════╡ [expr] ╞═══════════════════════════════════════╡ [expr] ╞════════════════════════════
|
|
229
|
+
# └────────┘ └────────┘
|
|
230
|
+
CHECK-LABEL: CIRCUIT NUMBER 14
|
|
231
|
+
CHECK: NOT SUPPORTED
|
|
232
|
+
Classical conditions not supported
|
|
233
|
+
|
|
234
|
+
# 15
|
|
235
|
+
# ┌─────────────────────────────────────────┐ ┌─────────────┐ ┌───────────────────────────────────┐
|
|
236
|
+
# q_0: ┤0 ├───┤ Rx(2*theta) ├───┤0 ├
|
|
237
|
+
# │ │ └─────────────┘ │ │
|
|
238
|
+
# q_1: ┤1 ├─────────────────────┤1 ├
|
|
239
|
+
# │ │ │ exp(-it (IIII + IYXZ + XZYI))(1) │
|
|
240
|
+
# q_2: ┤2 exp(-it (IIYX + XZYI + ZIZI))(0.5*phi) ├─────────────────────┤2 ├
|
|
241
|
+
# │ │┌───────────────────┐│ │
|
|
242
|
+
# q_3: ┤ ├┤ Ry((-3.14)*theta) ├┤3 ├
|
|
243
|
+
# │ │└───────────────────┘└───────────────────────────────────┘
|
|
244
|
+
# q_4: ┤3 ├──────────────────────────────────────────────────────────
|
|
245
|
+
# └─────────────────────────────────────────┘
|
|
246
|
+
CHECK-LABEL: CIRCUIT NUMBER 15
|
|
247
|
+
CHECK: NOT SUPPORTED
|
|
248
|
+
Symbolics not supported
|
|
249
|
+
|
|
250
|
+
# 16
|
|
251
|
+
# ┌───────┐┌──────────────────────┐
|
|
252
|
+
# q_0: ┤ Rx(π) ├┤0 ├
|
|
253
|
+
# └───────┘│ (XX+YY)((-2)*phi,0) │
|
|
254
|
+
# q_1: ─────────┤1 ├
|
|
255
|
+
# └──────────────────────┘
|
|
256
|
+
CHECK-LABEL: CIRCUIT NUMBER 16
|
|
257
|
+
CHECK: NOT SUPPORTED
|
|
258
|
+
Symbolics not supported
|
|
259
|
+
|
|
260
|
+
# 17
|
|
261
|
+
# ░ ░ ┌───┐
|
|
262
|
+
# q_0: ──────░───────░─┤ H ├
|
|
263
|
+
# ┌───┐ ░ ┌───┐ ░ └───┘
|
|
264
|
+
# q_1: ┤ X ├─░─┤ X ├─░──────
|
|
265
|
+
# └───┘ ░ └───┘ ░
|
|
266
|
+
# q_2: ──────────────░───■──
|
|
267
|
+
# ░ ┌─┴─┐
|
|
268
|
+
# q_3: ──────────────░─┤ X ├
|
|
269
|
+
# ░ └───┘
|
|
270
|
+
CHECK-LABEL: CIRCUIT NUMBER 17
|
|
271
|
+
CHECK: NOT SUPPORTED
|
|
272
|
+
Barriers not supported
|
|
273
|
+
|
|
274
|
+
CHECK-LABEL: CIRCUIT NUMBER 18
|
|
275
|
+
CHECK: %n_qubits = arith.constant 1 : i64
|
|
276
|
+
CHECK: %register = quake.alloca !quake.veq<?>[%n_qubits : i64]
|
|
277
|
+
CHECK-DAG: %[[q0:.+]] = quake.extract_ref %register[0] : (!quake.veq<?>) -> !quake.ref
|
|
278
|
+
CHECK-DAG: quake.s<adj> %[[q0]] : (!quake.ref) -> ()
|
|
279
|
+
CHECK-DAG: quake.t<adj> %[[q0]] : (!quake.ref) -> ()
|
|
280
|
+
CHECK: return
|
|
281
|
+
|
|
282
|
+
CHECK-LABEL: CIRCUIT NUMBER 19
|
|
283
|
+
CHECK: %n_qubits = arith.constant 4 : i64
|
|
284
|
+
CHECK: %register = quake.alloca !quake.veq<?>[%n_qubits : i64]
|
|
285
|
+
CHECK: %[[q1:.+]] = quake.extract_ref %register[0] : (!quake.veq<?>) -> !quake.ref
|
|
286
|
+
CHECK: %[[q2:.+]] = quake.extract_ref %register[1] : (!quake.veq<?>) -> !quake.ref
|
|
287
|
+
CHECK: %[[q3:.+]] = quake.extract_ref %register[2] : (!quake.veq<?>) -> !quake.ref
|
|
288
|
+
CHECK: %[[q4:.+]] = quake.extract_ref %register[3] : (!quake.veq<?>) -> !quake.ref
|
|
289
|
+
CHECK-DAG: %[[angle:.+]] = arith.constant 1.0 : f64
|
|
290
|
+
CHECK-DAG: quake.rx (%[[angle]]) [%[[q1]]] %[[q2]] : (f64, !quake.ref, !quake.ref) -> ()
|
|
291
|
+
CHECK-DAG: quake.swap %[[q2]], %[[q3]] : (!quake.ref, !quake.ref) -> ()
|
|
292
|
+
CHECK-DAG: quake.x [%[[q1]], %[[q3]]] %[[q4]] : (!quake.ref, !quake.ref, !quake.ref) -> ()
|
|
293
|
+
CHECK: return
|
|
294
|
+
"""
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# ParityQC © 2025. See the LICENSE file in the top level directory for details.
|
|
2
|
+
import shutil
|
|
3
|
+
import subprocess
|
|
4
|
+
from typing import final
|
|
5
|
+
|
|
6
|
+
import pytest
|
|
7
|
+
from filecheck.matcher import Matcher
|
|
8
|
+
from filecheck.options import Options
|
|
9
|
+
from parityos.bits import Qubit
|
|
10
|
+
from parityos.operators.circuit import CircuitLike
|
|
11
|
+
from parityos.utils.exceptions import ParityOSNotSupportedError
|
|
12
|
+
from parityos_cudaq.exporter import export_parityos_to_quake
|
|
13
|
+
from parityos_testutils.exporters.parityos_circuits import PARITYOS_CIRCUITS
|
|
14
|
+
|
|
15
|
+
from .quake_circuits import ADDITIONAL_PARITYOS_CIRCUITS
|
|
16
|
+
|
|
17
|
+
CUSTOM_PARITYOS_CIRCUITS = PARITYOS_CIRCUITS + ADDITIONAL_PARITYOS_CIRCUITS
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def check_qudaq_opt_installation() -> bool:
|
|
21
|
+
"""Checks if qudaq-opt is on the PATH."""
|
|
22
|
+
return bool(shutil.which("cudaq-opt"))
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@final
|
|
26
|
+
class TestQuakeExporterCudaQ:
|
|
27
|
+
"""Class to test that cudaq understands the exporter output.
|
|
28
|
+
|
|
29
|
+
These tests require the cudaq-opt binary to be available on the PATH, which is not expected in the CI.
|
|
30
|
+
To support them,
|
|
31
|
+
- Install a cudaq-opt binary.
|
|
32
|
+
- Make sure that cudaq-opt is on the PATH during python execution, such as the /venv/bin
|
|
33
|
+
directory.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
@pytest.mark.skipif(not check_qudaq_opt_installation(), reason="cudaq-opt not installed")
|
|
37
|
+
@pytest.mark.parametrize(
|
|
38
|
+
"parityos_circuit",
|
|
39
|
+
CUSTOM_PARITYOS_CIRCUITS,
|
|
40
|
+
ids=[i for i, _ in enumerate(CUSTOM_PARITYOS_CIRCUITS)],
|
|
41
|
+
)
|
|
42
|
+
def test_qudaq_opt_successful_execution(self, parityos_circuit: CircuitLike[Qubit]):
|
|
43
|
+
"""Tests successful qudaq-opt round trip on exported circuits."""
|
|
44
|
+
try:
|
|
45
|
+
quake_module = export_parityos_to_quake(parityos_circuit, True).module
|
|
46
|
+
except ParityOSNotSupportedError:
|
|
47
|
+
# Unsupported
|
|
48
|
+
return
|
|
49
|
+
|
|
50
|
+
cudaq_binary = str(shutil.which("cudaq-opt")) # The subprocess has no access to the PATH.
|
|
51
|
+
try:
|
|
52
|
+
subprocess.run(
|
|
53
|
+
[cudaq_binary],
|
|
54
|
+
input=quake_module,
|
|
55
|
+
capture_output=True,
|
|
56
|
+
text=True,
|
|
57
|
+
check=True, # Raise exception on non-zero exit code
|
|
58
|
+
)
|
|
59
|
+
except subprocess.CalledProcessError as e:
|
|
60
|
+
pytest.fail(
|
|
61
|
+
f"Test circuit did not pass cudaq-opt round trip (likely invalid syntax): {e}"
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@final
|
|
66
|
+
class TestQuakeExportWithFileCheck:
|
|
67
|
+
"""Class to run FileCheck tests."""
|
|
68
|
+
|
|
69
|
+
def test_quake_export(self, tmp_path):
|
|
70
|
+
from .quake_circuits import __file__ as match_filename
|
|
71
|
+
|
|
72
|
+
tmp_filename = tmp_path / "_quake_output.tmp.qke"
|
|
73
|
+
with open(tmp_filename, "w") as tmp_file:
|
|
74
|
+
for i, parityos_circuit in enumerate(CUSTOM_PARITYOS_CIRCUITS):
|
|
75
|
+
tmp_file.write(f"// CIRCUIT NUMBER {i}\n")
|
|
76
|
+
try:
|
|
77
|
+
export_result = export_parityos_to_quake(parityos_circuit, True)
|
|
78
|
+
tmp_file.write(export_result.module)
|
|
79
|
+
except ParityOSNotSupportedError:
|
|
80
|
+
tmp_file.write("// NOT SUPPORTED\n")
|
|
81
|
+
|
|
82
|
+
filecheck_options = Options(match_filename=match_filename, input_file=str(tmp_filename))
|
|
83
|
+
matcher = Matcher.from_opts(filecheck_options)
|
|
84
|
+
exitcode = matcher.run()
|
|
85
|
+
|
|
86
|
+
if exitcode != 0:
|
|
87
|
+
pytest.fail(f"Filecheck failed with exit code {exitcode}")
|