qevedo-compiler 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.
- qevedo_compiler-0.1.0/PKG-INFO +68 -0
- qevedo_compiler-0.1.0/README.md +54 -0
- qevedo_compiler-0.1.0/pyproject.toml +38 -0
- qevedo_compiler-0.1.0/qevedo/compiler/__init__.py +19 -0
- qevedo_compiler-0.1.0/qevedo/compiler/cli.py +79 -0
- qevedo_compiler-0.1.0/qevedo/compiler/compiler.py +51 -0
- qevedo_compiler-0.1.0/qevedo/compiler/device.py +84 -0
- qevedo_compiler-0.1.0/qevedo/compiler/emit/__init__.py +0 -0
- qevedo_compiler-0.1.0/qevedo/compiler/emit/qasm.py +83 -0
- qevedo_compiler-0.1.0/qevedo/compiler/frontend/__init__.py +0 -0
- qevedo_compiler-0.1.0/qevedo/compiler/frontend/qasm.py +346 -0
- qevedo_compiler-0.1.0/qevedo/compiler/ir.py +107 -0
- qevedo_compiler-0.1.0/qevedo/compiler/passes/__init__.py +12 -0
- qevedo_compiler-0.1.0/qevedo/compiler/passes/base.py +40 -0
- qevedo_compiler-0.1.0/qevedo/compiler/passes/decompose.py +82 -0
- qevedo_compiler-0.1.0/qevedo/compiler/passes/optimize.py +52 -0
- qevedo_compiler-0.1.0/qevedo/compiler/profiles/all_to_all.yaml +11 -0
- qevedo_compiler-0.1.0/qevedo/compiler/profiles/grid_2x4.yaml +20 -0
- qevedo_compiler-0.1.0/qevedo/compiler/profiles/linear_chain.yaml +25 -0
- qevedo_compiler-0.1.0/qevedo_compiler.egg-info/PKG-INFO +68 -0
- qevedo_compiler-0.1.0/qevedo_compiler.egg-info/SOURCES.txt +27 -0
- qevedo_compiler-0.1.0/qevedo_compiler.egg-info/dependency_links.txt +1 -0
- qevedo_compiler-0.1.0/qevedo_compiler.egg-info/entry_points.txt +2 -0
- qevedo_compiler-0.1.0/qevedo_compiler.egg-info/requires.txt +5 -0
- qevedo_compiler-0.1.0/qevedo_compiler.egg-info/top_level.txt +1 -0
- qevedo_compiler-0.1.0/setup.cfg +4 -0
- qevedo_compiler-0.1.0/tests/test_compile.py +48 -0
- qevedo_compiler-0.1.0/tests/test_frontend.py +171 -0
- qevedo_compiler-0.1.0/tests/test_integration.py +24 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: qevedo-compiler
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: QCC, the Quantum Compiler Collection: a technology-agnostic quantum circuit compiler (qevedo.compiler)
|
|
5
|
+
License-Expression: Apache-2.0
|
|
6
|
+
Project-URL: Homepage, https://github.com/qevedo/qevedo-compiler
|
|
7
|
+
Project-URL: Repository, https://github.com/qevedo/qevedo-compiler
|
|
8
|
+
Requires-Python: >=3.10
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
Requires-Dist: PyYAML>=6.0
|
|
11
|
+
Requires-Dist: openqasm<4,>=3
|
|
12
|
+
Provides-Extra: dev
|
|
13
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
14
|
+
|
|
15
|
+
# QCC: the Quantum Compiler Collection
|
|
16
|
+
|
|
17
|
+
A technology-agnostic quantum circuit compiler, part of the Qevedo packages:
|
|
18
|
+
it installs as `qevedo-compiler` and imports as `qevedo.compiler`.
|
|
19
|
+
|
|
20
|
+
## Install
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pip install qevedo-compiler
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
For development, from a checkout:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
cd qcc/python
|
|
30
|
+
pip install -e ".[dev]"
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
`openqasm` 3.x is installed from PyPI. To develop both together, also install
|
|
34
|
+
the sibling checkout: `pip install -e ../../openqasm`.
|
|
35
|
+
|
|
36
|
+
## Usage
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
qcc compile examples/bell.qasm -o /tmp/out.qasm --device qevedo/compiler/profiles/all_to_all.yaml
|
|
40
|
+
qcc compile program.qasm -o /tmp/out.qasm --qasm-version 3 -I lib/
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
From Python:
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
from qevedo.compiler import compile_source, emit_qasm
|
|
47
|
+
|
|
48
|
+
circuit = compile_source(open("examples/bell.qasm").read())
|
|
49
|
+
print(emit_qasm(circuit))
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
The reference device profiles ship with the package, in
|
|
53
|
+
`qevedo/compiler/profiles/`.
|
|
54
|
+
|
|
55
|
+
Input can be OpenQASM 2 or 3. Programs are checked by `openqasm.analyze`
|
|
56
|
+
before lowering, so errors are reported with their location. The frontend
|
|
57
|
+
supports qubit and bit registers, gate calls with constant parameters
|
|
58
|
+
(broadcast over registers), measurements, resets, barriers, `let` aliases of
|
|
59
|
+
qubits and `const` values. Control flow, gate modifiers, classical variables,
|
|
60
|
+
timing and hardware qubits are rejected with an error until the IR supports
|
|
61
|
+
them. Gate definitions are not inlined: gates stay named instructions for the
|
|
62
|
+
decomposition pass.
|
|
63
|
+
|
|
64
|
+
## Tests
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
pytest
|
|
68
|
+
```
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# QCC: the Quantum Compiler Collection
|
|
2
|
+
|
|
3
|
+
A technology-agnostic quantum circuit compiler, part of the Qevedo packages:
|
|
4
|
+
it installs as `qevedo-compiler` and imports as `qevedo.compiler`.
|
|
5
|
+
|
|
6
|
+
## Install
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
pip install qevedo-compiler
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
For development, from a checkout:
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
cd qcc/python
|
|
16
|
+
pip install -e ".[dev]"
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
`openqasm` 3.x is installed from PyPI. To develop both together, also install
|
|
20
|
+
the sibling checkout: `pip install -e ../../openqasm`.
|
|
21
|
+
|
|
22
|
+
## Usage
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
qcc compile examples/bell.qasm -o /tmp/out.qasm --device qevedo/compiler/profiles/all_to_all.yaml
|
|
26
|
+
qcc compile program.qasm -o /tmp/out.qasm --qasm-version 3 -I lib/
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
From Python:
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
from qevedo.compiler import compile_source, emit_qasm
|
|
33
|
+
|
|
34
|
+
circuit = compile_source(open("examples/bell.qasm").read())
|
|
35
|
+
print(emit_qasm(circuit))
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
The reference device profiles ship with the package, in
|
|
39
|
+
`qevedo/compiler/profiles/`.
|
|
40
|
+
|
|
41
|
+
Input can be OpenQASM 2 or 3. Programs are checked by `openqasm.analyze`
|
|
42
|
+
before lowering, so errors are reported with their location. The frontend
|
|
43
|
+
supports qubit and bit registers, gate calls with constant parameters
|
|
44
|
+
(broadcast over registers), measurements, resets, barriers, `let` aliases of
|
|
45
|
+
qubits and `const` values. Control flow, gate modifiers, classical variables,
|
|
46
|
+
timing and hardware qubits are rejected with an error until the IR supports
|
|
47
|
+
them. Gate definitions are not inlined: gates stay named instructions for the
|
|
48
|
+
decomposition pass.
|
|
49
|
+
|
|
50
|
+
## Tests
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
pytest
|
|
54
|
+
```
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "qevedo-compiler"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "QCC, the Quantum Compiler Collection: a technology-agnostic quantum circuit compiler (qevedo.compiler)"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "Apache-2.0"
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
dependencies = [
|
|
13
|
+
"PyYAML>=6.0",
|
|
14
|
+
"openqasm>=3,<4",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
[project.optional-dependencies]
|
|
18
|
+
dev = ["pytest>=7.0"]
|
|
19
|
+
|
|
20
|
+
[project.scripts]
|
|
21
|
+
qcc = "qevedo.compiler.cli:main"
|
|
22
|
+
|
|
23
|
+
[project.urls]
|
|
24
|
+
Homepage = "https://github.com/qevedo/qevedo-compiler"
|
|
25
|
+
Repository = "https://github.com/qevedo/qevedo-compiler"
|
|
26
|
+
|
|
27
|
+
# `qevedo` is a namespace package (no __init__.py) shared with the other
|
|
28
|
+
# Qevedo packages, such as qevedo-simulator.
|
|
29
|
+
[tool.setuptools.packages.find]
|
|
30
|
+
where = ["."]
|
|
31
|
+
include = ["qevedo.compiler*"]
|
|
32
|
+
namespaces = true
|
|
33
|
+
|
|
34
|
+
[tool.setuptools.package-data]
|
|
35
|
+
"qevedo.compiler" = ["profiles/*.yaml"]
|
|
36
|
+
|
|
37
|
+
[tool.pytest.ini_options]
|
|
38
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""QCC, the Quantum Compiler Collection (`qevedo.compiler`)."""
|
|
2
|
+
|
|
3
|
+
from qevedo.compiler.compiler import CompileOptions, compile_circuit, compile_file, compile_source
|
|
4
|
+
from qevedo.compiler.emit.qasm import emit_qasm
|
|
5
|
+
from qevedo.compiler.frontend.qasm import QasmFrontendError, parse_qasm
|
|
6
|
+
from qevedo.compiler.ir import Circuit, Instruction
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"Circuit",
|
|
10
|
+
"Instruction",
|
|
11
|
+
"CompileOptions",
|
|
12
|
+
"QasmFrontendError",
|
|
13
|
+
"compile_circuit",
|
|
14
|
+
"compile_file",
|
|
15
|
+
"compile_source",
|
|
16
|
+
"emit_qasm",
|
|
17
|
+
"parse_qasm",
|
|
18
|
+
]
|
|
19
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""QCC command-line interface."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import sys
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from qevedo.compiler.compiler import CompileOptions, compile_file
|
|
10
|
+
from qevedo.compiler.device import DeviceSpec, default_device
|
|
11
|
+
from qevedo.compiler.emit.qasm import emit_qasm
|
|
12
|
+
from qevedo.compiler.frontend.qasm import QasmFrontendError
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _profiles_dir() -> Path:
|
|
16
|
+
return Path(__file__).resolve().parent / "profiles"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def main(argv: list[str] | None = None) -> int:
|
|
20
|
+
parser = argparse.ArgumentParser(prog="qcc", description="Quantum Compiler Collection")
|
|
21
|
+
sub = parser.add_subparsers(dest="command", required=True)
|
|
22
|
+
|
|
23
|
+
compile_cmd = sub.add_parser("compile", help="Compile an OpenQASM 2 or 3 circuit")
|
|
24
|
+
compile_cmd.add_argument("input", type=Path, help="Input .qasm file")
|
|
25
|
+
compile_cmd.add_argument("-o", "--output", type=Path, required=True, help="Output .qasm file")
|
|
26
|
+
compile_cmd.add_argument(
|
|
27
|
+
"--device",
|
|
28
|
+
type=Path,
|
|
29
|
+
default=None,
|
|
30
|
+
help="Device profile YAML (default: built-in all-to-all)",
|
|
31
|
+
)
|
|
32
|
+
compile_cmd.add_argument(
|
|
33
|
+
"--qasm-version",
|
|
34
|
+
choices=["2", "3"],
|
|
35
|
+
default="2",
|
|
36
|
+
help="OpenQASM version of the output (default: 2)",
|
|
37
|
+
)
|
|
38
|
+
compile_cmd.add_argument(
|
|
39
|
+
"-I",
|
|
40
|
+
"--include",
|
|
41
|
+
action="append",
|
|
42
|
+
default=[],
|
|
43
|
+
type=Path,
|
|
44
|
+
metavar="DIR",
|
|
45
|
+
help="Directory searched by include statements (repeatable)",
|
|
46
|
+
)
|
|
47
|
+
compile_cmd.add_argument(
|
|
48
|
+
"--stats",
|
|
49
|
+
action="store_true",
|
|
50
|
+
help="Print gate counts and depth to stderr",
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
args = parser.parse_args(argv)
|
|
54
|
+
if args.command == "compile":
|
|
55
|
+
return _run_compile(args)
|
|
56
|
+
return 1
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _run_compile(args: argparse.Namespace) -> int:
|
|
60
|
+
device = DeviceSpec.from_yaml(args.device) if args.device else default_device()
|
|
61
|
+
try:
|
|
62
|
+
options = CompileOptions(device=device, include_paths=args.include)
|
|
63
|
+
compiled = compile_file(args.input, options)
|
|
64
|
+
except QasmFrontendError as error:
|
|
65
|
+
print(f"qcc: {error}", file=sys.stderr)
|
|
66
|
+
return 1
|
|
67
|
+
output = emit_qasm(compiled, version=f"{args.qasm_version}.0")
|
|
68
|
+
args.output.write_text(output + "\n", encoding="utf-8")
|
|
69
|
+
if args.stats:
|
|
70
|
+
counts = compiled.gate_counts()
|
|
71
|
+
print(f"device: {device.name}", file=sys.stderr)
|
|
72
|
+
print(f"depth: {compiled.depth()}", file=sys.stderr)
|
|
73
|
+
print(f"2q_gates: {compiled.two_qubit_gate_count()}", file=sys.stderr)
|
|
74
|
+
print(f"gates: {counts}", file=sys.stderr)
|
|
75
|
+
return 0
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
if __name__ == "__main__":
|
|
79
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""End-to-end compilation driver."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Sequence
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from qevedo.compiler.device import DeviceSpec, default_device
|
|
10
|
+
from qevedo.compiler.emit.qasm import emit_qasm
|
|
11
|
+
from qevedo.compiler.frontend.qasm import parse_qasm
|
|
12
|
+
from qevedo.compiler.ir import Circuit
|
|
13
|
+
from qevedo.compiler.passes.base import PassManager
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class CompileOptions:
|
|
18
|
+
device: DeviceSpec | None = None
|
|
19
|
+
pass_manager: PassManager | None = None
|
|
20
|
+
#: Directories searched by ``include`` statements, after the input's directory.
|
|
21
|
+
include_paths: Sequence[str | Path] = field(default_factory=tuple)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def compile_circuit(circuit: Circuit, options: CompileOptions | None = None) -> Circuit:
|
|
25
|
+
opts = options or CompileOptions()
|
|
26
|
+
device = opts.device or default_device()
|
|
27
|
+
manager = opts.pass_manager or PassManager.default(device)
|
|
28
|
+
return manager.run(circuit, device)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def compile_file(input_path: str | Path, options: CompileOptions | None = None) -> Circuit:
|
|
32
|
+
"""Compile an OpenQASM 2 or 3 file."""
|
|
33
|
+
opts = options or CompileOptions()
|
|
34
|
+
circuit = parse_qasm(path=input_path, include_paths=opts.include_paths)
|
|
35
|
+
return compile_circuit(circuit, opts)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def compile_source(source: str, options: CompileOptions | None = None) -> Circuit:
|
|
39
|
+
"""Compile OpenQASM 2 or 3 source text."""
|
|
40
|
+
opts = options or CompileOptions()
|
|
41
|
+
circuit = parse_qasm(source=source, include_paths=opts.include_paths)
|
|
42
|
+
return compile_circuit(circuit, opts)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def compile_to_qasm(
|
|
46
|
+
input_path: str | Path,
|
|
47
|
+
options: CompileOptions | None = None,
|
|
48
|
+
version: str = "2.0",
|
|
49
|
+
) -> str:
|
|
50
|
+
"""Compile a file and return the result as OpenQASM ``version`` source."""
|
|
51
|
+
return emit_qasm(compile_file(input_path, options), version=version)
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""Technology-agnostic backend description."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Dict, Iterable, List, Optional, Set, Tuple
|
|
8
|
+
|
|
9
|
+
import yaml
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class DeviceSpec:
|
|
14
|
+
"""Describes qubit connectivity and native gates for a target."""
|
|
15
|
+
|
|
16
|
+
name: str
|
|
17
|
+
topology: str
|
|
18
|
+
qubits: int
|
|
19
|
+
edges: List[Tuple[int, int]] = field(default_factory=list)
|
|
20
|
+
native_gates: Set[str] = field(default_factory=set)
|
|
21
|
+
directed_edges: bool = False
|
|
22
|
+
max_parallel_2q: int = 1
|
|
23
|
+
gate_rules: Dict[str, dict] = field(default_factory=dict)
|
|
24
|
+
|
|
25
|
+
@classmethod
|
|
26
|
+
def from_dict(cls, data: dict) -> DeviceSpec:
|
|
27
|
+
edges = [tuple(edge) for edge in data.get("edges", [])]
|
|
28
|
+
topology = data.get("topology", "coupling_graph")
|
|
29
|
+
qubits = int(data["qubits"])
|
|
30
|
+
if topology == "all_to_all" and not edges:
|
|
31
|
+
edges = [(i, j) for i in range(qubits) for j in range(i + 1, qubits)]
|
|
32
|
+
native = {gate.lower() for gate in data.get("native_gates", [])}
|
|
33
|
+
return cls(
|
|
34
|
+
name=data["name"],
|
|
35
|
+
topology=topology,
|
|
36
|
+
qubits=qubits,
|
|
37
|
+
edges=edges,
|
|
38
|
+
native_gates=native,
|
|
39
|
+
directed_edges=bool(data.get("directed_edges", False)),
|
|
40
|
+
max_parallel_2q=int(data.get("max_parallel_2q", 1)),
|
|
41
|
+
gate_rules=data.get("gate_rules", {}),
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
@classmethod
|
|
45
|
+
def from_yaml(cls, path: str | Path) -> DeviceSpec:
|
|
46
|
+
with open(path, encoding="utf-8") as handle:
|
|
47
|
+
data = yaml.safe_load(handle)
|
|
48
|
+
return cls.from_dict(data)
|
|
49
|
+
|
|
50
|
+
def adjacency(self) -> Dict[int, Set[int]]:
|
|
51
|
+
graph: Dict[int, Set[int]] = {q: set() for q in range(self.qubits)}
|
|
52
|
+
for a, b in self.edges:
|
|
53
|
+
graph.setdefault(a, set()).add(b)
|
|
54
|
+
if not self.directed_edges:
|
|
55
|
+
graph.setdefault(b, set()).add(a)
|
|
56
|
+
return graph
|
|
57
|
+
|
|
58
|
+
def supports_gate(self, gate: str, qubits: Iterable[int]) -> bool:
|
|
59
|
+
gate = gate.lower()
|
|
60
|
+
if gate not in self.native_gates:
|
|
61
|
+
return False
|
|
62
|
+
if gate not in self.gate_rules:
|
|
63
|
+
return True
|
|
64
|
+
rule = self.gate_rules[gate]
|
|
65
|
+
placement = rule.get("placement") or rule.get("on")
|
|
66
|
+
if placement == "edge" and len(list(qubits)) == 2:
|
|
67
|
+
pair = tuple(qubits)
|
|
68
|
+
if self.directed_edges:
|
|
69
|
+
return pair in self.edges
|
|
70
|
+
return pair in self.edges or (pair[1], pair[0]) in self.edges
|
|
71
|
+
return True
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def default_device() -> DeviceSpec:
|
|
75
|
+
"""All-to-all superconducting-style device with a common IBM-like gateset."""
|
|
76
|
+
qubits = 32
|
|
77
|
+
edges = [(i, j) for i in range(qubits) for j in range(i + 1, qubits)]
|
|
78
|
+
return DeviceSpec(
|
|
79
|
+
name="all_to_all_32",
|
|
80
|
+
topology="all_to_all",
|
|
81
|
+
qubits=qubits,
|
|
82
|
+
edges=edges,
|
|
83
|
+
native_gates={"rz", "sx", "x", "cx"},
|
|
84
|
+
)
|
|
File without changes
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""QCC IR → OpenQASM 2 or 3, printed by the ``openqasm`` package (3.x)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import math
|
|
6
|
+
from fractions import Fraction
|
|
7
|
+
|
|
8
|
+
import openqasm
|
|
9
|
+
from openqasm import ast
|
|
10
|
+
|
|
11
|
+
from qevedo.compiler.ir import Circuit, Clbit, Instruction, Qubit
|
|
12
|
+
|
|
13
|
+
__all__ = ["emit_qasm"]
|
|
14
|
+
|
|
15
|
+
# IR gate names that are spelled differently in OpenQASM 3's stdgates.inc.
|
|
16
|
+
_OPENQASM3_NAMES = {"u": "U"}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def emit_qasm(circuit: Circuit, version: str = "2.0", include_barriers: bool = True) -> str:
|
|
20
|
+
"""Serialize ``circuit`` as an OpenQASM ``version`` ("2.0" or "3.0") program.
|
|
21
|
+
|
|
22
|
+
Gates keep their IR names and come from ``qelib1.inc`` (OpenQASM 2) or
|
|
23
|
+
``stdgates.inc`` (OpenQASM 3).
|
|
24
|
+
"""
|
|
25
|
+
if version not in ("2.0", "3.0"):
|
|
26
|
+
raise ValueError(f"unsupported OpenQASM version {version!r}")
|
|
27
|
+
openqasm3 = version == "3.0"
|
|
28
|
+
statements: list[ast.Statement] = [ast.Include("stdgates.inc" if openqasm3 else "qelib1.inc")]
|
|
29
|
+
for name, size in circuit.qreg_sizes.items():
|
|
30
|
+
statements.append(ast.QubitDeclaration(ast.Identifier(name), ast.IntegerLiteral(size)))
|
|
31
|
+
for name, size in circuit.creg_sizes.items():
|
|
32
|
+
statements.append(
|
|
33
|
+
ast.ClassicalDeclaration(ast.BitType(ast.IntegerLiteral(size)), ast.Identifier(name))
|
|
34
|
+
)
|
|
35
|
+
for inst in circuit.instructions:
|
|
36
|
+
statement = _statement(inst, openqasm3, include_barriers)
|
|
37
|
+
if statement is not None:
|
|
38
|
+
statements.append(statement)
|
|
39
|
+
# The printer uses OpenQASM 2 syntax (qreg, creg, measure ->) for version 2.0.
|
|
40
|
+
return openqasm.dumps(ast.Program(statements, version=version)).rstrip("\n")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _statement(inst: Instruction, openqasm3: bool, include_barriers: bool) -> ast.Statement | None:
|
|
44
|
+
name = inst.name.lower()
|
|
45
|
+
if name == "barrier":
|
|
46
|
+
return ast.Barrier([_bit(q) for q in inst.qubits]) if include_barriers else None
|
|
47
|
+
if name == "measure":
|
|
48
|
+
measure = ast.MeasureExpression(_bit(inst.qubits[0]))
|
|
49
|
+
return ast.MeasureStatement(measure, _bit(inst.clbits[0]))
|
|
50
|
+
if name == "reset":
|
|
51
|
+
return ast.Reset(_bit(inst.qubits[0]))
|
|
52
|
+
name = _OPENQASM3_NAMES.get(name, inst.name) if openqasm3 else inst.name
|
|
53
|
+
return ast.GateCall(
|
|
54
|
+
[], ast.Identifier(name), [_angle(p) for p in inst.params], [_bit(q) for q in inst.qubits]
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _bit(bit: Qubit | Clbit) -> ast.IndexedIdentifier:
|
|
59
|
+
return ast.IndexedIdentifier(ast.Identifier(bit.register), [[ast.IntegerLiteral(bit.index)]])
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _angle(value: float) -> ast.Expression:
|
|
63
|
+
"""``value`` as a readable expression: a small rational multiple of pi when it is one."""
|
|
64
|
+
ratio = Fraction(value / math.pi).limit_denominator(16)
|
|
65
|
+
if ratio and abs(float(ratio) * math.pi - value) < 1e-12:
|
|
66
|
+
# Build "-3 * pi / 4" rather than "-(3 * pi / 4)".
|
|
67
|
+
expression: ast.Expression = ast.Identifier("pi")
|
|
68
|
+
numerator = abs(ratio.numerator)
|
|
69
|
+
if numerator != 1:
|
|
70
|
+
factor: ast.Expression = ast.IntegerLiteral(numerator)
|
|
71
|
+
if ratio < 0:
|
|
72
|
+
factor = ast.UnaryExpression(ast.UnaryOperator.NEGATE, factor)
|
|
73
|
+
expression = ast.BinaryExpression(ast.BinaryOperator.MULTIPLY, factor, expression)
|
|
74
|
+
elif ratio < 0:
|
|
75
|
+
expression = ast.UnaryExpression(ast.UnaryOperator.NEGATE, expression)
|
|
76
|
+
if ratio.denominator != 1:
|
|
77
|
+
expression = ast.BinaryExpression(
|
|
78
|
+
ast.BinaryOperator.DIVIDE, expression, ast.IntegerLiteral(ratio.denominator)
|
|
79
|
+
)
|
|
80
|
+
return expression
|
|
81
|
+
if value < 0:
|
|
82
|
+
return ast.UnaryExpression(ast.UnaryOperator.NEGATE, ast.FloatLiteral(-value))
|
|
83
|
+
return ast.FloatLiteral(value)
|
|
File without changes
|