From ca3862f335679c6fb5acdfcb360c47921b71268b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20Milie=C5=A1ka?= Date: Sun, 27 Sep 2026 18:12:23 +0200 Subject: [PATCH 1/4] Setup a clean local development workflow (#1) * chore: add local solver installer * chore: setup uv dev workflow & enable synthesis tests * chore: setup ruff and format everything * chore: make pyright/pylance work * chore: add @staticmethod and fix a typing * chore: make sure test fails when solvers are missing * chore: allow to run scripts from the root --- .editorconfig | 5 + .gitignore | 3 +- .python-version | 1 + README.md | 258 ++++--- pyproject.toml | 21 + quokka_sharp/comput2cnf_py_codegen.py | 191 +++--- .../comput2cnf_unweighted_py_codegen.py | 123 ++-- quokka_sharp/pauli2cnf_py_codegen.py | 456 +++++++------ .../pauli2cnf_unweighted_py_codegen.py | 203 +++--- quokka_sharp/quokka_sharp/__init__.py | 1 - quokka_sharp/quokka_sharp/config.py | 11 +- quokka_sharp/quokka_sharp/encoding/cnf.py | 558 ++++++++------- .../quokka_sharp/encoding/qasm_parser.py | 337 +++++---- .../quokka_sharp/encoding/utils/rlist.py | 13 +- quokka_sharp/quokka_sharp/eq.py | 101 +-- quokka_sharp/quokka_sharp/functionalities.py | 176 ++--- quokka_sharp/quokka_sharp/sim.py | 72 +- quokka_sharp/quokka_sharp/syn.py | 212 ++++-- quokka_sharp/quokka_sharp/utils/__init__.py | 2 +- quokka_sharp/quokka_sharp/utils/timeout.py | 8 +- quokka_sharp/quokka_sharp/utils/utils.py | 69 +- quokka_sharp/quokka_sharp/veri.py | 2 +- quokka_sharp/setup.py | 39 +- scripts/setup-solvers.sh | 168 +++++ tests/example_quokka_sharp.py | 103 +-- tests/test_quokka_sharp.py | 641 ++++++++++++------ tmp/.gitkeep | 0 uv.lock | 185 +++++ 28 files changed, 2504 insertions(+), 1455 deletions(-) create mode 100644 .editorconfig create mode 100644 .python-version create mode 100644 pyproject.toml create mode 100755 scripts/setup-solvers.sh create mode 100644 tmp/.gitkeep create mode 100644 uv.lock diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..39af06c06 --- /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/.gitignore b/.gitignore index 0a1ecc290..740fabe72 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 000000000..24ee5b1be --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.13 diff --git a/README.md b/README.md index 9b2d5b6a3..658fb3641 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 000000000..95945b397 --- /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 6b9fa332d..fa40a0977 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 4df6a0d9c..4b5e01681 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 ff06c5f44..a452596d6 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 f33eda15b..4ccd3e26d 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 51450bf4c..90104cded 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 0a3be9ea2..7c402509b 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 44a6ec5fd..45204fd41 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/qasm_parser.py b/quokka_sharp/quokka_sharp/encoding/qasm_parser.py index ae98fd009..0ce8cc735 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 e1a085934..4bcfae947 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 ef3b20c62..dd281d5a7 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 a891a4cd4..413199d8a 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 e7c607d48..c8b5277da 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 a53c81086..cccc83083 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 d83dc052b..99a9b15c7 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 26004a371..c66380447 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 536f8b9a6..4d4d10293 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 315e7bf5b..f87f89c4c 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 620700a28..9e58332b4 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 000000000..747506215 --- /dev/null +++ b/scripts/setup-solvers.sh @@ -0,0 +1,168 @@ +#!/usr/bin/env bash +# Install GPMC, Ganak, and d4 maxT locally, without messing with the system itself. +# Files installed by this script stay within this repository. +# GPMC and maxT use local libraries plus the OS's standard C library +# (glibc on Linux); they are 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 + 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 + mkdir -p "$bin_dir" "$src_dir" "$local_dir/cache" "$local_dir/tmp" + export XDG_CACHE_HOME="$local_dir/cache" TMPDIR="$local_dir/tmp" +} + +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" CONDA_PKGS_DIRS="$local_dir/mamba/pkgs" + local extra_packages=() candidate + if [[ "$platform" == osx-* ]]; then extra_packages+=("binutils_impl_$platform"); fi + "$bin_dir/micromamba" create -y -p "$env_dir" -c conda-forge --override-channels \ + 'cmake>=4.1' make \ + cxx-compiler gmp mpfr zlib boost-cpp "${extra_packages[@]}" + for candidate in "$env_dir/bin/"*-conda-linux-gnu-ar "$env_dir/bin/"*-apple-darwin*-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 + } +} + +download_verified() { + curl -fsSL --retry 3 "$1" -o "$2" + check_sha "$2" "$3" +} + +check_sha() { + local actual + if command -v sha256sum >/dev/null; then + actual=$(sha256sum "$1") + actual=${actual%% *} + else + actual=$(shasum -a 256 "$1") + actual=${actual%% *} + fi + [[ "$actual" == "$2" ]] || { + echo "Checksum mismatch: $1" >&2 + exit 1 + } +} + +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" +} + +extract() { + mkdir -p "$2" + curl -fsSL --retry 3 "$1" | tar -xz -C "$2" --strip-components=1 +} + +run() { "$bin_dir/micromamba" run -p "$env_dir" "$@"; } + +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" + # Upstream uses unbounded make -j; cap its builds at two jobs. + sed -i.bak 's/make -j/make -j2/g' "$d4_src/build.sh" "$d4_src/3rdParty/bipe/build.sh" + rm "$d4_src/build.sh.bak" "$d4_src/3rdParty/bipe/build.sh.bak" + (cd "$d4_src" && run bash ./build.sh) + 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" +} + +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" +} + +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 b61d527c7..d93d55d2b 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 6e982dbc5..8fe0634a2 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 000000000..e69de29bb diff --git a/uv.lock b/uv.lock new file mode 100644 index 000000000..cbe54c7fc --- /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" }, +] From 53c85cd14af1f9a56db113b788714bd48eeaf3bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20Milie=C5=A1ka?= Date: Mon, 28 Sep 2026 22:55:30 +0200 Subject: [PATCH 2/4] Fix solver setup for macOS (#2) --- .github/workflows/test.yml | 35 +++++ scripts/setup-solvers.sh | 275 +++++++++++++++++++++++++++++++------ 2 files changed, 267 insertions(+), 43 deletions(-) create mode 100644 .github/workflows/test.yml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 000000000..7eec200cc --- /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/scripts/setup-solvers.sh b/scripts/setup-solvers.sh index 747506215..9eb7d1118 100755 --- a/scripts/setup-solvers.sh +++ b/scripts/setup-solvers.sh @@ -1,8 +1,7 @@ #!/usr/bin/env bash -# Install GPMC, Ganak, and d4 maxT locally, without messing with the system itself. -# Files installed by this script stay within this repository. -# GPMC and maxT use local libraries plus the OS's standard C library -# (glibc on Linux); they are not fully static. +# 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) @@ -54,6 +53,7 @@ main() { build_gpmc build_d4 install_ganak + verify_install echo "Installed GPMC, Ganak, and maxT in $bin_dir" } @@ -72,91 +72,280 @@ prepare_install() { echo "Solver directory already exists: $local_dir (remove it to reinstall)" >&2 return 1 fi - mkdir -p "$bin_dir" "$src_dir" "$local_dir/cache" "$local_dir/tmp" - export XDG_CACHE_HOME="$local_dir/cache" TMPDIR="$local_dir/tmp" + + 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" + + 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" CONDA_PKGS_DIRS="$local_dir/mamba/pkgs" - local extra_packages=() candidate - if [[ "$platform" == osx-* ]]; then extra_packages+=("binutils_impl_$platform"); fi - "$bin_dir/micromamba" create -y -p "$env_dir" -c conda-forge --override-channels \ - 'cmake>=4.1' make \ - cxx-compiler gmp mpfr zlib boost-cpp "${extra_packages[@]}" - for candidate in "$env_dir/bin/"*-conda-linux-gnu-ar "$env_dir/bin/"*-apple-darwin*-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 - } + + 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 "$1" -o "$2" + 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") - actual=${actual%% *} else actual=$(shasum -a 256 "$1") - actual=${actual%% *} fi - [[ "$actual" == "$2" ]] || { + + [[ "${actual%% *}" == "$2" ]] || { echo "Checksum mismatch: $1" >&2 exit 1 } } -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" +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 + curl -fsSL --retry 3 "$1" | + tar -xz -C "$2" --strip-components=1 } -run() { "$bin_dir/micromamba" run -p "$env_dir" "$@"; } +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" - # Upstream uses unbounded make -j; cap its builds at two jobs. - sed -i.bak 's/make -j/make -j2/g' "$d4_src/build.sh" "$d4_src/3rdParty/bipe/build.sh" - rm "$d4_src/build.sh.bak" "$d4_src/3rdParty/bipe/build.sh.bak" - (cd "$d4_src" && run bash ./build.sh) - 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" + + 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" + + 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 From a9bf926a481ea61d7ad638dbb775987dee90abec Mon Sep 17 00:00:00 2001 From: gczapska Date: Fri, 2 Oct 2026 12:29:09 +0200 Subject: [PATCH 3/4] Add new rules in Pauli encoding --- .../quokka_sharp/encoding/pauli2cnf.py | 127 +++++++++++++----- 1 file changed, 94 insertions(+), 33 deletions(-) diff --git a/quokka_sharp/quokka_sharp/encoding/pauli2cnf.py b/quokka_sharp/quokka_sharp/encoding/pauli2cnf.py index 080955fd2..b0ae07eb5 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 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), From d04b1d5e9dccfa36e5511b124dcef91b7ec690f1 Mon Sep 17 00:00:00 2001 From: Asmita Joshi <117025497+AsmitaJAJ@users.noreply.github.com> Date: Sat, 3 Oct 2026 14:43:35 +0200 Subject: [PATCH 4/4] feat: more new rules to prune search space --- .../quokka_sharp/encoding/pauli2cnf.py | 56 ++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/quokka_sharp/quokka_sharp/encoding/pauli2cnf.py b/quokka_sharp/quokka_sharp/encoding/pauli2cnf.py index b0ae07eb5..78ed23322 100644 --- a/quokka_sharp/quokka_sharp/encoding/pauli2cnf.py +++ b/quokka_sharp/quokka_sharp/encoding/pauli2cnf.py @@ -1153,7 +1153,7 @@ def integer_only(lst): ) ]) - # Existing 3-layer T/CX pruning + # 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), @@ -1161,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