diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 00000000..39af06c0
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,5 @@
+root = true
+
+[*.{md,toml,json,yml,yaml,sh}]
+indent_style = space
+indent_size = 2
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
new file mode 100644
index 00000000..7eec200c
--- /dev/null
+++ b/.github/workflows/test.yml
@@ -0,0 +1,35 @@
+name: Test
+
+on:
+ push:
+ branches: [main]
+ pull_request:
+ workflow_dispatch:
+
+permissions:
+ contents: read
+
+jobs:
+ test:
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [ubuntu-24.04, macos-15]
+
+ steps:
+ - uses: actions/checkout@v4.4.0
+ - uses: astral-sh/setup-uv@v10.2.0
+
+ - name: Install Python and dependencies
+ run: |
+ uv python install
+ uv sync --locked
+
+ - name: Install all solvers
+ run: ./scripts/setup-solvers.sh
+
+ - name: Run tests
+ run: |
+ export PATH="$PWD/.solvers/bin:$PATH"
+ uv run --no-sync pytest tests/
diff --git a/.gitignore b/.gitignore
index 0a1ecc29..740fabe7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,5 +13,6 @@ experiment/run_benchmarks/results/*.png
experiment/run_benchmarks/results/*.tex
experiment/run_benchmarks/tmp/*
tmp/*
+!tmp/.gitkeep
quokka_sharp/debug.py
-config.json
\ No newline at end of file
+.solvers/
diff --git a/.python-version b/.python-version
new file mode 100644
index 00000000..24ee5b1b
--- /dev/null
+++ b/.python-version
@@ -0,0 +1 @@
+3.13
diff --git a/README.md b/README.md
index 9b2d5b6a..658fb364 100644
--- a/README.md
+++ b/README.md
@@ -1,68 +1,95 @@
# Quokka-Sharp
Quokka-Sharp is a quantum circuit tool based on model counting. It provides four functionalities: **Simulate**, **Verify**, **Equivalence Checking**, and **Synthesis**.
----
-## Prerequisites
+## Setup
+
+First, make sure you have [`uv`](https://docs.astral.sh/uv/) installed.
+
+From the repository root:
+
+- Install all dependencies
-Install the solvers required by Quokka-Sharp:
+```bash
+uv sync
+```
+
+- Install all solvers
+
+```bash
+./scripts/setup-solvers.sh
+```
+
+These solver binaries are installed under `.solvers/bin`:
- **GPMC** (simulation, verification, equivalence): [GPMC on GitHub](https://github.com/System-Verification-Lab/GPMC)
-- **Ganak** (alternative WMC solver): [Ganak releases](https://github.com/meelgroup/ganak/releases/tag/release%2F2.4.4)
+- **Ganak** (installed for experiments; not currently usable by changing the config alone): [Ganak releases](https://github.com/meelgroup/ganak/releases/tag/release/v2.7.0)
- **d4max** (synthesis only): [d4v2 on GitHub](https://github.com/jm62300/d4)
-Then install Quokka-Sharp:
+---
+
+## Development
+
+### Running code
+
+Activate the environment:
```bash
-pip install quokka_sharp
+source .venv/bin/activate
```
----
+Make sure the solvers are on $PATH:
-## Configuration
+```bash
+export PATH="$PWD/.solvers/bin:$PATH"
+```
-Copy the template and edit it to point at your solver binaries:
+To run a python file:
```bash
-cp config.example.json config.json
+python tests/example_quokka_sharp.py
```
-**For GPMC (simulation / verification / equivalence):**
-```json
-{
- "DEBUG": false,
- "TIMEOUT": 300,
- "ToolInvocation": "/path/to/gpmc -mode=1",
- "GetResult": "exact.double.prec-sci.(.+?)\\nc s",
- "FPE": 1e-12
-}
+To run the tests:
+
+```bash
+pytest tests/
```
-**For Ganak:**
+### Tooling
+
+The project uses `ruff` to format and check for warnings and errors.
+
+---
+
+## Configuration
+
+No configuration file is needed for the setup above. By default, Quokka-Sharp uses `gpmc -mode=1` for simulation, verification, and equivalence and `maxT_static` (`maxT`) for synthesis.
+
+However, it's possible to modify the configuration used. To do so, create a .json file with only keys you want to override. For example:
+
```json
{
- "DEBUG": false,
- "TIMEOUT": 1,
- "ToolInvocation": "/path/to/ganak --mode=6",
- "GetResult": "c s exact arb cpx (.+?)\\nc",
- "FPE": 1e-12
+ "DEBUG": true,
+ "TIMEOUT": 600
}
```
-**For d4max (synthesis):**
+Note that this is the default configuration:
+
```json
{
- "DEBUG": true,
- "TIMEOUT": 1000,
- "ToolInvocation": "/path/to/gpmc -mode=1",
- "D4ToolInvocation": "/path/to/d4maxT",
- "GetResult": "exact.double.prec-sci.(.+?)\\nc s",
+ "DEBUG": false,
+ "TIMEOUT": 300,
+ "ToolInvocation": "gpmc -mode=1",
+ "D4ToolInvocation": "maxT_static",
+ "GetResult": "exact.double.prec-sci.(.+?)\\\\nc s",
"FPE": 1e-12,
- "Precision": 32
+ "Precision": 50
}
```
-Set the environment variable before running anything:
+To use the configuration file set `QUOKKA_CONFIG` to that file before starting Python:
```bash
export QUOKKA_CONFIG=/path/to/config.json
@@ -83,8 +110,8 @@ import quokka_sharp as qk
prob = qk.functionalities.sim(
qasmfile="circuit.qasm",
- basis="comp", # "comp" (computational) or "pauli"
- measurement="allzero" # "allzero", "firstzero", or {qubit: 0_or_1}
+ basis="comp", # "comp" (computational) or "pauli"
+ measurement="allzero", # "allzero", "firstzero", or {qubit: 0_or_1}
)
# Returns a float, "TIMEOUT", or "MEMOUT"
```
@@ -98,7 +125,7 @@ Check that a circuit maps a given input state to a given output state.
res = qk.functionalities.verify(
"circuit.qasm",
basis="comp",
- precons={0: 0, 1: 0}, # input state constraints
+ precons={0: 0, 1: 0}, # input state constraints
postcons={0: 1, 1: 1}, # output state constraints
)
@@ -106,8 +133,8 @@ res = qk.functionalities.verify(
res = qk.functionalities.verify(
"circuit.qasm",
basis="pauli",
- precons={0: "Z"}, # qubit 0 starts with the subspace defined by Z
- postcons={0: "X"}, # qubit 0 ends in with the subspace defined by X
+ precons={0: "Z"}, # qubit 0 starts with the subspace defined by Z
+ postcons={0: "X"}, # qubit 0 ends in with the subspace defined by X
)
# Returns "True", "False", "TIMEOUT", or "MEMOUT"
```
@@ -120,9 +147,9 @@ Decide whether two circuits implement the same unitary.
res = qk.functionalities.eq(
"circuit1.qasm",
"circuit2.qasm",
- basis="comp", # "comp" → use check="cyclic"
- check="cyclic", # "pauli" → use check="linear"
- epsilon=0, # 0 for exact equivalence
+ basis="comp", # "comp" → use check="cyclic"
+ check="cyclic", # "pauli" → use check="linear"
+ epsilon=0, # 0 for exact equivalence
)
# Returns True, False, "TIMEOUT", or "MEMOUT"
```
@@ -133,15 +160,16 @@ Find the shortest circuit implementing a target unitary from a given gate set. R
```python
import os
+
os.makedirs("tmp", exist_ok=True)
outcome, weight, qasm_str, layers = qk.functionalities.syn(
"target.qasm",
gate_set={"h", "cx", "s"}, # supported: "h", "cx", "s", "t", "cz", "csqrtx"
- basis="pauli", # synthesis works in Pauli basis only
- fid=1.0, # target fidelity: 1.0 for exact synthesis
- files_root="tmp", # directory for intermediate CNF files
- cyc_lin_encoding=True, # use cyclic+linear encoding (recommended)
+ basis="pauli", # synthesis works in Pauli basis only
+ fid=1.0, # target fidelity: 1.0 for exact synthesis
+ files_root="tmp", # directory for intermediate CNF files
+ cyc_lin_encoding=True, # use cyclic+linear encoding (recommended)
)
# outcome : "FOUND" | "TIMEOUT" | "CRASH" | "ERROR#"
# weight : achieved fidelity (1.0 when FOUND)
@@ -151,7 +179,6 @@ outcome, weight, qasm_str, layers = qk.functionalities.syn(
> **Note:** Use either `"t"` or `"csqrtx"` for CCX synthesis, but not both simultaneously. Currently, we only implemented choosing different gate sets in Pauli basis.
----
---
# Test Suite
@@ -212,17 +239,17 @@ pytest tests/test_quokka_sharp.py -v
Verifies `qk.functionalities.sim()` in both computational and Pauli bases.
-| Circuit | Basis | Measurement | Expected |
-|---|---|---|---|
-| Identity wire | comp | allzero | 1.0 |
-| H | comp | allzero | 0.5 |
-| X | comp | allzero | 0.0 |
-| Z, S, T | comp | allzero | 1.0 |
-| HH, XXXX | comp | allzero | 1.0 (self-inverse) |
-| HZH | comp | allzero | 0.0 (= X) |
-| Bell | comp | allzero | 0.5 — P(00) |
-| GHZ | comp | allzero | 0.5 — P(000) |
-| Bell Ψ− | pauli | allzero | 0.0 |
+| Circuit | Basis | Measurement | Expected |
+| ------------- | ----- | ----------- | ------------------ |
+| Identity wire | comp | allzero | 1.0 |
+| H | comp | allzero | 0.5 |
+| X | comp | allzero | 0.0 |
+| Z, S, T | comp | allzero | 1.0 |
+| HH, XXXX | comp | allzero | 1.0 (self-inverse) |
+| HZH | comp | allzero | 0.0 (= X) |
+| Bell | comp | allzero | 0.5 — P(00) |
+| GHZ | comp | allzero | 0.5 — P(000) |
+| Bell Ψ− | pauli | allzero | 0.0 |
### 2 · Verification (`TestVerification`)
@@ -232,32 +259,41 @@ Verifies `qk.functionalities.verify()` with both computational and Pauli basis p
```python
# X flips the qubit
-qk.functionalities.verify("x.qasm", basis="comp",
- precons={0: 0}, postcons={0: 1}) # → True
+qk.functionalities.verify(
+ "x.qasm", basis="comp", precons={0: 0}, postcons={0: 1}
+) # → True
# Toffoli fires only when both controls are set
-qk.functionalities.verify("toffoli.qasm", basis="comp",
- precons={0: 1, 1: 1, 2: 0}, postcons={0: 1, 1: 1, 2: 1}) # → True
+qk.functionalities.verify(
+ "toffoli.qasm",
+ basis="comp",
+ precons={0: 1, 1: 1, 2: 0},
+ postcons={0: 1, 1: 1, 2: 1},
+) # → True
```
**Pauli basis** — stabilizer strings `{qubit: "Z"|"X"|"Y"|"I"}`:
```python
# H swaps Z and X stabilizers: |0⟩ → |+⟩
-qk.functionalities.verify("h.qasm", basis="pauli",
- precons={0: "Z"}, postcons={0: "X"}) # → True
+qk.functionalities.verify(
+ "h.qasm", basis="pauli", precons={0: "Z"}, postcons={0: "X"}
+) # → True
# S maps X-stabilizer to Y: |+⟩ → |i+⟩
-qk.functionalities.verify("s.qasm", basis="pauli",
- precons={0: "X"}, postcons={0: "Y"}) # → True
+qk.functionalities.verify(
+ "s.qasm", basis="pauli", precons={0: "X"}, postcons={0: "Y"}
+) # → True
# CNOT: control in |0⟩ leaves target X-stab unchanged
-qk.functionalities.verify("cx.qasm", basis="pauli",
- precons={0: "Z", 1: "X"}, postcons={0: "Z", 1: "X"}) # → True
+qk.functionalities.verify(
+ "cx.qasm", basis="pauli", precons={0: "Z", 1: "X"}, postcons={0: "Z", 1: "X"}
+) # → True
# CNOT entangles: q[0] alone is no longer X-stabilized after CX
-qk.functionalities.verify("cx.qasm", basis="pauli",
- precons={0: "Z", 1: "X"}, postcons={0: "X", 1: "I"}) # → False
+qk.functionalities.verify(
+ "cx.qasm", basis="pauli", precons={0: "Z", 1: "X"}, postcons={0: "X", 1: "I"}
+) # → False
```
### 3 · Equivalence Checking (`TestEquivalenceChecking`)
@@ -266,23 +302,23 @@ Verifies `qk.functionalities.eq()`. Use `check="cyclic"` with `basis="comp"` and
**Equivalent pairs (→ True):**
-| Circuit A | Circuit B | Identity |
-|---|---|---|
-| HH | Identity | HH = I |
-| SS | Z | SS = Z |
-| HXH | Z | basis-change identity |
-| CX CX | Identity (2q) | CX is self-inverse |
+| Circuit A | Circuit B | Identity |
+| ----------- | ------------- | ---------------------- |
+| HH | Identity | HH = I |
+| SS | Z | SS = Z |
+| HXH | Z | basis-change identity |
+| CX CX | Identity (2q) | CX is self-inverse |
| 3-CNOT SWAP | built-in SWAP | standard decomposition |
-| TT | S | TT = S |
+| TT | S | TT = S |
**Non-equivalent pairs (→ False):**
-| Circuit A | Circuit B |
-|---|---|
-| H | X |
-| H | HS |
-| Bell (H+CX) | CX only |
-| Z | S |
+| Circuit A | Circuit B |
+| ----------- | --------- |
+| H | X |
+| H | HS |
+| Bell (H+CX) | CX only |
+| Z | S |
### 4 · Synthesis (`TestSynthesis`)
@@ -348,28 +384,28 @@ Example output:
```python
# ── Simulation ──────────────────────────────────────────────
-SIM_FILE = fixture("multi_qubit", "bell.qasm")
-SIM_BASIS = "comp" # "comp" or "pauli"
-SIM_MEASUREMENT = "allzero" # "allzero", "firstzero", "{0:0,1:0}"
-
+SIM_FILE = fixture("multi_qubit", "bell.qasm")
+SIM_BASIS = "comp" # "comp" or "pauli"
+SIM_MEASUREMENT = "allzero" # "allzero", "firstzero", "{0:0,1:0}"
+
# ── Verification ────────────────────────────────────────────
-VER_FILE = fixture("verify", "v01_x_flip.qasm")
-VER_BASIS = "pauli"
-VER_PRECONS = {0: "Z"} # comp: {0: 0} pauli: {0: "Z"}
+VER_FILE = fixture("verify", "v01_x_flip.qasm")
+VER_BASIS = "pauli"
+VER_PRECONS = {0: "Z"} # comp: {0: 0} pauli: {0: "Z"}
VER_POSTCONS = {0: "X"}
# ── Equivalence ─────────────────────────────────────────────
-EQ_FILE1 = fixture("equiv_pairs", "eq1_hh.qasm")
-EQ_FILE2 = fixture("equiv_pairs", "eq1_identity.qasm")
-EQ_BASIS = "comp" # pair with EQ_CHECK = "cyclic"
-EQ_CHECK = "cyclic" # "cyclic" (comp) or "linear" (pauli)
+EQ_FILE1 = fixture("equiv_pairs", "eq1_hh.qasm")
+EQ_FILE2 = fixture("equiv_pairs", "eq1_identity.qasm")
+EQ_BASIS = "comp" # pair with EQ_CHECK = "cyclic"
+EQ_CHECK = "cyclic" # "cyclic" (comp) or "linear" (pauli)
# ── Synthesis ───────────────────────────────────────────────
-SYN_FILE = fixture("synthesis", "syn01_h_target.qasm")
-SYN_GATE_SET = {"h", "cx", "s"}
-SYN_FID = 1.0 # 1.0 for exact synthesis
-SYN_FILES_ROOT = "tmp" # directory for intermediate CNF files
+SYN_FILE = fixture("synthesis", "syn01_h_target.qasm")
+SYN_GATE_SET = {"h", "cx", "s"}
+SYN_FID = 1.0 # 1.0 for exact synthesis
+SYN_FILES_ROOT = "tmp" # directory for intermediate CNF files
SYN_CYC_LIN_ENC = True
```
@@ -418,19 +454,20 @@ qasm_fixtures/
## Modifications
### code structure:
-The Quokka sharp repository has two main directories. The first directory is named quokka_sharp, and has the source code for the Quokka library. The second directory is named experiments, has examples on how to use the tool and benchmarks to test it.
-
-In the directory qukkora_sharp/quokka_sharp the main functionalities are implemented each in its own file and they use the core libraries defined within the quokka_sharp/quokka_sharp encoding.
+The Quokka sharp repository has two main directories. The first directory is named quokka_sharp, and has the source code for the Quokka library. The second directory is named experiments, has examples on how to use the tool and benchmarks to test it.
+In the directory qukkora_sharp/quokka_sharp the main functionalities are implemented each in its own file and they use the core libraries defined within the quokka_sharp/quokka_sharp encoding.
### extension of the encodings:
+
Advanced users can extend `quokka-sharp` to support more quantum gates than the current gate set.
To do this, one needs to install [`SymPy`](https://docs.sympy.org/latest/index.html):
```bash
pip install sympy
```
+
The encoding supports a universal gate set: CNOT, CZ, H, S, T, RX, RZ.
To add direct encoding of other gates, add new encoding in Quokka-Sharp/quokka_sharp/quokka_sharp/encoding/pauli2cnf_py_codegen.py or Quokka-Sharp/quokka_sharp/quokka_sharp/encoding/comput2cnf_py_codegen.py, depending on the basis.
Then, update the ifelse cases at the "QASMparser" function in Quokka-Sharp/quokka_sharp/quokka_sharp/encoding/qasm_parser.py and the "encode_circuit" function in Quokka-Sharp/quokka_sharp/quokka_sharp/encoding/cnf.py.
@@ -439,34 +476,36 @@ Finally, run one of the following commands correspondingly:
```
python3 pauli2cnf_py_codegen.py>pauli2cnf.py
```
+
or
+
```
python3 comput2cnf_py_codegen.py>comput2cnf.py
```
### updating the installation
+
When changing the core files, Quokka# needs to be reinstalled from the local files. To do that, run:
+
```
-pip install ./quokka_sharp --force-reinstall
+pip install ./quokka_sharp --force-reinstall
```
## Evaluation
-For evaluation of Quokka# please refer to [quokka-sharp-ae26]([https://github.com/System-Verification-Lab/quokka-sharp-artifacts](https://github.com/JingyiMei98/quokka-sharp-ae26))
-
+For evaluation of Quokka# please refer to [quokka-sharp-ae26](<[https://github.com/System-Verification-Lab/quokka-sharp-artifacts](https://github.com/JingyiMei98/quokka-sharp-ae26)>)
## 📚 Citation
If you use the materials in this repository, please cite the following papers:
1. **Simulating Quantum Circuits by Model Counting**
- *Jingyi Mei, Marcello Bonsangue, Alfons Laarman*
- *Proceedings of the 36th International Conference on Computer Aided Verification (CAV 2024)*
+ _Jingyi Mei, Marcello Bonsangue, Alfons Laarman_\
+ _Proceedings of the 36th International Conference on Computer Aided Verification (CAV 2024)_
2. **Equivalence Checking of Quantum Circuits by Model Counting**
- *Jingyi Mei, Thijmen Coopmans, Marcello Bonsangue, Alfons Laarman*
- *Proceedings of the 12th International Joint Conference on Automated Reasoning (IJCAR 2024)*
-
+ _Jingyi Mei, Thijmen Coopmans, Marcello Bonsangue, Alfons Laarman_\
+ _Proceedings of the 12th International Joint Conference on Automated Reasoning (IJCAR 2024)_
📄 BibTeX
@@ -488,7 +527,8 @@ title="Equivalence Checking of Quantum Circuits by Model Counting",
booktitle="Automated Reasoning",
year="2024"
}
- ```
+```
+
## 📬 Contact
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 00000000..95945b39
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,21 @@
+[project]
+name = "quokka-sharp-root"
+version = "0.1.0"
+requires-python = ">=3.13"
+dependencies = ["quokka-sharp"]
+
+[tool.uv.sources]
+quokka-sharp = { path = "quokka_sharp", editable = true }
+
+[tool.ruff.format]
+exclude = [
+ "*.md",
+ "quokka_sharp/quokka_sharp/encoding/comput2cnf.py",
+ "quokka_sharp/quokka_sharp/encoding/pauli2cnf.py",
+]
+
+[tool.pyright]
+extraPaths = ["quokka_sharp"]
+
+[dependency-groups]
+dev = ["pytest>=9.1.1", "ruff>=0.16.9"]
diff --git a/quokka_sharp/comput2cnf_py_codegen.py b/quokka_sharp/comput2cnf_py_codegen.py
index 6b9fa332..fa40a097 100644
--- a/quokka_sharp/comput2cnf_py_codegen.py
+++ b/quokka_sharp/comput2cnf_py_codegen.py
@@ -9,52 +9,57 @@
k = 2
i = symbols("i")
-I = symbols('I')
-r = symbols('r')
-R = symbols('R')
-u = symbols('u')
-U = symbols('U')
-D = symbols('D')
+I = symbols("I")
+r = symbols("r")
+R = symbols("R")
+u = symbols("u")
+U = symbols("U")
+D = symbols("D")
-w = symbols('w')
+w = symbols("w")
-x = [0,0,0]
+x = [0, 0, 0]
x[k] = symbols("x[k]")
x[c] = symbols("x[c]")
x[t] = symbols("x[t]")
-X = symbols('X')
-Xc = symbols('Xc')
-Xt = symbols('Xt')
+X = symbols("X")
+Xc = symbols("Xc")
+Xt = symbols("Xt")
+
def strstrip(item1):
- return item1.lstrip(' ()~')
+ return item1.lstrip(" ()~")
+
def to_py(func, prefix="", simplify=True, force=True, comment=None):
- print(prefix+" # "+str(func))
+ print(prefix + " # " + str(func))
s = str(to_cnf(func, simplify=simplify, force=force))
for a in s.split("&"):
- print(prefix+" cnf.add_clause([", end="")
+ print(prefix + " cnf.add_clause([", end="")
l = a.split("|")
l.sort(key=strstrip)
for x in l:
- b = x.strip(' ()')
- if b.startswith('~'):
- b = b.replace('~', '-')
+ b = x.strip(" ()")
+ if b.startswith("~"):
+ b = b.replace("~", "-")
else:
- b = " "+ b
+ b = " " + b
print(b, end="")
if x != l[-1]:
print(", ", end="")
print("]", end="")
- if comment: print(f", comment=\"{comment}\"", end="")
+ if comment:
+ print(f', comment="{comment}"', end="")
print(f")")
-def add_dependent(func, weight, neg_weight, prefix = "", comment=""):
- print(prefix+f" # adding weights ({weight}, {neg_weight}) if {func}")
- print(prefix+" D = cnf.add_var()")
- print(prefix+" cnf.vars.DVar.append(D)")
- print(prefix+f" cnf.add_weight(D, {weight}, {neg_weight})")
- to_py( Equivalent(D, func), prefix, comment="i "+comment)
+
+def add_dependent(func, weight, neg_weight, prefix="", comment=""):
+ print(prefix + f" # adding weights ({weight}, {neg_weight}) if {func}")
+ print(prefix + " D = cnf.add_var()")
+ print(prefix + " cnf.vars.DVar.append(D)")
+ print(prefix + f" cnf.add_weight(D, {weight}, {neg_weight})")
+ to_py(Equivalent(D, func), prefix, comment="i " + comment)
+
def main():
print("# This code was generated by comput2cnf_weighted_py_codegen.py.")
@@ -67,9 +72,9 @@ def main():
print("frac1sqrt2 = (Decimal(1) / Decimal(2)).sqrt()")
print()
print("class comput2cnf:")
- print(''' \"\"\"
+ print(""" \"\"\"
This class contains the functions to convert a quantum circuit to CNF clauses in the Computationl basis.
- \"\"\"''')
+ \"\"\"""")
print()
frac1sqrt2 = (Decimal(1) / Decimal(2)).sqrt()
@@ -82,7 +87,7 @@ def main():
# H:
print(" def H2CNF(cnf, k):")
print(" x = cnf.vars.x")
- print()
+ print()
print(" X = cnf.add_var()")
print(" cnf.vars.XVar.append(X)")
print()
@@ -91,103 +96,103 @@ def main():
print(" cnf.vars.x[k] = X")
print()
- #X
+ # X
print(" def X2CNF(cnf, k):")
print(" x = cnf.vars.x")
print()
print(" X = cnf.add_var()")
print(" cnf.vars.XVar.append(X)")
- to_py( Equivalent(X, ~x[k]))
+ to_py(Equivalent(X, ~x[k]))
print()
print(" cnf.vars.x[k] = X")
print()
- #Y
+ # Y
print(" def Y2CNF(cnf, k):")
print(" x = cnf.vars.x")
print()
print(" X = cnf.add_var()")
print(" cnf.vars.XVar.append(X)")
- to_py( Equivalent(X, ~x[k]), comment="Y gate")
+ to_py(Equivalent(X, ~x[k]), comment="Y gate")
print(" cnf.add_weight(x[k], -1j, 1j)")
print()
print(" cnf.vars.x[k] = X")
print()
- #Z
+ # Z
print(" def Z2CNF(cnf, k):")
print(" x = cnf.vars.x")
print(" cnf.add_weight(x[k], -1, 1)")
print()
- #S
+ # S
print(" def S2CNF(cnf, k):")
print(" x = cnf.vars.x")
print(" cnf.add_weight(x[k], 1j, 1)")
print()
- #Sdg
+ # Sdg
print(" def Sdg2CNF(cnf, k):")
print(" x = cnf.vars.x")
print(" cnf.add_weight(x[k], -1j, 1)")
print()
- #T
+ # T
print(" def T2CNF(cnf, k):")
print(" x = cnf.vars.x")
print(" cnf.add_weight(x[k], complex(frac1sqrt2,frac1sqrt2), 1)")
print()
- #Tdg
+ # Tdg
print(" def Tdg2CNF(cnf, k):")
print(" x = cnf.vars.x")
print(" cnf.add_weight(x[k], complex(frac1sqrt2,-frac1sqrt2), 1)")
print()
- #CNOT
+ # CNOT
print(" def CNOT2CNF(cnf, c, t):")
print(" x = cnf.vars.x")
print()
print(" Xt = cnf.add_var()")
print(" cnf.vars.XVar.append(Xt)")
- to_py( Equivalent(Xt, (x[c] ^ x[t])))
+ to_py(Equivalent(Xt, (x[c] ^ x[t])))
print()
print(" cnf.vars.x[t] = Xt")
print()
- #CZ
+ # CZ
print(" def CZ2CNF(cnf, c, t):")
print(" x = cnf.vars.x")
print()
add_dependent(x[c] & x[t], -1, 1, comment="CZ gate")
print()
- #CY
+ # CY
print(" def CY2CNF(cnf, c, t):")
print(" comput2cnf.Sdg2CNF(cnf, t)")
print(" comput2cnf.CNOT2CNF(cnf, c, t)")
print(" comput2cnf.S2CNF(cnf, t)")
print()
- #SWAP
+ # SWAP
print(" def SWAP2CNF(cnf, c, t):")
print(" x = cnf.vars.x")
print(" x[c], x[t] = x[t], x[c]")
print()
- #ISWAP
+ # ISWAP
print(" def ISWAP2CNF(cnf, c, t):")
print(" x = cnf.vars.x")
print(" comput2cnf.SWAP2CNF(cnf, c, t)")
add_dependent(x[c] ^ x[t], 1j, 1, comment="ISWAP gate")
print()
- #CS
+ # CS
print(" def CS2CNF(cnf, c, t):")
print(" x = cnf.vars.x")
add_dependent(x[c] & x[t], 1j, 1, comment="CS gate")
- #CSdgr
+ # CSdgr
print(" def CSdg2CNF(cnf, c, t):")
print(" x = cnf.vars.x")
add_dependent(x[c] & x[t], -1j, 1)
@@ -198,9 +203,9 @@ def main():
print()
print(" Xt = cnf.add_var()")
print(" cnf.vars.XVar.append(Xt)")
- to_py( Equivalent(Xt, (x[c] ^ x[t])))
- add_dependent(x[c] & (x[t] ^ Xt), (1-1j)/2, 1)
- add_dependent(x[c] & ~(x[t] ^ Xt), (1+1j)/2, 1)
+ to_py(Equivalent(Xt, (x[c] ^ x[t])))
+ add_dependent(x[c] & (x[t] ^ Xt), (1 - 1j) / 2, 1)
+ add_dependent(x[c] & ~(x[t] ^ Xt), (1 + 1j) / 2, 1)
print()
print(" cnf.vars.x[t] = Xt")
@@ -210,24 +215,24 @@ def main():
print()
print(" Xt = cnf.add_var()")
print(" cnf.vars.XVar.append(Xt)")
- to_py( Equivalent(Xt, (x[c] ^ x[t])))
- add_dependent(x[c] & (x[t] ^ Xt), (1+1j)/2, 1)
- add_dependent(x[c] & ~(x[t] ^ Xt), (1-1j)/2, 1)
+ to_py(Equivalent(Xt, (x[c] ^ x[t])))
+ add_dependent(x[c] & (x[t] ^ Xt), (1 + 1j) / 2, 1)
+ add_dependent(x[c] & ~(x[t] ^ Xt), (1 - 1j) / 2, 1)
print()
print(" cnf.vars.x[t] = Xt")
- #TOFFOLI
+ # TOFFOLI
print(" def CCX2CNF(cnf, k, c, t):")
print(" x = cnf.vars.x")
print()
print(" Xt = cnf.add_var()")
print(" cnf.vars.XVar.append(Xt)")
- to_py( Equivalent(Xt, ((x[k] & x[c]) ^ x[t])))
+ to_py(Equivalent(Xt, ((x[k] & x[c]) ^ x[t])))
print()
print(" cnf.vars.x[t] = Xt")
print()
- #RX(theta)
+ # RX(theta)
print(" def RX2CNF(cnf, k, theta):")
print(" x = cnf.vars.x")
print(" X = cnf.add_var()")
@@ -235,36 +240,39 @@ def main():
print()
print(" w = cnf.add_var()")
print(" cnf.vars.UVar.append(w)")
- to_py( Equivalent(w, Equivalent(x[k], X) ))
- print(" cnf.add_weight(w, complex(Decimal(math.cos(theta/2)), 0), complex(0, -Decimal(math.sin(theta/2))))")
+ to_py(Equivalent(w, Equivalent(x[k], X)))
+ print(
+ " cnf.add_weight(w, complex(Decimal(math.cos(theta/2)), 0), complex(0, -Decimal(math.sin(theta/2))))"
+ )
print()
print(" cnf.vars.x[k] = X")
print()
- #RZ(theta)
+ # RZ(theta)
print(" def RZ2CNF(cnf, k, theta):")
print(" x = cnf.vars.x")
- print(" cnf.add_weight(x[k], complex(Decimal(math.cos(theta)), Decimal(math.sin(theta))), 1)")
+ print(
+ " cnf.add_weight(x[k], complex(Decimal(math.cos(theta)), Decimal(math.sin(theta))), 1)"
+ )
print()
-
# Synthesis
- add_synthesis()
+ add_synthesis()
def add_synthesis():
# dynamic single bit gate:
- idg = symbols('idg[k]')
- hg = symbols('hg[k]')
- tg = symbols('tg[k]')
- tdg = symbols('tdg[k]')
+ idg = symbols("idg[k]")
+ hg = symbols("hg[k]")
+ tg = symbols("tg[k]")
+ tdg = symbols("tdg[k]")
- Xk = symbols('X[k]')
- Rk = symbols('R[k]')
- Uk = symbols('U[k]')
- Wpk = symbols('Wp[k]')
- Wnk = symbols('Wn[k]')
+ Xk = symbols("X[k]")
+ Rk = symbols("R[k]")
+ Uk = symbols("U[k]")
+ Wpk = symbols("Wp[k]")
+ Wnk = symbols("Wn[k]")
I_x = idg >> Equivalent(Xk, x[k])
I_r = idg >> Equivalent(Rk, False)
@@ -280,24 +288,25 @@ def add_synthesis():
T_x = (tg | tdg) >> Equivalent(Xk, x[k])
T_r = (tg | tdg) >> Equivalent(Rk, False)
T_u = (tg | tdg) >> Equivalent(Uk, False)
- T_w = ((tg >> (Equivalent(Wpk, x[k]) & Equivalent(Wnk, False))) &
- (tdg >> (Equivalent(Wnk, x[k]) & Equivalent(Wpk, False))) )
+ T_w = (tg >> (Equivalent(Wpk, x[k]) & Equivalent(Wnk, False))) & (
+ tdg >> (Equivalent(Wnk, x[k]) & Equivalent(Wpk, False))
+ )
single_qb_gate_properties = [I_x, I_r, I_u, I_w, H_r, H_u, H_w, T_x, T_r, T_u, T_w]
-
+
# dynamic two bit gate:
- cg_ct = symbols('cg[c][t]')
- Xcc = symbols('X[c]')
- Xtt = symbols('X[t]')
- Rc = symbols('R[c]')
- Rt = symbols('R[t]')
- Uc = symbols('U[c]')
- Ut = symbols('U[t]')
- Wpc = symbols('Wp[c]')
- Wpt = symbols('Wp[t]')
- Wnc = symbols('Wn[c]')
- Wnt = symbols('Wn[t]')
+ cg_ct = symbols("cg[c][t]")
+ Xcc = symbols("X[c]")
+ Xtt = symbols("X[t]")
+ Rc = symbols("R[c]")
+ Rt = symbols("R[t]")
+ Uc = symbols("U[c]")
+ Ut = symbols("U[t]")
+ Wpc = symbols("Wp[c]")
+ Wpt = symbols("Wp[t]")
+ Wnc = symbols("Wn[c]")
+ Wnt = symbols("Wn[t]")
CX_xc = cg_ct >> Equivalent(Xcc, x[c])
CX_xt = cg_ct >> Equivalent(Xtt, x[t] ^ x[c])
@@ -323,7 +332,7 @@ def add_synthesis():
print()
print()
- print('''
+ print("""
def SynLayer2CNF(cnf):
n = cnf.n
x = cnf.vars.x
@@ -343,18 +352,18 @@ def SynLayer2CNF(cnf):
tdg = [cnf.add_var(syn_gate_pick = True, Name = 'tdg', bits = [k]) for k in range(n)]
cg = [[cnf.add_var(syn_gate_pick = True, Name = 'cx', bits = [c,t]) if c!=t else None for t in range(n)] for c in range(n)]
for k in range(n):
- ''')
+ """)
for p in single_qb_gate_properties:
- to_py( p, prefix=" ")
- print('''
+ to_py(p, prefix=" ")
+ print("""
c = k
for t in range(n):
if t==c:
continue
- ''')
+ """)
for p in double_qb_gate_properties:
- to_py( p, prefix=" ")
- print('''
+ to_py(p, prefix=" ")
+ print("""
cgs_k = [cg[k][i] for i in range(n) if i!=k] + [cg[i][k] for i in range(n) if i!=k]
gate_controlers = [idg[k], hg[k], tg[k], tdg[k]]+cgs_k
@@ -394,7 +403,7 @@ def SynLayer2CNF(cnf):
cnf.vars.x[:n] = X
- ''')
+ """)
if __name__ == "__main__":
diff --git a/quokka_sharp/comput2cnf_unweighted_py_codegen.py b/quokka_sharp/comput2cnf_unweighted_py_codegen.py
index 4df6a0d9..4b5e0168 100644
--- a/quokka_sharp/comput2cnf_unweighted_py_codegen.py
+++ b/quokka_sharp/comput2cnf_unweighted_py_codegen.py
@@ -1,34 +1,41 @@
from comput2cnf_py_codegen import *
-def add_sign(func, prefix = "", comment=""):
- print(prefix+f" # adding sign if {func}")
- print(prefix+" R = cnf.add_var()")
- print(prefix+" cnf.vars.RVar.append(R)")
- print(prefix+" r = cnf.vars.r")
- print(prefix+" cnf.vars.r = R")
- to_py( Equivalent(R, r ^ func), prefix, comment="- "+comment)
-
-def add_i(func, prefix = "", comment=""):
- print(prefix+f" # adding i if {func}")
- print(prefix+" I = cnf.add_var()")
- print(prefix+" cnf.vars.IVar.append(I)")
- print(prefix+" i = cnf.vars.i")
- to_py( Equivalent(I, i ^ func), prefix, comment="i "+comment)
+
+def add_sign(func, prefix="", comment=""):
+ print(prefix + f" # adding sign if {func}")
+ print(prefix + " R = cnf.add_var()")
+ print(prefix + " cnf.vars.RVar.append(R)")
+ print(prefix + " r = cnf.vars.r")
+ print(prefix + " cnf.vars.r = R")
+ to_py(Equivalent(R, r ^ func), prefix, comment="- " + comment)
+
+
+def add_i(func, prefix="", comment=""):
+ print(prefix + f" # adding i if {func}")
+ print(prefix + " I = cnf.add_var()")
+ print(prefix + " cnf.vars.IVar.append(I)")
+ print(prefix + " i = cnf.vars.i")
+ to_py(Equivalent(I, i ^ func), prefix, comment="i " + comment)
add_sign(i & ~I, prefix)
- print(prefix+" cnf.vars.i = I")
+ print(prefix + " cnf.vars.i = I")
+
-def add_sqrt_half(func, prefix = "", comment=""):
- print(prefix+" U = cnf.add_var()")
- print(prefix+" cnf.vars.UVar.append(U)")
- print(prefix+" cnf.power_two_normalisation += 0.5 ")
- print(prefix+" u = cnf.vars.u")
- print(prefix+" cnf.vars.u = U")
- to_py( Equivalent(U, u ^ ~func), prefix, comment="sqrt U update "+comment)
+def add_sqrt_half(func, prefix="", comment=""):
+ print(prefix + " U = cnf.add_var()")
+ print(prefix + " cnf.vars.UVar.append(U)")
+ print(prefix + " cnf.power_two_normalisation += 0.5 ")
+ print(prefix + " u = cnf.vars.u")
+ print(prefix + " cnf.vars.u = U")
+ to_py(Equivalent(U, u ^ ~func), prefix, comment="sqrt U update " + comment)
print()
- print(prefix+" D = cnf.add_var()")
- print(prefix+" cnf.vars.DVar.append(D)")
- print(prefix+f" cnf.add_clause([D, cnf.add_var()], comment=\"sqrt D double {comment}\")")
- to_py( Equivalent(D, u & ~U), prefix, comment="sqrt D update "+comment)
+ print(prefix + " D = cnf.add_var()")
+ print(prefix + " cnf.vars.DVar.append(D)")
+ print(
+ prefix
+ + f' cnf.add_clause([D, cnf.add_var()], comment="sqrt D double {comment}")'
+ )
+ to_py(Equivalent(D, u & ~U), prefix, comment="sqrt D update " + comment)
+
def main():
@@ -40,9 +47,9 @@ def main():
print("from .pauli2cnf import pauli2cnf")
print()
print("class comput2cnf:")
- print(''' \"\"\"
+ print(""" \"\"\"
This class contains the functions to convert a quantum circuit to CNF clauses in the Computationl basis.
- \"\"\"''')
+ \"\"\"""")
print()
# Initialization:
@@ -58,7 +65,7 @@ def main():
# H:
print(" def H2CNF(cnf, k):")
print(" x = cnf.vars.x")
- print()
+ print()
print(" X = cnf.add_var()")
print(" cnf.vars.XVar.append(X)")
print()
@@ -69,68 +76,70 @@ def main():
print(" cnf.vars.x[k] = X")
print()
- #CNOT
+ # CNOT
print(" def CNOT2CNF(cnf, c, t):")
print(" x = cnf.vars.x")
print()
print(" Xt = cnf.add_var()")
print(" cnf.vars.XVar.append(Xt)")
- to_py( Equivalent(Xt, (x[c] ^ x[t])))
+ to_py(Equivalent(Xt, (x[c] ^ x[t])))
print()
print(" cnf.vars.x[t] = Xt")
print()
- #TOFFOLI
+ # TOFFOLI
print(" def CCX2CNF(cnf, k, c, t):")
print(" x = cnf.vars.x")
print()
print(" Xt = cnf.add_var()")
print(" cnf.vars.XVar.append(Xt)")
- to_py( Equivalent(Xt, ((x[k] & x[c]) ^ x[t])))
+ to_py(Equivalent(Xt, ((x[k] & x[c]) ^ x[t])))
print()
print(" cnf.vars.x[t] = Xt")
print()
- #Z
+ # Z
print(" def Z2CNF(cnf, k):")
print(" x = cnf.vars.x")
print()
add_sign(x[k])
print()
- #RZ(theta)
+ # RZ(theta)
print(" def RZ2CNF(cnf, k, theta):")
print(" x = cnf.vars.x")
print()
print(" w = cnf.add_var()")
print(" cnf.vars.UVar.append(w)")
- print(" cnf.add_weight(w, complex(Decimal(math.cos(theta)), Decimal(math.sin(theta))), 1)")
- to_py( Equivalent(w, x[k]), comment="w (RZ)")
+ print(
+ " cnf.add_weight(w, complex(Decimal(math.cos(theta)), Decimal(math.sin(theta))), 1)"
+ )
+ to_py(Equivalent(w, x[k]), comment="w (RZ)")
print()
- #S
+ # S
print(" def S2CNF(cnf, k):")
print(" x = cnf.vars.x")
print()
add_i(x[k], comment="(S)")
print()
- #Sdg
+ # Sdg
print(" def Sdg2CNF(cnf, k):")
- print(" x = cnf.vars.x")
+ print(" x = cnf.vars.x")
add_i(x[k], comment="(Sdg)")
print()
add_sign(x[k], comment="(Sdg)")
print()
- #T
+ # T
print(" def T2CNF(cnf, k):")
print(" x = cnf.vars.x")
print(" i = cnf.vars.i")
print()
print(" I = cnf.add_var()")
print(" cnf.vars.IVar.append(I)")
- to_py( x[k] | Equivalent(I, i), comment="I (T)")
+ to_py(x[k] | Equivalent(I, i), comment="I (T)")
print()
add_sign(i & ~I, comment="(T)")
print()
@@ -138,14 +147,14 @@ def main():
print()
print(" cnf.vars.i = I")
- #Tdg
+ # Tdg
print(" def Tdg2CNF(cnf, k):")
print(" x = cnf.vars.x")
print(" i = cnf.vars.i")
print()
print(" I = cnf.add_var()")
print(" cnf.vars.IVar.append(I)")
- to_py( x[k] | Equivalent(I, i), comment="I (Tdg)")
+ to_py(x[k] | Equivalent(I, i), comment="I (Tdg)")
print()
add_sign(~i & I, comment="(Tdg)")
print()
@@ -153,24 +162,24 @@ def main():
print()
print(" cnf.vars.i = I")
- #X
+ # X
print(" def X2CNF(cnf, k):")
print(" x = cnf.vars.x")
print()
print(" X = cnf.add_var()")
print(" cnf.vars.XVar.append(X)")
- to_py( Equivalent(X, ~x[k]))
+ to_py(Equivalent(X, ~x[k]))
print()
print(" cnf.vars.x[k] = X")
print()
- #Y
+ # Y
print(" def Y2CNF(cnf, k):")
print(" x = cnf.vars.x")
print()
print(" X = cnf.add_var()")
print(" cnf.vars.XVar.append(X)")
- to_py( Equivalent(X, ~x[k]), comment="Y gate")
+ to_py(Equivalent(X, ~x[k]), comment="Y gate")
print()
add_sign(x[k])
print()
@@ -179,7 +188,7 @@ def main():
print(" cnf.vars.x[k] = X")
print()
- #RX(theta)
+ # RX(theta)
print(" def RX2CNF(cnf, k, theta):")
print(" x = cnf.vars.x")
print(" X = cnf.add_var()")
@@ -187,24 +196,26 @@ def main():
print()
print(" w = cnf.add_var()")
print(" cnf.vars.UVar.append(w)")
- to_py( Equivalent(w, Equivalent(x[k], X) ))
+ to_py(Equivalent(w, Equivalent(x[k], X)))
print()
print(" cnf.vars.x[k] = X")
print()
- print(" cnf.add_weight(w, complex(Decimal(math.cos(theta/2)), Decimal(math.sin(theta/2))), complex(0, -Decimal(math.sin(theta/2))))")
+ print(
+ " cnf.add_weight(w, complex(Decimal(math.cos(theta/2)), Decimal(math.sin(theta/2))), complex(0, -Decimal(math.sin(theta/2))))"
+ )
print()
- #CZ
+ # CZ
print(" def CZ2CNF(cnf, c, t):")
print(" x = cnf.vars.x")
print()
print(" Xc = cnf.add_var()")
print(" cnf.vars.XVar.append(Xc)")
- to_py( Equivalent(Xc, x[c]))
+ to_py(Equivalent(Xc, x[c]))
print()
print(" Xt = cnf.add_var()")
print(" cnf.vars.XVar.append(Xt)")
- to_py( Equivalent(Xt, x[t]))
+ to_py(Equivalent(Xt, x[t]))
print()
print(" cnf.vars.x[c] = Xc")
print(" cnf.vars.x[t] = Xt")
@@ -212,16 +223,16 @@ def main():
add_sign(x[c] & x[t])
print()
- #SWAP
+ # SWAP
print(" def SWAP2CNF(cnf, c, t):")
print(" x = cnf.vars.x")
print()
print(" x[c], x[t] = x[t], x[c]")
print()
-
# Synthesis
add_synthesis()
+
if __name__ == "__main__":
main()
diff --git a/quokka_sharp/pauli2cnf_py_codegen.py b/quokka_sharp/pauli2cnf_py_codegen.py
index ff06c5f4..a452596d 100644
--- a/quokka_sharp/pauli2cnf_py_codegen.py
+++ b/quokka_sharp/pauli2cnf_py_codegen.py
@@ -1,25 +1,25 @@
from sympy.logic.boolalg import *
from sympy.logic import simplify_logic
-from sympy import symbols
+from sympy import symbols
-r = symbols('r')
-R = symbols('R')
+r = symbols("r")
+R = symbols("R")
-u = symbols('u')
-U = symbols('U')
-d = symbols('d')
-D = symbols('D')
+u = symbols("u")
+U = symbols("U")
+d = symbols("d")
+D = symbols("D")
-u1 = symbols('u1')
-u2 = symbols('u2')
+u1 = symbols("u1")
+u2 = symbols("u2")
c = 0
t = 1
k = 2
-x = [0,0,0]
-z = [0,0,0]
+x = [0, 0, 0]
+z = [0, 0, 0]
x[k] = symbols("x[k]")
x[c] = symbols("x[c]")
x[t] = symbols("x[t]")
@@ -27,43 +27,48 @@
z[c] = symbols("z[c]")
z[t] = symbols("z[t]")
-X = symbols('X')
-Z = symbols('Z')
+X = symbols("X")
+Z = symbols("Z")
+
def strstrip(item1):
- return item1.lstrip(' ()~')
+ return item1.lstrip(" ()~")
+
def to_py(func, prefix="", simplify=True, force=True):
- print(prefix+" # "+str(func))
+ print(prefix + " # " + str(func))
s = str(to_cnf(func, simplify=simplify, force=force))
for a in s.split("&"):
- print(prefix+" cnf.add_clause([", end="")
+ print(prefix + " cnf.add_clause([", end="")
l = a.split("|")
l.sort(key=strstrip)
for x in l:
- b = x.strip(' ()')
- if b.startswith('~'):
- b = b.replace('~', '-')
+ b = x.strip(" ()")
+ if b.startswith("~"):
+ b = b.replace("~", "-")
else:
- b = " "+ b
+ b = " " + b
print(b, end="")
if x != l[-1]:
print(", ", end="")
print("])")
-def add_sign(func, prefix = ""):
- print(prefix+f" # adding sign if {func}")
- print(prefix+" R = cnf.add_var()")
- print(prefix+" cnf.vars.RVar.append(R)")
- print(prefix+" cnf.add_weight(R, -1, 1)")
- to_py( Equivalent(R, func), prefix)
-def add_sqrt_half(func, prefix = ""):
- print(prefix+f" # adding sqrt_half if {func}")
- print(prefix+" U = cnf.add_var()")
- print(prefix+" cnf.vars.UVar.append(U)")
- print(prefix+" cnf.add_weight(U, (Decimal(1) / Decimal(2)).sqrt(), 1)")
- to_py( Equivalent(U, func), prefix)
+def add_sign(func, prefix=""):
+ print(prefix + f" # adding sign if {func}")
+ print(prefix + " R = cnf.add_var()")
+ print(prefix + " cnf.vars.RVar.append(R)")
+ print(prefix + " cnf.add_weight(R, -1, 1)")
+ to_py(Equivalent(R, func), prefix)
+
+
+def add_sqrt_half(func, prefix=""):
+ print(prefix + f" # adding sqrt_half if {func}")
+ print(prefix + " U = cnf.add_var()")
+ print(prefix + " cnf.vars.UVar.append(U)")
+ print(prefix + " cnf.add_weight(U, (Decimal(1) / Decimal(2)).sqrt(), 1)")
+ to_py(Equivalent(U, func), prefix)
+
def main():
@@ -73,14 +78,14 @@ def main():
print("getcontext().prec = 32")
print()
print("class pauli2cnf:")
- print(''' \"\"\"
+ print(""" \"\"\"
This class contains the functions to convert a quantum circuit to CNF clauses in the Pauli basis.
- \"\"\"''')
+ \"\"\"""")
print()
# Initialization:
print(" def init(cnf):")
- print(" return")
+ print(" return")
print()
# H:
@@ -120,7 +125,7 @@ def main():
print()
print(" Z = cnf.add_var()")
print(" cnf.vars.ZVar.append(Z)")
- to_py( Equivalent(Z, x[k] ^ z[k]))
+ to_py(Equivalent(Z, x[k] ^ z[k]))
print()
add_sign(x[k] & z[k])
print()
@@ -134,22 +139,22 @@ def main():
print()
print(" Z = cnf.add_var()")
print(" cnf.vars.ZVar.append(Z)")
- to_py( Equivalent(Z, x[k] ^ z[k]))
+ to_py(Equivalent(Z, x[k] ^ z[k]))
print()
add_sign(x[k] & ~z[k])
- print()
+ print()
print(" cnf.vars.z[k] = Z")
print()
-
+
# T:
print(" def T2CNF(cnf, k):")
print(" x = cnf.vars.x")
print(" z = cnf.vars.z")
- print()
+ print()
print(" Z = cnf.add_var()")
print(" cnf.vars.ZVar.append(Z)")
- to_py( x[k] | Equivalent(Z, z[k]))
- print()
+ to_py(x[k] | Equivalent(Z, z[k]))
+ print()
add_sqrt_half(x[k])
print()
add_sign(x[k] & z[k] & ~Z)
@@ -162,15 +167,15 @@ def main():
print(" x = cnf.vars.x")
print(" z = cnf.vars.z")
print()
- print(" Z = cnf.add_var()")
+ print(" Z = cnf.add_var()")
print(" cnf.vars.ZVar.append(Z)")
- to_py( x[k] | Equivalent(Z, z[k]))
- print()
+ to_py(x[k] | Equivalent(Z, z[k]))
+ print()
add_sqrt_half(x[k])
print()
add_sign(x[k] & ~z[k] & Z)
print()
- print(" cnf.vars.z[k] = Z")
+ print(" cnf.vars.z[k] = Z")
print()
# CNOT
@@ -180,11 +185,11 @@ def main():
print()
print(" X = cnf.add_var()")
print(" cnf.vars.XVar.append(X)")
- to_py( Equivalent(X, x[t] ^ x[c]))
+ to_py(Equivalent(X, x[t] ^ x[c]))
print()
print(" Z = cnf.add_var()")
print(" cnf.vars.ZVar.append(Z)")
- to_py( Equivalent(Z, z[t] ^ z[c]))
+ to_py(Equivalent(Z, z[t] ^ z[c]))
print()
add_sign(x[c] & z[t] & (~x[t] ^ z[c]))
print()
@@ -192,20 +197,20 @@ def main():
print(" cnf.vars.z[c] = Z")
print()
- #CZ
- Zc = symbols('Zc')
- Zt = symbols('Zt')
+ # CZ
+ Zc = symbols("Zc")
+ Zt = symbols("Zt")
print(" def CZ2CNF(cnf, c, t):")
print(" x = cnf.vars.x")
print(" z = cnf.vars.z")
print()
print(" Zc = cnf.add_var()")
print(" cnf.vars.ZVar.append(Zc)")
- to_py( Equivalent(Zc, z[c] ^ x[t]))
+ to_py(Equivalent(Zc, z[c] ^ x[t]))
print()
print(" Zt = cnf.add_var()")
print(" cnf.vars.ZVar.append(Zt)")
- to_py( Equivalent(Zt, z[t] ^ x[c]))
+ to_py(Equivalent(Zt, z[t] ^ x[c]))
print()
add_sign(x[t] & x[c] & (z[t] ^ z[c]))
print()
@@ -213,14 +218,14 @@ def main():
print(" cnf.vars.z[t] = Zt")
print()
- #CY
+ # CY
print(" def CY2CNF(cnf, c, t):")
print(" pauli2cnf.Sdg2CNF(cnf, t)")
print(" pauli2cnf.CNOT2CNF(cnf, c, t)")
print(" pauli2cnf.S2CNF(cnf, t)")
print()
- #SWAP
+ # SWAP
print(" def SWAP2CNF(cnf, c, t):")
print(" x = cnf.vars.x")
print(" z = cnf.vars.z")
@@ -229,7 +234,7 @@ def main():
print(" z[c], z[t] = z[t], z[c]")
print()
- #ISWAP
+ # ISWAP
print(" def ISWAP2CNF(cnf, c, t):")
print(" pauli2cnf.SWAP2CNF(cnf, c, t)")
print(" pauli2cnf.CZ2CNF(cnf, c, t)")
@@ -237,83 +242,91 @@ def main():
print(" pauli2cnf.S2CNF(cnf, t)")
print()
- #CS
+ # CS
print(" def CS2CNF(cnf, c, t):")
print(" x = cnf.vars.x")
print(" z = cnf.vars.z")
print()
print(" Zc = cnf.add_var()")
print(" cnf.vars.ZVar.append(Zc)")
- to_py( x[c] | x[t] | Equivalent(Zc, z[c]))
+ to_py(x[c] | x[t] | Equivalent(Zc, z[c]))
print()
print(" Zt = cnf.add_var()")
print(" cnf.vars.ZVar.append(Zt)")
- to_py( x[c] | x[t] | Equivalent(Zt, z[t]))
+ to_py(x[c] | x[t] | Equivalent(Zt, z[t]))
print()
- add_sign( (x[c] & z[c] & ~Zc) ^ (x[t] & z[t] & ~Zt) ^ ((~x[c] | ~x[t]) & (z[t] ^ Zt) & (z[c] ^ Zc) ))
+ add_sign(
+ (x[c] & z[c] & ~Zc)
+ ^ (x[t] & z[t] & ~Zt)
+ ^ ((~x[c] | ~x[t]) & (z[t] ^ Zt) & (z[c] ^ Zc))
+ )
print()
print(" u = cnf.add_var()")
print(" cnf.vars.UVar.append(u)")
print(" cnf.add_weight(u, Decimal(1) / Decimal(2), 1)")
- to_py( Equivalent(u, x[c] | x[t]))
+ to_py(Equivalent(u, x[c] | x[t]))
print()
print(" cnf.vars.z[c] = Zc")
print(" cnf.vars.z[t] = Zt")
print()
- #CSdg
+ # CSdg
print(" def CSdg2CNF(cnf, c, t):")
print(" x = cnf.vars.x")
print(" z = cnf.vars.z")
print()
print(" Zc = cnf.add_var()")
print(" cnf.vars.ZVar.append(Zc)")
- to_py( x[c] | x[t] | Equivalent(Zc, z[c]))
+ to_py(x[c] | x[t] | Equivalent(Zc, z[c]))
print()
print(" Zt = cnf.add_var()")
print(" cnf.vars.ZVar.append(Zt)")
- to_py( x[c] | x[t] | Equivalent(Zt, z[t]))
+ to_py(x[c] | x[t] | Equivalent(Zt, z[t]))
print()
- add_sign( (x[c] & ~z[c] & Zc) ^ (x[t] & ~z[t] & Zt) ^ ((~x[c] | ~x[t]) & (z[t] ^ Zt) & (z[c] ^ Zc) ))
+ add_sign(
+ (x[c] & ~z[c] & Zc)
+ ^ (x[t] & ~z[t] & Zt)
+ ^ ((~x[c] | ~x[t]) & (z[t] ^ Zt) & (z[c] ^ Zc))
+ )
print()
print(" u = cnf.add_var()")
print(" cnf.vars.UVar.append(u)")
print(" cnf.add_weight(u, Decimal(1) / Decimal(2), 1)")
- to_py( Equivalent(u, x[c] | x[t]))
+ to_py(Equivalent(u, x[c] | x[t]))
print()
print(" cnf.vars.z[c] = Zc")
print(" cnf.vars.z[t] = Zt")
print()
# C√X
- Xt = symbols('Xt')
+ Xt = symbols("Xt")
print(" def CSqrtX2CNF(cnf, c, t):")
print(" x = cnf.vars.x")
print(" z = cnf.vars.z")
print()
print(" Zc = cnf.add_var()")
print(" cnf.vars.ZVar.append(Zc)")
- to_py( x[c] | z[t] | Equivalent(Zc, z[c]))
+ to_py(x[c] | z[t] | Equivalent(Zc, z[c]))
print()
print(" Xt = cnf.add_var()")
print(" cnf.vars.XVar.append(Xt)")
- to_py( x[c] | z[t] | Equivalent(Xt, x[t]))
- print()
- add_sign(
- (x[c] & x[t] & Xt & z[c] & ~Zc) |
- (Xt & z[c] & Zc & z[t] & ~x[t]) |
- (x[c] & Xt & Zc & ~x[t] & ~z[c]) |
- (x[t] & z[c] & z[t] & ~Xt & ~Zc) |
- (x[c] & z[c] & ~x[t] & ~Xt & ~Zc) |
- (Xt & z[t] & ~x[t] & ~z[c] & ~Zc) |
- (x[c] & x[t] & Zc & ~Xt & ~z[c] & ~z[t]) |
- (x[t] & Zc & z[t] & ~x[c] & ~Xt & ~z[c])
- )
+ to_py(x[c] | z[t] | Equivalent(Xt, x[t]))
+ print()
+ add_sign(
+ (x[c] & x[t] & Xt & z[c] & ~Zc)
+ | (Xt & z[c] & Zc & z[t] & ~x[t])
+ | (x[c] & Xt & Zc & ~x[t] & ~z[c])
+ | (x[t] & z[c] & z[t] & ~Xt & ~Zc)
+ | (x[c] & z[c] & ~x[t] & ~Xt & ~Zc)
+ | (Xt & z[t] & ~x[t] & ~z[c] & ~Zc)
+ | (x[c] & x[t] & Zc & ~Xt & ~z[c] & ~z[t])
+ | (x[t] & Zc & z[t] & ~x[c] & ~Xt & ~z[c])
+ )
print()
print(" u = cnf.add_var()")
print(" cnf.vars.UVar.append(u)")
print(" cnf.add_weight(u, Decimal(1) / Decimal(2), 1)")
- to_py( Equivalent(u, x[c] | z[t]))
+ to_py(Equivalent(u, x[c] | z[t]))
print()
print(" cnf.vars.z[c] = Zc")
print(" cnf.vars.x[t] = Xt")
@@ -326,33 +339,33 @@ def main():
print()
print(" Zc = cnf.add_var()")
print(" cnf.vars.ZVar.append(Zc)")
- to_py( x[c] | z[t] | Equivalent(Zc, z[c]))
+ to_py(x[c] | z[t] | Equivalent(Zc, z[c]))
print()
print(" Xt = cnf.add_var()")
print(" cnf.vars.XVar.append(Xt)")
- to_py( x[c] | z[t] | Equivalent(Xt, x[t]))
- print()
- add_sign(
- (x[c] & x[t] & Xt & Zc & ~z[c]) |
- (x[t] & z[c] & Zc & z[t] & ~Xt) |
- (x[c] & x[t] & z[c] & ~Xt & ~Zc) |
- (Xt & Zc & z[t] & ~x[t] & ~z[c]) |
- (x[c] & Zc & ~x[t] & ~Xt & ~z[c]) |
- (x[t] & z[t] & ~Xt & ~z[c] & ~Zc) |
- (x[c] & Xt & z[c] & ~x[t] & ~Zc & ~z[t]) |
- (Xt & z[c] & z[t] & ~x[c] & ~x[t] & ~Zc)
- )
+ to_py(x[c] | z[t] | Equivalent(Xt, x[t]))
+ print()
+ add_sign(
+ (x[c] & x[t] & Xt & Zc & ~z[c])
+ | (x[t] & z[c] & Zc & z[t] & ~Xt)
+ | (x[c] & x[t] & z[c] & ~Xt & ~Zc)
+ | (Xt & Zc & z[t] & ~x[t] & ~z[c])
+ | (x[c] & Zc & ~x[t] & ~Xt & ~z[c])
+ | (x[t] & z[t] & ~Xt & ~z[c] & ~Zc)
+ | (x[c] & Xt & z[c] & ~x[t] & ~Zc & ~z[t])
+ | (Xt & z[c] & z[t] & ~x[c] & ~x[t] & ~Zc)
+ )
print()
print(" u = cnf.add_var()")
print(" cnf.vars.UVar.append(u)")
print(" cnf.add_weight(u, Decimal(1) / Decimal(2), 1)")
- to_py( Equivalent(u, x[c] | z[t]))
+ to_py(Equivalent(u, x[c] | z[t]))
print()
print(" cnf.vars.z[c] = Zc")
print(" cnf.vars.x[t] = Xt")
print()
- #CCX
+ # CCX
print(" def CCX2CNF(cnf, k, c, t):")
print(" pauli2cnf.CSqrtX2CNF(cnf, k, t)")
print(" pauli2cnf.CSqrtX2CNF(cnf, c, t)")
@@ -361,24 +374,24 @@ def main():
print(" pauli2cnf.CNOT2CNF(cnf, k, c)")
print()
- #RX
+ # RX
print(" def RX2CNF(cnf, k, theta):")
print(" x = cnf.vars.x")
print(" z = cnf.vars.z")
print()
print(" X = cnf.add_var()")
print(" cnf.vars.XVar.append(X)")
- to_py( z[k] | Equivalent(X, x[k]))
- print()
+ to_py(z[k] | Equivalent(X, x[k]))
+ print()
print(" u1 = cnf.add_var()")
print(" cnf.vars.UVar.append(u1)")
print(" cnf.add_weight(u1, Decimal(math.cos(theta)), 1)")
- to_py( Equivalent(u1, z[k] & ((x[k] & X) | (~x[k] & ~X))))
- print()
+ to_py(Equivalent(u1, z[k] & ((x[k] & X) | (~x[k] & ~X))))
+ print()
print(" u2 = cnf.add_var()")
print(" cnf.vars.UVar.append(u2)")
print(" cnf.add_weight(u2, Decimal(math.sin(theta)), 1)")
- to_py( Equivalent(u2, z[k] & ((~x[k] & X) | (x[k] & ~X))))
+ to_py(Equivalent(u2, z[k] & ((~x[k] & X) | (x[k] & ~X))))
print()
add_sign(z[k] & ~x[k] & X)
print()
@@ -393,17 +406,17 @@ def main():
print()
print(" Z = cnf.add_var()")
print(" cnf.vars.ZVar.append(Z)")
- to_py( x[k] | Equivalent(Z, z[k]))
+ to_py(x[k] | Equivalent(Z, z[k]))
print()
print(" u1 = cnf.add_var()")
print(" cnf.vars.UVar.append(u1)")
print(" cnf.add_weight(u1, Decimal(math.cos(theta)), 1)")
- to_py( Equivalent(u1, x[k] & (((z[k] & Z)) | (~z[k] & ~Z))))
- print()
+ to_py(Equivalent(u1, x[k] & ((z[k] & Z) | (~z[k] & ~Z))))
+ print()
print(" u2 = cnf.add_var()")
print(" cnf.vars.UVar.append(u2)")
print(" cnf.add_weight(u2, Decimal(math.sin(theta)), 1)")
- to_py( Equivalent(u2, x[k] & (((~z[k] & Z)) | (z[k] & ~Z))))
+ to_py(Equivalent(u2, x[k] & ((~z[k] & Z) | (z[k] & ~Z))))
print()
add_sign(x[k] & z[k] & ~Z)
print()
@@ -416,8 +429,9 @@ def main():
# synthesis layer
add_synthesis()
+
def add_composition():
- print('''
+ print("""
def Composition(cnf, composition_dictionary):
x, z = zip(*[(cnf.add_var(), cnf.add_var()) for _ in range(cnf.n)])
weights = []
@@ -480,9 +494,9 @@ def Composition(cnf, composition_dictionary):
# cnf.add_clause([w])
return x,z,comp
- ''')
+ """)
- print('''
+ print("""
def Composition2CNF(cnf, composition_dictionary):
assert composition_dictionary["qubits"] == cnf.n
if cnf.n > 1:
@@ -499,54 +513,63 @@ def Composition2CNF(cnf, composition_dictionary):
for k in range(cnf.n):
X = Xs[k]
Z = Zs[k]
- ''')
- lx = symbols('lx[k]')
- lz = symbols('lz[k]')
- rx = symbols('rx[k]')
- rz = symbols('rz[k]')
-
- to_py(Equivalent(X, lx^x[k]^rx), prefix=" ")
- to_py(Equivalent(Z, lz^z[k]^rz), prefix=" ")
-
- add_sign(And(Or(( lx & ~x[k] & rx),
- ( ~lx & x[k] & ~rx),
- ( lz & ~z[k] & rz),
- ( ~lz & z[k] & ~rz)),
- Or(lx, lz),
- Or(x[k], z[k])), prefix=" ")
-
- to_py(Or(And(Equivalent(lx,x[k]),
- Equivalent(lz,z[k])),
- And(Equivalent(rx,x[k]),
- Equivalent(rz,z[k])),
- And(Equivalent(lx,rx),
- Equivalent(lz,rz)) ), prefix=" ")
-
- rc = symbols('rc')
- lc = symbols('lc')
+ """)
+ lx = symbols("lx[k]")
+ lz = symbols("lz[k]")
+ rx = symbols("rx[k]")
+ rz = symbols("rz[k]")
+
+ to_py(Equivalent(X, lx ^ x[k] ^ rx), prefix=" ")
+ to_py(Equivalent(Z, lz ^ z[k] ^ rz), prefix=" ")
+
+ add_sign(
+ And(
+ Or(
+ (lx & ~x[k] & rx),
+ (~lx & x[k] & ~rx),
+ (lz & ~z[k] & rz),
+ (~lz & z[k] & ~rz),
+ ),
+ Or(lx, lz),
+ Or(x[k], z[k]),
+ ),
+ prefix=" ",
+ )
+
+ to_py(
+ Or(
+ And(Equivalent(lx, x[k]), Equivalent(lz, z[k])),
+ And(Equivalent(rx, x[k]), Equivalent(rz, z[k])),
+ And(Equivalent(lx, rx), Equivalent(lz, rz)),
+ ),
+ prefix=" ",
+ )
+
+ rc = symbols("rc")
+ lc = symbols("lc")
to_py(Equivalent(rc, lc), prefix="")
- print(f'''
+ print(f"""
cnf.vars.x = Xs
cnf.vars.z = Zs
- ''')
+ """)
# =============================[ Synthesis ]============================== #
def add_synthesis():
### dynamic single bit gate (selectors used on the synthesis side) ###
- idg = symbols('idg[k]')
- hg = symbols('hg[k]')
- sg = symbols('sg[k]')
- sdg = symbols('sdg[k]')
- tg = symbols('tg[k]')
- tdg = symbols('tdg[k]')
+ idg = symbols("idg[k]")
+ hg = symbols("hg[k]")
+ sg = symbols("sg[k]")
+ sdg = symbols("sdg[k]")
+ tg = symbols("tg[k]")
+ tdg = symbols("tdg[k]")
# per-qubit next state and weights/signs used in the emitted code
- Xk = symbols('X[k]')
- Zk = symbols('Z[k]')
- Rk = symbols('R[k]')
- Uk = symbols('U[k]')
+ Xk = symbols("X[k]")
+ Zk = symbols("Z[k]")
+ Rk = symbols("R[k]")
+ Uk = symbols("U[k]")
# Single-qubit property formulas (left side is the selector)
# Identity
@@ -562,29 +585,29 @@ def add_synthesis():
H_u = hg >> Equivalent(Uk, False)
# S / Sdg
- S_r = sg >> Equivalent(Rk, x[k] & z[k])
- Sdg_r = sdg >> Equivalent(Rk, x[k] & ~z[k])
- S_x = (sg | sdg) >> Equivalent(Xk, x[k])
- S_z = (sg | sdg) >> Equivalent(Zk, x[k] ^ z[k])
- S_u = (sg | sdg) >> Equivalent(Uk, False)
+ S_r = sg >> Equivalent(Rk, x[k] & z[k])
+ Sdg_r = sdg >> Equivalent(Rk, x[k] & ~z[k])
+ S_x = (sg | sdg) >> Equivalent(Xk, x[k])
+ S_z = (sg | sdg) >> Equivalent(Zk, x[k] ^ z[k])
+ S_u = (sg | sdg) >> Equivalent(Uk, False)
# T / Tdg
- T_r = tg >> Equivalent(Rk, x[k] & z[k] & ~Zk)
- Tdg_r = tdg >> Equivalent(Rk, x[k] & ~z[k] & Zk)
- T_x = (tg | tdg) >> Equivalent(Xk, x[k])
- T_z = (tg | tdg) >> Equivalent(Zk, z[k]) | x[k]
- T_u = (tg | tdg) >> Equivalent(Uk, x[k])
+ T_r = tg >> Equivalent(Rk, x[k] & z[k] & ~Zk)
+ Tdg_r = tdg >> Equivalent(Rk, x[k] & ~z[k] & Zk)
+ T_x = (tg | tdg) >> Equivalent(Xk, x[k])
+ T_z = (tg | tdg) >> Equivalent(Zk, z[k]) | x[k]
+ T_u = (tg | tdg) >> Equivalent(Uk, x[k])
### dynamic two bit gate (selectors used on the synthesis side) ###
- cxgate_ct = symbols('cxgate[c][t]')
- Xc = symbols('X[c]')
- Xt = symbols('X[t]')
- Zc = symbols('Z[c]')
- Zt = symbols('Z[t]')
- Rc = symbols('R[c]')
- Rt = symbols('R[t]')
- Uc = symbols('U[c]')
- Ut = symbols('U[t]')
+ cxgate_ct = symbols("cxgate[c][t]")
+ Xc = symbols("X[c]")
+ Xt = symbols("X[t]")
+ Zc = symbols("Z[c]")
+ Zt = symbols("Z[t]")
+ Rc = symbols("R[c]")
+ Rt = symbols("R[t]")
+ Uc = symbols("U[c]")
+ Ut = symbols("U[t]")
# CX properties
CX_xc = cxgate_ct >> Equivalent(Xc, x[c])
@@ -597,7 +620,7 @@ def add_synthesis():
CX_ut = cxgate_ct >> Equivalent(Ut, False)
# CZ properties
- czgate_ct = symbols('czgate[c][t]')
+ czgate_ct = symbols("czgate[c][t]")
CZ_xc = czgate_ct >> Equivalent(Xc, x[c])
CZ_xt = czgate_ct >> Equivalent(Xt, x[t])
CZ_zc = czgate_ct >> Equivalent(Zc, z[c] ^ x[t])
@@ -608,37 +631,39 @@ def add_synthesis():
CZ_ut = czgate_ct >> Equivalent(Ut, False)
# CSqrtX / CSqrtXdg properties
- csqrtxgate_ct = symbols('csqrtxgate[c][t]')
- csqrtxdggate_ct = symbols('csqrtxdggate[c][t]')
+ csqrtxgate_ct = symbols("csqrtxgate[c][t]")
+ csqrtxdggate_ct = symbols("csqrtxdggate[c][t]")
CSqrtX_xc = (csqrtxgate_ct | csqrtxdggate_ct) >> Equivalent(Xc, x[c])
CSqrtX_xt = (csqrtxgate_ct | csqrtxdggate_ct) >> Equivalent(Xt, x[t]) | x[c] | z[t]
CSqrtX_zc = (csqrtxgate_ct | csqrtxdggate_ct) >> Equivalent(Zc, z[c]) | x[c] | z[t]
CSqrtX_zt = (csqrtxgate_ct | csqrtxdggate_ct) >> Equivalent(Zt, z[t])
- CSqrtX_rc = csqrtxgate_ct >> Equivalent(Rc,
- (x[c] & x[t] & Xt & z[c] & ~Zc) |
- (Xt & z[c] & Zc & z[t] & ~x[t]) |
- (x[c] & Xt & Zc & ~x[t] & ~z[c]) |
- (x[t] & z[c] & z[t] & ~Xt & ~Zc) |
- (x[c] & z[c] & ~x[t] & ~Xt & ~Zc) |
- (Xt & z[t] & ~x[t] & ~z[c] & ~Zc) |
- (x[c] & x[t] & Zc & ~Xt & ~z[c] & ~z[t]) |
- (x[t] & Zc & z[t] & ~x[c] & ~Xt & ~z[c])
- )
- CSqrtXdg_rc = csqrtxdggate_ct >> Equivalent(Rc,
- (x[c] & x[t] & Xt & Zc & ~z[c]) |
- (x[t] & z[c] & Zc & z[t] & ~Xt) |
- (x[c] & x[t] & z[c] & ~Xt & ~Zc) |
- (Xt & Zc & z[t] & ~x[t] & ~z[c]) |
- (x[c] & Zc & ~x[t] & ~Xt & ~z[c]) |
- (x[t] & z[t] & ~Xt & ~z[c] & ~Zc) |
- (x[c] & Xt & z[c] & ~x[t] & ~Zc & ~z[t]) |
- (Xt & z[c] & z[t] & ~x[c] & ~x[t] & ~Zc)
- )
+ CSqrtX_rc = csqrtxgate_ct >> Equivalent(
+ Rc,
+ (x[c] & x[t] & Xt & z[c] & ~Zc)
+ | (Xt & z[c] & Zc & z[t] & ~x[t])
+ | (x[c] & Xt & Zc & ~x[t] & ~z[c])
+ | (x[t] & z[c] & z[t] & ~Xt & ~Zc)
+ | (x[c] & z[c] & ~x[t] & ~Xt & ~Zc)
+ | (Xt & z[t] & ~x[t] & ~z[c] & ~Zc)
+ | (x[c] & x[t] & Zc & ~Xt & ~z[c] & ~z[t])
+ | (x[t] & Zc & z[t] & ~x[c] & ~Xt & ~z[c]),
+ )
+ CSqrtXdg_rc = csqrtxdggate_ct >> Equivalent(
+ Rc,
+ (x[c] & x[t] & Xt & Zc & ~z[c])
+ | (x[t] & z[c] & Zc & z[t] & ~Xt)
+ | (x[c] & x[t] & z[c] & ~Xt & ~Zc)
+ | (Xt & Zc & z[t] & ~x[t] & ~z[c])
+ | (x[c] & Zc & ~x[t] & ~Xt & ~z[c])
+ | (x[t] & z[t] & ~Xt & ~z[c] & ~Zc)
+ | (x[c] & Xt & z[c] & ~x[t] & ~Zc & ~z[t])
+ | (Xt & z[c] & z[t] & ~x[c] & ~x[t] & ~Zc),
+ )
CSqrtX_rt = (csqrtxgate_ct | csqrtxdggate_ct) >> Equivalent(Rt, False)
CSqrtX_uc = (csqrtxgate_ct | csqrtxdggate_ct) >> Equivalent(Ut, False)
CSqrtX_ut = (csqrtxgate_ct | csqrtxdggate_ct) >> Equivalent(Uc, x[c] | z[t])
- print('''
+ print("""
def AMO(cnf, var_list):
assert None not in var_list
# at least one:
@@ -646,7 +671,7 @@ def AMO(cnf, var_list):
# at most one:
[cnf.add_clause([-var_list[a], -var_list[b]])
for a in range(len(var_list)) for b in range(a+1, len(var_list))]
- ''')
+ """)
print('''
def SynLayer2CNF(cnf, gate_set=set(), limit_gates=False, h_layer=False):
@@ -757,11 +782,11 @@ def SynLayer2CNF(cnf, gate_set=set(), limit_gates=False, h_layer=False):
print()
print(" if ENABLE_S and (not limit_gates or not h_layer):")
- to_py(S_r, prefix=" ")
- to_py(Sdg_r, prefix=" ")
- to_py(S_x, prefix=" ")
- to_py(S_z, prefix=" ")
- to_py(S_u, prefix=" ")
+ to_py(S_r, prefix=" ")
+ to_py(Sdg_r, prefix=" ")
+ to_py(S_x, prefix=" ")
+ to_py(S_z, prefix=" ")
+ to_py(S_u, prefix=" ")
print()
print(" if ENABLE_H and (not limit_gates or h_layer):")
@@ -773,13 +798,13 @@ def SynLayer2CNF(cnf, gate_set=set(), limit_gates=False, h_layer=False):
print(" if ENABLE_T and (not limit_gates or not h_layer):")
to_py(Tdg_r, prefix=" ")
- to_py(T_r, prefix=" ")
- to_py(T_x, prefix=" ")
- to_py(T_z, prefix=" ")
- to_py(T_u, prefix=" ")
+ to_py(T_r, prefix=" ")
+ to_py(T_x, prefix=" ")
+ to_py(T_z, prefix=" ")
+ to_py(T_u, prefix=" ")
print()
- print('''
+ print("""
c = k
for t in range(n):
if t == c:
@@ -787,7 +812,7 @@ def SynLayer2CNF(cnf, gate_set=set(), limit_gates=False, h_layer=False):
# CX properties
if ENABLE_CX and (not limit_gates or not h_layer):
- ''')
+ """)
to_py(CX_xc, prefix=" ")
to_py(CX_xt, prefix=" ")
to_py(CX_zc, prefix=" ")
@@ -797,10 +822,10 @@ def SynLayer2CNF(cnf, gate_set=set(), limit_gates=False, h_layer=False):
to_py(CX_uc, prefix=" ")
to_py(CX_ut, prefix=" ")
- print('''
+ print("""
# CZ properties
if ENABLE_CZ and (not limit_gates or not h_layer):
- ''')
+ """)
to_py(CZ_xc, prefix=" ")
to_py(CZ_xt, prefix=" ")
to_py(CZ_zc, prefix=" ")
@@ -810,21 +835,21 @@ def SynLayer2CNF(cnf, gate_set=set(), limit_gates=False, h_layer=False):
to_py(CZ_uc, prefix=" ")
to_py(CZ_ut, prefix=" ")
- print('''
+ print("""
# CSqrtX / CSqrtXdg properties
if ENABLE_CSQRTX and (not limit_gates or not h_layer):
- ''')
- to_py(CSqrtX_xc, prefix=" ")
- to_py(CSqrtX_xt, prefix=" ")
- to_py(CSqrtX_zc, prefix=" ")
- to_py(CSqrtX_zt, prefix=" ")
- to_py(CSqrtX_rc, prefix=" ")
+ """)
+ to_py(CSqrtX_xc, prefix=" ")
+ to_py(CSqrtX_xt, prefix=" ")
+ to_py(CSqrtX_zc, prefix=" ")
+ to_py(CSqrtX_zt, prefix=" ")
+ to_py(CSqrtX_rc, prefix=" ")
to_py(CSqrtXdg_rc, prefix=" ")
- to_py(CSqrtX_rt, prefix=" ")
- to_py(CSqrtX_uc, prefix=" ")
- to_py(CSqrtX_ut, prefix=" ")
+ to_py(CSqrtX_rt, prefix=" ")
+ to_py(CSqrtX_uc, prefix=" ")
+ to_py(CSqrtX_ut, prefix=" ")
- print('''
+ print("""
# Build AMO controller set
def integer_only(lst):
return [v for v in lst if isinstance(v, int) and v != 0]
@@ -901,7 +926,8 @@ def integer_only(lst):
cnf.vars.x[:n] = X
cnf.vars.z[:n] = Z
- ''')
+ """)
+
if __name__ == "__main__":
main()
diff --git a/quokka_sharp/pauli2cnf_unweighted_py_codegen.py b/quokka_sharp/pauli2cnf_unweighted_py_codegen.py
index f33eda15..4ccd3e26 100644
--- a/quokka_sharp/pauli2cnf_unweighted_py_codegen.py
+++ b/quokka_sharp/pauli2cnf_unweighted_py_codegen.py
@@ -1,25 +1,28 @@
from pauli2cnf_py_codegen import *
-def add_sign(func, prefix = ""):
- print(prefix+f" # adding sign if {func}")
- print(prefix+" R = cnf.add_var()")
- print(prefix+" cnf.vars.RVar.append(R)")
- print(prefix+" r = cnf.vars.r")
- print(prefix+" cnf.vars.r = R")
- to_py( Equivalent(R, r ^ func), prefix)
-
-def add_sqrt_half(func, prefix = ""):
- print(prefix+f" # adding sqrt_half if {func}")
- print(prefix+" U = cnf.add_var()")
- print(prefix+" cnf.vars.UVar.append(U)")
- print(prefix+" cnf.power_two_normalisation += 0.5 ")
- print(prefix+" u = cnf.vars.u")
- print(prefix+" cnf.vars.u = U")
- to_py( Equivalent(U, u ^ ~func), prefix)
- print()
- print(prefix+" D = cnf.add_var()")
- print(prefix+" cnf.add_clause([D, cnf.add_var()])")
- to_py( Equivalent(D, u & ~U), prefix)
+
+def add_sign(func, prefix=""):
+ print(prefix + f" # adding sign if {func}")
+ print(prefix + " R = cnf.add_var()")
+ print(prefix + " cnf.vars.RVar.append(R)")
+ print(prefix + " r = cnf.vars.r")
+ print(prefix + " cnf.vars.r = R")
+ to_py(Equivalent(R, r ^ func), prefix)
+
+
+def add_sqrt_half(func, prefix=""):
+ print(prefix + f" # adding sqrt_half if {func}")
+ print(prefix + " U = cnf.add_var()")
+ print(prefix + " cnf.vars.UVar.append(U)")
+ print(prefix + " cnf.power_two_normalisation += 0.5 ")
+ print(prefix + " u = cnf.vars.u")
+ print(prefix + " cnf.vars.u = U")
+ to_py(Equivalent(U, u ^ ~func), prefix)
+ print()
+ print(prefix + " D = cnf.add_var()")
+ print(prefix + " cnf.add_clause([D, cnf.add_var()])")
+ to_py(Equivalent(D, u & ~U), prefix)
+
def main():
@@ -29,9 +32,9 @@ def main():
print("getcontext().prec = 32")
print()
print("class pauli2cnf:")
- print(''' \"\"\"
+ print(""" \"\"\"
This class contains the functions to convert a quantum circuit to CNF clauses in the Pauli basis.
- \"\"\"''')
+ \"\"\"""")
print()
# Initialization:
@@ -39,7 +42,7 @@ def main():
print(" cnf.r = cnf.add_var()")
print(" cnf.add_clause([-cnf.r])")
print(" cnf.u = cnf.add_var()")
- print(" cnf.add_clause([-cnf.u])")
+ print(" cnf.add_clause([-cnf.u])")
print()
# H:
@@ -81,7 +84,7 @@ def main():
print()
print(" Z = cnf.add_var()")
print(" cnf.vars.ZVar.append(Z)")
- to_py( Equivalent(Z, x[k] ^ z[k]))
+ to_py(Equivalent(Z, x[k] ^ z[k]))
print()
add_sign(x[k] & z[k])
print()
@@ -95,22 +98,22 @@ def main():
print()
print(" Z = cnf.add_var()")
print(" cnf.vars.ZVar.append(Z)")
- to_py( Equivalent(Z, x[k] ^ z[k]))
+ to_py(Equivalent(Z, x[k] ^ z[k]))
print()
add_sign(x[k] & ~z[k])
- print()
+ print()
print(" cnf.vars.z[k] = Z")
print()
-
+
# T:
print(" def T2CNF(cnf, k):")
print(" x = cnf.vars.x")
print(" z = cnf.vars.z")
- print()
+ print()
print(" Z = cnf.add_var()")
print(" cnf.vars.ZVar.append(Z)")
- to_py( x[k] | Equivalent(Z, z[k]))
- print()
+ to_py(x[k] | Equivalent(Z, z[k]))
+ print()
add_sqrt_half(x[k])
print()
add_sign(x[k] & z[k] & ~Z)
@@ -123,15 +126,15 @@ def main():
print(" x = cnf.vars.x")
print(" z = cnf.vars.z")
print()
- print(" Z = cnf.add_var()")
+ print(" Z = cnf.add_var()")
print(" cnf.vars.ZVar.append(Z)")
- to_py( x[k] | Equivalent(Z, z[k]))
- print()
+ to_py(x[k] | Equivalent(Z, z[k]))
+ print()
add_sqrt_half(x[k])
print()
add_sign(x[k] & ~z[k] & Z)
print()
- print(" cnf.vars.z[k] = Z")
+ print(" cnf.vars.z[k] = Z")
print()
# CNOT
@@ -141,32 +144,32 @@ def main():
print()
print(" X = cnf.add_var()")
print(" cnf.vars.XVar.append(X)")
- to_py( Equivalent(X, x[t] ^ x[c]))
+ to_py(Equivalent(X, x[t] ^ x[c]))
print()
print(" Z = cnf.add_var()")
print(" cnf.vars.ZVar.append(Z)")
- to_py( Equivalent(Z, z[t] ^ z[c]))
+ to_py(Equivalent(Z, z[t] ^ z[c]))
print()
add_sign(x[c] & z[t] & (~x[t] ^ z[c]))
print()
print(" cnf.vars.x[t] = X")
print(" cnf.vars.z[c] = Z")
print()
-
- #CZ
- Zc = symbols('Zc')
- Zt = symbols('Zt')
+
+ # CZ
+ Zc = symbols("Zc")
+ Zt = symbols("Zt")
print(" def CZ2CNF(cnf, c, t):")
print(" x = cnf.vars.x")
print(" z = cnf.vars.z")
print()
print(" Zc = cnf.add_var()")
print(" cnf.vars.ZVar.append(Zc)")
- to_py( Equivalent(Zc, z[c] ^ x[t]))
+ to_py(Equivalent(Zc, z[c] ^ x[t]))
print()
print(" Zt = cnf.add_var()")
print(" cnf.vars.ZVar.append(Zt)")
- to_py( Equivalent(Zt, z[t] ^ x[c]))
+ to_py(Equivalent(Zt, z[t] ^ x[c]))
print()
add_sign(x[t] & x[c] & (z[t] ^ z[c]))
print()
@@ -174,14 +177,14 @@ def main():
print(" cnf.vars.z[t] = Zt")
print()
- #CY
+ # CY
print(" def CY2CNF(cnf, c, t):")
print(" pauli2cnf.Sdg2CNF(cnf, t)")
print(" pauli2cnf.CNOT2CNF(cnf, c, t)")
print(" pauli2cnf.S2CNF(cnf, t)")
print()
- #SWAP
+ # SWAP
print(" def SWAP2CNF(cnf, c, t):")
print(" x = cnf.vars.x")
print(" z = cnf.vars.z")
@@ -190,7 +193,7 @@ def main():
print(" z[c], z[t] = z[t], z[c]")
print()
- #ISWAP
+ # ISWAP
print(" def ISWAP2CNF(cnf, c, t):")
print(" pauli2cnf.SWAP2CNF(cnf, c, t)")
print(" pauli2cnf.CZ2CNF(cnf, c, t)")
@@ -198,83 +201,91 @@ def main():
print(" pauli2cnf.S2CNF(cnf, t)")
print()
- #CS
+ # CS
print(" def CS2CNF(cnf, c, t):")
print(" x = cnf.vars.x")
print(" z = cnf.vars.z")
print()
print(" Zc = cnf.add_var()")
print(" cnf.vars.ZVar.append(Zc)")
- to_py( x[c] | x[t] | Equivalent(Zc, z[c]))
+ to_py(x[c] | x[t] | Equivalent(Zc, z[c]))
print()
print(" Zt = cnf.add_var()")
print(" cnf.vars.ZVar.append(Zt)")
- to_py( x[c] | x[t] | Equivalent(Zt, z[t]))
+ to_py(x[c] | x[t] | Equivalent(Zt, z[t]))
print()
- add_sign( (x[c] & z[c] & ~Zc) ^ (x[t] & z[t] & ~Zt) ^ ((~x[c] | ~x[t]) & (z[t] ^ Zt) & (z[c] ^ Zc) ))
+ add_sign(
+ (x[c] & z[c] & ~Zc)
+ ^ (x[t] & z[t] & ~Zt)
+ ^ ((~x[c] | ~x[t]) & (z[t] ^ Zt) & (z[c] ^ Zc))
+ )
print()
print(" u = cnf.add_var()")
print(" cnf.vars.UVar.append(u)")
print(" cnf.add_weight(u, Decimal(1) / Decimal(2), 1)")
- to_py( Equivalent(u, x[c] | x[t]))
+ to_py(Equivalent(u, x[c] | x[t]))
print()
print(" cnf.vars.z[c] = Zc")
print(" cnf.vars.z[t] = Zt")
print()
- #CSdg
+ # CSdg
print(" def CSdg2CNF(cnf, c, t):")
print(" x = cnf.vars.x")
print(" z = cnf.vars.z")
print()
print(" Zc = cnf.add_var()")
print(" cnf.vars.ZVar.append(Zc)")
- to_py( x[c] | x[t] | Equivalent(Zc, z[c]))
+ to_py(x[c] | x[t] | Equivalent(Zc, z[c]))
print()
print(" Zt = cnf.add_var()")
print(" cnf.vars.ZVar.append(Zt)")
- to_py( x[c] | x[t] | Equivalent(Zt, z[t]))
+ to_py(x[c] | x[t] | Equivalent(Zt, z[t]))
print()
- add_sign( (x[c] & ~z[c] & Zc) ^ (x[t] & ~z[t] & Zt) ^ ((~x[c] | ~x[t]) & (z[t] ^ Zt) & (z[c] ^ Zc) ))
+ add_sign(
+ (x[c] & ~z[c] & Zc)
+ ^ (x[t] & ~z[t] & Zt)
+ ^ ((~x[c] | ~x[t]) & (z[t] ^ Zt) & (z[c] ^ Zc))
+ )
print()
print(" u = cnf.add_var()")
print(" cnf.vars.UVar.append(u)")
print(" cnf.add_weight(u, Decimal(1) / Decimal(2), 1)")
- to_py( Equivalent(u, x[c] | x[t]))
+ to_py(Equivalent(u, x[c] | x[t]))
print()
print(" cnf.vars.z[c] = Zc")
print(" cnf.vars.z[t] = Zt")
print()
# C√X
- Xt = symbols('Xt')
+ Xt = symbols("Xt")
print(" def CSqrtX2CNF(cnf, c, t):")
print(" x = cnf.vars.x")
print(" z = cnf.vars.z")
print()
print(" Zc = cnf.add_var()")
print(" cnf.vars.ZVar.append(Zc)")
- to_py( x[c] | z[t] | Equivalent(Zc, z[c]))
+ to_py(x[c] | z[t] | Equivalent(Zc, z[c]))
print()
print(" Xt = cnf.add_var()")
print(" cnf.vars.XVar.append(Xt)")
- to_py( x[c] | z[t] | Equivalent(Xt, x[t]))
- print()
- add_sign(
- (x[c] & x[t] & Xt & z[c] & ~Zc) |
- (Xt & z[c] & Zc & z[t] & ~x[t]) |
- (x[c] & Xt & Zc & ~x[t] & ~z[c]) |
- (x[t] & z[c] & z[t] & ~Xt & ~Zc) |
- (x[c] & z[c] & ~x[t] & ~Xt & ~Zc) |
- (Xt & z[t] & ~x[t] & ~z[c] & ~Zc) |
- (x[c] & x[t] & Zc & ~Xt & ~z[c] & ~z[t]) |
- (x[t] & Zc & z[t] & ~x[c] & ~Xt & ~z[c])
- )
+ to_py(x[c] | z[t] | Equivalent(Xt, x[t]))
+ print()
+ add_sign(
+ (x[c] & x[t] & Xt & z[c] & ~Zc)
+ | (Xt & z[c] & Zc & z[t] & ~x[t])
+ | (x[c] & Xt & Zc & ~x[t] & ~z[c])
+ | (x[t] & z[c] & z[t] & ~Xt & ~Zc)
+ | (x[c] & z[c] & ~x[t] & ~Xt & ~Zc)
+ | (Xt & z[t] & ~x[t] & ~z[c] & ~Zc)
+ | (x[c] & x[t] & Zc & ~Xt & ~z[c] & ~z[t])
+ | (x[t] & Zc & z[t] & ~x[c] & ~Xt & ~z[c])
+ )
print()
print(" u = cnf.add_var()")
print(" cnf.vars.UVar.append(u)")
print(" cnf.add_weight(u, Decimal(1) / Decimal(2), 1)")
- to_py( Equivalent(u, x[c] | z[t]))
+ to_py(Equivalent(u, x[c] | z[t]))
print()
print(" cnf.vars.z[c] = Zc")
print(" cnf.vars.x[t] = Xt")
@@ -287,33 +298,33 @@ def main():
print()
print(" Zc = cnf.add_var()")
print(" cnf.vars.ZVar.append(Zc)")
- to_py( x[c] | z[t] | Equivalent(Zc, z[c]))
+ to_py(x[c] | z[t] | Equivalent(Zc, z[c]))
print()
print(" Xt = cnf.add_var()")
print(" cnf.vars.XVar.append(Xt)")
- to_py( x[c] | z[t] | Equivalent(Xt, x[t]))
- print()
- add_sign(
- (x[c] & x[t] & Xt & Zc & ~z[c]) |
- (x[t] & z[c] & Zc & z[t] & ~Xt) |
- (x[c] & x[t] & z[c] & ~Xt & ~Zc) |
- (Xt & Zc & z[t] & ~x[t] & ~z[c]) |
- (x[c] & Zc & ~x[t] & ~Xt & ~z[c]) |
- (x[t] & z[t] & ~Xt & ~z[c] & ~Zc) |
- (x[c] & Xt & z[c] & ~x[t] & ~Zc & ~z[t]) |
- (Xt & z[c] & z[t] & ~x[c] & ~x[t] & ~Zc)
- )
+ to_py(x[c] | z[t] | Equivalent(Xt, x[t]))
+ print()
+ add_sign(
+ (x[c] & x[t] & Xt & Zc & ~z[c])
+ | (x[t] & z[c] & Zc & z[t] & ~Xt)
+ | (x[c] & x[t] & z[c] & ~Xt & ~Zc)
+ | (Xt & Zc & z[t] & ~x[t] & ~z[c])
+ | (x[c] & Zc & ~x[t] & ~Xt & ~z[c])
+ | (x[t] & z[t] & ~Xt & ~z[c] & ~Zc)
+ | (x[c] & Xt & z[c] & ~x[t] & ~Zc & ~z[t])
+ | (Xt & z[c] & z[t] & ~x[c] & ~x[t] & ~Zc)
+ )
print()
print(" u = cnf.add_var()")
print(" cnf.vars.UVar.append(u)")
print(" cnf.add_weight(u, Decimal(1) / Decimal(2), 1)")
- to_py( Equivalent(u, x[c] | z[t]))
+ to_py(Equivalent(u, x[c] | z[t]))
print()
print(" cnf.vars.z[c] = Zc")
print(" cnf.vars.x[t] = Xt")
print()
- #CCX
+ # CCX
print(" def CCX2CNF(cnf, k, c, t):")
print(" pauli2cnf.CSqrtX2CNF(cnf, k, t)")
print(" pauli2cnf.CSqrtX2CNF(cnf, c, t)")
@@ -329,48 +340,47 @@ def main():
print()
print(" Z = cnf.add_var()")
print(" cnf.vars.ZVar.append(Z)")
- to_py( x[k] | Equivalent(Z, z[k]))
+ to_py(x[k] | Equivalent(Z, z[k]))
print()
print(" u1 = cnf.add_var()")
print(" cnf.vars.UVar.append(u1)")
print(" cnf.add_weight(u1, Decimal(math.cos(theta)), 1)")
- to_py( Equivalent(u1, x[k] & (((z[k] & Z)) | (~z[k] & ~Z))))
- print()
+ to_py(Equivalent(u1, x[k] & ((z[k] & Z) | (~z[k] & ~Z))))
+ print()
print(" u2 = cnf.add_var()")
print(" cnf.vars.UVar.append(u2)")
print(" cnf.add_weight(u2, Decimal(math.sin(theta)), 1)")
- to_py( Equivalent(u2, x[k] & (((~z[k] & Z)) | (z[k] & ~Z))))
+ to_py(Equivalent(u2, x[k] & ((~z[k] & Z) | (z[k] & ~Z))))
print()
add_sign(x[k] & z[k] & ~Z)
print()
print(" cnf.vars.z[k] = Z")
print()
- #RX
+ # RX
print(" def RX2CNF(cnf, k, theta):")
print(" x = cnf.vars.x")
print(" z = cnf.vars.z")
print()
print(" X = cnf.add_var()")
print(" cnf.vars.XVar.append(X)")
- to_py( z[k] | Equivalent(X, x[k]))
- print()
+ to_py(z[k] | Equivalent(X, x[k]))
+ print()
print(" u1 = cnf.add_var()")
print(" cnf.vars.UVar.append(u1)")
print(" cnf.add_weight(u1, Decimal(math.cos(theta)), 1)")
- to_py( Equivalent(u1, z[k] & ((x[k] & X) | (~x[k] & ~X))))
- print()
+ to_py(Equivalent(u1, z[k] & ((x[k] & X) | (~x[k] & ~X))))
+ print()
print(" u2 = cnf.add_var()")
print(" cnf.vars.UVar.append(u2)")
print(" cnf.add_weight(u2, Decimal(math.sin(theta)), 1)")
- to_py( Equivalent(u2, z[k] & ((~x[k] & X) | (x[k] & ~X))))
+ to_py(Equivalent(u2, z[k] & ((~x[k] & X) | (x[k] & ~X))))
print()
add_sign(z[k] & ~x[k] & X)
print()
print(" cnf.vars.x[k] = X")
print()
print()
-
# composition layer
add_composition()
@@ -378,5 +388,6 @@ def main():
# synthesis layer
add_synthesis()
+
if __name__ == "__main__":
main()
diff --git a/quokka_sharp/quokka_sharp/__init__.py b/quokka_sharp/quokka_sharp/__init__.py
index 51450bf4..90104cde 100644
--- a/quokka_sharp/quokka_sharp/__init__.py
+++ b/quokka_sharp/quokka_sharp/__init__.py
@@ -10,4 +10,3 @@
from .encoding import Variables
from .encoding import QASMparser
from .encoding import QASM2CNF
-
diff --git a/quokka_sharp/quokka_sharp/config.py b/quokka_sharp/quokka_sharp/config.py
index 0a3be9ea..7c402509 100644
--- a/quokka_sharp/quokka_sharp/config.py
+++ b/quokka_sharp/quokka_sharp/config.py
@@ -10,13 +10,14 @@
"ToolInvocation": "gpmc -mode=1",
"D4ToolInvocation": "maxT_static",
"GetResult": "exact.double.prec-sci.(.+?)\\\\nc s",
- "FPE" : 1e-12,
- "Precision" : 50
+ "FPE": 1e-12,
+ "Precision": 50,
}
+
def load_config():
config_path = os.getenv("QUOKKA_CONFIG", None)
-
+
if config_path and os.path.isfile(config_path):
try:
with open(config_path, "r") as f:
@@ -24,11 +25,13 @@ def load_config():
return {**DEFAULT_CONFIG, **user_config}
except Exception as e:
raise RuntimeError(f"Failed to load config file: {e}")
-
+
return DEFAULT_CONFIG
+
CONFIG = load_config()
+
def reload_config():
"""
Reload CONFIG from `config_path` (if provided) or from env var QUOKKA_CONFIG.
diff --git a/quokka_sharp/quokka_sharp/encoding/cnf.py b/quokka_sharp/quokka_sharp/encoding/cnf.py
index 44a6ec5f..45204fd4 100644
--- a/quokka_sharp/quokka_sharp/encoding/cnf.py
+++ b/quokka_sharp/quokka_sharp/encoding/cnf.py
@@ -7,13 +7,21 @@
from .utils import rlist
from .. import config as qc
-DEBUG = qc.CONFIG["DEBUG"]
+DEBUG = qc.CONFIG["DEBUG"]
+
class Variables:
"""
Class to manage the variables of the CNF encoding.
"""
- def __init__(self, cnf: 'CNF', computational_basis=False, unitary_encoding = False, Unitary = None):
+
+ def __init__(
+ self,
+ cnf: "CNF",
+ computational_basis=False,
+ unitary_encoding=False,
+ Unitary=None,
+ ):
"""
Args:
cnf (CNF): The CNF object to which the variables belong.
@@ -23,10 +31,10 @@ def __init__(self, cnf: 'CNF', computational_basis=False, unitary_encoding = Fal
"""
self.cnf = cnf
self.n = cnf.n + cnf.ancillas
- self.i = -1 # HACK: only for unweighted
- self.r = -1 # HACK: only for unweighted
- self.u = -1 # HACK: only for unweighted
- self.Udim = 2 ** self.n
+ self.i = -1 # HACK: only for unweighted
+ self.r = -1 # HACK: only for unweighted
+ self.u = -1 # HACK: only for unweighted
+ self.Udim = 2**self.n
self.var = 0
self.XVar = []
self.ZVar = []
@@ -46,7 +54,7 @@ def __init__(self, cnf: 'CNF', computational_basis=False, unitary_encoding = Fal
znew = self.add_var()
self.z.append(znew)
self.ZVar.append(znew)
- if i >= cnf.n: # ancilla init I
+ if i >= cnf.n: # ancilla init I
cnf.add_clause([-self.x[-1]])
if not self.computational_basis:
cnf.add_clause([-self.z[-1]])
@@ -60,9 +68,16 @@ def __init__(self, cnf: 'CNF', computational_basis=False, unitary_encoding = Fal
self.cnf.add_clause([-self.Unitaryvar[i][j]])
else:
if Unitary[i][j].imag == 0:
- self.cnf.add_weight(self.Unitaryvar[i][j], Unitary[i][j].real, 1)
+ self.cnf.add_weight(
+ self.Unitaryvar[i][j], Unitary[i][j].real, 1
+ )
else:
- self.cnf.add_weight(self.Unitaryvar[i][j], complex(Unitary[i][j].real, Unitary[i][j].imag), 1)
+ self.cnf.add_weight(
+ self.Unitaryvar[i][j],
+ complex(Unitary[i][j].real, Unitary[i][j].imag),
+ 1,
+ )
+
def copy(self):
"""
Create a deep copy of the Variables object.
@@ -91,7 +106,7 @@ def measurement(self, basis, prepend=False):
"""
Add a measurement clause to the CNF encoding.
Args:
- basis (str or dict): The measurement basis. Can be "allzero", "firstzero", or a dictionary specifying the measurement.
+ basis (str or dict): The measurement basis. Can be "allzero", "firstzero", or a dictionary specifying the measurement.
- "allzero": All qubits are measured in the |0> state.
- "firstzero": The first qubit is measured in the |0> state, and the rest are measured in the I state.
- dict: A dictionary specifying the measurement for each qubit e.g. {0: 1, 1: 0} means qubit 0 is measured in the |1> state and qubit 1 in the |0> state.
@@ -121,7 +136,9 @@ def measurement(self, basis, prepend=False):
self.projector(basis, var_curr=self.var, prepend=False)
self.normalize(len(basis))
else:
- Exception("Please choose firstzero, allzero or a list of qubits measurement")
+ Exception(
+ "Please choose firstzero, allzero or a list of qubits measurement"
+ )
def normalize(self, inc):
"""
@@ -129,7 +146,7 @@ def normalize(self, inc):
"""
if self.computational_basis == False:
self.cnf.power_two_normalisation += inc
-
+
def projectAllZero(self, prepend=False):
"""
Add a clause to project all qubits to the |0> state.
@@ -147,11 +164,13 @@ def projectZXi(self, Z_or_X, idx, prepend=False):
idx (int): The index of the qubit to project.
prepend (bool): If True, the clause is added at the beginning of the CNF encoding, otherwise at the end.
"""
- assert(not self.computational_basis)
+ assert not self.computational_basis
if Z_or_X:
- off_var = self.x; on_var = self.z
+ off_var = self.x
+ on_var = self.z
else:
- on_var = self.x; off_var = self.z
+ on_var = self.x
+ off_var = self.z
for i in range(self.n):
self.cnf.add_clause([-off_var[i]], prepend)
if i == idx:
@@ -160,7 +179,7 @@ def projectZXi(self, Z_or_X, idx, prepend=False):
self.cnf.add_clause([-on_var[i]], prepend)
def projectPauli(self, spec, prepend):
- assert(not self.computational_basis)
+ assert not self.computational_basis
x = self.x
z = self.z
qubitset = list(spec.keys())
@@ -171,17 +190,16 @@ def projectPauli(self, spec, prepend):
self.cnf.add_clause([-z[i]], prepend)
elif spec[i] == "Y":
self.cnf.add_clause([x[i]], prepend)
- self.cnf.add_clause([z[i]], prepend)
+ self.cnf.add_clause([z[i]], prepend)
elif spec[i] == "Z":
self.cnf.add_clause([-x[i]], prepend)
- self.cnf.add_clause([z[i]], prepend)
+ self.cnf.add_clause([z[i]], prepend)
elif spec[i] == "I":
self.cnf.add_clause([-x[i]], prepend)
- self.cnf.add_clause([-z[i]], prepend)
+ self.cnf.add_clause([-z[i]], prepend)
else:
self.cnf.add_clause([-x[i]], prepend)
self.cnf.add_clause([-z[i]], prepend)
-
def projector(self, spec, var_curr, prepend):
"""
@@ -192,13 +210,13 @@ def projector(self, spec, var_curr, prepend):
"""
x = self.x
qubitset = list(spec.keys())
-
+
var_increase = 0
-
+
if self.computational_basis:
# precondition
- for i in qubitset:
- if spec[i] == 0:
+ for i in qubitset:
+ if spec[i] == 0:
self.cnf.add_clause([-x[i]], prepend)
else:
self.cnf.add_clause([x[i]], prepend)
@@ -216,16 +234,16 @@ def projector(self, spec, var_curr, prepend):
var_curr += 1
var_increase += 1
R = var_curr
- # X2CNF flip 1 to 0
+ # X2CNF flip 1 to 0
self.cnf.add_weight(R, -1, 1)
# Equivalent(R, z[k])
- self.cnf.add_clause([ R, -z[i]], prepend)
- self.cnf.add_clause([-R, z[i]], prepend)
-
+ self.cnf.add_clause([R, -z[i]], prepend)
+ self.cnf.add_clause([-R, z[i]], prepend)
+
else:
- self.cnf.add_clause([-z[i]], prepend)
- return var_increase
-
+ self.cnf.add_clause([-z[i]], prepend)
+ return var_increase
+
def encode_unitary_static(self, U):
"""
Encode a unitary matrix into the CNF encoding.
@@ -234,9 +252,9 @@ def encode_unitary_static(self, U):
"""
assert self.cnf.unitary_encoding
assert U.shape[0] == U.shape[1]
- assert U.shape[0] == 2 ** self.n
- # is_unitary, num_qubits = check_unitary_and_qubits(U)
- Udim = 2 ** self.n
+ assert U.shape[0] == 2**self.n
+ # is_unitary, num_qubits = check_unitary_and_qubits(U)
+ Udim = 2**self.n
Uvar = [[0 for _ in range(Udim)] for _ in range(Udim)]
for i in range(Udim):
for j in range(Udim):
@@ -245,13 +263,23 @@ def encode_unitary_static(self, U):
self.cnf.add_clause([-Uvar[i][j]])
else:
self.cnf.add_weight(Uvar[i][j], U[i][j], 1)
- # add weight Uvar[i][j] --- U[i][j] and not Uvar[i][j] --- 1
-
+ # add weight Uvar[i][j] --- U[i][j] and not Uvar[i][j] --- 1
+
+
class CNF:
"""
Class to manage the CNF encoding of a quantum circuit.
"""
- def __init__(self, n = 0, ancillas=0, computational_basis=False, ganak = False, unitary_encoding = False, Unitary = None):
+
+ def __init__(
+ self,
+ n=0,
+ ancillas=0,
+ computational_basis=False,
+ ganak=False,
+ unitary_encoding=False,
+ Unitary=None,
+ ):
self.clause = 0
self.n = n
self.ancillas = ancillas
@@ -271,7 +299,7 @@ def __init__(self, n = 0, ancillas=0, computational_basis=False, ganak = False,
self.unitary_encoding = unitary_encoding
self.Unitary = Unitary
if unitary_encoding:
- try:
+ try:
is_unitary, num_qubits = check_unitary_and_qubits(Unitary)
except:
assert "Please give the matrix in np arrays"
@@ -279,31 +307,35 @@ def __init__(self, n = 0, ancillas=0, computational_basis=False, ganak = False,
assert "The matrix is not unitary matrix!"
else:
self.n = num_qubits
- dim = 2 ** num_qubits
-
- self.vars = Variables(self, computational_basis, unitary_encoding, Unitary) # variables at timestep m (end of circuit)
- self.vars_init = self.vars.copy() # variables at timestep 0
-
+ dim = 2**num_qubits
+
+ self.vars = Variables(
+ self, computational_basis, unitary_encoding, Unitary
+ ) # variables at timestep m (end of circuit)
+ self.vars_init = self.vars.copy() # variables at timestep 0
+
xbinary = generate_signed_combinations(self.vars.x)
X = []
for i in range(self.n):
Xnew = self.add_var()
X.append(Xnew)
Xbinary = generate_signed_combinations(X)
- # update the variables
+ # update the variables
self.vars.x = X
Ucons = []
for i in range(dim):
for j in range(dim):
for var in xbinary[i]:
- self.add_clause([var, - self.vars.Unitaryvar[i][j]])
+ self.add_clause([var, -self.vars.Unitaryvar[i][j]])
for var in Xbinary[j]:
- self.add_clause([var, - self.vars.Unitaryvar[i][j]])
+ self.add_clause([var, -self.vars.Unitaryvar[i][j]])
Ucons.append(self.vars.Unitaryvar[i][j])
- self.add_clause(Ucons)
+ self.add_clause(Ucons)
else:
- self.vars = Variables(self, computational_basis, unitary_encoding, Unitary) # variables at timestep m (end of circuit)
- self.vars_init = self.vars.copy() # variables at timestep 0
+ self.vars = Variables(
+ self, computational_basis, unitary_encoding, Unitary
+ ) # variables at timestep m (end of circuit)
+ self.vars_init = self.vars.copy() # variables at timestep 0
@staticmethod
def _is_zero_imag_part(value):
@@ -326,8 +358,6 @@ def copy(self):
self.vars_init.cnf = self
new.vars.cnf = new
new.vars_init.cnf = new
-
-
def finalize(self):
"""
@@ -348,7 +378,7 @@ def leftProjectZXi(self, Z_or_X, i):
Z_or_X (bool): If True, project to Z basis, otherwise to X.
i (int): The index of the qubit to project.
"""
- assert(not self.computational_basis)
+ assert not self.computational_basis
self.vars_init.projectZXi(Z_or_X, i, prepend=True)
def rightProjectAllZero(self):
@@ -366,10 +396,10 @@ def rightProjectZXi(self, Z_or_X, i):
Z_or_X (bool): If True, project to Z basis, otherwise to X.
i (int): The index of the qubit to project.
"""
- assert(not self.computational_basis)
+ assert not self.computational_basis
if not self.locked:
self.finalize()
- self.vars.projectZXi(Z_or_X, i, prepend=True)
+ self.vars.projectZXi(Z_or_X, i, prepend=True)
def precondition(self, spec):
"""
@@ -377,13 +407,15 @@ def precondition(self, spec):
Args:
spec (dict): A dictionary specifying the projector. The keys are qubit indices and the values are 0 or 1.
"""
-
+
vals = set(spec.values())
kind = (
- "bit" if vals <= {0, 1} else
- "pauli" if vals <= {"X", "Y", "Z", "I"} else
- "illegal"
+ "bit"
+ if vals <= {0, 1}
+ else "pauli"
+ if vals <= {"X", "Y", "Z", "I"}
+ else "illegal"
)
var_curr = self.vars.var
# precondition
@@ -395,9 +427,7 @@ def precondition(self, spec):
self.vars_init.projectPauli(spec, prepend=True)
else:
raise ValueError("The specification is illegal.")
-
-
-
+
# normalization
if self.computational_basis:
self.power_two_normalisation += self.n - len(spec)
@@ -406,7 +436,7 @@ def precondition(self, spec):
self.power_two_normalisation += len(spec)
elif kind == "pauli":
self.normalisation += len(spec)
-
+
def postcondition(self, spec):
"""
Add a projector clause to the CNF encoding for the final state.
@@ -416,9 +446,11 @@ def postcondition(self, spec):
vals = set(spec.values())
kind = (
- "bit" if vals <= {0, 1} else
- "pauli" if vals <= {"X", "Y", "Z", "I"} else
- "illegal"
+ "bit"
+ if vals <= {0, 1}
+ else "pauli"
+ if vals <= {"X", "Y", "Z", "I"}
+ else "illegal"
)
var_curr = self.vars.var
# postcondition
@@ -428,62 +460,70 @@ def postcondition(self, spec):
elif kind == "pauli":
self.vars.projectPauli(spec, prepend=False)
else:
- raise ValueError("The specification is illegal.")
+ raise ValueError("The specification is illegal.")
# if not self.computational_basis:
# self.power_two_normalisation -= len(spec)
if not self.locked:
self.finalize()
- def add_identity_clauses(self, constrain_2n = False, constrain_no_Y = False):
+
+ def add_identity_clauses(self, constrain_2n=False, constrain_no_Y=False):
"""
Add clauses dictating that the initial state matches the final state.
Args:
constrain_2n (bool): If True, limit the initial states to the 2*n states of single X or single Z.
- constrain_no_Y (bool): If True, limit the initial state to I, X or Z (no Y).
+ constrain_no_Y (bool): If True, limit the initial state to I, X or Z (no Y).
# no sense using both
"""
- assert(self.vars.n == self.vars_init.n)
+ assert self.vars.n == self.vars_init.n
assert not (constrain_2n and constrain_no_Y)
for i in range(self.vars.n):
- self.add_clause([ self.vars.x[i], -self.vars_init.x[i]])
- self.add_clause([-self.vars.x[i], self.vars_init.x[i]])
+ self.add_clause([self.vars.x[i], -self.vars_init.x[i]])
+ self.add_clause([-self.vars.x[i], self.vars_init.x[i]])
if not self.computational_basis:
- self.add_clause([ self.vars.z[i], -self.vars_init.z[i]])
- self.add_clause([-self.vars.z[i], self.vars_init.z[i]])
+ self.add_clause([self.vars.z[i], -self.vars_init.z[i]])
+ self.add_clause([-self.vars.z[i], self.vars_init.z[i]])
if constrain_2n:
if not self.computational_basis:
from .pauli2cnf import pauli2cnf as to_CNF
- to_CNF.AMO(self, self.vars_init.x+self.vars_init.z)
- else:
- assert False, f"ERROR: identity with constrain_2n for computational_basis not suported"
+
+ to_CNF.AMO(self, self.vars_init.x + self.vars_init.z)
+ else:
+ assert False, (
+ f"ERROR: identity with constrain_2n for computational_basis not suported"
+ )
if constrain_no_Y:
if not self.computational_basis:
for i in range(self.vars.n):
self.add_clause([-self.vars_init.x[i], -self.vars_init.z[i]])
- else:
- assert False, f"ERROR: identity with constrain_no_Y for computational_basis not suported"
+ else:
+ assert False, (
+ f"ERROR: identity with constrain_no_Y for computational_basis not suported"
+ )
if not self.locked:
- self.finalize()
+ self.finalize()
def add_measurement(self, basis):
"""
Add a measurement clause to the CNF encoding (for the final state).
Args:
- basis (str or dict): The measurement basis. Can be "allzero", "firstzero", or a dictionary specifying the measurement.
+ basis (str or dict): The measurement basis. Can be "allzero", "firstzero", or a dictionary specifying the measurement.
- "allzero": All qubits are measured in the |0> state.
- "firstzero": The first qubit is measured in the |0> state, and the rest are measured in the I state.
- dict: A dictionary specifying the measurement for each qubit e.g. {0: 1, 1: 0} means qubit 0 is measured in the |1> state and qubit 1 in the |0> state.
"""
- self.vars.measurement(basis, False)
+ self.vars.measurement(basis, False)
if not self.locked:
- self.finalize()
+ self.finalize()
def add_normalization(self):
"""
Increment CNF normalization factor if a measurement is replaced by a controlled gate.
"""
self.vars.normalize(1)
-
- def add_var(self, syn_gate_pick = False, Name ="UnNamed", bits = None, projection_var = False):
+
+ def add_var(
+ self, syn_gate_pick=False, Name="UnNamed", bits=None, projection_var=False
+ ):
"""
Add a new variable to the CNF encoding.
Args:
@@ -499,18 +539,38 @@ def add_var(self, syn_gate_pick = False, Name ="UnNamed", bits = None, projectio
if projection_var:
self.syn_projection_vars.add(var)
if syn_gate_pick:
- self.syn_gate_picking_vars[var] = {"Name": Name, "bits": bits, "layer": self.syn_gate_layer}
- if Name not in self.syn_gate_picking_vars_by_layer_and_gate[self.syn_gate_layer]:
- self.syn_gate_picking_vars_by_layer_and_gate[self.syn_gate_layer][Name] = {}
+ self.syn_gate_picking_vars[var] = {
+ "Name": Name,
+ "bits": bits,
+ "layer": self.syn_gate_layer,
+ }
+ if (
+ Name
+ not in self.syn_gate_picking_vars_by_layer_and_gate[self.syn_gate_layer]
+ ):
+ self.syn_gate_picking_vars_by_layer_and_gate[self.syn_gate_layer][
+ Name
+ ] = {}
if len(bits) == 1:
- self.syn_gate_picking_vars_by_layer_and_gate[self.syn_gate_layer][Name][bits[0]] = var
+ self.syn_gate_picking_vars_by_layer_and_gate[self.syn_gate_layer][Name][
+ bits[0]
+ ] = var
else:
- if bits[0] not in self.syn_gate_picking_vars_by_layer_and_gate[self.syn_gate_layer][Name]:
- self.syn_gate_picking_vars_by_layer_and_gate[self.syn_gate_layer][Name][bits[0]] = {}
- self.syn_gate_picking_vars_by_layer_and_gate[self.syn_gate_layer][Name][bits[0]][bits[1]] = var
+ if (
+ bits[0]
+ not in self.syn_gate_picking_vars_by_layer_and_gate[
+ self.syn_gate_layer
+ ][Name]
+ ):
+ self.syn_gate_picking_vars_by_layer_and_gate[self.syn_gate_layer][
+ Name
+ ][bits[0]] = {}
+ self.syn_gate_picking_vars_by_layer_and_gate[self.syn_gate_layer][Name][
+ bits[0]
+ ][bits[1]] = var
return var
-
- def get_syn_var_past_layer(self, Name ="UnNamed", bit = None, past=1):
+
+ def get_syn_var_past_layer(self, Name="UnNamed", bit=None, past=1):
"""
Get the variable for a specific gate and qubit index in the past layer.
Args:
@@ -523,14 +583,17 @@ def get_syn_var_past_layer(self, Name ="UnNamed", bit = None, past=1):
"""
try:
if type(bit) is int:
- return self.syn_gate_picking_vars_by_layer_and_gate[self.syn_gate_layer-past][Name][bit]
+ return self.syn_gate_picking_vars_by_layer_and_gate[
+ self.syn_gate_layer - past
+ ][Name][bit]
if type(bit) is list:
- return self.syn_gate_picking_vars_by_layer_and_gate[self.syn_gate_layer-past][Name][bit[0]][bit[1]]
+ return self.syn_gate_picking_vars_by_layer_and_gate[
+ self.syn_gate_layer - past
+ ][Name][bit[0]][bit[1]]
except KeyError:
return 0.5
-
- def add_clause(self, cons: list, prepend=False, comment=None):
+ def add_clause(self, cons: list, prepend=False, comment=None):
"""
Add a clause to the CNF encoding.
Args:
@@ -544,9 +607,11 @@ def add_clause(self, cons: list, prepend=False, comment=None):
return
if 0.5 in cons:
# 0.5 is false, so the clause is a contradiction!
- raise KeyError("used an invalid past variable, and added its true literal to a clause")
+ raise KeyError(
+ "used an invalid past variable, and added its true literal to a clause"
+ )
self.clause += 1
- constr = ''
+ constr = ""
for i in range(len(cons)):
assert type(cons[i]) is int
constr += str(cons[i]) + " "
@@ -564,14 +629,14 @@ def _normalize_weight_types(weight, neg_weight):
"""
Normalize weight and neg_weight to have the same type.
Both must be either Decimal/real or complex.
-
+
Args:
weight: Weight value (int, float, Decimal, or complex)
neg_weight: Negated weight value (int, float, Decimal, or complex)
-
+
Returns:
tuple: (normalized_weight, normalized_neg_weight) with consistent types
-
+
Raises:
TypeError: If types cannot be made consistent
"""
@@ -583,7 +648,7 @@ def _normalize_weight_types(weight, neg_weight):
# Convert to more specific types if needed
is_weight_complex = isinstance(weight, complex)
is_neg_weight_complex = isinstance(neg_weight, complex)
-
+
print(is_weight_complex, is_neg_weight_complex)
# If either is complex, convert both to complex
if is_weight_complex or is_neg_weight_complex:
@@ -603,7 +668,7 @@ def _normalize_weight_types(weight, neg_weight):
weight = Decimal(weight)
if not isinstance(neg_weight, Decimal):
neg_weight = Decimal(neg_weight)
-
+
return weight, neg_weight
def add_weight(self, var, weight, neg_weight, comment=None):
@@ -614,15 +679,15 @@ def add_weight(self, var, weight, neg_weight, comment=None):
weight: The weight to be added. May be Decimal or complex.
neg_weight: The weight for the negated literal `-var`. May be Decimal or complex.
comment (str): A comment to be added to the weight for debugging purposes (currently disabled since it causes problems for cnf solvers).
-
+
Note:
weight and neg_weight will be normalized to the same type:
- Both become complex if either is complex
- Both become Decimal otherwise
- """
+ """
# Normalize types to ensure consistency
# weight, neg_weight = self._normalize_weight_types(weight, neg_weight)
-
+
if var < len(self.weights) and self.weights[var] is not None:
# Multiply existing (weight, neg_weight) tuple by new values
old_w, old_nw = self.weights[var]
@@ -632,7 +697,7 @@ def add_weight(self, var, weight, neg_weight, comment=None):
else:
# Store new (weight, neg_weight) tuple
if DEBUG:
- print(f"ADD ({var}):", neg_weight, " ", weight)
+ print(f"ADD ({var}):", neg_weight, " ", weight)
self.weights[var] = (weight, neg_weight)
def _weights_to_string(self, syntesis_fomat=False):
@@ -652,17 +717,17 @@ def _weights_to_string(self, syntesis_fomat=False):
lines.append(line)
line = self.encode_weight(-var, neg_weight)
lines.append(line)
- return ''.join(lines)
+ return "".join(lines)
def encode_weight(self, var, weight):
is_complex = isinstance(weight, complex)
- weight_kind = "weight"
- # if not syntesis_fomat:
- # weight_kind = "weight"
+ weight_kind = "weight"
+ # if not syntesis_fomat:
+ # weight_kind = "weight"
real_part = weight.real if isinstance(weight, complex) else weight
line = f"c p {weight_kind} {var} {real_part}"
if is_complex:
- # support ganak complex format
+ # support ganak complex format
if self.ganak:
line += f" + {weight.imag}i"
else:
@@ -678,21 +743,42 @@ def ProjectionSet(self, VarList):
Returns:
str: The string representation of the variables for projection.
"""
- return ' '.join([str(v) for vars in VarList for v in vars])
+ return " ".join([str(v) for vars in VarList for v in vars])
- def write_to_file(self, cnf_file, syntesis_fomat = False, projectionset = []):
- with open(cnf_file, 'w') as the_file:
- the_file.writelines("p cnf " + str(self.vars.var)+" "+str(self.clause)+"\n")
+ def write_to_file(self, cnf_file, syntesis_fomat=False, projectionset=[]):
+ with open(cnf_file, "w") as the_file:
+ the_file.writelines(
+ "p cnf " + str(self.vars.var) + " " + str(self.clause) + "\n"
+ )
if len(projectionset) > 0:
- the_file.writelines("c p show " + self.ProjectionSet(projectionset) + " 0\n")
+ the_file.writelines(
+ "c p show " + self.ProjectionSet(projectionset) + " 0\n"
+ )
if syntesis_fomat:
- the_file.write("c max " +' '.join([str(v) for v in self.syn_gate_picking_vars.keys()]) + " 0\n")
- the_file.write("c ind " +' '.join([str(v) for v in (range(1,self.vars.var+1) - self.syn_gate_picking_vars.keys() - self.syn_projection_vars)]) + " 0\n")
+ the_file.write(
+ "c max "
+ + " ".join([str(v) for v in self.syn_gate_picking_vars.keys()])
+ + " 0\n"
+ )
+ the_file.write(
+ "c ind "
+ + " ".join(
+ [
+ str(v)
+ for v in (
+ range(1, self.vars.var + 1)
+ - self.syn_gate_picking_vars.keys()
+ - self.syn_projection_vars
+ )
+ ]
+ )
+ + " 0\n"
+ )
weights_str = self._weights_to_string(syntesis_fomat=syntesis_fomat)
the_file.write(weights_str)
- the_file.write(''.join(self.cons_list))
+ the_file.write("".join(self.cons_list))
- def encode_circuit(self, circuit : Circuit):
+ def encode_circuit(self, circuit: Circuit):
"""
Encode a quantum circuit into the CNF encoding.
Args:
@@ -704,136 +790,136 @@ def encode_circuit(self, circuit : Circuit):
self.circuit.append(copy.deepcopy(circuit))
if self.computational_basis:
- from .comput2cnf import comput2cnf as to_CNF
+ from .comput2cnf import comput2cnf as to_CNF
else:
from .pauli2cnf import pauli2cnf as to_CNF
to_CNF.init(self)
for element in circuit.circ:
- if len(element) == 4 and element[3] == 'if':
+ if len(element) == 4 and element[3] == "if":
self.add_normalization()
gate = element[0]
- if gate == 'id':
+ if gate == "id":
pass
- elif gate == 'h':
+ elif gate == "h":
k = int(element[1])
- to_CNF.H2CNF(self,k)
- elif gate == 'x':
+ to_CNF.H2CNF(self, k)
+ elif gate == "x":
k = int(element[1])
- to_CNF.X2CNF(self,k)
- elif gate == 'y':
+ to_CNF.X2CNF(self, k)
+ elif gate == "y":
k = int(element[1])
- to_CNF.Y2CNF(self,k)
- elif gate == 'z':
+ to_CNF.Y2CNF(self, k)
+ elif gate == "z":
k = int(element[1])
- to_CNF.Z2CNF(self,k)
- elif gate == 's':
+ to_CNF.Z2CNF(self, k)
+ elif gate == "s":
k = int(element[1])
- to_CNF.S2CNF(self,k)
- elif gate == 'sdg':
+ to_CNF.S2CNF(self, k)
+ elif gate == "sdg":
k = int(element[1])
- to_CNF.Sdg2CNF(self,k)
- elif gate == 't':
+ to_CNF.Sdg2CNF(self, k)
+ elif gate == "t":
k = int(element[1])
to_CNF.T2CNF(self, k)
- elif gate == 'tdg':
+ elif gate == "tdg":
k = int(element[1])
- to_CNF.Tdg2CNF(self,k)
- elif gate == 'cx':
+ to_CNF.Tdg2CNF(self, k)
+ elif gate == "cx":
j = int(element[1])
k = int(element[2])
- to_CNF.CNOT2CNF(self,j,k)
- elif gate == 'cz':
+ to_CNF.CNOT2CNF(self, j, k)
+ elif gate == "cz":
j = int(element[1])
k = int(element[2])
- to_CNF.CZ2CNF(self,j,k)
- elif gate == 'cy':
+ to_CNF.CZ2CNF(self, j, k)
+ elif gate == "cy":
j = int(element[1])
k = int(element[2])
- to_CNF.CY2CNF(self,j,k)
- elif gate == 'swap':
+ to_CNF.CY2CNF(self, j, k)
+ elif gate == "swap":
j = int(element[1])
k = int(element[2])
- to_CNF.SWAP2CNF(self,j,k)
- elif gate == 'iswap':
+ to_CNF.SWAP2CNF(self, j, k)
+ elif gate == "iswap":
j = int(element[1])
k = int(element[2])
- to_CNF.SWAP2CNF(self,j,k)
- to_CNF.CZ2CNF(self,j,k)
- to_CNF.S2CNF(self,j)
- to_CNF.S2CNF(self,k)
- elif gate == 'iswapdg':
+ to_CNF.SWAP2CNF(self, j, k)
+ to_CNF.CZ2CNF(self, j, k)
+ to_CNF.S2CNF(self, j)
+ to_CNF.S2CNF(self, k)
+ elif gate == "iswapdg":
j = int(element[1])
k = int(element[2])
- to_CNF.Sdg2CNF(self,k)
- to_CNF.Sdg2CNF(self,j)
- to_CNF.CZ2CNF(self,j,k)
- to_CNF.SWAP2CNF(self,j,k)
- elif gate == 'cs':
+ to_CNF.Sdg2CNF(self, k)
+ to_CNF.Sdg2CNF(self, j)
+ to_CNF.CZ2CNF(self, j, k)
+ to_CNF.SWAP2CNF(self, j, k)
+ elif gate == "cs":
j = int(element[1])
k = int(element[2])
- to_CNF.CS2CNF(self,j,k)
- elif gate == 'csdg':
+ to_CNF.CS2CNF(self, j, k)
+ elif gate == "csdg":
j = int(element[1])
k = int(element[2])
- to_CNF.CSdg2CNF(self,j,k)
- elif gate == 'ct':
+ to_CNF.CSdg2CNF(self, j, k)
+ elif gate == "ct":
j = int(element[1])
k = int(element[2])
- to_CNF.RZ2CNF(self,j, np.pi/8)
- to_CNF.RZ2CNF(self,k, np.pi/8)
- to_CNF.CNOT2CNF(self,j,k)
- to_CNF.RZ2CNF(self,k, -np.pi/8)
- to_CNF.CNOT2CNF(self,j,k)
- elif gate == 'ctdg':
+ to_CNF.RZ2CNF(self, j, np.pi / 8)
+ to_CNF.RZ2CNF(self, k, np.pi / 8)
+ to_CNF.CNOT2CNF(self, j, k)
+ to_CNF.RZ2CNF(self, k, -np.pi / 8)
+ to_CNF.CNOT2CNF(self, j, k)
+ elif gate == "ctdg":
j = int(element[1])
k = int(element[2])
- to_CNF.CNOT2CNF(self,j,k)
- to_CNF.RZ2CNF(self,k, np.pi/8)
- to_CNF.CNOT2CNF(self,j,k)
- to_CNF.RZ2CNF(self,k, -np.pi/8)
- to_CNF.RZ2CNF(self,j, -np.pi/8)
- elif gate == 'csqrtx':
+ to_CNF.CNOT2CNF(self, j, k)
+ to_CNF.RZ2CNF(self, k, np.pi / 8)
+ to_CNF.CNOT2CNF(self, j, k)
+ to_CNF.RZ2CNF(self, k, -np.pi / 8)
+ to_CNF.RZ2CNF(self, j, -np.pi / 8)
+ elif gate == "csqrtx":
j = int(element[1])
k = int(element[2])
- to_CNF.CSqrtX2CNF(self,j,k)
- elif gate == 'csqrtxdg':
+ to_CNF.CSqrtX2CNF(self, j, k)
+ elif gate == "csqrtxdg":
j = int(element[1])
k = int(element[2])
- to_CNF.CSqrtXdg2CNF(self,j,k)
- elif gate[0] == 'r':
+ to_CNF.CSqrtXdg2CNF(self, j, k)
+ elif gate[0] == "r":
angle = element[1]
k = int(element[2])
- if gate == 'rx':
- to_CNF.RX2CNF(self,k, angle)
- elif gate == 'rz':
- to_CNF.RZ2CNF(self,k, angle)
- elif gate == 'ry':
- to_CNF.S2CNF(self,k)
- to_CNF.RX2CNF(self,k, angle)
- to_CNF.Sdg2CNF(self,k)
- elif gate == 'rxdg':
- to_CNF.RX2CNF(self,k, -angle)
- elif gate == 'rzdg':
- to_CNF.RZ2CNF(self,k, -angle)
- elif gate == 'rydg':
- to_CNF.Sdg2CNF(self,k)
- to_CNF.RX2CNF(self,k, -angle)
- to_CNF.S2CNF(self,k)
+ if gate == "rx":
+ to_CNF.RX2CNF(self, k, angle)
+ elif gate == "rz":
+ to_CNF.RZ2CNF(self, k, angle)
+ elif gate == "ry":
+ to_CNF.S2CNF(self, k)
+ to_CNF.RX2CNF(self, k, angle)
+ to_CNF.Sdg2CNF(self, k)
+ elif gate == "rxdg":
+ to_CNF.RX2CNF(self, k, -angle)
+ elif gate == "rzdg":
+ to_CNF.RZ2CNF(self, k, -angle)
+ elif gate == "rydg":
+ to_CNF.Sdg2CNF(self, k)
+ to_CNF.RX2CNF(self, k, -angle)
+ to_CNF.S2CNF(self, k)
else:
- raise Exception(str(gate) + " undefined."+ str(element))
+ raise Exception(str(gate) + " undefined." + str(element))
elif gate == "ccx":
qubitc1 = int(element[1])
qubitc2 = int(element[2])
- qubitr = int(element[3])
+ qubitr = int(element[3])
to_CNF.CCX2CNF(self, qubitc1, qubitc2, qubitr)
- elif gate == 'measure':
+ elif gate == "measure":
qubit = int(element[1])
self.add_measurement({qubit: 0})
else:
- raise Exception(str(gate) + " undefined."+ str(element))
-
+ raise Exception(str(gate) + " undefined." + str(element))
+
def encode_composition(self, composition_dictionary):
"""
Encode a PauliStrings composition into the CNF encoding.
@@ -841,6 +927,7 @@ def encode_composition(self, composition_dictionary):
composition_dictionary (dict): The PauliStrings composition to be encoded.
"""
from .pauli2cnf import pauli2cnf as to_CNF
+
to_CNF.Composition2CNF(self, composition_dictionary)
def add_syn_layer(self, gate_set, n=1, limit_gates=False, h_layer=False):
@@ -852,17 +939,19 @@ def add_syn_layer(self, gate_set, n=1, limit_gates=False, h_layer=False):
h_layer (bool): Relevent only if limit_gates. If True, only allow Hadamard layer, else allow any gate but Hadamard.
"""
if self.computational_basis:
- from .comput2cnf import comput2cnf as to_CNF
+ from .comput2cnf import comput2cnf as to_CNF
else:
from .pauli2cnf import pauli2cnf as to_CNF
-
+
for _ in range(n):
self.syn_gate_layer += 1
self.syn_gate_picking_vars_by_layer_and_gate[self.syn_gate_layer] = {}
if self.computational_basis:
to_CNF.SynLayer2CNF(self, gate_set=gate_set)
else:
- to_CNF.SynLayer2CNF(self, gate_set=gate_set, limit_gates=limit_gates, h_layer=h_layer)
+ to_CNF.SynLayer2CNF(
+ self, gate_set=gate_set, limit_gates=limit_gates, h_layer=h_layer
+ )
def get_syn_circuit(self, assignment) -> Circuit:
"""
@@ -877,12 +966,12 @@ def get_syn_circuit(self, assignment) -> Circuit:
for v in assignment:
if int(v) > 0:
gate = self.syn_gate_picking_vars[int(v)]
- if gate['Name'] == "id":
+ if gate["Name"] == "id":
continue
- if len(gate['bits'])==1:
- circuit.add_single(gate['Name'], gate['bits'][0])
- elif len(gate['bits'])==2:
- circuit.add_double(gate['Name'], gate['bits'][0], gate['bits'][1])
+ if len(gate["bits"]) == 1:
+ circuit.add_single(gate["Name"], gate["bits"][0])
+ elif len(gate["bits"]) == 2:
+ circuit.add_double(gate["Name"], gate["bits"][0], gate["bits"][1])
else:
assert False
return circuit
@@ -907,9 +996,7 @@ def get_syn_circuit(self, assignment) -> Circuit:
# for b in gate['bits']:
# s += f" q[{b}]"
# s += f" ;\n"
- # return s
-
-
+ # return s
def get_syn_qasm(self, assignment) -> str:
"""
@@ -922,19 +1009,20 @@ def get_syn_qasm(self, assignment) -> str:
s = "OPENQASM 2.0;\n"
s += 'include "qelib1.inc";\n'
s += f"qreg q[{self.n + self.ancillas}];\n"
-
+
for v in assignment:
if int(v) > 0:
gate = self.syn_gate_picking_vars[int(v)]
-
+
if gate["Name"] == "id":
continue
-
+
args = ",".join(f"q[{b}]" for b in gate["bits"])
s += f"{gate['Name']} {args};\n"
-
+
return s
-
+
+
def generate_signed_combinations(lst):
"""
Generate all signed combinations of a list of numbers.
@@ -945,14 +1033,15 @@ def generate_signed_combinations(lst):
"""
n = len(lst)
result = {}
-
+
for signs in product([0, 1], repeat=n):
signed_list = [num if sign else -num for num, sign in zip(lst, signs)]
signs = signs[::-1]
- binary_code = ''.join(map(str,signs))
- result[int(binary_code,2)] = signed_list
+ binary_code = "".join(map(str, signs))
+ result[int(binary_code, 2)] = signed_list
return result
+
def check_unitary_and_qubits(U, tol=1e-12):
"""
Check if a matrix is unitary and determine the number of qubits.
@@ -965,23 +1054,27 @@ def check_unitary_and_qubits(U, tol=1e-12):
# Check if U is square
if U.shape[0] != U.shape[1]:
return False, None
-
+
# Check if U is unitary
U_dagger = np.conjugate(U).T
identity = np.eye(U.shape[0])
-
- is_unitary = np.allclose(U_dagger @ U, identity, atol=tol) and np.allclose(U @ U_dagger, identity, atol=tol)
-
+
+ is_unitary = np.allclose(U_dagger @ U, identity, atol=tol) and np.allclose(
+ U @ U_dagger, identity, atol=tol
+ )
+
# Determine the number of qubits
size = U.shape[0]
num_qubits = None
if (size & (size - 1)) == 0: # Check if size is a power of 2
num_qubits = int(np.log2(size))
-
+
return is_unitary, num_qubits
-def QASM2CNF(circuit: Circuit, computational_basis = False, ancillas = 0, ganak = False) -> CNF:
+def QASM2CNF(
+ circuit: Circuit, computational_basis=False, ancillas=0, ganak=False
+) -> CNF:
"""
Construct a CNF object for a given quantum circuit.
Args:
@@ -992,12 +1085,17 @@ def QASM2CNF(circuit: Circuit, computational_basis = False, ancillas = 0, ganak
Returns:
CNF: The CNF object representing the quantum circuit.
"""
- cnf = CNF(circuit.n, ancillas = circuit.ancillas + ancillas, computational_basis = computational_basis, ganak = ganak)
+ cnf = CNF(
+ circuit.n,
+ ancillas=circuit.ancillas + ancillas,
+ computational_basis=computational_basis,
+ ganak=ganak,
+ )
cnf.encode_circuit(circuit)
- return cnf
+ return cnf
-def Composition2CNF(composition_dictionary, ancillas = 0) -> CNF:
+def Composition2CNF(composition_dictionary, ancillas=0) -> CNF:
"""
Construct a CNF object for a given PauliStrings composition.
Args:
@@ -1006,6 +1104,8 @@ def Composition2CNF(composition_dictionary, ancillas = 0) -> CNF:
Returns:
CNF: The CNF object representing the PauliStrings composition.
"""
- cnf = CNF(composition_dictionary["qubits"], computational_basis = False, ancillas=ancillas)
+ cnf = CNF(
+ composition_dictionary["qubits"], computational_basis=False, ancillas=ancillas
+ )
cnf.encode_composition(composition_dictionary)
return cnf
diff --git a/quokka_sharp/quokka_sharp/encoding/pauli2cnf.py b/quokka_sharp/quokka_sharp/encoding/pauli2cnf.py
index 080955fd..78ed2332 100644
--- a/quokka_sharp/quokka_sharp/encoding/pauli2cnf.py
+++ b/quokka_sharp/quokka_sharp/encoding/pauli2cnf.py
@@ -778,8 +778,16 @@ def SynLayer2CNF(cnf, gate_set=set(), limit_gates=False, h_layer=False):
cxgate = [[0.5 if c != t else None for t in range(n)] for c in range(n)]
if ENABLE_CZ and (not limit_gates or not h_layer):
- czgate = [[cnf.add_var(syn_gate_pick=True, Name='cz', bits=[c, t]) if c != t else None
- for t in range(n)] for c in range(n)]
+ #czgate = [[cnf.add_var(syn_gate_pick=True, Name='cz', bits=[c, t]) if c != t else None
+ # for t in range(n)] for c in range(n)]
+ # CZ is symmetric: each pair is stored once, with the smaller qubit index first (c < t)
+ czgate = [[cnf.add_var(syn_gate_pick=True, Name='cz', bits=[c, t]) if c < t else None
+ for t in range(n)]for c in range(n)]
+ def get_cz(a, b):
+ if a == b:
+ return None
+ return czgate[min(a, b)][max(a, b)]
+
else:
czgate = [[0.5 if c != t else None for t in range(n)] for c in range(n)]
@@ -913,38 +921,38 @@ def SynLayer2CNF(cnf, gate_set=set(), limit_gates=False, h_layer=False):
# Implies(cxgate[c][t], ~U[t])
cnf.add_clause([-U[t], -cxgate[c][t]])
- # CZ properties
+ # CZ properties: introduce new function call get_cz(c,t)
if ENABLE_CZ and (not limit_gates or not h_layer):
-
- # Implies(czgate[c][t], Equivalent(X[c], x[c]))
- cnf.add_clause([ X[c], -czgate[c][t], -x[c]])
- cnf.add_clause([-X[c], -czgate[c][t], x[c]])
- # Implies(czgate[c][t], Equivalent(X[t], x[t]))
- cnf.add_clause([ X[t], -czgate[c][t], -x[t]])
- cnf.add_clause([-X[t], -czgate[c][t], x[t]])
- # Implies(czgate[c][t], Equivalent(Z[c], x[t] ^ z[c]))
- cnf.add_clause([ Z[c], -czgate[c][t], x[t], -z[c]])
- cnf.add_clause([ Z[c], -czgate[c][t], -x[t], z[c]])
- cnf.add_clause([-Z[c], -czgate[c][t], x[t], z[c]])
- cnf.add_clause([-Z[c], -czgate[c][t], -x[t], -z[c]])
- # Implies(czgate[c][t], Equivalent(Z[t], x[c] ^ z[t]))
- cnf.add_clause([ Z[t], -czgate[c][t], x[c], -z[t]])
- cnf.add_clause([ Z[t], -czgate[c][t], -x[c], z[t]])
- cnf.add_clause([-Z[t], -czgate[c][t], x[c], z[t]])
- cnf.add_clause([-Z[t], -czgate[c][t], -x[c], -z[t]])
- # Implies(czgate[c][t], Equivalent(R[c], x[c] & x[t] & (z[c] ^ z[t])))
- cnf.add_clause([-R[c], -czgate[c][t], x[c]])
- cnf.add_clause([-R[c], -czgate[c][t], x[t]])
- cnf.add_clause([-R[c], -czgate[c][t], z[c], z[t]])
- cnf.add_clause([-R[c], -czgate[c][t], -z[c], -z[t]])
- cnf.add_clause([ R[c], -czgate[c][t], -x[c], -x[t], z[c], -z[t]])
- cnf.add_clause([ R[c], -czgate[c][t], -x[c], -x[t], -z[c], z[t]])
- # Implies(czgate[c][t], ~R[t])
- cnf.add_clause([-R[t], -czgate[c][t]])
- # Implies(czgate[c][t], ~U[c])
- cnf.add_clause([-U[c], -czgate[c][t]])
- # Implies(czgate[c][t], ~U[t])
- cnf.add_clause([-U[t], -czgate[c][t]])
+
+ # Implies(get_cz(c, t), Equivalent(X[c], x[c]))
+ cnf.add_clause([ X[c], -get_cz(c, t), -x[c]])
+ cnf.add_clause([-X[c], -get_cz(c, t), x[c]])
+ # Implies(get_cz(c, t), Equivalent(X[t], x[t]))
+ cnf.add_clause([ X[t], -get_cz(c, t), -x[t]])
+ cnf.add_clause([-X[t], -get_cz(c, t), x[t]])
+ # Implies(get_cz(c, t), Equivalent(Z[c], x[t] ^ z[c]))
+ cnf.add_clause([ Z[c], -get_cz(c, t), x[t], -z[c]])
+ cnf.add_clause([ Z[c], -get_cz(c, t), -x[t], z[c]])
+ cnf.add_clause([-Z[c], -get_cz(c, t), x[t], z[c]])
+ cnf.add_clause([-Z[c], -get_cz(c, t), -x[t], -z[c]])
+ # Implies(get_cz(c, t), Equivalent(Z[t], x[c] ^ z[t]))
+ cnf.add_clause([ Z[t], -get_cz(c, t), x[c], -z[t]])
+ cnf.add_clause([ Z[t], -get_cz(c, t), -x[c], z[t]])
+ cnf.add_clause([-Z[t], -get_cz(c, t), x[c], z[t]])
+ cnf.add_clause([-Z[t], -get_cz(c, t), -x[c], -z[t]])
+ # Implies(get_cz(c, t), Equivalent(R[c], x[c] & x[t] & (z[c] ^ z[t])))
+ cnf.add_clause([-R[c], -get_cz(c, t), x[c]])
+ cnf.add_clause([-R[c], -get_cz(c, t), x[t]])
+ cnf.add_clause([-R[c], -get_cz(c, t), z[c], z[t]])
+ cnf.add_clause([-R[c], -get_cz(c, t), -z[c], -z[t]])
+ cnf.add_clause([ R[c], -get_cz(c, t), -x[c], -x[t], z[c], -z[t]])
+ cnf.add_clause([ R[c], -get_cz(c, t), -x[c], -x[t], -z[c], z[t]])
+ # Implies(get_cz(c, t), ~R[t])
+ cnf.add_clause([-R[t], -get_cz(c, t)])
+ # Implies(get_cz(c, t), ~U[c])
+ cnf.add_clause([-U[c], -get_cz(c, t)])
+ # Implies(get_cz(c, t), ~U[t])
+ cnf.add_clause([-U[t], -get_cz(c, t)])
# CSqrtX / CSqrtXdg properties
if ENABLE_CSQRTX and (not limit_gates or not h_layer):
@@ -1062,6 +1070,20 @@ def integer_only(lst):
if ENABLE_T:
cnf.add_clause([-tg[k], -cnf.get_syn_var_past_layer(Name='tdg', bit=k)])
cnf.add_clause([-tdg[k], -cnf.get_syn_var_past_layer(Name='t', bit=k)])
+
+ if ENABLE_T and ENABLE_S:
+ # T*T = S
+ cnf.add_clause([
+ -tg[k],
+ -cnf.get_syn_var_past_layer(Name='t', bit=k)
+ ])
+
+ # Tdg*Tdg = Sdg
+ cnf.add_clause([
+ -tdg[k],
+ -cnf.get_syn_var_past_layer(Name='tdg', bit=k)
+ ])
+
long_or = []
if ENABLE_CX: long_or += cx_k
if ENABLE_CZ: long_or += cz_k
@@ -1083,6 +1105,25 @@ def integer_only(lst):
cnf.add_clause([-cxgate[c][t],
-cnf.get_syn_var_past_layer(Name='id', bit=c),
-cnf.get_syn_var_past_layer(Name='id', bit=t)])
+
+ if ENABLE_CZ:
+ # Disallow consecutive identical CZ gates: CZ^2 = I
+ cnf.add_clause([
+ -get_cz(c, t),
+ -cnf.get_syn_var_past_layer(Name='cz', bit=[min(c, t), max(c, t)])
+ ])
+
+ # Promotes placing CZ gate so that it does not follow 2 idle opearations
+ cnf.add_clause([
+ -get_cz(c, t),
+ -cnf.get_syn_var_past_layer(
+ Name='id', bit=c
+ ),
+ -cnf.get_syn_var_past_layer(
+ Name='id', bit=t
+ )
+ ])
+
if ENABLE_CSQRTX:
cnf.add_clause([-csqrtxgate[c][t],
-cnf.get_syn_var_past_layer(Name='id', bit=c),
@@ -1093,6 +1134,26 @@ def integer_only(lst):
cnf.add_clause([-csqrtxgate[c][t], -cnf.get_syn_var_past_layer(Name='csqrtxdg', bit=[c, t])])
cnf.add_clause([-csqrtxdggate[c][t], -cnf.get_syn_var_past_layer(Name='csqrtx', bit=[c, t])])
+ if ENABLE_CX:
+ # CSqrtX * CSqrtX = CX, thus disallow 2 consecutive CSqrtX
+ cnf.add_clause([
+ -csqrtxgate[c][t],
+ -cnf.get_syn_var_past_layer(
+ Name='csqrtx',
+ bit=[c, t]
+ )
+ ])
+
+ # CSqrtXdg * CSqrtXdg = CX, thus disallow 2 consecutive CSqrtXdg
+ cnf.add_clause([
+ -csqrtxdggate[c][t],
+ -cnf.get_syn_var_past_layer(
+ Name='csqrtxdg',
+ bit=[c, t]
+ )
+ ])
+
+ # Existing 3-layer T/CX, T/CZ, S/CX, S/CZ pruning
if ENABLE_T and ENABLE_CX and cnf.syn_gate_layer >= 3:
cnf.add_clause([-cnf.get_syn_var_past_layer(Name='cx', bit=[c, t]),
-cnf.get_syn_var_past_layer(Name='tdg', bit=c, past=2),
@@ -1100,6 +1161,60 @@ def integer_only(lst):
cnf.add_clause([-cnf.get_syn_var_past_layer(Name='cx', bit=[c, t]),
-cnf.get_syn_var_past_layer(Name='t', bit=c, past=2),
-tdg[c]])
+ if ENABLE_T and ENABLE_CZ and cnf.syn_gate_layer >= 3:
+ cnf.add_clause([-cnf.get_syn_var_past_layer(Name='cz', bit=[c, t]),
+ -cnf.get_syn_var_past_layer(Name='tdg', bit=c, past=2),
+ -tg[c]])
+ cnf.add_clause([-cnf.get_syn_var_past_layer(Name='cz', bit=[c, t]),
+ -cnf.get_syn_var_past_layer(Name='t', bit=c, past=2),
+ -tdg[c]])
+ if ENABLE_S and ENABLE_CX and cnf.syn_gate_layer>=3:
+ cnf.add_clause([-cnf.get_syn_var_past_layer(Name='cx', bit=[c, t]),
+ -cnf.get_syn_var_past_layer(Name='sdg', bit=c, past=2),
+ -sg[c]])
+ cnf.add_clause([-cnf.get_syn_var_past_layer(Name='cx', bit=[c, t]),
+ -cnf.get_syn_var_past_layer(Name='s', bit=c, past=2),
+ -sdg[c]])
+ if ENABLE_S and ENABLE_CZ and cnf.syn_gate_layer>=3:
+ cnf.add_clause([-cnf.get_syn_var_past_layer(Name='cz', bit=[c, t]),
+ -cnf.get_syn_var_past_layer(Name='sdg', bit=c, past=2),
+ -sg[c]])
+ cnf.add_clause([-cnf.get_syn_var_past_layer(Name='cz', bit=[c, t]),
+ -cnf.get_syn_var_past_layer(Name='s', bit=c, past=2),
+ -sdg[c]])
+ if ENABLE_T and ENABLE_CSQRTX and cnf.syn_gate_layer >= 3:
+ cnf.add_clause([-cnf.get_syn_var_past_layer(Name='csqrtx', bit=[c, t]),
+ -cnf.get_syn_var_past_layer(Name='tdg', bit=c, past=2),
+ -tg[c]])
+ cnf.add_clause([-cnf.get_syn_var_past_layer(Name='csqrtx', bit=[c, t]),
+ -cnf.get_syn_var_past_layer(Name='t', bit=c, past=2),
+ -tdg[c]])
+ if ENABLE_S and ENABLE_CSQRTX and cnf.syn_gate_layer >= 3:
+ cnf.add_clause([-cnf.get_syn_var_past_layer(Name='csqrtx', bit=[c, t]),
+ -cnf.get_syn_var_past_layer(Name='sdg', bit=c, past=2),
+ -sg[c]])
+ cnf.add_clause([-cnf.get_syn_var_past_layer(Name='csqrtx', bit=[c, t]),
+ -cnf.get_syn_var_past_layer(Name='t', bit=c, past=2),
+ -sdg[c]])
+ #H CZ H =CX & HCXH=CZ, so HCZh, HCXH is not allowed in 3 layers
+ if ENABLE_H and ENABLE_CZ and ENABLE_CX and cnf.syn_gate_layer >= 3:
+ cnf.add_clause([
+ -cnf.get_syn_var_past_layer(
+ Name='cz', bit=[min(c, t), max(c, t)]
+ ),
+ -cnf.get_syn_var_past_layer(
+ Name='h', bit=c, past=2
+ ),
+ -hg[c]])
+ cnf.add_clause([
+ -cnf.get_syn_var_past_layer(
+ Name='cx', bit=[min(c, t), max(c, t)]
+ ),
+ -cnf.get_syn_var_past_layer(
+ Name='h', bit=c, past=2
+ ),
+ -hg[c]])
+
cnf.vars.x[:n] = X
cnf.vars.z[:n] = Z
diff --git a/quokka_sharp/quokka_sharp/encoding/qasm_parser.py b/quokka_sharp/quokka_sharp/encoding/qasm_parser.py
index ae98fd00..0ce8cc73 100644
--- a/quokka_sharp/quokka_sharp/encoding/qasm_parser.py
+++ b/quokka_sharp/quokka_sharp/encoding/qasm_parser.py
@@ -5,28 +5,46 @@
getcontext().prec = 32
-HermiGates = ['id', 'h', 'cx', 'cz', 'cy', 'swap', 'x', 'z', 'y', 'ccx']
-RotationGates = ['rx', 'ry', 'rz']
-NHermitGates = {'t': 'tdg', 'tdg': 't', 's': 'sdg', 'sdg': 's',
- 'iswap': 'iswapdg', 'iswapdg': 'iswap',
- 'ct': 'ctdg', 'ctdg': 'ct',
- 'cs': 'csdg', 'csdg': 'cs', 'csqrtx': 'csqrtxdg', 'csqrtxdg': 'csqrtx',
- 'rx': 'rxdg', 'rxdg': 'rx', 'rz': 'rzdg', 'rzdg': 'rz', 'ry': 'rydg', 'rydg': 'ry'}
+HermiGates = ["id", "h", "cx", "cz", "cy", "swap", "x", "z", "y", "ccx"]
+RotationGates = ["rx", "ry", "rz"]
+NHermitGates = {
+ "t": "tdg",
+ "tdg": "t",
+ "s": "sdg",
+ "sdg": "s",
+ "iswap": "iswapdg",
+ "iswapdg": "iswap",
+ "ct": "ctdg",
+ "ctdg": "ct",
+ "cs": "csdg",
+ "csdg": "cs",
+ "csqrtx": "csqrtxdg",
+ "csqrtxdg": "csqrtx",
+ "rx": "rxdg",
+ "rxdg": "rx",
+ "rz": "rzdg",
+ "rzdg": "rz",
+ "ry": "rydg",
+ "rydg": "ry",
+}
FeedbackMap = {
- 'x': 'cx',
- 'y': 'cy',
- 'z': 'cz',
- 's': 'cs',
- 't': 'ct',
- 'tdg': 'ctdg',
- 'sdg': 'csdg',
- 'sqrtx': 'csqrtx',
- 'sqrtxdg': 'csqrtxdg',
+ "x": "cx",
+ "y": "cy",
+ "z": "cz",
+ "s": "cs",
+ "t": "ct",
+ "tdg": "ctdg",
+ "sdg": "csdg",
+ "sqrtx": "csqrtx",
+ "sqrtxdg": "csqrtxdg",
}
+
+
class Circuit:
"""
A class to represent a quantum circuit.
"""
+
def __init__(self):
self.n: int = 0
self.tgate: int = 0
@@ -35,46 +53,46 @@ def __init__(self):
self.has_rotations = False
self.ancillas = 0
self.cregs = {}
-
+
def depth(self):
"""
Returns the NUMBER OF GATES in the circuit.
"""
return len(self.circ)
-
- def add_single(self,gate: str, qubit: int):
+
+ def add_single(self, gate: str, qubit: int):
"""
Adds a single qubit gate to the circuit.
"""
- if(gate == 't' or gate == 'tdg'):
+ if gate == "t" or gate == "tdg":
self.tgate += 1
self.circ.append([gate, qubit])
- def add_double(self, gate: str , qubitc: int, qubitr: int):
+ def add_double(self, gate: str, qubitc: int, qubitr: int):
"""
Adds a double qubit gate to the circuit.
"""
self.circ.append([gate, qubitc, qubitr])
-
+
def add_rotation(self, gate: str, angle: Decimal, qubit: int):
"""
Adds a rotation gate to the circuit.
"""
self.circ.append([gate, angle, qubit])
self.has_rotations = True
-
- def add_ccx(self,qubitc1: int,qubitc2: int, qubitr: int):
+
+ def add_ccx(self, qubitc1: int, qubitc2: int, qubitr: int):
"""
Adds a Toffoli gate (CCX) to the circuit.
"""
- self.circ.append(['ccx', qubitc1, qubitc2, qubitr])
+ self.circ.append(["ccx", qubitc1, qubitc2, qubitr])
def add_if(self, c_name: str, value: int, inner_op: list, c_bit: int | None = None):
"""
Adds a classically conditioned op: if (c_name[c_bit]==value) .
"""
cond_key = (c_name, c_bit) if c_bit is not None else c_name
- self.circ.append(['if', cond_key, value] + inner_op)
+ self.circ.append(["if", cond_key, value] + inner_op)
def add_measure(self, qubit: int, cname: str, cbit: int):
"""
@@ -83,8 +101,10 @@ def add_measure(self, qubit: int, cname: str, cbit: int):
if cname not in self.cregs:
raise Exception(f"Measurement register {cname} not defined.")
if cbit >= self.cregs[cname]:
- raise Exception(f"Measurement bit {cbit} out of range for register {cname}.")
- #self.measurements.append(['measure', qubit, cname, cbit])
+ raise Exception(
+ f"Measurement bit {cbit} out of range for register {cname}."
+ )
+ # self.measurements.append(['measure', qubit, cname, cbit])
self.measurements[(cname, cbit)] = qubit
def dagger(self):
@@ -99,13 +119,13 @@ def dagger(self):
elif NHermitGates.get(gate) != None:
self.circ[idx][0] = NHermitGates[gate]
else:
- raise Exception("Gate "+ gate[0] +" dagger not supported.")
+ raise Exception("Gate " + gate[0] + " dagger not supported.")
- def append(self, other: 'Circuit'):
+ def append(self, other: "Circuit"):
"""
Appends another circuit to this circuit.
"""
- self.circ.extend( other.circ )
+ self.circ.extend(other.circ)
self.n = min(self.n, other.n)
self.ancillas = max(self.n, other.n) - min(self.n, other.n)
self.tgate = self.tgate + other.tgate
@@ -118,7 +138,7 @@ def to_qasm(self):
Converts the circuit to QASM format.
"""
s = "OPENQASM 2.0;\n"
- s += "include \"qelib1.inc\";\n"
+ s += 'include "qelib1.inc";\n'
s += f"qreg q[{self.n + self.ancillas}];\n"
for gate in self.circ:
if gate[0] == "id":
@@ -137,7 +157,7 @@ def to_qasm(self):
def to_qasm3(self):
s = "OPENQASM 3;\n"
- s += "include \"stdgates.inc\";\n"
+ s += 'include "stdgates.inc";\n'
s += f"qubit[{self.n + self.ancillas}] q;\n"
for cname, size in self.cregs.items():
if size == 1:
@@ -147,10 +167,10 @@ def to_qasm3(self):
for gate in self.circ:
if gate[0] in RotationGates:
s += f"{gate[0]}({gate[1]}) q[{gate[2]}];\n"
- elif gate[0] == 'measure':
+ elif gate[0] == "measure":
_, qubit, cname, cbit = gate
s += f"{cname}[{cbit}] = measure q[{qubit}];\n"
- elif gate[0] == 'id':
+ elif gate[0] == "id":
continue
else:
s += f"{gate[0]}"
@@ -167,15 +187,14 @@ def translate_if_statements(self):
new_circ = []
for item in self.circ:
-
- if not (isinstance(item, list) and item and item[0] == 'if'):
+ if not (isinstance(item, list) and item and item[0] == "if"):
new_circ.append(item)
continue
cond_key = item[1]
val = int(item[2])
- op = item[3]
- q = item[4] if len(item) > 4 else None
+ op = item[3]
+ q = item[4] if len(item) > 4 else None
if isinstance(cond_key, tuple):
cname, cbit = cond_key
@@ -195,9 +214,9 @@ def translate_if_statements(self):
if v == 1:
new_circ.append([controlledgate, ctrl, q])
else:
- new_circ.append(['x', ctrl])
+ new_circ.append(["x", ctrl])
new_circ.append([controlledgate, ctrl, q])
- new_circ.append(['x', ctrl])
+ new_circ.append(["x", ctrl])
else:
cname = cond_key
if op not in FeedbackMap or not isinstance(q, int):
@@ -219,42 +238,58 @@ def translate_if_statements(self):
if expected_bit == 1:
new_circ.append([controlledgate, ctrl_qubit, q])
else:
- new_circ.append(['x', ctrl_qubit])
+ new_circ.append(["x", ctrl_qubit])
new_circ.append([controlledgate, ctrl_qubit, q])
- new_circ.append(['x', ctrl_qubit])
+ new_circ.append(["x", ctrl_qubit])
self.circ = new_circ
def __str__(self):
out = []
out.append(f"qubits={self.n} ancillas={self.ancillas} tgates={self.tgate}")
if self.cregs:
- out.append("cregs=" + ", ".join(f"{k}[{v}]" for k,v in self.cregs.items()))
+ out.append("cregs=" + ", ".join(f"{k}[{v}]" for k, v in self.cregs.items()))
for g in self.circ:
- if not g:
+ if not g:
continue
op = g[0]
- if op in ('rx','ry','rz'):
+ if op in ("rx", "ry", "rz"):
out.append(f"{op}({g[1]}) q[{g[2]}]")
- elif op == 'ccx':
+ elif op == "ccx":
out.append(f"ccx q[{g[1]}], q[{g[2]}], q[{g[3]}]")
- elif op in ('cx','cy','cz','swap','iswap','cs','csdg','csqrtx','csqrtxdg','ct','ctdg'):
+ elif op in (
+ "cx",
+ "cy",
+ "cz",
+ "swap",
+ "iswap",
+ "cs",
+ "csdg",
+ "csqrtx",
+ "csqrtxdg",
+ "ct",
+ "ctdg",
+ ):
out.append(f"{op} q[{g[1]}], q[{g[2]}]")
- elif op == 'measure':
+ elif op == "measure":
out.append(f"measure q[{g[1]}] -> {g[2]}[{g[3]}]")
- elif op == 'if':
+ elif op == "if":
cond_key = g[1]
val = g[2]
inner = g[3:]
if isinstance(cond_key, tuple):
cname, cbit = cond_key
- out.append(f"if({cname}[{cbit}]=={val}) " + " ".join(str(x) for x in inner))
+ out.append(
+ f"if({cname}[{cbit}]=={val}) " + " ".join(str(x) for x in inner)
+ )
else:
- out.append(f"if({cond_key}=={val}) " + " ".join(str(x) for x in inner))
+ out.append(
+ f"if({cond_key}=={val}) " + " ".join(str(x) for x in inner)
+ )
else:
if len(g) == 2 and isinstance(g[1], int):
out.append(f"{op} q[{g[1]}]")
else:
- qs = ", ".join(f"q[{x}]" for x in g[1:] if isinstance(x,int))
+ qs = ", ".join(f"q[{x}]" for x in g[1:] if isinstance(x, int))
out.append(f"{op} {qs}".rstrip())
return "\n".join(out)
@@ -263,27 +298,29 @@ def get_num(s: str):
"""
Extracts the number from a string in the format qreg[n].
"""
- num = ''
- idx1 = s.index('[')
- idx2 = s.index(']')
+ num = ""
+ idx1 = s.index("[")
+ idx2 = s.index("]")
qreg = s[0:idx1]
for i in range(idx1 + 1, idx2):
if s[i].isdigit():
num = num + s[i]
return globals()[qreg][int(num)]
+
def get_creg_bit(s: str):
"""
Parses 'cName[j]' into ('cName', j).
E.g. 'creg[3]' -> ('creg', 3)
"""
- s = s.rstrip(';')
- idx1 = s.index('[')
- idx2 = s.index(']')
+ s = s.rstrip(";")
+ idx1 = s.index("[")
+ idx2 = s.index("]")
name = s[0:idx1]
- bit = int(s[idx1+1:idx2])
+ bit = int(s[idx1 + 1 : idx2])
return name, bit
+
def frac_to_float(frac: str):
"""
Converts a string representation of a fraction to a float.
@@ -291,14 +328,14 @@ def frac_to_float(frac: str):
sign = 0
if "-" in frac:
sign = 1
- frac = frac.replace("-",'')
+ frac = frac.replace("-", "")
try:
return float(frac)
except:
try:
- num, denom = frac.split('/')
+ num, denom = frac.split("/")
except:
- num = frac.split('/')[0]
+ num = frac.split("/")[0]
denom = 1
piflag = 0
denom = float(denom)
@@ -307,13 +344,14 @@ def frac_to_float(frac: str):
num = 1
elif "pi" in num:
piflag = 1
- num = num.replace("pi",'')
- num = num.replace("*",'')
+ num = num.replace("pi", "")
+ num = num.replace("*", "")
num = float(num)
if piflag == 1:
- return math.pow(-1,sign) * num / denom * math.pi
+ return math.pow(-1, sign) * num / denom * math.pi
else:
- return Decimal(math.pow(-1,sign) * num / denom)
+ return Decimal(math.pow(-1, sign) * num / denom)
+
def get_angle(angle: str):
"""
@@ -324,12 +362,12 @@ def get_angle(angle: str):
theta = frac_to_float(angle)
else:
theta_str = angle
- if 'pi' in theta_str:
- theta = theta_str.replace('*', '')
- theta = theta.replace('pi', '')
- if theta == '-':
+ if "pi" in theta_str:
+ theta = theta_str.replace("*", "")
+ theta = theta.replace("pi", "")
+ if theta == "-":
theta = "-1"
- elif theta == '':
+ elif theta == "":
theta = "1"
theta = float(theta) * math.pi
else:
@@ -338,52 +376,67 @@ def get_angle(angle: str):
except:
raise Exception(angle, "is not supported")
-if_statement_regex = re.compile(r'^\(\s*(?P[A-Za-z_]\w*)(?:\[(?P\d+)\])?\s*==\s*(?P\d+)\s*\)$')
-if_bit_true_regex = re.compile(r'^\(\s*(?P[A-Za-z_]\w*)\[(?P\d+)\]\s*\)$')
-if_bit_not_regex = re.compile(r'^\(\s*!\s*(?P[A-Za-z_]\w*)\[(?P\d+)\]\s*\)$')
+
+if_statement_regex = re.compile(
+ r"^\(\s*(?P[A-Za-z_]\w*)(?:\[(?P\d+)\])?\s*==\s*(?P\d+)\s*\)$"
+)
+if_bit_true_regex = re.compile(r"^\(\s*(?P[A-Za-z_]\w*)\[(?P\d+)\]\s*\)$")
+if_bit_not_regex = re.compile(r"^\(\s*!\s*(?P[A-Za-z_]\w*)\[(?P\d+)\]\s*\)$")
+
def parse_condition(token: str, cregs: dict):
m = if_bit_true_regex.match(token)
if m:
- cname = m.group('creg'); bit = int(m.group('bit'))
+ cname = m.group("creg")
+ bit = int(m.group("bit"))
size = cregs.get(cname, 1)
if bit < 0 or bit >= size:
- raise Exception(f"if-condition bit out of range: {cname}[{bit}] (size {size})")
+ raise Exception(
+ f"if-condition bit out of range: {cname}[{bit}] (size {size})"
+ )
return cname, bit, 1
m = if_bit_not_regex.match(token)
if m:
- cname = m.group('creg'); bit = int(m.group('bit'))
+ cname = m.group("creg")
+ bit = int(m.group("bit"))
size = cregs.get(cname, 1)
if bit < 0 or bit >= size:
- raise Exception(f"if-condition bit out of range: {cname}[{bit}] (size {size})")
+ raise Exception(
+ f"if-condition bit out of range: {cname}[{bit}] (size {size})"
+ )
return cname, bit, 0
m = if_statement_regex.match(token)
if m:
- cname = m.group('creg')
- cbit = m.group('bit')
- val = int(m.group('val'))
+ cname = m.group("creg")
+ cbit = m.group("bit")
+ val = int(m.group("val"))
if cbit is not None:
bit = int(cbit)
size = cregs.get(cname, 1)
if bit < 0 or bit >= size:
- raise Exception(f"if-condition bit out of range: {cname}[{bit}] (size {size})")
- if val not in (0,1):
+ raise Exception(
+ f"if-condition bit out of range: {cname}[{bit}] (size {size})"
+ )
+ if val not in (0, 1):
raise Exception("Bit-indexed condition must compare to 0 or 1.")
return cname, bit, val
else:
size = cregs.get(cname, 1)
max_val = (1 << size) - 1
if val < 0 or val > max_val:
- raise Exception(f"Register value {val} out of range for {cname} (size {size}, max {max_val})")
+ raise Exception(
+ f"Register value {val} out of range for {cname} (size {size}, max {max_val})"
+ )
return cname, None, val
raise Exception(f"Illegal if-condition: {token}")
+
def QASMparser(filename) -> Circuit:
"""
Parses a QASM file and returns a Circuit object.
"""
qasm_list = []
- with open(filename,"r") as qasm:
+ with open(filename, "r") as qasm:
for line in qasm:
qasm_list.append(line.rsplit())
circuit = Circuit()
@@ -392,137 +445,149 @@ def QASMparser(filename) -> Circuit:
continue
gate = line[0]
if any(item in ["qreg"] for item in line):
- idx1 = line[1].find('[')
- idx2 = line[1].find(']')
+ idx1 = line[1].find("[")
+ idx2 = line[1].find("]")
qreg = line[1][0:idx1]
- num = int(line[1][idx1+1:idx2])
+ num = int(line[1][idx1 + 1 : idx2])
globals()[qreg] = [0] * num
for i in range(num):
globals()[qreg][i] = i + circuit.n
circuit.n += num
- elif gate == 'qubit':
+ elif gate == "qubit":
if len(line) >= 2:
- qname = line[1].rstrip(';')
+ qname = line[1].rstrip(";")
globals()[qname] = [i + circuit.n for i in range(1)]
circuit.n += 1
- elif gate.startswith('qubit['):
+ elif gate.startswith("qubit["):
tok = line[0]
- idx1 = tok.find('['); idx2 = tok.find(']')
- num = int(tok[idx1+1:idx2])
- qname = line[1].rstrip(';')
+ idx1 = tok.find("[")
+ idx2 = tok.find("]")
+ num = int(tok[idx1 + 1 : idx2])
+ qname = line[1].rstrip(";")
globals()[qname] = [i + circuit.n for i in range(num)]
circuit.n += num
- elif line[0] == 'bit' or gate.startswith('bit['):
- if line[0] == 'bit':
- reg = line[1].rstrip(';')
+ elif line[0] == "bit" or gate.startswith("bit["):
+ if line[0] == "bit":
+ reg = line[1].rstrip(";")
cname = reg
csize = 1
else:
tok = line[0]
- idx1 = tok.find('['); idx2 = tok.find(']')
- csize = int(tok[idx1+1:idx2])
- cname = line[1].rstrip(';')
+ idx1 = tok.find("[")
+ idx2 = tok.find("]")
+ csize = int(tok[idx1 + 1 : idx2])
+ cname = line[1].rstrip(";")
circuit.cregs[cname] = csize
globals()[cname] = list(range(csize))
- elif line[0] == 'creg':
- reg = line[1].rstrip(';')
- idx1 = reg.find('[')
- idx2 = reg.find(']')
+ elif line[0] == "creg":
+ reg = line[1].rstrip(";")
+ idx1 = reg.find("[")
+ idx2 = reg.find("]")
cname = reg[0:idx1]
- csize = int(reg[idx1+1:idx2])
+ csize = int(reg[idx1 + 1 : idx2])
circuit.cregs[cname] = csize
globals()[cname] = list(range(csize))
- elif gate.startswith('angle'):
+ elif gate.startswith("angle"):
continue
- elif(line[0] == 'barrier'):
+ elif line[0] == "barrier":
raise Exception("Syntax error:" + line[0])
- elif line[0] == '//' or \
- (gate[0] == '/' and gate[1] == '/') or \
- line[0].startswith('OPENQASM') or \
- line[0] == 'include':
+ elif (
+ line[0] == "//"
+ or (gate[0] == "/" and gate[1] == "/")
+ or line[0].startswith("OPENQASM")
+ or line[0] == "include"
+ ):
continue
- elif line[0] == 'if':
+ elif line[0] == "if":
cname, cbit, cval = parse_condition(line[1], circuit.cregs)
op = line[2]
- if op in ('id','z','y','x','h','s','sdg','t','tdg'):
+ if op in ("id", "z", "y", "x", "h", "s", "sdg", "t", "tdg"):
qubit = get_num(line[3])
circuit.add_if(cname, cval, [op, qubit], cbit)
else:
raise Exception(f"Unsupported conditional op: {op}")
- elif len(line) >= 4 and line[1] == '=' and line[2] == 'measure':
+ elif len(line) >= 4 and line[1] == "=" and line[2] == "measure":
cname, cbit = get_creg_bit(line[0])
qubit = get_num(line[3])
circuit.add_measure(qubit, cname, cbit)
- elif line[0] == 'measure':
+ elif line[0] == "measure":
qubit = get_num(line[1])
cname, cbit = get_creg_bit(line[3])
circuit.add_measure(qubit, cname, cbit)
- elif gate == 'cx' or gate == 'cz' or gate == 'cy' or \
- gate == 'swap' or gate == 'iswap' or \
- gate == 'cs' or gate == 'csdg' or \
- gate == 'csqrtx' or gate == 'csqrtxdg' or \
- gate == 'ct' or gate == 'ctdg':
- if(line[1].count('[') == 1):
+ elif (
+ gate == "cx"
+ or gate == "cz"
+ or gate == "cy"
+ or gate == "swap"
+ or gate == "iswap"
+ or gate == "cs"
+ or gate == "csdg"
+ or gate == "csqrtx"
+ or gate == "csqrtxdg"
+ or gate == "ct"
+ or gate == "ctdg"
+ ):
+ if line[1].count("[") == 1:
qubitc = get_num(line[1])
qubitr = get_num(line[2])
else:
- qubits = line[1].split(',')
+ qubits = line[1].split(",")
qubitc = get_num(qubits[0])
qubitr = get_num(qubits[1])
circuit.add_double(gate, qubitc, qubitr)
- elif gate == 'id' or gate == 'z' or gate == 'y' or gate == 'x' or gate == 'h':
+ elif gate == "id" or gate == "z" or gate == "y" or gate == "x" or gate == "h":
qubit = get_num(line[1])
circuit.add_single(gate, qubit)
- elif gate == 'rz(pi)' or gate == 'rz(1.0*pi)':
+ elif gate == "rz(pi)" or gate == "rz(1.0*pi)":
qubit = get_num(line[1])
- circuit.add_single('z',qubit)
+ circuit.add_single("z", qubit)
- elif gate == 's' or gate == "rz(0.5*pi)" or gate == "rz(pi/2)":
+ elif gate == "s" or gate == "rz(0.5*pi)" or gate == "rz(pi/2)":
qubit = get_num(line[1])
circuit.add_single("s", qubit)
- elif gate == 'sdg' or gate == 'rz(-0.5*pi)' or gate == "rz(-pi/2)":
+ elif gate == "sdg" or gate == "rz(-0.5*pi)" or gate == "rz(-pi/2)":
qubit = get_num(line[1])
circuit.add_single("sdg", qubit)
- elif gate == 't' or gate == "rz(0.25*pi)" or gate == "rz(pi/4)":
+ elif gate == "t" or gate == "rz(0.25*pi)" or gate == "rz(pi/4)":
qubit = get_num(line[1])
circuit.add_single("t", qubit)
- elif gate == 'tdg' or gate == 'rz(-0.25*pi)' or gate == "rz(-pi/4)":
+ elif gate == "tdg" or gate == "rz(-0.25*pi)" or gate == "rz(-pi/4)":
qubit = get_num(line[1])
circuit.add_single("tdg", qubit)
- elif gate == 'ccx':
- if(line[1].count('[') == 1):
+ elif gate == "ccx":
+ if line[1].count("[") == 1:
qubitc1 = get_num(line[1])
qubitc2 = get_num(line[2])
qubitr = get_num(line[3])
else:
- qubits = line[1].split(',')
+ qubits = line[1].split(",")
qubitc1 = get_num(qubits[0])
qubitc2 = get_num(qubits[1])
qubitr = get_num(qubits[2])
circuit.add_ccx(qubitc1, qubitc2, qubitr)
elif len(gate) > 4 and gate[0:2] in RotationGates:
- gate_angle = re.findall(r"(.*?)\((.*?)\)",gate)[0]
+ gate_angle = re.findall(r"(.*?)\((.*?)\)", gate)[0]
rgate = gate_angle[0]
raw = gate_angle[1]
- if raw != "pi" and re.fullmatch(r'[A-Za-z_]\w*', raw):
+ if raw != "pi" and re.fullmatch(r"[A-Za-z_]\w*", raw):
angle = raw
else:
angle = get_angle(raw)
@@ -535,5 +600,5 @@ def QASMparser(filename) -> Circuit:
for item in circuit.measurements:
cname, cbit = item
qubit = circuit.measurements[item]
- circuit.circ.append(['measure', qubit, cname, cbit])
+ circuit.circ.append(["measure", qubit, cname, cbit])
return circuit
diff --git a/quokka_sharp/quokka_sharp/encoding/utils/rlist.py b/quokka_sharp/quokka_sharp/encoding/utils/rlist.py
index e1a08593..4bcfae94 100644
--- a/quokka_sharp/quokka_sharp/encoding/utils/rlist.py
+++ b/quokka_sharp/quokka_sharp/encoding/utils/rlist.py
@@ -1,7 +1,8 @@
class rlist(list):
- def __init__(self, default):
- self._default = default
- def __setitem__(self, key, value):
- if key >= len(self):
- self += [self._default] * (key - len(self) + 1)
- super(rlist, self).__setitem__(key, value)
\ No newline at end of file
+ def __init__(self, default):
+ self._default = default
+
+ def __setitem__(self, key, value):
+ if key >= len(self):
+ self += [self._default] * (key - len(self) + 1)
+ super(rlist, self).__setitem__(key, value)
diff --git a/quokka_sharp/quokka_sharp/eq.py b/quokka_sharp/quokka_sharp/eq.py
index ef3b20c6..dd281d5a 100644
--- a/quokka_sharp/quokka_sharp/eq.py
+++ b/quokka_sharp/quokka_sharp/eq.py
@@ -13,17 +13,17 @@
from decimal import Decimal, getcontext
-
procdict = {}
+
# define Python user-defined exceptions
class InvalidProcessNumException(Exception):
"Raised when the process number is invalid"
+
pass
def get_result(result, expected_prob, sqaure, epsilon):
-
"""
Analyse the weighted model counting result to decide the circuits are equivalent or not:
if the result matches the expected probablity then the circuits are equivalent otherwise they are not.
@@ -34,21 +34,23 @@ def get_result(result, expected_prob, sqaure, epsilon):
Returns:
True if the circuits are equivalent otherwise False
"""
-
+
# Global constants from config
- DEBUG = qc.CONFIG["DEBUG"]
- FPE = qc.CONFIG["FPE"]
- precision = qc.CONFIG["Precision"]
+ DEBUG = qc.CONFIG["DEBUG"]
+ FPE = qc.CONFIG["FPE"]
+ precision = qc.CONFIG["Precision"]
getcontext().prec = precision
prob = parse_wmc_result(result, sqaure)
- if DEBUG: print("probability:", prob)
+ if DEBUG:
+ print("probability:", prob)
if abs(prob - expected_prob) < (expected_prob * (Decimal(FPE) + Decimal(epsilon))):
return True
else:
return False
-def basis(i, Z_or_X, cnf:'CNF', cnf_file_root):
+
+def basis(i, Z_or_X, cnf: "CNF", cnf_file_root):
"""
Create cnf file for each of the basis in the linear check
Args:
@@ -63,11 +65,14 @@ def basis(i, Z_or_X, cnf:'CNF', cnf_file_root):
cnf_temp.rightProjectZXi(Z_or_X, i)
cnf_temp.leftProjectZXi(Z_or_X, i)
- cnf_file = os.path.join(cnf_file_root, "quokka_eq_check_"+ ("Z" if Z_or_X else "X") + str(i) + ".cnf")
+ cnf_file = os.path.join(
+ cnf_file_root, "quokka_eq_check_" + ("Z" if Z_or_X else "X") + str(i) + ".cnf"
+ )
cnf_temp.write_to_file(cnf_file)
return cnf_file
-def identity_check(cnf:'CNF', cnf_file_root, constrain_2n = False, constrain_no_Y = False):
+
+def identity_check(cnf: "CNF", cnf_file_root, constrain_2n=False, constrain_no_Y=False):
"""
Add idenity clauses to the encoding
Args:
@@ -79,13 +84,20 @@ def identity_check(cnf:'CNF', cnf_file_root, constrain_2n = False, constrain_no_
cnf_file : the path of the generated cnf file
"""
cnf_temp = copy.deepcopy(cnf)
- cnf_temp.add_identity_clauses(constrain_2n = constrain_2n, constrain_no_Y = constrain_no_Y)
+ cnf_temp.add_identity_clauses(
+ constrain_2n=constrain_2n, constrain_no_Y=constrain_no_Y
+ )
- cnf_file = os.path.join(cnf_file_root, f"quokka_eq_check_identity_{random.random()}.cnf")
+ cnf_file = os.path.join(
+ cnf_file_root, f"quokka_eq_check_identity_{random.random()}.cnf"
+ )
cnf_temp.write_to_file(cnf_file)
return cnf_file
-def CheckEquivalence(cnf: 'CNF', cnf_file_root = tempfile.gettempdir(), check = "cyclic", N=16, epsilon = 0):
+
+def CheckEquivalence(
+ cnf: "CNF", cnf_file_root=tempfile.gettempdir(), check="cyclic", N=16, epsilon=0
+):
"""
Check if the given circuit is equivalent to the identity
Args:
@@ -99,10 +111,10 @@ def CheckEquivalence(cnf: 'CNF', cnf_file_root = tempfile.gettempdir(), check =
global procdict # make sure handler sees this
# Global constants from config
- DEBUG = qc.CONFIG["DEBUG"]
- TIMEOUT = qc.CONFIG["TIMEOUT"]
+ DEBUG = qc.CONFIG["DEBUG"]
+ TIMEOUT = qc.CONFIG["TIMEOUT"]
tool_invocation = qc.CONFIG["ToolInvocation"]
- precision = qc.CONFIG["Precision"]
+ precision = qc.CONFIG["Precision"]
getcontext().prec = precision
@@ -119,10 +131,11 @@ def reap_all(procdict):
p.communicate(timeout=0.2)
except Exception:
pass
-
-
- if DEBUG: print()
- if DEBUG: print(f"comp: {cnf.computational_basis}, check: {check}")
+
+ if DEBUG:
+ print()
+ if DEBUG:
+ print(f"comp: {cnf.computational_basis}, check: {check}")
try:
cnf_file_list = []
@@ -147,7 +160,9 @@ def cleanup():
if check == "cyclic":
if N > 1:
raise InvalidProcessNumException
- cnf_file_list.append(identity_check(cnf, cnf_file_root, constrain_2n = False))
+ cnf_file_list.append(
+ identity_check(cnf, cnf_file_root, constrain_2n=False)
+ )
if cnf.computational_basis:
expected_prob = Decimal(2**cnf.n)
expected_abs_value = False
@@ -157,13 +172,17 @@ def cleanup():
elif check == "cyclic_linear":
if N > 1:
raise InvalidProcessNumException
- cnf_file_list.append(identity_check(cnf, cnf_file_root, constrain_2n = True))
- expected_prob = Decimal(2*cnf.n)
+ cnf_file_list.append(
+ identity_check(cnf, cnf_file_root, constrain_2n=True)
+ )
+ expected_prob = Decimal(2 * cnf.n)
expected_abs_value = False
elif check == "cyclic_noY":
if N > 1:
raise InvalidProcessNumException
- cnf_file_list.append(identity_check(cnf, cnf_file_root, constrain_no_Y = True))
+ cnf_file_list.append(
+ identity_check(cnf, cnf_file_root, constrain_no_Y=True)
+ )
expected_prob = Decimal(3**cnf.n)
expected_abs_value = False
elif check == "linear":
@@ -177,10 +196,11 @@ def cleanup():
expected_abs_value = False
else:
raise ValueError(f"Invalid check type {check}")
- if DEBUG: print(f"expected: {expected_prob}")
+ if DEBUG:
+ print(f"expected: {expected_prob}")
result = True
- tool_command = tool_invocation.split(' ')
+ tool_command = tool_invocation.split(" ")
# parallel processes
# N = 16
while True:
@@ -189,22 +209,27 @@ def cleanup():
for i in range(min(N, length)):
cnf_file = cnf_file_list[i]
tool_file_command = tool_command + [cnf_file]
-
- if DEBUG:
+
+ if DEBUG:
print(" ".join(tool_file_command))
-
- p = Popen(tool_file_command, stdout= PIPE, stderr=PIPE, start_new_session=True)
+
+ p = Popen(
+ tool_file_command,
+ stdout=PIPE,
+ stderr=PIPE,
+ start_new_session=True,
+ )
procdict[p.pid] = p
-
+
if len(procdict) == 0:
break
-
+
while procdict:
# Enforce a single shared timeout for all subprocesses
if time.monotonic() > deadline:
cleanup()
return "TIMEOUT"
-
+
# Non-blocking wait: allows periodic timeout checks
pid, _ = os.waitpid(-1, os.WNOHANG)
if pid == 0:
@@ -212,11 +237,11 @@ def cleanup():
time.sleep(0.05)
continue
p = procdict.get(pid)
-
+
if p is None:
# Reaped an unrelated child process
continue
-
+
out, err = p.communicate()
result = get_result(out, expected_prob, expected_abs_value, epsilon)
@@ -224,11 +249,11 @@ def cleanup():
# Early termination: kill all remaining subprocesses
cleanup()
return result
-
+
procdict.pop(pid, None)
if length > N:
- cnf_file_list = cnf_file_list[N: length]
+ cnf_file_list = cnf_file_list[N:length]
else:
break
return result
@@ -246,4 +271,4 @@ def cleanup():
reap_all(procdict)
except Exception:
pass
- procdict = {}
\ No newline at end of file
+ procdict = {}
diff --git a/quokka_sharp/quokka_sharp/functionalities.py b/quokka_sharp/quokka_sharp/functionalities.py
index a891a4cd..413199d8 100644
--- a/quokka_sharp/quokka_sharp/functionalities.py
+++ b/quokka_sharp/quokka_sharp/functionalities.py
@@ -1,85 +1,103 @@
import quokka_sharp as qk
from .utils.utils import validate_basis
+
class functionalities:
- def eq(qasmfile1, qasmfile2, basis, check, N = 1, epsilon = 0):
- """
- Compare two quantum circuits given in QASM format.
- :param qasmfile1: Path to the first QASM file.
- :param qasmfile2: Path to the second QASM file.
- :param basis: The basis to use for the comparison, either "comp" for computational basis or "pauli" for Pauli basis.
- :param check: The type of check to use, either "cyclic", "linear", or "cyclic_linear". Must be "cyclic" if basis is "comp".
- :param N: The number of parallel calles to the WMC, relevant only if check is "linear".
- :return: Boolean indicating whether the two circuits are equivalent.
- """
- # Parse the circuit
- circuit1 = qk.encoding.QASMparser(qasmfile1)
- # Parse another circuit
- circuit2 = qk.encoding.QASMparser(qasmfile2)
- # Get (circuit1)^dagger(circuit2)
- circuit2.dagger()
- circuit1.append(circuit2)
- basis = validate_basis(basis)
- # Get CNF for the merged circuit (for computational base instaed of cliffordt, use `computational_basis = True`)
- cnf = qk.encoding.QASM2CNF(circuit1, computational_basis = (basis == "comp"))
- # "id" or "2n"
- return qk.CheckEquivalence(cnf, check = check, N = N, epsilon = epsilon)
-
- def sim(qasmfile, basis, measurement):
- """
- Simulate a quantum circuit given in QASM format.
- :param qasmfile: Path to the QASM file.
- :param basis: The basis to use for the simulation, either "comp" for computational basis or "pauli" for Pauli basis.
- :param measurement: The type of measurement to compute the probability for, either "allzero", "firstzero", or a dictionary with qubit indices and their expected values.
- :return: Simulation result, a float representing the probability of the measurement outcome.
- """
- # Parse the circuit
- circuit1 = qk.encoding.QASMparser(qasmfile)
- basis = validate_basis(basis)
- # Encode the circuit
- cnf = qk.encoding.QASM2CNF(circuit1, computational_basis = (basis == "comp"))
- cnf.leftProjectAllZero()
- cnf.add_measurement(measurement)
- return qk.Simulate(cnf)
-
- def syn(qasmfile, basis, cyc_lin_encoding=False, fid=1, files_root=None, gate_set={'h', 'cx', 't'}):
- """
- Synthesize a quantum circuit given in QASM format.
- :param qasmfile: Path to the QASM file.
- :param basis: The basis to use for the synthesis, either "comp" for computational basis or "pauli" for Pauli basis.
- :param cyc_lin_encoding: Whether to use the cyclic linear encoding, relevant only for the Pauli basis.
- :param fid: Fidelity threshold for the synthesis.
- :param files_root: Root directory for the cnf and MWMC output files.
- :param gate_set: A set of gates to use for the synthesis, relevant only for the Pauli basis. Default is {'h', 'cx', 't'}.
- :return: A tuple with four elements:
- - outcome(str): The outcome of the synthesis, which can be "FOUND", "TIMEOUT", or "CRASH".
- - weight(float): The weight of the best synthesized circuit.
- - qasm(str): A QASM representation of the synthesized circuit.
- - layers(int): The number of layers in the synthesized circuit.
- """
- circuit = qk.encoding.QASMparser(qasmfile)
- circuit.dagger()
- basis = validate_basis(basis)
- cnf = qk.encoding.QASM2CNF(circuit, computational_basis = (basis == "comp"))
+ @staticmethod
+ def eq(qasmfile1, qasmfile2, basis, check, N=1, epsilon=0):
+ """
+ Compare two quantum circuits given in QASM format.
+ :param qasmfile1: Path to the first QASM file.
+ :param qasmfile2: Path to the second QASM file.
+ :param basis: The basis to use for the comparison, either "comp" for computational basis or "pauli" for Pauli basis.
+ :param check: The type of check to use, either "cyclic", "linear", or "cyclic_linear". Must be "cyclic" if basis is "comp".
+ :param N: The number of parallel calles to the WMC, relevant only if check is "linear".
+ :return: Boolean indicating whether the two circuits are equivalent.
+ """
+ # Parse the circuit
+ circuit1 = qk.encoding.QASMparser(qasmfile1)
+ # Parse another circuit
+ circuit2 = qk.encoding.QASMparser(qasmfile2)
+ # Get (circuit1)^dagger(circuit2)
+ circuit2.dagger()
+ circuit1.append(circuit2)
+ basis = validate_basis(basis)
+ # Get CNF for the merged circuit (for computational base instaed of cliffordt, use `computational_basis = True`)
+ cnf = qk.encoding.QASM2CNF(circuit1, computational_basis=(basis == "comp"))
+ # "id" or "2n"
+ return qk.CheckEquivalence(cnf, check=check, N=N, epsilon=epsilon)
+
+ @staticmethod
+ def sim(qasmfile, basis, measurement):
+ """
+ Simulate a quantum circuit given in QASM format.
+ :param qasmfile: Path to the QASM file.
+ :param basis: The basis to use for the simulation, either "comp" for computational basis or "pauli" for Pauli basis.
+ :param measurement: The type of measurement to compute the probability for, either "allzero", "firstzero", or a dictionary with qubit indices and their expected values.
+ :return: Simulation result, a float representing the probability of the measurement outcome.
+ """
+ # Parse the circuit
+ circuit1 = qk.encoding.QASMparser(qasmfile)
+ basis = validate_basis(basis)
+ # Encode the circuit
+ cnf = qk.encoding.QASM2CNF(circuit1, computational_basis=(basis == "comp"))
+ cnf.leftProjectAllZero()
+ cnf.add_measurement(measurement)
+ return qk.Simulate(cnf)
+
+ @staticmethod
+ def syn(
+ qasmfile,
+ basis,
+ cyc_lin_encoding=False,
+ fid=1.0,
+ files_root=None,
+ gate_set={"h", "cx", "t"},
+ ):
+ """
+ Synthesize a quantum circuit given in QASM format.
+ :param qasmfile: Path to the QASM file.
+ :param basis: The basis to use for the synthesis, either "comp" for computational basis or "pauli" for Pauli basis.
+ :param cyc_lin_encoding: Whether to use the cyclic linear encoding, relevant only for the Pauli basis.
+ :param fid: Fidelity threshold for the synthesis.
+ :param files_root: Root directory for the cnf and MWMC output files.
+ :param gate_set: A set of gates to use for the synthesis, relevant only for the Pauli basis. Default is {'h', 'cx', 't'}.
+ :return: A tuple with four elements:
+ - outcome(str): The outcome of the synthesis, which can be "FOUND", "TIMEOUT", or "CRASH".
+ - weight(float): The weight of the best synthesized circuit.
+ - qasm(str): A QASM representation of the synthesized circuit.
+ - layers(int): The number of layers in the synthesized circuit.
+ """
+ circuit = qk.encoding.QASMparser(qasmfile)
+ circuit.dagger()
+ basis = validate_basis(basis)
+ cnf = qk.encoding.QASM2CNF(circuit, computational_basis=(basis == "comp"))
+
+ return qk.Synthesis(
+ cnf,
+ onehot_xz=cyc_lin_encoding,
+ fidelity_threshold=fid,
+ cnf_file_root=files_root,
+ gate_set=gate_set,
+ )
+
+ @staticmethod
+ def verify(qasmfile, basis, precons, postcons):
+ """
+ Verify a quantum circuit given in QASM format against preconditions and postconditions.
+ :param qasmfile: Path to the QASM file.
+ :param basis: The basis to use for the verification, either "comp" for computational basis or "pauli" for Pauli basis.
+ :param precons: A dictionary with qubit indices and their expected values describing the preconditions to enforce before the circuit execution.
+ :param postcons:A dictionary with qubit indices and their expected values describing the postconditions to check after the circuit execution.
+ :return: Verification result, which can be "True", "False", or "TIMEOUT".
+ """
- return qk.Synthesis(cnf, onehot_xz = cyc_lin_encoding, fidelity_threshold = fid, cnf_file_root=files_root, gate_set=gate_set)
-
- def verify(qasmfile, basis, precons, postcons):
- """
- Verify a quantum circuit given in QASM format against preconditions and postconditions.
- :param qasmfile: Path to the QASM file.
- :param basis: The basis to use for the verification, either "comp" for computational basis or "pauli" for Pauli basis.
- :param precons: A dictionary with qubit indices and their expected values describing the preconditions to enforce before the circuit execution.
- :param postcons:A dictionary with qubit indices and their expected values describing the postconditions to check after the circuit execution.
- :return: Verification result, which can be "True", "False", or "TIMEOUT".
- """
+ # Parse the circuit
+ circuit = qk.encoding.QASMparser(qasmfile)
+ basis = validate_basis(basis)
- # Parse the circuit
- circuit = qk.encoding.QASMparser(qasmfile)
- basis = validate_basis(basis)
-
- # Get CNF for the merged circuit (for computational base instaed of cliffordt, use `computational_basis = True`)
- cnf = qk.encoding.QASM2CNF(circuit, computational_basis = (basis == "comp"))
- # "id" or "2n"
- res = qk.Verify(cnf, precons, postcons)
- return res
\ No newline at end of file
+ # Get CNF for the merged circuit (for computational base instaed of cliffordt, use `computational_basis = True`)
+ cnf = qk.encoding.QASM2CNF(circuit, computational_basis=(basis == "comp"))
+ # "id" or "2n"
+ res = qk.Verify(cnf, precons, postcons)
+ return res
diff --git a/quokka_sharp/quokka_sharp/sim.py b/quokka_sharp/quokka_sharp/sim.py
index e7c607d4..c8b5277d 100644
--- a/quokka_sharp/quokka_sharp/sim.py
+++ b/quokka_sharp/quokka_sharp/sim.py
@@ -13,9 +13,6 @@
# Global constants from config
-
-
-
def WMC(wmc_file, square):
"""
Parse the output of WMC to get the weighted model counting result
@@ -25,21 +22,21 @@ def WMC(wmc_file, square):
Returns:
result : the probability of the circuit
"""
- TIMEOUT = qc.CONFIG["TIMEOUT"]
+ TIMEOUT = qc.CONFIG["TIMEOUT"]
tool_invocation = qc.CONFIG["ToolInvocation"]
- tool_command = tool_invocation.split(' ')
+ tool_command = tool_invocation.split(" ")
tool_command.append(wmc_file)
p = Popen(tool_command, stdout=PIPE)
- try:
- result = p.communicate(timeout = TIMEOUT)
+ try:
+ result = p.communicate(timeout=TIMEOUT)
return parse_wmc_result(result, square)
except TimeoutExpired:
os.system("kill -9 " + str(p.pid))
return "TIMEOUT"
-def Simulate(cnf: "CNF", cnf_file_root = tempfile.gettempdir()):
+def Simulate(cnf: "CNF", cnf_file_root=tempfile.gettempdir()):
"""
Simulate a quantum circuit and give the corresponding probability
Args:
@@ -47,14 +44,16 @@ def Simulate(cnf: "CNF", cnf_file_root = tempfile.gettempdir()):
Returns:
result : the probability of the circuit
"""
- DEBUG = qc.CONFIG["DEBUG"]
+ DEBUG = qc.CONFIG["DEBUG"]
if cnf.vars.i == -1:
filename = os.path.join(cnf_file_root, "for_sim.cnf")
cnf.write_to_file(filename)
- result = WMC(filename, square = cnf.square_result)
+ result = WMC(filename, square=cnf.square_result)
if result != "TIMEOUT":
- result = Decimal(result) * (Decimal(1/2)**Decimal(cnf.power_two_normalisation))
+ result = Decimal(result) * (
+ Decimal(1 / 2) ** Decimal(cnf.power_two_normalisation)
+ )
return result
else:
sum_results = 0
@@ -63,32 +62,53 @@ def Simulate(cnf: "CNF", cnf_file_root = tempfile.gettempdir()):
for s in ["p", "m"]:
for t in ["e", "o"]:
filename = os.path.join(cnf_file_root, "for_sim.cnf")
- if DEBUG: print(c,s,t)
+ if DEBUG:
+ print(c, s, t)
cnf_copy = copy.deepcopy(cnf)
if cnf.computational_basis:
- cnf_copy.add_clause([-cnf.vars.i] if (c == "r") else [cnf.vars.i], comment="complex")
- cnf_copy.add_clause([-cnf.vars.r] if (s == "p") else [cnf.vars.r], comment="sign")
- cnf_copy.add_clause([-cnf.vars.u] if (t == "e") else [cnf.vars.u], comment="sqrt2 norm")
-
+ cnf_copy.add_clause(
+ [-cnf.vars.i] if (c == "r") else [cnf.vars.i],
+ comment="complex",
+ )
+ cnf_copy.add_clause(
+ [-cnf.vars.r] if (s == "p") else [cnf.vars.r], comment="sign"
+ )
+ cnf_copy.add_clause(
+ [-cnf.vars.u] if (t == "e") else [cnf.vars.u],
+ comment="sqrt2 norm",
+ )
+
cnf_copy.write_to_file(filename)
- result = WMC(filename, square = False)
+ result = WMC(filename, square=False)
if result == "TIMEOUT":
return result
-
- if DEBUG: print(f" initial: {result}")
+
+ if DEBUG:
+ print(f" initial: {result}")
if s == "m":
result = -result
-
+
if t == "e":
- result = Decimal(result) * (Decimal(1/2)**Decimal(cnf.power_two_normalisation))
- if DEBUG: print(f" fixed: {result} (/{cnf.power_two_normalisation})")
+ result = Decimal(result) * (
+ Decimal(1 / 2) ** Decimal(cnf.power_two_normalisation)
+ )
+ if DEBUG:
+ print(
+ f" fixed: {result} (/{cnf.power_two_normalisation})"
+ )
else:
- result = Decimal(result) * (Decimal(1/2)**Decimal(cnf.power_two_normalisation-0.5))
- if DEBUG: print(f" fixed: {result} (/{cnf.power_two_normalisation-0.5})")
+ result = Decimal(result) * (
+ Decimal(1 / 2) ** Decimal(cnf.power_two_normalisation - 0.5)
+ )
+ if DEBUG:
+ print(
+ f" fixed: {result} (/{cnf.power_two_normalisation - 0.5})"
+ )
complex_sum += result
sum_results += complex_sum * complex_sum
if not cnf.square_result:
sum_results = sum_results.sqrt()
- if DEBUG: print(f" sum_results: {sum_results}")
- return sum_results
\ No newline at end of file
+ if DEBUG:
+ print(f" sum_results: {sum_results}")
+ return sum_results
diff --git a/quokka_sharp/quokka_sharp/syn.py b/quokka_sharp/quokka_sharp/syn.py
index a53c8108..cccc8308 100644
--- a/quokka_sharp/quokka_sharp/syn.py
+++ b/quokka_sharp/quokka_sharp/syn.py
@@ -12,7 +12,6 @@
from .utils.timeout import timeout, TimeoutException
-
def get_result(result_file, expexted_prob, abs_value):
"""
Parse the output of the tool to get the result
@@ -28,35 +27,41 @@ def get_result(result_file, expexted_prob, abs_value):
"""
# Global constants from config
- FPE = qc.CONFIG["FPE"]
- precision = qc.CONFIG["Precision"]
+ FPE = qc.CONFIG["FPE"]
+ precision = qc.CONFIG["Precision"]
- getcontext().prec = precision
+ getcontext().prec = precision
- with open(result_file, 'r') as file:
+ with open(result_file, "r") as file:
result_str = file.read()
- if re.findall(r"s UNSAT",result_str):
+ if re.findall(r"s UNSAT", result_str):
return (False, 0, [])
-
+
# get weight
weight_regex = r"-?[0-9\.\s+\-i]+"
- if re.findall(r"r SATISFIABLE",result_str):
- weight = re.findall(r"s " + weight_regex,result_str)
+ if re.findall(r"r SATISFIABLE", result_str):
+ weight = re.findall(r"s " + weight_regex, result_str)
else:
- weight = re.findall(r"[okt] " + weight_regex,result_str)
- if not weight:
+ weight = re.findall(r"[okt] " + weight_regex, result_str)
+ if not weight:
return (False, "CRASH", [])
- # get assignment
- assignment = re.findall(r"v [0-9\s\-]+ 0",result_str)
+ # get assignment
+ assignment = re.findall(r"v [0-9\s\-]+ 0", result_str)
assert len(weight) == 1
- weight = weight[0][2:].replace("\\n", "").replace(" ", "").replace("i", "j").replace("+-", "-")
+ weight = (
+ weight[0][2:]
+ .replace("\\n", "")
+ .replace(" ", "")
+ .replace("i", "j")
+ .replace("+-", "-")
+ )
weight = complex(weight)
real_w, imag_w = Decimal(weight.real), Decimal(weight.imag)
if abs_value:
- weight = (abs(real_w)**2 + abs(imag_w)**2).sqrt()
+ weight = (abs(real_w) ** 2 + abs(imag_w) ** 2).sqrt()
else:
weight = real_w
if assignment:
@@ -68,12 +73,13 @@ def get_result(result_file, expexted_prob, abs_value):
expexted_w = Decimal(expexted_prob)
# print(f"achived: {weight}/{expexted_w}")
- prec = weight/expexted_w
+ prec = weight / expexted_w
if abs_value:
prec = prec**2
- return (prec > (1-FPE), prec, assignment)
-
-def identity_check(cnf:'CNF', cnf_file_root, files_prefix, indx, onehot_xz = False):
+ return (prec > (1 - FPE), prec, assignment)
+
+
+def identity_check(cnf: "CNF", cnf_file_root, files_prefix, indx, onehot_xz=False):
"""
Function to create a temporary file with the identity check
Args:
@@ -84,18 +90,31 @@ def identity_check(cnf:'CNF', cnf_file_root, files_prefix, indx, onehot_xz = Fal
onehot_xz : when the value is true use one-hot encoding for x and z
"""
cnf_temp = copy.deepcopy(cnf)
- cnf_temp.add_identity_clauses(constrain_2n = onehot_xz)
-
- cnf_file = os.path.join(cnf_file_root, f"{files_prefix}_{indx}_quokka_syn.cnf") # overide files to reduce spaming
+ cnf_temp.add_identity_clauses(constrain_2n=onehot_xz)
+
+ cnf_file = os.path.join(
+ cnf_file_root, f"{files_prefix}_{indx}_quokka_syn.cnf"
+ ) # overide files to reduce spaming
cnf_temp.write_to_file(cnf_file, syntesis_fomat=True)
return cnf_file
-def Synthesis(cnf: 'CNF', cnf_file_root = tempfile.gettempdir(), gate_set = set(), fidelity_threshold = 1, bin_search=False, initial_depth=0, onehot_xz = False, h_sandwich = False, printing = False):
- printing = qc.CONFIG["DEBUG"]
+
+def Synthesis(
+ cnf: "CNF",
+ cnf_file_root=tempfile.gettempdir(),
+ gate_set=set(),
+ fidelity_threshold=1.0,
+ bin_search=False,
+ initial_depth=0,
+ onehot_xz=False,
+ h_sandwich=False,
+ printing=False,
+):
+ printing = qc.CONFIG["DEBUG"]
# Global constants from config
- TIMEOUT = qc.CONFIG["TIMEOUT"]
+ TIMEOUT = qc.CONFIG["TIMEOUT"]
tool_invocation = qc.CONFIG["D4ToolInvocation"]
- precision = qc.CONFIG["Precision"]
+ precision = qc.CONFIG["Precision"]
getcontext().prec = precision
"""
@@ -117,14 +136,20 @@ def Synthesis(cnf: 'CNF', cnf_file_root = tempfile.gettempdir(), gate_set = set(
qasm: the qasm of the found circuit
depth: the depth of the found circuit
"""
- if printing: print()
- if printing: print(f"basis:{'Comp' if cnf.computational_basis else 'Pauli'}, exact:{onehot_xz}")
- if printing: print(f"fidelity_threshold:{fidelity_threshold}")
+ if printing:
+ print()
+ if printing:
+ print(
+ f"basis:{'Comp' if cnf.computational_basis else 'Pauli'}, exact:{onehot_xz}"
+ )
+ if printing:
+ print(f"fidelity_threshold:{fidelity_threshold}")
p = None
def cleanup():
# Called on timeout to kill the process
- if printing: print(f"TIMEOUT expiered")
+ if printing:
+ print(f"TIMEOUT expiered")
if p is not None:
p.kill()
@@ -132,7 +157,7 @@ def cleanup():
with timeout(TIMEOUT, on_timeout=cleanup):
# Set expected probability and value type based on encoding
if onehot_xz:
- expected_prob = 2*cnf.n
+ expected_prob = 2 * cnf.n
expected_abs_value = False
elif cnf.computational_basis:
expected_prob = 2**cnf.n
@@ -140,7 +165,7 @@ def cleanup():
else:
expected_prob = 4**cnf.n
expected_abs_value = False
-
+
done = False
bin_lb = 0
bin_ub = None
@@ -162,19 +187,39 @@ def cleanup():
skip_first = True
it_counter = 0 if skip_first else 1
while not done:
- if printing: print()
+ if printing:
+ print()
# if printing: print(f"Global Time: {datetime.datetime.now()}")
start = time.time()
- if printing: print(f"Iteration: {it_counter}")
+ if printing:
+ print(f"Iteration: {it_counter}")
# Set file prefix for output files
- files_prefix = (("onehotXZ" if onehot_xz else "fullP") if not cnf.computational_basis else "comp") + "_" + ("HSan" if h_sandwich else "Reg")
+ files_prefix = (
+ (
+ ("onehotXZ" if onehot_xz else "fullP")
+ if not cnf.computational_basis
+ else "comp"
+ )
+ + "_"
+ + ("HSan" if h_sandwich else "Reg")
+ )
# Prepare CNF for this iteration
if bin_search:
cnf_copy = copy.deepcopy(cnf_copy_init)
- cnf_copy.add_syn_layer(gate_set, num_layers, limit_gates=h_sandwich, h_layer=False)
- cnf_copy.add_syn_layer(gate_set, 1, limit_gates=h_sandwich, h_layer=True)
- file = identity_check(cnf_copy, cnf_file_root, files_prefix, it_counter, onehot_xz = onehot_xz)
+ cnf_copy.add_syn_layer(
+ gate_set, num_layers, limit_gates=h_sandwich, h_layer=False
+ )
+ cnf_copy.add_syn_layer(
+ gate_set, 1, limit_gates=h_sandwich, h_layer=True
+ )
+ file = identity_check(
+ cnf_copy,
+ cnf_file_root,
+ files_prefix,
+ it_counter,
+ onehot_xz=onehot_xz,
+ )
else:
if h_sandwich:
cnf_copy = cnf_revert
@@ -184,21 +229,40 @@ def cleanup():
cnf_copy.add_syn_layer(gate_set)
if h_sandwich:
cnf_revert = copy.deepcopy(cnf_copy)
- cnf_copy.add_syn_layer(gate_set, 1, limit_gates=h_sandwich, h_layer=True)
- file = identity_check(cnf_copy, cnf_file_root, files_prefix, it_counter, onehot_xz = onehot_xz)
+ cnf_copy.add_syn_layer(
+ gate_set, 1, limit_gates=h_sandwich, h_layer=True
+ )
+ file = identity_check(
+ cnf_copy,
+ cnf_file_root,
+ files_prefix,
+ it_counter,
+ onehot_xz=onehot_xz,
+ )
# Build the command to run the external tool
- command = (tool_invocation.split(' ') +
- ["-i", file] +
- ["--complex", ("1" if cnf.computational_basis else "0")] +
- ["--threshold", str(expected_prob * fidelity_threshold) + (" 0" if cnf.computational_basis else "")]
- )
- out_file = os.path.join(cnf_file_root, f"{files_prefix}_{it_counter}_d4.out")
- err_file = os.path.join(cnf_file_root, f"{files_prefix}_{it_counter}_d4.err")
- res_file = os.path.join(cnf_file_root, f"{files_prefix}_{it_counter}_d4.res")
+ command = (
+ tool_invocation.split(" ")
+ + ["-i", file]
+ + ["--complex", ("1" if cnf.computational_basis else "0")]
+ + [
+ "--threshold",
+ str(expected_prob * fidelity_threshold)
+ + (" 0" if cnf.computational_basis else ""),
+ ]
+ )
+ out_file = os.path.join(
+ cnf_file_root, f"{files_prefix}_{it_counter}_d4.out"
+ )
+ err_file = os.path.join(
+ cnf_file_root, f"{files_prefix}_{it_counter}_d4.err"
+ )
+ res_file = os.path.join(
+ cnf_file_root, f"{files_prefix}_{it_counter}_d4.res"
+ )
# Run the external tool
- with open(out_file, 'w') as out:
- with open(err_file, 'w') as err:
+ with open(out_file, "w") as out:
+ with open(err_file, "w") as err:
p = Popen(command, stdout=out, stderr=err)
pid = None
err = 0
@@ -211,8 +275,11 @@ def cleanup():
if cerr:
return f"ERROR{cerr}", 0, res, cnf_copy.syn_gate_layer
# Parse the result
- found, weight, assignment = get_result(out_file, expected_prob, abs_value=expected_abs_value)
- if printing: print(f"fidelity:{weight}")
+ found, weight, assignment = get_result(
+ out_file, expected_prob, abs_value=expected_abs_value
+ )
+ if printing:
+ print(f"fidelity:{weight}")
if weight == "CRASH":
return "CRASH", 0, "", cnf_copy.syn_gate_layer
# Generate QASM from assignment
@@ -224,7 +291,7 @@ def cleanup():
if found:
bin_ub = num_layers
bin_ub_results = (weight, qasm)
- else:
+ else:
bin_lb = num_layers
if bin_lb + 1 == bin_ub:
if found == False:
@@ -233,26 +300,45 @@ def cleanup():
if bin_ub:
num_layers = int((bin_lb + bin_ub) / 2)
else:
- num_layers = int(bin_lb*2)
+ num_layers = int(bin_lb * 2)
else:
if found or weight > fidelity_threshold:
done = True
- if printing: print(f"Run Time: {time.time()-start}")
- it_counter+=1
+ if printing:
+ print(f"Run Time: {time.time() - start}")
+ it_counter += 1
# Return result if found
return "FOUND", weight, qasm, cnf_copy.syn_gate_layer
except TimeoutException as error:
- if printing: print(f"Run Time: {time.time()-start}")
- t_found, t_weight, t_assignment = get_result(out_file, expected_prob, expected_abs_value)
+ if printing:
+ print(f"Run Time: {time.time() - start}")
+ t_found, t_weight, t_assignment = get_result(
+ out_file, expected_prob, expected_abs_value
+ )
if t_found:
- return "FOUND", t_weight, cnf_copy.get_syn_qasm(t_assignment), cnf_copy.syn_gate_layer
+ return (
+ "FOUND",
+ t_weight,
+ cnf_copy.get_syn_qasm(t_assignment),
+ cnf_copy.syn_gate_layer,
+ )
if bin_ub_results:
- return str(error), bin_ub_results[0], bin_ub_results[1], cnf_copy.syn_gate_layer
+ return (
+ str(error),
+ bin_ub_results[0],
+ bin_ub_results[1],
+ cnf_copy.syn_gate_layer,
+ )
elif weight == "CRASH":
return str(error), weight, qasm, cnf_copy.syn_gate_layer
else:
- if t_weight!="CRASH" and t_weight > weight:
- return str(error), t_weight, cnf_copy.get_syn_qasm(t_assignment), cnf_copy.syn_gate_layer
- return str(error), weight, qasm, cnf_copy.syn_gate_layer
\ No newline at end of file
+ if t_weight != "CRASH" and t_weight > weight:
+ return (
+ str(error),
+ t_weight,
+ cnf_copy.get_syn_qasm(t_assignment),
+ cnf_copy.syn_gate_layer,
+ )
+ return str(error), weight, qasm, cnf_copy.syn_gate_layer
diff --git a/quokka_sharp/quokka_sharp/utils/__init__.py b/quokka_sharp/quokka_sharp/utils/__init__.py
index d83dc052..99a9b15c 100644
--- a/quokka_sharp/quokka_sharp/utils/__init__.py
+++ b/quokka_sharp/quokka_sharp/utils/__init__.py
@@ -4,4 +4,4 @@
from .timeout import timeout
-from .utils import parse_wmc_result
\ No newline at end of file
+from .utils import parse_wmc_result
diff --git a/quokka_sharp/quokka_sharp/utils/timeout.py b/quokka_sharp/quokka_sharp/utils/timeout.py
index 26004a37..c6638044 100644
--- a/quokka_sharp/quokka_sharp/utils/timeout.py
+++ b/quokka_sharp/quokka_sharp/utils/timeout.py
@@ -3,7 +3,7 @@
from .. import config as qc
tool_invocation = qc.CONFIG["ToolInvocation"]
-get_result = qc.CONFIG["GetResult"]
+get_result = qc.CONFIG["GetResult"]
procdict = {}
@@ -12,17 +12,23 @@
import signal
from contextlib import contextmanager
+
class TimeoutException(Exception):
pass
+
class MemoutError(Exception):
"""Raised when a memory limit is exceeded or result indicates MEMOUT"""
+
pass
+
class MCError(Exception):
"""Raised when model counter has reported an error."""
+
pass
+
@contextmanager
def timeout(seconds, on_timeout=None):
def _handle_timeout(signum, frame):
diff --git a/quokka_sharp/quokka_sharp/utils/utils.py b/quokka_sharp/quokka_sharp/utils/utils.py
index 536f8b9a..4d4d1029 100644
--- a/quokka_sharp/quokka_sharp/utils/utils.py
+++ b/quokka_sharp/quokka_sharp/utils/utils.py
@@ -5,9 +5,9 @@
from .. import config as qc
+_num = r"[+\-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+\-]?\d+)?"
+_pat = re.compile(rf"^\s*({_num})?\s*([+\-]\s*{_num})?\s*[ij]?\s*$")
-_num = r'[+\-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+\-]?\d+)?'
-_pat = re.compile(rf'^\s*({_num})?\s*([+\-]\s*{_num})?\s*[ij]?\s*$')
def parse_complex_decimal(s: str):
s = s.strip().replace("\\n", "").replace(" ", "")
@@ -15,7 +15,7 @@ def parse_complex_decimal(s: str):
if "j" not in s:
return Decimal(s), Decimal(0)
-
+
if not s.endswith("j"):
raise ValueError(f"Unexpected complex format: {s}")
core = s[:-1]
@@ -33,46 +33,61 @@ def parse_complex_decimal(s: str):
imag_s = (m.group(2) or "0").replace("+-", "-")
return Decimal(real_s), Decimal(imag_s)
+
def parse_wmc_result(result, square: bool):
- get_result = qc.CONFIG["GetResult"]
- DEBUG = qc.CONFIG["DEBUG"]
- FPE = qc.CONFIG["FPE"]
- Precision = qc.CONFIG["Precision"]
- getcontext().prec = Precision
+ get_result = qc.CONFIG["GetResult"]
+ DEBUG = qc.CONFIG["DEBUG"]
+ FPE = qc.CONFIG["FPE"]
+ Precision = qc.CONFIG["Precision"]
+ getcontext().prec = Precision
"""Parse the output of WMC to get the weighted model counting result."""
- ans_str = re.findall( re.compile(get_result), str(result))
- if DEBUG: print("weighted model counting result:", ans_str)
+ ans_str = re.findall(re.compile(get_result), str(result))
+ if DEBUG:
+ print("weighted model counting result:", ans_str)
if not ans_str:
- if DEBUG: print("No result found, returning -1")
- if DEBUG: print("output:", result)
- if DEBUG: print("regex:", get_result)
- if DEBUG: print("ans_str:", ans_str)
+ if DEBUG:
+ print("No result found, returning -1")
+ if DEBUG:
+ print("output:", result)
+ if DEBUG:
+ print("regex:", get_result)
+ if DEBUG:
+ print("ans_str:", ans_str)
raise MCError(
- f"Model counter produced no parseable result\n"
- f" output = {result}\n"
- f" regex = {get_result}\n"
- f" ans = {ans_str}")
- ans_str = ans_str[0].replace("\\n", "").replace(" ", "").replace("i", "j").replace("+-", "-")
+ f"Model counter produced no parseable result\n"
+ f" output = {result}\n"
+ f" regex = {get_result}\n"
+ f" ans = {ans_str}"
+ )
+ ans_str = (
+ ans_str[0]
+ .replace("\\n", "")
+ .replace(" ", "")
+ .replace("i", "j")
+ .replace("+-", "-")
+ )
# ans = complex(ans_str)
# real, imag = Decimal(ans.real), Decimal(ans.imag)
-
+
real, imag = parse_complex_decimal(ans_str)
if DEBUG:
print(real)
-
if abs(real) < FPE and abs(imag) < FPE:
return 0
elif abs(imag) < FPE:
return real * real if square else real
else:
- return (real * real + imag * imag) if square else (real * real + imag * imag).sqrt()
-
+ return (
+ (real * real + imag * imag)
+ if square
+ else (real * real + imag * imag).sqrt()
+ )
+
+
def validate_basis(basis: str) -> str:
basis = basis.lower()
if basis not in {"pauli", "comp"}:
- raise ValueError(
- f"Invalid basis: {basis!r}. Expected 'pauli' or 'comp'."
- )
- return basis
\ No newline at end of file
+ raise ValueError(f"Invalid basis: {basis!r}. Expected 'pauli' or 'comp'.")
+ return basis
diff --git a/quokka_sharp/quokka_sharp/veri.py b/quokka_sharp/quokka_sharp/veri.py
index 315e7bf5..f87f89c4 100644
--- a/quokka_sharp/quokka_sharp/veri.py
+++ b/quokka_sharp/quokka_sharp/veri.py
@@ -13,7 +13,7 @@ def Verify(cnf: "CNF", precons, postcons):
Returns:
result : "True" if the circuit is equivalent to the identity, "False" if not, "TIMEOUT" if the simulation times out.
"""
- FPE = qc.CONFIG["FPE"]
+ FPE = qc.CONFIG["FPE"]
cnf.precondition(precons)
cnf.postcondition(postcons)
diff --git a/quokka_sharp/setup.py b/quokka_sharp/setup.py
index 620700a2..9e58332b 100644
--- a/quokka_sharp/setup.py
+++ b/quokka_sharp/setup.py
@@ -1,34 +1,33 @@
from setuptools import setup, find_packages
-VERSION = '2.7.5'
-DESCRIPTION = 'Quokka Sharp'
-LONG_DESCRIPTION = '''
+VERSION = "2.7.5"
+DESCRIPTION = "Quokka Sharp"
+LONG_DESCRIPTION = """
Quokka Sharp for simulating and equivalence checking
of quantum circuits based on weighted model counting.
Please intall a weighted model counting tool first.
Please refer to https://github.com/System-Verification-Lab/Quokka-Sharp for more details.
- '''
+ """
setup(
- name='quokka_sharp',
+ name="quokka_sharp",
version=VERSION,
- author='System Verification Lab',
- author_email='j.mei@liacs.leidenuniv.nl',
+ author="System Verification Lab",
+ author_email="j.mei@liacs.leidenuniv.nl",
description=DESCRIPTION,
long_description=LONG_DESCRIPTION,
packages=find_packages(),
- keywords=['python', 'quantum circuits'],
+ package_dir={"": "."},
+ keywords=["python", "quantum circuits"],
classifiers=[
- "Programming Language :: Python :: 3",
- "Programming Language :: Python :: 3.8",
- "Programming Language :: Python :: 3.9",
- "Programming Language :: Python :: 3.10",
- "Operating System :: MacOS :: MacOS X",
- "Operating System :: Microsoft :: Windows",
- "Operating System :: POSIX :: Linux"
+ "Programming Language :: Python :: 3",
+ "Programming Language :: Python :: 3.8",
+ "Programming Language :: Python :: 3.9",
+ "Programming Language :: Python :: 3.10",
+ "Operating System :: MacOS :: MacOS X",
+ "Operating System :: Microsoft :: Windows",
+ "Operating System :: POSIX :: Linux",
],
- install_requires=[
- "numpy>=2.2"
- ],
- include_package_data=True
-)
\ No newline at end of file
+ install_requires=["numpy>=2.2"],
+ include_package_data=True,
+)
diff --git a/scripts/setup-solvers.sh b/scripts/setup-solvers.sh
new file mode 100755
index 00000000..9eb7d111
--- /dev/null
+++ b/scripts/setup-solvers.sh
@@ -0,0 +1,357 @@
+#!/usr/bin/env bash
+# Install GPMC, Ganak, and d4 maxT locally, without modifying the system.
+# Build tools, libraries, and patched sources stay in .solvers.
+# GPMC and maxT use local libraries plus the OS's C library (not fully static).
+set -euo pipefail
+
+root=$(cd "$(dirname "$0")/.." && pwd)
+local_dir="$root/.solvers"
+bin_dir="$local_dir/bin"
+env_dir="$local_dir/env"
+src_dir="$local_dir/src"
+
+case "$(uname -s):$(uname -m)" in
+Linux:x86_64)
+ platform=linux-64
+ ganak_platform=linux-amd64
+ mamba_sha=366cd9cd8be14df1ab8ed50352a82111082a36686b2d389fdb79a92c3fafb3e3
+ ganak_sha=ac84b42eb83a23ce8b09f2468ea59468ec4948321d9696216c6528603a33eb63
+ ;;
+Linux:aarch64 | Linux:arm64)
+ platform=linux-aarch64
+ ganak_platform=linux-arm64
+ mamba_sha=9f93b974adcb4d166996af969b6cd371287d1a3e52733704727884d9b74cb7a7
+ ganak_sha=8a01f915792d22e41631dc6e94396f71d7d372f1b44fc51eacca970666ccfb38
+ ;;
+Darwin:x86_64)
+ platform=osx-64
+ ganak_platform=mac-x86_64
+ mamba_sha=1e71054bb3ac9a076e21f7ec48acfef536f9b3f1408f371a942784bf5ef83d8a
+ ganak_sha=ccced5273894fe28cd3c145d427b2161d790a1373faf351df8824ba498874428
+ ;;
+Darwin:arm64)
+ platform=osx-arm64
+ ganak_platform=mac-arm64
+ mamba_sha=ec2a072f028e1a7cf20f3e2e74d5a8127cf5a5f27636375b5359811565f4e5be
+ ganak_sha=b527c061cc101744dd1b0e641aee2cde2c2a606556cfd3b8c0e31c495bd6bc46
+ ;;
+*)
+ echo "Unsupported platform: $(uname -s) $(uname -m)" >&2
+ exit 1
+ ;;
+esac
+
+gpmc_rev=df1aea7769887b62f59b803293678a1bbc5fe06d
+d4_rev=c121982c16b8195a617567e41ba3b63742d1edb1
+ganak_version=v2.7.0
+
+main() {
+ check_bootstrap_tools
+ prepare_install
+ trap cleanup EXIT
+ install_toolchain
+ build_gpmc
+ build_d4
+ install_ganak
+ verify_install
+ echo "Installed GPMC, Ganak, and maxT in $bin_dir"
+}
+
+check_bootstrap_tools() {
+ local tool
+ for tool in curl tar; do
+ command -v "$tool" >/dev/null || {
+ echo "Required bootstrap tool missing: $tool" >&2
+ return 1
+ }
+ done
+}
+
+prepare_install() {
+ if [[ -e "$local_dir" ]]; then
+ echo "Solver directory already exists: $local_dir (remove it to reinstall)" >&2
+ return 1
+ fi
+
+ export XDG_CACHE_HOME="$local_dir/cache"
+ export TMPDIR="$local_dir/tmp"
+ mkdir -p "$bin_dir" "$src_dir" "$XDG_CACHE_HOME" "$TMPDIR"
+}
+
+install_toolchain() {
+ echo "Downloading local build environment for $platform"
+
+ download_verified \
+ "https://github.com/mamba-org/micromamba-releases/releases/download/2.9.0-0/micromamba-$platform" \
+ "$bin_dir/micromamba" \
+ "$mamba_sha"
+
+ chmod +x "$bin_dir/micromamba"
+
+ export MAMBA_ROOT_PREFIX="$local_dir/mamba"
+ export CONDA_PKGS_DIRS="$local_dir/mamba/pkgs"
+
+ local candidate
+
+ "$bin_dir/micromamba" create -y -p "$env_dir" -c conda-forge \
+ --override-channels 'cmake>=4.1' make cxx-compiler gmp mpfr zlib boost-cpp
+
+ # Old makefiles need plain `ar`; macOS archive merges use libtool below.
+ if [[ "$platform" == osx-* ]]; then
+ [[ -x /usr/bin/ar && -x /usr/bin/libtool ]] || {
+ echo "macOS developer tools are required (/usr/bin/ar and /usr/bin/libtool)" >&2
+ return 1
+ }
+
+ ln -sf /usr/bin/ar "$env_dir/bin/ar"
+ else
+ for candidate in "$env_dir/bin/"*-conda-linux-gnu-ar; do
+ if [[ -x "$candidate" ]]; then
+ ln -sf "$candidate" "$env_dir/bin/ar"
+ break
+ fi
+ done
+
+ [[ -L "$env_dir/bin/ar" ]] || {
+ echo "Local GNU ar was not installed" >&2
+ return 1
+ }
+ fi
+}
+
+download_verified() {
+ curl -fsSL --retry 3 --retry-all-errors "$1" -o "$2"
+ check_sha "$2" "$3"
+}
+
+check_sha() {
+ local actual
+
+ if command -v sha256sum >/dev/null; then
+ actual=$(sha256sum "$1")
+ else
+ actual=$(shasum -a 256 "$1")
+ fi
+
+ [[ "${actual%% *}" == "$2" ]] || {
+ echo "Checksum mismatch: $1" >&2
+ exit 1
+ }
+}
+
+run() {
+ # Make defaults to g++, which bypasses Conda's Clang on macOS.
+ "$bin_dir/micromamba" run -p "$env_dir" env CXX="$env_dir/bin/c++" "$@"
+}
+
+extract() {
+ mkdir -p "$2"
+ curl -fsSL --retry 3 "$1" |
+ tar -xz -C "$2" --strip-components=1
+}
+
+edit() {
+ # The backup suffix makes in-place sed portable between GNU and BSD.
+ local file=$1
+ shift
+ sed -i.bak "$@" "$file"
+ rm "$file.bak"
+}
+
+build_gpmc() {
+ echo "Building GPMC"
+
+ local gpmc_src="$src_dir/gpmc"
+
+ extract \
+ "https://codeload.github.com/System-Verification-Lab/GPMC/tar.gz/$gpmc_rev" \
+ "$gpmc_src"
+
+ run cmake -S "$gpmc_src" -B "$gpmc_src/build" \
+ -DCMAKE_BUILD_TYPE=Release \
+ -DCMAKE_POLICY_VERSION_MINIMUM=3.5
+
+ run cmake --build "$gpmc_src/build" --parallel 2
+ install -m 755 "$gpmc_src/build/gpmc" "$bin_dir/gpmc"
+}
+
+build_d4() {
+ echo "Building d4 Max#SAT solver"
+
+ local d4_src="$src_dir/d4"
+
+ extract \
+ "https://codeload.github.com/jm62300/d4/tar.gz/$d4_rev" \
+ "$d4_src"
+
+ patch_d4 "$d4_src"
+
+ (
+ cd "$d4_src"
+ run bash ./build.sh
+ )
+
+ # Build the non-static maxT executable. Keep its conda libraries
+ # discoverable after installation via an embedded runtime search path.
+ run make -C "$d4_src/demo/maxT" -j2 \
+ "LIBS=-L$env_dir/lib -Wl,-rpath,$env_dir/lib ../../build/libd4.a -lboost_program_options -lz -lgmpxx -lgmp"
+
+ install -m 755 "$d4_src/demo/maxT/build/maxT" "$bin_dir/maxT"
+ ln -s maxT "$bin_dir/maxT_static"
+}
+
+patch_d4() {
+ local d4_src=$1
+ local file
+
+ # Upstream uses unbounded make -j; cap its builds at two jobs.
+ for file in "$d4_src/build.sh" "$d4_src/3rdParty/bipe/build.sh"; do
+ edit "$file" 's/make -j/make -j2/g'
+ done
+
+ # The nested MaxSharpSatResult calls getBit without a MaxT instance.
+ edit "$d4_src/src/methods/MaxT.hpp" \
+ 's/inline u_int8_t getBit(/static inline u_int8_t getBit(/'
+
+ # Fix both Glucose copies: exclude Main.cc and separate PRIu64 literals.
+ for file in \
+ "$d4_src/3rdParty/glucose-3.0" \
+ "$d4_src/3rdParty/bipe/3rdParty/glucose-3.0"; do
+ edit "$file/mtl/template.mk" 's#\*/Main\.#%/Main.#g'
+ edit "$file/core/Main.cc" 's/"PRIu64/" PRIu64/g'
+ done
+
+ if [[ "$platform" == osx-* ]]; then
+ patch_d4_macos "$d4_src"
+ fi
+}
+
+patch_d4_macos() {
+ local d4_src=$1
+ local file
+
+ local outer_system="$d4_src/3rdParty/glucose-3.0/utils/System.cc"
+ local bipe_system="$d4_src/3rdParty/bipe/3rdParty/glucose-3.0/utils/System.cc"
+
+ echo "Applying d4 macOS portability patches"
+
+ # Replace glibc-private headers with portable equivalents.
+ while IFS= read -r file; do
+ edit "$file" \
+ -e 's###g' \
+ -e 's###g'
+ done < <(
+ grep -rl \
+ -e '' \
+ -e '' \
+ "$d4_src/src"
+ )
+
+ # Correct bipe's namespace in its non-Linux memory implementation.
+ edit "$bipe_system" 's/double Glucose_bipe::memUsed/double bipe::Glucose::memUsed/g'
+
+ # Both Glucose copies lack memUsedPeak() in their Apple branches.
+ cat >>"$outer_system" <<'PATCH_EOF'
+
+#if defined(__APPLE__)
+double Glucose::memUsedPeak(void) { return memUsed(); }
+#endif
+PATCH_EOF
+
+ cat >>"$bipe_system" <<'PATCH_EOF'
+
+#if defined(__APPLE__)
+double bipe::Glucose::memUsedPeak(void) { return memUsed(); }
+#endif
+PATCH_EOF
+
+ # Bundled PaToH is Linux/x86-64 only. maxT uses BRANCHING_CLASSIC, so
+ # stub out the unused partitioner and omit its archive on macOS.
+ cat >"$d4_src/src/partitioner/PartitionerPatoh.cpp" <<'PATCH_EOF'
+#include "PartitionerPatoh.hpp"
+#include "src/exceptions/OptionException.hpp"
+
+namespace d4 {
+PartitionerPatoh::PartitionerPatoh(const InfoHyperGraph &, std::ostream &)
+ : m_xpins(nullptr),
+ m_pins(nullptr),
+ m_cwghts(nullptr),
+ m_partvec(nullptr),
+ m_partweights(nullptr) {}
+
+PartitionerPatoh::~PartitionerPatoh() = default;
+
+void PartitionerPatoh::computePartition(HyperGraph &, Level, std::vector &) {
+ throw OptionException("PaToH is unavailable in the macOS local solver build.",
+ __FILE__, __LINE__);
+}
+} // namespace d4
+PATCH_EOF
+
+ # Do not advertise/link the incompatible PaToH archive through CMake.
+ edit "$d4_src/CMakeLists.txt" \
+ 's# \${CMAKE_SOURCE_DIR}/3rdParty/patoh/libpatoh\.a##g'
+
+ # Merge Mach-O archives with Apple's libtool instead of GNU ar; omit PaToH.
+ edit "$d4_src/build.sh" \
+ 's#^ar cqT libd4\.a .*#/usr/bin/libtool -static -o libd4.a libd4tmp.a ../3rdParty/flowCutter/libflowCutter.a ../3rdParty/glucose-3.0/core/lib_glucose.a ../3rdParty/bipe/build/libbipe.a#'
+
+ edit "$d4_src/3rdParty/bipe/build.sh" \
+ 's#^ar cqT libbipe\.a .*#/usr/bin/libtool -static -o libbipe.a libbipetmp.a ../3rdParty/glucose-3.0/core/libglucose.a#'
+
+ # Make flowCutter respect the selected Conda compiler.
+ edit "$d4_src/3rdParty/flowCutter/Makefile" 's/^CC := g++$/CC := $(CXX)/'
+}
+
+install_ganak() {
+ echo "Installing Ganak"
+
+ local archive="$local_dir/ganak.tar.gz"
+
+ download_verified \
+ "https://github.com/meelgroup/ganak/releases/download/release/$ganak_version/ganak-$ganak_version-$ganak_platform.tar.gz" \
+ "$archive" \
+ "$ganak_sha"
+
+ tar -xzf "$archive" -C "$bin_dir" ganak
+ chmod +x "$bin_dir/ganak"
+}
+
+verify_install() {
+ local solver description
+
+ for solver in gpmc maxT ganak; do
+ [[ -x "$bin_dir/$solver" ]] || {
+ echo "Installed solver is missing or not executable: $bin_dir/$solver" >&2
+ return 1
+ }
+ done
+
+ # These also catch missing dynamic libraries at process startup.
+ "$bin_dir/maxT" --help >/dev/null
+ "$bin_dir/ganak" --version >/dev/null
+
+ # Require native Apple Silicon binaries, without relying on Rosetta.
+ if [[ "$platform" == osx-arm64 ]] && command -v file >/dev/null; then
+ for solver in gpmc maxT ganak; do
+ description=$(file "$bin_dir/$solver")
+ [[ "$description" == *arm64* ]] || {
+ echo "Expected an arm64 executable, got: $description" >&2
+ return 1
+ }
+ done
+ fi
+}
+
+cleanup() {
+ local status=$?
+
+ if ((status == 0)); then
+ rm -f "$local_dir/ganak.tar.gz"
+ else
+ echo "Setup failed; removing the incomplete .solvers directory" >&2
+ rm -rf "$local_dir"
+ fi
+}
+
+main
diff --git a/tests/example_quokka_sharp.py b/tests/example_quokka_sharp.py
index b61d527c..d93d55d2 100644
--- a/tests/example_quokka_sharp.py
+++ b/tests/example_quokka_sharp.py
@@ -24,24 +24,30 @@
# ── helpers ───────────────────────────────────────────────────────────────────
+
def to_float(v) -> float:
"""Convert Decimal / float / int to plain Python float."""
return float(v)
+
FIXTURES = os.path.join(os.path.dirname(__file__), "qasm_fixtures")
+
def fixture(subdir: str, filename: str) -> str:
return os.path.join(FIXTURES, subdir, filename)
+
def section(title: str):
print()
print("═" * 60)
print(f" {title}")
print("═" * 60)
+
def result_line(label: str, value):
print(f" {label:<32} {value}")
+
# ══════════════════════════════════════════════════════════════════════════════
# 1. SIMULATION
# Compute the probability of a measurement outcome after running a circuit.
@@ -61,21 +67,22 @@ def result_line(label: str, value):
# (Variables.projector missing var_curr argument).
# Use "allzero" / "firstzero" when possible.
-SIM_FILE = fixture("multi_qubit", "bell.qasm") # ← change me
-SIM_BASIS = "comp" # ← "comp" or "pauli"
-SIM_MEASUREMENT = "allzero" # ← see options above
+SIM_FILE = fixture("multi_qubit", "bell.qasm") # ← change me
+SIM_BASIS = "comp" # ← "comp" or "pauli"
+SIM_MEASUREMENT = "allzero" # ← see options above
prob = qk.functionalities.sim(
- qasmfile = SIM_FILE,
- basis = SIM_BASIS,
- measurement = SIM_MEASUREMENT,
+ qasmfile=SIM_FILE,
+ basis=SIM_BASIS,
+ measurement=SIM_MEASUREMENT,
)
-result_line("Circuit:", os.path.basename(SIM_FILE))
-result_line("Basis:", SIM_BASIS)
+result_line("Circuit:", os.path.basename(SIM_FILE))
+result_line("Basis:", SIM_BASIS)
result_line("Measurement:", SIM_MEASUREMENT)
-result_line("Probability:", prob if prob in ("TIMEOUT", "MEMOUT")
- else f"{to_float(prob):.10f}")
+result_line(
+ "Probability:", prob if prob in ("TIMEOUT", "MEMOUT") else f"{to_float(prob):.10f}"
+)
# ── More examples to try (uncomment one at a time) ────────────────────────────
#
@@ -125,23 +132,23 @@ def result_line(label: str, value):
# Result: True → circuit always maps precons → postcons
# False → it does NOT
-VER_FILE = fixture("verify", "v04_cx_control1.qasm") # ← change me
-VER_BASIS = "comp" # ← "comp" or "pauli"
-VER_PRECONS = {0: 0, 1: 0} # ← input state
-VER_POSTCONS = {0: 1, 1: 1} # ← output state
+VER_FILE = fixture("verify", "v04_cx_control1.qasm") # ← change me
+VER_BASIS = "comp" # ← "comp" or "pauli"
+VER_PRECONS = {0: 0, 1: 0} # ← input state
+VER_POSTCONS = {0: 1, 1: 1} # ← output state
verified = qk.functionalities.verify(
VER_FILE,
- basis = VER_BASIS,
- precons = VER_PRECONS,
- postcons = VER_POSTCONS,
+ basis=VER_BASIS,
+ precons=VER_PRECONS,
+ postcons=VER_POSTCONS,
)
-result_line("Circuit:", os.path.basename(VER_FILE))
-result_line("Basis:", VER_BASIS)
-result_line("Precondition:", VER_PRECONS)
+result_line("Circuit:", os.path.basename(VER_FILE))
+result_line("Basis:", VER_BASIS)
+result_line("Precondition:", VER_PRECONS)
result_line("Postcondition:", VER_POSTCONS)
-result_line("Result:", verified)
+result_line("Result:", verified)
# ── More examples to try (uncomment one at a time) ────────────────────────────
#
@@ -205,23 +212,23 @@ def result_line(label: str, value):
# "pauli" → use EQ_CHECK = "linear"
# EQ_EPSILON: 0 for exact equivalence
-EQ_FILE1 = fixture("equiv_pairs", "eq1_hh.qasm") # ← change me
-EQ_FILE2 = fixture("equiv_pairs", "eq1_identity.qasm") # ← change me
-EQ_BASIS = "comp" # ← "comp" or "pauli"
-EQ_CHECK = "cyclic" # ← "cyclic" or "linear"
-EQ_EPSILON = 0 # ← 0 for exact
+EQ_FILE1 = fixture("equiv_pairs", "eq1_hh.qasm") # ← change me
+EQ_FILE2 = fixture("equiv_pairs", "eq1_identity.qasm") # ← change me
+EQ_BASIS = "comp" # ← "comp" or "pauli"
+EQ_CHECK = "cyclic" # ← "cyclic" or "linear"
+EQ_EPSILON = 0 # ← 0 for exact
equiv = qk.functionalities.eq(
EQ_FILE1,
EQ_FILE2,
- basis = EQ_BASIS,
- check = EQ_CHECK,
- epsilon = EQ_EPSILON,
+ basis=EQ_BASIS,
+ check=EQ_CHECK,
+ epsilon=EQ_EPSILON,
)
-result_line("Circuit A:", os.path.basename(EQ_FILE1))
-result_line("Circuit B:", os.path.basename(EQ_FILE2))
-result_line("Basis:", EQ_BASIS)
+result_line("Circuit A:", os.path.basename(EQ_FILE1))
+result_line("Circuit B:", os.path.basename(EQ_FILE2))
+result_line("Basis:", EQ_BASIS)
result_line("Check mode:", EQ_CHECK)
result_line("Equivalent:", equiv)
@@ -278,29 +285,29 @@ def result_line(label: str, value):
# qasm_str: synthesised circuit as a QASM string
# layers : circuit depth of the solution
-SYN_FILE = fixture("synthesis", "syn01_h_target.qasm") # ← change me
-SYN_GATE_SET = {"h", "cx", "s"} # ← change me
-SYN_FID = 1.0 # ← 1.0 for exact
-SYN_FILES_ROOT = "tmp" # ← writable dir
-SYN_CYC_LIN_ENC = True # ← recommended
+SYN_FILE = fixture("synthesis", "syn01_h_target.qasm") # ← change me
+SYN_GATE_SET = {"h", "cx", "s"} # ← change me
+SYN_FID = 1.0 # ← 1.0 for exact
+SYN_FILES_ROOT = "tmp" # ← writable dir
+SYN_CYC_LIN_ENC = True # ← recommended
os.makedirs(SYN_FILES_ROOT, exist_ok=True)
outcome, weight, qasm_str, layers = qk.functionalities.syn(
SYN_FILE,
- basis = "pauli",
- fid = SYN_FID,
- files_root = SYN_FILES_ROOT,
- cyc_lin_encoding = SYN_CYC_LIN_ENC,
- gate_set = SYN_GATE_SET,
+ basis="pauli",
+ fid=SYN_FID,
+ files_root=SYN_FILES_ROOT,
+ cyc_lin_encoding=SYN_CYC_LIN_ENC,
+ gate_set=SYN_GATE_SET,
)
-result_line("Target circuit:", os.path.basename(SYN_FILE))
-result_line("Gate set:", str(SYN_GATE_SET))
-result_line("Target fidelity:", SYN_FID)
-result_line("Outcome:", outcome)
+result_line("Target circuit:", os.path.basename(SYN_FILE))
+result_line("Gate set:", str(SYN_GATE_SET))
+result_line("Target fidelity:", SYN_FID)
+result_line("Outcome:", outcome)
result_line("Achieved fidelity:", f"{float(weight):.6f}" if weight is not None else "—")
-result_line("Depth (layers):", layers)
+result_line("Depth (layers):", layers)
if outcome == "FOUND" and qasm_str:
print()
@@ -332,4 +339,4 @@ def result_line(label: str, value):
# SYN_FILE = "/path/to/my_target.qasm"
# SYN_GATE_SET = {"h", "cx", "s", "t"}
-print()
\ No newline at end of file
+print()
diff --git a/tests/test_quokka_sharp.py b/tests/test_quokka_sharp.py
index 6e982dbc..8fe0634a 100644
--- a/tests/test_quokka_sharp.py
+++ b/tests/test_quokka_sharp.py
@@ -29,6 +29,7 @@
os.path.join(os.path.dirname(__file__), "qasm_fixtures"),
)
+
def qasm(subdir: str, filename: str) -> str:
"""Return absolute path to a QASM fixture file."""
path = os.path.join(FIXTURE_ROOT, subdir, filename)
@@ -36,6 +37,7 @@ def qasm(subdir: str, filename: str) -> str:
pytest.skip(f"Fixture not found: {path}")
return path
+
FPE = 1e-9 # floating-point epsilon for probability assertions
@@ -61,6 +63,7 @@ def assert_prob(result, expected: float, fpe: float = FPE):
# 1. Simulation Tests
# ---------------------------------------------------------------------------
+
class TestSimulation:
"""
Tests for qk.functionalities.sim().
@@ -73,93 +76,133 @@ class TestSimulation:
def test_identity_allzero(self):
# I|0> = |0> → P(00) = 1.0
- p = qk.functionalities.sim(qasm("single_qubit", "identity_wire.qasm"), basis="comp", measurement="allzero")
+ p = qk.functionalities.sim(
+ qasm("single_qubit", "identity_wire.qasm"),
+ basis="comp",
+ measurement="allzero",
+ )
assert_prob(p, 1.0)
def test_h_allzero(self):
# H|0> = |+> → P(0) = 0.5
- p = qk.functionalities.sim(qasm("single_qubit", "h.qasm"), basis="comp", measurement="allzero")
+ p = qk.functionalities.sim(
+ qasm("single_qubit", "h.qasm"), basis="comp", measurement="allzero"
+ )
assert_prob(p, 0.5)
def test_h_firstzero(self):
# Same circuit, firstzero measurement → 0.5
- p = qk.functionalities.sim(qasm("single_qubit", "h.qasm"), basis="comp", measurement="firstzero")
+ p = qk.functionalities.sim(
+ qasm("single_qubit", "h.qasm"), basis="comp", measurement="firstzero"
+ )
assert_prob(p, 0.5)
def test_x_allzero(self):
# X|0> = |1> → P(0) = 0.0
- p = qk.functionalities.sim(qasm("single_qubit", "x.qasm"), basis="comp", measurement="allzero")
+ p = qk.functionalities.sim(
+ qasm("single_qubit", "x.qasm"), basis="comp", measurement="allzero"
+ )
assert_prob(p, 0.0)
def test_x_dict_one(self):
# X|0> = |1> → P(q0=1) = 1.0
- p = qk.functionalities.sim(qasm("single_qubit", "x.qasm"), basis="comp", measurement={0: 1})
+ p = qk.functionalities.sim(
+ qasm("single_qubit", "x.qasm"), basis="comp", measurement={0: 1}
+ )
assert_prob(p, 1.0)
def test_y_allzero(self):
# Y|0> = i|1> → P(0) = 0.0
- p = qk.functionalities.sim(qasm("single_qubit", "y.qasm"), basis="comp", measurement="allzero")
+ p = qk.functionalities.sim(
+ qasm("single_qubit", "y.qasm"), basis="comp", measurement="allzero"
+ )
assert_prob(p, 0.0)
def test_z_allzero(self):
# Z|0> = |0> → P(0) = 1.0
- p = qk.functionalities.sim(qasm("single_qubit", "z.qasm"), basis="comp", measurement="allzero")
+ p = qk.functionalities.sim(
+ qasm("single_qubit", "z.qasm"), basis="comp", measurement="allzero"
+ )
assert_prob(p, 1.0)
def test_s_allzero(self):
# S|0> = |0> → P(0) = 1.0
- p = qk.functionalities.sim(qasm("single_qubit", "s.qasm"), basis="comp", measurement="allzero")
+ p = qk.functionalities.sim(
+ qasm("single_qubit", "s.qasm"), basis="comp", measurement="allzero"
+ )
assert_prob(p, 1.0)
def test_t_allzero(self):
# T|0> = |0> → P(0) = 1.0
- p = qk.functionalities.sim(qasm("single_qubit", "t.qasm"), basis="comp", measurement="allzero")
+ p = qk.functionalities.sim(
+ qasm("single_qubit", "t.qasm"), basis="comp", measurement="allzero"
+ )
assert_prob(p, 1.0)
def test_hh_allzero(self):
# HH = I → P(0) = 1.0
- p = qk.functionalities.sim(qasm("single_qubit", "hh.qasm"), basis="comp", measurement="allzero")
+ p = qk.functionalities.sim(
+ qasm("single_qubit", "hh.qasm"), basis="comp", measurement="allzero"
+ )
assert_prob(p, 1.0)
def test_hzh_allzero(self):
# HZH = X → P(0) = 0.0
- p = qk.functionalities.sim(qasm("single_qubit", "hzh.qasm"), basis="comp", measurement="allzero")
+ p = qk.functionalities.sim(
+ qasm("single_qubit", "hzh.qasm"), basis="comp", measurement="allzero"
+ )
assert_prob(p, 0.0)
def test_xxxx_allzero(self):
# XXXX = I → P(0) = 1.0
- p = qk.functionalities.sim(qasm("single_qubit", "xxxx.qasm"), basis="comp", measurement="allzero")
+ p = qk.functionalities.sim(
+ qasm("single_qubit", "xxxx.qasm"), basis="comp", measurement="allzero"
+ )
assert_prob(p, 1.0)
# ── Pauli basis, single-qubit ───────────────────────────────────────────
def test_identity_pauli(self):
- p = qk.functionalities.sim(qasm("single_qubit", "identity_wire.qasm"), basis="pauli", measurement="allzero")
+ p = qk.functionalities.sim(
+ qasm("single_qubit", "identity_wire.qasm"),
+ basis="pauli",
+ measurement="allzero",
+ )
assert_prob(p, 1.0)
def test_h_pauli(self):
- p = qk.functionalities.sim(qasm("single_qubit", "h.qasm"), basis="pauli", measurement="allzero")
+ p = qk.functionalities.sim(
+ qasm("single_qubit", "h.qasm"), basis="pauli", measurement="allzero"
+ )
assert_prob(p, 0.5)
def test_x_pauli(self):
- p = qk.functionalities.sim(qasm("single_qubit", "x.qasm"), basis="pauli", measurement="allzero")
+ p = qk.functionalities.sim(
+ qasm("single_qubit", "x.qasm"), basis="pauli", measurement="allzero"
+ )
assert_prob(p, 0.0)
# ── Multi-qubit circuits ────────────────────────────────────────────────
def test_bell_p00(self):
# Bell: P(00) = 0.5
- p = qk.functionalities.sim(qasm("multi_qubit", "bell.qasm"), basis="comp", measurement="allzero")
+ p = qk.functionalities.sim(
+ qasm("multi_qubit", "bell.qasm"), basis="comp", measurement="allzero"
+ )
assert_prob(p, 0.5)
def test_bell_p11(self):
# Bell: P(11) = 0.5
- p = qk.functionalities.sim(qasm("multi_qubit", "bell.qasm"), basis="comp", measurement={0: 1, 1: 1})
+ p = qk.functionalities.sim(
+ qasm("multi_qubit", "bell.qasm"), basis="comp", measurement={0: 1, 1: 1}
+ )
assert_prob(p, 0.5)
def test_bell_p01(self):
# Bell: P(01) = 0.0
- p = qk.functionalities.sim(qasm("multi_qubit", "bell.qasm"), basis="comp", measurement={0: 0, 1: 1})
+ p = qk.functionalities.sim(
+ qasm("multi_qubit", "bell.qasm"), basis="comp", measurement={0: 0, 1: 1}
+ )
assert_prob(p, 0.0)
def test_bell_probabilities_sum_to_one(self):
@@ -167,19 +210,29 @@ def test_bell_probabilities_sum_to_one(self):
total = 0.0
for b0 in (0, 1):
for b1 in (0, 1):
- p = qk.functionalities.sim(qasm("multi_qubit", "bell.qasm"), basis="comp", measurement={0: b0, 1: b1})
+ p = qk.functionalities.sim(
+ qasm("multi_qubit", "bell.qasm"),
+ basis="comp",
+ measurement={0: b0, 1: b1},
+ )
skip_if_solver_issue(p)
total += to_float(p)
assert abs(total - 1.0) < FPE # total is already float
def test_ghz_p000(self):
# GHZ: P(000) = 0.5
- p = qk.functionalities.sim(qasm("multi_qubit", "ghz_3q.qasm"), basis="comp", measurement="allzero")
+ p = qk.functionalities.sim(
+ qasm("multi_qubit", "ghz_3q.qasm"), basis="comp", measurement="allzero"
+ )
assert_prob(p, 0.5)
def test_ghz_p111(self):
# GHZ: P(111) = 0.5
- p = qk.functionalities.sim(qasm("multi_qubit", "ghz_3q.qasm"), basis="comp", measurement={0: 1, 1: 1, 2: 1})
+ p = qk.functionalities.sim(
+ qasm("multi_qubit", "ghz_3q.qasm"),
+ basis="comp",
+ measurement={0: 1, 1: 1, 2: 1},
+ )
assert_prob(p, 0.5)
def test_ghz_p000_plus_p111_equals_one(self):
@@ -192,19 +245,28 @@ def test_ghz_p000_plus_p111_equals_one(self):
def test_cx_allzero(self):
# CX on |00>: stays |00> → P(allzero) = 1.0
- p = qk.functionalities.sim(qasm("multi_qubit", "cx_q0q1.qasm"), basis="comp", measurement="allzero")
+ p = qk.functionalities.sim(
+ qasm("multi_qubit", "cx_q0q1.qasm"), basis="comp", measurement="allzero"
+ )
assert_prob(p, 1.0)
def test_cx_x_cx_result(self):
# X·CX·X on |00>: gives |01> → P({0:0,1:1}) = 1.0
- p = qk.functionalities.sim(qasm("multi_qubit", "cx_x_cx.qasm"), basis="comp", measurement={0: 0, 1: 1})
+ p = qk.functionalities.sim(
+ qasm("multi_qubit", "cx_x_cx.qasm"), basis="comp", measurement={0: 0, 1: 1}
+ )
assert_prob(p, 1.0)
# ── Return-type contract ────────────────────────────────────────────────
def test_return_is_float_or_status(self):
- result = qk.functionalities.sim(qasm("single_qubit", "h.qasm"), basis="comp", measurement="allzero")
- assert isinstance(result, (float, decimal.Decimal)) or result in ("TIMEOUT", "MEMOUT")
+ result = qk.functionalities.sim(
+ qasm("single_qubit", "h.qasm"), basis="comp", measurement="allzero"
+ )
+ assert isinstance(result, (float, decimal.Decimal)) or result in (
+ "TIMEOUT",
+ "MEMOUT",
+ )
# ── Low-level API ───────────────────────────────────────────────────────
@@ -221,89 +283,160 @@ def test_low_level_simulate(self):
# 2. Verification Tests
# ---------------------------------------------------------------------------
+
class TestVerification:
"""Tests for qk.functionalities.verify(). Fixtures in verify/."""
def test_x_flip_pass(self):
# X maps |0>→|1>
- res = qk.functionalities.verify(qasm("verify", "v01_x_flip.qasm"), basis="comp", precons={0: 0}, postcons={0: 1})
+ res = qk.functionalities.verify(
+ qasm("verify", "v01_x_flip.qasm"),
+ basis="comp",
+ precons={0: 0},
+ postcons={0: 1},
+ )
skip_if_solver_issue(res)
assert res in ("True", True)
def test_x_flip_fail(self):
# X|0>=|1>, so post={0:0} must be False
- res = qk.functionalities.verify(qasm("verify", "v01_x_flip.qasm"), basis="comp", precons={0: 0}, postcons={0: 0})
+ res = qk.functionalities.verify(
+ qasm("verify", "v01_x_flip.qasm"),
+ basis="comp",
+ precons={0: 0},
+ postcons={0: 0},
+ )
skip_if_solver_issue(res)
assert res in ("False", False)
def test_identity_pass(self):
- res = qk.functionalities.verify(qasm("verify", "v02_identity.qasm"), basis="comp", precons={0: 0}, postcons={0: 0})
+ res = qk.functionalities.verify(
+ qasm("verify", "v02_identity.qasm"),
+ basis="comp",
+ precons={0: 0},
+ postcons={0: 0},
+ )
skip_if_solver_issue(res)
assert res in ("True", True)
def test_identity_fail(self):
- res = qk.functionalities.verify(qasm("verify", "v02_identity.qasm"), basis="comp", precons={0: 0}, postcons={0: 1})
+ res = qk.functionalities.verify(
+ qasm("verify", "v02_identity.qasm"),
+ basis="comp",
+ precons={0: 0},
+ postcons={0: 1},
+ )
skip_if_solver_issue(res)
assert res in ("False", False)
def test_cx_control0_pass(self):
# CX with q0=0: target unchanged → |00>→|00>
- res = qk.functionalities.verify(qasm("verify", "v03_cx_control0.qasm"), basis="comp", precons={0: 0, 1: 0}, postcons={0: 0, 1: 0})
+ res = qk.functionalities.verify(
+ qasm("verify", "v03_cx_control0.qasm"),
+ basis="comp",
+ precons={0: 0, 1: 0},
+ postcons={0: 0, 1: 0},
+ )
skip_if_solver_issue(res)
assert res in ("True", True)
def test_cx_control0_target_not_flipped(self):
# Control=0 so target must NOT flip
- res = qk.functionalities.verify(qasm("verify", "v03_cx_control0.qasm"), basis="comp", precons={0: 0, 1: 0}, postcons={0: 0, 1: 1})
+ res = qk.functionalities.verify(
+ qasm("verify", "v03_cx_control0.qasm"),
+ basis="comp",
+ precons={0: 0, 1: 0},
+ postcons={0: 0, 1: 1},
+ )
skip_if_solver_issue(res)
assert res in ("False", False)
def test_cx_control1_pass(self):
# Pre-set control=1: target flips → |00>→|11>
- res = qk.functionalities.verify(qasm("verify", "v04_cx_control1.qasm"), basis="comp", precons={0: 0, 1: 0}, postcons={0: 1, 1: 1})
+ res = qk.functionalities.verify(
+ qasm("verify", "v04_cx_control1.qasm"),
+ basis="comp",
+ precons={0: 0, 1: 0},
+ postcons={0: 1, 1: 1},
+ )
skip_if_solver_issue(res)
assert res in ("True", True)
def test_toffoli_no_flip(self):
# Both controls=0: target unchanged
- res = qk.functionalities.verify(qasm("verify", "v05_toffoli_no_flip.qasm"), basis="comp", precons={0: 0, 1: 0, 2: 0}, postcons={0: 0, 1: 0, 2: 0})
+ res = qk.functionalities.verify(
+ qasm("verify", "v05_toffoli_no_flip.qasm"),
+ basis="comp",
+ precons={0: 0, 1: 0, 2: 0},
+ postcons={0: 0, 1: 0, 2: 0},
+ )
skip_if_solver_issue(res)
assert res in ("True", True)
def test_toffoli_flip(self):
# Both controls=1: target flips
- res = qk.functionalities.verify(qasm("verify", "v06_toffoli_flip.qasm"), basis="comp", precons={0: 0, 1: 0, 2: 0}, postcons={0: 1, 1: 1, 2: 1})
+ res = qk.functionalities.verify(
+ qasm("verify", "v06_toffoli_flip.qasm"),
+ basis="comp",
+ precons={0: 0, 1: 0, 2: 0},
+ postcons={0: 1, 1: 1, 2: 1},
+ )
skip_if_solver_issue(res)
assert res in ("True", True)
def test_xx_identity_pass(self):
# XX = I
- res = qk.functionalities.verify(qasm("verify", "v07_xx_identity.qasm"), basis="comp", precons={0: 0}, postcons={0: 0})
+ res = qk.functionalities.verify(
+ qasm("verify", "v07_xx_identity.qasm"),
+ basis="comp",
+ precons={0: 0},
+ postcons={0: 0},
+ )
skip_if_solver_issue(res)
assert res in ("True", True)
def test_cx_swap_target_pass(self):
- res = qk.functionalities.verify(qasm("verify", "v08_cx_swap_target.qasm"), basis="comp", precons={0: 0, 1: 0}, postcons={0: 1, 1: 1})
+ res = qk.functionalities.verify(
+ qasm("verify", "v08_cx_swap_target.qasm"),
+ basis="comp",
+ precons={0: 0, 1: 0},
+ postcons={0: 1, 1: 1},
+ )
skip_if_solver_issue(res)
assert res in ("True", True)
def test_multi_gate_chain_pass(self):
# X H H X = I
- res = qk.functionalities.verify(qasm("verify", "v10_multi_gate_chain.qasm"), basis="comp", precons={0: 0}, postcons={0: 0})
+ res = qk.functionalities.verify(
+ qasm("verify", "v10_multi_gate_chain.qasm"),
+ basis="comp",
+ precons={0: 0},
+ postcons={0: 0},
+ )
skip_if_solver_issue(res)
assert res in ("True", True)
# ── Pauli basis ─────────────────────────────────────────────────────────
def test_x_flip_pauli(self):
- res = qk.functionalities.verify(qasm("verify", "v01_x_flip.qasm"), basis="pauli", precons={0: 0}, postcons={0: 1})
+ res = qk.functionalities.verify(
+ qasm("verify", "v01_x_flip.qasm"),
+ basis="pauli",
+ precons={0: 0},
+ postcons={0: 1},
+ )
skip_if_solver_issue(res)
assert res in ("True", True)
# ── Return-type contract ────────────────────────────────────────────────
def test_return_type(self):
- res = qk.functionalities.verify(qasm("verify", "v02_identity.qasm"), basis="comp", precons={0: 0}, postcons={0: 0})
+ res = qk.functionalities.verify(
+ qasm("verify", "v02_identity.qasm"),
+ basis="comp",
+ precons={0: 0},
+ postcons={0: 0},
+ )
assert res in ("True", "False", "TIMEOUT", "MEMOUT", True, False)
# ── Low-level API ───────────────────────────────────────────────────────
@@ -315,152 +448,182 @@ def test_low_level_verify(self):
skip_if_solver_issue(res)
assert res in ("True", True)
- # ── Pauli basis, Pauli-operator stabilizer pre/post ──────────────────────
- # ── H gate: Z → X ────────────────────────────────────────────────────────
-
+ # ── Pauli basis, Pauli-operator stabilizer pre/post ──────────────────────
+ # ── H gate: Z → X ────────────────────────────────────────────────────────
+
def test_h_z_to_x_pass(self):
# H|0⟩ = |+⟩ → pre={0:"Z"} post={0:"X"} should be True
res = qk.functionalities.verify(
- qasm("single_qubit", "h.qasm"), basis="pauli",
- precons={0: "Z"}, postcons={0: "X"},
+ qasm("single_qubit", "h.qasm"),
+ basis="pauli",
+ precons={0: "Z"},
+ postcons={0: "X"},
)
skip_if_solver_issue(res)
assert res in ("True", True)
-
+
def test_h_z_to_z_fail(self):
# H does NOT preserve Z-stabilizer → False
res = qk.functionalities.verify(
- qasm("single_qubit", "h.qasm"), basis="pauli",
- precons={0: "Z"}, postcons={0: "Z"},
+ qasm("single_qubit", "h.qasm"),
+ basis="pauli",
+ precons={0: "Z"},
+ postcons={0: "Z"},
)
skip_if_solver_issue(res)
assert res in ("False", False)
-
+
def test_h_x_to_z_pass(self):
# H|+⟩ = |0⟩ → pre={0:"X"} post={0:"Z"} should be True
res = qk.functionalities.verify(
- qasm("single_qubit", "h.qasm"), basis="pauli",
- precons={0: "X"}, postcons={0: "Z"},
+ qasm("single_qubit", "h.qasm"),
+ basis="pauli",
+ precons={0: "X"},
+ postcons={0: "Z"},
)
skip_if_solver_issue(res)
assert res in ("True", True)
-
+
def test_h_x_to_x_fail(self):
# H does NOT preserve X-stabilizer → False
res = qk.functionalities.verify(
- qasm("single_qubit", "h.qasm"), basis="pauli",
- precons={0: "X"}, postcons={0: "X"},
+ qasm("single_qubit", "h.qasm"),
+ basis="pauli",
+ precons={0: "X"},
+ postcons={0: "X"},
)
skip_if_solver_issue(res)
assert res in ("False", False)
-
+
# ── X gate: Z → -Z, X → X ────────────────────────────────────────────────
-
+
def test_x_z_to_z_fail(self):
# X|0⟩ = |1⟩, not |0⟩ → pre={0:"Z"} post={0:"Z"} should be False
res = qk.functionalities.verify(
- qasm("single_qubit", "x.qasm"), basis="pauli",
- precons={0: "Z"}, postcons={0: "Z"},
+ qasm("single_qubit", "x.qasm"),
+ basis="pauli",
+ precons={0: "Z"},
+ postcons={0: "Z"},
)
skip_if_solver_issue(res)
assert res in ("False", False)
-
+
def test_x_x_to_x_pass(self):
# X commutes with X-stabilizer: X|+⟩ = |+⟩ → True
res = qk.functionalities.verify(
- qasm("single_qubit", "x.qasm"), basis="pauli",
- precons={0: "X"}, postcons={0: "X"},
+ qasm("single_qubit", "x.qasm"),
+ basis="pauli",
+ precons={0: "X"},
+ postcons={0: "X"},
)
skip_if_solver_issue(res)
assert res in ("True", True)
-
+
# ── Z gate: Z → Z, X → -X ────────────────────────────────────────────────
-
+
def test_z_z_to_z_pass(self):
# Z|0⟩ = |0⟩ → pre={0:"Z"} post={0:"Z"} should be True
res = qk.functionalities.verify(
- qasm("single_qubit", "z.qasm"), basis="pauli",
- precons={0: "Z"}, postcons={0: "Z"},
+ qasm("single_qubit", "z.qasm"),
+ basis="pauli",
+ precons={0: "Z"},
+ postcons={0: "Z"},
)
skip_if_solver_issue(res)
assert res in ("True", True)
-
+
def test_z_x_to_x_fail(self):
# Z maps |+⟩ to |−⟩, not back to |+⟩ → False
res = qk.functionalities.verify(
- qasm("single_qubit", "z.qasm"), basis="pauli",
- precons={0: "X"}, postcons={0: "X"},
+ qasm("single_qubit", "z.qasm"),
+ basis="pauli",
+ precons={0: "X"},
+ postcons={0: "X"},
)
skip_if_solver_issue(res)
assert res in ("False", False)
-
+
# ── S gate: X → Y ────────────────────────────────────────────────────────
-
+
def test_s_x_to_y_pass(self):
# S|+⟩ = |i+⟩ → pre={0:"X"} post={0:"Y"} should be True
res = qk.functionalities.verify(
- qasm("single_qubit", "s.qasm"), basis="pauli",
- precons={0: "X"}, postcons={0: "Y"},
+ qasm("single_qubit", "s.qasm"),
+ basis="pauli",
+ precons={0: "X"},
+ postcons={0: "Y"},
)
skip_if_solver_issue(res)
assert res in ("True", True)
-
+
def test_s_x_to_x_fail(self):
# S does NOT preserve X-stabilizer → False
res = qk.functionalities.verify(
- qasm("single_qubit", "s.qasm"), basis="pauli",
- precons={0: "X"}, postcons={0: "X"},
+ qasm("single_qubit", "s.qasm"),
+ basis="pauli",
+ precons={0: "X"},
+ postcons={0: "X"},
)
skip_if_solver_issue(res)
assert res in ("False", False)
-
+
def test_s_z_to_z_pass(self):
# S|0⟩ = |0⟩ (phase only) → pre={0:"Z"} post={0:"Z"} should be True
res = qk.functionalities.verify(
- qasm("single_qubit", "s.qasm"), basis="pauli",
- precons={0: "Z"}, postcons={0: "Z"},
+ qasm("single_qubit", "s.qasm"),
+ basis="pauli",
+ precons={0: "Z"},
+ postcons={0: "Z"},
)
skip_if_solver_issue(res)
assert res in ("True", True)
-
+
# ── Identity: all stabilizers preserved ──────────────────────────────────
-
+
def test_identity_z_to_z_pass(self):
res = qk.functionalities.verify(
- qasm("single_qubit", "identity_wire.qasm"), basis="pauli",
- precons={0: "Z"}, postcons={0: "Z"},
+ qasm("single_qubit", "identity_wire.qasm"),
+ basis="pauli",
+ precons={0: "Z"},
+ postcons={0: "Z"},
)
skip_if_solver_issue(res)
assert res in ("True", True)
-
+
def test_identity_x_to_x_pass(self):
res = qk.functionalities.verify(
- qasm("single_qubit", "identity_wire.qasm"), basis="pauli",
- precons={0: "X"}, postcons={0: "X"},
+ qasm("single_qubit", "identity_wire.qasm"),
+ basis="pauli",
+ precons={0: "X"},
+ postcons={0: "X"},
)
skip_if_solver_issue(res)
assert res in ("True", True)
-
+
def test_identity_z_to_x_fail(self):
# Identity cannot change the stabilizer → False
res = qk.functionalities.verify(
- qasm("single_qubit", "identity_wire.qasm"), basis="pauli",
- precons={0: "Z"}, postcons={0: "X"},
+ qasm("single_qubit", "identity_wire.qasm"),
+ basis="pauli",
+ precons={0: "Z"},
+ postcons={0: "X"},
)
skip_if_solver_issue(res)
assert res in ("False", False)
-
+
# ── HH = Identity: Z → Z ─────────────────────────────────────────────────
-
+
def test_hh_z_to_z_pass(self):
# HH = I, so Z-stabilizer is preserved
res = qk.functionalities.verify(
- qasm("single_qubit", "hh.qasm"), basis="pauli",
- precons={0: "Z"}, postcons={0: "Z"},
+ qasm("single_qubit", "hh.qasm"),
+ basis="pauli",
+ precons={0: "Z"},
+ postcons={0: "Z"},
)
skip_if_solver_issue(res)
assert res in ("True", True)
-
+
# ── 2-qubit: CNOT stabilizer propagation ─────────────────────────────────
def test_cx_x_stab_fanout_pass(self):
@@ -469,8 +632,10 @@ def test_cx_x_stab_fanout_pass(self):
# CNOT output: (|00⟩+|11⟩)/√2 — now jointly stabilized by X⊗X, not X⊗I.
# Asking whether q[0] alone is X-stabilized post-entanglement → False.
res = qk.functionalities.verify(
- qasm("multi_qubit", "cx_q0q1.qasm"), basis="pauli",
- precons={0: "Z", 1: "X"}, postcons={0: "X", 1: "I"},
+ qasm("multi_qubit", "cx_q0q1.qasm"),
+ basis="pauli",
+ precons={0: "Z", 1: "X"},
+ postcons={0: "X", 1: "I"},
)
skip_if_solver_issue(res)
# q[0] transitioned from Z-stab to joint X⊗X; no longer individually X-stab → False
@@ -482,20 +647,24 @@ def test_cx_x_stab_propagates_pass(self):
# CNOT with control=|0⟩ acts as identity on target → target X-stab preserved.
# post: q[0]=Z (control unchanged), q[1]=X (target unchanged) → True
res = qk.functionalities.verify(
- qasm("multi_qubit", "cx_q0q1.qasm"), basis="pauli",
- precons={0: "Z", 1: "X"}, postcons={0: "Z", 1: "X"},
+ qasm("multi_qubit", "cx_q0q1.qasm"),
+ basis="pauli",
+ precons={0: "Z", 1: "X"},
+ postcons={0: "Z", 1: "X"},
)
skip_if_solver_issue(res)
assert res in ("True", True)
-
+
# ── Unconstrained qubit (I stabilizer) ───────────────────────────────────
-
+
def test_h_unconstrained_precon_pass(self):
# pre={0:"I"} means no constraint on input: verify always True for any post
# that the gate CAN produce (here we check it runs without error)
res = qk.functionalities.verify(
- qasm("single_qubit", "h.qasm"), basis="pauli",
- precons={0: "I"}, postcons={0: "I"},
+ qasm("single_qubit", "h.qasm"),
+ basis="pauli",
+ precons={0: "I"},
+ postcons={0: "I"},
)
assert res in ("True", "False", "TIMEOUT", "MEMOUT", True, False)
@@ -504,28 +673,34 @@ def test_h_unconstrained_precon_pass(self):
# 3. Equivalence Checking Tests
# ---------------------------------------------------------------------------
+
class TestEquivalenceChecking:
"""Tests for qk.functionalities.eq(). Fixtures in equiv_pairs/."""
# ── Equivalent pairs ────────────────────────────────────────────────────
- @pytest.mark.parametrize("lhs,rhs,basis,check", [
- ("eq1_hh.qasm", "eq1_identity.qasm", "pauli", "linear"),
- ("eq1_hh.qasm", "eq1_identity.qasm", "comp", "cyclic"),
- ("eq2_ss.qasm", "eq2_z.qasm", "pauli", "linear"),
- ("eq2_ss.qasm", "eq2_z.qasm", "comp", "cyclic"),
- ("eq3_hxh.qasm", "eq3_z.qasm", "pauli", "linear"),
- ("eq3_hxh.qasm", "eq3_z.qasm", "comp", "cyclic"),
- ("eq4_cxcx.qasm", "eq4_id2q.qasm", "comp", "cyclic"),
- ("eq5_3cx_swap.qasm", "eq5_swap.qasm", "comp", "cyclic"),
- ("eq6_tt.qasm", "eq6_s.qasm", "pauli", "linear"),
- ("eq6_tt.qasm", "eq6_s.qasm", "comp", "cyclic"),
- ])
+ @pytest.mark.parametrize(
+ "lhs,rhs,basis,check",
+ [
+ ("eq1_hh.qasm", "eq1_identity.qasm", "pauli", "linear"),
+ ("eq1_hh.qasm", "eq1_identity.qasm", "comp", "cyclic"),
+ ("eq2_ss.qasm", "eq2_z.qasm", "pauli", "linear"),
+ ("eq2_ss.qasm", "eq2_z.qasm", "comp", "cyclic"),
+ ("eq3_hxh.qasm", "eq3_z.qasm", "pauli", "linear"),
+ ("eq3_hxh.qasm", "eq3_z.qasm", "comp", "cyclic"),
+ ("eq4_cxcx.qasm", "eq4_id2q.qasm", "comp", "cyclic"),
+ ("eq5_3cx_swap.qasm", "eq5_swap.qasm", "comp", "cyclic"),
+ ("eq6_tt.qasm", "eq6_s.qasm", "pauli", "linear"),
+ ("eq6_tt.qasm", "eq6_s.qasm", "comp", "cyclic"),
+ ],
+ )
def test_equiv_pair(self, lhs, rhs, basis, check):
res = qk.functionalities.eq(
qasm("equiv_pairs", lhs),
qasm("equiv_pairs", rhs),
- basis=basis, check=check, epsilon=0,
+ basis=basis,
+ check=check,
+ epsilon=0,
)
skip_if_solver_issue(res)
assert res in (True, "True"), f"{lhs} should be equiv to {rhs}"
@@ -539,23 +714,28 @@ def test_circuit_equiv_itself(self):
# ── Non-equivalent pairs ─────────────────────────────────────────────────
- @pytest.mark.parametrize("lhs,rhs,basis,check", [
- ("neq1_h.qasm", "neq1_x.qasm", "comp", "cyclic"),
- ("neq1_h.qasm", "neq1_x.qasm", "pauli", "linear"),
- ("neq2_h.qasm", "neq2_hs.qasm", "pauli", "linear"),
- ("neq3_bell.qasm", "neq3_cx.qasm", "comp", "cyclic"),
- ("neq4_z.qasm", "neq4_s.qasm", "pauli", "linear"),
- ("neq4_z.qasm", "neq4_s.qasm", "comp", "cyclic"),
- ])
+ @pytest.mark.parametrize(
+ "lhs,rhs,basis,check",
+ [
+ ("neq1_h.qasm", "neq1_x.qasm", "comp", "cyclic"),
+ ("neq1_h.qasm", "neq1_x.qasm", "pauli", "linear"),
+ ("neq2_h.qasm", "neq2_hs.qasm", "pauli", "linear"),
+ ("neq3_bell.qasm", "neq3_cx.qasm", "comp", "cyclic"),
+ ("neq4_z.qasm", "neq4_s.qasm", "pauli", "linear"),
+ ("neq4_z.qasm", "neq4_s.qasm", "comp", "cyclic"),
+ ],
+ )
def test_non_equiv_pair(self, lhs, rhs, basis, check):
res = qk.functionalities.eq(
qasm("equiv_pairs", lhs),
qasm("equiv_pairs", rhs),
- basis=basis, check=check, epsilon=0,
+ basis=basis,
+ check=check,
+ epsilon=0,
)
skip_if_solver_issue(res)
assert res in (False, "False"), f"{lhs} should NOT be equiv to {rhs}"
-
+
# ── Low-level API ────────────────────────────────────────────────────────
def test_low_level_equiv_true(self):
@@ -591,95 +771,136 @@ def test_return_type(self):
# 4. Synthesis Tests
# ---------------------------------------------------------------------------
-# class TestSynthesis:
-# """Tests for qk.functionalities.syn(). Fixtures in cl/."""
-
-# @pytest.mark.parametrize("target,gate_set", [
-# ("syn01_h_target.qasm", {"h", "cx", "s"}),
-# ("syn02_x_target.qasm", {"h", "cx", "s"}),
-# ("syn03_z_target.qasm", {"h", "cx", "s"}),
-# ("syn05_t_target.qasm", {"h", "cx", "t"}),
-# ("syn06_identity_target.qasm", {"h", "cx", "s"}),
-# ])
-# def test_syn_structure(self, target, gate_set):
-# """syn() always returns a valid 4-tuple with a known outcome string."""
-# result = qk.functionalities.syn(qasm("synthesis", target), basis="pauli", files_root="tmp", fid=1.0, cyc_lin_encoding=True, gate_set=gate_set)
-# assert isinstance(result, tuple) and len(result) == 4
-# outcome, weight, qasm_str, layers = result
-# valid = {"FOUND", "TIMEOUT", "CRASH"}
-# assert outcome in valid or outcome.startswith("ERROR"), f"Bad outcome: {outcome}"
-
-# def test_found_weight_is_one(self):
-# """When synthesis finds an exact solution, fidelity = 1.0."""
-# outcome, weight, _, _ = qk.functionalities.syn(
-# qasm("synthesis", "syn01_h_target.qasm"),basis="pauli", fid=1.0,files_root="tmp", gate_set={"h", "cx", "s"}
-# )
-# if outcome == "FOUND":
-# assert abs(float(weight) - 1.0) < FPE
-
-# def test_found_qasm_nonempty(self):
-# """When synthesis finds a solution, it returns a non-empty QASM string."""
-# outcome, _, qasm_str, _ = qk.functionalities.syn(
-# qasm("synthesis", "syn01_h_target.qasm"), basis="pauli", fid=1.0,files_root="tmp", gate_set={"h", "cx", "s"}
-# )
-# if outcome == "FOUND":
-# assert isinstance(qasm_str, str) and len(qasm_str) > 0
-
-# def test_found_layers_positive(self):
-# """When synthesis finds a solution, layers >= 1."""
-# outcome, _, _, layers = qk.functionalities.syn(
-# qasm("synthesis", "syn01_h_target.qasm"), basis="pauli", fid=1.0, files_root="tmp", gate_set={"h", "cx", "s"}
-# )
-# if outcome == "FOUND":
-# assert isinstance(layers, int) and layers >= 1
-
-# def test_synthesised_circuit_is_equivalent(self):
-# """If synthesis finds a circuit, it must be equivalent to the target."""
-# import tempfile, os
-# target = qasm("synthesis", "syn01_h_target.qasm")
-# outcome, _, qasm_str, _ = qk.functionalities.syn(target, basis="pauli", fid=1.0, files_root="tmp", gate_set={"h", "cx", "s"})
-# if outcome != "FOUND":
-# pytest.skip(f"Synthesis did not find a solution: {outcome}")
-# # Write synthesised QASM to a temp file and check equivalence
-# with tempfile.NamedTemporaryFile(mode="w", suffix=".qasm", delete=False) as f:
-# f.write(qasm_str)
-# syn_path = f.name
-# try:
-# res = qk.functionalities.eq(target, syn_path, basis="comp", check="cyclic", epsilon=0)
-# skip_if_solver_issue(res)
-# assert res in (True, "True"), "Synthesised circuit is not equivalent to target"
-# finally:
-# os.unlink(syn_path)
-
-# def test_2q_synthesis_structure(self):
-# """2-qubit synthesis (CX target) returns a valid tuple."""
-# result = qk.functionalities.syn(qasm("synthesis", "syn04_cx_target.qasm"), basis="pauli", fid=1.0, files_root="tmp", gate_set={"h", "cx", "s"})
-# assert isinstance(result, tuple) and len(result) == 4
-
-# # ── Low-level API ────────────────────────────────────────────────────────
-
-# def test_low_level_synthesis(self):
-# circuit = qk.encoding.QASMparser(qasm("synthesis", "syn01_h_target.qasm"))
-# circuit.dagger()
-# cnf = qk.encoding.QASM2CNF(circuit, computational_basis=False)
-# result = qk.Synthesis(cnf, gate_set={"h", "cx", "s"})
-# assert isinstance(result, tuple) and len(result) == 4
+
+class TestSynthesis:
+ """Tests for qk.functionalities.syn(). Fixtures in cl/."""
+
+ @pytest.mark.parametrize(
+ "target,gate_set",
+ [
+ ("syn01_h_target.qasm", {"h", "cx", "s"}),
+ ("syn02_x_target.qasm", {"h", "cx", "s"}),
+ ("syn03_z_target.qasm", {"h", "cx", "s"}),
+ ("syn05_t_target.qasm", {"h", "cx", "t"}),
+ ("syn06_identity_target.qasm", {"h", "cx", "s"}),
+ ],
+ )
+ def test_syn_structure(self, target, gate_set):
+ """syn() always returns a valid 4-tuple with a known outcome string."""
+ result = qk.functionalities.syn(
+ qasm("synthesis", target),
+ basis="pauli",
+ files_root="tmp",
+ fid=1.0,
+ cyc_lin_encoding=True,
+ gate_set=gate_set,
+ )
+ assert isinstance(result, tuple) and len(result) == 4
+ outcome, weight, qasm_str, layers = result
+ valid = {"FOUND", "TIMEOUT", "CRASH"}
+ assert outcome in valid or outcome.startswith("ERROR"), (
+ f"Bad outcome: {outcome}"
+ )
+
+ def test_found_weight_is_one(self):
+ """When synthesis finds an exact solution, fidelity = 1.0."""
+ outcome, weight, _, _ = qk.functionalities.syn(
+ qasm("synthesis", "syn01_h_target.qasm"),
+ basis="pauli",
+ fid=1.0,
+ files_root="tmp",
+ gate_set={"h", "cx", "s"},
+ )
+ if outcome == "FOUND":
+ assert abs(float(weight) - 1.0) < FPE
+
+ def test_found_qasm_nonempty(self):
+ """When synthesis finds a solution, it returns a non-empty QASM string."""
+ outcome, _, qasm_str, _ = qk.functionalities.syn(
+ qasm("synthesis", "syn01_h_target.qasm"),
+ basis="pauli",
+ fid=1.0,
+ files_root="tmp",
+ gate_set={"h", "cx", "s"},
+ )
+ if outcome == "FOUND":
+ assert isinstance(qasm_str, str) and len(qasm_str) > 0
+
+ def test_found_layers_positive(self):
+ """When synthesis finds a solution, layers >= 1."""
+ outcome, _, _, layers = qk.functionalities.syn(
+ qasm("synthesis", "syn01_h_target.qasm"),
+ basis="pauli",
+ fid=1.0,
+ files_root="tmp",
+ gate_set={"h", "cx", "s"},
+ )
+ if outcome == "FOUND":
+ assert isinstance(layers, int) and layers >= 1
+
+ def test_synthesised_circuit_is_equivalent(self):
+ """If synthesis finds a circuit, it must be equivalent to the target."""
+ import tempfile, os
+
+ target = qasm("synthesis", "syn01_h_target.qasm")
+ outcome, _, qasm_str, _ = qk.functionalities.syn(
+ target, basis="pauli", fid=1.0, files_root="tmp", gate_set={"h", "cx", "s"}
+ )
+ assert outcome == "FOUND", f"Synthesis did not find a solution: {outcome}"
+ # Write synthesised QASM to a temp file and check equivalence
+ with tempfile.NamedTemporaryFile(mode="w", suffix=".qasm", delete=False) as f:
+ f.write(qasm_str)
+ syn_path = f.name
+ try:
+ res = qk.functionalities.eq(
+ target, syn_path, basis="comp", check="cyclic", epsilon=0
+ )
+ skip_if_solver_issue(res)
+ assert res in (True, "True"), (
+ "Synthesised circuit is not equivalent to target"
+ )
+ finally:
+ os.unlink(syn_path)
+
+ def test_2q_synthesis_structure(self):
+ """2-qubit synthesis (CX target) returns a valid tuple."""
+ result = qk.functionalities.syn(
+ qasm("synthesis", "syn04_cx_target.qasm"),
+ basis="pauli",
+ fid=1.0,
+ files_root="tmp",
+ gate_set={"h", "cx", "s"},
+ )
+ assert isinstance(result, tuple) and len(result) == 4
+
+ # ── Low-level API ────────────────────────────────────────────────────────
+
+ def test_low_level_synthesis(self):
+ circuit = qk.encoding.QASMparser(qasm("synthesis", "syn01_h_target.qasm"))
+ circuit.dagger()
+ cnf = qk.encoding.QASM2CNF(circuit, computational_basis=False)
+ result = qk.Synthesis(cnf, gate_set={"h", "cx", "s"})
+ assert isinstance(result, tuple) and len(result) == 4
# ---------------------------------------------------------------------------
# 5. Encoding / Parser Unit Tests
# ---------------------------------------------------------------------------
+
class TestEncodingAndParser:
"""Unit tests for the QASM parser and CNF encoder."""
- @pytest.mark.parametrize("subdir,filename", [
- ("single_qubit", "h.qasm"),
- ("single_qubit", "x.qasm"),
- ("multi_qubit", "bell.qasm"),
- ("multi_qubit", "ghz_3q.qasm"),
- ("multi_qubit", "toffoli.qasm"),
- ])
+ @pytest.mark.parametrize(
+ "subdir,filename",
+ [
+ ("single_qubit", "h.qasm"),
+ ("single_qubit", "x.qasm"),
+ ("multi_qubit", "bell.qasm"),
+ ("multi_qubit", "ghz_3q.qasm"),
+ ("multi_qubit", "toffoli.qasm"),
+ ],
+ )
def test_parser_loads(self, subdir, filename):
circuit = qk.encoding.QASMparser(qasm(subdir, filename))
assert circuit is not None
@@ -706,22 +927,24 @@ def test_dagger(self):
def test_append(self):
c1 = qk.encoding.QASMparser(qasm("single_qubit", "h.qasm"))
c2 = qk.encoding.QASMparser(qasm("single_qubit", "h.qasm"))
- c1.append(c2) # must not raise
+ c1.append(c2) # must not raise
# ---------------------------------------------------------------------------
# 6. Edge-case and Robustness Tests
# ---------------------------------------------------------------------------
-class TestEdgeCases:
+class TestEdgeCases:
def test_invalid_path_raises(self):
with pytest.raises(Exception):
qk.encoding.QASMparser("/nonexistent/path.qasm")
def test_invalid_basis_raises(self):
with pytest.raises(Exception):
- qk.functionalities.sim(qasm("single_qubit", "h.qasm"), basis="bad", measurement="allzero")
+ qk.functionalities.sim(
+ qasm("single_qubit", "h.qasm"), basis="bad", measurement="allzero"
+ )
def test_empty_precons(self):
# Empty precons = no constraint on input state
@@ -747,7 +970,11 @@ def test_eq_comp_basis_requires_cyclic(self):
def test_bell_psi_minus_no_allzero(self):
# |Psi-> has no |00> component
- p = qk.functionalities.sim(qasm("multi_qubit", "bell_psi_minus.qasm"), basis="pauli", measurement="allzero")
+ p = qk.functionalities.sim(
+ qasm("multi_qubit", "bell_psi_minus.qasm"),
+ basis="pauli",
+ measurement="allzero",
+ )
assert_prob(p, 0.0)
def test_bernstein_vazirani_result(self):
diff --git a/tmp/.gitkeep b/tmp/.gitkeep
new file mode 100644
index 00000000..e69de29b
diff --git a/uv.lock b/uv.lock
new file mode 100644
index 00000000..cbe54c7f
--- /dev/null
+++ b/uv.lock
@@ -0,0 +1,185 @@
+version = 1
+revision = 3
+requires-python = ">=3.13"
+
+[[package]]
+name = "colorama"
+version = "0.4.6"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
+]
+
+[[package]]
+name = "iniconfig"
+version = "2.3.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" },
+]
+
+[[package]]
+name = "numpy"
+version = "2.5.3"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/13/01/11703282db468b85f6f7b8c7f22d058de5970d5c7e60a3a8aaa313c3de36/numpy-2.5.3.tar.gz", hash = "sha256:df2d5874ff183595a4ba404edd04f6bd9b5505c1d7708573f6a6c17489a67563", size = 20791231, upload-time = "2026-09-06T16:27:47.073Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/79/e5/8fb89cd46d14e35699d13bf943a5f5f441ecee8667120a1f6105ab89e349/numpy-2.5.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:66a78fe4556c60aceda5916f9eacd638b18e9e681016ec302dcb4682d6d4d034", size = 16991061, upload-time = "2026-09-06T16:25:00.411Z" },
+ { url = "https://files.pythonhosted.org/packages/2f/06/9dc9e48b5e5e941c8b10350c5ff2d721da42a20517d911d15544246775ff/numpy-2.5.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92f30e89b8ee0ecf363033576c422b2f58fed6a80bed0aa48dff6d14c654663e", size = 12003676, upload-time = "2026-09-06T16:25:03.475Z" },
+ { url = "https://files.pythonhosted.org/packages/ab/2a/98282aa5b8f58b1157d440bb6282eed47e3632a5de53a714fbab17e659fe/numpy-2.5.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f9a2353b37a1a9e78fd82b27ad7e2a32a2d036604d18f02b05e3136c62ca3b09", size = 5439695, upload-time = "2026-09-06T16:25:05.978Z" },
+ { url = "https://files.pythonhosted.org/packages/a1/f9/b6533d777be9d6ffd29dc1be0867e563e6e8cc9a220ff1b716adc317f060/numpy-2.5.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:ccbc4665079665c3cf3bab4db9f6b095370cd6437d66be549b6c2a1fd19e1958", size = 6779395, upload-time = "2026-09-06T16:25:08.599Z" },
+ { url = "https://files.pythonhosted.org/packages/73/85/735720d04ec197c5dcfacdfc9922667c7f1f5f496a279b7ba4d7c74c4cc7/numpy-2.5.3-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c76d5dde9f445058f83d0c02af00557a4db91de9a9a57c0df87d1535001d654b", size = 15681750, upload-time = "2026-09-06T16:25:11.173Z" },
+ { url = "https://files.pythonhosted.org/packages/3a/1b/3b16a9bc514a440a7a0883684111dcb1ef1aee960af2ca95da8fc775f124/numpy-2.5.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a5fa86b80fd24bcd1aff83ad23be44ea323de3f787be8f8b15d4a65621e25321", size = 16708577, upload-time = "2026-09-06T16:25:14.171Z" },
+ { url = "https://files.pythonhosted.org/packages/69/c4/386f397831b07328b639c96c5b62719346cf4baf07c68d927239752b1534/numpy-2.5.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bd4cb9ad3c7889b9b3fe0a9a9fb5d2ed26f9879bff2608d9f01aed147a20d231", size = 17042047, upload-time = "2026-09-06T16:25:17.582Z" },
+ { url = "https://files.pythonhosted.org/packages/5f/3e/a700ecbf36e85ae8328fd3b0e12eeddc22ed6358a64cb2bd913e0d195d65/numpy-2.5.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1302b90c0e52281681b2975adfe8a860cb7b12216a27b4b0b4207c44bf7bccf0", size = 18465724, upload-time = "2026-09-06T16:25:20.949Z" },
+ { url = "https://files.pythonhosted.org/packages/41/ee/38e785e88a4045f6ad1d1f2808dcdfafdca48c760260c0587bf171e29fc9/numpy-2.5.3-cp313-cp313-win32.whl", hash = "sha256:1c80eabb4035ecf4ca9cd49cde8a9fdd69a729e63e6474887d1523ade7aa277f", size = 6129003, upload-time = "2026-09-06T16:25:23.664Z" },
+ { url = "https://files.pythonhosted.org/packages/f3/ec/100f2b1794ede74a9b3d7ec6b9736927f56713414c1dfe19ab6c383494bf/numpy-2.5.3-cp313-cp313-win_amd64.whl", hash = "sha256:71cad2b2a7451ab79d8f5e71b453485b6775963d5cf794179144a7463fe6e8ec", size = 12560965, upload-time = "2026-09-06T16:25:26.602Z" },
+ { url = "https://files.pythonhosted.org/packages/80/b1/7dc825ca94c12acebbce4c37caa5e198695eb31424bc579679f32b1bb49d/numpy-2.5.3-cp313-cp313-win_arm64.whl", hash = "sha256:8e4dd766076855b5ff7ea52fa5f07ce26286726e0f8bff446b7739d02e6ea204", size = 10482343, upload-time = "2026-09-06T16:25:29.772Z" },
+ { url = "https://files.pythonhosted.org/packages/70/78/cf416f15dc29375a229d9dfebf8db6e313f291580b39fa1a568b6052bb07/numpy-2.5.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:350ba9783ce969cf9f7ce6e6a9a58e1a6e2a19ca025b7ee448c4db727706212a", size = 16998686, upload-time = "2026-09-06T16:25:33.171Z" },
+ { url = "https://files.pythonhosted.org/packages/9e/59/abcc2d8def4fd60eec7d87f92d27c13448ffd9ab14339bcc63a0d7a2fdea/numpy-2.5.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:012e66aca395d795496446e52aeeb5866312a5d4d3f27da270e5a0b43f70dc5c", size = 12013862, upload-time = "2026-09-06T16:25:36.748Z" },
+ { url = "https://files.pythonhosted.org/packages/94/75/4640d2d6e4b64a049e48425a82728a41ef4adb61332d2cba68055774878b/numpy-2.5.3-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:adc1ada2662f8a5f960b8a10d9986897e7499ef07e06d4cfe7197f8cce923c07", size = 5449793, upload-time = "2026-09-06T16:25:39.476Z" },
+ { url = "https://files.pythonhosted.org/packages/96/cd/625b57ae33d4ca560f32cc0b47b4a5922146d9beb998ddf773900d440a73/numpy-2.5.3-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:54a115e5a73b8fc44f0cebef486365a1894b5c9760685d4558b72b7c3eb846e0", size = 6785176, upload-time = "2026-09-06T16:25:42.069Z" },
+ { url = "https://files.pythonhosted.org/packages/9c/72/12918652e7912ef9751e8694c88820fcd1908e0618cb23f5f3caa6004b7b/numpy-2.5.3-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be5a8381859b6da607c84f4f7d6847725f1cf1853ef8a2c9e115b7d58bef47dc", size = 15703377, upload-time = "2026-09-06T16:25:45.135Z" },
+ { url = "https://files.pythonhosted.org/packages/45/8f/9beacf79ca7c650688ad0baa80931adb988fe6e6e5d5903c23cc3dbd70eb/numpy-2.5.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b0521d0f4aebb6e06189451025fa17a913287b13c03d5fe05c017333b654ea5b", size = 16711928, upload-time = "2026-09-06T16:25:48.461Z" },
+ { url = "https://files.pythonhosted.org/packages/09/8d/41d0a56e1ac4c87495c897a211b1368691b7237aadabec8b3b8f3a74d48f/numpy-2.5.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9deb49575e5b0b94ed72c8a64ec4d033381adc27e9060ae842971f697ba96104", size = 17059507, upload-time = "2026-09-06T16:25:51.873Z" },
+ { url = "https://files.pythonhosted.org/packages/08/1e/0dfbc5cc251d54e2af790f254d24ec38637fa97ec7d5d11de7ffed787098/numpy-2.5.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b00eefbcf0f292945c4b4dec2ae845389ef5bcdcd596e6e4328051db5b5ba694", size = 18471002, upload-time = "2026-09-06T16:25:55.233Z" },
+ { url = "https://files.pythonhosted.org/packages/b5/2c/dfa40f6991f8185c8c30ffd023dfcbb11888e823cfab9557b920f3bb7bed/numpy-2.5.3-cp314-cp314-win32.whl", hash = "sha256:c2381f82999704f818e2c987a865050e285ec3621262c66d40f5a96c8f899f8e", size = 6180485, upload-time = "2026-09-06T16:25:58.157Z" },
+ { url = "https://files.pythonhosted.org/packages/a4/73/d2c08231e4fde7e415501fd02c715d96e98599b2d8384445933944152984/numpy-2.5.3-cp314-cp314-win_amd64.whl", hash = "sha256:2c25dfa72943e4336ddb6b0ee4277b47a0c85bede0807530ec68103bf58e2c10", size = 12698179, upload-time = "2026-09-06T16:26:00.789Z" },
+ { url = "https://files.pythonhosted.org/packages/5c/e9/dcdcc9b95cf5f49815055573aee1b11cfbf5299f38a180e437ded050810f/numpy-2.5.3-cp314-cp314-win_arm64.whl", hash = "sha256:15aa985ac73a8db02db7663381aa109510449d3819d37206caed27b33a65a8a6", size = 10769383, upload-time = "2026-09-06T16:26:04.011Z" },
+ { url = "https://files.pythonhosted.org/packages/49/c4/af8bc08a7ef4e1529a7c0cf24969accce316b783999802089a581ec99272/numpy-2.5.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ac7bb1c52d445bd4f8f7f97fefe6abc3a084dc4d63df50d79b17fa2b78e89297", size = 12132668, upload-time = "2026-09-06T16:26:07.138Z" },
+ { url = "https://files.pythonhosted.org/packages/c5/ae/0f15eb56d4ec5e13c1f7ff04ff407f997d1acbadb45d3e1f2e2645a8f43c/numpy-2.5.3-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:e6ab667ba76450084eb64013762c438ea76d9d29cc676dcd6c2e9892ba37f841", size = 5568580, upload-time = "2026-09-06T16:26:09.828Z" },
+ { url = "https://files.pythonhosted.org/packages/23/fb/c72a8f25d4b6e96c354e7ab45ace3b27dc11e5d6a13b6c7d0cd6b08bf112/numpy-2.5.3-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:f7fabeb6cea87d65f3b926de33d03fb016cfdc29314c90974383b5582ae72891", size = 6882634, upload-time = "2026-09-06T16:26:12.524Z" },
+ { url = "https://files.pythonhosted.org/packages/07/a9/968c90ed2ab15060c338e8137f1215b5a60756ae07328e0a60d1c6734df4/numpy-2.5.3-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1fb6f8fb9ff0b3a69f52c66ce397b0246583e9f28616231b0e32ca49259a5fa6", size = 15748923, upload-time = "2026-09-06T16:26:15.092Z" },
+ { url = "https://files.pythonhosted.org/packages/59/08/9df04103947b95e3b6b1f2ed1a70521f325647a31b82da6a2aae3a485508/numpy-2.5.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:93e1f5447e2b1e479d7bd74701e84746b86450cff1fc368b132d195e2b8f8211", size = 16746748, upload-time = "2026-09-06T16:26:18.43Z" },
+ { url = "https://files.pythonhosted.org/packages/41/a0/14c8d5fe5b53a334aabb653deb391c0fef49558f491880ea300ed6785224/numpy-2.5.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c00abe94c1a69d75d827dcf1c025b25c8a45d230b3bcd77a9020883a1b047653", size = 17111561, upload-time = "2026-09-06T16:26:22.113Z" },
+ { url = "https://files.pythonhosted.org/packages/c4/a6/d7e96e42f01522e154c32489640f16dfc4f6181d165d05fc3bec8c2c4999/numpy-2.5.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:536f963710a4e63934d80ac0dc4f478804a83e9a84b6828018f25d09953ada33", size = 18513945, upload-time = "2026-09-06T16:26:25.401Z" },
+ { url = "https://files.pythonhosted.org/packages/25/39/3453afb7119d0449ef11c886874120ff180e2c337760e0e2d88f70f1a945/numpy-2.5.3-cp314-cp314t-win32.whl", hash = "sha256:4c8a6d2ebce6305fd82fbefca827775437147052a976ee7c94b36a0c1b52ac6c", size = 6335421, upload-time = "2026-09-06T16:26:28.175Z" },
+ { url = "https://files.pythonhosted.org/packages/99/01/22815d2b19a1a746b1d45205cffebb3fe511a18acb75fba6c88491fc9894/numpy-2.5.3-cp314-cp314t-win_amd64.whl", hash = "sha256:9a37475425b431b4d060f23b4f52cd2f3aef6bc7c654bd760adf0040eec9d435", size = 12896420, upload-time = "2026-09-06T16:26:31.265Z" },
+ { url = "https://files.pythonhosted.org/packages/fa/ee/a7cbba67eeaff038dc29ca8b98a88396c8b0cc9c89d4924f4a27a5c9150b/numpy-2.5.3-cp314-cp314t-win_arm64.whl", hash = "sha256:2d8240cb4c16fd831074aa2b2cf9fc54664d826341d61c372245b96a74a49a9a", size = 10857177, upload-time = "2026-09-06T16:26:34.167Z" },
+ { url = "https://files.pythonhosted.org/packages/45/56/78194492883ff5eec90423fe56a3a44b154da047d88a6307f629713c584f/numpy-2.5.3-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:a6391fafaba97500887132cd582abc6e19452b1ac775a47caa7b24490e152058", size = 16996531, upload-time = "2026-09-06T16:26:37.287Z" },
+ { url = "https://files.pythonhosted.org/packages/11/39/dd55c0af90bbab564b09ae3b0aa60ec5c02b900fa4f1ba23440525c8b32d/numpy-2.5.3-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:09d5a423c71ad5feb5625844ad58050e35df43871004b52ac9c0ad44a56775be", size = 12012569, upload-time = "2026-09-06T16:26:40.707Z" },
+ { url = "https://files.pythonhosted.org/packages/b6/51/04f67d32e4862b281b1cb84ceeaed3421189a84fb6fb51a391cd6d5009f7/numpy-2.5.3-cp315-cp315-macosx_14_0_arm64.whl", hash = "sha256:f9579f383d1bf9df80081e72760e84960a7fd4f88cf0c9e535a8597c9bb646f5", size = 5448498, upload-time = "2026-09-06T16:26:43.435Z" },
+ { url = "https://files.pythonhosted.org/packages/a3/c9/25b4dc0dd1344ec26c7319e84fd4e9809d2b5628f4e12decd618036e5178/numpy-2.5.3-cp315-cp315-macosx_14_0_x86_64.whl", hash = "sha256:86bff898a431c0fb71f7610b75726e75a54d47b37edc9d537f48de63bb3c0b90", size = 6783026, upload-time = "2026-09-06T16:26:46.374Z" },
+ { url = "https://files.pythonhosted.org/packages/fc/c7/29285be1e5232a6e7ee3268a33c85843f5a8ee93350c6465cddd66ebbf76/numpy-2.5.3-cp315-cp315-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1f3ed25271581281f2fccb1adcedfcde4c07362eec69189b50baf6f90e3ae159", size = 15697322, upload-time = "2026-09-06T16:26:49.415Z" },
+ { url = "https://files.pythonhosted.org/packages/55/49/bbad5335fb4996a16881f853ff3e0ba582f01720e55c89b1c06b8fc42a90/numpy-2.5.3-cp315-cp315-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ffdc76bfcae6b255dff75202c5e7feaf95b40246bc0a17944facc1fecf9f79ab", size = 16708995, upload-time = "2026-09-06T16:26:53.127Z" },
+ { url = "https://files.pythonhosted.org/packages/ef/e9/1df35483760b04a65ea44669f89dc64f30e5aca098b48ceb8b1310b0e0fe/numpy-2.5.3-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:116f96cadd935c6122e9228d676fe7ede19e741f5c8bb1c3cddbe0c51ccebea2", size = 17052508, upload-time = "2026-09-06T16:26:56.464Z" },
+ { url = "https://files.pythonhosted.org/packages/b8/99/66e54da8265cc8be8a7382bf96edce17aaa2837d6f484432025932a3caa5/numpy-2.5.3-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:09ffa5d903faeaa5c4dd05009cf81c8bab9f2cb37c548b8d39b65b4cfa7c97f7", size = 18468224, upload-time = "2026-09-06T16:26:59.966Z" },
+ { url = "https://files.pythonhosted.org/packages/01/bc/b5e90a91c115168d793dfd2ad9c69c438c2fe7a13a437e770bc5b078e732/numpy-2.5.3-cp315-cp315-win32.whl", hash = "sha256:e01c918ac3d48e18a927cf7b14a26a3e29ff2bdf2eacb976da0aecd6a43ed034", size = 6179919, upload-time = "2026-09-06T16:27:03.166Z" },
+ { url = "https://files.pythonhosted.org/packages/37/ea/780748fd3985109075514ef8fc64cd25f943e40dde13a6d59141eb268fc8/numpy-2.5.3-cp315-cp315-win_amd64.whl", hash = "sha256:e931e4f499e0dc7ef29d269a8e5b35dd722e5d14be07df6240166ea7c6532fae", size = 12697656, upload-time = "2026-09-06T16:27:06.153Z" },
+ { url = "https://files.pythonhosted.org/packages/b3/16/407be69a2a87c8cab64d95975a8977a426a29e138f07e276ec258f0fe4e5/numpy-2.5.3-cp315-cp315-win_arm64.whl", hash = "sha256:26e15e4aecd8617dfbaecb37d223e365d7b39411fba20454be2670a96aa74cb5", size = 10767601, upload-time = "2026-09-06T16:27:09.297Z" },
+ { url = "https://files.pythonhosted.org/packages/44/bf/a97ffb01e41d50a32a9177aef942a4d0e389a3daf451d04e5f38ef6afb87/numpy-2.5.3-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:6cef4bb1706dfec49243c05d921eefb4e190d41e2528b30d8035ea1f36b4c24a", size = 17090092, upload-time = "2026-09-06T16:27:12.907Z" },
+ { url = "https://files.pythonhosted.org/packages/d1/24/136c02f2c2af9a067a84d0c3aa10c99012c0476fa5066732fa4a4202557d/numpy-2.5.3-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:d1c89973648c85069c5046ad460f7b8a00218b29a2e42359ac8cc63e9ab94832", size = 12129429, upload-time = "2026-09-06T16:27:16.089Z" },
+ { url = "https://files.pythonhosted.org/packages/fe/6c/b47582d6597789bf946d5efbeb6b9e56fd8bcbd5efc6fbf51dbe1ea31eb3/numpy-2.5.3-cp315-cp315t-macosx_14_0_arm64.whl", hash = "sha256:214045a5bf00113a146ab9ee9730c44501af6723cdf1f6830932f7b5ef2e7af0", size = 5565452, upload-time = "2026-09-06T16:27:19.868Z" },
+ { url = "https://files.pythonhosted.org/packages/be/b4/ef3cc6da73774202d4deae16bb321fd8298a4e0561e3539f8c4be237d916/numpy-2.5.3-cp315-cp315t-macosx_14_0_x86_64.whl", hash = "sha256:8617bbfae4486cf99c9f899966699428d19da931d06ca94ad3da986c76e15997", size = 6876736, upload-time = "2026-09-06T16:27:22.232Z" },
+ { url = "https://files.pythonhosted.org/packages/9e/24/e3813329498596cb842703dcacac1741612ed9fb9c4e6a3e0c7e2ebbc597/numpy-2.5.3-cp315-cp315t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:595d020938c84e320bcf40ad71089e108eac0d377cd018e14a8c094f39e98d85", size = 15745777, upload-time = "2026-09-06T16:27:25.181Z" },
+ { url = "https://files.pythonhosted.org/packages/4a/9e/4e7a07fd0776dc2210cdacf2010be8665194d094defc10c419d7dea794cc/numpy-2.5.3-cp315-cp315t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f24021b9f22bc6301c37b196974a92c1c18dccedb6fef3dd252e95f2d6adbe4", size = 16746949, upload-time = "2026-09-06T16:27:28.576Z" },
+ { url = "https://files.pythonhosted.org/packages/91/db/01674c0e20335057813a00c2ebd546ed25bff9ed7914f9bced00f8c55d94/numpy-2.5.3-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:71b39d9f935b6ec0f8753e3e2afb51e3efba6f2e05b68b32a40754d24bcd4a3c", size = 17108994, upload-time = "2026-09-06T16:27:31.946Z" },
+ { url = "https://files.pythonhosted.org/packages/45/7a/584c5e71f8d378e57cac0b033891ed65c683ef90573ba4854e8c28203db0/numpy-2.5.3-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:6b05c171afb3aa07adbd20abc00aea86fe375beb0fdb9ef780ec5b7f63bab1c0", size = 18512266, upload-time = "2026-09-06T16:27:35.196Z" },
+ { url = "https://files.pythonhosted.org/packages/a1/d2/4e1014173aa3c55e6a756e0e567290743a6ab33a288460374d7ef6bcd239/numpy-2.5.3-cp315-cp315t-win32.whl", hash = "sha256:f54660b0eb6b0b9f36e7fe1cdfdff472028dd0d14acd9b9b65098efbad059469", size = 6330292, upload-time = "2026-09-06T16:27:38.149Z" },
+ { url = "https://files.pythonhosted.org/packages/6c/b0/ff5658a58199b7bcaad87bf260eef6713d9d42cca4e028f935b4fc5fbac6/numpy-2.5.3-cp315-cp315t-win_amd64.whl", hash = "sha256:1aad64d99730d013cfc6debafed22783b4fc5a7f4b8bc744d2d8cf7dcc880551", size = 12884918, upload-time = "2026-09-06T16:27:40.965Z" },
+ { url = "https://files.pythonhosted.org/packages/fb/0b/b12a2df5d1b774bd9007a6fdff9381145b6223d37f11afc9c37ab0efd9a1/numpy-2.5.3-cp315-cp315t-win_arm64.whl", hash = "sha256:befa1ae5bd6030b3f512b43ff3fa5290bbed6b84411a44244b14adf835f5b89d", size = 10850807, upload-time = "2026-09-06T16:27:43.868Z" },
+]
+
+[[package]]
+name = "packaging"
+version = "26.3"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/7d/fa/3944b40b07da9ce895c0e6303a5ab7d53da063554f534556b134a54d6093/packaging-26.3.tar.gz", hash = "sha256:94edc256424af38762eb31306eed28beb9f0efc50a8837492c9d6fd6004aed79", size = 313412, upload-time = "2026-08-04T18:15:28.737Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/63/34/ba1c580383c9eada3711951fef0795c80b829a078d72188184bcab9dd527/packaging-26.3-py3-none-any.whl", hash = "sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c", size = 129956, upload-time = "2026-08-04T18:15:27.159Z" },
+]
+
+[[package]]
+name = "pluggy"
+version = "1.6.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
+]
+
+[[package]]
+name = "pygments"
+version = "2.21.0"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/49/2e/ced460408999b33da6b31b0021b0f37d329e202d4169aeb164493778f25b/pygments-2.21.0.tar.gz", hash = "sha256:610ca751c9bc2492b38eb9a38a7fbc93edbbb2d7182edaf34e66ae493dee5c8c", size = 5005329, upload-time = "2026-08-17T08:02:48.824Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/71/46/17f022dd3e953bf20a04a028a21ec746d942f8d2af30fa0f124fa0e6a684/pygments-2.21.0-py3-none-any.whl", hash = "sha256:2363c69b61c4a97c838da3b130dcd6468f4848992b21a82f2a63ec34377137d9", size = 1250147, upload-time = "2026-08-17T08:02:44.912Z" },
+]
+
+[[package]]
+name = "pytest"
+version = "9.1.1"
+source = { registry = "https://pypi.org/simple" }
+dependencies = [
+ { name = "colorama", marker = "sys_platform == 'win32'" },
+ { name = "iniconfig" },
+ { name = "packaging" },
+ { name = "pluggy" },
+ { name = "pygments" },
+]
+sdist = { url = "https://files.pythonhosted.org/packages/e4/47/b9efed96c114afcfa3c9d3fe98a76a1d14c74a9e266d397cf6eb64be5e01/pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313", size = 1636369, upload-time = "2026-06-19T10:58:32.857Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c", size = 386536, upload-time = "2026-06-19T10:58:31.347Z" },
+]
+
+[[package]]
+name = "quokka-sharp"
+version = "2.7.5"
+source = { editable = "quokka_sharp" }
+dependencies = [
+ { name = "numpy" },
+]
+
+[package.metadata]
+requires-dist = [{ name = "numpy", specifier = ">=2.2" }]
+
+[[package]]
+name = "quokka-sharp-root"
+version = "0.1.0"
+source = { virtual = "." }
+dependencies = [
+ { name = "quokka-sharp" },
+]
+
+[package.dev-dependencies]
+dev = [
+ { name = "pytest" },
+ { name = "ruff" },
+]
+
+[package.metadata]
+requires-dist = [{ name = "quokka-sharp", editable = "quokka_sharp" }]
+
+[package.metadata.requires-dev]
+dev = [
+ { name = "pytest", specifier = ">=9.1.1" },
+ { name = "ruff", specifier = ">=0.16.9" },
+]
+
+[[package]]
+name = "ruff"
+version = "0.16.9"
+source = { registry = "https://pypi.org/simple" }
+sdist = { url = "https://files.pythonhosted.org/packages/96/bf/c935ca98e73fe8ce65b87ef08a280c0c1e85295d569228c15e87d8fdfaf1/ruff-0.16.9.tar.gz", hash = "sha256:12b625c6cfba78d285d9f48eda5f053374f1e53cb10ef17342a383750db99161", size = 4948764, upload-time = "2026-09-24T20:37:49.416Z" }
+wheels = [
+ { url = "https://files.pythonhosted.org/packages/0d/26/df51322b52ee1ada7eff2d071ea09d11d5c2d1dcc9f02594f5785c4d1635/ruff-0.16.9-py3-none-linux_armv6l.whl", hash = "sha256:95e6f022090368ab3b824c36276839c53b2adf1a3f4c09fefc33dfc400f6da96", size = 10082922, upload-time = "2026-09-24T20:37:13.045Z" },
+ { url = "https://files.pythonhosted.org/packages/a5/27/7bf51f5a7aa375e9f339280a303aab44ca75dc1525f1cdc5991761685b0f/ruff-0.16.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:a5f27be168556594a86d2f415db0cf43f5291917849318f873c7e2791f7a8c67", size = 10236360, upload-time = "2026-09-24T20:37:16.049Z" },
+ { url = "https://files.pythonhosted.org/packages/b6/63/09659283f92f02dff45809da194a70da2f688d87c55d8875c4fae3536072/ruff-0.16.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:1632eb1d6197f33bd00b1acbc5b71009e89a8895c158e2d2b03a834fac964ab6", size = 9892940, upload-time = "2026-09-24T20:37:17.957Z" },
+ { url = "https://files.pythonhosted.org/packages/24/58/98de1b72ec172f5f8f1731236fe21585b3998bf7dfe9fcc44ae9ba626012/ruff-0.16.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b3f951b14d865d5952c89d40a5ca07e87abe24fa5453299878411e127748fb1c", size = 10032114, upload-time = "2026-09-24T20:37:19.942Z" },
+ { url = "https://files.pythonhosted.org/packages/c8/7d/f1e17c54ab59d4bad1dce8ee3e22a7a1d0ef4745240decacdcf3832b5bb2/ruff-0.16.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:447fc07e1573afff7cb02803462b12b6c8ece7cf10e2cd78565fa6d7a1c0bf8d", size = 9910227, upload-time = "2026-09-24T20:37:21.872Z" },
+ { url = "https://files.pythonhosted.org/packages/34/19/436f647a65075bbd3bab2668b3bdaa5120559b294694018cdcefabbbf30b/ruff-0.16.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8a3e039a6a40ed976c491722b60e0ae4a4aa1a86057f540ee7a37a5d19ae9120", size = 10547484, upload-time = "2026-09-24T20:37:24.229Z" },
+ { url = "https://files.pythonhosted.org/packages/03/59/38430a6bc2f6d8095447ac39625cf8b6e9344a47e6c26225c8ba1bff3ffb/ruff-0.16.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4684dded7db60aa57cb118fa158630f5feade4af5782903b6053484bdf9bd129", size = 11412367, upload-time = "2026-09-24T20:37:26.307Z" },
+ { url = "https://files.pythonhosted.org/packages/c8/bd/bbb6d7fc7f208c8b8c50dd5dc8206e4cfdb1e7a8fb852606a3adf370c880/ruff-0.16.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d29c934357e45642fda2f34c0b1f4025b4a6c01e15e4bf0016879d60078a142c", size = 10869787, upload-time = "2026-09-24T20:37:28.35Z" },
+ { url = "https://files.pythonhosted.org/packages/bc/b8/9c543074918061abbefc3bd139bee22de35abedb00dde0f2d27288838962/ruff-0.16.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a21713e629d3e5bdb2f5c2def1cc7f04f47fa8e1a7eb0571b4a28e1da64bc728", size = 10406494, upload-time = "2026-09-24T20:37:30.624Z" },
+ { url = "https://files.pythonhosted.org/packages/35/7a/5a8851bd146e7ccf8fd4b003f6c75c11f8fbdb0e60673b45097986b6bf41/ruff-0.16.9-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:7baa24ef5fc8e77aa93879e1d3f43754a01ae488e869f1ae30cf431afd4d2452", size = 10590083, upload-time = "2026-09-24T20:37:32.439Z" },
+ { url = "https://files.pythonhosted.org/packages/87/f0/4c3467188f23f806960b46fa76575a7cd0514c9ba90562650b71efc96980/ruff-0.16.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a41aac6230aadfaa133bdfa1614488531ffa3e0837567ae04c0da2058a9c0f9e", size = 10119151, upload-time = "2026-09-24T20:37:34.581Z" },
+ { url = "https://files.pythonhosted.org/packages/15/34/5a4def5adea572ce6aea0bb64f21f928ee317b80e0db747d7979b01d7261/ruff-0.16.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:c2529fb5896d49115b0e9aa8f887490b34bbe76baf879ec2264ac59406869ce7", size = 9911544, upload-time = "2026-09-24T20:37:36.796Z" },
+ { url = "https://files.pythonhosted.org/packages/62/5d/d15ebea7499eef9373318c0ee6ca127832927c6529731f6d48e18dce7ca9/ruff-0.16.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:41e3870277694177429b56406d65dfbdb2c2802c52b715edaf6a0b829c69d4ee", size = 10269884, upload-time = "2026-09-24T20:37:38.857Z" },
+ { url = "https://files.pythonhosted.org/packages/d1/56/c5d3cd119ded7a3c7aba0e961b69cb3df701c91662c98ad694467d060ce1/ruff-0.16.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:8adbe4e58af167f767d7b2ba5e83c42e878350796cf78c2f5e14ab9903a92588", size = 10749366, upload-time = "2026-09-24T20:37:41.042Z" },
+ { url = "https://files.pythonhosted.org/packages/ac/fe/734ec7527029ac757ecf821f143c9f3fcf69149c21f53a044a899b430f5a/ruff-0.16.9-py3-none-win32.whl", hash = "sha256:0e1dbc2073624dee6618d41d0098690a7244654af746704b64759e12b6b6b385", size = 10152355, upload-time = "2026-09-24T20:37:43.025Z" },
+ { url = "https://files.pythonhosted.org/packages/14/21/26e4643629b3ebb44f0a06f9c9a53058d63d989415f63a9a3c28e2ee7f22/ruff-0.16.9-py3-none-win_amd64.whl", hash = "sha256:6bd40fec8cd4c8a3d4dd589bd8ad4e6320c13c29234159bfd959a40d529d597b", size = 10592965, upload-time = "2026-09-24T20:37:44.944Z" },
+ { url = "https://files.pythonhosted.org/packages/51/60/5fb1a39dbb5ae314d5f59bc7348a63c1d5c20f3cd83914c4b5cb0be31d2d/ruff-0.16.9-py3-none-win_arm64.whl", hash = "sha256:ed1a252039200f57a59eebc063b54beabea67bfbaaca0eeaa7f54b5fbcda2284", size = 10458649, upload-time = "2026-09-24T20:37:46.882Z" },
+]